Skip to content

Commit

Permalink
fix(SILVA-550): Enhance Org Unit and Category Selection with Filterab…
Browse files Browse the repository at this point in the history
…le Multi-Select Dropdown (#442)

Co-authored-by: Paulo Gomes da Cruz Junior <[email protected]>
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
  • Loading branch information
3 people committed Nov 7, 2024
1 parent 95d2be5 commit 32c6eda
Show file tree
Hide file tree
Showing 14 changed files with 350 additions and 85 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -14,8 +14,8 @@
@Getter
@ToString
public class OpeningSearchFiltersDto {
private final String orgUnit;
private final String category;
private final List<String> orgUnit;
private final List<String> category;
private final List<String> statusList;
private final Boolean myOpenings;
private final Boolean submittedToFrpa;
Expand All @@ -39,8 +39,8 @@ public class OpeningSearchFiltersDto {

/** Creates an instance of the search opening filter dto. */
public OpeningSearchFiltersDto(
String orgUnit,
String category,
List<String> orgUnit,
List<String> category,
List<String> statusList,
Boolean myOpenings,
Boolean submittedToFrpa,
Expand All @@ -56,8 +56,18 @@ public OpeningSearchFiltersDto(
String cutBlockId,
String timberMark,
String mainSearchTerm) {
this.orgUnit = Objects.isNull(orgUnit) ? null : orgUnit.toUpperCase().trim();
this.category = Objects.isNull(category) ? null : category.toUpperCase().trim();
this.orgUnit = new ArrayList<>();
if (!Objects.isNull(orgUnit)) {
this.orgUnit.addAll(orgUnit.stream()
.map(s -> String.format("'%s'", s.toUpperCase().trim()))
.toList());
}
this.category = new ArrayList<>();
if (!Objects.isNull(category)) {
this.category.addAll(category.stream()
.map(s -> String.format("'%s'", s.toUpperCase().trim()))
.toList());
}
this.statusList = new ArrayList<>();
this.openingIds = new ArrayList<>();
if (!Objects.isNull(statusList)) {
Expand Down Expand Up @@ -114,8 +124,8 @@ public OpeningSearchFiltersDto(
*/
public boolean hasValue(String prop) {
return switch (prop) {
case SilvaOracleConstants.ORG_UNIT -> !Objects.isNull(this.orgUnit);
case SilvaOracleConstants.CATEGORY -> !Objects.isNull(this.category);
case SilvaOracleConstants.ORG_UNIT -> !this.orgUnit.isEmpty();
case SilvaOracleConstants.CATEGORY -> !this.category.isEmpty();
case SilvaOracleConstants.STATUS_LIST -> !this.statusList.isEmpty();
case SilvaOracleConstants.OPENING_IDS -> !this.openingIds.isEmpty();
case SilvaOracleConstants.MY_OPENINGS -> !Objects.isNull(this.myOpenings);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -63,9 +63,9 @@ public PaginatedResult<OpeningSearchResponseDto> openingSearch(
@RequestParam(value = "mainSearchTerm", required = false)
String mainSearchTerm,
@RequestParam(value = "orgUnit", required = false)
String orgUnit,
List<String> orgUnit,
@RequestParam(value = "category", required = false)
String category,
List<String> category,
@RequestParam(value = "statusList", required = false)
List<String> statusList,
@RequestParam(value = "myOpenings", required = false)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -254,13 +254,13 @@ private Query setQueryParameters(OpeningSearchFiltersDto filtersDto, String nati

// 1. Org Unit code
if (filtersDto.hasValue(SilvaOracleConstants.ORG_UNIT)) {
log.info("Setting orgUnit filter value");
query.setParameter("orgUnit", filtersDto.getOrgUnit());
log.info("Setting orgUnit filter values");
// No need to set value since the query already dit it. Didn't work set through named param
}
// 2. Category code
if (filtersDto.hasValue(SilvaOracleConstants.CATEGORY)) {
log.info("Setting category filter value");
query.setParameter("category", filtersDto.getCategory());
log.info("Setting category filter values");
// No need to set value since the query already dit it. Didn't work set through named param
}
// 3. Status list codes
if (filtersDto.hasValue(SilvaOracleConstants.STATUS_LIST)) {
Expand Down Expand Up @@ -427,13 +427,15 @@ private String createNativeSqlQuery(OpeningSearchFiltersDto filtersDto) {

// 1. Org Unit code
if (filtersDto.hasValue(SilvaOracleConstants.ORG_UNIT)) {
log.info("Filter orgUnit detected! orgUnit={}", filtersDto.getOrgUnit());
builder.append("AND ou.ORG_UNIT_CODE = :orgUnit ");
String orgUnits = String.join(",", filtersDto.getOrgUnit());
log.info("Filter orgUnit detected! orgUnit={}", orgUnits);
builder.append(String.format("AND ou.ORG_UNIT_CODE IN (%s) ", orgUnits));
}
// 2. Category code
if (filtersDto.hasValue(SilvaOracleConstants.CATEGORY)) {
log.info("Filter category detected! category={}", filtersDto.getCategory());
builder.append("AND o.OPEN_CATEGORY_CODE = :category ");
String categories = String.join(",", filtersDto.getCategory());
log.info("Filter category detected! statusList={}", categories);
builder.append(String.format("AND o.OPEN_CATEGORY_CODE IN (%s) ", categories));
}
// 3. Status code
if (filtersDto.hasValue(SilvaOracleConstants.STATUS_LIST)) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -42,8 +42,8 @@ class OpeningSearchRepositoryTest {
private OpeningSearchRepository openingSearchRepository;

private OpeningSearchFiltersDto mockFilter(
String orgUnit,
String category,
List<String> orgUnit,
List<String> category,
List<String> statusList,
Boolean myOpenings,
Boolean submittedToFrpa,
Expand Down Expand Up @@ -79,16 +79,16 @@ private OpeningSearchFiltersDto mockFilter(
mainSearchTerm);
}

private OpeningSearchFiltersDto mockOrgUnit(String orgUnit) {
private OpeningSearchFiltersDto mockOrgUnit(List<String> orgUnit) {
return mockFilter(
orgUnit, null, null, null, null, null, null, null, null, null, null, null, null, null, null,
null, null);
}

private OpeningSearchFiltersDto mockAllFilters() {
return mockFilter(
"DCR",
"FTML",
List.of("DCR"),
List.of("FTML"),
List.of("APP"),
true,
false,
Expand Down Expand Up @@ -481,7 +481,7 @@ void searchOpeningQuery_mainFilterString_shouldSucceed() {
@Test
@DisplayName("Search opening query org unit filter should succeed")
void searchOpeningQuery_orgUnitFilter_shouldSucceed() {
OpeningSearchFiltersDto filters = mockOrgUnit("DCR");
OpeningSearchFiltersDto filters = mockOrgUnit(List.of("DCR"));

PaginationParameters pagination = new PaginationParameters(0, 10);

Expand Down Expand Up @@ -575,15 +575,15 @@ void searchOpeningQuery_allFilters_shouldSucceed() {

Integer openingId = 123456789;
String openingNumber = "589";
OpeningCategoryEnum category = OpeningCategoryEnum.of(filters.getCategory());
OpeningCategoryEnum category = OpeningCategoryEnum.of("FTML");
OpeningStatusEnum status = OpeningStatusEnum.of(filters.getStatusList().get(0));
String cuttingPermitId = "123";
String timberMark = "EM2184";
String cutBlockId = "456";
BigDecimal openingGrossArea = new BigDecimal("11");
Timestamp disturbanceStartDate = Timestamp.valueOf(LocalDateTime.now());
String forestFileId = "TFL47";
String orgUnitCode = filters.getOrgUnit();
String orgUnitCode = "DCR";
String orgUnitName = "Org Name";
String clientNumber = "00012797";
String clientLocation = "00";
Expand Down Expand Up @@ -655,7 +655,7 @@ void searchOpeningQuery_allFilters_shouldSucceed() {
@Test
@DisplayName("Search opening query no records found should succeed")
void searchOpeningQuery_noRecordsFound_shouldSucceed() {
OpeningSearchFiltersDto filters = mockOrgUnit("AAA");
OpeningSearchFiltersDto filters = mockOrgUnit(List.of("AAA"));

PaginationParameters pagination = new PaginationParameters(0, 10);

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,8 @@
import com.github.tomakehurst.wiremock.junit5.WireMockExtension;
import java.math.BigDecimal;
import java.time.LocalDateTime;
import java.util.List;

import org.junit.jupiter.api.Assertions;
import org.junit.jupiter.api.DisplayName;
import org.junit.jupiter.api.Test;
Expand Down Expand Up @@ -106,7 +108,7 @@ void openingSearch_orgUnit_shouldSucceed() {

PaginatedResult<OpeningSearchResponseDto> result =
openingService.openingSearch(new OpeningSearchFiltersDto(
"TWO",
List.of("TWO"),
null,
null,
null,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@ import "@testing-library/jest-dom";
import OpeningsSearchBar from "../../../../components/SilvicultureSearch/Openings/OpeningsSearchBar";
import { vi } from "vitest";
import { QueryClient, QueryClientProvider } from '@tanstack/react-query';
import { useOpeningsSearch } from "../../../../contexts/search/OpeningsSearch";
import { OpeningsSearchProvider, useOpeningsSearch } from "../../../../contexts/search/OpeningsSearch";

// Mock the useOpeningsSearch context to avoid rendering errors
vi.mock("../../../../contexts/search/OpeningsSearch", () => ({
Expand Down Expand Up @@ -53,4 +53,72 @@ describe("OpeningsSearchBar", () => {
// Check if the onSearchClick function was called
expect(onSearchClick).toHaveBeenCalled();
});

it("should show AdvancedSearchDropdown if isOpen is true", () => {
// Create a mock function to pass as a prop
const onSearchClick = vi.fn();
const isOpen = false;
// Mock the useState calls
vi.spyOn(React, 'useState')
.mockImplementationOnce(() => [true, vi.fn()]) // Mocking isOpen state as false
.mockImplementationOnce(() => [false, vi.fn()]) // Mocking showFilters state as false
.mockImplementationOnce(() => ["", vi.fn()]) // Mocking searchInput state
.mockImplementationOnce(() => [0, vi.fn()]) // Mocking filtersCount state
.mockImplementationOnce(() => [null, vi.fn()]); // Mocking filtersList state
render(
<QueryClientProvider client={queryClient}>
<OpeningsSearchBar onSearchClick={onSearchClick} />
</QueryClientProvider>
);

// Check if an element with the class 'd-none' exists within the structure
const dNoneElement = screen.getAllByText("", {selector: ".d-block"})[0];
expect(dNoneElement).toBeInTheDocument();
});

it("should not show AdvancedSearchDropdown if isOpen is false", () => {
// Create a mock function to pass as a prop
const onSearchClick = vi.fn();
const isOpen = false;
// Mock the useState calls
vi.spyOn(React, 'useState')
.mockImplementationOnce(() => [false, vi.fn()]) // Mocking isOpen state as false
.mockImplementationOnce(() => [false, vi.fn()]) // Mocking showFilters state as false
.mockImplementationOnce(() => ["", vi.fn()]) // Mocking searchInput state
.mockImplementationOnce(() => [0, vi.fn()]) // Mocking filtersCount state
.mockImplementationOnce(() => [null, vi.fn()]); // Mocking filtersList state
render(
<QueryClientProvider client={queryClient}>
<OpeningsSearchBar onSearchClick={onSearchClick} />
</QueryClientProvider>
);

// Check if an element with the class 'd-none' exists within the structure
const dNoneElement = screen.getAllByText("", {selector: ".d-none"})[0];
expect(dNoneElement).toBeInTheDocument();
});

it("should show correct filter count, when count is greater than 0", () => {
// Create a mock function to pass as a prop
const onSearchClick = vi.fn();
// Mock the useState calls
vi.spyOn(React, 'useState')
.mockImplementationOnce(() => [false, vi.fn()]) // Mocking isOpen state as false
.mockImplementationOnce(() => [false, vi.fn()]) // Mocking showFilters state as false
.mockImplementationOnce(() => ["", vi.fn()]) // Mocking searchInput state
.mockImplementationOnce(() => [2, vi.fn()]) // Mocking filtersCount state
.mockImplementationOnce(() => [null, vi.fn()]); // Mocking filtersList state

render(
<QueryClientProvider client={queryClient}>
<OpeningsSearchBar onSearchClick={onSearchClick} />
</QueryClientProvider>
);

console.log(screen.debug());

// Check if an element with the class 'd-none' exists within the structure
const dNoneElement = screen.getByText('+2');
expect(dNoneElement).toBeInTheDocument();
});
});
56 changes: 56 additions & 0 deletions frontend/src/__test__/contexts/OpeningsSearch.test.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,56 @@
// OpeningsSearchProvider.test.tsx
import React from 'react';
import { describe, it, expect, beforeEach, vi } from 'vitest';
import { render, screen, fireEvent } from '@testing-library/react';
import { OpeningsSearchProvider, useOpeningsSearch } from '../../contexts/search/OpeningsSearch';

const TestComponent: React.FC = () => {
const { filters, setFilters, searchTerm, setSearchTerm, clearFilters, clearIndividualField } = useOpeningsSearch();

return (
<div>
<p data-testid="searchTerm">{searchTerm}</p>
<p data-testid="startDate">{String(filters.startDate)}</p>
<button onClick={() => setSearchTerm('test search')} data-testid="setSearchTerm">Set Search Term</button>
<button onClick={() => setFilters({ ...filters, startDate: new Date() })} data-testid="setFilters">Set Start Date</button>
<button onClick={() => clearFilters()} data-testid="clearFilters">Clear Filters</button>
<button onClick={() => clearIndividualField('startDate')} data-testid="clearStartDate">Clear Start Date</button>
</div>
);
};

describe('OpeningsSearchProvider', () => {
beforeEach(() => {
render(
<OpeningsSearchProvider>
<TestComponent />
</OpeningsSearchProvider>
);
});

it('should initialize with default values', () => {
expect(screen.getByTestId('searchTerm').textContent).toBe('');
expect(screen.getByTestId('startDate').textContent).toBe('null');
});

it('should update searchTerm', () => {
fireEvent.click(screen.getByTestId('setSearchTerm'));
expect(screen.getByTestId('searchTerm').textContent).toBe('test search');
});

it('should set and then clear filters', () => {
fireEvent.click(screen.getByTestId('setFilters'));
expect(screen.getByTestId('startDate').textContent).not.toBe('null');

fireEvent.click(screen.getByTestId('clearFilters'));
expect(screen.getByTestId('startDate').textContent).toBe('null');
});

it('should clear individual field', () => {
fireEvent.click(screen.getByTestId('setFilters'));
expect(screen.getByTestId('startDate').textContent).not.toBe('null');

fireEvent.click(screen.getByTestId('clearStartDate'));
expect(screen.getByTestId('startDate').textContent).toBe('null');
});
});
Loading

0 comments on commit 32c6eda

Please sign in to comment.