Skip to content

Commit

Permalink
Distinct page for each Trial in the UI
Browse files Browse the repository at this point in the history
  • Loading branch information
d-gol committed Feb 15, 2022
1 parent f5abfd0 commit 3562e5a
Show file tree
Hide file tree
Showing 18 changed files with 588 additions and 89 deletions.
1 change: 1 addition & 0 deletions cmd/new-ui/v1beta1/main.go
Original file line number Diff line number Diff line change
Expand Up @@ -55,6 +55,7 @@ func main() {
http.HandleFunc("/katib/delete_experiment/", kuh.DeleteExperiment)

http.HandleFunc("/katib/fetch_experiment/", kuh.FetchExperiment)
http.HandleFunc("/katib/fetch_trial/", kuh.FetchTrial)
http.HandleFunc("/katib/fetch_suggestion/", kuh.FetchSuggestion)

http.HandleFunc("/katib/fetch_hp_job_info/", kuh.FetchHPJobInfo)
Expand Down
24 changes: 24 additions & 0 deletions pkg/new-ui/v1beta1/backend.go
Original file line number Diff line number Diff line change
Expand Up @@ -396,3 +396,27 @@ func (k *KatibUIHandler) FetchSuggestion(w http.ResponseWriter, r *http.Request)
return
}
}

// FetchTrial gets trial in specific namespace.
func (k *KatibUIHandler) FetchTrial(w http.ResponseWriter, r *http.Request) {
trialName := r.URL.Query()["trialName"][0]
namespace := r.URL.Query()["namespace"][0]

trial, err := k.katibClient.GetTrial(trialName, namespace)
if err != nil {
log.Printf("GetTrial failed: %v", err)
http.Error(w, err.Error(), http.StatusInternalServerError)
return
}
response, err := json.Marshal(trial)
if err != nil {
log.Printf("Marshal Trial failed: %v", err)
http.Error(w, err.Error(), http.StatusInternalServerError)
return
}
if _, err = w.Write(response); err != nil {
log.Printf("Write trial failed: %v", err)
http.Error(w, err.Error(), http.StatusInternalServerError)
return
}
}
5 changes: 5 additions & 0 deletions pkg/new-ui/v1beta1/frontend/src/app/app-routing.module.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,11 +3,16 @@ import { Routes, RouterModule } from '@angular/router';
import { ExperimentsComponent } from './pages/experiments/experiments.component';
import { ExperimentDetailsComponent } from './pages/experiment-details/experiment-details.component';
import { ExperimentCreationComponent } from './pages/experiment-creation/experiment-creation.component';
import { TrialModalComponent } from './pages/experiment-details/trials-table/trial-modal/trial-modal.component';

const routes: Routes = [
{ path: '', component: ExperimentsComponent },
{ path: 'experiment/:experimentName', component: ExperimentDetailsComponent },
{ path: 'new', component: ExperimentCreationComponent },
{
path: 'experiment/:experimentName/trial/:trialName',
component: TrialModalComponent,
},
];

@NgModule({
Expand Down
2 changes: 2 additions & 0 deletions pkg/new-ui/v1beta1/frontend/src/app/app.module.ts
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@ import { AppComponent } from './app.component';
import { ExperimentsModule } from './pages/experiments/experiments.module';
import { ExperimentDetailsModule } from './pages/experiment-details/experiment-details.module';
import { ExperimentCreationModule } from './pages/experiment-creation/experiment-creation.module';
import { TrialModalModule } from './pages/experiment-details/trials-table/trial-modal/trial-modal.module';

@NgModule({
declarations: [AppComponent],
Expand All @@ -19,6 +20,7 @@ import { ExperimentCreationModule } from './pages/experiment-creation/experiment
ExperimentDetailsModule,
ReactiveFormsModule,
ExperimentCreationModule,
TrialModalModule,
],
providers: [],
bootstrap: [AppComponent],
Expand Down
84 changes: 84 additions & 0 deletions pkg/new-ui/v1beta1/frontend/src/app/models/trial.k8s.model.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,84 @@
import { K8sObject } from 'kubeflow';
import { V1Container } from '@kubernetes/client-node';

/*
* K8s object definitions
*/
export const TRIAL_KIND = 'Trial';
export const TRIAL_APIVERSION = 'kubeflow.org/v1beta1';

export interface TrialK8s extends K8sObject {
spec?: TrialSpec;
status?: TrialStatus;
}

export interface TrialSpec {
metricsCollector: MetricsCollector;
objective: Objective;
parameterAssignments: { name: string; value: number }[];
primaryContainerName: string;
successCondition: string;
failureCondition: string;
runSpec: K8sObject;
}

export interface MetricsCollector {
collector?: CollectorSpec;
}

export interface CollectorSpec {
kind: CollectorKind;
customCollector: V1Container;
}

export type CollectorKind =
| 'StdOut'
| 'File'
| 'TensorFlowEvent'
| 'PrometheusMetric'
| 'Custom'
| 'None';

export interface Objective {
type: ObjectiveType;
goal: number;
objectiveMetricName: string;
additionalMetricNames: string[];
metricStrategies: MetricStrategy[];
}

export type ObjectiveType = 'maximize' | 'minimize';

export interface MetricStrategy {
name: string;
value: string;
}

export interface RunSpec {}

/*
* status
*/

interface TrialStatus {
startTime: string;
completionTime: string;
conditions: TrialStatusCondition[];
observation: {
metrics: {
name: string;
latest: number;
min: number;
max: string;
}[];
};
}

interface TrialStatusCondition {
type: string;
status: boolean;
reason: string;
message: string;
lastUpdateTime: string;
lastTransitionTime: string;
}
Original file line number Diff line number Diff line change
Expand Up @@ -33,7 +33,12 @@
</ng-template>

<div class="tab-height-fix">
<mat-tab-group dynamicHeight animationDuration="0ms">
<mat-tab-group
dynamicHeight
animationDuration="0ms"
[(selectedIndex)]="selectedTab"
(selectedTabChange)="tabChanged($event)"
>
<mat-tab label="OVERVIEW">
<app-experiment-overview
[experimentName]="name"
Expand All @@ -45,6 +50,7 @@
(leaveMouseFromTrial)="mouseLeftTrial()"
(mouseOnTrial)="mouseOverTrial($event)"
[data]="details"
[experimentName]="name"
[displayedColumns]="columns"
[namespace]="namespace"
[bestTrialName]="bestTrialName"
Expand Down
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
import { Component, OnDestroy, OnInit } from '@angular/core';
import { ActivatedRoute, Router } from '@angular/router';
import { MatTabChangeEvent } from '@angular/material/tabs';
import {
ConfirmDialogService,
DIALOG_RESP,
Expand Down Expand Up @@ -35,6 +36,13 @@ export class ExperimentDetailsComponent implements OnInit, OnDestroy {
showGraph: boolean;
bestTrialName: string;
pageLoading = true;
selectedTab = 0;
tabs = new Map<string, number>([
['overview', 0],
['trials', 1],
['details', 2],
['yaml', 3],
]);

constructor(
private activatedRoute: ActivatedRoute,
Expand Down Expand Up @@ -62,6 +70,12 @@ export class ExperimentDetailsComponent implements OnInit, OnDestroy {
ngOnInit() {
this.name = this.activatedRoute.snapshot.params.experimentName;

if (this.activatedRoute.snapshot.queryParams['tab']) {
this.selectedTab = this.tabs.get(
this.activatedRoute.snapshot.queryParams['tab'],
);
}

this.subs.add(
this.namespaceService.getSelectedNamespace().subscribe(namespace => {
this.namespace = namespace;
Expand All @@ -70,6 +84,10 @@ export class ExperimentDetailsComponent implements OnInit, OnDestroy {
);
}

tabChanged(event: MatTabChangeEvent) {
this.selectedTab = event.index;
}

ngOnDestroy(): void {
this.subs.unsubscribe();
}
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
<lib-details-list-item key="Trial Name">
{{ trialName }}
</lib-details-list-item>

<lib-details-list-item key="Experiment Name">
{{ experimentName }}
</lib-details-list-item>

<lib-details-list-item key="Status" [icon]="statusIcon">
{{ status }}
</lib-details-list-item>

<lib-details-list-item [chipsList]="performance" key="Performance">
</lib-details-list-item>

<lib-conditions-table
*ngIf="trial"
[conditions]="trial.status.conditions"
title="Trial Conditions"
></lib-conditions-table>
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
import { async, ComponentFixture, TestBed } from '@angular/core/testing';

import { TrialModalOverviewComponent } from './trial-modal-overview.component';

describe('TrialModalOverviewComponent', () => {
let component: TrialModalOverviewComponent;
let fixture: ComponentFixture<TrialModalOverviewComponent>;

beforeEach(async(() => {
TestBed.configureTestingModule({
declarations: [TrialModalOverviewComponent],
}).compileComponents();
}));

beforeEach(() => {
fixture = TestBed.createComponent(TrialModalOverviewComponent);
component = fixture.componentInstance;
fixture.detectChanges();
});

it('should create', () => {
expect(component).toBeTruthy();
});
});
Original file line number Diff line number Diff line change
@@ -0,0 +1,111 @@
import {
ChangeDetectionStrategy,
Component,
Input,
OnChanges,
} from '@angular/core';
import { ChipDescriptor, getCondition } from 'kubeflow';
import { StatusEnum } from 'src/app/enumerations/status.enum';
import { TrialK8s } from 'src/app/models/trial.k8s.model';
import { numberToExponential } from 'src/app/shared/utils';

@Component({
selector: 'app-trial-modal-overview',
templateUrl: './trial-modal-overview.component.html',
changeDetection: ChangeDetectionStrategy.OnPush,
})
export class TrialModalOverviewComponent implements OnChanges {
status: string;
statusIcon: string;
performance: ChipDescriptor[];

@Input()
trialName: string;

@Input()
trial: TrialK8s;

@Input()
experimentName: string;

constructor() {}

ngOnInit() {
if (this.trial) {
const { status, statusIcon } = this.generateTrialStatus(this.trial);
this.status = status;
this.statusIcon = statusIcon;
}
}

ngOnChanges(): void {
if (this.trial) {
this.generateTrialPropsList(this.trial);
}
}

private generateTrialPropsList(trial: TrialK8s): void {
this.performance = this.generateTrialMetrics(this.trial);

const { status, statusIcon } = this.generateTrialStatus(trial);
this.status = status;
this.statusIcon = statusIcon;
}

private generateTrialStatus(trial: TrialK8s): {
status: string;
statusIcon: string;
} {
const succeededCondition = getCondition(trial, StatusEnum.SUCCEEDED);

if (succeededCondition && succeededCondition.status === 'True') {
return { status: succeededCondition.message, statusIcon: 'check_circle' };
}

const failedCondition = getCondition(trial, StatusEnum.FAILED);

if (failedCondition && failedCondition.status === 'True') {
return { status: failedCondition.message, statusIcon: 'warning' };
}

const runningCondition = getCondition(trial, StatusEnum.RUNNING);

if (runningCondition && runningCondition.status === 'True') {
return { status: runningCondition.message, statusIcon: 'schedule' };
}

const restartingCondition = getCondition(trial, StatusEnum.RESTARTING);

if (restartingCondition && restartingCondition.status === 'True') {
return { status: restartingCondition.message, statusIcon: 'loop' };
}

const createdCondition = getCondition(trial, StatusEnum.CREATED);

if (createdCondition && createdCondition.status === 'True') {
return {
status: createdCondition.message,
statusIcon: 'add_circle_outline',
};
}
}

private generateTrialMetrics(trial: TrialK8s): ChipDescriptor[] {
if (!trial.status.observation || !trial.status.observation.metrics) {
return [];
}

const metrics = trial.status.observation.metrics.map(
metric =>
`${metric.name}: ${
!isNaN(+metric.latest)
? numberToExponential(+metric.latest, 6)
: metric.latest
}`,
);

return metrics.map(m => {
return { value: m, color: 'primary', tooltip: 'Latest value' };
});
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
import { NgModule } from '@angular/core';
import { CommonModule } from '@angular/common';
import { ConditionsTableModule, DetailsListModule } from 'kubeflow';

import { TrialModalOverviewComponent } from './trial-modal-overview.component';

@NgModule({
declarations: [TrialModalOverviewComponent],
imports: [CommonModule, ConditionsTableModule, DetailsListModule],
exports: [TrialModalOverviewComponent],
})
export class TrialModalOverviewModule {}
Loading

0 comments on commit 3562e5a

Please sign in to comment.