diff options
author | Filipa Lacerda <filipa@gitlab.com> | 2017-06-02 13:24:42 +0000 |
---|---|---|
committer | Phil Hughes <me@iamphill.com> | 2017-06-02 13:24:42 +0000 |
commit | 228b73d5e9e68991017dbfc5d072c3831411c383 (patch) | |
tree | cc0435e671819a9e6e51600a2ab82b1659a65aba /app | |
parent | 68112433e1c5537538004f6279cfe83d38aaaa0b (diff) | |
download | gitlab-ce-228b73d5e9e68991017dbfc5d072c3831411c383.tar.gz |
Pipeline show view real time header section
Diffstat (limited to 'app')
16 files changed, 209 insertions, 48 deletions
diff --git a/app/assets/javascripts/commit/pipelines/pipelines_table.js b/app/assets/javascripts/commit/pipelines/pipelines_table.js index 98698143d22..082fbafb740 100644 --- a/app/assets/javascripts/commit/pipelines/pipelines_table.js +++ b/app/assets/javascripts/commit/pipelines/pipelines_table.js @@ -118,7 +118,7 @@ export default Vue.component('pipelines-table', { eventHub.$on('refreshPipelines', this.fetchPipelines); }, - beforeDestroyed() { + beforeDestroy() { eventHub.$off('refreshPipelines'); }, diff --git a/app/assets/javascripts/environments/components/environment.vue b/app/assets/javascripts/environments/components/environment.vue index 86d8fe89010..c9e489dd90e 100644 --- a/app/assets/javascripts/environments/components/environment.vue +++ b/app/assets/javascripts/environments/components/environment.vue @@ -109,7 +109,7 @@ export default { eventHub.$on('postAction', this.postAction); }, - beforeDestroyed() { + beforeDestroy() { eventHub.$off('toggleFolder'); eventHub.$off('postAction'); }, diff --git a/app/assets/javascripts/pipelines/components/header_component.vue b/app/assets/javascripts/pipelines/components/header_component.vue new file mode 100644 index 00000000000..4f6c5c177cf --- /dev/null +++ b/app/assets/javascripts/pipelines/components/header_component.vue @@ -0,0 +1,97 @@ +<script> +import ciHeader from '../../vue_shared/components/header_ci_component.vue'; +import eventHub from '../event_hub'; +import loadingIcon from '../../vue_shared/components/loading_icon.vue'; + +export default { + name: 'PipelineHeaderSection', + props: { + pipeline: { + type: Object, + required: true, + }, + isLoading: { + type: Boolean, + required: true, + }, + }, + components: { + ciHeader, + loadingIcon, + }, + + data() { + return { + actions: this.getActions(), + }; + }, + + computed: { + status() { + return this.pipeline.details && this.pipeline.details.status; + }, + shouldRenderContent() { + return !this.isLoading && Object.keys(this.pipeline).length; + }, + }, + + methods: { + postAction(action) { + const index = this.actions.indexOf(action); + + this.$set(this.actions[index], 'isLoading', true); + + eventHub.$emit('headerPostAction', action); + }, + + getActions() { + const actions = []; + + if (this.pipeline.retry_path) { + actions.push({ + label: 'Retry', + path: this.pipeline.retry_path, + cssClass: 'js-retry-button btn btn-inverted-secondary', + type: 'button', + isLoading: false, + }); + } + + if (this.pipeline.cancel_path) { + actions.push({ + label: 'Cancel running', + path: this.pipeline.cancel_path, + cssClass: 'js-btn-cancel-pipeline btn btn-danger', + type: 'button', + isLoading: false, + }); + } + + return actions; + }, + }, + + watch: { + pipeline() { + this.actions = this.getActions(); + }, + }, +}; +</script> +<template> + <div class="pipeline-header-container"> + <ci-header + v-if="shouldRenderContent" + :status="status" + item-name="Pipeline" + :item-id="pipeline.id" + :time="pipeline.created_at" + :user="pipeline.user" + :actions="actions" + @actionClicked="postAction" + /> + <loading-icon + v-else + size="2"/> + </div> +</template> diff --git a/app/assets/javascripts/pipelines/components/pipeline_url.vue b/app/assets/javascripts/pipelines/components/pipeline_url.vue index b8457fae967..4781a8ff1da 100644 --- a/app/assets/javascripts/pipelines/components/pipeline_url.vue +++ b/app/assets/javascripts/pipelines/components/pipeline_url.vue @@ -33,7 +33,7 @@ export default { <user-avatar-link v-if="user" class="js-pipeline-url-user" - :link-href="pipeline.user.web_url" + :link-href="pipeline.user.path" :img-src="pipeline.user.avatar_url" :tooltip-text="pipeline.user.name" /> diff --git a/app/assets/javascripts/pipelines/pipeline_details_bundle.js b/app/assets/javascripts/pipelines/pipeline_details_bundle.js index 5aab25e0348..bfc416da50b 100644 --- a/app/assets/javascripts/pipelines/pipeline_details_bundle.js +++ b/app/assets/javascripts/pipelines/pipeline_details_bundle.js @@ -1,6 +1,10 @@ +/* global Flash */ + import Vue from 'vue'; import PipelinesMediator from './pipeline_details_mediatior'; import pipelineGraph from './components/graph/graph_component.vue'; +import pipelineHeader from './components/header_component.vue'; +import eventHub from './event_hub'; document.addEventListener('DOMContentLoaded', () => { const dataset = document.querySelector('.js-pipeline-details-vue').dataset; @@ -9,7 +13,8 @@ document.addEventListener('DOMContentLoaded', () => { mediator.fetchPipeline(); - const pipelineGraphApp = new Vue({ + // eslint-disable-next-line + new Vue({ el: '#js-pipeline-graph-vue', data() { return { @@ -29,5 +34,37 @@ document.addEventListener('DOMContentLoaded', () => { }, }); - return pipelineGraphApp; + // eslint-disable-next-line + new Vue({ + el: '#js-pipeline-header-vue', + data() { + return { + mediator, + }; + }, + components: { + pipelineHeader, + }, + created() { + eventHub.$on('headerPostAction', this.postAction); + }, + beforeDestroy() { + eventHub.$off('headerPostAction', this.postAction); + }, + methods: { + postAction(action) { + this.mediator.service.postAction(action.path) + .then(() => this.mediator.refreshPipeline()) + .catch(() => new Flash('An error occurred while making the request.')); + }, + }, + render(createElement) { + return createElement('pipeline-header', { + props: { + isLoading: this.mediator.state.isLoading, + pipeline: this.mediator.store.state.pipeline, + }, + }); + }, + }); }); diff --git a/app/assets/javascripts/pipelines/pipeline_details_mediatior.js b/app/assets/javascripts/pipelines/pipeline_details_mediatior.js index b9a6d5ca5fc..82537ea06f5 100644 --- a/app/assets/javascripts/pipelines/pipeline_details_mediatior.js +++ b/app/assets/javascripts/pipelines/pipeline_details_mediatior.js @@ -26,6 +26,8 @@ export default class pipelinesMediator { if (!Visibility.hidden()) { this.state.isLoading = true; this.poll.makeRequest(); + } else { + this.refreshPipeline(); } Visibility.change(() => { @@ -48,4 +50,10 @@ export default class pipelinesMediator { this.state.isLoading = false; return new Flash('An error occurred while fetching the pipeline.'); } + + refreshPipeline() { + this.service.getPipeline() + .then(response => this.successCallback(response)) + .catch(() => this.errorCallback()); + } } diff --git a/app/assets/javascripts/pipelines/pipelines.js b/app/assets/javascripts/pipelines/pipelines.js index d6952d1ee5f..9f247af1dec 100644 --- a/app/assets/javascripts/pipelines/pipelines.js +++ b/app/assets/javascripts/pipelines/pipelines.js @@ -169,7 +169,7 @@ export default { eventHub.$on('refreshPipelines', this.fetchPipelines); }, - beforeDestroyed() { + beforeDestroy() { eventHub.$off('refreshPipelines'); }, diff --git a/app/assets/javascripts/pipelines/services/pipeline_service.js b/app/assets/javascripts/pipelines/services/pipeline_service.js index f1cc60c1ee0..3e0c52c7726 100644 --- a/app/assets/javascripts/pipelines/services/pipeline_service.js +++ b/app/assets/javascripts/pipelines/services/pipeline_service.js @@ -11,4 +11,9 @@ export default class PipelineService { getPipeline() { return this.pipeline.get(); } + + // eslint-disable-next-line + postAction(endpoint) { + return Vue.http.post(`${endpoint}.json`); + } } diff --git a/app/assets/javascripts/pipelines/services/pipelines_service.js b/app/assets/javascripts/pipelines/services/pipelines_service.js index b21f84b4545..e2285494e62 100644 --- a/app/assets/javascripts/pipelines/services/pipelines_service.js +++ b/app/assets/javascripts/pipelines/services/pipelines_service.js @@ -33,8 +33,6 @@ export default class PipelinesService { /** * Post request for all pipelines actions. - * Endpoint content type needs to be: - * `Content-Type:application/x-www-form-urlencoded` * * @param {String} endpoint * @return {Promise} diff --git a/app/assets/javascripts/vue_shared/components/commit.js b/app/assets/javascripts/vue_shared/components/commit.js index 23bc5fbc034..8e22057e2e9 100644 --- a/app/assets/javascripts/vue_shared/components/commit.js +++ b/app/assets/javascripts/vue_shared/components/commit.js @@ -91,7 +91,7 @@ export default { hasAuthor() { return this.author && this.author.avatar_url && - this.author.web_url && + this.author.path && this.author.username; }, @@ -140,7 +140,7 @@ export default { <user-avatar-link v-if="hasAuthor" class="avatar-image-container" - :link-href="author.web_url" + :link-href="author.path" :img-src="author.avatar_url" :img-alt="userImageAltDescription" :tooltip-text="author.username" diff --git a/app/assets/javascripts/vue_shared/components/header_ci_component.vue b/app/assets/javascripts/vue_shared/components/header_ci_component.vue index fd0dcd716d6..fe6d6a792e7 100644 --- a/app/assets/javascripts/vue_shared/components/header_ci_component.vue +++ b/app/assets/javascripts/vue_shared/components/header_ci_component.vue @@ -1,8 +1,9 @@ <script> import ciIconBadge from './ci_badge_link.vue'; +import loadingIcon from './loading_icon.vue'; import timeagoTooltip from './time_ago_tooltip.vue'; import tooltipMixin from '../mixins/tooltip'; -import userAvatarLink from './user_avatar/user_avatar_link.vue'; +import userAvatarImage from './user_avatar/user_avatar_image.vue'; /** * Renders header component for job and pipeline page based on UI mockups @@ -31,7 +32,8 @@ export default { }, user: { type: Object, - required: true, + required: false, + default: () => ({}), }, actions: { type: Array, @@ -46,8 +48,9 @@ export default { components: { ciIconBadge, + loadingIcon, timeagoTooltip, - userAvatarLink, + userAvatarImage, }, computed: { @@ -58,13 +61,13 @@ export default { methods: { onClickAction(action) { - this.$emit('postAction', action); + this.$emit('actionClicked', action); }, }, }; </script> <template> - <header class="page-content-header top-area"> + <header class="page-content-header"> <section class="header-main-content"> <ci-icon-badge :status="status" /> @@ -79,21 +82,23 @@ export default { by - <user-avatar-link - :link-href="user.web_url" - :img-src="user.avatar_url" - :img-alt="userAvatarAltText" - :tooltip-text="user.name" - :img-size="24" - /> - - <a - :href="user.web_url" - :title="user.email" - class="js-user-link commit-committer-link" - ref="tooltip"> - {{user.name}} - </a> + <template v-if="user"> + <a + :href="user.path" + :title="user.email" + class="js-user-link commit-committer-link" + ref="tooltip"> + + <user-avatar-image + :img-src="user.avatar_url" + :img-alt="userAvatarAltText" + :tooltip-text="user.name" + :img-size="24" + /> + + {{user.name}} + </a> + </template> </section> <section @@ -111,11 +116,17 @@ export default { <button v-else="action.type === 'button'" @click="onClickAction(action)" + :disabled="action.isLoading" :class="action.cssClass" type="button"> {{action.label}} - </button> + <i + v-show="action.isLoading" + class="fa fa-spin fa-spinner" + aria-hidden="true"> + </i> + </button> </template> </section> </header> diff --git a/app/assets/javascripts/vue_shared/components/pipelines_table_row.js b/app/assets/javascripts/vue_shared/components/pipelines_table_row.js index 3283a6bcacc..f60f8eeb43d 100644 --- a/app/assets/javascripts/vue_shared/components/pipelines_table_row.js +++ b/app/assets/javascripts/vue_shared/components/pipelines_table_row.js @@ -83,7 +83,7 @@ export default { } else { commitAuthorInformation = { avatar_url: this.pipeline.commit.author_gravatar_url, - web_url: `mailto:${this.pipeline.commit.author_email}`, + path: `mailto:${this.pipeline.commit.author_email}`, username: this.pipeline.commit.author_name, }; } diff --git a/app/assets/javascripts/vue_shared/components/user_avatar/user_avatar_image.vue b/app/assets/javascripts/vue_shared/components/user_avatar/user_avatar_image.vue index b8db6afda12..cd6f8c7aee4 100644 --- a/app/assets/javascripts/vue_shared/components/user_avatar/user_avatar_image.vue +++ b/app/assets/javascripts/vue_shared/components/user_avatar/user_avatar_image.vue @@ -60,6 +60,12 @@ export default { avatarSizeClass() { return `s${this.size}`; }, + // API response sends null when gravatar is disabled and + // we provide an empty string when we use it inside user avatar link. + // In both cases we should render the defaultAvatarUrl + imageSource() { + return this.imgSrc === '' || this.imgSrc === null ? defaultAvatarUrl : this.imgSrc; + }, }, }; </script> @@ -68,7 +74,7 @@ export default { <img class="avatar" :class="[avatarSizeClass, cssClasses]" - :src="imgSrc" + :src="imageSource" :width="size" :height="size" :alt="imgAlt" diff --git a/app/assets/stylesheets/pages/pipelines.scss b/app/assets/stylesheets/pages/pipelines.scss index cf2e565dd2d..58b458cd837 100644 --- a/app/assets/stylesheets/pages/pipelines.scss +++ b/app/assets/stylesheets/pages/pipelines.scss @@ -984,3 +984,11 @@ width: 12px; } } + +.pipeline-header-container { + min-height: 55px; + + .text-center { + padding-top: 12px; + } +} diff --git a/app/serializers/user_entity.rb b/app/serializers/user_entity.rb index 43754ea94f7..876512b12dc 100644 --- a/app/serializers/user_entity.rb +++ b/app/serializers/user_entity.rb @@ -1,2 +1,7 @@ class UserEntity < API::Entities::UserBasic + include RequestAwareEntity + + expose :path do |user| + user_path(user) + end end diff --git a/app/views/projects/pipelines/_info.html.haml b/app/views/projects/pipelines/_info.html.haml index 8607da8fcdd..673c3370b62 100644 --- a/app/views/projects/pipelines/_info.html.haml +++ b/app/views/projects/pipelines/_info.html.haml @@ -1,18 +1,4 @@ -.page-content-header - .header-main-content - = render 'ci/status/badge', status: @pipeline.detailed_status(current_user), title: @pipeline.status_title - %strong Pipeline ##{@pipeline.id} - triggered #{time_ago_with_tooltip(@pipeline.created_at)} - - if @pipeline.user - by - = user_avatar(user: @pipeline.user, size: 24) - = user_link(@pipeline.user) - .header-action-buttons - - if can?(current_user, :update_pipeline, @pipeline.project) - - if @pipeline.retryable? - = link_to "Retry", retry_namespace_project_pipeline_path(@pipeline.project.namespace, @pipeline.project, @pipeline.id), class: 'js-retry-button btn btn-inverted-secondary', method: :post - - if @pipeline.cancelable? - = link_to "Cancel running", cancel_namespace_project_pipeline_path(@pipeline.project.namespace, @pipeline.project, @pipeline.id), data: { confirm: 'Are you sure?' }, class: 'btn btn-danger', method: :post +#js-pipeline-header-vue.pipeline-header-container - if @commit .commit-box |