Commit 28ae1ad1 authored by Rémy Coutable's avatar Rémy Coutable

Merge branch 'ce-to-ee-2018-07-23' into 'master'

CE upstream - 2018-07-23 09:40 UTC

See merge request gitlab-org/gitlab-ee!6628
parents 247e6aa5 a607a7e8
...@@ -133,7 +133,7 @@ Most issues will have labels for at least one of the following: ...@@ -133,7 +133,7 @@ Most issues will have labels for at least one of the following:
- Type: ~"feature proposal", ~bug, ~customer, etc. - Type: ~"feature proposal", ~bug, ~customer, etc.
- Subject: ~wiki, ~"container registry", ~ldap, ~api, ~frontend, etc. - Subject: ~wiki, ~"container registry", ~ldap, ~api, ~frontend, etc.
- Team: ~"CI/CD", ~Plan, ~Quality, ~Platform, etc. - Team: ~"CI/CD", ~Plan, ~Manage, ~Quality, etc.
- Release Scoping: ~Deliverable, ~Stretch, ~"Next Patch Release" - Release Scoping: ~Deliverable, ~Stretch, ~"Next Patch Release"
- Priority: ~P1, ~P2, ~P3, ~P4 - Priority: ~P1, ~P2, ~P3, ~P4
- Severity: ~S1, ~S2, ~S3, ~S4 - Severity: ~S1, ~S2, ~S3, ~S4
...@@ -192,9 +192,9 @@ The current team labels are: ...@@ -192,9 +192,9 @@ The current team labels are:
- ~Documentation - ~Documentation
- ~Geo - ~Geo
- ~Gitaly - ~Gitaly
- ~Manage
- ~Monitoring - ~Monitoring
- ~Plan - ~Plan
- ~Platform
- ~Quality - ~Quality
- ~Release - ~Release
- ~"Security Products" - ~"Security Products"
......
...@@ -230,6 +230,9 @@ gem 'gemnasium-gitlab-service', '~> 0.2' ...@@ -230,6 +230,9 @@ gem 'gemnasium-gitlab-service', '~> 0.2'
# Slack integration # Slack integration
gem 'slack-notifier', '~> 1.5.1' gem 'slack-notifier', '~> 1.5.1'
# Hangouts Chat integration
gem 'hangouts-chat', '~> 0.0.5'
# Asana integration # Asana integration
gem 'asana', '~> 0.6.0' gem 'asana', '~> 0.6.0'
......
...@@ -414,6 +414,7 @@ GEM ...@@ -414,6 +414,7 @@ GEM
temple (>= 0.8.0) temple (>= 0.8.0)
thor thor
tilt tilt
hangouts-chat (0.0.5)
hashdiff (0.3.4) hashdiff (0.3.4)
hashie (3.5.7) hashie (3.5.7)
hashie-forbidden_attributes (0.1.1) hashie-forbidden_attributes (0.1.1)
...@@ -1098,6 +1099,7 @@ DEPENDENCIES ...@@ -1098,6 +1099,7 @@ DEPENDENCIES
gssapi gssapi
haml_lint (~> 0.26.0) haml_lint (~> 0.26.0)
hamlit (~> 2.8.8) hamlit (~> 2.8.8)
hangouts-chat (~> 0.0.5)
hashie-forbidden_attributes hashie-forbidden_attributes
health_check (~> 2.6.0) health_check (~> 2.6.0)
hipchat (~> 1.5.0) hipchat (~> 1.5.0)
......
...@@ -417,6 +417,7 @@ GEM ...@@ -417,6 +417,7 @@ GEM
temple (>= 0.8.0) temple (>= 0.8.0)
thor thor
tilt tilt
hangouts-chat (0.0.5)
hashdiff (0.3.4) hashdiff (0.3.4)
hashie (3.5.7) hashie (3.5.7)
hashie-forbidden_attributes (0.1.1) hashie-forbidden_attributes (0.1.1)
...@@ -1108,6 +1109,7 @@ DEPENDENCIES ...@@ -1108,6 +1109,7 @@ DEPENDENCIES
gssapi gssapi
haml_lint (~> 0.26.0) haml_lint (~> 0.26.0)
hamlit (~> 2.8.8) hamlit (~> 2.8.8)
hangouts-chat (~> 0.0.5)
hashie-forbidden_attributes hashie-forbidden_attributes
health_check (~> 2.6.0) health_check (~> 2.6.0)
hipchat (~> 1.5.0) hipchat (~> 1.5.0)
......
...@@ -72,7 +72,22 @@ module LfsRequest ...@@ -72,7 +72,22 @@ module LfsRequest
def lfs_download_access? def lfs_download_access?
return false unless project.lfs_enabled? return false unless project.lfs_enabled?
ci? || lfs_deploy_token? || user_can_download_code? || build_can_download_code? ci? || lfs_deploy_token? || user_can_download_code? || build_can_download_code? || deploy_token_can_download_code?
end
def deploy_token_can_download_code?
deploy_token_present? &&
deploy_token.project == project &&
deploy_token.active? &&
deploy_token.read_repository?
end
def deploy_token_present?
user && user.is_a?(DeployToken)
end
def deploy_token
user
end end
def lfs_upload_access? def lfs_upload_access?
...@@ -88,7 +103,7 @@ module LfsRequest ...@@ -88,7 +103,7 @@ module LfsRequest
end end
def user_can_download_code? def user_can_download_code?
has_authentication_ability?(:download_code) && can?(user, :download_code, project) has_authentication_ability?(:download_code) && can?(user, :download_code, project) && !deploy_token_present?
end end
def build_can_download_code? def build_can_download_code?
......
class Import::GitlabController < Import::BaseController class Import::GitlabController < Import::BaseController
MAX_PROJECT_PAGES = 15
PER_PAGE_PROJECTS = 100
before_action :verify_gitlab_import_enabled before_action :verify_gitlab_import_enabled
before_action :gitlab_auth, except: :callback before_action :gitlab_auth, except: :callback
...@@ -10,7 +13,7 @@ class Import::GitlabController < Import::BaseController ...@@ -10,7 +13,7 @@ class Import::GitlabController < Import::BaseController
end end
def status def status
@repos = client.projects @repos = client.projects(starting_page: 1, page_limit: MAX_PROJECT_PAGES, per_page: PER_PAGE_PROJECTS)
@already_added_projects = find_already_added_projects('gitlab') @already_added_projects = find_already_added_projects('gitlab')
already_added_projects_names = @already_added_projects.pluck(:import_source) already_added_projects_names = @already_added_projects.pluck(:import_source)
......
...@@ -126,10 +126,9 @@ module VisibilityLevelHelper ...@@ -126,10 +126,9 @@ module VisibilityLevelHelper
end end
def visibility_icon_description(form_model) def visibility_icon_description(form_model)
case form_model if form_model.respond_to?(:visibility_level_allowed_as_fork?)
when Project
project_visibility_icon_description(form_model.visibility_level) project_visibility_icon_description(form_model.visibility_level)
when Group elsif form_model.respond_to?(:visibility_level_allowed_by_sub_groups?)
group_visibility_icon_description(form_model.visibility_level) group_visibility_icon_description(form_model.visibility_level)
end end
end end
......
...@@ -3,6 +3,8 @@ class DeployToken < ActiveRecord::Base ...@@ -3,6 +3,8 @@ class DeployToken < ActiveRecord::Base
include TokenAuthenticatable include TokenAuthenticatable
add_authentication_token_field :token add_authentication_token_field :token
prepend EE::DeployToken
AVAILABLE_SCOPES = %i(read_repository read_registry).freeze AVAILABLE_SCOPES = %i(read_repository read_registry).freeze
GITLAB_DEPLOY_TOKEN_NAME = 'gitlab-deploy-token'.freeze GITLAB_DEPLOY_TOKEN_NAME = 'gitlab-deploy-token'.freeze
...@@ -27,7 +29,7 @@ class DeployToken < ActiveRecord::Base ...@@ -27,7 +29,7 @@ class DeployToken < ActiveRecord::Base
end end
def active? def active?
!revoked !revoked && expires_at > Date.today
end end
def scopes def scopes
...@@ -58,6 +60,10 @@ class DeployToken < ActiveRecord::Base ...@@ -58,6 +60,10 @@ class DeployToken < ActiveRecord::Base
write_attribute(:expires_at, value.presence || Forever.date) write_attribute(:expires_at, value.presence || Forever.date)
end end
def admin?
false
end
private private
def ensure_at_least_one_scope def ensure_at_least_one_scope
......
...@@ -159,6 +159,7 @@ class Project < ActiveRecord::Base ...@@ -159,6 +159,7 @@ class Project < ActiveRecord::Base
has_one :mock_monitoring_service has_one :mock_monitoring_service
has_one :microsoft_teams_service has_one :microsoft_teams_service
has_one :packagist_service has_one :packagist_service
has_one :hangouts_chat_service
# TODO: replace these relations with the fork network versions # TODO: replace these relations with the fork network versions
has_one :forked_project_link, foreign_key: "forked_to_project_id" has_one :forked_project_link, foreign_key: "forked_to_project_id"
......
require 'hangouts_chat'
class HangoutsChatService < ChatNotificationService
def title
'Hangouts Chat'
end
def description
'Receive event notifications in Google Hangouts Chat'
end
def self.to_param
'hangouts_chat'
end
def help
'This service sends notifications about projects events to Google Hangouts Chat room.<br />
To set up this service:
<ol>
<li><a href="https://developers.google.com/hangouts/chat/how-tos/webhooks">Set up an incoming webhook for your room</a>. All notifications will come to this room.</li>
<li>Paste the <strong>Webhook URL</strong> into the field below.</li>
<li>Select events below to enable notifications.</li>
</ol>'
end
def event_field(event)
end
def default_channel_placeholder
end
def webhook_placeholder
'https://chat.googleapis.com/v1/spaces…'
end
def default_fields
[
{ type: 'text', name: 'webhook', placeholder: "e.g. #{webhook_placeholder}" },
{ type: 'checkbox', name: 'notify_only_broken_pipelines' },
{ type: 'checkbox', name: 'notify_only_default_branch' }
]
end
private
def notify(message, opts)
simple_text = parse_simple_text_message(message)
HangoutsChat::Sender.new(webhook).simple(simple_text)
end
def parse_simple_text_message(message)
header = message.pretext
return header if message.attachments.empty?
attachment = message.attachments.first
title = format_attachment_title(attachment)
body = attachment[:text]
[header, title, body].compact.join("\n")
end
def format_attachment_title(attachment)
return attachment[:title] unless attachment[:title_link]
"<#{attachment[:title_link]}|#{attachment[:title]}>"
end
end
...@@ -255,6 +255,7 @@ class Service < ActiveRecord::Base ...@@ -255,6 +255,7 @@ class Service < ActiveRecord::Base
emails_on_push emails_on_push
external_wiki external_wiki
flowdock flowdock
hangouts_chat
hipchat hipchat
irker irker
jira jira
......
...@@ -11,7 +11,6 @@ ...@@ -11,7 +11,6 @@
- add_to_breadcrumbs _("Runners"), admin_runners_path - add_to_breadcrumbs _("Runners"), admin_runners_path
- breadcrumb_title "##{@runner.id}" - breadcrumb_title "##{@runner.id}"
- @no_container = true
- if @runner.instance_type? - if @runner.instance_type?
.bs-callout.bs-callout-success .bs-callout.bs-callout-success
......
- if current_user - if current_user
%button.btn.btn-default.star-btn.toggle-star{ type: "button", data: { endpoint: toggle_star_project_path(@project, :json) } } %button.btn.btn-default.star-btn.toggle-star{ type: "button", data: { endpoint: toggle_star_project_path(@project, :json) } }>
- if current_user.starred?(@project) - if current_user.starred?(@project)
= sprite_icon('star') = sprite_icon('star')
%span.starred= _('Unstar') %span.starred= _('Unstar')
......
---
title: Allow cloning LFS repositories through DeployTokens
merge_request: 20729
author:
type: other
---
title: Fix GitLab project imports not loading due to API timeouts
merge_request: 20599
author:
type: fixed
---
title: Add Hangouts Chat integration
merge_request: 20290
author: Kukovskii Vladimir
type: added
---
title: Fix project visibility tooltip
merge_request: 20535
author: Jamie Schembri
type: fixed
...@@ -78,15 +78,15 @@ production: &base ...@@ -78,15 +78,15 @@ production: &base
# username_changing_enabled: false # default: true - User can change her username/namespace # username_changing_enabled: false # default: true - User can change her username/namespace
## Default theme ID ## Default theme ID
## 1 - Indigo ## 1 - Indigo
## 2 - Light Indigo ## 2 - Dark
## 3 - Blue ## 3 - Light
## 4 - Light Blue ## 4 - Blue
## 5 - Green ## 5 - Green
## 6 - Light Green ## 6 - Light Indigo
## 7 - Red ## 7 - Light Blue
## 8 - Light Red ## 8 - Light Green
## 9 - Dark ## 9 - Red
## 10 - Light ## 10 - Light Red
# default_theme: 1 # default: 1 # default_theme: 1 # default: 1
## Automatic issue closing ## Automatic issue closing
......
...@@ -50,6 +50,7 @@ Example of response ...@@ -50,6 +50,7 @@ Example of response
"started_at": "2015-12-24T17:54:24.729Z", "started_at": "2015-12-24T17:54:24.729Z",
"status": "failed", "status": "failed",
"tag": false, "tag": false,
"web_url": "https://example.com/foo/bar/-/jobs/6",
"user": { "user": {
"avatar_url": "http://www.gravatar.com/avatar/e64c7d89f26bd1972efa854d13d7dd61?s=80&d=identicon", "avatar_url": "http://www.gravatar.com/avatar/e64c7d89f26bd1972efa854d13d7dd61?s=80&d=identicon",
"bio": null, "bio": null,
...@@ -82,7 +83,7 @@ Example of response ...@@ -82,7 +83,7 @@ Example of response
"size": 1000 "size": 1000
}, },
"finished_at": "2015-12-24T17:54:27.895Z", "finished_at": "2015-12-24T17:54:27.895Z",
"artifacts_expire_at": "2016-01-23T17:54:27.895Z" "artifacts_expire_at": "2016-01-23T17:54:27.895Z",
"id": 7, "id": 7,
"name": "teaspoon", "name": "teaspoon",
"pipeline": { "pipeline": {
...@@ -97,6 +98,7 @@ Example of response ...@@ -97,6 +98,7 @@ Example of response
"started_at": "2015-12-24T17:54:27.722Z", "started_at": "2015-12-24T17:54:27.722Z",
"status": "failed", "status": "failed",
"tag": false, "tag": false,
"web_url": "https://example.com/foo/bar/-/jobs/7",
"user": { "user": {
"avatar_url": "http://www.gravatar.com/avatar/e64c7d89f26bd1972efa854d13d7dd61?s=80&d=identicon", "avatar_url": "http://www.gravatar.com/avatar/e64c7d89f26bd1972efa854d13d7dd61?s=80&d=identicon",
"bio": null, "bio": null,
...@@ -151,7 +153,7 @@ Example of response ...@@ -151,7 +153,7 @@ Example of response
"created_at": "2015-12-24T15:51:21.727Z", "created_at": "2015-12-24T15:51:21.727Z",
"artifacts_file": null, "artifacts_file": null,
"finished_at": "2015-12-24T17:54:24.921Z", "finished_at": "2015-12-24T17:54:24.921Z",
"artifacts_expire_at": "2016-01-23T17:54:24.921Z" "artifacts_expire_at": "2016-01-23T17:54:24.921Z",
"id": 6, "id": 6,
"name": "rspec:other", "name": "rspec:other",
"pipeline": { "pipeline": {
...@@ -166,6 +168,7 @@ Example of response ...@@ -166,6 +168,7 @@ Example of response
"started_at": "2015-12-24T17:54:24.729Z", "started_at": "2015-12-24T17:54:24.729Z",
"status": "failed", "status": "failed",
"tag": false, "tag": false,
"web_url": "https://example.com/foo/bar/-/jobs/6",
"user": { "user": {
"avatar_url": "http://www.gravatar.com/avatar/e64c7d89f26bd1972efa854d13d7dd61?s=80&d=identicon", "avatar_url": "http://www.gravatar.com/avatar/e64c7d89f26bd1972efa854d13d7dd61?s=80&d=identicon",
"bio": null, "bio": null,
...@@ -198,7 +201,7 @@ Example of response ...@@ -198,7 +201,7 @@ Example of response
"size": 1000 "size": 1000
}, },
"finished_at": "2015-12-24T17:54:27.895Z", "finished_at": "2015-12-24T17:54:27.895Z",
"artifacts_expire_at": "2016-01-23T17:54:27.895Z" "artifacts_expire_at": "2016-01-23T17:54:27.895Z",
"id": 7, "id": 7,
"name": "teaspoon", "name": "teaspoon",
"pipeline": { "pipeline": {
...@@ -213,6 +216,7 @@ Example of response ...@@ -213,6 +216,7 @@ Example of response
"started_at": "2015-12-24T17:54:27.722Z", "started_at": "2015-12-24T17:54:27.722Z",
"status": "failed", "status": "failed",
"tag": false, "tag": false,
"web_url": "https://example.com/foo/bar/-/jobs/7",
"user": { "user": {
"avatar_url": "http://www.gravatar.com/avatar/e64c7d89f26bd1972efa854d13d7dd61?s=80&d=identicon", "avatar_url": "http://www.gravatar.com/avatar/e64c7d89f26bd1972efa854d13d7dd61?s=80&d=identicon",
"bio": null, "bio": null,
...@@ -280,6 +284,7 @@ Example of response ...@@ -280,6 +284,7 @@ Example of response
"started_at": "2015-12-24T17:54:30.733Z", "started_at": "2015-12-24T17:54:30.733Z",
"status": "failed", "status": "failed",
"tag": false, "tag": false,
"web_url": "https://example.com/foo/bar/-/jobs/8",
"user": { "user": {
"avatar_url": "http://www.gravatar.com/avatar/e64c7d89f26bd1972efa854d13d7dd61?s=80&d=identicon", "avatar_url": "http://www.gravatar.com/avatar/e64c7d89f26bd1972efa854d13d7dd61?s=80&d=identicon",
"bio": null, "bio": null,
...@@ -489,7 +494,7 @@ Example of response ...@@ -489,7 +494,7 @@ Example of response
"created_at": "2016-01-11T10:13:33.506Z", "created_at": "2016-01-11T10:13:33.506Z",
"artifacts_file": null, "artifacts_file": null,
"finished_at": "2016-01-11T10:14:09.526Z", "finished_at": "2016-01-11T10:14:09.526Z",
"id": 69, "id": 42,
"name": "rubocop", "name": "rubocop",
"ref": "master", "ref": "master",
"runner": null, "runner": null,
...@@ -497,6 +502,7 @@ Example of response ...@@ -497,6 +502,7 @@ Example of response
"started_at": null, "started_at": null,
"status": "canceled", "status": "canceled",
"tag": false, "tag": false,
"web_url": "https://example.com/foo/bar/-/jobs/42",
"user": null "user": null
} }
``` ```
...@@ -535,7 +541,7 @@ Example of response ...@@ -535,7 +541,7 @@ Example of response
"created_at": "2016-01-11T10:13:33.506Z", "created_at": "2016-01-11T10:13:33.506Z",
"artifacts_file": null, "artifacts_file": null,
"finished_at": null, "finished_at": null,
"id": 69, "id": 42,
"name": "rubocop", "name": "rubocop",
"ref": "master", "ref": "master",
"runner": null, "runner": null,
...@@ -543,6 +549,7 @@ Example of response ...@@ -543,6 +549,7 @@ Example of response
"started_at": null, "started_at": null,
"status": "pending", "status": "pending",
"tag": false, "tag": false,
"web_url": "https://example.com/foo/bar/-/jobs/42",
"user": null "user": null
} }
``` ```
...@@ -583,7 +590,7 @@ Example of response ...@@ -583,7 +590,7 @@ Example of response
}, },
"coverage": null, "coverage": null,
"download_url": null, "download_url": null,
"id": 69, "id": 42,
"name": "rubocop", "name": "rubocop",
"ref": "master", "ref": "master",
"runner": null, "runner": null,
...@@ -593,6 +600,7 @@ Example of response ...@@ -593,6 +600,7 @@ Example of response
"finished_at": "2016-01-11T10:15:10.506Z", "finished_at": "2016-01-11T10:15:10.506Z",
"status": "failed", "status": "failed",
"tag": false, "tag": false,
"web_url": "https://example.com/foo/bar/-/jobs/42",
"user": null "user": null
} }
``` ```
...@@ -633,7 +641,7 @@ Example response: ...@@ -633,7 +641,7 @@ Example response:
}, },
"coverage": null, "coverage": null,
"download_url": null, "download_url": null,
"id": 69, "id": 42,
"name": "rubocop", "name": "rubocop",
"ref": "master", "ref": "master",
"runner": null, "runner": null,
...@@ -643,6 +651,7 @@ Example response: ...@@ -643,6 +651,7 @@ Example response:
"finished_at": "2016-01-11T10:15:10.506Z", "finished_at": "2016-01-11T10:15:10.506Z",
"status": "failed", "status": "failed",
"tag": false, "tag": false,
"web_url": "https://example.com/foo/bar/-/jobs/42",
"user": null "user": null
} }
``` ```
...@@ -681,7 +690,7 @@ Example of response ...@@ -681,7 +690,7 @@ Example of response
"created_at": "2016-01-11T10:13:33.506Z", "created_at": "2016-01-11T10:13:33.506Z",
"artifacts_file": null, "artifacts_file": null,
"finished_at": null, "finished_at": null,
"id": 69, "id": 42,
"name": "rubocop", "name": "rubocop",
"ref": "master", "ref": "master",
"runner": null, "runner": null,
...@@ -689,6 +698,7 @@ Example of response ...@@ -689,6 +698,7 @@ Example of response
"started_at": null, "started_at": null,
"status": "started", "status": "started",
"tag": false, "tag": false,
"web_url": "https://example.com/foo/bar/-/jobs/42",
"user": null "user": null
} }
``` ```
......
...@@ -33,13 +33,15 @@ Example of response ...@@ -33,13 +33,15 @@ Example of response
"id": 47, "id": 47,
"status": "pending", "status": "pending",
"ref": "new-pipeline", "ref": "new-pipeline",
"sha": "a91957a858320c0e17f3a0eca7cfacbff50ea29a" "sha": "a91957a858320c0e17f3a0eca7cfacbff50ea29a",
"web_url": "https://example.com/foo/bar/pipelines/47"
}, },
{ {
"id": 48, "id": 48,
"status": "pending", "status": "pending",
"ref": "new-pipeline", "ref": "new-pipeline",
"sha": "eb94b618fb5865b26e80fdd8ae531b7a63ad851a" "sha": "eb94b618fb5865b26e80fdd8ae531b7a63ad851a",
"web_url": "https://example.com/foo/bar/pipelines/48"
} }
] ]
``` ```
...@@ -86,7 +88,8 @@ Example of response ...@@ -86,7 +88,8 @@ Example of response
"finished_at": "2016-08-11T11:32:35.145Z", "finished_at": "2016-08-11T11:32:35.145Z",
"committed_at": null, "committed_at": null,
"duration": null, "duration": null,
"coverage": "30.0" "coverage": "30.0",
"web_url": "https://example.com/foo/bar/pipelines/46"
} }
``` ```
...@@ -133,7 +136,8 @@ Example of response ...@@ -133,7 +136,8 @@ Example of response
"finished_at": null, "finished_at": null,
"committed_at": null, "committed_at": null,
"duration": null, "duration": null,
"coverage": null "coverage": null,
"web_url": "https://example.com/foo/bar/pipelines/61"
} }
``` ```
...@@ -179,7 +183,8 @@ Response: ...@@ -179,7 +183,8 @@ Response:
"finished_at": "2016-08-11T11:32:35.145Z", "finished_at": "2016-08-11T11:32:35.145Z",
"committed_at": null, "committed_at": null,
"duration": null, "duration": null,
"coverage": null "coverage": null,
"web_url": "https://example.com/foo/bar/pipelines/46"
} }
``` ```
...@@ -225,7 +230,8 @@ Response: ...@@ -225,7 +230,8 @@ Response:
"finished_at": "2016-08-11T11:32:35.145Z", "finished_at": "2016-08-11T11:32:35.145Z",
"committed_at": null, "committed_at": null,
"duration": null, "duration": null,
"coverage": null "coverage": null,
"web_url": "https://example.com/foo/bar/pipelines/46"
} }
``` ```
......
...@@ -577,7 +577,15 @@ If the project is a fork, and you provide a valid token to authenticate, the ...@@ -577,7 +577,15 @@ If the project is a fork, and you provide a valid token to authenticate, the
"avatar_url":"https://assets.gitlab-static.net/uploads/-/system/project/avatar/13083/logo-extra-whitespace.png", "avatar_url":"https://assets.gitlab-static.net/uploads/-/system/project/avatar/13083/logo-extra-whitespace.png",
"star_count":3812, "star_count":3812,
"forks_count":3561, "forks_count":3561,
"last_activity_at":"2018-01-02T11:40:26.570Z" "last_activity_at":"2018-01-02T11:40:26.570Z",
"namespace": {
"id": 72,
"name": "GitLab.org",
"path": "gitlab-org",
"kind": "group",
"full_path": "gitlab-org",
"parent_id": null
}
} }
... ...
......
...@@ -443,6 +443,54 @@ Get Gemnasium service settings for a project. ...@@ -443,6 +443,54 @@ Get Gemnasium service settings for a project.
GET /projects/:id/services/gemnasium GET /projects/:id/services/gemnasium
``` ```
## Hangouts Chat
Google GSuite team collaboration tool.
>**Note:** This service was [introduced in v11.2](https://gitlab.com/gitlab-org/gitlab-ce/merge_requests/20290)
### Create/Edit Hangouts Chat service
Set Hangouts Chat service for a project.
```
PUT /projects/:id/services/hangouts_chat
```
>**Note:** Specific event parameters (e.g. `push_events` flag) were [introduced in v10.4][11435]
Parameters:
| Parameter | Type | Required | Description |
| --------- | ---- | -------- | ----------- |
| `webhook` | string | true | The Hangouts Chat webhook. e.g. https://chat.googleapis.com/v1/spaces... |
| `notify_only_broken_pipelines` | boolean | false | Send notifications for broken pipelines |
| `notify_only_default_branch` | boolean | false | Send notifications only for the default branch |
| `push_events` | boolean | false | Enable notifications for push events |
| `issues_events` | boolean | false | Enable notifications for issue events |
| `confidential_issues_events` | boolean | false | Enable notifications for confidential issue events |
| `merge_requests_events` | boolean | false | Enable notifications for merge request events |
| `tag_push_events` | boolean | false | Enable notifications for tag push events |
| `note_events` | boolean | false | Enable notifications for note events |
| `pipeline_events` | boolean | false | Enable notifications for pipeline events |
| `wiki_page_events` | boolean | false | Enable notifications for wiki page events |
### Delete Hangouts Chat service
Delete Hangouts Chat service for a project.
```
DELETE /projects/:id/services/hangouts_chat
```
### Get Hangouts Chat service settings
Get Hangouts Chat service settings for a project.
```
GET /projects/:id/services/hangouts_chat
```
## HipChat ## HipChat
Private group chat and IM Private group chat and IM
......
# Hangouts Chat service
The Hangouts Chat service sends notifications from GitLab to the room for which the webhook was created.
## On Hangouts Chat
1. Open the chat room in which you want to see the notifications.
1. From the chat room menu, select **Configure Webhooks**.
1. Click on **ADD WEBHOOK** and fill in the name of the bot that will post the messages. Optionally define avatar.
1. Click **SAVE** and copy the **Webhook URL** of your webhook.
See also [the Hangouts Chat documentation for configuring incoming webhooks](https://developers.google.com/hangouts/chat/how-tos/webhooks)
## On GitLab
When you have the **Webhook URL** for your Hangouts Chat room webhook, you can setup the GitLab service.
1. Navigate to the [Integrations page](project_services.md#accessing-the-project-services) in your project's settings, i.e. **Project > Settings > Integrations**.
1. Select the **Hangouts Chat** project service to configure it.
1. Check the **Active** checkbox to turn on the service.
1. Check the checkboxes corresponding to the GitLab events you want to receive.
1. Paste the **Webhook URL** that you copied from the Hangouts Chat configuration step.
1. Configure the remaining options and click `Save changes`.
Your Hangouts Chat room will now start receiving GitLab event notifications as configured.
![Hangouts Chat configuration](img/hangouts_chat_configuration.png)
...@@ -34,8 +34,9 @@ Click on the service links to see further configuration instructions and details ...@@ -34,8 +34,9 @@ Click on the service links to see further configuration instructions and details
| [Emails on push](emails_on_push.md) | Email the commits and diff of each push to a list of recipients | | [Emails on push](emails_on_push.md) | Email the commits and diff of each push to a list of recipients |
| External Wiki | Replaces the link to the internal wiki with a link to an external wiki | | External Wiki | Replaces the link to the internal wiki with a link to an external wiki |
| Flowdock | Flowdock is a collaboration web app for technical teams | | Flowdock | Flowdock is a collaboration web app for technical teams |
| Gemnasium _(Has been deprecated in GitLab 11.0)_ | Gemnasium monitors your project dependencies and alerts you about updates and security vulnerabilities | | Gemnasium _(Has been deprecated in GitLab 11.0)_ | Gemnasium monitors your project dependencies and alerts you about updates and security vulnerabilities |
| [GitHub](github.md) | Sends pipeline notifications to GitHub | | [GitHub](github.md) | Sends pipeline notifications to GitHub |
| [Hangouts Chat](hangouts_chat.md) | Receive events notifications in Google Hangouts Chat |
| [HipChat](hipchat.md) | Private group chat and IM | | [HipChat](hipchat.md) | Private group chat and IM |
| [Irker (IRC gateway)](irker.md) | Send IRC messages, on update, to a list of recipients through an Irker gateway | | [Irker (IRC gateway)](irker.md) | Send IRC messages, on update, to a list of recipients through an Irker gateway |
| [JIRA](jira.md) | JIRA issue tracker | | [JIRA](jira.md) | JIRA issue tracker |
......
module EE
module DeployToken
def auditor?
false
end
def support_bot?
false
end
end
end
...@@ -132,6 +132,7 @@ module API ...@@ -132,6 +132,7 @@ module API
expose :star_count, :forks_count expose :star_count, :forks_count
expose :last_activity_at expose :last_activity_at
expose :namespace, using: 'API::Entities::NamespaceBasic'
expose :custom_attributes, using: 'API::Entities::CustomAttribute', if: :with_custom_attributes expose :custom_attributes, using: 'API::Entities::CustomAttribute', if: :with_custom_attributes
def self.preload_relation(projects_relation, options = {}) def self.preload_relation(projects_relation, options = {})
...@@ -194,7 +195,6 @@ module API ...@@ -194,7 +195,6 @@ module API
expose :shared_runners_enabled expose :shared_runners_enabled
expose :lfs_enabled?, as: :lfs_enabled expose :lfs_enabled?, as: :lfs_enabled
expose :creator_id expose :creator_id
expose :namespace, using: 'API::Entities::NamespaceBasic'
expose :forked_from_project, using: Entities::BasicProjectDetails, if: lambda { |project, options| project.forked? } expose :forked_from_project, using: Entities::BasicProjectDetails, if: lambda { |project, options| project.forked? }
expose :import_status expose :import_status
expose :import_error, if: lambda { |_project, options| options[:user_can_admin_project] } expose :import_error, if: lambda { |_project, options| options[:user_can_admin_project] }
...@@ -531,6 +531,10 @@ module API ...@@ -531,6 +531,10 @@ module API
class PipelineBasic < Grape::Entity class PipelineBasic < Grape::Entity
expose :id, :sha, :ref, :status expose :id, :sha, :ref, :status
expose :web_url do |pipeline, _options|
Gitlab::Routing.url_helpers.project_pipeline_url(pipeline.project, pipeline)
end
end end
class MergeRequestSimple < ProjectEntity class MergeRequestSimple < ProjectEntity
...@@ -1097,6 +1101,10 @@ module API ...@@ -1097,6 +1101,10 @@ module API
expose :user, with: User expose :user, with: User
expose :commit, with: Commit expose :commit, with: Commit
expose :pipeline, with: PipelineBasic expose :pipeline, with: PipelineBasic
expose :web_url do |job, _options|
Gitlab::Routing.url_helpers.project_job_url(job.project, job)
end
end end
class Job < JobBasic class Job < JobBasic
......
...@@ -54,7 +54,7 @@ module API ...@@ -54,7 +54,7 @@ module API
pipeline = user_project.pipelines.find(params[:pipeline_id]) pipeline = user_project.pipelines.find(params[:pipeline_id])
builds = pipeline.builds builds = pipeline.builds
builds = filter_builds(builds, params[:scope]) builds = filter_builds(builds, params[:scope])
builds = builds.preload(:job_artifacts_archive) builds = builds.preload(:job_artifacts_archive, project: [:namespace])
present paginate(builds), with: Entities::Job present paginate(builds), with: Entities::Job
end end
......
...@@ -368,6 +368,14 @@ module API ...@@ -368,6 +368,14 @@ module API
desc: "The project's slug on gemnasium.com" desc: "The project's slug on gemnasium.com"
} }
], ],
'hangouts-chat' => [
{
required: true,
name: :webhook,
type: String,
desc: 'The Hangouts Chat webhook. e.g. https://chat.googleapis.com/v1/spaces…'
}
],
'hipchat' => [ 'hipchat' => [
{ {
required: true, required: true,
...@@ -749,6 +757,7 @@ module API ...@@ -749,6 +757,7 @@ module API
FlowdockService, FlowdockService,
GemnasiumService, GemnasiumService,
GithubService, GithubService,
HangoutsChatService,
HipchatService, HipchatService,
IrkerService, IrkerService,
JiraService, JiraService,
......
...@@ -1253,14 +1253,6 @@ module Gitlab ...@@ -1253,14 +1253,6 @@ module Gitlab
run_git(args, env: source_repository.fetch_env) run_git(args, env: source_repository.fetch_env)
end end
def rugged_add_remote(remote_name, url, mirror_refmap)
rugged.remotes.create(remote_name, url)
set_remote_as_mirror(remote_name, refmap: mirror_refmap) if mirror_refmap
rescue Rugged::ConfigError
remote_update(remote_name, url: url)
end
def gitaly_delete_refs(*ref_names) def gitaly_delete_refs(*ref_names)
gitaly_ref_client.delete_refs(refs: ref_names) if ref_names.any? gitaly_ref_client.delete_refs(refs: ref_names) if ref_names.any?
end end
......
...@@ -32,15 +32,15 @@ module Gitlab ...@@ -32,15 +32,15 @@ module Gitlab
api.get("/api/v4/user").parsed api.get("/api/v4/user").parsed
end end
def issues(project_identifier) def issues(project_identifier, **kwargs)
lazy_page_iterator(PER_PAGE) do |page| lazy_page_iterator(**kwargs) do |page, per_page|
api.get("/api/v4/projects/#{project_identifier}/issues?per_page=#{PER_PAGE}&page=#{page}").parsed api.get("/api/v4/projects/#{project_identifier}/issues?per_page=#{per_page}&page=#{page}").parsed
end end
end end
def issue_comments(project_identifier, issue_id) def issue_comments(project_identifier, issue_id, **kwargs)
lazy_page_iterator(PER_PAGE) do |page| lazy_page_iterator(**kwargs) do |page, per_page|
api.get("/api/v4/projects/#{project_identifier}/issues/#{issue_id}/notes?per_page=#{PER_PAGE}&page=#{page}").parsed api.get("/api/v4/projects/#{project_identifier}/issues/#{issue_id}/notes?per_page=#{per_page}&page=#{page}").parsed
end end
end end
...@@ -48,23 +48,27 @@ module Gitlab ...@@ -48,23 +48,27 @@ module Gitlab
api.get("/api/v4/projects/#{id}").parsed api.get("/api/v4/projects/#{id}").parsed
end end
def projects def projects(**kwargs)
lazy_page_iterator(PER_PAGE) do |page| lazy_page_iterator(**kwargs) do |page, per_page|
api.get("/api/v4/projects?per_page=#{PER_PAGE}&page=#{page}").parsed api.get("/api/v4/projects?per_page=#{per_page}&page=#{page}&simple=true&membership=true").parsed
end end
end end
private private
def lazy_page_iterator(per_page) def lazy_page_iterator(starting_page: 1, page_limit: nil, per_page: PER_PAGE)
Enumerator.new do |y| Enumerator.new do |y|
page = 1 page = starting_page
page_limit = (starting_page - 1) + page_limit if page_limit
loop do loop do
items = yield(page) items = yield(page, per_page)
items.each do |item| items.each do |item|
y << item y << item
end end
break if items.empty? || items.size < per_page
break if items.empty? || items.size < per_page || (page_limit && page >= page_limit)
page += 1 page += 1
end end
......
...@@ -185,11 +185,11 @@ describe 'Merge request > User posts diff notes', :js do ...@@ -185,11 +185,11 @@ describe 'Merge request > User posts diff notes', :js do
end end
describe 'posting a note' do describe 'posting a note' do
xit 'adds as discussion' do it 'adds as discussion' do
expect(page).to have_css('.js-temp-notes-holder', count: 2) expect(page).to have_css('.js-temp-notes-holder', count: 2)
should_allow_commenting(find('[id="6eb14e00385d2fb284765eb1cd8d420d33d63fc9_22_22"]'), asset_form_reset: false) should_allow_commenting(find('[id="6eb14e00385d2fb284765eb1cd8d420d33d63fc9_22_22"]'), asset_form_reset: false)
expect(page).to have_css('.notes_holder .note', count: 1) expect(page).to have_css('.notes_holder .note.note-discussion', count: 1)
expect(page).to have_css('.js-temp-notes-holder', count: 1) expect(page).to have_css('.js-temp-notes-holder', count: 1)
expect(page).to have_button('Reply...') expect(page).to have_button('Reply...')
end end
......
...@@ -319,6 +319,10 @@ ...@@ -319,6 +319,10 @@
"id": "/properties/updated_at", "id": "/properties/updated_at",
"type": "string" "type": "string"
}, },
"web_url": {
"id": "/properties/web_url",
"type": "string"
},
"user": { "user": {
"id": "/properties/user", "id": "/properties/user",
"properties": { "properties": {
......
...@@ -4,13 +4,15 @@ ...@@ -4,13 +4,15 @@
"id", "id",
"sha", "sha",
"ref", "ref",
"status" "status",
"web_url"
], ],
"properties" : { "properties" : {
"id": { "type": "integer" }, "id": { "type": "integer" },
"sha": { "type": "string" }, "sha": { "type": "string" },
"ref": { "type": "string" }, "ref": { "type": "string" },
"status": { "type": "string" } "status": { "type": "string" },
"web_url": { "type": "string" }
}, },
"additionalProperties": false "additionalProperties": false
} }
...@@ -24,13 +24,24 @@ ...@@ -24,13 +24,24 @@
"avatar_url": { "type": ["string", "null"] }, "avatar_url": { "type": ["string", "null"] },
"star_count": { "type": "integer" }, "star_count": { "type": "integer" },
"forks_count": { "type": "integer" }, "forks_count": { "type": "integer" },
"last_activity_at": { "type": "date" } "last_activity_at": { "type": "date" },
"namespace": {
"type": "object",
"properties" : {
"id": { "type": "integer" },
"name": { "type": "string" },
"path": { "type": "string" },
"kind": { "type": "string" },
"full_path": { "type": "string" },
"parent_id": { "type": ["integer", "null"] }
}
}
}, },
"required": [ "required": [
"id", "name", "name_with_namespace", "description", "path", "id", "name", "name_with_namespace", "description", "path",
"path_with_namespace", "created_at", "default_branch", "tag_list", "path_with_namespace", "created_at", "default_branch", "tag_list",
"ssh_url_to_repo", "http_url_to_repo", "web_url", "avatar_url", "ssh_url_to_repo", "http_url_to_repo", "web_url", "avatar_url",
"star_count", "last_activity_at" "star_count", "last_activity_at", "namespace"
], ],
"additionalProperties": false "additionalProperties": false
} }
......
...@@ -6,6 +6,29 @@ describe VisibilityLevelHelper do ...@@ -6,6 +6,29 @@ describe VisibilityLevelHelper do
let(:personal_snippet) { build(:personal_snippet) } let(:personal_snippet) { build(:personal_snippet) }
let(:project_snippet) { build(:project_snippet) } let(:project_snippet) { build(:project_snippet) }
describe 'visibility_icon_description' do
context 'used with a Project' do
it 'delegates projects to #project_visibility_icon_description' do
expect(visibility_icon_description(project))
.to match /project/i
end
context 'used with a ProjectPresenter' do
it 'delegates projects to #project_visibility_icon_description' do
expect(visibility_icon_description(project.present))
.to match /project/i
end
end
context 'used with a Group' do
it 'delegates groups to #group_visibility_icon_description' do
expect(visibility_icon_description(group))
.to match /group/i
end
end
end
end
describe 'visibility_level_description' do describe 'visibility_level_description' do
context 'used with a Project' do context 'used with a Project' do
it 'delegates projects to #project_visibility_level_description' do it 'delegates projects to #project_visibility_level_description' do
......
...@@ -15,4 +15,88 @@ describe Gitlab::GitlabImport::Client do ...@@ -15,4 +15,88 @@ describe Gitlab::GitlabImport::Client do
expect(key).to be_kind_of(Symbol) expect(key).to be_kind_of(Symbol)
end end
end end
it 'uses membership and simple flags' do
stub_request('/api/v4/projects?membership=true&page=1&per_page=100&simple=true')
expect_any_instance_of(OAuth2::Response).to receive(:parsed).and_return([])
expect(client.projects.to_a).to eq []
end
shared_examples 'pagination params' do
before do
allow_any_instance_of(OAuth2::Response).to receive(:parsed).and_return([])
end
it 'allows page_limit param' do
allow_any_instance_of(OAuth2::Response).to receive(:parsed).and_return(element_list)
expect(client).to receive(:lazy_page_iterator).with(hash_including(page_limit: 2)).and_call_original
client.send(method, *args, page_limit: 2, per_page: 1).to_a
end
it 'allows per_page param' do
expect(client).to receive(:lazy_page_iterator).with(hash_including(per_page: 2)).and_call_original
client.send(method, *args, per_page: 2).to_a
end
it 'allows starting_page param' do
expect(client).to receive(:lazy_page_iterator).with(hash_including(starting_page: 3)).and_call_original
client.send(method, *args, starting_page: 3).to_a
end
end
describe '#projects' do
subject(:method) { :projects }
let(:args) { [] }
let(:element_list) { build_list(:project, 2) }
before do
stub_request('/api/v4/projects?membership=true&page=1&per_page=1&simple=true')
stub_request('/api/v4/projects?membership=true&page=2&per_page=1&simple=true')
stub_request('/api/v4/projects?membership=true&page=1&per_page=2&simple=true')
stub_request('/api/v4/projects?membership=true&page=3&per_page=100&simple=true')
end
it_behaves_like 'pagination params'
end
describe '#issues' do
subject(:method) { :issues }
let(:args) { [1] }
let(:element_list) { build_list(:issue, 2) }
before do
stub_request('/api/v4/projects/1/issues?page=1&per_page=1')
stub_request('/api/v4/projects/1/issues?page=2&per_page=1')
stub_request('/api/v4/projects/1/issues?page=1&per_page=2')
stub_request('/api/v4/projects/1/issues?page=3&per_page=100')
end
it_behaves_like 'pagination params'
end
describe '#issue_comments' do
subject(:method) { :issue_comments }
let(:args) { [1, 1] }
let(:element_list) { build_list(:note_on_issue, 2) }
before do
stub_request('/api/v4/projects/1/issues/1/notes?page=1&per_page=1')
stub_request('/api/v4/projects/1/issues/1/notes?page=2&per_page=1')
stub_request('/api/v4/projects/1/issues/1/notes?page=1&per_page=2')
stub_request('/api/v4/projects/1/issues/1/notes?page=3&per_page=100')
end
it_behaves_like 'pagination params'
end
def stub_request(path)
WebMock.stub_request(:get, "https://gitlab.com#{path}")
.to_return(status: 200)
end
end end
...@@ -234,6 +234,7 @@ project: ...@@ -234,6 +234,7 @@ project:
- slack_service - slack_service
- microsoft_teams_service - microsoft_teams_service
- mattermost_service - mattermost_service
- hangouts_chat_service
- buildkite_service - buildkite_service
- bamboo_service - bamboo_service
- teamcity_service - teamcity_service
......
...@@ -62,11 +62,18 @@ describe DeployToken do ...@@ -62,11 +62,18 @@ describe DeployToken do
end end
end end
context "when it hasn't been revoked" do context "when it hasn't been revoked and is not expired" do
it 'should return true' do it 'should return true' do
expect(deploy_token.active?).to be_truthy expect(deploy_token.active?).to be_truthy
end end
end end
context "when it hasn't been revoked and is expired" do
it 'should return true' do
deploy_token.update_attribute(:expires_at, Date.today - 5.days)
expect(deploy_token.active?).to be_falsy
end
end
end end
describe '#username' do describe '#username' do
......
require 'spec_helper'
describe HangoutsChatService do
describe 'Associations' do
it { is_expected.to belong_to :project }
it { is_expected.to have_one :service_hook }
end
describe 'Validations' do
context 'when service is active' do
before do
subject.active = true
end
it { is_expected.to validate_presence_of(:webhook) }
it_behaves_like 'issue tracker service URL attribute', :webhook
end
context 'when service is inactive' do
before do
subject.active = false
end
it { is_expected.not_to validate_presence_of(:webhook) }
end
end
describe '#execute' do
let(:user) { create(:user) }
let(:project) { create(:project, :repository) }
let(:webhook_url) { 'https://example.gitlab.com/' }
before do
allow(subject).to receive_messages(
project: project,
project_id: project.id,
service_hook: true,
webhook: webhook_url
)
WebMock.stub_request(:post, webhook_url)
end
shared_examples 'Hangouts Chat service' do
it 'calls Hangouts Chat API' do
subject.execute(sample_data)
expect(WebMock)
.to have_requested(:post, webhook_url)
.with { |req| req.body =~ /\A{"text":.+}\Z/ }
.once
end
end
context 'with push events' do
let(:sample_data) do
Gitlab::DataBuilder::Push.build_sample(project, user)
end
it_behaves_like 'Hangouts Chat service'
it 'specifies the webhook when it is configured' do
expect(HangoutsChat::Sender).to receive(:new).with(webhook_url).and_return(double(:hangouts_chat_service).as_null_object)
subject.execute(sample_data)
end
context 'with not default branch' do
let(:sample_data) do
Gitlab::DataBuilder::Push.build(project, user, nil, nil, 'not-the-default-branch')
end
context 'when notify_only_default_branch enabled' do
before do
subject.notify_only_default_branch = true
end
it 'does not call the Hangouts Chat API' do
result = subject.execute(sample_data)
expect(result).to be_falsy
end
end
context 'when notify_only_default_branch disabled' do
before do
subject.notify_only_default_branch = false
end
it_behaves_like 'Hangouts Chat service'
end
end
end
context 'with issue events' do
let(:opts) { { title: 'Awesome issue', description: 'please fix' } }
let(:sample_data) do
service = Issues::CreateService.new(project, user, opts)
issue = service.execute
service.hook_data(issue, 'open')
end
it_behaves_like 'Hangouts Chat service'
end
context 'with merge events' do
let(:opts) do
{
title: 'Awesome merge_request',
description: 'please fix',
source_branch: 'feature',
target_branch: 'master'
}
end
let(:sample_data) do
service = MergeRequests::CreateService.new(project, user, opts)
merge_request = service.execute
service.hook_data(merge_request, 'open')
end
before do
project.add_developer(user)
end
it_behaves_like 'Hangouts Chat service'
end
context 'with wiki page events' do
let(:opts) do
{
title: 'Awesome wiki_page',
content: 'Some text describing some thing or another',
format: 'md',
message: 'user created page: Awesome wiki_page'
}
end
let(:wiki_page) { create(:wiki_page, wiki: project.wiki, attrs: opts) }
let(:sample_data) { Gitlab::DataBuilder::WikiPage.build(wiki_page, user, 'create') }
it_behaves_like 'Hangouts Chat service'
end
context 'with note events' do
let(:sample_data) { Gitlab::DataBuilder::Note.build(note, user) }
context 'with commit comment' do
let(:note) do
create(:note_on_commit, author: user,
project: project,
commit_id: project.repository.commit.id,
note: 'a comment on a commit')
end
it_behaves_like 'Hangouts Chat service'
end
context 'with merge request comment' do
let(:note) do
create(:note_on_merge_request, project: project,
note: 'merge request note')
end
it_behaves_like 'Hangouts Chat service'
end
context 'with issue comment' do
let(:note) do
create(:note_on_issue, project: project, note: 'issue note')
end
it_behaves_like 'Hangouts Chat service'
end
context 'with snippet comment' do
let(:note) do
create(:note_on_project_snippet, project: project,
note: 'snippet note')
end
it_behaves_like 'Hangouts Chat service'
end
end
context 'with pipeline events' do
let(:pipeline) do
create(:ci_pipeline,
project: project, status: status,
sha: project.commit.sha, ref: project.default_branch)
end
let(:sample_data) { Gitlab::DataBuilder::Pipeline.build(pipeline) }
context 'with failed pipeline' do
let(:status) { 'failed' }
it_behaves_like 'Hangouts Chat service'
end
context 'with succeeded pipeline' do
let(:status) { 'success' }
context 'with default notify_only_broken_pipelines' do
it 'does not call Hangouts Chat API' do
result = subject.execute(sample_data)
expect(result).to be_falsy
end
end
context 'when notify_only_broken_pipelines is false' do
before do
subject.notify_only_broken_pipelines = false
end
it_behaves_like 'Hangouts Chat service'
end
end
context 'with not default branch' do
let(:pipeline) do
create(:ci_pipeline, project: project, status: 'failed', ref: 'not-the-default-branch')
end
context 'when notify_only_default_branch enabled' do
before do
subject.notify_only_default_branch = true
end
it 'does not call the Hangouts Chat API' do
result = subject.execute(sample_data)
expect(result).to be_falsy
end
end
context 'when notify_only_default_branch disabled' do
before do
subject.notify_only_default_branch = false
end
it_behaves_like 'Hangouts Chat service'
end
end
end
end
end
...@@ -26,6 +26,7 @@ describe Project do ...@@ -26,6 +26,7 @@ describe Project do
it { is_expected.to have_one(:slack_service) } it { is_expected.to have_one(:slack_service) }
it { is_expected.to have_one(:microsoft_teams_service) } it { is_expected.to have_one(:microsoft_teams_service) }
it { is_expected.to have_one(:mattermost_service) } it { is_expected.to have_one(:mattermost_service) }
it { is_expected.to have_one(:hangouts_chat_service) }
it { is_expected.to have_one(:packagist_service) } it { is_expected.to have_one(:packagist_service) }
it { is_expected.to have_one(:pushover_service) } it { is_expected.to have_one(:pushover_service) }
it { is_expected.to have_one(:asana_service) } it { is_expected.to have_one(:asana_service) }
......
...@@ -20,7 +20,7 @@ describe API::Environments do ...@@ -20,7 +20,7 @@ describe API::Environments do
path path_with_namespace path path_with_namespace
star_count forks_count star_count forks_count
created_at last_activity_at created_at last_activity_at
avatar_url avatar_url namespace
) )
get api("/projects/#{project.id}/environments", user) get api("/projects/#{project.id}/environments", user)
......
...@@ -221,6 +221,7 @@ describe API::Jobs do ...@@ -221,6 +221,7 @@ describe API::Jobs do
expect(Time.parse(json_response['finished_at'])).to be_like_time(job.finished_at) expect(Time.parse(json_response['finished_at'])).to be_like_time(job.finished_at)
expect(Time.parse(json_response['artifacts_expire_at'])).to be_like_time(job.artifacts_expire_at) expect(Time.parse(json_response['artifacts_expire_at'])).to be_like_time(job.artifacts_expire_at)
expect(json_response['duration']).to eq(job.duration) expect(json_response['duration']).to eq(job.duration)
expect(json_response['web_url']).to be_present
end end
it 'returns pipeline data' do it 'returns pipeline data' do
......
...@@ -24,7 +24,8 @@ describe API::Pipelines do ...@@ -24,7 +24,8 @@ describe API::Pipelines do
expect(json_response).to be_an Array expect(json_response).to be_an Array
expect(json_response.first['sha']).to match /\A\h{40}\z/ expect(json_response.first['sha']).to match /\A\h{40}\z/
expect(json_response.first['id']).to eq pipeline.id expect(json_response.first['id']).to eq pipeline.id
expect(json_response.first.keys).to contain_exactly(*%w[id sha ref status]) expect(json_response.first['web_url']).to be_present
expect(json_response.first.keys).to contain_exactly(*%w[id sha ref status web_url])
end end
context 'when parameter is passed' do context 'when parameter is passed' do
......
...@@ -225,7 +225,7 @@ describe API::Projects do ...@@ -225,7 +225,7 @@ describe API::Projects do
path path_with_namespace path path_with_namespace
star_count forks_count star_count forks_count
created_at last_activity_at created_at last_activity_at
avatar_url avatar_url namespace
) )
get api('/projects?simple=true', user) get api('/projects?simple=true', user)
......
...@@ -575,6 +575,40 @@ describe 'Git LFS API and storage' do ...@@ -575,6 +575,40 @@ describe 'Git LFS API and storage' do
end end
end end
context 'when using Deploy Tokens' do
let(:project) { create(:project, :repository) }
let(:authorization) { authorize_deploy_token }
let(:update_user_permissions) { nil }
let(:role) { nil }
let(:update_lfs_permissions) do
project.lfs_objects << lfs_object
end
context 'when Deploy Token is valid' do
let(:deploy_token) { create(:deploy_token, projects: [project]) }
it_behaves_like 'an authorized requests'
end
context 'when Deploy Token is not valid' do
let(:deploy_token) { create(:deploy_token, projects: [project], read_repository: false) }
it 'responds with access denied' do
expect(response).to have_gitlab_http_status(401)
end
end
context 'when Deploy Token is not related to the project' do
let(:another_project) { create(:project, :repository) }
let(:deploy_token) { create(:deploy_token, projects: [another_project]) }
it 'responds with access forbidden' do
# We render 404, to prevent data leakage about existence of the project
expect(response).to have_gitlab_http_status(404)
end
end
end
context 'when build is authorized as' do context 'when build is authorized as' do
let(:authorization) { authorize_ci_project } let(:authorization) { authorize_ci_project }
...@@ -1423,6 +1457,10 @@ describe 'Git LFS API and storage' do ...@@ -1423,6 +1457,10 @@ describe 'Git LFS API and storage' do
ActionController::HttpAuthentication::Basic.encode_credentials(user.username, Gitlab::LfsToken.new(user).token) ActionController::HttpAuthentication::Basic.encode_credentials(user.username, Gitlab::LfsToken.new(user).token)
end end
def authorize_deploy_token
ActionController::HttpAuthentication::Basic.encode_credentials(deploy_token.username, deploy_token.token)
end
def post_lfs_json(url, body = nil, headers = nil) def post_lfs_json(url, body = nil, headers = nil)
post(url, body.try(:to_json), (headers || {}).merge('Content-Type' => LfsRequest::CONTENT_TYPE)) post(url, body.try(:to_json), (headers || {}).merge('Content-Type' => LfsRequest::CONTENT_TYPE))
end end
......
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment