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:
committed by
Facebook Github Bot
parent
0d4e2e6eb3
commit
474cc1289e
@@ -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);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user