From d4d8e0174a8ef8612d8ebd1d3be9246512e26e4c Mon Sep 17 00:00:00 2001 From: Matthew Fuller Date: Tue, 17 Nov 2020 00:04:32 +0000 Subject: [PATCH] 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 --- client/src/app/ctl/phase/phase.component.css | 5 +++ client/src/app/ctl/phase/phase.component.html | 26 ++++++++++++- client/src/app/ctl/phase/phase.component.ts | 37 +++++++++++++++++++ client/src/app/ctl/phase/phase.models.ts | 2 +- client/src/services/ws/ws.models.ts | 1 + pkg/configs/configs.go | 2 +- pkg/ctl/phase.go | 13 +++++++ pkg/ctl/tree.go | 37 +++++++------------ 8 files changed, 95 insertions(+), 28 deletions(-) diff --git a/client/src/app/ctl/phase/phase.component.css b/client/src/app/ctl/phase/phase.component.css index 65361f8..4b82b27 100644 --- a/client/src/app/ctl/phase/phase.component.css +++ b/client/src/app/ctl/phase/phase.component.css @@ -39,6 +39,11 @@ flex-direction: row; } +.unloaded-phase { + display: flex; + flex-direction: row; +} + .docless-phase-btn { padding-left: 20px; padding-right: 0px; diff --git a/client/src/app/ctl/phase/phase.component.html b/client/src/app/ctl/phase/phase.component.html index a1e2823..9f17552 100755 --- a/client/src/app/ctl/phase/phase.component.html +++ b/client/src/app/ctl/phase/phase.component.html @@ -20,9 +20,31 @@ -
+
+ + + + + + + + +
+
- diff --git a/client/src/app/ctl/phase/phase.component.ts b/client/src/app/ctl/phase/phase.component.ts index 1538722..c1520b5 100755 --- a/client/src/app/ctl/phase/phase.component.ts +++ b/client/src/app/ctl/phase/phase.component.ts @@ -44,6 +44,7 @@ export class PhaseComponent implements WsReceiver { activeLink = 'overview'; phaseTree: KustomNode[] = []; + clickedNode: KustomNode; treeControl = new NestedTreeControl(node => node.children); dataSource = new MatTreeNestedDataSource(); @@ -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(); + } + } } diff --git a/client/src/app/ctl/phase/phase.models.ts b/client/src/app/ctl/phase/phase.models.ts index 39365bd..2ebe666 100644 --- a/client/src/app/ctl/phase/phase.models.ts +++ b/client/src/app/ctl/phase/phase.models.ts @@ -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; diff --git a/client/src/services/ws/ws.models.ts b/client/src/services/ws/ws.models.ts index 2631809..52b5d4f 100755 --- a/client/src/services/ws/ws.models.ts +++ b/client/src/services/ws/ws.models.ts @@ -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'; diff --git a/pkg/configs/configs.go b/pkg/configs/configs.go index 7998391..2f2966b 100644 --- a/pkg/configs/configs.go +++ b/pkg/configs/configs.go @@ -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" diff --git a/pkg/ctl/phase.go b/pkg/ctl/phase.go index 1b7ae07..3184e2e 100644 --- a/pkg/ctl/phase.go +++ b/pkg/ctl/phase.go @@ -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 diff --git a/pkg/ctl/tree.go b/pkg/ctl/tree.go index dafbbde..7acb541 100644 --- a/pkg/ctl/tree.go +++ b/pkg/ctl/tree.go @@ -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 {