diff --git a/build.gradle b/build.gradle index 9ae7948331..70d2ba3fb7 100644 --- a/build.gradle +++ b/build.gradle @@ -160,6 +160,7 @@ dependencies { testImplementation 'org.powermock:powermock-module-junit4-rule:2.0.9' testImplementation 'org.junit.jupiter:junit-jupiter-api:5.10.2' + testImplementation 'org.junit.jupiter:junit-jupiter-params:5.10.2' testRuntimeOnly 'org.junit.jupiter:junit-jupiter-engine:5.10.2' // Avoid "ERROR StatusLogger Log4j2 could not find a logging implementation. diff --git a/client/src/components/SearchFilters.js b/client/src/components/SearchFilters.js index 456cf94870..6f5c3c56e8 100644 --- a/client/src/components/SearchFilters.js +++ b/client/src/components/SearchFilters.js @@ -42,10 +42,7 @@ import LOCATIONS_ICON from "resources/locations.png" import PEOPLE_ICON from "resources/people.png" import POSITIONS_ICON from "resources/positions.png" import TASKS_ICON from "resources/tasks.png" -import { - POSITION_POSITION_TYPE_FILTER_KEY, - RECURSE_STRATEGY -} from "searchUtils" +import { RECURSE_STRATEGY } from "searchUtils" import Settings from "settings" export const SearchQueryPropType = PropTypes.shape({ @@ -407,6 +404,23 @@ export const searchFilters = function(includeAdminFilters) { labels: ["Yes", "No"] } }, + "Holding Position As": { + component: SelectFilter, + deserializer: deserializeSelectFilter, + props: { + queryKey: "positionType", + options: [ + Position.TYPE.REGULAR, + Position.TYPE.SUPERUSER, + Position.TYPE.ADMINISTRATOR + ], + labels: [ + Settings.fields.regular.position.name, + Settings.fields.superuser.position.name, + Settings.fields.administrator.position.name + ] + } + }, "Pending Verification": { component: RadioButtonFilter, deserializer: deserializeSelectFilter, @@ -458,7 +472,7 @@ export const searchFilters = function(includeAdminFilters) { filters[SEARCH_OBJECT_TYPES.POSITIONS] = { filters: { - [POSITION_POSITION_TYPE_FILTER_KEY]: { + Type: { component: SelectFilter, dictProps: Settings.fields.position.type, deserializer: deserializeSelectFilter, @@ -473,8 +487,7 @@ export const searchFilters = function(includeAdminFilters) { Settings.fields.regular.position.name, Settings.fields.superuser.position.name, Settings.fields.administrator.position.name - ], - isPositionTypeFilter: true + ] } }, "Within Organization": { diff --git a/client/src/components/advancedSearch/SelectFilter.js b/client/src/components/advancedSearch/SelectFilter.js index 9e18fe0114..64bd30d5ef 100644 --- a/client/src/components/advancedSearch/SelectFilter.js +++ b/client/src/components/advancedSearch/SelectFilter.js @@ -10,7 +10,6 @@ const SelectFilter = ({ queryKey, value: inputValue, onChange, - isPositionTypeFilter, options, labels }) => { @@ -57,8 +56,7 @@ SelectFilter.propTypes = { }) ]), onChange: PropTypes.func, // eslint-disable-line react/no-unused-prop-types - asFormField: PropTypes.bool, - isPositionTypeFilter: PropTypes.bool + asFormField: PropTypes.bool } SelectFilter.defaultProps = { asFormField: true diff --git a/client/src/searchUtils.js b/client/src/searchUtils.js index c6f2231e40..7acd1a005f 100644 --- a/client/src/searchUtils.js +++ b/client/src/searchUtils.js @@ -1,5 +1,3 @@ -export const POSITION_POSITION_TYPE_FILTER_KEY = "Position Type" - export const RECURSE_STRATEGY = { NONE: "NONE", CHILDREN: "CHILDREN", diff --git a/client/tests/webdriver/baseSpecs/advancedSearch.spec.js b/client/tests/webdriver/baseSpecs/advancedSearch.spec.js index 1f42d94f9e..3ac64d5d6a 100644 --- a/client/tests/webdriver/baseSpecs/advancedSearch.spec.js +++ b/client/tests/webdriver/baseSpecs/advancedSearch.spec.js @@ -1,32 +1,33 @@ import { expect } from "chai" +import _isEmpty from "lodash/isEmpty" import AdvancedSearch from "../pages/advancedSearch" import Home from "../pages/home.page" const ANET_OBJECT_TYPES = { Reports: { - sampleFilter: "Author" + sampleFilters: ["Author"] }, People: { - sampleFilter: "Organization" + sampleFilters: ["Within Organization", "Holding Position As"] }, Organizations: { - sampleFilter: "Within Organization" + sampleFilters: ["Within Organization"] }, Positions: { - sampleFilter: "Type" + sampleFilters: ["Type"] }, Locations: { - sampleFilter: "Type" + sampleFilters: ["Type"] }, "Objective / Efforts": { - sampleFilter: "Organization" + sampleFilters: ["Within Organization"] }, "Authorization Groups": { - sampleFilter: undefined + sampleFilters: [] } } const COMMON_FILTER_TEXT = "Status" -const ALL_COMMON_FILTERS = [COMMON_FILTER_TEXT, "Subscribed"] +const ALL_COMMON_FILTERS = [COMMON_FILTER_TEXT, "Subscribed", "With Email"] const PERSON_DEFAULT_FILTER = "Pending Verification" const PERSON_INDEX = 1 @@ -107,9 +108,9 @@ describe("When using advanced search", () => { const buttons = await AdvancedSearch.getAnetObjectSearchToggleButtons() for (const [i, button] of buttons.entries()) { await button.click() - const sampleFilter = - ANET_OBJECT_TYPES[await getObjectType(i)].sampleFilter - if (sampleFilter) { + const sampleFilters = + ANET_OBJECT_TYPES[await getObjectType(i)].sampleFilters + if (!_isEmpty(sampleFilters)) { await (await AdvancedSearch.getAddFilterButtonText()).waitForExist() await (await AdvancedSearch.getAddFilterButtonText()).waitForDisplayed() expect( @@ -123,17 +124,18 @@ describe("When using advanced search", () => { const buttons = await AdvancedSearch.getAnetObjectSearchToggleButtons() for (const [i, button] of buttons.entries()) { await button.click() - const sampleFilter = - ANET_OBJECT_TYPES[await getObjectType(i)].sampleFilter - if (sampleFilter) { - await (await AdvancedSearch.getAddFilterButtonText()).waitForExist() - await (await AdvancedSearch.getAddFilterButtonText()).waitForDisplayed() + const sampleFilters = + ANET_OBJECT_TYPES[await getObjectType(i)].sampleFilters + if (!_isEmpty(sampleFilters)) { await (await AdvancedSearch.getAddFilterButton()).click() await (await AdvancedSearch.getAddFilterPopover()).waitForExist() await (await AdvancedSearch.getAddFilterPopover()).waitForDisplayed() - expect( - await (await AdvancedSearch.getAddFilterPopover()).getText() - ).to.match(new RegExp(sampleFilter)) + for (const sampleFilter of sampleFilters) { + expect( + await (await AdvancedSearch.getAddFilterPopover()).getText() + ).to.match(new RegExp(sampleFilter)) + await (await AdvancedSearch.getSearchFilter(sampleFilter)).click() + } } } }) diff --git a/src/main/java/mil/dds/anet/beans/search/PersonSearchQuery.java b/src/main/java/mil/dds/anet/beans/search/PersonSearchQuery.java index a9f335d65c..16bfb8cfc7 100644 --- a/src/main/java/mil/dds/anet/beans/search/PersonSearchQuery.java +++ b/src/main/java/mil/dds/anet/beans/search/PersonSearchQuery.java @@ -3,6 +3,8 @@ import io.leangen.graphql.annotations.GraphQLInputField; import io.leangen.graphql.annotations.GraphQLQuery; import java.time.Instant; +import java.util.List; +import mil.dds.anet.beans.Position.PositionType; public class PersonSearchQuery extends SubscribableObjectSearchQuery { @@ -45,6 +47,11 @@ public class PersonSearchQuery extends SubscribableObjectSearchQuery positionType; + public PersonSearchQuery() { super(PersonSearchSortBy.NAME); this.setPageSize(100); @@ -131,6 +138,14 @@ public void setHasBiography(Boolean hasBiography) { this.hasBiography = hasBiography; } + public List getPositionType() { + return positionType; + } + + public void setPositionType(List positionType) { + this.positionType = positionType; + } + @Override public PersonSearchQuery clone() throws CloneNotSupportedException { return (PersonSearchQuery) super.clone(); diff --git a/src/main/java/mil/dds/anet/search/AbstractPersonSearcher.java b/src/main/java/mil/dds/anet/search/AbstractPersonSearcher.java index 0588244d34..cd74392a86 100644 --- a/src/main/java/mil/dds/anet/search/AbstractPersonSearcher.java +++ b/src/main/java/mil/dds/anet/search/AbstractPersonSearcher.java @@ -14,6 +14,7 @@ import mil.dds.anet.database.mappers.PersonMapper; import mil.dds.anet.search.AbstractSearchQueryBuilder.Comparison; import mil.dds.anet.utils.DaoUtils; +import mil.dds.anet.utils.Utils; import ru.vyarus.guicey.jdbi3.tx.InTransaction; public abstract class AbstractPersonSearcher extends AbstractSearcher @@ -49,7 +50,7 @@ protected void buildQuery(Set subFields, PersonSearchQuery query) { qb.addFromClause("people"); if (query.getOrgUuid() != null || query.getLocationUuid() != null - || query.getMatchPositionName()) { + || query.getMatchPositionName() || !Utils.isEmptyOrNull(query.getPositionType())) { qb.addFromClause("LEFT JOIN positions ON people.uuid = positions.\"currentPersonUuid\""); } @@ -92,6 +93,8 @@ protected void buildQuery(Set subFields, PersonSearchQuery query) { } } + qb.addInClause("types", "positions.type", query.getPositionType()); + if (Boolean.TRUE.equals(query.isInMyReports())) { qb.addSelectClause("\"inMyReports\".max AS \"inMyReports_max\""); qb.addFromClause("JOIN (" diff --git a/src/test/java/mil/dds/anet/test/resources/PersonResourceTest.java b/src/test/java/mil/dds/anet/test/resources/PersonResourceTest.java index ec2448cb24..0cf6c0b96f 100644 --- a/src/test/java/mil/dds/anet/test/resources/PersonResourceTest.java +++ b/src/test/java/mil/dds/anet/test/resources/PersonResourceTest.java @@ -40,6 +40,8 @@ import mil.dds.anet.test.utils.UtilsTest; import mil.dds.anet.utils.DaoUtils; import org.junit.jupiter.api.Test; +import org.junit.jupiter.params.ParameterizedTest; +import org.junit.jupiter.params.provider.EnumSource; public class PersonResourceTest extends AbstractResourceTest { @@ -317,6 +319,19 @@ void searchPerson() { assertThat(searchResults.getList()).isNotEmpty(); } + @ParameterizedTest + @EnumSource(value = PositionType.class, names = {"_PLACEHOLDER_1_"}, + mode = EnumSource.Mode.EXCLUDE) + void searchUsersByPositionType(PositionType positionType) { + final PersonSearchQueryInput query = PersonSearchQueryInput.builder().withPageSize(0) + .withPositionType(List.of(positionType)).build(); + final AnetBeanList_Person people = + withCredentials(adminUser, t -> queryExecutor.personList(getListFields(FIELDS), query)); + assertThat(people).isNotNull(); + assertThat(people.getList()).isNotEmpty() + .allMatch(p -> p.getPosition().getType() == positionType); + } + @Test void testInactivatePerson() { final OrganizationSearchQueryInput query = diff --git a/src/test/resources/anet.graphql b/src/test/resources/anet.graphql index 96b727f602..81635d814f 100644 --- a/src/test/resources/anet.graphql +++ b/src/test/resources/anet.graphql @@ -691,6 +691,7 @@ input PersonSearchQueryInput { pageNum: Int pageSize: Int pendingVerification: Boolean + positionType: [PositionType] rank: String sortBy: PersonSearchSortBy sortOrder: SortOrder