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 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>) - 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>) - 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 ### 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>) - 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>) - 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>) - 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>) - 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>) - 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>) - 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 ### Security

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

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

@ -141,9 +141,11 @@ export function updateProjectAsync(projectInstance: any): ThunkAction {
dispatch(projectActions.updateProject()); dispatch(projectActions.updateProject());
await projectInstance.save(); await projectInstance.save();
const [project] = await cvat.projects.get({ id: projectInstance.id }); 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)); dispatch(projectActions.updateProjectSuccess(project));
project.tasks.forEach((task: any) => { project.tasks.forEach((task: any) => {
dispatch(updateTaskSuccess(task)); dispatch(updateTaskSuccess(task, task.id));
}); });
} catch (error) { } catch (error) {
let project = null; let project = null;

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

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

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

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

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

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

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

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

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

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

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

@ -84,9 +84,9 @@ export default (state: TasksState = defaultState, action: AnyAction): TasksState
const { dumps } = state.activities; const { dumps } = state.activities;
dumps[task.id] = dumps[task.id] =
task.id in dumps && !dumps[task.id].includes(dumper.name) task.id in dumps && !dumps[task.id].includes(dumper.name) ?
? [...dumps[task.id], dumper.name] [...dumps[task.id], dumper.name] :
: dumps[task.id] || [dumper.name]; dumps[task.id] || [dumper.name];
return { return {
...state, ...state,
@ -122,9 +122,9 @@ export default (state: TasksState = defaultState, action: AnyAction): TasksState
const { exports: activeExports } = state.activities; const { exports: activeExports } = state.activities;
activeExports[task.id] = activeExports[task.id] =
task.id in activeExports && !activeExports[task.id].includes(exporter.name) task.id in activeExports && !activeExports[task.id].includes(exporter.name) ?
? [...activeExports[task.id], exporter.name] [...activeExports[task.id], exporter.name] :
: activeExports[task.id] || [exporter.name]; activeExports[task.id] || [exporter.name];
return { return {
...state, ...state,
@ -299,19 +299,30 @@ export default (state: TasksState = defaultState, action: AnyAction): TasksState
}; };
} }
case TasksActionTypes.UPDATE_TASK_SUCCESS: { 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 { return {
...state, ...state,
updating: false, updating: false,
current: state.current.map( current: state.current.map(
(task): Task => { (_task): Task => {
if (task.instance.id === action.payload.task.id) { if (_task.instance.id === task.id) {
return { return {
...task, ..._task,
instance: action.payload.task, instance: task,
}; };
} }
return task; return _task;
}, },
), ),
}; };

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

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

Loading…
Cancel
Save