diff --git a/.github/workflows/eslint.yml b/.github/workflows/eslint.yml index 7bd1683a..6dadc8c8 100644 --- a/.github/workflows/eslint.yml +++ b/.github/workflows/eslint.yml @@ -26,7 +26,7 @@ jobs: done if [[ ! -z $CHANGED_FILES ]]; then - yarn install --frozen-lockfile + yarn install --frozen-lockfile && cd tests && yarn install --frozen-lockfile && cd .. yarn add eslint-detailed-reporter -D -W mkdir -p eslint_report diff --git a/CHANGELOG.md b/CHANGELOG.md index 9a350e59..e6bcbe8b 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -23,6 +23,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 - Bumped nuclio version to 1.8.14 - Simplified running REST API tests. Extended CI-nightly workflow - REST API tests are partially moved to Python SDK (`users`, `projects`, `tasks`) +- cvat-ui: Improve UI/UX on label, create task and create project forms () - Removed link to OpenVINO documentation () - Clarified meaning of chunking for videos diff --git a/cvat-ui/package.json b/cvat-ui/package.json index 632e8ace..b5aaddf3 100644 --- a/cvat-ui/package.json +++ b/cvat-ui/package.json @@ -1,6 +1,6 @@ { "name": "cvat-ui", - "version": "1.39.1", + "version": "1.40.0", "description": "CVAT single-page application", "main": "src/index.tsx", "scripts": { diff --git a/cvat-ui/src/actions/projects-actions.ts b/cvat-ui/src/actions/projects-actions.ts index 0af733cc..198c2044 100644 --- a/cvat-ui/src/actions/projects-actions.ts +++ b/cvat-ui/src/actions/projects-actions.ts @@ -89,7 +89,7 @@ export function getProjectTasksAsync(tasksQuery: Partial = {}): Thun getState().projects.gettingQuery, tasksQuery, )); - const query: Partial = { + const query: TasksQuery = { ...state.projects.tasksGettingQuery, ...tasksQuery, }; @@ -149,8 +149,10 @@ export function createProjectAsync(data: any): ThunkAction { try { const savedProject = await projectInstance.save(); dispatch(projectActions.createProjectSuccess(savedProject.id)); + return savedProject; } catch (error) { dispatch(projectActions.createProjectFailed(error)); + throw error; } }; } diff --git a/cvat-ui/src/actions/tasks-actions.ts b/cvat-ui/src/actions/tasks-actions.ts index 98ae49d2..bd86479a 100644 --- a/cvat-ui/src/actions/tasks-actions.ts +++ b/cvat-ui/src/actions/tasks-actions.ts @@ -345,7 +345,7 @@ function createTaskUpdateStatus(status: string): AnyAction { } export function createTaskAsync(data: any): ThunkAction, {}, {}, AnyAction> { - return async (dispatch: ActionCreator): Promise => { + return async (dispatch: ActionCreator): Promise => { const description: any = { name: data.basic.name, labels: data.labels, @@ -417,8 +417,10 @@ export function createTaskAsync(data: any): ThunkAction, {}, {}, A dispatch(createTaskUpdateStatus(status + (progress !== null ? ` ${Math.floor(progress * 100)}%` : ''))); }); dispatch(createTaskSuccess(savedTask.id)); + return savedTask; } catch (error) { dispatch(createTaskFailed(error)); + throw error; } }; } diff --git a/cvat-ui/src/components/create-project-page/create-project-content.tsx b/cvat-ui/src/components/create-project-page/create-project-content.tsx index a119f7ed..accfc09a 100644 --- a/cvat-ui/src/components/create-project-page/create-project-content.tsx +++ b/cvat-ui/src/components/create-project-page/create-project-content.tsx @@ -3,9 +3,9 @@ // SPDX-License-Identifier: MIT import React, { - RefObject, useContext, useEffect, useRef, useState, + RefObject, useContext, useRef, useState, useEffect, } from 'react'; -import { useDispatch, useSelector } from 'react-redux'; +import { useDispatch } from 'react-redux'; import { useHistory } from 'react-router'; import Switch from 'antd/lib/switch'; import Select from 'antd/lib/select'; @@ -17,14 +17,16 @@ import Input from 'antd/lib/input'; import notification from 'antd/lib/notification'; import patterns from 'utils/validation-patterns'; -import { CombinedState } from 'reducers/interfaces'; import LabelsEditor from 'components/labels-editor/labels-editor'; import { createProjectAsync } from 'actions/projects-actions'; import CreateProjectContext from './create-project.context'; const { Option } = Select; -function NameConfigurationForm({ formRef }: { formRef: RefObject }): JSX.Element { +function NameConfigurationForm( + { formRef, inputRef }: + { formRef: RefObject, inputRef: RefObject }, +):JSX.Element { return (
} }, ]} > - +
); @@ -50,7 +52,7 @@ function AdaptiveAutoAnnotationForm({ formRef }: { formRef: RefObject - projectClass.set?.(v)}> @@ -60,7 +62,7 @@ function AdaptiveAutoAnnotationForm({ formRef }: { formRef: RefObject trainingEnabled.set(!trainingEnabled.value)} + onClick={() => trainingEnabled.set?.(!trainingEnabled.value)} /> @@ -125,64 +127,79 @@ function AdvancedConfigurationForm({ formRef }: { formRef: RefObject([]); - const shouldShowNotification = useRef(false); const nameFormRef = useRef(null); + const nameInputRef = useRef(null); const adaptiveAutoAnnotationFormRef = useRef(null); const advancedFormRef = useRef(null); const dispatch = useDispatch(); const history = useHistory(); - const newProjectId = useSelector((state: CombinedState) => state.projects.activities.creates.id); - const { isTrainingActive } = useContext(CreateProjectContext); - useEffect(() => { - if (Number.isInteger(newProjectId) && shouldShowNotification.current) { - const btn = ; + const resetForm = (): void => { + if (nameFormRef.current) nameFormRef.current.resetFields(); + if (advancedFormRef.current) advancedFormRef.current.resetFields(); + setProjectLabels([]); + }; + + const focusForm = (): void => { + nameInputRef.current?.focus(); + }; + + const sumbit = async (): Promise => { + try { + let projectData: Record = {}; + if (nameFormRef.current && advancedFormRef.current) { + const basicValues = await nameFormRef.current.validateFields(); + const advancedValues = await advancedFormRef.current.validateFields(); + const adaptiveAutoAnnotationValues = await adaptiveAutoAnnotationFormRef.current?.validateFields(); + projectData = { + ...projectData, + ...advancedValues, + name: basicValues.name, + }; + + if (adaptiveAutoAnnotationValues) { + projectData.training_project = { ...adaptiveAutoAnnotationValues }; + } + } + + projectData.labels = projectLabels; + + const createdProject = await dispatch(createProjectAsync(projectData)); + return createdProject; + } catch { + return false; + } + }; - // Clear new project forms - if (nameFormRef.current) nameFormRef.current.resetFields(); - if (advancedFormRef.current) advancedFormRef.current.resetFields(); - setProjectLabels([]); + const onSubmitAndOpen = async (): Promise => { + const createdProject = await sumbit(); + if (createdProject) { + history.push(`/projects/${createdProject.id}`); + } + }; + const onSubmitAndContinue = async (): Promise => { + const res = await sumbit(); + if (res) { + resetForm(); notification.info({ message: 'The project has been created', - btn, className: 'cvat-notification-create-project-success', }); + focusForm(); } - - shouldShowNotification.current = true; - }, [newProjectId]); - - const onSumbit = async (): Promise => { - let projectData: Record = {}; - if (nameFormRef.current && advancedFormRef.current) { - const basicValues = await nameFormRef.current.validateFields(); - const advancedValues = await advancedFormRef.current.validateFields(); - const adaptiveAutoAnnotationValues = await adaptiveAutoAnnotationFormRef.current?.validateFields(); - projectData = { - ...projectData, - ...advancedValues, - name: basicValues.name, - }; - - if (adaptiveAutoAnnotationValues) { - projectData.training_project = { ...adaptiveAutoAnnotationValues }; - } - } - - projectData.labels = projectLabels; - - if (!projectData.name) return; - - dispatch(createProjectAsync(projectData)); }; + useEffect(() => { + focusForm(); + }, []); + return ( - + {isTrainingActive.value && ( @@ -202,9 +219,18 @@ export default function CreateProjectContent(): JSX.Element { - + + + + + + + + ); diff --git a/cvat-ui/src/components/create-task-page/basic-configuration-form.tsx b/cvat-ui/src/components/create-task-page/basic-configuration-form.tsx index 656597e8..a30f8b6a 100644 --- a/cvat-ui/src/components/create-task-page/basic-configuration-form.tsx +++ b/cvat-ui/src/components/create-task-page/basic-configuration-form.tsx @@ -17,10 +17,12 @@ interface Props { export default class BasicConfigurationForm extends React.PureComponent { private formRef: RefObject; + private inputRef: RefObject; public constructor(props: Props) { super(props); this.formRef = React.createRef(); + this.inputRef = React.createRef(); } public submit(): Promise { @@ -41,6 +43,12 @@ export default class BasicConfigurationForm extends React.PureComponent { } } + public focus(): void { + if (this.inputRef.current) { + this.inputRef.current.focus(); + } + } + public render(): JSX.Element { return (
@@ -55,7 +63,7 @@ export default class BasicConfigurationForm extends React.PureComponent { }, ]} > - + ); diff --git a/cvat-ui/src/components/create-task-page/create-task-content.tsx b/cvat-ui/src/components/create-task-page/create-task-content.tsx index 1aeebde6..49cc63f8 100644 --- a/cvat-ui/src/components/create-task-page/create-task-content.tsx +++ b/cvat-ui/src/components/create-task-page/create-task-content.tsx @@ -85,31 +85,21 @@ class CreateTaskContent extends React.PureComponent history.push(`/tasks/${taskId}`)}>Open task; - notification.info({ - message: 'The task has been created', - btn, - className: 'cvat-notification-create-task-success', - }); + this.focusToForm(); + } - this.basicConfigurationComponent.current?.resetFields(); - this.advancedConfigurationComponent.current?.resetFields(); + private resetState = (): void => { + this.basicConfigurationComponent.current?.resetFields(); + this.advancedConfigurationComponent.current?.resetFields(); - this.fileManagerContainer.reset(); + this.fileManagerContainer.reset(); - this.setState((state) => ({ - ...defaultState, - projectId: state.projectId, - })); - } - } + this.setState((state) => ({ + ...defaultState, + projectId: state.projectId, + })); + }; private validateLabelsOrProject = (): boolean => { const { projectId, labels } = this.state; @@ -170,13 +160,42 @@ class CreateTaskContent extends React.PureComponent { + private focusToForm = (): void => { + this.basicConfigurationComponent.current?.focus(); + }; + + private handleSubmitAndOpen = (): void => { + const { history } = this.props; + + this.handleSubmit() + .then((createdTask) => { + const { id } = createdTask; + history.push(`/tasks/${id}`); + }) + .catch(() => {}); + }; + + private handleSubmitAndContinue = (): void => { + this.handleSubmit() + .then(() => { + notification.info({ + message: 'The task has been created', + className: 'cvat-notification-create-task-success', + }); + }) + .then(this.resetState) + .then(this.focusToForm) + .catch(() => {}); + }; + + private handleSubmit = (): Promise => new Promise((resolve, reject) => { if (!this.validateLabelsOrProject()) { notification.error({ message: 'Could not create a task', description: 'A task must contain at least one label or belong to some project', className: 'cvat-notification-create-task-fail', }); + reject(); return; } @@ -186,35 +205,43 @@ class CreateTaskContent extends React.PureComponent { - if (this.advancedConfigurationComponent.current) { - return this.advancedConfigurationComponent.current.submit(); - } - return Promise.resolve(); - }) - .then((): void => { - const { onCreate } = this.props; - onCreate(this.state); - }) - .catch((error: Error | ValidateErrorEntity): void => { - notification.error({ - message: 'Could not create a task', - description: (error as ValidateErrorEntity).errorFields ? - (error as ValidateErrorEntity).errorFields - .map((field) => `${field.name} : ${field.errors.join(';')}`) - .map((text: string): JSX.Element =>
{text}
) : - error.toString(), - className: 'cvat-notification-create-task-fail', - }); - }); + if (!this.basicConfigurationComponent.current) { + reject(); + return; } - }; + + this.basicConfigurationComponent.current + .submit() + .then(() => { + if (this.advancedConfigurationComponent.current) { + return this.advancedConfigurationComponent.current.submit(); + } + return Promise.resolve(); + }) + .then((): void => { + const { onCreate } = this.props; + return onCreate(this.state); + }) + .then((cratedTask) => { + resolve(cratedTask); + }) + .catch((error: Error | ValidateErrorEntity): void => { + notification.error({ + message: 'Could not create a task', + description: (error as ValidateErrorEntity).errorFields ? + (error as ValidateErrorEntity).errorFields + .map((field) => `${field.name} : ${field.errors.join(';')}`) + .map((text: string): JSX.Element =>
{text}
) : + error.toString(), + className: 'cvat-notification-create-task-fail', + }); + reject(error); + }); + }); private renderBasicBlock(): JSX.Element { return ( @@ -332,6 +359,23 @@ class CreateTaskContent extends React.PureComponent + + + + + + + + ); + } + public render(): JSX.Element { const { status } = this.props; const loading = !!status && status !== 'CREATED' && status !== 'FAILED'; @@ -349,11 +393,8 @@ class CreateTaskContent extends React.PureComponent{loading ? : null} - - + + {loading ? : this.renderActions()} ); diff --git a/cvat-ui/src/components/create-task-page/styles.scss b/cvat-ui/src/components/create-task-page/styles.scss index 6d14a5eb..91b728f6 100644 --- a/cvat-ui/src/components/create-task-page/styles.scss +++ b/cvat-ui/src/components/create-task-page/styles.scss @@ -30,11 +30,6 @@ margin-top: 10px; } - .cvat-create-task-submit-section > button { - float: right; - width: 120px; - } - .cvat-project-search-field { width: 100%; } diff --git a/cvat-ui/src/components/labels-editor/constructor-creator.tsx b/cvat-ui/src/components/labels-editor/constructor-creator.tsx index 6c083269..16b57cb0 100644 --- a/cvat-ui/src/components/labels-editor/constructor-creator.tsx +++ b/cvat-ui/src/components/labels-editor/constructor-creator.tsx @@ -9,7 +9,8 @@ import { Label } from './common'; interface Props { labelNames: string[]; - onCreate: (label: Label | null) => void; + onCreate: (label: Label) => void; + onCancel: () => void; } function compareProps(prevProps: Props, nextProps: Props): boolean { @@ -30,10 +31,10 @@ function compareProps(prevProps: Props, nextProps: Props): boolean { } function ConstructorCreator(props: Props): JSX.Element { - const { onCreate, labelNames } = props; + const { onCreate, onCancel, labelNames } = props; return (
- +
); } diff --git a/cvat-ui/src/components/labels-editor/constructor-updater.tsx b/cvat-ui/src/components/labels-editor/constructor-updater.tsx index 5ecfd299..1b8fc6a8 100644 --- a/cvat-ui/src/components/labels-editor/constructor-updater.tsx +++ b/cvat-ui/src/components/labels-editor/constructor-updater.tsx @@ -9,15 +9,16 @@ import { Label } from './common'; interface Props { label: Label; - onUpdate: (label: Label | null) => void; + onUpdate: (label: Label) => void; + onCancel: () => void; } export default function ConstructorUpdater(props: Props): JSX.Element { - const { label, onUpdate } = props; + const { label, onUpdate, onCancel } = props; return (
- +
); } diff --git a/cvat-ui/src/components/labels-editor/constructor-viewer.tsx b/cvat-ui/src/components/labels-editor/constructor-viewer.tsx index c3db3cfa..03425011 100644 --- a/cvat-ui/src/components/labels-editor/constructor-viewer.tsx +++ b/cvat-ui/src/components/labels-editor/constructor-viewer.tsx @@ -17,18 +17,20 @@ interface ConstructorViewerProps { } export default function ConstructorViewer(props: ConstructorViewerProps): JSX.Element { - const { onCreate } = props; + const { + onCreate, labels, onUpdate, onDelete, + } = props; const list = [ , ]; - for (const label of props.labels) { + for (const label of labels) { list.push( void; + onSubmit: (label: Label) => void; + onCancel: () => void; } export default class LabelForm extends React.Component { - private continueAfterSubmit: boolean; private formRef: RefObject; + private inputNameRef: RefObject; constructor(props: Props) { super(props); - this.continueAfterSubmit = false; this.formRef = React.createRef(); + this.inputNameRef = React.createRef(); } + private focus = (): void => { + this.inputNameRef.current?.focus({ + cursor: 'end', + }); + }; + private handleSubmit = (values: Store): void => { - const { label, onSubmit } = this.props; + const { label, onSubmit, onCancel } = this.props; + + if (!values.name) { + onCancel(); + return; + } onSubmit({ name: values.name, @@ -76,10 +88,10 @@ export default class LabelForm extends React.Component { // resetFields does not remove existed attributes this.formRef.current.setFieldsValue({ attributes: undefined }); this.formRef.current.resetFields(); - } - if (!this.continueAfterSubmit) { - onSubmit(null); + if (!label) { + this.focus(); + } } }; @@ -380,7 +392,7 @@ export default class LabelForm extends React.Component { }; private renderLabelNameInput(): JSX.Element { - const { label, labelNames } = this.props; + const { label, labelNames, onCancel } = this.props; const value = label ? label.name : ''; return ( @@ -390,7 +402,7 @@ export default class LabelForm extends React.Component { initialValue={value} rules={[ { - required: true, + required: !!label, message: 'Please specify a name', }, { @@ -407,7 +419,16 @@ export default class LabelForm extends React.Component { }, ]} > - + { + if (event.key === 'Escape' || event.key === 'Esc' || event.keyCode === 27) { + onCancel(); + } + }} + autoComplete='off' + /> ); } @@ -423,45 +444,26 @@ export default class LabelForm extends React.Component { ); } - private renderDoneButton(): JSX.Element { - return ( - - - - ); - } - - private renderContinueButton(): JSX.Element | null { + private renderSaveButton(): JSX.Element { const { label } = this.props; + const tooltipTitle = label ? 'Save the label and return' : 'Save the label and create one more'; + const buttonText = label ? 'Done' : 'Continue'; - if (label) return null; return ( - + ); } private renderCancelButton(): JSX.Element { - const { onSubmit } = this.props; + const { onCancel } = this.props; return ( @@ -470,7 +472,7 @@ export default class LabelForm extends React.Component { danger style={{ width: '150px' }} onClick={(): void => { - onSubmit(null); + onCancel(); }} > Cancel @@ -526,6 +528,8 @@ export default class LabelForm extends React.Component { this.formRef.current.setFieldsValue({ attributes: convertedAttributes }); } + + this.focus(); } public render(): JSX.Element { @@ -546,8 +550,7 @@ export default class LabelForm extends React.Component { - {this.renderDoneButton()} - {this.renderContinueButton()} + {this.renderSaveButton()} {this.renderCancelButton()} diff --git a/cvat-ui/src/components/labels-editor/labels-editor.tsx b/cvat-ui/src/components/labels-editor/labels-editor.tsx index 3ccda668..93917da7 100644 --- a/cvat-ui/src/components/labels-editor/labels-editor.tsx +++ b/cvat-ui/src/components/labels-editor/labels-editor.tsx @@ -98,48 +98,45 @@ export default class LabelsEditor extends React.PureComponent { - if (label === null) { - this.setState({ constructorMode: ConstructorMode.SHOW }); - } else { - const { unsavedLabels, savedLabels } = this.state; - const newUnsavedLabels = [ - ...unsavedLabels, - { - ...label, - id: idGenerator(), - }, - ]; + private handleCreate = (label: Label): void => { + const { unsavedLabels, savedLabels } = this.state; + const newUnsavedLabels = [ + ...unsavedLabels, + { + ...label, + id: idGenerator(), + }, + ]; - this.setState({ unsavedLabels: newUnsavedLabels }); - this.handleSubmit(savedLabels, newUnsavedLabels); - } + this.setState({ unsavedLabels: newUnsavedLabels }); + this.handleSubmit(savedLabels, newUnsavedLabels); }; - private handleUpdate = (label: Label | null): void => { + private handleUpdate = (label: Label): void => { const { savedLabels, unsavedLabels } = this.state; - if (label) { - const filteredSavedLabels = savedLabels.filter((_label: Label) => _label.id !== label.id); - const filteredUnsavedLabels = unsavedLabels.filter((_label: Label) => _label.id !== label.id); - if (label.id >= 0) { - filteredSavedLabels.push(label); - this.setState({ - savedLabels: filteredSavedLabels, - constructorMode: ConstructorMode.SHOW, - }); - } else { - filteredUnsavedLabels.push(label); - this.setState({ - unsavedLabels: filteredUnsavedLabels, - constructorMode: ConstructorMode.SHOW, - }); - } - - this.handleSubmit(filteredSavedLabels, filteredUnsavedLabels); + const filteredSavedLabels = savedLabels.filter((_label: Label) => _label.id !== label.id); + const filteredUnsavedLabels = unsavedLabels.filter((_label: Label) => _label.id !== label.id); + if (label.id >= 0) { + filteredSavedLabels.push(label); + this.setState({ + savedLabels: filteredSavedLabels, + constructorMode: ConstructorMode.SHOW, + }); } else { - this.setState({ constructorMode: ConstructorMode.SHOW }); + filteredUnsavedLabels.push(label); + this.setState({ + unsavedLabels: filteredUnsavedLabels, + constructorMode: ConstructorMode.SHOW, + }); } + + this.handleSubmit(filteredSavedLabels, filteredUnsavedLabels); + this.setState({ constructorMode: ConstructorMode.SHOW }); + }; + + private handlerCancel = (): void => { + this.setState({ constructorMode: ConstructorMode.SHOW }); }; private handleDelete = (label: Label): void => { @@ -198,6 +195,7 @@ export default class LabelsEditor extends React.PureComponent - + {constructorMode === ConstructorMode.SHOW && ( { this.setState({ constructorMode: ConstructorMode.UPDATE, @@ -244,10 +242,18 @@ export default class LabelsEditor extends React.PureComponent )} {constructorMode === ConstructorMode.UPDATE && labelForUpdate !== null && ( - + )} {constructorMode === ConstructorMode.CREATE && ( - l.name)} onCreate={this.handleCreate} /> + l.name)} + onCreate={this.handleCreate} + onCancel={this.handlerCancel} + /> )} diff --git a/cvat-ui/src/containers/create-task-page/create-task-page.tsx b/cvat-ui/src/containers/create-task-page/create-task-page.tsx index 872cdf15..f1bf23de 100644 --- a/cvat-ui/src/containers/create-task-page/create-task-page.tsx +++ b/cvat-ui/src/containers/create-task-page/create-task-page.tsx @@ -23,7 +23,7 @@ interface DispatchToProps { function mapDispatchToProps(dispatch: any): DispatchToProps { return { - onCreate: (data: CreateTaskData): void => dispatch(createTaskAsync(data)), + onCreate: (data: CreateTaskData): Promise => dispatch(createTaskAsync(data)), }; } diff --git a/tests/cypress/integration/actions_projects_models/case_116_creating_project_by_inserting_labels_from_task.js b/tests/cypress/integration/actions_projects_models/case_116_creating_project_by_inserting_labels_from_task.js index dfb7b194..bab4e543 100644 --- a/tests/cypress/integration/actions_projects_models/case_116_creating_project_by_inserting_labels_from_task.js +++ b/tests/cypress/integration/actions_projects_models/case_116_creating_project_by_inserting_labels_from_task.js @@ -71,7 +71,7 @@ context('Creating a project by inserting labels from a task.', { browser: '!fire cy.contains('button', 'Done').click(); cy.contains('[role="tab"]', 'Constructor').click(); cy.contains('.cvat-constructor-viewer-item', task.label).should('exist'); - cy.contains('button', 'Submit').click(); + cy.contains('button', 'Submit & Continue').click(); cy.get('.cvat-notification-create-project-success').should('exist').find('[data-icon="close"]').click(); cy.goToProjectsList(); cy.openProject(projectName); diff --git a/tests/cypress/integration/actions_projects_models/issue_2900_creating_more_one_tasks_from_project_per_time.js b/tests/cypress/integration/actions_projects_models/issue_2900_creating_more_one_tasks_from_project_per_time.js index 2d055ea5..411ddad1 100644 --- a/tests/cypress/integration/actions_projects_models/issue_2900_creating_more_one_tasks_from_project_per_time.js +++ b/tests/cypress/integration/actions_projects_models/issue_2900_creating_more_one_tasks_from_project_per_time.js @@ -31,7 +31,7 @@ context('Create more than one task per time when create from project.', () => { }); cy.get('.cvat-constructor-viewer-new-item').should('not.exist'); cy.get('input[type="file"]').attachFile(archiveName, { subjectType: 'drag-n-drop' }); - cy.contains('button', 'Submit').click(); + cy.contains('button', 'Submit & Continue').click(); cy.get('.cvat-notification-create-task-success').should('exist'); cy.get('.cvat-notification-create-task-fail').should('not.exist'); } diff --git a/tests/cypress/integration/actions_tasks/case_61_create_task_set_issue_tracker.js b/tests/cypress/integration/actions_tasks/case_61_create_task_set_issue_tracker.js index b36ba4e1..ad96f0ef 100644 --- a/tests/cypress/integration/actions_tasks/case_61_create_task_set_issue_tracker.js +++ b/tests/cypress/integration/actions_tasks/case_61_create_task_set_issue_tracker.js @@ -44,7 +44,7 @@ context('Create a task with set an issue tracker.', () => { cy.contains('Advanced configuration').click(); cy.get('#bugTracker').type(incorrectBugTrackerUrl); cy.contains('URL is not a valid URL').should('exist'); - cy.get('.cvat-create-task-submit-section').click(); + cy.contains('button', 'Submit & Continue').click(); cy.get('.cvat-notification-create-task-fail').should('exist').and('be.visible'); cy.closeNotification('.cvat-notification-create-task-fail'); }); @@ -52,7 +52,7 @@ context('Create a task with set an issue tracker.', () => { it('Set correct issue tracker URL. The task created.', () => { cy.get('#bugTracker').clear().type(dummyBugTrackerUrl); cy.contains('URL is not a valid URL').should('not.exist'); - cy.get('.cvat-create-task-submit-section').click(); + cy.contains('button', 'Submit & Continue').click(); cy.get('.cvat-notification-create-task-fail').should('not.exist'); cy.get('.cvat-notification-create-task-success').should('exist').and('be.visible'); }); diff --git a/tests/cypress/integration/actions_tasks2/case_33_button_continue_label_editor.js b/tests/cypress/integration/actions_tasks2/case_33_button_continue_label_editor.js index fd34f2df..7792c5ff 100644 --- a/tests/cypress/integration/actions_tasks2/case_33_button_continue_label_editor.js +++ b/tests/cypress/integration/actions_tasks2/case_33_button_continue_label_editor.js @@ -35,8 +35,7 @@ context('Button "Continue" in label editor.', () => { cy.get('.cvat-constructor-viewer-new-item').click(); cy.get('.cvat-label-constructor-creator').within(() => { cy.contains('button', 'Continue').click(); - cy.contains('[role="alert"]', 'Please specify a name').should('be.visible'); - cy.contains('button', 'Cancel').click(); + cy.get('.cvat-label-constructor-creator').should('not.exist'); }); }); }); diff --git a/tests/cypress/integration/actions_tasks2/case_40_create_task_without_necessary_arguments.js b/tests/cypress/integration/actions_tasks2/case_40_create_task_without_necessary_arguments.js index 156e383c..652216dd 100644 --- a/tests/cypress/integration/actions_tasks2/case_40_create_task_without_necessary_arguments.js +++ b/tests/cypress/integration/actions_tasks2/case_40_create_task_without_necessary_arguments.js @@ -36,28 +36,28 @@ context('Try to create a task without necessary arguments.', () => { describe(`Testing "${labelName}"`, () => { it('Try to create a task without any fields. A task is not created.', () => { - cy.get('.cvat-create-task-submit-section').click(); + cy.contains('button', 'Submit & Continue').click(); cy.get('.cvat-notification-create-task-fail').should('exist'); cy.closeNotification('.cvat-notification-create-task-fail'); }); it('Input a task name. A task is not created.', () => { cy.get('[id="name"]').type(taskName); - cy.get('.cvat-create-task-submit-section').click(); + cy.contains('button', 'Submit & Continue').click(); cy.get('.cvat-notification-create-task-fail').should('exist'); cy.closeNotification('.cvat-notification-create-task-fail'); }); it('Input task labels. A task is not created.', () => { cy.addNewLabel(labelName); - cy.get('.cvat-create-task-submit-section').click(); + cy.contains('button', 'Submit & Continue').click(); cy.get('.cvat-notification-create-task-fail').should('exist'); cy.closeNotification('.cvat-notification-create-task-fail'); }); it('Add some files. A task created.', () => { cy.get('input[type="file"]').attachFile(archiveName, { subjectType: 'drag-n-drop' }); - cy.get('.cvat-create-task-submit-section').click(); + cy.contains('button', 'Submit & Continue').click(); cy.get('.cvat-notification-create-task-fail').should('not.exist'); cy.get('.cvat-notification-create-task-success').should('exist'); // Check that the interface is prepared for creating the next task. diff --git a/tests/cypress/integration/actions_tasks2/case_41_add_delete_label_attribute.js b/tests/cypress/integration/actions_tasks2/case_41_add_delete_label_attribute.js index d4184b17..c33d1ff5 100644 --- a/tests/cypress/integration/actions_tasks2/case_41_add_delete_label_attribute.js +++ b/tests/cypress/integration/actions_tasks2/case_41_add_delete_label_attribute.js @@ -45,7 +45,8 @@ context('Add/delete labels and attributes.', () => { cy.get('.cvat-attribute-type-input').click(); cy.get('.cvat-attribute-type-input-text').click(); cy.get('.cvat-attribute-values-input').type(textDefaultValue); - cy.contains('[type="submit"]', 'Done').click(); + cy.contains('[type="submit"]', 'Continue').click(); + cy.contains('[type="button"]', 'Cancel').click(); cy.get('.cvat-constructor-viewer-item').should('exist'); }); diff --git a/tests/cypress/integration/actions_tasks2/case_42_change_label_name_via_label_constructor.js b/tests/cypress/integration/actions_tasks2/case_42_change_label_name_via_label_constructor.js index 032bb3e6..c10b14c4 100644 --- a/tests/cypress/integration/actions_tasks2/case_42_change_label_name_via_label_constructor.js +++ b/tests/cypress/integration/actions_tasks2/case_42_change_label_name_via_label_constructor.js @@ -17,15 +17,18 @@ context('Changing a label name via label constructor.', () => { }); describe(`Testing case "${caseId}"`, () => { - it('Set empty label name. Press "Done" button. Alert exist.', () => { + it('Set empty label name. Press "Continue" button. Label name is not created. Label constructor is closed.', () => { cy.get('.cvat-constructor-viewer-new-item').click(); // Open label constructor - cy.contains('[type="submit"]', 'Done').click(); - cy.contains('[role="alert"]', 'Please specify a name').should('exist').and('be.visible'); + cy.contains('[type="submit"]', 'Continue').click(); + cy.get('.cvat-label-constructor-creator').should('not.exist'); + cy.get('.cvat-constructor-viewer').should('be.visible'); }); - it('Change label name to any other correct value. Press "Done" button. The label created.', () => { + it('Change label name to any other correct value. Press "Continue" button. The label created.', () => { + cy.get('.cvat-constructor-viewer-new-item').click(); // Open label constructor cy.get('[placeholder="Label name"]').type(firstLabelName); - cy.contains('[type="submit"]', 'Done').click({ force: true }); + cy.contains('[type="submit"]', 'Continue').click({ force: true }); + cy.contains('[type="button"]', 'Cancel').click(); // Close label constructor cy.get('.cvat-constructor-viewer-item').should('exist').and('have.text', firstLabelName); }); diff --git a/tests/cypress/integration/actions_tasks2/case_76_try_create_task_incorrect_dataset_repo.js b/tests/cypress/integration/actions_tasks2/case_76_try_create_task_incorrect_dataset_repo.js index 733b08c1..6c6827a1 100644 --- a/tests/cypress/integration/actions_tasks2/case_76_try_create_task_incorrect_dataset_repo.js +++ b/tests/cypress/integration/actions_tasks2/case_76_try_create_task_incorrect_dataset_repo.js @@ -45,7 +45,7 @@ context('Try to create a task with an incorrect dataset repository.', () => { it('Set dummy dataset repository.', () => { cy.get('#repository').type(incorrectDatasetRepoUrlHttps); - cy.get('.cvat-create-task-submit-section').click(); + cy.contains('button', 'Submit & Continue').click(); cy.get('.cvat-notification-notice-create-task-failed').should('exist'); cy.closeNotification('.cvat-notification-notice-create-task-failed'); cy.get('#repository').clear(); @@ -53,7 +53,7 @@ context('Try to create a task with an incorrect dataset repository.', () => { it('Set repository with missing access.', () => { cy.get('#repository').type(repositoryWithMissingAccess); - cy.get('.cvat-create-task-submit-section').click(); + cy.contains('button', 'Submit & Continue').click(); cy.get('.cvat-notification-notice-create-task-failed').should('exist'); cy.get('.cvat-create-task-clone-repository-fail').should('exist'); }); diff --git a/tests/cypress/integration/actions_tasks3/case_107_connected_file_share.js b/tests/cypress/integration/actions_tasks3/case_107_connected_file_share.js index 11248dbe..e369dc59 100644 --- a/tests/cypress/integration/actions_tasks3/case_107_connected_file_share.js +++ b/tests/cypress/integration/actions_tasks3/case_107_connected_file_share.js @@ -34,9 +34,7 @@ context('Connected file share.', () => { }); }); }); - cy.contains('button', 'Submit').click(); - cy.get('.cvat-notification-create-task-success').should('exist').find('button').click(); - cy.get('.cvat-notification-create-task-success').should('exist').find('[data-icon="close"]').click(); + cy.contains('button', 'Submit & Open').click(); cy.get('.cvat-task-details').should('exist'); } diff --git a/tests/cypress/integration/actions_tasks3/case_46_create_task_with_files_from_remote_sources.js b/tests/cypress/integration/actions_tasks3/case_46_create_task_with_files_from_remote_sources.js index dd7ab262..f8d343c6 100644 --- a/tests/cypress/integration/actions_tasks3/case_46_create_task_with_files_from_remote_sources.js +++ b/tests/cypress/integration/actions_tasks3/case_46_create_task_with_files_from_remote_sources.js @@ -30,14 +30,14 @@ context('Create a task with files from remote sources.', () => { cy.addNewLabel(labelName); cy.contains('Remote sources').click(); cy.get('.cvat-file-selector-remote').type(wrongUrl); - cy.get('.cvat-create-task-submit-section').click(); + cy.contains('button', 'Submit & Continue').click(); cy.get('.cvat-notification-notice-create-task-failed').should('exist'); cy.closeNotification('.cvat-notification-notice-create-task-failed'); }); it('Set correct URL to remote file. The task is created.', () => { cy.get('.cvat-file-selector-remote').clear().type(correctUrl); - cy.get('.cvat-create-task-submit-section').click(); + cy.contains('button', 'Submit & Continue').click(); cy.get('.cvat-notification-create-task-success').should('exist'); cy.goToTaskList(); cy.contains('.cvat-item-task-name', taskName).should('exist'); diff --git a/tests/cypress/support/commands.js b/tests/cypress/support/commands.js index 22fb23cc..58facf0f 100644 --- a/tests/cypress/support/commands.js +++ b/tests/cypress/support/commands.js @@ -197,7 +197,7 @@ Cypress.Commands.add( if (multiAttrParams) { cy.updateAttributes(multiAttrParams); } - cy.contains('button', 'Done').click(); + cy.contains('button', 'Continue').click(); } else { if (attachToProject) { cy.get('.cvat-project-search-field').click(); @@ -217,7 +217,7 @@ Cypress.Commands.add( if (advancedConfigurationParams) { cy.advancedConfiguration(advancedConfigurationParams); } - cy.contains('button', 'Submit').click(); + cy.contains('button', 'Submit & Continue').click(); if (expectedResult === 'success') { cy.get('.cvat-notification-create-task-success').should('exist').find('[data-icon="close"]').click(); } @@ -699,7 +699,8 @@ Cypress.Commands.add('addNewLabel', (newLabelName, additionalAttrs, labelColor) cy.updateAttributes(additionalAttrs[i]); } } - cy.contains('button', 'Done').click(); + cy.contains('button', 'Continue').click(); + cy.contains('button', 'Cancel').click(); cy.get('.cvat-spinner').should('not.exist'); cy.get('.cvat-constructor-viewer').should('be.visible'); cy.contains('.cvat-constructor-viewer-item', new RegExp(`^${newLabelName}$`)).should('exist'); @@ -711,12 +712,9 @@ Cypress.Commands.add('addNewLabelViaContinueButton', (additionalLabels) => { cy.get('.cvat-constructor-viewer-new-item').click(); for (let j = 0; j < additionalLabels.length; j++) { cy.get('[placeholder="Label name"]').type(additionalLabels[j]); - if (j !== additionalLabels.length - 1) { - cy.contains('button', 'Continue').click(); - } else { - cy.contains('button', 'Done').click(); - } + cy.contains('button', 'Continue').click(); } + cy.contains('button', 'Cancel').click(); } }); }); diff --git a/tests/cypress/support/commands_projects.js b/tests/cypress/support/commands_projects.js index 7deebe33..dea1042e 100644 --- a/tests/cypress/support/commands_projects.js +++ b/tests/cypress/support/commands_projects.js @@ -26,9 +26,9 @@ Cypress.Commands.add( if (multiAttrParams) { cy.updateAttributes(multiAttrParams); } - cy.contains('button', 'Done').click(); + cy.contains('button', 'Continue').click(); cy.get('.cvat-create-project-content').within(() => { - cy.contains('Submit').click(); + cy.contains('button', 'Submit & Continue').click(); }); if (expectedResult === 'success') { cy.get('.cvat-notification-create-project-success').should('exist').find('[data-icon="close"]').click(); @@ -184,7 +184,6 @@ Cypress.Commands.add('deleteProjectViaActions', (projectName) => { Cypress.Commands.add('assignProjectToUser', (user) => { cy.get('.cvat-project-details').within(() => { cy.get('.cvat-user-search-field').click().type(user); - cy.wait(300); }); cy.get('.ant-select-dropdown') .not('.ant-select-dropdown-hidden')