Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 5 additions & 0 deletions lib/gui/app.ts
Original file line number Diff line number Diff line change
Expand Up @@ -53,6 +53,11 @@ export class App {
return this._toolRunner.findEqualDiffs(data);
}

async refreshTests(): Promise<ToolRunnerTree | null> {
await this._toolRunner.refreshTests();
return this._toolRunner.tree;
}

addClient(connection: Response): void {
this._toolRunner.addClient(connection);
}
Expand Down
9 changes: 9 additions & 0 deletions lib/gui/server.ts
Original file line number Diff line number Diff line change
Expand Up @@ -274,6 +274,15 @@ export const start = async (args: ServerArgs): Promise<ServerReadyData> => {
logger.log('server shutting down');
});

server.get('/refresh-tests', async (_req, res) => {
try {
const tree = await app.refreshTests();
res.json(tree satisfies GetInitResponse);
} catch (e: unknown) {
res.status(INTERNAL_SERVER_ERROR).send(`Error while refreshing tests: ${(e as Error).message}`);
}
});

server.post('/stop', (_req, res) => {
try {
// pass 0 to prevent terminating testplane process
Expand Down
5 changes: 5 additions & 0 deletions lib/gui/tool-runner/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -154,6 +154,11 @@ export class ToolRunner {
return this._toolAdapter.readTests(this._testFiles, this._globalOpts);
}

async refreshTests(): Promise<void> {
this._ensureReportBuilder().saveDb();
await this.initialize();
}

protected _ensureReportBuilder(): GuiReportBuilder {
if (!this._reportBuilder) {
throw new Error('ToolRunner has to be initialized before usage');
Expand Down
7 changes: 7 additions & 0 deletions lib/report-builder/gui.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
import _ from 'lodash';
import {StaticReportBuilder, StaticReportBuilderOptions} from './static';
import {TestAttemptManager} from '../test-attempt-manager';
import {GuiTestsTreeBuilder, TestBranch, TestEqualDiffsData, TestRefUpdateData} from '../tests-tree-builder/gui';
import {UPDATED, DB_COLUMNS, TestStatus, DEFAULT_TITLE_DELIMITER, SKIPPED, SUCCESS} from '../constants';
import {ConfigForStaticFile, getConfigForStaticFile} from '../server-utils';
Expand Down Expand Up @@ -76,6 +77,12 @@ export class GuiReportBuilder extends StaticReportBuilder {
};
}

resetTree(): void {
this._testsTree = GuiTestsTreeBuilder.create({baseHost: this._reporterConfig.baseHost});
this._testAttemptManager = new TestAttemptManager();
this._skips = [];
}

getTestBranch(id: string): TestBranch {
return this._testsTree.getTestBranch(id);
}
Expand Down
4 changes: 4 additions & 0 deletions lib/report-builder/static.ts
Original file line number Diff line number Diff line change
Expand Up @@ -76,6 +76,10 @@ export class StaticReportBuilder {
this._workers = workers;
}

saveDb(): void {
this._dbClient.save();
}

registerAttempt(testInfo: {fullName: string, browserId: string}, status: TestStatus): number {
return this._testAttemptManager.registerAttempt(testInfo, status);
}
Expand Down
4 changes: 4 additions & 0 deletions lib/sqlite-client.ts
Original file line number Diff line number Diff line change
Expand Up @@ -120,6 +120,10 @@ export class SqliteClient {
return this._db;
}

save(): void {
fs.writeFileSync(this._dbPath, this._db.export());
}

close(): void {
fs.writeFileSync(this._dbPath, this._db.run('VACUUM').export());

Expand Down
1 change: 1 addition & 0 deletions lib/static/modules/action-names.ts
Original file line number Diff line number Diff line change
Expand Up @@ -82,5 +82,6 @@ export default {
SNAPSHOTS_PLAYER_GO_TO_TIME: 'SNAPSHOTS_PLAYER_GO_TO_TIME',
SNAPSHOTS_PLAYER_TOGGLE_VISIBILITY: 'SNAPSHOTS_PLAYER_TOGGLE_VISIBILITY',
SET_BROWSER_FEATURES: 'SET_BROWSER_FEATURES',
SET_REFRESH_LOADING: 'SET_REFRESH_LOADING',
SET_SEARCH_LOADING: 'SET_SEARCH_LOADING'
} as const;
6 changes: 6 additions & 0 deletions lib/static/modules/actions/filters.ts
Original file line number Diff line number Diff line change
Expand Up @@ -38,10 +38,16 @@ export const setSearchLoading = (payload: boolean): SetSearchLoading => {
export type ChangeViewModeAction = Action<typeof actionNames.CHANGE_VIEW_MODE, FilterPayload<ViewMode>>;
export const changeViewMode = (payload: ChangeViewModeAction['payload']): ChangeViewModeAction => ({type: actionNames.CHANGE_VIEW_MODE, payload});

export type SetRefreshLoading = Action<typeof actionNames.SET_REFRESH_LOADING, boolean>;
export const setRefreshLoading = (payload: boolean): SetRefreshLoading => {
return {type: actionNames.SET_REFRESH_LOADING, payload};
};

export type FiltersAction =
| UpdateNameFilterAction
| SetMatchCaseFilterAction
| SetUseRegexFilterAction
| SelectBrowsersAction
| ChangeViewModeAction
| SetRefreshLoading
| SetSearchLoading;
20 changes: 20 additions & 0 deletions lib/static/modules/actions/lifecycle.ts
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@ import {GetInitResponse} from '@/gui/server';
import {Tree} from '@/tests-tree-builder/base';
import {BrowserItem} from '@/types';
import {createNotificationError} from '@/static/modules/actions/notifications';
import {setRefreshLoading} from '@/static/modules/actions/filters';
import {LocalStorageKey} from '@/constants/local-storage';
import * as localStorageWrapper from '@/static/modules/local-storage-wrapper';
import {updateTimeTravelSettings} from '../../new-ui/utils/api';
Expand Down Expand Up @@ -139,6 +140,25 @@ export const thunkInitStaticReport = ({isNewUi}: InitStaticReportData = {}): App
};
};

export const thunkRefreshGuiReport = (): AppThunk => {
return async (dispatch, getState) => {
dispatch(setRefreshLoading(true));
try {
const {db} = getState();
const response = await axios.get<GetInitResponse>('/refresh-tests');

if (!response.data || !db) {
throw new Error('Could not refresh tests data.');
}

dispatch(initGuiReport({...response.data, db}));
} catch (e: unknown) {
dispatch(createNotificationError('thunkRefreshGuiReport', e as Error));
dispatch(setRefreshLoading(false));
}
};
};

export type FinGuiReportAction = Action<typeof actionNames.FIN_GUI_REPORT>;
export const finGuiReport = (): FinGuiReportAction => ({type: actionNames.FIN_GUI_REPORT});

Expand Down
4 changes: 4 additions & 0 deletions lib/static/modules/reducers/filters.ts
Original file line number Diff line number Diff line change
Expand Up @@ -41,6 +41,7 @@ export default (state: State, action: FiltersAction | InitGuiReportAction | Init
state,
{
app: {
isRefreshLoading: false,
viewMode,
[Page.visualChecksPage]: {
diffMode: visualChecksPageDiffMode
Expand Down Expand Up @@ -99,6 +100,9 @@ export default (state: State, action: FiltersAction | InitGuiReportAction | Init
case actionNames.SET_SEARCH_LOADING:
return applyStateUpdate(state, {app: {isSearchLoading: action.payload}});

case actionNames.SET_REFRESH_LOADING:
return applyStateUpdate(state, {app: {isRefreshLoading: action.payload}});

default:
return state;
}
Expand Down
8 changes: 4 additions & 4 deletions lib/static/new-ui/components/AttemptPickerItem/index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -75,14 +75,14 @@ export const AttemptPickerItem = (props: AttemptPickerItemProps): ReactNode => {

const isGroupingEnabled = useSelector(({view: {keyToGroupTestsBy}, app: {isNewUi, suitesPage: {currentGroupId}}}: State) => isNewUi ? Boolean(currentGroupId) : Boolean(keyToGroupTestsBy));

const status = isFailStatus(result.status) && hasUnrelatedToScreenshotsErrors((result as ResultEntityError).error) ? TestStatus.FAIL_ERROR : result.status;

const buttonStyle = getButtonStyleByStatus(status);

if (!result) {
return null;
}

const status = isFailStatus(result.status) && hasUnrelatedToScreenshotsErrors((result as ResultEntityError).error) ? TestStatus.FAIL_ERROR : result.status;

const buttonStyle = getButtonStyleByStatus(status);

const className = classNames(
styles.attemptPickerItem,
{[styles['attempt-picker-item--active']]: isActive},
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,15 @@
z-index: 1;
}

@keyframes spin {
from { transform: rotate(0deg); }
to { transform: rotate(360deg); }
}

.is-refresh-loading {
animation: spin 0.8s linear infinite;
}

.buttons-container {
margin-left: auto;
display: flex;
Expand Down
22 changes: 20 additions & 2 deletions lib/static/new-ui/components/TreeActionsToolbar/index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,8 @@ import {
SquareMinus,
ListUl,
Hierarchy,
GearPlay
GearPlay,
ArrowsRotateRight
} from '@gravity-ui/icons';
import React, {ReactNode, useCallback, useMemo} from 'react';
import {useDispatch, useSelector} from 'react-redux';
Expand All @@ -24,7 +25,9 @@ import {
selectAll,
setAllTreeNodesState, setTreeViewMode,
staticAccepterStageScreenshot,
staticAccepterUnstageScreenshot, thunkRunTests
staticAccepterUnstageScreenshot,
thunkRunTests,
thunkRefreshGuiReport
} from '@/static/modules/actions';
import {ImageEntity, TreeViewMode} from '@/static/new-ui/types/store';
import {CHECKED, INDETERMINATE} from '@/constants/checked-statuses';
Expand Down Expand Up @@ -74,6 +77,7 @@ export function TreeActionsToolbar({onHighlightCurrentTest, className}: TreeActi
const selectedTests = useSelector(getCheckedTests);
const visibleBrowserIds: string[] = useSelector(getVisibleBrowserIds);
const isInitialized = useSelector(getIsInitialized);
const isRefreshLoading = useSelector((state) => state.app.isRefreshLoading);

const isRunTestsAvailable = useSelector(state => state.app.availableFeatures)
.find(feature => feature.name === RunTestsFeature.name);
Expand Down Expand Up @@ -192,8 +196,22 @@ export function TreeActionsToolbar({onHighlightCurrentTest, className}: TreeActi
const pluginComponents = getExtensionPointComponents(loadedPluginConfigs, ExtensionPointName.RunTestOptions);
const hasRunTestOptions = pluginComponents.length > 0;

const handleRefresh = useCallback(() => {
dispatch(thunkRefreshGuiReport());
}, [dispatch]);

const getViewButtons = (): ReactNode => (
<>
{isRunTestsAvailable && (
<IconButton
className={styles.iconButton}
icon={<Icon className={classNames({[styles.isRefreshLoading]: isRefreshLoading})} data={ArrowsRotateRight} height={14}/>}
tooltip="Refresh tests tree"
view="flat"
onClick={handleRefresh}
disabled={isRunning || !isInitialized || isRefreshLoading}
/>
)}
{isRunTestsAvailable && (
isRunning
? (
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -218,6 +218,12 @@ export function SuitesPage(): ReactNode {
}
}, [visibleTreeNodeIds, tree, dispatch]);

useEffect(() => {
if (currentBrowser && !currentBrowserEntity && isInitialized && visibleTreeNodeIds.length > 0) {
onSelectFirstResult();
}
}, [currentBrowser, currentBrowserEntity, isInitialized, visibleTreeNodeIds.length]);

useHotkey('ArrowDown', goToNextSuite);
useHotkey('ArrowUp', goToPrevSuite);
useHotkey('ArrowRight', goToNextAttempt);
Expand Down
8 changes: 6 additions & 2 deletions lib/static/new-ui/features/suites/selectors.ts
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,11 @@ export const getCurrentResultId = (state: State): string | null => {
return null;
}

const resultIds = state.tree.browsers.byId[browserId].resultIds;
const browser = state.tree.browsers.byId[browserId];
if (!browser) {
return null;
}
const resultIds = browser.resultIds;

const groupId = state.app.suitesPage.currentGroupId;

Expand Down Expand Up @@ -90,7 +94,7 @@ export const getAttempt = (state: State): number | null => {
const browserId = state.app.suitesPage.currentBrowserId;

if (browserId) {
return state.tree.browsers.stateById[browserId].retryIndex;
return state.tree.browsers.stateById[browserId]?.retryIndex;
}

return null;
Expand Down
2 changes: 1 addition & 1 deletion lib/static/new-ui/features/visual-checks/selectors.ts
Original file line number Diff line number Diff line change
Expand Up @@ -172,7 +172,7 @@ export const getAttempt = (state: State): number | null => {
const namedImage = getCurrentNamedImage(state);

if (namedImage) {
return state.tree.browsers.stateById[namedImage?.browserId].retryIndex;
return state.tree.browsers.stateById[namedImage?.browserId]?.retryIndex;
}

return null;
Expand Down
1 change: 1 addition & 0 deletions lib/static/new-ui/types/store.ts
Original file line number Diff line number Diff line change
Expand Up @@ -260,6 +260,7 @@ export interface State {
isInitialized: boolean;
availableFeatures: Feature[],
isSearchLoading?: boolean;
isRefreshLoading?: boolean;

// Filters in top of sidebar
nameFilter: string;
Expand Down
Loading