Skip to content

Commit 75e9c13

Browse files
committed
Distinct page for each Trial in the UI
1 parent 2c8758b commit 75e9c13

22 files changed

+679
-89
lines changed

cmd/new-ui/v1beta1/main.go

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -55,6 +55,7 @@ func main() {
5555
http.HandleFunc("/katib/delete_experiment/", kuh.DeleteExperiment)
5656

5757
http.HandleFunc("/katib/fetch_experiment/", kuh.FetchExperiment)
58+
http.HandleFunc("/katib/fetch_trial/", kuh.FetchTrial)
5859
http.HandleFunc("/katib/fetch_suggestion/", kuh.FetchSuggestion)
5960

6061
http.HandleFunc("/katib/fetch_hp_job_info/", kuh.FetchHPJobInfo)

pkg/new-ui/v1beta1/backend.go

Lines changed: 24 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -397,3 +397,27 @@ func (k *KatibUIHandler) FetchSuggestion(w http.ResponseWriter, r *http.Request)
397397
return
398398
}
399399
}
400+
401+
// FetchTrial gets trial in specific namespace.
402+
func (k *KatibUIHandler) FetchTrial(w http.ResponseWriter, r *http.Request) {
403+
trialName := r.URL.Query()["trialName"][0]
404+
namespace := r.URL.Query()["namespace"][0]
405+
406+
trial, err := k.katibClient.GetTrial(trialName, namespace)
407+
if err != nil {
408+
log.Printf("GetTrial failed: %v", err)
409+
http.Error(w, err.Error(), http.StatusInternalServerError)
410+
return
411+
}
412+
response, err := json.Marshal(trial)
413+
if err != nil {
414+
log.Printf("Marshal Trial failed: %v", err)
415+
http.Error(w, err.Error(), http.StatusInternalServerError)
416+
return
417+
}
418+
if _, err = w.Write(response); err != nil {
419+
log.Printf("Write trial failed: %v", err)
420+
http.Error(w, err.Error(), http.StatusInternalServerError)
421+
return
422+
}
423+
}

pkg/new-ui/v1beta1/frontend/src/app/app-routing.module.ts

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3,11 +3,16 @@ import { Routes, RouterModule } from '@angular/router';
33
import { ExperimentsComponent } from './pages/experiments/experiments.component';
44
import { ExperimentDetailsComponent } from './pages/experiment-details/experiment-details.component';
55
import { ExperimentCreationComponent } from './pages/experiment-creation/experiment-creation.component';
6+
import { TrialModalComponent } from './pages/experiment-details/trials-table/trial-modal/trial-modal.component';
67

78
const routes: Routes = [
89
{ path: '', component: ExperimentsComponent },
910
{ path: 'experiment/:experimentName', component: ExperimentDetailsComponent },
1011
{ path: 'new', component: ExperimentCreationComponent },
12+
{
13+
path: 'experiment/:experimentName/trial/:trialName',
14+
component: TrialModalComponent,
15+
},
1116
];
1217

1318
@NgModule({

pkg/new-ui/v1beta1/frontend/src/app/app.module.ts

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,7 @@ import { AppComponent } from './app.component';
88
import { ExperimentsModule } from './pages/experiments/experiments.module';
99
import { ExperimentDetailsModule } from './pages/experiment-details/experiment-details.module';
1010
import { ExperimentCreationModule } from './pages/experiment-creation/experiment-creation.module';
11+
import { TrialModalModule } from './pages/experiment-details/trials-table/trial-modal/trial-modal.module';
1112

1213
@NgModule({
1314
declarations: [AppComponent],
@@ -19,6 +20,7 @@ import { ExperimentCreationModule } from './pages/experiment-creation/experiment
1920
ExperimentDetailsModule,
2021
ReactiveFormsModule,
2122
ExperimentCreationModule,
23+
TrialModalModule,
2224
],
2325
providers: [],
2426
bootstrap: [AppComponent],
Lines changed: 84 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,84 @@
1+
import { K8sObject } from 'kubeflow';
2+
import { V1Container } from '@kubernetes/client-node';
3+
4+
/*
5+
* K8s object definitions
6+
*/
7+
export const TRIAL_KIND = 'Trial';
8+
export const TRIAL_APIVERSION = 'kubeflow.org/v1beta1';
9+
10+
export interface TrialK8s extends K8sObject {
11+
spec?: TrialSpec;
12+
status?: TrialStatus;
13+
}
14+
15+
export interface TrialSpec {
16+
metricsCollector: MetricsCollector;
17+
objective: Objective;
18+
parameterAssignments: { name: string; value: number }[];
19+
primaryContainerName: string;
20+
successCondition: string;
21+
failureCondition: string;
22+
runSpec: K8sObject;
23+
}
24+
25+
export interface MetricsCollector {
26+
collector?: CollectorSpec;
27+
}
28+
29+
export interface CollectorSpec {
30+
kind: CollectorKind;
31+
customCollector: V1Container;
32+
}
33+
34+
export type CollectorKind =
35+
| 'StdOut'
36+
| 'File'
37+
| 'TensorFlowEvent'
38+
| 'PrometheusMetric'
39+
| 'Custom'
40+
| 'None';
41+
42+
export interface Objective {
43+
type: ObjectiveType;
44+
goal: number;
45+
objectiveMetricName: string;
46+
additionalMetricNames: string[];
47+
metricStrategies: MetricStrategy[];
48+
}
49+
50+
export type ObjectiveType = 'maximize' | 'minimize';
51+
52+
export interface MetricStrategy {
53+
name: string;
54+
value: string;
55+
}
56+
57+
export interface RunSpec {}
58+
59+
/*
60+
* status
61+
*/
62+
63+
interface TrialStatus {
64+
startTime: string;
65+
completionTime: string;
66+
conditions: TrialStatusCondition[];
67+
observation: {
68+
metrics: {
69+
name: string;
70+
latest: string;
71+
min: string;
72+
max: string;
73+
}[];
74+
};
75+
}
76+
77+
interface TrialStatusCondition {
78+
type: string;
79+
status: boolean;
80+
reason: string;
81+
message: string;
82+
lastUpdateTime: string;
83+
lastTransitionTime: string;
84+
}

pkg/new-ui/v1beta1/frontend/src/app/pages/experiment-details/experiment-details.component.html

Lines changed: 7 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -33,7 +33,12 @@
3333
</ng-template>
3434

3535
<div class="tab-height-fix">
36-
<mat-tab-group dynamicHeight animationDuration="0ms">
36+
<mat-tab-group
37+
dynamicHeight
38+
animationDuration="0ms"
39+
[(selectedIndex)]="selectedTab"
40+
(selectedTabChange)="tabChanged($event)"
41+
>
3742
<mat-tab label="OVERVIEW">
3843
<app-experiment-overview
3944
[experimentName]="name"
@@ -45,6 +50,7 @@
4550
(leaveMouseFromTrial)="mouseLeftTrial()"
4651
(mouseOnTrial)="mouseOverTrial($event)"
4752
[data]="details"
53+
[experimentName]="name"
4854
[displayedColumns]="columns"
4955
[namespace]="namespace"
5056
[bestTrialName]="bestTrialName"

pkg/new-ui/v1beta1/frontend/src/app/pages/experiment-details/experiment-details.component.ts

Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
import { Component, OnDestroy, OnInit } from '@angular/core';
22
import { ActivatedRoute, Router } from '@angular/router';
3+
import { MatTabChangeEvent } from '@angular/material/tabs';
34
import {
45
ConfirmDialogService,
56
DIALOG_RESP,
@@ -35,6 +36,13 @@ export class ExperimentDetailsComponent implements OnInit, OnDestroy {
3536
showGraph: boolean;
3637
bestTrialName: string;
3738
pageLoading = true;
39+
selectedTab = 0;
40+
tabs = new Map<string, number>([
41+
['overview', 0],
42+
['trials', 1],
43+
['details', 2],
44+
['yaml', 3],
45+
]);
3846

3947
constructor(
4048
private activatedRoute: ActivatedRoute,
@@ -62,6 +70,12 @@ export class ExperimentDetailsComponent implements OnInit, OnDestroy {
6270
ngOnInit() {
6371
this.name = this.activatedRoute.snapshot.params.experimentName;
6472

73+
if (this.activatedRoute.snapshot.queryParams['tab']) {
74+
this.selectedTab = this.tabs.get(
75+
this.activatedRoute.snapshot.queryParams['tab'],
76+
);
77+
}
78+
6579
this.subs.add(
6680
this.namespaceService.getSelectedNamespace().subscribe(namespace => {
6781
this.namespace = namespace;
@@ -70,6 +84,10 @@ export class ExperimentDetailsComponent implements OnInit, OnDestroy {
7084
);
7185
}
7286

87+
tabChanged(event: MatTabChangeEvent) {
88+
this.selectedTab = event.index;
89+
}
90+
7391
ngOnDestroy(): void {
7492
this.subs.unsubscribe();
7593
}
Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,11 @@
1+
<lib-details-list-item key="Latest">
2+
{{ latest }}
3+
</lib-details-list-item>
4+
5+
<lib-details-list-item key="Minimum">
6+
{{ min }}
7+
</lib-details-list-item>
8+
9+
<lib-details-list-item key="Maximum">
10+
{{ max }}
11+
</lib-details-list-item>
Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,10 @@
1+
import { NgModule } from '@angular/core';
2+
import { ConditionsTableModule, DetailsListModule } from 'kubeflow';
3+
import { TrialModalMetricsComponent } from './metrics.component';
4+
5+
@NgModule({
6+
declarations: [TrialModalMetricsComponent],
7+
imports: [ConditionsTableModule, DetailsListModule],
8+
exports: [TrialModalMetricsComponent],
9+
})
10+
export class TrialModalMetricsModule {}
Lines changed: 24 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,24 @@
1+
import { async, ComponentFixture, TestBed } from '@angular/core/testing';
2+
3+
import { TrialModalMetricsComponent } from './metrics.component';
4+
5+
describe('TrialModalMetricsComponent', () => {
6+
let component: TrialModalMetricsComponent;
7+
let fixture: ComponentFixture<TrialModalMetricsComponent>;
8+
9+
beforeEach(async(() => {
10+
TestBed.configureTestingModule({
11+
declarations: [TrialModalMetricsComponent],
12+
}).compileComponents();
13+
}));
14+
15+
beforeEach(() => {
16+
fixture = TestBed.createComponent(TrialModalMetricsComponent);
17+
component = fixture.componentInstance;
18+
fixture.detectChanges();
19+
});
20+
21+
it('should create', () => {
22+
expect(component).toBeTruthy();
23+
});
24+
});

0 commit comments

Comments
 (0)