From 6b6139967f9c0da565c64c0bbb920f2f52d955a5 Mon Sep 17 00:00:00 2001 From: Andrey Zhavoronkov Date: Thu, 23 Apr 2020 19:22:41 +0300 Subject: [PATCH 01/11] added terms of use support --- cvat-core/src/api-implementation.js | 9 ++- cvat-core/src/api.js | 18 ++++- cvat-core/src/server-proxy.js | 19 ++++- cvat-ui/src/actions/auth-actions.ts | 4 +- cvat-ui/src/actions/useragreements-actions.ts | 37 ++++++++++ cvat-ui/src/components/cvat-app.tsx | 13 +++- .../register-page/register-form.tsx | 69 +++++++++++++++++++ .../register-page/register-page.tsx | 9 ++- .../register-page/register-page.tsx | 9 ++- cvat-ui/src/index.tsx | 8 +++ cvat-ui/src/reducers/interfaces.ts | 7 ++ cvat-ui/src/reducers/root-reducer.ts | 2 + .../src/reducers/useragreements-reducer.ts | 50 ++++++++++++++ cvat/apps/engine/urls.py | 2 + cvat/apps/restrictions/__init__.py | 3 + cvat/apps/restrictions/admin.py | 8 +++ cvat/apps/restrictions/apps.py | 10 +++ cvat/apps/restrictions/migrations/__init__.py | 3 + cvat/apps/restrictions/models.py | 8 +++ cvat/apps/restrictions/restrictions.py | 3 + cvat/apps/restrictions/serializers.py | 35 ++++++++++ cvat/apps/restrictions/tests.py | 8 +++ cvat/apps/restrictions/urls.py | 0 cvat/apps/restrictions/views.py | 41 +++++++++++ cvat/settings/base.py | 8 ++- 25 files changed, 370 insertions(+), 13 deletions(-) create mode 100644 cvat-ui/src/actions/useragreements-actions.ts create mode 100644 cvat-ui/src/reducers/useragreements-reducer.ts create mode 100644 cvat/apps/restrictions/__init__.py create mode 100644 cvat/apps/restrictions/admin.py create mode 100644 cvat/apps/restrictions/apps.py create mode 100644 cvat/apps/restrictions/migrations/__init__.py create mode 100644 cvat/apps/restrictions/models.py create mode 100644 cvat/apps/restrictions/restrictions.py create mode 100644 cvat/apps/restrictions/serializers.py create mode 100644 cvat/apps/restrictions/tests.py create mode 100644 cvat/apps/restrictions/urls.py create mode 100644 cvat/apps/restrictions/views.py diff --git a/cvat-core/src/api-implementation.js b/cvat-core/src/api-implementation.js index 4b7e2872719..cc5435bdb4e 100644 --- a/cvat-core/src/api-implementation.js +++ b/cvat-core/src/api-implementation.js @@ -74,10 +74,15 @@ return result; }; + cvat.server.userAgreements.implementation = async () => { + const result = await serverProxy.server.userAgreements(); + return result; + }; + cvat.server.register.implementation = async (username, firstName, lastName, - email, password1, password2) => { + email, password1, password2, userAgreements) => { await serverProxy.server.register(username, firstName, lastName, email, - password1, password2); + password1, password2, userAgreements); }; cvat.server.login.implementation = async (username, password) => { diff --git a/cvat-core/src/api.js b/cvat-core/src/api.js index 4eb4e99af00..d8cffbee1ce 100644 --- a/cvat-core/src/api.js +++ b/cvat-core/src/api.js @@ -132,6 +132,20 @@ function build() { .apiWrapper(cvat.server.datasetFormats); return result; }, + /** + * Method returns user agreements that the user must accept + * @method userAgreements + * @async + * @memberof module:API.cvat.server + * @returns {Object[]} + * @throws {module:API.cvat.exceptions.PluginError} + * @throws {module:API.cvat.exceptions.ServerError} + */ + async userAgreements() { + const result = await PluginRegistry + .apiWrapper(cvat.server.userAgreements); + return result; + }, /** * Method allows to register on a server * @method register @@ -146,10 +160,10 @@ function build() { * @throws {module:API.cvat.exceptions.PluginError} * @throws {module:API.cvat.exceptions.ServerError} */ - async register(username, firstName, lastName, email, password1, password2) { + async register(username, firstName, lastName, email, password1, password2, userAgreements) { const result = await PluginRegistry .apiWrapper(cvat.server.register, username, firstName, - lastName, email, password1, password2); + lastName, email, password1, password2, userAgreements); return result; }, /** diff --git a/cvat-core/src/server-proxy.js b/cvat-core/src/server-proxy.js index 8251328223d..ea8ec3957a2 100644 --- a/cvat-core/src/server-proxy.js +++ b/cvat-core/src/server-proxy.js @@ -170,7 +170,22 @@ return response; } - async function register(username, firstName, lastName, email, password1, password2) { + async function userAgreements() { + const { backendAPI } = config; + let response = null; + try { + response = await Axios.get(`${backendAPI}/restrictions/user_agreements`, { + proxy: config.proxy, + }); + + } catch (errorData) { + throw generateError(errorData); + } + + return response.data; + } + + async function register(username, firstName, lastName, email, password1, password2, userAgreements) { let response = null; try { const data = JSON.stringify({ @@ -180,6 +195,7 @@ email, password1, password2, + user_agreements: userAgreements, }); response = await Axios.post(`${config.backendAPI}/auth/register`, data, { proxy: config.proxy, @@ -671,6 +687,7 @@ authorized, register, request: serverRequest, + userAgreements, }), writable: false, }, diff --git a/cvat-ui/src/actions/auth-actions.ts b/cvat-ui/src/actions/auth-actions.ts index 71be07f2c54..e92de193cd0 100644 --- a/cvat-ui/src/actions/auth-actions.ts +++ b/cvat-ui/src/actions/auth-actions.ts @@ -3,6 +3,7 @@ // SPDX-License-Identifier: MIT import { ActionUnion, createAction, ThunkAction } from 'utils/redux'; +import { UserAgreement } from 'components/register-page/register-form' import getCore from 'cvat-core'; const cvat = getCore(); @@ -44,13 +45,14 @@ export const registerAsync = ( email: string, password1: string, password2: string, + userAgreements: UserAgreement[], ): ThunkAction => async ( dispatch, ) => { dispatch(authActions.register()); try { - await cvat.server.register(username, firstName, lastName, email, password1, password2); + await cvat.server.register(username, firstName, lastName, email, password1, password2, userAgreements); const users = await cvat.users.get({ self: true }); dispatch(authActions.registerSuccess(users[0])); diff --git a/cvat-ui/src/actions/useragreements-actions.ts b/cvat-ui/src/actions/useragreements-actions.ts new file mode 100644 index 00000000000..382ee038f5d --- /dev/null +++ b/cvat-ui/src/actions/useragreements-actions.ts @@ -0,0 +1,37 @@ +// Copyright (C) 2020 Intel Corporation +// +// SPDX-License-Identifier: MIT + +import { ActionUnion, createAction, ThunkAction } from 'utils/redux'; +import getCore from 'cvat-core'; + +const core = getCore(); + +export enum UserAgreementsActionTypes { + GET_USER_AGREEMENTS = 'GET_USER_AGREEMENTS', + GET_USER_AGREEMENTS_SUCCESS = 'GET_USER_AGREEMENTS_SUCCESS', + GET_USER_AGREEMENTS_FAILED = 'GET_USER_AGREEMENTS_FAILED', +} + +const userAgreementsActions = { + getUserAgreements: () => createAction(UserAgreementsActionTypes.GET_USER_AGREEMENTS), + getUserAgreementsSuccess: (userAgreements: any[]) => + createAction(UserAgreementsActionTypes.GET_USER_AGREEMENTS_SUCCESS, userAgreements), + getUserAgreementsFailed: (error: any) => + createAction(UserAgreementsActionTypes.GET_USER_AGREEMENTS_FAILED, { error }), +}; + +export type UserAgreementsActions = ActionUnion; + +export const getUserAgreementsAsync = (): ThunkAction => async (dispatch): Promise => { + dispatch(userAgreementsActions.getUserAgreements()); + + try { + const userAgreements = await core.server.userAgreements(); + dispatch( + userAgreementsActions.getUserAgreementsSuccess(userAgreements), + ); + } catch (error) { + dispatch(userAgreementsActions.getUserAgreementsFailed(error)); + } +}; \ No newline at end of file diff --git a/cvat-ui/src/components/cvat-app.tsx b/cvat-ui/src/components/cvat-app.tsx index f5a76fdb0ff..cbf7463bb9a 100644 --- a/cvat-ui/src/components/cvat-app.tsx +++ b/cvat-ui/src/components/cvat-app.tsx @@ -33,6 +33,7 @@ interface CVATAppProps { loadUsers: () => void; loadAbout: () => void; verifyAuthorized: () => void; + loadUserAgreements: () => void; initPlugins: () => void; resetErrors: () => void; resetMessages: () => void; @@ -51,6 +52,8 @@ interface CVATAppProps { installedAutoAnnotation: boolean; installedTFAnnotation: boolean; installedTFSegmentation: boolean; + userAgreementsFetching: boolean, + userAgreementsInitialized: boolean, notifications: NotificationsState; user: any; } @@ -58,7 +61,7 @@ interface CVATAppProps { class CVATApplication extends React.PureComponent { public componentDidMount(): void { const core = getCore(); - const { verifyAuthorized } = this.props; + const { verifyAuthorized, loadUserAgreements } = this.props; configure({ ignoreRepeatedEventsWhenKeyHeldDown: false }); // Logger configuration @@ -77,6 +80,7 @@ class CVATApplication extends React.PureComponent { callback(); }; + private validateAgrement = (agreement: any, value: any, callback: any): void => { + const { userAgreements } = this.props; + let isValid: boolean = true; + for (const userAgreement of userAgreements) { + if (agreement.field === userAgreement.name + && userAgreement.required && !value) { + isValid = false; + callback(`You must accept the ${userAgreement.display_text}!`); + break; + } + } + if (isValid) { + callback(); + } + }; + private handleSubmit = (e: React.FormEvent): void => { e.preventDefault(); const { form, onSubmit, + userAgreements, } = this.props; form.validateFields((error, values): void => { if (!error) { + values.userAgreements = [] + + for (const userAgreement of userAgreements) { + + values.userAgreements.push({ + name: userAgreement.name, + value: values[userAgreement.name] + }); + delete values[userAgreement.name]; + } + onSubmit(values); } }); @@ -217,6 +253,38 @@ class RegisterFormComponent extends React.PureComponent { ); } + private renderUserAgreements(): JSX.Element[] { + const { form, userAgreements } = this.props; + const getUserAgreementsElements = () => + { + const agreementsList: JSX.Element[] = []; + for (const userAgreement of userAgreements) { + agreementsList.push( + + {form.getFieldDecorator(userAgreement.name as string, { + initialValue: false, + valuePropName: 'checked', + rules: [{ + required: true, + message: 'You must accept!', + }, { + validator: this.validateAgrement, + }] + })( + + I accept the { userAgreement.display_text } + + )} + + ); + } + return agreementsList; + } + + return getUserAgreementsElements(); + } + public render(): JSX.Element { const { fetching } = this.props; @@ -228,6 +296,7 @@ class RegisterFormComponent extends React.PureComponent { {this.renderEmailField()} {this.renderPasswordField()} {this.renderPasswordConfirmationField()} + {this.renderUserAgreements()}