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:
- Type: ~"feature proposal", ~bug, ~customer, 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"
- Priority: ~P1, ~P2, ~P3, ~P4
- Severity: ~S1, ~S2, ~S3, ~S4
......@@ -192,9 +192,9 @@ The current team labels are:
- ~Documentation
- ~Geo
- ~Gitaly
- ~Manage
- ~Monitoring
- ~Plan
- ~Platform
- ~Quality
- ~Release
- ~"Security Products"
......
......@@ -230,6 +230,9 @@ gem 'gemnasium-gitlab-service', '~> 0.2'
# Slack integration
gem 'slack-notifier', '~> 1.5.1'
# Hangouts Chat integration
gem 'hangouts-chat', '~> 0.0.5'
# Asana integration
gem 'asana', '~> 0.6.0'
......
......@@ -414,6 +414,7 @@ GEM
temple (>= 0.8.0)
thor
tilt
hangouts-chat (0.0.5)
hashdiff (0.3.4)
hashie (3.5.7)
hashie-forbidden_attributes (0.1.1)
......@@ -1098,6 +1099,7 @@ DEPENDENCIES
gssapi
haml_lint (~> 0.26.0)
hamlit (~> 2.8.8)
hangouts-chat (~> 0.0.5)
hashie-forbidden_attributes
health_check (~> 2.6.0)
hipchat (~> 1.5.0)
......
......@@ -417,6 +417,7 @@ GEM
temple (>= 0.8.0)
thor
tilt
hangouts-chat (0.0.5)
hashdiff (0.3.4)
hashie (3.5.7)
hashie-forbidden_attributes (0.1.1)
......@@ -1108,6 +1109,7 @@ DEPENDENCIES
gssapi
haml_lint (~> 0.26.0)
hamlit (~> 2.8.8)
hangouts-chat (~> 0.0.5)
hashie-forbidden_attributes
health_check (~> 2.6.0)
hipchat (~> 1.5.0)
......
......@@ -72,7 +72,22 @@ module LfsRequest
def lfs_download_access?
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
def lfs_upload_access?
......@@ -88,7 +103,7 @@ module LfsRequest
end
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
def build_can_download_code?
......
class Import::GitlabController < Import::BaseController
MAX_PROJECT_PAGES = 15
PER_PAGE_PROJECTS = 100
before_action :verify_gitlab_import_enabled
before_action :gitlab_auth, except: :callback
......@@ -10,7 +13,7 @@ class Import::GitlabController < Import::BaseController
end
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_names = @already_added_projects.pluck(:import_source)
......
......@@ -126,10 +126,9 @@ module VisibilityLevelHelper
end
def visibility_icon_description(form_model)
case form_model
when Project
if form_model.respond_to?(:visibility_level_allowed_as_fork?)
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)
end
end
......
......@@ -3,6 +3,8 @@ class DeployToken < ActiveRecord::Base
include TokenAuthenticatable
add_authentication_token_field :token
prepend EE::DeployToken
AVAILABLE_SCOPES = %i(read_repository read_registry).freeze
GITLAB_DEPLOY_TOKEN_NAME = 'gitlab-deploy-token'.freeze
......@@ -27,7 +29,7 @@ class DeployToken < ActiveRecord::Base
end
def active?
!revoked
!revoked && expires_at > Date.today
end
def scopes
......@@ -58,6 +60,10 @@ class DeployToken < ActiveRecord::Base
write_attribute(:expires_at, value.presence || Forever.date)
end
def admin?
false
end
private
def ensure_at_least_one_scope
......
......@@ -159,6 +159,7 @@ class Project < ActiveRecord::Base
has_one :mock_monitoring_service
has_one :microsoft_teams_service
has_one :packagist_service
has_one :hangouts_chat_service
# TODO: replace these relations with the fork network versions
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
emails_on_push
external_wiki
flowdock
hangouts_chat
hipchat
irker
jira
......
......@@ -11,7 +11,6 @@
- add_to_breadcrumbs _("Runners"), admin_runners_path
- breadcrumb_title "##{@runner.id}"
- @no_container = true
- if @runner.instance_type?
.bs-callout.bs-callout-success
......
- 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)
= sprite_icon('star')
%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
# username_changing_enabled: false # default: true - User can change her username/namespace
## Default theme ID
## 1 - Indigo
## 2 - Light Indigo
## 3 - Blue
## 4 - Light Blue
## 2 - Dark
## 3 - Light
## 4 - Blue
## 5 - Green
## 6 - Light Green
## 7 - Red
## 8 - Light Red
## 9 - Dark
## 10 - Light
## 6 - Light Indigo
## 7 - Light Blue
## 8 - Light Green
## 9 - Red
## 10 - Light Red
# default_theme: 1 # default: 1
## Automatic issue closing
......
......@@ -50,6 +50,7 @@ Example of response
"started_at": "2015-12-24T17:54:24.729Z",
"status": "failed",
"tag": false,
"web_url": "https://example.com/foo/bar/-/jobs/6",
"user": {
"avatar_url": "http://www.gravatar.com/avatar/e64c7d89f26bd1972efa854d13d7dd61?s=80&d=identicon",
"bio": null,
......@@ -82,7 +83,7 @@ Example of response
"size": 1000
},
"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,
"name": "teaspoon",
"pipeline": {
......@@ -97,6 +98,7 @@ Example of response
"started_at": "2015-12-24T17:54:27.722Z",
"status": "failed",
"tag": false,
"web_url": "https://example.com/foo/bar/-/jobs/7",
"user": {
"avatar_url": "http://www.gravatar.com/avatar/e64c7d89f26bd1972efa854d13d7dd61?s=80&d=identicon",
"bio": null,
......@@ -151,7 +153,7 @@ Example of response
"created_at": "2015-12-24T15:51:21.727Z",
"artifacts_file": null,
"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,
"name": "rspec:other",
"pipeline": {
......@@ -166,6 +168,7 @@ Example of response
"started_at": "2015-12-24T17:54:24.729Z",
"status": "failed",
"tag": false,
"web_url": "https://example.com/foo/bar/-/jobs/6",
"user": {
"avatar_url": "http://www.gravatar.com/avatar/e64c7d89f26bd1972efa854d13d7dd61?s=80&d=identicon",
"bio": null,
......@@ -198,7 +201,7 @@ Example of response
"size": 1000
},
"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,
"name": "teaspoon",
"pipeline": {
......@@ -213,6 +216,7 @@ Example of response
"started_at": "2015-12-24T17:54:27.722Z",
"status": "failed",
"tag": false,
"web_url": "https://example.com/foo/bar/-/jobs/7",
"user": {
"avatar_url": "http://www.gravatar.com/avatar/e64c7d89f26bd1972efa854d13d7dd61?s=80&d=identicon",
"bio": null,
......@@ -280,6 +284,7 @@ Example of response
"started_at": "2015-12-24T17:54:30.733Z",
"status": "failed",
"tag": false,
"web_url": "https://example.com/foo/bar/-/jobs/8",
"user": {
"avatar_url": "http://www.gravatar.com/avatar/e64c7d89f26bd1972efa854d13d7dd61?s=80&d=identicon",
"bio": null,
......@@ -489,7 +494,7 @@ Example of response
"created_at": "2016-01-11T10:13:33.506Z",
"artifacts_file": null,
"finished_at": "2016-01-11T10:14:09.526Z",
"id": 69,
"id": 42,
"name": "rubocop",
"ref": "master",
"runner": null,
......@@ -497,6 +502,7 @@ Example of response
"started_at": null,
"status": "canceled",
"tag": false,
"web_url": "https://example.com/foo/bar/-/jobs/42",
"user": null
}
```
......@@ -535,7 +541,7 @@ Example of response
"created_at": "2016-01-11T10:13:33.506Z",
"artifacts_file": null,
"finished_at": null,
"id": 69,
"id": 42,
"name": "rubocop",
"ref": "master",
"runner": null,
......@@ -543,6 +549,7 @@ Example of response
"started_at": null,
"status": "pending",
"tag": false,
"web_url": "https://example.com/foo/bar/-/jobs/42",
"user": null
}
```
......@@ -583,7 +590,7 @@ Example of response
},
"coverage": null,
"download_url": null,
"id": 69,
"id": 42,
"name": "rubocop",
"ref": "master",
"runner": null,
......@@ -593,6 +600,7 @@ Example of response
"finished_at": "2016-01-11T10:15:10.506Z",
"status": "failed",
"tag": false,
"web_url": "https://example.com/foo/bar/-/jobs/42",
"user": null
}
```
......@@ -633,7 +641,7 @@ Example response:
},
"coverage": null,
"download_url": null,
"id": 69,
"id": 42,
"name": "rubocop",
"ref": "master",
"runner": null,
......@@ -643,6 +651,7 @@ Example response:
"finished_at": "2016-01-11T10:15:10.506Z",
"status": "failed",
"tag": false,
"web_url": "https://example.com/foo/bar/-/jobs/42",
"user": null
}
```
......@@ -681,7 +690,7 @@ Example of response
"created_at": "2016-01-11T10:13:33.506Z",
"artifacts_file": null,
"finished_at": null,
"id": 69,
"id": 42,
"name": "rubocop",
"ref": "master",
"runner": null,
......@@ -689,6 +698,7 @@ Example of response
"started_at": null,
"status": "started",
"tag": false,
"web_url": "https://example.com/foo/bar/-/jobs/42",
"user": null
}
```
......
......@@ -33,13 +33,15 @@ Example of response
"id": 47,
"status": "pending",
"ref": "new-pipeline",
"sha": "a91957a858320c0e17f3a0eca7cfacbff50ea29a"
"sha": "a91957a858320c0e17f3a0eca7cfacbff50ea29a",
"web_url": "https://example.com/foo/bar/pipelines/47"
},
{
"id": 48,
"status": "pending",
"ref": "new-pipeline",
"sha": "eb94b618fb5865b26e80fdd8ae531b7a63ad851a"
"sha": "eb94b618fb5865b26e80fdd8ae531b7a63ad851a",
"web_url": "https://example.com/foo/bar/pipelines/48"
}
]
```
......@@ -86,7 +88,8 @@ Example of response
"finished_at": "2016-08-11T11:32:35.145Z",
"committed_at": 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
"finished_at": null,
"committed_at": null,
"duration": null,
"coverage": null
"coverage": null,
"web_url": "https://example.com/foo/bar/pipelines/61"
}
```
......@@ -179,7 +183,8 @@ Response:
"finished_at": "2016-08-11T11:32:35.145Z",
"committed_at": null,
"duration": null,
"coverage": null
"coverage": null,
"web_url": "https://example.com/foo/bar/pipelines/46"
}
```
......@@ -225,7 +230,8 @@ Response:
"finished_at": "2016-08-11T11:32:35.145Z",
"committed_at": 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
"avatar_url":"https://assets.gitlab-static.net/uploads/-/system/project/avatar/13083/logo-extra-whitespace.png",
"star_count":3812,
"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.
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
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
| [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 |
| 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 |
| [Hangouts Chat](hangouts_chat.md) | Receive events notifications in Google Hangouts Chat |
| [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 |
| [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
expose :star_count, :forks_count
expose :last_activity_at
expose :namespace, using: 'API::Entities::NamespaceBasic'
expose :custom_attributes, using: 'API::Entities::CustomAttribute', if: :with_custom_attributes
def self.preload_relation(projects_relation, options = {})
......@@ -194,7 +195,6 @@ module API
expose :shared_runners_enabled
expose :lfs_enabled?, as: :lfs_enabled
expose :creator_id
expose :namespace, using: 'API::Entities::NamespaceBasic'
expose :forked_from_project, using: Entities::BasicProjectDetails, if: lambda { |project, options| project.forked? }
expose :import_status
expose :import_error, if: lambda { |_project, options| options[:user_can_admin_project] }
......@@ -531,6 +531,10 @@ module API
class PipelineBasic < Grape::Entity
expose :id, :sha, :ref, :status
expose :web_url do |pipeline, _options|
Gitlab::Routing.url_helpers.project_pipeline_url(pipeline.project, pipeline)
end
end
class MergeRequestSimple < ProjectEntity
......@@ -1097,6 +1101,10 @@ module API
expose :user, with: User
expose :commit, with: Commit
expose :pipeline, with: PipelineBasic
expose :web_url do |job, _options|
Gitlab::Routing.url_helpers.project_job_url(job.project, job)
end
end
class Job < JobBasic
......
......@@ -54,7 +54,7 @@ module API
pipeline = user_project.pipelines.find(params[:pipeline_id])
builds = pipeline.builds
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
end
......
......@@ -368,6 +368,14 @@ module API
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' => [
{
required: true,
......@@ -749,6 +757,7 @@ module API
FlowdockService,
GemnasiumService,
GithubService,
HangoutsChatService,
HipchatService,
IrkerService,
JiraService,
......
......@@ -1253,14 +1253,6 @@ module Gitlab
run_git(args, env: source_repository.fetch_env)
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)
gitaly_ref_client.delete_refs(refs: ref_names) if ref_names.any?
end
......
......@@ -32,15 +32,15 @@ module Gitlab
api.get("/api/v4/user").parsed
end
def issues(project_identifier)
lazy_page_iterator(PER_PAGE) do |page|
api.get("/api/v4/projects/#{project_identifier}/issues?per_page=#{PER_PAGE}&page=#{page}").parsed
def issues(project_identifier, **kwargs)
lazy_page_iterator(**kwargs) do |page, per_page|
api.get("/api/v4/projects/#{project_identifier}/issues?per_page=#{per_page}&page=#{page}").parsed
end
end
def issue_comments(project_identifier, issue_id)
lazy_page_iterator(PER_PAGE) do |page|
api.get("/api/v4/projects/#{project_identifier}/issues/#{issue_id}/notes?per_page=#{PER_PAGE}&page=#{page}").parsed
def issue_comments(project_identifier, issue_id, **kwargs)
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
end
end
......@@ -48,23 +48,27 @@ module Gitlab
api.get("/api/v4/projects/#{id}").parsed
end
def projects
lazy_page_iterator(PER_PAGE) do |page|
api.get("/api/v4/projects?per_page=#{PER_PAGE}&page=#{page}").parsed
def projects(**kwargs)
lazy_page_iterator(**kwargs) do |page, per_page|
api.get("/api/v4/projects?per_page=#{per_page}&page=#{page}&simple=true&membership=true").parsed
end
end
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|
page = 1
page = starting_page
page_limit = (starting_page - 1) + page_limit if page_limit
loop do
items = yield(page)
items = yield(page, per_page)
items.each do |item|
y << item
end
break if items.empty? || items.size < per_page
break if items.empty? || items.size < per_page || (page_limit && page >= page_limit)
page += 1
end
......
......@@ -185,11 +185,11 @@ describe 'Merge request > User posts diff notes', :js do
end
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)
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_button('Reply...')
end
......
......@@ -319,6 +319,10 @@
"id": "/properties/updated_at",
"type": "string"
},
"web_url": {
"id": "/properties/web_url",
"type": "string"
},
"user": {
"id": "/properties/user",
"properties": {
......
......@@ -4,13 +4,15 @@
"id",
"sha",
"ref",
"status"
"status",
"web_url"
],
"properties" : {
"id": { "type": "integer" },
"sha": { "type": "string" },
"ref": { "type": "string" },
"status": { "type": "string" }
"status": { "type": "string" },
"web_url": { "type": "string" }
},
"additionalProperties": false
}
......@@ -24,13 +24,24 @@
"avatar_url": { "type": ["string", "null"] },
"star_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": [
"id", "name", "name_with_namespace", "description", "path",
"path_with_namespace", "created_at", "default_branch", "tag_list",
"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
}
......
......@@ -6,6 +6,29 @@ describe VisibilityLevelHelper do
let(:personal_snippet) { build(:personal_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
context 'used with a Project' do
it 'delegates projects to #project_visibility_level_description' do
......
......@@ -15,4 +15,88 @@ describe Gitlab::GitlabImport::Client do
expect(key).to be_kind_of(Symbol)
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
......@@ -234,6 +234,7 @@ project:
- slack_service
- microsoft_teams_service
- mattermost_service
- hangouts_chat_service
- buildkite_service
- bamboo_service
- teamcity_service
......
......@@ -62,11 +62,18 @@ describe DeployToken do
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
expect(deploy_token.active?).to be_truthy
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
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
it { is_expected.to have_one(:slack_service) }
it { is_expected.to have_one(:microsoft_teams_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(:pushover_service) }
it { is_expected.to have_one(:asana_service) }
......
......@@ -20,7 +20,7 @@ describe API::Environments do
path path_with_namespace
star_count forks_count
created_at last_activity_at
avatar_url
avatar_url namespace
)
get api("/projects/#{project.id}/environments", user)
......
......@@ -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['artifacts_expire_at'])).to be_like_time(job.artifacts_expire_at)
expect(json_response['duration']).to eq(job.duration)
expect(json_response['web_url']).to be_present
end
it 'returns pipeline data' do
......
......@@ -24,7 +24,8 @@ describe API::Pipelines do
expect(json_response).to be_an Array
expect(json_response.first['sha']).to match /\A\h{40}\z/
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
context 'when parameter is passed' do
......
......@@ -225,7 +225,7 @@ describe API::Projects do
path path_with_namespace
star_count forks_count
created_at last_activity_at
avatar_url
avatar_url namespace
)
get api('/projects?simple=true', user)
......
......@@ -575,6 +575,40 @@ describe 'Git LFS API and storage' do
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
let(:authorization) { authorize_ci_project }
......@@ -1423,6 +1457,10 @@ describe 'Git LFS API and storage' do
ActionController::HttpAuthentication::Basic.encode_credentials(user.username, Gitlab::LfsToken.new(user).token)
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)
post(url, body.try(:to_json), (headers || {}).merge('Content-Type' => LfsRequest::CONTENT_TYPE))
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