diff --git a/packages/xod-client/src/debugger/reducer.js b/packages/xod-client/src/debugger/reducer.js
index 726fd73e..f7610efc 100644
--- a/packages/xod-client/src/debugger/reducer.js
+++ b/packages/xod-client/src/debugger/reducer.js
@@ -33,7 +33,12 @@ import {
import * as EAT from '../editor/actionTypes';
-import { UPLOAD_MSG_TYPE, LOG_TAB_TYPE, SESSION_TYPE, NEW_SHEET } from './constants';
+import {
+ UPLOAD_MSG_TYPE,
+ LOG_TAB_TYPE,
+ SESSION_TYPE,
+ NEW_SHEET,
+} from './constants';
import * as MSG from './messages';
import { STATUS } from '../utils/constants';
import { isXodErrorMessage } from './debugProtocol';
@@ -140,10 +145,15 @@ const addMessagesOrIncrementSkippedLines = R.curry(
const updateInteractiveNodeValues = R.curry((messageList, state) => {
const MapToRekey = R.prop('nodeIdsMap', state);
+ const tableLogNodeIds = R.compose(
+ R.unnest,
+ R.values,
+ R.prop('tableLogSources')
+ )(state);
// All node ids that is not represented in `tableLogNodeIds` is `watch` nodes
const [tableLogMessages, watchNodeMessages] = R.compose(
R.map(R.fromPairs),
- R.partition(R.compose(isAmong(state.tableLogNodeIds), R.nth(0))),
+ R.partition(R.compose(isAmong(tableLogNodeIds), R.nth(0))),
R.toPairs,
renameKeys(MapToRekey),
R.groupBy(R.prop('nodeId')),
@@ -203,6 +213,14 @@ const updateInteractiveNodeValues = R.curry((messageList, state) => {
)(state);
});
+const updateTableLogSources = R.curry((patchPath, tableLogNodeIds, state_) =>
+ R.over(
+ R.lensPath(['tableLogSources', patchPath]),
+ R.compose(R.uniq, R.concat(tableLogNodeIds), R.defaultTo([])),
+ state_
+ )
+);
+
/* eslint-disable no-bitwise */
const filterPinKeysByBitmask = R.curry((pinKeys, bitmask) =>
R.addIndex(R.filter)((val, idx) => bitmask & (1 << idx), pinKeys)
@@ -522,7 +540,10 @@ export default (state = initialState, action) => {
R.assoc('isSkippingNewSerialLogLines', false),
R.assoc('numberOfSkippedSerialLogLines', 0),
R.assoc('nodeIdsMap', invertedNodeIdsMap),
- R.assoc('tableLogNodeIds', action.payload.tableLogNodeIds),
+ updateTableLogSources(
+ action.payload.patchPath,
+ action.payload.tableLogNodeIds
+ ),
R.assoc('nodePinKeysMap', action.payload.nodePinKeysMap),
rekeyAndAssocPinsAffectedByErrorRaisers(
invertedNodeIdsMap,
@@ -648,7 +669,11 @@ export default (state = initialState, action) => {
R.assoc('isPreparingSimulation', false),
R.assoc('isOutdated', false),
R.assoc('uploadProgress', null),
- R.assoc('tableLogNodeIds', action.payload.tableLogNodeIds),
+ updateTableLogSources(
+ action.payload.patchPath,
+ action.payload.tableLogNodeIds
+ ),
+
R.assoc('nodeIdsMap', invertedNodeIdsMap),
R.assoc('nodePinKeysMap', action.payload.nodePinKeysMap),
R.assoc('globals', action.payload.globals),
diff --git a/packages/xod-client/src/debugger/selectors.js b/packages/xod-client/src/debugger/selectors.js
index a903d392..d308ff19 100644
--- a/packages/xod-client/src/debugger/selectors.js
+++ b/packages/xod-client/src/debugger/selectors.js
@@ -11,6 +11,7 @@ import { DEBUGGER_TAB_ID } from '../editor/constants';
import { SESSION_TYPE } from './constants';
import { createMemoizedSelector } from '../utils/selectorTools';
+import { getTableLogSourceLabels } from './utils';
export const getDebuggerState = R.prop('debugger');
@@ -308,7 +309,19 @@ export const getTableLogValues = R.compose(
getDebuggerState
);
-export const getTableLogSources = R.compose(R.keys, getTableLogValues);
+const getTableLogSourcesRaw = R.compose(
+ R.prop('tableLogSources'),
+ getDebuggerState
+);
+
+// :: State -> [{ nodeId: NodeId, label: String }]
+export const getTableLogSources = state =>
+ R.compose(
+ R.unnest,
+ R.values,
+ R.mapObjIndexed(getTableLogSourceLabels(state.project)),
+ getTableLogSourcesRaw
+ )(state);
export const getTableLogsByNodeId = R.curry((nodeId, state) =>
R.compose(R.pathOr([], ['tableLogValues', nodeId]), getDebuggerState)(state)
diff --git a/packages/xod-client/src/debugger/state.js b/packages/xod-client/src/debugger/state.js
index ed4e5f0c..7213be27 100644
--- a/packages/xod-client/src/debugger/state.js
+++ b/packages/xod-client/src/debugger/state.js
@@ -39,7 +39,10 @@ export default {
currentStage: LOG_TAB_TYPE.COMPILER,
nodeIdsMap: {},
watchNodeValues: {},
- tableLogNodeIds: [],
+ tableLogSources: {
+ // PatchPath : [NodeId]
+ // where PatchPath is a path of root patch that was compiled
+ },
tableLogValues: {
// NodeId : [ /* Experiments history */ [[String]] ]
},
diff --git a/packages/xod-client/src/debugger/utils.js b/packages/xod-client/src/debugger/utils.js
index 756dca34..f7efab5e 100644
--- a/packages/xod-client/src/debugger/utils.js
+++ b/packages/xod-client/src/debugger/utils.js
@@ -1,6 +1,6 @@
import * as R from 'ramda';
import * as XP from 'xod-project';
-import { foldMaybe } from 'xod-func-tools';
+import { foldMaybe, mapIndexed } from 'xod-func-tools';
import { UPLOAD_MSG_TYPE } from './constants';
@@ -38,3 +38,93 @@ export const getTetheringInetNodeId = R.curry(
);
export const isErrorMessage = R.propEq('type', UPLOAD_MSG_TYPE.ERROR);
+
+// :: Project -> [NodeId] -> PatchPath -> [{ nodeId: NodeId, label: String }]
+export const getTableLogSourceLabels = R.curry(
+ (project, sourceNodeIds, rootPatchPath) =>
+ R.compose(
+ R.map(
+ R.applySpec({
+ nodeId: R.prop('nodeId'),
+ label: R.compose(
+ R.join(' > '),
+ R.concat([rootPatchPath]),
+ R.pluck('finalLabel'),
+ R.prop('chunks')
+ ),
+ })
+ ),
+ R.reduce((acc, nextSource) => {
+ const reducedChunks = R.pluck('chunks', acc);
+ const result = R.over(
+ R.lensProp('chunks'),
+ mapIndexed((chunk, idx) =>
+ R.compose(
+ R.ifElse(
+ R.equals(0),
+ () => R.assoc('finalLabel', chunk.label, chunk),
+ num =>
+ R.assoc('finalLabel', `${chunk.label} #${num + 1}`, chunk)
+ ),
+ R.length,
+ R.uniqBy(R.both(R.prop('label'), R.prop('nodeId'))),
+ R.filter(
+ rc =>
+ rc.nodeId !== chunk.nodeId &&
+ rc.label === chunk.label &&
+ rc.parent === chunk.parent
+ ),
+ R.reject(R.isNil),
+ R.pluck(idx)
+ )(reducedChunks)
+ ),
+ nextSource
+ );
+ return [...acc, result];
+ }, []),
+ R.map(nodeId => {
+ // Convert chained NodeId (`a~b~c`) into [NodeId] (['a', 'b', 'c'])
+ const splittedNodeId = R.split('~', nodeId);
+ return R.compose(
+ foldMaybe(
+ {
+ nodeId,
+ chunks: [
+ {
+ nodeId,
+ label: [`DELETED (${nodeId})`],
+ },
+ ],
+ },
+ R.applySpec({
+ nodeId: R.always(nodeId),
+ chunks: R.identity,
+ })
+ ),
+ R.map(
+ mapIndexed((chunk, idx) =>
+ R.compose(
+ R.assoc('parent', R.__, chunk),
+ R.join('~'),
+ R.concat([rootPatchPath]),
+ R.take(idx)
+ )(splittedNodeId)
+ )
+ ),
+ XP.mapNestedNodes(
+ node => {
+ const nodeType = XP.getNodeType(node);
+ const label = XP.getNodeLabel(node);
+ return {
+ nodeType,
+ nodeId: XP.getNodeId(node),
+ label: label.length ? label : XP.getBaseName(nodeType),
+ };
+ },
+ project,
+ rootPatchPath
+ )
+ )(splittedNodeId);
+ })
+ )(sourceNodeIds)
+);
diff --git a/packages/xod-client/src/editor/containers/TableLog.jsx b/packages/xod-client/src/editor/containers/TableLog.jsx
index c66904f7..f14c8565 100644
--- a/packages/xod-client/src/editor/containers/TableLog.jsx
+++ b/packages/xod-client/src/editor/containers/TableLog.jsx
@@ -83,7 +83,7 @@ class TableLog extends React.Component {
onSaveLogClicked() {
const tsv = gridToTsv(this.getSheet());
- const defaultFilename = `log-${this.props.nodeId}-${
+ const defaultFilename = `log-${this.getNodeLabel()}-${
this.props.sheetIndex
}.txt`;
@@ -116,6 +116,13 @@ class TableLog extends React.Component {
return this.props.document.length;
}
+ getNodeLabel() {
+ return R.compose(
+ R.propOr('unknown', 'label'),
+ R.find(R.propEq('nodeId', this.props.nodeId))
+ )(this.props.sources);
+ }
+
isEmptySheet() {
return this.getSheet().length === 0;
}
@@ -136,9 +143,9 @@ class TableLog extends React.Component {
disabled={hasNoSources}
>
{hasNoSources ? : null}
- {this.props.sources.map(nodeId => (
+ {this.props.sources.map(({ nodeId, label }) => (
))}
@@ -202,7 +209,7 @@ TableLog.propTypes = {
// from parent
tabId: PropTypes.string.isRequired,
// connect
- sources: PropTypes.arrayOf(PropTypes.string).isRequired,
+ sources: PropTypes.arrayOf(PropTypes.object).isRequired,
document: PropTypes.array.isRequired,
nodeId: PropTypes.string.isRequired,
sheetIndex: PropTypes.number.isRequired,
diff --git a/packages/xod-client/test/tableLogSources.spec.js b/packages/xod-client/test/tableLogSources.spec.js
new file mode 100644
index 00000000..f8d290b6
--- /dev/null
+++ b/packages/xod-client/test/tableLogSources.spec.js
@@ -0,0 +1,263 @@
+import * as R from 'ramda';
+import { assert } from 'chai';
+import { defaultizeProject } from 'xod-project/test/helpers';
+
+import { getTableLogSourceLabels } from '../src/debugger/utils';
+
+describe('generate labels for table log sources', () => {
+ const assertSameSources = (project, rootPatchPath, expectedLabelsForIds) => {
+ const ids = R.keys(expectedLabelsForIds);
+ const result = getTableLogSourceLabels(project, ids, rootPatchPath);
+ const expectedResult = R.compose(
+ R.map(
+ R.applySpec({
+ nodeId: R.nth(0),
+ label: R.nth(1),
+ })
+ ),
+ R.toPairs
+ )(expectedLabelsForIds);
+ assert.deepEqual(result, expectedResult);
+ };
+
+ it('should contain `DELETED (nodeId)` for a source that not exists in the patch', () => {
+ const project = defaultizeProject({
+ patches: {
+ '@/main': {},
+ 'xod/debug/table-log': {},
+ },
+ });
+
+ assertSameSources(project, '@/main', {
+ 'a~n~t': '@/main > DELETED (a~n~t)',
+ t: '@/main > DELETED (t)',
+ });
+ });
+ it('should contain node type base names if the node is unlabeled', () => {
+ const project = defaultizeProject({
+ patches: {
+ '@/main': {
+ nodes: {
+ a: {
+ type: '@/nested-1',
+ },
+ b: {
+ type: '@/nested-2',
+ },
+ t: {
+ type: 'xod/debug/table-log',
+ },
+ },
+ },
+ '@/nested-1': {
+ nodes: {
+ n: {
+ type: '@/nested-2',
+ },
+ },
+ },
+ '@/nested-2': {
+ nodes: {
+ t: {
+ type: 'xod/debug/table-log',
+ },
+ },
+ },
+ 'xod/debug/table-log': {},
+ },
+ });
+
+ assertSameSources(project, '@/main', {
+ 'a~n~t': '@/main > nested-1 > nested-2 > table-log',
+ 'b~t': '@/main > nested-2 > table-log',
+ t: '@/main > table-log',
+ });
+ });
+ it('should contain node labels if it is set', () => {
+ const project = defaultizeProject({
+ patches: {
+ '@/main': {
+ nodes: {
+ a: {
+ type: '@/nested-1',
+ label: 'Nested One',
+ },
+ b: {
+ type: '@/nested-2',
+ label: 'Nested Two',
+ },
+ c: {
+ type: '@/nested-label',
+ label: 'Nested Label',
+ },
+ t: {
+ type: 'xod/debug/table-log',
+ label: 'My Table Log',
+ },
+ },
+ },
+ '@/nested-1': {
+ nodes: {
+ n: {
+ type: '@/nested-2',
+ },
+ },
+ },
+ '@/nested-2': {
+ nodes: {
+ t: {
+ type: 'xod/debug/table-log',
+ },
+ },
+ },
+ '@/nested-label': {
+ nodes: {
+ n: {
+ type: '@/nested-2',
+ label: 'Log',
+ },
+ },
+ },
+ 'xod/debug/table-log': {},
+ },
+ });
+
+ assertSameSources(project, '@/main', {
+ 'a~n~t': '@/main > Nested One > nested-2 > table-log',
+ 'b~t': '@/main > Nested Two > table-log',
+ 'c~n~t': '@/main > Nested Label > Log > table-log',
+ t: '@/main > My Table Log',
+ });
+ });
+ it('should add numbers to the duplicates on a correct nesting level', () => {
+ const project = defaultizeProject({
+ patches: {
+ '@/main': {
+ nodes: {
+ a: {
+ type: '@/nested-1',
+ label: 'Nested',
+ },
+ a2: {
+ type: '@/nested-1',
+ label: 'Nested',
+ },
+ a3: {
+ type: '@/nested-1',
+ },
+ b: {
+ type: '@/nested-label',
+ },
+ b2: {
+ type: '@/nested-label',
+ },
+ d: {
+ type: '@/nested-double',
+ },
+ d2: {
+ type: '@/nested-double',
+ },
+ d3: {
+ type: '@/nested-double',
+ label: 'Double Inside',
+ },
+ nc: {
+ type: '@/nested-complex',
+ },
+ nc2: {
+ type: '@/nested-complex',
+ },
+ t: {
+ type: 'xod/debug/table-log',
+ label: 'My Table Log',
+ },
+ t2: {
+ type: 'xod/debug/table-log',
+ label: 'My Table Log',
+ },
+ t3: {
+ type: 'xod/debug/table-log',
+ label: 'My Table Log',
+ },
+ },
+ },
+ '@/nested-1': {
+ nodes: {
+ n: {
+ type: '@/nested-2',
+ },
+ },
+ },
+ '@/nested-2': {
+ nodes: {
+ t: {
+ type: 'xod/debug/table-log',
+ },
+ },
+ },
+ '@/nested-label': {
+ nodes: {
+ n: {
+ type: '@/nested-2',
+ label: 'Log',
+ },
+ },
+ },
+ '@/nested-double': {
+ nodes: {
+ t: { type: 'xod/debug/table-log' },
+ t2: { type: 'xod/debug/table-log' },
+ },
+ },
+ '@/nested-complex': {
+ nodes: {
+ n: { type: '@/nested-1' },
+ n2: { type: '@/nested-1' },
+ nn: { type: '@/nested-2' },
+ nn2: { type: '@/nested-2' },
+ nd: { type: '@/nested-double' },
+ nd2: { type: '@/nested-double' },
+ },
+ },
+ 'xod/debug/table-log': {},
+ },
+ });
+
+ assertSameSources(project, '@/main', {
+ 'a~n~t': '@/main > Nested > nested-2 > table-log',
+ 'a2~n~t': '@/main > Nested #2 > nested-2 > table-log',
+ 'a3~n~t': '@/main > nested-1 > nested-2 > table-log',
+ 'b~n~t': '@/main > nested-label > Log > table-log',
+ 'b2~n~t': '@/main > nested-label #2 > Log > table-log',
+ 'd~t': '@/main > nested-double > table-log',
+ 'd~t2': '@/main > nested-double > table-log #2',
+ 'd2~t': '@/main > nested-double #2 > table-log',
+ 'd2~t2': '@/main > nested-double #2 > table-log #2',
+ 'd3~t': '@/main > Double Inside > table-log',
+ 'd3~t2': '@/main > Double Inside > table-log #2',
+ 'nc~n~n~t': '@/main > nested-complex > nested-1 > nested-2 > table-log',
+ 'nc~n2~n~t':
+ '@/main > nested-complex > nested-1 #2 > nested-2 > table-log',
+ 'nc~nn~t': '@/main > nested-complex > nested-2 > table-log',
+ 'nc~nn2~t': '@/main > nested-complex > nested-2 #2 > table-log',
+ 'nc~nd~t': '@/main > nested-complex > nested-double > table-log',
+ 'nc~nd~t2': '@/main > nested-complex > nested-double > table-log #2',
+ 'nc~nd2~t': '@/main > nested-complex > nested-double #2 > table-log',
+ 'nc~nd2~t2': '@/main > nested-complex > nested-double #2 > table-log #2',
+ 'nc2~n~n~t':
+ '@/main > nested-complex #2 > nested-1 > nested-2 > table-log',
+ 'nc2~n2~n~t':
+ '@/main > nested-complex #2 > nested-1 #2 > nested-2 > table-log',
+ 'nc2~nn~t': '@/main > nested-complex #2 > nested-2 > table-log',
+ 'nc2~nn2~t': '@/main > nested-complex #2 > nested-2 #2 > table-log',
+ 'nc2~nd~t': '@/main > nested-complex #2 > nested-double > table-log',
+ 'nc2~nd~t2': '@/main > nested-complex #2 > nested-double > table-log #2',
+ 'nc2~nd2~t': '@/main > nested-complex #2 > nested-double #2 > table-log',
+ 'nc2~nd2~t2':
+ '@/main > nested-complex #2 > nested-double #2 > table-log #2',
+ t: '@/main > My Table Log',
+ t2: '@/main > My Table Log #2',
+ t3: '@/main > My Table Log #3',
+ });
+ });
+});