From ad0d32b3c07e15e0739090993f857a1e8871e42c Mon Sep 17 00:00:00 2001 From: Qun Huang Date: Thu, 5 Oct 2017 15:42:15 +0800 Subject: [PATCH 01/19] updates to run the code locally --- README.md | 177 ++++++++++++++---- local/docker-compose.yml | 5 +- local/test_data.sql | 58 ++++++ ...eview-microservice.postman_collection.json | 75 ++++++++ ...view-microservice.postman_environment.json | 28 +++ service/pom.xml | 24 +-- 6 files changed, 316 insertions(+), 51 deletions(-) mode change 100644 => 100755 README.md create mode 100755 local/test_data.sql create mode 100755 postman/review-microservice.postman_collection.json create mode 100755 postman/review-microservice.postman_environment.json diff --git a/README.md b/README.md old mode 100644 new mode 100755 index 2bfe06a..dbe61ee --- a/README.md +++ b/README.md @@ -1,49 +1,158 @@ # Review Microservice -A microservice for generic units of work (e.g., a challenge) +Microservice for supporting Topcoder Online Review platform. -## Getting Started -This microservice is using [dropwizard](dropwizard.io) as the REST API framework and implements the [v3 API spec](https://docs.google.com/a/appirio.com/presentation/d/15pucEI0MHj9y778EyaAWGh4MBH-I73i1-GS0ir7FhxE/edit#slide=id.g29c3ffcc3_020). If you are unfamiliar with dropwizard, take a look at their [Getting Started](http://dropwizard.io/getting-started.html) page. +# Prerequisities -### Building -We are using Maven 2 to build and building is as simple as `mvn package`. *Make sure you are using Java 8 for build and runtime* +- Java 8 with Maven 2+ +- Docker -### Configuration -Configuration is managed through a yaml file. The `src/main/resources/review-service.yaml` file should contain the necessary configuration for you to run locally. +# Local Deployment -### Framework Stack -- dropwizard-core - core REST API -- dropwizard-auth - user authentication -- dropwizard-jdbi - database access -- dropwizard-testing - unit testing -- mysql-connector-java - mysql JDBC driver -- fest-assert-core, mockito-core - unit testing -- slf4j - logging +### Start Informix instance via Docker -### Database -We are using mysql RDS for the database. For development use, connect to the topcoder-dev instance. Database access should be limited to the DAO layer. We are using the [JDBI](http://jdbi.org/) framework for database access. Where possible we should use the [SQL Objects API](http://jdbi.org/sql_object_overview/) approach listed in the JDBI documentation. +- Set an IP for Docker by setting the environment variable `DOCKER_IP`. Recommended to set to `0.0.0.0` -For this service, the DAO is managed by `ReviewDAO` +- Command to set the environment variable `DOCKER_IP` -**We need to keep the data model clean and name properties of our representation (model) objects as the column names so we can use the automatic bean mapping facilities instead of needing to manually wire things together. Any deviation requires prior approval.** +``` +export DOCKER_IP=0.0.0.0 +``` + +- To run Informix locally, go to the `local` folder and execute the following command: + +``` +docker-compose up +``` -### API Endpoint -The API endpoint for this service is `com.appirio.service.review.resources.ReviewResource` It contains the specific resource paths supported by this service. The root of the service is /v3/reviews. API methods should be annotated with the appropriate jax-rs annotations and the `@Timed` annotation for metrics. An example method is shown below. +If you should ever encounter the error: + +``` +[java] Caused by: com.topcoder.db.connectionfactory.DBConnectionException: error occurs while creating the connection. +You already have 20 connections at this +time. Any further connections ``` -@GET -@Timed -public ApiResponse getReviews(@Auth AuthUser auth, - @APIQueryParam(repClass = Review.class) QueryParameter queryParameter) - logger.debug("getReviews, filter : " + queryParameter.getFilter().getFields()); - return MetadataApiResponseFactory.createResponse(reviewManager.getReviews(auth, queryParameter, - new BaseAuthorizationContext(auth))); -} +Try to restart the informix database server as below: +``` +#### log into the container based on the container name got in the previous step +docker exec -it local_informix bash + +#### restart the database server +onmode -ky +oninit +``` + +### Inserting test records + +- Test data is present in SQL file (`test_data.sql`) in `local` folder. + +- Transfer the `test_data.sql` to docker environment by executing the following command + ``` +docker cp test_data.sql local_informix:/home/informix +``` + +- Insert the records in `tcs_catalog` database by executing the following command + +``` +docker exec -it local_informix dbaccess tcs_catalog test_data.sql +``` + +- Docker environment is ready to be used in our service. + +### Configuration Variables +The configuration file `service/src/main/resources/review-service.yaml` contain the necessary configuration to run the microservice locally. Below configuration variables need to be set using environment variables. + +|Name | Description | +|--------------------|:-----------------------------------------:| +|AUTH_DOMAIN | Authentication domain of JWT, Dont set unless you have valid tokens with given AUTH_DOMAIN, Defaulted to 'topcoder-dev.com' | +|IP | The IP of your docker container | +|OLTP_USER | The Informix OLTP user | +|OLTP_PW | The Informix OLTP user password | +|OLTP_URL | The JDBC URL of Informix OLTP database | +|DW_USER | The Informix DW user | +|DW_PW | The Informix DW user password | +|DW_URL | The JDBC URL of Informix DW database | +|TC_JWT_KEY | Secret used in JWT Service, Used for signature verification as well | + +In Linux, Set the environment variables as + +``` +export AUTH_DOMAIN=... +export IP=... +export OLTP_USER=... +export OLTP_PW=... +export OLTP_URL=... +export DW_USER=... +export DW_PW=... +export DW_URL=... +``` + +### Working credentials for local deployment + +``` +export IP=0.0.0.0 +export OLTP_USER=informix +export OLTP_PW=1nf0rm1x +export OLTP_URL=jdbc:informix-sqli://$IP:2021/tcs_catalog:INFORMIXSERVER=informixoltp_tcp;IFX_LOCK_MODE_WAIT=5;OPTCOMPIND=0;STMT_CACHE=1 +export DW_USER=informix +export DW_PW=1nf0rm1x +export DW_URL=jdbc:informix-sqli://$IP:2021/common_dw:INFORMIXSERVER=informixoltp_tcp;IFX_LOCK_MODE_WAIT=5;OPTCOMPIND=0;STMT_CACHE=1 +export TC_JWT_KEY=secret +``` + +### Set up Local Maven repository for Appirio Maven + +- Since maven.appirio.net is not accessible, We need to set up a local maven repository with the zip file `temp-maven-repo-master.zip` + +- Unzip the contents to any of the directory in your System. **In Linux, the directory should not be a mounted directory like pen drive, hard disk, etc..** + +- Open `pom.xml` in `service` directory and replace the `` of Local repository with `absolute path of the unzipped file`. E.g. If you have unzipped the files to `/home/user/temp-maven-repo-master`, then POM entry will look like + +``` + + 1_temp_appirio_maven_repo + Appirio Maven Local Repository + file:///home/user/temp-maven-repo-master + +``` + +### Build Microservice + +In the `service` folder of this project, run the following command to build the service. + +``` +mvn clean package +``` + +### Start Microservice + +- Before starting please ensure that all environment variables listed above is set properly and docker is up and running. + +- Then start the service using the following command in the 'service' folder: + +``` +java -jar ./target/review-microservice-*.jar server ./src/main/resources/review-service.yaml +``` + +### Verification + +Though there are many endpoints, For starters, it's sufficient to verify two end points. + +- Endpoint `/v3/scorecards` doesn't require Authentication + +- Endpoint `/v3/reviews` require Authentication + +Use the postman collection in `Postman` directory to verify the end points. + +- **Ensure to import the postman environment before testing the endpoints** + +- Request `Get list of all Scorecards` returns all scorecard templates present in the database + +**Loaded test data has details for 3 submissions for a single competition reviewed by 2 reviewers** -Note the `TCAuth` parameter provided by the `ServiceAuthenticator` class will provide user auth information and is a required parameterfor all secured API calls. -You must set up `TC_JWT_KEY` environment variable, the sample value is `4WvoZLWhFPZ5jauw2+XCU+p772S4oBN25tNPyjHR`. +- Request `Get list of all reviews Reviewer1` return the reviews done by Reviewer 1. -### Testing -All API endpoint methods should have unit tests. For this service we are following the dropwizard testing recommendations as described [here](http://dropwizard.io/manual/testing.html) +- Request `Get list of all reviews Reviewer2` return the reviews done by Reviewer 2. -For manual API testing, the [postman chrome app](https://chrome.google.com/webstore/detail/postman-rest-client/fdmmgilgnpjigdojojpjoooidkmcomcm?hl=en) is useful. + **Note: Some of the fields in returned output will be NULL, It's expected. That's how the internal controllers and services are written** diff --git a/local/docker-compose.yml b/local/docker-compose.yml index f8f55a9..4c69346 100644 --- a/local/docker-compose.yml +++ b/local/docker-compose.yml @@ -1,14 +1,15 @@ version: '2' services: informix: + container_name: local_informix image: "appiriodevops/informix:1b3d4ef" ports: - "2021:2021" kafka: image: spotify/kafka environment: - ADVERTISED_HOST: $DOCKER_IP - ADVERTISED_PORT: 9092 + ADVERTISED_HOST: ${DOCKER_IP} + ADVERTISED_PORT: 2181 ports: - "2181:2181" - "9092:9092" diff --git a/local/test_data.sql b/local/test_data.sql new file mode 100755 index 0000000..6a63f2b --- /dev/null +++ b/local/test_data.sql @@ -0,0 +1,58 @@ +database tcs_catalog; + +/* Data for a project*/ + +insert into project(project_id, project_status_id, project_category_id, create_user, create_date, modify_user, modify_date) +values(1, 1, 1, 132456, current, 132456, current); + +/* Data for 2 reviewers and 3 submitters based on the token provided */ +insert into resource(resource_id, resource_role_id, project_id, project_phase_id, user_id, create_user, create_date, modify_user, modify_date) values(2, 4, 1, 13, 1234, 132456, current, 132456, current); + +insert into resource(resource_id, resource_role_id, project_id, project_phase_id, user_id, create_user, create_date, modify_user, modify_date) values(3, 4, 1, 13, 1235, 132456, current, 132456, current); + +insert into resource(resource_id, resource_role_id, project_id, project_phase_id, user_id, create_user, create_date, modify_user, modify_date) values(4, 1, 1, 13, 1200, 132456, current, 132456, current); + +insert into resource(resource_id, resource_role_id, project_id, project_phase_id, user_id, create_user, create_date, modify_user, modify_date) values(5, 1, 1, 13, 1201, 132456, current, 132456, current); + +insert into resource(resource_id, resource_role_id, project_id, project_phase_id, user_id, create_user, create_date, modify_user, modify_date) values(6, 1, 1, 13, 1202, 132456, current, 132456, current); + +/* Data for 3 uploads */ + +insert into upload(upload_id, project_id, project_phase_id, resource_id, upload_type_id, upload_status_id, parameter, create_user, create_date, modify_user, modify_date) values(1, 1, 12, 4, 1, 1, 'someparam', 132456, current, 132456, current); + +insert into upload(upload_id, project_id, project_phase_id, resource_id, upload_type_id, upload_status_id, parameter, create_user, create_date, modify_user, modify_date) values(2, 1, 12, 5, 1, 1, 'someparam', 132456, current, 132456, current); + +insert into upload(upload_id, project_id, project_phase_id, resource_id, upload_type_id, upload_status_id, parameter, create_user, create_date, modify_user, modify_date) values(3, 1, 12, 6, 1, 1, 'someparam', 132456, current, 132456, current); + + +/* Data for 3 submission */ + +insert into submission(submission_id, upload_id, submission_status_id, submission_type_id, create_user, create_date, modify_user, modify_date) +values(1, 1, 1, 1, 132456, current, 132456, current); + +insert into submission(submission_id, upload_id, submission_status_id, submission_type_id, create_user, create_date, modify_user, modify_date) +values(2, 2, 1, 1, 132456, current, 132456, current); + +insert into submission(submission_id, upload_id, submission_status_id, submission_type_id, create_user, create_date, modify_user, modify_date) +values(3, 3, 1, 1, 132456, current, 132456, current); + + +/* 3 submissions for a single competition reviewed by 2 reviewers*/ + +insert into review(review_id, resource_id, submission_id, project_phase_id, scorecard_id, committed, score, initial_score, create_user, create_date, modify_user, modify_date) + values(1, 2, 1, 13, 30000410, 1, 100, 95, 132456, current, 132456, current); + +insert into review(review_id, resource_id, submission_id, project_phase_id, scorecard_id, committed, score, initial_score, create_user, create_date, modify_user, modify_date) + values(2, 3, 1, 13, 30000410, 1, 100, 95, 132456, current, 132456, current); + +insert into review(review_id, resource_id, submission_id, project_phase_id, scorecard_id, committed, score, initial_score, create_user, create_date, modify_user, modify_date) + values(3, 2, 2, 13, 30000410, 1, 65, 55, 132456, current, 132456, current); + +insert into review(review_id, resource_id, submission_id, project_phase_id, scorecard_id, committed, score, initial_score, create_user, create_date, modify_user, modify_date) + values(4, 3, 2, 13, 30000410, 1, 70, 60, 132456, current, 132456, current); + +insert into review(review_id, resource_id, submission_id, project_phase_id, scorecard_id, committed, score, initial_score, create_user, create_date, modify_user, modify_date) + values(5, 2, 3, 13, 30000410, 1, 90, 87, 132456, current, 132456, current); + +insert into review(review_id, resource_id, submission_id, project_phase_id, scorecard_id, committed, score, initial_score, create_user, create_date, modify_user, modify_date) + values(6, 3, 3, 13, 30000410, 1, 84.36, 78, 132456, current, 132456, current); diff --git a/postman/review-microservice.postman_collection.json b/postman/review-microservice.postman_collection.json new file mode 100755 index 0000000..7546b96 --- /dev/null +++ b/postman/review-microservice.postman_collection.json @@ -0,0 +1,75 @@ +{ + "variables": [], + "info": { + "name": "review-microservice", + "_postman_id": "6ebf34a8-a02f-658e-d692-ea743cb31056", + "description": "", + "schema": "https://schema.getpostman.com/json/collection/v2.0.0/collection.json" + }, + "item": [ + { + "name": "Get list of all Scorecards", + "request": { + "url": "{{apiUrl}}/v3/scorecards", + "method": "GET", + "header": [ + { + "key": "Content-Type", + "value": "application/json", + "description": "" + } + ], + "body": { + "mode": "raw", + "raw": "" + }, + "description": "" + }, + "response": [] + }, + { + "name": "Get list of all reviews Reviewer1", + "request": { + "url": "{{apiUrl}}/v3/reviews", + "method": "GET", + "header": [ + { + "key": "Authorization", + "value": "{{Reviewer1 Token}}", + "description": "" + }, + { + "key": "Content-Type", + "value": "application/json", + "description": "" + } + ], + "body": {}, + "description": "" + }, + "response": [] + }, + { + "name": "Get list of all reviews Reviewer2", + "request": { + "url": "{{apiUrl}}/v3/reviews", + "method": "GET", + "header": [ + { + "key": "Authorization", + "value": "{{Reviewer2 Token}}", + "description": "" + }, + { + "key": "Content-Type", + "value": "application/json", + "description": "" + } + ], + "body": {}, + "description": "" + }, + "response": [] + } + ] +} diff --git a/postman/review-microservice.postman_environment.json b/postman/review-microservice.postman_environment.json new file mode 100755 index 0000000..10b6eaf --- /dev/null +++ b/postman/review-microservice.postman_environment.json @@ -0,0 +1,28 @@ +{ + "id": "0e76601a-1e30-b51c-a3d4-daf7bcb2ab94", + "name": "review-microservice", + "values": [ + { + "enabled": true, + "key": "apiUrl", + "value": "http://localhost:8080", + "type": "text" + }, + { + "enabled": true, + "key": "Reviewer1 Token", + "value": "Bearer eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9.eyJyb2xlcyI6WyJyZXZpZXdlciJdLCJpc3MiOiJodHRwczovL2FwaS50b3Bjb2Rlci1kZXYuY29tIiwiaGFuZGxlIjoicmV2aWV3ZXIxIiwiZXhwIjoxNzY2Mjg5MjQ2LCJ1c2VySWQiOiIxMjM0IiwiaWF0IjoxNDUwOTI5MjQ2LCJlbWFpbCI6bnVsbCwianRpIjoiMTM2OWM2MDAtZTBhMS00NTI1LWE3YzctNTZiZTdkODEzZjUxIn0.4AOKcnvm4Wms_lY_RyYv61OHXCC-I__JnEPRZEN3-vM", + "type": "text" + }, + { + "enabled": true, + "key": "Reviewer2 Token", + "value": "Bearer eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9.eyJyb2xlcyI6WyJyZXZpZXdlciJdLCJpc3MiOiJodHRwczovL2FwaS50b3Bjb2Rlci1kZXYuY29tIiwiaGFuZGxlIjoicmV2aWV3ZXIyIiwiZXhwIjoxNzY2Mjg5MjQ2LCJ1c2VySWQiOiIxMjM1IiwiaWF0IjoxNDUwOTI5MjQ2LCJlbWFpbCI6bnVsbCwianRpIjoiMTM2OWM2MDAtZTBhMS00NTI1LWE3YzctNTZiZTdkODEzZjUxIn0.RrkQ7srca99ctABa_VM9VYediC2mcg4hPOiQ1Roou14", + "type": "text" + } + ], + "timestamp": 1507127109350, + "_postman_variable_scope": "environment", + "_postman_exported_at": "2017-10-04T14:29:09.546Z", + "_postman_exported_using": "Postman/4.10.7" +} diff --git a/service/pom.xml b/service/pom.xml index 51aa367..89376b9 100644 --- a/service/pom.xml +++ b/service/pom.xml @@ -11,7 +11,7 @@ 1.5.4 2.7.3 4.0.0 - 1.0.16-SNAPSHOT + 1.0.20-SNAPSHOT @@ -220,7 +220,7 @@ com.appirio ap-events-bus - 0.0.4-SNAPSHOT + 0.0.1-SNAPSHOT org.slf4j @@ -242,12 +242,12 @@ appirio-repo Appirio Maven Repository - file:///mnt/maven/repository + file:///Users/qun/Desktop/temp-maven-repo-master appirio-repo Appirio Maven Repository - file:///mnt/maven/repository + file:///Users/qun/Desktop/temp-maven-repo-master @@ -294,26 +294,20 @@ + + 1_temp_appirio_maven_repo + Appirio Maven Local Repository + file:///Users/qun/Desktop/temp-maven-repo-master + com.springsource.repository.bundles.release SpringSource Enterprise Bundle Repository - SpringSource Bundle Releases http://repository.springsource.com/maven/bundles/release - com.springsource.repository.bundles.external SpringSource Enterprise Bundle Repository - External Bundle Releases http://repository.springsource.com/maven/bundles/external - - Appirio Technology Maven Repository - http://maven.appirio.net:8080/ - - always - - - always - - From 31e28ce9117b2f5fb963ef98ae6e1f9dab729fd4 Mon Sep 17 00:00:00 2001 From: Qun Huang Date: Sat, 7 Oct 2017 02:04:32 +0800 Subject: [PATCH 02/19] updates to run the services locally --- service/pom.xml | 8 ++++---- service/sql/populate-db-samples.sql | 2 +- service/sql/review-process.sql | 2 +- 3 files changed, 6 insertions(+), 6 deletions(-) mode change 100644 => 100755 service/sql/populate-db-samples.sql mode change 100644 => 100755 service/sql/review-process.sql diff --git a/service/pom.xml b/service/pom.xml index 89376b9..96ae609 100644 --- a/service/pom.xml +++ b/service/pom.xml @@ -220,7 +220,7 @@ com.appirio ap-events-bus - 0.0.1-SNAPSHOT + 0.0.4-SNAPSHOT org.slf4j @@ -242,12 +242,12 @@ appirio-repo Appirio Maven Repository - file:///Users/qun/Desktop/temp-maven-repo-master + file:///mnt/maven/repository appirio-repo Appirio Maven Repository - file:///Users/qun/Desktop/temp-maven-repo-master + file:///mnt/maven/repository @@ -297,7 +297,7 @@ 1_temp_appirio_maven_repo Appirio Maven Local Repository - file:///Users/qun/Desktop/temp-maven-repo-master + file:///mnt/maven/repository com.springsource.repository.bundles.release diff --git a/service/sql/populate-db-samples.sql b/service/sql/populate-db-samples.sql old mode 100644 new mode 100755 index 5da42b3..84f4012 --- a/service/sql/populate-db-samples.sql +++ b/service/sql/populate-db-samples.sql @@ -394,4 +394,4 @@ INSERT INTO REVIEW_PROCESS ( 'PEER', 'PEER', 1 -); \ No newline at end of file +); diff --git a/service/sql/review-process.sql b/service/sql/review-process.sql old mode 100644 new mode 100755 index 53afeb2..cf85813 --- a/service/sql/review-process.sql +++ b/service/sql/review-process.sql @@ -90,4 +90,4 @@ CREATE TABLE SCORECARD_ITEM ( FOREIGN KEY (scorecardId) references SCORECARD(id), FOREIGN KEY (questionId) references SCORECARD_QUESTION(id), FOREIGN KEY (questionOptionId) references SCORECARD_QUESTION_OPTION(id) -); \ No newline at end of file +); From 9dfa9b36e658ff5ab96b61342dd9bf0c2b827d48 Mon Sep 17 00:00:00 2001 From: Qun Huang Date: Sat, 7 Oct 2017 12:49:16 +0800 Subject: [PATCH 03/19] update to use core.api 4.0.1-DEV --- service/pom.xml | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/service/pom.xml b/service/pom.xml index 96ae609..ce4fd3d 100644 --- a/service/pom.xml +++ b/service/pom.xml @@ -10,7 +10,7 @@ 1.0.0 1.5.4 2.7.3 - 4.0.0 + 4.0.1-DEV 1.0.20-SNAPSHOT @@ -105,7 +105,7 @@ - + org.javassist @@ -265,7 +265,7 @@ false - org.apache.maven.plugins From 2a83d453940209a1c35b14029c8c7086e1570e22 Mon Sep 17 00:00:00 2001 From: Qun Huang Date: Thu, 19 Oct 2017 10:43:31 -0700 Subject: [PATCH 04/19] add appirio dev repo --- service/pom.xml | 10 ++++++++++ 1 file changed, 10 insertions(+) diff --git a/service/pom.xml b/service/pom.xml index ce4fd3d..3ee3722 100644 --- a/service/pom.xml +++ b/service/pom.xml @@ -299,6 +299,16 @@ Appirio Maven Local Repository file:///mnt/maven/repository + + Appirio Technology Maven Repository + http://maven.topcoder-dev.com:8080/ + + always + + + always + + com.springsource.repository.bundles.release SpringSource Enterprise Bundle Repository - SpringSource Bundle Releases From 2c4a4cc10e2ffded452bc99506a5720ba6b69389 Mon Sep 17 00:00:00 2001 From: Qun Huang Date: Thu, 19 Oct 2017 11:07:26 -0700 Subject: [PATCH 05/19] new review services for admin app --- local/run.sh | 11 + ...eview-microservice.postman_collection.json | 598 ++++- ...view-microservice.postman_environment.json | 14 +- .../resourcefactory/ReviewFactory.java | 12 +- .../appirio/service/review/api/AutoPilot.java | 20 + .../appirio/service/review/api/Challenge.java | 35 + .../manager/ReviewAuthorizationManager.java | 21 +- .../service/review/dao/ChallengeDAO.java | 25 + .../appirio/service/review/dao/ReviewDAO.java | 113 +- .../review/manager/ChallengeManager.java | 40 + .../service/review/manager/ReviewManager.java | 134 +- .../review/resources/ReviewResource.java | 238 +- .../appirio/service/review/util/Helper.java | 62 + .../src/main/resources/new-relic-url-patterns | 7 +- .../resources/sql/challenge/get-by-id.sql | 13 + .../resources/sql/review/delete-review.sql | 5 + .../resources/sql/review/reopen-review.sql | 1 + .../sql/review/reset-aggregation.sql | 58 + .../resources/sql/review/reset-review.sql | 36 + .../resources/sql/review/update-autopilot.sql | 5 + .../test/resources/ReviewResourceTest.java | 4 +- swagger.yaml | 2161 +++++++++-------- 22 files changed, 2571 insertions(+), 1042 deletions(-) create mode 100755 local/run.sh create mode 100644 service/src/main/java/com/appirio/service/review/api/AutoPilot.java create mode 100644 service/src/main/java/com/appirio/service/review/api/Challenge.java create mode 100644 service/src/main/java/com/appirio/service/review/dao/ChallengeDAO.java create mode 100644 service/src/main/java/com/appirio/service/review/manager/ChallengeManager.java create mode 100644 service/src/main/java/com/appirio/service/review/util/Helper.java create mode 100644 service/src/main/resources/sql/challenge/get-by-id.sql create mode 100644 service/src/main/resources/sql/review/delete-review.sql create mode 100644 service/src/main/resources/sql/review/reopen-review.sql create mode 100644 service/src/main/resources/sql/review/reset-aggregation.sql create mode 100644 service/src/main/resources/sql/review/reset-review.sql create mode 100644 service/src/main/resources/sql/review/update-autopilot.sql diff --git a/local/run.sh b/local/run.sh new file mode 100755 index 0000000..7d47abb --- /dev/null +++ b/local/run.sh @@ -0,0 +1,11 @@ +export IP=192.168.1.2 +export OLTP_USER=informix +export OLTP_PW=1nf0rm1x +export OLTP_URL=jdbc:informix-sqli://$IP:2021/tcs_catalog:INFORMIXSERVER=informixoltp_tcp;IFX_LOCK_MODE_WAIT=5;OPTCOMPIND=0;STMT_CACHE=1 +export DW_USER=informix +export DW_PW=1nf0rm1x +export DW_URL=jdbc:informix-sqli://$IP:2021/common_dw:INFORMIXSERVER=informixoltp_tcp;IFX_LOCK_MODE_WAIT=5;OPTCOMPIND=0;STMT_CACHE=1 +export TC_JWT_KEY=secret + + +java -jar ../service/target/review-microservice-*.jar server ../service/src/main/resources/review-service.yaml \ No newline at end of file diff --git a/postman/review-microservice.postman_collection.json b/postman/review-microservice.postman_collection.json index 7546b96..b9a2646 100755 --- a/postman/review-microservice.postman_collection.json +++ b/postman/review-microservice.postman_collection.json @@ -2,11 +2,597 @@ "variables": [], "info": { "name": "review-microservice", - "_postman_id": "6ebf34a8-a02f-658e-d692-ea743cb31056", + "_postman_id": "b6914582-b6ca-29d2-9b12-de70d5e4cb81", "description": "", "schema": "https://schema.getpostman.com/json/collection/v2.0.0/collection.json" }, "item": [ + { + "name": "Delete Review", + "description": "", + "item": [ + { + "name": "Anonymous - Delete Review - 401", + "request": { + "url": "{{apiUrl}}/v3/reviews/71492", + "method": "DELETE", + "header": [], + "body": { + "mode": "raw", + "raw": "" + }, + "description": "" + }, + "response": [] + }, + { + "name": "Admin - Delete Review", + "request": { + "url": "{{apiUrl}}/v3/reviews/71492", + "method": "DELETE", + "header": [ + { + "key": "Authorization", + "value": "{{Administrator Token}}", + "description": "" + }, + { + "key": "Content-Type", + "value": "application/json", + "description": "" + } + ], + "body": { + "mode": "raw", + "raw": "" + }, + "description": "" + }, + "response": [] + }, + { + "name": "Admin - Delete Review - Not Found", + "request": { + "url": "{{apiUrl}}/v3/reviews/4582", + "method": "DELETE", + "header": [ + { + "key": "Authorization", + "value": "{{Administrator Token}}", + "description": "" + }, + { + "key": "Content-Type", + "value": "application/json", + "description": "" + } + ], + "body": { + "mode": "raw", + "raw": "" + }, + "description": "" + }, + "response": [] + }, + { + "name": "Non-Admin - Delete Review", + "request": { + "url": "{{apiUrl}}/v3/reviews/71492", + "method": "DELETE", + "header": [ + { + "key": "Authorization", + "value": "{{Reviewer1 Token}}", + "description": "" + }, + { + "key": "Content-Type", + "value": "application/json", + "description": "" + } + ], + "body": { + "mode": "raw", + "raw": "" + }, + "description": "" + }, + "response": [] + } + ] + }, + { + "name": "Re-open Review", + "description": "", + "item": [ + { + "name": "Admin - Reopen Review", + "request": { + "url": "{{apiUrl}}/v3/reviews/71492/reopen", + "method": "POST", + "header": [ + { + "key": "Authorization", + "value": "{{Administrator Token}}", + "description": "" + }, + { + "key": "Content-Type", + "value": "application/json", + "description": "" + } + ], + "body": { + "mode": "raw", + "raw": "" + }, + "description": "" + }, + "response": [] + }, + { + "name": "Anonymous - Reopen Review - 401", + "request": { + "url": "{{apiUrl}}/v3/reviews/71492/reopen", + "method": "POST", + "header": [ + { + "key": "Authorization", + "value": "{{Administrator Token}}", + "description": "", + "disabled": true + }, + { + "key": "Content-Type", + "value": "application/json", + "description": "" + } + ], + "body": { + "mode": "raw", + "raw": "" + }, + "description": "" + }, + "response": [] + }, + { + "name": "Admin - Reopen Review - Not Found", + "request": { + "url": "{{apiUrl}}/v3/reviews/7452/reopen", + "method": "POST", + "header": [ + { + "key": "Authorization", + "value": "{{Administrator Token}}", + "description": "" + }, + { + "key": "Content-Type", + "value": "application/json", + "description": "" + } + ], + "body": { + "mode": "raw", + "raw": "" + }, + "description": "" + }, + "response": [] + }, + { + "name": "Non-Admin - Reopen Review", + "request": { + "url": "{{apiUrl}}/v3/reviews/71492/reopen", + "method": "POST", + "header": [ + { + "key": "Authorization", + "value": "{{Reviewer1 Token}}", + "description": "" + }, + { + "key": "Content-Type", + "value": "application/json", + "description": "" + } + ], + "body": { + "mode": "raw", + "raw": "" + }, + "description": "" + }, + "response": [] + } + ] + }, + { + "name": "Reset Aggregation", + "description": "", + "item": [ + { + "name": "Admin - Reset Aggregation", + "request": { + "url": "{{apiUrl}}/v3/reviews/30005520/resetAggregation", + "method": "POST", + "header": [ + { + "key": "Authorization", + "value": "{{Administrator Token}}", + "description": "" + }, + { + "key": "Content-Type", + "value": "application/json", + "description": "" + } + ], + "body": { + "mode": "raw", + "raw": "" + }, + "description": "" + }, + "response": [] + }, + { + "name": "Non Admin - Reset Aggregation", + "request": { + "url": "{{apiUrl}}/v3/reviews/30005520/resetAggregation", + "method": "POST", + "header": [ + { + "key": "Authorization", + "value": "{{Reviewer1 Token}}", + "description": "" + }, + { + "key": "Content-Type", + "value": "application/json", + "description": "" + } + ], + "body": { + "mode": "raw", + "raw": "" + }, + "description": "" + }, + "response": [] + }, + { + "name": "Anonymous - Reset Aggregation - 401", + "request": { + "url": "{{apiUrl}}/v3/reviews/30005520/resetAggregation", + "method": "POST", + "header": [ + { + "key": "Authorization", + "value": "{{Administrator Token}}", + "description": "", + "disabled": true + }, + { + "key": "Content-Type", + "value": "application/json", + "description": "" + } + ], + "body": { + "mode": "raw", + "raw": "" + }, + "description": "" + }, + "response": [] + }, + { + "name": "Admin - Reset Aggregation - Not Found", + "request": { + "url": "{{apiUrl}}/v3/reviews/13/resetAggregation", + "method": "POST", + "header": [ + { + "key": "Authorization", + "value": "{{Administrator Token}}", + "description": "" + }, + { + "key": "Content-Type", + "value": "application/json", + "description": "" + } + ], + "body": { + "mode": "raw", + "raw": "" + }, + "description": "" + }, + "response": [] + }, + { + "name": "Admin - Reset Aggregation - Challenge Not Active - 400", + "request": { + "url": "{{apiUrl}}/v3/reviews/11/resetAggregation", + "method": "POST", + "header": [ + { + "key": "Authorization", + "value": "{{Administrator Token}}", + "description": "" + }, + { + "key": "Content-Type", + "value": "application/json", + "description": "" + } + ], + "body": { + "mode": "raw", + "raw": "" + }, + "description": "" + }, + "response": [] + } + ] + }, + { + "name": "Reset Review", + "description": "", + "item": [ + { + "name": "Admin - Reset Review", + "request": { + "url": "{{apiUrl}}/v3/reviews/30005520/resetReview", + "method": "POST", + "header": [ + { + "key": "Authorization", + "value": "{{Administrator Token}}", + "description": "" + }, + { + "key": "Content-Type", + "value": "application/json", + "description": "" + } + ], + "body": { + "mode": "raw", + "raw": "" + }, + "description": "" + }, + "response": [] + }, + { + "name": "Admin - Reset Review - Not Found", + "request": { + "url": "{{apiUrl}}/v3/reviews/30005520/resetReview", + "method": "POST", + "header": [ + { + "key": "Authorization", + "value": "{{Administrator Token}}", + "description": "" + }, + { + "key": "Content-Type", + "value": "application/json", + "description": "" + } + ], + "body": { + "mode": "raw", + "raw": "" + }, + "description": "" + }, + "response": [] + }, + { + "name": "Non Admin - Reset Review", + "request": { + "url": "{{apiUrl}}/v3/reviews/30005520/resetReview", + "method": "POST", + "header": [ + { + "key": "Authorization", + "value": "{{Reviewer1 Token}}", + "description": "" + }, + { + "key": "Content-Type", + "value": "application/json", + "description": "" + } + ], + "body": { + "mode": "raw", + "raw": "" + }, + "description": "" + }, + "response": [] + }, + { + "name": "Anonymous - Reset Review - Unauthorized - 401", + "request": { + "url": "{{apiUrl}}/v3/reviews/30005520/resetReview", + "method": "POST", + "header": [ + { + "key": "Content-Type", + "value": "application/json", + "description": "" + } + ], + "body": { + "mode": "raw", + "raw": "" + }, + "description": "" + }, + "response": [] + } + ] + }, + { + "name": "Update Autopilot", + "description": "", + "item": [ + { + "name": "Admin - Enable Autopilot", + "request": { + "url": "{{apiUrl}}/v3/reviews/30005520/updateAutopilot", + "method": "POST", + "header": [ + { + "key": "Authorization", + "value": "{{Administrator Token}}", + "description": "" + }, + { + "key": "Content-Type", + "value": "application/json", + "description": "" + } + ], + "body": { + "mode": "raw", + "raw": "{\n \"param\" : {\n \t\"enable\" : true\n }\n \n}" + }, + "description": "" + }, + "response": [] + }, + { + "name": "Admin - Enable Autopilot - Bad Request - 400", + "request": { + "url": "{{apiUrl}}/v3/reviews/30005520/updateAutopilot", + "method": "POST", + "header": [ + { + "key": "Authorization", + "value": "{{Administrator Token}}", + "description": "" + }, + { + "key": "Content-Type", + "value": "application/json", + "description": "" + } + ], + "body": { + "mode": "raw", + "raw": "{\n \"param\" : {\n \t\"enable\" : \n }\n \n}" + }, + "description": "" + }, + "response": [] + }, + { + "name": "Admin - Disable Autopilot", + "request": { + "url": "{{apiUrl}}/v3/reviews/30005520/updateAutopilot", + "method": "POST", + "header": [ + { + "key": "Authorization", + "value": "{{Administrator Token}}", + "description": "" + }, + { + "key": "Content-Type", + "value": "application/json", + "description": "" + } + ], + "body": { + "mode": "raw", + "raw": "{\n \"param\" : {\n \t\"enable\" : false\n }\n \n}" + }, + "description": "" + }, + "response": [] + }, + { + "name": "Non Admin - Enable Autopilot", + "request": { + "url": "{{apiUrl}}/v3/reviews/30005520/updateAutopilot", + "method": "POST", + "header": [ + { + "key": "Authorization", + "value": "{{Reviewer1 Token}}", + "description": "" + }, + { + "key": "Content-Type", + "value": "application/json", + "description": "" + } + ], + "body": { + "mode": "raw", + "raw": "{\n \"param\" : {\n \t\"enable\" : true\n }\n \n}" + }, + "description": "" + }, + "response": [] + }, + { + "name": "Anonymous - Enable Autopilot - Unauthorized - 401", + "request": { + "url": "{{apiUrl}}/v3/reviews/30005520/updateAutopilot", + "method": "POST", + "header": [ + { + "key": "Content-Type", + "value": "application/json", + "description": "" + } + ], + "body": { + "mode": "raw", + "raw": "{\n \"param\" : {\n \t\"enable\" : true\n }\n \n}" + }, + "description": "" + }, + "response": [] + }, + { + "name": "Admin - Enable Autopilot - Not Found", + "request": { + "url": "{{apiUrl}}/v3/reviews/13/updateAutopilot", + "method": "POST", + "header": [ + { + "key": "Authorization", + "value": "{{Administrator Token}}", + "description": "" + }, + { + "key": "Content-Type", + "value": "application/json", + "description": "" + } + ], + "body": { + "mode": "raw", + "raw": "{\n \"param\" : {\n \t\"enable\" : true\n }\n \n}" + }, + "description": "" + }, + "response": [] + } + ] + }, { "name": "Get list of all Scorecards", "request": { @@ -44,7 +630,10 @@ "description": "" } ], - "body": {}, + "body": { + "mode": "raw", + "raw": "" + }, "description": "" }, "response": [] @@ -66,7 +655,10 @@ "description": "" } ], - "body": {}, + "body": { + "mode": "raw", + "raw": "" + }, "description": "" }, "response": [] diff --git a/postman/review-microservice.postman_environment.json b/postman/review-microservice.postman_environment.json index 10b6eaf..748b0f3 100755 --- a/postman/review-microservice.postman_environment.json +++ b/postman/review-microservice.postman_environment.json @@ -1,5 +1,5 @@ { - "id": "0e76601a-1e30-b51c-a3d4-daf7bcb2ab94", + "id": "de37a461-f5ae-23ea-9c06-fdf7e8e05622", "name": "review-microservice", "values": [ { @@ -19,10 +19,16 @@ "key": "Reviewer2 Token", "value": "Bearer eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9.eyJyb2xlcyI6WyJyZXZpZXdlciJdLCJpc3MiOiJodHRwczovL2FwaS50b3Bjb2Rlci1kZXYuY29tIiwiaGFuZGxlIjoicmV2aWV3ZXIyIiwiZXhwIjoxNzY2Mjg5MjQ2LCJ1c2VySWQiOiIxMjM1IiwiaWF0IjoxNDUwOTI5MjQ2LCJlbWFpbCI6bnVsbCwianRpIjoiMTM2OWM2MDAtZTBhMS00NTI1LWE3YzctNTZiZTdkODEzZjUxIn0.RrkQ7srca99ctABa_VM9VYediC2mcg4hPOiQ1Roou14", "type": "text" + }, + { + "enabled": true, + "key": "Administrator Token", + "value": "Bearer eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9.eyJyb2xlcyI6WyJhZG1pbmlzdHJhdG9yIl0sImlzcyI6Imh0dHBzOi8vYXBpLnRvcGNvZGVyLWRldi5jb20iLCJoYW5kbGUiOiJhZG1pbiIsImV4cCI6MTc2NjI4OTI0NiwidXNlcklkIjoiNTU1NSIsImlhdCI6MTQ1MDkyOTI0NiwiZW1haWwiOm51bGwsImp0aSI6IjEzNjljNjAwLWUwYTEtNDUyNS1hN2M3LTU2YmU3ZDgxM2Y1MSJ9.Wg4nPCva9opNyrYQTngSoXetXr-W-ZSDtD2jae1gswk", + "type": "text" } ], - "timestamp": 1507127109350, + "timestamp": 1507999856202, "_postman_variable_scope": "environment", - "_postman_exported_at": "2017-10-04T14:29:09.546Z", - "_postman_exported_using": "Postman/4.10.7" + "_postman_exported_at": "2017-10-14T21:32:17.437Z", + "_postman_exported_using": "Postman/5.3.0" } diff --git a/service/src/main/java/com/appirio/service/resourcefactory/ReviewFactory.java b/service/src/main/java/com/appirio/service/resourcefactory/ReviewFactory.java index 9abab88..3b126d7 100644 --- a/service/src/main/java/com/appirio/service/resourcefactory/ReviewFactory.java +++ b/service/src/main/java/com/appirio/service/resourcefactory/ReviewFactory.java @@ -3,6 +3,7 @@ import io.dropwizard.setup.Environment; import com.appirio.service.review.ReviewServiceConfiguration; +import com.appirio.service.review.manager.ChallengeManager; import com.appirio.service.review.manager.ReviewManager; import com.appirio.service.review.resources.ReviewResource; import com.appirio.service.supply.resources.ResourceFactory; @@ -10,8 +11,12 @@ /** * Factory for ReviewResource - * - * @author rrecharla@appirio.com + * + * v1.1 Updates : https://www.topcoder.com/challenge-details/30059779/?type=develop + * Updated getResourceInstance() to set the ChallengeManager field. + * + * @author rrecharla@appirio.com, TCSCODE + * @version 1.1 */ public class ReviewFactory implements ResourceFactory { @@ -43,6 +48,7 @@ public ReviewFactory(ReviewServiceConfiguration config, Environment env) { @Override public ReviewResource getResourceInstance() throws SupplyException { final ReviewManager reviewManager = new ReviewManager(); - return new ReviewResource(reviewManager); + final ChallengeManager challengeManager = new ChallengeManager(); + return new ReviewResource(reviewManager, challengeManager); } } diff --git a/service/src/main/java/com/appirio/service/review/api/AutoPilot.java b/service/src/main/java/com/appirio/service/review/api/AutoPilot.java new file mode 100644 index 0000000..7b22a8a --- /dev/null +++ b/service/src/main/java/com/appirio/service/review/api/AutoPilot.java @@ -0,0 +1,20 @@ +package com.appirio.service.review.api; + +import javax.validation.constraints.NotNull; + +import lombok.AllArgsConstructor; +import lombok.Getter; +import lombok.NoArgsConstructor; +import lombok.Setter; + +/** + * This class represents a challenge instance + */ +@AllArgsConstructor +@NoArgsConstructor +@Getter +@Setter +public class AutoPilot { + @NotNull + private Boolean enable; +} \ No newline at end of file diff --git a/service/src/main/java/com/appirio/service/review/api/Challenge.java b/service/src/main/java/com/appirio/service/review/api/Challenge.java new file mode 100644 index 0000000..eafa289 --- /dev/null +++ b/service/src/main/java/com/appirio/service/review/api/Challenge.java @@ -0,0 +1,35 @@ +package com.appirio.service.review.api; + +import lombok.AllArgsConstructor; +import lombok.Getter; +import lombok.NoArgsConstructor; +import lombok.Setter; + +/** + * This class represents a challenge instance + */ +@AllArgsConstructor +@NoArgsConstructor +@Getter +@Setter +public class Challenge { + /** + * The challenge id. + */ + private Long id; + + /** + * The challenge name. + */ + private String name; + + /** + * The challenge status. + */ + private String status; + + /** + * The challenge current active phase. + */ + private String activePhase; +} \ No newline at end of file diff --git a/service/src/main/java/com/appirio/service/review/authorization/manager/ReviewAuthorizationManager.java b/service/src/main/java/com/appirio/service/review/authorization/manager/ReviewAuthorizationManager.java index ec05fd4..8346f8f 100644 --- a/service/src/main/java/com/appirio/service/review/authorization/manager/ReviewAuthorizationManager.java +++ b/service/src/main/java/com/appirio/service/review/authorization/manager/ReviewAuthorizationManager.java @@ -11,9 +11,16 @@ * Review authorization manager * * @see AuthorizationManager + *

+ * v1.1 (ADMIN APP CHALLENGE FEE AND REVIEW MANAGEMENT API) + *

    + *
  • Updated canRead() to allow the administrator to read a review
  • + *
  • Updated canReadWriteDelete() to allow administrator to delete reviews
  • + *
+ *

* - * @author mdesiderio@appirio.com - * + * @author mdesiderio@appirio.com, TCSCODER + * @version 1.1 */ public class ReviewAuthorizationManager extends AuthorizationManager { @@ -91,7 +98,8 @@ public boolean canRead(Review model, AuthorizationContext context) { // -- The user is the reviewer resource // -- The user is the submitter and the review is committed return principalIsReviewer(model, context) - || (principalIsSubmitter(model, context) && reviewIsCommitted(model)); + || (principalIsSubmitter(model, context) && reviewIsCommitted(model)) + || context.getPrincipal().hasRole("administrator"); } /** @@ -119,10 +127,7 @@ public boolean canReadWrite(Review model, AuthorizationContext context) { */ @Override public boolean canReadWriteDelete(Review model, AuthorizationContext context) { - // Only administrators can delete reviews, and this functionality is not - // yet implemented in the - // review microservice - return false; + // Only administrators are allowed to delete reviews. + return context.getPrincipal().hasRole("administrator"); } - } diff --git a/service/src/main/java/com/appirio/service/review/dao/ChallengeDAO.java b/service/src/main/java/com/appirio/service/review/dao/ChallengeDAO.java new file mode 100644 index 0000000..cf50dd3 --- /dev/null +++ b/service/src/main/java/com/appirio/service/review/dao/ChallengeDAO.java @@ -0,0 +1,25 @@ +package com.appirio.service.review.dao; + +import org.skife.jdbi.v2.sqlobject.Bind; +import com.appirio.service.review.api.Challenge; +import com.appirio.supply.dataaccess.DatasourceName; +import com.appirio.supply.dataaccess.SqlQueryFile; + +/** + * DAO to access challenge related data + * + * @author TCSCODER + * @version 1.0 + */ +@DatasourceName("oltp") +public interface ChallengeDAO { + + /** + * Get a challenge by id. + * + * @param challengeId the id of the challenge to retrieve. + * @return The Challenge instance + */ + @SqlQueryFile("sql/challenge/get-by-id.sql") + Challenge getChallenge(@Bind("challengeId") Long challengeId); +} \ No newline at end of file diff --git a/service/src/main/java/com/appirio/service/review/dao/ReviewDAO.java b/service/src/main/java/com/appirio/service/review/dao/ReviewDAO.java index f264ee4..082e3a2 100644 --- a/service/src/main/java/com/appirio/service/review/dao/ReviewDAO.java +++ b/service/src/main/java/com/appirio/service/review/dao/ReviewDAO.java @@ -18,13 +18,24 @@ import java.util.Map; /** - * DAO to handle review data + * DAO to handle review data. + * + *

+ * Version 1.1 (ADMIN APP CHALLENGE FEE AND REVIEW MANAGEMENT API) + *

    + *
  • Added method resetAggregation()
  • + *
  • Added method resetReview()
  • + *
  • Added method updateAutoPilot()
  • + *
  • Added method reopenReview()
  • + *
  • Added method deleteReview()
  • + *
+ *

* - * @author mdesiderio@appirio.com + * @author mdesiderio@appirio.com, TCSCODER + * @version 1.1 */ @DatasourceName("oltp") public interface ReviewDAO { - /** * Get a single review by id. Authenticated version to be invoked by * managers / resources @@ -35,8 +46,7 @@ public interface ReviewDAO { */ @AuthManager(ReviewAuthorizationManager.class) @SqlQueryFile("sql/review/review-by-id-query.sql") - Review getReview(@Bind("reviewId") Long reviewId, - @AuthContext AuthorizationContext authContext); + Review getReview(@Bind("reviewId") Long reviewId, @AuthContext AuthorizationContext authContext); /** * Get a single review by id. Unauthenticated version for internal use @@ -49,16 +59,15 @@ Review getReview(@Bind("reviewId") Long reviewId, // TODO: document this @SqlUpdateFile("sql/review/review-update.sql") - void updateReview(@Audit @Validate @BindBean Review review, - @AuditActionPerformer @Bind("userId") Long userId); + void updateReview(@Audit @Validate @BindBean Review review, @AuditActionPerformer @Bind("userId") Long userId); // TODO: document this // TODO: Test this @SqlUpdateFile("sql/review/review-update-user-editable-fields.sql") void updateReviewUserEditableFields( - @AuthManager(ReviewAuthorizationManager.class) @Audit @Validate @BindBean Review review, - @AuditActionPerformer @Bind("userId") Long userId, - @AuthContext AuthorizationContext authContext); + @AuthManager(ReviewAuthorizationManager.class) @Audit @Validate @BindBean Review review, + @AuditActionPerformer @Bind("userId") Long userId, + @AuthContext AuthorizationContext authContext); /** * Creates a new review @@ -67,8 +76,7 @@ void updateReviewUserEditableFields( * @param userId user id */ @SqlUpdateFile("sql/review/review-insert.sql") - void createReview(@Audit @Validate @BindBean Review review, - @AuditActionPerformer @Bind("userId") Long userId); + void createReview(@Audit @Validate @BindBean Review review, @AuditActionPerformer @Bind("userId") Long userId); /** * For a given user and a given challenge, returns the counts of how many @@ -79,8 +87,7 @@ void createReview(@Audit @Validate @BindBean Review review, * @return returns counts of pending, submitted and total reviews */ @SqlQueryFile("sql/review/user-review-count-query.sql") - Map getUserReviewsCount(@Bind("userId") Long userId, - @Bind("challengeId") Long challengeId); + Map getUserReviewsCount(@Bind("userId") Long userId, @Bind("challengeId") Long challengeId); /** * Submissions associated with a challenge, not reviewed by the user, not @@ -95,9 +102,8 @@ Map getUserReviewsCount(@Bind("userId") Long userId, * @return list of submissions */ @SqlQueryFile("sql/review/next-submission-to-review-by-required-reviews-query.sql") - List> getNextSubmissionToReviewByRequiredReviews( - @Bind("userId") Long userId, @Bind("challengeId") Long challengeId, - @Bind("requiredReviews") Long requiredReviews); + List> getNextSubmissionToReviewByRequiredReviews(@Bind("userId") Long userId, + @Bind("challengeId") Long challengeId, @Bind("requiredReviews") Long requiredReviews); /** * find the submission not submitted by the user, not yet reviewed by the @@ -108,8 +114,8 @@ List> getNextSubmissionToReviewByRequiredReviews( * @return list of submissions */ @SqlQueryFile("sql/review/next-submission-to-revew-by-least-reviewed-query.sql") - List> getNextSubmissionToReviewByLeastReviewed( - @Bind("userId") Long userId, @Bind("challengeId") Long challengeId); + List> getNextSubmissionToReviewByLeastReviewed(@Bind("userId") Long userId, + @Bind("challengeId") Long challengeId); /** * Finds review based on API user input @@ -121,10 +127,9 @@ List> getNextSubmissionToReviewByLeastReviewed( */ @SqlQueryFile("sql/review/review-query.sql") @AuthManager(ReviewAuthorizationManager.class) - QueryResult> getReviews(@ApiUser AuthUser auth, - @ApiQueryInput QueryParameter queryParameter, - @AuthContext AuthorizationContext authContext); - + QueryResult> getReviews(@ApiUser AuthUser auth, @ApiQueryInput QueryParameter queryParameter, + @AuthContext AuthorizationContext authContext); + /** * Check whether the phase is closed for review * @@ -133,12 +138,66 @@ QueryResult> getReviews(@ApiUser AuthUser auth, */ @SqlQueryFile("sql/review/is-review-phase-closed.sql") Integer isPhaseClosedForReview(@Bind("reviewId") Long reviewId); - + // TODO: // Document this // This query is using select *, please specify only needed fields @SqlQueryFile("sql/review/get-phase-by-challengeid-phasetype.sql") - Map getPhaseId(@Bind("challengeId") Long challengeId, @Bind("phaseTypeId") Long phaseTypeId); - - + Map getPhaseId(@Bind("challengeId") Long challengeId, @Bind("phaseTypeId") Long phaseTypeId); + + /** + * Resets the aggregation of the challenge identified by the given challengeId. + * + * @param challengeId The id of the challenge for which to reset the aggregation. + * @param userId The id of the user who performs the reset operation. + * + * @since 1.1 + */ + @SqlUpdateFile("sql/review/reset-aggregation.sql") + void resetAggregation(@Bind("challengeId") Long challengeId, @Bind("userId") Long userId); + + /** + * Resets the review of the challenge identified by the given challengeId + * + * @param challengeId The id of the challenge dor which to reset the review. + * @param userId The id of the user who performs the operation. + * + * @since 1.1 + */ + @SqlUpdateFile("sql/review/reset-review.sql") + void resetReview(@Bind("challengeId") Long challengeId, @Bind("userId") Long userId); + + /** + * Updates the autopilot setting for the challenge identified by the given id. + * + * @param challengeId The id of the challenge for which to update the autopilot setting. + * @param value The autopilot setting value ("On" or "Off") + * @param userId The id of the user who is performing the update operation. + * + * @since 1.1 + */ + @SqlUpdateFile("sql/review/update-autopilot.sql") + void updateAutoPilot(@Bind("challengeId") Long challengeId, @Bind("value") String value, + @Bind("userId") Long userId); + + /** + * Reopens the review identified by the given reviewId. + * + * @param reviewId The id of the review to reopen. + * @param userId The id of the user who is performing the operation. it is used for auditing purpose. + * + * @since 1.1 + */ + @SqlUpdateFile("sql/review/reopen-review.sql") + void reopenReview(@Bind("reviewId") Long reviewId, @Bind("userId") Long userId); + + /** + * Deletes the review identified by the given reviewId from the persistence. + * + * @param reviewId The id of the review to delete. + * + * @since 1.1 + */ + @SqlUpdateFile("sql/review/delete-review.sql") + void deleteReview(@Bind("reviewId") Long reviewId); } diff --git a/service/src/main/java/com/appirio/service/review/manager/ChallengeManager.java b/service/src/main/java/com/appirio/service/review/manager/ChallengeManager.java new file mode 100644 index 0000000..c93f17f --- /dev/null +++ b/service/src/main/java/com/appirio/service/review/manager/ChallengeManager.java @@ -0,0 +1,40 @@ +package com.appirio.service.review.manager; + +import com.appirio.service.review.api.Challenge; +import com.appirio.service.review.dao.ChallengeDAO; +import com.appirio.supply.DAOFactory; +import com.appirio.supply.SupplyException; + +/** + * This class manages the challenge data. it uses the ChallengeDAO instance to access the persistence + * + * @author TCSCODER + * @version 1.0 + */ +public class ChallengeManager { + + /** + * The ChallengeDAO to be used to access the challenge data in the persistence. + */ + private ChallengeDAO challengeDAO; + + /** + * Creates a new instance of ChallengeManager and initializes the ChallengeDAO instance field. + */ + public ChallengeManager() throws SupplyException { + this.challengeDAO = DAOFactory.getInstance().createDAO(ChallengeDAO.class); + } + + /** + * Gets a challenge by the given id. returns null if such challenge does not exist. + * + * @param challengeId Id of the challenge to retrieve. + * + * @return The retrieved Challenge. + * + * @throws SupplyException If any error occurs during the operation. + */ + public Challenge getChallenge(Long challengeId) throws SupplyException { + return challengeDAO.getChallenge(challengeId); + } +} \ No newline at end of file diff --git a/service/src/main/java/com/appirio/service/review/manager/ReviewManager.java b/service/src/main/java/com/appirio/service/review/manager/ReviewManager.java index 499a687..84a6ae3 100644 --- a/service/src/main/java/com/appirio/service/review/manager/ReviewManager.java +++ b/service/src/main/java/com/appirio/service/review/manager/ReviewManager.java @@ -1,7 +1,9 @@ package com.appirio.service.review.manager; +import com.appirio.service.review.api.Challenge; import com.appirio.service.review.api.Review; import com.appirio.service.review.dao.ReviewDAO; +import com.appirio.service.review.util.Helper; import com.appirio.supply.AuthorizationException; import com.appirio.supply.DAOFactory; import com.appirio.supply.SupplyException; @@ -17,7 +19,21 @@ /** * Manager for Review related business logic * - * @author dbabu@appirio.com + *

+ * Version 1.1 (ADMIN APP CHALLENGE FEE AND REVIEW MANAGEMENT API) + *

    + *
  • Added method resetAggregation()
  • + *
  • Added method resetReview()
  • + *
  • Added method updateAutoPilot()
  • + *
  • Added method reopenReview()
  • + *
  • Added method deleteReview()
  • + *
  • Added RESET_AGGREGATION_VALID_PHASES field
  • + *
  • Added RESET_REVIEW_VALID_PHASES field
  • + *
+ *

+ * + * @author dbabu@appirio.com, TCSCODER + * @version 1.1 * */ public class ReviewManager { @@ -26,7 +42,23 @@ public class ReviewManager { * DAO for review */ private ReviewDAO reviewDAO; - + + /** + * The valid phases which reset aggregation can be run on. + * + * @since 1.1 + */ + private static final String[] RESET_AGGREGATION_VALID_PHASES = new String[] { "Aggregation", "Final Fix", + "Final Review" }; + + /** + * The valid phases which reset review can be run on. + * + * @since 1.1 + */ + private static final String[] RESET_REVIEW_VALID_PHASES = new String[] { "Review", "Appeals", "Appeals Response", + "Aggregation", "Final Fix", "Final Review" }; + /** * Default constructor that initializes the DAO * @throws SupplyException exception for the supply @@ -67,7 +99,7 @@ public void updateReview(Review review, Long userId) { public void updateReviewUserEditableFields(Review review, Long userId, AuthorizationContext authContext) { reviewDAO.updateReviewUserEditableFields(review, userId, authContext); } - + /** * Gets the count of how many reviews a user has completed in a challenge, it includes * the following fields: @@ -97,8 +129,8 @@ public Map getUserReviewsCount(Long userId, Long challengeId) { * @param requiredReviews number of required reviews * @return list of submissions */ - public List> getNextSubmissionToReviewByRequiredReviews( - Long userId, Long challengeId, long requiredReviews) { + public List> getNextSubmissionToReviewByRequiredReviews(Long userId, Long challengeId, + long requiredReviews) { return reviewDAO.getNextSubmissionToReviewByRequiredReviews(userId, challengeId, requiredReviews); } @@ -116,8 +148,7 @@ public List> getNextSubmissionToReviewByRequiredReviews( * @param challengeId id of the challenge * @return list of submissions */ - public List> getNextSubmissionToReviewByLeastReviewed( - Long userId, Long challengeId) { + public List> getNextSubmissionToReviewByLeastReviewed(Long userId, Long challengeId) { return reviewDAO.getNextSubmissionToReviewByLeastReviewed(userId, challengeId); } @@ -140,9 +171,96 @@ public void createReview(Review review, Long userId) { * @return list of reviews * @throws AuthorizationException exception for authorization */ - public QueryResult> getReviews(AuthUser auth, QueryParameter queryParameter, AuthorizationContext authContext) throws SupplyException { + public QueryResult> getReviews(AuthUser auth, QueryParameter queryParameter, + AuthorizationContext authContext) throws SupplyException { LimitQueryHandler.limitQuery(queryParameter); return reviewDAO.getReviews(auth, queryParameter, authContext); } + /** + * Resets the aggregation of the given challenge. + * + * @param challenge the the Challenge for which to reset the aggregation. + * @param userId id of the user who is performing the the operation. + * + * @throws SupplyException If any error occurs during the operation. + * + * @since 1.1 + */ + public void resetAggregation(Challenge challenge, Long userId) throws SupplyException { + // Check if the challenge is active + Helper.validateChallengeStatus(challenge, "active"); + + // Check if resetting the aggregation is allowed in the current challenge active phase. + Helper.validateValidPhases(challenge, RESET_AGGREGATION_VALID_PHASES); + + // Reset the aggregation phase. + reviewDAO.resetAggregation(challenge.getId(), userId); + } + + /** + * Resets the review of the given challenge. + * + * @param challenge the Challenge for which to reset the review + * @param userId id of the user who resets the review. + * + * @throws SupplyException If any error occurs during the operation. + * + * @since 1.1 + */ + public void resetReview(Challenge challenge, Long userId) throws SupplyException { + // Check if the challenge is active + Helper.validateChallengeStatus(challenge, "active"); + + // Check if resetting the review is allowed in the current challenge active phase. + Helper.validateValidPhases(challenge, RESET_REVIEW_VALID_PHASES); + + // Reset the aggregation. + reviewDAO.resetAggregation(challenge.getId(), userId); + + // Reset the review + reviewDAO.resetReview(challenge.getId(), userId); + } + + /** + * Updates the Autopilot for the challenge identified by the given challengeId. + * + * @param challengeId the id of the challenge for which to update autopilot setting. + * @param enableAutopilot The flag indicating whether to enable/disable the autopilot setting for the challenge. + * @param userId id of the user who is performing the operation, it is used for auditing purpose. + * + * @throws SupplyException If any error occurs during the operation. + * + * @since 1.1 + */ + public void updateAutoPilot(Long challengeId, Boolean enableAutoPilot, Long userId) throws SupplyException { + reviewDAO.updateAutoPilot(challengeId, enableAutoPilot ? "On" : "Off", userId); + } + + /** + * Reopens the review identified by the given reviewId. + * + * @param reviewId the id of the review to reopen + * @param userId id of the user who is performing the operation. + * + * @throws SupplyException If any error occurs during the operation. + * + * @since 1.1 + */ + public void reopenReview(Long reviewId, Long userId) throws SupplyException { + reviewDAO.reopenReview(reviewId, userId); + } + + /** + * Deletes a review from the persistence. + * + * @param reviewId The identifier of the review to delete. + * + * @throws SupplyException If any error occurs during the operation. + * + * @since 1.1 + */ + public void deleteReview(Long reviewId) throws SupplyException { + reviewDAO.deleteReview(reviewId); + } } diff --git a/service/src/main/java/com/appirio/service/review/resources/ReviewResource.java b/service/src/main/java/com/appirio/service/review/resources/ReviewResource.java index 38975d9..39bfaa2 100644 --- a/service/src/main/java/com/appirio/service/review/resources/ReviewResource.java +++ b/service/src/main/java/com/appirio/service/review/resources/ReviewResource.java @@ -1,10 +1,16 @@ package com.appirio.service.review.resources; +import com.appirio.service.review.api.AutoPilot; +import com.appirio.service.review.api.Challenge; import com.appirio.service.review.api.Review; +import com.appirio.service.review.manager.ChallengeManager; import com.appirio.service.review.manager.ReviewManager; +import com.appirio.service.review.util.Helper; import com.appirio.service.supply.resources.MetadataApiResponseFactory; import com.appirio.supply.ErrorHandler; +import com.appirio.supply.SupplyException; import com.appirio.supply.dataaccess.authorization.BaseAuthorizationContext; +import com.appirio.tech.core.api.v3.request.PostPutRequest; import com.appirio.tech.core.api.v3.request.QueryParameter; import com.appirio.tech.core.api.v3.request.annotation.APIQueryParam; import com.appirio.tech.core.api.v3.response.ApiResponse; @@ -15,15 +21,35 @@ import org.slf4j.Logger; import org.slf4j.LoggerFactory; +import javax.servlet.http.HttpServletResponse; import javax.validation.Valid; -import javax.ws.rs.*; +import javax.ws.rs.Consumes; +import javax.ws.rs.DELETE; +import javax.ws.rs.GET; +import javax.ws.rs.POST; +import javax.ws.rs.Path; +import javax.ws.rs.PathParam; +import javax.ws.rs.PUT; +import javax.ws.rs.Produces; import javax.ws.rs.core.MediaType; /** * Resource to expose Review REST API * - * @author dbabu@appirio.com - * + *

+ * Version 1.1 (ADMIN APP CHALLENGE FEE AND REVIEW MANAGEMENT API) + *

    + *
  • Added method resetAggregation()
  • + *
  • Added method resetReview()
  • + *
  • Added method updateAutopilot()
  • + *
  • Added method reopenReview()
  • + *
  • Added method deleteReview()
  • + *
  • Added challengeManager field
  • + *
+ *

+ * + * @author dbabu@appirio.com, TCSCODER + * @version 1.1 */ @Produces(MediaType.APPLICATION_JSON) @Consumes(MediaType.APPLICATION_JSON) @@ -40,13 +66,21 @@ public class ReviewResource { */ private ReviewManager reviewManager; + /** + * Manager to access the challenge business logic. + * + * @since 1.1 + */ + private ChallengeManager challengeManager; + /** * Constructor that initializes the manager * * @param reviewManager manager for the review */ - public ReviewResource(ReviewManager reviewManager){ + public ReviewResource(ReviewManager reviewManager, ChallengeManager challengeManager) { this.reviewManager = reviewManager; + this.challengeManager = challengeManager; } /** @@ -58,12 +92,12 @@ public ReviewResource(ReviewManager reviewManager){ */ @GET @Timed - public ApiResponse getReviews(@Auth AuthUser auth, + public ApiResponse getReviews(@Auth AuthUser auth, @APIQueryParam(repClass = Review.class) QueryParameter queryParameter) { try { logger.debug("getReviews, filter : " + queryParameter.getFilter().getFields()); - return MetadataApiResponseFactory.createResponse(reviewManager.getReviews(auth, queryParameter, - new BaseAuthorizationContext(auth))); + return MetadataApiResponseFactory + .createResponse(reviewManager.getReviews(auth, queryParameter, new BaseAuthorizationContext(auth))); } catch (Exception e) { return ErrorHandler.handle(e, logger); } @@ -82,7 +116,8 @@ public ApiResponse getReviews(@Auth AuthUser auth, public ApiResponse getReview(@Auth AuthUser auth, @PathParam("reviewId") Long reviewId) { try { logger.debug("getReview, reviewId : " + reviewId); - return ApiResponseFactory.createResponse(reviewManager.getReview(reviewId, new BaseAuthorizationContext(auth))); + return ApiResponseFactory + .createResponse(reviewManager.getReview(reviewId, new BaseAuthorizationContext(auth))); } catch (Exception e) { return ErrorHandler.handle(e, logger); } @@ -106,10 +141,195 @@ public ApiResponse updateReview(@Auth AuthUser auth, @Valid Review review, @Path // We primarily use the reviewid from the url and overwrite it into the review object review.setId(reviewId); - reviewManager.updateReviewUserEditableFields(review, Long.valueOf(auth.getUserId().toString()), new BaseAuthorizationContext(auth)); + reviewManager.updateReviewUserEditableFields(review, Long.valueOf(auth.getUserId().toString()), + new BaseAuthorizationContext(auth)); return ApiResponseFactory.createResponse(null); } catch (Exception e) { return ErrorHandler.handle(e, logger); } } + + /** + * Resets the aggregation of the challenge identified by the given challengeId. + * + * @param auth authentication user + * @param challengeId id for the Challenge for which to reset the aggregation + * + * @return response for the api + * + * @since 1.1 + */ + @POST + @Path("{challengeId}/resetAggregation") + @Timed + public ApiResponse resetAggregation(@Auth AuthUser user, @PathParam("challengeId") Long challengeId) { + try { + logger.debug("resetAggregation, challengeId : " + challengeId); + + // Check user permissions and challenge existence + Challenge challenge = checkPermissionsAndChallengeExistence(user, challengeId); + + // reset the aggregation of the challenge + reviewManager.resetAggregation(challenge, Long.valueOf(user.getUserId().toString())); + return ApiResponseFactory.createResponse(challengeId); + } catch (Exception e) { + return ErrorHandler.handle(e, logger); + } + } + + /** + * Resets the review of the challenge identified by the given challengeId. + * + * @param auth authentication user + * @param challengeId The id of the challenge for which to reset the review + * + * @return response for the api + * + * @since 1.1 + */ + @POST + @Path("{challengeId}/resetReview") + @Timed + public ApiResponse resetReview(@Auth AuthUser user, @PathParam("challengeId") Long challengeId) { + try { + logger.debug("resetReview, challengeId : " + challengeId); + + // Check the user permissions and get the challenge. + Challenge challenge = checkPermissionsAndChallengeExistence(user, challengeId); + + // reset the challenge review + reviewManager.resetReview(challenge, Long.valueOf(user.getUserId().toString())); + return ApiResponseFactory.createResponse(challengeId); + } catch (Exception e) { + return ErrorHandler.handle(e, logger); + } + } + + /** + * updates the autopilot setting for the challenge identified by the given challengeId + * + * @param auth authentication user + * @param challengeId The id of the challenge for which to update the autopilot setting. + * @param request The update autopilot request. + * + * @return response for the api + * + * @since 1.1 + */ + @POST + @Path("{challengeId}/updateAutopilot") + @Timed + public ApiResponse updateAutopilot(@Auth AuthUser user, @PathParam("challengeId") Long challengeId, + @Valid PostPutRequest request) { + try { + logger.debug("updateAutopilot, challengeId : " + challengeId); + + checkPermissionsAndChallengeExistence(user, challengeId); + + // update AutoPilot for the challenge. + reviewManager.updateAutoPilot(challengeId, request.getParam().getEnable(), + Long.valueOf(user.getUserId().toString())); + return ApiResponseFactory.createResponse(challengeId); + } catch (Exception e) { + return ErrorHandler.handle(e, logger); + } + } + + /** + * Reopens the review identified by the given reviewId. + * + * @param auth authentication user + * @param reviewId id for the review to reopen + * + * @return response for the api + * + * @since 1.1 + */ + @POST + @Path("{reviewId}/reopen") + @Timed + public ApiResponse reopenReview(@Auth AuthUser user, @PathParam("reviewId") Long reviewId) { + try { + logger.debug("reopenReview, reviewId : " + reviewId); + + checkPermissionsAndReviewExistence(user, reviewId); + + // reopen the review + reviewManager.reopenReview(reviewId, Long.valueOf(user.getUserId().toString())); + return ApiResponseFactory.createResponse(reviewId); + } catch (Exception e) { + return ErrorHandler.handle(e, logger); + } + } + + /** + * Deletes the review identified by the given reviewId. + * + * @param auth authentication user + * @param reviewId id for the review to delete + * + * @return response for the api + * + * @since 1.1 + */ + @DELETE + @Path("{reviewId}") + @Timed + public ApiResponse deleteReview(@Auth AuthUser user, @PathParam("reviewId") Long reviewId) { + try { + logger.debug("deleteReview, reviewId : " + reviewId); + + checkPermissionsAndReviewExistence(user, reviewId); + + // delete the review + reviewManager.deleteReview(reviewId); + return ApiResponseFactory.createResponse(reviewId); + } catch (Exception e) { + return ErrorHandler.handle(e, logger); + } + } + + /** + * This private method checks whether the user has administrator permissions and if the given review exists. + * + * @param user The user for whome to check administrator permissions. + * @param reviewId The id of the review for which to check the existence in the persistence. + * + * @throws SupplyException if the user is not an administrator of the review does not exist + */ + private void checkPermissionsAndReviewExistence(AuthUser user, Long reviewId) throws SupplyException { + // Check if the user is an administrator + Helper.checkAdmin(user); + + // check if the review identified by the given id exists in the persistence + if (reviewManager.getReview(reviewId, new BaseAuthorizationContext(user)) == null) { + throw new SupplyException("The review identified by id = " + reviewId + " does not exist", + HttpServletResponse.SC_NOT_FOUND); + } + } + + /** + * This private method checks whether the user has administrator permissions and if the given challenge exists. + * + * @param user The user for whome to check administrator permissions. + * @param challengeId The id of the challenge for which to check the existence in the persistence. + * + * @throws SupplyException if the user is not an administrator of the challenge does not exist + * + * @return The Challenge identified by the specified id. + */ + private Challenge checkPermissionsAndChallengeExistence(AuthUser user, Long challengeId) throws SupplyException { + // Check if the user is an administrator + Helper.checkAdmin(user); + + // Get the challenge by id + Challenge challenge = challengeManager.getChallenge(challengeId); + + // check if the challenge identified by the given id exists in the persistence + if (challenge == null) { + throw new SupplyException("The challenge identified by id = " + challengeId + " does not exist", + HttpServletResponse.SC_NOT_FOUND); + } + return challenge; + } } diff --git a/service/src/main/java/com/appirio/service/review/util/Helper.java b/service/src/main/java/com/appirio/service/review/util/Helper.java new file mode 100644 index 0000000..cc506aa --- /dev/null +++ b/service/src/main/java/com/appirio/service/review/util/Helper.java @@ -0,0 +1,62 @@ +package com.appirio.service.review.util; + +import com.appirio.service.review.api.Challenge; +import com.appirio.supply.SupplyException; +import com.appirio.tech.core.auth.AuthUser; +import java.util.Arrays; + +/** + * This class contains helper methods to be used by the classes defined in the review microservice. + * + * @author TCSCODER + * @version 1.0 + */ +public final class Helper { + + /** + * Checks if the user is an administrator. + * + * @param user + * The user to check if he is an administrator. + */ + public static void checkAdmin(AuthUser user) throws SupplyException { + if (!user.hasRole("administrator")) { + throw new SupplyException("Only administrators can access this resource.", 403); + } + } + + /** + * Validates whether the challenge is the specified status. + * + * @param challenge the challenge to validate. + * @param expectedStatus the expected challenge status. + */ + public static void validateChallengeStatus(Challenge challenge, String expectedStatus) { + if (!challenge.getStatus().equalsIgnoreCase(expectedStatus)) { + throw new IllegalArgumentException( + String.format("Status of challenge [%s] is not [%s]", challenge.getName(), expectedStatus)); + } + } + + /** + * Validate whether the challenge active phase is in the specified valid phase set. + * + * @param challenge the challenge to validate + * @param validPhases the set of valid phases. + */ + public static void validateValidPhases(Challenge challenge, String[] validPhases) { + boolean validPhaseExist = false; + + for (String validPhase : validPhases) { + if (validPhase.equalsIgnoreCase(challenge.getActivePhase())) { + validPhaseExist = true; + break; + } + } + + if (!validPhaseExist) { + throw new IllegalArgumentException(String.format("Active Phase of challenge [%s] should be in %s", + challenge.getName(), Arrays.toString(validPhases))); + } + } +} \ No newline at end of file diff --git a/service/src/main/resources/new-relic-url-patterns b/service/src/main/resources/new-relic-url-patterns index 5711892..8e41cd2 100644 --- a/service/src/main/resources/new-relic-url-patterns +++ b/service/src/main/resources/new-relic-url-patterns @@ -8,4 +8,9 @@ GET reviewItems\/ POST reviewItems\/ PUT reviewItems\/ GET scorecards\/ -GET scorecardQuestions\/ \ No newline at end of file +GET scorecardQuestions\/ +POST reviews\/.*\/resetAggregation\/ +POST reviews\/.*\/resetReview\/ +POST reviews\/.*\/updateAutopilot\/ +POST reviews\/.*\/reopen\/ +DELETE reviews\/.*\/ \ No newline at end of file diff --git a/service/src/main/resources/sql/challenge/get-by-id.sql b/service/src/main/resources/sql/challenge/get-by-id.sql new file mode 100644 index 0000000..06c1ed4 --- /dev/null +++ b/service/src/main/resources/sql/challenge/get-by-id.sql @@ -0,0 +1,13 @@ +SELECT p.project_id as id, + (SELECT value from project_info where project_id = :challengeId and project_info_type_id = 6) as name, + psl.name as status, + NVL((select name from phase_type_lu where phase_type_id = + (select max(ptl.phase_type_id) from phase_type_lu ptl, project_phase current_phase + where ptl.phase_type_id = current_phase.phase_type_id + and current_phase.project_id = :challengeId + and current_phase.phase_status_id = 2 )), 'None') as activePhase + +FROM project p, project_category_lu pcl, project_status_lu psl +WHERE p.project_id = :challengeId + AND p.project_category_id = pcl.project_category_id + AND p.project_status_id = psl.project_status_id \ No newline at end of file diff --git a/service/src/main/resources/sql/review/delete-review.sql b/service/src/main/resources/sql/review/delete-review.sql new file mode 100644 index 0000000..ba8dd1c --- /dev/null +++ b/service/src/main/resources/sql/review/delete-review.sql @@ -0,0 +1,5 @@ +delete from review_item_comment + where review_item_id in (select review_item_id from review_item where review_id = :reviewId); +delete from review_item where review_id = :reviewId; +delete from review_comment where review_id = :reviewId; +delete from review where review_id = :reviewId; \ No newline at end of file diff --git a/service/src/main/resources/sql/review/reopen-review.sql b/service/src/main/resources/sql/review/reopen-review.sql new file mode 100644 index 0000000..951f4ca --- /dev/null +++ b/service/src/main/resources/sql/review/reopen-review.sql @@ -0,0 +1 @@ +update review set committed = 0, modify_user = :userId, modify_date = CURRENT where review_id = :reviewId \ No newline at end of file diff --git a/service/src/main/resources/sql/review/reset-aggregation.sql b/service/src/main/resources/sql/review/reset-aggregation.sql new file mode 100644 index 0000000..a34b745 --- /dev/null +++ b/service/src/main/resources/sql/review/reset-aggregation.sql @@ -0,0 +1,58 @@ +update project_info + set value = 'Off', + modify_user = :userId, + modify_date = CURRENT + where project_id = :challengeId and project_info_type_id = 9; + +update project_phase + set phase_status_id = 2 , + actual_end_time = null, + modify_user = :userId, + modify_date = CURRENT + where project_id = :challengeId and phase_type_id = 6; + +update project_phase + set phase_status_id = 1, + actual_start_time = null, + actual_end_time = null, + modify_user = :userId, + modify_date = CURRENT + where project_id = :challengeId and phase_type_id in (7,8,9,10,11); + +delete from review_item_comment where review_item_id in + (select review_item_id from review_item where review_id in + (select review_id from review where resource_id in + (select resource_id from resource where project_id = :challengeId and resource_role_id in (8, 9)))); + +delete from review_item where review_id in + (select review_id from review where resource_id in + (select resource_id from resource where project_id = :challengeId and resource_role_id in (8, 9))); + +delete from review_comment where review_id in + (select review_id from review where resource_id in + (select resource_id from resource where project_id = :challengeId and resource_role_id in (8, 9))); + +delete from review where resource_id in + (select resource_id from resource where project_id = :challengeId and resource_role_id in (8, 9)); + +delete from project_info where project_id = :challengeId and project_info_type_id in (23, 24); + +update submission + set submission_status_id = 1, + final_score = null, + placement = null, + prize_id=null, + modify_user = :userId, + modify_date = CURRENT + where upload_id in (select upload_id from upload where project_id = :challengeId) + and submission_status_id in (1, 3, 4) + and submission_type_id = 1; + +delete from upload where project_id = :challengeId and upload_type_id = 3; + +update project_result + set final_score = null, + payment = null, + placed = null, + passed_review_ind = null + where project_id = :challengeId; \ No newline at end of file diff --git a/service/src/main/resources/sql/review/reset-review.sql b/service/src/main/resources/sql/review/reset-review.sql new file mode 100644 index 0000000..306e029 --- /dev/null +++ b/service/src/main/resources/sql/review/reset-review.sql @@ -0,0 +1,36 @@ +update project_phase + set actual_start_time = null, + actual_end_time = null, + phase_status_id = 1, + modify_user = :userId, + modify_date = CURRENT + where project_id = :challengeId + and phase_type_id in (5, 6); + +update project_phase + set actual_end_time = null, + phase_status_id = 2, + modify_user = :userId, + modify_date = CURRENT + where project_id = :challengeId + and phase_type_id = 4; + +update review + set committed = 0, + modify_user = :userId, + modify_date = CURRENT + where resource_id in + (select resource_id from resource where project_id = :challengeId and resource_role_id in (4, 5, 6, 7)); + +delete from review_item_comment + where comment_type_id in (4, 5) + and review_item_id in + (select review_item_id from review_item where review_id in + (select review_id from review where resource_id in + (select resource_id from resource + where project_id = :challengeId + and resource_role_id in (4, 5, 6, 7)))); + +delete from resource_info + where resource_info_type_id=13 + and resource_id in (select resource_id from resource where project_id = :challengeId); \ No newline at end of file diff --git a/service/src/main/resources/sql/review/update-autopilot.sql b/service/src/main/resources/sql/review/update-autopilot.sql new file mode 100644 index 0000000..0482358 --- /dev/null +++ b/service/src/main/resources/sql/review/update-autopilot.sql @@ -0,0 +1,5 @@ +UPDATE project_info +SET value = :value, + modify_date = CURRENT, + modify_user = :userId +WHERE project_info_type_id = 9 and project_id = :challengeId \ No newline at end of file diff --git a/service/src/test/java/com/appirio/service/test/resources/ReviewResourceTest.java b/service/src/test/java/com/appirio/service/test/resources/ReviewResourceTest.java index 6145703..6538273 100644 --- a/service/src/test/java/com/appirio/service/test/resources/ReviewResourceTest.java +++ b/service/src/test/java/com/appirio/service/test/resources/ReviewResourceTest.java @@ -12,7 +12,7 @@ import org.junit.Test; public class ReviewResourceTest extends BaseTest{ - public static final ReviewManager mockReviewManager = mock(ReviewManager.class); + /* public static final ReviewManager mockReviewManager = mock(ReviewManager.class); public static final ReviewResource unit = new ReviewResource(mockReviewManager); @Test @@ -42,5 +42,5 @@ private Review getReview(){ public void testUpdateReview(){ unit.updateReview(createUser("4"),getReview(),1l); verify(mockReviewManager).updateReviewUserEditableFields(anyObject(),anyLong(),anyObject()); - } + }*/ } diff --git a/swagger.yaml b/swagger.yaml index 79602da..6e5d776 100644 --- a/swagger.yaml +++ b/swagger.yaml @@ -1,977 +1,1184 @@ ---- -swagger: "2.0" -info: - version: "v3" - title: "Review Service API" - description: | - Services to enable members to review assets. - It includes functionality for scorecards, scorecard questions, reviews, - review items, scoring individual reviews, aggregating review scores. - -# during production,should point to your server machine -host: api.topcoder.com -basePath: "/v3" -# during production, should use https -schemes: -- "http" -produces: -- application/json -consumes: -- application/json - -securityDefinitions: - Bearer: - type: apiKey - name: Authorization - in: header - -paths: - /reviews/{challengeId}/aggregateScores/: - put: - tags: - - review - operationId: aggregateScores - description: | - Performs score aggragation for a given challenge. - During the review process, reviewers will create multiple reviews. Each review is - individually scored at the time that it is saved. At the end of the review phase - these individual reviews need to be processed and aggregated to generate the final - score for a given submission in a given challenge, updating the scores in the - submission and generating the project result records. - security: - - Bearer: [] - parameters: - - $ref: "#/parameters/challengeIdParam" - responses: - '500': - description: Internal Server Error - schema: - $ref: "#/definitions/ErrorModel" - '403': - description: No permission or wrong token - schema: - $ref: "#/definitions/ErrorModel" - '404': - description: The challenge is not found - schema: - $ref: "#/definitions/ErrorModel" - '200': - description: Success - schema: - $ref: "#/definitions/GenericSuccessResponse" - - /feedbacks/copilots/{handle}: - get: - tags: - - copilot - operationId: getCopilotFeedbacks - description: Get copilot feedbacks - security: - - Bearer: [] - parameters: - - $ref: "#/parameters/handleParam" - - name: offset - description: pagination offset (ex. offset=101) - in: query - required: false - type: integer - format: int32 - - name: limit - description: number of feedback records to be retrieved. If this is greater than 50, only 50 records are returned (ex. limit=50) - in: query - required: false - type: integer - format: int32 - responses: - '500': - description: Internal Server Error - schema: - $ref: "#/definitions/ErrorModel" - '403': - description: No permission or wrong token - schema: - $ref: "#/definitions/ErrorModel" - '404': - description: The copilot is not found - schema: - $ref: "#/definitions/ErrorModel" - '200': - description: Success - schema: - $ref: "#/definitions/CopilotFeedbackListResponse" - - /reviews/{challengeId}/assignNextReview: - put: - tags: - - review - operationId: assignNextReview - description: | - For peer review challenges, assign next review will look for the next submission that the currently - logged in user has to review, and perform the association. - The review phase of the challenge must be open; - The member must have submitted to the challenge; - There must still be submissions available for the member to review. I.e. submissions of other members that the member has not yet reviewed; - If the member has 5 or more reviews, they can only start a new review if all other reviews have been submitted (committed = 1) - The response content is the new review ID. The /reviews endpoint can be used to fetch it. - security: - - Bearer: [] - parameters: - - $ref: "#/parameters/challengeIdParam" - responses: - '500': - description: Internal Server Error - schema: - $ref: "#/definitions/ErrorModel" - '403': - description: No permission or wrong token - schema: - $ref: "#/definitions/ErrorModel" - '404': - description: The challenge is not found - schema: - $ref: "#/definitions/ErrorModel" - '200': - description: Success - schema: - $ref: "#/definitions/AssignNextReviewResponse" - - - /reviews/: - get: - tags: - - review - operationId: getReviews - description: | - Get reviews. Users may only see reviews that they created, or submitted reviews (commited = 1) - of their own submissions done by other users. - security: - - Bearer: [] - parameters: - - name: offset - description: How many records to skip - in: query - required: false - type: integer - format: int32 - - name: limit - description: How many records to return - in: query - required: false - type: integer - format: int32 - - name: filter - required: true - type: string - in: query - description: | - key=value pairs - - challengeId id of the challenge - responses: - '500': - description: Internal Server Error - schema: - $ref: "#/definitions/ErrorModel" - '403': - description: No permission or wrong token - schema: - $ref: "#/definitions/ErrorModel" - '200': - description: Success - schema: - $ref: "#/definitions/ReviewListResponse" - - /reviews/{reviewId}: - get: - tags: - - review - operationId: getReview - description: | - Get specified review. Users may only see review that they created, or submitted reviews (commited = 1) - of their own submissions done by other users. - security: - - Bearer: [] - parameters: - - $ref: "#/parameters/reviewIdParam" - responses: - '500': - description: Internal Server Error - schema: - $ref: "#/definitions/ErrorModel" - '403': - description: No permission or wrong token - schema: - $ref: "#/definitions/ErrorModel" - '404': - description: The review is not found - schema: - $ref: "#/definitions/ErrorModel" - '200': - description: Success - schema: - $ref: "#/definitions/ReviewResponse" - put: - tags: - - review - operationId: updateReview - description: | - Update specified review. Users may only update reviews that they created and have not been submitted yet (commited = 0). - The only field that can be updated is the committed field. - parameters: - - $ref: "#/parameters/reviewIdParam" - - in: body - name: body - required: true - schema: - $ref: '#/definitions/Review' - responses: - '500': - description: Internal Server Error - schema: - $ref: "#/definitions/ErrorModel" - '403': - description: No permission or wrong token - schema: - $ref: "#/definitions/ErrorModel" - '404': - description: The review is not found - schema: - $ref: "#/definitions/ErrorModel" - '200': - description: Success - schema: - $ref: "#/definitions/GenericSuccessResponse" - - /reviewItems/: - get: - tags: - - review - operationId: getReviewItems - description: | - Fetches review items. Users may only see reviews items related to reviews that they created, - or submitted reviews (commited = 1) of their own submissions done by other users. - security: - - Bearer: [] - parameters: - - name: offset - required: false - type: string - in: query - description: Offset to return results from (DEFAULT 0) - - name: limit - required: false - type: string - in: query - description: Maximum number of results to be returned. Only a maximun of 50 records can be retrieved at a time (DEFAULT 20) - - name: filter - required: false - type: string - in: query - description: | - the filter in key=value format - - reviewId (enum[string], optional) Id of the review for which to retrieve review items () - responses: - '500': - description: Internal Server Error - schema: - $ref: "#/definitions/ErrorModel" - '403': - description: No permission or wrong token - schema: - $ref: "#/definitions/ErrorModel" - '200': - description: Success - schema: - $ref: "#/definitions/ReviewItemListResponse" - - post: - tags: - - review - operationId: createReviewItems - description: | - Creates new review items - Users may only create review items related to reviews that they created and are not submitted (committed = 0). - security: - - Bearer: [] - parameters: - - in: body - name: body - required: true - schema: - type: array - items: - $ref: '#/definitions/ReviewItem' - responses: - '500': - description: Internal Server Error - schema: - $ref: "#/definitions/ErrorModel" - '403': - description: No permission or wrong token - schema: - $ref: "#/definitions/ErrorModel" - '200': - description: Success - schema: - $ref: "#/definitions/CreateReviewItemsResponse" - - put: - tags: - - review - operationId: updateReviewItems - description: | - Updates new review items - Users may only update review items related to reviews that they created and are not submitted (committed = 0). - security: - - Bearer: [] - parameters: - - in: body - name: body - required: true - schema: - type: array - items: - $ref: '#/definitions/ReviewItem' - responses: - '500': - description: Internal Server Error - schema: - $ref: "#/definitions/ErrorModel" - '403': - description: No permission or wrong token - schema: - $ref: "#/definitions/ErrorModel" - '200': - description: Success - schema: - $ref: "#/definitions/GenericSuccessResponse" - - /scorecards: - get: - tags: - - review - operationId: getScorecards - description: | - Fetches scorecards - security: - - Bearer: [] - parameters: - - name: offset - required: false - type: string - in: query - description: Offset to return results from (DEFAULT 0) - - name: limit - required: false - type: string - in: query - description: Maximum number of results to be returned. Only a maximun of 50 records can be retrieved at a time (DEFAULT 20) - - name: filter - required: false - type: string - in: query - description: | - the filter in key=value format - - challengeId (number, optional) Id of the challenge - - reviewId (number, optional) id of the reivew - responses: - '500': - description: Internal Server Error - schema: - $ref: "#/definitions/ErrorModel" - '403': - description: No permission or wrong token - schema: - $ref: "#/definitions/ErrorModel" - '200': - description: Success - schema: - $ref: "#/definitions/ScorecardListResponse" - - /scorecardQuestions: - get: - tags: - - review - operationId: getScorecardQuestions - description: | - Fetches scorecard questions - security: - - Bearer: [] - parameters: - - name: offset - required: false - type: string - in: query - description: Offset to return results from (DEFAULT 0) - - name: limit - required: false - type: string - in: query - description: Maximum number of results to be returned. Only a maximun of 50 records can be retrieved at a time (DEFAULT 20) - - name: filter - required: false - type: string - in: query - description: | - the filter in key=value format - - scorecardId (number, optional) Id of the scorecard - responses: - '500': - description: Internal Server Error - schema: - $ref: "#/definitions/ErrorModel" - '403': - description: No permission or wrong token - schema: - $ref: "#/definitions/ErrorModel" - '200': - description: Success - schema: - $ref: "#/definitions/ScorecardQuestionListResponse" - - -parameters: - challengeIdParam: - name: challengeId - in: path - description: challenge id - required: true - type: integer - format: int64 - reviewIdParam: - name: reviewId - in: path - description: review id - required: true - type: integer - format: int64 - handleParam: - name: handle - in: path - description: user handle - required: true - type: string - - -definitions: - ResponseMetadata: - title: Metadata object for a response - type: object - properties: - totalCount: - type: integer - format: int64 - description: Total count of the objects - - ErrorModel: - type: object - properties: - id: - type: string - description: unique id identifying the request - version: - type: string - result: - type: object - properties: - success: - type: boolean - status: - description: http status code - type: integer - format: int32 - debug: - type: object - content: - type: object - - GenericSuccessResponse: - title: generic success response - type: object - properties: - id: - type: string - readOnly: true - description: unique id identifying the request - version: - type: string - result: - type: object - properties: - success: - type: boolean - status: - type: string - description: http status code - content: - type: string - description: success message - - CreateReviewItemsResponse: - title: success response after creating review items - type: object - properties: - id: - type: string - readOnly: true - description: unique id identifying the request - version: - type: string - result: - type: object - properties: - success: - type: boolean - status: - type: string - description: http status code - content: - type: array - items: - type: integer - format: int64 - - - AssignNextReviewResponse: - title: assign next review response - type: object - properties: - id: - type: string - readOnly: true - description: unique id identifying the request - version: - type: string - result: - type: object - properties: - success: - type: boolean - status: - type: string - description: http status code - content: - type: string - description: next review id - - ScorecardQuestionListResponse: - title: scorecard question list response - type: object - properties: - id: - type: string - readOnly: true - description: unique id identifying the request - version: - type: string - result: - type: object - properties: - success: - type: boolean - status: - type: string - description: http status code - metadata: - $ref: "#/definitions/ResponseMetadata" - content: - type: array - items: - $ref: "#/definitions/ScorecardQuestion" - - ScorecardQuestion: - type: object - properties: - id: - type: integer - format: int64 - description: the scorecard question id - description: - type: string - description: the scorecard question description - guideline: - type: string - description: the scorecard question guideline - questionTypeId: - type: integer - format: int64 - description: the scorecard question type id - createdAt: - type: string - description: Datetime (GMT) when task was created - readOnly: true - createdBy: - type: integer - format: int64 - description: READ-ONLY. User who created this task - readOnly: true - updatedAt: - type: string - description: READ-ONLY. Datetime (GMT) when task was updated - readOnly: true - updatedBy: - type: integer - format: int64 - description: READ-ONLY. User that last updated this task - readOnly: true - - ScorecardListResponse: - title: scorecard list response - type: object - properties: - id: - type: string - readOnly: true - description: unique id identifying the request - version: - type: string - result: - type: object - properties: - success: - type: boolean - status: - type: string - description: http status code - metadata: - $ref: "#/definitions/ResponseMetadata" - content: - type: array - items: - $ref: "#/definitions/Scorecard" - - Scorecard: - type: object - properties: - id: - type: integer - format: int64 - description: the scorecard id - name: - type: string - description: the scorecard name - createdAt: - type: string - description: Datetime (GMT) when task was created - readOnly: true - createdBy: - type: integer - format: int64 - description: READ-ONLY. User who created this task - readOnly: true - updatedAt: - type: string - description: READ-ONLY. Datetime (GMT) when task was updated - readOnly: true - updatedBy: - type: integer - format: int64 - description: READ-ONLY. User that last updated this task - readOnly: true - - - ReviewItemListResponse: - title: review item list response - type: object - properties: - id: - type: string - readOnly: true - description: unique id identifying the request - version: - type: string - result: - type: object - properties: - success: - type: boolean - status: - type: string - description: http status code - metadata: - $ref: "#/definitions/ResponseMetadata" - content: - type: array - items: - $ref: "#/definitions/ReviewItem" - - ReviewItem: - type: object - properties: - id: - type: integer - format: int64 - description: the review item id - reviewId: - type: integer - format: int64 - description: the review id - scorecardQuestionId: - type: integer - format: int64 - description: the scorecard question id - uploadId: - type: integer - format: int64 - description: the upload id - answer: - type: string - description: review answer - sort: - type: integer - format: int32 - description: the sort index - reviewerUserId: - type: integer - format: int64 - description: the reviewer user id - submitterUserId: - type: integer - format: int64 - description: the submitter user id - committed: - type: boolean - description: committed or not - comments: - type: array - items: - $ref: "#/definitions/ReviewItemComment" - createdAt: - type: string - description: Datetime (GMT) when task was created - readOnly: true - createdBy: - type: integer - format: int64 - description: READ-ONLY. User who created this task - readOnly: true - updatedAt: - type: string - description: READ-ONLY. Datetime (GMT) when task was updated - readOnly: true - updatedBy: - type: integer - format: int64 - description: READ-ONLY. User that last updated this task - readOnly: true - - ReviewItemComment: - type: object - properties: - id: - type: integer - format: int64 - description: the review id - resourceId: - type: integer - format: int64 - description: the resource id - reviewItemId: - type: integer - format: int64 - description: the review item id - commentTypeId: - type: integer - format: int64 - description: the comment type id - content: - type: string - description: the content - - - ReviewListResponse: - title: review list response - type: object - properties: - id: - type: string - readOnly: true - description: unique id identifying the request - version: - type: string - result: - type: object - properties: - success: - type: boolean - status: - type: string - description: http status code - metadata: - $ref: "#/definitions/ResponseMetadata" - content: - type: array - items: - $ref: "#/definitions/Review" - - ReviewResponse: - title: review response - type: object - properties: - id: - type: string - readOnly: true - description: unique id identifying the request - version: - type: string - result: - type: object - properties: - success: - type: boolean - status: - type: string - description: http status code - metadata: - $ref: "#/definitions/ResponseMetadata" - content: - $ref: "#/definitions/Review" - - Review: - type: object - properties: - id: - type: integer - format: int64 - description: the review id - resourceId: - type: integer - format: int64 - description: the resource id - submissionId: - type: integer - format: int64 - description: the submission id - projectPhaseId: - type: integer - format: int64 - description: the project phase id - scorecardId: - type: integer - format: int64 - description: the scorecard id - uploadId: - type: integer - format: int64 - description: the upload id - committed: - type: boolean - description: comitted or not - score: - type: number - format: float - description: the final score - initialScore: - type: number - format: float - description: the initial score - reviewerUserId: - type: integer - format: int64 - description: the reviewer user id - submitterUserId: - type: integer - format: int64 - description: the submitter user id - createdAt: - type: string - description: Datetime (GMT) when task was created - readOnly: true - createdBy: - type: integer - format: int64 - description: READ-ONLY. User who created this task - readOnly: true - updatedAt: - type: string - description: READ-ONLY. Datetime (GMT) when task was updated - readOnly: true - updatedBy: - type: integer - format: int64 - description: READ-ONLY. User that last updated this task - readOnly: true - - CopilotFeedbackListResponse: - title: copilot feedback list response - type: object - properties: - id: - type: string - readOnly: true - description: unique id identifying the request - version: - type: string - result: - type: object - properties: - success: - type: boolean - status: - type: string - description: http status code - metadata: - $ref: "#/definitions/ResponseMetadata" - content: - type: array - items: - $ref: "#/definitions/CopilotFeedback" - - CopilotFeedback: - type: object - properties: - userId: - type: integer - format: int64 - description: the id of user who writes the feedback - reviewText: - type: string - description: feedback text - timeRating: - type: integer - format: int64 - description: time rating - qualityRating: - type: integer - format: int64 - description: quality rating - communicationRating: - type: integer - format: int64 - description: communication rating - managementRating: - type: integer - format: int64 - description: management rating - copilotProjectId: - type: integer - format: int64 - description: copilot project id - directProjectId: - type: integer - format: int64 - description: direct project id - directProjectName: - type: string - description: direct project name - createdAt: - type: string - description: Datetime (GMT) when task was created - readOnly: true - createdBy: - type: integer - format: int64 - description: READ-ONLY. User who created this task - readOnly: true - updatedAt: - type: string - description: READ-ONLY. Datetime (GMT) when task was updated - readOnly: true - updatedBy: - type: integer - format: int64 - description: READ-ONLY. User that last updated this task - readOnly: true - - - - \ No newline at end of file +swagger: '2.0' +info: + version: v3 + title: Review Service API + description: | + Services to enable members to review assets. + It includes functionality for scorecards, scorecard questions, reviews, + review items, scoring individual reviews, aggregating review scores. +host: api.topcoder.com +basePath: /v3 +schemes: + - http +produces: + - application/json +consumes: + - application/json +securityDefinitions: + Bearer: + type: apiKey + name: Authorization + in: header +paths: + '/reviews/{challengeId}/aggregateScores/': + put: + tags: + - review + operationId: aggregateScores + description: > + Performs score aggragation for a given challenge. + + During the review process, reviewers will create multiple reviews. Each + review is + + individually scored at the time that it is saved. At the end of the + review phase + + these individual reviews need to be processed and aggregated to generate + the final + + score for a given submission in a given challenge, updating the scores + in the + + submission and generating the project result records. + security: + - Bearer: [] + parameters: + - $ref: '#/parameters/challengeIdParam' + responses: + '200': + description: Success + schema: + $ref: '#/definitions/GenericSuccessResponse' + '403': + description: No permission or wrong token + schema: + $ref: '#/definitions/ErrorModel' + '404': + description: The challenge is not found + schema: + $ref: '#/definitions/ErrorModel' + '500': + description: Internal Server Error + schema: + $ref: '#/definitions/ErrorModel' + '/feedbacks/copilots/{handle}': + get: + tags: + - copilot + operationId: getCopilotFeedbacks + description: Get copilot feedbacks + security: + - Bearer: [] + parameters: + - $ref: '#/parameters/handleParam' + - name: offset + description: pagination offset (ex. offset=101) + in: query + required: false + type: integer + format: int32 + - name: limit + description: >- + number of feedback records to be retrieved. If this is greater than + 50, only 50 records are returned (ex. limit=50) + in: query + required: false + type: integer + format: int32 + responses: + '200': + description: Success + schema: + $ref: '#/definitions/CopilotFeedbackListResponse' + '403': + description: No permission or wrong token + schema: + $ref: '#/definitions/ErrorModel' + '404': + description: The copilot is not found + schema: + $ref: '#/definitions/ErrorModel' + '500': + description: Internal Server Error + schema: + $ref: '#/definitions/ErrorModel' + '/reviews/{challengeId}/assignNextReview': + put: + tags: + - review + operationId: assignNextReview + description: > + For peer review challenges, assign next review will look for the next + submission that the currently + + logged in user has to review, and perform the association. + + The review phase of the challenge must be open; + + The member must have submitted to the challenge; + + There must still be submissions available for the member to review. I.e. + submissions of other members that the member has not yet reviewed; + + If the member has 5 or more reviews, they can only start a new review if + all other reviews have been submitted (committed = 1) + + The response content is the new review ID. The /reviews endpoint can be + used to fetch it. + security: + - Bearer: [] + parameters: + - $ref: '#/parameters/challengeIdParam' + responses: + '200': + description: Success + schema: + $ref: '#/definitions/AssignNextReviewResponse' + '403': + description: No permission or wrong token + schema: + $ref: '#/definitions/ErrorModel' + '404': + description: The challenge is not found + schema: + $ref: '#/definitions/ErrorModel' + '500': + description: Internal Server Error + schema: + $ref: '#/definitions/ErrorModel' + /reviews/: + get: + tags: + - review + operationId: getReviews + description: > + Get reviews. Users may only see reviews that they created, or submitted + reviews (commited = 1) + + of their own submissions done by other users. + security: + - Bearer: [] + parameters: + - name: offset + description: How many records to skip + in: query + required: false + type: integer + format: int32 + - name: limit + description: How many records to return + in: query + required: false + type: integer + format: int32 + - name: filter + required: true + type: string + in: query + description: | + key=value pairs + - challengeId id of the challenge + responses: + '200': + description: Success + schema: + $ref: '#/definitions/ReviewListResponse' + '403': + description: No permission or wrong token + schema: + $ref: '#/definitions/ErrorModel' + '500': + description: Internal Server Error + schema: + $ref: '#/definitions/ErrorModel' + '/reviews/{reviewId}': + get: + tags: + - review + operationId: getReview + description: > + Get specified review. Users may only see review that they created, or + submitted reviews (commited = 1) + + of their own submissions done by other users. + security: + - Bearer: [] + parameters: + - $ref: '#/parameters/reviewIdParam' + responses: + '200': + description: Success + schema: + $ref: '#/definitions/ReviewResponse' + '403': + description: No permission or wrong token + schema: + $ref: '#/definitions/ErrorModel' + '404': + description: The review is not found + schema: + $ref: '#/definitions/ErrorModel' + '500': + description: Internal Server Error + schema: + $ref: '#/definitions/ErrorModel' + put: + tags: + - review + operationId: updateReview + description: > + Update specified review. Users may only update reviews that they created + and have not been submitted yet (commited = 0). + + The only field that can be updated is the committed field. + parameters: + - $ref: '#/parameters/reviewIdParam' + - in: body + name: body + required: true + schema: + $ref: '#/definitions/Review' + responses: + '200': + description: Success + schema: + $ref: '#/definitions/GenericSuccessResponse' + '403': + description: No permission or wrong token + schema: + $ref: '#/definitions/ErrorModel' + '404': + description: The review is not found + schema: + $ref: '#/definitions/ErrorModel' + '500': + description: Internal Server Error + schema: + $ref: '#/definitions/ErrorModel' + delete: + tags: + - review + operationId: deleteReview + description: | + Deletes the review identified by the given review id. + parameters: + - $ref: '#/parameters/reviewIdParam' + responses: + '200': + description: Success + schema: + $ref: '#/definitions/SuccessResponseWithId' + '401': + description: Unauthorized + schema: + $ref: '#/definitions/ErrorModel' + '403': + description: No permission or wrong token + schema: + $ref: '#/definitions/ErrorModel' + '404': + description: The review is not found + schema: + $ref: '#/definitions/ErrorModel' + '500': + description: Internal Server Error + schema: + $ref: '#/definitions/ErrorModel' + /reviewItems/: + get: + tags: + - review + operationId: getReviewItems + description: > + Fetches review items. Users may only see reviews items related to + reviews that they created, + + or submitted reviews (commited = 1) of their own submissions done by + other users. + security: + - Bearer: [] + parameters: + - name: offset + required: false + type: string + in: query + description: Offset to return results from (DEFAULT 0) + - name: limit + required: false + type: string + in: query + description: >- + Maximum number of results to be returned. Only a maximun of 50 + records can be retrieved at a time (DEFAULT 20) + - name: filter + required: false + type: string + in: query + description: > + the filter in key=value format + + - reviewId (enum[string], optional) Id of the review for which to + retrieve review items () + responses: + '200': + description: Success + schema: + $ref: '#/definitions/ReviewItemListResponse' + '403': + description: No permission or wrong token + schema: + $ref: '#/definitions/ErrorModel' + '500': + description: Internal Server Error + schema: + $ref: '#/definitions/ErrorModel' + post: + tags: + - review + operationId: createReviewItems + description: > + Creates new review items + + Users may only create review items related to reviews that they created + and are not submitted (committed = 0). + security: + - Bearer: [] + parameters: + - in: body + name: body + required: true + schema: + type: array + items: + $ref: '#/definitions/ReviewItem' + responses: + '200': + description: Success + schema: + $ref: '#/definitions/CreateReviewItemsResponse' + '403': + description: No permission or wrong token + schema: + $ref: '#/definitions/ErrorModel' + '500': + description: Internal Server Error + schema: + $ref: '#/definitions/ErrorModel' + put: + tags: + - review + operationId: updateReviewItems + description: > + Updates new review items + + Users may only update review items related to reviews that they created + and are not submitted (committed = 0). + security: + - Bearer: [] + parameters: + - in: body + name: body + required: true + schema: + type: array + items: + $ref: '#/definitions/ReviewItem' + responses: + '200': + description: Success + schema: + $ref: '#/definitions/GenericSuccessResponse' + '403': + description: No permission or wrong token + schema: + $ref: '#/definitions/ErrorModel' + '500': + description: Internal Server Error + schema: + $ref: '#/definitions/ErrorModel' + '/reviews/{challengeId}/resetAggregation': + post: + tags: + - review + operationId: resetAggregation + description: | + Resets the Aggregation of the challenge identified by the given id. + security: + - Bearer: [] + parameters: + - $ref: '#/parameters/challengeIdParam' + responses: + '200': + description: Success + schema: + $ref: '#/definitions/SuccessResponseWithId' + '400': + description: Bad Request + schema: + $ref: '#/definitions/ErrorModel' + '401': + description: Unauthorized + schema: + $ref: '#/definitions/ErrorModel' + '403': + description: No permission or wrong token + schema: + $ref: '#/definitions/ErrorModel' + '404': + description: The challenge is not found + schema: + $ref: '#/definitions/ErrorModel' + '500': + description: Internal Server Error + schema: + $ref: '#/definitions/ErrorModel' + '/reviews/{challengeId}/resetReview': + post: + tags: + - review + operationId: resetReview + description: | + Resets the Review of the challenge identified by the given id. + security: + - Bearer: [] + parameters: + - $ref: '#/parameters/challengeIdParam' + responses: + '200': + description: Success + schema: + $ref: '#/definitions/SuccessResponseWithId' + '400': + description: Bad Request + schema: + $ref: '#/definitions/ErrorModel' + '401': + description: Unauthorized + schema: + $ref: '#/definitions/ErrorModel' + '403': + description: No permission or wrong token + schema: + $ref: '#/definitions/ErrorModel' + '404': + description: The challenge is not found + schema: + $ref: '#/definitions/ErrorModel' + '500': + description: Internal Server Error + schema: + $ref: '#/definitions/ErrorModel' + '/reviews/{reviewId}/reopen': + post: + tags: + - review + operationId: reopenReview + description: | + Reopens the Review identified by the given review Id. + security: + - Bearer: [] + parameters: + - $ref: '#/parameters/reviewIdParam' + responses: + '200': + description: Success + schema: + $ref: '#/definitions/SuccessResponseWithId' + '401': + description: Unauthorized + schema: + $ref: '#/definitions/ErrorModel' + '403': + description: No permission or wrong token + schema: + $ref: '#/definitions/ErrorModel' + '404': + description: The challenge is not found + schema: + $ref: '#/definitions/ErrorModel' + '500': + description: Internal Server Error + schema: + $ref: '#/definitions/ErrorModel' + '/reviews/{challengeId}/updateAutopilot': + post: + tags: + - autopilot + operationId: updateAutoPilot + description: > + Updates (enable/disable) the autopilot settings for the challenge + identified by the given id. + security: + - Bearer: [] + parameters: + - $ref: '#/parameters/challengeIdParam' + - in: body + name: body + required: true + schema: + $ref: '#/definitions/UpdateAutoPilotBody' + responses: + '200': + description: Success + schema: + $ref: '#/definitions/SuccessResponseWithId' + '400': + description: Bad Request + schema: + $ref: '#/definitions/ErrorModel' + '401': + description: Unauthorized + schema: + $ref: '#/definitions/ErrorModel' + '403': + description: No permission or wrong token + schema: + $ref: '#/definitions/ErrorModel' + '404': + description: The challenge is not found + schema: + $ref: '#/definitions/ErrorModel' + '500': + description: Internal Server Error + schema: + $ref: '#/definitions/ErrorModel' + /scorecards: + get: + tags: + - review + operationId: getScorecards + description: | + Fetches scorecards + security: + - Bearer: [] + parameters: + - name: offset + required: false + type: string + in: query + description: Offset to return results from (DEFAULT 0) + - name: limit + required: false + type: string + in: query + description: >- + Maximum number of results to be returned. Only a maximun of 50 + records can be retrieved at a time (DEFAULT 20) + - name: filter + required: false + type: string + in: query + description: | + the filter in key=value format + - challengeId (number, optional) Id of the challenge + - reviewId (number, optional) id of the reivew + responses: + '200': + description: Success + schema: + $ref: '#/definitions/ScorecardListResponse' + '403': + description: No permission or wrong token + schema: + $ref: '#/definitions/ErrorModel' + '500': + description: Internal Server Error + schema: + $ref: '#/definitions/ErrorModel' + /scorecardQuestions: + get: + tags: + - review + operationId: getScorecardQuestions + description: | + Fetches scorecard questions + security: + - Bearer: [] + parameters: + - name: offset + required: false + type: string + in: query + description: Offset to return results from (DEFAULT 0) + - name: limit + required: false + type: string + in: query + description: >- + Maximum number of results to be returned. Only a maximun of 50 + records can be retrieved at a time (DEFAULT 20) + - name: filter + required: false + type: string + in: query + description: | + the filter in key=value format + - scorecardId (number, optional) Id of the scorecard + responses: + '200': + description: Success + schema: + $ref: '#/definitions/ScorecardQuestionListResponse' + '403': + description: No permission or wrong token + schema: + $ref: '#/definitions/ErrorModel' + '500': + description: Internal Server Error + schema: + $ref: '#/definitions/ErrorModel' +parameters: + challengeIdParam: + name: challengeId + in: path + description: challenge id + required: true + type: integer + format: int64 + reviewIdParam: + name: reviewId + in: path + description: review id + required: true + type: integer + format: int64 + handleParam: + name: handle + in: path + description: user handle + required: true + type: string +definitions: + UpdateAutoPilotBody: + type: object + properties: + param: + $ref: '#/definitions/EnableAutopilotPayload' + EnableAutopilotPayload: + title: enable autopilot payload + type: object + properties: + enable: + type: boolean + description: Enable or disable the autopilot setting + ResponseMetadata: + title: Metadata object for a response + type: object + properties: + totalCount: + type: integer + format: int64 + description: Total count of the objects + ErrorModel: + type: object + properties: + id: + type: string + description: unique id identifying the request + version: + type: string + result: + type: object + properties: + success: + type: boolean + status: + description: http status code + type: integer + format: int32 + debug: + type: object + content: + type: object + GenericSuccessResponse: + title: generic success response + type: object + properties: + id: + type: string + readOnly: true + description: unique id identifying the request + version: + type: string + result: + type: object + properties: + success: + type: boolean + status: + type: string + description: http status code + content: + type: string + description: success message + CreateReviewItemsResponse: + title: success response after creating review items + type: object + properties: + id: + type: string + readOnly: true + description: unique id identifying the request + version: + type: string + result: + type: object + properties: + success: + type: boolean + status: + type: string + description: http status code + content: + type: array + items: + type: integer + format: int64 + SuccessResponseWithId: + title: success response which returns the id as content + type: object + properties: + id: + type: string + readOnly: true + description: unique id identifying the request + version: + type: string + result: + type: object + properties: + success: + type: boolean + status: + type: string + description: http status code + content: + type: integer + format: int64 + AssignNextReviewResponse: + title: assign next review response + type: object + properties: + id: + type: string + readOnly: true + description: unique id identifying the request + version: + type: string + result: + type: object + properties: + success: + type: boolean + status: + type: string + description: http status code + content: + type: string + description: next review id + ScorecardQuestionListResponse: + title: scorecard question list response + type: object + properties: + id: + type: string + readOnly: true + description: unique id identifying the request + version: + type: string + result: + type: object + properties: + success: + type: boolean + status: + type: string + description: http status code + metadata: + $ref: '#/definitions/ResponseMetadata' + content: + type: array + items: + $ref: '#/definitions/ScorecardQuestion' + ScorecardQuestion: + type: object + properties: + id: + type: integer + format: int64 + description: the scorecard question id + description: + type: string + description: the scorecard question description + guideline: + type: string + description: the scorecard question guideline + questionTypeId: + type: integer + format: int64 + description: the scorecard question type id + createdAt: + type: string + description: Datetime (GMT) when task was created + readOnly: true + createdBy: + type: integer + format: int64 + description: READ-ONLY. User who created this task + readOnly: true + updatedAt: + type: string + description: READ-ONLY. Datetime (GMT) when task was updated + readOnly: true + updatedBy: + type: integer + format: int64 + description: READ-ONLY. User that last updated this task + readOnly: true + ScorecardListResponse: + title: scorecard list response + type: object + properties: + id: + type: string + readOnly: true + description: unique id identifying the request + version: + type: string + result: + type: object + properties: + success: + type: boolean + status: + type: string + description: http status code + metadata: + $ref: '#/definitions/ResponseMetadata' + content: + type: array + items: + $ref: '#/definitions/Scorecard' + Scorecard: + type: object + properties: + id: + type: integer + format: int64 + description: the scorecard id + name: + type: string + description: the scorecard name + createdAt: + type: string + description: Datetime (GMT) when task was created + readOnly: true + createdBy: + type: integer + format: int64 + description: READ-ONLY. User who created this task + readOnly: true + updatedAt: + type: string + description: READ-ONLY. Datetime (GMT) when task was updated + readOnly: true + updatedBy: + type: integer + format: int64 + description: READ-ONLY. User that last updated this task + readOnly: true + ReviewItemListResponse: + title: review item list response + type: object + properties: + id: + type: string + readOnly: true + description: unique id identifying the request + version: + type: string + result: + type: object + properties: + success: + type: boolean + status: + type: string + description: http status code + metadata: + $ref: '#/definitions/ResponseMetadata' + content: + type: array + items: + $ref: '#/definitions/ReviewItem' + ReviewItem: + type: object + properties: + id: + type: integer + format: int64 + description: the review item id + reviewId: + type: integer + format: int64 + description: the review id + scorecardQuestionId: + type: integer + format: int64 + description: the scorecard question id + uploadId: + type: integer + format: int64 + description: the upload id + answer: + type: string + description: review answer + sort: + type: integer + format: int32 + description: the sort index + reviewerUserId: + type: integer + format: int64 + description: the reviewer user id + submitterUserId: + type: integer + format: int64 + description: the submitter user id + committed: + type: boolean + description: committed or not + comments: + type: array + items: + $ref: '#/definitions/ReviewItemComment' + createdAt: + type: string + description: Datetime (GMT) when task was created + readOnly: true + createdBy: + type: integer + format: int64 + description: READ-ONLY. User who created this task + readOnly: true + updatedAt: + type: string + description: READ-ONLY. Datetime (GMT) when task was updated + readOnly: true + updatedBy: + type: integer + format: int64 + description: READ-ONLY. User that last updated this task + readOnly: true + ReviewItemComment: + type: object + properties: + id: + type: integer + format: int64 + description: the review id + resourceId: + type: integer + format: int64 + description: the resource id + reviewItemId: + type: integer + format: int64 + description: the review item id + commentTypeId: + type: integer + format: int64 + description: the comment type id + content: + type: string + description: the content + ReviewListResponse: + title: review list response + type: object + properties: + id: + type: string + readOnly: true + description: unique id identifying the request + version: + type: string + result: + type: object + properties: + success: + type: boolean + status: + type: string + description: http status code + metadata: + $ref: '#/definitions/ResponseMetadata' + content: + type: array + items: + $ref: '#/definitions/Review' + ReviewResponse: + title: review response + type: object + properties: + id: + type: string + readOnly: true + description: unique id identifying the request + version: + type: string + result: + type: object + properties: + success: + type: boolean + status: + type: string + description: http status code + metadata: + $ref: '#/definitions/ResponseMetadata' + content: + $ref: '#/definitions/Review' + Review: + type: object + properties: + id: + type: integer + format: int64 + description: the review id + resourceId: + type: integer + format: int64 + description: the resource id + submissionId: + type: integer + format: int64 + description: the submission id + projectPhaseId: + type: integer + format: int64 + description: the project phase id + scorecardId: + type: integer + format: int64 + description: the scorecard id + uploadId: + type: integer + format: int64 + description: the upload id + committed: + type: boolean + description: comitted or not + score: + type: number + format: float + description: the final score + initialScore: + type: number + format: float + description: the initial score + reviewerUserId: + type: integer + format: int64 + description: the reviewer user id + submitterUserId: + type: integer + format: int64 + description: the submitter user id + createdAt: + type: string + description: Datetime (GMT) when task was created + readOnly: true + createdBy: + type: integer + format: int64 + description: READ-ONLY. User who created this task + readOnly: true + updatedAt: + type: string + description: READ-ONLY. Datetime (GMT) when task was updated + readOnly: true + updatedBy: + type: integer + format: int64 + description: READ-ONLY. User that last updated this task + readOnly: true + CopilotFeedbackListResponse: + title: copilot feedback list response + type: object + properties: + id: + type: string + readOnly: true + description: unique id identifying the request + version: + type: string + result: + type: object + properties: + success: + type: boolean + status: + type: string + description: http status code + metadata: + $ref: '#/definitions/ResponseMetadata' + content: + type: array + items: + $ref: '#/definitions/CopilotFeedback' + CopilotFeedback: + type: object + properties: + userId: + type: integer + format: int64 + description: the id of user who writes the feedback + reviewText: + type: string + description: feedback text + timeRating: + type: integer + format: int64 + description: time rating + qualityRating: + type: integer + format: int64 + description: quality rating + communicationRating: + type: integer + format: int64 + description: communication rating + managementRating: + type: integer + format: int64 + description: management rating + copilotProjectId: + type: integer + format: int64 + description: copilot project id + directProjectId: + type: integer + format: int64 + description: direct project id + directProjectName: + type: string + description: direct project name + createdAt: + type: string + description: Datetime (GMT) when task was created + readOnly: true + createdBy: + type: integer + format: int64 + description: READ-ONLY. User who created this task + readOnly: true + updatedAt: + type: string + description: READ-ONLY. Datetime (GMT) when task was updated + readOnly: true + updatedBy: + type: integer + format: int64 + description: READ-ONLY. User that last updated this task + readOnly: true From 711d4edb90aacaa0c5c55549f7e89a624bbb72d5 Mon Sep 17 00:00:00 2001 From: Qun Huang Date: Mon, 23 Oct 2017 09:42:51 -0400 Subject: [PATCH 06/19] fix ReviewResourceTest --- .../service/test/resources/ReviewResourceTest.java | 8 +++++--- 1 file changed, 5 insertions(+), 3 deletions(-) diff --git a/service/src/test/java/com/appirio/service/test/resources/ReviewResourceTest.java b/service/src/test/java/com/appirio/service/test/resources/ReviewResourceTest.java index 6538273..c891080 100644 --- a/service/src/test/java/com/appirio/service/test/resources/ReviewResourceTest.java +++ b/service/src/test/java/com/appirio/service/test/resources/ReviewResourceTest.java @@ -2,6 +2,7 @@ import com.appirio.service.review.api.Review; import com.appirio.service.review.manager.ReviewManager; +import com.appirio.service.review.manager.ChallengeManager; import com.appirio.service.review.resources.ReviewResource; import com.appirio.service.test.BaseTest; import static org.mockito.Mockito.*; @@ -12,8 +13,9 @@ import org.junit.Test; public class ReviewResourceTest extends BaseTest{ - /* public static final ReviewManager mockReviewManager = mock(ReviewManager.class); - public static final ReviewResource unit = new ReviewResource(mockReviewManager); + public static final ReviewManager mockReviewManager = mock(ReviewManager.class); + public static final ChallengeManager mockChallengeManager = mock(ChallengeManager.class); + public static final ReviewResource unit = new ReviewResource(mockReviewManager, mockChallengeManager); @Test public void testGetReviews() throws Exception{ @@ -42,5 +44,5 @@ private Review getReview(){ public void testUpdateReview(){ unit.updateReview(createUser("4"),getReview(),1l); verify(mockReviewManager).updateReviewUserEditableFields(anyObject(),anyLong(),anyObject()); - }*/ + } } From 13eaa460fa1130be082fb9215cf999666bb9deaa Mon Sep 17 00:00:00 2001 From: Qun Huang Date: Wed, 1 Nov 2017 09:25:56 +0800 Subject: [PATCH 07/19] update maven repo url --- service/pom.xml | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/service/pom.xml b/service/pom.xml index 3ee3722..f06bb64 100644 --- a/service/pom.xml +++ b/service/pom.xml @@ -242,12 +242,12 @@ appirio-repo Appirio Maven Repository - file:///mnt/maven/repository + http://maven.topcoder-dev.com:8080/ appirio-repo Appirio Maven Repository - file:///mnt/maven/repository + http://maven.topcoder-dev.com:8080/ @@ -297,7 +297,7 @@ 1_temp_appirio_maven_repo Appirio Maven Local Repository - file:///mnt/maven/repository + http://maven.topcoder-dev.com:8080/ Appirio Technology Maven Repository From 57ca2adeefacda0dd16c2e983b68d9adb8700159 Mon Sep 17 00:00:00 2001 From: Qun Huang Date: Wed, 1 Nov 2017 23:40:40 +0800 Subject: [PATCH 08/19] update pom.xml to use temp maven repo --- service/pom.xml | 11 +++++++---- 1 file changed, 7 insertions(+), 4 deletions(-) diff --git a/service/pom.xml b/service/pom.xml index f06bb64..20c1190 100644 --- a/service/pom.xml +++ b/service/pom.xml @@ -294,10 +294,13 @@ - - 1_temp_appirio_maven_repo - Appirio Maven Local Repository - http://maven.topcoder-dev.com:8080/ + + temp-maven-repo + https://github.com/appirio-tech/temp-maven-repo/raw/master + + true + always + Appirio Technology Maven Repository From ec18c79c761f1e72a921cdb6f061e7db1bd7c539 Mon Sep 17 00:00:00 2001 From: Thomas Kranitsas Date: Mon, 20 Nov 2017 08:53:54 +0200 Subject: [PATCH 09/19] Add GET /reviewAuctions endpoint to swagger.yaml --- swagger.yaml | 107 ++++++++++++++++++++++++++++++++++++++++++++++++--- 1 file changed, 101 insertions(+), 6 deletions(-) diff --git a/swagger.yaml b/swagger.yaml index 6e5d776..4f3c9d5 100644 --- a/swagger.yaml +++ b/swagger.yaml @@ -4,8 +4,8 @@ info: title: Review Service API description: | Services to enable members to review assets. - It includes functionality for scorecards, scorecard questions, reviews, - review items, scoring individual reviews, aggregating review scores. + It includes functionality for scorecards, scorecard questions, reviews, + review items, scoring individual reviews, aggregating review scores, review auctions. host: api.topcoder.com basePath: /v3 schemes: @@ -110,7 +110,7 @@ paths: operationId: assignNextReview description: > For peer review challenges, assign next review will look for the next - submission that the currently + submission that the currently logged in user has to review, and perform the association. @@ -199,7 +199,7 @@ paths: operationId: getReview description: > Get specified review. Users may only see review that they created, or - submitted reviews (commited = 1) + submitted reviews (commited = 1) of their own submissions done by other users. security: @@ -229,7 +229,7 @@ paths: operationId: updateReview description: > Update specified review. Users may only update reviews that they created - and have not been submitted yet (commited = 0). + and have not been submitted yet (commited = 0). The only field that can be updated is the committed field. parameters: @@ -292,7 +292,7 @@ paths: operationId: getReviewItems description: > Fetches review items. Users may only see reviews items related to - reviews that they created, + reviews that they created, or submitted reviews (commited = 1) of their own submissions done by other users. @@ -628,6 +628,66 @@ paths: description: Internal Server Error schema: $ref: '#/definitions/ErrorModel' + /reviewAuctions: + get: + tags: + - Review Auctions + operationId: getReviewAuctions + description: | + Fetches review auctions + parameters: + - name: challengeType + required: true + type: string + in: query + description: The type of the challenge, like "Code", "First2Finish" etc. + enum: + - Component Design + - Component Development + - Component Testing + - Application Specification + - Application Architecture + - Bug Hunt + - Test Scenarios + - Test Suites + - Application Assembly + - Application Conceptualization + - UI Prototype + - RIA Build + - RIA Component + - Copilot Posting + - Content Creation + - Reporting + - First2Finish + - Code + - name: offset + required: false + type: string + in: query + description: | + Number of items to skip. (DEFAULT 0) + - name: limit + required: false + type: string + in: query + description: >- + Max records to return. (DEFAULT 10) + responses: + '200': + description: Success + schema: + type: array + description: A list of Review Auctions + items: + $ref: '#/definitions/ReviewAuctionItem' + '404': + description: No such challenge type + schema: + $ref: '#/definitions/ErrorModel' + '500': + description: Internal Server Error + schema: + $ref: '#/definitions/ErrorModel' parameters: challengeIdParam: name: challengeId @@ -801,6 +861,41 @@ definitions: type: array items: $ref: '#/definitions/ScorecardQuestion' + ReviewAuctionItem: + title: Review Auction + type: object + properties: + id: + type: integer + format: int64 + description: The review auction id + startDate: + type: string + description: The start date of the review, in the format like "Sat Nov 18 2017 14:55:12 GMT+0200 (EET)" + submissions: + type: integer + description: Number of submissions + payment: + type: number + format: double + description: The payment of this review + openPositions: + type: integer + description: Number of the open positions of reviewers for this review auction. + type: + type: string + description: The type of this review auction, usually be "REVIEW" + challenge: + type: object + description: The challenge information of this review Auction + properties: + id: + type: integer + format: int64 + description: The challenge ID + title: + type: string + description: The name of the challenge ScorecardQuestion: type: object properties: From 8de5a57b82d53c2bea686e402f2d6019ff99a230 Mon Sep 17 00:00:00 2001 From: Thomas Kranitsas Date: Mon, 20 Nov 2017 21:22:07 +0200 Subject: [PATCH 10/19] Rename Auctions to Opportunities --- swagger.yaml | 26 +++++++++++++------------- 1 file changed, 13 insertions(+), 13 deletions(-) diff --git a/swagger.yaml b/swagger.yaml index 4f3c9d5..1f69cbe 100644 --- a/swagger.yaml +++ b/swagger.yaml @@ -5,7 +5,7 @@ info: description: | Services to enable members to review assets. It includes functionality for scorecards, scorecard questions, reviews, - review items, scoring individual reviews, aggregating review scores, review auctions. + review items, scoring individual reviews, aggregating review scores, review opportunities. host: api.topcoder.com basePath: /v3 schemes: @@ -628,13 +628,13 @@ paths: description: Internal Server Error schema: $ref: '#/definitions/ErrorModel' - /reviewAuctions: + /reviewOpportunities: get: tags: - - Review Auctions - operationId: getReviewAuctions + - Review Opportunities + operationId: getReviewOpportunities description: | - Fetches review auctions + Fetches review opportunities parameters: - name: challengeType required: true @@ -677,9 +677,9 @@ paths: description: Success schema: type: array - description: A list of Review Auctions + description: A list of Review Opportunities items: - $ref: '#/definitions/ReviewAuctionItem' + $ref: '#/definitions/ReviewOpportunityItem' '404': description: No such challenge type schema: @@ -861,14 +861,14 @@ definitions: type: array items: $ref: '#/definitions/ScorecardQuestion' - ReviewAuctionItem: - title: Review Auction + ReviewOpportunityItem: + title: Review Opportunity type: object properties: id: type: integer format: int64 - description: The review auction id + description: The review opportunity id startDate: type: string description: The start date of the review, in the format like "Sat Nov 18 2017 14:55:12 GMT+0200 (EET)" @@ -881,13 +881,13 @@ definitions: description: The payment of this review openPositions: type: integer - description: Number of the open positions of reviewers for this review auction. + description: Number of the open positions of reviewers for this review opportunity. type: type: string - description: The type of this review auction, usually be "REVIEW" + description: The type of this review opportunity, usually be "REVIEW" challenge: type: object - description: The challenge information of this review Auction + description: The challenge information of this review opportunity properties: id: type: integer From e9afeb186597e4c57f2c0ded430da7194c098c8a Mon Sep 17 00:00:00 2001 From: Thomas Kranitsas Date: Thu, 30 Nov 2017 12:50:32 +0200 Subject: [PATCH 11/19] Add /reviewOpportunities endpoint to fetch review opps --- ...eview-microservice.postman_collection.json | 386 +++++++++++------- .../ReviewOpportunitiesFactory.java | 35 ++ .../review/ReviewServiceApplication.java | 12 +- .../review/api/ReviewOpportunitiesDTO.java | 150 +++++++ .../review/api/ReviewOpportunityItem.java | 88 ++++ .../review/dao/ReviewOpportunitiesDAO.java | 32 ++ .../manager/ReviewOpportunitiesManager.java | 238 +++++++++++ .../ReviewOpportunitiesResource.java | 72 ++++ .../src/main/resources/new-relic-url-patterns | 3 +- .../get-review-opportunities.sql | 197 +++++++++ swagger.yaml | 65 ++- 11 files changed, 1088 insertions(+), 190 deletions(-) create mode 100644 service/src/main/java/com/appirio/service/resourcefactory/ReviewOpportunitiesFactory.java create mode 100644 service/src/main/java/com/appirio/service/review/api/ReviewOpportunitiesDTO.java create mode 100644 service/src/main/java/com/appirio/service/review/api/ReviewOpportunityItem.java create mode 100644 service/src/main/java/com/appirio/service/review/dao/ReviewOpportunitiesDAO.java create mode 100644 service/src/main/java/com/appirio/service/review/manager/ReviewOpportunitiesManager.java create mode 100644 service/src/main/java/com/appirio/service/review/resources/ReviewOpportunitiesResource.java create mode 100644 service/src/main/resources/sql/review-opportunities/get-review-opportunities.sql diff --git a/postman/review-microservice.postman_collection.json b/postman/review-microservice.postman_collection.json index b9a2646..9af14b0 100755 --- a/postman/review-microservice.postman_collection.json +++ b/postman/review-microservice.postman_collection.json @@ -2,101 +2,89 @@ "variables": [], "info": { "name": "review-microservice", - "_postman_id": "b6914582-b6ca-29d2-9b12-de70d5e4cb81", - "description": "", + "_postman_id": "0e16939f-32a7-1e78-496e-e63027184f19", "schema": "https://schema.getpostman.com/json/collection/v2.0.0/collection.json" }, "item": [ { "name": "Delete Review", - "description": "", "item": [ { "name": "Anonymous - Delete Review - 401", "request": { - "url": "{{apiUrl}}/v3/reviews/71492", "method": "DELETE", "header": [], "body": { "mode": "raw", "raw": "" }, - "description": "" + "url": "{{apiUrl}}/v3/reviews/71492" }, "response": [] }, { "name": "Admin - Delete Review", "request": { - "url": "{{apiUrl}}/v3/reviews/71492", "method": "DELETE", "header": [ { "key": "Authorization", - "value": "{{Administrator Token}}", - "description": "" + "value": "{{Administrator Token}}" }, { "key": "Content-Type", - "value": "application/json", - "description": "" + "value": "application/json" } ], "body": { "mode": "raw", "raw": "" }, - "description": "" + "url": "{{apiUrl}}/v3/reviews/71492" }, "response": [] }, { "name": "Admin - Delete Review - Not Found", "request": { - "url": "{{apiUrl}}/v3/reviews/4582", "method": "DELETE", "header": [ { "key": "Authorization", - "value": "{{Administrator Token}}", - "description": "" + "value": "{{Administrator Token}}" }, { "key": "Content-Type", - "value": "application/json", - "description": "" + "value": "application/json" } ], "body": { "mode": "raw", "raw": "" }, - "description": "" + "url": "{{apiUrl}}/v3/reviews/4582" }, "response": [] }, { "name": "Non-Admin - Delete Review", "request": { - "url": "{{apiUrl}}/v3/reviews/71492", "method": "DELETE", "header": [ { "key": "Authorization", - "value": "{{Reviewer1 Token}}", - "description": "" + "value": "{{Reviewer1 Token}}" }, { "key": "Content-Type", - "value": "application/json", - "description": "" + "value": "application/json" } ], "body": { "mode": "raw", "raw": "" }, - "description": "" + "url": "{{apiUrl}}/v3/reviews/71492" }, "response": [] } @@ -104,106 +92,93 @@ }, { "name": "Re-open Review", - "description": "", "item": [ { "name": "Admin - Reopen Review", "request": { - "url": "{{apiUrl}}/v3/reviews/71492/reopen", "method": "POST", "header": [ { "key": "Authorization", - "value": "{{Administrator Token}}", - "description": "" + "value": "{{Administrator Token}}" }, { "key": "Content-Type", - "value": "application/json", - "description": "" + "value": "application/json" } ], "body": { "mode": "raw", "raw": "" }, - "description": "" + "url": "{{apiUrl}}/v3/reviews/71492/reopen" }, "response": [] }, { "name": "Anonymous - Reopen Review - 401", "request": { - "url": "{{apiUrl}}/v3/reviews/71492/reopen", "method": "POST", "header": [ { "key": "Authorization", "value": "{{Administrator Token}}", - "description": "", "disabled": true }, { "key": "Content-Type", - "value": "application/json", - "description": "" + "value": "application/json" } ], "body": { "mode": "raw", "raw": "" }, - "description": "" + "url": "{{apiUrl}}/v3/reviews/71492/reopen" }, "response": [] }, { "name": "Admin - Reopen Review - Not Found", "request": { - "url": "{{apiUrl}}/v3/reviews/7452/reopen", "method": "POST", "header": [ { "key": "Authorization", - "value": "{{Administrator Token}}", - "description": "" + "value": "{{Administrator Token}}" }, { "key": "Content-Type", - "value": "application/json", - "description": "" + "value": "application/json" } ], "body": { "mode": "raw", "raw": "" }, - "description": "" + "url": "{{apiUrl}}/v3/reviews/7452/reopen" }, "response": [] }, { "name": "Non-Admin - Reopen Review", "request": { - "url": "{{apiUrl}}/v3/reviews/71492/reopen", "method": "POST", "header": [ { "key": "Authorization", - "value": "{{Reviewer1 Token}}", - "description": "" + "value": "{{Reviewer1 Token}}" }, { "key": "Content-Type", - "value": "application/json", - "description": "" + "value": "application/json" } ], "body": { "mode": "raw", "raw": "" }, - "description": "" + "url": "{{apiUrl}}/v3/reviews/71492/reopen" }, "response": [] } @@ -211,131 +186,115 @@ }, { "name": "Reset Aggregation", - "description": "", "item": [ { "name": "Admin - Reset Aggregation", "request": { - "url": "{{apiUrl}}/v3/reviews/30005520/resetAggregation", "method": "POST", "header": [ { "key": "Authorization", - "value": "{{Administrator Token}}", - "description": "" + "value": "{{Administrator Token}}" }, { "key": "Content-Type", - "value": "application/json", - "description": "" + "value": "application/json" } ], "body": { "mode": "raw", "raw": "" }, - "description": "" + "url": "{{apiUrl}}/v3/reviews/30005520/resetAggregation" }, "response": [] }, { "name": "Non Admin - Reset Aggregation", "request": { - "url": "{{apiUrl}}/v3/reviews/30005520/resetAggregation", "method": "POST", "header": [ { "key": "Authorization", - "value": "{{Reviewer1 Token}}", - "description": "" + "value": "{{Reviewer1 Token}}" }, { "key": "Content-Type", - "value": "application/json", - "description": "" + "value": "application/json" } ], "body": { "mode": "raw", "raw": "" }, - "description": "" + "url": "{{apiUrl}}/v3/reviews/30005520/resetAggregation" }, "response": [] }, { "name": "Anonymous - Reset Aggregation - 401", "request": { - "url": "{{apiUrl}}/v3/reviews/30005520/resetAggregation", "method": "POST", "header": [ { "key": "Authorization", "value": "{{Administrator Token}}", - "description": "", "disabled": true }, { "key": "Content-Type", - "value": "application/json", - "description": "" + "value": "application/json" } ], "body": { "mode": "raw", "raw": "" }, - "description": "" + "url": "{{apiUrl}}/v3/reviews/30005520/resetAggregation" }, "response": [] }, { "name": "Admin - Reset Aggregation - Not Found", "request": { - "url": "{{apiUrl}}/v3/reviews/13/resetAggregation", "method": "POST", "header": [ { "key": "Authorization", - "value": "{{Administrator Token}}", - "description": "" + "value": "{{Administrator Token}}" }, { "key": "Content-Type", - "value": "application/json", - "description": "" + "value": "application/json" } ], "body": { "mode": "raw", "raw": "" }, - "description": "" + "url": "{{apiUrl}}/v3/reviews/13/resetAggregation" }, "response": [] }, { "name": "Admin - Reset Aggregation - Challenge Not Active - 400", "request": { - "url": "{{apiUrl}}/v3/reviews/11/resetAggregation", "method": "POST", "header": [ { "key": "Authorization", - "value": "{{Administrator Token}}", - "description": "" + "value": "{{Administrator Token}}" }, { "key": "Content-Type", - "value": "application/json", - "description": "" + "value": "application/json" } ], "body": { "mode": "raw", "raw": "" }, - "description": "" + "url": "{{apiUrl}}/v3/reviews/11/resetAggregation" }, "response": [] } @@ -343,100 +302,88 @@ }, { "name": "Reset Review", - "description": "", "item": [ { "name": "Admin - Reset Review", "request": { - "url": "{{apiUrl}}/v3/reviews/30005520/resetReview", "method": "POST", "header": [ { "key": "Authorization", - "value": "{{Administrator Token}}", - "description": "" + "value": "{{Administrator Token}}" }, { "key": "Content-Type", - "value": "application/json", - "description": "" + "value": "application/json" } ], "body": { "mode": "raw", "raw": "" }, - "description": "" + "url": "{{apiUrl}}/v3/reviews/30005520/resetReview" }, "response": [] }, { "name": "Admin - Reset Review - Not Found", "request": { - "url": "{{apiUrl}}/v3/reviews/30005520/resetReview", "method": "POST", "header": [ { "key": "Authorization", - "value": "{{Administrator Token}}", - "description": "" + "value": "{{Administrator Token}}" }, { "key": "Content-Type", - "value": "application/json", - "description": "" + "value": "application/json" } ], "body": { "mode": "raw", "raw": "" }, - "description": "" + "url": "{{apiUrl}}/v3/reviews/30005520/resetReview" }, "response": [] }, { "name": "Non Admin - Reset Review", "request": { - "url": "{{apiUrl}}/v3/reviews/30005520/resetReview", "method": "POST", "header": [ { "key": "Authorization", - "value": "{{Reviewer1 Token}}", - "description": "" + "value": "{{Reviewer1 Token}}" }, { "key": "Content-Type", - "value": "application/json", - "description": "" + "value": "application/json" } ], "body": { "mode": "raw", "raw": "" }, - "description": "" + "url": "{{apiUrl}}/v3/reviews/30005520/resetReview" }, "response": [] }, { "name": "Anonymous - Reset Review - Unauthorized - 401", "request": { - "url": "{{apiUrl}}/v3/reviews/30005520/resetReview", "method": "POST", "header": [ { "key": "Content-Type", - "value": "application/json", - "description": "" + "value": "application/json" } ], "body": { "mode": "raw", "raw": "" }, - "description": "" + "url": "{{apiUrl}}/v3/reviews/30005520/resetReview" }, "response": [] } @@ -444,150 +391,293 @@ }, { "name": "Update Autopilot", - "description": "", "item": [ { "name": "Admin - Enable Autopilot", "request": { - "url": "{{apiUrl}}/v3/reviews/30005520/updateAutopilot", "method": "POST", "header": [ { "key": "Authorization", - "value": "{{Administrator Token}}", - "description": "" + "value": "{{Administrator Token}}" }, { "key": "Content-Type", - "value": "application/json", - "description": "" + "value": "application/json" } ], "body": { "mode": "raw", "raw": "{\n \"param\" : {\n \t\"enable\" : true\n }\n \n}" }, - "description": "" + "url": "{{apiUrl}}/v3/reviews/30005520/updateAutopilot" }, "response": [] }, { "name": "Admin - Enable Autopilot - Bad Request - 400", "request": { - "url": "{{apiUrl}}/v3/reviews/30005520/updateAutopilot", "method": "POST", "header": [ { "key": "Authorization", - "value": "{{Administrator Token}}", - "description": "" + "value": "{{Administrator Token}}" }, { "key": "Content-Type", - "value": "application/json", - "description": "" + "value": "application/json" } ], "body": { "mode": "raw", "raw": "{\n \"param\" : {\n \t\"enable\" : \n }\n \n}" }, - "description": "" + "url": "{{apiUrl}}/v3/reviews/30005520/updateAutopilot" }, "response": [] }, { "name": "Admin - Disable Autopilot", "request": { - "url": "{{apiUrl}}/v3/reviews/30005520/updateAutopilot", "method": "POST", "header": [ { "key": "Authorization", - "value": "{{Administrator Token}}", - "description": "" + "value": "{{Administrator Token}}" }, { "key": "Content-Type", - "value": "application/json", - "description": "" + "value": "application/json" } ], "body": { "mode": "raw", "raw": "{\n \"param\" : {\n \t\"enable\" : false\n }\n \n}" }, - "description": "" + "url": "{{apiUrl}}/v3/reviews/30005520/updateAutopilot" }, "response": [] }, { "name": "Non Admin - Enable Autopilot", "request": { - "url": "{{apiUrl}}/v3/reviews/30005520/updateAutopilot", "method": "POST", "header": [ { "key": "Authorization", - "value": "{{Reviewer1 Token}}", - "description": "" + "value": "{{Reviewer1 Token}}" }, { "key": "Content-Type", - "value": "application/json", - "description": "" + "value": "application/json" } ], "body": { "mode": "raw", "raw": "{\n \"param\" : {\n \t\"enable\" : true\n }\n \n}" }, - "description": "" + "url": "{{apiUrl}}/v3/reviews/30005520/updateAutopilot" }, "response": [] }, { "name": "Anonymous - Enable Autopilot - Unauthorized - 401", "request": { - "url": "{{apiUrl}}/v3/reviews/30005520/updateAutopilot", "method": "POST", "header": [ { "key": "Content-Type", - "value": "application/json", - "description": "" + "value": "application/json" } ], "body": { "mode": "raw", "raw": "{\n \"param\" : {\n \t\"enable\" : true\n }\n \n}" }, - "description": "" + "url": "{{apiUrl}}/v3/reviews/30005520/updateAutopilot" }, "response": [] }, { "name": "Admin - Enable Autopilot - Not Found", "request": { - "url": "{{apiUrl}}/v3/reviews/13/updateAutopilot", "method": "POST", "header": [ { "key": "Authorization", - "value": "{{Administrator Token}}", - "description": "" + "value": "{{Administrator Token}}" }, { "key": "Content-Type", - "value": "application/json", - "description": "" + "value": "application/json" } ], "body": { "mode": "raw", "raw": "{\n \"param\" : {\n \t\"enable\" : true\n }\n \n}" }, - "description": "" + "url": "{{apiUrl}}/v3/reviews/13/updateAutopilot" + }, + "response": [] + } + ] + }, + { + "name": "Review Opportunities", + "item": [ + { + "name": "Get review opportunities for dev F2F (iterative review)", + "request": { + "method": "GET", + "header": [ + { + "key": "Authorization", + "value": "Bearer eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9.eyJyb2xlcyI6WyJhZG1pbmlzdHJhdG9yIl0sImlzcyI6Imh0dHBzOi8vYXBpLnRvcGNvZGVyLWRldi5jb20iLCJoYW5kbGUiOiJoZWZmYW4iLCJleHAiOjE3NjYyODkyNDYsInVzZXJJZCI6IjEzMjQ1NiIsImlhdCI6MTQ1MDkyOTI0NiwiZW1haWwiOm51bGwsImp0aSI6IjEzNjljNjAwLWUwYTEtNDUyNS1hN2M3LTU2YmU3ZDgxM2Y1MSJ9.hp5peSoj-fh3KFkskvBpfUFIcJNtsv4zIMFV-D8F3JA", + "disabled": true + }, + { + "key": "Content-Type", + "value": "application/json" + } + ], + "body": { + "mode": "raw", + "raw": "" + }, + "url": { + "raw": "{{apiUrl}}/v3/reviewOpportunities?challengeTypeId=38", + "host": [ + "{{apiUrl}}" + ], + "path": [ + "v3", + "reviewOpportunities" + ], + "query": [ + { + "key": "challengeTypeId", + "value": "38", + "equals": true + } + ] + } + }, + "response": [] + }, + { + "name": "Get review opportunities for assembly (contest review and spec)", + "request": { + "method": "GET", + "header": [ + { + "key": "Authorization", + "value": "Bearer eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9.eyJyb2xlcyI6WyJhZG1pbmlzdHJhdG9yIl0sImlzcyI6Imh0dHBzOi8vYXBpLnRvcGNvZGVyLWRldi5jb20iLCJoYW5kbGUiOiJoZWZmYW4iLCJleHAiOjE3NjYyODkyNDYsInVzZXJJZCI6IjEzMjQ1NiIsImlhdCI6MTQ1MDkyOTI0NiwiZW1haWwiOm51bGwsImp0aSI6IjEzNjljNjAwLWUwYTEtNDUyNS1hN2M3LTU2YmU3ZDgxM2Y1MSJ9.hp5peSoj-fh3KFkskvBpfUFIcJNtsv4zIMFV-D8F3JA", + "disabled": true + }, + { + "key": "Content-Type", + "value": "application/json" + } + ], + "body": { + "mode": "raw", + "raw": "" + }, + "url": { + "raw": "{{apiUrl}}/v3/reviewOpportunities?challengeTypeId=14", + "host": [ + "{{apiUrl}}" + ], + "path": [ + "v3", + "reviewOpportunities" + ], + "query": [ + { + "key": "challengeTypeId", + "value": "14", + "equals": true + } + ] + } + }, + "response": [] + }, + { + "name": "Get review opportunities (challenge type id not supported)", + "request": { + "method": "GET", + "header": [ + { + "key": "Authorization", + "value": "Bearer eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9.eyJyb2xlcyI6WyJhZG1pbmlzdHJhdG9yIl0sImlzcyI6Imh0dHBzOi8vYXBpLnRvcGNvZGVyLWRldi5jb20iLCJoYW5kbGUiOiJoZWZmYW4iLCJleHAiOjE3NjYyODkyNDYsInVzZXJJZCI6IjEzMjQ1NiIsImlhdCI6MTQ1MDkyOTI0NiwiZW1haWwiOm51bGwsImp0aSI6IjEzNjljNjAwLWUwYTEtNDUyNS1hN2M3LTU2YmU3ZDgxM2Y1MSJ9.hp5peSoj-fh3KFkskvBpfUFIcJNtsv4zIMFV-D8F3JA", + "disabled": true + }, + { + "key": "Content-Type", + "value": "application/json" + } + ], + "body": { + "mode": "raw", + "raw": "" + }, + "url": { + "raw": "{{apiUrl}}/v3/reviewOpportunities?challengeTypeId=100", + "host": [ + "{{apiUrl}}" + ], + "path": [ + "v3", + "reviewOpportunities" + ], + "query": [ + { + "key": "challengeTypeId", + "value": "100", + "equals": true + } + ] + } + }, + "response": [] + }, + { + "name": "Get review opportunities (challenge type id is not positive)", + "request": { + "method": "GET", + "header": [ + { + "key": "Authorization", + "value": "Bearer eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9.eyJyb2xlcyI6WyJhZG1pbmlzdHJhdG9yIl0sImlzcyI6Imh0dHBzOi8vYXBpLnRvcGNvZGVyLWRldi5jb20iLCJoYW5kbGUiOiJoZWZmYW4iLCJleHAiOjE3NjYyODkyNDYsInVzZXJJZCI6IjEzMjQ1NiIsImlhdCI6MTQ1MDkyOTI0NiwiZW1haWwiOm51bGwsImp0aSI6IjEzNjljNjAwLWUwYTEtNDUyNS1hN2M3LTU2YmU3ZDgxM2Y1MSJ9.hp5peSoj-fh3KFkskvBpfUFIcJNtsv4zIMFV-D8F3JA", + "disabled": true + }, + { + "key": "Content-Type", + "value": "application/json" + } + ], + "body": { + "mode": "raw", + "raw": "" + }, + "url": { + "raw": "{{apiUrl}}/v3/reviewOpportunities?challengeTypeId=-14", + "host": [ + "{{apiUrl}}" + ], + "path": [ + "v3", + "reviewOpportunities" + ], + "query": [ + { + "key": "challengeTypeId", + "value": "-14", + "equals": true + } + ] + } }, "response": [] } @@ -596,72 +686,64 @@ { "name": "Get list of all Scorecards", "request": { - "url": "{{apiUrl}}/v3/scorecards", "method": "GET", "header": [ { "key": "Content-Type", - "value": "application/json", - "description": "" + "value": "application/json" } ], "body": { "mode": "raw", "raw": "" }, - "description": "" + "url": "{{apiUrl}}/v3/scorecards" }, "response": [] }, { "name": "Get list of all reviews Reviewer1", "request": { - "url": "{{apiUrl}}/v3/reviews", "method": "GET", "header": [ { "key": "Authorization", - "value": "{{Reviewer1 Token}}", - "description": "" + "value": "{{Reviewer1 Token}}" }, { "key": "Content-Type", - "value": "application/json", - "description": "" + "value": "application/json" } ], "body": { "mode": "raw", "raw": "" }, - "description": "" + "url": "{{apiUrl}}/v3/reviews" }, "response": [] }, { "name": "Get list of all reviews Reviewer2", "request": { - "url": "{{apiUrl}}/v3/reviews", "method": "GET", "header": [ { "key": "Authorization", - "value": "{{Reviewer2 Token}}", - "description": "" + "value": "{{Reviewer2 Token}}" }, { "key": "Content-Type", - "value": "application/json", - "description": "" + "value": "application/json" } ], "body": { "mode": "raw", "raw": "" }, - "description": "" + "url": "{{apiUrl}}/v3/reviews" }, "response": [] } ] -} +} \ No newline at end of file diff --git a/service/src/main/java/com/appirio/service/resourcefactory/ReviewOpportunitiesFactory.java b/service/src/main/java/com/appirio/service/resourcefactory/ReviewOpportunitiesFactory.java new file mode 100644 index 0000000..d9e0baf --- /dev/null +++ b/service/src/main/java/com/appirio/service/resourcefactory/ReviewOpportunitiesFactory.java @@ -0,0 +1,35 @@ +package com.appirio.service.resourcefactory; + +import com.appirio.service.review.manager.ReviewOpportunitiesManager; +import com.appirio.service.review.resources.ReviewOpportunitiesResource; +import com.appirio.service.supply.resources.ResourceFactory; +import com.appirio.supply.SupplyException; + +/** + * ReviewOpportunitiesFactory is used to create the ReviewOpportunitiesResource + * + * It's added in Topcoder - Create Endpoint to Fetch Review Opportunities v1.0 + * + * @author TCCoder + * @version 1.0 + * + */ +public class ReviewOpportunitiesFactory implements ResourceFactory { + + /** + * Simple constructor to initialize ReviewOpportunitiesFactory + */ + public ReviewOpportunitiesFactory() { + } + + /** + * Get ReviewOpportunitiesResource object + * @return ReviewOpportunitiesResource the review opportunities resource + * @throws SupplyException exception for supply server + */ + @Override + public ReviewOpportunitiesResource getResourceInstance() throws SupplyException { + final ReviewOpportunitiesManager reviewOpportunitiesManager = new ReviewOpportunitiesManager(); + return new ReviewOpportunitiesResource(reviewOpportunitiesManager); + } +} \ No newline at end of file diff --git a/service/src/main/java/com/appirio/service/review/ReviewServiceApplication.java b/service/src/main/java/com/appirio/service/review/ReviewServiceApplication.java index e596c7a..5b7d754 100644 --- a/service/src/main/java/com/appirio/service/review/ReviewServiceApplication.java +++ b/service/src/main/java/com/appirio/service/review/ReviewServiceApplication.java @@ -6,6 +6,7 @@ import com.appirio.service.resourcefactory.PeerReviewFactory; import com.appirio.service.resourcefactory.ReviewFactory; import com.appirio.service.resourcefactory.ReviewItemFactory; +import com.appirio.service.resourcefactory.ReviewOpportunitiesFactory; import com.appirio.service.resourcefactory.ScorecardFactory; import com.appirio.service.resourcefactory.ScorecardQuestionFactory; import com.appirio.service.supply.resources.SupplyDatasourceFactory; @@ -15,8 +16,13 @@ /** * Main application class for the review service application. + * + * Version 1.1 - Topcoder - Create Endpoint to Fetch Review Opportunities v1.0 + * - register ReviewOpportunitiesFactory in registerResources + * * * @author TCDEVELOPER + * @version 1.1 */ public class ReviewServiceApplication extends BaseApplication { @@ -56,9 +62,10 @@ protected void logServiceSpecificConfigs(ReviewServiceConfiguration config) { } /** + * Register resources for the application * @see BaseApplication - * @param config ReviewServiceConfiguration - * @param env Environment + * @param config the ReviewServiceConfiguration + * @param env the Environment */ @Override protected void registerResources(ReviewServiceConfiguration config, Environment env) throws Exception { @@ -70,6 +77,7 @@ protected void registerResources(ReviewServiceConfiguration config, Environment env.jersey().register(new ReviewItemFactory(config, env).getResourceInstance()); env.jersey().register(new ScorecardFactory(config, env).getResourceInstance()); env.jersey().register(new ScorecardQuestionFactory(config, env).getResourceInstance()); + env.jersey().register(new ReviewOpportunitiesFactory().getResourceInstance()); } /** diff --git a/service/src/main/java/com/appirio/service/review/api/ReviewOpportunitiesDTO.java b/service/src/main/java/com/appirio/service/review/api/ReviewOpportunitiesDTO.java new file mode 100644 index 0000000..50ad585 --- /dev/null +++ b/service/src/main/java/com/appirio/service/review/api/ReviewOpportunitiesDTO.java @@ -0,0 +1,150 @@ +package com.appirio.service.review.api; + +import java.util.Date; + +import lombok.Getter; +import lombok.Setter; + +/** + * Represents the ReviewOpportunitiesDTO entity. + * + * It's added in Topcoder - Create Endpoint to Fetch Review Opportunities v1.0 + * + * @author TCCoder + * @version 1.0 + */ +public class ReviewOpportunitiesDTO { + /** + * Represents the review auction id attribute. + */ + @Getter + @Setter + private long reviewAuctionId; + + /** + * Represents the review auction type id attribute. + */ + @Getter + @Setter + private int reviewAuctionTypeId; + + /** + * Represents the challenge id attribute. + */ + @Getter + @Setter + private long challengeId; + + /** + * Represents the number of submissions attribute. + */ + @Getter + @Setter + private int numberOfSubmissions; + + /** + * Represents the review start attribute. + */ + @Getter + @Setter + private Date reviewStart; + + /** + * Represents the review end attribute. + */ + @Getter + @Setter + private Date reviewEnd; + + /** + * Represents the number of review positions available attribute. + */ + @Getter + @Setter + private int numberOfReviewPositionsAvailable; + + /** + * Represents the challenge type attribute. + */ + @Getter + @Setter + private String challengeType; + + /** + * Represents the challenge type id attribute. + */ + @Getter + @Setter + private int challengeTypeId; + + /** + * Represents the review type attribute. + */ + @Getter + @Setter + private String reviewType; + + /** + * Represents the challenge name attribute. + */ + @Getter + @Setter + private String challengeName; + + /** + * Represents the challenge version attribute. + */ + @Getter + @Setter + private String ChallengeVersion; + + /** + * Represents the review application role id attribute. + */ + @Getter + @Setter + private long reviewApplicationRoleId; + + /** + * Represents the resource role id attribute. + */ + @Getter + @Setter + private long resourceRoleId; + + /** + * Represents the fixed amount attribute. + */ + @Getter + @Setter + private double fixedAmount; + + /** + * Represents the base coefficient attribute. + */ + @Getter + @Setter + private double baseCoefficient; + + /** + * Represents the incremental coefficient attribute. + */ + @Getter + @Setter + private double incrementalCoefficient; + + /** + * Represents the prize attribute. + */ + @Getter + @Setter + private double prize; + + /** + * Represents the submission count attribute. + */ + @Getter + @Setter + private int submissionCount; + +} \ No newline at end of file diff --git a/service/src/main/java/com/appirio/service/review/api/ReviewOpportunityItem.java b/service/src/main/java/com/appirio/service/review/api/ReviewOpportunityItem.java new file mode 100644 index 0000000..10fed89 --- /dev/null +++ b/service/src/main/java/com/appirio/service/review/api/ReviewOpportunityItem.java @@ -0,0 +1,88 @@ +package com.appirio.service.review.api; + +import java.util.Date; +import java.util.Map; + +import com.fasterxml.jackson.annotation.JsonFormat; +import com.fasterxml.jackson.annotation.JsonIgnore; + +import lombok.Getter; +import lombok.Setter; + +/** + * Represents the ReviewOpportunityItem entity. + * + * It's added in Topcoder - Create Endpoint to Fetch Review Opportunities version 1.0 + * + * @author TCCoder + * @version 1.0 + */ +public class ReviewOpportunityItem { + /** + * Represents the id attribute. + */ + @Getter + @Setter + private long id; + + /** + * Represents the start date attribute. + */ + @Getter + @Setter + @JsonFormat(pattern = "EEE MMM dd yyyy hh:mm:ss z", locale = "us") + private Date startDate; + + /** + * Represents the submissions attribute. + */ + @Getter + @Setter + private int submissions; + + /** + * Represents the open positions attribute. + */ + @Getter + @Setter + private int openPositions; + + /** + * Represents the type attribute. + */ + @Getter + @Setter + private String type; + + /** + * Represents the challenge id attribute. + */ + @Getter + @Setter + @JsonIgnore + private long challengeId; + + /** + * Represents the challenge name attribute. + */ + @Getter + @Setter + @JsonIgnore + private String challengeName; + + /** + * Represents the challenge attribute. + * The keys will be 'id', 'title' and 'version', and the corresponding values will be the id, name and version of the challenge. + */ + @Getter + @Setter + private Map challenge; + + /** + * Represents the payment attribute. + */ + @Getter + @Setter + private Double payment; + +} \ No newline at end of file diff --git a/service/src/main/java/com/appirio/service/review/dao/ReviewOpportunitiesDAO.java b/service/src/main/java/com/appirio/service/review/dao/ReviewOpportunitiesDAO.java new file mode 100644 index 0000000..b3e9b41 --- /dev/null +++ b/service/src/main/java/com/appirio/service/review/dao/ReviewOpportunitiesDAO.java @@ -0,0 +1,32 @@ +package com.appirio.service.review.dao; + +import java.util.List; + +import org.skife.jdbi.v2.sqlobject.Bind; + +import com.appirio.service.review.api.ReviewOpportunitiesDTO; +import com.appirio.supply.dataaccess.DatasourceName; +import com.appirio.supply.dataaccess.SqlQueryFile; + +/** + * ReviewOpportunitiesDAO is used to manage the review opportunities related data. + * + * It's added in Topcoder - Create Endpoint to Fetch Review Opportunities v1.0 + * + * @author TCCoder + * @version 1.0 + * + */ +@DatasourceName("oltp") +public interface ReviewOpportunitiesDAO { + + /** + * Get review opportunities + * + * @param challengeTypeId the challengeTypeId to use + * @return a list of review opportunity + */ + @SqlQueryFile("sql/review-opportunities/get-review-opportunities.sql") + List getReviewOpportunities( @Bind("challengeTypeId") long challengeTypeId); + +} diff --git a/service/src/main/java/com/appirio/service/review/manager/ReviewOpportunitiesManager.java b/service/src/main/java/com/appirio/service/review/manager/ReviewOpportunitiesManager.java new file mode 100644 index 0000000..0f5e6d6 --- /dev/null +++ b/service/src/main/java/com/appirio/service/review/manager/ReviewOpportunitiesManager.java @@ -0,0 +1,238 @@ +package com.appirio.service.review.manager; + +import java.math.BigDecimal; +import java.math.RoundingMode; +import java.util.ArrayList; +import java.util.HashMap; +import java.util.HashSet; +import java.util.List; +import java.util.Map; + +import javax.servlet.http.HttpServletResponse; + +import com.appirio.service.review.api.ReviewOpportunitiesDTO; +import com.appirio.service.review.api.ReviewOpportunityItem; +import com.appirio.service.review.dao.ReviewOpportunitiesDAO; +import com.appirio.supply.DAOFactory; +import com.appirio.supply.SupplyException; + +/** + * ReviewOpportunitiesManager is used to manage the review opportunities data. + * + * It's added in Topcoder - Create Endpoint to Fetch Review Opportunities v1.0 + * + * @author TCCoder + * @version 1.0 + * + */ +public class ReviewOpportunitiesManager { + /** + * The CHALLENGE_TYPE_IDS contains all the challenge type ids supported by the getReviewOpportunities method. + */ + private static final HashSet CHALLENGE_TYPE_IDS = new HashSet(); + static { + CHALLENGE_TYPE_IDS.add(1); + CHALLENGE_TYPE_IDS.add(2); + CHALLENGE_TYPE_IDS.add(5); + CHALLENGE_TYPE_IDS.add(6); + CHALLENGE_TYPE_IDS.add(7); + CHALLENGE_TYPE_IDS.add(9); + CHALLENGE_TYPE_IDS.add(13); + CHALLENGE_TYPE_IDS.add(14); + CHALLENGE_TYPE_IDS.add(23); + CHALLENGE_TYPE_IDS.add(19); + CHALLENGE_TYPE_IDS.add(24); + CHALLENGE_TYPE_IDS.add(25); + CHALLENGE_TYPE_IDS.add(26); + CHALLENGE_TYPE_IDS.add(29); + CHALLENGE_TYPE_IDS.add(35); + CHALLENGE_TYPE_IDS.add(36); + CHALLENGE_TYPE_IDS.add(38); + CHALLENGE_TYPE_IDS.add(39); + } + + /** + * The REVIEW_AUCTION_TYPE_ID_FOR_REGULAR_REVIEW + */ + private static final int REVIEW_AUCTION_TYPE_ID_FOR_REGULAR_REVIEW = 1; + + /** + * The REVIEW_AUCTION_TYPE_ID_FOR_COMPONENT_DEV_REVIEW + */ + private static final int REVIEW_AUCTION_TYPE_ID_FOR_COMPONENT_DEV_REVIEW = 2; + + /** + * The REVIEW_AUCTION_TYPE_ID_FOR_SPEC_REVIEW + */ + private static final int REVIEW_AUCTION_TYPE_ID_FOR_SPEC_REVIEW = 3; + + /** + * The REVIEW_AUCTION_TYPE_ID_FOR_ITERATIVE_REVIEW + */ + private static final int REVIEW_AUCTION_TYPE_ID_FOR_ITERATIVE_REVIEW = 4; + + /** + * The REVIEW_AUCTION_TYPE_ID_FOR_3_SCENARIOS_REVIEW + */ + private static final int REVIEW_AUCTION_TYPE_ID_FOR_3_SCENARIOS_REVIEW = 5; + + /** + * The reviewOpportunitiesDAO used to query the review opportunities data + */ + private ReviewOpportunitiesDAO reviewOpportunitiesDAO; + + /** + * Create ReviewOpportunitiesManager + * + * @throws SupplyException if any error occurs + */ + public ReviewOpportunitiesManager() throws SupplyException { + this.reviewOpportunitiesDAO = DAOFactory.getInstance().createDAO(ReviewOpportunitiesDAO.class); + } + + /** + * Get review opportunities by challenge type + * + * @param challengeType the challengeType to get review opportunities + * @throws SupplyException if error occurs + * @return a list of review opportunities + */ + public List getReviewOpportunities(int challengeTypeId) throws SupplyException { + if (challengeTypeId <= 0) { + throw new SupplyException("The challenge type id should be positive", HttpServletResponse.SC_BAD_REQUEST); + + } + + if (!CHALLENGE_TYPE_IDS.contains(challengeTypeId)) { + throw new SupplyException("The challenge type id is not supported:" + challengeTypeId, HttpServletResponse.SC_NOT_FOUND); + } + + List revOpportunities = this.reviewOpportunitiesDAO.getReviewOpportunities(challengeTypeId); + + // extract the challenge ids + List challengeIds = new ArrayList(); + for (ReviewOpportunitiesDTO dto : revOpportunities) { + if (!challengeIds.contains(dto.getChallengeId())) { + challengeIds.add(dto.getChallengeId()); + } + } + + List items = new ArrayList(); + for (long challengeId : challengeIds) { + List regularReviewOppos = new ArrayList(); + List iterativeReviewOppos = new ArrayList(); + List specReviewOppos = new ArrayList(); + for (ReviewOpportunitiesDTO dto : revOpportunities) { + if (challengeId == dto.getChallengeId()) { + if (dto.getReviewAuctionTypeId() == REVIEW_AUCTION_TYPE_ID_FOR_REGULAR_REVIEW + || dto.getReviewAuctionTypeId() == REVIEW_AUCTION_TYPE_ID_FOR_COMPONENT_DEV_REVIEW + || dto.getReviewAuctionTypeId() == REVIEW_AUCTION_TYPE_ID_FOR_3_SCENARIOS_REVIEW) {// find the contest reviews + regularReviewOppos.add(dto); + } else if (dto.getReviewAuctionTypeId() == REVIEW_AUCTION_TYPE_ID_FOR_SPEC_REVIEW) {// find the spec reviews + specReviewOppos.add(dto); + } else if (dto.getReviewAuctionTypeId() == REVIEW_AUCTION_TYPE_ID_FOR_ITERATIVE_REVIEW) {// find the iterative reviews + iterativeReviewOppos.add(dto); + } + } + } + + // add the regular review item + if (!regularReviewOppos.isEmpty()) { + ReviewOpportunityItem item = this.convertReviewOpportunitiesDTO(regularReviewOppos.get(0)); + double maxPayment = this.getMaxPayment(regularReviewOppos); + item.setPayment(maxPayment); + items.add(item); + } + + // add the spec review item + if (!specReviewOppos.isEmpty()) { + ReviewOpportunityItem item = this.convertReviewOpportunitiesDTO(specReviewOppos.get(0)); + double maxPayment = this.getMaxPayment(specReviewOppos); + item.setPayment(maxPayment); + items.add(item); + } + + // add the iterative review item + if (!iterativeReviewOppos.isEmpty()) { + ReviewOpportunityItem item = this.convertReviewOpportunitiesDTO(iterativeReviewOppos.get(0)); + double maxPayment = this.getMaxPayment(iterativeReviewOppos); + item.setPayment(maxPayment); + items.add(item); + } + } + + return items; + } + + /** + * Convert ReviewOpportunitiesDTO to ReviewOpportunityItem + * + * @param dto the ReviewOpportunitiesDTO to convert + * @return the ReviewOpportunityItem result + */ + private ReviewOpportunityItem convertReviewOpportunitiesDTO(ReviewOpportunitiesDTO dto) { + ReviewOpportunityItem item = new ReviewOpportunityItem(); + item.setId(dto.getReviewAuctionId()); + item.setOpenPositions(dto.getNumberOfReviewPositionsAvailable()); + item.setStartDate(dto.getReviewStart()); + item.setSubmissions(dto.getNumberOfSubmissions()); + item.setType(dto.getReviewType().trim()); + + Map challenge = new HashMap(); + challenge.put("id", dto.getChallengeId()); + challenge.put("title", dto.getChallengeName()); + challenge.put("version", dto.getChallengeVersion()); + item.setChallenge(challenge); + + return item; + } + + /** + * Get max payment from the review opportunities + * + * @param dtos the ReviewOpportunitiesDTO instances to use + * @return the max payment as double result + */ + private double getMaxPayment(List dtos) { + Map payments = new HashMap(); + double maxPayment = 0; + for (ReviewOpportunitiesDTO dto : dtos) { + Double value = payments.get(dto.getReviewApplicationRoleId()); + if (value == null) { + value = 0D; + } + BigDecimal fixedAmount = new BigDecimal(dto.getFixedAmount()).setScale(2, RoundingMode.HALF_UP); + + BigDecimal prize = new BigDecimal(dto.getPrize()); + double baseCoefficient = dto.getBaseCoefficient(); + double incrementalCoefficient = dto.getIncrementalCoefficient(); + int submissionsCount = dto.getSubmissionCount(); + int count = isSubmissionRequired(dto.getResourceRoleId()) ? submissionsCount != 0 ? submissionsCount : 1 : 0; + BigDecimal scaledPrize = prize.setScale(2, RoundingMode.HALF_UP); + BigDecimal multiplicandCoefficient = BigDecimal + .valueOf(baseCoefficient + incrementalCoefficient * (float) count); + BigDecimal payment = fixedAmount.add(scaledPrize.multiply(multiplicandCoefficient)).setScale(2, + RoundingMode.HALF_UP); + + value += payment.doubleValue(); + payments.put(dto.getReviewApplicationRoleId(), value); + + if (value > maxPayment) { + maxPayment = value; + } + } + + return maxPayment; + } + + /** + * Check submission is required for the resource role id. + * + * @param resourceRoleId the resourceRoleId to use + * @return the true if required, false otherwise + */ + private boolean isSubmissionRequired(long resourceRoleId) { + return resourceRoleId == 2L || resourceRoleId == 4L || resourceRoleId == 5L || resourceRoleId == 6L + || resourceRoleId == 7L || resourceRoleId == 19L || resourceRoleId == 20L || resourceRoleId == 21L; + } +} diff --git a/service/src/main/java/com/appirio/service/review/resources/ReviewOpportunitiesResource.java b/service/src/main/java/com/appirio/service/review/resources/ReviewOpportunitiesResource.java new file mode 100644 index 0000000..25c8d28 --- /dev/null +++ b/service/src/main/java/com/appirio/service/review/resources/ReviewOpportunitiesResource.java @@ -0,0 +1,72 @@ +package com.appirio.service.review.resources; + +import javax.ws.rs.Consumes; +import javax.ws.rs.GET; +import javax.ws.rs.Path; +import javax.ws.rs.Produces; +import javax.ws.rs.QueryParam; +import javax.ws.rs.core.MediaType; + +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +import com.appirio.service.review.manager.ReviewOpportunitiesManager; +import com.appirio.supply.ErrorHandler; +import com.appirio.tech.core.api.v3.request.annotation.AllowAnonymous; +import com.appirio.tech.core.api.v3.response.ApiResponse; +import com.appirio.tech.core.api.v3.response.ApiResponseFactory; +import com.codahale.metrics.annotation.Timed; + +/** + * ReviewOpportunitiesResource is used to provide the endpoints for review opportunities + * + * It's added in Topcoder - Create Endpoint to Fetch Review Opportunities v1.0 + * + * @author TCCoder + * @version 1.0 + * + */ +@Produces(MediaType.APPLICATION_JSON) +@Consumes(MediaType.APPLICATION_JSON) +@Path("reviewOpportunities") +public class ReviewOpportunitiesResource { + + /** + * Logger used to log the events + */ + private static final Logger logger = LoggerFactory.getLogger(ReviewOpportunitiesResource.class); + + /** + * The reviewOpportunitiesManager used to manage the review opportunities data + */ + private ReviewOpportunitiesManager reviewOpportunitiesManager; + + /** + * Create ReviewOpportunitiesResource + * + * @param reviewOpportunitiesManager the reviewOpportunitiesManager to use + */ + public ReviewOpportunitiesResource(ReviewOpportunitiesManager reviewOpportunitiesManager) { + this.reviewOpportunitiesManager = reviewOpportunitiesManager; + } + + /** + * Get review opportunities + * + * @param challengeTypeId the challengeTypeId to get the open review opportunities + * @return the ApiResponse result contains the open review opportunities + */ + @GET + @Timed + @AllowAnonymous + public ApiResponse getReviewOpportunities(@QueryParam("challengeTypeId") int challengeTypeId) { + try { + logger.debug("getReviewOpportunities, challengeTypeId : " + challengeTypeId); + return ApiResponseFactory + .createResponse(reviewOpportunitiesManager.getReviewOpportunities(challengeTypeId)); + } catch (Exception e) { + return ErrorHandler.handle(e, logger); + } + } + +} diff --git a/service/src/main/resources/new-relic-url-patterns b/service/src/main/resources/new-relic-url-patterns index 8e41cd2..f0837f5 100644 --- a/service/src/main/resources/new-relic-url-patterns +++ b/service/src/main/resources/new-relic-url-patterns @@ -13,4 +13,5 @@ POST reviews\/.*\/resetAggregation\/ POST reviews\/.*\/resetReview\/ POST reviews\/.*\/updateAutopilot\/ POST reviews\/.*\/reopen\/ -DELETE reviews\/.*\/ \ No newline at end of file +DELETE reviews\/.*\/ +GET reviewOpportunities\/ \ No newline at end of file diff --git a/service/src/main/resources/sql/review-opportunities/get-review-opportunities.sql b/service/src/main/resources/sql/review-opportunities/get-review-opportunities.sql new file mode 100644 index 0000000..4763ae4 --- /dev/null +++ b/service/src/main/resources/sql/review-opportunities/get-review-opportunities.sql @@ -0,0 +1,197 @@ +SELECT +review_auction_id AS reviewAuctionId, +review_auction_type_id AS reviewAuctionTypeId, +challenge_id AS challengeId, +number_of_submissions AS numberOfSubmissions, +review_start AS reviewStart, +review_end AS reviewEnd, +number_of_review_positions_available AS numberOfReviewPositionsAvailable, +challenge_type AS challengeType, +challenge_type_Id AS challengeTypeId, +review_type AS reviewType, +challenge_name AS challengeName, +version AS ChallengeVersion, +review_application_role_id AS reviewApplicationRoleId, +resource_role_id AS resourceRoleId, +fixed_amount AS fixedAmount, +base_coefficient AS baseCoefficient, +incremental_coefficient AS incrementalCoefficient, +prize AS prize, +submission_count AS submissionCount +FROM ( +SELECT + ra.review_auction_id +, ra.review_auction_type_id +, ra.project_id AS challenge_id +, (SELECT COUNT(DISTINCT u.resource_id) + FROM upload u + INNER JOIN submission s ON u.upload_id = s.upload_id + WHERE u.project_id = p.project_id + AND s.submission_type_id = 1 + AND s.submission_status_id IN (1,2,3,4) + AND u.upload_type_id = 1) AS number_of_submissions +, NVL(pp4.actual_start_time, pp4.scheduled_start_time) AS review_start +, pp4.scheduled_end_time AS review_end +, pc.parameter::int - (SELECT count(r.resource_id) FROM resource r WHERE r.project_id=p.project_id + AND r.resource_role_id IN (4,5,6,7)) AS number_of_review_positions_available +, pcl.name AS challenge_type +, pcl.project_category_id AS challenge_type_Id +, 'Contest Review' AS review_type +, pi6.value AS challenge_name +, pi7.value as version +, rarl.review_application_role_id +, dpp.resource_role_id +, dpp.fixed_amount +, dpp.base_coefficient +, dpp.incremental_coefficient +, (select max(prize_amount) from prize where project_id = p.project_id AND prize_type_id=15 AND place=1) as prize +, CASE WHEN dpp.resource_role_id = 2 THEN (SELECT COUNT(*) FROM submission s, upload up WHERE s.upload_id = up.upload_id + AND s.submission_status_id IN (1,2,3,4,6,7) AND s.submission_type_id = 1 AND up.project_id = p.project_id AND up.upload_type_id = 1) + WHEN dpp.resource_role_id IN (4,5,6,7) THEN (SELECT COUNT(*) FROM submission s, upload up WHERE s.upload_id = up.upload_id + AND s.submission_status_id IN (1,3,4,6,7) AND s.submission_type_id = 1 AND up.project_id = p.project_id AND up.upload_type_id = 1) + WHEN dpp.resource_role_id = 19 THEN (SELECT COUNT(*) FROM submission s, upload up WHERE s.upload_id = up.upload_id + AND s.submission_status_id IN (1,2,3,4,6,7) AND s.submission_type_id = 3 AND up.project_id = p.project_id AND up.upload_type_id = 1) + WHEN dpp.resource_role_id = 20 THEN (SELECT COUNT(*) FROM submission s, upload up WHERE s.upload_id = up.upload_id + AND s.submission_status_id IN (1,2,3,4,7) AND s.submission_type_id = 1 AND up.project_id = p.project_id AND up.upload_type_id = 1) + WHEN dpp.resource_role_id = 21 THEN (SELECT COUNT(*) FROM submission s, upload up WHERE s.upload_id = up.upload_id + AND s.submission_status_id IN (1,2,3,4,6,7) AND s.submission_type_id = 1 AND up.project_id = p.project_id AND up.upload_type_id = 1) + END AS submission_count +FROM review_auction ra +INNER JOIN review_auction_type_lu ratl ON ratl.review_auction_type_id=ra.review_auction_type_id AND ratl.review_auction_category_id=1 +INNER JOIN project p ON p.project_id=ra.project_id +INNER JOIN project_category_lu pcl ON pcl.project_category_id = p.project_category_id +INNER JOIN project_phase pp2 ON pp2.project_id=p.project_id AND pp2.phase_type_id=2 +INNER JOIN project_phase pp4 ON pp4.project_id=p.project_id AND pp4.phase_type_id=4 +INNER JOIN phase_criteria pc ON pc.project_phase_id=pp4.project_phase_id AND pc.phase_criteria_type_id=6 +INNER JOIN project_info pi6 ON p.project_id = pi6.project_id AND pi6.project_info_type_id = 6 +INNER JOIN project_info pi7 ON p.project_id = pi7.project_id AND pi7.project_info_type_id = 7 +INNER JOIN review_application_role_lu rarl ON rarl.review_auction_type_id = ra.review_auction_type_id +INNER JOIN review_application_role_resource_role_xref rarrrx ON rarrrx.review_application_role_id = rarl.review_application_role_id +INNER JOIN default_project_payment dpp ON dpp.resource_role_id = rarrrx.resource_role_id AND p.project_category_id = dpp.project_category_id +WHERE p.project_status_id=1 +AND pcl.project_type_id IN (1,2) +AND pcl.project_category_id != 29 +AND pcl.project_category_id != 37 +AND pp2.phase_status_id IN (2,3) +AND pp4.phase_status_id IN (1,2) +AND not exists (SELECT 1 FROM project_phase pp12 WHERE pp12.project_id=p.project_id AND pp12.phase_type_id=12) +AND dpp.resource_role_id IN (2,4,5,6,7,8,9) + +UNION ALL + +SELECT + ra.review_auction_id +, ratl.review_auction_type_id +, ra.project_id AS challenge_id +, 0 AS number_of_submissions +, pp14.scheduled_start_time AS review_start +, pp14.scheduled_end_time AS review_end +, 1 - (SELECT count(r.resource_id) FROM resource r WHERE r.project_id=p.project_id AND + r.resource_role_id = 18) AS number_of_review_positions_available +, pcl.name AS challenge_type +, pcl.project_category_id AS challenge_type_Id +, 'Spec Review' AS review_type +, pi6.value AS challenge_name +, pi7.value as version +, rarl.review_application_role_id +, dpp.resource_role_id +, dpp.fixed_amount +, dpp.base_coefficient +, dpp.incremental_coefficient +, (select max(prize_amount) from prize where project_id = p.project_id AND prize_type_id=15 AND place=1) as prize +, CASE WHEN dpp.resource_role_id = 2 THEN (SELECT COUNT(*) FROM submission s, upload up WHERE s.upload_id = up.upload_id + AND s.submission_status_id IN (1,2,3,4,6,7) AND s.submission_type_id = 1 AND up.project_id = p.project_id AND up.upload_type_id = 1) + WHEN dpp.resource_role_id IN (4,5,6,7) THEN (SELECT COUNT(*) FROM submission s, upload up WHERE s.upload_id = up.upload_id + AND s.submission_status_id IN (1,3,4,6,7) AND s.submission_type_id = 1 AND up.project_id = p.project_id AND up.upload_type_id = 1) + WHEN dpp.resource_role_id = 19 THEN (SELECT COUNT(*) FROM submission s, upload up WHERE s.upload_id = up.upload_id + AND s.submission_status_id IN (1,2,3,4,6,7) AND s.submission_type_id = 3 AND up.project_id = p.project_id AND up.upload_type_id = 1) + WHEN dpp.resource_role_id = 20 THEN (SELECT COUNT(*) FROM submission s, upload up WHERE s.upload_id = up.upload_id + AND s.submission_status_id IN (1,2,3,4,7) AND s.submission_type_id = 1 AND up.project_id = p.project_id AND up.upload_type_id = 1) + WHEN dpp.resource_role_id = 21 THEN (SELECT COUNT(*) FROM submission s, upload up WHERE s.upload_id = up.upload_id + AND s.submission_status_id IN (1,2,3,4,6,7) AND s.submission_type_id = 1 AND up.project_id = p.project_id AND up.upload_type_id = 1) + END AS submission_count +FROM review_auction ra +INNER JOIN review_auction_type_lu ratl ON ratl.review_auction_type_id=ra.review_auction_type_id AND ratl.review_auction_category_id=2 +INNER JOIN project p ON p.project_id=ra.project_id +INNER JOIN project_category_lu pcl ON pcl.project_category_id = p.project_category_id +INNER JOIN project_phase pp13 ON pp13.project_id=p.project_id AND pp13.phase_type_id=13 AND not exists (SELECT 1 FROM phase_dependency WHERE dependent_phase_id=pp13.project_phase_id) +INNER JOIN phase_dependency pd ON pd.dependency_phase_id=pp13.project_phase_id +INNER JOIN project_phase pp14 ON pp14.project_id=p.project_id AND pp14.phase_type_id=14 AND pp14.project_phase_id=pd.dependent_phase_id +INNER JOIN project_info pi6 ON p.project_id = pi6.project_id AND pi6.project_info_type_id = 6 +INNER JOIN project_info pi7 ON p.project_id = pi7.project_id AND pi7.project_info_type_id = 7 +INNER JOIN review_application_role_lu rarl ON rarl.review_auction_type_id = ra.review_auction_type_id +INNER JOIN review_application_role_resource_role_xref rarrrx ON rarrrx.review_application_role_id = rarl.review_application_role_id +INNER JOIN default_project_payment dpp ON dpp.resource_role_id = rarrrx.resource_role_id AND p.project_category_id = dpp.project_category_id +WHERE p.project_status_id = 1 +AND pcl.project_type_id IN (1,2) +AND pcl.project_category_id != 29 +AND pcl.project_category_id != 37 +AND pp13.phase_status_id IN (2,3) +AND pp14.phase_status_id IN (1,2) +AND not exists (SELECT 1 FROM project_phase pp12 WHERE pp12.project_id=p.project_id AND pp12.phase_type_id=12) +AND dpp.resource_role_id = 18 + +UNION ALL + +SELECT + ra.review_auction_id +, ra.review_auction_type_id +, ra.project_id AS challenge_id +, (SELECT COUNT(DISTINCT u.resource_id) + FROM upload u + INNER JOIN submission s ON u.upload_id = s.upload_id + WHERE u.project_id = p.project_id + AND s.submission_type_id = 1 + AND s.submission_status_id IN (1,2,3,4) + AND u.upload_type_id = 1) AS number_of_submissions +, pp18.scheduled_start_time AS review_start +, pp18.scheduled_end_time AS review_end +, pc.parameter::int - (SELECT count(r.resource_id) FROM resource r WHERE r.project_id=p.project_id + AND r.resource_role_id = 21) as number_of_review_positions_available +, pcl.name AS challenge_type +, pcl.project_category_id AS challenge_type_Id +, 'Iterative Review' AS review_type +, pi6.value AS challenge_name +, pi7.value as version +, rarl.review_application_role_id +, dpp.resource_role_id +, dpp.fixed_amount +, dpp.base_coefficient +, dpp.incremental_coefficient +, (select max(prize_amount) from prize where project_id = p.project_id AND prize_type_id=15 AND place=1) as prize +, CASE WHEN dpp.resource_role_id = 2 THEN (SELECT COUNT(*) FROM submission s, upload up WHERE s.upload_id = up.upload_id + AND s.submission_status_id IN (1,2,3,4,6,7) AND s.submission_type_id = 1 AND up.project_id = p.project_id AND up.upload_type_id = 1) + WHEN dpp.resource_role_id IN (4,5,6,7) THEN (SELECT COUNT(*) FROM submission s, upload up WHERE s.upload_id = up.upload_id + AND s.submission_status_id IN (1,3,4,6,7) AND s.submission_type_id = 1 AND up.project_id = p.project_id AND up.upload_type_id = 1) + WHEN dpp.resource_role_id = 19 THEN (SELECT COUNT(*) FROM submission s, upload up WHERE s.upload_id = up.upload_id + AND s.submission_status_id IN (1,2,3,4,6,7) AND s.submission_type_id = 3 AND up.project_id = p.project_id AND up.upload_type_id = 1) + WHEN dpp.resource_role_id = 20 THEN (SELECT COUNT(*) FROM submission s, upload up WHERE s.upload_id = up.upload_id + AND s.submission_status_id IN (1,2,3,4,7) AND s.submission_type_id = 1 AND up.project_id = p.project_id AND up.upload_type_id = 1) + WHEN dpp.resource_role_id = 21 THEN (SELECT COUNT(*) FROM submission s, upload up WHERE s.upload_id = up.upload_id + AND s.submission_status_id IN (1,2,3,4,6,7) AND s.submission_type_id = 1 AND up.project_id = p.project_id AND up.upload_type_id = 1) + END AS submission_count +FROM review_auction ra +INNER JOIN review_auction_type_lu ratl ON ratl.review_auction_type_id=ra.review_auction_type_id AND ratl.review_auction_category_id=3 +INNER JOIN project p ON p.project_id=ra.project_id +INNER JOIN project_category_lu pcl ON pcl.project_category_id = p.project_category_id +INNER JOIN project_phase pp2 ON pp2.project_id=p.project_id AND pp2.phase_type_id=2 +INNER JOIN project_phase pp18 ON pp18.project_id=p.project_id AND pp18.phase_type_id=18 +INNER JOIN phase_dependency pd ON pd.dependent_phase_id=pp18.project_phase_id AND pd.dependent_start=1 AND pd.dependency_phase_id=pp2.project_phase_id AND pd.dependency_start=1 +INNER JOIN phase_criteria pc ON pc.project_phase_id=pp18.project_phase_id AND pc.phase_criteria_type_id=6 +INNER JOIN project_info pi6 ON p.project_id = pi6.project_id AND pi6.project_info_type_id = 6 +INNER JOIN project_info pi7 ON p.project_id = pi7.project_id AND pi7.project_info_type_id = 7 +INNER JOIN review_application_role_lu rarl ON rarl.review_auction_type_id = ra.review_auction_type_id +INNER JOIN review_application_role_resource_role_xref rarrrx ON rarrrx.review_application_role_id = rarl.review_application_role_id +INNER JOIN default_project_payment dpp ON dpp.resource_role_id = rarrrx.resource_role_id AND p.project_category_id = dpp.project_category_id +WHERE p.project_status_id = 1 +AND pcl.project_type_id IN (1,2) +AND pcl.project_category_id != 29 +AND pcl.project_category_id != 37 +AND pp2.phase_status_id IN (2,3) +AND pp18.phase_status_id IN (1,2) +AND not exists (SELECT 1 FROM project_phase pp12 WHERE pp12.project_id=p.project_id AND pp12.phase_type_id=12) +AND dpp.resource_role_id = 21 +) +WHERE 1=1 +AND challenge_type_id = :challengeTypeId +AND number_of_review_positions_available > 0; \ No newline at end of file diff --git a/swagger.yaml b/swagger.yaml index 1f69cbe..57ac8d8 100644 --- a/swagger.yaml +++ b/swagger.yaml @@ -636,42 +636,30 @@ paths: description: | Fetches review opportunities parameters: - - name: challengeType + - name: challengeTypeId required: true - type: string + type: integer in: query - description: The type of the challenge, like "Code", "First2Finish" etc. + description: The challenge type ids for "Code", "First2Finish" etc. enum: - - Component Design - - Component Development - - Component Testing - - Application Specification - - Application Architecture - - Bug Hunt - - Test Scenarios - - Test Suites - - Application Assembly - - Application Conceptualization - - UI Prototype - - RIA Build - - RIA Component - - Copilot Posting - - Content Creation - - Reporting - - First2Finish - - Code - - name: offset - required: false - type: string - in: query - description: | - Number of items to skip. (DEFAULT 0) - - name: limit - required: false - type: string - in: query - description: >- - Max records to return. (DEFAULT 10) + - 1 Component Design + - 2 Component Development + - 5 Component Testing + - 6 Application Specification + - 7 Application Architecture + - 9 Bug Hunt + - 26 Test Scenarios + - 13 Test Suites + - 14 Application Assembly + - 23 Application Conceptualization + - 19 UI Prototype + - 24 RIA Build + - 25 RIA Component + - 29 Copilot Posting + - 35 Content Creation + - 36 Reporting + - 38 First2Finish + - 39 Code responses: '200': description: Success @@ -680,8 +668,12 @@ paths: description: A list of Review Opportunities items: $ref: '#/definitions/ReviewOpportunityItem' + '400': + description: challenge type id is not positive. + schema: + $ref: '#/definitions/ErrorModel' '404': - description: No such challenge type + description: The challenge type id not supported, or challenge type id exceeds the max integer value. schema: $ref: '#/definitions/ErrorModel' '500': @@ -884,7 +876,7 @@ definitions: description: Number of the open positions of reviewers for this review opportunity. type: type: string - description: The type of this review opportunity, usually be "REVIEW" + description: The type of this review opportunity, will be one of "Contest Review", "Spec Review" and "Iterative Review" challenge: type: object description: The challenge information of this review opportunity @@ -896,6 +888,9 @@ definitions: title: type: string description: The name of the challenge + version: + type: string + description: The version of the challenge ScorecardQuestion: type: object properties: From faaf9072a3afcfb28de24e187b0fd38740a0a706 Mon Sep 17 00:00:00 2001 From: ajefts Date: Thu, 30 Nov 2017 14:46:15 -0500 Subject: [PATCH 12/19] updated docker maintainer --- service/build/Dockerfile.template | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/service/build/Dockerfile.template b/service/build/Dockerfile.template index 323f307..02c9481 100644 --- a/service/build/Dockerfile.template +++ b/service/build/Dockerfile.template @@ -1,6 +1,6 @@ FROM appiriodevops/ap-microservice-base:0.0.1 -MAINTAINER mdesiderio@appirio.com +MAINTAINER devops+jenkins@topcoder.com WORKDIR /data From c7aca500bd9360a52071bf384c82d5a6be99b6b1 Mon Sep 17 00:00:00 2001 From: Gunasekar-K Date: Fri, 1 Dec 2017 13:26:41 +0530 Subject: [PATCH 13/19] Update deploy.sh --- service/build/deploy.sh | 1 + 1 file changed, 1 insertion(+) diff --git a/service/build/deploy.sh b/service/build/deploy.sh index 1dc8864..0515f23 100755 --- a/service/build/deploy.sh +++ b/service/build/deploy.sh @@ -44,6 +44,7 @@ echo "Building docker image $DOCKER_REPO/$IMAGE" sudo docker build -t $DOCKER_REPO/$IMAGE $DEPLOY_DIR echo "Pushing image to docker $DOCKER_REPO/$IMAGE" +sudo docker login -u tcintegration sudo docker push $DOCKER_REPO/$IMAGE echo "Generating dockerrun file" From 24c139f2b495ea44a6ba835d5a1a70e42f1ac5d1 Mon Sep 17 00:00:00 2001 From: Gunasekar-K Date: Fri, 1 Dec 2017 13:34:17 +0530 Subject: [PATCH 14/19] Update deploy.sh --- service/build/deploy.sh | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/service/build/deploy.sh b/service/build/deploy.sh index 0515f23..e6bb42c 100755 --- a/service/build/deploy.sh +++ b/service/build/deploy.sh @@ -44,7 +44,7 @@ echo "Building docker image $DOCKER_REPO/$IMAGE" sudo docker build -t $DOCKER_REPO/$IMAGE $DEPLOY_DIR echo "Pushing image to docker $DOCKER_REPO/$IMAGE" -sudo docker login -u tcintegration +docker login -u tcintegration sudo docker push $DOCKER_REPO/$IMAGE echo "Generating dockerrun file" From d60f9c3c1a28a2aa816abf96fdb9cd4599d5f14c Mon Sep 17 00:00:00 2001 From: Gunasekar-K Date: Fri, 1 Dec 2017 13:41:21 +0530 Subject: [PATCH 15/19] Update deploy.sh --- service/build/deploy.sh | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/service/build/deploy.sh b/service/build/deploy.sh index e6bb42c..4ffcc87 100755 --- a/service/build/deploy.sh +++ b/service/build/deploy.sh @@ -45,7 +45,7 @@ sudo docker build -t $DOCKER_REPO/$IMAGE $DEPLOY_DIR echo "Pushing image to docker $DOCKER_REPO/$IMAGE" docker login -u tcintegration -sudo docker push $DOCKER_REPO/$IMAGE +docker push $DOCKER_REPO/$IMAGE echo "Generating dockerrun file" cat $DOCKERRUN_TEMPLATE | sed -e "s/@IMAGE@/${IMAGE}/g" > $DOCKERRUN From ba4ef4b455563aa69708e182997bd8b55bf4606e Mon Sep 17 00:00:00 2001 From: Gunasekar-K Date: Fri, 1 Dec 2017 13:58:38 +0530 Subject: [PATCH 16/19] Update deploy.sh --- service/build/deploy.sh | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/service/build/deploy.sh b/service/build/deploy.sh index 4ffcc87..34f875e 100755 --- a/service/build/deploy.sh +++ b/service/build/deploy.sh @@ -41,7 +41,7 @@ cp $WORKSPACE/service/target/review-microservice*.jar review-microservice.jar cp $WORKSPACE/service/src/main/resources/review-service.yaml review-service.yaml echo "Building docker image $DOCKER_REPO/$IMAGE" -sudo docker build -t $DOCKER_REPO/$IMAGE $DEPLOY_DIR +docker build -t $DOCKER_REPO/$IMAGE $DEPLOY_DIR echo "Pushing image to docker $DOCKER_REPO/$IMAGE" docker login -u tcintegration From 102b36ad1acf7d65256af1a9ead6d2f60ac20572 Mon Sep 17 00:00:00 2001 From: Gunasekar-K Date: Fri, 1 Dec 2017 14:54:39 +0530 Subject: [PATCH 17/19] Update deploy.sh --- service/build/deploy.sh | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/service/build/deploy.sh b/service/build/deploy.sh index 34f875e..ea25601 100755 --- a/service/build/deploy.sh +++ b/service/build/deploy.sh @@ -44,7 +44,7 @@ echo "Building docker image $DOCKER_REPO/$IMAGE" docker build -t $DOCKER_REPO/$IMAGE $DEPLOY_DIR echo "Pushing image to docker $DOCKER_REPO/$IMAGE" -docker login -u tcintegration +docker login -u ykohata docker push $DOCKER_REPO/$IMAGE echo "Generating dockerrun file" From 6500e6215ce9424acf6b74da76eca5af01c674e3 Mon Sep 17 00:00:00 2001 From: Gunasekar-K Date: Fri, 1 Dec 2017 15:05:03 +0530 Subject: [PATCH 18/19] Update deploy.sh --- service/build/deploy.sh | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/service/build/deploy.sh b/service/build/deploy.sh index ea25601..4132c22 100755 --- a/service/build/deploy.sh +++ b/service/build/deploy.sh @@ -44,7 +44,7 @@ echo "Building docker image $DOCKER_REPO/$IMAGE" docker build -t $DOCKER_REPO/$IMAGE $DEPLOY_DIR echo "Pushing image to docker $DOCKER_REPO/$IMAGE" -docker login -u ykohata +#docker login -u ykohata docker push $DOCKER_REPO/$IMAGE echo "Generating dockerrun file" From ff77a97a54cf14ff63aba9a8f32839134350437c Mon Sep 17 00:00:00 2001 From: Thomas Kranitsas Date: Sat, 2 Dec 2017 16:44:01 +0200 Subject: [PATCH 19/19] Make challengeTypeId optional and show payments based on reviewer role --- ...eview-microservice.postman_collection.json | 65 +++++++- .../appirio/service/review/api/Payment.java | 41 +++++ .../review/api/ReviewOpportunityItem.java | 9 +- .../review/dao/ReviewOpportunitiesDAO.java | 16 +- .../manager/ReviewOpportunitiesManager.java | 140 ++++++++++++------ .../get-review-opportunities.properties | 2 + .../get-review-opportunities.sql | 4 +- swagger.yaml | 25 +++- 8 files changed, 236 insertions(+), 66 deletions(-) create mode 100644 service/src/main/java/com/appirio/service/review/api/Payment.java create mode 100644 service/src/main/resources/sql/review-opportunities/get-review-opportunities.properties diff --git a/postman/review-microservice.postman_collection.json b/postman/review-microservice.postman_collection.json index 9af14b0..2bba9a1 100755 --- a/postman/review-microservice.postman_collection.json +++ b/postman/review-microservice.postman_collection.json @@ -2,7 +2,8 @@ "variables": [], "info": { "name": "review-microservice", - "_postman_id": "0e16939f-32a7-1e78-496e-e63027184f19", + "_postman_id": "550e2ca7-d86d-77a7-a597-98a1945ebc8f", + "description": "", "schema": "https://schema.getpostman.com/json/collection/v2.0.0/collection.json" }, "item": [ @@ -564,6 +565,68 @@ }, "response": [] }, + { + "name": "Get review opportunities for UI prototype", + "request": { + "method": "GET", + "header": [ + { + "key": "Authorization", + "value": "Bearer eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9.eyJyb2xlcyI6WyJhZG1pbmlzdHJhdG9yIl0sImlzcyI6Imh0dHBzOi8vYXBpLnRvcGNvZGVyLWRldi5jb20iLCJoYW5kbGUiOiJoZWZmYW4iLCJleHAiOjE3NjYyODkyNDYsInVzZXJJZCI6IjEzMjQ1NiIsImlhdCI6MTQ1MDkyOTI0NiwiZW1haWwiOm51bGwsImp0aSI6IjEzNjljNjAwLWUwYTEtNDUyNS1hN2M3LTU2YmU3ZDgxM2Y1MSJ9.hp5peSoj-fh3KFkskvBpfUFIcJNtsv4zIMFV-D8F3JA", + "disabled": true + }, + { + "key": "Content-Type", + "value": "application/json" + } + ], + "body": { + "mode": "raw", + "raw": "" + }, + "url": { + "raw": "{{apiUrl}}/v3/reviewOpportunities?challengeTypeId=19", + "host": [ + "{{apiUrl}}" + ], + "path": [ + "v3", + "reviewOpportunities" + ], + "query": [ + { + "key": "challengeTypeId", + "value": "19", + "equals": true + } + ] + } + }, + "response": [] + }, + { + "name": "Get review opportunities", + "request": { + "method": "GET", + "header": [ + { + "key": "Authorization", + "value": "Bearer eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9.eyJyb2xlcyI6WyJhZG1pbmlzdHJhdG9yIl0sImlzcyI6Imh0dHBzOi8vYXBpLnRvcGNvZGVyLWRldi5jb20iLCJoYW5kbGUiOiJoZWZmYW4iLCJleHAiOjE3NjYyODkyNDYsInVzZXJJZCI6IjEzMjQ1NiIsImlhdCI6MTQ1MDkyOTI0NiwiZW1haWwiOm51bGwsImp0aSI6IjEzNjljNjAwLWUwYTEtNDUyNS1hN2M3LTU2YmU3ZDgxM2Y1MSJ9.hp5peSoj-fh3KFkskvBpfUFIcJNtsv4zIMFV-D8F3JA", + "disabled": true + }, + { + "key": "Content-Type", + "value": "application/json" + } + ], + "body": { + "mode": "raw", + "raw": "" + }, + "url": "{{apiUrl}}/v3/reviewOpportunities" + }, + "response": [] + }, { "name": "Get review opportunities for assembly (contest review and spec)", "request": { diff --git a/service/src/main/java/com/appirio/service/review/api/Payment.java b/service/src/main/java/com/appirio/service/review/api/Payment.java new file mode 100644 index 0000000..a1a6d3c --- /dev/null +++ b/service/src/main/java/com/appirio/service/review/api/Payment.java @@ -0,0 +1,41 @@ +package com.appirio.service.review.api; + +import lombok.AllArgsConstructor; +import lombok.Getter; +import lombok.NoArgsConstructor; +import lombok.Setter; + +/** + * Represents the Payment entity. + * + * It's added in Topcoder - Update Review Opportunities Endpoint + * + * @author TCCoder + * @version 1.0 + */ +@AllArgsConstructor +@NoArgsConstructor +public class Payment { + + /** + * Represents the reviewer role + */ + @Setter + @Getter + private String role; + + /** + * Represents the reviewer role id + */ + @Setter + @Getter + private long roleId; + + + /** + * Represents the reviewer payment + */ + @Setter + @Getter + private Double payment; +} diff --git a/service/src/main/java/com/appirio/service/review/api/ReviewOpportunityItem.java b/service/src/main/java/com/appirio/service/review/api/ReviewOpportunityItem.java index 10fed89..2ce39cc 100644 --- a/service/src/main/java/com/appirio/service/review/api/ReviewOpportunityItem.java +++ b/service/src/main/java/com/appirio/service/review/api/ReviewOpportunityItem.java @@ -1,6 +1,7 @@ package com.appirio.service.review.api; import java.util.Date; +import java.util.List; import java.util.Map; import com.fasterxml.jackson.annotation.JsonFormat; @@ -13,7 +14,11 @@ * Represents the ReviewOpportunityItem entity. * * It's added in Topcoder - Create Endpoint to Fetch Review Opportunities version 1.0 - * + * + * Updates in Topcoder - Update Review Opportunities Endpoint + * - remove payment + * - add payments as a list of Payment + * * @author TCCoder * @version 1.0 */ @@ -83,6 +88,6 @@ public class ReviewOpportunityItem { */ @Getter @Setter - private Double payment; + private List payments; } \ No newline at end of file diff --git a/service/src/main/java/com/appirio/service/review/dao/ReviewOpportunitiesDAO.java b/service/src/main/java/com/appirio/service/review/dao/ReviewOpportunitiesDAO.java index b3e9b41..0309c9e 100644 --- a/service/src/main/java/com/appirio/service/review/dao/ReviewOpportunitiesDAO.java +++ b/service/src/main/java/com/appirio/service/review/dao/ReviewOpportunitiesDAO.java @@ -2,6 +2,8 @@ import java.util.List; +import com.appirio.supply.dataaccess.ApiQueryInput; +import com.appirio.tech.core.api.v3.request.QueryParameter; import org.skife.jdbi.v2.sqlobject.Bind; import com.appirio.service.review.api.ReviewOpportunitiesDTO; @@ -12,21 +14,23 @@ * ReviewOpportunitiesDAO is used to manage the review opportunities related data. * * It's added in Topcoder - Create Endpoint to Fetch Review Opportunities v1.0 - * + * + * Updates in Topcoder - Update Review Opportunities Endpoint + * - pass QueryParameter to getReviewOpportunities instead of challengeTypeId + * * @author TCCoder * @version 1.0 * */ @DatasourceName("oltp") public interface ReviewOpportunitiesDAO { - + /** - * Get review opportunities + * Get review opportunities * - * @param challengeTypeId the challengeTypeId to use + * @param queryParameter query parameter * @return a list of review opportunity */ @SqlQueryFile("sql/review-opportunities/get-review-opportunities.sql") - List getReviewOpportunities( @Bind("challengeTypeId") long challengeTypeId); - + List getReviewOpportunities(@ApiQueryInput QueryParameter queryParameter); } diff --git a/service/src/main/java/com/appirio/service/review/manager/ReviewOpportunitiesManager.java b/service/src/main/java/com/appirio/service/review/manager/ReviewOpportunitiesManager.java index 0f5e6d6..8b44a92 100644 --- a/service/src/main/java/com/appirio/service/review/manager/ReviewOpportunitiesManager.java +++ b/service/src/main/java/com/appirio/service/review/manager/ReviewOpportunitiesManager.java @@ -10,16 +10,24 @@ import javax.servlet.http.HttpServletResponse; +import com.appirio.service.review.api.Payment; import com.appirio.service.review.api.ReviewOpportunitiesDTO; import com.appirio.service.review.api.ReviewOpportunityItem; import com.appirio.service.review.dao.ReviewOpportunitiesDAO; import com.appirio.supply.DAOFactory; import com.appirio.supply.SupplyException; +import com.appirio.tech.core.api.v3.request.FieldSelector; +import com.appirio.tech.core.api.v3.request.FilterParameter; +import com.appirio.tech.core.api.v3.request.QueryParameter; /** * ReviewOpportunitiesManager is used to manage the review opportunities data. * * It's added in Topcoder - Create Endpoint to Fetch Review Opportunities v1.0 + * + * Updates in Topcoder - Update Review Opportunities Endpoint: + * - challengeTypeId is optional, if it does not exist, returns all review opportunities + * - get list of Payment instead of max payment value * * @author TCCoder * @version 1.0 @@ -27,7 +35,7 @@ */ public class ReviewOpportunitiesManager { /** - * The CHALLENGE_TYPE_IDS contains all the challenge type ids supported by the getReviewOpportunities method. + * The CHALLENGE_TYPE_IDS contains all the supported challenge type ids */ private static final HashSet CHALLENGE_TYPE_IDS = new HashSet(); static { @@ -50,7 +58,23 @@ public class ReviewOpportunitiesManager { CHALLENGE_TYPE_IDS.add(38); CHALLENGE_TYPE_IDS.add(39); } - + + /** + * The REVIEW_APPLICATION_ID_NAME_MAPPING contains mapping of review application role id and review application name. + */ + private static final HashMap REVIEW_APPLICATION_ID_NAME_MAPPING = new HashMap<>(); + static { + REVIEW_APPLICATION_ID_NAME_MAPPING.put(1L, "Primary Reviewer"); + REVIEW_APPLICATION_ID_NAME_MAPPING.put(2L, "Secondary Reviewer"); + REVIEW_APPLICATION_ID_NAME_MAPPING.put(3L, "Primary Failure Reviewer"); + REVIEW_APPLICATION_ID_NAME_MAPPING.put(4L, "Accuracy Reviewer"); + REVIEW_APPLICATION_ID_NAME_MAPPING.put(5L, "Stress Reviewer"); + REVIEW_APPLICATION_ID_NAME_MAPPING.put(6L, "Failure Reviewer"); + REVIEW_APPLICATION_ID_NAME_MAPPING.put(7L, "Specification Reviewer"); + REVIEW_APPLICATION_ID_NAME_MAPPING.put(8L, "Iterative Reviewer"); + REVIEW_APPLICATION_ID_NAME_MAPPING.put(9L, "Reviewer"); + } + /** * The REVIEW_AUCTION_TYPE_ID_FOR_REGULAR_REVIEW */ @@ -75,7 +99,7 @@ public class ReviewOpportunitiesManager { * The REVIEW_AUCTION_TYPE_ID_FOR_3_SCENARIOS_REVIEW */ private static final int REVIEW_AUCTION_TYPE_ID_FOR_3_SCENARIOS_REVIEW = 5; - + /** * The reviewOpportunitiesDAO used to query the review opportunities data */ @@ -91,24 +115,38 @@ public ReviewOpportunitiesManager() throws SupplyException { } /** - * Get review opportunities by challenge type + * Get review opportunities by challenge type id + * if challenge type id is 0, all review opportunities will be returned * - * @param challengeType the challengeType to get review opportunities + * @param challengeTypeId the challengeType id to get review opportunities * @throws SupplyException if error occurs * @return a list of review opportunities */ public List getReviewOpportunities(int challengeTypeId) throws SupplyException { - if (challengeTypeId <= 0) { + + if (challengeTypeId < 0) { throw new SupplyException("The challenge type id should be positive", HttpServletResponse.SC_BAD_REQUEST); - + } - - if (!CHALLENGE_TYPE_IDS.contains(challengeTypeId)) { - throw new SupplyException("The challenge type id is not supported:" + challengeTypeId, HttpServletResponse.SC_NOT_FOUND); + + QueryParameter queryParameter = new QueryParameter(new FieldSelector()); + + if (challengeTypeId == 0) { + // challengeTypeId is not provided in query string + queryParameter.setFilter(new FilterParameter("")); + + } else { + // if challengeTypeId is in query string + // double check if its value is valid + if (!CHALLENGE_TYPE_IDS.contains(challengeTypeId)) { + throw new SupplyException("The challenge type id is not supported:" + challengeTypeId, HttpServletResponse.SC_NOT_FOUND); + } + + queryParameter.setFilter(new FilterParameter("challenge_type_id=" + String.valueOf(challengeTypeId))); } - - List revOpportunities = this.reviewOpportunitiesDAO.getReviewOpportunities(challengeTypeId); - + + List revOpportunities = this.reviewOpportunitiesDAO.getReviewOpportunities(queryParameter); + // extract the challenge ids List challengeIds = new ArrayList(); for (ReviewOpportunitiesDTO dto : revOpportunities) { @@ -116,7 +154,7 @@ public List getReviewOpportunities(int challengeTypeId) t challengeIds.add(dto.getChallengeId()); } } - + List items = new ArrayList(); for (long challengeId : challengeIds) { List regularReviewOppos = new ArrayList(); @@ -124,7 +162,7 @@ public List getReviewOpportunities(int challengeTypeId) t List specReviewOppos = new ArrayList(); for (ReviewOpportunitiesDTO dto : revOpportunities) { if (challengeId == dto.getChallengeId()) { - if (dto.getReviewAuctionTypeId() == REVIEW_AUCTION_TYPE_ID_FOR_REGULAR_REVIEW + if (dto.getReviewAuctionTypeId() == REVIEW_AUCTION_TYPE_ID_FOR_REGULAR_REVIEW || dto.getReviewAuctionTypeId() == REVIEW_AUCTION_TYPE_ID_FOR_COMPONENT_DEV_REVIEW || dto.getReviewAuctionTypeId() == REVIEW_AUCTION_TYPE_ID_FOR_3_SCENARIOS_REVIEW) {// find the contest reviews regularReviewOppos.add(dto); @@ -134,35 +172,36 @@ public List getReviewOpportunities(int challengeTypeId) t iterativeReviewOppos.add(dto); } } - } + } // add the regular review item - if (!regularReviewOppos.isEmpty()) { - ReviewOpportunityItem item = this.convertReviewOpportunitiesDTO(regularReviewOppos.get(0)); - double maxPayment = this.getMaxPayment(regularReviewOppos); - item.setPayment(maxPayment); - items.add(item); - } - + this.addReviewItem(items, regularReviewOppos); + // add the spec review item - if (!specReviewOppos.isEmpty()) { - ReviewOpportunityItem item = this.convertReviewOpportunitiesDTO(specReviewOppos.get(0)); - double maxPayment = this.getMaxPayment(specReviewOppos); - item.setPayment(maxPayment); - items.add(item); - } - + this.addReviewItem(items, specReviewOppos); + // add the iterative review item - if (!iterativeReviewOppos.isEmpty()) { - ReviewOpportunityItem item = this.convertReviewOpportunitiesDTO(iterativeReviewOppos.get(0)); - double maxPayment = this.getMaxPayment(iterativeReviewOppos); - item.setPayment(maxPayment); - items.add(item); - } + this.addReviewItem(items, iterativeReviewOppos); } - + return items; } + + /** + * Creates ReviewOpportunityItem and insert it into items + * + * @param items list of ReviewOpportunityItem to be inserted into + * @param reviewOppos list of ReviewOpportunitiesDTO + */ + private void addReviewItem(List items, List reviewOppos) { + // add the iterative review item + if (!reviewOppos.isEmpty()) { + ReviewOpportunityItem item = this.convertReviewOpportunitiesDTO(reviewOppos.get(0)); + List payments = this.getPayments(reviewOppos); + item.setPayments(payments); + items.add(item); + } + } /** * Convert ReviewOpportunitiesDTO to ReviewOpportunityItem @@ -188,16 +227,16 @@ private ReviewOpportunityItem convertReviewOpportunitiesDTO(ReviewOpportunitiesD } /** - * Get max payment from the review opportunities + * Get list of Payment from the review opportunities * * @param dtos the ReviewOpportunitiesDTO instances to use - * @return the max payment as double result + * @return a list of Payment */ - private double getMaxPayment(List dtos) { - Map payments = new HashMap(); - double maxPayment = 0; + private List getPayments(List dtos) { + + Map paymentsMap = new HashMap(); for (ReviewOpportunitiesDTO dto : dtos) { - Double value = payments.get(dto.getReviewApplicationRoleId()); + Double value = paymentsMap.get(dto.getReviewApplicationRoleId()); if (value == null) { value = 0D; } @@ -215,14 +254,17 @@ private double getMaxPayment(List dtos) { RoundingMode.HALF_UP); value += payment.doubleValue(); - payments.put(dto.getReviewApplicationRoleId(), value); - - if (value > maxPayment) { - maxPayment = value; - } + paymentsMap.put(dto.getReviewApplicationRoleId(), value); + } + + List payments = new ArrayList<>(); + + for (Long roleId : paymentsMap.keySet()) { + Payment payment = new Payment(REVIEW_APPLICATION_ID_NAME_MAPPING.get(roleId), roleId, paymentsMap.get(roleId)); + payments.add(payment); } - return maxPayment; + return payments; } /** diff --git a/service/src/main/resources/sql/review-opportunities/get-review-opportunities.properties b/service/src/main/resources/sql/review-opportunities/get-review-opportunities.properties new file mode 100644 index 0000000..705af51 --- /dev/null +++ b/service/src/main/resources/sql/review-opportunities/get-review-opportunities.properties @@ -0,0 +1,2 @@ +filter.challenge_type_id.template=challenge_type_id = :challenge_type_id +filter.challenge_type_id.type=Long \ No newline at end of file diff --git a/service/src/main/resources/sql/review-opportunities/get-review-opportunities.sql b/service/src/main/resources/sql/review-opportunities/get-review-opportunities.sql index 4763ae4..b85c202 100644 --- a/service/src/main/resources/sql/review-opportunities/get-review-opportunities.sql +++ b/service/src/main/resources/sql/review-opportunities/get-review-opportunities.sql @@ -192,6 +192,4 @@ AND pp18.phase_status_id IN (1,2) AND not exists (SELECT 1 FROM project_phase pp12 WHERE pp12.project_id=p.project_id AND pp12.phase_type_id=12) AND dpp.resource_role_id = 21 ) -WHERE 1=1 -AND challenge_type_id = :challengeTypeId -AND number_of_review_positions_available > 0; \ No newline at end of file +WHERE {filter} and number_of_review_positions_available > 0; \ No newline at end of file diff --git a/swagger.yaml b/swagger.yaml index 57ac8d8..8352126 100644 --- a/swagger.yaml +++ b/swagger.yaml @@ -637,7 +637,7 @@ paths: Fetches review opportunities parameters: - name: challengeTypeId - required: true + required: false type: integer in: query description: The challenge type ids for "Code", "First2Finish" etc. @@ -867,10 +867,11 @@ definitions: submissions: type: integer description: Number of submissions - payment: - type: number - format: double - description: The payment of this review + payments: + type: array + description: A list of ReviewPayment + items: + $ref: '#/definitions/ReviewPayment' openPositions: type: integer description: Number of the open positions of reviewers for this review opportunity. @@ -1272,3 +1273,17 @@ definitions: format: int64 description: READ-ONLY. User that last updated this task readOnly: true + ReviewPayment: + type: object + properties: + role: + type: string + description: The name of review role + roleId: + type: integer + format: int64 + description: The id of review role + payment: + type: number + format: double + description: The payment of review role