Skip to content

Commit

Permalink
Fix: Github connector repos not updated when changing organization - M…
Browse files Browse the repository at this point in the history
…EED-2454 - Meeds-io/MIPs#64 (#93)

Prior to this change, when viewing an organization, repositories of previous organization are listed, it's due to a cache problem
  • Loading branch information
AzmiTouil committed Sep 20, 2023
1 parent 979616b commit fa89192
Show file tree
Hide file tree
Showing 13 changed files with 123 additions and 66 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -24,7 +24,7 @@
import org.exoplatform.commons.api.persistence.ExoEntity;

import lombok.Data;
import org.exoplatform.gamification.github.utils.StringListConverter;
import org.exoplatform.commons.utils.StringListConverter;

@Entity(name = "GitHubWebhooks")
@ExoEntity
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -227,6 +227,30 @@ public Response updateWebHookRepoStatus(@Parameter(description = "GitHub organiz
}
}

@Path("events/status")
@POST
@RolesAllowed("users")
@Operation(summary = "enables/disables event for gitHub organization.", description = "enables/disables event for gitHub organization", method = "POST")
@ApiResponses(value = {
@ApiResponse(responseCode = "204", description = "Request fulfilled"),
@ApiResponse(responseCode = "400", description = "Bad request"),
@ApiResponse(responseCode = "401", description = "Unauthorized operation"),
@ApiResponse(responseCode = "500", description = "Internal server error"), })
public Response updateWebHookEventStatus(@Parameter(description = "Event Id", required = true) @FormParam("eventId") long eventId,
@Parameter(description = "Organization remote Id", required = true) @FormParam("organizationId") long organizationId,
@Parameter(description = "Event status enabled/disabled. possible values: true for enabled, else false", required = true) @FormParam("enabled") boolean enabled) {

String currentUser = getCurrentUser();
try {
webhookService.setEventEnabledForOrganization(eventId, organizationId, enabled, currentUser);
return Response.noContent().build();
} catch (IllegalAccessException e) {
return Response.status(Response.Status.UNAUTHORIZED).entity(e.getMessage()).type(MediaType.TEXT_PLAIN).build();
} catch (ObjectNotFoundException e) {
return Response.status(Response.Status.NOT_FOUND).entity("Event not found").build();
}
}

@Path("watchScope/status")
@POST
@RolesAllowed("users")
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -67,23 +67,19 @@ public interface GithubConsumerService {
* Retrieve available github organization repositories.
*
* @param webHook webHook
* repositories
* @throws ObjectNotFoundException when the github organization identified by
* its technical name is not found
* @throws IllegalAccessException when user is not authorized to access remote
* organization repositories
* @param page page
* @param perPage perPage
*
* @return {@link List} of {@link RemoteRepository}
*/
List<RemoteRepository> retrieveOrganizationRepos(WebHook webHook,
int page,
int perPage) throws IllegalAccessException, ObjectNotFoundException;
int perPage);

/**
* Count github organization repositories.
*
* @param webHook webHook
* repositories
*
* @return repositories count
*/
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -187,4 +187,18 @@ List<RemoteRepository> retrieveOrganizationRepos(long organizationRemoteId,
int countOrganizationRepos(long organizationRemoteId, String currentUser) throws IllegalAccessException,
ObjectNotFoundException;

/**
* Enables/disables organization event
*
* @param eventId event Id
* @param organizationId organization remote id
* @param enabled true to enabled, else false
* @param currentUser user name attempting to enables/disables event.
* @throws IllegalAccessException when user is not authorized enables/disables
* organization event
*/
void setEventEnabledForOrganization(long eventId, long organizationId, boolean enabled, String currentUser) throws IllegalAccessException,
ObjectNotFoundException;


}
Original file line number Diff line number Diff line change
Expand Up @@ -47,7 +47,7 @@ public String deleteWebhook(WebHook webHook) {

@Override
public int countOrganizationRepos(WebHook webHook) {
return githubConsumerStorage.countOrganizationRepos(webHook);
return githubConsumerStorage.countOrganizationRepos(webHook.getOrganizationId(), webHook.getToken());
}

@Override
Expand All @@ -62,8 +62,8 @@ public RemoteOrganization retrieveRemoteOrganization(String organizationName,
}

@Override
public List<RemoteRepository> retrieveOrganizationRepos(WebHook webHook, int page, int perPage) throws IllegalAccessException {
return githubConsumerStorage.retrieveOrganizationRepos(webHook, page, perPage);
public List<RemoteRepository> retrieveOrganizationRepos(WebHook webHook, int page, int perPage) {
return githubConsumerStorage.retrieveOrganizationRepos(webHook.getOrganizationId(), webHook.getToken(), page, perPage);
}

@Override
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,8 @@
import java.util.*;
import java.util.stream.Collectors;

import io.meeds.gamification.model.EventDTO;
import io.meeds.gamification.service.EventService;
import io.meeds.gamification.utils.Utils;
import org.apache.commons.collections.CollectionUtils;
import org.apache.commons.lang3.StringUtils;
Expand Down Expand Up @@ -49,6 +51,8 @@ public class WebhookServiceImpl implements WebhookService {

private static final Scope DISABLED_REPOS_SCOPE = Scope.APPLICATION.id("disabledRepos");

public static final String ENABLED = ".enabled";

private final SettingService settingService;

private final WebHookStorage webHookStorage;
Expand All @@ -57,14 +61,18 @@ public class WebhookServiceImpl implements WebhookService {

private final GithubConsumerService githubServiceConsumer;

private final EventService eventService;

public WebhookServiceImpl(SettingService settingService,
GithubTriggerService githubTriggerService,
WebHookStorage webHookStorage,
GithubConsumerService githubServiceConsumer) {
GithubConsumerService githubServiceConsumer,
EventService eventService) {
this.settingService = settingService;
this.githubTriggerService = githubTriggerService;
this.webHookStorage = webHookStorage;
this.githubServiceConsumer = githubServiceConsumer;
this.eventService = eventService;
}

public List<WebHook> getWebhooks(String currentUser, int offset, int limit, boolean forceUpdate) throws IllegalAccessException {
Expand Down Expand Up @@ -277,6 +285,34 @@ public int countOrganizationRepos(long organizationRemoteId, String currentUser)
return githubServiceConsumer.countOrganizationRepos(webHook);
}

@Override
public void setEventEnabledForOrganization(long eventId,
long organizationId,
boolean enabled,
String currentUser) throws IllegalAccessException, ObjectNotFoundException {
if (!Utils.isRewardingManager(currentUser)) {
throw new IllegalAccessException("The user is not authorized to enable/disable event for organization");
}
EventDTO eventDTO = eventService.getEvent(eventId);
if (eventDTO == null) {
throw new ObjectNotFoundException("event not found");
}
Map<String, String> eventProperties = eventDTO.getProperties();
if (eventProperties != null && !eventProperties.isEmpty()) {
String eventProjectStatus = eventProperties.get(organizationId + ENABLED);
if (StringUtils.isNotBlank(eventProjectStatus)) {
eventProperties.remove(organizationId + ENABLED);
eventProperties.put(organizationId + ENABLED, String.valueOf(enabled));
eventDTO.setProperties(eventProperties);
}
} else {
Map<String, String> properties = new HashMap<>();
properties.put(organizationId + ENABLED, String.valueOf(enabled));
eventDTO.setProperties(properties);
}
eventService.updateEvent(eventDTO);
}

@SuppressWarnings("unchecked")
private void forceUpdateWebhook(WebHook webHook) {
TokenStatus tokenStatus = githubServiceConsumer.checkGitHubTokenStatus(webHook.getToken());
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -88,14 +88,14 @@ public String deleteWebhookHook(WebHook webHook) {
}
}

public List<RemoteRepository> retrieveOrganizationRepos(WebHook webHook, int page, int perPage) {
public List<RemoteRepository> retrieveOrganizationRepos(long organizationId, String accessToken , int page, int perPage) {

List<RemoteRepository> remoteRepositories = new ArrayList<>();
String url = GITHUB_API_URL + webHook.getOrganizationId() + "/repos?per_page=" + perPage + "&page=" + page;
String url = GITHUB_API_URL + organizationId + "/repos?per_page=" + perPage + "&page=" + page;

URI uri = URI.create(url);
try {
String response = processGet(uri, webHook.getToken());
String response = processGet(uri, accessToken);
if (response != null) {
Map<String, Object>[] repositoryMaps = fromJsonStringToMapCollection(response);
for (Map<String, Object> repoMap : repositoryMaps) {
Expand All @@ -116,11 +116,11 @@ public List<RemoteRepository> retrieveOrganizationRepos(WebHook webHook, int pag
return remoteRepositories;
}

public int countOrganizationRepos(WebHook webHook) {
String url = GITHUB_API_URL + webHook.getOrganizationId() + "/repos";
public int countOrganizationRepos(long organizationId, String accessToken) {
String url = GITHUB_API_URL + organizationId + "/repos";
URI uri = URI.create(url);
try {
String response = processGet(uri, webHook.getToken());
String response = processGet(uri, accessToken);
if (response != null) {
Map<String, Object>[] repositoryMaps = fromJsonStringToMapCollection(response);
return (int) Arrays.stream(repositoryMaps).count();
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -48,11 +48,12 @@ public GithubConsumerCachedStorage(CacheService cacheService) {
ExoCache<Serializable, Object> cacheInstance = cacheService.getCacheInstance(GITHUB_CACHE_NAME);
this.githubFutureCache = new FutureExoCache<>((context, key) -> {
if (ORG_REPOS_CONTEXT == context.getContext()) {
return GithubConsumerCachedStorage.super.retrieveOrganizationRepos(context.getWebHook(),
return GithubConsumerCachedStorage.super.retrieveOrganizationRepos(context.getOrganizationId(),
context.getAccessToken(),
context.getPage(),
context.getPerPage());
} else if (ORG_REPOS_COUNT_CONTEXT == context.getContext()) {
return GithubConsumerCachedStorage.super.countOrganizationRepos(context.getWebHook());
return GithubConsumerCachedStorage.super.countOrganizationRepos(context.getOrganizationId(), context.getAccessToken());
} else if (ORG_BY_ID_CONTEXT == context.getContext()) {
return GithubConsumerCachedStorage.super.retrieveRemoteOrganization(context.getOrganizationId(),
context.getAccessToken());
Expand All @@ -74,16 +75,16 @@ public String deleteWebhookHook(WebHook webHook) {
}

@Override
public List<RemoteRepository> retrieveOrganizationRepos(WebHook webHook, int page, int perPage) {
CacheKey cacheKey = new CacheKey(ORG_REPOS_CONTEXT, webHook, page, perPage);
public List<RemoteRepository> retrieveOrganizationRepos(long organizationId, String accessToken, int page, int perPage) {
CacheKey cacheKey = new CacheKey(ORG_REPOS_CONTEXT, organizationId, accessToken, page, perPage);
List<RemoteRepository> remoteRepositories =
(List<RemoteRepository>) this.githubFutureCache.get(cacheKey, cacheKey.hashCode());
return remoteRepositories == null ? Collections.emptyList() : remoteRepositories;
}

@Override
public int countOrganizationRepos(WebHook webHook) {
CacheKey cacheKey = new CacheKey(ORG_REPOS_COUNT_CONTEXT, webHook);
public int countOrganizationRepos(long organizationId, String accessToken) {
CacheKey cacheKey = new CacheKey(ORG_REPOS_COUNT_CONTEXT, organizationId, accessToken);
return (int) this.githubFutureCache.get(cacheKey, cacheKey.hashCode());
}

Expand All @@ -106,7 +107,7 @@ public void clearCache() {

@Override
public void clearCache(WebHook webHook) {
this.githubFutureCache.remove(new CacheKey(ORG_REPOS_COUNT_CONTEXT, webHook).hashCode());
this.githubFutureCache.remove(new CacheKey(ORG_REPOS_COUNT_CONTEXT, webHook.getOrganizationId(), webHook.getToken()).hashCode());
this.githubFutureCache.remove(new CacheKey(ORG_BY_ID_CONTEXT, webHook.getOrganizationId(), webHook.getToken()).hashCode());
this.githubFutureCache.remove(new CacheKey(TOKEN_STATUS_CONTEXT, webHook.getToken()).hashCode());
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -24,7 +24,6 @@
import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.NoArgsConstructor;
import org.exoplatform.gamification.github.model.WebHook;

@AllArgsConstructor
@NoArgsConstructor
Expand All @@ -33,8 +32,6 @@ public class CacheKey implements Serializable {

private static final long serialVersionUID = -8995567724453740730L;

private transient WebHook webHook;

private ProgramFilter programFilter;

private int page;
Expand All @@ -45,17 +42,11 @@ public class CacheKey implements Serializable {

private String accessToken;

private long programId;

private Integer context;

public CacheKey(Integer context, WebHook webHook) {
this.webHook = webHook;
this.context = context;
}

public CacheKey(Integer context, WebHook webHook, int page, int perPage) {
this.webHook = webHook;
public CacheKey(Integer context, long organizationId, String accessToken, int page, int perPage) {
this.organizationId = organizationId;
this.accessToken = accessToken;
this.page = page;
this.perPage = perPage;
this.context = context;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -274,29 +274,6 @@
</object-param>
</init-params>
</component-plugin>
<component-plugin>
<name>RequestReviewForPullRequest</name>
<set-method>addPlugin</set-method>
<type>io.meeds.gamification.plugin.EventConfigPlugin</type>
<init-params>
<value-param>
<name>event-title</name>
<value>requestReviewForPullRequest</value>
</value-param>
<value-param>
<name>event-type</name>
<value>github</value>
</value-param>
<value-param>
<name>event-trigger</name>
<value>pull_request</value>
</value-param>
<value-param>
<name>event-can-cancel</name>
<value>true</value>
</value-param>
</init-params>
</component-plugin>
</external-component-plugins>

<external-component-plugins>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -108,6 +108,9 @@
<column name="ORGANIZATION_ID" type="BIGINT">
<constraints nullable="false"/>
</column>
<column name="ORGANIZATION_NAME" type="NVARCHAR(250)">
<constraints nullable="false"/>
</column>
<column name="TRIGGERS" type="NVARCHAR(2000)">
<constraints nullable="false"/>
</column>
Expand Down Expand Up @@ -137,11 +140,6 @@
<changeSet author="exo-gamification" id="1.0.0-9" dbms="oracle,postgresql">
<createSequence sequenceName="SEQ_GITHUB_WEBHOOKS_ID" startValue="1"/>
</changeSet>
<changeSet author="exo-github-connector" id="1.0.0-10">
<addColumn tableName="GITHUB_WEBHOOKS">
<column name="ORGANIZATION_NAME" type="NVARCHAR(250)"/>
</addColumn>
</changeSet>
</databaseChangeLog>


Original file line number Diff line number Diff line change
Expand Up @@ -59,7 +59,7 @@ export default {
},
methods: {
enableDisableEvent() {
this.$gamificationConnectorService.saveEventStatus(this.id, this.organizationId, !this.enabled);
this.$githubConnectorService.saveEventStatus(this.id, this.organizationId, !this.enabled);
},
}
};
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -132,6 +132,26 @@ export function enableDisableWatchScope(organizationId, enabled) {
});
}

export function saveEventStatus(eventId, organizationId, enabled) {
const formData = new FormData();
formData.append('eventId', eventId);
formData.append('organizationId', organizationId);
formData.append('enabled', enabled);

return fetch(`${eXo.env.portal.context}/${eXo.env.portal.rest}/gamification/connectors/github/hooks/events/status`, {
method: 'POST',
credentials: 'include',
headers: {
'Content-Type': 'application/x-www-form-urlencoded',
},
body: new URLSearchParams(formData).toString(),
}).then(resp => {
if (!resp?.ok) {
throw new Error('Response code indicates a server error', resp);
}
});
}

export function forceUpdateWebhooks() {
return fetch(`${eXo.env.portal.context}/${eXo.env.portal.rest}/gamification/connectors/github/hooks/forceUpdate`, {
method: 'PATCH',
Expand Down

0 comments on commit fa89192

Please sign in to comment.