Skip to content

Commit

Permalink
RI-112: [admin/companies] ajoute un composant de filtre au tableau de…
Browse files Browse the repository at this point in the history
…s compagnies (#168)
  • Loading branch information
mathusha-sdv committed Nov 22, 2021
1 parent 252dcdd commit c22af2e
Show file tree
Hide file tree
Showing 31 changed files with 421 additions and 135 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -22,14 +22,15 @@ public class CompanyController {
private final CompanyService companyService;

@GetMapping
public ResponseEntity<Page<CompanyDto>> getAll(
public ResponseEntity<Page<CompanyDto>> getAllByCriteria(
@RequestParam(value = "page", defaultValue = "0") int page,
@RequestParam(value = "size", defaultValue = "10") int size,
@RequestParam(value = "rows", defaultValue = "10") int rows,
@RequestParam(value = "sortBy", defaultValue = "id") String sortBy,
@RequestParam(value = "sortOrder", defaultValue = "ASC") String sortOrder
@RequestParam(value = "sortOrder", defaultValue = "ASC") String sortOrder,
@RequestParam(value = "keyword", defaultValue = "") String keyword
) {
Pageable pageable = PageRequest.of(page, size, Sort.by(Sort.Direction.valueOf(sortOrder), sortBy));
Page<CompanyDto> companyDtos = companyService.getAll(pageable);
Pageable pageable = PageRequest.of(page, rows, Sort.by(Sort.Direction.valueOf(sortOrder), sortBy));
Page<CompanyDto> companyDtos = companyService.getAllWithCriteria(pageable, keyword);
return ResponseEntity.ok(companyDtos);
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,8 @@

import com.dynonuggets.refonteimplicaction.model.Company;
import org.springframework.data.jpa.repository.JpaRepository;
import org.springframework.stereotype.Repository;

public interface CompanyRepository extends JpaRepository<Company, Long> {
@Repository
public interface CompanyRepository extends JpaRepository<Company, Long>, CompanyRepositoryCustom {
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
package com.dynonuggets.refonteimplicaction.repository;

import com.dynonuggets.refonteimplicaction.model.Company;
import org.springframework.data.domain.Page;
import org.springframework.data.domain.Pageable;

public interface CompanyRepositoryCustom {
/**
* @param pageable l'objet de pagination
* @param keyword la chaîne de caractère à rechercher dans les champs keyword, description
* @return la liste de résultats paginée des Company correspondant aux critères
*/
Page<Company> findAllWithCriteria(final Pageable pageable, final String keyword);

}
Original file line number Diff line number Diff line change
@@ -0,0 +1,62 @@
package com.dynonuggets.refonteimplicaction.repository.impl;

import com.dynonuggets.refonteimplicaction.model.Company;
import com.dynonuggets.refonteimplicaction.repository.CompanyRepositoryCustom;
import lombok.AllArgsConstructor;
import org.apache.commons.lang3.StringUtils;
import org.springframework.data.domain.Page;
import org.springframework.data.domain.Pageable;
import org.springframework.data.jpa.repository.query.QueryUtils;
import org.springframework.data.support.PageableExecutionUtils;

import javax.persistence.EntityManager;
import javax.persistence.criteria.CriteriaBuilder;
import javax.persistence.criteria.CriteriaQuery;
import javax.persistence.criteria.Predicate;
import javax.persistence.criteria.Root;
import java.util.ArrayList;
import java.util.List;

@AllArgsConstructor
public class CompanyRepositoryImpl implements CompanyRepositoryCustom {
private final EntityManager entityManager;

@Override
public Page<Company> findAllWithCriteria(Pageable pageable, String keyword) {
CriteriaBuilder criteriaBuilder = entityManager.getCriteriaBuilder();
CriteriaQuery<Company> query = criteriaBuilder.createQuery(Company.class);
Root<Company> queryRoot = query.from(Company.class);
List<Predicate> predicates = new ArrayList<>();

// gestion du tri des résultats
if (!pageable.getSort().isEmpty()) {
query.orderBy(QueryUtils.toOrders(pageable.getSort(), queryRoot, criteriaBuilder));
}

// recherche par mot clé dans le nom et description
if (StringUtils.isNotEmpty(keyword)) {
keyword = "%" + keyword + "%";
Predicate namePredicate = criteriaBuilder.like(queryRoot.get("name"), keyword);
Predicate descriptionPredicate = criteriaBuilder.like(queryRoot.get("description"), keyword);
predicates.add(criteriaBuilder.or(namePredicate, descriptionPredicate));
}


// combinaison des différents prédicats
Predicate finalPredicate = criteriaBuilder.and(predicates.toArray(new Predicate[0]));

query.where(finalPredicate);

CriteriaQuery<Long> countQuery = criteriaBuilder.createQuery(Long.class);
countQuery.select(criteriaBuilder.count(countQuery.from(Company.class)));
final Long totalCount = entityManager.createQuery(countQuery).getSingleResult();
final int firstResult = pageable.getPageNumber() * pageable.getPageSize();

// lancement de la requête et mise en place de la pagination
final List<Company> results = entityManager.createQuery(query)
.setFirstResult(firstResult)
.setMaxResults(pageable.getPageSize())
.getResultList();
return PageableExecutionUtils.getPage(results, pageable, () -> totalCount);
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -8,8 +8,7 @@
import org.springframework.data.domain.Page;
import org.springframework.data.domain.Pageable;
import org.springframework.stereotype.Service;

import javax.transaction.Transactional;
import org.springframework.transaction.annotation.Transactional;

@Service
@AllArgsConstructor
Expand All @@ -26,6 +25,11 @@ public Page<CompanyDto> getAll(Pageable pageable) {
.map(companyAdapter::toDto);
}

public Page<CompanyDto> getAllWithCriteria(Pageable pageable, String keyword) {
return companyRepository.findAllWithCriteria(pageable, keyword)
.map(companyAdapter::toDto);
}

@Transactional
public CompanyDto saveOrUpdateCompany(CompanyDto companyDto) {
Company company = companyAdapter.toModel(companyDto);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@

import static org.hamcrest.Matchers.is;
import static org.mockito.ArgumentMatchers.any;
import static org.mockito.ArgumentMatchers.anyString;
import static org.mockito.BDDMockito.given;
import static org.mockito.Mockito.*;
import static org.springframework.http.MediaType.APPLICATION_JSON;
Expand All @@ -37,34 +38,65 @@ class CompanyControllerTest extends ControllerIntegrationTestBase {
@BeforeEach
protected void setUp() {
companyDtos = Arrays.asList(
CompanyDto.builder().id(1L).description("description").name("Idemia").logo("logo").url("url").build(),
CompanyDto.builder().id(2L).description("description2").name("SG").logo("logo2").url("url2").build());
CompanyDto.builder().id(1L).description("Société").name("Idemia").logo("logo").url("url").build(),
CompanyDto.builder().id(2L).description("description2").name("Société générale").logo("logo2").url("url2").build());

}

@WithMockUser
@Test
void getCompanysListShouldListAllCompanies() throws Exception {
// given
Page<CompanyDto> companyDtoPage = new PageImpl<>(companyDtos);
given(companyService.getAllWithCriteria(any(), anyString())).willReturn(companyDtoPage);

// test des données de pagination
given(companyService.getAll(DEFAULT_PAGEABLE)).willReturn(companyDtoPage);
ResultActions actions = mvc.perform(get(BASE_URI).contentType(APPLICATION_JSON))
.andDo(print())
// when
ResultActions resultActions = mvc.perform(get(BASE_URI).contentType(APPLICATION_JSON));

// then
resultActions
.andExpect(status().isOk())
.andExpect(jsonPath("$.totalPages").value(companyDtoPage.getTotalPages()))
.andExpect(jsonPath("$.totalElements").value(companyDtos.size()));

// test des propriétés de chaque éléments de la liste reçue
for (int i = 0; i < companyDtos.size(); i++) {
final String contentPath = String.format("$.content[%d]", i);
actions.andExpect(jsonPath(contentPath + ".id", is(Math.toIntExact(companyDtos.get(i).getId()))))
resultActions.andExpect(jsonPath(contentPath + ".id", is(Math.toIntExact(companyDtos.get(i).getId()))))
.andExpect(jsonPath(contentPath + ".description", is(companyDtos.get(i).getDescription())))
.andExpect(jsonPath(contentPath + ".name", is(companyDtos.get(i).getName())))
.andExpect(jsonPath(contentPath + ".logo", is(companyDtos.get(i).getLogo())))
.andExpect(jsonPath(contentPath + ".url", is(companyDtos.get(i).getUrl())));
}

verify(companyService, times(1)).getAll(any());
verify(companyService, times(1)).getAllWithCriteria(any(), anyString());
}

@WithMockUser
@Test
void getCompaniesListShouldListAllCompaniesByCriteria() throws Exception {
// given
Page<CompanyDto> companyDtoPage = new PageImpl<>(companyDtos);

// when
// test des données de pagination
given(companyService.getAllWithCriteria(any(), anyString())).willReturn(companyDtoPage);
ResultActions actions = mvc.perform(get(BASE_URI).contentType(APPLICATION_JSON));

// then
actions.andDo(print())
.andExpect(status().isOk());

// test des propriétés de chaque éléments de la liste reçue
for (int i = 0; i < companyDtos.size(); i++) {
final String contentPath = String.format("$.content[%d]", i);
actions.andExpect(jsonPath(contentPath + ".id", is(Math.toIntExact(companyDtos.get(i).getId()))))
.andExpect(jsonPath(contentPath + ".description", is(companyDtos.get(i).getDescription())))
.andExpect(jsonPath(contentPath + ".name", is(companyDtos.get(i).getName())))
.andExpect(jsonPath(contentPath + ".logo", is(companyDtos.get(i).getLogo())))
.andExpect(jsonPath(contentPath + ".url", is(companyDtos.get(i).getUrl())));
}
verify(companyService, times(1)).getAllWithCriteria(any(), anyString());
}

@Test
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@

import org.testcontainers.containers.MySQLContainer;

abstract class AbstractContainerBaseTest {
public abstract class AbstractContainerBaseTest {

@SuppressWarnings("rawtypes")
static final MySQLContainer MY_SQL_CONTAINER;
Expand Down
6 changes: 6 additions & 0 deletions frontend-implicaction/src/app/admin/admin-routing.module.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@ import {NgModule} from '@angular/core';
import {RouterModule, Routes} from '@angular/router';
import {AdminComponent} from './admin.component';
import {JobFilterComponent} from '../shared/components/job-filter/job-filter.component';
import {CompanyFilterComponent} from '../shared/components/company-filter/company-filter.component';


const routes: Routes = [
Expand Down Expand Up @@ -47,6 +48,11 @@ const routes: Routes = [
path: '',
loadChildren: () => import('./companies/companies.module').then(m => m.CompaniesModule),
outlet: 'admin-content'
},
{
path: '',
component: CompanyFilterComponent,
outlet: 'admin-filter'
}
]
},
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,7 @@ <h2 class="panel-title">Toutes les entreprises</h2>
<p-table
(onLazyLoad)="paginate($event)"
[lazy]="true"
[loading]="loading"
[loading]="isLoading"
[paginator]="true"
[rowsPerPageOptions]="[10, 25, 50]"
[rows]="pageable.rows"
Expand All @@ -39,7 +39,7 @@ <h2 class="panel-title">Toutes les entreprises</h2>
(error)="companyLogo.src = 'assets/img/job-posting-img.png'"
[src]="company?.logo || 'assets/img/job-posting-img.png'"
alt="job-logo"
class="card-img-top rounded-circle img-fluid shadow-sm user-avatar"
class="card-img-top"
/>
</td>
<td class="overflow-ellipsis text-wrap col-4 col-sm-3 col-lg-2">{{company.name}}</td>
Expand Down
Original file line number Diff line number Diff line change
@@ -1,45 +1,72 @@
import {Component, OnInit} from '@angular/core';
import {Pageable} from '../../../../shared/models/pageable';
import {Component, OnDestroy, OnInit} from '@angular/core';
import {finalize, take} from 'rxjs/operators';
import {Company} from '../../../../shared/models/company';
import {Constants} from '../../../../config/constants';
import {CompanyService} from '../../../../company/services/company.service';
import {ToasterService} from '../../../../core/services/toaster.service';
import {SidebarService} from '../../../../shared/services/sidebar.service';
import {CompaniesFormComponent} from '../companies-form/companies-form.component';
import {CompanyFilterContextService} from '../../../../company/services/company-filter-context.service';
import {ActivatedRoute} from '@angular/router';
import {CompanySortEnum} from '../../../../company/enums/company-sort.enum';
import {CompanyContextServiceService} from '../../../../shared/services/company-context-service.service';
import {BaseWithPaginationComponent} from '../../../../shared/components/base-with-pagination/base-with-pagination.component';
import {Subscription} from 'rxjs';
import {Criteria} from '../../../../shared/models/Criteria';

@Component({
selector: 'app-companies-table',
templateUrl: './companies-table.component.html',
styleUrls: ['./companies-table.component.scss']
})
export class CompaniesTableComponent extends BaseWithPaginationComponent<Company> implements OnInit {
export class CompaniesTableComponent extends BaseWithPaginationComponent<Company, Criteria> implements OnInit, OnDestroy {

readonly ROWS_PER_PAGE_OPTIONS = Constants.ROWS_PER_PAGE_OPTIONS;
loading = true; // indique si les données sont en chargement

// Pagination
pageable: Pageable = Constants.PAGEABLE_DEFAULT;
selectedOrder = CompanySortEnum.NAME_ASC;
selectedOrderCode: string;
subscription: Subscription;

constructor(
private companyService: CompanyService,
private toastService: ToasterService,
private sidebarService: SidebarService,
private companyContextService: CompanyContextServiceService
private companyContextService: CompanyContextServiceService,
private filterService: CompanyFilterContextService,
protected route: ActivatedRoute
) {
super();
super(route);
}

ngOnDestroy(): void {
// désinscription et réinitialisation des filtres
this.subscription?.unsubscribe();
this.filterService.criteria = {};
}

ngOnInit(): void {
this.companyContextService
this.pageable.sortOrder = CompanySortEnum.NAME_ASC.sortDirection;
this.pageable.sortBy = CompanySortEnum.NAME_ASC.sortBy;
this.selectedOrderCode = CompanySortEnum.NAME_ASC.code;

// on s'abonne à l'ajout d'une nouvelle entreprise
this.subscription = this.companyContextService
.observe$
.subscribe(company => {
if (company) {
this.paginate();
}
});
})
// ... et à la modification des critères de recherche
.add(
this.filterService
.observe()
.subscribe(criteria => {
this.criteria = criteria;
const objectParam = this.buildQueryParams();
this.filterService.updateRouteQueryParams(objectParam);
this.paginate();
})
);

this.getFilterFromQueryParams().then(() => this.filterService.criteria = this.criteria);
}

onEditCompany(company: Company): void {
Expand All @@ -66,10 +93,10 @@ export class CompaniesTableComponent extends BaseWithPaginationComponent<Company
this.pageable.sortBy = 'id';
this.pageable.sortOrder = 'ASC';
this.companyService
.getAll(this.pageable)
.getAllByCriteria(this.pageable, this.criteria)
.pipe(
take(1),
finalize(() => this.loading = false)
finalize(() => this.isLoading = false)
)
.subscribe(
data => {
Expand All @@ -78,7 +105,7 @@ export class CompaniesTableComponent extends BaseWithPaginationComponent<Company
this.pageable.totalElements = data.totalElements;
this.pageable.content = data.content;
},
() => this.toastService.error('Oops', 'Une erreur est survenue lors de la récupération des données'),
() => this.toastService.error('Oops', 'Une erreur est survenue lors de la récupération de la liste des compagnies')
);
}
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@ import {ToasterService} from '../../../../core/services/toaster.service';
import {SidebarService} from '../../../../shared/services/sidebar.service';
import {SidebarContentComponent} from '../../../../shared/models/sidebar-props';
import {Observable} from 'rxjs';
import {CompanyService} from '../../../../job/services/company.service';
import {CompanyService} from '../../../../company/services/company.service';
import {Pageable} from '../../../../shared/models/pageable';
import {Constants} from '../../../../config/constants';
import {Company} from '../../../../shared/models/company';
Expand Down
Loading

0 comments on commit c22af2e

Please sign in to comment.