-
Notifications
You must be signed in to change notification settings - Fork 31
Add gitlab support #378
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
base: main
Are you sure you want to change the base?
Add gitlab support #378
Conversation
Signed-off-by: Michael Engel <[email protected]>
Signed-off-by: Michael Engel <[email protected]>
webbnh
left a comment
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Hi @engelmi, this looks like a good start. You did note that it was a Draft, but I figured that, since you went to the trouble of posting it, I would review it for you. There's a good bit of change here, so I have a good number of comments. 🙂
| def fetch_notes_for_issue(self, iid): | ||
| issue = self.fetch_issue(iid) | ||
| return GitlabClient.map_notes_to_intermediary(issue.notes.list(all=True)) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Even though it's a static method, you can still refer to map_notes_to_intermediary() using self, and doing so avoids having to name the class within its implementation (which is awkward, if someone decides to rename the class or create a subclass of it).
| def fetch_notes_for_issue(self, iid): | |
| issue = self.fetch_issue(iid) | |
| return GitlabClient.map_notes_to_intermediary(issue.notes.list(all=True)) | |
| def fetch_notes_for_issue(self, iid): | |
| issue = self.fetch_issue(iid) | |
| return self.map_notes_to_intermediary(issue.notes.list(all=True)) |
Ditto for fetch_notes_for_mr().
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
(Alternatively, you can use __class__ instead of self, but I find that to be kind of ugly.)
| def fetch_issue(self, iid): | ||
| return self._project.issues.get(iid) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
It would be good if you include type hints in the function signatures. E.g.,
| def fetch_issue(self, iid): | |
| return self._project.issues.get(iid) | |
| def fetch_issue(self, iid: str) -> gitlab.v4.objects.ProjectIssueManager: | |
| return self._project.issues.get(iid) |
You can omit the type for self, but it would be good if all the other parameters as well as any return value were typed. (I had to guess at the types in my example, here....)
| except GithubException as e: | ||
| log.error("Unexpected GitHub error: %s", e) | ||
| except JIRAError as e: | ||
| log.error("Unexpected Jira error: %s", e) | ||
| except Exception as e: | ||
| log.exception("Unexpected error.", exc_info=e) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Should there be an except GitlabException clause here? Alternatively, since the log messages don't seem very different in the three existing cases, perhaps we should remove the special cases for GitHub and Jira and just let the catch-all handle those?
| if suffix in issue_handlers: | ||
| return issue_handlers.get(suffix) | ||
| elif suffix in pr_handlers: | ||
| return pr_handlers.get(suffix) | ||
| log.info("No github handler for %r %r %r", suffix, topic, idx) | ||
| return None |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
We can use the power of get() and avoid a redundant lookup:
| if suffix in issue_handlers: | |
| return issue_handlers.get(suffix) | |
| elif suffix in pr_handlers: | |
| return pr_handlers.get(suffix) | |
| log.info("No github handler for %r %r %r", suffix, topic, idx) | |
| return None | |
| handler = issue_handlers.get(suffix, pr_handlers.get(suffix)) | |
| if not handler: | |
| log.info("No github handler for %r %r %r", suffix, topic, idx) | |
| return handler |
| log.info("No github handler for %r %r %r", suffix, topic, idx) | ||
| return None |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Under the principle of "separation of policy and mechanism", I think the call to log.info() belongs in the caller of this function rather than here.
For instance, how do we know that the caller would not want to declare a WARNING or ERROR (or perhaps log it only for DEBUG'ing). Also, this function requires only the suffix parameter -- the others are supplied only for logging purposes -- so if we leave the logging to the caller then we can simplify the function interface.
(FWIW, my gut instinct is that, if we cannot find a handler, either that's an "error" or something totally uninteresting...so, INFO is almost certainly the wrong level...and we certainly don't have enough context here to pick the correct level, so we shouldn't be logging this here.)
| @mock.patch.dict( | ||
| PATH + "issue_handlers", {"github.issue.comment": lambda msg, c: None} | ||
| HANDLER_PATH + "issue_handlers", {"github.issue.comment": lambda msg, c: None} | ||
| ) | ||
| @mock.patch(PATH + "u_issue") | ||
| @mock.patch(PATH + "d_issue") | ||
| @mock.patch(HANDLER_PATH + "u_issue") | ||
| @mock.patch(HANDLER_PATH + "d_issue") | ||
| def test_handle_msg_no_issue(self, mock_d, mock_u): | ||
| """ | ||
| Tests 'handle_msg' function where there is no issue | ||
| """ | ||
| mock_u.handle_github_message.return_value = None |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
If you're going to mock handle_issue_msg(), do we still need the patch at line 348?
| mock_u.handle_github_message.assert_not_called() | ||
| mock_u.handle_github_message.assert_called_once() |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This seems like a behavior change...and that raises a red flag....
Assuming that the old test was correct for the old implementation, why are we expecting the new implementation to call handle_github_message() in this scenario when the old implementation did not?...did the implementation change in some significant way, or has the test scenario been changed?
As a general rule, we should not be changing test scenarios. (We can add new ones, but, unless our interface contract has changed, we should not be fundamentally altering old ones.)
| # Set up return values | ||
| mock_u.handle_github_message.return_value = "dummy_issue" |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Given this mock, do we need the one at line 371?
| """ | ||
| Tests 'handle_issue_msg' function | ||
| """ |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This is a pretty minimal description...can you enlarge it a bit? (E.g., are we testing a success scenario or a failure scenario? Can you qualify what we're trying to demonstrate?)
(It looks like we're exercising the normal, success path....)
| """ | ||
| Tests 'handle_msg' function | ||
| Tests 'handle_issue_msg' function | ||
| """ |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This scenario seems to test the handle_pr_msg() function, not the issue one. (And, it looks like it tests the normal, success case, which would be worth mentioning.)
|
/ok-to-test |
Added initial gitlab integration (initialize part for gitlab and unit tests are currently missing).