Skip to content

Commit

Permalink
[YUNIKORN-2491] Persist queue and partition selection and prompt user…
Browse files Browse the repository at this point in the history
… to pick queue on Applications page

 (#180)

- When a user lands on the applications page, queue selection is automatically focused and waits for user to select the queue
- Selected queue,partition pair is stored in localStorage

Closes: #180

Signed-off-by: Yu-Lin Chen <chenyulin0719@apache.org>
  • Loading branch information
dcoric authored and chenyulin0719 committed May 8, 2024
1 parent 9f43880 commit 440f03b
Show file tree
Hide file tree
Showing 5 changed files with 132 additions and 60 deletions.
2 changes: 1 addition & 1 deletion src/app/components/apps-view/apps-view.component.html
Original file line number Diff line number Diff line change
Expand Up @@ -31,7 +31,7 @@
<div class="dropdown-wrapper">
<label class="dropdown-label">Queue: </label>
<mat-form-field>
<mat-select [(value)]="leafQueueSelected" (selectionChange)="onQueueSelectionChanged($event)">
<mat-select [(value)]="leafQueueSelected" (selectionChange)="onQueueSelectionChanged($event)" #queueSelect>
<mat-option *ngFor="let queue of leafQueueList" [value]="queue.value">
{{ queue.name }}
</mat-option>
Expand Down
70 changes: 57 additions & 13 deletions src/app/components/apps-view/apps-view.component.ts
Original file line number Diff line number Diff line change
Expand Up @@ -21,7 +21,7 @@ import { ActivatedRoute, Router } from '@angular/router';
import { MatPaginator } from '@angular/material/paginator';
import { MatTableDataSource } from '@angular/material/table';
import { MatSort } from '@angular/material/sort';
import { MatSelectChange } from '@angular/material/select';
import { MatSelectChange, MatSelect } from '@angular/material/select';
import { finalize, debounceTime, distinctUntilChanged } from 'rxjs/operators';
import { NgxSpinnerService } from 'ngx-spinner';
import { fromEvent } from 'rxjs';
Expand All @@ -46,6 +46,7 @@ export class AppsViewComponent implements OnInit {
@ViewChild('appSort', { static: true }) appSort!: MatSort;
@ViewChild('allocSort', { static: true }) allocSort!: MatSort;
@ViewChild('searchInput', { static: true }) searchInput!: ElementRef;
@ViewChild('queueSelect', { static: false }) queueSelect!: MatSelect;

appDataSource = new MatTableDataSource<AppInfo>([]);
appColumnDef: ColumnDef[] = [];
Expand All @@ -62,7 +63,7 @@ export class AppsViewComponent implements OnInit {
partitionSelected = '';
leafQueueList: DropdownItem[] = [];
leafQueueSelected = '';

detailToggle: boolean = false;

constructor(
Expand Down Expand Up @@ -135,14 +136,15 @@ export class AppsViewComponent implements OnInit {
this.partitionList.push(new PartitionInfo(part.name, part.name));
});

this.partitionSelected = list[0].name;
this.partitionSelected = CommonUtil.getStoredPartition(list[0].name);
this.fetchQueuesForPartition(this.partitionSelected);
} else {
this.partitionList = [new PartitionInfo('-- Select --', '')];
this.partitionSelected = '';
this.leafQueueList = [new DropdownItem('-- Select --', '')];
this.leafQueueSelected = '';
this.appDataSource.data = [];
this.clearQueueSelection();
}
});
}
Expand All @@ -161,14 +163,37 @@ export class AppsViewComponent implements OnInit {
if (data && data.rootQueue) {
const leafQueueList = this.generateLeafQueueList(data.rootQueue);
this.leafQueueList = [new DropdownItem('-- Select --', ''), ...leafQueueList];
this.leafQueueSelected = '';
this.fetchApplicationsUsingQueryParams();
this.setDefaultQueue(leafQueueList);
} else {
this.leafQueueList = [new DropdownItem('-- Select --', '')];
}
});
}

setDefaultQueue(queueList: DropdownItem[]): void {
const storedPartitionAndQueue = localStorage.getItem('selectedPartitionAndQueue');

if (!storedPartitionAndQueue || storedPartitionAndQueue.indexOf(':') < 0) {
setTimeout(() => this.openQueueSelection(), 0);
return;
}

const [storedPartition, storedQueue] = storedPartitionAndQueue.split(':');
if (this.partitionSelected !== storedPartition) return;

const storedQueueDropdownItem = queueList.find((queue) => queue.value === storedQueue);
if (storedQueueDropdownItem) {
this.leafQueueSelected = storedQueueDropdownItem.value;
this.fetchAppListForPartitionAndQueue(this.partitionSelected, this.leafQueueSelected);
return;
} else {
this.leafQueueSelected = '';
this.appDataSource.data = [];
setTimeout(() => this.openQueueSelection(), 0); // Allows render to finish and then opens the queue select dropdown
}
}

generateLeafQueueList(rootQueue: QueueInfo, list: DropdownItem[] = []): DropdownItem[] {
if (rootQueue && rootQueue.isLeaf) {
list.push(new DropdownItem(rootQueue.queueName, rootQueue.queueName));
Expand Down Expand Up @@ -205,6 +230,7 @@ export class AppsViewComponent implements OnInit {
this.partitionSelected = partitionName;
this.leafQueueSelected = queueName;
this.fetchAppListForPartitionAndQueue(partitionName, queueName);
CommonUtil.setStoredQueueAndPartition(partitionName, queueName);
}

this.router.navigate([], {
Expand Down Expand Up @@ -284,13 +310,15 @@ export class AppsViewComponent implements OnInit {
this.partitionSelected = selected.value;
this.appDataSource.data = [];
this.removeRowSelection();
this.clearQueueSelection();
this.fetchQueuesForPartition(this.partitionSelected);
} else {
this.searchText = '';
this.partitionSelected = '';
this.leafQueueSelected = '';
this.appDataSource.data = [];
this.removeRowSelection();
this.clearQueueSelection();
}
}

Expand All @@ -301,35 +329,51 @@ export class AppsViewComponent implements OnInit {
this.appDataSource.data = [];
this.removeRowSelection();
this.fetchAppListForPartitionAndQueue(this.partitionSelected, this.leafQueueSelected);
CommonUtil.setStoredQueueAndPartition(this.partitionSelected, this.leafQueueSelected);
} else {
this.searchText = '';
this.leafQueueSelected = '';
this.appDataSource.data = [];
this.removeRowSelection();
this.clearQueueSelection();
}
}

formatResources(colValue:string):string[]{
const arr:string[]=colValue.split("<br/>")
formatResources(colValue: string): string[] {
const arr: string[] = colValue.split('<br/>');
// Check if there are "cpu" or "Memory" elements in the array
const hasCpu = arr.some((item) => item.toLowerCase().includes("cpu"));
const hasMemory = arr.some((item) => item.toLowerCase().includes("memory"));
const hasCpu = arr.some((item) => item.toLowerCase().includes('cpu'));
const hasMemory = arr.some((item) => item.toLowerCase().includes('memory'));
if (!hasCpu) {
arr.unshift("CPU: n/a");
arr.unshift('CPU: n/a');
}
if (!hasMemory) {
arr.unshift("Memory: n/a");
arr.unshift('Memory: n/a');
}

// Concatenate the two arrays, with "cpu" and "Memory" elements first
const cpuAndMemoryElements = arr.filter((item) => item.toLowerCase().includes("CPU") || item.toLowerCase().includes("Memory"));
const otherElements = arr.filter((item) => !item.toLowerCase().includes("CPU") && !item.toLowerCase().includes("Memory"));
const cpuAndMemoryElements = arr.filter(
(item) => item.toLowerCase().includes('CPU') || item.toLowerCase().includes('Memory')
);
const otherElements = arr.filter(
(item) => !item.toLowerCase().includes('CPU') && !item.toLowerCase().includes('Memory')
);
const result = cpuAndMemoryElements.concat(otherElements);

return result;
}

toggle(){
clearQueueSelection() {
CommonUtil.setStoredQueueAndPartition('');
this.leafQueueSelected = '';
this.openQueueSelection();
}

openQueueSelection() {
this.queueSelect.open();
}

toggle() {
this.detailToggle = !this.detailToggle;
}
}
101 changes: 56 additions & 45 deletions src/app/components/nodes-view/nodes-view.component.ts
Original file line number Diff line number Diff line change
Expand Up @@ -39,9 +39,8 @@ import { PartitionInfo } from '@app/models/partition-info.model';
export class NodesViewComponent implements OnInit {
@ViewChild('nodesViewMatPaginator', { static: true }) nodePaginator!: MatPaginator;
@ViewChild('allocationMatPaginator', { static: true }) allocPaginator!: MatPaginator;
@ViewChild('nodeSort', {static: true }) nodeSort!: MatSort;
@ViewChild('allocSort', {static: true }) allocSort!: MatSort;

@ViewChild('nodeSort', { static: true }) nodeSort!: MatSort;
@ViewChild('allocSort', { static: true }) allocSort!: MatSort;

nodeDataSource = new MatTableDataSource<NodeInfo>([]);
nodeColumnDef: ColumnDef[] = [];
Expand All @@ -58,7 +57,10 @@ export class NodesViewComponent implements OnInit {
detailToggle: boolean = false;
filterValue: string = '';

constructor(private scheduler: SchedulerService, private spinner: NgxSpinnerService) {}
constructor(
private scheduler: SchedulerService,
private spinner: NgxSpinnerService
) {}

ngOnInit() {
this.nodeDataSource.paginator = this.nodePaginator;
Expand Down Expand Up @@ -118,12 +120,13 @@ export class NodesViewComponent implements OnInit {
this.partitionList.push(new PartitionInfo(part.name, part.name));
});

this.partitionSelected = list[0].name;
this.partitionSelected = CommonUtil.getStoredPartition(list[0].name);
this.fetchNodeListForPartition(this.partitionSelected);
} else {
this.partitionList = [new PartitionInfo('-- Select --', '')];
this.partitionSelected = '';
this.nodeDataSource.data = [];
CommonUtil.setStoredQueueAndPartition('');
}
});
}
Expand Down Expand Up @@ -192,95 +195,103 @@ export class NodesViewComponent implements OnInit {
return this.allocDataSource.data && this.allocDataSource.data.length === 0;
}

formatColumn(){
if(this.nodeDataSource.data.length===0){
formatColumn() {
if (this.nodeDataSource.data.length === 0) {
return;
}
this.nodeColumnIds.forEach((colId)=>{
if (colId==='indicatorIcon'){
this.nodeColumnIds.forEach((colId) => {
if (colId === 'indicatorIcon') {
return;
}

// Verify whether all cells in the column are empty.
let isEmpty:boolean = true;
let isEmpty: boolean = true;
Object.values(this.nodeDataSource.data).forEach((node) => {
Object.entries(node).forEach(entry => {
Object.entries(node).forEach((entry) => {
const [key, value] = entry;
if (key===colId && !(value==='' || value==='n/a')){
isEmpty=false;
if (key === colId && !(value === '' || value === 'n/a')) {
isEmpty = false;
}
});
});
if (isEmpty){
this.nodeColumnIds = this.nodeColumnIds.filter(el => el!==colId);
this.nodeColumnIds = this.nodeColumnIds.filter(colId => colId!=="attributes");

if (isEmpty) {
this.nodeColumnIds = this.nodeColumnIds.filter((el) => el !== colId);
this.nodeColumnIds = this.nodeColumnIds.filter((colId) => colId !== 'attributes');
}
})
});
}

onPartitionSelectionChanged(selected: MatSelectChange) {
this.partitionSelected = selected.value;
this.clearRowSelection();
this.fetchNodeListForPartition(this.partitionSelected);
CommonUtil.setStoredQueueAndPartition(this.partitionSelected);
}

formatResources(colValue:string):string[]{
const arr:string[]=colValue.split("<br/>");
formatResources(colValue: string): string[] {
const arr: string[] = colValue.split('<br/>');
// Check if there are "cpu" or "Memory" elements in the array
const hasCpu = arr.some((item) => item.toLowerCase().includes("cpu"));
const hasMemory = arr.some((item) => item.toLowerCase().includes("memory"));
const hasCpu = arr.some((item) => item.toLowerCase().includes('cpu'));
const hasMemory = arr.some((item) => item.toLowerCase().includes('memory'));
if (!hasCpu) {
arr.unshift("CPU: n/a");
arr.unshift('CPU: n/a');
}
if (!hasMemory) {
arr.unshift("Memory: n/a");
arr.unshift('Memory: n/a');
}

// Concatenate the two arrays, with "cpu" and "Memory" elements first
const cpuAndMemoryElements = arr.filter((item) => item.toLowerCase().includes("CPU") || item.toLowerCase().includes("Memory"));
const otherElements = arr.filter((item) => !item.toLowerCase().includes("CPU") && !item.toLowerCase().includes("Memory"));
const cpuAndMemoryElements = arr.filter(
(item) => item.toLowerCase().includes('CPU') || item.toLowerCase().includes('Memory')
);
const otherElements = arr.filter(
(item) => !item.toLowerCase().includes('CPU') && !item.toLowerCase().includes('Memory')
);
const result = cpuAndMemoryElements.concat(otherElements);

return result;
}

formatAttribute(attributes:any):string[]{
let result:string[]=[];
Object.entries(attributes).forEach(entry=>{
formatAttribute(attributes: any): string[] {
let result: string[] = [];
Object.entries(attributes).forEach((entry) => {
const [key, value] = entry;
if (value==="" || key.includes("si")){
return
if (value === '' || key.includes('si')) {
return;
}
result.push(key+':'+value);
})
result.push(key + ':' + value);
});
return result;
}

toggle(){
toggle() {
this.detailToggle = !this.detailToggle;
this.displayAttribute(this.detailToggle);
}

displayAttribute(toggle:boolean) {
if (toggle){
displayAttribute(toggle: boolean) {
if (toggle) {
this.nodeColumnIds = [
...this.nodeColumnIds.slice(0, 1),
"attributes",
...this.nodeColumnIds.slice(1)
];
}else{
this.nodeColumnIds=this.nodeColumnIds.filter(colId => colId!=="attributes");
'attributes',
...this.nodeColumnIds.slice(1),
];
} else {
this.nodeColumnIds = this.nodeColumnIds.filter((colId) => colId !== 'attributes');
}
}

filterPredicate: ((data: NodeInfo, filter: string) => boolean) = (data: NodeInfo, filter: string): boolean => {
filterPredicate: (data: NodeInfo, filter: string) => boolean = (
data: NodeInfo,
filter: string
): boolean => {
// a deep copy of the NodeInfo with formatted attributes for filtering
const deepCopy = JSON.parse(JSON.stringify(data));
Object.entries(deepCopy.attributes).forEach(entry=>{
Object.entries(deepCopy.attributes).forEach((entry) => {
const [key, value] = entry;
deepCopy.attributes[key]= `${key}:${value}`
})
deepCopy.attributes[key] = `${key}:${value}`;
});
const objectString = JSON.stringify(deepCopy).toLowerCase();
return objectString.includes(filter);
};
Expand Down
6 changes: 5 additions & 1 deletion src/app/components/queues-view/queues-view.component.ts
Original file line number Diff line number Diff line change
Expand Up @@ -92,12 +92,14 @@ export class QueuesViewComponent implements OnInit {
this.partitionList.push(new PartitionInfo(part.name, part.name));
});

this.partitionSelected = list[0].name;
this.partitionSelected = CommonUtil.getStoredPartition(list[0].name);

this.fetchSchedulerQueuesForPartition(this.partitionSelected);
} else {
this.partitionList = [new PartitionInfo('-- Select --', '')];
this.partitionSelected = '';
this.queueList = {};
CommonUtil.setStoredQueueAndPartition('');
}
});
}
Expand Down Expand Up @@ -208,9 +210,11 @@ export class QueuesViewComponent implements OnInit {
this.partitionSelected = selected.value;
this.closeQueueDrawer();
this.fetchSchedulerQueuesForPartition(this.partitionSelected);
CommonUtil.setStoredQueueAndPartition(this.partitionSelected);
} else {
this.partitionSelected = '';
this.queueList = {};
CommonUtil.setStoredQueueAndPartition('');
}
}

Expand Down
Loading

0 comments on commit 440f03b

Please sign in to comment.