/** * See also {@link #reconcile(TurboIssue)} * * @param otherIssue */ private void reconcileLabels(TurboIssue otherIssue) { LocalDateTime thisIssueLabelsModifiedAt = this.getLabelsLastModifiedAt(); LocalDateTime otherIssueLabelsModifiedAt = otherIssue.getLabelsLastModifiedAt(); if (thisIssueLabelsModifiedAt.isBefore(otherIssueLabelsModifiedAt)) { logger.info( "Issue %s's labels %s are stale, replacing with %s", this, this.getLabels(), otherIssue.getLabels()); this.labels = otherIssue.getLabels(); this.labelsLastModifiedAt = Optional.of(otherIssue.getLabelsLastModifiedAt()); } }
/** * Possibly shows a notification or an error dialog depending on {@code success} and {@code * isUndo}. * * @param issue the TurboIssue acted on * @param action the Action that acted on the issue * @param success whether the action was successful * @param isUndo whether action is an undo * @return {@code success} */ private boolean handleActionResult( TurboIssue issue, Action<TurboIssue> action, Boolean success, boolean isUndo) { if (!success) { String errorMessage = "Please check if you have write permissions to " + issue.getRepoId(); showErrorDialog(issue, action, errorMessage); return success; } if (!isUndo) { showNotification(issue.getId(), issue.getTitle(), action.getDescription()); return success; } return success; }
/** * Tests that {@code editIssueState} finds issue with the right id and successfully modify the * issue's labels */ @Test public void editIssueState_successful() { String repoId = "testowner/testrepo"; Optional<TurboIssue> result; TurboIssue issue1 = LogicTests.createOpenIssue(); TurboIssue issue2 = LogicTests.createClosedIssue(); List<TurboIssue> issues = Arrays.asList(issue2, issue1); Model model = new Model( repoId, issues, new ArrayList<TurboLabel>(), new ArrayList<TurboMilestone>(), new ArrayList<TurboUser>()); result = model.editIssueState(issue1.getId(), false); assertEquals(issue1.getId(), result.get().getId()); assertEquals(false, result.get().isOpen()); result = model.editIssueState(issue2.getId(), true); assertEquals(issue2.getId(), result.get().getId()); assertEquals(true, result.get().isOpen()); result = model.editIssueState(issue2.getId(), true); assertEquals(issue2.getId(), result.get().getId()); assertEquals(true, result.get().isOpen()); }
/** * Combines data from a corresponding pull request with data in this issue This method returns a * new combined issue and does not mutate this issue * * @param pullRequest * @return new new combined issue */ public TurboIssue combineWithPullRequest(PullRequest pullRequest) { TurboIssue newIssue = new TurboIssue(this); if (pullRequest.getUpdatedAt() == null) { return newIssue; } LocalDateTime pullRequestUpdatedAt = Utility.dateToLocalDateTime(pullRequest.getUpdatedAt()); if (pullRequestUpdatedAt.isBefore(newIssue.getUpdatedAt())) { return newIssue; } newIssue.setUpdatedAt(pullRequestUpdatedAt); return newIssue; }
/** * Tests that replaceIssueAssigneeOnServer finds issue with the right id and successfully modify * the issue's assignee */ @Test public void replaceIssueAssignee_successful() { String repoId = "testowner/testrepo"; String originalAssignee = "user1"; String newAssignee = "user2"; TurboIssue issue1 = LogicTests.createIssueWithAssignee(1, originalAssignee); TurboIssue issue2 = LogicTests.createIssueWithAssignee(2, originalAssignee); TurboIssue issue3 = LogicTests.createIssueWithAssignee(3, originalAssignee); List<TurboIssue> issues = Arrays.asList(issue3, issue2, issue1); Model model = new Model(repoId, issues, new ArrayList<>(), new ArrayList<>(), new ArrayList<>()); Optional<TurboIssue> result = model.replaceIssueAssignee(issue1.getId(), Optional.of(newAssignee)); assertEquals(1, result.get().getId()); assertEquals(newAssignee, result.get().getAssignee().get()); }
/** * Updates data for issues with corresponding pull requests. Original list of issues and original * issue instances are not mutated * * @param issues * @param pullRequests * @return a new list of issues */ public static List<TurboIssue> combineWithPullRequests( List<TurboIssue> issues, List<PullRequest> pullRequests) { List<TurboIssue> issuesCopy = new ArrayList<>(issues); for (PullRequest pullRequest : pullRequests) { int id = pullRequest.getNumber(); Optional<Integer> corresponding = findIssueWithId(issuesCopy, id); if (corresponding.isPresent()) { TurboIssue issue = issuesCopy.get(corresponding.get()); issuesCopy.set(corresponding.get(), issue.combineWithPullRequest(pullRequest)); } else { String errorMsg = "No corresponding issue for pull request " + pullRequest; logger.error(errorMsg); } } return issuesCopy; }
/** * Tests that replaceIssueLabelsOnServer finds issue with the right id and successfully modify the * issue's labels */ @Test public void replaceIssueLabels_successful() { String repoId = "testowner/testrepo"; List<String> originalLabels = Arrays.asList("label1", "label2"); List<String> newLabels = Arrays.asList("label3", "label4"); TurboIssue issue1 = LogicTests.createIssueWithLabels(1, originalLabels); TurboIssue issue2 = LogicTests.createIssueWithLabels(2, originalLabels); TurboIssue issue3 = LogicTests.createIssueWithLabels(3, originalLabels); List<TurboIssue> issues = Arrays.asList(issue3, issue2, issue1); Model model = new Model( repoId, issues, new ArrayList<TurboLabel>(), new ArrayList<TurboMilestone>(), new ArrayList<TurboUser>()); Optional<TurboIssue> result = model.replaceIssueLabels(issue1.getId(), newLabels); assertEquals(1, result.get().getId()); assertEquals(newLabels, result.get().getLabels()); }
/** * Takes lists of TurboIssues and reconciles the changes between them, returning a list of * TurboIssues with updates from the second. * * @param existing * @param changed */ public static List<TurboIssue> reconcile(List<TurboIssue> existing, List<TurboIssue> changed) { List<TurboIssue> existingCopy = new ArrayList<>(existing); for (TurboIssue issue : changed) { int id = issue.getId(); Optional<Integer> correspondingIssueIndex = findIssueWithId(existingCopy, id); if (!correspondingIssueIndex.isPresent()) { existingCopy.add(new TurboIssue(issue)); } else { TurboIssue existingIssue = existingCopy.get(correspondingIssueIndex.get()); TurboIssue newIssue = new TurboIssue(issue); // newIssue is constructed from an external Issue object. // It won't have the transient state that its TurboIssue // counterpart has, so we have to explicitly transfer it. newIssue.transferTransientState(existingIssue); newIssue.reconcile(existingIssue); existingCopy.set(correspondingIssueIndex.get(), newIssue); } } return existingCopy; }
/** * Tests that replaceIssueMilestone finds issue with the right id and successfully modify the * issue's milestone */ @Test public void replaceIssueMilestone_successful() { Optional<Integer> milestoneIdReplacement = Optional.of(1); String repoId = "testowner/testrepo"; TurboIssue issue1 = LogicTests.createIssueWithMilestone(1, Optional.of(0)); TurboIssue issue2 = LogicTests.createIssueWithMilestone(2, Optional.of(1)); TurboIssue issue3 = LogicTests.createIssueWithMilestone(3, Optional.of(1)); List<TurboIssue> issues = Arrays.asList(issue3, issue2, issue1); Model model = new Model( repoId, issues, new ArrayList<TurboLabel>(), new ArrayList<TurboMilestone>(), new ArrayList<TurboUser>()); Optional<TurboIssue> result = model.replaceIssueMilestone(issue1.getId(), milestoneIdReplacement); assertEquals(1, result.get().getId()); assertTrue(result.get().getMilestone().isPresent()); assertEquals(milestoneIdReplacement, result.get().getMilestone()); }
/** * Determines if an issue has had new comments added (or removed) based on its last-known comment * count in {@link #issueCommentCounts}. * * @param issue * @return true if the issue has changed, false otherwise */ private boolean issueHasNewComments(TurboIssue issue, boolean hasMetadata) { if (currentFilterExpression.getQualifierNames().contains(Qualifier.UPDATED) && hasMetadata) { return issueNonSelfCommentCounts.containsKey(issue.getId()) && Math.abs( issueNonSelfCommentCounts.get(issue.getId()) - issue.getMetadata().getNonSelfCommentCount()) > 0; } else { return issueCommentCounts.containsKey(issue.getId()) && Math.abs(issueCommentCounts.get(issue.getId()) - issue.getCommentCount()) > 0; } }
/** * Updates {@link #issueCommentCounts} with the latest counts. Returns a list of issues which have * new comments. * * @return */ private HashSet<Integer> updateIssueCommentCounts(boolean hasMetadata) { HashSet<Integer> result = new HashSet<>(); for (TurboIssue issue : getIssueList()) { if (issueCommentCounts.containsKey(issue.getId())) { // We know about this issue; check if it's been updated if (issueHasNewComments(issue, hasMetadata)) { result.add(issue.getId()); } } else { // We don't know about this issue, just put the current comment count. issueNonSelfCommentCounts.put(issue.getId(), issue.getMetadata().getNonSelfCommentCount()); issueCommentCounts.put(issue.getId(), issue.getCommentCount()); } } return result; }
// Copy constructor public TurboIssue(TurboIssue issue) { this.id = issue.id; this.title = issue.title; this.creator = issue.creator; this.createdAt = issue.createdAt; this.isPullRequest = issue.isPullRequest; this.description = issue.description; this.updatedAt = replaceNull(issue.updatedAt, this.createdAt); this.commentCount = issue.commentCount; this.isOpen = issue.isOpen; this.assignee = issue.assignee; this.labels = new ArrayList<>(issue.labels); this.milestone = issue.milestone; this.metadata = issue.metadata; this.repoId = issue.repoId; this.markedReadAt = issue.markedReadAt; this.labelsLastModifiedAt = Optional.of(issue.getLabelsLastModifiedAt()); }
private void setupListView() { setVgrow(listView, Priority.ALWAYS); setupKeyboardShortcuts(); listView.setOnItemSelected( i -> { TurboIssue issue = listView.getItems().get(i); ui.triggerEvent( new IssueSelectedEvent( issue.getRepoId(), issue.getId(), panelIndex, issue.isPullRequest())); // Save the stored comment count as its own comment count. // The refreshItems(false) call that follows will remove the highlighted effect of the // comment bubble. // (if it was there before) issueCommentCounts.put(issue.getId(), issue.getCommentCount()); issueNonSelfCommentCounts.put( issue.getId(), issue.getMetadata().getNonSelfCommentCount()); // We assume we already have metadata, so we pass true to avoid refreshItems from trying // to get // metadata after clicking. refreshItems(true); }); }
private boolean isExistingAssignee(TurboIssue issue, PickerAssignee assignee) { if (!issue.getAssignee().isPresent()) return false; return issue.getAssignee().get().equals(assignee.getLoginName()); }
private void setupKeyboardShortcuts() { filterTextField.addEventHandler( KeyEvent.KEY_RELEASED, event -> { if (KeyboardShortcuts.BOX_TO_LIST.match(event)) { event.consume(); listView.selectFirstItem(); } if (event.getCode() == KeyboardShortcuts.DOUBLE_PRESS) { event.consume(); } if (KeyPress.isDoublePress(KeyboardShortcuts.DOUBLE_PRESS, event.getCode())) { event.consume(); listView.selectFirstItem(); } if (KeyboardShortcuts.MAXIMIZE_WINDOW.match(event)) { ui.maximizeWindow(); } if (KeyboardShortcuts.MINIMIZE_WINDOW.match(event)) { ui.minimizeWindow(); } if (KeyboardShortcuts.DEFAULT_SIZE_WINDOW.match(event)) { ui.setDefaultWidth(); } if (KeyboardShortcuts.SWITCH_DEFAULT_REPO.match(event)) { ui.switchDefaultRepo(); } }); addEventHandler( KeyEvent.KEY_RELEASED, event -> { if (event.getCode() == KeyboardShortcuts.markAsRead) { Optional<TurboIssue> item = listView.getSelectedItem(); if (!item.isPresent()) { return; } TurboIssue issue = item.get(); LocalDateTime now = LocalDateTime.now(); ui.prefs.setMarkedReadAt(issue.getRepoId(), issue.getId(), now); issue.setMarkedReadAt(Optional.of(now)); issue.setIsCurrentlyRead(true); parentPanelControl.refresh(); listView.selectNextItem(); } if (event.getCode() == KeyboardShortcuts.markAsUnread) { Optional<TurboIssue> item = listView.getSelectedItem(); if (!item.isPresent()) { return; } TurboIssue issue = item.get(); ui.prefs.clearMarkedReadAt(issue.getRepoId(), issue.getId()); issue.setMarkedReadAt(Optional.empty()); issue.setIsCurrentlyRead(false); parentPanelControl.refresh(); } if (event.getCode() == KeyboardShortcuts.SHOW_DOCS) { ui.getBrowserComponent().showDocs(); } if (KeyboardShortcuts.LIST_TO_BOX.match(event)) { setFocusToFilterBox(); } if (event.getCode() == KeyboardShortcuts.DOUBLE_PRESS && KeyPress.isDoublePress(KeyboardShortcuts.DOUBLE_PRESS, event.getCode())) { setFocusToFilterBox(); } if (event.getCode() == KeyboardShortcuts.SHOW_ISSUES) { if (KeyPress.isValidKeyCombination(KeyboardShortcuts.GOTO_MODIFIER, event.getCode())) { ui.getBrowserComponent().showIssues(); } } if (event.getCode() == KeyboardShortcuts.SHOW_PULL_REQUESTS) { if (KeyPress.isValidKeyCombination(KeyboardShortcuts.GOTO_MODIFIER, event.getCode())) { ui.getBrowserComponent().showPullRequests(); } } if (event.getCode() == KeyboardShortcuts.SHOW_HELP) { if (KeyPress.isValidKeyCombination(KeyboardShortcuts.GOTO_MODIFIER, event.getCode())) { ui.getBrowserComponent().showDocs(); } } if (event.getCode() == KeyboardShortcuts.SHOW_KEYBOARD_SHORTCUTS) { if (KeyPress.isValidKeyCombination(KeyboardShortcuts.GOTO_MODIFIER, event.getCode())) { ui.getBrowserComponent().showKeyboardShortcuts(); } } if (event.getCode() == KeyboardShortcuts.SHOW_CONTRIBUTORS) { if (KeyPress.isValidKeyCombination(KeyboardShortcuts.GOTO_MODIFIER, event.getCode())) { ui.getBrowserComponent().showContributors(); event.consume(); } } if (event.getCode() == KeyboardShortcuts.scrollToTop) { ui.getBrowserComponent().scrollToTop(); } if (event.getCode() == KeyboardShortcuts.scrollToBottom) { if (!KeyboardShortcuts.MINIMIZE_WINDOW.match(event)) { ui.getBrowserComponent().scrollToBottom(); } } if (event.getCode() == KeyboardShortcuts.scrollUp || event.getCode() == KeyboardShortcuts.scrollDown) { ui.getBrowserComponent().scrollPage(event.getCode() == KeyboardShortcuts.scrollDown); } if (event.getCode() == KeyboardShortcuts.GOTO_MODIFIER) { KeyPress.setLastKeyPressedCodeAndTime(event.getCode()); } if (event.getCode() == KeyboardShortcuts.NEW_COMMENT && ui.getBrowserComponent().isCurrentUrlIssue()) { ui.getBrowserComponent().jumpToComment(); } if (event.getCode() == KeyboardShortcuts.SHOW_LABELS) { if (KeyPress.isValidKeyCombination(KeyboardShortcuts.GOTO_MODIFIER, event.getCode())) { ui.getBrowserComponent().newLabel(); } else { ui.triggerEvent(new ShowLabelPickerEvent(getSelectedIssue())); } } if (event.getCode() == KeyboardShortcuts.MANAGE_ASSIGNEES && ui.getBrowserComponent().isCurrentUrlIssue()) { ui.getBrowserComponent().manageAssignees(event.getCode().toString()); } if (event.getCode() == KeyboardShortcuts.SHOW_MILESTONES) { if (KeyPress.isValidKeyCombination(KeyboardShortcuts.GOTO_MODIFIER, event.getCode())) { ui.getBrowserComponent().showMilestones(); } else if (ui.getBrowserComponent().isCurrentUrlIssue()) { ui.getBrowserComponent().manageMilestones(event.getCode().toString()); } } if (KeyboardShortcuts.MAXIMIZE_WINDOW.match(event)) { ui.maximizeWindow(); } if (KeyboardShortcuts.MINIMIZE_WINDOW.match(event)) { ui.minimizeWindow(); } if (KeyboardShortcuts.DEFAULT_SIZE_WINDOW.match(event)) { ui.setDefaultWidth(); } if (KeyboardShortcuts.SWITCH_DEFAULT_REPO.match(event)) { ui.switchDefaultRepo(); } }); }
@Test public void getters() { // ID assertEquals(REPO, modelUpdated.getRepoId()); assertEquals(modelUpdated.getRepoId(), modelUpdated.getRepoId()); // Signature assertEquals(true, modelEmptySig.getUpdateSignature().isEmpty()); assertEquals(modelEmptySig.getUpdateSignature(), UpdateSignature.EMPTY); assertEquals(modelEmptySig.getUpdateSignature(), modelEmptySig2.getUpdateSignature()); // Resources // Issues ArrayList<Integer> issueIds = new ArrayList<>(); for (int i = 1; i <= DummyRepoState.NO_OF_DUMMY_ISSUES; i++) { issueIds.add(i); } Collections.sort(issueIds); // 1, 2..10 int issueCount = 1; for (TurboIssue issue : modelUpdated.getIssues()) { assertEquals(issueCount, modelUpdated.getIssueById(issueCount).get().getId()); assertEquals(issueIds.get(issueCount - 1).intValue(), issue.getId()); issueCount++; } // Labels ArrayList<String> labelNames = new ArrayList<>(); for (int i = 1; i <= DummyRepoState.NO_OF_DUMMY_ISSUES; i++) { labelNames.add("Label " + i); } Collections.sort(labelNames); // Label 1, Label 10..12, Label 2..9 int labelCount = 1; for (TurboLabel label : modelUpdated.getLabels()) { if (label.getFullName().startsWith("Label")) { assertEquals(labelNames.get(labelCount - 1), label.getFullName()); assertEquals( "Label " + labelCount, modelUpdated.getLabelByActualName("Label " + labelCount).get().getFullName()); labelCount++; } } // Milestones ArrayList<Integer> milestoneIds = new ArrayList<>(); for (int i = 1; i <= DummyRepoState.NO_OF_DUMMY_ISSUES; i++) { milestoneIds.add(i); } Collections.sort(milestoneIds); // 1, 2..10 int milestoneCount = 1; for (TurboMilestone milestone : modelUpdated.getMilestones()) { assertEquals(milestoneCount, milestone.getId()); assertEquals( milestoneIds.get(milestoneCount - 1).intValue(), modelUpdated.getMilestoneById(milestoneCount).get().getId()); assertEquals( "Milestone " + milestoneCount, modelUpdated.getMilestoneByTitle("Milestone " + milestoneCount).get().getTitle()); milestoneCount++; } // Users ArrayList<String> userLogins = new ArrayList<>(); for (int i = 1; i <= DummyRepoState.NO_OF_DUMMY_ISSUES; i++) { userLogins.add("User " + i); } Collections.sort(userLogins); // User 1, User 10, User 2..9 int userCount = 1; for (TurboUser user : modelUpdated.getUsers()) { assertEquals(userLogins.get(userCount - 1), user.getLoginName()); assertEquals( "User " + userCount, modelUpdated.getUserByLogin("User " + userCount).get().getLoginName()); userCount++; } }