Lazy load phase tree in airshipui
Improves performance of the phase component in the UI by loading initial phase list, and only loading individual phase (kustomize) file trees when needed. Change-Id: I00b57021c182ff482ed0f0d341025e40d4b8ba3f
This commit is contained in:
parent
1de746fa77
commit
d4d8e0174a
@ -39,6 +39,11 @@
|
||||
flex-direction: row;
|
||||
}
|
||||
|
||||
.unloaded-phase {
|
||||
display: flex;
|
||||
flex-direction: row;
|
||||
}
|
||||
|
||||
.docless-phase-btn {
|
||||
padding-left: 20px;
|
||||
padding-right: 0px;
|
||||
|
@ -20,9 +20,31 @@
|
||||
<button *ngIf="node.hasError" mat-icon-button>
|
||||
<mat-icon class="error-icon" svgIcon="error"></mat-icon> {{node.name}}
|
||||
</button>
|
||||
<div *ngIf="node.isPhaseNode && !node.hasError" class="docless-phase">
|
||||
<div *ngIf="node.isPhaseNode && !node.hasError && node.hasDocuments" class="unloaded-phase">
|
||||
<button mat-button (click)="loadPhase(node)">
|
||||
<mat-icon class="mat-icon-rtl-mirror">chevron_right</mat-icon>{{node.name}}
|
||||
</button>
|
||||
<button class="menu-button" *ngIf="!node.running" mat-icon-button [matMenuTriggerFor]="menu">
|
||||
<mat-icon class="grey-icon" svgIcon="settings"></mat-icon>
|
||||
</button>
|
||||
<mat-spinner *ngIf="node.running" class="spinner" [diameter]="20"></mat-spinner>
|
||||
<mat-menu #menu="matMenu">
|
||||
<button mat-menu-item (click)="getPhase(node.phaseId)">
|
||||
<mat-icon class="grey-icon" svgIcon="open_in_new"></mat-icon>
|
||||
<span>View</span>
|
||||
</button>
|
||||
<button mat-menu-item (click)="validatePhase(node.phaseId)">
|
||||
<mat-icon>check_circle_icon</mat-icon>
|
||||
<span>Validate</span>
|
||||
</button>
|
||||
<button mat-menu-item (click)="confirmRunPhase(node)">
|
||||
<mat-icon>play_circle_outline</mat-icon>
|
||||
<span>Run</span>
|
||||
</button>
|
||||
</mat-menu>
|
||||
</div>
|
||||
<div *ngIf="node.isPhaseNode && !node.hasError && !node.hasDocuments" class="docless-phase">
|
||||
<button mat-button class="docless-phase-btn">{{node.name}}</button>
|
||||
<span class="spacer"></span>
|
||||
<button class="menu-button" *ngIf="!node.running" mat-icon-button [matMenuTriggerFor]="menu">
|
||||
<mat-icon class="grey-icon" svgIcon="settings"></mat-icon>
|
||||
</button>
|
||||
|
@ -44,6 +44,7 @@ export class PhaseComponent implements WsReceiver {
|
||||
activeLink = 'overview';
|
||||
|
||||
phaseTree: KustomNode[] = [];
|
||||
clickedNode: KustomNode;
|
||||
|
||||
treeControl = new NestedTreeControl<KustomNode>(node => node.children);
|
||||
dataSource = new MatTreeNestedDataSource<KustomNode>();
|
||||
@ -84,6 +85,9 @@ export class PhaseComponent implements WsReceiver {
|
||||
case WsConstants.GET_PHASE:
|
||||
this.handleGetPhase(message);
|
||||
break;
|
||||
case WsConstants.GET_PHASE_SOURCE_FILES:
|
||||
this.handleGetPhaseSourceFiles(message);
|
||||
break;
|
||||
case WsConstants.GET_YAML:
|
||||
this.handleGetYaml(message);
|
||||
break;
|
||||
@ -109,6 +113,13 @@ export class PhaseComponent implements WsReceiver {
|
||||
}
|
||||
}
|
||||
|
||||
handleGetPhaseSourceFiles(message: WsMessage): void {
|
||||
this.clickedNode.running = false;
|
||||
const data: KustomNode[] = [];
|
||||
Object.assign(data, message.data);
|
||||
this.updateTree(data);
|
||||
}
|
||||
|
||||
handleValidatePhase(message: WsMessage): void {
|
||||
this.websocketService.printIfToast(message);
|
||||
}
|
||||
@ -288,4 +299,30 @@ export class PhaseComponent implements WsReceiver {
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
getPhaseSourceFiles(node: KustomNode): void {
|
||||
const msg = new WsMessage(this.type, this.component, WsConstants.GET_PHASE_SOURCE_FILES);
|
||||
msg.id = JSON.stringify(node.phaseId);
|
||||
this.websocketService.sendMessage(msg);
|
||||
}
|
||||
|
||||
refreshTreeData(): void {
|
||||
const tmpdata = this.dataSource.data;
|
||||
this.dataSource.data = null;
|
||||
this.dataSource.data = tmpdata;
|
||||
}
|
||||
|
||||
loadPhase(node: KustomNode): void {
|
||||
this.clickedNode = node;
|
||||
this.clickedNode.running = true;
|
||||
this.getPhaseSourceFiles(this.clickedNode);
|
||||
}
|
||||
|
||||
updateTree(data: KustomNode[]): void {
|
||||
if (this.clickedNode !== undefined) {
|
||||
this.clickedNode.children = data;
|
||||
this.clickedNode.hasDocuments = false;
|
||||
this.refreshTreeData();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -16,7 +16,7 @@ export class KustomNode {
|
||||
id: string;
|
||||
phaseId: { Name: string, Namespace: string};
|
||||
name: string;
|
||||
canLoadChildren: boolean;
|
||||
hasDocuments: boolean;
|
||||
children: KustomNode[];
|
||||
isPhaseNode: boolean;
|
||||
running: boolean;
|
||||
|
@ -81,6 +81,7 @@ export class WsConstants {
|
||||
public static readonly GET_DOCUMENT_BY_SELECTOR = 'getDocumentsBySelector';
|
||||
public static readonly GET_EXECUTOR_DOC = 'getExecutorDoc';
|
||||
public static readonly GET_PHASE = 'getPhase';
|
||||
public static readonly GET_PHASE_SOURCE_FILES = 'getPhaseSourceFiles';
|
||||
public static readonly GET_PHASE_TREE = 'getPhaseTree';
|
||||
public static readonly GET_TARGET = 'getTarget';
|
||||
public static readonly GET_YAML = 'getYaml';
|
||||
|
@ -178,7 +178,7 @@ const (
|
||||
GetYaml WsSubComponentType = "getYaml"
|
||||
GetRendered WsSubComponentType = "getRendered"
|
||||
GetPhaseTree WsSubComponentType = "getPhaseTree"
|
||||
GetPhaseSourceFiles WsSubComponentType = "getPhaseSource"
|
||||
GetPhaseSourceFiles WsSubComponentType = "getPhaseSourceFiles"
|
||||
GetDocumentsBySelector WsSubComponentType = "getDocumentsBySelector"
|
||||
GetPhase WsSubComponentType = "getPhase"
|
||||
GetExecutorDoc WsSubComponentType = "getExecutorDoc"
|
||||
|
@ -89,6 +89,8 @@ func HandlePhaseRequest(user *string, request configs.WsMessage) configs.WsMessa
|
||||
s := "rendered"
|
||||
message = &s
|
||||
response.Name, response.YAML, err = client.GetExecutorDoc(id)
|
||||
case configs.GetPhaseSourceFiles:
|
||||
response.Data, err = client.getPhaseSource(id)
|
||||
default:
|
||||
err = fmt.Errorf("Subcomponent %s not found", request.SubComponent)
|
||||
}
|
||||
@ -103,6 +105,17 @@ func HandlePhaseRequest(user *string, request configs.WsMessage) configs.WsMessa
|
||||
return response
|
||||
}
|
||||
|
||||
func (c *Client) getPhaseSource(id string) ([]KustomNode, error) {
|
||||
phaseID := ifc.ID{}
|
||||
|
||||
err := json.Unmarshal([]byte(id), &phaseID)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return c.GetPhaseSourceFiles(phaseID)
|
||||
}
|
||||
|
||||
// this helper function will likely disappear once a clear workflow for
|
||||
// phase validation takes shape in UI. For now, it simply returns a
|
||||
// string message to be displayed as a toast in frontend client
|
||||
|
@ -70,26 +70,14 @@ func (client *Client) GetPhaseTree() ([]KustomNode, error) {
|
||||
|
||||
for _, p := range phases {
|
||||
pNode := KustomNode{
|
||||
ID: uuid.New().String(),
|
||||
PhaseID: ifc.ID{Name: p.Name, Namespace: p.Namespace},
|
||||
Name: fmt.Sprintf("Phase: %s", p.Name),
|
||||
IsPhaseNode: true,
|
||||
Children: []KustomNode{},
|
||||
ID: uuid.New().String(),
|
||||
PhaseID: ifc.ID{Name: p.Name, Namespace: p.Namespace},
|
||||
Name: fmt.Sprintf("Phase: %s", p.Name),
|
||||
IsPhaseNode: true,
|
||||
HasDocuments: p.Config.DocumentEntryPoint != "",
|
||||
Children: []KustomNode{},
|
||||
}
|
||||
|
||||
// some phases don't have any associated documents, so don't look
|
||||
// for children unless a DocumentEntryPoint has been specified
|
||||
if p.Config.DocumentEntryPoint != "" {
|
||||
children, err := client.GetPhaseSourceFiles(pNode.PhaseID)
|
||||
if err != nil {
|
||||
// TODO(mfuller): push an error to UI so it can be handled by
|
||||
// toastr service, pending refactor of webservice and configs pkgs
|
||||
log.Errorf("Error building tree for phase '%s': %s", p.Name, err)
|
||||
pNode.HasError = true
|
||||
} else {
|
||||
pNode.Children = children
|
||||
}
|
||||
}
|
||||
nodes = append(nodes, pNode)
|
||||
}
|
||||
|
||||
@ -181,12 +169,13 @@ func (client *Client) GetPhaseSourceFiles(id ifc.ID) ([]KustomNode, error) {
|
||||
// KustomNode structure to represent the kustomization tree for a given phase
|
||||
// bundle to be consumed by the UI frontend
|
||||
type KustomNode struct {
|
||||
ID string `json:"id"` // UUID for backend node index
|
||||
PhaseID ifc.ID `json:"phaseId"`
|
||||
Name string `json:"name"` // name used for display purposes (cli, ui)
|
||||
IsPhaseNode bool `json:"isPhaseNode"`
|
||||
HasError bool `json:"hasError"`
|
||||
Children []KustomNode `json:"children"`
|
||||
ID string `json:"id"`
|
||||
PhaseID ifc.ID `json:"phaseId"`
|
||||
Name string `json:"name"`
|
||||
IsPhaseNode bool `json:"isPhaseNode"`
|
||||
HasError bool `json:"hasError"`
|
||||
HasDocuments bool `json:"hasDocuments"`
|
||||
Children []KustomNode `json:"children"`
|
||||
}
|
||||
|
||||
func contains(dirs []string, val string) bool {
|
||||
|
Loading…
Reference in New Issue
Block a user