diff --git a/.gitignore b/.gitignore index 9b7f130217..17eca5e0b1 100644 --- a/.gitignore +++ b/.gitignore @@ -28,6 +28,9 @@ /.nb-gradle/ +### VS ### +.vs + ### VS Code ### .vscode/ @@ -80,7 +83,7 @@ replay_pid* logs/ target/ -.classpth +.classpath .mvn/timing.properties .mvn/wrapper/maven-wrapper.jar .project diff --git a/Dockerfile b/Dockerfile index 38f77dceee..b2f6718c77 100644 --- a/Dockerfile +++ b/Dockerfile @@ -1,37 +1,37 @@ -# Settings. +# Build arguments. ARG USER_ID=3001 ARG USER_NAME=vireo -ARG SOURCE_DIR=/$USER_NAME/source +ARG HOME_DIR=/$USER_NAME +ARG SOURCE_DIR=$HOME_DIR/source +ARG APP_PATH=/var/vireo ARG NODE_ENV=production # Maven stage. -FROM maven:3-openjdk-11-slim as maven +FROM maven:3-eclipse-temurin-11-alpine as maven ARG USER_ID ARG USER_NAME +ARG HOME_DIR ARG SOURCE_DIR +ARG APP_PATH ARG NODE_ENV ENV NODE_ENV=$NODE_ENV -# Create the user and group (use a high ID to attempt to avoid conflicts). -RUN groupadd --non-unique -g $USER_ID $USER_NAME && \ - useradd --non-unique -d /$USER_NAME -m -u $USER_ID -g $USER_ID $USER_NAME +# Create the group (use a high ID to attempt to avoid conflits). +RUN addgroup -g $USER_ID $USER_NAME -# Install stable Nodejs and npm. -RUN apt-get update && \ - apt-get upgrade -y && \ - apt-get install -y nodejs npm iproute2 && \ - apt-get clean && \ - rm -rf /var/lib/apt/lists/* && \ - npm cache clean -f && \ - npm install -g n && \ - n stable +# Create the user (use a high ID to attempt to avoid conflits). +RUN adduser -h $HOME_DIR -u $USER_ID -G $USER_NAME -D $USER_NAME # Ensure source directory exists and has appropriate file permissions. RUN mkdir -p $SOURCE_DIR && \ chown $USER_ID:$USER_ID $SOURCE_DIR && \ chmod g+s $SOURCE_DIR +# Upgrade the system and install dependencies. +RUN apk -U upgrade && \ + apk add --update --no-cache nodejs npm make g++ py3-pip + # Set deployment directory. WORKDIR $SOURCE_DIR @@ -43,7 +43,7 @@ COPY ./src ./src COPY ./build ./build COPY ./package.json ./package.json -# Assign file permissions. +# Change ownership of source directory. RUN chown -R $USER_ID:$USER_ID $SOURCE_DIR # Login as user. @@ -52,48 +52,47 @@ USER $USER_NAME # Build. RUN mvn package -Pproduction -# Switch to Normal JRE Stage. -FROM openjdk:11-jre-slim +# JRE Stage. +FROM eclipse-temurin:11-alpine ARG USER_ID ARG USER_NAME +ARG HOME_DIR ARG SOURCE_DIR +ARG APP_PATH + +ENV APP_PATH=$APP_PATH + +# Create the group (use a high ID to attempt to avoid conflits). +RUN addgroup -g $USER_ID $USER_NAME -RUN apt-get update && \ - apt-get upgrade -y && \ - apt-get -y install gettext-base && \ - apt-get clean && \ - rm -rf /var/lib/apt/lists/* +# Create the user (use a high ID to attempt to avoid conflits). +RUN adduser -h $HOME_DIR -u $USER_ID -G $USER_NAME -D $USER_NAME + +# Ensure app path directory exists and has appropriate file permissions. +RUN mkdir -p $APP_PATH && \ + chown $USER_ID:$USER_ID $APP_PATH && \ + chmod g+s $APP_PATH + +# Update the system and install gettext for envsubst. +RUN apk -U upgrade && \ + apk add --update --no-cache gettext # Copy files from outside docker to inside. COPY build/appConfig.js.template /usr/local/app/templates/appConfig.js.template COPY build/docker-entrypoint.sh /usr/local/bin/docker-entrypoint.sh - -# Enable execute of docker entrypoint for root user. -RUN chmod ugo+r /usr/local/app/templates/appConfig.js.template && \ - chmod ugo+rx /usr/local/bin/docker-entrypoint.sh - -# Create the user and group (use a high ID to attempt to avoid conflicts). -RUN groupadd --non-unique -g $USER_ID $USER_NAME && \ - useradd --non-unique -d /$USER_NAME -m -u $USER_ID -g $USER_ID $USER_NAME +RUN chmod ugo+x /usr/local/bin/docker-entrypoint.sh # Login as user. USER $USER_NAME # Set deployment directory. -WORKDIR /$USER_NAME +WORKDIR $HOME_DIR + # Copy over the built artifact and library from the maven image. COPY --from=maven $SOURCE_DIR/target/vireo-*.war ./vireo.war COPY --from=maven $SOURCE_DIR/target/libs ./libs -ENV AUTH_STRATEGY=weaverAuth - -ENV STOMP_DEBUG=false - -ENV AUTH_SERVICE_URL=http://localhost:9001/auth - -ENV APP_CONFIG_PATH=file:/$USER_NAME/appConfig.js - EXPOSE 9000 # Entrypoint performs environment substitution on appConfig.js. diff --git a/README.md b/README.md index 90497e80d7..1bde1045d6 100644 --- a/README.md +++ b/README.md @@ -66,7 +66,7 @@ $ mvn clean spring-boot:run -Dproduction $ mvn clean package -DskipTests -Dproduction -Dassets.uri=file:/opt/vireo/ -Dconfig.uri=file:/opt/vireo/config/ ``` -If build succeeds, you should have both a `vireo-4.1.5.war` and a `vireo-4.1.5-install.zip` in the `target/` directory. When building for production required static assets are copied into the packaged war file and the index.html template is optimized for production. For development a symlink is used to allow the application to access required static assets. +If build succeeds, you should have both a `vireo-4.2.0.war` and a `vireo-4.2.0-install.zip` in the `target/` directory. When building for production required static assets are copied into the packaged war file and the index.html template is optimized for production. For development a symlink is used to allow the application to access required static assets. #### Apache Reverse Proxy Config @@ -117,7 +117,7 @@ Unzip package into preferred directory (or any directory you choose): ```bash $ cd /opt/vireo -$ unzip vireo-4.1.5-install.zip +$ unzip vireo-4.2.0-install.zip ``` ### Directory Structure of installed package @@ -190,13 +190,13 @@ ln -s /opt/vireo/webapp /opt/tomcat/webapps/ROOT Copy war file into Tomcat webapps directory (your location may vary -- this is an example): ```bash -$ cp ~/vireo-4.1.5.war /usr/local/tomcat/webapps/vireo.war +$ cp ~/vireo-4.2.0.war /usr/local/tomcat/webapps/vireo.war ``` or as root: ```bash -$ cp ~/vireo-4.1.5.war /usr/local/tomcat/webapps/ROOT.war +$ cp ~/vireo-4.2.0.war /usr/local/tomcat/webapps/ROOT.war ``` **if not specifying assets.uri during build the assets will be stored under the vireo webapp's classpath, /opt/tomcat/webapps/vireo/WEB-INF/classes** @@ -209,7 +209,7 @@ $ cp ~/vireo-4.1.5.war /usr/local/tomcat/webapps/ROOT.war ## Running WAR as a stand-alone Spring Boot application ```bash -java -jar target/vireo-4.1.5.war +java -jar target/vireo-4.2.0.war ```
(back to top)
diff --git a/build/appConfig.js.template b/build/appConfig.js.template index 4ac6aaf4d1..85f2f54c5d 100644 --- a/build/appConfig.js.template +++ b/build/appConfig.js.template @@ -1,6 +1,6 @@ var appConfig = { - 'version': '4.1.5', + 'version': '4.2.0', 'allowAnonymous': true, 'anonymousRole': 'ROLE_ANONYMOUS', diff --git a/docker-compose.yml b/docker-compose.yml index 4186412126..944f28d76f 100644 --- a/docker-compose.yml +++ b/docker-compose.yml @@ -1,37 +1,50 @@ -version: '3.7' +version: '3.8' + +networks: + net: -networks: - weaver: - -services: - - vireo_db: - container_name: vireo_db - hostname: vireo_db - image: postgres - environment: - POSTGRES_DB: vireo - POSTGRES_USER: vireo - POSTGRES_PASSWORD: vireo - volumes: - - ${LOCAL_VIREO_PGDATA}:/var/lib/postgresql/data - networks: - weaver: - - vireo: +volumes: + pgdata: + assets: + +services: + + db: + image: postgres + environment: + - POSTGRES_DB=vireo + - POSTGRES_USER=vireo + - POSTGRES_PASSWORD=vireo + volumes: + - pgdata:/var/lib/postgresql/data + ports: + - 5432:5432 + networks: + - net + + adminer: + image: adminer + ports: + - 8080:8080 + depends_on: + - db + networks: + - net + + vireo: container_name: vireo - hostname: vireo - build: - dockerfile: Dockerfile - context: './' - image: ${IMAGE_HOST}/${SERVICE_PROJECT}${SERVICE_PATH}:${IMAGE_VERSION} - ports: - - 9000:9000 - env_file: - - .env - volumes: - - ${LOCAL_VIREO_PATH}:${APP_PATH} - depends_on: - - vireo_db - networks: - weaver: + hostname: vireo + image: ${IMAGE_HOST}/${SERVICE_PROJECT}/${SERVICE_PATH}:${IMAGE_VERSION} + build: + dockerfile: Dockerfile + context: './' + env_file: + - .env + volumes: + - assets:${APP_PATH} + ports: + - 9000:9000 + depends_on: + - db + networks: + - net diff --git a/example.env b/example.env index 961b1f0ad8..65d5deb8a9 100644 --- a/example.env +++ b/example.env @@ -4,32 +4,35 @@ ############################## IMAGE_HOST=127.0.0.1 +IMAGE_VERSION=4.2.0 +SERVICE_PROJECT=tdl +SERVICE_PATH=vireo -IMAGE_VERSION=0.0.1 +NODE_ENV=production -SERVICE_PROJECT=vireo +STOMP_DEBUG=false -# Must have leading slash. -SERVICE_PATH=/vireo +AUTH_STRATEGY=weaverAuth -LOCAL_VIREO_PATH=/var/vireo/ -LOCAL_VIREO_PGDATA=/var/vireo/pgdata/ +AUTH_SERVICE_URL="window.location.protocol + '//' + window.location.host + window.location.base + '/mock/auth'" -NODE_ENV=production -STOMP_DEBUG=false +#SPRING_JPA_DATABASE_PLATFORM=org.hibernate.dialect.H2Dialect +SPRING_JPA_DATABASE_PLATFORM=org.hibernate.dialect.PostgreSQLDialect -AUTH_SERVICE_URL="window.location.protocol + '//' + window.location.host + window.location.base + '/mock/auth'" +SPRING_JPA_HIBERNATE_DDL_AUTO=update + +#SPRING_DATASOURCE_URL=jdbc:h2:mem:AZ;DB_CLOSE_DELAY=-1;DB_CLOSE_ON_EXIT=FALSE +#SPRING_DATASOURCE_DRIVERCLASSNAME=org.h2.Driver +SPRING_DATASOURCE_URL=jdbc:postgresql://db:5432/vireo +SPRING_DATASOURCE_DRIVERCLASSNAME=org.postgresql.Driver -SPRING_DATASOURCE_URL=jdbc:h2:mem:AZ;DB_CLOSE_DELAY=-1;DB_CLOSE_ON_EXIT=FALSE -SPRING_DATASOURCE_DRIVERCLASSNAME=org.h2.Driver SPRING_DATASOURCE_USERNAME=vireo SPRING_DATASOURCE_PASSWORD=vireo -SPRING_JPA_DATABASE_PLATFORM=org.hibernate.dialect.H2Dialect -SPRING_JPA_HIBERNATE_DDL_AUTO=update + # location to place templated appConfig.js -APP_PATH=/var/vireo/ +APP_PATH=/var/vireo # must match directory of APP_PATH APP_CONFIG_URI=file:/var/vireo/appConfig.js APP_ASSETS_URI=file:/var/vireo/ diff --git a/package.json b/package.json index 447b647e19..e20b548fb1 100644 --- a/package.json +++ b/package.json @@ -1,7 +1,7 @@ { "name": "vireo", "private": false, - "version": "4.1.5", + "version": "4.2.0", "description": "Vireo 4", "homepage": "https://github.com/TexasDigitalLibrary/Vireo", "repository": { @@ -22,7 +22,7 @@ "build": "wvr build --clean" }, "dependencies": { - "@wvr/core": "2.2.4", + "@wvr/core": "2.2.5-rc.3", "angular-ui-tinymce": "0.0.19", "file-saver": "2.0.5", "ng-csv": "0.3.6", diff --git a/pom.xml b/pom.xml index eb434539fb..d578a2afbe 100644 --- a/pom.xml +++ b/pom.xml @@ -6,7 +6,7 @@ org.tdl vireo - 4.1.5 + 4.2.0 Vireo Vireo Thesis and Dissertation Submission System diff --git a/src/main/java/org/tdl/vireo/config/VireoDatabaseConfig.java b/src/main/java/org/tdl/vireo/config/VireoDatabaseConfig.java new file mode 100644 index 0000000000..7cc81d9876 --- /dev/null +++ b/src/main/java/org/tdl/vireo/config/VireoDatabaseConfig.java @@ -0,0 +1,20 @@ +package org.tdl.vireo.config; + +import org.springframework.beans.factory.annotation.Value; +import org.springframework.context.annotation.Configuration; + +@Configuration +public class VireoDatabaseConfig { + + @Value("${spring.sql.init.platform:''}") + private String platform; + + public String getPlatform() { + return platform; + } + + public void setPlatform(String platform) { + this.platform = platform; + } + +} diff --git a/src/main/java/org/tdl/vireo/config/constant/ConfigurationName.java b/src/main/java/org/tdl/vireo/config/constant/ConfigurationName.java index a7cd0535f1..c8381620f5 100644 --- a/src/main/java/org/tdl/vireo/config/constant/ConfigurationName.java +++ b/src/main/java/org/tdl/vireo/config/constant/ConfigurationName.java @@ -49,12 +49,21 @@ public class ConfigurationName { // Theme settings /** Background main color */ public final static String THEME_PATH = "theme_path"; + + /** Text main color */ + public final static String TEXT_MAIN_COLOR = "text_main_color"; /** Background main color */ public final static String BACKGROUND_MAIN_COLOR = "background_main_color"; /** Background highlight color */ public final static String BACKGROUND_HIGHLIGHT_COLOR = "background_highlight_color"; + + /** Background text color */ + public final static String BACKGROUND_HEADER_TEXT_COLOR = "background_header_text_color"; + + /** Background text color */ + public final static String BACKGROUNT_FOOTER_TEXT_COLOR = "background_footer_text_color"; /** Submission Step Button main color when in "on" state */ public final static String BUTTON_MAIN_COLOR_ON = "button_main_color_on"; @@ -62,12 +71,30 @@ public class ConfigurationName { /** Submission Step Button highlight color when in "on" state */ public final static String BUTTON_HIGHLIGHT_COLOR_ON = "button_highlight_color_on"; + /** Submission Step Button text color when in "on" state */ + public final static String BUTTON_TEXT_COLOR_ON = "button_text_color_on"; + /** Submission Step Button main color when in "off" state */ public final static String BUTTON_MAIN_COLOR_OFF = "button_main_color_off"; /** Submission Step Button highlight color when in "off" state */ public final static String BUTTON_HIGHLIGHT_COLOR_OFF = "button_highlight_color_off"; + /** Submission Step Button text color when in "off" state */ + public final static String BUTTON_TEXT_COLOR_OFF = "button_text_color_off"; + + /** Admin main navigation tab background color */ + public final static String ADMIN_TAB_MAIN_COLOR = "admin_tab_main_color"; + + /** Admin selected navigation tab background color */ + public final static String ADMIN_TAB_SELECTED_COLOR = "admin_tab_selected_color"; + + /** Admin navigation tab text color*/ + public final static String ADMIN_TAB_MAIN_TEXT_COLOR = "admin_tab_main_text_color"; + + /** Admin navigation tab text color*/ + public final static String ADMIN_TAB_SELECTED_TEXT_COLOR = "admin_tab_selected_text_color"; + /** Custom CSS */ public final static String LEFT_LOGO = "left_logo"; diff --git a/src/main/java/org/tdl/vireo/controller/SubmissionController.java b/src/main/java/org/tdl/vireo/controller/SubmissionController.java index eade201171..3006f822bb 100644 --- a/src/main/java/org/tdl/vireo/controller/SubmissionController.java +++ b/src/main/java/org/tdl/vireo/controller/SubmissionController.java @@ -213,7 +213,11 @@ public ApiResponse getActionLogs(@WeaverUser User user, @PathVariable Long submi @JsonView(Views.SubmissionIndividual.class) @RequestMapping("/advisor-review/{advisorAccessHash}") public ApiResponse getOne(@PathVariable String advisorAccessHash) { - return new ApiResponse(SUCCESS, submissionRepo.findOneByAdvisorAccessHash(advisorAccessHash)); + Submission submission = submissionRepo.findOneByAdvisorAccessHash(advisorAccessHash); + + return submission.getSubmissionStatus().isEditableByReviewer() + ? new ApiResponse(SUCCESS, submission) + : new ApiResponse(ERROR, "Submission is not editable by reviewer"); } @GetMapping("/advisor-review/{advisorAccessHash}/action-logs") diff --git a/src/main/java/org/tdl/vireo/controller/SubmissionListController.java b/src/main/java/org/tdl/vireo/controller/SubmissionListController.java index 83f9440e35..2079440521 100644 --- a/src/main/java/org/tdl/vireo/controller/SubmissionListController.java +++ b/src/main/java/org/tdl/vireo/controller/SubmissionListController.java @@ -4,10 +4,11 @@ import static edu.tamu.weaver.response.ApiStatus.SUCCESS; import static org.springframework.web.bind.annotation.RequestMethod.POST; -import java.util.ArrayList; +import edu.tamu.weaver.auth.annotation.WeaverUser; +import edu.tamu.weaver.response.ApiResponse; +import edu.tamu.weaver.validation.aspect.annotation.WeaverValidatedModel; import java.util.List; import java.util.Map; - import org.slf4j.Logger; import org.slf4j.LoggerFactory; import org.springframework.beans.factory.annotation.Autowired; @@ -33,10 +34,6 @@ import org.tdl.vireo.model.repo.UserRepo; import org.tdl.vireo.service.DefaultSubmissionListColumnService; -import edu.tamu.weaver.auth.annotation.WeaverUser; -import edu.tamu.weaver.response.ApiResponse; -import edu.tamu.weaver.validation.aspect.annotation.WeaverValidatedModel; - @RestController @RequestMapping("/submission-list") public class SubmissionListController { @@ -182,9 +179,14 @@ public ApiResponse getSavedFilters(@WeaverUser User user) { @PreAuthorize("hasRole('REVIEWER')") @RequestMapping(value = "/remove-saved-filter", method = POST) public ApiResponse removeSavedFilter(@WeaverUser User user, @WeaverValidatedModel NamedSearchFilterGroup savedFilter) { - user.getSavedFilters().remove(savedFilter); - user = userRepo.save(user); - return new ApiResponse(SUCCESS, user.getActiveFilter()); + if (user.getSavedFilters().contains(savedFilter)) { + user.getSavedFilters().remove(savedFilter); + user = userRepo.save(user); + + return new ApiResponse(SUCCESS, user.getActiveFilter()); + } + + return new ApiResponse(ERROR, "Cannot not find filter."); } @PreAuthorize("hasRole('REVIEWER')") @@ -281,14 +283,10 @@ public ApiResponse clearFilterCriteria(@WeaverUser User user) { @RequestMapping("/all-saved-filter-criteria") @PreAuthorize("hasRole('REVIEWER')") public ApiResponse getAllSaveFilterCriteria(@WeaverUser User user) { - List userSavedFilters = user.getSavedFilters(); - List publicSavedFilters = namedSearchFilterGroupRepo.findByPublicFlagTrue(); - - List allSavedFilters = new ArrayList(); - allSavedFilters.addAll(userSavedFilters); - allSavedFilters.addAll(publicSavedFilters); + List all = namedSearchFilterGroupRepo.findByUserIsNotAndPublicFlagTrue(user); + all.addAll(user.getSavedFilters()); - return new ApiResponse(SUCCESS, userSavedFilters); + return new ApiResponse(SUCCESS, all); } @RequestMapping(value = "/save-filter-criteria", method = POST) diff --git a/src/main/java/org/tdl/vireo/model/NamedSearchFilterGroup.java b/src/main/java/org/tdl/vireo/model/NamedSearchFilterGroup.java index e273c41f7c..3a4fb8a959 100644 --- a/src/main/java/org/tdl/vireo/model/NamedSearchFilterGroup.java +++ b/src/main/java/org/tdl/vireo/model/NamedSearchFilterGroup.java @@ -3,6 +3,7 @@ import static javax.persistence.CascadeType.MERGE; import static javax.persistence.CascadeType.REFRESH; import static javax.persistence.FetchType.EAGER; +import static javax.persistence.FetchType.LAZY; import java.util.ArrayList; import java.util.HashSet; @@ -63,7 +64,7 @@ public class NamedSearchFilterGroup extends ValidatingBaseEntity { private Sort sortDirection; @OrderColumn - @ManyToMany(cascade = { REFRESH, MERGE }, fetch = EAGER) + @ManyToMany(cascade = { REFRESH, MERGE }, fetch = LAZY) private List savedColumns; @Fetch(FetchMode.SELECT) diff --git a/src/main/java/org/tdl/vireo/model/User.java b/src/main/java/org/tdl/vireo/model/User.java index e84aadb92c..ff1ded4b8f 100644 --- a/src/main/java/org/tdl/vireo/model/User.java +++ b/src/main/java/org/tdl/vireo/model/User.java @@ -5,6 +5,7 @@ import static javax.persistence.CascadeType.REFRESH; import static javax.persistence.CascadeType.REMOVE; import static javax.persistence.FetchType.EAGER; +import static javax.persistence.FetchType.LAZY; import java.util.ArrayList; import java.util.Collection; @@ -121,7 +122,7 @@ public class User extends AbstractWeaverUserDetails { @ManyToMany(cascade = { REFRESH }, fetch = EAGER) private List filterColumns; - @ManyToOne(cascade = { REFRESH, MERGE }, fetch = EAGER, optional = true) + @ManyToOne(cascade = { REFRESH, MERGE }, fetch = LAZY, optional = true) private NamedSearchFilterGroup activeFilter; @Fetch(FetchMode.SELECT) diff --git a/src/main/java/org/tdl/vireo/model/repo/NamedSearchFilterGroupRepo.java b/src/main/java/org/tdl/vireo/model/repo/NamedSearchFilterGroupRepo.java index 544ec59792..0708fea185 100644 --- a/src/main/java/org/tdl/vireo/model/repo/NamedSearchFilterGroupRepo.java +++ b/src/main/java/org/tdl/vireo/model/repo/NamedSearchFilterGroupRepo.java @@ -3,13 +3,14 @@ import java.util.List; import org.tdl.vireo.model.NamedSearchFilterGroup; +import org.tdl.vireo.model.User; import org.tdl.vireo.model.repo.custom.NamedSearchFilterGroupRepoCustom; import edu.tamu.weaver.data.model.repo.WeaverRepo; public interface NamedSearchFilterGroupRepo extends WeaverRepo, NamedSearchFilterGroupRepoCustom { - public List findByPublicFlagTrue(); + public List findByUserIsNotAndPublicFlagTrue(User user); public NamedSearchFilterGroup findByNameAndPublicFlagTrue(String name); diff --git a/src/main/java/org/tdl/vireo/model/repo/impl/SubmissionRepoImpl.java b/src/main/java/org/tdl/vireo/model/repo/impl/SubmissionRepoImpl.java index 2642013089..510f088abc 100644 --- a/src/main/java/org/tdl/vireo/model/repo/impl/SubmissionRepoImpl.java +++ b/src/main/java/org/tdl/vireo/model/repo/impl/SubmissionRepoImpl.java @@ -36,6 +36,7 @@ import org.springframework.jdbc.core.JdbcTemplate; import org.springframework.jdbc.core.RowMapper; import org.springframework.transaction.annotation.Transactional; +import org.tdl.vireo.config.VireoDatabaseConfig; import org.tdl.vireo.exception.OrganizationDoesNotAcceptSubmissionsException; import org.tdl.vireo.model.Configuration; import org.tdl.vireo.model.CustomActionDefinition; @@ -100,6 +101,9 @@ public class SubmissionRepoImpl extends AbstractWeaverRepoImpl batchDynamicSubmissionQuery(NamedSearchFilterGroup activ @Override public Page pageableDynamicSubmissionQuery(NamedSearchFilterGroup activeFilter, List submissionListColums, Pageable pageable) throws ExecutionException { + long startTime = System.nanoTime(); + QueryStrings queryBuilder = craftDynamicSubmissionQuery(activeFilter, submissionListColums, pageable); Long total = jdbcTemplate.queryForObject(queryBuilder.getCountQuery(), Long.class); - List ids = jdbcTemplate.query(queryBuilder.getQuery(), new RowMapper() { + logger.debug("Count query for dynamic query took " + ((System.nanoTime() - startTime) / 1000000000.0) + " seconds"); + startTime = System.nanoTime(); + + List ids = jdbcTemplate.query(queryBuilder.getQuery(), new RowMapper<>() { public Long mapRow(ResultSet rs, int rowNum) throws SQLException { return rs.getLong("ID"); } }); - List submissions = new ArrayList(); + logger.debug("ID query for dynamic query took " + ((System.nanoTime() - startTime) / 1000000000.0) + " seconds"); + startTime = System.nanoTime(); + + List submissions = new ArrayList<>(); List unordered = submissionRepo.findAllById(ids); + logger.debug("Find all query for dynamic query took " + ((System.nanoTime() - startTime) / 1000000000.0) + " seconds"); + // order them for (Long id : ids) { for (Submission sub : unordered) { @@ -370,7 +384,7 @@ public Long mapRow(ResultSet rs, int rowNum) throws SQLException { private QueryStrings craftDynamicSubmissionQuery(NamedSearchFilterGroup activeFilter, List submissionListColums, Pageable pageable) { // set up storage for user's preferred columns - Set allColumnSearchFilters = new HashSet(); + Set allColumnSearchFilters = new HashSet<>(); // get all the possible columns, some of which we will make visible List allSubmissionListColumns = submissionListColumnRepo.findAll(); @@ -406,7 +420,7 @@ private QueryStrings craftDynamicSubmissionQuery(NamedSearchFilterGroup activeFi } // sort all submission list columns by sort order provided by users submission list columns - Collections.sort(allSubmissionListColumns, new Comparator() { + Collections.sort(allSubmissionListColumns, new Comparator<>() { @Override public int compare(SubmissionListColumn svc1, SubmissionListColumn svc2) { return svc1.getSortOrder().compareTo(svc2.getSortOrder()); @@ -415,17 +429,20 @@ public int compare(SubmissionListColumn svc1, SubmissionListColumn svc2) { StringBuilder sqlSelectBuilder = new StringBuilder("SELECT DISTINCT s.id,"); - StringBuilder sqlCountSelectBuilder = new StringBuilder("SELECT COUNT(DISTINCT s.id) FROM submission s "); + StringBuilder sqlCountSelectBuilder = new StringBuilder(); - Map> sqlColumnsBuilders = new HashMap>(); + Map> sqlColumnsBuilders = new HashMap<>(); + Map> sqlCountWhereFilterBuilders = new HashMap<>(); + Map sqlCountWherePredicate = new HashMap<>(); StringBuilder sqlJoinsBuilder = new StringBuilder(); - StringBuilder sqlWhereBuilder; + StringBuilder sqlBuilder; + StringBuilder sqlCountBuilder; StringBuilder sqlWheresExcludeBuilder = new StringBuilder(); StringBuilder sqlOrderBysBuilder = new StringBuilder(); ArrayList sqlWhereBuilderList; - ArrayList sqlAllColumnsWhereBuilderList = new ArrayList(); + ArrayList sqlAllColumnsWhereBuilderList = new ArrayList<>(); int n = 0; @@ -444,120 +461,165 @@ public int compare(SubmissionListColumn svc1, SubmissionListColumn svc2) { Long predicateId = fieldPredicateRepo.findByValue(submissionListColumn.getPredicate()).getId(); // @formatter:off - sqlJoinsBuilder.append("\nLEFT JOIN") - .append("\n (SELECT sfv").append(n).append(".submission_id, fv").append(n).append(".*") - .append("\n FROM submission_field_values sfv").append(n) - .append("\n LEFT JOIN field_value fv").append(n).append(" ON fv").append(n).append(".id=sfv").append(n).append(".field_values_id ") - .append("\n WHERE fv").append(n).append(".field_predicate_id=").append(predicateId).append(") pfv").append(n) - .append("\n ON pfv").append(n).append(".submission_id=s.id"); - // @formatter:on + if (submissionListColumn.getSortOrder() > 0 || submissionListColumn.getFilters().size() > 0) { + sqlJoinsBuilder + .append("\nLEFT JOIN") + .append("\n (SELECT sfv").append(n).append(".submission_id, fv").append(n).append(".*") + .append("\n FROM submission_field_values sfv").append(n) + .append("\n LEFT JOIN field_value fv").append(n) + .append(" ON fv").append(n).append(".id=sfv").append(n).append(".field_values_id ") + .append("\n WHERE fv").append(n).append(".field_predicate_id=").append(predicateId).append(") pfv").append(n) + .append("\n ON pfv").append(n).append(".submission_id=s.id"); + } if (submissionListColumn.getSortOrder() > 0) { - setColumnOrdering(submissionListColumn.getSort(), sqlSelectBuilder, sqlOrderBysBuilder, " pfv" + n + ".value"); + if (submissionListColumn.getInputType().getName().equals("INPUT_DEGREEDATE")) { + setColumnOrderingForMonthYearDateFormat(submissionListColumn.getSort(), sqlSelectBuilder, sqlOrderBysBuilder, " pfv" + n); + } else { + setColumnOrdering(submissionListColumn.getSort(), sqlSelectBuilder, sqlOrderBysBuilder, " pfv" + n + ".value"); + } } for (String filterString : submissionListColumn.getFilters()) { - sqlWhereBuilder = new StringBuilder(); + sqlBuilder = new StringBuilder(); + sqlCountBuilder = new StringBuilder(); switch (submissionListColumn.getInputType().getName()) { case "INPUT_DEGREEDATE": // Column's values are of type 'MMMM yyyy' (in SQL date format would be 'Month YYYY'). - sqlWhereBuilder.append("LOWER(pfv").append(n).append(".value) = LOWER('").append(filterString).append("')"); + sqlBuilder.append("LOWER(pfv").append(n).append(".value) = LOWER('").append(filterString).append("')"); + sqlCountBuilder.append("LOWER(fv.value) = LOWER('").append(filterString).append("')"); break; case "INPUT_DATE": // Column's values are of type 'yyyy-mm-dd' as required by the SQL standard to represent a date without time. if (filterString.contains("|")) { // Date Range String[] dates = filterString.split(Pattern.quote("|")); - sqlWhereBuilder + sqlBuilder .append("CAST(pfv").append(n) .append(".value AS DATE) BETWEEN CAST('").append(dates[0]) .append("' AS DATE) AND CAST('").append(dates[1]) .append("' AS DATE)"); + sqlCountBuilder + .append("CAST(fv.value AS DATE) BETWEEN CAST('").append(dates[0]) + .append("' AS DATE) AND CAST('").append(dates[1]) + .append("' AS DATE)"); } else { // Date Match - sqlWhereBuilder.append("pfv").append(n).append(".value = '").append(filterString).append("'"); + sqlBuilder.append("pfv").append(n).append(".value = '").append(filterString).append("'"); + sqlCountBuilder.append("fv.value = '").append(filterString).append("'"); } break; case "INPUT_CHECKBOX": - sqlWhereBuilder.append("pfv").append(n).append(".value = '").append(filterString).append("'"); + sqlBuilder.append("pfv").append(n).append(".value = '").append(filterString).append("'"); + sqlCountBuilder.append("fv.value = '").append(filterString).append("'"); // Column's values are a boolean if (!Boolean.valueOf(filterString)) { - sqlWhereBuilderList.add(sqlWhereBuilder); - sqlWhereBuilder = new StringBuilder(); + sqlWhereBuilderList.add(sqlBuilder); - sqlWhereBuilder.append(" pfv").append(n).append(".value IS NULL"); + if (!sqlCountWherePredicate.containsKey(predicateId)) { + sqlCountWherePredicate.put(predicateId, new StringBuilder()); + } + + sqlCountWherePredicate.get(predicateId).append(" (").append(sqlCountBuilder).append(") OR"); + + sqlBuilder = new StringBuilder(); + sqlBuilder.append(" pfv").append(n).append(".value IS NULL"); + + sqlCountBuilder = new StringBuilder(); + sqlCountBuilder.append(" fv.value IS NULL"); } + break; default: // Column's values can be handled by this default if (submissionListColumn.getExactMatch()) { // perform exact match - sqlWhereBuilder.append("pfv").append(n).append(".value = '").append(filterString).append("'"); + sqlBuilder.append("pfv").append(n).append(".value = '").append(filterString).append("'"); + sqlCountBuilder.append("fv.value = '").append(filterString).append("'"); } else { // perform like when input from text field - sqlWhereBuilder.append("LOWER(pfv").append(n).append(".value) LIKE '%").append(filterString.toLowerCase()).append("%'"); + sqlBuilder.append("LOWER(pfv").append(n).append(".value) LIKE '%").append(filterString.toLowerCase()).append("%'"); + sqlCountBuilder.append("LOWER(fv.value) LIKE '%").append(filterString.toLowerCase()).append("%'"); } + break; } - if (sqlWhereBuilder.length() > 0) { - sqlWhereBuilderList.add(sqlWhereBuilder); + if (sqlBuilder.length() > 0) { + sqlWhereBuilderList.add(sqlBuilder); + + if (!sqlCountWherePredicate.containsKey(predicateId)) { + sqlCountWherePredicate.put(predicateId, new StringBuilder()); + } + + sqlCountWherePredicate.get(predicateId).append(" (").append(sqlCountBuilder).append(") OR"); } } - // all column search filter - for (String filterString : allColumnSearchFilters) { - sqlWhereBuilder = new StringBuilder(); - sqlWhereBuilder.append("LOWER(pfv").append(n).append(".value) LIKE '%").append(filterString.toLowerCase()).append("%'"); - sqlAllColumnsWhereBuilderList.add(sqlWhereBuilder); - } + if (submissionListColumn.getSortOrder() > 0 || submissionListColumn.getFilters().size() > 0) { + // all column search filter + for (String filterString : allColumnSearchFilters) { + sqlBuilder = new StringBuilder(); + sqlBuilder.append("LOWER(pfv").append(n).append(".value) LIKE '%").append(filterString.toLowerCase()).append("%'"); + sqlAllColumnsWhereBuilderList.add(sqlBuilder); + } - n++; + n++; + } break; case "id": if (submissionListColumn.getSortOrder() > 0) { - setColumnOrdering(submissionListColumn.getSort(), sqlSelectBuilder, sqlOrderBysBuilder, " s.id"); + // The s.id is already on the submission, such just add it to the order by rather than call setColumnOrdering(). + Sort sort = submissionListColumn.getSort(); + if (sort == Sort.ASC || sort == Sort.DESC) { + sqlOrderBysBuilder.append(" s.id ").append(sort.name()).append(","); + } } for (String filterString : submissionListColumn.getFilters()) { - sqlWhereBuilder = new StringBuilder(); - sqlWhereBuilder.append("s").append(".id = ").append(filterString); - sqlWhereBuilderList.add(sqlWhereBuilder); + sqlBuilder = new StringBuilder(); + sqlBuilder.append("s").append(".id = ").append(filterString); + sqlWhereBuilderList.add(sqlBuilder); + getFromBuildersMap(sqlCountWhereFilterBuilders, "id").add(sqlBuilder); } break; case "submissionStatus.name": + sqlBuilder = new StringBuilder() + .append("\nLEFT JOIN submission_status ss ON ss.id=s.submission_status_id"); - sqlJoinsBuilder.append("\nLEFT JOIN submission_status ss ON ss.id=s.submission_status_id"); + sqlJoinsBuilder.append(sqlBuilder); + sqlCountSelectBuilder.append(sqlBuilder); if (submissionListColumn.getSortOrder() > 0) { setColumnOrdering(submissionListColumn.getSort(), sqlSelectBuilder, sqlOrderBysBuilder, " ss.name"); } for (String filterString : submissionListColumn.getFilters()) { - sqlWhereBuilder = new StringBuilder(); + sqlBuilder = new StringBuilder(); if (submissionListColumn.getExactMatch()) { - sqlWhereBuilder.append("ss").append(".name = '").append(filterString).append("'"); + sqlBuilder.append("ss").append(".name = '").append(filterString).append("'"); } else { // TODO: determine if status will ever be search using a like - sqlWhereBuilder.append("LOWER(ss").append(".name) LIKE '%").append(filterString.toLowerCase()).append("%'"); + sqlBuilder.append("LOWER(ss").append(".name) LIKE '%").append(filterString.toLowerCase()).append("%'"); } - sqlWhereBuilderList.add(sqlWhereBuilder); + sqlWhereBuilderList.add(sqlBuilder); + getFromBuildersMap(sqlCountWhereFilterBuilders, "submissionStatus.name").add(sqlBuilder); } // all column search filter for (String filterString : allColumnSearchFilters) { - sqlWhereBuilder = new StringBuilder(); - sqlWhereBuilder.append("LOWER(ss").append(".name) LIKE '%").append(filterString.toLowerCase()).append("%'"); - sqlAllColumnsWhereBuilderList.add(sqlWhereBuilder); + sqlBuilder = new StringBuilder(); + sqlBuilder.append("LOWER(ss").append(".name) LIKE '%").append(filterString.toLowerCase()).append("%'"); + sqlAllColumnsWhereBuilderList.add(sqlBuilder); } break; @@ -565,7 +627,11 @@ public int compare(SubmissionListColumn svc1, SubmissionListColumn svc2) { case "organization.name": if (sqlJoinsBuilder.indexOf("LEFT JOIN organization o ON o.id=s.organization_id") == -1) { - sqlJoinsBuilder.append("\nLEFT JOIN organization o ON o.id=s.organization_id"); + sqlBuilder = new StringBuilder() + .append("\nLEFT JOIN organization o ON o.id=s.organization_id"); + + sqlJoinsBuilder.append(sqlBuilder); + sqlCountSelectBuilder.append(sqlBuilder); } if (submissionListColumn.getSortOrder() > 0) { @@ -573,140 +639,154 @@ public int compare(SubmissionListColumn svc1, SubmissionListColumn svc2) { } for (String filterString : submissionListColumn.getFilters()) { - sqlWhereBuilder = new StringBuilder(); + sqlBuilder = new StringBuilder(); if (submissionListColumn.getExactMatch()) { - sqlWhereBuilder.append("o").append(".name = '").append(filterString).append("'"); + sqlBuilder.append("o").append(".name = '").append(filterString).append("'"); } else { // TODO: determine if organization name will ever be // search using a like - sqlWhereBuilder.append("LOWER(o").append(".name) LIKE '%").append(filterString.toLowerCase()).append("%'"); + sqlBuilder.append("LOWER(o").append(".name) LIKE '%").append(filterString.toLowerCase()).append("%'"); } - sqlWhereBuilderList.add(sqlWhereBuilder); + sqlWhereBuilderList.add(sqlBuilder); + getFromBuildersMap(sqlCountWhereFilterBuilders, "organization.name").add(sqlBuilder); } // all column search filter for (String filterString : allColumnSearchFilters) { - sqlWhereBuilder = new StringBuilder(); - sqlWhereBuilder.append("LOWER(o").append(".name) LIKE '%").append(filterString.toLowerCase()).append("%'"); - sqlAllColumnsWhereBuilderList.add(sqlWhereBuilder); + sqlBuilder = new StringBuilder(); + sqlBuilder.append("LOWER(o").append(".name) LIKE '%").append(filterString.toLowerCase()).append("%'"); + sqlAllColumnsWhereBuilderList.add(sqlBuilder); } break; case "organization.category.name": + sqlBuilder = new StringBuilder(); if (sqlJoinsBuilder.indexOf("LEFT JOIN organization o ON o.id=s.organization_id") == -1) { - sqlJoinsBuilder.append("\nLEFT JOIN organization o ON o.id=s.organization_id"); + sqlBuilder.append("\nLEFT JOIN organization o ON o.id=s.organization_id"); } - sqlJoinsBuilder.append("\nLEFT JOIN organization_category oc ON oc.id=o.category_id"); + sqlBuilder.append("\nLEFT JOIN organization_category oc ON oc.id=o.category_id"); + + sqlJoinsBuilder.append(sqlBuilder); + sqlCountSelectBuilder.append(sqlBuilder); if (submissionListColumn.getSortOrder() > 0) { setColumnOrdering(submissionListColumn.getSort(), sqlSelectBuilder, sqlOrderBysBuilder, " oc.name"); } for (String filterString : submissionListColumn.getFilters()) { - sqlWhereBuilder = new StringBuilder(); + sqlBuilder = new StringBuilder(); if (submissionListColumn.getExactMatch()) { - sqlWhereBuilder.append("oc").append(".name = '").append(filterString).append("'"); + sqlBuilder.append("oc").append(".name = '").append(filterString).append("'"); } else { // TODO: determine if organization category name // will ever be search using a like - sqlWhereBuilder.append("LOWER(oc").append(".name) LIKE '%").append(filterString.toLowerCase()).append("%'"); + sqlBuilder.append("LOWER(oc").append(".name) LIKE '%").append(filterString.toLowerCase()).append("%'"); } - sqlWhereBuilderList.add(sqlWhereBuilder); + sqlWhereBuilderList.add(sqlBuilder); + getFromBuildersMap(sqlCountWhereFilterBuilders, "organization.category.name").add(sqlBuilder); } // all column search filter for (String filterString : allColumnSearchFilters) { - sqlWhereBuilder = new StringBuilder(); - sqlWhereBuilder.append("LOWER(oc").append(".name) LIKE '%").append(filterString.toLowerCase()).append("%'"); - sqlAllColumnsWhereBuilderList.add(sqlWhereBuilder); + sqlBuilder = new StringBuilder(); + sqlBuilder.append("LOWER(oc").append(".name) LIKE '%").append(filterString.toLowerCase()).append("%'"); + sqlAllColumnsWhereBuilderList.add(sqlBuilder); } break; case "assignee.email": + sqlBuilder = new StringBuilder() + .append("\nLEFT JOIN weaver_users a ON a.id=s.assignee_id"); - sqlJoinsBuilder.append("\nLEFT JOIN weaver_users a ON a.id=s.assignee_id"); + sqlJoinsBuilder.append(sqlBuilder); + sqlCountSelectBuilder.append(sqlBuilder); if (submissionListColumn.getSortOrder() > 0) { setColumnOrdering(submissionListColumn.getSort(), sqlSelectBuilder, sqlOrderBysBuilder, " a.email"); } for (String filterString : submissionListColumn.getFilters()) { - sqlWhereBuilder = new StringBuilder(); + sqlBuilder = new StringBuilder(); if (filterString == null) { - sqlWhereBuilder.append("a").append(".email IS NULL"); + sqlBuilder.append("a").append(".email IS NULL"); } else if (submissionListColumn.getExactMatch()) { - sqlWhereBuilder.append("a").append(".email = '").append(filterString).append("'"); + sqlBuilder.append("a").append(".email = '").append(filterString).append("'"); } else { - sqlWhereBuilder.append("LOWER(a").append(".email) LIKE '%").append(filterString.toLowerCase()).append("%'"); + sqlBuilder.append("LOWER(a").append(".email) LIKE '%").append(filterString.toLowerCase()).append("%'"); } - sqlWhereBuilderList.add(sqlWhereBuilder); + sqlWhereBuilderList.add(sqlBuilder); + getFromBuildersMap(sqlCountWhereFilterBuilders, "assignee.email").add(sqlBuilder); } // all column search filter for (String filterString : allColumnSearchFilters) { - sqlWhereBuilder = new StringBuilder(); - sqlWhereBuilder.append("LOWER(a").append(".email) LIKE '%").append(filterString.toLowerCase()).append("%'"); - sqlAllColumnsWhereBuilderList.add(sqlWhereBuilder); + sqlBuilder = new StringBuilder(); + sqlBuilder.append("LOWER(a").append(".email) LIKE '%").append(filterString.toLowerCase()).append("%'"); + sqlAllColumnsWhereBuilderList.add(sqlBuilder); } break; case "embargoTypes.name": - // @formatter:off - sqlJoinsBuilder.append("\nLEFT JOIN") - .append("\n (SELECT e.id, e.name, semt.submission_id") - .append("\n FROM embargo e") - .append("\n LEFT JOIN submission_embargo_types semt") - .append("\n ON semt.embargo_types_id=e.id) embs") - .append("\n ON embs.submission_id=s.id"); - // @formatter:on + sqlBuilder = new StringBuilder() + .append("\nLEFT JOIN") + .append("\n (SELECT e.id, e.name, semt.submission_id") + .append("\n FROM embargo e") + .append("\n LEFT JOIN submission_embargo_types semt") + .append("\n ON semt.embargo_types_id=e.id) embs") + .append("\n ON embs.submission_id=s.id"); + + sqlJoinsBuilder.append(sqlBuilder); + sqlCountSelectBuilder.append(sqlBuilder); if (submissionListColumn.getSortOrder() > 0) { setColumnOrdering(submissionListColumn.getSort(), sqlSelectBuilder, sqlOrderBysBuilder, " embs.name"); } for (String filterString : submissionListColumn.getFilters()) { - sqlWhereBuilder = new StringBuilder(); + sqlBuilder = new StringBuilder(); - sqlWhereBuilder.append(" embs").append(".name = '").append(filterString).append("'"); + sqlBuilder.append(" embs").append(".name = '").append(filterString).append("'"); if (filterString.equals("None")) { - sqlWhereBuilderList.add(sqlWhereBuilder); - sqlWhereBuilder = new StringBuilder(); - sqlWhereBuilder.append("embs.id IS NULL"); + sqlWhereBuilderList.add(sqlBuilder); + sqlBuilder = new StringBuilder(); + sqlBuilder.append("embs.id IS NULL"); } - sqlWhereBuilderList.add(sqlWhereBuilder); + sqlWhereBuilderList.add(sqlBuilder); + getFromBuildersMap(sqlCountWhereFilterBuilders, "embargoTypes.name").add(sqlBuilder); } // all column search filter for (String filterString : allColumnSearchFilters) { - sqlWhereBuilder = new StringBuilder(); - sqlWhereBuilder.append("LOWER(embs").append(".name) LIKE '%").append(filterString.toLowerCase()).append("%'"); - sqlAllColumnsWhereBuilderList.add(sqlWhereBuilder); + sqlBuilder = new StringBuilder(); + sqlBuilder.append("LOWER(embs").append(".name) LIKE '%").append(filterString.toLowerCase()).append("%'"); + sqlAllColumnsWhereBuilderList.add(sqlBuilder); } break; case "lastEvent": - // @formatter:off - - sqlJoinsBuilder.append("\nLEFT JOIN") - .append("\n (SELECT al.id, al.action_date, al.entry, al.action_logs_id") - .append("\n FROM action_log al") - .append("\n WHERE (al.action_logs_id = id)") - .append("\n ORDER BY al.action_date DESC") - .append("\n LIMIT 1) als") - .append("\n ON action_logs_id = s.submission_status_id"); - // @formatter:on + sqlBuilder = new StringBuilder() + .append("\nLEFT JOIN") + .append("\n (SELECT al.id, al.action_date, al.entry, al.action_logs_id") + .append("\n FROM action_log al") + .append("\n WHERE (al.action_logs_id = id)") + .append("\n ORDER BY al.action_date DESC") + .append("\n LIMIT 1) als") + .append("\n ON action_logs_id = s.submission_status_id"); + + sqlJoinsBuilder.append(sqlBuilder); + sqlCountSelectBuilder.append(sqlBuilder); // TODO: finish sqlWheresBuilder. @@ -735,7 +815,9 @@ public int compare(SubmissionListColumn svc1, SubmissionListColumn svc2) { } for (String filterString : submissionListColumn.getFilters()) { - sqlWhereBuilderList.add(buildSubmissionDateFieldString("submission_date", filterString)); + sqlBuilder = buildSubmissionDateFieldString("submission_date", filterString); + sqlWhereBuilderList.add(sqlBuilder); + getFromBuildersMap(sqlCountWhereFilterBuilders, "submissionDate").add(sqlBuilder); } break; @@ -746,7 +828,9 @@ public int compare(SubmissionListColumn svc1, SubmissionListColumn svc2) { } for (String filterString : submissionListColumn.getFilters()) { - sqlWhereBuilderList.add(buildSubmissionDateFieldString("approve_application_date", filterString)); + sqlBuilder = buildSubmissionDateFieldString("approve_application_date", filterString); + sqlWhereBuilderList.add(sqlBuilder); + getFromBuildersMap(sqlCountWhereFilterBuilders, "approveApplicationDate").add(sqlBuilder); } break; @@ -757,7 +841,9 @@ public int compare(SubmissionListColumn svc1, SubmissionListColumn svc2) { } for (String filterString : submissionListColumn.getFilters()) { - sqlWhereBuilderList.add(buildSubmissionDateFieldString("approve_advisor_date", filterString)); + sqlBuilder = buildSubmissionDateFieldString("approve_advisor_date", filterString); + sqlWhereBuilderList.add(sqlBuilder); + getFromBuildersMap(sqlCountWhereFilterBuilders, "approveAdvisorDate").add(sqlBuilder); } break; @@ -768,25 +854,29 @@ public int compare(SubmissionListColumn svc1, SubmissionListColumn svc2) { } for (String filterString : submissionListColumn.getFilters()) { - sqlWhereBuilderList.add(buildSubmissionDateFieldString("approve_embargo_date", filterString)); + sqlBuilder = buildSubmissionDateFieldString("approve_embargo_date", filterString); + sqlWhereBuilderList.add(sqlBuilder); + getFromBuildersMap(sqlCountWhereFilterBuilders, "approveEmbargoDate").add(sqlBuilder); } break; case "customActionValues": + sqlBuilder = new StringBuilder() + .append("\nLEFT JOIN") + .append("\n (SELECT submission_id, value, label") + .append("\n FROM submission_custom_action_values scav") + .append("\n LEFT JOIN custom_action_value cav ON scav.custom_action_values_id = cav .id") + .append("\n LEFT JOIN custom_action_definition cad ON cav.definition_id = cad.id) scavcavcad") + .append("\n ON scavcavcad.submission_id = s.id"); - // @formatter:off - sqlJoinsBuilder.append("\nLEFT JOIN") - .append("\n (SELECT submission_id, value, label") - .append("\n FROM submission_custom_action_values scav") - .append("\n LEFT JOIN custom_action_value cav ON scav.custom_action_values_id = cav .id") - .append("\n LEFT JOIN custom_action_definition cad ON cav.definition_id = cad.id) scavcavcad") - .append("\n ON scavcavcad.submission_id = s.id"); - // @formatter:on + sqlJoinsBuilder.append(sqlBuilder); + sqlCountSelectBuilder.append(sqlBuilder); for (String filterString : submissionListColumn.getFilters()) { - sqlWhereBuilder = new StringBuilder(); - sqlWhereBuilder.append("scavcavcad.value = true AND scavcavcad.label = '" + filterString + "'"); - sqlWhereBuilderList.add(sqlWhereBuilder); + sqlBuilder = new StringBuilder(); + sqlBuilder.append("scavcavcad.value = true AND scavcavcad.label = '" + filterString + "'"); + sqlWhereBuilderList.add(sqlBuilder); + getFromBuildersMap(sqlCountWhereFilterBuilders, "customActionValues").add(sqlBuilder); } break; @@ -800,9 +890,10 @@ public int compare(SubmissionListColumn svc1, SubmissionListColumn svc2) { } } - // complete select clause + // Complete the select clause. sqlSelectBuilder.setLength(sqlSelectBuilder.length() - 1); sqlSelectBuilder.append(" FROM submission s"); + sqlCountSelectBuilder.insert(0, "SELECT COUNT(DISTINCT s.id) FROM submission s"); // if ordering, complete order by clause and strip the tailing comma if (sqlOrderBysBuilder.length() > 0) { @@ -811,46 +902,92 @@ public int compare(SubmissionListColumn svc1, SubmissionListColumn svc2) { } // build WHERE query such that OR is used for conditions inside a column and AND is used across each different column. - sqlWhereBuilder = new StringBuilder(); + sqlBuilder = new StringBuilder(); if (sqlColumnsBuilders.size() > 0 || sqlAllColumnsWhereBuilderList.size() > 0 || sqlWheresExcludeBuilder.length() > 0) { - sqlWhereBuilder.append("\nWHERE "); + sqlBuilder.append("\nWHERE "); for (Entry> list : sqlColumnsBuilders.entrySet()) { - sqlWhereBuilder.append("("); + sqlBuilder.append("("); for (StringBuilder builder : list.getValue()) { - sqlWhereBuilder.append("(").append(builder).append(") OR "); + sqlBuilder.append("(").append(builder).append(") OR "); } - // remove last " OR". - sqlWhereBuilder.setLength(sqlWhereBuilder.length() - 4); + // remove last " OR ". + sqlBuilder.setLength(sqlBuilder.length() - 4); - sqlWhereBuilder.append(") AND "); + sqlBuilder.append(") AND "); } if (sqlAllColumnsWhereBuilderList.size() > 0) { - sqlWhereBuilder.append("("); + sqlBuilder.append("("); for (StringBuilder builder : sqlAllColumnsWhereBuilderList) { - sqlWhereBuilder.append("(").append(builder).append(") OR "); + sqlBuilder.append("(").append(builder).append(") OR "); } - // remove last " OR". - sqlWhereBuilder.setLength(sqlWhereBuilder.length() - 4); + // remove last " OR ". + sqlBuilder.setLength(sqlBuilder.length() - 4); - sqlWhereBuilder.append(") AND "); + sqlBuilder.append(") AND "); } if (sqlWheresExcludeBuilder.length() > 0) { - sqlWhereBuilder.append("(").append(sqlWheresExcludeBuilder).append(")"); + sqlBuilder.append("(").append(sqlWheresExcludeBuilder).append(")"); + } else { + // remove last " AND " + sqlBuilder.setLength(sqlBuilder.length() - 5); + } + } + + if (sqlCountWherePredicate.size() > 0 || sqlCountWhereFilterBuilders.size() > 0 || sqlWheresExcludeBuilder.length() > 0) { + + // Conditions are AND across different predicates but are OR within the same predicate. + if (sqlCountWherePredicate.size() > 0) { + sqlCountSelectBuilder.append("\nLEFT JOIN submission_field_values sfv ON s.id = sfv.submission_id"); + sqlCountSelectBuilder.append("\nINNER JOIN field_value fv ON sfv.field_values_id = fv.id"); + sqlCountSelectBuilder.append("\nWHERE"); + + sqlCountWherePredicate.forEach((id, filter) -> { + if (filter.length() > 0) { + // Remove the last " OR". + filter.setLength(filter.length() - 3); + + sqlCountSelectBuilder + .append("\n(fv.field_predicate_id = ").append(id) + .append(" AND (").append(filter).append(")) AND"); + } + }); + } else { + sqlCountSelectBuilder.append("\nWHERE"); + } + + // Conditions are AND across different filters and OR within the same filter. + if (sqlCountWhereFilterBuilders.size() > 0) { + sqlCountWhereFilterBuilders.forEach((key, list) -> { + sqlCountSelectBuilder.append(" ("); + + list.forEach(filter -> { + sqlCountSelectBuilder.append(" (").append(filter).append(") OR"); + }); + + // Remove the last " OR". + sqlCountSelectBuilder.setLength(sqlCountSelectBuilder.length() - 3); + + sqlCountSelectBuilder.append(")\n AND"); + }); + } + + if (sqlWheresExcludeBuilder.length() > 0) { + sqlCountSelectBuilder.append("\n(").append(sqlWheresExcludeBuilder).append(")"); } else { // remove last " AND" - sqlWhereBuilder.setLength(sqlWhereBuilder.length() - 5); + sqlCountSelectBuilder.setLength(sqlCountSelectBuilder.length() - 4); } } - String sqlQuery = sqlSelectBuilder.toString() + sqlJoinsBuilder.toString() + sqlWhereBuilder.toString(); - String sqlCountQuery = sqlCountSelectBuilder.toString() + sqlJoinsBuilder.toString() + sqlWhereBuilder.toString(); + String sqlQuery = sqlSelectBuilder.toString() + sqlJoinsBuilder.toString() + sqlBuilder.toString(); + String sqlCountQuery = sqlCountSelectBuilder.toString(); if (pageable != null) { // determine the offset and limit of the query @@ -867,16 +1004,44 @@ public int compare(SubmissionListColumn svc1, SubmissionListColumn svc2) { } public void setColumnOrdering(Sort sort, StringBuilder sqlSelectBuilder, StringBuilder sqlOrderBysBuilder, String value) { - sqlSelectBuilder.append(value).append(","); - switch (sort) { - case ASC: - sqlOrderBysBuilder.append(value).append(" ASC,"); - break; - case DESC: - sqlOrderBysBuilder.append(value).append(" DESC,"); - break; - default: - break; + if (sort == Sort.ASC || sort == Sort.DESC) { + sqlSelectBuilder.append(value).append(","); + sqlOrderBysBuilder.append(value).append(" ").append(sort.name()).append(","); + } + } + + /** + * Handle case where Postgresql requires help casting the date string to a date type. + * + * Some SQL engines can directly cast the "Month Year" format to a date but postgresql cannot. + * Because of this limitation in Postgresql, all dates cannot be cast. + * Instead, the "Month Year" formatted data must be handled as an exception case. + * + * This converts the "Month Year" format into a "Month Day, Year" format to make Postgresql happy. + * Then this converts that date resulting string into a date type for proper SQL sorting. + * + * This would also be easier to add the cast on the ORDER BY and not need to add a column in the select clause. + * However, another Postgresql problem prevents this from working when DISTINCT is in use. + * Postgresql will falsely claim that pfv0.value is not in the SELECT clause when it actually is while DISTINCT is present. + * + * @param sort The sort direction. + * @param sqlSelectBuilder The SQL select builder string. + * @param sqlOrderBysBuilder The SQL order by builder string. + * @param table The table to select from when joining on the assumption that the value is "table".value. + */ + public void setColumnOrderingForMonthYearDateFormat(Sort sort, StringBuilder sqlSelectBuilder, StringBuilder sqlOrderBysBuilder, String table) { + if (sort == Sort.ASC || sort == Sort.DESC) { + sqlSelectBuilder.append(table).append(".value,"); + + if ("h2".equals(vireoDatabaseConfig.getPlatform())) { + sqlSelectBuilder.append(" PARSEDATETIME(").append(table).append(".value, 'MMM yyyy') AS"); + } else { + sqlSelectBuilder.append(" CAST(REPLACE(").append(table).append(".value, ' ', ' 1, ') AS DATE) AS"); + } + + sqlSelectBuilder.append(table).append("_date,"); + + sqlOrderBysBuilder.append(table).append("_date ").append(sort.name()).append(","); } } @@ -886,32 +1051,43 @@ protected String getChannel() { } /** - * Build a date field string given some filter. + * Build a submission date field string given some filter. * - * @param id The ID to append to the field name alias. + * This is form submission date fields that are already stored in the SQL date format. + * + * @param column The column name to filter. * @param filter The filter. * @return A constructed string builder appropriately casting the date. */ - private StringBuilder buildDateFieldString(int id, String filter) { + private StringBuilder buildSubmissionDateFieldString(String column, String filter) { + if (filter.contains("|")) { + String[] dates = filter.split(Pattern.quote("|")); return new StringBuilder() - .append("pfv").append(id) - .append(".value = CAST('").append(filter) + .append("s.").append(column) + .append(" BETWEEN CAST('").append(dates[0]) + .append("' AS DATE) AND CAST('").append(dates[1]) + .append("' AS DATE)"); + } + return new StringBuilder() + .append("s.").append(column) + .append(" = CAST('").append(filter) .append("' AS DATE)"); } /** - * Build a submission date field string given some filter. + * Get the builders list array for some key, initializing that key if not found. * - * This is form submission date fields that are already stored in the SQL date format. + * @param map The map of builders to select from. + * @param key The identifier. * - * @param column The column name to filter. - * @param filter The filter. - * @return A constructed string builder appropriately casting the date. + * @return An array of the builders for the given key. */ - private StringBuilder buildSubmissionDateFieldString(String column, String filter) { - return new StringBuilder() - .append("s.").append(column) - .append(" = CAST('").append(filter).append("' AS DATE)"); + private ArrayList getFromBuildersMap(Map> map, String key) { + if (!map.containsKey(key)) { + map.put(key, new ArrayList()); + } + + return map.get(key); } private class QueryStrings { diff --git a/src/main/java/org/tdl/vireo/service/VireoThemeManagerService.java b/src/main/java/org/tdl/vireo/service/VireoThemeManagerService.java index 9e4ce62afb..fb96bca5cc 100644 --- a/src/main/java/org/tdl/vireo/service/VireoThemeManagerService.java +++ b/src/main/java/org/tdl/vireo/service/VireoThemeManagerService.java @@ -24,7 +24,7 @@ public class VireoThemeManagerService extends SimpleThemeManagerService implemen @Override public Map getThemeProperties() { - String[] themePropertyNames = { "background_main_color", "background_highlight_color", "button_main_color_on", "button_highlight_color_on", "button_main_color_off", "button_highlight_color_off" }; + String[] themePropertyNames = {"text_main_color", "background_main_color", "background_highlight_color", "background_header_text_color", "background_footer_text_color", "button_main_color_on", "button_highlight_color_on", "button_text_color_on", "button_main_color_off", "button_highlight_color_off", "button_text_color_off", "admin_tab_main_color", "admin_tab_selected_color", "admin_tab_main_text_color", "admin_tab_selected_text_color"}; List themePropertyNamesList = Arrays.asList(themePropertyNames); List themeConfigurations = configurationRepo.getAllByType("lookAndFeel"); HashMap themeProperties = new HashMap(); diff --git a/src/main/resources/application.yml b/src/main/resources/application.yml index 17a9341694..ee87b0aed8 100644 --- a/src/main/resources/application.yml +++ b/src/main/resources/application.yml @@ -114,7 +114,7 @@ info: app: - url: http://localhost:9000 + url: http://localhost:${server.port} # value generated from property assets.uri # either defined in pom.xml or via package argument @@ -142,7 +142,7 @@ app: # edu.tamu.weaver.auth.service.CryptoService secret: verysecretsecret # edu.tamu.weaver.filter.CorsFilter - allow-access: http://localhost:9000 + allow-access: http://localhost:${server.port} # edu.tamu.weaver.email.config.WeaverEmailConfig email: diff --git a/src/main/resources/organization/SYSTEM_Organization_Definition.json b/src/main/resources/organization/SYSTEM_Organization_Definition.json index e02949061d..1e90a9ed21 100644 --- a/src/main/resources/organization/SYSTEM_Organization_Definition.json +++ b/src/main/resources/organization/SYSTEM_Organization_Definition.json @@ -18,7 +18,7 @@ "originalNotes":[ { "name":"name", - "text":"Your name should appear as it does on your title page. You can use Unicode characters, if your computer supports them", + "text":"Your name should appear as it does on your title page. You can use Unicode characters, if your computer supports them.", "originatingWorkflowStep":{ "name":"Verify Personal Information" } diff --git a/src/main/resources/settings/SYSTEM_Defaults.json b/src/main/resources/settings/SYSTEM_Defaults.json index f99f3754d3..bea6e82458 100644 --- a/src/main/resources/settings/SYSTEM_Defaults.json +++ b/src/main/resources/settings/SYSTEM_Defaults.json @@ -85,12 +85,21 @@ { "theme_path":"configuration/theme/" }, + { + "text_main_color":"#757575" + }, { "background_main_color":"#1b333f" }, { "background_highlight_color":"#43606e" }, + { + "background_header_text_color":"#ffffff" + }, + { + "background_footer_text_color":"#ffffff" + }, { "button_main_color_on":"#1b333f" }, @@ -98,10 +107,28 @@ "button_highlight_color_on":"#43606e" }, { - "button_main_color_off":"#92aa9c" + "button_text_color_on":"#ffffff" + }, + { + "button_main_color_off":"#424D46" + }, + { + "button_highlight_color_off":"#697A70" + }, + { + "button_text_color_off":"#ffffff" + }, + { + "admin_tab_main_color":"#adbdaa" + }, + { + "admin_tab_selected_color":"#ffffff" + }, + { + "admin_tab_main_text_color":"#1D3541" }, { - "button_highlight_color_off":"#adbdaa" + "admin_tab_selected_text_color":"#1D3541" }, { "left_logo":"resources/images/default-left-logo.png" diff --git a/src/main/webapp/app/controllers/settingsController.js b/src/main/webapp/app/controllers/settingsController.js index ff903f361f..461356c4b8 100644 --- a/src/main/webapp/app/controllers/settingsController.js +++ b/src/main/webapp/app/controllers/settingsController.js @@ -251,7 +251,9 @@ vireo.controller("SettingsController", function ($controller, $injector, $scope, }; $scope.resetConfiguration = function (type, name) { - return $scope.settings.configurable[type][name].reset(); + if ($scope.settings.configurable[type][name].id) { + return $scope.settings.configurable[type][name].reset(); + } }; $scope.saveDegree = function (degree) { diff --git a/src/main/webapp/app/controllers/submission/submissionListController.js b/src/main/webapp/app/controllers/submission/submissionListController.js index cf1c8edf19..2d59366374 100644 --- a/src/main/webapp/app/controllers/submission/submissionListController.js +++ b/src/main/webapp/app/controllers/submission/submissionListController.js @@ -29,6 +29,8 @@ vireo.controller("SubmissionListController", function (NgTableParams, $controlle $scope.fieldPredicates = FieldPredicateRepo.getAll(); + var rowFilterTitle = "Exclude"; + var ready = $q.all([SubmissionListColumnRepo.ready(), ManagerSubmissionListColumnRepo.ready(), EmailTemplateRepo.ready(), FieldPredicateRepo.ready()]); var updateChange = function(change) { @@ -51,25 +53,19 @@ vireo.controller("SubmissionListController", function (NgTableParams, $controlle var start; var query = function () { + var sessionPageNumber = sessionStorage.getItem("list-page-number"); + var sessionPageSize = sessionStorage.getItem("list-page-size"); + $scope.tableParams = new NgTableParams({ - page: $scope.page.number, - count: $scope.page.count + page: angular.isDefined(sessionPageNumber) && sessionPageNumber !== null ? sessionPageNumber : $scope.page.number, + count: angular.isDefined(sessionPageSize) && sessionPageSize !== null ? sessionPageSize : $scope.page.count, }, { counts: $scope.page.options, total: $scope.page.totalElements, filterDelay: 0, getData: function (params) { start = window.performance.now(); - return SubmissionRepo.query($scope.userColumns, params.page() > 0 ? params.page() - 1 : params.page(), params.count()).then(function (response) { - angular.extend($scope.page, angular.fromJson(response.body).payload.ApiPage); - // NOTE: this causes way to many subscriptions!!! - // SubmissionRepo.addAll($scope.page.content); - params.total($scope.page.totalElements); - $scope.page.count = params.count(); - sessionStorage.setItem("list-page-size", $scope.page.count); - sessionStorage.setItem("list-page-number", $scope.page.number + 1); - return $scope.page.content; - }); + return queryGetData(params); }.bind(start) }); $scope.finished = function() { @@ -77,54 +73,114 @@ vireo.controller("SubmissionListController", function (NgTableParams, $controlle }.bind(start); }.bind(start); - var update = function () { + var queryGetData = function (params, forcePageNumber) { + var requestPage = 0; - SavedFilterRepo.reset(); + if (forcePageNumber === undefined) { + if (params.page() > 0) { + requestPage = params.page() - 1; + } + } else { + requestPage = forcePageNumber; + } - SubmissionListColumnRepo.reset(); + return SubmissionRepo.query($scope.userColumns, requestPage, params.count()).then(function (response) { + var page = angular.fromJson(response.body).payload.ApiPage; - ManagerSubmissionListColumnRepo.reset(); + // Forcibly fix invalid page and try again, but only once. + if (forcePageNumber === undefined && angular.isDefined(page.number) && angular.isDefined(page.totalPages)) { + var totalPages = parseInt(page.totalPages); + var pageNumber = parseInt(page.number); - $q.all([SavedFilterRepo.ready(), SubmissionListColumnRepo.ready(), ManagerSubmissionListColumnRepo.ready()]).then(function() { - ManagerSubmissionListColumnRepo.submissionListPageSize().then(function(response) { - var apiRes = angular.fromJson(response.body); - if(apiRes.meta.status === 'SUCCESS') { - $scope.page.count = sessionStorage.getItem("list-page-size") ? sessionStorage.getItem("list-page-size") : apiRes.payload.Integer; + if (!isNaN(totalPages) && !isNaN(pageNumber) && (pageNumber > totalPages || pageNumber < 0)) { + pageNumber = pageNumber > totalPages && totalPages > 0 ? totalPages - 1 : 0; + + console.warn("Invalid page number '" + page.number + "' detected, forcibly resetting to page '" + pageNumber + "' and trying again."); + + return queryGetData(params, pageNumber); } + } - var managerFilterColumns = ManagerFilterColumnRepo.getAll(); - var submissionListColumns = SubmissionListColumnRepo.getAll(); + angular.extend($scope.page, page); - $scope.userColumns = angular.fromJson(angular.toJson(ManagerSubmissionListColumnRepo.getAll())); + // The service sets page number starting at 0, which needs to be incremented by 1 when provided. + if (angular.isDefined(page.number)) { + $scope.page.number++; + } + + params.total($scope.page.totalElements); + params.page($scope.page.number); + $scope.page.count = params.count(); + sessionStorage.setItem("list-page-size", $scope.page.count); + sessionStorage.setItem("list-page-number", $scope.page.number); - angular.forEach($scope.userColumns, function (userColumn) { - if ($scope.activeFilters.sortColumnTitle === userColumn.title) { - userColumn.sortOrder = 1; - userColumn.sort = $scope.activeFilters.sortDirection; + return $scope.page.content; + }); + } + + var update = function (reloadList) { + SavedFilterRepo.reset(); + ManagerFilterColumnRepo.reset(); + SubmissionListColumnRepo.reset(); + ManagerSubmissionListColumnRepo.reset(); + + $q.all([SavedFilterRepo.ready(), SubmissionListColumnRepo.ready(), ManagerSubmissionListColumnRepo.ready()]).then(function() { + + // Only get the list size if/when there is no page count set. + if (angular.isUndefined(sessionStorage.getItem("list-page-size")) || sessionStorage.getItem("list-page-size") === null) { + ManagerSubmissionListColumnRepo.submissionListPageSize().then(function (response) { + var apiRes = angular.fromJson(response.body); + + if (apiRes.meta.status === 'SUCCESS') { + $scope.page.count = apiRes.payload.Integer; + } else if (sessionStorage.getItem("list-page-size")) { + $scope.page.count = sessionStorage.getItem("list-page-size"); } + + processUpdate(reloadList); }); + } else { + processUpdate(reloadList); + } + }); + }; + + var processUpdate = function (reloadList) { + var managerFilterColumns = ManagerFilterColumnRepo.getAll().filter(function excludeSearchBox(slc) { + return slc.title !== 'Search Box'; + }); + var submissionListColumns = SubmissionListColumnRepo.getAll(); - $scope.excludedColumns = []; + $scope.userColumns = angular.fromJson(angular.toJson(ManagerSubmissionListColumnRepo.getAll())); - angular.copy($scope.userColumns, $scope.excludedColumns); + angular.forEach($scope.userColumns, function (userColumn) { + if ($scope.activeFilters.sortColumnTitle === userColumn.title) { + userColumn.sortOrder = 1; + userColumn.sort = $scope.activeFilters.sortDirection; + } + }); - $scope.excludedColumns.push(SubmissionListColumnRepo.findByTitle('Search Box')); + $scope.excludedColumns = []; - $scope.columns = angular.fromJson(angular.toJson($filter('orderBy')($filter('exclude')(submissionListColumns, $scope.excludedColumns, 'title'), 'title'))); + angular.copy($scope.userColumns, $scope.excludedColumns); - angular.extend(filterColumns, { - userFilterColumns: managerFilterColumns, - inactiveFilterColumns: $filter('orderBy')($filter('exclude')(submissionListColumns, managerFilterColumns, 'title'), 'title') - }); + $scope.excludedColumns.push(SubmissionListColumnRepo.findByTitle('Search Box')); - query(); + $scope.columns = angular.fromJson(angular.toJson($filter('orderBy')($filter('exclude')(submissionListColumns, $scope.excludedColumns, 'title'), 'title'))); - updateChange(false); - }); + angular.extend(filterColumns, { + userFilterColumns: managerFilterColumns, + inactiveFilterColumns: $filter('orderBy')($filter('exclude')(submissionListColumns, managerFilterColumns, 'title'), 'title') }); + + if (reloadList === true) { + query(); + } + + updateChange(false); }; - update(); + update(true); var assignableUsers = UserRepo.getAssignableUsers(0, 0); var savedFilters = SavedFilterRepo.getAll(); @@ -210,10 +266,13 @@ vireo.controller("SubmissionListController", function (NgTableParams, $controlle var addFilter = function (column, gloss) { + $scope.resetPagination(); + var filterValue = $scope.furtherFilterBy[column.title.split(" ").join("")]; if (filterValue !== null) { filterValue = filterValue.toString(); } + $scope.activeFilters.addFilter(column.title, filterValue, gloss, column.exactMatch).then(function () { $scope.furtherFilterBy[column.title.split(" ").join("")] = ""; query(); @@ -265,12 +324,17 @@ vireo.controller("SubmissionListController", function (NgTableParams, $controlle }); }; - $scope.addRowFilter = function ($index, row) { + $scope.addRowFilter = function ($index, row) { + // When removing the last row for a page, and the page number is the last + if ($scope.page.content.length == 1 && $scope.page.number > 0 && $scope.page.number == $scope.page.totalPages) { + sessionStorage.setItem("list-page-number", --$scope.page.number); + } + $scope.page.content.splice($index, 1); - var columnTitle = "Exclude"; + var value = row.id.toString(); var gloss = "Submission #" + row.id; - $scope.activeFilters.addFilter(columnTitle, value, gloss, true).then (function () { + $scope.activeFilters.addFilter(rowFilterTitle, value, gloss, true).then(function () { query(); }); }; @@ -599,9 +663,11 @@ vireo.controller("SubmissionListController", function (NgTableParams, $controlle }; $scope.displaySubmissionProperty = function (row, col) { - var value = $scope.getSubmissionProperty(row, col); + if (angular.isDefined(col) && col !== null) { + return $filter('displayFieldValue')($scope.getSubmissionProperty(row, col), col.inputType); + } - return angular.isDefined(col) ? $filter('displayFieldValue')(value, col.inputType) : value; + return value; }; $scope.getCustomActionLabelById = function (id) { @@ -635,12 +701,19 @@ vireo.controller("SubmissionListController", function (NgTableParams, $controlle }; $scope.removeFilterValue = function (criterionName, filterValue) { + // Reset filter except for when row filters are in use. + if (criterionName !== rowFilterTitle) { + $scope.resetPagination(); + } + $scope.activeFilters.removeFilter(criterionName, filterValue).then(function () { query(); }); }; $scope.clearFilters = function () { + $scope.resetPagination(); + $scope.activeFilters.clearFilters().then(function () { query(); }); @@ -696,7 +769,9 @@ vireo.controller("SubmissionListController", function (NgTableParams, $controlle updateChange(false); }; - $scope.removeFilter = function (filter) { + $scope.removeSaveFilter = function (filter) { + $scope.resetPagination(); + SavedFilterRepo.delete(filter).then(function () { SavedFilterRepo.reset(); }); @@ -721,10 +796,8 @@ vireo.controller("SubmissionListController", function (NgTableParams, $controlle }; $scope.resetColumns = function () { - ManagerSubmissionListColumnRepo.reset(); - update(); + update(true); $scope.closeModal(); - updateChange(false); }; $scope.resetColumnsToDefault = function () { @@ -734,20 +807,23 @@ vireo.controller("SubmissionListController", function (NgTableParams, $controlle }; $scope.saveColumns = function () { - ManagerSubmissionListColumnRepo.updateSubmissionListColumns($scope.userColumns, $scope.page.count).then(function () { - $scope.page.number = 1; - sessionStorage.setItem("list-page-size", $scope.page.count); - $scope.resetColumns(); + ManagerSubmissionListColumnRepo.updateSubmissionListColumns($scope.userColumns, $scope.page.count).then(function (res) { + var results = angular.fromJson(res.body); + if (results.meta.status === 'SUCCESS') { + $scope.resetPagination(); + sessionStorage.setItem("list-page-size", $scope.page.count); + $scope.resetColumns(); + } }); }; $scope.saveUserFilters = function () { - ManagerFilterColumnRepo.updateFilterColumns(filterColumns.userFilterColumns).then(function () { - for (var i in filterColumns.userFilterColumns) { - delete filterColumns.userFilterColumns[i].status; + ManagerFilterColumnRepo.updateFilterColumns(filterColumns.userFilterColumns).then(function (res) { + var results = angular.fromJson(res.body); + if (results.meta.status === 'SUCCESS') { + update(false); + $scope.closeModal(); } - update(); - $scope.closeModal(); }); }; @@ -888,13 +964,20 @@ vireo.controller("SubmissionListController", function (NgTableParams, $controlle "resetSaveUserFilters": $scope.resetSaveFilter, "applyFilter": $scope.applyFilter, "resetRemoveFilters": $scope.resetRemoveFilters, - "removeFilter": $scope.removeFilter, + "removeSaveFilter": $scope.removeSaveFilter, "getUserById": $scope.getUserById }, $scope.furtherFilterBy, $scope.advancedfeaturesBox ]); + $scope.resetPagination = function() { + $scope.pageNumber = 0; + $scope.page.number = 1; + + sessionStorage.setItem("list-page-number", 1); + }; + }); }); diff --git a/src/main/webapp/app/directives/tooltipDirective.js b/src/main/webapp/app/directives/tooltipDirective.js index 75ff191ca3..61a656fccc 100644 --- a/src/main/webapp/app/directives/tooltipDirective.js +++ b/src/main/webapp/app/directives/tooltipDirective.js @@ -23,7 +23,7 @@ vireo.directive('tooltip', function ($timeout) { }, 250); }; - $scope.mouseEnter = function() { + $scope.showTooltip = function() { open(); $timeout(function() { angular.element('.popover').hover(function() { @@ -34,7 +34,7 @@ vireo.directive('tooltip', function ($timeout) { }); }; - $scope.mouseLeave = function() { + $scope.hideTooltip = function() { close(); }; diff --git a/src/main/webapp/app/directives/validatedInputDirective.js b/src/main/webapp/app/directives/validatedInputDirective.js index d229990094..f5e5c3f2c5 100644 --- a/src/main/webapp/app/directives/validatedInputDirective.js +++ b/src/main/webapp/app/directives/validatedInputDirective.js @@ -73,9 +73,9 @@ vireo.directive("validatedinput", function ($q, $timeout) { } // escape(27): reset value using shadow else if ($event.which == 27) { - $scope.model.refresh(); - } else { - + if ($scope.model && typeof $scope.model.refresh === 'function') { + $scope.model.refresh(); + } } }; diff --git a/src/main/webapp/app/filters/displayFieldValue.js b/src/main/webapp/app/filters/displayFieldValue.js index 3d1ab79f1f..3ee5b0c78a 100644 --- a/src/main/webapp/app/filters/displayFieldValue.js +++ b/src/main/webapp/app/filters/displayFieldValue.js @@ -1,7 +1,7 @@ vireo.filter('displayFieldValue', function($filter, InputTypes) { return function(value, inputType) { if (angular.isUndefined(inputType)) { - return value; + return value; } var dateColumn = null; @@ -15,7 +15,7 @@ vireo.filter('displayFieldValue', function($filter, InputTypes) { if (type != null) { if (inputType.name == InputTypes.INPUT_LICENSE || inputType.name == InputTypes.INPUT_PROQUEST) { - return value == 'true' ? 'yes' : 'no'; + return value == 'true' ? 'yes' : 'no'; } if (angular.isDefined(appConfig.dateColumns) && angular.isDefined(inputType.name)) { @@ -35,17 +35,28 @@ vireo.filter('displayFieldValue', function($filter, InputTypes) { } } - if (dateColumn == null || angular.isUndefined(value) || value == null) { - return value; + if (dateColumn === null || angular.isUndefined(value) || value === null) { + return value; } // Some browsers, like Firefox, do not support 'MMMM yyyy' formats for Date.parse(). var stamp = Date.parse(value); if (isNaN(stamp) && dateColumn.format == 'MMMM yyyy') { var split = value.match(/^(\S+) (\d+)$/); + + if (split === null || split.length < 3) { + return value; + } + return $filter('date')(new Date(split[1] + ' 01, ' + split[2]).toISOString(), dateColumn.format, 'utc'); } - return $filter('date')(new Date(value).toISOString(), dateColumn.format, 'utc'); + var date = new Date(value); + + if (isNaN(date)) { + return value; + } + + return $filter('date')(date.toISOString(), dateColumn.format, 'utc'); }; }); diff --git a/src/main/webapp/app/model/submission.js b/src/main/webapp/app/model/submission.js index 2dc938dd89..3caceb95cf 100644 --- a/src/main/webapp/app/model/submission.js +++ b/src/main/webapp/app/model/submission.js @@ -514,7 +514,7 @@ var submissionModel = function ($filter, $q, ActionLog, FieldValue, FileService, 'uri': uri } }); - var promise = FileService.download(this.getMapping().file); + var promise = FileService.anonymousDownload(this.getMapping().file); return promise; }; diff --git a/src/main/webapp/app/resources/styles/sass/app.scss b/src/main/webapp/app/resources/styles/sass/app.scss index ffca4bb104..efa7378053 100644 --- a/src/main/webapp/app/resources/styles/sass/app.scss +++ b/src/main/webapp/app/resources/styles/sass/app.scss @@ -12,7 +12,7 @@ html { body { font-family: "Trebuchet MS", Verdana, Geneva, Arial, Helvetica, Sans-Serif; - color: #70706f; + color: $text_main_color; margin-right: 0 !important; padding-right: 0 !important; height: 100%; @@ -76,19 +76,18 @@ main footer { } .tab-nav li { - background: $button_highlight_color_off; + background: $admin_tab_main_color; padding: 0 10px; margin-right: 15px; min-width: 75px; text-align: center; - color: #1D3541; + color: $admin_tab_main_text_color; font-size: 1.75em; font-weight: bolder; display: inline-block; border-top-left-radius: 10px; border-top-right-radius: 10px; box-shadow: inset 0 -5px 7px -5px rgba(0, 0, 0, 0.75); - text-shadow: 0 0 10px rgba(255, 255, 245, 1), -1px -1px 0 rgba(250, 250, 235, 0.25), 1px 1px 0 rgba(250, 250, 235, 0.5); } .tab-nav li.settings-tab { @@ -101,8 +100,8 @@ main footer { .tab-nav li.active, .tab-nav li:hover { cursor: pointer; - background: #FFFFFF; - text-shadow: 0 0 0 #FFFFFF; + color: $admin_tab_selected_text_color; + background: $admin_tab_selected_color; box-shadow: inset 0 0 0 #FFFFFF; } @@ -113,8 +112,7 @@ main footer { .tab-nav li a { height: 45px; color: inherit; - border-top-left-radius: 10px; - border-top-right-radius: 10px; + background-color:transparent !important; } .tab-nav li.settings-tab a { @@ -154,6 +152,7 @@ main footer { } .modal-header-primary { + color: $background_header_text_color; background-color: $background_highlight_color; } @@ -393,12 +392,12 @@ tr.submission-list-row:hover td span.glyphicon.glyphicon-remove-circle { .navbar-default .navbar-nav > .active > a, .navbar-default .navbar-nav > .active > a:focus, .navbar-default .navbar-nav > .active > a:hover { - color: #fff; + color: $background_header_text_color; background-color: $background_highlight_color; } .navbar-default .navbar-nav > li > a { - color: #fff; + color: $background_header_text_color; } .navbar-default .navbar-brand { @@ -586,7 +585,7 @@ togglebutton { } .toggle-button .btn.active { - background-color: #a2ccb5; + background-color: #697A70; color: #FFFFFF; border: 1px solid #70706f; outline: none; @@ -684,26 +683,26 @@ h4 { padding-top: 35px; width: 250px; height: 150px; - color: #a6a6a6; + color: #696969; border-radius: 5px; transition: all 0.35s ease-in; } .trash-drop-zone.dragging { transition: all 0.35s ease-in; - border-color: #a6a6a6; + border-color: #696969; } .trash-drop-zone .glyphicon-trash { transition: all 0.35s ease-in; color: #fefefe; - text-shadow: 1px 0 5px #a6a6a6; + text-shadow: 1px 0 5px #696969; font-size: 70px; } .trash-drop-zone.dragging .glyphicon-trash { transition: all 0.35s ease-in; - color: #a6a6a6; + color: #696969; text-shadow: 1px 0 5px #fefefe; font-size: 70px; } @@ -715,20 +714,20 @@ h4 { padding-top: 35px; max-width: 250px; height: 150px; - color: #a6a6a6; + color: #696969; border-radius: 5px; transition: all 0.35s ease-in; } .upload-drop-zone.dragging-accept { transition: all 0.35s ease-in; - border-color: #a6a6a6; + border-color: #696969; } .upload-drop-zone .glyphicon-upload { transition: all 0.35s ease-in; color: #fefefe; - text-shadow: 1px 0 5px #a6a6a6; + text-shadow: 1px 0 5px #696969; font-size: 70px; } @@ -739,7 +738,7 @@ h4 { .upload-drop-zone.dragging .glyphicon-upload, .upload-drop-zone.reject .glyphicon-ban-circle { transition: all 0.35s ease-in; - color: #a6a6a6; + color: #696969; text-shadow: 1px 0 5px #fefefe; font-size: 70px; } @@ -842,7 +841,7 @@ legend.legendborder { footer a, footer li, footer p { - color: #fff; + color: $background_footer_text_color; font-size: 0.9em; } @@ -956,6 +955,22 @@ input[type=color]:focus { /************************** * LOOK AND FEEL ACCORDION * ***************************/ +.look-and-feel-h1 { + font-size: 18px; + font-style: bold; + margin-bottom: .2em; +} + +.look-and-feel-h2 { + font-size: 14px; + margin-bottom: 1.5em; +} + +.look-and-feel-h1 .glyphicon.glyphicon-info-sign { + vertical-align: text-top; + font-size: 12px; +} + .look-and-feel .color-reset { text-indent: 0; font-size: 0.85em; @@ -972,11 +987,12 @@ input[type=color]:focus { } .look-and-feel .color-input { - height: 37px; + height: 42px; + min-width: 78px; } .look-and-feel .color-selector { - max-width: 200px; + max-width: 100px; } .look-and-feel label.color-selector-swatch { @@ -1194,17 +1210,17 @@ input[type=color]:focus { .triptych-panel .list-group-item:hover { cursor: pointer; - color: #70706f; + color: $text_main_color; background: #efefef; } .triptych-panel.previously-active .list-group-item.selected { - color: #70706f; + color: $text_main_color; background: #E6ECE8; } .triptych-panel .list-group-item.selected { - color: #fff; + color: $text_main_color; background: #92AA9C; } @@ -1423,6 +1439,7 @@ input[type=color]:focus { * NOTES * *********/ .note { + color: #474747; position: relative; max-width: 225px; padding: 15px 15px 35px 35px; diff --git a/src/main/webapp/app/resources/styles/sass/directives/_tooltip.scss b/src/main/webapp/app/resources/styles/sass/directives/_tooltip.scss index ed3ccf8c7a..b0c26e3b08 100644 --- a/src/main/webapp/app/resources/styles/sass/directives/_tooltip.scss +++ b/src/main/webapp/app/resources/styles/sass/directives/_tooltip.scss @@ -37,6 +37,7 @@ background: -webkit-linear-gradient(to bottom, rgba(31, 58, 71, 0.95), rgba(80, 114, 129, 0.95)); background: -ms-linear-gradient(to bottom, rgba(31, 58, 71, 0.95), rgba(80, 114, 129, 0.95)); background: linear-gradient(to bottom, rgba(31, 58, 71, 0.95), rgba(80, 114, 129, 0.95)); + opacity: 1 !important; } .tooltip .popover-content a, diff --git a/src/main/webapp/app/resources/styles/sass/overrides/_bootstrap.scss b/src/main/webapp/app/resources/styles/sass/overrides/_bootstrap.scss index cfa3fafc74..3b79d26f6d 100644 --- a/src/main/webapp/app/resources/styles/sass/overrides/_bootstrap.scss +++ b/src/main/webapp/app/resources/styles/sass/overrides/_bootstrap.scss @@ -6,10 +6,10 @@ // $gray-light: lighten($gray-base, 46.7%) !default; // #777 // $gray-lighter: lighten($gray-base, 93.5%) !default; // #eee -$brand-primary: darken(#428bca, 6.5%) !default; // #337ab7 +$brand-primary: darken(#367DC4, 6.5%) !default; // #337ab7 // $brand-success: #5cb85c !default; -// $brand-info: #5bc0de !default; +$brand-info: #357AB9 !default; // $brand-warning: #f0ad4e !default; -$brand-danger: #CD7674 !default; +$brand-danger: #C15453 !default; -$icon-font-path: "../fonts/bootstrap/" !default; +$icon-font-path: "../fonts/bootstrap/" !default; \ No newline at end of file diff --git a/src/main/webapp/app/resources/styles/sass/views/admin/_view.scss b/src/main/webapp/app/resources/styles/sass/views/admin/_view.scss index ce371266c2..2cb0b007be 100644 --- a/src/main/webapp/app/resources/styles/sass/views/admin/_view.scss +++ b/src/main/webapp/app/resources/styles/sass/views/admin/_view.scss @@ -212,3 +212,6 @@ div .item-view div.row h1 span.spinning { color: #555; } +.list-view-table-controls { + margin-bottom: 5px; +} diff --git a/src/main/webapp/app/resources/styles/sass/views/submission/_studentSubmission.scss b/src/main/webapp/app/resources/styles/sass/views/submission/_studentSubmission.scss index 24323c2d6a..30e3dbd11a 100644 --- a/src/main/webapp/app/resources/styles/sass/views/submission/_studentSubmission.scss +++ b/src/main/webapp/app/resources/styles/sass/views/submission/_studentSubmission.scss @@ -70,7 +70,7 @@ padding: 10px 15px; border-radius: 4px; width: 100%; - color: white; + color: $button_text_color_off; background: $button_main_color_off; background: -moz-linear-gradient linear-gradient(to right, $button_main_color_off, $button_highlight_color_off); background: -webkit-linear-gradient(to right, $button_main_color_off, $button_highlight_color_off); @@ -82,6 +82,7 @@ .submission-workflow-step-navigation a .invisible-button:focus, .submission-workflow-step-navigation.active a .invisible-button { cursor: pointer; + color: $button_text_color_on; background: rgb(31, 58, 71); background: -moz-linear-gradient linear-gradient(to right, $button_main_color_on, $button_highlight_color_on); background: -webkit-linear-gradient(to right, $button_main_color_on, $button_highlight_color_on); diff --git a/src/main/webapp/app/resources/styles/sass/views/submission/_submissionAdvisor.scss b/src/main/webapp/app/resources/styles/sass/views/submission/_submissionAdvisor.scss index f207cf02fc..7f1b4734da 100644 --- a/src/main/webapp/app/resources/styles/sass/views/submission/_submissionAdvisor.scss +++ b/src/main/webapp/app/resources/styles/sass/views/submission/_submissionAdvisor.scss @@ -6,3 +6,9 @@ margin-left: 0; margin-right: 0; } + +.file-link { + color: #337ab7; + cursor: pointer; + text-decoration: none; +} diff --git a/src/main/webapp/app/resources/styles/sass/views/submission/_submissionDialog.scss b/src/main/webapp/app/resources/styles/sass/views/submission/_submissionDialog.scss index 50236b8309..24589b3d8b 100644 --- a/src/main/webapp/app/resources/styles/sass/views/submission/_submissionDialog.scss +++ b/src/main/webapp/app/resources/styles/sass/views/submission/_submissionDialog.scss @@ -18,7 +18,7 @@ submissiondialog div.dialog { } @media (min-width: 1540px) { submissiondialog div.dialog { - box-shadow: 3px 3px 10px -6px #000000; + box-shadow: 3px 3px 10px -6px #757575; } } diff --git a/src/main/webapp/app/views/admin/list.html b/src/main/webapp/app/views/admin/list.html index 9841a38494..fcbdd573f3 100644 --- a/src/main/webapp/app/views/admin/list.html +++ b/src/main/webapp/app/views/admin/list.html @@ -1,12 +1,20 @@
- +

List ETDs


- Customize view + +
+ + +
diff --git a/src/main/webapp/app/views/admin/settings/application/lookAndFeel.html b/src/main/webapp/app/views/admin/settings/application/lookAndFeel.html index 3fcc883e57..18b0f06654 100644 --- a/src/main/webapp/app/views/admin/settings/application/lookAndFeel.html +++ b/src/main/webapp/app/views/admin/settings/application/lookAndFeel.html @@ -1,20 +1,56 @@ -
+
-
-

Background -
- -
-

+
+
Text + +
-
+
Main Color + + + +
+ +
+ # + + + + + +
+
+
+ +
+
Header/Footer + +
+
+ +
+
+
+ Background Main Color @@ -44,10 +80,9 @@

Background

-
- Highlight Color + Background Highlight Color @@ -55,7 +90,6 @@

Background

-
# Background ng-change="updateHexValue('lookAndFeel','background_highlight_color')"/> +
+
+
+
+ Header Text Color + + + +
+
+ # + + + + + +
+
+
+
+ Footer Text Color + + +
+
+ # + + + + + +
-
- -
-

Submission Step Button (On) -
- -
-

+
+
Submission Step Button (On) + +
-
+
- Main Color + Background Main Color @@ -102,7 +188,6 @@

Submission Step Button (On)

-
# Submission Step Button (On) ng-change="updateHexValue('lookAndFeel','button_main_color_on')"/> -
-
-
- Highlight Color + Background Highlight Color @@ -156,28 +238,53 @@

Submission Step Button (On) ng-change="updateHexValue('lookAndFeel','button_highlight_color_on')"/> +

+
+
+
+ Text Color + + + +
+ +
+ # + + + + +
-
- -
-

Submission Step Button (Off) -
- -
-

+
+
Submission Step Button (Off) + +
-
+
- Main Color + Background Main Color @@ -212,7 +319,7 @@

Submission Step Button (Off)
- Highlight Color + Background Highlight Color @@ -240,18 +347,177 @@

Submission Step Button (Off) ng-change="updateHexValue('lookAndFeel','button_highlight_color_off')"/> +

+
+
+
+ Text Color + + + +
+ +
+ # + + + + + +
+
+
+

+ +
+
Admin Navigation Tab + +
+
+ +
+
+
+ Background Main Color + + + +
+ +
+ # + + + + + + +
+ +
+ + +
+
+ Background Selected Color + + + +
+ + +
+ # + + + + +
+
+
+
+ Main Text Color + + +
+
+ # + + + + + +
+
+
+ Selected Text Color + + + +
+
+ # + + + + + +
+
-
-
@@ -259,11 +525,9 @@

Submission Step Button (Off)
-

Logos -
- -
-

+
Logos + +
diff --git a/src/main/webapp/app/views/directives/reviewSubmissionFields.html b/src/main/webapp/app/views/directives/reviewSubmissionFields.html index 8f3ffd04a8..04aed31c5d 100644 --- a/src/main/webapp/app/views/directives/reviewSubmissionFields.html +++ b/src/main/webapp/app/views/directives/reviewSubmissionFields.html @@ -21,7 +21,7 @@

{{fv.contacts[0]}} - {{fv.fileInfo.name}} + {{fv.fileInfo.name}} diff --git a/src/main/webapp/app/views/directives/tooltip.html b/src/main/webapp/app/views/directives/tooltip.html index 42d622161f..0c9894655c 100644 --- a/src/main/webapp/app/views/directives/tooltip.html +++ b/src/main/webapp/app/views/directives/tooltip.html @@ -1 +1,14 @@ - + diff --git a/src/main/webapp/app/views/inputtype/input-conditional_textarea.html b/src/main/webapp/app/views/inputtype/input-conditional_textarea.html index 39936e1076..e894a13866 100644 --- a/src/main/webapp/app/views/inputtype/input-conditional_textarea.html +++ b/src/main/webapp/app/views/inputtype/input-conditional_textarea.html @@ -20,7 +20,7 @@ ng-disabled="fieldValue.updating" - +

diff --git a/src/main/webapp/app/views/inputtype/input-contact.html b/src/main/webapp/app/views/inputtype/input-contact.html index 73f3770cfd..80591e9b4f 100644 --- a/src/main/webapp/app/views/inputtype/input-contact.html +++ b/src/main/webapp/app/views/inputtype/input-contact.html @@ -15,7 +15,7 @@ - +
@@ -32,7 +32,7 @@ typeahead-no-results="noResults" typeahead-on-select="saveWithCV(fieldValue, $item)"/> - +

\ No newline at end of file diff --git a/src/main/webapp/app/views/inputtype/input-contact_select.html b/src/main/webapp/app/views/inputtype/input-contact_select.html index 530bdde75c..5bedf8b1dc 100644 --- a/src/main/webapp/app/views/inputtype/input-contact_select.html +++ b/src/main/webapp/app/views/inputtype/input-contact_select.html @@ -17,7 +17,7 @@
@@ -29,7 +29,7 @@ placeholder="{{fieldValue.contacts[0]}}" disabled> - +
diff --git a/src/main/webapp/app/views/inputtype/input-date.html b/src/main/webapp/app/views/inputtype/input-date.html index cdc57d8ba8..95bdf2666a 100644 --- a/src/main/webapp/app/views/inputtype/input-date.html +++ b/src/main/webapp/app/views/inputtype/input-date.html @@ -1,6 +1,6 @@
- - + + - + -
\ No newline at end of file +
diff --git a/src/main/webapp/app/views/inputtype/input-degreedate.html b/src/main/webapp/app/views/inputtype/input-degreedate.html index 8a4ec77477..1212f98447 100644 --- a/src/main/webapp/app/views/inputtype/input-degreedate.html +++ b/src/main/webapp/app/views/inputtype/input-degreedate.html @@ -1,23 +1,24 @@
- - - - - - -
\ No newline at end of file + + + + + + +
diff --git a/src/main/webapp/app/views/inputtype/input-email.html b/src/main/webapp/app/views/inputtype/input-email.html index 90546f8e1a..129b4c89e2 100644 --- a/src/main/webapp/app/views/inputtype/input-email.html +++ b/src/main/webapp/app/views/inputtype/input-email.html @@ -8,7 +8,7 @@ uib-typeahead="word.name as word.name for word in profile.controlledVocabulary.dictionary | filter: { name: $viewValue }" typeahead-on-select="saveWithCV(fieldValue, $item)"/> - +
\ No newline at end of file diff --git a/src/main/webapp/app/views/inputtype/input-file.html b/src/main/webapp/app/views/inputtype/input-file.html index 818a61ecfb..cf4f682064 100644 --- a/src/main/webapp/app/views/inputtype/input-file.html +++ b/src/main/webapp/app/views/inputtype/input-file.html @@ -2,7 +2,7 @@
- + @@ -41,7 +41,7 @@
- + diff --git a/src/main/webapp/app/views/inputtype/input-license.html b/src/main/webapp/app/views/inputtype/input-license.html index 83a370db21..dbb0538043 100644 --- a/src/main/webapp/app/views/inputtype/input-license.html +++ b/src/main/webapp/app/views/inputtype/input-license.html @@ -1,8 +1,9 @@
-
+
\ No newline at end of file diff --git a/src/main/webapp/app/views/inputtype/input-proquest.html b/src/main/webapp/app/views/inputtype/input-proquest.html index 4e28a16aa0..e306f47f28 100644 --- a/src/main/webapp/app/views/inputtype/input-proquest.html +++ b/src/main/webapp/app/views/inputtype/input-proquest.html @@ -1,8 +1,9 @@
-
+
-
\ No newline at end of file +
diff --git a/src/main/webapp/app/views/inputtype/input-radio.html b/src/main/webapp/app/views/inputtype/input-radio.html index f5e72696d6..1175cb6be9 100644 --- a/src/main/webapp/app/views/inputtype/input-radio.html +++ b/src/main/webapp/app/views/inputtype/input-radio.html @@ -14,7 +14,7 @@
{{word.name}}
{{word.definition}}
- +
diff --git a/src/main/webapp/app/views/inputtype/input-select.html b/src/main/webapp/app/views/inputtype/input-select.html index 3f62e56212..0922c3ded1 100644 --- a/src/main/webapp/app/views/inputtype/input-select.html +++ b/src/main/webapp/app/views/inputtype/input-select.html @@ -17,7 +17,7 @@
diff --git a/src/main/webapp/app/views/inputtype/input-tel.html b/src/main/webapp/app/views/inputtype/input-tel.html index 51d7307f0e..43e25a21a0 100644 --- a/src/main/webapp/app/views/inputtype/input-tel.html +++ b/src/main/webapp/app/views/inputtype/input-tel.html @@ -9,7 +9,7 @@ uib-typeahead="word.name as word.name for word in profile.controlledVocabulary.dictionary | filter: { name: $viewValue }" typeahead-on-select="saveWithCV(fieldValue, $item)"/> - +
\ No newline at end of file diff --git a/src/main/webapp/app/views/inputtype/input-text.html b/src/main/webapp/app/views/inputtype/input-text.html index 9b89f41a73..bef4750052 100644 --- a/src/main/webapp/app/views/inputtype/input-text.html +++ b/src/main/webapp/app/views/inputtype/input-text.html @@ -9,7 +9,7 @@ uib-typeahead="word.name as word.name for word in profile.controlledVocabulary.dictionary | filter: { name: $viewValue } | orderBy:'name'" typeahead-on-select="saveWithCV(fieldValue, $item)"/> - +
diff --git a/src/main/webapp/app/views/inputtype/input-textarea.html b/src/main/webapp/app/views/inputtype/input-textarea.html index 30e1465786..a027a6d4ea 100644 --- a/src/main/webapp/app/views/inputtype/input-textarea.html +++ b/src/main/webapp/app/views/inputtype/input-textarea.html @@ -4,10 +4,9 @@ ng-required="!profile.optional" ng-model="fieldValue.value" ng-blur="save(fieldValue)" - ng-disabled="fieldValue.updating" - + ng-disabled="fieldValue.updating"> - +
\ No newline at end of file diff --git a/src/main/webapp/app/views/inputtype/input-url.html b/src/main/webapp/app/views/inputtype/input-url.html index c4cabf5dc8..8672cd4932 100644 --- a/src/main/webapp/app/views/inputtype/input-url.html +++ b/src/main/webapp/app/views/inputtype/input-url.html @@ -9,7 +9,7 @@ ng-disabled="fieldValue.updating" uib-typeahead="word.name as word.name for word in profile.controlledVocabulary.dictionary | filter: { name: $viewValue }"/> - +
diff --git a/src/main/webapp/app/views/modals/submissions/customizeFilters.html b/src/main/webapp/app/views/modals/submissions/customizeFilters.html index ab22382999..a2085f034b 100644 --- a/src/main/webapp/app/views/modals/submissions/customizeFilters.html +++ b/src/main/webapp/app/views/modals/submissions/customizeFilters.html @@ -1,39 +1,39 @@ diff --git a/src/main/webapp/app/views/modals/submissions/customizeSubmissionListModal.html b/src/main/webapp/app/views/modals/submissions/customizeSubmissionList.html similarity index 100% rename from src/main/webapp/app/views/modals/submissions/customizeSubmissionListModal.html rename to src/main/webapp/app/views/modals/submissions/customizeSubmissionList.html diff --git a/src/main/webapp/app/views/modals/submissions/removeExistingFilters.html b/src/main/webapp/app/views/modals/submissions/removeExistingFilters.html index ae0ad0b0df..2414858782 100644 --- a/src/main/webapp/app/views/modals/submissions/removeExistingFilters.html +++ b/src/main/webapp/app/views/modals/submissions/removeExistingFilters.html @@ -5,28 +5,24 @@
-
- - - - - - - - - - - - - -
- {{column}} -
- - {{filter.name}}{{filter.columnsFlag}}{{box.getUserById(filter.user).firstName}} {{box.getUserById(filter.user).lastName}} {{filter.publicFlag ? 'public':'private'}} filter
-
-