From e8eac643b3da60f2694b8e2341bb84fc64e1116b Mon Sep 17 00:00:00 2001 From: Filipa Lacerda Date: Mon, 13 Mar 2017 17:37:18 +0000 Subject: Adds empty state for pipelines table --- .../javascripts/vue_pipelines_index/pipelines.js | 45 ++++++++++++++++++++-- .../javascripts/vue_pipelines_index/store.js | 1 + .../shared/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 -- cgit v1.2.3 From d51a5f0b4f0d6ff6fb634c8508f22a60296c7cdb Mon Sep 17 00:00:00 2001 From: Filipa Lacerda Date: Mon, 13 Mar 2017 17:39:57 +0000 Subject: Renders failed svg when an error occurred --- .../javascripts/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'/>
`, }); -- cgit v1.2.3 From d3732b5529249472ce0de4fbad36e3567f6cee54 Mon Sep 17 00:00:00 2001 From: Filipa Lacerda Date: Tue, 14 Mar 2017 09:30:57 +0000 Subject: Moves tabs into Vue Component --- .../javascripts/vue_pipelines_index/index.js | 10 +- .../javascripts/vue_pipelines_index/pipelines.js | 144 ++++++++++++++++++--- app/views/projects/pipelines/index.html.haml | 67 +++------- 3 files changed, 146 insertions(+), 75 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) - - %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') +- content_for :page_specific_javascripts do + = 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 } } -- cgit v1.2.3 From 6fb6632110ccec9c7ad64217647e17a29bdd66e3 Mon Sep 17 00:00:00 2001 From: Filipa Lacerda Date: Wed, 15 Mar 2017 21:51:44 +0000 Subject: Adds counters to badges --- .../javascripts/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
  • +
  • -- cgit v1.2.3 From 0de8dccd2a7a5e07805fe6c151e44cf4ad3bf86f Mon Sep 17 00:00:00 2001 From: "billy.lb" Date: Thu, 16 Mar 2017 21:28:34 +0800 Subject: 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' -- cgit v1.2.3 From 992e06805c14b9afebbc240c87b4e9487e6f374e Mon Sep 17 00:00:00 2001 From: Joshua Lambert Date: Fri, 17 Mar 2017 02:17:38 -0400 Subject: 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 -- cgit v1.2.3 From 03f6f51dcf5b0401257cddf8cbfd83aa1426f4bc Mon Sep 17 00:00:00 2001 From: Phil Hughes Date: Fri, 17 Mar 2017 10:05:27 +0000 Subject: 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, -- cgit v1.2.3 From c77fc4cee3d7a9f167cc5ca87fb5c8e22aed95f3 Mon Sep 17 00:00:00 2001 From: Sean McGivern Date: Fri, 17 Mar 2017 10:58:19 +0000 Subject: 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) -- cgit v1.2.3 From b5cc98088ecd6082c660f7046871bc527ae8be0d Mon Sep 17 00:00:00 2001 From: Achilleas Pipinellis Date: Fri, 17 Mar 2017 15:23:52 +0100 Subject: Update Impersonation tokens docs --- doc/api/README.md | 127 +++++++++++++++++++++++++++++------------------------- doc/api/users.md | 121 ++++++++++++++++++++++++++++++++++++++------------- 2 files changed, 161 insertions(+), 87 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,65 +119,25 @@ 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 - -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. - -## Basic Usage - -API requests should be prefixed with `api` and the API version. The API version -is defined in [`lib/api.rb`][lib-api-url]. - -Example of a valid API request: - -```shell -GET https://gitlab.example.com/api/v4/projects?private_token=9koXpg98eAheJpvBs5tK -``` - -Example of a valid API request using cURL and authentication via header: - -```shell -curl --header "PRIVATE-TOKEN: 9koXpg98eAheJpvBs5tK" "https://gitlab.example.com/api/v4/projects" -``` +### Impersonation tokens -The API uses JSON to serialize data. You don't need to specify `.json` at the -end of an API URL. +> [Introduced][ce-9099] in GitLab 9.0. Needs admin permissions. -## Status codes +Impersonation tokens are a type of [Personal Access Token](#personal-access-tokens) +that can only be created by an admin for a specific user. -The API is designed to return different status codes according to context and -action. This way, if a request results in an error, the caller is able to get -insight into what went wrong. +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. -The following table gives an overview of how the API functions generally behave. +For more information about the usage please refer to the +[users API](users.md#retrieve-user-impersonation-tokens) docs. -| Request type | Description | -| ------------ | ----------- | -| `GET` | Access one or more resources and return the result as JSON. | -| `POST` | Return `201 Created` if the resource is successfully created and return the newly created resource as JSON. | -| `GET` / `PUT` / `DELETE` | Return `200 OK` if the resource is accessed, modified or deleted successfully. The (modified) result is returned as JSON. | -| `DELETE` | Designed to be idempotent, meaning a request to a resource still returns `200 OK` even it was deleted before or is not available. The reasoning behind this, is that the user is not really interested if the resource existed before or not. | +### Sudo -The following table shows the possible return codes for API requests. - -| Return values | Description | -| ------------- | ----------- | -| `200 OK` | The `GET`, `PUT` or `DELETE` request was successful, the resource(s) itself is returned as JSON. | -| `204 No Content` | The server has successfully fulfilled the request and that there is no additional content to send in the response payload body. | -| `201 Created` | The `POST` request was successful and the resource is returned as JSON. | -| `304 Not Modified` | Indicates that the resource has not been modified since the last request. | -| `400 Bad Request` | A required attribute of the API request is missing, e.g., the title of an issue is not given. | -| `401 Unauthorized` | The user is not authenticated, a valid [user token](#authentication) is necessary. | -| `403 Forbidden` | The request is not allowed, e.g., the user is not allowed to delete a project. | -| `404 Not Found` | A resource could not be accessed, e.g., an ID for a resource could not be found. | -| `405 Method Not Allowed` | The request is not supported. | -| `409 Conflict` | A conflicting resource already exists, e.g., creating a project with a name that already exists. | -| `422 Unprocessable` | The entity could not be processed. | -| `500 Server Error` | While handling the request something went wrong server-side. | - -## 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 @@ -221,13 +187,57 @@ GET /projects?private_token=9koXpg98eAheJpvBs5tK&sudo=23 curl --header "PRIVATE-TOKEN: 9koXpg98eAheJpvBs5tK" --header "SUDO: 23" "https://gitlab.example.com/api/v4/projects" ``` -## Impersonation Tokens +## Basic Usage + +API requests should be prefixed with `api` and the API version. The API version +is defined in [`lib/api.rb`][lib-api-url]. + +Example of a valid API request: -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. +```shell +GET https://gitlab.example.com/api/v4/projects?private_token=9koXpg98eAheJpvBs5tK +``` -For more information about the usage please refer to the [Users](users.md) page +Example of a valid API request using cURL and authentication via header: + +```shell +curl --header "PRIVATE-TOKEN: 9koXpg98eAheJpvBs5tK" "https://gitlab.example.com/api/v4/projects" +``` + +The API uses JSON to serialize data. You don't need to specify `.json` at the +end of an API URL. + +## Status codes + +The API is designed to return different status codes according to context and +action. This way, if a request results in an error, the caller is able to get +insight into what went wrong. + +The following table gives an overview of how the API functions generally behave. + +| Request type | Description | +| ------------ | ----------- | +| `GET` | Access one or more resources and return the result as JSON. | +| `POST` | Return `201 Created` if the resource is successfully created and return the newly created resource as JSON. | +| `GET` / `PUT` / `DELETE` | Return `200 OK` if the resource is accessed, modified or deleted successfully. The (modified) result is returned as JSON. | +| `DELETE` | Designed to be idempotent, meaning a request to a resource still returns `200 OK` even it was deleted before or is not available. The reasoning behind this, is that the user is not really interested if the resource existed before or not. | + +The following table shows the possible return codes for API requests. + +| Return values | Description | +| ------------- | ----------- | +| `200 OK` | The `GET`, `PUT` or `DELETE` request was successful, the resource(s) itself is returned as JSON. | +| `204 No Content` | The server has successfully fulfilled the request and that there is no additional content to send in the response payload body. | +| `201 Created` | The `POST` request was successful and the resource is returned as JSON. | +| `304 Not Modified` | Indicates that the resource has not been modified since the last request. | +| `400 Bad Request` | A required attribute of the API request is missing, e.g., the title of an issue is not given. | +| `401 Unauthorized` | The user is not authenticated, a valid [user token](#authentication) is necessary. | +| `403 Forbidden` | The request is not allowed, e.g., the user is not allowed to delete a project. | +| `404 Not Found` | A resource could not be accessed, e.g., an ID for a resource could not be found. | +| `405 Method Not Allowed` | The request is not supported. | +| `409 Conflict` | A conflicting resource already exists, e.g., creating a project with a name that already exists. | +| `422 Unprocessable` | The entity could not be processed. | +| `500 Server Error` | While handling the request something went wrong server-side. | ## Pagination @@ -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 | -- cgit v1.2.3 From 32dfa801085f9d9ccca5040fd23175fa8a3c22d9 Mon Sep 17 00:00:00 2001 From: Filipa Lacerda Date: Fri, 17 Mar 2017 17:46:45 +0000 Subject: 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)); + } +} -- cgit v1.2.3 From 19a6cc2664c5753485efae2d0d89dc317a92200d Mon Sep 17 00:00:00 2001 From: Filipa Lacerda Date: Fri, 17 Mar 2017 18:04:35 +0000 Subject: 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: -- cgit v1.2.3 From 016602099b83dd23160748907d0dde08188c269f Mon Sep 17 00:00:00 2001 From: Filipa Lacerda Date: Fri, 17 Mar 2017 19:14:16 +0000 Subject: Update state. Divide into smaller components --- .../vue_pipelines_index/components/empty_state.js | 39 ++++ .../vue_pipelines_index/components/error_state.js | 25 +++ .../vue_pipelines_index/components/nav_controls.js | 52 +++++ .../components/navigation_tabs.js | 66 ++++++ .../javascripts/vue_pipelines_index/index.js | 6 +- .../javascripts/vue_pipelines_index/pipelines.js | 250 ++++++++------------- app/assets/stylesheets/pages/pipelines.scss | 1 + 7 files changed, 277 insertions(+), 162 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); }, - shouldRenderTable() { - return !this.hasError && - !this.pageRequest && this.pipelines.length; + /** + * 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; }, - /** - * 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; + shouldRenderTable() { + return !this.hasError && + !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 - -

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

    The API failed to fetch the pipelines.

    -
    -
    + + +
    +

    No pipelines to show.

    -
    - + +
    + :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 { -- cgit v1.2.3 From 9d1ab1e9bd474e467c75cf7a1f7b728d6832075c Mon Sep 17 00:00:00 2001 From: Filipa Lacerda Date: Fri, 17 Mar 2017 23:07:37 +0000 Subject: Add error state to commits and merge requests pipelines table --- .../javascripts/commit/pipelines/pipelines_table.js | 17 +++++++++++------ app/assets/javascripts/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 {
    - + -- cgit v1.2.3 From 2c85a20482c2e33f307ad8faf60e41e199aa25b8 Mon Sep 17 00:00:00 2001 From: Filipa Lacerda Date: Fri, 17 Mar 2017 23:15:28 +0000 Subject: 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: -- cgit v1.2.3 From 19677dd4f29910ad12d5970cbc24be1a5ac08ec6 Mon Sep 17 00:00:00 2001 From: Clement Ho Date: Fri, 10 Mar 2017 16:30:42 -0600 Subject: Add filter focus for filtered search visual tokens --- .../filtered_search/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); + }); + }); }); })(); -- cgit v1.2.3 From 652d80458af1ea4552ae5095e212ef770a6b229d Mon Sep 17 00:00:00 2001 From: Phil Hughes Date: Fri, 17 Mar 2017 15:38:41 +0000 Subject: 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 -- cgit v1.2.3 From 307d706ca627316472a362ad99da9c743fba48ce Mon Sep 17 00:00:00 2001 From: Joshua Lambert Date: Sat, 18 Mar 2017 11:24:48 -0400 Subject: 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 -- cgit v1.2.3 From 681af5bc4fe646a981c1d0bbbeddef00f5cee3b8 Mon Sep 17 00:00:00 2001 From: Stan Hu Date: Sat, 18 Mar 2017 02:29:25 -0700 Subject: 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 --- app/controllers/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/' -- cgit v1.2.3 From 4ccf874d31253a1354b418579f656b20866eee1d Mon Sep 17 00:00:00 2001 From: James Lopez Date: Sun, 19 Mar 2017 15:19:26 +0100 Subject: 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. -- cgit v1.2.3 From b730fb450f1d5f433cd99dd4fd66d491bd7ac9fa Mon Sep 17 00:00:00 2001 From: James Lopez Date: Sun, 19 Mar 2017 21:06:35 +0100 Subject: 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. -- cgit v1.2.3 From 770a703bd8e68601d166556124d3a4bd5b2a75da Mon Sep 17 00:00:00 2001 From: James Lopez Date: Sun, 19 Mar 2017 21:12:11 +0100 Subject: 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 -- cgit v1.2.3 From ef86353ed9db3b8873a5d10e0974ab1430550654 Mon Sep 17 00:00:00 2001 From: Phil Hughes Date: Mon, 20 Mar 2017 11:05:32 +0000 Subject: 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' }) -- cgit v1.2.3 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: 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, -- cgit v1.2.3 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: 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 -- cgit v1.2.3 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: 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 -- cgit v1.2.3 From 6c6de7f1e69cc736f86f1f76f4e2b3fd2a42b5fd Mon Sep 17 00:00:00 2001 From: Clement Ho Date: Mon, 20 Mar 2017 11:04:15 -0500 Subject: Add additional check for when inputContainer does not exist --- app/assets/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) { -- cgit v1.2.3 From 446b59dd4ee88f8ef86272c39408560a0107c48a Mon Sep 17 00:00:00 2001 From: Filipa Lacerda Date: Mon, 20 Mar 2017 15:36:02 +0000 Subject: Adds tests to new empty and error states --- .../commit/pipelines/pipelines_table.js | 9 ++ .../vue_pipelines_index/components/empty_state.js | 8 +- .../vue_pipelines_index/components/error_state.js | 18 ++-- .../components/navigation_tabs.js | 4 +- .../javascripts/vue_pipelines_index/pipelines.js | 70 +++++++------ app/views/projects/commit/_pipelines_list.haml | 1 + app/views/projects/pipelines/index.html.haml | 7 +- spec/features/projects/pipelines/pipelines_spec.rb | 2 +- .../javascripts/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 +++++ spec/javascripts/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, 460 insertions(+), 49 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: `
    - + diff --git a/app/assets/javascripts/vue_pipelines_index/components/empty_state.js b/app/assets/javascripts/vue_pipelines_index/components/empty_state.js index 78f111c74bc..56b4858f4b4 100644 --- a/app/assets/javascripts/vue_pipelines_index/components/empty_state.js +++ b/app/assets/javascripts/vue_pipelines_index/components/empty_state.js @@ -8,21 +8,15 @@ export default { }, }, - data() { - return { - pipelinesEmptyStateSVG, - }; - }, - template: ` -
    -
    +
    +
    ${pipelinesEmptyStateSVG}
    -
    +

    Build with confidence

    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 f197395a6db..e5d228bddf8 100644 --- a/app/assets/javascripts/vue_pipelines_index/components/error_state.js +++ b/app/assets/javascripts/vue_pipelines_index/components/error_state.js @@ -1,21 +1,15 @@ 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/pipelines.js b/app/assets/javascripts/vue_pipelines_index/pipelines.js index 885ad465179..796fd5b581e 100644 --- a/app/assets/javascripts/vue_pipelines_index/pipelines.js +++ b/app/assets/javascripts/vue_pipelines_index/pipelines.js @@ -1,7 +1,4 @@ -/* global Flash */ -/* eslint-disable no-new */ import Vue from 'vue'; -import '~/flash'; import PipelinesService from './services/pipelines_service'; import eventHub from './event_hub'; import PipelinesTableComponent from '../vue_shared/components/pipelines_table'; @@ -177,14 +174,12 @@ export default { .catch(() => { this.hasError = true; this.pageRequest = false; - new Flash('An error occurred while fetching the pipelines, please reload the page again.'); }); }, }, template: ` -
    +
    - +