From b3d5178767b1e1a957a9e5d1866a35e8737491f3 Mon Sep 17 00:00:00 2001 From: Martin Lowe <martin.lowe@eclipse-foundation.org> Date: Thu, 6 Feb 2025 14:24:57 -0500 Subject: [PATCH] update: add organization filter endpoint for working groups Using the available data from the membership endpoint, checks and retrieves an organization by ID, and maps the available WGPA for the org to the working groups. Resolves #54 --- spec/openapi.yaml | 27 +++++++++- .../wg/api/MembershipAPI.java | 2 +- .../wg/models/MemberOrganization.java | 14 +++++- .../wg/resource/WorkingGroupsResource.java | 7 +++ .../wg/services/WorkingGroupsService.java | 8 +++ .../impl/DefaultWorkingGroupsService.java | 45 +++++++++++------ .../resource/WorkingGroupsResourceTest.java | 50 +++++++++++++++++++ .../wg/test/api/MockMembershipAPI.java | 20 +++++--- 8 files changed, 148 insertions(+), 25 deletions(-) diff --git a/spec/openapi.yaml b/spec/openapi.yaml index b577120..423399b 100644 --- a/spec/openapi.yaml +++ b/spec/openapi.yaml @@ -174,7 +174,32 @@ paths: $ref: "#/components/schemas/Error" 500: description: Error while retrieving data - + /organization/{id}: + parameters: + - name: id + in: path + description: The ID of the organization to retrieve working groups for + required: true + schema: + type: integer + get: + summary: Working Group by Organization ID + description: Returns working groups that the target organization is a member of + responses: + 200: + description: Success + content: + application/json: + schema: + $ref: "#/components/schemas/WorkingGroups" + 404: + description: Not Found + content: + application/json: + schema: + $ref: "#/components/schemas/Error" + 500: + description: Error while retrieving data components: securitySchemes: openId: diff --git a/src/main/java/org/eclipsefoundation/wg/api/MembershipAPI.java b/src/main/java/org/eclipsefoundation/wg/api/MembershipAPI.java index d58e7ee..e0ad726 100644 --- a/src/main/java/org/eclipsefoundation/wg/api/MembershipAPI.java +++ b/src/main/java/org/eclipsefoundation/wg/api/MembershipAPI.java @@ -39,6 +39,6 @@ public interface MembershipAPI { */ @GET @Compressed - @Path("organizations/slim") + @Path("organizations") RestResponse<List<MemberOrganization>> getWGMemberOrgs(@BeanParam BaseAPIParameters baseParams); } diff --git a/src/main/java/org/eclipsefoundation/wg/models/MemberOrganization.java b/src/main/java/org/eclipsefoundation/wg/models/MemberOrganization.java index c69b71f..61e7080 100644 --- a/src/main/java/org/eclipsefoundation/wg/models/MemberOrganization.java +++ b/src/main/java/org/eclipsefoundation/wg/models/MemberOrganization.java @@ -9,12 +9,17 @@ **********************************************************************/ package org.eclipsefoundation.wg.models; +import java.util.List; import java.util.Objects; +import com.fasterxml.jackson.annotation.JsonProperty; +import com.fasterxml.jackson.annotation.JsonProperty.Access; + /** * Entity representing a working group member organization. */ -public record MemberOrganization(String name, Integer organizationId, MemberOrganizationLogos logos) { +public record MemberOrganization(String name, @JsonProperty(value = "organization_id") Integer organizationId, MemberOrganizationLogos logos, + @JsonProperty(access = Access.WRITE_ONLY) List<WorkingGroupParticipationAgreement> wgpas) { public MemberOrganization { Objects.requireNonNull(name); @@ -27,4 +32,11 @@ public record MemberOrganization(String name, Integer organizationId, MemberOrga public record MemberOrganizationLogos(String print, String web) { } + + /** + * Entity representing a member organization's WGPAs on file. + */ + public record WorkingGroupParticipationAgreement(String documentId, String description, String level, String workingGroup) { + + } } diff --git a/src/main/java/org/eclipsefoundation/wg/resource/WorkingGroupsResource.java b/src/main/java/org/eclipsefoundation/wg/resource/WorkingGroupsResource.java index a6892a0..1662970 100644 --- a/src/main/java/org/eclipsefoundation/wg/resource/WorkingGroupsResource.java +++ b/src/main/java/org/eclipsefoundation/wg/resource/WorkingGroupsResource.java @@ -65,6 +65,13 @@ public class WorkingGroupsResource { return RestResponse.ok(new ArrayList<>(wgService.get(status, parentOrganization))); } + @GET + @Path("organization/{organizationId}") + public RestResponse<List<WorkingGroup>> getWorkingGroupsForOrganization(@RestPath String organizationId) { + // return the results as a response + return RestResponse.ok(new ArrayList<>(wgService.getByOrganizationId(organizationId))); + } + @GET @Path("{alias}") public RestResponse<WorkingGroup> getWorkingGroup(@RestPath String alias) { diff --git a/src/main/java/org/eclipsefoundation/wg/services/WorkingGroupsService.java b/src/main/java/org/eclipsefoundation/wg/services/WorkingGroupsService.java index 46681a2..cf4a422 100644 --- a/src/main/java/org/eclipsefoundation/wg/services/WorkingGroupsService.java +++ b/src/main/java/org/eclipsefoundation/wg/services/WorkingGroupsService.java @@ -35,6 +35,14 @@ public interface WorkingGroupsService { */ public Set<WorkingGroup> get(List<String> projectStatus, List<String> parentOrganization); + /** + * Returns a set of working groups, filtering to only those that the target organization is a member of. + * + * @param organizationId the organization to retrieve working groups for + * @return set of working groups for the matching organization ID + */ + public Set<WorkingGroup> getByOrganizationId(String organizationId); + /** * Returns a working group based on the given alias. * diff --git a/src/main/java/org/eclipsefoundation/wg/services/impl/DefaultWorkingGroupsService.java b/src/main/java/org/eclipsefoundation/wg/services/impl/DefaultWorkingGroupsService.java index d7f1ab5..2bd5209 100644 --- a/src/main/java/org/eclipsefoundation/wg/services/impl/DefaultWorkingGroupsService.java +++ b/src/main/java/org/eclipsefoundation/wg/services/impl/DefaultWorkingGroupsService.java @@ -20,6 +20,7 @@ import java.util.HashSet; import java.util.List; import java.util.Map; import java.util.Map.Entry; +import java.util.Objects; import java.util.Optional; import java.util.Set; import java.util.stream.Collectors; @@ -47,11 +48,14 @@ import org.eclipsefoundation.wg.services.WorkingGroupsService; import org.slf4j.Logger; import org.slf4j.LoggerFactory; +import com.cronutils.utils.StringUtils; import com.fasterxml.jackson.databind.ObjectMapper; import io.quarkus.runtime.Startup; import jakarta.annotation.PostConstruct; import jakarta.enterprise.context.ApplicationScoped; +import jakarta.ws.rs.BadRequestException; +import jakarta.ws.rs.NotFoundException; import jakarta.ws.rs.ServerErrorException; import jakarta.ws.rs.core.MultivaluedHashMap; import jakarta.ws.rs.core.Response.Status; @@ -123,6 +127,20 @@ public class DefaultWorkingGroupsService implements WorkingGroupsService { return workingGroups.get(alias); } + @Override + public Set<WorkingGroup> getByOrganizationId(String organizationId) { + if (!StringUtils.isNumeric(organizationId)) { + throw new BadRequestException("Organization ID should be a valid number"); + } + Optional<MemberOrganization> org = getOrganizationById(Integer.parseInt(organizationId)); + if (org.isEmpty()) { + throw new NotFoundException("Could not find an org with the given id: " + TransformationHelper.formatLog(organizationId)); + } + + // map the WGPA aliases to the actual working groups, dropping non matching null values + return org.get().wgpas().stream().map(wgpa -> getByAlias(wgpa.workingGroup())).filter(Objects::nonNull).collect(Collectors.toSet()); + } + @Override public Map<String, List<String>> getWGPADocumentIDs() { return workingGroups.values().stream().collect(Collectors.toMap(WorkingGroup::alias, WorkingGroupsHelper::extractWGPADocumentIDs)); @@ -215,7 +233,7 @@ public class DefaultWorkingGroupsService implements WorkingGroupsService { String orgName = contact.organization().name1(); Integer orgId = contact.organization().organizationID(); MemberOrganizationLogos logos = getOrgLogosById(contact.organization().organizationID()); - return new CommitteeMember(name, username, new MemberOrganization(orgName, orgId, logos)); + return new CommitteeMember(name, username, new MemberOrganization(orgName, orgId, logos, Collections.emptyList())); } /** @@ -225,20 +243,9 @@ public class DefaultWorkingGroupsService implements WorkingGroupsService { * @return A MemberOrganizationLogos entity or null. */ private MemberOrganizationLogos getOrgLogosById(int orgId) { - List<MemberOrganization> memberOrgs = cacheManager - .getList(ParameterizedCacheKeyBuilder - .builder() - .id("all") - .clazz(MemberOrganization.class) - .params(new MultivaluedHashMap<>()) - .build()); - // Return null logos object if none found - return memberOrgs - .stream() - .filter(m -> m.organizationId() == orgId) - .findFirst() - .orElse(new MemberOrganization("", orgId, new MemberOrganizationLogos(null, null))) + return getOrganizationById(orgId) + .orElse(new MemberOrganization("", orgId, new MemberOrganizationLogos(null, null), Collections.emptyList())) .logos(); } @@ -287,4 +294,14 @@ public class DefaultWorkingGroupsService implements WorkingGroupsService { return orgContacts.get(); } + private Optional<MemberOrganization> getOrganizationById(int organizationId) { + List<MemberOrganization> memberOrgs = cacheManager + .getList(ParameterizedCacheKeyBuilder + .builder() + .id("all") + .clazz(MemberOrganization.class) + .params(new MultivaluedHashMap<>()) + .build()); + return memberOrgs.stream().filter(m -> m.organizationId() == organizationId).findFirst(); + } } diff --git a/src/test/java/org/eclipsefoundation/wg/resource/WorkingGroupsResourceTest.java b/src/test/java/org/eclipsefoundation/wg/resource/WorkingGroupsResourceTest.java index ccab398..6b96904 100644 --- a/src/test/java/org/eclipsefoundation/wg/resource/WorkingGroupsResourceTest.java +++ b/src/test/java/org/eclipsefoundation/wg/resource/WorkingGroupsResourceTest.java @@ -26,6 +26,8 @@ class WorkingGroupsResourceTest { public static final String WG_STATUS_URL = WGS_BASE_URL + "?status={param}"; public static final String WG_STATUSES_URL = WGS_BASE_URL + "?status={param1}&status={param2}"; + + public static final String WG_BY_ORGANIZATION_URL = WGS_BASE_URL + "/organization/{id}"; public static final String WG_BASE_URL = WGS_BASE_URL + "/{alias}"; @@ -54,6 +56,17 @@ class WorkingGroupsResourceTest { public static final EndpointTestCase GET_BY_ALIAS_404 = TestCaseHelper .buildNotFoundCase(WG_BASE_URL, new String[] { "invalid-Group" }, SchemaNamespaceHelper.ERROR_SCHEMA_PATH); + + /* + * GET_BY_ORGANIZATION + */ + public static final EndpointTestCase GET_BY_ORGANIZATION_SUCCESS = TestCaseHelper + .buildSuccessCase(WG_BY_ORGANIZATION_URL, new String[] { "11" }, SchemaNamespaceHelper.WORKING_GROUPS_SCHEMA_PATH); + + public static final EndpointTestCase GET_BY_ORGANIZATION_404 = TestCaseHelper + .buildNotFoundCase(WG_BY_ORGANIZATION_URL, new String[] { "12345" }, SchemaNamespaceHelper.ERROR_SCHEMA_PATH); + public static final EndpointTestCase GET_BY_ORGANIZATION_BAD_REQUEST = TestCaseHelper + .buildBadRequestCase(WG_BY_ORGANIZATION_URL, new String[] { "ibm" }, SchemaNamespaceHelper.ERROR_SCHEMA_PATH); /* * GET_RESOURCES @@ -176,6 +189,43 @@ class WorkingGroupsResourceTest { void getByAlias_failure_invalidName_validateSchema() { EndpointTestBuilder.from(GET_BY_ALIAS_404).run(); } + + /* + * GET_BY_ORGANIZATION + */ + @Test + void getWorkingGroupsForOrganization_success() { + EndpointTestBuilder.from(GET_BY_ORGANIZATION_SUCCESS).run(); + } + + @Test + void getWorkingGroupsForOrganization_success_validResponseFormat() { + EndpointTestBuilder.from(GET_BY_ORGANIZATION_SUCCESS).andCheckFormat().run(); + } + + @Test + void getWorkingGroupsForOrganization_success_matchingSpec() { + EndpointTestBuilder.from(GET_BY_ORGANIZATION_SUCCESS).andCheckSchema().run(); + } + + @Test + void getWorkingGroupsForOrganization_failure_noMatchingOrg() { + EndpointTestBuilder.from(GET_BY_ORGANIZATION_404).run(); + } + + @Test + void getWorkingGroupsForOrganization_failure_noMatchingOrg_validateSchema() { + EndpointTestBuilder.from(GET_BY_ORGANIZATION_404).andCheckSchema().run(); + } + @Test + void getWorkingGroupsForOrganization_failure_invalidName() { + EndpointTestBuilder.from(GET_BY_ORGANIZATION_BAD_REQUEST).run(); + } + + @Test + void getWorkingGroupsForOrganization_failure_invalidName_validateSchema() { + EndpointTestBuilder.from(GET_BY_ORGANIZATION_BAD_REQUEST).andCheckSchema().run(); + } /* * GET_RESOURCES diff --git a/src/test/java/org/eclipsefoundation/wg/test/api/MockMembershipAPI.java b/src/test/java/org/eclipsefoundation/wg/test/api/MockMembershipAPI.java index 8a8cc50..abbe5c9 100644 --- a/src/test/java/org/eclipsefoundation/wg/test/api/MockMembershipAPI.java +++ b/src/test/java/org/eclipsefoundation/wg/test/api/MockMembershipAPI.java @@ -13,6 +13,7 @@ package org.eclipsefoundation.wg.test.api; import java.util.ArrayList; import java.util.Arrays; +import java.util.Collections; import java.util.List; import org.eclipse.microprofile.rest.client.inject.RestClient; @@ -37,14 +38,17 @@ public class MockMembershipAPI implements MembershipAPI { this.memberOrgs = new ArrayList<>(); this.memberOrgs .addAll(Arrays - .asList(new MemberOrganization("org", 11, new MemberOrganizationLogos("url", "url")), - new MemberOrganization("fake company", 22, new MemberOrganizationLogos("url", "url")), - new MemberOrganization("real company", 33, new MemberOrganizationLogos("url", "url")), - new MemberOrganization("other org", 44, new MemberOrganizationLogos("url", "url")), - new MemberOrganization("fake inc", 55, new MemberOrganizationLogos("url", "url")), - new MemberOrganization("org name", 66, new MemberOrganizationLogos("url", "url")), - new MemberOrganization("org inc", 77, new MemberOrganizationLogos("url", "url")), - new MemberOrganization("company foundation", 88, new MemberOrganizationLogos("url", "url")))); + .asList(new MemberOrganization("org", 11, new MemberOrganizationLogos("url", "url"), Collections.emptyList()), + new MemberOrganization("fake company", 22, new MemberOrganizationLogos("url", "url"), + Collections.emptyList()), + new MemberOrganization("real company", 33, new MemberOrganizationLogos("url", "url"), + Collections.emptyList()), + new MemberOrganization("other org", 44, new MemberOrganizationLogos("url", "url"), Collections.emptyList()), + new MemberOrganization("fake inc", 55, new MemberOrganizationLogos("url", "url"), Collections.emptyList()), + new MemberOrganization("org name", 66, new MemberOrganizationLogos("url", "url"), Collections.emptyList()), + new MemberOrganization("org inc", 77, new MemberOrganizationLogos("url", "url"), Collections.emptyList()), + new MemberOrganization("company foundation", 88, new MemberOrganizationLogos("url", "url"), + Collections.emptyList()))); } @Override -- GitLab