Skip to content

Commit

Permalink
feat: add static image accepter
Browse files Browse the repository at this point in the history
  • Loading branch information
KuznetsovRoman committed Jul 9, 2024
1 parent 2a8e8d9 commit cdd8646
Show file tree
Hide file tree
Showing 37 changed files with 1,079 additions and 99 deletions.
8 changes: 6 additions & 2 deletions lib/common-utils.ts
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,9 @@ import {
SKIPPED,
SUCCESS,
TestStatus,
UPDATED
UPDATED,
STAGED,
COMMITED
} from './constants';

import {CHECKED, INDETERMINATE, UNCHECKED} from './constants/checked-statuses';
Expand All @@ -36,7 +38,7 @@ const statusPriority: TestStatus[] = [
RUNNING, QUEUED,

// final
ERROR, FAIL, UPDATED, SUCCESS, IDLE, SKIPPED
ERROR, FAIL, STAGED, COMMITED, UPDATED, SUCCESS, IDLE, SKIPPED
];

export const logger = pick(console, ['log', 'warn', 'error']);
Expand All @@ -48,6 +50,8 @@ export const isRunningStatus = (status: TestStatus): boolean => status === RUNNI
export const isErrorStatus = (status: TestStatus): boolean => status === ERROR;
export const isSkippedStatus = (status: TestStatus): boolean => status === SKIPPED;
export const isUpdatedStatus = (status: TestStatus): boolean => status === UPDATED;
export const isStagedStatus = (status: TestStatus): boolean => status === STAGED;
export const isCommitedStatus = (status: TestStatus): boolean => status === COMMITED;

export const determineFinalStatus = (statuses: TestStatus[]): TestStatus | null => {
if (!statuses.length) {
Expand Down
37 changes: 37 additions & 0 deletions lib/config/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,10 @@ const ALLOWED_PLUGIN_DESCRIPTION_FIELDS = new Set(['name', 'component', 'point',
type TypePredicateFn<T> = (value: unknown) => value is T;
type AssertionFn<T> = (value: unknown) => asserts value is T;

const isPlainObject = (value: unknown): value is Record<string, unknown> => {
return _.isPlainObject(value);
};

const assertType = <T>(name: string, validationFn: (value: unknown) => value is T, type: string): AssertionFn<T> => {
return (v: unknown): asserts v is T => {
if (!validationFn(v)) {
Expand All @@ -25,6 +29,7 @@ const assertType = <T>(name: string, validationFn: (value: unknown) => value is
const assertString = (name: string): AssertionFn<string> => assertType(name, _.isString, 'string');
const assertBoolean = (name: string): AssertionFn<boolean> => assertType(name, _.isBoolean, 'boolean');
const assertNumber = (name: string): AssertionFn<number> => assertType(name, _.isNumber, 'number');
const assertPlainObject = (name: string): AssertionFn<Record<string, unknown>> => assertType(name, isPlainObject, 'plain object');

const assertSaveFormat = (saveFormat: unknown): asserts saveFormat is SaveFormat => {
const formats = Object.values(SaveFormat);
Expand Down Expand Up @@ -219,6 +224,38 @@ const getParser = (): ReturnType<typeof root<ReporterConfig>> => {
parseEnv: JSON.parse,
parseCli: JSON.parse,
validate: assertArrayOf('plugin descriptions', 'plugins', assertPluginDescription)
}),
staticImageAccepter: section({
enabled: option({
defaultValue: configDefaults.staticImageAccepter.enabled,
parseEnv: JSON.parse,
parseCli: JSON.parse,
validate: assertBoolean('staticImageAccepter.enabled')
}),
repositoryUrl: option({
defaultValue: configDefaults.staticImageAccepter.repositoryUrl,
validate: assertString('staticImageAccepter.repositoryUrl')
}),
pullRequestUrl: option({
defaultValue: configDefaults.staticImageAccepter.pullRequestUrl,
validate: assertString('staticImageAccepter.pullRequestUrl')
}),
serviceUrl: option({
defaultValue: configDefaults.staticImageAccepter.serviceUrl,
validate: assertString('staticImageAccepter.serviceUrl')
}),
meta: option({
defaultValue: configDefaults.staticImageAccepter.meta,
parseEnv: JSON.parse,
parseCli: JSON.parse,
validate: assertPlainObject('staticImageAccepter.meta')
}),
axiosRequestOptions: option({
defaultValue: configDefaults.staticImageAccepter.axiosRequestOptions,
parseEnv: JSON.parse,
parseCli: JSON.parse,
validate: assertPlainObject('staticImageAccepter.axiosRequestOptions')
})
})
}), {envPrefix: ENV_PREFIX, cliPrefix: CLI_PREFIX});
};
Expand Down
8 changes: 8 additions & 0 deletions lib/constants/defaults.ts
Original file line number Diff line number Diff line change
Expand Up @@ -23,5 +23,13 @@ export const configDefaults: ReporterConfig = {
saveFormat: SaveFormat.SQLITE,
yandexMetrika: {
counterNumber: null
},
staticImageAccepter: {
enabled: false,
repositoryUrl: '',
pullRequestUrl: '',
serviceUrl: '',
meta: {},
axiosRequestOptions: {}
}
};
16 changes: 16 additions & 0 deletions lib/constants/test-statuses.ts
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,14 @@ export enum TestStatus {
ERROR = 'error',
SKIPPED = 'skipped',
UPDATED = 'updated',
/**
* @note used by staticImageAccepter only
*/
STAGED = 'staged',
/**
* @note used by staticImageAccepter only
*/
COMMITED = 'commited',
}

export const IDLE = TestStatus.IDLE;
Expand All @@ -17,3 +25,11 @@ export const FAIL = TestStatus.FAIL;
export const ERROR = TestStatus.ERROR;
export const SKIPPED = TestStatus.SKIPPED;
export const UPDATED = TestStatus.UPDATED;
/**
* @note used by staticImageAccepter only
*/
export const STAGED = TestStatus.STAGED;
/**
* @note used by staticImageAccepter only
*/
export const COMMITED = TestStatus.COMMITED;
2 changes: 2 additions & 0 deletions lib/constants/view-modes.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,4 +4,6 @@ export enum ViewMode {
FAILED = 'failed',
RETRIED = 'retried',
SKIPPED = 'skipped',
STAGED = 'staged',
COMMITED = 'commited',
}
3 changes: 2 additions & 1 deletion lib/server-utils.ts
Original file line number Diff line number Diff line change
Expand Up @@ -221,7 +221,8 @@ export function getConfigForStaticFile(pluginConfig: ReporterConfig): ConfigForS
'customScripts',
'yandexMetrika',
'pluginsEnabled',
'plugins'
'plugins',
'staticImageAccepter'
]);
}

Expand Down
14 changes: 11 additions & 3 deletions lib/static/components/controls/common-controls.jsx
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
import React, {Component} from 'react';
import {bindActionCreators} from 'redux';
import {connect} from 'react-redux';
import {capitalize} from 'lodash';
import {capitalize, pull} from 'lodash';
import * as actions from '../../modules/actions';
import ControlButton from './control-button';
import ControlSelect from './selects/control';
Expand All @@ -14,6 +14,14 @@ import {DiffModes} from '../../../constants/diff-modes';
import {EXPAND_ALL, COLLAPSE_ALL, EXPAND_ERRORS, EXPAND_RETRIES} from '../../../constants/expand-modes';

class ControlButtons extends Component {
getShowTestsOptions() {
const viewModes = Object.values(ViewMode).map(value => ({value, text: capitalize(value)}));

return this.props.isStatisImageAccepterEnabled
? viewModes
: viewModes.filter(viewMode => ![ViewMode.STAGED, ViewMode.COMMITED].includes(viewMode.value));
}

render() {
const {actions, view} = this.props;

Expand All @@ -23,7 +31,7 @@ class ControlButtons extends Component {
label="Show tests"
value={view.viewMode}
handler={actions.changeViewMode}
options = {Object.values(ViewMode).map((value) => ({value, text: capitalize(value)}))}
options = {this.getShowTestsOptions()}
/>
<div className="control-group">
<ControlButton
Expand Down Expand Up @@ -70,6 +78,6 @@ class ControlButtons extends Component {
}

export default connect(
({view}) => ({view}),
({view, staticImageAccepter: {enabled}}) => ({view, isStatisImageAccepterEnabled: enabled}),
(dispatch) => ({actions: bindActionCreators(actions, dispatch)})
)(ControlButtons);
63 changes: 49 additions & 14 deletions lib/static/components/controls/common-filters.jsx
Original file line number Diff line number Diff line change
@@ -1,34 +1,69 @@
'use strict';

import React, {Component} from 'react';
import React from 'react';
import {bindActionCreators} from 'redux';
import {connect} from 'react-redux';
import * as actions from '../../modules/actions';
import TestNameFilterInput from './test-name-filter-input';
import StrictMatchFilterInput from './strict-match-filter-input';
import ShowCheckboxesInput from './show-checkboxes-input';
import BrowserList from './browser-list';
import ProgressBar from '../progress-bar';
import ControlButton from './control-button';

class CommonFilters extends Component {
render() {
const {filteredBrowsers, browsers, gui, actions} = this.props;
const CommonFilters = (props) => {
const onCommitChanges = () => {
props.actions.staticAccepterOpenConfirm();
}

const renderStaticImageAccepterControls = () => {
const {staticImageAccepter} = props;

if (!staticImageAccepter.enabled) {
return null;
}

return (
<div className="control-container control-filters">
<BrowserList
available={browsers}
selected={filteredBrowsers}
onChange={actions.selectBrowsers}
<div className='static-image-accepter'>
<ControlButton
label={`Commit ${staticImageAccepter.imagesToCommitCount} images`}
title="Send request with imagesInfo to 'staticImageAccepter.serviceUrl'"
isActive={staticImageAccepter.imagesToCommitCount > 0}
isDisabled={staticImageAccepter.imagesToCommitCount === 0}
isSuiteControl={true}
extendClassNames="static-image-accepter-commit"
handler={onCommitChanges}
/>
<ProgressBar
done={staticImageAccepter.acceptedImagesCount}
total={staticImageAccepter.totalAcceptableImagesCount}
dataTestId="static-image-accepter-progress-bar"
/>
<TestNameFilterInput/>
<StrictMatchFilterInput/>
{gui && <ShowCheckboxesInput/>}
</div>
);
)
}

return (
<div className="control-container control-filters">
<BrowserList
available={props.browsers}
selected={props.filteredBrowsers}
onChange={props.actions.selectBrowsers}
/>
<TestNameFilterInput/>
<StrictMatchFilterInput/>
{props.gui && <ShowCheckboxesInput/>}
{renderStaticImageAccepterControls()}
</div>
);
}

export default connect(
({view, browsers, gui}) => ({filteredBrowsers: view.filteredBrowsers, browsers, gui}),
({view, browsers, gui, staticImageAccepter}) => ({
filteredBrowsers: view.filteredBrowsers,
browsers,
gui,
staticImageAccepter,
}),
(dispatch) => ({actions: bindActionCreators(actions, dispatch)})
)(CommonFilters);
4 changes: 3 additions & 1 deletion lib/static/components/modals/index.js
Original file line number Diff line number Diff line change
@@ -1,6 +1,8 @@
export const types = {
FIND_SAME_DIFFS: 'FindSameDiffs',
SCREENSHOT_ACCEPTER: 'ScreenshotAccepter'
SCREENSHOT_ACCEPTER: 'ScreenshotAccepter',
STATIC_ACCEPTER_CONFIRM: 'StaticAccepterConfirm'
};
export {default as FindSameDiffs} from './find-same-diffs';
export {default as ScreenshotAccepter} from './screenshot-accepter';
export {default as StaticAccepterConfirm} from './static-accepter-confirm';
29 changes: 25 additions & 4 deletions lib/static/components/modals/screenshot-accepter/header.jsx
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@ import ControlButton from '../../controls/control-button';
import ControlSelect from '../../controls/selects/control';
import RetrySwitcher from '../../retry-switcher';
import {DiffModes} from '../../../../constants/diff-modes';
import {staticImageAccepterPropType} from "../../../modules/static-image-accepter";

export default class ScreenshotAccepterHeader extends Component {
static propTypes = {
Expand All @@ -28,7 +29,9 @@ export default class ScreenshotAccepterHeader extends Component {
onActiveImageChange: PropTypes.func.isRequired,
onScreenshotAccept: PropTypes.func.isRequired,
onScreenshotUndo: PropTypes.func.isRequired,
onShowMeta: PropTypes.func.isRequired
onShowMeta: PropTypes.func.isRequired,
onCommitChanges: PropTypes.func.isRequired,
staticImageAccepter: staticImageAccepterPropType
};

constructor(props) {
Expand Down Expand Up @@ -96,6 +99,11 @@ export default class ScreenshotAccepterHeader extends Component {
event.preventDefault();

const {images, retryIndex, onScreenshotAccept} = this.props;

if (retryIndex === null) {
return;
}

const imageId = images[retryIndex].id;

onScreenshotAccept(imageId);
Expand All @@ -109,11 +117,16 @@ export default class ScreenshotAccepterHeader extends Component {

render() {
const {actions, view, images, stateNameImageIds, retryIndex,
showMeta, onClose, onRetryChange, onShowMeta,
totalImages, acceptedImages
showMeta, onClose, onRetryChange, onShowMeta, onCommitChanges,
totalImages, acceptedImages, staticImageAccepter
} = this.props;
const resultIds = uniqBy(images, 'id').map((image) => image.parentId);
const isArrowControlDisabed = stateNameImageIds.length <= 1;
const staticAccepterDelayedImages = staticImageAccepter.accepterDelayedImages.length;
const imagesToCommitCount = staticImageAccepter.imagesToCommitCount + staticAccepterDelayedImages;
const isUndoEnabled = staticImageAccepter.enabled
? Boolean(staticImageAccepter.accepterDelayedImages.length)
: Boolean(acceptedImages);

return (
<Fragment>
Expand Down Expand Up @@ -148,7 +161,7 @@ export default class ScreenshotAccepterHeader extends Component {
<ControlButton
label="⎌ Undo"
title="Revert last updated screenshot"
isDisabled={!acceptedImages}
isDisabled={!isUndoEnabled}
isSuiteControl={true}
extendClassNames="screenshot-accepter__undo-btn"
handler={this.handleScreenUndo}
Expand Down Expand Up @@ -189,6 +202,14 @@ export default class ScreenshotAccepterHeader extends Component {
handler={onClose}
dataTestId="screenshot-accepter-switch-accept-mode"
/>
{staticImageAccepter.enabled && <ControlButton
label={`Commit ${imagesToCommitCount} images`}
title="Send request with imagesInfo to 'staticImageAccepter.serviceUrl'"
isActive={imagesToCommitCount > 0}
isDisabled={imagesToCommitCount === 0}
isSuiteControl={true}
handler={onCommitChanges}
/>}
<ProgressBar done={acceptedImages} total={totalImages} dataTestId="screenshot-accepter-progress-bar"/>
</div>
</header>
Expand Down
Loading

0 comments on commit cdd8646

Please sign in to comment.