diff --git a/lib/static/gui.jsx b/lib/static/gui.jsx
index 64c3a0ffb..2637afb6b 100644
--- a/lib/static/gui.jsx
+++ b/lib/static/gui.jsx
@@ -3,210 +3,18 @@ import {createRoot} from 'react-dom/client';
import {Provider} from 'react-redux';
import store from './modules/store';
import Gui from './components/gui';
-import {Button, Icon, RadioButton, Select, TextInput, ThemeProvider, Popover} from '@gravity-ui/uikit';
-import {AsideHeader} from '@gravity-ui/navigation';
-import TestplaneIcon from './icons/testplane.svg';
-
-import Split from 'react-split';
+import {ThemeProvider} from '@gravity-ui/uikit';
import '@gravity-ui/uikit/styles/fonts.css';
import '@gravity-ui/uikit/styles/styles.css';
-import {CircleDashed, SquareCheck, ListCheck, Eye, CircleInfo, Sliders, Check, Xmark, ArrowRotateLeft, BarsDescendingAlignLeftArrowDown, Layers3Diagonal, ChevronsExpandVertical, Globe, Ban, ArrowsRotateLeft, CloudCheck} from '@gravity-ui/icons';
-import {unstable_ListContainerView as ListContainerView, unstable_ListItemView as ListItemView} from '@gravity-ui/uikit/unstable';
-import {AutoSizer} from 'react-virtualized';
-import {VariableSizeList} from 'react-window';
-
const rootEl = document.getElementById('app');
const root = createRoot(rootEl);
root.render(
- {
- console.log(args);
- const treeItems = [
- {id: 'id-1', title: 'hey'},
- {id: 'id-2', title: 'hey2'}
- ];
-
- return
-
-
-
-
Suites
-
- } openOnHover={false}>
-
-
-
-
-
-
-
-
-
-
-
- 20,256
},
- {value: 'passed', content:
10,123
},
- {value: 'failed', content:
10,453
},
- {value: 'retried', content:
},
- {value: 'skipped', content:
132
},
- {value: 'updated', content:
},
- {value: 'commited', content:
10
}
- ]}>
-
-
-
-
- {({width, height}) => (
- 30}
- >
- {({index, style, data}) => (
-
-
-
- )}
-
- )}
-
-
-
-
-
-
-
- ;
- }
- } hideCollapseButton={true} />
+
);
diff --git a/lib/static/new-ui.css b/lib/static/new-ui.css
index 91e213369..d0063f5c7 100644
--- a/lib/static/new-ui.css
+++ b/lib/static/new-ui.css
@@ -1,20 +1,5 @@
.g-root {
--g-font-family-sans: 'Jost', sans-serif;
-
- /*--g-text-header-font-weight: 600;*/
- /*--g-text-subheader-font-weight: 600;*/
- /*--g-text-display-font-weight: 600;*/
- /*--g-text-accent-font-weight: 600;*/
-
- /*--g-color-base-brand: rgb(117, 155, 255);*/
- /*--g-color-base-brand-hover: rgb(99, 143, 255);*/
- /*--g-color-base-selection: rgba(82, 130, 255, 0.05);*/
- /*--g-color-base-selection-hover: rgba(82, 130, 255, 0.1);*/
- /*--g-color-line-brand: rgb(117, 155, 255);*/
- /*--g-color-text-brand: rgb(117, 155, 255);*/
- /*--g-color-text-brand-contrast: rgb(255, 255 ,255);*/
- /*--g-color-text-link: rgb(117, 155, 255);*/
- /*--g-color-text-link-hover: rgb(82, 130, 255);*/
}
.report {
diff --git a/lib/static/new-ui/app/App.tsx b/lib/static/new-ui/app/App.tsx
index ddb30f268..e5db30ba8 100644
--- a/lib/static/new-ui/app/App.tsx
+++ b/lib/static/new-ui/app/App.tsx
@@ -1,5 +1,5 @@
import {ThemeProvider} from '@gravity-ui/uikit';
-import React, {StrictMode} from 'react';
+import React, {ReactNode, StrictMode} from 'react';
import {MainLayout} from '../components/MainLayout';
import {HashRouter, Navigate, Route, Routes} from 'react-router-dom';
import {CircleInfo, Eye, ListCheck} from '@gravity-ui/icons';
@@ -13,7 +13,7 @@ import '../../new-ui.css';
import {Provider} from 'react-redux';
import store from '../../modules/store';
-export function App(): JSX.Element {
+export function App(): ReactNode {
const pages = [
{
title: 'Suites',
diff --git a/lib/static/new-ui/app/gui.tsx b/lib/static/new-ui/app/gui.tsx
index 5d02acfb3..5c9f3682f 100644
--- a/lib/static/new-ui/app/gui.tsx
+++ b/lib/static/new-ui/app/gui.tsx
@@ -1,4 +1,4 @@
-import React, {useEffect} from 'react';
+import React, {ReactNode, useEffect} from 'react';
import {createRoot} from 'react-dom/client';
import {App} from './App';
import store from '../../modules/store';
@@ -7,7 +7,7 @@ import {finGuiReport, initGuiReport} from '../../modules/actions';
const rootEl = document.getElementById('app') as HTMLDivElement;
const root = createRoot(rootEl);
-function Gui(): React.JSX.Element {
+function Gui(): ReactNode {
useEffect(() => {
store.dispatch(initGuiReport());
diff --git a/lib/static/new-ui/app/report.tsx b/lib/static/new-ui/app/report.tsx
index ee7a0163c..2323e0294 100644
--- a/lib/static/new-ui/app/report.tsx
+++ b/lib/static/new-ui/app/report.tsx
@@ -1,4 +1,4 @@
-import React, {useEffect} from 'react';
+import React, {ReactNode, useEffect} from 'react';
import {createRoot} from 'react-dom/client';
import {App} from './App';
import store from '../../modules/store';
@@ -7,7 +7,7 @@ import {initStaticReport, finStaticReport} from '../../modules/actions';
const rootEl = document.getElementById('app') as HTMLDivElement;
const root = createRoot(rootEl);
-function Gui(): React.JSX.Element {
+function Gui(): ReactNode {
useEffect(() => {
store.dispatch(initStaticReport());
diff --git a/lib/static/new-ui/features/suites/components/ImageWithMagnifier.tsx b/lib/static/new-ui/components/ImageWithMagnifier.tsx
similarity index 98%
rename from lib/static/new-ui/features/suites/components/ImageWithMagnifier.tsx
rename to lib/static/new-ui/components/ImageWithMagnifier.tsx
index 583b21e2a..4ff41fc4c 100644
--- a/lib/static/new-ui/features/suites/components/ImageWithMagnifier.tsx
+++ b/lib/static/new-ui/components/ImageWithMagnifier.tsx
@@ -55,7 +55,6 @@ export function ImageWithMagnifier({
useEffect(() => {
setMagnifierStyle({
display: showMagnifier ? '' : 'none',
- // position: 'absolute',
position: 'fixed',
pointerEvents: 'none',
height: `${magnifierHeight}px`,
diff --git a/lib/static/new-ui/features/suites/components/StatusFilter.tsx b/lib/static/new-ui/features/suites/components/StatusFilter.tsx
deleted file mode 100644
index 597a41eec..000000000
--- a/lib/static/new-ui/features/suites/components/StatusFilter.tsx
+++ /dev/null
@@ -1,42 +0,0 @@
-import {ArrowRotateLeft, ArrowsRotateLeft, Ban, Check, CircleDashed, CloudCheck, Xmark} from '@gravity-ui/icons';
-import {ControlGroupOption, RadioButton} from '@gravity-ui/uikit';
-import React from 'react';
-import {connect} from 'react-redux';
-import {getStatsFilteredByBrowsers} from '../../../../modules/selectors/stats';
-
-interface StatusFilterInternalProps {
- stats: {
- total: number;
- passed: number;
- failed: number;
- skipped: number;
- retries: number;
- },
-}
-
-function StatusFilterInternal(props: StatusFilterInternalProps): JSX.Element {
- const statusToIcon = {
- total: ,
- passed: ,
- failed: ,
- retried: ,
- skipped: ,
- updated: ,
- commited:
- } as const;
-
- const options: ControlGroupOption[] = Object.entries(props.stats)
- .filter(([status, count]) => count > 0 || status === 'total')
- .map(([status, count]) => ({
- value: status,
- content:
{statusToIcon[status as keyof typeof statusToIcon]}{count}
- }));
-
- return ;
-}
-
-export const StatusFilter = connect(
- (state) => ({
- stats: getStatsFilteredByBrowsers(state)
- })
-)(StatusFilterInternal);
diff --git a/lib/static/new-ui/features/suites/components/SuitesPage/index.module.css b/lib/static/new-ui/features/suites/components/SuitesPage/index.module.css
index 0ff23045b..db28706b6 100644
--- a/lib/static/new-ui/features/suites/components/SuitesPage/index.module.css
+++ b/lib/static/new-ui/features/suites/components/SuitesPage/index.module.css
@@ -6,10 +6,6 @@
}
.tree-view {
- /*--g-text-subheader-1-font-size: 50px;*/
- /*--g-text-subheader-1-line-height: 50px;*/
- /*font-size: var(--g-text-body-3-font-size);*/
- /*user-select: none;*/
margin-top: var(--g-spacing-2);
}
.tree-view__container {
diff --git a/lib/static/new-ui/features/suites/components/SuitesPage/index.tsx b/lib/static/new-ui/features/suites/components/SuitesPage/index.tsx
index 8db29d105..9349935d0 100644
--- a/lib/static/new-ui/features/suites/components/SuitesPage/index.tsx
+++ b/lib/static/new-ui/features/suites/components/SuitesPage/index.tsx
@@ -1,184 +1,25 @@
-import React, {ChangeEvent, useCallback, useEffect, useState} from 'react';
-import {Box, Flex, TextInput} from '@gravity-ui/uikit';
-import {debounce} from 'lodash';
-import classNames from 'classnames';
+import {Flex} from '@gravity-ui/uikit';
+import React, {ReactNode} from 'react';
import {connect} from 'react-redux';
-import {
- unstable_useList as useList,
- unstable_ListContainerView as ListContainerView,
- unstable_ListItemView as ListItemView,
- unstable_getItemRenderState as getItemRenderState
-} from '@gravity-ui/uikit/unstable';
-import styles from './index.module.css';
-import {useVirtualizer} from '@tanstack/react-virtual';
-import {bindActionCreators} from 'redux';
-import {State} from '@/static/new-ui/types/store';
-import {
- getTreeViewExpandedById,
- getTreeViewItems
-} from '@/static/new-ui/features/suites/components/SuitesPage/selectors';
-import {
- TreeViewBrowserData,
- TreeViewItem, TreeViewItemType,
- TreeViewSuiteData
-} from '@/static/new-ui/features/suites/components/SuitesPage/types';
import {SplitViewLayout} from '@/static/new-ui/components/SplitViewLayout';
-import {getIconByStatus} from '@/static/new-ui/utils';
-import {TreeViewItemTitle} from '@/static/new-ui/features/suites/components/TreeViewItemTitle';
-import {TreeViewItemSubtitle} from '@/static/new-ui/features/suites/components/TreeViewItemSubtitle';
-import * as actions from '@/static/modules/actions';
-import {useNavigate, useParams} from 'react-router-dom';
-
-interface SuitesPageInternalProps {
- treeViewItems: TreeViewItem[];
- treeViewExpandedById: Record;
- actions: typeof actions;
- isInitialized: boolean;
- currentSuiteId: string | null;
- testNameFilter: string;
-}
-
-function SuitesPageInternal(props: SuitesPageInternalProps): JSX.Element {
- const navigate = useNavigate();
- const {suiteId} = useParams();
- const itemsOriginal = props.treeViewItems;
-
- const list = useList({
- items: itemsOriginal,
- withExpandedState: true,
- getItemId: item => {
- return item.fullTitle;
- },
- controlledState: {
- expandedById: props.treeViewExpandedById
- }
- });
-
- const onItemClick = useCallback(({id}: {id: string}): void => {
- const item = list.structure.itemsById[id];
-
- if (item.type === TreeViewItemType.Suite && list.state.expandedById && id in list.state.expandedById && list.state.setExpanded) {
- props.actions.toggleSuiteSection({suiteId: item.fullTitle, shouldBeOpened: !props.treeViewExpandedById[item.fullTitle]});
- } else if (item.type === TreeViewItemType.Browser) {
- props.actions.suitesPageSetCurrentSuite(id);
-
- navigate(encodeURIComponent(item.fullTitle));
- }
- }, [list, props.actions, props.treeViewExpandedById]);
-
- const updateTestNameFilter = useCallback(debounce(
- (testName) => props.actions.updateTestNameFilter(testName),
- 500,
- {maxWait: 3000}
- ), []);
-
- const parentRef = React.useRef(null);
-
- const virtualizer = useVirtualizer({
- count: list.structure.visibleFlattenIds.length,
- getScrollElement: () => parentRef.current,
- estimateSize: () => 28,
- getItemKey: useCallback((index: number) => list.structure.visibleFlattenIds[index], [list]),
- overscan: 200
- });
-
- useEffect(() => {
- if (!suiteId && props.currentSuiteId && suiteId !== props.currentSuiteId) {
- navigate(encodeURIComponent(props.currentSuiteId));
- }
- }, []);
-
- useEffect(() => {
- if (!props.isInitialized) {
- return;
- }
-
- props.actions.setStrictMatchFilter(false);
-
- if (suiteId) {
- props.actions.suitesPageSetCurrentSuite(suiteId);
- virtualizer.scrollToIndex(list.structure.visibleFlattenIds.indexOf(suiteId), {align: 'start'});
- }
- }, [props.isInitialized]);
-
- const [testNameFilter, setTestNameFilter] = useState(props.testNameFilter);
- const onChange = useCallback((event: ChangeEvent): void => {
- setTestNameFilter(event.target.value);
- updateTestNameFilter(event.target.value);
- }, [setTestNameFilter, updateTestNameFilter]);
-
- const items = virtualizer.getVirtualItems();
+import {TestNameFilter} from '@/static/new-ui/features/suites/components/TestNameFilter';
+import {SuitesTreeView} from '@/static/new-ui/features/suites/components/SuitesTreeView';
+import styles from './index.module.css';
+function SuitesPageInternal(): ReactNode {
return
Suites
-
+
-
-
-
-
- {items.map((virtualRow) => {
- const item = list.structure.itemsById[virtualRow.key];
- const classes = [styles['tree-view__item']];
- if (item.fullTitle === props.currentSuiteId) {
- classes.push(styles['tree-item--current']);
- } else if ((item.status === 'fail' || item.status === 'error') && item.type === TreeViewItemType.Browser) {
- classes.push(styles['tree-item--error']);
- }
- if (item.type === TreeViewItemType.Browser) {
- classes.push(styles['tree-item--browser']);
- }
-
- return
- {
- return {
- startSlot: getIconByStatus(x.status),
- title: ,
- subtitle:
- };
- }
- }).props}/>
- ;
- })}
-
-
-
-
+
;
}
-export const SuitesPage = connect(
- (state: State) => ({
- isInitialized: state.app.isInitialized,
- currentSuiteId: state.app.currentSuiteId,
- treeViewItems: getTreeViewItems(state),
- treeViewExpandedById: getTreeViewExpandedById(state),
- testNameFilter: state.view.testNameFilter
- }),
- (dispatch) => ({actions: bindActionCreators(actions, dispatch)})
-)(SuitesPageInternal);
+export const SuitesPage = connect()(SuitesPageInternal);
diff --git a/lib/static/new-ui/features/suites/components/SuitesTreeView/index.tsx b/lib/static/new-ui/features/suites/components/SuitesTreeView/index.tsx
new file mode 100644
index 000000000..f133d2ebd
--- /dev/null
+++ b/lib/static/new-ui/features/suites/components/SuitesTreeView/index.tsx
@@ -0,0 +1,155 @@
+import {Box} from '@gravity-ui/uikit';
+import {
+ unstable_getItemRenderState as getItemRenderState, unstable_ListContainerView as ListContainerView,
+ unstable_ListItemView as ListItemView, unstable_useList as useList
+} from '@gravity-ui/uikit/unstable';
+import {useVirtualizer} from '@tanstack/react-virtual';
+import classNames from 'classnames';
+import React, {ReactNode, useCallback, useEffect} from 'react';
+import {connect} from 'react-redux';
+import {useNavigate, useParams} from 'react-router-dom';
+import {bindActionCreators} from 'redux';
+
+import * as actions from '@/static/modules/actions';
+import styles from '@/static/new-ui/features/suites/components/SuitesPage/index.module.css';
+import {
+ TreeViewBrowserData,
+ TreeViewItem,
+ TreeViewItemType,
+ TreeViewSuiteData
+} from '@/static/new-ui/features/suites/components/SuitesPage/types';
+import {getIconByStatus} from '@/static/new-ui/utils';
+import {TreeViewItemTitle} from '@/static/new-ui/features/suites/components/TreeViewItemTitle';
+import {TreeViewItemSubtitle} from '@/static/new-ui/features/suites/components/TreeViewItemSubtitle';
+import {State} from '@/static/new-ui/types/store';
+import {
+ getTreeViewExpandedById,
+ getTreeViewItems
+} from '@/static/new-ui/features/suites/components/SuitesTreeView/selectors';
+
+interface SuitesTreeViewProps {
+ treeViewItems: TreeViewItem[];
+ treeViewExpandedById: Record;
+ actions: typeof actions;
+ isInitialized: boolean;
+ currentSuiteId: string | null;
+}
+
+function SuitesTreeViewInternal(props: SuitesTreeViewProps): ReactNode {
+ const navigate = useNavigate();
+ const {suiteId} = useParams();
+
+ const list = useList({
+ items: props.treeViewItems,
+ withExpandedState: true,
+ getItemId: item => {
+ return item.fullTitle;
+ },
+ controlledState: {
+ expandedById: props.treeViewExpandedById
+ }
+ });
+
+ const parentRef = React.useRef(null);
+
+ const virtualizer = useVirtualizer({
+ count: list.structure.visibleFlattenIds.length,
+ getScrollElement: () => parentRef.current,
+ estimateSize: () => 28,
+ getItemKey: useCallback((index: number) => list.structure.visibleFlattenIds[index], [list]),
+ overscan: 200
+ });
+
+ const virtualizedItems = virtualizer.getVirtualItems();
+
+ // Effects
+ useEffect(() => {
+ if (!suiteId && props.currentSuiteId && suiteId !== props.currentSuiteId) {
+ navigate(encodeURIComponent(props.currentSuiteId));
+ }
+ }, []);
+
+ useEffect(() => {
+ if (!props.isInitialized) {
+ return;
+ }
+
+ props.actions.setStrictMatchFilter(false);
+
+ if (suiteId) {
+ props.actions.suitesPageSetCurrentSuite(suiteId);
+ virtualizer.scrollToIndex(list.structure.visibleFlattenIds.indexOf(suiteId), {align: 'start'});
+ }
+ }, [props.isInitialized]);
+
+ // Event handlers
+ const onItemClick = useCallback(({id}: {id: string}): void => {
+ const item = list.structure.itemsById[id];
+
+ if (item.type === TreeViewItemType.Suite && list.state.expandedById && id in list.state.expandedById && list.state.setExpanded) {
+ props.actions.toggleSuiteSection({suiteId: item.fullTitle, shouldBeOpened: !props.treeViewExpandedById[item.fullTitle]});
+ } else if (item.type === TreeViewItemType.Browser) {
+ props.actions.suitesPageSetCurrentSuite(id);
+
+ navigate(encodeURIComponent(item.fullTitle));
+ }
+ }, [list, props.actions, props.treeViewExpandedById]);
+
+ return
+
+
+
+ {virtualizedItems.map((virtualRow) => {
+ const item = list.structure.itemsById[virtualRow.key];
+ const classes = [styles['tree-view__item']];
+ if (item.fullTitle === props.currentSuiteId) {
+ classes.push(styles['tree-item--current']);
+ } else if ((item.status === 'fail' || item.status === 'error') && item.type === TreeViewItemType.Browser) {
+ classes.push(styles['tree-item--error']);
+ }
+ if (item.type === TreeViewItemType.Browser) {
+ classes.push(styles['tree-item--browser']);
+ }
+
+ return
+ {
+ return {
+ startSlot: getIconByStatus(x.status),
+ title: ,
+ subtitle:
+ };
+ }
+ }).props}/>
+ ;
+ })}
+
+
+
+ ;
+}
+
+export const SuitesTreeView = connect((state: State) => ({
+ isInitialized: state.app.isInitialized,
+ currentSuiteId: state.app.currentSuiteId,
+ treeViewItems: getTreeViewItems(state),
+ treeViewExpandedById: getTreeViewExpandedById(state)
+}),
+(dispatch) => ({actions: bindActionCreators(actions, dispatch)}))(SuitesTreeViewInternal);
diff --git a/lib/static/new-ui/features/suites/components/SuitesPage/selectors.ts b/lib/static/new-ui/features/suites/components/SuitesTreeView/selectors.ts
similarity index 100%
rename from lib/static/new-ui/features/suites/components/SuitesPage/selectors.ts
rename to lib/static/new-ui/features/suites/components/SuitesTreeView/selectors.ts
diff --git a/lib/static/new-ui/features/suites/components/TestNameFilter/index.tsx b/lib/static/new-ui/features/suites/components/TestNameFilter/index.tsx
new file mode 100644
index 000000000..bc5679d71
--- /dev/null
+++ b/lib/static/new-ui/features/suites/components/TestNameFilter/index.tsx
@@ -0,0 +1,34 @@
+import {TextInput} from '@gravity-ui/uikit';
+import React, {ChangeEvent, ReactNode, useCallback, useState} from 'react';
+import {debounce} from 'lodash';
+import {connect} from 'react-redux';
+import {bindActionCreators} from 'redux';
+import * as actions from '@/static/modules/actions';
+import {State} from '@/static/new-ui/types/store';
+
+interface TestNameFilterProps {
+ testNameFilter: string;
+ actions: typeof actions;
+}
+
+function TestNameFilterInternal(props: TestNameFilterProps): ReactNode {
+ const [testNameFilter, setTestNameFilter] = useState(props.testNameFilter);
+
+ const updateTestNameFilter = useCallback(debounce(
+ (testName) => props.actions.updateTestNameFilter(testName),
+ 500,
+ {maxWait: 3000}
+ ), []);
+
+ const onChange = useCallback((event: ChangeEvent): void => {
+ setTestNameFilter(event.target.value);
+ updateTestNameFilter(event.target.value);
+ }, [setTestNameFilter, updateTestNameFilter]);
+
+ return ;
+}
+
+export const TestNameFilter = connect(
+ (state: State) => ({testNameFilter: state.view.testNameFilter}),
+ (dispatch) => ({actions: bindActionCreators(actions, dispatch)})
+)(TestNameFilterInternal);
diff --git a/lib/static/new-ui/features/suites/components/TreeViewItemSubtitle/index.tsx b/lib/static/new-ui/features/suites/components/TreeViewItemSubtitle/index.tsx
index a4bcf62a6..f9af37f59 100644
--- a/lib/static/new-ui/features/suites/components/TreeViewItemSubtitle/index.tsx
+++ b/lib/static/new-ui/features/suites/components/TreeViewItemSubtitle/index.tsx
@@ -1,7 +1,7 @@
import React, {ReactNode} from 'react';
import {TreeViewData, TreeViewItemType} from '@/static/new-ui/features/suites/components/SuitesPage/types';
import styles from './index.module.css';
-import {ImageWithMagnifier} from '@/static/new-ui/features/suites/components/ImageWithMagnifier';
+import {ImageWithMagnifier} from '@/static/new-ui/components/ImageWithMagnifier';
export function TreeViewItemSubtitle(props: {item: TreeViewData}): ReactNode {
if (props.item.type === TreeViewItemType.Browser && props.item.diffImg) {