Skip to content

Commit

Permalink
feat: implement tests sorting (#621)
Browse files Browse the repository at this point in the history
* 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
  • Loading branch information
shadowusr authored Dec 8, 2024
1 parent 1277bef commit 4a341c3
Show file tree
Hide file tree
Showing 30 changed files with 1,014 additions and 254 deletions.
5 changes: 5 additions & 0 deletions lib/static/constants/sort-tests.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
import {SortByExpression, SortType} from '@/static/new-ui/types/store';

export const SORT_BY_NAME: SortByExpression = {id: 'by-name', label: 'Name', type: SortType.ByName};
export const SORT_BY_FAILED_RETRIES: SortByExpression = {id: 'by-failed-runs', label: 'Failed runs count', type: SortType.ByFailedRuns};
export const SORT_BY_TESTS_COUNT: SortByExpression = {id: 'by-tests-count', label: 'Tests count', type: SortType.ByTestsCount};
5 changes: 4 additions & 1 deletion lib/static/modules/action-names.ts
Original file line number Diff line number Diff line change
Expand Up @@ -69,11 +69,14 @@ export default {
SUITES_PAGE_SET_ALL_TREE_NODES: 'SUITES_PAGE_SET_ALL_TREE_NODES',
SUITES_PAGE_REVEAL_TREE_NODE: 'SUITES_PAGE_REVEAL_TREE_NODE',
SUITES_PAGE_SET_STEPS_EXPANDED: 'SUITES_PAGE_SET_STEPS_EXPANDED',
SUITES_PAGE_SET_TREE_VIEW_MODE: 'SUITES_PAGE_SET_TREE_VIEW_MODE',
VISUAL_CHECKS_PAGE_SET_CURRENT_NAMED_IMAGE: 'VISUAL_CHECKS_PAGE_SET_CURRENT_NAMED_IMAGE',
UPDATE_LOADING_PROGRESS: 'UPDATE_LOADING_PROGRESS',
UPDATE_LOADING_VISIBILITY: 'UPDATE_LOADING_VISIBILITY',
UPDATE_LOADING_TITLE: 'UPDATE_LOADING_TITLE',
UPDATE_LOADING_IS_IN_PROGRESS: 'UPDATE_LOADING_IS_IN_PROGRESS',
SELECT_ALL: 'SELECT_ALL',
DESELECT_ALL: 'DESELECT_ALL'
DESELECT_ALL: 'DESELECT_ALL',
SORT_TESTS_SET_CURRENT_EXPRESSION: 'SORT_TESTS_SET_CURRENT_EXPRESSION',
SORT_TESTS_SET_DIRECTION: 'SORT_TESTS_SET_DIRECTION'
} as const;
19 changes: 19 additions & 0 deletions lib/static/modules/actions/sort-tests.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
import actionNames from '@/static/modules/action-names';
import {Action} from '@/static/modules/actions/types';
import {SortDirection} from '@/static/new-ui/types/store';

type SetCurrentSortByExpressionAction = Action<typeof actionNames.SORT_TESTS_SET_CURRENT_EXPRESSION, {
expressionIds: string[];
}>;
export const setCurrentSortByExpression = (payload: SetCurrentSortByExpressionAction['payload']): SetCurrentSortByExpressionAction =>
({type: actionNames.SORT_TESTS_SET_CURRENT_EXPRESSION, payload});

type SetSortByDirectionAction = Action<typeof actionNames.SORT_TESTS_SET_DIRECTION, {
direction: SortDirection
}>;
export const setSortByDirection = (payload: SetSortByDirectionAction['payload']): SetSortByDirectionAction =>
({type: actionNames.SORT_TESTS_SET_DIRECTION, payload});

export type SortTestsAction =
| SetCurrentSortByExpressionAction
| SetSortByDirectionAction;
10 changes: 9 additions & 1 deletion lib/static/modules/actions/suites-page.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
import actionNames from '@/static/modules/action-names';
import {Action} from '@/static/modules/actions/types';
import {TreeViewMode} from '@/static/new-ui/types/store';

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

type SetTreeViewModeAction = Action<typeof actionNames.SUITES_PAGE_SET_TREE_VIEW_MODE, {
treeViewMode: TreeViewMode;
}>;
export const setTreeViewMode = (payload: SetTreeViewModeAction['payload']): SetTreeViewModeAction =>
({type: actionNames.SUITES_PAGE_SET_TREE_VIEW_MODE, payload});

export type SuitesPageAction =
| SetTreeNodeExpandedStateAction
| SetAllTreeNodesStateAction
| SuitesPageSetCurrentTreeNodeAction
| SetSectionExpandedStateAction
| SetStepsExpandedStateAction
| RevealTreeNodeAction;
| RevealTreeNodeAction
| SetTreeViewModeAction;
4 changes: 3 additions & 1 deletion lib/static/modules/actions/types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@ import {ThunkAction} from 'redux-thunk';
import {State} from '@/static/new-ui/types/store';
import {LifecycleAction} from '@/static/modules/actions/lifecycle';
import {SuitesPageAction} from '@/static/modules/actions/suites-page';
import {SortTestsAction} from '@/static/modules/actions/sort-tests';

export type {Dispatch} from 'redux';

Expand All @@ -22,4 +23,5 @@ export type AppThunk<ReturnType = Promise<void>> = ThunkAction<ReturnType, State
export type SomeAction =
| GroupTestsAction
| LifecycleAction
| SuitesPageAction;
| SuitesPageAction
| SortTestsAction;
8 changes: 7 additions & 1 deletion lib/static/modules/default-state.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@ import {DiffModes} from '../../constants/diff-modes';
import {EXPAND_ERRORS} from '../../constants/expand-modes';
import {RESULT_KEYS} from '../../constants/group-tests';
import {ToolName} from '../../constants';
import {State} from '@/static/new-ui/types/store';
import {SortDirection, State, TreeViewMode} from '@/static/new-ui/types/store';

export default Object.assign({config: configDefaults}, {
gui: true,
Expand Down Expand Up @@ -121,10 +121,16 @@ export default Object.assign({config: configDefaults}, {
availableSections: [],
availableExpressions: [],
currentExpressionIds: []
},
sortTestsData: {
availableExpressions: [],
currentExpressionIds: [],
currentDirection: SortDirection.Asc
}
},
ui: {
suitesPage: {
treeViewMode: TreeViewMode.Tree,
retryIndexByTreeNodeId: {},
expandedSectionsById: {},
expandedStepsByResultId: {},
Expand Down
2 changes: 2 additions & 0 deletions lib/static/modules/reducers/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,7 @@ import suitesPage from './suites-page';
import visualChecksPage from './visual-checks-page';
import isInitialized from './is-initialized';
import newUiGroupedTests from './new-ui-grouped-tests';
import sortTests from './sort-tests';

// The order of specifying reducers is important.
// At the top specify reducers that does not depend on other state fields.
Expand Down Expand Up @@ -60,6 +61,7 @@ export default reduceReducers(
tree,
groupedTests,
newUiGroupedTests,
sortTests,
plugins,
progressBar,
suitesPage,
Expand Down
4 changes: 2 additions & 2 deletions lib/static/modules/reducers/new-ui-grouped-tests/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -16,10 +16,10 @@ export default (state: State, action: SomeAction): State => {
case actionNames.INIT_STATIC_REPORT: {
const availableSections: GroupBySection[] = [{
id: 'meta',
label: 'meta'
label: 'Meta'
}, {
id: 'error',
label: 'error'
label: 'Error'
}];

const availableExpressions: GroupByExpression[] = [];
Expand Down
55 changes: 35 additions & 20 deletions lib/static/modules/reducers/new-ui-grouped-tests/utils.ts
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@ import {isAssertViewError} from '@/common-utils';
import stripAnsi from 'strip-ansi';
import {IMAGE_COMPARISON_FAILED_MESSAGE, TestStatus} from '@/constants';
import {stringify} from '@/static/new-ui/utils';
import {EntityType} from '@/static/new-ui/features/suites/components/SuitesPage/types';

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

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

if (!groups[groupingKey]) {
groups[groupingKey] = {
id: id.toString(),
if (!groupingKeyToId[groupingKey]) {
groupingKeyToId[groupingKey] = id.toString();
id++;
}

const groupId = groupingKeyToId[groupingKey];
if (!groupsById[groupId]) {
groupsById[groupId] = {
id: groupId,
key: expr.key,
label: stringify(result.metaInfo[expr.key]),
resultIds: [],
browserIds: []
browserIds: [],
type: EntityType.Group
};
id++;
}

groups[groupingKey].resultIds.push(result.id);
if (!groups[groupingKey].browserIds.includes(result.parentId)) {
groups[groupingKey].browserIds.push(result.parentId);
groupsById[groupId].resultIds.push(result.id);
if (!groupsById[groupId].browserIds.includes(result.parentId)) {
groupsById[groupId].browserIds.push(result.parentId);
}
}

return groups;
return groupsById;
};

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

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

if (!groups[groupingKey]) {
groups[groupingKey] = {
id: id.toString(),
if (!groupingKeyToId[groupingKey]) {
groupingKeyToId[groupingKey] = id.toString();
id++;
}

const groupId = groupingKeyToId[groupingKey];
if (!groupsById[groupId]) {
groupsById[groupId] = {
id: groupId,
key: 'error',
label: stripAnsi(groupLabel),
resultIds: [],
browserIds: []
browserIds: [],
type: EntityType.Group
};
id++;
}

groups[groupingKey].resultIds.push(result.id);
if (!groups[groupingKey].browserIds.includes(result.parentId)) {
groups[groupingKey].browserIds.push(result.parentId);
groupsById[groupId].resultIds.push(result.id);
if (!groupsById[groupId].browserIds.includes(result.parentId)) {
groupsById[groupId].browserIds.push(result.parentId);
}
}
}

return groups;
return groupsById;
};

export const groupTests = (groupByExpressions: GroupByExpression[], resultsById: Record<string, ResultEntity>, imagesById: Record<string, ImageEntity>, errorPatterns: State['config']['errorPatterns']): Record<string, GroupEntity> => {
Expand Down
71 changes: 71 additions & 0 deletions lib/static/modules/reducers/sort-tests.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,71 @@
import {SortByExpression, SortDirection, State} from '@/static/new-ui/types/store';
import {SomeAction} from '@/static/modules/actions/types';
import actionNames from '@/static/modules/action-names';
import {applyStateUpdate} from '@/static/modules/utils';
import {SORT_BY_FAILED_RETRIES, SORT_BY_NAME, SORT_BY_TESTS_COUNT} from '@/static/constants/sort-tests';

export default (state: State, action: SomeAction): State => {
switch (action.type) {
case actionNames.INIT_STATIC_REPORT:
case actionNames.INIT_GUI_REPORT: {
const availableExpressions: SortByExpression[] = [
SORT_BY_NAME,
SORT_BY_FAILED_RETRIES
];

return applyStateUpdate(state, {
app: {
sortTestsData: {
availableExpressions,
currentDirection: SortDirection.Asc,
currentExpressionIds: [availableExpressions[0].id]
}
}
});
}
case actionNames.SORT_TESTS_SET_CURRENT_EXPRESSION: {
return applyStateUpdate(state, {
app: {
sortTestsData: {
currentExpressionIds: action.payload.expressionIds
}
}
});
}
case actionNames.SORT_TESTS_SET_DIRECTION: {
return applyStateUpdate(state, {
app: {
sortTestsData: {
currentDirection: action.payload.direction
}
}
});
}
case actionNames.GROUP_TESTS_SET_CURRENT_EXPRESSION: {
let availableExpressions: SortByExpression[];

if (action.payload.expressionIds.length > 0) {
availableExpressions = [
SORT_BY_NAME,
SORT_BY_FAILED_RETRIES,
SORT_BY_TESTS_COUNT
];
} else {
availableExpressions = [
SORT_BY_NAME,
SORT_BY_FAILED_RETRIES
];
}

return applyStateUpdate(state, {
app: {
sortTestsData: {
availableExpressions
}
}
});
}
default:
return state;
}
};
14 changes: 11 additions & 3 deletions lib/static/modules/reducers/suites-page.ts
Original file line number Diff line number Diff line change
Expand Up @@ -9,18 +9,21 @@ export default (state: State, action: SomeAction): State => {
switch (action.type) {
case actionNames.INIT_STATIC_REPORT:
case actionNames.INIT_GUI_REPORT:
case actionNames.SUITES_PAGE_SET_TREE_VIEW_MODE:
case actionNames.CHANGE_VIEW_MODE as any: // eslint-disable-line @typescript-eslint/no-explicit-any
case actionNames.GROUP_TESTS_SET_CURRENT_EXPRESSION: {
const {allTreeNodeIds} = getTreeViewItems(state);

const expandedTreeNodesById: Record<string, boolean> = {};
const expandedTreeNodesById: Record<string, boolean> = Object.assign({}, state.ui.suitesPage.expandedTreeNodesById);

for (const nodeId of allTreeNodeIds) {
expandedTreeNodesById[nodeId] = true;
}

let currentGroupId: string | null | undefined = null;
let currentTreeNodeId: string | null | undefined;
if (action.type === actionNames.GROUP_TESTS_SET_CURRENT_EXPRESSION) {
let treeViewMode = state.ui.suitesPage.treeViewMode;
if (action.type === actionNames.GROUP_TESTS_SET_CURRENT_EXPRESSION || action.type === actionNames.SUITES_PAGE_SET_TREE_VIEW_MODE) {
const {currentBrowserId} = state.app.suitesPage;
if (currentBrowserId) {
const {tree} = getTreeViewItems(state);
Expand All @@ -32,6 +35,10 @@ export default (state: State, action: SomeAction): State => {
}
}

if (action.type === actionNames.SUITES_PAGE_SET_TREE_VIEW_MODE) {
treeViewMode = action.payload.treeViewMode;
}

return applyStateUpdate(state, {
app: {
suitesPage: {
Expand All @@ -41,7 +48,8 @@ export default (state: State, action: SomeAction): State => {
},
ui: {
suitesPage: {
expandedTreeNodesById
expandedTreeNodesById,
treeViewMode
}
}
});
Expand Down
39 changes: 39 additions & 0 deletions lib/static/new-ui/components/AdaptiveSelect/index.module.css
Original file line number Diff line number Diff line change
@@ -0,0 +1,39 @@
.select-popup {
--g-color-text-info: #000;

font-size: var(--g-text-body-1-font-size);
}

.select :global(.g-select-control__label) {
margin-inline-end: 0;
}

.selected-option {
margin-inline-start: 4px;
}

.label-icons-container {
position: relative;
padding-right: 2px;
}

.label-dot {
display: none;
position: absolute;
right: 0;
top: 0;
height: 4px;
width: 4px;
border-radius: 100vh;
background-color: var(--g-color-private-red-600-solid);
}

@container (max-width: 450px) {
.select :global(.g-select-control__option-text) {
display: none;
}

.label-dot {
display: block;
}
}
Loading

0 comments on commit 4a341c3

Please sign in to comment.