Add data model to tree and colour changeset operation

Summary:
This colours data model nodes in the tree to show how they were part of the current changeset: whether the data model caused an insert, update, remove etc.
This also adds phantom Section nodes for Sections that were completely removed from the tree; this is helpful for debugging cases where a section's key changes without intention, causing items to be completely removed and inserted.

Reviewed By: topwu

Differential Revision: D16165767

fbshipit-source-id: a011666dce33e4d757fb415e71ba163aa334f6e6
This commit is contained in:
Mihaela Ogrezeanu
2019-07-11 09:50:54 -07:00
committed by Facebook Github Bot
parent 0d4e2e6eb3
commit 474cc1289e

View File

@@ -6,6 +6,7 @@
*/
package com.facebook.flipper.plugins.sections;
import android.annotation.SuppressLint;
import com.facebook.flipper.core.FlipperArray;
import com.facebook.flipper.core.FlipperObject;
import com.facebook.litho.sections.Change;
@@ -15,8 +16,11 @@ import com.facebook.litho.sections.ChangesetDebugConfiguration.ChangesetDebugLis
import com.facebook.litho.sections.Section;
import com.facebook.litho.sections.SectionsLogEventUtils;
import com.facebook.litho.sections.SectionsLogEventUtils.ApplyNewChangeSet;
import java.lang.reflect.Field;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.concurrent.atomic.AtomicInteger;
public class ChangesetDebug implements ChangesetDebugListener {
@@ -49,7 +53,7 @@ public class ChangesetDebug implements ChangesetDebugListener {
@Override
public void onChangesetApplied(
Section rootSection,
Section oldSection,
Section oldRootSection,
ChangesInfo changesInfo,
String surfaceId,
@ApplyNewChangeSet int attribution,
@@ -58,9 +62,13 @@ public class ChangesetDebug implements ChangesetDebugListener {
final FlipperObject.Builder changesetData = new FlipperObject.Builder();
final String sourceName = SectionsLogEventUtils.applyNewChangeSetSourceToString(attribution);
extractSidePanelChangesetData(changesInfo, changesetData, rootSection.getGlobalKey());
createSectionTree(rootSection, "", tree, oldSection);
createSectionTree(rootSection, tree, oldRootSection);
List<DataModelChangeInfo> prevData = getDataFromPreviousTree(oldRootSection);
applyChangesInfoOnPreviousData(prevData, changesInfo, tree);
sSectionsFlipperPlugin.onChangesetApplied(
sourceName + " " + extra,
@@ -154,7 +162,235 @@ public class ChangesetDebug implements ChangesetDebugListener {
return null;
}
static void createSectionTree(
/** Represents a data model node in a DiffSection. */
private static class DataModelChangeInfo {
static final int UNCHANGED = -100;
Object model;
int operation = UNCHANGED;
String sectionKey;
}
/**
* Skips all nodes which have been removed and finds the item on which the Change operation with
* the given index is applied on.
*/
private static int getPositionWithChangesApplied(
List<DataModelChangeInfo> dataInfos, int operationIndex) {
int count = -1;
int i = 0;
int size = dataInfos.size();
for (i = 0; i < size; i++) {
if (dataInfos.get(i).operation != Change.DELETE
&& dataInfos.get(i).operation != Change.DELETE_RANGE) {
count++;
}
if (count == operationIndex) {
break;
}
}
return i;
}
/** For a given DiffSectionSpec section, returns the list of data on which it performs diffing. */
private static List getDataFromPreviousSection(Section previousSection) {
List data = new ArrayList();
if (previousSection == null) {
return data;
}
Class clasz = previousSection.getClass();
final String diffSectionType = clasz.getSimpleName();
try {
if (diffSectionType.equals("DataDiffSection")) {
Field field = clasz.getDeclaredField("data");
field.setAccessible(true);
data = (List) field.get(previousSection);
} else if (diffSectionType.equals("SingleComponentSection")) {
Field field = clasz.getDeclaredField("component");
field.setAccessible(true);
data.add(field.get(previousSection));
}
} catch (@SuppressLint("NewApi") NoSuchFieldException | IllegalAccessException e) {
e.printStackTrace();
}
return data;
}
private static void addRemovedSectionNodes(
Section previousRoot, Section currentRoot, final FlipperArray.Builder tree) {
final Map<String, Section> prevSections = serializeChildren(previousRoot);
final Map<String, Section> currSections = serializeChildren(currentRoot);
for (String prevSectionKey : prevSections.keySet()) {
if (!currSections.containsKey(prevSectionKey)) {
final FlipperObject.Builder nodeBuilder = new FlipperObject.Builder();
final Section section = prevSections.get(prevSectionKey);
nodeBuilder.put("identifier", prevSectionKey);
nodeBuilder.put("name", section.getSimpleName());
nodeBuilder.put(
"parent", section.getParent() == null ? null : section.getParent().getGlobalKey());
nodeBuilder.put("removed", true);
tree.put(nodeBuilder.build());
}
}
}
private static Map<String, Section> serializeChildren(Section root) {
final Map<String, Section> children = new HashMap<>();
serializeChildrenRecursive(root, children);
return children;
}
private static void serializeChildrenRecursive(Section root, Map<String, Section> children) {
if (root == null) {
return;
}
children.put(root.getGlobalKey(), root);
if (root.getChildren() == null) {
return;
}
for (Section child : root.getChildren()) {
serializeChildrenRecursive(child, children);
}
}
private static List<DataModelChangeInfo> getDataFromPreviousTree(Section previousRoot) {
final List<DataModelChangeInfo> dataModelChangeInfos = new ArrayList<>();
getDataFromPreviousTreeRecursive(previousRoot, dataModelChangeInfos);
return dataModelChangeInfos;
}
private static void getDataFromPreviousTreeRecursive(
Section previousRoot, List<DataModelChangeInfo> data) {
if (previousRoot == null) {
return;
}
if (previousRoot.isDiffSectionSpec()) {
List models = getDataFromPreviousSection(previousRoot);
for (int i = 0; i < models.size(); i++) {
DataModelChangeInfo dataModelChangeInfo = new DataModelChangeInfo();
dataModelChangeInfo.model = models.get(i);
dataModelChangeInfo.sectionKey = previousRoot.getGlobalKey();
data.add(dataModelChangeInfo);
}
} else if (previousRoot.getChildren() != null) {
for (Section child : previousRoot.getChildren()) {
getDataFromPreviousTreeRecursive(child, data);
}
}
}
private static void applyChangesInfoOnPreviousData(
List<DataModelChangeInfo> dataInfos, ChangesInfo changesInfo, FlipperArray.Builder tree) {
final List<Change> changes = changesInfo.getAllChanges();
for (int i = 0; i < changes.size(); i++) {
final Change change = changes.get(i);
int index = change.getIndex();
switch (change.getType()) {
case Change.INSERT:
{
DataModelChangeInfo dataInfo = new DataModelChangeInfo();
if (change.getNextData() != null) {
dataInfo.model = change.getNextData().get(0);
}
dataInfo.sectionKey =
(String) change.getRenderInfo().getDebugInfo("section_global_key");
dataInfo.operation = Change.INSERT;
int addToPosition = getPositionWithChangesApplied(dataInfos, index);
dataInfos.add(addToPosition, dataInfo);
break;
}
case Change.INSERT_RANGE:
{
int addToPosition = getPositionWithChangesApplied(dataInfos, index);
for (int item = 0; item < change.getCount(); item++) {
DataModelChangeInfo dataInfo = new DataModelChangeInfo();
if (change.getNextData() != null) {
dataInfo.model = change.getNextData().get(item);
}
dataInfo.operation = Change.INSERT_RANGE;
dataInfo.sectionKey =
(String) change.getRenderInfos().get(item).getDebugInfo("section_global_key");
dataInfos.add(addToPosition + item, dataInfo);
}
break;
}
case Change.DELETE:
{
int addToPosition = getPositionWithChangesApplied(dataInfos, index);
DataModelChangeInfo dataInfo = dataInfos.get(addToPosition);
dataInfo.operation = Change.DELETE;
break;
}
case Change.DELETE_RANGE:
{
int addToPosition = getPositionWithChangesApplied(dataInfos, index);
for (int del = addToPosition; del < addToPosition + change.getCount(); del++) {
dataInfos.get(del).operation = Change.DELETE_RANGE;
}
break;
}
case Change.UPDATE:
{
int getPosition = getPositionWithChangesApplied(dataInfos, index);
dataInfos.get(getPosition).operation = Change.UPDATE;
break;
}
case Change.UPDATE_RANGE:
{
for (int updateIndex = index; updateIndex < index + change.getCount(); updateIndex++) {
int getPosition = getPositionWithChangesApplied(dataInfos, updateIndex);
dataInfos.get(getPosition).operation = Change.UPDATE_RANGE;
}
break;
}
default:
break;
}
}
for (int i = 0; i < dataInfos.size(); i++) {
DataModelChangeInfo dataInfo = dataInfos.get(i);
final FlipperObject.Builder dataObject = new FlipperObject.Builder();
String name = dataInfo.model == null ? "N/A" : dataInfo.model.toString();
int operation = dataInfo.operation;
dataObject.put("identifier", name);
dataObject.put("name", name);
dataObject.put("parent", dataInfo.sectionKey);
dataObject.put("unchanged", operation == DataModelChangeInfo.UNCHANGED);
dataObject.put("inserted", operation == Change.INSERT || operation == Change.INSERT_RANGE);
dataObject.put("removed", operation == Change.DELETE || operation == Change.DELETE_RANGE);
dataObject.put("updated", operation == Change.UPDATE || operation == Change.UPDATE_RANGE);
tree.put(dataObject.build());
}
}
private static void createSectionTree(
Section rootSection, FlipperArray.Builder tree, Section oldRootSection) {
createSectionTreeRecursive(rootSection, "", tree, oldRootSection);
addRemovedSectionNodes(oldRootSection, rootSection, tree);
}
private static void createSectionTreeRecursive(
Section rootSection, String parentKey, FlipperArray.Builder tree, Section oldRootSection) {
if (rootSection == null) {
return;
@@ -179,7 +415,7 @@ public class ChangesetDebug implements ChangesetDebugListener {
}
for (int i = 0; i < rootSection.getChildren().size(); i++) {
createSectionTree(rootSection.getChildren().get(i), globalKey, tree, oldRootSection);
createSectionTreeRecursive(rootSection.getChildren().get(i), globalKey, tree, oldRootSection);
}
}
}