From 1bc5dab7b4f2650b5afb7c0e4c70e5ac9f66eba0 Mon Sep 17 00:00:00 2001 From: 3kami3 Date: Wed, 1 Mar 2017 23:16:38 +0900 Subject: [PATCH 001/238] Add real_ip setting to nginx example. ref) https://docs.gitlab.com/omnibus/settings/nginx.html#configuring-gitlab-trusted_proxies-and-the-nginx-real_ip-module --- lib/support/nginx/gitlab | 6 ++++++ lib/support/nginx/gitlab-ssl | 6 ++++++ 2 files changed, 12 insertions(+) diff --git a/lib/support/nginx/gitlab b/lib/support/nginx/gitlab index 2f7c34a3f31..78f28347d1a 100644 --- a/lib/support/nginx/gitlab +++ b/lib/support/nginx/gitlab @@ -38,6 +38,12 @@ server { ## See app/controllers/application_controller.rb for headers set + ## Real IP Module Config + ## http://nginx.org/en/docs/http/ngx_http_realip_module.html + real_ip_header X-Real-IP; ## X-Real-IP or X-Forwarded-For or proxy_protocol + real_ip_recursive off; ## If you enable 'on' + set_real_ip_from YOUR_TRUSTED_ADDRESS; ## Replace this with something like 192.168.1.0/24 + ## Individual nginx logs for this GitLab vhost access_log /var/log/nginx/gitlab_access.log; error_log /var/log/nginx/gitlab_error.log; diff --git a/lib/support/nginx/gitlab-ssl b/lib/support/nginx/gitlab-ssl index 5661394058d..1bccb1c2451 100644 --- a/lib/support/nginx/gitlab-ssl +++ b/lib/support/nginx/gitlab-ssl @@ -82,6 +82,12 @@ server { ## # ssl_dhparam /etc/ssl/certs/dhparam.pem; + ## Real IP Module Config + ## http://nginx.org/en/docs/http/ngx_http_realip_module.html + real_ip_header X-Real-IP; ## X-Real-IP or X-Forwarded-For or proxy_protocol + real_ip_recursive off; ## If you enable 'on' + set_real_ip_from YOUR_TRUSTED_ADDRESS; ## Replace this with something like 192.168.1.0/24 + ## Individual nginx logs for this GitLab vhost access_log /var/log/nginx/gitlab_access.log; error_log /var/log/nginx/gitlab_error.log; From 330d4f38041e2727a741d800b623563acef5e695 Mon Sep 17 00:00:00 2001 From: Nur Rony Date: Fri, 24 Feb 2017 22:18:08 +0600 Subject: [PATCH 002/238] dismissable error close is not visible enough --- app/assets/stylesheets/pages/note_form.scss | 12 ++++++++++++ ...ix-dismissable-error-close-not-visible-enough.yml | 4 ++++ 2 files changed, 16 insertions(+) create mode 100644 changelogs/unreleased/28660-fix-dismissable-error-close-not-visible-enough.yml diff --git a/app/assets/stylesheets/pages/note_form.scss b/app/assets/stylesheets/pages/note_form.scss index f984b469609..47e1c7902f8 100644 --- a/app/assets/stylesheets/pages/note_form.scss +++ b/app/assets/stylesheets/pages/note_form.scss @@ -148,6 +148,18 @@ .error-alert > .alert { margin-top: 5px; margin-bottom: 5px; + + &.alert-dismissable { + .close { + color: $white-light; + opacity: 0.85; + font-weight: normal; + + &:hover { + opacity: 1; + } + } + } } .discussion-body, diff --git a/changelogs/unreleased/28660-fix-dismissable-error-close-not-visible-enough.yml b/changelogs/unreleased/28660-fix-dismissable-error-close-not-visible-enough.yml new file mode 100644 index 00000000000..8b592766bf3 --- /dev/null +++ b/changelogs/unreleased/28660-fix-dismissable-error-close-not-visible-enough.yml @@ -0,0 +1,4 @@ +--- +title: Fixes dismissable error close is not visible enough +merge_request: 9516 +author: From 79c3ace80b690c9ccc2d6190fcf1f14f735f566c Mon Sep 17 00:00:00 2001 From: 3kami3 Date: Fri, 3 Mar 2017 22:20:29 +0900 Subject: [PATCH 003/238] https://gitlab.com/gitlab-org/gitlab-ce/merge_requests/9623#note_24573655 Fixed issues pointed out. --- lib/support/nginx/gitlab | 3 ++- lib/support/nginx/gitlab-ssl | 3 ++- 2 files changed, 4 insertions(+), 2 deletions(-) diff --git a/lib/support/nginx/gitlab b/lib/support/nginx/gitlab index 78f28347d1a..f25e66d54c8 100644 --- a/lib/support/nginx/gitlab +++ b/lib/support/nginx/gitlab @@ -42,7 +42,8 @@ server { ## http://nginx.org/en/docs/http/ngx_http_realip_module.html real_ip_header X-Real-IP; ## X-Real-IP or X-Forwarded-For or proxy_protocol real_ip_recursive off; ## If you enable 'on' - set_real_ip_from YOUR_TRUSTED_ADDRESS; ## Replace this with something like 192.168.1.0/24 + ## If you have a trusted IP address, uncomment it and set it + # set_real_ip_from YOUR_TRUSTED_ADDRESS; ## Replace this with something like 192.168.1.0/24 ## Individual nginx logs for this GitLab vhost access_log /var/log/nginx/gitlab_access.log; diff --git a/lib/support/nginx/gitlab-ssl b/lib/support/nginx/gitlab-ssl index 1bccb1c2451..67dac676e49 100644 --- a/lib/support/nginx/gitlab-ssl +++ b/lib/support/nginx/gitlab-ssl @@ -86,7 +86,8 @@ server { ## http://nginx.org/en/docs/http/ngx_http_realip_module.html real_ip_header X-Real-IP; ## X-Real-IP or X-Forwarded-For or proxy_protocol real_ip_recursive off; ## If you enable 'on' - set_real_ip_from YOUR_TRUSTED_ADDRESS; ## Replace this with something like 192.168.1.0/24 + ## If you have a trusted IP address, uncomment it and set it + # set_real_ip_from YOUR_TRUSTED_ADDRESS; ## Replace this with something like 192.168.1.0/24 ## Individual nginx logs for this GitLab vhost access_log /var/log/nginx/gitlab_access.log; From e8eac643b3da60f2694b8e2341bb84fc64e1116b Mon Sep 17 00:00:00 2001 From: Filipa Lacerda Date: Mon, 13 Mar 2017 17:37:18 +0000 Subject: [PATCH 004/238] Adds empty state for pipelines table --- .../vue_pipelines_index/pipelines.js | 45 +++++++++++++++++-- .../javascripts/vue_pipelines_index/store.js | 1 + .../empty_states/icons/_pipelines_empty.svg | 1 + .../empty_states/icons/_pipelines_failed.svg | 1 + 4 files changed, 44 insertions(+), 4 deletions(-) create mode 100644 app/views/shared/empty_states/icons/_pipelines_empty.svg create mode 100644 app/views/shared/empty_states/icons/_pipelines_failed.svg diff --git a/app/assets/javascripts/vue_pipelines_index/pipelines.js b/app/assets/javascripts/vue_pipelines_index/pipelines.js index 601ef41e917..d835a537979 100644 --- a/app/assets/javascripts/vue_pipelines_index/pipelines.js +++ b/app/assets/javascripts/vue_pipelines_index/pipelines.js @@ -1,5 +1,7 @@ /* global Vue, gl */ /* eslint-disable no-param-reassign */ +import pipelinesEmptyStateSVG from 'empty_states/icons/_pipelines_empty.svg'; +import pipelinesErrorStateSVG from 'empty_states/icons/_pipelines_failed.svg'; window.Vue = require('vue'); require('../vue_shared/components/table_pagination'); @@ -25,6 +27,9 @@ const CommitPipelinesStoreWithTimeAgo = require('../commit/pipelines/pipelines_s pagenum: 1, count: {}, pageRequest: false, + hasError: false, + pipelinesEmptyStateSVG, + pipelinesErrorStateSVG, }; }, props: ['scope', 'store'], @@ -43,6 +48,16 @@ const CommitPipelinesStoreWithTimeAgo = require('../commit/pipelines/pipelines_s } }, + computed: { + shouldRenderErrorState() { + return this.hasError && !this.pageRequest; + }, + + shouldRenderEmptyState() { + return !this.hasError && !this.pageRequest && !this.pipelines.length; + }, + }, + methods: { /** * Will change the page number and update the URL. @@ -62,11 +77,33 @@ const CommitPipelinesStoreWithTimeAgo = require('../commit/pipelines/pipelines_s +
+ +
+
+ ${pipelinesEmptyStateSVG} +
+
+ +
+
+

Build with confidence

+

+ Continous Integration can help catch bugs by running your tests automatically, + while Continuous Deployment can help you deliver code to your product environment. + + + Get started with Pipelines + +

+
+
+
+
-

- No pipelines to show -

+ v-if="shouldRenderErrorState"> + ${pipelinesErrorStateSVG}
diff --git a/app/assets/javascripts/vue_pipelines_index/store.js b/app/assets/javascripts/vue_pipelines_index/store.js index 909007267b9..08327ae146f 100644 --- a/app/assets/javascripts/vue_pipelines_index/store.js +++ b/app/assets/javascripts/vue_pipelines_index/store.js @@ -24,6 +24,7 @@ this.pageRequest = false; }, () => { this.pageRequest = false; + this.hasError = true; return new Flash('An error occurred while fetching the pipelines, please reload the page again.'); }); } diff --git a/app/views/shared/empty_states/icons/_pipelines_empty.svg b/app/views/shared/empty_states/icons/_pipelines_empty.svg new file mode 100644 index 00000000000..8119d5bebe0 --- /dev/null +++ b/app/views/shared/empty_states/icons/_pipelines_empty.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/app/views/shared/empty_states/icons/_pipelines_failed.svg b/app/views/shared/empty_states/icons/_pipelines_failed.svg new file mode 100644 index 00000000000..7dbabf7e4ef --- /dev/null +++ b/app/views/shared/empty_states/icons/_pipelines_failed.svg @@ -0,0 +1 @@ + \ No newline at end of file From d51a5f0b4f0d6ff6fb634c8508f22a60296c7cdb Mon Sep 17 00:00:00 2001 From: Filipa Lacerda Date: Mon, 13 Mar 2017 17:39:57 +0000 Subject: [PATCH 005/238] Renders failed svg when an error occurred --- .../vue_pipelines_index/pipelines.js | 21 +++++++++++++------ 1 file changed, 15 insertions(+), 6 deletions(-) diff --git a/app/assets/javascripts/vue_pipelines_index/pipelines.js b/app/assets/javascripts/vue_pipelines_index/pipelines.js index d835a537979..512771b4e30 100644 --- a/app/assets/javascripts/vue_pipelines_index/pipelines.js +++ b/app/assets/javascripts/vue_pipelines_index/pipelines.js @@ -101,9 +101,20 @@ const CommitPipelinesStoreWithTimeAgo = require('../commit/pipelines/pipelines_s
-
- ${pipelinesErrorStateSVG} +
+ +
+
+ ${pipelinesErrorStateSVG} +
+
+ +
+
+

The API failed to fetch the pipelines.

+
+
@@ -115,9 +126,7 @@ const CommitPipelinesStoreWithTimeAgo = require('../commit/pipelines/pipelines_s :pagenum='pagenum' :change='change' :count='count.all' - :pageInfo='pageInfo' - > - + :pageInfo='pageInfo'/>
`, }); From d3732b5529249472ce0de4fbad36e3567f6cee54 Mon Sep 17 00:00:00 2001 From: Filipa Lacerda Date: Tue, 14 Mar 2017 09:30:57 +0000 Subject: [PATCH 006/238] Moves tabs into Vue Component --- .../javascripts/vue_pipelines_index/index.js | 10 +- .../vue_pipelines_index/pipelines.js | 144 +++++++++++++++--- app/views/projects/pipelines/index.html.haml | 65 ++------ 3 files changed, 145 insertions(+), 74 deletions(-) diff --git a/app/assets/javascripts/vue_pipelines_index/index.js b/app/assets/javascripts/vue_pipelines_index/index.js index a90bd1518e9..6c1554b4c9d 100644 --- a/app/assets/javascripts/vue_pipelines_index/index.js +++ b/app/assets/javascripts/vue_pipelines_index/index.js @@ -7,13 +7,10 @@ require('../vue_shared/vue_resource_interceptor'); require('./pipelines'); $(() => new Vue({ - el: document.querySelector('.vue-pipelines-index'), + el: document.querySelector('#pipelines-list-vue'), data() { - const project = document.querySelector('.pipelines'); - return { - scope: project.dataset.url, store: new gl.PipelineStore(), }; }, @@ -21,9 +18,6 @@ $(() => new Vue({ 'vue-pipelines': gl.VuePipelines, }, template: ` - - + `, })); diff --git a/app/assets/javascripts/vue_pipelines_index/pipelines.js b/app/assets/javascripts/vue_pipelines_index/pipelines.js index 512771b4e30..e9b99fede10 100644 --- a/app/assets/javascripts/vue_pipelines_index/pipelines.js +++ b/app/assets/javascripts/vue_pipelines_index/pipelines.js @@ -18,10 +18,11 @@ const CommitPipelinesStoreWithTimeAgo = require('../commit/pipelines/pipelines_s }, data() { + const pipelinesData = document.querySelector('#pipelines-list-vue').dataset; + return { + ...pipelinesData, pipelines: [], - timeLoopInterval: '', - intervalId: '', apiScope: 'all', pageInfo: {}, pagenum: 1, @@ -39,7 +40,7 @@ const CommitPipelinesStoreWithTimeAgo = require('../commit/pipelines/pipelines_s if (pagenum) this.pagenum = pagenum; if (scope) this.apiScope = scope; - this.store.fetchDataLoop.call(this, Vue, this.pagenum, this.scope, this.apiScope); + this.store.fetchDataLoop.call(this, Vue, this.pagenum, this.endpoint, this.apiScope); }, beforeUpdate() { @@ -49,12 +50,56 @@ const CommitPipelinesStoreWithTimeAgo = require('../commit/pipelines/pipelines_s }, computed: { + canCreatePipelineParsed() { + return gl.utils.convertPermissionToBoolean(this.canCreatePipeline); + }, + + scope() { + return gl.utils.getParameterByName('scope'); + }, + shouldRenderErrorState() { return this.hasError && !this.pageRequest; }, + + /** + * The empty state should only be rendered when the request is made to fetch all pipelines + * and none is returned. + * + * @return {Boolean} + */ shouldRenderEmptyState() { - return !this.hasError && !this.pageRequest && !this.pipelines.length; + return !this.hasError && + !this.pageRequest && ( + !this.pipelines.length && (this.scope === 'all' || this.scope === null) + ); + }, + + shouldRenderTable() { + return !this.hasError && + !this.pageRequest && this.pipelines.length; + }, + + /** + * Header tabs should only be rendered when we receive an error or a successfull response with + * pipelines. + * + * @return {Boolean} + */ + shouldRenderTabs() { + return !this.pageRequest && !this.hasError && this.pipelines.length; + }, + + /** + * Pagination should only be rendered when there is more than one page. + * + * @return {Boolean} + */ + shouldRenderPagination() { + return !this.pageRequest && + this.pipelines.length && + this.pageInfo.total > this.pageInfo.perPage; }, }, @@ -72,14 +117,80 @@ const CommitPipelinesStoreWithTimeAgo = require('../commit/pipelines/pipelines_s }, }, template: ` -
-
- +
+
+ + + +
+ +
+
-
${pipelinesEmptyStateSVG} @@ -92,8 +203,7 @@ const CommitPipelinesStoreWithTimeAgo = require('../commit/pipelines/pipelines_s

Continous Integration can help catch bugs by running your tests automatically, while Continuous Deployment can help you deliver code to your product environment. - - + Get started with Pipelines

@@ -103,7 +213,6 @@ const CommitPipelinesStoreWithTimeAgo = require('../commit/pipelines/pipelines_s
-
${pipelinesErrorStateSVG} @@ -117,16 +226,17 @@ const CommitPipelinesStoreWithTimeAgo = require('../commit/pipelines/pipelines_s
-
+
+ v-if="shouldRenderPagination" + :pagenum="pagenum" + :change="change" + :count="count.all" + :pageInfo="pageInfo"/>
`, }); diff --git a/app/views/projects/pipelines/index.html.haml b/app/views/projects/pipelines/index.html.haml index 5d59ce06612..38cea4429ba 100644 --- a/app/views/projects/pipelines/index.html.haml +++ b/app/views/projects/pipelines/index.html.haml @@ -2,53 +2,20 @@ - page_title "Pipelines" = render "projects/pipelines/head" -%div{ class: container_class } - .top-area - %ul.nav-links - %li.js-pipelines-tab-all{ class: active_when(@scope.nil?) }> - = link_to project_pipelines_path(@project) do - All - %span.badge.js-totalbuilds-count - = number_with_delimiter(@pipelines_count) +- content_for :page_specific_javascripts do + = page_specific_javascript_bundle_tag("common_vue") + = page_specific_javascript_bundle_tag("vue_pipelines") - %li.js-pipelines-tab-pending{ class: active_when(@scope == 'pending') }> - = link_to project_pipelines_path(@project, scope: :pending) do - Pending - %span.badge - = number_with_delimiter(@pending_count) - - %li.js-pipelines-tab-running{ class: active_when(@scope == 'running') }> - = link_to project_pipelines_path(@project, scope: :running) do - Running - %span.badge.js-running-count - = number_with_delimiter(@running_count) - - %li.js-pipelines-tab-finished{ class: active_when(@scope == 'finished') }> - = link_to project_pipelines_path(@project, scope: :finished) do - Finished - %span.badge - = number_with_delimiter(@finished_count) - - %li.js-pipelines-tab-branches{ class: active_when(@scope == 'branches') }> - = link_to project_pipelines_path(@project, scope: :branches) do - Branches - - %li.js-pipelines-tab-tags{ class: active_when(@scope == 'tags') }> - = link_to project_pipelines_path(@project, scope: :tags) do - Tags - - .nav-controls - - if can? current_user, :create_pipeline, @project - = link_to new_namespace_project_pipeline_path(@project.namespace, @project), class: 'btn btn-create' do - Run pipeline - - - unless @repository.gitlab_ci_yml - = link_to 'Get started with Pipelines', help_page_path('ci/quick_start/README'), class: 'btn btn-info' - - = 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) } } - .vue-pipelines-index - -= page_specific_javascript_bundle_tag('common_vue') -= page_specific_javascript_bundle_tag('vue_pipelines') +#pipelines-list-vue{ data: { endpoint: namespace_project_pipelines_path(@project.namespace, @project, format: :json), + "css-class" => container_class, + "help-page-path" => help_page_path('ci/quick_start/README'), + "new-pipeline-path" => new_namespace_project_pipeline_path(@project.namespace, @project), + "can-create-pipeline" => can?(current_user, :create_pipeline, @project).to_s, + "all-path" => project_pipelines_path(@project), + "pending-path" => project_pipelines_path(@project, scope: :pending), + "running-path" => project_pipelines_path(@project, scope: :running), + "finished-path" => project_pipelines_path(@project, scope: :finished), + "branches-path" => project_pipelines_path(@project, scope: :branches), + "tags-path" => project_pipelines_path(@project, scope: :tags), + "has-ci" => @repository.gitlab_ci_yml, + "ci-lint-path" => ci_lint_path } } From 6fb6632110ccec9c7ad64217647e17a29bdd66e3 Mon Sep 17 00:00:00 2001 From: Filipa Lacerda Date: Wed, 15 Mar 2017 21:51:44 +0000 Subject: [PATCH 007/238] Adds counters to badges --- .../vue_pipelines_index/pipelines.js | 35 +++++++++++++++---- 1 file changed, 29 insertions(+), 6 deletions(-) diff --git a/app/assets/javascripts/vue_pipelines_index/pipelines.js b/app/assets/javascripts/vue_pipelines_index/pipelines.js index e9b99fede10..ffcf3744233 100644 --- a/app/assets/javascripts/vue_pipelines_index/pipelines.js +++ b/app/assets/javascripts/vue_pipelines_index/pipelines.js @@ -26,7 +26,12 @@ const CommitPipelinesStoreWithTimeAgo = require('../commit/pipelines/pipelines_s apiScope: 'all', pageInfo: {}, pagenum: 1, - count: {}, + count: { + all: 0, + pending: 0, + running: 0, + finished: 0, + }, pageRequest: false, hasError: false, pipelinesEmptyStateSVG, @@ -127,6 +132,7 @@ const CommitPipelinesStoreWithTimeAgo = require('../commit/pipelines/pipelines_s All + {{count.all}}
  • Pending - + + + {{count.pending}} +
  • - Running - + + + Running + + + + {{count.running}} +
  • +
  • - Finished - + + + Finished + + + {{count.finished}} +
  • +
  • Branches
  • +
  • From 0de8dccd2a7a5e07805fe6c151e44cf4ad3bf86f Mon Sep 17 00:00:00 2001 From: "billy.lb" Date: Thu, 16 Mar 2017 21:28:34 +0800 Subject: [PATCH 008/238] Fix bug when system hook for deploy key --- app/services/system_hooks_service.rb | 9 ++++----- changelogs/unreleased/bugfix-systemhook.yml | 4 ++++ spec/services/system_hooks_service_spec.rb | 3 +++ 3 files changed, 11 insertions(+), 5 deletions(-) create mode 100644 changelogs/unreleased/bugfix-systemhook.yml diff --git a/app/services/system_hooks_service.rb b/app/services/system_hooks_service.rb index 868fa7b3f21..af0ddbe5934 100644 --- a/app/services/system_hooks_service.rb +++ b/app/services/system_hooks_service.rb @@ -24,10 +24,9 @@ class SystemHooksService key: model.key, id: model.id ) + if model.user - data.merge!( - username: model.user.username - ) + data[:username] = model.user.username end when Project data.merge!(project_data(model)) @@ -35,8 +34,6 @@ class SystemHooksService if event == :rename || event == :transfer data[:old_path_with_namespace] = model.old_path_with_namespace end - - data when User data.merge!({ name: model.name, @@ -59,6 +56,8 @@ class SystemHooksService when GroupMember data.merge!(group_member_data(model)) end + + data end def build_event_name(model, event) diff --git a/changelogs/unreleased/bugfix-systemhook.yml b/changelogs/unreleased/bugfix-systemhook.yml new file mode 100644 index 00000000000..4c4d0dcc7a2 --- /dev/null +++ b/changelogs/unreleased/bugfix-systemhook.yml @@ -0,0 +1,4 @@ +--- +title: Fix bug when system hook for deploy key +merge_request: 9796 +author: billy.lb diff --git a/spec/services/system_hooks_service_spec.rb b/spec/services/system_hooks_service_spec.rb index db9f1231682..11037a4917b 100644 --- a/spec/services/system_hooks_service_spec.rb +++ b/spec/services/system_hooks_service_spec.rb @@ -5,6 +5,7 @@ describe SystemHooksService, services: true do let(:project) { create :project } let(:project_member) { create :project_member } let(:key) { create(:key, user: user) } + let(:deploy_key) { create(:key) } let(:group) { create(:group) } let(:group_member) { create(:group_member) } @@ -18,6 +19,8 @@ describe SystemHooksService, services: true do it { expect(event_data(project_member, :destroy)).to include(:event_name, :created_at, :updated_at, :project_name, :project_path, :project_path_with_namespace, :project_id, :user_name, :user_username, :user_email, :user_id, :access_level, :project_visibility) } it { expect(event_data(key, :create)).to include(:username, :key, :id) } it { expect(event_data(key, :destroy)).to include(:username, :key, :id) } + it { expect(event_data(deploy_key, :create)).to include(:key, :id) } + it { expect(event_data(deploy_key, :destroy)).to include(:key, :id) } it do project.old_path_with_namespace = 'renamed_from_path' From 992e06805c14b9afebbc240c87b4e9487e6f374e Mon Sep 17 00:00:00 2001 From: Joshua Lambert Date: Fri, 17 Mar 2017 02:17:38 -0400 Subject: [PATCH 009/238] Clarify help text --- app/models/project_services/prometheus_service.rb | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/app/models/project_services/prometheus_service.rb b/app/models/project_services/prometheus_service.rb index 375966b9efc..cd397a8a62c 100644 --- a/app/models/project_services/prometheus_service.rb +++ b/app/models/project_services/prometheus_service.rb @@ -30,7 +30,7 @@ class PrometheusService < MonitoringService end def help - 'Retrieves `container_cpu_usage_seconds_total` and `container_memory_usage_bytes` from the configured Prometheus server. An `environment` label is required on each metric to identify the Environment.' + 'Retrieves `container_cpu_usage_seconds_total` and `container_memory_usage_bytes` from the configured Prometheus server. If you are not using Auto-Deploy or have set up your own Prometheus server, an `environment` label is required on each metric to identify the Environment.' end def self.to_param From 03f6f51dcf5b0401257cddf8cbfd83aa1426f4bc Mon Sep 17 00:00:00 2001 From: Phil Hughes Date: Fri, 17 Mar 2017 10:05:27 +0000 Subject: [PATCH 010/238] Reset issue boards add issues modal page when filtering The page param is reset when filtering or when opening, previously it was possible to get the page param stuck on a high number making it impossible to either filter or to close & open the modal and get the issues you want to see. Closes #29616 --- app/assets/javascripts/boards/components/modal/index.js | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/app/assets/javascripts/boards/components/modal/index.js b/app/assets/javascripts/boards/components/modal/index.js index 1b66c8b922d..4240c97617d 100644 --- a/app/assets/javascripts/boards/components/modal/index.js +++ b/app/assets/javascripts/boards/components/modal/index.js @@ -64,6 +64,7 @@ require('./empty_state'); }, filter: { handler() { + this.page = 1; this.loadIssues(true); }, deep: true, @@ -115,6 +116,9 @@ require('./empty_state'); return this.activeTab === 'selected' && this.selectedIssues.length === 0; }, }, + created() { + this.page = 1; + }, components: { 'modal-header': gl.issueBoards.ModalHeader, 'modal-list': gl.issueBoards.ModalList, From c77fc4cee3d7a9f167cc5ca87fb5c8e22aed95f3 Mon Sep 17 00:00:00 2001 From: Sean McGivern Date: Fri, 17 Mar 2017 10:58:19 +0000 Subject: [PATCH 011/238] Add global `g t` shortcut to go to todos --- app/assets/javascripts/shortcuts_dashboard_navigation.js | 3 +++ app/assets/javascripts/shortcuts_navigation.js | 3 +++ app/views/help/_shortcuts.html.haml | 6 ++++++ app/views/layouts/header/_default.html.haml | 2 +- changelogs/unreleased/add-todos-shortcut.yml | 4 ++++ doc/workflow/shortcuts.md | 1 + spec/features/dashboard/shortcuts_spec.rb | 5 +++++ 7 files changed, 23 insertions(+), 1 deletion(-) create mode 100644 changelogs/unreleased/add-todos-shortcut.yml diff --git a/app/assets/javascripts/shortcuts_dashboard_navigation.js b/app/assets/javascripts/shortcuts_dashboard_navigation.js index e7baea894f6..4f1a19924a4 100644 --- a/app/assets/javascripts/shortcuts_dashboard_navigation.js +++ b/app/assets/javascripts/shortcuts_dashboard_navigation.js @@ -22,6 +22,9 @@ require('./shortcuts'); Mousetrap.bind('g m', function() { return ShortcutsDashboardNavigation.findAndFollowLink('.dashboard-shortcuts-merge_requests'); }); + Mousetrap.bind('g t', function() { + return ShortcutsDashboardNavigation.findAndFollowLink('.shortcuts-todos'); + }); Mousetrap.bind('g p', function() { return ShortcutsDashboardNavigation.findAndFollowLink('.dashboard-shortcuts-projects'); }); diff --git a/app/assets/javascripts/shortcuts_navigation.js b/app/assets/javascripts/shortcuts_navigation.js index 09a58cad2b2..3f5d6724417 100644 --- a/app/assets/javascripts/shortcuts_navigation.js +++ b/app/assets/javascripts/shortcuts_navigation.js @@ -43,6 +43,9 @@ require('./shortcuts'); Mousetrap.bind('g m', function() { return ShortcutsNavigation.findAndFollowLink('.shortcuts-merge_requests'); }); + Mousetrap.bind('g t', function() { + return ShortcutsNavigation.findAndFollowLink('.shortcuts-todos'); + }); Mousetrap.bind('g w', function() { return ShortcutsNavigation.findAndFollowLink('.shortcuts-wiki'); }); diff --git a/app/views/help/_shortcuts.html.haml b/app/views/help/_shortcuts.html.haml index 2684f16c373..8e6da3fad90 100644 --- a/app/views/help/_shortcuts.html.haml +++ b/app/views/help/_shortcuts.html.haml @@ -118,6 +118,12 @@ .key m %td Go to merge requests + %tr + %td.shortcut + .key g + .key t + %td + Go to todos %tbody %tr %th diff --git a/app/views/layouts/header/_default.html.haml b/app/views/layouts/header/_default.html.haml index 5fde5c2613e..7ddee0e5244 100644 --- a/app/views/layouts/header/_default.html.haml +++ b/app/views/layouts/header/_default.html.haml @@ -32,7 +32,7 @@ = link_to admin_root_path, title: 'Admin Area', aria: { label: "Admin Area" }, data: {toggle: 'tooltip', placement: 'bottom', container: 'body'} do = icon('wrench fw') %li - = link_to dashboard_todos_path, title: 'Todos', aria: { label: "Todos" }, data: {toggle: 'tooltip', placement: 'bottom', container: 'body'} do + = link_to dashboard_todos_path, title: 'Todos', aria: { label: "Todos" }, class: 'shortcuts-todos', data: {toggle: 'tooltip', placement: 'bottom', container: 'body'} do = icon('bell fw') %span.badge.todos-pending-count{ class: ("hidden" if todos_pending_count == 0) } = todos_count_format(todos_pending_count) diff --git a/changelogs/unreleased/add-todos-shortcut.yml b/changelogs/unreleased/add-todos-shortcut.yml new file mode 100644 index 00000000000..41d42775937 --- /dev/null +++ b/changelogs/unreleased/add-todos-shortcut.yml @@ -0,0 +1,4 @@ +--- +title: Add `g t` global shortcut to go to todos +merge_request: +author: diff --git a/doc/workflow/shortcuts.md b/doc/workflow/shortcuts.md index 7aa9b46081a..f94357abec9 100644 --- a/doc/workflow/shortcuts.md +++ b/doc/workflow/shortcuts.md @@ -36,6 +36,7 @@ You can see GitLab's keyboard shortcuts by using 'shift + ?' | g + p | Go to projects | | g + i | Go to issues | | g + m | Go to merge requests | +| g + t | Go to todos | ## Project diff --git a/spec/features/dashboard/shortcuts_spec.rb b/spec/features/dashboard/shortcuts_spec.rb index 62a2c54c94c..3642c0bfb5b 100644 --- a/spec/features/dashboard/shortcuts_spec.rb +++ b/spec/features/dashboard/shortcuts_spec.rb @@ -21,6 +21,11 @@ feature 'Dashboard shortcuts', feature: true, js: true do find('body').native.send_key('m') check_page_title('Merge Requests') + + find('body').native.send_key('g') + find('body').native.send_key('t') + + check_page_title('Todos') end def check_page_title(title) From cc25c4c06c532eef592cb88440f271694faf97ef Mon Sep 17 00:00:00 2001 From: Adam Niedzielski Date: Fri, 17 Mar 2017 14:12:16 +0100 Subject: [PATCH 012/238] Allow creating merge request even if target branch is not specified in query params Closes #28890 --- app/services/merge_requests/build_service.rb | 2 +- .../28890-allow-creating-mr-without-target-branch-in-url.yml | 5 +++++ .../features/merge_requests/user_uses_slash_commands_spec.rb | 1 - spec/services/merge_requests/build_service_spec.rb | 5 ++++- 4 files changed, 10 insertions(+), 3 deletions(-) create mode 100644 changelogs/unreleased/28890-allow-creating-mr-without-target-branch-in-url.yml diff --git a/app/services/merge_requests/build_service.rb b/app/services/merge_requests/build_service.rb index 9d4739e37bb..fdce542bd9e 100644 --- a/app/services/merge_requests/build_service.rb +++ b/app/services/merge_requests/build_service.rb @@ -6,7 +6,7 @@ module MergeRequests merge_request.source_project = find_source_project merge_request.target_project = find_target_project merge_request.target_branch = find_target_branch - merge_request.can_be_created = branches_valid? && source_branch_specified? && target_branch_specified? + merge_request.can_be_created = branches_valid? compare_branches if branches_present? assign_title_and_description if merge_request.can_be_created diff --git a/changelogs/unreleased/28890-allow-creating-mr-without-target-branch-in-url.yml b/changelogs/unreleased/28890-allow-creating-mr-without-target-branch-in-url.yml new file mode 100644 index 00000000000..114a14ec2df --- /dev/null +++ b/changelogs/unreleased/28890-allow-creating-mr-without-target-branch-in-url.yml @@ -0,0 +1,5 @@ +--- +title: Allow creating merge request even if target branch is not specified in query + params +merge_request: 9968 +author: diff --git a/spec/features/merge_requests/user_uses_slash_commands_spec.rb b/spec/features/merge_requests/user_uses_slash_commands_spec.rb index 2f3c3e45ae6..a1f4eb2688b 100644 --- a/spec/features/merge_requests/user_uses_slash_commands_spec.rb +++ b/spec/features/merge_requests/user_uses_slash_commands_spec.rb @@ -133,7 +133,6 @@ feature 'Merge Requests > User uses slash commands', feature: true, js: true do it 'changes target_branch in new merge_request' do visit new_namespace_project_merge_request_path(another_project.namespace, another_project, new_url_opts) - click_button "Compare branches and continue" fill_in "merge_request_title", with: 'My brand new feature' fill_in "merge_request_description", with: "le feature \n/target_branch fix\nFeature description:" diff --git a/spec/services/merge_requests/build_service_spec.rb b/spec/services/merge_requests/build_service_spec.rb index 0768f644036..adfa75a524f 100644 --- a/spec/services/merge_requests/build_service_spec.rb +++ b/spec/services/merge_requests/build_service_spec.rb @@ -49,10 +49,13 @@ describe MergeRequests::BuildService, services: true do let(:commits) { Commit.decorate([commit_1], project) } it 'creates compare object with target branch as default branch' do - expect(merge_request.can_be_created).to eq(false) expect(merge_request.compare).to be_present expect(merge_request.target_branch).to eq(project.default_branch) end + + it 'allows the merge request to be created' do + expect(merge_request.can_be_created).to eq(true) + end end context 'same source and target branch' do From faa4b53593dd39be8eaf3ac24dfb3902eaffbbad Mon Sep 17 00:00:00 2001 From: Phil Hughes Date: Fri, 17 Mar 2017 15:56:58 +0000 Subject: [PATCH 013/238] Fixed filter bar alignment issues Closes #29543 --- app/assets/stylesheets/framework/filters.scss | 6 ++++++ app/views/shared/_sort_dropdown.html.haml | 1 - 2 files changed, 6 insertions(+), 1 deletion(-) diff --git a/app/assets/stylesheets/framework/filters.scss b/app/assets/stylesheets/framework/filters.scss index 2ebeaf9a40d..ff2a61a2616 100644 --- a/app/assets/stylesheets/framework/filters.scss +++ b/app/assets/stylesheets/framework/filters.scss @@ -221,6 +221,10 @@ .filter-dropdown-container { display: -webkit-flex; display: flex; + + .dropdown-toggle { + line-height: 22px; + } } .dropdown-menu .filter-dropdown-item { @@ -246,7 +250,9 @@ background-color: $white-light; border-top: 0; } +} +@media (max-width: $screen-xs) { .filter-dropdown-container { .dropdown-toggle, .dropdown { diff --git a/app/views/shared/_sort_dropdown.html.haml b/app/views/shared/_sort_dropdown.html.haml index 367aa550a78..a212c714826 100644 --- a/app/views/shared/_sort_dropdown.html.haml +++ b/app/views/shared/_sort_dropdown.html.haml @@ -1,6 +1,5 @@ .dropdown.inline.prepend-left-10 %button.dropdown-toggle{ type: 'button', data: {toggle: 'dropdown' } } - %span.light - if @sort.present? = sort_options_hash[@sort] - else From 06c4410948bb0e5a7efd6646e81f221dc3f10f08 Mon Sep 17 00:00:00 2001 From: Phil Hughes Date: Fri, 17 Mar 2017 16:12:00 +0000 Subject: [PATCH 014/238] Fixed mobile alignment issues --- app/assets/stylesheets/framework/filters.scss | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/app/assets/stylesheets/framework/filters.scss b/app/assets/stylesheets/framework/filters.scss index ff2a61a2616..569a4c651cc 100644 --- a/app/assets/stylesheets/framework/filters.scss +++ b/app/assets/stylesheets/framework/filters.scss @@ -158,8 +158,8 @@ background-color: $white-light; @media (max-width: $screen-xs-min) { - -webkit-flex: 1 1 100%; - flex: 1 1 100%; + -webkit-flex: 1 1 auto; + flex: 1 1 auto; margin-bottom: 10px; .dropdown-menu { From b5cc98088ecd6082c660f7046871bc527ae8be0d Mon Sep 17 00:00:00 2001 From: Achilleas Pipinellis Date: Fri, 17 Mar 2017 15:23:52 +0100 Subject: [PATCH 015/238] Update Impersonation tokens docs --- doc/api/README.md | 135 +++++++++++++++++++++++++--------------------- doc/api/users.md | 121 +++++++++++++++++++++++++++++++---------- 2 files changed, 165 insertions(+), 91 deletions(-) diff --git a/doc/api/README.md b/doc/api/README.md index 58d090b8f5e..953bf4d6e97 100644 --- a/doc/api/README.md +++ b/doc/api/README.md @@ -74,6 +74,12 @@ returned with status code `401`: } ``` +### Session Cookie + +When signing in to GitLab as an ordinary user, a `_gitlab_session` cookie is +set. The API will use this cookie for authentication if it is present, but using +the API to generate a new session cookie is currently not supported. + ### Private Tokens You need to pass a `private_token` parameter via query string or header. If passed as a @@ -113,11 +119,73 @@ moment – `read_user` and `api` – the groundwork has been laid to add more sc At any time you can revoke any personal access token by just clicking **Revoke**. -### Session Cookie +### Impersonation tokens -When signing in to GitLab as an ordinary user, a `_gitlab_session` cookie is -set. The API will use this cookie for authentication if it is present, but using -the API to generate a new session cookie is currently not supported. +> [Introduced][ce-9099] in GitLab 9.0. Needs admin permissions. + +Impersonation tokens are a type of [Personal Access Token](#personal-access-tokens) +that can only be created by an admin for a specific user. + +They are a better alternative to using the user's password/private token +or using the [Sudo](#sudo) feature which also requires the admin's password +or private token, since the password/token can change over time. Impersonation +tokens are a great fit if you want to build applications or tools which +authenticate with the API as a specific user. + +For more information about the usage please refer to the +[users API](users.md#retrieve-user-impersonation-tokens) docs. + +### Sudo + +> Needs admin permissions. + +All API requests support performing an API call as if you were another user, +provided your private token is from an administrator account. You need to pass +the `sudo` parameter either via query string or a header with an ID/username of +the user you want to perform the operation as. If passed as a header, the +header name must be `SUDO` (uppercase). + +If a non administrative `private_token` is provided, then an error message will +be returned with status code `403`: + +```json +{ + "message": "403 Forbidden - Must be admin to use sudo" +} +``` + +If the sudo user ID or username cannot be found, an error message will be +returned with status code `404`: + +```json +{ + "message": "404 Not Found: No user id or username for: " +} +``` + +--- + +Example of a valid API call and a request using cURL with sudo request, +providing a username: + +```shell +GET /projects?private_token=9koXpg98eAheJpvBs5tK&sudo=username +``` + +```shell +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, +providing an ID: + +```shell +GET /projects?private_token=9koXpg98eAheJpvBs5tK&sudo=23 +``` + +```shell +curl --header "PRIVATE-TOKEN: 9koXpg98eAheJpvBs5tK" --header "SUDO: 23" "https://gitlab.example.com/api/v4/projects" +``` ## Basic Usage @@ -171,64 +239,6 @@ The following table shows the possible return codes for API requests. | `422 Unprocessable` | The entity could not be processed. | | `500 Server Error` | While handling the request something went wrong server-side. | -## Sudo - -All API requests support performing an API call as if you were another user, -provided your private token is from an administrator account. You need to pass -the `sudo` parameter either via query string or a header with an ID/username of -the user you want to perform the operation as. If passed as a header, the -header name must be `SUDO` (uppercase). - -If a non administrative `private_token` is provided, then an error message will -be returned with status code `403`: - -```json -{ - "message": "403 Forbidden - Must be admin to use sudo" -} -``` - -If the sudo user ID or username cannot be found, an error message will be -returned with status code `404`: - -```json -{ - "message": "404 Not Found: No user id or username for: " -} -``` - ---- - -Example of a valid API call and a request using cURL with sudo request, -providing a username: - -```shell -GET /projects?private_token=9koXpg98eAheJpvBs5tK&sudo=username -``` - -```shell -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, -providing an ID: - -```shell -GET /projects?private_token=9koXpg98eAheJpvBs5tK&sudo=23 -``` - -```shell -curl --header "PRIVATE-TOKEN: 9koXpg98eAheJpvBs5tK" --header "SUDO: 23" "https://gitlab.example.com/api/v4/projects" -``` - -## Impersonation Tokens - -Impersonation Tokens are a type of Personal Access Token that can only be created by an admin for a specific user. These can be used by automated tools -to authenticate with the API as a specific user, as a better alternative to using the user's password or private token directly, which may change over time, -and to using the [Sudo](#sudo) feature, which requires the tool to know an admin's password or private token, which can change over time as well and are extremely powerful. - -For more information about the usage please refer to the [Users](users.md) page - ## Pagination Sometimes the returned result will span across many pages. When listing @@ -398,3 +408,4 @@ programming languages. Visit the [GitLab website] for a complete list. [lib-api-url]: https://gitlab.com/gitlab-org/gitlab-ce/tree/master/lib/api/api.rb [ce-3749]: https://gitlab.com/gitlab-org/gitlab-ce/merge_requests/3749 [ce-5951]: https://gitlab.com/gitlab-org/gitlab-ce/merge_requests/5951 +[ce-9099]: https://gitlab.com/gitlab-org/gitlab-ce/merge_requests/9099 diff --git a/doc/api/users.md b/doc/api/users.md index 14b5c6c713e..2ada4d09c84 100644 --- a/doc/api/users.md +++ b/doc/api/users.md @@ -828,10 +828,12 @@ Example response: ] ``` -## Retrieve user impersonation tokens +## Get all impersonation tokens of a user -It retrieves every impersonation token of the user. Note that only administrators can do this. -This function takes pagination parameters `page` and `per_page` to restrict the list of impersonation tokens. +> Requires admin permissions. + +It retrieves every impersonation token of the user. Use the pagination +parameters `page` and `per_page` to restrict the list of impersonation tokens. ``` GET /users/:user_id/impersonation_tokens @@ -842,27 +844,50 @@ Parameters: | Attribute | Type | Required | Description | | --------- | ---- | -------- | ----------- | | `user_id` | integer | yes | The ID of the user | -| `state` | string | no | filter tokens based on state (all, active, inactive) | +| `state` | string | no | filter tokens based on state (`all`, `active`, `inactive`) | + +``` +curl --header "PRIVATE-TOKEN: 9koXpg98eAheJpvBs5tK" https://gitlab.example.com/api/v4/users/42/impersonation_tokens +``` Example response: + ```json [ - { - "id": 1, - "name": "mytoken", - "revoked": false, - "expires_at": "2017-01-04", - "scopes": ['api'], - "active": true, - "impersonation": true, - "token": "9koXpg98eAheJpvBs5tK" - } + { + "active" : true, + "token" : "EsMo-vhKfXGwX9RKrwiy", + "scopes" : [ + "api" + ], + "revoked" : false, + "name" : "mytoken", + "id" : 2, + "created_at" : "2017-03-17T17:18:09.283Z", + "impersonation" : true, + "expires_at" : "2017-04-04" + }, + { + "active" : false, + "scopes" : [ + "read_user" + ], + "revoked" : true, + "token" : "ZcZRpLeEuQRprkRjYydY", + "name" : "mytoken2", + "created_at" : "2017-03-17T17:19:28.697Z", + "id" : 3, + "impersonation" : true, + "expires_at" : "2017-04-14" + } ] ``` -## Show a user's impersonation token +## Get an impersonation token of a user -It shows a user's impersonation token. Note that only administrators can do this. +> Requires admin permissions. + +It shows a user's impersonation token. ``` GET /users/:user_id/impersonation_tokens/:impersonation_token_id @@ -875,7 +900,31 @@ Parameters: | `user_id` | integer | yes | The ID of the user | | `impersonation_token_id` | integer | yes | The ID of the impersonation token | -## Create a impersonation token +``` +curl --header "PRIVATE-TOKEN: 9koXpg98eAheJpvBs5tK" https://gitlab.example.com/api/v4/users/42/impersonation_tokens/2 +``` + +Example response: + +```json +{ + "active" : true, + "token" : "EsMo-vhKfXGwX9RKrwiy", + "scopes" : [ + "api" + ], + "revoked" : false, + "name" : "mytoken", + "id" : 2, + "created_at" : "2017-03-17T17:18:09.283Z", + "impersonation" : true, + "expires_at" : "2017-04-04" +} +``` + +## Create an impersonation token + +> Requires admin permissions. It creates a new impersonation token. Note that only administrators can do this. You are only able to create impersonation tokens to impersonate the user and perform @@ -891,32 +940,46 @@ Parameters: | Attribute | Type | Required | Description | | --------- | ---- | -------- | ----------- | | `user_id` | integer | yes | The ID of the user | -| `name` | string | yes | The name of the impersonation token | -| `expires_at` | date | no | The expiration date of the impersonation token | -| `scopes` | array | no | The array of scopes of the impersonation token (api, read_user) | +| `name` | string | yes | The name of the impersonation token | +| `expires_at` | date | no | The expiration date of the impersonation token in ISO format (`YYYY-MM-DD`)| +| `scopes` | array | yes | The array of scopes of the impersonation token (`api`, `read_user`) | + +``` +curl --request POST --header "PRIVATE-TOKEN: 9koXpg98eAheJpvBs5tK" --data "name=mytoken" --data "expires_at=2017-04-04" --data "scopes[]=api" https://gitlab.example.com/api/v4/users/42/impersonation_tokens +``` Example response: + ```json { - "id": 1, - "name": "mytoken", - "revoked": false, - "expires_at": "2017-01-04", - "scopes": ['api'], - "active": true, - "impersonation": true, - "token": "9koXpg98eAheJpvBs5tK" + "id" : 2, + "revoked" : false, + "scopes" : [ + "api" + ], + "token" : "EsMo-vhKfXGwX9RKrwiy", + "active" : true, + "impersonation" : true, + "name" : "mytoken", + "created_at" : "2017-03-17T17:18:09.283Z", + "expires_at" : "2017-04-04" } ``` ## Revoke an impersonation token -It revokes an impersonation token. Note that only administrators can revoke impersonation tokens. +> Requires admin permissions. + +It revokes an impersonation token. ``` DELETE /users/:user_id/impersonation_tokens/:impersonation_token_id ``` +``` +curl --request DELETE --header "PRIVATE-TOKEN: 9koXpg98eAheJpvBs5tK" https://gitlab.example.com/api/v4/users/42/impersonation_tokens/1 +``` + Parameters: | Attribute | Type | Required | Description | From 32dfa801085f9d9ccca5040fd23175fa8a3c22d9 Mon Sep 17 00:00:00 2001 From: Filipa Lacerda Date: Fri, 17 Mar 2017 17:46:45 +0000 Subject: [PATCH 016/238] Moves poll inside GL --- app/assets/javascripts/lib/utils/poll.js | 54 ++++++++++++++++++++++++ 1 file changed, 54 insertions(+) create mode 100644 app/assets/javascripts/lib/utils/poll.js diff --git a/app/assets/javascripts/lib/utils/poll.js b/app/assets/javascripts/lib/utils/poll.js new file mode 100644 index 00000000000..b6c7c809525 --- /dev/null +++ b/app/assets/javascripts/lib/utils/poll.js @@ -0,0 +1,54 @@ +import Vue from 'vue'; +import VueResource from 'vue-resource'; +import httpStatusCodes from './http_status'; + +Vue.use(VueResource); + +/** + * Polling utility for handling realtime updates with Vue.Resource get method. + * + * @example + * new poll({ + * url: 'endopoint', + * data: {}, + * successCallback: () => {} + * errorCallback: () => {} + * }).makeRequest(); + * + * + * 1. Checks for response and headers before start polling + * 2. Interval is provided by `X-Poll-Interval` header. + * 3. If `X-Poll-Interval` is -1, we stop polling + * 4. If HTTP response is 200, we poll. + * 5. If HTTP response is different from 200, we stop polling. + * + */ +export default class poll { + constructor(options) { + this.options = options || {}; + + this.intervalHeader = 'POLL-INTERVAL'; + } + + checkConditions(response) { + const headers = gl.utils.normalizeHeaders(response.headers); + const pollInterval = headers[this.intervalHeader]; + + if (pollInterval > 0 && response.status === httpStatusCodes.OK) { + this.options.successCallback(response); + setTimeout(() => { + this.makeRequest() + .then(this.checkConditions) + .catch(error => this.options.errorCallback(error)); + }, pollInterval); + } else { + this.options.successCallback(response); + } + } + + makeRequest() { + return Vue.http.get(this.options.url, this.options.data) + .then(this.checkConditions) + .catch(error => this.options.errorCallback(error)); + } +} From 19a6cc2664c5753485efae2d0d89dc317a92200d Mon Sep 17 00:00:00 2001 From: Filipa Lacerda Date: Fri, 17 Mar 2017 18:04:35 +0000 Subject: [PATCH 017/238] Adss changelog entry --- app/assets/javascripts/lib/utils/poll.js | 12 ++++++------ changelogs/unreleased/29575-polling.yml | 4 ++++ 2 files changed, 10 insertions(+), 6 deletions(-) create mode 100644 changelogs/unreleased/29575-polling.yml diff --git a/app/assets/javascripts/lib/utils/poll.js b/app/assets/javascripts/lib/utils/poll.js index b6c7c809525..56b6adaed66 100644 --- a/app/assets/javascripts/lib/utils/poll.js +++ b/app/assets/javascripts/lib/utils/poll.js @@ -11,21 +11,21 @@ Vue.use(VueResource); * new poll({ * url: 'endopoint', * data: {}, - * successCallback: () => {} - * errorCallback: () => {} + * successCallback: () => {}, + * errorCallback: () => {}, * }).makeRequest(); * * * 1. Checks for response and headers before start polling - * 2. Interval is provided by `X-Poll-Interval` header. - * 3. If `X-Poll-Interval` is -1, we stop polling + * 2. Interval is provided by `Poll-Interval` header. + * 3. If `Poll-Interval` is -1, we stop polling * 4. If HTTP response is 200, we poll. * 5. If HTTP response is different from 200, we stop polling. * */ export default class poll { - constructor(options) { - this.options = options || {}; + constructor(options = {}) { + this.options = options; this.intervalHeader = 'POLL-INTERVAL'; } diff --git a/changelogs/unreleased/29575-polling.yml b/changelogs/unreleased/29575-polling.yml new file mode 100644 index 00000000000..75016afd455 --- /dev/null +++ b/changelogs/unreleased/29575-polling.yml @@ -0,0 +1,4 @@ +--- +title: Adds polling utility function for vue resource +merge_request: +author: From 94c19fbfe87ed1e262bc7a88ca65719d7271ad7b Mon Sep 17 00:00:00 2001 From: Felipe Artur Date: Wed, 15 Mar 2017 17:58:09 -0300 Subject: [PATCH 018/238] Add closed_at field to issues --- app/models/issue.rb | 8 +++++++ changelogs/unreleased/issue_27212.yml | 4 ++++ .../20170315194013_add_closed_at_to_issues.rb | 7 ++++++ db/schema.rb | 3 ++- .../import_export/safe_model_attributes.yml | 1 + spec/models/issue_spec.rb | 24 +++++++++++++++++++ 6 files changed, 46 insertions(+), 1 deletion(-) create mode 100644 changelogs/unreleased/issue_27212.yml create mode 100644 db/migrate/20170315194013_add_closed_at_to_issues.rb diff --git a/app/models/issue.rb b/app/models/issue.rb index 1427fdc31a4..602eed86d9e 100644 --- a/app/models/issue.rb +++ b/app/models/issue.rb @@ -55,6 +55,14 @@ class Issue < ActiveRecord::Base state :opened state :reopened state :closed + + before_transition any => :closed do |issue| + issue.closed_at = Time.zone.now + end + + before_transition closed: any do |issue| + issue.closed_at = nil + end end def hook_attrs diff --git a/changelogs/unreleased/issue_27212.yml b/changelogs/unreleased/issue_27212.yml new file mode 100644 index 00000000000..7a7e04f7ca7 --- /dev/null +++ b/changelogs/unreleased/issue_27212.yml @@ -0,0 +1,4 @@ +--- +title: Add closed_at field to issues +merge_request: +author: diff --git a/db/migrate/20170315194013_add_closed_at_to_issues.rb b/db/migrate/20170315194013_add_closed_at_to_issues.rb new file mode 100644 index 00000000000..1326118cc8d --- /dev/null +++ b/db/migrate/20170315194013_add_closed_at_to_issues.rb @@ -0,0 +1,7 @@ +class AddClosedAtToIssues < ActiveRecord::Migration + DOWNTIME = false + + def change + add_column :issues, :closed_at, :datetime + end +end diff --git a/db/schema.rb b/db/schema.rb index 6eb3c95de93..e228b0a3568 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: 20170315174634) do +ActiveRecord::Schema.define(version: 20170315194013) do # These are extensions that must be enabled in order to support this database enable_extension "plpgsql" @@ -531,6 +531,7 @@ ActiveRecord::Schema.define(version: 20170315174634) do t.text "description_html" t.integer "time_estimate" t.integer "relative_position" + t.datetime "closed_at" end add_index "issues", ["assignee_id"], name: "index_issues_on_assignee_id", using: :btree diff --git a/spec/lib/gitlab/import_export/safe_model_attributes.yml b/spec/lib/gitlab/import_export/safe_model_attributes.yml index c718e792461..424b8b352cc 100644 --- a/spec/lib/gitlab/import_export/safe_model_attributes.yml +++ b/spec/lib/gitlab/import_export/safe_model_attributes.yml @@ -15,6 +15,7 @@ Issue: - updated_by_id - confidential - deleted_at +- closed_at - due_date - moved_to_id - lock_version diff --git a/spec/models/issue_spec.rb b/spec/models/issue_spec.rb index 9ffcb88bafd..73977d031f9 100644 --- a/spec/models/issue_spec.rb +++ b/spec/models/issue_spec.rb @@ -37,6 +37,30 @@ describe Issue, models: true do end end + describe '#closed_at' do + after do + Timecop.return + end + + let!(:now) { Timecop.freeze(Time.now) } + + it 'sets closed_at to Time.now when issue is closed' do + issue = create(:issue, state: 'opened') + + issue.close + + expect(issue.closed_at).to eq(now) + end + + it 'sets closed_at to nil when issue is reopened' do + issue = create(:issue, state: 'closed') + + issue.reopen + + expect(issue.closed_at).to be_nil + end + end + describe '#to_reference' do let(:namespace) { build(:namespace, path: 'sample-namespace') } let(:project) { build(:empty_project, name: 'sample-project', namespace: namespace) } From f8ceee24686bb5ed680de00c47f5dab08072e026 Mon Sep 17 00:00:00 2001 From: blackst0ne Date: Sat, 18 Mar 2017 08:29:08 +1100 Subject: [PATCH 019/238] Rename 'All issues' to 'Open issues' in Add issues modal --- app/assets/javascripts/boards/components/modal/empty_state.js | 4 ++-- app/assets/javascripts/boards/components/modal/tabs.js | 2 +- changelogs/unreleased/rename_all_issues.yml | 4 ++++ 3 files changed, 7 insertions(+), 3 deletions(-) create mode 100644 changelogs/unreleased/rename_all_issues.yml diff --git a/app/assets/javascripts/boards/components/modal/empty_state.js b/app/assets/javascripts/boards/components/modal/empty_state.js index 9538f5b69e9..e6973c3fd59 100644 --- a/app/assets/javascripts/boards/components/modal/empty_state.js +++ b/app/assets/javascripts/boards/components/modal/empty_state.js @@ -30,7 +30,7 @@ if (this.activeTab === 'selected') { obj.title = 'You haven\'t selected any issues yet'; obj.content = ` - Go back to All issues and select some issues + Go back to Open issues and select some issues to add to your board. `; } @@ -59,7 +59,7 @@ class="btn btn-default" @click="changeTab('all')" v-if="activeTab === 'selected'"> - All issues + Open issues
  • diff --git a/app/assets/javascripts/boards/components/modal/tabs.js b/app/assets/javascripts/boards/components/modal/tabs.js index e8cb43f3503..1cd6ca0ee88 100644 --- a/app/assets/javascripts/boards/components/modal/tabs.js +++ b/app/assets/javascripts/boards/components/modal/tabs.js @@ -23,7 +23,7 @@ href="#" role="button" @click.prevent="changeTab('all')"> - All issues + Open issues {{ issuesCount }} diff --git a/changelogs/unreleased/rename_all_issues.yml b/changelogs/unreleased/rename_all_issues.yml new file mode 100644 index 00000000000..d3109bdb17e --- /dev/null +++ b/changelogs/unreleased/rename_all_issues.yml @@ -0,0 +1,4 @@ +--- +title: Rename 'All issues' to 'Open issues' in Add issues modal +merge_request: 10042 +author: blackst0ne From 016602099b83dd23160748907d0dde08188c269f Mon Sep 17 00:00:00 2001 From: Filipa Lacerda Date: Fri, 17 Mar 2017 19:14:16 +0000 Subject: [PATCH 020/238] Update state. Divide into smaller components --- .../components/empty_state.js | 39 +++ .../components/error_state.js | 25 ++ .../components/nav_controls.js | 52 ++++ .../components/navigation_tabs.js | 66 +++++ .../javascripts/vue_pipelines_index/index.js | 6 +- .../vue_pipelines_index/pipelines.js | 246 +++++++----------- app/assets/stylesheets/pages/pipelines.scss | 1 + 7 files changed, 275 insertions(+), 160 deletions(-) create mode 100644 app/assets/javascripts/vue_pipelines_index/components/empty_state.js create mode 100644 app/assets/javascripts/vue_pipelines_index/components/error_state.js create mode 100644 app/assets/javascripts/vue_pipelines_index/components/nav_controls.js create mode 100644 app/assets/javascripts/vue_pipelines_index/components/navigation_tabs.js diff --git a/app/assets/javascripts/vue_pipelines_index/components/empty_state.js b/app/assets/javascripts/vue_pipelines_index/components/empty_state.js new file mode 100644 index 00000000000..cfe23b65d55 --- /dev/null +++ b/app/assets/javascripts/vue_pipelines_index/components/empty_state.js @@ -0,0 +1,39 @@ +import pipelinesEmptyStateSVG from 'empty_states/icons/_pipelines_empty.svg'; + +export default { + props: { + helpPagePath: { + type: String, + required: true, + }, + }, + + data() { + return { + pipelinesEmptyStateSVG, + }; + }, + + template: ` +
    +
    +
    + ${pipelinesEmptyStateSVG} +
    +
    + +
    +
    +

    Build with confidence

    +

    + Continous Integration can help catch bugs by running your tests automatically, + while Continuous Deployment can help you deliver code to your product environment. + + Get started with Pipelines + +

    +
    +
    +
    + `, +}; diff --git a/app/assets/javascripts/vue_pipelines_index/components/error_state.js b/app/assets/javascripts/vue_pipelines_index/components/error_state.js new file mode 100644 index 00000000000..9071ecdea73 --- /dev/null +++ b/app/assets/javascripts/vue_pipelines_index/components/error_state.js @@ -0,0 +1,25 @@ +import pipelinesErrorStateSVG from 'empty_states/icons/_pipelines_failed.svg'; + +export default { + data() { + return { + pipelinesErrorStateSVG, + }; + }, + + template: ` +
    +
    +
    + ${pipelinesErrorStateSVG} +
    +
    + +
    +
    +

    The API failed to fetch the pipelines.

    +
    +
    +
    + `, +}; diff --git a/app/assets/javascripts/vue_pipelines_index/components/nav_controls.js b/app/assets/javascripts/vue_pipelines_index/components/nav_controls.js new file mode 100644 index 00000000000..73eaaf6aa72 --- /dev/null +++ b/app/assets/javascripts/vue_pipelines_index/components/nav_controls.js @@ -0,0 +1,52 @@ +export default { + props: { + newPipelinePath: { + type: String, + required: true, + }, + + hasCIEnabled: { + type: Boolean, + required: true, + }, + + helpPagePath: { + type: String, + required: true, + }, + + ciLintPath: { + type: String, + required: true, + }, + + canCreatePipeline: { + type: Boolean, + required: true, + }, + }, + + template: ` + + `, +}; diff --git a/app/assets/javascripts/vue_pipelines_index/components/navigation_tabs.js b/app/assets/javascripts/vue_pipelines_index/components/navigation_tabs.js new file mode 100644 index 00000000000..a494d2459aa --- /dev/null +++ b/app/assets/javascripts/vue_pipelines_index/components/navigation_tabs.js @@ -0,0 +1,66 @@ +export default { + props: { + scope: { + type: String, + required: true, + }, + + count: { + type: Object, + required: true, + }, + + paths: { + type: Object, + required: true, + }, + }, + + template: ` + + `, +}; diff --git a/app/assets/javascripts/vue_pipelines_index/index.js b/app/assets/javascripts/vue_pipelines_index/index.js index c35fc63e7b5..031b78b8410 100644 --- a/app/assets/javascripts/vue_pipelines_index/index.js +++ b/app/assets/javascripts/vue_pipelines_index/index.js @@ -9,20 +9,16 @@ $(() => new Vue({ el: document.querySelector('#pipelines-list-vue'), data() { - const project = document.querySelector('.pipelines'); const store = new PipelinesStore(); return { store, - endpoint: project.dataset.url, }; }, components: { 'vue-pipelines': PipelinesComponent, }, template: ` - + `, })); diff --git a/app/assets/javascripts/vue_pipelines_index/pipelines.js b/app/assets/javascripts/vue_pipelines_index/pipelines.js index 4a729153bfa..64b8be78bc1 100644 --- a/app/assets/javascripts/vue_pipelines_index/pipelines.js +++ b/app/assets/javascripts/vue_pipelines_index/pipelines.js @@ -2,17 +2,19 @@ /* eslint-disable no-new */ import Vue from 'vue'; import '~/flash'; -import pipelinesEmptyStateSVG from 'empty_states/icons/_pipelines_empty.svg'; -import pipelinesErrorStateSVG from 'empty_states/icons/_pipelines_failed.svg'; import PipelinesService from './services/pipelines_service'; import eventHub from './event_hub'; import PipelinesTableComponent from '../vue_shared/components/pipelines_table'; import TablePaginationComponent from '../vue_shared/components/table_pagination'; +import EmptyState from './components/empty_state'; +import ErrorState from './components/error_state'; +import NavigationTabs from './components/navigation_tabs'; +import NavigationControls from './components/nav_controls'; export default { props: { - endpoint: { - type: String, + store: { + type: Object, required: true, }, }, @@ -20,6 +22,23 @@ export default { components: { 'gl-pagination': TablePaginationComponent, 'pipelines-table-component': PipelinesTableComponent, + 'empty-state': EmptyState, + 'error-state': ErrorState, + 'navigation-tabs': NavigationTabs, + 'navigation-controls': NavigationControls, + }, + + data() { + const pipelinesData = document.querySelector('#pipelines-list-vue').dataset; + + return { + ...pipelinesData, + state: this.store.state, + apiScope: 'all', + pagenum: 1, + pageRequest: false, + hasError: false, + }; }, computed: { @@ -28,7 +47,8 @@ export default { }, scope() { - return gl.utils.getParameterByName('scope'); + const scope = gl.utils.getParameterByName('scope'); + return scope === null ? 'all' : scope; }, shouldRenderErrorState() { @@ -42,25 +62,28 @@ export default { * @return {Boolean} */ shouldRenderEmptyState() { - return !this.hasError && - !this.pageRequest && ( - !this.pipelines.length && (this.scope === 'all' || this.scope === null) - ); + return !this.pageRequest && + !this.hasError && + !this.state.pipelines.length && + (this.scope === 'all' || this.scope === null); + }, + + /** + * When a specific scope does not have pipelines we render a message. + * + * @return {Boolean} + */ + shouldRenderNoPipelinesMessage() { + return !this.pageRequest && + !this.hasError && + !this.state.pipelines.length && + this.scope !== 'all' && + this.scope !== null; }, shouldRenderTable() { return !this.hasError && - !this.pageRequest && this.pipelines.length; - }, - - /** - * Header tabs should only be rendered when we receive an error or a successfull response with - * pipelines. - * - * @return {Boolean} - */ - shouldRenderTabs() { - return !this.pageRequest && !this.hasError && this.pipelines.length; + !this.pageRequest && this.state.pipelines.length; }, /** @@ -70,24 +93,24 @@ export default { */ shouldRenderPagination() { return !this.pageRequest && - this.pipelines.length && - this.pageInfo.total > this.pageInfo.perPage; + this.state.pipelines.length && + this.state.pageInfo.total > this.state.pageInfo.perPage; }, - }, - data() { - const pipelinesData = document.querySelector('#pipelines-list-vue').dataset; + hasCIEnabled() { + return this.hasCi !== undefined; + }, - return { - ...pipelinesData, - state: this.store.state, - apiScope: 'all', - pagenum: 1, - pageRequest: false, - hasError: false, - pipelinesEmptyStateSVG, - pipelinesErrorStateSVG, - }; + paths() { + return { + allPath: this.allPath, + pendingPath: this.pendingPath, + finishedPath: this.finishedPath, + runningPath: this.runningPath, + branchesPath: this.branchesPath, + tagsPath: this.tagsPath, + }; + }, }, created() { @@ -147,144 +170,57 @@ export default { }, template: ` -
    -
    - - - +
    -
    -
    -
    -
    - ${pipelinesEmptyStateSVG} -
    -
    + -
    -
    -

    Build with confidence

    -

    - Continous Integration can help catch bugs by running your tests automatically, - while Continuous Deployment can help you deliver code to your product environment. - - Get started with Pipelines - -

    -
    -
    + + +
    +

    No pipelines to show.

    -
    -
    -
    - ${pipelinesErrorStateSVG} -
    -
    - -
    -
    -

    The API failed to fetch the pipelines.

    -
    -
    -
    - -
    - + +
    + :count="state.count.all" + :pageInfo="state.pageInfo"/>
    `, }; diff --git a/app/assets/stylesheets/pages/pipelines.scss b/app/assets/stylesheets/pages/pipelines.scss index 33b38ca6923..44ed6dcf33a 100644 --- a/app/assets/stylesheets/pages/pipelines.scss +++ b/app/assets/stylesheets/pages/pipelines.scss @@ -2,6 +2,7 @@ .realtime-loading { font-size: 40px; text-align: center; + margin: 0 auto; } .stage { From 9d1ab1e9bd474e467c75cf7a1f7b728d6832075c Mon Sep 17 00:00:00 2001 From: Filipa Lacerda Date: Fri, 17 Mar 2017 23:07:37 +0000 Subject: [PATCH 021/238] Add error state to commits and merge requests pipelines table --- .../commit/pipelines/pipelines_table.js | 17 +++++++++++------ .../vue_pipelines_index/pipelines.js | 3 ++- 2 files changed, 13 insertions(+), 7 deletions(-) diff --git a/app/assets/javascripts/commit/pipelines/pipelines_table.js b/app/assets/javascripts/commit/pipelines/pipelines_table.js index 832c4b1bd2a..29ee3e5e67b 100644 --- a/app/assets/javascripts/commit/pipelines/pipelines_table.js +++ b/app/assets/javascripts/commit/pipelines/pipelines_table.js @@ -5,6 +5,7 @@ import PipelinesTableComponent from '../../vue_shared/components/pipelines_table import PipelinesService from '../../vue_pipelines_index/services/pipelines_service'; import PipelineStore from '../../vue_pipelines_index/stores/pipelines_store'; import eventHub from '../../vue_pipelines_index/event_hub'; +import ErrorState from '../../vue_pipelines_index/components/error_state'; import '../../lib/utils/common_utils'; import '../../vue_shared/vue_resource_interceptor'; @@ -22,6 +23,7 @@ import '../../vue_shared/vue_resource_interceptor'; export default Vue.component('pipelines-table', { components: { 'pipelines-table-component': PipelinesTableComponent, + 'error-state': ErrorState, }, /** @@ -39,9 +41,16 @@ export default Vue.component('pipelines-table', { store, state: store.state, isLoading: false, + hasError: false, }; }, + computed: { + shouldRenderErrorState() { + return this.hasError && !this.pageRequest; + }, + }, + /** * When the component is about to be mounted, tell the service to fetch the data * @@ -80,6 +89,7 @@ export default Vue.component('pipelines-table', { this.isLoading = false; }) .catch(() => { + this.hasError = true; this.isLoading = false; new Flash('An error occurred while fetching the pipelines, please reload the page again.'); }); @@ -92,12 +102,7 @@ export default Vue.component('pipelines-table', {
    -
    -

    - No pipelines to show -

    -
    +
    diff --git a/app/assets/javascripts/vue_pipelines_index/pipelines.js b/app/assets/javascripts/vue_pipelines_index/pipelines.js index 64b8be78bc1..87242ff0369 100644 --- a/app/assets/javascripts/vue_pipelines_index/pipelines.js +++ b/app/assets/javascripts/vue_pipelines_index/pipelines.js @@ -163,6 +163,7 @@ export default { this.pageRequest = false; }) .catch(() => { + this.hasError = true; this.pageRequest = false; new Flash('An error occurred while fetching the pipelines, please reload the page again.'); }); @@ -196,7 +197,7 @@ export default {
    - + From 2c85a20482c2e33f307ad8faf60e41e199aa25b8 Mon Sep 17 00:00:00 2001 From: Filipa Lacerda Date: Fri, 17 Mar 2017 23:15:28 +0000 Subject: [PATCH 022/238] Adds changelog --- changelogs/unreleased/27574-pipelines-empty-state.yml | 4 ++++ 1 file changed, 4 insertions(+) create mode 100644 changelogs/unreleased/27574-pipelines-empty-state.yml diff --git a/changelogs/unreleased/27574-pipelines-empty-state.yml b/changelogs/unreleased/27574-pipelines-empty-state.yml new file mode 100644 index 00000000000..c26ea97205f --- /dev/null +++ b/changelogs/unreleased/27574-pipelines-empty-state.yml @@ -0,0 +1,4 @@ +--- +title: Adds empty and error state to pipelines +merge_request: +author: From 35de410de193fb08575d114f0b4756b54f5a4d39 Mon Sep 17 00:00:00 2001 From: Sam Rose Date: Wed, 15 Mar 2017 11:18:51 -0400 Subject: [PATCH 023/238] Append _spec to group_name_toggle spec file --- .../groups/{group_name_toggle.rb => group_name_toggle_spec.rb} | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) rename spec/features/groups/{group_name_toggle.rb => group_name_toggle_spec.rb} (95%) diff --git a/spec/features/groups/group_name_toggle.rb b/spec/features/groups/group_name_toggle_spec.rb similarity index 95% rename from spec/features/groups/group_name_toggle.rb rename to spec/features/groups/group_name_toggle_spec.rb index ada4ac66e04..8528718a2f7 100644 --- a/spec/features/groups/group_name_toggle.rb +++ b/spec/features/groups/group_name_toggle_spec.rb @@ -1,6 +1,6 @@ require 'spec_helper' -feature 'Group name toggle', js: true do +feature 'Group name toggle', feature: true, js: true do let(:group) { create(:group) } let(:nested_group_1) { create(:group, parent: group) } let(:nested_group_2) { create(:group, parent: nested_group_1) } From 67eefe7eac45c36f8f8c6143f7df6e7eb47b7234 Mon Sep 17 00:00:00 2001 From: Simon Knox Date: Wed, 15 Mar 2017 23:08:01 +1100 Subject: [PATCH 024/238] don't show loading spinners for server-rendered sidebar fields --- app/assets/javascripts/due_date_select.js | 2 +- app/assets/javascripts/labels_select.js | 2 +- app/assets/javascripts/milestone_select.js | 4 ++-- app/assets/javascripts/users_select.js | 4 ++-- app/views/shared/issuable/_sidebar.html.haml | 10 +++++----- changelogs/unreleased/24683-sidebar-spinners.yml | 4 ++++ 6 files changed, 15 insertions(+), 11 deletions(-) create mode 100644 changelogs/unreleased/24683-sidebar-spinners.yml diff --git a/app/assets/javascripts/due_date_select.js b/app/assets/javascripts/due_date_select.js index fdbb4644971..db10b383913 100644 --- a/app/assets/javascripts/due_date_select.js +++ b/app/assets/javascripts/due_date_select.js @@ -132,7 +132,7 @@ class DueDateSelect { const selectedDateValue = this.datePayload[this.abilityName].due_date; const displayedDateStyle = this.displayedDate !== 'No due date' ? 'bold' : 'no-value'; - this.$loading.fadeIn(); + this.$loading.removeClass('hidden').fadeIn(); if (isDropdown) { this.$dropdown.trigger('loading.gl.dropdown'); diff --git a/app/assets/javascripts/labels_select.js b/app/assets/javascripts/labels_select.js index c648a0f076c..443fb3e0ca9 100644 --- a/app/assets/javascripts/labels_select.js +++ b/app/assets/javascripts/labels_select.js @@ -76,7 +76,7 @@ if (!selected.length) { data[abilityName].label_ids = ['']; } - $loading.fadeIn(); + $loading.removeClass('hidden').fadeIn(); $dropdown.trigger('loading.gl.dropdown'); return $.ajax({ type: 'PUT', diff --git a/app/assets/javascripts/milestone_select.js b/app/assets/javascripts/milestone_select.js index 02ff6f5682c..40e977df693 100644 --- a/app/assets/javascripts/milestone_select.js +++ b/app/assets/javascripts/milestone_select.js @@ -159,7 +159,7 @@ } $dropdown.trigger('loading.gl.dropdown'); - $loading.fadeIn(); + $loading.removeClass('hidden').fadeIn(); gl.issueBoards.BoardsStore.detail.issue.update($dropdown.attr('data-issue-update')) .then(function () { @@ -171,7 +171,7 @@ data = {}; data[abilityName] = {}; data[abilityName].milestone_id = selected != null ? selected : null; - $loading.fadeIn(); + $loading.removeClass('hidden').fadeIn(); $dropdown.trigger('loading.gl.dropdown'); return $.ajax({ type: 'PUT', diff --git a/app/assets/javascripts/users_select.js b/app/assets/javascripts/users_select.js index c7a57b47834..eb897e9dfe9 100644 --- a/app/assets/javascripts/users_select.js +++ b/app/assets/javascripts/users_select.js @@ -53,7 +53,7 @@ $loading = $block.find('.block-loading').fadeOut(); var updateIssueBoardsIssue = function () { - $loading.fadeIn(); + $loading.removeClass('hidden').fadeIn(); gl.issueBoards.BoardsStore.detail.issue.update($dropdown.attr('data-issue-update')) .then(function () { $loading.fadeOut(); @@ -90,7 +90,7 @@ data = {}; data[abilityName] = {}; data[abilityName].assignee_id = selected != null ? selected : null; - $loading.fadeIn(); + $loading.removeClass('hidden').fadeIn(); $dropdown.trigger('loading.gl.dropdown'); return $.ajax({ type: 'PUT', diff --git a/app/views/shared/issuable/_sidebar.html.haml b/app/views/shared/issuable/_sidebar.html.haml index 048fc488207..25a4aec0a38 100644 --- a/app/views/shared/issuable/_sidebar.html.haml +++ b/app/views/shared/issuable/_sidebar.html.haml @@ -30,7 +30,7 @@ = icon('user', 'aria-hidden': 'true') .title.hide-collapsed Assignee - = icon('spinner spin', class: 'block-loading', 'aria-hidden': 'true') + = icon('spinner spin', class: 'hidden block-loading', 'aria-hidden': 'true') - if can_edit_issuable = link_to 'Edit', '#', class: 'edit-link pull-right' .value.hide-collapsed @@ -64,7 +64,7 @@ None .title.hide-collapsed Milestone - = icon('spinner spin', class: 'block-loading', 'aria-hidden': 'true') + = icon('spinner spin', class: 'hidden block-loading', 'aria-hidden': 'true') - if can_edit_issuable = link_to 'Edit', '#', class: 'edit-link pull-right' .value.hide-collapsed @@ -91,7 +91,7 @@ = issuable.due_date.try(:to_s, :medium) || 'None' .title.hide-collapsed Due date - = icon('spinner spin', class: 'block-loading', 'aria-hidden': 'true') + = icon('spinner spin', class: 'hidden 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 @@ -121,12 +121,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', 'aria-hidden': 'true') + = icon('tags', class: 'hidden', 'aria-hidden': 'true') %span = selected_labels.size .title.hide-collapsed Labels - = icon('spinner spin', class: 'block-loading', 'aria-hidden': 'true') + = icon('spinner spin', class: 'hidden 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?) } diff --git a/changelogs/unreleased/24683-sidebar-spinners.yml b/changelogs/unreleased/24683-sidebar-spinners.yml new file mode 100644 index 00000000000..3fec273152f --- /dev/null +++ b/changelogs/unreleased/24683-sidebar-spinners.yml @@ -0,0 +1,4 @@ +--- +title: hide loading spinners for server-rendered sidebar fields +merge_request: +author: From 19677dd4f29910ad12d5970cbc24be1a5ac08ec6 Mon Sep 17 00:00:00 2001 From: Clement Ho Date: Fri, 10 Mar 2017 16:30:42 -0600 Subject: [PATCH 025/238] Add filter focus for filtered search visual tokens --- .../filtered_search_manager.js | 23 +++++++++++++++++++ app/assets/stylesheets/framework/filters.scss | 18 +++++++++++---- .../filtered_search_manager_spec.js | 12 ++++++++++ 3 files changed, 49 insertions(+), 4 deletions(-) diff --git a/app/assets/javascripts/filtered_search/filtered_search_manager.js b/app/assets/javascripts/filtered_search/filtered_search_manager.js index 7ace51748aa..4c91c19a506 100644 --- a/app/assets/javascripts/filtered_search/filtered_search_manager.js +++ b/app/assets/javascripts/filtered_search/filtered_search_manager.js @@ -40,6 +40,8 @@ import FilteredSearchContainer from './container'; this.unselectEditTokensWrapper = this.unselectEditTokens.bind(this); this.editTokenWrapper = this.editToken.bind(this); this.tokenChange = this.tokenChange.bind(this); + this.addInputContainerFocusWrapper = this.addInputContainerFocus.bind(this); + this.removeInputContainerFocusWrapper = this.removeInputContainerFocus.bind(this); this.filteredSearchInputForm = this.filteredSearchInput.form; this.filteredSearchInputForm.addEventListener('submit', this.handleFormSubmit); @@ -51,11 +53,13 @@ import FilteredSearchContainer from './container'; this.filteredSearchInput.addEventListener('keyup', this.checkForBackspaceWrapper); this.filteredSearchInput.addEventListener('click', this.tokenChange); this.filteredSearchInput.addEventListener('keyup', this.tokenChange); + this.filteredSearchInput.addEventListener('focus', this.addInputContainerFocusWrapper); this.tokensContainer.addEventListener('click', FilteredSearchManager.selectToken); this.tokensContainer.addEventListener('dblclick', this.editTokenWrapper); this.clearSearchButton.addEventListener('click', this.clearSearchWrapper); document.addEventListener('click', gl.FilteredSearchVisualTokens.unselectTokens); document.addEventListener('click', this.unselectEditTokensWrapper); + document.addEventListener('click', this.removeInputContainerFocusWrapper); document.addEventListener('keydown', this.removeSelectedTokenWrapper); } @@ -69,11 +73,13 @@ import FilteredSearchContainer from './container'; this.filteredSearchInput.removeEventListener('keyup', this.checkForBackspaceWrapper); this.filteredSearchInput.removeEventListener('click', this.tokenChange); this.filteredSearchInput.removeEventListener('keyup', this.tokenChange); + this.filteredSearchInput.removeEventListener('focus', this.addInputContainerFocusWrapper); this.tokensContainer.removeEventListener('click', FilteredSearchManager.selectToken); this.tokensContainer.removeEventListener('dblclick', this.editTokenWrapper); this.clearSearchButton.removeEventListener('click', this.clearSearchWrapper); document.removeEventListener('click', gl.FilteredSearchVisualTokens.unselectTokens); document.removeEventListener('click', this.unselectEditTokensWrapper); + document.removeEventListener('click', this.removeInputContainerFocusWrapper); document.removeEventListener('keydown', this.removeSelectedTokenWrapper); } @@ -124,6 +130,23 @@ import FilteredSearchContainer from './container'; } } + addInputContainerFocus() { + this.filteredSearchInput.closest('.filtered-search-input-container').classList.add('focus'); + } + + removeInputContainerFocus(e) { + const inputContainer = this.filteredSearchInput.closest('.filtered-search-input-container'); + const isElementInFilteredSearch = inputContainer && inputContainer.contains(e.target); + const isElementInDynamicFilterDropdown = e.target.closest('.filter-dropdown') !== null; + const isElementInStaticFilterDropdown = e.target.closest('ul[data-dropdown]') !== null; + + if (!isElementInFilteredSearch && + !isElementInDynamicFilterDropdown && + !isElementInStaticFilterDropdown) { + inputContainer.classList.remove('focus'); + } + } + static selectToken(e) { const button = e.target.closest('.selectable'); diff --git a/app/assets/stylesheets/framework/filters.scss b/app/assets/stylesheets/framework/filters.scss index 2ebeaf9a40d..a1b0025a519 100644 --- a/app/assets/stylesheets/framework/filters.scss +++ b/app/assets/stylesheets/framework/filters.scss @@ -171,6 +171,20 @@ } } + &:hover { + @extend .form-control:hover; + } + + &.focus, + &.focus:hover { + border-color: $dropdown-input-focus-border; + box-shadow: 0 0 4px $search-input-focus-shadow-color; + } + + &.focus .fa-filter { + color: $common-gray-dark; + } + .form-control { position: relative; min-width: 200px; @@ -178,10 +192,6 @@ padding-right: 25px; border-color: transparent; - &:focus ~ .fa-filter { - color: $common-gray-dark; - } - &:focus, &:hover { outline: none; diff --git a/spec/javascripts/filtered_search/filtered_search_manager_spec.js b/spec/javascripts/filtered_search/filtered_search_manager_spec.js index ae9c263d1d7..113161c21c6 100644 --- a/spec/javascripts/filtered_search/filtered_search_manager_spec.js +++ b/spec/javascripts/filtered_search/filtered_search_manager_spec.js @@ -246,5 +246,17 @@ const FilteredSearchSpecHelper = require('../helpers/filtered_search_spec_helper expect(gl.FilteredSearchVisualTokens.unselectTokens).toHaveBeenCalled(); }); }); + + describe('toggleInputContainerFocus', () => { + it('toggles on focus', () => { + input.focus(); + expect(document.querySelector('.filtered-search-input-container').classList.contains('focus')).toEqual(true); + }); + + it('toggles on blur', () => { + input.blur(); + expect(document.querySelector('.filtered-search-input-container').classList.contains('focus')).toEqual(false); + }); + }); }); })(); From 666c20cb705a9e65e2700b1fb0c6bac8f73e827e Mon Sep 17 00:00:00 2001 From: mhasbini Date: Fri, 17 Mar 2017 23:26:46 +0200 Subject: [PATCH 026/238] Remove repeated routes.path check for postgresql database --- app/models/concerns/routable.rb | 10 ++++++---- changelogs/unreleased/routes-lower-case.yml | 4 ++++ 2 files changed, 10 insertions(+), 4 deletions(-) create mode 100644 changelogs/unreleased/routes-lower-case.yml diff --git a/app/models/concerns/routable.rb b/app/models/concerns/routable.rb index 9f6d215ceb3..529fb5ce988 100644 --- a/app/models/concerns/routable.rb +++ b/app/models/concerns/routable.rb @@ -51,11 +51,13 @@ module Routable paths.each do |path| path = connection.quote(path) - where = "(routes.path = #{path})" - if cast_lower - where = "(#{where} OR (LOWER(routes.path) = LOWER(#{path})))" - end + where = + if cast_lower + "(LOWER(routes.path) = LOWER(#{path}))" + else + "(routes.path = #{path})" + end wheres << where end diff --git a/changelogs/unreleased/routes-lower-case.yml b/changelogs/unreleased/routes-lower-case.yml new file mode 100644 index 00000000000..2110956680c --- /dev/null +++ b/changelogs/unreleased/routes-lower-case.yml @@ -0,0 +1,4 @@ +--- +title: Remove repeated routes.path check for postgresql database +merge_request: +author: mhasbini From 652d80458af1ea4552ae5095e212ef770a6b229d Mon Sep 17 00:00:00 2001 From: Phil Hughes Date: Fri, 17 Mar 2017 15:38:41 +0000 Subject: [PATCH 027/238] Fixed pagination in projects & snippets on user page Changed it from being json links to normal links & then doing a AJAX request to get the content. Closes #29624 --- app/assets/javascripts/user_tabs.js | 21 ++++++++++++++++--- app/controllers/users_controller.rb | 4 ++-- spec/features/users/projects_spec.rb | 31 ++++++++++++++++++++++++++++ 3 files changed, 51 insertions(+), 5 deletions(-) create mode 100644 spec/features/users/projects_spec.rb diff --git a/app/assets/javascripts/user_tabs.js b/app/assets/javascripts/user_tabs.js index 465618e3d53..5db0d936ad8 100644 --- a/app/assets/javascripts/user_tabs.js +++ b/app/assets/javascripts/user_tabs.js @@ -1,4 +1,4 @@ -/* eslint-disable max-len, space-before-function-paren, no-underscore-dangle, consistent-return, comma-dangle, no-unused-vars, dot-notation, no-new, no-return-assign, camelcase, no-param-reassign */ +/* eslint-disable max-len, space-before-function-paren, no-underscore-dangle, consistent-return, comma-dangle, no-unused-vars, dot-notation, no-new, no-return-assign, camelcase, no-param-reassign, class-methods-use-this */ /* UserTabs @@ -82,8 +82,19 @@ content on the Users#show page. } bindEvents() { - return this.$parentEl.off('shown.bs.tab', '.nav-links a[data-toggle="tab"]') + this.changeProjectsPageWrapper = this.changeProjectsPage.bind(this); + + this.$parentEl.off('shown.bs.tab', '.nav-links a[data-toggle="tab"]') .on('shown.bs.tab', '.nav-links a[data-toggle="tab"]', event => this.tabShown(event)); + + this.$parentEl.on('click', '.gl-pagination a', this.changeProjectsPageWrapper); + } + + changeProjectsPage(e) { + e.preventDefault(); + + $('.tab-pane.active').empty(); + this.loadTab($(e.target).attr('href'), this.getCurrentAction()); } tabShown(event) { @@ -119,7 +130,7 @@ content on the Users#show page. complete: () => this.toggleLoading(false), dataType: 'json', type: 'GET', - url: `${source}.json`, + url: source, success: (data) => { const tabSelector = `div#${action}`; this.$parentEl.find(tabSelector).html(data.html); @@ -153,6 +164,10 @@ content on the Users#show page. }, document.title, new_state); return new_state; } + + getCurrentAction() { + return this.$parentEl.find('.nav-links .active a').data('action'); + } } global.UserTabs = UserTabs; })(window.gl || (window.gl = {})); diff --git a/app/controllers/users_controller.rb b/app/controllers/users_controller.rb index 6e29f1e8a65..2683614d2e8 100644 --- a/app/controllers/users_controller.rb +++ b/app/controllers/users_controller.rb @@ -39,7 +39,7 @@ class UsersController < ApplicationController format.html { render 'show' } format.json do render json: { - html: view_to_html_string("shared/projects/_list", projects: @projects, remote: true) + html: view_to_html_string("shared/projects/_list", projects: @projects) } end end @@ -65,7 +65,7 @@ class UsersController < ApplicationController format.html { render 'show' } format.json do render json: { - html: view_to_html_string("snippets/_snippets", collection: @snippets, remote: true) + html: view_to_html_string("snippets/_snippets", collection: @snippets) } end end diff --git a/spec/features/users/projects_spec.rb b/spec/features/users/projects_spec.rb new file mode 100644 index 00000000000..1d75fe434b0 --- /dev/null +++ b/spec/features/users/projects_spec.rb @@ -0,0 +1,31 @@ +require 'spec_helper' + +describe 'Projects tab on a user profile', :feature, :js do + include WaitForAjax + + let(:user) { create(:user) } + let!(:project) { create(:empty_project, namespace: user.namespace) } + let!(:project2) { create(:empty_project, namespace: user.namespace) } + + before do + allow(Project).to receive(:default_per_page).and_return(1) + + login_as(user) + + visit user_path(user) + + page.within('.user-profile-nav') do + click_link('Personal projects') + end + + wait_for_ajax + end + + it 'paginates results' do + expect(page).to have_content(project2.name) + + click_link('Next') + + expect(page).to have_content(project.name) + end +end From 92b5e396209175176c971c3a3caad8b79f5ee3d6 Mon Sep 17 00:00:00 2001 From: Dark Dragon Date: Sat, 18 Mar 2017 14:01:09 +0000 Subject: [PATCH 028/238] Update 8.12-to-8.13.md: gitlab version 8.13 wants gitlab-shell v3.6.7 --- doc/update/8.12-to-8.13.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/doc/update/8.12-to-8.13.md b/doc/update/8.12-to-8.13.md index 75956aeb360..ed0e668d854 100644 --- a/doc/update/8.12-to-8.13.md +++ b/doc/update/8.12-to-8.13.md @@ -72,7 +72,7 @@ sudo -u git -H git checkout 8-13-stable-ee ```bash cd /home/git/gitlab-shell sudo -u git -H git fetch --all --tags -sudo -u git -H git checkout v3.6.6 +sudo -u git -H git checkout v3.6.7 ``` ### 6. Update gitlab-workhorse From 307d706ca627316472a362ad99da9c743fba48ce Mon Sep 17 00:00:00 2001 From: Joshua Lambert Date: Sat, 18 Mar 2017 11:24:48 -0400 Subject: [PATCH 029/238] Add additional details on metric source being k8s nodes --- app/models/project_services/prometheus_service.rb | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/app/models/project_services/prometheus_service.rb b/app/models/project_services/prometheus_service.rb index cd397a8a62c..8c2220c4ad1 100644 --- a/app/models/project_services/prometheus_service.rb +++ b/app/models/project_services/prometheus_service.rb @@ -30,7 +30,7 @@ class PrometheusService < MonitoringService end def help - 'Retrieves `container_cpu_usage_seconds_total` and `container_memory_usage_bytes` from the configured Prometheus server. If you are not using Auto-Deploy or have set up your own Prometheus server, an `environment` label is required on each metric to identify the Environment.' + 'Retrieves the Kubernetes node metrics `container_cpu_usage_seconds_total` and `container_memory_usage_bytes` from the configured Prometheus server. If you are not using Auto-Deploy or have set up your own Prometheus server, an `environment` label is required on each metric to identify the Environment.' end def self.to_param From 681af5bc4fe646a981c1d0bbbeddef00f5cee3b8 Mon Sep 17 00:00:00 2001 From: Stan Hu Date: Sat, 18 Mar 2017 02:29:25 -0700 Subject: [PATCH 030/238] Fix Error 500 when application settings are saved Due to a Rails bug, fetching the application settings from Redis may prevent the attribute methods from being loaded for the `ApplicationSetting` model. More details here: https://github.com/rails/rails/issues/27348 There was also a secondary problem introduced by overriding these association methods which caused all default visibility levels to be set to `nil`. Before, the previous implementation allowed the string "20" to be saved as an integer, while now a table lookup happens before that. We fix this by enforcing the integer value in the controller and default to PRIVATE. Closes #29674 --- .../admin/application_settings_controller.rb | 12 ++++++++++++ app/models/application_setting.rb | 9 +++++++++ spec/features/admin/admin_settings_spec.rb | 8 ++++++++ 3 files changed, 29 insertions(+) diff --git a/app/controllers/admin/application_settings_controller.rb b/app/controllers/admin/application_settings_controller.rb index 8d831ffdd70..11f69c14a5d 100644 --- a/app/controllers/admin/application_settings_controller.rb +++ b/app/controllers/admin/application_settings_controller.rb @@ -45,6 +45,18 @@ class Admin::ApplicationSettingsController < Admin::ApplicationController end def application_setting_params + default_visibilities = [:default_project_visibility, :default_snippet_visibility, :default_group_visibility] + + default_visibilities.each do |visibility| + value = params[:application_setting][visibility] + params[:application_setting][visibility] = + if value.present? + value.to_i + else + Gitlab::VisibilityLevel::PRIVATE + end + end + restricted_levels = params[:application_setting][:restricted_visibility_levels] if restricted_levels.nil? params[:application_setting][:restricted_visibility_levels] = [] diff --git a/app/models/application_setting.rb b/app/models/application_setting.rb index be632930895..671a0fe98cc 100644 --- a/app/models/application_setting.rb +++ b/app/models/application_setting.rb @@ -163,6 +163,8 @@ class ApplicationSetting < ActiveRecord::Base end def self.current + ensure_cache_setup + Rails.cache.fetch(CACHE_KEY) do ApplicationSetting.last end @@ -176,9 +178,16 @@ class ApplicationSetting < ActiveRecord::Base end def self.cached + ensure_cache_setup Rails.cache.fetch(CACHE_KEY) end + def self.ensure_cache_setup + # This is a workaround for a Rails bug that causes attribute methods not + # to be loaded when read from cache: https://github.com/rails/rails/issues/27348 + ApplicationSetting.define_attribute_methods + end + def self.defaults_ce { after_sign_up_text: nil, diff --git a/spec/features/admin/admin_settings_spec.rb b/spec/features/admin/admin_settings_spec.rb index de42ab81fac..b54ba9f5f88 100644 --- a/spec/features/admin/admin_settings_spec.rb +++ b/spec/features/admin/admin_settings_spec.rb @@ -9,6 +9,14 @@ feature 'Admin updates settings', feature: true do visit admin_application_settings_path end + scenario 'Change visibility settings' do + first(:radio_button, 'Public').set(true) + click_button 'Save' + + expect(page).to have_content "Application settings saved successfully" + expect(ApplicationSetting.current.default_project_visibility).to eq(Gitlab::VisibilityLevel::PUBLIC) + end + scenario 'Change application settings' do uncheck 'Gravatar enabled' fill_in 'Home page URL', with: 'https://about.gitlab.com/' From 4ccf874d31253a1354b418579f656b20866eee1d Mon Sep 17 00:00:00 2001 From: James Lopez Date: Sun, 19 Mar 2017 15:19:26 +0100 Subject: [PATCH 031/238] Update CHANGELOG.md for 8.15.8 [ci skip] --- CHANGELOG.md | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index 42e094bdfc6..3d4c0c9cc50 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -411,6 +411,11 @@ entry. - Add margin to markdown math blocks. - Add hover state to MR comment reply button. +## 8.15.8 (2017-03-19) + +- Only show public emails in atom feeds. +- To protect against Server-side Request Forgery project import URLs are now prohibited against localhost or the server IP except for the assigned instance URL and port. Imports are also prohibited from ports below 1024 with the exception of ports 22, 80, and 443. + ## 8.15.7 (2017-02-15) - No changes. From b730fb450f1d5f433cd99dd4fd66d491bd7ac9fa Mon Sep 17 00:00:00 2001 From: James Lopez Date: Sun, 19 Mar 2017 21:06:35 +0100 Subject: [PATCH 032/238] Update CHANGELOG.md for 8.16.8 [ci skip] --- CHANGELOG.md | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index 3d4c0c9cc50..f95c2777964 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -210,6 +210,11 @@ entry. - Remove deprecated GitlabCiService. - Requeue pending deletion projects. +## 8.16.8 (2017-03-19) + +- Only show public emails in atom feeds. +- To protect against Server-side Request Forgery project import URLs are now prohibited against localhost or the server IP except for the assigned instance URL and port. Imports are also prohibited from ports below 1024 with the exception of ports 22, 80, and 443. + ## 8.16.7 (2017-02-27) - No changes. From 770a703bd8e68601d166556124d3a4bd5b2a75da Mon Sep 17 00:00:00 2001 From: James Lopez Date: Sun, 19 Mar 2017 21:12:11 +0100 Subject: [PATCH 033/238] Update CHANGELOG.md for 8.17.4 [ci skip] --- CHANGELOG.md | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index f95c2777964..da1898e3770 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -2,6 +2,11 @@ documentation](doc/development/changelog.md) for instructions on adding your own entry. +## 8.17.4 (2017-03-19) + +- Only show public emails in atom feeds. +- To protect against Server-side Request Forgery project import URLs are now prohibited against localhost or the server IP except for the assigned instance URL and port. Imports are also prohibited from ports below 1024 with the exception of ports 22, 80, and 443. + ## 8.17.3 (2017-03-07) - Fix the redirect to custom home page URL. !9518 From aaaee8ae277a94c64927d57e3ce283930938a766 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?R=C3=A9my=20Coutable?= Date: Mon, 20 Mar 2017 09:37:14 +0100 Subject: [PATCH 034/238] Allow unauthenticated access to some Branch API GET endpoints MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: Rémy Coutable --- ...662-allow-unauthenticated-branches-api.yml | 4 + doc/api/branches.md | 5 +- lib/api/branches.rb | 2 +- spec/requests/api/branches_spec.rb | 187 ++++++++++++------ 4 files changed, 137 insertions(+), 61 deletions(-) create mode 100644 changelogs/unreleased/29662-allow-unauthenticated-branches-api.yml diff --git a/changelogs/unreleased/29662-allow-unauthenticated-branches-api.yml b/changelogs/unreleased/29662-allow-unauthenticated-branches-api.yml new file mode 100644 index 00000000000..15d7b9dcafb --- /dev/null +++ b/changelogs/unreleased/29662-allow-unauthenticated-branches-api.yml @@ -0,0 +1,4 @@ +--- +title: Allow unauthenticated access to some Branch API GET endpoints +merge_request: +author: diff --git a/doc/api/branches.md b/doc/api/branches.md index 83705106160..815aabda8e3 100644 --- a/doc/api/branches.md +++ b/doc/api/branches.md @@ -3,6 +3,8 @@ ## List repository branches Get a list of repository branches from a project, sorted by name alphabetically. +This endpoint can be accessed without authentication if the repository is +publicly accessible. ``` GET /projects/:id/repository/branches @@ -48,7 +50,8 @@ Example response: ## Get single repository branch -Get a single project repository branch. +Get a single project repository branch. This endpoint can be accessed without +authentication if the repository is publicly accessible. ``` GET /projects/:id/repository/branches/:branch diff --git a/lib/api/branches.rb b/lib/api/branches.rb index 2cc64fc6712..f35084a582a 100644 --- a/lib/api/branches.rb +++ b/lib/api/branches.rb @@ -4,7 +4,6 @@ module API class Branches < Grape::API include PaginationParams - before { authenticate! } before { authorize! :download_code, user_project } params do @@ -102,6 +101,7 @@ module API end post ":id/repository/branches" do authorize_push_project + result = CreateBranchService.new(user_project, current_user). execute(params[:branch], params[:ref]) diff --git a/spec/requests/api/branches_spec.rb b/spec/requests/api/branches_spec.rb index ab5a7e4d3de..a70f7beaae0 100644 --- a/spec/requests/api/branches_spec.rb +++ b/spec/requests/api/branches_spec.rb @@ -5,77 +5,146 @@ describe API::Branches, api: true do include ApiHelpers let(:user) { create(:user) } - let(:user2) { create(:user) } let!(:project) { create(:project, :repository, creator: user) } let!(:master) { create(:project_member, :master, user: user, project: project) } - let!(:guest) { create(:project_member, :guest, user: user2, project: project) } + let(:guest) { create(:user).tap { |u| create(:project_member, :guest, user: u, project: project) } } let!(:branch_name) { 'feature' } let!(:branch_sha) { '0b4bc9a49b562e85de7cc9e834518ea6828729b9' } - let!(:branch_with_dot) { CreateBranchService.new(project, user).execute("with.1.2.3", "master") } + let(:branch_with_dot) { CreateBranchService.new(project, user).execute("with.1.2.3", "master")[:branch] } describe "GET /projects/:id/repository/branches" do - it "returns an array of project branches" do - project.repository.expire_all_method_caches + let(:route) { "/projects/#{project.id}/repository/branches" } - get api("/projects/#{project.id}/repository/branches", user), per_page: 100 + shared_examples_for 'repository branches' do + it 'returns the repository branches' do + get api(route, current_user), per_page: 100 - expect(response).to have_http_status(200) - expect(response).to include_pagination_headers - expect(json_response).to be_an Array - branch_names = json_response.map { |x| x['name'] } - expect(branch_names).to match_array(project.repository.branch_names) + expect(response).to have_http_status(200) + expect(response).to include_pagination_headers + expect(json_response).to be_an Array + branch_names = json_response.map { |x| x['name'] } + expect(branch_names).to match_array(project.repository.branch_names) + end + + context 'when repository is disabled' do + include_context 'disabled repository' + + it_behaves_like '403 response' do + let(:request) { get api(route, current_user) } + end + end + end + + context 'when unauthenticated', 'and project is public' do + it_behaves_like 'repository branches' do + let(:project) { create(:project, :public, :repository) } + let(:current_user) { nil } + end + end + + context 'when unauthenticated', 'and project is private' do + it_behaves_like '404 response' do + let(:request) { get api(route) } + let(:message) { '404 Project Not Found' } + end + end + + context 'when authenticated', 'as a developer' do + it_behaves_like 'repository branches' do + let(:current_user) { user } + end + end + + context 'when authenticated', 'as a guest' do + it_behaves_like '403 response' do + let(:request) { get api(route, guest) } + end end end describe "GET /projects/:id/repository/branches/:branch" do - it "returns the branch information for a single branch" do - get api("/projects/#{project.id}/repository/branches/#{branch_name}", user) - expect(response).to have_http_status(200) + let(:route) { "/projects/#{project.id}/repository/branches/#{branch_name}" } - expect(json_response['name']).to eq(branch_name) - json_commit = json_response['commit'] - expect(json_commit['id']).to eq(branch_sha) - expect(json_commit).to have_key('short_id') - expect(json_commit).to have_key('title') - expect(json_commit).to have_key('message') - expect(json_commit).to have_key('author_name') - expect(json_commit).to have_key('author_email') - expect(json_commit).to have_key('authored_date') - expect(json_commit).to have_key('committer_name') - expect(json_commit).to have_key('committer_email') - expect(json_commit).to have_key('committed_date') - expect(json_commit).to have_key('parent_ids') - expect(json_response['merged']).to eq(false) - expect(json_response['protected']).to eq(false) - expect(json_response['developers_can_push']).to eq(false) - expect(json_response['developers_can_merge']).to eq(false) - end - - it "returns the branch information for a single branch with dots in the name" do - get api("/projects/#{project.id}/repository/branches/with.1.2.3", user) - - expect(response).to have_http_status(200) - expect(json_response['name']).to eq("with.1.2.3") - end - - context 'on a merged branch' do - it "returns the branch information for a single branch" do - get api("/projects/#{project.id}/repository/branches/merge-test", user) + shared_examples_for 'repository branch' do |merged: false| + it 'returns the repository branch' do + get api(route, current_user) expect(response).to have_http_status(200) - expect(json_response['name']).to eq('merge-test') - expect(json_response['merged']).to eq(true) + expect(json_response['name']).to eq(branch_name) + expect(json_response['merged']).to eq(merged) + expect(json_response['protected']).to eq(false) + expect(json_response['developers_can_push']).to eq(false) + expect(json_response['developers_can_merge']).to eq(false) + + json_commit = json_response['commit'] + expect(json_commit['id']).to eq(branch_sha) + expect(json_commit).to have_key('short_id') + expect(json_commit).to have_key('title') + expect(json_commit).to have_key('message') + expect(json_commit).to have_key('author_name') + expect(json_commit).to have_key('author_email') + expect(json_commit).to have_key('authored_date') + expect(json_commit).to have_key('committer_name') + expect(json_commit).to have_key('committer_email') + expect(json_commit).to have_key('committed_date') + expect(json_commit).to have_key('parent_ids') + end + + context 'when branch does not exist' do + let(:branch_name) { 'unknown' } + + it_behaves_like '404 response' do + let(:request) { get api(route, current_user) } + let(:message) { '404 Branch Not Found' } + end + end + + context 'when repository is disabled' do + include_context 'disabled repository' + + it_behaves_like '403 response' do + let(:request) { get api(route, current_user) } + end end end - it "returns a 403 error if guest" do - get api("/projects/#{project.id}/repository/branches", user2) - expect(response).to have_http_status(403) + context 'when unauthenticated', 'and project is public' do + it_behaves_like 'repository branch' do + let(:project) { create(:project, :public, :repository) } + let(:current_user) { nil } + end end - it "returns a 404 error if branch is not available" do - get api("/projects/#{project.id}/repository/branches/unknown", user) - expect(response).to have_http_status(404) + context 'when unauthenticated', 'and project is private' do + it_behaves_like '404 response' do + let(:request) { get api(route) } + let(:message) { '404 Project Not Found' } + end + end + + context 'when authenticated', 'as a developer' do + let(:current_user) { user } + it_behaves_like 'repository branch' + + context 'when branch contains a dot' do + let(:branch_name) { branch_with_dot.name } + let(:branch_sha) { project.commit('master').sha } + + it_behaves_like 'repository branch' + end + + context 'when branch is merged' do + let(:branch_name) { 'merge-test' } + let(:branch_sha) { project.commit('merge-test').sha } + + it_behaves_like 'repository branch', merged: true + end + end + + context 'when authenticated', 'as a guest' do + it_behaves_like '403 response' do + let(:request) { get api(route, guest) } + end end end @@ -93,10 +162,10 @@ describe API::Branches, api: true do end it "protects a single branch with dots in the name" do - put api("/projects/#{project.id}/repository/branches/with.1.2.3/protect", user) + put api("/projects/#{project.id}/repository/branches/#{branch_with_dot.name}/protect", user) expect(response).to have_http_status(200) - expect(json_response['name']).to eq("with.1.2.3") + expect(json_response['name']).to eq(branch_with_dot.name) expect(json_response['protected']).to eq(true) end @@ -234,7 +303,7 @@ describe API::Branches, api: true do end it "returns a 403 error if guest" do - put api("/projects/#{project.id}/repository/branches/#{branch_name}/protect", user2) + put api("/projects/#{project.id}/repository/branches/#{branch_name}/protect", guest) expect(response).to have_http_status(403) end end @@ -250,10 +319,10 @@ describe API::Branches, api: true do end it "update branches with dots in branch name" do - put api("/projects/#{project.id}/repository/branches/with.1.2.3/unprotect", user) + put api("/projects/#{project.id}/repository/branches/#{branch_with_dot.name}/unprotect", user) expect(response).to have_http_status(200) - expect(json_response['name']).to eq("with.1.2.3") + expect(json_response['name']).to eq(branch_with_dot.name) expect(json_response['protected']).to eq(false) end @@ -282,7 +351,7 @@ describe API::Branches, api: true do end it "denies for user without push access" do - post api("/projects/#{project.id}/repository/branches", user2), + post api("/projects/#{project.id}/repository/branches", guest), branch: branch_name, ref: branch_sha expect(response).to have_http_status(403) @@ -330,7 +399,7 @@ describe API::Branches, api: true do end it "removes a branch with dots in the branch name" do - delete api("/projects/#{project.id}/repository/branches/with.1.2.3", user) + delete api("/projects/#{project.id}/repository/branches/#{branch_with_dot.name}", user) expect(response).to have_http_status(204) end @@ -367,7 +436,7 @@ describe API::Branches, api: true do end it 'returns a 403 error if guest' do - delete api("/projects/#{project.id}/repository/merged_branches", user2) + delete api("/projects/#{project.id}/repository/merged_branches", guest) expect(response).to have_http_status(403) end end From bbf4d27a5c046f95b6fda109dcda109fd00298b1 Mon Sep 17 00:00:00 2001 From: Tomasz Maczukin Date: Wed, 15 Mar 2017 00:27:10 +0100 Subject: [PATCH 035/238] Make runner's veryfication working again In APIv1 we were using UpdateJob to verify if the runner exists. It was the only method that was using Runner's token and used in special way had no side effects (like scheduling a new job or unregisterring a Runner). In APIv4 we've change UpdateJob to use job's token as authentication credentials, and that way we've removed the only endpoint that could be used to verify if the Runner with a certain token exists in target GitLab installation. This commit adds `POST /api/v4/runners/verify` endpoint whose only responsibility is to respond if Runner with posted credentials exists or not. --- lib/api/runner.rb | 11 +++++++++++ spec/requests/api/runner_spec.rb | 28 ++++++++++++++++++++++++++++ 2 files changed, 39 insertions(+) diff --git a/lib/api/runner.rb b/lib/api/runner.rb index c700d2ef4a1..b80f7284735 100644 --- a/lib/api/runner.rb +++ b/lib/api/runner.rb @@ -47,6 +47,17 @@ module API authenticate_runner! Ci::Runner.find_by_token(params[:token]).destroy end + + desc 'Validates authentication credentials' do + http_codes [[200, 'Credentials are valid'], [403, 'Forbidden']] + end + params do + requires :token, type: String, desc: %q(Runner's authentication token) + end + post '/verify' do + authenticate_runner! + status 200 + end end resource :jobs do diff --git a/spec/requests/api/runner_spec.rb b/spec/requests/api/runner_spec.rb index 442b2df1952..2e0bdc08631 100644 --- a/spec/requests/api/runner_spec.rb +++ b/spec/requests/api/runner_spec.rb @@ -152,6 +152,34 @@ describe API::Runner do end end end + + describe 'POST /api/v4/runners/verify' do + let(:runner) { create(:ci_runner) } + + context 'when no token is provided' do + it 'returns 400 error' do + post api('/runners/verify') + + expect(response).to have_http_status :bad_request + end + end + + context 'when invalid token is provided' do + it 'returns 403 error' do + post api('/runners/verify'), token: 'invalid-token' + + expect(response).to have_http_status 403 + end + end + + context 'when valid token is provided' do + it 'deletes Runner' do + post api('/runners/verify'), token: runner.token + + expect(response).to have_http_status 200 + end + end + end end describe '/api/v4/jobs' do From e79ab1115bf6ab4776678a379f7d507455d867c0 Mon Sep 17 00:00:00 2001 From: Tomasz Maczukin Date: Thu, 16 Mar 2017 03:31:09 +0100 Subject: [PATCH 036/238] Remove legacy Runners support in /api/v4/jobs/request In Runner v1.3.0 we've started to send User-Agent header with Runner's version data. Since GitLab v8.12.0 we've started to use this header to check if used Runner's version supports 204 status code instead of 404 as a response when there is no jobs to execute by a Runner. In APIv4 (introduced in GitLab 9.0.0) will require Runner v9.0.0. And writing more accurately: GitLab Runner v9.0.0 will require GitLab at least 9.0.0. Because of such breaking change we are able to switch entirely to 204 response code and there is no need to do check of User-Agent. This commit removes useless code and complexity. --- lib/api/helpers/runner.rb | 8 -------- lib/api/runner.rb | 9 ++++++--- spec/requests/api/runner_spec.rb | 16 ++-------------- 3 files changed, 8 insertions(+), 25 deletions(-) diff --git a/lib/api/helpers/runner.rb b/lib/api/helpers/runner.rb index ec2bcaed929..74848a6e144 100644 --- a/lib/api/helpers/runner.rb +++ b/lib/api/helpers/runner.rb @@ -41,14 +41,6 @@ module API (Time.now - current_runner.contacted_at) >= contacted_at_max_age end - def job_not_found! - if headers['User-Agent'].to_s =~ /gitlab(-ci-multi)?-runner \d+\.\d+\.\d+(~beta\.\d+\.g[0-9a-f]+)? / - no_content! - else - not_found! - end - end - def validate_job!(job) not_found! unless job diff --git a/lib/api/runner.rb b/lib/api/runner.rb index b80f7284735..4c9db2c8716 100644 --- a/lib/api/runner.rb +++ b/lib/api/runner.rb @@ -63,6 +63,9 @@ module API resource :jobs do desc 'Request a job' do success Entities::JobRequest::Response + http_codes [[201, 'Job was scheduled'], + [204, 'No job for Runner'], + [403, 'Forbidden']] end params do requires :token, type: String, desc: %q(Runner's authentication token) @@ -71,13 +74,13 @@ module API end post '/request' do authenticate_runner! - not_found! unless current_runner.active? + no_content! unless current_runner.active? update_runner_info if current_runner.is_runner_queue_value_latest?(params[:last_update]) header 'X-GitLab-Last-Update', params[:last_update] Gitlab::Metrics.add_event(:build_not_found_cached) - return job_not_found! + return no_content! end new_update = current_runner.ensure_runner_queue_value @@ -91,7 +94,7 @@ module API else Gitlab::Metrics.add_event(:build_not_found) header 'X-GitLab-Last-Update', new_update - job_not_found! + no_content! end else # We received build that is invalid due to concurrency conflict diff --git a/spec/requests/api/runner_spec.rb b/spec/requests/api/runner_spec.rb index 2e0bdc08631..65949fc195e 100644 --- a/spec/requests/api/runner_spec.rb +++ b/spec/requests/api/runner_spec.rb @@ -248,18 +248,6 @@ describe API::Runner do it { expect(response).to have_http_status(204) } end end - - context "when runner doesn't send version in User-Agent" do - let(:user_agent) { 'Go-http-client/1.1' } - - it { expect(response).to have_http_status(404) } - end - - context "when runner doesn't have a User-Agent" do - let(:user_agent) { nil } - - it { expect(response).to have_http_status(404) } - end end context 'when no token is provided' do @@ -282,10 +270,10 @@ describe API::Runner do context 'when Runner is not active' do let(:runner) { create(:ci_runner, :inactive) } - it 'returns 404 error' do + it 'returns 204 error' do request_job - expect(response).to have_http_status 404 + expect(response).to have_http_status 204 end end From 9267a9b19d6c7e090c286e9d922c71fe64190a47 Mon Sep 17 00:00:00 2001 From: Tomasz Maczukin Date: Thu, 16 Mar 2017 05:18:37 +0100 Subject: [PATCH 037/238] Send token of depenent job Artifacts download for authorization is using a job token of job to which the artifact belongs. In APIv1 the token was sent with dependent jobs details and in APIv4 it was designed to also contain it. However I forgot about this parameter while working on `/api/v4/jobs/request` endpoint. This commit adds a missing parameter which is required for APIv4 to work properly. --- lib/api/entities.rb | 2 +- spec/requests/api/runner_spec.rb | 5 +++-- 2 files changed, 4 insertions(+), 3 deletions(-) diff --git a/lib/api/entities.rb b/lib/api/entities.rb index 0a12ee72d49..29c640ddb19 100644 --- a/lib/api/entities.rb +++ b/lib/api/entities.rb @@ -768,7 +768,7 @@ module API end class Dependency < Grape::Entity - expose :id, :name + expose :id, :name, :token expose :artifacts_file, using: ArtifactFile, if: ->(job, _) { job.artifacts? } end diff --git a/spec/requests/api/runner_spec.rb b/spec/requests/api/runner_spec.rb index 65949fc195e..76461aabd9a 100644 --- a/spec/requests/api/runner_spec.rb +++ b/spec/requests/api/runner_spec.rb @@ -417,7 +417,8 @@ describe API::Runner do end context 'when project and pipeline have multiple jobs' do - let!(:test_job) { create(:ci_build, pipeline: pipeline, name: 'deploy', stage: 'deploy', stage_idx: 1) } + let!(:job) { create(:ci_build_tag, pipeline: pipeline, token: 'job-token', name: 'spinach', stage: 'test', stage_idx: 0) } + let!(:test_job) { create(:ci_build, pipeline: pipeline, token: 'test-job-token', name: 'deploy', stage: 'deploy', stage_idx: 1) } before { job.success } @@ -427,7 +428,7 @@ describe API::Runner do expect(response).to have_http_status(201) expect(json_response['id']).to eq(test_job.id) expect(json_response['dependencies'].count).to eq(1) - expect(json_response['dependencies'][0]).to include('id' => job.id, 'name' => 'spinach') + expect(json_response['dependencies'][0]).to include('id' => job.id, 'name' => 'spinach', 'token' => job.token) end end From c191c1103b37903f2293c2a662cdc616228b9eb7 Mon Sep 17 00:00:00 2001 From: Tomasz Maczukin Date: Thu, 16 Mar 2017 14:45:05 +0100 Subject: [PATCH 038/238] Send only defined dependencies In APIv1 we've been sending all jobs from previous stages and a `dependencies` list with names of jobs that user want to download artifacts from. This was selected on Runners side. In APIv1 we've planned to send only jobs that were defined (if any; and all previous jobs by default). However I've missed the fact that it was Runner who selected jobs, not GitLab. And now current version of APIV4 sends all jobs everytime. This commit fixes this. If user will define `dependencies` in his job, then GitLab will send only selected jobs. --- app/models/ci/build.rb | 15 +++++++++++++ lib/api/entities.rb | 2 +- spec/requests/api/runner_spec.rb | 37 ++++++++++++++++++++++++++++---- 3 files changed, 49 insertions(+), 5 deletions(-) diff --git a/app/models/ci/build.rb b/app/models/ci/build.rb index 53fc0d87823..90a195dc048 100644 --- a/app/models/ci/build.rb +++ b/app/models/ci/build.rb @@ -539,6 +539,21 @@ module Ci Gitlab::Ci::Build::Credentials::Factory.new(self).create! end + def dependencies + depended_jobs = depends_on_builds + + return depended_jobs unless options[:dependencies] && !options[:dependencies].empty? + + selected = [] + depended_jobs.each do |job| + options[:dependencies].each do |job_name| + selected << job if job.name == job_name + end + end + + selected + end + private def update_artifacts_size diff --git a/lib/api/entities.rb b/lib/api/entities.rb index 29c640ddb19..5954aea8041 100644 --- a/lib/api/entities.rb +++ b/lib/api/entities.rb @@ -796,7 +796,7 @@ module API expose :artifacts, using: Artifacts expose :cache, using: Cache expose :credentials, using: Credentials - expose :depends_on_builds, as: :dependencies, using: Dependency + expose :dependencies, using: Dependency end end end diff --git a/spec/requests/api/runner_spec.rb b/spec/requests/api/runner_spec.rb index 76461aabd9a..d4d6ac51c03 100644 --- a/spec/requests/api/runner_spec.rb +++ b/spec/requests/api/runner_spec.rb @@ -417,10 +417,39 @@ describe API::Runner do end context 'when project and pipeline have multiple jobs' do - let!(:job) { create(:ci_build_tag, pipeline: pipeline, token: 'job-token', name: 'spinach', stage: 'test', stage_idx: 0) } - let!(:test_job) { create(:ci_build, pipeline: pipeline, token: 'test-job-token', name: 'deploy', stage: 'deploy', stage_idx: 1) } + let!(:job) { create(:ci_build_tag, pipeline: pipeline, name: 'spinach', stage: 'test', stage_idx: 0) } + let!(:job2) { create(:ci_build_tag, pipeline: pipeline, name: 'rubocop', stage: 'test', stage_idx: 0) } + let!(:test_job) { create(:ci_build, pipeline: pipeline, name: 'deploy', stage: 'deploy', stage_idx: 1) } - before { job.success } + before do + job.success + job2.success + end + + it 'returns dependent jobs' do + request_job + + expect(response).to have_http_status(201) + expect(json_response['id']).to eq(test_job.id) + expect(json_response['dependencies'].count).to eq(2) + expect(json_response['dependencies']).to include({ 'id' => job.id, 'name' => job.name, 'token' => job.token }, + { 'id' => job2.id, 'name' => job2.name, 'token' => job2.token }) + end + end + + context 'when explicit dependencies are defined' do + let!(:job) { create(:ci_build_tag, pipeline: pipeline, name: 'spinach', stage: 'test', stage_idx: 0) } + let!(:job2) { create(:ci_build_tag, pipeline: pipeline, name: 'rubocop', stage: 'test', stage_idx: 0) } + let!(:test_job) do + create(:ci_build, pipeline: pipeline, token: 'test-job-token', name: 'deploy', + stage: 'deploy', stage_idx: 1, + options: { dependencies: [job2.name] }) + end + + before do + job.success + job2.success + end it 'returns dependent jobs' do request_job @@ -428,7 +457,7 @@ describe API::Runner do expect(response).to have_http_status(201) expect(json_response['id']).to eq(test_job.id) expect(json_response['dependencies'].count).to eq(1) - expect(json_response['dependencies'][0]).to include('id' => job.id, 'name' => 'spinach', 'token' => job.token) + expect(json_response['dependencies'][0]).to include('id' => job2.id, 'name' => job2.name, 'token' => job2.token) end end From 45f1975d7e3ae0f26e59d5e7e08c261575147ec5 Mon Sep 17 00:00:00 2001 From: Tomasz Maczukin Date: Sun, 19 Mar 2017 00:35:17 +0100 Subject: [PATCH 039/238] Simplify Ci::Build#dependencies method --- app/models/ci/build.rb | 11 +++-------- 1 file changed, 3 insertions(+), 8 deletions(-) diff --git a/app/models/ci/build.rb b/app/models/ci/build.rb index 90a195dc048..ad0be70c32a 100644 --- a/app/models/ci/build.rb +++ b/app/models/ci/build.rb @@ -542,16 +542,11 @@ module Ci def dependencies depended_jobs = depends_on_builds - return depended_jobs unless options[:dependencies] && !options[:dependencies].empty? + return depended_jobs unless options[:dependencies].present? - selected = [] - depended_jobs.each do |job| - options[:dependencies].each do |job_name| - selected << job if job.name == job_name - end + depended_jobs.select do |job| + options[:dependencies].include?(job.name) end - - selected end private From 37b36b469381089c3ed24ed6b477a8a8cc0e6b59 Mon Sep 17 00:00:00 2001 From: Tomasz Maczukin Date: Sun, 19 Mar 2017 00:43:35 +0100 Subject: [PATCH 040/238] Update example's name in /runners/verify tests --- spec/requests/api/runner_spec.rb | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/spec/requests/api/runner_spec.rb b/spec/requests/api/runner_spec.rb index d4d6ac51c03..044b989e5ba 100644 --- a/spec/requests/api/runner_spec.rb +++ b/spec/requests/api/runner_spec.rb @@ -173,7 +173,7 @@ describe API::Runner do end context 'when valid token is provided' do - it 'deletes Runner' do + it 'verifies Runner credentials' do post api('/runners/verify'), token: runner.token expect(response).to have_http_status 200 From 6006aaf5e889b0d7d81923cbc9056b89cdc7c4d6 Mon Sep 17 00:00:00 2001 From: Jacob Vosmaer Date: Thu, 16 Mar 2017 17:15:01 +0100 Subject: [PATCH 041/238] Use google-protobuf 3.2.0.2 --- Gemfile.lock | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Gemfile.lock b/Gemfile.lock index 734911baf3f..043ca4f8800 100644 --- a/Gemfile.lock +++ b/Gemfile.lock @@ -304,7 +304,7 @@ GEM multi_json (~> 1.10) retriable (~> 1.4) signet (~> 0.6) - google-protobuf (3.2.0) + google-protobuf (3.2.0.2) googleauth (0.5.1) faraday (~> 0.9) jwt (~> 1.4) From ef86353ed9db3b8873a5d10e0974ab1430550654 Mon Sep 17 00:00:00 2001 From: Phil Hughes Date: Mon, 20 Mar 2017 11:05:32 +0000 Subject: [PATCH 042/238] Fixed source branch name not being in new merge request dropdown toggle Closes #29660 --- app/views/projects/merge_requests/_new_compare.html.haml | 2 +- spec/features/merge_requests/create_new_mr_spec.rb | 6 ++++++ 2 files changed, 7 insertions(+), 1 deletion(-) diff --git a/app/views/projects/merge_requests/_new_compare.html.haml b/app/views/projects/merge_requests/_new_compare.html.haml index ad14b4e583e..8d134aaac67 100644 --- a/app/views/projects/merge_requests/_new_compare.html.haml +++ b/app/views/projects/merge_requests/_new_compare.html.haml @@ -21,7 +21,7 @@ selected: f.object.source_project_id .merge-request-select.dropdown = f.hidden_field :source_branch - = dropdown_toggle local_assigns.fetch(f.object.source_branch, "Select source branch"), { toggle: "dropdown", field_name: "#{f.object_name}[source_branch]" }, { toggle_class: "js-compare-dropdown js-source-branch" } + = dropdown_toggle f.object.source_branch || "Select source branch", { toggle: "dropdown", field_name: "#{f.object_name}[source_branch]" }, { toggle_class: "js-compare-dropdown js-source-branch" } .dropdown-menu.dropdown-menu-selectable.dropdown-source-branch = dropdown_title("Select source branch") = dropdown_filter("Search branches") diff --git a/spec/features/merge_requests/create_new_mr_spec.rb b/spec/features/merge_requests/create_new_mr_spec.rb index 0832a3656a8..8cc0996acab 100644 --- a/spec/features/merge_requests/create_new_mr_spec.rb +++ b/spec/features/merge_requests/create_new_mr_spec.rb @@ -46,6 +46,12 @@ feature 'Create New Merge Request', feature: true, js: true do end end + it 'populates source branch button' do + visit new_namespace_project_merge_request_path(project.namespace, project, change_branches: true, merge_request: { target_branch: 'master', source_branch: 'fix' }) + + expect(find('.js-source-branch')).to have_content('fix') + end + it 'allows to change the diff view' do visit new_namespace_project_merge_request_path(project.namespace, project, merge_request: { target_branch: 'master', source_branch: 'fix' }) From bf41a2e7525cc952686623b508023c169dbdfe2d Mon Sep 17 00:00:00 2001 From: Jarka Kadlecova Date: Mon, 20 Mar 2017 09:20:46 +0100 Subject: [PATCH 043/238] Todos performance: Include associations in Finder --- app/controllers/dashboard/todos_controller.rb | 2 +- app/finders/todos_finder.rb | 12 +++++++ app/helpers/todos_helper.rb | 10 ++++-- app/models/merge_request.rb | 5 +-- spec/factories/merge_requests.rb | 1 + spec/helpers/todos_helper_spec.rb | 34 +++++++++++++++++++ 6 files changed, 56 insertions(+), 8 deletions(-) diff --git a/app/controllers/dashboard/todos_controller.rb b/app/controllers/dashboard/todos_controller.rb index 498690e8f11..096de8032ae 100644 --- a/app/controllers/dashboard/todos_controller.rb +++ b/app/controllers/dashboard/todos_controller.rb @@ -51,7 +51,7 @@ class Dashboard::TodosController < Dashboard::ApplicationController private def find_todos - @todos ||= TodosFinder.new(current_user, params).execute + @todos ||= TodosFinder.new(current_user, params.merge(include_associations: true)).execute end def todos_counts diff --git a/app/finders/todos_finder.rb b/app/finders/todos_finder.rb index b7f091f334d..13d33a1c31b 100644 --- a/app/finders/todos_finder.rb +++ b/app/finders/todos_finder.rb @@ -24,6 +24,7 @@ class TodosFinder def execute items = current_user.todos + items = include_associations(items) items = by_action_id(items) items = by_action(items) items = by_author(items) @@ -38,6 +39,17 @@ class TodosFinder private + def include_associations(items) + return items unless params[:include_associations] + + items.includes( + [ + target: { project: [:route, namespace: :route] }, + author: { namespace: :route }, + ] + ) + end + def action_id? action_id.present? && Todo::ACTION_NAMES.has_key?(action_id.to_i) end diff --git a/app/helpers/todos_helper.rb b/app/helpers/todos_helper.rb index 4f5adf623f2..847a8fdfca6 100644 --- a/app/helpers/todos_helper.rb +++ b/app/helpers/todos_helper.rb @@ -39,9 +39,13 @@ module TodosHelper namespace_project_commit_path(todo.project.namespace.becomes(Namespace), todo.project, todo.target, anchor: anchor) else - path = [todo.project.namespace.becomes(Namespace), todo.project, todo.target] - - path.unshift(:pipelines) if todo.build_failed? + if todo.build_failed? + # associated namespace and route would be loaded from the db again if todo.project was used + project = todo.target.project + path = [:pipelines, project.namespace.becomes(Namespace), project, todo.target] + else + path = [todo.target] + end polymorphic_path(path, anchor: anchor) end diff --git a/app/models/merge_request.rb b/app/models/merge_request.rb index 4759829a15c..cef8ad76b07 100644 --- a/app/models/merge_request.rb +++ b/app/models/merge_request.rb @@ -7,6 +7,7 @@ class MergeRequest < ActiveRecord::Base belongs_to :target_project, class_name: "Project" belongs_to :source_project, class_name: "Project" + belongs_to :project, foreign_key: :target_project_id belongs_to :merge_user, class_name: "User" has_many :merge_request_diffs, dependent: :destroy @@ -540,10 +541,6 @@ class MergeRequest < ActiveRecord::Base target_project != source_project end - def project - target_project - end - # If the merge request closes any issues, save this information in the # `MergeRequestsClosingIssues` model. This is a performance optimization. # Calculating this information for a number of merge requests requires diff --git a/spec/factories/merge_requests.rb b/spec/factories/merge_requests.rb index ae0bbbd6aeb..21487541507 100644 --- a/spec/factories/merge_requests.rb +++ b/spec/factories/merge_requests.rb @@ -4,6 +4,7 @@ FactoryGirl.define do author association :source_project, :repository, factory: :project target_project { source_project } + project { target_project } # $ git log --pretty=oneline feature..master # 5937ac0a7beb003549fc5fd26fc247adbce4a52e Add submodule from gitlab.com diff --git a/spec/helpers/todos_helper_spec.rb b/spec/helpers/todos_helper_spec.rb index 50060a0925d..21e0e74e008 100644 --- a/spec/helpers/todos_helper_spec.rb +++ b/spec/helpers/todos_helper_spec.rb @@ -1,6 +1,40 @@ require "spec_helper" describe TodosHelper do + include GitlabRoutingHelper + + describe '#todo_target_path' do + let(:project) { create(:project) } + let(:merge_request) { create(:merge_request, target_project: project, source_project: project) } + let(:issue) { create(:issue, project: project) } + let(:note) { create(:note_on_issue, noteable: issue, project: project) } + + let(:mr_todo) { build(:todo, project: project, target: merge_request) } + let(:issue_todo) { build(:todo, project: project, target: issue) } + let(:note_todo) { build(:todo, project: project, target: issue, note: note) } + let(:build_failed_todo) { build(:todo, :build_failed, project: project, target: merge_request) } + + it 'returns correct path to the todo MR' do + expect(todo_target_path(mr_todo)). + to eq("/#{project.full_path}/merge_requests/#{merge_request.iid}") + end + + it 'returns correct path to the todo issue' do + expect(todo_target_path(issue_todo)). + to eq("/#{project.full_path}/issues/#{issue.iid}") + end + + it 'returns correct path to the todo note' do + expect(todo_target_path(note_todo)). + to eq("/#{project.full_path}/issues/#{issue.iid}#note_#{note.id}") + end + + it 'returns correct path to build_todo MR when pipeline failed' do + expect(todo_target_path(build_failed_todo)). + to eq("/#{project.full_path}/merge_requests/#{merge_request.iid}/pipelines") + end + end + describe '#todo_projects_options' do let(:projects) { create_list(:empty_project, 3) } let(:user) { create(:user) } From 67603d04ca2e4123b7018e240ddc87f0eda99a59 Mon Sep 17 00:00:00 2001 From: winniehell Date: Wed, 8 Mar 2017 20:39:43 +0100 Subject: [PATCH 044/238] Simplify trigger_docs build job for CE and EE (!9820) --- .gitlab-ci.yml | 3 ++- changelogs/unreleased/simplify-docs-trigger.yml | 4 ++++ 2 files changed, 6 insertions(+), 1 deletion(-) create mode 100644 changelogs/unreleased/simplify-docs-trigger.yml diff --git a/.gitlab-ci.yml b/.gitlab-ci.yml index 080d8cd6c7f..03292302a5b 100644 --- a/.gitlab-ci.yml +++ b/.gitlab-ci.yml @@ -389,9 +389,10 @@ trigger_docs: cache: {} artifacts: {} script: - - "curl -X POST -F token=${DOCS_TRIGGER_TOKEN} -F ref=master -F variables[PROJECT]=ce https://gitlab.com/api/v3/projects/1794617/trigger/builds" + - "curl -X POST -F token=${DOCS_TRIGGER_TOKEN} -F ref=master -F variables[PROJECT]=${CI_PROJECT_NAME} https://gitlab.com/api/v3/projects/1794617/trigger/builds" only: - master@gitlab-org/gitlab-ce + - master@gitlab-org/gitlab-ee # Notify slack in the end notify:slack: diff --git a/changelogs/unreleased/simplify-docs-trigger.yml b/changelogs/unreleased/simplify-docs-trigger.yml new file mode 100644 index 00000000000..062626359ef --- /dev/null +++ b/changelogs/unreleased/simplify-docs-trigger.yml @@ -0,0 +1,4 @@ +--- +title: Simplify trigger_docs build job for CE and EE +merge_request: 9820 +author: winniehell From acbb8c0ff0103b2dc4138384b7be2d51bdf7e337 Mon Sep 17 00:00:00 2001 From: winniehell Date: Thu, 9 Mar 2017 21:52:47 +0100 Subject: [PATCH 045/238] Fail trigger_docs if triggering build was not successful --- .gitlab-ci.yml | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/.gitlab-ci.yml b/.gitlab-ci.yml index 03292302a5b..3cea727f811 100644 --- a/.gitlab-ci.yml +++ b/.gitlab-ci.yml @@ -389,7 +389,8 @@ trigger_docs: cache: {} artifacts: {} script: - - "curl -X POST -F token=${DOCS_TRIGGER_TOKEN} -F ref=master -F variables[PROJECT]=${CI_PROJECT_NAME} https://gitlab.com/api/v3/projects/1794617/trigger/builds" + - "HTTP_STATUS=$(curl -X POST -F token=${DOCS_TRIGGER_TOKEN} -F ref=master -F variables[PROJECT]=${CI_PROJECT_NAME} --silent --output curl.log --write-out '%{http_code}' https://gitlab.com/api/v3/projects/1794617/trigger/builds)" + - if [ "${HTTP_STATUS}" -ne "201" ]; then echo "Error ${HTTP_STATUS}"; cat curl.log; echo; exit 1; fi only: - master@gitlab-org/gitlab-ce - master@gitlab-org/gitlab-ee From de518e124746e922fc19541fd8dd6e2c0a36841e Mon Sep 17 00:00:00 2001 From: George Andrinopoulos Date: Mon, 20 Mar 2017 12:49:31 +0000 Subject: [PATCH 046/238] Allow dot in branch name in trigger/builds endpoint in API --- ...8-fix-trigger-webhook-for-ref-with-dot.yml | 4 +++ lib/api/triggers.rb | 2 +- lib/api/v3/triggers.rb | 2 +- spec/requests/api/triggers_spec.rb | 30 ++++++++++++++----- spec/requests/api/v3/triggers_spec.rb | 28 ++++++++++++----- 5 files changed, 49 insertions(+), 17 deletions(-) create mode 100644 changelogs/unreleased/29438-fix-trigger-webhook-for-ref-with-dot.yml diff --git a/changelogs/unreleased/29438-fix-trigger-webhook-for-ref-with-dot.yml b/changelogs/unreleased/29438-fix-trigger-webhook-for-ref-with-dot.yml new file mode 100644 index 00000000000..61ffb64fa8f --- /dev/null +++ b/changelogs/unreleased/29438-fix-trigger-webhook-for-ref-with-dot.yml @@ -0,0 +1,4 @@ +--- +title: Fix trigger webhook for ref with a dot +merge_request: 10001 +author: George Andrinopoulos diff --git a/lib/api/triggers.rb b/lib/api/triggers.rb index aa3c9a06ed5..a9f2ca2608e 100644 --- a/lib/api/triggers.rb +++ b/lib/api/triggers.rb @@ -14,7 +14,7 @@ module API requires :token, type: String, desc: 'The unique token of trigger' optional :variables, type: Hash, desc: 'The list of variables to be injected into build' end - post ":id/(ref/:ref/)trigger/pipeline" do + post ":id/(ref/:ref/)trigger/pipeline", requirements: { ref: /.+/ } do project = find_project(params[:id]) trigger = Ci::Trigger.find_by_token(params[:token].to_s) not_found! unless project && trigger diff --git a/lib/api/v3/triggers.rb b/lib/api/v3/triggers.rb index b46639a2205..a23d6b6b48c 100644 --- a/lib/api/v3/triggers.rb +++ b/lib/api/v3/triggers.rb @@ -15,7 +15,7 @@ module API requires :token, type: String, desc: 'The unique token of trigger' optional :variables, type: Hash, desc: 'The list of variables to be injected into build' end - post ":id/(ref/:ref/)trigger/builds" do + post ":id/(ref/:ref/)trigger/builds", requirements: { ref: /.+/ } do project = find_project(params[:id]) trigger = Ci::Trigger.find_by_token(params[:token].to_s) not_found! unless project && trigger diff --git a/spec/requests/api/triggers_spec.rb b/spec/requests/api/triggers_spec.rb index 424c02932ab..d93a734f5b6 100644 --- a/spec/requests/api/triggers_spec.rb +++ b/spec/requests/api/triggers_spec.rb @@ -59,14 +59,6 @@ describe API::Triggers do expect(pipeline.builds.size).to eq(5) end - it 'creates builds on webhook from other gitlab repository and branch' do - expect do - post api("/projects/#{project.id}/ref/master/trigger/pipeline?token=#{trigger_token}"), { ref: 'refs/heads/other-branch' } - end.to change(project.builds, :count).by(5) - - expect(response).to have_http_status(201) - end - it 'returns bad request with no pipeline created if there\'s no commit for that ref' do post api("/projects/#{project.id}/trigger/pipeline"), options.merge(ref: 'other-branch') @@ -101,6 +93,28 @@ describe API::Triggers do end end end + + context 'when triggering a pipeline from a trigger token' do + it 'creates builds from the ref given in the URL, not in the body' do + expect do + post api("/projects/#{project.id}/ref/master/trigger/pipeline?token=#{trigger_token}"), { ref: 'refs/heads/other-branch' } + end.to change(project.builds, :count).by(5) + + expect(response).to have_http_status(201) + end + + context 'when ref contains a dot' do + it 'creates builds from the ref given in the URL, not in the body' do + project.repository.create_file(user, '.gitlab/gitlabhq/new_feature.md', 'something valid', message: 'new_feature', branch_name: 'v.1-branch') + + expect do + post api("/projects/#{project.id}/ref/v.1-branch/trigger/pipeline?token=#{trigger_token}"), { ref: 'refs/heads/other-branch' } + end.to change(project.builds, :count).by(4) + + expect(response).to have_http_status(201) + end + end + end end describe 'GET /projects/:id/triggers' do diff --git a/spec/requests/api/v3/triggers_spec.rb b/spec/requests/api/v3/triggers_spec.rb index 4819269d69f..9233e9621bf 100644 --- a/spec/requests/api/v3/triggers_spec.rb +++ b/spec/requests/api/v3/triggers_spec.rb @@ -51,13 +51,6 @@ describe API::V3::Triggers do expect(pipeline.builds.size).to eq(5) end - it 'creates builds on webhook from other gitlab repository and branch' do - expect do - post v3_api("/projects/#{project.id}/ref/master/trigger/builds?token=#{trigger_token}"), { ref: 'refs/heads/other-branch' } - end.to change(project.builds, :count).by(5) - expect(response).to have_http_status(201) - end - it 'returns bad request with no builds created if there\'s no commit for that ref' do post v3_api("/projects/#{project.id}/trigger/builds"), options.merge(ref: 'other-branch') expect(response).to have_http_status(400) @@ -89,6 +82,27 @@ describe API::V3::Triggers do end end end + + context 'when triggering a pipeline from a trigger token' do + it 'creates builds from the ref given in the URL, not in the body' do + expect do + post v3_api("/projects/#{project.id}/ref/master/trigger/builds?token=#{trigger_token}"), { ref: 'refs/heads/other-branch' } + end.to change(project.builds, :count).by(5) + expect(response).to have_http_status(201) + end + + context 'when ref contains a dot' do + it 'creates builds from the ref given in the URL, not in the body' do + project.repository.create_file(user, '.gitlab/gitlabhq/new_feature.md', 'something valid', message: 'new_feature', branch_name: 'v.1-branch') + + expect do + post v3_api("/projects/#{project.id}/ref/v.1-branch/trigger/builds?token=#{trigger_token}"), { ref: 'refs/heads/other-branch' } + end.to change(project.builds, :count).by(4) + + expect(response).to have_http_status(201) + end + end + end end describe 'GET /projects/:id/triggers' do From 6181b35409ad1ef6561b5487323adb9ba6b0d3e7 Mon Sep 17 00:00:00 2001 From: Nur Rony Date: Sat, 25 Feb 2017 13:33:15 +0600 Subject: [PATCH 047/238] fixes large text tooltip in diff file name --- app/assets/stylesheets/framework/common.scss | 6 ++++++ .../28499-fix-large-text-tooltip-in-diff-file-name.yml | 4 ++++ 2 files changed, 10 insertions(+) create mode 100644 changelogs/unreleased/28499-fix-large-text-tooltip-in-diff-file-name.yml diff --git a/app/assets/stylesheets/framework/common.scss b/app/assets/stylesheets/framework/common.scss index a4b38723bbd..2c33b235980 100644 --- a/app/assets/stylesheets/framework/common.scss +++ b/app/assets/stylesheets/framework/common.scss @@ -429,3 +429,9 @@ table { @include str-truncated(100%); } } + +.tooltip { + .tooltip-inner { + word-wrap: break-word; + } +} diff --git a/changelogs/unreleased/28499-fix-large-text-tooltip-in-diff-file-name.yml b/changelogs/unreleased/28499-fix-large-text-tooltip-in-diff-file-name.yml new file mode 100644 index 00000000000..660a881e094 --- /dev/null +++ b/changelogs/unreleased/28499-fix-large-text-tooltip-in-diff-file-name.yml @@ -0,0 +1,4 @@ +--- +title: Fixes large file name tooltip cutoff in diff header +merge_request: 9529 +author: From 04f62ac235f9b5e8b33ff2429cec3376a7608738 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?R=C3=A9my=20Coutable?= Date: Mon, 20 Mar 2017 14:47:50 +0100 Subject: [PATCH 048/238] Remove useless options from db/fixtures/development/17_cycle_analytics.rb MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: Rémy Coutable --- db/fixtures/development/17_cycle_analytics.rb | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/db/fixtures/development/17_cycle_analytics.rb b/db/fixtures/development/17_cycle_analytics.rb index aea0a72b633..4bc735916c1 100644 --- a/db/fixtures/development/17_cycle_analytics.rb +++ b/db/fixtures/development/17_cycle_analytics.rb @@ -155,7 +155,7 @@ class Gitlab::Seeder::CycleAnalytics issue.project.repository.add_branch(@user, branch_name, 'master') - commit_sha = issue.project.repository.create_file(@user, filename, "content", options, message: "Commit for ##{issue.iid}", branch_name: branch_name) + commit_sha = issue.project.repository.create_file(@user, filename, "content", message: "Commit for ##{issue.iid}", branch_name: branch_name) issue.project.repository.commit(commit_sha) GitPushService.new(issue.project, From d29f90b54c11620b0b1879fceb1372300be00d8e Mon Sep 17 00:00:00 2001 From: Adam Niedzielski Date: Mon, 20 Mar 2017 13:49:05 +0000 Subject: [PATCH 049/238] Update deploy keys documentation --- doc/ssh/README.md | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/doc/ssh/README.md b/doc/ssh/README.md index 678f5199b02..cf28f1a2eca 100644 --- a/doc/ssh/README.md +++ b/doc/ssh/README.md @@ -170,12 +170,12 @@ Integration (CI) server. By using deploy keys, you don't have to setup a 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 +project settings under the section 'Repository'. Specify a title for the new +deploy key and paste a public SSH key. After this, the machine that uses 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. +You can't add the same deploy key twice using the form. If you want to add the same key to another project, please enable it in the list that says 'Deploy keys from projects available to you'. All the deploy keys of all the projects you have access to are available. This project From 2dd9b8a38ad545c98355115589a9060b93de0b03 Mon Sep 17 00:00:00 2001 From: Dongqing Hu Date: Mon, 20 Mar 2017 13:53:23 +0000 Subject: [PATCH 050/238] Fix Project Wiki update --- app/controllers/projects/wikis_controller.rb | 3 +- app/helpers/nav_helper.rb | 1 + app/models/wiki_page.rb | 8 +++-- .../29405-fix-project-wiki-update.yml | 4 +++ .../wiki/user_updates_wiki_page_spec.rb | 29 ++++++++++++++----- 5 files changed, 34 insertions(+), 11 deletions(-) create mode 100644 changelogs/unreleased/29405-fix-project-wiki-update.yml diff --git a/app/controllers/projects/wikis_controller.rb b/app/controllers/projects/wikis_controller.rb index 8b6c83d4fed..f210f7e61d2 100644 --- a/app/controllers/projects/wikis_controller.rb +++ b/app/controllers/projects/wikis_controller.rb @@ -45,8 +45,9 @@ class Projects::WikisController < Projects::ApplicationController return render('empty') unless can?(current_user, :create_wiki, @project) @page = @project_wiki.find_page(params[:id]) + @page = WikiPages::UpdateService.new(@project, current_user, wiki_params).execute(@page) - if @page = WikiPages::UpdateService.new(@project, current_user, wiki_params).execute(@page) + if @page.valid? redirect_to( namespace_project_wiki_path(@project.namespace, @project, @page), notice: 'Wiki was successfully updated.' diff --git a/app/helpers/nav_helper.rb b/app/helpers/nav_helper.rb index c1523b4dabf..a8f167cbff2 100644 --- a/app/helpers/nav_helper.rb +++ b/app/helpers/nav_helper.rb @@ -16,6 +16,7 @@ module NavHelper "page-gutter build-sidebar right-sidebar-expanded" elsif current_path?('wikis#show') || current_path?('wikis#edit') || + current_path?('wikis#update') || current_path?('wikis#history') || current_path?('wikis#git_access') "page-gutter wiki-sidebar right-sidebar-expanded" diff --git a/app/models/wiki_page.rb b/app/models/wiki_page.rb index 465c4d903ac..c771c22f46a 100644 --- a/app/models/wiki_page.rb +++ b/app/models/wiki_page.rb @@ -155,7 +155,7 @@ class WikiPage end # Returns boolean True or False if this instance - # has been fully saved to disk or not. + # has been fully created on disk or not. def persisted? @persisted == true end @@ -226,6 +226,8 @@ class WikiPage end def save(method, *args) + saved = false + project_wiki = wiki if valid? && project_wiki.send(method, *args) @@ -243,10 +245,10 @@ class WikiPage set_attributes @persisted = true + saved = true else errors.add(:base, project_wiki.error_message) if project_wiki.error_message - @persisted = false end - @persisted + saved end end diff --git a/changelogs/unreleased/29405-fix-project-wiki-update.yml b/changelogs/unreleased/29405-fix-project-wiki-update.yml new file mode 100644 index 00000000000..85be36f7902 --- /dev/null +++ b/changelogs/unreleased/29405-fix-project-wiki-update.yml @@ -0,0 +1,4 @@ +--- +title: Fix Project Wiki update +merge_request: 9990 +author: Dongqing Hu diff --git a/spec/features/projects/wiki/user_updates_wiki_page_spec.rb b/spec/features/projects/wiki/user_updates_wiki_page_spec.rb index f842d14fa96..aedc0333cb9 100644 --- a/spec/features/projects/wiki/user_updates_wiki_page_spec.rb +++ b/spec/features/projects/wiki/user_updates_wiki_page_spec.rb @@ -15,15 +15,30 @@ feature 'Projects > Wiki > User updates wiki page', feature: true do context 'in the user namespace' do let(:project) { create(:project, namespace: user.namespace) } - scenario 'the home page' do - click_link 'Edit' + context 'the home page' do + scenario 'success when the wiki content is not empty' do + click_link 'Edit' - fill_in :wiki_content, with: 'My awesome wiki!' - click_button 'Save changes' + fill_in :wiki_content, with: 'My awesome wiki!' + click_button 'Save changes' - expect(page).to have_content('Home') - expect(page).to have_content("Last edited by #{user.name}") - expect(page).to have_content('My awesome wiki!') + expect(page).to have_content('Home') + expect(page).to have_content("Last edited by #{user.name}") + expect(page).to have_content('My awesome wiki!') + end + + scenario 'failure when the wiki content is empty' do + click_link 'Edit' + + fill_in :wiki_content, with: '' + click_button 'Save changes' + + expect(page).to have_selector('.wiki-form') + expect(page).to have_content('Edit Page') + expect(page).to have_content('The form contains the following error:') + expect(page).to have_content('Content can\'t be blank') + expect(find('textarea#wiki_content').value).to eq '' + end end end From aea78ded3408a7f68f226049389d52abe8bb087f Mon Sep 17 00:00:00 2001 From: Sam Rose Date: Mon, 6 Mar 2017 15:18:39 -0500 Subject: [PATCH 051/238] Redirect to signin when New Issue for not logged in --- app/controllers/projects/issues_controller.rb | 2 + app/views/projects/issues/index.html.haml | 17 +++--- app/views/projects/issues/show.html.haml | 57 +++++++++---------- .../shared/empty_states/_issues.html.haml | 5 +- .../projects/issues_controller_spec.rb | 11 ++++ spec/features/issues_spec.rb | 20 ++++++- 6 files changed, 69 insertions(+), 43 deletions(-) diff --git a/app/controllers/projects/issues_controller.rb b/app/controllers/projects/issues_controller.rb index 1151555b8fa..549c192f218 100644 --- a/app/controllers/projects/issues_controller.rb +++ b/app/controllers/projects/issues_controller.rb @@ -6,6 +6,8 @@ class Projects::IssuesController < Projects::ApplicationController include IssuableCollections include SpammableActions + prepend_before_action :authenticate_user!, only: [:new] + before_action :redirect_to_external_issue_tracker, only: [:index, :new] before_action :module_enabled before_action :issue, only: [:edit, :update, :show, :referenced_merge_requests, diff --git a/app/views/projects/issues/index.html.haml b/app/views/projects/issues/index.html.haml index 7b7d7b1e00e..f3a429d12d9 100644 --- a/app/views/projects/issues/index.html.haml +++ b/app/views/projects/issues/index.html.haml @@ -19,15 +19,14 @@ .nav-controls = 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, - issue: { assignee_id: issues_finder.assignee.try(:id), - milestone_id: issues_finder.milestones.first.try(:id) }), - class: "btn btn-new", - title: "New Issue", - id: "new_issue_link" do - New Issue + = link_to new_namespace_project_issue_path(@project.namespace, + @project, + issue: { assignee_id: issues_finder.assignee.try(:id), + milestone_id: issues_finder.milestones.first.try(:id) }), + class: "btn btn-new", + title: "New Issue", + id: "new_issue_link" do + New Issue = render 'shared/issuable/search_bar', type: :issues .issues-holder diff --git a/app/views/projects/issues/show.html.haml b/app/views/projects/issues/show.html.haml index d39f36e94c7..6ac05bf3afe 100644 --- a/app/views/projects/issues/show.html.haml +++ b/app/views/projects/issues/show.html.haml @@ -20,37 +20,34 @@ = confidential_icon(@issue) = issuable_meta(@issue, @project, "Issue") - - if can?(current_user, :create_issue, @project) || can?(current_user, :update_issue, @issue) - .issuable-actions - .clearfix.issue-btn-group.dropdown - %button.btn.btn-default.pull-left.hidden-md.hidden-lg{ type: "button", data: { toggle: "dropdown" } } - Options - = icon('caret-down') - .dropdown-menu.dropdown-menu-align-right.hidden-lg - %ul - - if can?(current_user, :create_issue, @project) - %li - = link_to 'New issue', new_namespace_project_issue_path(@project.namespace, @project), title: 'New issue', id: 'new_issue_link' - - if can?(current_user, :update_issue, @issue) - %li - = link_to 'Reopen issue', issue_path(@issue, issue: { state_event: :reopen }, format: 'json'), data: {no_turbolink: true}, class: "btn-reopen #{issue_button_visibility(@issue, false)}", title: 'Reopen issue' - %li - = link_to 'Close issue', issue_path(@issue, issue: { state_event: :close }, format: 'json'), data: {no_turbolink: true}, class: "btn-close #{issue_button_visibility(@issue, true)}", title: 'Close issue' - %li - = link_to 'Edit', edit_namespace_project_issue_path(@project.namespace, @project, @issue) - - if @issue.submittable_as_spam_by?(current_user) - %li - = link_to 'Submit as spam', mark_as_spam_namespace_project_issue_path(@project.namespace, @project, @issue), method: :post, class: 'btn-spam', title: 'Submit as spam' - - - if can?(current_user, :create_issue, @project) - = link_to new_namespace_project_issue_path(@project.namespace, @project), class: 'hidden-xs hidden-sm btn btn-grouped new-issue-link btn-new btn-inverted', title: 'New issue', id: 'new_issue_link' do - New issue - - if can?(current_user, :update_issue, @issue) - = link_to 'Reopen issue', issue_path(@issue, issue: { state_event: :reopen }, format: 'json'), data: {no_turbolink: true}, class: "hidden-xs hidden-sm btn btn-grouped btn-reopen #{issue_button_visibility(@issue, false)}", title: 'Reopen issue' - = link_to 'Close issue', issue_path(@issue, issue: { state_event: :close }, format: 'json'), data: {no_turbolink: true}, class: "hidden-xs hidden-sm btn btn-grouped btn-close #{issue_button_visibility(@issue, true)}", title: 'Close issue' + .issuable-actions + .clearfix.issue-btn-group.dropdown + %button.btn.btn-default.pull-left.hidden-md.hidden-lg{ type: "button", data: { toggle: "dropdown" } } + Options + = icon('caret-down') + .dropdown-menu.dropdown-menu-align-right.hidden-lg + %ul + %li + = link_to 'New issue', new_namespace_project_issue_path(@project.namespace, @project), title: 'New issue', id: 'new_issue_link' + - if can?(current_user, :update_issue, @issue) + %li + = link_to 'Reopen issue', issue_path(@issue, issue: { state_event: :reopen }, format: 'json'), data: {no_turbolink: true}, class: "btn-reopen #{issue_button_visibility(@issue, false)}", title: 'Reopen issue' + %li + = link_to 'Close issue', issue_path(@issue, issue: { state_event: :close }, format: 'json'), data: {no_turbolink: true}, class: "btn-close #{issue_button_visibility(@issue, true)}", title: 'Close issue' + %li + = link_to 'Edit', edit_namespace_project_issue_path(@project.namespace, @project, @issue) - if @issue.submittable_as_spam_by?(current_user) - = link_to 'Submit as spam', mark_as_spam_namespace_project_issue_path(@project.namespace, @project, @issue), method: :post, class: 'hidden-xs hidden-sm btn btn-grouped btn-spam', title: 'Submit as spam' - = link_to 'Edit', edit_namespace_project_issue_path(@project.namespace, @project, @issue), class: 'hidden-xs hidden-sm btn btn-grouped issuable-edit' + %li + = link_to 'Submit as spam', mark_as_spam_namespace_project_issue_path(@project.namespace, @project, @issue), method: :post, class: 'btn-spam', title: 'Submit as spam' + + = link_to new_namespace_project_issue_path(@project.namespace, @project), class: 'hidden-xs hidden-sm btn btn-grouped new-issue-link btn-new btn-inverted', title: 'New issue', id: 'new_issue_link' do + New issue + - if can?(current_user, :update_issue, @issue) + = link_to 'Reopen issue', issue_path(@issue, issue: { state_event: :reopen }, format: 'json'), data: {no_turbolink: true}, class: "hidden-xs hidden-sm btn btn-grouped btn-reopen #{issue_button_visibility(@issue, false)}", title: 'Reopen issue' + = link_to 'Close issue', issue_path(@issue, issue: { state_event: :close }, format: 'json'), data: {no_turbolink: true}, class: "hidden-xs hidden-sm btn btn-grouped btn-close #{issue_button_visibility(@issue, true)}", title: 'Close issue' + - if @issue.submittable_as_spam_by?(current_user) + = link_to 'Submit as spam', mark_as_spam_namespace_project_issue_path(@project.namespace, @project, @issue), method: :post, class: 'hidden-xs hidden-sm btn btn-grouped btn-spam', title: 'Submit as spam' + = link_to 'Edit', edit_namespace_project_issue_path(@project.namespace, @project, @issue), class: 'hidden-xs hidden-sm btn btn-grouped issuable-edit' .issue-details.issuable-details diff --git a/app/views/shared/empty_states/_issues.html.haml b/app/views/shared/empty_states/_issues.html.haml index e2033654018..7a7e3d46796 100644 --- a/app/views/shared/empty_states/_issues.html.haml +++ b/app/views/shared/empty_states/_issues.html.haml @@ -16,7 +16,6 @@ Also, issues are searchable and filterable. - if project_select_button = render 'shared/new_project_item_select', path: 'issues/new', label: 'New issue' - - else - = link_to 'New issue', button_path, class: 'btn btn-new', title: 'New issue', id: 'new_issue_link' - else - %h4.text-center There are no issues to show. + %h4 There are no issues to show. + = link_to 'New issue', button_path, class: 'btn btn-new', title: 'New issue', id: 'new_issue_link' diff --git a/spec/controllers/projects/issues_controller_spec.rb b/spec/controllers/projects/issues_controller_spec.rb index 46c758b4654..ed2d986f6f0 100644 --- a/spec/controllers/projects/issues_controller_spec.rb +++ b/spec/controllers/projects/issues_controller_spec.rb @@ -87,6 +87,12 @@ describe Projects::IssuesController do end describe 'GET #new' do + it 'redirects to signin if not logged in' do + get :new, namespace_id: project.namespace, project_id: project + + expect(response).to redirect_to(new_user_session_path) + end + context 'internal issue tracker' do before do sign_in(user) @@ -112,6 +118,11 @@ describe Projects::IssuesController do end context 'external issue tracker' do + before do + sign_in(user) + project.team << [user, :developer] + end + it 'redirects to the external issue tracker' do external = double(new_issue_path: 'https://example.com/issues/new') allow(project).to receive(:external_issue_tracker).and_return(external) diff --git a/spec/features/issues_spec.rb b/spec/features/issues_spec.rb index 1c8267b1593..a58aedc924e 100644 --- a/spec/features/issues_spec.rb +++ b/spec/features/issues_spec.rb @@ -6,7 +6,7 @@ describe 'Issues', feature: true do include SortingHelper include WaitForAjax - let(:project) { create(:project) } + let(:project) { create(:project, :public) } before do login_as :user @@ -565,6 +565,24 @@ describe 'Issues', feature: true do end describe 'new issue' do + context 'by unauthenticated user' do + before do + logout + end + + it 'redirects to signin then back to new issue after signin' do + visit namespace_project_issues_path(project.namespace, project) + + click_link 'New issue' + + expect(current_path).to eq new_user_session_path + + login_as :user + + expect(current_path).to eq new_namespace_project_issue_path(project.namespace, project) + end + end + context 'dropzone upload file', js: true do before do visit new_namespace_project_issue_path(project.namespace, project) From 51f07069071673332703e8778a7d3a52041558d4 Mon Sep 17 00:00:00 2001 From: Simon Knox Date: Fri, 17 Mar 2017 11:10:35 +1100 Subject: [PATCH 052/238] handle commas in Issue total count --- app/assets/javascripts/issue.js | 8 +++++--- spec/javascripts/issue_spec.js | 15 +++++++++++++++ 2 files changed, 20 insertions(+), 3 deletions(-) diff --git a/app/assets/javascripts/issue.js b/app/assets/javascripts/issue.js index ef4029a8623..47e675f537e 100644 --- a/app/assets/javascripts/issue.js +++ b/app/assets/javascripts/issue.js @@ -2,6 +2,7 @@ /* global Flash */ require('./flash'); +require('~/lib/utils/text_utility'); require('vendor/jquery.waitforimages'); require('./task_list'); @@ -50,20 +51,21 @@ class Issue { success: function(data, textStatus, jqXHR) { if ('id' in data) { $(document).trigger('issuable:change'); - const currentTotal = Number($('.issue_counter').text()); + let total = Number($('.issue_counter').text().replace(/[^\d]/, '')); if (isClose) { $('a.btn-close').addClass('hidden'); $('a.btn-reopen').removeClass('hidden'); $('div.status-box-closed').removeClass('hidden'); $('div.status-box-open').addClass('hidden'); - $('.issue_counter').text(currentTotal - 1); + total -= 1; } else { $('a.btn-reopen').addClass('hidden'); $('a.btn-close').removeClass('hidden'); $('div.status-box-closed').addClass('hidden'); $('div.status-box-open').removeClass('hidden'); - $('.issue_counter').text(currentTotal + 1); + total += 1; } + $('.issue_counter').text(gl.text.addDelimiter(total)); } else { new Flash(issueFailMessage, 'alert'); } diff --git a/spec/javascripts/issue_spec.js b/spec/javascripts/issue_spec.js index 8d25500b9fd..aabc8bea12f 100644 --- a/spec/javascripts/issue_spec.js +++ b/spec/javascripts/issue_spec.js @@ -136,6 +136,21 @@ describe('Issue', function() { expectErrorMessage(); expect($('.issue_counter')).toHaveText(1); }); + + it('updates counter', () => { + spyOn(jQuery, 'ajax').and.callFake(function(req) { + expectPendingRequest(req, $btnClose); + req.success({ + id: 34 + }); + }); + + expect($('.issue_counter')).toHaveText(1); + $('.issue_counter').text('1,001'); + expect($('.issue_counter').text()).toEqual('1,001'); + $btnClose.trigger('click'); + expect($('.issue_counter').text()).toEqual('1,000'); + }); }); describe('reopen issue', function() { From 24a3311848d9b7d21f1e0ad3d745689104915b8a Mon Sep 17 00:00:00 2001 From: Joshua Lambert Date: Mon, 20 Mar 2017 11:17:57 -0400 Subject: [PATCH 053/238] Update docs to indicate prometheus k8s monitoring is on by default --- doc/administration/monitoring/prometheus/index.md | 13 +++++-------- 1 file changed, 5 insertions(+), 8 deletions(-) diff --git a/doc/administration/monitoring/prometheus/index.md b/doc/administration/monitoring/prometheus/index.md index 69b16b7c483..558275ba244 100644 --- a/doc/administration/monitoring/prometheus/index.md +++ b/doc/administration/monitoring/prometheus/index.md @@ -96,21 +96,18 @@ Sample Prometheus queries: > Introduced in GitLab 9.0. -If your GitLab server is running within Kubernetes, an option is now available -to monitor the health of each node in the cluster. This is particularly helpful -if your CI/CD environments run in the same cluster, and you would like enable -[Prometheus integration][] to monitor them. +If your GitLab server is running within Kubernetes, Prometheus will collect metrics from each Node in the cluster including performance data on each container. This is particularly helpful if your CI/CD environments run in the same cluster, as you can use the [Prometheus project integration][] to monitor them. When enabled, the bundled Prometheus server monitors Kubernetes and automatically [collects metrics][prometheus-cadvisor-metrics] from each Node in the cluster. -To enable the Kubernetes monitoring: +To disable the monitoring of Kubernetes: 1. Edit `/etc/gitlab/gitlab.rb` -1. Add or find and uncomment the following line: +1. Add or find and uncomment the following line and set it to `false`: ```ruby - prometheus['monitor_kubernetes'] = true + prometheus['monitor_kubernetes'] = false ``` 1. Save the file and [reconfigure GitLab][reconfigure] for the changes to @@ -165,4 +162,4 @@ The GitLab monitor exporter allows you to measure various GitLab metrics. [reconfigure]: ../../restart_gitlab.md#omnibus-gitlab-reconfigure [1261]: https://gitlab.com/gitlab-org/omnibus-gitlab/merge_requests/1261 [prometheus integration]: ../../../user/project/integrations/prometheus.md -[rometheus-cadvisor-metrics]: https://github.com/google/cadvisor/blob/master/docs/storage/prometheus.md +[prometheus-cadvisor-metrics]: https://github.com/google/cadvisor/blob/master/docs/storage/prometheus.md From fa87ff0e309069611ce564eba838a91f42057512 Mon Sep 17 00:00:00 2001 From: Joshua Lambert Date: Mon, 20 Mar 2017 11:21:24 -0400 Subject: [PATCH 054/238] Minor tweak --- doc/administration/monitoring/prometheus/index.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/doc/administration/monitoring/prometheus/index.md b/doc/administration/monitoring/prometheus/index.md index 558275ba244..95b2bc62c7b 100644 --- a/doc/administration/monitoring/prometheus/index.md +++ b/doc/administration/monitoring/prometheus/index.md @@ -96,7 +96,7 @@ Sample Prometheus queries: > Introduced in GitLab 9.0. -If your GitLab server is running within Kubernetes, Prometheus will collect metrics from each Node in the cluster including performance data on each container. This is particularly helpful if your CI/CD environments run in the same cluster, as you can use the [Prometheus project integration][] to monitor them. +If your GitLab server is running within Kubernetes, Prometheus will collect metrics from the Nodes in the cluster including performance data on each container. This is particularly helpful if your CI/CD environments run in the same cluster, as you can use the [Prometheus project integration][] to monitor them. When enabled, the bundled Prometheus server monitors Kubernetes and automatically [collects metrics][prometheus-cadvisor-metrics] from each Node in the cluster. From be25bbc4d2c7e3d5cf3da6f51cb7f7355295ef52 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?R=C3=A9my=20Coutable?= Date: Mon, 20 Mar 2017 10:56:43 +0100 Subject: [PATCH 055/238] Fix ProjectWiki#http_url_to_repo signature MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit New Gitlab::UrlSanitizer.http_credentials_for_user method responsible for generating a credentials hash from a user. Signed-off-by: Rémy Coutable --- app/models/project.rb | 8 ++--- app/models/project_wiki.rb | 7 ++-- lib/gitlab/url_sanitizer.rb | 6 ++++ .../wiki/user_git_access_wiki_page_spec.rb | 26 ++++++++++++++ spec/lib/gitlab/url_sanitizer_spec.rb | 34 +++++++++++++++---- spec/models/project_spec.rb | 8 ++--- spec/models/project_wiki_spec.rb | 21 +++++++++--- 7 files changed, 87 insertions(+), 23 deletions(-) create mode 100644 spec/features/projects/wiki/user_git_access_wiki_page_spec.rb diff --git a/app/models/project.rb b/app/models/project.rb index 17cf8226bcc..963faed5df9 100644 --- a/app/models/project.rb +++ b/app/models/project.rb @@ -880,13 +880,9 @@ class Project < ActiveRecord::Base end def http_url_to_repo(user = nil) - url = web_url + credentials = Gitlab::UrlSanitizer.http_credentials_for_user(user) - if user - url.sub!(%r{\Ahttps?://}) { |protocol| "#{protocol}#{user.username}@" } - end - - "#{url}.git" + Gitlab::UrlSanitizer.new("#{web_url}.git", credentials: credentials).full_url end # Check if current branch name is marked as protected in the system diff --git a/app/models/project_wiki.rb b/app/models/project_wiki.rb index 539b31780b3..70eef359cdd 100644 --- a/app/models/project_wiki.rb +++ b/app/models/project_wiki.rb @@ -42,8 +42,11 @@ class ProjectWiki url_to_repo end - def http_url_to_repo - [Gitlab.config.gitlab.url, "/", path_with_namespace, ".git"].join('') + def http_url_to_repo(user = nil) + url = "#{Gitlab.config.gitlab.url}/#{path_with_namespace}.git" + credentials = Gitlab::UrlSanitizer.http_credentials_for_user(user) + + Gitlab::UrlSanitizer.new(url, credentials: credentials).full_url end def wiki_base_path diff --git a/lib/gitlab/url_sanitizer.rb b/lib/gitlab/url_sanitizer.rb index c81dc7e30d0..9ce13feb79a 100644 --- a/lib/gitlab/url_sanitizer.rb +++ b/lib/gitlab/url_sanitizer.rb @@ -18,6 +18,12 @@ module Gitlab false end + def self.http_credentials_for_user(user) + return {} unless user.respond_to?(:username) + + { user: user.username } + end + def initialize(url, credentials: nil) @url = Addressable::URI.parse(url.strip) @credentials = credentials diff --git a/spec/features/projects/wiki/user_git_access_wiki_page_spec.rb b/spec/features/projects/wiki/user_git_access_wiki_page_spec.rb new file mode 100644 index 00000000000..6825b95c8aa --- /dev/null +++ b/spec/features/projects/wiki/user_git_access_wiki_page_spec.rb @@ -0,0 +1,26 @@ +require 'spec_helper' + +describe 'Projects > Wiki > User views Git access wiki page', :feature do + let(:user) { create(:user) } + let(:project) { create(:project, :public) } + let(:wiki_page) do + WikiPages::CreateService.new( + project, + user, + title: 'home', + content: '[some link](other-page)' + ).execute + end + + before do + login_as(user) + end + + scenario 'Visit Wiki Page Current Commit' do + visit namespace_project_wiki_path(project.namespace, project, wiki_page) + + click_link 'Clone repository' + expect(page).to have_text("Clone repository #{project.wiki.path_with_namespace}") + expect(page).to have_text(project.wiki.http_url_to_repo(user)) + end +end diff --git a/spec/lib/gitlab/url_sanitizer_spec.rb b/spec/lib/gitlab/url_sanitizer_spec.rb index 3fd361de458..fc144a2556a 100644 --- a/spec/lib/gitlab/url_sanitizer_spec.rb +++ b/spec/lib/gitlab/url_sanitizer_spec.rb @@ -5,6 +5,7 @@ describe Gitlab::UrlSanitizer, lib: true do let(:url_sanitizer) do described_class.new("https://github.com/me/project.git", credentials: credentials) end + let(:user) { double(:user, username: 'john.doe') } describe '.sanitize' do def sanitize_url(url) @@ -53,12 +54,33 @@ describe Gitlab::UrlSanitizer, lib: true do end end + describe '.valid?' do + it 'validates url strings' do + expect(described_class.valid?(nil)).to be(false) + expect(described_class.valid?('valid@project:url.git')).to be(true) + expect(described_class.valid?('123://invalid:url')).to be(false) + end + end + + describe '.http_credentials_for_user' do + it { expect(described_class.http_credentials_for_user(user)).to eq({ user: 'john.doe' }) } + it { expect(described_class.http_credentials_for_user('foo')).to eq({}) } + end + describe '#sanitized_url' do it { expect(url_sanitizer.sanitized_url).to eq("https://github.com/me/project.git") } end describe '#credentials' do it { expect(url_sanitizer.credentials).to eq(credentials) } + + context 'when user is given to #initialize' do + let(:url_sanitizer) do + described_class.new("https://github.com/me/project.git", credentials: described_class.http_credentials_for_user(user)) + end + + it { expect(url_sanitizer.credentials).to eq({ user: 'john.doe' }) } + end end describe '#full_url' do @@ -69,13 +91,13 @@ describe Gitlab::UrlSanitizer, lib: true do expect(sanitizer.full_url).to eq('user@server:project.git') end - end - describe '.valid?' do - it 'validates url strings' do - expect(described_class.valid?(nil)).to be(false) - expect(described_class.valid?('valid@project:url.git')).to be(true) - expect(described_class.valid?('123://invalid:url')).to be(false) + context 'when user is given to #initialize' do + let(:url_sanitizer) do + described_class.new("https://github.com/me/project.git", credentials: described_class.http_credentials_for_user(user)) + end + + it { expect(url_sanitizer.full_url).to eq("https://john.doe@github.com/me/project.git") } end end end diff --git a/spec/models/project_spec.rb b/spec/models/project_spec.rb index 618ce2b6d53..b3efc8cec9e 100644 --- a/spec/models/project_spec.rb +++ b/spec/models/project_spec.rb @@ -1900,10 +1900,8 @@ describe Project, models: true do context 'when no user is given' do it 'returns the url to the repo without a username' do - url = project.http_url_to_repo - - expect(url).to eq(project.http_url_to_repo) - expect(url).not_to include('@') + expect(project.http_url_to_repo).to eq("#{project.web_url}.git") + expect(project.http_url_to_repo).not_to include('@') end end @@ -1911,7 +1909,7 @@ describe Project, models: true do it 'returns the url to the repo with the username' do user = build_stubbed(:user) - expect(project.http_url_to_repo(user)).to match(%r{https?:\/\/#{user.username}@}) + expect(project.http_url_to_repo(user)).to start_with("http://#{user.username}@") end end end diff --git a/spec/models/project_wiki_spec.rb b/spec/models/project_wiki_spec.rb index 58b57bd4fef..b5b9cd024b0 100644 --- a/spec/models/project_wiki_spec.rb +++ b/spec/models/project_wiki_spec.rb @@ -35,10 +35,23 @@ describe ProjectWiki, models: true do end describe "#http_url_to_repo" do - it "provides the full http url to the repo" do - gitlab_url = Gitlab.config.gitlab.url - repo_http_url = "#{gitlab_url}/#{subject.path_with_namespace}.git" - expect(subject.http_url_to_repo).to eq(repo_http_url) + let(:project) { create :empty_project } + + context 'when no user is given' do + it 'returns the url to the repo without a username' do + expected_url = "#{Gitlab.config.gitlab.url}/#{subject.path_with_namespace}.git" + + expect(project_wiki.http_url_to_repo).to eq(expected_url) + expect(project_wiki.http_url_to_repo).not_to include('@') + end + end + + context 'when user is given' do + it 'returns the url to the repo with the username' do + user = build_stubbed(:user) + + expect(project_wiki.http_url_to_repo(user)).to start_with("http://#{user.username}@") + end end end From 2413a4b5eb00035fb0a24ff535461c21d6f3e220 Mon Sep 17 00:00:00 2001 From: Joshua Lambert Date: Mon, 20 Mar 2017 11:24:40 -0400 Subject: [PATCH 056/238] Remove unnecessary paragraph --- doc/administration/monitoring/prometheus/index.md | 3 --- 1 file changed, 3 deletions(-) diff --git a/doc/administration/monitoring/prometheus/index.md b/doc/administration/monitoring/prometheus/index.md index 95b2bc62c7b..b2445d1c0e5 100644 --- a/doc/administration/monitoring/prometheus/index.md +++ b/doc/administration/monitoring/prometheus/index.md @@ -98,9 +98,6 @@ Sample Prometheus queries: If your GitLab server is running within Kubernetes, Prometheus will collect metrics from the Nodes in the cluster including performance data on each container. This is particularly helpful if your CI/CD environments run in the same cluster, as you can use the [Prometheus project integration][] to monitor them. -When enabled, the bundled Prometheus server monitors Kubernetes and automatically -[collects metrics][prometheus-cadvisor-metrics] from each Node in the cluster. - To disable the monitoring of Kubernetes: 1. Edit `/etc/gitlab/gitlab.rb` From 970541e0330bce5fea9fa9af4cb26141b455a757 Mon Sep 17 00:00:00 2001 From: Clement Ho Date: Mon, 20 Mar 2017 10:42:41 -0500 Subject: [PATCH 057/238] Fix input token spacing --- app/assets/stylesheets/framework/filters.scss | 10 ++++++---- 1 file changed, 6 insertions(+), 4 deletions(-) diff --git a/app/assets/stylesheets/framework/filters.scss b/app/assets/stylesheets/framework/filters.scss index 2ebeaf9a40d..712e4851a9e 100644 --- a/app/assets/stylesheets/framework/filters.scss +++ b/app/assets/stylesheets/framework/filters.scss @@ -76,12 +76,14 @@ } .input-token { - flex: 1; - -webkit-flex: 1; + max-width: 200px; } - .filtered-search-token + .input-token:not(:last-child) { - max-width: 200px; + .input-token:only-child, + .input-token:last-child { + flex: 1; + -webkit-flex: 1; + max-width: initial; } } From a13b8434ae52dbdb5c9db09d86e7baa03358f500 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?R=C3=A9my=20Coutable?= Date: Mon, 20 Mar 2017 16:51:39 +0100 Subject: [PATCH 058/238] Make the v3_to_v4.md more consistent MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit [ci skip] Signed-off-by: Rémy Coutable --- doc/api/v3_to_v4.md | 60 ++++++++++++++++++++++----------------------- 1 file changed, 30 insertions(+), 30 deletions(-) diff --git a/doc/api/v3_to_v4.md b/doc/api/v3_to_v4.md index 0794156bc39..7f4426ee85d 100644 --- a/doc/api/v3_to_v4.md +++ b/doc/api/v3_to_v4.md @@ -8,16 +8,16 @@ Below are the changes made between V3 and V4. ### 8.17 -- Removed `/projects/:search` (use: `/projects?search=x`) [!8877](https://gitlab.com/gitlab-org/gitlab-ce/merge_requests/8877) -- `iid` filter has been removed from `projects/:id/issues` [!8967](https://gitlab.com/gitlab-org/gitlab-ce/merge_requests/8967) -- `projects/:id/merge_requests?iid[]=x&iid[]=y` array filter has been renamed to `iids` [!8793](https://gitlab.com/gitlab-org/gitlab-ce/merge_requests/8793) -- Endpoints under `projects/merge_request/:id` have been removed (use: `projects/merge_requests/:id`) [!8793](https://gitlab.com/gitlab-org/gitlab-ce/merge_requests/8793) +- Removed `GET /projects/:search` (use: `GET /projects?search=x`) [!8877](https://gitlab.com/gitlab-org/gitlab-ce/merge_requests/8877) +- `iid` filter has been removed from `GET /projects/:id/issues` [!8967](https://gitlab.com/gitlab-org/gitlab-ce/merge_requests/8967) +- `GET /projects/:id/merge_requests?iid[]=x&iid[]=y` array filter has been renamed to `iids` [!8793](https://gitlab.com/gitlab-org/gitlab-ce/merge_requests/8793) +- Endpoints under `GET /projects/merge_request/:id` have been removed (use: `GET /projects/merge_requests/:id`) [!8793](https://gitlab.com/gitlab-org/gitlab-ce/merge_requests/8793) - Project snippets do not return deprecated field `expires_at` [!8723](https://gitlab.com/gitlab-org/gitlab-ce/merge_requests/8723) -- Endpoints under `projects/:id/keys` have been removed (use `projects/:id/deploy_keys`) [!8716](https://gitlab.com/gitlab-org/gitlab-ce/merge_requests/8716) +- Endpoints under `GET /projects/:id/keys` have been removed (use `GET /projects/:id/deploy_keys`) [!8716](https://gitlab.com/gitlab-org/gitlab-ce/merge_requests/8716) ### 9.0 -- Status 409 returned for POST `project/:id/members` when a member already exists [!9093](https://gitlab.com/gitlab-org/gitlab-ce/merge_requests/9093) +- Status 409 returned for `POST /projects/:id/members` when a member already exists [!9093](https://gitlab.com/gitlab-org/gitlab-ce/merge_requests/9093) - Moved `DELETE /projects/:id/star` to `POST /projects/:id/unstar` [!9328](https://gitlab.com/gitlab-org/gitlab-ce/merge_requests/9328) - Removed the following deprecated Templates endpoints (these are still accessible with `/templates` prefix) [!8853](https://gitlab.com/gitlab-org/gitlab-ce/merge_requests/8853) - `/licences` @@ -28,31 +28,31 @@ Below are the changes made between V3 and V4. - `/gitignores/:key` - `/gitlab_ci_ymls/:key` - `/dockerfiles/:key` -- Moved `/projects/fork/:id` to `/projects/:id/fork` [!8940](https://gitlab.com/gitlab-org/gitlab-ce/merge_requests/8940) +- Moved `POST /projects/fork/:id` to `POST /projects/:id/fork` [!8940](https://gitlab.com/gitlab-org/gitlab-ce/merge_requests/8940) - 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) - Project filters are no longer available as `GET /projects/foo`, but as `GET /projects?foo=true` instead [!8962](https://gitlab.com/gitlab-org/gitlab-ce/merge_requests/8962) - `GET /projects/visible` & `GET /projects/all` are consolidated into `GET /projects` and can be used with or without authorization - `GET /projects/owned` moved to `GET /projects?owned=true` - `GET /projects/starred` moved to `GET /projects?starred=true` - `GET /projects` returns all projects visible to current user, even if the user is not a member [!9674](https://gitlab.com/gitlab-org/gitlab-ce/merge_requests/9674) - - To get projects the user is a member of, use `/projects?membership=true` + - To get projects the user is a member of, use `GET /projects?membership=true` - 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) +- 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) -- Labels filter on `projects/:id/issues` and `/issues` now matches only issues containing all labels (i.e.: Logical AND, not OR) [!8849](https://gitlab.com/gitlab-org/gitlab-ce/merge_requests/8849) +- Make subscription API more RESTful. Use `POST /projects/:id/:subscribable_type/:subscribable_id/subscribe` to subscribe and `POST /projects/:id/:subscribable_type/:subscribable_id/unsubscribe` to unsubscribe from a resource. [!9325](https://gitlab.com/gitlab-org/gitlab-ce/merge_requests/9325) +- Labels filter on `GET /projects/:id/issues` and `GET /issues` now matches only issues containing all labels (i.e.: Logical AND, not OR) [!8849](https://gitlab.com/gitlab-org/gitlab-ce/merge_requests/8849) - Renamed param `branch_name` to `branch` on the following endpoints [!8936](https://gitlab.com/gitlab-org/gitlab-ce/merge_requests/8936) - - 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) + - `POST /projects/:id/repository/branches` + - `POST /projects/:id/repository/commits` + - `POST/PUT/DELETE :id/repository/files` +- Renamed the `merge_when_build_succeeds` parameter to `merge_when_pipeline_succeeds` 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 /projects/: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) - Remove `subscribed` field from responses returning list of issues or merge requests. Fetch individual issues or merge requests to obtain the value @@ -62,21 +62,21 @@ Below are the changes made between V3 and V4. - 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) +- 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) +- `GET /projects/:id/milestones?iid[]=x&iid[]=y` array filter has been renamed to `iids` [!9096](https://gitlab.com/gitlab-org/gitlab-ce/merge_requests/9096) - Return basic info about pipeline in `GET /projects/:id/pipelines` [!8875](https://gitlab.com/gitlab-org/gitlab-ce/merge_requests/8875) - Renamed all `build` references to `job` [!9463](https://gitlab.com/gitlab-org/gitlab-ce/merge_requests/9463) -- Drop GET '/projects/:id/repository/commits/:sha/jobs' [!9463](https://gitlab.com/gitlab-org/gitlab-ce/merge_requests/9463) +- Drop `GET /projects/:id/repository/commits/:sha/jobs` [!9463](https://gitlab.com/gitlab-org/gitlab-ce/merge_requests/9463) - Rename Build Triggers to be Pipeline Triggers API [!9713](https://gitlab.com/gitlab-org/gitlab-ce/merge_requests/9713) - `POST /projects/:id/trigger/builds` to `POST /projects/:id/trigger/pipeline` - Require description when creating a new trigger `POST /projects/:id/triggers` - Simplify project payload exposed on Environment endpoints [!9675](https://gitlab.com/gitlab-org/gitlab-ce/merge_requests/9675) - API uses merge request `IID`s (internal ID, as in the web UI) rather than `ID`s. This affects the merge requests, award emoji, todos, and time tracking APIs. [!9530](https://gitlab.com/gitlab-org/gitlab-ce/merge_requests/9530) - API uses issue `IID`s (internal ID, as in the web UI) rather than `ID`s. This affects the issues, award emoji, todos, and time tracking APIs. [!9530](https://gitlab.com/gitlab-org/gitlab-ce/merge_requests/9530) -- Change initial page from `0` to `1` on `GET projects/:id/repository/commits` (like on the rest of the API) [!9679] (https://gitlab.com/gitlab-org/gitlab-ce/merge_requests/9679) -- Return correct `Link` header data for `GET projects/:id/repository/commits` [!9679] (https://gitlab.com/gitlab-org/gitlab-ce/merge_requests/9679) +- Change initial page from `0` to `1` on `GET /projects/:id/repository/commits` (like on the rest of the API) [!9679] (https://gitlab.com/gitlab-org/gitlab-ce/merge_requests/9679) +- Return correct `Link` header data for `GET /projects/:id/repository/commits` [!9679] (https://gitlab.com/gitlab-org/gitlab-ce/merge_requests/9679) - Update endpoints for repository files [!9637](https://gitlab.com/gitlab-org/gitlab-ce/merge_requests/9637) - - Moved `/projects/:id/repository/files?file_path=:file_path` to `/projects/:id/repository/files/:file_path` (`:file_path` should be URL-encoded) - - `/projects/:id/repository/blobs/:sha` now returns JSON attributes for the blob identified by `:sha`, instead of finding the commit identified by `:sha` and returning the raw content of the blob in that commit identified by the required `?filepath=:filepath` - - Moved `/projects/:id/repository/commits/:sha/blob?file_path=:file_path` and `/projects/:id/repository/blobs/:sha?file_path=:file_path` to `/projects/:id/repository/files/:file_path/raw?ref=:sha` - - `/projects/:id/repository/tree` parameter `ref_name` has been renamed to `ref` for consistency + - Moved `GET /projects/:id/repository/files?file_path=:file_path` to `GET /projects/:id/repository/files/:file_path` (`:file_path` should be URL-encoded) + - `GET /projects/:id/repository/blobs/:sha` now returns JSON attributes for the blob identified by `:sha`, instead of finding the commit identified by `:sha` and returning the raw content of the blob in that commit identified by the required `?filepath=:filepath` + - Moved `GET /projects/:id/repository/commits/:sha/blob?file_path=:file_path` and `GET /projects/:id/repository/blobs/:sha?file_path=:file_path` to `GET /projects/:id/repository/files/:file_path/raw?ref=:sha` + - `GET /projects/:id/repository/tree` parameter `ref_name` has been renamed to `ref` for consistency From 6c6de7f1e69cc736f86f1f76f4e2b3fd2a42b5fd Mon Sep 17 00:00:00 2001 From: Clement Ho Date: Mon, 20 Mar 2017 11:04:15 -0500 Subject: [PATCH 059/238] Add additional check for when inputContainer does not exist --- .../javascripts/filtered_search/filtered_search_manager.js | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/app/assets/javascripts/filtered_search/filtered_search_manager.js b/app/assets/javascripts/filtered_search/filtered_search_manager.js index 4c91c19a506..2e62a9f28b7 100644 --- a/app/assets/javascripts/filtered_search/filtered_search_manager.js +++ b/app/assets/javascripts/filtered_search/filtered_search_manager.js @@ -131,7 +131,11 @@ import FilteredSearchContainer from './container'; } addInputContainerFocus() { - this.filteredSearchInput.closest('.filtered-search-input-container').classList.add('focus'); + const inputContainer = this.filteredSearchInput.closest('.filtered-search-input-container'); + + if (inputContainer) { + inputContainer.classList.add('focus'); + } } removeInputContainerFocus(e) { From 446b59dd4ee88f8ef86272c39408560a0107c48a Mon Sep 17 00:00:00 2001 From: Filipa Lacerda Date: Mon, 20 Mar 2017 15:36:02 +0000 Subject: [PATCH 060/238] Adds tests to new empty and error states --- .../commit/pipelines/pipelines_table.js | 9 ++ .../components/empty_state.js | 8 +- .../components/error_state.js | 18 +-- .../components/navigation_tabs.js | 4 +- .../vue_pipelines_index/pipelines.js | 80 +++++++------ .../projects/commit/_pipelines_list.haml | 1 + app/views/projects/pipelines/index.html.haml | 7 +- .../projects/pipelines/pipelines_spec.rb | 2 +- .../commit/pipelines/pipelines_spec.js | 4 +- spec/javascripts/fixtures/pipelines.html.haml | 14 +++ .../vue_pipelines_index/empty_state_spec.js | 38 ++++++ .../vue_pipelines_index/error_state_spec.js | 23 ++++ .../vue_pipelines_index/mock_data.js | 107 +++++++++++++++++ .../vue_pipelines_index/nav_controls_spec.js | 93 +++++++++++++++ .../vue_pipelines_index/pipelines_spec.js | 111 ++++++++++++++++++ 15 files changed, 465 insertions(+), 54 deletions(-) create mode 100644 spec/javascripts/fixtures/pipelines.html.haml create mode 100644 spec/javascripts/vue_pipelines_index/empty_state_spec.js create mode 100644 spec/javascripts/vue_pipelines_index/error_state_spec.js create mode 100644 spec/javascripts/vue_pipelines_index/mock_data.js create mode 100644 spec/javascripts/vue_pipelines_index/nav_controls_spec.js create mode 100644 spec/javascripts/vue_pipelines_index/pipelines_spec.js diff --git a/app/assets/javascripts/commit/pipelines/pipelines_table.js b/app/assets/javascripts/commit/pipelines/pipelines_table.js index 29ee3e5e67b..189dd2c7dce 100644 --- a/app/assets/javascripts/commit/pipelines/pipelines_table.js +++ b/app/assets/javascripts/commit/pipelines/pipelines_table.js @@ -5,6 +5,7 @@ import PipelinesTableComponent from '../../vue_shared/components/pipelines_table import PipelinesService from '../../vue_pipelines_index/services/pipelines_service'; import PipelineStore from '../../vue_pipelines_index/stores/pipelines_store'; import eventHub from '../../vue_pipelines_index/event_hub'; +import EmptyState from '../../vue_pipelines_index/components/empty_state'; import ErrorState from '../../vue_pipelines_index/components/error_state'; import '../../lib/utils/common_utils'; import '../../vue_shared/vue_resource_interceptor'; @@ -24,6 +25,7 @@ export default Vue.component('pipelines-table', { components: { 'pipelines-table-component': PipelinesTableComponent, 'error-state': ErrorState, + 'empty-state': EmptyState, }, /** @@ -38,6 +40,7 @@ export default Vue.component('pipelines-table', { return { endpoint: pipelinesTableData.endpoint, + helpPagePath: pipelinesTableData.helpPagePath, store, state: store.state, isLoading: false, @@ -49,6 +52,10 @@ export default Vue.component('pipelines-table', { shouldRenderErrorState() { return this.hasError && !this.pageRequest; }, + + shouldRenderEmptyState() { + return !this.state.pipelines.length && !this.pageRequest; + }, }, /** @@ -102,6 +109,8 @@ export default Vue.component('pipelines-table', {
    + +
    +
    ${pipelinesEmptyStateSVG} @@ -28,10 +28,10 @@ export default {

    Continous Integration can help catch bugs by running your tests automatically, while Continuous Deployment can help you deliver code to your product environment. - - Get started with Pipelines -

    + + Get started with Pipelines +
    diff --git a/app/assets/javascripts/vue_pipelines_index/components/error_state.js b/app/assets/javascripts/vue_pipelines_index/components/error_state.js index 9071ecdea73..f197395a6db 100644 --- a/app/assets/javascripts/vue_pipelines_index/components/error_state.js +++ b/app/assets/javascripts/vue_pipelines_index/components/error_state.js @@ -8,18 +8,18 @@ export default { }, template: ` -
    -
    -
    - ${pipelinesErrorStateSVG} +
    +
    +
    + ${pipelinesErrorStateSVG} +
    -
    -
    -
    -

    The API failed to fetch the pipelines.

    +
    +
    +

    The API failed to fetch the pipelines.

    +
    -
    `, }; diff --git a/app/assets/javascripts/vue_pipelines_index/components/navigation_tabs.js b/app/assets/javascripts/vue_pipelines_index/components/navigation_tabs.js index a494d2459aa..b4480bd98c7 100644 --- a/app/assets/javascripts/vue_pipelines_index/components/navigation_tabs.js +++ b/app/assets/javascripts/vue_pipelines_index/components/navigation_tabs.js @@ -18,7 +18,9 @@ export default { template: `