Skip to content

Commit

Permalink
feat: home page (#6)
Browse files Browse the repository at this point in the history
* feat: integration with backend

* fix: update user page

---------

Co-authored-by: Aleksei Moiseev <[email protected]>
  • Loading branch information
kharkevich and Aleksei Moiseev authored Apr 6, 2024
1 parent f093832 commit e0a2994
Show file tree
Hide file tree
Showing 13 changed files with 221 additions and 71 deletions.
11 changes: 10 additions & 1 deletion mlflow_oidc_auth/views.py
Original file line number Diff line number Diff line change
Expand Up @@ -367,7 +367,16 @@ def create_access_token():

def get_current_user():
user = store.get_user(_get_username())
return jsonify(user.to_json())
user_json = user.to_json()
user_json["experiment_permissions"] = [
{
"name": mlflow_client.get_experiment(permission.experiment_id).name,
"id": permission.experiment_id,
"permission": permission.permission
}
for permission in user.experiment_permissions
]
return jsonify(user_json)


def update_username_password():
Expand Down
1 change: 1 addition & 0 deletions web-ui/package.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
{
"name": "mlflow-oidc-auth-front",
"version": "0.0.0",
"license":"Apache-2.0",
"scripts": {
"ng": "ng",
"start": "ng serve",
Expand Down
10 changes: 6 additions & 4 deletions web-ui/src/app/app.component.ts
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@ import { AuthService, DataService } from './shared/services';
export class AppComponent implements OnInit {
title = 'mlflow-oidc-auth-front';

name: string = 'Alex';
name: string = '';

constructor(
private readonly dataService: DataService,
Expand All @@ -18,8 +18,10 @@ export class AppComponent implements OnInit {
}

ngOnInit(): void {
this.dataService.getCurrentUser().subscribe(({ username }) => {
this.authService.setUser(username);
});
this.dataService.getCurrentUser()
.subscribe((userInfo) => {
this.authService.setUserInfo(userInfo);
this.name = userInfo.display_name;
});
}
}
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
<div class="my-3">
<div class="header-section">
<h3>Model Access</h3>
<button mat-button (click)="add()">
<button mat-button (click)="addModelPermissionToUser()">
<mat-icon>add</mat-icon>
<span>Add</span>
</button>
Expand All @@ -11,14 +11,14 @@ <h3>Model Access</h3>
[columnConfig]="modelColumnConfig"
[data]="modelDataSource"
[isActionsActive]="true"
(editEvent)="handleUserEditForModel($event)"
(editEvent)="handleUserEditForModel()"
></ml-table>
</div>

<div class="my-3">
<div class="header-section">
<h3>Experiment Access</h3>
<button mat-button>
<button mat-button (click)="addExperimentPermissionToUser()">
<mat-icon>add</mat-icon>
<span>Add</span>
</button>
Expand All @@ -28,6 +28,6 @@ <h3>Experiment Access</h3>
[columnConfig]="experimentColumnConfig"
[data]="experimentDataSource"
[isActionsActive]="true"
(editEvent)="handleUserEditForExperiment($event)"
(editEvent)="handleUserEditForExperiment()"
></ml-table>
</div>
Original file line number Diff line number Diff line change
@@ -1,14 +1,21 @@
import { Component, OnInit } from '@angular/core';
import { ActivatedRoute, Router } from '@angular/router';
import { MatDialog } from '@angular/material/dialog';
import { EditPermissionsModalComponent, GrantPermissionModalComponent } from '../../../../../shared/components';
import {
EditPermissionsModalComponent,
GrantPermissionModalComponent,
GrantPermissionModalData,
} from '../../../../../shared/components';
import { DataService } from '../../../../../shared/services';
import { filter, switchMap } from 'rxjs';
import { ActivatedRoute } from '@angular/router';

@Component({
selector: 'ml-user-permission-details',
templateUrl: './user-permission-details.component.html',
styleUrls: ['./user-permission-details.component.scss'],
})
export class UserPermissionDetailsComponent implements OnInit {
userId: string | null = null;
modelColumnConfig = [
{
title: 'Modal name',
Expand Down Expand Up @@ -55,17 +62,18 @@ export class UserPermissionDetailsComponent implements OnInit {


constructor(
private router: Router,
private route: ActivatedRoute,
public dialog: MatDialog,
private readonly dialog: MatDialog,
private readonly dataService: DataService,
private readonly route: ActivatedRoute,
) {
}

ngOnInit(): void {
this.userId = this.route.snapshot.paramMap.get('id');
}


handleUserEditForModel($event: any) {
handleUserEditForModel() {
this.dialog
.open(EditPermissionsModalComponent)
.afterClosed()
Expand All @@ -74,7 +82,7 @@ export class UserPermissionDetailsComponent implements OnInit {
})
}

handleUserEditForExperiment($event: any) {
handleUserEditForExperiment() {
this.dialog
.open(EditPermissionsModalComponent)
.afterClosed()
Expand All @@ -88,4 +96,50 @@ export class UserPermissionDetailsComponent implements OnInit {
.afterClosed()
.subscribe(console.log);
}

addModelPermissionToUser() {
this.dataService.getAllModels()
.pipe(
switchMap(({ models }) => this.dialog.open<GrantPermissionModalComponent, GrantPermissionModalData>(GrantPermissionModalComponent, {
data: {
type: 'model',
entities: models,
userName: this.userId ? this.userId : '',
}
})
.afterClosed()),
filter(Boolean),
)
.subscribe((data) => {
const { entity, permission, user } = data;
this.dataService.createModelPermission({
user_name: user,
model_name: entity,
new_permission: permission,
}).subscribe();
});
}

addExperimentPermissionToUser() {
this.dataService.getAllExperiments()
.pipe(
switchMap(({ experiments }) => this.dialog.open<GrantPermissionModalComponent, GrantPermissionModalData>(GrantPermissionModalComponent, {
data: {
type: 'experiment',
entities: experiments,
userName: this.userId ? this.userId : '',
}
})
.afterClosed()),
filter(Boolean),
)
.subscribe((data) => {
const { entity, permission, user } = data;
this.dataService.createExperimentPermission({
user_name: user,
experiment_name: entity,
new_permission: permission,
}).subscribe();
});
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -8,13 +8,26 @@
</div>

<div class="content d-flex flex-column gap-3 py-3" *ngIf="!loading; else loader">
<ml-table [columnConfig]="experimentsColumnConfig" [data]="experimentsDataSource"></ml-table>
<ml-table [columnConfig]="modelsColumnConfig" [data]="modelsDataSource"></ml-table>
<h4>Experiments</h4>
<ml-table [columnConfig]="experimentsColumnConfig" [data]="experimentsDataSource" *ngIf="experimentsDataSource.length; else noExperimentData"></ml-table>
<h4>Models</h4>
<ml-table [columnConfig]="modelsColumnConfig" [data]="modelsDataSource" *ngIf="modelsDataSource.length; else noModelData"></ml-table>
</div>


<ng-template #loader>
<div class="d-flex w-100 justify-content-center py-5">
<mat-spinner color="primary"></mat-spinner>
</div>
</ng-template>

<ng-template #noExperimentData>
<div class="d-flex w-100 justify-content-center py-5">
<span>You have no experiments</span>
</div>
</ng-template>

<ng-template #noModelData>
<div class="d-flex w-100 justify-content-center py-5">
<span>You have no models</span>
</div>
</ng-template>
Original file line number Diff line number Diff line change
Expand Up @@ -6,14 +6,15 @@ import {
} from '../../../../shared/components';
import { finalize, forkJoin, switchMap } from 'rxjs';
import { AuthService, DataService } from '../../../../shared/services';
import { ExperimentModel, ModelModel, UserResponseModel } from '../../../../shared/interfaces/data.interfaces';

@Component({
selector: 'ml-home-page',
templateUrl: './home-page.component.html',
styleUrls: ['./home-page.component.scss'],
})
export class HomePageComponent implements OnInit {
currentUser?: string;
currentUserInfo: UserResponseModel | null = null;
loading = false;
experimentsColumnConfig = [
{
Expand All @@ -22,22 +23,22 @@ export class HomePageComponent implements OnInit {
},
{
title: 'Permissions',
key: 'permissions',
key: 'permission',
},
];
experimentsDataSource = [];

modelsColumnConfig = [
{
title: 'Model name',
key: 'name',
},
{
title: 'Permissions',
key: 'permissions',
key: 'permission',
},
];
modelsDataSource = [];
experimentsDataSource: ExperimentModel[] = [];

modelsDataSource: ModelModel[] = [];

constructor(
private readonly dialog: MatDialog,
Expand All @@ -47,21 +48,15 @@ export class HomePageComponent implements OnInit {
}

ngOnInit(): void {
this.currentUser = this.authService.getUser();
this.currentUserInfo = this.authService.getUserInfo();

if (this.currentUser) {
this.loading = true;
forkJoin([
this.dataService.getExperimentsForUser(this.currentUser),
this.dataService.getModelsForUser(this.currentUser),
])
.pipe(
finalize(() => this.loading = false),
)
.subscribe(([experiments, models]) => {
this.experimentsDataSource = experiments;
this.modelsDataSource = models;
});
if (this.currentUserInfo) {
const { username } = this.currentUserInfo;

if (username) {
this.experimentsDataSource = this.currentUserInfo.experiment_permissions;
this.modelsDataSource = this.currentUserInfo.registered_model_permissions;
}
}
}

Expand Down
Original file line number Diff line number Diff line change
@@ -1,22 +1,26 @@
<h2 mat-dialog-title>"User name" permissions for model/experiment "name"</h2>
<mat-dialog-content>
<p>Permissions</p>
<mat-form-field>
<mat-label>Exp</mat-label>
<select matNativeControl required>
<option value="manage">Exp 1</option>
</select>
</mat-form-field>
<mat-form-field>
<mat-label>Permissions</mat-label>
<select matNativeControl required>
<option value="manage">Manage</option>
<option value="read">Read</option>
<option value="edit">Edit</option>
</select>
</mat-form-field>
<h2 mat-dialog-title>Add {{data.type}} permissions for {{data.userName}}</h2>
<mat-dialog-content [formGroup]="form">
<div class="d-flex flex-column">
<mat-form-field>
<mat-label>{{data.type | titlecase}}</mat-label>
<select matNativeControl formControlName="entity">
<ng-container *ngFor="let entity of data.entities">
<option value="{{entity}}">{{entity}}</option>
</ng-container>
</select>
</mat-form-field>
<mat-form-field>
<mat-label>Permissions</mat-label>
<select matNativeControl formControlName="permission">
<option value="MANAGE">Manage</option>
<option value="READ">Read</option>
<option value="EDIT">Edit</option>
</select>
</mat-form-field>
</div>

</mat-dialog-content>
<mat-dialog-actions>
<button mat-button (click)="onNoClick()">Cancel</button>
<button mat-button [mat-dialog-close]="null" cdkFocusInitial>Ok</button>
<button mat-button [mat-dialog-close]="null">Cancel</button>
<button mat-button [mat-dialog-close]="form.value" cdkFocusInitial [disabled]="form.invalid">Ok</button>
</mat-dialog-actions>
Original file line number Diff line number Diff line change
@@ -1,18 +1,33 @@
import { Component, OnInit } from '@angular/core';
import { Component, Inject, OnInit } from '@angular/core';
import { MAT_DIALOG_DATA } from '@angular/material/dialog';
import { FormBuilder, FormGroup, Validators } from '@angular/forms';

export interface GrantPermissionModalData {
userName: string;
type: 'model' | 'experiment';
entities: string[];
}

@Component({
selector: 'ml-grant-permission-modal',
templateUrl: './grant-permission-modal.component.html',
styleUrls: ['./grant-permission-modal.component.scss']
})
export class GrantPermissionModalComponent implements OnInit {
form!: FormGroup;

constructor() { }

ngOnInit(): void {
constructor(
@Inject(MAT_DIALOG_DATA) public data: GrantPermissionModalData,
private readonly fb: FormBuilder,
) {
}

onNoClick() {

ngOnInit(): void {
this.form = this.fb.group({
user: this.data.userName,
type: this.data.type,
permission: [null, Validators.required],
entity: [null, Validators.required],
})
}
}
Loading

0 comments on commit e0a2994

Please sign in to comment.