Skip to content

Commit 9c32aa2

Browse files
committed
feat(dashboard): support group semantics in dashboard
Signed-off-by: Adrian Azoitei <azo.adrian@gmail.com>
1 parent 64297c4 commit 9c32aa2

6 files changed

Lines changed: 180 additions & 41 deletions

File tree

components/centraldashboard/app/api_workgroup.ts

Lines changed: 23 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,7 @@ const EMAIL_RGX = /^[a-zA-Z0-9.!#$%&'*+\/=?^_`{|}~-]+@[a-zA-Z0-9](?:[a-zA-Z0-9-]
1313

1414
// Valid actions for handling a contributor
1515
type ContributorActions = 'create' | 'remove';
16+
type ContributorType = 'user' | 'group';
1617

1718
interface CreateProfileRequest {
1819
namespace?: string;
@@ -21,6 +22,7 @@ interface CreateProfileRequest {
2122

2223
interface AddOrRemoveContributorRequest {
2324
contributor?: string;
25+
cType?: ContributorType;
2426
}
2527

2628
interface HasWorkgroupResponse {
@@ -53,6 +55,7 @@ export interface SimpleBinding {
5355
namespace: string;
5456
role: SimpleRole;
5557
subject: string;
58+
kind?: string;
5659
}
5760

5861
export interface WorkgroupInfo {
@@ -68,6 +71,7 @@ export function mapWorkgroupBindingToSimpleBinding (bindings: WorkgroupBinding[]
6871
subject: n.subject.name,
6972
namespace: n.referredNamespace,
7073
role: roleMap.get(n.roleRef.name as Role) as SimpleRole,
74+
kind: n.subject.kind,
7175
}));
7276
}
7377

@@ -87,10 +91,10 @@ export function mapNamespacesToSimpleBinding (subject: string, namespaces: V1Nam
8791
* Converts SimpleBinding to Workgroup Binding from Profile Controller
8892
*/
8993
export function mapSimpleBindingToWorkgroupBinding (binding: SimpleBinding): WorkgroupBinding {
90-
const {subject, namespace, role} = binding;
94+
const {subject, namespace, role, kind: subjectKind} = binding;
9195
return {
9296
subject: {
93-
kind: 'User',
97+
kind: subjectKind.charAt(0).toUpperCase() + subjectKind.slice(1),
9498
name: subject,
9599
},
96100
referredNamespace: namespace,
@@ -191,7 +195,7 @@ export class WorkgroupApi {
191195
}
192196
async handleContributor(action: ContributorActions, req: Request, res: Response) {
193197
const {namespace} = req.params;
194-
const {contributor} = req.body as AddOrRemoveContributorRequest;
198+
const {contributor, cType} = req.body as AddOrRemoveContributorRequest;
195199
const {profilesService} = this;
196200
if (!contributor || !namespace) {
197201
const missing = [];
@@ -204,7 +208,7 @@ export class WorkgroupApi {
204208
error: `Missing ${missing.join(' and ')} field${missing.length-1?'s':''}.`,
205209
});
206210
}
207-
if (!EMAIL_RGX.test(contributor)) {
211+
if (cType === "user" && !EMAIL_RGX.test(contributor)) {
208212
return apiError({
209213
res,
210214
error: `Contributor doesn't look like a valid email address`,
@@ -214,6 +218,7 @@ export class WorkgroupApi {
214218
try {
215219
const binding = mapSimpleBindingToWorkgroupBinding({
216220
subject: contributor,
221+
kind: cType,
217222
namespace,
218223
role: 'contributor',
219224
});
@@ -226,8 +231,8 @@ export class WorkgroupApi {
226231
const actionAPI = action === 'create' ? 'createBinding' : 'deleteBinding';
227232
await profilesService[actionAPI](binding, {headers});
228233
errIndex++;
229-
const users = await this.getContributors(namespace);
230-
res.json(users);
234+
const contributors = await this.getContributors(namespace);
235+
res.json(contributors);
231236
} catch (err) {
232237
const errMessage = [
233238
`Unable to add new contributor for ${namespace}. HTTP ${err.response.statusCode || '???'} - ${err.response.statusMessage || 'Unknown'}`,
@@ -244,13 +249,19 @@ export class WorkgroupApi {
244249
* Given an owned namespace, list all contributors under it
245250
* @param namespace Namespace to find contributors for
246251
*/
247-
async getContributors(namespace: string) {
252+
async getContributors(namespace: string): Promise<Array<{name: string, kind?: string}>> {
248253
const {body} = await this.profilesService
249254
.readBindings(undefined, namespace);
250-
const users = mapWorkgroupBindingToSimpleBinding(body.bindings)
255+
const simpleBindings = mapWorkgroupBindingToSimpleBinding(body.bindings);
256+
console.log(simpleBindings);
257+
const contributors = simpleBindings
251258
.filter((b) => b.role === 'contributor')
252-
.map((b) => b.subject);
253-
return users;
259+
.map((b) => ({
260+
name: b.subject,
261+
kind: b.kind,
262+
}));
263+
console.log(contributors);
264+
return contributors;
254265
}
255266
routes() {return Router()
256267
.get('/exists', async (req: Request, res: Response) => {
@@ -374,8 +385,8 @@ export class WorkgroupApi {
374385
.get('/get-contributors/:namespace', async (req: Request, res: Response) => {
375386
const {namespace} = req.params;
376387
try {
377-
const users = await this.getContributors(namespace);
378-
res.json(users);
388+
const contributors = await this.getContributors(namespace);
389+
res.json(contributors);
379390
} catch (err) {
380391
surfaceProfileControllerErrors({
381392
res,

components/centraldashboard/app/api_workgroup_test.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -152,6 +152,7 @@ describe('Workgroup API', () => {
152152
{
153153
subject: 'test@testdomain.com',
154154
namespace: 'test',
155+
kind: 'user',
155156
role: 'contributor',
156157
},
157158
],

components/centraldashboard/public/components/logout-button.js

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
import { html, PolymerElement } from '@polymer/polymer/polymer-element.js';
1+
import {html, PolymerElement} from '@polymer/polymer/polymer-element.js';
22
import '@polymer/paper-button/paper-button.js';
33
import css from './logout-button.css';
44

@@ -18,7 +18,6 @@ export class LogoutButton extends PolymerElement {
1818
</paper-button>
1919
</a>
2020
`]);
21-
;
2221
}
2322

2423
/**

components/centraldashboard/public/components/manage-users-view-contributor.js

Lines changed: 51 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -31,6 +31,9 @@ export class ManageUsersViewContributor extends utilitiesMixin(PolymerElement) {
3131
user: {type: String, value: 'Loading...'},
3232
ownedNamespace: {type: Object, value: () => ({})},
3333
newContribEmail: String,
34+
newGroupName: String,
35+
userContributorList: {type: Array, value: () => []},
36+
groupContributorList: {type: Array, value: () => []},
3437
contribError: Object,
3538
contributorInputEl: Object,
3639
};
@@ -44,23 +47,51 @@ export class ManageUsersViewContributor extends utilitiesMixin(PolymerElement) {
4447
}
4548

4649
/**
47-
* Triggers an API call to create a new Contributor
50+
* Triggers an API call to create a new user Contributor
4851
*/
4952
addNewContrib() {
50-
// Need to call the api directly here.
5153
const api = this.$.AddContribAjax;
52-
api.body = {contributor: this.newContribEmail};
54+
api.body = {contributor: this.newContribEmail, cType: 'user'};
5355
api.generateRequest();
5456
}
5557
/**
56-
* Triggers an API call to remove a Contributor
58+
* Triggers an API call to create a new group Contributor
59+
*/
60+
addNewGroupContrib() {
61+
const api = this.$.AddContribAjax;
62+
api.body = {contributor: this.newGroupName, cType: 'group'};
63+
api.generateRequest();
64+
}
65+
/**
66+
* Triggers an API call to remove a user Contributor
5767
* @param {Event} e
5868
*/
5969
removeContributor(e) {
6070
const api = this.$.RemoveContribAjax;
61-
api.body = {contributor: e.model.item};
71+
api.body = {contributor: e.model.item, cType: 'user'};
72+
api.generateRequest();
73+
}
74+
/**
75+
* Triggers an API call to remove a group Contributor
76+
* @param {Event} e
77+
*/
78+
removeGroupContributor(e) {
79+
const api = this.$.RemoveContribAjax;
80+
api.body = {contributor: e.model.item, cType: 'group'};
6281
api.generateRequest();
6382
}
83+
/**
84+
* Splits a contributors response array into user and group lists.
85+
* @param {Array} contribs
86+
*/
87+
_updateContributorLists(contribs) {
88+
this.groupContributorList = contribs
89+
.filter((c) => c.kind && c.kind.toLowerCase() === 'group')
90+
.map((c) => c.name);
91+
this.userContributorList = contribs
92+
.filter((c) => c.kind && c.kind.toLowerCase() === 'user')
93+
.map((c) => c.name);
94+
}
6495
/**
6596
* Takes an event from iron-ajax and isolates the error from a request that
6697
* failed
@@ -81,8 +112,8 @@ export class ManageUsersViewContributor extends utilitiesMixin(PolymerElement) {
81112
this.contribCreateError = error;
82113
return;
83114
}
84-
this.contributorList = e.detail.response;
85-
this.newContribEmail = this.contribCreateError = '';
115+
this._updateContributorLists(e.detail.response);
116+
this.newContribEmail = this.newGroupName = this.contribCreateError = '';
86117
}
87118
/**
88119
* Iron-Ajax response / error handler for removeContributor
@@ -94,8 +125,19 @@ export class ManageUsersViewContributor extends utilitiesMixin(PolymerElement) {
94125
this.contribCreateError = error;
95126
return;
96127
}
97-
this.contributorList = e.detail.response;
98-
this.newContribEmail = this.contribCreateError = '';
128+
this._updateContributorLists(e.detail.response);
129+
this.newContribEmail = this.newGroupName = this.contribCreateError = '';
130+
}
131+
/**
132+
* Iron-Ajax response handler for getContributors
133+
* @param {IronAjaxEvent} e
134+
*/
135+
handleContribFetch(e) {
136+
if (e.detail.error) {
137+
this.onContribFetchError(e);
138+
return;
139+
}
140+
this._updateContributorLists(e.detail.response);
99141
}
100142
/**
101143
* Iron-Ajax error handler for getContributors

components/centraldashboard/public/components/manage-users-view-contributor.pug

Lines changed: 9 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -3,18 +3,24 @@ iron-ajax#RemoveContribAjax(method='DELETE', url='/api/workgroup/remove-contribu
33
iron-ajax#AddContribAjax(method='POST', url='/api/workgroup/add-contributor/[[ownedNamespace.namespace]]',
44
on-response='handleContribCreate', on-error='handleContribCreate', handle-as='json', content-type='application/json')
55
iron-ajax#GetContribsAjax(auto='[[!empty(ownedNamespace)]]', url='/api/workgroup/get-contributors/[[ownedNamespace.namespace]]',
6-
last-response='{{contributorList}}', on-error='onContribFetchError', handle-as='json')
6+
on-response='handleContribFetch', on-error='onContribFetchError', handle-as='json')
77
h2
88
iron-icon.icon(icon='kubeflow:account-group')
99
span.text
1010
| Contributors for -
1111
|
1212
code [[ownedNamespace.namespace]]
1313
.content.small
14-
md2-input(label='Email Addresses', value='{{newContribEmail}}', on-submit='addNewContrib', placeholder='Add by email address', error$='[[contribCreateError]]')
14+
h3 Users
15+
md2-input(label='Email Addresses', value='{{newContribEmail}}', on-submit='addNewContrib', placeholder='Add user by email address', error$='[[contribCreateError]]')
1516
.prefix(slot='prefix')
16-
template(is='dom-repeat', items='[[contributorList]]')
17+
template(is='dom-repeat', items='[[userContributorList]]')
1718
paper-chip(on-remove='removeContributor') [[item]]
19+
h3 Groups
20+
md2-input(label='Groups', value='{{newGroupName}}', on-submit='addNewGroupContrib', placeholder='Add group by name', error$='[[contribCreateError]]')
21+
.prefix(slot='prefix')
22+
template(is='dom-repeat', items='[[groupContributorList]]')
23+
paper-chip(on-remove='removeGroupContributor') [[item]]
1824
paper-toast#ContribError(duration=5000)
1925
| Failed to fetch contributor list for {{ownedNamespace.namespace}}, because:
2026
strong [[contribError]]

0 commit comments

Comments
 (0)