Skip to content
Snippets Groups Projects
Commit 3b63ee02 authored by Martin Lowe's avatar Martin Lowe :flag_ca:
Browse files

Iss #132 - Update API to use shared projects services

In the API common lib, there is a new efservices module which provides
access to the Accounts and Projects service in a standardized way. This
PR removes most of the code associated with previous projects logic and
keeps a helper to help with formating and filtering the data.

This currently uses a snapshot version, so there will need to be a
release for this code to go live and be stable.
parent e04f9e1a
No related branches found
No related tags found
1 merge request!140Iss #132 - Projects and auth token code migrations, update openapi spec
Showing
with 330 additions and 671 deletions
......@@ -125,7 +125,7 @@
readTrusted 'pom.xml'
sh 'make generate-spec'
withCredentials([string(credentialsId: 'sonarcloud-token-git-eca-rest-api', variable: 'SONAR_TOKEN')]) {
sh 'mvn clean verify org.sonarsource.scanner.maven:sonar-maven-plugin:sonar -B -Dsonar.login=${SONAR_TOKEN}'
sh 'mvn org.sonarsource.scanner.maven:sonar-maven-plugin:sonar -Dsonar.login=${SONAR_TOKEN} -Dmaven.test.skip=true'
  • Maintainer

    I think this change needs to be reverted. clean verify is required here.

  • Please register or sign in to reply
}
stash name: "target", includes: "target/**/*"
}
......
......@@ -5,7 +5,7 @@
<artifactId>git-eca</artifactId>
<version>1.1.0</version>
<properties>
<eclipse-api-version>0.7.4</eclipse-api-version>
<eclipse-api-version>0.7.6-SNAPSHOT</eclipse-api-version>
<compiler-plugin.version>3.8.1</compiler-plugin.version>
<maven.compiler.parameters>true</maven.compiler.parameters>
<maven.compiler.source>11</maven.compiler.source>
......@@ -14,7 +14,7 @@
<project.reporting.outputEncoding>UTF-8</project.reporting.outputEncoding>
<quarkus.platform.artifact-id>quarkus-universe-bom</quarkus.platform.artifact-id>
<quarkus.platform.group-id>io.quarkus</quarkus.platform.group-id>
<quarkus.platform.version>2.14.2.Final</quarkus.platform.version>
<quarkus.platform.version>2.16.3.Final</quarkus.platform.version>
<surefire-plugin.version>2.22.1</surefire-plugin.version>
<auto-value.version>1.8.2</auto-value.version>
<org.mapstruct.version>1.4.1.Final</org.mapstruct.version>
......@@ -63,6 +63,11 @@
<artifactId>quarkus-persistence</artifactId>
<version>${eclipse-api-version}</version>
</dependency>
<dependency>
<groupId>org.eclipsefoundation</groupId>
<artifactId>quarkus-efservices</artifactId>
<version>${eclipse-api-version}</version>
</dependency>
<dependency>
<groupId>io.quarkus</groupId>
<artifactId>quarkus-resteasy</artifactId>
......
/*********************************************************************
* Copyright (c) 2022 Eclipse Foundation.
*
* This program and the accompanying materials are made
* available under the terms of the Eclipse Public License 2.0
* which is available at https://www.eclipse.org/legal/epl-2.0/
*
* Author: Martin Lowe <martin.lowe@eclipse-foundation.org>
*
* SPDX-License-Identifier: EPL-2.0
**********************************************************************/
package org.eclipsefoundation.git.eca.api;
import javax.enterprise.context.ApplicationScoped;
import javax.ws.rs.BeanParam;
import javax.ws.rs.GET;
import javax.ws.rs.Path;
import javax.ws.rs.Produces;
import javax.ws.rs.core.Response;
import org.eclipse.microprofile.rest.client.inject.RegisterRestClient;
import org.eclipsefoundation.core.service.APIMiddleware.BaseAPIParameters;
import org.jboss.resteasy.annotations.GZIP;
/**
* Interface for interacting with the PMI Projects API. Used to link Git
* repos/projects with an Eclipse project to validate committer access.
*
* @author Martin Lowe
*
*/
@ApplicationScoped
@Path("/api")
@RegisterRestClient
@GZIP
public interface ProjectsAPI {
/**
* Retrieves all projects with the given repo URL.
*
* @param repoUrl the target repos URL
* @return a list of Eclipse Foundation projects.
*/
@GET
@Path("projects")
Response getProjects(@BeanParam BaseAPIParameters baseParams);
@GET
@Path("interest-groups")
@Produces("application/json")
Response getInterestGroups(@BeanParam BaseAPIParameters params);
}
package org.eclipsefoundation.git.eca.api.models;
import java.util.List;
import org.eclipsefoundation.git.eca.api.models.Project.GitlabProject;
import org.eclipsefoundation.git.eca.api.models.Project.User;
import com.fasterxml.jackson.databind.annotation.JsonDeserialize;
import com.fasterxml.jackson.databind.annotation.JsonPOJOBuilder;
import com.google.auto.value.AutoValue;
@AutoValue
@JsonDeserialize(builder = AutoValue_InterestGroupData.Builder.class)
public abstract class InterestGroupData {
public abstract String getId();
public abstract String getTitle();
public abstract String getLogo();
public abstract String getState();
public abstract String getProjectId();
public abstract Descriptor getDescription();
public abstract Descriptor getScope();
public abstract List<User> getLeads();
public abstract List<User> getParticipants();
public abstract GitlabProject getGitlab();
public static Builder builder() {
return new AutoValue_InterestGroupData.Builder();
}
@AutoValue.Builder
@JsonPOJOBuilder(withPrefix = "set")
public abstract static class Builder {
public abstract Builder setId(String id);
public abstract Builder setTitle(String title);
public abstract Builder setGitlab(GitlabProject gitlab);
public abstract Builder setLogo(String logo);
public abstract Builder setState(String state);
public abstract Builder setProjectId(String foundationdbProjectId);
public abstract Builder setDescription(Descriptor description);
public abstract Builder setScope(Descriptor scope);
public abstract Builder setLeads(List<User> leads);
public abstract Builder setParticipants(List<User> participants);
public abstract InterestGroupData build();
}
@AutoValue
@JsonDeserialize(builder = AutoValue_InterestGroupData_Descriptor.Builder.class)
public abstract static class Descriptor {
public abstract String getSummary();
public abstract String getFull();
public static Builder builder() {
return new AutoValue_InterestGroupData_Descriptor.Builder();
}
@AutoValue.Builder
@JsonPOJOBuilder(withPrefix = "set")
public abstract static class Builder {
public abstract Builder setSummary(String summary);
public abstract Builder setFull(String full);
public abstract Descriptor build();
}
}
}
/*********************************************************************
* Copyright (c) 2020 Eclipse Foundation.
*
* This program and the accompanying materials are made
* available under the terms of the Eclipse Public License 2.0
* which is available at https://www.eclipse.org/legal/epl-2.0/
*
* Author: Martin Lowe <martin.lowe@eclipse-foundation.org>
*
* SPDX-License-Identifier: EPL-2.0
**********************************************************************/
package org.eclipsefoundation.git.eca.api.models;
import java.util.ArrayList;
import java.util.List;
import java.util.Map;
import javax.annotation.Nullable;
import com.fasterxml.jackson.databind.annotation.JsonDeserialize;
import com.fasterxml.jackson.databind.annotation.JsonPOJOBuilder;
import com.google.auto.value.AutoValue;
import com.google.auto.value.extension.memoized.Memoized;
/**
* Represents a project in the Eclipse API, along with the users and repos that exist within the context of the project.
*
* @author Martin Lowe
*
*/
@AutoValue
@JsonDeserialize(builder = $AutoValue_Project.Builder.class)
public abstract class Project {
public abstract String getProjectId();
public abstract String getName();
public abstract List<User> getCommitters();
public abstract List<User> getProjectLeads();
@Nullable
public abstract List<Repo> getRepos();
public abstract List<Repo> getGitlabRepos();
public abstract List<Repo> getGithubRepos();
public abstract List<Repo> getGerritRepos();
public abstract Object getSpecProjectWorkingGroup();
public abstract GitlabProject getGitlab();
public abstract GithubProject getGithub();
@Nullable
@Memoized
public String getSpecWorkingGroup() {
// stored as map as empty returns an array instead of a map
Object specProjectWorkingGroup = getSpecProjectWorkingGroup();
if (specProjectWorkingGroup instanceof Map) {
// we checked in line above that the map exists, so we can safely cast it
@SuppressWarnings("unchecked")
Object raw = ((Map<Object, Object>) specProjectWorkingGroup).get("id");
if (raw instanceof String) {
return (String) raw;
}
}
return null;
}
public static Builder builder() {
// adds empty lists as default values
return new AutoValue_Project.Builder()
.setRepos(new ArrayList<>())
.setCommitters(new ArrayList<>())
.setGithubRepos(new ArrayList<>())
.setGitlabRepos(new ArrayList<>())
.setGerritRepos(new ArrayList<>());
}
@AutoValue.Builder
@JsonPOJOBuilder(withPrefix = "set")
public abstract static class Builder {
public abstract Builder setProjectId(String projectId);
public abstract Builder setName(String name);
public abstract Builder setProjectLeads(List<User> projectLeads);
public abstract Builder setCommitters(List<User> committers);
public abstract Builder setRepos(@Nullable List<Repo> repos);
public abstract Builder setGitlabRepos(List<Repo> gitlabRepos);
public abstract Builder setGithubRepos(List<Repo> githubRepos);
public abstract Builder setGerritRepos(List<Repo> gerritRepos);
public abstract Builder setSpecProjectWorkingGroup(Object specProjectWorkingGroup);
public abstract Builder setGitlab(GitlabProject gitlab);
public abstract Builder setGithub(GithubProject github);
public abstract Project build();
}
@AutoValue
@JsonDeserialize(builder = AutoValue_Project_User.Builder.class)
public abstract static class User {
public abstract String getUsername();
public abstract String getUrl();
public static Builder builder() {
return new AutoValue_Project_User.Builder();
}
@AutoValue.Builder
@JsonPOJOBuilder(withPrefix = "set")
public abstract static class Builder {
public abstract Builder setUsername(String username);
public abstract Builder setUrl(String url);
public abstract User build();
}
}
@AutoValue
@JsonDeserialize(builder = AutoValue_Project_GitlabProject.Builder.class)
public abstract static class GitlabProject {
public abstract String getProjectGroup();
public abstract List<String> getIgnoredSubGroups();
public static Builder builder() {
return new AutoValue_Project_GitlabProject.Builder();
}
@AutoValue.Builder
@JsonPOJOBuilder(withPrefix = "set")
public abstract static class Builder {
public abstract Builder setProjectGroup(String projectGroup);
public abstract Builder setIgnoredSubGroups(List<String> ignoredSubGroups);
public abstract GitlabProject build();
}
}
@AutoValue
@JsonDeserialize(builder = AutoValue_Project_GithubProject.Builder.class)
public abstract static class GithubProject {
public abstract String getOrg();
public abstract List<String> getIgnoredRepos();
public static Builder builder() {
return new AutoValue_Project_GithubProject.Builder();
}
@AutoValue.Builder
@JsonPOJOBuilder(withPrefix = "set")
public abstract static class Builder {
public abstract Builder setOrg(String org);
public abstract Builder setIgnoredRepos(List<String> ignoredRepos);
public abstract GithubProject build();
}
}
/**
* Does not use autovalue as the value should be mutable.
*
* @author Martin Lowe
*
*/
public static class Repo {
private String url;
public String getUrl() {
return this.url;
}
public void setUrl(String url) {
this.url = url;
}
}
}
......@@ -16,7 +16,7 @@ import java.util.stream.Collectors;
import javax.ws.rs.core.MultivaluedMap;
import org.eclipsefoundation.git.eca.api.models.Project;
import org.eclipsefoundation.efservices.api.models.Project;
import org.eclipsefoundation.git.eca.model.Commit;
import org.eclipsefoundation.git.eca.model.ValidationRequest;
import org.eclipsefoundation.git.eca.namespace.GitEcaParameterNames;
......
/*********************************************************************
* Copyright (c) 2020 Eclipse Foundation.
*
* This program and the accompanying materials are made
* available under the terms of the Eclipse Public License 2.0
* which is available at https://www.eclipse.org/legal/epl-2.0/
*
* Author: Martin Lowe <martin.lowe@eclipse-foundation.org>
* Zachary Sabourin <zachary.sabourin@eclipse-foundation.org>
*
* SPDX-License-Identifier: EPL-2.0
**********************************************************************/
package org.eclipsefoundation.git.eca.service.impl;
/**
* Copyright (c) 2023 Eclipse Foundation
*
* This program and the accompanying materials are made
* available under the terms of the Eclipse Public License 2.0
* which is available at https://www.eclipse.org/legal/epl-2.0/
*
* Author: Martin Lowe <martin.lowe@eclipse-foundation.org>
*
* SPDX-License-Identifier: EPL-2.0
*/
package org.eclipsefoundation.git.eca.helper;
import java.net.URI;
import java.util.Collections;
import java.util.List;
import java.util.concurrent.ExecutionException;
import java.util.concurrent.TimeUnit;
import java.util.stream.Collectors;
import javax.annotation.PostConstruct;
import javax.enterprise.context.ApplicationScoped;
import javax.inject.Inject;
import org.apache.commons.lang3.StringUtils;
import org.eclipse.microprofile.config.inject.ConfigProperty;
import org.eclipse.microprofile.context.ManagedExecutor;
import org.eclipse.microprofile.rest.client.inject.RestClient;
import org.eclipsefoundation.core.service.APIMiddleware;
import org.eclipsefoundation.core.service.CachingService;
import org.eclipsefoundation.git.eca.api.ProjectsAPI;
import org.eclipsefoundation.git.eca.api.models.Project;
import org.eclipsefoundation.efservices.api.models.InterestGroup;
import org.eclipsefoundation.efservices.api.models.Project;
import org.eclipsefoundation.efservices.api.models.Project.GithubProject;
import org.eclipsefoundation.efservices.api.models.Project.ProjectParticipant;
import org.eclipsefoundation.efservices.services.ProjectService;
import org.eclipsefoundation.git.eca.model.ValidationRequest;
import org.eclipsefoundation.git.eca.namespace.ProviderType;
import org.eclipsefoundation.git.eca.service.InterestGroupService;
import org.eclipsefoundation.git.eca.service.ProjectsService;
import org.jboss.resteasy.specimpl.MultivaluedMapImpl;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import com.google.common.cache.CacheBuilder;
import com.google.common.cache.CacheLoader;
import com.google.common.cache.LoadingCache;
import com.google.common.util.concurrent.ListenableFuture;
import com.google.common.util.concurrent.ListenableFutureTask;
import io.quarkus.runtime.Startup;
/**
* Projects service implementation that handles pagination of data manually, as well as makes use of a loading cache to
* have data be always available with as little latency to the user as possible.
*
* Helps manage projects by providing filters on top of the generic service as well as operations like adapting interest
* groups to projects for easier processing.
*
* @author Martin Lowe
* @author Zachary Sabourin
*
*/
@Startup
@ApplicationScoped
public class PaginationProjectsService implements ProjectsService {
private static final Logger LOGGER = LoggerFactory.getLogger(PaginationProjectsService.class);
public final class ProjectHelper {
private static final Logger LOGGER = LoggerFactory.getLogger(ProjectHelper.class);
@ConfigProperty(name = "eclipse.projects.precache.enabled", defaultValue = "true")
boolean isEnabled;
@ConfigProperty(name = "cache.pagination.refresh-frequency-seconds", defaultValue = "3600")
long refreshAfterWrite;
@Inject
@RestClient
ProjectsAPI projects;
@Inject
InterestGroupService ig;
@Inject
CachingService cache;
@Inject
APIMiddleware middleware;
@Inject
ManagedExecutor exec;
// this class has a separate cache as this data is long to load and should be
// always available.
LoadingCache<String, List<Project>> internalCache;
ProjectService projects;
/**
* Initializes the internal loader cache and pre-populates the data with the one available key. If more than one key is
* used, eviction of previous results will happen and create degraded performance.
*/
@PostConstruct
public void init() {
// set up the internal cache
this.internalCache = CacheBuilder
.newBuilder()
.maximumSize(1)
.refreshAfterWrite(refreshAfterWrite, TimeUnit.SECONDS)
.build(new CacheLoader<String, List<Project>>() {
@Override
public List<Project> load(String key) throws Exception {
return getProjectsInternal();
}
/**
* Implementation required for refreshAfterRewrite to be async rather than sync and blocking while awaiting for
* expensive reload to complete.
*/
@Override
public ListenableFuture<List<Project>> reload(String key, List<Project> oldValue) throws Exception {
ListenableFutureTask<List<Project>> task = ListenableFutureTask.create(() -> {
LOGGER.debug("Retrieving new project data async");
List<Project> newProjects = oldValue;
try {
newProjects = getProjectsInternal();
} catch (Exception e) {
LOGGER.error("Error while reloading internal projects data, data will be stale for current cycle.", e);
}
LOGGER.debug("Done refreshing project values");
return newProjects;
});
// run the task using the Quarkus managed executor
exec.execute(task);
return task;
}
});
if (isEnabled) {
// pre-cache the projects to reduce load time for other users
LOGGER.debug("Starting pre-cache of projects");
if (getProjects() == null) {
LOGGER.warn("Unable to populate pre-cache for Eclipse projects. Calls may experience degraded performance.");
}
LOGGER.debug("Completed pre-cache of projects assets");
}
}
@Override
public List<Project> getProjects() {
try {
return internalCache.get("projects");
} catch (ExecutionException e) {
throw new RuntimeException("Could not load Eclipse projects", e);
}
}
@Override
public List<Project> retrieveProjectsForRequest(ValidationRequest req) {
String repoUrl = req.getRepoUrl().getPath();
if (repoUrl == null) {
......@@ -149,7 +57,6 @@ public class PaginationProjectsService implements ProjectsService {
return retrieveProjectsForRepoURL(repoUrl, req.getProvider());
}
@Override
public List<Project> retrieveProjectsForRepoURL(String repoUrl, ProviderType provider) {
if (repoUrl == null) {
LOGGER.warn("Can not match null repo URL to projects");
......@@ -157,10 +64,6 @@ public class PaginationProjectsService implements ProjectsService {
}
// check for all projects that make use of the given repo
List<Project> availableProjects = getProjects();
availableProjects
.addAll(cache
.get("all", new MultivaluedMapImpl<>(), Project.class, () -> ig.adaptInterestGroups(ig.getInterestGroups()))
.orElse(Collections.emptyList()));
if (availableProjects.isEmpty()) {
LOGGER.warn("Could not find any projects to match against");
return Collections.emptyList();
......@@ -195,19 +98,66 @@ public class PaginationProjectsService implements ProjectsService {
}
/**
* Logic for retrieving projects from API.
*
* @return list of projects for the cache
* Retrieve cached and adapted projects list to avoid having to create all interest group adaptations on every request.
*
* @return list of available projects or empty list if none found.
*/
private List<Project> getProjectsInternal() {
public List<Project> getProjects() {
return cache.get("all-combined", new MultivaluedMapImpl<>(), Project.class, () -> {
List<Project> availableProjects = projects.getAllProjects();
availableProjects.addAll(adaptInterestGroups(projects.getAllInterestGroups()));
return availableProjects;
}).orElseGet(Collections::emptyList);
}
return middleware.getAll(params -> projects.getProjects(params), Project.class).stream().map(proj -> {
proj.getGerritRepos().forEach(repo -> {
if (repo.getUrl().endsWith(".git")) {
repo.setUrl(repo.getUrl().substring(0, repo.getUrl().length() - 4));
}
});
return proj;
}).collect(Collectors.toList());
private List<Project> adaptInterestGroups(List<InterestGroup> igs) {
return igs
.stream()
.map(ig -> Project
.builder()
.setProjectId(ig.getProjectId())
.setGerritRepos(Collections.emptyList())
.setGithubRepos(Collections.emptyList())
.setGitlabRepos(Collections.emptyList())
.setGitlab(ig.getGitlab())
.setCommitters(ig
.getParticipants()
.stream()
.map(p -> ProjectParticipant
.builder()
.setFullName(p.getFullName())
.setUrl(p.getUrl())
.setUsername(p.getUsername())
.build())
.collect(Collectors.toList()))
.setProjectLeads(ig
.getLeads()
.stream()
.map(p -> ProjectParticipant
.builder()
.setFullName(p.getFullName())
.setUrl(p.getUrl())
.setUsername(p.getUsername())
.build())
.collect(Collectors.toList()))
.setContributors(Collections.emptyList())
.setShortProjectId(ig.getShortProjectId())
.setSlsaLevel("")
.setSummary("")
.setWebsiteUrl("")
.setWebsiteRepo(Collections.emptyList())
.setGitlab(ig.getGitlab())
.setGithub(GithubProject.builder().setOrg("").setIgnoredRepos(Collections.emptyList()).build())
.setWorkingGroups(Collections.emptyList())
.setIndustryCollaborations(Collections.emptyList())
.setReleases(Collections.emptyList())
.setTopLevelProject("")
.setUrl("")
.setLogo(ig.getLogo())
.setTags(Collections.emptyList())
.setName(ig.getTitle())
.setSpecProjectWorkingGroup(Collections.emptyMap())
.build())
.collect(Collectors.toList());
}
}
......@@ -27,14 +27,14 @@ import javax.ws.rs.core.MediaType;
import javax.ws.rs.core.Response;
import org.apache.commons.lang3.StringUtils;
import org.eclipsefoundation.efservices.api.models.Project;
import org.eclipsefoundation.git.eca.api.models.GithubWebhookRequest;
import org.eclipsefoundation.git.eca.api.models.GithubWebhookRequest.PullRequest;
import org.eclipsefoundation.git.eca.api.models.Project;
import org.eclipsefoundation.git.eca.dto.CommitValidationStatus;
import org.eclipsefoundation.git.eca.dto.GithubWebhookTracking;
import org.eclipsefoundation.git.eca.helper.ProjectHelper;
import org.eclipsefoundation.git.eca.model.Commit;
import org.eclipsefoundation.git.eca.model.ValidationRequest;
import org.eclipsefoundation.git.eca.service.ProjectsService;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
......@@ -52,7 +52,7 @@ public class StatusResource extends GithubAdjacentResource {
private static final Logger LOGGER = LoggerFactory.getLogger(StatusResource.class);
@Inject
ProjectsService projects;
ProjectHelper projects;
// Qute templates, generates UI status page
@Location("simple_fingerprint_ui")
......
......@@ -29,11 +29,10 @@ import org.eclipse.microprofile.config.inject.ConfigProperty;
import org.eclipsefoundation.core.model.RequestWrapper;
import org.eclipsefoundation.core.service.CachingService;
import org.eclipsefoundation.git.eca.api.models.EclipseUser;
import org.eclipsefoundation.git.eca.helper.ProjectHelper;
import org.eclipsefoundation.git.eca.model.ValidationRequest;
import org.eclipsefoundation.git.eca.model.ValidationResponse;
import org.eclipsefoundation.git.eca.namespace.APIStatusCode;
import org.eclipsefoundation.git.eca.service.InterestGroupService;
import org.eclipsefoundation.git.eca.service.ProjectsService;
import org.eclipsefoundation.git.eca.service.UserService;
import org.eclipsefoundation.git.eca.service.ValidationService;
import org.jboss.resteasy.annotations.jaxrs.QueryParam;
......@@ -67,13 +66,11 @@ public class ValidationResource {
@Inject
CachingService cache;
@Inject
ProjectsService projects;
ProjectHelper projects;
@Inject
UserService users;
@Inject
ValidationService validation;
@Inject
InterestGroupService ig;
/**
* Consuming a JSON request, this method will validate all passed commits, using the repo URL and the repository
......
package org.eclipsefoundation.git.eca.service;
import java.util.List;
import org.eclipsefoundation.git.eca.api.models.InterestGroupData;
import org.eclipsefoundation.git.eca.api.models.Project;
/**
* Service for retrieving and interacting with interest groups.
*
* @author Martin Lowe
*
*/
public interface InterestGroupService {
/**
* Retrieve all available interest groups.
*
* @return list of all available interest groups
*/
List<InterestGroupData> getInterestGroups();
/**
* Converts interest groups into projects for processing downstream.
*
* @param igs the interest groups to convert
* @return the converted interest groups
*/
List<Project> adaptInterestGroups(List<InterestGroupData> igs);
}
/*********************************************************************
* Copyright (c) 2020 Eclipse Foundation.
*
* This program and the accompanying materials are made
* available under the terms of the Eclipse Public License 2.0
* which is available at https://www.eclipse.org/legal/epl-2.0/
*
* Author: Martin Lowe <martin.lowe@eclipse-foundation.org>
*
* SPDX-License-Identifier: EPL-2.0
**********************************************************************/
package org.eclipsefoundation.git.eca.service;
import java.util.List;
import org.eclipsefoundation.git.eca.api.models.Project;
import org.eclipsefoundation.git.eca.model.ValidationRequest;
import org.eclipsefoundation.git.eca.namespace.ProviderType;
/**
* Intermediate layer between resource and API layers that handles retrieval of all projects and caching of that data
* for availability purposes.
*
* @author Martin Lowe
*
*/
public interface ProjectsService {
/**
* Retrieves all currently available projects from cache if available, otherwise going to API to retrieve a fresh copy
* of the data.
*
* @return list of projects available from API.
*/
List<Project> getProjects();
/**
* Retrieves projects valid for the current request, or an empty list if no data or matching project repos could be
* found.
*
* @param req the current request
* @return list of matching projects for the current request, or an empty list if none found.
*/
List<Project> retrieveProjectsForRequest(ValidationRequest req);
/**
* Retrieves projects for given provider, using the repo URL to match to a stored repository.
*
* @param repoUrl the repo URL to match
* @param provider the provider that is being served for the request.
* @return a list of matching projects, or an empty list if none are found.
*/
List<Project> retrieveProjectsForRepoURL(String repoUrl, ProviderType provider);
}
......@@ -13,8 +13,8 @@ package org.eclipsefoundation.git.eca.service;
import java.util.List;
import org.eclipsefoundation.efservices.api.models.Project;
import org.eclipsefoundation.git.eca.api.models.EclipseUser;
import org.eclipsefoundation.git.eca.api.models.Project;
public interface UserService {
......
......@@ -16,7 +16,7 @@ import java.security.NoSuchAlgorithmException;
import java.util.List;
import org.eclipsefoundation.core.model.RequestWrapper;
import org.eclipsefoundation.git.eca.api.models.Project;
import org.eclipsefoundation.efservices.api.models.Project;
import org.eclipsefoundation.git.eca.dto.CommitValidationStatus;
import org.eclipsefoundation.git.eca.model.ValidationRequest;
import org.eclipsefoundation.git.eca.model.ValidationResponse;
......
......@@ -14,8 +14,8 @@ package org.eclipsefoundation.git.eca.service.impl;
import java.util.Collections;
import java.util.Iterator;
import java.util.List;
import java.util.Optional;
import java.util.Map.Entry;
import java.util.Optional;
import java.util.regex.Pattern;
import java.util.stream.Collectors;
......@@ -28,10 +28,10 @@ import org.apache.commons.lang3.StringUtils;
import org.eclipse.microprofile.config.inject.ConfigProperty;
import org.eclipse.microprofile.rest.client.inject.RestClient;
import org.eclipsefoundation.core.service.CachingService;
import org.eclipsefoundation.efservices.api.models.Project;
import org.eclipsefoundation.git.eca.api.AccountsAPI;
import org.eclipsefoundation.git.eca.api.BotsAPI;
import org.eclipsefoundation.git.eca.api.models.EclipseUser;
import org.eclipsefoundation.git.eca.api.models.Project;
import org.eclipsefoundation.git.eca.service.OAuthService;
import org.eclipsefoundation.git.eca.service.UserService;
import org.jboss.resteasy.specimpl.MultivaluedMapImpl;
......
package org.eclipsefoundation.git.eca.service.impl;
import java.util.Collections;
import java.util.List;
import java.util.stream.Collectors;
import javax.enterprise.context.ApplicationScoped;
import javax.inject.Inject;
import org.eclipse.microprofile.rest.client.inject.RestClient;
import org.eclipsefoundation.core.service.APIMiddleware;
import org.eclipsefoundation.core.service.CachingService;
import org.eclipsefoundation.git.eca.api.ProjectsAPI;
import org.eclipsefoundation.git.eca.api.models.InterestGroupData;
import org.eclipsefoundation.git.eca.api.models.Project;
import org.eclipsefoundation.git.eca.service.InterestGroupService;
import org.jboss.resteasy.specimpl.MultivaluedMapImpl;
/**
* Default implementation of interest group service.
*
* @author Martin Lowe
*/
@ApplicationScoped
public class DefaultInterestGroupService implements InterestGroupService {
@Inject
APIMiddleware middleware;
@Inject
CachingService cache;
@Inject
@RestClient
ProjectsAPI api;
@Override
public List<InterestGroupData> getInterestGroups() {
return cache
.get("all", new MultivaluedMapImpl<>(), InterestGroupData.class,
() -> middleware.getAll(api::getInterestGroups, InterestGroupData.class))
.orElse(Collections.emptyList());
}
@Override
public List<Project> adaptInterestGroups(List<InterestGroupData> igs) {
return igs
.stream()
.map(ig -> Project
.builder()
.setProjectId(ig.getProjectId())
.setGerritRepos(Collections.emptyList())
.setGithubRepos(Collections.emptyList())
.setGitlabRepos(Collections.emptyList())
.setRepos(Collections.emptyList())
.setGitlab(ig.getGitlab())
.setCommitters(ig.getParticipants())
.setProjectLeads(ig.getLeads())
.setName(ig.getTitle())
.setSpecProjectWorkingGroup(Collections.emptyMap())
.build())
.collect(Collectors.toList());
}
}
......@@ -28,20 +28,19 @@ import org.eclipse.microprofile.config.inject.ConfigProperty;
import org.eclipsefoundation.core.helper.DateTimeHelper;
import org.eclipsefoundation.core.model.RequestWrapper;
import org.eclipsefoundation.core.service.CachingService;
import org.eclipsefoundation.efservices.api.models.Project;
import org.eclipsefoundation.git.eca.api.models.EclipseUser;
import org.eclipsefoundation.git.eca.api.models.Project;
import org.eclipsefoundation.git.eca.dto.CommitValidationMessage;
import org.eclipsefoundation.git.eca.dto.CommitValidationStatus;
import org.eclipsefoundation.git.eca.dto.CommitValidationStatusGrouping;
import org.eclipsefoundation.git.eca.helper.CommitHelper;
import org.eclipsefoundation.git.eca.helper.ProjectHelper;
import org.eclipsefoundation.git.eca.model.Commit;
import org.eclipsefoundation.git.eca.model.GitUser;
import org.eclipsefoundation.git.eca.model.ValidationRequest;
import org.eclipsefoundation.git.eca.model.ValidationResponse;
import org.eclipsefoundation.git.eca.namespace.APIStatusCode;
import org.eclipsefoundation.git.eca.namespace.GitEcaParameterNames;
import org.eclipsefoundation.git.eca.service.InterestGroupService;
import org.eclipsefoundation.git.eca.service.ProjectsService;
import org.eclipsefoundation.git.eca.service.UserService;
import org.eclipsefoundation.git.eca.service.ValidationService;
import org.eclipsefoundation.persistence.dao.PersistenceDao;
......@@ -66,9 +65,7 @@ public class DefaultValidationService implements ValidationService {
List<String> allowListUsers;
@Inject
ProjectsService projects;
@Inject
InterestGroupService ig;
ProjectHelper projects;
@Inject
UserService users;
......@@ -386,7 +383,7 @@ public class DefaultValidationService implements ValidationService {
if (p.getCommitters().stream().anyMatch(u -> u.getUsername().equals(user.getName()))) {
// check if the current project is a committer project, and if the user can
// commit to specs
if (p.getSpecWorkingGroup() != null && !user.getECA().getCanContributeSpecProject()) {
if (p.getSpecWorkingGroup().isPresent() && !user.getECA().getCanContributeSpecProject()) {
// set error + update response status
r
.addError(hash, String
......
......@@ -18,9 +18,9 @@ import java.util.Optional;
import javax.inject.Inject;
import org.eclipsefoundation.efservices.api.models.Project;
import org.eclipsefoundation.git.eca.api.models.EclipseUser;
import org.eclipsefoundation.git.eca.api.models.Project;
import org.eclipsefoundation.git.eca.service.ProjectsService;
import org.eclipsefoundation.git.eca.helper.ProjectHelper;
import org.eclipsefoundation.git.eca.service.UserService;
import org.junit.jupiter.api.Assertions;
import org.junit.jupiter.api.Test;
......@@ -40,7 +40,7 @@ class CachedUserServiceTest {
@Inject
UserService users;
@Inject
ProjectsService projects;
ProjectHelper projects;
@Test
void getUser_success() {
......
/*********************************************************************
* Copyright (c) 2020 Eclipse Foundation.
*
* This program and the accompanying materials are made
* available under the terms of the Eclipse Public License 2.0
* which is available at https://www.eclipse.org/legal/epl-2.0/
*
* Author: Martin Lowe <martin.lowe@eclipse-foundation.org>
*
* SPDX-License-Identifier: EPL-2.0
**********************************************************************/
package org.eclipsefoundation.git.eca.service.impl;
import java.util.List;
import java.util.stream.Collectors;
import javax.inject.Inject;
import org.eclipsefoundation.git.eca.api.models.Project;
import org.eclipsefoundation.git.eca.api.models.Project.Repo;
import org.eclipsefoundation.git.eca.service.ProjectsService;
import org.junit.jupiter.api.Assertions;
import org.junit.jupiter.api.Test;
import io.quarkus.test.junit.QuarkusTest;
@QuarkusTest
class PaginationProjectsServiceTest {
// get the projects service
@Inject
ProjectsService ps;
@Test
void validateGerritUrlScrubbed() {
// get all projects
List<Project> projectsAll = ps.getProjects();
// get projects that have gerrit repos
List<Project> projs = projectsAll
.stream()
.filter(p -> !p.getGerritRepos().isEmpty())
.collect(Collectors.toList());
// for all repos, check that none end with .git (this doesn't account for
// .git.git, but I assume that would be an entry error)
for (Project p : projs) {
for (Repo r : p.getGerritRepos()) {
Assertions.assertFalse(r.getUrl().endsWith(".git"), "Expected no URLs to end with '.git'");
}
}
}
@Test
void validateGithubUrlNotScrubbed() {
// get all projects
List<Project> projectsAll = ps.getProjects();
// get projects that have github repos
List<Project> projs = projectsAll
.stream()
.filter(p -> !p.getGithubRepos().isEmpty())
.collect(Collectors.toList());
// for all repos, check that at least one ends with .git
boolean foundGitSuffix = false;
for (Project p : projs) {
for (Repo r : p.getGithubRepos()) {
if (r.getUrl().endsWith(".git")) {
foundGitSuffix = true;
}
}
}
Assertions.assertTrue(foundGitSuffix, "Expected a URL to end with '.git'");
}
@Test
void validateGitlabUrlNotScrubbed() {
// get all projects
List<Project> projectsAll = ps.getProjects();
// get projects that have gitlab repos
List<Project> projs = projectsAll
.stream()
.filter(p -> !p.getGitlabRepos().isEmpty())
.collect(Collectors.toList());
// for all repos, check that at least one ends with .git
boolean foundGitSuffix = false;
for (Project p : projs) {
for (Repo r : p.getGitlabRepos()) {
if (r.getUrl().endsWith(".git")) {
foundGitSuffix = true;
}
}
}
Assertions.assertTrue(foundGitSuffix, "Expected a URL to end with '.git'");
}
}
/*********************************************************************
* Copyright (c) 2020 Eclipse Foundation.
* Copyright (c) 2023 Eclipse Foundation.
*
* This program and the accompanying materials are made
* available under the terms of the Eclipse Public License 2.0
* which is available at https://www.eclipse.org/legal/epl-2.0/
*
* Author: Martin Lowe <martin.lowe@eclipse-foundation.org>
* Author: Zachary Sabourin <zachary.sabourin@eclipse-foundation.org>
*
* SPDX-License-Identifier: EPL-2.0
**********************************************************************/
......@@ -14,24 +14,26 @@ package org.eclipsefoundation.git.eca.test.api;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collections;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.stream.Collectors;
import javax.annotation.PostConstruct;
import javax.enterprise.context.ApplicationScoped;
import javax.ws.rs.core.Response;
import org.eclipse.microprofile.rest.client.inject.RestClient;
import org.eclipsefoundation.core.service.APIMiddleware.BaseAPIParameters;
import org.eclipsefoundation.git.eca.api.ProjectsAPI;
import org.eclipsefoundation.git.eca.api.models.InterestGroupData;
import org.eclipsefoundation.git.eca.api.models.Project;
import org.eclipsefoundation.git.eca.api.models.InterestGroupData.Descriptor;
import org.eclipsefoundation.git.eca.api.models.Project.GithubProject;
import org.eclipsefoundation.git.eca.api.models.Project.GitlabProject;
import org.eclipsefoundation.git.eca.api.models.Project.Repo;
import org.eclipsefoundation.git.eca.api.models.Project.User;
import org.eclipsefoundation.efservices.api.ProjectsAPI;
import org.eclipsefoundation.efservices.api.models.GitlabProject;
import org.eclipsefoundation.efservices.api.models.InterestGroup;
import org.eclipsefoundation.efservices.api.models.InterestGroup.Descriptor;
import org.eclipsefoundation.efservices.api.models.InterestGroup.InterestGroupParticipant;
import org.eclipsefoundation.efservices.api.models.InterestGroup.Organization;
import org.eclipsefoundation.efservices.api.models.InterestGroup.Resource;
import org.eclipsefoundation.efservices.api.models.Project;
import org.eclipsefoundation.efservices.api.models.Project.GithubProject;
import org.eclipsefoundation.efservices.api.models.Project.ProjectParticipant;
import org.eclipsefoundation.efservices.api.models.Project.Repo;
import io.quarkus.test.Mock;
......@@ -40,124 +42,169 @@ import io.quarkus.test.Mock;
@ApplicationScoped
public class MockProjectsAPI implements ProjectsAPI {
private List<Project> src;
private List<Project> projects;
@PostConstruct
public void build() {
this.src = new ArrayList<>();
public MockProjectsAPI() {
this.projects = new ArrayList<>();
// sample repos
Repo r1 = new Repo();
r1.setUrl("http://www.github.com/eclipsefdn/sample");
Repo r2 = new Repo();
r2.setUrl("http://www.github.com/eclipsefdn/test");
Repo r3 = new Repo();
r3.setUrl("http://www.github.com/eclipsefdn/prototype.git");
Repo r5 = new Repo();
r5.setUrl("/gitroot/sample/gerrit.project.git");
Repo r6 = new Repo();
r6.setUrl("/gitroot/sample/gerrit.other-project");
Repo r7 = new Repo();
r7.setUrl("https://gitlab.eclipse.org/eclipse/dash/dash.git");
Repo r8 = new Repo();
r8.setUrl("https://gitlab.eclipse.org/eclipse/dash-second/dash.handbook.test");
// sample users, correlates to users in Mock projects API
User u1 = User.builder().setUrl("").setUsername("da_wizz").build();
User u2 = User.builder().setUrl("").setUsername("grunter").build();
// projects
Project p1 = Project
.builder()
Repo r1 = Repo.builder().setUrl("http://www.github.com/eclipsefdn/sample").build();
Repo r2 = Repo.builder().setUrl("http://www.github.com/eclipsefdn/test").build();
Repo r3 = Repo.builder().setUrl("http://www.github.com/eclipsefdn/prototype.git").build();
Repo r4 = Repo.builder().setUrl("http://www.github.com/eclipsefdn/tck-proto").build();
Repo r5 = Repo.builder().setUrl("/gitroot/sample/gerrit.project.git").build();
Repo r6 = Repo.builder().setUrl("/gitroot/sample/gerrit.other-project").build();
Repo r7 = Repo.builder().setUrl("https://gitlab.eclipse.org/eclipse/dash/dash.git").build();
Repo r8 = Repo.builder().setUrl("https://gitlab.eclipse.org/eclipse/dash-second/dash.handbook.test").build();
// sample users
ProjectParticipant u1 = ProjectParticipant.builder().setUrl("").setUsername("da_wizz").setFullName("da_wizz")
.build();
ProjectParticipant u2 = ProjectParticipant.builder().setUrl("").setUsername("grunter").setFullName("grunter")
.build();
this.projects.add(Project.builder()
.setName("Sample project")
.setProjectId("sample.proj")
.setSpecProjectWorkingGroup(Collections.emptyList())
.setSpecProjectWorkingGroup(Collections.emptyMap())
.setGithubRepos(Arrays.asList(r1, r2))
.setGerritRepos(Arrays.asList(r5))
.setCommitters(Arrays.asList(u1, u2))
.setProjectLeads(Collections.emptyList())
.setGitlab(GitlabProject.builder().setIgnoredSubGroups(Collections.emptyList()).setProjectGroup("").build())
.setGithub(GithubProject.builder().setIgnoredRepos(Collections.emptyList()).setOrg("").build())
.build();
src.add(p1);
.setGitlab(GitlabProject.builder().setIgnoredSubGroups(Collections.emptyList()).setProjectGroup("")
.build())
.setShortProjectId("sample.proj")
.setSummary("summary")
.setUrl("project.url.com")
.setWebsiteUrl("someproject.com")
.setWebsiteRepo(Collections.emptyList())
.setLogo("logoUrl.com")
.setTags(Collections.emptyList())
.setGithub(GithubProject.builder()
.setOrg("")
.setIgnoredRepos(Collections.emptyList()).build())
.setGitlabRepos(Collections.emptyList())
.setContributors(Collections.emptyList())
.setWorkingGroups(Collections.emptyList())
.setIndustryCollaborations(Collections.emptyList())
.setReleases(Collections.emptyList())
.setTopLevelProject("eclipse")
.setSlsaLevel("1")
.build());
Project p2 = Project
.builder()
this.projects.add(Project.builder()
.setName("Prototype thing")
.setProjectId("sample.proto")
.setSpecProjectWorkingGroup(Collections.emptyList())
.setSpecProjectWorkingGroup(Collections.emptyMap())
.setGithubRepos(Arrays.asList(r3))
.setGerritRepos(Arrays.asList(r6))
.setGitlabRepos(Arrays.asList(r8))
.setCommitters(Arrays.asList(u2))
.setProjectLeads(Collections.emptyList())
.setGitlab(
GitlabProject.builder().setIgnoredSubGroups(Collections.emptyList()).setProjectGroup("eclipse/dash-second").build())
.setGithub(GithubProject.builder().setIgnoredRepos(Collections.emptyList()).setOrg("").build())
.build();
src.add(p2);
GitlabProject.builder().setIgnoredSubGroups(Collections.emptyList())
.setProjectGroup("eclipse/dash-second").build())
.setShortProjectId("sample.proto")
.setWebsiteUrl("someproject.com")
.setSummary("summary")
.setUrl("project.url.com")
.setWebsiteRepo(Collections.emptyList())
.setLogo("logoUrl.com")
.setTags(Collections.emptyList())
.setGithub(GithubProject.builder()
.setOrg("")
.setIgnoredRepos(Collections.emptyList()).build())
.setContributors(Collections.emptyList())
.setWorkingGroups(Collections.emptyList())
.setIndustryCollaborations(Collections.emptyList())
.setReleases(Collections.emptyList())
.setTopLevelProject("eclipse")
.setSlsaLevel("1")
.build());
Map<String, String> map = new HashMap<>();
map.put("id", "proj1");
Project p3 = Project
.builder()
this.projects.add(Project.builder()
.setName("Spec project")
.setProjectId("spec.proj")
.setSpecProjectWorkingGroup(map)
.setGithubRepos(Collections.emptyList())
.setSpecProjectWorkingGroup(Map.of("id", "proj1", "name", "proj1"))
.setGithubRepos(Arrays.asList(r4))
.setGitlabRepos(Arrays.asList(r7))
.setGerritRepos(Collections.emptyList())
.setGitlab(GitlabProject
.builder()
.setIgnoredSubGroups(Arrays.asList("eclipse/dash/mirror"))
.setProjectGroup("eclipse/dash")
.build())
.setGithub(GithubProject
.builder()
.setIgnoredRepos(Arrays.asList("eclipsefdn-tck/tck-ignored"))
.setOrg("eclipsefdn-tck")
.build())
.setCommitters(Arrays.asList(u1, u2))
.setProjectLeads(Collections.emptyList())
.build();
src.add(p3);
.setShortProjectId("spec.proj")
.setSummary("summary")
.setUrl("project.url.com")
.setWebsiteUrl("someproject.com")
.setWebsiteRepo(Collections.emptyList())
.setLogo("logoUrl.com")
.setTags(Collections.emptyList())
.setGithub(GithubProject.builder()
.setOrg("eclipsefdn-tck")
.setIgnoredRepos(Arrays.asList("eclipsefdn-tck/tck-ignored")).build())
.setContributors(Collections.emptyList())
.setWorkingGroups(Collections.emptyList())
.setIndustryCollaborations(Collections.emptyList())
.setReleases(Collections.emptyList())
.setTopLevelProject("eclipse")
.setSlsaLevel("1")
.build());
}
@Override
public Response getProjects(BaseAPIParameters baseParams) {
return Response.ok(baseParams.getPage() == 1 ? new ArrayList<>(src) : Collections.emptyList()).build();
public Response getProjects(BaseAPIParameters params, int isSpecProject) {
if (isSpecProject == 1) {
return Response
.ok(projects.stream().filter(p -> p.getSpecWorkingGroup().isPresent()).collect(Collectors.toList()))
.build();
}
return Response.ok(projects).build();
}
@Override
public Response getInterestGroups(BaseAPIParameters params) {
return Response
.ok(Arrays
.asList(InterestGroupData
.builder()
.setProjectId("foundation-internal.ig.mittens")
.setId("1")
.setLogo("")
.setState("active")
.setTitle("Magical IG Tributed To Eclipse News Sources")
.setDescription(Descriptor.builder().setFull("Sample").setSummary("Sample").build())
.setScope(Descriptor.builder().setFull("Sample").setSummary("Sample").build())
.setGitlab(GitlabProject
.builder()
.setIgnoredSubGroups(Collections.emptyList())
.setProjectGroup("eclipse-ig/mittens")
.build())
.setLeads(Arrays
.asList(User
.builder()
.setUrl("https://api.eclipse.org/account/profile/zacharysabourin")
.setUsername("zacharysabourin")
.build()))
.setParticipants(Arrays
.asList(User
.builder()
.setUrl("https://api.eclipse.org/account/profile/skilpatrick")
.setUsername("skilpatrick")
.build()))
.build()))
.build();
return Response.ok(Arrays.asList(InterestGroup
.builder()
.setProjectId("foundation-internal.ig.mittens")
.setId("1")
.setLogo("")
.setState("active")
.setTitle("Magical IG Tributed To Eclipse News Sources")
.setDescription(Descriptor.builder().setFull("Sample").setSummary("Sample").build())
.setScope(Descriptor.builder().setFull("Sample").setSummary("Sample").build())
.setGitlab(GitlabProject.builder()
.setIgnoredSubGroups(Collections.emptyList())
.setProjectGroup("eclipse-ig/mittens")
.build())
.setLeads(Arrays.asList(InterestGroupParticipant
.builder()
.setUrl("https://api.eclipse.org/account/profile/zacharysabourin")
.setUsername("zacharysabourin")
.setFullName("zachary sabourin")
.setOrganization(Organization.builder()
.setDocuments(Collections.emptyMap())
.setId("id")
.setName("org").build())
.build()))
.setParticipants(Arrays.asList(InterestGroupParticipant
.builder()
.setUrl("https://api.eclipse.org/account/profile/skilpatrick")
.setUsername("skilpatrick")
.setFullName("Skil Patrick")
.setOrganization(Organization.builder()
.setDocuments(Collections.emptyMap())
.setId("id")
.setName("org").build())
.build()))
.setShortProjectId("mittens")
.setResources(Resource.builder().setMembers("members").setWebsite("google.com").build())
.setMailingList("mailinglist.com")
.build())).build();
}
}
0% Loading or .
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment