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

FINERACT-1754: Fix Search API #3311

Merged
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.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
Expand Up @@ -79,37 +79,37 @@ public Collection<SearchData> retriveMatchingData(final SearchConditions searchC
public String searchSchema(final SearchConditions searchConditions) {

final String union = " union ";
final String clientMatchSql = " (select 'CLIENT' as entityType, c.id as entityId, c.display_name as entityName, c.external_id as entityExternalId, c.account_no as entityAccountNo "
final String clientMatchSql = "( (select 'CLIENT' as entityType, c.id as entityId, c.display_name as entityName, c.external_id as entityExternalId, c.account_no as entityAccountNo "
+ " , c.office_id as parentId, o.name as parentName, c.mobile_no as entityMobileNo,c.status_enum as entityStatusEnum, null as subEntityType, null as parentType "
+ " from m_client c join m_office o on o.id = c.office_id where o.hierarchy like :hierarchy and (c.account_no like :search or c.display_name like :search or c.external_id like :search or c.mobile_no like :search)) "
+ " order by c.id desc";
+ " order by c.id desc)";

final String loanMatchSql = " (select 'LOAN' as entityType, l.id as entityId, pl.name as entityName, l.external_id as entityExternalId, l.account_no as entityAccountNo "
final String loanMatchSql = "( (select 'LOAN' as entityType, l.id as entityId, pl.name as entityName, l.external_id as entityExternalId, l.account_no as entityAccountNo "
+ " , coalesce(c.id,g.id) as parentId, coalesce(c.display_name,g.display_name) as parentName, null as entityMobileNo, l.loan_status_id as entityStatusEnum, null as subEntityType, CASE WHEN g.id is null THEN 'client' ELSE 'group' END as parentType "
+ " from m_loan l left join m_client c on l.client_id = c.id left join m_group g ON l.group_id = g.id left join m_office o on o.id = c.office_id left join m_product_loan pl on pl.id=l.product_id where (o.hierarchy IS NULL OR o.hierarchy like :hierarchy) and (l.account_no like :search or l.external_id like :search)) "
+ " order by l.id desc";
+ " order by l.id desc)";

final String savingMatchSql = " (select 'SAVING' as entityType, s.id as entityId, sp.name as entityName, s.external_id as entityExternalId, s.account_no as entityAccountNo "
+ " , coalesce(c.id,g.id) as parentId, coalesce(c.display_name,g.display_name) as parentName, null as entityMobileNo, s.status_enum as entityStatusEnum, s.deposit_type_enum as subEntityType, CASE WHEN g.id is null THEN 'client' ELSE 'group' END as parentType "
final String savingMatchSql = "( (select 'SAVING' as entityType, s.id as entityId, sp.name as entityName, s.external_id as entityExternalId, s.account_no as entityAccountNo "
+ " , coalesce(c.id,g.id) as parentId, coalesce(c.display_name, g.display_name) as parentName, null as entityMobileNo, s.status_enum as entityStatusEnum, concat(s.deposit_type_enum, '') as subEntityType, CASE WHEN g.id is null THEN 'client' ELSE 'group' END as parentType "
Copy link
Contributor

@adamsaghy adamsaghy Jul 18, 2023

Choose a reason for hiding this comment

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

Every other places the subEntityType is null, i dont think thats the place to fix this. Please rather have a condition in the mapper if you dont wanna see DepositType.INVALID

Copy link
Contributor Author

Choose a reason for hiding this comment

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

The reason to have this is because the RDBMS can not have in the same column strings (due the nulls) and integer for the Savings for the column deposit types

Copy link
Contributor

Choose a reason for hiding this comment

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

Nothing changed...

Copy link
Contributor Author

Choose a reason for hiding this comment

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

If we leave the original SQL we get an error when we use more than one resource why? because the column subEntityType (as you have viewed) in almost all the SQL uses null (the null is considered as string type by the RDBMS) and in the Savings SQL filter It has a value for the Savings attribute deposit_type_enum (int type) and that difference to the RDBMS generates tan error, so It is not possible to manage after run the SQL in the mapper

Copy link
Contributor

Choose a reason for hiding this comment

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

how does solve this problem by changing this to not be null only one place? what happens if you search in SAVINGS accounts and LOAN accounts?

Copy link
Contributor Author

Choose a reason for hiding this comment

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

There will be not a problem, because in the SQL both values are now Strings values in the Loan will be null and in the Savings will be the Deposit Type (if exists) as String too. We get this because we are using the concat function. Even in the POJO SearchData this attribute is String type
private final String subEntityType;

+ " from m_savings_account s left join m_client c on s.client_id = c.id left join m_group g ON s.group_id = g.id left join m_office o on o.id = c.office_id left join m_savings_product sp on sp.id=s.product_id "
+ " where (o.hierarchy IS NULL OR o.hierarchy like :hierarchy) and (s.account_no like :search or s.external_id like :search)) "
+ " order by s.id desc";
+ " order by s.id desc)";

final String shareMatchSql = " (select 'SHARE' as entityType, s.id as entityId, sp.name as entityName, s.external_id as entityExternalId, s.account_no as entityAccountNo "
final String shareMatchSql = "( (select 'SHARE' as entityType, s.id as entityId, sp.name as entityName, s.external_id as entityExternalId, s.account_no as entityAccountNo "
+ " , c.id as parentId, c.display_name as parentName, null as entityMobileNo, s.status_enum as entityStatusEnum, null as subEntityType, 'client' as parentType "
+ " from m_share_account s left join m_client c on s.client_id = c.id left join m_office o on o.id = c.office_id left join m_share_product sp on sp.id=s.product_id "
+ " where (o.hierarchy IS NULL OR o.hierarchy like :hierarchy) and (s.account_no like :search or s.external_id like :search)) "
+ " order by s.id desc";
+ " order by s.id desc)";

final String clientIdentifierMatchSql = " (select 'CLIENTIDENTIFIER' as entityType, ci.id as entityId, ci.document_key as entityName, "
final String clientIdentifierMatchSql = "( (select 'CLIENTIDENTIFIER' as entityType, ci.id as entityId, ci.document_key as entityName, "
+ " null as entityExternalId, null as entityAccountNo, c.id as parentId, c.display_name as parentName,null as entityMobileNo, c.status_enum as entityStatusEnum, null as subEntityType, null as parentType "
+ " from m_client_identifier ci join m_client c on ci.client_id=c.id join m_office o on o.id = c.office_id "
+ " where o.hierarchy like :hierarchy and ci.document_key like :search ) " + " order by ci.id desc";
+ " where o.hierarchy like :hierarchy and ci.document_key like :search ) " + " order by ci.id desc)";

final String groupMatchSql = " (select CASE WHEN g.level_id=1 THEN 'CENTER' ELSE 'GROUP' END as entityType, g.id as entityId, g.display_name as entityName, g.external_id as entityExternalId, g.account_no as entityAccountNo, "
final String groupMatchSql = "( (select CASE WHEN g.level_id=1 THEN 'CENTER' ELSE 'GROUP' END as entityType, g.id as entityId, g.display_name as entityName, g.external_id as entityExternalId, g.account_no as entityAccountNo, "
+ " g.office_id as parentId, o.name as parentName, null as entityMobileNo, g.status_enum as entityStatusEnum, null as subEntityType, null as parentType "
+ " from m_group g join m_office o on o.id = g.office_id where o.hierarchy like :hierarchy and (g.account_no like :search or g.display_name like :search or g.external_id like :search or g.id like :search )) "
adamsaghy marked this conversation as resolved.
Show resolved Hide resolved
+ " order by g.id desc";
+ " from m_group g join m_office o on o.id = g.office_id where o.hierarchy like :hierarchy and (g.account_no like :search or g.display_name like :search or g.external_id like :search )) "
+ " order by g.id desc)";

final StringBuilder sql = new StringBuilder();

Expand Down
@@ -0,0 +1,109 @@
/**
* Licensed to the Apache Software Foundation (ASF) under one
* or more contributor license agreements. See the NOTICE file
* distributed with this work for additional information
* regarding copyright ownership. The ASF licenses this file
* to you under the Apache License, Version 2.0 (the
* "License"); you may not use this file except in compliance
* with the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing,
* software distributed under the License is distributed on an
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
* KIND, either express or implied. See the License for the
* specific language governing permissions and limitations
* under the License.
*/
package org.apache.fineract.integrationtests;

import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertNotNull;

import io.restassured.builder.RequestSpecBuilder;
import io.restassured.builder.ResponseSpecBuilder;
import io.restassured.http.ContentType;
import io.restassured.specification.RequestSpecification;
import io.restassured.specification.ResponseSpecification;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;
import org.apache.fineract.client.models.GetClientsClientIdResponse;
import org.apache.fineract.client.models.GetSearchResponse;
import org.apache.fineract.client.models.PostClientsResponse;
import org.apache.fineract.integrationtests.common.ClientHelper;
import org.apache.fineract.integrationtests.common.SearchHelper;
import org.apache.fineract.integrationtests.common.Utils;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Test;

public class SearchResourcesTest {

private ResponseSpecification responseSpec;
private RequestSpecification requestSpec;

@BeforeEach
public void setup() {
Utils.initializeRESTAssured();
this.requestSpec = new RequestSpecBuilder().setContentType(ContentType.JSON).build();
this.requestSpec.header("Authorization", "Basic " + Utils.loginIntoServerAndGetBase64EncodedAuthenticationKey());
this.responseSpec = new ResponseSpecBuilder().expectStatusCode(200).build();
}

@Test
public void searchAnyValueOverAllResources() {
final List<String> resources = Arrays.asList("clients", "clientIdentifiers", "groups", "savings", "shares", "loans");

final String query = Utils.randomStringGenerator("C", 12);
final ArrayList<GetSearchResponse> searchResponse = SearchHelper.getSearch(requestSpec, responseSpec, query, Boolean.TRUE,
resources.toString());
assertNotNull(searchResponse);
assertEquals(0, searchResponse.size());
}

@Test
public void searchAnyValueOverClientResources() {
final List<String> resources = Arrays.asList("clients");

final String query = Utils.randomStringGenerator("C", 12);
final ArrayList<GetSearchResponse> searchResponse = SearchHelper.getSearch(requestSpec, responseSpec, query, Boolean.TRUE,
getResources(resources));
assertNotNull(searchResponse);
assertEquals(0, searchResponse.size());
}

@Test
public void searchOverClientResources() {
final List<String> resources = Arrays.asList("clients");

// Client and Loan account creation
String jsonPayload = ClientHelper.getBasicClientAsJSON(ClientHelper.DEFAULT_OFFICE_ID, ClientHelper.LEGALFORM_ID_PERSON, null);
final PostClientsResponse clientResponse = ClientHelper.addClientAsPerson(requestSpec, responseSpec, jsonPayload);
final Long clientId = clientResponse.getClientId();
final GetClientsClientIdResponse getClientResponse = ClientHelper.getClient(requestSpec, responseSpec, clientId.intValue());
final String query = getClientResponse.getAccountNo();

final ArrayList<GetSearchResponse> searchResponse = SearchHelper.getSearch(requestSpec, responseSpec, query, Boolean.FALSE,
getResources(resources));
assertNotNull(searchResponse);
assertEquals(1, searchResponse.size());
assertEquals("Client name comparation", getClientResponse.getDisplayName(), searchResponse.get(0).getEntityName());
}

@Test
public void searchAnyValueOverLoanResources() {
final List<String> resources = Arrays.asList("loans");

final String query = Utils.randomStringGenerator("L", 12);
final ArrayList<GetSearchResponse> searchResponse = SearchHelper.getSearch(requestSpec, responseSpec, query, Boolean.TRUE,
getResources(resources));
assertNotNull(searchResponse);
assertEquals(0, searchResponse.size());
}

private String getResources(final List<String> resources) {
return String.join(",", resources);
}

}
@@ -0,0 +1,52 @@
/**
* Licensed to the Apache Software Foundation (ASF) under one
* or more contributor license agreements. See the NOTICE file
* distributed with this work for additional information
* regarding copyright ownership. The ASF licenses this file
* to you under the Apache License, Version 2.0 (the
* "License"); you may not use this file except in compliance
* with the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing,
* software distributed under the License is distributed on an
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
* KIND, either express or implied. See the License for the
* specific language governing permissions and limitations
* under the License.
*/
package org.apache.fineract.integrationtests.common;

import com.google.gson.Gson;
import com.linecorp.armeria.internal.shaded.guava.reflect.TypeToken;
import io.restassured.specification.RequestSpecification;
import io.restassured.specification.ResponseSpecification;
import java.lang.reflect.Type;
import java.util.ArrayList;
import lombok.extern.slf4j.Slf4j;
import org.apache.fineract.client.models.GetSearchResponse;
import org.apache.fineract.client.util.JSON;

@Slf4j
public final class SearchHelper {

private static final Gson GSON = new JSON().getGson();

private SearchHelper() {

}

private static final String SEARCH_URL = "/fineract-provider/api/v1/search?" + Utils.TENANT_IDENTIFIER;

public static ArrayList<GetSearchResponse> getSearch(final RequestSpecification requestSpec, final ResponseSpecification responseSpec,
final String query, final Boolean exactMatch, final String resources) {
final String urlSearch = SEARCH_URL + "&exactMatch=" + exactMatch.toString() + "&query=" + query + "&resource=" + resources;
log.info("URL to search: {}", urlSearch);
final String response = Utils.performServerGet(requestSpec, responseSpec, urlSearch);
log.info("Result: {}", response);
Type searchResourcesListType = new TypeToken<ArrayList<GetSearchResponse>>() {}.getType();
return GSON.fromJson(response, searchResourcesListType);
}

}