Skip to content

Commit

Permalink
Merge pull request #109 from 12oprs/add-page-listOfAllUsers
Browse files Browse the repository at this point in the history
#106 Add page list of all users
  • Loading branch information
kacetal committed Sep 6, 2022
2 parents 479503e + 679aab2 commit 6cb195d
Show file tree
Hide file tree
Showing 10 changed files with 248 additions and 2 deletions.
Expand Up @@ -56,6 +56,18 @@ public Workspace removeTypo(final Typo typo) {
return this;
}

public Workspace addAccount(final Account account) {
accounts.add(account);
account.setWorkspace(this);
return this;
}

public Workspace removeAccount(final Account account) {
accounts.remove(account);
account.setWorkspace(null);
return this;
}

@Override
public int hashCode() {
return 31;
Expand Down
Expand Up @@ -4,11 +4,21 @@
import org.springframework.data.jpa.repository.JpaRepository;
import org.springframework.stereotype.Repository;

import io.hexlet.typoreporter.domain.typo.*;
import org.apache.commons.lang3.tuple.Pair;
import org.springframework.data.domain.*;
import org.springframework.data.jpa.repository.*;
import org.springframework.stereotype.Repository;

import java.util.*;

import java.util.Optional;

@Repository
public interface AccountRepository extends JpaRepository<Account, Long> {

Optional<Account> findAccountByEmail(String email);

Page<Account> findPageAccountByWorkspaceName(Pageable pageable, String name);

}
2 changes: 2 additions & 0 deletions src/main/java/io/hexlet/typoreporter/web/Routers.java
Expand Up @@ -21,6 +21,8 @@ public class Routers {

public static final String DEFAULT_SORT_FIELD = "createdDate";

public static final String USERS = "/users";

@NoArgsConstructor(access = AccessLevel.PRIVATE)
public static class Workspace {

Expand Down
3 changes: 3 additions & 0 deletions src/main/java/io/hexlet/typoreporter/web/Templates.java
Expand Up @@ -17,5 +17,8 @@ public class Templates {

static final String WKS_TYPOS_TEMPLATE = WKS_TEMPLATE_DIR + "/wks-typos";

static final String WKS_USERS_TEMPLATE = WKS_TEMPLATE_DIR + "/wks-users";

static final String WKS_UPDATE_TEMPLATE = WKS_TEMPLATE_DIR + "/wks-update";

}
38 changes: 38 additions & 0 deletions src/main/java/io/hexlet/typoreporter/web/WorkspaceController.java
Expand Up @@ -4,6 +4,7 @@
import io.hexlet.typoreporter.service.dto.typo.TypoInfo;
import io.hexlet.typoreporter.service.dto.workspace.CreateWorkspace;
import io.hexlet.typoreporter.web.exception.*;
import io.hexlet.typoreporter.repository.AccountRepository;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.lang3.tuple.Pair;
Expand Down Expand Up @@ -40,6 +41,8 @@ public class WorkspaceController {

private final WorkspaceService workspaceService;

private final AccountRepository accountRepository;

@GetMapping
public String getWorkspaceInfoPage(Model model, @PathVariable String wksName) {
var wksOptional = workspaceService.getWorkspaceInfoByName(wksName);
Expand Down Expand Up @@ -191,4 +194,39 @@ private void getLastTypoDataToModel(final Model model, final String wksName) {
model.addAttribute("lastTypoCreatedDate", createdDate);
model.addAttribute("lastTypoCreatedDateAgo", createdDate.map(new PrettyTime()::format));
}

@GetMapping(USERS)
public String getWorkspaceUsersPage(Model model,
@PathVariable String wksName,
@SortDefault(DEFAULT_SORT_FIELD) Pageable pageable) {
var wksOptional = workspaceService.getWorkspaceInfoByName(wksName);
if (wksOptional.isEmpty()) {
//TODO send error page
log.error("Workspace with name {} not found", wksName);
return REDIRECT_ROOT;
}
model.addAttribute("wksName", wksName);
model.addAttribute("wksInfo", wksOptional.get());
getStatisticDataToModel(model, wksName);
getLastTypoDataToModel(model, wksName);

var size = Optional.ofNullable(availableSizes.floor(pageable.getPageSize())).orElseGet(availableSizes::first);
var pageRequest = PageRequest.of(pageable.getPageNumber(), size, pageable.getSort());
var userPage = accountRepository.findPageAccountByWorkspaceName(pageable, wksName);


var sort = userPage.getSort()
.stream()
.findFirst()
.orElseGet(() -> asc(DEFAULT_SORT_FIELD));

model.addAttribute("userPage", userPage);
model.addAttribute("availableSizes", availableSizes);
model.addAttribute("sortProp", sort.getProperty());
model.addAttribute("sortDir", sort.getDirection());
model.addAttribute("DESC", DESC);
model.addAttribute("ASC", ASC);
return WKS_USERS_TEMPLATE;
}

}
5 changes: 4 additions & 1 deletion src/main/resources/templates/fragments/panels.html
Expand Up @@ -35,7 +35,10 @@
th:aria-current="${itemActive} == 'settings'"
th:classappend="${itemActive} == 'settings' ? 'active' : ''"
th:href="@{'/workspace/' + ${wksName} + '/settings'}">Settings</a>
<a aria-disabled="true" class="list-group-item list-group-item-action disabled" href="#" tabindex="-1">Users</a>
<a class="list-group-item list-group-item-action"
th:aria-current="${itemActive} == 'users'"
th:classappend="${itemActive} == 'users' ? 'active' : ''"
th:href="@{'/workspace/' + ${wksName} + '/users'}">Users</a>
<a aria-disabled="true" class="list-group-item list-group-item-action disabled" href="#" tabindex="-1">Integration</a>
</div>
</th:block>
Expand Down
1 change: 1 addition & 0 deletions src/main/resources/templates/index.html
Expand Up @@ -12,6 +12,7 @@ <h5 class="card-title" th:text="*{name}"></h5>
<p class="card-text" th:text="*{description}"></p>
<a class="card-link" th:href="@{'/workspace/' + *{name}}">Info</a>
<a class="card-link" th:href="@{'/workspace/' + *{name} + '/typos'}">Typos</a>
<a class="card-link" th:href="@{'/workspace/' + *{name} + '/users'}">Users</a>
</div>
</div>
</div>
Expand Down
129 changes: 129 additions & 0 deletions src/main/resources/templates/workspace/wks-users.html
@@ -0,0 +1,129 @@
<!DOCTYPE html>
<html lang="en" xmlns:th="http://www.thymeleaf.org" xmlns:sd="http://www.w3.org/1999/xhtml">
<head th:replace="fragments/header :: head"></head>
<body style="padding-top: 4.5rem">
<nav th:replace="fragments/panels :: mainNavbarTop"></nav>
<main class="container-fluid">
<div class="row">
<!-- Workspace Side Bar -->
<div class="col-2" th:include="fragments/panels :: workspaceSideBar (${wksName}, 'users')"></div>
<div class="col">
<!-- Workspace progress typo bar -->
<div class="row my-3"
th:if="${sumTypoInWks} > 0"
th:insert="fragments/workspace :: wksProgressBar (${sumTypoInWks}, ${countTypoByStatus})"></div>
<!-- Workspace number of typo -->
<div class="row">
<p th:text="|Total number of typos in workspace ${wksName} : ${sumTypoInWks}|"></p>
</div>
<!-- Workspace last typo date -->
<div class="row"
th:if="${lastTypoCreatedDateAgo.isPresent() && lastTypoCreatedDate.isPresent()}"
th:insert="fragments/workspace :: wksLastTypoDate (${lastTypoCreatedDateAgo}, ${lastTypoCreatedDate})"></div>
<!-- Workspace typo page -->
<div class="row">
<main class="col">
<div class="row">
<!-- Page size dropdown -->
<div class="col-2">
<div class="dropdown">
<button aria-expanded="false"
class="btn btn-secondary dropdown-toggle"
data-bs-toggle="dropdown"
id="dropdownSizePage"
th:text="|Page size ${userPage.size}|"
type="button">
</button>
<ul aria-labelledby="dropdownSizePage" class="dropdown-menu">
<li th:each="avalibleSize : ${availableSizes}">
<a class="dropdown-item"
th:classappend="(${userPage.size} == ${avalibleSize} ? 'disabled' : '')"
th:href="@{'/workspace/' + ${wksName} + '/users'(page=0,size=${avalibleSize},sort=|${sortProp},${sortDir}|)}"
th:text="${avalibleSize}">
</a>
</li>
</ul>
</div>
</div>
<!-- Page pagination -->
<div class="col-sm-6">
<ul class="pagination" sd:pagination="full"></ul>
</div>
</div>
<!-- Workspace user table -->
<div class="row">
<div class="col">
<table class="table">
<!-- Workspace user table head -->
<thead th:object="${userPage}">
<tr>
<th scope="col">
<a class="btn btn-light"
th:href="@{'/workspace/' + ${wksName} + '/users'(size=*{size},sort=|id,${sortProp == 'id' && sortDir.isAscending() ? DESC : ASC}|)}">
Id <span>[[${sortProp == 'id' && sortDir.isAscending() ? '&uArr;' : '&dArr;'}]]</span>
</a>
</th>
<th scope="col">
<a class="btn btn-light"
th:href="@{'/workspace/' + ${wksName} + '/users'(size=*{size},sort=|firstName,${sortProp == 'firstName' && sortDir.isAscending() ? DESC : ASC}|)}">
First Name <span>[[${sortProp == 'firstName' && sortDir.isAscending() ? '&uArr;' : '&dArr;'}]]</span>
</a>
</th>
<th scope="col">
<a class="btn btn-light"
th:href="@{'/workspace/' + ${wksName} + '/users'(size=*{size},sort=|lastName,${sortProp == 'lastName' && sortDir.isAscending() ? DESC : ASC}|)}">
Last Name <span>[[${sortProp == 'lastName' && sortDir.isAscending() ? '&uArr;' : '&dArr;'}]]</span>
</a>
</th>
<th scope="col">
<a class="btn btn-light"
th:href="@{'/workspace/' + ${wksName} + '/users'(size=*{size},sort=|email,${sortProp == 'email' && sortDir.isAscending() ? DESC : ASC}|)}">
Email <span>[[${sortProp == 'email' && sortDir.isAscending() ? '&uArr;' : '&dArr;'}]]</span>
</a>
</th>
</tr>
</thead>
<tbody>
<th:block th:each="userInfo : ${userPage.content}" th:object="${userInfo}">
<!-- Workspace user table row -->
<tr aria-expanded="false" class="accordion collapsed" data-bs-toggle="collapse"
th:aria-controls="|collapseUserRowId_*{id}|"
th:classappend="|table-*{userStatus.getStyle}|"
th:data-bs-target="|#collapseUserRowId_*{id}|">
<td th:text="*{id}"></td>
<td th:text="*{firstName}"></td>
<td th:text="*{lastName}"></td>
<td th:text="*{email}"></td>
</tr>
<!-- Workspace user table row collapsed part -->
<tr>
<td class="p-0" colspan="7">
<div class="accordion-collapse collapse" th:id="|collapseUserRowId_*{id}|">
<div class="p-4">
<div class="row mb-2">
<!-- Workspace user table row delete buttons -->
<div class="col-1">
<form th:action="@{'/users/' + *{id}(wksName=${wksName})}"
th:method="delete">
<button class="btn btn-danger" type="submit">
Delete
</button>
</form>
</div>
</div>
</div>
</div>
</td>
</tr>
</th:block>
</tbody>
</table>
</div>
</div>
</main>
</div>
</div>
</div>
</main>
</body>
</html>
Expand Up @@ -8,6 +8,7 @@
import io.hexlet.typoreporter.domain.typo.Typo;
import io.hexlet.typoreporter.domain.workspace.Workspace;
import io.hexlet.typoreporter.test.DBUnitEnumPostgres;
import org.junit.jupiter.api.Test;
import org.junit.jupiter.params.ParameterizedTest;
import org.junit.jupiter.params.provider.MethodSource;
import org.junit.jupiter.params.provider.NullAndEmptySource;
Expand All @@ -17,6 +18,10 @@
import org.springframework.boot.test.autoconfigure.orm.jpa.DataJpaTest;
import org.springframework.context.annotation.Import;
import org.springframework.dao.DataIntegrityViolationException;
import org.springframework.data.domain.Page;
import org.springframework.data.domain.Pageable;
import org.springframework.data.domain.PageRequest;
import org.springframework.data.domain.Sort;
import org.springframework.test.context.DynamicPropertyRegistry;
import org.springframework.test.context.DynamicPropertySource;
import org.springframework.transaction.annotation.Transactional;
Expand All @@ -35,7 +40,7 @@
@Transactional
@DBRider
@DBUnit(caseInsensitiveStrategy = LOWERCASE, dataTypeFactoryClass = DBUnitEnumPostgres.class, cacheConnection = false)
@DataSet(value = {"accounts.yml"})
@DataSet(value = {"accounts.yml", "workspaces.yml"})
@AutoConfigureTestDatabase(replace = AutoConfigureTestDatabase.Replace.NONE)
public class AccountRepositoryIT {

Expand All @@ -47,6 +52,9 @@ public class AccountRepositoryIT {
@Autowired
private AccountRepository accountRepository;

@Autowired
private WorkspaceRepository workspaceRepository;

@DynamicPropertySource
static void datasourceProperties(DynamicPropertyRegistry registry) {
registry.add("spring.datasource.url", postgreSQLContainer::getJdbcUrl);
Expand All @@ -68,4 +76,15 @@ void getAccountByEmail(final String email) {
void getAccountByEmailNotExist(final String email) {
assertThat(accountRepository.findAccountByEmail(email)).isEmpty();
}

@Test
void getPageAccountByWorkspaceName() {
Workspace wks = workspaceRepository.findAll().stream().findFirst().get();
accountRepository.findAll().forEach(account -> account.setWorkspace(wks));
Pageable pageable = PageRequest.of(0, 10, Sort.by("id"));
Page<Account> page = accountRepository.findPageAccountByWorkspaceName(pageable, wks.getName());
assertThat(page).isNotEmpty();
assertThat(page.getTotalElements()).isEqualTo(accountRepository.count());
assertThat(page.getTotalPages()).isEqualTo(1);
}
}
Expand Up @@ -5,8 +5,10 @@
import com.github.database.rider.spring.api.DBRider;
import io.hexlet.typoreporter.domain.AbstractAuditingEntity;
import io.hexlet.typoreporter.domain.typo.Typo;
import io.hexlet.typoreporter.domain.account.Account;
import io.hexlet.typoreporter.domain.workspace.Workspace;
import io.hexlet.typoreporter.repository.WorkspaceRepository;
import io.hexlet.typoreporter.repository.AccountRepository;
import io.hexlet.typoreporter.service.WorkspaceService;
import io.hexlet.typoreporter.service.dto.workspace.CreateWorkspace;
import io.hexlet.typoreporter.test.DBUnitEnumPostgres;
Expand All @@ -25,11 +27,14 @@

import java.time.LocalDateTime;
import java.util.Comparator;
import java.util.stream.Collectors;
import java.util.Set;

import static com.github.database.rider.core.api.configuration.Orthography.LOWERCASE;
import static io.hexlet.typoreporter.test.Constraints.POSTGRES_IMAGE;
import static io.hexlet.typoreporter.web.Routers.SETTINGS;
import static io.hexlet.typoreporter.web.Routers.UPDATE;
import static io.hexlet.typoreporter.web.Routers.USERS;
import static io.hexlet.typoreporter.web.Routers.Typo.TYPOS;
import static io.hexlet.typoreporter.web.Routers.Workspace.WKS_NAME_PATH;
import static io.hexlet.typoreporter.web.Routers.Workspace.WORKSPACE;
Expand Down Expand Up @@ -69,6 +74,9 @@ static void datasourceProperties(DynamicPropertyRegistry registry) {
@Autowired
private WorkspaceService service;

@Autowired
private AccountRepository accountRepository;

@Autowired
private MockMvc mockMvc;

Expand Down Expand Up @@ -134,6 +142,27 @@ void getWorkspaceSettingsPageWithoutWksInfo() throws Exception {
.andExpect(redirectedUrl("/"));
}

@ParameterizedTest
@MethodSource("io.hexlet.typoreporter.test.factory.EntitiesFactory#getWorkspaceNamesExist")
void getWorkspaceUsersPage(final String wksName) throws Exception {
Workspace workspace = repository.getWorkspaceByName(wksName).orElse(null);
Set<Account> accounts = accountRepository.findAll().stream().collect(Collectors.toSet());
accounts.forEach(account -> workspace.addAccount(account));

MockHttpServletResponse response = mockMvc.perform(get(WORKSPACE + WKS_NAME_PATH + USERS, wksName))
.andExpect(model().attributeExists("wksInfo", "wksName", "userPage", "availableSizes", "sortProp", "sortDir", "DESC", "ASC"))
.andReturn().getResponse();

for (Account account : workspace.getAccounts()) {
assertThat(response.getContentAsString()).contains(
account.getId().toString(),
account.getFirstName(),
account.getLastName(),
account.getEmail()
);
}
}


@ParameterizedTest
@MethodSource("io.hexlet.typoreporter.test.factory.EntitiesFactory#getWorkspaceNamesExist")
Expand Down

0 comments on commit 6cb195d

Please sign in to comment.