Commit 77d0de91 authored by Zeger-Jan van de Weg's avatar Zeger-Jan van de Weg

Merge branch 'master' into 'zj-create-mattermost-team'

# Conflicts:
#   db/schema.rb
parents 597639c0 348dff0a
...@@ -438,3 +438,4 @@ cache gems: ...@@ -438,3 +438,4 @@ cache gems:
- vendor/cache - vendor/cache
only: only:
- master@gitlab-org/gitlab-ce - master@gitlab-org/gitlab-ce
- master@gitlab-org/gitlab-ee
...@@ -198,7 +198,7 @@ require('./task_list'); ...@@ -198,7 +198,7 @@ require('./task_list');
this.refreshing = true; this.refreshing = true;
return $.ajax({ return $.ajax({
url: this.notes_url, url: this.notes_url,
data: "last_fetched_at=" + this.last_fetched_at, headers: { "X-Last-Fetched-At": this.last_fetched_at },
dataType: "json", dataType: "json",
success: (function(_this) { success: (function(_this) {
return function(data) { return function(data) {
......
...@@ -8,6 +8,19 @@ body { ...@@ -8,6 +8,19 @@ body {
&.navless { &.navless {
background-color: $white-light !important; background-color: $white-light !important;
} }
&.card-content {
background-color: $gray-darker;
.content-wrapper {
padding: 0;
.container-fluid,
.container-limited {
background-color: $gray-darker;
}
}
}
} }
.container { .container {
......
...@@ -10,11 +10,6 @@ class Profiles::KeysController < Profiles::ApplicationController ...@@ -10,11 +10,6 @@ class Profiles::KeysController < Profiles::ApplicationController
@key = current_user.keys.find(params[:id]) @key = current_user.keys.find(params[:id])
end end
# Back-compat: We need to support this URL since git-annex webapp points to it
def new
redirect_to profile_keys_path
end
def create def create
@key = current_user.keys.new(key_params) @key = current_user.keys.new(key_params)
......
...@@ -47,11 +47,14 @@ class ProfilesController < Profiles::ApplicationController ...@@ -47,11 +47,14 @@ class ProfilesController < Profiles::ApplicationController
end end
def update_username def update_username
@user.update_attributes(username: user_params[:username]) if @user.update_attributes(username: user_params[:username])
options = { notice: "Username successfully changed" }
respond_to do |format| else
format.js message = @user.errors.full_messages.uniq.join('. ')
options = { alert: "Username change failed - #{message}" }
end end
redirect_back_or_default(default: { action: 'show' }, options: options)
end end
private private
......
...@@ -20,7 +20,7 @@ class Projects::BranchesController < Projects::ApplicationController ...@@ -20,7 +20,7 @@ class Projects::BranchesController < Projects::ApplicationController
respond_to do |format| respond_to do |format|
format.html format.html
format.json do format.json do
render json: @repository.branch_names render json: @branches.map(&:name)
end end
end end
end end
......
...@@ -211,6 +211,11 @@ class Projects::NotesController < Projects::ApplicationController ...@@ -211,6 +211,11 @@ class Projects::NotesController < Projects::ApplicationController
end end
def find_current_user_notes def find_current_user_notes
@notes = NotesFinder.new(project, current_user, params).execute.inc_author @notes = NotesFinder.new(project, current_user, params.merge(last_fetched_at: last_fetched_at))
.execute.inc_author
end
def last_fetched_at
request.headers['X-Last-Fetched-At']
end end
end end
module MattermostHelper module MattermostHelper
def mattermost_teams_options(teams) def mattermost_teams_options(teams)
teams_options = teams.map do |id, options| teams.map do |team|
[options['display_name'] || options['name'], id] [team['display_name'] || team['name'], team['id']]
end end
teams_options.compact.unshift(['Select team...', '0'])
end end
end end
module TriggersHelper module TriggersHelper
def builds_trigger_url(project_id, ref: nil) def builds_trigger_url(project_id, ref: nil)
if ref.nil? if ref.nil?
"#{Settings.gitlab.url}/api/v3/projects/#{project_id}/trigger/builds" "#{Settings.gitlab.url}/api/v4/projects/#{project_id}/trigger/pipeline"
else else
"#{Settings.gitlab.url}/api/v3/projects/#{project_id}/ref/#{ref}/trigger/builds" "#{Settings.gitlab.url}/api/v4/projects/#{project_id}/ref/#{ref}/trigger/pipeline"
end end
end end
......
...@@ -5,10 +5,11 @@ module Ci ...@@ -5,10 +5,11 @@ module Ci
acts_as_paranoid acts_as_paranoid
belongs_to :project, foreign_key: :gl_project_id belongs_to :project, foreign_key: :gl_project_id
belongs_to :owner, class_name: "User"
has_many :trigger_requests, dependent: :destroy has_many :trigger_requests, dependent: :destroy
validates :token, presence: true validates :token, presence: true, uniqueness: true
validates :token, uniqueness: true
before_validation :set_default_values before_validation :set_default_values
...@@ -25,7 +26,11 @@ module Ci ...@@ -25,7 +26,11 @@ module Ci
end end
def short_token def short_token
token[0...10] token[0...4]
end
def can_show_token?(user)
owner.blank? || owner == user
end end
end end
end end
...@@ -85,6 +85,7 @@ class Note < ActiveRecord::Base ...@@ -85,6 +85,7 @@ class Note < ActiveRecord::Base
before_validation :nullify_blank_type, :nullify_blank_line_code before_validation :nullify_blank_type, :nullify_blank_line_code
before_validation :set_discussion_id before_validation :set_discussion_id
after_save :keep_around_commit, unless: :for_personal_snippet? after_save :keep_around_commit, unless: :for_personal_snippet?
after_save :expire_etag_cache
class << self class << self
def model_name def model_name
...@@ -272,4 +273,16 @@ class Note < ActiveRecord::Base ...@@ -272,4 +273,16 @@ class Note < ActiveRecord::Base
self.class.build_discussion_id(noteable_type, noteable_id || commit_id) self.class.build_discussion_id(noteable_type, noteable_id || commit_id)
end end
end end
def expire_etag_cache
return unless for_issue?
key = Gitlab::Routing.url_helpers.namespace_project_noteable_notes_path(
noteable.project.namespace,
noteable.project,
target_type: noteable_type.underscore,
target_id: noteable.id
)
Gitlab::EtagCaching::Store.new.touch(key)
end
end end
...@@ -95,6 +95,7 @@ class User < ActiveRecord::Base ...@@ -95,6 +95,7 @@ class User < ActiveRecord::Base
has_many :todos, dependent: :destroy has_many :todos, dependent: :destroy
has_many :notification_settings, dependent: :destroy has_many :notification_settings, dependent: :destroy
has_many :award_emoji, dependent: :destroy has_many :award_emoji, dependent: :destroy
has_many :triggers, dependent: :destroy, class_name: 'Ci::Trigger', foreign_key: :owner_id
has_many :assigned_issues, dependent: :nullify, foreign_key: :assignee_id, class_name: "Issue" has_many :assigned_issues, dependent: :nullify, foreign_key: :assignee_id, class_name: "Issue"
has_many :assigned_merge_requests, dependent: :nullify, foreign_key: :assignee_id, class_name: "MergeRequest" has_many :assigned_merge_requests, dependent: :nullify, foreign_key: :assignee_id, class_name: "MergeRequest"
......
...@@ -3,7 +3,7 @@ module Ci ...@@ -3,7 +3,7 @@ module Ci
def execute(project, trigger, ref, variables = nil) def execute(project, trigger, ref, variables = nil)
trigger_request = trigger.trigger_requests.create(variables: variables) trigger_request = trigger.trigger_requests.create(variables: variables)
pipeline = Ci::CreatePipelineService.new(project, nil, ref: ref). pipeline = Ci::CreatePipelineService.new(project, trigger.owner, ref: ref).
execute(ignore_skip_ci: true, trigger_request: trigger_request) execute(ignore_skip_ci: true, trigger_request: trigger_request)
if pipeline.persisted? if pipeline.persisted?
trigger_request trigger_request
......
...@@ -34,6 +34,8 @@ module Projects ...@@ -34,6 +34,8 @@ module Projects
end end
rescue => e rescue => e
error(e.message) error(e.message)
ensure
build.erase_artifacts! unless build.has_expiring_artifacts?
end end
private private
......
!!! 5 !!! 5
%html{ lang: "en", class: "#{page_class}" } %html{ lang: "en", class: "#{page_class}" }
= render "layouts/head" = render "layouts/head"
%body{ data: { page: body_data_page, project: "#{@project.path if @project}", group: "#{@group.path if @group}" } } %body{ class: @body_class, data: { page: body_data_page, project: "#{@project.path if @project}", group: "#{@group.path if @group}" } }
= Gon::Base.render_data = Gon::Base.render_data
= render "layouts/header/default", title: header_title = render "layouts/header/default", title: header_title
......
...@@ -93,7 +93,7 @@ ...@@ -93,7 +93,7 @@
%p %p
Changing your username will change path to all personal projects! Changing your username will change path to all personal projects!
.col-lg-9 .col-lg-9
= form_for @user, url: update_username_profile_path, method: :put, remote: true, html: {class: "update-username"} do |f| = form_for @user, url: update_username_profile_path, method: :put, html: {class: "update-username"} do |f|
.form-group .form-group
= f.label :username, "Path", class: "label-light" = f.label :username, "Path", class: "label-light"
.input-group .input-group
......
- if @user.valid?
:plain
new Flash("Username successfully changed", "notice")
- else
- error = @user.errors.full_messages.first
:plain
new Flash("Username change failed - #{escape_javascript error.html_safe}", "alert")
...@@ -2,16 +2,15 @@ ...@@ -2,16 +2,15 @@
This service will be installed on the Mattermost instance at This service will be installed on the Mattermost instance at
%strong= link_to Gitlab.config.mattermost.host, Gitlab.config.mattermost.host %strong= link_to Gitlab.config.mattermost.host, Gitlab.config.mattermost.host
%hr %hr
= form_for(:mattermost, method: :post, url: namespace_project_mattermost_path(@project.namespace, @project)) do |f| = form_for(:mattermost, method: :post, url: namespace_project_mattermost_path(@project.namespace, @project), html: { class: 'js-requires-input'} ) do |f|
%h4 Team %h4 Team
%p %p
= @teams.one? ? 'The team' : 'Select the team' = @teams.one? ? 'The team' : 'Select the team'
where the slash commands will be used in where the slash commands will be used in
- selected_id = @teams.one? ? @teams.keys.first : 0 - selected_id = @teams.one? ? @teams.first['id'] : nil
- options = mattermost_teams_options(@teams) - options = options_for_select(mattermost_teams_options(@teams), selected_id)
- options = options_for_select(options, selected_id) = f.select(:team_id, options, { include_blank: 'Select team...'}, { class: 'form-control', disabled: @teams.one?, selected: selected_id, required: true })
= f.select(:team_id, options, {}, { class: 'form-control', disabled: @teams.one?, selected: selected_id }) = f.hidden_field(:team_id, value: selected_id, required: true) if @teams.one?
= f.hidden_field(:team_id, value: selected_id) if @teams.one?
.help-block .help-block
- if @teams.one? - if @teams.one?
This is the only available team. This is the only available team.
...@@ -25,7 +24,7 @@ ...@@ -25,7 +24,7 @@
%hr %hr
%h4 Command trigger word %h4 Command trigger word
%p Choose the word that will trigger commands %p Choose the word that will trigger commands
= f.text_field(:trigger, value: @project.path, class: 'form-control') = f.text_field(:trigger, value: @project.path, class: 'form-control', required: true)
.help-block .help-block
%p %p
Trigger word must be unique, and can't begin with a slash or contain any spaces. Trigger word must be unique, and can't begin with a slash or contain any spaces.
......
- @body_class = 'card-content'
.service-installation .service-installation
.inline.pull-right .inline.pull-right
= custom_icon('mattermost_logo', size: 48) = custom_icon('mattermost_logo', size: 48)
......
...@@ -23,4 +23,4 @@ ...@@ -23,4 +23,4 @@
to post a comment to post a comment
:javascript :javascript
var notes = new Notes("#{namespace_project_notes_path(namespace_id: @project.namespace, project_id: @project, target_id: @noteable.id, target_type: @noteable.class.name.underscore)}", #{@notes.map(&:id).to_json}, #{Time.now.to_i}, "#{diff_view}") var notes = new Notes("#{namespace_project_noteable_notes_path(namespace_id: @project.namespace, project_id: @project, target_id: @noteable.id, target_type: @noteable.class.name.underscore)}", #{@notes.map(&:id).to_json}, #{Time.now.to_i}, "#{diff_view}")
---
title: Remove remnants of git annex support.
merge_request:
author:
---
title: Update account view to display new username
merge_request:
author:
---
title: Fix json response in branches controller
merge_request: 9710
author: George Andrinopoulos
---
title: Delete artifacts for pages unless expiry date is specified
merge_request: 9716
author:
---
title: Use ETag to improve performance of issue notes polling
merge_request: 9036
author:
---
title: Introduce Pipeline Triggers that are user-aware
merge_request:
author:
# This middleware has to come after Gitlab::Metrics::RackMiddleware
# in the middleware stack, because it tracks events with
# GitLab Performance Monitoring
Rails.application.config.middleware.use(Gitlab::EtagCaching::Middleware)
...@@ -21,7 +21,7 @@ resource :profile, only: [:show, :update] do ...@@ -21,7 +21,7 @@ resource :profile, only: [:show, :update] do
end end
end end
resource :preferences, only: [:show, :update] resource :preferences, only: [:show, :update]
resources :keys, only: [:index, :show, :new, :create, :destroy] resources :keys, only: [:index, :show, :create, :destroy]
resources :emails, only: [:index, :create, :destroy] resources :emails, only: [:index, :create, :destroy]
resources :chat_names, only: [:index, :new, :create, :destroy] do resources :chat_names, only: [:index, :new, :create, :destroy] do
collection do collection do
......
...@@ -267,7 +267,7 @@ constraints(ProjectUrlConstrainer.new) do ...@@ -267,7 +267,7 @@ constraints(ProjectUrlConstrainer.new) do
resources :group_links, only: [:index, :create, :update, :destroy], constraints: { id: /\d+/ } resources :group_links, only: [:index, :create, :update, :destroy], constraints: { id: /\d+/ }
resources :notes, only: [:index, :create, :destroy, :update], concerns: :awardable, constraints: { id: /\d+/ } do resources :notes, only: [:create, :destroy, :update], concerns: :awardable, constraints: { id: /\d+/ } do
member do member do
delete :delete_attachment delete :delete_attachment
post :resolve post :resolve
...@@ -275,6 +275,8 @@ constraints(ProjectUrlConstrainer.new) do ...@@ -275,6 +275,8 @@ constraints(ProjectUrlConstrainer.new) do
end end
end end
get 'noteable/:target_type/:target_id/notes' => 'notes#index', as: 'noteable_notes'
resources :boards, only: [:index, :show] do resources :boards, only: [:index, :show] do
scope module: :boards do scope module: :boards do
resources :issues, only: [:index, :update] resources :issues, only: [:index, :update]
......
class AddOwnerIdToTriggers < ActiveRecord::Migration
include Gitlab::Database::MigrationHelpers
DOWNTIME = false
def change
add_column :ci_triggers, :owner_id, :integer
end
end
class AddDescriptionToTriggers < ActiveRecord::Migration
include Gitlab::Database::MigrationHelpers
DOWNTIME = false
def change
add_column :ci_triggers, :description, :string
end
end
class AddOwnerIdForeignKey < ActiveRecord::Migration
include Gitlab::Database::MigrationHelpers
DOWNTIME = false
disable_ddl_transaction!
def change
add_concurrent_foreign_key :ci_triggers, :users, column: :owner_id, on_delete: :cascade
end
end
...@@ -11,7 +11,7 @@ ...@@ -11,7 +11,7 @@
# #
# It's strongly recommended that you check this file into your version control system. # It's strongly recommended that you check this file into your version control system.
ActiveRecord::Schema.define(version: 20170217151947) do ActiveRecord::Schema.define(version: 20170305203726) do
# These are extensions that must be enabled in order to support this database # These are extensions that must be enabled in order to support this database
enable_extension "plpgsql" enable_extension "plpgsql"
...@@ -111,7 +111,7 @@ ActiveRecord::Schema.define(version: 20170217151947) do ...@@ -111,7 +111,7 @@ ActiveRecord::Schema.define(version: 20170217151947) do
t.boolean "plantuml_enabled" t.boolean "plantuml_enabled"
t.integer "max_pages_size", default: 100, null: false t.integer "max_pages_size", default: 100, null: false
t.integer "terminal_max_session_time", default: 0, null: false t.integer "terminal_max_session_time", default: 0, null: false
t.string "default_artifacts_expire_in", default: '0', null: false t.string "default_artifacts_expire_in", default: "0", null: false
end end
create_table "audit_events", force: :cascade do |t| create_table "audit_events", force: :cascade do |t|
...@@ -387,6 +387,8 @@ ActiveRecord::Schema.define(version: 20170217151947) do ...@@ -387,6 +387,8 @@ ActiveRecord::Schema.define(version: 20170217151947) do
t.datetime "created_at" t.datetime "created_at"
t.datetime "updated_at" t.datetime "updated_at"
t.integer "gl_project_id" t.integer "gl_project_id"
t.integer "owner_id"
t.string "description"
end end
add_index "ci_triggers", ["gl_project_id"], name: "index_ci_triggers_on_gl_project_id", using: :btree add_index "ci_triggers", ["gl_project_id"], name: "index_ci_triggers_on_gl_project_id", using: :btree
...@@ -1344,6 +1346,7 @@ ActiveRecord::Schema.define(version: 20170217151947) do ...@@ -1344,6 +1346,7 @@ ActiveRecord::Schema.define(version: 20170217151947) do
add_foreign_key "boards", "projects" add_foreign_key "boards", "projects"
add_foreign_key "chat_teams", "namespaces", on_delete: :cascade add_foreign_key "chat_teams", "namespaces", on_delete: :cascade
add_foreign_key "ci_triggers", "users", column: "owner_id", name: "fk_e8e10d1964", on_delete: :cascade
add_foreign_key "issue_metrics", "issues", on_delete: :cascade add_foreign_key "issue_metrics", "issues", on_delete: :cascade
add_foreign_key "label_priorities", "labels", on_delete: :cascade add_foreign_key "label_priorities", "labels", on_delete: :cascade
add_foreign_key "label_priorities", "projects", on_delete: :cascade add_foreign_key "label_priorities", "projects", on_delete: :cascade
......
...@@ -12,7 +12,6 @@ following locations: ...@@ -12,7 +12,6 @@ following locations:
- [Branches](branches.md) - [Branches](branches.md)
- [Broadcast Messages](broadcast_messages.md) - [Broadcast Messages](broadcast_messages.md)
- [Builds](builds.md) - [Builds](builds.md)
- [Build Triggers](build_triggers.md)
- [Build Variables](build_variables.md) - [Build Variables](build_variables.md)
- [Commits](commits.md) - [Commits](commits.md)
- [Deployments](deployments.md) - [Deployments](deployments.md)
...@@ -33,6 +32,7 @@ following locations: ...@@ -33,6 +32,7 @@ following locations:
- [Notes](notes.md) (comments) - [Notes](notes.md) (comments)
- [Notification settings](notification_settings.md) - [Notification settings](notification_settings.md)
- [Pipelines](pipelines.md) - [Pipelines](pipelines.md)
- [Pipeline Triggers](pipeline_triggers.md)
- [Projects](projects.md) including setting Webhooks - [Projects](projects.md) including setting Webhooks
- [Project Access Requests](access_requests.md) - [Project Access Requests](access_requests.md)
- [Project Members](members.md) - [Project Members](members.md)
......
# Build triggers This document was moved to [Pipeline Triggers](pipeline_triggers.md).
You can read more about [triggering builds through the API](../ci/triggers/README.md).
## List project triggers
Get a list of project's build triggers.
```
GET /projects/:id/triggers
```
| Attribute | Type | required | Description |
|-----------|---------|----------|---------------------|
| `id` | integer | yes | The ID of a project |
```
curl --header "PRIVATE-TOKEN: 9koXpg98eAheJpvBs5tK" "https://gitlab.example.com/api/v4/projects/1/triggers"
```
```json
[
{
"created_at": "2015-12-23T16:24:34.716Z",
"deleted_at": null,
"last_used": "2016-01-04T15:41:21.986Z",
"token": "fbdb730c2fbdb095a0862dbd8ab88b",
"updated_at": "2015-12-23T16:24:34.716Z"
},
{
"created_at": "2015-12-23T16:25:56.760Z",
"deleted_at": null,
"last_used": null,
"token": "7b9148c158980bbd9bcea92c17522d",
"updated_at": "2015-12-23T16:25:56.760Z"
}
]
```
## Get trigger details
Get details of project's build trigger.
```
GET /projects/:id/triggers/:token
```
| Attribute | Type | required | Description |
|-----------|---------|----------|--------------------------|
| `id` | integer | yes | The ID of a project |
| `token` | string | yes | The `token` of a trigger |
```
curl --header "PRIVATE-TOKEN: 9koXpg98eAheJpvBs5tK" "https://gitlab.example.com/api/v4/projects/1/triggers/7b9148c158980bbd9bcea92c17522d"
```
```json
{
"created_at": "2015-12-23T16:25:56.760Z",
"deleted_at": null,
"last_used": null,
"token": "7b9148c158980bbd9bcea92c17522d",
"updated_at": "2015-12-23T16:25:56.760Z"
}
```
## Create a project trigger
Create a build trigger for a project.
```
POST /projects/:id/triggers
```
| Attribute | Type | required | Description |
|-----------|---------|----------|--------------------------|
| `id` | integer | yes | The ID of a project |
```
curl --request POST --header "PRIVATE-TOKEN: 9koXpg98eAheJpvBs5tK" "https://gitlab.example.com/api/v4/projects/1/triggers"
```
```json
{
"created_at": "2016-01-07T09:53:58.235Z",
"deleted_at": null,
"last_used": null,
"token": "6d056f63e50fe6f8c5f8f4aa10edb7",
"updated_at": "2016-01-07T09:53:58.235Z"
}
```
## Remove a project trigger
Remove a project's build trigger.
```
DELETE /projects/:id/triggers/:token
```
| Attribute | Type | required | Description |
|-----------|---------|----------|--------------------------|
| `id` | integer | yes | The ID of a project |
| `token` | string | yes | The `token` of a trigger |
```
curl --request DELETE --header "PRIVATE-TOKEN: 9koXpg98eAheJpvBs5tK" "https://gitlab.example.com/api/v4/projects/1/triggers/7b9148c158980bbd9bcea92c17522d"
```
# Pipeline triggers
You can read more about [triggering pipelines through the API](../ci/triggers/README.md).
## List project triggers
Get a list of project's build triggers.
```
GET /projects/:id/triggers
```
| Attribute | Type | required | Description |
|-----------|---------|----------|---------------------|
| `id` | integer | yes | The ID of a project |
```
curl --header "PRIVATE-TOKEN: 9koXpg98eAheJpvBs5tK" "https://gitlab.example.com/api/v4/projects/1/triggers"
```
```json
[
{
"id": 10,
"description": "my trigger",
"created_at": "2016-01-07T09:53:58.235Z",
"deleted_at": null,
"last_used": null,
"token": "6d056f63e50fe6f8c5f8f4aa10edb7",
"updated_at": "2016-01-07T09:53:58.235Z",
"owner": null
}
]
```
## Get trigger details
Get details of project's build trigger.
```
GET /projects/:id/triggers/:trigger_id
```
| Attribute | Type | required | Description |
|-----------|---------|----------|--------------------------|
| `id` | integer | yes | The ID of a project |
| `token` | string | yes | The `token` of a trigger |
```
curl --header "PRIVATE-TOKEN: 9koXpg98eAheJpvBs5tK" "https://gitlab.example.com/api/v4/projects/1/triggers/5"
```
```json
{
"id": 10,
"description": "my trigger",
"created_at": "2016-01-07T09:53:58.235Z",
"deleted_at": null,
"last_used": null,
"token": "6d056f63e50fe6f8c5f8f4aa10edb7",
"updated_at": "2016-01-07T09:53:58.235Z",
"owner": null
}
```
## Create a project trigger
Create a trigger for a project.
```
POST /projects/:id/triggers
```
| Attribute | Type | required | Description |
|---------------|---------|----------|--------------------------|
| `id` | integer | yes | The ID of a project |
| `description` | string | yes | The trigger name |
```
curl --request POST --header "PRIVATE-TOKEN: 9koXpg98eAheJpvBs5tK" --form description="my description" "https://gitlab.example.com/api/v4/projects/1/triggers"
```
```json
{
"id": 10,
"description": "my trigger",
"created_at": "2016-01-07T09:53:58.235Z",
"deleted_at": null,
"last_used": null,
"token": "6d056f63e50fe6f8c5f8f4aa10edb7",
"updated_at": "2016-01-07T09:53:58.235Z",
"owner": null
}
```
## Update a project trigger
Update a trigger for a project.
```
PUT /projects/:id/triggers/:trigger_id
```
| Attribute | Type | required | Description |
|---------------|---------|----------|--------------------------|
| `trigger_id` | integer | yes | The trigger id |
| `description` | string | no | The trigger name |
```
curl --request PUT --header "PRIVATE-TOKEN: 9koXpg98eAheJpvBs5tK" --form description="my description" "https://gitlab.example.com/api/v4/projects/1/triggers/10"
```
```json
{
"id": 10,
"description": "my trigger",
"created_at": "2016-01-07T09:53:58.235Z",
"deleted_at": null,
"last_used": null,
"token": "6d056f63e50fe6f8c5f8f4aa10edb7",
"updated_at": "2016-01-07T09:53:58.235Z",
"owner": null
}
```
## Take ownership of a project trigger
Update an owner of a project trigger.
```
POST /projects/:id/triggers/:trigger_id/take_ownership
```
| Attribute | Type | required | Description |
|---------------|---------|----------|--------------------------|
| `trigger_id` | integer | yes | The trigger id |
```
curl --request POST --header "PRIVATE-TOKEN: 9koXpg98eAheJpvBs5tK" "https://gitlab.example.com/api/v4/projects/1/triggers/10/take_ownership"
```
```json
{
"id": 10,
"description": "my trigger",
"created_at": "2016-01-07T09:53:58.235Z",
"deleted_at": null,
"last_used": null,
"token": "6d056f63e50fe6f8c5f8f4aa10edb7",
"updated_at": "2016-01-07T09:53:58.235Z",
"owner": null
}
```
## Remove a project trigger
Remove a project's build trigger.
```
DELETE /projects/:id/triggers/:trigger_id
```
| Attribute | Type | required | Description |
|----------------|---------|----------|--------------------------|
| `id` | integer | yes | The ID of a project |
| `trigger_id` | integer | yes | The trigger id |
```
curl --request DELETE --header "PRIVATE-TOKEN: 9koXpg98eAheJpvBs5tK" "https://gitlab.example.com/api/v4/projects/1/triggers/5"
```
...@@ -59,3 +59,6 @@ changes are in V4: ...@@ -59,3 +59,6 @@ changes are in V4:
- Return 202 with JSON body on async removals on V4 API (DELETE `/projects/:id/repository/merged_branches` and DELETE `/projects/:id`) [!9449](https://gitlab.com/gitlab-org/gitlab-ce/merge_requests/9449) - Return 202 with JSON body on async removals on V4 API (DELETE `/projects/:id/repository/merged_branches` and DELETE `/projects/:id`) [!9449](https://gitlab.com/gitlab-org/gitlab-ce/merge_requests/9449)
- `projects/:id/milestones?iid[]=x&iid[]=y` array filter has been renamed to `iids` [!9096](https://gitlab.com/gitlab-org/gitlab-ce/merge_requests/9096) - `projects/:id/milestones?iid[]=x&iid[]=y` array filter has been renamed to `iids` [!9096](https://gitlab.com/gitlab-org/gitlab-ce/merge_requests/9096)
- Return basic info about pipeline in `GET /projects/:id/pipelines` [!8875](https://gitlab.com/gitlab-org/gitlab-ce/merge_requests/8875) - Return basic info about pipeline in `GET /projects/:id/pipelines` [!8875](https://gitlab.com/gitlab-org/gitlab-ce/merge_requests/8875)
- Rename Build Triggers to be Pipeline Triggers API [!9713](https://gitlab.com/gitlab-org/gitlab-ce/merge_requests/9713)
- `POST /projects/:id/trigger/builds` to `POST /projects/:id/trigger/pipeline`
- Require description when creating a new trigger `POST /projects/:id/triggers`
...@@ -36,7 +36,7 @@ it will not trigger a job. ...@@ -36,7 +36,7 @@ it will not trigger a job.
To trigger a job you need to send a `POST` request to GitLab's API endpoint: To trigger a job you need to send a `POST` request to GitLab's API endpoint:
``` ```
POST /projects/:id/trigger/builds POST /projects/:id/trigger/pipeline
``` ```
The required parameters are the trigger's `token` and the Git `ref` on which The required parameters are the trigger's `token` and the Git `ref` on which
...@@ -71,7 +71,7 @@ To trigger a job from webhook of another project you need to add the following ...@@ -71,7 +71,7 @@ To trigger a job from webhook of another project you need to add the following
webhook url for Push and Tag push events: webhook url for Push and Tag push events:
``` ```
https://gitlab.example.com/api/v4/projects/:id/ref/:ref/trigger/builds?token=TOKEN https://gitlab.example.com/api/v4/projects/:id/ref/:ref/trigger/pipeline?token=TOKEN
``` ```
> **Note**: > **Note**:
...@@ -105,7 +105,7 @@ Using cURL you can trigger a rebuild with minimal effort, for example: ...@@ -105,7 +105,7 @@ Using cURL you can trigger a rebuild with minimal effort, for example:
curl --request POST \ curl --request POST \
--form token=TOKEN \ --form token=TOKEN \
--form ref=master \ --form ref=master \
https://gitlab.example.com/api/v4/projects/9/trigger/builds https://gitlab.example.com/api/v4/projects/9/trigger/pipeline
``` ```
In this case, the project with ID `9` will get rebuilt on `master` branch. In this case, the project with ID `9` will get rebuilt on `master` branch.
...@@ -114,7 +114,7 @@ Alternatively, you can pass the `token` and `ref` arguments in the query string: ...@@ -114,7 +114,7 @@ Alternatively, you can pass the `token` and `ref` arguments in the query string:
```bash ```bash
curl --request POST \ curl --request POST \
"https://gitlab.example.com/api/v4/projects/9/trigger/builds?token=TOKEN&ref=master" "https://gitlab.example.com/api/v4/projects/9/trigger/pipeline?token=TOKEN&ref=master"
``` ```
### Triggering a job within `.gitlab-ci.yml` ### Triggering a job within `.gitlab-ci.yml`
...@@ -128,7 +128,7 @@ need to add in project's A `.gitlab-ci.yml`: ...@@ -128,7 +128,7 @@ need to add in project's A `.gitlab-ci.yml`:
build_docs: build_docs:
stage: deploy stage: deploy
script: script:
- "curl --request POST --form token=TOKEN --form ref=master https://gitlab.example.com/api/v4/projects/9/trigger/builds" - "curl --request POST --form token=TOKEN --form ref=master https://gitlab.example.com/api/v4/projects/9/trigger/pipeline"
only: only:
- tags - tags
``` ```
...@@ -187,7 +187,7 @@ curl --request POST \ ...@@ -187,7 +187,7 @@ curl --request POST \
--form token=TOKEN \ --form token=TOKEN \
--form ref=master \ --form ref=master \
--form "variables[UPLOAD_TO_S3]=true" \ --form "variables[UPLOAD_TO_S3]=true" \
https://gitlab.example.com/api/v4/projects/9/trigger/builds https://gitlab.example.com/api/v4/projects/9/trigger/pipeline
``` ```
### Using webhook to trigger job ### Using webhook to trigger job
...@@ -195,7 +195,7 @@ curl --request POST \ ...@@ -195,7 +195,7 @@ curl --request POST \
You can add the following webhook to another project in order to trigger a job: You can add the following webhook to another project in order to trigger a job:
``` ```
https://gitlab.example.com/api/v4/projects/9/ref/master/trigger/builds?token=TOKEN&variables[UPLOAD_TO_S3]=true https://gitlab.example.com/api/v4/projects/9/ref/master/trigger/pipeline?token=TOKEN&variables[UPLOAD_TO_S3]=true
``` ```
### Using cron to trigger nightly jobs ### Using cron to trigger nightly jobs
...@@ -205,7 +205,7 @@ in conjunction with cron. The example below triggers a job on the `master` ...@@ -205,7 +205,7 @@ in conjunction with cron. The example below triggers a job on the `master`
branch of project with ID `9` every night at `00:30`: branch of project with ID `9` every night at `00:30`:
```bash ```bash
30 0 * * * curl --request POST --form token=TOKEN --form ref=master https://gitlab.example.com/api/v4/projects/9/trigger/builds 30 0 * * * curl --request POST --form token=TOKEN --form ref=master https://gitlab.example.com/api/v4/projects/9/trigger/pipeline
``` ```
[ci-229]: https://gitlab.com/gitlab-org/gitlab-ci/merge_requests/229 [ci-229]: https://gitlab.com/gitlab-org/gitlab-ci/merge_requests/229
...@@ -308,8 +308,8 @@ Running on runner-8a2f473d-project-1796893-concurrent-0 via runner-8a2f473d-mach ...@@ -308,8 +308,8 @@ Running on runner-8a2f473d-project-1796893-concurrent-0 via runner-8a2f473d-mach
++ CI_RUNNER_ID=1337 ++ CI_RUNNER_ID=1337
++ export CI_RUNNER_DESCRIPTION=shared-runners-manager-1.example.com ++ export CI_RUNNER_DESCRIPTION=shared-runners-manager-1.example.com
++ CI_RUNNER_DESCRIPTION=shared-runners-manager-1.example.com ++ CI_RUNNER_DESCRIPTION=shared-runners-manager-1.example.com
++ export 'CI_RUNNER_TAGS=shared, docker, linux, ruby, mysql, postgres, mongo, git-annex' ++ export 'CI_RUNNER_TAGS=shared, docker, linux, ruby, mysql, postgres, mongo'
++ CI_RUNNER_TAGS='shared, docker, linux, ruby, mysql, postgres, mongo, git-annex' ++ CI_RUNNER_TAGS='shared, docker, linux, ruby, mysql, postgres, mongo'
++ export CI_REGISTRY=registry.example.com ++ export CI_REGISTRY=registry.example.com
++ CI_REGISTRY=registry.example.com ++ CI_REGISTRY=registry.example.com
++ export CI_DEBUG_TRACE=true ++ export CI_DEBUG_TRACE=true
......
...@@ -35,7 +35,7 @@ Using this method is in general preferred over directly calling the various ...@@ -35,7 +35,7 @@ Using this method is in general preferred over directly calling the various
instrumentation methods. instrumentation methods.
Method instrumentation should be added in the initializer Method instrumentation should be added in the initializer
`config/initializers/metrics.rb`. `config/initializers/8_metrics.rb`.
### Examples ### Examples
......
...@@ -15,13 +15,6 @@ Kerberos and Atlassian Crowd are only available on the Enterprise Edition, so ...@@ -15,13 +15,6 @@ Kerberos and Atlassian Crowd are only available on the Enterprise Edition, so
you should disable these mechanisms before downgrading and you should provide you should disable these mechanisms before downgrading and you should provide
alternative authentication methods to your users. alternative authentication methods to your users.
### Git Annex
Git Annex is also only available on the Enterprise Edition. This means that if
you have repositories that use Git Annex to store large files, these files will
no longer be easily available via Git. You should consider migrating these
repositories to use Git LFS before downgrading to the Community Edition.
### Remove Jenkins CI Service entries from the database ### Remove Jenkins CI Service entries from the database
The `JenkinsService` class is only available on the Enterprise Edition codebase, The `JenkinsService` class is only available on the Enterprise Edition codebase,
......
...@@ -165,7 +165,7 @@ The curriculum is composed of GitLab videos, screencasts, presentations, project ...@@ -165,7 +165,7 @@ The curriculum is composed of GitLab videos, screencasts, presentations, project
#### 3.4. Large Files #### 3.4. Large Files
1. [Big files in Git (Git LFS, Annex) - Video](https://www.youtube.com/watch?v=DawznUxYDe4) 1. [Big files in Git (Git LFS) - Video](https://www.youtube.com/watch?v=DawznUxYDe4)
#### 3.5. LDAP and Active Directory #### 3.5. LDAP and Active Directory
......
...@@ -167,7 +167,6 @@ Some tickets need specific knowledge or a deep understanding of a particular com ...@@ -167,7 +167,6 @@ Some tickets need specific knowledge or a deep understanding of a particular com
Move on to understanding some of GitLab's more advanced features. You can make use of GitLab.com to understand the features from an end-user perspective and then use your own instance to understand setup and configuration of the feature from an Administrative perspective Move on to understanding some of GitLab's more advanced features. You can make use of GitLab.com to understand the features from an end-user perspective and then use your own instance to understand setup and configuration of the feature from an Administrative perspective
- Set up and try [Git Annex](https://docs.gitlab.com/ee/workflow/git_annex.html)
- Set up and try [Git LFS](https://docs.gitlab.com/ee/workflow/lfs/manage_large_binaries_with_git_lfs.html) - Set up and try [Git LFS](https://docs.gitlab.com/ee/workflow/lfs/manage_large_binaries_with_git_lfs.html)
- Get to know the [GitLab API](https://docs.gitlab.com/ee/api/README.html), its capabilities and shortcomings - Get to know the [GitLab API](https://docs.gitlab.com/ee/api/README.html), its capabilities and shortcomings
- Learn how to [migrate from SVN to Git](https://docs.gitlab.com/ee/workflow/importing/migrating_from_svn.html) - Learn how to [migrate from SVN to Git](https://docs.gitlab.com/ee/workflow/importing/migrating_from_svn.html)
......
...@@ -133,6 +133,9 @@ your Jekyll 3.4.0 site with GitLab Pages. This is the minimum ...@@ -133,6 +133,9 @@ your Jekyll 3.4.0 site with GitLab Pages. This is the minimum
configuration for our example. On the steps below, we'll refine configuration for our example. On the steps below, we'll refine
the script by adding extra options to our GitLab CI. the script by adding extra options to our GitLab CI.
Artifacts will be automatically deleted once GitLab Pages got deployed.
You can preserve artifacts for limited time by specifying the expiry time.
### Image ### Image
At this point, you probably ask yourself: "okay, but to install Jekyll At this point, you probably ask yourself: "okay, but to install Jekyll
......
...@@ -4,13 +4,6 @@ Managing large files such as audio, video and graphics files has always been one ...@@ -4,13 +4,6 @@ Managing large files such as audio, video and graphics files has always been one
of the shortcomings of Git. The general recommendation is to not have Git repositories of the shortcomings of Git. The general recommendation is to not have Git repositories
larger than 1GB to preserve performance. larger than 1GB to preserve performance.
GitLab already supports [managing large files with git annex](http://docs.gitlab.com/ee/workflow/git_annex.html)
(EE only), however in certain environments it is not always convenient to use
different commands to differentiate between the large files and regular ones.
Git LFS makes this simpler for the end user by removing the requirement to
learn new commands.
## How it works ## How it works
Git LFS client talks with the GitLab server over HTTPS. It uses HTTP Basic Authentication Git LFS client talks with the GitLab server over HTTPS. It uses HTTP Basic Authentication
......
...@@ -592,10 +592,6 @@ module API ...@@ -592,10 +592,6 @@ module API
end end
end end
class TriggerRequest < Grape::Entity
expose :id, :variables
end
class Runner < Grape::Entity class Runner < Grape::Entity
expose :id expose :id
expose :description expose :description
...@@ -643,7 +639,10 @@ module API ...@@ -643,7 +639,10 @@ module API
end end
class Trigger < Grape::Entity class Trigger < Grape::Entity
expose :token, :created_at, :updated_at, :deleted_at, :last_used expose :id
expose :token, :description
expose :created_at, :updated_at, :deleted_at, :last_used
expose :owner, using: Entities::UserBasic
end end
class Variable < Grape::Entity class Variable < Grape::Entity
......
...@@ -6,15 +6,15 @@ module API ...@@ -6,15 +6,15 @@ module API
requires :id, type: String, desc: 'The ID of a project' requires :id, type: String, desc: 'The ID of a project'
end end
resource :projects do resource :projects do
desc 'Trigger a GitLab project build' do desc 'Trigger a GitLab project pipeline' do
success Entities::TriggerRequest success Entities::Pipeline
end end
params do params do
requires :ref, type: String, desc: 'The commit sha or name of a branch or tag' requires :ref, type: String, desc: 'The commit sha or name of a branch or tag'
requires :token, type: String, desc: 'The unique token of trigger' requires :token, type: String, desc: 'The unique token of trigger'
optional :variables, type: Hash, desc: 'The list of variables to be injected into build' optional :variables, type: Hash, desc: 'The list of variables to be injected into build'
end end
post ":id/(ref/:ref/)trigger/builds" do post ":id/(ref/:ref/)trigger/pipeline" do
project = find_project(params[:id]) project = find_project(params[:id])
trigger = Ci::Trigger.find_by_token(params[:token].to_s) trigger = Ci::Trigger.find_by_token(params[:token].to_s)
not_found! unless project && trigger not_found! unless project && trigger
...@@ -29,9 +29,9 @@ module API ...@@ -29,9 +29,9 @@ module API
# create request and trigger builds # create request and trigger builds
trigger_request = Ci::CreateTriggerRequestService.new.execute(project, trigger, params[:ref].to_s, variables) trigger_request = Ci::CreateTriggerRequestService.new.execute(project, trigger, params[:ref].to_s, variables)
if trigger_request if trigger_request
present trigger_request, with: Entities::TriggerRequest present trigger_request.pipeline, with: Entities::Pipeline
else else
errors = 'No builds created' errors = 'No pipeline created'
render_api_error!(errors, 400) render_api_error!(errors, 400)
end end
end end
...@@ -55,13 +55,13 @@ module API ...@@ -55,13 +55,13 @@ module API
success Entities::Trigger success Entities::Trigger
end end
params do params do
requires :token, type: String, desc: 'The unique token of trigger' requires :trigger_id, type: Integer, desc: 'The trigger ID'
end end
get ':id/triggers/:token' do get ':id/triggers/:trigger_id' do
authenticate! authenticate!
authorize! :admin_build, user_project authorize! :admin_build, user_project
trigger = user_project.triggers.find_by(token: params[:token].to_s) trigger = user_project.triggers.find(params.delete(:trigger_id))
return not_found!('Trigger') unless trigger return not_found!('Trigger') unless trigger
present trigger, with: Entities::Trigger present trigger, with: Entities::Trigger
...@@ -70,26 +70,76 @@ module API ...@@ -70,26 +70,76 @@ module API
desc 'Create a trigger' do desc 'Create a trigger' do
success Entities::Trigger success Entities::Trigger
end end
params do
requires :description, type: String, desc: 'The trigger description'
end
post ':id/triggers' do post ':id/triggers' do
authenticate! authenticate!
authorize! :admin_build, user_project authorize! :admin_build, user_project
trigger = user_project.triggers.create trigger = user_project.triggers.create(
declared_params(include_missing: false).merge(owner: current_user))
present trigger, with: Entities::Trigger if trigger.valid?
present trigger, with: Entities::Trigger
else
render_validation_error!(trigger)
end
end
desc 'Update a trigger' do
success Entities::Trigger
end
params do
requires :trigger_id, type: Integer, desc: 'The trigger ID'
optional :description, type: String, desc: 'The trigger description'
end
put ':id/triggers/:trigger_id' do
authenticate!
authorize! :admin_build, user_project
trigger = user_project.triggers.find(params.delete(:trigger_id))
return not_found!('Trigger') unless trigger
if trigger.update(declared_params(include_missing: false))
present trigger, with: Entities::Trigger
else
render_validation_error!(trigger)
end
end
desc 'Take ownership of trigger' do
success Entities::Trigger
end
params do
requires :trigger_id, type: Integer, desc: 'The trigger ID'
end
post ':id/triggers/:trigger_id/take_ownership' do
authenticate!
authorize! :admin_build, user_project
trigger = user_project.triggers.find(params.delete(:trigger_id))
return not_found!('Trigger') unless trigger
if trigger.update(owner: current_user)
status :ok
present trigger, with: Entities::Trigger
else
render_validation_error!(trigger)
end
end end
desc 'Delete a trigger' do desc 'Delete a trigger' do
success Entities::Trigger success Entities::Trigger
end end
params do params do
requires :token, type: String, desc: 'The unique token of trigger' requires :trigger_id, type: Integer, desc: 'The trigger ID'
end end
delete ':id/triggers/:token' do delete ':id/triggers/:trigger_id' do
authenticate! authenticate!
authorize! :admin_build, user_project authorize! :admin_build, user_project
trigger = user_project.triggers.find_by(token: params[:token].to_s) trigger = user_project.triggers.find(params.delete(:trigger_id))
return not_found!('Trigger') unless trigger return not_found!('Trigger') unless trigger
trigger.destroy trigger.destroy
......
...@@ -186,6 +186,15 @@ module API ...@@ -186,6 +186,15 @@ module API
class Environment < ::API::Entities::EnvironmentBasic class Environment < ::API::Entities::EnvironmentBasic
expose :project, using: Entities::Project expose :project, using: Entities::Project
end end
class Trigger < Grape::Entity
expose :token, :created_at, :updated_at, :deleted_at, :last_used
expose :owner, using: ::API::Entities::UserBasic
end
class TriggerRequest < Grape::Entity
expose :id, :variables
end
end end
end end
end end
...@@ -7,8 +7,81 @@ module API ...@@ -7,8 +7,81 @@ module API
requires :id, type: String, desc: 'The ID of a project' requires :id, type: String, desc: 'The ID of a project'
end end
resource :projects do resource :projects do
desc 'Trigger a GitLab project build' do
success ::API::V3::Entities::TriggerRequest
end
params do
requires :ref, type: String, desc: 'The commit sha or name of a branch or tag'
requires :token, type: String, desc: 'The unique token of trigger'
optional :variables, type: Hash, desc: 'The list of variables to be injected into build'
end
post ":id/(ref/:ref/)trigger/builds" do
project = find_project(params[:id])
trigger = Ci::Trigger.find_by_token(params[:token].to_s)
not_found! unless project && trigger
unauthorized! unless trigger.project == project
# validate variables
variables = params[:variables].to_h
unless variables.all? { |key, value| key.is_a?(String) && value.is_a?(String) }
render_api_error!('variables needs to be a map of key-valued strings', 400)
end
# create request and trigger builds
trigger_request = Ci::CreateTriggerRequestService.new.execute(project, trigger, params[:ref].to_s, variables)
if trigger_request
present trigger_request, with: ::API::V3::Entities::TriggerRequest
else
errors = 'No builds created'
render_api_error!(errors, 400)
end
end
desc 'Get triggers list' do
success ::API::V3::Entities::Trigger
end
params do
use :pagination
end
get ':id/triggers' do
authenticate!
authorize! :admin_build, user_project
triggers = user_project.triggers.includes(:trigger_requests)
present paginate(triggers), with: ::API::V3::Entities::Trigger
end
desc 'Get specific trigger of a project' do
success ::API::V3::Entities::Trigger
end
params do
requires :token, type: String, desc: 'The unique token of trigger'
end
get ':id/triggers/:token' do
authenticate!
authorize! :admin_build, user_project
trigger = user_project.triggers.find_by(token: params[:token].to_s)
return not_found!('Trigger') unless trigger
present trigger, with: ::API::V3::Entities::Trigger
end
desc 'Create a trigger' do
success ::API::V3::Entities::Trigger
end
post ':id/triggers' do
authenticate!
authorize! :admin_build, user_project
trigger = user_project.triggers.create
present trigger, with: ::API::V3::Entities::Trigger
end
desc 'Delete a trigger' do desc 'Delete a trigger' do
success ::API::Entities::Trigger success ::API::V3::Entities::Trigger
end end
params do params do
requires :token, type: String, desc: 'The unique token of trigger' requires :token, type: String, desc: 'The unique token of trigger'
...@@ -22,7 +95,7 @@ module API ...@@ -22,7 +95,7 @@ module API
trigger.destroy trigger.destroy
present trigger, with: ::API::Entities::Trigger present trigger, with: ::API::V3::Entities::Trigger
end end
end end
end end
......
...@@ -180,9 +180,8 @@ module Backup ...@@ -180,9 +180,8 @@ module Backup
return unless Dir.exist?(path) return unless Dir.exist?(path)
dir_entries = Dir.entries(path) dir_entries = Dir.entries(path)
%w[annex custom_hooks].each do |entry|
yield(entry) if dir_entries.include?(entry) yield('custom_hooks') if dir_entries.include?('custom_hooks')
end
end end
def prepare def prepare
......
module Gitlab
module EtagCaching
class Middleware
RESERVED_WORDS = ProjectPathValidator::RESERVED.map { |word| "/#{word}/" }.join('|')
ROUTE_REGEXP = Regexp.union(
%r(^(?!.*(#{RESERVED_WORDS})).*/noteable/issue/\d+/notes\z)
)
def initialize(app)
@app = app
end
def call(env)
return @app.call(env) unless enabled_for_current_route?(env)
Gitlab::Metrics.add_event(:etag_caching_middleware_used)
etag, cached_value_present = get_etag(env)
if_none_match = env['HTTP_IF_NONE_MATCH']
if if_none_match == etag
Gitlab::Metrics.add_event(:etag_caching_cache_hit)
[304, { 'ETag' => etag }, ['']]
else
track_cache_miss(if_none_match, cached_value_present)
status, headers, body = @app.call(env)
headers['ETag'] = etag
[status, headers, body]
end
end
private
def enabled_for_current_route?(env)
ROUTE_REGEXP.match(env['PATH_INFO'])
end
def get_etag(env)
cache_key = env['PATH_INFO']
store = Store.new
current_value = store.get(cache_key)
cached_value_present = current_value.present?
unless cached_value_present
current_value = store.touch(cache_key, only_if_missing: true)
end
[weak_etag_format(current_value), cached_value_present]
end
def weak_etag_format(value)
%Q{W/"#{value}"}
end
def track_cache_miss(if_none_match, cached_value_present)
if if_none_match.blank?
Gitlab::Metrics.add_event(:etag_caching_header_missing)
elsif !cached_value_present
Gitlab::Metrics.add_event(:etag_caching_key_not_found)
else
Gitlab::Metrics.add_event(:etag_caching_resource_changed)
end
end
end
end
end
module Gitlab
module EtagCaching
class Store
EXPIRY_TIME = 10.minutes
REDIS_NAMESPACE = 'etag:'.freeze
def get(key)
Gitlab::Redis.with { |redis| redis.get(redis_key(key)) }
end
def touch(key, only_if_missing: false)
etag = generate_etag
Gitlab::Redis.with do |redis|
redis.set(redis_key(key), etag, ex: EXPIRY_TIME, nx: only_if_missing)
end
etag
end
private
def generate_etag
SecureRandom.hex
end
def redis_key(key)
"#{REDIS_NAMESPACE}#{key}"
end
end
end
end
...@@ -2,7 +2,7 @@ module Mattermost ...@@ -2,7 +2,7 @@ module Mattermost
class Team < Client class Team < Client
# Returns **all** teams for an admin # Returns **all** teams for an admin
def all def all
session_get('/api/v3/teams/all') session_get('/api/v3/teams/all').values
end end
# Creates a team on the linked Mattermost instance, the team admin will be the # Creates a team on the linked Mattermost instance, the team admin will be the
......
...@@ -3,16 +3,6 @@ require 'spec_helper' ...@@ -3,16 +3,6 @@ require 'spec_helper'
describe Profiles::KeysController do describe Profiles::KeysController do
let(:user) { create(:user) } let(:user) { create(:user) }
describe '#new' do
before { sign_in(user) }
it 'redirects to #index' do
get :new
expect(response).to redirect_to(profile_keys_path)
end
end
describe "#get_keys" do describe "#get_keys" do
describe "non existant user" do describe "non existant user" do
it "does not generally work" do it "does not generally work" do
......
...@@ -244,4 +244,27 @@ describe Projects::BranchesController do ...@@ -244,4 +244,27 @@ describe Projects::BranchesController do
end end
end end
end end
describe "GET index" do
render_views
before do
sign_in(user)
end
context 'when rendering a JSON format' do
it 'filters branches by name' do
get :index,
namespace_id: project.namespace,
project_id: project,
format: :json,
search: 'master'
parsed_response = JSON.parse(response.body)
expect(parsed_response.length).to eq 1
expect(parsed_response.first).to eq 'master'
end
end
end
end end
...@@ -30,18 +30,18 @@ describe Projects::GraphsController do ...@@ -30,18 +30,18 @@ describe Projects::GraphsController do
double(languages: { double(languages: {
'Ruby' => 1000, 'Ruby' => 1000,
'CoffeeScript' => 350, 'CoffeeScript' => 350,
'PowerShell' => 15 'NSIS' => 15
}) })
end end
let(:expected_values) do let(:expected_values) do
ps_color = "##{Digest::SHA256.hexdigest('PowerShell')[0...6]}" nsis_color = "##{Digest::SHA256.hexdigest('NSIS')[0...6]}"
[ [
# colors from Linguist: # colors from Linguist:
{ label: "Ruby", color: "#701516", highlight: "#701516" }, { label: "Ruby", color: "#701516", highlight: "#701516" },
{ label: "CoffeeScript", color: "#244776", highlight: "#244776" }, { label: "CoffeeScript", color: "#244776", highlight: "#244776" },
# colors from SHA256 fallback: # colors from SHA256 fallback:
{ label: "PowerShell", color: ps_color, highlight: ps_color } { label: "NSIS", color: nsis_color, highlight: nsis_color }
] ]
end end
......
...@@ -200,4 +200,31 @@ describe Projects::NotesController do ...@@ -200,4 +200,31 @@ describe Projects::NotesController do
end end
end end
end end
describe 'GET index' do
let(:last_fetched_at) { '1487756246' }
let(:request_params) do
{
namespace_id: project.namespace,
project_id: project,
target_type: 'issue',
target_id: issue.id
}
end
before do
sign_in(user)
project.team << [user, :developer]
end
it 'passes last_fetched_at from headers to NotesFinder' do
request.headers['X-Last-Fetched-At'] = last_fetched_at
expect(NotesFinder).to receive(:new)
.with(anything, anything, hash_including(last_fetched_at: last_fetched_at))
.and_call_original
get :index, request_params
end
end
end end
...@@ -61,4 +61,18 @@ describe 'Profile account page', feature: true do ...@@ -61,4 +61,18 @@ describe 'Profile account page', feature: true do
expect(find('#incoming-email-token').value).not_to eq(previous_token) expect(find('#incoming-email-token').value).not_to eq(previous_token)
end end
end end
describe 'when I change my username' do
before do
visit profile_account_path
end
it 'changes my username' do
fill_in 'user_username', with: 'new-username'
click_button('Update username')
expect(page).to have_content('new-username')
end
end
end end
require 'spec_helper' require 'spec_helper'
feature 'Setup Mattermost slash commands', feature: true do feature 'Setup Mattermost slash commands', :feature, :js do
let(:user) { create(:user) } let(:user) { create(:user) }
let(:project) { create(:empty_project) } let(:project) { create(:empty_project) }
let(:service) { project.create_mattermost_slash_commands_service } let(:service) { project.create_mattermost_slash_commands_service }
...@@ -62,11 +62,11 @@ feature 'Setup Mattermost slash commands', feature: true do ...@@ -62,11 +62,11 @@ feature 'Setup Mattermost slash commands', feature: true do
click_link 'Add to Mattermost' click_link 'Add to Mattermost'
team_name = teams.first[1]['display_name'] team_name = teams.first['display_name']
select_element = find('select#mattermost_team_id') select_element = find('#mattermost_team_id')
selected_option = select_element.find('option[selected]') selected_option = select_element.find('option[selected]')
expect(select_element['disabled']).to eq('disabled') expect(select_element['disabled']).to be(true)
expect(selected_option).to have_content(team_name.to_s) expect(selected_option).to have_content(team_name.to_s)
end end
...@@ -75,7 +75,7 @@ feature 'Setup Mattermost slash commands', feature: true do ...@@ -75,7 +75,7 @@ feature 'Setup Mattermost slash commands', feature: true do
click_link 'Add to Mattermost' click_link 'Add to Mattermost'
expect(find('input#mattermost_team_id', visible: false).value).to eq(teams.first[0].to_s) expect(find('input#mattermost_team_id', visible: false).value).to eq(teams.first['id'])
end end
it 'shows an explanation user is a member of multiple teams' do it 'shows an explanation user is a member of multiple teams' do
...@@ -92,12 +92,9 @@ feature 'Setup Mattermost slash commands', feature: true do ...@@ -92,12 +92,9 @@ feature 'Setup Mattermost slash commands', feature: true do
click_link 'Add to Mattermost' click_link 'Add to Mattermost'
select_element = find('select#mattermost_team_id') select_element = find('#mattermost_team_id')
selected_option = select_element.find('option[selected]')
expect(select_element['disabled']).to be(nil) expect(select_element['disabled']).to be(false)
expect(selected_option).to have_content('Select team...')
# The 'Select team...' placeholder is item `0`.
expect(select_element.all('option').count).to eq(3) expect(select_element.all('option').count).to eq(3)
end end
...@@ -110,20 +107,37 @@ feature 'Setup Mattermost slash commands', feature: true do ...@@ -110,20 +107,37 @@ feature 'Setup Mattermost slash commands', feature: true do
expect(page).to have_content('test mattermost error message') expect(page).to have_content('test mattermost error message')
end end
it 'enables the submit button if the required fields are provided', :js do
stub_teams(count: 1)
click_link 'Add to Mattermost'
expect(find('input[type="submit"]')['disabled']).not_to be(true)
end
it 'disables the submit button if the required fields are not provided', :js do
stub_teams(count: 1)
click_link 'Add to Mattermost'
fill_in('mattermost_trigger', with: '')
expect(find('input[type="submit"]')['disabled']).to be(true)
end
def stub_teams(count: 0) def stub_teams(count: 0)
teams = create_teams(count) teams = create_teams(count)
allow_any_instance_of(MattermostSlashCommandsService).to receive(:list_teams) { teams } allow_any_instance_of(MattermostSlashCommandsService).to receive(:list_teams) { [teams, nil] }
teams teams
end end
def create_teams(count = 0) def create_teams(count = 0)
teams = {} teams = []
count.times do |i| count.times do |i|
i += 1 teams.push({ "id" => "x#{i}", "display_name" => "x#{i}-name" })
teams[i] = { id: i, display_name: i }
end end
teams teams
......
require 'spec_helper' require 'spec_helper'
require_relative '../../config/initializers/metrics' require_relative '../../config/initializers/8_metrics'
describe 'instrument_classes', lib: true do describe 'instrument_classes', lib: true do
let(:config) { double(:config) } let(:config) { double(:config) }
......
require 'spec_helper'
describe Gitlab::EtagCaching::Middleware do
let(:app) { double(:app) }
let(:middleware) { described_class.new(app) }
let(:app_status_code) { 200 }
let(:if_none_match) { nil }
let(:enabled_path) { '/gitlab-org/gitlab-ce/noteable/issue/1/notes' }
context 'when ETag caching is not enabled for current route' do
let(:path) { '/gitlab-org/gitlab-ce/tree/master/noteable/issue/1/notes' }
before do
mock_app_response
end
it 'does not add ETag header' do
_, headers, _ = middleware.call(build_env(path, if_none_match))
expect(headers['ETag']).to be_nil
end
it 'passes status code from app' do
status, _, _ = middleware.call(build_env(path, if_none_match))
expect(status).to eq app_status_code
end
end
context 'when there is no ETag in store for given resource' do
let(:path) { enabled_path }
before do
mock_app_response
mock_value_in_store(nil)
end
it 'generates ETag' do
expect_any_instance_of(Gitlab::EtagCaching::Store)
.to receive(:touch).and_return('123')
middleware.call(build_env(path, if_none_match))
end
context 'when If-None-Match header was specified' do
let(:if_none_match) { 'W/"abc"' }
it 'tracks "etag_caching_key_not_found" event' do
expect(Gitlab::Metrics).to receive(:add_event)
.with(:etag_caching_middleware_used)
expect(Gitlab::Metrics).to receive(:add_event)
.with(:etag_caching_key_not_found)
middleware.call(build_env(path, if_none_match))
end
end
end
context 'when there is ETag in store for given resource' do
let(:path) { enabled_path }
before do
mock_app_response
mock_value_in_store('123')
end
it 'returns this value as header' do
_, headers, _ = middleware.call(build_env(path, if_none_match))
expect(headers['ETag']).to eq 'W/"123"'
end
end
context 'when If-None-Match header matches ETag in store' do
let(:path) { enabled_path }
let(:if_none_match) { 'W/"123"' }
before do
mock_value_in_store('123')
end
it 'does not call app' do
expect(app).not_to receive(:call)
middleware.call(build_env(path, if_none_match))
end
it 'returns status code 304' do
status, _, _ = middleware.call(build_env(path, if_none_match))
expect(status).to eq 304
end
it 'tracks "etag_caching_cache_hit" event' do
expect(Gitlab::Metrics).to receive(:add_event)
.with(:etag_caching_middleware_used)
expect(Gitlab::Metrics).to receive(:add_event)
.with(:etag_caching_cache_hit)
middleware.call(build_env(path, if_none_match))
end
end
context 'when If-None-Match header does not match ETag in store' do
let(:path) { enabled_path }
let(:if_none_match) { 'W/"abc"' }
before do
mock_value_in_store('123')
end
it 'calls app' do
expect(app).to receive(:call).and_return([app_status_code, {}, ['body']])
middleware.call(build_env(path, if_none_match))
end
it 'tracks "etag_caching_resource_changed" event' do
mock_app_response
expect(Gitlab::Metrics).to receive(:add_event)
.with(:etag_caching_middleware_used)
expect(Gitlab::Metrics).to receive(:add_event)
.with(:etag_caching_resource_changed)
middleware.call(build_env(path, if_none_match))
end
end
context 'when If-None-Match header is not specified' do
let(:path) { enabled_path }
before do
mock_value_in_store('123')
mock_app_response
end
it 'tracks "etag_caching_header_missing" event' do
expect(Gitlab::Metrics).to receive(:add_event)
.with(:etag_caching_middleware_used)
expect(Gitlab::Metrics).to receive(:add_event)
.with(:etag_caching_header_missing)
middleware.call(build_env(path, if_none_match))
end
end
def mock_app_response
allow(app).to receive(:call).and_return([app_status_code, {}, ['body']])
end
def mock_value_in_store(value)
allow_any_instance_of(Gitlab::EtagCaching::Store)
.to receive(:get).and_return(value)
end
def build_env(path, if_none_match)
{
'PATH_INFO' => path,
'HTTP_IF_NONE_MATCH' => if_none_match
}
end
end
...@@ -97,6 +97,7 @@ variables: ...@@ -97,6 +97,7 @@ variables:
triggers: triggers:
- project - project
- trigger_requests - trigger_requests
- owner
deploy_keys: deploy_keys:
- user - user
- deploy_keys_projects - deploy_keys_projects
......
...@@ -240,6 +240,8 @@ Ci::Trigger: ...@@ -240,6 +240,8 @@ Ci::Trigger:
- created_at - created_at
- updated_at - updated_at
- gl_project_id - gl_project_id
- owner_id
- description
DeployKey: DeployKey:
- id - id
- user_id - user_id
......
...@@ -13,7 +13,7 @@ describe Mattermost::Team do ...@@ -13,7 +13,7 @@ describe Mattermost::Team do
context 'for valid request' do context 'for valid request' do
let(:response) do let(:response) do
[{ { "xiyro8huptfhdndadpz8r3wnbo" => {
"id" => "xiyro8huptfhdndadpz8r3wnbo", "id" => "xiyro8huptfhdndadpz8r3wnbo",
"create_at" => 1482174222155, "create_at" => 1482174222155,
"update_at" => 1482174222155, "update_at" => 1482174222155,
...@@ -26,7 +26,7 @@ describe Mattermost::Team do ...@@ -26,7 +26,7 @@ describe Mattermost::Team do
"allowed_domains" => "", "allowed_domains" => "",
"invite_id" => "o4utakb9jtb7imctdfzbf9r5ro", "invite_id" => "o4utakb9jtb7imctdfzbf9r5ro",
"allow_open_invite" => false "allow_open_invite" => false
}] } }
end end
before do before do
...@@ -39,7 +39,7 @@ describe Mattermost::Team do ...@@ -39,7 +39,7 @@ describe Mattermost::Team do
end end
it 'returns a token' do it 'returns a token' do
is_expected.to eq(response) is_expected.to eq(response.values)
end end
end end
......
require 'spec_helper' require 'spec_helper'
describe Ci::Trigger, models: true do describe Ci::Trigger, models: true do
let(:project) { FactoryGirl.create :empty_project } let(:project) { create :empty_project }
describe 'associations' do
it { is_expected.to belong_to(:project) }
it { is_expected.to belong_to(:owner) }
it { is_expected.to have_many(:trigger_requests) }
end
describe 'before_validation' do describe 'before_validation' do
it 'sets an random token if none provided' do it 'sets an random token if none provided' do
trigger = FactoryGirl.create :ci_trigger_without_token, project: project trigger = create(:ci_trigger_without_token, project: project)
expect(trigger.token).not_to be_nil expect(trigger.token).not_to be_nil
end end
it 'does not set an random token if one provided' do it 'does not set an random token if one provided' do
trigger = FactoryGirl.create :ci_trigger, project: project trigger = create(:ci_trigger, project: project)
expect(trigger.token).to eq('token') expect(trigger.token).to eq('token')
end end
end end
......
...@@ -387,4 +387,16 @@ describe Note, models: true do ...@@ -387,4 +387,16 @@ describe Note, models: true do
end end
end end
end end
describe 'expiring ETag cache' do
let(:note) { build(:note_on_issue) }
it "expires cache for note's issue when note is saved" do
expect_any_instance_of(Gitlab::EtagCaching::Store)
.to receive(:touch)
.with("/#{note.project.namespace.to_param}/#{note.project.to_param}/noteable/issue/#{note.noteable.id}/notes")
note.save!
end
end
end end
...@@ -92,7 +92,7 @@ describe MattermostSlashCommandsService, :models do ...@@ -92,7 +92,7 @@ describe MattermostSlashCommandsService, :models do
to_return( to_return(
status: 200, status: 200,
headers: { 'Content-Type' => 'application/json' }, headers: { 'Content-Type' => 'application/json' },
body: ['list'].to_json body: { 'list' => true }.to_json
) )
end end
......
...@@ -32,6 +32,7 @@ describe User, models: true do ...@@ -32,6 +32,7 @@ describe User, models: true do
it { is_expected.to have_many(:spam_logs).dependent(:destroy) } it { is_expected.to have_many(:spam_logs).dependent(:destroy) }
it { is_expected.to have_many(:todos).dependent(:destroy) } it { is_expected.to have_many(:todos).dependent(:destroy) }
it { is_expected.to have_many(:award_emoji).dependent(:destroy) } it { is_expected.to have_many(:award_emoji).dependent(:destroy) }
it { is_expected.to have_many(:triggers).dependent(:destroy) }
it { is_expected.to have_many(:builds).dependent(:nullify) } it { is_expected.to have_many(:builds).dependent(:nullify) }
it { is_expected.to have_many(:pipelines).dependent(:nullify) } it { is_expected.to have_many(:pipelines).dependent(:nullify) }
it { is_expected.to have_many(:chat_names).dependent(:destroy) } it { is_expected.to have_many(:chat_names).dependent(:destroy) }
......
...@@ -14,7 +14,7 @@ describe API::Triggers do ...@@ -14,7 +14,7 @@ describe API::Triggers do
let!(:trigger2) { create(:ci_trigger, project: project, token: trigger_token_2) } let!(:trigger2) { create(:ci_trigger, project: project, token: trigger_token_2) }
let!(:trigger_request) { create(:ci_trigger_request, trigger: trigger, created_at: '2015-01-01 12:13:14') } let!(:trigger_request) { create(:ci_trigger_request, trigger: trigger, created_at: '2015-01-01 12:13:14') }
describe 'POST /projects/:project_id/trigger' do describe 'POST /projects/:project_id/trigger/pipeline' do
let!(:project2) { create(:project) } let!(:project2) { create(:project) }
let(:options) do let(:options) do
{ {
...@@ -28,17 +28,20 @@ describe API::Triggers do ...@@ -28,17 +28,20 @@ describe API::Triggers do
context 'Handles errors' do context 'Handles errors' do
it 'returns bad request if token is missing' do it 'returns bad request if token is missing' do
post api("/projects/#{project.id}/trigger/builds"), ref: 'master' post api("/projects/#{project.id}/trigger/pipeline"), ref: 'master'
expect(response).to have_http_status(400) expect(response).to have_http_status(400)
end end
it 'returns not found if project is not found' do it 'returns not found if project is not found' do
post api('/projects/0/trigger/builds'), options.merge(ref: 'master') post api('/projects/0/trigger/pipeline'), options.merge(ref: 'master')
expect(response).to have_http_status(404) expect(response).to have_http_status(404)
end end
it 'returns unauthorized if token is for different project' do it 'returns unauthorized if token is for different project' do
post api("/projects/#{project2.id}/trigger/builds"), options.merge(ref: 'master') post api("/projects/#{project2.id}/trigger/pipeline"), options.merge(ref: 'master')
expect(response).to have_http_status(401) expect(response).to have_http_status(401)
end end
end end
...@@ -46,9 +49,11 @@ describe API::Triggers do ...@@ -46,9 +49,11 @@ describe API::Triggers do
context 'Have a commit' do context 'Have a commit' do
let(:pipeline) { project.pipelines.last } let(:pipeline) { project.pipelines.last }
it 'creates builds' do it 'creates pipeline' do
post api("/projects/#{project.id}/trigger/builds"), options.merge(ref: 'master') post api("/projects/#{project.id}/trigger/pipeline"), options.merge(ref: 'master')
expect(response).to have_http_status(201) expect(response).to have_http_status(201)
expect(json_response).to include('id' => pipeline.id)
pipeline.builds.reload pipeline.builds.reload
expect(pipeline.builds.pending.size).to eq(2) expect(pipeline.builds.pending.size).to eq(2)
expect(pipeline.builds.size).to eq(5) expect(pipeline.builds.size).to eq(5)
...@@ -56,15 +61,17 @@ describe API::Triggers do ...@@ -56,15 +61,17 @@ describe API::Triggers do
it 'creates builds on webhook from other gitlab repository and branch' do it 'creates builds on webhook from other gitlab repository and branch' do
expect do expect do
post api("/projects/#{project.id}/ref/master/trigger/builds?token=#{trigger_token}"), { ref: 'refs/heads/other-branch' } post api("/projects/#{project.id}/ref/master/trigger/pipeline?token=#{trigger_token}"), { ref: 'refs/heads/other-branch' }
end.to change(project.builds, :count).by(5) end.to change(project.builds, :count).by(5)
expect(response).to have_http_status(201) expect(response).to have_http_status(201)
end end
it 'returns bad request with no builds created if there\'s no commit for that ref' do it 'returns bad request with no pipeline created if there\'s no commit for that ref' do
post api("/projects/#{project.id}/trigger/builds"), options.merge(ref: 'other-branch') post api("/projects/#{project.id}/trigger/pipeline"), options.merge(ref: 'other-branch')
expect(response).to have_http_status(400) expect(response).to have_http_status(400)
expect(json_response['message']).to eq('No builds created') expect(json_response['message']).to eq('No pipeline created')
end end
context 'Validates variables' do context 'Validates variables' do
...@@ -73,22 +80,24 @@ describe API::Triggers do ...@@ -73,22 +80,24 @@ describe API::Triggers do
end end
it 'validates variables to be a hash' do it 'validates variables to be a hash' do
post api("/projects/#{project.id}/trigger/builds"), options.merge(variables: 'value', ref: 'master') post api("/projects/#{project.id}/trigger/pipeline"), options.merge(variables: 'value', ref: 'master')
expect(response).to have_http_status(400) expect(response).to have_http_status(400)
expect(json_response['error']).to eq('variables is invalid') expect(json_response['error']).to eq('variables is invalid')
end end
it 'validates variables needs to be a map of key-valued strings' do it 'validates variables needs to be a map of key-valued strings' do
post api("/projects/#{project.id}/trigger/builds"), options.merge(variables: { key: %w(1 2) }, ref: 'master') post api("/projects/#{project.id}/trigger/pipeline"), options.merge(variables: { key: %w(1 2) }, ref: 'master')
expect(response).to have_http_status(400) expect(response).to have_http_status(400)
expect(json_response['message']).to eq('variables needs to be a map of key-valued strings') expect(json_response['message']).to eq('variables needs to be a map of key-valued strings')
end end
it 'creates trigger request with variables' do it 'creates trigger request with variables' do
post api("/projects/#{project.id}/trigger/builds"), options.merge(variables: variables, ref: 'master') post api("/projects/#{project.id}/trigger/pipeline"), options.merge(variables: variables, ref: 'master')
expect(response).to have_http_status(201) expect(response).to have_http_status(201)
pipeline.builds.reload expect(pipeline.builds.reload.first.trigger_request.variables).to eq(variables)
expect(pipeline.builds.first.trigger_request.variables).to eq(variables)
end end
end end
end end
...@@ -123,17 +132,17 @@ describe API::Triggers do ...@@ -123,17 +132,17 @@ describe API::Triggers do
end end
end end
describe 'GET /projects/:id/triggers/:token' do describe 'GET /projects/:id/triggers/:trigger_id' do
context 'authenticated user with valid permissions' do context 'authenticated user with valid permissions' do
it 'returns trigger details' do it 'returns trigger details' do
get api("/projects/#{project.id}/triggers/#{trigger.token}", user) get api("/projects/#{project.id}/triggers/#{trigger.id}", user)
expect(response).to have_http_status(200) expect(response).to have_http_status(200)
expect(json_response).to be_a(Hash) expect(json_response).to be_a(Hash)
end end
it 'responds with 404 Not Found if requesting non-existing trigger' do it 'responds with 404 Not Found if requesting non-existing trigger' do
get api("/projects/#{project.id}/triggers/abcdef012345", user) get api("/projects/#{project.id}/triggers/-5", user)
expect(response).to have_http_status(404) expect(response).to have_http_status(404)
end end
...@@ -141,7 +150,7 @@ describe API::Triggers do ...@@ -141,7 +150,7 @@ describe API::Triggers do
context 'authenticated user with invalid permissions' do context 'authenticated user with invalid permissions' do
it 'does not return triggers list' do it 'does not return triggers list' do
get api("/projects/#{project.id}/triggers/#{trigger.token}", user2) get api("/projects/#{project.id}/triggers/#{trigger.id}", user2)
expect(response).to have_http_status(403) expect(response).to have_http_status(403)
end end
...@@ -149,7 +158,7 @@ describe API::Triggers do ...@@ -149,7 +158,7 @@ describe API::Triggers do
context 'unauthenticated user' do context 'unauthenticated user' do
it 'does not return triggers list' do it 'does not return triggers list' do
get api("/projects/#{project.id}/triggers/#{trigger.token}") get api("/projects/#{project.id}/triggers/#{trigger.id}")
expect(response).to have_http_status(401) expect(response).to have_http_status(401)
end end
...@@ -158,19 +167,31 @@ describe API::Triggers do ...@@ -158,19 +167,31 @@ describe API::Triggers do
describe 'POST /projects/:id/triggers' do describe 'POST /projects/:id/triggers' do
context 'authenticated user with valid permissions' do context 'authenticated user with valid permissions' do
it 'creates trigger' do context 'with required parameters' do
expect do it 'creates trigger' do
expect do
post api("/projects/#{project.id}/triggers", user),
description: 'trigger'
end.to change{project.triggers.count}.by(1)
expect(response).to have_http_status(201)
expect(json_response).to include('description' => 'trigger')
end
end
context 'without required parameters' do
it 'does not create trigger' do
post api("/projects/#{project.id}/triggers", user) post api("/projects/#{project.id}/triggers", user)
end.to change{project.triggers.count}.by(1)
expect(response).to have_http_status(201) expect(response).to have_http_status(:bad_request)
expect(json_response).to be_a(Hash) end
end end
end end
context 'authenticated user with invalid permissions' do context 'authenticated user with invalid permissions' do
it 'does not create trigger' do it 'does not create trigger' do
post api("/projects/#{project.id}/triggers", user2) post api("/projects/#{project.id}/triggers", user2),
description: 'trigger'
expect(response).to have_http_status(403) expect(response).to have_http_status(403)
end end
...@@ -178,25 +199,87 @@ describe API::Triggers do ...@@ -178,25 +199,87 @@ describe API::Triggers do
context 'unauthenticated user' do context 'unauthenticated user' do
it 'does not create trigger' do it 'does not create trigger' do
post api("/projects/#{project.id}/triggers") post api("/projects/#{project.id}/triggers"),
description: 'trigger'
expect(response).to have_http_status(401)
end
end
end
describe 'PUT /projects/:id/triggers/:trigger_id' do
context 'authenticated user with valid permissions' do
let(:new_description) { 'new description' }
it 'updates description' do
put api("/projects/#{project.id}/triggers/#{trigger.id}", user),
description: new_description
expect(response).to have_http_status(200)
expect(json_response).to include('description' => new_description)
expect(trigger.reload.description).to eq(new_description)
end
end
context 'authenticated user with invalid permissions' do
it 'does not update trigger' do
put api("/projects/#{project.id}/triggers/#{trigger.id}", user2)
expect(response).to have_http_status(403)
end
end
context 'unauthenticated user' do
it 'does not update trigger' do
put api("/projects/#{project.id}/triggers/#{trigger.id}")
expect(response).to have_http_status(401)
end
end
end
describe 'POST /projects/:id/triggers/:trigger_id/take_ownership' do
context 'authenticated user with valid permissions' do
it 'updates owner' do
expect(trigger.owner).to be_nil
post api("/projects/#{project.id}/triggers/#{trigger.id}/take_ownership", user)
expect(response).to have_http_status(200)
expect(json_response).to include('owner')
expect(trigger.reload.owner).to eq(user)
end
end
context 'authenticated user with invalid permissions' do
it 'does not update owner' do
post api("/projects/#{project.id}/triggers/#{trigger.id}/take_ownership", user2)
expect(response).to have_http_status(403)
end
end
context 'unauthenticated user' do
it 'does not update owner' do
post api("/projects/#{project.id}/triggers/#{trigger.id}/take_ownership")
expect(response).to have_http_status(401) expect(response).to have_http_status(401)
end end
end end
end end
describe 'DELETE /projects/:id/triggers/:token' do describe 'DELETE /projects/:id/triggers/:trigger_id' do
context 'authenticated user with valid permissions' do context 'authenticated user with valid permissions' do
it 'deletes trigger' do it 'deletes trigger' do
expect do expect do
delete api("/projects/#{project.id}/triggers/#{trigger.token}", user) delete api("/projects/#{project.id}/triggers/#{trigger.id}", user)
expect(response).to have_http_status(204) expect(response).to have_http_status(204)
end.to change{project.triggers.count}.by(-1) end.to change{project.triggers.count}.by(-1)
end end
it 'responds with 404 Not Found if requesting non-existing trigger' do it 'responds with 404 Not Found if requesting non-existing trigger' do
delete api("/projects/#{project.id}/triggers/abcdef012345", user) delete api("/projects/#{project.id}/triggers/-5", user)
expect(response).to have_http_status(404) expect(response).to have_http_status(404)
end end
...@@ -204,7 +287,7 @@ describe API::Triggers do ...@@ -204,7 +287,7 @@ describe API::Triggers do
context 'authenticated user with invalid permissions' do context 'authenticated user with invalid permissions' do
it 'does not delete trigger' do it 'does not delete trigger' do
delete api("/projects/#{project.id}/triggers/#{trigger.token}", user2) delete api("/projects/#{project.id}/triggers/#{trigger.id}", user2)
expect(response).to have_http_status(403) expect(response).to have_http_status(403)
end end
...@@ -212,7 +295,7 @@ describe API::Triggers do ...@@ -212,7 +295,7 @@ describe API::Triggers do
context 'unauthenticated user' do context 'unauthenticated user' do
it 'does not delete trigger' do it 'does not delete trigger' do
delete api("/projects/#{project.id}/triggers/#{trigger.token}") delete api("/projects/#{project.id}/triggers/#{trigger.id}")
expect(response).to have_http_status(401) expect(response).to have_http_status(401)
end end
......
...@@ -11,6 +11,177 @@ describe API::V3::Triggers do ...@@ -11,6 +11,177 @@ describe API::V3::Triggers do
let!(:developer) { create(:project_member, :developer, user: user2, project: project) } let!(:developer) { create(:project_member, :developer, user: user2, project: project) }
let!(:trigger) { create(:ci_trigger, project: project, token: trigger_token) } let!(:trigger) { create(:ci_trigger, project: project, token: trigger_token) }
describe 'POST /projects/:project_id/trigger' do
let!(:project2) { create(:project) }
let(:options) do
{
token: trigger_token
}
end
before do
stub_ci_pipeline_to_return_yaml_file
end
context 'Handles errors' do
it 'returns bad request if token is missing' do
post v3_api("/projects/#{project.id}/trigger/builds"), ref: 'master'
expect(response).to have_http_status(400)
end
it 'returns not found if project is not found' do
post v3_api('/projects/0/trigger/builds'), options.merge(ref: 'master')
expect(response).to have_http_status(404)
end
it 'returns unauthorized if token is for different project' do
post v3_api("/projects/#{project2.id}/trigger/builds"), options.merge(ref: 'master')
expect(response).to have_http_status(401)
end
end
context 'Have a commit' do
let(:pipeline) { project.pipelines.last }
it 'creates builds' do
post v3_api("/projects/#{project.id}/trigger/builds"), options.merge(ref: 'master')
expect(response).to have_http_status(201)
pipeline.builds.reload
expect(pipeline.builds.pending.size).to eq(2)
expect(pipeline.builds.size).to eq(5)
end
it 'creates builds on webhook from other gitlab repository and branch' do
expect do
post v3_api("/projects/#{project.id}/ref/master/trigger/builds?token=#{trigger_token}"), { ref: 'refs/heads/other-branch' }
end.to change(project.builds, :count).by(5)
expect(response).to have_http_status(201)
end
it 'returns bad request with no builds created if there\'s no commit for that ref' do
post v3_api("/projects/#{project.id}/trigger/builds"), options.merge(ref: 'other-branch')
expect(response).to have_http_status(400)
expect(json_response['message']).to eq('No builds created')
end
context 'Validates variables' do
let(:variables) do
{ 'TRIGGER_KEY' => 'TRIGGER_VALUE' }
end
it 'validates variables to be a hash' do
post v3_api("/projects/#{project.id}/trigger/builds"), options.merge(variables: 'value', ref: 'master')
expect(response).to have_http_status(400)
expect(json_response['error']).to eq('variables is invalid')
end
it 'validates variables needs to be a map of key-valued strings' do
post v3_api("/projects/#{project.id}/trigger/builds"), options.merge(variables: { key: %w(1 2) }, ref: 'master')
expect(response).to have_http_status(400)
expect(json_response['message']).to eq('variables needs to be a map of key-valued strings')
end
it 'creates trigger request with variables' do
post v3_api("/projects/#{project.id}/trigger/builds"), options.merge(variables: variables, ref: 'master')
expect(response).to have_http_status(201)
pipeline.builds.reload
expect(pipeline.builds.first.trigger_request.variables).to eq(variables)
end
end
end
end
describe 'GET /projects/:id/triggers' do
context 'authenticated user with valid permissions' do
it 'returns list of triggers' do
get v3_api("/projects/#{project.id}/triggers", user)
expect(response).to have_http_status(200)
expect(response).to include_pagination_headers
expect(json_response).to be_a(Array)
expect(json_response[0]).to have_key('token')
end
end
context 'authenticated user with invalid permissions' do
it 'does not return triggers list' do
get v3_api("/projects/#{project.id}/triggers", user2)
expect(response).to have_http_status(403)
end
end
context 'unauthenticated user' do
it 'does not return triggers list' do
get v3_api("/projects/#{project.id}/triggers")
expect(response).to have_http_status(401)
end
end
end
describe 'GET /projects/:id/triggers/:token' do
context 'authenticated user with valid permissions' do
it 'returns trigger details' do
get v3_api("/projects/#{project.id}/triggers/#{trigger.token}", user)
expect(response).to have_http_status(200)
expect(json_response).to be_a(Hash)
end
it 'responds with 404 Not Found if requesting non-existing trigger' do
get v3_api("/projects/#{project.id}/triggers/abcdef012345", user)
expect(response).to have_http_status(404)
end
end
context 'authenticated user with invalid permissions' do
it 'does not return triggers list' do
get v3_api("/projects/#{project.id}/triggers/#{trigger.token}", user2)
expect(response).to have_http_status(403)
end
end
context 'unauthenticated user' do
it 'does not return triggers list' do
get v3_api("/projects/#{project.id}/triggers/#{trigger.token}")
expect(response).to have_http_status(401)
end
end
end
describe 'POST /projects/:id/triggers' do
context 'authenticated user with valid permissions' do
it 'creates trigger' do
expect do
post v3_api("/projects/#{project.id}/triggers", user)
end.to change{project.triggers.count}.by(1)
expect(response).to have_http_status(201)
expect(json_response).to be_a(Hash)
end
end
context 'authenticated user with invalid permissions' do
it 'does not create trigger' do
post v3_api("/projects/#{project.id}/triggers", user2)
expect(response).to have_http_status(403)
end
end
context 'unauthenticated user' do
it 'does not create trigger' do
post v3_api("/projects/#{project.id}/triggers")
expect(response).to have_http_status(401)
end
end
end
describe 'DELETE /projects/:id/triggers/:token' do describe 'DELETE /projects/:id/triggers/:token' do
context 'authenticated user with valid permissions' do context 'authenticated user with valid permissions' do
it 'deletes trigger' do it 'deletes trigger' do
......
...@@ -431,12 +431,22 @@ describe 'project routing' do ...@@ -431,12 +431,22 @@ describe 'project routing' do
end end
end end
# project_notes GET /:project_id/notes(.:format) notes#index # project_noteable_notes GET /:project_id/noteable/:target_type/:target_id/notes notes#index
# POST /:project_id/notes(.:format) notes#create # POST /:project_id/notes(.:format) notes#create
# project_note DELETE /:project_id/notes/:id(.:format) notes#destroy # project_note DELETE /:project_id/notes/:id(.:format) notes#destroy
describe Projects::NotesController, 'routing' do describe Projects::NotesController, 'routing' do
it 'to #index' do
expect(get('/gitlab/gitlabhq/noteable/issue/1/notes')).to route_to(
'projects/notes#index',
namespace_id: 'gitlab',
project_id: 'gitlabhq',
target_type: 'issue',
target_id: '1'
)
end
it_behaves_like 'RESTful project resources' do it_behaves_like 'RESTful project resources' do
let(:actions) { [:index, :create, :destroy] } let(:actions) { [:create, :destroy] }
let(:controller) { 'notes' } let(:controller) { 'notes' }
end end
end end
......
...@@ -13,8 +13,22 @@ describe Ci::CreateTriggerRequestService, services: true do ...@@ -13,8 +13,22 @@ describe Ci::CreateTriggerRequestService, services: true do
context 'valid params' do context 'valid params' do
subject { service.execute(project, trigger, 'master') } subject { service.execute(project, trigger, 'master') }
it { expect(subject).to be_kind_of(Ci::TriggerRequest) } context 'without owner' do
it { expect(subject.builds.first).to be_kind_of(Ci::Build) } it { expect(subject).to be_kind_of(Ci::TriggerRequest) }
it { expect(subject.pipeline).to be_kind_of(Ci::Pipeline) }
it { expect(subject.builds.first).to be_kind_of(Ci::Build) }
end
context 'with owner' do
let(:owner) { create(:user) }
let(:trigger) { create(:ci_trigger, project: project, owner: owner) }
it { expect(subject).to be_kind_of(Ci::TriggerRequest) }
it { expect(subject.pipeline).to be_kind_of(Ci::Pipeline) }
it { expect(subject.pipeline.user).to eq(owner) }
it { expect(subject.builds.first).to be_kind_of(Ci::Build) }
it { expect(subject.builds.first.user).to eq(owner) }
end
end end
context 'no commit for ref' do context 'no commit for ref' do
......
...@@ -26,6 +26,28 @@ describe Projects::UpdatePagesService do ...@@ -26,6 +26,28 @@ describe Projects::UpdatePagesService do
build.update_attributes(artifacts_metadata: metadata) build.update_attributes(artifacts_metadata: metadata)
end end
describe 'pages artifacts' do
context 'with expiry date' do
before do
build.artifacts_expire_in = "2 days"
end
it "doesn't delete artifacts" do
expect(execute).to eq(:success)
expect(build.reload.artifacts_file?).to eq(true)
end
end
context 'without expiry date' do
it "does delete artifacts" do
expect(execute).to eq(:success)
expect(build.reload.artifacts_file?).to eq(false)
end
end
end
it 'succeeds' do it 'succeeds' do
expect(project.pages_deployed?).to be_falsey expect(project.pages_deployed?).to be_falsey
expect(execute).to eq(:success) expect(execute).to eq(:success)
......
...@@ -108,7 +108,7 @@ describe 'gitlab:app namespace rake task' do ...@@ -108,7 +108,7 @@ describe 'gitlab:app namespace rake task' do
$stdout = orig_stdout $stdout = orig_stdout
end end
describe 'backup creation and deletion using annex and custom_hooks' do describe 'backup creation and deletion using custom_hooks' do
let(:project) { create(:project) } let(:project) { create(:project) }
let(:user_backup_path) { "repositories/#{project.path_with_namespace}" } let(:user_backup_path) { "repositories/#{project.path_with_namespace}" }
...@@ -132,25 +132,6 @@ describe 'gitlab:app namespace rake task' do ...@@ -132,25 +132,6 @@ describe 'gitlab:app namespace rake task' do
Dir.chdir(@origin_cd) Dir.chdir(@origin_cd)
end end
context 'project uses git-annex and successfully creates backup' do
let(:filename) { "annex" }
it 'creates annex.tar and project bundle' do
tar_contents, exit_status = Gitlab::Popen.popen(%W{tar -tvf #{@backup_tar}})
expect(exit_status).to eq(0)
expect(tar_contents).to match(user_backup_path)
expect(tar_contents).to match("#{user_backup_path}/annex.tar")
expect(tar_contents).to match("#{user_backup_path}.bundle")
end
it 'restores files correctly' do
restore_backup
expect(Dir.entries(File.join(project.repository.path, "annex"))).to include("dummy.txt")
end
end
context 'project uses custom_hooks and successfully creates backup' do context 'project uses custom_hooks and successfully creates backup' do
let(:filename) { "custom_hooks" } let(:filename) { "custom_hooks" }
......
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