Commit efc82ebf authored by Dmitriy Zaporozhets's avatar Dmitriy Zaporozhets

Merge branch 'master' into 'dz-nested-groups-improvements-3'

# Conflicts:
#   doc/api/projects.md
parents 6676b4f0 ad5e772b
...@@ -12,12 +12,18 @@ ...@@ -12,12 +12,18 @@
"localStorage": false "localStorage": false
}, },
"plugins": [ "plugins": [
"filenames" "filenames",
"import"
], ],
"settings": {
"import/resolver": {
"webpack": {
"config": "./config/webpack.config.js"
}
}
},
"rules": { "rules": {
"filenames/match-regex": [2, "^[a-z0-9_]+(.js)?$"], "filenames/match-regex": [2, "^[a-z0-9_]+(.js)?$"],
"no-multiple-empty-lines": ["error", { "max": 1 }], "no-multiple-empty-lines": ["error", { "max": 1 }]
"import/no-extraneous-dependencies": "off",
"import/no-unresolved": "off"
} }
} }
...@@ -107,7 +107,10 @@ setup-test-env: ...@@ -107,7 +107,10 @@ setup-test-env:
<<: *dedicated-runner <<: *dedicated-runner
stage: prepare stage: prepare
script: script:
- npm install - node --version
- yarn --version
- yarn install --pure-lockfile
- yarn check # ensure that yarn.lock matches package.json
- bundle exec rake gitlab:assets:compile - bundle exec rake gitlab:assets:compile
- bundle exec ruby -Ispec -e 'require "spec_helper" ; TestEnv.init' - bundle exec ruby -Ispec -e 'require "spec_helper" ; TestEnv.init'
artifacts: artifacts:
...@@ -246,7 +249,6 @@ karma: ...@@ -246,7 +249,6 @@ karma:
<<: *use-db <<: *use-db
<<: *dedicated-runner <<: *dedicated-runner
script: script:
- npm link istanbul
- bundle exec rake karma - bundle exec rake karma
artifacts: artifacts:
name: coverage-javascript name: coverage-javascript
...@@ -325,11 +327,9 @@ lint:javascript: ...@@ -325,11 +327,9 @@ lint:javascript:
paths: paths:
- node_modules/ - node_modules/
stage: test stage: test
image: "node:7.1" before_script: []
before_script:
- npm install
script: script:
- npm --silent run eslint - yarn run eslint
lint:javascript:report: lint:javascript:report:
<<: *dedicated-runner <<: *dedicated-runner
...@@ -337,12 +337,10 @@ lint:javascript:report: ...@@ -337,12 +337,10 @@ lint:javascript:report:
paths: paths:
- node_modules/ - node_modules/
stage: post-test stage: post-test
image: "node:7.1" before_script: []
before_script:
- npm install
script: script:
- find app/ spec/ -name '*.js' -or -name '*.js.es6' -exec sed --in-place 's|/\* eslint-disable .*\*/||' {} \; # run report over all files - find app/ spec/ -name '*.js' -or -name '*.js.es6' -exec sed --in-place 's|/\* eslint-disable .*\*/||' {} \; # run report over all files
- npm --silent run eslint-report || true # ignore exit code - yarn run eslint-report || true # ignore exit code
artifacts: artifacts:
name: eslint-report name: eslint-report
expire_in: 31d expire_in: 31d
......
...@@ -29,6 +29,7 @@ gem 'omniauth-github', '~> 1.1.1' ...@@ -29,6 +29,7 @@ gem 'omniauth-github', '~> 1.1.1'
gem 'omniauth-gitlab', '~> 1.0.2' gem 'omniauth-gitlab', '~> 1.0.2'
gem 'omniauth-google-oauth2', '~> 0.4.1' gem 'omniauth-google-oauth2', '~> 0.4.1'
gem 'omniauth-kerberos', '~> 0.3.0', group: :kerberos gem 'omniauth-kerberos', '~> 0.3.0', group: :kerberos
gem 'omniauth-oauth2-generic', '~> 0.2.2'
gem 'omniauth-saml', '~> 1.7.0' gem 'omniauth-saml', '~> 1.7.0'
gem 'omniauth-shibboleth', '~> 1.2.0' gem 'omniauth-shibboleth', '~> 1.2.0'
gem 'omniauth-twitter', '~> 1.2.0' gem 'omniauth-twitter', '~> 1.2.0'
......
...@@ -483,6 +483,8 @@ GEM ...@@ -483,6 +483,8 @@ GEM
omniauth-oauth2 (1.3.1) omniauth-oauth2 (1.3.1)
oauth2 (~> 1.0) oauth2 (~> 1.0)
omniauth (~> 1.2) omniauth (~> 1.2)
omniauth-oauth2-generic (0.2.2)
omniauth-oauth2 (~> 1.0)
omniauth-saml (1.7.0) omniauth-saml (1.7.0)
omniauth (~> 1.3) omniauth (~> 1.3)
ruby-saml (~> 1.4) ruby-saml (~> 1.4)
...@@ -931,6 +933,7 @@ DEPENDENCIES ...@@ -931,6 +933,7 @@ DEPENDENCIES
omniauth-gitlab (~> 1.0.2) omniauth-gitlab (~> 1.0.2)
omniauth-google-oauth2 (~> 0.4.1) omniauth-google-oauth2 (~> 0.4.1)
omniauth-kerberos (~> 0.3.0) omniauth-kerberos (~> 0.3.0)
omniauth-oauth2-generic (~> 0.2.2)
omniauth-saml (~> 1.7.0) omniauth-saml (~> 1.7.0)
omniauth-shibboleth (~> 1.2.0) omniauth-shibboleth (~> 1.2.0)
omniauth-twitter (~> 1.2.0) omniauth-twitter (~> 1.2.0)
......
...@@ -56,8 +56,7 @@ requireAll(require.context('./u2f', false, /^\.\/.*\.(js|es6)$/)); ...@@ -56,8 +56,7 @@ requireAll(require.context('./u2f', false, /^\.\/.*\.(js|es6)$/));
requireAll(require.context('./droplab', false, /^\.\/.*\.(js|es6)$/)); requireAll(require.context('./droplab', false, /^\.\/.*\.(js|es6)$/));
requireAll(require.context('.', false, /^\.\/(?!application\.js).*\.(js|es6)$/)); requireAll(require.context('.', false, /^\.\/(?!application\.js).*\.(js|es6)$/));
require('vendor/fuzzaldrin-plus'); require('vendor/fuzzaldrin-plus');
window.ES6Promise = require('vendor/es6-promise.auto'); require('es6-promise').polyfill();
window.ES6Promise.polyfill();
(function () { (function () {
document.addEventListener('beforeunload', function () { document.addEventListener('beforeunload', function () {
......
...@@ -8,7 +8,22 @@ ...@@ -8,7 +8,22 @@
* Uses Vue.Resource * Uses Vue.Resource
*/ */
class PipelinesService { class PipelinesService {
constructor(endpoint) {
/**
* FIXME: The url provided to request the pipelines in the new merge request
* page already has `.json`.
* This should be fixed when the endpoint is improved.
*
* @param {String} root
*/
constructor(root) {
let endpoint;
if (root.indexOf('.json') === -1) {
endpoint = `${root}.json`;
} else {
endpoint = root;
}
this.pipelines = Vue.resource(endpoint); this.pipelines = Vue.resource(endpoint);
} }
......
...@@ -47,9 +47,11 @@ ...@@ -47,9 +47,11 @@
} }
// Only filter asynchronously only if option remote is set // Only filter asynchronously only if option remote is set
if (this.options.remote) { if (this.options.remote) {
$inputContainer.parent().addClass('is-loading');
clearTimeout(timeout); clearTimeout(timeout);
return timeout = setTimeout(function() { return timeout = setTimeout(function() {
return this.options.query(this.input.val(), function(data) { return this.options.query(this.input.val(), function(data) {
$inputContainer.parent().removeClass('is-loading');
return this.options.callback(data); return this.options.callback(data);
}.bind(this)); }.bind(this));
}.bind(this), 250); }.bind(this), 250);
......
...@@ -864,7 +864,7 @@ ...@@ -864,7 +864,7 @@
overflow: hidden; overflow: hidden;
white-space: nowrap; white-space: nowrap;
text-overflow: ellipsis; text-overflow: ellipsis;
width: 90px; max-width: 70%;
color: $gl-text-color-secondary; color: $gl-text-color-secondary;
margin-left: 2px; margin-left: 2px;
display: inline-block; display: inline-block;
......
...@@ -84,7 +84,7 @@ class Projects::WikisController < Projects::ApplicationController ...@@ -84,7 +84,7 @@ class Projects::WikisController < Projects::ApplicationController
def destroy def destroy
@page = @project_wiki.find_page(params[:id]) @page = @project_wiki.find_page(params[:id])
@page&.delete WikiPages::DestroyService.new(@project, current_user).execute(@page)
redirect_to( redirect_to(
namespace_project_wiki_path(@project.namespace, @project, :home), namespace_project_wiki_path(@project.namespace, @project, :home),
......
...@@ -45,9 +45,10 @@ class Event < ActiveRecord::Base ...@@ -45,9 +45,10 @@ class Event < ActiveRecord::Base
class << self class << self
# Update Gitlab::ContributionsCalendar#activity_dates if this changes # Update Gitlab::ContributionsCalendar#activity_dates if this changes
def contributions def contributions
where("action = ? OR (target_type in (?) AND action in (?))", where("action = ? OR (target_type IN (?) AND action IN (?)) OR (target_type = ? AND action = ?)",
Event::PUSHED, ["MergeRequest", "Issue"], Event::PUSHED,
[Event::CREATED, Event::CLOSED, Event::MERGED]) ["MergeRequest", "Issue"], [Event::CREATED, Event::CLOSED, Event::MERGED],
"Note", Event::COMMENTED)
end end
def limit_recent(limit = 20, offset = nil) def limit_recent(limit = 20, offset = nil)
......
...@@ -207,6 +207,10 @@ class WikiPage ...@@ -207,6 +207,10 @@ class WikiPage
'projects/wikis/wiki_page' 'projects/wikis/wiki_page'
end end
def id
page.version.to_s
end
private private
def set_attributes def set_attributes
......
module WikiPages
class DestroyService < WikiPages::BaseService
def execute(page)
if page&.delete
execute_hooks(page, 'delete')
end
page
end
end
end
.clearfix.calendar .clearfix.calendar
.js-contrib-calendar .js-contrib-calendar
.calendar-hint .calendar-hint
Summary of issues, merge requests, and push events Summary of issues, merge requests, push events, and comments
:javascript :javascript
new Calendar( new Calendar(
#{@activity_dates.to_json}, #{@activity_dates.to_json},
......
...@@ -13,8 +13,10 @@ ...@@ -13,8 +13,10 @@
#{event.action_name} #{event.ref_type} #{event.ref_name} #{event.action_name} #{event.ref_type} #{event.ref_name}
- else - else
= event_action_name(event) = event_action_name(event)
- if event.target - if event.note?
%strong= link_to "#{event.target.to_reference}", [event.project.namespace.becomes(Namespace), event.project, event.target] %strong= link_to event.note_target.to_reference, event_note_target_path(event)
- elsif event.target
%strong= link_to event.target.to_reference, [event.project.namespace.becomes(Namespace), event.project, event.target]
at at
%strong %strong
......
...@@ -106,6 +106,8 @@ ...@@ -106,6 +106,8 @@
%i.fa.fa-spinner.fa-spin %i.fa.fa-spinner.fa-spin
.user-calendar-activities .user-calendar-activities
%h4.prepend-top-20
Most Recent Activity
.content_list{ data: { href: user_path } } .content_list{ data: { href: user_path } }
= spinner = spinner
......
---
title: Move /projects/fork/:id to /projects/:id/fork
merge_request: 8940
author:
---
title: Execute web hooks for WikiPage delete operation
merge_request: 8198
author:
---
title: Add discussion events to contributions calendar
merge_request: 8821
author:
---
title: 'API: Consolidate /projects endpoint'
merge_request: 8962
author:
---
title: Added 'Most Recent Activity' header to the User Profile page
merge_request: 9189
author: Jan Christophersen
title: Add the oauth2_generic OmniAuth strategy
merge_request: 9048
author: Joe Marty
\ No newline at end of file
---
title: Update doc for enabling or disabling GitLab CI
merge_request: 8965
author: Takuya Noguchi
---
title: Set maximum width for mini pipeline graph text so it is not truncated to early
merge_request: 9188
author:
---
title: Fix Merge request pipelines displays JSON
merge_request:
author:
---
title: Display loading indicator when filtering ref switcher dropdown
merge_request:
author:
---
title: Replace static fixture for right_sidebar_spec.js
merge_request: 9211
author: winniehell
---
title: Don't connect in Gitlab::Database.adapter_name
merge_request:
author:
---
title: Remove inactive default email services
merge_request: 8987
author:
---
title: replace npm with yarn and add yarn.lock
merge_request: 9055
author:
---
title: Replace static fixture for behaviors/requires_input_spec.js
merge_request: 9162
author: winniehell
class RemoveInactiveDefaultEmailServices < ActiveRecord::Migration
include Gitlab::Database::MigrationHelpers
DOWNTIME = false
disable_ddl_transaction!
def up
Gitlab::Database.with_connection_pool(2) do |pool|
threads = []
threads << Thread.new do
pool.with_connection do |connection|
connection.execute <<-SQL.strip_heredoc
DELETE FROM services
WHERE type = 'BuildsEmailService'
AND active IS FALSE
AND properties = '{"notify_only_broken_builds":true}';
SQL
end
end
threads << Thread.new do
pool.with_connection do |connection|
connection.execute <<-SQL.strip_heredoc
DELETE FROM services
WHERE type = 'PipelinesEmailService'
AND active IS FALSE
AND properties = '{"notify_only_broken_pipelines":true}';
SQL
end
end
threads.each(&:join)
end
end
def down
# Nothing can be done to restore the records
end
end
...@@ -73,6 +73,8 @@ Parameters: ...@@ -73,6 +73,8 @@ Parameters:
| `sort` | string | no | Return projects sorted in `asc` or `desc` order. Default is `desc` | | `sort` | string | no | Return projects sorted in `asc` or `desc` order. Default is `desc` |
| `search` | string | no | Return list of authorized projects matching the search criteria | | `search` | string | no | Return list of authorized projects matching the search criteria |
| `simple` | boolean | no | Return only the ID, URL, name, and path of each project | | `simple` | boolean | no | Return only the ID, URL, name, and path of each project |
| `owned` | boolean | no | Limit by projects owned by the current user |
| `starred` | boolean | no | Limit by projects starred by the current user |
Example response: Example response:
......
...@@ -36,6 +36,8 @@ Parameters: ...@@ -36,6 +36,8 @@ Parameters:
| `sort` | string | no | Return projects sorted in `asc` or `desc` order. Default is `desc` | | `sort` | string | no | Return projects sorted in `asc` or `desc` order. Default is `desc` |
| `search` | string | no | Return list of authorized projects matching the search criteria | | `search` | string | no | Return list of authorized projects matching the search criteria |
| `simple` | boolean | no | Return only the ID, URL, name, and path of each project | | `simple` | boolean | no | Return only the ID, URL, name, and path of each project |
| `owned` | boolean | no | Limit by projects owned by the current user |
| `starred` | boolean | no | Limit by projects starred by the current user |
```json ```json
[ [
...@@ -154,192 +156,6 @@ Parameters: ...@@ -154,192 +156,6 @@ Parameters:
] ]
``` ```
Get a list of projects which the authenticated user can see.
```
GET /projects/visible
```
Parameters:
| Attribute | Type | Required | Description |
| --------- | ---- | -------- | ----------- |
| `archived` | boolean | no | Limit by archived status |
| `visibility` | string | no | Limit by visibility `public`, `internal`, or `private` |
| `order_by` | string | no | Return projects ordered by `id`, `name`, `path`, `created_at`, `updated_at`, or `last_activity_at` fields. Default is `created_at` |
| `sort` | string | no | Return projects sorted in `asc` or `desc` order. Default is `desc` |
| `search` | string | no | Return list of authorized projects matching the search criteria |
| `simple` | boolean | no | Return only the ID, URL, name, and path of each project |
```json
[
{
"id": 4,
"description": null,
"default_branch": "master",
"public": false,
"visibility_level": 0,
"ssh_url_to_repo": "git@example.com:diaspora/diaspora-client.git",
"http_url_to_repo": "http://example.com/diaspora/diaspora-client.git",
"web_url": "http://example.com/diaspora/diaspora-client",
"tag_list": [
"example",
"disapora client"
],
"owner": {
"id": 3,
"name": "Diaspora",
"created_at": "2013-09-30T13:46:02Z"
},
"name": "Diaspora Client",
"name_with_namespace": "Diaspora / Diaspora Client",
"path": "diaspora-client",
"path_with_namespace": "diaspora/diaspora-client",
"issues_enabled": true,
"open_issues_count": 1,
"merge_requests_enabled": true,
"builds_enabled": true,
"wiki_enabled": true,
"snippets_enabled": false,
"container_registry_enabled": false,
"created_at": "2013-09-30T13:46:02Z",
"last_activity_at": "2013-09-30T13:46:02Z",
"creator_id": 3,
"namespace": {
"id": 3,
"name": "Diaspora",
"path": "diaspora",
"kind": "group",
"full_path": "diaspora"
},
"archived": false,
"avatar_url": "http://example.com/uploads/project/avatar/4/uploads/avatar.png",
"shared_runners_enabled": true,
"forks_count": 0,
"star_count": 0,
"runners_token": "b8547b1dc37721d05889db52fa2f02",
"public_builds": true,
"shared_with_groups": []
},
{
"id": 6,
"description": null,
"default_branch": "master",
"public": false,
"visibility_level": 0,
"ssh_url_to_repo": "git@example.com:brightbox/puppet.git",
"http_url_to_repo": "http://example.com/brightbox/puppet.git",
"web_url": "http://example.com/brightbox/puppet",
"tag_list": [
"example",
"puppet"
],
"owner": {
"id": 4,
"name": "Brightbox",
"created_at": "2013-09-30T13:46:02Z"
},
"name": "Puppet",
"name_with_namespace": "Brightbox / Puppet",
"path": "puppet",
"path_with_namespace": "brightbox/puppet",
"issues_enabled": true,
"open_issues_count": 1,
"merge_requests_enabled": true,
"builds_enabled": true,
"wiki_enabled": true,
"snippets_enabled": false,
"container_registry_enabled": false,
"created_at": "2013-09-30T13:46:02Z",
"last_activity_at": "2013-09-30T13:46:02Z",
"creator_id": 3,
"namespace": {
"id": 4,
"name": "Brightbox",
"path": "brightbox",
"kind": "group",
"full_path": "brightbox"
},
"permissions": {
"project_access": {
"access_level": 10,
"notification_level": 3
},
"group_access": {
"access_level": 50,
"notification_level": 3
}
},
"archived": false,
"avatar_url": null,
"shared_runners_enabled": true,
"forks_count": 0,
"star_count": 0,
"runners_token": "b8547b1dc37721d05889db52fa2f02",
"public_builds": true,
"shared_with_groups": []
}
]
```
### List owned projects
Get a list of projects which are owned by the authenticated user.
```
GET /projects/owned
```
Parameters:
| Attribute | Type | Required | Description |
| --------- | ---- | -------- | ----------- |
| `archived` | boolean | no | Limit by archived status |
| `visibility` | string | no | Limit by visibility `public`, `internal`, or `private` |
| `order_by` | string | no | Return projects ordered by `id`, `name`, `path`, `created_at`, `updated_at`, or `last_activity_at` fields. Default is `created_at` |
| `sort` | string | no | Return projects sorted in `asc` or `desc` order. Default is `desc` |
| `search` | string | no | Return list of authorized projects matching the search criteria |
| `simple` | boolean | no | Return only the ID, URL, name, and path of each project |
| `statistics` | boolean | no | Include project statistics |
### List starred projects
Get a list of projects which are starred by the authenticated user.
```
GET /projects/starred
```
Parameters:
| Attribute | Type | Required | Description |
| --------- | ---- | -------- | ----------- |
| `archived` | boolean | no | Limit by archived status |
| `visibility` | string | no | Limit by visibility `public`, `internal`, or `private` |
| `order_by` | string | no | Return projects ordered by `id`, `name`, `path`, `created_at`, `updated_at`, or `last_activity_at` fields. Default is `created_at` |
| `sort` | string | no | Return projects sorted in `asc` or `desc` order. Default is `desc` |
| `search` | string | no | Return list of authorized projects matching the search criteria |
| `simple` | boolean | no | Return only the ID, URL, name, and path of each project |
### List ALL projects
Get a list of all GitLab projects (admin only).
```
GET /projects/all
```
Parameters:
| Attribute | Type | Required | Description |
| --------- | ---- | -------- | ----------- |
| `archived` | boolean | no | Limit by archived status |
| `visibility` | string | no | Limit by visibility `public`, `internal`, or `private` |
| `order_by` | string | no | Return projects ordered by `id`, `name`, `path`, `created_at`, `updated_at`, or `last_activity_at` fields. Default is `created_at` |
| `sort` | string | no | Return projects sorted in `asc` or `desc` order. Default is `desc` |
| `search` | string | no | Return list of authorized projects matching the search criteria |
| `statistics` | boolean | no | Include project statistics |
### Get single project ### Get single project
Get a specific project, identified by project ID or NAMESPACE/PROJECT_NAME, which is owned by the authenticated user. Get a specific project, identified by project ID or NAMESPACE/PROJECT_NAME, which is owned by the authenticated user.
...@@ -710,7 +526,7 @@ Parameters: ...@@ -710,7 +526,7 @@ Parameters:
Forks a project into the user namespace of the authenticated user or the one provided. Forks a project into the user namespace of the authenticated user or the one provided.
``` ```
POST /projects/fork/:id POST /projects/:id/fork
``` ```
Parameters: Parameters:
......
...@@ -22,4 +22,5 @@ changes are in V4: ...@@ -22,4 +22,5 @@ changes are in V4:
- `/gitignores/:key` - `/gitignores/:key`
- `/gitlab_ci_ymls/:key` - `/gitlab_ci_ymls/:key`
- `/dockerfiles/:key` - `/dockerfiles/:key`
- Moved `/projects/fork/:id` to `/projects/:id/fork`
- Endpoints `/projects/owned`, `/projects/visible`, `/projects/starred` & `/projects/all` are consolidated into `/projects` using query parameters
...@@ -11,10 +11,10 @@ API. ...@@ -11,10 +11,10 @@ API.
--- ---
As of GitLab 8.2, GitLab CI is mainly exposed via the `/builds` page of a GitLab CI is exposed via the `/pipelines` and `/builds` pages of a project.
project. Disabling GitLab CI in a project does not delete any previous builds. Disabling GitLab CI in a project does not delete any previous builds.
In fact, the `/builds` page can still be accessed, although it's hidden from In fact, the `/pipelines` and `/builds` pages can still be accessed, although
the left sidebar menu. it's hidden from the left sidebar menu.
GitLab CI is enabled by default on new installations and can be disabled either GitLab CI is enabled by default on new installations and can be disabled either
individually under each project's settings, or site-wide by modifying the individually under each project's settings, or site-wide by modifying the
...@@ -23,12 +23,12 @@ respectively. ...@@ -23,12 +23,12 @@ respectively.
### Per-project user setting ### Per-project user setting
The setting to enable or disable GitLab CI can be found with the name **Builds** The setting to enable or disable GitLab CI can be found with the name **Pipelines**
under the **Features** area of a project's settings along with **Issues**, under the **Sharing & Permissions** area of a project's settings along with
**Merge Requests**, **Wiki** and **Snippets**. Select or deselect the checkbox **Merge Requests**. Choose one of **Disabled**, **Only team members** and
and hit **Save** for the settings to take effect. **Everyone with access** and hit **Save changes** for the settings to take effect.
![Features settings](img/features_settings.png) ![Sharing & Permissions settings](img/permissions_settings.png)
--- ---
......
This source diff could not be displayed because it is too large. You can view the blob instead.
This diff is collapsed.
...@@ -13,6 +13,28 @@ executed. ...@@ -13,6 +13,28 @@ executed.
![Pipelines example](img/pipelines.png) ![Pipelines example](img/pipelines.png)
## Types of Pipelines
There are three types of pipelines that often use the single shorthand of "pipeline". People often talk about them as if each one is "the" pipeline, but really, they're just pieces of a single, comprehensive pipeline.
![Types of Pipelines](img/types-of-pipelines.svg)
1. **CI Pipeline**: Build and test stages defined in `.gitlab-ci.yml`
2. **Deploy Pipeline**: Deploy stage(s) defined in `.gitlab-ci.yml` The flow of deploying code to servers through various stages: e.g. development to staging to production
3. **Project Pipeline**: Cross-project CI dependencies [triggered via API]((triggers)), particularly for micro-services, but also for complicated build dependencies: e.g. api -> front-end, ce/ee -> omnibus.
## Development Workflows
Pipelines accommodate several development workflows:
1. **Branch Flow** (e.g. different branch for dev, qa, staging, production)
2. **Trunk-based Flow** (e.g. feature branches and single master branch, possibly with tags for releases)
3. **Fork-based Flow** (e.g. merge requests come from forks)
Example continuous delivery flow:
![CD Flow](img/pipelines-goal.svg)
## Builds ## Builds
Builds are individual runs of [jobs]. Not to be confused with a `build` job or Builds are individual runs of [jobs]. Not to be confused with a `build` job or
......
# Database MySQL # Database MySQL
## Note >**Note:**
We do not recommend using MySQL due to various issues. For example, case
We do not recommend using MySQL due to various issues. For example, case [(in)sensitivity](https://dev.mysql.com/doc/refman/5.0/en/case-sensitivity.html) and [problems](https://bugs.mysql.com/bug.php?id=65830) that [suggested](https://bugs.mysql.com/bug.php?id=50909) [fixes](https://bugs.mysql.com/bug.php?id=65830) [have](https://bugs.mysql.com/bug.php?id=63164). [(in)sensitivity](https://dev.mysql.com/doc/refman/5.0/en/case-sensitivity.html)
and [problems](https://bugs.mysql.com/bug.php?id=65830) that
[suggested](https://bugs.mysql.com/bug.php?id=50909)
[fixes](https://bugs.mysql.com/bug.php?id=65830) [have](https://bugs.mysql.com/bug.php?id=63164).
## Initial database setup ## Initial database setup
# Install the database packages ```
sudo apt-get install -y mysql-server mysql-client libmysqlclient-dev # Install the database packages
sudo apt-get install -y mysql-server mysql-client libmysqlclient-dev
# Ensure you have MySQL version 5.5.14 or later
mysql --version
# Pick a MySQL root password (can be anything), type it and press enter # Ensure you have MySQL version 5.5.14 or later
# Retype the MySQL root password and press enter mysql --version
# Secure your installation # Pick a MySQL root password (can be anything), type it and press enter
sudo mysql_secure_installation # Retype the MySQL root password and press enter
# Login to MySQL # Secure your installation
mysql -u root -p sudo mysql_secure_installation
# Type the MySQL root password # Login to MySQL
mysql -u root -p
# Create a user for GitLab # Type the MySQL root password
# do not type the 'mysql>', this is part of the prompt
# change $password in the command below to a real password you pick
mysql> CREATE USER 'git'@'localhost' IDENTIFIED BY '$password';
# Ensure you can use the InnoDB engine which is necessary to support long indexes # Create a user for GitLab
# If this fails, check your MySQL config files (e.g. `/etc/mysql/*.cnf`, `/etc/mysql/conf.d/*`) for the setting "innodb = off" # do not type the 'mysql>', this is part of the prompt
mysql> SET storage_engine=INNODB; # change $password in the command below to a real password you pick
mysql> CREATE USER 'git'@'localhost' IDENTIFIED BY '$password';
# If you have MySQL < 5.7.7 and want to enable utf8mb4 character set support with your GitLab install, you must set the following NOW: # Ensure you can use the InnoDB engine which is necessary to support long indexes
mysql> SET GLOBAL innodb_file_per_table=1, innodb_file_format=Barracuda, innodb_large_prefix=1; # If this fails, check your MySQL config files (e.g. `/etc/mysql/*.cnf`, `/etc/mysql/conf.d/*`) for the setting "innodb = off"
mysql> SET storage_engine=INNODB;
# Create the GitLab production database # If you have MySQL < 5.7.7 and want to enable utf8mb4 character set support with your GitLab install, you must set the following NOW:
mysql> CREATE DATABASE IF NOT EXISTS `gitlabhq_production` DEFAULT CHARACTER SET `utf8` COLLATE `utf8_general_ci`; mysql> SET GLOBAL innodb_file_per_table=1, innodb_file_format=Barracuda, innodb_large_prefix=1;
# Grant the GitLab user necessary permissions on the database # Create the GitLab production database
mysql> GRANT SELECT, INSERT, UPDATE, DELETE, CREATE, CREATE TEMPORARY TABLES, DROP, INDEX, ALTER, LOCK TABLES, REFERENCES ON `gitlabhq_production`.* TO 'git'@'localhost'; mysql> CREATE DATABASE IF NOT EXISTS `gitlabhq_production` DEFAULT CHARACTER SET `utf8` COLLATE `utf8_general_ci`;
# Quit the database session # Grant the GitLab user necessary permissions on the database
mysql> \q mysql> GRANT SELECT, INSERT, UPDATE, DELETE, CREATE, CREATE TEMPORARY TABLES, DROP, INDEX, ALTER, LOCK TABLES, REFERENCES ON `gitlabhq_production`.* TO 'git'@'localhost';
# Try connecting to the new database with the new user # Quit the database session
sudo -u git -H mysql -u git -p -D gitlabhq_production mysql> \q
# Type the password you replaced $password with earlier # Try connecting to the new database with the new user
sudo -u git -H mysql -u git -p -D gitlabhq_production
# You should now see a 'mysql>' prompt # Type the password you replaced $password with earlier
# Quit the database session # You should now see a 'mysql>' prompt
mysql> \q
# You are done installing the database for now and can go back to the rest of the installation. # Quit the database session
mysql> \q
```
You are done installing the database for now and can go back to the rest of the installation.
Please proceed to the rest of the installation before running through the utf8mb4 support section. Please proceed to the rest of the installation before running through the utf8mb4 support section.
### MySQL utf8mb4 support ### MySQL utf8mb4 support
After installation or upgrade, remember to [convert any new tables](#convert) to `utf8mb4`/`utf8mb4_general_ci`. After installation or upgrade, remember to [convert any new tables](#convert) to `utf8mb4`/`utf8mb4_general_ci`.
......
...@@ -15,11 +15,11 @@ For the installations options please see [the installation page on the GitLab we ...@@ -15,11 +15,11 @@ For the installations options please see [the installation page on the GitLab we
### Unsupported Unix distributions ### Unsupported Unix distributions
- OS X
- Arch Linux - Arch Linux
- Fedora - Fedora
- Gentoo
- FreeBSD - FreeBSD
- Gentoo
- macOS
On the above unsupported distributions is still possible to install GitLab yourself. On the above unsupported distributions is still possible to install GitLab yourself.
Please see the [installation from source guide](installation.md) and the [installation guides](https://about.gitlab.com/installation/) for more information. Please see the [installation from source guide](installation.md) and the [installation guides](https://about.gitlab.com/installation/) for more information.
...@@ -120,7 +120,12 @@ To change the Unicorn workers when you have the Omnibus package please see [the ...@@ -120,7 +120,12 @@ To change the Unicorn workers when you have the Omnibus package please see [the
## Database ## Database
If you want to run the database separately expect a size of about 1 MB per user. We currently support the following databases:
- PostgreSQL (recommended)
- MySQL/MariaDB
If you want to run the database separately, expect a size of about 1 MB per user.
### PostgreSQL Requirements ### PostgreSQL Requirements
...@@ -128,7 +133,9 @@ Users using PostgreSQL must ensure the `pg_trgm` extension is loaded into every ...@@ -128,7 +133,9 @@ Users using PostgreSQL must ensure the `pg_trgm` extension is loaded into every
GitLab database. This extension can be enabled (using a PostgreSQL super user) GitLab database. This extension can be enabled (using a PostgreSQL super user)
by running the following query for every database: by running the following query for every database:
CREATE EXTENSION pg_trgm; ```
CREATE EXTENSION pg_trgm;
```
On some systems you may need to install an additional package (e.g. On some systems you may need to install an additional package (e.g.
`postgresql-contrib`) for this extension to become available. `postgresql-contrib`) for this extension to become available.
......
# Sign into GitLab with (almost) any OAuth2 provider
The `omniauth-oauth2-generic` gem allows Single Sign On between GitLab and your own OAuth2 provider
(or any OAuth2 provider compatible with this gem)
This strategy is designed to allow configuration of the simple OmniAuth SSO process outlined below:
1. Strategy directs client to your authorization URL (**configurable**), with specified ID and key
1. OAuth provider handles authentication of request, user, and (optionally) authorization to access user's profile
1. OAuth provider directs client back to GitLab where Strategy handles retrieval of access token
1. Strategy requests user information from a **configurable** "user profile" URL (using the access token)
1. Strategy parses user information from the response, using a **configurable** format
1. GitLab finds or creates the returned user and logs them in
### Limitations of this Strategy:
- It can only be used for Single Sign on, and will not provide any other access granted by any OAuth provider
(importing projects or users, etc)
- It only supports the Authorization Grant flow (most common for client-server applications, like GitLab)
- It is not able to fetch user information from more than one URL
- It has not been tested with user information formats other than JSON
### Config Instructions
1. Register your application in the OAuth2 provider you wish to authenticate with.
The redirect URI you provide when registering the application should be:
```
http://your-gitlab.host.com/users/auth/oauth2_generic/callback
```
1. You should now be able to get a Client ID and Client Secret.
Where this shows up will differ for each provider.
This may also be called Application ID and Secret
1. On your GitLab server, open the configuration file.
For Omnibus package:
```sh
sudo editor /etc/gitlab/gitlab.rb
```
For installations from source:
```sh
cd /home/git/gitlab
sudo -u git -H editor config/gitlab.yml
```
1. See [Initial OmniAuth Configuration](omniauth.md#initial-omniauth-configuration) for initial settings
1. Add the provider-specific configuration for your provider, as [described in the gem's README][1]
1. Save the configuration file
1. Restart GitLab for the changes to take effect
On the sign in page there should now be a new button below the regular sign in form.
Click the button to begin your provider's authentication process. This will direct
the browser to your OAuth2 Provider's authentication page. If everything goes well
the user will be returned to your GitLab instance and will be signed in.
[1]: https://gitlab.com/satorix/omniauth-oauth2-generic#gitlab-config-example
\ No newline at end of file
...@@ -31,6 +31,7 @@ contains some settings that are common for all providers. ...@@ -31,6 +31,7 @@ contains some settings that are common for all providers.
- [Azure](azure.md) - [Azure](azure.md)
- [Auth0](auth0.md) - [Auth0](auth0.md)
- [Authentiq](../administration/auth/authentiq.md) - [Authentiq](../administration/auth/authentiq.md)
- [OAuth2Generic](oauth2_generic.md)
## Initial OmniAuth Configuration ## Initial OmniAuth Configuration
......
...@@ -714,7 +714,7 @@ X-Gitlab-Event: Merge Request Hook ...@@ -714,7 +714,7 @@ X-Gitlab-Event: Merge Request Hook
### Wiki Page events ### Wiki Page events
Triggered when a wiki page is created or edited. Triggered when a wiki page is created, edited or deleted.
**Request Header**: **Request Header**:
......
...@@ -143,6 +143,9 @@ module API ...@@ -143,6 +143,9 @@ module API
desc: 'Return projects sorted in ascending and descending order' desc: 'Return projects sorted in ascending and descending order'
optional :simple, type: Boolean, default: false, optional :simple, type: Boolean, default: false,
desc: 'Return only the ID, URL, name, and path of each project' desc: 'Return only the ID, URL, name, and path of each project'
optional :owned, type: Boolean, default: false, desc: 'Limit by owned by authenticated user'
optional :starred, type: Boolean, default: false, desc: 'Limit by starred status'
use :pagination use :pagination
end end
get ":id/projects" do get ":id/projects" do
......
...@@ -256,6 +256,14 @@ module API ...@@ -256,6 +256,14 @@ module API
# project helpers # project helpers
def filter_projects(projects) def filter_projects(projects)
if params[:owned]
projects = projects.merge(current_user.owned_projects)
end
if params[:starred]
projects = projects.merge(current_user.starred_projects)
end
if params[:search].present? if params[:search].present?
projects = projects.search(params[:search]) projects = projects.search(params[:search])
end end
......
...@@ -50,6 +50,8 @@ module API ...@@ -50,6 +50,8 @@ module API
optional :visibility, type: String, values: %w[public internal private], optional :visibility, type: String, values: %w[public internal private],
desc: 'Limit by visibility' desc: 'Limit by visibility'
optional :search, type: String, desc: 'Return list of authorized projects matching the search criteria' optional :search, type: String, desc: 'Return list of authorized projects matching the search criteria'
optional :owned, type: Boolean, default: false, desc: 'Limit by owned by authenticated user'
optional :starred, type: Boolean, default: false, desc: 'Limit by starred status'
end end
params :statistics_params do params :statistics_params do
...@@ -82,62 +84,9 @@ module API ...@@ -82,62 +84,9 @@ module API
params do params do
use :collection_params use :collection_params
end end
get '/visible' do
entity = current_user ? Entities::ProjectWithAccess : Entities::BasicProjectDetails
present_projects ProjectsFinder.new.execute(current_user), with: entity
end
desc 'Get a projects list for authenticated user' do
success Entities::BasicProjectDetails
end
params do
use :collection_params
end
get do get do
authenticate! entity = current_user ? Entities::ProjectWithAccess : Entities::BasicProjectDetails
present_projects ProjectsFinder.new.execute(current_user), with: entity, statistics: params[:statistics]
present_projects current_user.authorized_projects,
with: Entities::ProjectWithAccess
end
desc 'Get an owned projects list for authenticated user' do
success Entities::BasicProjectDetails
end
params do
use :collection_params
use :statistics_params
end
get '/owned' do
authenticate!
present_projects current_user.owned_projects,
with: Entities::ProjectWithAccess,
statistics: params[:statistics]
end
desc 'Gets starred project for the authenticated user' do
success Entities::BasicProjectDetails
end
params do
use :collection_params
end
get '/starred' do
authenticate!
present_projects current_user.viewable_starred_projects
end
desc 'Get all projects for admin user' do
success Entities::BasicProjectDetails
end
params do
use :collection_params
use :statistics_params
end
get '/all' do
authenticated_as_admin!
present_projects Project.all, with: Entities::ProjectWithAccess, statistics: params[:statistics]
end end
desc 'Create new project' do desc 'Create new project' do
...@@ -220,7 +169,7 @@ module API ...@@ -220,7 +169,7 @@ module API
params do params do
optional :namespace, type: String, desc: 'The ID or name of the namespace that the project will be forked into' optional :namespace, type: String, desc: 'The ID or name of the namespace that the project will be forked into'
end end
post 'fork/:id' do post ':id/fork' do
fork_params = declared_params(include_missing: false) fork_params = declared_params(include_missing: false)
namespace_id = fork_params[:namespace] namespace_id = fork_params[:namespace]
......
...@@ -22,8 +22,10 @@ module Gitlab ...@@ -22,8 +22,10 @@ module Gitlab
having(action: [Event::CREATED, Event::CLOSED], target_type: "Issue") having(action: [Event::CREATED, Event::CLOSED], target_type: "Issue")
mr_events = event_counts(date_from, :merge_requests). mr_events = event_counts(date_from, :merge_requests).
having(action: [Event::MERGED, Event::CREATED, Event::CLOSED], target_type: "MergeRequest") having(action: [Event::MERGED, Event::CREATED, Event::CLOSED], target_type: "MergeRequest")
note_events = event_counts(date_from, :merge_requests).
having(action: [Event::COMMENTED], target_type: "Note")
union = Gitlab::SQL::Union.new([repo_events, issue_events, mr_events]) union = Gitlab::SQL::Union.new([repo_events, issue_events, mr_events, note_events])
events = Event.find_by_sql(union.to_sql).map(&:attributes) events = Event.find_by_sql(union.to_sql).map(&:attributes)
@activity_events = events.each_with_object(Hash.new {|h, k| h[k] = 0 }) do |event, activities| @activity_events = events.each_with_object(Hash.new {|h, k| h[k] = 0 }) do |event, activities|
...@@ -38,7 +40,7 @@ module Gitlab ...@@ -38,7 +40,7 @@ module Gitlab
# Use visible_to_user? instead of the complicated logic in activity_dates # Use visible_to_user? instead of the complicated logic in activity_dates
# because we're only viewing the events for a single day. # because we're only viewing the events for a single day.
events.select {|event| event.visible_to_user?(current_user) } events.select { |event| event.visible_to_user?(current_user) }
end end
def starting_year def starting_year
......
...@@ -6,7 +6,7 @@ module Gitlab ...@@ -6,7 +6,7 @@ module Gitlab
MAX_INT_VALUE = 2147483647 MAX_INT_VALUE = 2147483647
def self.adapter_name def self.adapter_name
connection.adapter_name ActiveRecord::Base.configurations[Rails.env]['adapter']
end end
def self.mysql? def self.mysql?
...@@ -69,6 +69,31 @@ module Gitlab ...@@ -69,6 +69,31 @@ module Gitlab
end end
end end
def self.with_connection_pool(pool_size)
pool = create_connection_pool(pool_size)
begin
yield(pool)
ensure
pool.disconnect!
end
end
def self.create_connection_pool(pool_size)
# See activerecord-4.2.7.1/lib/active_record/connection_adapters/connection_specification.rb
env = Rails.env
original_config = ActiveRecord::Base.configurations
env_config = original_config[env].merge('pool' => pool_size)
config = original_config.merge(env => env_config)
spec =
ActiveRecord::
ConnectionAdapters::
ConnectionSpecification::Resolver.new(config).spec(env.to_sym)
ActiveRecord::ConnectionAdapters::ConnectionPool.new(spec)
end
def self.connection def self.connection
ActiveRecord::Base.connection ActiveRecord::Base.connection
end end
......
unless Rails.env.production? unless Rails.env.production?
desc "GitLab | Run ESLint" desc "GitLab | Run ESLint"
task :eslint do task :eslint do
system("npm", "run", "eslint") system("yarn", "run", "eslint")
end end
end end
...@@ -11,7 +11,7 @@ unless Rails.env.production? ...@@ -11,7 +11,7 @@ unless Rails.env.production?
desc 'GitLab | Karma | Run JavaScript tests' desc 'GitLab | Karma | Run JavaScript tests'
task :tests do task :tests do
sh "npm run karma" do |ok, res| sh "yarn run karma" do |ok, res|
abort('rake karma:tests failed') unless ok abort('rake karma:tests failed') unless ok
end end
end end
......
...@@ -3,12 +3,12 @@ ...@@ -3,12 +3,12 @@
"scripts": { "scripts": {
"dev-server": "webpack-dev-server --config config/webpack.config.js", "dev-server": "webpack-dev-server --config config/webpack.config.js",
"eslint": "eslint --max-warnings 0 --ext .js,.js.es6 .", "eslint": "eslint --max-warnings 0 --ext .js,.js.es6 .",
"eslint-fix": "npm run eslint -- --fix", "eslint-fix": "eslint --max-warnings 0 --ext .js,.js.es6 --fix .",
"eslint-report": "npm run eslint -- --format html --output-file ./eslint-report.html", "eslint-report": "eslint --max-warnings 0 --ext .js,.js.es6 --format html --output-file ./eslint-report.html .",
"karma": "karma start config/karma.config.js --single-run", "karma": "karma start config/karma.config.js --single-run",
"karma-start": "karma start config/karma.config.js", "karma-start": "karma start config/karma.config.js",
"webpack": "webpack --config config/webpack.config.js", "webpack": "webpack --config config/webpack.config.js",
"webpack-prod": "NODE_ENV=production npm run webpack" "webpack-prod": "NODE_ENV=production webpack --config config/webpack.config.js"
}, },
"dependencies": { "dependencies": {
"babel-core": "^6.22.1", "babel-core": "^6.22.1",
...@@ -19,6 +19,7 @@ ...@@ -19,6 +19,7 @@
"compression-webpack-plugin": "^0.3.2", "compression-webpack-plugin": "^0.3.2",
"d3": "3.5.11", "d3": "3.5.11",
"dropzone": "4.2.0", "dropzone": "4.2.0",
"es6-promise": "^4.0.5",
"imports-loader": "^0.6.5", "imports-loader": "^0.6.5",
"jquery": "2.2.1", "jquery": "2.2.1",
"jquery-ui": "github:jquery/jquery-ui#1.11.4", "jquery-ui": "github:jquery/jquery-ui#1.11.4",
...@@ -38,6 +39,7 @@ ...@@ -38,6 +39,7 @@
"devDependencies": { "devDependencies": {
"eslint": "^3.10.1", "eslint": "^3.10.1",
"eslint-config-airbnb-base": "^10.0.1", "eslint-config-airbnb-base": "^10.0.1",
"eslint-import-resolver-webpack": "^0.8.1",
"eslint-plugin-filenames": "^1.1.0", "eslint-plugin-filenames": "^1.1.0",
"eslint-plugin-import": "^2.2.0", "eslint-plugin-import": "^2.2.0",
"eslint-plugin-jasmine": "^2.1.0", "eslint-plugin-jasmine": "^2.1.0",
......
...@@ -2,8 +2,26 @@ require 'ostruct' ...@@ -2,8 +2,26 @@ require 'ostruct'
FactoryGirl.define do FactoryGirl.define do
factory :wiki_page do factory :wiki_page do
transient do
attrs do
{
title: 'Title',
content: 'Content for wiki page',
format: 'markdown'
}
end
end
page { OpenStruct.new(url_path: 'some-name') } page { OpenStruct.new(url_path: 'some-name') }
association :wiki, factory: :project_wiki, strategy: :build association :wiki, factory: :project_wiki, strategy: :build
initialize_with { new(wiki, page, true) } initialize_with { new(wiki, page, true) }
before(:create) do |page, evaluator|
page.attributes = evaluator.attrs
end
to_create do |page|
page.create
end
end end
end end
require 'spec_helper' require 'spec_helper'
feature 'Contributions Calendar', js: true, feature: true do feature 'Contributions Calendar', :feature, :js do
include WaitForAjax include WaitForAjax
let(:user) { create(:user) }
let(:contributed_project) { create(:project, :public) } let(:contributed_project) { create(:project, :public) }
let(:issue_note) { create(:note, project: contributed_project) }
# Ex/ Sunday Jan 1, 2016 # Ex/ Sunday Jan 1, 2016
date_format = '%A %b %-d, %Y' date_format = '%A %b %-d, %Y'
...@@ -12,30 +14,30 @@ feature 'Contributions Calendar', js: true, feature: true do ...@@ -12,30 +14,30 @@ feature 'Contributions Calendar', js: true, feature: true do
issue_params = { title: issue_title } issue_params = { title: issue_title }
def get_cell_color_selector(contributions) def get_cell_color_selector(contributions)
contribution_cell = '.user-contrib-cell' activity_colors = %w[#ededed #acd5f2 #7fa8c9 #527ba0 #254e77]
activity_colors = Array['#ededed', '#acd5f2', '#7fa8c9', '#527ba0', '#254e77'] # We currently don't actually test the cases with contributions >= 20
activity_colors_index = 0 activity_colors_index =
if contributions > 0 && contributions < 10 if contributions > 0 && contributions < 10
activity_colors_index = 1 1
elsif contributions >= 10 && contributions < 20 elsif contributions >= 10 && contributions < 20
activity_colors_index = 2 2
elsif contributions >= 20 && contributions < 30 elsif contributions >= 20 && contributions < 30
activity_colors_index = 3 3
elsif contributions >= 30 elsif contributions >= 30
activity_colors_index = 4 4
else
0
end end
"#{contribution_cell}[fill='#{activity_colors[activity_colors_index]}']" ".user-contrib-cell[fill='#{activity_colors[activity_colors_index]}']"
end end
def get_cell_date_selector(contributions, date) def get_cell_date_selector(contributions, date)
contribution_text = 'No contributions' contribution_text =
if contributions.zero?
if contributions === 1 'No contributions'
contribution_text = '1 contribution' else
elsif contributions > 1 "#{contributions} #{'contribution'.pluralize(contributions)}"
contribution_text = "#{contributions} contributions"
end end
"#{get_cell_color_selector(contributions)}[data-original-title='#{contribution_text}<br />#{date}']" "#{get_cell_color_selector(contributions)}[data-original-title='#{contribution_text}<br />#{date}']"
...@@ -45,129 +47,155 @@ feature 'Contributions Calendar', js: true, feature: true do ...@@ -45,129 +47,155 @@ feature 'Contributions Calendar', js: true, feature: true do
push_params = { push_params = {
project: contributed_project, project: contributed_project,
action: Event::PUSHED, action: Event::PUSHED,
author_id: @user.id, author_id: user.id,
data: { commit_count: 3 } data: { commit_count: 3 }
} }
Event.create(push_params) Event.create(push_params)
end end
def get_first_cell_content def note_comment_contribution
note_comment_params = {
project: contributed_project,
action: Event::COMMENTED,
target: issue_note,
author_id: user.id
}
Event.create(note_comment_params)
end
def selected_day_activities
find('.user-calendar-activities').text find('.user-calendar-activities').text
end end
before do before do
login_as :user login_as user
visit @user.username end
describe 'calendar day selection' do
before do
visit user.username
wait_for_ajax wait_for_ajax
end end
it 'displays calendar', js: true do it 'displays calendar' do
expect(page).to have_css('.js-contrib-calendar') expect(page).to have_css('.js-contrib-calendar')
end end
describe 'select calendar day', js: true do describe 'select calendar day' do
let(:cells) { page.all('.user-contrib-cell') } let(:cells) { page.all('.user-contrib-cell') }
let(:first_cell_content_before) { get_first_cell_content }
before do before do
cells[0].click cells[0].click
wait_for_ajax wait_for_ajax
first_cell_content_before @first_day_activities = selected_day_activities
end end
it 'displays calendar day activities', js: true do it 'displays calendar day activities' do
expect(get_first_cell_content).not_to eq('') expect(selected_day_activities).not_to be_empty
end end
describe 'select another calendar day', js: true do describe 'select another calendar day' do
before do before do
cells[1].click cells[1].click
wait_for_ajax wait_for_ajax
end end
it 'displays different calendar day activities', js: true do it 'displays different calendar day activities' do
expect(get_first_cell_content).not_to eq(first_cell_content_before) expect(selected_day_activities).not_to eq(@first_day_activities)
end end
end end
describe 'deselect calendar day', js: true do describe 'deselect calendar day' do
before do before do
cells[0].click cells[0].click
wait_for_ajax wait_for_ajax
end end
it 'hides calendar day activities', js: true do it 'hides calendar day activities' do
expect(get_first_cell_content).to eq('') expect(selected_day_activities).to be_empty
end
end end
end end
end end
describe '1 calendar activity' do describe 'calendar daily activities' do
shared_context 'visit user page' do
before do before do
Issues::CreateService.new(contributed_project, @user, issue_params).execute visit user.username
visit @user.username
wait_for_ajax wait_for_ajax
end end
it 'displays calendar activity log', js: true do
expect(find('.content_list .event-note')).to have_content issue_title
end end
it 'displays calendar activity square color for 1 contribution', js: true do shared_examples 'a day with activity' do |contribution_count:|
expect(page).to have_selector(get_cell_color_selector(1), count: 1) include_context 'visit user page'
it 'displays calendar activity square color for 1 contribution' do
expect(page).to have_selector(get_cell_color_selector(contribution_count), count: 1)
end end
it 'displays calendar activity square on the correct date', js: true do it 'displays calendar activity square on the correct date' do
today = Date.today.strftime(date_format) today = Date.today.strftime(date_format)
expect(page).to have_selector(get_cell_date_selector(1, today), count: 1) expect(page).to have_selector(get_cell_date_selector(contribution_count, today), count: 1)
end end
end end
describe '10 calendar activities' do describe '1 issue creation calendar activity' do
before do before do
(0..9).each do |i| Issues::CreateService.new(contributed_project, user, issue_params).execute
push_code_contribution()
end end
visit @user.username it_behaves_like 'a day with activity', contribution_count: 1
wait_for_ajax
describe 'issue title is shown on activity page' do
include_context 'visit user page'
it 'displays calendar activity log' do
expect(find('.content_list .event-note')).to have_content issue_title
end
end
end end
it 'displays calendar activity square color for 10 contributions', js: true do describe '1 comment calendar activity' do
expect(page).to have_selector(get_cell_color_selector(10), count: 1) before do
note_comment_contribution
end end
it 'displays calendar activity square on the correct date', js: true do it_behaves_like 'a day with activity', contribution_count: 1
today = Date.today.strftime(date_format) end
expect(page).to have_selector(get_cell_date_selector(10, today), count: 1)
describe '10 calendar activities' do
before do
10.times { push_code_contribution }
end end
it_behaves_like 'a day with activity', contribution_count: 10
end end
describe 'calendar activity on two days' do describe 'calendar activity on two days' do
before do before do
push_code_contribution() push_code_contribution
Timecop.freeze(Date.yesterday) Timecop.freeze(Date.yesterday) do
Issues::CreateService.new(contributed_project, @user, issue_params).execute Issues::CreateService.new(contributed_project, user, issue_params).execute
Timecop.return
visit @user.username
wait_for_ajax
end end
end
include_context 'visit user page'
it 'displays calendar activity squares for both days', js: true do it 'displays calendar activity squares for both days' do
expect(page).to have_selector(get_cell_color_selector(1), count: 2) expect(page).to have_selector(get_cell_color_selector(1), count: 2)
end end
it 'displays calendar activity square for yesterday', js: true do it 'displays calendar activity square for yesterday' do
yesterday = Date.yesterday.strftime(date_format) yesterday = Date.yesterday.strftime(date_format)
expect(page).to have_selector(get_cell_date_selector(1, yesterday), count: 1) expect(page).to have_selector(get_cell_date_selector(1, yesterday), count: 1)
end end
it 'displays calendar activity square for today', js: true do it 'displays calendar activity square for today' do
today = Date.today.strftime(date_format) today = Date.today.strftime(date_format)
expect(page).to have_selector(get_cell_date_selector(1, today), count: 1) expect(page).to have_selector(get_cell_date_selector(1, today), count: 1)
end end
end end
end
end end
require 'spec_helper' require 'spec_helper'
feature 'Create New Merge Request', feature: true, js: true do feature 'Create New Merge Request', feature: true, js: true do
include WaitForVueResource
let(:user) { create(:user) } let(:user) { create(:user) }
let(:project) { create(:project, :public) } let(:project) { create(:project, :public) }
...@@ -99,6 +101,7 @@ feature 'Create New Merge Request', feature: true, js: true do ...@@ -99,6 +101,7 @@ feature 'Create New Merge Request', feature: true, js: true do
page.within('.merge-request') do page.within('.merge-request') do
click_link 'Pipelines' click_link 'Pipelines'
wait_for_vue_resource
expect(page).to have_content "##{pipeline.id}" expect(page).to have_content "##{pipeline.id}"
end end
......
...@@ -20,10 +20,10 @@ feature 'Ref switcher', feature: true, js: true do ...@@ -20,10 +20,10 @@ feature 'Ref switcher', feature: true, js: true do
input.set 'binary' input.set 'binary'
wait_for_ajax wait_for_ajax
input.native.send_keys :down page.within '.dropdown-content ul' do
input.native.send_keys :down
input.native.send_keys :enter input.native.send_keys :enter
end end
end
expect(page).to have_title 'binary-encoding' expect(page).to have_title 'binary-encoding'
end end
......
...@@ -4,18 +4,19 @@ require('~/behaviors/requires_input'); ...@@ -4,18 +4,19 @@ require('~/behaviors/requires_input');
(function() { (function() {
describe('requiresInput', function() { describe('requiresInput', function() {
preloadFixtures('static/behaviors/requires_input.html.raw'); preloadFixtures('branches/new_branch.html.raw');
beforeEach(function() { beforeEach(function() {
return loadFixtures('static/behaviors/requires_input.html.raw'); loadFixtures('branches/new_branch.html.raw');
this.submitButton = $('button[type="submit"]');
}); });
it('disables submit when any field is required', function() { it('disables submit when any field is required', function() {
$('.js-requires-input').requiresInput(); $('.js-requires-input').requiresInput();
return expect($('.submit')).toBeDisabled(); return expect(this.submitButton).toBeDisabled();
}); });
it('enables submit when no field is required', function() { it('enables submit when no field is required', function() {
$('*[required=required]').removeAttr('required'); $('*[required=required]').removeAttr('required');
$('.js-requires-input').requiresInput(); $('.js-requires-input').requiresInput();
return expect($('.submit')).not.toBeDisabled(); return expect(this.submitButton).not.toBeDisabled();
}); });
it('enables submit when all required fields are pre-filled', function() { it('enables submit when all required fields are pre-filled', function() {
$('*[required=required]').remove(); $('*[required=required]').remove();
...@@ -25,9 +26,9 @@ require('~/behaviors/requires_input'); ...@@ -25,9 +26,9 @@ require('~/behaviors/requires_input');
it('enables submit when all required fields receive input', function() { it('enables submit when all required fields receive input', function() {
$('.js-requires-input').requiresInput(); $('.js-requires-input').requiresInput();
$('#required1').val('input1').change(); $('#required1').val('input1').change();
expect($('.submit')).toBeDisabled(); expect(this.submitButton).toBeDisabled();
$('#optional1').val('input1').change(); $('#optional1').val('input1').change();
expect($('.submit')).toBeDisabled(); expect(this.submitButton).toBeDisabled();
$('#required2').val('input2').change(); $('#required2').val('input2').change();
$('#required3').val('input3').change(); $('#required3').val('input3').change();
$('#required4').val('input4').change(); $('#required4').val('input4').change();
......
%form.js-requires-input
%input{type: 'text', id: 'required1', required: 'required'}
%input{type: 'text', id: 'required2', required: 'required'}
%input{type: 'text', id: 'required3', required: 'required', value: 'Pre-filled'}
%input{type: 'text', id: 'optional1'}
%textarea{id: 'required4', required: 'required'}
%textarea{id: 'optional2'}
%select{id: 'required5', required: 'required'}
%option Zero
%option{value: '1'} One
%select{id: 'optional3', required: 'required'}
%option Zero
%option{value: '1'} One
%button.submit{type: 'submit', value: 'Submit'}
%input.submit{type: 'submit', value: 'Submit'}
{
"count": 1,
"delete_path": "/dashboard/todos/1"
}
\ No newline at end of file
require 'spec_helper'
describe 'Todos (JavaScript fixtures)' do
include JavaScriptFixturesHelpers
let(:admin) { create(:admin) }
let(:namespace) { create(:namespace, name: 'frontend-fixtures' )}
let(:project) { create(:project_empty_repo, namespace: namespace, path: 'todos-project') }
let(:issue_1) { create(:issue, title: 'issue_1', project: project) }
let!(:todo_1) { create(:todo, user: admin, project: project, target: issue_1, created_at: 5.hours.ago) }
let(:issue_2) { create(:issue, title: 'issue_2', project: project) }
let!(:todo_2) { create(:todo, :done, user: admin, project: project, target: issue_2, created_at: 50.hours.ago) }
before(:all) do
clean_frontend_fixtures('todos/')
end
describe Dashboard::TodosController, '(JavaScript fixtures)', type: :controller do
render_views
before(:each) do
sign_in(admin)
end
it 'todos/todos.html.raw' do |example|
get :index
expect(response).to be_success
store_frontend_fixture(response, example.description)
end
end
describe Projects::TodosController, '(JavaScript fixtures)', type: :controller do
render_views
before(:each) do
sign_in(admin)
end
it 'todos/todos.json' do |example|
post :create,
namespace_id: namespace.path,
project_id: project.path,
issuable_type: 'issue',
issuable_id: issue_2.id,
format: 'json'
expect(response).to be_success
store_frontend_fixture(response, example.description)
end
end
end
...@@ -139,6 +139,14 @@ require('~/lib/utils/url_utility'); ...@@ -139,6 +139,14 @@ require('~/lib/utils/url_utility');
this.dropdownButtonElement.click(); this.dropdownButtonElement.click();
}); });
it('should show loading indicator while search results are being fetched by backend', () => {
const dropdownMenu = document.querySelector('.dropdown-menu');
expect(dropdownMenu.className.indexOf('is-loading') !== -1).toEqual(true);
remoteCallback();
expect(dropdownMenu.className.indexOf('is-loading') !== -1).toEqual(false);
});
it('should not focus search input while remote task is not complete', () => { it('should not focus search input while remote task is not complete', () => {
expect($(document.activeElement)).not.toEqual($(SEARCH_INPUT_SELECTOR)); expect($(document.activeElement)).not.toEqual($(SEARCH_INPUT_SELECTOR));
remoteCallback(); remoteCallback();
......
...@@ -34,7 +34,7 @@ require('~/extensions/jquery.js'); ...@@ -34,7 +34,7 @@ require('~/extensions/jquery.js');
describe('RightSidebar', function() { describe('RightSidebar', function() {
var fixtureName = 'issues/open-issue.html.raw'; var fixtureName = 'issues/open-issue.html.raw';
preloadFixtures(fixtureName); preloadFixtures(fixtureName);
loadJSONFixtures('todos.json'); loadJSONFixtures('todos/todos.json');
beforeEach(function() { beforeEach(function() {
loadFixtures(fixtureName); loadFixtures(fixtureName);
...@@ -64,7 +64,7 @@ require('~/extensions/jquery.js'); ...@@ -64,7 +64,7 @@ require('~/extensions/jquery.js');
}); });
it('should broadcast todo:toggle event when add todo clicked', function() { it('should broadcast todo:toggle event when add todo clicked', function() {
var todos = getJSONFixture('todos.json'); var todos = getJSONFixture('todos/todos.json');
spyOn(jQuery, 'ajax').and.callFake(function() { spyOn(jQuery, 'ajax').and.callFake(function() {
var d = $.Deferred(); var d = $.Deferred();
var response = todos; var response = todos;
......
...@@ -5,6 +5,12 @@ class MigrationTest ...@@ -5,6 +5,12 @@ class MigrationTest
end end
describe Gitlab::Database, lib: true do describe Gitlab::Database, lib: true do
describe '.adapter_name' do
it 'returns the name of the adapter' do
expect(described_class.adapter_name).to be_an_instance_of(String)
end
end
# These are just simple smoke tests to check if the methods work (regardless # These are just simple smoke tests to check if the methods work (regardless
# of what they may return). # of what they may return).
describe '.mysql?' do describe '.mysql?' do
...@@ -71,6 +77,54 @@ describe Gitlab::Database, lib: true do ...@@ -71,6 +77,54 @@ describe Gitlab::Database, lib: true do
end end
end end
describe '.with_connection_pool' do
it 'creates a new connection pool and disconnect it after used' do
closed_pool = nil
described_class.with_connection_pool(1) do |pool|
pool.with_connection do |connection|
connection.execute('SELECT 1 AS value')
end
expect(pool).to be_connected
closed_pool = pool
end
expect(closed_pool).not_to be_connected
end
it 'disconnects the pool even an exception was raised' do
error = Class.new(RuntimeError)
closed_pool = nil
begin
described_class.with_connection_pool(1) do |pool|
pool.with_connection do |connection|
connection.execute('SELECT 1 AS value')
end
closed_pool = pool
raise error.new('boom')
end
rescue error
end
expect(closed_pool).not_to be_connected
end
end
describe '.create_connection_pool' do
it 'creates a new connection pool with specific pool size' do
pool = described_class.create_connection_pool(5)
expect(pool)
.to be_kind_of(ActiveRecord::ConnectionAdapters::ConnectionPool)
expect(pool.spec.config[:pool]).to eq(5)
end
end
describe '#true_value' do describe '#true_value' do
it 'returns correct value for PostgreSQL' do it 'returns correct value for PostgreSQL' do
expect(described_class).to receive(:postgresql?).and_return(true) expect(described_class).to receive(:postgresql?).and_return(true)
......
...@@ -318,6 +318,19 @@ describe WikiPage, models: true do ...@@ -318,6 +318,19 @@ describe WikiPage, models: true do
end end
end end
describe '#==' do
let(:original_wiki_page) { create(:wiki_page) }
it 'returns true for identical wiki page' do
expect(original_wiki_page).to eq(original_wiki_page)
end
it 'returns false for updated wiki page' do
updated_wiki_page = original_wiki_page.update("Updated content")
expect(original_wiki_page).not_to eq(updated_wiki_page)
end
end
private private
def remove_temp_repo(path) def remove_temp_repo(path)
......
require 'spec_helper'
describe API::Projects, api: true do
include ApiHelpers
let(:user) { create(:user) }
let(:user2) { create(:user) }
let(:admin) { create(:admin) }
let(:group) { create(:group) }
let(:group2) do
group = create(:group, name: 'group2_name')
group.add_owner(user2)
group
end
describe 'POST /projects/fork/:id' do
let(:project) do
create(:project, :repository, creator: user, namespace: user.namespace)
end
before do
project.add_reporter(user2)
end
context 'when authenticated' do
it 'forks if user has sufficient access to project' do
post api("/projects/fork/#{project.id}", user2)
expect(response).to have_http_status(201)
expect(json_response['name']).to eq(project.name)
expect(json_response['path']).to eq(project.path)
expect(json_response['owner']['id']).to eq(user2.id)
expect(json_response['namespace']['id']).to eq(user2.namespace.id)
expect(json_response['forked_from_project']['id']).to eq(project.id)
end
it 'forks if user is admin' do
post api("/projects/fork/#{project.id}", admin)
expect(response).to have_http_status(201)
expect(json_response['name']).to eq(project.name)
expect(json_response['path']).to eq(project.path)
expect(json_response['owner']['id']).to eq(admin.id)
expect(json_response['namespace']['id']).to eq(admin.namespace.id)
expect(json_response['forked_from_project']['id']).to eq(project.id)
end
it 'fails on missing project access for the project to fork' do
new_user = create(:user)
post api("/projects/fork/#{project.id}", new_user)
expect(response).to have_http_status(404)
expect(json_response['message']).to eq('404 Project Not Found')
end
it 'fails if forked project exists in the user namespace' do
post api("/projects/fork/#{project.id}", user)
expect(response).to have_http_status(409)
expect(json_response['message']['name']).to eq(['has already been taken'])
expect(json_response['message']['path']).to eq(['has already been taken'])
end
it 'fails if project to fork from does not exist' do
post api('/projects/fork/424242', user)
expect(response).to have_http_status(404)
expect(json_response['message']).to eq('404 Project Not Found')
end
it 'forks with explicit own user namespace id' do
post api("/projects/fork/#{project.id}", user2), namespace: user2.namespace.id
expect(response).to have_http_status(201)
expect(json_response['owner']['id']).to eq(user2.id)
end
it 'forks with explicit own user name as namespace' do
post api("/projects/fork/#{project.id}", user2), namespace: user2.username
expect(response).to have_http_status(201)
expect(json_response['owner']['id']).to eq(user2.id)
end
it 'forks to another user when admin' do
post api("/projects/fork/#{project.id}", admin), namespace: user2.username
expect(response).to have_http_status(201)
expect(json_response['owner']['id']).to eq(user2.id)
end
it 'fails if trying to fork to another user when not admin' do
post api("/projects/fork/#{project.id}", user2), namespace: admin.namespace.id
expect(response).to have_http_status(404)
end
it 'fails if trying to fork to non-existent namespace' do
post api("/projects/fork/#{project.id}", user2), namespace: 42424242
expect(response).to have_http_status(404)
expect(json_response['message']).to eq('404 Target Namespace Not Found')
end
it 'forks to owned group' do
post api("/projects/fork/#{project.id}", user2), namespace: group2.name
expect(response).to have_http_status(201)
expect(json_response['namespace']['name']).to eq(group2.name)
end
it 'fails to fork to not owned group' do
post api("/projects/fork/#{project.id}", user2), namespace: group.name
expect(response).to have_http_status(404)
end
it 'forks to not owned group when admin' do
post api("/projects/fork/#{project.id}", admin), namespace: group.name
expect(response).to have_http_status(201)
expect(json_response['namespace']['name']).to eq(group.name)
end
end
context 'when unauthenticated' do
it 'returns authentication error' do
post api("/projects/fork/#{project.id}")
expect(response).to have_http_status(401)
expect(json_response['message']).to eq('401 Unauthorized')
end
end
end
end
...@@ -338,6 +338,26 @@ describe API::Groups, api: true do ...@@ -338,6 +338,26 @@ describe API::Groups, api: true do
expect(json_response.length).to eq(1) expect(json_response.length).to eq(1)
expect(json_response.first['name']).to eq(project3.name) expect(json_response.first['name']).to eq(project3.name)
end end
it 'only returns the projects owned by user' do
project2.group.add_owner(user3)
get api("/groups/#{project2.group.id}/projects", user3), owned: true
expect(response).to have_http_status(200)
expect(json_response.length).to eq(1)
expect(json_response.first['name']).to eq(project2.name)
end
it 'only returns the projects starred by user' do
user1.starred_projects = [project1]
get api("/groups/#{group1.id}/projects", user1), starred: true
expect(response).to have_http_status(200)
expect(json_response.length).to eq(1)
expect(json_response.first['name']).to eq(project1.name)
end
end end
context "when authenticated as admin" do context "when authenticated as admin" do
......
This diff is collapsed.
require 'spec_helper'
describe WikiPages::CreateService, services: true do
let(:project) { create(:empty_project) }
let(:user) { create(:user) }
let(:opts) do
{
title: 'Title',
content: 'Content for wiki page',
format: 'markdown'
}
end
let(:service) { described_class.new(project, user, opts) }
describe '#execute' do
context "valid params" do
before do
allow(service).to receive(:execute_hooks)
project.add_master(user)
end
subject { service.execute }
it 'creates a valid wiki page' do
is_expected.to be_valid
expect(subject.title).to eq(opts[:title])
expect(subject.content).to eq(opts[:content])
expect(subject.format).to eq(opts[:format].to_sym)
end
it 'executes webhooks' do
expect(service).to have_received(:execute_hooks).once.with(subject, 'create')
end
end
end
end
require 'spec_helper'
describe WikiPages::DestroyService, services: true do
let(:project) { create(:empty_project) }
let(:user) { create(:user) }
let(:wiki_page) { create(:wiki_page) }
let(:service) { described_class.new(project, user) }
describe '#execute' do
before do
allow(service).to receive(:execute_hooks)
project.add_master(user)
end
it 'executes webhooks' do
service.execute(wiki_page)
expect(service).to have_received(:execute_hooks).once.with(wiki_page, 'delete')
end
end
end
require 'spec_helper'
describe WikiPages::UpdateService, services: true do
let(:project) { create(:empty_project) }
let(:user) { create(:user) }
let(:wiki_page) { create(:wiki_page) }
let(:opts) do
{
content: 'New content for wiki page',
format: 'markdown',
message: 'New wiki message'
}
end
let(:service) { described_class.new(project, user, opts) }
describe '#execute' do
context "valid params" do
before do
allow(service).to receive(:execute_hooks)
project.add_master(user)
end
subject { service.execute(wiki_page) }
it 'updates the wiki page' do
is_expected.to be_valid
expect(subject.content).to eq(opts[:content])
expect(subject.format).to eq(opts[:format].to_sym)
expect(subject.message).to eq(opts[:message])
end
it 'executes webhooks' do
expect(service).to have_received(:execute_hooks).once.with(subject, 'update')
end
end
end
end
This diff is collapsed.
This diff is collapsed.
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