Skip to content

Commit 4a341c3

Browse files
authored
feat: implement tests sorting (#621)
* feat: implement tests sorting * refactor: simplify sorting logic * fix: make toolbar select popups stay on top of tree view * fix: adaptive select redesign, group by message fixes, perf optimisation * fix: make custom sorting groups possible * fix: fix imports * refactor: introduce tree node weight metadata, move tags logic to a helper
1 parent 1277bef commit 4a341c3

File tree

30 files changed

+1014
-254
lines changed

30 files changed

+1014
-254
lines changed

lib/static/constants/sort-tests.ts

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
import {SortByExpression, SortType} from '@/static/new-ui/types/store';
2+
3+
export const SORT_BY_NAME: SortByExpression = {id: 'by-name', label: 'Name', type: SortType.ByName};
4+
export const SORT_BY_FAILED_RETRIES: SortByExpression = {id: 'by-failed-runs', label: 'Failed runs count', type: SortType.ByFailedRuns};
5+
export const SORT_BY_TESTS_COUNT: SortByExpression = {id: 'by-tests-count', label: 'Tests count', type: SortType.ByTestsCount};

lib/static/modules/action-names.ts

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -69,11 +69,14 @@ export default {
6969
SUITES_PAGE_SET_ALL_TREE_NODES: 'SUITES_PAGE_SET_ALL_TREE_NODES',
7070
SUITES_PAGE_REVEAL_TREE_NODE: 'SUITES_PAGE_REVEAL_TREE_NODE',
7171
SUITES_PAGE_SET_STEPS_EXPANDED: 'SUITES_PAGE_SET_STEPS_EXPANDED',
72+
SUITES_PAGE_SET_TREE_VIEW_MODE: 'SUITES_PAGE_SET_TREE_VIEW_MODE',
7273
VISUAL_CHECKS_PAGE_SET_CURRENT_NAMED_IMAGE: 'VISUAL_CHECKS_PAGE_SET_CURRENT_NAMED_IMAGE',
7374
UPDATE_LOADING_PROGRESS: 'UPDATE_LOADING_PROGRESS',
7475
UPDATE_LOADING_VISIBILITY: 'UPDATE_LOADING_VISIBILITY',
7576
UPDATE_LOADING_TITLE: 'UPDATE_LOADING_TITLE',
7677
UPDATE_LOADING_IS_IN_PROGRESS: 'UPDATE_LOADING_IS_IN_PROGRESS',
7778
SELECT_ALL: 'SELECT_ALL',
78-
DESELECT_ALL: 'DESELECT_ALL'
79+
DESELECT_ALL: 'DESELECT_ALL',
80+
SORT_TESTS_SET_CURRENT_EXPRESSION: 'SORT_TESTS_SET_CURRENT_EXPRESSION',
81+
SORT_TESTS_SET_DIRECTION: 'SORT_TESTS_SET_DIRECTION'
7982
} as const;
Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,19 @@
1+
import actionNames from '@/static/modules/action-names';
2+
import {Action} from '@/static/modules/actions/types';
3+
import {SortDirection} from '@/static/new-ui/types/store';
4+
5+
type SetCurrentSortByExpressionAction = Action<typeof actionNames.SORT_TESTS_SET_CURRENT_EXPRESSION, {
6+
expressionIds: string[];
7+
}>;
8+
export const setCurrentSortByExpression = (payload: SetCurrentSortByExpressionAction['payload']): SetCurrentSortByExpressionAction =>
9+
({type: actionNames.SORT_TESTS_SET_CURRENT_EXPRESSION, payload});
10+
11+
type SetSortByDirectionAction = Action<typeof actionNames.SORT_TESTS_SET_DIRECTION, {
12+
direction: SortDirection
13+
}>;
14+
export const setSortByDirection = (payload: SetSortByDirectionAction['payload']): SetSortByDirectionAction =>
15+
({type: actionNames.SORT_TESTS_SET_DIRECTION, payload});
16+
17+
export type SortTestsAction =
18+
| SetCurrentSortByExpressionAction
19+
| SetSortByDirectionAction;

lib/static/modules/actions/suites-page.ts

Lines changed: 9 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
import actionNames from '@/static/modules/action-names';
22
import {Action} from '@/static/modules/actions/types';
3+
import {TreeViewMode} from '@/static/new-ui/types/store';
34

45
export type SuitesPageSetCurrentTreeNodeAction = Action<typeof actionNames.SUITES_PAGE_SET_CURRENT_SUITE, Partial<{
56
treeNodeId: string;
@@ -43,10 +44,17 @@ type SetStepsExpandedStateAction = Action<typeof actionNames.SUITES_PAGE_SET_STE
4344
export const setStepsExpandedState = (payload: SetStepsExpandedStateAction['payload']): SetStepsExpandedStateAction =>
4445
({type: actionNames.SUITES_PAGE_SET_STEPS_EXPANDED, payload});
4546

47+
type SetTreeViewModeAction = Action<typeof actionNames.SUITES_PAGE_SET_TREE_VIEW_MODE, {
48+
treeViewMode: TreeViewMode;
49+
}>;
50+
export const setTreeViewMode = (payload: SetTreeViewModeAction['payload']): SetTreeViewModeAction =>
51+
({type: actionNames.SUITES_PAGE_SET_TREE_VIEW_MODE, payload});
52+
4653
export type SuitesPageAction =
4754
| SetTreeNodeExpandedStateAction
4855
| SetAllTreeNodesStateAction
4956
| SuitesPageSetCurrentTreeNodeAction
5057
| SetSectionExpandedStateAction
5158
| SetStepsExpandedStateAction
52-
| RevealTreeNodeAction;
59+
| RevealTreeNodeAction
60+
| SetTreeViewModeAction;

lib/static/modules/actions/types.ts

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,7 @@ import {ThunkAction} from 'redux-thunk';
77
import {State} from '@/static/new-ui/types/store';
88
import {LifecycleAction} from '@/static/modules/actions/lifecycle';
99
import {SuitesPageAction} from '@/static/modules/actions/suites-page';
10+
import {SortTestsAction} from '@/static/modules/actions/sort-tests';
1011

1112
export type {Dispatch} from 'redux';
1213

@@ -22,4 +23,5 @@ export type AppThunk<ReturnType = Promise<void>> = ThunkAction<ReturnType, State
2223
export type SomeAction =
2324
| GroupTestsAction
2425
| LifecycleAction
25-
| SuitesPageAction;
26+
| SuitesPageAction
27+
| SortTestsAction;

lib/static/modules/default-state.ts

Lines changed: 7 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,7 @@ import {DiffModes} from '../../constants/diff-modes';
44
import {EXPAND_ERRORS} from '../../constants/expand-modes';
55
import {RESULT_KEYS} from '../../constants/group-tests';
66
import {ToolName} from '../../constants';
7-
import {State} from '@/static/new-ui/types/store';
7+
import {SortDirection, State, TreeViewMode} from '@/static/new-ui/types/store';
88

99
export default Object.assign({config: configDefaults}, {
1010
gui: true,
@@ -121,10 +121,16 @@ export default Object.assign({config: configDefaults}, {
121121
availableSections: [],
122122
availableExpressions: [],
123123
currentExpressionIds: []
124+
},
125+
sortTestsData: {
126+
availableExpressions: [],
127+
currentExpressionIds: [],
128+
currentDirection: SortDirection.Asc
124129
}
125130
},
126131
ui: {
127132
suitesPage: {
133+
treeViewMode: TreeViewMode.Tree,
128134
retryIndexByTreeNodeId: {},
129135
expandedSectionsById: {},
130136
expandedStepsByResultId: {},

lib/static/modules/reducers/index.js

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -30,6 +30,7 @@ import suitesPage from './suites-page';
3030
import visualChecksPage from './visual-checks-page';
3131
import isInitialized from './is-initialized';
3232
import newUiGroupedTests from './new-ui-grouped-tests';
33+
import sortTests from './sort-tests';
3334

3435
// The order of specifying reducers is important.
3536
// At the top specify reducers that does not depend on other state fields.
@@ -60,6 +61,7 @@ export default reduceReducers(
6061
tree,
6162
groupedTests,
6263
newUiGroupedTests,
64+
sortTests,
6365
plugins,
6466
progressBar,
6567
suitesPage,

lib/static/modules/reducers/new-ui-grouped-tests/index.ts

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -16,10 +16,10 @@ export default (state: State, action: SomeAction): State => {
1616
case actionNames.INIT_STATIC_REPORT: {
1717
const availableSections: GroupBySection[] = [{
1818
id: 'meta',
19-
label: 'meta'
19+
label: 'Meta'
2020
}, {
2121
id: 'error',
22-
label: 'error'
22+
label: 'Error'
2323
}];
2424

2525
const availableExpressions: GroupByExpression[] = [];

lib/static/modules/reducers/new-ui-grouped-tests/utils.ts

Lines changed: 35 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,7 @@ import {isAssertViewError} from '@/common-utils';
1212
import stripAnsi from 'strip-ansi';
1313
import {IMAGE_COMPARISON_FAILED_MESSAGE, TestStatus} from '@/constants';
1414
import {stringify} from '@/static/new-ui/utils';
15+
import {EntityType} from '@/static/new-ui/features/suites/components/SuitesPage/types';
1516

1617
const extractErrors = (result: ResultEntity, images: ImageEntity[]): string[] => {
1718
const errors = new Set<string>();
@@ -48,7 +49,8 @@ const extractErrors = (result: ResultEntity, images: ImageEntity[]): string[] =>
4849
const groupTestsByMeta = (expr: GroupByMetaExpression, resultsById: Record<string, ResultEntity>): Record<string, GroupEntity> => {
4950
const DEFAULT_GROUP = `__${GroupByType.Meta}__DEFAULT_GROUP`;
5051
const results = Object.values(resultsById);
51-
const groups: Record<string | symbol, GroupEntity> = {};
52+
const groupsById: Record<string | symbol, GroupEntity> = {};
53+
const groupingKeyToId: Record<string, string> = {};
5254
let id = 1;
5355

5456
for (const result of results) {
@@ -59,28 +61,35 @@ const groupTestsByMeta = (expr: GroupByMetaExpression, resultsById: Record<strin
5961
groupingKey = `${GroupByType.Meta}__${expr.key}__${stringify(result.metaInfo[expr.key])}`;
6062
}
6163

62-
if (!groups[groupingKey]) {
63-
groups[groupingKey] = {
64-
id: id.toString(),
64+
if (!groupingKeyToId[groupingKey]) {
65+
groupingKeyToId[groupingKey] = id.toString();
66+
id++;
67+
}
68+
69+
const groupId = groupingKeyToId[groupingKey];
70+
if (!groupsById[groupId]) {
71+
groupsById[groupId] = {
72+
id: groupId,
6573
key: expr.key,
6674
label: stringify(result.metaInfo[expr.key]),
6775
resultIds: [],
68-
browserIds: []
76+
browserIds: [],
77+
type: EntityType.Group
6978
};
70-
id++;
7179
}
7280

73-
groups[groupingKey].resultIds.push(result.id);
74-
if (!groups[groupingKey].browserIds.includes(result.parentId)) {
75-
groups[groupingKey].browserIds.push(result.parentId);
81+
groupsById[groupId].resultIds.push(result.id);
82+
if (!groupsById[groupId].browserIds.includes(result.parentId)) {
83+
groupsById[groupId].browserIds.push(result.parentId);
7684
}
7785
}
7886

79-
return groups;
87+
return groupsById;
8088
};
8189

8290
const groupTestsByError = (resultsById: Record<string, ResultEntity>, imagesById: Record<string, ImageEntity>, errorPatterns: State['config']['errorPatterns']): Record<string, GroupEntity> => {
83-
const groups: Record<string | symbol, GroupEntity> = {};
91+
const groupsById: Record<string | symbol, GroupEntity> = {};
92+
const groupingKeyToId: Record<string, string> = {};
8493
const results = Object.values(resultsById);
8594
let id = 1;
8695

@@ -101,25 +110,31 @@ const groupTestsByError = (resultsById: Record<string, ResultEntity>, imagesById
101110
groupingKey = `${GroupByType.Error}__${errorText}`;
102111
}
103112

104-
if (!groups[groupingKey]) {
105-
groups[groupingKey] = {
106-
id: id.toString(),
113+
if (!groupingKeyToId[groupingKey]) {
114+
groupingKeyToId[groupingKey] = id.toString();
115+
id++;
116+
}
117+
118+
const groupId = groupingKeyToId[groupingKey];
119+
if (!groupsById[groupId]) {
120+
groupsById[groupId] = {
121+
id: groupId,
107122
key: 'error',
108123
label: stripAnsi(groupLabel),
109124
resultIds: [],
110-
browserIds: []
125+
browserIds: [],
126+
type: EntityType.Group
111127
};
112-
id++;
113128
}
114129

115-
groups[groupingKey].resultIds.push(result.id);
116-
if (!groups[groupingKey].browserIds.includes(result.parentId)) {
117-
groups[groupingKey].browserIds.push(result.parentId);
130+
groupsById[groupId].resultIds.push(result.id);
131+
if (!groupsById[groupId].browserIds.includes(result.parentId)) {
132+
groupsById[groupId].browserIds.push(result.parentId);
118133
}
119134
}
120135
}
121136

122-
return groups;
137+
return groupsById;
123138
};
124139

125140
export const groupTests = (groupByExpressions: GroupByExpression[], resultsById: Record<string, ResultEntity>, imagesById: Record<string, ImageEntity>, errorPatterns: State['config']['errorPatterns']): Record<string, GroupEntity> => {
Lines changed: 71 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,71 @@
1+
import {SortByExpression, SortDirection, State} from '@/static/new-ui/types/store';
2+
import {SomeAction} from '@/static/modules/actions/types';
3+
import actionNames from '@/static/modules/action-names';
4+
import {applyStateUpdate} from '@/static/modules/utils';
5+
import {SORT_BY_FAILED_RETRIES, SORT_BY_NAME, SORT_BY_TESTS_COUNT} from '@/static/constants/sort-tests';
6+
7+
export default (state: State, action: SomeAction): State => {
8+
switch (action.type) {
9+
case actionNames.INIT_STATIC_REPORT:
10+
case actionNames.INIT_GUI_REPORT: {
11+
const availableExpressions: SortByExpression[] = [
12+
SORT_BY_NAME,
13+
SORT_BY_FAILED_RETRIES
14+
];
15+
16+
return applyStateUpdate(state, {
17+
app: {
18+
sortTestsData: {
19+
availableExpressions,
20+
currentDirection: SortDirection.Asc,
21+
currentExpressionIds: [availableExpressions[0].id]
22+
}
23+
}
24+
});
25+
}
26+
case actionNames.SORT_TESTS_SET_CURRENT_EXPRESSION: {
27+
return applyStateUpdate(state, {
28+
app: {
29+
sortTestsData: {
30+
currentExpressionIds: action.payload.expressionIds
31+
}
32+
}
33+
});
34+
}
35+
case actionNames.SORT_TESTS_SET_DIRECTION: {
36+
return applyStateUpdate(state, {
37+
app: {
38+
sortTestsData: {
39+
currentDirection: action.payload.direction
40+
}
41+
}
42+
});
43+
}
44+
case actionNames.GROUP_TESTS_SET_CURRENT_EXPRESSION: {
45+
let availableExpressions: SortByExpression[];
46+
47+
if (action.payload.expressionIds.length > 0) {
48+
availableExpressions = [
49+
SORT_BY_NAME,
50+
SORT_BY_FAILED_RETRIES,
51+
SORT_BY_TESTS_COUNT
52+
];
53+
} else {
54+
availableExpressions = [
55+
SORT_BY_NAME,
56+
SORT_BY_FAILED_RETRIES
57+
];
58+
}
59+
60+
return applyStateUpdate(state, {
61+
app: {
62+
sortTestsData: {
63+
availableExpressions
64+
}
65+
}
66+
});
67+
}
68+
default:
69+
return state;
70+
}
71+
};

0 commit comments

Comments
 (0)