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:
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 = [];
|
||||
}
|
||||
}
|
||||
@@ -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}}
|
||||
|
||||
Reference in new issue
Block a user