Merged develop

main
Boris Sekachev 5 years ago
commit 26b2276c42

@ -14,6 +14,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
- Added documentation on how to mount cloud starage(AWS S3 bucket, Azure container, Google Drive) as FUSE (<https://github.com/openvinotoolkit/cvat/pull/2377>)
- Added ability to work with share files without copying inside (<https://github.com/openvinotoolkit/cvat/pull/2377>)
- Tooltips in label selectors (<https://github.com/openvinotoolkit/cvat/pull/2509>)
- Page redirect after login using `next` query parameter (<https://github.com/openvinotoolkit/cvat/pull/2527>)
### Changed
@ -41,10 +42,10 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
- Projects view layout fix (<https://github.com/openvinotoolkit/cvat/pull/2503>)
- Fixed the tasks view (infinite loading) when it is impossible to get a preview of the task (<https://github.com/openvinotoolkit/cvat/pull/2504>)
- Empty frames navigation (<https://github.com/openvinotoolkit/cvat/pull/2505>)
- Disabled position editing in AAM (<https://github.com/openvinotoolkit/cvat/pull/2506>)
- TypeError: Cannot read property 'toString' of undefined (<https://github.com/openvinotoolkit/cvat/pull/2517>)
- Extra shapes are drawn after Esc, or G pressed while drawing a region in grouping (<https://github.com/openvinotoolkit/cvat/pull/2507>)
- Reset state (reviews, issues) after logout or changing a job (<https://github.com/openvinotoolkit/cvat/pull/2525>)
- TypeError: Cannot read property 'id' of undefined when updating a task (<https://github.com/openvinotoolkit/cvat/pull/2544>)
### Security

@ -1,6 +1,6 @@
{
"name": "cvat-ui",
"version": "1.12.0",
"version": "1.13.0",
"lockfileVersion": 1,
"requires": true,
"dependencies": {

@ -1,6 +1,6 @@
{
"name": "cvat-ui",
"version": "1.12.0",
"version": "1.13.0",
"description": "CVAT single-page application",
"main": "src/index.tsx",
"scripts": {

@ -141,9 +141,11 @@ export function updateProjectAsync(projectInstance: any): ThunkAction {
dispatch(projectActions.updateProject());
await projectInstance.save();
const [project] = await cvat.projects.get({ id: projectInstance.id });
// TODO: Check case when a project is not available anymore after update
// (assignee changes assignee and project is not public)
dispatch(projectActions.updateProjectSuccess(project));
project.tasks.forEach((task: any) => {
dispatch(updateTaskSuccess(task));
dispatch(updateTaskSuccess(task, task.id));
});
} catch (error) {
let project = null;

@ -52,13 +52,7 @@ export const reviewActions = {
reopenIssueSuccess: () => createAction(ReviewActionTypes.REOPEN_ISSUE_SUCCESS),
reopenIssueFailed: (error: any) => createAction(ReviewActionTypes.REOPEN_ISSUE_FAILED, { error }),
submitReview: (reviewId: number) => createAction(ReviewActionTypes.SUBMIT_REVIEW, { reviewId }),
submitReviewSuccess: (activeReview: any, reviews: any[], issues: any[], frame: number) =>
createAction(ReviewActionTypes.SUBMIT_REVIEW_SUCCESS, {
activeReview,
reviews,
issues,
frame,
}),
submitReviewSuccess: () => createAction(ReviewActionTypes.SUBMIT_REVIEW_SUCCESS),
submitReviewFailed: (error: any) => createAction(ReviewActionTypes.SUBMIT_REVIEW_FAILED, { error }),
switchIssuesHiddenFlag: (hidden: boolean) => createAction(ReviewActionTypes.SWITCH_ISSUES_HIDDEN_FLAG, { hidden }),
};
@ -193,9 +187,6 @@ export const submitReviewAsync = (review: any): ThunkAction => async (dispatch,
const {
annotation: {
job: { instance: jobInstance },
player: {
frame: { number: frame },
},
},
} = state;
@ -204,13 +195,8 @@ export const submitReviewAsync = (review: any): ThunkAction => async (dispatch,
await review.submit(jobInstance.id);
const [task] = await cvat.tasks.get({ id: jobInstance.task.id });
dispatch(updateTaskSuccess(task));
const reviews = await jobInstance.reviews();
const issues = await jobInstance.issues();
const reviewInstance = new cvat.classes.Review({ job: jobInstance.id });
dispatch(reviewActions.submitReviewSuccess(reviewInstance, reviews, issues, frame));
dispatch(updateTaskSuccess(task, jobInstance.task.id));
dispatch(reviewActions.submitReviewSuccess());
} catch (error) {
dispatch(reviewActions.submitReviewFailed(error));
}

@ -437,10 +437,10 @@ function updateTask(): AnyAction {
return action;
}
export function updateTaskSuccess(task: any): AnyAction {
export function updateTaskSuccess(task: any, taskID: number): AnyAction {
const action = {
type: TasksActionTypes.UPDATE_TASK_SUCCESS,
payload: { task },
payload: { task, taskID },
};
return action;
@ -465,7 +465,7 @@ export function updateTaskAsync(taskInstance: any): ThunkAction<Promise<void>, C
const userFetching = getState().auth.fetching;
if (!userFetching && nextUser && currentUser.username === nextUser.username) {
const [task] = await cvat.tasks.get({ id: taskInstance.id });
dispatch(updateTaskSuccess(task));
dispatch(updateTaskSuccess(task, taskInstance.id));
}
} catch (error) {
// try abort all changes
@ -490,7 +490,7 @@ export function updateJobAsync(jobInstance: any): ThunkAction<Promise<void>, {},
dispatch(updateTask());
await jobInstance.save();
const [task] = await cvat.tasks.get({ id: jobInstance.task.id });
dispatch(updateTaskSuccess(task));
dispatch(updateTaskSuccess(task, jobInstance.task.id));
} catch (error) {
// try abort all changes
let task = null;

@ -53,11 +53,13 @@ export default function AnnotationPageComponent(props: Props): JSX.Element {
};
}, []);
if (job === null) {
if (!fetching) {
useEffect(() => {
if (job === null && !fetching) {
getJob();
}
}, [job, fetching]);
if (job === null) {
return <Spin size='large' className='cvat-spinner' />;
}

@ -109,7 +109,7 @@ export default class CanvasWrapperComponent extends React.PureComponent<Props> {
autoborders: automaticBordering,
undefinedAttrValue: consts.UNDEFINED_ATTRIBUTE_VALUE,
displayAllText: showObjectsTextAlways,
forceDisableEditing: [Workspace.ATTRIBUTE_ANNOTATION, Workspace.REVIEW_WORKSPACE].includes(workspace),
forceDisableEditing: workspace === Workspace.REVIEW_WORKSPACE,
});
this.initialSetup();
@ -260,11 +260,11 @@ export default class CanvasWrapperComponent extends React.PureComponent<Props> {
}
if (prevProps.workspace !== workspace) {
if ([Workspace.ATTRIBUTE_ANNOTATION, Workspace.REVIEW_WORKSPACE].includes(workspace)) {
if (workspace === Workspace.REVIEW_WORKSPACE) {
canvasInstance.configure({
forceDisableEditing: true,
});
} else if ([Workspace.ATTRIBUTE_ANNOTATION, Workspace.REVIEW_WORKSPACE].includes(prevProps.workspace)) {
} else if (prevProps.workspace === Workspace.REVIEW_WORKSPACE) {
canvasInstance.configure({
forceDisableEditing: false,
});

@ -233,16 +233,22 @@ class CVATApplication extends React.PureComponent<CVATAppProps & RouteComponentP
aboutInitialized,
pluginsInitialized,
formatsInitialized,
modelsInitialized,
switchShortcutsDialog,
switchSettingsDialog,
user,
keyMap,
location,
isModelPluginActive,
} = this.props;
const readyForRender =
(userInitialized && (user == null || !user.isVerified)) ||
(userInitialized && formatsInitialized && pluginsInitialized && aboutInitialized);
(userInitialized &&
formatsInitialized &&
pluginsInitialized &&
aboutInitialized &&
(!isModelPluginActive || modelsInitialized));
const subKeyMap = {
SWITCH_SHORTCUTS: keyMap.SWITCH_SHORTCUTS,
@ -312,7 +318,10 @@ class CVATApplication extends React.PureComponent<CVATAppProps & RouteComponentP
{isModelPluginActive && (
<Route exact path='/models' component={ModelsPageContainer} />
)}
<Redirect push to='/tasks' />
<Redirect
push
to={new URLSearchParams(location.search).get('next') || '/tasks'}
/>
</Switch>
</GlobalHotKeys>
{/* eslint-disable-next-line */}
@ -339,7 +348,9 @@ class CVATApplication extends React.PureComponent<CVATAppProps & RouteComponentP
path='/auth/password/reset/confirm'
component={ResetPasswordPageConfirmComponent}
/>
<Redirect to='/auth/login' />
<Redirect
to={location.pathname.length > 1 ? `/auth/login/?next=${location.pathname}` : '/auth/login'}
/>
</Switch>
</GlobalErrorBoundary>
);

@ -3,15 +3,17 @@
// SPDX-License-Identifier: MIT
import React, { useEffect } from 'react';
import { Redirect, useParams } from 'react-router';
import { Redirect, useParams, useLocation } from 'react-router';
import { useCookies } from 'react-cookie';
export default function LoginWithTokenComponent(): JSX.Element {
const { sessionId, token } = useParams();
const location = useLocation();
const { sessionId, token } = useParams<{ sessionId: string; token: string }>();
const [cookies, setCookie] = useCookies(['sessionid', 'csrftoken']);
const expires1y = new Date(new Date().setFullYear(new Date().getFullYear() + 1));
const expires2w = new Date(new Date().setDate(new Date().getDate() + 13));
const search = new URLSearchParams(location.search);
setCookie('sessionid', sessionId, { path: '/', expires: expires2w });
setCookie('csrftoken', token, { path: '/', expires: expires1y });
@ -24,7 +26,7 @@ export default function LoginWithTokenComponent(): JSX.Element {
);
if (cookies.sessionid && cookies.csrftoken) {
return <Redirect to='/tasks' />;
return <Redirect to={search.get('next') || '/tasks'} />;
}
return <></>;
}

@ -42,7 +42,7 @@ export default function ProjectListComponent(): JSX.Element {
<Col className='cvat-projects-list' md={22} lg={18} xl={16} xxl={14}>
{projectInstances.map(
(row: any[]): JSX.Element => (
<Row gutter={[8, 8]}>
<Row key={row[0].id} gutter={[8, 8]}>
{row.map((instance: any) => (
<Col span={6} key={instance.id}>
<ProjectItem projectInstance={instance} />

@ -28,6 +28,14 @@ interface TaskPageComponentProps {
type Props = TaskPageComponentProps & RouteComponentProps<{ id: string }>;
class TaskPageComponent extends React.PureComponent<Props> {
public componentDidMount(): void {
const { task, fetching, getTask } = this.props;
if (task === null && !fetching) {
getTask();
}
}
public componentDidUpdate(): void {
const { deleteActivity, history } = this.props;
@ -37,15 +45,9 @@ class TaskPageComponent extends React.PureComponent<Props> {
}
public render(): JSX.Element {
const {
task, fetching, updating, getTask,
} = this.props;
const { task, updating } = this.props;
if (task === null || updating) {
if (task === null && !fetching) {
getTask();
}
return <Spin size='large' className='cvat-spinner' />;
}

@ -202,7 +202,7 @@ export default (state = defaultState, action: AnyAction): AnnotationState => {
fetching: false,
},
},
}
};
}
case AnnotationActionTypes.CHANGE_FRAME: {
return {

@ -94,9 +94,9 @@ export default function (state = defaultState, action: AuthActions | BoundariesA
return {
...state,
showChangePasswordDialog:
typeof action.payload.showChangePasswordDialog === 'undefined'
? !state.showChangePasswordDialog
: action.payload.showChangePasswordDialog,
typeof action.payload.showChangePasswordDialog === 'undefined' ?
!state.showChangePasswordDialog :
action.payload.showChangePasswordDialog,
};
case AuthActionTypes.REQUEST_PASSWORD_RESET:
return {

@ -61,17 +61,8 @@ export default function (state: ReviewState = defaultState, action: any): Review
};
}
case ReviewActionTypes.SUBMIT_REVIEW_SUCCESS: {
const {
activeReview, reviews, issues, frame,
} = action.payload;
const frameIssues = computeFrameIssues(issues, activeReview, frame);
return {
...state,
activeReview,
reviews,
issues,
frameIssues,
fetching: {
...state.fetching,
reviewId: null,

@ -84,9 +84,9 @@ export default (state: TasksState = defaultState, action: AnyAction): TasksState
const { dumps } = state.activities;
dumps[task.id] =
task.id in dumps && !dumps[task.id].includes(dumper.name)
? [...dumps[task.id], dumper.name]
: dumps[task.id] || [dumper.name];
task.id in dumps && !dumps[task.id].includes(dumper.name) ?
[...dumps[task.id], dumper.name] :
dumps[task.id] || [dumper.name];
return {
...state,
@ -122,9 +122,9 @@ export default (state: TasksState = defaultState, action: AnyAction): TasksState
const { exports: activeExports } = state.activities;
activeExports[task.id] =
task.id in activeExports && !activeExports[task.id].includes(exporter.name)
? [...activeExports[task.id], exporter.name]
: activeExports[task.id] || [exporter.name];
task.id in activeExports && !activeExports[task.id].includes(exporter.name) ?
[...activeExports[task.id], exporter.name] :
activeExports[task.id] || [exporter.name];
return {
...state,
@ -299,19 +299,30 @@ export default (state: TasksState = defaultState, action: AnyAction): TasksState
};
}
case TasksActionTypes.UPDATE_TASK_SUCCESS: {
// a task will be undefined after updating when a user doesn't have access to the task anymore
const { task, taskID } = action.payload;
if (typeof task === 'undefined') {
return {
...state,
updating: false,
current: state.current.filter((_task: Task): boolean => _task.instance.id !== taskID),
};
}
return {
...state,
updating: false,
current: state.current.map(
(task): Task => {
if (task.instance.id === action.payload.task.id) {
(_task): Task => {
if (_task.instance.id === task.id) {
return {
...task,
instance: action.payload.task,
..._task,
instance: task,
};
}
return task;
return _task;
},
),
};

@ -25,7 +25,7 @@ context("Object can't be draggable/resizable in AAM", () => {
});
describe(`Testing issue "${issueId}"`, () => {
it('Create, acttivate a object', () => {
it.skip('Create, acttivate a object', () => {
cy.createRectangle(createRectangleShape2Points);
cy.get('#cvat_canvas_shape_1')
.should('not.have.class', 'cvat_canvas_shape_activated')
@ -33,7 +33,7 @@ context("Object can't be draggable/resizable in AAM", () => {
.should('have.class', 'cvat_canvas_shape_activated');
});
it('Go to AAM', () => {
it.skip('Go to AAM', () => {
cy.changeWorkspace('Attribute annotation', labelName);
cy.get('#cvat_canvas_shape_1')
.then((shape) => {
@ -50,7 +50,7 @@ context("Object can't be draggable/resizable in AAM", () => {
});
});
it('Try to move/resize the object', () => {
it.skip('Try to move/resize the object', () => {
cy.get('.cvat-canvas-container')
.trigger('mousedown', { button: 0 })
.trigger('mousemove', 550, 251)

@ -23,6 +23,7 @@ Cypress.Commands.add('logout', (username = Cypress.env('user')) => {
});
cy.get('span[aria-label="logout"]').click();
cy.url().should('include', '/auth/login');
cy.visit('/auth/login'); // clear query parameter "next"
});
Cypress.Commands.add('userRegistration', (firstName, lastName, userName, emailAddr, password) => {

Loading…
Cancel
Save