feat(xod-client): show user-friendly table log source labels

This commit is contained in:
Kirill Shumilov
2021-02-05 18:07:12 +03:00
parent 7029d1fc89
commit dacf10e60d
6 changed files with 412 additions and 11 deletions

View File

@@ -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),

View File

@@ -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)

View File

@@ -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]] ]
},

View File

@@ -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)
);

View File

@@ -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 ? <option>No logs</option> : null}
{this.props.sources.map(nodeId => (
{this.props.sources.map(({ nodeId, label }) => (
<option key={nodeId} value={nodeId}>
{nodeId}
{label}
</option>
))}
</select>
@@ -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,

View File

@@ -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',
});
});
});