Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

RI-112: [admin/companies] ajoute un composant de filtre au tableau des compagnies #168

Merged
merged 11 commits into from
Nov 22, 2021
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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;
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Je vois bien que la valeur est affectée ici, mais aussi qu'elle ne sert à rien d'autre.


// 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';
mathusha-sdv marked this conversation as resolved.
Show resolved Hide resolved
import {Pageable} from '../../../../shared/models/pageable';
import {Constants} from '../../../../config/constants';
import {Company} from '../../../../shared/models/company';
Expand Down
Loading