From bfc433dc3842a4762f72d7e8f2d4b37b50ba280d Mon Sep 17 00:00:00 2001 From: Randall Morey Date: Thu, 28 Jul 2022 17:34:03 -0700 Subject: [PATCH] =?UTF-8?q?feat:=20=F0=9F=8E=B8=20add=20scope=20filtering?= =?UTF-8?q?=20to=20role/add-principals=20route=20(#1211)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * feat: 🎸 add scope filtering to role/add-principals route * fix: 🐛 remove unnecessary helper * feat: 🎸 add scope filtering to group add-members route --- .../scopes/scope/groups/group/add-members.js | 17 +++++ .../scopes/scope/roles/role/add-principals.js | 17 +++++ .../scopes/scope/groups/group/add-members.js | 63 ++++++++++++++-- .../scopes/scope/roles/role/add-principals.js | 73 +++++++++++++++++-- ui/admin/app/styles/app.scss | 6 +- .../scopes/scope/groups/group/add-members.hbs | 59 +++++++++++++++ .../scope/roles/role/add-principals.hbs | 59 +++++++++++++++ 7 files changed, 279 insertions(+), 15 deletions(-) diff --git a/ui/admin/app/controllers/scopes/scope/groups/group/add-members.js b/ui/admin/app/controllers/scopes/scope/groups/group/add-members.js index 6ce53c655..18e401dcd 100644 --- a/ui/admin/app/controllers/scopes/scope/groups/group/add-members.js +++ b/ui/admin/app/controllers/scopes/scope/groups/group/add-members.js @@ -15,4 +15,21 @@ export default class ScopesScopeGroupsGroupAddMembersController extends Controll get breadCrumb() { return this.intl.t('resources.group.messages.add-members.title'); } + + /** + * Returns a flat array of scopes, sorted and "grouped by" parent scope, + * beginning with global. + */ + get flatSortedScopes() { + const { scopes } = this.model; + const global = scopes.find((scope) => scope.isGlobal); + const orgsAndProjects = scopes + .filter((scope) => scope.isOrg) + .map((org) => [ + org, + ...scopes.filter((scope) => scope.scopeID === org.id), + ]); + const sorted = [global, ...orgsAndProjects].flat(); + return sorted; + } } diff --git a/ui/admin/app/controllers/scopes/scope/roles/role/add-principals.js b/ui/admin/app/controllers/scopes/scope/roles/role/add-principals.js index 48dc430b0..6cf1157ce 100644 --- a/ui/admin/app/controllers/scopes/scope/roles/role/add-principals.js +++ b/ui/admin/app/controllers/scopes/scope/roles/role/add-principals.js @@ -15,4 +15,21 @@ export default class ScopesScopeRolesRoleAddPrincipalsController extends Control get breadCrumb() { return this.intl.t('resources.role.principal.actions.add-principals'); } + + /** + * Returns a flat array of scopes, sorted and "grouped by" parent scope, + * beginning with global. + */ + get flatSortedScopes() { + const { scopes } = this.model; + const global = scopes.find((scope) => scope.isGlobal); + const orgsAndProjects = scopes + .filter((scope) => scope.isOrg) + .map((org) => [ + org, + ...scopes.filter((scope) => scope.scopeID === org.id), + ]); + const sorted = [global, ...orgsAndProjects].flat(); + return sorted; + } } diff --git a/ui/admin/app/routes/scopes/scope/groups/group/add-members.js b/ui/admin/app/routes/scopes/scope/groups/group/add-members.js index 4906130cd..66df5a849 100644 --- a/ui/admin/app/routes/scopes/scope/groups/group/add-members.js +++ b/ui/admin/app/routes/scopes/scope/groups/group/add-members.js @@ -4,6 +4,7 @@ import { action } from '@ember/object'; import { hash } from 'rsvp'; import { loading } from 'ember-loading'; import { notifySuccess, notifyError } from 'core/decorators/notify'; +import { resourceFilter } from 'core/decorators/resource-filter'; export default class ScopesScopeGroupsGroupAddMembersRoute extends Route { // =services @@ -11,14 +12,27 @@ export default class ScopesScopeGroupsGroupAddMembersRoute extends Route { @service intl; @service notify; @service router; + @service store; + @service resourceFilterStore; + + // =attributes + + @resourceFilter({ + allowed: (route) => route.store.peekAll('scope').toArray(), + serialize: ({ id }) => id, + findBySerialized: ({ id }, value) => id === value, + }) + scope; // =methods /** - * Emtpy out any previously loaded users. + * Preload all scopes recursively, but allow this to fail. */ - beforeModel() { - this.store.unloadAll('user'); + async beforeModel() { + await this.store + .query('scope', { scope_id: 'global', recursive: true }) + .catch(() => {}); } /** @@ -26,11 +40,26 @@ export default class ScopesScopeGroupsGroupAddMembersRoute extends Route { * @return {Promise{GroupModel, [UserModel]}} */ model() { - return hash({ - group: this.modelFor('scopes.scope.groups.group'), - // load all users from all scopes - users: this.store.query('user', { scope_id: 'global', recursive: true }), - }); + const group = this.modelFor('scopes.scope.groups.group'); + // filter out projects, since the user resource exists only on org and above + const scopes = this.store + .peekAll('scope') + .filter((scope) => !scope.isProject) + .toArray(); + const scopeIDs = this.scope?.map((scope) => scope.id); + const users = scopeIDs?.length + ? this.resourceFilterStore.queryBy( + 'user', + { + scope_id: scopeIDs, + }, + { + scope_id: 'global', + recursive: true, + } + ) + : this.store.query('user', { scope_id: 'global', recursive: true }); + return hash({ group, users, scopes }); } // =actions @@ -57,4 +86,22 @@ export default class ScopesScopeGroupsGroupAddMembersRoute extends Route { cancel() { this.router.replaceWith('scopes.scope.groups.group.members'); } + + /** + * Sets the specified resource filter field to the specified value. + * @param {string} field + * @param value + */ + @action + filterBy(field, value) { + this[field] = value; + } + + /** + * Clears and filter selections. + */ + @action + clearAllFilters() { + this.scope = []; + } } diff --git a/ui/admin/app/routes/scopes/scope/roles/role/add-principals.js b/ui/admin/app/routes/scopes/scope/roles/role/add-principals.js index af1b96692..f900f0c33 100644 --- a/ui/admin/app/routes/scopes/scope/roles/role/add-principals.js +++ b/ui/admin/app/routes/scopes/scope/roles/role/add-principals.js @@ -4,6 +4,7 @@ import { action } from '@ember/object'; import { hash } from 'rsvp'; import { loading } from 'ember-loading'; import { notifySuccess, notifyError } from 'core/decorators/notify'; +import { resourceFilter } from 'core/decorators/resource-filter'; export default class ScopesScopeRolesRoleAddPrincipalsRoute extends Route { // =services @@ -11,22 +12,66 @@ export default class ScopesScopeRolesRoleAddPrincipalsRoute extends Route { @service intl; @service notify; @service router; + @service store; + @service resourceFilterStore; + + // =attributes + + @resourceFilter({ + allowed: (route) => route.store.peekAll('scope').toArray(), + serialize: ({ id }) => id, + findBySerialized: ({ id }, value) => id === value, + }) + scope; // =methods + /** + * Preload all scopes recursively, but allow this to fail. + */ + async beforeModel() { + await this.store + .query('scope', { scope_id: 'global', recursive: true }) + .catch(() => {}); + } + /** * Returns the current role, all users, and all groups * @return {{role: RoleModel, users: [UserModel], groups: [GroupModel]}} */ model() { const role = this.modelFor('scopes.scope.roles.role'); + const scopes = this.store.peekAll('scope').toArray(); + const scopeIDs = this.scope?.map((scope) => scope.id); + const users = scopeIDs?.length + ? this.resourceFilterStore.queryBy( + 'user', + { + scope_id: scopeIDs, + }, + { + scope_id: 'global', + recursive: true, + } + ) + : this.store.query('user', { scope_id: 'global', recursive: true }); + const groups = scopeIDs?.length + ? this.resourceFilterStore.queryBy( + 'group', + { + scope_id: scopeIDs, + }, + { + scope_id: 'global', + recursive: true, + } + ) + : this.store.query('group', { scope_id: 'global', recursive: true }); return hash({ role, - users: this.store.query('user', { scope_id: 'global', recursive: true }), - groups: this.store.query('group', { - scope_id: 'global', - recursive: true, - }), + scopes, + users, + groups, }); } @@ -53,4 +98,22 @@ export default class ScopesScopeRolesRoleAddPrincipalsRoute extends Route { cancel() { this.router.replaceWith('scopes.scope.roles.role.principals'); } + + /** + * Sets the specified resource filter field to the specified value. + * @param {string} field + * @param value + */ + @action + filterBy(field, value) { + this[field] = value; + } + + /** + * Clears and filter selections. + */ + @action + clearAllFilters() { + this.scope = []; + } } diff --git a/ui/admin/app/styles/app.scss b/ui/admin/app/styles/app.scss index 0836c7c5b..5dbef9350 100644 --- a/ui/admin/app/styles/app.scss +++ b/ui/admin/app/styles/app.scss @@ -62,13 +62,15 @@ } .indent-label-1 { - .rose-form-radio-label { + .rose-form-radio-label, + .rose-form-checkbox-label { padding-left: $indent; } } .indent-label-2 { - .rose-form-radio-label { + .rose-form-radio-label, + .rose-form-checkbox-label { padding-left: $indent + $icon-width; } } diff --git a/ui/admin/app/templates/scopes/scope/groups/group/add-members.hbs b/ui/admin/app/templates/scopes/scope/groups/group/add-members.hbs index 3013ba2f7..69b9863f8 100644 --- a/ui/admin/app/templates/scopes/scope/groups/group/add-members.hbs +++ b/ui/admin/app/templates/scopes/scope/groups/group/add-members.hbs @@ -15,6 +15,65 @@ + + {{#with + (resource-filter 'scopes.scope.groups.group.add-members' 'scope') + as |filter| + }} + + + + +
    + +
  • + + + +
  • +
    +
+
+ + {{#if + (has-resource-filter-selections + 'scopes.scope.groups.group.add-members' 'scope' + ) + }} + {{t 'actions.clear-all-filters'}} + + {{/if}} +
+
+
+ {{/with}} + +
+ + + {{#with + (resource-filter 'scopes.scope.roles.role.add-principals' 'scope') + as |filter| + }} + + + + +
    + +
  • + + + +
  • +
    +
+
+ + {{#if + (has-resource-filter-selections + 'scopes.scope.roles.role.add-principals' 'scope' + ) + }} + {{t 'actions.clear-all-filters'}} + + {{/if}} +
+
+
+ {{/with}} + +
+