summaryrefslogtreecommitdiffstats
path: root/controller-api
diff options
context:
space:
mode:
authorJon Marius Venstad <jvenstad@yahoo-inc.com>2017-10-24 15:06:17 +0200
committerJon Marius Venstad <jvenstad@yahoo-inc.com>2017-10-24 15:06:17 +0200
commite5afd0ea72b735adf1d755c35f38e9dabe682b02 (patch)
treefcce53365fa0cc7473f0c16bbb68d3d47e63e902 /controller-api
parente5e197ec9390033da499cebfb68ba92ac74cb17b (diff)
Adjustments, mock and tests
Diffstat (limited to 'controller-api')
-rw-r--r--controller-api/src/main/java/com/yahoo/vespa/hosted/controller/api/integration/organization/Issue.java32
-rw-r--r--controller-api/src/main/java/com/yahoo/vespa/hosted/controller/api/integration/organization/MockOrganization.java146
-rw-r--r--controller-api/src/main/java/com/yahoo/vespa/hosted/controller/api/integration/organization/Organization.java109
-rw-r--r--controller-api/src/main/java/com/yahoo/vespa/hosted/controller/api/integration/organization/User.java8
4 files changed, 230 insertions, 65 deletions
diff --git a/controller-api/src/main/java/com/yahoo/vespa/hosted/controller/api/integration/organization/Issue.java b/controller-api/src/main/java/com/yahoo/vespa/hosted/controller/api/integration/organization/Issue.java
index 71ad7df6249..74f02172af0 100644
--- a/controller-api/src/main/java/com/yahoo/vespa/hosted/controller/api/integration/organization/Issue.java
+++ b/controller-api/src/main/java/com/yahoo/vespa/hosted/controller/api/integration/organization/Issue.java
@@ -3,36 +3,46 @@ package com.yahoo.vespa.hosted.controller.api.integration.organization;
import com.yahoo.vespa.hosted.controller.api.identifiers.PropertyId;
+import java.util.Objects;
import java.util.Optional;
public class Issue {
private final String summary;
private final String description;
+ private final String label;
private final User assignee;
private final PropertyId propertyId;
- private Issue(String summary, String description, User assignee, PropertyId propertyId) {
+ private Issue(String summary, String description, String label, User assignee, PropertyId propertyId) {
+ if (summary.isEmpty()) throw new IllegalArgumentException("Issue summary can not be empty!");
+ if (description.isEmpty()) throw new IllegalArgumentException("Issue description can not be empty!");
+ Objects.requireNonNull(propertyId, "An issue must belong to a property!");
+
this.summary = summary;
this.description = description;
+ this.label = label;
this.assignee = assignee;
this.propertyId = propertyId;
}
- public Issue(String summary, String description) {
- this(summary, description, null, null);
+ public Issue(String summary, String description, PropertyId propertyId) {
+ this(summary, description, null, null, propertyId);
}
public Issue append(String appendage) {
- return new Issue(summary, description + appendage, assignee, propertyId);
+ return new Issue(summary, description + appendage, label, assignee, propertyId);
}
- public Issue withUser(User assignee) {
- return new Issue(summary, description, assignee, propertyId);
+ public Issue withLabel(String label) {
+ return new Issue(summary, description, label, assignee, propertyId);
+ }
+ public Issue withAssignee(User assignee) {
+ return new Issue(summary, description, label, assignee, propertyId);
}
public Issue withPropertyId(PropertyId propertyId) {
- return new Issue(summary, description, assignee, propertyId);
+ return new Issue(summary, description, label, assignee, propertyId);
}
public String summary() {
@@ -43,12 +53,16 @@ public class Issue {
return description;
}
+ public Optional<String> label() {
+ return Optional.ofNullable(label);
+ }
+
public Optional<User> assignee() {
return Optional.ofNullable(assignee);
}
- public Optional<PropertyId> propertyId() {
- return Optional.ofNullable(propertyId);
+ public PropertyId propertyId() {
+ return propertyId;
}
}
diff --git a/controller-api/src/main/java/com/yahoo/vespa/hosted/controller/api/integration/organization/MockOrganization.java b/controller-api/src/main/java/com/yahoo/vespa/hosted/controller/api/integration/organization/MockOrganization.java
new file mode 100644
index 00000000000..51bba7bb52c
--- /dev/null
+++ b/controller-api/src/main/java/com/yahoo/vespa/hosted/controller/api/integration/organization/MockOrganization.java
@@ -0,0 +1,146 @@
+package com.yahoo.vespa.hosted.controller.api.integration.organization;
+
+import com.yahoo.vespa.hosted.controller.api.identifiers.PropertyId;
+
+import java.net.URI;
+import java.time.Clock;
+import java.time.Duration;
+import java.time.Instant;
+import java.util.Collections;
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+import java.util.Optional;
+import java.util.concurrent.atomic.AtomicLong;
+
+public class MockOrganization implements Organization {
+
+ private final Clock clock;
+ private final AtomicLong counter;
+ private final HashMap<IssueId, WrappedIssue> issues;
+ private final HashMap<PropertyId, PropertyInfo> properties;
+
+ public MockOrganization(Clock clock) {
+ this.clock = clock;
+
+ counter = new AtomicLong();
+ issues = new HashMap<>();
+ properties = new HashMap<>();
+ }
+
+ @Override
+ public IssueId file(Issue issue) {
+ IssueId issueId = IssueId.from("" + counter.incrementAndGet());
+ issues.put(issueId, new WrappedIssue(issue));
+ return issueId;
+ }
+
+ @Override
+ public Optional<IssueId> findBySimilarity(Issue issue) {
+ return issues.entrySet().stream()
+ .filter(entry -> entry.getValue().issue.summary().equals(issue.summary()))
+ .findFirst()
+ .map(Map.Entry::getKey);
+ }
+
+ @Override
+ public void update(IssueId issueId, String description) {
+ touch(issueId);
+ }
+
+ @Override
+ public void commentOn(IssueId issueId, String comment) {
+ touch(issueId);
+ }
+
+ @Override
+ public boolean isOpen(IssueId issueId) {
+ return issues.get(issueId).open;
+ }
+
+ @Override
+ public boolean isActive(IssueId issueId, Duration maxInactivity) {
+ return issues.get(issueId).updated.isAfter(clock.instant().minus(maxInactivity));
+ }
+
+ @Override
+ public Optional<User> assigneeOf(IssueId issueId) {
+ return Optional.ofNullable(issues.get(issueId).assignee);
+ }
+
+ @Override
+ public boolean reassign(IssueId issueId, User assignee) {
+ issues.get(issueId).assignee = assignee;
+ touch(issueId);
+ return true;
+ }
+
+ @Override
+ public List<? extends List<? extends User>> contactsFor(PropertyId propertyId) {
+ return properties.get(propertyId).contacts;
+ }
+
+ @Override
+ public URI issueCreationUri(PropertyId propertyId) {
+ return null;
+ }
+
+ @Override
+ public URI contactsUri(PropertyId propertyId) {
+ return null;
+ }
+
+ @Override
+ public URI propertyUri(PropertyId propertyId) {
+ return null;
+ }
+
+ public void close(IssueId issueId) {
+ issues.get(issueId).open = false;
+ touch(issueId);
+ }
+
+ public void setDefaultAssigneeFor(PropertyId propertyId, User defaultAssignee) {
+ properties.get(propertyId).defaultAssignee = defaultAssignee;
+ }
+
+ public void setContactsFor(PropertyId propertyId, List<List<User>> contacts) {
+ properties.get(propertyId).contacts = contacts;
+ }
+
+ public void addProperty(PropertyId propertyId) {
+ properties.put(propertyId, new PropertyInfo());
+ }
+
+ private void touch(IssueId issueId) {
+ issues.get(issueId).updated = clock.instant();
+ }
+
+
+ private class WrappedIssue {
+
+ private Issue issue;
+ private Instant updated;
+ private boolean open;
+ private User assignee;
+
+ private WrappedIssue(Issue issue) {
+ this.issue = issue;
+
+ updated = clock.instant();
+ open = true;
+ assignee = issue.assignee().orElse(properties.get(issue.propertyId()).defaultAssignee);
+ }
+
+ }
+
+
+ private class PropertyInfo {
+
+ private User defaultAssignee;
+ private List<List<User>> contacts = Collections.emptyList();
+
+ }
+
+}
+
diff --git a/controller-api/src/main/java/com/yahoo/vespa/hosted/controller/api/integration/organization/Organization.java b/controller-api/src/main/java/com/yahoo/vespa/hosted/controller/api/integration/organization/Organization.java
index 7e07a710e37..913c2d96a14 100644
--- a/controller-api/src/main/java/com/yahoo/vespa/hosted/controller/api/integration/organization/Organization.java
+++ b/controller-api/src/main/java/com/yahoo/vespa/hosted/controller/api/integration/organization/Organization.java
@@ -2,25 +2,15 @@ package com.yahoo.vespa.hosted.controller.api.integration.organization;
import com.yahoo.vespa.hosted.controller.api.identifiers.PropertyId;
+import java.io.UncheckedIOException;
+import java.net.URI;
import java.time.Duration;
import java.util.List;
-import java.util.stream.Collectors;
+import java.util.Optional;
public interface Organization {
/**
- * Returns a flat list of all escalation targets among the given users.
- * An escalation target is anyone of higher rank than the given assignee.
- */
- static List<User> escalationTargetsFrom(List<List<User>> contacts, User assignee) {
- for (int i = 0; i < contacts.size(); i++)
- if (contacts.get(i).contains(assignee))
- return contacts.subList(i + 1, contacts.size()).stream().flatMap(List::stream).collect(Collectors.toList());
-
- return contacts.stream().flatMap(List::stream).collect(Collectors.toList());
- }
-
- /**
* File an issue with its given property or the default, and with the specific assignee, if present.
*
* @param issue The issue to file.
@@ -29,47 +19,52 @@ public interface Organization {
IssueId file(Issue issue);
/**
- * File the given issue, or update it if is already exists (based on similarity).
+ * Returns the ID of this issue, if it exists and is open, based on a similarity search.
*
- * @param issue The issue to file or update.
- * @return ID of the created or updated issue.
+ * @param issue The issue to search for; relevant fields are the summary and the owner (propertyId).
+ * @return ID of the issue, if it is found.
*/
- IssueId fileOrUpdate(Issue issue);
+ Optional<IssueId> findBySimilarity(Issue issue);
/**
- * Reassign an issue to the Vespa operations team for termination.
+ * Update the description of the issue with the given ID.
*
- * @param issueId ID of the issue to reassign.
+ * @param issueId ID of the issue to comment on.
+ * @param description The updated description.
*/
- void terminate(IssueId issueId);
+ void update(IssueId issueId, String description);
/**
- * Escalate an issue filed with the given property.
+ * Add a comment to the issue with the given ID.
*
- * @param issueId ID of the issue to escalate.
- * @param propertyId PropertyId of the tenant owning the application for which the issue was filed.
+ * @param issueId ID of the issue to comment on.
+ * @param comment The comment to add.
*/
- default void escalate(IssueId issueId, PropertyId propertyId) {
- for (User target : escalationTargetsFrom(contactsFor(propertyId), assigneeOf(issueId)))
- if (reassign(issueId, target))
- break;
- }
+ void commentOn(IssueId issueId, String comment);
/**
- * Returns the user assigned to the given issue, if any.
+ * Returns whether the issue is still under investigation.
*
- * @param issueId ID of the issue for which to find the assignee.
- * @return The user responsible for fixing the given issue, if found.
+ * @param issueId ID of the issue to examine.
+ * @return Whether the given issue is under investigation.
*/
- User assigneeOf(IssueId issueId);
+ boolean isOpen(IssueId issueId);
/**
- * Add a comment to the issue with the given ID.
+ * Returns whether there has been significant activity on the issue within the given duration.
*
- * @param issueId ID of the issue to comment on.
- * @param comment The comment to add.
+ * @param issueId ID of the issue to examine.
+ * @return Whether the given issue is actively worked on.
*/
- void comment(IssueId issueId, String comment);
+ boolean isActive(IssueId issueId, Duration maxInactivity);
+
+ /**
+ * Returns the user assigned to the given issue, if any.
+ *
+ * @param issueId ID of the issue for which to find the assignee.
+ * @return The user responsible for fixing the given issue, if found.
+ */
+ Optional<User> assigneeOf(IssueId issueId);
/**
* Reassign the issue with the given ID to the given user, and returns the outcome of this.
@@ -81,28 +76,42 @@ public interface Organization {
boolean reassign(IssueId issueId, User assignee);
/**
- * Returns whether the issue is still under investigation.
- *
- * @param issueId ID of the issue to examine.
- * @return Whether the given issue is under investigation.
- */
- boolean isOpen(IssueId issueId);
-
- /**
- * Returns whether there has been significant activity on the issue within the given duration.
+ * Escalate an issue filed with the given property.
*
- * @param issueId ID of the issue to examine.
- * @return Whether the given issue is actively worked on.
+ * @param issueId ID of the issue to escalate.
+ * @param propertyId PropertyId of the tenant owning the application for which the issue was filed.
*/
- boolean isActive(IssueId issueId, Duration maxInactivityAge);
+ default boolean escalate(IssueId issueId, PropertyId propertyId) {
+ List<? extends List<? extends User>> contacts = contactsFor(propertyId);
+
+ Optional<User> assignee = assigneeOf(issueId);
+ int assigneeLevel = -1;
+ if (assignee.isPresent())
+ for (int level = contacts.size(); --level > assigneeLevel; )
+ if (contacts.get(level).contains(assignee.get()))
+ assigneeLevel = level;
+
+ for (int level = assigneeLevel + 1; level < contacts.size(); level++)
+ for (User target : contacts.get(level))
+ if (reassign(issueId, target))
+ return true;
+
+ return false;
+ }
/**
* Returns a nested list where the entries have increasing rank, and where each entry is
* a list of the users of that rank, by decreasing relevance.
*
* @param propertyId ID of the property for which to list contacts.
- * @return A sorted, nested, sorted list of contacts.
+ * @return A sorted, nested, reverse sorted list of contacts.
*/
- List<List<User>> contactsFor(PropertyId propertyId);
+ List<? extends List<? extends User>> contactsFor(PropertyId propertyId);
+
+ URI issueCreationUri(PropertyId propertyId);
+
+ URI contactsUri(PropertyId propertyId);
+
+ URI propertyUri(PropertyId propertyId);
}
diff --git a/controller-api/src/main/java/com/yahoo/vespa/hosted/controller/api/integration/organization/User.java b/controller-api/src/main/java/com/yahoo/vespa/hosted/controller/api/integration/organization/User.java
index 60a94e04116..13141ee2adb 100644
--- a/controller-api/src/main/java/com/yahoo/vespa/hosted/controller/api/integration/organization/User.java
+++ b/controller-api/src/main/java/com/yahoo/vespa/hosted/controller/api/integration/organization/User.java
@@ -5,10 +5,6 @@ import java.util.Objects;
public class User {
- public static final User none = new User("") {
- public String toString() { return "no one"; }
- };
-
private final String username;
protected User(String username) {
@@ -21,7 +17,7 @@ public class User {
public static User from(String username) {
if (username.isEmpty())
- throw new IllegalArgumentException("username may not be empty");
+ throw new IllegalArgumentException("Username may not be empty!");
return new User(username);
}
@@ -29,7 +25,7 @@ public class User {
@Override
public boolean equals(Object o) {
if (this == o) return true;
- if (o == null || getClass() != o.getClass()) return false;
+ if ( ! (o instanceof User)) return false;
User that = (User) o;
return Objects.equals(username, that.username);
}