refactor aggrow to make adding new sources of data easier
Reviewed By: michalgr Differential Revision: D3961648 fbshipit-source-id: 3c77d3c1352fd89e12163eee393ffcebe09ea8e3
This commit is contained in:
parent
cd6f9f95d2
commit
6ddf8a8795
|
@ -3,8 +3,8 @@
|
|||
<head>
|
||||
<meta charset="utf-8">
|
||||
<title>JSC Heap Capture</title>
|
||||
<script src="https://cdnjs.cloudflare.com/ajax/libs/react/0.14.1/react.min.js"></script>
|
||||
<script src="https://cdnjs.cloudflare.com/ajax/libs/react/0.14.1/react-dom.min.js"></script>
|
||||
<script src="https://cdnjs.cloudflare.com/ajax/libs/react/0.14.1/react.js"></script>
|
||||
<script src="https://cdnjs.cloudflare.com/ajax/libs/react/0.14.1/react-dom.js"></script>
|
||||
<script src="out/aggrow.js"></script>
|
||||
<script src="out/table.js"></script>
|
||||
</head>
|
||||
|
|
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
|
@ -16,7 +16,7 @@
|
|||
// pivot around frames in the middle of a stack by callers / callees
|
||||
// graphing?
|
||||
|
||||
function stringInterner() { // eslint-disable-line no-unused-vars
|
||||
function StringInterner() { // eslint-disable-line no-unused-vars
|
||||
const strings = [];
|
||||
const ids = {};
|
||||
return {
|
||||
|
@ -37,20 +37,16 @@ function stringInterner() { // eslint-disable-line no-unused-vars
|
|||
};
|
||||
}
|
||||
|
||||
function stackData(stackIdMap, maxDepth) { // eslint-disable-line no-unused-vars
|
||||
return {
|
||||
maxDepth: maxDepth,
|
||||
get: function getStack(id) {
|
||||
return stackIdMap[id];
|
||||
},
|
||||
};
|
||||
}
|
||||
|
||||
function stackRegistry() { // eslint-disable-line no-unused-vars
|
||||
function StackRegistry() { // eslint-disable-line no-unused-vars
|
||||
return {
|
||||
root: { id: 0 },
|
||||
nodeCount: 1,
|
||||
maxDepth: -1,
|
||||
stackIdMap: null,
|
||||
insert: function insertNode(parent, frameId) {
|
||||
if (this.stackIdMap !== null) {
|
||||
throw 'stacks already flattened';
|
||||
}
|
||||
let node = parent[frameId];
|
||||
if (node === undefined) {
|
||||
node = { id: this.nodeCount };
|
||||
|
@ -59,7 +55,13 @@ function stackRegistry() { // eslint-disable-line no-unused-vars
|
|||
}
|
||||
return node;
|
||||
},
|
||||
get: function getStackArray(id) {
|
||||
return this.stackIdMap[id];
|
||||
},
|
||||
flatten: function flattenStacks() {
|
||||
if (this.stackIdMap !== null) {
|
||||
return;
|
||||
}
|
||||
let stackFrameCount = 0;
|
||||
function countStacks(tree, depth) {
|
||||
let leaf = true;
|
||||
|
@ -109,13 +111,150 @@ function stackRegistry() { // eslint-disable-line no-unused-vars
|
|||
return stackIdMap[id];
|
||||
}
|
||||
flattenStacksImpl(this.root, []);
|
||||
|
||||
return new stackData(stackIdMap, maxStackDepth);
|
||||
this.root = null;
|
||||
this.stackIdMap = stackIdMap;
|
||||
this.maxDepth = maxStackDepth;
|
||||
},
|
||||
};
|
||||
}
|
||||
|
||||
function aggrow(numRows) { // eslint-disable-line no-unused-vars
|
||||
function AggrowData(columns) { // eslint-disable-line no-unused-vars
|
||||
const columnCount = columns.length;
|
||||
const columnConverter = columns.map(c => {
|
||||
switch (c.type) {
|
||||
case 'int': // stores raw value
|
||||
return (i) => i;
|
||||
case 'string': // stores interned id of string
|
||||
return (s) => c.strings.intern(s);
|
||||
case 'stack': // stores id of stack node
|
||||
return (s) => s.id;
|
||||
default:
|
||||
throw 'unknown AggrowData column type';
|
||||
}
|
||||
});
|
||||
return {
|
||||
data: new Int32Array(0),
|
||||
columns: columns,
|
||||
rowCount: 0,
|
||||
rowInserter: function rowInserter(numRows) {
|
||||
console.log(
|
||||
'increasing row data from ' + (this.data.length * 4).toLocaleString() + ' B to ' +
|
||||
(this.data.length * 4 + numRows * columnCount * 4).toLocaleString() + ' B'
|
||||
);
|
||||
const newData = new Int32Array(this.data.length + numRows * columnCount);
|
||||
newData.set(this.data);
|
||||
let currOffset = this.data.length;
|
||||
const endOffset = newData.length;
|
||||
this.data = newData;
|
||||
this.rowCount = newData.length / columnCount;
|
||||
return {
|
||||
insertRow: function insertRow() {
|
||||
if (currOffset >= endOffset) {
|
||||
throw 'tried to insert data off end of added range';
|
||||
}
|
||||
if (arguments.length !== columnCount) {
|
||||
throw 'expected data for ' + columnCount.toString() + ' columns, got' +
|
||||
arguments.length.toString() + ' columns';
|
||||
}
|
||||
for (let i = 0; i < arguments.length; i++) {
|
||||
newData[currOffset + i] = columnConverter[i](arguments[i]);
|
||||
}
|
||||
currOffset += columnCount;
|
||||
},
|
||||
done: function done() {
|
||||
if (currOffset !== endOffset) {
|
||||
throw 'unfilled rows';
|
||||
}
|
||||
},
|
||||
};
|
||||
},
|
||||
};
|
||||
}
|
||||
|
||||
function Aggrow(aggrowData) {
|
||||
const columns = aggrowData.columns;
|
||||
const columnCount = columns.length;
|
||||
const data = aggrowData.data;
|
||||
function columnIndex(columnName, columnType) {
|
||||
const index = columns.findIndex(c => c.name === columnName && c.type === columnType);
|
||||
if (index < 0) {
|
||||
throw 'did not find data column ' + columnName + ' with type ' + columnType;
|
||||
}
|
||||
return index;
|
||||
}
|
||||
for (let i = 0; i < columns.length; i++) {
|
||||
if (columns[i].type === 'stack') {
|
||||
columns[i].stacks.flatten();
|
||||
}
|
||||
}
|
||||
return {
|
||||
expander: new AggrowExpander(aggrowData.rowCount),
|
||||
addSumAggregator: function addSumAggregator(aggregatorName, columnName) {
|
||||
const index = columnIndex(columnName, 'int');
|
||||
return this.expander.addAggregator(
|
||||
aggregatorName,
|
||||
function aggregateSize(indices) {
|
||||
let size = 0;
|
||||
for (let i = 0; i < indices.length; i++) {
|
||||
const row = indices[i];
|
||||
size += data[row * columnCount + index];
|
||||
}
|
||||
return size;
|
||||
},
|
||||
(value) => value.toLocaleString(),
|
||||
(a, b) => b - a,
|
||||
);
|
||||
},
|
||||
addCountAggregator: function addCountAggregator(aggregatorName) {
|
||||
return this.expander.addAggregator(
|
||||
aggregatorName,
|
||||
function aggregateCount(indices) {
|
||||
return indices.length;
|
||||
},
|
||||
(value) => value.toLocaleString(),
|
||||
(a, b) => b - a,
|
||||
);
|
||||
},
|
||||
addStringExpander: function addStringExpander(expanderName, columnName) {
|
||||
const index = columnIndex(columnName, 'string');
|
||||
const strings = columns[index].strings;
|
||||
return this.expander.addFieldExpander(
|
||||
expanderName,
|
||||
(row) => strings.get(data[row * columnCount + index]),
|
||||
(rowA, rowB) => data[rowA * columnCount + index] - data[rowB * columnCount + index],
|
||||
);
|
||||
},
|
||||
addNumberExpander: function addNumberExpander(expanderName, columnName) {
|
||||
const index = columnIndex(columnName, 'int');
|
||||
return this.expander.addFieldExpander(
|
||||
expanderName,
|
||||
(row) => data[row * columnCount + index].toLocaleString(),
|
||||
(rowA, rowB) => data[rowA * columnCount + index] - data[rowB * columnCount + index],
|
||||
);
|
||||
},
|
||||
addPointerExpander: function addPointerExpander(expanderName, columnName) {
|
||||
const index = columnIndex(columnName, 'int');
|
||||
return this.expander.addFieldExpander(
|
||||
expanderName,
|
||||
(row) => '0x' + (data[row * columnCount + index] >>> 0).toString(),
|
||||
(rowA, rowB) => data[rowA * columnCount + index] - data[rowB * columnCount + index],
|
||||
);
|
||||
},
|
||||
addStackExpander: function addStackExpander(expanderName, columnName, formatter) {
|
||||
// TODO: options for caller/callee, pivoting
|
||||
const index = columnIndex(columnName, 'stack');
|
||||
const stacks = columns[index].stacks;
|
||||
return this.expander.addCalleeStackExpander(
|
||||
expanderName,
|
||||
stacks.maxDepth,
|
||||
(row) => stacks.get(data[row * columnCount + index]),
|
||||
formatter,
|
||||
);
|
||||
},
|
||||
};
|
||||
}
|
||||
|
||||
function AggrowExpander(numRows) { // eslint-disable-line no-unused-vars
|
||||
// expander ID definitions
|
||||
const FIELD_EXPANDER_ID_MIN = 0x0000;
|
||||
const FIELD_EXPANDER_ID_MAX = 0x7fff;
|
||||
|
|
|
@ -8,7 +8,7 @@
|
|||
*/
|
||||
'use strict';
|
||||
/*eslint no-console-disallow: "off"*/
|
||||
/*global React ReactDOM Table stringInterner stackRegistry aggrow preLoadedCapture:true*/
|
||||
/*global React ReactDOM Table StringInterner StackRegistry AggrowData Aggrow preLoadedCapture:true*/
|
||||
|
||||
function RefVisitor(refs, id) {
|
||||
this.refs = refs;
|
||||
|
@ -292,189 +292,88 @@ function registerPathToRoot(refs, registry, strings) {
|
|||
}
|
||||
}
|
||||
|
||||
function captureRegistry() {
|
||||
const strings = stringInterner();
|
||||
const stacks = stackRegistry(strings);
|
||||
const data = new Int32Array(0);
|
||||
|
||||
const idField = 0;
|
||||
const typeField = 1;
|
||||
const sizeField = 2;
|
||||
const traceField = 3;
|
||||
const pathField = 4;
|
||||
const reactField = 5;
|
||||
const valueField = 6;
|
||||
const moduleField = 7;
|
||||
const numFields = 8;
|
||||
|
||||
return {
|
||||
strings: strings,
|
||||
stacks: stacks,
|
||||
data: data,
|
||||
register: function registerCapture(captureId, capture) {
|
||||
// NB: capture.refs is potentially VERY large, so we try to avoid making
|
||||
// copies, even of iteration is a bit more annoying.
|
||||
let rowCount = 0;
|
||||
for (const id in capture.refs) { // eslint-disable-line no-unused-vars
|
||||
rowCount++;
|
||||
}
|
||||
for (const id in capture.markedBlocks) { // eslint-disable-line no-unused-vars
|
||||
rowCount++;
|
||||
}
|
||||
console.log(
|
||||
'increasing row data from ' + (this.data.length * 4).toString() + 'B to ' +
|
||||
(this.data.length * 4 + rowCount * numFields * 4).toString() + 'B'
|
||||
);
|
||||
const newData = new Int32Array(this.data.length + rowCount * numFields);
|
||||
newData.set(data);
|
||||
let dataOffset = this.data.length;
|
||||
this.data = null;
|
||||
|
||||
registerPathToRoot(capture.refs, this.stacks, this.strings);
|
||||
const internedCaptureId = this.strings.intern(captureId);
|
||||
const noneString = this.strings.intern('#none');
|
||||
const noneStack = this.stacks.insert(this.stacks.root, noneString);
|
||||
forEachRef(capture.refs, (visitor) => {
|
||||
const ref = visitor.getRef();
|
||||
const id = visitor.id;
|
||||
newData[dataOffset + idField] = parseInt(id, 16);
|
||||
newData[dataOffset + typeField] = this.strings.intern(ref.type);
|
||||
newData[dataOffset + sizeField] = ref.size;
|
||||
newData[dataOffset + traceField] = internedCaptureId;
|
||||
if (ref.rootPath === undefined) {
|
||||
newData[dataOffset + pathField] = noneStack.id;
|
||||
} else {
|
||||
newData[dataOffset + pathField] = ref.rootPath.id;
|
||||
}
|
||||
if (ref.reactTree === undefined) {
|
||||
newData[dataOffset + reactField] = noneStack.id;
|
||||
} else {
|
||||
newData[dataOffset + reactField] = ref.reactTree.id;
|
||||
}
|
||||
newData[dataOffset + valueField] = this.strings.intern(visitor.getValue());
|
||||
if (ref.module) {
|
||||
newData[dataOffset + moduleField] = this.strings.intern(ref.module);
|
||||
} else {
|
||||
newData[dataOffset + moduleField] = noneString;
|
||||
}
|
||||
dataOffset += numFields;
|
||||
});
|
||||
for (const id in capture.markedBlocks) {
|
||||
const block = capture.markedBlocks[id];
|
||||
newData[dataOffset + idField] = parseInt(id, 16);
|
||||
newData[dataOffset + typeField] = this.strings.intern('Marked Block Overhead');
|
||||
newData[dataOffset + sizeField] = block.capacity - block.size;
|
||||
newData[dataOffset + traceField] = internedCaptureId;
|
||||
newData[dataOffset + pathField] = noneStack.id;
|
||||
newData[dataOffset + reactField] = noneStack.id;
|
||||
newData[dataOffset + valueField] = this.strings.intern(
|
||||
'capacity: ' + block.capacity +
|
||||
', size: ' + block.size +
|
||||
', granularity: ' + block.cellSize
|
||||
);
|
||||
newData[dataOffset + moduleField] = noneString;
|
||||
dataOffset += numFields;
|
||||
}
|
||||
this.data = newData;
|
||||
},
|
||||
getAggrow: function getAggrow() {
|
||||
const agStrings = this.strings;
|
||||
const agStacks = this.stacks.flatten();
|
||||
const agData = this.data;
|
||||
const agNumRows = agData.length / numFields;
|
||||
const ag = new aggrow(agNumRows);
|
||||
|
||||
ag.addFieldExpander('Id',
|
||||
function getId(row) {
|
||||
let id = agData[row * numFields + idField];
|
||||
if (id < 0) {
|
||||
id += 0x100000000; // data is int32, id is uint32
|
||||
}
|
||||
return '0x' + id.toString(16);
|
||||
},
|
||||
function compareAddress(rowA, rowB) {
|
||||
return agData[rowA * numFields + idField] - agData[rowB * numFields + idField];
|
||||
});
|
||||
|
||||
const typeExpander = ag.addFieldExpander('Type',
|
||||
function getType(row) { return agStrings.get(agData[row * numFields + typeField]); },
|
||||
function compareType(rowA, rowB) {
|
||||
return agData[rowA * numFields + typeField] - agData[rowB * numFields + typeField];
|
||||
});
|
||||
|
||||
ag.addFieldExpander('Size',
|
||||
function getSize(row) { return agData[row * numFields + sizeField].toString(); },
|
||||
function compareSize(rowA, rowB) {
|
||||
return agData[rowA * numFields + sizeField] - agData[rowB * numFields + sizeField];
|
||||
});
|
||||
|
||||
ag.addFieldExpander('Trace',
|
||||
function getSize(row) { return agStrings.get(agData[row * numFields + traceField]); },
|
||||
function compareSize(rowA, rowB) {
|
||||
return agData[rowA * numFields + traceField] - agData[rowB * numFields + traceField];
|
||||
});
|
||||
|
||||
const pathExpander = ag.addCalleeStackExpander(
|
||||
'Path',
|
||||
agStacks.maxDepth,
|
||||
function getStack(row) { return agStacks.get(agData[row * numFields + pathField]); },
|
||||
function getFrame(id) { return agStrings.get(id); },
|
||||
);
|
||||
|
||||
const reactExpander = ag.addCalleeStackExpander(
|
||||
'React Tree',
|
||||
agStacks.maxDepth,
|
||||
function getStack(row) { return agStacks.get(agData[row * numFields + reactField]); },
|
||||
function getFrame(id) { return agStrings.get(id); },
|
||||
);
|
||||
|
||||
const valueExpander = ag.addFieldExpander('Value',
|
||||
function getValue(row) { return agStrings.get(agData[row * numFields + valueField]); },
|
||||
function compareValue(rowA, rowB) {
|
||||
return agData[rowA * numFields + valueField] - agData[rowB * numFields + valueField];
|
||||
});
|
||||
|
||||
const moduleExpander = ag.addFieldExpander('Module',
|
||||
function getModule(row) { return agStrings.get(agData[row * numFields + moduleField]); },
|
||||
function compareModule(rowA, rowB) {
|
||||
return agData[rowA * numFields + moduleField] - agData[rowB * numFields + moduleField];
|
||||
});
|
||||
|
||||
const sizeAggregator = ag.addAggregator('Size',
|
||||
function aggregateSize(indices) {
|
||||
let size = 0;
|
||||
for (let i = 0; i < indices.length; i++) {
|
||||
const row = indices[i];
|
||||
size += agData[row * numFields + sizeField];
|
||||
}
|
||||
return size;
|
||||
},
|
||||
function formatSize(value) { return value.toString(); },
|
||||
function sortSize(a, b) { return b - a; } );
|
||||
|
||||
const countAggregator = ag.addAggregator('Count',
|
||||
function aggregateCount(indices) {
|
||||
return indices.length;
|
||||
},
|
||||
function formatCount(value) { return value.toString(); },
|
||||
function sortCount(a, b) { return b - a; } );
|
||||
|
||||
ag.setActiveExpanders([
|
||||
pathExpander,
|
||||
reactExpander,
|
||||
moduleExpander,
|
||||
typeExpander,
|
||||
valueExpander,
|
||||
]);
|
||||
ag.setActiveAggregators([sizeAggregator, countAggregator]);
|
||||
return ag;
|
||||
},
|
||||
};
|
||||
function registerCapture(data, captureId, capture, stacks, strings) {
|
||||
// NB: capture.refs is potentially VERY large, so we try to avoid making
|
||||
// copies, even if iteration is a bit more annoying.
|
||||
let rowCount = 0;
|
||||
for (const id in capture.refs) { // eslint-disable-line no-unused-vars
|
||||
rowCount++;
|
||||
}
|
||||
for (const id in capture.markedBlocks) { // eslint-disable-line no-unused-vars
|
||||
rowCount++;
|
||||
}
|
||||
const inserter = data.rowInserter(rowCount);
|
||||
registerPathToRoot(capture.refs, stacks, strings);
|
||||
const noneString = strings.intern('#none');
|
||||
const noneStack = stacks.insert(stacks.root, noneString);
|
||||
forEachRef(capture.refs, (visitor) => {
|
||||
// want to data.append(value, value, value), not IDs
|
||||
const ref = visitor.getRef();
|
||||
const id = visitor.id;
|
||||
inserter.insertRow(
|
||||
parseInt(id, 16),
|
||||
ref.type,
|
||||
ref.size,
|
||||
captureId,
|
||||
ref.rootPath === undefined ? noneStack : ref.rootPath,
|
||||
ref.reactTree === undefined ? noneStack : ref.reactTree,
|
||||
visitor.getValue(),
|
||||
ref.module === undefined ? '#none' : ref.module,
|
||||
);
|
||||
});
|
||||
for (const id in capture.markedBlocks) {
|
||||
const block = capture.markedBlocks[id];
|
||||
inserter.insertRow(
|
||||
parseInt(id, 16),
|
||||
'Marked Block Overhead',
|
||||
block.capacity - block.size,
|
||||
captureId,
|
||||
noneStack,
|
||||
noneStack,
|
||||
'capacity: ' + block.capacity + ', size: ' + block.size + ', granularity: ' + block.cellSize,
|
||||
'#none',
|
||||
);
|
||||
}
|
||||
inserter.done();
|
||||
}
|
||||
|
||||
if (preLoadedCapture) {
|
||||
const r = new captureRegistry();
|
||||
r.register('trace', preLoadedCapture);
|
||||
const strings = StringInterner();
|
||||
const stacks = new StackRegistry();
|
||||
const columns = [
|
||||
{ name: 'id', type: 'int' },
|
||||
{ name: 'type', type: 'string', strings: strings },
|
||||
{ name: 'size', type: 'int' },
|
||||
{ name: 'trace', type: 'string', strings: strings },
|
||||
{ name: 'path', type: 'stack', stacks: stacks },
|
||||
{ name: 'react', type: 'stack', stacks: stacks },
|
||||
{ name: 'value', type: 'string', strings: strings },
|
||||
{ name: 'module', type: 'string', strings: strings },
|
||||
];
|
||||
const data = new AggrowData(columns);
|
||||
registerCapture(data, 'trace', preLoadedCapture, stacks, strings);
|
||||
preLoadedCapture = undefined; // let GG clean up the capture
|
||||
ReactDOM.render(<Table aggrow={r.getAggrow()} />, document.body);
|
||||
const aggrow = new Aggrow(data);
|
||||
aggrow.addPointerExpander('Id', 'id');
|
||||
const typeExpander = aggrow.addStringExpander('Type', 'type');
|
||||
aggrow.addNumberExpander('Size', 'size');
|
||||
aggrow.addStringExpander('Trace', 'trace');
|
||||
const pathExpander = aggrow.addStackExpander('Path', 'path', strings.get);
|
||||
const reactExpander = aggrow.addStackExpander('React Tree', 'react', strings.get);
|
||||
const valueExpander = aggrow.addStringExpander('Value', 'value');
|
||||
const moduleExpander = aggrow.addStringExpander('Module', 'module');
|
||||
aggrow.expander.setActiveExpanders([
|
||||
pathExpander,
|
||||
reactExpander,
|
||||
moduleExpander,
|
||||
typeExpander,
|
||||
valueExpander,
|
||||
]);
|
||||
const sizeAggregator = aggrow.addSumAggregator('Size', 'size');
|
||||
const countAggregator = aggrow.addCountAggregator('Count');
|
||||
aggrow.expander.setActiveAggregators([
|
||||
sizeAggregator,
|
||||
countAggregator,
|
||||
]);
|
||||
ReactDOM.render(<Table aggrow={aggrow.expander} />, document.body);
|
||||
}
|
||||
|
|
|
@ -24,12 +24,14 @@ class Draggable extends React.Component { // eslint-disable-line no-unused-vars
|
|||
|
||||
render() {
|
||||
const id = this.props.id;
|
||||
function dragStart(e) {
|
||||
e.dataTransfer.setData('text/plain', id);
|
||||
}
|
||||
return React.cloneElement(
|
||||
this.props.children,
|
||||
{ draggable: 'true', onDragStart: dragStart }
|
||||
{
|
||||
draggable: 'true',
|
||||
onDragStart: (e) => {
|
||||
e.dataTransfer.setData('text', id);
|
||||
},
|
||||
}
|
||||
);
|
||||
}
|
||||
}
|
||||
|
@ -45,23 +47,15 @@ class DropTarget extends React.Component { // eslint-disable-line no-unused-vars
|
|||
|
||||
render() {
|
||||
const thisId = this.props.id;
|
||||
const dropFilter = this.props.dropFilter;
|
||||
const dropAction = this.props.dropAction;
|
||||
return React.cloneElement(
|
||||
this.props.children,
|
||||
{
|
||||
onDragOver: (e) => {
|
||||
const sourceId = e.dataTransfer.getData('text/plain');
|
||||
if (dropFilter(sourceId)) {
|
||||
e.preventDefault();
|
||||
}
|
||||
},
|
||||
onDragOver: (e) => e.preventDefault(),
|
||||
onDrop: (e) => {
|
||||
const sourceId = e.dataTransfer.getData('text/plain');
|
||||
if (dropFilter(sourceId)) {
|
||||
e.preventDefault();
|
||||
dropAction(sourceId, thisId);
|
||||
}
|
||||
const sourceId = e.dataTransfer.getData('text');
|
||||
e.preventDefault();
|
||||
dropAction(sourceId, thisId);
|
||||
},
|
||||
}
|
||||
);
|
||||
|
@ -71,7 +65,6 @@ class DropTarget extends React.Component { // eslint-disable-line no-unused-vars
|
|||
DropTarget.propTypes = {
|
||||
children: React.PropTypes.element.isRequired,
|
||||
id: React.PropTypes.string.isRequired,
|
||||
dropFilter: React.PropTypes.func.isRequired,
|
||||
dropAction: React.PropTypes.func.isRequired,
|
||||
};
|
||||
|
||||
|
@ -156,7 +149,6 @@ class TableHeader extends React.Component {
|
|||
headers.push((
|
||||
<DropTarget
|
||||
id={'aggregate:insert:' + i.toString()}
|
||||
dropFilter={(s) => s.startsWith('aggregate')}
|
||||
dropAction={this.props.dropAction}
|
||||
>
|
||||
<div style={{
|
||||
|
@ -173,7 +165,6 @@ class TableHeader extends React.Component {
|
|||
headers.push((
|
||||
<DropTarget
|
||||
id="divider:insert"
|
||||
dropFilter={(s) => s.startsWith('aggregate') || s.startsWith('expander')}
|
||||
dropAction={this.props.dropAction}
|
||||
>
|
||||
<div style={{
|
||||
|
@ -200,7 +191,6 @@ class TableHeader extends React.Component {
|
|||
headers.push((
|
||||
<DropTarget
|
||||
id={'expander:insert:' + (i + 1).toString()}
|
||||
dropFilter={()=>{return true; }}
|
||||
dropAction={this.props.dropAction}
|
||||
>
|
||||
<div style={{
|
||||
|
|
Loading…
Reference in New Issue