This is an automated email from the ASF dual-hosted git repository.
ccondit pushed a commit to branch master
in repository https://gitbox.apache.org/repos/asf/yunikorn-web.git
The following commit(s) were added to refs/heads/master by this push:
new 048f46c [YUNIKORN-2354] Add detailed queue information to new queue
view (#197)
048f46c is described below
commit 048f46cc711a510ec7e3518f66ffbb15b15809c5
Author: DouPache <[email protected]>
AuthorDate: Tue Sep 24 16:59:36 2024 -0500
[YUNIKORN-2354] Add detailed queue information to new queue view (#197)
Closes: #197
Signed-off-by: Craig Condit <[email protected]>
---
json-db.json | 21 +++++
.../components/queue-v2/queues-v2.component.html | 95 +++++++++++++++++++++-
.../components/queue-v2/queues-v2.component.scss | 93 ++++++++++++++++++++-
src/app/components/queue-v2/queues-v2.component.ts | 69 ++++++++++++++--
.../queues-view/queues-view.component.ts | 2 +-
src/app/utils/common.util.spec.ts | 44 ++++++++++
src/app/utils/common.util.ts | 37 +++++++++
7 files changed, 352 insertions(+), 9 deletions(-)
diff --git a/json-db.json b/json-db.json
index c95a332..ae20745 100644
--- a/json-db.json
+++ b/json-db.json
@@ -61,6 +61,27 @@
}
},
"children": [
+ {
+ "queuename": "root.inactive",
+ "status": "Stopped",
+ "partition": "",
+ "pendingResource": {},
+ "maxResource": {},
+ "guaranteedResource": {},
+ "allocatedResource": {},
+ "preemptingResource": {},
+ "isLeaf": true,
+ "isManaged": true,
+ "properties": {},
+ "parent": "root",
+ "template": null,
+ "children": [],
+ "absUsedCapacity": {},
+ "maxRunningApps": 0,
+ "runningApps": 0,
+ "currentPriority": -2147483648,
+ "allocatingAcceptedApps": []
+ },
{
"queuename": "root.b",
"status": "Active",
diff --git a/src/app/components/queue-v2/queues-v2.component.html
b/src/app/components/queue-v2/queues-v2.component.html
index c3e5153..d1397ed 100644
--- a/src/app/components/queue-v2/queues-v2.component.html
+++ b/src/app/components/queue-v2/queues-v2.component.html
@@ -30,7 +30,100 @@
</div>
</div>
<div class="body">
- <div class="visualize-area">
+ <div class="content-wrapper">
+ <div class="visualize-area">
+ </div>
+
+ <div class="additional-info-element">
+ <!-- additional info header -->
+ <div class="additional-info-header">
+ <strong>Queue Info</strong>
+ </div>
+ <!-- additional info content -->
+ <div class="additional-info-content">
+ <div class="info-section">
+
+ <div class="info-block">
+ <div class="info-block-header">Name:</div>
+ <div class="info-block-text">
+ {{ seletedInfo?.queueName }}
+ </div>
+ <div class="info-block-text queue-status"
+ [innerHTML]="showQueueStats(seletedInfo?.status)">
+ </div>
+ </div>
+
+ <div class="info-block">
+ <div class="info-block-header">Allocated:</div>
+ <div
+ class="allocated-block"
+
[innerHTML]="resourceValueFormatter(seletedInfo?.allocatedResource)">
+ </div>
+ </div>
+
+ <div class="info-block">
+ <div class="info-block-header">Pending:</div>
+ <div
+ class="pending-block"
+
[innerHTML]="resourceValueFormatter(seletedInfo?.pendingResource)">
+ </div>
+ </div>
+
+ <div class="info-block">
+ <div class="info-block-header">Max:</div>
+ <div
+ class="max-block"
+
[innerHTML]="resourceValueFormatter(seletedInfo?.maxResource)">
+ </div>
+ </div>
+
+ <div class="info-block">
+ <div class="info-block-header">Guaranteed:</div>
+ <div
+ class="guaranteed-block"
+
[innerHTML]="resourceValueFormatter(seletedInfo?.guaranteedResource)">
+ </div>
+ </div>
+
+ <div class="info-block">
+ <div class="info-block-header">Absolute Used Capacity:</div>
+ <div class="capacity-block">
+ <div class="capacity-icon">
+ <!-- SVG for CpuIcon -->
+ <svg xmlns="http://www.w3.org/2000/svg" width="24"
height="24" viewBox="0 0 24 24" fill="none" stroke="#2196F3" strokeWidth="2"
strokeLinecap="round" strokeLinejoin="round">
+ <rect width="16" height="16" x="4" y="4" rx="2"
/>
+ <rect width="6" height="6" x="9" y="9" rx="1" />
+ <path d="M15 2v2" />
+ <path d="M15 20v2" />
+ <path d="M2 15h2" />
+ <path d="M2 9h2" />
+ <path d="M20 15h2" />
+ <path d="M20 9h2" />
+ <path d="M9 2v2" />
+ <path d="M9 20v2" />
+ </svg>
+ <div
[innerHTML]="memoryValueFormatter(seletedInfo?.absoluteUsedCapacity)"></div>
+ </div>
+ <div class="capacity-icon">
+ <!-- SVG for MemoryStickIcon -->
+ <svg xmlns="http://www.w3.org/2000/svg" width="24"
height="24" viewBox="0 0 24 24" fill="none" stroke="#4CAF50" strokeWidth="2"
strokeLinecap="round" strokeLinejoin="round">
+ <path d="M6 19v-3" />
+ <path d="M10 19v-3" />
+ <path d="M14 19v-3" />
+ <path d="M18 19v-3" />
+ <path d="M8 11V9" />
+ <path d="M16 11V9" />
+ <path d="M12 11V9" />
+ <path d="M2 15h20" />
+ <path d="M2 7a2 2 0 0 1 2-2h16a2 2 0 0 1 2
2v1.1a2 2 0 0 0 0 3.837V17a2 2 0 0 1-2 2H4a2 2 0 0 1-2-2v-5.1a2 2 0 0 0
0-3.837Z" />
+ </svg>
+ <div
[innerHTML]="cpuValueFormatter(seletedInfo?.absoluteUsedCapacity)"></div>
+ </div>
+ </div>
+ </div>
+ </div>
+ </div>
+ </div>
</div>
</div>
</div>
diff --git a/src/app/components/queue-v2/queues-v2.component.scss
b/src/app/components/queue-v2/queues-v2.component.scss
index 0323d73..8f0f1ef 100644
--- a/src/app/components/queue-v2/queues-v2.component.scss
+++ b/src/app/components/queue-v2/queues-v2.component.scss
@@ -79,6 +79,10 @@
background-color: #8090a5;
}
+ .content-wrapper {
+ display: flex;
+ }
+
.visualize-area {
display: flex;
flex-direction: column;
@@ -92,4 +96,91 @@
background-color: #f9fafb;
gap: 0.2rem;
padding: 20px;
- }
\ No newline at end of file
+ }
+
+ .additional-info-element {
+ width: 420px;
+ height: 600px; // as same as visualize-area
+ align-items: stretch;
+ background-color: #f3f6f8;
+ border-radius: 0.5rem;
+ border: 1px groove #1a1a1b;
+ display: none;
+ overflow: scroll;
+ }
+
+ .additional-info-header {
+ color: #010407;
+ font-size: 1.25rem;
+ padding-top: 10px;
+ padding-bottom: 10px;
+ padding-left: 10px;
+ }
+
+ .info-section {
+ background-color: #fff;
+ border-radius: 8px;
+ box-shadow: 0 2px 4px rgba(0,0,0,0.05);
+ margin-left: 16px;
+ width: 90%;
+ }
+
+ .info-block {
+ width: 90%;
+ border-bottom: 1px solid #eaeaea;
+ padding: 10px;
+ }
+
+ .queue-status {
+ color: #e13020;
+ font-size: 10px;
+ }
+
+ .info-block:last-child {
+ border-bottom: none;
+ }
+
+ .info-block-header {
+ font-size: 16px;
+ color: #4a4a4a;
+ font-weight: bold;
+ }
+
+ .info-block-text {
+ font-size: 14px;
+ display:inline;
+ }
+
+ .allocated-block, .capacity-block {
+ font-size: 13px;
+ background-color: #f9f9f9;
+ border-radius: 8px;
+ padding: 4px;
+ }
+
+ .pending-block {
+ font-size: 13px;
+ background-color: #ffffe0;
+ border-radius: 8px;
+ padding: 4px;
+ }
+
+ .max-block {
+ font-size: 13px;
+ background-color: #e0ffe0; /* Light green background */
+ border-radius: 8px;
+ padding: 4px;
+ }
+
+ .guaranteed-block {
+ font-size: 13px;
+ background-color: #d8f3fa; /* Light green background */
+ border-radius: 8px;
+ padding: 4px;
+ }
+
+ .capacity-icon{
+ display: flex;
+ align-items: center;
+ gap: 8px;
+ }
diff --git a/src/app/components/queue-v2/queues-v2.component.ts
b/src/app/components/queue-v2/queues-v2.component.ts
index c9f8d27..1020e4f 100644
--- a/src/app/components/queue-v2/queues-v2.component.ts
+++ b/src/app/components/queue-v2/queues-v2.component.ts
@@ -28,6 +28,7 @@ import * as d3hierarchy from "d3-hierarchy";
import * as d3flextree from "d3-flextree";
import * as d3zoom from "d3-zoom";
import { transition } from 'd3-transition'; // we need to import transition
even if we don't use it explicitly
+import { CommonUtil } from '@app/utils/common.util';
export interface TreeNode {
name: string;
@@ -43,6 +44,10 @@ export interface TreeNode {
export class QueueV2Component implements OnInit {
rootQueue: QueueInfo | null = null;
+ seletedInfo: QueueInfo | null = null;
+ resourceValueFormatter = CommonUtil.queueResourceColumnFormatter;
+ memoryValueFormatter = CommonUtil.absoluteUsedMemoryColumnFormatter;
+ cpuValueFormatter = CommonUtil.absoluteUsedCPUColumnFormatter;
constructor(
private scheduler: SchedulerService,
@@ -68,7 +73,7 @@ export class QueueV2Component implements OnInit {
.subscribe((data) => {
if (data && data.rootQueue) {
this.rootQueue = data.rootQueue;
- queueVisualization(this.rootQueue as QueueInfo)
+ queueVisualization(this.rootQueue as QueueInfo, this)
setTimeout(() => this.adjustToScreen(),1000) // since the
ngAfterViewInit hook is not working, we used setTimeout instead
}
});
@@ -78,10 +83,22 @@ export class QueueV2Component implements OnInit {
const fitButton = document.getElementById('fitButton');
fitButton?.click();
}
+
+ showQueueStats(status: string | undefined) {
+ console.log('sssss', status)
+ if(status !== 'Active'){
+ return '[Inactive]';
+ } else{
+ return null;
+ }
+ }
}
-function queueVisualization(rawData : QueueInfo){
+function queueVisualization(rawData : QueueInfo , componentInstance:
QueueV2Component){
let numberOfNode = 0;
+ let isShowingDetails = false;
+ let selectedNode: any = null;
+
const duration = 750;
const svg = select('.visualize-area').append('svg')
@@ -190,18 +207,21 @@ function queueVisualization(rawData : QueueInfo){
.attr("stroke", "white")
.attr("stroke-width", 2)
.attr("rx", 10)
- .attr("ry", 10);
+ .attr("ry", 10)
+ .attr("class", "cardMain");
group.append("rect")
.attr("width", 300)
.attr("height", 30)
- .attr("fill", "#d4eaf7");
+ .attr("fill", "#d4eaf7")
+ .attr("class", "cardTop");
group.append("rect")
.attr("y", 30)
.attr("width", 300)
.attr("height", 60)
- .attr("fill", "white");
+ .attr("fill", "white")
+ .attr("class", "cardMiddle");
group.append("rect")
.attr("y", 90)
@@ -232,7 +252,10 @@ function queueVisualization(rawData : QueueInfo){
.attr("stroke", "black")
.attr("stroke-width", 1)
.style("visibility", "hidden")
- .on('click', click);
+ .on('click', function(event) {
+ event.stopPropagation(); // Prevents the event from bubbling up to
parent elements
+ click(event, d);
+ });
const plusText = group.append("text")
.attr("x", 150)
@@ -248,6 +271,40 @@ function queueVisualization(rawData : QueueInfo){
plusCircle.style("visibility", "visible");
plusText.style("visibility", "visible");
});
+
+ group.on("click", function() {
+ if(selectedNode == this || selectedNode == null){
+ isShowingDetails = !isShowingDetails;
+ }else{
+ //set the previous selected node to its original css
+ select(selectedNode).select('.cardMain').attr("stroke", "white")
+ .attr("stroke-width", 2)
+
+ select(selectedNode).select('.cardTop').attr("fill", "#d4eaf7")
+ }
+
+ selectedNode = this;
+ componentInstance.seletedInfo = d.data;
+
+ if(isShowingDetails){
+ console.log("showing details", componentInstance.seletedInfo)
+ select(this).select('.cardMain').attr("stroke-width", 8)
+ .attr("stroke", "#50505c")
+
+ select(this).select('.cardTop').attr("fill", "#95d5f9")
+
+ select(".additional-info-element").style("display", "block");
+ } else {
+ select(this).select('.cardMain').attr("stroke-width", 2)
+ .attr("stroke", "white")
+
+ select(this).select('.cardTop').attr("fill", "#d4eaf7")
+
+ select(".additional-info-element").style("display", "none");
+ }
+
+ adjustVisulizeArea(duration)
+ });
// Hide the circle and '+' text when the mouse leaves the node
group.on("mouseout", function() {
diff --git a/src/app/components/queues-view/queues-view.component.ts
b/src/app/components/queues-view/queues-view.component.ts
index 253b37f..36d16f8 100644
--- a/src/app/components/queues-view/queues-view.component.ts
+++ b/src/app/components/queues-view/queues-view.component.ts
@@ -64,7 +64,7 @@ export class QueuesViewComponent implements OnInit {
{ level: 'level_07', next: 'level_08' },
{ level: 'level_08', next: 'level_09' },
];
- resourceValueFormatter = CommonUtil.resourceColumnFormatter;
+ resourceValueFormatter = CommonUtil.queueResourceColumnFormatter;
constructor(
private scheduler: SchedulerService,
diff --git a/src/app/utils/common.util.spec.ts
b/src/app/utils/common.util.spec.ts
index 831e057..076be59 100644
--- a/src/app/utils/common.util.spec.ts
+++ b/src/app/utils/common.util.spec.ts
@@ -58,4 +58,48 @@ describe('CommonUtil', () => {
expect(CommonUtil.formatOtherResource(inputs[index].toString())).toEqual(expected[index]);
}
});
+
+ describe('checkin absoluteUsedMemoryColumnFormatter method result', () => {
+ it('should return an empty string for undefined input', () => {
+ expect(CommonUtil.absoluteUsedMemoryColumnFormatter(undefined)).toBe('');
+ });
+
+ it('should return "n/a" for "n/a" input', () => {
+
expect(CommonUtil.absoluteUsedMemoryColumnFormatter('n/a')).toBe('<strong>Memory:</strong>
n/a');
+ });
+
+ it('should format memory percentage correctly', () => {
+ expect(CommonUtil.absoluteUsedMemoryColumnFormatter('Memory:
40%')).toBe('<strong>Memory:</strong> 40%');
+ });
+ });
+
+ describe('checkin absoluteUsedCPUColumnFormatter method result', () => {
+ it('should return an empty string for undefined input', () => {
+ expect(CommonUtil.absoluteUsedMemoryColumnFormatter(undefined)).toBe('');
+ });
+
+ it('should return "n/a" for "n/a" input', () => {
+
expect(CommonUtil.absoluteUsedCPUColumnFormatter('n/a')).toBe('<strong>CPU:</strong>
n/a');
+ });
+
+ it('should format memory percentage correctly', () => {
+ expect(CommonUtil.absoluteUsedMemoryColumnFormatter('CPU:
60%')).toBe('<strong>CPU:</strong> 60%');
+ });
+ });
+
+ describe('queueResourceColumnFormatter', () => {
+ it('should return an empty string for undefined input', () => {
+ expect(CommonUtil.queueResourceColumnFormatter(undefined)).toBe('');
+ });
+
+ it('should format multiple resources correctly', () => {
+ const input = 'Memory: 50%, CPU: 75%';
+ const expected = '<strong>Memory:</strong> 50%<br/><strong>CPU:</strong>
75%';
+ expect(CommonUtil.queueResourceColumnFormatter(input)).toBe(expected);
+ });
+
+ it('should handle "n/a" values correctly', () => {
+ expect(CommonUtil.queueResourceColumnFormatter('n/a')).toBe('n/a');
+ });
+ });
});
diff --git a/src/app/utils/common.util.ts b/src/app/utils/common.util.ts
index a5183c0..2a0d54c 100644
--- a/src/app/utils/common.util.ts
+++ b/src/app/utils/common.util.ts
@@ -81,10 +81,47 @@ export class CommonUtil {
return `${toValue.toLocaleString(undefined, { maximumFractionDigits: 2
})}${unit}`;
}
+ static absoluteUsedMemoryColumnFormatter(value: string | undefined): string {
+ if (!value) {
+ return '';
+ }
+ if (value === 'n/a') {
+ return '<strong>Memory:</strong> n/a';
+ }
+ let memory = value.split('%')[0] + '%';
+ return CommonUtil.queueResourceColumnFormatter(memory);
+ }
+
+ static absoluteUsedCPUColumnFormatter(value: string | undefined): string {
+ if (!value) {
+ return '';
+ }
+ if (value === 'n/a') {
+ return '<strong>CPU:</strong> n/a';
+ }
+ let cpu = value.split('%')[1] + '%';
+ cpu = cpu.replace(',', '');
+
+ return CommonUtil.queueResourceColumnFormatter(cpu);
+ }
+
static resourceColumnFormatter(value: string): string {
return value.split(', ').join('<br/>');
}
+ static queueResourceColumnFormatter(value: string | undefined): string {
+ if (!value) {
+ return '';
+ }
+ return value.split(', ').map(part => {
+ const [key, val] = part.split(': ');
+ if (key === 'n/a') {
+ return key;
+ }
+ return `<strong>${key}:</strong> ${val}`;
+ }).join('<br/>');
+ }
+
static formatPercent(value: number | string): string {
const toValue = +value;
return `${toValue.toFixed(0)}%`;
---------------------------------------------------------------------
To unsubscribe, e-mail: [email protected]
For additional commands, e-mail: [email protected]