Skip to content

Commit 37dac8f

Browse files
authored
fix: improve working with browser versions and old URLs (#738)
* fix: show all browser versions if only browser id is specified in url * fix: fix empty browsers select when using with browser versions * fix: show version tag for browsers with multiple versions * feat: implemented seamless migration from old URL in new UI
1 parent beebfb4 commit 37dac8f

19 files changed

Lines changed: 466 additions & 101 deletions

File tree

.eslintignore

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -17,3 +17,4 @@ tmp
1717
**/playwright-report
1818
**/html-report/*
1919
examples/**
20+
wt/**

lib/static/modules/query-params.js

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -88,6 +88,18 @@ export function decodeBrowsers(browsers) {
8888
.map(decode);
8989
}
9090

91+
export function expandBrowserVersions(filteredBrowsers, availableBrowsers) {
92+
return filteredBrowsers.map(filteredBrowser => {
93+
if (isEmpty(filteredBrowser.versions)) {
94+
const availableBrowser = availableBrowsers.find(b => b.id === filteredBrowser.id);
95+
if (availableBrowser) {
96+
return {id: filteredBrowser.id, versions: [...availableBrowser.versions]};
97+
}
98+
}
99+
return filteredBrowser;
100+
});
101+
}
102+
91103
export function setFilteredBrowsers(browsers) {
92104
const urlExtendedWithBrowsers = appendQuery(
93105
window.location.href,

lib/static/modules/reducers/filters.ts

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@ import {DiffModeId, DiffModes, ViewMode} from '@/constants';
66
import {BrowserItem} from '@/types';
77
import * as localStorageWrapper from '@/static/modules/local-storage-wrapper';
88
import {getViewQuery} from '@/static/modules/custom-queries';
9+
import {expandBrowserVersions} from '@/static/modules/query-params';
910
import {isEmpty} from 'lodash';
1011
import {applyStateUpdate} from '@/static/modules/utils/state';
1112

@@ -33,6 +34,8 @@ export default (state: State, action: FiltersAction | InitGuiReportAction | Init
3334

3435
if (isEmpty(viewQuery.filteredBrowsers)) {
3536
viewQuery.filteredBrowsers = state.browsers;
37+
} else {
38+
viewQuery.filteredBrowsers = expandBrowserVersions(viewQuery.filteredBrowsers as BrowserItem[], state.browsers);
3639
}
3740

3841
const newState = applyStateUpdate(

lib/static/new-ui.css

Lines changed: 41 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -110,4 +110,44 @@ a:hover {
110110

111111
b {
112112
font-weight: 500;
113-
}
113+
}
114+
115+
116+
.toaster {
117+
border: 1px solid rgb(229, 231, 235);
118+
box-shadow: rgb(255, 255, 255) 0px 0px 0px 0px, rgba(0, 0, 0, 0.1) 0px 10px 15px -3px, rgba(0, 0, 0, 0.1) 0px 4px 6px -4px !important;
119+
margin-right: 10px;
120+
margin-bottom: 20px !important;
121+
}
122+
123+
@keyframes toast-appear {
124+
from {
125+
opacity: 0;
126+
scale: 0.95;
127+
}
128+
129+
to {
130+
opacity: 1;
131+
scale: 1;
132+
}
133+
}
134+
135+
.toaster:global(.g-toast-animation-desktop_enter_active) {
136+
animation: toast-appear .15s forwards ease;
137+
}
138+
139+
.toaster:global(.g-toast-animation-desktop_exit_active) {
140+
animation: toast-appear .15s forwards ease reverse;
141+
}
142+
143+
.toaster :global(.g-toast__title) {
144+
font-weight: 450;
145+
}
146+
147+
.toaster :global(.g-toast__content) {
148+
color: var(--g-color-private-black-400);
149+
}
150+
151+
.toaster__icon--error {
152+
color: var(--g-color-private-red-600-solid);
153+
}

lib/static/new-ui/components/BrowsersSelect/index.tsx

Lines changed: 32 additions & 41 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
import {PlanetEarth} from '@gravity-ui/icons';
2-
import {Button, Flex, Icon, Select, SelectRenderControlProps, SelectRenderOption} from '@gravity-ui/uikit';
3-
import React, {ReactNode, Ref, useEffect, useState} from 'react';
2+
import {Button, Flex, Icon, Select, SelectRenderControlProps, SelectRenderOption, useSelectOptions} from '@gravity-ui/uikit';
3+
import React, {ReactNode, Ref, useEffect, useMemo, useState} from 'react';
44
import {useDispatch, useSelector} from 'react-redux';
55

66
import {selectBrowsers} from '@/static/modules/actions';
@@ -75,48 +75,40 @@ export function BrowsersSelect(): ReactNode {
7575
setSelectedBrowsers(selectedItems);
7676
};
7777

78-
const renderOptions = (): React.JSX.Element | React.JSX.Element[] => {
78+
const getOptionData = (browser: BrowserItem, version: string, content: string): {value: string; content: string; data: {id: string; version: string}} => ({
79+
value: serializeBrowserData(browser.id, version),
80+
content,
81+
data: {id: browser.id, version}
82+
});
83+
84+
const rawOptions = useMemo(() => {
7985
const browsersWithMultipleVersions = browsers.filter(browser => browser.versions.length > 1);
8086
const browsersWithSingleVersion = browsers.filter(browser => browser.versions.length === 1);
8187

82-
const getOptionProps = (browser: BrowserItem, version: string): {value: string; content: string; data: Record<string, unknown>} => ({
83-
value: serializeBrowserData(browser.id, version),
84-
content: browser.id,
85-
data: {id: browser.id, version}
86-
});
87-
8888
if (browsersWithMultipleVersions.length === 0) {
89-
// If there are no browsers with multiple versions, we want to render a simple plain list
90-
return browsers.map(browser => <Select.Option
91-
key={browser.id}
92-
{...getOptionProps(browser, browser.versions[0])}
93-
/>);
89+
// If there are no browsers with multiple versions, we want to render a simple flat list
90+
return browsers.map(browser => getOptionData(browser, browser.versions[0], browser.id));
9491
} else {
95-
// Otherwise render browser version groups and place all browsers with single version into "Other" group
96-
return (
97-
<>
98-
{browsersWithMultipleVersions.map(browser => (
99-
<Select.OptionGroup key={browser.id} label={browser.id}>
100-
{browser.versions.map(version => (
101-
<Select.Option
102-
key={version}
103-
{...getOptionProps(browser, version)}
104-
/>
105-
))}
106-
</Select.OptionGroup>
107-
))}
108-
<Select.OptionGroup label="Other">
109-
{browsersWithSingleVersion.map(browser => (
110-
<Select.Option
111-
key={browser.id}
112-
{...getOptionProps(browser, browser.versions[0])}
113-
/>
114-
))}
115-
</Select.OptionGroup>
116-
</>
117-
);
92+
// Otherwise render browser version groups and place all browsers with a single version in the "Other" group
93+
const groups: {label: string; options: ReturnType<typeof getOptionData>[]}[] = [];
94+
95+
browsersWithMultipleVersions.forEach(browser => {
96+
groups.push({
97+
label: browser.id,
98+
options: browser.versions.map(version => getOptionData(browser, version, version))
99+
});
100+
});
101+
102+
groups.push({
103+
label: 'Other',
104+
options: browsersWithSingleVersion.map(browser => getOptionData(browser, browser.versions[0], browser.id))
105+
});
106+
107+
return groups;
118108
}
119-
};
109+
}, [browsers]);
110+
111+
const options = useSelectOptions({options: rawOptions});
120112

121113
const isInitialized = useSelector(getIsInitialized);
122114

@@ -171,6 +163,7 @@ export function BrowsersSelect(): ReactNode {
171163
<Select
172164
disablePortal
173165
value={selected}
166+
options={options}
174167
multiple={true}
175168
hasCounter
176169
filterable
@@ -182,9 +175,7 @@ export function BrowsersSelect(): ReactNode {
182175
onUpdate={onUpdate}
183176
onFocus={onFocus}
184177
onClose={onClose}
185-
>
186-
{renderOptions()}
187-
</Select>
178+
/>
188179
</>
189180
);
190181
}

lib/static/new-ui/components/GuiniToolbarOverlay/index.module.css

Lines changed: 0 additions & 39 deletions
Original file line numberDiff line numberDiff line change
@@ -65,42 +65,3 @@
6565
.modal-button-primary {
6666
composes: regular-button from global, action-button from global;
6767
}
68-
69-
.error-toaster {
70-
border: 1px solid rgb(229, 231, 235);
71-
box-shadow: rgb(255, 255, 255) 0px 0px 0px 0px, rgba(0, 0, 0, 0.1) 0px 10px 15px -3px, rgba(0, 0, 0, 0.1) 0px 4px 6px -4px !important;
72-
margin-right: 10px;
73-
margin-bottom: 20px !important;
74-
}
75-
76-
@keyframes toast-appear {
77-
from {
78-
opacity: 0;
79-
scale: 0.95;
80-
}
81-
82-
to {
83-
opacity: 1;
84-
scale: 1;
85-
}
86-
}
87-
88-
.error-toaster:global(.g-toast-animation-desktop_enter_active) {
89-
animation: toast-appear .15s forwards ease;
90-
}
91-
92-
.error-toaster:global(.g-toast-animation-desktop_exit_active) {
93-
animation: toast-appear .15s forwards ease reverse;
94-
}
95-
96-
.error-toaster :global(.g-toast__title) {
97-
font-weight: 450;
98-
}
99-
100-
.error-toaster :global(.g-toast__content) {
101-
color: var(--g-color-private-black-400);
102-
}
103-
104-
.error-toaster-icon {
105-
color: var(--g-color-private-red-600-solid);
106-
}

lib/static/new-ui/components/GuiniToolbarOverlay/index.tsx

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -93,8 +93,8 @@ export function GuiniToolbarOverlay(): ReactNode {
9393
content: result.error.message + '. See console for details.',
9494
isClosable: true,
9595
autoHiding: 5000,
96-
renderIcon: () => <TriangleExclamation className={styles.errorToasterIcon} width={20} height={20} />,
97-
className: styles.errorToaster
96+
renderIcon: () => <TriangleExclamation className='toaster__icon--error' width={20} height={20} />,
97+
className: 'toaster'
9898
});
9999
}
100100
};

lib/static/new-ui/components/SuiteTitle/index.tsx

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -34,7 +34,7 @@ export function SuiteTitle(props: SuiteTitleProps): ReactNode {
3434
))}
3535
</div>}
3636
<div className={styles.titleContainer}>
37-
<h2 className={classNames('text-display-1')}>
37+
<h2 className={classNames('text-display-1')} data-qa="suite-title">
3838
{suiteName ?? 'Unknown Suite'}
3939
</h2>
4040
<div className={styles.labelsContainer}>

lib/static/new-ui/features/suites/components/SuitesPage/index.tsx

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -37,11 +37,15 @@ import {getIconByStatus} from '@/static/new-ui/utils';
3737
import {Page} from '@/constants';
3838
import {usePage} from '@/static/new-ui/hooks/usePage';
3939
import {useHotkey} from '@/static/new-ui/hooks/useHotkey';
40+
import {useLegacyUrlMigration} from '@/static/new-ui/hooks/useLegacyUrlMigration';
4041
import {changeTestRetry, setCurrentTreeNode, setStrictMatchFilter} from '@/static/modules/actions';
4142
import {getUrl} from '@/static/new-ui/utils/getUrl';
4243

4344
export function SuitesPage(): ReactNode {
4445
const page = usePage();
46+
47+
useLegacyUrlMigration();
48+
4549
const currentResult = useSelector(getCurrentResult);
4650
const treeData = useSelector(getSuitesTreeViewData);
4751
const resultImages = useSelector(getCurrentResultImages);

lib/static/new-ui/features/suites/components/SuitesPage/selectors.ts

Lines changed: 4 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@ import {createSelector} from 'reselect';
22
import {
33
getAllRootGroupIds,
44
getBrowsers,
5+
getBrowsersList,
56
getBrowsersState,
67
getGroups,
78
getImages,
@@ -18,14 +19,14 @@ import {State} from '@/static/new-ui/types/store';
1819

1920
// Converts the existing store structure to the one that can be consumed by GravityUI
2021
export const getSuitesTreeViewData = createSelector(
21-
[getGroups, getSuites, getAllRootGroupIds, getBrowsers, getBrowsersState, getResults, getImages, getTreeViewMode, getSortTestsData],
22-
(groups, suites, rootGroupIds, browsers, browsersState, results, images, treeViewMode, sortTestsData): TreeViewData => {
22+
[getGroups, getSuites, getAllRootGroupIds, getBrowsers, getBrowsersState, getResults, getImages, getTreeViewMode, getSortTestsData, getBrowsersList],
23+
(groups, suites, rootGroupIds, browsers, browsersState, results, images, treeViewMode, sortTestsData, browsersList): TreeViewData => {
2324
const currentSortDirection = sortTestsData.currentDirection;
2425
const currentSortExpression = sortTestsData.availableExpressions
2526
.find(expr => expr.id === sortTestsData.currentExpressionIds[0])
2627
?? sortTestsData.availableExpressions[0];
2728

28-
const entitiesContext = {results, images, suites, treeViewMode, browsersState, browsers, groups, currentSortDirection, currentSortExpression};
29+
const entitiesContext = {results, images, suites, treeViewMode, browsersState, browsers, groups, currentSortDirection, currentSortExpression, browsersList};
2930

3031
const isGroupingEnabled = rootGroupIds.length > 0;
3132
if (isGroupingEnabled) {

0 commit comments

Comments
 (0)