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.1 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.1 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.1 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.1 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.1 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.1 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.1 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.1 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.1 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.1 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.1 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.1 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.1 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.1 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.1 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: ` -
    +
    - +