Skip to content

Commit 0d76a12

Browse files
authored
Changes reorder column events to use source and destination (#6733)
## Motivation for features / changes To prepare for showing shared hparam columns across runs and scalar tables, we need to unify the column re-ordering logic to use the same source/destination parameters (introduced in #6727). ## Technical description of changes - Changes the DataTable component (shared by runs and scalar tables) to use the new ReorderColumnEvent type when column order is changed. The changes propagate to scalar card scalar column editor containers. Most of the changes are getting the tests to pass with the new signature. - Changes the metrics reducers to correctly handle these new events. Logic is mostly unchanged, except that sort order will now be preserved even after column toggle (previously, toggling would group all disabled columns together, which destroys relative column order misc: - creates moveColumn utility to abstract shared logic between the hparams and metrics reducers. Removes redundant tests in hparams reducer ## Detailed steps to verify changes work correctly (as executed by you) - Unit tests pass - Manually tested sorting behavior
1 parent 304046d commit 0d76a12

27 files changed

+528
-794
lines changed

tensorboard/webapp/hparams/_redux/BUILD

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -67,6 +67,7 @@ tf_ts_library(
6767
"//tensorboard/webapp/hparams:_types",
6868
"//tensorboard/webapp/runs/actions",
6969
"//tensorboard/webapp/widgets/data_table:types",
70+
"//tensorboard/webapp/widgets/data_table:utils",
7071
"@npm//@ngrx/store",
7172
],
7273
)
@@ -174,6 +175,7 @@ tf_ts_library(
174175
"//tensorboard/webapp/util:types",
175176
"//tensorboard/webapp/webapp_data_source:http_client_testing",
176177
"//tensorboard/webapp/widgets/data_table:types",
178+
"//tensorboard/webapp/widgets/data_table:utils",
177179
"@npm//@ngrx/effects",
178180
"@npm//@ngrx/store",
179181
"@npm//@types/jasmine",

tensorboard/webapp/hparams/_redux/hparams_reducers.ts

Lines changed: 7 additions & 22 deletions
Original file line numberDiff line numberDiff line change
@@ -13,7 +13,8 @@ See the License for the specific language governing permissions and
1313
limitations under the License.
1414
==============================================================================*/
1515
import {Action, ActionReducer, createReducer, on} from '@ngrx/store';
16-
import {ColumnHeader, Side} from '../../widgets/data_table/types';
16+
import {Side} from '../../widgets/data_table/types';
17+
import {DataTableUtils} from '../../widgets/data_table/utils';
1718
import * as actions from './hparams_actions';
1819
import {HparamsState} from './types';
1920

@@ -141,28 +142,12 @@ const reducer: ActionReducer<HparamsState, Action> = createReducer(
141142
actions.dashboardHparamColumnOrderChanged,
142143
(state, {source, destination, side}) => {
143144
const {dashboardDisplayedHparamColumns: columns} = state;
144-
const sourceIndex = columns.findIndex(
145-
(column: ColumnHeader) => column.name === source.name
145+
const newColumns = DataTableUtils.moveColumn(
146+
columns,
147+
source,
148+
destination,
149+
side
146150
);
147-
let destinationIndex = columns.findIndex(
148-
(column: ColumnHeader) => column.name === destination.name
149-
);
150-
if (sourceIndex === -1 || sourceIndex === destinationIndex) {
151-
return state;
152-
}
153-
if (destinationIndex === -1) {
154-
// Use side as a backup to determine source position if destination isn't found.
155-
if (side !== undefined) {
156-
destinationIndex = side === Side.LEFT ? 0 : columns.length - 1;
157-
} else {
158-
return state;
159-
}
160-
}
161-
162-
const newColumns = [...columns];
163-
newColumns.splice(sourceIndex, 1);
164-
newColumns.splice(destinationIndex, 0, source);
165-
166151
return {
167152
...state,
168153
dashboardDisplayedHparamColumns: newColumns,

tensorboard/webapp/hparams/_redux/hparams_reducers_test.ts

Lines changed: 13 additions & 94 deletions
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,7 @@ import * as actions from './hparams_actions';
1818
import {reducers} from './hparams_reducers';
1919
import {buildHparamSpec, buildHparamsState, buildMetricSpec} from './testing';
2020
import {ColumnHeaderType, Side} from '../../widgets/data_table/types';
21+
import {DataTableUtils} from '../../widgets/data_table/utils';
2122

2223
describe('hparams/_redux/hparams_reducers_test', () => {
2324
describe('hparamsFetchSessionGroupsSucceeded', () => {
@@ -594,104 +595,15 @@ describe('hparams/_redux/hparams_reducers_test', () => {
594595
},
595596
];
596597

597-
it('does nothing if source is not found', () => {
598+
it('moves source to destination using moveColumn', () => {
598599
const state = buildHparamsState({
599600
dashboardDisplayedHparamColumns: fakeColumns,
600601
});
601-
const state2 = reducers(
602-
state,
603-
actions.dashboardHparamColumnOrderChanged({
604-
source: {
605-
type: ColumnHeaderType.HPARAM,
606-
name: 'nonexistent_param',
607-
displayName: 'Nonexistent param',
608-
enabled: false,
609-
},
610-
destination: {
611-
type: ColumnHeaderType.HPARAM,
612-
name: 'conv_kernel_size',
613-
displayName: 'Conv Kernel Size',
614-
enabled: true,
615-
},
616-
side: Side.LEFT,
617-
})
618-
);
619-
620-
expect(state2.dashboardDisplayedHparamColumns).toEqual(fakeColumns);
621-
});
622-
623-
it('does nothing if source equals dest', () => {
624-
const state = buildHparamsState({
625-
dashboardDisplayedHparamColumns: fakeColumns,
626-
});
627-
const state2 = reducers(
628-
state,
629-
actions.dashboardHparamColumnOrderChanged({
630-
source: {
631-
type: ColumnHeaderType.HPARAM,
632-
name: 'conv_kernel_size',
633-
displayName: 'Conv Kernel Size',
634-
enabled: false,
635-
},
636-
destination: {
637-
type: ColumnHeaderType.HPARAM,
638-
name: 'conv_kernel_size',
639-
displayName: 'Conv Kernel Size',
640-
enabled: true,
641-
},
642-
side: Side.LEFT,
643-
})
644-
);
645-
646-
expect(state2.dashboardDisplayedHparamColumns).toEqual(fakeColumns);
647-
});
602+
const moveColumnSpy = spyOn(
603+
DataTableUtils,
604+
'moveColumn'
605+
).and.callThrough();
648606

649-
[
650-
{
651-
testDesc: 'to front if side is left',
652-
side: Side.LEFT,
653-
expectedResult: [
654-
fakeColumns[1],
655-
fakeColumns[0],
656-
...fakeColumns.slice(2),
657-
],
658-
},
659-
{
660-
testDesc: 'to back if side is right',
661-
side: Side.RIGHT,
662-
expectedResult: [
663-
fakeColumns[0],
664-
...fakeColumns.slice(2),
665-
fakeColumns[1],
666-
],
667-
},
668-
].forEach(({testDesc, side, expectedResult}) => {
669-
it(`if destination not found, moves source ${testDesc}`, () => {
670-
const state = buildHparamsState({
671-
dashboardDisplayedHparamColumns: fakeColumns,
672-
});
673-
const state2 = reducers(
674-
state,
675-
actions.dashboardHparamColumnOrderChanged({
676-
source: fakeColumns[1],
677-
destination: {
678-
type: ColumnHeaderType.HPARAM,
679-
name: 'nonexistent param',
680-
displayName: 'Nonexistent param',
681-
enabled: true,
682-
},
683-
side,
684-
})
685-
);
686-
687-
expect(state2.dashboardDisplayedHparamColumns).toEqual(expectedResult);
688-
});
689-
});
690-
691-
it('swaps source and destination positions if destination is found', () => {
692-
const state = buildHparamsState({
693-
dashboardDisplayedHparamColumns: fakeColumns,
694-
});
695607
const state2 = reducers(
696608
state,
697609
actions.dashboardHparamColumnOrderChanged({
@@ -701,6 +613,13 @@ describe('hparams/_redux/hparams_reducers_test', () => {
701613
})
702614
);
703615

616+
// Edge cases are tested by moveColumn tests.
617+
expect(moveColumnSpy).toHaveBeenCalledWith(
618+
fakeColumns,
619+
fakeColumns[1],
620+
fakeColumns[0],
621+
Side.LEFT
622+
);
704623
expect(state2.dashboardDisplayedHparamColumns).toEqual([
705624
fakeColumns[1],
706625
fakeColumns[0],

tensorboard/webapp/metrics/actions/index.ts

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -234,8 +234,8 @@ export const sortingDataTable = createAction(
234234
props<SortingInfo>()
235235
);
236236

237-
export const dataTableColumnEdited = createAction(
238-
'[Metrics] Data table columns edited in edit menu',
237+
export const dataTableColumnOrderChanged = createAction(
238+
'[Metrics] Data table columns order changed',
239239
props<HeaderEditInfo>()
240240
);
241241

tensorboard/webapp/metrics/internal_types.ts

Lines changed: 3 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -16,8 +16,8 @@ import {TimeSelection} from '../widgets/card_fob/card_fob_types';
1616
import {HistogramMode} from '../widgets/histogram/histogram_types';
1717
import {
1818
ColumnHeader,
19-
ColumnHeaderType,
2019
DataTableMode,
20+
ReorderColumnEvent,
2121
} from '../widgets/data_table/types';
2222

2323
export {HistogramMode, TimeSelection};
@@ -94,15 +94,14 @@ export interface URLDeserializedState {
9494
};
9595
}
9696

97-
export interface HeaderEditInfo {
97+
export interface HeaderEditInfo extends ReorderColumnEvent {
9898
dataTableMode: DataTableMode;
99-
headers: ColumnHeader[];
10099
}
101100

102101
export interface HeaderToggleInfo {
103102
header: ColumnHeader;
104103
cardId?: CardId;
105-
dataTableMode?: DataTableMode;
104+
dataTableMode?: DataTableMode | undefined;
106105
}
107106

108107
export const SCALARS_SMOOTHING_MIN = 0;

tensorboard/webapp/metrics/store/metrics_reducers.ts

Lines changed: 26 additions & 76 deletions
Original file line numberDiff line numberDiff line change
@@ -46,11 +46,7 @@ import {
4646
URLDeserializedState,
4747
} from '../types';
4848
import {groupCardIdWithMetdata} from '../utils';
49-
import {
50-
ColumnHeader,
51-
ColumnHeaderType,
52-
DataTableMode,
53-
} from '../../widgets/data_table/types';
49+
import {ColumnHeaderType, DataTableMode} from '../../widgets/data_table/types';
5450
import {
5551
buildOrReturnStateWithPinnedCopy,
5652
buildOrReturnStateWithUnresolvedImportedPins,
@@ -82,6 +78,7 @@ import {
8278
TimeSeriesData,
8379
TimeSeriesLoadable,
8480
} from './metrics_types';
81+
import {DataTableUtils} from '../../widgets/data_table/utils';
8582

8683
function buildCardMetadataList(tagMetadata: TagMetadata): CardMetadata[] {
8784
const results: CardMetadata[] = [];
@@ -1434,34 +1431,30 @@ const reducer = createReducer(
14341431
tableEditorSelectedTab: tab,
14351432
};
14361433
}),
1437-
on(actions.dataTableColumnEdited, (state, {dataTableMode, headers}) => {
1438-
const enabledNewHeaders: ColumnHeader[] = [];
1439-
const disabledNewHeaders: ColumnHeader[] = [];
1440-
1441-
// All enabled headers appear above all disabled headers.
1442-
headers.forEach((header) => {
1443-
if (header.enabled) {
1444-
enabledNewHeaders.push(header);
1445-
} else {
1446-
disabledNewHeaders.push(header);
1434+
on(
1435+
actions.dataTableColumnOrderChanged,
1436+
(state, {source, destination, side, dataTableMode}) => {
1437+
let headers =
1438+
dataTableMode === DataTableMode.RANGE
1439+
? [...state.rangeSelectionHeaders]
1440+
: [...state.singleSelectionHeaders];
1441+
headers = DataTableUtils.moveColumn(headers, source, destination, side);
1442+
1443+
if (dataTableMode === DataTableMode.RANGE) {
1444+
return {
1445+
...state,
1446+
rangeSelectionHeaders: headers,
1447+
};
14471448
}
1448-
});
1449-
1450-
if (dataTableMode === DataTableMode.RANGE) {
14511449
return {
14521450
...state,
1453-
rangeSelectionHeaders: enabledNewHeaders.concat(disabledNewHeaders),
1451+
singleSelectionHeaders: headers,
14541452
};
14551453
}
1456-
1457-
return {
1458-
...state,
1459-
singleSelectionHeaders: enabledNewHeaders.concat(disabledNewHeaders),
1460-
};
1461-
}),
1454+
),
14621455
on(
14631456
actions.dataTableColumnToggled,
1464-
(state, {dataTableMode, header, cardId}) => {
1457+
(state, {dataTableMode, header: toggledHeader, cardId}) => {
14651458
const {cardStateMap, rangeSelectionEnabled, linkedTimeEnabled} = state;
14661459
const rangeEnabled = cardId
14671460
? cardRangeSelectionEnabled(
@@ -1471,40 +1464,24 @@ const reducer = createReducer(
14711464
cardId
14721465
)
14731466
: dataTableMode === DataTableMode.RANGE;
1474-
14751467
const targetedHeaders = rangeEnabled
14761468
? state.rangeSelectionHeaders
14771469
: state.singleSelectionHeaders;
14781470

1479-
const currentToggledHeaderIndex = targetedHeaders.findIndex(
1480-
(element) => element.name === header.name
1481-
);
1482-
1483-
// If the header is being enabled it goes at the bottom of the currently
1484-
// enabled headers. If it is being disabled it goes to the top of the
1485-
// currently disabled headers.
1486-
let newToggledHeaderIndex = getEnabledCount(targetedHeaders);
1487-
if (targetedHeaders[currentToggledHeaderIndex].enabled) {
1488-
newToggledHeaderIndex--;
1489-
}
1490-
const newHeaders = moveHeader(
1491-
currentToggledHeaderIndex,
1492-
newToggledHeaderIndex,
1493-
targetedHeaders
1494-
);
1495-
1496-
newHeaders[newToggledHeaderIndex] = {
1497-
...newHeaders[newToggledHeaderIndex],
1498-
enabled: !newHeaders[newToggledHeaderIndex].enabled,
1499-
};
1471+
const newHeaders = targetedHeaders.map((header) => {
1472+
const newHeader = {...header};
1473+
if (header.name === toggledHeader.name) {
1474+
newHeader.enabled = !newHeader.enabled;
1475+
}
1476+
return newHeader;
1477+
});
15001478

15011479
if (rangeEnabled) {
15021480
return {
15031481
...state,
15041482
rangeSelectionHeaders: newHeaders,
15051483
};
15061484
}
1507-
15081485
return {
15091486
...state,
15101487
singleSelectionHeaders: newHeaders,
@@ -1583,30 +1560,3 @@ function buildTagToRuns(runTagInfo: {[run: string]: string[]}) {
15831560
}
15841561
return tagToRuns;
15851562
}
1586-
1587-
/**
1588-
* Returns a copy of the headers array with item at sourceIndex moved to
1589-
* destinationIndex.
1590-
*/
1591-
function moveHeader(
1592-
sourceIndex: number,
1593-
destinationIndex: number,
1594-
headers: ColumnHeader[]
1595-
) {
1596-
const newHeaders = [...headers];
1597-
// Delete from original location
1598-
newHeaders.splice(sourceIndex, 1);
1599-
// Insert at destinationIndex.
1600-
newHeaders.splice(destinationIndex, 0, headers[sourceIndex]);
1601-
return newHeaders;
1602-
}
1603-
1604-
function getEnabledCount(headers: ColumnHeader[]) {
1605-
let count = 0;
1606-
headers.forEach((header) => {
1607-
if (header.enabled) {
1608-
count++;
1609-
}
1610-
});
1611-
return count;
1612-
}

0 commit comments

Comments
 (0)