#42 merge with dev

This commit is contained in:
T-Midnight 2019-04-18 22:34:20 +04:00
commit 416ad7eeda
33 changed files with 740 additions and 272 deletions

View File

@ -22,7 +22,7 @@ checkRun:
checkStyle: checkStyle:
stage: test stage: test
script: ./gradlew check script: ./gradlew check -x test
deploy: deploy:
stage: deploy stage: deploy

View File

@ -1,31 +1,29 @@
## Краткое описание задачи ## Краткое описание задачи
```
Что требуется сделать Что требуется сделать
```
## `Опционально` Список верстаемых страниц ## `Опционально` Список верстаемых страниц
```
Будут затронуты страницы: Будут затронуты страницы:
* page1.html * page1.html
* page2.html * page2.html
* page3.html * page3.html
```
## `Опционально` Список затрагиваемых модулей ## `Опционально` Список затрагиваемых модулей
```
При реализации задачи потребуется также реализовать методы контроллера При реализации задачи потребуется также реализовать методы контроллера
```
## `Опционально` Список реализуемых функций ## `Опционально` Список реализуемых функций
```
После выполнения задачи станет доступным: После выполнения задачи станет доступным:
* просмотр `entity_name` * просмотр `entity_name`
* редактирование `entity_name` * редактирование `entity_name`
* валидация `entity_name` * валидация `entity_name`
```
## `Опционально` Сценарии работы ## `Опционально` Сценарии работы
```
Сценарий просмотра: Сценарий просмотра:
1. Зайти на главную страницу приложения 1. Зайти на главную страницу приложения
2. Перейти в раздел `section_name` 2. Перейти в раздел `section_name`
@ -38,13 +36,11 @@
3. Перейти к списку `entity_name` 3. Перейти к списку `entity_name`
4. Выбрать нужную `entity_name` и нажать на нее 4. Выбрать нужную `entity_name` и нажать на нее
5. Внести нужные правки в поля и сохранить 5. Внести нужные правки в поля и сохранить
```
## Описание конечного результата, дающего возможность проверки выполнения задачи: компоненты проекта, сценарии работы ## Описание конечного результата, дающего возможность проверки выполнения задачи: компоненты проекта, сценарии работы
```
* Сверстаны страницы page1.hml, page2.hml, page3.hml * Сверстаны страницы page1.hml, page2.hml, page3.hml
* Реализован контроллер для обслуживания страниц * Реализован контроллер для обслуживания страниц
* Сохранение в БД еще не реализовано * Сохранение в БД еще не реализовано
* Валидация происходит по полям `field1, field2` * Валидация происходит по полям `field1, field2`
* Сценарий просмотра проверяется при ручном внечении записей в БД * Сценарий просмотра проверяется при ручном внечении записей в БД
```

View File

@ -114,9 +114,6 @@ dependencies {
compile group: 'org.liquibase', name: 'liquibase-core', version: '3.5.3' compile group: 'org.liquibase', name: 'liquibase-core', version: '3.5.3'
compile group: 'com.mattbertolini', name: 'liquibase-slf4j', version: '2.0.0' compile group: 'com.mattbertolini', name: 'liquibase-slf4j', version: '2.0.0'
compile group: 'org.apache.poi', name: 'poi', version: '3.9'
compile group: 'org.apache.poi', name: 'poi-ooxml', version: '3.9'
compile group: 'org.apache.commons', name: 'commons-lang3', version: '3.7' compile group: 'org.apache.commons', name: 'commons-lang3', version: '3.7'
compile group: 'org.webjars', name: 'bootstrap', version: '4.1.0' compile group: 'org.webjars', name: 'bootstrap', version: '4.1.0'
@ -125,8 +122,10 @@ dependencies {
compile group: 'org.webjars.npm', name: 'jquery.easing', version: '1.4.1' compile group: 'org.webjars.npm', name: 'jquery.easing', version: '1.4.1'
compile group: 'org.webjars', name: 'font-awesome', version: '4.7.0' compile group: 'org.webjars', name: 'font-awesome', version: '4.7.0'
compile group: 'io.springfox', name: 'springfox-swagger2', version: '2.5.0' compile group: 'io.springfox', name: 'springfox-swagger2', version: '2.6.0'
compile group: 'io.springfox', name: 'springfox-swagger-ui', version: '2.5.0' compile group: 'io.springfox', name: 'springfox-swagger-ui', version: '2.6.0'
testCompile group: 'org.springframework.boot', name: 'spring-boot-starter-test' testCompile group: 'org.springframework.boot', name: 'spring-boot-starter-test'
testCompile group: 'org.seleniumhq.selenium', name: 'selenium-java', version: '3.3.1'
} }

View File

@ -1,7 +0,0 @@
package ru.ulstu.core.error;
public class XlsLoadException extends Exception {
public XlsLoadException(String s) {
super(s);
}
}

View File

@ -1,7 +0,0 @@
package ru.ulstu.core.error;
public class XlsParseException extends Exception {
public XlsParseException(String s) {
super(s);
}
}

View File

@ -1,31 +0,0 @@
package ru.ulstu.core.service;
import org.springframework.stereotype.Service;
import ru.ulstu.core.model.TreeDto;
import ru.ulstu.core.model.TreeEntity;
import java.util.List;
import java.util.function.Predicate;
@Service
public class TreeService<T extends TreeEntity> {
public TreeDto getTree(String rootName, List<T> rootItems) {
return addChildNode(new TreeDto(rootName), rootItems, element -> true);
}
public TreeDto getTree(String rootName, List<T> rootItems, Predicate<T> filterPredicate) {
return addChildNode(new TreeDto(rootName), rootItems, filterPredicate);
}
private TreeDto addChildNode(TreeDto currentRoot, List<T> children, Predicate<T> filterPredicate) {
if (children != null) {
children.stream()
.filter(filterPredicate)
.forEach(item -> {
TreeDto newNode = new TreeDto(item);
currentRoot.getChildren().add(addChildNode(newNode, item.getChildren(), filterPredicate));
});
}
return currentRoot;
}
}

View File

@ -1,207 +0,0 @@
package ru.ulstu.core.service;
import org.apache.poi.POIXMLDocument;
import org.apache.poi.hssf.usermodel.HSSFWorkbook;
import org.apache.poi.poifs.filesystem.POIFSFileSystem;
import org.apache.poi.ss.usermodel.*;
import org.apache.poi.ss.util.CellRangeAddress;
import org.apache.poi.ss.util.RegionUtil;
import org.apache.poi.xssf.usermodel.XSSFWorkbook;
import ru.ulstu.core.error.XlsParseException;
import java.io.*;
public class XlsDocumentBuilder {
private static final int DEFAULT_SHEET_NUM = 0;
private File documentFile;
private Workbook workBook;
private Sheet currentSheet;
/**
* Constructor for reading and writing data from/to *.[xls|xlsx] document
*
* @param file contains existing document for reading or new document to save
*/
public XlsDocumentBuilder(File file) throws IOException, XlsParseException {
this.documentFile = file;
if (file.exists()) {
workBook = getWorkBook(file);
currentSheet = workBook.getSheetAt(DEFAULT_SHEET_NUM);
} else {
workBook = new XSSFWorkbook();
currentSheet = workBook.createSheet();
}
}
private Workbook getWorkBook(File file) throws XlsParseException, IOException {
InputStream inputStream = new PushbackInputStream(new FileInputStream(file), 4096);
if (isXlsx(inputStream)) {
return new XSSFWorkbook(inputStream);
} else if (isExcel(inputStream)) {
return new HSSFWorkbook(inputStream);
}
throw new XlsParseException("Wrong document format");
}
/**
* Change active sheet to write or read data
*
* @param index index of sheet to activate
*/
public XlsDocumentBuilder setActiveSheet(int index) {
workBook.setActiveSheet(index);
currentSheet = workBook.getSheetAt(index);
return this;
}
/**
* Create new sheet in document and set it active
*
* @param sheetName
*/
public XlsDocumentBuilder insertNewSheet(String sheetName) {
currentSheet = workBook.createSheet(sheetName);
workBook.setActiveSheet(getSheetCount() - 1);
return this;
}
public XlsDocumentBuilder insertNewSheet(String sheetName, int order) {
insertNewSheet(sheetName);
workBook.setSheetOrder(sheetName, order);
return this;
}
/**
* Returns number of sheet in document
*
* @return sheets count
*/
public int getSheetCount() {
return workBook.getNumberOfSheets();
}
/**
* Returns number of rows in sheet
*
* @return rows count
*/
public int getRowCount() {
return currentSheet.getLastRowNum();
}
/**
* Returns number of columns in sheet
*
* @return columns count
*/
public int getColumnCount() {
Row row = currentSheet.getRow(getRowCount());
if (row == null) {
return 0;
}
return row.getLastCellNum() - 1;
}
/**
* Returns converted to string representation of cell value
*
* @param rowIndex row index of current sheet
* @param colIndex column index of current sheet
* @return string value of cell
*/
public String getCellAsString(int rowIndex, int colIndex) {
Cell cell = currentSheet.getRow(rowIndex).getCell(colIndex);
cell.setCellType(Cell.CELL_TYPE_STRING);
return cell.getStringCellValue();
}
/**
* Sets string cell value
*
* @param rowIndex row index of current sheet
* @param colIndex column index of current sheet
*/
public XlsDocumentBuilder setCellValue(int rowIndex, int colIndex, String value) {
if (currentSheet.getRow(rowIndex) == null) {
currentSheet.createRow(rowIndex);
}
if (currentSheet.getRow(rowIndex).getCell(colIndex) == null) {
currentSheet.getRow(rowIndex).createCell(colIndex);
}
Cell cell = currentSheet.getRow(rowIndex).getCell(colIndex);
cell.setCellValue(value);
setColumnAutosize(colIndex, colIndex);
return this;
}
public XlsDocumentBuilder setCellValue(int rowIndex, int colIndex, int value) {
return setCellValue(rowIndex, colIndex, String.valueOf(value));
}
public XlsDocumentBuilder setCellValue(int rowIndex, int colIndex, long value) {
return setCellValue(rowIndex, colIndex, String.valueOf(value));
}
/**
* Save current file
*/
public XlsDocumentBuilder save() throws IOException {
OutputStream out = new FileOutputStream(documentFile);
workBook.write(out);
return this;
}
private boolean isExcel(InputStream i) throws IOException {
return (POIFSFileSystem.hasPOIFSHeader(i) || POIXMLDocument.hasOOXMLHeader(i));
}
private boolean isXlsx(InputStream i) throws IOException {
return POIXMLDocument.hasOOXMLHeader(i);
}
public int getActiveSheetIndex() {
return workBook.getActiveSheetIndex();
}
public XlsDocumentBuilder mergeCells(int rowFrom, int rowTo, int colFrom, int colTo) {
currentSheet.addMergedRegion(new CellRangeAddress(rowFrom, rowTo, colFrom, colTo));
return this;
}
public void setRegionBorderWithMedium(int rowFrom, int rowTo, int colFrom, int colTo) {
for (int row = rowFrom; row < rowTo; row++) {
for (int col = colFrom; col <= colTo; col++) {
CellRangeAddress cellRangeAddress = new CellRangeAddress(row, row, col, col);
RegionUtil.setBorderBottom(CellStyle.BORDER_THIN, cellRangeAddress, currentSheet, workBook);
RegionUtil.setBorderLeft(CellStyle.BORDER_THIN, cellRangeAddress, currentSheet, workBook);
RegionUtil.setBorderRight(CellStyle.BORDER_THIN, cellRangeAddress, currentSheet, workBook);
RegionUtil.setBorderTop(CellStyle.BORDER_THIN, cellRangeAddress, currentSheet, workBook);
}
}
}
public XlsDocumentBuilder setColumnAutosize(int from, int to) {
for (int col = from; col <= to; col++) {
currentSheet.autoSizeColumn(col, true);
}
return this;
}
public XlsDocumentBuilder setRowAutosize(int from, int to) {
CellStyle style = workBook.createCellStyle();
style.setWrapText(true);
for (int row = from; row <= to; row++) {
for (int col = 0; col <= currentSheet.getRow(row).getLastCellNum(); col++) {
if (currentSheet.getRow(row).getCell(col) != null) {
currentSheet.getRow(row).getCell(col).setCellStyle(style);
}
}
}
return this;
}
public XlsDocumentBuilder deleteSheet(int index) {
workBook.removeSheetAt(index);
return this;
}
}

View File

@ -0,0 +1,49 @@
package ru.ulstu.project.controller;
import org.springframework.stereotype.Controller;
import org.springframework.ui.ModelMap;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.ModelAttribute;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestParam;
import ru.ulstu.project.model.Project;
import ru.ulstu.project.model.ProjectDto;
import ru.ulstu.project.service.ProjectService;
import springfox.documentation.annotations.ApiIgnore;
import java.util.List;
@Controller()
@RequestMapping(value = "/projects")
@ApiIgnore
public class ProjectController {
private final ProjectService projectService;
public ProjectController(ProjectService projectService) {
this.projectService = projectService;
}
@GetMapping("/dashboard")
public void getDashboard(ModelMap modelMap) {
modelMap.put("projects", projectService.findAllDto());
}
@GetMapping("/projects")
public void getProjects(ModelMap modelMap) {
modelMap.put("projects", projectService.findAllDto());
}
@GetMapping("/project")
public void getProject(ModelMap modelMap, @RequestParam(value = "id") Integer id) {
if (id != null && id > 0) {
modelMap.put("projectDto", projectService.findOneDto(id));
} else {
modelMap.put("projectDto", new ProjectDto());
}
}
@ModelAttribute("allStatuses")
public List<Project.ProjectStatus> getProjectStatuses() {
return projectService.getProjectStatuses();
}
}

View File

@ -3,24 +3,60 @@ package ru.ulstu.project.model;
import org.hibernate.validator.constraints.NotBlank; import org.hibernate.validator.constraints.NotBlank;
import ru.ulstu.core.model.BaseEntity; import ru.ulstu.core.model.BaseEntity;
import ru.ulstu.deadline.model.Deadline; import ru.ulstu.deadline.model.Deadline;
import ru.ulstu.grant.model.Grant;
import javax.persistence.CascadeType; import javax.persistence.CascadeType;
import javax.persistence.Entity; import javax.persistence.Entity;
import javax.persistence.EnumType;
import javax.persistence.Enumerated;
import javax.persistence.JoinColumn; import javax.persistence.JoinColumn;
import javax.persistence.ManyToOne;
import javax.persistence.OneToMany; import javax.persistence.OneToMany;
import javax.validation.constraints.NotNull;
import java.util.ArrayList; import java.util.ArrayList;
import java.util.List; import java.util.List;
@Entity @Entity
public class Project extends BaseEntity { public class Project extends BaseEntity {
public enum ProjectStatus {
APPLICATION("Заявка"),
ON_COMPETITION("Отправлен на конкурс"),
SUCCESSFUL_PASSAGE("Успешное прохождение"),
IN_WORK("В работе"),
COMPLETED("Завершен"),
FAILED("Провалены сроки");
private String statusName;
ProjectStatus(String statusName) {
this.statusName = statusName;
}
public String getStatusName() {
return statusName;
}
}
@NotBlank @NotBlank
private String title; private String title;
@Enumerated(value = EnumType.STRING)
private ProjectStatus status = ProjectStatus.APPLICATION;
@NotNull
private String description;
@OneToMany(cascade = CascadeType.ALL) @OneToMany(cascade = CascadeType.ALL)
@JoinColumn(name = "project_id") @JoinColumn(name = "project_id")
private List<Deadline> deadlines = new ArrayList<>(); private List<Deadline> deadlines = new ArrayList<>();
@ManyToOne(cascade = CascadeType.ALL)
@JoinColumn(name = "grant_id")
private Grant grant;
@NotNull
private String repository;
public String getTitle() { public String getTitle() {
return title; return title;
} }
@ -29,6 +65,38 @@ public class Project extends BaseEntity {
this.title = title; this.title = title;
} }
public ProjectStatus getStatus() {
return status;
}
public void setStatus(ProjectStatus status) {
this.status = status;
}
public String getDescription() {
return description;
}
public void setDescription(String description) {
this.description = description;
}
public Grant getGrant() {
return grant;
}
public void setGrant(Grant grant) {
this.grant = grant;
}
public String getRepository() {
return repository;
}
public void setRepository(String repository) {
this.repository = repository;
}
public List<Deadline> getDeadlines() { public List<Deadline> getDeadlines() {
return deadlines; return deadlines;
} }

View File

@ -4,6 +4,7 @@ import com.fasterxml.jackson.annotation.JsonCreator;
import com.fasterxml.jackson.annotation.JsonProperty; import com.fasterxml.jackson.annotation.JsonProperty;
import org.hibernate.validator.constraints.NotEmpty; import org.hibernate.validator.constraints.NotEmpty;
import ru.ulstu.deadline.model.Deadline; import ru.ulstu.deadline.model.Deadline;
import ru.ulstu.grant.model.GrantDto;
import java.util.ArrayList; import java.util.ArrayList;
import java.util.List; import java.util.List;
@ -13,8 +14,11 @@ public class ProjectDto {
@NotEmpty @NotEmpty
private String title; private String title;
private Project.ProjectStatus status;
private String description;
private List<Deadline> deadlines = new ArrayList<>(); private List<Deadline> deadlines = new ArrayList<>();
private GrantDto grant;
private String repository;
public ProjectDto() { public ProjectDto() {
} }
@ -26,9 +30,17 @@ public class ProjectDto {
@JsonCreator @JsonCreator
public ProjectDto(@JsonProperty("id") Integer id, public ProjectDto(@JsonProperty("id") Integer id,
@JsonProperty("title") String title, @JsonProperty("title") String title,
@JsonProperty("status") Project.ProjectStatus status,
@JsonProperty("description") String description,
@JsonProperty("grant") GrantDto grant,
@JsonProperty("repository") String repository,
@JsonProperty("deadlines") List<Deadline> deadlines) { @JsonProperty("deadlines") List<Deadline> deadlines) {
this.id = id; this.id = id;
this.title = title; this.title = title;
this.status = status;
this.description = description;
this.grant = grant;
this.repository = repository;
this.deadlines = deadlines; this.deadlines = deadlines;
} }
@ -36,6 +48,10 @@ public class ProjectDto {
public ProjectDto(Project project) { public ProjectDto(Project project) {
this.id = project.getId(); this.id = project.getId();
this.title = project.getTitle(); this.title = project.getTitle();
this.status = project.getStatus();
this.description = project.getDescription();
this.grant = project.getGrant() == null ? null : new GrantDto(project.getGrant());
this.repository = project.getRepository();
this.deadlines = project.getDeadlines(); this.deadlines = project.getDeadlines();
} }
@ -55,6 +71,38 @@ public class ProjectDto {
this.title = title; this.title = title;
} }
public Project.ProjectStatus getStatus() {
return status;
}
public void setStatus(Project.ProjectStatus status) {
this.status = status;
}
public String getDescription() {
return description;
}
public void setDescription(String description) {
this.description = description;
}
public GrantDto getGrant() {
return grant;
}
public void setGrant(GrantDto grant) {
this.grant = grant;
}
public String getRepository() {
return repository;
}
public void setRepository(String repository) {
this.repository = repository;
}
public List<Deadline> getDeadlines() { public List<Deadline> getDeadlines() {
return deadlines; return deadlines;
} }

View File

@ -2,17 +2,21 @@ package ru.ulstu.project.service;
import org.springframework.stereotype.Service; import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional; import org.springframework.transaction.annotation.Transactional;
import org.thymeleaf.util.StringUtils;
import ru.ulstu.deadline.service.DeadlineService; import ru.ulstu.deadline.service.DeadlineService;
import ru.ulstu.project.model.Project; import ru.ulstu.project.model.Project;
import ru.ulstu.project.model.ProjectDto; import ru.ulstu.project.model.ProjectDto;
import ru.ulstu.project.repository.ProjectRepository; import ru.ulstu.project.repository.ProjectRepository;
import java.util.Arrays;
import java.util.List; import java.util.List;
import static org.springframework.util.ObjectUtils.isEmpty; import static org.springframework.util.ObjectUtils.isEmpty;
import static ru.ulstu.core.util.StreamApiUtils.convert;
@Service @Service
public class ProjectService { public class ProjectService {
private final static int MAX_DISPLAY_SIZE = 40;
private final ProjectRepository projectRepository; private final ProjectRepository projectRepository;
private final DeadlineService deadlineService; private final DeadlineService deadlineService;
@ -27,6 +31,20 @@ public class ProjectService {
return projectRepository.findAll(); return projectRepository.findAll();
} }
public List<ProjectDto> findAllDto() {
List<ProjectDto> projects = convert(findAll(), ProjectDto::new);
projects.forEach(projectDto -> projectDto.setTitle(StringUtils.abbreviate(projectDto.getTitle(), MAX_DISPLAY_SIZE)));
return projects;
}
public ProjectDto findOneDto(Integer id) {
return new ProjectDto(projectRepository.findOne(id));
}
public List<Project.ProjectStatus> getProjectStatuses() {
return Arrays.asList(Project.ProjectStatus.values());
}
@Transactional @Transactional
public Project create(ProjectDto projectDto) { public Project create(ProjectDto projectDto) {
Project newProject = copyFromDto(new Project(), projectDto); Project newProject = copyFromDto(new Project(), projectDto);

View File

@ -0,0 +1,22 @@
<?xml version="1.1" encoding="UTF-8" standalone="no"?>
<databaseChangeLog xmlns="http://www.liquibase.org/xml/ns/dbchangelog"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://www.liquibase.org/xml/ns/dbchangelog http://www.liquibase.org/xml/ns/dbchangelog/dbchangelog-3.1.xsd">
<changeSet author="anton" id="20190418_000000-1">
<addColumn tableName="project">
<column name="status" type="varchar(255)"/>
</addColumn>
<addColumn tableName="project">
<column name="description" type="varchar(255)"/>
</addColumn>
<addColumn tableName="project">
<column name="grant_id" type="integer"/>
</addColumn>
<addForeignKeyConstraint baseTableName="project" baseColumnNames="grant_id"
constraintName="fk_project_grant_id" referencedTableName="grants"
referencedColumnNames="id"/>
<addColumn tableName="project">
<column name="repository" type="varchar(255)"/>
</addColumn>
</changeSet>
</databaseChangeLog>

View File

@ -24,6 +24,7 @@
<include file="db/changelog-20190331_000000-schema.xml"/> <include file="db/changelog-20190331_000000-schema.xml"/>
<include file="db/changelog-20190331_000010-schema.xml"/> <include file="db/changelog-20190331_000010-schema.xml"/>
<include file="db/changelog-20190410_000000-schema.xml"/> <include file="db/changelog-20190410_000000-schema.xml"/>
<include file="db/changelog-20190418_000000-schema.xml"/>
<include file="db/common/changelog-20190312_130000-schema.xml"/> <include file="db/common/changelog-20190312_130000-schema.xml"/>
<include file="db/changelog-20190402_000000-schema.xml"/> <include file="db/changelog-20190402_000000-schema.xml"/>
<include file="db/changelog-20190404_000000-schema.xml"/> <include file="db/changelog-20190404_000000-schema.xml"/>

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

View File

@ -40,7 +40,7 @@
</div> </div>
</div> </div>
<div class="col-md-4 col-sm-6 portfolio-item"> <div class="col-md-4 col-sm-6 portfolio-item">
<a class="portfolio-link" data-toggle="modal" href="/projects"> <a class="portfolio-link" href="./projects/dashboard">
<div class="portfolio-hover"> <div class="portfolio-hover">
<div class="portfolio-hover-content"> <div class="portfolio-hover-content">
<i class="fa fa-arrow-right fa-3x"></i> <i class="fa fa-arrow-right fa-3x"></i>

View File

@ -0,0 +1,24 @@
<!DOCTYPE html>
<html lang="en"
xmlns:layout="http://www.ultraq.net.nz/thymeleaf/layout"
layout:decorator="default" xmlns:th="http://www.w3.org/1999/xhtml">
<head>
</head>
<body>
<div class="container" layout:fragment="content">
<section id="services">
<div class="container">
<div class="col-lg-12 text-center">
<h2 class="section-heading text-uppercase">Проекты</h2>
<div th:replace="projects/fragments/projectNavigationFragment"/>
</div>
<div class="row justify-content-center" id="dashboard">
<th:block>
<div/>
</th:block>
</div>
</div>
</section>
</div>
</body>
</html>

View File

@ -0,0 +1,18 @@
<!DOCTYPE HTML>
<html xmlns:th="http://www.thymeleaf.org">
<head th:fragment="headerfiles">
<meta charset="UTF-8"/>
</head>
<body>
<div th:fragment="projectLine (project)" class="row text-left project-row" style="background-color: white;">
<div class="col">
<span th:replace="projects/fragments/projectStatusFragment :: projectStatus(projectStatus=${project.status})"/>
<a th:href="@{'project?id='+${project.id}}">
<span class="h6" th:text="${project.title}"/>
<span class="text-muted" th:text="${project.description}"/>
</a>
<input class="id-class" type="hidden" th:value="${project.id}"/>
</div>
</div>
</body>
</html>

View File

@ -0,0 +1,26 @@
<!DOCTYPE HTML>
<html xmlns:th="http://www.thymeleaf.org">
<head th:fragment="headerfiles">
<meta charset="UTF-8"/>
</head>
<body>
<div class="row justify-content-center">
<div class="col-12 col-sm-12 col-md-12 col-lg-4 col-xl-3">
<a href="./projects" class="btn btn-light toolbar-button"><i class="fa fa-list-alt"></i>
Список</a>
</div>
<div class="col-12 col-sm-12 col-md-12 col-lg-4 col-xl-3">
<a href="./dashboard" class="btn btn-light toolbar-button"><i class="fa fa-newspaper-o"
aria-hidden="true"></i> Панель
управления</a>
</div>
<div class="col-12 col-sm-12 col-md-12 col-lg-4 col-xl-3">
<a href="./project?id=0" class="btn btn-light toolbar-button"><i class="fa fa-plus-circle"
aria-hidden="true"></i>
Добавить проект</a>
</div>
</div>
</body>
</html>

View File

@ -0,0 +1,31 @@
<!DOCTYPE HTML>
<html xmlns:th="http://www.thymeleaf.org">
<head th:fragment="headerfiles">
<meta charset="UTF-8"/>
</head>
<body>
<span th:fragment="paperStatus (paperStatus)" class="fa-stack fa-1x">
<th:block th:switch="${projectStatus.name()}">
<div th:case="'APPLICATION'">
<i class="fa fa-circle fa-stack-2x text-draft"></i>
</div>
<div th:case="'ON_COMPETITION'">
<i class="fa fa-circle fa-stack-2x text-review"></i>
</div>
<div th:case="'SUCCESSFUL_PASSAGE'">
<i class="fa fa-circle fa-stack-2x text-accepted"></i>
</div>
<div th:case="'IN_WORK'">
<i class="fa fa-circle fa-stack-2x text-primary"></i>
</div>
<div th:case="'COMPLETED'">
<i class="fa fa-circle fa-stack-2x text-success"></i>
</div>
<div th:case="'FAILED'">
<i class="fa fa-circle fa-stack-2x text-failed"></i>
</div>
</th:block>
<i class="fa fa-file-text-o fa-stack-1x fa-inverse"></i>
</span>
</body>
</html>

View File

@ -0,0 +1,141 @@
<!DOCTYPE html>
<html lang="en"
xmlns:layout="http://www.ultraq.net.nz/thymeleaf/layout"
layout:decorator="default" xmlns:th="http://www.w3.org/1999/xhtml" xmlns="http://www.w3.org/1999/html">
<head>
</head>
<body>
<div class="container" layout:fragment="content">
<section id="paper">
<div class="container">
<div class="row">
<div class="col-lg-12 text-center">
<h2 class="section-heading text-uppercase">Редактирование проекта</h2>
<div th:replace="projects/fragments/projectNavigationFragment"/>
</div>
</div>
<div class="row">
<div class="col-lg-12">
<form id="project-form" method="post" th:action="@{'/projects/project?id='+ *{id == null ? '' : id} + ''}"
th:object="${projectDto}">
<div class="row">
<div class="col-md-6 col-sm-12">
<input type="hidden" name="id" th:field="*{id}"/>
<div class="form-group">
<label for="title">Название:</label>
<input class="form-control" id="title" type="text"
placeholder="Название проекта"
th:field="*{title}"/>
<p th:if="${#fields.hasErrors('title')}" th:errors="*{title}"
class="alert alert-danger">Incorrect title</p>
<p class="help-block text-danger"></p>
</div>
<div class="form-group">
<label for="status">Статус:</label>
<select class="form-control" th:field="*{status}" id="status">
<option th:each="status : ${allStatuses}" th:value="${status}"
th:text="${status.statusName}">Status
</option>
</select>
</div>
<div class="form-group">
<label for="description">Описание:</label>
<textarea class="form-control" rows="3" id="description"
th:field="*{description}"></textarea>
</div>
<div class="form-group">
<label for="createGrant">Добавить грант:</label>
<div th:if="*{grant} == null">
<input type="submit" id="createGrant" name="createGrant" class="btn btn-primary"
value="Добавить грант"/>
</div>
<input type = "hidden" th:field="*{grant.id}"/>
</div>
<div class="form-group">
<label for="repository">Ссылка на репозиторий:</label>
<input class="form-control" id="repository" type="text"
placeholder="http://"
th:field="*{repository}"/>
<p th:if="${#fields.hasErrors('repository')}" th:errors="*{repository}"
class="alert alert-danger">Incorrect repository link</p>
<p class="help-block text-danger"></p>
</div>
<div class="form-group">
<label>Дедлайны показателей:</label>
<div class="row">
<input type="hidden"/>
<div class="col-6">
<input type="date" class="form-control" name="deadline"/>
</div>
<div class="col-4">
<input class="form-control" type="text" placeholder="Описание"/>
</div>
<div class="col-2">
<a class="btn btn-danger float-right"><span
aria-hidden="true"><i class="fa fa-times"/></span>
</a>
</div>
</div>
<p th:if="${#fields.hasErrors('deadlines')}" th:errors="*{deadlines}"
class="alert alert-danger">Incorrect title</p>
</div>
<div class="form-group">
<input type="submit" id="addDeadline" name="addDeadline" class="btn btn-primary" value="Добавить
дедлайн"/>
</div>
<div class="form-group">
<label for="loader">Загрузить файл:</label>
<div id="loader">
</div>
</div>
</div>
<div class="clearfix"></div>
<div class="col-lg-12">
<div class="form-group">
<button id="sendMessageButton" name="save" class="btn btn-success text-uppercase"
type="submit">
Сохранить
</button>
<button id="cancelButton" class="btn btn-default text-uppercase" href="/projects/projects">
Отмена
</button>
</div>
</div>
</div>
</form>
</div>
</div>
</div>
</section>
<script type="text/javascript" src="/js/file-loader.js"></script>
<script>
/*<![CDATA[*/
$(document).ready(function () {
new FileLoader({
div: "loader",
url: urlFileUpload,
maxSize: 2,
extensions: ["doc", "docx", "xls", "jpg", "png", "pdf", "txt"],
callback: function (response) {
showFeedbackMessage("Файл успешно загружен");
console.debug(response);
}
});
$('.selectpicker').selectpicker();
});
/*]]>*/
</script>
</div>
</body>
</html>

View File

@ -0,0 +1,33 @@
<!DOCTYPE html>
<html lang="en"
xmlns:layout="http://www.ultraq.net.nz/thymeleaf/layout"
layout:decorator="default" xmlns:th="">
<head>
</head>
<body>
<div class="container" layout:fragment="content">
<form id="projects-form" method="post" th:action="@{'/projects/projects'}">
<input th:type="hidden" name="projectDeleteId" id="projectDeleteId"/>
<section id="projects">
<div class="container">
<div class="row" id="project-list">
<div class="col-lg-12 text-center">
<h2 class="section-heading text-uppercase">Проекты</h2>
<div th:replace="projects/fragments/projectNavigationFragment"/>
</div>
</div>
<div class="row">
<div class="col-md-9 col-sm-12">
<th:block th:each="project : ${projects}">
<div th:replace="projects/fragments/projectLineFragment :: projectLine(project=${project})"/>
</th:block>
</div>
<div class="col-md-3 col-sm-12">
</div>
</div>
</div>
</section>
</form>
</div>
</body>
</html>

View File

@ -0,0 +1,52 @@
import com.google.common.collect.ImmutableMap;
import core.PageObject;
import core.TestTemplate;
import org.assertj.core.api.Assertions;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.context.SpringBootTest;
import org.springframework.test.context.junit4.SpringRunner;
import paper.PaperPage;
import paper.PapersDashboardPage;
import paper.PapersPage;
import ru.ulstu.NgTrackerApplication;
import ru.ulstu.configuration.ApplicationProperties;
import java.util.Arrays;
import java.util.List;
import java.util.Map;
@RunWith(SpringRunner.class)
@SpringBootTest(classes = NgTrackerApplication.class, webEnvironment = SpringBootTest.WebEnvironment.DEFINED_PORT)
public class IndexPageTest extends TestTemplate {
private final Map<PageObject, List<String>> navigationHolder = ImmutableMap.of(
new PapersPage(), Arrays.asList("СТАТЬИ", "/papers/papers"),
new PaperPage(), Arrays.asList("РЕДАКТИРОВАНИЕ СТАТЬИ", "/papers/paper?id=0"),
new PapersDashboardPage(), Arrays.asList("СТАТЬИ", "/papers/dashboard")
);
@Autowired
private ApplicationProperties applicationProperties;
@Test
public void testStartApplication() {
getContext().goTo(applicationProperties.getBaseUrl());
Assertions
.assertThat(getContext().getTitle())
.isEqualTo("NG-Tracker");
}
@Test
public void testModulesNavigation() {
navigationHolder.entrySet()
.stream()
.forEach(navigationEntry -> {
getContext().goTo(applicationProperties.getBaseUrl() + navigationEntry.getValue().get(1));
PageObject pageObject = getContext().initPage(navigationEntry.getKey());
Assertions
.assertThat(pageObject.getSubTitle())
.isEqualToIgnoringCase(navigationEntry.getValue().get(0));
});
}
}

View File

@ -0,0 +1,27 @@
package context;
import org.openqa.selenium.chrome.ChromeDriver;
import org.openqa.selenium.chrome.ChromeOptions;
public class ChromeContext extends Context {
private final static String WINDOWS_DRIVER = "chromedriver.exe";
private final static String LINUX_DRIVER = "chromedriver";
private final static String DRIVER_TYPE = "webdriver.chrome.driver";
@Override
protected void createDriver() {
final ChromeOptions chromeOptions = new ChromeOptions();
chromeOptions.addArguments("--headless");
driver = new ChromeDriver(chromeOptions);
}
@Override
protected String getDriverExecutable(boolean isWindows) {
return isWindows ? WINDOWS_DRIVER : LINUX_DRIVER;
}
@Override
protected String getDriverType() {
return DRIVER_TYPE;
}
}

View File

@ -0,0 +1,67 @@
package context;
import core.PageObject;
import org.openqa.selenium.Dimension;
import org.openqa.selenium.WebDriver;
import java.util.concurrent.TimeUnit;
//import org.openqa.selenium.support.PageFactory;
public abstract class Context {
private final static String DRIVER_LOCATION = "drivers/%s";
protected WebDriver driver;
protected WebDriver getDriver() {
if (driver != null) {
return driver;
}
throw new IllegalStateException("WebDriver is not initialized");
}
public void start() {
System.setProperty(getDriverType(), getDriverExecutablePath());
createDriver();
// это плохая инструкция для автотестов, т.к. лучше задавать для конкретного элемента или кейса
getDriver().manage().timeouts().implicitlyWait(10, TimeUnit.SECONDS);
}
public void close() {
if (driver != null) {
driver.quit();
}
}
public void goTo(String url) {
getDriver().get(url);
}
public String getTitle() {
return getDriver().getTitle();
}
public <T extends PageObject> T initPage(T pageObject) {
return (T) pageObject.setDriver(getDriver());
}
protected abstract void createDriver();
protected abstract String getDriverType();
protected abstract String getDriverExecutable(boolean windows);
private String getDriverExecutablePath() {
return Context.class.getClassLoader().getResource(
String.format(DRIVER_LOCATION, getDriverExecutable(isWindows()))).getFile();
}
private boolean isWindows() {
return System.getProperty("os.name").toLowerCase().contains("windows");
}
public void setSize(Dimension dimension) {
driver.manage().window().setSize(dimension);
}
}

View File

@ -0,0 +1,24 @@
package context;
import org.openqa.selenium.firefox.FirefoxDriver;
public class FirefoxContext extends Context {
private final static String WINDOWS_DRIVER = "geckodriver.exe";
private final static String LINUX_DRIVER = "geckodriver";
private final static String DRIVER_TYPE = "webdriver.gecko.driver";
@Override
protected void createDriver() {
driver = new FirefoxDriver();
}
@Override
protected String getDriverExecutable(boolean isWindows) {
return isWindows ? WINDOWS_DRIVER : LINUX_DRIVER;
}
@Override
protected String getDriverType() {
return DRIVER_TYPE;
}
}

View File

@ -0,0 +1,14 @@
package core;
import org.openqa.selenium.WebDriver;
public abstract class PageObject {
protected WebDriver driver;
public abstract String getSubTitle();
public PageObject setDriver(WebDriver driver) {
this.driver = driver;
return this;
}
}

View File

@ -0,0 +1,29 @@
package core;
import context.ChromeContext;
import context.Context;
import org.junit.AfterClass;
import org.junit.BeforeClass;
import org.openqa.selenium.Dimension;
public abstract class TestTemplate {
private static Context context;
public static Context getContext() {
return context;
}
@BeforeClass
public static void setup() {
context = new ChromeContext();
context.start();
context.setSize(new Dimension(1600, 900));
}
@AfterClass
public static void quit() {
if (context != null) {
context.close();
}
}
}

View File

@ -0,0 +1,11 @@
package paper;
import core.PageObject;
import org.openqa.selenium.By;
public class PaperPage extends PageObject {
public String getSubTitle() {
return driver.findElement(By.tagName("h2")).getText();
}
}

View File

@ -0,0 +1,11 @@
package paper;
import core.PageObject;
import org.openqa.selenium.By;
public class PapersDashboardPage extends PageObject {
public String getSubTitle() {
return driver.findElement(By.tagName("h2")).getText();
}
}

View File

@ -0,0 +1,11 @@
package paper;
import core.PageObject;
import org.openqa.selenium.By;
public class PapersPage extends PageObject {
public String getSubTitle() {
return driver.findElement(By.tagName("h2")).getText();
}
}