Skip to content

Commit

Permalink
feat(FSADT1-1517): CLIENT3 increment (#1203)
Browse files Browse the repository at this point in the history
  • Loading branch information
mamartinezmejia authored Oct 16, 2024
1 parent 36efbe6 commit abb5d03
Show file tree
Hide file tree
Showing 35 changed files with 1,527 additions and 86 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -6,10 +6,8 @@
import ca.bc.gov.app.service.client.ClientCodeService;
import ca.bc.gov.app.service.client.ClientCountryProvinceService;
import ca.bc.gov.app.service.client.ClientDistrictService;
import ca.bc.gov.app.service.client.ClientService;
import io.micrometer.observation.annotation.Observed;
import java.time.LocalDate;
import java.time.LocalDateTime;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.springframework.http.MediaType;
Expand All @@ -28,7 +26,6 @@
@Observed
public class ClientCodesController {

private final ClientService clientService;
private final ClientDistrictService clientDistrictService;
private final ClientCountryProvinceService clientCountryProvinceService;
private final ClientCodeService clientCodeService;
Expand Down
Original file line number Diff line number Diff line change
@@ -1,15 +1,21 @@
package ca.bc.gov.app.controller.client;

import ca.bc.gov.app.ApplicationConstant;
import ca.bc.gov.app.dto.bcregistry.ClientDetailsDto;
import ca.bc.gov.app.dto.client.ClientListDto;
import ca.bc.gov.app.dto.client.ClientLookUpDto;
import ca.bc.gov.app.exception.NoClientDataFound;
import ca.bc.gov.app.service.client.ClientLegacyService;
import ca.bc.gov.app.service.client.ClientService;
import ca.bc.gov.app.util.JwtPrincipalUtil;
import io.micrometer.observation.annotation.Observed;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import java.util.List;
import org.apache.commons.text.WordUtils;
import org.springframework.data.util.Pair;
import org.springframework.http.MediaType;
import org.springframework.http.server.reactive.ServerHttpResponse;
import org.springframework.security.oauth2.server.resource.authentication.JwtAuthenticationToken;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PathVariable;
Expand All @@ -27,6 +33,7 @@
public class ClientController {

private final ClientService clientService;
private final ClientLegacyService clientLegacyService;

@GetMapping("/{clientNumber}")
public Mono<ClientDetailsDto> getClientDetails(
Expand All @@ -45,7 +52,44 @@ public Mono<ClientDetailsDto> getClientDetails(
JwtPrincipalUtil.getProvider(principal)
);
}

@GetMapping("/search")
public Flux<ClientListDto> fullSearch(
@RequestParam(required = false, defaultValue = "0") int page,
@RequestParam(required = false, defaultValue = "10") int size,
@RequestParam(required = false, defaultValue = "") String keyword,
ServerHttpResponse serverResponse) {

log.info("Listing clients: page={}, size={}, keyword={}", page, size, keyword);

return clientLegacyService
.search(
page,
size,
keyword
)
.doOnNext(pair -> {
Long count = pair.getSecond();

serverResponse
.getHeaders()
.putIfAbsent(
ApplicationConstant.X_TOTAL_COUNT,
List.of(count.toString())
);
}
)
.map(Pair::getFirst)
.doFinally(signalType ->
serverResponse
.getHeaders()
.putIfAbsent(
ApplicationConstant.X_TOTAL_COUNT,
List.of("0")
)
);
}

/**
* Retrieve a Flux of ClientLookUpDto objects by searching for clients with a specific name.
*
Expand Down
10 changes: 10 additions & 0 deletions backend/src/main/java/ca/bc/gov/app/dto/client/ClientListDto.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
package ca.bc.gov.app.dto.client;

public record ClientListDto(
String clientNumber,
String clientAcronym,
String clientFullName,
String clientType,
String city,
String clientStatus) {
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
package ca.bc.gov.app.exception;

import org.springframework.http.HttpStatus;
import org.springframework.web.bind.annotation.ResponseStatus;
import org.springframework.web.server.ResponseStatusException;

@ResponseStatus(HttpStatus.EXPECTATION_FAILED)
public class MissingRequiredParameterException extends ResponseStatusException {
public MissingRequiredParameterException(String parameterName) {
super(HttpStatus.EXPECTATION_FAILED,
String.format("Missing value for parameter %s", parameterName));
}
}
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
package ca.bc.gov.app.service.client;

import ca.bc.gov.app.dto.client.ClientListDto;
import ca.bc.gov.app.dto.legacy.AddressSearchDto;
import ca.bc.gov.app.dto.legacy.ContactSearchDto;
import ca.bc.gov.app.dto.legacy.ForestClientDto;
Expand All @@ -11,6 +12,7 @@
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.lang3.StringUtils;
import org.springframework.beans.factory.annotation.Qualifier;
import org.springframework.data.util.Pair;
import org.springframework.stereotype.Service;
import org.springframework.util.CollectionUtils;
import org.springframework.web.reactive.function.BodyInserters;
Expand Down Expand Up @@ -307,6 +309,38 @@ public Flux<ForestClientDto> searchContact(ContactSearchDto dto) {
client.clientNumber())
);
}

public Flux<Pair<ClientListDto, Long>> search(int page, int size, String keyword) {
log.info(
"Searching clients by keyword {} with page {} and size {}",
keyword,
page,
size
);

return legacyApi
.get()
.uri(builder ->
builder
.path("/api/search")
.queryParam("page", page)
.queryParam("size", size)
.queryParam("value", keyword)
.build(Map.of())
)
.exchangeToFlux(response -> {
List<String> totalCountHeader = response.headers().header("X-Total-Count");
Long count = totalCountHeader.isEmpty() ? 0L : Long.valueOf(totalCountHeader.get(0));

return response
.bodyToFlux(ClientListDto.class)
.map(dto -> Pair.of(dto, count));
})
.doOnNext(pair -> {
ClientListDto dto = pair.getFirst();
Long totalCount = pair.getSecond();
log.info("Found clients by keyword {}, total count: {}", dto.clientNumber(), totalCount);
});
}

}
Original file line number Diff line number Diff line change
Expand Up @@ -11,13 +11,11 @@
import ca.bc.gov.app.dto.client.ClientContactDto;
import ca.bc.gov.app.dto.client.ClientListSubmissionDto;
import ca.bc.gov.app.dto.client.ClientSubmissionDto;
import ca.bc.gov.app.dto.client.DistrictDto;
import ca.bc.gov.app.dto.submissions.SubmissionAddressDto;
import ca.bc.gov.app.dto.submissions.SubmissionApproveRejectDto;
import ca.bc.gov.app.dto.submissions.SubmissionBusinessDto;
import ca.bc.gov.app.dto.submissions.SubmissionContactDto;
import ca.bc.gov.app.dto.submissions.SubmissionDetailsDto;
import ca.bc.gov.app.entity.client.ClientTypeCodeEntity;
import ca.bc.gov.app.entity.client.SubmissionDetailEntity;
import ca.bc.gov.app.entity.client.SubmissionEntity;
import ca.bc.gov.app.entity.client.SubmissionLocationContactEntity;
Expand All @@ -30,7 +28,6 @@
import ca.bc.gov.app.predicates.QueryPredicates;
import ca.bc.gov.app.predicates.SubmissionDetailPredicates;
import ca.bc.gov.app.predicates.SubmissionPredicates;
import ca.bc.gov.app.repository.client.DistrictCodeRepository;
import ca.bc.gov.app.repository.client.SubmissionContactRepository;
import ca.bc.gov.app.repository.client.SubmissionDetailRepository;
import ca.bc.gov.app.repository.client.SubmissionLocationContactRepository;
Expand Down
134 changes: 134 additions & 0 deletions frontend/cypress/e2e/pages/SearchPage.cy.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,134 @@
import type { ClientSearchResult } from "@/dto/CommonTypesDto";

describe("Search Page", () => {
const predictiveSearchCounter = {
count: 0,
};

const checkDisplayedResults = (clientList: ClientSearchResult[]) => {
clientList.forEach((client) => {
cy.get("#search-box")
.find(`cds-combo-box-item[data-value^="${client.clientNumber}"]`)
.should("exist");
});
};
beforeEach(() => {
// reset counter
predictiveSearchCounter.count = 0;

cy.intercept("/api/clients/search?keyword=*", (req) => {
predictiveSearchCounter.count++;
req.continue();
}).as("predictiveSearch");

cy.viewport(1920, 1080);
cy.visit("/");

cy.login("[email protected]", "Uat Test", "idir", {
given_name: "James",
family_name: "Baxter",
"cognito:groups": ["CLIENT_VIEWER"],
});

// Check if the Client search button is visible
cy.get("#menu-list-search").should("be.visible").click();

cy.get("h1").should("be.visible").should("contain", "Client search");

cy.window().then((win) => {
cy.stub(win, "open").as("windowOpen");
});
});

describe("when user fills in the search box with a valid value", () => {
beforeEach(() => {
cy.fillFormEntry("#search-box", "car", { skipBlur: true });
});

it("makes the API call with the entered keywords", () => {
cy.wait("@predictiveSearch").then((interception) => {
expect(interception.request.query.keyword).to.eq("car");
});
cy.wrap(predictiveSearchCounter).its("count").should("eq", 1);
});

it("displays autocomplete results", () => {
cy.wait("@predictiveSearch").then((interception) => {
const data = interception.response.body;

cy.wrap(data).should("be.an", "array").and("have.length.greaterThan", 0);

cy.get("#search-box")
.find("cds-combo-box-item")
.should("have.length", data.length)
.should("be.visible");

checkDisplayedResults(data);
});
});

describe("and types more characters", () => {
beforeEach(() => {
cy.wait("@predictiveSearch");
cy.fillFormEntry("#search-box", "d", { skipBlur: true });
});

it("makes another the API call with the updated keywords", () => {
cy.wait("@predictiveSearch").then((interception) => {
expect(interception.request.query.keyword).to.eq("card");
});
cy.wrap(predictiveSearchCounter).its("count").should("eq", 2);
});

it("updates the autocomplete results", () => {
cy.wait("@predictiveSearch").then((interception) => {
const data = interception.response.body;

cy.wrap(data).should("be.an", "array").and("have.length.greaterThan", 0);

cy.get("#search-box")
.find("cds-combo-box-item")
.should("have.length", data.length)
.should("be.visible");

checkDisplayedResults(data);
});
});
});

describe("and user clicks a result", () => {
const clientNumber = "00054076";
beforeEach(() => {
cy.get("#search-box")
.find("cds-combo-box-item")
.should("have.length.greaterThan", 0)
.should("be.visible");

cy.get("#search-box").find(`cds-combo-box-item[data-value^="${clientNumber}"]`).click();
});
it("navigates to the client details", () => {
const greenDomain = "green-domain.com";
cy.get("@windowOpen").should(
"be.calledWith",
`https://${greenDomain}/int/client/client02MaintenanceAction.do?bean.clientNumber=${clientNumber}`,
"_blank",
"noopener",
);
});
});
});

describe("when user fills in the search box with an invalid value", () => {
beforeEach(() => {
cy.fillFormEntry("#search-box", "até", { skipBlur: true });
});

it("shows an error message", () => {
cy.contains("The search terms can only contain: A-Z, a-z, 0-9, space or common symbols");
});
it("makes no API call", () => {
cy.wait(500); // This time has to be greater than the debouncing time
cy.wrap(predictiveSearchCounter).its("count").should("eq", 0);
});
});
});
46 changes: 38 additions & 8 deletions frontend/cypress/support/commands.ts
Original file line number Diff line number Diff line change
Expand Up @@ -146,15 +146,45 @@ Cypress.Commands.add("getMany", (names: string[]): Cypress.Chainable<any[]> => {
return cy.wrap(values);
});

Cypress.Commands.add("fillFormEntry",(field: string, value: string, delayMS: number = 10, area: boolean = false) =>{
interface FillFormEntryOptions {
delayMS?: number;
area?: boolean;
skipBlur?: boolean;
}

interface FillFormEntry {
(field: string, value: string, delayMS?: number, area?: boolean): void;
(field: string, value: string, options?: FillFormEntryOptions): void;
}

const fillFormEntry: FillFormEntry = (
field: string,
value: string,
arg3: number | FillFormEntryOptions = 10,
arg4: boolean | never = false,
) => {
const options =
typeof arg3 === "object"
? arg3
: {
delayMS: arg3,
area: arg4,
};
const { delayMS, area, skipBlur } = options;
cy.get(field)
.should("exist")
.shadow()
.find(area ? "textarea" : "input")
.focus()
.type(value,{ delay: delayMS })
.blur();
});
.should("exist")
.shadow()
.find(area ? "textarea" : "input")
.focus()
.type(value, { delay: delayMS })
.then((subject) => {
if (!skipBlur) {
cy.wrap(subject).blur();
}
});
};

Cypress.Commands.add("fillFormEntry", fillFormEntry);

Cypress.Commands.add("clearFormEntry",(field: string, area: boolean = false) =>{
cy.get(field)
Expand Down
Loading

0 comments on commit abb5d03

Please sign in to comment.