diff --git a/addons/api/addon/adapters/application.js b/addons/api/addon/adapters/application.js index 182fa3493..09b772cb7 100644 --- a/addons/api/addon/adapters/application.js +++ b/addons/api/addon/adapters/application.js @@ -1,6 +1,7 @@ import RESTAdapter from '@ember-data/adapter/rest'; +import AdapterBuildURLMixin from '../mixins/adapter-build-url'; import config from 'ember-get-config'; -import { get } from '@ember/object'; +import { get, getWithDefault } from '@ember/object'; import { InvalidError } from '@ember-data/adapter/error'; import { dasherize } from '@ember/string'; import { pluralize } from 'ember-inflector'; @@ -36,7 +37,9 @@ function prenormalizeArrayResponse(response) { return payloadIsBlank(response) ? {items: []} : response; } -export default class ApplicationAdapter extends RESTAdapter { +export default class ApplicationAdapter extends RESTAdapter.extend( + AdapterBuildURLMixin +) { // =attributes /** @@ -55,6 +58,60 @@ export default class ApplicationAdapter extends RESTAdapter { // =methods + /** + * Prepends a scope to the URL prefix. Precedence is given to the ID passed + * via `adapterOptions.scopeID`. If this value isn't found in + * `adapterOptions`, we look to the `scope.scope_id` field on the snapshot, + * which most resources have. + * + * Note: scopes themselves never receive a scope URL prefix. + * + * @override + * @param {string} path + * @param {string} parentURL + * @param {string} modelName + * @param {string} id + * @param {object} snapshot + * @return {string} + */ + urlPrefix(path, parentURL, modelName, id, snapshot) { + const prefix = super.urlPrefix(...arguments); + const isScope = modelName === 'scope'; + let scopePath = ''; + let scopeID = ''; + if (snapshot) { + // Not all snapshots have `attr` (such as array snapshots), + // so we do this sort of ugly check. + if (snapshot.attr) { + const parentScope = snapshot.attr('scope'); + if (parentScope) scopeID = parentScope.attr('scope_id'); + } + // Attempt to get scopeID from adapterOptions and fallback on its current + // value if it wasn't passed through options. + scopeID = getWithDefault(snapshot, 'adapterOptions.scopeID', scopeID); + } + // Only non-scope resources need a scope path, since scope resources + // aren't technically "scoped" the same way. + // Ensure a slash is added between prefix + scope path if needed. + if (!isScope && scopeID && prefix.charAt(prefix.length - 1) !== '/') { + scopePath = `/scopes/${scopeID}`; + } + return `${prefix}${scopePath}`; + } + + /** + * Appends a custom method after a colon, if a method is passed via the + * snapshot's `adapterOptions.method` field. + * @param {string} modelName + * @param {string} id + * @param {object} snapshot + * @return {string} + */ + urlSuffix(modelName, id, snapshot={}) { + const method = getWithDefault(snapshot, 'adapterOptions.method', ''); + return method ? `:${method}` : ''; + } + /** * Transforms the type to a dasherized string used in our API paths. * diff --git a/addons/api/addon/adapters/auth-method.js b/addons/api/addon/adapters/auth-method.js deleted file mode 100644 index 2da687841..000000000 --- a/addons/api/addon/adapters/auth-method.js +++ /dev/null @@ -1,16 +0,0 @@ -import ApplicationAdapter from './application'; -import ScopeAdapterMixin from '../mixins/scope-adapter'; - -export default class AuthMethodAdapter extends ApplicationAdapter.extend( - ScopeAdapterMixin -) { - - // =attributes - - /** - * Auth method is scoped to org only. - * @type {boolean} - */ - includeProject = false; - -} diff --git a/addons/api/addon/adapters/group.js b/addons/api/addon/adapters/group.js deleted file mode 100644 index b077634f4..000000000 --- a/addons/api/addon/adapters/group.js +++ /dev/null @@ -1,14 +0,0 @@ -import ApplicationAdapter from './application'; -import ScopeAdapterMixin from '../mixins/scope-adapter'; - -export default class GroupAdapter extends ApplicationAdapter.extend( - ScopeAdapterMixin -) { - // =attributes - - /** - * Group is scoped to org only, for now. - * @type {boolean} - */ - includeProject = false; -} diff --git a/addons/api/addon/adapters/host-catalog.js b/addons/api/addon/adapters/host-catalog.js deleted file mode 100644 index 79d4b9444..000000000 --- a/addons/api/addon/adapters/host-catalog.js +++ /dev/null @@ -1,6 +0,0 @@ -import ApplicationAdapter from './application'; -import ScopeAdapterMixin from '../mixins/scope-adapter'; - -export default class HostCatalogAdapter extends ApplicationAdapter.extend( - ScopeAdapterMixin -) { } diff --git a/addons/api/addon/adapters/project.js b/addons/api/addon/adapters/project.js deleted file mode 100644 index a3a02d3e7..000000000 --- a/addons/api/addon/adapters/project.js +++ /dev/null @@ -1,14 +0,0 @@ -import ApplicationAdapter from './application'; -import ScopeAdapterMixin from '../mixins/scope-adapter'; - -export default class ProjectAdapter extends ApplicationAdapter.extend( - ScopeAdapterMixin -) { - // =attributes - - /** - * Project is scoped to org only, technically. - * @type {boolean} - */ - includeProject = false; -} diff --git a/addons/api/addon/adapters/role.js b/addons/api/addon/adapters/role.js deleted file mode 100644 index c9b0c13b0..000000000 --- a/addons/api/addon/adapters/role.js +++ /dev/null @@ -1,14 +0,0 @@ -import ApplicationAdapter from './application'; -import ScopeAdapterMixin from '../mixins/scope-adapter'; - -export default class RoleAdapter extends ApplicationAdapter.extend( - ScopeAdapterMixin -) { - // =attributes - - /** - * Role is scoped to org only, for now. - * @type {boolean} - */ - includeProject = false; -} diff --git a/addons/api/addon/adapters/scope.js b/addons/api/addon/adapters/scope.js new file mode 100644 index 000000000..6d4549844 --- /dev/null +++ b/addons/api/addon/adapters/scope.js @@ -0,0 +1,27 @@ +import ApplicationAdapter from './application'; +import { serializeIntoHash } from '@ember-data/adapter/-private'; + +export default class ScopeAdapter extends ApplicationAdapter { + + /** + * Identical to default method except that we pass a query parameter + * indicating the parent scope. + * @override + * @param {Store} store + * @param {Model} type + * @param {Snapshot} snapshot + * @return {Promise} promise + */ + createRecord(store, type, snapshot) { + const parentScopeID = snapshot.attr('scope').attr('scope_id'); + let url = + this.buildURL(type.modelName, null, snapshot, 'createRecord'); + // TODO this should be generalized into `buildURL`, but currently Ember Data + // doesn't provide a way to manage query parameters. They are passed on + // to the underlying ajax/fetch service. + url = `${url}?scope_id=${parentScopeID}`; + const data = serializeIntoHash(store, type, snapshot); + return this.ajax(url, 'POST', { data }); + } + +} diff --git a/addons/api/addon/adapters/user.js b/addons/api/addon/adapters/user.js deleted file mode 100644 index b32743c79..000000000 --- a/addons/api/addon/adapters/user.js +++ /dev/null @@ -1,14 +0,0 @@ -import ApplicationAdapter from './application'; -import ScopeAdapterMixin from '../mixins/scope-adapter'; - -export default class UserAdapter extends ApplicationAdapter.extend( - ScopeAdapterMixin -) { - // =attributes - - /** - * Project is scoped to org only, technically. - * @type {boolean} - */ - includeProject = false; -} diff --git a/addons/api/addon/generated/models/project.js b/addons/api/addon/generated/models/project.js deleted file mode 100644 index abfe583d4..000000000 --- a/addons/api/addon/generated/models/project.js +++ /dev/null @@ -1,36 +0,0 @@ -import BaseModel from '../../models/base'; -import { attr } from '@ember-data/model'; - -/** - * Project contains all fields related to a Project resource - */ -export default class GeneratedProjectModel extends BaseModel { - // =attributes - - @attr('string', { - description: 'Optional name for identification purposes', - }) - name; - - @attr('string', { - description: 'Optional user-set description for identification purposes', - }) - description; - - @attr('date', { - description: 'The time this resource was created\nOutput only.', - readOnly: true, - }) - created_time; - - @attr('date', { - description: 'The time this resource was last updated\nOutput only.', - readOnly: true, - }) - updated_time; - - @attr('boolean', { - description: 'Whether the resource is disabled', - }) - disabled; -} diff --git a/addons/api/addon/generated/models/org.js b/addons/api/addon/generated/models/scope.js similarity index 81% rename from addons/api/addon/generated/models/org.js rename to addons/api/addon/generated/models/scope.js index 64cea0154..a7448b034 100644 --- a/addons/api/addon/generated/models/org.js +++ b/addons/api/addon/generated/models/scope.js @@ -4,10 +4,14 @@ import { attr } from '@ember-data/model'; /** * */ -export default class GeneratedOrgModel extends BaseModel { +export default class GeneratedScopeModel extends BaseModel { // =attributes + @attr('string', { + description: 'The type of the resource' + }) type; + @attr('string', { description: 'Optional name for identification purposes', }) diff --git a/addons/api/addon/mixins/adapter-build-url.js b/addons/api/addon/mixins/adapter-build-url.js new file mode 100644 index 000000000..264364d0d --- /dev/null +++ b/addons/api/addon/mixins/adapter-build-url.js @@ -0,0 +1,153 @@ +import Mixin from '@ember/object/mixin'; +import { get } from '@ember/object'; + +/** + * Overrides URL-building methods in an adpater for greater flexibility. + * Specifically enhances calls to `urlPrefix` with `modelName`, `id`, and + * `snapshot`, which aren't normally passed. And adds support for a + * `urlSuffix` method, the return value of which is appended to the end of + * a generated URL. + * + * While this mixin provides an enhanced API, it doesn't actually change the + * way URLs are generated by default. Override methods in the adapter that + * uses this mixin to alter generated URLs. + */ +export default Mixin.create({ + + // =methods + + /** + * This override behaves identically to the default method by default. + * It exists only to document the additional paramters to the method. + * @override + * @param {string} path + * @param {string} parentURL + * @param {string} modelName + * @param {string} id + * @param {object} snapshot + * @return {string} urlPrefix + */ + urlPrefix(/* path, parentURL, modelName, id, snapshot */) { + // What's this super syntax? + // Ember mixins are still based on the older Ember Object, rather than + // native ES classes. Thus we have to call super in this strange way. + // https://blog.emberjs.com/2019/01/26/emberjs-native-class-update-2019-edition.html + return this._super(...arguments); + }, + + /** + * Generates a string to be appended to a URL. Empty string by default. + * @param {string} modelName + * @param {string} id + * @param {?object} snapshot + * @return {string} + */ + urlSuffix(/* modelName, id, snapshot */) { + return ''; + }, + + /** + * Overrides the default `_buildURL` with a nearly identical version. + * This method simply passes arguments into the `urlPrefix` method for + * convenience, where by default it receives no arguments. + * + * Adds a suffix, generated by the `urlSuffix` method, to the URL. + * @override + * @param {String} modelName + * @param {String} id + * @return {String} url + */ + _buildURL(modelName, id, snapshot) { + let path; + let url = []; + const host = get(this, 'host'); + const prefix = this.urlPrefix(null, null, modelName, id, snapshot); + const suffix = this.urlSuffix(modelName, id, snapshot); + if (modelName) { + path = this.pathForType(modelName); + if (path) url.push(path); + } + if (id) url.push(encodeURIComponent(id)); + if (prefix) url.unshift(prefix); + url = url.join('/'); + if (!host && url && url.charAt(0) !== '/') url = '/' + url; + return `${url}${suffix}`; + }, + + /** + * Overrides the default method simply to pass an additional argument + * `snapshot` to the `_buildURL` method. + * @override + * @param {string} id + * @param {string} modelName + * @param {object} snapshot + * @return {string} + */ + urlForFindRecord(id, modelName, snapshot) { + return this._buildURL(modelName, id, snapshot); + }, + + /** + * Overrides the default method simply to pass an additional argument + * `snapshot` to the `_buildURL` method. + * @override + * @param {string} modelName + * @param {object} snapshot + * @return {string} + */ + urlForFindAll(modelName, snapshot) { + return this._buildURL(modelName, null, snapshot); + }, + + /** + * Overrides the default method simply to pass an additional argument + * `snapshot` to the `_buildURL` method. + * @override + * @param {string} id + * @param {string} modelName + * @param {object} snapshot + * @return {string} + */ + urlForFindBelongsTo(id, modelName, snapshot) { + return this._buildURL(modelName, id, snapshot); + }, + + /** + * Overrides the default method simply to pass an additional argument + * `snapshot` to the `_buildURL` method. + * @override + * @param {string} modelName + * @param {object} snapshot + * @return {string} + */ + urlForCreateRecord(modelName, snapshot) { + return this._buildURL(modelName, null, snapshot); + }, + + /** + * Overrides the default method simply to pass an additional argument + * `snapshot` to the `_buildURL` method. + * @override + * @param {string} id + * @param {string} modelName + * @param {object} snapshot + * @return {string} + */ + urlForUpdateRecord(id, modelName, snapshot) { + return this._buildURL(modelName, id, snapshot); + }, + + /** + * Overrides the default method simply to pass an additional argument + * `snapshot` to the `_buildURL` method. + * @override + * @param {string} id + * @param {string} modelName + * @param {object} snapshot + * @return {string} + */ + urlForDeleteRecord(id, modelName, snapshot) { + return this._buildURL(modelName, id, snapshot); + } + +}); diff --git a/addons/api/addon/mixins/scope-adapter.js b/addons/api/addon/mixins/scope-adapter.js deleted file mode 100644 index 705e5f1c0..000000000 --- a/addons/api/addon/mixins/scope-adapter.js +++ /dev/null @@ -1,53 +0,0 @@ -import Mixin from '@ember/object/mixin'; -import { inject as service } from '@ember/service'; - -/** - * Add this mixin to resource adapters which should be accessed via an - * org/project-scoped URL. This mixin appends a scope path based on the scope - * reported by `ScopeService`. - */ -export default Mixin.create({ - // =services - - scope: service(), - - // =properties - - /** - * If true and the current scope has a project, will include the project in - * the resource path. Set to false for resources that are scoped exclusively - * to org. - * @type {boolean} - */ - includeProject: true, - - // =methods - - /** - * Appends a scope path to the URL prefix, if a scope is set. - * @override - * @return {string} - */ - urlPrefix() { - const prefix = this._super(...arguments); - let scopePath = this.scopePath; - // Ensure a slash is added between prefix + scope path if needed. - if (scopePath && prefix.charAt(prefix.length - 1) !== '/') { - scopePath = `/${scopePath}`; - } - return `${prefix}${scopePath}`; - }, - - /** - * Generates a scope path for the current scope as reported by `ScopeService`. - * If no scope is set, returns empty string. - * @return {string} - */ - get scopePath() { - const { org, project } = this.scope; - const scopePath = []; - if (org) scopePath.push('orgs', org.id); - if (project && this.includeProject) scopePath.push('projects', project.id); - return scopePath.join('/'); - }, -}); diff --git a/addons/api/addon/models/base.js b/addons/api/addon/models/base.js index a680bce7a..cfbcce14b 100644 --- a/addons/api/addon/models/base.js +++ b/addons/api/addon/models/base.js @@ -1,9 +1,63 @@ import Model from '@ember-data/model'; +import { fragment } from 'ember-data-model-fragments/attributes'; import { computed } from '@ember/object'; +import { get } from '@ember/object'; +/** + * Base model class provides common functionality for all models. + * + * One of the most interesting features of most resources is the + * `scope` field, which arrives from the API as an embedded JSON fragment. + * It contains information such as the parent scope ID and type. This scope ID + * is then used when generation scoped resource URLs via the + * `adapterOptions.scopeID` option (see `save` method below). + * + * For convenience, developers may interact with scope in a number of ways. + * The simplest method, when you don't care about other information, is to set + * the `scopeID`, e.g. `record.scopeID = 'o_123'`. The scope JSON fragment + * may be set directly, `record.scope = { scope_id: 'o_123' }`. + * For convenience, you may even set the scope using a `Scope` model: + * `record.scopeModel = scopeModelInstance`, which automatically converts the + * scope to a JSON fragment of the proper shape and assigns it to `scope`. + * + * A record's scope may be read in one of two ways: + * + * 1) As a JSON fragment: `record.scope`, + * 2) Or as an ID: `record.scopeID`. + */ export default class BaseModel extends Model { + + // =relationships + + /** + * Scope of this resource, if any, represented as a JSON fragment. + * @type {FragmentScope} + */ + @fragment('fragment-scope') scope; + // =attributes + set scopeModel(model) { + if (model) { + const json = model.serialize(); + json.scope_id = model.id; + this.scope = json; + } + } + + /** + * Convenience for getting and setting the parent scope ID. + * @type {string + */ + @computed('scope.scope_id') + get scopeID() { + return get(this, 'scope.scope_id'); + } + set scopeID(id) { + if (!this.scope) this.scope = {}; + this.scope.scope_id = id; + } + /** * Names are optional on models in our API. Thus we need to fallback on ID * for display purposes. @@ -29,4 +83,5 @@ export default class BaseModel extends Model { get cannotSave() { return !this.canSave; } + } diff --git a/addons/api/addon/models/fragment-scope.js b/addons/api/addon/models/fragment-scope.js new file mode 100644 index 000000000..1a402c383 --- /dev/null +++ b/addons/api/addon/models/fragment-scope.js @@ -0,0 +1,36 @@ +import Fragment from 'ember-data-model-fragments/fragment'; +import { computed } from '@ember/object'; +import { attr } from '@ember-data/model'; +import { scopeTypes } from './scope'; + +export default class FragmentScope extends Fragment { + + // =attributes + + @attr('string', { + description: 'ID of the related scope' + }) scope_id; + + @attr('string', { + description: 'The type of the resource' + }) type; + + /** + * @type {boolean} + */ + @computed('type') + get isGlobal() { return this.type === scopeTypes.global; } + + /** + * @type {boolean} + */ + @computed('type') + get isOrg() { return this.type === scopeTypes.org; } + + /** + * @type {boolean} + */ + @computed('type') + get isProject() { return this.type === scopeTypes.project; } + +} diff --git a/addons/api/addon/models/org.js b/addons/api/addon/models/org.js deleted file mode 100644 index 5b6bb4136..000000000 --- a/addons/api/addon/models/org.js +++ /dev/null @@ -1,3 +0,0 @@ -import GeneratedOrgModel from '../generated/models/org'; - -export default class OrgModel extends GeneratedOrgModel {} diff --git a/addons/api/addon/models/project.js b/addons/api/addon/models/project.js deleted file mode 100644 index 063ed5110..000000000 --- a/addons/api/addon/models/project.js +++ /dev/null @@ -1,3 +0,0 @@ -import GeneratedProjectModel from '../generated/models/project'; - -export default class ProjectModel extends GeneratedProjectModel {} diff --git a/addons/api/addon/models/scope.js b/addons/api/addon/models/scope.js new file mode 100644 index 000000000..061501759 --- /dev/null +++ b/addons/api/addon/models/scope.js @@ -0,0 +1,46 @@ +import GeneratedScopeModel from '../generated/models/scope'; +import { computed } from '@ember/object'; + +export const scopeTypes = { + global: 'global', + org: 'org', + project: 'project' +}; + +export default class ScopeModel extends GeneratedScopeModel { + + // =attributes + + /** + * @type {boolean} + */ + @computed('type') + get isGlobal() { + return this.type === scopeTypes.global; + } + // There is only one global scope and it cannot be created by clients, + // thus no set. + + /** + * @type {boolean} + */ + @computed('type') + get isOrg() { + return this.type === scopeTypes.org; + } + set isOrg(value) { + if (value) this.type = scopeTypes.org; + } + + /** + * @type {boolean} + */ + @computed('type') + get isProject() { + return this.type === scopeTypes.project; + } + set isProject(value) { + if (value) this.type = scopeTypes.project; + } + +} diff --git a/addons/api/addon/serializers/application.js b/addons/api/addon/serializers/application.js index d5b1e333e..c839eb78c 100644 --- a/addons/api/addon/serializers/application.js +++ b/addons/api/addon/serializers/application.js @@ -1,11 +1,19 @@ import RESTSerializer from '@ember-data/serializer/rest'; import { underscore } from '@ember/string'; +import { get } from '@ember/object'; import { copy } from 'ember-copy'; /** * Manages serialization/normalization of data to/from the API. */ export default class ApplicationSerializer extends RESTSerializer { + + // =attributes + + attrs = { + scope: { serialize: false } + }; + // =methods /** @@ -91,7 +99,7 @@ export default class ApplicationSerializer extends RESTSerializer { /** * In our API, singluar resources are _unrooted_, whereas Ember Data expects - * them to be rooted under their model name, e.g. `{project: {…}}`. + * them to be rooted under their model name, e.g. `{modelName: {…}}`. * This method makes this transformation to accommodate Ember Data. * * @method normalizeSingleResponse @@ -110,7 +118,7 @@ export default class ApplicationSerializer extends RESTSerializer { primaryModelClass.modelName ); // Copy the unrooted payload under the expected root key name. - transformedPayload[payloadKey] = copy(payload); + transformedPayload[payloadKey] = copy(payload, true); // Return the result of normalizing the transformed payload. return super.normalizeSingleResponse( store, @@ -120,4 +128,22 @@ export default class ApplicationSerializer extends RESTSerializer { requestType ); } + + /** + * One consequence of using model fragments to represent the embedded scope + * is that they cannot have `id` fields. We still need to know scope IDs, + * so we copy the `scope.id` value into the `scope.scope_id` field. + * @override + * @see FragmentScope + * @param {Model} typeClass + * @param {Object} hash + * @return {Object} + */ + normalize(typeClass, hash) { + const normalizedHash = copy(hash, true); + const scopeID = get(normalizedHash, 'scope.id'); + if (scopeID) normalizedHash.scope.scope_id = scopeID; + return super.normalize(typeClass, normalizedHash); + } + } diff --git a/addons/api/addon/services/scope.js b/addons/api/addon/services/scope.js deleted file mode 100644 index 370bdfb8c..000000000 --- a/addons/api/addon/services/scope.js +++ /dev/null @@ -1,123 +0,0 @@ -import Service from '@ember/service'; -import { tracked } from '@glimmer/tracking'; -import { computed } from '@ember/object'; - -/** - * This module includes two parts: - * - * - `Scope` class: a data structure representing an org/project tuple - * - `ScopeService`: manages the scope and exposes it to the application - */ - -/** - * Represents a _scope_, which is defined as an organization/project tuple. - * This class is intended as a simple data structure and is used by the - * scope service (see below). - * Immutable. - */ -class Scope { - /** - * @type {?object} - * @private - */ - #org = null; - - /** - * @type {?object} - * @private - */ - #project = null; - - /** - * Initializes an instance of `Scope` with optional org and project. - * A scope with only project is invalid and in thi scenario, project will - * be null. - * @param {?object} org - * @param {?object} project - */ - constructor(org = null, project = null) { - if (org) this.#org = org; - if (org && project) this.#project = project; - } - - /** - * Org for this scope. Immutable. - * @type {?object} - */ - get org() { - return this.#org; - } - - /** - * Project for this scope. - * @type {?object} - */ - get project() { - return this.#project; - } - - /** - * Returns the JSON representation of this scope. - * @return {?object} - */ - toJSON() { - const { org, project } = this; - return { org, project }; - } -} - -export { Scope }; - -/** - * The scope service manages the current scope, making it available throughout - * the Ember application. Use `service.org` and `service.project` directly, - * rather than interacting with the internal `scope` member. - * - * @example - * // inject the scope service - * import { inject as service } from '@ember/service'; - * class MyClass { - * @service scope; - * } - * - * // set an org, project, or both - * this.scope.org = {id: 1}; - * this.scope.project = {id: 2}; - * - * // retrieve the org and/or project - * const org = this.scope.org; - * const project = this.scope.project; - */ -export default class ScopeService extends Service { - /** - * The currently selected scope. This is an internal member only. - * @type {?Scope} - */ - @tracked scope = new Scope(); - - /** - * When setting org, `service.org = {...}`, a new scope is instantiated, - * since scopes are immutable. - * @type {?object} - */ - @computed('scope.org') - get org() { - return this.scope ? this.scope.org : null; - } - set org(org) { - this.scope = new Scope(org, this.project); - } - - /** - * When setting project, `service.project = {...}`, a new scope is - * instantiated, since scopes are immutable. - * @type {?object} - */ - @computed('scope.project') - get project() { - return this.scope ? this.scope.project : null; - } - set project(project) { - this.scope = new Scope(this.org, project); - } -} diff --git a/addons/api/app/adapters/auth-method.js b/addons/api/app/adapters/auth-method.js deleted file mode 100644 index 02c325d6e..000000000 --- a/addons/api/app/adapters/auth-method.js +++ /dev/null @@ -1 +0,0 @@ -export { default } from 'api/adapters/auth-method'; diff --git a/addons/api/app/adapters/group.js b/addons/api/app/adapters/group.js deleted file mode 100644 index bb77ff26c..000000000 --- a/addons/api/app/adapters/group.js +++ /dev/null @@ -1 +0,0 @@ -export { default } from 'api/adapters/group'; diff --git a/addons/api/app/adapters/host-catalog.js b/addons/api/app/adapters/host-catalog.js deleted file mode 100644 index 00197a624..000000000 --- a/addons/api/app/adapters/host-catalog.js +++ /dev/null @@ -1 +0,0 @@ -export { default } from 'api/adapters/host-catalog'; diff --git a/addons/api/app/adapters/project.js b/addons/api/app/adapters/project.js deleted file mode 100644 index 0908e069c..000000000 --- a/addons/api/app/adapters/project.js +++ /dev/null @@ -1 +0,0 @@ -export { default } from 'api/adapters/project'; diff --git a/addons/api/app/adapters/role.js b/addons/api/app/adapters/role.js deleted file mode 100644 index f12325923..000000000 --- a/addons/api/app/adapters/role.js +++ /dev/null @@ -1 +0,0 @@ -export { default } from 'api/adapters/role'; diff --git a/addons/api/app/adapters/scope.js b/addons/api/app/adapters/scope.js new file mode 100644 index 000000000..10878851f --- /dev/null +++ b/addons/api/app/adapters/scope.js @@ -0,0 +1 @@ +export { default } from 'api/adapters/scope'; diff --git a/addons/api/app/adapters/user.js b/addons/api/app/adapters/user.js deleted file mode 100644 index 78f340b99..000000000 --- a/addons/api/app/adapters/user.js +++ /dev/null @@ -1 +0,0 @@ -export { default } from 'api/adapters/user'; diff --git a/addons/api/app/models/fragment-scope.js b/addons/api/app/models/fragment-scope.js new file mode 100644 index 000000000..277f701cf --- /dev/null +++ b/addons/api/app/models/fragment-scope.js @@ -0,0 +1 @@ +export { default } from 'api/models/fragment-scope'; diff --git a/addons/api/app/models/org.js b/addons/api/app/models/org.js deleted file mode 100644 index 8ef9d3f59..000000000 --- a/addons/api/app/models/org.js +++ /dev/null @@ -1 +0,0 @@ -export { default } from 'api/models/org'; diff --git a/addons/api/app/models/project.js b/addons/api/app/models/project.js deleted file mode 100644 index 1fc732fe1..000000000 --- a/addons/api/app/models/project.js +++ /dev/null @@ -1 +0,0 @@ -export { default } from 'api/models/project'; diff --git a/addons/api/app/models/scope.js b/addons/api/app/models/scope.js new file mode 100644 index 000000000..66442e397 --- /dev/null +++ b/addons/api/app/models/scope.js @@ -0,0 +1 @@ +export { default } from 'api/models/scope'; diff --git a/addons/api/app/services/scope.js b/addons/api/app/services/scope.js deleted file mode 100644 index 6c4b4d6c8..000000000 --- a/addons/api/app/services/scope.js +++ /dev/null @@ -1 +0,0 @@ -export { default, Scope } from 'api/services/scope'; diff --git a/addons/api/package.json b/addons/api/package.json index 00d40d798..5c52d99d5 100644 --- a/addons/api/package.json +++ b/addons/api/package.json @@ -41,6 +41,7 @@ "ember-cli-uglify": "^3.0.0", "ember-copy": "^1.0.0", "ember-data": "^3.16.0", + "ember-data-model-fragments": "5.0.0-beta.0", "ember-disable-prototype-extensions": "^1.1.3", "ember-export-application-global": "^2.0.1", "ember-get-config": "^0.2.4", diff --git a/addons/api/tests/dummy/app/routes/application.js b/addons/api/tests/dummy/app/routes/application.js index 036973710..4d3bf9a90 100644 --- a/addons/api/tests/dummy/app/routes/application.js +++ b/addons/api/tests/dummy/app/routes/application.js @@ -1,10 +1,7 @@ import Route from '@ember/routing/route'; -import { inject as service } from '@ember/service'; export default class ApplicationRoute extends Route { - @service scope; - model() { - this.store.findAll('project'); + this.store.findAll('scope'); } } diff --git a/addons/api/tests/unit/adapters/application-test.js b/addons/api/tests/unit/adapters/application-test.js index 366c44c4e..c3d72d469 100644 --- a/addons/api/tests/unit/adapters/application-test.js +++ b/addons/api/tests/unit/adapters/application-test.js @@ -18,9 +18,86 @@ module('Unit | Adapter | application', function (hooks) { }); test('it generates correct URL prefixes', function (assert) { + assert.expect(2); + const adapter = this.owner.lookup('adapter:application'); + assert.ok(config.api.namespace); + assert.equal(adapter.urlPrefix(), config.api.namespace); + }); + + test('it generates URL prefixes with optional `scopeID` from adapterOptions', function (assert) { + assert.expect(2); + const scopeID = 'global'; + const adapter = this.owner.lookup('adapter:application'); + const prefix = adapter.urlPrefix(null, null, null, null, { + adapterOptions: { scopeID } + }); + assert.ok(config.api.namespace); + assert.equal(prefix, `v1/scopes/${scopeID}`); + }); + + test('it does not prefixes URLs with `scopeID` for scope resources', function (assert) { + assert.expect(1); + const scopeID = 'global'; + const adapter = this.owner.lookup('adapter:application'); + const prefix = adapter.urlPrefix(null, null, 'scope', null, { + adapterOptions: { scopeID } + }); + assert.equal(prefix, `v1`); + }); + + test('it generates correct default URL suffixes', function (assert) { assert.expect(1); const adapter = this.owner.lookup('adapter:application'); - assert.equal(adapter.urlPrefix(), config.api.namespace); + assert.equal(adapter.urlSuffix(), ''); + }); + + test('it generates URL suffixes with optional `method` from adapterOptions', function (assert) { + assert.expect(2); + const method = 'set-something'; + const adapter = this.owner.lookup('adapter:application'); + const suffix = adapter.urlSuffix(null, null, { + adapterOptions: { method } + }); + assert.ok(config.api.namespace); + assert.equal(suffix, ':set-something'); + }); + + test('it generates correct complete URLs', function (assert) { + assert.expect(6); + const scopeID = 'o_1'; + const method = 'my-custom-method'; + const mockSnapshot = { + adapterOptions: { + scopeID, + method + } + }; + const adapter = this.owner.lookup('adapter:application'); + // test URL generation for each request type... + const findRecordURL = adapter.buildURL('user', '1', mockSnapshot, 'findRecord'); + assert.equal(findRecordURL, '/v1/scopes/o_1/users/1:my-custom-method'); + const findAllURL = adapter.buildURL('user', null, mockSnapshot, 'findAll'); + assert.equal(findAllURL, '/v1/scopes/o_1/users:my-custom-method'); + const findBelongsToURL = adapter.buildURL('user', '2', mockSnapshot, 'findBelongsTo'); + assert.equal(findBelongsToURL, '/v1/scopes/o_1/users/2:my-custom-method'); + const createRecordURL = adapter.buildURL('user', null, mockSnapshot, 'createRecord'); + assert.equal(createRecordURL, '/v1/scopes/o_1/users:my-custom-method'); + const updateRecordURL = adapter.buildURL('user', '3', mockSnapshot, 'updateRecord'); + assert.equal(updateRecordURL, '/v1/scopes/o_1/users/3:my-custom-method'); + const deleteRecordURL = adapter.buildURL('user', '4', mockSnapshot, 'deleteRecord'); + assert.equal(deleteRecordURL, '/v1/scopes/o_1/users/4:my-custom-method'); + }); + + test('it can request records through the store from a specified scope', async function (assert) { + assert.expect(1); + const store = this.owner.lookup('service:store'); + this.server.get('/v1/scopes/p_456/groups', () => { + assert.ok(true, 'Scoped resource URL was requested.'); + return {items: []}; + }); + await store.findAll('group', { + adapterOptions: { scopeID: 'p_456' } + }); }); test('it rewrites PUT to PATCH, but leaves others unchanged', function (assert) { @@ -37,10 +114,10 @@ module('Unit | Adapter | application', function (hooks) { test('it prenormalizes "empty" responses into a form that the fetch-manager will not reject', async function (assert) { assert.expect(1); - const adapter = this.owner.lookup('adapter:project'); + const adapter = this.owner.lookup('adapter:application'); const store = this.owner.lookup('service:store'); - this.server.get('/v1/projects', () => new Response({})); - const prenormalized = await adapter.findAll(store, {modelName: 'project'}, null, []); + this.server.get('/v1/users', () => new Response({})); + const prenormalized = await adapter.findAll(store, {modelName: 'user'}, null, []); assert.deepEqual(prenormalized, {items: []}); }); diff --git a/addons/api/tests/unit/adapters/auth-method-test.js b/addons/api/tests/unit/adapters/auth-method-test.js deleted file mode 100644 index d7c2f176c..000000000 --- a/addons/api/tests/unit/adapters/auth-method-test.js +++ /dev/null @@ -1,16 +0,0 @@ -import { module, test } from 'qunit'; -import { setupTest } from 'ember-qunit'; -import config from 'ember-get-config'; - -module('Unit | Adapter | auth method', function (hooks) { - setupTest(hooks); - - test('it generates URL prefixes without project scope', function (assert) { - assert.expect(1); - const service = this.owner.lookup('service:scope'); - service.org = { id: 1 }; - service.project = { id: 2 }; - const adapter = this.owner.lookup('adapter:auth-method'); - assert.equal(adapter.urlPrefix(), `${config.api.namespace}/orgs/1`); - }); -}); diff --git a/addons/api/tests/unit/adapters/group-test.js b/addons/api/tests/unit/adapters/group-test.js deleted file mode 100644 index 2c1c53d3c..000000000 --- a/addons/api/tests/unit/adapters/group-test.js +++ /dev/null @@ -1,21 +0,0 @@ -import { module, test } from 'qunit'; -import { setupTest } from 'ember-qunit'; -import config from 'ember-get-config'; - -module('Unit | Adapter | group', function(hooks) { - setupTest(hooks); - - test('it exists', function(assert) { - let adapter = this.owner.lookup('adapter:group'); - assert.ok(adapter); - }); - - test('it generates URL prefixes without project scope', function (assert) { - assert.expect(1); - const service = this.owner.lookup('service:scope'); - service.org = { id: 1 }; - service.project = { id: 2 }; - const adapter = this.owner.lookup('adapter:group'); - assert.equal(adapter.urlPrefix(), `${config.api.namespace}/orgs/1`); - }); -}); diff --git a/addons/api/tests/unit/adapters/host-catalog-test.js b/addons/api/tests/unit/adapters/host-catalog-test.js deleted file mode 100644 index 6250cc547..000000000 --- a/addons/api/tests/unit/adapters/host-catalog-test.js +++ /dev/null @@ -1,11 +0,0 @@ -import { module, test } from 'qunit'; -import { setupTest } from 'ember-qunit'; - -module('Unit | Adapter | host catalog', function(hooks) { - setupTest(hooks); - - test('it exists', function(assert) { - let adapter = this.owner.lookup('adapter:host-catalog'); - assert.ok(adapter); - }); -}); diff --git a/addons/api/tests/unit/adapters/project-test.js b/addons/api/tests/unit/adapters/project-test.js deleted file mode 100644 index 90576b2bd..000000000 --- a/addons/api/tests/unit/adapters/project-test.js +++ /dev/null @@ -1,16 +0,0 @@ -import { module, test } from 'qunit'; -import { setupTest } from 'ember-qunit'; -import config from 'ember-get-config'; - -module('Unit | Adapter | project', function (hooks) { - setupTest(hooks); - - test('it generates URL prefixes without project scope', function (assert) { - assert.expect(1); - const service = this.owner.lookup('service:scope'); - service.org = { id: 1 }; - service.project = { id: 2 }; - const adapter = this.owner.lookup('adapter:project'); - assert.equal(adapter.urlPrefix(), `${config.api.namespace}/orgs/1`); - }); -}); diff --git a/addons/api/tests/unit/adapters/role-test.js b/addons/api/tests/unit/adapters/role-test.js deleted file mode 100644 index adb7b1abd..000000000 --- a/addons/api/tests/unit/adapters/role-test.js +++ /dev/null @@ -1,16 +0,0 @@ -import { module, test } from 'qunit'; -import { setupTest } from 'ember-qunit'; -import config from 'ember-get-config'; - -module('Unit | Adapter | role', function(hooks) { - setupTest(hooks); - - test('it generates URL prefixes without project scope', function (assert) { - assert.expect(1); - const service = this.owner.lookup('service:scope'); - service.org = { id: 1 }; - service.project = { id: 2 }; - const adapter = this.owner.lookup('adapter:role'); - assert.equal(adapter.urlPrefix(), `${config.api.namespace}/orgs/1`); - }); -}); diff --git a/addons/api/tests/unit/adapters/scope-test.js b/addons/api/tests/unit/adapters/scope-test.js new file mode 100644 index 000000000..7dcd482c7 --- /dev/null +++ b/addons/api/tests/unit/adapters/scope-test.js @@ -0,0 +1,23 @@ +import { module, test } from 'qunit'; +import { setupTest } from 'ember-qunit'; +import setupMirage from 'ember-cli-mirage/test-support/setup-mirage'; + +module('Unit | Adapter | scope', function(hooks) { + setupTest(hooks); + setupMirage(hooks); + + test('it generates correct createRecord URLs with a scope_id query parameter', function (assert) { + assert.expect(1); + this.server.post('/v1/scopes', (schema, { queryParams }) =>{ + assert.equal(queryParams.scope_id, 'o_1'); + }); + const store = this.owner.lookup('service:store'); + const scope = store.createRecord('scope', { + scopeID: 'o_1' + }); + const type = scope.constructor; + const snapshot = scope._createSnapshot(); + const adapter = this.owner.lookup('adapter:scope'); + adapter.createRecord(store, type, snapshot); + }); +}); diff --git a/addons/api/tests/unit/adapters/user-test.js b/addons/api/tests/unit/adapters/user-test.js deleted file mode 100644 index 17dc82ff6..000000000 --- a/addons/api/tests/unit/adapters/user-test.js +++ /dev/null @@ -1,21 +0,0 @@ -import { module, test } from 'qunit'; -import { setupTest } from 'ember-qunit'; -import config from 'ember-get-config'; - -module('Unit | Adapter | user', function(hooks) { - setupTest(hooks); - - test('it exists', function(assert) { - let adapter = this.owner.lookup('adapter:user'); - assert.ok(adapter); - }); - - test('it generates URL prefixes without project scope', function (assert) { - assert.expect(1); - const service = this.owner.lookup('service:scope'); - service.org = { id: 1 }; - service.project = { id: 2 }; - const adapter = this.owner.lookup('adapter:user'); - assert.equal(adapter.urlPrefix(), `${config.api.namespace}/orgs/1`); - }); -}); diff --git a/addons/api/tests/unit/mixins/adapter-build-url-test.js b/addons/api/tests/unit/mixins/adapter-build-url-test.js new file mode 100644 index 000000000..560f3f8fc --- /dev/null +++ b/addons/api/tests/unit/mixins/adapter-build-url-test.js @@ -0,0 +1,55 @@ +import RESTAdapter from '@ember-data/adapter/rest'; +import AdapterBuildURLMixin from 'api/mixins/adapter-build-url'; +import { module, test } from 'qunit'; +import { setupTest } from 'ember-qunit'; + +module('Unit | Mixin | build-url', function(hooks) { + setupTest(hooks); + + test('it does not interfere with basic URLs', function (assert) { + assert.expect(1); + const Adapter = RESTAdapter.extend(AdapterBuildURLMixin); + this.owner.register('adapter:adapter', Adapter); + const adapter = this.owner.lookup('adapter:adapter'); + const url = adapter._buildURL('model', 1); + assert.equal(url, '/models/1'); + }); + + test('it does not interfere with namespaced URLs', function (assert) { + assert.expect(1); + const Adapter = RESTAdapter.extend(AdapterBuildURLMixin, { + namespace: 'api/v1' + }); + this.owner.register('adapter:adapter', Adapter); + const adapter = this.owner.lookup('adapter:adapter'); + const url = adapter._buildURL('model', 1); + assert.equal(url, '/api/v1/models/1'); + }); + + test('it can prepend a prefix', function (assert) { + assert.expect(1); + const Adapter = RESTAdapter.extend(AdapterBuildURLMixin, { + urlPrefix() { + return '/foo/bars' + } + }); + this.owner.register('adapter:adapter', Adapter); + const adapter = this.owner.lookup('adapter:adapter'); + const url = adapter._buildURL('model', 1); + assert.equal(url, '/foo/bars/models/1'); + }); + + test('it can append a suffix', function (assert) { + assert.expect(1); + const Adapter = RESTAdapter.extend(AdapterBuildURLMixin, { + urlSuffix() { + return ':method' + } + }); + this.owner.register('adapter:adapter', Adapter); + const adapter = this.owner.lookup('adapter:adapter'); + const url = adapter._buildURL('model', 1); + assert.equal(url, '/models/1:method'); + }); + +}); diff --git a/addons/api/tests/unit/mixins/scope-adapter-test.js b/addons/api/tests/unit/mixins/scope-adapter-test.js deleted file mode 100644 index 5e042ae93..000000000 --- a/addons/api/tests/unit/mixins/scope-adapter-test.js +++ /dev/null @@ -1,68 +0,0 @@ -import RESTAdapter from '@ember-data/adapter/rest'; -import ScopeAdapterMixin from 'api/mixins/scope-adapter'; -import { module, test } from 'qunit'; -import { setupTest } from 'ember-qunit'; - -module('Unit | Mixin | scope-adapter', function (hooks) { - setupTest(hooks); - - test('it does not interfere with basic URLs', function (assert) { - assert.expect(1); - //const service = this.owner.lookup('service:scope'); - const ScopeAdapter = RESTAdapter.extend(ScopeAdapterMixin); - this.owner.register('adapter:scope', ScopeAdapter); - const adapter = this.owner.lookup('adapter:scope'); - const url = adapter._buildURL('model', 1); - assert.equal(url, '/models/1'); - }); - - test('it does not interfere with namespaced URLs', function (assert) { - assert.expect(1); - //const service = this.owner.lookup('service:scope'); - const ScopeAdapter = RESTAdapter.extend(ScopeAdapterMixin, { - namespace: 'api/v1', - }); - this.owner.register('adapter:scope', ScopeAdapter); - const adapter = this.owner.lookup('adapter:scope'); - const url = adapter._buildURL('model', 1); - assert.equal(url, '/api/v1/models/1'); - }); - - test('it adds a scope to basic URLs when scope is set', function (assert) { - assert.expect(4); - const service = this.owner.lookup('service:scope'); - service.org = { id: 1 }; - const ScopeAdapter = RESTAdapter.extend(ScopeAdapterMixin, { - namespace: 'api/v1', - }); - this.owner.register('adapter:scope', ScopeAdapter); - const adapter = this.owner.lookup('adapter:scope'); - let url = adapter._buildURL('model', 1); - assert.equal(url, '/api/v1/orgs/1/models/1'); - service.project = { id: 2 }; - url = adapter._buildURL('model', 1); - assert.equal(url, '/api/v1/orgs/1/projects/2/models/1'); - service.org = { id: 'foobar' }; - url = adapter._buildURL('model', 1); - assert.equal(url, '/api/v1/orgs/foobar/projects/2/models/1'); - // can't have a project without an org - service.org = null; - url = adapter._buildURL('model', 1); - assert.equal(url, '/api/v1/models/1'); - }); - - test('it does not add a project scope to basic URLs when includeProject is false', function (assert) { - assert.expect(1); - const service = this.owner.lookup('service:scope'); - service.org = { id: 1 }; - service.project = { id: 2 }; - const ScopeAdapter = RESTAdapter.extend(ScopeAdapterMixin, { - namespace: 'api/v1', - includeProject: false, - }); - this.owner.register('adapter:scope', ScopeAdapter); - const adapter = this.owner.lookup('adapter:scope'); - let url = adapter._buildURL('model', 1); - assert.equal(url, '/api/v1/orgs/1/models/1'); - }); -}); diff --git a/addons/api/tests/unit/models/base-test.js b/addons/api/tests/unit/models/base-test.js index 670168d48..40be071fe 100644 --- a/addons/api/tests/unit/models/base-test.js +++ b/addons/api/tests/unit/models/base-test.js @@ -1,8 +1,10 @@ import { module, test } from 'qunit'; import { setupTest } from 'ember-qunit'; +import setupMirage from 'ember-cli-mirage/test-support/setup-mirage'; module('Unit | Model | base', function (hooks) { setupTest(hooks); + setupMirage(hooks); // Replace this with your real tests. test('it exists', function (assert) { @@ -11,17 +13,54 @@ module('Unit | Model | base', function (hooks) { assert.ok(model); }); + test('it may have a scope fragment', function (assert) { + assert.expect(1); + const store = this.owner.lookup('service:store'); + store.push({ + data: { + id: 'o_1', + type: 'scope' + } + }); + store.push({ + data: { + id: '123abc', + type: 'user', + attributes: { + scope: { + scope_id: 'o_1', + type: 'scope' + } + } + } + }); + const scope = store.peekRecord('scope', 'o_1'); + const model = store.peekRecord('user', '123abc'); + assert.equal(model.scope.scope_id, scope.id); + }); + + test('it may accept a `scopeModel` for convenience, instead of a fragment', function (assert) { + assert.expect(3); + const store = this.owner.lookup('service:store'); + const scope = store.createRecord('scope', {id: 'o_1', type: 'org'}); + const model = store.createRecord('user', '123abc'); + assert.notEqual(model.scopeID, scope.id); + model.scopeModel = scope; + assert.equal(model.scopeID, scope.id); + assert.equal(model.scope.isOrg, true); + }); + test('it has a displayName attribute', function (assert) { assert.expect(3); const store = this.owner.lookup('service:store'); store.push({ data: { id: '123abc', - type: 'project', + type: 'user', attributes: {}, }, }); - const model = store.peekRecord('project', '123abc'); + const model = store.peekRecord('user', '123abc'); assert.equal(model.name, null); assert.equal(model.displayName, '123abc'); model.name = 'Test'; @@ -34,15 +73,15 @@ module('Unit | Model | base', function (hooks) { store.push({ data: { id: '1', - type: 'project', + type: 'user', attributes: {}, }, }); - const model = store.peekRecord('project', '1'); + const model = store.peekRecord('user', '1'); assert.equal(model.canSave, false); assert.equal(model.cannotSave, true); // Should be able to save if dirty - model.name = 'Project'; + model.name = 'User'; assert.equal(model.hasDirtyAttributes, true); assert.equal(model.canSave, true); assert.equal(model.cannotSave, false); @@ -53,4 +92,61 @@ module('Unit | Model | base', function (hooks) { assert.equal(model.cannotSave, true); model.transitionTo('loaded'); }); + + test('it saves records to a scoped URL', async function (assert) { + assert.expect(3); + const store = this.owner.lookup('service:store'); + const model = store.createRecord('user', { scopeID: 'o_123' }); + this.server.post('/v1/scopes/o_123/users', () => { + assert.ok(true, 'Correctly scoped create record URL was requested.') + return {}; + }); + await model.save(); + this.server.patch('/v1/scopes/o_123/users', () => { + assert.ok(true, 'Correctly scoped update record URL was requested.') + return {}; + }); + await model.save(); + this.server.delete('/v1/scopes/o_123/users', () => { + assert.ok(true, 'Correctly scoped delete record URL was requested.') + return {}; + }); + await model.destroyRecord(); + }); + + test('it saves to a custom scoped URL if requested', async function (assert) { + assert.expect(3); + const store = this.owner.lookup('service:store'); + const customScopeID = 'global'; + const scope = {scope_id: 'o_123'}; + const model = store.createRecord('user', { scope }); + this.server.post('/v1/scopes/global/users', () => { + assert.ok(true, 'Correctly scoped create record URL was requested.') + return {}; + }); + await model.save({adapterOptions: {scopeID: customScopeID}}); + this.server.patch('/v1/scopes/global/users', () => { + assert.ok(true, 'Correctly scoped update record URL was requested.') + return {}; + }); + await model.save({adapterOptions: {scopeID: customScopeID}}); + this.server.delete('/v1/scopes/global/users', () => { + assert.ok(true, 'Correctly scoped delete record URL was requested.') + return {}; + }); + await model.destroyRecord({adapterOptions: {scopeID: customScopeID}}); + }); + + test('it saves records to a URL with a custom method if requested', async function (assert) { + assert.expect(1); + const store = this.owner.lookup('service:store'); + const method = 'my-custom-method'; + const scope = {scope_id: 'o_123'}; + const model = store.createRecord('user', { scope }); + this.server.post(`/v1/scopes/o_123/users:${method}`, () => { + assert.ok(true, 'Correctly scoped create record URL was requested.') + return {}; + }); + await model.save({adapterOptions: {method}}); + }); }); diff --git a/addons/api/tests/unit/models/org-test.js b/addons/api/tests/unit/models/org-test.js deleted file mode 100644 index 2f042eadc..000000000 --- a/addons/api/tests/unit/models/org-test.js +++ /dev/null @@ -1,13 +0,0 @@ -import { module, test } from 'qunit'; -import { setupTest } from 'ember-qunit'; - -module('Unit | Model | org', function(hooks) { - setupTest(hooks); - - // Replace this with your real tests. - test('it exists', function(assert) { - let store = this.owner.lookup('service:store'); - let model = store.createRecord('org', {}); - assert.ok(model); - }); -}); diff --git a/addons/api/tests/unit/models/project-test.js b/addons/api/tests/unit/models/project-test.js deleted file mode 100644 index 9b2a93d73..000000000 --- a/addons/api/tests/unit/models/project-test.js +++ /dev/null @@ -1,12 +0,0 @@ -import { module, test } from 'qunit'; -import { setupTest } from 'ember-qunit'; - -module('Unit | Model | project', function (hooks) { - setupTest(hooks); - - test('it exists', function (assert) { - let store = this.owner.lookup('service:store'); - let model = store.createRecord('project', {}); - assert.ok(model); - }); -}); diff --git a/addons/api/tests/unit/models/scope-test.js b/addons/api/tests/unit/models/scope-test.js new file mode 100644 index 000000000..ee2e212b9 --- /dev/null +++ b/addons/api/tests/unit/models/scope-test.js @@ -0,0 +1,49 @@ +import { module, test } from 'qunit'; +import { setupTest } from 'ember-qunit'; +import setupMirage from 'ember-cli-mirage/test-support/setup-mirage'; + +module('Unit | Model | scope', function(hooks) { + setupTest(hooks); + setupMirage(hooks); + + test('it may have a scope fragment', async function(assert) { + assert.expect(6); + const store = this.owner.lookup('service:store'); + this.server.get('/v1/scopes', () => ({ + items: [ + {id: 'global', type: 'global'}, + {id: 'o_1', type: 'org', scope: {scope_id: 'global'}}, + {id: 'o_2', type: 'org', scope: {scope_id: 'global'}}, + {id: 'p_1', type: 'project', scope: {scope_id: 'o_1'}}, + {id: 'p_2', type: 'project', scope: {scope_id: 'o_1'}}, + {id: 'p_3', type: 'project', scope: {scope_id: 'o_2'}}, + ] + })); + const scopes = await store.findAll('scope'); + // check integrity of scope relationships + assert.notOk(await scopes.firstObject.get('scope'), 'Global scope has no parent'); + assert.equal(await scopes.objectAt(1).get('scope.scope_id'), 'global', 'Org 1 parent scope is global'); + assert.equal(await scopes.objectAt(2).get('scope.scope_id'), 'global', 'Org 2 parent scope is global'); + assert.equal(await scopes.objectAt(3).get('scope.scope_id'), 'o_1', 'Project 1 parent scope is org 1'); + assert.equal(await scopes.objectAt(4).get('scope.scope_id'), 'o_1', 'Project 2 parent scope is org 1'); + assert.equal(await scopes.objectAt(5).get('scope.scope_id'), 'o_2', 'Project 3 parent scope is org 2'); + }); + + test('it has isType boolean getters and setters', async function(assert) { + assert.expect(9); + const store = this.owner.lookup('service:store'); + const model = store.createRecord('scope', {type: 'global'}); + assert.ok(model.isGlobal); + assert.notOk(model.isOrg); + assert.notOk(model.isProject); + model.isOrg = true; + assert.notOk(model.isGlobal); + assert.ok(model.isOrg); + assert.notOk(model.isProject); + model.isProject = true; + assert.notOk(model.isGlobal); + assert.notOk(model.isOrg); + assert.ok(model.isProject); + }); + +}); diff --git a/addons/api/tests/unit/serializers/application-test.js b/addons/api/tests/unit/serializers/application-test.js index a9f999501..4769cfa3f 100644 --- a/addons/api/tests/unit/serializers/application-test.js +++ b/addons/api/tests/unit/serializers/application-test.js @@ -14,33 +14,36 @@ module('Unit | Serializer | application', function (hooks) { test('it serializes records', function (assert) { assert.expect(1); const store = this.owner.lookup('service:store'); - const record = store.createRecord('project', { - name: 'Project', + const record = store.createRecord('user', { + name: 'User', description: 'Description', + scope: { + scope_id: 'global', + type: 'global' + } }); const serializedRecord = record.serialize(); assert.deepEqual(serializedRecord, { - name: 'Project', - description: 'Description', - //disabled: false, // TODO: disabled is temporarily disabled + name: 'User', + description: 'Description' }); }); test('it normalizes array records from an `items` root key', function (assert) { assert.expect(1); const store = this.owner.lookup('service:store'); - const serializer = store.serializerFor('project'); - const projectModelClass = store.createRecord('project').constructor; + const serializer = store.serializerFor('user'); + const userModelClass = store.createRecord('user').constructor; const payload = { items: [ - { id: 1, name: 'Project 1' }, - { id: 2, name: 'Project 2' }, - { id: 3, name: 'Project 3' }, + { id: 1, name: 'User 1', scope: { id: 'o_123' } }, + { id: 2, name: 'User 2', scope: { id: 'o_123' } }, + { id: 3, name: 'User 3', scope: { id: 'o_123' } }, ], }; const normalizedArray = serializer.normalizeArrayResponse( store, - projectModelClass, + userModelClass, payload ); assert.deepEqual(normalizedArray, { @@ -48,20 +51,20 @@ module('Unit | Serializer | application', function (hooks) { data: [ { id: '1', - type: 'project', - attributes: { name: 'Project 1' }, + type: 'user', + attributes: { name: 'User 1', scope: { scope_id: 'o_123' } }, relationships: {}, }, { id: '2', - type: 'project', - attributes: { name: 'Project 2' }, + type: 'user', + attributes: { name: 'User 2', scope: { scope_id: 'o_123' } }, relationships: {}, }, { id: '3', - type: 'project', - attributes: { name: 'Project 3' }, + type: 'user', + attributes: { name: 'User 3', scope: { scope_id: 'o_123' } }, relationships: {}, }, ], @@ -71,23 +74,24 @@ module('Unit | Serializer | application', function (hooks) { test('it normalizes single, unrooted records', function (assert) { assert.expect(1); const store = this.owner.lookup('service:store'); - const serializer = store.serializerFor('project'); - const projectModelClass = store.createRecord('project').constructor; + const serializer = store.serializerFor('user'); + const userModelClass = store.createRecord('user').constructor; const payload = { id: '1', - name: 'Project 1', + name: 'User 1', + scope: { id: 'o_123' } }; const normalized = serializer.normalizeSingleResponse( store, - projectModelClass, + userModelClass, payload ); assert.deepEqual(normalized, { included: [], data: { id: '1', - type: 'project', - attributes: { name: 'Project 1' }, + type: 'user', + attributes: { name: 'User 1', scope: { scope_id: 'o_123' } }, relationships: {}, }, }); diff --git a/addons/api/tests/unit/services/scope-test.js b/addons/api/tests/unit/services/scope-test.js deleted file mode 100644 index c94b1bfd5..000000000 --- a/addons/api/tests/unit/services/scope-test.js +++ /dev/null @@ -1,66 +0,0 @@ -import { module, test } from 'qunit'; -import { setupTest } from 'ember-qunit'; -import { Scope } from '../../../services/scope'; - -module('Unit | Service | scope class', function (hooks) { - setupTest(hooks); - - test('it provides a scope class', function (assert) { - assert.expect(1); - assert.ok(Scope); - }); - - test('it has initializable org and project members', function (assert) { - assert.expect(2); - const org = { id: 1 }; - const project = { id: 2 }; - const scope = new Scope(org, project); - assert.equal(scope.org.id, 1); - assert.equal(scope.project.id, 2); - }); - - test('it has a null project if org is null, even if project was initialized to non-null', function (assert) { - assert.expect(2); - const org = null; - const project = { id: 2 }; - const scope = new Scope(org, project); - assert.notOk(scope.org); - assert.notOk(scope.project); - }); - - test('it serializes to JSON', function (assert) { - assert.expect(2); - let scope = new Scope({ id: 1 }); - assert.deepEqual(scope.toJSON(), { - org: { id: 1 }, - project: null, - }); - scope = new Scope({ id: 1 }, { id: 2 }); - assert.deepEqual(scope.toJSON(), { - org: { id: 1 }, - project: { id: 2 }, - }); - }); -}); - -module('Unit | Service | scope', function (hooks) { - setupTest(hooks); - - test('it returns nulls when scope is not set', function (assert) { - assert.expect(2); - const service = this.owner.lookup('service:scope'); - assert.equal(service.org, null); - assert.equal(service.project, null); - }); - - test('it aliases org and project to a scope instance', function (assert) { - assert.expect(2); - const service = this.owner.lookup('service:scope'); - const org = { id: 1 }; - const project = { id: 2 }; - service.org = org; - service.project = project; - assert.equal(service.org, service.scope.org); - assert.equal(service.project, service.scope.project); - }); -}); diff --git a/addons/auth/addon/.gitkeep b/addons/auth/addon/.gitkeep deleted file mode 100644 index e69de29bb..000000000 diff --git a/addons/auth/addon/authenticators/password.js b/addons/auth/addon/authenticators/password.js new file mode 100644 index 000000000..7c6d79a4e --- /dev/null +++ b/addons/auth/addon/authenticators/password.js @@ -0,0 +1,87 @@ +import BaseAuthenticator from 'ember-simple-auth/authenticators/base'; +import { resolve, reject } from 'rsvp'; +import fetch from 'fetch'; + +/** + * + * This authenticator is intended for use with Ember Simple Auth. + * + * Simple id/password base authenticator sends credentials to an endpoint + * specified by the `buildAuthEndpointURL`. If the HTTP response code is in the + * success range, authentication resolves. Otherwise it rejects. + * + * Upon session invalidation, deauthentication is attempted at the URL generated + * by `buildDeauthEndpointURL`, but is not guaranteed. + * + * This authenticator should not be used directly because it does not specify + * an `buildAuthEndpointURL` or `buildDeauthEndpointURL` of its own. + * To use, generate an application authenticator in your app + * `authenticators/application.js` and extend this class. + * + * @example + * + * import PasswordAuthenticator from 'auth/authenticators/password'; + * export default class ApplicationAuthenticator extends PasswordAuthenticator { + * authEndpoint = '/api/authenticate'; + * deauthEndpoint = '/api/deauthenticate'; + * } + * + */ +export default class PasswordAuthenticator extends BaseAuthenticator { + // =methods + + /** + * Restores the session if data is present. Otherwise rejects. + * @override + * @param {object} data + * @return {Promise} + */ + restore(data) { + return data ? resolve(data) : reject(); + } + + /** + * Posts credentials to the URL specified in `authEndpoint` and resolves + * if a success HTTP status code is received, otherwise rejects. + * + * If requesting cookies, be sure to use the cookie session store. Otherwise + * use the local storage session store. + * + * @override + * @param {object} creds + * @param {string} creds.identification is sent to server as `name` + * @param {string} creds.password + * @param {boolean} requestCookies request cookie tokens (default `true`) + * @return {Promise} + */ + async authenticate( + { identification: name, password }, + requestCookies = true, + options + ) { + const body = JSON.stringify({ + token_type: requestCookies ? 'cookie' : null, + credentials: { name, password }, + }); + const authEndpointURL = this.buildAuthEndpointURL(options); + const response = await fetch(authEndpointURL, { method: 'post', body }); + const json = await response.json(); + return response.status < 400 ? resolve(json) : reject(); + } + + /** + * Posts to the `deauthEntpoint` on a best-effort basis and then returns. + * Deauthentication with the server is not guaranteed and request failures + * are ignored. + * + * @override + * @return {Promise} + */ + invalidate(options) { + const deauthEndpointURL = this.buildDeauthEndpointURL(options); + fetch(deauthEndpointURL, { method: 'post' }).catch(() => { + /* no op */ + }); + return super.invalidate(...arguments); + } +} diff --git a/addons/auth/addon/session-stores/cookie.js b/addons/auth/addon/session-stores/cookie.js new file mode 100644 index 000000000..ea0501ad5 --- /dev/null +++ b/addons/auth/addon/session-stores/cookie.js @@ -0,0 +1,134 @@ +import BaseSessionStore from 'ember-simple-auth/session-stores/base'; +import { resolve, reject } from 'rsvp'; +import { get } from '@ember/object'; +import { inject as service } from '@ember/service'; + +/** + * + * This session store is intended for use with Ember Simple Auth when requesting + * that the server send cookies (see authenticators). + * + * The cookie session store is used to maintain session state for auth servers + * that send cookies. In this strategy, the server sets any cookies needed for + * future authorization. This store determines if a session is authenticated + * by looking for the presence of a cookie with name specified in `cookieName`. + * + * This session store should not be used directly because it does not specify + * `cookiePath` or `cookieName`. To use, create an application session store + * and extend this class, being sure to specify these attributes. + * + * @example + * + * import CookieSessionStore from 'auth/session-stores/cookie'; + * export default class ApplicationSessionStore extends CookieSessionStore { + * cookiePath = '/'; + * cookieName = 'auth-token'; + * } + * + */ +export default class CookieSessionStore extends BaseSessionStore { + // =services + + @service cookies; + + // =attributes + + /** + * The name of the cookie that stores the name of the authenticator, derived + * from the name of the session cookie. The authenticator name is used + * internally by Ember Simple Auth, since it supports simultaneous + * authentication schemes within a single application. + * @type {string} + */ + get authenticatorCookieName() { + return `${this.cookieName}-authenticator`; + } + + /** + * Name of the authenticator used, if any. + * @type {?string} + */ + get authenticatorName() { + return this.cookies.read(this.authenticatorCookieName); + } + + /** + * True if both session and authenticator cookies are present. + * False otherwise. + * @type {boolean} + */ + get sessionExists() { + const tokenExists = this.cookies.exists(this.cookieName); + const authenticatorExists = this.cookies.exists( + this.authenticatorCookieName + ); + return tokenExists && authenticatorExists; + } + + // =methods + + /** + * This method is called automatically on initialization to perform cleanup + * if necessary. + * + * Sessions exist only if both the session and authenticator cookies are + * present. If either one is missing, we want to cleanup to be sure they + * are both absent. + */ + init() { + if (!this.sessionExists) this.clear(); + } + + /** + * While the primary session cookie is persisted by the server, not the + * application, we still need to "remember" the name of the authenticator + * used to authenticate to properly restore a session. Thus when this method + * is called with an authenticator name, we save it into a cookie with name + * `authenticatorCookieName`. If this method is called without an + * authenticator, all related cookies are cleared. + * @param {object} data + * @return {Promise} + */ + persist(data) { + const authenticator = get(data, 'authenticated.authenticator'); + const path = this.cookiePath; + if (authenticator) { + this.cookies.write(this.authenticatorCookieName, authenticator, { path }); + } else { + this.clear(); + } + return resolve(); + } + + /** + * If a session exists (as reported by `sessionExists`), then this method + * resolves with the authentication object expected by Ember Simple Auth, + * which must specify the name of the authenticator used to authenticate. + * + * If the session does not exist, all related cookies are cleared and this + * method rejects. + * + * @return {Promise} + */ + restore() { + if (this.sessionExists) { + return resolve({ + authenticated: { + authenticator: this.authenticatorName, + }, + }); + } + this.clear(); + return reject(); + } + + /** + * Clears both cookies related to the session: the server specified session + * cookie as well as the authenticator name cookie. + */ + clear() { + const path = this.cookiePath; + this.cookies.clear(this.cookieName, { path }); + this.cookies.clear(this.authenticatorCookieName, { path }); + } +} diff --git a/addons/auth/addon/session-stores/local-storage.js b/addons/auth/addon/session-stores/local-storage.js new file mode 100644 index 000000000..7c216fa55 --- /dev/null +++ b/addons/auth/addon/session-stores/local-storage.js @@ -0,0 +1,20 @@ +import LocalStorageSessionStore from 'ember-simple-auth/session-stores/local-storage'; + +/** + * + * This session store is intended for use with Ember Simple Auth when requesting + * that the server send a full token (e.g. cookies are not requested, + * see authenticators). + * + * NOTE: when using a local storage scheme, authorization is not provided + * automatically. In order to achieve authorization... TODO + * + * @example + * + * import LocalStorageSessionStore from 'auth/session-stores/local-storage'; + * export default class ApplicationSessionStore extends LocalStorageSessionStore { + * + * } + * + */ +export default class AuthLocalStorageSessionStore extends LocalStorageSessionStore {} diff --git a/addons/auth/package.json b/addons/auth/package.json index cdd1f5623..33199987a 100644 --- a/addons/auth/package.json +++ b/addons/auth/package.json @@ -33,16 +33,20 @@ "ember-cli-dependency-checker": "^3.2.0", "ember-cli-eslint": "^5.1.0", "ember-cli-inject-live-reload": "^2.0.2", + "ember-cli-mirage": "^1.1.7", "ember-cli-sri": "^2.1.1", "ember-cli-template-lint": "^1.0.0-beta.3", "ember-cli-uglify": "^3.0.0", + "ember-cookies": "^0.5.2", "ember-data": "^3.16.0", "ember-disable-prototype-extensions": "^1.1.3", "ember-export-application-global": "^2.0.1", + "ember-fetch": "^8.0.1", "ember-load-initializers": "^2.1.1", "ember-maybe-import-regenerator": "^0.1.6", "ember-qunit": "^4.6.0", "ember-resolver": "^7.0.0", + "ember-simple-auth": "^3.0.0", "ember-source": "~3.16.0", "ember-source-channel-url": "^2.0.1", "ember-try": "^1.4.0", diff --git a/addons/auth/tests/dummy/app/authenticators/password.js b/addons/auth/tests/dummy/app/authenticators/password.js new file mode 100644 index 000000000..e259039a3 --- /dev/null +++ b/addons/auth/tests/dummy/app/authenticators/password.js @@ -0,0 +1,13 @@ +import PasswordAuthenticator from 'auth/authenticators/password'; + +export default class ApplicationPasswordAuthenticator extends PasswordAuthenticator { + + buildAuthEndpointURL() { + return '/authenticate'; + } + + buildDeauthEndpointURL() { + return '/deauthenticate'; + } + +} diff --git a/addons/auth/tests/dummy/app/session-stores/cookie.js b/addons/auth/tests/dummy/app/session-stores/cookie.js new file mode 100644 index 000000000..50c1a4441 --- /dev/null +++ b/addons/auth/tests/dummy/app/session-stores/cookie.js @@ -0,0 +1,6 @@ +import CookieSessionStore from 'auth/session-stores/cookie'; + +export default class ApplicationCookieSessionStore extends CookieSessionStore { + cookiePath = '/'; + cookieName = 'my-cookie'; +} diff --git a/addons/auth/tests/dummy/app/session-stores/local-storage.js b/addons/auth/tests/dummy/app/session-stores/local-storage.js new file mode 100644 index 000000000..b7546d920 --- /dev/null +++ b/addons/auth/tests/dummy/app/session-stores/local-storage.js @@ -0,0 +1,3 @@ +import LocalStorageSessionStore from 'auth/session-stores/local-storage'; + +export default class ApplicationLocalStorageSessionStore extends LocalStorageSessionStore {} diff --git a/addons/auth/tests/dummy/mirage/config.js b/addons/auth/tests/dummy/mirage/config.js new file mode 100644 index 000000000..3e74fe7c0 --- /dev/null +++ b/addons/auth/tests/dummy/mirage/config.js @@ -0,0 +1,22 @@ +export default function () { + // These comments are here to help you get started. Feel free to delete them. + /* + Config (with defaults). + + Note: these only affect routes defined *after* them! + */ + // this.urlPrefix = ''; // make this `http://localhost:8080`, for example, if your API is on a different server + // this.namespace = ''; // make this `/api`, for example, if your API is namespaced + // this.timing = 400; // delay for each request, automatically set to 0 during testing + /* + Shorthand cheatsheet: + + this.get('/posts'); + this.post('/posts'); + this.get('/posts/:id'); + this.put('/posts/:id'); // or this.patch + this.del('/posts/:id'); + + https://www.ember-cli-mirage.com/docs/route-handlers/shorthands + */ +} diff --git a/addons/auth/tests/dummy/mirage/scenarios/default.js b/addons/auth/tests/dummy/mirage/scenarios/default.js new file mode 100644 index 000000000..d388296b5 --- /dev/null +++ b/addons/auth/tests/dummy/mirage/scenarios/default.js @@ -0,0 +1,7 @@ +export default function (/* server */) { + /* + Seed your development database using your factories. + This data will not be loaded in your tests. + */ + // server.createList('post', 10); +} diff --git a/addons/auth/tests/dummy/mirage/serializers/application.js b/addons/auth/tests/dummy/mirage/serializers/application.js new file mode 100644 index 000000000..6224ab8c9 --- /dev/null +++ b/addons/auth/tests/dummy/mirage/serializers/application.js @@ -0,0 +1,3 @@ +import { JSONAPISerializer } from 'ember-cli-mirage'; + +export default JSONAPISerializer.extend({}); diff --git a/addons/auth/tests/unit/authenticators/password-test.js b/addons/auth/tests/unit/authenticators/password-test.js new file mode 100644 index 000000000..aebdfee56 --- /dev/null +++ b/addons/auth/tests/unit/authenticators/password-test.js @@ -0,0 +1,90 @@ +import { module, test } from 'qunit'; +import { setupTest } from 'ember-qunit'; +import { Response } from 'miragejs'; +import setupMirage from 'ember-cli-mirage/test-support/setup-mirage'; + +module('Unit | Authenticator | password', function (hooks) { + setupTest(hooks); + setupMirage(hooks); + + test('it authenticates to the specified authEndpoint', async function (assert) { + assert.expect(2); + const authenticator = this.owner.lookup('authenticator:password'); + this.server.post(authenticator.buildAuthEndpointURL(), (schema, request) => { + const json = JSON.parse(request.requestBody); + assert.ok(json.token_type, 'Requested token cookies by default'); + return new Response(200); + }); + await authenticator.authenticate({}).then(() => { + assert.ok(true, 'authentication succeeded'); + }); + }); + + test('it can authenticate without requesting cookies', async function (assert) { + assert.expect(2); + const authenticator = this.owner.lookup('authenticator:password'); + this.server.post(authenticator.buildAuthEndpointURL(), (schema, request) => { + const json = JSON.parse(request.requestBody); + assert.notOk(json.token_type, 'Did not request tokens cookies'); + return new Response(200); + }); + await authenticator.authenticate({}, false).then(() => { + assert.ok(true, 'authentication succeeded'); + }); + }); + + test('it authenticates with the expected payload', async function (assert) { + assert.expect(1); + const authenticator = this.owner.lookup('authenticator:password'); + this.server.post(authenticator.buildAuthEndpointURL(), (schema, request) => { + const json = JSON.parse(request.requestBody); + assert.deepEqual(json, { + token_type: 'cookie', + credentials: { + name: 'foo', + password: 'bar', + }, + }); + return new Response(200); + }); + const creds = { + identification: 'foo', + password: 'bar', + }; + await authenticator.authenticate(creds); + }); + + test('it rejects if the endpoint sends an error status code', async function (assert) { + assert.expect(1); + const authenticator = this.owner.lookup('authenticator:password'); + this.server.post(authenticator.buildAuthEndpointURL(), () => { + return new Response(400); + }); + await authenticator.authenticate({}).catch(() => { + assert.ok(true, 'authentication failed'); + }); + }); + + test('it deauthenticates on invalidation', async function (assert) { + assert.expect(1); + const authenticator = this.owner.lookup('authenticator:password'); + this.server.post(authenticator.buildDeauthEndpointURL(), () => { + assert.ok(true, 'deauthentication occurred'); + return new Response(200); + }); + await authenticator.invalidate(); + }); + + test('it invalidation succeeds even if the deauthentication request fails', async function (assert) { + assert.expect(2); + const authenticator = this.owner.lookup('authenticator:password'); + this.server.post(authenticator.buildDeauthEndpointURL(), () => { + assert.ok(true, 'deauthentication was requested'); + return new Response(500); + }); + await authenticator + .invalidate() + .then(() => assert.ok(true)) + .catch(() => assert.notOk(true, 'uh oh, this should not happen')); + }); +}); diff --git a/addons/auth/tests/unit/session-stores/cookie-test.js b/addons/auth/tests/unit/session-stores/cookie-test.js new file mode 100644 index 000000000..464ebab52 --- /dev/null +++ b/addons/auth/tests/unit/session-stores/cookie-test.js @@ -0,0 +1,92 @@ +import { module, test } from 'qunit'; +import { setupTest } from 'ember-qunit'; + +module('Unit | Session Store | cookie', function (hooks) { + setupTest(hooks); + + let sessionStore; + + hooks.beforeEach(function () { + sessionStore = this.owner.lookup('session-store:cookie'); + sessionStore.clear(); + }); + + hooks.afterEach(function () { + sessionStore.clear(); + }); + + test('it restores a session if both necessary cookies are present (session exists)', async function (assert) { + assert.expect(2); + sessionStore.cookies.write(sessionStore.cookieName, 'token-value', { + path: '/', + }); + sessionStore.cookies.write( + sessionStore.authenticatorCookieName, + 'authenticator-name', + { path: '/' } + ); + assert.ok(sessionStore.sessionExists); + const session = await sessionStore.restore(); + assert.deepEqual(session, { + authenticated: { + authenticator: 'authenticator-name', + }, + }); + }); + + test('it rejects restore if the session does not exist', async function (assert) { + assert.expect(2); + assert.notOk(sessionStore.sessionExists); + await sessionStore.restore().catch(() => {}); + assert.ok(true, 'Rejected'); + }); + + test('it cleans up cookies on restore rejection', async function (assert) { + assert.expect(4); + // For session cookie + sessionStore.cookies.write(sessionStore.cookieName, 'token-value', { + path: '/', + }); + assert.ok( + sessionStore.cookies.exists(sessionStore.cookieName), + 'cookie exists' + ); + await sessionStore.restore().catch(() => {}); + assert.notOk( + sessionStore.cookies.exists(sessionStore.cookieName), + 'cookie was cleared' + ); + // For authenticator name cookie + sessionStore.cookies.write( + sessionStore.authenticatorCookieName, + 'authenticator-name', + { path: '/' } + ); + assert.ok( + sessionStore.cookies.exists(sessionStore.authenticatorCookieName), + 'cookie exists' + ); + await sessionStore.restore().catch(() => {}); + assert.notOk( + sessionStore.cookies.exists(sessionStore.authenticatorCookieName), + 'cookie was cleared' + ); + }); + + test('it persists only an authenticator name cookie, since the server persists the session cookie', async function (assert) { + assert.expect(4); + assert.notOk(sessionStore.cookies.exists(sessionStore.cookieName)); + assert.notOk( + sessionStore.cookies.exists(sessionStore.authenticatorCookieName) + ); + sessionStore.persist({ authenticated: { authenticator: 'foobar' } }); + assert.notOk( + sessionStore.cookies.exists(sessionStore.cookieName), + 'session cookie was not set' + ); + assert.equal( + sessionStore.cookies.read(sessionStore.authenticatorCookieName), + 'foobar' + ); + }); +}); diff --git a/addons/rose/addon/styles/rose/components/form/_input.scss b/addons/rose/addon/styles/rose/components/form/_input.scss index 6c0186b50..7b11a0a8e 100644 --- a/addons/rose/addon/styles/rose/components/form/_input.scss +++ b/addons/rose/addon/styles/rose/components/form/_input.scss @@ -19,6 +19,7 @@ .rose-form-input-field { @include type.type(m); + box-sizing: border-box; margin-bottom: sizing.rems(xs); padding: sizing.rems(xs); color: var(--stark); diff --git a/addons/rose/addon/styles/rose/variables/_media.scss b/addons/rose/addon/styles/rose/variables/_media.scss index 289c96ad9..95d1ab86d 100644 --- a/addons/rose/addon/styles/rose/variables/_media.scss +++ b/addons/rose/addon/styles/rose/variables/_media.scss @@ -9,7 +9,7 @@ $width-breakpoints: ( xlarge: 1200px, large: 1024px, medium: 768px, - small: 480px, + small: 540px, xsmall: 320px, ); diff --git a/package.json b/package.json index e455ef34b..f07bec9d6 100644 --- a/package.json +++ b/package.json @@ -24,7 +24,7 @@ "lint:addons:rose": "echo \"\n\nlint: addons/rose\n\" && cd addons/rose && yarn lint", "lint:ui:core": "echo \"\n\nlint: ui/core\n\" && cd ui/core && yarn lint", "lint:ui:connect-client": "echo \"\n\nlint: ui/connect-client\n\" && cd ui/connect-client && yarn lint", - "test": "yarn test:addons:api && yarn test:addons:auth && yarn test:addons:rose && yarn test:ui:core && yarn test:ui:connect-client", + "test": "yarn test:addons:api && yarn test:addons:auth && yarn test:addons:rose && yarn test:ui:core", "test:addons:api": "echo \"\n\ntest: addons/api\n\" && cd addons/api && yarn test", "test:addons:auth": "echo \"\n\ntest: addons/auth\n\" && cd addons/auth && yarn test", "test:addons:rose": "echo \"\n\ntest: addons/rose\n\" && cd addons/rose && yarn test", diff --git a/ui/core/app/authenticators/password.js b/ui/core/app/authenticators/password.js new file mode 100644 index 000000000..a70c89ac7 --- /dev/null +++ b/ui/core/app/authenticators/password.js @@ -0,0 +1,45 @@ +import BasePasswordAuthenticator from 'auth/authenticators/password'; +import { inject as service } from '@ember/service'; + +/** + * A username/password authenticator that authenticates with + * a scope/auth-method endpoint. Authentication occurs on an auth method + * endpoint, while deauthentication occurs on a scope endpoint. + */ +export default class PasswordAuthenticator extends BasePasswordAuthenticator { + // =services + + @service store; + + // =attributes + + /** + * Generates an auth method URL with which to authenticate. + * @override + * @param {object} options + * @param {string} options.scope.scope_id + * @param {string} options.authMethod.id + * @return {string} + */ + buildAuthEndpointURL({ + scope: { id: scopeID }, + authMethod: { id: authMethodID }, + }) { + const adapter = this.store.adapterFor('application'); + const options = { adapterOptions: { scopeID, method: 'authenticate' } }; + return adapter.buildURL('auth-method', authMethodID, options, 'findRecord'); + } + + /** + * Generates a scope URL with which to deauthenticate. + * @override + * @param {object} options + * @param {string} scopeID + * @return {string} + */ + buildDeauthEndpointURL({ org_id: scopeID }) { + const adapter = this.store.adapterFor('application'); + const options = { adapterOptions: { method: 'deauthenticate' } }; + return adapter.buildURL('scope', scopeID, options, 'findRecord'); + } +} diff --git a/ui/core/app/controllers/application.js b/ui/core/app/controllers/application.js new file mode 100644 index 000000000..565eb7d7a --- /dev/null +++ b/ui/core/app/controllers/application.js @@ -0,0 +1,8 @@ +import Controller from '@ember/controller'; +import { inject as service } from '@ember/service'; + +export default class ApplicationController extends Controller { + // =services + + @service session; +} diff --git a/ui/core/app/controllers/orgs/org/groups/group.js b/ui/core/app/controllers/orgs/org/groups/group.js deleted file mode 100644 index 972fb39ed..000000000 --- a/ui/core/app/controllers/orgs/org/groups/group.js +++ /dev/null @@ -1,12 +0,0 @@ -import Controller from '@ember/controller'; -import { alias } from '@ember/object/computed'; - -export default class OrgsOrgGroupsGroupController extends Controller { - // =attributes - - /** - * @type {string} - */ - @alias('model.displayName') breadCrumb; - -} diff --git a/ui/core/app/controllers/orgs/org/projects/project.js b/ui/core/app/controllers/orgs/org/projects/project.js deleted file mode 100644 index d56cf15d9..000000000 --- a/ui/core/app/controllers/orgs/org/projects/project.js +++ /dev/null @@ -1,13 +0,0 @@ -import Controller from '@ember/controller'; -import { alias } from '@ember/object/computed'; - -export default class OrgsOrgProjectsProjectController extends Controller { - - // =attributes - - /** - * @type {string} - */ - @alias('model.displayName') breadCrumb; - -} diff --git a/ui/core/app/controllers/orgs/org/projects/project/host-catalogs/host-catalog.js b/ui/core/app/controllers/orgs/org/projects/project/host-catalogs/host-catalog.js deleted file mode 100644 index 57d0c2724..000000000 --- a/ui/core/app/controllers/orgs/org/projects/project/host-catalogs/host-catalog.js +++ /dev/null @@ -1,13 +0,0 @@ -import Controller from '@ember/controller'; -import { alias } from '@ember/object/computed'; - -export default class OrgsOrgProjectsProjectHostCatalogsHostCatalogController extends Controller { - - // =attributes - - /** - * @type {string} - */ - @alias('model.displayName') breadCrumb; - -} diff --git a/ui/core/app/controllers/orgs/org/roles/role.js b/ui/core/app/controllers/orgs/org/roles/role.js deleted file mode 100644 index 23fe25882..000000000 --- a/ui/core/app/controllers/orgs/org/roles/role.js +++ /dev/null @@ -1,12 +0,0 @@ -import Controller from '@ember/controller'; -import { alias } from '@ember/object/computed'; - -export default class OrgsOrgRolesRoleController extends Controller { - // =attributes - - /** - * @type {string} - */ - @alias('model.displayName') breadCrumb; - -} diff --git a/ui/core/app/controllers/orgs/org/users/user.js b/ui/core/app/controllers/orgs/org/users/user.js deleted file mode 100644 index 3461021b1..000000000 --- a/ui/core/app/controllers/orgs/org/users/user.js +++ /dev/null @@ -1,11 +0,0 @@ -import Controller from '@ember/controller'; -import { alias } from '@ember/object/computed'; - -export default class OrgsOrgUsersUserController extends Controller { - // =attributes - - /** - * @type {string} - */ - @alias('model.displayName') breadCrumb; -} diff --git a/ui/core/app/controllers/scopes/scope.js b/ui/core/app/controllers/scopes/scope.js new file mode 100644 index 000000000..de850e223 --- /dev/null +++ b/ui/core/app/controllers/scopes/scope.js @@ -0,0 +1,8 @@ +import Controller from '@ember/controller'; +import { inject as service } from '@ember/service'; + +export default class ScopesScopeController extends Controller { + // =services + + @service session; +} diff --git a/ui/core/app/controllers/orgs/org/groups.js b/ui/core/app/controllers/scopes/scope/groups.js similarity index 70% rename from ui/core/app/controllers/orgs/org/groups.js rename to ui/core/app/controllers/scopes/scope/groups.js index b6c1716e9..000d18739 100644 --- a/ui/core/app/controllers/orgs/org/groups.js +++ b/ui/core/app/controllers/scopes/scope/groups.js @@ -1,7 +1,7 @@ import Controller from '@ember/controller'; import { inject as service } from '@ember/service'; -export default class OrgsOrgGroupsController extends Controller { +export default class ScopesScopeGroupsController extends Controller { // =services @service intl; @@ -9,6 +9,7 @@ export default class OrgsOrgGroupsController extends Controller { // =attributes /** + * Translated groups breadcrumb * @type {string} */ get breadCrumb() { diff --git a/ui/core/app/controllers/scopes/scope/groups/group.js b/ui/core/app/controllers/scopes/scope/groups/group.js new file mode 100644 index 000000000..dcd1e5296 --- /dev/null +++ b/ui/core/app/controllers/scopes/scope/groups/group.js @@ -0,0 +1,13 @@ +import Controller from '@ember/controller'; + +export default class ScopesScopeGroupsGroupController extends Controller { + // =attributes + + /** + * A group breadcrumb + * @type {string} + */ + get breadCrumb() { + return this.model.displayName; + } +} diff --git a/ui/core/app/controllers/orgs/org/projects/new.js b/ui/core/app/controllers/scopes/scope/groups/new.js similarity index 69% rename from ui/core/app/controllers/orgs/org/projects/new.js rename to ui/core/app/controllers/scopes/scope/groups/new.js index 5f9540189..0de96d821 100644 --- a/ui/core/app/controllers/orgs/org/projects/new.js +++ b/ui/core/app/controllers/scopes/scope/groups/new.js @@ -1,8 +1,7 @@ import Controller from '@ember/controller'; import { inject as service } from '@ember/service'; -export default class OrgsOrgProjectsNewController extends Controller { - +export default class ScopesScopeGroupsNewController extends Controller { // =services @service intl; @@ -10,10 +9,10 @@ export default class OrgsOrgProjectsNewController extends Controller { // =attributes /** + * Translated new group breadcrumb * @type {string} */ get breadCrumb() { return this.intl.t('titles.new'); } - } diff --git a/ui/core/app/controllers/orgs/org/projects.js b/ui/core/app/controllers/scopes/scope/projects.js similarity index 77% rename from ui/core/app/controllers/orgs/org/projects.js rename to ui/core/app/controllers/scopes/scope/projects.js index e1199f8e8..af28cf813 100644 --- a/ui/core/app/controllers/orgs/org/projects.js +++ b/ui/core/app/controllers/scopes/scope/projects.js @@ -1,8 +1,7 @@ import Controller from '@ember/controller'; import { inject as service } from '@ember/service'; -export default class OrgsOrgProjectsController extends Controller { - +export default class ScopesScopeProjectsController extends Controller { // =services @service intl; @@ -15,5 +14,4 @@ export default class OrgsOrgProjectsController extends Controller { get breadCrumb() { return this.intl.t('resources.projects'); } - } diff --git a/ui/core/app/controllers/orgs/org/users/new.js b/ui/core/app/controllers/scopes/scope/projects/new.js similarity index 76% rename from ui/core/app/controllers/orgs/org/users/new.js rename to ui/core/app/controllers/scopes/scope/projects/new.js index 5e047015a..d1f87399f 100644 --- a/ui/core/app/controllers/orgs/org/users/new.js +++ b/ui/core/app/controllers/scopes/scope/projects/new.js @@ -1,7 +1,7 @@ import Controller from '@ember/controller'; import { inject as service } from '@ember/service'; -export default class OrgsOrgUsersNewController extends Controller { +export default class ScopesScopeProjectsNewController extends Controller { // =services @service intl; diff --git a/ui/core/app/controllers/scopes/scope/projects/project.js b/ui/core/app/controllers/scopes/scope/projects/project.js new file mode 100644 index 000000000..8d1219267 --- /dev/null +++ b/ui/core/app/controllers/scopes/scope/projects/project.js @@ -0,0 +1,12 @@ +import Controller from '@ember/controller'; + +export default class ScopesScopeProjectsProjectController extends Controller { + // =attributes + + /** + * @type {string} + */ + get breadCrumb() { + return this.model.displayName; + } +} diff --git a/ui/core/app/controllers/orgs/org/projects/project/host-catalogs.js b/ui/core/app/controllers/scopes/scope/projects/project/host-catalogs.js similarity index 74% rename from ui/core/app/controllers/orgs/org/projects/project/host-catalogs.js rename to ui/core/app/controllers/scopes/scope/projects/project/host-catalogs.js index 80dde2c39..854de55e2 100644 --- a/ui/core/app/controllers/orgs/org/projects/project/host-catalogs.js +++ b/ui/core/app/controllers/scopes/scope/projects/project/host-catalogs.js @@ -1,7 +1,7 @@ import Controller from '@ember/controller'; import { inject as service } from '@ember/service'; -export default class OrgsOrgProjectsProjectHostCatalogsController extends Controller { +export default class ScopesScopeProjectsProjectHostCatalogsController extends Controller { // =services diff --git a/ui/core/app/controllers/scopes/scope/projects/project/host-catalogs/host-catalog.js b/ui/core/app/controllers/scopes/scope/projects/project/host-catalogs/host-catalog.js new file mode 100644 index 000000000..97fa6533b --- /dev/null +++ b/ui/core/app/controllers/scopes/scope/projects/project/host-catalogs/host-catalog.js @@ -0,0 +1,14 @@ +import Controller from '@ember/controller'; + +export default class ScopesScopeProjectsProjectHostCatalogsHostCatalogController extends Controller { + + // =attributes + + /** + * @type {string} + */ + get breadCrumb() { + return this.model.displayName; + } + +} diff --git a/ui/core/app/controllers/orgs/org/groups/new.js b/ui/core/app/controllers/scopes/scope/projects/project/host-catalogs/new.js similarity index 65% rename from ui/core/app/controllers/orgs/org/groups/new.js rename to ui/core/app/controllers/scopes/scope/projects/project/host-catalogs/new.js index 9ca97fbe5..549965fb3 100644 --- a/ui/core/app/controllers/orgs/org/groups/new.js +++ b/ui/core/app/controllers/scopes/scope/projects/project/host-catalogs/new.js @@ -1,7 +1,8 @@ import Controller from '@ember/controller'; import { inject as service } from '@ember/service'; -export default class OrgsOrgGroupsNewController extends Controller { +export default class ScopesScopeProjectsProjectHostCatalogsNewController extends Controller { + // =services @service intl; @@ -9,6 +10,7 @@ export default class OrgsOrgGroupsNewController extends Controller { // =attributes /** + * Translated new role breadcrumb * @type {string} */ get breadCrumb() { diff --git a/ui/core/app/controllers/orgs/org/roles.js b/ui/core/app/controllers/scopes/scope/roles.js similarity index 71% rename from ui/core/app/controllers/orgs/org/roles.js rename to ui/core/app/controllers/scopes/scope/roles.js index 7f62b0e1a..bbd84249e 100644 --- a/ui/core/app/controllers/orgs/org/roles.js +++ b/ui/core/app/controllers/scopes/scope/roles.js @@ -1,7 +1,7 @@ import Controller from '@ember/controller'; import { inject as service } from '@ember/service'; -export default class OrgsOrgRolesController extends Controller { +export default class ScopesScopeRolesController extends Controller { // =services @service intl; @@ -9,10 +9,10 @@ export default class OrgsOrgRolesController extends Controller { // =attributes /** + * Translated roles breadcrumb * @type {string} */ get breadCrumb() { return this.intl.t('resources.roles'); } - } diff --git a/ui/core/app/controllers/orgs/org/projects/project/host-catalogs/new.js b/ui/core/app/controllers/scopes/scope/roles/new.js similarity index 69% rename from ui/core/app/controllers/orgs/org/projects/project/host-catalogs/new.js rename to ui/core/app/controllers/scopes/scope/roles/new.js index 4fb2aed63..598ec9760 100644 --- a/ui/core/app/controllers/orgs/org/projects/project/host-catalogs/new.js +++ b/ui/core/app/controllers/scopes/scope/roles/new.js @@ -1,8 +1,7 @@ import Controller from '@ember/controller'; import { inject as service } from '@ember/service'; -export default class OrgsOrgProjectsProjectHostCatalogsNewController extends Controller { - +export default class ScopesScopeRolesNewController extends Controller { // =services @service intl; @@ -10,10 +9,10 @@ export default class OrgsOrgProjectsProjectHostCatalogsNewController extends Con // =attributes /** + * Translated new role breadcrumb * @type {string} */ get breadCrumb() { return this.intl.t('titles.new'); } - } diff --git a/ui/core/app/controllers/scopes/scope/roles/role.js b/ui/core/app/controllers/scopes/scope/roles/role.js new file mode 100644 index 000000000..39ba35717 --- /dev/null +++ b/ui/core/app/controllers/scopes/scope/roles/role.js @@ -0,0 +1,13 @@ +import Controller from '@ember/controller'; + +export default class ScopesScopeRolesRoleController extends Controller { + // =attributes + + /** + * A role breadcrumb + * @type {string} + */ + get breadCrumb() { + return this.model.displayName; + } +} diff --git a/ui/core/app/controllers/orgs/org/users.js b/ui/core/app/controllers/scopes/scope/users.js similarity index 71% rename from ui/core/app/controllers/orgs/org/users.js rename to ui/core/app/controllers/scopes/scope/users.js index ae81e0302..c88b342fc 100644 --- a/ui/core/app/controllers/orgs/org/users.js +++ b/ui/core/app/controllers/scopes/scope/users.js @@ -1,7 +1,7 @@ import Controller from '@ember/controller'; import { inject as service } from '@ember/service'; -export default class OrgsOrgUsersController extends Controller { +export default class ScopesScopeUsersController extends Controller { // =services @service intl; @@ -9,10 +9,10 @@ export default class OrgsOrgUsersController extends Controller { // =attributes /** + * Translated users breadcrumb * @type {string} */ get breadCrumb() { return this.intl.t('resources.users'); } - } diff --git a/ui/core/app/controllers/orgs/org/roles/new.js b/ui/core/app/controllers/scopes/scope/users/new.js similarity index 69% rename from ui/core/app/controllers/orgs/org/roles/new.js rename to ui/core/app/controllers/scopes/scope/users/new.js index 38a098eaa..91d3f0cb4 100644 --- a/ui/core/app/controllers/orgs/org/roles/new.js +++ b/ui/core/app/controllers/scopes/scope/users/new.js @@ -1,7 +1,7 @@ import Controller from '@ember/controller'; import { inject as service } from '@ember/service'; -export default class OrgsOrgRolesNewController extends Controller { +export default class ScopesScopeUsersNewController extends Controller { // =services @service intl; @@ -9,10 +9,10 @@ export default class OrgsOrgRolesNewController extends Controller { // =attributes /** + * Translated new user breadcrumb * @type {string} */ get breadCrumb() { return this.intl.t('titles.new'); } - } diff --git a/ui/core/app/controllers/scopes/scope/users/user.js b/ui/core/app/controllers/scopes/scope/users/user.js new file mode 100644 index 000000000..6f7d42c7a --- /dev/null +++ b/ui/core/app/controllers/scopes/scope/users/user.js @@ -0,0 +1,13 @@ +import Controller from '@ember/controller'; + +export default class ScopesScopeUsersUserController extends Controller { + // =attributes + + /** + * An user breadcrumb + * @type {string} + */ + get breadCrumb() { + return this.model.displayName; + } +} diff --git a/ui/core/app/router.js b/ui/core/app/router.js index 61566937f..a4f121c19 100644 --- a/ui/core/app/router.js +++ b/ui/core/app/router.js @@ -7,32 +7,32 @@ export default class Router extends EmberRouter { } Router.map(function () { - this.route('orgs', function () { - this.route('org', { path: ':org_id' }, function () { - this.route('projects', function () { + this.route('scopes', function () { + this.route('scope', { path: ':scope_id' }, function () { + this.route('authenticate', function () { + this.route('method', { path: ':auth_method_id' }); + }); + this.route('users', function () { + this.route('user', { path: ':user_id' }, function () {}); + this.route('new'); + }); + this.route('groups', function () { + this.route('group', { path: ':group_id' }, function () {}); + this.route('new'); + }); + this.route('roles', function () { + this.route('role', { path: ':role_id' }, function () {}); + this.route('new'); + }); + this.route('projects', function() { this.route('project', { path: ':project_id' }, function() { this.route('host-catalogs', function() { - this.route('host-catalog', { path: ':host_catalog_id' }, function() {}); this.route('new'); + this.route('host-catalog', { path: ':host_catalog_id' }, function() {}); }); }); this.route('new'); }); - - this.route('users', function() { - this.route('user', { path: ':user_id' }, function() {}); - this.route('new'); - }); - - this.route('roles', function() { - this.route('role', { path: ':role_id' }, function() {}); - this.route('new'); - }); - - this.route('groups', function() { - this.route('group', { path: ':group_id' }, function() {}); - this.route('new'); - }); }); }); }); diff --git a/ui/core/app/routes/application.js b/ui/core/app/routes/application.js index 6cc5ca574..e67f524e1 100644 --- a/ui/core/app/routes/application.js +++ b/ui/core/app/routes/application.js @@ -1,6 +1,62 @@ import Route from '@ember/routing/route'; +import ApplicationRouteMixin from 'ember-simple-auth/mixins/application-route-mixin'; +import Ember from 'ember'; +import { getOwner } from '@ember/application'; +import { inject as service } from '@ember/service'; +import { action } from '@ember/object'; +import { later } from '@ember/runloop'; /** * Entry route for the application. */ -export default class ApplicationRoute extends Route {} +export default class ApplicationRoute extends Route.extend( + ApplicationRouteMixin +) { + // =services + + @service session; + + // =attributes + + routeIfUnauthenticated = 'index'; + + // =methods + + /** + * After becoming authenticated, does nothing. This overrides the default + * behavior of the ApplicationRouteMixin, which is to redirect after auth. + * We'll handle this redirect manually in sub routes. + * @override + */ + sessionAuthenticated() { + // no op + } + + /** + * When the session ends, redirect to authenticate and reload the page to + * purge any in-memory state. + */ + async sessionInvalidated() { + // Catch error in this transition, since it will be aborted by the + // scope auth route when it redirects to the first auth method. + await this.transitionTo(this.routeIfUnauthenticated).catch(() => {}); + // The Ember way of accessing globals... + const document = getOwner(this).lookup('service:-document').documentElement; + // defaultView === window, but without using globals directly + const { location } = document.parentNode.defaultView; + // Wait a beat, then reload the page... + // This is mostly to give the deauth request a chance to fire. + /* istanbul ignore if */ + if (!Ember.testing) later(location, location.reload, 150); + } + + // =actions + + /** + * Delegates invalidation to the session service. + */ + @action + invalidateSession() { + this.session.invalidate(); + } +} diff --git a/ui/core/app/routes/index.js b/ui/core/app/routes/index.js new file mode 100644 index 000000000..c8728d037 --- /dev/null +++ b/ui/core/app/routes/index.js @@ -0,0 +1,12 @@ +import Route from '@ember/routing/route'; + +export default class IndexRoute extends Route { + // =methods + + /** + * Redirects to scopes route for further processing. + */ + redirect() { + this.transitionTo('scopes'); + } +} diff --git a/ui/core/app/routes/orgs.js b/ui/core/app/routes/orgs.js deleted file mode 100644 index 199076b0b..000000000 --- a/ui/core/app/routes/orgs.js +++ /dev/null @@ -1,15 +0,0 @@ -import Route from '@ember/routing/route'; - -export default class OrgsRoute extends Route { - - // =methods - - /** - * Returns all orgs from the store. - * @return {Promise[OrgModel]} - */ - model() { - return this.store.findAll('org'); - } - -} diff --git a/ui/core/app/routes/orgs/org.js b/ui/core/app/routes/orgs/org.js deleted file mode 100644 index 59055906e..000000000 --- a/ui/core/app/routes/orgs/org.js +++ /dev/null @@ -1,28 +0,0 @@ -import Route from '@ember/routing/route'; -import { inject as service } from '@ember/service'; - -export default class OrgsOrgRoute extends Route { - // =services - - @service scope; - - // =methods - - /** - * Returns an org by id. - * @param {object} params - * @return {Promise{OrgModel}} - */ - model({ org_id: id }) { - return this.store.findRecord('org', id); - } - - /** - * Adds the org to the scope. - * @param {object} org - */ - afterModel(org) { - this.scope.org = org; - return super.afterModel(...arguments); - } -} diff --git a/ui/core/app/routes/orgs/org/groups.js b/ui/core/app/routes/orgs/org/groups.js deleted file mode 100644 index 80bb0af1a..000000000 --- a/ui/core/app/routes/orgs/org/groups.js +++ /dev/null @@ -1,13 +0,0 @@ -import Route from '@ember/routing/route'; - -export default class OrgsOrgGroupsRoute extends Route { - // =methods - - /** - * Returns all groups from store. - * @return {Promise[GroupModel]} - */ - model() { - return this.store.findAll('group'); - } -} diff --git a/ui/core/app/routes/orgs/org/groups/group.js b/ui/core/app/routes/orgs/org/groups/group.js deleted file mode 100644 index 98c6b65c9..000000000 --- a/ui/core/app/routes/orgs/org/groups/group.js +++ /dev/null @@ -1,64 +0,0 @@ -import Route from '@ember/routing/route'; -import { inject as service } from '@ember/service'; -import { action } from '@ember/object'; - -export default class OrgsOrgGroupsGroupRoute extends Route { - // =services - - @service intl; - @service notify; - - // =methods - - /** - * Return a group with id - * @param {object} params - * @return {Promise{GroupModel}} - */ - model({ group_id: id }) { - return this.store.findRecord('group', id); - } - - // =actions - - /** - * Rollback changes on group. - * @param {GroupModel} group - */ - @action - cancel(group) { - group.rollbackAttributes(); - } - - /** - * Handle saving a group. - * @param {GroupModel} group - * @param {Event} e - */ - @action - async save(group) { - try { - await group.save(); - this.notify.success(this.intl.t('notify.save-success')); - } catch (error) { - // TODO: replace with translated strings - this.notify.error(error.message, { closeAfter: null }); - } - } - - /** - * Delete group and redirect to groups index. - * @param {GroupModel} group - */ - @action - async delete(group) { - try { - await group.destroyRecord(); - this.transitionTo('orgs.org.groups'); - this.notify.success(this.intl.t('notify.group.delete-success')); - } catch (error) { - // TODO: replace with translated strings - this.notify.error(error.message, { closeAfter: null }); - } - } -} diff --git a/ui/core/app/routes/orgs/org/groups/group/index.js b/ui/core/app/routes/orgs/org/groups/group/index.js deleted file mode 100644 index 521f73a9c..000000000 --- a/ui/core/app/routes/orgs/org/groups/group/index.js +++ /dev/null @@ -1,4 +0,0 @@ -import Route from '@ember/routing/route'; - -export default class OrgsOrgGroupsGroupIndexRoute extends Route { -} diff --git a/ui/core/app/routes/orgs/org/groups/index.js b/ui/core/app/routes/orgs/org/groups/index.js deleted file mode 100644 index 9c566dfa0..000000000 --- a/ui/core/app/routes/orgs/org/groups/index.js +++ /dev/null @@ -1,3 +0,0 @@ -import Route from '@ember/routing/route'; - -export default class OrgsOrgGroupsIndexRoute extends Route {} diff --git a/ui/core/app/routes/orgs/org/groups/new.js b/ui/core/app/routes/orgs/org/groups/new.js deleted file mode 100644 index 6f353d368..000000000 --- a/ui/core/app/routes/orgs/org/groups/new.js +++ /dev/null @@ -1,50 +0,0 @@ -import Route from '@ember/routing/route'; -import { inject as service } from '@ember/service'; -import { action } from '@ember/object'; - -export default class OrgsOrgGroupsNewRoute extends Route { - // =services - - @service intl; - @service notify; - - // =methods - - /** - * Returns a new unsaved group. - * @return {Promise{GroupModel}} - */ - model() { - return this.store.createRecord('group'); - } - - // =actions - - /** - * Rollback changes on group by destroying unsaved instance - * and redirect to group index. - * @param {GroupModel} group - */ - @action - cancel(group) { - group.rollbackAttributes(); - this.transitionTo('orgs.org.groups'); - } - - /** - * Handle saving a group. - * @param {GroupModel} group - * @param {Event} e - */ - @action - async save(group) { - try { - await group.save(); - this.transitionTo('orgs.org.groups.group', group); - this.notify.success(this.intl.t('notify.group.create-success')); - } catch (error) { - // TODO: replace with translated strings - this.notify.error(error.message, { closeAfter: null }); - } - } -} diff --git a/ui/core/app/routes/orgs/org/projects.js b/ui/core/app/routes/orgs/org/projects.js deleted file mode 100644 index ee50e8f8a..000000000 --- a/ui/core/app/routes/orgs/org/projects.js +++ /dev/null @@ -1,13 +0,0 @@ -import Route from '@ember/routing/route'; - -export default class OrgsOrgProjectsRoute extends Route { - // =methods - - /** - * Returns all projects from the store. - * @return {Promise[ProjectModel]} - */ - model() { - return this.store.findAll('project'); - } -} diff --git a/ui/core/app/routes/orgs/org/projects/index.js b/ui/core/app/routes/orgs/org/projects/index.js deleted file mode 100644 index c22022cf9..000000000 --- a/ui/core/app/routes/orgs/org/projects/index.js +++ /dev/null @@ -1,3 +0,0 @@ -import Route from '@ember/routing/route'; - -export default class OrgsOrgProjectsIndexRoute extends Route { } diff --git a/ui/core/app/routes/orgs/org/projects/new.js b/ui/core/app/routes/orgs/org/projects/new.js deleted file mode 100644 index 1314716cd..000000000 --- a/ui/core/app/routes/orgs/org/projects/new.js +++ /dev/null @@ -1,50 +0,0 @@ -import Route from '@ember/routing/route'; -import { inject as service } from '@ember/service'; -import { action } from '@ember/object'; - -export default class OrgsOrgProjectsNewRoute extends Route { - // =services - - @service notify; - - // =methods - - /** - * Returns a new unsaved project. - * @return {Promise{ProjectModel}} - */ - model() { - return this.store.createRecord('project'); - } - - // =actions - - /** - * Rollback changes on project (which destroys unsaved instances) - * and redirects to projects index. - * @param {ProjectModel} project - */ - @action - cancel(project) { - project.rollbackAttributes(); - this.transitionTo('orgs.org.projects'); - } - - /** - * Handle save project. - * @param {ProjectModel} project - * @param {Event} e - */ - @action - async save(project) { - try { - await project.save(); - this.transitionTo('orgs.org.projects.project', project); - // TODO: replace with translated strings - this.notify.success('Project created.'); - } catch (error) { - // TODO: replace with translated strings - this.notify.error(error.message, { closeAfter: null }); - } - } -} diff --git a/ui/core/app/routes/orgs/org/projects/project.js b/ui/core/app/routes/orgs/org/projects/project.js deleted file mode 100644 index 8f06533bc..000000000 --- a/ui/core/app/routes/orgs/org/projects/project.js +++ /dev/null @@ -1,90 +0,0 @@ -import Route from '@ember/routing/route'; -import { inject as service } from '@ember/service'; -import { action } from '@ember/object'; - -export default class OrgsOrgProjectsProjectRoute extends Route { - // =services - - @service scope; - @service notify; - - // =methods - - /** - * Returns a project by id. - * @param {object} params - * @return {Promise{ProjectModel}} - */ - model({ project_id: id }) { - return this.store.findRecord('project', id); - } - - /** - * Adds the project to the scope. - * @param {ProjectModel} project - */ - afterModel(project) { - this.scope.project = project; - return super.afterModel(...arguments); - } - - /** - * Renders the project-specific sidebar template. - * @override - * @param {object} controller - * @param {object} model - */ - renderTemplate(controller, model) { - this.render('orgs/org/projects/project/_sidebar', { - into: 'orgs/org', - outlet: 'sidebar', - model: model - }); - super.renderTemplate(...arguments); - } - - // =actions - - /** - * Rollback changes on project. - * @param {ProjectModel} project - */ - @action - cancel(project) { - project.rollbackAttributes(); - } - - /** - * Handle save project. - * @param {ProjectModel} project - * @param {Event} e - */ - @action - async save(project) { - try { - await project.save(); - // TODO: replace with translated strings - this.notify.success('Save succeeded.'); - } catch (error) { - // TODO: replace with translated strings - this.notify.error(error.message, { closeAfter: null }); - } - } - - /** - * Deletes the project and redirects to index. - * @param {ProjectModel} project - */ - @action - async delete(project) { - try { - await project.destroyRecord(); - this.transitionTo('orgs.org.projects'); - // TODO: replace with translated strings - this.notify.success('Deleted project succesfully.'); - } catch (error) { - // TODO: replace with translated strings - this.notify.error(error.message, { closeAfter: null }); - } - } -} diff --git a/ui/core/app/routes/orgs/org/projects/project/host-catalogs.js b/ui/core/app/routes/orgs/org/projects/project/host-catalogs.js deleted file mode 100644 index eacc28056..000000000 --- a/ui/core/app/routes/orgs/org/projects/project/host-catalogs.js +++ /dev/null @@ -1,14 +0,0 @@ -import Route from '@ember/routing/route'; - -export default class OrgsOrgProjectsProjectHostCatalogsRoute extends Route { - - // =methods - - /** - * Returns host catalogs for the current scope. - * @return {Promise[HostCatalogModel]} - */ - model() { - return this.store.findAll('host-catalog'); - } -} diff --git a/ui/core/app/routes/orgs/org/projects/project/host-catalogs/host-catalog/index.js b/ui/core/app/routes/orgs/org/projects/project/host-catalogs/host-catalog/index.js deleted file mode 100644 index ffba38db4..000000000 --- a/ui/core/app/routes/orgs/org/projects/project/host-catalogs/host-catalog/index.js +++ /dev/null @@ -1,4 +0,0 @@ -import Route from '@ember/routing/route'; - -export default class OrgsOrgProjectsProjectHostCatalogsHostCatalogIndexRoute extends Route { -} diff --git a/ui/core/app/routes/orgs/org/projects/project/host-catalogs/index.js b/ui/core/app/routes/orgs/org/projects/project/host-catalogs/index.js deleted file mode 100644 index a29543a30..000000000 --- a/ui/core/app/routes/orgs/org/projects/project/host-catalogs/index.js +++ /dev/null @@ -1,4 +0,0 @@ -import Route from '@ember/routing/route'; - -export default class OrgsOrgProjectsProjectHostCatalogsIndexRoute extends Route { -} diff --git a/ui/core/app/routes/orgs/org/projects/project/host-catalogs/new.js b/ui/core/app/routes/orgs/org/projects/project/host-catalogs/new.js deleted file mode 100644 index a2f4e2bc6..000000000 --- a/ui/core/app/routes/orgs/org/projects/project/host-catalogs/new.js +++ /dev/null @@ -1,50 +0,0 @@ -import Route from '@ember/routing/route'; -import { inject as service } from '@ember/service'; -import { action } from '@ember/object'; - -export default class OrgsOrgProjectsProjectHostCatalogsNewRoute extends Route { - // =services - - @service notify; - - // =methods - - /** - * Returns a new unsaved host catalog. - * @return {Promise{HostCatalogModel}} - */ - model() { - return this.store.createRecord('hostCatalog'); - } - - // =actions - - /** - * Rollback changes on host catalog (which destroys unsaved instances) - * and redirects to host catalogs index. - * @param {HostCatalogModel} hostCatalog - */ - @action - cancel(hostCatalog) { - hostCatalog.rollbackAttributes(); - this.transitionTo('orgs.org.projects.project.host-catalogs'); - } - - /** - * Handle save host catalog. - * @param {HostCatalogModel} hostCatalog - * @param {Event} e - */ - @action - async save(hostCatalog) { - try { - await hostCatalog.save(); - this.transitionTo('orgs.org.projects.project.host-catalogs.host-catalog', hostCatalog); - // TODO: replace with translated strings - this.notify.success('Host catalog created.'); - } catch (error) { - // TODO: replace with translated strings - this.notify.error(error.message, { closeAfter: null }); - } - } -} diff --git a/ui/core/app/routes/orgs/org/projects/project/index.js b/ui/core/app/routes/orgs/org/projects/project/index.js deleted file mode 100644 index 0843242d7..000000000 --- a/ui/core/app/routes/orgs/org/projects/project/index.js +++ /dev/null @@ -1,4 +0,0 @@ -import Route from '@ember/routing/route'; - -export default class OrgsOrgProjectsProjectIndexRoute extends Route { -} diff --git a/ui/core/app/routes/orgs/org/roles.js b/ui/core/app/routes/orgs/org/roles.js deleted file mode 100644 index 807c2aeee..000000000 --- a/ui/core/app/routes/orgs/org/roles.js +++ /dev/null @@ -1,14 +0,0 @@ -import Route from '@ember/routing/route'; - -export default class OrgsOrgRolesRoute extends Route { - // =methods - - /** - * Returns all roles from store. - * @return {Promise[RoleModel]} - */ - model() { - return this.store.findAll('role'); - } - -} diff --git a/ui/core/app/routes/orgs/org/roles/index.js b/ui/core/app/routes/orgs/org/roles/index.js deleted file mode 100644 index 92a715a56..000000000 --- a/ui/core/app/routes/orgs/org/roles/index.js +++ /dev/null @@ -1,3 +0,0 @@ -import Route from '@ember/routing/route'; - -export default class OrgsOrgRolesIndexRoute extends Route {} diff --git a/ui/core/app/routes/orgs/org/roles/new.js b/ui/core/app/routes/orgs/org/roles/new.js deleted file mode 100644 index cad253804..000000000 --- a/ui/core/app/routes/orgs/org/roles/new.js +++ /dev/null @@ -1,50 +0,0 @@ -import Route from '@ember/routing/route'; -import { inject as service } from '@ember/service'; -import { action } from '@ember/object'; - -export default class OrgsOrgRolesNewRoute extends Route { - // =services - - @service intl; - @service notify; - - // =methods - - /** - * Returns a new unsaved role. - * @return {Promise{RoleModel}} - */ - model() { - return this.store.createRecord('role'); - } - - // =actions - - /** - * Rollback changes on role by destroying unsaved instance - * and redirect to role index. - * @param {RoleModel} role - */ - @action - cancel(role) { - role.rollbackAttributes(); - this.transitionTo('orgs.org.roles'); - } - - /** - * Handle saving a role. - * @param {RoleModel} role - * @param {Event} e - */ - @action - async save(role) { - try { - await role.save(); - this.transitionTo('orgs.org.roles.role', role); - this.notify.success(this.intl.t('notify.role.create-success')); - } catch (error) { - // TODO: replace with translated strings - this.notify.error(error.message, { closeAfter: null }); - } - } -} diff --git a/ui/core/app/routes/orgs/org/roles/role.js b/ui/core/app/routes/orgs/org/roles/role.js deleted file mode 100644 index 1820f2ba1..000000000 --- a/ui/core/app/routes/orgs/org/roles/role.js +++ /dev/null @@ -1,64 +0,0 @@ -import Route from '@ember/routing/route'; -import { inject as service } from '@ember/service'; -import { action } from '@ember/object'; - -export default class OrgsOrgRolesRoleRoute extends Route { - // =services - - @service intl; - @service notify; - - // =methods - - /** - * Return a role with id - * @param {object} params - * @return {Promise{RoleModel}} - */ - model({ role_id: id }) { - return this.store.findRecord('role', id); - } - - // =actions - - /** - * Rollback changes on role. - * @param {RoleModel} role - */ - @action - cancel(role) { - role.rollbackAttributes(); - } - - /** - * Handle saving a role. - * @param {RoleModel} role - * @param {Event} e - */ - @action - async save(role) { - try { - await role.save(); - this.notify.success(this.intl.t('notify.save-success')); - } catch (error) { - // TODO: replace with translated strings - this.notify.error(error.message, { closeAfter: null }); - } - } - - /** - * Delete role and redirect to roles index. - * @param {RoleModel} role - */ - @action - async delete(role) { - try { - await role.destroyRecord(); - this.transitionTo('orgs.org.roles'); - this.notify.success(this.intl.t('notify.role.delete-success')); - } catch (error) { - // TODO: replace with translated strings - this.notify.error(error.message, { closeAfter: null }); - } - } -} diff --git a/ui/core/app/routes/orgs/org/users.js b/ui/core/app/routes/orgs/org/users.js deleted file mode 100644 index 7833312e3..000000000 --- a/ui/core/app/routes/orgs/org/users.js +++ /dev/null @@ -1,13 +0,0 @@ -import Route from '@ember/routing/route'; - -export default class OrgsOrgUsersRoute extends Route { - // =methods - - /** - * Returns all users from the store. - * @return {Promise[UserModel]} - */ - model() { - return this.store.findAll('user'); - } -} diff --git a/ui/core/app/routes/orgs/org/users/index.js b/ui/core/app/routes/orgs/org/users/index.js deleted file mode 100644 index b6203b0d4..000000000 --- a/ui/core/app/routes/orgs/org/users/index.js +++ /dev/null @@ -1,4 +0,0 @@ -import Route from '@ember/routing/route'; - -export default class OrgsOrgUsersIndexRoute extends Route { -} diff --git a/ui/core/app/routes/orgs/org/users/new.js b/ui/core/app/routes/orgs/org/users/new.js deleted file mode 100644 index a740f6caf..000000000 --- a/ui/core/app/routes/orgs/org/users/new.js +++ /dev/null @@ -1,50 +0,0 @@ -import Route from '@ember/routing/route'; -import { inject as service } from '@ember/service'; -import { action } from '@ember/object'; - -export default class OrgsOrgUsersNewRoute extends Route { - // =services - - @service intl; - @service notify; - - // =methods - - /** - * Returns a new unsaved user. - * @return {Promise{UserModel}} - */ - model() { - return this.store.createRecord('user'); - } - - // =actions - - /** - * Rollback changes on user to destroy unsaved instance - * and redirect to users index. - * @param {UserModel} user - */ - @action - cancel(user) { - user.rollbackAttributes(); - this.transitionTo('orgs.org.users'); - } - - /** - * Handle saving a user. - * @param {UserModel} user - * @param {Event} e - */ - @action - async save(user) { - try { - await user.save(); - this.transitionTo('orgs.org.users.user', user); - this.notify.success(this.intl.t('notify.user.create-success')); - } catch (error) { - // TODO: replace with translated strings - this.notify.error(error.message, { closeAfter: null }); - } - } -} diff --git a/ui/core/app/routes/scopes.js b/ui/core/app/routes/scopes.js new file mode 100644 index 000000000..337b319c9 --- /dev/null +++ b/ui/core/app/routes/scopes.js @@ -0,0 +1,22 @@ +import Route from '@ember/routing/route'; +import { A } from '@ember/array'; + +export default class ScopesRoute extends Route { + // =methods + + /** + * Attempt to load the specified scope from the API. This is allowed + * to fail, since in some cases the user may not have permission to read a + * scope directly, but may have permission to read resources under it. + * If the scope fails to load, we still proceed using a temporary scope object + * consisting of only the specified ID. + * @param {object} params + * @param {string} params.scope_id + * @return {Promise{ScopeModel}} + */ + model() { + // NOTE: In the absence of a `scope_id` query parameter, this endpoint is + // expected to default to the global scope, thus returning org scopes. + return this.store.findAll('scope').catch(() => A([])); + } +} diff --git a/ui/core/app/routes/scopes/index.js b/ui/core/app/routes/scopes/index.js new file mode 100644 index 000000000..562a27a15 --- /dev/null +++ b/ui/core/app/routes/scopes/index.js @@ -0,0 +1,29 @@ +import Route from '@ember/routing/route'; +import { inject as service } from '@ember/service'; +import { get } from '@ember/object'; + +export default class ScopesIndexRoute extends Route { + // =services + + @service session; + + // =methods + + /** + * If authenticated, redirects to the scope route of the authenticted scope. + * If unauthenticated, redirects to the first scope that was loaded (if any). + * @param {[ScopeModel]} model + * @param {?ScopeModel} model.firstObject + */ + redirect({ firstObject }) { + const authenticatedScopeID = get( + this.session, + 'data.authenticated.scope.id' + ); + if (authenticatedScopeID) { + this.transitionTo('scopes.scope', authenticatedScopeID); + } else if (firstObject) { + this.transitionTo('scopes.scope', firstObject.id); + } + } +} diff --git a/ui/core/app/routes/scopes/scope.js b/ui/core/app/routes/scopes/scope.js new file mode 100644 index 000000000..2707a238b --- /dev/null +++ b/ui/core/app/routes/scopes/scope.js @@ -0,0 +1,24 @@ +import Route from '@ember/routing/route'; + +export default class ScopesScopeRoute extends Route { + // =methods + + /** + * Attempt to load the specified scope from the API. This is allowed + * to fail, since in some cases the user may not have permission to read a + * scope directly, but may have permission to read resources under it. + * If the scope fails to load, we still proceed using a temporary scope object + * consisting of only the specified ID. + * @param {object} params + * @param {string} params.scope_id + * @return {Promise{ScopeModel}} + */ + model({ scope_id: id }) { + return this.store.findRecord('scope', id).catch(() => { + const maybeExistingScope = this.store.peekRecord('scope', id); + // TODO it's unclear if creating a temporary unsaved scope is reliable + // we may need to revert to a POJO. + return maybeExistingScope || this.store.createRecord('scope', { id }); + }); + } +} diff --git a/ui/core/app/routes/scopes/scope/authenticate.js b/ui/core/app/routes/scopes/scope/authenticate.js new file mode 100644 index 000000000..d7dae560d --- /dev/null +++ b/ui/core/app/routes/scopes/scope/authenticate.js @@ -0,0 +1,43 @@ +import Route from '@ember/routing/route'; +import { inject as service } from '@ember/service'; +import { hash } from 'rsvp'; + +export default class ScopesScopeAuthenticateRoute extends Route { + // =services + + @service session; + + // =attributes + + // TODO: this assumes we're in an org scope, but the "landing" + // for global scope is probably different. + routeIfAlreadyAuthenticated = 'scopes.scope.projects'; + + // =methods + + /** + * Returns all auth methods for the current scope, along with the current + * scope and all scopes (for org navigation). + * @return {Promise} `{scope, scopes, authMethods}` + */ + model() { + const adapterOptions = { scopeID: this.modelFor('scopes.scope').id }; + return hash({ + scope: this.modelFor('scopes.scope'), + scopes: this.modelFor('scopes'), + authMethods: this.store.findAll('auth-method', { adapterOptions }), + + // for integration testing: + // authMethods: A([{ + // id: 'am_1234567890', + // type: 'password' + // }]) + }); + } + + redirect() { + if (this.session.isAuthenticated) { + this.transitionTo(this.routeIfAlreadyAuthenticated); + } + } +} diff --git a/ui/core/app/routes/scopes/scope/authenticate/index.js b/ui/core/app/routes/scopes/scope/authenticate/index.js new file mode 100644 index 000000000..9d6ce9d61 --- /dev/null +++ b/ui/core/app/routes/scopes/scope/authenticate/index.js @@ -0,0 +1,18 @@ +import Route from '@ember/routing/route'; + +export default class ScopesScopeAuthenticateIndexRoute extends Route { + // =methods + + /** + * This index route exists solely to redirect the user on to the + * first auth method. + * @param {object} model + * @param {[AuthMethodModel]} model.authMethods + */ + redirect({ authMethods }) { + const firstAuthMethod = authMethods.firstObject; + if (firstAuthMethod) { + this.transitionTo('scopes.scope.authenticate.method', firstAuthMethod); + } + } +} diff --git a/ui/core/app/routes/scopes/scope/authenticate/method.js b/ui/core/app/routes/scopes/scope/authenticate/method.js new file mode 100644 index 000000000..5208b152d --- /dev/null +++ b/ui/core/app/routes/scopes/scope/authenticate/method.js @@ -0,0 +1,64 @@ +import Route from '@ember/routing/route'; +import { inject as service } from '@ember/service'; +import { action } from '@ember/object'; + +export default class ScopesScopeAuthenticateMethodRoute extends Route { + // =services + + @service session; + @service notify; + @service intl; + + // =attributes + + routeAfterAuthentication = 'scopes.scope.projects'; + + // =methods + + /** + * Returns an auth method by id. + * @param {object} params + * @return {Promise{AuthMethodModel}} + */ + model({ auth_method_id: id }) { + const adapterOptions = { scopeID: this.modelFor('scopes.scope').id }; + return this.store.findRecord('auth-method', id, adapterOptions); + } + + /** + * Resets the password field on the controller so it is not + * inadvertantly retained in memory. + */ + resetCredentials() { + this.controller.setProperties({ + password: null, + }); + } + + // =actions + + /** + * Delegates authentication to the `password` authenticator. + * If authentication fails, notifies with an alert. + */ + @action + async authenticate(creds) { + this.resetCredentials(); + const authenticatorName = 'authenticator:password'; + const scope = this.modelFor('scopes.scope'); + const authMethod = this.currentModel; + const requestCookies = false; + try { + await this.session.authenticate( + authenticatorName, + creds, + requestCookies, + { scope, authMethod } + ); + this.transitionTo(this.routeAfterAuthentication); + } catch (e) { + const errorMessage = this.intl.t('errors.titles.authentication-failed'); + this.notify.error(errorMessage, { closeAfter: null }); + } + } +} diff --git a/ui/core/app/routes/scopes/scope/groups.js b/ui/core/app/routes/scopes/scope/groups.js new file mode 100644 index 000000000..7eed4406e --- /dev/null +++ b/ui/core/app/routes/scopes/scope/groups.js @@ -0,0 +1,70 @@ +import Route from '@ember/routing/route'; +import { inject as service } from '@ember/service'; +import { action } from '@ember/object'; + +export default class ScopesScopeGroupsRoute extends Route { + // =services + + @service intl; + @service notify; + + // =methods + + /** + * Load all groups under current scope + * @return {Promise[GroupModel]} + */ + async model() { + const { id: scopeID } = this.modelFor('scopes.scope'); + return this.store.findAll('group', { adapterOptions: { scopeID } }); + } + + // =actions + /** + * Rollback changes on a group. + * @param {GroupModel} group + */ + @action + cancel(group) { + const { isNew } = group; + group.rollbackAttributes(); + if (isNew) this.transitionTo('scopes.scope.groups'); + } + + /** + * Save a group in current scope. + * @param {GroupModel} group + */ + @action + async save(group) { + const { isNew } = group; + try { + await group.save(); + this.refresh(); + this.notify.success( + this.intl.t(isNew ? 'notify.create-success' : 'notify.save-success') + ); + this.transitionTo('scopes.scope.groups.group', group); + } catch (error) { + //TODO: replace with translated strings + this.notify.error(error.message, { closeAfter: null }); + } + } + + /** + * Delete a group in current scope and redirect to index + * @param {GroupModel} group + */ + @action + async delete(group) { + try { + await group.destroyRecord(); + this.refresh(); + this.notify.success(this.intl.t('notify.delete-success')); + this.transitionTo('scopes.scope.groups'); + } catch (error) { + //TODO: replace with translated strings + this.notify.error(error.message, { closeAfter: null }); + } + } +} diff --git a/ui/core/app/routes/scopes/scope/groups/group.js b/ui/core/app/routes/scopes/scope/groups/group.js new file mode 100644 index 000000000..6231ab13f --- /dev/null +++ b/ui/core/app/routes/scopes/scope/groups/group.js @@ -0,0 +1,18 @@ +import Route from '@ember/routing/route'; + +export default class ScopesScopeGroupsGroupRoute extends Route { + // =methods + + /** + * Load a group in current scope. + * @param {object} params + * @param {string} params.group_id + * @return {groupModel} + */ + async model({ group_id }) { + const { id: scopeID } = this.modelFor('scopes.scope'); + return this.store.findRecord('group', group_id, { + adapterOptions: { scopeID }, + }); + } +} diff --git a/ui/core/app/routes/scopes/scope/groups/group/index.js b/ui/core/app/routes/scopes/scope/groups/group/index.js new file mode 100644 index 000000000..90f0fb3a1 --- /dev/null +++ b/ui/core/app/routes/scopes/scope/groups/group/index.js @@ -0,0 +1,3 @@ +import Route from '@ember/routing/route'; + +export default class ScopesScopeGroupsGroupIndexRoute extends Route {} diff --git a/ui/core/app/routes/scopes/scope/groups/index.js b/ui/core/app/routes/scopes/scope/groups/index.js new file mode 100644 index 000000000..7d2a20fb0 --- /dev/null +++ b/ui/core/app/routes/scopes/scope/groups/index.js @@ -0,0 +1,3 @@ +import Route from '@ember/routing/route'; + +export default class ScopesScopeGroupsIndexRoute extends Route {} diff --git a/ui/core/app/routes/scopes/scope/groups/new.js b/ui/core/app/routes/scopes/scope/groups/new.js new file mode 100644 index 000000000..baf98da65 --- /dev/null +++ b/ui/core/app/routes/scopes/scope/groups/new.js @@ -0,0 +1,14 @@ +import Route from '@ember/routing/route'; + +export default class ScopesScopeGroupsNewRoute extends Route { + // =methods + + /** + * Creates a new unsaved group. + * @return {GroupModel} + */ + model() { + const scopeModel = this.modelFor('scopes.scope'); + return this.store.createRecord('group', { scopeModel }); + } +} diff --git a/ui/core/app/routes/scopes/scope/index.js b/ui/core/app/routes/scopes/scope/index.js new file mode 100644 index 000000000..36261b82d --- /dev/null +++ b/ui/core/app/routes/scopes/scope/index.js @@ -0,0 +1,12 @@ +import Route from '@ember/routing/route'; + +export default class ScopesScopeIndexRoute extends Route { + // =methods + + /** + * Redirects to scopes/scope/authenticate for further processing. + */ + redirect() { + this.transitionTo('scopes.scope.authenticate'); + } +} diff --git a/ui/core/app/routes/scopes/scope/projects.js b/ui/core/app/routes/scopes/scope/projects.js new file mode 100644 index 000000000..8d9ef9c00 --- /dev/null +++ b/ui/core/app/routes/scopes/scope/projects.js @@ -0,0 +1,94 @@ +import Route from '@ember/routing/route'; +import { inject as service } from '@ember/service'; +import { action } from '@ember/object'; + +export default class ScopesScopeProjectsRoute extends Route { + // =services + + @service intl; + @service notify; + @service session; + + // =methods + + /** + * If arriving here unauthenticated, redirect to index for further processing. + */ + beforeModel() { + if (!this.session.isAuthenticated) this.transitionTo('index'); + } + + /** + * Loads all scopes under the current scope. + * @return {Promise{[ScopeModel]}} + */ + async model() { + const { id: scope_id } = this.modelFor('scopes.scope'); + return this.store.query('scope', { scope_id }); + } + + /** + * If arriving here within a verifiable project scope, redirect to its + * parent, which is necessarily an org, in order to list projects. + * + * TODO: this conditional redirect based on the scope type is probably + * necessary for many routes directly under scope, so we may want + * to move this into a mixin. + * @param {ScopeModel} + */ + redirect() { + const { isProject, scopeID } = this.modelFor('scopes.scope'); + if (isProject) this.transitionTo('scopes.scope.projects', scopeID); + } + + // =actions + + /** + * Rollback changes on project. + * @param {ProjectModel} project + */ + @action + cancel(project) { + const { isNew } = project; + project.rollbackAttributes(); + if (isNew) this.transitionTo('scopes.scope.projects'); + } + + /** + * Handle save project. + * @param {ProjectModel} project + * @param {Event} e + */ + @action + async save(project) { + const { isNew } = project; + try { + await project.save(); + this.refresh(); + this.notify.success( + this.intl.t(isNew ? 'notify.create-success' : 'notify.save-success') + ); + this.transitionTo('scopes.scope.projects.project', project); + } catch (error) { + // TODO: replace with translated strings + this.notify.error(error.message, { closeAfter: null }); + } + } + + /** + * Deletes the project and redirects to index. + * @param {ProjectModel} project + */ + @action + async delete(project) { + try { + await project.destroyRecord(); + this.refresh(); + this.notify.success('notify.delete-success'); + this.transitionTo('scopes.scope.projects'); + } catch (error) { + // TODO: replace with translated strings + this.notify.error(error.message, { closeAfter: null }); + } + } +} diff --git a/ui/core/app/routes/scopes/scope/projects/index.js b/ui/core/app/routes/scopes/scope/projects/index.js new file mode 100644 index 000000000..d763c183c --- /dev/null +++ b/ui/core/app/routes/scopes/scope/projects/index.js @@ -0,0 +1,3 @@ +import Route from '@ember/routing/route'; + +export default class ScopesScopeProjectsIndexRoute extends Route {} diff --git a/ui/core/app/routes/scopes/scope/projects/new.js b/ui/core/app/routes/scopes/scope/projects/new.js new file mode 100644 index 000000000..207ca3dbc --- /dev/null +++ b/ui/core/app/routes/scopes/scope/projects/new.js @@ -0,0 +1,17 @@ +import Route from '@ember/routing/route'; + +export default class ScopesScopeProjectsNewRoute extends Route { + // =methods + + /** + * Creates a new unsaved project scope belonging to the current org scope. + * @return {ScopeModel} + */ + model() { + const { id } = this.modelFor('scopes.scope'); + return this.store.createRecord('scope', { + type: 'project', + scopeID: id, + }); + } +} diff --git a/ui/core/app/routes/scopes/scope/projects/project.js b/ui/core/app/routes/scopes/scope/projects/project.js new file mode 100644 index 000000000..dae7bbcd3 --- /dev/null +++ b/ui/core/app/routes/scopes/scope/projects/project.js @@ -0,0 +1,30 @@ +import Route from '@ember/routing/route'; + +export default class ScopesScopeProjectsProjectRoute extends Route { + // =methods + + /** + * Loads a scope by ID (ostensibly a project scope). + * @param {object} params + * @param {string} params.project_id + * @return {ScopeModel} + */ + model({ project_id }) { + return this.store.findRecord('scope', project_id); + } + + /** + * Renders the project-specific sidebar template. + * @override + * @param {object} controller + * @param {object} model + */ + renderTemplate(controller, model) { + this.render('scopes/scope/projects/project/_sidebar', { + into: 'scopes/scope', + outlet: 'sidebar', + model: model + }); + super.renderTemplate(...arguments); + } +} diff --git a/ui/core/app/routes/orgs/org/projects/project/host-catalogs/host-catalog.js b/ui/core/app/routes/scopes/scope/projects/project/host-catalogs.js similarity index 56% rename from ui/core/app/routes/orgs/org/projects/project/host-catalogs/host-catalog.js rename to ui/core/app/routes/scopes/scope/projects/project/host-catalogs.js index ace30520a..de945f71c 100644 --- a/ui/core/app/routes/orgs/org/projects/project/host-catalogs/host-catalog.js +++ b/ui/core/app/routes/scopes/scope/projects/project/host-catalogs.js @@ -2,7 +2,7 @@ import Route from '@ember/routing/route'; import { inject as service } from '@ember/service'; import { action } from '@ember/object'; -export default class OrgsOrgProjectsProjectHostCatalogsHostCatalogRoute extends Route { +export default class ScopesScopeProjectsProjectHostCatalogsRoute extends Route { // =services @@ -10,37 +10,42 @@ export default class OrgsOrgProjectsProjectHostCatalogsHostCatalogRoute extends // =methods + /** - * Returns a host catalog by id. - * @param {object} params - * @return {Promise{HostCatalogModel}} + * Loads all scopes under the current scope. + * @return {Promise{[HostCatalogModel]}} */ - model({ host_catalog_id: id }) { - return this.store.findRecord('host-catalog', id); + async model() { + const scopeID = this.modelFor('scopes.scope.projects.project').id; + return this.store.findAll('host-catalog', { adapterOptions: { scopeID } }); } // =actions /** - * Rollback changes on host catalog. + * Rollback changes on project. * @param {HostCatalogModel} hostCatalog */ @action cancel(hostCatalog) { + const { isNew } = hostCatalog; hostCatalog.rollbackAttributes(); + if (isNew) this.transitionTo('scopes.scope.projects.project.host-catalogs'); } /** - * Handle save host catalog. - * @param {HostCatalogModel} host catalog + * Handle save. + * @param {HostCatalogModel} hostCatalog * @param {Event} e */ @action async save(hostCatalog) { try { await hostCatalog.save(); + this.refresh(); // TODO: replace with translated strings this.notify.success('Save succeeded.'); + this.transitionTo('scopes.scope.projects.project.host-catalogs.host-catalog', hostCatalog); } catch (error) { // TODO: replace with translated strings this.notify.error(error.message, { closeAfter: null }); @@ -49,13 +54,14 @@ export default class OrgsOrgProjectsProjectHostCatalogsHostCatalogRoute extends /** * Deletes the host catalog and redirects to index. - * @param {HostCatalogModel} hostCatalog + * @param {ProjectModel} project */ @action - async delete(hostCatalog) { + async delete(project) { try { - await hostCatalog.destroyRecord(); - this.transitionTo('orgs.org.projects.project.host-catalogs'); + await project.destroyRecord(); + this.refresh(); + this.transitionTo('scopes.scope.projects.project.host-catalogs'); // TODO: replace with translated strings this.notify.success('Deleted host catalog succesfully.'); } catch (error) { diff --git a/ui/core/app/routes/scopes/scope/projects/project/host-catalogs/host-catalog.js b/ui/core/app/routes/scopes/scope/projects/project/host-catalogs/host-catalog.js new file mode 100644 index 000000000..f54947470 --- /dev/null +++ b/ui/core/app/routes/scopes/scope/projects/project/host-catalogs/host-catalog.js @@ -0,0 +1,4 @@ +import Route from '@ember/routing/route'; + +export default class ScopesScopeProjectsProjectHostCatalogsHostCatalogRoute extends Route { +} diff --git a/ui/core/app/routes/scopes/scope/projects/project/host-catalogs/host-catalog/index.js b/ui/core/app/routes/scopes/scope/projects/project/host-catalogs/host-catalog/index.js new file mode 100644 index 000000000..34bccc035 --- /dev/null +++ b/ui/core/app/routes/scopes/scope/projects/project/host-catalogs/host-catalog/index.js @@ -0,0 +1,4 @@ +import Route from '@ember/routing/route'; + +export default class ScopesScopeProjectsProjectHostCatalogsHostCatalogIndexRoute extends Route { +} diff --git a/ui/core/app/routes/scopes/scope/projects/project/host-catalogs/index.js b/ui/core/app/routes/scopes/scope/projects/project/host-catalogs/index.js new file mode 100644 index 000000000..b01277c47 --- /dev/null +++ b/ui/core/app/routes/scopes/scope/projects/project/host-catalogs/index.js @@ -0,0 +1,4 @@ +import Route from '@ember/routing/route'; + +export default class ScopesScopeProjectsProjectHostCatalogsIndexRoute extends Route { +} diff --git a/ui/core/app/routes/scopes/scope/projects/project/host-catalogs/new.js b/ui/core/app/routes/scopes/scope/projects/project/host-catalogs/new.js new file mode 100644 index 000000000..22097c23d --- /dev/null +++ b/ui/core/app/routes/scopes/scope/projects/project/host-catalogs/new.js @@ -0,0 +1,19 @@ +import Route from '@ember/routing/route'; + +export default class ScopesScopeProjectsProjectHostCatalogsNewRoute extends Route { + + // =methods + + /** + * Creates a new unsaved host catalog belonging to the current project scope. + * @return {HostCatalogModel} + */ + model() { + const scopeModel = this.modelFor('scopes.scope.projects.project'); + return this.store.createRecord('host-catalog', { + type: 'static', + scopeModel + }); + } + +} diff --git a/ui/core/app/routes/scopes/scope/projects/project/index.js b/ui/core/app/routes/scopes/scope/projects/project/index.js new file mode 100644 index 000000000..a004bac08 --- /dev/null +++ b/ui/core/app/routes/scopes/scope/projects/project/index.js @@ -0,0 +1,3 @@ +import Route from '@ember/routing/route'; + +export default class ScopesScopeProjectsProjectIndexRoute extends Route {} diff --git a/ui/core/app/routes/scopes/scope/roles.js b/ui/core/app/routes/scopes/scope/roles.js new file mode 100644 index 000000000..84cf2ae0f --- /dev/null +++ b/ui/core/app/routes/scopes/scope/roles.js @@ -0,0 +1,70 @@ +import Route from '@ember/routing/route'; +import { inject as service } from '@ember/service'; +import { action } from '@ember/object'; + +export default class ScopesScopeRolesRoute extends Route { + // =services + + @service intl; + @service notify; + + // =methods + + /** + * Load all roles under current scope. + * @return {Promise{[RoleModel]}} + */ + async model() { + const { id: scopeID } = this.modelFor('scopes.scope'); + return this.store.findAll('role', { adapterOptions: { scopeID } }); + } + + // =actions + /** + * Rollback changes on an role. + * @param {RoleModel} role + */ + @action + cancel(role) { + const { isNew } = role; + role.rollbackAttributes(); + if (isNew) this.transitionTo('scopes.scope.roles'); + } + + /** + * Save an role in current scope. + * @param {RoleModel} role + */ + @action + async save(role) { + const { isNew } = role; + try { + await role.save(); + this.refresh(); + this.notify.success( + this.intl.t(isNew ? 'notify.create-success' : 'notify.save-success') + ); + this.transitionTo('scopes.scope.roles.role', role); + } catch (error) { + // TODO: replace with translated strings + this.notify.error(error.message, { closeAfter: null }); + } + } + + /** + * Delete role in current scope and redirect to index. + * @param {RoleModel} role + */ + @action + async delete(role) { + try { + await role.destroyRecord(); + this.refresh(); + this.notify.success(this.intl.t('notify.delete-success')); + this.transitionTo('scopes.scope.roles'); + } catch (error) { + // TODO: replace with translated strings + this.notify.error(error.message, { closeAfter: null }); + } + } +} diff --git a/ui/core/app/routes/scopes/scope/roles/index.js b/ui/core/app/routes/scopes/scope/roles/index.js new file mode 100644 index 000000000..f2a8d7af1 --- /dev/null +++ b/ui/core/app/routes/scopes/scope/roles/index.js @@ -0,0 +1,3 @@ +import Route from '@ember/routing/route'; + +export default class ScopesScopeRolesIndexRoute extends Route {} diff --git a/ui/core/app/routes/scopes/scope/roles/new.js b/ui/core/app/routes/scopes/scope/roles/new.js new file mode 100644 index 000000000..6b5e27688 --- /dev/null +++ b/ui/core/app/routes/scopes/scope/roles/new.js @@ -0,0 +1,14 @@ +import Route from '@ember/routing/route'; + +export default class ScopesScopeRolesNewRoute extends Route { + // =methods + + /** + * Creates a new unsaved role. + * @return {RoleModel} + */ + model() { + const scopeModel = this.modelFor('scopes.scope'); + return this.store.createRecord('role', { scopeModel }); + } +} diff --git a/ui/core/app/routes/scopes/scope/roles/role.js b/ui/core/app/routes/scopes/scope/roles/role.js new file mode 100644 index 000000000..7fb8d3db7 --- /dev/null +++ b/ui/core/app/routes/scopes/scope/roles/role.js @@ -0,0 +1,18 @@ +import Route from '@ember/routing/route'; + +export default class ScopesScopeRolesRoleRoute extends Route { + // =methods + + /** + * Load a role in current scope. + * @param {object} params + * @param {string} params.role_id + * @return {RoleModel} + */ + async model({ role_id }) { + const { id: scopeID } = this.modelFor('scopes.scope'); + return this.store.findRecord('role', role_id, { + adapterOptions: { scopeID }, + }); + } +} diff --git a/ui/core/app/routes/scopes/scope/roles/role/index.js b/ui/core/app/routes/scopes/scope/roles/role/index.js new file mode 100644 index 000000000..359e9c9f5 --- /dev/null +++ b/ui/core/app/routes/scopes/scope/roles/role/index.js @@ -0,0 +1,3 @@ +import Route from '@ember/routing/route'; + +export default class ScopesScopeRolesRoleIndexRoute extends Route {} diff --git a/ui/core/app/routes/orgs/org/users/user.js b/ui/core/app/routes/scopes/scope/users.js similarity index 50% rename from ui/core/app/routes/orgs/org/users/user.js rename to ui/core/app/routes/scopes/scope/users.js index be2ea6f98..f1982b769 100644 --- a/ui/core/app/routes/orgs/org/users/user.js +++ b/ui/core/app/routes/scopes/scope/users.js @@ -2,7 +2,7 @@ import Route from '@ember/routing/route'; import { inject as service } from '@ember/service'; import { action } from '@ember/object'; -export default class OrgsOrgUsersUserRoute extends Route { +export default class ScopesScopeUsersRoute extends Route { // =services @service intl; @@ -11,35 +11,41 @@ export default class OrgsOrgUsersUserRoute extends Route { // =methods /** - * Returns a user by id. - * @param {object} params - * @return {Promise{UserModel}} + * Load all users under current scope. + * @return {Promise{[UserModel]}} */ - model({ user_id: id }) { - return this.store.findRecord('user', id); + async model() { + const { id: scopeID } = this.modelFor('scopes.scope'); + return this.store.findAll('user', { adapterOptions: { scopeID } }); } // =actions - /** - * Rollback changes on user. + * Rollback changes on an user. * @param {UserModel} user */ @action cancel(user) { + const { isNew } = user; user.rollbackAttributes(); + if (isNew) this.transitionTo('scopes.scope.users'); } /** - * Handle save user. + * Save an user in current scope. * @param {UserModel} user * @param {Event} e */ @action async save(user) { + const { isNew } = user; try { await user.save(); - this.notify.success(this.intl.t('notify.save-success')); + this.refresh(); + this.notify.success( + this.intl.t(isNew ? 'notify.create-success' : 'notify.save-success') + ); + this.transitionTo('scopes.scope.users.user', user); } catch (error) { // TODO: replace with translated strings this.notify.error(error.message, { closeAfter: null }); @@ -47,15 +53,16 @@ export default class OrgsOrgUsersUserRoute extends Route { } /** - * Deletes the user and redirects to index. + * Delete user in current scope and redirect to index. * @param {UserModel} user */ @action async delete(user) { try { await user.destroyRecord(); - this.transitionTo('orgs.org.users'); - this.notify.success(this.intl.t('notify.user.delete-success')); + this.refresh(); + this.notify.success(this.intl.t('notify.delete-success')); + this.transitionTo('scopes.scope.users'); } catch (error) { // TODO: replace with translated strings this.notify.error(error.message, { closeAfter: null }); diff --git a/ui/core/app/routes/scopes/scope/users/index.js b/ui/core/app/routes/scopes/scope/users/index.js new file mode 100644 index 000000000..f59170106 --- /dev/null +++ b/ui/core/app/routes/scopes/scope/users/index.js @@ -0,0 +1,3 @@ +import Route from '@ember/routing/route'; + +export default class ScopesScopeUsersIndexRoute extends Route {} diff --git a/ui/core/app/routes/scopes/scope/users/new.js b/ui/core/app/routes/scopes/scope/users/new.js new file mode 100644 index 000000000..469680a4e --- /dev/null +++ b/ui/core/app/routes/scopes/scope/users/new.js @@ -0,0 +1,14 @@ +import Route from '@ember/routing/route'; + +export default class ScopesScopeUsersNewRoute extends Route { + // =methods + + /** + * Creates a new unsaved user. + * @return {UserModel} + */ + model() { + const scopeModel = this.modelFor('scopes.scope'); + return this.store.createRecord('user', { scopeModel }); + } +} diff --git a/ui/core/app/routes/scopes/scope/users/user.js b/ui/core/app/routes/scopes/scope/users/user.js new file mode 100644 index 000000000..ffbf19e7e --- /dev/null +++ b/ui/core/app/routes/scopes/scope/users/user.js @@ -0,0 +1,18 @@ +import Route from '@ember/routing/route'; + +export default class ScopesScopeUsersUserRoute extends Route { + // =methods + + /** + * Load a user in current scope. + * @param {object} params + * @param {string} params.user_id + * @return {UserModel} + */ + async model({ user_id }) { + const { id: scopeID } = this.modelFor('scopes.scope'); + return this.store.findRecord('user', user_id, { + adapterOptions: { scopeID }, + }); + } +} diff --git a/ui/core/app/routes/scopes/scope/users/user/index.js b/ui/core/app/routes/scopes/scope/users/user/index.js new file mode 100644 index 000000000..fde042f35 --- /dev/null +++ b/ui/core/app/routes/scopes/scope/users/user/index.js @@ -0,0 +1,3 @@ +import Route from '@ember/routing/route'; + +export default class ScopesScopeUsersUserIndexRoute extends Route {} diff --git a/ui/core/app/session-stores/application.js b/ui/core/app/session-stores/application.js new file mode 100644 index 000000000..48b3bc4be --- /dev/null +++ b/ui/core/app/session-stores/application.js @@ -0,0 +1,62 @@ +import LocalStorageSessionStore from 'auth/session-stores/local-storage'; +import { inject as service } from '@ember/service'; +import { get } from '@ember/object'; +//import config from '../config/environment'; + +export default class ApplicationSessionStore extends LocalStorageSessionStore { + @service store; + + /** + * If the auth sessionData includes a token, apply it as a bearer token in the + * application adapter's Authorization header. Otherwise clear the header. + * @param {object} sessionData + * @return {object} + */ + persist(sessionData) { + this.addTokenToAuthorization(sessionData); + this.addScopeToSession(sessionData); + return super.persist(...arguments); + } + + /** + * If the session has a full token, it is added as a bearer token to the + * `Authorization` header for all API requests. + * @param {object} sessionData + */ + addTokenToAuthorization(sessionData) { + const token = get(sessionData, 'authenticated.token'); + const adapter = this.store.adapterFor('application'); + const headers = get(adapter, 'headers'); + if (!headers) adapter.headers = {}; + adapter.headers.Authorization = null; + if (token) adapter.headers.Authorization = `Bearer ${token}`; + } + + /** + * @param {object} sessionData + */ + addScopeToSession(sessionData) { + const scope = get(this, 'scope'); + const authenticated = get(sessionData, 'authenticated'); + if (scope && authenticated) authenticated.scope = scope; + } +} + +// import CookieSessionStore from 'auth/session-stores/cookie'; +// import config from '../config/environment'; +// +// export default class ApplicationSessionStore extends CookieSessionStore { +// +// // =attributes +// +// /** +// * @type {string} +// */ +// cookiePath = config.rootURL; +// +// /** +// * @type {string} +// */ +// cookieName = config.auth.passwordCookieName; +// +// } diff --git a/ui/core/app/templates/application.hbs b/ui/core/app/templates/application.hbs index 576e7875a..0f0b66871 100644 --- a/ui/core/app/templates/application.hbs +++ b/ui/core/app/templates/application.hbs @@ -1,8 +1,13 @@ + +{{title (app-name)}} + + + {{!-- {{t 'dev.menu-item'}} @@ -12,16 +17,22 @@ {{t 'dev.section'}} {{t 'dev.section'}} - - - {{t 'dev.menu-item'}} - {{t 'dev.button-menu-item'}} - - + --}} + + {{#if this.session.isAuthenticated}} + + + + {{t 'actions.deauthenticate'}} + + + + {{/if}} + diff --git a/ui/core/app/templates/orgs/org.hbs b/ui/core/app/templates/orgs/org.hbs deleted file mode 100644 index 72326b152..000000000 --- a/ui/core/app/templates/orgs/org.hbs +++ /dev/null @@ -1,35 +0,0 @@ - - - - - - - {{t "resources.projects"}} - - - - {{outlet "sidebar"}} - - - - {{t "resources.users"}} - - - - {{t "resources.groups"}} - - - - {{t "resources.roles"}} - - - - - - - - {{outlet}} - - - - diff --git a/ui/core/app/templates/orgs/org/error.hbs b/ui/core/app/templates/orgs/org/error.hbs deleted file mode 100644 index b903c31ad..000000000 --- a/ui/core/app/templates/orgs/org/error.hbs +++ /dev/null @@ -1,58 +0,0 @@ -{{!-- - In an error route, the error instance is `@model`. Error instances contain - a list of errors, each of which has one convenience boolean set on it: - - * Error booleans are attached based on the error status code: - * - * - `error.isUnauthenticated`: the session isn't authenticated - * - `error.isForbidden`: the session is authenticated but does not have - * permission to perform the requested action - * - `error.isNotFound`: the requested resource could not be found - * - `error.isServer`: an internal server error occurred - * - `error.isUnknown`: an error occurred, but we don't know which or - * we don't distinguish it yet - ---}} - - {{#each @model.errors as |error|}} - {{!-- error.status === 401 --}} - {{#if error.isUnauthenticated}} - - {{t 'errors.descriptions.unauthenticated-error'}} - {{t 'actions.help'}} - - {{/if}} - - {{!-- error.status === 403 --}} - {{#if error.isForbidden}} - - {{t 'errors.descriptions.forbidden-error'}} - {{t 'actions.help'}} - - {{/if}} - - {{!-- error.status === 404 --}} - {{#if error.isNotFound}} - - {{t 'errors.descriptions.notfound-error'}} - {{t 'actions.help'}} - - {{/if}} - - {{!-- error.status === 500 --}} - {{#if error.isServer}} - - {{t 'errors.descriptions.server-error'}} - {{t 'actions.help'}} - - {{/if}} - - {{!-- error.status === 🤷 everything else --}} - {{#if error.isUnknown}} - - {{t 'errors.descriptions.unknown-error'}} - {{t 'actions.help'}} - - {{/if}} - {{/each}} - diff --git a/ui/core/app/templates/orgs/org/groups/group.hbs b/ui/core/app/templates/orgs/org/groups/group.hbs deleted file mode 100644 index 2b05f838d..000000000 --- a/ui/core/app/templates/orgs/org/groups/group.hbs +++ /dev/null @@ -1,3 +0,0 @@ -{{title @model.name}} - -{{outlet}} diff --git a/ui/core/app/templates/orgs/org/projects/project/_sidebar.hbs b/ui/core/app/templates/orgs/org/projects/project/_sidebar.hbs deleted file mode 100644 index 8581e1414..000000000 --- a/ui/core/app/templates/orgs/org/projects/project/_sidebar.hbs +++ /dev/null @@ -1,15 +0,0 @@ - - - - {{t "titles.overview"}} - - - - {{t "resources.host-catalogs"}} - - - diff --git a/ui/core/app/templates/orgs/org/projects/project/host-catalogs/new.hbs b/ui/core/app/templates/orgs/org/projects/project/host-catalogs/new.hbs deleted file mode 100644 index 1ac7bf86f..000000000 --- a/ui/core/app/templates/orgs/org/projects/project/host-catalogs/new.hbs +++ /dev/null @@ -1,18 +0,0 @@ - - - - - - - - {{t "titles.new"}} - - - - - - - diff --git a/ui/core/app/templates/orgs/org/users/user.hbs b/ui/core/app/templates/orgs/org/users/user.hbs deleted file mode 100644 index 2b05f838d..000000000 --- a/ui/core/app/templates/orgs/org/users/user.hbs +++ /dev/null @@ -1,3 +0,0 @@ -{{title @model.name}} - -{{outlet}} diff --git a/ui/core/app/templates/orgs.hbs b/ui/core/app/templates/scopes.hbs similarity index 100% rename from ui/core/app/templates/orgs.hbs rename to ui/core/app/templates/scopes.hbs diff --git a/ui/core/app/templates/scopes/index.hbs b/ui/core/app/templates/scopes/index.hbs new file mode 100644 index 000000000..e2147cab0 --- /dev/null +++ b/ui/core/app/templates/scopes/index.hbs @@ -0,0 +1 @@ +{{outlet}} \ No newline at end of file diff --git a/ui/core/app/templates/scopes/scope.hbs b/ui/core/app/templates/scopes/scope.hbs new file mode 100644 index 000000000..62f8be2de --- /dev/null +++ b/ui/core/app/templates/scopes/scope.hbs @@ -0,0 +1,34 @@ + + + {{#if this.session.isAuthenticated}} + + + + {{t "resources.projects"}} + + + + + + {{t "resources.users"}} + + + {{t "resources.groups"}} + + + {{t "resources.roles"}} + + + + {{outlet "sidebar"}} + + + {{/if}} + + + + {{outlet}} + + + + diff --git a/ui/core/app/templates/scopes/scope/authenticate.hbs b/ui/core/app/templates/scopes/scope/authenticate.hbs new file mode 100644 index 000000000..43b329188 --- /dev/null +++ b/ui/core/app/templates/scopes/scope/authenticate.hbs @@ -0,0 +1,54 @@ +{{title (t "actions.authenticate")}} + + + + + + {{t "actions.authenticate"}} + {{t "auth.descriptions.authenticating-with-org" name=@model.scope.displayName}} + + + + + + {{#each @model.scopes as |scope|}} + + {{scope.displayName}} + + {{/each}} + + + + + + {{#each @model.authMethods as |authMethod|}} + + {{t (concat "auth.method-types." authMethod.type)}} + + {{/each}} + + + + + {{#if @model.authMethods}} + {{outlet}} + {{/if}} + {{#unless @model.authMethods}} + + + + {{t "auth.descriptions.no-methods"}} + + + + {{/unless}} + + + + diff --git a/ui/core/app/templates/scopes/scope/authenticate/index.hbs b/ui/core/app/templates/scopes/scope/authenticate/index.hbs new file mode 100644 index 000000000..22cc46f4e --- /dev/null +++ b/ui/core/app/templates/scopes/scope/authenticate/index.hbs @@ -0,0 +1,12 @@ +{{#unless @model}} + + + + {{t "auth.descriptions.no-methods"}} + + + +{{/unless}} diff --git a/ui/core/app/templates/scopes/scope/authenticate/method.hbs b/ui/core/app/templates/scopes/scope/authenticate/method.hbs new file mode 100644 index 000000000..60057d773 --- /dev/null +++ b/ui/core/app/templates/scopes/scope/authenticate/method.hbs @@ -0,0 +1,26 @@ + + + + + + + + + diff --git a/ui/core/app/templates/orgs/org/groups.hbs b/ui/core/app/templates/scopes/scope/groups.hbs similarity index 100% rename from ui/core/app/templates/orgs/org/groups.hbs rename to ui/core/app/templates/scopes/scope/groups.hbs diff --git a/ui/core/app/templates/orgs/org/projects/project.hbs b/ui/core/app/templates/scopes/scope/groups/group.hbs similarity index 100% rename from ui/core/app/templates/orgs/org/projects/project.hbs rename to ui/core/app/templates/scopes/scope/groups/group.hbs diff --git a/ui/core/app/templates/orgs/org/groups/group/index.hbs b/ui/core/app/templates/scopes/scope/groups/group/index.hbs similarity index 71% rename from ui/core/app/templates/orgs/org/groups/group/index.hbs rename to ui/core/app/templates/scopes/scope/groups/group/index.hbs index 576796dae..ac8915f59 100644 --- a/ui/core/app/templates/orgs/org/groups/group/index.hbs +++ b/ui/core/app/templates/scopes/scope/groups/group/index.hbs @@ -8,8 +8,16 @@ {{t "resources.group"}} + + + + {{t 'titles.details'}} + + + + - {{#if @model}} - {{t 'titles.new'}} + {{t 'titles.new'}} {{/if}} @@ -30,7 +30,7 @@ {{group.name}} @@ -49,7 +49,7 @@ {{t 'descriptions.groups-welcome'}} - + {{t 'titles.new'}} diff --git a/ui/core/app/templates/orgs/org/groups/new.hbs b/ui/core/app/templates/scopes/scope/groups/new.hbs similarity index 92% rename from ui/core/app/templates/orgs/org/groups/new.hbs rename to ui/core/app/templates/scopes/scope/groups/new.hbs index 64602883c..b8f782b23 100644 --- a/ui/core/app/templates/orgs/org/groups/new.hbs +++ b/ui/core/app/templates/scopes/scope/groups/new.hbs @@ -1,3 +1,5 @@ +{{title (t 'titles.new')}} + @@ -15,4 +17,5 @@ @cancel={{route-action "cancel" @model}} /> + diff --git a/ui/core/app/templates/scopes/scope/index.hbs b/ui/core/app/templates/scopes/scope/index.hbs new file mode 100644 index 000000000..c24cd6895 --- /dev/null +++ b/ui/core/app/templates/scopes/scope/index.hbs @@ -0,0 +1 @@ +{{outlet}} diff --git a/ui/core/app/templates/orgs/org/projects.hbs b/ui/core/app/templates/scopes/scope/projects.hbs similarity index 100% rename from ui/core/app/templates/orgs/org/projects.hbs rename to ui/core/app/templates/scopes/scope/projects.hbs diff --git a/ui/core/app/templates/orgs/org/projects/index.hbs b/ui/core/app/templates/scopes/scope/projects/index.hbs similarity index 79% rename from ui/core/app/templates/orgs/org/projects/index.hbs rename to ui/core/app/templates/scopes/scope/projects/index.hbs index b6cac496b..c07e265ec 100644 --- a/ui/core/app/templates/orgs/org/projects/index.hbs +++ b/ui/core/app/templates/scopes/scope/projects/index.hbs @@ -10,7 +10,12 @@ {{#if @model}} - {{t 'titles.new'}} + + {{t 'titles.new'}} + {{/if}} @@ -24,7 +29,7 @@ @title={{project.name}} @subtitle={{project.id}} @description={{project.description}} - @route="orgs.org.projects.project" + @route="scopes.scope.projects.project" @model={{project}} /> {{/each}} @@ -37,7 +42,7 @@ {{t 'descriptions.projects-welcome'}} - + {{t 'titles.new'}} diff --git a/ui/core/app/templates/orgs/org/projects/new.hbs b/ui/core/app/templates/scopes/scope/projects/new.hbs similarity index 92% rename from ui/core/app/templates/orgs/org/projects/new.hbs rename to ui/core/app/templates/scopes/scope/projects/new.hbs index 5289636f7..a8758e3c0 100644 --- a/ui/core/app/templates/orgs/org/projects/new.hbs +++ b/ui/core/app/templates/scopes/scope/projects/new.hbs @@ -1,4 +1,7 @@ +{{title (t 'titles.new')}} + + @@ -14,4 +17,5 @@ @cancel={{route-action "cancel" @model}} /> + diff --git a/ui/core/app/templates/orgs/org/projects/project/host-catalogs/host-catalog.hbs b/ui/core/app/templates/scopes/scope/projects/project.hbs similarity index 100% rename from ui/core/app/templates/orgs/org/projects/project/host-catalogs/host-catalog.hbs rename to ui/core/app/templates/scopes/scope/projects/project.hbs diff --git a/ui/core/app/templates/scopes/scope/projects/project/_sidebar.hbs b/ui/core/app/templates/scopes/scope/projects/project/_sidebar.hbs new file mode 100644 index 000000000..8f46f6113 --- /dev/null +++ b/ui/core/app/templates/scopes/scope/projects/project/_sidebar.hbs @@ -0,0 +1,5 @@ + + + {{t "resources.host-catalogs"}} + + diff --git a/ui/core/app/templates/orgs/org/projects/project/host-catalogs.hbs b/ui/core/app/templates/scopes/scope/projects/project/host-catalogs.hbs similarity index 100% rename from ui/core/app/templates/orgs/org/projects/project/host-catalogs.hbs rename to ui/core/app/templates/scopes/scope/projects/project/host-catalogs.hbs diff --git a/ui/core/app/templates/orgs/org/roles/role.hbs b/ui/core/app/templates/scopes/scope/projects/project/host-catalogs/host-catalog.hbs similarity index 100% rename from ui/core/app/templates/orgs/org/roles/role.hbs rename to ui/core/app/templates/scopes/scope/projects/project/host-catalogs/host-catalog.hbs diff --git a/ui/core/app/templates/orgs/org/projects/project/host-catalogs/host-catalog/index.hbs b/ui/core/app/templates/scopes/scope/projects/project/host-catalogs/host-catalog/index.hbs similarity index 71% rename from ui/core/app/templates/orgs/org/projects/project/host-catalogs/host-catalog/index.hbs rename to ui/core/app/templates/scopes/scope/projects/project/host-catalogs/host-catalog/index.hbs index 735397eef..cb0a4a0cf 100644 --- a/ui/core/app/templates/orgs/org/projects/project/host-catalogs/host-catalog/index.hbs +++ b/ui/core/app/templates/scopes/scope/projects/project/host-catalogs/host-catalog/index.hbs @@ -8,6 +8,14 @@ {{t "resources.host-catalog"}} + + + + {{t 'titles.details'}} + + + + - {{t 'resources.host-catalogs'}} - {{t 'descriptions.host-catalogs'}} + {{t "resources.host-catalogs"}} + {{t "descriptions.host-catalogs"}} {{#if @model}} - - {{t 'titles.new'}} + + {{t "titles.new"}} {{/if}} @@ -24,24 +24,27 @@ - {{t 'form.label.name'}} - {{t 'form.label.type'}} - {{t 'form.label.description'}} + {{t "form.label.id"}} + {{t "form.label.name"}} + {{t "form.label.type"}} {{#each @model as |hostCatalog|}} + + {{hostCatalog.id}} + - {{hostCatalog.name}} + {{hostCatalog.displayName}} + {{hostCatalog.description}} {{hostCatalog.type}} - {{hostCatalog.description}} {{/each}} @@ -50,13 +53,13 @@ {{else}} - + - {{t 'descriptions.host-catalogs-welcome'}} + {{t "descriptions.host-catalogs-welcome"}} - + - {{t 'titles.new'}} + {{t "titles.new"}} diff --git a/ui/core/app/templates/scopes/scope/projects/project/host-catalogs/new.hbs b/ui/core/app/templates/scopes/scope/projects/project/host-catalogs/new.hbs new file mode 100644 index 000000000..0d969f874 --- /dev/null +++ b/ui/core/app/templates/scopes/scope/projects/project/host-catalogs/new.hbs @@ -0,0 +1,21 @@ +{{title (t 'titles.new')}} + + + + + + + + + {{t "titles.new"}} + + + + + + + diff --git a/ui/core/app/templates/orgs/org/projects/project/index.hbs b/ui/core/app/templates/scopes/scope/projects/project/index.hbs similarity index 88% rename from ui/core/app/templates/orgs/org/projects/project/index.hbs rename to ui/core/app/templates/scopes/scope/projects/project/index.hbs index 77050cd72..5dccae834 100644 --- a/ui/core/app/templates/orgs/org/projects/project/index.hbs +++ b/ui/core/app/templates/scopes/scope/projects/project/index.hbs @@ -10,8 +10,8 @@ - - {{t 'titles.settings'}} + + {{t 'titles.details'}} diff --git a/ui/core/app/templates/orgs/org/roles.hbs b/ui/core/app/templates/scopes/scope/roles.hbs similarity index 100% rename from ui/core/app/templates/orgs/org/roles.hbs rename to ui/core/app/templates/scopes/scope/roles.hbs diff --git a/ui/core/app/templates/orgs/org/roles/index.hbs b/ui/core/app/templates/scopes/scope/roles/index.hbs similarity index 86% rename from ui/core/app/templates/orgs/org/roles/index.hbs rename to ui/core/app/templates/scopes/scope/roles/index.hbs index 07e3acfaa..e0e28cf9a 100644 --- a/ui/core/app/templates/orgs/org/roles/index.hbs +++ b/ui/core/app/templates/scopes/scope/roles/index.hbs @@ -10,7 +10,7 @@ {{#if @model}} - {{t 'titles.new'}} + {{t 'titles.new'}} {{/if}} @@ -30,7 +30,7 @@ {{role.displayName}} @@ -49,7 +49,7 @@ {{t 'descriptions.roles-welcome'}} - + {{t 'titles.new'}} diff --git a/ui/core/app/templates/orgs/org/roles/new.hbs b/ui/core/app/templates/scopes/scope/roles/new.hbs similarity index 92% rename from ui/core/app/templates/orgs/org/roles/new.hbs rename to ui/core/app/templates/scopes/scope/roles/new.hbs index 911310f26..efddd8fb9 100644 --- a/ui/core/app/templates/orgs/org/roles/new.hbs +++ b/ui/core/app/templates/scopes/scope/roles/new.hbs @@ -1,3 +1,5 @@ +{{title (t 'titles.new')}} + diff --git a/ui/core/app/templates/scopes/scope/roles/role.hbs b/ui/core/app/templates/scopes/scope/roles/role.hbs new file mode 100644 index 000000000..d66e83390 --- /dev/null +++ b/ui/core/app/templates/scopes/scope/roles/role.hbs @@ -0,0 +1,3 @@ +{{title @model.displayName}} + +{{outlet}} diff --git a/ui/core/app/templates/orgs/org/roles/role/index.hbs b/ui/core/app/templates/scopes/scope/roles/role/index.hbs similarity index 100% rename from ui/core/app/templates/orgs/org/roles/role/index.hbs rename to ui/core/app/templates/scopes/scope/roles/role/index.hbs diff --git a/ui/core/app/templates/orgs/org/users.hbs b/ui/core/app/templates/scopes/scope/users.hbs similarity index 100% rename from ui/core/app/templates/orgs/org/users.hbs rename to ui/core/app/templates/scopes/scope/users.hbs diff --git a/ui/core/app/templates/orgs/org/users/index.hbs b/ui/core/app/templates/scopes/scope/users/index.hbs similarity index 86% rename from ui/core/app/templates/orgs/org/users/index.hbs rename to ui/core/app/templates/scopes/scope/users/index.hbs index 8e147d3ac..1ee0f3ac2 100644 --- a/ui/core/app/templates/orgs/org/users/index.hbs +++ b/ui/core/app/templates/scopes/scope/users/index.hbs @@ -10,7 +10,7 @@ {{#if @model}} - {{t 'titles.new'}} + {{t 'titles.new'}} {{/if}} @@ -30,7 +30,7 @@ {{user.name}} @@ -49,7 +49,7 @@ {{t 'descriptions.users-welcome'}} - + {{t 'titles.new'}} diff --git a/ui/core/app/templates/orgs/org/users/new.hbs b/ui/core/app/templates/scopes/scope/users/new.hbs similarity index 92% rename from ui/core/app/templates/orgs/org/users/new.hbs rename to ui/core/app/templates/scopes/scope/users/new.hbs index 4ae7ef85c..08e72531c 100644 --- a/ui/core/app/templates/orgs/org/users/new.hbs +++ b/ui/core/app/templates/scopes/scope/users/new.hbs @@ -1,4 +1,7 @@ +{{title (t 'titles.new')}} + + @@ -14,4 +17,5 @@ @cancel={{route-action "cancel" @model}} /> + diff --git a/ui/core/app/templates/scopes/scope/users/user.hbs b/ui/core/app/templates/scopes/scope/users/user.hbs new file mode 100644 index 000000000..d66e83390 --- /dev/null +++ b/ui/core/app/templates/scopes/scope/users/user.hbs @@ -0,0 +1,3 @@ +{{title @model.displayName}} + +{{outlet}} diff --git a/ui/core/app/templates/orgs/org/users/user/index.hbs b/ui/core/app/templates/scopes/scope/users/user/index.hbs similarity index 71% rename from ui/core/app/templates/orgs/org/users/user/index.hbs rename to ui/core/app/templates/scopes/scope/users/user/index.hbs index a1b876705..d80978336 100644 --- a/ui/core/app/templates/orgs/org/users/user/index.hbs +++ b/ui/core/app/templates/scopes/scope/users/user/index.hbs @@ -8,8 +8,16 @@ {{t "resources.user"}} + + + + {{t 'titles.details'}} + + + + - { + // Default parent scope is global + if (!scope_id) scope_id = 'global'; + const results = scopes.where(scope => { + return scope.scope ? scope.scope.id === scope_id : false + }); + return results; + }); + this.post('/scopes', function ({ scopes }, request) { + // Parent scope comes through the payload via `scope_id`, but this needs + // to be expanded to a scope object `scope: {id:..., type:...}` before + // saving, which is the gist of this function. + const attrs = this.normalizedRequestAttrs(); + const parentScopeAttrs = this.serialize(scopes.find(request.queryParams.scope_id)); + attrs.scope = parentScopeAttrs; + return scopes.create(attrs); + }); + this.get('/scopes/:id'); + this.patch('/scopes/:id'); + this.del('/scopes/:id'); // Auth & IAM resources - this.get('/orgs/:org_id/auth-methods'); - this.post('/orgs/:org_id/auth-methods'); - this.get('/orgs/:org_id/auth-methods/:id'); - this.patch('/orgs/:org_id/auth-methods/:id'); - this.del('/orgs/:org_id/auth-methods/:id'); + this.get('/scopes/:scope_id/auth-methods'); + this.post('/scopes/:scope_id/auth-methods'); + this.get('/scopes/:scope_id/auth-methods/:id'); + this.patch('/scopes/:scope_id/auth-methods/:id'); + this.del('/scopes/:scope_id/auth-methods/:id'); + + // Authenticate/deauthenticate routes + this.post('/scopes/:scope_id/auth-methods/:id_method', authHandler); + this.post('/scopes/:id_method', deauthHandler); // IAM : Users - this.get('/orgs/:org_id/users'); - this.post('/orgs/:org_id/users'); - this.get('/orgs/:org_id/users/:id'); - this.patch('/orgs/:org_id/users/:id'); - this.del('/orgs/:org_id/users/:id'); - - // IAM: Roles - this.get('/orgs/:org_id/roles'); - this.post('/orgs/:org_id/roles'); - this.get('/orgs/:org_id/roles/:id'); - this.patch('/orgs/:org_id/roles/:id'); - this.del('/orgs/:org_id/roles/:id'); + this.get('/scopes/:scope_id/users'); + this.post('/scopes/:scope_id/users'); + this.get('/scopes/:scope_id/users/:id'); + this.patch('/scopes/:scope_id/users/:id'); + this.del('/scopes/:scope_id/users/:id'); - // group - this.get('/orgs/:org_id/groups'); - this.post('/orgs/:org_id/groups'); - this.get('/orgs/:org_id/groups/:id'); - this.patch('/orgs/:org_id/groups/:id'); - this.del('/orgs/:org_id/groups/:id'); + // IAM: Roles + this.get('/scopes/:scope_id/roles'); + this.post('/scopes/:scope_id/roles'); + this.get('/scopes/:scope_id/roles/:id'); + this.patch('/scopes/:scope_id/roles/:id'); + this.del('/scopes/:scope_id/roles/:id'); + + // IAM: Groups + this.get('/scopes/:scope_id/groups'); + this.post('/scopes/:scope_id/groups'); + this.get('/scopes/:scope_id/groups/:id'); + this.patch('/scopes/:scope_id/groups/:id'); + this.del('/scopes/:scope_id/groups/:id'); // Other resources // host-catalog - this.get('/orgs/:org_id/projects/:project_id/host-catalogs'); - this.post('/orgs/:org_id/projects/:project_id/host-catalogs'); - this.get('/orgs/:org_id/projects/:project_id/host-catalogs/:id'); - this.patch('/orgs/:org_id/projects/:project_id/host-catalogs/:id'); - this.del('/orgs/:org_id/projects/:project_id/host-catalogs/:id'); + this.get('/scopes/:scope_id/host-catalogs'); + this.post('/scopes/:scope_id/host-catalogs'); + this.get('/scopes/:scope_id/host-catalogs/:id'); + this.patch('/scopes/:scope_id/host-catalogs/:id'); + this.del('/scopes/:scope_id/host-catalogs/:id'); // Uncomment the following line and the Response import above // Then change the response code to simulate error responses. - // this.get('/orgs/:org_id/projects', () => new Response(505)); + // this.get('/scopes/:scope_id/projects', () => new Response(505)); // Update error payload to simulate specific error responses. - // this.get('/orgs/:org_id/projects', () => new Response(505, {}, { + // this.get('/scopes/:scope_id/projects', () => new Response(505, {}, { // errors: [{ // status: 505, // message: 'HTTP version not supported.', diff --git a/ui/core/mirage/factories/scope.js b/ui/core/mirage/factories/scope.js new file mode 100644 index 000000000..703e80ad4 --- /dev/null +++ b/ui/core/mirage/factories/scope.js @@ -0,0 +1,25 @@ +import factory from '../generated/factories/scope'; +import { trait } from 'ember-cli-mirage'; + +export default factory.extend({ + + type: 'global', + + withChildren: trait({ + afterCreate(record, server) { + if (record.type === 'global') { + server.createList('scope', 1, { + type: 'org', + scope: { id: record.id, type: record.type } + }, 'withChildren'); + } + if (record.type === 'org') { + server.createList('scope', 5, { + type: 'project', + scope: { id: record.id, type: record.type } + }); + } + } + }) + +}); diff --git a/ui/core/mirage/generated/factories/scope.js b/ui/core/mirage/generated/factories/scope.js new file mode 100644 index 000000000..cc574d3cd --- /dev/null +++ b/ui/core/mirage/generated/factories/scope.js @@ -0,0 +1,15 @@ +import { Factory } from 'ember-cli-mirage'; +import { random, date } from 'faker'; + +/** + * GeneratedScopeModelFactory + */ +export default Factory.extend({ + + name: () => random.words(), + description: () => random.words(), + created_time: () => date.recent(), + updated_time: () => date.recent(), + disabled: () => random.boolean(), + +}); diff --git a/ui/core/mirage/route-handlers/auth.js b/ui/core/mirage/route-handlers/auth.js new file mode 100644 index 000000000..596500f46 --- /dev/null +++ b/ui/core/mirage/route-handlers/auth.js @@ -0,0 +1,50 @@ +// import config from '../../config/environment'; +// import { v1 } from 'ember-uuid'; +import { Response } from 'miragejs'; + +// /** +// * Simulates a cookie-based response to authentication requests. Any user/pass +// * values are allowed *except* when `username` equals `error`, in which case a +// * simulated error response is returned. +// * @param {object} schema +// * @param {object} request +// * @return {Response} +// */ +// export default function authenticateHandler(schema, request) { +// const payload = JSON.parse(request.requestBody); +// if (payload.credentials.name === 'error') { +// return new Response(400); +// } else { +// const cookieName = config.auth.passwordCookieName; +// const cookieValue = v1(); +// const cookiePath = config.rootURL; +// document.cookie = `${cookieName}=${cookieValue};path=${cookiePath}`; +// return new Response(200); +// } +// } + +export function authHandler({ scopes }, request) { + const payload = JSON.parse(request.requestBody); + if (payload.credentials.name === 'error') { + return new Response(400); + } else { + const scopeAttrs = + this.serialize(scopes.find(request.params.scope_id)); + const scope = scopeAttrs; + return new Response(200, {}, { + scope, + id: 'token123', + token: 'thetokenstring', + user_id: 'user123', + auth_method_id: 'authmethod123', + created_time: '', + updated_time: '', + last_used_time: '', + expiration_time: '' + }); + } +} + +export function deauthHandler() { + return new Response(200); +} diff --git a/ui/core/mirage/scenarios/default.js b/ui/core/mirage/scenarios/default.js index d4e4dc2b7..01b55037b 100644 --- a/ui/core/mirage/scenarios/default.js +++ b/ui/core/mirage/scenarios/default.js @@ -2,26 +2,33 @@ export default function(server) { // Scope resources - server.createList('org', 3); - server.createList('project', 6); + const globalScope = server.create('scope', { id: 'global' }); // creates a global scope + const orgScope = server.createList('scope', 3, { + type: 'org', + scope: { id: globalScope.id, type: globalScope.type } + }, 'withChildren')[0]; + const scope = { id: orgScope.id, type: orgScope.type }; // Auth // Auth methods exist both at the global scope and the org scope. // For simplicity we don't scope our mock auth methods and just return the // same items for request at all scopes. - server.createList('auth-method', 3); + server.createList('auth-method', 3, { scope }); // User - server.createList('user', 5); + server.createList('user', 5, { scope }); // Role - server.createList('role', 5); + server.createList('role', 5, { scope }); // Groups - server.createList('group', 5); + server.createList('group', 5, { scope }); // Other resources - server.createList('host-catalog', 6); + server.db.scopes.where({type: 'project'}).forEach(projectScope => { + const scope = { id: projectScope.id, type: projectScope.type }; + server.createList('host-catalog', 2, { scope }); + }); } diff --git a/ui/core/mirage/serializers/scope.js b/ui/core/mirage/serializers/scope.js new file mode 100644 index 000000000..bfb33f398 --- /dev/null +++ b/ui/core/mirage/serializers/scope.js @@ -0,0 +1,5 @@ +import ApplicationSerializer from './application'; + +export default ApplicationSerializer.extend({ + modelName: 'scope' +}); diff --git a/ui/core/package.json b/ui/core/package.json index 02bcc7ea3..37cb246e5 100644 --- a/ui/core/package.json +++ b/ui/core/package.json @@ -32,6 +32,7 @@ "@glimmer/component": "^1.0.0", "@glimmer/tracking": "^1.0.0", "api": "*", + "auth": "*", "babel-eslint": "^10.0.3", "babel-loader": "^8.1.0", "broccoli-asset-rev": "^3.0.0", @@ -54,6 +55,7 @@ "ember-cli-template-lint": "^1.0.0-beta.3", "ember-cli-uglify": "^3.0.0", "ember-data": "~3.16.0", + "ember-data-model-fragments": "5.0.0-beta.0", "ember-exam": "^5.0.1", "ember-export-application-global": "^2.0.1", "ember-fetch": "^7.0.0", @@ -67,6 +69,7 @@ "ember-qunit": "^4.6.0", "ember-resolver": "^7.0.0", "ember-route-action-helper": "^2.0.8", + "ember-simple-auth": "^3.0.0", "ember-source": "~3.16.0", "ember-uuid": "^2.1.0", "ember-welcome-page": "^4.0.0", diff --git a/ui/core/tests/acceptance/authentication-test.js b/ui/core/tests/acceptance/authentication-test.js new file mode 100644 index 000000000..3481337bb --- /dev/null +++ b/ui/core/tests/acceptance/authentication-test.js @@ -0,0 +1,131 @@ +import { module, test } from 'qunit'; +import { visit, currentURL, fillIn, click, find } from '@ember/test-helpers'; +import { setupApplicationTest } from 'ember-qunit'; +import setupMirage from 'ember-cli-mirage/test-support/setup-mirage'; +import a11yAudit from 'ember-a11y-testing/test-support/audit'; +import { + currentSession, + authenticateSession, + invalidateSession, +} from 'ember-simple-auth/test-support'; + +module('Acceptance | authentication', function (hooks) { + setupApplicationTest(hooks); + setupMirage(hooks); + + let indexURL; + let globalScope; + let orgScope; + let orgScopeID; + let scopesURL; + let orgScopeURL; + let scope; + let authMethod; + let authMethodID; + let authenticateURL; + let authMethodAuthenticateURL; + let projectsURL; + + hooks.beforeEach(function () { + invalidateSession(); + indexURL = '/'; + globalScope = this.server.create('scope', { id: 'global' }); + orgScope = this.server.create( + 'scope', + { + type: 'org', + scope: { id: globalScope.id, type: globalScope.type }, + }, + 'withChildren' + ); + scope = { id: orgScope.id, type: orgScope.type }; + authMethod = this.server.create('auth-method', { scope }); + orgScopeID = orgScope.id; + authMethodID = authMethod.id; + scopesURL = `/scopes`; + orgScopeURL = `/scopes/${orgScopeID}`; + authenticateURL = `/scopes/${orgScopeID}/authenticate`; + authMethodAuthenticateURL = `/scopes/${orgScopeID}/authenticate/${authMethodID}`; + projectsURL = `/scopes/${orgScopeID}/projects`; + }); + + test('visiting auth methods authenticate route redirects to first auth method', async function (assert) { + assert.expect(1); + await visit(authenticateURL); + await a11yAudit(); + assert.equal(currentURL(), authMethodAuthenticateURL); + }); + + test('visiting any authentication parent route while unauthenticated redirects to first authenticate method', async function (assert) { + assert.expect(3); + await visit(indexURL); + assert.equal(currentURL(), authMethodAuthenticateURL); + await visit(scopesURL); + assert.equal(currentURL(), authMethodAuthenticateURL); + assert.notOk(currentSession().isAuthenticated); + }); + + test('visiting projects while unauthenticated redirects to first authenticate method', async function (assert) { + assert.expect(2); + await visit(projectsURL); + assert.equal(currentURL(), authMethodAuthenticateURL); + assert.notOk(currentSession().isAuthenticated); + }); + + test('visiting auth method authenticate route', async function (assert) { + assert.expect(1); + await visit(authMethodAuthenticateURL); + await a11yAudit(); + assert.equal(currentURL(), authMethodAuthenticateURL); + }); + + test('visiting any authentication parent route while already authenticated redirects to projects', async function (assert) { + assert.expect(6); + authenticateSession({ scope }); + await visit(indexURL); + assert.equal(currentURL(), projectsURL); + await visit(scopesURL); + assert.equal(currentURL(), projectsURL); + await visit(orgScopeURL); + assert.equal(currentURL(), projectsURL); + await visit(authenticateURL); + assert.equal(currentURL(), projectsURL); + await visit(authMethodAuthenticateURL); + assert.equal(currentURL(), projectsURL); + assert.ok(currentSession().isAuthenticated); + }); + + test('failed authentication shows a notification message', async function (assert) { + assert.expect(3); + await visit(authMethodAuthenticateURL); + assert.notOk(currentSession().isAuthenticated); + await fillIn('[name="identification"]', 'error'); + await click('[type="submit"]'); + assert.ok(find('.rose-notification.is-error')); + assert.notOk(currentSession().isAuthenticated); + }); + + test('successful authentication redirects to projects', async function (assert) { + assert.expect(4); + await visit(authMethodAuthenticateURL); + assert.notOk(currentSession().isAuthenticated); + assert.equal(currentURL(), authMethodAuthenticateURL); + await fillIn('[name="identification"]', 'test'); + await fillIn('[name="password"]', 'test'); + await click('[type="submit"]'); + assert.equal(currentURL(), projectsURL); + assert.ok(currentSession().isAuthenticated); + }); + + test('deauthentication redirects to first authenticate method', async function (assert) { + assert.expect(3); + await visit(authMethodAuthenticateURL); + await fillIn('[name="identification"]', 'test'); + await fillIn('[name="password"]', 'test'); + await click('[type="submit"]'); + assert.ok(currentSession().isAuthenticated); + await click('.rose-button-header-dropdown'); + assert.notOk(currentSession().isAuthenticated); + assert.equal(currentURL(), authMethodAuthenticateURL); + }); +}); diff --git a/ui/core/tests/acceptance/bread-crumbs-test.js b/ui/core/tests/acceptance/bread-crumbs-test.js index 7c33595e9..879eb311b 100644 --- a/ui/core/tests/acceptance/bread-crumbs-test.js +++ b/ui/core/tests/acceptance/bread-crumbs-test.js @@ -2,12 +2,19 @@ import { module, test } from 'qunit'; import { visit, currentURL, click, find, findAll } from '@ember/test-helpers'; import { setupApplicationTest } from 'ember-qunit'; import setupMirage from 'ember-cli-mirage/test-support/setup-mirage'; +import { + authenticateSession, + // These are left here intentionally for future reference. + //currentSession, + //invalidateSession, +} from 'ember-simple-auth/test-support'; module('Acceptance | breadcrumbs', function (hooks) { setupApplicationTest(hooks); setupMirage(hooks); - let org; + let orgScope; + let getProjectScope; let project; let hostCatalog; let user; @@ -22,142 +29,224 @@ module('Acceptance | breadcrumbs', function (hooks) { let crumbPath; hooks.beforeEach(function () { - org = this.server.create('org'); - project = this.server.create('project'); - user = this.server.create('user'); + authenticateSession({}); + orgScope = this.server.create( + 'scope', + { + type: 'org', + }, + 'withChildren' + ); - hostCatalog = this.server.create('host-catalog'); + getProjectScope = () => + this.server.schema.scopes.where({ type: 'project' }).models[0]; + + project = getProjectScope(); + user = this.server.create('user'); role = this.server.create('role'); group = this.server.create('group'); + hostCatalog = this.server.create('host-catalog', { + scope: { + id: project.id, + type: project.type + } + }); - projectsURL = `/orgs/${org.id}/projects`; + projectsURL = `/scopes/${orgScope.id}/projects`; newProjectURL = `${projectsURL}/new`; projectURL = `${projectsURL}/${project.id}`; + rolesURL = `/scopes/${orgScope.id}/roles`; + newRoleURL = `${rolesURL}/new`; + roleURL = `${rolesURL}/${role.id}`; + + groupsURL = `/scopes/${orgScope.id}/groups`; + newGroupURL = `${groupsURL}/new`; + groupURL = `${groupsURL}/${group.id}`; + + usersURL = `/scopes/${orgScope.id}/users`; + newUserURL = `${usersURL}/${user.id}`; + userURL = `${usersURL}/new`; + hostCatalogsURL = `${projectURL}/host-catalogs`; newHostCatalogURL = `${hostCatalogsURL}/new`; hostCatalogURL = `${hostCatalogsURL}/${hostCatalog.id}`; - - rolesURL = `/orgs/${org.id}/roles`; - newRoleURL = `${rolesURL}/new`; - roleURL = `${rolesURL}/${role.id}`; - - groupsURL = `/orgs/${org.id}/groups`; - newGroupURL = `${groupsURL}/new`; - groupURL = `${groupsURL}/${group.id}`; - - usersURL = `/orgs/${org.id}/users`; - newUserURL = `${usersURL}/${user.id}`; - userURL = `${usersURL}/new`; }); test('can navigate via breadcrumbs to projects from project', async function (assert) { assert.expect(3); await visit(projectURL); assert.equal(currentURL(), projectURL, 'We begin on the expected page.'); - crumbPath = new URL(find('.rose-nav-breadcrumbs-link:last-child').href).pathname; + crumbPath = new URL(find('.rose-nav-breadcrumbs-link:last-child').href) + .pathname; assert.equal(crumbPath, projectURL, 'Last crumb has expected URL.'); await click(find('.rose-nav-breadcrumbs-link:first-child')); - assert.equal(currentURL(), projectsURL, 'After navigating via breadcrumbs, we are one level up.'); + assert.equal( + currentURL(), + projectsURL, + 'After navigating via breadcrumbs, we are one level up.' + ); }); test('can navigate via breadcrumbs to projects from new project', async function (assert) { assert.expect(3); await visit(newProjectURL); assert.equal(currentURL(), newProjectURL, 'We begin on the expected page.'); - crumbPath = new URL(find('.rose-nav-breadcrumbs-link:last-child').href).pathname; + crumbPath = new URL(find('.rose-nav-breadcrumbs-link:last-child').href) + .pathname; assert.equal(crumbPath, newProjectURL, 'Last crumb has expected URL.'); await click(find('.rose-nav-breadcrumbs-link:first-child')); - assert.equal(currentURL(), projectsURL, 'After navigating via breadcrumbs, we are one level up.'); + assert.equal( + currentURL(), + projectsURL, + 'After navigating via breadcrumbs, we are one level up.' + ); }); test('can navigate via breadcrumbs to project from host catalogs', async function (assert) { assert.expect(3); await visit(hostCatalogsURL); - assert.equal(currentURL(), hostCatalogsURL, 'We begin on the expected page.'); - crumbPath = new URL(find('.rose-nav-breadcrumbs-link:last-child').href).pathname; + assert.equal( + currentURL(), + hostCatalogsURL, + 'We begin on the expected page.' + ); + crumbPath = new URL(find('.rose-nav-breadcrumbs-link:last-child').href) + .pathname; assert.equal(crumbPath, hostCatalogsURL, 'Last crumb has expected URL.'); await click(findAll('.rose-nav-breadcrumbs-link')[1]); - assert.equal(currentURL(), projectURL, 'After navigating via breadcrumbs, we are one level up.'); + assert.equal( + currentURL(), + projectURL, + 'After navigating via breadcrumbs, we are one level up.' + ); }); test('can navigate via breadcrumbs to host catalogs from host catalog', async function (assert) { assert.expect(3); await visit(hostCatalogURL); - assert.equal(currentURL(), hostCatalogURL, 'We begin on the expected page.'); - crumbPath = new URL(find('.rose-nav-breadcrumbs-link:last-child').href).pathname; + assert.equal( + currentURL(), + hostCatalogURL, + 'We begin on the expected page.' + ); + crumbPath = new URL(find('.rose-nav-breadcrumbs-link:last-child').href) + .pathname; assert.equal(crumbPath, hostCatalogURL, 'Last crumb has expected URL.'); await click(findAll('.rose-nav-breadcrumbs-link')[2]); - assert.equal(currentURL(), hostCatalogsURL, 'After navigating via breadcrumbs, we are one level up.'); + assert.equal( + currentURL(), + hostCatalogsURL, + 'After navigating via breadcrumbs, we are one level up.' + ); }); test('can navigate via breadcrumbs to host catalogs from new host catalog', async function (assert) { assert.expect(3); await visit(newHostCatalogURL); - assert.equal(currentURL(), newHostCatalogURL, 'We begin on the expected page.'); - crumbPath = new URL(find('.rose-nav-breadcrumbs-link:last-child').href).pathname; + assert.equal( + currentURL(), + newHostCatalogURL, + 'We begin on the expected page.' + ); + crumbPath = new URL(find('.rose-nav-breadcrumbs-link:last-child').href) + .pathname; assert.equal(crumbPath, newHostCatalogURL, 'Last crumb has expected URL.'); await click(findAll('.rose-nav-breadcrumbs-link')[2]); - assert.equal(currentURL(), hostCatalogsURL, 'After navigating via breadcrumbs, we are one level up.'); + assert.equal( + currentURL(), + hostCatalogsURL, + 'After navigating via breadcrumbs, we are one level up.' + ); }); test('can navigate via breadcrumbs to roles from role', async function (assert) { assert.expect(3); await visit(roleURL); assert.equal(currentURL(), roleURL, 'We begin on the expected page.'); - crumbPath = new URL(find('.rose-nav-breadcrumbs-link:last-child').href).pathname; + crumbPath = new URL(find('.rose-nav-breadcrumbs-link:last-child').href) + .pathname; assert.equal(crumbPath, roleURL, 'Last crumb has expected URL.'); await click(find('.rose-nav-breadcrumbs-link:first-child')); - assert.equal(currentURL(), rolesURL, 'After navigating via breadcrumbs, we are one level up.'); + assert.equal( + currentURL(), + rolesURL, + 'After navigating via breadcrumbs, we are one level up.' + ); }); test('can navigate via breadcrumbs to roles from new role', async function (assert) { assert.expect(3); await visit(newRoleURL); assert.equal(currentURL(), newRoleURL, 'We begin on the expected page.'); - crumbPath = new URL(find('.rose-nav-breadcrumbs-link:last-child').href).pathname; + crumbPath = new URL(find('.rose-nav-breadcrumbs-link:last-child').href) + .pathname; assert.equal(crumbPath, newRoleURL, 'Last crumb has expected URL.'); await click(find('.rose-nav-breadcrumbs-link:first-child')); - assert.equal(currentURL(), rolesURL, 'After navigating via breadcrumbs, we are one level up.'); + assert.equal( + currentURL(), + rolesURL, + 'After navigating via breadcrumbs, we are one level up.' + ); }); test('can navigate via breadcrumbs to groups from group', async function (assert) { assert.expect(3); await visit(groupURL); assert.equal(currentURL(), groupURL, 'We begin on the expected page.'); - crumbPath = new URL(find('.rose-nav-breadcrumbs-link:last-child').href).pathname; + crumbPath = new URL(find('.rose-nav-breadcrumbs-link:last-child').href) + .pathname; assert.equal(crumbPath, groupURL, 'Last crumb has expected URL.'); await click(find('.rose-nav-breadcrumbs-link:first-child')); - assert.equal(currentURL(), groupsURL, 'After navigating via breadcrumbs, we are one level up.'); + assert.equal( + currentURL(), + groupsURL, + 'After navigating via breadcrumbs, we are one level up.' + ); }); test('can navigate via breadcrumbs to groups from new group', async function (assert) { assert.expect(3); await visit(newGroupURL); assert.equal(currentURL(), newGroupURL, 'We begin on the expected page.'); - crumbPath = new URL(find('.rose-nav-breadcrumbs-link:last-child').href).pathname; + crumbPath = new URL(find('.rose-nav-breadcrumbs-link:last-child').href) + .pathname; assert.equal(crumbPath, newGroupURL, 'Last crumb has expected URL.'); await click(find('.rose-nav-breadcrumbs-link:first-child')); - assert.equal(currentURL(), groupsURL, 'After navigating via breadcrumbs, we are one level up.'); + assert.equal( + currentURL(), + groupsURL, + 'After navigating via breadcrumbs, we are one level up.' + ); }); test('can navigate via breadcrumbs to users from user', async function (assert) { assert.expect(3); await visit(userURL); assert.equal(currentURL(), userURL, 'We begin on the expected page.'); - crumbPath = new URL(find('.rose-nav-breadcrumbs-link:last-child').href).pathname; + crumbPath = new URL(find('.rose-nav-breadcrumbs-link:last-child').href) + .pathname; assert.equal(crumbPath, userURL, 'Last crumb has expected URL.'); await click(find('.rose-nav-breadcrumbs-link:first-child')); - assert.equal(currentURL(), usersURL, 'After navigating via breadcrumbs, we are one level up.'); + assert.equal( + currentURL(), + usersURL, + 'After navigating via breadcrumbs, we are one level up.' + ); }); test('can navigate via breadcrumbs to users from new user', async function (assert) { assert.expect(3); await visit(newUserURL); assert.equal(currentURL(), newUserURL, 'We begin on th expected page.'); - crumbPath = new URL(find('.rose-nav-breadcrumbs-link:last-child').href).pathname; + crumbPath = new URL(find('.rose-nav-breadcrumbs-link:last-child').href) + .pathname; assert.equal(crumbPath, newUserURL, 'Last crumb has expected URL.'); await click(find('.rose-nav-breadcrumbs-link:first-child')); - assert.equal(currentURL(), usersURL, 'After navigating via breadcrumbs, we are one level up.'); + assert.equal( + currentURL(), + usersURL, + 'After navigating via breadcrumbs, we are one level up.' + ); }); }); diff --git a/ui/core/tests/acceptance/groups-test.js b/ui/core/tests/acceptance/groups-test.js index 7f3f2ec69..b619d09ce 100644 --- a/ui/core/tests/acceptance/groups-test.js +++ b/ui/core/tests/acceptance/groups-test.js @@ -4,47 +4,81 @@ import { setupApplicationTest } from 'ember-qunit'; import setupMirage from 'ember-cli-mirage/test-support/setup-mirage'; import a11yAudit from 'ember-a11y-testing/test-support/audit'; import { Response } from 'miragejs'; +import { + authenticateSession, + // These are left here intentionally for future reference. + //currentSession, + //invalidateSession, +} from 'ember-simple-auth/test-support'; - -module('Acceptance | groups', function(hooks) { +module('Acceptance | groups', function (hooks) { setupApplicationTest(hooks); setupMirage(hooks); + let orgScope; + let groupsURL; + let groupURL; + let newGroupURL; + hooks.beforeEach(function () { - this.server.create('org'); + orgScope = this.server.create( + 'scope', + { + type: 'org', + }, + 'withChildren' + ); + + const group = this.server.create('group', { + scope: { + id: orgScope.id, + type: orgScope.type, + }, + }); + + groupsURL = `/scopes/${orgScope.id}/groups`; + groupURL = `${groupsURL}/${group.id}`; + newGroupURL = `${groupsURL}/new`; + + authenticateSession({}); }); test('visiting groups', async function (assert) { assert.expect(1); - await visit('/orgs/1/groups'); + await visit(groupsURL); await a11yAudit(); - assert.equal(currentURL(), '/orgs/1/groups'); + assert.equal(currentURL(), groupsURL); + }); + + test('visiting a group', async function (assert) { + assert.expect(1); + await visit(newGroupURL); + await a11yAudit(); + assert.equal(currentURL(), newGroupURL); }); test('can create new group', async function (assert) { - assert.expect(4); - assert.equal(this.server.db.groups.length, 0); - await visit('/orgs/1/groups/new'); + assert.expect(1); + const groupsCount = this.server.db.groups.length; + await visit(newGroupURL); await fillIn('[name="name"]', 'group name'); await click('[type="submit"]'); - assert.equal(currentURL(), '/orgs/1/groups/1'); - assert.equal(this.server.db.groups.length, 1); - assert.equal(this.server.db.groups[0].name, 'group name'); + assert.equal(this.server.db.groups.length, groupsCount + 1); }); test('can cancel new group creation', async function (assert) { - assert.expect(3); - assert.equal(this.server.db.groups.length, 0); - await visit('/orgs/1/groups/new'); + assert.expect(2); + const groupsCount = this.server.db.groups.length; + await visit(newGroupURL); await fillIn('[name="name"]', 'group name'); await click('.rose-form-actions [type="button"]'); - assert.equal(currentURL(), '/orgs/1/groups'); - assert.equal(this.server.db.groups.length, 0); + assert.equal(currentURL(), groupsURL); + assert.equal(this.server.db.groups.length, groupsCount); }); test('saving a new group with invalid fields displays error messages', async function (assert) { assert.expect(2); - this.server.post('/orgs/:org_id/groups', () => { + this.server.post('/scopes/:scope_id/groups', () => { return new Response( 400, {}, @@ -63,7 +97,7 @@ module('Acceptance | groups', function(hooks) { } ); }); - await visit('/orgs/1/groups/new'); + await visit(newGroupURL); await fillIn('[name="name"]', 'group name'); await click('[type="submit"]'); assert.ok( @@ -80,36 +114,32 @@ module('Acceptance | groups', function(hooks) { test('can save changes to an existing group', async function (assert) { assert.expect(2); - this.server.createList('group', 1, {name: 'Admin group'}); - await visit('/orgs/1/groups/1'); + await visit(groupURL); await fillIn('[name="name"]', 'Updated admin group'); await click('.rose-form-actions [type="submit"]'); - assert.equal(currentURL(), '/orgs/1/groups/1'); + assert.equal(currentURL(), groupURL); assert.equal(this.server.db.groups[0].name, 'Updated admin group'); }); test('can cancel changes to an existing group', async function (assert) { assert.expect(1); - this.server.createList('group', 1, {name: 'Admin group'}); - await visit('/orgs/1/groups/1'); + await visit(groupURL); await fillIn('[name="name"]', 'Updated admin group'); await click('.rose-form-actions [type="button"]'); - assert.equal(find('[name="name"]').value, 'Admin group'); + assert.notEqual(find('[name="name"]').value, 'Updated admin group'); }); - test('can delete a group', async function(assert) { - assert.expect(2); - this.server.createList('group', 1); - assert.equal(this.server.db.groups.length, 1); - await visit('/orgs/1/groups/1'); + test('can delete a group', async function (assert) { + assert.expect(1); + const groupsCount = this.server.db.groups.length; + await visit(groupURL); await click('.rose-button-warning'); - assert.equal(this.server.db.groups.length, 0); + assert.equal(this.server.db.groups.length, groupsCount - 1); }); test('saving an existing group with invalid fields displays error messages', async function (assert) { assert.expect(2); - this.server.createList('group', 1); - this.server.patch('/orgs/:org_id/groups/:id', () => { + this.server.patch('/scopes/:scope_id/groups/:id', () => { return new Response( 400, {}, @@ -128,7 +158,7 @@ module('Acceptance | groups', function(hooks) { } ); }); - await visit('/orgs/1/groups/1'); + await visit(groupURL); await fillIn('[name="name"]', 'random string'); await click('[type="submit"]'); assert.ok( @@ -145,8 +175,7 @@ module('Acceptance | groups', function(hooks) { test('errors are displayed when save project fails', async function (assert) { assert.expect(1); - this.server.createList('group', 1); - this.server.patch('/orgs/:org_id/groups/:id', () => { + this.server.patch('/scopes/:scope_id/groups/:id', () => { return new Response( 490, {}, @@ -157,7 +186,7 @@ module('Acceptance | groups', function(hooks) { } ); }); - await visit('/orgs/1/groups/1'); + await visit(groupURL); await fillIn('[name="name"]', 'Role name'); await click('[type="submit"]'); assert.ok( @@ -169,8 +198,7 @@ module('Acceptance | groups', function(hooks) { test('errors are displayed when delete project fails', async function (assert) { assert.expect(1); - this.server.createList('group', 1); - this.server.del('/orgs/:org_id/groups/:id', () => { + this.server.del('/scopes/:scope_id/groups/:id', () => { return new Response( 490, {}, @@ -181,7 +209,7 @@ module('Acceptance | groups', function(hooks) { } ); }); - await visit('/orgs/1/groups/1'); + await visit(groupURL); await click('.rose-button-warning'); assert.ok( find('[role="alert"]').textContent.trim(), diff --git a/ui/core/tests/acceptance/host-catalogs-test.js b/ui/core/tests/acceptance/host-catalogs-test.js index 4be43309f..59bc08dcc 100644 --- a/ui/core/tests/acceptance/host-catalogs-test.js +++ b/ui/core/tests/acceptance/host-catalogs-test.js @@ -1,92 +1,130 @@ import { module, test } from 'qunit'; -import { visit, currentURL, click, fillIn, find } from '@ember/test-helpers'; +import { visit, currentURL, fillIn, click, find } from '@ember/test-helpers'; import { setupApplicationTest } from 'ember-qunit'; import setupMirage from 'ember-cli-mirage/test-support/setup-mirage'; import a11yAudit from 'ember-a11y-testing/test-support/audit'; import { Response } from 'miragejs'; +import { + authenticateSession, + // These are left here intentionally for future reference. + //currentSession, + //invalidateSession, +} from 'ember-simple-auth/test-support'; -module('Acceptance | host catalogs', function(hooks) { +module('Acceptance | host catalogs', function (hooks) { setupApplicationTest(hooks); setupMirage(hooks); + const instances = { + scopes: { + global: null, + org: null, + project: null + }, + hostCatalog: null + }; + const urls = { + orgScope: null, + projects: null, + project: null, + hostCatalogs: null, + newHostCatalog: null, + hostCatalog: null + }; + hooks.beforeEach(function () { - this.server.create('org'); + // Setup Mirage mock resources for this test + instances.scopes.global = this.server.create('scope', { id: 'global' }); + instances.scopes.org = this.server.create('scope', { type: 'org', + scope: { id: instances.scopes.global.id, type: instances.scopes.global.type } + }); + instances.scopes.project = this.server.create('scope', { type: 'project', + scope: { id: instances.scopes.org.id, type: instances.scopes.org.type } + }); + instances.hostCatalog = this.server.create('host-catalog', { + scope: { + id: instances.scopes.project.id, + type: instances.scopes.project.type + } + }); + + // Generate route URLs for resources + urls.orgScope = `/scopes/${instances.scopes.org.id}`; + urls.projects = `${urls.orgScope}/projects`; + urls.project = `${urls.projects}/${instances.hostCatalog.scope.id}`; + urls.hostCatalogs = `${urls.project}/host-catalogs`; + urls.newHostCatalog = `${urls.hostCatalogs}/new`; + urls.hostCatalog = `${urls.hostCatalogs}/${instances.hostCatalog.id}`; }); test('visiting host catalogs', async function (assert) { assert.expect(1); - this.server.createList('project', 1); - this.server.createList('host-catalog', 1); - await visit('/orgs/1/projects/1/host-catalogs'); + authenticateSession(); + await visit(urls.hostCatalogs); await a11yAudit(); - assert.equal(currentURL(), '/orgs/1/projects/1/host-catalogs'); + assert.equal(currentURL(), urls.hostCatalogs); }); - test('can navigate to host catalog form', async function(assert) { + test('can navigate to host catalog form', async function (assert) { assert.expect(1); - this.server.createList('project', 1); - this.server.createList('host-catalog', 1); - await visit('/orgs/1/projects/1/host-catalogs'); - await click('main .rose-table-cell:first-child a'); + authenticateSession({}); + await visit(urls.hostCatalogs); + await click('main tbody .rose-table-cell:nth-child(2) a'); await a11yAudit(); - assert.equal(currentURL(), '/orgs/1/projects/1/host-catalogs/1'); + assert.equal(currentURL(), urls.hostCatalog); }); - test('can delete host catalog', async function(assert) { + test('can delete host catalog', async function (assert) { assert.expect(2); - this.server.createList('project', 1); - this.server.createList('host-catalog', 1); - assert.equal(this.server.db.hostCatalogs.length, 1); - await visit('/orgs/1/projects/1/host-catalogs/1'); + const hostCatalogsCount = this.server.db.hostCatalogs.length; + authenticateSession({}); + await visit(urls.hostCatalog); + assert.equal(currentURL(), urls.hostCatalog); await click('.rose-button-warning'); - assert.equal(this.server.db.hostCatalogs.length, 0); + assert.equal(this.server.db.hostCatalogs.length, hostCatalogsCount - 1); }); - test('can update host catalog and save changes', async function(assert) { + test('can update host catalog and save changes', async function (assert) { assert.expect(1); - this.server.createList('project', 1); - this.server.createList('host-catalog', 1); - await visit('/orgs/1/projects/1/host-catalogs/1'); + authenticateSession({}); + await visit(urls.hostCatalog); await fillIn('[name="name"]', 'Test Name'); await click('form [type="submit"]:not(:disabled)'); assert.equal(this.server.db.hostCatalogs[0].name, 'Test Name'); }); - test('can update host catalog and cancel changes', async function(assert) { + test('can update host catalog and cancel changes', async function (assert) { assert.expect(1); - this.server.createList('project', 1); - this.server.createList('host-catalog', 1); - await visit('/orgs/1/projects/1/host-catalogs/1'); + authenticateSession({}); + await visit(urls.hostCatalog); await fillIn('[name="name"]', 'Test Name'); await click('form button:not([type="submit"])'); assert.notEqual(this.server.db.hostCatalogs[0].name, 'Test Name'); }); test('can create host catalog and save changes', async function(assert) { - assert.expect(2); - this.server.createList('project', 1); - await visit('/orgs/1/projects/1/host-catalogs/new'); + assert.expect(1); + const hostCatalogsCount = this.server.db.hostCatalogs.length; + authenticateSession({}); + await visit(urls.newHostCatalog); await fillIn('[name="name"]', 'Test Name'); await click('form [type="submit"]:not(:disabled)'); - assert.equal(this.server.db.hostCatalogs.length, 1); - assert.equal(currentURL(), '/orgs/1/projects/1/host-catalogs/1'); + assert.equal(this.server.db.hostCatalogs.length, hostCatalogsCount + 1); }); test('can create host catalog and cancel changes', async function(assert) { - assert.expect(2); - this.server.createList('project', 1); - await visit('/orgs/1/projects/1/host-catalogs/new'); + assert.expect(1); + const hostCatalogsCount = this.server.db.hostCatalogs.length; + authenticateSession({}); + await visit(urls.newHostCatalog); await fillIn('[name="name"]', 'Test Name'); await click('form button:not([type="submit"])'); - assert.equal(this.server.db.hostCatalogs.length, 0); - assert.equal(currentURL(), '/orgs/1/projects/1/host-catalogs'); + assert.equal(this.server.db.hostCatalogs.length, hostCatalogsCount); }); test('errors are displayed when save host catalog fails', async function (assert) { assert.expect(1); - this.server.createList('project', 1); - this.server.createList('host-catalog', 1); - this.server.patch('/orgs/:org_id/projects/:project_id/host-catalogs/:id', () => { + this.server.patch('/scopes/:scope_id/host-catalogs/:id', () => { return new Response( 490, {}, @@ -97,7 +135,8 @@ module('Acceptance | host catalogs', function(hooks) { } ); }); - await visit('/orgs/1/projects/1/host-catalogs/1'); + authenticateSession({}); + await visit(urls.hostCatalog); await fillIn('[name="name"]', 'random string'); await click('form [type="submit"]'); await a11yAudit(); @@ -110,9 +149,7 @@ module('Acceptance | host catalogs', function(hooks) { test('errors are displayed when delete host catalog fails', async function (assert) { assert.expect(1); - this.server.createList('project', 1); - this.server.createList('host-catalog', 1); - this.server.del('/orgs/:org_id/projects/:project_id/host-catalogs/:id', () => { + this.server.del('/scopes/:scope_id/host-catalogs/:id', () => { return new Response( 490, {}, @@ -123,7 +160,8 @@ module('Acceptance | host catalogs', function(hooks) { } ); }); - await visit('/orgs/1/projects/1/host-catalogs/1'); + authenticateSession({}); + await visit(urls.hostCatalog); await click('.rose-button-warning'); await a11yAudit(); assert.ok( @@ -135,7 +173,7 @@ module('Acceptance | host catalogs', function(hooks) { test('saving a new host catalog with invalid fields displays error messages', async function (assert) { assert.expect(2); - this.server.post('/orgs/:org_id/projects/:project_id/host-catalogs', () => { + this.server.post('/scopes/:scope_id/host-catalogs', () => { return new Response( 400, {}, @@ -154,8 +192,8 @@ module('Acceptance | host catalogs', function(hooks) { } ); }); - this.server.createList('project', 1); - await visit('/orgs/1/projects/1/host-catalogs/new'); + authenticateSession({}); + await visit(urls.newHostCatalog); await fillIn('[name="name"]', 'random string'); await click('form [type="submit"]'); await a11yAudit(); @@ -173,9 +211,7 @@ module('Acceptance | host catalogs', function(hooks) { test('saving an existing host catalog with invalid fields displays error messages', async function (assert) { assert.expect(2); - this.server.createList('project', 1); - this.server.createList('host-catalog', 1); - this.server.patch('/orgs/:org_id/projects/:project_id/host-catalogs/:id', () => { + this.server.patch('/scopes/:scope_id/host-catalogs/:id', () => { return new Response( 400, {}, @@ -194,7 +230,8 @@ module('Acceptance | host catalogs', function(hooks) { } ); }); - await visit('/orgs/1/projects/1/host-catalogs/1'); + authenticateSession({}); + await visit(urls.hostCatalog); await fillIn('[name="name"]', 'random string'); await click('form [type="submit"]'); await a11yAudit(); @@ -209,5 +246,4 @@ module('Acceptance | host catalogs', function(hooks) { 'Displays field-level errors.' ); }); - }); diff --git a/ui/core/tests/acceptance/index-test.js b/ui/core/tests/acceptance/index-test.js deleted file mode 100644 index 5a2ec4631..000000000 --- a/ui/core/tests/acceptance/index-test.js +++ /dev/null @@ -1,15 +0,0 @@ -import { module, test } from 'qunit'; -import { visit, currentURL } from '@ember/test-helpers'; -import { setupApplicationTest } from 'ember-qunit'; -import a11yAudit from 'ember-a11y-testing/test-support/audit'; - -module('Acceptance | index', function (hooks) { - setupApplicationTest(hooks); - - test('visiting /', async function (assert) { - assert.expect(1); - await visit('/'); - await a11yAudit(); - assert.equal(currentURL(), '/'); - }); -}); diff --git a/ui/core/tests/acceptance/projects-test.js b/ui/core/tests/acceptance/projects-test.js index 0e7877c97..ced4a8f13 100644 --- a/ui/core/tests/acceptance/projects-test.js +++ b/ui/core/tests/acceptance/projects-test.js @@ -4,46 +4,83 @@ import { setupApplicationTest } from 'ember-qunit'; import setupMirage from 'ember-cli-mirage/test-support/setup-mirage'; import a11yAudit from 'ember-a11y-testing/test-support/audit'; import { Response } from 'miragejs'; +import { + authenticateSession, + // These are left here intentionally for future reference. + //currentSession, + //invalidateSession, +} from 'ember-simple-auth/test-support'; module('Acceptance | projects', function (hooks) { setupApplicationTest(hooks); setupMirage(hooks); + let org; + let orgID; + let projectsURL; + let newProjectURL; + let existingProject; + let existingProjectURL; + let getProjectScopesCount; + let getFirstProjectScope; + let initialProjectScopesCount; + hooks.beforeEach(function () { - this.server.create('org'); + authenticateSession({}); + org = this.server.create('scope', { type: 'org' }, 'withChildren'); + getProjectScopesCount = () => + this.server.schema.scopes.where({ type: 'project' }).length; + getFirstProjectScope = () => + this.server.schema.scopes.where({ type: 'project' }).models[0]; + initialProjectScopesCount = getProjectScopesCount(); + orgID = org.id; + projectsURL = `/scopes/${orgID}/projects`; + newProjectURL = `/scopes/${orgID}/projects/new`; + existingProject = getFirstProjectScope(); + existingProjectURL = `/scopes/${orgID}/projects/${existingProject.id}`; }); test('visiting projects', async function (assert) { assert.expect(1); - this.server.createList('project', 1); - await visit('/orgs/1/projects'); + await visit(projectsURL); await a11yAudit(); - assert.equal(currentURL(), '/orgs/1/projects'); + assert.equal(currentURL(), projectsURL); + }); + + test('visiting projects within a project scope redirects to the parent org scope', async function (assert) { + assert.expect(2); + const wrongProjectsURL = `/scopes/${existingProject.id}/projects`; + assert.notEqual(wrongProjectsURL, projectsURL); + await visit(wrongProjectsURL); + await a11yAudit(); + assert.equal( + currentURL(), + projectsURL, + 'Wrong projects path was redirected to correct path.' + ); }); test('can create new projects', async function (assert) { - assert.expect(3); - assert.equal(this.server.db.projects.length, 0); - await visit('/orgs/1/projects/new'); + assert.expect(1); + await visit(newProjectURL); await fillIn('[name="name"]', 'random string'); await click('[type="submit"]'); - assert.equal(currentURL(), '/orgs/1/projects/1'); - assert.equal(this.server.db.projects.length, 1); + assert.equal(getProjectScopesCount(), initialProjectScopesCount + 1); }); test('can cancel create new projects', async function (assert) { assert.expect(3); - assert.equal(this.server.db.projects.length, 0); - await visit('/orgs/1/projects/new'); + assert.equal(getProjectScopesCount(), initialProjectScopesCount); + await visit(newProjectURL); await fillIn('[name="name"]', 'random string'); await click('.rose-form-actions [type="button"]'); - assert.equal(currentURL(), '/orgs/1/projects'); - assert.equal(this.server.db.projects.length, 0); + assert.equal(currentURL(), projectsURL); + assert.equal(getProjectScopesCount(), initialProjectScopesCount); }); test('saving a new project with invalid fields displays error messages', async function (assert) { assert.expect(2); - this.server.post('/orgs/:org_id/projects', () => { + this.server.post('/scopes', () => { return new Response( 400, {}, @@ -62,8 +99,7 @@ module('Acceptance | projects', function (hooks) { } ); }); - await visit('/orgs/1/projects/new'); - await fillIn('[name="name"]', 'random string'); + await visit(newProjectURL); await click('[type="submit"]'); assert.ok( find('[role="alert"]').textContent.trim(), @@ -78,37 +114,34 @@ module('Acceptance | projects', function (hooks) { }); test('can save changes to existing project', async function (assert) { - assert.expect(2); - this.server.createList('project', 1, {name: 'MyProject'}); - await visit('/orgs/1/projects/1'); + assert.expect(3); + assert.notEqual(existingProject.name, 'random string'); + await visit(existingProjectURL); await fillIn('[name="name"]', 'random string'); await click('.rose-form-actions [type="submit"]'); - assert.equal(currentURL(), '/orgs/1/projects/1'); - assert.equal(this.server.db.projects[0].name, 'random string'); + assert.equal(currentURL(), existingProjectURL); + assert.equal(getFirstProjectScope().name, 'random string'); }); test('can cancel changes to existing project', async function (assert) { - assert.expect(1); - this.server.createList('project', 1, {name: 'MyProject'}); - await visit('/orgs/1/projects/1'); + assert.expect(2); + await visit(existingProjectURL); await fillIn('[name="name"]', 'random string'); await click('.rose-form-actions [type="button"]'); - assert.equal(find('[name="name"]').value, 'MyProject'); + assert.notEqual(existingProject.name, 'random string'); + assert.equal(find('[name="name"]').value, existingProject.name); }); - test('can delete project', async function(assert) { - assert.expect(2); - this.server.createList('project', 1); - assert.equal(this.server.db.projects.length, 1); - await visit('/orgs/1/projects/1'); + test('can delete project', async function (assert) { + assert.expect(1); + await visit(existingProjectURL); await click('.rose-button-warning'); - assert.equal(this.server.db.projects.length, 0); + assert.equal(getProjectScopesCount(), initialProjectScopesCount - 1); }); test('saving an existing project with invalid fields displays error messages', async function (assert) { assert.expect(2); - this.server.createList('project', 1); - this.server.patch('/orgs/:org_id/projects/:id', () => { + this.server.patch('/scopes/:id', () => { return new Response( 400, {}, @@ -127,7 +160,7 @@ module('Acceptance | projects', function (hooks) { } ); }); - await visit('/orgs/1/projects/1'); + await visit(existingProjectURL); await fillIn('[name="name"]', 'random string'); await click('[type="submit"]'); assert.ok( @@ -144,8 +177,7 @@ module('Acceptance | projects', function (hooks) { test('errors are displayed when save project fails', async function (assert) { assert.expect(1); - this.server.createList('project', 1); - this.server.patch('/orgs/:org_id/projects/:id', () => { + this.server.patch('/scopes/:id', () => { return new Response( 490, {}, @@ -156,7 +188,7 @@ module('Acceptance | projects', function (hooks) { } ); }); - await visit('/orgs/1/projects/1'); + await visit(existingProjectURL); await fillIn('[name="name"]', 'random string'); await click('[type="submit"]'); assert.ok( @@ -168,8 +200,7 @@ module('Acceptance | projects', function (hooks) { test('errors are displayed when delete project fails', async function (assert) { assert.expect(1); - this.server.createList('project', 1); - this.server.del('/orgs/:org_id/projects/:id', () => { + this.server.del('/scopes/:id', () => { return new Response( 490, {}, @@ -180,7 +211,7 @@ module('Acceptance | projects', function (hooks) { } ); }); - await visit('/orgs/1/projects/1'); + await visit(existingProjectURL); await click('.rose-button-warning'); assert.ok( find('[role="alert"]').textContent.trim(), diff --git a/ui/core/tests/acceptance/roles-test.js b/ui/core/tests/acceptance/roles-test.js index 976ba47b7..dd8483574 100644 --- a/ui/core/tests/acceptance/roles-test.js +++ b/ui/core/tests/acceptance/roles-test.js @@ -4,46 +4,81 @@ import { setupApplicationTest } from 'ember-qunit'; import setupMirage from 'ember-cli-mirage/test-support/setup-mirage'; import a11yAudit from 'ember-a11y-testing/test-support/audit'; import { Response } from 'miragejs'; +import { + authenticateSession, + // These are left here intentionally for future reference. + //currentSession, + //invalidateSession, +} from 'ember-simple-auth/test-support'; module('Acceptance | roles', function (hooks) { setupApplicationTest(hooks); setupMirage(hooks); + let orgScope; + let rolesURL; + let roleURL; + let newRoleURL; + hooks.beforeEach(function () { - this.server.create('org'); + orgScope = this.server.create( + 'scope', + { + type: 'org', + }, + 'withChildren' + ); + + const role = this.server.create('role', { + scope: { + id: orgScope.id, + type: orgScope.type, + }, + }); + + rolesURL = `/scopes/${orgScope.id}/roles`; + roleURL = `${rolesURL}/${role.id}`; + newRoleURL = `${rolesURL}/new`; + + authenticateSession({}); }); test('visiting roles', async function (assert) { assert.expect(1); - await visit('/orgs/1/roles'); + await visit(rolesURL); await a11yAudit(); - assert.equal(currentURL(), '/orgs/1/roles'); + assert.equal(currentURL(), rolesURL); + }); + + test('visiting a role', async function (assert) { + assert.expect(1); + await visit(newRoleURL); + await a11yAudit(); + assert.equal(currentURL(), newRoleURL); }); test('can create new role', async function (assert) { - assert.expect(4); - assert.equal(this.server.db.roles.length, 0); - await visit('/orgs/1/roles/new'); + assert.expect(1); + const rolesCount = this.server.db.roles.length; + await visit(newRoleURL); await fillIn('[name="name"]', 'role name'); await click('[type="submit"]'); - assert.equal(currentURL(), '/orgs/1/roles/1'); - assert.equal(this.server.db.roles.length, 1); - assert.equal(this.server.db.roles[0].name, 'role name'); + assert.equal(this.server.db.roles.length, rolesCount + 1); }); test('can cancel new role creation', async function (assert) { - assert.expect(3); - assert.equal(this.server.db.roles.length, 0); - await visit('/orgs/1/roles/new'); + assert.expect(2); + const rolesCount = this.server.db.roles.length; + await visit(newRoleURL); await fillIn('[name="name"]', 'role name'); await click('.rose-form-actions [type="button"]'); - assert.equal(currentURL(), '/orgs/1/roles'); - assert.equal(this.server.db.roles.length, 0); + assert.equal(currentURL(), rolesURL); + assert.equal(this.server.db.roles.length, rolesCount); }); test('saving a new role with invalid fields displays error messages', async function (assert) { assert.expect(2); - this.server.post('/orgs/:org_id/roles', () => { + this.server.post('/scopes/:scope_id/roles', () => { return new Response( 400, {}, @@ -62,7 +97,7 @@ module('Acceptance | roles', function (hooks) { } ); }); - await visit('/orgs/1/roles/new'); + await visit(newRoleURL); await fillIn('[name="name"]', 'role name'); await click('[type="submit"]'); assert.ok( @@ -79,36 +114,32 @@ module('Acceptance | roles', function (hooks) { test('can save changes to an existing role', async function (assert) { assert.expect(2); - this.server.createList('role', 1, {name: 'Admin role'}); - await visit('/orgs/1/roles/1'); + await visit(roleURL); await fillIn('[name="name"]', 'Updated admin role'); await click('.rose-form-actions [type="submit"]'); - assert.equal(currentURL(), '/orgs/1/roles/1'); + assert.equal(currentURL(), roleURL); assert.equal(this.server.db.roles[0].name, 'Updated admin role'); }); test('can cancel changes to an existing role', async function (assert) { assert.expect(1); - this.server.createList('role', 1, {name: 'Admin role'}); - await visit('/orgs/1/roles/1'); + await visit(roleURL); await fillIn('[name="name"]', 'Updated admin role'); await click('.rose-form-actions [type="button"]'); - assert.equal(find('[name="name"]').value, 'Admin role'); + assert.notEqual(find('[name="name"]').value, 'Updated admin role'); }); - test('can delete a role', async function(assert) { - assert.expect(2); - this.server.createList('role', 1); - assert.equal(this.server.db.roles.length, 1); - await visit('/orgs/1/roles/1'); + test('can delete a role', async function (assert) { + assert.expect(1); + const rolesCount = this.server.db.roles.length; + await visit(roleURL); await click('.rose-button-warning'); - assert.equal(this.server.db.roles.length, 0); + assert.equal(this.server.db.roles.length, rolesCount - 1); }); test('saving an existing role with invalid fields displays error messages', async function (assert) { assert.expect(2); - this.server.createList('role', 1); - this.server.patch('/orgs/:org_id/roles/:id', () => { + this.server.patch('/scopes/:scope_id/roles/:id', () => { return new Response( 400, {}, @@ -127,7 +158,7 @@ module('Acceptance | roles', function (hooks) { } ); }); - await visit('/orgs/1/roles/1'); + await visit(roleURL); await fillIn('[name="name"]', 'random string'); await click('[type="submit"]'); assert.ok( @@ -144,8 +175,7 @@ module('Acceptance | roles', function (hooks) { test('errors are displayed when save project fails', async function (assert) { assert.expect(1); - this.server.createList('role', 1); - this.server.patch('/orgs/:org_id/roles/:id', () => { + this.server.patch('/scopes/:scope_id/roles/:id', () => { return new Response( 490, {}, @@ -156,7 +186,7 @@ module('Acceptance | roles', function (hooks) { } ); }); - await visit('/orgs/1/roles/1'); + await visit(roleURL); await fillIn('[name="name"]', 'Role name'); await click('[type="submit"]'); assert.ok( @@ -168,8 +198,7 @@ module('Acceptance | roles', function (hooks) { test('errors are displayed when delete project fails', async function (assert) { assert.expect(1); - this.server.createList('role', 1); - this.server.del('/orgs/:org_id/roles/:id', () => { + this.server.del('/scopes/:scope_id/roles/:id', () => { return new Response( 490, {}, @@ -180,7 +209,7 @@ module('Acceptance | roles', function (hooks) { } ); }); - await visit('/orgs/1/roles/1'); + await visit(roleURL); await click('.rose-button-warning'); assert.ok( find('[role="alert"]').textContent.trim(), diff --git a/ui/core/tests/acceptance/users-test.js b/ui/core/tests/acceptance/users-test.js index f1160c7f3..31fa5c98e 100644 --- a/ui/core/tests/acceptance/users-test.js +++ b/ui/core/tests/acceptance/users-test.js @@ -4,47 +4,81 @@ import { setupApplicationTest } from 'ember-qunit'; import setupMirage from 'ember-cli-mirage/test-support/setup-mirage'; import a11yAudit from 'ember-a11y-testing/test-support/audit'; import { Response } from 'miragejs'; +import { + authenticateSession, + // These are left here intentionally for future reference. + //currentSession, + //invalidateSession, +} from 'ember-simple-auth/test-support'; -module('Acceptance | users', function(hooks) { +module('Acceptance | users', function (hooks) { setupApplicationTest(hooks); setupMirage(hooks); + let orgScope; + let usersURL; + let userURL; + let newUserURL; + hooks.beforeEach(function () { - this.server.create('org'); + orgScope = this.server.create( + 'scope', + { + type: 'org', + }, + 'withChildren' + ); + + const user = this.server.create('user', { + scope: { + id: orgScope.id, + type: orgScope.type, + }, + }); + + usersURL = `/scopes/${orgScope.id}/users`; + userURL = `${usersURL}/${user.id}`; + newUserURL = `${usersURL}/new`; + + authenticateSession({}); }); test('visiting users', async function (assert) { assert.expect(1); - this.server.createList('user', 1); - await visit('/orgs/1/users'); + await visit(usersURL); await a11yAudit(); - assert.equal(currentURL(), '/orgs/1/users'); + assert.equal(currentURL(), usersURL); + }); + + test('visiting a user', async function (assert) { + assert.expect(1); + await visit(newUserURL); + await a11yAudit(); + assert.equal(currentURL(), newUserURL); }); test('can create new users', async function (assert) { - assert.expect(4); - assert.equal(this.server.db.users.length, 0); - await visit('/orgs/1/users/new'); + assert.expect(1); + const usersCount = this.server.db.users.length; + await visit(newUserURL); await fillIn('[name="name"]', 'User name'); await click('[type="submit"]'); - assert.equal(currentURL(), '/orgs/1/users/1'); - assert.equal(this.server.db.users.length, 1); - assert.equal(this.server.db.users[0].name, 'User name'); + assert.equal(this.server.db.users.length, usersCount + 1); }); test('can cancel creation of a new user', async function (assert) { - assert.expect(3); - assert.equal(this.server.db.users.length, 0); - await visit('/orgs/1/users/new'); + assert.expect(2); + const usersCount = this.server.db.users.length; + await visit(newUserURL); await fillIn('[name="name"]', 'User name'); await click('.rose-form-actions [type="button"]'); - assert.equal(currentURL(), '/orgs/1/users'); - assert.equal(this.server.db.users.length, 0); + assert.equal(currentURL(), usersURL); + assert.equal(this.server.db.users.length, usersCount); }); test('saving a new user with invalid fields displays error messages', async function (assert) { assert.expect(2); - this.server.post('/orgs/:org_id/users', () => { + this.server.post('scopes/:scope_id/users', () => { return new Response( 400, {}, @@ -63,7 +97,7 @@ module('Acceptance | users', function(hooks) { } ); }); - await visit('/orgs/1/users/new'); + await visit(newUserURL); await fillIn('[name="name"]', 'User name'); await click('[type="submit"]'); assert.ok( @@ -80,36 +114,32 @@ module('Acceptance | users', function(hooks) { test('can save changes to an existing user', async function (assert) { assert.expect(2); - this.server.createList('user', 1, {name: 'Test User'}); - await visit('/orgs/1/users/1'); + await visit(userURL); await fillIn('[name="name"]', 'Updated user name'); await click('.rose-form-actions [type="submit"]'); - assert.equal(currentURL(), '/orgs/1/users/1'); + assert.equal(currentURL(), userURL); assert.equal(this.server.db.users[0].name, 'Updated user name'); }); test('can cancel changes to an existing user', async function (assert) { assert.expect(1); - this.server.createList('user', 1, {name: 'Test User'}); - await visit('/orgs/1/users/1'); + await visit(userURL); await fillIn('[name="name"]', 'Unsaved user name'); await click('.rose-form-actions [type="button"]'); - assert.equal(find('[name="name"]').value, 'Test User'); + assert.notEqual(find('[name="name"]').value, 'Unsaved user name'); }); - test('can delete an user', async function(assert) { - assert.expect(2); - this.server.createList('user', 1); - assert.equal(this.server.db.users.length, 1); - await visit('/orgs/1/users/1'); + test('can delete an user', async function (assert) { + assert.expect(1); + const usersCount = this.server.db.users.length; + await visit(userURL); await click('.rose-button-warning'); - assert.equal(this.server.db.users.length, 0); + assert.equal(this.server.db.users.length, usersCount - 1); }); test('saving an existing user with invalid fields displays error messages', async function (assert) { assert.expect(2); - this.server.createList('user', 1); - this.server.patch('/orgs/:org_id/users/:id', () => { + this.server.patch('/scopes/:scope_id/users/:id', () => { return new Response( 400, {}, @@ -128,7 +158,7 @@ module('Acceptance | users', function(hooks) { } ); }); - await visit('/orgs/1/users/1'); + await visit(userURL); await fillIn('[name="name"]', 'User name'); await click('[type="submit"]'); assert.ok( @@ -145,8 +175,7 @@ module('Acceptance | users', function(hooks) { test('errors are displayed when saving user fails', async function (assert) { assert.expect(1); - this.server.createList('user', 1); - this.server.patch('/orgs/:org_id/users/:id', () => { + this.server.patch('/scopes/:scope_id/users/:id', () => { return new Response( 490, {}, @@ -157,7 +186,7 @@ module('Acceptance | users', function(hooks) { } ); }); - await visit('/orgs/1/users/1'); + await visit(userURL); await fillIn('[name="name"]', 'User name'); await click('[type="submit"]'); assert.ok( @@ -169,8 +198,7 @@ module('Acceptance | users', function(hooks) { test('errors are displayed when user deletion fails', async function (assert) { assert.expect(1); - this.server.createList('user', 1); - this.server.del('/orgs/:org_id/users/:id', () => { + this.server.del('/scopes/:scope_id/users/:id', () => { return new Response( 490, {}, @@ -181,7 +209,7 @@ module('Acceptance | users', function(hooks) { } ); }); - await visit('/orgs/1/users/1'); + await visit(userURL); await click('.rose-button-warning'); assert.ok( find('[role="alert"]').textContent.trim(), diff --git a/ui/core/tests/helpers/.gitkeep b/ui/core/tests/helpers/.gitkeep deleted file mode 100644 index e69de29bb..000000000 diff --git a/ui/core/tests/integration/components/bread-crumbs-test.js b/ui/core/tests/integration/components/bread-crumbs-test.js index a6f3579c8..601741311 100644 --- a/ui/core/tests/integration/components/bread-crumbs-test.js +++ b/ui/core/tests/integration/components/bread-crumbs-test.js @@ -3,11 +3,11 @@ import { setupRenderingTest } from 'ember-qunit'; import { render, find, findAll } from '@ember/test-helpers'; import { hbs } from 'ember-cli-htmlbars'; -module('Integration | Component | bread-crumbs', function(hooks) { +module('Integration | Component | bread-crumbs', function (hooks) { setupRenderingTest(hooks); - test('it renders', async function(assert) { - this.set('breadCrumbs', [{label: 'Level 1'}]); + test('it renders', async function (assert) { + this.set('breadCrumbs', [{ label: 'Level 1' }]); await render(hbs``); // Default index route @@ -15,28 +15,34 @@ module('Integration | Component | bread-crumbs', function(hooks) { assert.equal(find('.rose-nav-breadcrumbs').textContent.trim(), 'Level 1'); }); - test('it renders with model', async function(assert) { + test('it renders with model', async function (assert) { let store = this.owner.lookup('service:store'); - let project = store.createRecord('project', {id: '1234'}); + let scope = store.createRecord('scope'); - this.set('breadCrumbs', [{ - label: 'Level 2', - path: 'orgs.org.projects.project', - model: project - }]); + this.set('breadCrumbs', [ + { + label: 'Level 1', + path: 'scopes.scope', + model: scope, + }, + ]); await render(hbs``); - assert.equal(find('.rose-nav-breadcrumbs').textContent.trim(), 'Level 2'); + assert.equal(find('.rose-nav-breadcrumbs').textContent.trim(), 'Level 1'); }); - test('it renders multiple breadCrumbs', async function(assert) { - this.set('breadCrumbs', [{ - label: 'Level 1' - },{ - label: 'Level 2' - }, { - label: 'Level 3' - }]); + test('it renders multiple breadCrumbs', async function (assert) { + this.set('breadCrumbs', [ + { + label: 'Level 1', + }, + { + label: 'Level 2', + }, + { + label: 'Level 3', + }, + ]); await render(hbs``); assert.equal(findAll('a').length, 3); diff --git a/ui/core/tests/integration/components/form/host-catalog-test.js b/ui/core/tests/integration/components/form/host-catalog-test.js index 0e3b51865..20dbd545f 100644 --- a/ui/core/tests/integration/components/form/host-catalog-test.js +++ b/ui/core/tests/integration/components/form/host-catalog-test.js @@ -3,10 +3,10 @@ import { setupRenderingTest } from 'ember-qunit'; import { render, find } from '@ember/test-helpers'; import { hbs } from 'ember-cli-htmlbars'; -module('Integration | Component | form/host-catalog', function(hooks) { +module('Integration | Component | form/host-catalog', function (hooks) { setupRenderingTest(hooks); - test('it renders', async function(assert) { + test('it renders', async function (assert) { // Set any properties with this.set('myProperty', 'value'); // Handle any actions with this.set('myAction', function(val) { ... }); this.submit = () => {}; diff --git a/ui/core/tests/unit/controllers/orgs/org/users-test.js b/ui/core/tests/unit/controllers/application-test.js similarity index 51% rename from ui/core/tests/unit/controllers/orgs/org/users-test.js rename to ui/core/tests/unit/controllers/application-test.js index c5be2fb52..eb120e471 100644 --- a/ui/core/tests/unit/controllers/orgs/org/users-test.js +++ b/ui/core/tests/unit/controllers/application-test.js @@ -1,12 +1,12 @@ import { module, test } from 'qunit'; import { setupTest } from 'ember-qunit'; -module('Unit | Controller | orgs/org/users', function(hooks) { +module('Unit | Controller | application', function (hooks) { setupTest(hooks); // Replace this with your real tests. - test('it exists', function(assert) { - let controller = this.owner.lookup('controller:orgs/org/users'); + test('it exists', function (assert) { + let controller = this.owner.lookup('controller:application'); assert.ok(controller); }); }); diff --git a/ui/core/tests/unit/controllers/orgs/org/groups/group-test.js b/ui/core/tests/unit/controllers/orgs/org/groups/group-test.js deleted file mode 100644 index 49cf5b3a6..000000000 --- a/ui/core/tests/unit/controllers/orgs/org/groups/group-test.js +++ /dev/null @@ -1,12 +0,0 @@ -import { module, test } from 'qunit'; -import { setupTest } from 'ember-qunit'; - -module('Unit | Controller | orgs/org/groups/group', function(hooks) { - setupTest(hooks); - - // Replace this with your real tests. - test('it exists', function(assert) { - let controller = this.owner.lookup('controller:orgs/org/groups/group'); - assert.ok(controller); - }); -}); diff --git a/ui/core/tests/unit/controllers/orgs/org/projects-test.js b/ui/core/tests/unit/controllers/orgs/org/projects-test.js deleted file mode 100644 index 6176a173c..000000000 --- a/ui/core/tests/unit/controllers/orgs/org/projects-test.js +++ /dev/null @@ -1,12 +0,0 @@ -import { module, test } from 'qunit'; -import { setupTest } from 'ember-qunit'; - -module('Unit | Controller | orgs/org/projects', function(hooks) { - setupTest(hooks); - - // Replace this with your real tests. - test('it exists', function(assert) { - let controller = this.owner.lookup('controller:orgs/org/projects'); - assert.ok(controller); - }); -}); diff --git a/ui/core/tests/unit/controllers/orgs/org/projects/project-test.js b/ui/core/tests/unit/controllers/orgs/org/projects/project-test.js deleted file mode 100644 index 36a2c2482..000000000 --- a/ui/core/tests/unit/controllers/orgs/org/projects/project-test.js +++ /dev/null @@ -1,12 +0,0 @@ -import { module, test } from 'qunit'; -import { setupTest } from 'ember-qunit'; - -module('Unit | Controller | orgs/org/projects/project', function(hooks) { - setupTest(hooks); - - // Replace this with your real tests. - test('it exists', function(assert) { - let controller = this.owner.lookup('controller:orgs/org/projects/project'); - assert.ok(controller); - }); -}); diff --git a/ui/core/tests/unit/controllers/orgs/org/projects/project/host-catalogs/new-test.js b/ui/core/tests/unit/controllers/orgs/org/projects/project/host-catalogs/new-test.js deleted file mode 100644 index 1981e1857..000000000 --- a/ui/core/tests/unit/controllers/orgs/org/projects/project/host-catalogs/new-test.js +++ /dev/null @@ -1,12 +0,0 @@ -import { module, test } from 'qunit'; -import { setupTest } from 'ember-qunit'; - -module('Unit | Controller | orgs/org/projects/project/host-catalogs/new', function(hooks) { - setupTest(hooks); - - // Replace this with your real tests. - test('it exists', function(assert) { - let controller = this.owner.lookup('controller:orgs/org/projects/project/host-catalogs/new'); - assert.ok(controller); - }); -}); diff --git a/ui/core/tests/unit/controllers/orgs/org/roles-test.js b/ui/core/tests/unit/controllers/orgs/org/roles-test.js deleted file mode 100644 index 816459cd3..000000000 --- a/ui/core/tests/unit/controllers/orgs/org/roles-test.js +++ /dev/null @@ -1,11 +0,0 @@ -import { module, test } from 'qunit'; -import { setupTest } from 'ember-qunit'; - -module('Unit | Controller | orgs/org/roles', function(hooks) { - setupTest(hooks); - - test('it exists', function(assert) { - let controller = this.owner.lookup('controller:orgs/org/roles'); - assert.ok(controller); - }); -}); diff --git a/ui/core/tests/unit/controllers/orgs/org/roles/new-test.js b/ui/core/tests/unit/controllers/orgs/org/roles/new-test.js deleted file mode 100644 index 8d948ae06..000000000 --- a/ui/core/tests/unit/controllers/orgs/org/roles/new-test.js +++ /dev/null @@ -1,11 +0,0 @@ -import { module, test } from 'qunit'; -import { setupTest } from 'ember-qunit'; - -module('Unit | Controller | orgs/org/roles/new', function(hooks) { - setupTest(hooks); - - test('it exists', function(assert) { - let controller = this.owner.lookup('controller:orgs/org/roles/new'); - assert.ok(controller); - }); -}); diff --git a/ui/core/tests/unit/controllers/orgs/org/roles/role-test.js b/ui/core/tests/unit/controllers/orgs/org/roles/role-test.js deleted file mode 100644 index cd56c941f..000000000 --- a/ui/core/tests/unit/controllers/orgs/org/roles/role-test.js +++ /dev/null @@ -1,11 +0,0 @@ -import { module, test } from 'qunit'; -import { setupTest } from 'ember-qunit'; - -module('Unit | Controller | orgs/org/roles/role', function(hooks) { - setupTest(hooks); - - test('it exists', function(assert) { - let controller = this.owner.lookup('controller:orgs/org/roles/role'); - assert.ok(controller); - }); -}); diff --git a/ui/core/tests/unit/controllers/orgs/org/users/new-test.js b/ui/core/tests/unit/controllers/orgs/org/users/new-test.js deleted file mode 100644 index e6925b4d7..000000000 --- a/ui/core/tests/unit/controllers/orgs/org/users/new-test.js +++ /dev/null @@ -1,12 +0,0 @@ -import { module, test } from 'qunit'; -import { setupTest } from 'ember-qunit'; - -module('Unit | Controller | orgs/org/users/new', function(hooks) { - setupTest(hooks); - - // Replace this with your real tests. - test('it exists', function(assert) { - let controller = this.owner.lookup('controller:orgs/org/users/new'); - assert.ok(controller); - }); -}); diff --git a/ui/core/tests/unit/controllers/orgs/org/groups-test.js b/ui/core/tests/unit/controllers/scopes/scope-test.js similarity index 50% rename from ui/core/tests/unit/controllers/orgs/org/groups-test.js rename to ui/core/tests/unit/controllers/scopes/scope-test.js index 0ca58eed0..1ac27e62b 100644 --- a/ui/core/tests/unit/controllers/orgs/org/groups-test.js +++ b/ui/core/tests/unit/controllers/scopes/scope-test.js @@ -1,12 +1,12 @@ import { module, test } from 'qunit'; import { setupTest } from 'ember-qunit'; -module('Unit | Controller | orgs/org/groups', function(hooks) { +module('Unit | Controller | scopes/scope', function (hooks) { setupTest(hooks); // Replace this with your real tests. - test('it exists', function(assert) { - let controller = this.owner.lookup('controller:orgs/org/groups'); + test('it exists', function (assert) { + let controller = this.owner.lookup('controller:scopes/scope'); assert.ok(controller); }); }); diff --git a/ui/core/tests/unit/controllers/scopes/scope/groups-test.js b/ui/core/tests/unit/controllers/scopes/scope/groups-test.js new file mode 100644 index 000000000..4f1a10f19 --- /dev/null +++ b/ui/core/tests/unit/controllers/scopes/scope/groups-test.js @@ -0,0 +1,12 @@ +import { module, test } from 'qunit'; +import { setupTest } from 'ember-qunit'; + +module('Unit | Controller | scopes/scope/groups', function (hooks) { + setupTest(hooks); + + // Replace this with your real tests. + test('it exists', function (assert) { + let controller = this.owner.lookup('controller:scopes/scope/groups'); + assert.ok(controller); + }); +}); diff --git a/ui/core/tests/unit/controllers/scopes/scope/groups/group-test.js b/ui/core/tests/unit/controllers/scopes/scope/groups/group-test.js new file mode 100644 index 000000000..bb3e8b7cc --- /dev/null +++ b/ui/core/tests/unit/controllers/scopes/scope/groups/group-test.js @@ -0,0 +1,12 @@ +import { module, test } from 'qunit'; +import { setupTest } from 'ember-qunit'; + +module('Unit | Controller | scopes/scope/groups/group', function (hooks) { + setupTest(hooks); + + // Replace this with your real tests. + test('it exists', function (assert) { + let controller = this.owner.lookup('controller:scopes/scope/groups/group'); + assert.ok(controller); + }); +}); diff --git a/ui/core/tests/unit/controllers/scopes/scope/groups/new-test.js b/ui/core/tests/unit/controllers/scopes/scope/groups/new-test.js new file mode 100644 index 000000000..56eaa6c76 --- /dev/null +++ b/ui/core/tests/unit/controllers/scopes/scope/groups/new-test.js @@ -0,0 +1,12 @@ +import { module, test } from 'qunit'; +import { setupTest } from 'ember-qunit'; + +module('Unit | Controller | scopes/scope/groups/new', function (hooks) { + setupTest(hooks); + + // Replace this with your real tests. + test('it exists', function (assert) { + let controller = this.owner.lookup('controller:scopes/scope/groups/new'); + assert.ok(controller); + }); +}); diff --git a/ui/core/tests/unit/controllers/scopes/scope/projects-test.js b/ui/core/tests/unit/controllers/scopes/scope/projects-test.js new file mode 100644 index 000000000..052687cd2 --- /dev/null +++ b/ui/core/tests/unit/controllers/scopes/scope/projects-test.js @@ -0,0 +1,12 @@ +import { module, test } from 'qunit'; +import { setupTest } from 'ember-qunit'; + +module('Unit | Controller | scopes/scope/projects', function (hooks) { + setupTest(hooks); + + // Replace this with your real tests. + test('it exists', function (assert) { + let controller = this.owner.lookup('controller:scopes/scope/projects'); + assert.ok(controller); + }); +}); diff --git a/ui/core/tests/unit/controllers/scopes/scope/projects/new-test.js b/ui/core/tests/unit/controllers/scopes/scope/projects/new-test.js new file mode 100644 index 000000000..8da2a6f6d --- /dev/null +++ b/ui/core/tests/unit/controllers/scopes/scope/projects/new-test.js @@ -0,0 +1,12 @@ +import { module, test } from 'qunit'; +import { setupTest } from 'ember-qunit'; + +module('Unit | Controller | scopes/scope/projects/new', function (hooks) { + setupTest(hooks); + + // Replace this with your real tests. + test('it exists', function (assert) { + let controller = this.owner.lookup('controller:scopes/scope/projects/new'); + assert.ok(controller); + }); +}); diff --git a/ui/core/tests/unit/controllers/scopes/scope/projects/project-test.js b/ui/core/tests/unit/controllers/scopes/scope/projects/project-test.js new file mode 100644 index 000000000..ee20af93d --- /dev/null +++ b/ui/core/tests/unit/controllers/scopes/scope/projects/project-test.js @@ -0,0 +1,14 @@ +import { module, test } from 'qunit'; +import { setupTest } from 'ember-qunit'; + +module('Unit | Controller | scopes/scope/projects/project', function (hooks) { + setupTest(hooks); + + // Replace this with your real tests. + test('it exists', function (assert) { + let controller = this.owner.lookup( + 'controller:scopes/scope/projects/project' + ); + assert.ok(controller); + }); +}); diff --git a/ui/core/tests/unit/controllers/orgs/org/projects/project/host-catalogs-test.js b/ui/core/tests/unit/controllers/scopes/scope/projects/project/host-catalogs-test.js similarity index 53% rename from ui/core/tests/unit/controllers/orgs/org/projects/project/host-catalogs-test.js rename to ui/core/tests/unit/controllers/scopes/scope/projects/project/host-catalogs-test.js index 808f14138..ce6e91cd3 100644 --- a/ui/core/tests/unit/controllers/orgs/org/projects/project/host-catalogs-test.js +++ b/ui/core/tests/unit/controllers/scopes/scope/projects/project/host-catalogs-test.js @@ -1,12 +1,12 @@ import { module, test } from 'qunit'; import { setupTest } from 'ember-qunit'; -module('Unit | Controller | orgs/org/projects/project/host-catalogs', function(hooks) { +module('Unit | Controller | scopes/scope/projects/project/host-catalogs', function(hooks) { setupTest(hooks); // Replace this with your real tests. test('it exists', function(assert) { - let controller = this.owner.lookup('controller:orgs/org/projects/project/host-catalogs'); + let controller = this.owner.lookup('controller:scopes/scope/projects/project/host-catalogs'); assert.ok(controller); }); }); diff --git a/ui/core/tests/unit/controllers/orgs/org/projects/project/host-catalogs/host-catalog-test.js b/ui/core/tests/unit/controllers/scopes/scope/projects/project/host-catalogs/host-catalog-test.js similarity index 50% rename from ui/core/tests/unit/controllers/orgs/org/projects/project/host-catalogs/host-catalog-test.js rename to ui/core/tests/unit/controllers/scopes/scope/projects/project/host-catalogs/host-catalog-test.js index 4efc4b85c..b93e2d1ce 100644 --- a/ui/core/tests/unit/controllers/orgs/org/projects/project/host-catalogs/host-catalog-test.js +++ b/ui/core/tests/unit/controllers/scopes/scope/projects/project/host-catalogs/host-catalog-test.js @@ -1,12 +1,12 @@ import { module, test } from 'qunit'; import { setupTest } from 'ember-qunit'; -module('Unit | Controller | orgs/org/projects/project/host-catalogs/host-catalog', function(hooks) { +module('Unit | Controller | scopes/scope/projects/project/host-catalogs/host-catalog', function(hooks) { setupTest(hooks); // Replace this with your real tests. test('it exists', function(assert) { - let controller = this.owner.lookup('controller:orgs/org/projects/project/host-catalogs/host-catalog'); + let controller = this.owner.lookup('controller:scopes/scope/projects/project/host-catalogs/host-catalog'); assert.ok(controller); }); }); diff --git a/ui/core/tests/unit/controllers/orgs/org/projects/new-test.js b/ui/core/tests/unit/controllers/scopes/scope/projects/project/host-catalogs/new-test.js similarity index 52% rename from ui/core/tests/unit/controllers/orgs/org/projects/new-test.js rename to ui/core/tests/unit/controllers/scopes/scope/projects/project/host-catalogs/new-test.js index 9e3731313..2f653e30b 100644 --- a/ui/core/tests/unit/controllers/orgs/org/projects/new-test.js +++ b/ui/core/tests/unit/controllers/scopes/scope/projects/project/host-catalogs/new-test.js @@ -1,12 +1,12 @@ import { module, test } from 'qunit'; import { setupTest } from 'ember-qunit'; -module('Unit | Controller | orgs/org/projects/new', function(hooks) { +module('Unit | Controller | scopes/scope/projects/project/host-catalogs/new', function(hooks) { setupTest(hooks); // Replace this with your real tests. test('it exists', function(assert) { - let controller = this.owner.lookup('controller:orgs/org/projects/new'); + let controller = this.owner.lookup('controller:scopes/scope/projects/project/host-catalogs/new'); assert.ok(controller); }); }); diff --git a/ui/core/tests/unit/controllers/orgs/org/groups/new-test.js b/ui/core/tests/unit/controllers/scopes/scope/roles-test.js similarity index 50% rename from ui/core/tests/unit/controllers/orgs/org/groups/new-test.js rename to ui/core/tests/unit/controllers/scopes/scope/roles-test.js index ef02542c9..1567c0d37 100644 --- a/ui/core/tests/unit/controllers/orgs/org/groups/new-test.js +++ b/ui/core/tests/unit/controllers/scopes/scope/roles-test.js @@ -1,12 +1,12 @@ import { module, test } from 'qunit'; import { setupTest } from 'ember-qunit'; -module('Unit | Controller | orgs/org/groups/new', function(hooks) { +module('Unit | Controller | scopes/scope/roles', function (hooks) { setupTest(hooks); // Replace this with your real tests. - test('it exists', function(assert) { - let controller = this.owner.lookup('controller:orgs/org/groups/new'); + test('it exists', function (assert) { + let controller = this.owner.lookup('controller:scopes/scope/roles'); assert.ok(controller); }); }); diff --git a/ui/core/tests/unit/controllers/scopes/scope/roles/new-test.js b/ui/core/tests/unit/controllers/scopes/scope/roles/new-test.js new file mode 100644 index 000000000..ee2595d36 --- /dev/null +++ b/ui/core/tests/unit/controllers/scopes/scope/roles/new-test.js @@ -0,0 +1,12 @@ +import { module, test } from 'qunit'; +import { setupTest } from 'ember-qunit'; + +module('Unit | Controller | scopes/scope/roles/new', function (hooks) { + setupTest(hooks); + + // Replace this with your real tests. + test('it exists', function (assert) { + let controller = this.owner.lookup('controller:scopes/scope/roles/new'); + assert.ok(controller); + }); +}); diff --git a/ui/core/tests/unit/controllers/scopes/scope/roles/role-test.js b/ui/core/tests/unit/controllers/scopes/scope/roles/role-test.js new file mode 100644 index 000000000..4edaa508f --- /dev/null +++ b/ui/core/tests/unit/controllers/scopes/scope/roles/role-test.js @@ -0,0 +1,12 @@ +import { module, test } from 'qunit'; +import { setupTest } from 'ember-qunit'; + +module('Unit | Controller | scopes/scope/roles/role', function (hooks) { + setupTest(hooks); + + // Replace this with your real tests. + test('it exists', function (assert) { + let controller = this.owner.lookup('controller:scopes/scope/roles/role'); + assert.ok(controller); + }); +}); diff --git a/ui/core/tests/unit/controllers/orgs/org/users/user-test.js b/ui/core/tests/unit/controllers/scopes/scope/users-test.js similarity index 50% rename from ui/core/tests/unit/controllers/orgs/org/users/user-test.js rename to ui/core/tests/unit/controllers/scopes/scope/users-test.js index f35f620fa..25ced1669 100644 --- a/ui/core/tests/unit/controllers/orgs/org/users/user-test.js +++ b/ui/core/tests/unit/controllers/scopes/scope/users-test.js @@ -1,12 +1,12 @@ import { module, test } from 'qunit'; import { setupTest } from 'ember-qunit'; -module('Unit | Controller | orgs/org/users/user', function(hooks) { +module('Unit | Controller | scopes/scope/users', function (hooks) { setupTest(hooks); // Replace this with your real tests. - test('it exists', function(assert) { - let controller = this.owner.lookup('controller:orgs/org/users/user'); + test('it exists', function (assert) { + let controller = this.owner.lookup('controller:scopes/scope/users'); assert.ok(controller); }); }); diff --git a/ui/core/tests/unit/controllers/scopes/scope/users/new-test.js b/ui/core/tests/unit/controllers/scopes/scope/users/new-test.js new file mode 100644 index 000000000..440b4523c --- /dev/null +++ b/ui/core/tests/unit/controllers/scopes/scope/users/new-test.js @@ -0,0 +1,12 @@ +import { module, test } from 'qunit'; +import { setupTest } from 'ember-qunit'; + +module('Unit | Controller | scopes/scope/users/new', function (hooks) { + setupTest(hooks); + + // Replace this with your real tests. + test('it exists', function (assert) { + let controller = this.owner.lookup('controller:scopes/scope/users/new'); + assert.ok(controller); + }); +}); diff --git a/ui/core/tests/unit/controllers/scopes/scope/users/user-test.js b/ui/core/tests/unit/controllers/scopes/scope/users/user-test.js new file mode 100644 index 000000000..17d292de0 --- /dev/null +++ b/ui/core/tests/unit/controllers/scopes/scope/users/user-test.js @@ -0,0 +1,12 @@ +import { module, test } from 'qunit'; +import { setupTest } from 'ember-qunit'; + +module('Unit | Controller | scopes/scope/users/user', function (hooks) { + setupTest(hooks); + + // Replace this with your real tests. + test('it exists', function (assert) { + let controller = this.owner.lookup('controller:scopes/scope/users/user'); + assert.ok(controller); + }); +}); diff --git a/ui/core/tests/unit/routes/orgs-test.js b/ui/core/tests/unit/routes/index-test.js similarity index 63% rename from ui/core/tests/unit/routes/orgs-test.js rename to ui/core/tests/unit/routes/index-test.js index 72888b83a..01131fb9a 100644 --- a/ui/core/tests/unit/routes/orgs-test.js +++ b/ui/core/tests/unit/routes/index-test.js @@ -1,11 +1,11 @@ import { module, test } from 'qunit'; import { setupTest } from 'ember-qunit'; -module('Unit | Route | orgs', function (hooks) { +module('Unit | Route | index', function (hooks) { setupTest(hooks); test('it exists', function (assert) { - let route = this.owner.lookup('route:orgs'); + let route = this.owner.lookup('route:index'); assert.ok(route); }); }); diff --git a/ui/core/tests/unit/routes/orgs/org/groups-test.js b/ui/core/tests/unit/routes/orgs/org/groups-test.js deleted file mode 100644 index a56f69e76..000000000 --- a/ui/core/tests/unit/routes/orgs/org/groups-test.js +++ /dev/null @@ -1,11 +0,0 @@ -import { module, test } from 'qunit'; -import { setupTest } from 'ember-qunit'; - -module('Unit | Route | orgs/org/groups', function(hooks) { - setupTest(hooks); - - test('it exists', function(assert) { - let route = this.owner.lookup('route:orgs/org/groups'); - assert.ok(route); - }); -}); diff --git a/ui/core/tests/unit/routes/orgs/org/groups/group-test.js b/ui/core/tests/unit/routes/orgs/org/groups/group-test.js deleted file mode 100644 index cec8245a8..000000000 --- a/ui/core/tests/unit/routes/orgs/org/groups/group-test.js +++ /dev/null @@ -1,11 +0,0 @@ -import { module, test } from 'qunit'; -import { setupTest } from 'ember-qunit'; - -module('Unit | Route | orgs/org/groups/group', function(hooks) { - setupTest(hooks); - - test('it exists', function(assert) { - let route = this.owner.lookup('route:orgs/org/groups/group'); - assert.ok(route); - }); -}); diff --git a/ui/core/tests/unit/routes/orgs/org/groups/group/index-test.js b/ui/core/tests/unit/routes/orgs/org/groups/group/index-test.js deleted file mode 100644 index 2496b24bf..000000000 --- a/ui/core/tests/unit/routes/orgs/org/groups/group/index-test.js +++ /dev/null @@ -1,11 +0,0 @@ -import { module, test } from 'qunit'; -import { setupTest } from 'ember-qunit'; - -module('Unit | Route | orgs/org/groups/group/index', function(hooks) { - setupTest(hooks); - - test('it exists', function(assert) { - let route = this.owner.lookup('route:orgs/org/groups/group/index'); - assert.ok(route); - }); -}); diff --git a/ui/core/tests/unit/routes/orgs/org/groups/index-test.js b/ui/core/tests/unit/routes/orgs/org/groups/index-test.js deleted file mode 100644 index c2f9135eb..000000000 --- a/ui/core/tests/unit/routes/orgs/org/groups/index-test.js +++ /dev/null @@ -1,11 +0,0 @@ -import { module, test } from 'qunit'; -import { setupTest } from 'ember-qunit'; - -module('Unit | Route | orgs/org/groups/index', function(hooks) { - setupTest(hooks); - - test('it exists', function(assert) { - let route = this.owner.lookup('route:orgs/org/groups/index'); - assert.ok(route); - }); -}); diff --git a/ui/core/tests/unit/routes/orgs/org/groups/new-test.js b/ui/core/tests/unit/routes/orgs/org/groups/new-test.js deleted file mode 100644 index 09f10c831..000000000 --- a/ui/core/tests/unit/routes/orgs/org/groups/new-test.js +++ /dev/null @@ -1,11 +0,0 @@ -import { module, test } from 'qunit'; -import { setupTest } from 'ember-qunit'; - -module('Unit | Route | orgs/org/groups/new', function(hooks) { - setupTest(hooks); - - test('it exists', function(assert) { - let route = this.owner.lookup('route:orgs/org/groups/new'); - assert.ok(route); - }); -}); diff --git a/ui/core/tests/unit/routes/orgs/org/projects/project/host-catalogs-test.js b/ui/core/tests/unit/routes/orgs/org/projects/project/host-catalogs-test.js deleted file mode 100644 index f7c144429..000000000 --- a/ui/core/tests/unit/routes/orgs/org/projects/project/host-catalogs-test.js +++ /dev/null @@ -1,11 +0,0 @@ -import { module, test } from 'qunit'; -import { setupTest } from 'ember-qunit'; - -module('Unit | Route | orgs/org/projects/project/host-catalogs', function(hooks) { - setupTest(hooks); - - test('it exists', function(assert) { - let route = this.owner.lookup('route:orgs/org/projects/project/host-catalogs'); - assert.ok(route); - }); -}); diff --git a/ui/core/tests/unit/routes/orgs/org/projects/project/host-catalogs/host-catalog-test.js b/ui/core/tests/unit/routes/orgs/org/projects/project/host-catalogs/host-catalog-test.js deleted file mode 100644 index 6313bc8f0..000000000 --- a/ui/core/tests/unit/routes/orgs/org/projects/project/host-catalogs/host-catalog-test.js +++ /dev/null @@ -1,11 +0,0 @@ -import { module, test } from 'qunit'; -import { setupTest } from 'ember-qunit'; - -module('Unit | Route | orgs/org/projects/project/host-catalogs/host-catalog', function(hooks) { - setupTest(hooks); - - test('it exists', function(assert) { - let route = this.owner.lookup('route:orgs/org/projects/project/host-catalogs/host-catalog'); - assert.ok(route); - }); -}); diff --git a/ui/core/tests/unit/routes/orgs/org/projects/project/host-catalogs/host-catalog/index-test.js b/ui/core/tests/unit/routes/orgs/org/projects/project/host-catalogs/host-catalog/index-test.js deleted file mode 100644 index a19bd70e9..000000000 --- a/ui/core/tests/unit/routes/orgs/org/projects/project/host-catalogs/host-catalog/index-test.js +++ /dev/null @@ -1,11 +0,0 @@ -import { module, test } from 'qunit'; -import { setupTest } from 'ember-qunit'; - -module('Unit | Route | orgs/org/projects/project/host-catalogs/host-catalog/index', function(hooks) { - setupTest(hooks); - - test('it exists', function(assert) { - let route = this.owner.lookup('route:orgs/org/projects/project/host-catalogs/host-catalog/index'); - assert.ok(route); - }); -}); diff --git a/ui/core/tests/unit/routes/orgs/org/projects/project/host-catalogs/index-test.js b/ui/core/tests/unit/routes/orgs/org/projects/project/host-catalogs/index-test.js deleted file mode 100644 index d8a8d036e..000000000 --- a/ui/core/tests/unit/routes/orgs/org/projects/project/host-catalogs/index-test.js +++ /dev/null @@ -1,11 +0,0 @@ -import { module, test } from 'qunit'; -import { setupTest } from 'ember-qunit'; - -module('Unit | Route | orgs/org/projects/project/host-catalogs/index', function(hooks) { - setupTest(hooks); - - test('it exists', function(assert) { - let route = this.owner.lookup('route:orgs/org/projects/project/host-catalogs/index'); - assert.ok(route); - }); -}); diff --git a/ui/core/tests/unit/routes/orgs/org/projects/project/host-catalogs/new-test.js b/ui/core/tests/unit/routes/orgs/org/projects/project/host-catalogs/new-test.js deleted file mode 100644 index 2529656b7..000000000 --- a/ui/core/tests/unit/routes/orgs/org/projects/project/host-catalogs/new-test.js +++ /dev/null @@ -1,11 +0,0 @@ -import { module, test } from 'qunit'; -import { setupTest } from 'ember-qunit'; - -module('Unit | Route | orgs/org/projects/project/host-catalogs/new', function(hooks) { - setupTest(hooks); - - test('it exists', function(assert) { - let route = this.owner.lookup('route:orgs/org/projects/project/host-catalogs/new'); - assert.ok(route); - }); -}); diff --git a/ui/core/tests/unit/routes/orgs/org/projects/project/index-test.js b/ui/core/tests/unit/routes/orgs/org/projects/project/index-test.js deleted file mode 100644 index bc1d36103..000000000 --- a/ui/core/tests/unit/routes/orgs/org/projects/project/index-test.js +++ /dev/null @@ -1,11 +0,0 @@ -import { module, test } from 'qunit'; -import { setupTest } from 'ember-qunit'; - -module('Unit | Route | orgs/org/projects/project/index', function(hooks) { - setupTest(hooks); - - test('it exists', function(assert) { - let route = this.owner.lookup('route:orgs/org/projects/project/index'); - assert.ok(route); - }); -}); diff --git a/ui/core/tests/unit/routes/orgs/org/roles-test.js b/ui/core/tests/unit/routes/orgs/org/roles-test.js deleted file mode 100644 index 38bc3acb2..000000000 --- a/ui/core/tests/unit/routes/orgs/org/roles-test.js +++ /dev/null @@ -1,11 +0,0 @@ -import { module, test } from 'qunit'; -import { setupTest } from 'ember-qunit'; - -module('Unit | Route | orgs/org/roles', function(hooks) { - setupTest(hooks); - - test('it exists', function(assert) { - let route = this.owner.lookup('route:orgs/org/roles'); - assert.ok(route); - }); -}); diff --git a/ui/core/tests/unit/routes/orgs/org/roles/index-test.js b/ui/core/tests/unit/routes/orgs/org/roles/index-test.js deleted file mode 100644 index 12f247adb..000000000 --- a/ui/core/tests/unit/routes/orgs/org/roles/index-test.js +++ /dev/null @@ -1,11 +0,0 @@ -import { module, test } from 'qunit'; -import { setupTest } from 'ember-qunit'; - -module('Unit | Route | orgs/org/roles/index', function(hooks) { - setupTest(hooks); - - test('it exists', function(assert) { - let route = this.owner.lookup('route:orgs/org/roles/index'); - assert.ok(route); - }); -}); diff --git a/ui/core/tests/unit/routes/orgs/org/roles/new-test.js b/ui/core/tests/unit/routes/orgs/org/roles/new-test.js deleted file mode 100644 index d613eb34a..000000000 --- a/ui/core/tests/unit/routes/orgs/org/roles/new-test.js +++ /dev/null @@ -1,11 +0,0 @@ -import { module, test } from 'qunit'; -import { setupTest } from 'ember-qunit'; - -module('Unit | Route | orgs/org/roles/new', function(hooks) { - setupTest(hooks); - - test('it exists', function(assert) { - let route = this.owner.lookup('route:orgs/org/roles/new'); - assert.ok(route); - }); -}); diff --git a/ui/core/tests/unit/routes/orgs/org/roles/role-test.js b/ui/core/tests/unit/routes/orgs/org/roles/role-test.js deleted file mode 100644 index 149fd657e..000000000 --- a/ui/core/tests/unit/routes/orgs/org/roles/role-test.js +++ /dev/null @@ -1,11 +0,0 @@ -import { module, test } from 'qunit'; -import { setupTest } from 'ember-qunit'; - -module('Unit | Route | orgs/org/roles/role', function(hooks) { - setupTest(hooks); - - test('it exists', function(assert) { - let route = this.owner.lookup('route:orgs/org/roles/role'); - assert.ok(route); - }); -}); diff --git a/ui/core/tests/unit/routes/orgs/org/users-test.js b/ui/core/tests/unit/routes/orgs/org/users-test.js deleted file mode 100644 index 93956225b..000000000 --- a/ui/core/tests/unit/routes/orgs/org/users-test.js +++ /dev/null @@ -1,11 +0,0 @@ -import { module, test } from 'qunit'; -import { setupTest } from 'ember-qunit'; - -module('Unit | Route | orgs/org/users', function(hooks) { - setupTest(hooks); - - test('it exists', function(assert) { - let route = this.owner.lookup('route:orgs/org/users'); - assert.ok(route); - }); -}); diff --git a/ui/core/tests/unit/routes/orgs/org/users/index-test.js b/ui/core/tests/unit/routes/orgs/org/users/index-test.js deleted file mode 100644 index cd215662e..000000000 --- a/ui/core/tests/unit/routes/orgs/org/users/index-test.js +++ /dev/null @@ -1,11 +0,0 @@ -import { module, test } from 'qunit'; -import { setupTest } from 'ember-qunit'; - -module('Unit | Route | orgs/org/users/index', function(hooks) { - setupTest(hooks); - - test('it exists', function(assert) { - let route = this.owner.lookup('route:orgs/org/users/index'); - assert.ok(route); - }); -}); diff --git a/ui/core/tests/unit/routes/orgs/org/users/new-test.js b/ui/core/tests/unit/routes/orgs/org/users/new-test.js deleted file mode 100644 index 1aef05bab..000000000 --- a/ui/core/tests/unit/routes/orgs/org/users/new-test.js +++ /dev/null @@ -1,11 +0,0 @@ -import { module, test } from 'qunit'; -import { setupTest } from 'ember-qunit'; - -module('Unit | Route | orgs/org/users/new', function(hooks) { - setupTest(hooks); - - test('it exists', function(assert) { - let route = this.owner.lookup('route:orgs/org/users/new'); - assert.ok(route); - }); -}); diff --git a/ui/core/tests/unit/routes/orgs/org/users/user-test.js b/ui/core/tests/unit/routes/orgs/org/users/user-test.js deleted file mode 100644 index 4bd6f26a2..000000000 --- a/ui/core/tests/unit/routes/orgs/org/users/user-test.js +++ /dev/null @@ -1,11 +0,0 @@ -import { module, test } from 'qunit'; -import { setupTest } from 'ember-qunit'; - -module('Unit | Route | orgs/org/users/user', function(hooks) { - setupTest(hooks); - - test('it exists', function(assert) { - let route = this.owner.lookup('route:orgs/org/users/user'); - assert.ok(route); - }); -}); diff --git a/ui/core/tests/unit/routes/orgs/org-test.js b/ui/core/tests/unit/routes/scopes-test.js similarity index 62% rename from ui/core/tests/unit/routes/orgs/org-test.js rename to ui/core/tests/unit/routes/scopes-test.js index 8f1eb4fe2..914469429 100644 --- a/ui/core/tests/unit/routes/orgs/org-test.js +++ b/ui/core/tests/unit/routes/scopes-test.js @@ -1,11 +1,11 @@ import { module, test } from 'qunit'; import { setupTest } from 'ember-qunit'; -module('Unit | Route | orgs/org', function (hooks) { +module('Unit | Route | scopes', function (hooks) { setupTest(hooks); test('it exists', function (assert) { - let route = this.owner.lookup('route:orgs/org'); + let route = this.owner.lookup('route:scopes'); assert.ok(route); }); }); diff --git a/ui/core/tests/unit/routes/orgs/org/projects-test.js b/ui/core/tests/unit/routes/scopes/index-test.js similarity index 58% rename from ui/core/tests/unit/routes/orgs/org/projects-test.js rename to ui/core/tests/unit/routes/scopes/index-test.js index ce18ada89..22a5c073a 100644 --- a/ui/core/tests/unit/routes/orgs/org/projects-test.js +++ b/ui/core/tests/unit/routes/scopes/index-test.js @@ -1,11 +1,11 @@ import { module, test } from 'qunit'; import { setupTest } from 'ember-qunit'; -module('Unit | Route | orgs/org/projects', function (hooks) { +module('Unit | Route | scopes/index', function (hooks) { setupTest(hooks); test('it exists', function (assert) { - let route = this.owner.lookup('route:orgs/org/projects'); + let route = this.owner.lookup('route:scopes/index'); assert.ok(route); }); }); diff --git a/ui/core/tests/unit/routes/scopes/scope-test.js b/ui/core/tests/unit/routes/scopes/scope-test.js new file mode 100644 index 000000000..3cdcf409a --- /dev/null +++ b/ui/core/tests/unit/routes/scopes/scope-test.js @@ -0,0 +1,11 @@ +import { module, test } from 'qunit'; +import { setupTest } from 'ember-qunit'; + +module('Unit | Route | scopes/scope', function (hooks) { + setupTest(hooks); + + test('it exists', function (assert) { + let route = this.owner.lookup('route:scopes/scope'); + assert.ok(route); + }); +}); diff --git a/ui/core/tests/unit/routes/orgs/org/projects/project-test.js b/ui/core/tests/unit/routes/scopes/scope/authenticate-test.js similarity index 57% rename from ui/core/tests/unit/routes/orgs/org/projects/project-test.js rename to ui/core/tests/unit/routes/scopes/scope/authenticate-test.js index a1e01801f..5a1db715d 100644 --- a/ui/core/tests/unit/routes/orgs/org/projects/project-test.js +++ b/ui/core/tests/unit/routes/scopes/scope/authenticate-test.js @@ -1,11 +1,11 @@ import { module, test } from 'qunit'; import { setupTest } from 'ember-qunit'; -module('Unit | Route | orgs/org/projects/project', function (hooks) { +module('Unit | Route | scopes/scope/authenticate', function (hooks) { setupTest(hooks); test('it exists', function (assert) { - let route = this.owner.lookup('route:orgs/org/projects/project'); + let route = this.owner.lookup('route:scopes/scope/authenticate'); assert.ok(route); }); }); diff --git a/ui/core/tests/unit/routes/scopes/scope/authenticate/index-test.js b/ui/core/tests/unit/routes/scopes/scope/authenticate/index-test.js new file mode 100644 index 000000000..d9e86f2da --- /dev/null +++ b/ui/core/tests/unit/routes/scopes/scope/authenticate/index-test.js @@ -0,0 +1,11 @@ +import { module, test } from 'qunit'; +import { setupTest } from 'ember-qunit'; + +module('Unit | Route | scopes/scope/authenticate/index', function (hooks) { + setupTest(hooks); + + test('it exists', function (assert) { + let route = this.owner.lookup('route:scopes/scope/authenticate/index'); + assert.ok(route); + }); +}); diff --git a/ui/core/tests/unit/routes/scopes/scope/authenticate/method-test.js b/ui/core/tests/unit/routes/scopes/scope/authenticate/method-test.js new file mode 100644 index 000000000..f077f994b --- /dev/null +++ b/ui/core/tests/unit/routes/scopes/scope/authenticate/method-test.js @@ -0,0 +1,11 @@ +import { module, test } from 'qunit'; +import { setupTest } from 'ember-qunit'; + +module('Unit | Route | scopes/scope/authenticate/method', function (hooks) { + setupTest(hooks); + + test('it exists', function (assert) { + let route = this.owner.lookup('route:scopes/scope/authenticate/method'); + assert.ok(route); + }); +}); diff --git a/ui/core/tests/unit/routes/scopes/scope/groups-test.js b/ui/core/tests/unit/routes/scopes/scope/groups-test.js new file mode 100644 index 000000000..879a3925b --- /dev/null +++ b/ui/core/tests/unit/routes/scopes/scope/groups-test.js @@ -0,0 +1,11 @@ +import { module, test } from 'qunit'; +import { setupTest } from 'ember-qunit'; + +module('Unit | Route | scopes/scope/groups', function (hooks) { + setupTest(hooks); + + test('it exists', function (assert) { + let route = this.owner.lookup('route:scopes/scope/groups'); + assert.ok(route); + }); +}); diff --git a/ui/core/tests/unit/routes/scopes/scope/groups/group-test.js b/ui/core/tests/unit/routes/scopes/scope/groups/group-test.js new file mode 100644 index 000000000..8f8cc6713 --- /dev/null +++ b/ui/core/tests/unit/routes/scopes/scope/groups/group-test.js @@ -0,0 +1,11 @@ +import { module, test } from 'qunit'; +import { setupTest } from 'ember-qunit'; + +module('Unit | Route | scopes/scope/groups/group', function (hooks) { + setupTest(hooks); + + test('it exists', function (assert) { + let route = this.owner.lookup('route:scopes/scope/groups/group'); + assert.ok(route); + }); +}); diff --git a/ui/core/tests/unit/routes/scopes/scope/groups/group/index-test.js b/ui/core/tests/unit/routes/scopes/scope/groups/group/index-test.js new file mode 100644 index 000000000..3981831ad --- /dev/null +++ b/ui/core/tests/unit/routes/scopes/scope/groups/group/index-test.js @@ -0,0 +1,11 @@ +import { module, test } from 'qunit'; +import { setupTest } from 'ember-qunit'; + +module('Unit | Route | scopes/scope/groups/group/index', function (hooks) { + setupTest(hooks); + + test('it exists', function (assert) { + let route = this.owner.lookup('route:scopes/scope/groups/group/index'); + assert.ok(route); + }); +}); diff --git a/ui/core/tests/unit/routes/scopes/scope/groups/index-test.js b/ui/core/tests/unit/routes/scopes/scope/groups/index-test.js new file mode 100644 index 000000000..f50de2922 --- /dev/null +++ b/ui/core/tests/unit/routes/scopes/scope/groups/index-test.js @@ -0,0 +1,11 @@ +import { module, test } from 'qunit'; +import { setupTest } from 'ember-qunit'; + +module('Unit | Route | scopes/scope/groups/index', function (hooks) { + setupTest(hooks); + + test('it exists', function (assert) { + let route = this.owner.lookup('route:scopes/scope/groups/index'); + assert.ok(route); + }); +}); diff --git a/ui/core/tests/unit/routes/orgs/org/projects/index-test.js b/ui/core/tests/unit/routes/scopes/scope/groups/new-test.js similarity index 58% rename from ui/core/tests/unit/routes/orgs/org/projects/index-test.js rename to ui/core/tests/unit/routes/scopes/scope/groups/new-test.js index 2bc8af956..6e689bdd3 100644 --- a/ui/core/tests/unit/routes/orgs/org/projects/index-test.js +++ b/ui/core/tests/unit/routes/scopes/scope/groups/new-test.js @@ -1,11 +1,11 @@ import { module, test } from 'qunit'; import { setupTest } from 'ember-qunit'; -module('Unit | Route | orgs/org/projects/index', function (hooks) { +module('Unit | Route | scopes/scope/groups/new', function (hooks) { setupTest(hooks); test('it exists', function (assert) { - let route = this.owner.lookup('route:orgs/org/projects/index'); + let route = this.owner.lookup('route:scopes/scope/groups/new'); assert.ok(route); }); }); diff --git a/ui/core/tests/unit/routes/scopes/scope/index-test.js b/ui/core/tests/unit/routes/scopes/scope/index-test.js new file mode 100644 index 000000000..c623e801b --- /dev/null +++ b/ui/core/tests/unit/routes/scopes/scope/index-test.js @@ -0,0 +1,11 @@ +import { module, test } from 'qunit'; +import { setupTest } from 'ember-qunit'; + +module('Unit | Route | scopes/scope/index', function (hooks) { + setupTest(hooks); + + test('it exists', function (assert) { + let route = this.owner.lookup('route:scopes/scope/index'); + assert.ok(route); + }); +}); diff --git a/ui/core/tests/unit/routes/orgs/org/projects/new-test.js b/ui/core/tests/unit/routes/scopes/scope/projects-test.js similarity index 58% rename from ui/core/tests/unit/routes/orgs/org/projects/new-test.js rename to ui/core/tests/unit/routes/scopes/scope/projects-test.js index f084da7bf..127213dac 100644 --- a/ui/core/tests/unit/routes/orgs/org/projects/new-test.js +++ b/ui/core/tests/unit/routes/scopes/scope/projects-test.js @@ -1,11 +1,11 @@ import { module, test } from 'qunit'; import { setupTest } from 'ember-qunit'; -module('Unit | Route | orgs/org/projects/new', function (hooks) { +module('Unit | Route | scopes/scope/projects', function (hooks) { setupTest(hooks); test('it exists', function (assert) { - let route = this.owner.lookup('route:orgs/org/projects/new'); + let route = this.owner.lookup('route:scopes/scope/projects'); assert.ok(route); }); }); diff --git a/ui/core/tests/unit/routes/scopes/scope/projects/index-test.js b/ui/core/tests/unit/routes/scopes/scope/projects/index-test.js new file mode 100644 index 000000000..74a15f224 --- /dev/null +++ b/ui/core/tests/unit/routes/scopes/scope/projects/index-test.js @@ -0,0 +1,11 @@ +import { module, test } from 'qunit'; +import { setupTest } from 'ember-qunit'; + +module('Unit | Route | scopes/scope/projects/index', function (hooks) { + setupTest(hooks); + + test('it exists', function (assert) { + let route = this.owner.lookup('route:scopes/scope/projects/index'); + assert.ok(route); + }); +}); diff --git a/ui/core/tests/unit/routes/scopes/scope/projects/new-test.js b/ui/core/tests/unit/routes/scopes/scope/projects/new-test.js new file mode 100644 index 000000000..8c28e87b5 --- /dev/null +++ b/ui/core/tests/unit/routes/scopes/scope/projects/new-test.js @@ -0,0 +1,11 @@ +import { module, test } from 'qunit'; +import { setupTest } from 'ember-qunit'; + +module('Unit | Route | scopes/scope/projects/new', function (hooks) { + setupTest(hooks); + + test('it exists', function (assert) { + let route = this.owner.lookup('route:scopes/scope/projects/new'); + assert.ok(route); + }); +}); diff --git a/ui/core/tests/unit/routes/scopes/scope/projects/project-test.js b/ui/core/tests/unit/routes/scopes/scope/projects/project-test.js new file mode 100644 index 000000000..ed0ffe51a --- /dev/null +++ b/ui/core/tests/unit/routes/scopes/scope/projects/project-test.js @@ -0,0 +1,11 @@ +import { module, test } from 'qunit'; +import { setupTest } from 'ember-qunit'; + +module('Unit | Route | scopes/scope/projects/project', function (hooks) { + setupTest(hooks); + + test('it exists', function (assert) { + let route = this.owner.lookup('route:scopes/scope/projects/project'); + assert.ok(route); + }); +}); diff --git a/ui/core/tests/unit/routes/scopes/scope/projects/project/host-catalogs-test.js b/ui/core/tests/unit/routes/scopes/scope/projects/project/host-catalogs-test.js new file mode 100644 index 000000000..dde4328e4 --- /dev/null +++ b/ui/core/tests/unit/routes/scopes/scope/projects/project/host-catalogs-test.js @@ -0,0 +1,11 @@ +import { module, test } from 'qunit'; +import { setupTest } from 'ember-qunit'; + +module('Unit | Route | scopes/scope/projects/project/host-catalogs', function(hooks) { + setupTest(hooks); + + test('it exists', function(assert) { + let route = this.owner.lookup('route:scopes/scope/projects/project/host-catalogs'); + assert.ok(route); + }); +}); diff --git a/ui/core/tests/unit/routes/scopes/scope/projects/project/host-catalogs/host-catalog-test.js b/ui/core/tests/unit/routes/scopes/scope/projects/project/host-catalogs/host-catalog-test.js new file mode 100644 index 000000000..9cdc47735 --- /dev/null +++ b/ui/core/tests/unit/routes/scopes/scope/projects/project/host-catalogs/host-catalog-test.js @@ -0,0 +1,11 @@ +import { module, test } from 'qunit'; +import { setupTest } from 'ember-qunit'; + +module('Unit | Route | scopes/scope/projects/project/host-catalogs/host-catalog', function(hooks) { + setupTest(hooks); + + test('it exists', function(assert) { + let route = this.owner.lookup('route:scopes/scope/projects/project/host-catalogs/host-catalog'); + assert.ok(route); + }); +}); diff --git a/ui/core/tests/unit/routes/scopes/scope/projects/project/host-catalogs/host-catalog/index-test.js b/ui/core/tests/unit/routes/scopes/scope/projects/project/host-catalogs/host-catalog/index-test.js new file mode 100644 index 000000000..92f1aa677 --- /dev/null +++ b/ui/core/tests/unit/routes/scopes/scope/projects/project/host-catalogs/host-catalog/index-test.js @@ -0,0 +1,11 @@ +import { module, test } from 'qunit'; +import { setupTest } from 'ember-qunit'; + +module('Unit | Route | scopes/scope/projects/project/host-catalogs/host-catalog/index', function(hooks) { + setupTest(hooks); + + test('it exists', function(assert) { + let route = this.owner.lookup('route:scopes/scope/projects/project/host-catalogs/host-catalog/index'); + assert.ok(route); + }); +}); diff --git a/ui/core/tests/unit/routes/scopes/scope/projects/project/host-catalogs/index-test.js b/ui/core/tests/unit/routes/scopes/scope/projects/project/host-catalogs/index-test.js new file mode 100644 index 000000000..5cc1d842c --- /dev/null +++ b/ui/core/tests/unit/routes/scopes/scope/projects/project/host-catalogs/index-test.js @@ -0,0 +1,11 @@ +import { module, test } from 'qunit'; +import { setupTest } from 'ember-qunit'; + +module('Unit | Route | scopes/scope/projects/project/host-catalogs/index', function(hooks) { + setupTest(hooks); + + test('it exists', function(assert) { + let route = this.owner.lookup('route:scopes/scope/projects/project/host-catalogs/index'); + assert.ok(route); + }); +}); diff --git a/ui/core/tests/unit/routes/scopes/scope/projects/project/host-catalogs/new-test.js b/ui/core/tests/unit/routes/scopes/scope/projects/project/host-catalogs/new-test.js new file mode 100644 index 000000000..96ccdff8b --- /dev/null +++ b/ui/core/tests/unit/routes/scopes/scope/projects/project/host-catalogs/new-test.js @@ -0,0 +1,11 @@ +import { module, test } from 'qunit'; +import { setupTest } from 'ember-qunit'; + +module('Unit | Route | scopes/scope/projects/project/host-catalogs/new', function(hooks) { + setupTest(hooks); + + test('it exists', function(assert) { + let route = this.owner.lookup('route:scopes/scope/projects/project/host-catalogs/new'); + assert.ok(route); + }); +}); diff --git a/ui/core/tests/unit/routes/scopes/scope/projects/project/index-test.js b/ui/core/tests/unit/routes/scopes/scope/projects/project/index-test.js new file mode 100644 index 000000000..f14370229 --- /dev/null +++ b/ui/core/tests/unit/routes/scopes/scope/projects/project/index-test.js @@ -0,0 +1,11 @@ +import { module, test } from 'qunit'; +import { setupTest } from 'ember-qunit'; + +module('Unit | Route | scopes/scope/projects/project/index', function (hooks) { + setupTest(hooks); + + test('it exists', function (assert) { + let route = this.owner.lookup('route:scopes/scope/projects/project/index'); + assert.ok(route); + }); +}); diff --git a/ui/core/tests/unit/routes/scopes/scope/roles-test.js b/ui/core/tests/unit/routes/scopes/scope/roles-test.js new file mode 100644 index 000000000..f2aa633ee --- /dev/null +++ b/ui/core/tests/unit/routes/scopes/scope/roles-test.js @@ -0,0 +1,11 @@ +import { module, test } from 'qunit'; +import { setupTest } from 'ember-qunit'; + +module('Unit | Route | scopes/scope/roles', function (hooks) { + setupTest(hooks); + + test('it exists', function (assert) { + let route = this.owner.lookup('route:scopes/scope/roles'); + assert.ok(route); + }); +}); diff --git a/ui/core/tests/unit/routes/scopes/scope/roles/index-test.js b/ui/core/tests/unit/routes/scopes/scope/roles/index-test.js new file mode 100644 index 000000000..aad8dd94c --- /dev/null +++ b/ui/core/tests/unit/routes/scopes/scope/roles/index-test.js @@ -0,0 +1,11 @@ +import { module, test } from 'qunit'; +import { setupTest } from 'ember-qunit'; + +module('Unit | Route | scopes/scope/roles/index', function (hooks) { + setupTest(hooks); + + test('it exists', function (assert) { + let route = this.owner.lookup('route:scopes/scope/roles/index'); + assert.ok(route); + }); +}); diff --git a/ui/core/tests/unit/routes/scopes/scope/roles/new-test.js b/ui/core/tests/unit/routes/scopes/scope/roles/new-test.js new file mode 100644 index 000000000..1ddf02ba6 --- /dev/null +++ b/ui/core/tests/unit/routes/scopes/scope/roles/new-test.js @@ -0,0 +1,11 @@ +import { module, test } from 'qunit'; +import { setupTest } from 'ember-qunit'; + +module('Unit | Route | scopes/scope/roles/new', function (hooks) { + setupTest(hooks); + + test('it exists', function (assert) { + let route = this.owner.lookup('route:scopes/scope/roles/new'); + assert.ok(route); + }); +}); diff --git a/ui/core/tests/unit/routes/scopes/scope/roles/role-test.js b/ui/core/tests/unit/routes/scopes/scope/roles/role-test.js new file mode 100644 index 000000000..41e292c3c --- /dev/null +++ b/ui/core/tests/unit/routes/scopes/scope/roles/role-test.js @@ -0,0 +1,11 @@ +import { module, test } from 'qunit'; +import { setupTest } from 'ember-qunit'; + +module('Unit | Route | scopes/scope/roles/role', function (hooks) { + setupTest(hooks); + + test('it exists', function (assert) { + let route = this.owner.lookup('route:scopes/scope/roles/role'); + assert.ok(route); + }); +}); diff --git a/ui/core/tests/unit/routes/scopes/scope/roles/role/index-test.js b/ui/core/tests/unit/routes/scopes/scope/roles/role/index-test.js new file mode 100644 index 000000000..1a4aecbf1 --- /dev/null +++ b/ui/core/tests/unit/routes/scopes/scope/roles/role/index-test.js @@ -0,0 +1,11 @@ +import { module, test } from 'qunit'; +import { setupTest } from 'ember-qunit'; + +module('Unit | Route | scopes/scope/roles/role/index', function (hooks) { + setupTest(hooks); + + test('it exists', function (assert) { + let route = this.owner.lookup('route:scopes/scope/roles/role/index'); + assert.ok(route); + }); +}); diff --git a/ui/core/tests/unit/routes/scopes/scope/users-test.js b/ui/core/tests/unit/routes/scopes/scope/users-test.js new file mode 100644 index 000000000..12f7813b1 --- /dev/null +++ b/ui/core/tests/unit/routes/scopes/scope/users-test.js @@ -0,0 +1,11 @@ +import { module, test } from 'qunit'; +import { setupTest } from 'ember-qunit'; + +module('Unit | Route | scopes/scope/users', function (hooks) { + setupTest(hooks); + + test('it exists', function (assert) { + let route = this.owner.lookup('route:scopes/scope/users'); + assert.ok(route); + }); +}); diff --git a/ui/core/tests/unit/routes/scopes/scope/users/index-test.js b/ui/core/tests/unit/routes/scopes/scope/users/index-test.js new file mode 100644 index 000000000..f2c453604 --- /dev/null +++ b/ui/core/tests/unit/routes/scopes/scope/users/index-test.js @@ -0,0 +1,11 @@ +import { module, test } from 'qunit'; +import { setupTest } from 'ember-qunit'; + +module('Unit | Route | scopes/scope/users/index', function (hooks) { + setupTest(hooks); + + test('it exists', function (assert) { + let route = this.owner.lookup('route:scopes/scope/users/index'); + assert.ok(route); + }); +}); diff --git a/ui/core/tests/unit/routes/scopes/scope/users/new-test.js b/ui/core/tests/unit/routes/scopes/scope/users/new-test.js new file mode 100644 index 000000000..147af3ba2 --- /dev/null +++ b/ui/core/tests/unit/routes/scopes/scope/users/new-test.js @@ -0,0 +1,11 @@ +import { module, test } from 'qunit'; +import { setupTest } from 'ember-qunit'; + +module('Unit | Route | scopes/scope/users/new', function (hooks) { + setupTest(hooks); + + test('it exists', function (assert) { + let route = this.owner.lookup('route:scopes/scope/users/new'); + assert.ok(route); + }); +}); diff --git a/ui/core/tests/unit/routes/scopes/scope/users/user-test.js b/ui/core/tests/unit/routes/scopes/scope/users/user-test.js new file mode 100644 index 000000000..69f34e847 --- /dev/null +++ b/ui/core/tests/unit/routes/scopes/scope/users/user-test.js @@ -0,0 +1,11 @@ +import { module, test } from 'qunit'; +import { setupTest } from 'ember-qunit'; + +module('Unit | Route | scopes/scope/users/user', function (hooks) { + setupTest(hooks); + + test('it exists', function (assert) { + let route = this.owner.lookup('route:scopes/scope/users/user'); + assert.ok(route); + }); +}); diff --git a/ui/core/tests/unit/routes/scopes/scope/users/user/index-test.js b/ui/core/tests/unit/routes/scopes/scope/users/user/index-test.js new file mode 100644 index 000000000..21302ee4b --- /dev/null +++ b/ui/core/tests/unit/routes/scopes/scope/users/user/index-test.js @@ -0,0 +1,11 @@ +import { module, test } from 'qunit'; +import { setupTest } from 'ember-qunit'; + +module('Unit | Route | scopes/scope/users/user/index', function (hooks) { + setupTest(hooks); + + test('it exists', function (assert) { + let route = this.owner.lookup('route:scopes/scope/users/user/index'); + assert.ok(route); + }); +}); diff --git a/ui/core/tests/unit/session-stores/application.js b/ui/core/tests/unit/session-stores/application.js new file mode 100644 index 000000000..dd7132709 --- /dev/null +++ b/ui/core/tests/unit/session-stores/application.js @@ -0,0 +1,53 @@ +import { module, test } from 'qunit'; +import { setupTest } from 'ember-qunit'; + +module('Unit | Session Store | application', function (hooks) { + setupTest(hooks); + + let store; + let applicationAdapter; + let sessionStore; + + hooks.beforeEach(function () { + store = this.owner.lookup('service:store'); + applicationAdapter = store.adapterFor('application'); + sessionStore = this.owner.lookup('session-store:application'); + sessionStore.clear(); + }); + + hooks.afterEach(function () { + sessionStore.clear(); + }); + + test('it does not add an authorization header to application adapter on persist if no token is present', async function (assert) { + assert.expect(1); + sessionStore.persist({ authenticated: { token: null } }); + assert.notOk(applicationAdapter.headers.Authorization); + }); + + test('it adds an authorization header to application adapter on persist', async function (assert) { + assert.expect(1); + sessionStore.persist({ authenticated: { token: 'token1234' } }); + assert.equal(applicationAdapter.headers.Authorization, 'Bearer token1234'); + }); + + test('it adds an authorization header to application adapter which already has headers on persist', async function (assert) { + assert.expect(2); + applicationAdapter.headers = { foo: 'bar' }; + sessionStore.persist({ authenticated: { token: 'token1234' } }); + assert.equal(applicationAdapter.headers.foo, 'bar'); + assert.equal(applicationAdapter.headers.Authorization, 'Bearer token1234'); + }); + + test('it adds an org_id to the session data if authenticating within a scope', async function (assert) { + assert.expect(2); + let sessionData = {}; + sessionStore.set('scope', {}); + sessionStore.persist(sessionData); + assert.notOk(sessionData.authenticated, 'There is no authenticated key'); + sessionData = { authenticated: {} }; + sessionStore.set('scope', { org: { id: 1 } }); + sessionStore.persist(sessionData); + assert.equal(sessionData.authenticated.org_id, 1); + }); +}); diff --git a/ui/core/translations/en-us.yaml b/ui/core/translations/en-us.yaml index bda14642a..e563eb166 100644 --- a/ui/core/translations/en-us.yaml +++ b/ui/core/translations/en-us.yaml @@ -4,9 +4,24 @@ actions: dismiss: Dismiss save: Save help: Need help? + authenticate: Authenticate + deauthenticate: Deauthenticate + choose-org: Choose Org + choose-different-org: Choose a Different Org +auth: + titles: + no-orgs: No Organizations Available + no-methods: No Authentication Method + descriptions: + no-orgs: No organizations were found. To authenticate, ask your administrator to create an organization and grant you access. + no-methods: No authentication methods were found for this organization. Switch organizations and try again. + choose-org: Choose an organization to authenticate. + authenticating-with-org: "You're authenticating with {name}." + method-types: + password: Password resources: user: User - users: Users + users: Users role: Role roles: Roles group: Group @@ -18,6 +33,8 @@ resources: titles: iam: IAM new: New + resources: Resources + details: Details general: General overview: Overview settings: Settings @@ -42,9 +59,12 @@ states: error: Error form: label: + id: ID name: Name description: Description type: Type + identification: Username + password: Password errors: titles: unauthenticated-error: You are not signed in @@ -52,6 +72,7 @@ errors: notfound-error: Resource not found server-error: Server error unknown-error: Something went wrong + authentication-failed: Authentication Failed descriptions: unauthenticated-error: You are not authenticated. Please authenticate and try again later. forbidden-error: You must be granted permissions to view this data. Ask your administrator if you think you should have access. @@ -59,16 +80,9 @@ errors: server-error: We ran into a problem and could not continue. You can ask your administrator or try again later. unknown-error: Please contact your administrator or try again later. notify: - save-success: Save succeeded. - user: - create-success: User created succesfully. - delete-success: Deleted user successfully. - role: - create-success: Role created succesfully. - delete-success: Deleted role successfully. - group: - create-success: Group created succesfully. - delete-success: Deleted group successfully. + create-success: Created succesfully. + save-success: Saved succesfully. + delete-success: Deleted successfully. dev: default-org: Default Org menu-item: Menu item diff --git a/yarn.lock b/yarn.lock index 5c4a10c5f..5304e5ace 100644 --- a/yarn.lock +++ b/yarn.lock @@ -16,6 +16,22 @@ dependencies: "@babel/highlight" "^7.8.3" +"@babel/code-frame@^7.10.4": + version "7.10.4" + resolved "https://registry.yarnpkg.com/@babel/code-frame/-/code-frame-7.10.4.tgz#168da1a36e90da68ae8d49c0f1b48c7c6249213a" + integrity sha512-vG6SvB6oYEhvgisZNFRmRCUkLz11c7rp+tbNTynGqc6mS1d5ATd/sGyV6W0KZZnXRKMTzZDRgQT3Ou9jhpAfUg== + dependencies: + "@babel/highlight" "^7.10.4" + +"@babel/compat-data@^7.10.4": + version "7.10.5" + resolved "https://registry.yarnpkg.com/@babel/compat-data/-/compat-data-7.10.5.tgz#d38425e67ea96b1480a3f50404d1bf85676301a6" + integrity sha512-mPVoWNzIpYJHbWje0if7Ck36bpbtTvIxOi9+6WSK9wjGEXearAqlwBoTQvVjsAY2VIwgcs8V940geY3okzRCEw== + dependencies: + browserslist "^4.12.0" + invariant "^2.2.4" + semver "^5.5.0" + "@babel/compat-data@^7.9.6": version "7.9.6" resolved "https://registry.yarnpkg.com/@babel/compat-data/-/compat-data-7.9.6.tgz#3f604c40e420131affe6f2c8052e9a275ae2049b" @@ -47,6 +63,37 @@ semver "^5.4.1" source-map "^0.5.0" +"@babel/core@^7.10.2": + version "7.10.5" + resolved "https://registry.yarnpkg.com/@babel/core/-/core-7.10.5.tgz#1f15e2cca8ad9a1d78a38ddba612f5e7cdbbd330" + integrity sha512-O34LQooYVDXPl7QWCdW9p4NR+QlzOr7xShPPJz8GsuCU3/8ua/wqTr7gmnxXv+WBESiGU/G5s16i6tUvHkNb+w== + dependencies: + "@babel/code-frame" "^7.10.4" + "@babel/generator" "^7.10.5" + "@babel/helper-module-transforms" "^7.10.5" + "@babel/helpers" "^7.10.4" + "@babel/parser" "^7.10.5" + "@babel/template" "^7.10.4" + "@babel/traverse" "^7.10.5" + "@babel/types" "^7.10.5" + convert-source-map "^1.7.0" + debug "^4.1.0" + gensync "^1.0.0-beta.1" + json5 "^2.1.2" + lodash "^4.17.19" + resolve "^1.3.2" + semver "^5.4.1" + source-map "^0.5.0" + +"@babel/generator@^7.10.5": + version "7.10.5" + resolved "https://registry.yarnpkg.com/@babel/generator/-/generator-7.10.5.tgz#1b903554bc8c583ee8d25f1e8969732e6b829a69" + integrity sha512-3vXxr3FEW7E7lJZiWQ3bM4+v/Vyr9C+hpolQ8BGFr9Y8Ri2tFLWTixmwKBafDujO1WVah4fhZBeU1bieKdghig== + dependencies: + "@babel/types" "^7.10.5" + jsesc "^2.5.1" + source-map "^0.5.0" + "@babel/generator@^7.4.0", "@babel/generator@^7.9.6": version "7.9.6" resolved "https://registry.yarnpkg.com/@babel/generator/-/generator-7.9.6.tgz#5408c82ac5de98cda0d77d8124e99fa1f2170a43" @@ -57,6 +104,13 @@ lodash "^4.17.13" source-map "^0.5.0" +"@babel/helper-annotate-as-pure@^7.10.4": + version "7.10.4" + resolved "https://registry.yarnpkg.com/@babel/helper-annotate-as-pure/-/helper-annotate-as-pure-7.10.4.tgz#5bf0d495a3f757ac3bda48b5bf3b3ba309c72ba3" + integrity sha512-XQlqKQP4vXFB7BN8fEEerrmYvHp3fK/rBkRFz9jaJbzK0B1DSfej9Kc7ZzE8Z/OnId1jpJdNAZ3BFQjWG68rcA== + dependencies: + "@babel/types" "^7.10.4" + "@babel/helper-annotate-as-pure@^7.8.3": version "7.8.3" resolved "https://registry.yarnpkg.com/@babel/helper-annotate-as-pure/-/helper-annotate-as-pure-7.8.3.tgz#60bc0bc657f63a0924ff9a4b4a0b24a13cf4deee" @@ -64,6 +118,14 @@ dependencies: "@babel/types" "^7.8.3" +"@babel/helper-builder-binary-assignment-operator-visitor@^7.10.4": + version "7.10.4" + resolved "https://registry.yarnpkg.com/@babel/helper-builder-binary-assignment-operator-visitor/-/helper-builder-binary-assignment-operator-visitor-7.10.4.tgz#bb0b75f31bf98cbf9ff143c1ae578b87274ae1a3" + integrity sha512-L0zGlFrGWZK4PbT8AszSfLTM5sDU1+Az/En9VrdT8/LmEiJt4zXt+Jve9DCAnQcbqDhCI+29y/L93mrDzddCcg== + dependencies: + "@babel/helper-explode-assignable-expression" "^7.10.4" + "@babel/types" "^7.10.4" + "@babel/helper-builder-binary-assignment-operator-visitor@^7.8.3": version "7.8.3" resolved "https://registry.yarnpkg.com/@babel/helper-builder-binary-assignment-operator-visitor/-/helper-builder-binary-assignment-operator-visitor-7.8.3.tgz#c84097a427a061ac56a1c30ebf54b7b22d241503" @@ -89,6 +151,17 @@ "@babel/helper-annotate-as-pure" "^7.8.3" "@babel/types" "^7.9.0" +"@babel/helper-compilation-targets@^7.10.2", "@babel/helper-compilation-targets@^7.10.4": + version "7.10.4" + resolved "https://registry.yarnpkg.com/@babel/helper-compilation-targets/-/helper-compilation-targets-7.10.4.tgz#804ae8e3f04376607cc791b9d47d540276332bd2" + integrity sha512-a3rYhlsGV0UHNDvrtOXBg8/OpfV0OKTkxKPzIplS1zpx7CygDcWWxckxZeDd3gzPzC4kUT0A4nVFDK0wGMh4MQ== + dependencies: + "@babel/compat-data" "^7.10.4" + browserslist "^4.12.0" + invariant "^2.2.4" + levenary "^1.1.1" + semver "^5.5.0" + "@babel/helper-compilation-targets@^7.8.7", "@babel/helper-compilation-targets@^7.9.6": version "7.9.6" resolved "https://registry.yarnpkg.com/@babel/helper-compilation-targets/-/helper-compilation-targets-7.9.6.tgz#1e05b7ccc9d38d2f8b40b458b380a04dcfadd38a" @@ -100,6 +173,18 @@ levenary "^1.1.1" semver "^5.5.0" +"@babel/helper-create-class-features-plugin@^7.10.4", "@babel/helper-create-class-features-plugin@^7.10.5": + version "7.10.5" + resolved "https://registry.yarnpkg.com/@babel/helper-create-class-features-plugin/-/helper-create-class-features-plugin-7.10.5.tgz#9f61446ba80e8240b0a5c85c6fdac8459d6f259d" + integrity sha512-0nkdeijB7VlZoLT3r/mY3bUkw3T8WG/hNw+FATs/6+pG2039IJWjTYL0VTISqsNHMUTEnwbVnc89WIJX9Qed0A== + dependencies: + "@babel/helper-function-name" "^7.10.4" + "@babel/helper-member-expression-to-functions" "^7.10.5" + "@babel/helper-optimise-call-expression" "^7.10.4" + "@babel/helper-plugin-utils" "^7.10.4" + "@babel/helper-replace-supers" "^7.10.4" + "@babel/helper-split-export-declaration" "^7.10.4" + "@babel/helper-create-class-features-plugin@^7.5.5", "@babel/helper-create-class-features-plugin@^7.8.3", "@babel/helper-create-class-features-plugin@^7.9.6": version "7.9.6" resolved "https://registry.yarnpkg.com/@babel/helper-create-class-features-plugin/-/helper-create-class-features-plugin-7.9.6.tgz#965c8b0a9f051801fd9d3b372ca0ccf200a90897" @@ -112,6 +197,15 @@ "@babel/helper-replace-supers" "^7.9.6" "@babel/helper-split-export-declaration" "^7.8.3" +"@babel/helper-create-regexp-features-plugin@^7.10.4": + version "7.10.4" + resolved "https://registry.yarnpkg.com/@babel/helper-create-regexp-features-plugin/-/helper-create-regexp-features-plugin-7.10.4.tgz#fdd60d88524659a0b6959c0579925e425714f3b8" + integrity sha512-2/hu58IEPKeoLF45DBwx3XFqsbCXmkdAay4spVr2x0jYgRxrSNp+ePwvSsy9g6YSaNDcKIQVPXk1Ov8S2edk2g== + dependencies: + "@babel/helper-annotate-as-pure" "^7.10.4" + "@babel/helper-regex" "^7.10.4" + regexpu-core "^4.7.0" + "@babel/helper-create-regexp-features-plugin@^7.8.3", "@babel/helper-create-regexp-features-plugin@^7.8.8": version "7.8.8" resolved "https://registry.yarnpkg.com/@babel/helper-create-regexp-features-plugin/-/helper-create-regexp-features-plugin-7.8.8.tgz#5d84180b588f560b7864efaeea89243e58312087" @@ -121,6 +215,15 @@ "@babel/helper-regex" "^7.8.3" regexpu-core "^4.7.0" +"@babel/helper-define-map@^7.10.4": + version "7.10.5" + resolved "https://registry.yarnpkg.com/@babel/helper-define-map/-/helper-define-map-7.10.5.tgz#b53c10db78a640800152692b13393147acb9bb30" + integrity sha512-fMw4kgFB720aQFXSVaXr79pjjcW5puTCM16+rECJ/plGS+zByelE8l9nCpV1GibxTnFVmUuYG9U8wYfQHdzOEQ== + dependencies: + "@babel/helper-function-name" "^7.10.4" + "@babel/types" "^7.10.5" + lodash "^4.17.19" + "@babel/helper-define-map@^7.8.3": version "7.8.3" resolved "https://registry.yarnpkg.com/@babel/helper-define-map/-/helper-define-map-7.8.3.tgz#a0655cad5451c3760b726eba875f1cd8faa02c15" @@ -130,6 +233,14 @@ "@babel/types" "^7.8.3" lodash "^4.17.13" +"@babel/helper-explode-assignable-expression@^7.10.4": + version "7.10.4" + resolved "https://registry.yarnpkg.com/@babel/helper-explode-assignable-expression/-/helper-explode-assignable-expression-7.10.4.tgz#40a1cd917bff1288f699a94a75b37a1a2dbd8c7c" + integrity sha512-4K71RyRQNPRrR85sr5QY4X3VwG4wtVoXZB9+L3r1Gp38DhELyHCtovqydRi7c1Ovb17eRGiQ/FD5s8JdU0Uy5A== + dependencies: + "@babel/traverse" "^7.10.4" + "@babel/types" "^7.10.4" + "@babel/helper-explode-assignable-expression@^7.8.3": version "7.8.3" resolved "https://registry.yarnpkg.com/@babel/helper-explode-assignable-expression/-/helper-explode-assignable-expression-7.8.3.tgz#a728dc5b4e89e30fc2dfc7d04fa28a930653f982" @@ -138,6 +249,15 @@ "@babel/traverse" "^7.8.3" "@babel/types" "^7.8.3" +"@babel/helper-function-name@^7.10.4": + version "7.10.4" + resolved "https://registry.yarnpkg.com/@babel/helper-function-name/-/helper-function-name-7.10.4.tgz#d2d3b20c59ad8c47112fa7d2a94bc09d5ef82f1a" + integrity sha512-YdaSyz1n8gY44EmN7x44zBn9zQ1Ry2Y+3GTA+3vH6Mizke1Vw0aWDM66FOYEPw8//qKkmqOckrGgTYa+6sceqQ== + dependencies: + "@babel/helper-get-function-arity" "^7.10.4" + "@babel/template" "^7.10.4" + "@babel/types" "^7.10.4" + "@babel/helper-function-name@^7.8.3", "@babel/helper-function-name@^7.9.5": version "7.9.5" resolved "https://registry.yarnpkg.com/@babel/helper-function-name/-/helper-function-name-7.9.5.tgz#2b53820d35275120e1874a82e5aabe1376920a5c" @@ -147,6 +267,13 @@ "@babel/template" "^7.8.3" "@babel/types" "^7.9.5" +"@babel/helper-get-function-arity@^7.10.4": + version "7.10.4" + resolved "https://registry.yarnpkg.com/@babel/helper-get-function-arity/-/helper-get-function-arity-7.10.4.tgz#98c1cbea0e2332f33f9a4661b8ce1505b2c19ba2" + integrity sha512-EkN3YDB+SRDgiIUnNgcmiD361ti+AVbL3f3Henf6dqqUyr5dMsorno0lJWJuLhDhkI5sYEpgj6y9kB8AOU1I2A== + dependencies: + "@babel/types" "^7.10.4" + "@babel/helper-get-function-arity@^7.8.3": version "7.8.3" resolved "https://registry.yarnpkg.com/@babel/helper-get-function-arity/-/helper-get-function-arity-7.8.3.tgz#b894b947bd004381ce63ea1db9f08547e920abd5" @@ -154,6 +281,13 @@ dependencies: "@babel/types" "^7.8.3" +"@babel/helper-hoist-variables@^7.10.4": + version "7.10.4" + resolved "https://registry.yarnpkg.com/@babel/helper-hoist-variables/-/helper-hoist-variables-7.10.4.tgz#d49b001d1d5a68ca5e6604dda01a6297f7c9381e" + integrity sha512-wljroF5PgCk2juF69kanHVs6vrLwIPNp6DLD+Lrl3hoQ3PpPPikaDRNFA+0t81NOoMt2DL6WW/mdU8k4k6ZzuA== + dependencies: + "@babel/types" "^7.10.4" + "@babel/helper-hoist-variables@^7.8.3": version "7.8.3" resolved "https://registry.yarnpkg.com/@babel/helper-hoist-variables/-/helper-hoist-variables-7.8.3.tgz#1dbe9b6b55d78c9b4183fc8cdc6e30ceb83b7134" @@ -161,6 +295,13 @@ dependencies: "@babel/types" "^7.8.3" +"@babel/helper-member-expression-to-functions@^7.10.4", "@babel/helper-member-expression-to-functions@^7.10.5": + version "7.10.5" + resolved "https://registry.yarnpkg.com/@babel/helper-member-expression-to-functions/-/helper-member-expression-to-functions-7.10.5.tgz#172f56e7a63e78112f3a04055f24365af702e7ee" + integrity sha512-HiqJpYD5+WopCXIAbQDG0zye5XYVvcO9w/DHp5GsaGkRUaamLj2bEtu6i8rnGGprAhHM3qidCMgp71HF4endhA== + dependencies: + "@babel/types" "^7.10.5" + "@babel/helper-member-expression-to-functions@^7.8.3": version "7.8.3" resolved "https://registry.yarnpkg.com/@babel/helper-member-expression-to-functions/-/helper-member-expression-to-functions-7.8.3.tgz#659b710498ea6c1d9907e0c73f206eee7dadc24c" @@ -175,6 +316,26 @@ dependencies: "@babel/types" "^7.8.3" +"@babel/helper-module-imports@^7.10.4": + version "7.10.4" + resolved "https://registry.yarnpkg.com/@babel/helper-module-imports/-/helper-module-imports-7.10.4.tgz#4c5c54be04bd31670a7382797d75b9fa2e5b5620" + integrity sha512-nEQJHqYavI217oD9+s5MUBzk6x1IlvoS9WTPfgG43CbMEeStE0v+r+TucWdx8KFGowPGvyOkDT9+7DHedIDnVw== + dependencies: + "@babel/types" "^7.10.4" + +"@babel/helper-module-transforms@^7.10.4", "@babel/helper-module-transforms@^7.10.5": + version "7.10.5" + resolved "https://registry.yarnpkg.com/@babel/helper-module-transforms/-/helper-module-transforms-7.10.5.tgz#120c271c0b3353673fcdfd8c053db3c544a260d6" + integrity sha512-4P+CWMJ6/j1W915ITJaUkadLObmCRRSC234uctJfn/vHrsLNxsR8dwlcXv9ZhJWzl77awf+mWXSZEKt5t0OnlA== + dependencies: + "@babel/helper-module-imports" "^7.10.4" + "@babel/helper-replace-supers" "^7.10.4" + "@babel/helper-simple-access" "^7.10.4" + "@babel/helper-split-export-declaration" "^7.10.4" + "@babel/template" "^7.10.4" + "@babel/types" "^7.10.5" + lodash "^4.17.19" + "@babel/helper-module-transforms@^7.9.0": version "7.9.0" resolved "https://registry.yarnpkg.com/@babel/helper-module-transforms/-/helper-module-transforms-7.9.0.tgz#43b34dfe15961918707d247327431388e9fe96e5" @@ -188,6 +349,13 @@ "@babel/types" "^7.9.0" lodash "^4.17.13" +"@babel/helper-optimise-call-expression@^7.10.4": + version "7.10.4" + resolved "https://registry.yarnpkg.com/@babel/helper-optimise-call-expression/-/helper-optimise-call-expression-7.10.4.tgz#50dc96413d594f995a77905905b05893cd779673" + integrity sha512-n3UGKY4VXwXThEiKrgRAoVPBMqeoPgHVqiHZOanAJCG9nQUL2pLRQirUzl0ioKclHGpGqRgIOkgcIJaIWLpygg== + dependencies: + "@babel/types" "^7.10.4" + "@babel/helper-optimise-call-expression@^7.8.3": version "7.8.3" resolved "https://registry.yarnpkg.com/@babel/helper-optimise-call-expression/-/helper-optimise-call-expression-7.8.3.tgz#7ed071813d09c75298ef4f208956006b6111ecb9" @@ -200,6 +368,18 @@ resolved "https://registry.yarnpkg.com/@babel/helper-plugin-utils/-/helper-plugin-utils-7.8.3.tgz#9ea293be19babc0f52ff8ca88b34c3611b208670" integrity sha512-j+fq49Xds2smCUNYmEHF9kGNkhbet6yVIBp4e6oeQpH1RUs/Ir06xUKzDjDkGcaaokPiTNs2JBWHjaE4csUkZQ== +"@babel/helper-plugin-utils@^7.10.4": + version "7.10.4" + resolved "https://registry.yarnpkg.com/@babel/helper-plugin-utils/-/helper-plugin-utils-7.10.4.tgz#2f75a831269d4f677de49986dff59927533cf375" + integrity sha512-O4KCvQA6lLiMU9l2eawBPMf1xPP8xPfB3iEQw150hOVTqj/rfXz0ThTb4HEzqQfs2Bmo5Ay8BzxfzVtBrr9dVg== + +"@babel/helper-regex@^7.10.4": + version "7.10.5" + resolved "https://registry.yarnpkg.com/@babel/helper-regex/-/helper-regex-7.10.5.tgz#32dfbb79899073c415557053a19bd055aae50ae0" + integrity sha512-68kdUAzDrljqBrio7DYAEgCoJHxppJOERHOgOrDN7WjOzP0ZQ1LsSDRXcemzVZaLvjaJsJEESb6qt+znNuENDg== + dependencies: + lodash "^4.17.19" + "@babel/helper-regex@^7.8.3": version "7.8.3" resolved "https://registry.yarnpkg.com/@babel/helper-regex/-/helper-regex-7.8.3.tgz#139772607d51b93f23effe72105b319d2a4c6965" @@ -207,6 +387,17 @@ dependencies: lodash "^4.17.13" +"@babel/helper-remap-async-to-generator@^7.10.4": + version "7.10.4" + resolved "https://registry.yarnpkg.com/@babel/helper-remap-async-to-generator/-/helper-remap-async-to-generator-7.10.4.tgz#fce8bea4e9690bbe923056ded21e54b4e8b68ed5" + integrity sha512-86Lsr6NNw3qTNl+TBcF1oRZMaVzJtbWTyTko+CQL/tvNvcGYEFKbLXDPxtW0HKk3McNOk4KzY55itGWCAGK5tg== + dependencies: + "@babel/helper-annotate-as-pure" "^7.10.4" + "@babel/helper-wrap-function" "^7.10.4" + "@babel/template" "^7.10.4" + "@babel/traverse" "^7.10.4" + "@babel/types" "^7.10.4" + "@babel/helper-remap-async-to-generator@^7.8.3": version "7.8.3" resolved "https://registry.yarnpkg.com/@babel/helper-remap-async-to-generator/-/helper-remap-async-to-generator-7.8.3.tgz#273c600d8b9bf5006142c1e35887d555c12edd86" @@ -218,6 +409,16 @@ "@babel/traverse" "^7.8.3" "@babel/types" "^7.8.3" +"@babel/helper-replace-supers@^7.10.4": + version "7.10.4" + resolved "https://registry.yarnpkg.com/@babel/helper-replace-supers/-/helper-replace-supers-7.10.4.tgz#d585cd9388ea06e6031e4cd44b6713cbead9e6cf" + integrity sha512-sPxZfFXocEymYTdVK1UNmFPBN+Hv5mJkLPsYWwGBxZAxaWfFu+xqp7b6qWD0yjNuNL2VKc6L5M18tOXUP7NU0A== + dependencies: + "@babel/helper-member-expression-to-functions" "^7.10.4" + "@babel/helper-optimise-call-expression" "^7.10.4" + "@babel/traverse" "^7.10.4" + "@babel/types" "^7.10.4" + "@babel/helper-replace-supers@^7.8.3", "@babel/helper-replace-supers@^7.8.6", "@babel/helper-replace-supers@^7.9.6": version "7.9.6" resolved "https://registry.yarnpkg.com/@babel/helper-replace-supers/-/helper-replace-supers-7.9.6.tgz#03149d7e6a5586ab6764996cd31d6981a17e1444" @@ -228,6 +429,14 @@ "@babel/traverse" "^7.9.6" "@babel/types" "^7.9.6" +"@babel/helper-simple-access@^7.10.4": + version "7.10.4" + resolved "https://registry.yarnpkg.com/@babel/helper-simple-access/-/helper-simple-access-7.10.4.tgz#0f5ccda2945277a2a7a2d3a821e15395edcf3461" + integrity sha512-0fMy72ej/VEvF8ULmX6yb5MtHG4uH4Dbd6I/aHDb/JVg0bbivwt9Wg+h3uMvX+QSFtwr5MeItvazbrc4jtRAXw== + dependencies: + "@babel/template" "^7.10.4" + "@babel/types" "^7.10.4" + "@babel/helper-simple-access@^7.8.3": version "7.8.3" resolved "https://registry.yarnpkg.com/@babel/helper-simple-access/-/helper-simple-access-7.8.3.tgz#7f8109928b4dab4654076986af575231deb639ae" @@ -236,6 +445,13 @@ "@babel/template" "^7.8.3" "@babel/types" "^7.8.3" +"@babel/helper-split-export-declaration@^7.10.4": + version "7.10.4" + resolved "https://registry.yarnpkg.com/@babel/helper-split-export-declaration/-/helper-split-export-declaration-7.10.4.tgz#2c70576eaa3b5609b24cb99db2888cc3fc4251d1" + integrity sha512-pySBTeoUff56fL5CBU2hWm9TesA4r/rOkI9DyJLvvgz09MB9YtfIYe3iBriVaYNaPe+Alua0vBIOVOLs2buWhg== + dependencies: + "@babel/types" "^7.10.4" + "@babel/helper-split-export-declaration@^7.8.3": version "7.8.3" resolved "https://registry.yarnpkg.com/@babel/helper-split-export-declaration/-/helper-split-export-declaration-7.8.3.tgz#31a9f30070f91368a7182cf05f831781065fc7a9" @@ -243,11 +459,26 @@ dependencies: "@babel/types" "^7.8.3" +"@babel/helper-validator-identifier@^7.10.4": + version "7.10.4" + resolved "https://registry.yarnpkg.com/@babel/helper-validator-identifier/-/helper-validator-identifier-7.10.4.tgz#a78c7a7251e01f616512d31b10adcf52ada5e0d2" + integrity sha512-3U9y+43hz7ZM+rzG24Qe2mufW5KhvFg/NhnNph+i9mgCtdTCtMJuI1TMkrIUiK7Ix4PYlRF9I5dhqaLYA/ADXw== + "@babel/helper-validator-identifier@^7.9.0", "@babel/helper-validator-identifier@^7.9.5": version "7.9.5" resolved "https://registry.yarnpkg.com/@babel/helper-validator-identifier/-/helper-validator-identifier-7.9.5.tgz#90977a8e6fbf6b431a7dc31752eee233bf052d80" integrity sha512-/8arLKUFq882w4tWGj9JYzRpAlZgiWUJ+dtteNTDqrRBz9Iguck9Rn3ykuBDoUwh2TO4tSAJlrxDUOXWklJe4g== +"@babel/helper-wrap-function@^7.10.4": + version "7.10.4" + resolved "https://registry.yarnpkg.com/@babel/helper-wrap-function/-/helper-wrap-function-7.10.4.tgz#8a6f701eab0ff39f765b5a1cfef409990e624b87" + integrity sha512-6py45WvEF0MhiLrdxtRjKjufwLL1/ob2qDJgg5JgNdojBAZSAKnAjkyOCNug6n+OBl4VW76XjvgSFTdaMcW0Ug== + dependencies: + "@babel/helper-function-name" "^7.10.4" + "@babel/template" "^7.10.4" + "@babel/traverse" "^7.10.4" + "@babel/types" "^7.10.4" + "@babel/helper-wrap-function@^7.8.3": version "7.8.3" resolved "https://registry.yarnpkg.com/@babel/helper-wrap-function/-/helper-wrap-function-7.8.3.tgz#9dbdb2bb55ef14aaa01fe8c99b629bd5352d8610" @@ -258,6 +489,15 @@ "@babel/traverse" "^7.8.3" "@babel/types" "^7.8.3" +"@babel/helpers@^7.10.4": + version "7.10.4" + resolved "https://registry.yarnpkg.com/@babel/helpers/-/helpers-7.10.4.tgz#2abeb0d721aff7c0a97376b9e1f6f65d7a475044" + integrity sha512-L2gX/XeUONeEbI78dXSrJzGdz4GQ+ZTA/aazfUsFaWjSe95kiCuOZ5HsXvkiw3iwF+mFHSRUfJU8t6YavocdXA== + dependencies: + "@babel/template" "^7.10.4" + "@babel/traverse" "^7.10.4" + "@babel/types" "^7.10.4" + "@babel/helpers@^7.9.6": version "7.9.6" resolved "https://registry.yarnpkg.com/@babel/helpers/-/helpers-7.9.6.tgz#092c774743471d0bb6c7de3ad465ab3d3486d580" @@ -276,11 +516,34 @@ chalk "^2.0.0" js-tokens "^4.0.0" +"@babel/highlight@^7.10.4": + version "7.10.4" + resolved "https://registry.yarnpkg.com/@babel/highlight/-/highlight-7.10.4.tgz#7d1bdfd65753538fabe6c38596cdb76d9ac60143" + integrity sha512-i6rgnR/YgPEQzZZnbTHHuZdlE8qyoBNalD6F+q4vAFlcMEcqmkoG+mPqJYJCo63qPf74+Y1UZsl3l6f7/RIkmA== + dependencies: + "@babel/helper-validator-identifier" "^7.10.4" + chalk "^2.0.0" + js-tokens "^4.0.0" + "@babel/parser@^7.1.6", "@babel/parser@^7.3.4", "@babel/parser@^7.4.2", "@babel/parser@^7.4.3", "@babel/parser@^7.4.5", "@babel/parser@^7.6.0", "@babel/parser@^7.7.0", "@babel/parser@^7.8.6", "@babel/parser@^7.9.6": version "7.9.6" resolved "https://registry.yarnpkg.com/@babel/parser/-/parser-7.9.6.tgz#3b1bbb30dabe600cd72db58720998376ff653bc7" integrity sha512-AoeIEJn8vt+d/6+PXDRPaksYhnlbMIiejioBZvvMQsOjW/JYK6k/0dKnvvP3EhK5GfMBWDPtrxRtegWdAcdq9Q== +"@babel/parser@^7.10.4", "@babel/parser@^7.10.5": + version "7.10.5" + resolved "https://registry.yarnpkg.com/@babel/parser/-/parser-7.10.5.tgz#e7c6bf5a7deff957cec9f04b551e2762909d826b" + integrity sha512-wfryxy4bE1UivvQKSQDU4/X6dr+i8bctjUjj8Zyt3DQy7NtPizJXT8M52nqpNKL+nq2PW8lxk4ZqLj0fD4B4hQ== + +"@babel/plugin-proposal-async-generator-functions@^7.10.4": + version "7.10.5" + resolved "https://registry.yarnpkg.com/@babel/plugin-proposal-async-generator-functions/-/plugin-proposal-async-generator-functions-7.10.5.tgz#3491cabf2f7c179ab820606cec27fed15e0e8558" + integrity sha512-cNMCVezQbrRGvXJwm9fu/1sJj9bHdGAgKodZdLqOQIpfoH3raqmRPBM17+lh7CzhiKRRBrGtZL9WcjxSoGYUSg== + dependencies: + "@babel/helper-plugin-utils" "^7.10.4" + "@babel/helper-remap-async-to-generator" "^7.10.4" + "@babel/plugin-syntax-async-generators" "^7.8.0" + "@babel/plugin-proposal-async-generator-functions@^7.8.3": version "7.8.3" resolved "https://registry.yarnpkg.com/@babel/plugin-proposal-async-generator-functions/-/plugin-proposal-async-generator-functions-7.8.3.tgz#bad329c670b382589721b27540c7d288601c6e6f" @@ -298,6 +561,23 @@ "@babel/helper-create-class-features-plugin" "^7.8.3" "@babel/helper-plugin-utils" "^7.8.3" +"@babel/plugin-proposal-class-properties@^7.10.1", "@babel/plugin-proposal-class-properties@^7.10.4": + version "7.10.4" + resolved "https://registry.yarnpkg.com/@babel/plugin-proposal-class-properties/-/plugin-proposal-class-properties-7.10.4.tgz#a33bf632da390a59c7a8c570045d1115cd778807" + integrity sha512-vhwkEROxzcHGNu2mzUC0OFFNXdZ4M23ib8aRRcJSsW8BZK9pQMD7QB7csl97NBbgGZO7ZyHUyKDnxzOaP4IrCg== + dependencies: + "@babel/helper-create-class-features-plugin" "^7.10.4" + "@babel/helper-plugin-utils" "^7.10.4" + +"@babel/plugin-proposal-decorators@^7.10.1": + version "7.10.5" + resolved "https://registry.yarnpkg.com/@babel/plugin-proposal-decorators/-/plugin-proposal-decorators-7.10.5.tgz#42898bba478bc4b1ae242a703a953a7ad350ffb4" + integrity sha512-Sc5TAQSZuLzgY0664mMDn24Vw2P8g/VhyLyGPaWiHahhgLqeZvcGeyBZOrJW0oSKIK2mvQ22a1ENXBIQLhrEiQ== + dependencies: + "@babel/helper-create-class-features-plugin" "^7.10.5" + "@babel/helper-plugin-utils" "^7.10.4" + "@babel/plugin-syntax-decorators" "^7.10.4" + "@babel/plugin-proposal-decorators@^7.8.3": version "7.8.3" resolved "https://registry.yarnpkg.com/@babel/plugin-proposal-decorators/-/plugin-proposal-decorators-7.8.3.tgz#2156860ab65c5abf068c3f67042184041066543e" @@ -307,6 +587,14 @@ "@babel/helper-plugin-utils" "^7.8.3" "@babel/plugin-syntax-decorators" "^7.8.3" +"@babel/plugin-proposal-dynamic-import@^7.10.4": + version "7.10.4" + resolved "https://registry.yarnpkg.com/@babel/plugin-proposal-dynamic-import/-/plugin-proposal-dynamic-import-7.10.4.tgz#ba57a26cb98b37741e9d5bca1b8b0ddf8291f17e" + integrity sha512-up6oID1LeidOOASNXgv/CFbgBqTuKJ0cJjz6An5tWD+NVBNlp3VNSBxv2ZdU7SYl3NxJC7agAQDApZusV6uFwQ== + dependencies: + "@babel/helper-plugin-utils" "^7.10.4" + "@babel/plugin-syntax-dynamic-import" "^7.8.0" + "@babel/plugin-proposal-dynamic-import@^7.8.3": version "7.8.3" resolved "https://registry.yarnpkg.com/@babel/plugin-proposal-dynamic-import/-/plugin-proposal-dynamic-import-7.8.3.tgz#38c4fe555744826e97e2ae930b0fb4cc07e66054" @@ -315,6 +603,14 @@ "@babel/helper-plugin-utils" "^7.8.3" "@babel/plugin-syntax-dynamic-import" "^7.8.0" +"@babel/plugin-proposal-json-strings@^7.10.4": + version "7.10.4" + resolved "https://registry.yarnpkg.com/@babel/plugin-proposal-json-strings/-/plugin-proposal-json-strings-7.10.4.tgz#593e59c63528160233bd321b1aebe0820c2341db" + integrity sha512-fCL7QF0Jo83uy1K0P2YXrfX11tj3lkpN7l4dMv9Y9VkowkhkQDwFHFd8IiwyK5MZjE8UpbgokkgtcReH88Abaw== + dependencies: + "@babel/helper-plugin-utils" "^7.10.4" + "@babel/plugin-syntax-json-strings" "^7.8.0" + "@babel/plugin-proposal-json-strings@^7.8.3": version "7.8.3" resolved "https://registry.yarnpkg.com/@babel/plugin-proposal-json-strings/-/plugin-proposal-json-strings-7.8.3.tgz#da5216b238a98b58a1e05d6852104b10f9a70d6b" @@ -323,6 +619,14 @@ "@babel/helper-plugin-utils" "^7.8.3" "@babel/plugin-syntax-json-strings" "^7.8.0" +"@babel/plugin-proposal-nullish-coalescing-operator@^7.10.4": + version "7.10.4" + resolved "https://registry.yarnpkg.com/@babel/plugin-proposal-nullish-coalescing-operator/-/plugin-proposal-nullish-coalescing-operator-7.10.4.tgz#02a7e961fc32e6d5b2db0649e01bf80ddee7e04a" + integrity sha512-wq5n1M3ZUlHl9sqT2ok1T2/MTt6AXE0e1Lz4WzWBr95LsAZ5qDXe4KnFuauYyEyLiohvXFMdbsOTMyLZs91Zlw== + dependencies: + "@babel/helper-plugin-utils" "^7.10.4" + "@babel/plugin-syntax-nullish-coalescing-operator" "^7.8.0" + "@babel/plugin-proposal-nullish-coalescing-operator@^7.4.4", "@babel/plugin-proposal-nullish-coalescing-operator@^7.8.3": version "7.8.3" resolved "https://registry.yarnpkg.com/@babel/plugin-proposal-nullish-coalescing-operator/-/plugin-proposal-nullish-coalescing-operator-7.8.3.tgz#e4572253fdeed65cddeecfdab3f928afeb2fd5d2" @@ -331,6 +635,14 @@ "@babel/helper-plugin-utils" "^7.8.3" "@babel/plugin-syntax-nullish-coalescing-operator" "^7.8.0" +"@babel/plugin-proposal-numeric-separator@^7.10.4": + version "7.10.4" + resolved "https://registry.yarnpkg.com/@babel/plugin-proposal-numeric-separator/-/plugin-proposal-numeric-separator-7.10.4.tgz#ce1590ff0a65ad12970a609d78855e9a4c1aef06" + integrity sha512-73/G7QoRoeNkLZFxsoCCvlg4ezE4eM+57PnOqgaPOozd5myfj7p0muD1mRVJvbUWbOzD+q3No2bWbaKy+DJ8DA== + dependencies: + "@babel/helper-plugin-utils" "^7.10.4" + "@babel/plugin-syntax-numeric-separator" "^7.10.4" + "@babel/plugin-proposal-numeric-separator@^7.8.3": version "7.8.3" resolved "https://registry.yarnpkg.com/@babel/plugin-proposal-numeric-separator/-/plugin-proposal-numeric-separator-7.8.3.tgz#5d6769409699ec9b3b68684cd8116cedff93bad8" @@ -348,6 +660,23 @@ "@babel/plugin-syntax-object-rest-spread" "^7.8.0" "@babel/plugin-transform-parameters" "^7.9.5" +"@babel/plugin-proposal-object-rest-spread@^7.10.4": + version "7.10.4" + resolved "https://registry.yarnpkg.com/@babel/plugin-proposal-object-rest-spread/-/plugin-proposal-object-rest-spread-7.10.4.tgz#50129ac216b9a6a55b3853fdd923e74bf553a4c0" + integrity sha512-6vh4SqRuLLarjgeOf4EaROJAHjvu9Gl+/346PbDH9yWbJyfnJ/ah3jmYKYtswEyCoWZiidvVHjHshd4WgjB9BA== + dependencies: + "@babel/helper-plugin-utils" "^7.10.4" + "@babel/plugin-syntax-object-rest-spread" "^7.8.0" + "@babel/plugin-transform-parameters" "^7.10.4" + +"@babel/plugin-proposal-optional-catch-binding@^7.10.4": + version "7.10.4" + resolved "https://registry.yarnpkg.com/@babel/plugin-proposal-optional-catch-binding/-/plugin-proposal-optional-catch-binding-7.10.4.tgz#31c938309d24a78a49d68fdabffaa863758554dd" + integrity sha512-LflT6nPh+GK2MnFiKDyLiqSqVHkQnVf7hdoAvyTnnKj9xB3docGRsdPuxp6qqqW19ifK3xgc9U5/FwrSaCNX5g== + dependencies: + "@babel/helper-plugin-utils" "^7.10.4" + "@babel/plugin-syntax-optional-catch-binding" "^7.8.0" + "@babel/plugin-proposal-optional-catch-binding@^7.8.3": version "7.8.3" resolved "https://registry.yarnpkg.com/@babel/plugin-proposal-optional-catch-binding/-/plugin-proposal-optional-catch-binding-7.8.3.tgz#9dee96ab1650eed88646ae9734ca167ac4a9c5c9" @@ -356,6 +685,14 @@ "@babel/helper-plugin-utils" "^7.8.3" "@babel/plugin-syntax-optional-catch-binding" "^7.8.0" +"@babel/plugin-proposal-optional-chaining@^7.10.4": + version "7.10.4" + resolved "https://registry.yarnpkg.com/@babel/plugin-proposal-optional-chaining/-/plugin-proposal-optional-chaining-7.10.4.tgz#750f1255e930a1f82d8cdde45031f81a0d0adff7" + integrity sha512-ZIhQIEeavTgouyMSdZRap4VPPHqJJ3NEs2cuHs5p0erH+iz6khB0qfgU8g7UuJkG88+fBMy23ZiU+nuHvekJeQ== + dependencies: + "@babel/helper-plugin-utils" "^7.10.4" + "@babel/plugin-syntax-optional-chaining" "^7.8.0" + "@babel/plugin-proposal-optional-chaining@^7.6.0", "@babel/plugin-proposal-optional-chaining@^7.9.0": version "7.9.0" resolved "https://registry.yarnpkg.com/@babel/plugin-proposal-optional-chaining/-/plugin-proposal-optional-chaining-7.9.0.tgz#31db16b154c39d6b8a645292472b98394c292a58" @@ -364,6 +701,22 @@ "@babel/helper-plugin-utils" "^7.8.3" "@babel/plugin-syntax-optional-chaining" "^7.8.0" +"@babel/plugin-proposal-private-methods@^7.10.4": + version "7.10.4" + resolved "https://registry.yarnpkg.com/@babel/plugin-proposal-private-methods/-/plugin-proposal-private-methods-7.10.4.tgz#b160d972b8fdba5c7d111a145fc8c421fc2a6909" + integrity sha512-wh5GJleuI8k3emgTg5KkJK6kHNsGEr0uBTDBuQUBJwckk9xs1ez79ioheEVVxMLyPscB0LfkbVHslQqIzWV6Bw== + dependencies: + "@babel/helper-create-class-features-plugin" "^7.10.4" + "@babel/helper-plugin-utils" "^7.10.4" + +"@babel/plugin-proposal-unicode-property-regex@^7.10.4": + version "7.10.4" + resolved "https://registry.yarnpkg.com/@babel/plugin-proposal-unicode-property-regex/-/plugin-proposal-unicode-property-regex-7.10.4.tgz#4483cda53041ce3413b7fe2f00022665ddfaa75d" + integrity sha512-H+3fOgPnEXFL9zGYtKQe4IDOPKYlZdF1kqFDQRRb8PK4B8af1vAGK04tF5iQAAsui+mHNBQSAtd2/ndEDe9wuA== + dependencies: + "@babel/helper-create-regexp-features-plugin" "^7.10.4" + "@babel/helper-plugin-utils" "^7.10.4" + "@babel/plugin-proposal-unicode-property-regex@^7.4.4", "@babel/plugin-proposal-unicode-property-regex@^7.8.3": version "7.8.8" resolved "https://registry.yarnpkg.com/@babel/plugin-proposal-unicode-property-regex/-/plugin-proposal-unicode-property-regex-7.8.8.tgz#ee3a95e90cdc04fe8cd92ec3279fa017d68a0d1d" @@ -379,6 +732,20 @@ dependencies: "@babel/helper-plugin-utils" "^7.8.0" +"@babel/plugin-syntax-class-properties@^7.10.4": + version "7.10.4" + resolved "https://registry.yarnpkg.com/@babel/plugin-syntax-class-properties/-/plugin-syntax-class-properties-7.10.4.tgz#6644e6a0baa55a61f9e3231f6c9eeb6ee46c124c" + integrity sha512-GCSBF7iUle6rNugfURwNmCGG3Z/2+opxAMLs1nND4bhEG5PuxTIggDBoeYYSujAlLtsupzOHYJQgPS3pivwXIA== + dependencies: + "@babel/helper-plugin-utils" "^7.10.4" + +"@babel/plugin-syntax-decorators@^7.10.4": + version "7.10.4" + resolved "https://registry.yarnpkg.com/@babel/plugin-syntax-decorators/-/plugin-syntax-decorators-7.10.4.tgz#6853085b2c429f9d322d02f5a635018cdeb2360c" + integrity sha512-2NaoC6fAk2VMdhY1eerkfHV+lVYC1u8b+jmRJISqANCJlTxYy19HGdIkkQtix2UtkcPuPu+IlDgrVseZnU03bw== + dependencies: + "@babel/helper-plugin-utils" "^7.10.4" + "@babel/plugin-syntax-decorators@^7.8.3": version "7.8.3" resolved "https://registry.yarnpkg.com/@babel/plugin-syntax-decorators/-/plugin-syntax-decorators-7.8.3.tgz#8d2c15a9f1af624b0025f961682a9d53d3001bda" @@ -421,6 +788,13 @@ dependencies: "@babel/helper-plugin-utils" "^7.8.0" +"@babel/plugin-syntax-numeric-separator@^7.10.4": + version "7.10.4" + resolved "https://registry.yarnpkg.com/@babel/plugin-syntax-numeric-separator/-/plugin-syntax-numeric-separator-7.10.4.tgz#b9b070b3e33570cd9fd07ba7fa91c0dd37b9af97" + integrity sha512-9H6YdfkcK/uOnY/K7/aA2xpzaAgkQn37yzWUMRK7OaPOqOpGS1+n0H5hxT9AUw9EsSjPW8SVyMJwYRtWs3X3ug== + dependencies: + "@babel/helper-plugin-utils" "^7.10.4" + "@babel/plugin-syntax-numeric-separator@^7.8.0", "@babel/plugin-syntax-numeric-separator@^7.8.3": version "7.8.3" resolved "https://registry.yarnpkg.com/@babel/plugin-syntax-numeric-separator/-/plugin-syntax-numeric-separator-7.8.3.tgz#0e3fb63e09bea1b11e96467271c8308007e7c41f" @@ -449,6 +823,13 @@ dependencies: "@babel/helper-plugin-utils" "^7.8.0" +"@babel/plugin-syntax-top-level-await@^7.10.4": + version "7.10.4" + resolved "https://registry.yarnpkg.com/@babel/plugin-syntax-top-level-await/-/plugin-syntax-top-level-await-7.10.4.tgz#4bbeb8917b54fcf768364e0a81f560e33a3ef57d" + integrity sha512-ni1brg4lXEmWyafKr0ccFWkJG0CeMt4WV1oyeBW6EFObF4oOHclbkj5cARxAPQyAQ2UTuplJyK4nfkXIMMFvsQ== + dependencies: + "@babel/helper-plugin-utils" "^7.10.4" + "@babel/plugin-syntax-top-level-await@^7.8.3": version "7.8.3" resolved "https://registry.yarnpkg.com/@babel/plugin-syntax-top-level-await/-/plugin-syntax-top-level-await-7.8.3.tgz#3acdece695e6b13aaf57fc291d1a800950c71391" @@ -456,6 +837,13 @@ dependencies: "@babel/helper-plugin-utils" "^7.8.3" +"@babel/plugin-syntax-typescript@^7.10.4": + version "7.10.4" + resolved "https://registry.yarnpkg.com/@babel/plugin-syntax-typescript/-/plugin-syntax-typescript-7.10.4.tgz#2f55e770d3501e83af217d782cb7517d7bb34d25" + integrity sha512-oSAEz1YkBCAKr5Yiq8/BNtvSAPwkp/IyUnwZogd8p+F0RuYQQrLeRUzIQhueQTTBy/F+a40uS7OFKxnkRvmvFQ== + dependencies: + "@babel/helper-plugin-utils" "^7.10.4" + "@babel/plugin-syntax-typescript@^7.2.0", "@babel/plugin-syntax-typescript@^7.8.3": version "7.8.3" resolved "https://registry.yarnpkg.com/@babel/plugin-syntax-typescript/-/plugin-syntax-typescript-7.8.3.tgz#c1f659dda97711a569cef75275f7e15dcaa6cabc" @@ -463,6 +851,13 @@ dependencies: "@babel/helper-plugin-utils" "^7.8.3" +"@babel/plugin-transform-arrow-functions@^7.10.4": + version "7.10.4" + resolved "https://registry.yarnpkg.com/@babel/plugin-transform-arrow-functions/-/plugin-transform-arrow-functions-7.10.4.tgz#e22960d77e697c74f41c501d44d73dbf8a6a64cd" + integrity sha512-9J/oD1jV0ZCBcgnoFWFq1vJd4msoKb/TCpGNFyyLt0zABdcvgK3aYikZ8HjzB14c26bc7E3Q1yugpwGy2aTPNA== + dependencies: + "@babel/helper-plugin-utils" "^7.10.4" + "@babel/plugin-transform-arrow-functions@^7.8.3": version "7.8.3" resolved "https://registry.yarnpkg.com/@babel/plugin-transform-arrow-functions/-/plugin-transform-arrow-functions-7.8.3.tgz#82776c2ed0cd9e1a49956daeb896024c9473b8b6" @@ -470,6 +865,15 @@ dependencies: "@babel/helper-plugin-utils" "^7.8.3" +"@babel/plugin-transform-async-to-generator@^7.10.4": + version "7.10.4" + resolved "https://registry.yarnpkg.com/@babel/plugin-transform-async-to-generator/-/plugin-transform-async-to-generator-7.10.4.tgz#41a5017e49eb6f3cda9392a51eef29405b245a37" + integrity sha512-F6nREOan7J5UXTLsDsZG3DXmZSVofr2tGNwfdrVwkDWHfQckbQXnXSPfD7iO+c/2HGqycwyLST3DnZ16n+cBJQ== + dependencies: + "@babel/helper-module-imports" "^7.10.4" + "@babel/helper-plugin-utils" "^7.10.4" + "@babel/helper-remap-async-to-generator" "^7.10.4" + "@babel/plugin-transform-async-to-generator@^7.8.3": version "7.8.3" resolved "https://registry.yarnpkg.com/@babel/plugin-transform-async-to-generator/-/plugin-transform-async-to-generator-7.8.3.tgz#4308fad0d9409d71eafb9b1a6ee35f9d64b64086" @@ -479,6 +883,13 @@ "@babel/helper-plugin-utils" "^7.8.3" "@babel/helper-remap-async-to-generator" "^7.8.3" +"@babel/plugin-transform-block-scoped-functions@^7.10.4": + version "7.10.4" + resolved "https://registry.yarnpkg.com/@babel/plugin-transform-block-scoped-functions/-/plugin-transform-block-scoped-functions-7.10.4.tgz#1afa595744f75e43a91af73b0d998ecfe4ebc2e8" + integrity sha512-WzXDarQXYYfjaV1szJvN3AD7rZgZzC1JtjJZ8dMHUyiK8mxPRahynp14zzNjU3VkPqPsO38CzxiWO1c9ARZ8JA== + dependencies: + "@babel/helper-plugin-utils" "^7.10.4" + "@babel/plugin-transform-block-scoped-functions@^7.8.3": version "7.8.3" resolved "https://registry.yarnpkg.com/@babel/plugin-transform-block-scoped-functions/-/plugin-transform-block-scoped-functions-7.8.3.tgz#437eec5b799b5852072084b3ae5ef66e8349e8a3" @@ -486,6 +897,13 @@ dependencies: "@babel/helper-plugin-utils" "^7.8.3" +"@babel/plugin-transform-block-scoping@^7.10.4": + version "7.10.5" + resolved "https://registry.yarnpkg.com/@babel/plugin-transform-block-scoping/-/plugin-transform-block-scoping-7.10.5.tgz#b81b8aafefbfe68f0f65f7ef397b9ece68a6037d" + integrity sha512-6Ycw3hjpQti0qssQcA6AMSFDHeNJ++R6dIMnpRqUjFeBBTmTDPa8zgF90OVfTvAo11mXZTlVUViY1g8ffrURLg== + dependencies: + "@babel/helper-plugin-utils" "^7.10.4" + "@babel/plugin-transform-block-scoping@^7.7.4", "@babel/plugin-transform-block-scoping@^7.8.3": version "7.8.3" resolved "https://registry.yarnpkg.com/@babel/plugin-transform-block-scoping/-/plugin-transform-block-scoping-7.8.3.tgz#97d35dab66857a437c166358b91d09050c868f3a" @@ -494,6 +912,20 @@ "@babel/helper-plugin-utils" "^7.8.3" lodash "^4.17.13" +"@babel/plugin-transform-classes@^7.10.4": + version "7.10.4" + resolved "https://registry.yarnpkg.com/@babel/plugin-transform-classes/-/plugin-transform-classes-7.10.4.tgz#405136af2b3e218bc4a1926228bc917ab1a0adc7" + integrity sha512-2oZ9qLjt161dn1ZE0Ms66xBncQH4In8Sqw1YWgBUZuGVJJS5c0OFZXL6dP2MRHrkU/eKhWg8CzFJhRQl50rQxA== + dependencies: + "@babel/helper-annotate-as-pure" "^7.10.4" + "@babel/helper-define-map" "^7.10.4" + "@babel/helper-function-name" "^7.10.4" + "@babel/helper-optimise-call-expression" "^7.10.4" + "@babel/helper-plugin-utils" "^7.10.4" + "@babel/helper-replace-supers" "^7.10.4" + "@babel/helper-split-export-declaration" "^7.10.4" + globals "^11.1.0" + "@babel/plugin-transform-classes@^7.9.5": version "7.9.5" resolved "https://registry.yarnpkg.com/@babel/plugin-transform-classes/-/plugin-transform-classes-7.9.5.tgz#800597ddb8aefc2c293ed27459c1fcc935a26c2c" @@ -508,6 +940,13 @@ "@babel/helper-split-export-declaration" "^7.8.3" globals "^11.1.0" +"@babel/plugin-transform-computed-properties@^7.10.4": + version "7.10.4" + resolved "https://registry.yarnpkg.com/@babel/plugin-transform-computed-properties/-/plugin-transform-computed-properties-7.10.4.tgz#9ded83a816e82ded28d52d4b4ecbdd810cdfc0eb" + integrity sha512-JFwVDXcP/hM/TbyzGq3l/XWGut7p46Z3QvqFMXTfk6/09m7xZHJUN9xHfsv7vqqD4YnfI5ueYdSJtXqqBLyjBw== + dependencies: + "@babel/helper-plugin-utils" "^7.10.4" + "@babel/plugin-transform-computed-properties@^7.8.3": version "7.8.3" resolved "https://registry.yarnpkg.com/@babel/plugin-transform-computed-properties/-/plugin-transform-computed-properties-7.8.3.tgz#96d0d28b7f7ce4eb5b120bb2e0e943343c86f81b" @@ -515,6 +954,13 @@ dependencies: "@babel/helper-plugin-utils" "^7.8.3" +"@babel/plugin-transform-destructuring@^7.10.4": + version "7.10.4" + resolved "https://registry.yarnpkg.com/@babel/plugin-transform-destructuring/-/plugin-transform-destructuring-7.10.4.tgz#70ddd2b3d1bea83d01509e9bb25ddb3a74fc85e5" + integrity sha512-+WmfvyfsyF603iPa6825mq6Qrb7uLjTOsa3XOFzlYcYDHSS4QmpOWOL0NNBY5qMbvrcf3tq0Cw+v4lxswOBpgA== + dependencies: + "@babel/helper-plugin-utils" "^7.10.4" + "@babel/plugin-transform-destructuring@^7.9.5": version "7.9.5" resolved "https://registry.yarnpkg.com/@babel/plugin-transform-destructuring/-/plugin-transform-destructuring-7.9.5.tgz#72c97cf5f38604aea3abf3b935b0e17b1db76a50" @@ -522,6 +968,14 @@ dependencies: "@babel/helper-plugin-utils" "^7.8.3" +"@babel/plugin-transform-dotall-regex@^7.10.4": + version "7.10.4" + resolved "https://registry.yarnpkg.com/@babel/plugin-transform-dotall-regex/-/plugin-transform-dotall-regex-7.10.4.tgz#469c2062105c1eb6a040eaf4fac4b488078395ee" + integrity sha512-ZEAVvUTCMlMFAbASYSVQoxIbHm2OkG2MseW6bV2JjIygOjdVv8tuxrCTzj1+Rynh7ODb8GivUy7dzEXzEhuPaA== + dependencies: + "@babel/helper-create-regexp-features-plugin" "^7.10.4" + "@babel/helper-plugin-utils" "^7.10.4" + "@babel/plugin-transform-dotall-regex@^7.4.4", "@babel/plugin-transform-dotall-regex@^7.8.3": version "7.8.3" resolved "https://registry.yarnpkg.com/@babel/plugin-transform-dotall-regex/-/plugin-transform-dotall-regex-7.8.3.tgz#c3c6ec5ee6125c6993c5cbca20dc8621a9ea7a6e" @@ -530,6 +984,13 @@ "@babel/helper-create-regexp-features-plugin" "^7.8.3" "@babel/helper-plugin-utils" "^7.8.3" +"@babel/plugin-transform-duplicate-keys@^7.10.4": + version "7.10.4" + resolved "https://registry.yarnpkg.com/@babel/plugin-transform-duplicate-keys/-/plugin-transform-duplicate-keys-7.10.4.tgz#697e50c9fee14380fe843d1f306b295617431e47" + integrity sha512-GL0/fJnmgMclHiBTTWXNlYjYsA7rDrtsazHG6mglaGSTh0KsrW04qml+Bbz9FL0LcJIRwBWL5ZqlNHKTkU3xAA== + dependencies: + "@babel/helper-plugin-utils" "^7.10.4" + "@babel/plugin-transform-duplicate-keys@^7.8.3": version "7.8.3" resolved "https://registry.yarnpkg.com/@babel/plugin-transform-duplicate-keys/-/plugin-transform-duplicate-keys-7.8.3.tgz#8d12df309aa537f272899c565ea1768e286e21f1" @@ -537,6 +998,14 @@ dependencies: "@babel/helper-plugin-utils" "^7.8.3" +"@babel/plugin-transform-exponentiation-operator@^7.10.4": + version "7.10.4" + resolved "https://registry.yarnpkg.com/@babel/plugin-transform-exponentiation-operator/-/plugin-transform-exponentiation-operator-7.10.4.tgz#5ae338c57f8cf4001bdb35607ae66b92d665af2e" + integrity sha512-S5HgLVgkBcRdyQAHbKj+7KyuWx8C6t5oETmUuwz1pt3WTWJhsUV0WIIXuVvfXMxl/QQyHKlSCNNtaIamG8fysw== + dependencies: + "@babel/helper-builder-binary-assignment-operator-visitor" "^7.10.4" + "@babel/helper-plugin-utils" "^7.10.4" + "@babel/plugin-transform-exponentiation-operator@^7.8.3": version "7.8.3" resolved "https://registry.yarnpkg.com/@babel/plugin-transform-exponentiation-operator/-/plugin-transform-exponentiation-operator-7.8.3.tgz#581a6d7f56970e06bf51560cd64f5e947b70d7b7" @@ -553,6 +1022,13 @@ "@babel/helper-plugin-utils" "^7.8.3" "@babel/plugin-syntax-flow" "^7.8.3" +"@babel/plugin-transform-for-of@^7.10.4": + version "7.10.4" + resolved "https://registry.yarnpkg.com/@babel/plugin-transform-for-of/-/plugin-transform-for-of-7.10.4.tgz#c08892e8819d3a5db29031b115af511dbbfebae9" + integrity sha512-ItdQfAzu9AlEqmusA/65TqJ79eRcgGmpPPFvBnGILXZH975G0LNjP1yjHvGgfuCxqrPPueXOPe+FsvxmxKiHHQ== + dependencies: + "@babel/helper-plugin-utils" "^7.10.4" + "@babel/plugin-transform-for-of@^7.9.0": version "7.9.0" resolved "https://registry.yarnpkg.com/@babel/plugin-transform-for-of/-/plugin-transform-for-of-7.9.0.tgz#0f260e27d3e29cd1bb3128da5e76c761aa6c108e" @@ -560,6 +1036,14 @@ dependencies: "@babel/helper-plugin-utils" "^7.8.3" +"@babel/plugin-transform-function-name@^7.10.4": + version "7.10.4" + resolved "https://registry.yarnpkg.com/@babel/plugin-transform-function-name/-/plugin-transform-function-name-7.10.4.tgz#6a467880e0fc9638514ba369111811ddbe2644b7" + integrity sha512-OcDCq2y5+E0dVD5MagT5X+yTRbcvFjDI2ZVAottGH6tzqjx/LKpgkUepu3hp/u4tZBzxxpNGwLsAvGBvQ2mJzg== + dependencies: + "@babel/helper-function-name" "^7.10.4" + "@babel/helper-plugin-utils" "^7.10.4" + "@babel/plugin-transform-function-name@^7.8.3": version "7.8.3" resolved "https://registry.yarnpkg.com/@babel/plugin-transform-function-name/-/plugin-transform-function-name-7.8.3.tgz#279373cb27322aaad67c2683e776dfc47196ed8b" @@ -568,6 +1052,13 @@ "@babel/helper-function-name" "^7.8.3" "@babel/helper-plugin-utils" "^7.8.3" +"@babel/plugin-transform-literals@^7.10.4": + version "7.10.4" + resolved "https://registry.yarnpkg.com/@babel/plugin-transform-literals/-/plugin-transform-literals-7.10.4.tgz#9f42ba0841100a135f22712d0e391c462f571f3c" + integrity sha512-Xd/dFSTEVuUWnyZiMu76/InZxLTYilOSr1UlHV+p115Z/Le2Fi1KXkJUYz0b42DfndostYlPub3m8ZTQlMaiqQ== + dependencies: + "@babel/helper-plugin-utils" "^7.10.4" + "@babel/plugin-transform-literals@^7.8.3": version "7.8.3" resolved "https://registry.yarnpkg.com/@babel/plugin-transform-literals/-/plugin-transform-literals-7.8.3.tgz#aef239823d91994ec7b68e55193525d76dbd5dc1" @@ -575,6 +1066,13 @@ dependencies: "@babel/helper-plugin-utils" "^7.8.3" +"@babel/plugin-transform-member-expression-literals@^7.10.4": + version "7.10.4" + resolved "https://registry.yarnpkg.com/@babel/plugin-transform-member-expression-literals/-/plugin-transform-member-expression-literals-7.10.4.tgz#b1ec44fcf195afcb8db2c62cd8e551c881baf8b7" + integrity sha512-0bFOvPyAoTBhtcJLr9VcwZqKmSjFml1iVxvPL0ReomGU53CX53HsM4h2SzckNdkQcHox1bpAqzxBI1Y09LlBSw== + dependencies: + "@babel/helper-plugin-utils" "^7.10.4" + "@babel/plugin-transform-member-expression-literals@^7.8.3": version "7.8.3" resolved "https://registry.yarnpkg.com/@babel/plugin-transform-member-expression-literals/-/plugin-transform-member-expression-literals-7.8.3.tgz#963fed4b620ac7cbf6029c755424029fa3a40410" @@ -582,6 +1080,15 @@ dependencies: "@babel/helper-plugin-utils" "^7.8.3" +"@babel/plugin-transform-modules-amd@^7.10.1", "@babel/plugin-transform-modules-amd@^7.10.4": + version "7.10.5" + resolved "https://registry.yarnpkg.com/@babel/plugin-transform-modules-amd/-/plugin-transform-modules-amd-7.10.5.tgz#1b9cddaf05d9e88b3aad339cb3e445c4f020a9b1" + integrity sha512-elm5uruNio7CTLFItVC/rIzKLfQ17+fX7EVz5W0TMgIHFo1zY0Ozzx+lgwhL4plzl8OzVn6Qasx5DeEFyoNiRw== + dependencies: + "@babel/helper-module-transforms" "^7.10.5" + "@babel/helper-plugin-utils" "^7.10.4" + babel-plugin-dynamic-import-node "^2.3.3" + "@babel/plugin-transform-modules-amd@^7.7.5", "@babel/plugin-transform-modules-amd@^7.9.0", "@babel/plugin-transform-modules-amd@^7.9.6": version "7.9.6" resolved "https://registry.yarnpkg.com/@babel/plugin-transform-modules-amd/-/plugin-transform-modules-amd-7.9.6.tgz#8539ec42c153d12ea3836e0e3ac30d5aae7b258e" @@ -591,6 +1098,16 @@ "@babel/helper-plugin-utils" "^7.8.3" babel-plugin-dynamic-import-node "^2.3.3" +"@babel/plugin-transform-modules-commonjs@^7.10.4": + version "7.10.4" + resolved "https://registry.yarnpkg.com/@babel/plugin-transform-modules-commonjs/-/plugin-transform-modules-commonjs-7.10.4.tgz#66667c3eeda1ebf7896d41f1f16b17105a2fbca0" + integrity sha512-Xj7Uq5o80HDLlW64rVfDBhao6OX89HKUmb+9vWYaLXBZOma4gA6tw4Ni1O5qVDoZWUV0fxMYA0aYzOawz0l+1w== + dependencies: + "@babel/helper-module-transforms" "^7.10.4" + "@babel/helper-plugin-utils" "^7.10.4" + "@babel/helper-simple-access" "^7.10.4" + babel-plugin-dynamic-import-node "^2.3.3" + "@babel/plugin-transform-modules-commonjs@^7.9.6": version "7.9.6" resolved "https://registry.yarnpkg.com/@babel/plugin-transform-modules-commonjs/-/plugin-transform-modules-commonjs-7.9.6.tgz#64b7474a4279ee588cacd1906695ca721687c277" @@ -601,6 +1118,16 @@ "@babel/helper-simple-access" "^7.8.3" babel-plugin-dynamic-import-node "^2.3.3" +"@babel/plugin-transform-modules-systemjs@^7.10.4": + version "7.10.5" + resolved "https://registry.yarnpkg.com/@babel/plugin-transform-modules-systemjs/-/plugin-transform-modules-systemjs-7.10.5.tgz#6270099c854066681bae9e05f87e1b9cadbe8c85" + integrity sha512-f4RLO/OL14/FP1AEbcsWMzpbUz6tssRaeQg11RH1BP/XnPpRoVwgeYViMFacnkaw4k4wjRSjn3ip1Uw9TaXuMw== + dependencies: + "@babel/helper-hoist-variables" "^7.10.4" + "@babel/helper-module-transforms" "^7.10.5" + "@babel/helper-plugin-utils" "^7.10.4" + babel-plugin-dynamic-import-node "^2.3.3" + "@babel/plugin-transform-modules-systemjs@^7.9.6": version "7.9.6" resolved "https://registry.yarnpkg.com/@babel/plugin-transform-modules-systemjs/-/plugin-transform-modules-systemjs-7.9.6.tgz#207f1461c78a231d5337a92140e52422510d81a4" @@ -611,6 +1138,14 @@ "@babel/helper-plugin-utils" "^7.8.3" babel-plugin-dynamic-import-node "^2.3.3" +"@babel/plugin-transform-modules-umd@^7.10.4": + version "7.10.4" + resolved "https://registry.yarnpkg.com/@babel/plugin-transform-modules-umd/-/plugin-transform-modules-umd-7.10.4.tgz#9a8481fe81b824654b3a0b65da3df89f3d21839e" + integrity sha512-mohW5q3uAEt8T45YT7Qc5ws6mWgJAaL/8BfWD9Dodo1A3RKWli8wTS+WiQ/knF+tXlPirW/1/MqzzGfCExKECA== + dependencies: + "@babel/helper-module-transforms" "^7.10.4" + "@babel/helper-plugin-utils" "^7.10.4" + "@babel/plugin-transform-modules-umd@^7.9.0": version "7.9.0" resolved "https://registry.yarnpkg.com/@babel/plugin-transform-modules-umd/-/plugin-transform-modules-umd-7.9.0.tgz#e909acae276fec280f9b821a5f38e1f08b480697" @@ -619,6 +1154,13 @@ "@babel/helper-module-transforms" "^7.9.0" "@babel/helper-plugin-utils" "^7.8.3" +"@babel/plugin-transform-named-capturing-groups-regex@^7.10.4": + version "7.10.4" + resolved "https://registry.yarnpkg.com/@babel/plugin-transform-named-capturing-groups-regex/-/plugin-transform-named-capturing-groups-regex-7.10.4.tgz#78b4d978810b6f3bcf03f9e318f2fc0ed41aecb6" + integrity sha512-V6LuOnD31kTkxQPhKiVYzYC/Jgdq53irJC/xBSmqcNcqFGV+PER4l6rU5SH2Vl7bH9mLDHcc0+l9HUOe4RNGKA== + dependencies: + "@babel/helper-create-regexp-features-plugin" "^7.10.4" + "@babel/plugin-transform-named-capturing-groups-regex@^7.8.3": version "7.8.3" resolved "https://registry.yarnpkg.com/@babel/plugin-transform-named-capturing-groups-regex/-/plugin-transform-named-capturing-groups-regex-7.8.3.tgz#a2a72bffa202ac0e2d0506afd0939c5ecbc48c6c" @@ -626,6 +1168,13 @@ dependencies: "@babel/helper-create-regexp-features-plugin" "^7.8.3" +"@babel/plugin-transform-new-target@^7.10.4": + version "7.10.4" + resolved "https://registry.yarnpkg.com/@babel/plugin-transform-new-target/-/plugin-transform-new-target-7.10.4.tgz#9097d753cb7b024cb7381a3b2e52e9513a9c6888" + integrity sha512-YXwWUDAH/J6dlfwqlWsztI2Puz1NtUAubXhOPLQ5gjR/qmQ5U96DY4FQO8At33JN4XPBhrjB8I4eMmLROjjLjw== + dependencies: + "@babel/helper-plugin-utils" "^7.10.4" + "@babel/plugin-transform-new-target@^7.8.3": version "7.8.3" resolved "https://registry.yarnpkg.com/@babel/plugin-transform-new-target/-/plugin-transform-new-target-7.8.3.tgz#60cc2ae66d85c95ab540eb34babb6434d4c70c43" @@ -640,6 +1189,14 @@ dependencies: "@babel/helper-plugin-utils" "^7.8.3" +"@babel/plugin-transform-object-super@^7.10.4": + version "7.10.4" + resolved "https://registry.yarnpkg.com/@babel/plugin-transform-object-super/-/plugin-transform-object-super-7.10.4.tgz#d7146c4d139433e7a6526f888c667e314a093894" + integrity sha512-5iTw0JkdRdJvr7sY0vHqTpnruUpTea32JHmq/atIWqsnNussbRzjEDyWep8UNztt1B5IusBYg8Irb0bLbiEBCQ== + dependencies: + "@babel/helper-plugin-utils" "^7.10.4" + "@babel/helper-replace-supers" "^7.10.4" + "@babel/plugin-transform-object-super@^7.8.3": version "7.8.3" resolved "https://registry.yarnpkg.com/@babel/plugin-transform-object-super/-/plugin-transform-object-super-7.8.3.tgz#ebb6a1e7a86ffa96858bd6ac0102d65944261725" @@ -648,6 +1205,14 @@ "@babel/helper-plugin-utils" "^7.8.3" "@babel/helper-replace-supers" "^7.8.3" +"@babel/plugin-transform-parameters@^7.10.4": + version "7.10.5" + resolved "https://registry.yarnpkg.com/@babel/plugin-transform-parameters/-/plugin-transform-parameters-7.10.5.tgz#59d339d58d0b1950435f4043e74e2510005e2c4a" + integrity sha512-xPHwUj5RdFV8l1wuYiu5S9fqWGM2DrYc24TMvUiRrPVm+SM3XeqU9BcokQX/kEUe+p2RBwy+yoiR1w/Blq6ubw== + dependencies: + "@babel/helper-get-function-arity" "^7.10.4" + "@babel/helper-plugin-utils" "^7.10.4" + "@babel/plugin-transform-parameters@^7.9.5": version "7.9.5" resolved "https://registry.yarnpkg.com/@babel/plugin-transform-parameters/-/plugin-transform-parameters-7.9.5.tgz#173b265746f5e15b2afe527eeda65b73623a0795" @@ -656,6 +1221,13 @@ "@babel/helper-get-function-arity" "^7.8.3" "@babel/helper-plugin-utils" "^7.8.3" +"@babel/plugin-transform-property-literals@^7.10.4": + version "7.10.4" + resolved "https://registry.yarnpkg.com/@babel/plugin-transform-property-literals/-/plugin-transform-property-literals-7.10.4.tgz#f6fe54b6590352298785b83edd815d214c42e3c0" + integrity sha512-ofsAcKiUxQ8TY4sScgsGeR2vJIsfrzqvFb9GvJ5UdXDzl+MyYCaBj/FGzXuv7qE0aJcjWMILny1epqelnFlz8g== + dependencies: + "@babel/helper-plugin-utils" "^7.10.4" + "@babel/plugin-transform-property-literals@^7.8.3": version "7.8.3" resolved "https://registry.yarnpkg.com/@babel/plugin-transform-property-literals/-/plugin-transform-property-literals-7.8.3.tgz#33194300d8539c1ed28c62ad5087ba3807b98263" @@ -680,6 +1252,13 @@ "@babel/helper-plugin-utils" "^7.8.3" "@babel/plugin-syntax-jsx" "^7.8.3" +"@babel/plugin-transform-regenerator@^7.10.4": + version "7.10.4" + resolved "https://registry.yarnpkg.com/@babel/plugin-transform-regenerator/-/plugin-transform-regenerator-7.10.4.tgz#2015e59d839074e76838de2159db421966fd8b63" + integrity sha512-3thAHwtor39A7C04XucbMg17RcZ3Qppfxr22wYzZNcVIkPHfpM9J0SO8zuCV6SZa265kxBJSrfKTvDCYqBFXGw== + dependencies: + regenerator-transform "^0.14.2" + "@babel/plugin-transform-regenerator@^7.8.7": version "7.8.7" resolved "https://registry.yarnpkg.com/@babel/plugin-transform-regenerator/-/plugin-transform-regenerator-7.8.7.tgz#5e46a0dca2bee1ad8285eb0527e6abc9c37672f8" @@ -687,6 +1266,13 @@ dependencies: regenerator-transform "^0.14.2" +"@babel/plugin-transform-reserved-words@^7.10.4": + version "7.10.4" + resolved "https://registry.yarnpkg.com/@babel/plugin-transform-reserved-words/-/plugin-transform-reserved-words-7.10.4.tgz#8f2682bcdcef9ed327e1b0861585d7013f8a54dd" + integrity sha512-hGsw1O6Rew1fkFbDImZIEqA8GoidwTAilwCyWqLBM9f+e/u/sQMQu7uX6dyokfOayRuuVfKOW4O7HvaBWM+JlQ== + dependencies: + "@babel/helper-plugin-utils" "^7.10.4" + "@babel/plugin-transform-reserved-words@^7.8.3": version "7.8.3" resolved "https://registry.yarnpkg.com/@babel/plugin-transform-reserved-words/-/plugin-transform-reserved-words-7.8.3.tgz#9a0635ac4e665d29b162837dd3cc50745dfdf1f5" @@ -694,6 +1280,16 @@ dependencies: "@babel/helper-plugin-utils" "^7.8.3" +"@babel/plugin-transform-runtime@^7.10.1": + version "7.10.5" + resolved "https://registry.yarnpkg.com/@babel/plugin-transform-runtime/-/plugin-transform-runtime-7.10.5.tgz#3b39b7b24830e0c2d8ff7a4489fe5cf99fbace86" + integrity sha512-tV4V/FjElJ9lQtyjr5xD2IFFbgY46r7EeVu5a8CpEKT5laheHKSlFeHjpkPppW3PqzGLAuv5k2qZX5LgVZIX5w== + dependencies: + "@babel/helper-module-imports" "^7.10.4" + "@babel/helper-plugin-utils" "^7.10.4" + resolve "^1.8.1" + semver "^5.5.1" + "@babel/plugin-transform-runtime@^7.9.0": version "7.9.6" resolved "https://registry.yarnpkg.com/@babel/plugin-transform-runtime/-/plugin-transform-runtime-7.9.6.tgz#3ba804438ad0d880a17bca5eaa0cdf1edeedb2fd" @@ -704,6 +1300,13 @@ resolve "^1.8.1" semver "^5.5.1" +"@babel/plugin-transform-shorthand-properties@^7.10.4": + version "7.10.4" + resolved "https://registry.yarnpkg.com/@babel/plugin-transform-shorthand-properties/-/plugin-transform-shorthand-properties-7.10.4.tgz#9fd25ec5cdd555bb7f473e5e6ee1c971eede4dd6" + integrity sha512-AC2K/t7o07KeTIxMoHneyX90v3zkm5cjHJEokrPEAGEy3UCp8sLKfnfOIGdZ194fyN4wfX/zZUWT9trJZ0qc+Q== + dependencies: + "@babel/helper-plugin-utils" "^7.10.4" + "@babel/plugin-transform-shorthand-properties@^7.8.3": version "7.8.3" resolved "https://registry.yarnpkg.com/@babel/plugin-transform-shorthand-properties/-/plugin-transform-shorthand-properties-7.8.3.tgz#28545216e023a832d4d3a1185ed492bcfeac08c8" @@ -711,6 +1314,13 @@ dependencies: "@babel/helper-plugin-utils" "^7.8.3" +"@babel/plugin-transform-spread@^7.10.4": + version "7.10.4" + resolved "https://registry.yarnpkg.com/@babel/plugin-transform-spread/-/plugin-transform-spread-7.10.4.tgz#4e2c85ea0d6abaee1b24dcfbbae426fe8d674cff" + integrity sha512-1e/51G/Ni+7uH5gktbWv+eCED9pP8ZpRhZB3jOaI3mmzfvJTWHkuyYTv0Z5PYtyM+Tr2Ccr9kUdQxn60fI5WuQ== + dependencies: + "@babel/helper-plugin-utils" "^7.10.4" + "@babel/plugin-transform-spread@^7.8.3": version "7.8.3" resolved "https://registry.yarnpkg.com/@babel/plugin-transform-spread/-/plugin-transform-spread-7.8.3.tgz#9c8ffe8170fdfb88b114ecb920b82fb6e95fe5e8" @@ -718,6 +1328,14 @@ dependencies: "@babel/helper-plugin-utils" "^7.8.3" +"@babel/plugin-transform-sticky-regex@^7.10.4": + version "7.10.4" + resolved "https://registry.yarnpkg.com/@babel/plugin-transform-sticky-regex/-/plugin-transform-sticky-regex-7.10.4.tgz#8f3889ee8657581130a29d9cc91d7c73b7c4a28d" + integrity sha512-Ddy3QZfIbEV0VYcVtFDCjeE4xwVTJWTmUtorAJkn6u/92Z/nWJNV+mILyqHKrUxXYKA2EoCilgoPePymKL4DvQ== + dependencies: + "@babel/helper-plugin-utils" "^7.10.4" + "@babel/helper-regex" "^7.10.4" + "@babel/plugin-transform-sticky-regex@^7.8.3": version "7.8.3" resolved "https://registry.yarnpkg.com/@babel/plugin-transform-sticky-regex/-/plugin-transform-sticky-regex-7.8.3.tgz#be7a1290f81dae767475452199e1f76d6175b100" @@ -726,6 +1344,14 @@ "@babel/helper-plugin-utils" "^7.8.3" "@babel/helper-regex" "^7.8.3" +"@babel/plugin-transform-template-literals@^7.10.4": + version "7.10.5" + resolved "https://registry.yarnpkg.com/@babel/plugin-transform-template-literals/-/plugin-transform-template-literals-7.10.5.tgz#78bc5d626a6642db3312d9d0f001f5e7639fde8c" + integrity sha512-V/lnPGIb+KT12OQikDvgSuesRX14ck5FfJXt6+tXhdkJ+Vsd0lDCVtF6jcB4rNClYFzaB2jusZ+lNISDk2mMMw== + dependencies: + "@babel/helper-annotate-as-pure" "^7.10.4" + "@babel/helper-plugin-utils" "^7.10.4" + "@babel/plugin-transform-template-literals@^7.8.3": version "7.8.3" resolved "https://registry.yarnpkg.com/@babel/plugin-transform-template-literals/-/plugin-transform-template-literals-7.8.3.tgz#7bfa4732b455ea6a43130adc0ba767ec0e402a80" @@ -734,6 +1360,13 @@ "@babel/helper-annotate-as-pure" "^7.8.3" "@babel/helper-plugin-utils" "^7.8.3" +"@babel/plugin-transform-typeof-symbol@^7.10.4": + version "7.10.4" + resolved "https://registry.yarnpkg.com/@babel/plugin-transform-typeof-symbol/-/plugin-transform-typeof-symbol-7.10.4.tgz#9509f1a7eec31c4edbffe137c16cc33ff0bc5bfc" + integrity sha512-QqNgYwuuW0y0H+kUE/GWSR45t/ccRhe14Fs/4ZRouNNQsyd4o3PG4OtHiIrepbM2WKUBDAXKCAK/Lk4VhzTaGA== + dependencies: + "@babel/helper-plugin-utils" "^7.10.4" + "@babel/plugin-transform-typeof-symbol@^7.8.4": version "7.8.4" resolved "https://registry.yarnpkg.com/@babel/plugin-transform-typeof-symbol/-/plugin-transform-typeof-symbol-7.8.4.tgz#ede4062315ce0aaf8a657a920858f1a2f35fc412" @@ -741,6 +1374,15 @@ dependencies: "@babel/helper-plugin-utils" "^7.8.3" +"@babel/plugin-transform-typescript@^7.10.1": + version "7.10.5" + resolved "https://registry.yarnpkg.com/@babel/plugin-transform-typescript/-/plugin-transform-typescript-7.10.5.tgz#edf353944e979f40d8ff9fe4e9975d0a465037c5" + integrity sha512-YCyYsFrrRMZ3qR7wRwtSSJovPG5vGyG4ZdcSAivGwTfoasMp3VOB/AKhohu3dFtmB4cCDcsndCSxGtrdliCsZQ== + dependencies: + "@babel/helper-create-class-features-plugin" "^7.10.5" + "@babel/helper-plugin-utils" "^7.10.4" + "@babel/plugin-syntax-typescript" "^7.10.4" + "@babel/plugin-transform-typescript@^7.9.0": version "7.9.6" resolved "https://registry.yarnpkg.com/@babel/plugin-transform-typescript/-/plugin-transform-typescript-7.9.6.tgz#2248971416a506fc78278fc0c0ea3179224af1e9" @@ -776,6 +1418,21 @@ "@babel/helper-plugin-utils" "^7.8.3" "@babel/plugin-syntax-typescript" "^7.8.3" +"@babel/plugin-transform-unicode-escapes@^7.10.4": + version "7.10.4" + resolved "https://registry.yarnpkg.com/@babel/plugin-transform-unicode-escapes/-/plugin-transform-unicode-escapes-7.10.4.tgz#feae523391c7651ddac115dae0a9d06857892007" + integrity sha512-y5XJ9waMti2J+e7ij20e+aH+fho7Wb7W8rNuu72aKRwCHFqQdhkdU2lo3uZ9tQuboEJcUFayXdARhcxLQ3+6Fg== + dependencies: + "@babel/helper-plugin-utils" "^7.10.4" + +"@babel/plugin-transform-unicode-regex@^7.10.4": + version "7.10.4" + resolved "https://registry.yarnpkg.com/@babel/plugin-transform-unicode-regex/-/plugin-transform-unicode-regex-7.10.4.tgz#e56d71f9282fac6db09c82742055576d5e6d80a8" + integrity sha512-wNfsc4s8N2qnIwpO/WP2ZiSyjfpTamT2C9V9FDH/Ljub9zw6P3SjkXcFmc0RQUt96k2fmIvtla2MMjgTwIAC+A== + dependencies: + "@babel/helper-create-regexp-features-plugin" "^7.10.4" + "@babel/helper-plugin-utils" "^7.10.4" + "@babel/plugin-transform-unicode-regex@^7.8.3": version "7.8.3" resolved "https://registry.yarnpkg.com/@babel/plugin-transform-unicode-regex/-/plugin-transform-unicode-regex-7.8.3.tgz#0cef36e3ba73e5c57273effb182f46b91a1ecaad" @@ -784,6 +1441,14 @@ "@babel/helper-create-regexp-features-plugin" "^7.8.3" "@babel/helper-plugin-utils" "^7.8.3" +"@babel/polyfill@^7.10.1": + version "7.10.4" + resolved "https://registry.yarnpkg.com/@babel/polyfill/-/polyfill-7.10.4.tgz#915e5bfe61490ac0199008e35ca9d7d151a8e45a" + integrity sha512-8BYcnVqQ5kMD2HXoHInBH7H1b/uP3KdnwCYXOqFnXqguOyuu443WXusbIUbWEfY3Z0Txk0M1uG/8YuAMhNl6zg== + dependencies: + core-js "^2.6.5" + regenerator-runtime "^0.13.4" + "@babel/polyfill@^7.8.3", "@babel/polyfill@^7.8.7": version "7.8.7" resolved "https://registry.yarnpkg.com/@babel/polyfill/-/polyfill-7.8.7.tgz#151ec24c7135481336168c3bd8b8bf0cf91c032f" @@ -858,6 +1523,76 @@ levenary "^1.1.1" semver "^5.5.0" +"@babel/preset-env@^7.10.2": + version "7.10.4" + resolved "https://registry.yarnpkg.com/@babel/preset-env/-/preset-env-7.10.4.tgz#fbf57f9a803afd97f4f32e4f798bb62e4b2bef5f" + integrity sha512-tcmuQ6vupfMZPrLrc38d0sF2OjLT3/bZ0dry5HchNCQbrokoQi4reXqclvkkAT5b+gWc23meVWpve5P/7+w/zw== + dependencies: + "@babel/compat-data" "^7.10.4" + "@babel/helper-compilation-targets" "^7.10.4" + "@babel/helper-module-imports" "^7.10.4" + "@babel/helper-plugin-utils" "^7.10.4" + "@babel/plugin-proposal-async-generator-functions" "^7.10.4" + "@babel/plugin-proposal-class-properties" "^7.10.4" + "@babel/plugin-proposal-dynamic-import" "^7.10.4" + "@babel/plugin-proposal-json-strings" "^7.10.4" + "@babel/plugin-proposal-nullish-coalescing-operator" "^7.10.4" + "@babel/plugin-proposal-numeric-separator" "^7.10.4" + "@babel/plugin-proposal-object-rest-spread" "^7.10.4" + "@babel/plugin-proposal-optional-catch-binding" "^7.10.4" + "@babel/plugin-proposal-optional-chaining" "^7.10.4" + "@babel/plugin-proposal-private-methods" "^7.10.4" + "@babel/plugin-proposal-unicode-property-regex" "^7.10.4" + "@babel/plugin-syntax-async-generators" "^7.8.0" + "@babel/plugin-syntax-class-properties" "^7.10.4" + "@babel/plugin-syntax-dynamic-import" "^7.8.0" + "@babel/plugin-syntax-json-strings" "^7.8.0" + "@babel/plugin-syntax-nullish-coalescing-operator" "^7.8.0" + "@babel/plugin-syntax-numeric-separator" "^7.10.4" + "@babel/plugin-syntax-object-rest-spread" "^7.8.0" + "@babel/plugin-syntax-optional-catch-binding" "^7.8.0" + "@babel/plugin-syntax-optional-chaining" "^7.8.0" + "@babel/plugin-syntax-top-level-await" "^7.10.4" + "@babel/plugin-transform-arrow-functions" "^7.10.4" + "@babel/plugin-transform-async-to-generator" "^7.10.4" + "@babel/plugin-transform-block-scoped-functions" "^7.10.4" + "@babel/plugin-transform-block-scoping" "^7.10.4" + "@babel/plugin-transform-classes" "^7.10.4" + "@babel/plugin-transform-computed-properties" "^7.10.4" + "@babel/plugin-transform-destructuring" "^7.10.4" + "@babel/plugin-transform-dotall-regex" "^7.10.4" + "@babel/plugin-transform-duplicate-keys" "^7.10.4" + "@babel/plugin-transform-exponentiation-operator" "^7.10.4" + "@babel/plugin-transform-for-of" "^7.10.4" + "@babel/plugin-transform-function-name" "^7.10.4" + "@babel/plugin-transform-literals" "^7.10.4" + "@babel/plugin-transform-member-expression-literals" "^7.10.4" + "@babel/plugin-transform-modules-amd" "^7.10.4" + "@babel/plugin-transform-modules-commonjs" "^7.10.4" + "@babel/plugin-transform-modules-systemjs" "^7.10.4" + "@babel/plugin-transform-modules-umd" "^7.10.4" + "@babel/plugin-transform-named-capturing-groups-regex" "^7.10.4" + "@babel/plugin-transform-new-target" "^7.10.4" + "@babel/plugin-transform-object-super" "^7.10.4" + "@babel/plugin-transform-parameters" "^7.10.4" + "@babel/plugin-transform-property-literals" "^7.10.4" + "@babel/plugin-transform-regenerator" "^7.10.4" + "@babel/plugin-transform-reserved-words" "^7.10.4" + "@babel/plugin-transform-shorthand-properties" "^7.10.4" + "@babel/plugin-transform-spread" "^7.10.4" + "@babel/plugin-transform-sticky-regex" "^7.10.4" + "@babel/plugin-transform-template-literals" "^7.10.4" + "@babel/plugin-transform-typeof-symbol" "^7.10.4" + "@babel/plugin-transform-unicode-escapes" "^7.10.4" + "@babel/plugin-transform-unicode-regex" "^7.10.4" + "@babel/preset-modules" "^0.1.3" + "@babel/types" "^7.10.4" + browserslist "^4.12.0" + core-js-compat "^3.6.2" + invariant "^2.2.2" + levenary "^1.1.1" + semver "^5.5.0" + "@babel/preset-flow@^7.0.0": version "7.9.0" resolved "https://registry.yarnpkg.com/@babel/preset-flow/-/preset-flow-7.9.0.tgz#fee847c3e090b0b2d9227c1949e4da1d1379280d" @@ -903,6 +1638,22 @@ dependencies: regenerator-runtime "^0.13.4" +"@babel/runtime@^7.10.2": + version "7.10.5" + resolved "https://registry.yarnpkg.com/@babel/runtime/-/runtime-7.10.5.tgz#303d8bd440ecd5a491eae6117fd3367698674c5c" + integrity sha512-otddXKhdNn7d0ptoFRHtMLa8LqDxLYwTjB4nYgM1yy5N6gU/MUf8zqyyLltCH3yAVitBzmwK4us+DD0l/MauAg== + dependencies: + regenerator-runtime "^0.13.4" + +"@babel/template@^7.10.4": + version "7.10.4" + resolved "https://registry.yarnpkg.com/@babel/template/-/template-7.10.4.tgz#3251996c4200ebc71d1a8fc405fba940f36ba278" + integrity sha512-ZCjD27cGJFUB6nmCB1Enki3r+L5kJveX9pq1SvAUKoICy6CZ9yD8xO086YXdYhvNjBdnekm4ZnaP5yC8Cs/1tA== + dependencies: + "@babel/code-frame" "^7.10.4" + "@babel/parser" "^7.10.4" + "@babel/types" "^7.10.4" + "@babel/template@^7.4.0", "@babel/template@^7.8.3", "@babel/template@^7.8.6": version "7.8.6" resolved "https://registry.yarnpkg.com/@babel/template/-/template-7.8.6.tgz#86b22af15f828dfb086474f964dcc3e39c43ce2b" @@ -927,6 +1678,21 @@ globals "^11.1.0" lodash "^4.17.13" +"@babel/traverse@^7.10.4", "@babel/traverse@^7.10.5": + version "7.10.5" + resolved "https://registry.yarnpkg.com/@babel/traverse/-/traverse-7.10.5.tgz#77ce464f5b258be265af618d8fddf0536f20b564" + integrity sha512-yc/fyv2gUjPqzTz0WHeRJH2pv7jA9kA7mBX2tXl/x5iOE81uaVPuGPtaYk7wmkx4b67mQ7NqI8rmT2pF47KYKQ== + dependencies: + "@babel/code-frame" "^7.10.4" + "@babel/generator" "^7.10.5" + "@babel/helper-function-name" "^7.10.4" + "@babel/helper-split-export-declaration" "^7.10.4" + "@babel/parser" "^7.10.5" + "@babel/types" "^7.10.5" + debug "^4.1.0" + globals "^11.1.0" + lodash "^4.17.19" + "@babel/types@^7.1.6", "@babel/types@^7.3.2", "@babel/types@^7.3.4", "@babel/types@^7.4.0", "@babel/types@^7.4.4", "@babel/types@^7.6.0", "@babel/types@^7.7.0", "@babel/types@^7.7.2", "@babel/types@^7.8.3", "@babel/types@^7.8.6", "@babel/types@^7.9.0", "@babel/types@^7.9.5", "@babel/types@^7.9.6": version "7.9.6" resolved "https://registry.yarnpkg.com/@babel/types/-/types-7.9.6.tgz#2c5502b427251e9de1bd2dff95add646d95cc9f7" @@ -936,6 +1702,15 @@ lodash "^4.17.13" to-fast-properties "^2.0.0" +"@babel/types@^7.10.4", "@babel/types@^7.10.5": + version "7.10.5" + resolved "https://registry.yarnpkg.com/@babel/types/-/types-7.10.5.tgz#d88ae7e2fde86bfbfe851d4d81afa70a997b5d15" + integrity sha512-ixV66KWfCI6GKoA/2H9v6bQdbfXEwwpOdQ8cRvb4F+eyvhlaHxWFMQB4+3d9QFJXZsiiiqVrewNV0DFEQpyT4Q== + dependencies: + "@babel/helper-validator-identifier" "^7.10.4" + lodash "^4.17.19" + to-fast-properties "^2.0.0" + "@base2/pretty-print-object@1.0.0": version "1.0.0" resolved "https://registry.yarnpkg.com/@base2/pretty-print-object/-/pretty-print-object-1.0.0.tgz#860ce718b0b73f4009e153541faff2cb6b85d047" @@ -3917,6 +4692,13 @@ babel-plugin-ember-modules-api-polyfill@^2.12.0, babel-plugin-ember-modules-api- dependencies: ember-rfc176-data "^0.3.13" +babel-plugin-ember-modules-api-polyfill@^2.13.4: + version "2.13.4" + resolved "https://registry.yarnpkg.com/babel-plugin-ember-modules-api-polyfill/-/babel-plugin-ember-modules-api-polyfill-2.13.4.tgz#cf62bc9bfd808c48d810d5194f4329e9453bd603" + integrity sha512-uxQPkEQAzCYdwhZk16O9m1R4xtCRNy4oEUTBrccOPfzlIahRZJic/JeP/ZEL0BC6Mfq6r55eOg6gMF/zdFoCvA== + dependencies: + ember-rfc176-data "^0.3.13" + babel-plugin-emotion@^10.0.20, babel-plugin-emotion@^10.0.27: version "10.0.33" resolved "https://registry.yarnpkg.com/babel-plugin-emotion/-/babel-plugin-emotion-10.0.33.tgz#ce1155dcd1783bbb9286051efee53f4e2be63e03" @@ -4553,6 +5335,11 @@ balanced-match@^1.0.0: resolved "https://registry.yarnpkg.com/balanced-match/-/balanced-match-1.0.0.tgz#89b4d199ab2bee49de164ea02b89ce462d71b767" integrity sha1-ibTRmasr7kneFk6gK4nORi1xt2c= +base-64@^0.1.0: + version "0.1.0" + resolved "https://registry.yarnpkg.com/base-64/-/base-64-0.1.0.tgz#780a99c84e7d600260361511c4877613bf24f6bb" + integrity sha1-eAqZyE59YAJgNhURxId2E78k9rs= + base64-arraybuffer@0.1.5: version "0.1.5" resolved "https://registry.yarnpkg.com/base64-arraybuffer/-/base64-arraybuffer-0.1.5.tgz#73926771923b5a19747ad666aa5cd4bf9c6e9ce8" @@ -4842,6 +5629,24 @@ broccoli-babel-transpiler@^7.3.0, broccoli-babel-transpiler@^7.4.0: rsvp "^4.8.4" workerpool "^3.1.1" +broccoli-babel-transpiler@^7.5.0: + version "7.6.0" + resolved "https://registry.yarnpkg.com/broccoli-babel-transpiler/-/broccoli-babel-transpiler-7.6.0.tgz#819e6d228b8aa3b4f3b6dfc4b546c13dd304b462" + integrity sha512-VjqIl4qe3zEkGjsPA2jgw/jakjfLjw2IpQ62d4f1kArZyMW/cQ6o54VD0Cd6cai4J08nb4WPLu4cr4Z62vr5uw== + dependencies: + "@babel/core" "^7.10.2" + "@babel/polyfill" "^7.10.1" + broccoli-funnel "^2.0.2" + broccoli-merge-trees "^3.0.2" + broccoli-persistent-filter "^2.2.1" + clone "^2.1.2" + hash-for-dep "^1.4.7" + heimdalljs "^0.2.1" + heimdalljs-logger "^0.1.9" + json-stable-stringify "^1.0.1" + rsvp "^4.8.4" + workerpool "^3.1.1" + broccoli-builder@^0.18.14: version "0.18.14" resolved "https://registry.yarnpkg.com/broccoli-builder/-/broccoli-builder-0.18.14.tgz#4b79e2f844de11a4e1b816c3f49c6df4776c312d" @@ -4907,6 +5712,23 @@ broccoli-concat@^3.2.2, broccoli-concat@^3.7.1, broccoli-concat@^3.7.4, broccoli lodash.uniq "^4.2.0" walk-sync "^0.3.2" +broccoli-concat@^4.2.4: + version "4.2.4" + resolved "https://registry.yarnpkg.com/broccoli-concat/-/broccoli-concat-4.2.4.tgz#78e359ddc540b999d815355163bf3cfb6bd67322" + integrity sha512-NgdBIE57r+U/AslBohQr0mCS7PopIWL8dihMI1CzqffQkisAgqWMuddjYmizqRBQlml7crBFaBeUnPDHhf4/RQ== + dependencies: + broccoli-debug "^0.6.5" + broccoli-kitchen-sink-helpers "^0.3.1" + broccoli-plugin "^4.0.2" + ensure-posix-path "^1.0.2" + fast-sourcemap-concat "^2.1.0" + find-index "^1.1.0" + fs-extra "^8.1.0" + fs-tree-diff "^2.0.1" + lodash.merge "^4.6.2" + lodash.omit "^4.1.0" + lodash.uniq "^4.2.0" + broccoli-config-loader@^1.0.1: version "1.0.1" resolved "https://registry.yarnpkg.com/broccoli-config-loader/-/broccoli-config-loader-1.0.1.tgz#d10aaf8ebc0cb45c1da5baa82720e1d88d28c80a" @@ -4944,7 +5766,7 @@ broccoli-file-creator@^1.1.1: broccoli-plugin "^1.1.0" mkdirp "^0.5.1" -broccoli-file-creator@^2.1.1: +broccoli-file-creator@^2.0.0, broccoli-file-creator@^2.1.1: version "2.1.1" resolved "https://registry.yarnpkg.com/broccoli-file-creator/-/broccoli-file-creator-2.1.1.tgz#7351dd2496c762cfce7736ce9b49e3fce0c7b7db" integrity sha512-YpjOExWr92C5vhnK0kmD81kM7U09kdIRZk9w4ZDCDHuHXW+VE/x6AGEOQQW3loBQQ6Jk+k+TSm8dESy4uZsnjw== @@ -5016,7 +5838,7 @@ broccoli-funnel@^1.0.1: symlink-or-copy "^1.0.0" walk-sync "^0.3.1" -broccoli-funnel@^2.0.0, broccoli-funnel@^2.0.1, broccoli-funnel@^2.0.2, broccoli-funnel@~2.0.2: +"broccoli-funnel@^1.2.0 || ^2.0.0", broccoli-funnel@^2.0.0, broccoli-funnel@^2.0.1, broccoli-funnel@^2.0.2, broccoli-funnel@~2.0.2: version "2.0.2" resolved "https://registry.yarnpkg.com/broccoli-funnel/-/broccoli-funnel-2.0.2.tgz#0edf629569bc10bd02cc525f74b9a38e71366a75" integrity sha512-/vDTqtv7ipjEZQOVqO4vGDVAOZyuYzQ/EgGoyewfOgh1M7IQAToBKZI0oAQPgMBeFPPlIbfMuAngk+ohPBuaHQ== @@ -5096,7 +5918,7 @@ broccoli-merge-trees@^2.0.0: broccoli-plugin "^1.3.0" merge-trees "^1.0.1" -broccoli-merge-trees@^3.0.0, broccoli-merge-trees@^3.0.1, broccoli-merge-trees@^3.0.2, broccoli-merge-trees@~3.0.2: +"broccoli-merge-trees@^2.0.0 || ^3.0.0", broccoli-merge-trees@^3.0.0, broccoli-merge-trees@^3.0.1, broccoli-merge-trees@^3.0.2, broccoli-merge-trees@~3.0.2: version "3.0.2" resolved "https://registry.yarnpkg.com/broccoli-merge-trees/-/broccoli-merge-trees-3.0.2.tgz#f33b451994225522b5c9bcf27d59decfd8ba537d" integrity sha512-ZyPAwrOdlCddduFbsMyyFzJUrvW6b04pMvDiAQZrCwghlvgowJDY+EfoXn+eR1RRA5nmGHJ+B68T63VnpRiT1A== @@ -5684,6 +6506,13 @@ cachedir@2.2.0: resolved "https://registry.yarnpkg.com/cachedir/-/cachedir-2.2.0.tgz#19afa4305e05d79e417566882e0c8f960f62ff0e" integrity sha512-VvxA0xhNqIIfg0V9AmJkDg91DaJwryutH5rVEZAhcNi4iJFj9f+QxmAjgK1LT9I8OgToX27fypX6/MeCXVbBjQ== +calculate-cache-key-for-tree@^1.1.0: + version "1.2.3" + resolved "https://registry.yarnpkg.com/calculate-cache-key-for-tree/-/calculate-cache-key-for-tree-1.2.3.tgz#5a5e4fcfa2d374a63e47fe967593f179e8282825" + integrity sha512-PPQorvdNw8K8k7UftCeradwOmKDSDJs8wcqYTtJPEt3fHbZyK8QsorybJA+lOmk0dgE61vX6R+5Kd3W9h4EMGg== + dependencies: + json-stable-stringify "^1.0.1" + calculate-cache-key-for-tree@^2.0.0: version "2.0.0" resolved "https://registry.yarnpkg.com/calculate-cache-key-for-tree/-/calculate-cache-key-for-tree-2.0.0.tgz#7ac57f149a4188eacb0a45b210689215d3fef8d6" @@ -7522,6 +8351,38 @@ ember-cli-babel-plugin-helpers@^1.0.0, ember-cli-babel-plugin-helpers@^1.1.0: resolved "https://registry.yarnpkg.com/ember-cli-babel-plugin-helpers/-/ember-cli-babel-plugin-helpers-1.1.0.tgz#de3baedd093163b6c2461f95964888c1676325ac" integrity sha512-Zr4my8Xn+CzO0gIuFNXji0eTRml5AxZUTDQz/wsNJ5AJAtyFWCY4QtKdoELNNbiCVGt1lq5yLiwTm4scGKu6xA== +ember-cli-babel@7, ember-cli-babel@^7.1.0: + version "7.21.0" + resolved "https://registry.yarnpkg.com/ember-cli-babel/-/ember-cli-babel-7.21.0.tgz#c79e888876aee87dfc3260aee7cb580b74264bbc" + integrity sha512-jHVi9melAibo0DrAG3GAxid+29xEyjBoU53652B4qcu3Xp58feZGTH/JGXovH7TjvbeNn65zgNyoV3bk1onULw== + dependencies: + "@babel/core" "^7.10.2" + "@babel/helper-compilation-targets" "^7.10.2" + "@babel/plugin-proposal-class-properties" "^7.10.1" + "@babel/plugin-proposal-decorators" "^7.10.1" + "@babel/plugin-transform-modules-amd" "^7.10.1" + "@babel/plugin-transform-runtime" "^7.10.1" + "@babel/plugin-transform-typescript" "^7.10.1" + "@babel/polyfill" "^7.10.1" + "@babel/preset-env" "^7.10.2" + "@babel/runtime" "^7.10.2" + amd-name-resolver "^1.2.1" + babel-plugin-debug-macros "^0.3.3" + babel-plugin-ember-data-packages-polyfill "^0.1.2" + babel-plugin-ember-modules-api-polyfill "^2.13.4" + babel-plugin-module-resolver "^3.1.1" + broccoli-babel-transpiler "^7.5.0" + broccoli-debug "^0.6.4" + broccoli-funnel "^2.0.1" + broccoli-source "^1.1.0" + clone "^2.1.2" + ember-cli-babel-plugin-helpers "^1.1.0" + ember-cli-version-checker "^4.1.0" + ensure-posix-path "^1.0.2" + fixturify-project "^1.10.0" + rimraf "^3.0.1" + semver "^5.5.0" + ember-cli-babel@7.19.0: version "7.19.0" resolved "https://registry.yarnpkg.com/ember-cli-babel/-/ember-cli-babel-7.19.0.tgz#e6eddea18a867231fcf90a80689e92b98be9a63b" @@ -8053,7 +8914,7 @@ ember-cli@~3.16.1, ember-cli@~3.16.2: watch-detector "^1.0.0" yam "^1.0.0" -ember-compatibility-helpers@^1.1.1, ember-compatibility-helpers@^1.1.2, ember-compatibility-helpers@^1.2.0: +ember-compatibility-helpers@^1.1.1, ember-compatibility-helpers@^1.1.2, ember-compatibility-helpers@^1.2.0, ember-compatibility-helpers@^1.2.1: version "1.2.1" resolved "https://registry.yarnpkg.com/ember-compatibility-helpers/-/ember-compatibility-helpers-1.2.1.tgz#87c92c4303f990ff455c28ca39fb3ee11441aa16" integrity sha512-6wzYvnhg1ihQUT5yGqnLtleq3Nv5KNv79WhrEuNU9SwR4uIxCO+KpyC7r3d5VI0EM7/Nmv9Nd0yTkzmTMdVG1A== @@ -8062,13 +8923,35 @@ ember-compatibility-helpers@^1.1.1, ember-compatibility-helpers@^1.1.2, ember-co ember-cli-version-checker "^2.1.1" semver "^5.4.1" -ember-copy@^1.0.0: +ember-cookies@^0.5.0, ember-cookies@^0.5.2: + version "0.5.2" + resolved "https://registry.yarnpkg.com/ember-cookies/-/ember-cookies-0.5.2.tgz#06e33463f2f83254fefaf224cc944dec3fb9d3ba" + integrity sha512-nZ7oG97kBcO9UHjO95ryABpnVx62Bhxo7lIsBJNmWvFXleILm9DGueiAynzXxuYWWPuKIeeSbYakrS1869tNTw== + dependencies: + ember-cli-babel "^7.1.0" + ember-getowner-polyfill "^1.1.0 || ^2.0.0" + +ember-copy@1.0.0, ember-copy@^1.0.0: version "1.0.0" resolved "https://registry.yarnpkg.com/ember-copy/-/ember-copy-1.0.0.tgz#426554ba6cf65920f31d24d0a3ca2cb1be16e4aa" integrity sha512-aiZNAvOmdemHdvZNn0b5b/0d9g3JFpcOsrDgfhYEbfd7SzE0b69YiaVK2y3wjqfjuuiA54vOllGN4pjSzECNSw== dependencies: ember-cli-babel "^6.6.0" +ember-data-model-fragments@5.0.0-beta.0: + version "5.0.0-beta.0" + resolved "https://registry.yarnpkg.com/ember-data-model-fragments/-/ember-data-model-fragments-5.0.0-beta.0.tgz#da90799970317ca852f96b2ea1548ca70094a5bb" + integrity sha512-vjApz3ZWSiLyUUU2IRi/ArKVBJe+iW+BvKkiQ6an/e7ln4Jt06P9xXm1Fu3hfqkQOQlfn7QkUnlzm4XxRtlMfg== + dependencies: + broccoli-file-creator "^2.1.1" + broccoli-merge-trees "^3.0.0" + calculate-cache-key-for-tree "^1.1.0" + ember-cli-babel "7" + ember-compatibility-helpers "^1.2.1" + ember-copy "1.0.0" + git-repo-info "^2.0.0" + npm-git-info "^1.0.3" + ember-data@^3.16.0: version "3.18.0" resolved "https://registry.yarnpkg.com/ember-data/-/ember-data-3.18.0.tgz#e7c27e311b62f986e55075dc61ca581e7c74a4d3" @@ -8178,6 +9061,25 @@ ember-fetch@^7.0.0: node-fetch "^2.6.0" whatwg-fetch "^3.0.0" +ember-fetch@^8.0.1: + version "8.0.1" + resolved "https://registry.yarnpkg.com/ember-fetch/-/ember-fetch-8.0.1.tgz#dc57f3c3ba464e4a8722785f6b44265c46b38020" + integrity sha512-J+knuBFQJlMQUuij2tziMiVcJ+Ss0lCV2XiCQO2cHmG2PUsfvLlTGZ04uUyOXPqrJISxY2W3Et43fQBg4jy7SA== + dependencies: + abortcontroller-polyfill "^1.4.0" + broccoli-concat "^4.2.4" + broccoli-debug "^0.6.5" + broccoli-merge-trees "^4.2.0" + broccoli-rollup "^2.1.1" + broccoli-stew "^3.0.0" + broccoli-templater "^2.0.1" + calculate-cache-key-for-tree "^2.0.0" + caniuse-api "^3.0.0" + ember-cli-babel "^7.13.2" + ember-cli-typescript "^3.1.3" + node-fetch "^2.6.0" + whatwg-fetch "^3.0.0" + ember-get-config@0.2.4, ember-get-config@^0.2.2, ember-get-config@^0.2.4: version "0.2.4" resolved "https://registry.yarnpkg.com/ember-get-config/-/ember-get-config-0.2.4.tgz#118492a2a03d73e46004ed777928942021fe1ecd" @@ -8186,7 +9088,7 @@ ember-get-config@0.2.4, ember-get-config@^0.2.2, ember-get-config@^0.2.4: broccoli-file-creator "^1.1.1" ember-cli-babel "^6.3.0" -ember-getowner-polyfill@^2.0.0: +"ember-getowner-polyfill@^1.1.0 || ^2.0.0", ember-getowner-polyfill@^2.0.0: version "2.2.0" resolved "https://registry.yarnpkg.com/ember-getowner-polyfill/-/ember-getowner-polyfill-2.2.0.tgz#38e7dccbcac69d5ec694000329ec0b2be651d2b2" integrity sha512-rwGMJgbGzxIAiWYjdpAh04Abvt0s3HuS/VjHzUFhVyVg2pzAuz45B9AzOxYXzkp88vFC7FPaiA4kE8NxNk4A4Q== @@ -8394,6 +9296,20 @@ ember-router-generator@^2.0.0: "@babel/traverse" "^7.4.5" recast "^0.18.1" +ember-simple-auth@^3.0.0: + version "3.0.0" + resolved "https://registry.yarnpkg.com/ember-simple-auth/-/ember-simple-auth-3.0.0.tgz#d994f512cda1a8a5a6ef5a853fac60c0c0d6acd6" + integrity sha512-JaEw5W/Z9+HIH9I2BmE/JD4KofH4rp79Y51zJpt3UQI1OwWFOzFa+ulN0rK/DVLpGOz7uL7hBIE/rtEcEN1eqA== + dependencies: + base-64 "^0.1.0" + broccoli-file-creator "^2.0.0" + broccoli-funnel "^1.2.0 || ^2.0.0" + broccoli-merge-trees "^2.0.0 || ^3.0.0" + ember-cli-babel "^7.12.0" + ember-cli-is-package-missing "^1.0.0" + ember-cookies "^0.5.0" + silent-error "^1.0.0" + ember-source-channel-url@^1.0.1: version "1.2.0" resolved "https://registry.yarnpkg.com/ember-source-channel-url/-/ember-source-channel-url-1.2.0.tgz#77eb9d0889e5f5370e6c70fcb2696c63ff4a34a1" @@ -9356,6 +10272,20 @@ fast-sourcemap-concat@^1.4.0: source-map-url "^0.3.0" sourcemap-validator "^1.1.0" +fast-sourcemap-concat@^2.1.0: + version "2.1.0" + resolved "https://registry.yarnpkg.com/fast-sourcemap-concat/-/fast-sourcemap-concat-2.1.0.tgz#12dd36bfc38c804093e4bd1de61dd6216f574211" + integrity sha512-L9uADEnnHOeF4U5Kc3gzEs3oFpNCFkiTJXvT+nKmR0zcFqHZJJbszWT7dv4t9558FJRGpCj8UxUpTgz2zwiIZA== + dependencies: + chalk "^2.0.0" + fs-extra "^5.0.0" + heimdalljs-logger "^0.1.9" + memory-streams "^0.1.3" + mkdirp "^0.5.0" + source-map "^0.4.2" + source-map-url "^0.3.0" + sourcemap-validator "^1.1.0" + fastq@^1.6.0: version "1.8.0" resolved "https://registry.yarnpkg.com/fastq/-/fastq-1.8.0.tgz#550e1f9f59bbc65fe185cb6a9b4d95357107f481" @@ -10127,7 +11057,7 @@ git-repo-info@^1.4.1: resolved "https://registry.yarnpkg.com/git-repo-info/-/git-repo-info-1.4.1.tgz#2a072823254aaf62fcf0766007d7b6651bd41943" integrity sha1-KgcoIyVKr2L88HZgB9e2ZRvUGUM= -git-repo-info@^2.1.1: +git-repo-info@^2.0.0, git-repo-info@^2.1.1: version "2.1.1" resolved "https://registry.yarnpkg.com/git-repo-info/-/git-repo-info-2.1.1.tgz#220ffed8cbae74ef8a80e3052f2ccb5179aed058" integrity sha512-8aCohiDo4jwjOwma4FmYFd3i97urZulL8XL24nIPxuE+GZnfsAyy/g2Shqx6OjUiFKUXZM+Yy+KHnOmmA3FVcg== @@ -12758,6 +13688,11 @@ lodash@4.17.15, lodash@^4.0.0, lodash@^4.0.1, lodash@^4.15.0, lodash@^4.17.10, l resolved "https://registry.yarnpkg.com/lodash/-/lodash-4.17.15.tgz#b447f6670a0455bbfeedd11392eff330ea097548" integrity sha512-8xOcRHvCjnocdS5cpwXQXVzmmh5e5+saE2QGoeQmbKmRS6J3VQppPOIt0MnmE+4xlZoumy0GPG0D0MVIQbNA1A== +lodash@^4.17.19: + version "4.17.19" + resolved "https://registry.yarnpkg.com/lodash/-/lodash-4.17.19.tgz#e48ddedbe30b3321783c5b4301fbd353bc1e4a4b" + integrity sha512-JNvd8XER9GQX0v2qJgsaN/mzFCNA5BRe/j8JN9d+tWyGLSodKQHKFicdwNYzWwI3wjRnaKPsGj1XkBjx/F96DQ== + log-symbols@^2.2.0: version "2.2.0" resolved "https://registry.yarnpkg.com/log-symbols/-/log-symbols-2.2.0.tgz#5740e1c5d6f0dfda4ad9323b5332107ef6b4c40a"
{{t "auth.descriptions.authenticating-with-org" name=@model.scope.displayName}}
{{t 'descriptions.host-catalogs'}}
{{t "descriptions.host-catalogs"}}
{{hostCatalog.id}}
{{hostCatalog.description}}