diff --git a/init.rb b/init.rb index 06e496a2c61e28a4c5d53b5048d9c85f6633782c..fd20f54b433e8118c4515d47e27300603793784b 100644 --- a/init.rb +++ b/init.rb @@ -6,6 +6,7 @@ require_relative 'lib/version' require_relative 'lib/monthly_issue' require_relative 'lib/patch_issue' require_relative 'lib/regression_issue' +require_relative 'lib/merge_request' require_relative 'lib/security_patch_issue' require_relative 'lib/release/gitlab_ce_release' require_relative 'lib/release/gitlab_ee_release' diff --git a/lib/base_issue.rb b/lib/base_issue.rb deleted file mode 100644 index 5c53b9f604dca7a38e8e28620dfa197579661f30..0000000000000000000000000000000000000000 --- a/lib/base_issue.rb +++ /dev/null @@ -1,39 +0,0 @@ -require 'erb' - -require_relative 'gitlab_client' - -class BaseIssue - def description - ERB.new(template).result(binding) - end - - def create - GitlabClient.create_issue(self) - end - - def exists? - !remote_issue.nil? - end - - def remote_issue - GitlabClient.find_issue(self) - end - - def url - if exists? - GitlabClient.issue_url(remote_issue) - else - '' - end - end - - def confidential? - false - end - - protected - - def template - File.read(template_path) - end -end diff --git a/lib/gitlab_client.rb b/lib/gitlab_client.rb index e999520bd18c8a5bad48184ef9624bf76892821a..4e05f9395819baf224b46613c0afaa718dbb4c64 100644 --- a/lib/gitlab_client.rb +++ b/lib/gitlab_client.rb @@ -1,4 +1,5 @@ -require 'gitlab' +require_relative 'project/gitlab_ce' +require_relative 'project/gitlab_ee' class GitlabClient class MissingMilestone @@ -7,66 +8,133 @@ class GitlabClient end end - # Hard-code IDs following the 'namespace/repo' pattern - CE_PROJECT_ID = 'gitlab-org/gitlab-ce'.freeze - def self.current_user @current_user ||= Gitlab.user end - def self.ce_issues(options = {}) - client.issues(CE_PROJECT_ID, options) + def self.issues(project = Project::GitlabCe, options = {}) + client.issues(project.path, options) + end + + def self.merge_requests(project = Project::GitlabCe, options = {}) + client.merge_requests(project.path, options) end - def self.ce_milestones(options = {}) - client.milestones(CE_PROJECT_ID, options) + def self.milestones(project = Project::GitlabCe, options = {}) + client.milestones(project.path, options) end - def self.ce_milestone(title) - ce_milestones + def self.milestone(project = Project::GitlabCe, title:) + milestones(project.path) .detect { |m| m.title == title } || MissingMilestone.new end # Create an issue in the CE project based on the provided issue # # issue - An object that responds to the following messages: - # :title - Issue title String - # :description - Issue description String - # :labels - Comma-separated String of label names - # :version - Version object + # :title - Issue title String + # :description - Issue description String + # :labels - Comma-separated String of label names + # :version - Version object + # project - An object that responds to :path # # The issue is always assigned to the authenticated user. # # Returns a Gitlab::ObjectifiedHash object - def self.create_issue(issue) - milestone = ce_milestone(issue.version.milestone_name) - - client.create_issue( - CE_PROJECT_ID, - issue.title, - description: issue.description, - assignee_id: current_user.id, + def self.create_issue(issue, project = Project::GitlabCe) + milestone = milestone(project, title: issue.version.milestone_name) + + client.create_issue(project.path, issue.title, + description: issue.description, + assignee_id: current_user.id, milestone_id: milestone.id, labels: issue.labels, - confidential: issue.confidential? - ) + confidential: issue.confidential?) end - # Find an issue in the CE project based on the provided issue + # Create a merge request in the given project based on the provided merge request + # + # merge_request - An object that responds to the following messages: + # :title - Merge request title String + # :description - Merge request description String + # :labels - Comma-separated String of label names + # :source_branch - The source branch + # :target_branch - The target branch + # project - An object that responds to :path + # + # The merge request is always assigned to the authenticated user. + # + # Returns a Gitlab::ObjectifiedHash object + def self.create_merge_request(merge_request, project = Project::GitlabCe) + client.create_merge_request( + project.path, + merge_request.title, + description: merge_request.description, + assignee_id: current_user.id, + labels: merge_request.labels, + source_branch: merge_request.source_branch, + target_branch: merge_request.target_branch || 'master') + end + + # Find an issue in the given project based on the provided issue # # issue - An object that responds to the following messages: - # :title - Issue title String - # :labels - Comma-separated String of label names + # :title - Issue title String + # :labels - Comma-separated String of label names + # project - An object that responds to :path + # + # Returns a Gitlab::ObjectifiedHash object, or nil + def self.find_issue(issue, project = Project::GitlabCe) + opts = { + labels: issue.labels, + milestone: issue.version.milestone_name + } + + issues(project, opts).detect { |i| i.title == issue.title } + end + + # Find an open merge request in the given project based on the provided merge request + # + # merge_request - An object that responds to the following messages: + # :title - Merge request title String + # :labels - Comma-separated String of label names + # project - An object that responds to :path # # Returns a Gitlab::ObjectifiedHash object, or nil - def self.find_issue(issue) - opts = { labels: issue.labels, milestone: issue.version.milestone_name } + def self.find_merge_request(merge_request, project = Project::GitlabCe) + opts = { + labels: merge_request.labels, + state: 'opened' + } - ce_issues(opts).detect { |i| i.title == issue.title } + merge_requests(project, opts) + .detect { |i| i.title == merge_request.title } end - def self.issue_url(issue) - "https://gitlab.com/gitlab-org/gitlab-ce/issues/#{issue.iid}" + # Returns the URL of an issue in the given project based on the provided issue + # + # issue - An object that responds to the following messages: + # :iid - Issue IID String + # project - An object that responds to :path + # + # Returns an URL + def self.issue_url(issue, project = Project::GitlabCe) + return '' if issue.iid.nil? + + "https://gitlab.com/#{project.path}/issues/#{issue.iid}" + end + + # Returns the URL of a merge request in the given project based on the provided merge request + # + # merge_request - An object that responds to the following messages: + # :iid - Merge request IID String + # project - An object that responds to :path + # + # Returns an URL + def self.merge_request_url(merge_request, project = Project::GitlabCe) + return '' if merge_request.iid.nil? + + "https://gitlab.com/#{project.path}/merge_requests/#{merge_request.iid}" end def self.client diff --git a/lib/issuable.rb b/lib/issuable.rb new file mode 100644 index 0000000000000000000000000000000000000000..4835d84c2248cededab4c585cfd09c219105f98e --- /dev/null +++ b/lib/issuable.rb @@ -0,0 +1,43 @@ +require 'erb' +require 'ostruct' + +class Issuable < OpenStruct + def initialize(*args) + super + yield self if block_given? + end + + def description + ERB.new(template).result(binding) + end + + def project + self[:project] || Project::GitlabCe + end + + def iid + remote_issuable&.iid + end + + def exists? + !remote_issuable.nil? + end + + def create + raise NotImplementedError + end + + def remote_issuable + raise NotImplementedError + end + + def url + raise NotImplementedError + end + + private + + def template + File.read(template_path) + end +end diff --git a/lib/issue.rb b/lib/issue.rb new file mode 100644 index 0000000000000000000000000000000000000000..500cda87fd903d6f76189f5d379bda8fcfae6e1f --- /dev/null +++ b/lib/issue.rb @@ -0,0 +1,22 @@ +require_relative 'issuable' +require_relative 'gitlab_client' + +class Issue < Issuable + def create + GitlabClient.create_issue(self, project) + end + + def remote_issuable + return @remote_issuable if defined?(@remote_issuable) + + @remote_issuable ||= GitlabClient.find_issue(self, project) + end + + def url + GitlabClient.issue_url(self, project) + end + + def confidential? + false + end +end diff --git a/lib/merge_request.rb b/lib/merge_request.rb new file mode 100644 index 0000000000000000000000000000000000000000..0700f57d7a9d900023b91e5fe34e53a958ce2c6e --- /dev/null +++ b/lib/merge_request.rb @@ -0,0 +1,18 @@ +require_relative 'issuable' +require_relative 'gitlab_client' + +class MergeRequest < Issuable + def create + GitlabClient.create_merge_request(self, project) + end + + def remote_issuable + return @remote_issuable if defined?(@remote_issuable) + + @remote_issuable ||= GitlabClient.find_merge_request(self, project) + end + + def url + GitlabClient.merge_request_url(self, project) + end +end diff --git a/lib/monthly_issue.rb b/lib/monthly_issue.rb index ac27ea4abee18758cb7ba5af4b7600ce2a5f22a2..e2bec51dd0f39a839c00632677b879afbc8e96f0 100644 --- a/lib/monthly_issue.rb +++ b/lib/monthly_issue.rb @@ -8,10 +8,10 @@ require 'active_support/core_ext/integer' require 'active_support/core_ext/numeric' require 'weekdays' -require_relative 'base_issue' +require_relative 'issue' require_relative 'release' -class MonthlyIssue < BaseIssue +class MonthlyIssue < Issue attr_reader :release_date, :version def initialize(version, release_date = Release.next_date) diff --git a/lib/patch_issue.rb b/lib/patch_issue.rb index 72564014fe05849eeb9e4dde55e388db19497a7e..bf0c2b8c4ae4ef7d1c9a3a109077be267fcd4bff 100644 --- a/lib/patch_issue.rb +++ b/lib/patch_issue.rb @@ -1,8 +1,8 @@ -require_relative 'base_issue' +require_relative 'issue' require_relative 'regression_issue' require_relative 'omnibus_gitlab_version' -class PatchIssue < BaseIssue +class PatchIssue < Issue attr_reader :version, :omnibus_version def initialize(version) diff --git a/lib/project/gitlab_ce.rb b/lib/project/gitlab_ce.rb new file mode 100644 index 0000000000000000000000000000000000000000..3aa6116a011ee50256da0b6a83ff9c3749d82cd1 --- /dev/null +++ b/lib/project/gitlab_ce.rb @@ -0,0 +1,7 @@ +module Project + class GitlabCe + def self.path + 'gitlab-org/gitlab-ce' + end + end +end diff --git a/lib/project/gitlab_ee.rb b/lib/project/gitlab_ee.rb new file mode 100644 index 0000000000000000000000000000000000000000..63efe4a7ad86ad709dd227b99d0d4b83c9a5b12f --- /dev/null +++ b/lib/project/gitlab_ee.rb @@ -0,0 +1,7 @@ +module Project + class GitlabEe + def self.path + 'gitlab-org/gitlab-ee' + end + end +end diff --git a/lib/regression_issue.rb b/lib/regression_issue.rb index 500bccceb044eee9b2126f60c7f1a782604a5e0b..06adf7fa3ea76c0eb1ffc8c939d1d73163fb8dd9 100644 --- a/lib/regression_issue.rb +++ b/lib/regression_issue.rb @@ -1,6 +1,6 @@ -require_relative 'base_issue' +require_relative 'issue' -class RegressionIssue < BaseIssue +class RegressionIssue < Issue attr_reader :version def initialize(version) diff --git a/spec/fixtures/merge_requests/fix-geo-middleware.yml b/spec/fixtures/merge_requests/fix-geo-middleware.yml new file mode 100644 index 0000000000000000000000000000000000000000..edd318954ee8f27607106f518751263ed71bcb15 --- /dev/null +++ b/spec/fixtures/merge_requests/fix-geo-middleware.yml @@ -0,0 +1,120 @@ +--- +http_interactions: +- request: + method: get + uri: "[GITLAB_API_ENDPOINT]/projects/gitlab-org%2Fgitlab-ee/merge_requests?labels=geo&state=opened" + body: + encoding: US-ASCII + string: '' + headers: + Accept: + - application/json + Private-Token: + - "[GITLAB_API_PRIVATE_TOKEN]" + response: + status: + code: 200 + message: OK + headers: + Server: + - nginx + Date: + - Mon, 26 Jun 2017 11:23:32 GMT + Content-Type: + - application/json + Content-Length: + - '12589' + Cache-Control: + - max-age=0, private, must-revalidate + Etag: + - W/"3b7e51dad8fba180356a3a64030ce6b5" + Link: + - <[GITLAB_API_ENDPOINT]/projects/gitlab-org%2Fgitlab-ee/merge_requests?id=gitlab-org%2Fgitlab-ee&labels=geo&order_by=created_at&page=1&per_page=20&sort=desc&state=opened>; + rel="first", <[GITLAB_API_ENDPOINT]/projects/gitlab-org%2Fgitlab-ee/merge_requests?id=gitlab-org%2Fgitlab-ee&labels=geo&order_by=created_at&page=1&per_page=20&sort=desc&state=opened>; + rel="last" + Vary: + - Origin + X-Frame-Options: + - SAMEORIGIN + X-Next-Page: + - '' + X-Page: + - '1' + X-Per-Page: + - '20' + X-Prev-Page: + - '' + X-Request-Id: + - 8a927938-ac62-4129-8784-dc7168b29432 + X-Runtime: + - '4.386995' + X-Total: + - '7' + X-Total-Pages: + - '1' + Strict-Transport-Security: + - max-age=31536000 + body: + encoding: UTF-8 + string: '[{"id":3969539,"iid":2241,"project_id":278964,"title":"Geo: fix removal + of repositories from disk on secondary nodes (Backport to 9.2)","description":"## + What does this MR do?\n\nBackports gitlab-org/gitlab-ee!2210 to %9.2\n\n## + Are there points in the code the reviewer needs to double check?\n\n## Why + was this MR needed?\n\nThis is a critical fix for Geo, while we want people + to always use our latest versions, we understand that there are reasons why + people sometime keeps on the previous stable version for a while.\n\n## Does + this MR meet the acceptance criteria?\n\n- [ ] [Changelog entry](https://docs.gitlab.com/ce/development/changelog.html) + added, if necessary\n- [ ] [Documentation created/updated](https://gitlab.com/gitlab-org/gitlab-ee/blob/master/doc/development/doc_styleguide.md)\n- + [ ] API support added\n- Tests\n - [ ] Added for this feature/bug\n - [ + ] All builds are passing\n- [ ] Conform by the [merge request performance + guides](http://docs.gitlab.com/ee/development/merge_request_performance_guidelines.html)\n- + [ ] Conform by the [style guides](https://gitlab.com/gitlab-org/gitlab-ee/blob/master/CONTRIBUTING.md#style-guides)\n- + [ ] Branch has no merge conflicts with `master` (if it does - rebase it please)\n- + [ ] [Squashed related commits together](https://git-scm.com/book/en/Git-Tools-Rewriting-History#Squashing-Commits)\n\n## + What are the relevant issue numbers?\n\n* gitlab-org/gitlab-ee!2210\n* gitlab-org/gitlab-ee#2701","state":"opened","created_at":"2017-06-24T01:59:51.096Z","updated_at":"2017-06-24T20:35:22.782Z","target_branch":"9-2-stable-ee","source_branch":"backport-2210-to-9-2","upvotes":0,"downvotes":0,"author":{"name":"Gabriel + Mazetto","username":"brodock","id":2293,"state":"active","avatar_url":"https://secure.gravatar.com/avatar/0194f7ce33ec23b1788ffe382d4a4f08?s=80\u0026d=identicon","web_url":"https://gitlab.com/brodock"},"assignee":{"name":"Douwe + Maan","username":"DouweM","id":87854,"state":"active","avatar_url":"https://gitlab.com/uploads/system/user/avatar/87854/avatar.png","web_url":"https://gitlab.com/DouweM"},"source_project_id":278964,"target_project_id":278964,"labels":["customer","geo","regression"],"work_in_progress":false,"milestone":{"id":269773,"iid":23,"project_id":278964,"title":"9.2","description":"","state":"active","created_at":"2017-02-20T13:55:42.958Z","updated_at":"2017-04-07T14:54:22.817Z","due_date":"2017-05-21","start_date":"2017-04-08"},"merge_when_pipeline_succeeds":false,"merge_status":"can_be_merged","sha":"2c15628547416cecd86cf4497ed61dd85d36e757","merge_commit_sha":null,"user_notes_count":1,"approvals_before_merge":null,"should_remove_source_branch":null,"force_remove_source_branch":true,"squash":false,"web_url":"https://gitlab.com/gitlab-org/gitlab-ee/merge_requests/2241"},{"id":3967652,"iid":2238,"project_id":278964,"title":"WIP: + Add Geo repository renamed event log","description":"Closes #2641","state":"opened","created_at":"2017-06-23T20:47:12.832Z","updated_at":"2017-06-23T21:34:20.390Z","target_branch":"master","source_branch":"da-geo-log-namespace-project-rename-events","upvotes":0,"downvotes":0,"author":{"name":"Douglas + Barbosa Alexandre","username":"dbalexandre","id":283999,"state":"active","avatar_url":"https://gitlab.com/uploads/system/user/avatar/283999/douglas.png","web_url":"https://gitlab.com/dbalexandre"},"assignee":null,"source_project_id":278964,"target_project_id":278964,"labels":["geo"],"work_in_progress":true,"milestone":null,"merge_when_pipeline_succeeds":false,"merge_status":"unchecked","sha":"1d6eb05b07bc41873e3e34ca31a5db7342083e36","merge_commit_sha":null,"user_notes_count":0,"approvals_before_merge":null,"should_remove_source_branch":null,"force_remove_source_branch":true,"squash":false,"web_url":"https://gitlab.com/gitlab-org/gitlab-ee/merge_requests/2238"},{"id":3844913,"iid":2130,"project_id":278964,"title":"WIP: + Perform Geo::RepositorySyncService for dirty projects","description":"Depends + on https://gitlab.com/gitlab-org/gitlab-ee/merge_requests/1988","state":"opened","created_at":"2017-06-14T19:59:01.026Z","updated_at":"2017-06-26T00:07:50.621Z","target_branch":"2467-geo-implement-consumer-aka-geologcursor-for-event-log","source_branch":"2467-repository-sync-dirty-projects","upvotes":0,"downvotes":0,"author":{"name":"Douglas + Barbosa Alexandre","username":"dbalexandre","id":283999,"state":"active","avatar_url":"https://gitlab.com/uploads/system/user/avatar/283999/douglas.png","web_url":"https://gitlab.com/dbalexandre"},"assignee":{"name":"Robert + Speicher","username":"rspeicher","id":15139,"state":"active","avatar_url":"https://secure.gravatar.com/avatar/19c928bc60c7b0d10e708c429e389523?s=80\u0026d=identicon","web_url":"https://gitlab.com/rspeicher"},"source_project_id":278964,"target_project_id":278964,"labels":["geo"],"work_in_progress":true,"milestone":{"id":305235,"iid":26,"project_id":278964,"title":"9.4","description":"","state":"active","created_at":"2017-04-26T13:01:26.869Z","updated_at":"2017-06-07T16:59:43.882Z","due_date":"2017-07-22","start_date":"2017-06-08"},"merge_when_pipeline_succeeds":false,"merge_status":"cannot_be_merged","sha":"97cb7404e91517c7af2a8e2ee13f21ad2c07fa96","merge_commit_sha":null,"user_notes_count":15,"approvals_before_merge":null,"should_remove_source_branch":null,"force_remove_source_branch":null,"squash":false,"web_url":"https://gitlab.com/gitlab-org/gitlab-ee/merge_requests/2130"},{"id":3834194,"iid":2126,"project_id":278964,"title":"Add + Geo delete event log","description":"","state":"opened","created_at":"2017-06-14T06:32:14.025Z","updated_at":"2017-06-24T21:42:39.360Z","target_branch":"master","source_branch":"sh-geo-add-project-deletion-events","upvotes":0,"downvotes":0,"author":{"name":"Stan + Hu","username":"stanhu","id":64248,"state":"active","avatar_url":"https://gitlab.com/uploads/system/user/avatar/64248/stanhu.jpg","web_url":"https://gitlab.com/stanhu"},"assignee":{"name":"Stan + Hu","username":"stanhu","id":64248,"state":"active","avatar_url":"https://gitlab.com/uploads/system/user/avatar/64248/stanhu.jpg","web_url":"https://gitlab.com/stanhu"},"source_project_id":278964,"target_project_id":278964,"labels":["geo"],"work_in_progress":false,"milestone":null,"merge_when_pipeline_succeeds":false,"merge_status":"unchecked","sha":"a07c013ecf8da1efd68847274c7f3f02ea3a2655","merge_commit_sha":null,"user_notes_count":12,"approvals_before_merge":null,"should_remove_source_branch":null,"force_remove_source_branch":true,"squash":false,"web_url":"https://gitlab.com/gitlab-org/gitlab-ee/merge_requests/2126"},{"id":3615638,"iid":1988,"project_id":278964,"title":"Geo: + Implement GeoLogCursor for event log","description":"The GeoLogCursor is a + daemon that reads like a cursor through existing `geo_log_events` and process + each one of them.\n\nIn this first iteration we want to handle repository + and wiki update events. Whenever the cursor process an event, it checks the + metadata and mark the affected repository or wiki as requiring a resync.\n\n## + Are there points in the code the reviewer needs to double check?\n\nThis MR + is rebased on top of gitlab-org/gitlab-ee!1976, so most of the code in the + MR can be ignored as it''s duplicated from other. You can check the last commit + which includes the changes that are specific to this MR.\n\n## Why was this + MR needed?\n\n## Screenshots (if relevant)\n\n## Does this MR meet the acceptance + criteria?\n\n- [x] [Changelog entry](https://docs.gitlab.com/ce/development/changelog.html) + added, if necessary\n- [ ] [Documentation created/updated](https://gitlab.com/gitlab-org/gitlab-ee/blob/master/doc/development/doc_styleguide.md)\n- + [ ] API support added\n- Tests\n - [x] Added for this feature/bug\n - [x] + All builds are passing\n- [ ] Conform by the [merge request performance guides](http://docs.gitlab.com/ee/development/merge_request_performance_guidelines.html)\n- + [ ] Conform by the [style guides](https://gitlab.com/gitlab-org/gitlab-ee/blob/master/CONTRIBUTING.md#style-guides)\n- + [ ] Branch has no merge conflicts with `master` (if it does - rebase it please)\n- + [ ] [Squashed related commits together](https://git-scm.com/book/en/Git-Tools-Rewriting-History#Squashing-Commits)\n\n## + What are the relevant issue numbers?\n\n\nCloses #2467","state":"opened","created_at":"2017-05-26T01:10:46.814Z","updated_at":"2017-06-26T05:38:46.861Z","target_branch":"master","source_branch":"2467-geo-implement-consumer-aka-geologcursor-for-event-log","upvotes":0,"downvotes":0,"author":{"name":"Gabriel + Mazetto","username":"brodock","id":2293,"state":"active","avatar_url":"https://secure.gravatar.com/avatar/0194f7ce33ec23b1788ffe382d4a4f08?s=80\u0026d=identicon","web_url":"https://gitlab.com/brodock"},"assignee":{"name":"Douwe + Maan","username":"DouweM","id":87854,"state":"active","avatar_url":"https://gitlab.com/uploads/system/user/avatar/87854/avatar.png","web_url":"https://gitlab.com/DouweM"},"source_project_id":278964,"target_project_id":278964,"labels":["geo"],"work_in_progress":false,"milestone":{"id":305235,"iid":26,"project_id":278964,"title":"9.4","description":"","state":"active","created_at":"2017-04-26T13:01:26.869Z","updated_at":"2017-06-07T16:59:43.882Z","due_date":"2017-07-22","start_date":"2017-06-08"},"merge_when_pipeline_succeeds":false,"merge_status":"can_be_merged","sha":"160103f5d7658def6d69398efac10decd0666f9a","merge_commit_sha":null,"user_notes_count":58,"approvals_before_merge":null,"should_remove_source_branch":null,"force_remove_source_branch":null,"squash":false,"web_url":"https://gitlab.com/gitlab-org/gitlab-ee/merge_requests/1988"},{"id":3172886,"iid":1679,"project_id":278964,"title":"Delete + primary Geo system hook","description":"When a primary Geo node is created, + tag push and push events are added to the system hook. With each push to primary, + the primary also receives an event, which causes unnecessary work.\r\n\r\nCloses + #1752","state":"reopened","created_at":"2017-04-19T20:43:06.288Z","updated_at":"2017-06-25T06:16:13.915Z","target_branch":"master","source_branch":"sh-disable-geo-primary-system-hook","upvotes":0,"downvotes":0,"author":{"name":"Stan + Hu","username":"stanhu","id":64248,"state":"active","avatar_url":"https://gitlab.com/uploads/system/user/avatar/64248/stanhu.jpg","web_url":"https://gitlab.com/stanhu"},"assignee":{"name":"Robert + Speicher","username":"rspeicher","id":15139,"state":"active","avatar_url":"https://secure.gravatar.com/avatar/19c928bc60c7b0d10e708c429e389523?s=80\u0026d=identicon","web_url":"https://gitlab.com/rspeicher"},"source_project_id":278964,"target_project_id":278964,"labels":["geo"],"work_in_progress":false,"milestone":null,"merge_when_pipeline_succeeds":false,"merge_status":"can_be_merged","sha":"7e5cbcd438e7a2d4162a2431729769da15b329ab","merge_commit_sha":null,"user_notes_count":19,"approvals_before_merge":null,"should_remove_source_branch":null,"force_remove_source_branch":true,"squash":false,"web_url":"https://gitlab.com/gitlab-org/gitlab-ee/merge_requests/1679"},{"id":3068870,"iid":1610,"project_id":278964,"title":"Add + a health check for the state of Geo replication lag in secondary","description":"If + streaming replication is not working, this MR will report that the secondary + node is not working. Also move the check for the tracking DB last so that + we can report basic Geo failures first.\r\n\r\nPartial fix to #1929","state":"opened","created_at":"2017-04-08T05:53:47.935Z","updated_at":"2017-06-01T00:53:57.492Z","target_branch":"master","source_branch":"sh-check-replication-secondary","upvotes":0,"downvotes":0,"author":{"name":"Stan + Hu","username":"stanhu","id":64248,"state":"active","avatar_url":"https://gitlab.com/uploads/system/user/avatar/64248/stanhu.jpg","web_url":"https://gitlab.com/stanhu"},"assignee":{"name":"Stan + Hu","username":"stanhu","id":64248,"state":"active","avatar_url":"https://gitlab.com/uploads/system/user/avatar/64248/stanhu.jpg","web_url":"https://gitlab.com/stanhu"},"source_project_id":278964,"target_project_id":278964,"labels":["geo"],"work_in_progress":false,"milestone":{"id":261163,"iid":22,"project_id":278964,"title":"9.1","description":"","state":"active","created_at":"2017-02-03T20:12:11.321Z","updated_at":"2017-04-07T14:54:23.322Z","due_date":"2017-04-21","start_date":"2017-03-08"},"merge_when_pipeline_succeeds":false,"merge_status":"unchecked","sha":"53440933e717028f803eded14da8441ed4206d74","merge_commit_sha":null,"user_notes_count":4,"approvals_before_merge":null,"should_remove_source_branch":null,"force_remove_source_branch":true,"squash":false,"web_url":"https://gitlab.com/gitlab-org/gitlab-ee/merge_requests/1610"}]' + http_version: + recorded_at: Mon, 26 Jun 2017 11:23:32 GMT +recorded_with: VCR 2.9.3 diff --git a/spec/fixtures/merge_requests/foo.yml b/spec/fixtures/merge_requests/foo.yml new file mode 100644 index 0000000000000000000000000000000000000000..5fe24a12d6e639de969d38b71423d32a15862b58 --- /dev/null +++ b/spec/fixtures/merge_requests/foo.yml @@ -0,0 +1,62 @@ +--- +http_interactions: +- request: + method: get + uri: "[GITLAB_API_ENDPOINT]/projects/gitlab-org%2Fgitlab-ee/merge_requests?labels=Release&state=opened" + body: + encoding: US-ASCII + string: '' + headers: + Accept: + - application/json + Private-Token: + - "[GITLAB_API_PRIVATE_TOKEN]" + response: + status: + code: 200 + message: OK + headers: + Server: + - nginx + Date: + - Mon, 26 Jun 2017 11:23:27 GMT + Content-Type: + - application/json + Content-Length: + - '2' + Cache-Control: + - max-age=0, private, must-revalidate + Etag: + - W/"d751713988987e9331980363e24189ce" + Link: + - <[GITLAB_API_ENDPOINT]/projects/gitlab-org%2Fgitlab-ee/merge_requests?id=gitlab-org%2Fgitlab-ee&labels=Release&order_by=created_at&page=1&per_page=20&sort=desc&state=opened>; + rel="first", <[GITLAB_API_ENDPOINT]/projects/gitlab-org%2Fgitlab-ee/merge_requests?id=gitlab-org%2Fgitlab-ee&labels=Release&order_by=created_at&page=0&per_page=20&sort=desc&state=opened>; + rel="last" + Vary: + - Origin + X-Frame-Options: + - SAMEORIGIN + X-Next-Page: + - '' + X-Page: + - '1' + X-Per-Page: + - '20' + X-Prev-Page: + - '' + X-Request-Id: + - 96c285ec-6e7e-458a-af76-810b0ac56cc9 + X-Runtime: + - '1.573066' + X-Total: + - '0' + X-Total-Pages: + - '0' + Strict-Transport-Security: + - max-age=31536000 + body: + encoding: UTF-8 + string: "[]" + http_version: + recorded_at: Mon, 26 Jun 2017 11:23:27 GMT +recorded_with: VCR 2.9.3 diff --git a/spec/fixtures/merge_requests/related-issues-ux-improvments.yml b/spec/fixtures/merge_requests/related-issues-ux-improvments.yml new file mode 100644 index 0000000000000000000000000000000000000000..7bac53224c04398d92f65aff6ffdd43287bbd6f3 --- /dev/null +++ b/spec/fixtures/merge_requests/related-issues-ux-improvments.yml @@ -0,0 +1,89 @@ +--- +http_interactions: +- request: + method: get + uri: "[GITLAB_API_ENDPOINT]/projects/gitlab-org%2Fgitlab-ee/merge_requests?labels=Discussion&state=opened" + body: + encoding: US-ASCII + string: '' + headers: + Accept: + - application/json + Private-Token: + - "[GITLAB_API_PRIVATE_TOKEN]" + response: + status: + code: 200 + message: OK + headers: + Server: + - nginx + Date: + - Mon, 26 Jun 2017 11:23:36 GMT + Content-Type: + - application/json + Content-Length: + - '4318' + Cache-Control: + - max-age=0, private, must-revalidate + Etag: + - W/"86d6a91e0269bb7d3503573f2ab2d93b" + Link: + - <[GITLAB_API_ENDPOINT]/projects/gitlab-org%2Fgitlab-ee/merge_requests?id=gitlab-org%2Fgitlab-ee&labels=Discussion&order_by=created_at&page=1&per_page=20&sort=desc&state=opened>; + rel="first", <[GITLAB_API_ENDPOINT]/projects/gitlab-org%2Fgitlab-ee/merge_requests?id=gitlab-org%2Fgitlab-ee&labels=Discussion&order_by=created_at&page=1&per_page=20&sort=desc&state=opened>; + rel="last" + Vary: + - Origin + X-Frame-Options: + - SAMEORIGIN + X-Next-Page: + - '' + X-Page: + - '1' + X-Per-Page: + - '20' + X-Prev-Page: + - '' + X-Request-Id: + - ed712f39-ec2d-43ac-b6a1-e83303fa56e4 + X-Runtime: + - '3.078313' + X-Total: + - '2' + X-Total-Pages: + - '1' + Strict-Transport-Security: + - max-age=31536000 + body: + encoding: UTF-8 + string: '[{"id":3976656,"iid":2246,"project_id":278964,"title":"Related Issues + UX improvements - loading","description":"## What does this MR do?\n\n - Move + the loading icon into the panel body\n - Add transition when the panel body + collapses away after fetching and the loading icon goes away\n - Add loading + icon to the submit button\n - Server-side render basic panel heading for Related + Issues as a placeholder until the JS kicks-in\n\n![](http://imgur.com/tlFbcFr.gif)\n\n![](http://imgur.com/I7UE3zU.gif)\n\n![](http://imgur.com/V0zjc41.gif)\n\n\n## + Are there points in the code the reviewer needs to double check?\n\n - Do + we want the loading spinner to be centered?\n - The `loadingIcon` Vue component + has `text-center` in it\n\n\n## Why was this MR needed?\n\nFollow-up feedback + to address, https://gitlab.com/gitlab-org/gitlab-ce/issues/33533\n\n\n## Does + this MR meet the acceptance criteria?\n\n- [ ] [Documentation created/updated](https://gitlab.com/gitlab-org/gitlab-ee/blob/master/doc/development/doc_styleguide.md)\n- + Tests\n - [x] Added for this feature/bug\n - [ ] All builds are passing\n- + [x] Conform by the [merge request performance guides](http://docs.gitlab.com/ee/development/merge_request_performance_guidelines.html)\n- + [x] Conform by the [style guides](https://gitlab.com/gitlab-org/gitlab-ee/blob/master/CONTRIBUTING.md#style-guides)\n- + [x] Branch has no merge conflicts with `master` (if it does - rebase it please)\n- + [x] [Squashed related commits together](https://git-scm.com/book/en/Git-Tools-Rewriting-History#Squashing-Commits)\n\n## + What are the relevant issue numbers?\n\n\nCloses #33533","state":"opened","created_at":"2017-06-26T06:14:43.005Z","updated_at":"2017-06-26T07:24:16.423Z","target_branch":"master","source_branch":"33533-related-issues-ux-improvements","upvotes":0,"downvotes":0,"author":{"name":"Eric + Eastwood","username":"MadLittleMods","id":892863,"state":"active","avatar_url":"https://secure.gravatar.com/avatar/4d634a2b818e2265fa2924b5f4c2da71?s=80\u0026d=identicon","web_url":"https://gitlab.com/MadLittleMods"},"assignee":null,"source_project_id":278964,"target_project_id":278964,"labels":["Discussion","UI + polish","frontend","issues"],"work_in_progress":false,"milestone":{"id":305235,"iid":26,"project_id":278964,"title":"9.4","description":"","state":"active","created_at":"2017-04-26T13:01:26.869Z","updated_at":"2017-06-07T16:59:43.882Z","due_date":"2017-07-22","start_date":"2017-06-08"},"merge_when_pipeline_succeeds":false,"merge_status":"unchecked","sha":"29da5584e2f7a2cef38ce31adc6b65b68c8fe586","merge_commit_sha":null,"user_notes_count":0,"approvals_before_merge":null,"should_remove_source_branch":null,"force_remove_source_branch":true,"squash":false,"web_url":"https://gitlab.com/gitlab-org/gitlab-ee/merge_requests/2246"},{"id":696921,"iid":620,"project_id":278964,"title":"Filter + merge requests by approver","description":"## What does this MR do?\r\n\r\nAdd + a filter to the merge request lists to enable the user to filter by approver.\r\n\r\n## + Why was this MR needed?\r\n\r\nWhen there are many merge requests, it is very + inconvenient to browse through all the merge requests to view the approvers. + A common use case is to view all the merge requests the current user needs + to review/approve. \r\n\r\n## What are the relevant issue numbers?\r\n* Closes + #52\r\n* Partially addresses https://gitlab.com/gitlab-org/gitlab-ee/issues/769","state":"opened","created_at":"2016-08-04T10:44:05.281Z","updated_at":"2017-06-12T09:44:14.392Z","target_branch":"master","source_branch":"52-filter-by-approver","upvotes":15,"downvotes":0,"author":{"name":"Jan + Heijmans","username":"heijmans","id":647088,"state":"active","avatar_url":"https://gitlab.com/uploads/system/user/avatar/647088/avatar.png","web_url":"https://gitlab.com/heijmans"},"assignee":null,"source_project_id":1475723,"target_project_id":278964,"labels":["Discussion","approvals","coach + will finish"],"work_in_progress":false,"milestone":null,"merge_when_pipeline_succeeds":false,"merge_status":"unchecked","sha":"aa9f8e6df142411310322c9030e5637b3d9f40cb","merge_commit_sha":null,"user_notes_count":35,"approvals_before_merge":null,"should_remove_source_branch":null,"force_remove_source_branch":null,"squash":false,"web_url":"https://gitlab.com/gitlab-org/gitlab-ee/merge_requests/620"}]' + http_version: + recorded_at: Mon, 26 Jun 2017 11:23:36 GMT +recorded_with: VCR 2.9.3 diff --git a/spec/lib/base_issue_spec.rb b/spec/lib/base_issue_spec.rb deleted file mode 100644 index c592697c842e9639c9b59726dbeffa2f79221f18..0000000000000000000000000000000000000000 --- a/spec/lib/base_issue_spec.rb +++ /dev/null @@ -1,81 +0,0 @@ -require 'spec_helper' - -require 'base_issue' -require 'gitlab_client' - -class TestIssue < BaseIssue - protected - - def template - "<%= RUBY_VERSION %>" - end -end - -describe BaseIssue do - describe '#description' do - it "renders ERB using the defined template" do - issue = TestIssue.new - - expect(issue.description).to eq RUBY_VERSION - end - end - - describe '#create' do - it 'calls GitlabClient.create_issue' do - issue = TestIssue.new - - expect(GitlabClient).to receive(:create_issue).with(issue) - - issue.create - end - end - - describe '#exists?' do - it 'is true when issue exists' do - issue = TestIssue.new - - allow(issue).to receive(:remote_issue).and_return(double) - - expect(issue.exists?).to be_truthy - end - - it 'is false when issue is missing' do - issue = TestIssue.new - - allow(issue).to receive(:remote_issue).and_return(nil) - - expect(issue.exists?).to be_falsey - end - end - - describe '#remote_issue' do - it 'delegates to GitlabClient' do - issue = TestIssue.new - - expect(GitlabClient).to receive(:find_issue).with(issue) - - issue.remote_issue - end - end - - describe '#url' do - it 'returns a blank string when remote issue does not exist' do - issue = TestIssue.new - - allow(issue).to receive(:remote_issue).and_return(nil) - - expect(GitlabClient).not_to receive(:issue_url) - expect(issue.url).to eq '' - end - - it 'returns the remote_issue url' do - remote = double - issue = TestIssue.new - - allow(issue).to receive(:remote_issue).and_return(remote) - - expect(GitlabClient).to receive(:issue_url).with(remote).and_return('https://example.com/') - expect(issue.url).to eq 'https://example.com/' - end - end -end diff --git a/spec/lib/gitlab_client_spec.rb b/spec/lib/gitlab_client_spec.rb index c7ecd03875157519b792336ea9dce78bca5e37b2..398971052b47ef0082c2b90ea1c513ee7e982646 100644 --- a/spec/lib/gitlab_client_spec.rb +++ b/spec/lib/gitlab_client_spec.rb @@ -3,6 +3,35 @@ require 'spec_helper' require 'gitlab_client' describe GitlabClient do + describe '.create_merge_request' do + context 'when issue is open' do + it 'finds issues by title', vcr: { cassette_name: 'issues/release-8-7' } do + version = double(milestone_name: '8.7') + issue = double(title: 'Release 8.7', labels: 'Release', version: version) + + expect(described_class.find_issue(issue)).not_to be_nil + end + end + + context 'when issue is closed' do + it 'finds issues by title', vcr: { cassette_name: 'issues/regressions-8-5' } do + version = double(milestone_name: '8.5') + issue = double(title: '8.5 Regressions', labels: 'Release', state_filter: nil, version: version) + + expect(described_class.find_issue(issue)).not_to be_nil + end + end + + context 'when issue cannot be found' do + it 'does not find non-matching issues', vcr: { cassette_name: 'issues/release-7-14' } do + version = double(milestone_name: '7.14') + issue = double(title: 'Release 7.14', labels: 'Release', version: version) + + expect(described_class.find_issue(issue)).to be_nil + end + end + end + describe '.find_issue' do context 'when issue is open' do it 'finds issues by title', vcr: { cassette_name: 'issues/release-8-7' } do @@ -32,12 +61,67 @@ describe GitlabClient do end end + describe '.find_merge_request' do + context 'when merge request is open' do + it 'finds merge requests by title', vcr: { cassette_name: 'merge_requests/related-issues-ux-improvments' } do + merge_request = double(title: 'Related Issues UX improvements - loading', labels: 'Discussion') + + expect(described_class.find_merge_request(merge_request, Project::GitlabEe)).not_to be_nil + end + end + + context 'when merge request is merged' do + it 'does not find merged merge requests', vcr: { cassette_name: 'merge_requests/fix-geo-middleware' } do + merge_request = double(title: 'Fix Geo middleware to work properly with multiple requests', labels: 'geo') + + expect(described_class.find_merge_request(merge_request, Project::GitlabEe)).to be_nil + end + end + + context 'when merge request cannot be found' do + it 'does not find non-matching merge requests', vcr: { cassette_name: 'merge_requests/foo' } do + merge_request = double(title: 'Foo', labels: 'Release') + + expect(described_class.find_merge_request(merge_request, Project::GitlabEe)).to be_nil + end + end + end + describe '.issue_url' do - it 'returns the full URL to the issue' do - issue = double(iid: 1234) + context 'when iid is nil' do + it 'returns an empty string' do + issue = double('Issue', iid: nil) - expect(described_class.issue_url(issue)) - .to eq "https://gitlab.com/gitlab-org/gitlab-ce/issues/1234" + expect(described_class.issue_url(issue)).to eq '' + end + end + + context 'when iid is not nil' do + it 'returns the full URL to the issue' do + issue = double('Issue', iid: 1234) + + expect(described_class.issue_url(issue)) + .to eq "https://gitlab.com/gitlab-org/gitlab-ce/issues/1234" + end + end + end + + describe '.merge_request_url' do + context 'when iid is nil' do + it 'returns an empty string' do + merge_request = double('MergeRequest', iid: nil) + + expect(described_class.merge_request_url(merge_request)).to eq '' + end + end + + context 'when iid is not nil' do + it 'returns the full URL to the merge request' do + merge_request = double('MergeRequest', iid: 1234) + + expect(described_class.merge_request_url(merge_request)) + .to eq "https://gitlab.com/gitlab-org/gitlab-ce/merge_requests/1234" + end end end end diff --git a/spec/lib/issuable_spec.rb b/spec/lib/issuable_spec.rb new file mode 100644 index 0000000000000000000000000000000000000000..e0ee84797d1056f29dfdc2a7f7b1b5fad5224065 --- /dev/null +++ b/spec/lib/issuable_spec.rb @@ -0,0 +1,89 @@ +require 'spec_helper' + +require 'issuable' +require 'gitlab_client' + +class TestIssuable < Issuable + protected + + def template + "<%= RUBY_VERSION %>" + end +end + +describe Issuable do + subject { TestIssuable.new } + + describe '#initialize' do + it 'accepts arbitrary attributes as arguments' do + issuable = TestIssuable.new(foo: 'bar') + + expect(issuable.foo).to eq('bar') + end + + it 'accepts a block' do + issuable = TestIssuable.new do |new_issuable| + new_issuable.foo = 'bar' + end + + expect(issuable.foo).to eq('bar') + end + end + + describe '#description' do + it { expect(subject.description).to eq RUBY_VERSION } + end + + describe '#project' do + it 'returns Project::GitlabCe by default' do + expect(subject.project).to eq(Project::GitlabCe) + end + + context 'when a project is set' do + subject { described_class.new(project: Project::GitlabEe) } + + it 'returns the given project' do + expect(subject.project).to eq(Project::GitlabEe) + end + end + end + + describe '#iid' do + it 'delegates to remote_issuable' do + remote_issuable = double(iid: 1234) + allow(subject).to receive(:remote_issuable).and_return(remote_issuable) + + expect(subject.iid).to eq(1234) + end + end + + describe '#exists?' do + context 'when remote subject does not exist' do + before do + allow(subject).to receive(:remote_issuable).and_return(nil) + end + + it { is_expected.not_to be_exists } + end + + context 'when remote subject exists' do + before do + allow(subject).to receive(:remote_issuable).and_return(double) + end + + it { is_expected.to be_exists } + end + end + + describe '#create' do + it { expect { subject.create }.to raise_error(NotImplementedError) } + end + + describe '#remote_issuable' do + it { expect { subject.remote_issuable }.to raise_error(NotImplementedError) } + end + + describe '#url' do + it { expect { subject.url }.to raise_error(NotImplementedError) } + end +end diff --git a/spec/lib/issue_spec.rb b/spec/lib/issue_spec.rb new file mode 100644 index 0000000000000000000000000000000000000000..a07a03f6a015872c4cee9379f348bbdb2edd3a3b --- /dev/null +++ b/spec/lib/issue_spec.rb @@ -0,0 +1,13 @@ +require 'spec_helper' + +require 'issue' + +describe Issue do + it_behaves_like 'issuable #create', :create_issue + it_behaves_like 'issuable #remote_issuable', :find_issue + it_behaves_like 'issuable #url', :issue_url + + describe '#confidential?' do + it { expect(subject).not_to be_confidential } + end +end diff --git a/spec/lib/merge_request_spec.rb b/spec/lib/merge_request_spec.rb new file mode 100644 index 0000000000000000000000000000000000000000..27fcbe9667ffc537da95cd374d7710f383ebd5ef --- /dev/null +++ b/spec/lib/merge_request_spec.rb @@ -0,0 +1,9 @@ +require 'spec_helper' + +require 'merge_request' + +describe MergeRequest do + it_behaves_like 'issuable #create', :create_merge_request + it_behaves_like 'issuable #remote_issuable', :find_merge_request + it_behaves_like 'issuable #url', :merge_request_url +end diff --git a/spec/support/shared_examples/issuable_shared_examples.rb b/spec/support/shared_examples/issuable_shared_examples.rb new file mode 100644 index 0000000000000000000000000000000000000000..a3b9accf3ddea2f2dfb627b839a1b019e53e92e0 --- /dev/null +++ b/spec/support/shared_examples/issuable_shared_examples.rb @@ -0,0 +1,40 @@ +RSpec.shared_examples 'issuable #create' do |create_issuable_method| + it 'calls GitlabClient.create_issue' do + expect(GitlabClient).to receive(create_issuable_method).with(subject, Project::GitlabCe) + + subject.create + end +end + +RSpec.shared_examples 'issuable #remote_issuable' do |find_issuable_method| + it 'delegates to GitlabClient' do + expect(GitlabClient).to receive(find_issuable_method).with(subject, Project::GitlabCe) + + subject.remote_issuable + end + + context 'when remote issuable does not exist' do + it 'memoizes the remote issuable' do + expect(GitlabClient).to receive(find_issuable_method).once + .with(subject, Project::GitlabCe).and_return(nil) + + 2.times { subject.remote_issuable } + end + end + + context 'when remote issuable exists' do + it 'memoizes the remote issuable' do + expect(GitlabClient).to receive(find_issuable_method).once + .with(subject, Project::GitlabCe).and_return(double) + + 2.times { subject.remote_issuable } + end + end +end + +RSpec.shared_examples 'issuable #url' do |issuable_url_method| + it 'returns the remote_issuable url' do + expect(GitlabClient).to receive(issuable_url_method).with(subject, Project::GitlabCe).and_return('https://example.com/') + expect(subject.url).to eq 'https://example.com/' + end +end