diff options
460 files changed, 6316 insertions, 2787 deletions
diff --git a/.gitlab-ci.yml b/.gitlab-ci.yml index e7a279c828b..9006fef6a27 100644 --- a/.gitlab-ci.yml +++ b/.gitlab-ci.yml @@ -300,7 +300,7 @@ bundler:audit: - master@gitlab/gitlabhq - master@gitlab/gitlab-ee script: - - "bundle exec bundle-audit check --update --ignore OSVDB-115941 CVE-2016-6316 CVE-2016-6317" + - "bundle exec bundle-audit check --update" migration paths: stage: test diff --git a/CHANGELOG.md b/CHANGELOG.md index f279a57105c..e075de055e3 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -2,6 +2,10 @@ documentation](doc/development/changelog.md) for instructions on adding your own entry. +## 8.17.2 (2017-03-01) + +- Expire all webpack assets after 8.17.1 included a badly compiled asset. !9602 + ## 8.17.1 (2017-02-28) - Replace setInterval with setTimeout to prevent highly frequent requests. !9271 (Takuya Noguchi) diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index eed63127d9f..3cbc826e6db 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -1,32 +1,48 @@ +## Contributor license agreement + +By submitting code as an individual you agree to the +[individual contributor license agreement](doc/legal/individual_contributor_license_agreement.md). +By submitting code as an entity you agree to the +[corporate contributor license agreement](doc/legal/corporate_contributor_license_agreement.md). + +_This notice should stay as the first item in the CONTRIBUTING.MD file._ + +--- + <!-- START doctoc generated TOC please keep comment here to allow auto update --> <!-- DON'T EDIT THIS SECTION, INSTEAD RE-RUN doctoc TO UPDATE --> **Table of Contents** *generated with [DocToc](https://github.com/thlorenz/doctoc)* +- [Contributor license agreement](#contributor-license-agreement) - [Contribute to GitLab](#contribute-to-gitlab) - - [Contributor license agreement](#contributor-license-agreement) - - [Security vulnerability disclosure](#security-vulnerability-disclosure) - - [Closing policy for issues and merge requests](#closing-policy-for-issues-and-merge-requests) - - [Helping others](#helping-others) - - [I want to contribute!](#i-want-to-contribute) - - [Implement design & UI elements](#implement-design-ui-elements) - - [Issue tracker](#issue-tracker) - - [Feature proposals](#feature-proposals) - - [Issue tracker guidelines](#issue-tracker-guidelines) - - [Issue weight](#issue-weight) - - [Regression issues](#regression-issues) - - [Technical debt](#technical-debt) - - [Stewardship](#stewardship) - - [Merge requests](#merge-requests) - - [Merge request guidelines](#merge-request-guidelines) - - [Contribution acceptance criteria](#contribution-acceptance-criteria) - - [Changes for Stable Releases](#changes-for-stable-releases) - - [Definition of done](#definition-of-done) - - [Style guides](#style-guides) - - [Code of conduct](#code-of-conduct) +- [Security vulnerability disclosure](#security-vulnerability-disclosure) +- [Closing policy for issues and merge requests](#closing-policy-for-issues-and-merge-requests) +- [Helping others](#helping-others) +- [I want to contribute!](#i-want-to-contribute) +- [Implement design & UI elements](#implement-design-ui-elements) +- [Release retrospective and kickoff](#release-retrospective-and-kickoff) + - [Retrospective](#retrospective) + - [Kickoff](#kickoff) +- [Issue tracker](#issue-tracker) + - [Feature proposals](#feature-proposals) + - [Issue tracker guidelines](#issue-tracker-guidelines) + - [Issue weight](#issue-weight) + - [Regression issues](#regression-issues) + - [Technical debt](#technical-debt) + - [Stewardship](#stewardship) +- [Merge requests](#merge-requests) + - [Merge request guidelines](#merge-request-guidelines) + - [Contribution acceptance criteria](#contribution-acceptance-criteria) +- [Changes for Stable Releases](#changes-for-stable-releases) +- [Definition of done](#definition-of-done) +- [Style guides](#style-guides) +- [Code of conduct](#code-of-conduct) <!-- END doctoc generated TOC please keep comment here to allow auto update --> -# Contribute to GitLab +--- + +## Contribute to GitLab Thank you for your interest in contributing to GitLab. This guide details how to contribute to GitLab in a way that is efficient for everyone. @@ -41,13 +57,6 @@ operates please see [the GitLab contributing process](PROCESS.md). - [GitLab Inc engineers should refer to the engineering workflow document](https://about.gitlab.com/handbook/engineering/workflow/) -## Contributor license agreement - -By submitting code as an individual you agree to the -[individual contributor license agreement](doc/legal/individual_contributor_license_agreement.md). -By submitting code as an entity you agree to the -[corporate contributor license agreement](doc/legal/corporate_contributor_license_agreement.md). - ## Security vulnerability disclosure Please report suspected security vulnerabilities in private to @@ -236,7 +236,6 @@ gem 'gemojione', '~> 3.0' gem 'gon', '~> 6.1.0' gem 'jquery-atwho-rails', '~> 1.3.2' gem 'jquery-rails', '~> 4.1.0' -gem 'jquery-ui-rails', '~> 5.0.0' gem 'request_store', '~> 1.3' gem 'select2-rails', '~> 3.5.9' gem 'virtus', '~> 1.0.1' @@ -329,8 +328,6 @@ group :test do gem 'timecop', '~> 0.8.0' end -gem 'newrelic_rpm', '~> 3.16' - gem 'octokit', '~> 4.6.2' gem 'mail_room', '~> 0.9.1' @@ -352,3 +349,6 @@ gem 'health_check', '~> 2.2.0' # System information gem 'vmstat', '~> 2.3.0' gem 'sys-filesystem', '~> 1.1.6' + +# Gitaly GRPC client +gem 'gitaly', '~> 0.2.1' diff --git a/Gemfile.lock b/Gemfile.lock index 472cee510cb..2d7e6f6f0bf 100644 --- a/Gemfile.lock +++ b/Gemfile.lock @@ -245,6 +245,9 @@ GEM json get_process_mem (0.2.0) gherkin-ruby (0.3.2) + gitaly (0.2.1) + google-protobuf (~> 3.1) + grpc (~> 1.0) github-linguist (4.7.6) charlock_holmes (~> 0.7.3) escape_utils (~> 1.1.0) @@ -296,6 +299,7 @@ GEM multi_json (~> 1.10) retriable (~> 1.4) signet (~> 0.6) + google-protobuf (3.2.0) googleauth (0.5.1) faraday (~> 0.9) jwt (~> 1.4) @@ -317,6 +321,9 @@ GEM grape-entity (0.6.0) activesupport multi_json (>= 1.3.2) + grpc (1.1.2) + google-protobuf (~> 3.1) + googleauth (~> 0.5.1) haml (4.0.7) tilt haml_lint (0.21.0) @@ -367,8 +374,6 @@ GEM rails-dom-testing (>= 1, < 3) railties (>= 4.2.0) thor (>= 0.14, < 2.0) - jquery-ui-rails (5.0.5) - railties (>= 3.2.16) json (1.8.6) json-schema (2.6.2) addressable (~> 2.3.8) @@ -427,7 +432,6 @@ GEM net-ldap (0.12.1) net-ssh (3.0.1) netrc (0.11.0) - newrelic_rpm (3.16.0.318) nokogiri (1.6.8.1) mini_portile2 (~> 2.1.0) numerizer (0.1.1) @@ -660,7 +664,7 @@ GEM sexp_processor (~> 4.1) rubyntlm (0.5.2) rubypants (0.2.0) - rubyzip (1.2.0) + rubyzip (1.2.1) rufus-scheduler (3.1.10) rugged (0.24.0) safe_yaml (1.0.4) @@ -878,6 +882,7 @@ DEPENDENCIES fuubar (~> 2.0.0) gemnasium-gitlab-service (~> 0.2) gemojione (~> 3.0) + gitaly (~> 0.2.1) github-linguist (~> 4.7.0) gitlab-flowdock-git-hook (~> 1.0.1) gitlab-markup (~> 1.5.1) @@ -899,7 +904,6 @@ DEPENDENCIES jira-ruby (~> 1.1.2) jquery-atwho-rails (~> 1.3.2) jquery-rails (~> 4.1.0) - jquery-ui-rails (~> 5.0.0) json-schema (~> 2.6.2) jwt (~> 1.5.6) kaminari (~> 0.17.0) @@ -915,7 +919,6 @@ DEPENDENCIES mousetrap-rails (~> 1.4.6) mysql2 (~> 0.3.16) net-ssh (~> 3.0.1) - newrelic_rpm (~> 3.16) nokogiri (~> 1.6.7, >= 1.6.7.2) oauth2 (~> 1.2.0) octokit (~> 4.6.2) @@ -1011,4 +1014,4 @@ DEPENDENCIES wikicloth (= 0.8.1) BUNDLED WITH - 1.14.4 + 1.14.5 diff --git a/app/assets/javascripts/application.js b/app/assets/javascripts/application.js index c51860d1604..4c24d35b5bb 100644 --- a/app/assets/javascripts/application.js +++ b/app/assets/javascripts/application.js @@ -9,7 +9,6 @@ window.$ = window.jQuery = require('jquery'); require('jquery-ujs'); require('vendor/jquery.endless-scroll'); -require('vendor/jquery.highlight'); require('vendor/jquery.waitforimages'); require('vendor/jquery.caret'); require('vendor/jquery.atwho'); @@ -18,15 +17,11 @@ window.Cookies = require('js-cookie'); require('./autosave'); require('bootstrap/js/affix'); require('bootstrap/js/alert'); -require('bootstrap/js/button'); -require('bootstrap/js/collapse'); require('bootstrap/js/dropdown'); require('bootstrap/js/modal'); -require('bootstrap/js/scrollspy'); require('bootstrap/js/tab'); require('bootstrap/js/transition'); require('bootstrap/js/tooltip'); -require('bootstrap/js/popover'); require('select2/select2.js'); window.Pikaday = require('pikaday'); window._ = require('underscore'); diff --git a/app/assets/javascripts/boards/components/board_card.js b/app/assets/javascripts/boards/components/board_card.js index 52f61d84517..795b3cf2ec0 100644 --- a/app/assets/javascripts/boards/components/board_card.js +++ b/app/assets/javascripts/boards/components/board_card.js @@ -3,7 +3,7 @@ require('./issue_card_inner'); const Store = gl.issueBoards.BoardsStore; -module.exports = { +export default { name: 'BoardsIssueCard', template: ` <li class="card" diff --git a/app/assets/javascripts/boards/components/board_list.js.es6 b/app/assets/javascripts/boards/components/board_list.js.es6 index d92047cc0f8..2d52e96e7fb 100644 --- a/app/assets/javascripts/boards/components/board_list.js.es6 +++ b/app/assets/javascripts/boards/components/board_list.js.es6 @@ -2,8 +2,8 @@ /* global Vue */ /* global Sortable */ -const boardCard = require('./board_card'); -require('./board_new_issue'); +import boardNewIssue from './board_new_issue'; +import boardCard from './board_card'; (() => { const Store = gl.issueBoards.BoardsStore; @@ -15,7 +15,7 @@ require('./board_new_issue'); template: '#js-board-list-template', components: { boardCard, - 'board-new-issue': gl.issueBoards.BoardNewIssue + boardNewIssue, }, props: { disabled: Boolean, @@ -81,6 +81,12 @@ require('./board_new_issue'); }); } }, + toggleForm() { + this.showIssueForm = !this.showIssueForm; + }, + }, + created() { + gl.IssueBoardsApp.$on(`hide-issue-form-${this.list.id}`, this.toggleForm); }, mounted () { const options = gl.issueBoards.getBoardSortableDefaultOptions({ @@ -115,6 +121,9 @@ require('./board_new_issue'); this.loadNextPage(); } }; - } + }, + beforeDestroy() { + gl.IssueBoardsApp.$off(`hide-issue-form-${this.list.id}`, this.toggleForm); + }, }); })(); diff --git a/app/assets/javascripts/boards/components/board_new_issue.js b/app/assets/javascripts/boards/components/board_new_issue.js new file mode 100644 index 00000000000..b88f59dd6d4 --- /dev/null +++ b/app/assets/javascripts/boards/components/board_new_issue.js @@ -0,0 +1,92 @@ +/* global ListIssue */ +const Store = gl.issueBoards.BoardsStore; + +export default { + name: 'BoardNewIssue', + props: { + list: Object, + }, + data() { + return { + title: '', + error: false, + }; + }, + methods: { + submit(e) { + e.preventDefault(); + if (this.title.trim() === '') return; + + this.error = false; + + const labels = this.list.label ? [this.list.label] : []; + const issue = new ListIssue({ + title: this.title, + labels, + subscribed: true, + }); + + this.list.newIssue(issue) + .then(() => { + // Need this because our jQuery very kindly disables buttons on ALL form submissions + $(this.$refs.submitButton).enable(); + + Store.detail.issue = issue; + Store.detail.list = this.list; + }) + .catch(() => { + // Need this because our jQuery very kindly disables buttons on ALL form submissions + $(this.$refs.submitButton).enable(); + + // Remove the issue + this.list.removeIssue(issue); + + // Show error message + this.error = true; + }); + + this.cancel(); + }, + cancel() { + this.title = ''; + gl.IssueBoardsApp.$emit(`hide-issue-form-${this.list.id}`); + }, + }, + mounted() { + this.$refs.input.focus(); + }, + template: ` + <div class="card board-new-issue-form"> + <form @submit="submit($event)"> + <div class="flash-container" + v-if="error"> + <div class="flash-alert"> + An error occured. Please try again. + </div> + </div> + <label class="label-light" + :for="list.id + '-title'"> + Title + </label> + <input class="form-control" + type="text" + v-model="title" + ref="input" + :id="list.id + '-title'" /> + <div class="clearfix prepend-top-10"> + <button class="btn btn-success pull-left" + type="submit" + :disabled="title === ''" + ref="submit-button"> + Submit issue + </button> + <button class="btn btn-default pull-right" + type="button" + @click="cancel"> + Cancel + </button> + </div> + </form> + </div> + `, +}; diff --git a/app/assets/javascripts/boards/components/board_new_issue.js.es6 b/app/assets/javascripts/boards/components/board_new_issue.js.es6 deleted file mode 100644 index b5c14a198ba..00000000000 --- a/app/assets/javascripts/boards/components/board_new_issue.js.es6 +++ /dev/null @@ -1,64 +0,0 @@ -/* eslint-disable comma-dangle, no-unused-vars */ -/* global Vue */ -/* global ListIssue */ - -(() => { - const Store = gl.issueBoards.BoardsStore; - - window.gl = window.gl || {}; - - gl.issueBoards.BoardNewIssue = Vue.extend({ - props: { - list: Object, - }, - data() { - return { - title: '', - error: false - }; - }, - methods: { - submit(e) { - e.preventDefault(); - if (this.title.trim() === '') return; - - this.error = false; - - const labels = this.list.label ? [this.list.label] : []; - const issue = new ListIssue({ - title: this.title, - labels, - subscribed: true - }); - - this.list.newIssue(issue) - .then((data) => { - // Need this because our jQuery very kindly disables buttons on ALL form submissions - $(this.$refs.submitButton).enable(); - - Store.detail.issue = issue; - Store.detail.list = this.list; - }) - .catch(() => { - // Need this because our jQuery very kindly disables buttons on ALL form submissions - $(this.$refs.submitButton).enable(); - - // Remove the issue - this.list.removeIssue(issue); - - // Show error message - this.error = true; - }); - - this.cancel(); - }, - cancel() { - this.title = ''; - this.$parent.showIssueForm = false; - } - }, - mounted() { - this.$refs.input.focus(); - }, - }); -})(); diff --git a/app/assets/javascripts/commit/pipelines/pipelines_table.js.es6 b/app/assets/javascripts/commit/pipelines/pipelines_table.js.es6 index e7c6c063413..631ed34851c 100644 --- a/app/assets/javascripts/commit/pipelines/pipelines_table.js.es6 +++ b/app/assets/javascripts/commit/pipelines/pipelines_table.js.es6 @@ -39,15 +39,10 @@ const PipelineStore = require('./pipelines_store'); */ data() { const pipelinesTableData = document.querySelector('#commit-pipeline-table-view').dataset; - const svgsData = document.querySelector('.pipeline-svgs').dataset; const store = new PipelineStore(); - // Transform svgs DOMStringMap to a plain Object. - const svgsObject = gl.utils.DOMStringMapToObject(svgsData); - return { endpoint: pipelinesTableData.endpoint, - svgs: svgsObject, store, state: store.state, isLoading: false, @@ -69,7 +64,9 @@ const PipelineStore = require('./pipelines_store'); return pipelinesService.all() .then(response => response.json()) .then((json) => { - this.store.storePipelines(json); + // depending of the endpoint the response can either bring a `pipelines` key or not. + const pipelines = json.pipelines || json; + this.store.storePipelines(pipelines); this.isLoading = false; }) .catch(() => { @@ -99,10 +96,7 @@ const PipelineStore = require('./pipelines_store'); <div class="table-holder pipelines" v-if="!isLoading && state.pipelines.length > 0"> - <pipelines-table-component - :pipelines="state.pipelines" - :svgs="svgs"> - </pipelines-table-component> + <pipelines-table-component :pipelines="state.pipelines"/> </div> </div> `, diff --git a/app/assets/javascripts/copy_as_gfm.js.es6 b/app/assets/javascripts/copy_as_gfm.js.es6 index 4bd537a6f28..2bc3d85fba4 100644 --- a/app/assets/javascripts/copy_as_gfm.js.es6 +++ b/app/assets/javascripts/copy_as_gfm.js.es6 @@ -25,6 +25,9 @@ require('./lib/utils/common_utils'); }, }, ReferenceFilter: { + '.tooltip'(el, text) { + return ''; + }, 'a.gfm:not([data-link=true])'(el, text) { return el.dataset.original || text; }, diff --git a/app/assets/javascripts/cycle_analytics/components/stage_plan_component.js.es6 b/app/assets/javascripts/cycle_analytics/components/stage_plan_component.js.es6 index 8652479e7bf..42e1bbce744 100644 --- a/app/assets/javascripts/cycle_analytics/components/stage_plan_component.js.es6 +++ b/app/assets/javascripts/cycle_analytics/components/stage_plan_component.js.es6 @@ -1,5 +1,6 @@ /* eslint-disable no-param-reassign */ -/* global Vue */ +import Vue from 'vue'; +import iconCommit from '../svg/icon_commit.svg'; ((global) => { global.cycleAnalytics = global.cycleAnalytics || {}; @@ -9,6 +10,11 @@ items: Array, stage: Object, }, + + data() { + return { iconCommit }; + }, + template: ` <div> <div class="events-description"> @@ -31,7 +37,7 @@ </h5> <span> First - <span class="commit-icon">${global.cycleAnalytics.svgs.iconCommit}</span> + <span class="commit-icon">${iconCommit}</span> <a :href="commit.commitUrl" class="commit-hash-link monospace">{{ commit.shortSha }}</a> pushed by <a :href="commit.author.webUrl" class="commit-author-link"> diff --git a/app/assets/javascripts/cycle_analytics/components/stage_staging_component.js.es6 b/app/assets/javascripts/cycle_analytics/components/stage_staging_component.js.es6 index 82622232f64..8fa63734cf1 100644 --- a/app/assets/javascripts/cycle_analytics/components/stage_staging_component.js.es6 +++ b/app/assets/javascripts/cycle_analytics/components/stage_staging_component.js.es6 @@ -1,5 +1,6 @@ /* eslint-disable no-param-reassign */ -/* global Vue */ +import Vue from 'vue'; +import iconBranch from '../svg/icon_branch.svg'; ((global) => { global.cycleAnalytics = global.cycleAnalytics || {}; @@ -9,6 +10,9 @@ items: Array, stage: Object, }, + data() { + return { iconBranch }; + }, template: ` <div> <div class="events-description"> @@ -22,7 +26,7 @@ <a :href="build.url" class="pipeline-id">#{{ build.id }}</a> <i class="fa fa-code-fork"></i> <a :href="build.branch.url" class="branch-name monospace">{{ build.branch.name }}</a> - <span class="icon-branch">${global.cycleAnalytics.svgs.iconBranch}</span> + <span class="icon-branch">${iconBranch}</span> <a :href="build.commitUrl" class="short-sha monospace">{{ build.shortSha }}</a> </h5> <span> diff --git a/app/assets/javascripts/cycle_analytics/components/stage_test_component.js.es6 b/app/assets/javascripts/cycle_analytics/components/stage_test_component.js.es6 index 4bfd363a1f1..0015249cfaa 100644 --- a/app/assets/javascripts/cycle_analytics/components/stage_test_component.js.es6 +++ b/app/assets/javascripts/cycle_analytics/components/stage_test_component.js.es6 @@ -1,5 +1,7 @@ /* eslint-disable no-param-reassign */ -/* global Vue */ +import Vue from 'vue'; +import iconBuildStatus from '../svg/icon_build_status.svg'; +import iconBranch from '../svg/icon_branch.svg'; ((global) => { global.cycleAnalytics = global.cycleAnalytics || {}; @@ -9,6 +11,9 @@ items: Array, stage: Object, }, + data() { + return { iconBuildStatus, iconBranch }; + }, template: ` <div> <div class="events-description"> @@ -18,13 +23,13 @@ <li v-for="build in items" class="stage-event-item item-build-component"> <div class="item-details"> <h5 class="item-title"> - <span class="icon-build-status">${global.cycleAnalytics.svgs.iconBuildStatus}</span> + <span class="icon-build-status">${iconBuildStatus}</span> <a :href="build.url" class="item-build-name">{{ build.name }}</a> · <a :href="build.url" class="pipeline-id">#{{ build.id }}</a> <i class="fa fa-code-fork"></i> <a :href="build.branch.url" class="branch-name monospace">{{ build.branch.name }}</a> - <span class="icon-branch">${global.cycleAnalytics.svgs.iconBranch}</span> + <span class="icon-branch">${iconBranch}</span> <a :href="build.commitUrl" class="short-sha monospace">{{ build.shortSha }}</a> </h5> <span> diff --git a/app/assets/javascripts/cycle_analytics/cycle_analytics_bundle.js.es6 b/app/assets/javascripts/cycle_analytics/cycle_analytics_bundle.js.es6 index 411ac7b24b2..beff293b587 100644 --- a/app/assets/javascripts/cycle_analytics/cycle_analytics_bundle.js.es6 +++ b/app/assets/javascripts/cycle_analytics/cycle_analytics_bundle.js.es6 @@ -4,9 +4,6 @@ window.Vue = require('vue'); window.Cookies = require('js-cookie'); -require('./svg/icon_branch'); -require('./svg/icon_build_status'); -require('./svg/icon_commit'); require('./components/stage_code_component'); require('./components/stage_issue_component'); require('./components/stage_plan_component'); diff --git a/app/assets/javascripts/cycle_analytics/svg/icon_branch.js.es6 b/app/assets/javascripts/cycle_analytics/svg/icon_branch.js.es6 deleted file mode 100644 index 5d486bcaf66..00000000000 --- a/app/assets/javascripts/cycle_analytics/svg/icon_branch.js.es6 +++ /dev/null @@ -1,7 +0,0 @@ -/* eslint-disable no-param-reassign */ -((global) => { - global.cycleAnalytics = global.cycleAnalytics || {}; - global.cycleAnalytics.svgs = global.cycleAnalytics.svgs || {}; - - global.cycleAnalytics.svgs.iconBranch = '<svg xmlns="http://www.w3.org/2000/svg" width="14" height="14"><path fill="#8C8C8C" fill-rule="evenodd" d="M9.678 6.722C9.353 5.167 8.053 4 6.5 4S3.647 5.167 3.322 6.722h-2.6c-.397 0-.722.35-.722.778 0 .428.325.778.722.778h2.6C3.647 9.833 4.947 11 6.5 11s2.853-1.167 3.178-2.722h2.6c.397 0 .722-.35.722-.778 0-.428-.325-.778-.722-.778h-2.6zM4.694 7.5c0-1.09.795-1.944 1.806-1.944 1.01 0 1.806.855 1.806 1.944 0 1.09-.795 1.944-1.806 1.944-1.01 0-1.806-.855-1.806-1.944z"/></svg>'; -})(window.gl || (window.gl = {})); diff --git a/app/assets/javascripts/cycle_analytics/svg/icon_branch.svg b/app/assets/javascripts/cycle_analytics/svg/icon_branch.svg new file mode 100644 index 00000000000..9f547d3d744 --- /dev/null +++ b/app/assets/javascripts/cycle_analytics/svg/icon_branch.svg @@ -0,0 +1 @@ +<svg xmlns="http://www.w3.org/2000/svg" width="14" height="14"><path fill="#8C8C8C" fill-rule="evenodd" d="M9.678 6.722C9.353 5.167 8.053 4 6.5 4S3.647 5.167 3.322 6.722h-2.6c-.397 0-.722.35-.722.778 0 .428.325.778.722.778h2.6C3.647 9.833 4.947 11 6.5 11s2.853-1.167 3.178-2.722h2.6c.397 0 .722-.35.722-.778 0-.428-.325-.778-.722-.778h-2.6zM4.694 7.5c0-1.09.795-1.944 1.806-1.944 1.01 0 1.806.855 1.806 1.944 0 1.09-.795 1.944-1.806 1.944-1.01 0-1.806-.855-1.806-1.944z"/></svg> diff --git a/app/assets/javascripts/cycle_analytics/svg/icon_build_status.js.es6 b/app/assets/javascripts/cycle_analytics/svg/icon_build_status.js.es6 deleted file mode 100644 index 661bf9e9f1c..00000000000 --- a/app/assets/javascripts/cycle_analytics/svg/icon_build_status.js.es6 +++ /dev/null @@ -1,7 +0,0 @@ -/* eslint-disable no-param-reassign */ -((global) => { - global.cycleAnalytics = global.cycleAnalytics || {}; - global.cycleAnalytics.svgs = global.cycleAnalytics.svgs || {}; - - global.cycleAnalytics.svgs.iconBuildStatus = '<svg xmlns="http://www.w3.org/2000/svg" width="14" height="14"><g fill="#31AF64" fill-rule="evenodd"><path d="M12.5 7c0-3.038-2.462-5.5-5.5-5.5S1.5 3.962 1.5 7s2.462 5.5 5.5 5.5 5.5-2.462 5.5-5.5zM0 7c0-3.866 3.134-7 7-7s7 3.134 7 7-3.134 7-7 7-7-3.134-7-7z"/><path d="M6.28 7.697L5.045 6.464c-.117-.117-.305-.117-.42-.002l-.614.614c-.11.113-.11.303.007.42l1.91 1.91c.19.19.51.197.703.004l.264-.265L9.997 6.04c.108-.107.107-.293-.01-.408l-.612-.614c-.114-.113-.298-.12-.41-.01L6.28 7.7z"/></g></svg>'; -})(window.gl || (window.gl = {})); diff --git a/app/assets/javascripts/cycle_analytics/svg/icon_build_status.svg b/app/assets/javascripts/cycle_analytics/svg/icon_build_status.svg new file mode 100644 index 00000000000..b932d90618a --- /dev/null +++ b/app/assets/javascripts/cycle_analytics/svg/icon_build_status.svg @@ -0,0 +1 @@ +<svg xmlns="http://www.w3.org/2000/svg" width="14" height="14"><g fill="#31AF64" fill-rule="evenodd"><path d="M12.5 7c0-3.038-2.462-5.5-5.5-5.5S1.5 3.962 1.5 7s2.462 5.5 5.5 5.5 5.5-2.462 5.5-5.5zM0 7c0-3.866 3.134-7 7-7s7 3.134 7 7-3.134 7-7 7-7-3.134-7-7z"/><path d="M6.28 7.697L5.045 6.464c-.117-.117-.305-.117-.42-.002l-.614.614c-.11.113-.11.303.007.42l1.91 1.91c.19.19.51.197.703.004l.264-.265L9.997 6.04c.108-.107.107-.293-.01-.408l-.612-.614c-.114-.113-.298-.12-.41-.01L6.28 7.7z"/></g></svg> diff --git a/app/assets/javascripts/cycle_analytics/svg/icon_commit.js.es6 b/app/assets/javascripts/cycle_analytics/svg/icon_commit.js.es6 deleted file mode 100644 index 2208c27a619..00000000000 --- a/app/assets/javascripts/cycle_analytics/svg/icon_commit.js.es6 +++ /dev/null @@ -1,7 +0,0 @@ -/* eslint-disable no-param-reassign */ -((global) => { - global.cycleAnalytics = global.cycleAnalytics || {}; - global.cycleAnalytics.svgs = global.cycleAnalytics.svgs || {}; - - global.cycleAnalytics.svgs.iconCommit = '<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 40 40"><path fill="#8F8F8F" fill-rule="evenodd" d="M28.777 18c-.91-4.008-4.494-7-8.777-7-4.283 0-7.868 2.992-8.777 7H4.01C2.9 18 2 18.895 2 20c0 1.112.9 2 2.01 2h7.213c.91 4.008 4.494 7 8.777 7 4.283 0 7.868-2.992 8.777-7h7.214C37.1 22 38 21.105 38 20c0-1.112-.9-2-2.01-2h-7.213zM20 25c2.76 0 5-2.24 5-5s-2.24-5-5-5-5 2.24-5 5 2.24 5 5 5z"/></svg>'; -})(window.gl || (window.gl = {})); diff --git a/app/assets/javascripts/cycle_analytics/svg/icon_commit.svg b/app/assets/javascripts/cycle_analytics/svg/icon_commit.svg new file mode 100644 index 00000000000..6a517756058 --- /dev/null +++ b/app/assets/javascripts/cycle_analytics/svg/icon_commit.svg @@ -0,0 +1 @@ +<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 40 40"><path fill="#8F8F8F" fill-rule="evenodd" d="M28.777 18c-.91-4.008-4.494-7-8.777-7-4.283 0-7.868 2.992-8.777 7H4.01C2.9 18 2 18.895 2 20c0 1.112.9 2 2.01 2h7.213c.91 4.008 4.494 7 8.777 7 4.283 0 7.868-2.992 8.777-7h7.214C37.1 22 38 21.105 38 20c0-1.112-.9-2-2.01-2h-7.213zM20 25c2.76 0 5-2.24 5-5s-2.24-5-5-5-5 2.24-5 5 2.24 5 5 5z"/></svg> diff --git a/app/assets/javascripts/diff.js.es6 b/app/assets/javascripts/diff.js.es6 index ccccd0a36ff..6829e8aeaea 100644 --- a/app/assets/javascripts/diff.js.es6 +++ b/app/assets/javascripts/diff.js.es6 @@ -25,6 +25,10 @@ require('./lib/utils/url_utility'); isBound = true; } + if (gl.utils.getLocationHash()) { + this.highlightSelectedLine(); + } + this.openAnchoredDiff(); } @@ -78,7 +82,7 @@ require('./lib/utils/url_utility'); if (nothingHereBlock.length) { const clickTarget = $('.js-file-title, .click-to-expand', diffFile); diffFile.data('singleFileDiff').toggleDiff(clickTarget, () => { - this.highlighSelectedLine(); + this.highlightSelectedLine(); if (cb) cb(); }); } else if (cb) { @@ -94,7 +98,7 @@ require('./lib/utils/url_utility'); } else { window.location.hash = hash; } - this.highlighSelectedLine(); + this.highlightSelectedLine(); } diffViewType() { @@ -108,7 +112,7 @@ require('./lib/utils/url_utility'); return line.find('.diff-line-num').map((i, elm) => parseInt($(elm).data('linenumber'), 10)); } - highlighSelectedLine() { + highlightSelectedLine() { const hash = gl.utils.getLocationHash(); const $diffFiles = $('.diff-file'); $diffFiles.find('.hll').removeClass('hll'); diff --git a/app/assets/javascripts/dispatcher.js.es6 b/app/assets/javascripts/dispatcher.js.es6 index 0f678492d4c..fc25122aedc 100644 --- a/app/assets/javascripts/dispatcher.js.es6 +++ b/app/assets/javascripts/dispatcher.js.es6 @@ -35,6 +35,8 @@ /* global Labels */ /* global Shortcuts */ +import GroupsList from './groups_list'; + const ShortcutsBlob = require('./shortcuts_blob'); const UserCallout = require('./user_callout'); @@ -96,6 +98,10 @@ const UserCallout = require('./user_callout'); case 'dashboard:todos:index': new gl.Todos(); break; + case 'dashboard:groups:index': + case 'explore:groups:index': + new GroupsList(); + break; case 'projects:milestones:new': case 'projects:milestones:edit': case 'projects:milestones:update': diff --git a/app/assets/javascripts/environments/components/environment.js.es6 b/app/assets/javascripts/environments/components/environment.js.es6 index 5869323d1e2..2cb48dde628 100644 --- a/app/assets/javascripts/environments/components/environment.js.es6 +++ b/app/assets/javascripts/environments/components/environment.js.es6 @@ -35,9 +35,6 @@ module.exports = Vue.component('environment-component', { projectStoppedEnvironmentsPath: environmentsData.projectStoppedEnvironmentsPath, newEnvironmentPath: environmentsData.newEnvironmentPath, helpPagePath: environmentsData.helpPagePath, - commitIconSvg: environmentsData.commitIconSvg, - playIconSvg: environmentsData.playIconSvg, - terminalIconSvg: environmentsData.terminalIconSvg, // Pagination Properties, paginationInformation: {}, @@ -78,7 +75,7 @@ module.exports = Vue.component('environment-component', { this.isLoading = true; - return service.all() + return service.get() .then(resp => ({ headers: resp.headers, body: resp.json(), @@ -176,11 +173,7 @@ module.exports = Vue.component('environment-component', { <environment-table :environments="state.environments" :can-create-deployment="canCreateDeploymentParsed" - :can-read-environment="canReadEnvironmentParsed" - :play-icon-svg="playIconSvg" - :terminal-icon-svg="terminalIconSvg" - :commit-icon-svg="commitIconSvg"> - </environment-table> + :can-read-environment="canReadEnvironmentParsed"/> </div> <table-pagination v-if="state.paginationInformation && state.paginationInformation.totalPages > 1" diff --git a/app/assets/javascripts/environments/components/environment_actions.js.es6 b/app/assets/javascripts/environments/components/environment_actions.js.es6 index 978d4dd8b6b..15e3f8823d2 100644 --- a/app/assets/javascripts/environments/components/environment_actions.js.es6 +++ b/app/assets/javascripts/environments/components/environment_actions.js.es6 @@ -1,4 +1,5 @@ const Vue = require('vue'); +const playIconSvg = require('icons/_icon_play.svg'); module.exports = Vue.component('actions-component', { props: { @@ -7,11 +8,10 @@ module.exports = Vue.component('actions-component', { required: false, default: () => [], }, + }, - playIconSvg: { - type: String, - required: false, - }, + data() { + return { playIconSvg }; }, template: ` @@ -28,9 +28,7 @@ module.exports = Vue.component('actions-component', { data-method="post" rel="nofollow" class="js-manual-action-link"> - - <span class="js-action-play-icon-container" v-html="playIconSvg"></span> - + ${playIconSvg} <span> {{action.name}} </span> diff --git a/app/assets/javascripts/environments/components/environment_item.js.es6 b/app/assets/javascripts/environments/components/environment_item.js.es6 index 3f782742c56..7f4e070b229 100644 --- a/app/assets/javascripts/environments/components/environment_item.js.es6 +++ b/app/assets/javascripts/environments/components/environment_item.js.es6 @@ -46,21 +46,6 @@ module.exports = Vue.component('environment-item', { required: false, default: false, }, - - commitIconSvg: { - type: String, - required: false, - }, - - playIconSvg: { - type: String, - required: false, - }, - - terminalIconSvg: { - type: String, - required: false, - }, }, computed: { @@ -487,9 +472,7 @@ module.exports = Vue.component('environment-item', { :commit-url="commitUrl" :short-sha="commitShortSha" :title="commitTitle" - :author="commitAuthor" - :commit-icon-svg="commitIconSvg"> - </commit-component> + :author="commitAuthor"/> </div> <p v-if="!model.isFolder && !hasLastDeploymentKey" class="commit-title"> No deployments yet @@ -506,27 +489,20 @@ module.exports = Vue.component('environment-item', { <td class="environments-actions"> <div v-if="!model.isFolder" class="btn-group pull-right" role="group"> <actions-component v-if="hasManualActions && canCreateDeployment" - :play-icon-svg="playIconSvg" - :actions="manualActions"> - </actions-component> + :actions="manualActions"/> <external-url-component v-if="externalURL && canReadEnvironment" - :external-url="externalURL"> - </external-url-component> + :external-url="externalURL"/> <stop-component v-if="hasStopAction && canCreateDeployment" - :stop-url="model.stop_path"> - </stop-component> + :stop-url="model.stop_path"/> <terminal-button-component v-if="model && model.terminal_path" - :terminal-icon-svg="terminalIconSvg" - :terminal-path="model.terminal_path"> - </terminal-button-component> + :terminal-path="model.terminal_path"/> <rollback-component v-if="canRetry && canCreateDeployment" :is-last-deployment="isLastDeployment" - :retry-url="retryUrl"> - </rollback-component> + :retry-url="retryUrl"/> </div> </td> </tr> diff --git a/app/assets/javascripts/environments/components/environment_terminal_button.js.es6 b/app/assets/javascripts/environments/components/environment_terminal_button.js.es6 index 481e0d15e7a..e86607e78f4 100644 --- a/app/assets/javascripts/environments/components/environment_terminal_button.js.es6 +++ b/app/assets/javascripts/environments/components/environment_terminal_button.js.es6 @@ -3,6 +3,7 @@ * Used in environments table. */ const Vue = require('vue'); +const terminalIconSvg = require('icons/_icon_terminal.svg'); module.exports = Vue.component('terminal-button-component', { props: { @@ -10,16 +11,16 @@ module.exports = Vue.component('terminal-button-component', { type: String, default: '', }, - terminalIconSvg: { - type: String, - default: '', - }, + }, + + data() { + return { terminalIconSvg }; }, template: ` <a class="btn terminal-button" :href="terminalPath"> - <span class="js-terminal-icon-container" v-html="terminalIconSvg"></span> + ${terminalIconSvg} </a> `, }); diff --git a/app/assets/javascripts/environments/components/environments_table.js.es6 b/app/assets/javascripts/environments/components/environments_table.js.es6 index 33ebca19f5d..4088d63be80 100644 --- a/app/assets/javascripts/environments/components/environments_table.js.es6 +++ b/app/assets/javascripts/environments/components/environments_table.js.es6 @@ -28,21 +28,6 @@ module.exports = Vue.component('environment-table-component', { required: false, default: false, }, - - commitIconSvg: { - type: String, - required: false, - }, - - playIconSvg: { - type: String, - required: false, - }, - - terminalIconSvg: { - type: String, - required: false, - }, }, template: ` @@ -63,10 +48,7 @@ module.exports = Vue.component('environment-table-component', { <tr is="environment-item" :model="model" :can-create-deployment="canCreateDeployment" - :can-read-environment="canReadEnvironment" - :play-icon-svg="playIconSvg" - :terminal-icon-svg="terminalIconSvg" - :commit-icon-svg="commitIconSvg"></tr> + :can-read-environment="canReadEnvironment"></tr> </template> </tbody> </table> diff --git a/app/assets/javascripts/environments/folder/environments_folder_view.js.es6 b/app/assets/javascripts/environments/folder/environments_folder_view.js.es6 index 53d52965758..2a9d0492d7a 100644 --- a/app/assets/javascripts/environments/folder/environments_folder_view.js.es6 +++ b/app/assets/javascripts/environments/folder/environments_folder_view.js.es6 @@ -92,7 +92,7 @@ module.exports = Vue.component('environment-folder-view', { this.isLoading = true; - return service.all() + return service.get() .then(resp => ({ headers: resp.headers, body: resp.json(), diff --git a/app/assets/javascripts/environments/services/environments_service.js.es6 b/app/assets/javascripts/environments/services/environments_service.js.es6 index 9cef335868e..effc6c4c838 100644 --- a/app/assets/javascripts/environments/services/environments_service.js.es6 +++ b/app/assets/javascripts/environments/services/environments_service.js.es6 @@ -5,7 +5,7 @@ class EnvironmentsService { this.environments = Vue.resource(endpoint); } - all() { + get() { return this.environments.get(); } } diff --git a/app/assets/javascripts/groups_list.js b/app/assets/javascripts/groups_list.js new file mode 100644 index 00000000000..0ef81e49444 --- /dev/null +++ b/app/assets/javascripts/groups_list.js @@ -0,0 +1,47 @@ +/** + * Based on project list search. + * Makes search request for groups when user types a value in the search input. + * Updates the html content of the page with the received one. + */ +export default class GroupsList { + constructor() { + this.groupsListFilterElement = document.querySelector('.js-groups-list-filter'); + this.groupsListHolderElement = document.querySelector('.js-groups-list-holder'); + + this.initSearch(); + } + + initSearch() { + this.debounceFilter = _.debounce(this.filterResults.bind(this), 500); + + this.groupsListFilterElement.removeEventListener('input', this.debounceFilter); + this.groupsListFilterElement.addEventListener('input', this.debounceFilter); + } + + filterResults() { + const form = document.querySelector('form#group-filter-form'); + const groupFilterUrl = `${form.getAttribute('action')}?${$(form).serialize()}`; + + $(this.groupsListHolderElement).fadeTo(250, 0.5); + + return $.ajax({ + url: form.getAttribute('action'), + data: $(form).serialize(), + type: 'GET', + dataType: 'json', + context: this, + complete() { + $(this.groupsListHolderElement).fadeTo(250, 1); + }, + success(data) { + this.groupsListHolderElement.innerHTML = data.html; + + // Change url so if user reload a page - search results are saved + return window.history.replaceState({ + page: groupFilterUrl, + + }, document.title, groupFilterUrl); + }, + }); + } +} diff --git a/app/assets/javascripts/issuable/time_tracking/components/collapsed_state.js.es6 b/app/assets/javascripts/issuable/time_tracking/components/collapsed_state.js.es6 index bf27fbac5d7..357b3487ca9 100644 --- a/app/assets/javascripts/issuable/time_tracking/components/collapsed_state.js.es6 +++ b/app/assets/javascripts/issuable/time_tracking/components/collapsed_state.js.es6 @@ -1,4 +1,6 @@ /* global Vue */ +import stopwatchSvg from 'icons/_icon_stopwatch.svg'; + require('../../../lib/utils/pretty_time'); (() => { @@ -11,7 +13,6 @@ require('../../../lib/utils/pretty_time'); 'showNoTimeTrackingState', 'timeSpentHumanReadable', 'timeEstimateHumanReadable', - 'stopwatchSvg', ], methods: { abbreviateTime(timeStr) { @@ -20,7 +21,7 @@ require('../../../lib/utils/pretty_time'); }, template: ` <div class='sidebar-collapsed-icon'> - <div v-html='stopwatchSvg'></div> + ${stopwatchSvg} <div class='time-tracking-collapsed-summary'> <div class='compare' v-if='showComparisonState'> <span>{{ abbreviateTime(timeSpentHumanReadable) }} / {{ abbreviateTime(timeEstimateHumanReadable) }}</span> diff --git a/app/assets/javascripts/issuable/time_tracking/components/time_tracker.js.es6 b/app/assets/javascripts/issuable/time_tracking/components/time_tracker.js.es6 index e38f7852b1c..1fae2d62b14 100644 --- a/app/assets/javascripts/issuable/time_tracking/components/time_tracker.js.es6 +++ b/app/assets/javascripts/issuable/time_tracking/components/time_tracker.js.es6 @@ -15,7 +15,6 @@ require('./comparison_pane'); 'time_spent', 'human_time_estimate', 'human_time_spent', - 'stopwatchSvg', 'docsUrl', ], data() { @@ -71,20 +70,19 @@ require('./comparison_pane'); :show-spent-only-state='showSpentOnlyState' :show-estimate-only-state='showEstimateOnlyState' :time-spent-human-readable='timeSpentHumanReadable' - :time-estimate-human-readable='timeEstimateHumanReadable' - :stopwatch-svg='stopwatchSvg'> + :time-estimate-human-readable='timeEstimateHumanReadable'> </time-tracking-collapsed-state> <div class='title hide-collapsed'> Time tracking <div class='help-button pull-right' v-if='!showHelpState' @click='toggleHelpState(true)'> - <i class='fa fa-question-circle'></i> + <i class='fa fa-question-circle' aria-hidden='true'></i> </div> <div class='close-help-button pull-right' v-if='showHelpState' @click='toggleHelpState(false)'> - <i class='fa fa-close'></i> + <i class='fa fa-close' aria-hidden='true'></i> </div> </div> <div class='time-tracking-content hide-collapsed'> diff --git a/app/assets/javascripts/issuable/time_tracking/time_tracking_bundle.js.es6 b/app/assets/javascripts/issuable/time_tracking/time_tracking_bundle.js.es6 index 1ca01d3bdb9..958a0cc6d50 100644 --- a/app/assets/javascripts/issuable/time_tracking/time_tracking_bundle.js.es6 +++ b/app/assets/javascripts/issuable/time_tracking/time_tracking_bundle.js.es6 @@ -39,8 +39,9 @@ require('../../subbable_resource'); listenForSlashCommands() { $(document).on('ajax:success', '.gfm-form', (e, data) => { const subscribedCommands = ['spend_time', 'time_estimate']; - const changedCommands = data.commands_changes; - + const changedCommands = data.commands_changes + ? Object.keys(data.commands_changes) + : []; if (changedCommands && _.intersection(subscribedCommands, changedCommands).length) { this.fetchIssuable(); } diff --git a/app/assets/javascripts/lib/utils/common_utils.js.es6 b/app/assets/javascripts/lib/utils/common_utils.js.es6 index 0242350f718..a1423b6fda5 100644 --- a/app/assets/javascripts/lib/utils/common_utils.js.es6 +++ b/app/assets/javascripts/lib/utils/common_utils.js.es6 @@ -247,17 +247,6 @@ }); /** - * Transforms a DOMStringMap into a plain object. - * - * @param {DOMStringMap} DOMStringMapObject - * @returns {Object} - */ - w.gl.utils.DOMStringMapToObject = DOMStringMapObject => Object.keys(DOMStringMapObject).reduce((acc, element) => { - acc[element] = DOMStringMapObject[element]; - return acc; - }, {}); - - /** * Updates the search parameter of a URL given the parameter and values provided. * * If no search params are present we'll add it. diff --git a/app/assets/javascripts/lib/utils/text_utility.js b/app/assets/javascripts/lib/utils/text_utility.js index 579d322e3fb..2e5f8a09fc1 100644 --- a/app/assets/javascripts/lib/utils/text_utility.js +++ b/app/assets/javascripts/lib/utils/text_utility.js @@ -65,9 +65,10 @@ require('vendor/latinise'); } }; gl.text.insertText = function(textArea, text, tag, blockTag, selected, wrap) { - var insertText, inserted, selectedSplit, startChar, removedLastNewLine, removedFirstNewLine; + var insertText, inserted, selectedSplit, startChar, removedLastNewLine, removedFirstNewLine, currentLineEmpty, lastNewLine; removedLastNewLine = false; removedFirstNewLine = false; + currentLineEmpty = false; // Remove the first newline if (selected.indexOf('\n') === 0) { @@ -82,7 +83,17 @@ require('vendor/latinise'); } selectedSplit = selected.split('\n'); - startChar = !wrap && textArea.selectionStart > 0 ? '\n' : ''; + + if (!wrap) { + lastNewLine = textArea.value.substr(0, textArea.selectionStart).lastIndexOf('\n'); + + // Check whether the current line is empty or consists only of spaces(=handle as empty) + if (/^\s*$/.test(textArea.value.substring(lastNewLine, textArea.selectionStart))) { + currentLineEmpty = true; + } + } + + startChar = !wrap && !currentLineEmpty && textArea.selectionStart > 0 ? '\n' : ''; if (selectedSplit.length > 1 && (!wrap || (blockTag != null))) { if (blockTag != null) { @@ -142,9 +153,8 @@ require('vendor/latinise'); } }; gl.text.updateText = function(textArea, tag, blockTag, wrap) { - var $textArea, oldVal, selected, text; + var $textArea, selected, text; $textArea = $(textArea); - oldVal = $textArea.val(); textArea = $textArea.get(0); text = $textArea.val(); selected = this.selectedText(text, textArea); diff --git a/app/assets/javascripts/merge_request_widget.js.es6 b/app/assets/javascripts/merge_request_widget.js.es6 index 00c6c050612..5f1bd474a0c 100644 --- a/app/assets/javascripts/merge_request_widget.js.es6 +++ b/app/assets/javascripts/merge_request_widget.js.es6 @@ -129,8 +129,9 @@ require('./smart_interval'); }; MergeRequestWidget.prototype.getMergeStatus = function() { - return $.get(this.opts.merge_check_url, function(data) { + return $.get(this.opts.merge_check_url, (data) => { var $html = $(data); + this.updateMergeButton(this.status, this.hasCi, $html); $('.mr-widget-body').replaceWith($html.find('.mr-widget-body')); $('.mr-widget-footer').replaceWith($html.find('.mr-widget-footer')); }); @@ -154,9 +155,9 @@ require('./smart_interval'); return $.getJSON(this.opts.ci_status_url, (function(_this) { return function(data) { var message, status, title; - if (!data.status) { - return; - } + _this.status = data.status; + _this.hasCi = data.has_ci; + _this.updateMergeButton(_this.status, _this.hasCi); if (data.environments && data.environments.length) _this.renderEnvironments(data.environments); if (data.status !== _this.opts.ci_status || data.sha !== _this.opts.ci_sha || @@ -232,36 +233,45 @@ require('./smart_interval'); return; } $('.ci_widget').hide(); - allowed_states = ["failed", "canceled", "running", "pending", "success", "success_with_warnings", "skipped", "not_found"]; - if (indexOf.call(allowed_states, state) !== -1) { - $('.ci_widget.ci-' + state).show(); + $('.ci_widget.ci-' + state).show(); + + this.initMiniPipelineGraph(); + }; + + MergeRequestWidget.prototype.showCICoverage = function(coverage) { + var text = `Coverage ${coverage}%`; + return $('.ci_widget:visible .ci-coverage').text(text); + }; + + MergeRequestWidget.prototype.updateMergeButton = function(state, hasCi, $html) { + const allowed_states = ["failed", "canceled", "running", "pending", "success", "success_with_warnings", "skipped", "not_found"]; + let stateClass = 'btn-danger'; + if (!hasCi) { + stateClass = 'btn-create'; + } else if (indexOf.call(allowed_states, state) !== -1) { switch (state) { case "failed": case "canceled": case "not_found": - this.setMergeButtonClass('btn-danger'); + stateClass = 'btn-danger'; break; case "running": - this.setMergeButtonClass('btn-info'); + stateClass = 'btn-info'; break; case "success": case "success_with_warnings": - this.setMergeButtonClass('btn-create'); + stateClass = 'btn-create'; } } else { $('.ci_widget.ci-error').show(); - this.setMergeButtonClass('btn-danger'); + stateClass = 'btn-danger'; } - }; - MergeRequestWidget.prototype.showCICoverage = function(coverage) { - var text; - text = 'Coverage ' + coverage + '%'; - return $('.ci_widget:visible .ci-coverage').text(text); + this.setMergeButtonClass(stateClass, $html); }; - MergeRequestWidget.prototype.setMergeButtonClass = function(css_class) { - return $('.js-merge-button,.accept-action .dropdown-toggle').removeClass('btn-danger btn-info btn-create').addClass(css_class); + MergeRequestWidget.prototype.setMergeButtonClass = function(css_class, $html = $('.mr-state-widget')) { + return $html.find('.js-merge-button').removeClass('btn-danger btn-info btn-create').addClass(css_class); }; MergeRequestWidget.prototype.updatePipelineUrls = function(id) { diff --git a/app/assets/javascripts/merge_request_widget/ci_bundle.js.es6 b/app/assets/javascripts/merge_request_widget/ci_bundle.js.es6 index 5840916846b..21d7c3e168e 100644 --- a/app/assets/javascripts/merge_request_widget/ci_bundle.js.es6 +++ b/app/assets/javascripts/merge_request_widget/ci_bundle.js.es6 @@ -15,15 +15,15 @@ }); $(document) - .off('click', '.accept_merge_request') - .on('click', '.accept_merge_request', () => { - $('.js-merge-button').html('<i class="fa fa-spinner fa-spin"></i> Merge in progress'); + .off('click', '.accept-merge-request') + .on('click', '.accept-merge-request', () => { + $('.js-merge-button, .js-merge-when-pipeline-succeeds-button').html('<i class="fa fa-spinner fa-spin"></i> Merge in progress'); }); $(document) - .off('click', '.merge_when_build_succeeds') - .on('click', '.merge_when_build_succeeds', () => { - $('#merge_when_build_succeeds').val('1'); + .off('click', '.merge-when-pipeline-succeeds') + .on('click', '.merge-when-pipeline-succeeds', () => { + $('#merge_when_pipeline_succeeds').val('1'); }); $(document) diff --git a/app/assets/javascripts/notes.js b/app/assets/javascripts/notes.js index 03504255bda..47fa0f2eb96 100644 --- a/app/assets/javascripts/notes.js +++ b/app/assets/javascripts/notes.js @@ -246,12 +246,21 @@ require('./task_list'); }; Notes.prototype.handleCreateChanges = function(note) { + var votesBlock; if (typeof note === 'undefined') { return; } - if (note.commands_changes && note.commands_changes.indexOf('merge') !== -1) { - $.get(mrRefreshWidgetUrl); + if (note.commands_changes) { + if ('merge' in note.commands_changes) { + $.get(mrRefreshWidgetUrl); + } + + if ('emoji_award' in note.commands_changes) { + votesBlock = $('.js-awards-block').eq(0); + gl.awardsHandler.addAwardToEmojiBar(votesBlock, note.commands_changes.emoji_award); + return gl.awardsHandler.scrollToAwards(); + } } }; @@ -262,26 +271,16 @@ require('./task_list'); */ Notes.prototype.renderNote = function(note) { - var $notesList, votesBlock; + var $notesList; if (!note.valid) { - if (note.award) { - new Flash('You have already awarded this emoji!', 'alert', this.parentTimeline); - } - else { - if (note.errors.commands_only) { - new Flash(note.errors.commands_only, 'notice', this.parentTimeline); - this.refresh(); - } + if (note.errors.commands_only) { + new Flash(note.errors.commands_only, 'notice', this.parentTimeline); + this.refresh(); } return; } - if (note.award) { - votesBlock = $('.js-awards-block').eq(0); - gl.awardsHandler.addAwardToEmojiBar(votesBlock, note.name); - return gl.awardsHandler.scrollToAwards(); - // render note if it not present in loaded list - // or skip if rendered - } else if (this.isNewNote(note)) { + + if (this.isNewNote(note)) { this.note_ids.push(note.id); $notesList = $('ul.main-notes-list'); $notesList.append(note.html).syntaxHighlight(); diff --git a/app/assets/javascripts/profile/gl_crop.js.es6 b/app/assets/javascripts/profile/gl_crop.js.es6 index 42e9847af91..192b1192d07 100644 --- a/app/assets/javascripts/profile/gl_crop.js.es6 +++ b/app/assets/javascripts/profile/gl_crop.js.es6 @@ -13,7 +13,7 @@ this.onPickImageClick = this.onPickImageClick.bind(this); this.fileInput = $(input); this.modalCropImg = _.isString(this.modalCropImg) ? $(this.modalCropImg) : this.modalCropImg; - this.fileInput.attr('name', `${this.fileInput.attr('name')}-trigger`).attr('id', `this.fileInput.attr('id')-trigger`); + this.fileInput.attr('name', `${this.fileInput.attr('name')}-trigger`).attr('id', `${this.fileInput.attr('id')}-trigger`); this.exportWidth = exportWidth; this.exportHeight = exportHeight; this.cropBoxWidth = cropBoxWidth; diff --git a/app/assets/javascripts/shortcuts_navigation.js b/app/assets/javascripts/shortcuts_navigation.js index 542cd586df0..73db8c10b99 100644 --- a/app/assets/javascripts/shortcuts_navigation.js +++ b/app/assets/javascripts/shortcuts_navigation.js @@ -16,9 +16,6 @@ require('./shortcuts'); Mousetrap.bind('g p', function() { return ShortcutsNavigation.findAndFollowLink('.shortcuts-project'); }); - Mousetrap.bind('g e', function() { - return ShortcutsNavigation.findAndFollowLink('.shortcuts-project-activity'); - }); Mousetrap.bind('g f', function() { return ShortcutsNavigation.findAndFollowLink('.shortcuts-tree'); }); @@ -31,9 +28,6 @@ require('./shortcuts'); Mousetrap.bind('g n', function() { return ShortcutsNavigation.findAndFollowLink('.shortcuts-network'); }); - Mousetrap.bind('g g', function() { - return ShortcutsNavigation.findAndFollowLink('.shortcuts-graphs'); - }); Mousetrap.bind('g i', function() { return ShortcutsNavigation.findAndFollowLink('.shortcuts-issues'); }); diff --git a/app/assets/javascripts/user_callout.js b/app/assets/javascripts/user_callout.js index 74b869502a4..99419e85b20 100644 --- a/app/assets/javascripts/user_callout.js +++ b/app/assets/javascripts/user_callout.js @@ -43,6 +43,8 @@ class UserCallout { this.userCalloutBody.append($template); $template.find(closeButton).on('click', e => this.dismissCallout(e)); $template.find(userCalloutBtn).on('click', e => this.dismissCallout(e)); + } else { + this.userCalloutBody.remove(); } } @@ -50,7 +52,7 @@ class UserCallout { Cookies.set(USER_CALLOUT_COOKIE, 'true'); const $currentTarget = $(e.currentTarget); if ($currentTarget.hasClass('close-user-callout')) { - this.userCalloutBody.empty(); + this.userCalloutBody.remove(); } } } diff --git a/app/assets/javascripts/vue_pipelines_index/index.js.es6 b/app/assets/javascripts/vue_pipelines_index/index.js.es6 index e7432afb56e..a90bd1518e9 100644 --- a/app/assets/javascripts/vue_pipelines_index/index.js.es6 +++ b/app/assets/javascripts/vue_pipelines_index/index.js.es6 @@ -11,15 +11,10 @@ $(() => new Vue({ data() { const project = document.querySelector('.pipelines'); - const svgs = document.querySelector('.pipeline-svgs').dataset; - - // Transform svgs DOMStringMap to a plain Object. - const svgsObject = gl.utils.DOMStringMapToObject(svgs); return { scope: project.dataset.url, store: new gl.PipelineStore(), - svgs: svgsObject, }; }, components: { @@ -27,10 +22,8 @@ $(() => new Vue({ }, template: ` <vue-pipelines - :scope='scope' - :store='store' - :svgs='svgs' - > + :scope="scope" + :store="store"> </vue-pipelines> `, })); diff --git a/app/assets/javascripts/vue_pipelines_index/pipeline_actions.js.es6 b/app/assets/javascripts/vue_pipelines_index/pipeline_actions.js.es6 index b50afe7c594..891f1f17fb3 100644 --- a/app/assets/javascripts/vue_pipelines_index/pipeline_actions.js.es6 +++ b/app/assets/javascripts/vue_pipelines_index/pipeline_actions.js.es6 @@ -1,9 +1,10 @@ /* global Vue, Flash, gl */ -/* eslint-disable no-param-reassign, no-alert */ +/* eslint-disable no-param-reassign, no-alert */ +const playIconSvg = require('icons/_icon_play.svg'); ((gl) => { gl.VuePipelineActions = Vue.extend({ - props: ['pipeline', 'svgs'], + props: ['pipeline'], computed: { actions() { return this.pipeline.details.manual_actions.length > 0; @@ -31,6 +32,11 @@ } }, }, + + data() { + return { playIconSvg }; + }, + template: ` <td class="pipeline-actions"> <div class="pull-right"> @@ -42,7 +48,7 @@ title="Manual job" data-placement="top" aria-label="Manual job"> - <span v-html="svgs.iconPlay" aria-hidden="true"></span> + <span v-html="playIconSvg" aria-hidden="true"></span> <i class="fa fa-caret-down" aria-hidden="true"></i> </button> <ul class="dropdown-menu dropdown-menu-align-right"> @@ -50,8 +56,8 @@ <a rel="nofollow" data-method="post" - :href="action.path"> - <span v-html="svgs.iconPlay" aria-hidden="true"></span> + :href="action.path" > + <span v-html="playIconSvg" aria-hidden="true"></span> <span>{{action.name}}</span> </a> </li> diff --git a/app/assets/javascripts/vue_pipelines_index/pipelines.js.es6 b/app/assets/javascripts/vue_pipelines_index/pipelines.js.es6 index 9275cdf78f7..601ef41e917 100644 --- a/app/assets/javascripts/vue_pipelines_index/pipelines.js.es6 +++ b/app/assets/javascripts/vue_pipelines_index/pipelines.js.es6 @@ -27,7 +27,7 @@ const CommitPipelinesStoreWithTimeAgo = require('../commit/pipelines/pipelines_s pageRequest: false, }; }, - props: ['scope', 'store', 'svgs'], + props: ['scope', 'store'], created() { const pagenum = gl.utils.getParameterByName('page'); const scope = gl.utils.getParameterByName('scope'); @@ -70,10 +70,7 @@ const CommitPipelinesStoreWithTimeAgo = require('../commit/pipelines/pipelines_s </div> <div class="table-holder" v-if='!pageRequest && pipelines.length'> - <pipelines-table-component - :pipelines='pipelines' - :svgs='svgs'> - </pipelines-table-component> + <pipelines-table-component :pipelines='pipelines'/> </div> <gl-pagination diff --git a/app/assets/javascripts/vue_pipelines_index/stage.js.es6 b/app/assets/javascripts/vue_pipelines_index/stage.js.es6 index 67fdd729e41..f67ebd6a265 100644 --- a/app/assets/javascripts/vue_pipelines_index/stage.js.es6 +++ b/app/assets/javascripts/vue_pipelines_index/stage.js.es6 @@ -1,27 +1,42 @@ /* global Vue, Flash, gl */ /* eslint-disable no-param-reassign */ +import canceledSvg from 'icons/_icon_status_canceled_borderless.svg'; +import createdSvg from 'icons/_icon_status_created_borderless.svg'; +import failedSvg from 'icons/_icon_status_failed_borderless.svg'; +import manualSvg from 'icons/_icon_status_manual_borderless.svg'; +import pendingSvg from 'icons/_icon_status_pending_borderless.svg'; +import runningSvg from 'icons/_icon_status_running_borderless.svg'; +import skippedSvg from 'icons/_icon_status_skipped_borderless.svg'; +import successSvg from 'icons/_icon_status_success_borderless.svg'; +import warningSvg from 'icons/_icon_status_warning_borderless.svg'; ((gl) => { gl.VueStage = Vue.extend({ data() { + const svgsDictionary = { + icon_status_canceled: canceledSvg, + icon_status_created: createdSvg, + icon_status_failed: failedSvg, + icon_status_manual: manualSvg, + icon_status_pending: pendingSvg, + icon_status_running: runningSvg, + icon_status_skipped: skippedSvg, + icon_status_success: successSvg, + icon_status_warning: warningSvg, + }; + return { builds: '', spinner: '<span class="fa fa-spinner fa-spin"></span>', + svg: svgsDictionary[this.stage.status.icon], }; }, + props: { stage: { type: Object, required: true, }, - svgs: { - type: Object, - required: true, - }, - match: { - type: Function, - required: true, - }, }, updated() { @@ -73,11 +88,6 @@ tooltip() { return `has-tooltip ci-status-icon ci-status-icon-${this.stage.status.group}`; }, - svg() { - const { icon } = this.stage.status; - const stageIcon = icon.replace(/icon/i, 'stage_icon'); - return this.svgs[this.match(stageIcon)]; - }, triggerButtonClass() { return `mini-pipeline-graph-dropdown-toggle has-tooltip js-builds-dropdown-button ci-status-icon-${this.stage.status.group}`; }, @@ -91,8 +101,7 @@ data-placement="top" data-toggle="dropdown" type="button" - :aria-label="stage.title" - > + :aria-label="stage.title"> <span v-html="svg" aria-hidden="true"></span> <i class="fa fa-caret-down" aria-hidden="true"></i> </button> @@ -101,8 +110,7 @@ <div :class="dropdownClass" class="js-builds-dropdown-list scrollable-menu" - v-html="buildsOrSpinner" - > + v-html="buildsOrSpinner"> </div> </ul> </div> diff --git a/app/assets/javascripts/vue_pipelines_index/status.js.es6 b/app/assets/javascripts/vue_pipelines_index/status.js.es6 index 05175082704..8d9f83ac113 100644 --- a/app/assets/javascripts/vue_pipelines_index/status.js.es6 +++ b/app/assets/javascripts/vue_pipelines_index/status.js.es6 @@ -1,32 +1,62 @@ /* global Vue, gl */ /* eslint-disable no-param-reassign */ +import canceledSvg from 'icons/_icon_status_canceled.svg'; +import createdSvg from 'icons/_icon_status_created.svg'; +import failedSvg from 'icons/_icon_status_failed.svg'; +import manualSvg from 'icons/_icon_status_manual.svg'; +import pendingSvg from 'icons/_icon_status_pending.svg'; +import runningSvg from 'icons/_icon_status_running.svg'; +import skippedSvg from 'icons/_icon_status_skipped.svg'; +import successSvg from 'icons/_icon_status_success.svg'; +import warningSvg from 'icons/_icon_status_warning.svg'; + ((gl) => { gl.VueStatusScope = Vue.extend({ props: [ - 'pipeline', 'svgs', 'match', + 'pipeline', ], + + data() { + const svgsDictionary = { + icon_status_canceled: canceledSvg, + icon_status_created: createdSvg, + icon_status_failed: failedSvg, + icon_status_manual: manualSvg, + icon_status_pending: pendingSvg, + icon_status_running: runningSvg, + icon_status_skipped: skippedSvg, + icon_status_success: successSvg, + icon_status_warning: warningSvg, + }; + + return { + svg: svgsDictionary[this.pipeline.details.status.icon], + }; + }, + computed: { cssClasses() { const cssObject = { 'ci-status': true }; cssObject[`ci-${this.pipeline.details.status.group}`] = true; return cssObject; }, - svg() { - return this.svgs[this.match(this.pipeline.details.status.icon)]; - }, + detailsPath() { const { status } = this.pipeline.details; return status.has_details ? status.details_path : false; }, + + content() { + return `${this.svg} ${this.pipeline.details.status.text}`; + }, }, template: ` <td class="commit-link"> <a - :class='cssClasses' - :href='detailsPath' - v-html='svg + pipeline.details.status.text' - > + :class="cssClasses" + :href="detailsPath" + v-html="content"> </a> </td> `, diff --git a/app/assets/javascripts/vue_pipelines_index/time_ago.js.es6 b/app/assets/javascripts/vue_pipelines_index/time_ago.js.es6 index 6048fa691dc..a383570857d 100644 --- a/app/assets/javascripts/vue_pipelines_index/time_ago.js.es6 +++ b/app/assets/javascripts/vue_pipelines_index/time_ago.js.es6 @@ -4,14 +4,17 @@ window.Vue = require('vue'); require('../lib/utils/datetime_utility'); +const iconTimerSvg = require('../../../views/shared/icons/_icon_timer.svg'); + ((gl) => { gl.VueTimeAgo = Vue.extend({ data() { return { currentTime: new Date(), + iconTimerSvg, }; }, - props: ['pipeline', 'svgs'], + props: ['pipeline'], computed: { timeAgo() { return gl.utils.getTimeago(); @@ -56,7 +59,7 @@ require('../lib/utils/datetime_utility'); template: ` <td class="pipelines-time-ago"> <p class="duration" v-if='duration'> - <span v-html='svgs.iconTimer'></span> + <span v-html="iconTimerSvg"></span> {{duration}} </p> <p class="finished-at" v-if='timeStopped'> diff --git a/app/assets/javascripts/vue_shared/components/commit.js.es6 b/app/assets/javascripts/vue_shared/components/commit.js.es6 index ff88e236829..4381487b79e 100644 --- a/app/assets/javascripts/vue_shared/components/commit.js.es6 +++ b/app/assets/javascripts/vue_shared/components/commit.js.es6 @@ -1,5 +1,6 @@ /* global Vue */ window.Vue = require('vue'); +const commitIconSvg = require('icons/_icon_commit.svg'); (() => { window.gl = window.gl || {}; @@ -69,11 +70,6 @@ window.Vue = require('vue'); required: false, default: () => ({}), }, - - commitIconSvg: { - type: String, - required: false, - }, }, computed: { @@ -116,6 +112,10 @@ window.Vue = require('vue'); }, }, + data() { + return { commitIconSvg }; + }, + template: ` <div class="branch-commit"> diff --git a/app/assets/javascripts/vue_shared/components/pipelines_table.js.es6 b/app/assets/javascripts/vue_shared/components/pipelines_table.js.es6 index 34d3bbdd80d..0d8f85db965 100644 --- a/app/assets/javascripts/vue_shared/components/pipelines_table.js.es6 +++ b/app/assets/javascripts/vue_shared/components/pipelines_table.js.es6 @@ -21,14 +21,6 @@ require('./pipelines_table_row'); default: () => ([]), }, - /** - * TODO: Remove this when we have webpack. - */ - svgs: { - type: Object, - required: true, - default: () => ({}), - }, }, components: { @@ -51,8 +43,7 @@ require('./pipelines_table_row'); <template v-for="model in pipelines" v-bind:model="model"> <tr is="pipelines-table-row-component" - :pipeline="model" - :svgs="svgs"></tr> + :pipeline="model"></tr> </template> </tbody> </table> diff --git a/app/assets/javascripts/vue_shared/components/pipelines_table_row.js.es6 b/app/assets/javascripts/vue_shared/components/pipelines_table_row.js.es6 index 61c1b72d9d2..e5e88186a85 100644 --- a/app/assets/javascripts/vue_shared/components/pipelines_table_row.js.es6 +++ b/app/assets/javascripts/vue_shared/components/pipelines_table_row.js.es6 @@ -25,14 +25,6 @@ require('./commit'); default: () => ({}), }, - /** - * TODO: Remove this when we have webpack; - */ - svgs: { - type: Object, - required: true, - default: () => ({}), - }, }, components: { @@ -174,30 +166,9 @@ require('./commit'); }, }, - methods: { - /** - * FIXME: This should not be in this component but in the components that - * need this function. - * - * Used to render SVGs in the following components: - * - status-scope - * - dropdown-stage - * - * @param {String} string - * @return {String} - */ - match(string) { - return string.replace(/_([a-z])/g, (m, w) => w.toUpperCase()); - }, - }, - template: ` <tr class="commit"> - <status-scope - :pipeline="pipeline" - :svgs="svgs" - :match="match"> - </status-scope> + <status-scope :pipeline="pipeline"/> <pipeline-url :pipeline="pipeline"></pipeline-url> @@ -208,26 +179,20 @@ require('./commit'); :commit-url="commitUrl" :short-sha="commitShortSha" :title="commitTitle" - :author="commitAuthor" - :commit-icon-svg="svgs.commitIconSvg"> - </commit-component> + :author="commitAuthor"/> </td> <td class="stage-cell"> <div class="stage-container dropdown js-mini-pipeline-graph" v-if="pipeline.details.stages.length > 0" v-for="stage in pipeline.details.stages"> - <dropdown-stage - :stage="stage" - :svgs="svgs" - :match="match"> - </dropdown-stage> + <dropdown-stage :stage="stage"/> </div> </td> - <time-ago :pipeline="pipeline" :svgs="svgs"></time-ago> + <time-ago :pipeline="pipeline"/> - <pipeline-actions :pipeline="pipeline" :svgs="svgs"></pipeline-actions> + <pipeline-actions :pipeline="pipeline" /> </tr> `, }); diff --git a/app/assets/javascripts/vue_shared/components/table_pagination.js.es6 b/app/assets/javascripts/vue_shared/components/table_pagination.js.es6 index dd046405575..8943b850a72 100644 --- a/app/assets/javascripts/vue_shared/components/table_pagination.js.es6 +++ b/app/assets/javascripts/vue_shared/components/table_pagination.js.es6 @@ -19,7 +19,6 @@ window.Vue = require('vue'); /** This function will take the information given by the pagination component - And make a new Turbolinks call Here is an example `change` method: diff --git a/app/assets/stylesheets/framework/calendar.scss b/app/assets/stylesheets/framework/calendar.scss index fb8ea18d122..9a0f7a14e57 100644 --- a/app/assets/stylesheets/framework/calendar.scss +++ b/app/assets/stylesheets/framework/calendar.scss @@ -1,6 +1,7 @@ .calender-block { padding-left: 0; padding-right: 0; + border-top: 0; direction: rtl; @media (min-width: $screen-sm-min) and (max-width: $screen-md-max) { diff --git a/app/assets/stylesheets/framework/dropdowns.scss b/app/assets/stylesheets/framework/dropdowns.scss index ff31e7f7b3d..6e8a5cc688b 100644 --- a/app/assets/stylesheets/framework/dropdowns.scss +++ b/app/assets/stylesheets/framework/dropdowns.scss @@ -107,11 +107,12 @@ &.fa-spinner { font-size: 16px; - margin-top: -8px; + margin-top: -3px; } } - .fa-chevron-down { + .fa-chevron-down, + .fa-spinner { position: absolute; top: 11px; right: 8px; @@ -192,6 +193,10 @@ &.is-focused { background-color: $dropdown-link-hover-bg; text-decoration: none; + + .badge { + background-color: darken($row-hover, 5%); + } } &.dropdown-menu-empty-link { @@ -228,6 +233,12 @@ padding: 5px 8px; color: $gl-text-color-secondary; } + + .badge { + position: absolute; + right: 8px; + top: 5px; + } } .dropdown-menu-drop-up { diff --git a/app/assets/stylesheets/framework/files.scss b/app/assets/stylesheets/framework/files.scss index 30f242a35db..ffece53a093 100644 --- a/app/assets/stylesheets/framework/files.scss +++ b/app/assets/stylesheets/framework/files.scss @@ -271,6 +271,7 @@ span.idiff { font-size: 13px; line-height: 28px; display: inline-block; + float: none; } } } diff --git a/app/assets/stylesheets/framework/header.scss b/app/assets/stylesheets/framework/header.scss index 685a4847731..f4316ec7022 100644 --- a/app/assets/stylesheets/framework/header.scss +++ b/app/assets/stylesheets/framework/header.scss @@ -149,14 +149,14 @@ header { .header-logo { display: inline-block; - margin: 0 8px 0 3px; + margin: 0 7px 0 2px; position: relative; - top: 7px; + top: 10px; transition-duration: .3s; svg, img { - height: 36px; + height: 28px; } &:hover { diff --git a/app/assets/stylesheets/framework/sidebar.scss b/app/assets/stylesheets/framework/sidebar.scss index 8978e284f55..40e93032f59 100644 --- a/app/assets/stylesheets/framework/sidebar.scss +++ b/app/assets/stylesheets/framework/sidebar.scss @@ -73,10 +73,6 @@ right: $gutter_collapsed_width; } } - - &.with-overlay { - padding-right: $gutter_collapsed_width; - } } .right-sidebar { diff --git a/app/assets/stylesheets/highlight/dark.scss b/app/assets/stylesheets/highlight/dark.scss index 9c76e58dfc8..09951fe3d3e 100644 --- a/app/assets/stylesheets/highlight/dark.scss +++ b/app/assets/stylesheets/highlight/dark.scss @@ -21,6 +21,7 @@ $dark-highlight-color: $black; $dark-pre-hll-bg: #373b41; $dark-hll-bg: #373b41; $dark-over-bg: #9f9ab5; +$dark-expanded-bg: #3e3e3e; $dark-c: #969896; $dark-err: #c66; $dark-k: #b294bb; @@ -155,6 +156,22 @@ $dark-il: #de935f; .line_content.match { @include dark-diff-match-line; } + + &:not(.diff-expanded) + .diff-expanded, + &.diff-expanded + .line_holder:not(.diff-expanded) { + > .diff-line-num, + > .line_content { + border-top: 1px solid $black; + } + } + + &.diff-expanded { + > .diff-line-num, + > .line_content { + background: $dark-expanded-bg; + border-color: $dark-expanded-bg; + } + } } // highlight line via anchor diff --git a/app/assets/stylesheets/highlight/monokai.scss b/app/assets/stylesheets/highlight/monokai.scss index 6488a099c74..b6a6d298adf 100644 --- a/app/assets/stylesheets/highlight/monokai.scss +++ b/app/assets/stylesheets/highlight/monokai.scss @@ -14,6 +14,7 @@ $monokai-line-empty-border: darken($monokai-line-empty-bg, 15%); $monokai-diff-border: #808080; $monokai-highlight-bg: #ffe792; $monokai-over-bg: #9f9ab5; +$monokai-expanded-bg: #3e3e3e; $monokai-new-bg: rgba(166, 226, 46, 0.1); $monokai-new-idiff: rgba(166, 226, 46, 0.15); @@ -155,6 +156,22 @@ $monokai-gi: #a6e22e; .line_content.match { @include dark-diff-match-line; } + + &:not(.diff-expanded) + .diff-expanded, + &.diff-expanded + .line_holder:not(.diff-expanded) { + > .diff-line-num, + > .line_content { + border-top: 1px solid $black; + } + } + + &.diff-expanded { + > .diff-line-num, + > .line_content { + background: $monokai-expanded-bg; + border-color: $monokai-expanded-bg; + } + } } // highlight line via anchor diff --git a/app/assets/stylesheets/highlight/solarized_dark.scss b/app/assets/stylesheets/highlight/solarized_dark.scss index 00079cc2b5b..4f7a50dcb4f 100644 --- a/app/assets/stylesheets/highlight/solarized_dark.scss +++ b/app/assets/stylesheets/highlight/solarized_dark.scss @@ -18,6 +18,7 @@ $solarized-dark-line-color-old: #7a6c71; $solarized-dark-highlight: #094554; $solarized-dark-hll-bg: #174652; $solarized-dark-over-bg: #9f9ab5; +$solarized-dark-expanded-bg: #010d10; $solarized-dark-c: #586e75; $solarized-dark-err: #93a1a1; $solarized-dark-g: #93a1a1; @@ -159,6 +160,22 @@ $solarized-dark-il: #2aa198; .line_content.match { @include dark-diff-match-line; } + + &:not(.diff-expanded) + .diff-expanded, + &.diff-expanded + .line_holder:not(.diff-expanded) { + > .diff-line-num, + > .line_content { + border-top: 1px solid $black; + } + } + + &.diff-expanded { + > .diff-line-num, + > .line_content { + background: $solarized-dark-expanded-bg; + border-color: $solarized-dark-expanded-bg; + } + } } // highlight line via anchor diff --git a/app/assets/stylesheets/highlight/solarized_light.scss b/app/assets/stylesheets/highlight/solarized_light.scss index 2e3b68f1a58..6463fe96c1b 100644 --- a/app/assets/stylesheets/highlight/solarized_light.scss +++ b/app/assets/stylesheets/highlight/solarized_light.scss @@ -19,6 +19,8 @@ $solarized-light-line-color-old: #ad9186; $solarized-light-highlight: #eee8d5; $solarized-light-hll-bg: #ddd8c5; $solarized-light-over-bg: #ded7fc; +$solarized-light-expanded-border: #d2cdbd; +$solarized-light-expanded-bg: #ece6d4; $solarized-light-c: #93a1a1; $solarized-light-err: #586e75; $solarized-light-g: #586e75; @@ -166,6 +168,22 @@ $solarized-light-il: #2aa198; .line_content.match { @include matchLine; } + + &:not(.diff-expanded) + .diff-expanded, + &.diff-expanded + .line_holder:not(.diff-expanded) { + > .diff-line-num, + > .line_content { + border-top: 1px solid $solarized-light-expanded-border; + } + } + + &.diff-expanded { + > .diff-line-num, + > .line_content { + background: $solarized-light-expanded-bg; + border-color: $solarized-light-expanded-bg; + } + } } // highlight line via anchor diff --git a/app/assets/stylesheets/highlight/white.scss b/app/assets/stylesheets/highlight/white.scss index 0eca30e570f..ab2018bfbca 100644 --- a/app/assets/stylesheets/highlight/white.scss +++ b/app/assets/stylesheets/highlight/white.scss @@ -8,6 +8,8 @@ $white-highlight: #fafe3d; $white-pre-hll-bg: #f8eec7; $white-hll-bg: #f8f8f8; $white-over-bg: #ded7fc; +$white-expanded-border: #e0e0e0; +$white-expanded-bg: #f7f7f7; $white-c: #998; $white-err: #a61717; $white-err-bg: #e3d2d2; @@ -140,6 +142,22 @@ $white-gc-bg: #eaf2f5; } } + &:not(.diff-expanded) + .diff-expanded, + &.diff-expanded + .line_holder:not(.diff-expanded) { + > .diff-line-num, + > .line_content { + border-top: 1px solid $white-expanded-border; + } + } + + &.diff-expanded { + > .diff-line-num, + > .line_content { + background: $white-expanded-bg; + border-color: $white-expanded-bg; + } + } + .line_content { &.old { background-color: $line-removed; diff --git a/app/assets/stylesheets/pages/diff.scss b/app/assets/stylesheets/pages/diff.scss index 339cdcde480..5d0c247dea8 100644 --- a/app/assets/stylesheets/pages/diff.scss +++ b/app/assets/stylesheets/pages/diff.scss @@ -133,8 +133,13 @@ width: 35px; font-weight: normal; - &:hover { - text-decoration: underline; + &[disabled] { + cursor: default; + + &:hover, + &:active { + text-decoration: none; + } } } } diff --git a/app/assets/stylesheets/pages/events.scss b/app/assets/stylesheets/pages/events.scss index 5776d86983a..08398bb43a2 100644 --- a/app/assets/stylesheets/pages/events.scss +++ b/app/assets/stylesheets/pages/events.scss @@ -155,7 +155,7 @@ @media (max-width: $screen-xs-max) { .event-item { - padding-left: $gl-padding; + padding-left: 0; .event-title { white-space: normal; @@ -169,8 +169,7 @@ .event-body { margin: 0; - border-left: 2px solid $events-body-border; - padding-left: 10px; + padding-left: 0; } .event-item-timestamp { diff --git a/app/assets/stylesheets/pages/merge_requests.scss b/app/assets/stylesheets/pages/merge_requests.scss index 0b0c4bc130d..a629a5333d7 100644 --- a/app/assets/stylesheets/pages/merge_requests.scss +++ b/app/assets/stylesheets/pages/merge_requests.scss @@ -29,7 +29,7 @@ background-color: $gl-success; } - .accept_merge_request { + .accept-merge-request { &.ci-pending, &.ci-running { @include btn-blue; @@ -42,6 +42,12 @@ @include btn-red; } } + + .dropdown-toggle { + .fa { + color: inherit; + } + } } .accept-control { diff --git a/app/assets/stylesheets/pages/profile.scss b/app/assets/stylesheets/pages/profile.scss index aad1a8986b0..1a983d8c9ef 100644 --- a/app/assets/stylesheets/pages/profile.scss +++ b/app/assets/stylesheets/pages/profile.scss @@ -279,7 +279,7 @@ table.u2f-registrations { } .user-callout { - margin: 24px auto 0; + margin: 0 auto; .bordered-box { border: 1px solid $border-color; @@ -287,6 +287,7 @@ table.u2f-registrations { } .landing { + margin-top: $gl-padding; margin-bottom: $gl-padding; .close { diff --git a/app/assets/stylesheets/pages/tree.scss b/app/assets/stylesheets/pages/tree.scss index e4487dbcb87..8d1063fc26f 100644 --- a/app/assets/stylesheets/pages/tree.scss +++ b/app/assets/stylesheets/pages/tree.scss @@ -178,3 +178,29 @@ margin-left: $btn-side-margin; } } + +.repo-charts { + .sub-header { + margin: 20px 0; + } + + .sub-header-block.border-top { + margin-top: 20px; + padding: 0; + border-top: 1px solid $white-dark; + border-bottom: none; + } + + .commit-stats li { + font-size: 16px; + } + + .tree-ref-header { + margin-bottom: 20px; + + h4 { + margin: 0; + line-height: 36px; + } + } +} diff --git a/app/controllers/application_controller.rb b/app/controllers/application_controller.rb index e42e48f87d2..32484f810da 100644 --- a/app/controllers/application_controller.rb +++ b/app/controllers/application_controller.rb @@ -72,14 +72,6 @@ class ApplicationController < ActionController::Base end end - def authenticate_user!(*args) - if redirect_to_home_page_url? - return redirect_to current_application_settings.home_page_url - end - - super(*args) - end - def log_exception(exception) application_trace = ActionDispatch::ExceptionWrapper.new(env, exception).application_trace application_trace.map!{ |t| " #{t}\n" } @@ -287,19 +279,6 @@ class ApplicationController < ActionController::Base session[:skip_tfa] && session[:skip_tfa] > Time.current end - def redirect_to_home_page_url? - # If user is not signed-in and tries to access root_path - redirect him to landing page - # Don't redirect to the default URL to prevent endless redirections - return false unless current_application_settings.home_page_url.present? - - home_page_url = current_application_settings.home_page_url.chomp('/') - root_urls = [Gitlab.config.gitlab['url'].chomp('/'), root_url.chomp('/')] - - return false if root_urls.include?(home_page_url) - - current_user.nil? && root_path == request.path - end - # U2F (universal 2nd factor) devices need a unique identifier for the application # to perform authentication. # https://developers.yubico.com/U2F/App_ID.html diff --git a/app/controllers/ci/projects_controller.rb b/app/controllers/ci/projects_controller.rb deleted file mode 100644 index ff297d6ff13..00000000000 --- a/app/controllers/ci/projects_controller.rb +++ /dev/null @@ -1,47 +0,0 @@ -module Ci - class ProjectsController < ::ApplicationController - before_action :project - before_action :no_cache, only: [:badge] - before_action :authorize_read_project!, except: [:badge, :index] - skip_before_action :authenticate_user!, only: [:badge] - protect_from_forgery - - def index - redirect_to root_path - end - - def show - # Temporary compatibility with CI badges pointing to CI project page - redirect_to namespace_project_path(project.namespace, project) - end - - # Project status badge - # Image with build status for sha or ref - # - # This action in DEPRECATED, this is here only for backwards compatibility - # with projects migrated from GitLab CI. - # - def badge - return render_404 unless @project - - image = Ci::ImageForBuildService.new.execute(@project, params) - send_file image.path, filename: image.name, disposition: 'inline', type: "image/svg+xml" - end - - protected - - def project - @project ||= Project.find_by(ci_id: params[:id].to_i) - end - - def no_cache - response.headers["Cache-Control"] = "no-cache, no-store, max-age=0, must-revalidate" - response.headers["Pragma"] = "no-cache" - response.headers["Expires"] = "Fri, 01 Jan 1990 00:00:00 GMT" - end - - def authorize_read_project! - return access_denied! unless can?(current_user, :read_project, project) - end - end -end diff --git a/app/controllers/concerns/creates_commit.rb b/app/controllers/concerns/creates_commit.rb index 2fe03020d2d..f897f828cec 100644 --- a/app/controllers/concerns/creates_commit.rb +++ b/app/controllers/concerns/creates_commit.rb @@ -4,7 +4,7 @@ module CreatesCommit def create_commit(service, success_path:, failure_path:, failure_view: nil, success_notice: nil) set_commit_variables - start_branch = @mr_target_branch unless initial_commit? + start_branch = @mr_target_branch commit_params = @commit_params.merge( start_project: @mr_target_project, start_branch: start_branch, @@ -117,11 +117,6 @@ module CreatesCommit @mr_source_branch = guess_mr_source_branch end - def initial_commit? - @mr_target_branch.nil? || - !@mr_target_project.repository.branch_exists?(@mr_target_branch) - end - def guess_mr_source_branch # XXX: Happens when viewing a commit without a branch. In this case, # @target_branch would be the default branch for @mr_source_project, diff --git a/app/controllers/dashboard/groups_controller.rb b/app/controllers/dashboard/groups_controller.rb index 0b7cf8167f0..d03265e9f20 100644 --- a/app/controllers/dashboard/groups_controller.rb +++ b/app/controllers/dashboard/groups_controller.rb @@ -1,5 +1,17 @@ class Dashboard::GroupsController < Dashboard::ApplicationController def index - @group_members = current_user.group_members.includes(source: :route).page(params[:page]) + @group_members = current_user.group_members.includes(source: :route).joins(:group) + @group_members = @group_members.merge(Group.search(params[:filter_groups])) if params[:filter_groups].present? + @group_members = @group_members.merge(Group.sort(@sort = params[:sort])) + @group_members = @group_members.page(params[:page]) + + respond_to do |format| + format.html + format.json do + render json: { + html: view_to_html_string("dashboard/groups/_groups", locals: { group_members: @group_members }) + } + end + end end end diff --git a/app/controllers/explore/groups_controller.rb b/app/controllers/explore/groups_controller.rb index a962f9a0937..68228c095da 100644 --- a/app/controllers/explore/groups_controller.rb +++ b/app/controllers/explore/groups_controller.rb @@ -1,8 +1,17 @@ class Explore::GroupsController < Explore::ApplicationController def index @groups = GroupsFinder.new.execute(current_user) - @groups = @groups.search(params[:search]) if params[:search].present? + @groups = @groups.search(params[:filter_groups]) if params[:filter_groups].present? @groups = @groups.sort(@sort = params[:sort]) @groups = @groups.page(params[:page]) + + respond_to do |format| + format.html + format.json do + render json: { + html: view_to_html_string("explore/groups/_groups", locals: { groups: @groups }) + } + end + end end end diff --git a/app/controllers/projects/blob_controller.rb b/app/controllers/projects/blob_controller.rb index 39ba815cfca..f9a5ef46786 100644 --- a/app/controllers/projects/blob_controller.rb +++ b/app/controllers/projects/blob_controller.rb @@ -5,7 +5,7 @@ class Projects::BlobController < Projects::ApplicationController include ActionView::Helpers::SanitizeHelper # Raised when given an invalid file path - class InvalidPathError < StandardError; end + InvalidPathError = Class.new(StandardError) before_action :require_non_empty_project, except: [:new, :create] before_action :authorize_download_code! diff --git a/app/controllers/projects/graphs_controller.rb b/app/controllers/projects/graphs_controller.rb index 923e7340e69..43fc0c39801 100644 --- a/app/controllers/projects/graphs_controller.rb +++ b/app/controllers/projects/graphs_controller.rb @@ -17,6 +17,25 @@ class Projects::GraphsController < Projects::ApplicationController end def commits + redirect_to action: 'charts' + end + + def languages + redirect_to action: 'charts' + end + + def charts + get_commits + get_languages + end + + def ci + redirect_to charts_namespace_project_pipelines_path(@project.namespace, @project) + end + + private + + def get_commits @commits = @project.repository.commits(@ref, limit: 2000, skip_merges: true) @commits_graph = Gitlab::Graphs::Commits.new(@commits) @commits_per_week_days = @commits_graph.commits_per_week_days @@ -24,15 +43,7 @@ class Projects::GraphsController < Projects::ApplicationController @commits_per_month = @commits_graph.commits_per_month end - def ci - @charts = {} - @charts[:week] = Ci::Charts::WeekChart.new(project) - @charts[:month] = Ci::Charts::MonthChart.new(project) - @charts[:year] = Ci::Charts::YearChart.new(project) - @charts[:build_times] = Ci::Charts::BuildTime.new(project) - end - - def languages + def get_languages @languages = Linguist::Repository.new(@repository.rugged, @repository.rugged.head.target_id).languages total = @languages.map(&:last).sum @@ -52,8 +63,6 @@ class Projects::GraphsController < Projects::ApplicationController end end - private - def fetch_graph @commits = @project.repository.commits(@ref, limit: 6000, skip_merges: true) @log = [] diff --git a/app/controllers/projects/merge_requests_controller.rb b/app/controllers/projects/merge_requests_controller.rb index 53f30a24312..82f9b6e06db 100644 --- a/app/controllers/projects/merge_requests_controller.rb +++ b/app/controllers/projects/merge_requests_controller.rb @@ -10,11 +10,11 @@ class Projects::MergeRequestsController < Projects::ApplicationController before_action :module_enabled before_action :merge_request, only: [ :edit, :update, :show, :diffs, :commits, :conflicts, :conflict_for_path, :pipelines, :merge, :merge_check, - :ci_status, :ci_environments_status, :toggle_subscription, :cancel_merge_when_build_succeeds, :remove_wip, :resolve_conflicts, :assign_related_issues + :ci_status, :ci_environments_status, :toggle_subscription, :cancel_merge_when_pipeline_succeeds, :remove_wip, :resolve_conflicts, :assign_related_issues ] before_action :validates_merge_request, only: [:show, :diffs, :commits, :pipelines] before_action :define_show_vars, only: [:show, :diffs, :commits, :conflicts, :conflict_for_path, :builds, :pipelines] - before_action :define_widget_vars, only: [:merge, :cancel_merge_when_build_succeeds, :merge_check] + before_action :define_widget_vars, only: [:merge, :cancel_merge_when_pipeline_succeeds, :merge_check] before_action :define_commit_vars, only: [:diffs] before_action :define_diff_comment_vars, only: [:diffs] before_action :ensure_ref_fetched, only: [:show, :diffs, :commits, :builds, :conflicts, :conflict_for_path, :pipelines] @@ -245,9 +245,11 @@ class Projects::MergeRequestsController < Projects::ApplicationController format.json do define_pipelines_vars - render json: PipelineSerializer + render json: { + pipelines: PipelineSerializer .new(project: @project, user: @current_user) .represent(@pipelines) + } end end end @@ -322,12 +324,13 @@ class Projects::MergeRequestsController < Projects::ApplicationController def merge_check @merge_request.check_if_can_be_merged + @pipelines = @merge_request.all_pipelines render partial: "projects/merge_requests/widget/show.html.haml", layout: false end - def cancel_merge_when_build_succeeds - unless @merge_request.can_cancel_merge_when_build_succeeds?(current_user) + def cancel_merge_when_pipeline_succeeds + unless @merge_request.can_cancel_merge_when_pipeline_succeeds?(current_user) return access_denied! end @@ -339,9 +342,9 @@ class Projects::MergeRequestsController < Projects::ApplicationController def merge return access_denied! unless @merge_request.can_be_merged_by?(current_user) - # Disable the CI check if merge_when_build_succeeds is enabled since we have + # Disable the CI check if merge_when_pipeline_succeeds is enabled since we have # to wait until CI completes to know - unless @merge_request.mergeable?(skip_ci_check: merge_when_build_succeeds_active?) + unless @merge_request.mergeable?(skip_ci_check: merge_when_pipeline_succeeds_active?) @status = :failed return end @@ -353,7 +356,7 @@ class Projects::MergeRequestsController < Projects::ApplicationController @merge_request.update(merge_error: nil) - if params[:merge_when_build_succeeds].present? + if params[:merge_when_pipeline_succeeds].present? unless @merge_request.head_pipeline @status = :failed return @@ -364,7 +367,7 @@ class Projects::MergeRequestsController < Projects::ApplicationController .new(@project, current_user, merge_params) .execute(@merge_request) - @status = :merge_when_build_succeeds + @status = :merge_when_pipeline_succeeds elsif @merge_request.head_pipeline.success? # This can be triggered when a user clicks the auto merge button while # the tests finish at about the same time @@ -381,8 +384,8 @@ class Projects::MergeRequestsController < Projects::ApplicationController def merge_widget_refresh @status = - if merge_request.merge_when_build_succeeds - :merge_when_build_succeeds + if merge_request.merge_when_pipeline_succeeds + :merge_when_pipeline_succeeds else # Only MRs that can be merged end in this action # MR can be already picked up for merge / merged already or can be waiting for worker to be picked up @@ -444,6 +447,7 @@ class Projects::MergeRequestsController < Projects::ApplicationController def ci_status pipeline = @merge_request.head_pipeline + @pipelines = @merge_request.all_pipelines if pipeline status = pipeline.status @@ -462,7 +466,8 @@ class Projects::MergeRequestsController < Projects::ApplicationController sha: (merge_request.diff_head_commit.short_id if merge_request.diff_head_sha), status: status, coverage: coverage, - pipeline: pipeline.try(:id) + pipeline: pipeline.try(:id), + has_ci: @merge_request.has_ci? } render json: response @@ -672,8 +677,8 @@ class Projects::MergeRequestsController < Projects::ApplicationController @merge_request.ensure_ref_fetched end - def merge_when_build_succeeds_active? - params[:merge_when_build_succeeds].present? && + def merge_when_pipeline_succeeds_active? + params[:merge_when_pipeline_succeeds].present? && @merge_request.head_pipeline && @merge_request.head_pipeline.active? end diff --git a/app/controllers/projects/notes_controller.rb b/app/controllers/projects/notes_controller.rb index b033f7b5ea9..5cf3a7f593b 100644 --- a/app/controllers/projects/notes_controller.rb +++ b/app/controllers/projects/notes_controller.rb @@ -148,17 +148,10 @@ class Projects::NotesController < Projects::ApplicationController def note_json(note) attrs = { - award: false, id: note.id } - if note.is_a?(AwardEmoji) - attrs.merge!( - valid: note.valid?, - award: true, - name: note.name - ) - elsif note.persisted? + if note.persisted? Banzai::NoteRenderer.render([note], @project, current_user) attrs.merge!( @@ -198,7 +191,7 @@ class Projects::NotesController < Projects::ApplicationController ) end - attrs[:commands_changes] = note.commands_changes unless attrs[:award] + attrs[:commands_changes] = note.commands_changes attrs end diff --git a/app/controllers/projects/pipelines_controller.rb b/app/controllers/projects/pipelines_controller.rb index 8657bc4dfdc..718d9e86bea 100644 --- a/app/controllers/projects/pipelines_controller.rb +++ b/app/controllers/projects/pipelines_controller.rb @@ -1,9 +1,10 @@ class Projects::PipelinesController < Projects::ApplicationController - before_action :pipeline, except: [:index, :new, :create] + before_action :pipeline, except: [:index, :new, :create, :charts] before_action :commit, only: [:show, :builds] before_action :authorize_read_pipeline! before_action :authorize_create_pipeline!, only: [:new, :create] before_action :authorize_update_pipeline!, only: [:retry, :cancel] + before_action :builds_enabled, only: :charts def index @scope = params[:scope] @@ -92,6 +93,14 @@ class Projects::PipelinesController < Projects::ApplicationController redirect_back_or_default default: namespace_project_pipelines_path(project.namespace, project) end + def charts + @charts = {} + @charts[:week] = Ci::Charts::WeekChart.new(project) + @charts[:month] = Ci::Charts::MonthChart.new(project) + @charts[:year] = Ci::Charts::YearChart.new(project) + @charts[:build_times] = Ci::Charts::BuildTime.new(project) + end + private def create_params diff --git a/app/controllers/projects_controller.rb b/app/controllers/projects_controller.rb index acca821782c..3e2015b7d5e 100644 --- a/app/controllers/projects_controller.rb +++ b/app/controllers/projects_controller.rb @@ -314,7 +314,7 @@ class ProjectsController < Projects::ApplicationController :name, :namespace_id, :only_allow_merge_if_all_discussions_are_resolved, - :only_allow_merge_if_build_succeeds, + :only_allow_merge_if_pipeline_succeeds, :path, :public_builds, :request_access_enabled, diff --git a/app/controllers/root_controller.rb b/app/controllers/root_controller.rb index db2817fadf6..1b4545e4a49 100644 --- a/app/controllers/root_controller.rb +++ b/app/controllers/root_controller.rb @@ -8,7 +8,9 @@ # `DashboardController#show`, which is the default. class RootController < Dashboard::ProjectsController skip_before_action :authenticate_user!, only: [:index] - before_action :redirect_to_custom_dashboard, only: [:index] + + before_action :redirect_unlogged_user, if: -> { current_user.nil? } + before_action :redirect_logged_user, if: -> { current_user.present? } def index super @@ -16,23 +18,38 @@ class RootController < Dashboard::ProjectsController private - def redirect_to_custom_dashboard - return redirect_to new_user_session_path unless current_user + def redirect_unlogged_user + if redirect_to_home_page_url? + redirect_to(current_application_settings.home_page_url) + else + redirect_to(new_user_session_path) + end + end + def redirect_logged_user case current_user.dashboard when 'stars' flash.keep - redirect_to starred_dashboard_projects_path + redirect_to(starred_dashboard_projects_path) when 'project_activity' - redirect_to activity_dashboard_path + redirect_to(activity_dashboard_path) when 'starred_project_activity' - redirect_to activity_dashboard_path(filter: 'starred') + redirect_to(activity_dashboard_path(filter: 'starred')) when 'groups' - redirect_to dashboard_groups_path + redirect_to(dashboard_groups_path) when 'todos' - redirect_to dashboard_todos_path - else - return + redirect_to(dashboard_todos_path) end end + + def redirect_to_home_page_url? + # If user is not signed-in and tries to access root_path - redirect him to landing page + # Don't redirect to the default URL to prevent endless redirections + return false unless current_application_settings.home_page_url.present? + + home_page_url = current_application_settings.home_page_url.chomp('/') + root_urls = [Gitlab.config.gitlab['url'].chomp('/'), root_url.chomp('/')] + + root_urls.exclude?(home_page_url) + end end diff --git a/app/helpers/button_helper.rb b/app/helpers/button_helper.rb index 195094730aa..0b30471f2ae 100644 --- a/app/helpers/button_helper.rb +++ b/app/helpers/button_helper.rb @@ -19,7 +19,7 @@ module ButtonHelper title = data[:title] || 'Copy to clipboard' data = { toggle: 'tooltip', placement: 'bottom', container: 'body' }.merge(data) content_tag :button, - icon('clipboard'), + icon('clipboard', 'aria-hidden': 'true'), class: "btn #{css_class}", data: data, type: :button, diff --git a/app/helpers/explore_helper.rb b/app/helpers/explore_helper.rb index 2b1f3825adc..bbcb52f7eaf 100644 --- a/app/helpers/explore_helper.rb +++ b/app/helpers/explore_helper.rb @@ -9,12 +9,20 @@ module ExploreHelper } options = exist_opts.merge(options) - path = request.path - path << "?#{options.to_param}" - path + request_path_with_options(options) + end + + def filter_groups_path(options = {}) + request_path_with_options(options) end def explore_controller? controller.class.name.split("::").first == "Explore" end + + private + + def request_path_with_options(options = {}) + request.path + "?#{options.to_param}" + end end diff --git a/app/helpers/issuables_helper.rb b/app/helpers/issuables_helper.rb index 860a665ae26..c2b399041c6 100644 --- a/app/helpers/issuables_helper.rb +++ b/app/helpers/issuables_helper.rb @@ -1,6 +1,6 @@ module IssuablesHelper def sidebar_gutter_toggle_icon - sidebar_gutter_collapsed? ? icon('angle-double-left') : icon('angle-double-right') + sidebar_gutter_collapsed? ? icon('angle-double-left', { 'aria-hidden': 'true' }) : icon('angle-double-right', { 'aria-hidden': 'true' }) end def sidebar_gutter_collapsed_class diff --git a/app/helpers/merge_requests_helper.rb b/app/helpers/merge_requests_helper.rb index 7d8505d704e..38be073c8dc 100644 --- a/app/helpers/merge_requests_helper.rb +++ b/app/helpers/merge_requests_helper.rb @@ -146,7 +146,7 @@ module MergeRequestsHelper def merge_params(merge_request) { - merge_when_build_succeeds: true, + merge_when_pipeline_succeeds: true, should_remove_source_branch: true, sha: merge_request.diff_head_sha }.merge(merge_params_ee(merge_request)) diff --git a/app/helpers/rss_helper.rb b/app/helpers/rss_helper.rb new file mode 100644 index 00000000000..ea5d2932ef4 --- /dev/null +++ b/app/helpers/rss_helper.rb @@ -0,0 +1,5 @@ +module RssHelper + def rss_url_options + { format: :atom, private_token: current_user.try(:private_token) } + end +end diff --git a/app/models/application_setting.rb b/app/models/application_setting.rb index dc36c754438..255e8c4ff78 100644 --- a/app/models/application_setting.rb +++ b/app/models/application_setting.rb @@ -179,6 +179,7 @@ class ApplicationSetting < ActiveRecord::Base default_project_visibility: Settings.gitlab.default_projects_features['visibility_level'], default_projects_limit: Settings.gitlab['default_projects_limit'], default_snippet_visibility: Settings.gitlab.default_projects_features['visibility_level'], + default_group_visibility: Settings.gitlab.default_projects_features['visibility_level'], disabled_oauth_sign_in_sources: [], domain_whitelist: Settings.gitlab['domain_whitelist'], gravatar_enabled: Settings.gravatar['enabled'], @@ -277,6 +278,22 @@ class ApplicationSetting < ActiveRecord::Base self.repository_storages = [value] end + def default_project_visibility=(level) + super(Gitlab::VisibilityLevel.level_value(level)) + end + + def default_snippet_visibility=(level) + super(Gitlab::VisibilityLevel.level_value(level)) + end + + def default_group_visibility=(level) + super(Gitlab::VisibilityLevel.level_value(level)) + end + + def restricted_visibility_levels=(levels) + super(levels.map { |level| Gitlab::VisibilityLevel.level_value(level) }) + end + # Choose one of the available repository storage options. Currently all have # equal weighting. def pick_repository_storage diff --git a/app/models/ci/build.rb b/app/models/ci/build.rb index 77aba91f65c..f2989eff22d 100644 --- a/app/models/ci/build.rb +++ b/app/models/ci/build.rb @@ -55,15 +55,6 @@ module Ci pending.unstarted.order('created_at ASC').first end - def create_from(build) - new_build = build.dup - new_build.status = 'pending' - new_build.runner_id = nil - new_build.trigger_request_id = nil - new_build.token = nil - new_build.save - end - def retry(build, current_user) Ci::RetryBuildService .new(build.project, current_user) diff --git a/app/models/group.rb b/app/models/group.rb index 240a17f1dc1..7d23f655225 100644 --- a/app/models/group.rb +++ b/app/models/group.rb @@ -93,7 +93,7 @@ class Group < Namespace end def visibility_level_field - visibility_level + :visibility_level end def visibility_level_allowed_by_projects diff --git a/app/models/merge_request.rb b/app/models/merge_request.rb index d6e7ed87555..0f7b8311588 100644 --- a/app/models/merge_request.rb +++ b/app/models/merge_request.rb @@ -97,7 +97,7 @@ class MergeRequest < ActiveRecord::Base validates :source_branch, presence: true validates :target_project, presence: true validates :target_branch, presence: true - validates :merge_user, presence: true, if: :merge_when_build_succeeds?, unless: :importing? + validates :merge_user, presence: true, if: :merge_when_pipeline_succeeds?, unless: :importing? validate :validate_branches, unless: [:allow_broken, :importing?, :closed_without_fork?] validate :validate_fork, unless: :closed_without_fork? @@ -436,7 +436,7 @@ class MergeRequest < ActiveRecord::Base true end - def can_cancel_merge_when_build_succeeds?(current_user) + def can_cancel_merge_when_pipeline_succeeds?(current_user) can_be_merged_by?(current_user) || self.author == current_user end @@ -644,10 +644,10 @@ class MergeRequest < ActiveRecord::Base message.join("\n\n") end - def reset_merge_when_build_succeeds - return unless merge_when_build_succeeds? + def reset_merge_when_pipeline_succeeds + return unless merge_when_pipeline_succeeds? - self.merge_when_build_succeeds = false + self.merge_when_pipeline_succeeds = false self.merge_user = nil if merge_params merge_params.delete('should_remove_source_branch') @@ -684,7 +684,10 @@ class MergeRequest < ActiveRecord::Base end def has_ci? - source_project.try(:ci_service) && commits.any? + has_ci_integration = source_project.try(:ci_service) + uses_gitlab_ci = all_pipelines.any? + + (has_ci_integration || uses_gitlab_ci) && commits.any? end def branch_missing? @@ -706,7 +709,7 @@ class MergeRequest < ActiveRecord::Base end def mergeable_ci_state? - return true unless project.only_allow_merge_if_build_succeeds? + return true unless project.only_allow_merge_if_pipeline_succeeds? !head_pipeline || head_pipeline.success? || head_pipeline.skipped? end diff --git a/app/models/note.rb b/app/models/note.rb index d6d5396afa5..4c97e4a986c 100644 --- a/app/models/note.rb +++ b/app/models/note.rb @@ -231,10 +231,6 @@ class Note < ActiveRecord::Base note =~ /\A#{Banzai::Filter::EmojiFilter.emoji_pattern}\s?\Z/ end - def award_emoji_name - note.match(Banzai::Filter::EmojiFilter.emoji_pattern)[1] - end - def to_ability_name for_personal_snippet? ? 'personal_snippet' : noteable_type.underscore end diff --git a/app/models/project.rb b/app/models/project.rb index e06fc30dc8a..1ac4a178a9b 100644 --- a/app/models/project.rb +++ b/app/models/project.rb @@ -19,7 +19,7 @@ class Project < ActiveRecord::Base extend Gitlab::ConfigHelper - class BoardLimitExceeded < StandardError; end + BoardLimitExceeded = Class.new(StandardError) NUMBER_OF_PERMITTED_BOARDS = 1 UNKNOWN_IMPORT_URL = 'http://unknown.git'.freeze @@ -113,6 +113,7 @@ class Project < ActiveRecord::Base has_one :gitlab_issue_tracker_service, dependent: :destroy, inverse_of: :project has_one :external_wiki_service, dependent: :destroy has_one :kubernetes_service, dependent: :destroy, inverse_of: :project + has_one :mock_ci_service, dependent: :destroy has_one :forked_project_link, dependent: :destroy, foreign_key: "forked_to_project_id" has_one :forked_from_project, through: :forked_project_link @@ -334,7 +335,7 @@ class Project < ActiveRecord::Base end def search_by_visibility(level) - where(visibility_level: Gitlab::VisibilityLevel.const_get(level.upcase)) + where(visibility_level: Gitlab::VisibilityLevel.string_options[level]) end def search_by_title(query) @@ -1003,7 +1004,7 @@ class Project < ActiveRecord::Base end def visibility_level_field - visibility_level + :visibility_level end def archive! diff --git a/app/models/project_wiki.rb b/app/models/project_wiki.rb index 9891f5edf41..539b31780b3 100644 --- a/app/models/project_wiki.rb +++ b/app/models/project_wiki.rb @@ -7,7 +7,7 @@ class ProjectWiki 'AsciiDoc' => :asciidoc }.freeze unless defined?(MARKUPS) - class CouldNotCreateWikiError < StandardError; end + CouldNotCreateWikiError = Class.new(StandardError) # Returns a string describing what went wrong after # an operation fails. diff --git a/app/models/repository.rb b/app/models/repository.rb index 0dbf246c3a4..d410d60dbad 100644 --- a/app/models/repository.rb +++ b/app/models/repository.rb @@ -995,6 +995,8 @@ class Repository end def with_repo_branch_commit(start_repository, start_branch_name) + return yield(nil) if start_repository.empty_repo? + branch_name_or_sha = if start_repository == self start_branch_name diff --git a/app/models/snippet.rb b/app/models/snippet.rb index 2665a7249a3..dbd564e5e7d 100644 --- a/app/models/snippet.rb +++ b/app/models/snippet.rb @@ -120,7 +120,7 @@ class Snippet < ActiveRecord::Base end def visibility_level_field - visibility_level + :visibility_level end def no_highlighting? diff --git a/app/models/user.rb b/app/models/user.rb index 6fb5ac4a4ef..8443594c055 100644 --- a/app/models/user.rb +++ b/app/models/user.rb @@ -346,7 +346,11 @@ class User < ActiveRecord::Base # Return (create if necessary) the ghost user. The ghost user # owns records previously belonging to deleted users. def ghost - User.find_by_ghost(true) || create_ghost_user + unique_internal(where(ghost: true), 'ghost', 'ghost%s@example.com') do |u| + u.bio = 'This is a "Ghost User", created to hold all issues authored by users that have since been deleted. This user cannot be removed.' + u.state = :blocked + u.name = 'Ghost User' + end end end @@ -1017,10 +1021,14 @@ class User < ActiveRecord::Base end end - def self.create_ghost_user - # Since we only want a single ghost user in an instance, we use an + def self.unique_internal(scope, username, email_pattern, &b) + scope.first || create_unique_internal(scope, username, email_pattern, &b) + end + + def self.create_unique_internal(scope, username, email_pattern, &creation_block) + # Since we only want a single one of these in an instance, we use an # exclusive lease to ensure than this block is never run concurrently. - lease_key = "ghost_user_creation" + lease_key = "user:unique_internal:#{username}" lease = Gitlab::ExclusiveLease.new(lease_key, timeout: 1.minute.to_i) until uuid = lease.try_obtain @@ -1029,25 +1037,25 @@ class User < ActiveRecord::Base sleep(1) end - # Recheck if a ghost user is already present. One might have been + # Recheck if the user is already present. One might have been # added between the time we last checked (first line of this method) # and the time we acquired the lock. - ghost_user = User.find_by_ghost(true) - return ghost_user if ghost_user.present? + existing_user = uncached { scope.first } + return existing_user if existing_user.present? uniquify = Uniquify.new - username = uniquify.string("ghost") { |s| User.find_by_username(s) } + username = uniquify.string(username) { |s| User.find_by_username(s) } - email = uniquify.string(-> (n) { "ghost#{n}@example.com" }) do |s| + email = uniquify.string(-> (n) { Kernel.sprintf(email_pattern, n) }) do |s| User.find_by_email(s) end - bio = 'This is a "Ghost User", created to hold all issues authored by users that have since been deleted. This user cannot be removed.' - - User.create( - username: username, password: Devise.friendly_token, bio: bio, - email: email, name: "Ghost User", state: :blocked, ghost: true + scope.create( + username: username, + password: Devise.friendly_token, + email: email, + &creation_block ) ensure Gitlab::ExclusiveLease.cancel(lease_key, uuid) diff --git a/app/policies/group_policy.rb b/app/policies/group_policy.rb index 0be6e113655..4cc21696eb6 100644 --- a/app/policies/group_policy.rb +++ b/app/policies/group_policy.rb @@ -33,8 +33,6 @@ class GroupPolicy < BasePolicy if globally_viewable && @subject.request_access_enabled && !member can! :request_access end - - additional_rules!(master) end def can_read_group? @@ -45,8 +43,4 @@ class GroupPolicy < BasePolicy GroupProjectsFinder.new(@subject).execute(@user).any? end - - def additional_rules!(master) - # This is meant to be overriden in EE - end end diff --git a/app/serializers/merge_request_entity.rb b/app/serializers/merge_request_entity.rb index 7445298c714..5f80ab397a9 100644 --- a/app/serializers/merge_request_entity.rb +++ b/app/serializers/merge_request_entity.rb @@ -6,7 +6,7 @@ class MergeRequestEntity < IssuableEntity expose :merge_params expose :merge_status expose :merge_user_id - expose :merge_when_build_succeeds + expose :merge_when_pipeline_succeeds expose :source_branch expose :source_project_id expose :target_branch diff --git a/app/serializers/pipeline_serializer.rb b/app/serializers/pipeline_serializer.rb index 2bc6cf3266e..ab2d3d5a3ec 100644 --- a/app/serializers/pipeline_serializer.rb +++ b/app/serializers/pipeline_serializer.rb @@ -1,5 +1,5 @@ class PipelineSerializer < BaseSerializer - class InvalidResourceError < StandardError; end + InvalidResourceError = Class.new(StandardError) entity PipelineEntity diff --git a/app/services/ci/image_for_build_service.rb b/app/services/ci/image_for_build_service.rb deleted file mode 100644 index 240ddabec36..00000000000 --- a/app/services/ci/image_for_build_service.rb +++ /dev/null @@ -1,25 +0,0 @@ -module Ci - class ImageForBuildService - def execute(project, opts) - ref = opts[:ref] - sha = opts[:sha] || ref_sha(project, ref) - pipelines = project.pipelines.where(sha: sha) - - image_name = image_for_status(pipelines.latest_status(ref)) - image_path = Rails.root.join('public/ci', image_name) - - OpenStruct.new(path: image_path, name: image_name) - end - - private - - def ref_sha(project, ref) - project.commit(ref).try(:sha) if ref - end - - def image_for_status(status) - status ||= 'unknown' - 'build-' + status + ".svg" - end - end -end diff --git a/app/services/commits/change_service.rb b/app/services/commits/change_service.rb index 25e22f14e60..8a9bcd2d053 100644 --- a/app/services/commits/change_service.rb +++ b/app/services/commits/change_service.rb @@ -1,7 +1,7 @@ module Commits class ChangeService < ::BaseService - class ValidationError < StandardError; end - class ChangeError < StandardError; end + ValidationError = Class.new(StandardError) + ChangeError = Class.new(StandardError) def execute @start_project = params[:start_project] || @project diff --git a/app/services/files/base_service.rb b/app/services/files/base_service.rb index 0a25f56d24c..c8a60422bf4 100644 --- a/app/services/files/base_service.rb +++ b/app/services/files/base_service.rb @@ -1,6 +1,6 @@ module Files class BaseService < ::BaseService - class ValidationError < StandardError; end + ValidationError = Class.new(StandardError) def execute @start_project = params[:start_project] || @project @@ -58,16 +58,12 @@ module Files raise_error("You are not allowed to push into this branch") end - unless project.empty_repo? - unless @start_project.repository.branch_exists?(@start_branch) - raise_error('You can only create or edit files when you are on a branch') - end + if !@start_project.empty_repo? && !@start_project.repository.branch_exists?(@start_branch) + raise ValidationError, 'You can only create or edit files when you are on a branch' + end - if different_branch? - if repository.branch_exists?(@target_branch) - raise_error('Branch with such name already exists. You need to switch to this branch in order to make changes') - end - end + if !project.empty_repo? && different_branch? && repository.branch_exists?(@branch_name) + raise ValidationError, "A branch called #{@branch_name} already exists. Switch to that branch in order to make changes" end end diff --git a/app/services/files/multi_service.rb b/app/services/files/multi_service.rb index 0609c6219e7..700f9f4f6f0 100644 --- a/app/services/files/multi_service.rb +++ b/app/services/files/multi_service.rb @@ -1,6 +1,6 @@ module Files class MultiService < Files::BaseService - class FileChangedError < StandardError; end + FileChangedError = Class.new(StandardError) ACTIONS = %w[create update delete move].freeze diff --git a/app/services/files/update_service.rb b/app/services/files/update_service.rb index 54e1aaf3f67..fbbab97632e 100644 --- a/app/services/files/update_service.rb +++ b/app/services/files/update_service.rb @@ -1,6 +1,6 @@ module Files class UpdateService < Files::BaseService - class FileChangedError < StandardError; end + FileChangedError = Class.new(StandardError) def commit repository.update_file(current_user, @file_path, @file_content, diff --git a/app/services/git_operation_service.rb b/app/services/git_operation_service.rb index 27bcc047601..ed6ea638235 100644 --- a/app/services/git_operation_service.rb +++ b/app/services/git_operation_service.rb @@ -56,12 +56,16 @@ class GitOperationService start_project: repository.project, &block) - check_with_branch_arguments!( - branch_name, start_branch_name, start_project) + start_repository = start_project.repository + start_branch_name = nil if start_repository.empty_repo? + + if start_branch_name && !start_repository.branch_exists?(start_branch_name) + raise ArgumentError, "Cannot find branch #{start_branch_name} in #{start_repository.path_with_namespace}" + end update_branch_with_hooks(branch_name) do repository.with_repo_branch_commit( - start_project.repository, + start_repository, start_branch_name || branch_name, &block) end @@ -149,31 +153,4 @@ class GitOperationService repository.raw_repository.autocrlf = :input end end - - def check_with_branch_arguments!( - branch_name, start_branch_name, start_project) - return if repository.branch_exists?(branch_name) - - if repository.project != start_project - unless start_branch_name - raise ArgumentError, - 'Should also pass :start_branch_name if' + - ' :start_project is different from current project' - end - - unless start_project.repository.branch_exists?(start_branch_name) - raise ArgumentError, - "Cannot find branch #{branch_name} nor" \ - " #{start_branch_name} from" \ - " #{start_project.path_with_namespace}" - end - elsif start_branch_name - unless repository.branch_exists?(start_branch_name) - raise ArgumentError, - "Cannot find branch #{branch_name} nor" \ - " #{start_branch_name} from" \ - " #{repository.project.path_with_namespace}" - end - end - end end diff --git a/app/services/issuable_base_service.rb b/app/services/issuable_base_service.rb index 9500faf2862..b618c3e038e 100644 --- a/app/services/issuable_base_service.rb +++ b/app/services/issuable_base_service.rb @@ -203,6 +203,7 @@ class IssuableBaseService < BaseService change_state(issuable) change_subscription(issuable) change_todo(issuable) + toggle_award(issuable) filter_params(issuable) old_labels = issuable.labels.to_a old_mentioned_users = issuable.mentioned_users.to_a @@ -263,6 +264,14 @@ class IssuableBaseService < BaseService end end + def toggle_award(issuable) + award = params.delete(:emoji_award) + if award + todo_service.new_award_emoji(issuable, current_user) + issuable.toggle_award_emoji(award, current_user) + end + end + def has_changes?(issuable, old_labels: []) valid_attrs = [:title, :description, :assignee_id, :milestone_id, :target_branch] diff --git a/app/services/issues/move_service.rb b/app/services/issues/move_service.rb index a2a5f57d069..711f4035c55 100644 --- a/app/services/issues/move_service.rb +++ b/app/services/issues/move_service.rb @@ -1,6 +1,6 @@ module Issues class MoveService < Issues::BaseService - class MoveError < StandardError; end + MoveError = Class.new(StandardError) def execute(issue, new_project) @old_issue = issue diff --git a/app/services/merge_requests/merge_when_pipeline_succeeds_service.rb b/app/services/merge_requests/merge_when_pipeline_succeeds_service.rb index 5081dd5a0c4..aed5287940e 100644 --- a/app/services/merge_requests/merge_when_pipeline_succeeds_service.rb +++ b/app/services/merge_requests/merge_when_pipeline_succeeds_service.rb @@ -1,18 +1,18 @@ module MergeRequests class MergeWhenPipelineSucceedsService < MergeRequests::BaseService - # Marks the passed `merge_request` to be merged when the build succeeds or + # Marks the passed `merge_request` to be merged when the pipeline succeeds or # updates the params for the automatic merge def execute(merge_request) merge_request.merge_params.merge!(params) # The service is also called when the merge params are updated. - already_approved = merge_request.merge_when_build_succeeds? + already_approved = merge_request.merge_when_pipeline_succeeds? unless already_approved - merge_request.merge_when_build_succeeds = true - merge_request.merge_user = @current_user + merge_request.merge_when_pipeline_succeeds = true + merge_request.merge_user = @current_user - SystemNoteService.merge_when_build_succeeds(merge_request, @project, @current_user, merge_request.diff_head_commit) + SystemNoteService.merge_when_pipeline_succeeds(merge_request, @project, @current_user, merge_request.diff_head_commit) end merge_request.save @@ -23,7 +23,7 @@ module MergeRequests return unless pipeline.success? pipeline_merge_requests(pipeline) do |merge_request| - next unless merge_request.merge_when_build_succeeds? + next unless merge_request.merge_when_pipeline_succeeds? unless merge_request.mergeable? todo_service.merge_request_became_unmergeable(merge_request) @@ -36,9 +36,9 @@ module MergeRequests # Cancels the automatic merge def cancel(merge_request) - if merge_request.merge_when_build_succeeds? && merge_request.open? - merge_request.reset_merge_when_build_succeeds - SystemNoteService.cancel_merge_when_build_succeeds(merge_request, @project, @current_user) + if merge_request.merge_when_pipeline_succeeds? && merge_request.open? + merge_request.reset_merge_when_pipeline_succeeds + SystemNoteService.cancel_merge_when_pipeline_succeeds(merge_request, @project, @current_user) success else diff --git a/app/services/merge_requests/refresh_service.rb b/app/services/merge_requests/refresh_service.rb index 581d18032e6..1131d6f4913 100644 --- a/app/services/merge_requests/refresh_service.rb +++ b/app/services/merge_requests/refresh_service.rb @@ -11,7 +11,7 @@ module MergeRequests # empty diff during a manual merge close_merge_requests reload_merge_requests - reset_merge_when_build_succeeds + reset_merge_when_pipeline_succeeds mark_pending_todos_done cache_merge_requests_closing_issues @@ -78,8 +78,8 @@ module MergeRequests end end - def reset_merge_when_build_succeeds - merge_requests_for_source_branch.each(&:reset_merge_when_build_succeeds) + def reset_merge_when_pipeline_succeeds + merge_requests_for_source_branch.each(&:reset_merge_when_pipeline_succeeds) end def mark_pending_todos_done diff --git a/app/services/merge_requests/resolve_service.rb b/app/services/merge_requests/resolve_service.rb index d22a1d3e0ad..82cd89d9a0b 100644 --- a/app/services/merge_requests/resolve_service.rb +++ b/app/services/merge_requests/resolve_service.rb @@ -1,7 +1,6 @@ module MergeRequests class ResolveService < MergeRequests::BaseService - class MissingFiles < Gitlab::Conflict::ResolutionError - end + MissingFiles = Class.new(Gitlab::Conflict::ResolutionError) attr_accessor :conflicts, :rugged, :merge_index, :merge_request diff --git a/app/services/notes/create_service.rb b/app/services/notes/create_service.rb index b4f8b33d564..61d66a26932 100644 --- a/app/services/notes/create_service.rb +++ b/app/services/notes/create_service.rb @@ -8,14 +8,6 @@ module Notes note.author = current_user note.system = false - if note.award_emoji? - noteable = note.noteable - if noteable.user_can_award?(current_user, note.award_emoji_name) - todo_service.new_award_emoji(noteable, current_user) - return noteable.create_award_emoji(note.award_emoji_name, current_user) - end - end - # We execute commands (extracted from `params[:note]`) on the noteable # **before** we save the note because if the note consists of commands # only, there is no need be create a note! @@ -48,7 +40,7 @@ module Notes note.errors.add(:commands_only, 'Commands applied') end - note.commands_changes = command_params.keys + note.commands_changes = command_params end note diff --git a/app/services/notification_service.rb b/app/services/notification_service.rb index 3734e3c4253..fbad85d310e 100644 --- a/app/services/notification_service.rb +++ b/app/services/notification_service.rb @@ -135,7 +135,7 @@ class NotificationService merge_request.target_project, current_user, :merged_merge_request_email, - skip_current_user: !merge_request.merge_when_build_succeeds? + skip_current_user: !merge_request.merge_when_pipeline_succeeds? ) end diff --git a/app/services/projects/create_service.rb b/app/services/projects/create_service.rb index 6dc3d8c2416..fbdaa455651 100644 --- a/app/services/projects/create_service.rb +++ b/app/services/projects/create_service.rb @@ -12,7 +12,7 @@ module Projects @project = Project.new(params) # Make sure that the user is allowed to use the specified visibility level - unless Gitlab::VisibilityLevel.allowed_for?(current_user, params[:visibility_level]) + unless Gitlab::VisibilityLevel.allowed_for?(current_user, @project.visibility_level) deny_visibility_level(@project) return @project end diff --git a/app/services/projects/destroy_service.rb b/app/services/projects/destroy_service.rb index 2e06826c311..a7142d5950e 100644 --- a/app/services/projects/destroy_service.rb +++ b/app/services/projects/destroy_service.rb @@ -2,7 +2,7 @@ module Projects class DestroyService < BaseService include Gitlab::ShellAdapter - class DestroyError < StandardError; end + DestroyError = Class.new(StandardError) DELETED_FLAG = '+deleted'.freeze diff --git a/app/services/projects/import_service.rb b/app/services/projects/import_service.rb index cd230528743..1c5a549feb9 100644 --- a/app/services/projects/import_service.rb +++ b/app/services/projects/import_service.rb @@ -2,7 +2,7 @@ module Projects class ImportService < BaseService include Gitlab::ShellAdapter - class Error < StandardError; end + Error = Class.new(StandardError) def execute add_repository_to_project unless project.gitlab_project_import? diff --git a/app/services/projects/transfer_service.rb b/app/services/projects/transfer_service.rb index 20dfbddc823..da6e6acd4a7 100644 --- a/app/services/projects/transfer_service.rb +++ b/app/services/projects/transfer_service.rb @@ -9,7 +9,7 @@ module Projects class TransferService < BaseService include Gitlab::ShellAdapter - class TransferError < StandardError; end + TransferError = Class.new(StandardError) def execute(new_namespace) if allowed_transfer?(current_user, project, new_namespace) diff --git a/app/services/slash_commands/interpret_service.rb b/app/services/slash_commands/interpret_service.rb index 3e0a85cf059..595653ea58a 100644 --- a/app/services/slash_commands/interpret_service.rb +++ b/app/services/slash_commands/interpret_service.rb @@ -59,7 +59,7 @@ module SlashCommands @updates[:state_event] = 'reopen' end - desc 'Merge (when build succeeds)' + desc 'Merge (when the pipeline succeeds)' condition do last_diff_sha = params && params[:merge_request_diff_head_sha] issuable.is_a?(MergeRequest) && @@ -255,6 +255,18 @@ module SlashCommands @updates[:wip_event] = issuable.work_in_progress? ? 'unwip' : 'wip' end + desc 'Toggle emoji reward' + params ':emoji:' + condition do + issuable.persisted? + end + command :award do |emoji| + name = award_emoji_name(emoji) + if name && issuable.user_can_award?(current_user, name) + @updates[:emoji_award] = name + end + end + desc 'Set time estimate' params '<1w 3d 2h 14m>' condition do @@ -329,5 +341,10 @@ module SlashCommands ext.references(type) end + + def award_emoji_name(emoji) + match = emoji.match(Banzai::Filter::EmojiFilter.emoji_pattern) + match[1] if match + end end end diff --git a/app/services/system_hooks_service.rb b/app/services/system_hooks_service.rb index 9b6dd013e3a..868fa7b3f21 100644 --- a/app/services/system_hooks_service.rb +++ b/app/services/system_hooks_service.rb @@ -84,7 +84,7 @@ class SystemHooksService project_id: model.id, owner_name: owner.name, owner_email: owner.respond_to?(:email) ? owner.email : "", - project_visibility: Project.visibility_levels.key(model.visibility_level_field).downcase + project_visibility: Project.visibility_levels.key(model.visibility_level_value).downcase } end @@ -101,7 +101,7 @@ class SystemHooksService user_email: model.user.email, user_id: model.user.id, access_level: model.human_access, - project_visibility: Project.visibility_levels.key(project.visibility_level_field).downcase + project_visibility: Project.visibility_levels.key(project.visibility_level_value).downcase } end diff --git a/app/services/system_note_service.rb b/app/services/system_note_service.rb index 55b548a12f9..db6a092d8fc 100644 --- a/app/services/system_note_service.rb +++ b/app/services/system_note_service.rb @@ -187,14 +187,14 @@ module SystemNoteService end # Called when 'merge when pipeline succeeds' is executed - def merge_when_build_succeeds(noteable, project, author, last_commit) + def merge_when_pipeline_succeeds(noteable, project, author, last_commit) body = "enabled an automatic merge when the pipeline for #{last_commit.to_reference(project)} succeeds" create_note(noteable: noteable, project: project, author: author, note: body) end # Called when 'merge when pipeline succeeds' is canceled - def cancel_merge_when_build_succeeds(noteable, project, author) + def cancel_merge_when_pipeline_succeeds(noteable, project, author) body = 'canceled the automatic merge' create_note(noteable: noteable, project: project, author: author, note: body) diff --git a/app/services/todo_service.rb b/app/services/todo_service.rb index ad86b4f9f42..8787a1c93a9 100644 --- a/app/services/todo_service.rb +++ b/app/services/todo_service.rb @@ -103,7 +103,7 @@ class TodoService # def merge_request_build_failed(merge_request) create_build_failed_todo(merge_request, merge_request.author) - create_build_failed_todo(merge_request, merge_request.merge_user) if merge_request.merge_when_build_succeeds? + create_build_failed_todo(merge_request, merge_request.merge_user) if merge_request.merge_when_pipeline_succeeds? end # When a new commit is pushed to a merge request we should: @@ -121,7 +121,7 @@ class TodoService # def merge_request_build_retried(merge_request) mark_pending_todos_as_done(merge_request, merge_request.author) - mark_pending_todos_as_done(merge_request, merge_request.merge_user) if merge_request.merge_when_build_succeeds? + mark_pending_todos_as_done(merge_request, merge_request.merge_user) if merge_request.merge_when_pipeline_succeeds? end # When a merge request could not be automatically merged due to its unmergeable state we should: @@ -129,7 +129,7 @@ class TodoService # * create a todo for a merge_user # def merge_request_became_unmergeable(merge_request) - create_unmergeable_todo(merge_request, merge_request.merge_user) if merge_request.merge_when_build_succeeds? + create_unmergeable_todo(merge_request, merge_request.merge_user) if merge_request.merge_when_pipeline_succeeds? end # When create a note we should: diff --git a/app/views/dashboard/_activities.html.haml b/app/views/dashboard/_activities.html.haml index 0dbb0ca6958..89d991abe54 100644 --- a/app/views/dashboard/_activities.html.haml +++ b/app/views/dashboard/_activities.html.haml @@ -2,10 +2,9 @@ = render "events/event_last_push", event: @last_push .nav-block - - if current_user - .controls - = link_to dashboard_projects_path(:atom, { private_token: current_user.private_token }), class: 'btn rss-btn has-tooltip', title: 'Subscribe' do - %i.fa.fa-rss + .controls + = link_to dashboard_projects_path(rss_url_options), class: 'btn rss-btn has-tooltip', title: 'Subscribe' do + %i.fa.fa-rss = render 'shared/event_filter' .content_list diff --git a/app/views/dashboard/_groups_head.html.haml b/app/views/dashboard/_groups_head.html.haml index 23c145ebbb4..c6d5937a3c3 100644 --- a/app/views/dashboard/_groups_head.html.haml +++ b/app/views/dashboard/_groups_head.html.haml @@ -6,7 +6,10 @@ = nav_link(page: explore_groups_path) do = link_to explore_groups_path, title: 'Explore groups' do Explore Groups - - if current_user.can_create_group? - .nav-controls + .nav-controls + = form_tag request.path, method: :get, class: 'group-filter-form', id: 'group-filter-form' do |f| + = search_field_tag :filter_groups, params[:filter_groups], placeholder: 'Filter by name...', class: 'group-filter-form-field form-control input-short js-groups-list-filter', spellcheck: false, id: 'group-filter-form-field', tabindex: "2" + = render 'shared/groups/dropdown' + - if current_user.can_create_group? = link_to new_group_path, class: "btn btn-new" do New Group diff --git a/app/views/dashboard/activity.html.haml b/app/views/dashboard/activity.html.haml index aa57df14c23..190ad4b40a5 100644 --- a/app/views/dashboard/activity.html.haml +++ b/app/views/dashboard/activity.html.haml @@ -1,6 +1,5 @@ = content_for :meta_tags do - - if current_user - = auto_discovery_link_tag(:atom, dashboard_projects_url(format: :atom, private_token: current_user.private_token), title: "All activity") + = auto_discovery_link_tag(:atom, dashboard_projects_url(rss_url_options), title: "All activity") - page_title "Activity" - header_title "Activity", activity_dashboard_path diff --git a/app/views/dashboard/groups/_groups.html.haml b/app/views/dashboard/groups/_groups.html.haml new file mode 100644 index 00000000000..6c3bf1a2b3b --- /dev/null +++ b/app/views/dashboard/groups/_groups.html.haml @@ -0,0 +1,6 @@ +.js-groups-list-holder + %ul.content-list + - @group_members.each do |group_member| + = render 'shared/groups/group', group: group_member.group, group_member: group_member + + = paginate @group_members, theme: 'gitlab' diff --git a/app/views/dashboard/groups/index.html.haml b/app/views/dashboard/groups/index.html.haml index 1a679c51774..73ab2c95ff9 100644 --- a/app/views/dashboard/groups/index.html.haml +++ b/app/views/dashboard/groups/index.html.haml @@ -5,9 +5,4 @@ - if @group_members.empty? = render 'empty_state' - else - %ul.content-list - - @group_members.each do |group_member| - - group = group_member.group - = render 'shared/groups/group', group: group, group_member: group_member - - = paginate @group_members, theme: 'gitlab' + = render 'groups' diff --git a/app/views/dashboard/issues.html.haml b/app/views/dashboard/issues.html.haml index 653052f7c54..9a4e423f896 100644 --- a/app/views/dashboard/issues.html.haml +++ b/app/views/dashboard/issues.html.haml @@ -1,17 +1,15 @@ - page_title "Issues" - header_title "Issues", issues_dashboard_path(assignee_id: current_user.id) = content_for :meta_tags do - - if current_user - = auto_discovery_link_tag(:atom, url_for(params.merge(format: :atom, private_token: current_user.private_token)), title: "#{current_user.name} issues") + = auto_discovery_link_tag(:atom, params.merge(rss_url_options), title: "#{current_user.name} issues") .top-area = render 'shared/issuable/nav', type: :issues .nav-controls - - if current_user - = link_to url_for(params.merge(format: :atom, private_token: current_user.private_token)), class: 'btn' do - = icon('rss') - %span.icon-label - Subscribe + = link_to params.merge(rss_url_options), class: 'btn' do + = icon('rss') + %span.icon-label + Subscribe = render 'shared/new_project_item_select', path: 'issues/new', label: "New Issue" = render 'shared/issuable/filter', type: :issues diff --git a/app/views/dashboard/projects/index.atom.builder b/app/views/dashboard/projects/index.atom.builder index fb5be63b472..13f7a8ddcec 100644 --- a/app/views/dashboard/projects/index.atom.builder +++ b/app/views/dashboard/projects/index.atom.builder @@ -1,7 +1,7 @@ xml.instruct! xml.feed "xmlns" => "http://www.w3.org/2005/Atom", "xmlns:media" => "http://search.yahoo.com/mrss/" do xml.title "Activity" - xml.link href: dashboard_projects_url(format: :atom, private_token: current_user.try(:private_token)), rel: "self", type: "application/atom+xml" + xml.link href: dashboard_projects_url(rss_url_options), rel: "self", type: "application/atom+xml" xml.link href: dashboard_projects_url, rel: "alternate", type: "text/html" xml.id dashboard_projects_url xml.updated @events[0].updated_at.xmlschema if @events[0] diff --git a/app/views/dashboard/projects/index.html.haml b/app/views/dashboard/projects/index.html.haml index b82b933c3ad..f0adbad8412 100644 --- a/app/views/dashboard/projects/index.html.haml +++ b/app/views/dashboard/projects/index.html.haml @@ -1,6 +1,5 @@ = content_for :meta_tags do - - if current_user - = auto_discovery_link_tag(:atom, dashboard_projects_url(format: :atom, private_token: current_user.private_token), title: "All activity") + = auto_discovery_link_tag(:atom, dashboard_projects_url(rss_url_options), title: "All activity") - page_title "Projects" - header_title "Projects", dashboard_projects_path diff --git a/app/views/explore/groups/_groups.html.haml b/app/views/explore/groups/_groups.html.haml new file mode 100644 index 00000000000..794c6d1d170 --- /dev/null +++ b/app/views/explore/groups/_groups.html.haml @@ -0,0 +1,6 @@ +.js-groups-list-holder + %ul.content-list + - @groups.each do |group| + = render 'shared/groups/group', group: group + + = paginate @groups, theme: 'gitlab' diff --git a/app/views/explore/groups/index.html.haml b/app/views/explore/groups/index.html.haml index 73cf6e87eb4..7f1bacc91cb 100644 --- a/app/views/explore/groups/index.html.haml +++ b/app/views/explore/groups/index.html.haml @@ -6,40 +6,10 @@ - else = render 'explore/head' -.row-content-block.clearfix - .pull-left - = form_tag explore_groups_path, method: :get, class: 'form-inline form-tiny' do |f| - = hidden_field_tag :sort, @sort - .form-group - = search_field_tag :search, params[:search], placeholder: "Filter by name", class: "form-control search-text-input", id: "groups_search", spellcheck: false - .form-group - = button_tag 'Search', class: "btn btn-default" - - .pull-right - .dropdown.inline - %button.dropdown-toggle{ type: 'button', 'data-toggle' => 'dropdown' } - %span.light - - if @sort.present? - = sort_options_hash[@sort] - - else - = sort_title_recently_created - = icon('chevron-down') - %ul.dropdown-menu.dropdown-menu-align-right - %li - = link_to explore_groups_path(sort: sort_value_recently_created) do - = sort_title_recently_created - = link_to explore_groups_path(sort: sort_value_oldest_created) do - = sort_title_oldest_created - = link_to explore_groups_path(sort: sort_value_recently_updated) do - = sort_title_recently_updated - = link_to explore_groups_path(sort: sort_value_oldest_updated) do - = sort_title_oldest_updated - -%ul.content-list - - @groups.each do |group| - = render 'shared/groups/group', group: group - - unless @groups.present? - .nothing-here-block No public groups +- if @groups.present? + = render 'groups' +- else + .nothing-here-block No public groups = paginate @groups, theme: "gitlab" diff --git a/app/views/groups/_activities.html.haml b/app/views/groups/_activities.html.haml index c442cf056c3..d7851c79990 100644 --- a/app/views/groups/_activities.html.haml +++ b/app/views/groups/_activities.html.haml @@ -2,10 +2,9 @@ = render "events/event_last_push", event: @last_push .nav-block - - if current_user - .controls - = link_to group_path(@group, format: :atom, private_token: current_user.private_token), class: 'btn rss-btn has-tooltip' , title: 'Subscribe' do - %i.fa.fa-rss + .controls + = link_to group_path(@group, rss_url_options), class: 'btn rss-btn has-tooltip' , title: 'Subscribe' do + %i.fa.fa-rss = render 'shared/event_filter' .content_list diff --git a/app/views/groups/activity.html.haml b/app/views/groups/activity.html.haml index d7375b23524..3969e56f937 100644 --- a/app/views/groups/activity.html.haml +++ b/app/views/groups/activity.html.haml @@ -1,6 +1,5 @@ = content_for :meta_tags do - - if current_user - = auto_discovery_link_tag(:atom, group_url(@group, format: :atom, private_token: current_user.private_token), title: "#{@group.name} activity") + = auto_discovery_link_tag(:atom, group_url(@group, rss_url_options), title: "#{@group.name} activity") - page_title "Activity" = render 'groups/head' diff --git a/app/views/groups/issues.html.haml b/app/views/groups/issues.html.haml index 939bddf3fe9..f4c17dc2d16 100644 --- a/app/views/groups/issues.html.haml +++ b/app/views/groups/issues.html.haml @@ -1,19 +1,17 @@ - page_title "Issues" = render "head_issues" = content_for :meta_tags do - - if current_user - = auto_discovery_link_tag(:atom, url_for(params.merge(format: :atom, private_token: current_user.private_token)), title: "#{@group.name} issues") + = auto_discovery_link_tag(:atom, params.merge(rss_url_options), title: "#{@group.name} issues") - if group_issues(@group).exists? .top-area = render 'shared/issuable/nav', type: :issues - - if current_user - .nav-controls - = link_to url_for(params.merge(format: :atom, private_token: current_user.private_token)), class: 'btn' do - = icon('rss') - %span.icon-label - Subscribe - = render 'shared/new_project_item_select', path: 'issues/new', label: "New Issue" + .nav-controls + = link_to params.merge(rss_url_options), class: 'btn' do + = icon('rss') + %span.icon-label + Subscribe + = render 'shared/new_project_item_select', path: 'issues/new', label: "New Issue" = render 'shared/issuable/filter', type: :issues diff --git a/app/views/groups/show.atom.builder b/app/views/groups/show.atom.builder index b68bf444d27..914091dfd15 100644 --- a/app/views/groups/show.atom.builder +++ b/app/views/groups/show.atom.builder @@ -1,7 +1,7 @@ xml.instruct! xml.feed "xmlns" => "http://www.w3.org/2005/Atom", "xmlns:media" => "http://search.yahoo.com/mrss/" do xml.title "#{@group.name} activity" - xml.link href: group_url(@group, format: :atom, private_token: current_user.try(:private_token)), rel: "self", type: "application/atom+xml" + xml.link href: group_url(@group, rss_url_options), rel: "self", type: "application/atom+xml" xml.link href: group_url(@group), rel: "alternate", type: "text/html" xml.id group_url(@group) xml.updated @events[0].updated_at.xmlschema if @events[0] diff --git a/app/views/groups/show.html.haml b/app/views/groups/show.html.haml index 3d7b469660a..8f0f2708194 100644 --- a/app/views/groups/show.html.haml +++ b/app/views/groups/show.html.haml @@ -1,8 +1,7 @@ - @no_container = true = content_for :meta_tags do - - if current_user - = auto_discovery_link_tag(:atom, group_url(@group, format: :atom, private_token: current_user.private_token), title: "#{@group.name} activity") + = auto_discovery_link_tag(:atom, group_url(@group, rss_url_options), title: "#{@group.name} activity") = render 'groups/head' = render 'groups/home_panel' diff --git a/app/views/help/_shortcuts.html.haml b/app/views/help/_shortcuts.html.haml index 705e20112fa..5d1369c2010 100644 --- a/app/views/help/_shortcuts.html.haml +++ b/app/views/help/_shortcuts.html.haml @@ -131,12 +131,6 @@ %tr %td.shortcut .key g - .key e - %td - Go to the project's activity feed - %tr - %td.shortcut - .key g .key f %td Go to files @@ -161,12 +155,6 @@ %tr %td.shortcut .key g - .key g - %td - Go to graphs - %tr - %td.shortcut - .key g .key i %td Go to issues diff --git a/app/views/layouts/header/_default.html.haml b/app/views/layouts/header/_default.html.haml index c28661c2351..555ec8ad079 100644 --- a/app/views/layouts/header/_default.html.haml +++ b/app/views/layouts/header/_default.html.haml @@ -55,8 +55,6 @@ = link_to "Profile", current_user, class: 'profile-link', aria: { label: "Profile" }, data: { user: current_user.username } %li = link_to "Settings", profile_path, aria: { label: "Settings" } - %li - = link_to "Help", help_path, aria: { label: "Help" } %li.divider %li = link_to "Sign out", destroy_user_session_path, method: :delete, class: "sign-out-link", aria: { label: "Sign out" } diff --git a/app/views/layouts/nav/_dashboard.html.haml b/app/views/layouts/nav/_dashboard.html.haml index 5d4178f03d7..15285ee32a3 100644 --- a/app/views/layouts/nav/_dashboard.html.haml +++ b/app/views/layouts/nav/_dashboard.html.haml @@ -24,16 +24,16 @@ = link_to assigned_issues_dashboard_path, title: 'Issues', class: 'dashboard-shortcuts-issues' do %span Issues - (#{number_with_delimiter(cached_assigned_issuables_count(current_user, :issues, :opened))}) + .badge= number_with_delimiter(cached_assigned_issuables_count(current_user, :issues, :opened)) = nav_link(path: 'dashboard#merge_requests') do = link_to assigned_mrs_dashboard_path, title: 'Merge Requests', class: 'dashboard-shortcuts-merge_requests' do %span Merge Requests - (#{number_with_delimiter(cached_assigned_issuables_count(current_user, :merge_requests, :opened))}) + .badge= number_with_delimiter(cached_assigned_issuables_count(current_user, :merge_requests, :opened)) = nav_link(controller: 'dashboard/snippets') do = link_to dashboard_snippets_path, title: 'Snippets' do %span Snippets %li.divider %li - = link_to "About GitLab CE", help_path, title: 'About GitLab CE', class: 'about-gitlab' + = link_to "Help", help_path, title: 'About GitLab CE', class: 'about-gitlab' diff --git a/app/views/layouts/nav/_project.html.haml b/app/views/layouts/nav/_project.html.haml index 7883823b21e..2335d467389 100644 --- a/app/views/layouts/nav/_project.html.haml +++ b/app/views/layouts/nav/_project.html.haml @@ -21,40 +21,23 @@ .fade-right = icon('angle-right') %ul.nav-links.scrolling-tabs - = nav_link(path: 'projects#show', html_options: {class: 'home'}) do + = nav_link(path: ['projects#show', 'projects#activity', 'cycle_analytics#show'], html_options: { class: 'home' }) do = link_to project_path(@project), title: 'Project', class: 'shortcuts-project' do %span Project - = nav_link(path: 'projects#activity') do - = link_to activity_project_path(@project), title: 'Activity', class: 'shortcuts-project-activity' do - %span - Activity - - if project_nav_tab? :files - = nav_link(controller: %w(tree blob blame edit_tree new_tree find_file commit commits compare repositories tags branches releases network)) do + = nav_link(controller: %w(tree blob blame edit_tree new_tree find_file commit commits compare repositories tags branches releases graphs network)) do = link_to project_files_path(@project), title: 'Repository', class: 'shortcuts-tree' do %span Repository - - if project_nav_tab? :pipelines - = nav_link(controller: [:pipelines, :builds, :environments, :cycle_analytics]) do - = link_to project_pipelines_path(@project), title: 'Pipelines', class: 'shortcuts-pipelines' do - %span - Pipelines - - if project_nav_tab? :container_registry = nav_link(controller: %w(container_registry)) do = link_to project_container_registry_path(@project), title: 'Container Registry', class: 'shortcuts-container-registry' do %span Registry - - if project_nav_tab? :graphs - = nav_link(controller: %w(graphs)) do - = link_to namespace_project_graph_path(@project.namespace, @project, current_ref), title: 'Graphs', class: 'shortcuts-graphs' do - %span - Graphs - - if project_nav_tab? :issues = nav_link(controller: [:issues, :labels, :milestones, :boards]) do = link_to namespace_project_issues_path(@project.namespace, @project), title: 'Issues', class: 'shortcuts-issues' do @@ -70,6 +53,12 @@ Merge Requests %span.badge.count.merge_counter= number_with_delimiter(MergeRequestsFinder.new(current_user, project_id: @project.id).execute.opened.count) + - if project_nav_tab? :pipelines + = nav_link(controller: [:pipelines, :builds, :environments]) do + = link_to project_pipelines_path(@project), title: 'Pipelines', class: 'shortcuts-pipelines' do + %span + Pipelines + - if project_nav_tab? :wiki = nav_link(controller: :wikis) do = link_to get_project_wiki_path(@project), title: 'Wiki', class: 'shortcuts-wiki' do diff --git a/app/views/projects/_activity.html.haml b/app/views/projects/_activity.html.haml index 4268337fd6d..fb990dd9592 100644 --- a/app/views/projects/_activity.html.haml +++ b/app/views/projects/_activity.html.haml @@ -1,11 +1,11 @@ - @no_container = true += render "projects/head" %div{ class: container_class } .nav-block.activity-filter-block - - if current_user - .controls - = link_to namespace_project_path(@project.namespace, @project, format: :atom, private_token: current_user.private_token), title: "Subscribe", class: 'btn rss-btn has-tooltip' do - = icon('rss') + .controls + = link_to namespace_project_path(@project.namespace, @project, rss_url_options), title: "Subscribe", class: 'btn rss-btn has-tooltip' do + = icon('rss') = render 'shared/event_filter' diff --git a/app/views/projects/_head.html.haml b/app/views/projects/_head.html.haml new file mode 100644 index 00000000000..db08b77c8e0 --- /dev/null +++ b/app/views/projects/_head.html.haml @@ -0,0 +1,20 @@ += content_for :sub_nav do + .scrolling-tabs-container.sub-nav-scroll + = render 'shared/nav_scroll' + .nav-links.sub-nav.scrolling-tabs + %ul{ class: container_class } + = nav_link(path: 'projects#show') do + = link_to project_path(@project), title: 'Project home', class: 'shortcuts-project' do + %span + Home + + = nav_link(path: 'projects#activity') do + = link_to activity_project_path(@project), title: 'Activity', class: 'shortcuts-project-activity' do + %span + Activity + + - if can?(current_user, :read_cycle_analytics, @project) + = nav_link(path: 'cycle_analytics#show') do + = link_to project_cycle_analytics_path(@project), title: 'Cycle Analytics', class: 'shortcuts-project-cycle-analytics' do + %span + Cycle Analytics diff --git a/app/views/projects/_merge_request_merge_settings.html.haml b/app/views/projects/_merge_request_merge_settings.html.haml index 27d25a6b682..188198c47d5 100644 --- a/app/views/projects/_merge_request_merge_settings.html.haml +++ b/app/views/projects/_merge_request_merge_settings.html.haml @@ -2,8 +2,8 @@ .form-group .checkbox.builds-feature - = form.label :only_allow_merge_if_build_succeeds do - = form.check_box :only_allow_merge_if_build_succeeds + = form.label :only_allow_merge_if_pipeline_succeeds do + = form.check_box :only_allow_merge_if_pipeline_succeeds %strong Only allow merge requests to be merged if the pipeline succeeds %br %span.descr diff --git a/app/views/projects/blob/_actions.html.haml b/app/views/projects/blob/_actions.html.haml index 7b9cfbbd067..c44d8fcd430 100644 --- a/app/views/projects/blob/_actions.html.haml +++ b/app/views/projects/blob/_actions.html.haml @@ -1,7 +1,8 @@ -.btn-group - = view_on_environment_button(@commit.sha, @path, @environment) if @environment +- if @environment + .btn-group< + = view_on_environment_button(@commit.sha, @path, @environment) -.btn-group.tree-btn-group +.btn-group{ role: "group" }< = link_to 'Raw', namespace_project_raw_path(@project.namespace, @project, @id), class: 'btn btn-sm', target: '_blank' -# only show normal/blame view links for text files @@ -18,7 +19,7 @@ tree_join(@commit.sha, @path)), class: 'btn btn-sm js-data-file-blob-permalink-url' - if current_user - .btn-group{ role: "group" } + .btn-group{ role: "group" }< - if blob_text_viewable?(@blob) = edit_blob_link = replace_blob_link diff --git a/app/views/projects/blob/_blob.html.haml b/app/views/projects/blob/_blob.html.haml index 19fa4c78501..41a7191302d 100644 --- a/app/views/projects/blob/_blob.html.haml +++ b/app/views/projects/blob/_blob.html.haml @@ -24,12 +24,13 @@ #blob-content-holder.blob-content-holder %article.file-holder - .js-file-title.file-title - = blob_icon blob.mode, blob.name - %strong - = blob.name - %small - = number_to_human_size(blob_size(blob)) + .js-file-title.file-title-flex-parent + .file-header-content + = blob_icon blob.mode, blob.name + %strong.file-title-name + = blob.name + %small + = number_to_human_size(blob_size(blob)) .file-actions.hidden-xs = render "actions" = render blob, blob: blob diff --git a/app/views/projects/blob/diff.html.haml b/app/views/projects/blob/diff.html.haml index c48d9dd982c..d1d448f0d4c 100644 --- a/app/views/projects/blob/diff.html.haml +++ b/app/views/projects/blob/diff.html.haml @@ -9,20 +9,20 @@ - line_old = line_new - @form.offset - line_content = capture do %td.line_content.noteable_line{ class: line_class }==#{' ' * @form.indent}#{line} - %tr.line_holder{ id: line_old, class: line_class } + %tr.line_holder.diff-expanded{ id: line_old, class: line_class } - case diff_view - when :inline %td.old_line.diff-line-num{ data: { linenumber: line_old } } - %a{ href: "##{line_old}", data: { linenumber: line_old } } + %a{ href: "#", data: { linenumber: line_old }, disabled: true } %td.new_line.diff-line-num{ data: { linenumber: line_new } } - %a{ href: "##{line_new}", data: { linenumber: line_new } } + %a{ href: "#", data: { linenumber: line_new }, disabled: true } = line_content - when :parallel %td.old_line.diff-line-num{ data: { linenumber: line_old } } - %a{ href: "##{line_old}", data: { linenumber: line_old } } + %a{ href: "##{line_old}", data: { linenumber: line_old }, disabled: true } = line_content %td.new_line.diff-line-num{ data: { linenumber: line_new } } - %a{ href: "##{line_new}", data: { linenumber: line_new } } + %a{ href: "##{line_new}", data: { linenumber: line_new }, disabled: true } = line_content - if @form.unfold? && @form.bottom? && @form.to < @blob.lines.size diff --git a/app/views/projects/boards/components/_board_list.html.haml b/app/views/projects/boards/components/_board_list.html.haml index f413a5e94c1..0993e880da9 100644 --- a/app/views/projects/boards/components/_board_list.html.haml +++ b/app/views/projects/boards/components/_board_list.html.haml @@ -2,28 +2,8 @@ .board-list-loading.text-center{ "v-if" => "loading" } = icon("spinner spin") - if can? current_user, :create_issue, @project - %board-new-issue{ "inline-template" => true, - ":list" => "list", + %board-new-issue{ ":list" => "list", "v-if" => 'list.type !== "done" && showIssueForm' } - .card.board-new-issue-form - %form{ "@submit" => "submit($event)" } - .flash-container{ "v-if" => "error" } - .flash-alert - An error occured. Please try again. - %label.label-light{ ":for" => 'list.id + "-title"' } - Title - %input.form-control{ type: "text", - "v-model" => "title", - "ref" => "input", - ":id" => 'list.id + "-title"' } - .clearfix.prepend-top-10 - %button.btn.btn-success.pull-left{ type: "submit", - ":disabled" => 'title === ""', - "ref" => "submit-button" } - Submit issue - %button.btn.btn-default.pull-right{ type: "button", - "@click" => "cancel" } - Cancel %ul.board-list{ "ref" => "list", "v-show" => "!loading", ":data-board" => "list.id", diff --git a/app/views/projects/commit/_pipelines_list.haml b/app/views/projects/commit/_pipelines_list.haml index 33917513f37..6792b3f7a83 100644 --- a/app/views/projects/commit/_pipelines_list.haml +++ b/app/views/projects/commit/_pipelines_list.haml @@ -2,27 +2,6 @@ #commit-pipeline-table-view{ data: { disable_initialization: disable_initialization, endpoint: endpoint, } } -.pipeline-svgs{ data: { "commit_icon_svg" => custom_icon("icon_commit"), - "icon_status_canceled" => custom_icon("icon_status_canceled"), - "icon_status_running" => custom_icon("icon_status_running"), - "icon_status_skipped" => custom_icon("icon_status_skipped"), - "icon_status_created" => custom_icon("icon_status_created"), - "icon_status_pending" => custom_icon("icon_status_pending"), - "icon_status_success" => custom_icon("icon_status_success"), - "icon_status_failed" => custom_icon("icon_status_failed"), - "icon_status_warning" => custom_icon("icon_status_warning"), - "stage_icon_status_canceled" => custom_icon("icon_status_canceled_borderless"), - "stage_icon_status_running" => custom_icon("icon_status_running_borderless"), - "stage_icon_status_skipped" => custom_icon("icon_status_skipped_borderless"), - "stage_icon_status_created" => custom_icon("icon_status_created_borderless"), - "stage_icon_status_pending" => custom_icon("icon_status_pending_borderless"), - "stage_icon_status_success" => custom_icon("icon_status_success_borderless"), - "stage_icon_status_failed" => custom_icon("icon_status_failed_borderless"), - "stage_icon_status_warning" => custom_icon("icon_status_warning_borderless"), - "icon_play" => custom_icon("icon_play"), - "icon_timer" => custom_icon("icon_timer"), - "icon_status_manual" => custom_icon("icon_status_manual"), -} } - content_for :page_specific_javascripts do = page_specific_javascript_bundle_tag('commit_pipelines') diff --git a/app/views/projects/commits/_head.html.haml b/app/views/projects/commits/_head.html.haml index 80763ce67ca..dd6797f10c0 100644 --- a/app/views/projects/commits/_head.html.haml +++ b/app/views/projects/commits/_head.html.haml @@ -11,14 +11,6 @@ = link_to namespace_project_commits_path(@project.namespace, @project, current_ref) do Commits - = nav_link(controller: %w(network)) do - = link_to namespace_project_network_path(@project.namespace, @project, current_ref) do - Network - - = nav_link(controller: :compare) do - = link_to namespace_project_compare_index_path(@project.namespace, @project, from: @repository.root_ref, to: current_ref) do - Compare - = nav_link(html_options: {class: branches_tab_class}) do = link_to namespace_project_branches_path(@project.namespace, @project) do Branches @@ -26,3 +18,19 @@ = nav_link(controller: [:tags, :releases]) do = link_to namespace_project_tags_path(@project.namespace, @project) do Tags + + = nav_link(path: 'graphs#show') do + = link_to namespace_project_graph_path(@project.namespace, @project, current_ref) do + Contributors + + = nav_link(controller: %w(network)) do + = link_to namespace_project_network_path(@project.namespace, @project, current_ref) do + Graph + + = nav_link(controller: :compare) do + = link_to namespace_project_compare_index_path(@project.namespace, @project, from: @repository.root_ref, to: current_ref) do + Compare + + = nav_link(path: 'graphs#charts') do + = link_to charts_namespace_project_graph_path(@project.namespace, @project, current_ref) do + Charts diff --git a/app/views/projects/commits/show.atom.builder b/app/views/projects/commits/show.atom.builder index 30bb7412073..2f0b6e39800 100644 --- a/app/views/projects/commits/show.atom.builder +++ b/app/views/projects/commits/show.atom.builder @@ -1,7 +1,7 @@ xml.instruct! xml.feed "xmlns" => "http://www.w3.org/2005/Atom", "xmlns:media" => "http://search.yahoo.com/mrss/" do xml.title "#{@project.name}:#{@ref} commits" - xml.link href: namespace_project_commits_url(@project.namespace, @project, @ref, format: :atom, private_token: current_user.try(:private_token)), rel: "self", type: "application/atom+xml" + xml.link href: namespace_project_commits_url(@project.namespace, @project, @ref, rss_url_options), rel: "self", type: "application/atom+xml" xml.link href: namespace_project_commits_url(@project.namespace, @project, @ref), rel: "alternate", type: "text/html" xml.id namespace_project_commits_url(@project.namespace, @project, @ref) xml.updated @commits.first.committed_date.xmlschema if @commits.any? diff --git a/app/views/projects/commits/show.html.haml b/app/views/projects/commits/show.html.haml index 08cb8a04413..38dbf2ac10b 100644 --- a/app/views/projects/commits/show.html.haml +++ b/app/views/projects/commits/show.html.haml @@ -2,8 +2,7 @@ - page_title "Commits", @ref = content_for :meta_tags do - - if current_user - = auto_discovery_link_tag(:atom, namespace_project_commits_url(@project.namespace, @project, @ref, format: :atom, private_token: current_user.private_token), title: "#{@project.name}:#{@ref} commits") + = auto_discovery_link_tag(:atom, namespace_project_commits_url(@project.namespace, @project, @ref, rss_url_options), title: "#{@project.name}:#{@ref} commits") = content_for :sub_nav do = render "head" @@ -27,10 +26,9 @@ .control = form_tag(namespace_project_commits_path(@project.namespace, @project, @id), method: :get, class: 'commits-search-form') do = search_field_tag :search, params[:search], { placeholder: 'Filter by commit message', id: 'commits-search', class: 'form-control search-text-input input-short', spellcheck: false } - - if current_user && current_user.private_token - .control - = link_to namespace_project_commits_path(@project.namespace, @project, @ref, { format: :atom, private_token: current_user.private_token }), title: "Commits Feed", class: 'btn' do - = icon("rss") + .control + = link_to namespace_project_commits_path(@project.namespace, @project, @ref, rss_url_options), title: "Commits Feed", class: 'btn' do + = icon("rss") %div{ id: dom_id(@project) } %ol#commits-list.list-unstyled.content_list diff --git a/app/views/projects/cycle_analytics/show.html.haml b/app/views/projects/cycle_analytics/show.html.haml index 5405ff16bea..be17f2c764e 100644 --- a/app/views/projects/cycle_analytics/show.html.haml +++ b/app/views/projects/cycle_analytics/show.html.haml @@ -3,7 +3,7 @@ - content_for :page_specific_javascripts do = page_specific_javascript_bundle_tag('cycle_analytics') -= render "projects/pipelines/head" += render "projects/head" #cycle-analytics{ class: container_class, "v-cloak" => "true", data: { request_path: project_cycle_analytics_path(@project) } } - if @cycle_analytics_no_data diff --git a/app/views/projects/environments/index.html.haml b/app/views/projects/environments/index.html.haml index 1f27d41ddd9..d6366b57957 100644 --- a/app/views/projects/environments/index.html.haml +++ b/app/views/projects/environments/index.html.haml @@ -13,7 +13,4 @@ "project-stopped-environments-path" => project_environments_path(@project, scope: :stopped), "new-environment-path" => new_namespace_project_environment_path(@project.namespace, @project), "help-page-path" => help_page_path("ci/environments"), - "css-class" => container_class, - "commit-icon-svg" => custom_icon("icon_commit"), - "terminal-icon-svg" => custom_icon("icon_terminal"), - "play-icon-svg" => custom_icon("icon_play") } } + "css-class" => container_class } } diff --git a/app/views/projects/graphs/_head.html.haml b/app/views/projects/graphs/_head.html.haml deleted file mode 100644 index 67018aaa2ac..00000000000 --- a/app/views/projects/graphs/_head.html.haml +++ /dev/null @@ -1,19 +0,0 @@ -= content_for :sub_nav do - .scrolling-tabs-container.sub-nav-scroll - = render 'shared/nav_scroll' - .nav-links.sub-nav.scrolling-tabs - %ul{ class: (container_class) } - - - content_for :page_specific_javascripts do - = page_specific_javascript_bundle_tag('lib_chart') - = page_specific_javascript_bundle_tag('graphs') - = nav_link(action: :show) do - = link_to 'Contributors', namespace_project_graph_path - = nav_link(action: :commits) do - = link_to 'Commits', commits_namespace_project_graph_path - = nav_link(action: :languages) do - = link_to 'Languages', languages_namespace_project_graph_path - - if @project.feature_available?(:builds, current_user) - = nav_link(action: :ci) do - = link_to ci_namespace_project_graph_path do - Continuous Integration diff --git a/app/views/projects/graphs/commits.html.haml b/app/views/projects/graphs/charts.html.haml index c8a82f7bca3..d3bce45e974 100644 --- a/app/views/projects/graphs/commits.html.haml +++ b/app/views/projects/graphs/charts.html.haml @@ -1,38 +1,58 @@ - @no_container = true -- page_title "Commits", "Graphs" -= render 'head' +- page_title "Charts" +- content_for :page_specific_javascripts do + = page_specific_javascript_bundle_tag('lib_chart') + = page_specific_javascript_bundle_tag('graphs') += render "projects/commits/head" -%div{ class: container_class } - .sub-header-block - .tree-ref-holder - = render 'shared/ref_switcher', destination: 'graphs_commits' - %ul.breadcrumb.repo-breadcrumb - = commits_breadcrumbs +.repo-charts{ class: container_class } + %h4.sub-header + Programming languages used in this repository - %p.lead - Commit statistics for - %strong= @ref - #{@commits_graph.start_date.strftime('%b %d')} - #{@commits_graph.end_date.strftime('%b %d')} + .row + .col-md-4 + %ul.bordered-list + - @languages.each do |language| + %li + %span{ style: "color: #{language[:color]}" } + = icon('circle') + + = language[:label] + .pull-right + = language[:value] + \% + .col-md-8 + %canvas#languages-chart{ height: 400 } + +.repo-charts{ class: container_class } + .sub-header-block.border-top + + .row.tree-ref-header + .col-md-6 + %h4 + Commit statistics for + %strong= @ref + #{@commits_graph.start_date.strftime('%b %d')} - #{@commits_graph.end_date.strftime('%b %d')} + + .col-md-6 + .tree-ref-container + .tree-ref-holder + = render 'shared/ref_switcher', destination: 'graphs_commits' + %ul.breadcrumb.repo-breadcrumb + = commits_breadcrumbs .row .col-md-6 - %ul + %ul.commit-stats %li - %p.lead - %strong= @commits_graph.commits.size - commits during - %strong= @commits_graph.duration - days + Total: + %strong #{@commits_graph.commits.size} commits %li - %p.lead - Average - %strong= @commits_graph.commit_per_day - commits per day + Average per day: + %strong #{@commits_graph.commit_per_day} commits %li - %p.lead - Contributed by - %strong= @commits_graph.authors - authors + Authors: + %strong= @commits_graph.authors .col-md-6 %div %p.slead @@ -40,15 +60,18 @@ %canvas#month-chart .row .col-md-6 - %div - %p.slead - Commits per day hour (UTC) - %canvas#hour-chart .col-md-6 %div %p.slead Commits per weekday %canvas#weekday-chart + .row + .col-md-6 + .col-md-6 + %div + %p.slead + Commits per day hour (UTC) + %canvas#hour-chart :javascript var responsiveChart = function (selector, data) { @@ -93,3 +116,12 @@ var monthData = chartData(#{@commits_per_month.keys.to_json}, #{@commits_per_month.values.to_json}); responsiveChart($('#month-chart'), monthData); + + var data = #{@languages.to_json}; + var ctx = $("#languages-chart").get(0).getContext("2d"); + var options = { + scaleOverlay: true, + responsive: true, + maintainAspectRatio: false + } + var myPieChart = new Chart(ctx).Pie(data, options); diff --git a/app/views/projects/graphs/ci.html.haml b/app/views/projects/graphs/ci.html.haml deleted file mode 100644 index 6be4273b6ab..00000000000 --- a/app/views/projects/graphs/ci.html.haml +++ /dev/null @@ -1,18 +0,0 @@ -- @no_container = true -- page_title "Continuous Integration", "Graphs" -= render 'head' - -%div{ class: container_class } - .sub-header-block - .oneline - A collection of graphs for Continuous Integration - - #charts.ci-charts - .row - .col-md-6 - = render 'projects/graphs/ci/overall' - .col-md-6 - = render 'projects/graphs/ci/build_times' - - %hr - = render 'projects/graphs/ci/builds' diff --git a/app/views/projects/graphs/languages.html.haml b/app/views/projects/graphs/languages.html.haml deleted file mode 100644 index fcfcae0be20..00000000000 --- a/app/views/projects/graphs/languages.html.haml +++ /dev/null @@ -1,33 +0,0 @@ -- @no_container = true -- page_title "Languages", "Graphs" -= render 'head' - -%div{ class: container_class } - .sub-header-block - .oneline - Programming languages used in this repository - - .row - .col-md-8 - %canvas#languages-chart{ height: 400 } - .col-md-4 - %ul.bordered-list - - @languages.each do |language| - %li - %span{ style: "color: #{language[:color]}" } - = icon('circle') - - = language[:label] - .pull-right - = language[:value] - \% - -:javascript - var data = #{@languages.to_json}; - var ctx = $("#languages-chart").get(0).getContext("2d"); - var options = { - scaleOverlay: true, - responsive: true, - maintainAspectRatio: false - } - var myPieChart = new Chart(ctx).Pie(data, options); diff --git a/app/views/projects/graphs/show.html.haml b/app/views/projects/graphs/show.html.haml index 5ebb939a109..d89dfe31e47 100644 --- a/app/views/projects/graphs/show.html.haml +++ b/app/views/projects/graphs/show.html.haml @@ -1,6 +1,9 @@ - @no_container = true -- page_title "Contributors", "Graphs" -= render 'head' +- page_title "Contributors" +- content_for :page_specific_javascripts do + = page_specific_javascript_bundle_tag('lib_chart') + = page_specific_javascript_bundle_tag('graphs') += render 'projects/commits/head' %div{ class: container_class } .sub-header-block diff --git a/app/views/projects/issues/_head.html.haml b/app/views/projects/issues/_head.html.haml index 4825820c4d9..7a188cb6445 100644 --- a/app/views/projects/issues/_head.html.haml +++ b/app/views/projects/issues/_head.html.haml @@ -7,7 +7,7 @@ = nav_link(controller: :issues) do = link_to namespace_project_issues_path(@project.namespace, @project), title: 'Issues' do %span - Issues + List = nav_link(controller: :boards) do = link_to namespace_project_boards_path(@project.namespace, @project), title: 'Board' do diff --git a/app/views/projects/issues/index.html.haml b/app/views/projects/issues/index.html.haml index 8ea1a3a45e1..7b7d7b1e00e 100644 --- a/app/views/projects/issues/index.html.haml +++ b/app/views/projects/issues/index.html.haml @@ -10,17 +10,15 @@ = page_specific_javascript_bundle_tag('filtered_search') = content_for :meta_tags do - - if current_user - = auto_discovery_link_tag(:atom, url_for(params.merge(format: :atom, private_token: current_user.private_token)), title: "#{@project.name} issues") + = auto_discovery_link_tag(:atom, params.merge(rss_url_options), title: "#{@project.name} issues") - if project_issues(@project).exists? %div{ class: (container_class) } .top-area = render 'shared/issuable/nav', type: :issues .nav-controls - - if current_user - = link_to url_for(params.merge(format: :atom, private_token: current_user.private_token)), class: 'btn append-right-10 has-tooltip', title: 'Subscribe' do - = icon('rss') + = link_to params.merge(rss_url_options), class: 'btn append-right-10 has-tooltip', title: 'Subscribe' do + = icon('rss') - if can? current_user, :create_issue, @project = link_to new_namespace_project_issue_path(@project.namespace, @project, diff --git a/app/views/projects/merge_requests/cancel_merge_when_build_succeeds.js.haml b/app/views/projects/merge_requests/cancel_merge_when_pipeline_succeeds.js.haml index eab5be488b5..eab5be488b5 100644 --- a/app/views/projects/merge_requests/cancel_merge_when_build_succeeds.js.haml +++ b/app/views/projects/merge_requests/cancel_merge_when_pipeline_succeeds.js.haml diff --git a/app/views/projects/merge_requests/index.html.haml b/app/views/projects/merge_requests/index.html.haml index 83e6c026ba7..8a96c8dacf6 100644 --- a/app/views/projects/merge_requests/index.html.haml +++ b/app/views/projects/merge_requests/index.html.haml @@ -2,7 +2,6 @@ - @bulk_edit = can?(current_user, :admin_merge_request, @project) - page_title "Merge Requests" -= render "projects/issues/head" = render 'projects/last_push' - content_for :page_specific_javascripts do diff --git a/app/views/projects/merge_requests/merge.js.haml b/app/views/projects/merge_requests/merge.js.haml index 84b6c9ebc5c..f0a23bec5e7 100644 --- a/app/views/projects/merge_requests/merge.js.haml +++ b/app/views/projects/merge_requests/merge.js.haml @@ -2,9 +2,9 @@ - when :success :plain merge_request_widget.mergeInProgress(#{params[:should_remove_source_branch] == '1'}); -- when :merge_when_build_succeeds +- when :merge_when_pipeline_succeeds :plain - $('.mr-widget-body').html("#{escape_javascript(render('projects/merge_requests/widget/open/merge_when_build_succeeds'))}"); + $('.mr-widget-body').html("#{escape_javascript(render('projects/merge_requests/widget/open/merge_when_pipeline_succeeds'))}"); - when :sha_mismatch :plain $('.mr-widget-body').html("#{escape_javascript(render('projects/merge_requests/widget/open/sha_mismatch'))}"); diff --git a/app/views/projects/merge_requests/widget/_open.html.haml b/app/views/projects/merge_requests/widget/_open.html.haml index c0d6ab669b8..f0ccc4e00fd 100644 --- a/app/views/projects/merge_requests/widget/_open.html.haml +++ b/app/views/projects/merge_requests/widget/_open.html.haml @@ -19,8 +19,8 @@ = render 'projects/merge_requests/widget/open/conflicts' - elsif @merge_request.work_in_progress? = render 'projects/merge_requests/widget/open/wip' - - elsif @merge_request.merge_when_build_succeeds? - = render 'projects/merge_requests/widget/open/merge_when_build_succeeds' + - elsif @merge_request.merge_when_pipeline_succeeds? + = render 'projects/merge_requests/widget/open/merge_when_pipeline_succeeds' - elsif !@merge_request.can_be_merged_by?(current_user) = render 'projects/merge_requests/widget/open/not_allowed' - elsif !@merge_request.mergeable_ci_state? && (@pipeline.failed? || @pipeline.canceled?) diff --git a/app/views/projects/merge_requests/widget/open/_accept.html.haml b/app/views/projects/merge_requests/widget/open/_accept.html.haml index b730ced4214..c94c7944c0b 100644 --- a/app/views/projects/merge_requests/widget/open/_accept.html.haml +++ b/app/views/projects/merge_requests/widget/open/_accept.html.haml @@ -1,8 +1,6 @@ - content_for :page_specific_javascripts do = page_specific_javascript_bundle_tag('merge_request_widget') -- status_class = @pipeline ? " ci-#{@pipeline.status}" : nil - = form_for [:merge, @project.namespace.becomes(Namespace), @project, @merge_request], remote: true, method: :post, html: { class: 'accept-mr-form js-quick-submit js-requires-input' } do |f| = hidden_field_tag :authenticity_token, form_authenticity_token = hidden_field_tag :sha, @merge_request.diff_head_sha @@ -11,24 +9,24 @@ .accept-action - if @pipeline && @pipeline.active? %span.btn-group - = button_tag class: "btn btn-create js-merge-button merge_when_build_succeeds" do + = button_tag class: "btn btn-info js-merge-when-pipeline-succeeds-button merge-when-pipeline-succeeds" do Merge When Pipeline Succeeds - - unless @project.only_allow_merge_if_build_succeeds? - = button_tag class: "btn btn-success dropdown-toggle", 'data-toggle' => 'dropdown' do + - unless @project.only_allow_merge_if_pipeline_succeeds? + = button_tag class: "btn btn-info dropdown-toggle", 'data-toggle' => 'dropdown' do = icon('caret-down') %span.sr-only Select Merge Moment %ul.js-merge-dropdown.dropdown-menu.dropdown-menu-right{ role: 'menu' } %li - = link_to "#", class: "merge_when_build_succeeds" do + = link_to "#", class: "merge_when_pipeline_succeeds" do = icon('check fw') Merge When Pipeline Succeeds %li - = link_to "#", class: "accept_merge_request" do + = link_to "#", class: "accept-merge-request" do = icon('warning fw') Merge Immediately - else - = f.button class: "btn btn-create btn-grouped js-merge-button accept_merge_request #{status_class}" do + = f.button class: "btn btn-grouped js-merge-button accept-merge-request" do Accept Merge Request - if @merge_request.force_remove_source_branch? .accept-control @@ -49,4 +47,4 @@ text: @merge_request.merge_commit_message, rows: 14, hint: true - = hidden_field_tag :merge_when_build_succeeds, "", autocomplete: "off" + = hidden_field_tag :merge_when_pipeline_succeeds, "", autocomplete: "off" diff --git a/app/views/projects/merge_requests/widget/open/_merge_when_build_succeeds.html.haml b/app/views/projects/merge_requests/widget/open/_merge_when_pipeline_succeeds.html.haml index cf7abf3756c..40a683d3fbd 100644 --- a/app/views/projects/merge_requests/widget/open/_merge_when_build_succeeds.html.haml +++ b/app/views/projects/merge_requests/widget/open/_merge_when_pipeline_succeeds.html.haml @@ -15,7 +15,7 @@ The source branch will not be removed. - remove_source_branch_button = !@merge_request.remove_source_branch? && @merge_request.can_remove_source_branch?(current_user) && @merge_request.merge_user == current_user - - user_can_cancel_automatic_merge = @merge_request.can_cancel_merge_when_build_succeeds?(current_user) + - user_can_cancel_automatic_merge = @merge_request.can_cancel_merge_when_pipeline_succeeds?(current_user) - if remove_source_branch_button || user_can_cancel_automatic_merge .clearfix.prepend-top-10 - if remove_source_branch_button @@ -24,5 +24,5 @@ Remove Source Branch When Merged - if user_can_cancel_automatic_merge - = link_to cancel_merge_when_build_succeeds_namespace_project_merge_request_path(@merge_request.target_project.namespace, @merge_request.target_project, @merge_request), remote: true, method: :post, class: "btn btn-grouped btn-sm" do + = link_to cancel_merge_when_pipeline_succeeds_namespace_project_merge_request_path(@merge_request.target_project.namespace, @merge_request.target_project, @merge_request), remote: true, method: :post, class: "btn btn-grouped btn-sm" do Cancel Automatic Merge diff --git a/app/views/projects/network/show.html.haml b/app/views/projects/network/show.html.haml index b88eef65cef..a4a24a217d3 100644 --- a/app/views/projects/network/show.html.haml +++ b/app/views/projects/network/show.html.haml @@ -1,4 +1,4 @@ -- page_title "Network", @ref +- page_title "Graph", @ref - content_for :page_specific_javascripts do = page_specific_javascript_tag('lib/raphael.js') = page_specific_javascript_bundle_tag('network') diff --git a/app/views/projects/pipelines/_head.html.haml b/app/views/projects/pipelines/_head.html.haml index 721a9b6beb5..a5acb7ac4a5 100644 --- a/app/views/projects/pipelines/_head.html.haml +++ b/app/views/projects/pipelines/_head.html.haml @@ -4,25 +4,25 @@ .nav-links.sub-nav.scrolling-tabs{ class: ('build' if local_assigns.fetch(:build_subnav, false)) } %ul{ class: (container_class) } - if project_nav_tab? :pipelines - = nav_link(controller: :pipelines) do + = nav_link(path: 'pipelines#index', controller: :pipelines) do = link_to project_pipelines_path(@project), title: 'Pipelines', class: 'shortcuts-pipelines' do %span Pipelines - if project_nav_tab? :builds - = nav_link(controller: %w(builds)) do + = nav_link(path: 'builds#index', controller: :builds) do = link_to project_builds_path(@project), title: 'Jobs', class: 'shortcuts-builds' do %span Jobs - if project_nav_tab? :environments - = nav_link(controller: %w(environments)) do + = nav_link(path: 'environments#index', controller: :environments) do = link_to project_environments_path(@project), title: 'Environments', class: 'shortcuts-environments' do %span Environments - - if can?(current_user, :read_cycle_analytics, @project) - = nav_link(controller: %w(cycle_analytics)) do - = link_to project_cycle_analytics_path(@project), title: 'Cycle Analytics' do + - if @project.feature_available?(:builds, current_user) && !@project.empty_repo? + = nav_link(path: 'pipelines#charts') do + = link_to charts_namespace_project_pipelines_path(@project.namespace, @project), title: 'Charts', class: 'shortcuts-pipelines-charts' do %span - Cycle Analytics + Charts diff --git a/app/views/projects/pipelines/charts.html.haml b/app/views/projects/pipelines/charts.html.haml new file mode 100644 index 00000000000..8ffdfa1a2cf --- /dev/null +++ b/app/views/projects/pipelines/charts.html.haml @@ -0,0 +1,21 @@ +- @no_container = true +- page_title "Charts", "Pipelines" +- content_for :page_specific_javascripts do + = page_specific_javascript_bundle_tag('lib_chart') + = page_specific_javascript_bundle_tag('graphs') += render 'head' + +%div{ class: container_class } + .sub-header-block + .oneline + A collection of graphs for Continuous Integration + + #charts.ci-charts + .row + .col-md-6 + = render 'projects/pipelines/charts/overall' + .col-md-6 + = render 'projects/pipelines/charts/build_times' + + %hr + = render 'projects/pipelines/charts/builds' diff --git a/app/views/projects/graphs/ci/_build_times.haml b/app/views/projects/pipelines/charts/_build_times.haml index bb0975a9535..bb0975a9535 100644 --- a/app/views/projects/graphs/ci/_build_times.haml +++ b/app/views/projects/pipelines/charts/_build_times.haml diff --git a/app/views/projects/graphs/ci/_builds.haml b/app/views/projects/pipelines/charts/_builds.haml index b6f453b9736..b6f453b9736 100644 --- a/app/views/projects/graphs/ci/_builds.haml +++ b/app/views/projects/pipelines/charts/_builds.haml diff --git a/app/views/projects/graphs/ci/_overall.haml b/app/views/projects/pipelines/charts/_overall.haml index edc4f7b079f..edc4f7b079f 100644 --- a/app/views/projects/graphs/ci/_overall.haml +++ b/app/views/projects/pipelines/charts/_overall.haml diff --git a/app/views/projects/pipelines/index.html.haml b/app/views/projects/pipelines/index.html.haml index 4147a617d95..acb61aa2490 100644 --- a/app/views/projects/pipelines/index.html.haml +++ b/app/views/projects/pipelines/index.html.haml @@ -48,28 +48,6 @@ = link_to ci_lint_path, class: 'btn btn-default' do %span CI Lint .content-list.pipelines{ data: { url: namespace_project_pipelines_path(@project.namespace, @project, format: :json) } } - .pipeline-svgs{ "data" => {"commit_icon_svg" => custom_icon("icon_commit"), - "icon_status_canceled" => custom_icon("icon_status_canceled"), - "icon_status_running" => custom_icon("icon_status_running"), - "icon_status_skipped" => custom_icon("icon_status_skipped"), - "icon_status_created" => custom_icon("icon_status_created"), - "icon_status_pending" => custom_icon("icon_status_pending"), - "icon_status_success" => custom_icon("icon_status_success"), - "icon_status_failed" => custom_icon("icon_status_failed"), - "icon_status_warning" => custom_icon("icon_status_warning"), - "stage_icon_status_canceled" => custom_icon("icon_status_canceled_borderless"), - "stage_icon_status_running" => custom_icon("icon_status_running_borderless"), - "stage_icon_status_skipped" => custom_icon("icon_status_skipped_borderless"), - "stage_icon_status_created" => custom_icon("icon_status_created_borderless"), - "stage_icon_status_pending" => custom_icon("icon_status_pending_borderless"), - "stage_icon_status_success" => custom_icon("icon_status_success_borderless"), - "stage_icon_status_failed" => custom_icon("icon_status_failed_borderless"), - "stage_icon_status_warning" => custom_icon("icon_status_warning_borderless"), - "icon_play" => custom_icon("icon_play"), - "icon_timer" => custom_icon("icon_timer"), - "icon_status_manual" => custom_icon("icon_status_manual"), - } } - - .vue-pipelines-index + .vue-pipelines-index = page_specific_javascript_bundle_tag('vue_pipelines') diff --git a/app/views/projects/show.atom.builder b/app/views/projects/show.atom.builder index 11310d5e1e1..5c7f2e315f0 100644 --- a/app/views/projects/show.atom.builder +++ b/app/views/projects/show.atom.builder @@ -1,7 +1,7 @@ xml.instruct! xml.feed "xmlns" => "http://www.w3.org/2005/Atom", "xmlns:media" => "http://search.yahoo.com/mrss/" do xml.title "#{@project.name} activity" - xml.link href: namespace_project_url(@project.namespace, @project, format: :atom, private_token: current_user.try(:private_token)), rel: "self", type: "application/atom+xml" + xml.link href: namespace_project_url(@project.namespace, @project, rss_url_options), rel: "self", type: "application/atom+xml" xml.link href: namespace_project_url(@project.namespace, @project), rel: "alternate", type: "text/html" xml.id namespace_project_url(@project.namespace, @project) xml.updated @events[0].updated_at.xmlschema if @events[0] diff --git a/app/views/projects/show.html.haml b/app/views/projects/show.html.haml index 80d4081dd7b..30d185e6556 100644 --- a/app/views/projects/show.html.haml +++ b/app/views/projects/show.html.haml @@ -1,15 +1,15 @@ - @no_container = true = content_for :meta_tags do - - if current_user - = auto_discovery_link_tag(:atom, namespace_project_path(@project.namespace, @project, format: :atom, private_token: current_user.private_token), title: "#{@project.name} activity") + = auto_discovery_link_tag(:atom, namespace_project_path(@project.namespace, @project, rss_url_options), title: "#{@project.name} activity") = content_for :flash_message do - if current_user && can?(current_user, :download_code, @project) = render 'shared/no_ssh' = render 'shared/no_password' -= render 'projects/last_push' += render "projects/head" += render "projects/last_push" = render "home_panel" - if current_user && can?(current_user, :download_code, @project) diff --git a/app/views/projects/tree/show.html.haml b/app/views/projects/tree/show.html.haml index 9864be3562a..a2a26039220 100644 --- a/app/views/projects/tree/show.html.haml +++ b/app/views/projects/tree/show.html.haml @@ -2,8 +2,7 @@ - page_title @path.presence || "Files", @ref = content_for :meta_tags do - - if current_user - = auto_discovery_link_tag(:atom, namespace_project_commits_url(@project.namespace, @project, @ref, format: :atom, private_token: current_user.private_token), title: "#{@project.name}:#{@ref} commits") + = auto_discovery_link_tag(:atom, namespace_project_commits_url(@project.namespace, @project, @ref, rss_url_options), title: "#{@project.name}:#{@ref} commits") = render "projects/commits/head" = render 'projects/last_push' diff --git a/app/views/shared/_logo.svg b/app/views/shared/_logo.svg index 9b67422da2c..10e6c49ae9f 100644 --- a/app/views/shared/_logo.svg +++ b/app/views/shared/_logo.svg @@ -1,4 +1,4 @@ -<svg width="36" height="36" class="tanuki-logo"> +<svg width="28" height="28" class="tanuki-logo" viewBox="0 0 36 36"> <path class="tanuki-shape tanuki-left-ear" fill="#e24329" d="M2 14l9.38 9v-9l-4-12.28c-.205-.632-1.176-.632-1.38 0z"/> <path class="tanuki-shape tanuki-right-ear" fill="#e24329" d="M34 14l-9.38 9v-9l4-12.28c.205-.632 1.176-.632 1.38 0z"/> <path class="tanuki-shape tanuki-nose" fill="#e24329" d="M18,34.38 3,14 33,14 Z"/> diff --git a/app/views/shared/groups/_dropdown.html.haml b/app/views/shared/groups/_dropdown.html.haml new file mode 100644 index 00000000000..37589b634fa --- /dev/null +++ b/app/views/shared/groups/_dropdown.html.haml @@ -0,0 +1,18 @@ +.dropdown.inline + %button.dropdown-toggle{ type: 'button', 'data-toggle' => 'dropdown' } + %span.light + - if @sort.present? + = sort_options_hash[@sort] + - else + = sort_title_recently_created + = icon('chevron-down') + %ul.dropdown-menu.dropdown-menu-align-right + %li + = link_to filter_groups_path(sort: sort_value_recently_created) do + = sort_title_recently_created + = link_to filter_groups_path(sort: sort_value_oldest_created) do + = sort_title_oldest_created + = link_to filter_groups_path(sort: sort_value_recently_updated) do + = sort_title_recently_updated + = link_to filter_groups_path(sort: sort_value_oldest_updated) do + = sort_title_oldest_updated diff --git a/app/views/shared/issuable/_sidebar.html.haml b/app/views/shared/issuable/_sidebar.html.haml index 6c730e16f67..37a0c63e514 100644 --- a/app/views/shared/issuable/_sidebar.html.haml +++ b/app/views/shared/issuable/_sidebar.html.haml @@ -9,16 +9,16 @@ - if current_user %span.issuable-header-text.hide-collapsed.pull-left Todo - %a.gutter-toggle.pull-right.js-sidebar-toggle{ role: "button", href: "#", aria: { label: "Toggle sidebar" } } + %a.gutter-toggle.pull-right.js-sidebar-toggle{ role: "button", href: "#", "aria-label" => "Toggle sidebar" } = sidebar_gutter_toggle_icon - if current_user - %button.btn.btn-default.issuable-header-btn.pull-right.js-issuable-todo{ type: "button", aria: { label: (todo.nil? ? "Add todo" : "Mark done") }, data: { todo_text: "Add todo", mark_text: "Mark done", issuable_id: issuable.id, issuable_type: issuable.class.name.underscore, url: namespace_project_todos_path(@project.namespace, @project), delete_path: (dashboard_todo_path(todo) if todo) } } + %button.btn.btn-default.issuable-header-btn.pull-right.js-issuable-todo{ type: "button", "aria-label" => (todo.nil? ? "Add todo" : "Mark done"), data: { todo_text: "Add todo", mark_text: "Mark done", issuable_id: issuable.id, issuable_type: issuable.class.name.underscore, url: namespace_project_todos_path(@project.namespace, @project), delete_path: (dashboard_todo_path(todo) if todo) } } %span.js-issuable-todo-text - if todo Mark done - else Add todo - = icon('spin spinner', class: 'hidden js-issuable-todo-loading') + = icon('spin spinner', class: 'hidden js-issuable-todo-loading', 'aria-hidden': 'true') = form_for [@project.namespace.becomes(Namespace), @project, issuable], remote: true, format: :json, html: { class: 'issuable-context-form inline-update js-issuable-update' } do |f| .block.assignee @@ -26,10 +26,10 @@ - if issuable.assignee = link_to_member(@project, issuable.assignee, size: 24) - else - = icon('user') + = icon('user', 'aria-hidden': 'true') .title.hide-collapsed Assignee - = icon('spinner spin', class: 'block-loading') + = icon('spinner spin', class: 'block-loading', 'aria-hidden': 'true') - if can_edit_issuable = link_to 'Edit', '#', class: 'edit-link pull-right' .value.hide-collapsed @@ -37,7 +37,7 @@ = link_to_member(@project, issuable.assignee, size: 32, extra_class: 'bold') do - if issuable.instance_of?(MergeRequest) && !issuable.can_be_merged_by?(issuable.assignee) %span.pull-right.cannot-be-merged{ data: { toggle: 'tooltip', placement: 'left' }, title: 'Not allowed to merge' } - = icon('exclamation-triangle') + = icon('exclamation-triangle', 'aria-hidden': 'true') %span.username = issuable.assignee.to_reference - else @@ -54,7 +54,7 @@ .block.milestone .sidebar-collapsed-icon - = icon('clock-o') + = icon('clock-o', 'aria-hidden': 'true') %span - if issuable.milestone %span.has-tooltip{ title: milestone_remaining_days(issuable.milestone), data: { container: 'body', html: 1, placement: 'left' } } @@ -63,7 +63,7 @@ None .title.hide-collapsed Milestone - = icon('spinner spin', class: 'block-loading') + = icon('spinner spin', class: 'block-loading', 'aria-hidden': 'true') - if can_edit_issuable = link_to 'Edit', '#', class: 'edit-link pull-right' .value.hide-collapsed @@ -77,20 +77,20 @@ = dropdown_tag('Milestone', options: { title: 'Assign milestone', toggle_class: 'js-milestone-select js-extra-options', filter: true, dropdown_class: 'dropdown-menu-selectable', placeholder: 'Search milestones', data: { show_no: true, field_name: "#{issuable.to_ability_name}[milestone_id]", project_id: @project.id, issuable_id: issuable.id, milestones: namespace_project_milestones_path(@project.namespace, @project, :json), ability_name: issuable.to_ability_name, issue_update: issuable_json_path(issuable), use_id: true }}) - if issuable.has_attribute?(:time_estimate) #issuable-time-tracker.block - %issuable-time-tracker{ ':time_estimate' => 'issuable.time_estimate', ':time_spent' => 'issuable.total_time_spent', ':human_time_estimate' => 'issuable.human_time_estimate', ':human_time_spent' => 'issuable.human_total_time_spent', 'stopwatch-svg' => custom_icon('icon_stopwatch'), 'docs-url' => help_page_path('workflow/time_tracking.md') } + %issuable-time-tracker{ ':time_estimate' => 'issuable.time_estimate', ':time_spent' => 'issuable.total_time_spent', ':human_time_estimate' => 'issuable.human_time_estimate', ':human_time_spent' => 'issuable.human_total_time_spent', 'docs-url' => help_page_path('workflow/time_tracking.md') } // Fallback while content is loading .title.hide-collapsed Time tracking - = icon('spinner spin') + = icon('spinner spin', 'aria-hidden': 'true') - if issuable.has_attribute?(:due_date) .block.due_date .sidebar-collapsed-icon - = icon('calendar') + = icon('calendar', 'aria-hidden': 'true') %span.js-due-date-sidebar-value = issuable.due_date.try(:to_s, :medium) || 'None' .title.hide-collapsed Due date - = icon('spinner spin', class: 'block-loading') + = icon('spinner spin', class: 'block-loading', 'aria-hidden': 'true') - if can?(current_user, :"admin_#{issuable.to_ability_name}", @project) = link_to 'Edit', '#', class: 'edit-link pull-right' .value.hide-collapsed @@ -110,7 +110,7 @@ .dropdown %button.dropdown-menu-toggle.js-due-date-select{ type: 'button', data: { toggle: 'dropdown', field_name: "#{issuable.to_ability_name}[due_date]", ability_name: issuable.to_ability_name, issue_update: issuable_json_path(issuable) } } %span.dropdown-toggle-text Due date - = icon('chevron-down') + = icon('chevron-down', 'aria-hidden': 'true') .dropdown-menu.dropdown-menu-due-date = dropdown_title('Due date') = dropdown_content do @@ -120,12 +120,12 @@ - selected_labels = issuable.labels .block.labels .sidebar-collapsed-icon.js-sidebar-labels-tooltip{ title: issuable_labels_tooltip(issuable.labels_array), data: { placement: "left", container: "body" } } - = icon('tags') + = icon('tags', 'aria-hidden': 'true') %span = selected_labels.size .title.hide-collapsed Labels - = icon('spinner spin', class: 'block-loading') + = icon('spinner spin', class: 'block-loading', 'aria-hidden': 'true') - if can_edit_issuable = link_to 'Edit', '#', class: 'edit-link pull-right' .value.issuable-show-labels.hide-collapsed{ class: ("has-labels" if selected_labels.any?) } @@ -141,7 +141,7 @@ %button.dropdown-menu-toggle.js-label-select.js-multiselect.js-label-sidebar-dropdown{ type: "button", data: {toggle: "dropdown", default_label: "Labels", field_name: "#{issuable.to_ability_name}[label_names][]", ability_name: issuable.to_ability_name, show_no: "true", show_any: "true", namespace_path: @project.try(:namespace).try(:path), project_path: @project.try(:path), issue_update: issuable_json_path(issuable), labels: (namespace_project_labels_path(@project.namespace, @project, :json) if @project) } } %span.dropdown-toggle-text{ class: ("is-default" if selected_labels.empty?) } = multi_label_name(selected_labels, "Labels") - = icon('chevron-down') + = icon('chevron-down', 'aria-hidden': 'true') .dropdown-menu.dropdown-select.dropdown-menu-paging.dropdown-menu-labels.dropdown-menu-selectable = render partial: "shared/issuable/label_page_default" - if can? current_user, :admin_label, @project and @project @@ -152,7 +152,7 @@ - subscribed = issuable.subscribed?(current_user, @project) .block.light.subscription{ data: { url: toggle_subscription_path(issuable) } } .sidebar-collapsed-icon - = icon('rss') + = icon('rss', 'aria-hidden': 'true') %span.issuable-header-text.hide-collapsed.pull-left Notifications - subscribtion_status = subscribed ? 'subscribed' : 'unsubscribed' diff --git a/app/views/users/show.html.haml b/app/views/users/show.html.haml index c130f3d9e17..af091f9ab88 100644 --- a/app/views/users/show.html.haml +++ b/app/views/users/show.html.haml @@ -24,13 +24,12 @@ = link_to new_abuse_report_path(user_id: @user.id, ref_url: request.referrer), class: 'btn btn-gray', title: 'Report abuse', data: { toggle: 'tooltip', placement: 'bottom', container: 'body' } do = icon('exclamation-circle') - - if current_user - = link_to user_path(@user, :atom, { private_token: current_user.private_token }), class: 'btn btn-gray' do - = icon('rss') - - if current_user.admin? - = link_to [:admin, @user], class: 'btn btn-gray', title: 'View user in admin area', - data: {toggle: 'tooltip', placement: 'bottom', container: 'body'} do - = icon('users') + = link_to user_path(@user, rss_url_options), class: 'btn btn-gray' do + = icon('rss') + - if current_user && current_user.admin? + = link_to [:admin, @user], class: 'btn btn-gray', title: 'View user in admin area', + data: {toggle: 'tooltip', placement: 'bottom', container: 'body'} do + = icon('users') .profile-header .avatar-holder diff --git a/changelogs/unreleased/22951-fix-todos-api-endpoint-error-for-commits.yml b/changelogs/unreleased/22951-fix-todos-api-endpoint-error-for-commits.yml new file mode 100644 index 00000000000..a53e7d77c16 --- /dev/null +++ b/changelogs/unreleased/22951-fix-todos-api-endpoint-error-for-commits.yml @@ -0,0 +1,4 @@ +--- +title: Add spec for todo with target_type Commit +merge_request: 9351 +author: George Andrinopoulos diff --git a/changelogs/unreleased/25437-just-emoji.yml b/changelogs/unreleased/25437-just-emoji.yml new file mode 100644 index 00000000000..ceb81a47f2d --- /dev/null +++ b/changelogs/unreleased/25437-just-emoji.yml @@ -0,0 +1,4 @@ +--- +title: Introduce /award slash command; Allow posting of just an emoji in comment +merge_request: 9382 +author: mhasbini diff --git a/changelogs/unreleased/26136-list-repository-tree-api-doc.yml b/changelogs/unreleased/26136-list-repository-tree-api-doc.yml new file mode 100644 index 00000000000..85d8bc6ca8a --- /dev/null +++ b/changelogs/unreleased/26136-list-repository-tree-api-doc.yml @@ -0,0 +1,4 @@ +--- +title: Make documentation of list repository tree API call more detailed +merge_request: 9532 +author: Marius Kleiner diff --git a/changelogs/unreleased/2629-show-public-rss-feeds-to-anonymous-users.yml b/changelogs/unreleased/2629-show-public-rss-feeds-to-anonymous-users.yml new file mode 100644 index 00000000000..6ee8e5724bc --- /dev/null +++ b/changelogs/unreleased/2629-show-public-rss-feeds-to-anonymous-users.yml @@ -0,0 +1,4 @@ +--- +title: Show public RSS feeds to anonymous users +merge_request: 9596 +author: Michael Kozono diff --git a/changelogs/unreleased/26348-cleanup-navigation-order.yml b/changelogs/unreleased/26348-cleanup-navigation-order.yml new file mode 100644 index 00000000000..d5324f9e025 --- /dev/null +++ b/changelogs/unreleased/26348-cleanup-navigation-order.yml @@ -0,0 +1,4 @@ +--- +title: Clean-up Project navigation order +merge_request: 9272 +author: diff --git a/changelogs/unreleased/27142-api-replace-destroy-with-stop-environment.yml b/changelogs/unreleased/27142-api-replace-destroy-with-stop-environment.yml new file mode 100644 index 00000000000..ee236310a71 --- /dev/null +++ b/changelogs/unreleased/27142-api-replace-destroy-with-stop-environment.yml @@ -0,0 +1,4 @@ +--- +title: API: Add environment stop action +merge_request: 8808 +author: diff --git a/changelogs/unreleased/27501-api-use-visibility-everywhere.yml b/changelogs/unreleased/27501-api-use-visibility-everywhere.yml new file mode 100644 index 00000000000..f1b70687878 --- /dev/null +++ b/changelogs/unreleased/27501-api-use-visibility-everywhere.yml @@ -0,0 +1,4 @@ +--- +title: "API: Use `visibility` as string parameter everywhere" +merge_request: 9337 +author: diff --git a/changelogs/unreleased/27532_api_changes.yml b/changelogs/unreleased/27532_api_changes.yml new file mode 100644 index 00000000000..778469d5a86 --- /dev/null +++ b/changelogs/unreleased/27532_api_changes.yml @@ -0,0 +1,4 @@ +--- +title: Use iids as filter parameter +merge_request: 9096 +author: diff --git a/changelogs/unreleased/27778-a11y-sidebar.yml b/changelogs/unreleased/27778-a11y-sidebar.yml new file mode 100644 index 00000000000..fb37d7fdb35 --- /dev/null +++ b/changelogs/unreleased/27778-a11y-sidebar.yml @@ -0,0 +1,5 @@ +--- +title: Improves a11y in sidebar by adding aria-hidden attributes in i tags and by + fixing two broken aria-hidden attributes +merge_request: +author: diff --git a/changelogs/unreleased/27978-improve-task-list-ux.yml b/changelogs/unreleased/27978-improve-task-list-ux.yml new file mode 100644 index 00000000000..a6bd99da82e --- /dev/null +++ b/changelogs/unreleased/27978-improve-task-list-ux.yml @@ -0,0 +1,4 @@ +--- +title: Only add a newline in the Markdown Editor if the current line is not empty +merge_request: 9455 +author: Jan Christophersen diff --git a/changelogs/unreleased/28010-mr-merge-button-default-to-danger.yml b/changelogs/unreleased/28010-mr-merge-button-default-to-danger.yml new file mode 100644 index 00000000000..06bb669ceac --- /dev/null +++ b/changelogs/unreleased/28010-mr-merge-button-default-to-danger.yml @@ -0,0 +1,4 @@ +--- +title: Default to subtle MR mege button until CI status is available +merge_request: +author: diff --git a/changelogs/unreleased/28257-issues-iids.yml b/changelogs/unreleased/28257-issues-iids.yml new file mode 100644 index 00000000000..0a85504a8de --- /dev/null +++ b/changelogs/unreleased/28257-issues-iids.yml @@ -0,0 +1,4 @@ +--- +title: API issues - support filtering by iids +merge_request: +author: diff --git a/changelogs/unreleased/28410-dropdown-styling.yml b/changelogs/unreleased/28410-dropdown-styling.yml new file mode 100644 index 00000000000..2a7af1dd6e8 --- /dev/null +++ b/changelogs/unreleased/28410-dropdown-styling.yml @@ -0,0 +1,4 @@ +--- +title: Add badges to global dropdown +merge_request: +author: diff --git a/changelogs/unreleased/28609-fix-redirect-to-home-page-url.yml b/changelogs/unreleased/28609-fix-redirect-to-home-page-url.yml new file mode 100644 index 00000000000..baf832d4495 --- /dev/null +++ b/changelogs/unreleased/28609-fix-redirect-to-home-page-url.yml @@ -0,0 +1,4 @@ +--- +title: Fix the redirect to custom home page URL +merge_request: 9518 +author: diff --git a/changelogs/unreleased/28805-download-archive-with-branch-like-feature-xxxx-add-extra-directory-level.yml b/changelogs/unreleased/28805-download-archive-with-branch-like-feature-xxxx-add-extra-directory-level.yml new file mode 100644 index 00000000000..38ff6b97b2b --- /dev/null +++ b/changelogs/unreleased/28805-download-archive-with-branch-like-feature-xxxx-add-extra-directory-level.yml @@ -0,0 +1,4 @@ +--- +title: Ensure archive download is only one directory deep +merge_request: 9616 +author: diff --git a/changelogs/unreleased/28807-search-for-milestone-by-title-in-rest-api.yml b/changelogs/unreleased/28807-search-for-milestone-by-title-in-rest-api.yml new file mode 100644 index 00000000000..0016253e32e --- /dev/null +++ b/changelogs/unreleased/28807-search-for-milestone-by-title-in-rest-api.yml @@ -0,0 +1,4 @@ +--- +title: Enable filtering milestones by search criteria in the API +merge_request: 9606 +author: diff --git a/changelogs/unreleased/28837-remove-help-duplicate.yml b/changelogs/unreleased/28837-remove-help-duplicate.yml new file mode 100644 index 00000000000..b1001245663 --- /dev/null +++ b/changelogs/unreleased/28837-remove-help-duplicate.yml @@ -0,0 +1,4 @@ +--- +title: Remove help link from right dropdown +merge_request: +author: diff --git a/changelogs/unreleased/28850-fix-broken-migration.yml b/changelogs/unreleased/28850-fix-broken-migration.yml new file mode 100644 index 00000000000..7f59a7708bc --- /dev/null +++ b/changelogs/unreleased/28850-fix-broken-migration.yml @@ -0,0 +1,4 @@ +--- +title: Fix broken migration when upgrading straight to 8.17.1 +merge_request: 9613 +author: diff --git a/changelogs/unreleased/28893-highlighted-diff-doesn-t-stay-highlighted-on-refresh.yml b/changelogs/unreleased/28893-highlighted-diff-doesn-t-stay-highlighted-on-refresh.yml new file mode 100644 index 00000000000..9ba33af010c --- /dev/null +++ b/changelogs/unreleased/28893-highlighted-diff-doesn-t-stay-highlighted-on-refresh.yml @@ -0,0 +1,4 @@ +--- +title: Highlight line number if specified on diff pages when page loads +merge_request: 9664 +author: diff --git a/changelogs/unreleased/28935-make-logo-smaller.yml b/changelogs/unreleased/28935-make-logo-smaller.yml new file mode 100644 index 00000000000..ef79fc7d212 --- /dev/null +++ b/changelogs/unreleased/28935-make-logo-smaller.yml @@ -0,0 +1,4 @@ +--- +title: Decrease tanuki logo size +merge_request: +author: diff --git a/changelogs/unreleased/diff-make-obvious-cant-comment.yml b/changelogs/unreleased/diff-make-obvious-cant-comment.yml new file mode 100644 index 00000000000..2cb95947939 --- /dev/null +++ b/changelogs/unreleased/diff-make-obvious-cant-comment.yml @@ -0,0 +1,4 @@ +--- +title: Visually show expanded diff lines cant have comments +merge_request: +author: diff --git a/changelogs/unreleased/dm-dont-copy-toolip.yml b/changelogs/unreleased/dm-dont-copy-toolip.yml new file mode 100644 index 00000000000..2b134da66ab --- /dev/null +++ b/changelogs/unreleased/dm-dont-copy-toolip.yml @@ -0,0 +1,4 @@ +--- +title: Don't copy tooltip when copying GFM +merge_request: +author: diff --git a/changelogs/unreleased/dm-fix-api-create-file-on-empty-repo.yml b/changelogs/unreleased/dm-fix-api-create-file-on-empty-repo.yml new file mode 100644 index 00000000000..7ac25c0a83e --- /dev/null +++ b/changelogs/unreleased/dm-fix-api-create-file-on-empty-repo.yml @@ -0,0 +1,4 @@ +--- +title: Fix creating a file in an empty repo using the API +merge_request: 9632 +author: diff --git a/changelogs/unreleased/dm-group-reference-full-name.yml b/changelogs/unreleased/dm-group-reference-full-name.yml new file mode 100644 index 00000000000..f445d955529 --- /dev/null +++ b/changelogs/unreleased/dm-group-reference-full-name.yml @@ -0,0 +1,4 @@ +--- +title: Use full group name in GFM group reference title +merge_request: +author: diff --git a/changelogs/unreleased/dz-dashboard-groups-search.yml b/changelogs/unreleased/dz-dashboard-groups-search.yml new file mode 100644 index 00000000000..c473cba774d --- /dev/null +++ b/changelogs/unreleased/dz-dashboard-groups-search.yml @@ -0,0 +1,4 @@ +--- +title: Add filter and sorting to dashboard groups page +merge_request: 9619 +author: diff --git a/changelogs/unreleased/expose-pagination-headers.yml b/changelogs/unreleased/expose-pagination-headers.yml new file mode 100644 index 00000000000..1b4cd43fa06 --- /dev/null +++ b/changelogs/unreleased/expose-pagination-headers.yml @@ -0,0 +1,4 @@ +--- +title: 'CORS: Whitelist pagination headers' +merge_request: 9651 +author: Robert Schilling diff --git a/changelogs/unreleased/fix-gb-remove-deprecated-ci-build-status-badge.yml b/changelogs/unreleased/fix-gb-remove-deprecated-ci-build-status-badge.yml new file mode 100644 index 00000000000..71ff768a190 --- /dev/null +++ b/changelogs/unreleased/fix-gb-remove-deprecated-ci-build-status-badge.yml @@ -0,0 +1,4 @@ +--- +title: Remove deprecated build status badge and related services +merge_request: 9620 +author: diff --git a/changelogs/unreleased/fix-gb-update-commit-status-api.yml b/changelogs/unreleased/fix-gb-update-commit-status-api.yml new file mode 100644 index 00000000000..aa4fcba4e89 --- /dev/null +++ b/changelogs/unreleased/fix-gb-update-commit-status-api.yml @@ -0,0 +1,4 @@ +--- +title: Fix updaing commit status when using optional attributes +merge_request: 9618 +author: diff --git a/changelogs/unreleased/gitaly-post-receive.yml b/changelogs/unreleased/gitaly-post-receive.yml new file mode 100644 index 00000000000..cf206e39084 --- /dev/null +++ b/changelogs/unreleased/gitaly-post-receive.yml @@ -0,0 +1,4 @@ +--- +title: Add internal API to notify Gitaly of post receive +merge_request: 8983 +author: diff --git a/changelogs/unreleased/issue-descrpiption-spinner-off.yml b/changelogs/unreleased/issue-descrpiption-spinner-off.yml new file mode 100644 index 00000000000..87104d09804 --- /dev/null +++ b/changelogs/unreleased/issue-descrpiption-spinner-off.yml @@ -0,0 +1,4 @@ +--- +title: Fixed loading spinner position on issue template toggle +merge_request: +author: diff --git a/changelogs/unreleased/long-file-name-overflow.yml b/changelogs/unreleased/long-file-name-overflow.yml new file mode 100644 index 00000000000..7ccf05491e1 --- /dev/null +++ b/changelogs/unreleased/long-file-name-overflow.yml @@ -0,0 +1,4 @@ +--- +title: Fixed long file names overflowing under action buttons +merge_request: +author: diff --git a/changelogs/unreleased/remove-new-relic-gem.yml b/changelogs/unreleased/remove-new-relic-gem.yml new file mode 100644 index 00000000000..b15ecd3e4e7 --- /dev/null +++ b/changelogs/unreleased/remove-new-relic-gem.yml @@ -0,0 +1,4 @@ +--- +title: Remove the newrelic gem +merge_request: 9622 +author: Robert Schilling diff --git a/changelogs/unreleased/use-v3-api-on-frontend.yml b/changelogs/unreleased/use-v3-api-on-frontend.yml new file mode 100644 index 00000000000..467ad3c8276 --- /dev/null +++ b/changelogs/unreleased/use-v3-api-on-frontend.yml @@ -0,0 +1,4 @@ +--- +title: Make projects dropdown only show projects you are a member of +merge_request: 9614 +author: diff --git a/changelogs/unreleased/user-calendar-border.yml b/changelogs/unreleased/user-calendar-border.yml new file mode 100644 index 00000000000..8ebcca83256 --- /dev/null +++ b/changelogs/unreleased/user-calendar-border.yml @@ -0,0 +1,4 @@ +--- +title: Removed top border from user contribution calendar +merge_request: +author: diff --git a/config/application.rb b/config/application.rb index 9088d3c432b..45f3b20d214 100644 --- a/config/application.rb +++ b/config/application.rb @@ -120,7 +120,7 @@ module Gitlab credentials: true, headers: :any, methods: :any, - expose: ['Link'] + expose: ['Link', 'X-Total', 'X-Total-Pages', 'X-Per-Page', 'X-Page', 'X-Next-Page', 'X-Prev-Page'] end # Cross-origin requests must not have the session cookie available @@ -130,7 +130,7 @@ module Gitlab credentials: false, headers: :any, methods: :any, - expose: ['Link'] + expose: ['Link', 'X-Total', 'X-Total-Pages', 'X-Per-Page', 'X-Page', 'X-Next-Page', 'X-Prev-Page'] end end diff --git a/config/initializers/8_gitaly.rb b/config/initializers/8_gitaly.rb new file mode 100644 index 00000000000..07dd30f0a24 --- /dev/null +++ b/config/initializers/8_gitaly.rb @@ -0,0 +1,2 @@ +# Make sure we initialize a Gitaly channel before Sidekiq starts multi-threaded execution. +Gitlab::GitalyClient.channel unless Rails.env.test? diff --git a/config/newrelic.yml b/config/newrelic.yml deleted file mode 100644 index 9ef922a38d9..00000000000 --- a/config/newrelic.yml +++ /dev/null @@ -1,16 +0,0 @@ -# New Relic configuration file -# -# This file is here to make sure the New Relic gem stays -# quiet by default. -# -# To enable and configure New Relic, please use -# environment variables, e.g. NEW_RELIC_ENABLED=true - -production: - enabled: false - -development: - enabled: false - -test: - enabled: false diff --git a/config/routes/ci.rb b/config/routes/ci.rb index 47a049d5b20..8d23aa8fbf6 100644 --- a/config/routes/ci.rb +++ b/config/routes/ci.rb @@ -5,11 +5,5 @@ namespace :ci do resource :lint, only: [:show, :create] - resources :projects, only: [:index, :show] do - member do - get :status, to: 'projects#badge' - end - end - - root to: 'projects#index' + root to: redirect('/') end diff --git a/config/routes/project.rb b/config/routes/project.rb index 84f123ff717..2703bf4ab46 100644 --- a/config/routes/project.rb +++ b/config/routes/project.rb @@ -58,6 +58,7 @@ constraints(ProjectUrlConstrainer.new) do resources :graphs, only: [:show], constraints: { id: Gitlab::Regex.git_reference_regex } do member do + get :charts get :commits get :ci get :languages @@ -100,7 +101,7 @@ constraints(ProjectUrlConstrainer.new) do get :merge_check post :merge get :merge_widget_refresh - post :cancel_merge_when_build_succeeds + post :cancel_merge_when_pipeline_succeeds get :ci_status get :ci_environments_status post :toggle_subscription @@ -140,6 +141,7 @@ constraints(ProjectUrlConstrainer.new) do resources :pipelines, only: [:index, :new, :create, :show] do collection do resource :pipelines_settings, path: 'settings', only: [:show, :update] + get :charts end member do diff --git a/config/webpack.config.js b/config/webpack.config.js index a71ec0c5f52..13273902b0e 100644 --- a/config/webpack.config.js +++ b/config/webpack.config.js @@ -28,6 +28,7 @@ var config = { environments_folder: './environments/folder/environments_folder_bundle.js', filtered_search: './filtered_search/filtered_search_bundle.js', graphs: './graphs/graphs_bundle.js', + groups_list: './groups_list.js', issuable: './issuable/issuable_bundle.js', merge_conflicts: './merge_conflicts/merge_conflicts_bundle.js', merge_request_widget: './merge_request_widget/ci_bundle.js', @@ -63,6 +64,10 @@ var config = { 'stage-2' ] } + }, + { + test: /\.svg$/, + use: 'raw-loader' } ] }, @@ -86,6 +91,7 @@ var config = { '~': path.join(ROOT_PATH, 'app/assets/javascripts'), 'bootstrap/js': 'bootstrap-sass/assets/javascripts/bootstrap', 'emoji-aliases$': path.join(ROOT_PATH, 'fixtures/emojis/aliases.json'), + 'icons': path.join(ROOT_PATH, 'app/views/shared/icons'), 'vendor': path.join(ROOT_PATH, 'vendor/assets/javascripts'), 'vue$': 'vue/dist/vue.common.js', } diff --git a/db/migrate/20170217132157_rename_merge_when_build_succeeds.rb b/db/migrate/20170217132157_rename_merge_when_build_succeeds.rb new file mode 100644 index 00000000000..9011526565d --- /dev/null +++ b/db/migrate/20170217132157_rename_merge_when_build_succeeds.rb @@ -0,0 +1,29 @@ +# See http://doc.gitlab.com/ce/development/migration_style_guide.html +# for more information on how to write migrations for GitLab. + +class RenameMergeWhenBuildSucceeds < ActiveRecord::Migration + include Gitlab::Database::MigrationHelpers + + # Set this constant to true if this migration requires downtime. + DOWNTIME = true + + # When a migration requires downtime you **must** uncomment the following + # constant and define a short and easy to understand explanation as to why the + # migration requires downtime. + DOWNTIME_REASON = 'Renaming the column merge_when_build_succeeds' + + # When using the methods "add_concurrent_index" or "add_column_with_default" + # you must disable the use of transactions as these methods can not run in an + # existing transaction. When using "add_concurrent_index" make sure that this + # method is the _only_ method called in the migration, any other changes + # should go in a separate migration. This ensures that upon failure _only_ the + # index creation fails and can be retried or reverted easily. + # + # To disable transactions uncomment the following line and remove these + # comments: + # disable_ddl_transaction! + + def change + rename_column :merge_requests, :merge_when_build_succeeds, :merge_when_pipeline_succeeds + end +end diff --git a/db/migrate/20170217151947_rename_only_allow_merge_if_build_succeeds.rb b/db/migrate/20170217151947_rename_only_allow_merge_if_build_succeeds.rb new file mode 100644 index 00000000000..b2b68ff72d1 --- /dev/null +++ b/db/migrate/20170217151947_rename_only_allow_merge_if_build_succeeds.rb @@ -0,0 +1,29 @@ +# See http://doc.gitlab.com/ce/development/migration_style_guide.html +# for more information on how to write migrations for GitLab. + +class RenameOnlyAllowMergeIfBuildSucceeds < ActiveRecord::Migration + include Gitlab::Database::MigrationHelpers + + # Set this constant to true if this migration requires downtime. + DOWNTIME = true + + # When a migration requires downtime you **must** uncomment the following + # constant and define a short and easy to understand explanation as to why the + # migration requires downtime. + DOWNTIME_REASON = 'Renaming the column only_allow_merge_if_build_succeeds' + + # When using the methods "add_concurrent_index" or "add_column_with_default" + # you must disable the use of transactions as these methods can not run in an + # existing transaction. When using "add_concurrent_index" make sure that this + # method is the _only_ method called in the migration, any other changes + # should go in a separate migration. This ensures that upon failure _only_ the + # index creation fails and can be retried or reverted easily. + # + # To disable transactions uncomment the following line and remove these + # comments: + # disable_ddl_transaction! + + def change + rename_column :projects, :only_allow_merge_if_build_succeeds, :only_allow_merge_if_pipeline_succeeds + end +end diff --git a/db/post_migrate/20170211073944_disable_invalid_service_templates.rb b/db/post_migrate/20170211073944_disable_invalid_service_templates.rb index 84954b1ef64..603efc43782 100644 --- a/db/post_migrate/20170211073944_disable_invalid_service_templates.rb +++ b/db/post_migrate/20170211073944_disable_invalid_service_templates.rb @@ -1,10 +1,8 @@ class DisableInvalidServiceTemplates < ActiveRecord::Migration DOWNTIME = false - unless defined?(Service) - class Service < ActiveRecord::Base - self.inheritance_column = nil - end + class Service < ActiveRecord::Base + self.inheritance_column = nil end def up diff --git a/db/schema.rb b/db/schema.rb index 1d94368f66e..cd5aa339269 100644 --- a/db/schema.rb +++ b/db/schema.rb @@ -11,7 +11,7 @@ # # It's strongly recommended that you check this file into your version control system. -ActiveRecord::Schema.define(version: 20170216141440) do +ActiveRecord::Schema.define(version: 20170217151947) do # These are extensions that must be enabled in order to support this database enable_extension "plpgsql" @@ -689,7 +689,7 @@ ActiveRecord::Schema.define(version: 20170216141440) do t.integer "updated_by_id" t.text "merge_error" t.text "merge_params" - t.boolean "merge_when_build_succeeds", default: false, null: false + t.boolean "merge_when_pipeline_succeeds", default: false, null: false t.integer "merge_user_id" t.string "merge_commit_sha" t.datetime "deleted_at" @@ -972,7 +972,7 @@ ActiveRecord::Schema.define(version: 20170216141440) do t.boolean "last_repository_check_failed" t.datetime "last_repository_check_at" t.boolean "container_registry_enabled" - t.boolean "only_allow_merge_if_build_succeeds", default: false, null: false + t.boolean "only_allow_merge_if_pipeline_succeeds", default: false, null: false t.boolean "has_external_issue_tracker" t.string "repository_storage", default: "default", null: false t.boolean "request_access_enabled", default: false, null: false diff --git a/doc/api/README.md b/doc/api/README.md index 1c3b2ad0fbc..3399e2bb5f6 100644 --- a/doc/api/README.md +++ b/doc/api/README.md @@ -89,7 +89,7 @@ You can use an OAuth 2 token to authenticate with the API by passing it either i Example of using the OAuth2 token in the header: ```shell -curl --header "Authorization: Bearer OAUTH-TOKEN" https://gitlab.example.com/api/v3/projects +curl --header "Authorization: Bearer OAUTH-TOKEN" https://gitlab.example.com/api/v4/projects ``` Read more about [GitLab as an OAuth2 client](oauth2.md). @@ -127,13 +127,13 @@ is defined in [`lib/api.rb`][lib-api-url]. Example of a valid API request: ```shell -GET https://gitlab.example.com/api/v3/projects?private_token=9koXpg98eAheJpvBs5tK +GET https://gitlab.example.com/api/v4/projects?private_token=9koXpg98eAheJpvBs5tK ``` Example of a valid API request using cURL and authentication via header: ```shell -curl --header "PRIVATE-TOKEN: 9koXpg98eAheJpvBs5tK" "https://gitlab.example.com/api/v3/projects" +curl --header "PRIVATE-TOKEN: 9koXpg98eAheJpvBs5tK" "https://gitlab.example.com/api/v4/projects" ``` The API uses JSON to serialize data. You don't need to specify `.json` at the @@ -159,7 +159,7 @@ The following table shows the possible return codes for API requests. | Return values | Description | | ------------- | ----------- | | `200 OK` | The `GET`, `PUT` or `DELETE` request was successful, the resource(s) itself is returned as JSON. | -| `204 OK` | The server has successfully fulfilled the request and that there is no additional content to send in the response payload body. | +| `204 No Content` | The server has successfully fulfilled the request and that there is no additional content to send in the response payload body. | | `201 Created` | The `POST` request was successful and the resource is returned as JSON. | | `304 Not Modified` | Indicates that the resource has not been modified since the last request. | | `400 Bad Request` | A required attribute of the API request is missing, e.g., the title of an issue is not given. | @@ -207,7 +207,7 @@ GET /projects?private_token=9koXpg98eAheJpvBs5tK&sudo=username ``` ```shell -curl --header "PRIVATE-TOKEN: 9koXpg98eAheJpvBs5tK" --header "SUDO: username" "https://gitlab.example.com/api/v3/projects" +curl --header "PRIVATE-TOKEN: 9koXpg98eAheJpvBs5tK" --header "SUDO: username" "https://gitlab.example.com/api/v4/projects" ``` Example of a valid API call and a request using cURL with sudo request, @@ -218,7 +218,7 @@ GET /projects?private_token=9koXpg98eAheJpvBs5tK&sudo=23 ``` ```shell -curl --header "PRIVATE-TOKEN: 9koXpg98eAheJpvBs5tK" --header "SUDO: 23" "https://gitlab.example.com/api/v3/projects" +curl --header "PRIVATE-TOKEN: 9koXpg98eAheJpvBs5tK" --header "SUDO: 23" "https://gitlab.example.com/api/v4/projects" ``` ## Pagination @@ -234,7 +234,7 @@ resources you can pass the following parameters: In the example below, we list 50 [namespaces](namespaces.md) per page. ```bash -curl --request PUT --header "PRIVATE-TOKEN: 9koXpg98eAheJpvBs5tK" "https://gitlab.example.com/api/v3/namespaces?per_page=50 +curl --request PUT --header "PRIVATE-TOKEN: 9koXpg98eAheJpvBs5tK" "https://gitlab.example.com/api/v4/namespaces?per_page=50 ``` ### Pagination Link header @@ -248,7 +248,7 @@ and we request the second page (`page=2`) of [comments](notes.md) of the issue with ID `8` which belongs to the project with ID `8`: ```bash -curl --head --header "PRIVATE-TOKEN: 9koXpg98eAheJpvBs5tK" https://gitlab.example.com/api/v3/projects/8/issues/8/notes?per_page=3&page=2 +curl --head --header "PRIVATE-TOKEN: 9koXpg98eAheJpvBs5tK" https://gitlab.example.com/api/v4/projects/8/issues/8/notes?per_page=3&page=2 ``` The response will then be: @@ -259,7 +259,7 @@ Cache-Control: no-cache Content-Length: 1103 Content-Type: application/json Date: Mon, 18 Jan 2016 09:43:18 GMT -Link: <https://gitlab.example.com/api/v3/projects/8/issues/8/notes?page=1&per_page=3>; rel="prev", <https://gitlab.example.com/api/v3/projects/8/issues/8/notes?page=3&per_page=3>; rel="next", <https://gitlab.example.com/api/v3/projects/8/issues/8/notes?page=1&per_page=3>; rel="first", <https://gitlab.example.com/api/v3/projects/8/issues/8/notes?page=3&per_page=3>; rel="last" +Link: <https://gitlab.example.com/api/v4/projects/8/issues/8/notes?page=1&per_page=3>; rel="prev", <https://gitlab.example.com/api/v4/projects/8/issues/8/notes?page=3&per_page=3>; rel="next", <https://gitlab.example.com/api/v4/projects/8/issues/8/notes?page=1&per_page=3>; rel="first", <https://gitlab.example.com/api/v4/projects/8/issues/8/notes?page=3&per_page=3>; rel="last" Status: 200 OK Vary: Origin X-Next-Page: 3 diff --git a/doc/api/access_requests.md b/doc/api/access_requests.md index dee3e384080..96b8d654c58 100644 --- a/doc/api/access_requests.md +++ b/doc/api/access_requests.md @@ -28,8 +28,8 @@ GET /projects/:id/access_requests | `id` | integer/string | yes | The group/project ID or path | ```bash -curl --header "PRIVATE-TOKEN: 9koXpg98eAheJpvBs5tK" https://gitlab.example.com/api/v3/groups/:id/access_requests -curl --header "PRIVATE-TOKEN: 9koXpg98eAheJpvBs5tK" https://gitlab.example.com/api/v3/projects/:id/access_requests +curl --header "PRIVATE-TOKEN: 9koXpg98eAheJpvBs5tK" https://gitlab.example.com/api/v4/groups/:id/access_requests +curl --header "PRIVATE-TOKEN: 9koXpg98eAheJpvBs5tK" https://gitlab.example.com/api/v4/projects/:id/access_requests ``` Example response: @@ -69,8 +69,8 @@ POST /projects/:id/access_requests | `id` | integer/string | yes | The group/project ID or path | ```bash -curl --request POST --header "PRIVATE-TOKEN: 9koXpg98eAheJpvBs5tK" https://gitlab.example.com/api/v3/groups/:id/access_requests -curl --request POST --header "PRIVATE-TOKEN: 9koXpg98eAheJpvBs5tK" https://gitlab.example.com/api/v3/projects/:id/access_requests +curl --request POST --header "PRIVATE-TOKEN: 9koXpg98eAheJpvBs5tK" https://gitlab.example.com/api/v4/groups/:id/access_requests +curl --request POST --header "PRIVATE-TOKEN: 9koXpg98eAheJpvBs5tK" https://gitlab.example.com/api/v4/projects/:id/access_requests ``` Example response: @@ -102,8 +102,8 @@ PUT /projects/:id/access_requests/:user_id/approve | `access_level` | integer | no | A valid access level (defaults: `30`, developer access level) | ```bash -curl --request PUT --header "PRIVATE-TOKEN: 9koXpg98eAheJpvBs5tK" https://gitlab.example.com/api/v3/groups/:id/access_requests/:user_id/approve?access_level=20 -curl --request PUT --header "PRIVATE-TOKEN: 9koXpg98eAheJpvBs5tK" https://gitlab.example.com/api/v3/projects/:id/access_requests/:user_id/approve?access_level=20 +curl --request PUT --header "PRIVATE-TOKEN: 9koXpg98eAheJpvBs5tK" https://gitlab.example.com/api/v4/groups/:id/access_requests/:user_id/approve?access_level=20 +curl --request PUT --header "PRIVATE-TOKEN: 9koXpg98eAheJpvBs5tK" https://gitlab.example.com/api/v4/projects/:id/access_requests/:user_id/approve?access_level=20 ``` Example response: @@ -134,6 +134,6 @@ DELETE /projects/:id/access_requests/:user_id | `user_id` | integer | yes | The user ID of the access requester | ```bash -curl --request DELETE --header "PRIVATE-TOKEN: 9koXpg98eAheJpvBs5tK" https://gitlab.example.com/api/v3/groups/:id/access_requests/:user_id -curl --request DELETE --header "PRIVATE-TOKEN: 9koXpg98eAheJpvBs5tK" https://gitlab.example.com/api/v3/projects/:id/access_requests/:user_id +curl --request DELETE --header "PRIVATE-TOKEN: 9koXpg98eAheJpvBs5tK" https://gitlab.example.com/api/v4/groups/:id/access_requests/:user_id +curl --request DELETE --header "PRIVATE-TOKEN: 9koXpg98eAheJpvBs5tK" https://gitlab.example.com/api/v4/projects/:id/access_requests/:user_id ``` diff --git a/doc/api/award_emoji.md b/doc/api/award_emoji.md index c6fd8c5fa53..3470f8ce497 100644 --- a/doc/api/award_emoji.md +++ b/doc/api/award_emoji.md @@ -27,7 +27,7 @@ Parameters: | `awardable_id` | integer | yes | The ID of an awardable | ```bash -curl --header "PRIVATE-TOKEN: 9koXpg98eAheJpvBs5tK" http://gitlab.example.com/api/v3/projects/1/issues/80/award_emoji +curl --header "PRIVATE-TOKEN: 9koXpg98eAheJpvBs5tK" https://gitlab.example.com/api/v4/projects/1/issues/80/award_emoji ``` Example Response: @@ -88,7 +88,7 @@ Parameters: | `award_id` | integer | yes | The ID of the award emoji | ```bash -curl --header "PRIVATE-TOKEN: 9koXpg98eAheJpvBs5tK" http://gitlab.example.com/api/v3/projects/1/issues/80/award_emoji/1 +curl --header "PRIVATE-TOKEN: 9koXpg98eAheJpvBs5tK" https://gitlab.example.com/api/v4/projects/1/issues/80/award_emoji/1 ``` Example Response: @@ -131,7 +131,7 @@ Parameters: | `name` | string | yes | The name of the emoji, without colons | ```bash -curl --request POST --header "PRIVATE-TOKEN: 9koXpg98eAheJpvBs5tK" http://gitlab.example.com/api/v3/projects/1/issues/80/award_emoji?name=blowfish +curl --request POST --header "PRIVATE-TOKEN: 9koXpg98eAheJpvBs5tK" https://gitlab.example.com/api/v4/projects/1/issues/80/award_emoji?name=blowfish ``` Example Response: @@ -175,7 +175,7 @@ Parameters: | `award_id` | integer | yes | The ID of a award_emoji | ```bash -curl --request DELETE --header "PRIVATE-TOKEN: 9koXpg98eAheJpvBs5tK" http://gitlab.example.com/api/v3/projects/1/issues/80/award_emoji/344 +curl --request DELETE --header "PRIVATE-TOKEN: 9koXpg98eAheJpvBs5tK" https://gitlab.example.com/api/v4/projects/1/issues/80/award_emoji/344 ``` ## Award Emoji on Notes @@ -201,7 +201,7 @@ Parameters: ```bash -curl --header "PRIVATE-TOKEN: 9koXpg98eAheJpvBs5tK" http://gitlab.example.com/api/v3/projects/1/issues/80/notes/1/award_emoji +curl --header "PRIVATE-TOKEN: 9koXpg98eAheJpvBs5tK" https://gitlab.example.com/api/v4/projects/1/issues/80/notes/1/award_emoji ``` Example Response: @@ -243,7 +243,7 @@ Parameters: | `award_id` | integer | yes | The ID of the award emoji | ```bash -curl --header "PRIVATE-TOKEN: 9koXpg98eAheJpvBs5tK" http://gitlab.example.com/api/v3/projects/1/issues/80/notes/1/award_emoji/2 +curl --header "PRIVATE-TOKEN: 9koXpg98eAheJpvBs5tK" https://gitlab.example.com/api/v4/projects/1/issues/80/notes/1/award_emoji/2 ``` Example Response: @@ -283,7 +283,7 @@ Parameters: | `name` | string | yes | The name of the emoji, without colons | ```bash -curl --request POST --header "PRIVATE-TOKEN: 9koXpg98eAheJpvBs5tK" http://gitlab.example.com/api/v3/projects/1/issues/80/notes/1/award_emoji?name=rocket +curl --request POST --header "PRIVATE-TOKEN: 9koXpg98eAheJpvBs5tK" https://gitlab.example.com/api/v4/projects/1/issues/80/notes/1/award_emoji?name=rocket ``` Example Response: @@ -326,7 +326,7 @@ Parameters: | `award_id` | integer | yes | The ID of a award_emoji | ```bash -curl --request DELETE --header "PRIVATE-TOKEN: 9koXpg98eAheJpvBs5tK" http://gitlab.example.com/api/v3/projects/1/issues/80/award_emoji/345 +curl --request DELETE --header "PRIVATE-TOKEN: 9koXpg98eAheJpvBs5tK" https://gitlab.example.com/api/v4/projects/1/issues/80/award_emoji/345 ``` [ce-4575]: https://gitlab.com/gitlab-org/gitlab-ce/merge_requests/4575 diff --git a/doc/api/boards.md b/doc/api/boards.md index f80b98f960b..a74e82335eb 100644 --- a/doc/api/boards.md +++ b/doc/api/boards.md @@ -18,7 +18,7 @@ GET /projects/:id/boards | `id` | integer | yes | The ID of a project | ```bash -curl --header "PRIVATE-TOKEN: 9koXpg98eAheJpvBs5tK" https://gitlab.example.com/api/v3/projects/:id/boards +curl --header "PRIVATE-TOKEN: 9koXpg98eAheJpvBs5tK" https://gitlab.example.com/api/v4/projects/:id/boards ``` Example response: @@ -75,7 +75,7 @@ GET /projects/:id/boards/:board_id/lists | `board_id` | integer | yes | The ID of a board | ```bash -curl --header "PRIVATE-TOKEN: 9koXpg98eAheJpvBs5tK" https://gitlab.example.com/api/v3/projects/5/boards/1/lists +curl --header "PRIVATE-TOKEN: 9koXpg98eAheJpvBs5tK" https://gitlab.example.com/api/v4/projects/5/boards/1/lists ``` Example response: @@ -127,7 +127,7 @@ GET /projects/:id/boards/:board_id/lists/:list_id | `list_id`| integer | yes | The ID of a board's list | ```bash -curl --header "PRIVATE-TOKEN: 9koXpg98eAheJpvBs5tK" https://gitlab.example.com/api/v3/projects/5/boards/1/lists/1 +curl --header "PRIVATE-TOKEN: 9koXpg98eAheJpvBs5tK" https://gitlab.example.com/api/v4/projects/5/boards/1/lists/1 ``` Example response: @@ -159,7 +159,7 @@ POST /projects/:id/boards/:board_id/lists | `label_id` | integer | yes | The ID of a label | ```bash -curl --request POST --header "PRIVATE-TOKEN: 9koXpg98eAheJpvBs5tK" https://gitlab.example.com/api/v3/projects/5/boards/1/lists?label_id=5 +curl --request POST --header "PRIVATE-TOKEN: 9koXpg98eAheJpvBs5tK" https://gitlab.example.com/api/v4/projects/5/boards/1/lists?label_id=5 ``` Example response: @@ -192,7 +192,7 @@ PUT /projects/:id/boards/:board_id/lists/:list_id | `position` | integer | yes | The position of the list | ```bash -curl --request PUT --header "PRIVATE-TOKEN: 9koXpg98eAheJpvBs5tK" https://gitlab.example.com/api/v3/projects/5/boards/1/lists/1?position=2 +curl --request PUT --header "PRIVATE-TOKEN: 9koXpg98eAheJpvBs5tK" https://gitlab.example.com/api/v4/projects/5/boards/1/lists/1?position=2 ``` Example response: @@ -224,5 +224,5 @@ DELETE /projects/:id/boards/:board_id/lists/:list_id | `list_id` | integer | yes | The ID of a board's list | ```bash -curl --request DELETE --header "PRIVATE-TOKEN: 9koXpg98eAheJpvBs5tK" https://gitlab.example.com/api/v3/projects/5/boards/1/lists/1 +curl --request DELETE --header "PRIVATE-TOKEN: 9koXpg98eAheJpvBs5tK" https://gitlab.example.com/api/v4/projects/5/boards/1/lists/1 ``` diff --git a/doc/api/branches.md b/doc/api/branches.md index f29a8518945..83705106160 100644 --- a/doc/api/branches.md +++ b/doc/api/branches.md @@ -13,7 +13,7 @@ GET /projects/:id/repository/branches | `id` | integer | yes | The ID of a project | ```bash -curl --header "PRIVATE-TOKEN: 9koXpg98eAheJpvBs5tK" https://gitlab.example.com/api/v3/projects/5/repository/branches +curl --header "PRIVATE-TOKEN: 9koXpg98eAheJpvBs5tK" https://gitlab.example.com/api/v4/projects/5/repository/branches ``` Example response: @@ -60,7 +60,7 @@ GET /projects/:id/repository/branches/:branch | `branch` | string | yes | The name of the branch | ```bash -curl --header "PRIVATE-TOKEN: 9koXpg98eAheJpvBs5tK" https://gitlab.example.com/api/v3/projects/5/repository/branches/master +curl --header "PRIVATE-TOKEN: 9koXpg98eAheJpvBs5tK" https://gitlab.example.com/api/v4/projects/5/repository/branches/master ``` Example response: @@ -101,7 +101,7 @@ PUT /projects/:id/repository/branches/:branch/protect ``` ```bash -curl --request PUT --header "PRIVATE-TOKEN: 9koXpg98eAheJpvBs5tK" https://gitlab.example.com/api/v3/projects/5/repository/branches/master/protect?developers_can_push=true&developers_can_merge=true +curl --request PUT --header "PRIVATE-TOKEN: 9koXpg98eAheJpvBs5tK" https://gitlab.example.com/api/v4/projects/5/repository/branches/master/protect?developers_can_push=true&developers_can_merge=true ``` | Attribute | Type | Required | Description | @@ -149,7 +149,7 @@ PUT /projects/:id/repository/branches/:branch/unprotect ``` ```bash -curl --request PUT --header "PRIVATE-TOKEN: 9koXpg98eAheJpvBs5tK" https://gitlab.example.com/api/v3/projects/5/repository/branches/master/unprotect +curl --request PUT --header "PRIVATE-TOKEN: 9koXpg98eAheJpvBs5tK" https://gitlab.example.com/api/v4/projects/5/repository/branches/master/unprotect ``` | Attribute | Type | Required | Description | @@ -197,7 +197,7 @@ POST /projects/:id/repository/branches | `ref` | string | yes | The branch name or commit SHA to create branch from | ```bash -curl --request POST --header "PRIVATE-TOKEN: 9koXpg98eAheJpvBs5tK" "https://gitlab.example.com/api/v3/projects/5/repository/branches?branch=newbranch&ref=master" +curl --request POST --header "PRIVATE-TOKEN: 9koXpg98eAheJpvBs5tK" "https://gitlab.example.com/api/v4/projects/5/repository/branches?branch=newbranch&ref=master" ``` Example response: @@ -241,7 +241,7 @@ DELETE /projects/:id/repository/branches/:branch In case of an error, an explaining message is provided. ```bash -curl --request DELETE --header "PRIVATE-TOKEN: 9koXpg98eAheJpvBs5tK" "https://gitlab.example.com/api/v3/projects/5/repository/branches/newbranch" +curl --request DELETE --header "PRIVATE-TOKEN: 9koXpg98eAheJpvBs5tK" "https://gitlab.example.com/api/v4/projects/5/repository/branches/newbranch" ``` ## Delete merged branches @@ -258,5 +258,5 @@ DELETE /projects/:id/repository/merged_branches ```bash -curl --request DELETE --header "PRIVATE-TOKEN: 9koXpg98eAheJpvBs5tK" "https://gitlab.example.com/api/v3/projects/5/repository/merged_branches" +curl --request DELETE --header "PRIVATE-TOKEN: 9koXpg98eAheJpvBs5tK" "https://gitlab.example.com/api/v4/projects/5/repository/merged_branches" ``` diff --git a/doc/api/broadcast_messages.md b/doc/api/broadcast_messages.md index fecfb142ab1..ad254e3515e 100644 --- a/doc/api/broadcast_messages.md +++ b/doc/api/broadcast_messages.md @@ -13,7 +13,7 @@ GET /broadcast_messages ``` ```bash -curl --header "PRIVATE-TOKEN: 9koXpg98eAheJpvBs5tK" https://gitlab.example.com/api/v3/broadcast_messages +curl --header "PRIVATE-TOKEN: 9koXpg98eAheJpvBs5tK" https://gitlab.example.com/api/v4/broadcast_messages ``` Example response: @@ -43,7 +43,7 @@ GET /broadcast_messages/:id | `id` | integer | yes | Broadcast message ID | ```bash -curl --header "PRIVATE-TOKEN: 9koXpg98eAheJpvBs5tK" https://gitlab.example.com/api/v3/broadcast_messages/1 +curl --header "PRIVATE-TOKEN: 9koXpg98eAheJpvBs5tK" https://gitlab.example.com/api/v4/broadcast_messages/1 ``` Example response: @@ -75,7 +75,7 @@ POST /broadcast_messages | `font` | string | no | Foreground color hex code | ```bash -curl --data "message=Deploy in progress&color=#cecece" --header "PRIVATE-TOKEN: 9koXpg98eAheJpvBs5tK" https://gitlab.example.com/api/v3/broadcast_messages +curl --data "message=Deploy in progress&color=#cecece" --header "PRIVATE-TOKEN: 9koXpg98eAheJpvBs5tK" https://gitlab.example.com/api/v4/broadcast_messages ``` Example response: @@ -108,7 +108,7 @@ PUT /broadcast_messages/:id | `font` | string | no | Foreground color hex code | ```bash -curl --request PUT --data "message=Update message&color=#000" --header "PRIVATE-TOKEN: 9koXpg98eAheJpvBs5tK" https://gitlab.example.com/api/v3/broadcast_messages/1 +curl --request PUT --data "message=Update message&color=#000" --header "PRIVATE-TOKEN: 9koXpg98eAheJpvBs5tK" https://gitlab.example.com/api/v4/broadcast_messages/1 ``` Example response: @@ -136,5 +136,5 @@ DELETE /broadcast_messages/:id | `id` | integer | yes | Broadcast message ID | ```bash -curl --request DELETE --header "PRIVATE-TOKEN: 9koXpg98eAheJpvBs5tK" https://gitlab.example.com/api/v3/broadcast_messages/1 +curl --request DELETE --header "PRIVATE-TOKEN: 9koXpg98eAheJpvBs5tK" https://gitlab.example.com/api/v4/broadcast_messages/1 ``` diff --git a/doc/api/build_triggers.md b/doc/api/build_triggers.md index 6adefe8c58c..28befba69d6 100644 --- a/doc/api/build_triggers.md +++ b/doc/api/build_triggers.md @@ -15,7 +15,7 @@ GET /projects/:id/triggers | `id` | integer | yes | The ID of a project | ``` -curl --header "PRIVATE-TOKEN: 9koXpg98eAheJpvBs5tK" "https://gitlab.example.com/api/v3/projects/1/triggers" +curl --header "PRIVATE-TOKEN: 9koXpg98eAheJpvBs5tK" "https://gitlab.example.com/api/v4/projects/1/triggers" ``` ```json @@ -51,7 +51,7 @@ GET /projects/:id/triggers/:token | `token` | string | yes | The `token` of a trigger | ``` -curl --header "PRIVATE-TOKEN: 9koXpg98eAheJpvBs5tK" "https://gitlab.example.com/api/v3/projects/1/triggers/7b9148c158980bbd9bcea92c17522d" +curl --header "PRIVATE-TOKEN: 9koXpg98eAheJpvBs5tK" "https://gitlab.example.com/api/v4/projects/1/triggers/7b9148c158980bbd9bcea92c17522d" ``` ```json @@ -77,7 +77,7 @@ POST /projects/:id/triggers | `id` | integer | yes | The ID of a project | ``` -curl --request POST --header "PRIVATE-TOKEN: 9koXpg98eAheJpvBs5tK" "https://gitlab.example.com/api/v3/projects/1/triggers" +curl --request POST --header "PRIVATE-TOKEN: 9koXpg98eAheJpvBs5tK" "https://gitlab.example.com/api/v4/projects/1/triggers" ``` ```json @@ -104,5 +104,5 @@ DELETE /projects/:id/triggers/:token | `token` | string | yes | The `token` of a trigger | ``` -curl --request DELETE --header "PRIVATE-TOKEN: 9koXpg98eAheJpvBs5tK" "https://gitlab.example.com/api/v3/projects/1/triggers/7b9148c158980bbd9bcea92c17522d" +curl --request DELETE --header "PRIVATE-TOKEN: 9koXpg98eAheJpvBs5tK" "https://gitlab.example.com/api/v4/projects/1/triggers/7b9148c158980bbd9bcea92c17522d" ``` diff --git a/doc/api/build_variables.md b/doc/api/build_variables.md index c21d5ab2787..1c26e9b33ab 100644 --- a/doc/api/build_variables.md +++ b/doc/api/build_variables.md @@ -13,7 +13,7 @@ GET /projects/:id/variables | `id` | integer | yes | The ID of a project | ``` -curl --header "PRIVATE-TOKEN: 9koXpg98eAheJpvBs5tK" "https://gitlab.example.com/api/v3/projects/1/variables" +curl --header "PRIVATE-TOKEN: 9koXpg98eAheJpvBs5tK" "https://gitlab.example.com/api/v4/projects/1/variables" ``` ```json @@ -43,7 +43,7 @@ GET /projects/:id/variables/:key | `key` | string | yes | The `key` of a variable | ``` -curl --header "PRIVATE-TOKEN: 9koXpg98eAheJpvBs5tK" "https://gitlab.example.com/api/v3/projects/1/variables/TEST_VARIABLE_1" +curl --header "PRIVATE-TOKEN: 9koXpg98eAheJpvBs5tK" "https://gitlab.example.com/api/v4/projects/1/variables/TEST_VARIABLE_1" ``` ```json @@ -68,7 +68,7 @@ POST /projects/:id/variables | `value` | string | yes | The `value` of a variable | ``` -curl --request POST --header "PRIVATE-TOKEN: 9koXpg98eAheJpvBs5tK" "https://gitlab.example.com/api/v3/projects/1/variables" --form "key=NEW_VARIABLE" --form "value=new value" +curl --request POST --header "PRIVATE-TOKEN: 9koXpg98eAheJpvBs5tK" "https://gitlab.example.com/api/v4/projects/1/variables" --form "key=NEW_VARIABLE" --form "value=new value" ``` ```json @@ -93,7 +93,7 @@ PUT /projects/:id/variables/:key | `value` | string | yes | The `value` of a variable | ``` -curl --request PUT --header "PRIVATE-TOKEN: 9koXpg98eAheJpvBs5tK" "https://gitlab.example.com/api/v3/projects/1/variables/NEW_VARIABLE" --form "value=updated value" +curl --request PUT --header "PRIVATE-TOKEN: 9koXpg98eAheJpvBs5tK" "https://gitlab.example.com/api/v4/projects/1/variables/NEW_VARIABLE" --form "value=updated value" ``` ```json @@ -117,5 +117,5 @@ DELETE /projects/:id/variables/:key | `key` | string | yes | The `key` of a variable | ``` -curl --request DELETE --header "PRIVATE-TOKEN: 9koXpg98eAheJpvBs5tK" "https://gitlab.example.com/api/v3/projects/1/variables/VARIABLE_1" +curl --request DELETE --header "PRIVATE-TOKEN: 9koXpg98eAheJpvBs5tK" "https://gitlab.example.com/api/v4/projects/1/variables/VARIABLE_1" ``` diff --git a/doc/api/builds.md b/doc/api/builds.md index bca2f9e44ef..84214e4708f 100644 --- a/doc/api/builds.md +++ b/doc/api/builds.md @@ -14,7 +14,7 @@ GET /projects/:id/builds | `scope` | string **or** array of strings | no | The scope of builds to show, one or array of: `created`, `pending`, `running`, `failed`, `success`, `canceled`, `skipped`; showing all builds if none provided | ``` -curl --header "PRIVATE-TOKEN: 9koXpg98eAheJpvBs5tK" 'https://gitlab.example.com/api/v3/projects/1/builds?scope%5B0%5D=pending&scope%5B1%5D=running' +curl --header "PRIVATE-TOKEN: 9koXpg98eAheJpvBs5tK" 'https://gitlab.example.com/api/v4/projects/1/builds?scope%5B0%5D=pending&scope%5B1%5D=running' ``` Example of response @@ -135,7 +135,7 @@ GET /projects/:id/repository/commits/:sha/builds | `scope` | string **or** array of strings | no | The scope of builds to show, one or array of: `created`, `pending`, `running`, `failed`, `success`, `canceled`, `skipped`; showing all builds if none provided | ``` -curl --header "PRIVATE-TOKEN: 9koXpg98eAheJpvBs5tK" 'https://gitlab.example.com/api/v3/projects/1/repository/commits/0ff3ae198f8601a285adcf5c0fff204ee6fba5fd/builds?scope%5B0%5D=pending&scope%5B1%5D=running' +curl --header "PRIVATE-TOKEN: 9koXpg98eAheJpvBs5tK" 'https://gitlab.example.com/api/v4/projects/1/repository/commits/0ff3ae198f8601a285adcf5c0fff204ee6fba5fd/builds?scope%5B0%5D=pending&scope%5B1%5D=running' ``` Example of response @@ -233,7 +233,7 @@ GET /projects/:id/builds/:build_id | `build_id` | integer | yes | The ID of a build | ``` -curl --header "PRIVATE-TOKEN: 9koXpg98eAheJpvBs5tK" "https://gitlab.example.com/api/v3/projects/1/builds/8" +curl --header "PRIVATE-TOKEN: 9koXpg98eAheJpvBs5tK" "https://gitlab.example.com/api/v4/projects/1/builds/8" ``` Example of response @@ -301,7 +301,7 @@ GET /projects/:id/builds/:build_id/artifacts | `build_id` | integer | yes | The ID of a build | ``` -curl --header "PRIVATE-TOKEN: 9koXpg98eAheJpvBs5tK" "https://gitlab.example.com/api/v3/projects/1/builds/8/artifacts" +curl --header "PRIVATE-TOKEN: 9koXpg98eAheJpvBs5tK" "https://gitlab.example.com/api/v4/projects/1/builds/8/artifacts" ``` Response: @@ -335,7 +335,7 @@ Parameters Example request: ``` -curl --header "PRIVATE-TOKEN: 9koXpg98eAheJpvBs5tK" "https://gitlab.example.com/api/v3/projects/1/builds/artifacts/master/download?job=test" +curl --header "PRIVATE-TOKEN: 9koXpg98eAheJpvBs5tK" "https://gitlab.example.com/api/v4/projects/1/builds/artifacts/master/download?job=test" ``` Example response: @@ -361,7 +361,7 @@ GET /projects/:id/builds/:build_id/trace | build_id | integer | yes | The ID of a build | ``` -curl --header "PRIVATE-TOKEN: 9koXpg98eAheJpvBs5tK" "https://gitlab.example.com/api/v3/projects/1/builds/8/trace" +curl --header "PRIVATE-TOKEN: 9koXpg98eAheJpvBs5tK" "https://gitlab.example.com/api/v4/projects/1/builds/8/trace" ``` Response: @@ -385,7 +385,7 @@ POST /projects/:id/builds/:build_id/cancel | `build_id` | integer | yes | The ID of a build | ``` -curl --request POST --header "PRIVATE-TOKEN: 9koXpg98eAheJpvBs5tK" "https://gitlab.example.com/api/v3/projects/1/builds/1/cancel" +curl --request POST --header "PRIVATE-TOKEN: 9koXpg98eAheJpvBs5tK" "https://gitlab.example.com/api/v4/projects/1/builds/1/cancel" ``` Example of response @@ -431,7 +431,7 @@ POST /projects/:id/builds/:build_id/retry | `build_id` | integer | yes | The ID of a build | ``` -curl --request POST --header "PRIVATE-TOKEN: 9koXpg98eAheJpvBs5tK" "https://gitlab.example.com/api/v3/projects/1/builds/1/retry" +curl --request POST --header "PRIVATE-TOKEN: 9koXpg98eAheJpvBs5tK" "https://gitlab.example.com/api/v4/projects/1/builds/1/retry" ``` Example of response @@ -481,7 +481,7 @@ Parameters Example of request ``` -curl --request POST --header "PRIVATE-TOKEN: 9koXpg98eAheJpvBs5tK" "https://gitlab.example.com/api/v3/projects/1/builds/1/erase" +curl --request POST --header "PRIVATE-TOKEN: 9koXpg98eAheJpvBs5tK" "https://gitlab.example.com/api/v4/projects/1/builds/1/erase" ``` Example of response @@ -531,7 +531,7 @@ Parameters Example request: ``` -curl --request POST --header "PRIVATE-TOKEN: 9koXpg98eAheJpvBs5tK" "https://gitlab.example.com/api/v3/projects/1/builds/1/artifacts/keep" +curl --request POST --header "PRIVATE-TOKEN: 9koXpg98eAheJpvBs5tK" "https://gitlab.example.com/api/v4/projects/1/builds/1/artifacts/keep" ``` Example response: @@ -577,7 +577,7 @@ POST /projects/:id/builds/:build_id/play | `build_id` | integer | yes | The ID of a build | ``` -curl --request POST --header "PRIVATE-TOKEN: 9koXpg98eAheJpvBs5tK" "https://gitlab.example.com/api/v3/projects/1/builds/1/play" +curl --request POST --header "PRIVATE-TOKEN: 9koXpg98eAheJpvBs5tK" "https://gitlab.example.com/api/v4/projects/1/builds/1/play" ``` Example of response diff --git a/doc/api/ci/lint.md b/doc/api/ci/lint.md index 0c96b3ee335..74def207816 100644 --- a/doc/api/ci/lint.md +++ b/doc/api/ci/lint.md @@ -13,7 +13,7 @@ POST ci/lint | `content` | string | yes | the .gitlab-ci.yaml content| ```bash -curl --header "Content-Type: application/json" https://gitlab.example.com/api/v3/ci/lint --data '{"content": "{ \"image\": \"ruby:2.1\", \"services\": [\"postgres\"], \"before_script\": [\"gem install bundler\", \"bundle install\", \"bundle exec rake db:create\"], \"variables\": {\"DB_NAME\": \"postgres\"}, \"types\": [\"test\", \"deploy\", \"notify\"], \"rspec\": { \"script\": \"rake spec\", \"tags\": [\"ruby\", \"postgres\"], \"only\": [\"branches\"]}}"}' +curl --header "Content-Type: application/json" https://gitlab.example.com/api/v4/ci/lint --data '{"content": "{ \"image\": \"ruby:2.1\", \"services\": [\"postgres\"], \"before_script\": [\"gem install bundler\", \"bundle install\", \"bundle exec rake db:create\"], \"variables\": {\"DB_NAME\": \"postgres\"}, \"types\": [\"test\", \"deploy\", \"notify\"], \"rspec\": { \"script\": \"rake spec\", \"tags\": [\"ruby\", \"postgres\"], \"only\": [\"branches\"]}}"}' ``` Be sure to copy paste the exact contents of `.gitlab-ci.yml` as YAML is very picky about indentation and spaces. diff --git a/doc/api/commits.md b/doc/api/commits.md index 18bc2873678..24c402346b1 100644 --- a/doc/api/commits.md +++ b/doc/api/commits.md @@ -16,7 +16,7 @@ GET /projects/:id/repository/commits | `until` | string | no | Only commits before or on this date will be returned in ISO 8601 format YYYY-MM-DDTHH:MM:SSZ | ```bash -curl --header "PRIVATE-TOKEN: 9koXpg98eAheJpvBs5tK" "https://gitlab.example.com/api/v3/projects/5/repository/commits" +curl --header "PRIVATE-TOKEN: 9koXpg98eAheJpvBs5tK" "https://gitlab.example.com/api/v4/projects/5/repository/commits" ``` Example response: @@ -114,7 +114,7 @@ PAYLOAD=$(cat << 'JSON' } JSON ) -curl --request POST --header "PRIVATE-TOKEN: 9koXpg98eAheJpvBs5tK" --header "Content-Type: application/json" --data "$PAYLOAD" https://gitlab.example.com/api/v3/projects/1/repository/commits +curl --request POST --header "PRIVATE-TOKEN: 9koXpg98eAheJpvBs5tK" --header "Content-Type: application/json" --data "$PAYLOAD" https://gitlab.example.com/api/v4/projects/1/repository/commits ``` Example response: @@ -159,7 +159,7 @@ Parameters: | `sha` | string | yes | The commit hash or name of a repository branch or tag | ```bash -curl --header "PRIVATE-TOKEN: 9koXpg98eAheJpvBs5tK" "https://gitlab.example.com/api/v3/projects/5/repository/commits/master +curl --header "PRIVATE-TOKEN: 9koXpg98eAheJpvBs5tK" "https://gitlab.example.com/api/v4/projects/5/repository/commits/master ``` Example response: @@ -208,7 +208,7 @@ Parameters: | `branch` | string | yes | The name of the branch | ```bash -curl --request POST --header "PRIVATE-TOKEN: 9koXpg98eAheJpvBs5tK" --form "branch=master" "https://gitlab.example.com/api/v3/projects/5/repository/commits/master/cherry_pick" +curl --request POST --header "PRIVATE-TOKEN: 9koXpg98eAheJpvBs5tK" --form "branch=master" "https://gitlab.example.com/api/v4/projects/5/repository/commits/master/cherry_pick" ``` Example response: @@ -249,7 +249,7 @@ Parameters: | `sha` | string | yes | The commit hash or name of a repository branch or tag | ```bash -curl --header "PRIVATE-TOKEN: 9koXpg98eAheJpvBs5tK" "https://gitlab.example.com/api/v3/projects/5/repository/commits/master/diff" +curl --header "PRIVATE-TOKEN: 9koXpg98eAheJpvBs5tK" "https://gitlab.example.com/api/v4/projects/5/repository/commits/master/diff" ``` Example response: @@ -285,7 +285,7 @@ Parameters: | `sha` | string | yes | The commit hash or name of a repository branch or tag | ```bash -curl --header "PRIVATE-TOKEN: 9koXpg98eAheJpvBs5tK" "https://gitlab.example.com/api/v3/projects/5/repository/commits/master/comments" +curl --header "PRIVATE-TOKEN: 9koXpg98eAheJpvBs5tK" "https://gitlab.example.com/api/v4/projects/5/repository/commits/master/comments" ``` Example response: @@ -338,7 +338,7 @@ POST /projects/:id/repository/commits/:sha/comments | `line_type` | string | no | The line type. Takes `new` or `old` as arguments | ```bash -curl --request POST --header "PRIVATE-TOKEN: 9koXpg98eAheJpvBs5tK" --form "note=Nice picture man\!" --form "path=dudeism.md" --form "line=11" --form "line_type=new" https://gitlab.example.com/api/v3/projects/17/repository/commits/18f3e63d05582537db6d183d9d557be09e1f90c8/comments +curl --request POST --header "PRIVATE-TOKEN: 9koXpg98eAheJpvBs5tK" --form "note=Nice picture man\!" --form "path=dudeism.md" --form "line=11" --form "line_type=new" https://gitlab.example.com/api/v4/projects/17/repository/commits/18f3e63d05582537db6d183d9d557be09e1f90c8/comments ``` Example response: @@ -383,7 +383,7 @@ GET /projects/:id/repository/commits/:sha/statuses | `all` | boolean | no | Return all statuses, not only the latest ones ```bash -curl --header "PRIVATE-TOKEN: 9koXpg98eAheJpvBs5tK" "https://gitlab.example.com/api/v3/projects/17/repository/commits/18f3e63d05582537db6d183d9d557be09e1f90c8/statuses +curl --header "PRIVATE-TOKEN: 9koXpg98eAheJpvBs5tK" "https://gitlab.example.com/api/v4/projects/17/repository/commits/18f3e63d05582537db6d183d9d557be09e1f90c8/statuses ``` Example response: @@ -459,7 +459,7 @@ POST /projects/:id/statuses/:sha | `coverage` | float | no | The total code coverage ```bash -curl --request POST --header "PRIVATE-TOKEN: 9koXpg98eAheJpvBs5tK" "https://gitlab.example.com/api/v3/projects/17/statuses/18f3e63d05582537db6d183d9d557be09e1f90c8?state=success" +curl --request POST --header "PRIVATE-TOKEN: 9koXpg98eAheJpvBs5tK" "https://gitlab.example.com/api/v4/projects/17/statuses/18f3e63d05582537db6d183d9d557be09e1f90c8?state=success" ``` Example response: diff --git a/doc/api/deploy_key_multiple_projects.md b/doc/api/deploy_key_multiple_projects.md index 73cb4b7ea8c..f94dbfa4059 100644 --- a/doc/api/deploy_key_multiple_projects.md +++ b/doc/api/deploy_key_multiple_projects.md @@ -7,16 +7,16 @@ First, find the ID of the projects you're interested in, by either listing all projects: ``` -curl --header 'PRIVATE-TOKEN: 9koXpg98eAheJpvBs5tK' https://gitlab.example.com/api/v3/projects +curl --header 'PRIVATE-TOKEN: 9koXpg98eAheJpvBs5tK' https://gitlab.example.com/api/v4/projects ``` Or finding the ID of a group and then listing all projects in that group: ``` -curl --header 'PRIVATE-TOKEN: 9koXpg98eAheJpvBs5tK' https://gitlab.example.com/api/v3/groups +curl --header 'PRIVATE-TOKEN: 9koXpg98eAheJpvBs5tK' https://gitlab.example.com/api/v4/groups # For group 1234: -curl --header 'PRIVATE-TOKEN: 9koXpg98eAheJpvBs5tK' https://gitlab.example.com/api/v3/groups/1234 +curl --header 'PRIVATE-TOKEN: 9koXpg98eAheJpvBs5tK' https://gitlab.example.com/api/v4/groups/1234 ``` With those IDs, add the same deploy key to all: @@ -24,6 +24,6 @@ With those IDs, add the same deploy key to all: ``` for project_id in 321 456 987; do curl --request POST --header "PRIVATE-TOKEN: 9koXpg98eAheJpvBs5tK" --header "Content-Type: application/json" \ - --data '{"title": "my key", "key": "ssh-rsa AAAA..."}' https://gitlab.example.com/api/v3/projects/${project_id}/deploy_keys + --data '{"title": "my key", "key": "ssh-rsa AAAA..."}' https://gitlab.example.com/api/v4/projects/${project_id}/deploy_keys done ``` diff --git a/doc/api/deploy_keys.md b/doc/api/deploy_keys.md index d03d94cb867..f051f55ac3e 100644 --- a/doc/api/deploy_keys.md +++ b/doc/api/deploy_keys.md @@ -9,7 +9,7 @@ GET /deploy_keys ``` ```bash -curl --header "PRIVATE-TOKEN: 9koXpg98eAheJpvBs5tK" "https://gitlab.example.com/api/v3/deploy_keys" +curl --header "PRIVATE-TOKEN: 9koXpg98eAheJpvBs5tK" "https://gitlab.example.com/api/v4/deploy_keys" ``` Example response: @@ -46,7 +46,7 @@ GET /projects/:id/deploy_keys | `id` | integer | yes | The ID of the project | ```bash -curl --header "PRIVATE-TOKEN: 9koXpg98eAheJpvBs5tK" "https://gitlab.example.com/api/v3/projects/5/deploy_keys" +curl --header "PRIVATE-TOKEN: 9koXpg98eAheJpvBs5tK" "https://gitlab.example.com/api/v4/projects/5/deploy_keys" ``` Example response: @@ -86,7 +86,7 @@ Parameters: | `key_id` | integer | yes | The ID of the deploy key | ```bash -curl --header "PRIVATE-TOKEN: 9koXpg98eAheJpvBs5tK" "https://gitlab.example.com/api/v3/projects/5/deploy_keys/11" +curl --header "PRIVATE-TOKEN: 9koXpg98eAheJpvBs5tK" "https://gitlab.example.com/api/v4/projects/5/deploy_keys/11" ``` Example response: @@ -120,7 +120,7 @@ POST /projects/:id/deploy_keys | `can_push` | boolean | no | Can deploy key push to the project's repository | ```bash -curl --request POST --header "PRIVATE-TOKEN: 9koXpg98eAheJpvBs5tK" --header "Content-Type: application/json" --data '{"title": "My deploy key", "key": "ssh-rsa AAAA...", "can_push": "true"}' "https://gitlab.example.com/api/v3/projects/5/deploy_keys/" +curl --request POST --header "PRIVATE-TOKEN: 9koXpg98eAheJpvBs5tK" --header "Content-Type: application/json" --data '{"title": "My deploy key", "key": "ssh-rsa AAAA...", "can_push": "true"}' "https://gitlab.example.com/api/v4/projects/5/deploy_keys/" ``` Example response: @@ -149,7 +149,7 @@ DELETE /projects/:id/deploy_keys/:key_id | `key_id` | integer | yes | The ID of the deploy key | ```bash -curl --request DELETE --header "PRIVATE-TOKEN: 9koXpg98eAheJpvBs5tK" "https://gitlab.example.com/api/v3/projects/5/deploy_keys/13" +curl --request DELETE --header "PRIVATE-TOKEN: 9koXpg98eAheJpvBs5tK" "https://gitlab.example.com/api/v4/projects/5/deploy_keys/13" ``` ## Enable a deploy key @@ -157,7 +157,7 @@ curl --request DELETE --header "PRIVATE-TOKEN: 9koXpg98eAheJpvBs5tK" "https://gi Enables a deploy key for a project so this can be used. Returns the enabled key, with a status code 201 when successful. ```bash -curl --request POST --header "PRIVATE-TOKEN: 9koXpg98eAheJpvBs5tK" https://gitlab.example.com/api/v3/projects/5/deploy_keys/13/enable +curl --request POST --header "PRIVATE-TOKEN: 9koXpg98eAheJpvBs5tK" https://gitlab.example.com/api/v4/projects/5/deploy_keys/13/enable ``` | Attribute | Type | Required | Description | diff --git a/doc/api/deployments.md b/doc/api/deployments.md index 3d95c4cde60..76e18c8a9bd 100644 --- a/doc/api/deployments.md +++ b/doc/api/deployments.md @@ -13,7 +13,7 @@ GET /projects/:id/deployments | `id` | integer | yes | The ID of a project | ```bash -curl --header "PRIVATE-TOKEN: 9koXpg98eAheJpvBs5tK" "https://gitlab.example.com/api/v3/projects/1/deployments" +curl --header "PRIVATE-TOKEN: 9koXpg98eAheJpvBs5tK" "https://gitlab.example.com/api/v4/projects/1/deployments" ``` Example of response @@ -151,7 +151,7 @@ GET /projects/:id/deployments/:deployment_id | `deployment_id` | integer | yes | The ID of the deployment | ```bash -curl --header "PRIVATE-TOKEN: 9koXpg98eAheJpvBs5tK" "https://gitlab.example.com/api/v3/projects/1/deployments/1" +curl --header "PRIVATE-TOKEN: 9koXpg98eAheJpvBs5tK" "https://gitlab.example.com/api/v4/projects/1/deployments/1" ``` Example of response diff --git a/doc/api/enviroments.md b/doc/api/enviroments.md index e510f723e26..3f0a8d989f9 100644 --- a/doc/api/enviroments.md +++ b/doc/api/enviroments.md @@ -13,7 +13,7 @@ GET /projects/:id/environments | `id` | integer | yes | The ID of the project | ```bash -curl --header "PRIVATE-TOKEN: 9koXpg98eAheJpvBs5tK" https://gitlab.example.com/api/v3/projects/1/environments +curl --header "PRIVATE-TOKEN: 9koXpg98eAheJpvBs5tK" https://gitlab.example.com/api/v4/projects/1/environments ``` Example response: @@ -33,7 +33,7 @@ Example response: Creates a new environment with the given name and external_url. -It returns 201 if the environment was successfully created, 400 for wrong parameters. +It returns `201` if the environment was successfully created, `400` for wrong parameters. ``` POST /projects/:id/environment @@ -46,7 +46,7 @@ POST /projects/:id/environment | `external_url` | string | no | Place to link to for this environment | ```bash -curl --data "name=deploy&external_url=https://deploy.example.gitlab.com" --header "PRIVATE-TOKEN: 9koXpg98eAheJpvBs5tK" "https://gitlab.example.com/api/v3/projects/1/environments" +curl --data "name=deploy&external_url=https://deploy.example.gitlab.com" --header "PRIVATE-TOKEN: 9koXpg98eAheJpvBs5tK" "https://gitlab.example.com/api/v4/projects/1/environments" ``` Example response: @@ -64,7 +64,7 @@ Example response: Updates an existing environment's name and/or external_url. -It returns 200 if the environment was successfully updated. In case of an error, a status code 400 is returned. +It returns `200` if the environment was successfully updated. In case of an error, a status code `400` is returned. ``` PUT /projects/:id/environments/:environments_id @@ -78,7 +78,7 @@ PUT /projects/:id/environments/:environments_id | `external_url` | string | no | The new external_url | ```bash -curl --request PUT --data "name=staging&external_url=https://staging.example.gitlab.com" --header "PRIVATE-TOKEN: 9koXpg98eAheJpvBs5tK" "https://gitlab.example.com/api/v3/projects/1/environments/1" +curl --request PUT --data "name=staging&external_url=https://staging.example.gitlab.com" --header "PRIVATE-TOKEN: 9koXpg98eAheJpvBs5tK" "https://gitlab.example.com/api/v4/projects/1/environments/1" ``` Example response: @@ -94,7 +94,7 @@ Example response: ## Delete an environment -It returns 200 if the environment was successfully deleted, and 404 if the environment does not exist. +It returns `200` if the environment was successfully deleted, and `404` if the environment does not exist. ``` DELETE /projects/:id/environments/:environment_id @@ -106,5 +106,33 @@ DELETE /projects/:id/environments/:environment_id | `environment_id` | integer | yes | The ID of the environment | ```bash -curl --request DELETE --header "PRIVATE-TOKEN: 9koXpg98eAheJpvBs5tK" "https://gitlab.example.com/api/v3/projects/1/environments/1" +curl --request DELETE --header "PRIVATE-TOKEN: 9koXpg98eAheJpvBs5tK" "https://gitlab.example.com/api/v4/projects/1/environments/1" +``` + +## Stop an environment + +It returns `200` if the environment was successfully stopped, and `404` if the environment does not exist. + +``` +POST /projects/:id/environments/:environment_id/stop +``` + +| Attribute | Type | Required | Description | +| --------- | ------- | -------- | --------------------- | +| `id` | integer | yes | The ID of the project | +| `environment_id` | integer | yes | The ID of the environment | + +```bash +curl --request POST --header "PRIVATE-TOKEN: 9koXpg98eAheJpvBs5tK" "https://gitlab.example.com/api/v3/projects/1/environments/1/stop" +``` + +Example response: + +```json +{ + "id": 1, + "name": "deploy", + "slug": "deploy", + "external_url": "https://deploy.example.gitlab.com" +} ``` diff --git a/doc/api/groups.md b/doc/api/groups.md index 39adb5be502..80c08096dea 100644 --- a/doc/api/groups.md +++ b/doc/api/groups.md @@ -27,7 +27,7 @@ GET /groups "name": "Foobar Group", "path": "foo-bar", "description": "An interesting group", - "visibility_level": 20, + "visibility": "public", "lfs_enabled": true, "avatar_url": "http://localhost:3000/uploads/group/avatar/1/foo.jpg", "web_url": "http://localhost:3000/groups/foo-bar", @@ -72,9 +72,8 @@ Example response: "description": "foo", "default_branch": "master", "tag_list": [], - "public": false, "archived": false, - "visibility_level": 10, + "visibility": "internal", "ssh_url_to_repo": "git@gitlab.example.com/html5-boilerplate.git", "http_url_to_repo": "http://gitlab.example.com/h5bp/html5-boilerplate.git", "web_url": "http://gitlab.example.com/h5bp/html5-boilerplate", @@ -123,7 +122,7 @@ Parameters: | `id` | integer/string | yes | The ID or path of a group | ```bash -curl --header "PRIVATE-TOKEN: 9koXpg98eAheJpvBs5tK" https://gitlab.example.com/api/v3/groups/4 +curl --header "PRIVATE-TOKEN: 9koXpg98eAheJpvBs5tK" https://gitlab.example.com/api/v4/groups/4 ``` Example response: @@ -134,7 +133,7 @@ Example response: "name": "Twitter", "path": "twitter", "description": "Aliquid qui quis dignissimos distinctio ut commodi voluptas est.", - "visibility_level": 20, + "visibility": "public", "avatar_url": null, "web_url": "https://gitlab.example.com/groups/twitter", "request_access_enabled": false, @@ -147,9 +146,8 @@ Example response: "description": "Voluptas veniam qui et beatae voluptas doloremque explicabo facilis.", "default_branch": "master", "tag_list": [], - "public": true, "archived": false, - "visibility_level": 20, + "visibility": "public", "ssh_url_to_repo": "git@gitlab.example.com:twitter/typeahead-js.git", "http_url_to_repo": "https://gitlab.example.com/twitter/typeahead-js.git", "web_url": "https://gitlab.example.com/twitter/typeahead-js", @@ -186,9 +184,8 @@ Example response: "description": "Aspernatur omnis repudiandae qui voluptatibus eaque.", "default_branch": "master", "tag_list": [], - "public": false, "archived": false, - "visibility_level": 10, + "visibility": "internal", "ssh_url_to_repo": "git@gitlab.example.com:twitter/flight.git", "http_url_to_repo": "https://gitlab.example.com/twitter/flight.git", "web_url": "https://gitlab.example.com/twitter/flight", @@ -227,9 +224,8 @@ Example response: "description": "Velit eveniet provident fugiat saepe eligendi autem.", "default_branch": "master", "tag_list": [], - "public": false, "archived": false, - "visibility_level": 0, + "visibility": "private", "ssh_url_to_repo": "git@gitlab.example.com:h5bp/html5-boilerplate.git", "http_url_to_repo": "https://gitlab.example.com/h5bp/html5-boilerplate.git", "web_url": "https://gitlab.example.com/h5bp/html5-boilerplate", @@ -288,7 +284,7 @@ Parameters: - `name` (required) - The name of the group - `path` (required) - The path of the group - `description` (optional) - The group's description -- `visibility_level` (optional) - The group's visibility. 0 for private, 10 for internal, 20 for public. +- `visibility` (optional) - The group's visibility. Can be `private`, `internal`, or `public`. - `lfs_enabled` (optional) - Enable/disable Large File Storage (LFS) for the projects in this group - `request_access_enabled` (optional) - Allow users to request member access. - `parent_id` (optional) - The parent group id for creating nested group. @@ -320,12 +316,12 @@ PUT /groups/:id | `name` | string | no | The name of the group | | `path` | string | no | The path of the group | | `description` | string | no | The description of the group | -| `visibility_level` | integer | no | The visibility level of the group. 0 for private, 10 for internal, 20 for public. | +| `visibility` | string | no | The visibility level of the group. Can be `private`, `internal`, or `public`. | | `lfs_enabled` (optional) | boolean | no | Enable/disable Large File Storage (LFS) for the projects in this group | | `request_access_enabled` | boolean | no | Allow users to request member access. | ```bash -curl --request PUT --header "PRIVATE-TOKEN: 9koXpg98eAheJpvBs5tK" "https://gitlab.example.com/api/v3/groups/5?name=Experimental" +curl --request PUT --header "PRIVATE-TOKEN: 9koXpg98eAheJpvBs5tK" "https://gitlab.example.com/api/v4/groups/5?name=Experimental" ``` @@ -337,7 +333,7 @@ Example response: "name": "Experimental", "path": "h5bp", "description": "foo", - "visibility_level": 10, + "visibility": "internal", "avatar_url": null, "web_url": "http://gitlab.example.com/groups/h5bp", "request_access_enabled": false, @@ -352,7 +348,7 @@ Example response: "tag_list": [], "public": false, "archived": false, - "visibility_level": 10, + "visibility": "internal", "ssh_url_to_repo": "git@gitlab.example.com/html5-boilerplate.git", "http_url_to_repo": "http://gitlab.example.com/h5bp/html5-boilerplate.git", "web_url": "http://gitlab.example.com/h5bp/html5-boilerplate", diff --git a/doc/api/issues.md b/doc/api/issues.md index b6798fba0ae..51ce08c873e 100644 --- a/doc/api/issues.md +++ b/doc/api/issues.md @@ -25,6 +25,7 @@ GET /issues?labels=foo,bar GET /issues?labels=foo,bar&state=opened GET /issues?milestone=1.0.0 GET /issues?milestone=1.0.0&state=opened +GET /issues?iids[]=42&iids[]=43 ``` | Attribute | Type | Required | Description | @@ -32,11 +33,12 @@ GET /issues?milestone=1.0.0&state=opened | `state` | string | no | Return all issues or just those that are `opened` or `closed`| | `labels` | string | no | Comma-separated list of label names, issues must have all labels to be returned | | `milestone` | string| no | The milestone title | +| `iids` | Array[integer] | no | Return only the issues having the given `iid` | | `order_by`| string | no | Return requests ordered by `created_at` or `updated_at` fields. Default is `created_at` | | `sort` | string | no | Return requests sorted in `asc` or `desc` order. Default is `desc` | ```bash -curl --header "PRIVATE-TOKEN: 9koXpg98eAheJpvBs5tK" https://gitlab.example.com/api/v3/issues +curl --header "PRIVATE-TOKEN: 9koXpg98eAheJpvBs5tK" https://gitlab.example.com/api/v4/issues ``` Example response: @@ -102,6 +104,7 @@ GET /groups/:id/issues?labels=foo,bar GET /groups/:id/issues?labels=foo,bar&state=opened GET /groups/:id/issues?milestone=1.0.0 GET /groups/:id/issues?milestone=1.0.0&state=opened +GET /groups/:id/issues?iids[]=42&iids[]=43 ``` | Attribute | Type | Required | Description | @@ -109,13 +112,14 @@ GET /groups/:id/issues?milestone=1.0.0&state=opened | `id` | integer | yes | The ID of a group | | `state` | string | no | Return all issues or just those that are `opened` or `closed`| | `labels` | string | no | Comma-separated list of label names, issues must have all labels to be returned | +| `iids` | Array[integer] | no | Return only the issues having the given `iid` | | `milestone` | string| no | The milestone title | | `order_by`| string | no | Return requests ordered by `created_at` or `updated_at` fields. Default is `created_at` | | `sort` | string | no | Return requests sorted in `asc` or `desc` order. Default is `desc` | ```bash -curl --header "PRIVATE-TOKEN: 9koXpg98eAheJpvBs5tK" https://gitlab.example.com/api/v3/groups/4/issues +curl --header "PRIVATE-TOKEN: 9koXpg98eAheJpvBs5tK" https://gitlab.example.com/api/v4/groups/4/issues ``` Example response: @@ -181,12 +185,13 @@ GET /projects/:id/issues?labels=foo,bar GET /projects/:id/issues?labels=foo,bar&state=opened GET /projects/:id/issues?milestone=1.0.0 GET /projects/:id/issues?milestone=1.0.0&state=opened +GET /projects/:id/issues?iids[]=42&iids[]=43 ``` | Attribute | Type | Required | Description | | --------- | ---- | -------- | ----------- | | `id` | integer | yes | The ID of a project | -| `iid` | integer | no | Return the issue having the given `iid` | +| `iids` | Array[integer] | no | Return only the milestone having the given `iid` | | `state` | string | no | Return all issues or just those that are `opened` or `closed`| | `labels` | string | no | Comma-separated list of label names, issues must have all labels to be returned | | `milestone` | string| no | The milestone title | @@ -195,7 +200,7 @@ GET /projects/:id/issues?milestone=1.0.0&state=opened ```bash -curl --header "PRIVATE-TOKEN: 9koXpg98eAheJpvBs5tK" https://gitlab.example.com/api/v3/projects/4/issues +curl --header "PRIVATE-TOKEN: 9koXpg98eAheJpvBs5tK" https://gitlab.example.com/api/v4/projects/4/issues ``` Example response: @@ -262,7 +267,7 @@ GET /projects/:id/issues/:issue_id | `issue_id`| integer | yes | The ID of a project's issue | ```bash -curl --header "PRIVATE-TOKEN: 9koXpg98eAheJpvBs5tK" https://gitlab.example.com/api/v3/projects/4/issues/41 +curl --header "PRIVATE-TOKEN: 9koXpg98eAheJpvBs5tK" https://gitlab.example.com/api/v4/projects/4/issues/41 ``` Example response: @@ -335,7 +340,7 @@ POST /projects/:id/issues | `merge_request_for_resolving_discussions` | integer | no | The IID of a merge request in which to resolve all issues. This will fill the issue with a default description and mark all discussions as resolved. When passing a description or title, these values will take precedence over the default values. | ```bash -curl --request POST --header "PRIVATE-TOKEN: 9koXpg98eAheJpvBs5tK" https://gitlab.example.com/api/v3/projects/4/issues?title=Issues%20with%20auth&labels=bug +curl --request POST --header "PRIVATE-TOKEN: 9koXpg98eAheJpvBs5tK" https://gitlab.example.com/api/v4/projects/4/issues?title=Issues%20with%20auth&labels=bug ``` Example response: @@ -395,7 +400,7 @@ PUT /projects/:id/issues/:issue_id | `due_date` | string | no | Date time string in the format YEAR-MONTH-DAY, e.g. `2016-03-11` | ```bash -curl --request PUT --header "PRIVATE-TOKEN: 9koXpg98eAheJpvBs5tK" https://gitlab.example.com/api/v3/projects/4/issues/85?state_event=close +curl --request PUT --header "PRIVATE-TOKEN: 9koXpg98eAheJpvBs5tK" https://gitlab.example.com/api/v4/projects/4/issues/85?state_event=close ``` Example response: @@ -445,7 +450,7 @@ DELETE /projects/:id/issues/:issue_id | `issue_id` | integer | yes | The ID of a project's issue | ```bash -curl --request DELETE --header "PRIVATE-TOKEN: 9koXpg98eAheJpvBs5tK" https://gitlab.example.com/api/v3/projects/4/issues/85 +curl --request DELETE --header "PRIVATE-TOKEN: 9koXpg98eAheJpvBs5tK" https://gitlab.example.com/api/v4/projects/4/issues/85 ``` ## Move an issue @@ -468,7 +473,7 @@ POST /projects/:id/issues/:issue_id/move | `to_project_id` | integer | yes | The ID of the new project | ```bash -curl --request POST --header "PRIVATE-TOKEN: 9koXpg98eAheJpvBs5tK" https://gitlab.example.com/api/v3/projects/4/issues/85/move +curl --request POST --header "PRIVATE-TOKEN: 9koXpg98eAheJpvBs5tK" https://gitlab.example.com/api/v4/projects/4/issues/85/move ``` Example response: @@ -523,7 +528,7 @@ POST /projects/:id/issues/:issue_id/subscribe | `issue_id` | integer | yes | The ID of a project's issue | ```bash -curl --request POST --header "PRIVATE-TOKEN: 9koXpg98eAheJpvBs5tK" https://gitlab.example.com/api/v3/projects/5/issues/93/subscribe +curl --request POST --header "PRIVATE-TOKEN: 9koXpg98eAheJpvBs5tK" https://gitlab.example.com/api/v4/projects/5/issues/93/subscribe ``` Example response: @@ -578,7 +583,7 @@ POST /projects/:id/issues/:issue_id/unsubscribe | `issue_id` | integer | yes | The ID of a project's issue | ```bash -curl --request POST --header "PRIVATE-TOKEN: 9koXpg98eAheJpvBs5tK" https://gitlab.example.com/api/v3/projects/5/issues/93/unsubscribe +curl --request POST --header "PRIVATE-TOKEN: 9koXpg98eAheJpvBs5tK" https://gitlab.example.com/api/v4/projects/5/issues/93/unsubscribe ``` ## Create a todo @@ -597,7 +602,7 @@ POST /projects/:id/issues/:issue_id/todo | `issue_id` | integer | yes | The ID of a project's issue | ```bash -curl --request POST --header "PRIVATE-TOKEN: 9koXpg98eAheJpvBs5tK" https://gitlab.example.com/api/v3/projects/5/issues/93/todo +curl --request POST --header "PRIVATE-TOKEN: 9koXpg98eAheJpvBs5tK" https://gitlab.example.com/api/v4/projects/5/issues/93/todo ``` Example response: @@ -689,7 +694,7 @@ POST /projects/:id/issues/:issue_id/time_estimate | `duration` | string | yes | The duration in human format. e.g: 3h30m | ```bash -curl --request POST --header "PRIVATE-TOKEN: 9koXpg98eAheJpvBs5tK" https://gitlab.example.com/api/v3/projects/5/issues/93/time_estimate?duration=3h30m +curl --request POST --header "PRIVATE-TOKEN: 9koXpg98eAheJpvBs5tK" https://gitlab.example.com/api/v4/projects/5/issues/93/time_estimate?duration=3h30m ``` Example response: @@ -717,7 +722,7 @@ POST /projects/:id/issues/:issue_id/reset_time_estimate | `issue_id` | integer | yes | The ID of a project's issue | ```bash -curl --request POST --header "PRIVATE-TOKEN: 9koXpg98eAheJpvBs5tK" https://gitlab.example.com/api/v3/projects/5/issues/93/reset_time_estimate +curl --request POST --header "PRIVATE-TOKEN: 9koXpg98eAheJpvBs5tK" https://gitlab.example.com/api/v4/projects/5/issues/93/reset_time_estimate ``` Example response: @@ -746,7 +751,7 @@ POST /projects/:id/issues/:issue_id/add_spent_time | `duration` | string | yes | The duration in human format. e.g: 3h30m | ```bash -curl --request POST --header "PRIVATE-TOKEN: 9koXpg98eAheJpvBs5tK" https://gitlab.example.com/api/v3/projects/5/issues/93/add_spent_time?duration=1h +curl --request POST --header "PRIVATE-TOKEN: 9koXpg98eAheJpvBs5tK" https://gitlab.example.com/api/v4/projects/5/issues/93/add_spent_time?duration=1h ``` Example response: @@ -774,7 +779,7 @@ POST /projects/:id/issues/:issue_id/reset_spent_time | `issue_id` | integer | yes | The ID of a project's issue | ```bash -curl --request POST --header "PRIVATE-TOKEN: 9koXpg98eAheJpvBs5tK" https://gitlab.example.com/api/v3/projects/5/issues/93/reset_spent_time +curl --request POST --header "PRIVATE-TOKEN: 9koXpg98eAheJpvBs5tK" https://gitlab.example.com/api/v4/projects/5/issues/93/reset_spent_time ``` Example response: @@ -800,7 +805,7 @@ GET /projects/:id/issues/:issue_id/time_stats | `issue_id` | integer | yes | The ID of a project's issue | ```bash -curl --request GET --header "PRIVATE-TOKEN: 9koXpg98eAheJpvBs5tK" https://gitlab.example.com/api/v3/projects/5/issues/93/time_stats +curl --request GET --header "PRIVATE-TOKEN: 9koXpg98eAheJpvBs5tK" https://gitlab.example.com/api/v4/projects/5/issues/93/time_stats ``` Example response: diff --git a/doc/api/labels.md b/doc/api/labels.md index 85bd9647a7b..e8c220f6809 100644 --- a/doc/api/labels.md +++ b/doc/api/labels.md @@ -13,7 +13,7 @@ GET /projects/:id/labels | `id` | integer | yes | The ID of the project | ```bash -curl --header "PRIVATE-TOKEN: 9koXpg98eAheJpvBs5tK" https://gitlab.example.com/api/v3/projects/1/labels +curl --header "PRIVATE-TOKEN: 9koXpg98eAheJpvBs5tK" https://gitlab.example.com/api/v4/projects/1/labels ``` Example response: @@ -95,7 +95,7 @@ POST /projects/:id/labels | `priority` | integer | no | The priority of the label. Must be greater or equal than zero or `null` to remove the priority. | ```bash -curl --data "name=feature&color=#5843AD" --header "PRIVATE-TOKEN: 9koXpg98eAheJpvBs5tK" "https://gitlab.example.com/api/v3/projects/1/labels" +curl --data "name=feature&color=#5843AD" --header "PRIVATE-TOKEN: 9koXpg98eAheJpvBs5tK" "https://gitlab.example.com/api/v4/projects/1/labels" ``` Example response: @@ -128,7 +128,7 @@ DELETE /projects/:id/labels | `name` | string | yes | The name of the label | ```bash -curl --request DELETE --header "PRIVATE-TOKEN: 9koXpg98eAheJpvBs5tK" "https://gitlab.example.com/api/v3/projects/1/labels?name=bug" +curl --request DELETE --header "PRIVATE-TOKEN: 9koXpg98eAheJpvBs5tK" "https://gitlab.example.com/api/v4/projects/1/labels?name=bug" ``` ## Edit an existing label @@ -151,7 +151,7 @@ PUT /projects/:id/labels ```bash -curl --request PUT --data "name=documentation&new_name=docs&color=#8E44AD&description=Documentation" --header "PRIVATE-TOKEN: 9koXpg98eAheJpvBs5tK" "https://gitlab.example.com/api/v3/projects/1/labels" +curl --request PUT --data "name=documentation&new_name=docs&color=#8E44AD&description=Documentation" --header "PRIVATE-TOKEN: 9koXpg98eAheJpvBs5tK" "https://gitlab.example.com/api/v4/projects/1/labels" ``` Example response: @@ -186,7 +186,7 @@ POST /projects/:id/labels/:label_id/subscribe | `label_id` | integer or string | yes | The ID or title of a project's label | ```bash -curl --request POST --header "PRIVATE-TOKEN: 9koXpg98eAheJpvBs5tK" https://gitlab.example.com/api/v3/projects/5/labels/1/subscribe +curl --request POST --header "PRIVATE-TOKEN: 9koXpg98eAheJpvBs5tK" https://gitlab.example.com/api/v4/projects/5/labels/1/subscribe ``` Example response: @@ -221,5 +221,5 @@ POST /projects/:id/labels/:label_id/unsubscribe | `label_id` | integer or string | yes | The ID or title of a project's label | ```bash -curl --request POST --header "PRIVATE-TOKEN: 9koXpg98eAheJpvBs5tK" https://gitlab.example.com/api/v3/projects/5/labels/1/unsubscribe +curl --request POST --header "PRIVATE-TOKEN: 9koXpg98eAheJpvBs5tK" https://gitlab.example.com/api/v4/projects/5/labels/1/unsubscribe ``` diff --git a/doc/api/members.md b/doc/api/members.md index 5dcb2a5f60a..fe46f8f84bc 100644 --- a/doc/api/members.md +++ b/doc/api/members.md @@ -27,8 +27,8 @@ GET /projects/:id/members | `query` | string | no | A query string to search for members | ```bash -curl --header "PRIVATE-TOKEN: 9koXpg98eAheJpvBs5tK" https://gitlab.example.com/api/v3/groups/:id/members -curl --header "PRIVATE-TOKEN: 9koXpg98eAheJpvBs5tK" https://gitlab.example.com/api/v3/projects/:id/members +curl --header "PRIVATE-TOKEN: 9koXpg98eAheJpvBs5tK" https://gitlab.example.com/api/v4/groups/:id/members +curl --header "PRIVATE-TOKEN: 9koXpg98eAheJpvBs5tK" https://gitlab.example.com/api/v4/projects/:id/members ``` Example response: @@ -69,8 +69,8 @@ GET /projects/:id/members/:user_id | `user_id` | integer | yes | The user ID of the member | ```bash -curl --header "PRIVATE-TOKEN: 9koXpg98eAheJpvBs5tK" https://gitlab.example.com/api/v3/groups/:id/members/:user_id -curl --header "PRIVATE-TOKEN: 9koXpg98eAheJpvBs5tK" https://gitlab.example.com/api/v3/projects/:id/members/:user_id +curl --header "PRIVATE-TOKEN: 9koXpg98eAheJpvBs5tK" https://gitlab.example.com/api/v4/groups/:id/members/:user_id +curl --header "PRIVATE-TOKEN: 9koXpg98eAheJpvBs5tK" https://gitlab.example.com/api/v4/projects/:id/members/:user_id ``` Example response: @@ -104,8 +104,8 @@ POST /projects/:id/members | `expires_at` | string | no | A date string in the format YEAR-MONTH-DAY | ```bash -curl --request POST --header "PRIVATE-TOKEN: 9koXpg98eAheJpvBs5tK" --data "user_id=1&access_level=30" https://gitlab.example.com/api/v3/groups/:id/members -curl --request POST --header "PRIVATE-TOKEN: 9koXpg98eAheJpvBs5tK" --data "user_id=1&access_level=30" https://gitlab.example.com/api/v3/projects/:id/members +curl --request POST --header "PRIVATE-TOKEN: 9koXpg98eAheJpvBs5tK" --data "user_id=1&access_level=30" https://gitlab.example.com/api/v4/groups/:id/members +curl --request POST --header "PRIVATE-TOKEN: 9koXpg98eAheJpvBs5tK" --data "user_id=1&access_level=30" https://gitlab.example.com/api/v4/projects/:id/members ``` Example response: @@ -138,8 +138,8 @@ PUT /projects/:id/members/:user_id | `expires_at` | string | no | A date string in the format YEAR-MONTH-DAY | ```bash -curl --request PUT --header "PRIVATE-TOKEN: 9koXpg98eAheJpvBs5tK" https://gitlab.example.com/api/v3/groups/:id/members/:user_id?access_level=40 -curl --request PUT --header "PRIVATE-TOKEN: 9koXpg98eAheJpvBs5tK" https://gitlab.example.com/api/v3/projects/:id/members/:user_id?access_level=40 +curl --request PUT --header "PRIVATE-TOKEN: 9koXpg98eAheJpvBs5tK" https://gitlab.example.com/api/v4/groups/:id/members/:user_id?access_level=40 +curl --request PUT --header "PRIVATE-TOKEN: 9koXpg98eAheJpvBs5tK" https://gitlab.example.com/api/v4/projects/:id/members/:user_id?access_level=40 ``` Example response: @@ -170,6 +170,6 @@ DELETE /projects/:id/members/:user_id | `user_id` | integer | yes | The user ID of the member | ```bash -curl --request DELETE --header "PRIVATE-TOKEN: 9koXpg98eAheJpvBs5tK" https://gitlab.example.com/api/v3/groups/:id/members/:user_id -curl --request DELETE --header "PRIVATE-TOKEN: 9koXpg98eAheJpvBs5tK" https://gitlab.example.com/api/v3/projects/:id/members/:user_id +curl --request DELETE --header "PRIVATE-TOKEN: 9koXpg98eAheJpvBs5tK" https://gitlab.example.com/api/v4/groups/:id/members/:user_id +curl --request DELETE --header "PRIVATE-TOKEN: 9koXpg98eAheJpvBs5tK" https://gitlab.example.com/api/v4/projects/:id/members/:user_id ``` diff --git a/doc/api/merge_requests.md b/doc/api/merge_requests.md index ea30a163a12..e178d5c1629 100644 --- a/doc/api/merge_requests.md +++ b/doc/api/merge_requests.md @@ -65,7 +65,7 @@ Parameters: "updated_at": "2015-02-02T19:49:26.013Z", "due_date": null }, - "merge_when_build_succeeds": true, + "merge_when_pipeline_succeeds": true, "merge_status": "can_be_merged", "subscribed" : false, "sha": "8888888888888888888888888888888888888888", @@ -134,7 +134,7 @@ Parameters: "updated_at": "2015-02-02T19:49:26.013Z", "due_date": null }, - "merge_when_build_succeeds": true, + "merge_when_pipeline_succeeds": true, "merge_status": "can_be_merged", "subscribed" : true, "sha": "8888888888888888888888888888888888888888", @@ -239,7 +239,7 @@ Parameters: "updated_at": "2015-02-02T19:49:26.013Z", "due_date": null }, - "merge_when_build_succeeds": true, + "merge_when_pipeline_succeeds": true, "merge_status": "can_be_merged", "subscribed" : true, "sha": "8888888888888888888888888888888888888888", @@ -326,7 +326,7 @@ POST /projects/:id/merge_requests "updated_at": "2015-02-02T19:49:26.013Z", "due_date": null }, - "merge_when_build_succeeds": true, + "merge_when_pipeline_succeeds": true, "merge_status": "can_be_merged", "subscribed" : true, "sha": "8888888888888888888888888888888888888888", @@ -403,7 +403,7 @@ Must include at least one non-required attribute from above. "updated_at": "2015-02-02T19:49:26.013Z", "due_date": null }, - "merge_when_build_succeeds": true, + "merge_when_pipeline_succeeds": true, "merge_status": "can_be_merged", "subscribed" : true, "sha": "8888888888888888888888888888888888888888", @@ -429,7 +429,7 @@ DELETE /projects/:id/merge_requests/:merge_request_id | `merge_request_id` | integer | yes | The ID of a project's merge request | ```bash -curl --request DELETE --header "PRIVATE-TOKEN: 9koXpg98eAheJpvBs5tK" https://gitlab.example.com/api/v3/projects/4/merge_requests/85 +curl --request DELETE --header "PRIVATE-TOKEN: 9koXpg98eAheJpvBs5tK" https://gitlab.example.com/api/v4/projects/4/merge_requests/85 ``` ## Accept MR @@ -455,7 +455,7 @@ Parameters: - `merge_request_id` (required) - ID of MR - `merge_commit_message` (optional) - Custom merge commit message - `should_remove_source_branch` (optional) - if `true` removes the source branch -- `merge_when_build_succeeds` (optional) - if `true` the MR is merged when the build succeeds +- `merge_when_pipeline_succeeds` (optional) - if `true` the MR is merged when the pipeline succeeds - `sha` (optional) - if present, then this SHA must match the HEAD of the source branch, otherwise the merge will fail ```json @@ -501,7 +501,7 @@ Parameters: "updated_at": "2015-02-02T19:49:26.013Z", "due_date": null }, - "merge_when_build_succeeds": true, + "merge_when_pipeline_succeeds": true, "merge_status": "can_be_merged", "subscribed" : true, "sha": "8888888888888888888888888888888888888888", @@ -519,9 +519,9 @@ If you don't have permissions to accept this merge request - you'll get a `401` If the merge request is already merged or closed - you get `405` and error message 'Method Not Allowed' -In case the merge request is not set to be merged when the build succeeds, you'll also get a `406` error. +In case the merge request is not set to be merged when the pipeline succeeds, you'll also get a `406` error. ``` -PUT /projects/:id/merge_requests/:merge_request_id/cancel_merge_when_build_succeeds +PUT /projects/:id/merge_requests/:merge_request_id/cancel_merge_when_pipeline_succeeds ``` Parameters: @@ -571,7 +571,7 @@ Parameters: "updated_at": "2015-02-02T19:49:26.013Z", "due_date": null }, - "merge_when_build_succeeds": true, + "merge_when_pipeline_succeeds": true, "merge_status": "can_be_merged", "subscribed" : true, "sha": "8888888888888888888888888888888888888888", @@ -601,7 +601,7 @@ GET /projects/:id/merge_requests/:merge_request_id/closes_issues | `merge_request_id` | integer | yes | The ID of the merge request | ```bash -curl --header "PRIVATE-TOKEN: 9koXpg98eAheJpvBs5tK" https://gitlab.example.com/api/v3/projects/76/merge_requests/1/closes_issues +curl --header "PRIVATE-TOKEN: 9koXpg98eAheJpvBs5tK" https://gitlab.example.com/api/v4/projects/76/merge_requests/1/closes_issues ``` Example response when the GitLab issue tracker is used: @@ -676,7 +676,7 @@ POST /projects/:id/merge_requests/:merge_request_id/subscribe | `merge_request_id` | integer | yes | The ID of the merge request | ```bash -curl --request POST --header "PRIVATE-TOKEN: 9koXpg98eAheJpvBs5tK" https://gitlab.example.com/api/v3/projects/5/merge_requests/17/subscribe +curl --request POST --header "PRIVATE-TOKEN: 9koXpg98eAheJpvBs5tK" https://gitlab.example.com/api/v4/projects/5/merge_requests/17/subscribe ``` Example response: @@ -726,7 +726,7 @@ Example response: "updated_at": "2016-04-05T21:41:40.905Z", "due_date": null }, - "merge_when_build_succeeds": false, + "merge_when_pipeline_succeeds": false, "merge_status": "cannot_be_merged", "subscribed": true, "sha": "8888888888888888888888888888888888888888", @@ -750,7 +750,7 @@ POST /projects/:id/merge_requests/:merge_request_id/unsubscribe | `merge_request_id` | integer | yes | The ID of the merge request | ```bash -curl --request POST --header "PRIVATE-TOKEN: 9koXpg98eAheJpvBs5tK" https://gitlab.example.com/api/v3/projects/5/merge_requests/17/unsubscribe +curl --request POST --header "PRIVATE-TOKEN: 9koXpg98eAheJpvBs5tK" https://gitlab.example.com/api/v4/projects/5/merge_requests/17/unsubscribe ``` Example response: @@ -800,7 +800,7 @@ Example response: "updated_at": "2016-04-05T21:41:40.905Z", "due_date": null }, - "merge_when_build_succeeds": false, + "merge_when_pipeline_succeeds": false, "merge_status": "cannot_be_merged", "subscribed": false, "sha": "8888888888888888888888888888888888888888", @@ -824,7 +824,7 @@ POST /projects/:id/merge_requests/:merge_request_id/todo | `merge_request_id` | integer | yes | The ID of the merge request | ```bash -curl --request POST --header "PRIVATE-TOKEN: 9koXpg98eAheJpvBs5tK" https://gitlab.example.com/api/v3/projects/5/merge_requests/27/todo +curl --request POST --header "PRIVATE-TOKEN: 9koXpg98eAheJpvBs5tK" https://gitlab.example.com/api/v4/projects/5/merge_requests/27/todo ``` Example response: @@ -893,7 +893,7 @@ Example response: "updated_at": "2016-06-17T07:47:33.840Z", "due_date": null }, - "merge_when_build_succeeds": false, + "merge_when_pipeline_succeeds": false, "merge_status": "unchecked", "subscribed": true, "sha": "8888888888888888888888888888888888888888", @@ -924,7 +924,7 @@ GET /projects/:id/merge_requests/:merge_request_id/versions | `merge_request_id` | integer | yes | The ID of the merge request | ```bash -curl --header "PRIVATE-TOKEN: 9koXpg98eAheJpvBs5tK" https://gitlab.example.com/api/v3/projects/1/merge_requests/1/versions +curl --header "PRIVATE-TOKEN: 9koXpg98eAheJpvBs5tK" https://gitlab.example.com/api/v4/projects/1/merge_requests/1/versions ``` Example response: @@ -966,7 +966,7 @@ GET /projects/:id/merge_requests/:merge_request_id/versions/:version_id | `version_id` | integer | yes | The ID of the merge request diff version | ```bash -curl --header "PRIVATE-TOKEN: 9koXpg98eAheJpvBs5tK" https://gitlab.example.com/api/v3/projects/1/merge_requests/1/versions/1 +curl --header "PRIVATE-TOKEN: 9koXpg98eAheJpvBs5tK" https://gitlab.example.com/api/v4/projects/1/merge_requests/1/versions/1 ``` Example response: @@ -1033,7 +1033,7 @@ POST /projects/:id/merge_requests/:merge_request_id/time_estimate | `duration` | string | yes | The duration in human format. e.g: 3h30m | ```bash -curl --request POST --header "PRIVATE-TOKEN: 9koXpg98eAheJpvBs5tK" https://gitlab.example.com/api/v3/projects/5/merge_requests/93/time_estimate?duration=3h30m +curl --request POST --header "PRIVATE-TOKEN: 9koXpg98eAheJpvBs5tK" https://gitlab.example.com/api/v4/projects/5/merge_requests/93/time_estimate?duration=3h30m ``` Example response: @@ -1061,7 +1061,7 @@ POST /projects/:id/merge_requests/:merge_request_id/reset_time_estimate | `merge_request_id` | integer | yes | The ID of a project's merge_request | ```bash -curl --request POST --header "PRIVATE-TOKEN: 9koXpg98eAheJpvBs5tK" https://gitlab.example.com/api/v3/projects/5/merge_requests/93/reset_time_estimate +curl --request POST --header "PRIVATE-TOKEN: 9koXpg98eAheJpvBs5tK" https://gitlab.example.com/api/v4/projects/5/merge_requests/93/reset_time_estimate ``` Example response: @@ -1090,7 +1090,7 @@ POST /projects/:id/merge_requests/:merge_request_id/add_spent_time | `duration` | string | yes | The duration in human format. e.g: 3h30m | ```bash -curl --request POST --header "PRIVATE-TOKEN: 9koXpg98eAheJpvBs5tK" https://gitlab.example.com/api/v3/projects/5/merge_requests/93/add_spent_time?duration=1h +curl --request POST --header "PRIVATE-TOKEN: 9koXpg98eAheJpvBs5tK" https://gitlab.example.com/api/v4/projects/5/merge_requests/93/add_spent_time?duration=1h ``` Example response: @@ -1118,7 +1118,7 @@ POST /projects/:id/merge_requests/:merge_request_id/reset_spent_time | `merge_request_id` | integer | yes | The ID of a project's merge_request | ```bash -curl --request POST --header "PRIVATE-TOKEN: 9koXpg98eAheJpvBs5tK" https://gitlab.example.com/api/v3/projects/5/merge_requests/93/reset_spent_time +curl --request POST --header "PRIVATE-TOKEN: 9koXpg98eAheJpvBs5tK" https://gitlab.example.com/api/v4/projects/5/merge_requests/93/reset_spent_time ``` Example response: @@ -1144,7 +1144,7 @@ GET /projects/:id/merge_requests/:merge_request_id/time_stats | `merge_request_id` | integer | yes | The ID of a project's merge request | ```bash -curl --request GET --header "PRIVATE-TOKEN: 9koXpg98eAheJpvBs5tK" https://gitlab.example.com/api/v3/projects/5/merge_requests/93/time_stats +curl --request GET --header "PRIVATE-TOKEN: 9koXpg98eAheJpvBs5tK" https://gitlab.example.com/api/v4/projects/5/merge_requests/93/time_stats ``` Example response: diff --git a/doc/api/milestones.md b/doc/api/milestones.md index bf7dcc008e9..3c86357a6c3 100644 --- a/doc/api/milestones.md +++ b/doc/api/milestones.md @@ -6,10 +6,11 @@ Returns a list of project milestones. ``` GET /projects/:id/milestones -GET /projects/:id/milestones?iid=42 -GET /projects/:id/milestones?iid[]=42&iid[]=43 +GET /projects/:id/milestones?iids=42 +GET /projects/:id/milestones?iids[]=42&iids[]=43 GET /projects/:id/milestones?state=active GET /projects/:id/milestones?state=closed +GET /projects/:id/milestones?search=version ``` Parameters: @@ -17,11 +18,12 @@ Parameters: | Attribute | Type | Required | Description | | --------- | ---- | -------- | ----------- | | `id` | integer | yes | The ID of a project | -| `iid` | Array[integer] | optional | Return only the milestone having the given `iid` | -| `state` | string | optional | Return only `active` or `closed` milestones` | +| `iids` | Array[integer] | optional | Return only the milestones having the given `iids` | +| `state` | string | optional | Return only `active` or `closed` milestones` | +| `search` | string | optional | Return only milestones with a title or description matching the provided string | ```bash -curl --header "PRIVATE-TOKEN: 9koXpg98eAheJpvBs5tK" https://gitlab.example.com/api/v3/projects/5/milestones +curl --header "PRIVATE-TOKEN: 9koXpg98eAheJpvBs5tK" https://gitlab.example.com/api/v4/projects/5/milestones ``` Example Response: @@ -115,4 +117,4 @@ GET /projects/:id/milestones/:milestone_id/merge_requests Parameters: - `id` (required) - The ID of a project -- `milestone_id` (required) - The ID of a project milestone
\ No newline at end of file +- `milestone_id` (required) - The ID of a project milestone diff --git a/doc/api/namespaces.md b/doc/api/namespaces.md index 1d97b5de688..eef06d5f324 100644 --- a/doc/api/namespaces.md +++ b/doc/api/namespaces.md @@ -19,7 +19,7 @@ GET /namespaces Example request: ```bash -curl --header "PRIVATE-TOKEN: 9koXpg98eAheJpvBs5tK" https://gitlab.example.com/api/v3/namespaces +curl --header "PRIVATE-TOKEN: 9koXpg98eAheJpvBs5tK" https://gitlab.example.com/api/v4/namespaces ``` Example response: @@ -60,7 +60,7 @@ GET /namespaces?search=foobar Example request: ```bash -curl --header "PRIVATE-TOKEN: 9koXpg98eAheJpvBs5tK" https://gitlab.example.com/api/v3/namespaces?search=twitter +curl --header "PRIVATE-TOKEN: 9koXpg98eAheJpvBs5tK" https://gitlab.example.com/api/v4/namespaces?search=twitter ``` Example response: diff --git a/doc/api/notes.md b/doc/api/notes.md index 7dc1fd930de..6ef06b2c2e9 100644 --- a/doc/api/notes.md +++ b/doc/api/notes.md @@ -120,7 +120,7 @@ Parameters: | `note_id` | integer | yes | The ID of a note | ```bash -curl --request DELETE --header "PRIVATE-TOKEN: 9koXpg98eAheJpvBs5tK" https://gitlab.example.com/api/v3/projects/5/issues/11/notes/636 +curl --request DELETE --header "PRIVATE-TOKEN: 9koXpg98eAheJpvBs5tK" https://gitlab.example.com/api/v4/projects/5/issues/11/notes/636 ``` ## Snippets @@ -218,7 +218,7 @@ Parameters: | `note_id` | integer | yes | The ID of a note | ```bash -curl --request DELETE --header "PRIVATE-TOKEN: 9koXpg98eAheJpvBs5tK" https://gitlab.example.com/api/v3/projects/5/snippets/52/notes/1659 +curl --request DELETE --header "PRIVATE-TOKEN: 9koXpg98eAheJpvBs5tK" https://gitlab.example.com/api/v4/projects/5/snippets/52/notes/1659 ``` ## Merge Requests @@ -319,5 +319,5 @@ Parameters: | `note_id` | integer | yes | The ID of a note | ```bash -curl --request DELETE --header "PRIVATE-TOKEN: 9koXpg98eAheJpvBs5tK" https://gitlab.example.com/api/v3/projects/5/merge_requests/7/notes/1602 +curl --request DELETE --header "PRIVATE-TOKEN: 9koXpg98eAheJpvBs5tK" https://gitlab.example.com/api/v4/projects/5/merge_requests/7/notes/1602 ``` diff --git a/doc/api/notification_settings.md b/doc/api/notification_settings.md index aea1c12a392..43047917f77 100644 --- a/doc/api/notification_settings.md +++ b/doc/api/notification_settings.md @@ -41,7 +41,7 @@ GET /notification_settings ``` ```bash -curl --header "PRIVATE-TOKEN: 9koXpg98eAheJpvBs5tK" https://gitlab.example.com/api/v3/notification_settings +curl --header "PRIVATE-TOKEN: 9koXpg98eAheJpvBs5tK" https://gitlab.example.com/api/v4/notification_settings ``` Example response: @@ -62,7 +62,7 @@ PUT /notification_settings ``` ```bash -curl --request PUT --header "PRIVATE-TOKEN: 9koXpg98eAheJpvBs5tK" https://gitlab.example.com/api/v3/notification_settings?level=watch +curl --request PUT --header "PRIVATE-TOKEN: 9koXpg98eAheJpvBs5tK" https://gitlab.example.com/api/v4/notification_settings?level=watch ``` | Attribute | Type | Required | Description | @@ -101,8 +101,8 @@ GET /projects/:id/notification_settings ``` ```bash -curl --header "PRIVATE-TOKEN: 9koXpg98eAheJpvBs5tK" https://gitlab.example.com/api/v3/groups/5/notification_settings -curl --header "PRIVATE-TOKEN: 9koXpg98eAheJpvBs5tK" https://gitlab.example.com/api/v3/projects/8/notification_settings +curl --header "PRIVATE-TOKEN: 9koXpg98eAheJpvBs5tK" https://gitlab.example.com/api/v4/groups/5/notification_settings +curl --header "PRIVATE-TOKEN: 9koXpg98eAheJpvBs5tK" https://gitlab.example.com/api/v4/projects/8/notification_settings ``` | Attribute | Type | Required | Description | @@ -127,8 +127,8 @@ PUT /projects/:id/notification_settings ``` ```bash -curl --request PUT --header "PRIVATE-TOKEN: 9koXpg98eAheJpvBs5tK" https://gitlab.example.com/api/v3/groups/5/notification_settings?level=watch -curl --request PUT --header "PRIVATE-TOKEN: 9koXpg98eAheJpvBs5tK" https://gitlab.example.com/api/v3/projects/8/notification_settings?level=custom&new_note=true +curl --request PUT --header "PRIVATE-TOKEN: 9koXpg98eAheJpvBs5tK" https://gitlab.example.com/api/v4/groups/5/notification_settings?level=watch +curl --request PUT --header "PRIVATE-TOKEN: 9koXpg98eAheJpvBs5tK" https://gitlab.example.com/api/v4/projects/8/notification_settings?level=custom&new_note=true ``` | Attribute | Type | Required | Description | diff --git a/doc/api/oauth2.md b/doc/api/oauth2.md index eab532af594..46fe64d382e 100644 --- a/doc/api/oauth2.md +++ b/doc/api/oauth2.md @@ -77,13 +77,13 @@ You can now make requests to the API with the access token returned. The access token allows you to make requests to the API on a behalf of a user. ``` -GET https://gitlab.example.com/api/v3/user?access_token=OAUTH-TOKEN +GET https://gitlab.example.com/api/v4/user?access_token=OAUTH-TOKEN ``` Or you can put the token to the Authorization header: ``` -curl --header "Authorization: Bearer OAUTH-TOKEN" https://gitlab.example.com/api/v3/user +curl --header "Authorization: Bearer OAUTH-TOKEN" https://gitlab.example.com/api/v4/user ``` ## Resource Owner Password Credentials diff --git a/doc/api/pipelines.md b/doc/api/pipelines.md index f3c9827f742..9d6f3ea41d9 100644 --- a/doc/api/pipelines.md +++ b/doc/api/pipelines.md @@ -13,7 +13,7 @@ GET /projects/:id/pipelines | `id` | integer | yes | The ID of a project | ``` -curl --header "PRIVATE-TOKEN: 9koXpg98eAheJpvBs5tK" "https://gitlab.example.com/api/v3/projects/1/pipelines" +curl --header "PRIVATE-TOKEN: 9koXpg98eAheJpvBs5tK" "https://gitlab.example.com/api/v4/projects/1/pipelines" ``` Example of response @@ -85,7 +85,7 @@ GET /projects/:id/pipelines/:pipeline_id | `pipeline_id` | integer | yes | The ID of a pipeline | ``` -curl --header "PRIVATE-TOKEN: 9koXpg98eAheJpvBs5tK" "https://gitlab.example.com/api/v3/projects/1/pipeline/46" +curl --header "PRIVATE-TOKEN: 9koXpg98eAheJpvBs5tK" "https://gitlab.example.com/api/v4/projects/1/pipeline/46" ``` Example of response @@ -131,7 +131,7 @@ POST /projects/:id/pipeline | `ref` | string | yes | Reference to commit | ``` -curl --request POST --header "PRIVATE-TOKEN: 9koXpg98eAheJpvBs5tK" "https://gitlab.example.com/api/v3/projects/1/pipeline?ref=master" +curl --request POST --header "PRIVATE-TOKEN: 9koXpg98eAheJpvBs5tK" "https://gitlab.example.com/api/v4/projects/1/pipeline?ref=master" ``` Example of response @@ -177,7 +177,7 @@ POST /projects/:id/pipelines/:pipeline_id/retry | `pipeline_id` | integer | yes | The ID of a pipeline | ``` -curl --header "PRIVATE-TOKEN: 9koXpg98eAheJpvBs5tK" "https://gitlab.example.com/api/v3/projects/1/pipelines/46/retry" +curl --header "PRIVATE-TOKEN: 9koXpg98eAheJpvBs5tK" "https://gitlab.example.com/api/v4/projects/1/pipelines/46/retry" ``` Response: @@ -223,7 +223,7 @@ POST /projects/:id/pipelines/:pipeline_id/cancel | `pipeline_id` | integer | yes | The ID of a pipeline | ``` -curl --header "PRIVATE-TOKEN: 9koXpg98eAheJpvBs5tK" "https://gitlab.example.com/api/v3/projects/1/pipelines/46/cancel" +curl --header "PRIVATE-TOKEN: 9koXpg98eAheJpvBs5tK" "https://gitlab.example.com/api/v4/projects/1/pipelines/46/cancel" ``` Response: diff --git a/doc/api/project_snippets.md b/doc/api/project_snippets.md index 404876f6237..4f6f561b83e 100644 --- a/doc/api/project_snippets.md +++ b/doc/api/project_snippets.md @@ -3,15 +3,15 @@ ### Snippet visibility level Snippets in GitLab can be either private, internal or public. -You can set it with the `visibility_level` field in the snippet. +You can set it with the `visibility` field in the snippet. Constants for snippet visibility levels are: -| Visibility | visibility_level | Description | -| ---------- | ---------------- | ----------- | -| Private | `0` | The snippet is visible only the snippet creator | -| Internal | `10` | The snippet is visible for any logged in user | -| Public | `20` | The snippet can be accessed without any authentication | +| visibility | Description | +| ---------- | ----------- | +| `private` | The snippet is visible only the snippet creator | +| `internal` | The snippet is visible for any logged in user | +| `public` | The snippet can be accessed without any authentication | ## List snippets @@ -71,7 +71,7 @@ Parameters: - `title` (required) - The title of a snippet - `file_name` (required) - The name of a snippet file - `code` (required) - The content of a snippet -- `visibility_level` (required) - The snippet's visibility +- `visibility` (required) - The snippet's visibility ## Update snippet @@ -88,7 +88,7 @@ Parameters: - `title` (optional) - The title of a snippet - `file_name` (optional) - The name of a snippet file - `code` (optional) - The content of a snippet -- `visibility_level` (optional) - The snippet's visibility +- `visibility` (optional) - The snippet's visibility ## Delete snippet diff --git a/doc/api/projects.md b/doc/api/projects.md index 5de23908b42..f6eabc1f663 100644 --- a/doc/api/projects.md +++ b/doc/api/projects.md @@ -4,17 +4,17 @@ ### Project visibility level Project in GitLab has be either private, internal or public. -You can determine it by `visibility_level` field in project. +You can determine it by `visibility` field in project. Constants for project visibility levels are next: -* Private. `visibility_level` is `0`. +* `private`: Project access must be granted explicitly for each user. -* Internal. `visibility_level` is `10`. +* `internal`: The project can be cloned by any logged in user. -* Public. `visibility_level` is `20`. +* `public`: The project can be cloned without any authentication. @@ -45,8 +45,7 @@ Parameters: "id": 4, "description": null, "default_branch": "master", - "public": false, - "visibility_level": 0, + "visibility": "private", "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", @@ -88,7 +87,7 @@ Parameters: "runners_token": "b8547b1dc37721d05889db52fa2f02", "public_builds": true, "shared_with_groups": [], - "only_allow_merge_if_build_succeeds": false, + "only_allow_merge_if_pipeline_succeeds": false, "only_allow_merge_if_all_discussions_are_resolved": false, "request_access_enabled": false }, @@ -96,8 +95,7 @@ Parameters: "id": 6, "description": null, "default_branch": "master", - "public": false, - "visibility_level": 0, + "visibility": "private", "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", @@ -149,7 +147,7 @@ Parameters: "runners_token": "b8547b1dc37721d05889db52fa2f02", "public_builds": true, "shared_with_groups": [], - "only_allow_merge_if_build_succeeds": false, + "only_allow_merge_if_pipeline_succeeds": false, "only_allow_merge_if_all_discussions_are_resolved": false, "request_access_enabled": false } @@ -177,8 +175,7 @@ Parameters: "id": 3, "description": null, "default_branch": "master", - "public": false, - "visibility_level": 0, + "visibility": "private", "ssh_url_to_repo": "git@example.com:diaspora/diaspora-project-site.git", "http_url_to_repo": "http://example.com/diaspora/diaspora-project-site.git", "web_url": "http://example.com/diaspora/diaspora-project-site", @@ -241,7 +238,7 @@ Parameters: "group_access_level": 10 } ], - "only_allow_merge_if_build_succeeds": false, + "only_allow_merge_if_pipeline_succeeds": false, "only_allow_merge_if_all_discussions_are_resolved": false, "request_access_enabled": false } @@ -446,10 +443,10 @@ Parameters: | `snippets_enabled` | boolean | no | Enable snippets for this project | | `container_registry_enabled` | boolean | no | Enable container registry for this project | | `shared_runners_enabled` | boolean | no | Enable shared runners for this project | -| `visibility_level` | integer | no | See [project visibility level](#project-visibility-level) | +| `visibility` | String | no | See [project visibility level](#project-visibility-level) | | `import_url` | string | no | URL to import repository from | | `public_builds` | boolean | no | If `true`, builds can be viewed by non-project-members | -| `only_allow_merge_if_build_succeeds` | boolean | no | Set whether merge requests can only be merged with successful builds | +| `only_allow_merge_if_pipeline_succeeds` | boolean | no | Set whether merge requests can only be merged with successful builds | | `only_allow_merge_if_all_discussions_are_resolved` | boolean | no | Set whether merge requests can only be merged when all the discussions are resolved | | `lfs_enabled` | boolean | no | Enable LFS | | `request_access_enabled` | boolean | no | Allow users to request member access | @@ -479,10 +476,10 @@ Parameters: | `snippets_enabled` | boolean | no | Enable snippets for this project | | `container_registry_enabled` | boolean | no | Enable container registry for this project | | `shared_runners_enabled` | boolean | no | Enable shared runners for this project | -| `visibility_level` | integer | no | See [project visibility level](#project-visibility-level) | +| `visibility` | string | no | See [project visibility level](#project-visibility-level) | | `import_url` | string | no | URL to import repository from | | `public_builds` | boolean | no | If `true`, builds can be viewed by non-project-members | -| `only_allow_merge_if_build_succeeds` | boolean | no | Set whether merge requests can only be merged with successful builds | +| `only_allow_merge_if_pipeline_succeeds` | boolean | no | Set whether merge requests can only be merged with successful builds | | `only_allow_merge_if_all_discussions_are_resolved` | boolean | no | Set whether merge requests can only be merged when all the discussions are resolved | | `lfs_enabled` | boolean | no | Enable LFS | | `request_access_enabled` | boolean | no | Allow users to request member access | @@ -511,10 +508,10 @@ Parameters: | `snippets_enabled` | boolean | no | Enable snippets for this project | | `container_registry_enabled` | boolean | no | Enable container registry for this project | | `shared_runners_enabled` | boolean | no | Enable shared runners for this project | -| `visibility_level` | integer | no | See [project visibility level](#project-visibility-level) | +| `visibility` | string | no | See [project visibility level](#project-visibility-level) | | `import_url` | string | no | URL to import repository from | | `public_builds` | boolean | no | If `true`, builds can be viewed by non-project-members | -| `only_allow_merge_if_build_succeeds` | boolean | no | Set whether merge requests can only be merged with successful builds | +| `only_allow_merge_if_pipeline_succeeds` | boolean | no | Set whether merge requests can only be merged with successful builds | | `only_allow_merge_if_all_discussions_are_resolved` | boolean | no | Set whether merge requests can only be merged when all the discussions are resolved | | `lfs_enabled` | boolean | no | Enable LFS | | `request_access_enabled` | boolean | no | Allow users to request member access | @@ -549,7 +546,7 @@ Parameters: | `id` | integer/string | yes | The ID or NAMESPACE/PROJECT_NAME of the project | ```bash -curl --request POST --header "PRIVATE-TOKEN: 9koXpg98eAheJpvBs5tK" "https://gitlab.example.com/api/v3/projects/5/star" +curl --request POST --header "PRIVATE-TOKEN: 9koXpg98eAheJpvBs5tK" "https://gitlab.example.com/api/v4/projects/5/star" ``` Example response: @@ -559,8 +556,7 @@ Example response: "id": 3, "description": null, "default_branch": "master", - "public": false, - "visibility_level": 10, + "visibility": "internal", "ssh_url_to_repo": "git@example.com:diaspora/diaspora-project-site.git", "http_url_to_repo": "http://example.com/diaspora/diaspora-project-site.git", "web_url": "http://example.com/diaspora/diaspora-project-site", @@ -596,7 +592,7 @@ Example response: "star_count": 1, "public_builds": true, "shared_with_groups": [], - "only_allow_merge_if_build_succeeds": false, + "only_allow_merge_if_pipeline_succeeds": false, "only_allow_merge_if_all_discussions_are_resolved": false, "request_access_enabled": false } @@ -615,7 +611,7 @@ POST /projects/:id/unstar | `id` | integer/string | yes | The ID of the project or NAMESPACE/PROJECT_NAME | ```bash -curl --request POST --header "PRIVATE-TOKEN: 9koXpg98eAheJpvBs5tK" "https://gitlab.example.com/api/v3/projects/5/unstar" +curl --request POST --header "PRIVATE-TOKEN: 9koXpg98eAheJpvBs5tK" "https://gitlab.example.com/api/v4/projects/5/unstar" ``` Example response: @@ -625,8 +621,7 @@ Example response: "id": 3, "description": null, "default_branch": "master", - "public": false, - "visibility_level": 10, + "visibility": "internal", "ssh_url_to_repo": "git@example.com:diaspora/diaspora-project-site.git", "http_url_to_repo": "http://example.com/diaspora/diaspora-project-site.git", "web_url": "http://example.com/diaspora/diaspora-project-site", @@ -662,7 +657,7 @@ Example response: "star_count": 0, "public_builds": true, "shared_with_groups": [], - "only_allow_merge_if_build_succeeds": false, + "only_allow_merge_if_pipeline_succeeds": false, "only_allow_merge_if_all_discussions_are_resolved": false, "request_access_enabled": false } @@ -682,7 +677,7 @@ POST /projects/:id/archive | `id` | integer/string | yes | The ID of the project or NAMESPACE/PROJECT_NAME | ```bash -curl --request POST --header "PRIVATE-TOKEN: 9koXpg98eAheJpvBs5tK" "https://gitlab.example.com/api/v3/projects/5/archive" +curl --request POST --header "PRIVATE-TOKEN: 9koXpg98eAheJpvBs5tK" "https://gitlab.example.com/api/v4/projects/5/archive" ``` Example response: @@ -692,8 +687,7 @@ Example response: "id": 3, "description": null, "default_branch": "master", - "public": false, - "visibility_level": 0, + "visibility": "private", "ssh_url_to_repo": "git@example.com:diaspora/diaspora-project-site.git", "http_url_to_repo": "http://example.com/diaspora/diaspora-project-site.git", "web_url": "http://example.com/diaspora/diaspora-project-site", @@ -745,7 +739,7 @@ Example response: "runners_token": "b8bc4a7a29eb76ea83cf79e4908c2b", "public_builds": true, "shared_with_groups": [], - "only_allow_merge_if_build_succeeds": false, + "only_allow_merge_if_pipeline_succeeds": false, "only_allow_merge_if_all_discussions_are_resolved": false, "request_access_enabled": false } @@ -765,7 +759,7 @@ POST /projects/:id/unarchive | `id` | integer/string | yes | The ID of the project or NAMESPACE/PROJECT_NAME | ```bash -curl --request POST --header "PRIVATE-TOKEN: 9koXpg98eAheJpvBs5tK" "https://gitlab.example.com/api/v3/projects/5/unarchive" +curl --request POST --header "PRIVATE-TOKEN: 9koXpg98eAheJpvBs5tK" "https://gitlab.example.com/api/v4/projects/5/unarchive" ``` Example response: @@ -775,8 +769,7 @@ Example response: "id": 3, "description": null, "default_branch": "master", - "public": false, - "visibility_level": 0, + "visibility": "private", "ssh_url_to_repo": "git@example.com:diaspora/diaspora-project-site.git", "http_url_to_repo": "http://example.com/diaspora/diaspora-project-site.git", "web_url": "http://example.com/diaspora/diaspora-project-site", @@ -828,7 +821,7 @@ Example response: "runners_token": "b8bc4a7a29eb76ea83cf79e4908c2b", "public_builds": true, "shared_with_groups": [], - "only_allow_merge_if_build_succeeds": false, + "only_allow_merge_if_pipeline_succeeds": false, "only_allow_merge_if_all_discussions_are_resolved": false, "request_access_enabled": false } @@ -914,7 +907,7 @@ Parameters: | `group_id` | integer | yes | The ID of the group | ```bash -curl --request DELETE --header "PRIVATE-TOKEN: 9koXpg98eAheJpvBs5tK" https://gitlab.example.com/api/v3/projects/5/share/17 +curl --request DELETE --header "PRIVATE-TOKEN: 9koXpg98eAheJpvBs5tK" https://gitlab.example.com/api/v4/projects/5/share/17 ``` ## Hooks diff --git a/doc/api/repositories.md b/doc/api/repositories.md index 727617f1ecc..ddd11bb2a14 100644 --- a/doc/api/repositories.md +++ b/doc/api/repositories.md @@ -5,6 +5,8 @@ Get a list of repository files and directories in a project. This endpoint can be accessed without authentication if the repository is publicly accessible. +This command provides essentially the same functionality as the `git ls-tree` command. For more information, see the section _Tree Objects_ in the [Git internals documentation](https://git-scm.com/book/en/v2/Git-Internals-Git-Objects/#_tree_objects). + ``` GET /projects/:id/repository/tree ``` diff --git a/doc/api/repository_files.md b/doc/api/repository_files.md index 677e209ccd9..ec56d0efa1c 100644 --- a/doc/api/repository_files.md +++ b/doc/api/repository_files.md @@ -15,7 +15,7 @@ GET /projects/:id/repository/files ``` ```bash -curl --request GET --header 'PRIVATE-TOKEN: 9koXpg98eAheJpvBs5tK' 'https://gitlab.example.com/api/v3/projects/13083/repository/files?file_path=app/models/key.rb&ref=master' +curl --request GET --header 'PRIVATE-TOKEN: 9koXpg98eAheJpvBs5tK' 'https://gitlab.example.com/api/v4/projects/13083/repository/files?file_path=app/models/key.rb&ref=master' ``` Example response: @@ -46,7 +46,7 @@ POST /projects/:id/repository/files ``` ```bash -curl --request POST --header 'PRIVATE-TOKEN: 9koXpg98eAheJpvBs5tK' 'https://gitlab.example.com/api/v3/projects/13083/repository/files?file_path=app/project.rb&branch=master&author_email=author%40example.com&author_name=Firstname%20Lastname&content=some%20content&commit_message=create%20a%20new%20file' +curl --request POST --header 'PRIVATE-TOKEN: 9koXpg98eAheJpvBs5tK' 'https://gitlab.example.com/api/v4/projects/13083/repository/files?file_path=app/project.rb&branch=master&author_email=author%40example.com&author_name=Firstname%20Lastname&content=some%20content&commit_message=create%20a%20new%20file' ``` Example response: @@ -75,7 +75,7 @@ PUT /projects/:id/repository/files ``` ```bash -curl --request PUT --header 'PRIVATE-TOKEN: 9koXpg98eAheJpvBs5tK' 'https://gitlab.example.com/api/v3/projects/13083/repository/files?file_path=app/project.rb&branch=master&author_email=author%40example.com&author_name=Firstname%20Lastname&content=some%20other%20content&commit_message=update%20file' +curl --request PUT --header 'PRIVATE-TOKEN: 9koXpg98eAheJpvBs5tK' 'https://gitlab.example.com/api/v4/projects/13083/repository/files?file_path=app/project.rb&branch=master&author_email=author%40example.com&author_name=Firstname%20Lastname&content=some%20other%20content&commit_message=update%20file' ``` Example response: @@ -113,7 +113,7 @@ DELETE /projects/:id/repository/files ``` ```bash -curl --request DELETE --header 'PRIVATE-TOKEN: 9koXpg98eAheJpvBs5tK' 'https://gitlab.example.com/api/v3/projects/13083/repository/files?file_path=app/project.rb&branch=master&author_email=author%40example.com&author_name=Firstname%20Lastname&commit_message=delete%20file' +curl --request DELETE --header 'PRIVATE-TOKEN: 9koXpg98eAheJpvBs5tK' 'https://gitlab.example.com/api/v4/projects/13083/repository/files?file_path=app/project.rb&branch=master&author_email=author%40example.com&author_name=Firstname%20Lastname&commit_message=delete%20file' ``` Example response: diff --git a/doc/api/runners.md b/doc/api/runners.md index 27d8e7640b2..46f882ce937 100644 --- a/doc/api/runners.md +++ b/doc/api/runners.md @@ -18,7 +18,7 @@ GET /runners?scope=active | `scope` | string | no | The scope of specific runners to show, one of: `active`, `paused`, `online`; showing all runners if none provided | ``` -curl --header "PRIVATE-TOKEN: 9koXpg98eAheJpvBs5tK" "https://gitlab.example.com/api/v3/runners" +curl --header "PRIVATE-TOKEN: 9koXpg98eAheJpvBs5tK" "https://gitlab.example.com/api/v4/runners" ``` Example response: @@ -57,7 +57,7 @@ GET /runners/all?scope=online | `scope` | string | no | The scope of runners to show, one of: `specific`, `shared`, `active`, `paused`, `online`; showing all runners if none provided | ``` -curl --header "PRIVATE-TOKEN: 9koXpg98eAheJpvBs5tK" "https://gitlab.example.com/api/v3/runners/all" +curl --header "PRIVATE-TOKEN: 9koXpg98eAheJpvBs5tK" "https://gitlab.example.com/api/v4/runners/all" ``` Example response: @@ -108,7 +108,7 @@ GET /runners/:id | `id` | integer | yes | The ID of a runner | ``` -curl --header "PRIVATE-TOKEN: 9koXpg98eAheJpvBs5tK" "https://gitlab.example.com/api/v3/runners/6" +curl --header "PRIVATE-TOKEN: 9koXpg98eAheJpvBs5tK" "https://gitlab.example.com/api/v4/runners/6" ``` Example response: @@ -158,7 +158,7 @@ PUT /runners/:id | `tag_list` | array | no | The list of tags for a runner; put array of tags, that should be finally assigned to a runner | ``` -curl --request PUT --header "PRIVATE-TOKEN: 9koXpg98eAheJpvBs5tK" "https://gitlab.example.com/api/v3/runners/6" --form "description=test-1-20150125-test" --form "tag_list=ruby,mysql,tag1,tag2" +curl --request PUT --header "PRIVATE-TOKEN: 9koXpg98eAheJpvBs5tK" "https://gitlab.example.com/api/v4/runners/6" --form "description=test-1-20150125-test" --form "tag_list=ruby,mysql,tag1,tag2" ``` Example response: @@ -207,7 +207,7 @@ DELETE /runners/:id | `id` | integer | yes | The ID of a runner | ``` -curl --request DELETE --header "PRIVATE-TOKEN: 9koXpg98eAheJpvBs5tK" "https://gitlab.example.com/api/v3/runners/6" +curl --request DELETE --header "PRIVATE-TOKEN: 9koXpg98eAheJpvBs5tK" "https://gitlab.example.com/api/v4/runners/6" ``` ## List project's runners @@ -225,7 +225,7 @@ GET /projects/:id/runners | `id` | integer | yes | The ID of a project | ``` -curl --header "PRIVATE-TOKEN: 9koXpg98eAheJpvBs5tK" "https://gitlab.example.com/api/v3/projects/9/runners" +curl --header "PRIVATE-TOKEN: 9koXpg98eAheJpvBs5tK" "https://gitlab.example.com/api/v4/projects/9/runners" ``` Example response: @@ -263,7 +263,7 @@ POST /projects/:id/runners | `runner_id` | integer | yes | The ID of a runner | ``` -curl --request POST --header "PRIVATE-TOKEN: 9koXpg98eAheJpvBs5tK" "https://gitlab.example.com/api/v3/projects/9/runners" --form "runner_id=9" +curl --request POST --header "PRIVATE-TOKEN: 9koXpg98eAheJpvBs5tK" "https://gitlab.example.com/api/v4/projects/9/runners" --form "runner_id=9" ``` Example response: @@ -294,5 +294,5 @@ DELETE /projects/:id/runners/:runner_id | `runner_id` | integer | yes | The ID of a runner | ``` -curl --request DELETE --header "PRIVATE-TOKEN: 9koXpg98eAheJpvBs5tK" "https://gitlab.example.com/api/v3/projects/9/runners/9" +curl --request DELETE --header "PRIVATE-TOKEN: 9koXpg98eAheJpvBs5tK" "https://gitlab.example.com/api/v4/projects/9/runners/9" ``` diff --git a/doc/api/session.md b/doc/api/session.md index d7809716fbe..056cc32597c 100644 --- a/doc/api/session.md +++ b/doc/api/session.md @@ -21,7 +21,7 @@ POST /session | `password` | string | yes | The password of the user | ```bash -curl --request POST "https://gitlab.example.com/api/v3/session?login=john_smith&password=strongpassw0rd" +curl --request POST "https://gitlab.example.com/api/v4/session?login=john_smith&password=strongpassw0rd" ``` Example response: diff --git a/doc/api/settings.md b/doc/api/settings.md index ca6b9347877..38a37cd920c 100644 --- a/doc/api/settings.md +++ b/doc/api/settings.md @@ -13,7 +13,7 @@ GET /application/settings ``` ```bash -curl --header "PRIVATE-TOKEN: 9koXpg98eAheJpvBs5tK" https://gitlab.example.com/api/v3/application/settings +curl --header "PRIVATE-TOKEN: 9koXpg98eAheJpvBs5tK" https://gitlab.example.com/api/v4/application/settings ``` Example response: @@ -32,12 +32,13 @@ Example response: "updated_at" : "2016-01-04T15:44:55.176Z", "session_expire_delay" : 10080, "home_page_url" : null, - "default_snippet_visibility" : 0, + "default_snippet_visibility" : "private", "domain_whitelist" : [], "domain_blacklist_enabled" : false, "domain_blacklist" : [], "created_at" : "2016-01-04T15:44:55.176Z", - "default_project_visibility" : 0, + "default_project_visibility" : "private", + "default_group_visibility" : "private", "gravatar_enabled" : true, "sign_in_text" : null, "container_registry_token_expire_delay": 5, @@ -66,11 +67,12 @@ PUT /application/settings | `sign_in_text` | string | no | Text on login page | | `home_page_url` | string | no | Redirect to this URL when not logged in | | `default_branch_protection` | integer | no | Determine if developers can push to master. Can take `0` _(not protected, both developers and masters can push new commits, force push or delete the branch)_, `1` _(partially protected, developers can push new commits, but cannot force push or delete the branch, masters can do anything)_ or `2` _(fully protected, developers cannot push new commits, force push or delete the branch, masters can do anything)_ as a parameter. Default is `2`. | -| `restricted_visibility_levels` | array of integers | no | Selected levels cannot be used by non-admin users for projects or snippets. Can take `0` _(Private)_, `1` _(Internal)_ and `2` _(Public)_ as a parameter. Default is null which means there is no restriction. | +| `restricted_visibility_levels` | array of strings | no | Selected levels cannot be used by non-admin users for projects or snippets. Can take `private`, `internal` and `public` as a parameter. Default is null which means there is no restriction. | | `max_attachment_size` | integer | no | Limit attachment size in MB | | `session_expire_delay` | integer | no | Session duration in minutes. GitLab restart is required to apply changes | -| `default_project_visibility` | integer | no | What visibility level new projects receive. Can take `0` _(Private)_, `1` _(Internal)_ and `2` _(Public)_ as a parameter. Default is `0`.| -| `default_snippet_visibility` | integer | no | What visibility level new snippets receive. Can take `0` _(Private)_, `1` _(Internal)_ and `2` _(Public)_ as a parameter. Default is `0`.| +| `default_project_visibility` | string | no | What visibility level new projects receive. Can take `private`, `internal` and `public` as a parameter. Default is `private`.| +| `default_snippet_visibility` | string | no | What visibility level new snippets receive. Can take `private`, `internal` and `public` as a parameter. Default is `private`.| +| `default_group_visibility` | string | no | What visibility level new groups receive. Can take `private`, `internal` and `public` as a parameter. Default is `private`.| | `domain_whitelist` | array of strings | no | Force people to use only corporate emails for sign-up. Default is null, meaning there is no restriction. | | `domain_blacklist_enabled` | boolean | no | Enable/disable the `domain_blacklist` | | `domain_blacklist` | array of strings | yes (if `domain_blacklist_enabled` is `true`) | People trying to sign-up with emails from this domain will not be allowed to do so. | @@ -88,7 +90,7 @@ PUT /application/settings | `terminal_max_session_time` | integer | no | Maximum time for web terminal websocket connection (in seconds). Set to 0 for unlimited time. | ```bash -curl --request PUT --header "PRIVATE-TOKEN: 9koXpg98eAheJpvBs5tK" https://gitlab.example.com/api/v3/application/settings?signup_enabled=false&default_project_visibility=1 +curl --request PUT --header "PRIVATE-TOKEN: 9koXpg98eAheJpvBs5tK" https://gitlab.example.com/api/v4/application/settings?signup_enabled=false&default_project_visibility=internal ``` Example response: @@ -108,8 +110,9 @@ Example response: "restricted_visibility_levels": [], "max_attachment_size": 10, "session_expire_delay": 10080, - "default_project_visibility": 1, - "default_snippet_visibility": 0, + "default_project_visibility": "internal", + "default_snippet_visibility": "private", + "default_group_visibility": "private", "domain_whitelist": [], "domain_blacklist_enabled" : false, "domain_blacklist" : [], diff --git a/doc/api/sidekiq_metrics.md b/doc/api/sidekiq_metrics.md index 1ae732d40d6..ea10a26bcd0 100644 --- a/doc/api/sidekiq_metrics.md +++ b/doc/api/sidekiq_metrics.md @@ -15,7 +15,7 @@ GET /sidekiq/queue_metrics ``` ```bash -curl --header "PRIVATE-TOKEN: 9koXpg98eAheJpvBs5tK" https://gitlab.example.com/api/v3/sidekiq/queue_metrics +curl --header "PRIVATE-TOKEN: 9koXpg98eAheJpvBs5tK" https://gitlab.example.com/api/v4/sidekiq/queue_metrics ``` Example response: @@ -40,7 +40,7 @@ GET /sidekiq/process_metrics ``` ```bash -curl --header "PRIVATE-TOKEN: 9koXpg98eAheJpvBs5tK" https://gitlab.example.com/api/v3/sidekiq/process_metrics +curl --header "PRIVATE-TOKEN: 9koXpg98eAheJpvBs5tK" https://gitlab.example.com/api/v4/sidekiq/process_metrics ``` Example response: @@ -82,7 +82,7 @@ GET /sidekiq/job_stats ``` ```bash -curl --header "PRIVATE-TOKEN: 9koXpg98eAheJpvBs5tK" https://gitlab.example.com/api/v3/sidekiq/job_stats +curl --header "PRIVATE-TOKEN: 9koXpg98eAheJpvBs5tK" https://gitlab.example.com/api/v4/sidekiq/job_stats ``` Example response: @@ -106,7 +106,7 @@ GET /sidekiq/compound_metrics ``` ```bash -curl --header "PRIVATE-TOKEN: 9koXpg98eAheJpvBs5tK" https://gitlab.example.com/api/v3/sidekiq/compound_metrics +curl --header "PRIVATE-TOKEN: 9koXpg98eAheJpvBs5tK" https://gitlab.example.com/api/v4/sidekiq/compound_metrics ``` Example response: diff --git a/doc/api/snippets.md b/doc/api/snippets.md index 5a5dc162ffe..e09d930698e 100644 --- a/doc/api/snippets.md +++ b/doc/api/snippets.md @@ -5,15 +5,15 @@ ### Snippet visibility level Snippets in GitLab can be either private, internal, or public. -You can set it with the `visibility_level` field in the snippet. +You can set it with the `visibility` field in the snippet. Constants for snippet visibility levels are: -| Visibility | Visibility level | Description | -| ---------- | ---------------- | ----------- | -| Private | `0` | The snippet is visible only to the snippet creator | -| Internal | `10` | The snippet is visible for any logged in user | -| Public | `20` | The snippet can be accessed without any authentication | +| Visibility | Description | +| ---------- | ----------- | +| `private` | The snippet is visible only to the snippet creator | +| `internal` | The snippet is visible for any logged in user | +| `public` | The snippet can be accessed without any authentication | ## List snippets @@ -38,7 +38,7 @@ Parameters: | `id` | Integer | yes | The ID of a snippet | ``` bash -curl --header "PRIVATE-TOKEN: 9koXpg98eAheJpvBs5tK" https://gitlab.example.com/api/v3/snippets/1 +curl --header "PRIVATE-TOKEN: 9koXpg98eAheJpvBs5tK" https://gitlab.example.com/api/v4/snippets/1 ``` Example response: @@ -78,11 +78,11 @@ Parameters: | `title` | String | yes | The title of a snippet | | `file_name` | String | yes | The name of a snippet file | | `content` | String | yes | The content of a snippet | -| `visibility_level` | Integer | yes | The snippet's visibility | +| `visibility` | String | yes | The snippet's visibility | ``` bash -curl --request POST --data '{"title": "This is a snippet", "content": "Hello world", "file_name": "test.txt", "visibility_level": 10 }' --header "PRIVATE-TOKEN: 9koXpg98eAheJpvBs5tK" https://gitlab.example.com/api/v3/snippets +curl --request POST --data '{"title": "This is a snippet", "content": "Hello world", "file_name": "test.txt", "visibility": "internal" }' --header "PRIVATE-TOKEN: 9koXpg98eAheJpvBs5tK" https://gitlab.example.com/api/v4/snippets ``` Example response: @@ -123,11 +123,11 @@ Parameters: | `title` | String | no | The title of a snippet | | `file_name` | String | no | The name of a snippet file | | `content` | String | no | The content of a snippet | -| `visibility_level` | Integer | no | The snippet's visibility | +| `visibility` | String | no | The snippet's visibility | ``` bash -curl --request PUT --header "PRIVATE-TOKEN: 9koXpg98eAheJpvBs5tK" --data '{"title": "foo", "content": "bar"}' https://gitlab.example.com/api/v3/snippets/1 +curl --request PUT --header "PRIVATE-TOKEN: 9koXpg98eAheJpvBs5tK" --data '{"title": "foo", "content": "bar"}' https://gitlab.example.com/api/v4/snippets/1 ``` Example response: @@ -154,7 +154,7 @@ Example response: ## Delete snippet -Deletes an existing snippet. +Deletes an existing snippet. ``` DELETE /snippets/:id @@ -168,7 +168,7 @@ Parameters: ``` -curl --request DELETE --header "PRIVATE-TOKEN: 9koXpg98eAheJpvBs5tK" "https://gitlab.example.com/api/v3/snippets/1" +curl --request DELETE --header "PRIVATE-TOKEN: 9koXpg98eAheJpvBs5tK" "https://gitlab.example.com/api/v4/snippets/1" ``` upon successful delete a `204 No content` HTTP code shall be expected, with no data, @@ -186,7 +186,7 @@ GET /snippets/public | `page` | Integer | no | the page to retrieve | ``` bash -curl --header "PRIVATE-TOKEN: 9koXpg98eAheJpvBs5tK" https://gitlab.example.com/api/v3/snippets/public?per_page=2&page=1 +curl --header "PRIVATE-TOKEN: 9koXpg98eAheJpvBs5tK" https://gitlab.example.com/api/v4/snippets/public?per_page=2&page=1 ``` Example response: @@ -229,4 +229,3 @@ Example response: } ] ``` - diff --git a/doc/api/system_hooks.md b/doc/api/system_hooks.md index a9edff799ac..bad380794c1 100644 --- a/doc/api/system_hooks.md +++ b/doc/api/system_hooks.md @@ -20,7 +20,7 @@ GET /hooks Example request: ```bash -curl --header "PRIVATE-TOKEN: 9koXpg98eAheJpvBs5tK" https://gitlab.example.com/api/v3/hooks +curl --header "PRIVATE-TOKEN: 9koXpg98eAheJpvBs5tK" https://gitlab.example.com/api/v4/hooks ``` Example response: @@ -59,7 +59,7 @@ POST /hooks Example request: ```bash -curl --request POST --header "PRIVATE-TOKEN: 9koXpg98eAheJpvBs5tK" "https://gitlab.example.com/api/v3/hooks?url=https://gitlab.example.com/hook" +curl --request POST --header "PRIVATE-TOKEN: 9koXpg98eAheJpvBs5tK" "https://gitlab.example.com/api/v4/hooks?url=https://gitlab.example.com/hook" ``` Example response: @@ -90,7 +90,7 @@ GET /hooks/:id Example request: ```bash -curl --header "PRIVATE-TOKEN: 9koXpg98eAheJpvBs5tK" https://gitlab.example.com/api/v3/hooks/2 +curl --header "PRIVATE-TOKEN: 9koXpg98eAheJpvBs5tK" https://gitlab.example.com/api/v4/hooks/2 ``` Example response: @@ -123,5 +123,5 @@ DELETE /hooks/:id Example request: ```bash -curl --request DELETE --header "PRIVATE-TOKEN: 9koXpg98eAheJpvBs5tK" https://gitlab.example.com/api/v3/hooks/2 +curl --request DELETE --header "PRIVATE-TOKEN: 9koXpg98eAheJpvBs5tK" https://gitlab.example.com/api/v4/hooks/2 ``` diff --git a/doc/api/tags.md b/doc/api/tags.md index abeb4bfb40e..bf350f024f5 100644 --- a/doc/api/tags.md +++ b/doc/api/tags.md @@ -26,7 +26,7 @@ Parameters: "committer_email": "jack@example.com", "id": "2695effb5807a22ff3d138d593fd856244e155e7", "message": "Initial commit", - "parents_ids": [ + "parent_ids": [ "2a4b78934375d7f53875269ffd4f45fd83a84ebe" ] }, @@ -57,7 +57,7 @@ Parameters: | `tag_name` | string | yes | The name of the tag | ```bash -curl --header "PRIVATE-TOKEN: 9koXpg98eAheJpvBs5tK" https://gitlab.example.com/api/v3/projects/5/repository/tags/v1.0.0 +curl --header "PRIVATE-TOKEN: 9koXpg98eAheJpvBs5tK" https://gitlab.example.com/api/v4/projects/5/repository/tags/v1.0.0 ``` Example Response: @@ -110,7 +110,7 @@ Parameters: "committer_email": "jack@example.com", "id": "2695effb5807a22ff3d138d593fd856244e155e7", "message": "Initial commit", - "parents_ids": [ + "parent_ids": [ "2a4b78934375d7f53875269ffd4f45fd83a84ebe" ] }, diff --git a/doc/api/templates/gitignores.md b/doc/api/templates/gitignores.md index 8235be92b12..3f2f4ed54e0 100644 --- a/doc/api/templates/gitignores.md +++ b/doc/api/templates/gitignores.md @@ -9,7 +9,7 @@ GET /templates/gitignores ``` ```bash -curl https://gitlab.example.com/api/v3/templates/gitignores +curl https://gitlab.example.com/api/v4/templates/gitignores ``` Example response: @@ -566,7 +566,7 @@ GET /templates/gitignores/:key | `key` | string | yes | The key of the gitignore template | ```bash -curl https://gitlab.example.com/api/v3/templates/gitignores/Ruby +curl https://gitlab.example.com/api/v4/templates/gitignores/Ruby ``` Example response: diff --git a/doc/api/templates/gitlab_ci_ymls.md b/doc/api/templates/gitlab_ci_ymls.md index e120016fbe6..27e8973da58 100644 --- a/doc/api/templates/gitlab_ci_ymls.md +++ b/doc/api/templates/gitlab_ci_ymls.md @@ -9,7 +9,7 @@ GET /templates/gitlab_ci_ymls ``` ```bash -curl https://gitlab.example.com/api/v3/templates/gitlab_ci_ymls +curl https://gitlab.example.com/api/v4/templates/gitlab_ci_ymls ``` Example response: @@ -107,7 +107,7 @@ GET /templates/gitlab_ci_ymls/:key | `key` | string | yes | The key of the GitLab CI YML template | ```bash -curl https://gitlab.example.com/api/v3/templates/gitlab_ci_ymls/Ruby +curl https://gitlab.example.com/api/v4/templates/gitlab_ci_ymls/Ruby ``` Example response: diff --git a/doc/api/templates/licenses.md b/doc/api/templates/licenses.md index ae7218cf1bd..33018f0c53f 100644 --- a/doc/api/templates/licenses.md +++ b/doc/api/templates/licenses.md @@ -13,7 +13,7 @@ GET /templates/licenses | `popular` | boolean | no | If passed, returns only popular licenses | ```bash -curl https://gitlab.example.com/api/v3/templates/licenses?popular=1 +curl https://gitlab.example.com/api/v4/templates/licenses?popular=1 ``` Example response: @@ -116,7 +116,7 @@ If you omit the `fullname` parameter but authenticate your request, the name of the authenticated user will be used to replace the copyright holder placeholder. ```bash -curl --header "PRIVATE-TOKEN: 9koXpg98eAheJpvBs5tK" https://gitlab.example.com/api/v3/templates/licenses/mit?project=My+Cool+Project +curl --header "PRIVATE-TOKEN: 9koXpg98eAheJpvBs5tK" https://gitlab.example.com/api/v4/templates/licenses/mit?project=My+Cool+Project ``` Example response: diff --git a/doc/api/todos.md b/doc/api/todos.md index a2fbbc7e1f8..77667a57195 100644 --- a/doc/api/todos.md +++ b/doc/api/todos.md @@ -22,7 +22,7 @@ Parameters: | `type` | string | no | The type of a todo. Can be either `Issue` or `MergeRequest` | ```bash -curl --header "PRIVATE-TOKEN: 9koXpg98eAheJpvBs5tK" https://gitlab.example.com/api/v3/todos +curl --header "PRIVATE-TOKEN: 9koXpg98eAheJpvBs5tK" https://gitlab.example.com/api/v4/todos ``` Example Response: @@ -92,7 +92,7 @@ Example Response: "updated_at": "2016-06-17T07:47:34.163Z", "due_date": null }, - "merge_when_build_succeeds": false, + "merge_when_pipeline_succeeds": false, "merge_status": "cannot_be_merged", "subscribed": true, "user_notes_count": 7 @@ -165,7 +165,7 @@ Example Response: "updated_at": "2016-06-17T07:47:34.163Z", "due_date": null }, - "merge_when_build_succeeds": false, + "merge_when_pipeline_succeeds": false, "merge_status": "cannot_be_merged", "subscribed": true, "user_notes_count": 7 @@ -194,7 +194,7 @@ Parameters: | `id` | integer | yes | The ID of a todo | ```bash -curl --request POST --header "PRIVATE-TOKEN: 9koXpg98eAheJpvBs5tK" https://gitlab.example.com/api/v3/todos/130/mark_as_done +curl --request POST --header "PRIVATE-TOKEN: 9koXpg98eAheJpvBs5tK" https://gitlab.example.com/api/v4/todos/130/mark_as_done ``` Example Response: @@ -263,7 +263,7 @@ Example Response: "updated_at": "2016-06-17T07:47:34.163Z", "due_date": null }, - "merge_when_build_succeeds": false, + "merge_when_pipeline_succeeds": false, "merge_status": "cannot_be_merged", "subscribed": true, "user_notes_count": 7 @@ -284,7 +284,7 @@ POST /todos/mark_as_done ``` ```bash -curl --request POST --header "PRIVATE-TOKEN: 9koXpg98eAheJpvBs5tK" https://gitlab.example.com/api/v3/todos/donmark_as_donee +curl --request POST --header "PRIVATE-TOKEN: 9koXpg98eAheJpvBs5tK" https://gitlab.example.com/api/v4/todos/donmark_as_donee ``` diff --git a/doc/api/users.md b/doc/api/users.md index d14548e8bbb..95f6bcfccb6 100644 --- a/doc/api/users.md +++ b/doc/api/users.md @@ -699,7 +699,7 @@ Parameters: | `id` | integer | yes | The ID of the user | ```bash -curl --header "PRIVATE-TOKEN: 9koXpg98eAheJpvBs5tK" https://gitlab.example.com/api/v3/users/:id/events +curl --header "PRIVATE-TOKEN: 9koXpg98eAheJpvBs5tK" https://gitlab.example.com/api/v4/users/:id/events ``` Example response: diff --git a/doc/api/v3_to_v4.md b/doc/api/v3_to_v4.md index c178e224cc5..cca58894476 100644 --- a/doc/api/v3_to_v4.md +++ b/doc/api/v3_to_v4.md @@ -30,6 +30,7 @@ changes are in V4: - Moved `DELETE /todos` to `POST /todos/mark_as_done` and `DELETE /todos/:todo_id` to `POST /todos/:todo_id/mark_as_done` [!9410](https://gitlab.com/gitlab-org/gitlab-ce/merge_requests/9410) - Endpoints `/projects/owned`, `/projects/visible`, `/projects/starred` & `/projects/all` are consolidated into `/projects` using query parameters [!8962](https://gitlab.com/gitlab-org/gitlab-ce/merge_requests/8962) - Return pagination headers for all endpoints that return an array [!8606](https://gitlab.com/gitlab-org/gitlab-ce/merge_requests/8606) +- Added `POST /environments/:environment_id/stop` to stop an environment [!8808](https://gitlab.com/gitlab-org/gitlab-ce/merge_requests/8808) - Removed `DELETE projects/:id/deploy_keys/:key_id/disable`. Use `DELETE projects/:id/deploy_keys/:key_id` instead [!9366](https://gitlab.com/gitlab-org/gitlab-ce/merge_requests/9366) - Moved `PUT /users/:id/(block|unblock)` to `POST /users/:id/(block|unblock)` [!9371](https://gitlab.com/gitlab-org/gitlab-ce/merge_requests/9371) - Make subscription API more RESTful. Use `post ":project_id/:subscribable_type/:subscribable_id/subscribe"` to subscribe and `post ":project_id/:subscribable_type/:subscribable_id/unsubscribe"` to unsubscribe from a resource. [!9325](https://gitlab.com/gitlab-org/gitlab-ce/merge_requests/9325) @@ -38,9 +39,17 @@ changes are in V4: - POST `:id/repository/branches` - POST `:id/repository/commits` - POST/PUT/DELETE `:id/repository/files` +- Renamed `merge when build succeeds` to merge `when pipeline succeeds parameters` on the following endpoints: [!9335](https://gitlab.com/gitlab-org/gitlab-ce/merge_requests/) + - PUT `projects/:id/merge_requests/:merge_request_id/merge` + - POST `projects/:id/merge_requests/:merge_request_id/cancel_merge_when_pipeline_succeeds` + - POST `projects` + - POST `projects/user/:user_id` + - PUT `projects/:id` - Renamed `branch_name` to `branch` on DELETE `id/repository/branches/:branch` response [!8936](https://gitlab.com/gitlab-org/gitlab-ce/merge_requests/8936) - Remove `public` param from create and edit actions of projects [!8736](https://gitlab.com/gitlab-org/gitlab-ce/merge_requests/8736) +- Use `visibility` as string parameter everywhere [!9337](https://gitlab.com/gitlab-org/gitlab-ce/merge_requests/9337) - Notes do not return deprecated field `upvote` and `downvote` [!9384](https://gitlab.com/gitlab-org/gitlab-ce/merge_requests/9384) - Return HTTP status code `400` for all validation errors when creating or updating a member instead of sometimes `422` error. [!9523](https://gitlab.com/gitlab-org/gitlab-ce/merge_requests/9523) - Remove `GET /groups/owned`. Use `GET /groups?owned=true` instead [!9505](https://gitlab.com/gitlab-org/gitlab-ce/merge_requests/9505) - Return 202 with JSON body on async removals on V4 API (DELETE `/projects/:id/repository/merged_branches` and DELETE `/projects/:id`) [!9449](https://gitlab.com/gitlab-org/gitlab-ce/merge_requests/9449) +- `projects/:id/milestones?iid[]=x&iid[]=y` array filter has been renamed to `iids` [!9096](https://gitlab.com/gitlab-org/gitlab-ce/merge_requests/9096) diff --git a/doc/api/version.md b/doc/api/version.md index 287d17cf97f..8b2a5b51bc5 100644 --- a/doc/api/version.md +++ b/doc/api/version.md @@ -10,7 +10,7 @@ GET /version ``` ```bash -curl --header "PRIVATE-TOKEN: 9koXpg98eAheJpvBs5tK" https://gitlab.example.com/api/v3/version +curl --header "PRIVATE-TOKEN: 9koXpg98eAheJpvBs5tK" https://gitlab.example.com/api/v4/version ``` Example response: diff --git a/doc/ci/triggers/README.md b/doc/ci/triggers/README.md index 740edba1f59..1ad9621c8a0 100644 --- a/doc/ci/triggers/README.md +++ b/doc/ci/triggers/README.md @@ -71,7 +71,7 @@ To trigger a job from webhook of another project you need to add the following webhook url for Push and Tag push events: ``` -https://gitlab.example.com/api/v3/projects/:id/ref/:ref/trigger/builds?token=TOKEN +https://gitlab.example.com/api/v4/projects/:id/ref/:ref/trigger/builds?token=TOKEN ``` > **Note**: @@ -105,7 +105,7 @@ Using cURL you can trigger a rebuild with minimal effort, for example: curl --request POST \ --form token=TOKEN \ --form ref=master \ - https://gitlab.example.com/api/v3/projects/9/trigger/builds + https://gitlab.example.com/api/v4/projects/9/trigger/builds ``` In this case, the project with ID `9` will get rebuilt on `master` branch. @@ -114,7 +114,7 @@ Alternatively, you can pass the `token` and `ref` arguments in the query string: ```bash curl --request POST \ - "https://gitlab.example.com/api/v3/projects/9/trigger/builds?token=TOKEN&ref=master" + "https://gitlab.example.com/api/v4/projects/9/trigger/builds?token=TOKEN&ref=master" ``` ### Triggering a job within `.gitlab-ci.yml` @@ -128,7 +128,7 @@ need to add in project's A `.gitlab-ci.yml`: build_docs: stage: deploy script: - - "curl --request POST --form token=TOKEN --form ref=master https://gitlab.example.com/api/v3/projects/9/trigger/builds" + - "curl --request POST --form token=TOKEN --form ref=master https://gitlab.example.com/api/v4/projects/9/trigger/builds" only: - tags ``` @@ -187,7 +187,7 @@ curl --request POST \ --form token=TOKEN \ --form ref=master \ --form "variables[UPLOAD_TO_S3]=true" \ - https://gitlab.example.com/api/v3/projects/9/trigger/builds + https://gitlab.example.com/api/v4/projects/9/trigger/builds ``` ### Using webhook to trigger job @@ -195,7 +195,7 @@ curl --request POST \ You can add the following webhook to another project in order to trigger a job: ``` -https://gitlab.example.com/api/v3/projects/9/ref/master/trigger/builds?token=TOKEN&variables[UPLOAD_TO_S3]=true +https://gitlab.example.com/api/v4/projects/9/ref/master/trigger/builds?token=TOKEN&variables[UPLOAD_TO_S3]=true ``` ### Using cron to trigger nightly jobs @@ -205,7 +205,7 @@ in conjunction with cron. The example below triggers a job on the `master` branch of project with ID `9` every night at `00:30`: ```bash -30 0 * * * curl --request POST --form token=TOKEN --form ref=master https://gitlab.example.com/api/v3/projects/9/trigger/builds +30 0 * * * curl --request POST --form token=TOKEN --form ref=master https://gitlab.example.com/api/v4/projects/9/trigger/builds ``` [ci-229]: https://gitlab.com/gitlab-org/gitlab-ci/merge_requests/229 diff --git a/doc/ci/variables/README.md b/doc/ci/variables/README.md index 620d4744685..04c0af44237 100644 --- a/doc/ci/variables/README.md +++ b/doc/ci/variables/README.md @@ -131,6 +131,16 @@ job_name: variables: [] ``` +You are able to use other variables inside your variable definition (or escape them with `$$`): + +```yaml +variables: + LS_CMD: 'ls $FLAGS $$TMP_DIR' + FLAGS: '-al' +script: + - 'eval $LS_CMD' # will execute 'ls -al $TMP_DIR' +``` + ## Secret variables >**Notes:** diff --git a/doc/development/doc_styleguide.md b/doc/development/doc_styleguide.md index fc948a7a116..9bed441c131 100644 --- a/doc/development/doc_styleguide.md +++ b/doc/development/doc_styleguide.md @@ -444,7 +444,7 @@ Rendered example: ### cURL commands -- Use `https://gitlab.example.com/api/v3/` as an endpoint. +- Use `https://gitlab.example.com/api/v4/` as an endpoint. - Wherever needed use this private token: `9koXpg98eAheJpvBs5tK`. - Always put the request first. `GET` is the default so you don't have to include it. @@ -468,7 +468,7 @@ Below is a set of [cURL][] examples that you can use in the API documentation. Get the details of a group: ```bash -curl --header "PRIVATE-TOKEN: 9koXpg98eAheJpvBs5tK" https://gitlab.example.com/api/v3/groups/gitlab-org +curl --header "PRIVATE-TOKEN: 9koXpg98eAheJpvBs5tK" https://gitlab.example.com/api/v4/groups/gitlab-org ``` #### cURL example with parameters passed in the URL @@ -476,7 +476,7 @@ curl --header "PRIVATE-TOKEN: 9koXpg98eAheJpvBs5tK" https://gitlab.example.com/a Create a new project under the authenticated user's namespace: ```bash -curl --request POST --header "PRIVATE-TOKEN: 9koXpg98eAheJpvBs5tK" "https://gitlab.example.com/api/v3/projects?name=foo" +curl --request POST --header "PRIVATE-TOKEN: 9koXpg98eAheJpvBs5tK" "https://gitlab.example.com/api/v4/projects?name=foo" ``` #### Post data using cURL's --data @@ -486,7 +486,7 @@ cURL's `--data` option. The example below will create a new project `foo` under the authenticated user's namespace. ```bash -curl --data "name=foo" --header "PRIVATE-TOKEN: 9koXpg98eAheJpvBs5tK" "https://gitlab.example.com/api/v3/projects" +curl --data "name=foo" --header "PRIVATE-TOKEN: 9koXpg98eAheJpvBs5tK" "https://gitlab.example.com/api/v4/projects" ``` #### Post data using JSON content @@ -495,7 +495,7 @@ curl --data "name=foo" --header "PRIVATE-TOKEN: 9koXpg98eAheJpvBs5tK" "https://g and double quotes. ```bash -curl --request POST --header "PRIVATE-TOKEN: 9koXpg98eAheJpvBs5tK" --header "Content-Type: application/json" --data '{"path": "my-group", "name": "My group"}' https://gitlab.example.com/api/v3/groups +curl --request POST --header "PRIVATE-TOKEN: 9koXpg98eAheJpvBs5tK" --header "Content-Type: application/json" --data '{"path": "my-group", "name": "My group"}' https://gitlab.example.com/api/v4/groups ``` #### Post data using form-data @@ -504,7 +504,7 @@ Instead of using JSON or urlencode you can use multipart/form-data which properly handles data encoding: ```bash -curl --request POST --header "PRIVATE-TOKEN: 9koXpg98eAheJpvBs5tK" --form "title=ssh-key" --form "key=ssh-rsa AAAAB3NzaC1yc2EA..." https://gitlab.example.com/api/v3/users/25/keys +curl --request POST --header "PRIVATE-TOKEN: 9koXpg98eAheJpvBs5tK" --form "title=ssh-key" --form "key=ssh-rsa AAAAB3NzaC1yc2EA..." https://gitlab.example.com/api/v4/users/25/keys ``` The above example is run by and administrator and will add an SSH public key @@ -518,7 +518,7 @@ contains spaces in its title. Observe how spaces are escaped using the `%20` ASCII code. ```bash -curl --request POST --header "PRIVATE-TOKEN: 9koXpg98eAheJpvBs5tK" "https://gitlab.example.com/api/v3/projects/42/issues?title=Hello%20Dude" +curl --request POST --header "PRIVATE-TOKEN: 9koXpg98eAheJpvBs5tK" "https://gitlab.example.com/api/v4/projects/42/issues?title=Hello%20Dude" ``` Use `%2F` for slashes (`/`). @@ -530,7 +530,7 @@ restrict the sign-up e-mail domains of a GitLab instance to `*.example.com` and `example.net`, you would do something like this: ```bash -curl --request PUT --header "PRIVATE-TOKEN: 9koXpg98eAheJpvBs5tK" --data "domain_whitelist[]=*.example.com" --data "domain_whitelist[]=example.net" https://gitlab.example.com/api/v3/application/settings +curl --request PUT --header "PRIVATE-TOKEN: 9koXpg98eAheJpvBs5tK" --data "domain_whitelist[]=*.example.com" --data "domain_whitelist[]=example.net" https://gitlab.example.com/api/v4/application/settings ``` [cURL]: http://curl.haxx.se/ "cURL website" diff --git a/doc/development/ux_guide/img/karolina-plaskaty.png b/doc/development/ux_guide/img/karolina-plaskaty.png Binary files differnew file mode 100644 index 00000000000..2e356c99762 --- /dev/null +++ b/doc/development/ux_guide/img/karolina-plaskaty.png diff --git a/doc/development/ux_guide/img/nazim-ramesh.png b/doc/development/ux_guide/img/nazim-ramesh.png Binary files differnew file mode 100644 index 00000000000..01ba0391630 --- /dev/null +++ b/doc/development/ux_guide/img/nazim-ramesh.png diff --git a/doc/development/ux_guide/users.md b/doc/development/ux_guide/users.md index 137154e24f3..cbd7c17de41 100644 --- a/doc/development/ux_guide/users.md +++ b/doc/development/ux_guide/users.md @@ -14,7 +14,7 @@ ### Nazim Ramesh - Small to medium size organisations using GitLab CE -<img src="img/steven-lyons.png" width="300px"> +<img src="img/nazim-ramesh.png" width="300px"> #### Demographics @@ -131,7 +131,7 @@ James and his team use CI quite heavily for several projects. Whilst they’ve w - Would like to use GitLab at work - Working for a medium to large size organisation -<img src="img/harry-robison.png" width="300px"> +<img src="img/karolina-plaskaty.png" width="300px"> #### Demographics diff --git a/doc/gitlab-basics/img/create_new_project_button.png b/doc/gitlab-basics/img/create_new_project_button.png Binary files differindex a19f0e57b56..8d7a69e55ed 100644 --- a/doc/gitlab-basics/img/create_new_project_button.png +++ b/doc/gitlab-basics/img/create_new_project_button.png diff --git a/doc/install/README.md b/doc/install/README.md index 2d2fd8cb380..d35709266e4 100644 --- a/doc/install/README.md +++ b/doc/install/README.md @@ -1,9 +1,32 @@ # Installation -- [Installation](installation.md) -- [Requirements](requirements.md) -- [Structure](structure.md) -- [Database MySQL](database_mysql.md) -- [Digital Ocean and Docker](digitaloceandocker.md) -- [Docker](https://gitlab.com/gitlab-org/gitlab-ce/tree/master/docker) -- [All installation methods](https://about.gitlab.com/installation/) +GitLab can be installed via various ways. Check the [installation methods][methods] +for an overview. + +## Requirements + +Before installing GitLab, make sure to check the [requirements documentation](requirements.md) +which includes useful information on the supported Operating Systems as well as +the hardware requirements. + +## Installation methods + +- [Installation using the Omnibus packages](https://about.gitlab.com/downloads/) - + Install GitLab using our official deb/rpm repositories. This is the + recommended way. +- [Installation from source](installation.md) - Install GitLab from source. + Useful for unsupported systems like *BSD. For an overview of the directory + structure, read the [structure documentation](structure.md). +- [Docker](https://docs.gitlab.com/omnibus/docker/) - Install GitLab using Docker. +- [Installation on Google Cloud Platform](google_cloud_platform/index.md) - Install + GitLab on Google Cloud Platform using our official image. +- [Digital Ocean and Docker](digitaloceandocker.md) - Install GitLab quickly + on DigitalOcean using Docker. + +## Database + +While the recommended database is PostgreSQL, we provide information to install +GitLab using MySQL. Check the [MySQL documentation](database_mysql.md) for more +information. + +[methods]: https://about.gitlab.com/installation/ diff --git a/doc/install/google_cloud_platform/img/change_admin_passwd_email.png b/doc/install/google_cloud_platform/img/change_admin_passwd_email.png Binary files differnew file mode 100644 index 00000000000..1ffe14f60ff --- /dev/null +++ b/doc/install/google_cloud_platform/img/change_admin_passwd_email.png diff --git a/doc/install/google_cloud_platform/img/chrome_not_secure_page.png b/doc/install/google_cloud_platform/img/chrome_not_secure_page.png Binary files differnew file mode 100644 index 00000000000..e732066908f --- /dev/null +++ b/doc/install/google_cloud_platform/img/chrome_not_secure_page.png diff --git a/doc/install/google_cloud_platform/img/gcp_gitlab_being_deployed.png b/doc/install/google_cloud_platform/img/gcp_gitlab_being_deployed.png Binary files differnew file mode 100644 index 00000000000..2a1859da6e3 --- /dev/null +++ b/doc/install/google_cloud_platform/img/gcp_gitlab_being_deployed.png diff --git a/doc/install/google_cloud_platform/img/gcp_gitlab_overview.png b/doc/install/google_cloud_platform/img/gcp_gitlab_overview.png Binary files differnew file mode 100644 index 00000000000..1c4c870dbc9 --- /dev/null +++ b/doc/install/google_cloud_platform/img/gcp_gitlab_overview.png diff --git a/doc/install/google_cloud_platform/img/gcp_landing.png b/doc/install/google_cloud_platform/img/gcp_landing.png Binary files differnew file mode 100644 index 00000000000..6398d247ba0 --- /dev/null +++ b/doc/install/google_cloud_platform/img/gcp_landing.png diff --git a/doc/install/google_cloud_platform/img/gcp_launcher_console_home_page.png b/doc/install/google_cloud_platform/img/gcp_launcher_console_home_page.png Binary files differnew file mode 100644 index 00000000000..f492888ea4a --- /dev/null +++ b/doc/install/google_cloud_platform/img/gcp_launcher_console_home_page.png diff --git a/doc/install/google_cloud_platform/img/gcp_search_for_gitlab.png b/doc/install/google_cloud_platform/img/gcp_search_for_gitlab.png Binary files differnew file mode 100644 index 00000000000..b38af3966e2 --- /dev/null +++ b/doc/install/google_cloud_platform/img/gcp_search_for_gitlab.png diff --git a/doc/install/google_cloud_platform/img/gitlab_deployed_page.png b/doc/install/google_cloud_platform/img/gitlab_deployed_page.png Binary files differnew file mode 100644 index 00000000000..fef9ae45f32 --- /dev/null +++ b/doc/install/google_cloud_platform/img/gitlab_deployed_page.png diff --git a/doc/install/google_cloud_platform/img/gitlab_first_sign_in.png b/doc/install/google_cloud_platform/img/gitlab_first_sign_in.png Binary files differnew file mode 100644 index 00000000000..381c0fe48a5 --- /dev/null +++ b/doc/install/google_cloud_platform/img/gitlab_first_sign_in.png diff --git a/doc/install/google_cloud_platform/img/gitlab_launch_button.png b/doc/install/google_cloud_platform/img/gitlab_launch_button.png Binary files differnew file mode 100644 index 00000000000..50f66f66118 --- /dev/null +++ b/doc/install/google_cloud_platform/img/gitlab_launch_button.png diff --git a/doc/install/google_cloud_platform/img/new_gitlab_deployment_settings.png b/doc/install/google_cloud_platform/img/new_gitlab_deployment_settings.png Binary files differnew file mode 100644 index 00000000000..00060841619 --- /dev/null +++ b/doc/install/google_cloud_platform/img/new_gitlab_deployment_settings.png diff --git a/doc/install/google_cloud_platform/img/ssh_via_button.png b/doc/install/google_cloud_platform/img/ssh_via_button.png Binary files differnew file mode 100644 index 00000000000..26106f159ad --- /dev/null +++ b/doc/install/google_cloud_platform/img/ssh_via_button.png diff --git a/doc/install/google_cloud_platform/index.md b/doc/install/google_cloud_platform/index.md new file mode 100644 index 00000000000..26506111548 --- /dev/null +++ b/doc/install/google_cloud_platform/index.md @@ -0,0 +1,168 @@ +# Installing GitLab on Google Cloud Platform + +![GCP landing page](img/gcp_landing.png) + +The fastest way to get started on [Google Cloud Platform (GCP)][gcp] is through +the [Google Cloud Launcher][launcher] program. + +## Prerequisites + +There are only two prerequisites in order to install GitLab on GCP: + +1. You need to have a Google account. +1. You need to sign up for the GCP program. If this is your first time, Google + gives you [$300 credit for free][freetrial] to consume over a 60-day period. + +Once you have performed those two steps, you can visit the +[GCP launcher console][console] which has a list of all the things you can +deploy on GCP. + +![GCP launcher console](img/gcp_launcher_console_home_page.png) + +The next step is to find and install GitLab. + +## Configuring and deploying the VM + +To deploy GitLab on GCP you need to follow five simple steps: + +1. Go to https://cloud.google.com/launcher and login with your Google credentials +1. Search for GitLab from GitLab Inc. (not the same as Bitnami) and click on + the tile. + + ![Search for GitLab](img/gcp_search_for_gitlab.png) + +1. In the next page, you can see an overview of the GitLab VM as well as some + estimated costs. Click the **Launch on Compute Engine** button to choose the + hardware and network settings. + + ![Launch on Compute Engine](img/gcp_gitlab_overview.png) + +1. In the settings page you can choose things like the datacenter where your GitLab + server will be hosted, the number of CPUs and amount of RAM, the disk size + and type, etc. Read GitLab's [requirements documentation][req] for more + details on what to choose depending on your needs. + + ![Deploy settings](img/new_gitlab_deployment_settings.png) + +1. As a last step, hit **Deploy** when ready. The process will finish in a few + seconds. + + ![Deploy in progress](img/gcp_gitlab_being_deployed.png) + + +## Visiting GitLab for the first time + +After a few seconds, GitLab will be successfully deployed and you should be +able to see the IP address that Google assigned to the VM, as well as the +credentials to the GitLab admin account. + +![Deploy settings](img/gitlab_deployed_page.png) + +1. Click on the IP under **Site address** to visit GitLab. +1. Accept the self-signed certificate that Google automatically deployed in + order to securely reach GitLab's login page. +1. Use the username and password that are present in the Google console page + to login into GitLab and click **Sign in**. + + ![GitLab first sign in](img/gitlab_first_sign_in.png) + +Congratulations! GitLab is now installed and you can access it via your browser, +but we're not done yet. There are some steps you need to take in order to have +a fully functional GitLab installation. + +## Next steps + +These are the most important next steps to take after you installed GitLab for +the first time. + +### Changing the admin password and email + +Google assigned a random password for the GitLab admin account and you should +change it ASAP: + +1. Visit the GitLab admin page through the link in the Google console under + **Admin URL**. +1. Find the Administrator user under the **Users** page and hit **Edit**. +1. Change the email address to a real one and enter a new password. + + ![Change GitLab admin password](img/change_admin_passwd_email.png) + +1. Hit **Save changes** for the changes to take effect. +1. After changing the password, you will be signed out from GitLab. Use the + new credentials to login again. + +### Assigning a static IP + +By default, Google assigns an ephemeral IP to your instance. It is strongly +recommended to assign a static IP if you are going to use GitLab in production +and use a domain name as we'll see below. + +Read Google's documentation on how to [promote an ephemeral IP address][ip]. + +### Using a domain name + +Assuming you have a domain name in your possession and you have correctly +set up DNS to point to the static IP you configured in the previous step, +here's how you configure GitLab to be aware of the change: + +1. SSH into the VM. You can easily use the **SSH** button in the Google console + and a new window will pop up. + + ![SSH button](img/ssh_via_button.png) + + In the future you might want to set up [connecting with an SSH key][ssh] + instead. + +1. Edit the config file of Omnibus GitLab using your favorite text editor: + + ``` + sudo vim /etc/gitlab/gitlab.rb + ``` + +1. Set the `external_url` value to the domain name you wish GitLab to have + **without** `https`: + + ``` + external_url 'http://gitlab.example.com' + ``` + + We will set up HTTPS in the next step, no need to do this now. + +1. Reconfigure GitLab for the changes to take effect: + + ``` + sudo gitlab-ctl reconfigure + ``` + +1. You can now visit GitLab using the domain name. + +### Configuring HTTPS with the domain name + +Although not needed, it's strongly recommended to secure GitLab with a TLS +certificate. Follow the steps in the [Omnibus documentation][omni-ssl]. + +### Configuring the email SMTP settings + +You need to configure the email SMTP settings correctly otherwise GitLab will +not be able to send notification emails, like comments, and password changes. +Check the [Omnibus documentation][omni-smtp] how to do so. + +## Further reading + +GitLab can be configured to authenticate with other OAuth providers, LDAP, SAML, +Kerberos, etc. Here are some documents you might be interested in reading: + +- [Omnibus GitLab documentation](https://docs.gitlab.com/omnibus/) +- [Integration documentation](https://docs.gitlab.com/ce/integration/) +- [GitLab Pages configuration](https://docs.gitlab.com/ce/administration/pages/index.html) +- [GitLab Container Registry configuration](https://docs.gitlab.com/ce/administration/container_registry.html) + +[console]: https://console.cloud.google.com/launcher "GCP launcher console" +[freetrial]: https://console.cloud.google.com/freetrial "GCP free trial" +[ip]: https://cloud.google.com/compute/docs/configure-instance-ip-addresses#promote_ephemeral_ip "Configuring an Instance's IP Addresses" +[gcp]: https://cloud.google.com/ "Google Cloud Platform" +[launcher]: https://cloud.google.com/launcher/ "Google Cloud Launcher home page" +[req]: ../requirements.md "GitLab hardware and software requirements" +[ssh]: https://cloud.google.com/compute/docs/instances/connecting-to-instance "Connecting to Linux Instances" +[omni-smtp]: https://docs.gitlab.com/omnibus/settings/smtp.html#smtp-settings "Omnibus GitLab SMTP settings" +[omni-ssl]: https://docs.gitlab.com/omnibus/settings/nginx.html#enable-https "Omnibus GitLab enable HTTPS" diff --git a/doc/install/installation.md b/doc/install/installation.md index 5ba338ba7d1..bb4141c6cd3 100644 --- a/doc/install/installation.md +++ b/doc/install/installation.md @@ -155,10 +155,9 @@ page](https://golang.org/dl). ## 4. Node Since GitLab 8.17, GitLab requires the use of node >= v4.3.0 to compile -javascript assets, and starting in GitLab 9.0, yarn >= v0.17.0 is required to -manage javascript dependencies. In many distros the versions provided by the -official package repositories are out of date, so we'll need to install through -the following commands: +javascript assets, and yarn >= v0.17.0 to manage javascript dependencies. +In many distros the versions provided by the official package repositories +are out of date, so we'll need to install through the following commands: # install node v7.x curl --location https://deb.nodesource.com/setup_7.x | bash - diff --git a/doc/raketasks/backup_restore.md b/doc/raketasks/backup_restore.md index a5b8cd6455c..96ec1b178b6 100644 --- a/doc/raketasks/backup_restore.md +++ b/doc/raketasks/backup_restore.md @@ -38,23 +38,6 @@ If you are running GitLab within a Docker container, you can run the backup from docker exec -t <container name> gitlab-rake gitlab:backup:create ``` -You can specify that portions of the application data be skipped using the -environment variable `SKIP`. You can skip: - -- `db` (database) -- `uploads` (attachments) -- `repositories` (Git repositories data) -- `builds` (CI job output logs) -- `artifacts` (CI job artifacts) -- `lfs` (LFS objects) -- `registry` (Container Registry images) - -Separate multiple data types to skip using a comma. For example: - -``` -sudo gitlab-rake gitlab:backup:create SKIP=db,uploads -``` - Example output: ``` @@ -111,13 +94,14 @@ To use the `copy` strategy instead of the default streaming strategy, specify You can choose what should be backed up by adding the environment variable `SKIP`. The available options are: -* `db` -* `uploads` (attachments) -* `repositories` -* `builds` (CI build output logs) -* `artifacts` (CI build artifacts) -* `lfs` (LFS objects) -* `pages` (pages content) +- `db` (database) +- `uploads` (attachments) +- `repositories` (Git repositories data) +- `builds` (CI job output logs) +- `artifacts` (CI job artifacts) +- `lfs` (LFS objects) +- `registry` (Container Registry images) +- `pages` (Pages content) Use a comma to specify several options at the same time: @@ -416,7 +400,7 @@ sudo gitlab-rake gitlab:check SANITIZE=true If there is a GitLab version mismatch between your backup tar file and the installed version of GitLab, the restore command will abort with an error. Install the -[correct GitLab version](https://www.gitlab.com/downloads/archives/) and try again. +[correct GitLab version](https://about.gitlab.com/downloads/archives/) and try again. ## Configure cron to make daily backups diff --git a/doc/ssh/README.md b/doc/ssh/README.md index 9e391d647a8..678f5199b02 100644 --- a/doc/ssh/README.md +++ b/doc/ssh/README.md @@ -13,7 +13,7 @@ read [this nice tutorial by DigitalOcean](https://www.digitalocean.com/community ## Locating an existing SSH key pair -Before generating a new SSH key check if your system already has one +Before generating a new SSH key pair check if your system already has one at the default location by opening a shell, or Command Prompt on Windows, and running the following command: @@ -23,43 +23,49 @@ and running the following command: type %userprofile%\.ssh\id_rsa.pub ``` -**GNU/Linux / macOS / PowerShell:** +**Git Bash on Windows / GNU/Linux / macOS / PowerShell:** ```bash cat ~/.ssh/id_rsa.pub ``` If you see a string starting with `ssh-rsa` you already have an SSH key pair -and you can skip the next step **Generating a new SSH key pair** -and continue onto **Copying your public SSH key to the clipboard**. +and you can skip the generate portion of the next section and skip to the copy +to clipboard step. If you don't see the string or would like to generate a SSH key pair with a custom name continue onto the next step. +> +**Note:** Public SSH key may also be named as follows: +- `id_dsa.pub` +- `id_ecdsa.pub` +- `id_ed25519.pub` + ## Generating a new SSH key pair -1. To generate a new SSH key, use the following command: +1. To generate a new SSH key pair, use the following command: - **GNU/Linux / macOS:** + **Git Bash on Windows / GNU/Linux / macOS:** ```bash - ssh-keygen -t rsa -C "GitLab" -b 4096 + ssh-keygen -t rsa -C "your.email@example.com" -b 4096 ``` **Windows:** - On Windows you will need to download + Alternatively on Windows you can download [PuttyGen](http://www.chiark.greenend.org.uk/~sgtatham/putty/download.html) - and follow this [documentation article][winputty] to generate a SSH key pair. + and follow [this documentation article][winputty] to generate a SSH key pair. -1. Next, you will be prompted to input a file path to save your key pair to. +1. Next, you will be prompted to input a file path to save your SSH key pair to. If you don't already have an SSH key pair use the suggested path by pressing - enter. Using the suggested path will allow your SSH client - to automatically use the key pair with no additional configuration. + enter. Using the suggested path will normally allow your SSH client + to automatically use the SSH key pair with no additional configuration. - If you already have a key pair with the suggested file path, you will need - to input a new file path and declare what host this key pair will be used - for in your `.ssh/config` file, see **Working with non-default SSH key pair paths** + If you already have a SSH key pair with the suggested file path, you will need + to input a new file path and declare what host this SSH key pair will be used + for in your `.ssh/config` file, see [**Working with non-default SSH key pair paths**](#working-with-non-default-ssh-key-pair-paths) for more information. 1. Once you have input a file path you will be prompted to input a password to @@ -68,12 +74,12 @@ custom name continue onto the next step. pressing enter. >**Note:** - If you want to change the password of your key, you can use `ssh-keygen -p <keyname>`. + If you want to change the password of your SSH key pair, you can use + `ssh-keygen -p <keyname>`. -1. The next step is to copy the public key as we will need it afterwards. +1. The next step is to copy the public SSH key as we will need it afterwards. - To copy your public key to the clipboard, use the appropriate code for your - operating system below: + To copy your public SSH key to the clipboard, use the appropriate code below: **macOS:** @@ -93,7 +99,7 @@ custom name continue onto the next step. type %userprofile%\.ssh\id_rsa.pub | clip ``` - **Windows PowerShell:** + **Git Bash on Windows / Windows PowerShell:** ```bash cat ~/.ssh/id_rsa.pub | clip @@ -101,22 +107,38 @@ custom name continue onto the next step. 1. The final step is to add your public SSH key to GitLab. - Navigate to the 'SSH Keys' tab in you 'Profile Settings'. + Navigate to the 'SSH Keys' tab in your 'Profile Settings'. Paste your key in the 'Key' section and give it a relevant 'Title'. Use an identifiable title like 'Work Laptop - Windows 7' or 'Home MacBook Pro 15'. If you manually copied your public SSH key make sure you copied the entire key starting with `ssh-rsa` and ending with your email. + +1. Optionally you can test your setup by running `ssh -T git@example.com` + (replacing `example.com` with your GitLab domain) and verifying that you + receive a `Welcome to GitLab` message. ## Working with non-default SSH key pair paths If you used a non-default file path for your GitLab SSH key pair, -you must configure your SSH client to find your GitLab SSH private key -for connections to your GitLab server (perhaps gitlab.com). +you must configure your SSH client to find your GitLab private SSH key +for connections to your GitLab server (perhaps `gitlab.com`). + +For your current terminal session you can do so using the following commands +(replacing `other_id_rsa` with your private SSH key): -For OpenSSH clients this is configured in the `~/.ssh/config` file. -Below are two example host configurations using their own key: +**Git Bash on Windows / GNU/Linux / macOS:** + +```bash +eval $(ssh-agent -s) +ssh-add ~/.ssh/other_id_rsa +``` + +To retain these settings you'll need to save them to a configuration file. +For OpenSSH clients this is configured in the `~/.ssh/config` file for some +operating systems. +Below are two example host configurations using their own SSH key: ``` # GitLab.com server @@ -140,8 +162,8 @@ That's why it needs to uniquely map to a single user. ## Deploy keys -Deploy keys allow read-only access to multiple projects with a single SSH -key. +Deploy keys allow read-only or read-write (if enabled) access to one or +multiple projects with a single SSH key pair. This is really useful for cloning repositories to your Continuous Integration (CI) server. By using deploy keys, you don't have to setup a @@ -150,7 +172,8 @@ dummy user account. If you are a project master or owner, you can add a deploy key in the project settings under the section 'Deploy Keys'. Press the 'New Deploy Key' button and upload a public SSH key. After this, the machine that uses -the corresponding private key has read-only access to the project. +the corresponding private SSH key has read-only or read-write (if enabled) +access to the project. You can't add the same deploy key twice with the 'New Deploy Key' option. If you want to add the same key to another project, please enable it in the @@ -166,6 +189,18 @@ project. ### Eclipse -How to add your ssh key to Eclipse: https://wiki.eclipse.org/EGit/User_Guide#Eclipse_SSH_Configuration +How to add your SSH key to Eclipse: https://wiki.eclipse.org/EGit/User_Guide#Eclipse_SSH_Configuration [winputty]: https://the.earth.li/~sgtatham/putty/0.67/htmldoc/Chapter8.html#pubkey-puttygen + +## Troubleshooting + +If on Git clone you are prompted for a password like `git@gitlab.com's password:` +something is wrong with your SSH setup. + +- Ensure that you generated your SSH key pair correctly and added the public SSH + key to your GitLab profile +- Try manually registering your private SSH key using `ssh-agent` as documented + earlier in this document +- Try to debug the connection by running `ssh -Tv git@example.com` + (replacing `example.com` with your GitLab domain) diff --git a/doc/user/project/merge_requests/img/merge_when_build_succeeds_enable.png b/doc/user/project/merge_requests/img/merge_when_build_succeeds_enable.png Binary files differdeleted file mode 100644 index f50a1be24f2..00000000000 --- a/doc/user/project/merge_requests/img/merge_when_build_succeeds_enable.png +++ /dev/null diff --git a/doc/user/project/merge_requests/img/merge_when_build_succeeds_only_if_succeeds_settings.png b/doc/user/project/merge_requests/img/merge_when_build_succeeds_only_if_succeeds_settings.png Binary files differdeleted file mode 100644 index ddc58ff2630..00000000000 --- a/doc/user/project/merge_requests/img/merge_when_build_succeeds_only_if_succeeds_settings.png +++ /dev/null diff --git a/doc/user/project/merge_requests/img/merge_when_build_succeeds_status.png b/doc/user/project/merge_requests/img/merge_when_build_succeeds_status.png Binary files differdeleted file mode 100644 index a98636ee359..00000000000 --- a/doc/user/project/merge_requests/img/merge_when_build_succeeds_status.png +++ /dev/null diff --git a/doc/user/project/merge_requests/img/merge_when_pipeline_succeeds_enable.png b/doc/user/project/merge_requests/img/merge_when_pipeline_succeeds_enable.png Binary files differnew file mode 100644 index 00000000000..33f5a4a7a02 --- /dev/null +++ b/doc/user/project/merge_requests/img/merge_when_pipeline_succeeds_enable.png diff --git a/doc/user/project/merge_requests/img/merge_when_build_succeeds_only_if_succeeds_msg.png b/doc/user/project/merge_requests/img/merge_when_pipeline_succeeds_only_if_succeeds_msg.png Binary files differindex c43f76b058c..c43f76b058c 100644 --- a/doc/user/project/merge_requests/img/merge_when_build_succeeds_only_if_succeeds_msg.png +++ b/doc/user/project/merge_requests/img/merge_when_pipeline_succeeds_only_if_succeeds_msg.png diff --git a/doc/user/project/merge_requests/img/merge_when_pipeline_succeeds_only_if_succeeds_settings.png b/doc/user/project/merge_requests/img/merge_when_pipeline_succeeds_only_if_succeeds_settings.png Binary files differnew file mode 100644 index 00000000000..9629ed99838 --- /dev/null +++ b/doc/user/project/merge_requests/img/merge_when_pipeline_succeeds_only_if_succeeds_settings.png diff --git a/doc/user/project/merge_requests/img/merge_when_pipeline_succeeds_status.png b/doc/user/project/merge_requests/img/merge_when_pipeline_succeeds_status.png Binary files differnew file mode 100644 index 00000000000..d0691437c65 --- /dev/null +++ b/doc/user/project/merge_requests/img/merge_when_pipeline_succeeds_status.png diff --git a/doc/user/project/merge_requests/merge_when_pipeline_succeeds.md b/doc/user/project/merge_requests/merge_when_pipeline_succeeds.md index c63a408505f..bdd7d0022e6 100644 --- a/doc/user/project/merge_requests/merge_when_pipeline_succeeds.md +++ b/doc/user/project/merge_requests/merge_when_pipeline_succeeds.md @@ -5,7 +5,7 @@ more CI jobs running, you can set it to be merged automatically when the jobs pipeline succeeds. This way, you don't have to wait for the jobs to finish and remember to merge the request manually. -![Enable](img/merge_when_build_succeeds_enable.png) +![Enable](img/merge_when_pipeline_succeeds_enable.png) When you hit the "Merge When Pipeline Succeeds" button, the status of the merge request will be updated to represent the impending merge. If you cannot wait @@ -16,7 +16,7 @@ Both team developers and the author of the merge request have the option to cancel the automatic merge if they find a reason why it shouldn't be merged after all. -![Status](img/merge_when_build_succeeds_status.png) +![Status](img/merge_when_pipeline_succeeds_status.png) When the pipeline succeeds, the merge request will automatically be merged. When the pipeline fails, the author gets a chance to retry any failed jobs, @@ -32,15 +32,16 @@ changes to be reviewed. > **Note:** You need to have jobs configured to enable this feature. -You can prevent merge requests from being merged if their pipeline did not succeed. +You can prevent merge requests from being merged if their pipeline did not succeed +or if there are discussions to be resolved. Navigate to your project's settings page, select the **Only allow merge requests to be merged if the pipeline succeeds** check box and hit **Save** for the changes to take effect. -![Only allow merge if pipeline succeeds settings](img/merge_when_build_succeeds_only_if_succeeds_settings.png) +![Only allow merge if pipeline succeeds settings](img/merge_when_pipeline_succeeds_only_if_succeeds_settings.png) From now on, every time the pipeline fails you will not be able to merge the merge request from the UI, until you make all relevant jobs pass. -![Only allow merge if pipeline succeeds message](img/merge_when_build_succeeds_only_if_succeeds_msg.png) +![Only allow merge if pipeline succeeds message](img/merge_when_pipeline_succeeds_only_if_succeeds_msg.png) diff --git a/doc/user/project/pages/getting_started_part_four.md b/doc/user/project/pages/getting_started_part_four.md new file mode 100644 index 00000000000..6edf99239ea --- /dev/null +++ b/doc/user/project/pages/getting_started_part_four.md @@ -0,0 +1,382 @@ +# GitLab Pages from A to Z: Part 4 + +- [Part 1: Static sites and GitLab Pages domains](getting_started_part_one.md) +- [Part 2: Quick start guide - Setting up GitLab Pages](getting_started_part_two.md) +- [Part 3: Setting Up Custom Domains - DNS Records and SSL/TLS Certificates](getting_started_part_three.md) +- **Part 4: Creating and tweaking `.gitlab-ci.yml` for GitLab Pages** + +## Creating and Tweaking `.gitlab-ci.yml` for GitLab Pages + +[GitLab CI](https://about.gitlab.com/gitlab-ci/) serves +numerous purposes, to build, test, and deploy your app +from GitLab through +[Continuous Integration, Continuous Delivery, and Continuous Deployment](https://about.gitlab.com/2016/08/05/continuous-integration-delivery-and-deployment-with-gitlab/) +methods. You will need it to build your website with GitLab Pages, +and deploy it to the Pages server. + +What this file actually does is telling the +[GitLab Runner](https://docs.gitlab.com/runner/) to run scripts +as you would do from the command line. The Runner acts as your +terminal. GitLab CI tells the Runner which commands to run. +Both are built-in in GitLab, and you don't need to set up +anything for them to work. + +Explaining [every detail of GitLab CI](https://docs.gitlab.com/ce/ci/yaml/README.html) +and GitLab Runner is out of the scope of this guide, but we'll +need to understand just a few things to be able to write our own +`.gitlab-ci.yml` or tweak an existing one. It's an +[Yaml](http://docs.ansible.com/ansible/YAMLSyntax.html) file, +with its own syntax. You can always check your CI syntax with +the [GitLab CI Lint Tool](https://gitlab.com/ci/lint). + +**Practical Example:** + +Let's consider you have a [Jekyll](https://jekyllrb.com/) site. +To build it locally, you would open your terminal, and run `jekyll build`. +Of course, before building it, you had to install Jekyll in your computer. +For that, you had to open your terminal and run `gem install jekyll`. +Right? GitLab CI + GitLab Runner do the same thing. But you need to +write in the `.gitlab-ci.yml` the script you want to run so +GitLab Runner will do it for you. It looks more complicated then it +is. What you need to tell the Runner: + +``` +$ gem install jekyll +$ jekyll build +``` + +### Script + +To transpose this script to Yaml, it would be like this: + +```yaml +script: + - gem install jekyll + - jekyll build +``` + +### Job + +So far so good. Now, each `script`, in GitLab is organized by +a `job`, which is a bunch of scripts and settings you want to +apply to that specific task. + +```yaml +job: + script: + - gem install jekyll + - jekyll build +``` + +For GitLab Pages, this `job` has a specific name, called `pages`, +which tells the Runner you want that task to deploy your website +with GitLab Pages: + +```yaml +pages: + script: + - gem install jekyll + - jekyll build +``` + +### The `public` directory + +We also need to tell Jekyll where do you want the website to build, +and GitLab Pages will only consider files in a directory called `public`. +To do that with Jekyll, we need to add a flag specifying the +[destination (`-d`)](https://jekyllrb.com/docs/usage/) of the +built website: `jekyll build -d public`. Of course, we need +to tell this to our Runner: + +```yaml +pages: + script: + - gem install jekyll + - jekyll build -d public +``` + +### Artifacts + +We also need to tell the Runner that this _job_ generates +_artifacts_, which is the site built by Jekyll. +Where are these artifacts stored? In the `public` directory: + +```yaml +pages: + script: + - gem install jekyll + - jekyll build -d public + artifacts: + paths: + - public +``` + +The script above would be enough to build your Jekyll +site with GitLab Pages. But, from Jekyll 3.4.0 on, its default +template originated by `jekyll new project` requires +[Bundler](http://bundler.io/) to install Jekyll dependencies +and the default theme. To adjust our script to meet these new +requirements, we only need to install and build Jekyll with Bundler: + +```yaml +pages: + script: + - bundle install + - bundle exec jekyll build -d public + artifacts: + paths: + - public +``` + +That's it! A `.gitlab-ci.yml` with the content above would deploy +your Jekyll 3.4.0 site with GitLab Pages. This is the minimum +configuration for our example. On the steps below, we'll refine +the script by adding extra options to our GitLab CI. + +### Image + +At this point, you probably ask yourself: "okay, but to install Jekyll +I need Ruby. Where is Ruby on that script?". The answer is simple: the +first thing GitLab Runner will look for in your `.gitlab-ci.yml` is a +[Docker](https://www.docker.com/) image specifying what do you need in +your container to run that script: + +```yaml +image: ruby:2.3 + +pages: + script: + - bundle install + - bundle exec jekyll build -d public + artifacts: + paths: + - public +``` + +In this case, you're telling the Runner to pull this image, which +contains Ruby 2.3 as part of its file system. When you don't specify +this image in your configuration, the Runner will use a default +image, which is Ruby 2.1. + +If your SSG needs [NodeJS](https://nodejs.org/) to build, you'll +need to specify which image you want to use, and this image should +contain NodeJS as part of its file system. E.g., for a +[Hexo](https://gitlab.com/pages/hexo) site, you can use `image: node:4.2.2`. + +>**Note:** +We're not trying to explain what a Docker image is, +we just need to introduce the concept with a minimum viable +explanation. To know more about Docker images, please visit +their website or take a look at a +[summarized explanation](http://paislee.io/how-to-automate-docker-deployments/) here. + +Let's go a little further. + +### Branching + +If you use GitLab as a version control platform, you will have your +branching strategy to work on your project. Meaning, you will have +other branches in your project, but you'll want only pushes to the +default branch (usually `master`) to be deployed to your website. +To do that, we need to add another line to our CI, telling the Runner +to only perform that _job_ called `pages` on the `master` branch `only`: + +```yaml +image: ruby:2.3 + +pages: + script: + - bundle install + - bundle exec jekyll build -d public + artifacts: + paths: + - public + only: + - master +``` + +### Stages + +Another interesting concept to keep in mind are build stages. +Your web app can pass through a lot of tests and other tasks +until it's deployed to staging or production environments. +There are three default stages on GitLab CI: build, test, +and deploy. To specify which stage your _job_ is running, +simply add another line to your CI: + +```yaml +image: ruby:2.3 + +pages: + stage: deploy + script: + - bundle install + - bundle exec jekyll build -d public + artifacts: + paths: + - public + only: + - master +``` + +You might ask yourself: "why should I bother with stages +at all?" Well, let's say you want to be able to test your +script and check the built site before deploying your site +to production. You want to run the test exactly as your +script will do when you push to `master`. It's simple, +let's add another task (_job_) to our CI, telling it to +test every push to other branches, `except` the `master` branch: + +```yaml +image: ruby:2.3 + +pages: + stage: deploy + script: + - bundle install + - bundle exec jekyll build -d public + artifacts: + paths: + - public + only: + - master + +test: + stage: test + script: + - bundle install + - bundle exec jekyll build -d test + artifacts: + paths: + - test + except: + - master +``` + +The `test` job is running on the stage `test`, Jekyll +will build the site in a directory called `test`, and +this job will affect all the branches except `master`. + +The best benefit of applying _stages_ to different +_jobs_ is that every job in the same stage builds in +parallel. So, if your web app needs more than one test +before being deployed, you can run all your test at the +same time, it's not necessary to wait one test to finish +to run the other. Of course, this is just a brief +introduction of GitLab CI and GitLab Runner, which are +tools much more powerful than that. This is what you +need to be able to create and tweak your builds for +your GitLab Pages site. + +### Before Script + +To avoid running the same script multiple times across +your _jobs_, you can add the parameter `before_script`, +in which you specify which commands you want to run for +every single _job_. In our example, notice that we run +`bundle install` for both jobs, `pages` and `test`. +We don't need to repeat it: + +```yaml +image: ruby:2.3 + +before_script: + - bundle install + +pages: + stage: deploy + script: + - bundle exec jekyll build -d public + artifacts: + paths: + - public + only: + - master + +test: + stage: test + script: + - bundle exec jekyll build -d test + artifacts: + paths: + - test + except: + - master +``` + +### Caching Dependencies + +If you want to cache the installation files for your +projects dependencies, for building faster, you can +use the parameter `cache`. For this example, we'll +cache Jekyll dependencies in a `vendor` directory +when we run `bundle install`: + +```yaml +image: ruby:2.3 + +cache: + paths: + - vendor/ + +before_script: + - bundle install --path vendor + +pages: + stage: deploy + script: + - bundle exec jekyll build -d public + artifacts: + paths: + - public + only: + - master + +test: + stage: test + script: + - bundle exec jekyll build -d test + artifacts: + paths: + - test + except: + - master +``` + +For this specific case, we need to exclude `/vendor` +from Jekyll `_config.yml` file, otherwise Jekyll will +understand it as a regular directory to build +together with the site: + +```yml +exclude: + - vendor +``` + +There we go! Now our GitLab CI not only builds our website, +but also **continuously test** pushes to feature-branches, +**caches** dependencies installed with Bundler, and +**continuously deploy** every push to the `master` branch. + +## Advanced GitLab CI for GitLab Pages + +What you can do with GitLab CI is pretty much up to your +creativity. Once you get used to it, you start creating +awesome scripts that automate most of tasks you'd do +manually in the past. Read through the +[documentation of GitLab CI](https://docs.gitlab.com/ce/ci/yaml/README.html) +to understand how to go even further on your scripts. + +- On this blog post, understand the concept of +[using GitLab CI `environments` to deploy your +web app to staging and production](https://about.gitlab.com/2016/08/26/ci-deployment-and-environments/). +- On this post, learn [how to run jobs sequentially, +in parallel, or build a custom pipeline](https://about.gitlab.com/2016/07/29/the-basics-of-gitlab-ci/) +- On this blog post, we go through the process of +[pulling specific directories from different projects](https://about.gitlab.com/2016/12/07/building-a-new-gitlab-docs-site-with-nanoc-gitlab-ci-and-gitlab-pages/) +to deploy this website you're looking at, docs.gitlab.com. +- On this blog post, we teach you [how to use GitLab Pages to produce a code coverage report](https://about.gitlab.com/2016/11/03/publish-code-coverage-report-with-gitlab-pages/). + +||| +|:--|--:| +|[**← Part 3: Setting Up Custom Domains - DNS Records and SSL/TLS Certificates**](getting_started_part_three.md)|| diff --git a/doc/user/project/pages/getting_started_part_one.md b/doc/user/project/pages/getting_started_part_one.md index c5b1aa4b654..582a4afbab4 100644 --- a/doc/user/project/pages/getting_started_part_one.md +++ b/doc/user/project/pages/getting_started_part_one.md @@ -1,15 +1,28 @@ # GitLab Pages from A to Z: Part 1 -- **Part 1: Static Sites, Domains, DNS Records, and SSL/TLS Certificates** -- _[Part 2: Quick Start Guide - Setting Up GitLab Pages](getting_started_part_two.md)_ -- _[Part 3: Creating and Tweaking `.gitlab-ci.yml` for GitLab Pages](getting_started_part_three.md)_ +- **Part 1: Static sites and GitLab Pages domains** +- [Part 2: Quick start guide - Setting up GitLab Pages](getting_started_part_two.md) +- [Part 3: Setting Up Custom Domains - DNS Records and SSL/TLS Certificates](getting_started_part_three.md) +- [Part 4: Creating and tweaking `.gitlab-ci.yml` for GitLab Pages](getting_started_part_four.md) ----- +## GitLab Pages form A to Z This is a comprehensive guide, made for those who want to publish a website with GitLab Pages but aren't familiar with the entire process involved. +This [first part](#what-you-need-to-know-before-getting-started) of this series will present you to the concepts of +static sites, and go over how the default Pages domains work. + +The [second part](getting_started_part_two.md) covers how to get started with GitLab Pages: deploy +a website from a forked project or create a new one from scratch. + +The [third part](getting_started_part_three.md) will show you how to set up a custom domain or subdomain +to your site already deployed. + +The [fourth part](getting_started_part_four.md) will show you how to create and tweak GitLab CI for +GitLab Pages. + To **enable** GitLab Pages for GitLab CE (Community Edition) and GitLab EE (Enterprise Edition), please read the [admin documentation](https://docs.gitlab.com/ce/administration/pages/index.html), @@ -33,7 +46,7 @@ CSS, and JS, or use a [Static Site Generator (SSG)](https://www.staticgen.com/) to simplify your code and build the static site for you, which is highly recommendable and much faster than hardcoding. ---- +#### Further Reading - Read through this technical overview on [Static versus Dynamic Websites](https://about.gitlab.com/2016/06/03/ssg-overview-gitlab-pages-part-1-dynamic-x-static/) - Understand [how modern Static Site Generators work](https://about.gitlab.com/2016/06/10/ssg-overview-gitlab-pages-part-2/) and what you can add to your static site @@ -88,179 +101,6 @@ your website will be published under `https://websites.gitlab.io`. - On your GitLab instance, replace `gitlab.io` above with your Pages server domain. Ask your sysadmin for this information. -### DNS Records - -A Domain Name System (DNS) web service routes visitors to websites -by translating domain names (such as `www.example.com`) into the -numeric IP addresses (such as `192.0.2.1`) that computers use to -connect to each other. - -A DNS record is created to point a (sub)domain to a certain location, -which can be an IP address or another domain. In case you want to use -GitLab Pages with your own (sub)domain, you need to access your domain's -registrar control panel to add a DNS record pointing it back to your -GitLab Pages site. - -Note that **how to** add DNS records depends on which server your domain -is hosted on. Every control panel has its own place to do it. If you are -not an admin of your domain, and don't have access to your registrar, -you'll need to ask for the technical support of your hosting service -to do it for you. - -To help you out, we've gathered some instructions on how to do that -for the most popular hosting services: - -- [Amazon](http://docs.aws.amazon.com/gettingstarted/latest/swh/getting-started-configure-route53.html) -- [Bluehost](https://my.bluehost.com/cgi/help/559) -- [CloudFlare](https://support.cloudflare.com/hc/en-us/articles/200169096-How-do-I-add-A-records-) -- [cPanel](https://documentation.cpanel.net/display/ALD/Edit+DNS+Zone) -- [DreamHost](https://help.dreamhost.com/hc/en-us/articles/215414867-How-do-I-add-custom-DNS-records-) -- [Go Daddy](https://www.godaddy.com/help/add-an-a-record-19238) -- [Hostgator](http://support.hostgator.com/articles/changing-dns-records) -- [Inmotion hosting](https://my.bluehost.com/cgi/help/559) -- [Media Temple](https://mediatemple.net/community/products/dv/204403794/how-can-i-change-the-dns-records-for-my-domain) -- [Microsoft](https://msdn.microsoft.com/en-us/library/bb727018.aspx) - -If your hosting service is not listed above, you can just try to -search the web for "how to add dns record on <my hosting service>". - -#### DNS A record - -In case you want to point a root domain (`example.com`) to your -GitLab Pages site, deployed to `namespace.gitlab.io`, you need to -log into your domain's admin control panel and add a DNS `A` record -pointing your domain to Pages' server IP address. For projects on -GitLab.com, this IP is `104.208.235.32`. For projects leaving in -other GitLab instances (CE or EE), please contact your sysadmin -asking for this information (which IP address is Pages server -running on your instance). - -**Practical Example:** - -![DNS A record pointing to GitLab.com Pages server](img/dns_a_record_example.png) - -#### DNS CNAME record - -In case you want to point a subdomain (`hello-world.example.com`) -to your GitLab Pages site initially deployed to `namespace.gitlab.io`, -you need to log into your domain's admin control panel and add a DNS -`CNAME` record pointing your subdomain to your website URL -(`namespace.gitlab.io`) address. - -Notice that, despite it's a user or project website, the `CNAME` -should point to your Pages domain (`namespace.gitlab.io`), -without any `/project-name`. - -**Practical Example:** - -![DNS CNAME record pointing to GitLab.com project](img/dns_cname_record_example.png) - -#### TL;DR - -| From | DNS Record | To | -| ---- | ---------- | -- | -| domain.com | A | 104.208.235.32 | -| subdomain.domain.com | CNAME | namespace.gitlab.io | - -> **Notes**: -> -> - **Do not** use a CNAME record if you want to point your -`domain.com` to your GitLab Pages site. Use an `A` record instead. -> - **Do not** add any special chars after the default Pages -domain. E.g., **do not** point your `subdomain.domain.com` to -`namespace.gitlab.io.` or `namespace.gitlab.io/`. - -### SSL/TLS Certificates - -Every GitLab Pages project on GitLab.com will be available under -HTTPS for the default Pages domain (`*.gitlab.io`). Once you set -up your Pages project with your custom (sub)domain, if you want -it secured by HTTPS, you will have to issue a certificate for that -(sub)domain and install it on your project. - ->**Note:** -Certificates are NOT required to add to your custom -(sub)domain on your GitLab Pages project, though they are -highly recommendable. - -The importance of having any website securely served under HTTPS -is explained on the introductory section of the blog post -[Secure GitLab Pages with StartSSL](https://about.gitlab.com/2016/06/24/secure-gitlab-pages-with-startssl/#https-a-quick-overview). - -The reason why certificates are so important is that they encrypt -the connection between the **client** (you, me, your visitors) -and the **server** (where you site lives), through a keychain of -authentications and validations. - -### Issuing Certificates - -GitLab Pages accepts [PEM](https://support.quovadisglobal.com/kb/a37/what-is-pem-format.aspx) certificates issued by -[Certificate Authorities (CA)](https://en.wikipedia.org/wiki/Certificate_authority) -and self-signed certificates. Of course, -[you'd rather issue a certificate than generate a self-signed](https://en.wikipedia.org/wiki/Self-signed_certificate), -for security reasons and for having browsers trusting your -site's certificate. - -There are several different kinds of certificates, each one -with certain security level. A static personal website will -not require the same security level as an online banking web app, -for instance. There are a couple Certificate Authorities that -offer free certificates, aiming to make the internet more secure -to everyone. The most popular is [Let's Encrypt](https://letsencrypt.org/), -which issues certificates trusted by most of browsers, it's open -source, and free to use. Please read through this tutorial to -understand [how to secure your GitLab Pages website with Let's Encrypt](https://about.gitlab.com/2016/04/11/tutorial-securing-your-gitlab-pages-with-tls-and-letsencrypt/). - -With the same popularity, there are [certificates issued by CloudFlare](https://www.cloudflare.com/ssl/), -which also offers a [free CDN service](https://blog.cloudflare.com/cloudflares-free-cdn-and-you/). -Their certs are valid up to 15 years. Read through the tutorial on -[how to add a CloudFlare Certificate to your GitLab Pages website](https://about.gitlab.com/2017/02/07/setting-up-gitlab-pages-with-cloudflare-certificates/). - -### Adding certificates to your project - -Regardless the CA you choose, the steps to add your certificate to -your Pages project are the same. - -#### What do you need - -1. A PEM certificate -1. An intermediate certificate -1. A public key - -![Pages project - adding certificates](img/add_certificate_to_pages.png) - -These fields are found under your **Project**'s **Settings** > **Pages** > **New Domain**. - -#### What's what? - -- A PEM certificate is the certificate generated by the CA, -which needs to be added to the field **Certificate (PEM)**. -- An [intermediate certificate](https://en.wikipedia.org/wiki/Intermediate_certificate_authority) (aka "root certificate") is -the part of the encryption keychain that identifies the CA. -Usually it's combined with the PEM certificate, but there are -some cases in which you need to add them manually. -[CloudFlare certs](https://about.gitlab.com/2017/02/07/setting-up-gitlab-pages-with-cloudflare-certificates/) -are one of these cases. -- A public key is an encrypted key which validates -your PEM against your domain. - -#### Now what? - -Now that you hopefully understand why you need all -of this, it's simple: - -- Your PEM certificate needs to be added to the first field -- If your certificate is missing its intermediate, copy -and paste the root certificate (usually available from your CA website) -and paste it in the [same field as your PEM certificate](https://about.gitlab.com/2017/02/07/setting-up-gitlab-pages-with-cloudflare-certificates/), -just jumping a line between them. -- Copy your public key and paste it in the last field - ->**Note:** -**Do not** open certificates or encryption keys in -regular text editors. Always use code editors (such as -Sublime Text, Atom, Dreamweaver, Brackets, etc). - ||| |:--|--:| ||[**Part 2: Quick start guide - Setting up GitLab Pages →**](getting_started_part_two.md)| diff --git a/doc/user/project/pages/getting_started_part_three.md b/doc/user/project/pages/getting_started_part_three.md index ef47abef3a0..dba5fb6c17a 100644 --- a/doc/user/project/pages/getting_started_part_three.md +++ b/doc/user/project/pages/getting_started_part_three.md @@ -1,383 +1,189 @@ # GitLab Pages from A to Z: Part 3 -- _[Part 1: Static Sites, Domains, DNS Records, and SSL/TLS Certificates](getting_started_part_one.md)_ -- _[Part 2: Quick Start Guide - Setting Up GitLab Pages](getting_started_part_two.md)_ -- **Part 3: Creating and Tweaking `.gitlab-ci.yml` for GitLab Pages** - ---- - -## Creating and Tweaking `.gitlab-ci.yml` for GitLab Pages - -[GitLab CI](https://about.gitlab.com/gitlab-ci/) serves -numerous purposes, to build, test, and deploy your app -from GitLab through -[Continuous Integration, Continuous Delivery, and Continuous Deployment](https://about.gitlab.com/2016/08/05/continuous-integration-delivery-and-deployment-with-gitlab/) -methods. You will need it to build your website with GitLab Pages, -and deploy it to the Pages server. - -What this file actually does is telling the -[GitLab Runner](https://docs.gitlab.com/runner/) to run scripts -as you would do from the command line. The Runner acts as your -terminal. GitLab CI tells the Runner which commands to run. -Both are built-in in GitLab, and you don't need to set up -anything for them to work. - -Explaining [every detail of GitLab CI](https://docs.gitlab.com/ce/ci/yaml/README.html) -and GitLab Runner is out of the scope of this guide, but we'll -need to understand just a few things to be able to write our own -`.gitlab-ci.yml` or tweak an existing one. It's an -[Yaml](http://docs.ansible.com/ansible/YAMLSyntax.html) file, -with its own syntax. You can always check your CI syntax with -the [GitLab CI Lint Tool](https://gitlab.com/ci/lint). +- [Part 1: Static sites and GitLab Pages domains](getting_started_part_one.md) +- [Part 2: Quick start guide - Setting up GitLab Pages](getting_started_part_two.md) +- **Part 3: Setting Up Custom Domains - DNS Records and SSL/TLS Certificates** +- [Part 4: Creating and tweaking `.gitlab-ci.yml` for GitLab Pages](getting_started_part_four.md) + +## Setting Up Custom Domains - DNS Records and SSL/TLS Certificates + +As described in the previous part of this series, setting up GitLab Pages with custom domains, and adding SSL/TLS certificates to them, are optional features of GitLab Pages. + +These steps assume you've already [set your site up](getting_started_part_two.md) and and it's served under the default Pages domain `namespace.gitlab.io`, or `namespace.gitlab.io/project-name`. + +### DNS Records + +A Domain Name System (DNS) web service routes visitors to websites +by translating domain names (such as `www.example.com`) into the +numeric IP addresses (such as `192.0.2.1`) that computers use to +connect to each other. + +A DNS record is created to point a (sub)domain to a certain location, +which can be an IP address or another domain. In case you want to use +GitLab Pages with your own (sub)domain, you need to access your domain's +registrar control panel to add a DNS record pointing it back to your +GitLab Pages site. + +Note that **how to** add DNS records depends on which server your domain +is hosted on. Every control panel has its own place to do it. If you are +not an admin of your domain, and don't have access to your registrar, +you'll need to ask for the technical support of your hosting service +to do it for you. + +To help you out, we've gathered some instructions on how to do that +for the most popular hosting services: + +- [Amazon](http://docs.aws.amazon.com/gettingstarted/latest/swh/getting-started-configure-route53.html) +- [Bluehost](https://my.bluehost.com/cgi/help/559) +- [CloudFlare](https://support.cloudflare.com/hc/en-us/articles/200169096-How-do-I-add-A-records-) +- [cPanel](https://documentation.cpanel.net/display/ALD/Edit+DNS+Zone) +- [DreamHost](https://help.dreamhost.com/hc/en-us/articles/215414867-How-do-I-add-custom-DNS-records-) +- [Go Daddy](https://www.godaddy.com/help/add-an-a-record-19238) +- [Hostgator](http://support.hostgator.com/articles/changing-dns-records) +- [Inmotion hosting](https://my.bluehost.com/cgi/help/559) +- [Media Temple](https://mediatemple.net/community/products/dv/204403794/how-can-i-change-the-dns-records-for-my-domain) +- [Microsoft](https://msdn.microsoft.com/en-us/library/bb727018.aspx) + +If your hosting service is not listed above, you can just try to +search the web for "how to add dns record on <my hosting service>". + +#### DNS A record + +In case you want to point a root domain (`example.com`) to your +GitLab Pages site, deployed to `namespace.gitlab.io`, you need to +log into your domain's admin control panel and add a DNS `A` record +pointing your domain to Pages' server IP address. For projects on +GitLab.com, this IP is `104.208.235.32`. For projects leaving in +other GitLab instances (CE or EE), please contact your sysadmin +asking for this information (which IP address is Pages server +running on your instance). **Practical Example:** -Let's consider you have a [Jekyll](https://jekyllrb.com/) site. -To build it locally, you would open your terminal, and run `jekyll build`. -Of course, before building it, you had to install Jekyll in your computer. -For that, you had to open your terminal and run `gem install jekyll`. -Right? GitLab CI + GitLab Runner do the same thing. But you need to -write in the `.gitlab-ci.yml` the script you want to run so -GitLab Runner will do it for you. It looks more complicated then it -is. What you need to tell the Runner: - -``` -$ gem install jekyll -$ jekyll build -``` - -### Script - -To transpose this script to Yaml, it would be like this: - -```yaml -script: - - gem install jekyll - - jekyll build -``` - -### Job - -So far so good. Now, each `script`, in GitLab is organized by -a `job`, which is a bunch of scripts and settings you want to -apply to that specific task. - -```yaml -job: - script: - - gem install jekyll - - jekyll build -``` - -For GitLab Pages, this `job` has a specific name, called `pages`, -which tells the Runner you want that task to deploy your website -with GitLab Pages: - -```yaml -pages: - script: - - gem install jekyll - - jekyll build -``` - -### The `public` directory - -We also need to tell Jekyll where do you want the website to build, -and GitLab Pages will only consider files in a directory called `public`. -To do that with Jekyll, we need to add a flag specifying the -[destination (`-d`)](https://jekyllrb.com/docs/usage/) of the -built website: `jekyll build -d public`. Of course, we need -to tell this to our Runner: - -```yaml -pages: - script: - - gem install jekyll - - jekyll build -d public -``` - -### Artifacts - -We also need to tell the Runner that this _job_ generates -_artifacts_, which is the site built by Jekyll. -Where are these artifacts stored? In the `public` directory: - -```yaml -pages: - script: - - gem install jekyll - - jekyll build -d public - artifacts: - paths: - - public -``` - -The script above would be enough to build your Jekyll -site with GitLab Pages. But, from Jekyll 3.4.0 on, its default -template originated by `jekyll new project` requires -[Bundler](http://bundler.io/) to install Jekyll dependencies -and the default theme. To adjust our script to meet these new -requirements, we only need to install and build Jekyll with Bundler: - -```yaml -pages: - script: - - bundle install - - bundle exec jekyll build -d public - artifacts: - paths: - - public -``` - -That's it! A `.gitlab-ci.yml` with the content above would deploy -your Jekyll 3.4.0 site with GitLab Pages. This is the minimum -configuration for our example. On the steps below, we'll refine -the script by adding extra options to our GitLab CI. - -### Image - -At this point, you probably ask yourself: "okay, but to install Jekyll -I need Ruby. Where is Ruby on that script?". The answer is simple: the -first thing GitLab Runner will look for in your `.gitlab-ci.yml` is a -[Docker](https://www.docker.com/) image specifying what do you need in -your container to run that script: - -```yaml -image: ruby:2.3 - -pages: - script: - - bundle install - - bundle exec jekyll build -d public - artifacts: - paths: - - public -``` - -In this case, you're telling the Runner to pull this image, which -contains Ruby 2.3 as part of its file system. When you don't specify -this image in your configuration, the Runner will use a default -image, which is Ruby 2.1. - -If your SSG needs [NodeJS](https://nodejs.org/) to build, you'll -need to specify which image you want to use, and this image should -contain NodeJS as part of its file system. E.g., for a -[Hexo](https://gitlab.com/pages/hexo) site, you can use `image: node:4.2.2`. +![DNS A record pointing to GitLab.com Pages server](img/dns_a_record_example.png) + +#### DNS CNAME record + +In case you want to point a subdomain (`hello-world.example.com`) +to your GitLab Pages site initially deployed to `namespace.gitlab.io`, +you need to log into your domain's admin control panel and add a DNS +`CNAME` record pointing your subdomain to your website URL +(`namespace.gitlab.io`) address. + +Notice that, despite it's a user or project website, the `CNAME` +should point to your Pages domain (`namespace.gitlab.io`), +without any `/project-name`. + +**Practical Example:** + +![DNS CNAME record pointing to GitLab.com project](img/dns_cname_record_example.png) + +#### TL;DR + +| From | DNS Record | To | +| ---- | ---------- | -- | +| domain.com | A | 104.208.235.32 | +| subdomain.domain.com | CNAME | namespace.gitlab.io | + +> **Notes**: +> +> - **Do not** use a CNAME record if you want to point your +`domain.com` to your GitLab Pages site. Use an `A` record instead. +> - **Do not** add any special chars after the default Pages +domain. E.g., **do not** point your `subdomain.domain.com` to +`namespace.gitlab.io.` or `namespace.gitlab.io/`. + +### SSL/TLS Certificates + +Every GitLab Pages project on GitLab.com will be available under +HTTPS for the default Pages domain (`*.gitlab.io`). Once you set +up your Pages project with your custom (sub)domain, if you want +it secured by HTTPS, you will have to issue a certificate for that +(sub)domain and install it on your project. + +>**Note:** +Certificates are NOT required to add to your custom +(sub)domain on your GitLab Pages project, though they are +highly recommendable. + +The importance of having any website securely served under HTTPS +is explained on the introductory section of the blog post +[Secure GitLab Pages with StartSSL](https://about.gitlab.com/2016/06/24/secure-gitlab-pages-with-startssl/#https-a-quick-overview). + +The reason why certificates are so important is that they encrypt +the connection between the **client** (you, me, your visitors) +and the **server** (where you site lives), through a keychain of +authentications and validations. + +### Issuing Certificates + +GitLab Pages accepts [PEM](https://support.quovadisglobal.com/kb/a37/what-is-pem-format.aspx) certificates issued by +[Certificate Authorities (CA)](https://en.wikipedia.org/wiki/Certificate_authority) +and self-signed certificates. Of course, +[you'd rather issue a certificate than generate a self-signed](https://en.wikipedia.org/wiki/Self-signed_certificate), +for security reasons and for having browsers trusting your +site's certificate. + +There are several different kinds of certificates, each one +with certain security level. A static personal website will +not require the same security level as an online banking web app, +for instance. There are a couple Certificate Authorities that +offer free certificates, aiming to make the internet more secure +to everyone. The most popular is [Let's Encrypt](https://letsencrypt.org/), +which issues certificates trusted by most of browsers, it's open +source, and free to use. Please read through this tutorial to +understand [how to secure your GitLab Pages website with Let's Encrypt](https://about.gitlab.com/2016/04/11/tutorial-securing-your-gitlab-pages-with-tls-and-letsencrypt/). + +With the same popularity, there are [certificates issued by CloudFlare](https://www.cloudflare.com/ssl/), +which also offers a [free CDN service](https://blog.cloudflare.com/cloudflares-free-cdn-and-you/). +Their certs are valid up to 15 years. Read through the tutorial on +[how to add a CloudFlare Certificate to your GitLab Pages website](https://about.gitlab.com/2017/02/07/setting-up-gitlab-pages-with-cloudflare-certificates/). + +### Adding certificates to your project + +Regardless the CA you choose, the steps to add your certificate to +your Pages project are the same. + +#### What do you need + +1. A PEM certificate +1. An intermediate certificate +1. A public key + +![Pages project - adding certificates](img/add_certificate_to_pages.png) + +These fields are found under your **Project**'s **Settings** > **Pages** > **New Domain**. + +#### What's what? + +- A PEM certificate is the certificate generated by the CA, +which needs to be added to the field **Certificate (PEM)**. +- An [intermediate certificate](https://en.wikipedia.org/wiki/Intermediate_certificate_authority) (aka "root certificate") is +the part of the encryption keychain that identifies the CA. +Usually it's combined with the PEM certificate, but there are +some cases in which you need to add them manually. +[CloudFlare certs](https://about.gitlab.com/2017/02/07/setting-up-gitlab-pages-with-cloudflare-certificates/) +are one of these cases. +- A public key is an encrypted key which validates +your PEM against your domain. + +#### Now what? + +Now that you hopefully understand why you need all +of this, it's simple: + +- Your PEM certificate needs to be added to the first field +- If your certificate is missing its intermediate, copy +and paste the root certificate (usually available from your CA website) +and paste it in the [same field as your PEM certificate](https://about.gitlab.com/2017/02/07/setting-up-gitlab-pages-with-cloudflare-certificates/), +just jumping a line between them. +- Copy your public key and paste it in the last field >**Note:** -We're not trying to explain what a Docker image is, -we just need to introduce the concept with a minimum viable -explanation. To know more about Docker images, please visit -their website or take a look at a -[summarized explanation](http://paislee.io/how-to-automate-docker-deployments/) here. - -Let's go a little further. - -### Branching - -If you use GitLab as a version control platform, you will have your -branching strategy to work on your project. Meaning, you will have -other branches in your project, but you'll want only pushes to the -default branch (usually `master`) to be deployed to your website. -To do that, we need to add another line to our CI, telling the Runner -to only perform that _job_ called `pages` on the `master` branch `only`: - -```yaml -image: ruby:2.3 - -pages: - script: - - bundle install - - bundle exec jekyll build -d public - artifacts: - paths: - - public - only: - - master -``` - -### Stages - -Another interesting concept to keep in mind are build stages. -Your web app can pass through a lot of tests and other tasks -until it's deployed to staging or production environments. -There are three default stages on GitLab CI: build, test, -and deploy. To specify which stage your _job_ is running, -simply add another line to your CI: - -```yaml -image: ruby:2.3 - -pages: - stage: deploy - script: - - bundle install - - bundle exec jekyll build -d public - artifacts: - paths: - - public - only: - - master -``` - -You might ask yourself: "why should I bother with stages -at all?" Well, let's say you want to be able to test your -script and check the built site before deploying your site -to production. You want to run the test exactly as your -script will do when you push to `master`. It's simple, -let's add another task (_job_) to our CI, telling it to -test every push to other branches, `except` the `master` branch: - -```yaml -image: ruby:2.3 - -pages: - stage: deploy - script: - - bundle install - - bundle exec jekyll build -d public - artifacts: - paths: - - public - only: - - master - -test: - stage: test - script: - - bundle install - - bundle exec jekyll build -d test - artifacts: - paths: - - test - except: - - master -``` - -The `test` job is running on the stage `test`, Jekyll -will build the site in a directory called `test`, and -this job will affect all the branches except `master`. - -The best benefit of applying _stages_ to different -_jobs_ is that every job in the same stage builds in -parallel. So, if your web app needs more than one test -before being deployed, you can run all your test at the -same time, it's not necessary to wait one test to finish -to run the other. Of course, this is just a brief -introduction of GitLab CI and GitLab Runner, which are -tools much more powerful than that. This is what you -need to be able to create and tweak your builds for -your GitLab Pages site. - -### Before Script - -To avoid running the same script multiple times across -your _jobs_, you can add the parameter `before_script`, -in which you specify which commands you want to run for -every single _job_. In our example, notice that we run -`bundle install` for both jobs, `pages` and `test`. -We don't need to repeat it: - -```yaml -image: ruby:2.3 - -before_script: - - bundle install - -pages: - stage: deploy - script: - - bundle exec jekyll build -d public - artifacts: - paths: - - public - only: - - master - -test: - stage: test - script: - - bundle exec jekyll build -d test - artifacts: - paths: - - test - except: - - master -``` - -### Caching Dependencies - -If you want to cache the installation files for your -projects dependencies, for building faster, you can -use the parameter `cache`. For this example, we'll -cache Jekyll dependencies in a `vendor` directory -when we run `bundle install`: - -```yaml -image: ruby:2.3 - -cache: - paths: - - vendor/ - -before_script: - - bundle install --path vendor - -pages: - stage: deploy - script: - - bundle exec jekyll build -d public - artifacts: - paths: - - public - only: - - master - -test: - stage: test - script: - - bundle exec jekyll build -d test - artifacts: - paths: - - test - except: - - master -``` - -For this specific case, we need to exclude `/vendor` -from Jekyll `_config.yml` file, otherwise Jekyll will -understand it as a regular directory to build -together with the site: - -```yml -exclude: - - vendor -``` - -There we go! Now our GitLab CI not only builds our website, -but also **continuously test** pushes to feature-branches, -**caches** dependencies installed with Bundler, and -**continuously deploy** every push to the `master` branch. - -## Advanced GitLab CI for GitLab Pages - -What you can do with GitLab CI is pretty much up to your -creativity. Once you get used to it, you start creating -awesome scripts that automate most of tasks you'd do -manually in the past. Read through the -[documentation of GitLab CI](https://docs.gitlab.com/ce/ci/yaml/README.html) -to understand how to go even further on your scripts. - -- On this blog post, understand the concept of -[using GitLab CI `environments` to deploy your -web app to staging and production](https://about.gitlab.com/2016/08/26/ci-deployment-and-environments/). -- On this post, learn [how to run jobs sequentially, -in parallel, or build a custom pipeline](https://about.gitlab.com/2016/07/29/the-basics-of-gitlab-ci/) -- On this blog post, we go through the process of -[pulling specific directories from different projects](https://about.gitlab.com/2016/12/07/building-a-new-gitlab-docs-site-with-nanoc-gitlab-ci-and-gitlab-pages/) -to deploy this website you're looking at, docs.gitlab.com. -- On this blog post, we teach you [how to use GitLab Pages to produce a code coverage report](https://about.gitlab.com/2016/11/03/publish-code-coverage-report-with-gitlab-pages/). +**Do not** open certificates or encryption keys in +regular text editors. Always use code editors (such as +Sublime Text, Atom, Dreamweaver, Brackets, etc). ||| |:--|--:| -|[**← Part 2: Quick start guide - Setting up GitLab Pages**](getting_started_part_two.md)|| +|[**← Part 2: Quick start guide - Setting up GitLab Pages**](getting_started_part_two.md)|[**Part 4: Creating and tweaking `.gitlab-ci.yml` for GitLab Pages →**](getting_started_part_four.md)| diff --git a/doc/user/project/pages/getting_started_part_two.md b/doc/user/project/pages/getting_started_part_two.md index 07dd24122c4..d0e2c467fee 100644 --- a/doc/user/project/pages/getting_started_part_two.md +++ b/doc/user/project/pages/getting_started_part_two.md @@ -1,14 +1,9 @@ # GitLab Pages from A to Z: Part 2 -> Type: user guide -> -> Level: beginner - -- _[Part 1: Static Sites, Domains, DNS Records, and SSL/TLS Certificates](getting_started_part_one.md)_ -- **Part 2: Quick Start Guide - Setting Up GitLab Pages** -- _[Part 3: Creating and Tweaking `.gitlab-ci.yml` for GitLab Pages](getting_started_part_three.md)_ - ----- +- [Part 1: Static sites and GitLab Pages domains](getting_started_part_one.md) +- **Part 2: Quick start guide - Setting up GitLab Pages** +- [Part 3: Setting Up Custom Domains - DNS Records and SSL/TLS Certificates](getting_started_part_three.md) +- [Part 4: Creating and tweaking `.gitlab-ci.yml` for GitLab Pages](getting_started_part_four.md) ## Setting up GitLab Pages @@ -30,6 +25,8 @@ Optional Features: 1. **Optional**: an SSL/TLS certificate so your custom domain is accessible under HTTPS. +The optional settings, custom domain, DNS records, and SSL/TLS certificates, are described in [Part 3](getting_started_part_three.md)). + ## Project Your GitLab Pages project is a regular project created the @@ -106,7 +103,7 @@ where you'll find its default URL. > - GitLab Pages [supports any SSG](https://about.gitlab.com/2016/06/17/ssg-overview-gitlab-pages-part-3-examples-ci/), but, if you don't find yours among the templates, you'll need to configure your own `.gitlab-ci.yml`. Do do that, please -read through the article [Creating and Tweaking `.gitlab-ci.yml` for GitLab Pages](getting_started_part_three.md). New SSGs are very welcome among +read through the article [Creating and Tweaking `.gitlab-ci.yml` for GitLab Pages](getting_started_part_four.md). New SSGs are very welcome among the [example projects](https://gitlab.com/pages). If you set up a new one, please [contribute](https://gitlab.com/pages/pages.gitlab.io/blob/master/CONTRIBUTING.md) @@ -147,6 +144,11 @@ example we've just mentioned, you'd have to change Jekyll's `_config.yml` to: baseurl: "" ``` +### Custom Domains + +GitLab Pages supports custom domains and subdomains, served under HTTPS or HTTPS. +Please check the [next part](getting_started_part_three.md) of this series for an overview. + ||| |:--|--:| -|[**← Part 1: Static sites, domains, DNS records, and SSL/TLS certificates**](getting_started_part_one.md)|[**Part 3: Creating and tweaking `.gitlab-ci.yml` for GitLab Pages →**](getting_started_part_three.md)| +|[**← Part 1: Static sites, domains, DNS records, and SSL/TLS certificates**](getting_started_part_one.md)|[**Setting Up Custom Domains - DNS Records and SSL/TLS Certificates →**](getting_started_part_three.md)| diff --git a/doc/user/project/pages/index.md b/doc/user/project/pages/index.md index dbb9d9ad9c4..1366756d593 100644 --- a/doc/user/project/pages/index.md +++ b/doc/user/project/pages/index.md @@ -1,49 +1,48 @@ -# All you need to know about GitLab Pages +# GitLab Pages documentation With GitLab Pages you can create static websites for your GitLab projects, groups, or user accounts. You can use any static website generator: Jekyll, Middleman, Hexo, Hugo, Pelican, you name it! Connect as many customs domains as you like and bring your own TLS certificate to secure them. -Here's some info we have gathered to get you started. +Here's some info we've gathered to get you started. ## General info - [Product webpage](https://pages.gitlab.io) -- [We're bringing GitLab Pages to CE](https://about.gitlab.com/2016/12/24/were-bringing-gitlab-pages-to-community-edition/) +- ["We're bringing GitLab Pages to CE" blog post](https://about.gitlab.com/2016/12/24/were-bringing-gitlab-pages-to-community-edition/) - [Pages group - templates](https://gitlab.com/pages) +- [General user documentation](introduction.md) +- [Admin documentation - Set GitLab Pages on your own GitLab instance](../../../administration/pages/index.md) ## Getting started -- GitLab Pages from A to Z - - [Part 1: Static sites, domains, DNS records, and SSL/TLS certificates](getting_started_part_one.md) +- **GitLab Pages from A to Z** + - [Part 1: Static sites and GitLab Pages domains](getting_started_part_one.md) - [Part 2: Quick start guide - Setting up GitLab Pages](getting_started_part_two.md) - - [Part 3: Creating and tweaking `.gitlab-ci.yml` for GitLab Pages](getting_started_part_three.md) -- [Hosting on GitLab.com with GitLab Pages](https://about.gitlab.com/2016/04/07/gitlab-pages-setup/) a comprehensive step-by-step guide -- Secure GitLab Pages custom domain with SSL/TLS certificates - - [Let's Encrypt](https://about.gitlab.com/2016/04/11/tutorial-securing-your-gitlab-pages-with-tls-and-letsencrypt/) - - [CloudFlare](https://about.gitlab.com/2017/02/07/setting-up-gitlab-pages-with-cloudflare-certificates/) - - [StartSSL](https://about.gitlab.com/2016/06/24/secure-gitlab-pages-with-startssl/) -- Static Site Generators - Blog posts series + - [Part 3: Setting Up Custom Domains - DNS Records and SSL/TLS Certificates](getting_started_part_three.md) + - [Part 4: Creating and tweaking `.gitlab-ci.yml` for GitLab Pages](getting_started_part_four.md) +- **Static Site Generators - Blog posts series** - [SSGs part 1: Static vs dynamic websites](https://about.gitlab.com/2016/06/03/ssg-overview-gitlab-pages-part-1-dynamic-x-static/) - [SSGs part 2: Modern static site generators](https://about.gitlab.com/2016/06/10/ssg-overview-gitlab-pages-part-2/) - [SSGs part 3: Build any SSG site with GitLab Pages](https://about.gitlab.com/2016/06/17/ssg-overview-gitlab-pages-part-3-examples-ci/) -- [Posting to your GitLab Pages blog from iOS](https://about.gitlab.com/2016/08/19/posting-to-your-gitlab-pages-blog-from-ios/) +- **Secure GitLab Pages custom domain with SSL/TLS certificates** + - [Let's Encrypt](https://about.gitlab.com/2016/04/11/tutorial-securing-your-gitlab-pages-with-tls-and-letsencrypt/) + - [CloudFlare](https://about.gitlab.com/2017/02/07/setting-up-gitlab-pages-with-cloudflare-certificates/) + - [StartSSL](https://about.gitlab.com/2016/06/24/secure-gitlab-pages-with-startssl/) +- **General** + - [Hosting on GitLab.com with GitLab Pages](https://about.gitlab.com/2016/04/07/gitlab-pages-setup/) a comprehensive step-by-step guide + - [Posting to your GitLab Pages blog from iOS](https://about.gitlab.com/2016/08/19/posting-to-your-gitlab-pages-blog-from-ios/) ## Video tutorials - [How to publish a website with GitLab Pages on GitLab.com: from a forked project](https://youtu.be/TWqh9MtT4Bg) -- [How to Enable GitLab Pages for GitLab CE and EE](https://youtu.be/dD8c7WNcc6s) +- [How to Enable GitLab Pages for GitLab CE and EE (for Admins only)](https://youtu.be/dD8c7WNcc6s) ## Advanced use -- Blog Posts: +- **Blog Posts** - [GitLab CI: Run jobs sequentially, in parallel, or build a custom pipeline](https://about.gitlab.com/2016/07/29/the-basics-of-gitlab-ci/) - [GitLab CI: Deployment & environments](https://about.gitlab.com/2016/08/26/ci-deployment-and-environments/) - [Building a new GitLab docs site with Nanoc, GitLab CI, and GitLab Pages](https://about.gitlab.com/2016/12/07/building-a-new-gitlab-docs-site-with-nanoc-gitlab-ci-and-gitlab-pages/) - [Publish code coverage reports with GitLab Pages](https://about.gitlab.com/2016/11/03/publish-code-coverage-report-with-gitlab-pages/) - -## Specific documentation - -- [User docs](introduction.md) -- [Admin docs](../../../administration/pages/index.md) diff --git a/doc/user/project/slash_commands.md b/doc/user/project/slash_commands.md index ad5d51d34f2..45176fde9db 100644 --- a/doc/user/project/slash_commands.md +++ b/doc/user/project/slash_commands.md @@ -35,3 +35,4 @@ do. | <code>/spend <1h 30m | -1h 5m></code> | Add or subtract spent time | | `/remove_time_spent` | Remove time spent | | `/target_branch <Branch Name>` | Set target branch for current merge request | +| `/award :emoji:` | Toggle award for :emoji: | diff --git a/doc/workflow/shortcuts.md b/doc/workflow/shortcuts.md index 2a5e622dc7d..65e67aa1512 100644 --- a/doc/workflow/shortcuts.md +++ b/doc/workflow/shortcuts.md @@ -42,12 +42,10 @@ You can see GitLab's keyboard shortcuts by using 'shift + ?' | Keyboard Shortcut | Description | | ----------------- | ----------- | | <kbd>g</kbd> + <kbd>p</kbd> | Go to the project's home page | -| <kbd>g</kbd> + <kbd>e</kbd> | Go to the project's activity feed | | <kbd>g</kbd> + <kbd>f</kbd> | Go to files | | <kbd>g</kbd> + <kbd>c</kbd> | Go to commits | | <kbd>g</kbd> + <kbd>b</kbd> | Go to jobs | | <kbd>g</kbd> + <kbd>n</kbd> | Go to network graph | -| <kbd>g</kbd> + <kbd>g</kbd> | Go to graphs | | <kbd>g</kbd> + <kbd>i</kbd> | Go to issues | | <kbd>g</kbd> + <kbd>m</kbd> | Go to merge requests | | <kbd>g</kbd> + <kbd>s</kbd> | Go to snippets | diff --git a/features/project/active_tab.feature b/features/project/active_tab.feature index 5c14c5db665..1dd2bdd9b36 100644 --- a/features/project/active_tab.feature +++ b/features/project/active_tab.feature @@ -80,9 +80,9 @@ Feature: Project Active Tab And no other sub tabs should be active And the active main tab should be Repository - Scenario: On Project Repository/Network - Given I visit my project's network page - Then the active sub tab should be Network + Scenario: On Project Repository/Graph + Given I visit my project's graph page + Then the active sub tab should be Graph And no other sub tabs should be active And the active main tab should be Repository diff --git a/features/project/graph.feature b/features/project/graph.feature index 63793d6f989..b25c73ad870 100644 --- a/features/project/graph.feature +++ b/features/project/graph.feature @@ -9,9 +9,10 @@ Feature: Project Graph Then page should have graphs @javascript - Scenario: I should see project commits graphs + Scenario: I should see project languages & commits graphs on commits graph url When I visit project "Shop" commits graph page Then page should have commits graphs + Then page should have languages graphs @javascript Scenario: I should see project ci graphs @@ -20,6 +21,13 @@ Feature: Project Graph Then page should have CI graphs @javascript - Scenario: I should see project languages graphs + Scenario: I should see project languages & commits graphs on language graph url When I visit project "Shop" languages graph page Then page should have languages graphs + Then page should have commits graphs + + @javascript + Scenario: I should see project languages & commits graphs on charts url + When I visit project "Shop" chart page + Then page should have languages graphs + Then page should have commits graphs diff --git a/features/project/issues/award_emoji.feature b/features/project/issues/award_emoji.feature index f0fd414a9f9..1d7adfdd2c2 100644 --- a/features/project/issues/award_emoji.feature +++ b/features/project/issues/award_emoji.feature @@ -42,4 +42,4 @@ Feature: Award Emoji @javascript Scenario: I add award emoji using regular comment Given I leave comment with a single emoji - Then I have award added + Then I have new comment with emoji added diff --git a/features/project/shortcuts.feature b/features/project/shortcuts.feature index f71f69ef060..95de63ba21a 100644 --- a/features/project/shortcuts.feature +++ b/features/project/shortcuts.feature @@ -19,17 +19,12 @@ Feature: Project Shortcuts Then the active sub tab should be Commits @javascript - Scenario: Navigate to network tab + Scenario: Navigate to graph tab Given I press "g" and "n" - Then the active sub tab should be Network + Then the active sub tab should be Graph And the active main tab should be Repository @javascript - Scenario: Navigate to graphs tab - Given I press "g" and "g" - Then the active main tab should be Graphs - - @javascript Scenario: Navigate to issues tab Given I press "g" and "i" Then the active main tab should be Issues @@ -53,8 +48,3 @@ Feature: Project Shortcuts Scenario: Navigate to project home Given I press "g" and "p" Then the active main tab should be Home - - @javascript - Scenario: Navigate to project feed - Given I press "g" and "e" - Then the active main tab should be Activity diff --git a/features/steps/project/graph.rb b/features/steps/project/graph.rb index 48ac7a98f0d..176d04d721c 100644 --- a/features/steps/project/graph.rb +++ b/features/steps/project/graph.rb @@ -18,6 +18,10 @@ class Spinach::Features::ProjectGraph < Spinach::FeatureSteps visit languages_namespace_project_graph_path(project.namespace, project, "master") end + step 'I visit project "Shop" chart page' do + visit charts_namespace_project_graph_path(project.namespace, project, "master") + end + step 'page should have languages graphs' do expect(page).to have_content /Ruby 66.* %/ expect(page).to have_content /JavaScript 22.* %/ diff --git a/features/steps/project/issues/award_emoji.rb b/features/steps/project/issues/award_emoji.rb index cbe5738e7e4..dd7a58b454a 100644 --- a/features/steps/project/issues/award_emoji.rb +++ b/features/steps/project/issues/award_emoji.rb @@ -44,6 +44,10 @@ class Spinach::Features::AwardEmoji < Spinach::FeatureSteps end end + step 'I have new comment with emoji added' do + expect(page).to have_selector ".emoji[title=':smile:']" + end + step 'I have award added' do page.within '.awards' do expect(page).to have_selector '.js-emoji-btn' diff --git a/features/steps/project/network_graph.rb b/features/steps/project/network_graph.rb index ff9251615c9..370e46265c7 100644 --- a/features/steps/project/network_graph.rb +++ b/features/steps/project/network_graph.rb @@ -66,7 +66,7 @@ class Spinach::Features::ProjectNetworkGraph < Spinach::FeatureSteps end step 'page should have "v1.0.0" in title' do - expect(page).to have_css 'title', text: 'Network · v1.0.0', visible: false + expect(page).to have_css 'title', text: 'Graph · v1.0.0', visible: false end step 'page should only have content from "v1.0.0"' do diff --git a/features/steps/project/project_shortcuts.rb b/features/steps/project/project_shortcuts.rb index 8143b01ca40..02c08b784bc 100644 --- a/features/steps/project/project_shortcuts.rb +++ b/features/steps/project/project_shortcuts.rb @@ -34,9 +34,4 @@ class Spinach::Features::ProjectShortcuts < Spinach::FeatureSteps find('body').native.send_key('g') find('body').native.send_key('w') end - - step 'I press "g" and "e"' do - find('body').native.send_key('g') - find('body').native.send_key('e') - end end diff --git a/features/steps/shared/paths.rb b/features/steps/shared/paths.rb index 718cf924729..d5b3bb34d7a 100644 --- a/features/steps/shared/paths.rb +++ b/features/steps/shared/paths.rb @@ -232,7 +232,7 @@ module SharedPaths visit stats_namespace_project_repository_path(@project.namespace, @project) end - step "I visit my project's network page" do + step "I visit my project's graph page" do # Stub Graph max_size to speed up test (10 commits vs. 650) Network::Graph.stub(max_count: 10) diff --git a/features/steps/shared/project_tab.rb b/features/steps/shared/project_tab.rb index d6024212601..83446afe424 100644 --- a/features/steps/shared/project_tab.rb +++ b/features/steps/shared/project_tab.rb @@ -12,10 +12,6 @@ module SharedProjectTab ensure_active_main_tab('Repository') end - step 'the active main tab should be Graphs' do - ensure_active_main_tab('Graphs') - end - step 'the active main tab should be Issues' do ensure_active_main_tab('Issues') end @@ -40,12 +36,8 @@ module SharedProjectTab expect(page).to have_selector('.layout-nav .nav-links > li.active', count: 0) end - step 'the active main tab should be Activity' do - ensure_active_main_tab('Activity') - end - - step 'the active sub tab should be Network' do - ensure_active_sub_tab('Network') + step 'the active sub tab should be Graph' do + ensure_active_sub_tab('Graph') end step 'the active sub tab should be Files' do diff --git a/lib/api/api.rb b/lib/api/api.rb index b27ac3f1d15..3e53ab693ab 100644 --- a/lib/api/api.rb +++ b/lib/api/api.rb @@ -21,11 +21,14 @@ module API mount ::API::V3::MergeRequests mount ::API::V3::Notes mount ::API::V3::ProjectHooks + mount ::API::V3::Milestones mount ::API::V3::Projects mount ::API::V3::ProjectSnippets mount ::API::V3::Repositories mount ::API::V3::Runners mount ::API::V3::Services + mount ::API::V3::Settings + mount ::API::V3::Snippets mount ::API::V3::Subscriptions mount ::API::V3::SystemHooks mount ::API::V3::Tags diff --git a/lib/api/api_guard.rb b/lib/api/api_guard.rb index c11f8529183..409cb5b924f 100644 --- a/lib/api/api_guard.rb +++ b/lib/api/api_guard.rb @@ -160,13 +160,10 @@ module API # Exceptions # - class MissingTokenError < StandardError; end - - class TokenNotFoundError < StandardError; end - - class ExpiredError < StandardError; end - - class RevokedError < StandardError; end + MissingTokenError = Class.new(StandardError) + TokenNotFoundError = Class.new(StandardError) + ExpiredError = Class.new(StandardError) + RevokedError = Class.new(StandardError) class InsufficientScopeError < StandardError attr_reader :scopes diff --git a/lib/api/commit_statuses.rb b/lib/api/commit_statuses.rb index dba0831664c..9d9f82fdb83 100644 --- a/lib/api/commit_statuses.rb +++ b/lib/api/commit_statuses.rb @@ -72,14 +72,15 @@ module API status = GenericCommitStatus.running_or_pending.find_or_initialize_by( project: @project, pipeline: pipeline, - user: current_user, name: name, ref: ref, - target_url: params[:target_url], - description: params[:description], - coverage: params[:coverage] + user: current_user ) + optional_attributes = + attributes_for_keys(%w[target_url description coverage]) + + status.update(optional_attributes) if optional_attributes.any? render_validation_error!(status) if status.invalid? begin diff --git a/lib/api/entities.rb b/lib/api/entities.rb index a99d9cadc8a..d2d21f5d03a 100644 --- a/lib/api/entities.rb +++ b/lib/api/entities.rb @@ -69,9 +69,8 @@ module API class Project < Grape::Entity expose :id, :description, :default_branch, :tag_list - expose :public?, as: :public expose :archived?, as: :archived - expose :visibility_level, :ssh_url_to_repo, :http_url_to_repo, :web_url + expose :visibility, :ssh_url_to_repo, :http_url_to_repo, :web_url expose :owner, using: Entities::UserBasic, unless: ->(project, options) { project.group } expose :name, :name_with_namespace expose :path, :path_with_namespace @@ -98,7 +97,7 @@ module API expose :shared_with_groups do |project, options| SharedGroup.represent(project.project_group_links.all, options) end - expose :only_allow_merge_if_build_succeeds + expose :only_allow_merge_if_pipeline_succeeds expose :request_access_enabled expose :only_allow_merge_if_all_discussions_are_resolved @@ -132,7 +131,7 @@ module API end class Group < Grape::Entity - expose :id, :name, :path, :description, :visibility_level + expose :id, :name, :path, :description, :visibility expose :lfs_enabled?, as: :lfs_enabled expose :avatar_url expose :web_url @@ -288,7 +287,7 @@ module API expose :label_names, as: :labels expose :work_in_progress?, as: :work_in_progress expose :milestone, using: Entities::Milestone - expose :merge_when_build_succeeds + expose :merge_when_pipeline_succeeds expose :merge_status expose :diff_head_sha, as: :sha expose :merge_commit_sha @@ -394,7 +393,8 @@ module API expose :target_type expose :target do |todo, options| - Entities.const_get(todo.target_type).represent(todo.target, options) + target = todo.target_type == 'Commit' ? 'RepoCommit' : todo.target_type + Entities.const_get(target).represent(todo.target, options) end expose :target_url do |todo, options| @@ -551,12 +551,14 @@ module API expose :updated_at expose :home_page_url expose :default_branch_protection - expose :restricted_visibility_levels + expose(:restricted_visibility_levels) do |setting, _options| + setting.restricted_visibility_levels.map { |level| Gitlab::VisibilityLevel.string_level(level) } + end expose :max_attachment_size expose :session_expire_delay - expose :default_project_visibility - expose :default_snippet_visibility - expose :default_group_visibility + expose(:default_project_visibility) { |setting, _options| Gitlab::VisibilityLevel.string_level(setting.default_project_visibility) } + expose(:default_snippet_visibility) { |setting, _options| Gitlab::VisibilityLevel.string_level(setting.default_snippet_visibility) } + expose(:default_group_visibility) { |setting, _options| Gitlab::VisibilityLevel.string_level(setting.default_group_visibility) } expose :default_artifacts_expire_in expose :domain_whitelist expose :domain_blacklist_enabled diff --git a/lib/api/environments.rb b/lib/api/environments.rb index dbdf29a9640..ebe8c3a5b2c 100644 --- a/lib/api/environments.rb +++ b/lib/api/environments.rb @@ -81,6 +81,23 @@ module API environment.destroy end + + desc 'Stops an existing environment' do + success Entities::Environment + end + params do + requires :environment_id, type: Integer, desc: 'The environment ID' + end + post ':id/environments/:environment_id/stop' do + authorize! :create_deployment, user_project + + environment = user_project.environments.find(params[:environment_id]) + + environment.stop_with_action!(current_user) + + status 200 + present environment, with: Entities::Environment + end end end end diff --git a/lib/api/groups.rb b/lib/api/groups.rb index 9cffd6180ae..b862ff70b31 100644 --- a/lib/api/groups.rb +++ b/lib/api/groups.rb @@ -7,7 +7,7 @@ module API helpers do params :optional_params do optional :description, type: String, desc: 'The description of the group' - optional :visibility_level, type: Integer, desc: 'The visibility level of the group' + optional :visibility, type: String, values: Gitlab::VisibilityLevel.string_values, desc: 'The visibility of the group' optional :lfs_enabled, type: Boolean, desc: 'Enable/disable LFS for the projects in this group' optional :request_access_enabled, type: Boolean, desc: 'Allow users to request member access' end @@ -92,7 +92,7 @@ module API optional :name, type: String, desc: 'The name of the group' optional :path, type: String, desc: 'The path of the group' use :optional_params - at_least_one_of :name, :path, :description, :visibility_level, + at_least_one_of :name, :path, :description, :visibility, :lfs_enabled, :request_access_enabled end put ':id' do @@ -126,7 +126,7 @@ module API end params do optional :archived, type: Boolean, default: false, desc: 'Limit by archived status' - optional :visibility, type: String, values: %w[public internal private], + optional :visibility, type: String, values: Gitlab::VisibilityLevel.string_values, desc: 'Limit by visibility' optional :search, type: String, desc: 'Return list of authorized projects matching the search criteria' optional :order_by, type: String, values: %w[id name path created_at updated_at last_activity_at], diff --git a/lib/api/helpers.rb b/lib/api/helpers.rb index 72d2b320077..4600abc7dc7 100644 --- a/lib/api/helpers.rb +++ b/lib/api/helpers.rb @@ -164,6 +164,10 @@ module API items.where(iid: iid) end + def filter_by_search(items, text) + items.search(text) + end + # error helpers def forbidden!(reason = nil) diff --git a/lib/api/internal.rb b/lib/api/internal.rb index d235977fbd8..7eed93aba00 100644 --- a/lib/api/internal.rb +++ b/lib/api/internal.rb @@ -132,6 +132,18 @@ module API { success: true, recovery_codes: codes } end + + post "/notify_post_receive" do + status 200 + + return unless Gitlab::GitalyClient.enabled? + + begin + Gitlab::GitalyClient::Notifications.new.post_receive(params[:repo_path]) + rescue GRPC::Unavailable => e + render_api_error(e, 500) + end + end end end end diff --git a/lib/api/issues.rb b/lib/api/issues.rb index 6d30c5d81b1..1d6d0b05750 100644 --- a/lib/api/issues.rb +++ b/lib/api/issues.rb @@ -25,6 +25,7 @@ module API optional :sort, type: String, values: %w[asc desc], default: 'desc', desc: 'Return issues sorted in `asc` or `desc` order.' optional :milestone, type: String, desc: 'Return issues for a specific milestone' + optional :iids, type: Array[Integer], desc: 'The IID array of issues' use :pagination end diff --git a/lib/api/merge_requests.rb b/lib/api/merge_requests.rb index bdd764abfeb..4638a66811d 100644 --- a/lib/api/merge_requests.rb +++ b/lib/api/merge_requests.rb @@ -168,8 +168,8 @@ module API optional :merge_commit_message, type: String, desc: 'Custom merge commit message' optional :should_remove_source_branch, type: Boolean, desc: 'When true, the source branch will be deleted if possible' - optional :merge_when_build_succeeds, type: Boolean, - desc: 'When true, this merge request will be merged when the pipeline succeeds' + optional :merge_when_pipeline_succeeds, type: Boolean, + desc: 'When true, this merge request will be merged when the pipeline succeeds' optional :sha, type: String, desc: 'When present, must have the HEAD SHA of the source branch' end put ':id/merge_requests/:merge_request_id/merge' do @@ -192,7 +192,7 @@ module API should_remove_source_branch: params[:should_remove_source_branch] } - if params[:merge_when_build_succeeds] && merge_request.head_pipeline && merge_request.head_pipeline.active? + if params[:merge_when_pipeline_succeeds] && merge_request.head_pipeline && merge_request.head_pipeline.active? ::MergeRequests::MergeWhenPipelineSucceedsService .new(merge_request.target_project, current_user, merge_params) .execute(merge_request) @@ -208,10 +208,10 @@ module API desc 'Cancel merge if "Merge When Pipeline Succeeds" is enabled' do success Entities::MergeRequest end - post ':id/merge_requests/:merge_request_id/cancel_merge_when_build_succeeds' do + post ':id/merge_requests/:merge_request_id/cancel_merge_when_pipeline_succeeds' do merge_request = find_project_merge_request(params[:merge_request_id]) - unauthorized! unless merge_request.can_cancel_merge_when_build_succeeds?(current_user) + unauthorized! unless merge_request.can_cancel_merge_when_pipeline_succeeds?(current_user) ::MergeRequest::MergeWhenPipelineSucceedsService .new(merge_request.target_project, current_user) diff --git a/lib/api/milestones.rb b/lib/api/milestones.rb index 0b4ed76b35c..bd74174c655 100644 --- a/lib/api/milestones.rb +++ b/lib/api/milestones.rb @@ -30,7 +30,8 @@ module API params do optional :state, type: String, values: %w[active closed all], default: 'all', desc: 'Return "active", "closed", or "all" milestones' - optional :iid, type: Array[Integer], desc: 'The IID of the milestone' + optional :iids, type: Array[Integer], desc: 'The IIDs of the milestones' + optional :search, type: String, desc: 'The search criteria for the title or description of the milestone' use :pagination end get ":id/milestones" do @@ -38,7 +39,8 @@ module API milestones = user_project.milestones milestones = filter_milestones_state(milestones, params[:state]) - milestones = filter_by_iid(milestones, params[:iid]) if params[:iid].present? + milestones = filter_by_iid(milestones, params[:iids]) if params[:iids].present? + milestones = filter_by_search(milestones, params[:search]) if params[:search] present paginate(milestones), with: Entities::Milestone end diff --git a/lib/api/project_snippets.rb b/lib/api/project_snippets.rb index 2a1cce73f3f..f57e7ea4032 100644 --- a/lib/api/project_snippets.rb +++ b/lib/api/project_snippets.rb @@ -50,11 +50,9 @@ module API requires :title, type: String, desc: 'The title of the snippet' requires :file_name, type: String, desc: 'The file name of the snippet' requires :code, type: String, desc: 'The content of the snippet' - requires :visibility_level, type: Integer, - values: [Gitlab::VisibilityLevel::PRIVATE, - Gitlab::VisibilityLevel::INTERNAL, - Gitlab::VisibilityLevel::PUBLIC], - desc: 'The visibility level of the snippet' + requires :visibility, type: String, + values: Gitlab::VisibilityLevel.string_values, + desc: 'The visibility of the snippet' end post ":id/snippets" do authorize! :create_project_snippet, user_project @@ -80,11 +78,9 @@ module API optional :title, type: String, desc: 'The title of the snippet' optional :file_name, type: String, desc: 'The file name of the snippet' optional :code, type: String, desc: 'The content of the snippet' - optional :visibility_level, type: Integer, - values: [Gitlab::VisibilityLevel::PRIVATE, - Gitlab::VisibilityLevel::INTERNAL, - Gitlab::VisibilityLevel::PUBLIC], - desc: 'The visibility level of the snippet' + optional :visibility, type: String, + values: Gitlab::VisibilityLevel.string_values, + desc: 'The visibility of the snippet' at_least_one_of :title, :file_name, :code, :visibility_level end put ":id/snippets/:snippet_id" do diff --git a/lib/api/projects.rb b/lib/api/projects.rb index b8a8cee0cea..f302496c12b 100644 --- a/lib/api/projects.rb +++ b/lib/api/projects.rb @@ -16,14 +16,10 @@ module API optional :shared_runners_enabled, type: Boolean, desc: 'Flag indication if shared runners are enabled for that project' optional :container_registry_enabled, type: Boolean, desc: 'Flag indication if the container registry is enabled for that project' optional :lfs_enabled, type: Boolean, desc: 'Flag indication if Git LFS is enabled for that project' - optional :visibility_level, type: Integer, values: [ - Gitlab::VisibilityLevel::PRIVATE, - Gitlab::VisibilityLevel::INTERNAL, - Gitlab::VisibilityLevel::PUBLIC - ], desc: 'Create a public project. The same as visibility_level = 20.' + optional :visibility, type: String, values: Gitlab::VisibilityLevel.string_values, desc: 'The visibility of the project.' optional :public_builds, type: Boolean, desc: 'Perform public builds' optional :request_access_enabled, type: Boolean, desc: 'Allow users to request member access' - optional :only_allow_merge_if_build_succeeds, type: Boolean, desc: 'Only allow to merge if builds succeed' + optional :only_allow_merge_if_pipeline_succeeds, type: Boolean, desc: 'Only allow to merge if builds succeed' optional :only_allow_merge_if_all_discussions_are_resolved, type: Boolean, desc: 'Only allow to merge if all discussions are resolved' end end @@ -48,7 +44,7 @@ module API params :filter_params do optional :archived, type: Boolean, default: false, desc: 'Limit by archived status' - optional :visibility, type: String, values: %w[public internal private], + optional :visibility, type: String, values: Gitlab::VisibilityLevel.string_values, 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' @@ -208,8 +204,8 @@ module API at_least_one_of :name, :description, :issues_enabled, :merge_requests_enabled, :wiki_enabled, :builds_enabled, :snippets_enabled, :shared_runners_enabled, :container_registry_enabled, - :lfs_enabled, :visibility_level, :public_builds, - :request_access_enabled, :only_allow_merge_if_build_succeeds, + :lfs_enabled, :visibility, :public_builds, + :request_access_enabled, :only_allow_merge_if_pipeline_succeeds, :only_allow_merge_if_all_discussions_are_resolved, :path, :default_branch end @@ -217,7 +213,7 @@ module API authorize_admin_project attrs = declared_params(include_missing: false) authorize! :rename_project, user_project if attrs[:name].present? - authorize! :change_visibility_level, user_project if attrs[:visibility_level].present? + authorize! :change_visibility_level, user_project if attrs[:visibility].present? result = ::Projects::UpdateService.new(user_project, current_user, attrs).execute diff --git a/lib/api/settings.rb b/lib/api/settings.rb index 936c7e0930b..d4d3229f0d1 100644 --- a/lib/api/settings.rb +++ b/lib/api/settings.rb @@ -21,9 +21,9 @@ module API end params do optional :default_branch_protection, type: Integer, values: [0, 1, 2], desc: 'Determine if developers can push to master' - optional :default_project_visibility, type: Integer, values: Gitlab::VisibilityLevel.values, desc: 'The default project visibility' - optional :default_snippet_visibility, type: Integer, values: Gitlab::VisibilityLevel.values, desc: 'The default snippet visibility' - optional :default_group_visibility, type: Integer, values: Gitlab::VisibilityLevel.values, desc: 'The default group visibility' + optional :default_project_visibility, type: String, values: Gitlab::VisibilityLevel.string_values, desc: 'The default project visibility' + optional :default_snippet_visibility, type: String, values: Gitlab::VisibilityLevel.string_values, desc: 'The default snippet visibility' + optional :default_group_visibility, type: String, values: Gitlab::VisibilityLevel.string_values, desc: 'The default group visibility' optional :restricted_visibility_levels, type: Array[String], desc: 'Selected levels cannot be used by non-admin users for projects or snippets. If the public level is restricted, user profiles are only visible to logged in users.' optional :import_sources, type: Array[String], values: %w[github bitbucket gitlab google_code fogbugz git gitlab_project], desc: 'Enabled sources for code import during project creation. OmniAuth must be configured for GitHub, Bitbucket, and GitLab.com' @@ -128,7 +128,9 @@ module API :housekeeping_enabled, :terminal_max_session_time end put "application/settings" do - if current_settings.update_attributes(declared_params(include_missing: false)) + attrs = declared_params(include_missing: false) + + if current_settings.update_attributes(attrs) present current_settings, with: Entities::ApplicationSetting else render_validation_error!(current_settings) diff --git a/lib/api/snippets.rb b/lib/api/snippets.rb index 0f86fdb3075..b93fdc62808 100644 --- a/lib/api/snippets.rb +++ b/lib/api/snippets.rb @@ -58,10 +58,10 @@ module API requires :title, type: String, desc: 'The title of a snippet' requires :file_name, type: String, desc: 'The name of a snippet file' requires :content, type: String, desc: 'The content of a snippet' - optional :visibility_level, type: Integer, - values: Gitlab::VisibilityLevel.values, - default: Gitlab::VisibilityLevel::INTERNAL, - desc: 'The visibility level of the snippet' + optional :visibility, type: String, + values: Gitlab::VisibilityLevel.string_values, + default: 'internal', + desc: 'The visibility of the snippet' end post do attrs = declared_params(include_missing: false).merge(request: request, api: true) @@ -85,10 +85,10 @@ module API optional :title, type: String, desc: 'The title of a snippet' optional :file_name, type: String, desc: 'The name of a snippet file' optional :content, type: String, desc: 'The content of a snippet' - optional :visibility_level, type: Integer, - values: Gitlab::VisibilityLevel.values, - desc: 'The visibility level of the snippet' - at_least_one_of :title, :file_name, :content, :visibility_level + optional :visibility, type: String, + values: Gitlab::VisibilityLevel.string_values, + desc: 'The visibility of the snippet' + at_least_one_of :title, :file_name, :content, :visibility end put ':id' do snippet = snippets_for_current_user.find_by(id: params.delete(:id)) diff --git a/lib/api/v3/entities.rb b/lib/api/v3/entities.rb index 11d0e6dbf71..270d99a2348 100644 --- a/lib/api/v3/entities.rb +++ b/lib/api/v3/entities.rb @@ -45,6 +45,147 @@ module API expose :created_at, :updated_at expose :awardable_id, :awardable_type end + + class Project < Grape::Entity + expose :id, :description, :default_branch, :tag_list + expose :public?, as: :public + expose :archived?, as: :archived + expose :visibility_level, :ssh_url_to_repo, :http_url_to_repo, :web_url + expose :owner, using: ::API::Entities::UserBasic, unless: ->(project, options) { project.group } + expose :name, :name_with_namespace + expose :path, :path_with_namespace + expose :container_registry_enabled + + # Expose old field names with the new permissions methods to keep API compatible + expose(:issues_enabled) { |project, options| project.feature_available?(:issues, options[:current_user]) } + expose(:merge_requests_enabled) { |project, options| project.feature_available?(:merge_requests, options[:current_user]) } + expose(:wiki_enabled) { |project, options| project.feature_available?(:wiki, options[:current_user]) } + expose(:builds_enabled) { |project, options| project.feature_available?(:builds, options[:current_user]) } + expose(:snippets_enabled) { |project, options| project.feature_available?(:snippets, options[:current_user]) } + + expose :created_at, :last_activity_at + expose :shared_runners_enabled + expose :lfs_enabled?, as: :lfs_enabled + expose :creator_id + expose :namespace, using: 'API::Entities::Namespace' + expose :forked_from_project, using: ::API::Entities::BasicProjectDetails, if: lambda{ |project, options| project.forked? } + expose :avatar_url + expose :star_count, :forks_count + expose :open_issues_count, if: lambda { |project, options| project.feature_available?(:issues, options[:current_user]) && project.default_issues_tracker? } + expose :runners_token, if: lambda { |_project, options| options[:user_can_admin_project] } + expose :public_builds + expose :shared_with_groups do |project, options| + ::API::Entities::SharedGroup.represent(project.project_group_links.all, options) + end + expose :only_allow_merge_if_pipeline_succeeds, as: :only_allow_merge_if_build_succeeds + expose :request_access_enabled + expose :only_allow_merge_if_all_discussions_are_resolved + + expose :statistics, using: 'API::Entities::ProjectStatistics', if: :statistics + end + + class ProjectWithAccess < Project + expose :permissions do + expose :project_access, using: ::API::Entities::ProjectAccess do |project, options| + project.project_members.find_by(user_id: options[:current_user].id) + end + + expose :group_access, using: ::API::Entities::GroupAccess do |project, options| + if project.group + project.group.group_members.find_by(user_id: options[:current_user].id) + end + end + end + end + + class MergeRequest < Grape::Entity + expose :id, :iid + expose(:project_id) { |entity| entity.project.id } + expose :title, :description + expose :state, :created_at, :updated_at + expose :target_branch, :source_branch + expose :upvotes, :downvotes + expose :author, :assignee, using: ::API::Entities::UserBasic + expose :source_project_id, :target_project_id + expose :label_names, as: :labels + expose :work_in_progress?, as: :work_in_progress + expose :milestone, using: ::API::Entities::Milestone + expose :merge_when_pipeline_succeeds, as: :merge_when_build_succeeds + expose :merge_status + expose :diff_head_sha, as: :sha + expose :merge_commit_sha + expose :subscribed do |merge_request, options| + merge_request.subscribed?(options[:current_user], options[:project]) + end + expose :user_notes_count + expose :should_remove_source_branch?, as: :should_remove_source_branch + expose :force_remove_source_branch?, as: :force_remove_source_branch + + expose :web_url do |merge_request, options| + Gitlab::UrlBuilder.build(merge_request) + end + end + + class Group < Grape::Entity + expose :id, :name, :path, :description, :visibility_level + expose :lfs_enabled?, as: :lfs_enabled + expose :avatar_url + expose :web_url + expose :request_access_enabled + expose :full_name, :full_path + expose :parent_id + + expose :statistics, if: :statistics do + with_options format_with: -> (value) { value.to_i } do + expose :storage_size + expose :repository_size + expose :lfs_objects_size + expose :build_artifacts_size + end + end + end + + class GroupDetail < Group + expose :projects, using: Entities::Project + expose :shared_projects, using: Entities::Project + end + + class ApplicationSetting < Grape::Entity + expose :id + expose :default_projects_limit + expose :signup_enabled + expose :signin_enabled + expose :gravatar_enabled + expose :sign_in_text + expose :after_sign_up_text + expose :created_at + expose :updated_at + expose :home_page_url + expose :default_branch_protection + expose :restricted_visibility_levels + expose :max_attachment_size + expose :session_expire_delay + expose :default_project_visibility + expose :default_snippet_visibility + expose :default_group_visibility + expose :domain_whitelist + expose :domain_blacklist_enabled + expose :domain_blacklist + expose :user_oauth_applications + expose :after_sign_out_path + expose :container_registry_token_expire_delay + expose :repository_storage + expose :repository_storages + expose :koding_enabled + expose :koding_url + expose :plantuml_enabled + expose :plantuml_url + expose :terminal_max_session_time + end + + class Environment < ::API::Entities::EnvironmentBasic + expose :project, using: Entities::Project + end end end end diff --git a/lib/api/v3/environments.rb b/lib/api/v3/environments.rb index 3effccfa708..3056b70e6ef 100644 --- a/lib/api/v3/environments.rb +++ b/lib/api/v3/environments.rb @@ -1,6 +1,7 @@ module API module V3 class Environments < Grape::API + include ::API::Helpers::CustomValidators include PaginationParams before { authenticate! } @@ -9,9 +10,66 @@ module API requires :id, type: String, desc: 'The project ID' end resource :projects do + desc 'Get all environments of the project' do + detail 'This feature was introduced in GitLab 8.11.' + success Entities::Environment + end + params do + use :pagination + end + get ':id/environments' do + authorize! :read_environment, user_project + + present paginate(user_project.environments), with: Entities::Environment + end + + desc 'Creates a new environment' do + detail 'This feature was introduced in GitLab 8.11.' + success Entities::Environment + end + params do + requires :name, type: String, desc: 'The name of the environment to be created' + optional :external_url, type: String, desc: 'URL on which this deployment is viewable' + optional :slug, absence: { message: "is automatically generated and cannot be changed" } + end + post ':id/environments' do + authorize! :create_environment, user_project + + environment = user_project.environments.create(declared_params) + + if environment.persisted? + present environment, with: Entities::Environment + else + render_validation_error!(environment) + end + end + + desc 'Updates an existing environment' do + detail 'This feature was introduced in GitLab 8.11.' + success Entities::Environment + end + params do + requires :environment_id, type: Integer, desc: 'The environment ID' + optional :name, type: String, desc: 'The new environment name' + optional :external_url, type: String, desc: 'The new URL on which this deployment is viewable' + optional :slug, absence: { message: "is automatically generated and cannot be changed" } + end + put ':id/environments/:environment_id' do + authorize! :update_environment, user_project + + environment = user_project.environments.find(params[:environment_id]) + + update_params = declared_params(include_missing: false).extract!(:name, :external_url) + if environment.update(update_params) + present environment, with: Entities::Environment + else + render_validation_error!(environment) + end + end + desc 'Deletes an existing environment' do detail 'This feature was introduced in GitLab 8.11.' - success ::API::Entities::Environment + success Entities::Environment end params do requires :environment_id, type: Integer, desc: 'The environment ID' @@ -21,7 +79,7 @@ module API environment = user_project.environments.find(params[:environment_id]) - present environment.destroy, with: ::API::Entities::Environment + present environment.destroy, with: Entities::Environment end end end diff --git a/lib/api/v3/groups.rb b/lib/api/v3/groups.rb index c826bc4fe0b..0aad87a3f58 100644 --- a/lib/api/v3/groups.rb +++ b/lib/api/v3/groups.rb @@ -6,13 +6,20 @@ module API before { authenticate! } helpers do + params :optional_params do + optional :description, type: String, desc: 'The description of the group' + optional :visibility_level, type: Integer, desc: 'The visibility level of the group' + optional :lfs_enabled, type: Boolean, desc: 'Enable/disable LFS for the projects in this group' + optional :request_access_enabled, type: Boolean, desc: 'Allow users to request member access' + end + params :statistics_params do optional :statistics, type: Boolean, default: false, desc: 'Include project statistics' end def present_groups(groups, options = {}) options = options.reverse_merge( - with: ::API::Entities::Group, + with: Entities::Group, current_user: current_user, ) @@ -22,8 +29,36 @@ module API end resource :groups do + desc 'Get a groups list' do + success Entities::Group + end + params do + use :statistics_params + optional :skip_groups, type: Array[Integer], desc: 'Array of group ids to exclude from list' + optional :all_available, type: Boolean, desc: 'Show all group that you have access to' + optional :search, type: String, desc: 'Search for a specific group' + optional :order_by, type: String, values: %w[name path], default: 'name', desc: 'Order by name or path' + optional :sort, type: String, values: %w[asc desc], default: 'asc', desc: 'Sort by asc (ascending) or desc (descending)' + use :pagination + end + get do + groups = if current_user.admin + Group.all + elsif params[:all_available] + GroupsFinder.new.execute(current_user) + else + current_user.groups + end + + groups = groups.search(params[:search]) if params[:search].present? + groups = groups.where.not(id: params[:skip_groups]) if params[:skip_groups].present? + groups = groups.reorder(params[:order_by] => params[:sort]) + + present_groups groups, statistics: params[:statistics] && current_user.is_admin? + end + desc 'Get list of owned groups for authenticated user' do - success ::API::Entities::Group + success Entities::Group end params do use :pagination @@ -32,6 +67,114 @@ module API get '/owned' do present_groups current_user.owned_groups, statistics: params[:statistics] end + + desc 'Create a group. Available only for users who can create groups.' do + success Entities::Group + end + params do + requires :name, type: String, desc: 'The name of the group' + requires :path, type: String, desc: 'The path of the group' + optional :parent_id, type: Integer, desc: 'The parent group id for creating nested group' + use :optional_params + end + post do + authorize! :create_group + + group = ::Groups::CreateService.new(current_user, declared_params(include_missing: false)).execute + + if group.persisted? + present group, with: Entities::Group, current_user: current_user + else + render_api_error!("Failed to save group #{group.errors.messages}", 400) + end + end + end + + params do + requires :id, type: String, desc: 'The ID of a group' + end + resource :groups do + desc 'Update a group. Available only for users who can administrate groups.' do + success Entities::Group + end + params do + optional :name, type: String, desc: 'The name of the group' + optional :path, type: String, desc: 'The path of the group' + use :optional_params + at_least_one_of :name, :path, :description, :visibility_level, + :lfs_enabled, :request_access_enabled + end + put ':id' do + group = find_group!(params[:id]) + authorize! :admin_group, group + + if ::Groups::UpdateService.new(group, current_user, declared_params(include_missing: false)).execute + present group, with: Entities::GroupDetail, current_user: current_user + else + render_validation_error!(group) + end + end + + desc 'Get a single group, with containing projects.' do + success Entities::GroupDetail + end + get ":id" do + group = find_group!(params[:id]) + present group, with: Entities::GroupDetail, current_user: current_user + end + + desc 'Remove a group.' + delete ":id" do + group = find_group!(params[:id]) + authorize! :admin_group, group + present ::Groups::DestroyService.new(group, current_user).execute, with: Entities::GroupDetail, current_user: current_user + end + + desc 'Get a list of projects in this group.' do + success Entities::Project + end + params do + optional :archived, type: Boolean, default: false, desc: 'Limit by archived status' + 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 :order_by, type: String, values: %w[id name path created_at updated_at last_activity_at], + default: 'created_at', desc: 'Return projects ordered by field' + optional :sort, type: String, values: %w[asc desc], default: 'desc', + 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 + group = find_group!(params[:id]) + projects = GroupProjectsFinder.new(group).execute(current_user) + projects = filter_projects(projects) + entity = params[:simple] ? ::API::Entities::BasicProjectDetails : Entities::Project + present paginate(projects), with: entity, current_user: current_user + end + + desc 'Transfer a project to the group namespace. Available only for admin.' do + success Entities::GroupDetail + end + params do + requires :project_id, type: String, desc: 'The ID or path of the project' + end + post ":id/projects/:project_id" do + authenticated_as_admin! + group = find_group!(params[:id]) + project = find_project!(params[:project_id]) + result = ::Projects::TransferService.new(project, current_user).execute(group) + + if result + present group, with: Entities::GroupDetail, current_user: current_user + else + render_api_error!("Failed to transfer project #{project.errors.messages}", 400) + end + end end end end diff --git a/lib/api/v3/merge_requests.rb b/lib/api/v3/merge_requests.rb index c6574a9104b..654e818e1b5 100644 --- a/lib/api/v3/merge_requests.rb +++ b/lib/api/v3/merge_requests.rb @@ -39,7 +39,7 @@ module API desc 'List merge requests' do detail 'iid filter is deprecated have been removed on V4' - success ::API::Entities::MergeRequest + success ::API::V3::Entities::MergeRequest end params do optional :state, type: String, values: %w[opened closed merged all], default: 'all', @@ -66,11 +66,11 @@ module API end merge_requests = merge_requests.reorder(params[:order_by] => params[:sort]) - present paginate(merge_requests), with: ::API::Entities::MergeRequest, current_user: current_user, project: user_project + present paginate(merge_requests), with: ::API::V3::Entities::MergeRequest, current_user: current_user, project: user_project end desc 'Create a merge request' do - success ::API::Entities::MergeRequest + success ::API::V3::Entities::MergeRequest end params do requires :title, type: String, desc: 'The title of the merge request' @@ -89,7 +89,7 @@ module API merge_request = ::MergeRequests::CreateService.new(user_project, current_user, mr_params).execute if merge_request.valid? - present merge_request, with: ::API::Entities::MergeRequest, current_user: current_user, project: user_project + present merge_request, with: ::API::V3::Entities::MergeRequest, current_user: current_user, project: user_project else handle_merge_request_errors! merge_request.errors end @@ -116,12 +116,12 @@ module API if status == :deprecated detail DEPRECATION_MESSAGE end - success ::API::Entities::MergeRequest + success ::API::V3::Entities::MergeRequest end get path do merge_request = find_merge_request_with_access(params[:merge_request_id]) - present merge_request, with: ::API::Entities::MergeRequest, current_user: current_user, project: user_project + present merge_request, with: ::API::V3::Entities::MergeRequest, current_user: current_user, project: user_project end desc 'Get the commits of a merge request' do @@ -143,7 +143,7 @@ module API end desc 'Update a merge request' do - success ::API::Entities::MergeRequest + success ::API::V3::Entities::MergeRequest end params do optional :title, type: String, allow_blank: false, desc: 'The title of the merge request' @@ -164,21 +164,21 @@ module API merge_request = ::MergeRequests::UpdateService.new(user_project, current_user, mr_params).execute(merge_request) if merge_request.valid? - present merge_request, with: ::API::Entities::MergeRequest, current_user: current_user, project: user_project + present merge_request, with: ::API::V3::Entities::MergeRequest, current_user: current_user, project: user_project else handle_merge_request_errors! merge_request.errors end end desc 'Merge a merge request' do - success ::API::Entities::MergeRequest + success ::API::V3::Entities::MergeRequest end params do optional :merge_commit_message, type: String, desc: 'Custom merge commit message' optional :should_remove_source_branch, type: Boolean, desc: 'When true, the source branch will be deleted if possible' optional :merge_when_build_succeeds, type: Boolean, - desc: 'When true, this merge request will be merged when the pipeline succeeds' + desc: 'When true, this merge request will be merged when the build succeeds' optional :sha, type: String, desc: 'When present, must have the HEAD SHA of the source branch' end put "#{path}/merge" do @@ -211,16 +211,16 @@ module API .execute(merge_request) end - present merge_request, with: ::API::Entities::MergeRequest, current_user: current_user, project: user_project + present merge_request, with: ::API::V3::Entities::MergeRequest, current_user: current_user, project: user_project end - desc 'Cancel merge if "Merge When Pipeline Succeeds" is enabled' do - success ::API::Entities::MergeRequest + desc 'Cancel merge if "Merge When Build succeeds" is enabled' do + success ::API::V3::Entities::MergeRequest end post "#{path}/cancel_merge_when_build_succeeds" do merge_request = find_project_merge_request(params[:merge_request_id]) - unauthorized! unless merge_request.can_cancel_merge_when_build_succeeds?(current_user) + unauthorized! unless merge_request.can_cancel_merge_when_pipeline_succeeds?(current_user) ::MergeRequest::MergeWhenPipelineSucceedsService .new(merge_request.target_project, current_user) diff --git a/lib/api/v3/milestones.rb b/lib/api/v3/milestones.rb new file mode 100644 index 00000000000..bbc29c40ee2 --- /dev/null +++ b/lib/api/v3/milestones.rb @@ -0,0 +1,43 @@ +module API + module V3 + class Milestones < Grape::API + include PaginationParams + + before { authenticate! } + + helpers do + def filter_milestones_state(milestones, state) + case state + when 'active' then milestones.active + when 'closed' then milestones.closed + else milestones + end + end + end + + params do + requires :id, type: String, desc: 'The ID of a project' + end + resource :projects do + desc 'Get a list of project milestones' do + success ::API::Entities::Milestone + end + params do + optional :state, type: String, values: %w[active closed all], default: 'all', + desc: 'Return "active", "closed", or "all" milestones' + optional :iid, type: Array[Integer], desc: 'The IID of the milestone' + use :pagination + end + get ":id/milestones" do + authorize! :read_milestone, user_project + + milestones = user_project.milestones + milestones = filter_milestones_state(milestones, params[:state]) + milestones = filter_by_iid(milestones, params[:iid]) if params[:iid].present? + + present paginate(milestones), with: ::API::Entities::Milestone + end + end + end + end +end diff --git a/lib/api/v3/projects.rb b/lib/api/v3/projects.rb index 881d52e4aa4..47bfc12035a 100644 --- a/lib/api/v3/projects.rb +++ b/lib/api/v3/projects.rb @@ -5,6 +5,10 @@ module API before { authenticate_non_get! } + after_validation do + set_only_allow_merge_if_pipeline_succeeds! + end + helpers do params :optional_params do optional :description, type: String, desc: 'The description of the project' @@ -25,6 +29,7 @@ module API optional :public_builds, type: Boolean, desc: 'Perform public builds' optional :request_access_enabled, type: Boolean, desc: 'Allow users to request member access' optional :only_allow_merge_if_build_succeeds, type: Boolean, desc: 'Only allow to merge if builds succeed' + optional :only_allow_merge_if_pipeline_succeeds, type: Boolean, desc: 'Only allow to merge if builds succeed' optional :only_allow_merge_if_all_discussions_are_resolved, type: Boolean, desc: 'Only allow to merge if all discussions are resolved' end @@ -37,6 +42,12 @@ module API end attrs end + + def set_only_allow_merge_if_pipeline_succeeds! + if params.has_key?(:only_allow_merge_if_build_succeeds) + params[:only_allow_merge_if_pipeline_succeeds] = params.delete(:only_allow_merge_if_build_succeeds) + end + end end resource :projects do @@ -75,7 +86,7 @@ module API def present_projects(projects, options = {}) options = options.reverse_merge( - with: ::API::Entities::Project, + with: ::API::V3::Entities::Project, current_user: current_user, simple: params[:simple], ) @@ -95,7 +106,7 @@ module API use :collection_params end get '/visible' do - entity = current_user ? ::API::Entities::ProjectWithAccess : ::API::Entities::BasicProjectDetails + entity = current_user ? ::API::V3::Entities::ProjectWithAccess : ::API::Entities::BasicProjectDetails present_projects ProjectsFinder.new.execute(current_user), with: entity end @@ -109,7 +120,7 @@ module API authenticate! present_projects current_user.authorized_projects, - with: ::API::Entities::ProjectWithAccess + with: ::API::V3::Entities::ProjectWithAccess end desc 'Get an owned projects list for authenticated user' do @@ -123,7 +134,7 @@ module API authenticate! present_projects current_user.owned_projects, - with: ::API::Entities::ProjectWithAccess, + with: ::API::V3::Entities::ProjectWithAccess, statistics: params[:statistics] end @@ -149,11 +160,11 @@ module API get '/all' do authenticated_as_admin! - present_projects Project.all, with: ::API::Entities::ProjectWithAccess, statistics: params[:statistics] + present_projects Project.all, with: ::API::V3::Entities::ProjectWithAccess, statistics: params[:statistics] end desc 'Search for projects the current user has access to' do - success ::API::Entities::Project + success ::API::V3::Entities::Project end params do requires :query, type: String, desc: 'The project name to be searched' @@ -165,11 +176,11 @@ module API projects = search_service.objects('projects', params[:page]) projects = projects.reorder(params[:order_by] => params[:sort]) - present paginate(projects), with: ::API::Entities::Project + present paginate(projects), with: ::API::V3::Entities::Project end desc 'Create new project' do - success ::API::Entities::Project + success ::API::V3::Entities::Project end params do optional :name, type: String, desc: 'The name of the project' @@ -183,7 +194,7 @@ module API project = ::Projects::CreateService.new(current_user, attrs).execute if project.saved? - present project, with: ::API::Entities::Project, + present project, with: ::API::V3::Entities::Project, user_can_admin_project: can?(current_user, :admin_project, project) else if project.errors[:limit_reached].present? @@ -194,7 +205,7 @@ module API end desc 'Create new project for a specified user. Only available to admin users.' do - success ::API::Entities::Project + success ::API::V3::Entities::Project end params do requires :name, type: String, desc: 'The name of the project' @@ -212,7 +223,7 @@ module API project = ::Projects::CreateService.new(user, attrs).execute if project.saved? - present project, with: ::API::Entities::Project, + present project, with: ::API::V3::Entities::Project, user_can_admin_project: can?(current_user, :admin_project, project) else render_validation_error!(project) @@ -225,10 +236,10 @@ module API end resource :projects, requirements: { id: /[^\/]+/ } do desc 'Get a single project' do - success ::API::Entities::ProjectWithAccess + success ::API::V3::Entities::ProjectWithAccess end get ":id" do - entity = current_user ? ::API::Entities::ProjectWithAccess : ::API::Entities::BasicProjectDetails + entity = current_user ? ::API::V3::Entities::ProjectWithAccess : ::API::Entities::BasicProjectDetails present user_project, with: entity, current_user: current_user, user_can_admin_project: can?(current_user, :admin_project, user_project) end @@ -244,7 +255,7 @@ module API end desc 'Fork new project for the current user or provided namespace.' do - success ::API::Entities::Project + success ::API::V3::Entities::Project end params do optional :namespace, type: String, desc: 'The ID or name of the namespace that the project will be forked into' @@ -270,13 +281,13 @@ module API if forked_project.errors.any? conflict!(forked_project.errors.messages) else - present forked_project, with: ::API::Entities::Project, + present forked_project, with: ::API::V3::Entities::Project, user_can_admin_project: can?(current_user, :admin_project, forked_project) end end desc 'Update an existing project' do - success ::API::Entities::Project + success ::API::V3::Entities::Project end params do optional :name, type: String, desc: 'The name of the project' @@ -300,7 +311,7 @@ module API result = ::Projects::UpdateService.new(user_project, current_user, attrs).execute if result[:status] == :success - present user_project, with: ::API::Entities::Project, + present user_project, with: ::API::V3::Entities::Project, user_can_admin_project: can?(current_user, :admin_project, user_project) else render_validation_error!(user_project) @@ -308,29 +319,29 @@ module API end desc 'Archive a project' do - success ::API::Entities::Project + success ::API::V3::Entities::Project end post ':id/archive' do authorize!(:archive_project, user_project) user_project.archive! - present user_project, with: ::API::Entities::Project + present user_project, with: ::API::V3::Entities::Project end desc 'Unarchive a project' do - success ::API::Entities::Project + success ::API::V3::Entities::Project end post ':id/unarchive' do authorize!(:archive_project, user_project) user_project.unarchive! - present user_project, with: ::API::Entities::Project + present user_project, with: ::API::V3::Entities::Project end desc 'Star a project' do - success ::API::Entities::Project + success ::API::V3::Entities::Project end post ':id/star' do if current_user.starred?(user_project) @@ -339,19 +350,19 @@ module API current_user.toggle_star(user_project) user_project.reload - present user_project, with: ::API::Entities::Project + present user_project, with: ::API::V3::Entities::Project end end desc 'Unstar a project' do - success ::API::Entities::Project + success ::API::V3::Entities::Project end delete ':id/star' do if current_user.starred?(user_project) current_user.toggle_star(user_project) user_project.reload - present user_project, with: ::API::Entities::Project + present user_project, with: ::API::V3::Entities::Project else not_modified! end diff --git a/lib/api/v3/settings.rb b/lib/api/v3/settings.rb new file mode 100644 index 00000000000..748d6b97d4f --- /dev/null +++ b/lib/api/v3/settings.rb @@ -0,0 +1,137 @@ +module API + module V3 + class Settings < Grape::API + before { authenticated_as_admin! } + + helpers do + def current_settings + @current_setting ||= + (ApplicationSetting.current || ApplicationSetting.create_from_defaults) + end + end + + desc 'Get the current application settings' do + success Entities::ApplicationSetting + end + get "application/settings" do + present current_settings, with: Entities::ApplicationSetting + end + + desc 'Modify application settings' do + success Entities::ApplicationSetting + end + params do + optional :default_branch_protection, type: Integer, values: [0, 1, 2], desc: 'Determine if developers can push to master' + optional :default_project_visibility, type: Integer, values: Gitlab::VisibilityLevel.values, desc: 'The default project visibility' + optional :default_snippet_visibility, type: Integer, values: Gitlab::VisibilityLevel.values, desc: 'The default snippet visibility' + optional :default_group_visibility, type: Integer, values: Gitlab::VisibilityLevel.values, desc: 'The default group visibility' + optional :restricted_visibility_levels, type: Array[String], desc: 'Selected levels cannot be used by non-admin users for projects or snippets. If the public level is restricted, user profiles are only visible to logged in users.' + optional :import_sources, type: Array[String], values: %w[github bitbucket gitlab google_code fogbugz git gitlab_project], + desc: 'Enabled sources for code import during project creation. OmniAuth must be configured for GitHub, Bitbucket, and GitLab.com' + optional :disabled_oauth_sign_in_sources, type: Array[String], desc: 'Disable certain OAuth sign-in sources' + optional :enabled_git_access_protocol, type: String, values: %w[ssh http nil], desc: 'Allow only the selected protocols to be used for Git access.' + optional :gravatar_enabled, type: Boolean, desc: 'Flag indicating if the Gravatar service is enabled' + optional :default_projects_limit, type: Integer, desc: 'The maximum number of personal projects' + optional :max_attachment_size, type: Integer, desc: 'Maximum attachment size in MB' + optional :session_expire_delay, type: Integer, desc: 'Session duration in minutes. GitLab restart is required to apply changes.' + optional :user_oauth_applications, type: Boolean, desc: 'Allow users to register any application to use GitLab as an OAuth provider' + optional :user_default_external, type: Boolean, desc: 'Newly registered users will by default be external' + optional :signup_enabled, type: Boolean, desc: 'Flag indicating if sign up is enabled' + optional :send_user_confirmation_email, type: Boolean, desc: 'Send confirmation email on sign-up' + optional :domain_whitelist, type: String, desc: 'ONLY users with e-mail addresses that match these domain(s) will be able to sign-up. Wildcards allowed. Use separate lines for multiple entries. Ex: domain.com, *.domain.com' + optional :domain_blacklist_enabled, type: Boolean, desc: 'Enable domain blacklist for sign ups' + given domain_blacklist_enabled: ->(val) { val } do + requires :domain_blacklist, type: String, desc: 'Users with e-mail addresses that match these domain(s) will NOT be able to sign-up. Wildcards allowed. Use separate lines for multiple entries. Ex: domain.com, *.domain.com' + end + optional :after_sign_up_text, type: String, desc: 'Text shown after sign up' + optional :signin_enabled, type: Boolean, desc: 'Flag indicating if sign in is enabled' + optional :require_two_factor_authentication, type: Boolean, desc: 'Require all users to setup Two-factor authentication' + given require_two_factor_authentication: ->(val) { val } do + requires :two_factor_grace_period, type: Integer, desc: 'Amount of time (in hours) that users are allowed to skip forced configuration of two-factor authentication' + end + optional :home_page_url, type: String, desc: 'We will redirect non-logged in users to this page' + optional :after_sign_out_path, type: String, desc: 'We will redirect users to this page after they sign out' + optional :sign_in_text, type: String, desc: 'The sign in text of the GitLab application' + optional :help_page_text, type: String, desc: 'Custom text displayed on the help page' + optional :shared_runners_enabled, type: Boolean, desc: 'Enable shared runners for new projects' + given shared_runners_enabled: ->(val) { val } do + requires :shared_runners_text, type: String, desc: 'Shared runners text ' + end + optional :max_artifacts_size, type: Integer, desc: "Set the maximum file size each build's artifacts can have" + optional :max_pages_size, type: Integer, desc: 'Maximum size of pages in MB' + optional :container_registry_token_expire_delay, type: Integer, desc: 'Authorization token duration (minutes)' + optional :metrics_enabled, type: Boolean, desc: 'Enable the InfluxDB metrics' + given metrics_enabled: ->(val) { val } do + requires :metrics_host, type: String, desc: 'The InfluxDB host' + requires :metrics_port, type: Integer, desc: 'The UDP port to use for connecting to InfluxDB' + requires :metrics_pool_size, type: Integer, desc: 'The amount of InfluxDB connections to open' + requires :metrics_timeout, type: Integer, desc: 'The amount of seconds after which an InfluxDB connection will time out' + requires :metrics_method_call_threshold, type: Integer, desc: 'A method call is only tracked when it takes longer to complete than the given amount of milliseconds.' + requires :metrics_sample_interval, type: Integer, desc: 'The sampling interval in seconds' + requires :metrics_packet_size, type: Integer, desc: 'The amount of points to store in a single UDP packet' + end + optional :sidekiq_throttling_enabled, type: Boolean, desc: 'Enable Sidekiq Job Throttling' + given sidekiq_throttling_enabled: ->(val) { val } do + requires :sidekiq_throttling_queus, type: Array[String], desc: 'Choose which queues you wish to throttle' + requires :sidekiq_throttling_factor, type: Float, desc: 'The factor by which the queues should be throttled. A value between 0.0 and 1.0, exclusive.' + end + optional :recaptcha_enabled, type: Boolean, desc: 'Helps prevent bots from creating accounts' + given recaptcha_enabled: ->(val) { val } do + requires :recaptcha_site_key, type: String, desc: 'Generate site key at http://www.google.com/recaptcha' + requires :recaptcha_private_key, type: String, desc: 'Generate private key at http://www.google.com/recaptcha' + end + optional :akismet_enabled, type: Boolean, desc: 'Helps prevent bots from creating issues' + given akismet_enabled: ->(val) { val } do + requires :akismet_api_key, type: String, desc: 'Generate API key at http://www.akismet.com' + end + optional :admin_notification_email, type: String, desc: 'Abuse reports will be sent to this address if it is set. Abuse reports are always available in the admin area.' + optional :sentry_enabled, type: Boolean, desc: 'Sentry is an error reporting and logging tool which is currently not shipped with GitLab, get it here: https://getsentry.com' + given sentry_enabled: ->(val) { val } do + requires :sentry_dsn, type: String, desc: 'Sentry Data Source Name' + end + optional :repository_storage, type: String, desc: 'Storage paths for new projects' + optional :repository_checks_enabled, type: Boolean, desc: "GitLab will periodically run 'git fsck' in all project and wiki repositories to look for silent disk corruption issues." + optional :koding_enabled, type: Boolean, desc: 'Enable Koding' + given koding_enabled: ->(val) { val } do + requires :koding_url, type: String, desc: 'The Koding team URL' + end + optional :plantuml_enabled, type: Boolean, desc: 'Enable PlantUML' + given plantuml_enabled: ->(val) { val } do + requires :plantuml_url, type: String, desc: 'The PlantUML server URL' + end + optional :version_check_enabled, type: Boolean, desc: 'Let GitLab inform you when an update is available.' + optional :email_author_in_body, type: Boolean, desc: 'Some email servers do not support overriding the email sender name. Enable this option to include the name of the author of the issue, merge request or comment in the email body instead.' + optional :html_emails_enabled, type: Boolean, desc: 'By default GitLab sends emails in HTML and plain text formats so mail clients can choose what format to use. Disable this option if you only want to send emails in plain text format.' + optional :housekeeping_enabled, type: Boolean, desc: 'Enable automatic repository housekeeping (git repack, git gc)' + given housekeeping_enabled: ->(val) { val } do + requires :housekeeping_bitmaps_enabled, type: Boolean, desc: "Creating pack file bitmaps makes housekeeping take a little longer but bitmaps should accelerate 'git clone' performance." + requires :housekeeping_incremental_repack_period, type: Integer, desc: "Number of Git pushes after which an incremental 'git repack' is run." + requires :housekeeping_full_repack_period, type: Integer, desc: "Number of Git pushes after which a full 'git repack' is run." + requires :housekeeping_gc_period, type: Integer, desc: "Number of Git pushes after which 'git gc' is run." + end + optional :terminal_max_session_time, type: Integer, desc: 'Maximum time for web terminal websocket connection (in seconds). Set to 0 for unlimited time.' + at_least_one_of :default_branch_protection, :default_project_visibility, :default_snippet_visibility, + :default_group_visibility, :restricted_visibility_levels, :import_sources, + :enabled_git_access_protocol, :gravatar_enabled, :default_projects_limit, + :max_attachment_size, :session_expire_delay, :disabled_oauth_sign_in_sources, + :user_oauth_applications, :user_default_external, :signup_enabled, + :send_user_confirmation_email, :domain_whitelist, :domain_blacklist_enabled, + :after_sign_up_text, :signin_enabled, :require_two_factor_authentication, + :home_page_url, :after_sign_out_path, :sign_in_text, :help_page_text, + :shared_runners_enabled, :max_artifacts_size, :max_pages_size, :container_registry_token_expire_delay, + :metrics_enabled, :sidekiq_throttling_enabled, :recaptcha_enabled, + :akismet_enabled, :admin_notification_email, :sentry_enabled, + :repository_storage, :repository_checks_enabled, :koding_enabled, :plantuml_enabled, + :version_check_enabled, :email_author_in_body, :html_emails_enabled, + :housekeeping_enabled, :terminal_max_session_time + end + put "application/settings" do + if current_settings.update_attributes(declared_params(include_missing: false)) + present current_settings, with: Entities::ApplicationSetting + else + render_validation_error!(current_settings) + end + end + end + end +end diff --git a/lib/api/v3/snippets.rb b/lib/api/v3/snippets.rb new file mode 100644 index 00000000000..07dac7e9904 --- /dev/null +++ b/lib/api/v3/snippets.rb @@ -0,0 +1,138 @@ +module API + module V3 + class Snippets < Grape::API + include PaginationParams + + before { authenticate! } + + resource :snippets do + helpers do + def snippets_for_current_user + SnippetsFinder.new.execute(current_user, filter: :by_user, user: current_user) + end + + def public_snippets + SnippetsFinder.new.execute(current_user, filter: :public) + end + end + + desc 'Get a snippets list for authenticated user' do + detail 'This feature was introduced in GitLab 8.15.' + success ::API::Entities::PersonalSnippet + end + params do + use :pagination + end + get do + present paginate(snippets_for_current_user), with: ::API::Entities::PersonalSnippet + end + + desc 'List all public snippets current_user has access to' do + detail 'This feature was introduced in GitLab 8.15.' + success ::API::Entities::PersonalSnippet + end + params do + use :pagination + end + get 'public' do + present paginate(public_snippets), with: ::API::Entities::PersonalSnippet + end + + desc 'Get a single snippet' do + detail 'This feature was introduced in GitLab 8.15.' + success ::API::Entities::PersonalSnippet + end + params do + requires :id, type: Integer, desc: 'The ID of a snippet' + end + get ':id' do + snippet = snippets_for_current_user.find(params[:id]) + present snippet, with: ::API::Entities::PersonalSnippet + end + + desc 'Create new snippet' do + detail 'This feature was introduced in GitLab 8.15.' + success ::API::Entities::PersonalSnippet + end + params do + requires :title, type: String, desc: 'The title of a snippet' + requires :file_name, type: String, desc: 'The name of a snippet file' + requires :content, type: String, desc: 'The content of a snippet' + optional :visibility_level, type: Integer, + values: Gitlab::VisibilityLevel.values, + default: Gitlab::VisibilityLevel::INTERNAL, + desc: 'The visibility level of the snippet' + end + post do + attrs = declared_params(include_missing: false).merge(request: request, api: true) + snippet = CreateSnippetService.new(nil, current_user, attrs).execute + + if snippet.persisted? + present snippet, with: ::API::Entities::PersonalSnippet + else + render_validation_error!(snippet) + end + end + + desc 'Update an existing snippet' do + detail 'This feature was introduced in GitLab 8.15.' + success ::API::Entities::PersonalSnippet + end + params do + requires :id, type: Integer, desc: 'The ID of a snippet' + optional :title, type: String, desc: 'The title of a snippet' + optional :file_name, type: String, desc: 'The name of a snippet file' + optional :content, type: String, desc: 'The content of a snippet' + optional :visibility_level, type: Integer, + values: Gitlab::VisibilityLevel.values, + desc: 'The visibility level of the snippet' + at_least_one_of :title, :file_name, :content, :visibility_level + end + put ':id' do + snippet = snippets_for_current_user.find_by(id: params.delete(:id)) + return not_found!('Snippet') unless snippet + authorize! :update_personal_snippet, snippet + + attrs = declared_params(include_missing: false) + + UpdateSnippetService.new(nil, current_user, snippet, attrs).execute + if snippet.persisted? + present snippet, with: ::API::Entities::PersonalSnippet + else + render_validation_error!(snippet) + end + end + + desc 'Remove snippet' do + detail 'This feature was introduced in GitLab 8.15.' + success ::API::Entities::PersonalSnippet + end + params do + requires :id, type: Integer, desc: 'The ID of a snippet' + end + delete ':id' do + snippet = snippets_for_current_user.find_by(id: params.delete(:id)) + return not_found!('Snippet') unless snippet + authorize! :destroy_personal_snippet, snippet + snippet.destroy + no_content! + end + + desc 'Get a raw snippet' do + detail 'This feature was introduced in GitLab 8.15.' + end + params do + requires :id, type: Integer, desc: 'The ID of a snippet' + end + get ":id/raw" do + snippet = snippets_for_current_user.find_by(id: params.delete(:id)) + return not_found!('Snippet') unless snippet + + env['api.format'] = :txt + content_type 'text/plain' + present snippet.content + end + end + end + end +end diff --git a/lib/banzai/filter/user_reference_filter.rb b/lib/banzai/filter/user_reference_filter.rb index c973897f420..849e1142841 100644 --- a/lib/banzai/filter/user_reference_filter.rb +++ b/lib/banzai/filter/user_reference_filter.rb @@ -133,7 +133,7 @@ module Banzai data = data_attribute(group: namespace.id) content = link_content || Group.reference_prefix + group - link_tag(url, data, content, namespace.name) + link_tag(url, data, content, namespace.full_name) end def link_to_user(user, namespace, link_content: nil) diff --git a/lib/bitbucket/error/unauthorized.rb b/lib/bitbucket/error/unauthorized.rb index 5e2eb57bb0e..efe10542f19 100644 --- a/lib/bitbucket/error/unauthorized.rb +++ b/lib/bitbucket/error/unauthorized.rb @@ -1,6 +1,5 @@ module Bitbucket module Error - class Unauthorized < StandardError - end + Unauthorized = Class.new(StandardError) end end diff --git a/lib/ci/gitlab_ci_yaml_processor.rb b/lib/ci/gitlab_ci_yaml_processor.rb index 649ee4d018b..e390919ae1d 100644 --- a/lib/ci/gitlab_ci_yaml_processor.rb +++ b/lib/ci/gitlab_ci_yaml_processor.rb @@ -1,6 +1,6 @@ module Ci class GitlabCiYamlProcessor - class ValidationError < StandardError; end + ValidationError = Class.new(StandardError) include Gitlab::Ci::Config::Entry::LegacyValidationHelpers diff --git a/lib/extracts_path.rb b/lib/extracts_path.rb index 9ece84cc469..dd864eea3fa 100644 --- a/lib/extracts_path.rb +++ b/lib/extracts_path.rb @@ -2,7 +2,7 @@ # file path string when combined in a request parameter module ExtractsPath # Raised when given an invalid file path - class InvalidPathError < StandardError; end + InvalidPathError = Class.new(StandardError) # Given a string containing both a Git tree-ish, such as a branch or tag, and # a filesystem path joined by forward slashes, attempts to separate the two. diff --git a/lib/gitlab/access.rb b/lib/gitlab/access.rb index 3b210eeda9d..8c28009b9c6 100644 --- a/lib/gitlab/access.rb +++ b/lib/gitlab/access.rb @@ -5,7 +5,7 @@ # module Gitlab module Access - class AccessDeniedError < StandardError; end + AccessDeniedError = Class.new(StandardError) NO_ACCESS = 0 GUEST = 10 diff --git a/lib/gitlab/auth.rb b/lib/gitlab/auth.rb index 89db6c3da46..0a5abc92190 100644 --- a/lib/gitlab/auth.rb +++ b/lib/gitlab/auth.rb @@ -1,6 +1,6 @@ module Gitlab module Auth - class MissingPersonalTokenError < StandardError; end + MissingPersonalTokenError = Class.new(StandardError) SCOPES = [:api, :read_user].freeze DEFAULT_SCOPES = [:api].freeze diff --git a/lib/gitlab/ci/build/artifacts/metadata.rb b/lib/gitlab/ci/build/artifacts/metadata.rb index cd2e83b4c27..a375ccbece0 100644 --- a/lib/gitlab/ci/build/artifacts/metadata.rb +++ b/lib/gitlab/ci/build/artifacts/metadata.rb @@ -6,7 +6,7 @@ module Gitlab module Build module Artifacts class Metadata - class ParserError < StandardError; end + ParserError = Class.new(StandardError) VERSION_PATTERN = /^[\w\s]+(\d+\.\d+\.\d+)/ INVALID_PATH_PATTERN = %r{(^\.?\.?/)|(/\.?\.?/)} diff --git a/lib/gitlab/ci/config/entry/factory.rb b/lib/gitlab/ci/config/entry/factory.rb index 9f5e393d191..6be8288748f 100644 --- a/lib/gitlab/ci/config/entry/factory.rb +++ b/lib/gitlab/ci/config/entry/factory.rb @@ -6,7 +6,7 @@ module Gitlab # Factory class responsible for fabricating entry objects. # class Factory - class InvalidFactory < StandardError; end + InvalidFactory = Class.new(StandardError) def initialize(entry) @entry = entry diff --git a/lib/gitlab/ci/config/entry/node.rb b/lib/gitlab/ci/config/entry/node.rb index 5eef2868cd6..55a5447ab51 100644 --- a/lib/gitlab/ci/config/entry/node.rb +++ b/lib/gitlab/ci/config/entry/node.rb @@ -6,7 +6,7 @@ module Gitlab # Base abstract class for each configuration entry node. # class Node - class InvalidError < StandardError; end + InvalidError = Class.new(StandardError) attr_reader :config, :metadata attr_accessor :key, :parent, :description diff --git a/lib/gitlab/ci/config/loader.rb b/lib/gitlab/ci/config/loader.rb index dbf6eb0edbe..e7d9f6a7761 100644 --- a/lib/gitlab/ci/config/loader.rb +++ b/lib/gitlab/ci/config/loader.rb @@ -2,7 +2,7 @@ module Gitlab module Ci class Config class Loader - class FormatError < StandardError; end + FormatError = Class.new(StandardError) def initialize(config) @config = YAML.safe_load(config, [Symbol], [], true) diff --git a/lib/gitlab/conflict/file.rb b/lib/gitlab/conflict/file.rb index d80bc748209..75a213ef752 100644 --- a/lib/gitlab/conflict/file.rb +++ b/lib/gitlab/conflict/file.rb @@ -4,8 +4,7 @@ module Gitlab include Gitlab::Routing.url_helpers include IconsHelper - class MissingResolution < ResolutionError - end + MissingResolution = Class.new(ResolutionError) CONTEXT_LINES = 3 diff --git a/lib/gitlab/conflict/file_collection.rb b/lib/gitlab/conflict/file_collection.rb index fa5bd4649d4..990b719ecfd 100644 --- a/lib/gitlab/conflict/file_collection.rb +++ b/lib/gitlab/conflict/file_collection.rb @@ -1,8 +1,7 @@ module Gitlab module Conflict class FileCollection - class ConflictSideMissing < StandardError - end + ConflictSideMissing = Class.new(StandardError) attr_reader :merge_request, :our_commit, :their_commit diff --git a/lib/gitlab/conflict/parser.rb b/lib/gitlab/conflict/parser.rb index ddd657903fb..d3524c338ee 100644 --- a/lib/gitlab/conflict/parser.rb +++ b/lib/gitlab/conflict/parser.rb @@ -1,25 +1,15 @@ module Gitlab module Conflict class Parser - class UnresolvableError < StandardError - end - - class UnmergeableFile < UnresolvableError - end - - class UnsupportedEncoding < UnresolvableError - end + UnresolvableError = Class.new(StandardError) + UnmergeableFile = Class.new(UnresolvableError) + UnsupportedEncoding = Class.new(UnresolvableError) # Recoverable errors - the conflict can be resolved in an editor, but not with # sections. - class ParserError < StandardError - end - - class UnexpectedDelimiter < ParserError - end - - class MissingEndDelimiter < ParserError - end + ParserError = Class.new(StandardError) + UnexpectedDelimiter = Class.new(ParserError) + MissingEndDelimiter = Class.new(ParserError) def parse(text, our_path:, their_path:, parent_file: nil) raise UnmergeableFile if text.blank? # Typically a binary file diff --git a/lib/gitlab/conflict/resolution_error.rb b/lib/gitlab/conflict/resolution_error.rb index a0f2006bc24..0b61256b35a 100644 --- a/lib/gitlab/conflict/resolution_error.rb +++ b/lib/gitlab/conflict/resolution_error.rb @@ -1,6 +1,5 @@ module Gitlab module Conflict - class ResolutionError < StandardError - end + ResolutionError = Class.new(StandardError) end end diff --git a/lib/gitlab/email/receiver.rb b/lib/gitlab/email/receiver.rb index b64db5d01ae..ec0529b5a4b 100644 --- a/lib/gitlab/email/receiver.rb +++ b/lib/gitlab/email/receiver.rb @@ -4,19 +4,19 @@ require_dependency 'gitlab/email/handler' # Inspired in great part by Discourse's Email::Receiver module Gitlab module Email - class ProcessingError < StandardError; end - class EmailUnparsableError < ProcessingError; end - class SentNotificationNotFoundError < ProcessingError; end - class ProjectNotFound < ProcessingError; end - class EmptyEmailError < ProcessingError; end - class AutoGeneratedEmailError < ProcessingError; end - class UserNotFoundError < ProcessingError; end - class UserBlockedError < ProcessingError; end - class UserNotAuthorizedError < ProcessingError; end - class NoteableNotFoundError < ProcessingError; end - class InvalidNoteError < ProcessingError; end - class InvalidIssueError < ProcessingError; end - class UnknownIncomingEmail < ProcessingError; end + ProcessingError = Class.new(StandardError) + EmailUnparsableError = Class.new(ProcessingError) + SentNotificationNotFoundError = Class.new(ProcessingError) + ProjectNotFound = Class.new(ProcessingError) + EmptyEmailError = Class.new(ProcessingError) + AutoGeneratedEmailError = Class.new(ProcessingError) + UserNotFoundError = Class.new(ProcessingError) + UserBlockedError = Class.new(ProcessingError) + UserNotAuthorizedError = Class.new(ProcessingError) + NoteableNotFoundError = Class.new(ProcessingError) + InvalidNoteError = Class.new(ProcessingError) + InvalidIssueError = Class.new(ProcessingError) + UnknownIncomingEmail = Class.new(ProcessingError) class Receiver def initialize(raw) diff --git a/lib/gitlab/git/diff.rb b/lib/gitlab/git/diff.rb index d6b3b5705a9..2a017c93f57 100644 --- a/lib/gitlab/git/diff.rb +++ b/lib/gitlab/git/diff.rb @@ -2,7 +2,7 @@ module Gitlab module Git class Diff - class TimeoutError < StandardError; end + TimeoutError = Class.new(StandardError) include Gitlab::Git::EncodingHelper # Diff properties diff --git a/lib/gitlab/git/repository.rb b/lib/gitlab/git/repository.rb index 8ec90885231..6540730ca7a 100644 --- a/lib/gitlab/git/repository.rb +++ b/lib/gitlab/git/repository.rb @@ -10,9 +10,9 @@ module Gitlab SEARCH_CONTEXT_LINES = 3 - class NoRepository < StandardError; end - class InvalidBlobName < StandardError; end - class InvalidRef < StandardError; end + NoRepository = Class.new(StandardError) + InvalidBlobName = Class.new(StandardError) + InvalidRef = Class.new(StandardError) # Full path to repo attr_reader :path @@ -199,13 +199,17 @@ module Gitlab nil end + def archive_prefix(ref, sha) + project_name = self.name.chomp('.git') + "#{project_name}-#{ref.parameterize}-#{sha}" + end + def archive_metadata(ref, storage_path, format = "tar.gz") ref ||= root_ref commit = Gitlab::Git::Commit.find(self, ref) return {} if commit.nil? - project_name = self.name.chomp('.git') - prefix = "#{project_name}-#{ref}-#{commit.id}" + prefix = archive_prefix(ref, commit.id) { 'RepoPath' => path, diff --git a/lib/gitlab/gitaly_client.rb b/lib/gitlab/gitaly_client.rb new file mode 100644 index 00000000000..b981a629fb0 --- /dev/null +++ b/lib/gitlab/gitaly_client.rb @@ -0,0 +1,29 @@ +require 'gitaly' + +module Gitlab + module GitalyClient + def self.gitaly_address + if Gitlab.config.gitaly.socket_path + "unix://#{Gitlab.config.gitaly.socket_path}" + end + end + + def self.channel + return @channel if defined?(@channel) + + @channel = + if enabled? + # NOTE: Gitaly currently runs on a Unix socket, so permissions are + # handled using the file system and no additional authentication is + # required (therefore the :this_channel_is_insecure flag) + GRPC::Core::Channel.new(gitaly_address, {}, :this_channel_is_insecure) + else + nil + end + end + + def self.enabled? + gitaly_address.present? + end + end +end diff --git a/lib/gitlab/gitaly_client/notifications.rb b/lib/gitlab/gitaly_client/notifications.rb new file mode 100644 index 00000000000..b827a56207f --- /dev/null +++ b/lib/gitlab/gitaly_client/notifications.rb @@ -0,0 +1,17 @@ +module Gitlab + module GitalyClient + class Notifications + attr_accessor :stub + + def initialize + @stub = Gitaly::Notifications::Stub.new(nil, nil, channel_override: GitalyClient.channel) + end + + def post_receive(repo_path) + repository = Gitaly::Repository.new(path: repo_path) + request = Gitaly::PostReceiveRequest.new(repository: repository) + stub.post_receive(request) + end + end + end +end diff --git a/lib/gitlab/gon_helper.rb b/lib/gitlab/gon_helper.rb index 101b1b80c1e..9c384069661 100644 --- a/lib/gitlab/gon_helper.rb +++ b/lib/gitlab/gon_helper.rb @@ -1,7 +1,7 @@ module Gitlab module GonHelper def add_gon_variables - gon.api_version = API::API.version + gon.api_version = 'v3' # v4 Is not officially released yet, therefore can't be considered as "frozen" gon.default_avatar_url = URI.join(Gitlab.config.gitlab.url, ActionController::Base.helpers.image_path('no_avatar.png')).to_s gon.max_file_size = current_application_settings.max_attachment_size gon.relative_url_root = Gitlab.config.gitlab.relative_url_root diff --git a/lib/gitlab/import_export/error.rb b/lib/gitlab/import_export/error.rb index e341c4d9cf8..788eedf2686 100644 --- a/lib/gitlab/import_export/error.rb +++ b/lib/gitlab/import_export/error.rb @@ -1,5 +1,5 @@ module Gitlab module ImportExport - class Error < StandardError; end + Error = Class.new(StandardError) end end diff --git a/lib/gitlab/o_auth/user.rb b/lib/gitlab/o_auth/user.rb index 95d2f559588..fcf51b7fc5b 100644 --- a/lib/gitlab/o_auth/user.rb +++ b/lib/gitlab/o_auth/user.rb @@ -5,7 +5,7 @@ # module Gitlab module OAuth - class SignupDisabledError < StandardError; end + SignupDisabledError = Class.new(StandardError) class User attr_accessor :auth_hash, :gl_user diff --git a/lib/gitlab/route_map.rb b/lib/gitlab/route_map.rb index 72d00abfcc2..36791fae60f 100644 --- a/lib/gitlab/route_map.rb +++ b/lib/gitlab/route_map.rb @@ -1,6 +1,6 @@ module Gitlab class RouteMap - class FormatError < StandardError; end + FormatError = Class.new(StandardError) def initialize(data) begin diff --git a/lib/gitlab/serializer/pagination.rb b/lib/gitlab/serializer/pagination.rb index bf2c0acc729..9c92b83dddc 100644 --- a/lib/gitlab/serializer/pagination.rb +++ b/lib/gitlab/serializer/pagination.rb @@ -1,7 +1,7 @@ module Gitlab module Serializer class Pagination - class InvalidResourceError < StandardError; end + InvalidResourceError = Class.new(StandardError) include ::API::Helpers::Pagination def initialize(request, response) diff --git a/lib/gitlab/shell.rb b/lib/gitlab/shell.rb index 7374d2bc8b8..da8d8ddb8ed 100644 --- a/lib/gitlab/shell.rb +++ b/lib/gitlab/shell.rb @@ -2,7 +2,7 @@ require 'securerandom' module Gitlab class Shell - class Error < StandardError; end + Error = Class.new(StandardError) KeyAdder = Struct.new(:io) do def add_key(id, key) diff --git a/lib/gitlab/template/finders/repo_template_finder.rb b/lib/gitlab/template/finders/repo_template_finder.rb index 22c39436cb2..cb7957e2af9 100644 --- a/lib/gitlab/template/finders/repo_template_finder.rb +++ b/lib/gitlab/template/finders/repo_template_finder.rb @@ -4,7 +4,7 @@ module Gitlab module Finders class RepoTemplateFinder < BaseTemplateFinder # Raised when file is not found - class FileNotFoundError < StandardError; end + FileNotFoundError = Class.new(StandardError) def initialize(project, base_dir, extension, categories = {}) @categories = categories diff --git a/lib/gitlab/update_path_error.rb b/lib/gitlab/update_path_error.rb index ce14cc887d0..8947ecfb92e 100644 --- a/lib/gitlab/update_path_error.rb +++ b/lib/gitlab/update_path_error.rb @@ -1,3 +1,3 @@ module Gitlab - class UpdatePathError < StandardError; end + UpdatePathError = Class.new(StandardError) end diff --git a/lib/gitlab/visibility_level.rb b/lib/gitlab/visibility_level.rb index b28708c34e1..2248763c106 100644 --- a/lib/gitlab/visibility_level.rb +++ b/lib/gitlab/visibility_level.rb @@ -35,6 +35,10 @@ module Gitlab class << self delegate :values, to: :options + def string_values + string_options.keys + end + def options { 'Private' => PRIVATE, @@ -43,6 +47,14 @@ module Gitlab } end + def string_options + { + 'private' => PRIVATE, + 'internal' => INTERNAL, + 'public' => PUBLIC + } + end + def highest_allowed_level restricted_levels = current_application_settings.restricted_visibility_levels @@ -82,18 +94,39 @@ module Gitlab level_name end + + def level_value(level) + return string_options[level] if level.is_a? String + level + end + + def string_level(level) + string_options.key(level) + end end def private? - visibility_level_field == PRIVATE + visibility_level_value == PRIVATE end def internal? - visibility_level_field == INTERNAL + visibility_level_value == INTERNAL end def public? - visibility_level_field == PUBLIC + visibility_level_value == PUBLIC + end + + def visibility_level_value + self[visibility_level_field] + end + + def visibility + Gitlab::VisibilityLevel.string_level(visibility_level_value) + end + + def visibility=(level) + self[visibility_level_field] = Gitlab::VisibilityLevel.level_value(level) end end end diff --git a/lib/mattermost/client.rb b/lib/mattermost/client.rb index e55c0d6ac49..ad6df246091 100644 --- a/lib/mattermost/client.rb +++ b/lib/mattermost/client.rb @@ -1,5 +1,5 @@ module Mattermost - class ClientError < Mattermost::Error; end + ClientError = Class.new(Mattermost::Error) class Client attr_reader :user diff --git a/lib/mattermost/error.rb b/lib/mattermost/error.rb index 014df175be0..dee6deb7974 100644 --- a/lib/mattermost/error.rb +++ b/lib/mattermost/error.rb @@ -1,3 +1,3 @@ module Mattermost - class Error < StandardError; end + Error = Class.new(StandardError) end diff --git a/lib/mattermost/session.rb b/lib/mattermost/session.rb index 377cb7b1021..5388966605d 100644 --- a/lib/mattermost/session.rb +++ b/lib/mattermost/session.rb @@ -5,7 +5,7 @@ module Mattermost end end - class ConnectionError < Mattermost::Error; end + ConnectionError = Class.new(Mattermost::Error) # This class' prime objective is to obtain a session token on a Mattermost # instance with SSO configured where this GitLab instance is the provider. diff --git a/package.json b/package.json index 5528303ab21..6b2991f9608 100644 --- a/package.json +++ b/package.json @@ -21,11 +21,11 @@ "dropzone": "^4.2.0", "es6-promise": "^4.0.5", "jquery": "^2.2.1", - "jquery-ui": "git+https://github.com/jquery/jquery-ui#1.11.4", "jquery-ujs": "^1.2.1", "js-cookie": "^2.1.3", "mousetrap": "^1.4.6", "pikaday": "^1.5.1", + "raw-loader": "^0.5.1", "select2": "3.5.2-browserify", "stats-webpack-plugin": "^0.4.3", "timeago.js": "^2.0.5", diff --git a/public/ci/build-canceled.svg b/public/ci/build-canceled.svg deleted file mode 100644 index 922e28bf696..00000000000 --- a/public/ci/build-canceled.svg +++ /dev/null @@ -1 +0,0 @@ -<svg xmlns="http://www.w3.org/2000/svg" width="97" height="20"><linearGradient id="b" x2="0" y2="100%"><stop offset="0" stop-color="#bbb" stop-opacity=".1"/><stop offset="1" stop-opacity=".1"/></linearGradient><mask id="a"><rect width="97" height="20" rx="3" fill="#fff"/></mask><g mask="url(#a)"><path fill="#555" d="M0 0h37v20H0z"/><path fill="#9f9f9f" d="M37 0h60v20H37z"/><path fill="url(#b)" d="M0 0h97v20H0z"/></g><g fill="#fff" text-anchor="middle" font-family="DejaVu Sans,Verdana,Geneva,sans-serif" font-size="11"><text x="18.5" y="15" fill="#010101" fill-opacity=".3">build</text><text x="18.5" y="14">build</text><text x="66" y="15" fill="#010101" fill-opacity=".3">canceled</text><text x="66" y="14">canceled</text></g></svg>
\ No newline at end of file diff --git a/public/ci/build-failed.svg b/public/ci/build-failed.svg deleted file mode 100644 index 1aefd3f1761..00000000000 --- a/public/ci/build-failed.svg +++ /dev/null @@ -1 +0,0 @@ -<svg xmlns="http://www.w3.org/2000/svg" width="78" height="20"><linearGradient id="b" x2="0" y2="100%"><stop offset="0" stop-color="#bbb" stop-opacity=".1"/><stop offset="1" stop-opacity=".1"/></linearGradient><mask id="a"><rect width="78" height="20" rx="3" fill="#fff"/></mask><g mask="url(#a)"><path fill="#555" d="M0 0h37v20H0z"/><path fill="#e05d44" d="M37 0h41v20H37z"/><path fill="url(#b)" d="M0 0h78v20H0z"/></g><g fill="#fff" text-anchor="middle" font-family="DejaVu Sans,Verdana,Geneva,sans-serif" font-size="11"><text x="18.5" y="15" fill="#010101" fill-opacity=".3">build</text><text x="18.5" y="14">build</text><text x="56.5" y="15" fill="#010101" fill-opacity=".3">failed</text><text x="56.5" y="14">failed</text></g></svg>
\ No newline at end of file diff --git a/public/ci/build-pending.svg b/public/ci/build-pending.svg deleted file mode 100644 index 536931af84d..00000000000 --- a/public/ci/build-pending.svg +++ /dev/null @@ -1 +0,0 @@ -<svg xmlns="http://www.w3.org/2000/svg" width="92" height="20"><linearGradient id="b" x2="0" y2="100%"><stop offset="0" stop-color="#bbb" stop-opacity=".1"/><stop offset="1" stop-opacity=".1"/></linearGradient><mask id="a"><rect width="92" height="20" rx="3" fill="#fff"/></mask><g mask="url(#a)"><path fill="#555" d="M0 0h37v20H0z"/><path fill="#dfb317" d="M37 0h55v20H37z"/><path fill="url(#b)" d="M0 0h92v20H0z"/></g><g fill="#fff" text-anchor="middle" font-family="DejaVu Sans,Verdana,Geneva,sans-serif" font-size="11"><text x="18.5" y="15" fill="#010101" fill-opacity=".3">build</text><text x="18.5" y="14">build</text><text x="63.5" y="15" fill="#010101" fill-opacity=".3">pending</text><text x="63.5" y="14">pending</text></g></svg>
\ No newline at end of file diff --git a/public/ci/build-running.svg b/public/ci/build-running.svg deleted file mode 100644 index 0d71eef3c34..00000000000 --- a/public/ci/build-running.svg +++ /dev/null @@ -1 +0,0 @@ -<svg xmlns="http://www.w3.org/2000/svg" width="90" height="20"><linearGradient id="b" x2="0" y2="100%"><stop offset="0" stop-color="#bbb" stop-opacity=".1"/><stop offset="1" stop-opacity=".1"/></linearGradient><mask id="a"><rect width="90" height="20" rx="3" fill="#fff"/></mask><g mask="url(#a)"><path fill="#555" d="M0 0h37v20H0z"/><path fill="#dfb317" d="M37 0h53v20H37z"/><path fill="url(#b)" d="M0 0h90v20H0z"/></g><g fill="#fff" text-anchor="middle" font-family="DejaVu Sans,Verdana,Geneva,sans-serif" font-size="11"><text x="18.5" y="15" fill="#010101" fill-opacity=".3">build</text><text x="18.5" y="14">build</text><text x="62.5" y="15" fill="#010101" fill-opacity=".3">running</text><text x="62.5" y="14">running</text></g></svg>
\ No newline at end of file diff --git a/public/ci/build-skipped.svg b/public/ci/build-skipped.svg deleted file mode 100644 index f15507188e0..00000000000 --- a/public/ci/build-skipped.svg +++ /dev/null @@ -1 +0,0 @@ -<svg xmlns="http://www.w3.org/2000/svg" width="97" height="20"><linearGradient id="b" x2="0" y2="100%"><stop offset="0" stop-color="#bbb" stop-opacity=".1"/><stop offset="1" stop-opacity=".1"/></linearGradient><mask id="a"><rect width="97" height="20" rx="3" fill="#fff"/></mask><g mask="url(#a)"><path fill="#555" d="M0 0h37v20H0z"/><path fill="#9f9f9f" d="M37 0h60v20H37z"/><path fill="url(#b)" d="M0 0h97v20H0z"/></g><g fill="#fff" text-anchor="middle" font-family="DejaVu Sans,Verdana,Geneva,sans-serif" font-size="11"><text x="18.5" y="15" fill="#010101" fill-opacity=".3">build</text><text x="18.5" y="14">build</text><text x="66" y="15" fill="#010101" fill-opacity=".3">skipped</text><text x="66" y="14">skipped</text></g></svg>
\ No newline at end of file diff --git a/public/ci/build-success.svg b/public/ci/build-success.svg deleted file mode 100644 index 43b67e45f42..00000000000 --- a/public/ci/build-success.svg +++ /dev/null @@ -1 +0,0 @@ -<svg xmlns="http://www.w3.org/2000/svg" width="91" height="20"><linearGradient id="b" x2="0" y2="100%"><stop offset="0" stop-color="#bbb" stop-opacity=".1"/><stop offset="1" stop-opacity=".1"/></linearGradient><mask id="a"><rect width="91" height="20" rx="3" fill="#fff"/></mask><g mask="url(#a)"><path fill="#555" d="M0 0h37v20H0z"/><path fill="#4c1" d="M37 0h54v20H37z"/><path fill="url(#b)" d="M0 0h91v20H0z"/></g><g fill="#fff" text-anchor="middle" font-family="DejaVu Sans,Verdana,Geneva,sans-serif" font-size="11"><text x="18.5" y="15" fill="#010101" fill-opacity=".3">build</text><text x="18.5" y="14">build</text><text x="63" y="15" fill="#010101" fill-opacity=".3">success</text><text x="63" y="14">success</text></g></svg>
\ No newline at end of file diff --git a/public/ci/build-unknown.svg b/public/ci/build-unknown.svg deleted file mode 100644 index c72a2f5a7f5..00000000000 --- a/public/ci/build-unknown.svg +++ /dev/null @@ -1 +0,0 @@ -<svg xmlns="http://www.w3.org/2000/svg" width="98" height="20"><linearGradient id="b" x2="0" y2="100%"><stop offset="0" stop-color="#bbb" stop-opacity=".1"/><stop offset="1" stop-opacity=".1"/></linearGradient><mask id="a"><rect width="98" height="20" rx="3" fill="#fff"/></mask><g mask="url(#a)"><path fill="#555" d="M0 0h37v20H0z"/><path fill="#9f9f9f" d="M37 0h61v20H37z"/><path fill="url(#b)" d="M0 0h98v20H0z"/></g><g fill="#fff" text-anchor="middle" font-family="DejaVu Sans,Verdana,Geneva,sans-serif" font-size="11"><text x="18.5" y="15" fill="#010101" fill-opacity=".3">build</text><text x="18.5" y="14">build</text><text x="66.5" y="15" fill="#010101" fill-opacity=".3">unknown</text><text x="66.5" y="14">unknown</text></g></svg>
\ No newline at end of file diff --git a/rubocop/cop/custom_error_class.rb b/rubocop/cop/custom_error_class.rb new file mode 100644 index 00000000000..38d93acfe88 --- /dev/null +++ b/rubocop/cop/custom_error_class.rb @@ -0,0 +1,64 @@ +module RuboCop + module Cop + # This cop makes sure that custom error classes, when empty, are declared + # with Class.new. + # + # @example + # # bad + # class FooError < StandardError + # end + # + # # okish + # class FooError < StandardError; end + # + # # good + # FooError = Class.new(StandardError) + class CustomErrorClass < RuboCop::Cop::Cop + MSG = 'Use `Class.new(SuperClass)` to define an empty custom error class.'.freeze + + def on_class(node) + _klass, parent, body = node.children + + return if body + + parent_klass = class_name_from_node(parent) + + return unless parent_klass && parent_klass.to_s.end_with?('Error') + + add_offense(node, :expression) + end + + def autocorrect(node) + klass, parent, _body = node.children + replacement = "#{class_name_from_node(klass)} = Class.new(#{class_name_from_node(parent)})" + + lambda do |corrector| + corrector.replace(node.source_range, replacement) + end + end + + private + + # The nested constant `Foo::Bar::Baz` looks like: + # + # s(:const, + # s(:const, + # s(:const, nil, :Foo), :Bar), :Baz) + # + # So recurse through that to get the name as written in the source. + # + def class_name_from_node(node, suffix = nil) + return unless node&.type == :const + + name = node.children[1].to_s + name = "#{name}::#{suffix}" if suffix + + if node.children[0] + class_name_from_node(node.children[0], name) + else + name + end + end + end + end +end diff --git a/rubocop/rubocop.rb b/rubocop/rubocop.rb index d4266d0deae..ea8e0f64b0d 100644 --- a/rubocop/rubocop.rb +++ b/rubocop/rubocop.rb @@ -1,3 +1,4 @@ +require_relative 'cop/custom_error_class' require_relative 'cop/gem_fetcher' require_relative 'cop/migration/add_column' require_relative 'cop/migration/add_column_with_default' diff --git a/spec/controllers/ci/projects_controller_spec.rb b/spec/controllers/ci/projects_controller_spec.rb deleted file mode 100644 index 86f01f437a2..00000000000 --- a/spec/controllers/ci/projects_controller_spec.rb +++ /dev/null @@ -1,74 +0,0 @@ -require 'spec_helper' - -describe Ci::ProjectsController do - let(:visibility) { :public } - let!(:project) { create(:empty_project, visibility, ci_id: 1) } - let(:ci_id) { project.ci_id } - - describe '#index' do - context 'user signed in' do - before do - sign_in(create(:user)) - get(:index) - end - - it 'redirects to /' do - expect(response).to redirect_to(root_path) - end - end - - context 'user not signed in' do - before { get(:index) } - - it 'redirects to sign in page' do - expect(response).to redirect_to(new_user_session_path) - end - end - end - - ## - # Specs for *deprecated* CI badge - # - describe '#badge' do - shared_examples 'badge provider' do - it 'shows badge' do - expect(response.status).to eq 200 - expect(response.headers) - .to include('Content-Type' => 'image/svg+xml') - end - end - - context 'user not signed in' do - before { get(:badge, id: ci_id) } - - context 'project has no ci_id reference' do - let(:ci_id) { 123 } - - it 'returns 404' do - expect(response.status).to eq 404 - end - end - - context 'project is public' do - let(:visibility) { :public } - it_behaves_like 'badge provider' - end - - context 'project is private' do - let(:visibility) { :private } - it_behaves_like 'badge provider' - end - end - - context 'user signed in' do - let(:user) { create(:user) } - before { sign_in(user) } - before { get(:badge, id: ci_id) } - - context 'private is internal' do - let(:visibility) { :internal } - it_behaves_like 'badge provider' - end - end - end -end diff --git a/spec/controllers/projects/graphs_controller_spec.rb b/spec/controllers/projects/graphs_controller_spec.rb index c4a7aa7d63e..049bae1899d 100644 --- a/spec/controllers/projects/graphs_controller_spec.rb +++ b/spec/controllers/projects/graphs_controller_spec.rb @@ -9,7 +9,23 @@ describe Projects::GraphsController do project.team << [user, :master] end - describe 'GET #languages' do + describe 'GET languages' do + it "redirects_to action charts" do + get(:commits, namespace_id: project.namespace.path, project_id: project.path, id: 'master') + + expect(response).to redirect_to action: :charts + end + end + + describe 'GET commits' do + it "redirects_to action charts" do + get(:commits, namespace_id: project.namespace.path, project_id: project.path, id: 'master') + + expect(response).to redirect_to action: :charts + end + end + + describe 'GET charts' do let(:linguist_repository) do double(languages: { 'Ruby' => 1000, @@ -34,7 +50,7 @@ describe Projects::GraphsController do end it 'sets the correct colour according to language' do - get(:languages, namespace_id: project.namespace, project_id: project, id: 'master') + get(:charts, namespace_id: project.namespace, project_id: project, id: 'master') expected_values.each do |val| expect(assigns(:languages)).to include(a_hash_including(val)) diff --git a/spec/controllers/projects/merge_requests_controller_spec.rb b/spec/controllers/projects/merge_requests_controller_spec.rb index 1ced666bb36..250d64f7055 100644 --- a/spec/controllers/projects/merge_requests_controller_spec.rb +++ b/spec/controllers/projects/merge_requests_controller_spec.rb @@ -43,7 +43,8 @@ describe Projects::MergeRequestsController do submit_new_merge_request(format: :json) expect(response).to be_ok - expect(json_response).not_to be_empty + expect(json_response).to have_key 'pipelines' + expect(json_response['pipelines']).not_to be_empty end end end @@ -319,41 +320,41 @@ describe Projects::MergeRequestsController do merge_with_sha end - context 'when merge_when_build_succeeds is passed' do - def merge_when_build_succeeds - post :merge, base_params.merge(sha: merge_request.diff_head_sha, merge_when_build_succeeds: '1') + context 'when the pipeline succeeds is passed' do + def merge_when_pipeline_succeeds + post :merge, base_params.merge(sha: merge_request.diff_head_sha, merge_when_pipeline_succeeds: '1') end before do create(:ci_empty_pipeline, project: project, sha: merge_request.diff_head_sha, ref: merge_request.source_branch) end - it 'returns :merge_when_build_succeeds' do - merge_when_build_succeeds + it 'returns :merge_when_pipeline_succeeds' do + merge_when_pipeline_succeeds - expect(assigns(:status)).to eq(:merge_when_build_succeeds) + expect(assigns(:status)).to eq(:merge_when_pipeline_succeeds) end - it 'sets the MR to merge when the build succeeds' do - service = double(:merge_when_build_succeeds_service) + it 'sets the MR to merge when the pipeline succeeds' do + service = double(:merge_when_pipeline_succeeds_service) expect(MergeRequests::MergeWhenPipelineSucceedsService) .to receive(:new).with(project, anything, anything) .and_return(service) expect(service).to receive(:execute).with(merge_request) - merge_when_build_succeeds + merge_when_pipeline_succeeds end - context 'when project.only_allow_merge_if_build_succeeds? is true' do + context 'when project.only_allow_merge_if_pipeline_succeeds? is true' do before do - project.update_column(:only_allow_merge_if_build_succeeds, true) + project.update_column(:only_allow_merge_if_pipeline_succeeds, true) end - it 'returns :merge_when_build_succeeds' do - merge_when_build_succeeds + it 'returns :merge_when_pipeline_succeeds' do + merge_when_pipeline_succeeds - expect(assigns(:status)).to eq(:merge_when_build_succeeds) + expect(assigns(:status)).to eq(:merge_when_pipeline_succeeds) end end end @@ -1134,14 +1135,14 @@ describe Projects::MergeRequestsController do end context 'when waiting for build' do - let(:merge_request) { create(:merge_request, source_project: project, merge_when_build_succeeds: true, merge_user: user) } + let(:merge_request) { create(:merge_request, source_project: project, merge_when_pipeline_succeeds: true, merge_user: user) } it 'returns an OK response' do expect(response).to have_http_status(:ok) end - it 'sets status to :merge_when_build_succeeds' do - expect(assigns(:status)).to eq(:merge_when_build_succeeds) + it 'sets status to :merge_when_pipeline_succeeds' do + expect(assigns(:status)).to eq(:merge_when_pipeline_succeeds) expect(response).to render_template('merge') end end diff --git a/spec/controllers/root_controller_spec.rb b/spec/controllers/root_controller_spec.rb index b14d275f7fa..b32eb39b1fb 100644 --- a/spec/controllers/root_controller_spec.rb +++ b/spec/controllers/root_controller_spec.rb @@ -2,6 +2,26 @@ require 'spec_helper' describe RootController do describe 'GET index' do + context 'when user is not logged in' do + it 'redirects to the sign-in page' do + get :index + + expect(response).to redirect_to(new_user_session_path) + end + + context 'when a custom home page URL is defined' do + before do + stub_application_setting(home_page_url: 'https://gitlab.com') + end + + it 'redirects the user to the custom home page URL' do + get :index + + expect(response).to redirect_to('https://gitlab.com') + end + end + end + context 'with a user' do let(:user) { create(:user) } @@ -12,55 +32,60 @@ describe RootController do context 'who has customized their dashboard setting for starred projects' do before do - user.update_attribute(:dashboard, 'stars') + user.dashboard = 'stars' end it 'redirects to their specified dashboard' do get :index + expect(response).to redirect_to starred_dashboard_projects_path end end context 'who has customized their dashboard setting for project activities' do before do - user.update_attribute(:dashboard, 'project_activity') + user.dashboard = 'project_activity' end it 'redirects to the activity list' do get :index + expect(response).to redirect_to activity_dashboard_path end end context 'who has customized their dashboard setting for starred project activities' do before do - user.update_attribute(:dashboard, 'starred_project_activity') + user.dashboard = 'starred_project_activity' end it 'redirects to the activity list' do get :index + expect(response).to redirect_to activity_dashboard_path(filter: 'starred') end end context 'who has customized their dashboard setting for groups' do before do - user.update_attribute(:dashboard, 'groups') + user.dashboard = 'groups' end it 'redirects to their group list' do get :index + expect(response).to redirect_to dashboard_groups_path end end context 'who has customized their dashboard setting for todos' do before do - user.update_attribute(:dashboard, 'todos') + user.dashboard = 'todos' end it 'redirects to their todo list' do get :index + expect(response).to redirect_to dashboard_todos_path end end @@ -68,6 +93,7 @@ describe RootController do context 'who uses the default dashboard setting' do it 'renders the default dashboard' do get :index + expect(response).to render_template 'dashboard/projects/index' end end diff --git a/spec/factories/merge_requests.rb b/spec/factories/merge_requests.rb index 22f84150bb3..ae0bbbd6aeb 100644 --- a/spec/factories/merge_requests.rb +++ b/spec/factories/merge_requests.rb @@ -59,8 +59,8 @@ FactoryGirl.define do target_branch "master" end - trait :merge_when_build_succeeds do - merge_when_build_succeeds true + trait :merge_when_pipeline_succeeds do + merge_when_pipeline_succeeds true merge_user author end diff --git a/spec/factories/projects.rb b/spec/factories/projects.rb index 586efdefdb3..04de3512125 100644 --- a/spec/factories/projects.rb +++ b/spec/factories/projects.rb @@ -39,6 +39,10 @@ FactoryGirl.define do trait :empty_repo do after(:create) do |project| project.create_repository + + # We delete hooks so that gitlab-shell will not try to authenticate with + # an API that isn't running + FileUtils.rm_r(File.join(project.repository_storage_path, "#{project.path_with_namespace}.git", 'hooks')) end end diff --git a/spec/factories/todos.rb b/spec/factories/todos.rb index a5265f1b189..c1ac3bb84ad 100644 --- a/spec/factories/todos.rb +++ b/spec/factories/todos.rb @@ -18,11 +18,6 @@ FactoryGirl.define do action { Todo::DIRECTLY_ADDRESSED } end - trait :on_commit do - commit_id RepoHelpers.sample_commit.id - target_type "Commit" - end - trait :build_failed do action { Todo::BUILD_FAILED } target factory: :merge_request @@ -48,4 +43,13 @@ FactoryGirl.define do state :done end end + + factory :on_commit_todo, class: Todo do + project factory: :empty_project + author + user + action { Todo::ASSIGNED } + commit_id RepoHelpers.sample_commit.id + target_type "Commit" + end end diff --git a/spec/features/dashboard/activity_spec.rb b/spec/features/dashboard/activity_spec.rb new file mode 100644 index 00000000000..c977f266296 --- /dev/null +++ b/spec/features/dashboard/activity_spec.rb @@ -0,0 +1,11 @@ +require 'spec_helper' + +RSpec.describe 'Dashboard Activity', feature: true do + before do + login_as(create :user) + visit activity_dashboard_path + end + + it_behaves_like "it has an RSS button with current_user's private token" + it_behaves_like "an autodiscoverable RSS feed with current_user's private token" +end diff --git a/spec/features/dashboard/groups_list_spec.rb b/spec/features/dashboard/groups_list_spec.rb new file mode 100644 index 00000000000..ca04107d33a --- /dev/null +++ b/spec/features/dashboard/groups_list_spec.rb @@ -0,0 +1,47 @@ +require 'spec_helper' + +describe 'Dashboard Groups page', js: true, feature: true do + include WaitForAjax + + let!(:user) { create :user } + let!(:group) { create(:group) } + let!(:nested_group) { create(:group, :nested) } + let!(:another_group) { create(:group) } + + before do + group.add_owner(user) + nested_group.add_owner(user) + + login_as(user) + + visit dashboard_groups_path + end + + it 'shows groups user is member of' do + expect(page).to have_content(group.full_name) + expect(page).to have_content(nested_group.full_name) + expect(page).not_to have_content(another_group.full_name) + end + + it 'filters groups' do + fill_in 'filter_groups', with: group.name + wait_for_ajax + + expect(page).to have_content(group.full_name) + expect(page).not_to have_content(nested_group.full_name) + expect(page).not_to have_content(another_group.full_name) + end + + it 'resets search when user cleans the input' do + fill_in 'filter_groups', with: group.name + wait_for_ajax + + fill_in 'filter_groups', with: "" + wait_for_ajax + + expect(page).to have_content(group.full_name) + expect(page).to have_content(nested_group.full_name) + expect(page).not_to have_content(another_group.full_name) + expect(page.all('.js-groups-list-holder .content-list li').length).to eq 2 + end +end diff --git a/spec/features/dashboard/issues_spec.rb b/spec/features/dashboard/issues_spec.rb index 2db1cf71209..f4420814c3a 100644 --- a/spec/features/dashboard/issues_spec.rb +++ b/spec/features/dashboard/issues_spec.rb @@ -45,4 +45,7 @@ RSpec.describe 'Dashboard Issues', feature: true do expect(page).to have_content(assigned_issue.title) expect(page).to have_content(other_issue.title) end + + it_behaves_like "it has an RSS button with current_user's private token" + it_behaves_like "an autodiscoverable RSS feed with current_user's private token" end diff --git a/spec/features/dashboard/projects_spec.rb b/spec/features/dashboard/projects_spec.rb new file mode 100644 index 00000000000..63eb5c697c2 --- /dev/null +++ b/spec/features/dashboard/projects_spec.rb @@ -0,0 +1,10 @@ +require 'spec_helper' + +RSpec.describe 'Dashboard Projects', feature: true do + before do + login_as(create :user) + visit dashboard_projects_path + end + + it_behaves_like "an autodiscoverable RSS feed with current_user's private token" +end diff --git a/spec/features/explore/groups_list_spec.rb b/spec/features/explore/groups_list_spec.rb new file mode 100644 index 00000000000..773ae4b38bc --- /dev/null +++ b/spec/features/explore/groups_list_spec.rb @@ -0,0 +1,46 @@ +require 'spec_helper' + +describe 'Explore Groups page', js: true, feature: true do + include WaitForAjax + + let!(:user) { create :user } + let!(:group) { create(:group) } + let!(:public_group) { create(:group, :public) } + let!(:private_group) { create(:group, :private) } + + before do + group.add_owner(user) + + login_as(user) + + visit explore_groups_path + end + + it 'shows groups user is member of' do + expect(page).to have_content(group.full_name) + expect(page).to have_content(public_group.full_name) + expect(page).not_to have_content(private_group.full_name) + end + + it 'filters groups' do + fill_in 'filter_groups', with: group.name + wait_for_ajax + + expect(page).to have_content(group.full_name) + expect(page).not_to have_content(public_group.full_name) + expect(page).not_to have_content(private_group.full_name) + end + + it 'resets search when user cleans the input' do + fill_in 'filter_groups', with: group.name + wait_for_ajax + + fill_in 'filter_groups', with: "" + wait_for_ajax + + expect(page).to have_content(group.full_name) + expect(page).to have_content(public_group.full_name) + expect(page).not_to have_content(private_group.full_name) + expect(page.all('.js-groups-list-holder .content-list li').length).to eq 2 + end +end diff --git a/spec/features/groups/activity_spec.rb b/spec/features/groups/activity_spec.rb new file mode 100644 index 00000000000..3b481cba424 --- /dev/null +++ b/spec/features/groups/activity_spec.rb @@ -0,0 +1,26 @@ +require 'spec_helper' + +feature 'Group activity page', feature: true do + let(:group) { create(:group) } + let(:path) { activity_group_path(group) } + + context 'when signed in' do + before do + user = create(:group_member, :developer, user: create(:user), group: group ).user + login_as(user) + visit path + end + + it_behaves_like "it has an RSS button with current_user's private token" + it_behaves_like "an autodiscoverable RSS feed with current_user's private token" + end + + context 'when signed out' do + before do + visit path + end + + it_behaves_like "it has an RSS button without a private token" + it_behaves_like "an autodiscoverable RSS feed without a private token" + end +end diff --git a/spec/features/groups/issues_spec.rb b/spec/features/groups/issues_spec.rb index 476eca17a9d..1b3747c390b 100644 --- a/spec/features/groups/issues_spec.rb +++ b/spec/features/groups/issues_spec.rb @@ -5,4 +5,22 @@ feature 'Group issues page', feature: true do let(:issuable) { create(:issue, project: project, title: "this is my created issuable")} include_examples 'project features apply to issuables', Issue + + context 'rss feed' do + let(:access_level) { ProjectFeature::ENABLED } + + context 'when signed in' do + let(:user) { user_in_group } + + it_behaves_like "it has an RSS button with current_user's private token" + it_behaves_like "an autodiscoverable RSS feed with current_user's private token" + end + + context 'when signed out' do + let(:user) { nil } + + it_behaves_like "it has an RSS button without a private token" + it_behaves_like "an autodiscoverable RSS feed without a private token" + end + end end diff --git a/spec/features/groups/show_spec.rb b/spec/features/groups/show_spec.rb new file mode 100644 index 00000000000..fb39693e8ca --- /dev/null +++ b/spec/features/groups/show_spec.rb @@ -0,0 +1,24 @@ +require 'spec_helper' + +feature 'Group show page', feature: true do + let(:group) { create(:group) } + let(:path) { group_path(group) } + + context 'when signed in' do + before do + user = create(:group_member, :developer, user: create(:user), group: group ).user + login_as(user) + visit path + end + + it_behaves_like "an autodiscoverable RSS feed with current_user's private token" + end + + context 'when signed out' do + before do + visit path + end + + it_behaves_like "an autodiscoverable RSS feed without a private token" + end +end diff --git a/spec/features/issues/award_emoji_spec.rb b/spec/features/issues/award_emoji_spec.rb index 73e43316dc7..3ab3d2d4229 100644 --- a/spec/features/issues/award_emoji_spec.rb +++ b/spec/features/issues/award_emoji_spec.rb @@ -67,6 +67,18 @@ describe 'Awards Emoji', feature: true do expect(page).not_to have_selector(emoji_counter) end end + + context 'execute /award slash command' do + it 'toggles the emoji award on noteable', js: true do + execute_slash_command('/award :100:') + + expect(find(noteable_award_counter)).to have_text("1") + + execute_slash_command('/award :100:') + + expect(page).not_to have_selector(noteable_award_counter) + end + end end end @@ -80,6 +92,15 @@ describe 'Awards Emoji', feature: true do end end + def execute_slash_command(cmd) + within('.js-main-target-form') do + fill_in 'note[note]', with: cmd + click_button 'Comment' + end + + wait_for_ajax + end + def thumbsup_emoji page.all(emoji_counter).first end @@ -92,6 +113,10 @@ describe 'Awards Emoji', feature: true do 'span.js-counter' end + def noteable_award_counter + ".awards .active" + end + def toggle_smiley_emoji(status) within('.note') do find('.note-emoji-button').click diff --git a/spec/features/merge_requests/merge_immediately_with_pipeline_spec.rb b/spec/features/merge_requests/merge_immediately_with_pipeline_spec.rb index f2f8f11ab28..0ceaf7bc830 100644 --- a/spec/features/merge_requests/merge_immediately_with_pipeline_spec.rb +++ b/spec/features/merge_requests/merge_immediately_with_pipeline_spec.rb @@ -34,7 +34,7 @@ feature 'Merge immediately', :feature, :js do click_link 'Merge Immediately' - expect(find('.js-merge-button')).to have_content('Merge in progress') + expect(find('.js-merge-when-pipeline-succeeds-button')).to have_content('Merge in progress') end end end diff --git a/spec/features/merge_requests/merge_when_pipeline_succeeds_spec.rb b/spec/features/merge_requests/merge_when_pipeline_succeeds_spec.rb index 2ea9c317bd1..ed7193b9777 100644 --- a/spec/features/merge_requests/merge_when_pipeline_succeeds_spec.rb +++ b/spec/features/merge_requests/merge_when_pipeline_succeeds_spec.rb @@ -75,7 +75,7 @@ feature 'Merge When Pipeline Succeeds', :feature, :js do context 'when it was enabled and then canceled' do let(:merge_request) do create(:merge_request_with_diffs, - :merge_when_build_succeeds, + :merge_when_pipeline_succeeds, source_project: project, title: 'Bug NS-04', author: user, @@ -97,7 +97,7 @@ feature 'Merge When Pipeline Succeeds', :feature, :js do author: user, merge_user: user, title: 'MepMep', - merge_when_build_succeeds: true) + merge_when_pipeline_succeeds: true) end let!(:build) do diff --git a/spec/features/merge_requests/only_allow_merge_if_build_succeeds_spec.rb b/spec/features/merge_requests/only_allow_merge_if_build_succeeds_spec.rb index d2f5c4afc93..447764566e0 100644 --- a/spec/features/merge_requests/only_allow_merge_if_build_succeeds_spec.rb +++ b/spec/features/merge_requests/only_allow_merge_if_build_succeeds_spec.rb @@ -1,6 +1,6 @@ require 'spec_helper' -feature 'Only allow merge requests to be merged if the build succeeds', feature: true do +feature 'Only allow merge requests to be merged if the pipeline succeeds', feature: true do let(:merge_request) { create(:merge_request_with_diffs) } let(:project) { merge_request.target_project } @@ -27,9 +27,9 @@ feature 'Only allow merge requests to be merged if the build succeeds', feature: status: status) end - context 'when merge requests can only be merged if the build succeeds' do + context 'when merge requests can only be merged if the pipeline succeeds' do before do - project.update_attribute(:only_allow_merge_if_build_succeeds, true) + project.update_attribute(:only_allow_merge_if_pipeline_succeeds, true) end context 'when CI is running' do @@ -88,7 +88,7 @@ feature 'Only allow merge requests to be merged if the build succeeds', feature: context 'when merge requests can be merged when the build failed' do before do - project.update_attribute(:only_allow_merge_if_build_succeeds, false) + project.update_attribute(:only_allow_merge_if_pipeline_succeeds, false) end context 'when CI is running' do diff --git a/spec/features/merge_requests/widget_spec.rb b/spec/features/merge_requests/widget_spec.rb index 4ad944366c8..b575aeff0d8 100644 --- a/spec/features/merge_requests/widget_spec.rb +++ b/spec/features/merge_requests/widget_spec.rb @@ -3,8 +3,8 @@ require 'rails_helper' describe 'Merge request', :feature, :js do include WaitForAjax - let(:project) { create(:project) } let(:user) { create(:user) } + let(:project) { create(:project) } let(:merge_request) { create(:merge_request, source_project: project) } before do @@ -31,7 +31,7 @@ describe 'Merge request', :feature, :js do wait_for_ajax - expect(page).to have_selector('.accept_merge_request') + expect(page).to have_selector('.accept-merge-request') end end @@ -51,6 +51,69 @@ describe 'Merge request', :feature, :js do expect(find('.js-environment-link')[:href]).to include(environment.formatted_external_url) end end + + it 'shows green accept merge request button' do + # Wait for the `ci_status` and `merge_check` requests + wait_for_ajax + expect(page).to have_selector('.accept-merge-request.btn-create') + end + end + + context 'view merge request with external CI service' do + before do + create(:service, project: project, + active: true, + type: 'CiService', + category: 'ci') + + visit namespace_project_merge_request_path(project.namespace, project, merge_request) + end + + it 'has danger button while waiting for external CI status' do + # Wait for the `ci_status` and `merge_check` requests + wait_for_ajax + expect(page).to have_selector('.accept-merge-request.btn-danger') + end + end + + context 'view merge request with failed GitLab CI pipelines' do + before do + commit_status = create(:commit_status, project: project, status: 'failed') + pipeline = create(:ci_pipeline, project: project, + sha: merge_request.diff_head_sha, + ref: merge_request.source_branch, + status: 'failed', + statuses: [commit_status]) + create(:ci_build, :pending, pipeline: pipeline) + + visit namespace_project_merge_request_path(project.namespace, project, merge_request) + end + + it 'has danger button when not succeeded' do + # Wait for the `ci_status` and `merge_check` requests + wait_for_ajax + expect(page).to have_selector('.accept-merge-request.btn-danger') + end + end + + context 'view merge request with MWBS button' do + before do + commit_status = create(:commit_status, project: project, status: 'pending') + pipeline = create(:ci_pipeline, project: project, + sha: merge_request.diff_head_sha, + ref: merge_request.source_branch, + status: 'pending', + statuses: [commit_status]) + create(:ci_build, :pending, pipeline: pipeline) + + visit namespace_project_merge_request_path(project.namespace, project, merge_request) + end + + it 'has info button when MWBS button' do + # Wait for the `ci_status` and `merge_check` requests + wait_for_ajax + expect(page).to have_selector('.merge-when-pipeline-succeeds.btn-info') + end end context 'merge error' do diff --git a/spec/features/projects/activity/rss_spec.rb b/spec/features/projects/activity/rss_spec.rb new file mode 100644 index 00000000000..b47c6d431eb --- /dev/null +++ b/spec/features/projects/activity/rss_spec.rb @@ -0,0 +1,29 @@ +require 'spec_helper' + +feature 'Project Activity RSS' do + let(:project) { create(:empty_project, visibility_level: Gitlab::VisibilityLevel::PUBLIC) } + let(:path) { activity_namespace_project_path(project.namespace, project) } + + before do + create(:issue, project: project) + end + + context 'when signed in' do + before do + user = create(:user) + project.team << [user, :developer] + login_as(user) + visit path + end + + it_behaves_like "it has an RSS button with current_user's private token" + end + + context 'when signed out' do + before do + visit path + end + + it_behaves_like "it has an RSS button without a private token" + end +end diff --git a/spec/features/projects/commit/rss_spec.rb b/spec/features/projects/commit/rss_spec.rb new file mode 100644 index 00000000000..6e0e1916f87 --- /dev/null +++ b/spec/features/projects/commit/rss_spec.rb @@ -0,0 +1,27 @@ +require 'spec_helper' + +feature 'Project Commits RSS' do + let(:project) { create(:project, :repository, visibility_level: Gitlab::VisibilityLevel::PUBLIC) } + let(:path) { namespace_project_commits_path(project.namespace, project, :master) } + + context 'when signed in' do + before do + user = create(:user) + project.team << [user, :developer] + login_as(user) + visit path + end + + it_behaves_like "it has an RSS button with current_user's private token" + it_behaves_like "an autodiscoverable RSS feed with current_user's private token" + end + + context 'when signed out' do + before do + visit path + end + + it_behaves_like "it has an RSS button without a private token" + it_behaves_like "an autodiscoverable RSS feed without a private token" + end +end diff --git a/spec/features/projects/guest_navigation_menu_spec.rb b/spec/features/projects/guest_navigation_menu_spec.rb index 8120a51c515..726469daba4 100644 --- a/spec/features/projects/guest_navigation_menu_spec.rb +++ b/spec/features/projects/guest_navigation_menu_spec.rb @@ -15,13 +15,11 @@ describe "Guest navigation menu" do within(".nav-links") do expect(page).to have_content 'Project' - expect(page).to have_content 'Activity' expect(page).to have_content 'Issues' expect(page).to have_content 'Wiki' expect(page).not_to have_content 'Repository' expect(page).not_to have_content 'Pipelines' - expect(page).not_to have_content 'Graphs' expect(page).not_to have_content 'Merge Requests' end end diff --git a/spec/features/projects/issues/rss_spec.rb b/spec/features/projects/issues/rss_spec.rb new file mode 100644 index 00000000000..71429f00095 --- /dev/null +++ b/spec/features/projects/issues/rss_spec.rb @@ -0,0 +1,31 @@ +require 'spec_helper' + +feature 'Project Issues RSS' do + let(:project) { create(:empty_project, visibility_level: Gitlab::VisibilityLevel::PUBLIC) } + let(:path) { namespace_project_issues_path(project.namespace, project) } + + before do + create(:issue, project: project) + end + + context 'when signed in' do + before do + user = create(:user) + project.team << [user, :developer] + login_as(user) + visit path + end + + it_behaves_like "it has an RSS button with current_user's private token" + it_behaves_like "an autodiscoverable RSS feed with current_user's private token" + end + + context 'when signed out' do + before do + visit path + end + + it_behaves_like "it has an RSS button without a private token" + it_behaves_like "an autodiscoverable RSS feed without a private token" + end +end diff --git a/spec/features/projects/main/rss_spec.rb b/spec/features/projects/main/rss_spec.rb new file mode 100644 index 00000000000..b1a3af612a1 --- /dev/null +++ b/spec/features/projects/main/rss_spec.rb @@ -0,0 +1,25 @@ +require 'spec_helper' + +feature 'Project RSS' do + let(:project) { create(:project, :repository, visibility_level: Gitlab::VisibilityLevel::PUBLIC) } + let(:path) { namespace_project_path(project.namespace, project) } + + context 'when signed in' do + before do + user = create(:user) + project.team << [user, :developer] + login_as(user) + visit path + end + + it_behaves_like "an autodiscoverable RSS feed with current_user's private token" + end + + context 'when signed out' do + before do + visit path + end + + it_behaves_like "an autodiscoverable RSS feed without a private token" + end +end diff --git a/spec/features/projects/tree/rss_spec.rb b/spec/features/projects/tree/rss_spec.rb new file mode 100644 index 00000000000..9ac51997d65 --- /dev/null +++ b/spec/features/projects/tree/rss_spec.rb @@ -0,0 +1,25 @@ +require 'spec_helper' + +feature 'Project Tree RSS' do + let(:project) { create(:project, :repository, visibility_level: Gitlab::VisibilityLevel::PUBLIC) } + let(:path) { namespace_project_tree_path(project.namespace, project, :master) } + + context 'when signed in' do + before do + user = create(:user) + project.team << [user, :developer] + login_as(user) + visit path + end + + it_behaves_like "an autodiscoverable RSS feed with current_user's private token" + end + + context 'when signed out' do + before do + visit path + end + + it_behaves_like "an autodiscoverable RSS feed without a private token" + end +end diff --git a/spec/features/users/rss_spec.rb b/spec/features/users/rss_spec.rb new file mode 100644 index 00000000000..14564abb16d --- /dev/null +++ b/spec/features/users/rss_spec.rb @@ -0,0 +1,22 @@ +require 'spec_helper' + +feature 'User RSS' do + let(:path) { user_path(create(:user)) } + + context 'when signed in' do + before do + login_as(create(:user)) + visit path + end + + it_behaves_like "it has an RSS button with current_user's private token" + end + + context 'when signed out' do + before do + visit path + end + + it_behaves_like "it has an RSS button without a private token" + end +end diff --git a/spec/helpers/rss_helper_spec.rb b/spec/helpers/rss_helper_spec.rb new file mode 100644 index 00000000000..f3f174f3d14 --- /dev/null +++ b/spec/helpers/rss_helper_spec.rb @@ -0,0 +1,20 @@ +require 'spec_helper' + +describe RssHelper do + describe '#rss_url_options' do + context 'when signed in' do + it "includes the current_user's private_token" do + current_user = create(:user) + allow(helper).to receive(:current_user).and_return(current_user) + expect(helper.rss_url_options).to include private_token: current_user.private_token + end + end + + context 'when signed out' do + it "does not have a private_token" do + allow(helper).to receive(:current_user).and_return(nil) + expect(helper.rss_url_options[:private_token]).to be_nil + end + end + end +end diff --git a/spec/javascripts/boards/board_card_spec.js b/spec/javascripts/boards/board_card_spec.js index 192916fbc6a..be31f644e20 100644 --- a/spec/javascripts/boards/board_card_spec.js +++ b/spec/javascripts/boards/board_card_spec.js @@ -8,7 +8,7 @@ require('~/boards/models/list'); require('~/boards/models/label'); require('~/boards/stores/boards_store'); -const boardCard = require('~/boards/components/board_card'); +const boardCard = require('~/boards/components/board_card').default; require('./mock_data'); describe('Issue card', () => { diff --git a/spec/javascripts/boards/board_new_issue_spec.js b/spec/javascripts/boards/board_new_issue_spec.js new file mode 100644 index 00000000000..22c9f12951b --- /dev/null +++ b/spec/javascripts/boards/board_new_issue_spec.js @@ -0,0 +1,191 @@ +/* global boardsMockInterceptor */ +/* global BoardService */ +/* global List */ +/* global listObj */ + +import Vue from 'vue'; +import boardNewIssue from '~/boards/components/board_new_issue'; + +require('~/boards/models/list'); +require('./mock_data'); +require('es6-promise').polyfill(); + +describe('Issue boards new issue form', () => { + let vm; + let list; + const promiseReturn = { + json() { + return { + iid: 100, + }; + }, + }; + const submitIssue = () => { + vm.$el.querySelector('.btn-success').click(); + }; + + beforeEach((done) => { + const BoardNewIssueComp = Vue.extend(boardNewIssue); + + Vue.http.interceptors.push(boardsMockInterceptor); + gl.boardService = new BoardService('/test/issue-boards/board', '', '1'); + gl.issueBoards.BoardsStore.create(); + gl.IssueBoardsApp = new Vue(); + + setTimeout(() => { + list = new List(listObj); + + spyOn(gl.boardService, 'newIssue').and.callFake(() => new Promise((resolve, reject) => { + if (vm.title === 'error') { + reject(); + } else { + resolve(promiseReturn); + } + })); + + vm = new BoardNewIssueComp({ + propsData: { + list, + }, + }).$mount(); + + done(); + }, 0); + }); + + afterEach(() => { + Vue.http.interceptors = _.without(Vue.http.interceptors, boardsMockInterceptor); + }); + + it('disables submit button if title is empty', () => { + expect(vm.$el.querySelector('.btn-success').disabled).toBe(true); + }); + + it('enables submit button if title is not empty', (done) => { + vm.title = 'Testing Title'; + + setTimeout(() => { + expect(vm.$el.querySelector('.form-control').value).toBe('Testing Title'); + expect(vm.$el.querySelector('.btn-success').disabled).not.toBe(true); + + done(); + }, 0); + }); + + it('clears title after clicking cancel', (done) => { + vm.$el.querySelector('.btn-default').click(); + + setTimeout(() => { + expect(vm.title).toBe(''); + done(); + }, 0); + }); + + it('does not create new issue if title is empty', (done) => { + submitIssue(); + + setTimeout(() => { + expect(gl.boardService.newIssue).not.toHaveBeenCalled(); + done(); + }, 0); + }); + + describe('submit success', () => { + it('creates new issue', (done) => { + vm.title = 'submit title'; + + setTimeout(() => { + submitIssue(); + + expect(gl.boardService.newIssue).toHaveBeenCalled(); + done(); + }, 0); + }); + + it('enables button after submit', (done) => { + vm.title = 'submit issue'; + + setTimeout(() => { + submitIssue(); + + expect(vm.$el.querySelector('.btn-success').disbled).not.toBe(true); + done(); + }, 0); + }); + + it('clears title after submit', (done) => { + vm.title = 'submit issue'; + + setTimeout(() => { + submitIssue(); + + expect(vm.title).toBe(''); + done(); + }, 0); + }); + + it('adds new issue to list after submit', (done) => { + vm.title = 'submit issue'; + + setTimeout(() => { + submitIssue(); + + expect(list.issues.length).toBe(2); + expect(list.issues[1].title).toBe('submit issue'); + expect(list.issues[1].subscribed).toBe(true); + done(); + }, 0); + }); + + it('sets detail issue after submit', (done) => { + vm.title = 'submit issue'; + + setTimeout(() => { + submitIssue(); + + expect(gl.issueBoards.BoardsStore.detail.issue.title).toBe('submit issue'); + done(); + }); + }); + + it('sets detail list after submit', (done) => { + vm.title = 'submit issue'; + + setTimeout(() => { + submitIssue(); + + expect(gl.issueBoards.BoardsStore.detail.list.id).toBe(list.id); + done(); + }, 0); + }); + }); + + describe('submit error', () => { + it('removes issue', (done) => { + vm.title = 'error'; + + setTimeout(() => { + submitIssue(); + + setTimeout(() => { + expect(list.issues.length).toBe(1); + done(); + }, 500); + }, 0); + }); + + it('shows error', (done) => { + vm.title = 'error'; + submitIssue(); + + setTimeout(() => { + submitIssue(); + + setTimeout(() => { + expect(vm.error).toBe(true); + done(); + }, 500); + }, 0); + }); + }); +}); diff --git a/spec/javascripts/environments/environment_actions_spec.js.es6 b/spec/javascripts/environments/environment_actions_spec.js.es6 index 850586f9f3a..d50d45d295e 100644 --- a/spec/javascripts/environments/environment_actions_spec.js.es6 +++ b/spec/javascripts/environments/environment_actions_spec.js.es6 @@ -23,7 +23,6 @@ describe('Actions Component', () => { el: document.querySelector('.test-dom-element'), propsData: { actions: actionsMock, - playIconSvg: '<svg></svg>', }, }); @@ -34,33 +33,4 @@ describe('Actions Component', () => { component.$el.querySelector('.dropdown-menu li a').getAttribute('href'), ).toEqual(actionsMock[0].play_path); }); - - it('should render a dropdown with the provided svg', () => { - const actionsMock = [ - { - name: 'bar', - play_path: 'https://gitlab.com/play', - }, - { - name: 'foo', - play_path: '#', - }, - ]; - - const component = new ActionsComponent({ - el: document.querySelector('.test-dom-element'), - propsData: { - actions: actionsMock, - playIconSvg: '<svg></svg>', - }, - }); - - expect( - component.$el.querySelector('.js-dropdown-play-icon-container').children, - ).toContain('svg'); - - expect( - component.$el.querySelector('.js-action-play-icon-container').children, - ).toContain('svg'); - }); }); diff --git a/spec/javascripts/fixtures/projects.json b/spec/javascripts/fixtures/projects.json index 4ce7f5c601a..1339ee00870 100644 --- a/spec/javascripts/fixtures/projects.json +++ b/spec/javascripts/fixtures/projects.json @@ -43,7 +43,7 @@ "avatar_url": null, "star_count": 0, "forks_count": 0, - "only_allow_merge_if_build_succeeds": false, + "only_allow_merge_if_pipeline_succeeds": false, "open_issues_count": 0, "permissions": { "project_access": null, @@ -88,7 +88,7 @@ "avatar_url": null, "star_count": 0, "forks_count": 0, - "only_allow_merge_if_build_succeeds": false, + "only_allow_merge_if_pipeline_succeeds": false, "open_issues_count": 5, "permissions": { "project_access": { @@ -139,7 +139,7 @@ "avatar_url": null, "star_count": 0, "forks_count": 0, - "only_allow_merge_if_build_succeeds": true, + "only_allow_merge_if_pipeline_succeeds": true, "open_issues_count": 4, "permissions": { "project_access": null, @@ -187,7 +187,7 @@ "avatar_url": null, "star_count": 0, "forks_count": 0, - "only_allow_merge_if_build_succeeds": true, + "only_allow_merge_if_pipeline_succeeds": true, "open_issues_count": 4, "permissions": { "project_access": null, @@ -235,7 +235,7 @@ "avatar_url": null, "star_count": 0, "forks_count": 0, - "only_allow_merge_if_build_succeeds": false, + "only_allow_merge_if_pipeline_succeeds": false, "open_issues_count": 5, "permissions": { "project_access": null, @@ -283,7 +283,7 @@ "avatar_url": null, "star_count": 0, "forks_count": 0, - "only_allow_merge_if_build_succeeds": false, + "only_allow_merge_if_pipeline_succeeds": false, "open_issues_count": 5, "permissions": { "project_access": { @@ -334,7 +334,7 @@ "avatar_url": null, "star_count": 0, "forks_count": 0, - "only_allow_merge_if_build_succeeds": false, + "only_allow_merge_if_pipeline_succeeds": false, "open_issues_count": 3, "permissions": { "project_access": null, @@ -382,7 +382,7 @@ "avatar_url": null, "star_count": 0, "forks_count": 0, - "only_allow_merge_if_build_succeeds": false, + "only_allow_merge_if_pipeline_succeeds": false, "open_issues_count": 5, "permissions": { "project_access": { @@ -433,7 +433,7 @@ "avatar_url": null, "star_count": 0, "forks_count": 0, - "only_allow_merge_if_build_succeeds": false, + "only_allow_merge_if_pipeline_succeeds": false, "open_issues_count": 5, "permissions": { "project_access": null, diff --git a/spec/javascripts/lib/utils/text_utility_spec.js.es6 b/spec/javascripts/lib/utils/text_utility_spec.js.es6 index 06b69b8ac17..4200e943121 100644 --- a/spec/javascripts/lib/utils/text_utility_spec.js.es6 +++ b/spec/javascripts/lib/utils/text_utility_spec.js.es6 @@ -46,5 +46,65 @@ require('~/lib/utils/text_utility'); expect(gl.text.highCountTrim(45)).toBe(45); }); }); + + describe('gl.text.insertText', () => { + let textArea; + + beforeAll(() => { + textArea = document.createElement('textarea'); + document.querySelector('body').appendChild(textArea); + }); + + afterAll(() => { + textArea.parentNode.removeChild(textArea); + }); + + describe('without selection', () => { + it('inserts the tag on an empty line', () => { + const initialValue = ''; + + textArea.value = initialValue; + textArea.selectionStart = 0; + textArea.selectionEnd = 0; + + gl.text.insertText(textArea, textArea.value, '*', null, '', false); + + expect(textArea.value).toEqual(`${initialValue}* `); + }); + + it('inserts the tag on a new line if the current one is not empty', () => { + const initialValue = 'some text'; + + textArea.value = initialValue; + textArea.setSelectionRange(initialValue.length, initialValue.length); + + gl.text.insertText(textArea, textArea.value, '*', null, '', false); + + expect(textArea.value).toEqual(`${initialValue}\n* `); + }); + + it('inserts the tag on the same line if the current line only contains spaces', () => { + const initialValue = ' '; + + textArea.value = initialValue; + textArea.setSelectionRange(initialValue.length, initialValue.length); + + gl.text.insertText(textArea, textArea.value, '*', null, '', false); + + expect(textArea.value).toEqual(`${initialValue}* `); + }); + + it('inserts the tag on the same line if the current line only contains tabs', () => { + const initialValue = '\t\t\t'; + + textArea.value = initialValue; + textArea.setSelectionRange(initialValue.length, initialValue.length); + + gl.text.insertText(textArea, textArea.value, '*', null, '', false); + + expect(textArea.value).toEqual(`${initialValue}* `); + }); + }); + }); }); })(); diff --git a/spec/javascripts/new_branch_spec.js b/spec/javascripts/new_branch_spec.js index f132537b943..90a429beeca 100644 --- a/spec/javascripts/new_branch_spec.js +++ b/spec/javascripts/new_branch_spec.js @@ -1,7 +1,6 @@ /* eslint-disable space-before-function-paren, one-var, no-var, one-var-declaration-per-line, no-return-assign, quotes, max-len */ /* global NewBranchForm */ -require('jquery-ui/ui/autocomplete'); require('~/new_branch_form'); (function() { diff --git a/spec/javascripts/user_callout_spec.js.es6 b/spec/javascripts/user_callout_spec.js.es6 index 6ee63f56a26..205e72af600 100644 --- a/spec/javascripts/user_callout_spec.js.es6 +++ b/spec/javascripts/user_callout_spec.js.es6 @@ -3,35 +3,55 @@ const UserCallout = require('~/user_callout'); const USER_CALLOUT_COOKIE = 'user_callout_dismissed'; const Cookie = window.Cookies; -describe('UserCallout', () => { +describe('UserCallout', function () { const fixtureName = 'static/user_callout.html.raw'; preloadFixtures(fixtureName); - beforeEach(function () { + beforeEach(() => { loadFixtures(fixtureName); + Cookie.remove(USER_CALLOUT_COOKIE); + this.userCallout = new UserCallout(); this.closeButton = $('.close-user-callout'); this.userCalloutBtn = $('.user-callout-btn'); this.userCalloutContainer = $('.user-callout'); - Cookie.set(USER_CALLOUT_COOKIE, 'false'); }); - afterEach(function () { - Cookie.set(USER_CALLOUT_COOKIE, 'false'); + it('does not show when cookie is set not defined', () => { + expect(Cookie.get(USER_CALLOUT_COOKIE)).toBeUndefined(); + expect(this.userCalloutContainer.is(':visible')).toBe(true); }); - it('shows when cookie is set to false', function () { + it('shows when cookie is set to false', () => { + Cookie.set(USER_CALLOUT_COOKIE, 'false'); + expect(Cookie.get(USER_CALLOUT_COOKIE)).toBeDefined(); expect(this.userCalloutContainer.is(':visible')).toBe(true); }); - it('hides when user clicks on the dismiss-icon', function () { + it('hides when user clicks on the dismiss-icon', () => { this.closeButton.click(); expect(Cookie.get(USER_CALLOUT_COOKIE)).toBe('true'); }); - it('hides when user clicks on the "check it out" button', function () { + it('hides when user clicks on the "check it out" button', () => { this.userCalloutBtn.click(); expect(Cookie.get(USER_CALLOUT_COOKIE)).toBe('true'); }); }); + +describe('UserCallout when cookie is present', function () { + const fixtureName = 'static/user_callout.html.raw'; + preloadFixtures(fixtureName); + + beforeEach(() => { + loadFixtures(fixtureName); + Cookie.set(USER_CALLOUT_COOKIE, 'true'); + this.userCallout = new UserCallout(); + this.userCalloutContainer = $('.user-callout'); + }); + + it('removes the DOM element', () => { + expect(this.userCalloutContainer.length).toBe(0); + }); +}); diff --git a/spec/lib/banzai/filter/user_reference_filter_spec.rb b/spec/lib/banzai/filter/user_reference_filter_spec.rb index d5d128c1907..9873774909e 100644 --- a/spec/lib/banzai/filter/user_reference_filter_spec.rb +++ b/spec/lib/banzai/filter/user_reference_filter_spec.rb @@ -123,6 +123,12 @@ describe Banzai::Filter::UserReferenceFilter, lib: true do expect(doc.css('a').first.attr('href')).to eq urls.group_url(group) end + + it 'has the full group name as a title' do + doc = reference_filter("Hey #{reference}") + + expect(doc.css('a').first.attr('title')).to eq group.full_name + end end it 'links with adjacent text' do diff --git a/spec/lib/gitlab/git/repository_spec.rb b/spec/lib/gitlab/git/repository_spec.rb index 1919542ca25..3f11f0a4516 100644 --- a/spec/lib/gitlab/git/repository_spec.rb +++ b/spec/lib/gitlab/git/repository_spec.rb @@ -47,7 +47,7 @@ describe Gitlab::Git::Repository, seed_helper: true do end end - describe :branch_names do + describe '#branch_names' do subject { repository.branch_names } it 'has SeedRepo::Repo::BRANCHES.size elements' do @@ -57,7 +57,7 @@ describe Gitlab::Git::Repository, seed_helper: true do it { is_expected.not_to include("branch-from-space") } end - describe :tag_names do + describe '#tag_names' do subject { repository.tag_names } it { is_expected.to be_kind_of Array } @@ -78,49 +78,63 @@ describe Gitlab::Git::Repository, seed_helper: true do it { expect(metadata['ArchivePath']).to end_with extenstion } end - describe :archive do + describe '#archive_prefix' do + let(:project_name) { 'project-name'} + + before do + expect(repository).to receive(:name).once.and_return(project_name) + end + + it 'returns parameterised string for a ref containing slashes' do + prefix = repository.archive_prefix('test/branch', 'SHA') + + expect(prefix).to eq("#{project_name}-test-branch-SHA") + end + end + + describe '#archive' do let(:metadata) { repository.archive_metadata('master', '/tmp') } it_should_behave_like 'archive check', '.tar.gz' end - describe :archive_zip do + describe '#archive_zip' do let(:metadata) { repository.archive_metadata('master', '/tmp', 'zip') } it_should_behave_like 'archive check', '.zip' end - describe :archive_bz2 do + describe '#archive_bz2' do let(:metadata) { repository.archive_metadata('master', '/tmp', 'tbz2') } it_should_behave_like 'archive check', '.tar.bz2' end - describe :archive_fallback do + describe '#archive_fallback' do let(:metadata) { repository.archive_metadata('master', '/tmp', 'madeup') } it_should_behave_like 'archive check', '.tar.gz' end - describe :size do + describe '#size' do subject { repository.size } it { is_expected.to be < 2 } end - describe :has_commits? do + describe '#has_commits?' do it { expect(repository.has_commits?).to be_truthy } end - describe :empty? do + describe '#empty?' do it { expect(repository.empty?).to be_falsey } end - describe :bare? do + describe '#bare?' do it { expect(repository.bare?).to be_truthy } end - describe :heads do + describe '#heads' do let(:heads) { repository.heads } subject { heads } @@ -147,7 +161,7 @@ describe Gitlab::Git::Repository, seed_helper: true do end end - describe :ref_names do + describe '#ref_names' do let(:ref_names) { repository.ref_names } subject { ref_names } @@ -164,7 +178,7 @@ describe Gitlab::Git::Repository, seed_helper: true do end end - describe :search_files do + describe '#search_files' do let(:results) { repository.search_files('rails', 'master') } subject { results } @@ -200,7 +214,7 @@ describe Gitlab::Git::Repository, seed_helper: true do end end - context :submodules do + context '#submodules' do let(:repository) { Gitlab::Git::Repository.new(TEST_REPO_PATH) } context 'where repo has submodules' do @@ -264,7 +278,7 @@ describe Gitlab::Git::Repository, seed_helper: true do end end - describe :commit_count do + describe '#commit_count' do it { expect(repository.commit_count("master")).to eq(25) } it { expect(repository.commit_count("feature")).to eq(9) } end diff --git a/spec/lib/gitlab/gitaly_client/notifications_spec.rb b/spec/lib/gitlab/gitaly_client/notifications_spec.rb new file mode 100644 index 00000000000..a6252c99aa1 --- /dev/null +++ b/spec/lib/gitlab/gitaly_client/notifications_spec.rb @@ -0,0 +1,20 @@ +require 'spec_helper' + +describe Gitlab::GitalyClient::Notifications do + let(:client) { Gitlab::GitalyClient::Notifications.new } + + before do + allow(Gitlab.config.gitaly).to receive(:socket_path).and_return('path/to/gitaly.socket') + end + + describe '#post_receive' do + let(:repo_path) { '/path/to/my_repo.git' } + + it 'sends a post_receive message' do + expect_any_instance_of(Gitaly::Notifications::Stub). + to receive(:post_receive).with(post_receive_request_with_repo_path(repo_path)) + + client.post_receive(repo_path) + end + end +end diff --git a/spec/lib/gitlab/import_export/all_models.yml b/spec/lib/gitlab/import_export/all_models.yml index 06617f3b007..eef283c2460 100644 --- a/spec/lib/gitlab/import_export/all_models.yml +++ b/spec/lib/gitlab/import_export/all_models.yml @@ -153,6 +153,7 @@ project: - gitlab_issue_tracker_service - external_wiki_service - kubernetes_service +- mock_ci_service - forked_project_link - forked_from_project - forked_project_links diff --git a/spec/lib/gitlab/import_export/project.json b/spec/lib/gitlab/import_export/project.json index 2e9f60432b4..c3d5c451a3c 100644 --- a/spec/lib/gitlab/import_export/project.json +++ b/spec/lib/gitlab/import_export/project.json @@ -2539,7 +2539,7 @@ "merge_params": { "force_remove_source_branch": null }, - "merge_when_build_succeeds": true, + "merge_when_pipeline_succeeds": true, "merge_user_id": null, "merge_commit_sha": null, "deleted_at": null, @@ -2976,7 +2976,7 @@ "merge_params": { "force_remove_source_branch": null }, - "merge_when_build_succeeds": false, + "merge_when_pipeline_succeeds": false, "merge_user_id": null, "merge_commit_sha": null, "deleted_at": null, @@ -3260,7 +3260,7 @@ "merge_params": { "force_remove_source_branch": null }, - "merge_when_build_succeeds": false, + "merge_when_pipeline_succeeds": false, "merge_user_id": null, "merge_commit_sha": null, "deleted_at": null, @@ -3544,7 +3544,7 @@ "merge_params": { "force_remove_source_branch": null }, - "merge_when_build_succeeds": false, + "merge_when_pipeline_succeeds": false, "merge_user_id": null, "merge_commit_sha": null, "deleted_at": null, @@ -4234,7 +4234,7 @@ "merge_params": { "force_remove_source_branch": null }, - "merge_when_build_succeeds": false, + "merge_when_pipeline_succeeds": false, "merge_user_id": null, "merge_commit_sha": null, "deleted_at": null, @@ -4782,7 +4782,7 @@ "merge_params": { "force_remove_source_branch": null }, - "merge_when_build_succeeds": false, + "merge_when_pipeline_succeeds": false, "merge_user_id": null, "merge_commit_sha": null, "deleted_at": null, @@ -5281,7 +5281,7 @@ "merge_params": { "force_remove_source_branch": null }, - "merge_when_build_succeeds": false, + "merge_when_pipeline_succeeds": false, "merge_user_id": null, "merge_commit_sha": null, "deleted_at": null, @@ -5541,7 +5541,7 @@ "merge_params": { "force_remove_source_branch": null }, - "merge_when_build_succeeds": false, + "merge_when_pipeline_succeeds": false, "merge_user_id": null, "merge_commit_sha": null, "deleted_at": null, @@ -6231,7 +6231,7 @@ "merge_params": { "force_remove_source_branch": null }, - "merge_when_build_succeeds": false, + "merge_when_pipeline_succeeds": false, "merge_user_id": null, "merge_commit_sha": null, "deleted_at": null, diff --git a/spec/lib/gitlab/import_export/safe_model_attributes.yml b/spec/lib/gitlab/import_export/safe_model_attributes.yml index c5ac702d831..6534902b52d 100644 --- a/spec/lib/gitlab/import_export/safe_model_attributes.yml +++ b/spec/lib/gitlab/import_export/safe_model_attributes.yml @@ -142,7 +142,7 @@ MergeRequest: - updated_by_id - merge_error - merge_params -- merge_when_build_succeeds +- merge_when_pipeline_succeeds - merge_user_id - merge_commit_sha - deleted_at diff --git a/spec/models/ci/build_spec.rb b/spec/models/ci/build_spec.rb index b963ca4e542..5743c555cbe 100644 --- a/spec/models/ci/build_spec.rb +++ b/spec/models/ci/build_spec.rb @@ -181,20 +181,6 @@ describe Ci::Build, :models do end end - describe '#create_from' do - before do - build.status = 'success' - build.save - end - let(:create_from_build) { Ci::Build.create_from build } - - it 'exists a pending task' do - expect(Ci::Build.pending.count(:all)).to eq 0 - create_from_build - expect(Ci::Build.pending.count(:all)).to be > 0 - end - end - describe '#depends_on_builds' do let!(:build) { create(:ci_build, pipeline: pipeline, name: 'build', stage_idx: 0, stage: 'build') } let!(:rspec_test) { create(:ci_build, pipeline: pipeline, name: 'rspec', stage_idx: 1, stage: 'test') } diff --git a/spec/models/merge_request_spec.rb b/spec/models/merge_request_spec.rb index 9331dc41a5e..e000d0d38b3 100644 --- a/spec/models/merge_request_spec.rb +++ b/spec/models/merge_request_spec.rb @@ -37,12 +37,12 @@ describe MergeRequest, models: true do end it "is invalid without merge user" do - subject.merge_when_build_succeeds = true + subject.merge_when_pipeline_succeeds = true expect(subject).not_to be_valid end it "is valid with merge user" do - subject.merge_when_build_succeeds = true + subject.merge_when_pipeline_succeeds = true subject.merge_user = build(:user) expect(subject).to be_valid @@ -55,7 +55,7 @@ describe MergeRequest, models: true do it { is_expected.to respond_to(:can_be_merged?) } it { is_expected.to respond_to(:cannot_be_merged?) } it { is_expected.to respond_to(:merge_params) } - it { is_expected.to respond_to(:merge_when_build_succeeds) } + it { is_expected.to respond_to(:merge_when_pipeline_succeeds) } end describe '.in_projects' do @@ -508,17 +508,17 @@ describe MergeRequest, models: true do end end - describe "#reset_merge_when_build_succeeds" do + describe "#reset_merge_when_pipeline_succeeds" do let(:merge_if_green) do - create :merge_request, merge_when_build_succeeds: true, merge_user: create(:user), + create :merge_request, merge_when_pipeline_succeeds: true, merge_user: create(:user), merge_params: { "should_remove_source_branch" => "1", "commit_message" => "msg" } end it "sets the item to false" do - merge_if_green.reset_merge_when_build_succeeds + merge_if_green.reset_merge_when_pipeline_succeeds merge_if_green.reload - expect(merge_if_green.merge_when_build_succeeds).to be_falsey + expect(merge_if_green.merge_when_pipeline_succeeds).to be_falsey expect(merge_if_green.merge_params["should_remove_source_branch"]).to be_nil expect(merge_if_green.merge_params["commit_message"]).to be_nil end @@ -812,7 +812,7 @@ describe MergeRequest, models: true do end describe '#check_if_can_be_merged' do - let(:project) { create(:empty_project, only_allow_merge_if_build_succeeds: true) } + let(:project) { create(:empty_project, only_allow_merge_if_pipeline_succeeds: true) } subject { create(:merge_request, source_project: project, merge_status: :unchecked) } @@ -927,7 +927,7 @@ describe MergeRequest, models: true do end describe '#mergeable_ci_state?' do - let(:project) { create(:empty_project, only_allow_merge_if_build_succeeds: true) } + let(:project) { create(:empty_project, only_allow_merge_if_pipeline_succeeds: true) } let(:pipeline) { create(:ci_empty_pipeline) } subject { build(:merge_request, target_project: project) } @@ -970,7 +970,7 @@ describe MergeRequest, models: true do end context 'when merges are not restricted to green builds' do - subject { build(:merge_request, target_project: build(:empty_project, only_allow_merge_if_build_succeeds: false)) } + subject { build(:merge_request, target_project: build(:empty_project, only_allow_merge_if_pipeline_succeeds: false)) } context 'and a failed pipeline is associated' do before do @@ -1575,7 +1575,7 @@ describe MergeRequest, models: true do status: status) end - let(:project) { create(:project, :public, :repository, only_allow_merge_if_build_succeeds: true) } + let(:project) { create(:project, :public, :repository, only_allow_merge_if_pipeline_succeeds: true) } let(:developer) { create(:user) } let(:user) { create(:user) } let(:merge_request) { create(:merge_request, source_project: project) } diff --git a/spec/requests/api/commit_statuses_spec.rb b/spec/requests/api/commit_statuses_spec.rb index 81a8856b8f1..d8b3cc041a5 100644 --- a/spec/requests/api/commit_statuses_spec.rb +++ b/spec/requests/api/commit_statuses_spec.rb @@ -151,26 +151,62 @@ describe API::CommitStatuses, api: true do end context 'with all optional parameters' do - before do - optional_params = { state: 'success', - context: 'coverage', - ref: 'develop', - description: 'test', - coverage: 80.0, - target_url: 'http://gitlab.com/status' } - - post api(post_url, developer), optional_params + context 'when creating a commit status' do + it 'creates commit status' do + post api(post_url, developer), { + state: 'success', + context: 'coverage', + ref: 'develop', + description: 'test', + coverage: 80.0, + target_url: 'http://gitlab.com/status' + } + + expect(response).to have_http_status(201) + expect(json_response['sha']).to eq(commit.id) + expect(json_response['status']).to eq('success') + expect(json_response['name']).to eq('coverage') + expect(json_response['ref']).to eq('develop') + expect(json_response['coverage']).to eq(80.0) + expect(json_response['description']).to eq('test') + expect(json_response['target_url']).to eq('http://gitlab.com/status') + end end - it 'creates commit status' do - expect(response).to have_http_status(201) - expect(json_response['sha']).to eq(commit.id) - expect(json_response['status']).to eq('success') - expect(json_response['name']).to eq('coverage') - expect(json_response['ref']).to eq('develop') - expect(json_response['coverage']).to eq(80.0) - expect(json_response['description']).to eq('test') - expect(json_response['target_url']).to eq('http://gitlab.com/status') + context 'when updatig a commit status' do + before do + post api(post_url, developer), { + state: 'running', + context: 'coverage', + ref: 'develop', + description: 'coverage test', + coverage: 0.0, + target_url: 'http://gitlab.com/status' + } + + post api(post_url, developer), { + state: 'success', + name: 'coverage', + ref: 'develop', + description: 'new description', + coverage: 90.0 + } + end + + it 'updates a commit status' do + expect(response).to have_http_status(201) + expect(json_response['sha']).to eq(commit.id) + expect(json_response['status']).to eq('success') + expect(json_response['name']).to eq('coverage') + expect(json_response['ref']).to eq('develop') + expect(json_response['coverage']).to eq(90.0) + expect(json_response['description']).to eq('new description') + expect(json_response['target_url']).to eq('http://gitlab.com/status') + end + + it 'does not create a new commit status' do + expect(CommitStatus.count).to eq 1 + end end end diff --git a/spec/requests/api/environments_spec.rb b/spec/requests/api/environments_spec.rb index d66eb63fd0a..f2fd1dfc8db 100644 --- a/spec/requests/api/environments_spec.rb +++ b/spec/requests/api/environments_spec.rb @@ -24,6 +24,7 @@ describe API::Environments, api: true do expect(json_response.first['name']).to eq(environment.name) expect(json_response.first['external_url']).to eq(environment.external_url) expect(json_response.first['project']['id']).to eq(project.id) + expect(json_response.first['project']['visibility']).to be_present end end @@ -141,4 +142,39 @@ describe API::Environments, api: true do end end end + + describe 'POST /projects/:id/environments/:environment_id/stop' do + context 'as a master' do + context 'with a stoppable environment' do + before do + environment.update(state: :available) + + post api("/projects/#{project.id}/environments/#{environment.id}/stop", user) + end + + it 'returns a 200' do + expect(response).to have_http_status(200) + end + + it 'actually stops the environment' do + expect(environment.reload).to be_stopped + end + end + + it 'returns a 404 for non existing id' do + post api("/projects/#{project.id}/environments/12345/stop", user) + + expect(response).to have_http_status(404) + expect(json_response['message']).to eq('404 Not found') + end + end + + context 'a non member' do + it 'rejects the request' do + post api("/projects/#{project.id}/environments/#{environment.id}/stop", non_member) + + expect(response).to have_http_status(404) + end + end + end end diff --git a/spec/requests/api/files_spec.rb b/spec/requests/api/files_spec.rb index 31b1aca6d73..91f8a35e045 100644 --- a/spec/requests/api/files_spec.rb +++ b/spec/requests/api/files_spec.rb @@ -147,6 +147,20 @@ describe API::Files, api: true do expect(last_commit.author_name).to eq(author_name) end end + + context 'when the repo is empty' do + let!(:project) { create(:project_empty_repo, namespace: user.namespace ) } + + it "creates a new file in project repo" do + post api("/projects/#{project.id}/repository/files", user), valid_params + + expect(response).to have_http_status(201) + expect(json_response['file_path']).to eq('newfile.rb') + last_commit = project.repository.commit.raw + expect(last_commit.author_email).to eq(user.email) + expect(last_commit.author_name).to eq(user.name) + end + end end describe "PUT /projects/:id/repository/files" do diff --git a/spec/requests/api/groups_spec.rb b/spec/requests/api/groups_spec.rb index b0ba3ea912d..2b8fd7e31a1 100644 --- a/spec/requests/api/groups_spec.rb +++ b/spec/requests/api/groups_spec.rb @@ -176,7 +176,7 @@ describe API::Groups, api: true do expect(json_response['name']).to eq(group1.name) expect(json_response['path']).to eq(group1.path) expect(json_response['description']).to eq(group1.description) - expect(json_response['visibility_level']).to eq(group1.visibility_level) + expect(json_response['visibility']).to eq(Gitlab::VisibilityLevel.string_level(group1.visibility_level)) expect(json_response['avatar_url']).to eq(group1.avatar_url) expect(json_response['web_url']).to eq(group1.web_url) expect(json_response['request_access_enabled']).to eq(group1.request_access_enabled) @@ -295,7 +295,7 @@ describe API::Groups, api: true do expect(json_response.length).to eq(2) project_names = json_response.map { |proj| proj['name'] } expect(project_names).to match_array([project1.name, project3.name]) - expect(json_response.first['visibility_level']).to be_present + expect(json_response.first['visibility']).to be_present end it "returns the group's projects with simple representation" do @@ -306,7 +306,7 @@ describe API::Groups, api: true do expect(json_response.length).to eq(2) project_names = json_response.map { |proj| proj['name'] } expect(project_names).to match_array([project1.name, project3.name]) - expect(json_response.first['visibility_level']).not_to be_present + expect(json_response.first['visibility']).not_to be_present end it 'filters the groups projects' do diff --git a/spec/requests/api/internal_spec.rb b/spec/requests/api/internal_spec.rb index ffeacb15f17..f18b8e98707 100644 --- a/spec/requests/api/internal_spec.rb +++ b/spec/requests/api/internal_spec.rb @@ -409,6 +409,34 @@ describe API::Internal, api: true do end end + describe 'POST /notify_post_receive' do + let(:valid_params) do + { repo_path: project.repository.path, secret_token: secret_token } + end + + before do + allow(Gitlab.config.gitaly).to receive(:socket_path).and_return('path/to/gitaly.socket') + end + + it "calls the Gitaly client if it's enabled" do + expect_any_instance_of(Gitlab::GitalyClient::Notifications). + to receive(:post_receive).with(project.repository.path) + + post api("/internal/notify_post_receive"), valid_params + + expect(response).to have_http_status(200) + end + + it "returns 500 if the gitaly call fails" do + expect_any_instance_of(Gitlab::GitalyClient::Notifications). + to receive(:post_receive).with(project.repository.path).and_raise(GRPC::Unavailable) + + post api("/internal/notify_post_receive"), valid_params + + expect(response).to have_http_status(500) + end + end + def project_with_repo_path(path) double().tap do |fake_project| allow(fake_project).to receive_message_chain('repository.path_to_repo' => path) diff --git a/spec/requests/api/issues_spec.rb b/spec/requests/api/issues_spec.rb index ddc2e51821e..710e4320fd1 100644 --- a/spec/requests/api/issues_spec.rb +++ b/spec/requests/api/issues_spec.rb @@ -212,6 +212,25 @@ describe API::Issues, api: true do expect(json_response.first['id']).to eq(confidential_issue.id) end + it 'returns an array of issues found by iids' do + get api('/issues', user), iids: [closed_issue.iid] + + expect(response).to have_http_status(200) + expect(response).to include_pagination_headers + expect(json_response).to be_an Array + expect(json_response.length).to eq(1) + expect(json_response.first['id']).to eq(closed_issue.id) + end + + it 'returns an empty array if iid does not exist' do + get api("/issues", user), iids: [99999] + + expect(response).to have_http_status(200) + expect(response).to include_pagination_headers + expect(json_response).to be_an Array + expect(json_response.length).to eq(0) + end + it 'sorts by created_at descending by default' do get api('/issues', user) @@ -377,6 +396,25 @@ describe API::Issues, api: true do expect(json_response.first['labels']).to eq([label_c.title, label_b.title, group_label.title]) end + it 'returns an array of issues found by iids' do + get api(base_url, user), iids: [group_issue.iid] + + expect(response).to have_http_status(200) + expect(response).to include_pagination_headers + expect(json_response).to be_an Array + expect(json_response.length).to eq(1) + expect(json_response.first['id']).to eq(group_issue.id) + end + + it 'returns an empty array if iid does not exist' do + get api(base_url, user), iids: [99999] + + expect(response).to have_http_status(200) + expect(response).to include_pagination_headers + expect(json_response).to be_an Array + expect(json_response.length).to eq(0) + end + it 'returns an empty array if no group issue matches labels' do get api("#{base_url}?labels=foo,bar", user) @@ -586,6 +624,25 @@ describe API::Issues, api: true do expect(json_response.first['labels']).to eq([label_c.title, label_b.title, label.title]) end + it 'returns an array of issues found by iids' do + get api("#{base_url}/issues", user), iids: [issue.iid] + + expect(response).to have_http_status(200) + expect(response).to include_pagination_headers + expect(json_response).to be_an Array + expect(json_response.length).to eq(1) + expect(json_response.first['id']).to eq(issue.id) + end + + it 'returns an empty array if iid does not exist' do + get api("#{base_url}/issues", user), iids: [99999] + + expect(response).to have_http_status(200) + expect(response).to include_pagination_headers + expect(json_response).to be_an Array + expect(json_response.length).to eq(0) + end + it 'returns an empty array if not all labels matches' do get api("#{base_url}/issues?labels=#{label.title},foo", user) diff --git a/spec/requests/api/merge_requests_spec.rb b/spec/requests/api/merge_requests_spec.rb index 5522154899c..b3f0876c822 100644 --- a/spec/requests/api/merge_requests_spec.rb +++ b/spec/requests/api/merge_requests_spec.rb @@ -170,7 +170,7 @@ describe API::MergeRequests, api: true do expect(json_response['source_project_id']).to eq(merge_request.source_project.id) expect(json_response['target_project_id']).to eq(merge_request.target_project.id) expect(json_response['work_in_progress']).to be_falsy - expect(json_response['merge_when_build_succeeds']).to be_falsy + expect(json_response['merge_when_pipeline_succeeds']).to be_falsy expect(json_response['merge_status']).to eq('can_be_merged') expect(json_response['should_close_merge_request']).to be_falsy expect(json_response['force_close_merge_request']).to be_falsy @@ -483,11 +483,11 @@ describe API::MergeRequests, api: true do allow_any_instance_of(MergeRequest).to receive(:head_pipeline).and_return(pipeline) allow(pipeline).to receive(:active?).and_return(true) - put api("/projects/#{project.id}/merge_requests/#{merge_request.id}/merge", user), merge_when_build_succeeds: true + put api("/projects/#{project.id}/merge_requests/#{merge_request.id}/merge", user), merge_when_pipeline_succeeds: true expect(response).to have_http_status(200) expect(json_response['title']).to eq('Test') - expect(json_response['merge_when_build_succeeds']).to eq(true) + expect(json_response['merge_when_pipeline_succeeds']).to eq(true) end end diff --git a/spec/requests/api/milestones_spec.rb b/spec/requests/api/milestones_spec.rb index 418bf5a507c..78c230117b8 100644 --- a/spec/requests/api/milestones_spec.rb +++ b/spec/requests/api/milestones_spec.rb @@ -4,8 +4,8 @@ describe API::Milestones, api: true do include ApiHelpers let(:user) { create(:user) } let!(:project) { create(:empty_project, namespace: user.namespace ) } - let!(:closed_milestone) { create(:closed_milestone, project: project) } - let!(:milestone) { create(:milestone, project: project) } + let!(:closed_milestone) { create(:closed_milestone, project: project, title: 'version1', description: 'closed milestone') } + let!(:milestone) { create(:milestone, project: project, title: 'version2', description: 'open milestone') } before { project.team << [user, :developer] } @@ -45,8 +45,37 @@ describe API::Milestones, api: true do expect(json_response.first['id']).to eq(closed_milestone.id) end - it 'returns a project milestone by iid' do - get api("/projects/#{project.id}/milestones?iid=#{closed_milestone.iid}", user) + it 'returns an array of milestones specified by iids' do + other_milestone = create(:milestone, project: project) + + get api("/projects/#{project.id}/milestones", user), iids: [closed_milestone.iid, other_milestone.iid] + + expect(response).to have_http_status(200) + expect(json_response).to be_an Array + expect(json_response.length).to eq(2) + expect(json_response.map{ |m| m['id'] }).to match_array([closed_milestone.id, other_milestone.id]) + end + + it 'does not return any milestone if none found' do + get api("/projects/#{project.id}/milestones", user), iids: [Milestone.maximum(:iid).succ] + + expect(response).to have_http_status(200) + expect(json_response).to be_an Array + expect(json_response.length).to eq(0) + end + end + + describe 'GET /projects/:id/milestones/:milestone_id' do + it 'returns a project milestone by id' do + get api("/projects/#{project.id}/milestones/#{milestone.id}", user) + + expect(response).to have_http_status(200) + expect(json_response['title']).to eq(milestone.title) + expect(json_response['iid']).to eq(milestone.iid) + end + + it 'returns a project milestone by iids array' do + get api("/projects/#{project.id}/milestones?iids=#{closed_milestone.iid}", user) expect(response.status).to eq 200 expect(response).to include_pagination_headers @@ -56,21 +85,22 @@ describe API::Milestones, api: true do expect(json_response.first['id']).to eq closed_milestone.id end - it 'returns a project milestone by iid array' do - get api("/projects/#{project.id}/milestones", user), iid: [milestone.iid, closed_milestone.iid] + it 'returns a project milestone by searching for title' do + get api("/projects/#{project.id}/milestones", user), search: 'version2' expect(response).to have_http_status(200) - expect(json_response.size).to eq(2) + expect(response).to include_pagination_headers + expect(json_response.size).to eq(1) expect(json_response.first['title']).to eq milestone.title expect(json_response.first['id']).to eq milestone.id end - it 'returns a project milestone by iid array' do - get api("/projects/#{project.id}/milestones", user), iid: [milestone.iid, closed_milestone.iid] + it 'returns a project milestones by searching for description' do + get api("/projects/#{project.id}/milestones", user), search: 'open' expect(response).to have_http_status(200) expect(response).to include_pagination_headers - expect(json_response.size).to eq(2) + expect(json_response.size).to eq(1) expect(json_response.first['title']).to eq milestone.title expect(json_response.first['id']).to eq milestone.id end diff --git a/spec/requests/api/notes_spec.rb b/spec/requests/api/notes_spec.rb index 9d3c821b692..347f8f6fa3b 100644 --- a/spec/requests/api/notes_spec.rb +++ b/spec/requests/api/notes_spec.rb @@ -225,11 +225,11 @@ describe API::Notes, api: true do context 'when the user is posting an award emoji on an issue created by someone else' do let(:issue2) { create(:issue, project: project) } - it 'returns an award emoji' do + it 'creates a new issue note' do post api("/projects/#{project.id}/issues/#{issue2.id}/notes", user), body: ':+1:' expect(response).to have_http_status(201) - expect(json_response['awardable_id']).to eq issue2.id + expect(json_response['body']).to eq(':+1:') end end diff --git a/spec/requests/api/project_snippets_spec.rb b/spec/requests/api/project_snippets_spec.rb index 2c4602faf2c..9e88c19b0bc 100644 --- a/spec/requests/api/project_snippets_spec.rb +++ b/spec/requests/api/project_snippets_spec.rb @@ -44,7 +44,7 @@ describe API::ProjectSnippets, api: true do title: 'Test Title', file_name: 'test.rb', code: 'puts "hello world"', - visibility_level: Snippet::PUBLIC + visibility: 'public' } end @@ -56,7 +56,7 @@ describe API::ProjectSnippets, api: true do expect(snippet.content).to eq(params[:code]) expect(snippet.title).to eq(params[:title]) expect(snippet.file_name).to eq(params[:file_name]) - expect(snippet.visibility_level).to eq(params[:visibility_level]) + expect(snippet.visibility_level).to eq(Snippet::PUBLIC) end it 'returns 400 for missing parameters' do @@ -80,14 +80,14 @@ describe API::ProjectSnippets, api: true do context 'when the snippet is private' do it 'creates the snippet' do - expect { create_snippet(project, visibility_level: Snippet::PRIVATE) }. + expect { create_snippet(project, visibility: 'private') }. to change { Snippet.count }.by(1) end end context 'when the snippet is public' do - it 'rejects the shippet' do - expect { create_snippet(project, visibility_level: Snippet::PUBLIC) }. + it 'rejects the snippet' do + expect { create_snippet(project, visibility: 'public') }. not_to change { Snippet.count } expect(response).to have_http_status(400) @@ -95,7 +95,7 @@ describe API::ProjectSnippets, api: true do end it 'creates a spam log' do - expect { create_snippet(project, visibility_level: Snippet::PUBLIC) }. + expect { create_snippet(project, visibility: 'public') }. to change { SpamLog.count }.by(1) end end @@ -165,7 +165,7 @@ describe API::ProjectSnippets, api: true do let(:visibility_level) { Snippet::PRIVATE } it 'rejects the snippet' do - expect { update_snippet(title: 'Foo', visibility_level: Snippet::PUBLIC) }. + expect { update_snippet(title: 'Foo', visibility: 'public') }. not_to change { snippet.reload.title } expect(response).to have_http_status(400) @@ -173,7 +173,7 @@ describe API::ProjectSnippets, api: true do end it 'creates a spam log' do - expect { update_snippet(title: 'Foo', visibility_level: Snippet::PUBLIC) }. + expect { update_snippet(title: 'Foo', visibility: 'public') }. to change { SpamLog.count }.by(1) end end diff --git a/spec/requests/api/projects_spec.rb b/spec/requests/api/projects_spec.rb index 3a00d974633..2e8b557e9e2 100644 --- a/spec/requests/api/projects_spec.rb +++ b/spec/requests/api/projects_spec.rb @@ -320,7 +320,7 @@ describe API::Projects, api: true do issues_enabled: false, merge_requests_enabled: false, wiki_enabled: false, - only_allow_merge_if_build_succeeds: false, + only_allow_merge_if_pipeline_succeeds: false, request_access_enabled: true, only_allow_merge_if_all_discussions_are_resolved: false }) @@ -340,36 +340,39 @@ describe API::Projects, api: true do end it 'sets a project as public' do - project = attributes_for(:project, :public) + project = attributes_for(:project, visibility: 'public') + post api('/projects', user), project - expect(json_response['public']).to be_truthy - expect(json_response['visibility_level']).to eq(Gitlab::VisibilityLevel::PUBLIC) + + expect(json_response['visibility']).to eq('public') end it 'sets a project as internal' do - project = attributes_for(:project, :internal) + project = attributes_for(:project, visibility: 'internal') + post api('/projects', user), project - expect(json_response['public']).to be_falsey - expect(json_response['visibility_level']).to eq(Gitlab::VisibilityLevel::INTERNAL) + + expect(json_response['visibility']).to eq('internal') end it 'sets a project as private' do - project = attributes_for(:project, :private) + project = attributes_for(:project, visibility: 'private') + post api('/projects', user), project - expect(json_response['public']).to be_falsey - expect(json_response['visibility_level']).to eq(Gitlab::VisibilityLevel::PRIVATE) + + expect(json_response['visibility']).to eq('private') end it 'sets a project as allowing merge even if build fails' do - project = attributes_for(:project, { only_allow_merge_if_build_succeeds: false }) + project = attributes_for(:project, { only_allow_merge_if_pipeline_succeeds: false }) post api('/projects', user), project - expect(json_response['only_allow_merge_if_build_succeeds']).to be_falsey + expect(json_response['only_allow_merge_if_pipeline_succeeds']).to be_falsey end - it 'sets a project as allowing merge only if build succeeds' do - project = attributes_for(:project, { only_allow_merge_if_build_succeeds: true }) + it 'sets a project as allowing merge only if merge_when_pipeline_succeeds' do + project = attributes_for(:project, { only_allow_merge_if_pipeline_succeeds: true }) post api('/projects', user), project - expect(json_response['only_allow_merge_if_build_succeeds']).to be_truthy + expect(json_response['only_allow_merge_if_pipeline_succeeds']).to be_truthy end it 'sets a project as allowing merge even if discussions are unresolved' do @@ -397,7 +400,7 @@ describe API::Projects, api: true do end context 'when a visibility level is restricted' do - let(:project_param) { attributes_for(:project, :public) } + let(:project_param) { attributes_for(:project, visibility: 'public') } before do stub_application_setting(restricted_visibility_levels: [Gitlab::VisibilityLevel::PUBLIC]) @@ -415,10 +418,7 @@ describe API::Projects, api: true do it 'allows an admin to override restricted visibility settings' do post api('/projects', admin), project_param - expect(json_response['public']).to be_truthy - expect(json_response['visibility_level']).to( - eq(Gitlab::VisibilityLevel::PUBLIC) - ) + expect(json_response['visibility']).to eq('public') end end end @@ -459,40 +459,41 @@ describe API::Projects, api: true do end it 'sets a project as public' do - project = attributes_for(:project, :public) + project = attributes_for(:project, visibility: 'public') + post api("/projects/user/#{user.id}", admin), project expect(response).to have_http_status(201) - expect(json_response['public']).to be_truthy - expect(json_response['visibility_level']).to eq(Gitlab::VisibilityLevel::PUBLIC) + expect(json_response['visibility']).to eq('public') end it 'sets a project as internal' do - project = attributes_for(:project, :internal) + project = attributes_for(:project, visibility: 'internal') + post api("/projects/user/#{user.id}", admin), project expect(response).to have_http_status(201) - expect(json_response['public']).to be_falsey - expect(json_response['visibility_level']).to eq(Gitlab::VisibilityLevel::INTERNAL) + expect(json_response['visibility']).to eq('internal') end it 'sets a project as private' do - project = attributes_for(:project, :private) + project = attributes_for(:project, visibility: 'private') + post api("/projects/user/#{user.id}", admin), project - expect(json_response['public']).to be_falsey - expect(json_response['visibility_level']).to eq(Gitlab::VisibilityLevel::PRIVATE) + + expect(json_response['visibility']).to eq('private') end it 'sets a project as allowing merge even if build fails' do - project = attributes_for(:project, { only_allow_merge_if_build_succeeds: false }) + project = attributes_for(:project, { only_allow_merge_if_pipeline_succeeds: false }) post api("/projects/user/#{user.id}", admin), project - expect(json_response['only_allow_merge_if_build_succeeds']).to be_falsey + expect(json_response['only_allow_merge_if_pipeline_succeeds']).to be_falsey end - it 'sets a project as allowing merge only if build succeeds' do - project = attributes_for(:project, { only_allow_merge_if_build_succeeds: true }) + it 'sets a project as allowing merge only if merge_when_pipeline_succeeds' do + project = attributes_for(:project, { only_allow_merge_if_pipeline_succeeds: true }) post api("/projects/user/#{user.id}", admin), project - expect(json_response['only_allow_merge_if_build_succeeds']).to be_truthy + expect(json_response['only_allow_merge_if_pipeline_succeeds']).to be_truthy end it 'sets a project as allowing merge even if discussions are unresolved' do @@ -556,9 +557,8 @@ describe API::Projects, api: true do expect(json_response['description']).to eq(project.description) expect(json_response['default_branch']).to eq(project.default_branch) expect(json_response['tag_list']).to be_an Array - expect(json_response['public']).to be_falsey expect(json_response['archived']).to be_falsey - expect(json_response['visibility_level']).to be_present + expect(json_response['visibility']).to be_present expect(json_response['ssh_url_to_repo']).to be_present expect(json_response['http_url_to_repo']).to be_present expect(json_response['web_url']).to be_present @@ -586,7 +586,7 @@ describe API::Projects, api: true do expect(json_response['shared_with_groups'][0]['group_id']).to eq(group.id) expect(json_response['shared_with_groups'][0]['group_name']).to eq(group.name) expect(json_response['shared_with_groups'][0]['group_access_level']).to eq(link.group_access) - expect(json_response['only_allow_merge_if_build_succeeds']).to eq(project.only_allow_merge_if_build_succeeds) + expect(json_response['only_allow_merge_if_pipeline_succeeds']).to eq(project.only_allow_merge_if_pipeline_succeeds) expect(json_response['only_allow_merge_if_all_discussions_are_resolved']).to eq(project.only_allow_merge_if_all_discussions_are_resolved) end @@ -812,8 +812,7 @@ describe API::Projects, api: true do describe 'POST /projects/:id/snippets' do it 'creates a new project snippet' do post api("/projects/#{project.id}/snippets", user), - title: 'api test', file_name: 'sample.rb', code: 'test', - visibility_level: Gitlab::VisibilityLevel::PRIVATE + title: 'api test', file_name: 'sample.rb', code: 'test', visibility: 'private' expect(response).to have_http_status(201) expect(json_response['title']).to eq('api test') end @@ -1065,7 +1064,7 @@ describe API::Projects, api: true do end it 'updates visibility_level' do - project_param = { visibility_level: Gitlab::VisibilityLevel::PUBLIC } + project_param = { visibility: 'public' } put api("/projects/#{project3.id}", user), project_param expect(response).to have_http_status(200) project_param.each_pair do |k, v| @@ -1075,13 +1074,13 @@ describe API::Projects, api: true do it 'updates visibility_level from public to private' do project3.update_attributes({ visibility_level: Gitlab::VisibilityLevel::PUBLIC }) - project_param = { visibility_level: Gitlab::VisibilityLevel::PRIVATE } + project_param = { visibility: 'private' } put api("/projects/#{project3.id}", user), project_param expect(response).to have_http_status(200) project_param.each_pair do |k, v| expect(json_response[k.to_s]).to eq(v) end - expect(json_response['visibility_level']).to eq(Gitlab::VisibilityLevel::PRIVATE) + expect(json_response['visibility']).to eq('private') end it 'does not update name to existing name' do @@ -1148,7 +1147,7 @@ describe API::Projects, api: true do end it 'does not update visibility_level' do - project_param = { visibility_level: Gitlab::VisibilityLevel::PUBLIC } + project_param = { visibility: 'public' } put api("/projects/#{project3.id}", user4), project_param expect(response).to have_http_status(403) end diff --git a/spec/requests/api/settings_spec.rb b/spec/requests/api/settings_spec.rb index 411905edb49..11b4b718e2c 100644 --- a/spec/requests/api/settings_spec.rb +++ b/spec/requests/api/settings_spec.rb @@ -18,6 +18,9 @@ describe API::Settings, 'Settings', api: true do expect(json_response['koding_url']).to be_nil expect(json_response['plantuml_enabled']).to be_falsey expect(json_response['plantuml_url']).to be_nil + expect(json_response['default_project_visibility']).to be_a String + expect(json_response['default_snippet_visibility']).to be_a String + expect(json_response['default_group_visibility']).to be_a String end end @@ -37,6 +40,8 @@ describe API::Settings, 'Settings', api: true do koding_url: 'http://koding.example.com', plantuml_enabled: true, plantuml_url: 'http://plantuml.example.com', + default_snippet_visibility: 'internal', + restricted_visibility_levels: ['public'], default_artifacts_expire_in: '2 days' expect(response).to have_http_status(200) expect(json_response['default_projects_limit']).to eq(3) @@ -47,6 +52,8 @@ describe API::Settings, 'Settings', api: true do expect(json_response['koding_url']).to eq('http://koding.example.com') expect(json_response['plantuml_enabled']).to be_truthy expect(json_response['plantuml_url']).to eq('http://plantuml.example.com') + expect(json_response['default_snippet_visibility']).to eq('internal') + expect(json_response['restricted_visibility_levels']).to eq(['public']) expect(json_response['default_artifacts_expire_in']).to eq('2 days') end end diff --git a/spec/requests/api/snippets_spec.rb b/spec/requests/api/snippets_spec.rb index 5219f6eed42..5d75b47b3cd 100644 --- a/spec/requests/api/snippets_spec.rb +++ b/spec/requests/api/snippets_spec.rb @@ -87,7 +87,7 @@ describe API::Snippets, api: true do title: 'Test Title', file_name: 'test.rb', content: 'puts "hello world"', - visibility_level: Snippet::PUBLIC + visibility: 'public' } end @@ -120,14 +120,14 @@ describe API::Snippets, api: true do context 'when the snippet is private' do it 'creates the snippet' do - expect { create_snippet(visibility_level: Snippet::PRIVATE) }. + expect { create_snippet(visibility: 'private') }. to change { Snippet.count }.by(1) end end context 'when the snippet is public' do it 'rejects the shippet' do - expect { create_snippet(visibility_level: Snippet::PUBLIC) }. + expect { create_snippet(visibility: 'public') }. not_to change { Snippet.count } expect(response).to have_http_status(400) @@ -135,7 +135,7 @@ describe API::Snippets, api: true do end it 'creates a spam log' do - expect { create_snippet(visibility_level: Snippet::PUBLIC) }. + expect { create_snippet(visibility: 'public') }. to change { SpamLog.count }.by(1) end end @@ -218,12 +218,12 @@ describe API::Snippets, api: true do let(:visibility_level) { Snippet::PRIVATE } it 'rejects the snippet' do - expect { update_snippet(title: 'Foo', visibility_level: Snippet::PUBLIC) }. + expect { update_snippet(title: 'Foo', visibility: 'public') }. not_to change { snippet.reload.title } end it 'creates a spam log' do - expect { update_snippet(title: 'Foo', visibility_level: Snippet::PUBLIC) }. + expect { update_snippet(title: 'Foo', visibility: 'public') }. to change { SpamLog.count }.by(1) end end diff --git a/spec/requests/api/todos_spec.rb b/spec/requests/api/todos_spec.rb index f35e963a14b..1e401935662 100644 --- a/spec/requests/api/todos_spec.rb +++ b/spec/requests/api/todos_spec.rb @@ -3,7 +3,7 @@ require 'spec_helper' describe API::Todos, api: true do include ApiHelpers - let(:project_1) { create(:empty_project) } + let(:project_1) { create(:empty_project, :test_repo) } let(:project_2) { create(:empty_project) } let(:author_1) { create(:user) } let(:author_2) { create(:user) } @@ -11,7 +11,7 @@ describe API::Todos, api: true do let(:merge_request) { create(:merge_request, source_project: project_1) } let!(:pending_1) { create(:todo, :mentioned, project: project_1, author: author_1, user: john_doe) } let!(:pending_2) { create(:todo, project: project_2, author: author_2, user: john_doe) } - let!(:pending_3) { create(:todo, project: project_1, author: author_2, user: john_doe) } + let!(:pending_3) { create(:on_commit_todo, project: project_1, author: author_2, user: john_doe) } let!(:done) { create(:todo, :done, project: project_1, author: author_1, user: john_doe) } before do diff --git a/spec/requests/api/v3/environments_spec.rb b/spec/requests/api/v3/environments_spec.rb index 1ac666ab240..216192c9d34 100644 --- a/spec/requests/api/v3/environments_spec.rb +++ b/spec/requests/api/v3/environments_spec.rb @@ -12,6 +12,132 @@ describe API::V3::Environments, api: true do project.team << [user, :master] end + shared_examples 'a paginated resources' do + before do + # Fires the request + request + end + + it 'has pagination headers' do + expect(response.headers).to include('X-Total') + expect(response.headers).to include('X-Total-Pages') + expect(response.headers).to include('X-Per-Page') + expect(response.headers).to include('X-Page') + expect(response.headers).to include('X-Next-Page') + expect(response.headers).to include('X-Prev-Page') + expect(response.headers).to include('Link') + end + end + + describe 'GET /projects/:id/environments' do + context 'as member of the project' do + it_behaves_like 'a paginated resources' do + let(:request) { get v3_api("/projects/#{project.id}/environments", user) } + end + + it 'returns project environments' do + get v3_api("/projects/#{project.id}/environments", user) + + expect(response).to have_http_status(200) + expect(json_response).to be_an Array + expect(json_response.size).to eq(1) + expect(json_response.first['name']).to eq(environment.name) + expect(json_response.first['external_url']).to eq(environment.external_url) + expect(json_response.first['project']['id']).to eq(project.id) + expect(json_response.first['project']['visibility_level']).to be_present + end + end + + context 'as non member' do + it 'returns a 404 status code' do + get v3_api("/projects/#{project.id}/environments", non_member) + + expect(response).to have_http_status(404) + end + end + end + + describe 'POST /projects/:id/environments' do + context 'as a member' do + it 'creates a environment with valid params' do + post v3_api("/projects/#{project.id}/environments", user), name: "mepmep" + + expect(response).to have_http_status(201) + expect(json_response['name']).to eq('mepmep') + expect(json_response['slug']).to eq('mepmep') + expect(json_response['external']).to be nil + end + + it 'requires name to be passed' do + post v3_api("/projects/#{project.id}/environments", user), external_url: 'test.gitlab.com' + + expect(response).to have_http_status(400) + end + + it 'returns a 400 if environment already exists' do + post v3_api("/projects/#{project.id}/environments", user), name: environment.name + + expect(response).to have_http_status(400) + end + + it 'returns a 400 if slug is specified' do + post v3_api("/projects/#{project.id}/environments", user), name: "foo", slug: "foo" + + expect(response).to have_http_status(400) + expect(json_response["error"]).to eq("slug is automatically generated and cannot be changed") + end + end + + context 'a non member' do + it 'rejects the request' do + post v3_api("/projects/#{project.id}/environments", non_member), name: 'gitlab.com' + + expect(response).to have_http_status(404) + end + + it 'returns a 400 when the required params are missing' do + post v3_api("/projects/12345/environments", non_member), external_url: 'http://env.git.com' + end + end + end + + describe 'PUT /projects/:id/environments/:environment_id' do + it 'returns a 200 if name and external_url are changed' do + url = 'https://mepmep.whatever.ninja' + put v3_api("/projects/#{project.id}/environments/#{environment.id}", user), + name: 'Mepmep', external_url: url + + expect(response).to have_http_status(200) + expect(json_response['name']).to eq('Mepmep') + expect(json_response['external_url']).to eq(url) + end + + it "won't allow slug to be changed" do + slug = environment.slug + api_url = v3_api("/projects/#{project.id}/environments/#{environment.id}", user) + put api_url, slug: slug + "-foo" + + expect(response).to have_http_status(400) + expect(json_response["error"]).to eq("slug is automatically generated and cannot be changed") + end + + it "won't update the external_url if only the name is passed" do + url = environment.external_url + put v3_api("/projects/#{project.id}/environments/#{environment.id}", user), + name: 'Mepmep' + + expect(response).to have_http_status(200) + expect(json_response['name']).to eq('Mepmep') + expect(json_response['external_url']).to eq(url) + end + + it 'returns a 404 if the environment does not exist' do + put v3_api("/projects/#{project.id}/environments/12345", user) + + expect(response).to have_http_status(404) + end + end + describe 'DELETE /projects/:id/environments/:environment_id' do context 'as a master' do it 'returns a 200 for an existing environment' do diff --git a/spec/requests/api/v3/files_spec.rb b/spec/requests/api/v3/files_spec.rb index 93637053626..3b61139a2cd 100644 --- a/spec/requests/api/v3/files_spec.rb +++ b/spec/requests/api/v3/files_spec.rb @@ -148,6 +148,20 @@ describe API::V3::Files, api: true do expect(last_commit.author_name).to eq(author_name) end end + + context 'when the repo is empty' do + let!(:project) { create(:project_empty_repo, namespace: user.namespace ) } + + it "creates a new file in project repo" do + post v3_api("/projects/#{project.id}/repository/files", user), valid_params + + expect(response).to have_http_status(201) + expect(json_response['file_path']).to eq('newfile.rb') + last_commit = project.repository.commit.raw + expect(last_commit.author_email).to eq(user.email) + expect(last_commit.author_name).to eq(user.name) + end + end end describe "PUT /projects/:id/repository/files" do diff --git a/spec/requests/api/v3/groups_spec.rb b/spec/requests/api/v3/groups_spec.rb index 8b29ad03737..a71b7d4b008 100644 --- a/spec/requests/api/v3/groups_spec.rb +++ b/spec/requests/api/v3/groups_spec.rb @@ -4,14 +4,144 @@ describe API::V3::Groups, api: true do include ApiHelpers include UploadHelpers + let(:user1) { create(:user, can_create_group: false) } let(:user2) { create(:user) } + let(:user3) { create(:user) } + let(:admin) { create(:admin) } + let!(:group1) { create(:group, avatar: File.open(uploaded_image_temp_path)) } let!(:group2) { create(:group, :private) } + let!(:project1) { create(:empty_project, namespace: group1) } let!(:project2) { create(:empty_project, namespace: group2) } + let!(:project3) { create(:empty_project, namespace: group1, path: 'test', visibility_level: Gitlab::VisibilityLevel::PRIVATE) } before do + group1.add_owner(user1) group2.add_owner(user2) end + describe "GET /groups" do + context "when unauthenticated" do + it "returns authentication error" do + get v3_api("/groups") + + expect(response).to have_http_status(401) + end + end + + context "when authenticated as user" do + it "normal user: returns an array of groups of user1" do + get v3_api("/groups", user1) + + expect(response).to have_http_status(200) + expect(json_response).to be_an Array + expect(json_response.length).to eq(1) + expect(json_response) + .to satisfy_one { |group| group['name'] == group1.name } + end + + it "does not include statistics" do + get v3_api("/groups", user1), statistics: true + + expect(response).to have_http_status(200) + expect(json_response).to be_an Array + expect(json_response.first).not_to include 'statistics' + end + end + + context "when authenticated as admin" do + it "admin: returns an array of all groups" do + get v3_api("/groups", admin) + + expect(response).to have_http_status(200) + expect(json_response).to be_an Array + expect(json_response.length).to eq(2) + end + + it "does not include statistics by default" do + get v3_api("/groups", admin) + + expect(response).to have_http_status(200) + expect(json_response).to be_an Array + expect(json_response.first).not_to include('statistics') + end + + it "includes statistics if requested" do + attributes = { + storage_size: 702, + repository_size: 123, + lfs_objects_size: 234, + build_artifacts_size: 345, + }.stringify_keys + + project1.statistics.update!(attributes) + + get v3_api("/groups", admin), statistics: true + + expect(response).to have_http_status(200) + expect(json_response).to be_an Array + expect(json_response) + .to satisfy_one { |group| group['statistics'] == attributes } + end + end + + context "when using skip_groups in request" do + it "returns all groups excluding skipped groups" do + get v3_api("/groups", admin), skip_groups: [group2.id] + + expect(response).to have_http_status(200) + expect(json_response).to be_an Array + expect(json_response.length).to eq(1) + end + end + + context "when using all_available in request" do + let(:response_groups) { json_response.map { |group| group['name'] } } + + it "returns all groups you have access to" do + public_group = create :group, :public + + get v3_api("/groups", user1), all_available: true + + expect(response).to have_http_status(200) + expect(json_response).to be_an Array + expect(response_groups).to contain_exactly(public_group.name, group1.name) + end + end + + context "when using sorting" do + let(:group3) { create(:group, name: "a#{group1.name}", path: "z#{group1.path}") } + let(:response_groups) { json_response.map { |group| group['name'] } } + + before do + group3.add_owner(user1) + end + + it "sorts by name ascending by default" do + get v3_api("/groups", user1) + + expect(response).to have_http_status(200) + expect(json_response).to be_an Array + expect(response_groups).to eq([group3.name, group1.name]) + end + + it "sorts in descending order when passed" do + get v3_api("/groups", user1), sort: "desc" + + expect(response).to have_http_status(200) + expect(json_response).to be_an Array + expect(response_groups).to eq([group1.name, group3.name]) + end + + it "sorts by the order_by param" do + get v3_api("/groups", user1), order_by: "path" + + expect(response).to have_http_status(200) + expect(json_response).to be_an Array + expect(response_groups).to eq([group1.name, group3.name]) + end + end + end + describe 'GET /groups/owned' do context 'when unauthenticated' do it 'returns authentication error' do @@ -32,4 +162,404 @@ describe API::V3::Groups, api: true do end end end + + describe "GET /groups/:id" do + context "when authenticated as user" do + it "returns one of user1's groups" do + project = create(:empty_project, namespace: group2, path: 'Foo') + create(:project_group_link, project: project, group: group1) + + get v3_api("/groups/#{group1.id}", user1) + + expect(response).to have_http_status(200) + expect(json_response['id']).to eq(group1.id) + expect(json_response['name']).to eq(group1.name) + expect(json_response['path']).to eq(group1.path) + expect(json_response['description']).to eq(group1.description) + expect(json_response['visibility_level']).to eq(group1.visibility_level) + expect(json_response['avatar_url']).to eq(group1.avatar_url) + expect(json_response['web_url']).to eq(group1.web_url) + expect(json_response['request_access_enabled']).to eq(group1.request_access_enabled) + expect(json_response['full_name']).to eq(group1.full_name) + expect(json_response['full_path']).to eq(group1.full_path) + expect(json_response['parent_id']).to eq(group1.parent_id) + expect(json_response['projects']).to be_an Array + expect(json_response['projects'].length).to eq(2) + expect(json_response['shared_projects']).to be_an Array + expect(json_response['shared_projects'].length).to eq(1) + expect(json_response['shared_projects'][0]['id']).to eq(project.id) + end + + it "does not return a non existing group" do + get v3_api("/groups/1328", user1) + + expect(response).to have_http_status(404) + end + + it "does not return a group not attached to user1" do + get v3_api("/groups/#{group2.id}", user1) + + expect(response).to have_http_status(404) + end + end + + context "when authenticated as admin" do + it "returns any existing group" do + get v3_api("/groups/#{group2.id}", admin) + + expect(response).to have_http_status(200) + expect(json_response['name']).to eq(group2.name) + end + + it "does not return a non existing group" do + get v3_api("/groups/1328", admin) + + expect(response).to have_http_status(404) + end + end + + context 'when using group path in URL' do + it 'returns any existing group' do + get v3_api("/groups/#{group1.path}", admin) + + expect(response).to have_http_status(200) + expect(json_response['name']).to eq(group1.name) + end + + it 'does not return a non existing group' do + get v3_api('/groups/unknown', admin) + + expect(response).to have_http_status(404) + end + + it 'does not return a group not attached to user1' do + get v3_api("/groups/#{group2.path}", user1) + + expect(response).to have_http_status(404) + end + end + end + + describe 'PUT /groups/:id' do + let(:new_group_name) { 'New Group'} + + context 'when authenticated as the group owner' do + it 'updates the group' do + put v3_api("/groups/#{group1.id}", user1), name: new_group_name, request_access_enabled: true + + expect(response).to have_http_status(200) + expect(json_response['name']).to eq(new_group_name) + expect(json_response['request_access_enabled']).to eq(true) + end + + it 'returns 404 for a non existing group' do + put v3_api('/groups/1328', user1), name: new_group_name + + expect(response).to have_http_status(404) + end + end + + context 'when authenticated as the admin' do + it 'updates the group' do + put v3_api("/groups/#{group1.id}", admin), name: new_group_name + + expect(response).to have_http_status(200) + expect(json_response['name']).to eq(new_group_name) + end + end + + context 'when authenticated as an user that can see the group' do + it 'does not updates the group' do + put v3_api("/groups/#{group1.id}", user2), name: new_group_name + + expect(response).to have_http_status(403) + end + end + + context 'when authenticated as an user that cannot see the group' do + it 'returns 404 when trying to update the group' do + put v3_api("/groups/#{group2.id}", user1), name: new_group_name + + expect(response).to have_http_status(404) + end + end + end + + describe "GET /groups/:id/projects" do + context "when authenticated as user" do + it "returns the group's projects" do + get v3_api("/groups/#{group1.id}/projects", user1) + + expect(response).to have_http_status(200) + expect(json_response.length).to eq(2) + project_names = json_response.map { |proj| proj['name'] } + expect(project_names).to match_array([project1.name, project3.name]) + expect(json_response.first['visibility_level']).to be_present + end + + it "returns the group's projects with simple representation" do + get v3_api("/groups/#{group1.id}/projects", user1), simple: true + + expect(response).to have_http_status(200) + expect(json_response.length).to eq(2) + project_names = json_response.map { |proj| proj['name'] } + expect(project_names).to match_array([project1.name, project3.name]) + expect(json_response.first['visibility_level']).not_to be_present + end + + it 'filters the groups projects' do + public_project = create(:empty_project, :public, path: 'test1', group: group1) + + get v3_api("/groups/#{group1.id}/projects", user1), visibility: 'public' + + expect(response).to have_http_status(200) + expect(json_response).to be_an(Array) + expect(json_response.length).to eq(1) + expect(json_response.first['name']).to eq(public_project.name) + end + + it "does not return a non existing group" do + get v3_api("/groups/1328/projects", user1) + + expect(response).to have_http_status(404) + end + + it "does not return a group not attached to user1" do + get v3_api("/groups/#{group2.id}/projects", user1) + + expect(response).to have_http_status(404) + end + + it "only returns projects to which user has access" do + project3.team << [user3, :developer] + + get v3_api("/groups/#{group1.id}/projects", user3) + + expect(response).to have_http_status(200) + 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 v3_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 v3_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 + it "returns any existing group" do + get v3_api("/groups/#{group2.id}/projects", admin) + + 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 "does not return a non existing group" do + get v3_api("/groups/1328/projects", admin) + + expect(response).to have_http_status(404) + end + end + + context 'when using group path in URL' do + it 'returns any existing group' do + get v3_api("/groups/#{group1.path}/projects", admin) + + expect(response).to have_http_status(200) + project_names = json_response.map { |proj| proj['name'] } + expect(project_names).to match_array([project1.name, project3.name]) + end + + it 'does not return a non existing group' do + get v3_api('/groups/unknown/projects', admin) + + expect(response).to have_http_status(404) + end + + it 'does not return a group not attached to user1' do + get v3_api("/groups/#{group2.path}/projects", user1) + + expect(response).to have_http_status(404) + end + end + end + + describe "POST /groups" do + context "when authenticated as user without group permissions" do + it "does not create group" do + post v3_api("/groups", user1), attributes_for(:group) + + expect(response).to have_http_status(403) + end + end + + context "when authenticated as user with group permissions" do + it "creates group" do + group = attributes_for(:group, { request_access_enabled: false }) + + post v3_api("/groups", user3), group + + expect(response).to have_http_status(201) + + expect(json_response["name"]).to eq(group[:name]) + expect(json_response["path"]).to eq(group[:path]) + expect(json_response["request_access_enabled"]).to eq(group[:request_access_enabled]) + end + + it "creates a nested group" do + parent = create(:group) + parent.add_owner(user3) + group = attributes_for(:group, { parent_id: parent.id }) + + post v3_api("/groups", user3), group + + expect(response).to have_http_status(201) + + expect(json_response["full_path"]).to eq("#{parent.path}/#{group[:path]}") + expect(json_response["parent_id"]).to eq(parent.id) + end + + it "does not create group, duplicate" do + post v3_api("/groups", user3), { name: 'Duplicate Test', path: group2.path } + + expect(response).to have_http_status(400) + expect(response.message).to eq("Bad Request") + end + + it "returns 400 bad request error if name not given" do + post v3_api("/groups", user3), { path: group2.path } + + expect(response).to have_http_status(400) + end + + it "returns 400 bad request error if path not given" do + post v3_api("/groups", user3), { name: 'test' } + + expect(response).to have_http_status(400) + end + end + end + + describe "DELETE /groups/:id" do + context "when authenticated as user" do + it "removes group" do + delete v3_api("/groups/#{group1.id}", user1) + + expect(response).to have_http_status(200) + end + + it "does not remove a group if not an owner" do + user4 = create(:user) + group1.add_master(user4) + + delete v3_api("/groups/#{group1.id}", user3) + + expect(response).to have_http_status(403) + end + + it "does not remove a non existing group" do + delete v3_api("/groups/1328", user1) + + expect(response).to have_http_status(404) + end + + it "does not remove a group not attached to user1" do + delete v3_api("/groups/#{group2.id}", user1) + + expect(response).to have_http_status(404) + end + end + + context "when authenticated as admin" do + it "removes any existing group" do + delete v3_api("/groups/#{group2.id}", admin) + + expect(response).to have_http_status(200) + end + + it "does not remove a non existing group" do + delete v3_api("/groups/1328", admin) + + expect(response).to have_http_status(404) + end + end + end + + describe "POST /groups/:id/projects/:project_id" do + let(:project) { create(:empty_project) } + let(:project_path) { "#{project.namespace.path}%2F#{project.path}" } + + before(:each) do + allow_any_instance_of(Projects::TransferService). + to receive(:execute).and_return(true) + end + + context "when authenticated as user" do + it "does not transfer project to group" do + post v3_api("/groups/#{group1.id}/projects/#{project.id}", user2) + + expect(response).to have_http_status(403) + end + end + + context "when authenticated as admin" do + it "transfers project to group" do + post v3_api("/groups/#{group1.id}/projects/#{project.id}", admin) + + expect(response).to have_http_status(201) + end + + context 'when using project path in URL' do + context 'with a valid project path' do + it "transfers project to group" do + post v3_api("/groups/#{group1.id}/projects/#{project_path}", admin) + + expect(response).to have_http_status(201) + end + end + + context 'with a non-existent project path' do + it "does not transfer project to group" do + post v3_api("/groups/#{group1.id}/projects/nogroup%2Fnoproject", admin) + + expect(response).to have_http_status(404) + end + end + end + + context 'when using a group path in URL' do + context 'with a valid group path' do + it "transfers project to group" do + post v3_api("/groups/#{group1.path}/projects/#{project_path}", admin) + + expect(response).to have_http_status(201) + end + end + + context 'with a non-existent group path' do + it "does not transfer project to group" do + post v3_api("/groups/noexist/projects/#{project_path}", admin) + + expect(response).to have_http_status(404) + end + end + end + end + end end diff --git a/spec/requests/api/v3/milestones_spec.rb b/spec/requests/api/v3/milestones_spec.rb new file mode 100644 index 00000000000..77705d8c839 --- /dev/null +++ b/spec/requests/api/v3/milestones_spec.rb @@ -0,0 +1,232 @@ +require 'spec_helper' + +describe API::V3::Milestones, api: true do + include ApiHelpers + let(:user) { create(:user) } + let!(:project) { create(:empty_project, namespace: user.namespace ) } + let!(:closed_milestone) { create(:closed_milestone, project: project) } + let!(:milestone) { create(:milestone, project: project) } + + before { project.team << [user, :developer] } + + describe 'GET /projects/:id/milestones' do + it 'returns project milestones' do + get v3_api("/projects/#{project.id}/milestones", user) + + expect(response).to have_http_status(200) + expect(json_response).to be_an Array + expect(json_response.first['title']).to eq(milestone.title) + end + + it 'returns a 401 error if user not authenticated' do + get v3_api("/projects/#{project.id}/milestones") + + expect(response).to have_http_status(401) + end + + it 'returns an array of active milestones' do + get v3_api("/projects/#{project.id}/milestones?state=active", user) + + expect(response).to have_http_status(200) + expect(json_response).to be_an Array + expect(json_response.length).to eq(1) + expect(json_response.first['id']).to eq(milestone.id) + end + + it 'returns an array of closed milestones' do + get v3_api("/projects/#{project.id}/milestones?state=closed", user) + + expect(response).to have_http_status(200) + expect(json_response).to be_an Array + expect(json_response.length).to eq(1) + expect(json_response.first['id']).to eq(closed_milestone.id) + end + end + + describe 'GET /projects/:id/milestones/:milestone_id' do + it 'returns a project milestone by id' do + get v3_api("/projects/#{project.id}/milestones/#{milestone.id}", user) + + expect(response).to have_http_status(200) + expect(json_response['title']).to eq(milestone.title) + expect(json_response['iid']).to eq(milestone.iid) + end + + it 'returns a project milestone by iid' do + get v3_api("/projects/#{project.id}/milestones?iid=#{closed_milestone.iid}", user) + + expect(response.status).to eq 200 + expect(json_response.size).to eq(1) + expect(json_response.first['title']).to eq closed_milestone.title + expect(json_response.first['id']).to eq closed_milestone.id + end + + it 'returns a project milestone by iid array' do + get v3_api("/projects/#{project.id}/milestones", user), iid: [milestone.iid, closed_milestone.iid] + + expect(response).to have_http_status(200) + expect(json_response.size).to eq(2) + expect(json_response.first['title']).to eq milestone.title + expect(json_response.first['id']).to eq milestone.id + end + + it 'returns 401 error if user not authenticated' do + get v3_api("/projects/#{project.id}/milestones/#{milestone.id}") + + expect(response).to have_http_status(401) + end + + it 'returns a 404 error if milestone id not found' do + get v3_api("/projects/#{project.id}/milestones/1234", user) + + expect(response).to have_http_status(404) + end + end + + describe 'POST /projects/:id/milestones' do + it 'creates a new project milestone' do + post v3_api("/projects/#{project.id}/milestones", user), title: 'new milestone' + + expect(response).to have_http_status(201) + expect(json_response['title']).to eq('new milestone') + expect(json_response['description']).to be_nil + end + + it 'creates a new project milestone with description and dates' do + post v3_api("/projects/#{project.id}/milestones", user), + title: 'new milestone', description: 'release', due_date: '2013-03-02', start_date: '2013-02-02' + + expect(response).to have_http_status(201) + expect(json_response['description']).to eq('release') + expect(json_response['due_date']).to eq('2013-03-02') + expect(json_response['start_date']).to eq('2013-02-02') + end + + it 'returns a 400 error if title is missing' do + post v3_api("/projects/#{project.id}/milestones", user) + + expect(response).to have_http_status(400) + end + + it 'returns a 400 error if params are invalid (duplicate title)' do + post v3_api("/projects/#{project.id}/milestones", user), + title: milestone.title, description: 'release', due_date: '2013-03-02' + + expect(response).to have_http_status(400) + end + + it 'creates a new project with reserved html characters' do + post v3_api("/projects/#{project.id}/milestones", user), title: 'foo & bar 1.1 -> 2.2' + + expect(response).to have_http_status(201) + expect(json_response['title']).to eq('foo & bar 1.1 -> 2.2') + expect(json_response['description']).to be_nil + end + end + + describe 'PUT /projects/:id/milestones/:milestone_id' do + it 'updates a project milestone' do + put v3_api("/projects/#{project.id}/milestones/#{milestone.id}", user), + title: 'updated title' + + expect(response).to have_http_status(200) + expect(json_response['title']).to eq('updated title') + end + + it 'removes a due date if nil is passed' do + milestone.update!(due_date: "2016-08-05") + + put v3_api("/projects/#{project.id}/milestones/#{milestone.id}", user), due_date: nil + + expect(response).to have_http_status(200) + expect(json_response['due_date']).to be_nil + end + + it 'returns a 404 error if milestone id not found' do + put v3_api("/projects/#{project.id}/milestones/1234", user), + title: 'updated title' + + expect(response).to have_http_status(404) + end + end + + describe 'PUT /projects/:id/milestones/:milestone_id to close milestone' do + it 'updates a project milestone' do + put v3_api("/projects/#{project.id}/milestones/#{milestone.id}", user), + state_event: 'close' + expect(response).to have_http_status(200) + + expect(json_response['state']).to eq('closed') + end + end + + describe 'PUT /projects/:id/milestones/:milestone_id to test observer on close' do + it 'creates an activity event when an milestone is closed' do + expect(Event).to receive(:create) + + put v3_api("/projects/#{project.id}/milestones/#{milestone.id}", user), + state_event: 'close' + end + end + + describe 'GET /projects/:id/milestones/:milestone_id/issues' do + before do + milestone.issues << create(:issue, project: project) + end + it 'returns project issues for a particular milestone' do + get v3_api("/projects/#{project.id}/milestones/#{milestone.id}/issues", user) + + expect(response).to have_http_status(200) + expect(json_response).to be_an Array + expect(json_response.first['milestone']['title']).to eq(milestone.title) + end + + it 'returns a 401 error if user not authenticated' do + get v3_api("/projects/#{project.id}/milestones/#{milestone.id}/issues") + + expect(response).to have_http_status(401) + end + + describe 'confidential issues' do + let(:public_project) { create(:empty_project, :public) } + let(:milestone) { create(:milestone, project: public_project) } + let(:issue) { create(:issue, project: public_project) } + let(:confidential_issue) { create(:issue, confidential: true, project: public_project) } + + before do + public_project.team << [user, :developer] + milestone.issues << issue << confidential_issue + end + + it 'returns confidential issues to team members' do + get v3_api("/projects/#{public_project.id}/milestones/#{milestone.id}/issues", user) + + expect(response).to have_http_status(200) + expect(json_response).to be_an Array + expect(json_response.size).to eq(2) + expect(json_response.map { |issue| issue['id'] }).to include(issue.id, confidential_issue.id) + end + + it 'does not return confidential issues to team members with guest role' do + member = create(:user) + project.team << [member, :guest] + + get v3_api("/projects/#{public_project.id}/milestones/#{milestone.id}/issues", member) + + expect(response).to have_http_status(200) + expect(json_response).to be_an Array + expect(json_response.size).to eq(1) + expect(json_response.map { |issue| issue['id'] }).to include(issue.id) + end + + it 'does not return confidential issues to regular users' do + get v3_api("/projects/#{public_project.id}/milestones/#{milestone.id}/issues", create(:user)) + + expect(response).to have_http_status(200) + expect(json_response).to be_an Array + expect(json_response.size).to eq(1) + expect(json_response.map { |issue| issue['id'] }).to include(issue.id) + end + end + end +end diff --git a/spec/requests/api/v3/notes_spec.rb b/spec/requests/api/v3/notes_spec.rb index b8f0260c6a2..ddef2d5eb04 100644 --- a/spec/requests/api/v3/notes_spec.rb +++ b/spec/requests/api/v3/notes_spec.rb @@ -228,11 +228,11 @@ describe API::V3::Notes, api: true do context 'when the user is posting an award emoji on an issue created by someone else' do let(:issue2) { create(:issue, project: project) } - it 'returns an award emoji' do + it 'creates a new issue note' do post v3_api("/projects/#{project.id}/issues/#{issue2.id}/notes", user), body: ':+1:' expect(response).to have_http_status(201) - expect(json_response['awardable_id']).to eq issue2.id + expect(json_response['body']).to eq(':+1:') end end diff --git a/spec/requests/api/v3/projects_spec.rb b/spec/requests/api/v3/projects_spec.rb index 34940b2f1c7..d8bb562587d 100644 --- a/spec/requests/api/v3/projects_spec.rb +++ b/spec/requests/api/v3/projects_spec.rb @@ -427,7 +427,7 @@ describe API::V3::Projects, api: true do expect(json_response['only_allow_merge_if_build_succeeds']).to be_falsey end - it 'sets a project as allowing merge only if build succeeds' do + it 'sets a project as allowing merge only if merge_when_pipeline_succeeds' do project = attributes_for(:project, { only_allow_merge_if_build_succeeds: true }) post v3_api('/projects', user), project expect(json_response['only_allow_merge_if_build_succeeds']).to be_truthy @@ -572,7 +572,7 @@ describe API::V3::Projects, api: true do expect(json_response['only_allow_merge_if_build_succeeds']).to be_falsey end - it 'sets a project as allowing merge only if build succeeds' do + it 'sets a project as allowing merge only if merge_when_pipeline_succeeds' do project = attributes_for(:project, { only_allow_merge_if_build_succeeds: true }) post v3_api("/projects/user/#{user.id}", admin), project expect(json_response['only_allow_merge_if_build_succeeds']).to be_truthy @@ -669,7 +669,7 @@ describe API::V3::Projects, api: true do expect(json_response['shared_with_groups'][0]['group_id']).to eq(group.id) expect(json_response['shared_with_groups'][0]['group_name']).to eq(group.name) expect(json_response['shared_with_groups'][0]['group_access_level']).to eq(link.group_access) - expect(json_response['only_allow_merge_if_build_succeeds']).to eq(project.only_allow_merge_if_build_succeeds) + expect(json_response['only_allow_merge_if_build_succeeds']).to eq(project.only_allow_merge_if_pipeline_succeeds) expect(json_response['only_allow_merge_if_all_discussions_are_resolved']).to eq(project.only_allow_merge_if_all_discussions_are_resolved) end diff --git a/spec/requests/api/v3/settings_spec.rb b/spec/requests/api/v3/settings_spec.rb new file mode 100644 index 00000000000..a9fa5adac17 --- /dev/null +++ b/spec/requests/api/v3/settings_spec.rb @@ -0,0 +1,65 @@ +require 'spec_helper' + +describe API::V3::Settings, 'Settings', api: true do + include ApiHelpers + + let(:user) { create(:user) } + let(:admin) { create(:admin) } + + describe "GET /application/settings" do + it "returns application settings" do + get v3_api("/application/settings", admin) + expect(response).to have_http_status(200) + expect(json_response).to be_an Hash + expect(json_response['default_projects_limit']).to eq(42) + expect(json_response['signin_enabled']).to be_truthy + expect(json_response['repository_storage']).to eq('default') + expect(json_response['koding_enabled']).to be_falsey + expect(json_response['koding_url']).to be_nil + expect(json_response['plantuml_enabled']).to be_falsey + expect(json_response['plantuml_url']).to be_nil + end + end + + describe "PUT /application/settings" do + context "custom repository storage type set in the config" do + before do + storages = { 'custom' => 'tmp/tests/custom_repositories' } + allow(Gitlab.config.repositories).to receive(:storages).and_return(storages) + end + + it "updates application settings" do + put v3_api("/application/settings", admin), + default_projects_limit: 3, signin_enabled: false, repository_storage: 'custom', koding_enabled: true, koding_url: 'http://koding.example.com', + plantuml_enabled: true, plantuml_url: 'http://plantuml.example.com' + expect(response).to have_http_status(200) + expect(json_response['default_projects_limit']).to eq(3) + expect(json_response['signin_enabled']).to be_falsey + expect(json_response['repository_storage']).to eq('custom') + expect(json_response['repository_storages']).to eq(['custom']) + expect(json_response['koding_enabled']).to be_truthy + expect(json_response['koding_url']).to eq('http://koding.example.com') + expect(json_response['plantuml_enabled']).to be_truthy + expect(json_response['plantuml_url']).to eq('http://plantuml.example.com') + end + end + + context "missing koding_url value when koding_enabled is true" do + it "returns a blank parameter error message" do + put v3_api("/application/settings", admin), koding_enabled: true + + expect(response).to have_http_status(400) + expect(json_response['error']).to eq('koding_url is missing') + end + end + + context "missing plantuml_url value when plantuml_enabled is true" do + it "returns a blank parameter error message" do + put v3_api("/application/settings", admin), plantuml_enabled: true + + expect(response).to have_http_status(400) + expect(json_response['error']).to eq('plantuml_url is missing') + end + end + end +end diff --git a/spec/requests/api/v3/snippets_spec.rb b/spec/requests/api/v3/snippets_spec.rb new file mode 100644 index 00000000000..05653bd0d51 --- /dev/null +++ b/spec/requests/api/v3/snippets_spec.rb @@ -0,0 +1,187 @@ +require 'rails_helper' + +describe API::V3::Snippets, api: true do + include ApiHelpers + let!(:user) { create(:user) } + + describe 'GET /snippets/' do + it 'returns snippets available' do + public_snippet = create(:personal_snippet, :public, author: user) + private_snippet = create(:personal_snippet, :private, author: user) + internal_snippet = create(:personal_snippet, :internal, author: user) + + get v3_api("/snippets/", user) + + expect(response).to have_http_status(200) + expect(json_response.map { |snippet| snippet['id']} ).to contain_exactly( + public_snippet.id, + internal_snippet.id, + private_snippet.id) + expect(json_response.last).to have_key('web_url') + expect(json_response.last).to have_key('raw_url') + end + + it 'hides private snippets from regular user' do + create(:personal_snippet, :private) + + get v3_api("/snippets/", user) + expect(response).to have_http_status(200) + expect(json_response.size).to eq(0) + end + end + + describe 'GET /snippets/public' do + let!(:other_user) { create(:user) } + let!(:public_snippet) { create(:personal_snippet, :public, author: user) } + let!(:private_snippet) { create(:personal_snippet, :private, author: user) } + let!(:internal_snippet) { create(:personal_snippet, :internal, author: user) } + let!(:public_snippet_other) { create(:personal_snippet, :public, author: other_user) } + let!(:private_snippet_other) { create(:personal_snippet, :private, author: other_user) } + let!(:internal_snippet_other) { create(:personal_snippet, :internal, author: other_user) } + + it 'returns all snippets with public visibility from all users' do + get v3_api("/snippets/public", user) + + expect(response).to have_http_status(200) + expect(json_response.map { |snippet| snippet['id']} ).to contain_exactly( + public_snippet.id, + public_snippet_other.id) + expect(json_response.map{ |snippet| snippet['web_url']} ).to include( + "http://localhost/snippets/#{public_snippet.id}", + "http://localhost/snippets/#{public_snippet_other.id}") + expect(json_response.map{ |snippet| snippet['raw_url']} ).to include( + "http://localhost/snippets/#{public_snippet.id}/raw", + "http://localhost/snippets/#{public_snippet_other.id}/raw") + end + end + + describe 'GET /snippets/:id/raw' do + let(:snippet) { create(:personal_snippet, author: user) } + + it 'returns raw text' do + get v3_api("/snippets/#{snippet.id}/raw", user) + + expect(response).to have_http_status(200) + expect(response.content_type).to eq 'text/plain' + expect(response.body).to eq(snippet.content) + end + + it 'returns 404 for invalid snippet id' do + delete v3_api("/snippets/1234", user) + + expect(response).to have_http_status(404) + expect(json_response['message']).to eq('404 Snippet Not Found') + end + end + + describe 'POST /snippets/' do + let(:params) do + { + title: 'Test Title', + file_name: 'test.rb', + content: 'puts "hello world"', + visibility_level: Snippet::PUBLIC + } + end + + it 'creates a new snippet' do + expect do + post v3_api("/snippets/", user), params + end.to change { PersonalSnippet.count }.by(1) + + expect(response).to have_http_status(201) + expect(json_response['title']).to eq(params[:title]) + expect(json_response['file_name']).to eq(params[:file_name]) + end + + it 'returns 400 for missing parameters' do + params.delete(:title) + + post v3_api("/snippets/", user), params + + expect(response).to have_http_status(400) + end + + context 'when the snippet is spam' do + def create_snippet(snippet_params = {}) + post v3_api('/snippets', user), params.merge(snippet_params) + end + + before do + allow_any_instance_of(AkismetService).to receive(:is_spam?).and_return(true) + end + + context 'when the snippet is private' do + it 'creates the snippet' do + expect { create_snippet(visibility_level: Snippet::PRIVATE) }. + to change { Snippet.count }.by(1) + end + end + + context 'when the snippet is public' do + it 'rejects the shippet' do + expect { create_snippet(visibility_level: Snippet::PUBLIC) }. + not_to change { Snippet.count } + expect(response).to have_http_status(400) + end + + it 'creates a spam log' do + expect { create_snippet(visibility_level: Snippet::PUBLIC) }. + to change { SpamLog.count }.by(1) + end + end + end + end + + describe 'PUT /snippets/:id' do + let(:other_user) { create(:user) } + let(:public_snippet) { create(:personal_snippet, :public, author: user) } + it 'updates snippet' do + new_content = 'New content' + + put v3_api("/snippets/#{public_snippet.id}", user), content: new_content + + expect(response).to have_http_status(200) + public_snippet.reload + expect(public_snippet.content).to eq(new_content) + end + + it 'returns 404 for invalid snippet id' do + put v3_api("/snippets/1234", user), title: 'foo' + + expect(response).to have_http_status(404) + expect(json_response['message']).to eq('404 Snippet Not Found') + end + + it "returns 404 for another user's snippet" do + put v3_api("/snippets/#{public_snippet.id}", other_user), title: 'fubar' + + expect(response).to have_http_status(404) + expect(json_response['message']).to eq('404 Snippet Not Found') + end + + it 'returns 400 for missing parameters' do + put v3_api("/snippets/1234", user) + + expect(response).to have_http_status(400) + end + end + + describe 'DELETE /snippets/:id' do + let!(:public_snippet) { create(:personal_snippet, :public, author: user) } + it 'deletes snippet' do + expect do + delete v3_api("/snippets/#{public_snippet.id}", user) + + expect(response).to have_http_status(204) + end.to change { PersonalSnippet.count }.by(-1) + end + + it 'returns 404 for invalid snippet id' do + delete v3_api("/snippets/1234", user) + + expect(response).to have_http_status(404) + expect(json_response['message']).to eq('404 Snippet Not Found') + end + end +end diff --git a/spec/rubocop/cop/custom_error_class_spec.rb b/spec/rubocop/cop/custom_error_class_spec.rb new file mode 100644 index 00000000000..381d7871a40 --- /dev/null +++ b/spec/rubocop/cop/custom_error_class_spec.rb @@ -0,0 +1,111 @@ +require 'spec_helper' + +require 'rubocop' +require 'rubocop/rspec/support' + +require_relative '../../../rubocop/cop/custom_error_class' + +describe RuboCop::Cop::CustomErrorClass do + include CopHelper + + subject(:cop) { described_class.new } + + context 'when a class has a body' do + it 'does nothing' do + inspect_source(cop, 'class CustomError < StandardError; def foo; end; end') + + expect(cop.offenses).to be_empty + end + end + + context 'when a class has no explicit superclass' do + it 'does nothing' do + inspect_source(cop, 'class CustomError; end') + + expect(cop.offenses).to be_empty + end + end + + context 'when a class has a superclass that does not end in Error' do + it 'does nothing' do + inspect_source(cop, 'class CustomError < BasicObject; end') + + expect(cop.offenses).to be_empty + end + end + + context 'when a class is empty and inherits from a class ending in Error' do + context 'when the class is on a single line' do + let(:source) do + <<-SOURCE + module Foo + class CustomError < Bar::Baz::BaseError; end + end + SOURCE + end + + let(:expected) do + <<-EXPECTED + module Foo + CustomError = Class.new(Bar::Baz::BaseError) + end + EXPECTED + end + + it 'registers an offense' do + expected_highlights = source.split("\n")[1].strip + + inspect_source(cop, source) + + aggregate_failures do + expect(cop.offenses.size).to eq(1) + expect(cop.offenses.map(&:line)).to eq([2]) + expect(cop.highlights).to contain_exactly(expected_highlights) + end + end + + it 'autocorrects to the right version' do + autocorrected = autocorrect_source(cop, source, 'foo/custom_error.rb') + + expect(autocorrected).to eq(expected) + end + end + + context 'when the class is on multiple lines' do + let(:source) do + <<-SOURCE + module Foo + class CustomError < Bar::Baz::BaseError + end + end + SOURCE + end + + let(:expected) do + <<-EXPECTED + module Foo + CustomError = Class.new(Bar::Baz::BaseError) + end + EXPECTED + end + + it 'registers an offense' do + expected_highlights = source.split("\n")[1..2].join("\n").strip + + inspect_source(cop, source) + + aggregate_failures do + expect(cop.offenses.size).to eq(1) + expect(cop.offenses.map(&:line)).to eq([2]) + expect(cop.highlights).to contain_exactly(expected_highlights) + end + end + + it 'autocorrects to the right version' do + autocorrected = autocorrect_source(cop, source, 'foo/custom_error.rb') + + expect(autocorrected).to eq(expected) + end + end + end +end diff --git a/spec/services/ci/image_for_build_service_spec.rb b/spec/services/ci/image_for_build_service_spec.rb deleted file mode 100644 index b3e0a7b9b58..00000000000 --- a/spec/services/ci/image_for_build_service_spec.rb +++ /dev/null @@ -1,50 +0,0 @@ -require 'spec_helper' - -module Ci - describe ImageForBuildService, services: true do - let(:service) { ImageForBuildService.new } - let(:project) { FactoryGirl.create(:empty_project) } - let(:commit_sha) { '01234567890123456789' } - let(:pipeline) { project.ensure_pipeline('master', commit_sha) } - let(:build) { FactoryGirl.create(:ci_build, pipeline: pipeline) } - - describe '#execute' do - before { build } - - context 'branch name' do - before { allow(project).to receive(:commit).and_return(OpenStruct.new(sha: commit_sha)) } - before { build.run! } - let(:image) { service.execute(project, ref: 'master') } - - it { expect(image).to be_kind_of(OpenStruct) } - it { expect(image.path.to_s).to include('public/ci/build-running.svg') } - it { expect(image.name).to eq('build-running.svg') } - end - - context 'unknown branch name' do - let(:image) { service.execute(project, ref: 'feature') } - - it { expect(image).to be_kind_of(OpenStruct) } - it { expect(image.path.to_s).to include('public/ci/build-unknown.svg') } - it { expect(image.name).to eq('build-unknown.svg') } - end - - context 'commit sha' do - before { build.run! } - let(:image) { service.execute(project, sha: build.sha) } - - it { expect(image).to be_kind_of(OpenStruct) } - it { expect(image.path.to_s).to include('public/ci/build-running.svg') } - it { expect(image.name).to eq('build-running.svg') } - end - - context 'unknown commit sha' do - let(:image) { service.execute(project, sha: '0000000') } - - it { expect(image).to be_kind_of(OpenStruct) } - it { expect(image.path.to_s).to include('public/ci/build-unknown.svg') } - it { expect(image.name).to eq('build-unknown.svg') } - end - end - end -end diff --git a/spec/services/ci/process_pipeline_service_spec.rb b/spec/services/ci/process_pipeline_service_spec.rb index b818dfdd50c..de68fb64726 100644 --- a/spec/services/ci/process_pipeline_service_spec.rb +++ b/spec/services/ci/process_pipeline_service_spec.rb @@ -341,7 +341,7 @@ describe Ci::ProcessPipelineService, :services do expect(builds.pending.count).to eq(1) expect(all_builds.count).to eq(4) - # When pending build succeeds in stage test, we enqueue deploy stage. + # When pending merge_when_pipeline_succeeds in stage test, we enqueue deploy stage. # succeed_pending process_pipeline diff --git a/spec/services/merge_requests/merge_when_pipeline_succeeds_service_spec.rb b/spec/services/merge_requests/merge_when_pipeline_succeeds_service_spec.rb index 0ff6e8fda16..c2f205c389d 100644 --- a/spec/services/merge_requests/merge_when_pipeline_succeeds_service_spec.rb +++ b/spec/services/merge_requests/merge_when_pipeline_succeeds_service_spec.rb @@ -5,7 +5,7 @@ describe MergeRequests::MergeWhenPipelineSucceedsService do let(:project) { create(:project) } let(:mr_merge_if_green_enabled) do - create(:merge_request, merge_when_build_succeeds: true, merge_user: user, + create(:merge_request, merge_when_pipeline_succeeds: true, merge_user: user, source_branch: "master", target_branch: 'feature', source_project: project, target_project: project, state: "opened") end @@ -36,7 +36,7 @@ describe MergeRequests::MergeWhenPipelineSucceedsService do it 'sets the params, merge_user, and flag' do expect(merge_request).to be_valid - expect(merge_request.merge_when_build_succeeds).to be_truthy + expect(merge_request.merge_when_pipeline_succeeds).to be_truthy expect(merge_request.merge_params).to eq commit_message: 'Awesome message' expect(merge_request.merge_user).to be user end @@ -62,7 +62,7 @@ describe MergeRequests::MergeWhenPipelineSucceedsService do end it 'updates the merge params' do - expect(SystemNoteService).not_to receive(:merge_when_build_succeeds) + expect(SystemNoteService).not_to receive(:merge_when_pipeline_succeeds) service.execute(mr_merge_if_green_enabled) expect(mr_merge_if_green_enabled.merge_params).to have_key(:new_key) @@ -82,7 +82,7 @@ describe MergeRequests::MergeWhenPipelineSucceedsService do sha: merge_request_head, status: 'success') end - it "merges all merge requests with merge when build succeeds enabled" do + it "merges all merge requests with merge when the pipeline succeeds enabled" do expect(MergeWorker).to receive(:perform_async) service.trigger(triggering_pipeline) end @@ -114,7 +114,7 @@ describe MergeRequests::MergeWhenPipelineSucceedsService do context 'when the merge request is not mergeable' do let(:mr_conflict) do - create(:merge_request, merge_when_build_succeeds: true, merge_user: user, + create(:merge_request, merge_when_pipeline_succeeds: true, merge_user: user, source_branch: 'master', target_branch: 'feature-conflict', source_project: project, target_project: project) end @@ -143,8 +143,8 @@ describe MergeRequests::MergeWhenPipelineSucceedsService do service.cancel(mr_merge_if_green_enabled) end - it "resets all the merge_when_build_succeeds params" do - expect(mr_merge_if_green_enabled.merge_when_build_succeeds).to be_falsey + it "resets all the pipeline succeeds params" do + expect(mr_merge_if_green_enabled.merge_when_pipeline_succeeds).to be_falsey expect(mr_merge_if_green_enabled.merge_params).to eq({}) expect(mr_merge_if_green_enabled.merge_user).to be nil end diff --git a/spec/services/merge_requests/refresh_service_spec.rb b/spec/services/merge_requests/refresh_service_spec.rb index 983dac6efdb..ff367f54d2a 100644 --- a/spec/services/merge_requests/refresh_service_spec.rb +++ b/spec/services/merge_requests/refresh_service_spec.rb @@ -18,7 +18,7 @@ describe MergeRequests::RefreshService, services: true do source_branch: 'master', target_branch: 'feature', target_project: @project, - merge_when_build_succeeds: true, + merge_when_pipeline_succeeds: true, merge_user: @user) @fork_merge_request = create(:merge_request, @@ -62,7 +62,7 @@ describe MergeRequests::RefreshService, services: true do it { expect(@merge_request.notes).not_to be_empty } it { expect(@merge_request).to be_open } - it { expect(@merge_request.merge_when_build_succeeds).to be_falsey } + it { expect(@merge_request.merge_when_pipeline_succeeds).to be_falsey } it { expect(@merge_request.diff_head_sha).to eq(@newrev) } it { expect(@fork_merge_request).to be_open } it { expect(@fork_merge_request.notes).to be_empty } diff --git a/spec/services/notes/create_service_spec.rb b/spec/services/notes/create_service_spec.rb index 9c92a5080c6..152c6d20daa 100644 --- a/spec/services/notes/create_service_spec.rb +++ b/spec/services/notes/create_service_spec.rb @@ -102,47 +102,19 @@ describe Notes::CreateService, services: true do expect(subject.note).to eq(params[:note]) end end - end - - describe "award emoji" do - before do - project.team << [user, :master] - end - - it "creates an award emoji" do - opts = { - note: ':smile: ', - noteable_type: 'Issue', - noteable_id: issue.id - } - note = described_class.new(project, user, opts).execute - - expect(note).to be_valid - expect(note.name).to eq('smile') - end - it "creates regular note if emoji name is invalid" do - opts = { - note: ':smile: moretext:', - noteable_type: 'Issue', - noteable_id: issue.id - } - note = described_class.new(project, user, opts).execute - - expect(note).to be_valid - expect(note.note).to eq(opts[:note]) - end - - it "normalizes the emoji name" do - opts = { - note: ':+1:', - noteable_type: 'Issue', - noteable_id: issue.id - } - - expect_any_instance_of(TodoService).to receive(:new_award_emoji).with(issue, user) + describe 'note with emoji only' do + it 'creates regular note' do + opts = { + note: ':smile: ', + noteable_type: 'Issue', + noteable_id: issue.id + } + note = described_class.new(project, user, opts).execute - described_class.new(project, user, opts).execute + expect(note).to be_valid + expect(note.note).to eq(':smile:') + end end end end diff --git a/spec/services/notification_service_spec.rb b/spec/services/notification_service_spec.rb index 839250b7d84..ebbaea4e59a 100644 --- a/spec/services/notification_service_spec.rb +++ b/spec/services/notification_service_spec.rb @@ -1050,22 +1050,22 @@ describe NotificationService, services: true do should_not_email(@u_lazy_participant) end - it "notifies the merger when merge_when_build_succeeds is true" do - merge_request.merge_when_build_succeeds = true + it "notifies the merger when the pipeline succeeds is true" do + merge_request.merge_when_pipeline_succeeds = true notification.merge_mr(merge_request, @u_watcher) should_email(@u_watcher) end - it "does not notify the merger when merge_when_build_succeeds is false" do - merge_request.merge_when_build_succeeds = false + it "does not notify the merger when the pipeline succeeds is false" do + merge_request.merge_when_pipeline_succeeds = false notification.merge_mr(merge_request, @u_watcher) should_not_email(@u_watcher) end - it "notifies the merger when merge_when_build_succeeds is false but they've opted into notifications about their activity" do - merge_request.merge_when_build_succeeds = false + it "notifies the merger when the pipeline succeeds is false but they've opted into notifications about their activity" do + merge_request.merge_when_pipeline_succeeds = false @u_watcher.notified_of_own_activity = true notification.merge_mr(merge_request, @u_watcher) diff --git a/spec/services/slash_commands/interpret_service_spec.rb b/spec/services/slash_commands/interpret_service_spec.rb index 0b0925983eb..52e8678cb9d 100644 --- a/spec/services/slash_commands/interpret_service_spec.rb +++ b/spec/services/slash_commands/interpret_service_spec.rb @@ -267,6 +267,14 @@ describe SlashCommands::InterpretService, services: true do end end + shared_examples 'award command' do + it 'toggle award 100 emoji if content containts /award :100:' do + _, updates = service.execute(content, issuable) + + expect(updates).to eq(emoji_award: "100") + end + end + it_behaves_like 'reopen command' do let(:content) { '/reopen' } let(:issuable) { issue } @@ -654,6 +662,37 @@ describe SlashCommands::InterpretService, services: true do end end + context '/award command' do + it_behaves_like 'award command' do + let(:content) { '/award :100:' } + let(:issuable) { issue } + end + + it_behaves_like 'award command' do + let(:content) { '/award :100:' } + let(:issuable) { merge_request } + end + + context 'ignores command with no argument' do + it_behaves_like 'empty command' do + let(:content) { '/award' } + let(:issuable) { issue } + end + end + + context 'ignores non-existing / invalid emojis' do + it_behaves_like 'empty command' do + let(:content) { '/award noop' } + let(:issuable) { issue } + end + + it_behaves_like 'empty command' do + let(:content) { '/award :lorem_ipsum:' } + let(:issuable) { issue } + end + end + end + context '/target_branch command' do let(:non_empty_project) { create(:project) } let(:another_merge_request) { create(:merge_request, author: developer, source_project: non_empty_project) } diff --git a/spec/services/system_note_service_spec.rb b/spec/services/system_note_service_spec.rb index eca5a418f2a..1f2ec9eacf0 100644 --- a/spec/services/system_note_service_spec.rb +++ b/spec/services/system_note_service_spec.rb @@ -215,13 +215,13 @@ describe SystemNoteService, services: true do end end - describe '.merge_when_build_succeeds' do + describe '.merge_when_pipeline_succeeds' do let(:pipeline) { build(:ci_pipeline_without_jobs )} let(:noteable) do create(:merge_request, source_project: project, target_project: project) end - subject { described_class.merge_when_build_succeeds(noteable, project, author, noteable.diff_head_commit) } + subject { described_class.merge_when_pipeline_succeeds(noteable, project, author, noteable.diff_head_commit) } it_behaves_like 'a system note' @@ -230,12 +230,12 @@ describe SystemNoteService, services: true do end end - describe '.cancel_merge_when_build_succeeds' do + describe '.cancel_merge_when_pipeline_succeeds' do let(:noteable) do create(:merge_request, source_project: project, target_project: project) end - subject { described_class.cancel_merge_when_build_succeeds(noteable, project, author) } + subject { described_class.cancel_merge_when_pipeline_succeeds(noteable, project, author) } it_behaves_like 'a system note' diff --git a/spec/services/todo_service_spec.rb b/spec/services/todo_service_spec.rb index 9f24cc0f3f2..fb9a8462f84 100644 --- a/spec/services/todo_service_spec.rb +++ b/spec/services/todo_service_spec.rb @@ -680,7 +680,7 @@ describe TodoService, services: true do end it 'creates a pending todo for merge_user' do - mr_unassigned.update(merge_when_build_succeeds: true, merge_user: admin) + mr_unassigned.update(merge_when_pipeline_succeeds: true, merge_user: admin) service.merge_request_build_failed(mr_unassigned) should_create_todo(user: admin, author: admin, target: mr_unassigned, action: Todo::BUILD_FAILED) @@ -700,7 +700,7 @@ describe TodoService, services: true do describe '#merge_request_became_unmergeable' do it 'creates a pending todo for a merge_user' do - mr_unassigned.update(merge_when_build_succeeds: true, merge_user: admin) + mr_unassigned.update(merge_when_pipeline_succeeds: true, merge_user: admin) service.merge_request_became_unmergeable(mr_unassigned) should_create_todo(user: admin, author: admin, target: mr_unassigned, action: Todo::UNMERGEABLE) diff --git a/spec/support/features/rss_shared_examples.rb b/spec/support/features/rss_shared_examples.rb new file mode 100644 index 00000000000..9a3b0a731ad --- /dev/null +++ b/spec/support/features/rss_shared_examples.rb @@ -0,0 +1,23 @@ +shared_examples "an autodiscoverable RSS feed with current_user's private token" do + it "has an RSS autodiscovery link tag with current_user's private token" do + expect(page).to have_css("link[type*='atom+xml'][href*='private_token=#{Thread.current[:current_user].private_token}']", visible: false) + end +end + +shared_examples "it has an RSS button with current_user's private token" do + it "shows the RSS button with current_user's private token" do + expect(page).to have_css("a:has(.fa-rss)[href*='private_token=#{Thread.current[:current_user].private_token}']") + end +end + +shared_examples "an autodiscoverable RSS feed without a private token" do + it "has an RSS autodiscovery link tag without a private token" do + expect(page).to have_css("link[type*='atom+xml']:not([href*='private_token'])", visible: false) + end +end + +shared_examples "it has an RSS button without a private token" do + it "shows the RSS button without a private token" do + expect(page).to have_css("a:has(.fa-rss):not([href*='private_token'])") + end +end diff --git a/spec/support/matchers/gitaly_matchers.rb b/spec/support/matchers/gitaly_matchers.rb new file mode 100644 index 00000000000..d7a53820684 --- /dev/null +++ b/spec/support/matchers/gitaly_matchers.rb @@ -0,0 +1,3 @@ +RSpec::Matchers.define :post_receive_request_with_repo_path do |path| + match { |actual| actual.repository.path == path } +end diff --git a/spec/support/project_features_apply_to_issuables_shared_examples.rb b/spec/support/project_features_apply_to_issuables_shared_examples.rb index 4621d17549b..f8b7d0527ba 100644 --- a/spec/support/project_features_apply_to_issuables_shared_examples.rb +++ b/spec/support/project_features_apply_to_issuables_shared_examples.rb @@ -18,7 +18,7 @@ shared_examples 'project features apply to issuables' do |klass| before do _ = issuable - login_as(user) + login_as(user) if user visit path end diff --git a/spec/views/ci/status/_badge.html.haml_spec.rb b/spec/views/ci/status/_badge.html.haml_spec.rb new file mode 100644 index 00000000000..c62450fb8e2 --- /dev/null +++ b/spec/views/ci/status/_badge.html.haml_spec.rb @@ -0,0 +1,89 @@ +require 'spec_helper' + +describe 'ci/status/_badge', :view do + let(:user) { create(:user) } + let(:project) { create(:empty_project, :private) } + let(:pipeline) { create(:ci_pipeline, project: project) } + + context 'when rendering status for build' do + let(:build) do + create(:ci_build, :success, pipeline: pipeline) + end + + context 'when user has ability to see details' do + before do + project.add_developer(user) + end + + it 'has link to build details page' do + details_path = namespace_project_build_path( + project.namespace, project, build) + + render_status(build) + + expect(rendered).to have_link 'passed', href: details_path + end + end + + context 'when user do not have ability to see build details' do + before do + render_status(build) + end + + it 'contains build status text' do + expect(rendered).to have_content 'passed' + end + + it 'does not contain links' do + expect(rendered).not_to have_link 'passed' + end + end + end + + context 'when rendering status for external job' do + context 'when user has ability to see commit status details' do + before do + project.add_developer(user) + end + + context 'status has external target url' do + before do + external_job = create(:generic_commit_status, + status: :running, + pipeline: pipeline, + target_url: 'http://gitlab.com') + + render_status(external_job) + end + + it 'contains valid commit status text' do + expect(rendered).to have_content 'running' + end + + it 'has link to external status page' do + expect(rendered).to have_link 'running', href: 'http://gitlab.com' + end + end + + context 'status do not have external target url' do + before do + external_job = create(:generic_commit_status, status: :canceled) + + render_status(external_job) + end + + it 'contains valid commit status text' do + expect(rendered).to have_content 'canceled' + end + + it 'has link to external status page' do + expect(rendered).not_to have_link 'canceled' + end + end + end + end + + def render_status(resource) + render 'ci/status/badge', status: resource.detailed_status(user) + end +end diff --git a/vendor/assets/javascripts/jquery.highlight.js b/vendor/assets/javascripts/jquery.highlight.js deleted file mode 100644 index 7a67cf99844..00000000000 --- a/vendor/assets/javascripts/jquery.highlight.js +++ /dev/null @@ -1,53 +0,0 @@ -/* - -highlight v3 - -Highlights arbitrary terms. - -<http://johannburkard.de/blog/programming/javascript/highlight-javascript-text-higlighting-jquery-plugin.html> - -MIT license. - -Johann Burkard -<http://johannburkard.de> -<mailto:jb@eaio.com> - -*/ - -jQuery.fn.highlight = function(pat) { - function innerHighlight(node, pat) { - var skip = 0; - if (node.nodeType == 3) { - var pos = node.data.toUpperCase().indexOf(pat); - if (pos >= 0) { - var spannode = document.createElement('span'); - spannode.className = 'highlight_word'; - var middlebit = node.splitText(pos); - var endbit = middlebit.splitText(pat.length); - var middleclone = middlebit.cloneNode(true); - spannode.appendChild(middleclone); - middlebit.parentNode.replaceChild(spannode, middlebit); - skip = 1; - } - } - else if (node.nodeType == 1 && node.childNodes && !/(script|style)/i.test(node.tagName)) { - for (var i = 0; i < node.childNodes.length; ++i) { - i += innerHighlight(node.childNodes[i], pat); - } - } - return skip; - } - return this.each(function() { - innerHighlight(this, pat.toUpperCase()); - }); -}; - -jQuery.fn.removeHighlight = function() { - return this.find("span.highlight").each(function() { - this.parentNode.firstChild.nodeName; - with (this.parentNode) { - replaceChild(this.firstChild, this); - normalize(); - } - }).end(); -}; diff --git a/yarn.lock b/yarn.lock index cb4ef36119f..fd8d67a6b0d 100644 --- a/yarn.lock +++ b/yarn.lock @@ -25,7 +25,7 @@ acorn-jsx@^3.0.0: dependencies: acorn "^3.0.4" -acorn@4.0.4, acorn@^4.0.3, acorn@^4.0.4: +acorn@4.0.4, acorn@^4.0.4: version "4.0.4" resolved "https://registry.yarnpkg.com/acorn/-/acorn-4.0.4.tgz#17a8d6a7a6c4ef538b814ec9abac2779293bf30a" @@ -33,7 +33,7 @@ acorn@^3.0.4: version "3.3.0" resolved "https://registry.yarnpkg.com/acorn/-/acorn-3.3.0.tgz#45e37fb39e8da3f25baee3ff5369e2bb5f22017a" -acorn@^4.0.11: +acorn@^4.0.11, acorn@^4.0.3: version "4.0.11" resolved "https://registry.yarnpkg.com/acorn/-/acorn-4.0.11.tgz#edcda3bd937e7556410d42ed5860f67399c794c0" @@ -2639,10 +2639,6 @@ jodid25519@^1.0.0: dependencies: jsbn "~0.1.0" -"jquery-ui@git+https://github.com/jquery/jquery-ui#1.11.4": - version "1.11.4" - resolved "git+https://github.com/jquery/jquery-ui#d6713024e16de90ea71dc0544ba34e1df01b4d8a" - jquery-ujs@^1.2.1: version "1.2.1" resolved "https://registry.yarnpkg.com/jquery-ujs/-/jquery-ujs-1.2.1.tgz#6ee75b1ef4e9ac95e7124f8d71f7d351f5548e92" @@ -3566,6 +3562,10 @@ raw-body@~2.2.0: iconv-lite "0.4.15" unpipe "1.0.0" +raw-loader@^0.5.1: + version "0.5.1" + resolved "https://registry.yarnpkg.com/raw-loader/-/raw-loader-0.5.1.tgz#0c3d0beaed8a01c966d9787bf778281252a979aa" + rc@~1.1.6: version "1.1.6" resolved "https://registry.yarnpkg.com/rc/-/rc-1.1.6.tgz#43651b76b6ae53b5c802f1151fa3fc3b059969c9" |