Skip to content

Commit 3efd9c9

Browse files
author
rocketraccoon
committed
feat: test retry ui
1 parent 045150e commit 3efd9c9

16 files changed

Lines changed: 145 additions & 42 deletions

File tree

lib/gui/app.ts

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
import type {Response} from 'express';
22

3-
import {ToolRunner, ToolRunnerTree, UndoAcceptImagesResult} from './tool-runner';
3+
import {RunParams, ToolRunner, ToolRunnerTree, UndoAcceptImagesResult} from './tool-runner';
44
import {TestBranch, TestEqualDiffsData, TestRefUpdateData} from '../tests-tree-builder/gui';
55

66
import type {ServerArgs} from './index';
@@ -29,8 +29,8 @@ export class App {
2929
return this._toolRunner.finalize();
3030
}
3131

32-
async run(tests: TestSpec[]): Promise<boolean> {
33-
return this._toolRunner.run(tests);
32+
async run(tests: TestSpec[], params: RunParams): Promise<boolean> {
33+
return this._toolRunner.run(tests, params);
3434
}
3535

3636
getTestsDataToUpdateRefs(imageIds: string[] = []): TestRefUpdateData[] {

lib/gui/server.ts

Lines changed: 6 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -160,10 +160,14 @@ export const start = async (args: ServerArgs): Promise<ServerReadyData> => {
160160
}
161161
});
162162

163-
server.post('/run', (req, res) => {
163+
server.post('/run', async (req, res) => {
164164
try {
165+
const {tests, repeatCount} = req.body;
165166
// do not wait for completion so that response does not hang and browser does not restart it by timeout
166-
app.run(req.body);
167+
for (let i = 0; i < repeatCount; i++) {
168+
await app.run(tests, {retry: repeatCount === 1});
169+
}
170+
167171
res.sendStatus(OK);
168172
} catch (e) {
169173
res.status(INTERNAL_SERVER_ERROR).send(`Error while trying to run tests: ${(e as Error).message}`);

lib/gui/tool-runner/index.ts

Lines changed: 6 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -57,6 +57,10 @@ export interface UndoAcceptImagesResult {
5757
removedResults: string[];
5858
}
5959

60+
export interface RunParams {
61+
retry?: boolean;
62+
}
63+
6064
export class ToolRunner {
6165
private _testFiles: string[];
6266
private _toolAdapter: ToolAdapter;
@@ -337,13 +341,13 @@ export class ToolRunner {
337341
return comparisons.filter(Boolean).map(image => (image as TestEqualDiffsData).id);
338342
}
339343

340-
async run(tests: TestSpec[] = []): Promise<boolean> {
344+
async run(tests: TestSpec[] = [], runParams: RunParams = {retry: true}): Promise<boolean> {
341345
const testCollection = this._ensureTestCollection();
342346
const shouldRunAllTests = _.isEmpty(tests);
343347

344348
// if tests are not passed, then run all tests with all available retries
345349
// if tests are specified, then retry only passed tests without retries
346-
return shouldRunAllTests
350+
return (shouldRunAllTests && runParams.retry)
347351
? this._toolAdapter.run(testCollection, tests, this._globalOpts)
348352
: this._toolAdapter.runWithoutRetries(testCollection, tests, this._globalOpts);
349353
}

lib/static/components/extension-point.jsx

Lines changed: 32 additions & 25 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,8 @@ import PropTypes from 'prop-types';
33
import ErrorBoundary from './error-boundary';
44
import * as plugins from '../modules/plugins';
55

6+
import {TestRepeaterComponent} from './test-repeater';
7+
68
export default class ExtensionPoint extends Component {
79
static propTypes = {
810
name: PropTypes.string.isRequired,
@@ -12,13 +14,14 @@ export default class ExtensionPoint extends Component {
1214
render() {
1315
const loadedPluginConfigs = plugins.getLoadedConfigs();
1416

15-
if (loadedPluginConfigs.length) {
16-
const {name: pointName, children: reportComponent, ...componentProps} = this.props;
17-
const pluginComponents = getExtensionPointComponents(loadedPluginConfigs, pointName);
18-
return getComponentsComposition(pluginComponents, reportComponent, componentProps);
19-
}
17+
const {name: pointName, children: reportComponent, ...componentProps} = this.props;
18+
const pluginComponents = getExtensionPointComponents(loadedPluginConfigs, pointName);
2019

21-
return this.props.children;
20+
return (
21+
<div style={{display: 'flex', gap: '12px', flexDirection: 'column'}}>
22+
{getComponentsComposition(pluginComponents, reportComponent, componentProps)}
23+
</div>
24+
);
2225
}
2326
}
2427

@@ -60,26 +63,30 @@ function composeComponents(PluginComponent, pluginProps, currentComponent, posit
6063
}
6164
}
6265

66+
const defaultComponents = [
67+
TestRepeaterComponent
68+
];
69+
6370
export function getExtensionPointComponents(loadedPluginConfigs, pointName) {
64-
return loadedPluginConfigs
65-
.map(config => {
66-
try {
67-
const PluginComponent = plugins.get(config.name, config.component);
68-
return {
69-
PluginComponent,
70-
name,
71-
point: getComponentPoint(PluginComponent, config),
72-
position: getComponentPosition(PluginComponent, config),
73-
config
74-
};
75-
} catch (err) {
76-
console.error(err);
77-
return {};
78-
}
79-
})
80-
.filter(({point, position}) => {
81-
return point && position && point === pointName;
82-
});
71+
return [
72+
...defaultComponents,
73+
...loadedPluginConfigs
74+
.map(config => {
75+
try {
76+
const PluginComponent = plugins.get(config.name, config.component);
77+
return {
78+
PluginComponent,
79+
name,
80+
point: getComponentPoint(PluginComponent, config),
81+
position: getComponentPosition(PluginComponent, config),
82+
config
83+
};
84+
} catch (err) {
85+
console.error(err);
86+
return {};
87+
}
88+
})
89+
].filter(({point, position}) => (point && position && point === pointName));
8390
}
8491

8592
function getComponentPoint(component, config) {
Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,11 @@
1+
.test-repeater-container {
2+
display: flex;
3+
justify-content: space-between;
4+
align-items: center;
5+
width: 100%;
6+
}
7+
8+
.test-repeater-input {
9+
display: flex;
10+
flex-direction: row;
11+
}
Lines changed: 55 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,55 @@
1+
import React from 'react';
2+
import {useDispatch, useSelector} from 'react-redux';
3+
import {NumberInput, Button, Icon} from '@gravity-ui/uikit';
4+
import {Plus, Minus} from '@gravity-ui/icons';
5+
import styles from './index.module.css';
6+
import {setRepeatCount} from '@/static/modules/actions';
7+
import {ExtensionPointName} from '@/static/new-ui/constants/plugins';
8+
9+
const MAX_REPEATER_COUNT = 99;
10+
const MIN_REPEATER_COUNT = 1;
11+
12+
const PluginComponent = (): React.ReactNode => {
13+
const dispatch = useDispatch();
14+
const repeatCount = useSelector((state) => state.repeatCount);
15+
16+
const changeRepeatCount = (newValue: number): void => {
17+
if (newValue >= MIN_REPEATER_COUNT && newValue < MAX_REPEATER_COUNT) {
18+
dispatch(setRepeatCount(newValue));
19+
}
20+
};
21+
22+
return (
23+
<div className={styles.testRepeaterContainer}>
24+
<span className="text-header-1">Number of repeats</span>
25+
<NumberInput
26+
size='m'
27+
value={repeatCount}
28+
style={{maxWidth: '78px'}}
29+
onChange={(e): void => changeRepeatCount(parseInt(e.target.value, 10))}
30+
qa='repeat-count'
31+
hiddenControls
32+
endContent={(
33+
<div className={styles.testRepeaterInput}>
34+
<Button view="flat" size="s" onClick={(): void => changeRepeatCount(repeatCount - 1)}>
35+
<Icon data={Minus} size={14}/>
36+
</Button>
37+
<Button view="flat" size="s" onClick={(): void => changeRepeatCount(repeatCount + 1)}>
38+
<Icon data={Plus} size={14}/>
39+
</Button>
40+
</div>
41+
)}
42+
/>
43+
</div>
44+
);
45+
};
46+
47+
// PluginComponent.point = ExtensionPointName.RunTestOptions;
48+
49+
export const TestRepeaterComponent = {
50+
PluginComponent,
51+
name: 'Test Repeater',
52+
position: 'after',
53+
point: ExtensionPointName.RunTestOptions,
54+
config: {}
55+
};

lib/static/modules/action-names.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@ export default {
55
FIN_STATIC_REPORT: 'FIN_STATIC_REPORT',
66
RUN_ALL_TESTS: 'RUN_ALL_TESTS',
77
RUN_FAILED_TESTS: 'RUN_FAILED_TESTS',
8+
SET_REPEAT_COUNT: 'SET_REPEAT_COUNT',
89
STOP_TESTS: 'STOP_TESTS',
910
RETRY_SUITE: 'RETRY_SUITE',
1011
RETRY_TEST: 'RETRY_TEST',

lib/static/modules/actions/run-tests.ts

Lines changed: 5 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -11,12 +11,15 @@ import {TestStatus} from '@/constants';
1111

1212
export type RunTestAction = Action<typeof actionNames.RETRY_TEST>;
1313
export const runTest = (): RunTestAction => ({type: actionNames.RETRY_TEST});
14+
export const setRepeatCount = (repeatCount: number): Action<typeof actionNames.SET_REPEAT_COUNT, {repeatCount: number}> => ({type: actionNames.SET_REPEAT_COUNT, payload: {repeatCount}});
1415

1516
export const thunkRunTests = ({tests = []}: {tests?: TestSpec[]} = {}): AppThunk => {
16-
return async (dispatch) => {
17+
return async (dispatch, getState) => {
18+
const {repeatCount} = getState();
19+
1720
dispatch(runTest());
1821
try {
19-
await axios.post('/run', tests);
22+
await axios.post('/run', {tests, repeatCount});
2023
} catch (e) {
2124
// TODO: report error via notifications
2225
console.error('Error while running tests:', e);

lib/static/modules/default-state.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,7 @@ import {MIN_SECTION_SIZE_PERCENT} from '../new-ui/features/suites/constants';
77
export default Object.assign({config: configDefaults}, {
88
gui: true,
99
running: false,
10+
repeatCount: 1,
1011
processing: false,
1112
stopping: false,
1213
autoRun: false,

lib/static/modules/reducers/running.js

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,10 @@ import {initSearch} from '@/static/modules/search';
33

44
export default (state, action) => {
55
switch (action.type) {
6+
case actionNames.SET_REPEAT_COUNT: {
7+
return {...state, repeatCount: action.payload.repeatCount};
8+
}
9+
case actionNames.TEST_BEGIN:
610
case actionNames.RUN_ALL_TESTS:
711
case actionNames.RUN_FAILED_TESTS:
812
case actionNames.RETRY_SUITE:

0 commit comments

Comments
 (0)