summaryrefslogtreecommitdiff
path: root/app
diff options
context:
space:
mode:
authorFilipa Lacerda <filipa@gitlab.com>2017-06-02 13:24:42 +0000
committerPhil Hughes <me@iamphill.com>2017-06-02 13:24:42 +0000
commit228b73d5e9e68991017dbfc5d072c3831411c383 (patch)
treecc0435e671819a9e6e51600a2ab82b1659a65aba /app
parent68112433e1c5537538004f6279cfe83d38aaaa0b (diff)
downloadgitlab-ce-228b73d5e9e68991017dbfc5d072c3831411c383.tar.gz
Pipeline show view real time header section
Diffstat (limited to 'app')
-rw-r--r--app/assets/javascripts/commit/pipelines/pipelines_table.js2
-rw-r--r--app/assets/javascripts/environments/components/environment.vue2
-rw-r--r--app/assets/javascripts/pipelines/components/header_component.vue97
-rw-r--r--app/assets/javascripts/pipelines/components/pipeline_url.vue2
-rw-r--r--app/assets/javascripts/pipelines/pipeline_details_bundle.js41
-rw-r--r--app/assets/javascripts/pipelines/pipeline_details_mediatior.js8
-rw-r--r--app/assets/javascripts/pipelines/pipelines.js2
-rw-r--r--app/assets/javascripts/pipelines/services/pipeline_service.js5
-rw-r--r--app/assets/javascripts/pipelines/services/pipelines_service.js2
-rw-r--r--app/assets/javascripts/vue_shared/components/commit.js4
-rw-r--r--app/assets/javascripts/vue_shared/components/header_ci_component.vue53
-rw-r--r--app/assets/javascripts/vue_shared/components/pipelines_table_row.js2
-rw-r--r--app/assets/javascripts/vue_shared/components/user_avatar/user_avatar_image.vue8
-rw-r--r--app/assets/stylesheets/pages/pipelines.scss8
-rw-r--r--app/serializers/user_entity.rb5
-rw-r--r--app/views/projects/pipelines/_info.html.haml16
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