feat: 🎸 add scope filtering to role/add-principals route (#1211)

* feat: 🎸 add scope filtering to role/add-principals route

* fix: 🐛 remove unnecessary helper

* feat: 🎸 add scope filtering to group add-members route
This commit is contained in:
Randall Morey authored and GitHub committed 2022-07-28 17:34:03 -07:00
1 parent f6e1fb0e3b
commit bfc433dc38
7 files changed
+279 -15

No files matched your search

@@ -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;
}
}
@@ -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;
}
}
@@ -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 = [];
}
}
@@ -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 = [];
}
}
+4 -2
View File
@@ -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;
}
}
@@ -15,6 +15,65 @@
</page.header>
<page.body>
{{#with
(resource-filter 'scopes.scope.groups.group.add-members' 'scope')
as |filter|
}}
<Rose::Toolbar as |toolbar|>
<toolbar.primary>
<Rose::Form as |form|>
<Rose::Dropdown
@text={{t (concat 'resources.' filter.name '.title')}}
@count={{filter.selectedValue.length}}
@ignoreClickInside={{true}}
as |dropdown|
>
<ul>
<form.checkboxGroup
@name={{filter.name}}
@items={{this.flatSortedScopes}}
@selectedItems={{filter.selectedValue}}
@onChange={{route-action 'filterBy' filter.name}}
as |group|
>
<li
class='{{if
group.item.isOrg
'indent-label-1'
(if group.item.isProject 'indent-label-2')
}}'
>
<dropdown.item>
<group.checkbox
@label={{group.item.displayName}}
value={{group.item}}
/>
</dropdown.item>
</li>
</form.checkboxGroup>
</ul>
</Rose::Dropdown>
{{#if
(has-resource-filter-selections
'scopes.scope.groups.group.add-members' 'scope'
)
}}
<Rose::Button
@style='inline-link-action'
@iconLeft='flight-icons/svg/x-16'
{{on 'click' (route-action 'clearAllFilters')}}
>{{t 'actions.clear-all-filters'}}
</Rose::Button>
{{/if}}
</Rose::Form>
</toolbar.primary>
</Rose::Toolbar>
{{/with}}
<br />
<Form::Group::AddMembers
@model={{@model.group}}
@users={{@model.users}}
@@ -15,6 +15,65 @@
</page.header>
<page.body>
{{#with
(resource-filter 'scopes.scope.roles.role.add-principals' 'scope')
as |filter|
}}
<Rose::Toolbar as |toolbar|>
<toolbar.primary>
<Rose::Form as |form|>
<Rose::Dropdown
@text={{t (concat 'resources.' filter.name '.title')}}
@count={{filter.selectedValue.length}}
@ignoreClickInside={{true}}
as |dropdown|
>
<ul>
<form.checkboxGroup
@name={{filter.name}}
@items={{this.flatSortedScopes}}
@selectedItems={{filter.selectedValue}}
@onChange={{route-action 'filterBy' filter.name}}
as |group|
>
<li
class='{{if
group.item.isOrg
'indent-label-1'
(if group.item.isProject 'indent-label-2')
}}'
>
<dropdown.item>
<group.checkbox
@label={{group.item.displayName}}
value={{group.item}}
/>
</dropdown.item>
</li>
</form.checkboxGroup>
</ul>
</Rose::Dropdown>
{{#if
(has-resource-filter-selections
'scopes.scope.roles.role.add-principals' 'scope'
)
}}
<Rose::Button
@style='inline-link-action'
@iconLeft='flight-icons/svg/x-16'
{{on 'click' (route-action 'clearAllFilters')}}
>{{t 'actions.clear-all-filters'}}
</Rose::Button>
{{/if}}
</Rose::Form>
</toolbar.primary>
</Rose::Toolbar>
{{/with}}
<br />
<Form::Role::AddPrincipals
@model={{@model.role}}
@users={{@model.users}}