Refactor Tree Generator/Transformer.

Properly type all the tree utils.

Bug: b/246498545
Test: npm run test:all
Change-Id: I715e5b1cdc103fda228aac492e7d4c368601803c
This commit is contained in:
Priyanka Patel
2022-09-13 08:39:36 +00:00
parent 74d9c50466
commit d7e342220d
26 changed files with 866 additions and 813 deletions

View File

@@ -88,7 +88,7 @@ export default class ObjectFormatter {
* @param obj The raw object to format
* @return The formatted object
*/
static format(obj: any): {} {
static format(obj: any): any {
const properties = this.getProperties(obj);
const sortedProperties = properties.sort()

View File

@@ -0,0 +1,162 @@
/*
* Copyright (C) 2022 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
import { HierarchyTree } from "viewers/common/tree_utils";
import Chip from "viewers/common/chip";
class HierarchyTreeBuilder {
stableId = "";
name = "";
kind = "";
children: HierarchyTree[] = [];
shortName?: string;
type?: string;
id?: string | number;
layerId?: number;
displayId?: number;
stackId?: number;
isVisible?: boolean;
isMissing?: boolean;
hwcCompositionType?: number;
zOrderRelativeOfId?: number;
zOrderRelativeOf?: any;
zOrderRelativeParentOf?: any;
isRootLayer?: boolean;
showInFilteredView = true;
showInOnlyVisibleView?: boolean;
simplifyNames = false;
chips: Chip[] = [];
diffType?: string;
skip?: any;
setId(id: number) {
this.id = id;
return this;
}
setKind(kind: string) {
this.kind = kind;
return this;
}
setStableId(stableId: string) {
this.stableId = stableId;
return this;
}
setName(name: string) {
this.name = name;
return this;
}
setShortName(shortName: string) {
this.shortName = shortName;
return this;
}
setChips(chips: Chip[]) {
this.chips = chips;
return this;
}
setDiffType(diffType: string) {
this.diffType = diffType;
return this;
}
setChildren(children: HierarchyTree[]) {
this.children = children;
return this;
}
setDisplayId(displayId: number) {
this.displayId = displayId;
return this;
}
setLayerId(layerId: number) {
this.layerId = layerId;
return this;
}
setStackId(stackId: number) {
this.stackId = stackId;
return this;
}
setIsVisible(isVisible: boolean) {
this.isVisible = isVisible;
return this;
}
setVisibleView(showInOnlyVisibleView: boolean) {
this.showInOnlyVisibleView = showInOnlyVisibleView;
return this;
}
setFilteredView(showInFilteredView: boolean) {
this.showInFilteredView = showInFilteredView;
return this;
}
setSimplifyNames(simplifyNames: boolean) {
this.simplifyNames = simplifyNames;
return this;
}
build(): HierarchyTree {
const node = new HierarchyTree(this.name, this.kind, this.stableId, this.children);
node.chips = this.chips;
node.showInFilteredView = this.showInFilteredView;
node.simplifyNames = this.simplifyNames;
if (this.id) {
node.id = this.id;
}
if (this.diffType) {
node.diffType = this.diffType;
}
if (this.displayId) {
node.displayId = this.displayId;
}
if (this.layerId) {
node.layerId = this.layerId;
}
if (this.stackId) {
node.stackId = this.stackId;
}
if (this.isVisible) {
node.isVisible = this.isVisible;
}
if (this.showInOnlyVisibleView) {
node.showInOnlyVisibleView = this.showInOnlyVisibleView;
}
if (this.shortName) {
node.shortName = this.shortName;
}
return node;
}
}
export {HierarchyTreeBuilder};

View File

@@ -13,237 +13,105 @@
* See the License for the specific language governing permissions and
* limitations under the License.
*/
import { DiffType, getFilter } from "viewers/common/tree_utils";
import { DiffType, getFilter, HierarchyTree, TreeFlickerItem } from "viewers/common/tree_utils";
import { TreeGenerator } from "viewers/common/tree_generator";
import { HierarchyTreeBuilder } from "test/unit/hierarchy_tree_builder";
describe("TreeGenerator", () => {
it("generates tree", () => {
const tree = {
let entry: TreeFlickerItem;
beforeAll(async () => {
entry = {
kind: "entry",
name: "BaseLayerTraceEntry",
shortName: "BLTE",
stableId: "BaseLayerTraceEntry",
id: 0,
chips: [],
children: [{
kind: "3",
id: "3",
id: 3,
name: "Child1",
stableId: "3 Child1",
children: [
{
kind: "2",
id: "2",
id: 2,
name: "Child2",
stableId: "2 Child2",
children: []
}
]}]
};
const expected = {
simplifyNames: false,
name: "BaseLayerTraceEntry",
id: 0,
children: [
{
id: "3",
name: "Child1",
children: [{
kind: "2",
id: "2",
name: "Child2",
children: [],
simplifyNames: false,
showInFilteredView: true,
stableId: undefined,
shortName: undefined,
chips: []
}],
kind: "3",
simplifyNames: false,
showInFilteredView: true,
stableId: undefined,
shortName: undefined,
chips: [],
}
],
kind: "entry",
stableId: undefined,
shortName: "BLTE",
chips: [],
showInFilteredView: true,
};
});
it("generates tree", () => {
const expected: HierarchyTree = new HierarchyTreeBuilder().setName("BaseLayerTraceEntry").setKind("entry").setStableId("BaseLayerTraceEntry")
.setChildren([
new HierarchyTreeBuilder().setName("Child1").setStableId("3 Child1").setKind("3").setChildren([
new HierarchyTreeBuilder().setName("Child2").setStableId("2 Child2").setKind("2").setId(2).build()
]).setId(3).build()
]).setId(0).build();
const filter = getFilter("");
const generator = new TreeGenerator(tree, filter);
const generator = new TreeGenerator(entry, filter);
expect(generator.generateTree()).toEqual(expected);
});
it("generates diff tree with no diff", () => {
const tree = {
kind: "entry",
name: "BaseLayerTraceEntry",
shortName: "BLTE",
stableId: "0",
chips: [],
id: 0,
children: [{
kind: "3",
id: "3",
stableId: "3",
name: "Child1",
children: [
{
kind: "2",
id: "2",
stableId: "2",
name: "Child2",
}
]}]
};
const newTree = tree;
const expected = {
simplifyNames: false,
name: "BaseLayerTraceEntry",
id: 0,
stableId: "0",
children: [
{
id: "3",
stableId: "3",
name: "Child1",
children: [{
kind: "2",
id: "2",
name: "Child2",
children: [],
simplifyNames: false,
showInFilteredView: true,
stableId: "2",
shortName: undefined,
diffType: DiffType.NONE,
chips: []
}],
kind: "3",
shortName: undefined,
simplifyNames: false,
showInFilteredView: true,
chips: [],
diffType: DiffType.NONE
}
],
kind: "entry",
shortName: "BLTE",
chips: [],
diffType: DiffType.NONE,
showInFilteredView: true,
};
const expected: HierarchyTree = new HierarchyTreeBuilder().setName("BaseLayerTraceEntry").setKind("entry").setStableId("BaseLayerTraceEntry")
.setChildren([
new HierarchyTreeBuilder().setName("Child1").setStableId("3 Child1").setKind("3").setChildren([
new HierarchyTreeBuilder().setName("Child2").setStableId("2 Child2").setKind("2").setId(2).setDiffType(DiffType.NONE).build()
]).setId(3).setDiffType(DiffType.NONE).build()
]).setId(0).setDiffType(DiffType.NONE).build();
const filter = getFilter("");
const generator = new TreeGenerator(tree, filter);
expect(generator.withUniqueNodeId((node: any) => {
const tree = new TreeGenerator(entry, filter).withUniqueNodeId((node: any) => {
if (node) return node.stableId;
else return null;
}).compareWith(newTree).generateFinalDiffTree()).toEqual(expected);
}).compareWith(entry).generateFinalTreeWithDiff();
expect(tree).toEqual(expected);
});
it("generates diff tree with moved node", () => {
const tree = {
const prevEntry: TreeFlickerItem = {
kind: "entry",
name: "BaseLayerTraceEntry",
shortName: "BLTE",
stableId: "0",
chips: [],
id: 0,
children: [{
kind: "3",
id: "3",
stableId: "3",
name: "Child1",
children: [
{
kind: "2",
id: "2",
stableId: "2",
name: "Child2",
}
]}]
};
const newTree = {
kind: "entry",
name: "BaseLayerTraceEntry",
shortName: "BLTE",
stableId: "0",
stableId: "BaseLayerTraceEntry",
chips: [],
id: 0,
children: [
{
kind: "3",
id: "3",
stableId: "3",
id: 3,
stableId: "3 Child1",
name: "Child1",
children: []
},
{
kind: "2",
id: "2",
stableId: "2",
id: 2,
stableId: "2 Child2",
name: "Child2",
children: [],
}
]
};
const expected = {
simplifyNames: false,
name: "BaseLayerTraceEntry",
id: 0,
stableId: "0",
children: [
{
id: "3",
stableId: "3",
name: "Child1",
children: [ {
kind: "2",
id: "2",
name: "Child2",
children: [],
simplifyNames: false,
showInFilteredView: true,
stableId: "2",
shortName: undefined,
diffType: DiffType.ADDED_MOVE,
chips: []
}],
kind: "3",
shortName: undefined,
simplifyNames: false,
showInFilteredView: true,
chips: [],
diffType: DiffType.NONE
},
{
kind: "2",
id: "2",
name: "Child2",
children: [],
simplifyNames: false,
showInFilteredView: true,
stableId: "2",
shortName: undefined,
chips: [],
diffType: DiffType.DELETED_MOVE
}
],
kind: "entry",
shortName: "BLTE",
chips: [],
diffType: DiffType.NONE,
showInFilteredView: true
};
const expected: HierarchyTree = new HierarchyTreeBuilder().setName("BaseLayerTraceEntry").setKind("entry").setStableId("BaseLayerTraceEntry")
.setChildren([
new HierarchyTreeBuilder().setName("Child1").setStableId("3 Child1").setKind("3").setChildren([
new HierarchyTreeBuilder().setName("Child2").setStableId("2 Child2").setKind("2").setId(2).setDiffType(DiffType.ADDED_MOVE).build()
]).setId(3).setDiffType(DiffType.NONE).build(),
new HierarchyTreeBuilder().setName("Child2").setStableId("2 Child2").setKind("2").setId(2).setDiffType(DiffType.DELETED_MOVE).build()
]).setId(0).setDiffType(DiffType.NONE).build();
const filter = getFilter("");
const generator = new TreeGenerator(tree, filter);
const generator = new TreeGenerator(entry, filter);
const newDiffTree = generator.withUniqueNodeId((node: any) => {
if (node) return node.stableId;
else return null;
}).compareWith(newTree).generateFinalDiffTree();
}).compareWith(prevEntry).generateFinalTreeWithDiff();
expect(newDiffTree).toEqual(expected);
});
});

View File

@@ -15,10 +15,11 @@
*/
import {
FilterType,
Tree,
TreeFlickerItem,
DiffType,
isVisibleNode,
isParentNode
isParentNode,
HierarchyTree
} from "./tree_utils";
import ObjectFormatter from "common/trace/flickerlib/ObjectFormatter";
import {
@@ -30,11 +31,9 @@ import {
RELATIVE_Z_PARENT_CHIP
} from "viewers/common/chip";
type GetNodeIdCallbackType = (node: Tree | null) => number | null;
type IsModifiedCallbackType = (newTree: Tree | null, oldTree: Tree | null) => boolean;
interface IdNodeMap {
[key: string]: Tree
}
type GetNodeIdCallbackType = (node: TreeFlickerItem | null) => string | null;
type IsModifiedCallbackType = (newTree: TreeFlickerItem | null, oldTree: TreeFlickerItem | null) => boolean;
const HwcCompositionType = {
CLIENT: 1,
DEVICE: 2,
@@ -46,49 +45,49 @@ export class TreeGenerator {
private isSimplifyNames = false;
private isFlatView = false;
private filter: FilterType;
private tree: Tree;
private diffWithTree: Tree | null = null;
private inputEntry: TreeFlickerItem;
private previousEntry: TreeFlickerItem | null = null;
private getNodeId?: GetNodeIdCallbackType;
private isModified?: IsModifiedCallbackType;
private newMapping: IdNodeMap | null = null;
private oldMapping: IdNodeMap | null = null;
private newMapping: Map<string, TreeFlickerItem> | null = null;
private oldMapping: Map<string, TreeFlickerItem> | null = null;
private readonly pinnedIds: Array<string>;
private pinnedItems: Array<Tree> = [];
private relZParentIds: Array<number> = [];
private flattenedChildren: Array<Tree> = [];
private pinnedItems: Array<HierarchyTree> = [];
private relZParentIds: Array<string> = [];
private flattenedChildren: Array<HierarchyTree> = [];
constructor(tree: Tree, filter: FilterType, pinnedIds?: Array<string>) {
this.tree = tree;
constructor(inputEntry: TreeFlickerItem, filter: FilterType, pinnedIds?: Array<string>) {
this.inputEntry = inputEntry;
this.filter = filter;
this.pinnedIds = pinnedIds ?? [];
}
public setIsOnlyVisibleView(enabled: boolean) {
public setIsOnlyVisibleView(enabled: boolean): TreeGenerator {
this.isOnlyVisibleView = enabled;
return this;
}
public setIsSimplifyNames(enabled: boolean) {
public setIsSimplifyNames(enabled: boolean): TreeGenerator {
this.isSimplifyNames = enabled;
return this;
}
public setIsFlatView(enabled: boolean) {
public setIsFlatView(enabled: boolean): TreeGenerator {
this.isFlatView = enabled;
return this;
}
public generateTree(): Tree {
return this.getCustomisedTree(this.tree);
public generateTree(): HierarchyTree | null {
return this.getCustomisedTree(this.inputEntry);
}
public compareWith(tree: Tree | null): TreeGenerator {
this.diffWithTree = tree;
public compareWith(previousEntry: TreeFlickerItem | null): TreeGenerator {
this.previousEntry = previousEntry;
return this;
}
public withUniqueNodeId(getNodeId?: GetNodeIdCallbackType): TreeGenerator {
this.getNodeId = (node: Tree | null) => {
this.getNodeId = (node: TreeFlickerItem | null) => {
const id = getNodeId ? getNodeId(node) : this.defaultNodeIdCallback(node);
if (id === null || id === undefined) {
console.error("Null node ID for node", node);
@@ -104,11 +103,11 @@ export class TreeGenerator {
return this;
}
public generateFinalDiffTree(): Tree {
this.newMapping = this.generateIdToNodeMapping(this.tree);
this.oldMapping = this.diffWithTree ? this.generateIdToNodeMapping(this.diffWithTree) : null;
public generateFinalTreeWithDiff(): HierarchyTree | null {
this.newMapping = this.generateIdToNodeMapping(this.inputEntry);
this.oldMapping = this.previousEntry ? this.generateIdToNodeMapping(this.previousEntry) : null;
const diffTrees = this.generateDiffTree(this.tree, this.diffWithTree, [], []);
const diffTrees = this.generateDiffTree(this.inputEntry, this.previousEntry, [], []);
let diffTree;
if (diffTrees.length > 1) {
@@ -124,23 +123,24 @@ export class TreeGenerator {
return this.getCustomisedTree(diffTree);
}
private getCustomisedTree(tree: Tree | null) {
private getCustomisedTree(tree: TreeFlickerItem | null): HierarchyTree | null {
if (!tree) return null;
tree = this.generateTreeWithUserOptions(tree, false);
tree = this.updateTreeWithRelZParentChips(tree);
let newTree = this.generateTreeWithUserOptions(tree, false);
if (!newTree) return null;
newTree = this.updateTreeWithRelZParentChips(newTree);
if (this.isFlatView && tree.children) {
this.flattenChildren(tree.children);
tree.children = this.flattenedChildren;
if (this.isFlatView && newTree.children) {
this.flattenChildren(newTree.children);
newTree.children = this.flattenedChildren;
}
return Object.freeze(tree);
return Object.freeze(newTree);
}
public getPinnedItems() {
public getPinnedItems(): Array<HierarchyTree> {
return this.pinnedItems;
}
private flattenChildren(children: Array<Tree>): Tree {
private flattenChildren(children: Array<HierarchyTree>) {
for (let i = 0; i < children.length; i++) {
const child = children[i];
const childIsVisibleNode = child.isVisible && isVisibleNode(child.kind, child.type);
@@ -155,27 +155,26 @@ export class TreeGenerator {
}
}
private filterMatches(item: Tree | null): boolean {
private filterMatches(item: HierarchyTree | null): boolean {
return this.filter(item) ?? false;
}
private generateTreeWithUserOptions(
tree: Tree | null,
tree: TreeFlickerItem,
parentFilterMatch: boolean
): Tree | null {
return tree ? this.applyChecks(
): HierarchyTree | null {
return this.applyChecks(
tree,
this.cloneNode(tree, true),
parentFilterMatch
) : null;
);
}
private updateTreeWithRelZParentChips(tree: Tree): Tree {
private updateTreeWithRelZParentChips(tree: HierarchyTree): HierarchyTree {
return this.applyRelZParentCheck(tree);
}
private applyRelZParentCheck(tree: Tree) {
if (this.relZParentIds.includes(tree.id)) {
private applyRelZParentCheck(tree: HierarchyTree) {
if (tree.id && tree.chips && this.relZParentIds.includes(`${tree.id}`)) {
tree.chips.push(RELATIVE_Z_PARENT_CHIP);
}
@@ -187,7 +186,7 @@ export class TreeGenerator {
return tree;
}
private addChips(tree: Tree) {
private addChips(tree: HierarchyTree): HierarchyTree {
tree.chips = [];
if (tree.hwcCompositionType == HwcCompositionType.CLIENT) {
tree.chips.push(GPU_CHIP);
@@ -205,7 +204,7 @@ export class TreeGenerator {
&& !tree.isRootLayer
) {
tree.chips.push(RELATIVE_Z_CHIP);
this.relZParentIds.push(tree.zOrderRelativeOfId);
this.relZParentIds.push(`${tree.zOrderRelativeOfId}`);
}
if (tree.isMissing) {
tree.chips.push(MISSING_LAYER);
@@ -214,16 +213,13 @@ export class TreeGenerator {
}
private applyChecks(
tree: Tree | null,
newTree: Tree | null,
tree: TreeFlickerItem,
parentFilterMatch: boolean
): Tree | null {
if (!tree || !newTree) {
return null;
}
): HierarchyTree | null {
let newTree = this.getTreeNode(tree);
// add id field to tree if id does not exist (e.g. for WM traces)
if (!newTree.id && newTree.layerId) {
if (!newTree?.id && newTree?.layerId) {
newTree.id = newTree.layerId;
}
@@ -275,15 +271,15 @@ export class TreeGenerator {
return newTree;
}
private generateIdToNodeMapping(node: Tree, acc?: IdNodeMap): IdNodeMap {
acc = acc || {};
private generateIdToNodeMapping(node: TreeFlickerItem, acc?: Map<string, TreeFlickerItem>): Map<string, TreeFlickerItem> {
acc = acc || new Map<string, TreeFlickerItem>();
const nodeId = this.getNodeId!(node)!;
const nodeId: string = this.getNodeId!(node)!;
if (acc[nodeId]) {
if (acc.get(nodeId)) {
throw new Error(`Duplicate node id '${nodeId}' detected...`);
}
acc[nodeId] = node;
acc.set(nodeId, node);
if (node.children) {
for (const child of node.children) {
@@ -293,7 +289,7 @@ export class TreeGenerator {
return acc;
}
private cloneNode(node: Tree | null, postDiff = false): Tree | null {
private cloneDiffTreeNode(node: TreeFlickerItem | null): TreeFlickerItem | null {
const clone = ObjectFormatter.cloneObject(node);
if (node) {
clone.children = node.children;
@@ -301,22 +297,41 @@ export class TreeGenerator {
clone.kind = node.kind;
clone.stableId = node.stableId;
clone.shortName = node.shortName;
if ("chips" in node) {
if (node.chips) {
clone.chips = node.chips.slice();
}
if (postDiff && "diffType" in node) {
clone.diffType = node.diffType;
}
}
return clone;
}
private getTreeNode(node: TreeFlickerItem): HierarchyTree {
const clone = new HierarchyTree(
node.name,
node.kind,
node.stableId,
);
if (node.shortName) clone.shortName = node.shortName;
if (node.type) clone.type = node.type;
if (node.id) clone.id = node.id;
if (node.layerId) clone.layerId = node.layerId;
if (node.isVisible) clone.isVisible = node.isVisible;
if (node.isMissing) clone.isMissing = node.isMissing;
if (node.hwcCompositionType) clone.hwcCompositionType = node.hwcCompositionType;
if (node.zOrderRelativeOfId) clone.zOrderRelativeOfId = node.zOrderRelativeOfId;
if (node.isRootLayer) clone.isRootLayer = node.isRootLayer;
if (node.chips) clone.chips = node.chips.slice();
if (node.diffType) clone.diffType = node.diffType;
if (node.skip) clone.skip = node.skip;
return clone;
}
private generateDiffTree(
newTree: Tree | null,
oldTree: Tree | null,
newTreeSiblings: Array<Tree | null>,
oldTreeSiblings: Array<Tree | null>
): Array<Tree | null> {
newTree: TreeFlickerItem | null,
oldTree: TreeFlickerItem | null,
newTreeSiblings: Array<TreeFlickerItem | null>,
oldTreeSiblings: Array<TreeFlickerItem | null>
): Array<TreeFlickerItem> {
const diffTrees = [];
// NOTE: A null ID represents a non existent node.
if (!this.getNodeId) {
@@ -330,24 +345,24 @@ export class TreeGenerator {
if (newTree) {
// Clone is required because trees are frozen objects — we can't modify the original tree object.
const diffTree = this.cloneNode(newTree)!;
const diffTree = this.cloneDiffTreeNode(newTree)!;
// Default to no changes
diffTree.diffType = DiffType.NONE;
if (!isParentNode(newTree.kind) && newId !== oldId) {
// A move, addition, or deletion has occurred
let nextOldTree = null;
let nextOldTree: TreeFlickerItem | null = null;
// Check if newTree has been added or moved
if (newId && !oldTreeSiblingIds.includes(newId)) {
if (this.oldMapping && this.oldMapping[newId]) {
if (this.oldMapping && this.oldMapping.get(newId)) {
// Objected existed in old tree, so DELETED_MOVE will be/has been flagged and added to the
// diffTree when visiting it in the oldTree.
diffTree.diffType = DiffType.ADDED_MOVE;
// Switch out oldTree for new one to compare against
nextOldTree = this.oldMapping[newId];
nextOldTree = this.oldMapping.get(newId) ?? null;
} else {
diffTree.diffType = DiffType.ADDED;
@@ -358,9 +373,9 @@ export class TreeGenerator {
// Check if oldTree has been deleted of moved
if (oldId && oldTree && !newTreeSiblingIds.includes(oldId)) {
const deletedTreeDiff = this.cloneNode(oldTree)!;
const deletedTreeDiff = this.cloneDiffTreeNode(oldTree)!;
if (this.newMapping![oldId]) {
if (this.newMapping && this.newMapping.get(oldId)) {
deletedTreeDiff.diffType = DiffType.DELETED_MOVE;
// Stop comparing against oldTree, will be/has been
@@ -388,10 +403,10 @@ export class TreeGenerator {
} else if (oldTree) {
if (oldId && !newTreeSiblingIds.includes(oldId)) {
// Deep clone oldTree omitting children field
const diffTree = this.cloneNode(oldTree)!;
const diffTree = this.cloneDiffTreeNode(oldTree)!;
// newTree doesn't exist, oldTree has either been moved or deleted.
if (this.newMapping![oldId]) {
if (this.newMapping && this.newMapping.get(oldId)) {
diffTree.diffType = DiffType.DELETED_MOVE;
} else {
diffTree.diffType = DiffType.DELETED;
@@ -407,39 +422,38 @@ export class TreeGenerator {
return diffTrees;
}
private visitChildren(newTree: Tree | null, oldTree: Tree | null) {
private visitChildren(newTree: TreeFlickerItem | null, oldTree: TreeFlickerItem | null): Array<TreeFlickerItem> {
// Recursively traverse all children of new and old tree.
const diffChildren = [];
if (!newTree) newTree = {};
if (!oldTree) oldTree = {};
const numOfChildren = Math.max(newTree.children?.length ?? 0, oldTree.children?.length ?? 0);
const numOfChildren = Math.max(newTree?.children?.length ?? 0, oldTree?.children?.length ?? 0);
for (let i = 0; i < numOfChildren; i++) {
const newChild = newTree.children ? newTree.children[i] : null;
const oldChild = oldTree.children ? oldTree.children[i] : null;
const newChild = newTree?.children ? newTree.children[i] : null;
const oldChild = oldTree?.children ? oldTree.children[i] : null;
const childDiffTrees = this.generateDiffTree(
newChild, oldChild,
newTree.children ?? [], oldTree.children ?? [],
);
newTree?.children ?? [], oldTree?.children ?? [],
).filter(tree => tree != null);
diffChildren.push(...childDiffTrees);
}
return diffChildren;
}
private defaultNodeIdCallback(node: Tree | null): number | null {
private defaultNodeIdCallback(node: TreeFlickerItem | null): string | null {
return node ? node.stableId : null;
}
private defaultModifiedCheck(newNode: Tree | null, oldNode: Tree | null): boolean {
private defaultModifiedCheck(newNode: TreeFlickerItem | null, oldNode: TreeFlickerItem | null): boolean {
if (!newNode && !oldNode) {
return false;
} else if (newNode && isParentNode(newNode.kind)) {
return false;
} else if ((newNode && !oldNode) || (!newNode && oldNode)) {
return true;
} else if (newNode?.equals) {
return !newNode.equals(oldNode);
}
return !newNode.equals(oldNode);
return false;
}
}

View File

@@ -14,21 +14,19 @@
* limitations under the License.
*/
import { TreeTransformer } from "viewers/common/tree_transformer";
import { DiffType, getFilter, Terminal } from "viewers/common/tree_utils";
import { DiffType, getFilter, HierarchyTree, Terminal, TreeFlickerItem } from "viewers/common/tree_utils";
describe("TreeTransformer", () => {
it("creates ordinary properties tree without show diff enabled", () => {
const selectedTree = {
id: "3",
let entry: TreeFlickerItem;
let selectedTree: HierarchyTree;
beforeAll(async () => {
entry = {
id: 3,
name: "Child1",
stackId: 0,
isVisible: true,
kind: "3",
stableId: "3 Child1",
shortName: undefined,
simplifyNames: true,
showInFilteredView: true,
skip: null,
proto: {
barrierLayer: [],
id: 3,
@@ -37,14 +35,12 @@ describe("TreeTransformer", () => {
},
chips: [],
children: [{
id: "2",
id: 2,
name: "Child2",
stackId: 0,
children: [],
kind: "2",
stableId: "2 Child2",
shortName: undefined,
simplifyNames: true,
proto: {
barrierLayer: [],
id: 2,
@@ -52,10 +48,34 @@ describe("TreeTransformer", () => {
type: "ContainerLayer",
},
isVisible: true,
}],
};
selectedTree = {
id: 3,
name: "Child1",
stackId: 0,
isVisible: true,
kind: "3",
stableId: "3 Child1",
showInFilteredView: true,
skip: null,
chips: [],
children: [{
id: 2,
name: "Child2",
stackId: 0,
children: [],
kind: "2",
stableId: "2 Child2",
isVisible: true,
showInFilteredView: true,
chips: [],
}],
};
});
it("creates ordinary properties tree without show diff enabled", () => {
const expected = {
kind: "",
name: "Child1",
@@ -63,88 +83,37 @@ describe("TreeTransformer", () => {
children: [
{
kind: "",
name: "proto",
stableId: "3 Child1.proto",
children: [
{
kind: "",
name: "id: empty",
stableId: "3 Child1.proto.id",
children: [],
combined: true,
propertyKey: "id",
propertyValue: "empty"},
{
kind: "",
name: "type: ContainerLayer",
stableId: "3 Child1.proto.type",
children: [],
combined: true,
propertyKey: "type",
propertyValue: "ContainerLayer"
}
],
propertyKey: "proto",
propertyValue: null,
name: "id: empty",
stableId: "3 Child1.id",
children: [],
combined: true,
propertyKey: "id",
propertyValue: "empty"
},
{
kind: "",
name: new Terminal(),
stableId: "3 Child1.null",
children: []
}
name: "type: ContainerLayer",
stableId: "3 Child1.type",
children: [],
combined: true,
propertyKey: "type",
propertyValue: "ContainerLayer"
},
],
propertyKey: "Child1",
propertyValue: null
};
const filter = getFilter("");
const transformer = new TreeTransformer(selectedTree, filter);
const transformer = new TreeTransformer(selectedTree, filter)
.showOnlyProtoDump()
.setProperties(entry);
const transformedTree = transformer.transform();
expect(transformedTree).toEqual(expected);
});
it("creates properties tree with show diff enabled, comparing to a null previous entry", () => {
const selectedTree = {
id: "3",
name: "Child1",
stackId: 0,
isVisible: true,
kind: "3",
stableId: "3 Child1",
shortName: undefined,
simplifyNames: true,
showInFilteredView: true,
skip: null,
proto: {
barrierLayer: [],
id: 3,
parent: 1,
type: "ContainerLayer",
},
chips: [],
children: [{
id: "2",
name: "Child2",
stackId: 0,
children: [],
kind: "2",
stableId: "2 Child2",
shortName: undefined,
simplifyNames: true,
proto: {
barrierLayer: [],
id: 2,
parent: 3,
type: "ContainerLayer",
},
isVisible: true,
showInFilteredView: true,
chips: [],
}],
};
const expected = {
kind: "",
name: "Child1",
@@ -152,34 +121,24 @@ describe("TreeTransformer", () => {
children: [
{
kind: "",
name: "proto",
stableId: "3 Child1.proto",
children: [
{
kind: "",
name: "id: empty",
stableId: "3 Child1.proto.id",
children: [],
combined: true,
diffType: DiffType.ADDED,
propertyKey: "id",
propertyValue: "empty",
},
{
kind: "",
name: "type: ContainerLayer",
stableId: "3 Child1.proto.type",
children: [],
combined: true,
diffType: DiffType.ADDED,
propertyKey: "type",
propertyValue: "ContainerLayer",
}
],
name: "id: empty",
diffType: DiffType.ADDED,
propertyKey: "proto",
propertyValue: null,
}
stableId: "3 Child1.id",
children: [],
combined: true,
propertyKey: "id",
propertyValue: "empty"
},
{
kind: "",
name: "type: ContainerLayer",
diffType: DiffType.ADDED,
stableId: "3 Child1.type",
children: [],
combined: true,
propertyKey: "type",
propertyValue: "ContainerLayer"
},
],
diffType: DiffType.NONE,
propertyKey: "Child1",
@@ -189,6 +148,8 @@ describe("TreeTransformer", () => {
const filter = getFilter("");
const transformer = new TreeTransformer(selectedTree, filter)
.setIsShowDiff(true)
.showOnlyProtoDump()
.setProperties(entry)
.setDiffProperties(null);
const transformedTree = transformer.transform();

View File

@@ -18,9 +18,11 @@ import ObjectFormatter from "common/trace/flickerlib/ObjectFormatter";
import {
FilterType,
PropertiesTree,
Tree,
DiffType,
Terminal
Terminal,
TreeFlickerItem,
HierarchyTree,
PropertiesDump
} from "./tree_utils";
interface TransformOptions {
@@ -32,10 +34,6 @@ interface TreeTransformerOptions {
skip?: any;
formatter?: any;
}
interface TransformedPropertiesObject {
properties: any;
diffType?: string;
}
export class TreeTransformer {
private stableId: string;
@@ -43,32 +41,37 @@ export class TreeTransformer {
private isShowDefaults = false;
private isShowDiff = false;
private filter: FilterType;
private properties: PropertiesTree;
private compareWithProperties: PropertiesTree | null = null;
private properties: PropertiesDump | Terminal | null = null;
private compareWithProperties: PropertiesDump | Terminal | null = null;
private options?: TreeTransformerOptions;
private onlyProtoDump = false;
private transformOptions: TransformOptions = {
keepOriginal: false, freeze: true, metadataKey: null,
};
constructor(tree: Tree, filter: FilterType) {
this.stableId = this.compatibleStableId(tree);
this.rootName = tree.name;
constructor(selectedTree: HierarchyTree, filter: FilterType) {
this.stableId = this.compatibleStableId(selectedTree);
this.rootName = selectedTree.name;
this.filter = filter;
this.setProperties(tree);
this.setTransformerOptions({});
}
public setIsShowDefaults(enabled: boolean) {
public showOnlyProtoDump(): TreeTransformer {
this.onlyProtoDump = true;
return this;
}
public setIsShowDefaults(enabled: boolean): TreeTransformer {
this.isShowDefaults = enabled;
return this;
}
public setIsShowDiff(enabled: boolean) {
public setIsShowDiff(enabled: boolean): TreeTransformer {
this.isShowDiff = enabled;
return this;
}
public setTransformerOptions(options: TreeTransformerOptions) {
public setTransformerOptions(options: TreeTransformerOptions): TreeTransformer {
this.options = options;
if (!this.options.formatter) {
this.options.formatter = this.formatProto;
@@ -76,66 +79,97 @@ export class TreeTransformer {
return this;
}
public setProperties(tree: Tree) {
const target = tree.obj ?? tree;
public setProperties(currentEntry: TreeFlickerItem): TreeTransformer {
const currFlickerItem = this.getOriginalFlickerItem(currentEntry, this.stableId);
const target = currFlickerItem ? currFlickerItem.obj ?? currFlickerItem : null;
ObjectFormatter.displayDefaults = this.isShowDefaults;
this.properties = this.getPropertiesForDisplay(target);
this.properties = this.onlyProtoDump ? this.getProtoDumpPropertiesForDisplay(target) : this.getPropertiesForDisplay(target);
return this;
}
public setDiffProperties(previousEntry: any) {
public setDiffProperties(previousEntry: TreeFlickerItem | null): TreeTransformer {
if (this.isShowDiff) {
const tree = this.findTree(previousEntry, this.stableId);
const target = tree ? tree.obj ?? tree : null;
this.compareWithProperties = this.getPropertiesForDisplay(target);
const prevFlickerItem = this.findFlickerItem(previousEntry, this.stableId);
const target = prevFlickerItem ? prevFlickerItem.obj ?? prevFlickerItem : null;
this.compareWithProperties = this.onlyProtoDump ? this.getProtoDumpPropertiesForDisplay(target) : this.getPropertiesForDisplay(target);
}
return this;
}
public getOriginalLayer(entry: any, stableId: string) {
return this.findTree(entry, stableId);
public getOriginalFlickerItem(entry: TreeFlickerItem, stableId: string): TreeFlickerItem | null {
return this.findFlickerItem(entry, stableId);
}
private getPropertiesForDisplay(entry: any): any {
private getProtoDumpPropertiesForDisplay(entry: TreeFlickerItem): PropertiesDump | null {
if (!entry) {
return;
return null;
}
let obj: any = {};
obj.proto = Object.assign({}, entry.proto);
if (obj.proto.children) delete obj.proto.children;
if (obj.proto.childWindows) delete obj.proto.childWindows;
if (obj.proto.childrenWindows) delete obj.proto.childrenWindows;
if (obj.proto.childContainers) delete obj.proto.childContainers;
if (obj.proto.windowToken) delete obj.proto.windowToken;
if (obj.proto.rootDisplayArea) delete obj.proto.rootDisplayArea;
if (obj.proto.rootWindowContainer) delete obj.proto.rootWindowContainer;
if (obj.proto.windowContainer?.children) delete obj.proto.windowContainer.children;
const obj: PropertiesDump = {};
const proto = ObjectFormatter.format(entry.proto);
if (proto) {
Object.keys(proto).forEach((prop: string) => {
obj[prop] = proto[prop] ?? "empty";
obj = ObjectFormatter.format(obj);
if (Object.keys(obj[prop]).length === 0) {
obj[prop]= "empty";
}
});
}
Object.keys(obj.proto).forEach((prop: string) => {
if (Object.keys(obj.proto[prop]).length === 0) {
obj.proto[prop] = "empty";
}
});
return obj;
}
private findTree(tree: any, stableId: string) {
if (!tree) {
private getPropertiesForDisplay(entry: TreeFlickerItem): PropertiesDump | null {
if (!entry) {
return null;
}
if (tree.stableId && tree.stableId === stableId) {
return tree;
}
let obj: PropertiesDump = {};
if (!tree.children) {
const properties = ObjectFormatter.getProperties(entry);
properties.forEach(prop => {
if (entry.get) obj[prop] = entry.get(prop);
});
if (obj["children"]) delete obj["children"];
if (obj["proto"]) delete obj["proto"];
obj["proto"] = Object.assign({}, entry.proto);
if (obj["proto"].children) delete obj["proto"].children;
if (obj["proto"].childWindows) delete obj["proto"].childWindows;
if (obj["proto"].childrenWindows) delete obj["proto"].childrenWindows;
if (obj["proto"].childContainers) delete obj["proto"].childContainers;
if (obj["proto"].windowToken) delete obj["proto"].windowToken;
if (obj["proto"].rootDisplayArea) delete obj["proto"].rootDisplayArea;
if (obj["proto"].rootWindowContainer) delete obj["proto"].rootWindowContainer;
if (obj["proto"].windowContainer?.children) delete obj["proto"].windowContainer.children;
obj = ObjectFormatter.format(obj);
Object.keys(obj["proto"]).forEach((prop: string) => {
if (Object.keys(obj["proto"][prop]).length === 0) {
obj["proto"][prop] = "empty";
}
});
return obj;
}
private findFlickerItem(entryFlickerItem: TreeFlickerItem | null, stableId: string): TreeFlickerItem | null {
if (!entryFlickerItem) {
return null;
}
for (const child of tree.children) {
const foundEntry: any = this.findTree(child, stableId);
if (entryFlickerItem.stableId && entryFlickerItem.stableId === stableId) {
return entryFlickerItem;
}
if (!entryFlickerItem.children) {
return null;
}
for (const child of entryFlickerItem.children) {
const foundEntry: any = this.findFlickerItem(child, stableId);
if (foundEntry) {
return foundEntry;
}
@@ -145,7 +179,7 @@ export class TreeTransformer {
}
public transform() {
public transform(): PropertiesTree {
const {formatter} = this.options!;
if (!formatter) {
throw new Error("Missing formatter, please set with setOptions()");
@@ -154,18 +188,17 @@ export class TreeTransformer {
const transformedTree = this.transformTree(this.properties, this.rootName,
this.compareWithProperties, this.rootName,
this.stableId, this.transformOptions);
return transformedTree;
}
private transformTree(
properties: PropertiesTree | Terminal,
properties: PropertiesDump | null | Terminal,
name: string | Terminal,
compareWithProperties: PropertiesTree | Terminal,
compareWithProperties: PropertiesDump | null | Terminal,
compareWithName: string | Terminal,
stableId: string,
transformOptions: TransformOptions,
) {
): PropertiesTree {
const originalProperties = properties;
const metadata = this.getMetadata(
originalProperties, transformOptions.metadataKey
@@ -173,12 +206,12 @@ export class TreeTransformer {
const children: any[] = [];
if (!this.isTerminal(properties)) {
if (properties && !this.isTerminal(properties)) {
const transformedProperties = this.transformProperties(properties, transformOptions.metadataKey);
properties = transformedProperties.properties;
}
if (!this.isTerminal(compareWithProperties)) {
if (compareWithProperties && !this.isTerminal(compareWithProperties)) {
const transformedProperties = this.transformProperties(
compareWithProperties,
transformOptions.metadataKey
@@ -187,10 +220,10 @@ export class TreeTransformer {
}
for (const key in properties) {
if (properties[key]) {
if (!(properties instanceof Terminal) && properties[key]) {
let compareWithChild = new Terminal();
let compareWithChildName = new Terminal();
if (compareWithProperties[key]) {
if (compareWithProperties && !(compareWithProperties instanceof Terminal) && compareWithProperties[key]) {
compareWithChild = compareWithProperties[key];
compareWithChildName = key;
}
@@ -204,7 +237,8 @@ export class TreeTransformer {
// Takes care of adding deleted items to final tree
for (const key in compareWithProperties) {
if (!properties[key] && compareWithProperties[key]) {
if (properties && !(properties instanceof Terminal) && !properties[key] &&
!(compareWithProperties instanceof Terminal) && compareWithProperties[key]) {
const child = this.transformTree(new Terminal(), new Terminal(),
compareWithProperties[key], key,
`${stableId}.${key}`, transformOptions);
@@ -268,11 +302,10 @@ export class TreeTransformer {
!this.hasChildMatchingFilter(transformedProperties?.children)) {
transformedProperties.propertyKey = new Terminal();
}
return transformOptions.freeze ? Object.freeze(transformedProperties) : transformedProperties;
}
private hasChildMatchingFilter(children: PropertiesTree[] | null | undefined) {
private hasChildMatchingFilter(children: PropertiesTree[] | null | undefined): boolean {
if (!children || children.length === 0) return false;
let match = false;
@@ -285,8 +318,11 @@ export class TreeTransformer {
return match;
}
private getMetadata(obj: PropertiesTree, metadataKey: string | null) {
if (metadataKey && obj[metadataKey]) {
private getMetadata(obj: PropertiesDump | null | Terminal, metadataKey: string | null): any {
if (obj == null) {
return null;
}
if (metadataKey && !(obj instanceof Terminal) && obj[metadataKey]) {
const metadata = obj[metadataKey];
obj[metadataKey] = undefined;
return metadata;
@@ -295,28 +331,28 @@ export class TreeTransformer {
}
}
private getPropertyKey(item: PropertiesTree) {
if (!item.children || item.children.length === 0) {
return item.name.split(": ")[0];
private getPropertyKey(item: PropertiesDump): string {
if (item["name"] && (!item["children"] || item["children"].length === 0)) {
return item["name"].split(": ")[0];
}
return item.name;
return item["name"];
}
private getPropertyValue(item: PropertiesTree) {
if (!item.children || item.children.length === 0) {
return item.name.split(": ").slice(1).join(": ");
private getPropertyValue(item: PropertiesDump): string | null {
if (item["name"] && (!item["children"] || item["children"].length === 0)) {
return item["name"].split(": ").slice(1).join(": ");
}
return null;
}
private filterMatches(item: PropertiesTree | null): boolean {
private filterMatches(item: PropertiesDump | null): boolean {
return this.filter(item) ?? false;
}
private transformProperties(properties: PropertiesTree, metadataKey: string | null) {
private transformProperties(properties: PropertiesDump, metadataKey: string | null): PropertiesTree {
const {skip, formatter} = this.options!;
const transformedProperties: TransformedPropertiesObject = {
const transformedProperties: PropertiesTree = {
properties: {},
};
let formatted = undefined;
@@ -351,7 +387,7 @@ export class TreeTransformer {
return transformedProperties;
}
private getDiff(val: string | Terminal, compareVal: string | Terminal) {
private getDiff(val: string | Terminal, compareVal: string | Terminal): string {
if (val && this.isTerminal(compareVal)) {
return DiffType.ADDED;
} else if (this.isTerminal(val) && compareVal) {
@@ -363,7 +399,7 @@ export class TreeTransformer {
}
}
private compatibleStableId(item: Tree) {
private compatibleStableId(item: HierarchyTree): string {
// For backwards compatibility
// (the only item that doesn't have a unique stable ID in the tree)
if (item.stableId === "winToken|-|") {
@@ -378,7 +414,7 @@ export class TreeTransformer {
}
}
private isTerminal(item: any) {
private isTerminal(item: any): boolean {
return item instanceof Terminal;
}
}

View File

@@ -13,12 +13,87 @@
* See the License for the specific language governing permissions and
* limitations under the License.
*/
import { Layer, BaseLayerTraceEntry } from "common/trace/flickerlib/common";
export type FilterType = (item: Tree | null) => boolean;
export type Tree = Layer | BaseLayerTraceEntry;
export type PropertiesTree = any; //TODO: make specific
export type TreeSummary = Array<{key: string, value: string}>
import Chip from "./chip";
export type FilterType = (item: HierarchyTree | PropertiesTree | null) => boolean;
export type Tree = HierarchyTree | PropertiesTree;
export class HierarchyTree {
constructor(
public name: string,
public kind: string,
public stableId: string,
children?: HierarchyTree[]
) {
this.children = children ?? [];
}
children: HierarchyTree[];
shortName?: string;
type?: string;
id?: string | number;
layerId?: number;
displayId?: number;
stackId?: number;
isVisible?: boolean;
isMissing?: boolean;
hwcCompositionType?: number;
zOrderRelativeOfId?: number;
zOrderRelativeOf?: any;
zOrderRelativeParentOf?: any;
isRootLayer?: boolean;
showInFilteredView?: boolean;
showInOnlyVisibleView?: boolean;
simplifyNames?: boolean;
chips?: Chip[] = [];
diffType?: string;
skip?: any;
}
export interface TreeFlickerItem {
children: TreeFlickerItem[];
name: string;
kind: string;
stableId: string;
displays?: TreeFlickerItem[];
windowStates?: TreeFlickerItem[];
shortName?: string;
type?: string;
id?: string | number;
layerId?: number;
displayId?: number;
stackId?: number;
isVisible?: boolean;
isMissing?: boolean;
hwcCompositionType?: number;
zOrderRelativeOfId?: number;
isRootLayer?: boolean;
chips?: Chip[];
diffType?: string;
skip?: any;
equals?: any;
obj?: any;
get?: any;
proto?: any;
}
export interface PropertiesDump {
[key: string]: any;
}
export interface PropertiesTree {
properties?: any;
kind?: string;
stableId?: string;
children?: PropertiesTree[];
propertyKey?: string | Terminal | null;
propertyValue?: string | Terminal | null;
name?: string | Terminal;
diffType?: string;
combined?: boolean;
} //TODO: make specific
export const DiffType = {
NONE: "none",
@@ -32,18 +107,18 @@ export const DiffType = {
export class Terminal {}
export function diffClass(item: Tree): string {
const diffType = item!.diffType;
const diffType = item.diffType;
return diffType ?? "";
}
export function isHighlighted(item: Tree, highlightedItems: Array<string>) {
return highlightedItems.includes(`${item.id}`);
return item instanceof HierarchyTree && highlightedItems.includes(`${item.id}`);
}
export function getFilter(filterString: string): FilterType {
const filterStrings = filterString.split(",");
const positive: Tree | null[] = [];
const negative: Tree | null[] = [];
const positive: any[] = [];
const negative: any[] = [];
filterStrings.forEach((f) => {
f = f.trim();
if (f.startsWith("!")) {

View File

@@ -22,6 +22,8 @@ import { MatInputModule } from "@angular/material/input";
import { MatFormFieldModule } from "@angular/material/form-field";
import { MatCheckboxModule } from "@angular/material/checkbox";
import { BrowserAnimationsModule } from "@angular/platform-browser/animations";
import { HierarchyTree } from "viewers/common/tree_utils";
import { HierarchyTreeBuilder } from "test/unit/hierarchy_tree_builder";
describe("HierarchyComponent", () => {
let fixture: ComponentFixture<HierarchyComponent>;
@@ -51,14 +53,11 @@ describe("HierarchyComponent", () => {
fixture = TestBed.createComponent(HierarchyComponent);
component = fixture.componentInstance;
htmlElement = fixture.nativeElement;
component.tree = {
simplifyNames: false,
kind: "entry",
name: "BaseLayerTraceEntry",
shortName: "BLTE",
chips: [],
children: [{kind: "3", id: "3", name: "Child1"}]
};
component.tree = new HierarchyTreeBuilder().setName("BaseLayerTraceEntry").setKind("entry").setStableId("BaseEntry")
.setChildren([new HierarchyTreeBuilder().setName("Child1").setStableId("3 Child1").build()])
.build();
component.store = new PersistentStore();
component.userOptions = {
onlyVisible: {
@@ -66,14 +65,7 @@ describe("HierarchyComponent", () => {
enabled: false
},
};
component.pinnedItems = [{
simplifyNames: false,
kind: "entry",
name: "BaseLayerTraceEntry",
shortName: "BLTE",
chips: [],
children: [{kind: "3", id: "3", name: "Child1"}]
}];
component.pinnedItems = [component.tree];
component.diffClass = jasmine.createSpy().and.returnValue("none");
});

View File

@@ -16,7 +16,7 @@
import { Component, Input, Inject, ElementRef } from "@angular/core";
import { UserOptions } from "viewers/common/user_options";
import { PersistentStore } from "common/persistent_store";
import { Tree, diffClass, isHighlighted } from "viewers/common/tree_utils";
import { HierarchyTree, diffClass, isHighlighted, Tree } from "viewers/common/tree_utils";
import { nodeStyles } from "viewers/components/styles/node.styles";
import { ViewerEvents } from "viewers/common/viewer_events";
import { TraceType } from "common/trace/trace_type";
@@ -56,7 +56,7 @@ import { TraceType } from "common/trace/trace_type";
[isPinned]="true"
[isInPinnedSection]="true"
(pinNodeChange)="pinnedItemChange($event)"
(click)="onPinnedNodeClick($event, pinnedItem.id, pinnedItem)"
(click)="onPinnedNodeClick($event, pinnedItem)"
></tree-node>
</div>
</mat-card-header>
@@ -150,10 +150,10 @@ export class HierarchyComponent {
diffClass = diffClass;
isHighlighted = isHighlighted;
@Input() tree!: Tree | null;
@Input() tree!: HierarchyTree | null;
@Input() dependencies: Array<TraceType> = [];
@Input() highlightedItems: Array<string> = [];
@Input() pinnedItems: Array<Tree> = [];
@Input() pinnedItems: Array<HierarchyTree> = [];
@Input() store!: PersistentStore;
@Input() userOptions: UserOptions = {};
@@ -172,12 +172,12 @@ export class HierarchyComponent {
};
}
public onPinnedNodeClick(event: MouseEvent, pinnedItemId: string, pinnedItem: Tree) {
public onPinnedNodeClick(event: MouseEvent, pinnedItem: HierarchyTree) {
event.preventDefault();
if (window.getSelection()?.type === "range") {
return;
}
this.highlightedItemChange(`${pinnedItemId}`);
if (pinnedItem.id) this.highlightedItemChange(`${pinnedItem.id}`);
this.selectedTreeChange(pinnedItem);
}
@@ -212,6 +212,9 @@ export class HierarchyComponent {
}
public selectedTreeChange(item: Tree) {
if (!(item instanceof HierarchyTree)) {
return;
}
const event: CustomEvent = new CustomEvent(
ViewerEvents.SelectedTreeChange,
{
@@ -222,6 +225,9 @@ export class HierarchyComponent {
}
public pinnedItemChange(item: Tree) {
if (!(item instanceof HierarchyTree)) {
return;
}
const event: CustomEvent = new CustomEvent(
ViewerEvents.HierarchyPinnedChange,
{

View File

@@ -54,9 +54,8 @@ describe("PropertiesComponent", () => {
fixture = TestBed.createComponent(PropertiesComponent);
component = fixture.componentInstance;
htmlElement = fixture.nativeElement;
component.selectedTree = {};
component.selectedLayer = {};
component.summary = [];
component.propertiesTree = {};
component.selectedFlickerItem = null;
component.userOptions = {
showDefaults: {
name: "Show defaults",

View File

@@ -16,8 +16,7 @@
import { Component, Input, Inject, ElementRef } from "@angular/core";
import { UserOptions } from "viewers/common/user_options";
import { ViewerEvents } from "viewers/common/viewer_events";
import { PropertiesTree, TreeSummary, Terminal } from "viewers/common/tree_utils";
import { Layer } from "common/trace/flickerlib/common";
import { PropertiesTree, Terminal, TreeFlickerItem } from "viewers/common/tree_utils";
@Component({
selector: "properties-view",
@@ -44,22 +43,21 @@ import { Layer } from "common/trace/flickerlib/common";
[matTooltip]="userOptions[option].tooltip ?? ''"
>{{userOptions[option].name}}</mat-checkbox>
</div>
<div *ngIf="objectKeys(selectedLayer).length > 0 && propertyGroups" class="element-summary">
<div *ngIf="itemIsSelected() && propertyGroups" class="element-summary">
<property-groups
[item]="selectedLayer"
[summary]="summary"
[item]="selectedFlickerItem"
></property-groups>
</div>
</mat-card-header>
<mat-card-content class="properties-content" [style]="maxPropertiesHeight()">
<span *ngIf="objectKeys(propertiesTree).length > 0" class="properties-title"> Properties - Proto Dump </span>
<div class="tree-wrapper">
<tree-view
class="tree-view"
[item]="selectedTree"
[item]="propertiesTree"
[showNode]="showNode"
[isLeaf]="isLeaf"
*ngIf="objectKeys(selectedTree).length > 0"
[isPropertiesTree]="true"
*ngIf="objectKeys(propertiesTree).length > 0"
[isAlwaysCollapsed]="true"
></tree-view>
</div>
@@ -136,10 +134,9 @@ export class PropertiesComponent {
filterString = "";
@Input() userOptions: UserOptions = {};
@Input() selectedTree: PropertiesTree = {};
@Input() selectedLayer: Layer = {};
@Input() propertiesTree: PropertiesTree = {};
@Input() selectedFlickerItem: TreeFlickerItem | null = null;
@Input() propertyGroups = false;
@Input() summary?: TreeSummary = [];
constructor(
@Inject(ElementRef) private elementRef: ElementRef,
@@ -182,4 +179,8 @@ export class PropertiesComponent {
return !item.children || item.children.length === 0
|| item.children.filter((c: any) => !(c instanceof Terminal)).length === 0;
}
public itemIsSelected() {
return this.selectedFlickerItem && Object.keys(this.selectedFlickerItem).length > 0;
}
}

View File

@@ -14,13 +14,28 @@
* limitations under the License.
*/
import { Component, Input } from "@angular/core";
import { TreeSummary } from "viewers/common/tree_utils";
import { Layer } from "common/trace/flickerlib/common";
@Component({
selector: "property-groups",
template: `
<div>
<div class="group">
<span class="group-header">
<span class="group-heading">Visibility</span>
</span>
<div class="left-column">
<span class="key">Flags:</span>
<span class="value">{{ item.flags }}</span>
<div></div>
<div *ngIf="summary().length > 0">
<div *ngFor="let reason of summary()">
<span class="key">{{ reason.key }}:</span>
<span class="value">{{ reason.value }}</span>
</div>
</div>
</div>
</div>
<div class="group">
<span class="group-header">Geometry</span>
<div class="left-column">
@@ -76,24 +91,24 @@ import { Layer } from "common/trace/flickerlib/common";
<span
class="key"
matTooltip="Scales buffer to the frame by overriding the requested transform
for this layer."
for this item."
>Destination Frame:</span>
<span class="value">{{ getDestinationFrame() }}</span>
<div></div>
<span
*ngIf="hasIgnoreDestinationFrame()"
class="value"
>Destination Frame ignored because layer has eIgnoreDestinationFrame
>Destination Frame ignored because item has eIgnoreDestinationFrame
flag set.
</span>
</div>
<div *ngIf="item.isContainerLayer" class="left-column">
<span class="key"></span>
<span class="value">Container layer</span>
<span class="value">Container item</span>
</div>
<div *ngIf="item.isEffectLayer" class="left-column">
<span class="key"></span>
<span class="value">Effect layer</span>
<span class="value">Effect item</span>
</div>
</div>
<div class="group">
@@ -107,7 +122,7 @@ import { Layer } from "common/trace/flickerlib/common";
<div></div>
<span
class="key"
matTooltip="Layer is z-ordered relative to its relative parents but its bounds
matTooltip="item is z-ordered relative to its relative parents but its bounds
and other properties are inherited from its parents."
>relative parent:</span>
<span class="value">
@@ -133,7 +148,7 @@ import { Layer } from "common/trace/flickerlib/common";
<span
class="key"
matTooltip="Crop used to define the bounds of the corner radii. If the bounds
are greater than the layer bounds then the rounded corner will not
are greater than the item bounds then the rounded corner will not
be visible."
>Corner Radius Crop:</span>
<span class="value">{{ item.cornerRadiusCrop }}</span>
@@ -175,21 +190,21 @@ import { Layer } from "common/trace/flickerlib/common";
<span class="group-header">
<span class="group-heading">Input</span>
</span>
<div *ngIf="item.proto?.inputWindowInfo" class="left-column">
<div *ngIf="hasInputChannel()" class="left-column">
<span class="key">To Display Transform:</span>
<transform-matrix [transform]="item.inputTransform" [formatFloat]="formatFloat"></transform-matrix>
<div></div>
<span class="key">Touchable Region:</span>
<span class="value">{{ item.inputRegion }}</span>
</div>
<div *ngIf="item.proto?.inputWindowInfo" class="right-column">
<div *ngIf="hasInputChannel()" class="right-column">
<span class="key">Config:</span>
<span class="value"></span>
<div></div>
<span class="key">Focusable:</span>
<span class="value">{{ item.proto?.inputWindowInfo.focusable }}</span>
<div></div>
<span class="key">Crop touch region with layer:</span>
<span class="key">Crop touch region with item:</span>
<span class="value">
{{
item.proto?.inputWindowInfo.cropLayerId &lt;= 0
@@ -205,28 +220,11 @@ import { Layer } from "common/trace/flickerlib/common";
}}
</span>
</div>
<div *ngIf="!item.proto?.inputWindowInfo" class="left-column">
<div *ngIf="!hasInputChannel()" class="left-column">
<span class="key"></span>
<span class="value">No input channel set</span>
</div>
</div>
<div class="group">
<span class="group-header">
<span class="group-heading">Visibility</span>
</span>
<div class="left-column">
<span class="key">Flags:</span>
<span class="value">{{ item.flags }}</span>
<div></div>
<div *ngIf="summary">
<div *ngFor="let reason of summary">
<span class="key">{{ reason.key }}:</span>
<span class="value">{{ reason.value }}</span>
</div>
</div>
</div>
</div>
</div>
`,
styles: [
@@ -285,7 +283,10 @@ import { Layer } from "common/trace/flickerlib/common";
export class PropertyGroupsComponent {
@Input() item!: Layer;
@Input() summary?: TreeSummary | null = null;
public hasInputChannel() {
return this.item.proto?.inputWindowInfo;
}
public getDestinationFrame() {
const frame = this.item.proto?.destinationFrame;
@@ -302,4 +303,41 @@ export class PropertyGroupsComponent {
public formatFloat(num: number) {
return Math.round(num * 100) / 100;
}
public summary(): TreeSummary {
const summary = [];
if (this.item?.visibilityReason?.length > 0) {
let reason = "";
if (Array.isArray(this.item.visibilityReason)) {
reason = this.item.visibilityReason.join(", ");
} else {
reason = this.item.visibilityReason;
}
summary.push({key: "Invisible due to", value: reason});
}
if (this.item?.occludedBy?.length > 0) {
summary.push({key: "Occluded by", value: this.item.occludedBy.map((it: any) => it.id).join(", ")});
}
if (this.item?.partiallyOccludedBy?.length > 0) {
summary.push({
key: "Partially occluded by",
value: this.item.partiallyOccludedBy.map((it: any) => it.id).join(", "),
});
}
if (this.item?.coveredBy?.length > 0) {
summary.push({key: "Covered by", value: this.item.coveredBy.map((it: any) => it.id).join(", ")});
}
return summary;
}
}
type TreeSummary = Array<{key: string, value: string}>

View File

@@ -23,11 +23,11 @@ export class CanvasGraphics {
constructor() {
//set up camera
const left = -this.CAMERA_HALF_WIDTH,
right = this.CAMERA_HALF_WIDTH,
top = this.CAMERA_HALF_HEIGHT,
bottom = -this.CAMERA_HALF_HEIGHT,
near = 0.001,
far = 100;
right = this.CAMERA_HALF_WIDTH,
top = this.CAMERA_HALF_HEIGHT,
bottom = -this.CAMERA_HALF_HEIGHT,
near = 0.001,
far = 100;
this.camera = new THREE.OrthographicCamera(
left, right, top, bottom, near, far
);

View File

@@ -16,7 +16,7 @@
import { Component, Inject, Input, Output, ElementRef, EventEmitter } from "@angular/core";
import { PersistentStore } from "common/persistent_store";
import { nodeStyles, treeNodeDataViewStyles } from "viewers/components/styles/node.styles";
import { Tree, diffClass, isHighlighted, PropertiesTree, Terminal, isParentNode } from "viewers/common/tree_utils";
import { Tree, diffClass, isHighlighted, PropertiesTree, Terminal, isParentNode, HierarchyTree } from "viewers/common/tree_utils";
import { TraceType } from "common/trace/trace_type";
@Component({
@@ -39,7 +39,6 @@ import { TraceType } from "common/trace/trace_type";
[flattened]="isFlattened"
[isLeaf]="isLeaf(this.item)"
[isCollapsed]="isAlwaysCollapsed ?? isCollapsed()"
[isPropertiesTreeNode]="isPropertiesTree"
[hasChildren]="hasChildren()"
[isPinned]="isPinned()"
(toggleTreeChange)="toggleTree()"
@@ -58,7 +57,6 @@ import { TraceType } from "common/trace/trace_type";
[isLeaf]="isLeaf"
[dependencies]="dependencies"
[isFlattened]="isFlattened"
[isPropertiesTree]="isPropertiesTree"
[isShaded]="!isShaded"
[useGlobalCollapsedState]="useGlobalCollapsedState"
[initialDepth]="initialDepth + 1"
@@ -82,17 +80,16 @@ export class TreeComponent {
diffClass = diffClass;
isHighlighted = isHighlighted;
@Input() item!: Tree | PropertiesTree | Terminal;
@Input() item!: Tree;
@Input() dependencies: Array<TraceType> = [];
@Input() store!: PersistentStore;
@Input() isFlattened? = false;
@Input() isShaded? = false;
@Input() initialDepth = 0;
@Input() highlightedItems: Array<string> = [];
@Input() pinnedItems?: Array<Tree> = [];
@Input() pinnedItems?: Array<HierarchyTree> = [];
@Input() itemsClickable?: boolean;
@Input() useGlobalCollapsedState?: boolean;
@Input() isPropertiesTree?: boolean;
@Input() isAlwaysCollapsed?: boolean;
@Input() showNode: (item?: any) => boolean = () => true;
@Input() isLeaf: (item: any) => boolean = (item: any) => !item.children || item.children.length === 0;
@@ -126,7 +123,7 @@ export class TreeComponent {
}
ngOnChanges() {
if (isHighlighted(this.item, this.highlightedItems)) {
if (this.item instanceof HierarchyTree && isHighlighted(this.item, this.highlightedItems)) {
this.selectedTreeChange.emit(this.item);
}
}
@@ -162,16 +159,18 @@ export class TreeComponent {
}
private updateHighlightedItems() {
if (this.item && this.item.id) {
this.highlightedItemChange.emit(`${this.item.id}`);
} else if (!this.item.id) {
this.selectedTreeChange.emit(this.item);
if (this.item instanceof HierarchyTree) {
if (this.item && this.item.id) {
this.highlightedItemChange.emit(`${this.item.id}`);
} else if (!this.item.id) {
this.selectedTreeChange.emit(this.item);
}
}
}
public isPinned() {
if (this.item) {
return this.pinnedItems?.map((item: Tree) => `${item.id}`).includes(`${this.item.id}`);
if (this.item instanceof HierarchyTree) {
return this.pinnedItems?.map(item => `${item.id}`).includes(`${this.item.id}`);
}
return false;
}
@@ -206,18 +205,18 @@ export class TreeComponent {
}
if (this.useGlobalCollapsedState) {
return this.store.getFromStore(`collapsedState.item.${this.dependencies}.${this.item.id}`)==="true"
return this.store.getFromStore(`collapsedState.item.${this.dependencies}.${this.item.stableId}`)==="true"
?? this.isCollapsedByDefault;
}
return this.localCollapsedState;
}
public children() {
return this.item.children;
public children(): Tree[] {
return this.item.children ?? [];
}
public hasChildren() {
const isParentEntryInFlatView = isParentNode(this.item.kind) && this.isFlattened;
const isParentEntryInFlatView = isParentNode(this.item.kind ?? "") && this.isFlattened;
return (!this.isFlattened || isParentEntryInFlatView) && !this.isLeaf(this.item);
}
@@ -241,7 +240,7 @@ export class TreeComponent {
private setCollapseValue(isCollapsed: boolean) {
if (this.useGlobalCollapsedState) {
this.store.addToStore(`collapsedState.item.${this.dependencies}.${this.item.id}`, `${isCollapsed}`);
this.store.addToStore(`collapsedState.item.${this.dependencies}.${this.item.stableId}`, `${isCollapsed}`);
} else {
this.localCollapsedState = isCollapsed;
}

View File

@@ -54,7 +54,6 @@ describe("TreeNodeComponent", () => {
[isPinned]="false"
[isInPinnedSection]="false"
[hasChildren]="false"
[isPropertiesTreeNode]="false"
></tree-node>
`
})

View File

@@ -15,7 +15,7 @@
*/
import { Component, Input, Output, EventEmitter } from "@angular/core";
import { nodeInnerItemStyles } from "viewers/components/styles/node.styles";
import { PropertiesTree, Tree, DiffType, isParentNode } from "viewers/common/tree_utils";
import { Tree, DiffType, isParentNode, HierarchyTree } from "viewers/common/tree_utils";
@Component({
selector: "tree-node",
@@ -50,11 +50,11 @@ import { PropertiesTree, Tree, DiffType, isParentNode } from "viewers/common/tre
<div class="description">
<tree-node-data-view
[item]="item"
*ngIf="!isPropertiesTreeNode"
*ngIf="!isPropertiesTreeNode()"
></tree-node-data-view>
<tree-node-properties-data-view
[item]="item"
*ngIf="isPropertiesTreeNode"
*ngIf="isPropertiesTreeNode()"
></tree-node-properties-data-view>
</div>
@@ -76,14 +76,13 @@ import { PropertiesTree, Tree, DiffType, isParentNode } from "viewers/common/tre
})
export class TreeNodeComponent {
@Input() item!: Tree | PropertiesTree;
@Input() item!: Tree;
@Input() isLeaf?: boolean;
@Input() flattened?: boolean;
@Input() isCollapsed?: boolean;
@Input() hasChildren?: boolean = false;
@Input() isPinned?: boolean = false;
@Input() isInPinnedSection?: boolean = false;
@Input() isPropertiesTreeNode?: boolean;
@Input() isAlwaysCollapsed?: boolean;
@Output() toggleTreeChange = new EventEmitter<void>();
@@ -96,8 +95,12 @@ export class TreeNodeComponent {
this.collapseDiffClass = this.updateCollapseDiffClass();
}
public isPropertiesTreeNode() {
return !(this.item instanceof HierarchyTree);
}
public showPinNodeIcon() {
return (!this.isPropertiesTreeNode && !isParentNode(this.item.kind)) ?? false;
return (!this.isPropertiesTreeNode() && !isParentNode(this.item.kind ?? "")) ?? false;
}
public toggleTree(event: MouseEvent) {
@@ -145,7 +148,7 @@ export class TreeNodeComponent {
return DiffType.MODIFIED;
}
private getAllDiffTypesOfChildren(item: Tree | PropertiesTree) {
private getAllDiffTypesOfChildren(item: Tree) {
if (!item.children) {
return new Set();
}

View File

@@ -15,7 +15,7 @@
*/
import { Component, Input } from "@angular/core";
import { treeNodeDataViewStyles } from "viewers/components/styles/tree_node_data_view.styles";
import { Tree } from "viewers/common/tree_utils";
import { Terminal, HierarchyTree, Tree } from "viewers/common/tree_utils";
import Chip from "viewers/common/chip";
@Component({
@@ -24,10 +24,10 @@ import Chip from "viewers/common/chip";
<span>
<span class="kind">{{item.kind}}</span>
<span *ngIf="item.kind && item.name">-</span>
<span *ngIf="showShortName()" [matTooltip]="item.name">{{ item.shortName }}</span>
<span *ngIf="showShortName()" [matTooltip]="itemTooltip()">{{ itemShortName() }}</span>
<span *ngIf="!showShortName()">{{item.name}}</span>
<div
*ngFor="let chip of item.chips"
*ngFor="let chip of chips()"
[class]="chipClass(chip)"
[matTooltip]="chip.long"
>{{chip.short}}</div>
@@ -39,8 +39,23 @@ import Chip from "viewers/common/chip";
export class TreeNodeDataViewComponent {
@Input() item!: Tree;
public chips() {
return (this.item instanceof HierarchyTree) ? this.item.chips : [];
}
public itemShortName() {
return (this.item instanceof HierarchyTree)? this.item.shortName : "";
}
public itemTooltip() {
if (this.item.name instanceof Terminal) {
return "";
}
return this.item.name ?? "";
}
public showShortName() {
return this.item.simplifyNames && this.item.shortName !== this.item.name;
return (this.item instanceof HierarchyTree) && this.item.simplifyNames && this.item.shortName !== this.item.name;
}
public chipClass(chip: Chip) {

View File

@@ -49,7 +49,7 @@ export class TreeNodePropertiesDataViewComponent {
return "false";
}
if (!isNaN(this.item.propertyValue)) {
if (!isNaN(Number(this.item.propertyValue))) {
return "number";
}
return null;

View File

@@ -17,57 +17,25 @@ import { Presenter } from "./presenter";
import { UiData } from "./ui_data";
import { UserOptions } from "viewers/common/user_options";
import { TraceType } from "common/trace/trace_type";
import { RELATIVE_Z_CHIP, VISIBLE_CHIP } from "viewers/common/chip";
import { LayerTraceEntry } from "common/trace/flickerlib/common";
import { DiffType, PropertiesTree, Terminal, Tree } from "viewers/common/tree_utils";
import { HierarchyTree, PropertiesTree } from "viewers/common/tree_utils";
import { UnitTestUtils } from "test/unit/utils";
import { HierarchyTreeBuilder } from "test/unit/hierarchy_tree_builder";
describe("PresenterSurfaceFlinger", () => {
let presenter: Presenter;
let uiData: UiData;
let entries: Map<TraceType, any>;
let selectedItem: Tree;
let selectedTree: HierarchyTree;
beforeAll(async () => {
entries = new Map<TraceType, any>();
const entry: LayerTraceEntry = await UnitTestUtils.getLayerTraceEntry();
selectedItem = {
id: "3",
name: "Child1",
stackId: 0,
isVisible: true,
kind: "3",
stableId: "3 Child1",
shortName: undefined,
simplifyNames: true,
showInFilteredView: true,
proto: {
barrierLayer: [],
id: 3,
parent: 1,
type: "ContainerLayer",
},
chips: [ VISIBLE_CHIP, RELATIVE_Z_CHIP ],
children: [{
id: "2",
name: "Child2",
stackId: 0,
children: [],
kind: "2",
stableId: "2 Child2",
shortName: undefined,
simplifyNames: true,
proto: {
barrierLayer: [],
id: 2,
parent: 3,
type: "ContainerLayer",
},
isVisible: true,
showInFilteredView: true,
chips: [ VISIBLE_CHIP, RELATIVE_Z_CHIP ],
}],
};
selectedTree = new HierarchyTreeBuilder().setName("Dim layer#53").setStableId("EffectLayer 53 Dim layer#53")
.setFilteredView(true).setKind("53").setDiffType("EffectLayer").setId(53).build();
entries.set(TraceType.SURFACE_FLINGER, [entry, null]);
});
@@ -92,12 +60,12 @@ describe("PresenterSurfaceFlinger", () => {
expect(propertyOpts).toBeTruthy();
// does not check specific tree values as tree generation method may change
expect(Object.keys(uiData.tree).length > 0).toBeTrue();
expect(Object.keys(uiData.tree!).length > 0).toBeTrue();
});
it("can handle unavailable trace entry", () => {
presenter.notifyCurrentTraceEntries(entries);
expect(Object.keys(uiData.tree).length > 0).toBeTrue();
expect(Object.keys(uiData.tree!).length > 0).toBeTrue();
const emptyEntries = new Map<TraceType, any>();
presenter.notifyCurrentTraceEntries(emptyEntries);
expect(uiData.tree).toBeFalsy();
@@ -105,13 +73,8 @@ describe("PresenterSurfaceFlinger", () => {
it("can update pinned items", () => {
expect(uiData.pinnedItems).toEqual([]);
const pinnedItem = {
name: "FirstPinnedItem",
kind: "4",
id: 4,
type: "TestItem",
stableId: "TestItem 4 FirstPinnedItem"
};
const pinnedItem = new HierarchyTreeBuilder().setName("FirstPinnedItem")
.setStableId("TestItem 4").setLayerId(4).build();
presenter.updatePinnedItems(pinnedItem);
expect(uiData.pinnedItems).toContain(pinnedItem);
});
@@ -145,12 +108,12 @@ describe("PresenterSurfaceFlinger", () => {
};
presenter.notifyCurrentTraceEntries(entries);
expect(uiData.tree.children.length).toEqual(3);
expect(uiData.tree?.children.length).toEqual(3);
presenter.updateHierarchyTree(userOptions);
expect(uiData.hierarchyUserOptions).toEqual(userOptions);
// nested children should now be on same level as initial parents
expect(uiData.tree.children.length).toEqual(94);
expect(uiData.tree?.children.length).toEqual(94);
});
it("can filter hierarchy tree", () => {
@@ -171,21 +134,21 @@ describe("PresenterSurfaceFlinger", () => {
name: "Flat",
enabled: true
}
}
};
presenter.notifyCurrentTraceEntries(entries);
presenter.updateHierarchyTree(userOptions);
expect(uiData.tree.children.length).toEqual(94);
expect(uiData.tree?.children.length).toEqual(94);
presenter.filterHierarchyTree("Wallpaper");
// All but four layers should be filtered out
expect(uiData.tree.children.length).toEqual(4);
expect(uiData.tree?.children.length).toEqual(4);
});
it("can set new properties tree and associated ui data", () => {
presenter.notifyCurrentTraceEntries(entries);
presenter.newPropertiesTree(selectedItem);
presenter.newPropertiesTree(selectedTree);
// does not check specific tree values as tree transformation method may change
expect(Object.keys(uiData.selectedTree).length > 0).toBeTrue();
expect(uiData.propertiesTree).toBeTruthy();
});
it("can update properties tree", () => {
@@ -207,33 +170,27 @@ describe("PresenterSurfaceFlinger", () => {
};
presenter.notifyCurrentTraceEntries(entries);
presenter.newPropertiesTree(selectedItem);
presenter.newPropertiesTree(selectedTree);
expect(uiData.propertiesTree?.diffType).toBeFalsy();
presenter.updatePropertiesTree(userOptions);
expect(uiData.propertiesUserOptions).toEqual(userOptions);
//check that diff type added
expect(uiData.selectedTree.diffType).toEqual(DiffType.NONE);
expect(uiData.propertiesTree?.diffType).toBeTruthy();
});
it("can filter properties tree", () => {
presenter.notifyCurrentTraceEntries(entries);
presenter.newPropertiesTree(selectedItem);
presenter.newPropertiesTree(selectedTree);
let nonTerminalChildren = uiData.propertiesTree?.children?.filter(
(child: PropertiesTree) => typeof child.propertyKey === "string"
) ?? [];
let nonTerminalChildren = uiData.selectedTree
.children[0]
.children.filter(
(child: PropertiesTree) => !(child.propertyKey instanceof Terminal)
);
expect(nonTerminalChildren.length).toEqual(55);
presenter.filterPropertiesTree("bound");
expect(nonTerminalChildren.length).toEqual(2);
presenter.filterPropertiesTree("ContainerLayer");
// one child should be filtered out
nonTerminalChildren = uiData.selectedTree
.children[0]
.children.filter(
(child: PropertiesTree) => !(child.propertyKey instanceof Terminal)
);
expect(nonTerminalChildren.length).toEqual(1);
nonTerminalChildren = uiData.propertiesTree?.children?.filter(
(child: PropertiesTree) => typeof child.propertyKey === "string"
) ?? [];
expect(nonTerminalChildren.length).toEqual(3);
});
});

View File

@@ -18,9 +18,10 @@ import { UiData } from "./ui_data";
import { Rectangle, RectMatrix, RectTransform } from "viewers/common/rectangle";
import { TraceType } from "common/trace/trace_type";
import { UserOptions } from "viewers/common/user_options";
import { getFilter, FilterType, Tree, TreeSummary } from "viewers/common/tree_utils";
import { getFilter, FilterType, HierarchyTree, Tree, TreeFlickerItem, PropertiesTree } from "viewers/common/tree_utils";
import { TreeGenerator } from "viewers/common/tree_generator";
import { TreeTransformer } from "viewers/common/tree_transformer";
import { Layer, LayerTraceEntry } from "common/trace/flickerlib/common";
type NotifyViewCallbackType = (uiData: UiData) => void;
@@ -31,7 +32,7 @@ export class Presenter {
this.notifyViewCallback(this.uiData);
}
public updatePinnedItems(pinnedItem: Tree) {
public updatePinnedItems(pinnedItem: HierarchyTree) {
const pinnedId = `${pinnedItem.id}`;
if (this.pinnedItems.map(item => `${item.id}`).includes(pinnedId)) {
this.pinnedItems = this.pinnedItems.filter(pinned => `${pinned.id}` != pinnedId);
@@ -47,7 +48,7 @@ export class Presenter {
if (this.highlightedItems.includes(id)) {
this.highlightedItems = this.highlightedItems.filter(hl => hl != id);
} else {
this.highlightedItems = []; //if multi-select implemented, remove this line
this.highlightedItems = []; //if multi-select surfaces implemented, remove this line
this.highlightedItems.push(id);
}
this.uiData.highlightedItems = this.highlightedItems;
@@ -78,12 +79,12 @@ export class Presenter {
this.updateSelectedTreeUiData();
}
public newPropertiesTree(selectedItem: any) {
this.selectedTree = selectedItem;
public newPropertiesTree(selectedItem: HierarchyTree) {
this.selectedHierarchyTree = selectedItem;
this.updateSelectedTreeUiData();
}
public notifyCurrentTraceEntries(entries: Map<TraceType, any>) {
public notifyCurrentTraceEntries(entries: Map<TraceType, [any, any]>) {
this.uiData = new UiData();
this.uiData.hierarchyUserOptions = this.hierarchyUserOptions;
this.uiData.propertiesUserOptions = this.propertiesUserOptions;
@@ -127,49 +128,13 @@ export class Presenter {
}
private updateSelectedTreeUiData() {
if (this.selectedTree) {
this.uiData.selectedTree = this.getTreeWithTransformedProperties(this.selectedTree);
this.uiData.selectedTreeSummary = this.getSelectedTreeSummary(this.selectedTree);
if (this.selectedHierarchyTree) {
this.uiData.propertiesTree = this.getTreeWithTransformedProperties(this.selectedHierarchyTree);
this.uiData.selectedLayer = this.selectedLayer;
}
this.notifyViewCallback(this.uiData);
}
private getSelectedTreeSummary(layer: Tree): TreeSummary | undefined {
const summary = [];
if (layer?.visibilityReason?.length > 0) {
let reason = "";
if (Array.isArray(layer.visibilityReason)) {
reason = layer.visibilityReason.join(", ");
} else {
reason = layer.visibilityReason;
}
summary.push({key: "Invisible due to", value: reason});
}
if (layer?.occludedBy?.length > 0) {
summary.push({key: "Occluded by", value: layer.occludedBy.map((it:Tree) => it.id).join(", ")});
}
if (layer?.partiallyOccludedBy?.length > 0) {
summary.push({
key: "Partially occluded by",
value: layer.partiallyOccludedBy.map((it:Tree) => it.id).join(", "),
});
}
if (layer?.coveredBy?.length > 0) {
summary.push({key: "Covered by", value: layer.coveredBy.map((it:Tree) => it.id).join(", ")});
}
if (summary.length === 0) {
return undefined;
}
return summary;
}
private generateTree() {
if (!this.entry) {
return null;
@@ -180,13 +145,13 @@ export class Presenter {
.setIsSimplifyNames(this.hierarchyUserOptions["simplifyNames"]?.enabled)
.setIsFlatView(this.hierarchyUserOptions["flat"]?.enabled)
.withUniqueNodeId();
let tree: Tree;
let tree: HierarchyTree | null;
if (!this.hierarchyUserOptions["showDiff"]?.enabled) {
tree = generator.generateTree();
} else {
tree = generator.compareWith(this.previousEntry)
.withModifiedCheck()
.generateFinalDiffTree();
.generateFinalTreeWithDiff();
}
this.pinnedItems = generator.getPinnedItems();
this.uiData.pinnedItems = this.pinnedItems;
@@ -245,13 +210,15 @@ export class Presenter {
}
}
private getTreeWithTransformedProperties(selectedTree: Tree) {
private getTreeWithTransformedProperties(selectedTree: HierarchyTree): PropertiesTree {
const transformer = new TreeTransformer(selectedTree, this.propertiesFilter)
.showOnlyProtoDump()
.setIsShowDefaults(this.propertiesUserOptions["showDefaults"]?.enabled)
.setIsShowDiff(this.propertiesUserOptions["showDiff"]?.enabled)
.setTransformerOptions({skip: selectedTree.skip})
.setProperties(this.entry)
.setDiffProperties(this.previousEntry);
this.uiData.selectedLayer = transformer.getOriginalLayer(this.entry, selectedTree.stableId);
this.selectedLayer = transformer.getOriginalFlickerItem(this.entry, selectedTree.stableId);
const transformedTree = transformer.transform();
return transformedTree;
}
@@ -262,11 +229,12 @@ export class Presenter {
private propertiesFilter: FilterType = getFilter("");
private highlightedItems: Array<string> = [];
private displayIds: Array<number> = [];
private pinnedItems: Array<Tree> = [];
private pinnedItems: Array<HierarchyTree> = [];
private pinnedIds: Array<string> = [];
private selectedTree: any = null;
private previousEntry: any = null;
private entry: any = null;
private selectedHierarchyTree: HierarchyTree | null = null;
private selectedLayer: LayerTraceEntry | Layer | null = null;
private previousEntry: LayerTraceEntry | null = null;
private entry: LayerTraceEntry | null = null;
private hierarchyUserOptions: UserOptions = {
showDiff: {
name: "Show diff",

View File

@@ -13,7 +13,7 @@
* See the License for the specific language governing permissions and
* limitations under the License.
*/
import { Tree, TreeSummary } from "viewers/common/tree_utils";
import { HierarchyTree, PropertiesTree } from "viewers/common/tree_utils";
import { UserOptions } from "viewers/common/user_options";
import { Layer } from "common/trace/flickerlib/common";
import { TraceType } from "common/trace/trace_type";
@@ -21,15 +21,14 @@ import { Rectangle } from "viewers/common/rectangle";
export class UiData {
dependencies: Array<TraceType> = [TraceType.SURFACE_FLINGER];
rects?: Rectangle[] = [];
displayIds?: number[] = [];
hasVirtualDisplays? = false;
highlightedItems?: Array<string> = [];
pinnedItems?: Array<Tree> = [];
hierarchyUserOptions?: UserOptions = {};
propertiesUserOptions?: UserOptions = {};
tree?: Tree | null = null;
selectedTree?: any = {};
selectedLayer?: Layer = {};
selectedTreeSummary?: TreeSummary = [];
rects: Rectangle[] = [];
displayIds: number[] = [];
hasVirtualDisplays = false;
highlightedItems: Array<string> = [];
pinnedItems: Array<HierarchyTree> = [];
hierarchyUserOptions: UserOptions = {};
propertiesUserOptions: UserOptions = {};
tree: HierarchyTree | null = null;
propertiesTree: PropertiesTree | null = null;
selectedLayer: Layer = {};
}

View File

@@ -38,7 +38,7 @@ import { PersistentStore } from "common/persistent_store";
<div fxLayout="row wrap" fxLayoutGap="10px grid" class="card-grid">
<mat-card id="sf-hierarchy-view" class="hierarchy-view">
<hierarchy-view
[tree]="inputData?.tree"
[tree]="inputData?.tree ?? null"
[dependencies]="inputData?.dependencies ?? []"
[highlightedItems]="inputData?.highlightedItems ?? []"
[pinnedItems]="inputData?.pinnedItems ?? []"
@@ -49,9 +49,8 @@ import { PersistentStore } from "common/persistent_store";
<mat-card id="sf-properties-view" class="properties-view">
<properties-view
[userOptions]="inputData?.propertiesUserOptions ?? {}"
[selectedTree]="inputData?.selectedTree ?? {}"
[selectedLayer]="inputData?.selectedLayer ?? {}"
[summary]="inputData?.selectedTreeSummary ?? []"
[propertiesTree]="inputData?.propertiesTree ?? {}"
[selectedFlickerItem]="inputData?.selectedLayer ?? {}"
[propertyGroups]="true"
></properties-view>
</mat-card>

View File

@@ -1,4 +1,4 @@
9/*
9;/*
* Copyright (C) 2022 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
@@ -18,64 +18,26 @@ import { UiData } from "./ui_data";
import { UserOptions } from "viewers/common/user_options";
import { TraceType } from "common/trace/trace_type";
import { WindowManagerState } from "common/trace/flickerlib/common";
import { DiffType, PropertiesTree, Terminal, Tree } from "viewers/common/tree_utils";
import { PropertiesTree, Terminal, HierarchyTree } from "viewers/common/tree_utils";
import { UnitTestUtils } from "test/unit/utils";
import { HierarchyTreeBuilder } from "test/unit/hierarchy_tree_builder";
import { VISIBLE_CHIP } from "viewers/common/chip";
describe("PresenterWindowManager", () => {
let presenter: Presenter;
let uiData: UiData;
let entries: Map<TraceType, any>;
let selectedItem: Tree;
let selectedTree: HierarchyTree;
beforeAll(async () => {
entries = new Map<TraceType, any>();
const entry: WindowManagerState = await UnitTestUtils.getWindowManagerState();
selectedItem = {
layerId: "3",
name: "Child1",
displayId: 0,
isVisible: true,
stableId: "3 Child1",
shortName: undefined,
simplifyNames: true,
showInFilteredView: true,
proto: {
name: "KeepInFilter",
},
chips: [],
children: [
{
layerId: "2",
name: "Child2",
displayId: 0,
children: [],
stableId: "2 Child2",
shortName: undefined,
simplifyNames: true,
proto: {
name: "KeepInFilter",
},
isVisible: true,
showInFilteredView: true,
chips: [],
},
{
layerId: "8",
name: "Child8",
displayId: 0,
children: [],
stableId: "8 Child8",
shortName: undefined,
simplifyNames: true,
proto: {
name: "RejectFromFilter",
},
isVisible: true,
showInFilteredView: true,
chips: [],
},
],
};
selectedTree = new HierarchyTreeBuilder().setName("ScreenDecorOverlayBottom")
.setStableId("WindowState 2088ac1 ScreenDecorOverlayBottom").setKind("WindowState")
.setSimplifyNames(true).setShortName("ScreenDecorOverlayBottom").setLayerId(61)
.setFilteredView(true).setIsVisible(true).setChips([VISIBLE_CHIP]).build();
entries.set(TraceType.WINDOW_MANAGER, [entry, null]);
});
@@ -100,12 +62,12 @@ describe("PresenterWindowManager", () => {
expect(propertyOpts).toBeTruthy();
// does not check specific tree values as tree generation method may change
expect(Object.keys(uiData.tree).length > 0).toBeTrue();
expect(Object.keys(uiData.tree!).length > 0).toBeTrue();
});
it("can handle unavailable trace entry", () => {
presenter.notifyCurrentTraceEntries(entries);
expect(Object.keys(uiData.tree).length > 0).toBeTrue();
expect(Object.keys(uiData.tree!).length > 0).toBeTrue();
const emptyEntries = new Map<TraceType, any>();
presenter.notifyCurrentTraceEntries(emptyEntries);
expect(uiData.tree).toBeFalsy();
@@ -114,11 +76,10 @@ describe("PresenterWindowManager", () => {
it("can update pinned items", () => {
presenter.notifyCurrentTraceEntries(entries);
expect(uiData.pinnedItems).toEqual([]);
const pinnedItem = {
name: "FirstPinnedItem",
layerId: 4,
stableId: "TestItem 4 FirstPinnedItem"
};
const pinnedItem = new HierarchyTreeBuilder().setName("FirstPinnedItem")
.setStableId("TestItem 4").setLayerId(4).build();
presenter.updatePinnedItems(pinnedItem);
expect(uiData.pinnedItems).toContain(pinnedItem);
});
@@ -152,12 +113,11 @@ describe("PresenterWindowManager", () => {
};
presenter.notifyCurrentTraceEntries(entries);
expect(uiData.tree.children.length).toEqual(1);
expect(uiData.tree?.children.length).toEqual(1);
presenter.updateHierarchyTree(userOptions);
expect(uiData.hierarchyUserOptions).toEqual(userOptions);
// nested children should now be on same level initial parent
expect(uiData.tree.children.length).toEqual(72);
expect(uiData.tree?.children.length).toEqual(72);
});
it("can filter hierarchy tree", () => {
@@ -178,21 +138,21 @@ describe("PresenterWindowManager", () => {
name: "Flat",
enabled: true
}
}
};
presenter.notifyCurrentTraceEntries(entries);
presenter.updateHierarchyTree(userOptions);
expect(uiData.tree.children.length).toEqual(72);
expect(uiData.tree?.children.length).toEqual(72);
presenter.filterHierarchyTree("ScreenDecor");
// All but two window states should be filtered out
expect(uiData.tree.children.length).toEqual(2);
expect(uiData.tree?.children.length).toEqual(2);
});
it("can set new properties tree and associated ui data", () => {
presenter.notifyCurrentTraceEntries(entries);
presenter.newPropertiesTree(selectedItem);
presenter.newPropertiesTree(selectedTree);
// does not check specific tree values as tree transformation method may change
expect(Object.keys(uiData.selectedTree).length > 0).toBeTrue();
expect(uiData.propertiesTree).toBeTruthy();
});
it("can update properties tree", () => {
@@ -214,31 +174,28 @@ describe("PresenterWindowManager", () => {
};
presenter.notifyCurrentTraceEntries(entries);
presenter.newPropertiesTree(selectedItem);
presenter.newPropertiesTree(selectedTree);
expect(uiData.propertiesTree?.diffType).toBeFalsy();
presenter.updatePropertiesTree(userOptions);
expect(uiData.propertiesUserOptions).toEqual(userOptions);
//check that diff type added
expect(uiData.selectedTree.diffType).toEqual(DiffType.NONE);
expect(uiData.propertiesTree?.diffType).toBeTruthy();
});
it("can filter properties tree", () => {
presenter.notifyCurrentTraceEntries(entries);
presenter.newPropertiesTree(selectedItem);
presenter.newPropertiesTree(selectedTree);
let nonTerminalChildren = uiData.selectedTree
.children.filter(
(child: PropertiesTree) => !(child.propertyKey instanceof Terminal)
);
let nonTerminalChildren = uiData.propertiesTree?.children?.filter(
(child: PropertiesTree) => typeof child.propertyKey === "string"
) ?? [];
expect(nonTerminalChildren.length).toEqual(2);
expect(nonTerminalChildren.length).toEqual(45);
presenter.filterPropertiesTree("visible");
presenter.filterPropertiesTree("KeepInFilter");
// one child should be filtered out
nonTerminalChildren = uiData.selectedTree
.children.filter(
(child: PropertiesTree) => !(child.propertyKey instanceof Terminal)
);
expect(nonTerminalChildren.length).toEqual(1);
nonTerminalChildren = uiData.propertiesTree?.children?.filter(
(child: PropertiesTree) => typeof child.propertyKey === "string"
) ?? [];
expect(nonTerminalChildren.length).toEqual(4);
});
});

View File

@@ -17,7 +17,7 @@ import { UiData } from "./ui_data";
import { Rectangle, RectMatrix, RectTransform } from "viewers/common/rectangle";
import { TraceType } from "common/trace/trace_type";
import { UserOptions } from "viewers/common/user_options";
import { getFilter, FilterType, Tree } from "viewers/common/tree_utils";
import { getFilter, FilterType, Tree, HierarchyTree, PropertiesTree, TreeFlickerItem } from "viewers/common/tree_utils";
import { TreeGenerator } from "viewers/common/tree_generator";
import { TreeTransformer } from "viewers/common/tree_transformer";
import DisplayContent from "common/trace/flickerlib/windows/DisplayContent";
@@ -31,7 +31,7 @@ export class Presenter {
this.notifyViewCallback(this.uiData);
}
public updatePinnedItems(pinnedItem: Tree) {
public updatePinnedItems(pinnedItem: HierarchyTree) {
const pinnedId = `${pinnedItem.id}`;
if (this.pinnedItems.map(item => `${item.id}`).includes(pinnedId)) {
this.pinnedItems = this.pinnedItems.filter(pinned => `${pinned.id}` != pinnedId);
@@ -78,8 +78,8 @@ export class Presenter {
this.updateSelectedTreeUiData();
}
public newPropertiesTree(selectedItem: any) {
this.selectedTree = selectedItem;
public newPropertiesTree(selectedTree: HierarchyTree) {
this.selectedHierarchyTree = selectedTree;
this.updateSelectedTreeUiData();
}
@@ -103,7 +103,7 @@ export class Presenter {
}
private generateRects(): Rectangle[] {
const displayRects = this.entry.displays.map((display: DisplayContent) => {
const displayRects = this.entry?.displays?.map((display: DisplayContent) => {
const rect = display.displayRect;
rect.label = display.title;
rect.id = display.layerId;
@@ -113,7 +113,7 @@ export class Presenter {
return rect;
}) ?? [];
this.displayIds = [];
const rects = this.entry.windowStates.reverse()
const rects = this.entry?.windowStates?.reverse()
.map((it: any) => {
const rect = it.rect;
rect.id = it.layerId;
@@ -122,13 +122,13 @@ export class Presenter {
this.displayIds.push(it.displayId);
}
return rect;
});
}) ?? [];
return this.rectsToUiData(rects.concat(displayRects));
}
private updateSelectedTreeUiData() {
if (this.selectedTree) {
this.uiData.selectedTree = this.getTreeWithTransformedProperties(this.selectedTree);
if (this.selectedHierarchyTree) {
this.uiData.propertiesTree = this.getTreeWithTransformedProperties(this.selectedHierarchyTree);
}
this.notifyViewCallback(this.uiData);
}
@@ -143,13 +143,13 @@ export class Presenter {
.setIsSimplifyNames(this.hierarchyUserOptions["simplifyNames"]?.enabled)
.setIsFlatView(this.hierarchyUserOptions["flat"]?.enabled)
.withUniqueNodeId();
let tree: Tree;
let tree: HierarchyTree | null;
if (!this.hierarchyUserOptions["showDiff"]?.enabled) {
tree = generator.generateTree();
} else {
tree = generator.compareWith(this.previousEntry)
.withModifiedCheck()
.generateFinalDiffTree();
.generateFinalTreeWithDiff();
}
this.pinnedItems = generator.getPinnedItems();
this.uiData.pinnedItems = this.pinnedItems;
@@ -208,11 +208,16 @@ export class Presenter {
}
}
private getTreeWithTransformedProperties(selectedTree: Tree) {
private getTreeWithTransformedProperties(selectedTree: HierarchyTree): PropertiesTree {
if (!this.entry) {
return {};
}
const transformer = new TreeTransformer(selectedTree, this.propertiesFilter)
.showOnlyProtoDump()
.setIsShowDefaults(this.propertiesUserOptions["showDefaults"]?.enabled)
.setIsShowDiff(this.propertiesUserOptions["showDiff"]?.enabled)
.setTransformerOptions({skip: selectedTree.skip})
.setProperties(this.entry)
.setDiffProperties(this.previousEntry);
const transformedTree = transformer.transform();
return transformedTree;
@@ -224,11 +229,11 @@ export class Presenter {
private propertiesFilter: FilterType = getFilter("");
private highlightedItems: Array<string> = [];
private displayIds: Array<number> = [];
private pinnedItems: Array<Tree> = [];
private pinnedItems: Array<HierarchyTree> = [];
private pinnedIds: Array<string> = [];
private selectedTree: any = null;
private previousEntry: any = null;
private entry: any = null;
private selectedHierarchyTree: HierarchyTree | null = null;
private previousEntry: TreeFlickerItem | null = null;
private entry: TreeFlickerItem | null = null;
private hierarchyUserOptions: UserOptions = {
showDiff: {
name: "Show diff",

View File

@@ -13,19 +13,19 @@
* See the License for the specific language governing permissions and
* limitations under the License.
*/
import { Tree } from "viewers/common/tree_utils";
import { HierarchyTree, PropertiesTree } from "viewers/common/tree_utils";
import { UserOptions } from "viewers/common/user_options";
import { TraceType } from "common/trace/trace_type";
import { Rectangle } from "viewers/common/rectangle";
export class UiData {
dependencies: Array<TraceType> = [TraceType.WINDOW_MANAGER];
rects?: Rectangle[] = [];
displayIds?: number[] = [];
highlightedItems?: Array<string> = [];
pinnedItems?: Array<Tree> = [];
hierarchyUserOptions?: UserOptions = {};
propertiesUserOptions?: UserOptions = {};
tree?: Tree | null = null;
selectedTree?: any = {};
rects: Rectangle[] = [];
displayIds: number[] = [];
highlightedItems: Array<string> = [];
pinnedItems: Array<HierarchyTree> = [];
hierarchyUserOptions: UserOptions = {};
propertiesUserOptions: UserOptions = {};
tree: HierarchyTree | null = null;
propertiesTree: PropertiesTree | null = null;
}

View File

@@ -37,7 +37,7 @@ import { PersistentStore } from "common/persistent_store";
<div fxLayout="row wrap" fxLayoutGap="10px grid" class="card-grid">
<mat-card id="wm-hierarchy-view" class="hierarchy-view">
<hierarchy-view
[tree]="inputData?.tree"
[tree]="inputData?.tree ?? null"
[dependencies]="inputData?.dependencies ?? []"
[highlightedItems]="inputData?.highlightedItems ?? []"
[pinnedItems]="inputData?.pinnedItems ?? []"
@@ -48,7 +48,7 @@ import { PersistentStore } from "common/persistent_store";
<mat-card id="wm-properties-view" class="properties-view">
<properties-view
[userOptions]="inputData?.propertiesUserOptions ?? {}"
[selectedTree]="inputData?.selectedTree ?? {}"
[propertiesTree]="inputData?.propertiesTree ?? {}"
></properties-view>
</mat-card>
</div>