Skip to content

Commit

Permalink
🧵 🐛 🫣 Clean up user modification
Browse files Browse the repository at this point in the history
  • Loading branch information
ebullient committed Jun 11, 2024
1 parent cf70916 commit 5f1d452
Show file tree
Hide file tree
Showing 14 changed files with 517 additions and 230 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -77,7 +77,7 @@ public DataCommonObject(DataCommonObject other) {
this.body = other.body;
}

public Date mostRecent() {
public Date mostRecentEdit() {
return lastEditedAt == null ? createdAt : lastEditedAt;
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,7 @@

import org.kohsuke.github.GHFileNotFoundException;
import org.kohsuke.github.GHIOException;
import org.kohsuke.github.GHIssue;
import org.kohsuke.github.GHOrganization;
import org.kohsuke.github.GHRepository;
import org.kohsuke.github.GHTeam;
Expand Down Expand Up @@ -497,6 +498,22 @@ public DataCommonItem updateItemDescription(EventType eventType, String nodeId,
};
}

public boolean closeIssue(GHIssue issue) {
if (isDryRun()) {
Log.debugf("[%s] closeIssue would close issue %s", getLogId(), issue.getNumber());
return true;
}
execGitHubSync((gh, dryRun) -> {
issue.close();
return null;
});
if (hasErrors()) {
clearNotFound();
return false;
}
return true;
}

public List<DataPullRequestReview> queryReviews(String nodeId) {
if (hasErrors()) {
return List.of();
Expand Down Expand Up @@ -713,6 +730,27 @@ public Set<String> collaborators(String repoFullName) {
return collaborators;
}

public void addTeamMember(GHUser user, String teamFullName) {
if (isDryRun()) {
Log.debugf("[%s] addTeamMember would add %s to %s", getLogId(), user.getLogin(), teamFullName);
return;
}
// this will trigger membership change events, which will come back around to update
// the cache.
String orgName = toOrganizationName(teamFullName);
String relativeName = toRelativeName(orgName, teamFullName);
GHOrganization org = getOrganization(orgName);
execGitHubSync((gh, dryRun) -> {
GHTeam ghTeam = org.getTeamByName(relativeName);
ghTeam.add(user);
return null;
});
if (hasErrors()) {
clearNotFound();
}
TEAM_MEMBERS.invalidate(teamFullName);
}

public String[] getErrorAddresses() {
return ctx.botErrorEmailAddress();
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@
import org.commonhaus.automation.admin.api.CommonhausUser.MembershipApplication;
import org.commonhaus.automation.github.context.DataCommonComment;
import org.commonhaus.automation.github.context.DataCommonItem;
import org.commonhaus.automation.github.context.DataLabel;
import org.commonhaus.automation.markdown.MarkdownConverter;

import com.fasterxml.jackson.annotation.JsonIgnore;
Expand All @@ -15,6 +16,10 @@

@RegisterForReflection
public class ApplicationData {
public static final String NEW = "application/new";
public static final String ACCEPTED = "application/accepted";
public static final String DECLINED = "application/declined";

static final Pattern CONTRIBUTIONS = Pattern.compile(
"([\\s\\S]*?<!--CONTRIBUTION::-->)([\\s\\S]*?)(<!--::CONTRIBUTION-->[\\s\\S]*?)", Pattern.CASE_INSENSITIVE);
static final Pattern NOTES = Pattern.compile("([\\s\\S]*?<!--NOTES::-->)([\\s\\S]*?)(<!--::NOTES-->[\\s\\S]*?)",
Expand All @@ -30,9 +35,9 @@ public class ApplicationData {
String additionalNotes;
Feedback feedback;

public ApplicationData(MemberSession session, DataCommonItem issue) {
public ApplicationData(String login, DataCommonItem issue) {
this.title = issue == null ? null : issue.title;
if (title == null || !ownerEquals(session.login())) {
if (title == null || !ownerEquals(login)) {
return;
}
application = MembershipApplication.fromDataCommonType(issue);
Expand All @@ -58,7 +63,7 @@ public void setFeedback(Feedback feedback) {
}

@JsonIgnore
public boolean isOwner() {
public boolean isValid() {
return application != null;
}

Expand All @@ -84,14 +89,19 @@ public Feedback(DataCommonComment dataCommonComment) {
String content = dataCommonComment.body.replaceAll("::response::", "").trim();

this.htmlContent = MarkdownConverter.toHtml(content);
date = dataCommonComment.mostRecent().toString();
date = dataCommonComment.mostRecentEdit().toString();
}
}

public static String issueContent(MemberSession session, ApplicationPost applicationPost) {
return """
[%s](%s)
> [!TIP]
> - Include "::response::" in your comment to send feedback to the applicant.
> - Add the 'application/accepted' label to accept the application.
> - Add the 'application/declined' label to decline or reject the application.
## Contribution Details
<!--CONTRIBUTION::-->
%s
Expand All @@ -112,12 +122,25 @@ public static String createTitle(MemberSession session) {
return "Membership application: %s (%s)".formatted(session.name(), session.login());
}

public static String getLogin(DataCommonItem issue) {
return issue.title.replaceAll("Membership application: .*? \\((.*)\\)", "$1");
}

public static boolean isMemberApplicationEvent(DataCommonItem issue, DataLabel label) {
return issue.title.startsWith("Membership application:")
&& (ACCEPTED.equals(label.name) || DECLINED.equals(label.name));
}

public static boolean isUserFeedback(String body) {
return body.contains("::response::");
}

public static boolean isAccepted(DataLabel label) {
return ACCEPTED.equals(label.name);
}

public static boolean isNewer(DataCommonComment x, Date issueMostRecent) {
Log.debugf("isNewer: %s %s", x.mostRecent(), issueMostRecent);
return x.mostRecent().after(issueMostRecent);
Log.debugf("isNewer: %s %s", x.mostRecentEdit(), issueMostRecent);
return x.mostRecentEdit().after(issueMostRecent);
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -36,7 +36,7 @@ public enum MemberStatus {
ACTIVE,
PENDING,
INACTIVE,
DENIED,
DECLINED,
REVOKED,
SUSPENDED,
SPONSOR,
Expand Down Expand Up @@ -66,7 +66,7 @@ public boolean updateToPending() {
|| this == INACTIVE;
}

public boolean updateToActive() {
public boolean updateFromPending() {
return this == UNKNOWN
|| this == PENDING;
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,7 @@
import org.commonhaus.automation.admin.forwardemail.Alias;
import org.commonhaus.automation.admin.github.AppContextService;
import org.commonhaus.automation.admin.github.CommonhausDatastore;
import org.commonhaus.automation.admin.github.CommonhausDatastore.UpdateEvent;

import io.quarkus.security.Authenticated;

Expand Down Expand Up @@ -68,9 +69,7 @@ public Response getAliases(@DefaultValue("false") @QueryParam("refresh") boolean
aliasMap = ctx.getAliases(emailAddresses, refresh);

if (!forwardEmail.configured && !aliasMap.isEmpty()) {
forwardEmail.configured = true;
user = datastore.setCommonhausUser(user, session.roles(),
"Fix forward email service active flag", false);
user = updatedConfiguredFlag(user);
}
}
return user.toResponse()
Expand Down Expand Up @@ -112,9 +111,7 @@ public Response updateAliases(Map<String, Set<String>> aliases) {
// Update alias mappings
Map<String, Alias> aliasMap = ctx.setRecipients(session.name(), aliases);
if (!forwardEmail.configured && !aliasMap.isEmpty()) {
forwardEmail.configured = true;
user = datastore.setCommonhausUser(user, session.roles(),
"Fix forward email service active flag", false);
user = updatedConfiguredFlag(user);
}
return user.toResponse()
.setData(ApiResponse.Type.ALIAS, aliasMap)
Expand All @@ -127,6 +124,18 @@ public Response updateAliases(Map<String, Set<String>> aliases) {
}
}

CommonhausUser updatedConfiguredFlag(CommonhausUser user) {
// eventual consistency. No big deal if this
CommonhausUser result = datastore.setCommonhausUser(new UpdateEvent(user,
(c, u) -> {
u.services().forwardEmail().configured = true;
},
"Fix forward email service active flag",
false,
false));
return result == null ? user : result;
}

List<String> getEmailAddresses(MemberSession session, ForwardEmail forwardEmail) {
List<String> addresses = new ArrayList<>();
addresses.add(session.login());
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,106 @@
package org.commonhaus.automation.admin.api;

import java.util.Date;
import java.util.List;

import jakarta.enterprise.context.ApplicationScoped;
import jakarta.inject.Inject;

import org.commonhaus.automation.admin.api.ApplicationData.Feedback;
import org.commonhaus.automation.admin.api.CommonhausUser.MemberStatus;
import org.commonhaus.automation.admin.github.AppContextService;
import org.commonhaus.automation.admin.github.CommonhausDatastore;
import org.commonhaus.automation.admin.github.CommonhausDatastore.UpdateEvent;
import org.commonhaus.automation.admin.github.ScopedQueryContext;
import org.commonhaus.automation.github.context.DataCommonComment;
import org.commonhaus.automation.github.context.DataCommonItem;
import org.commonhaus.automation.github.context.DataLabel;
import org.commonhaus.automation.github.context.EventType;
import org.kohsuke.github.GHIssue;
import org.kohsuke.github.GHUser;

@ApplicationScoped
public class MemberApplicationProcess {

@Inject
AppContextService ctx;

@Inject
CommonhausDatastore datastore;

public void handleApplicationEvent(ScopedQueryContext qc, GHIssue issue, DataCommonItem item, DataLabel label) {
if (!ApplicationData.isMemberApplicationEvent(item, label)) {
return;
}

String login = ApplicationData.getLogin(item);
GHUser applicant = qc.getUser(login);
CommonhausUser user = datastore.getCommonhausUser(login, applicant.getId(), false, false);
if (user == null) {
return;
}

ApplicationData applicationData = new ApplicationData(login, item);
if (!applicationData.isValid()) {
// TODO: do we fix bad data from this side? (hasn't happened yet.. )
ctx.logAndSendEmail(qc.getLogId(), "Invalid application data",
"Unable to find valid application data for login %s and issue %s (%s)"
.formatted(login, item.id, item.title),
null, null);
return;
}

if (ApplicationData.isAccepted(label)) {
datastore.setCommonhausUser(new UpdateEvent(user,
(c, u) -> {
if (u.status().updateFromPending()) {
u.status(MemberStatus.ACTIVE);
}
u.application = null;
},
"Membership application accepted",
true,
true));
String teamFullName = ctx.getTeamForRole("member");
if (teamFullName != null && !qc.hasErrors()) {
qc.addTeamMember(applicant, teamFullName);
}
} else {
datastore.setCommonhausUser(new UpdateEvent(user,
(c, u) -> {
if (u.status().updateFromPending()) {
u.status(MemberStatus.DECLINED);
}
},
"Membership application declined",
true,
true));
}
if (!qc.hasErrors()) {
qc.closeIssue(issue);
qc.removeLabels(item.id, List.of(ApplicationData.NEW));
}
}

ApplicationData findUserApplication(MemberSession session, String applicationId) {
ScopedQueryContext qc = ctx.getDatastoreContext();
DataCommonItem issue = qc.getItem(EventType.issue, applicationId);
ApplicationData application = new ApplicationData(session.login(), issue);
if (application.isValid()) {
Feedback feedback = getFeedback(qc, applicationId, issue.mostRecentEdit());
if (feedback != null) {
application.setFeedback(feedback);
}
}
return application;
}

Feedback getFeedback(ScopedQueryContext qc, String nodeId, Date mostRecentEdit) {
List<DataCommonComment> comments = qc.getComments(nodeId,
x -> ApplicationData.isUserFeedback(x.body) && ApplicationData.isNewer(x, mostRecentEdit));

return (comments == null || comments.isEmpty())
? null
: new Feedback(comments.get(0));
}
}
Loading

0 comments on commit 5f1d452

Please sign in to comment.