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 @@
"localStorage": false
},
"plugins": [
"filenames"
"filenames",
"import"
],
"settings": {
"import/resolver": {
"webpack": {
"config": "./config/webpack.config.js"
}
}
},
"rules": {
"filenames/match-regex": [2, "^[a-z0-9_]+(.js)?$"],
"no-multiple-empty-lines": ["error", { "max": 1 }],
"import/no-extraneous-dependencies": "off",
"import/no-unresolved": "off"
"no-multiple-empty-lines": ["error", { "max": 1 }]
}
}
......@@ -107,7 +107,10 @@ setup-test-env:
<<: *dedicated-runner
stage: prepare
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 ruby -Ispec -e 'require "spec_helper" ; TestEnv.init'
artifacts:
......@@ -246,7 +249,6 @@ karma:
<<: *use-db
<<: *dedicated-runner
script:
- npm link istanbul
- bundle exec rake karma
artifacts:
name: coverage-javascript
......@@ -325,11 +327,9 @@ lint:javascript:
paths:
- node_modules/
stage: test
image: "node:7.1"
before_script:
- npm install
before_script: []
script:
- npm --silent run eslint
- yarn run eslint
lint:javascript:report:
<<: *dedicated-runner
......@@ -337,12 +337,10 @@ lint:javascript:report:
paths:
- node_modules/
stage: post-test
image: "node:7.1"
before_script:
- npm install
before_script: []
script:
- 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:
name: eslint-report
expire_in: 31d
......
......@@ -29,6 +29,7 @@ gem 'omniauth-github', '~> 1.1.1'
gem 'omniauth-gitlab', '~> 1.0.2'
gem 'omniauth-google-oauth2', '~> 0.4.1'
gem 'omniauth-kerberos', '~> 0.3.0', group: :kerberos
gem 'omniauth-oauth2-generic', '~> 0.2.2'
gem 'omniauth-saml', '~> 1.7.0'
gem 'omniauth-shibboleth', '~> 1.2.0'
gem 'omniauth-twitter', '~> 1.2.0'
......
......@@ -483,6 +483,8 @@ GEM
omniauth-oauth2 (1.3.1)
oauth2 (~> 1.0)
omniauth (~> 1.2)
omniauth-oauth2-generic (0.2.2)
omniauth-oauth2 (~> 1.0)
omniauth-saml (1.7.0)
omniauth (~> 1.3)
ruby-saml (~> 1.4)
......@@ -931,6 +933,7 @@ DEPENDENCIES
omniauth-gitlab (~> 1.0.2)
omniauth-google-oauth2 (~> 0.4.1)
omniauth-kerberos (~> 0.3.0)
omniauth-oauth2-generic (~> 0.2.2)
omniauth-saml (~> 1.7.0)
omniauth-shibboleth (~> 1.2.0)
omniauth-twitter (~> 1.2.0)
......
......@@ -56,8 +56,7 @@ requireAll(require.context('./u2f', false, /^\.\/.*\.(js|es6)$/));
requireAll(require.context('./droplab', false, /^\.\/.*\.(js|es6)$/));
requireAll(require.context('.', false, /^\.\/(?!application\.js).*\.(js|es6)$/));
require('vendor/fuzzaldrin-plus');
window.ES6Promise = require('vendor/es6-promise.auto');
window.ES6Promise.polyfill();
require('es6-promise').polyfill();
(function () {
document.addEventListener('beforeunload', function () {
......
......@@ -8,7 +8,22 @@
* Uses Vue.Resource
*/
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);
}
......
......@@ -47,9 +47,11 @@
}
// Only filter asynchronously only if option remote is set
if (this.options.remote) {
$inputContainer.parent().addClass('is-loading');
clearTimeout(timeout);
return timeout = setTimeout(function() {
return this.options.query(this.input.val(), function(data) {
$inputContainer.parent().removeClass('is-loading');
return this.options.callback(data);
}.bind(this));
}.bind(this), 250);
......
......@@ -864,7 +864,7 @@
overflow: hidden;
white-space: nowrap;
text-overflow: ellipsis;
width: 90px;
max-width: 70%;
color: $gl-text-color-secondary;
margin-left: 2px;
display: inline-block;
......
......@@ -84,7 +84,7 @@ class Projects::WikisController < Projects::ApplicationController
def destroy
@page = @project_wiki.find_page(params[:id])
@page&.delete
WikiPages::DestroyService.new(@project, current_user).execute(@page)
redirect_to(
namespace_project_wiki_path(@project.namespace, @project, :home),
......
......@@ -45,9 +45,10 @@ class Event < ActiveRecord::Base
class << self
# Update Gitlab::ContributionsCalendar#activity_dates if this changes
def contributions
where("action = ? OR (target_type in (?) AND action in (?))",
Event::PUSHED, ["MergeRequest", "Issue"],
[Event::CREATED, Event::CLOSED, Event::MERGED])
where("action = ? OR (target_type IN (?) AND action IN (?)) OR (target_type = ? AND action = ?)",
Event::PUSHED,
["MergeRequest", "Issue"], [Event::CREATED, Event::CLOSED, Event::MERGED],
"Note", Event::COMMENTED)
end
def limit_recent(limit = 20, offset = nil)
......
......@@ -207,6 +207,10 @@ class WikiPage
'projects/wikis/wiki_page'
end
def id
page.version.to_s
end
private
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
.js-contrib-calendar
.calendar-hint
Summary of issues, merge requests, and push events
Summary of issues, merge requests, push events, and comments
:javascript
new Calendar(
#{@activity_dates.to_json},
......
......@@ -13,8 +13,10 @@
#{event.action_name} #{event.ref_type} #{event.ref_name}
- else
= event_action_name(event)
- if event.target
%strong= link_to "#{event.target.to_reference}", [event.project.namespace.becomes(Namespace), event.project, event.target]
- if event.note?
%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
%strong
......
......@@ -106,6 +106,8 @@
%i.fa.fa-spinner.fa-spin
.user-calendar-activities
%h4.prepend-top-20
Most Recent Activity
.content_list{ data: { href: user_path } }
= 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:
| `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 |
| `owned` | boolean | no | Limit by projects owned by the current user |
| `starred` | boolean | no | Limit by projects starred by the current user |
Example response:
......
......@@ -36,6 +36,8 @@ Parameters:
| `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 |
| `owned` | boolean | no | Limit by projects owned by the current user |
| `starred` | boolean | no | Limit by projects starred by the current user |
```json
[
......@@ -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 a specific project, identified by project ID or NAMESPACE/PROJECT_NAME, which is owned by the authenticated user.
......@@ -710,7 +526,7 @@ Parameters:
Forks a project into the user namespace of the authenticated user or the one provided.
```
POST /projects/fork/:id
POST /projects/:id/fork
```
Parameters:
......@@ -1378,4 +1194,4 @@ Parameters:
| --------- | ---- | -------- | ----------- |
| `query` | string | yes | A string contained in the project name |
| `order_by` | string | no | Return requests ordered by `id`, `name`, `created_at` or `last_activity_at` fields |
| `sort` | string | no | Return requests sorted in `asc` or `desc` order |
| `sort` | string | no | Return requests sorted in `asc` or `desc` order |
\ No newline at end of file
......@@ -22,4 +22,5 @@ changes are in V4:
- `/gitignores/:key`
- `/gitlab_ci_ymls/: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.
---
As of GitLab 8.2, GitLab CI is mainly exposed via the `/builds` page of a
project. 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
the left sidebar menu.
GitLab CI is exposed via the `/pipelines` and `/builds` pages of a project.
Disabling GitLab CI in a project does not delete any previous builds.
In fact, the `/pipelines` and `/builds` pages can still be accessed, although
it's hidden from the left sidebar menu.
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
......@@ -23,12 +23,12 @@ respectively.
### Per-project user setting
The setting to enable or disable GitLab CI can be found with the name **Builds**
under the **Features** area of a project's settings along with **Issues**,
**Merge Requests**, **Wiki** and **Snippets**. Select or deselect the checkbox
and hit **Save** for the settings to take effect.
The setting to enable or disable GitLab CI can be found with the name **Pipelines**
under the **Sharing & Permissions** area of a project's settings along with
**Merge Requests**. Choose one of **Disabled**, **Only team members** and
**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.
![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 are individual runs of [jobs]. Not to be confused with a `build` job or
......
# Database MySQL
## Note
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).
>**Note:**
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).
## Initial database setup
# 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
```
# Install the database packages
sudo apt-get install -y mysql-server mysql-client libmysqlclient-dev
# Pick a MySQL root password (can be anything), type it and press enter
# Retype the MySQL root password and press enter
# Ensure you have MySQL version 5.5.14 or later
mysql --version
# Secure your installation
sudo mysql_secure_installation
# Pick a MySQL root password (can be anything), type it and press enter
# Retype the MySQL root password and press enter
# Login to MySQL
mysql -u root -p
# Secure your installation
sudo mysql_secure_installation
# Type the MySQL root password
# Login to MySQL
mysql -u root -p
# Create a user for GitLab
# 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';
# Type the MySQL root password
# Ensure you can use the InnoDB engine which is necessary to support long indexes
# 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 a user for GitLab
# 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';
# 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> SET GLOBAL innodb_file_per_table=1, innodb_file_format=Barracuda, innodb_large_prefix=1;
# Ensure you can use the InnoDB engine which is necessary to support long indexes
# 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
mysql> CREATE DATABASE IF NOT EXISTS `gitlabhq_production` DEFAULT CHARACTER SET `utf8` COLLATE `utf8_general_ci`;
# 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> SET GLOBAL innodb_file_per_table=1, innodb_file_format=Barracuda, innodb_large_prefix=1;
# Grant the GitLab user necessary permissions on the database
mysql> GRANT SELECT, INSERT, UPDATE, DELETE, CREATE, CREATE TEMPORARY TABLES, DROP, INDEX, ALTER, LOCK TABLES, REFERENCES ON `gitlabhq_production`.* TO 'git'@'localhost';
# Create the GitLab production database
mysql> CREATE DATABASE IF NOT EXISTS `gitlabhq_production` DEFAULT CHARACTER SET `utf8` COLLATE `utf8_general_ci`;
# Quit the database session
mysql> \q
# Grant the GitLab user necessary permissions on the database
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
sudo -u git -H mysql -u git -p -D gitlabhq_production
# Quit the database session
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
mysql> \q
# You should now see a 'mysql>' prompt
# 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.
### MySQL utf8mb4 support
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
### Unsupported Unix distributions
- OS X
- Arch Linux
- Fedora
- Gentoo
- FreeBSD
- Gentoo
- macOS
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.
......@@ -120,7 +120,12 @@ To change the Unicorn workers when you have the Omnibus package please see [the
## 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
......@@ -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)
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.
`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.
- [Azure](azure.md)
- [Auth0](auth0.md)
- [Authentiq](../administration/auth/authentiq.md)
- [OAuth2Generic](oauth2_generic.md)
## Initial OmniAuth Configuration
......
......@@ -714,7 +714,7 @@ X-Gitlab-Event: Merge Request Hook
### Wiki Page events
Triggered when a wiki page is created or edited.
Triggered when a wiki page is created, edited or deleted.
**Request Header**:
......
......@@ -143,6 +143,9 @@ module API
desc: 'Return projects sorted in ascending and descending order'
optional :simple, type: Boolean, default: false,
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
end
get ":id/projects" do
......
......@@ -256,6 +256,14 @@ module API
# project helpers
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?
projects = projects.search(params[:search])
end
......
......@@ -50,6 +50,8 @@ module API
optional :visibility, type: String, values: %w[public internal private],
desc: 'Limit by visibility'
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
params :statistics_params do
......@@ -82,62 +84,9 @@ module API
params do
use :collection_params
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
authenticate!
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]
entity = current_user ? Entities::ProjectWithAccess : Entities::BasicProjectDetails
present_projects ProjectsFinder.new.execute(current_user), with: entity, statistics: params[:statistics]
end
desc 'Create new project' do
......@@ -220,7 +169,7 @@ module API
params do
optional :namespace, type: String, desc: 'The ID or name of the namespace that the project will be forked into'
end
post 'fork/:id' do
post ':id/fork' do
fork_params = declared_params(include_missing: false)
namespace_id = fork_params[:namespace]
......
......@@ -22,8 +22,10 @@ module Gitlab
having(action: [Event::CREATED, Event::CLOSED], target_type: "Issue")
mr_events = event_counts(date_from, :merge_requests).
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)
@activity_events = events.each_with_object(Hash.new {|h, k| h[k] = 0 }) do |event, activities|
......@@ -38,7 +40,7 @@ module Gitlab
# Use visible_to_user? instead of the complicated logic in activity_dates
# 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
def starting_year
......
......@@ -6,7 +6,7 @@ module Gitlab
MAX_INT_VALUE = 2147483647
def self.adapter_name
connection.adapter_name
ActiveRecord::Base.configurations[Rails.env]['adapter']
end
def self.mysql?
......@@ -69,6 +69,31 @@ module Gitlab
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
ActiveRecord::Base.connection
end
......
unless Rails.env.production?
desc "GitLab | Run ESLint"
task :eslint do
system("npm", "run", "eslint")
system("yarn", "run", "eslint")
end
end
......@@ -11,7 +11,7 @@ unless Rails.env.production?
desc 'GitLab | Karma | Run JavaScript tests'
task :tests do
sh "npm run karma" do |ok, res|
sh "yarn run karma" do |ok, res|
abort('rake karma:tests failed') unless ok
end
end
......
......@@ -3,12 +3,12 @@
"scripts": {
"dev-server": "webpack-dev-server --config config/webpack.config.js",
"eslint": "eslint --max-warnings 0 --ext .js,.js.es6 .",
"eslint-fix": "npm run eslint -- --fix",
"eslint-report": "npm run eslint -- --format html --output-file ./eslint-report.html",
"eslint-fix": "eslint --max-warnings 0 --ext .js,.js.es6 --fix .",
"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-start": "karma start config/karma.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": {
"babel-core": "^6.22.1",
......@@ -19,6 +19,7 @@
"compression-webpack-plugin": "^0.3.2",
"d3": "3.5.11",
"dropzone": "4.2.0",
"es6-promise": "^4.0.5",
"imports-loader": "^0.6.5",
"jquery": "2.2.1",
"jquery-ui": "github:jquery/jquery-ui#1.11.4",
......@@ -38,6 +39,7 @@
"devDependencies": {
"eslint": "^3.10.1",
"eslint-config-airbnb-base": "^10.0.1",
"eslint-import-resolver-webpack": "^0.8.1",
"eslint-plugin-filenames": "^1.1.0",
"eslint-plugin-import": "^2.2.0",
"eslint-plugin-jasmine": "^2.1.0",
......
......@@ -2,8 +2,26 @@ require 'ostruct'
FactoryGirl.define 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') }
association :wiki, factory: :project_wiki, strategy: :build
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
require 'spec_helper'
feature 'Contributions Calendar', js: true, feature: true do
feature 'Contributions Calendar', :feature, :js do
include WaitForAjax
let(:user) { create(:user) }
let(:contributed_project) { create(:project, :public) }
let(:issue_note) { create(:note, project: contributed_project) }
# Ex/ Sunday Jan 1, 2016
date_format = '%A %b %-d, %Y'
......@@ -12,31 +14,31 @@ feature 'Contributions Calendar', js: true, feature: true do
issue_params = { title: issue_title }
def get_cell_color_selector(contributions)
contribution_cell = '.user-contrib-cell'
activity_colors = Array['#ededed', '#acd5f2', '#7fa8c9', '#527ba0', '#254e77']
activity_colors_index = 0
if contributions > 0 && contributions < 10
activity_colors_index = 1
elsif contributions >= 10 && contributions < 20
activity_colors_index = 2
elsif contributions >= 20 && contributions < 30
activity_colors_index = 3
elsif contributions >= 30
activity_colors_index = 4
end
activity_colors = %w[#ededed #acd5f2 #7fa8c9 #527ba0 #254e77]
# We currently don't actually test the cases with contributions >= 20
activity_colors_index =
if contributions > 0 && contributions < 10
1
elsif contributions >= 10 && contributions < 20
2
elsif contributions >= 20 && contributions < 30
3
elsif contributions >= 30
4
else
0
end
"#{contribution_cell}[fill='#{activity_colors[activity_colors_index]}']"
".user-contrib-cell[fill='#{activity_colors[activity_colors_index]}']"
end
def get_cell_date_selector(contributions, date)
contribution_text = 'No contributions'
if contributions === 1
contribution_text = '1 contribution'
elsif contributions > 1
contribution_text = "#{contributions} contributions"
end
contribution_text =
if contributions.zero?
'No contributions'
else
"#{contributions} #{'contribution'.pluralize(contributions)}"
end
"#{get_cell_color_selector(contributions)}[data-original-title='#{contribution_text}<br />#{date}']"
end
......@@ -45,129 +47,155 @@ feature 'Contributions Calendar', js: true, feature: true do
push_params = {
project: contributed_project,
action: Event::PUSHED,
author_id: @user.id,
author_id: user.id,
data: { commit_count: 3 }
}
Event.create(push_params)
end
def get_first_cell_content
find('.user-calendar-activities').text
end
def note_comment_contribution
note_comment_params = {
project: contributed_project,
action: Event::COMMENTED,
target: issue_note,
author_id: user.id
}
before do
login_as :user
visit @user.username
wait_for_ajax
Event.create(note_comment_params)
end
it 'displays calendar', js: true do
expect(page).to have_css('.js-contrib-calendar')
def selected_day_activities
find('.user-calendar-activities').text
end
describe 'select calendar day', js: true do
let(:cells) { page.all('.user-contrib-cell') }
let(:first_cell_content_before) { get_first_cell_content }
before do
login_as user
end
describe 'calendar day selection' do
before do
cells[0].click
visit user.username
wait_for_ajax
first_cell_content_before
end
it 'displays calendar day activities', js: true do
expect(get_first_cell_content).not_to eq('')
it 'displays calendar' do
expect(page).to have_css('.js-contrib-calendar')
end
describe 'select another calendar day', js: true do
describe 'select calendar day' do
let(:cells) { page.all('.user-contrib-cell') }
before do
cells[1].click
cells[0].click
wait_for_ajax
@first_day_activities = selected_day_activities
end
it 'displays different calendar day activities', js: true do
expect(get_first_cell_content).not_to eq(first_cell_content_before)
it 'displays calendar day activities' do
expect(selected_day_activities).not_to be_empty
end
end
describe 'deselect calendar day', js: true do
before do
cells[0].click
wait_for_ajax
describe 'select another calendar day' do
before do
cells[1].click
wait_for_ajax
end
it 'displays different calendar day activities' do
expect(selected_day_activities).not_to eq(@first_day_activities)
end
end
it 'hides calendar day activities', js: true do
expect(get_first_cell_content).to eq('')
describe 'deselect calendar day' do
before do
cells[0].click
wait_for_ajax
end
it 'hides calendar day activities' do
expect(selected_day_activities).to be_empty
end
end
end
end
describe '1 calendar activity' do
before do
Issues::CreateService.new(contributed_project, @user, issue_params).execute
visit @user.username
wait_for_ajax
describe 'calendar daily activities' do
shared_context 'visit user page' do
before do
visit user.username
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
shared_examples 'a day with activity' do |contribution_count:|
include_context 'visit user page'
it 'displays calendar activity square color for 1 contribution', js: true do
expect(page).to have_selector(get_cell_color_selector(1), count: 1)
end
it 'displays calendar activity square color for 1 contribution' do
expect(page).to have_selector(get_cell_color_selector(contribution_count), count: 1)
end
it 'displays calendar activity square on the correct date', js: true do
today = Date.today.strftime(date_format)
expect(page).to have_selector(get_cell_date_selector(1, today), count: 1)
it 'displays calendar activity square on the correct date' do
today = Date.today.strftime(date_format)
expect(page).to have_selector(get_cell_date_selector(contribution_count, today), count: 1)
end
end
end
describe '10 calendar activities' do
before do
(0..9).each do |i|
push_code_contribution()
describe '1 issue creation calendar activity' do
before do
Issues::CreateService.new(contributed_project, user, issue_params).execute
end
visit @user.username
wait_for_ajax
end
it_behaves_like 'a day with activity', contribution_count: 1
it 'displays calendar activity square color for 10 contributions', js: true do
expect(page).to have_selector(get_cell_color_selector(10), count: 1)
end
describe 'issue title is shown on activity page' do
include_context 'visit user page'
it 'displays calendar activity square on the correct date', js: true do
today = Date.today.strftime(date_format)
expect(page).to have_selector(get_cell_date_selector(10, today), count: 1)
it 'displays calendar activity log' do
expect(find('.content_list .event-note')).to have_content issue_title
end
end
end
end
describe 'calendar activity on two days' do
before do
push_code_contribution()
Timecop.freeze(Date.yesterday)
Issues::CreateService.new(contributed_project, @user, issue_params).execute
Timecop.return
describe '1 comment calendar activity' do
before do
note_comment_contribution
end
visit @user.username
wait_for_ajax
it_behaves_like 'a day with activity', contribution_count: 1
end
it 'displays calendar activity squares for both days', js: true do
expect(page).to have_selector(get_cell_color_selector(1), count: 2)
end
describe '10 calendar activities' do
before do
10.times { push_code_contribution }
end
it 'displays calendar activity square for yesterday', js: true do
yesterday = Date.yesterday.strftime(date_format)
expect(page).to have_selector(get_cell_date_selector(1, yesterday), count: 1)
it_behaves_like 'a day with activity', contribution_count: 10
end
it 'displays calendar activity square for today', js: true do
today = Date.today.strftime(date_format)
expect(page).to have_selector(get_cell_date_selector(1, today), count: 1)
describe 'calendar activity on two days' do
before do
push_code_contribution
Timecop.freeze(Date.yesterday) do
Issues::CreateService.new(contributed_project, user, issue_params).execute
end
end
include_context 'visit user page'
it 'displays calendar activity squares for both days' do
expect(page).to have_selector(get_cell_color_selector(1), count: 2)
end
it 'displays calendar activity square for yesterday' do
yesterday = Date.yesterday.strftime(date_format)
expect(page).to have_selector(get_cell_date_selector(1, yesterday), count: 1)
end
it 'displays calendar activity square for today' do
today = Date.today.strftime(date_format)
expect(page).to have_selector(get_cell_date_selector(1, today), count: 1)
end
end
end
end
require 'spec_helper'
feature 'Create New Merge Request', feature: true, js: true do
include WaitForVueResource
let(:user) { create(:user) }
let(:project) { create(:project, :public) }
......@@ -99,6 +101,7 @@ feature 'Create New Merge Request', feature: true, js: true do
page.within('.merge-request') do
click_link 'Pipelines'
wait_for_vue_resource
expect(page).to have_content "##{pipeline.id}"
end
......
......@@ -20,9 +20,9 @@ feature 'Ref switcher', feature: true, js: true do
input.set 'binary'
wait_for_ajax
input.native.send_keys :down
input.native.send_keys :down
input.native.send_keys :enter
page.within '.dropdown-content ul' do
input.native.send_keys :enter
end
end
expect(page).to have_title 'binary-encoding'
......
......@@ -4,18 +4,19 @@ require('~/behaviors/requires_input');
(function() {
describe('requiresInput', function() {
preloadFixtures('static/behaviors/requires_input.html.raw');
preloadFixtures('branches/new_branch.html.raw');
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() {
$('.js-requires-input').requiresInput();
return expect($('.submit')).toBeDisabled();
return expect(this.submitButton).toBeDisabled();
});
it('enables submit when no field is required', function() {
$('*[required=required]').removeAttr('required');
$('.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() {
$('*[required=required]').remove();
......@@ -25,9 +26,9 @@ require('~/behaviors/requires_input');
it('enables submit when all required fields receive input', function() {
$('.js-requires-input').requiresInput();
$('#required1').val('input1').change();
expect($('.submit')).toBeDisabled();
expect(this.submitButton).toBeDisabled();
$('#optional1').val('input1').change();
expect($('.submit')).toBeDisabled();
expect(this.submitButton).toBeDisabled();
$('#required2').val('input2').change();
$('#required3').val('input3').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');
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', () => {
expect($(document.activeElement)).not.toEqual($(SEARCH_INPUT_SELECTOR));
remoteCallback();
......
......@@ -34,7 +34,7 @@ require('~/extensions/jquery.js');
describe('RightSidebar', function() {
var fixtureName = 'issues/open-issue.html.raw';
preloadFixtures(fixtureName);
loadJSONFixtures('todos.json');
loadJSONFixtures('todos/todos.json');
beforeEach(function() {
loadFixtures(fixtureName);
......@@ -64,7 +64,7 @@ require('~/extensions/jquery.js');
});
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() {
var d = $.Deferred();
var response = todos;
......
......@@ -5,6 +5,12 @@ class MigrationTest
end
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
# of what they may return).
describe '.mysql?' do
......@@ -71,6 +77,54 @@ describe Gitlab::Database, lib: true do
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
it 'returns correct value for PostgreSQL' do
expect(described_class).to receive(:postgresql?).and_return(true)
......
......@@ -318,6 +318,19 @@ describe WikiPage, models: true do
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
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
expect(json_response.length).to eq(1)
expect(json_response.first['name']).to eq(project3.name)
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
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