Welcome to mirror list, hosted at ThFree Co, Russian Federation.

gitlab.com/gitlab-org/gitlab-foss.git - Unnamed repository; edit this file 'description' to name the repository.
summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorKamil Trzcinski <ayufan@ayufan.eu>2017-05-06 20:02:06 +0300
committerKamil Trzcinski <ayufan@ayufan.eu>2017-05-06 20:04:48 +0300
commitaa440eb1c0947d2dc551c61abbd9d271b9002050 (patch)
tree907f60233034d904edd1b5d8ea4c9d6df9278ec5 /app/assets/javascripts/pipelines
parentc17e6a6c68b0412b3433632802b852db474a7b30 (diff)
Single commit squash of all changes for https://gitlab.com/gitlab-org/gitlab-ce/merge_requests/10878
It's needed due to https://gitlab.com/gitlab-org/gitlab-ce/merge_requests/10777 being merged with squash.
Diffstat (limited to 'app/assets/javascripts/pipelines')
-rw-r--r--app/assets/javascripts/pipelines/components/graph/action_component.vue59
-rw-r--r--app/assets/javascripts/pipelines/components/graph/dropdown_action_component.vue56
-rw-r--r--app/assets/javascripts/pipelines/components/graph/dropdown_job_component.vue86
-rw-r--r--app/assets/javascripts/pipelines/components/graph/graph_component.vue92
-rw-r--r--app/assets/javascripts/pipelines/components/graph/job_component.vue124
-rw-r--r--app/assets/javascripts/pipelines/components/graph/job_name_component.vue37
-rw-r--r--app/assets/javascripts/pipelines/components/graph/stage_column_component.vue64
-rw-r--r--app/assets/javascripts/pipelines/components/stage.vue6
-rw-r--r--app/assets/javascripts/pipelines/graph_bundle.js10
-rw-r--r--app/assets/javascripts/pipelines/services/pipeline_service.js14
-rw-r--r--app/assets/javascripts/pipelines/stores/pipeline_store.js11
11 files changed, 556 insertions, 3 deletions
diff --git a/app/assets/javascripts/pipelines/components/graph/action_component.vue b/app/assets/javascripts/pipelines/components/graph/action_component.vue
new file mode 100644
index 00000000000..14e485791ea
--- /dev/null
+++ b/app/assets/javascripts/pipelines/components/graph/action_component.vue
@@ -0,0 +1,59 @@
+<script>
+ import getActionIcon from '../../../vue_shared/ci_action_icons';
+ import tooltipMixin from '../../../vue_shared/mixins/tooltip';
+
+ /**
+ * Renders either a cancel, retry or play icon pointing to the given path.
+ * TODO: Remove UJS from here and use an async request instead.
+ */
+ export default {
+ props: {
+ tooltipText: {
+ type: String,
+ required: true,
+ },
+
+ link: {
+ type: String,
+ required: true,
+ },
+
+ actionMethod: {
+ type: String,
+ required: true,
+ },
+
+ actionIcon: {
+ type: String,
+ required: true,
+ },
+ },
+
+ mixins: [
+ tooltipMixin,
+ ],
+
+ computed: {
+ actionIconSvg() {
+ return getActionIcon(this.actionIcon);
+ },
+ },
+ };
+</script>
+<template>
+ <a
+ :data-method="actionMethod"
+ :title="tooltipText"
+ :href="link"
+ ref="tooltip"
+ class="ci-action-icon-container"
+ data-toggle="tooltip"
+ data-container="body">
+
+ <i
+ class="ci-action-icon-wrapper"
+ v-html="actionIconSvg"
+ aria-hidden="true"
+ />
+ </a>
+</template>
diff --git a/app/assets/javascripts/pipelines/components/graph/dropdown_action_component.vue b/app/assets/javascripts/pipelines/components/graph/dropdown_action_component.vue
new file mode 100644
index 00000000000..19cafff4e1c
--- /dev/null
+++ b/app/assets/javascripts/pipelines/components/graph/dropdown_action_component.vue
@@ -0,0 +1,56 @@
+<script>
+ import getActionIcon from '../../../vue_shared/ci_action_icons';
+ import tooltipMixin from '../../../vue_shared/mixins/tooltip';
+
+ /**
+ * Renders either a cancel, retry or play icon pointing to the given path.
+ * TODO: Remove UJS from here and use an async request instead.
+ */
+ export default {
+ props: {
+ tooltipText: {
+ type: String,
+ required: true,
+ },
+
+ link: {
+ type: String,
+ required: true,
+ },
+
+ actionMethod: {
+ type: String,
+ required: true,
+ },
+
+ actionIcon: {
+ type: String,
+ required: true,
+ },
+ },
+
+ mixins: [
+ tooltipMixin,
+ ],
+
+ computed: {
+ actionIconSvg() {
+ return getActionIcon(this.actionIcon);
+ },
+ },
+ };
+</script>
+<template>
+ <a
+ :data-method="actionMethod"
+ :title="tooltipText"
+ :href="link"
+ ref="tooltip"
+ rel="nofollow"
+ class="ci-action-icon-wrapper js-ci-status-icon"
+ data-toggle="tooltip"
+ data-container="body"
+ v-html="actionIconSvg"
+ aria-label="Job's action">
+ </a>
+</template>
diff --git a/app/assets/javascripts/pipelines/components/graph/dropdown_job_component.vue b/app/assets/javascripts/pipelines/components/graph/dropdown_job_component.vue
new file mode 100644
index 00000000000..d597af8dfb5
--- /dev/null
+++ b/app/assets/javascripts/pipelines/components/graph/dropdown_job_component.vue
@@ -0,0 +1,86 @@
+<script>
+ import jobNameComponent from './job_name_component.vue';
+ import jobComponent from './job_component.vue';
+ import tooltipMixin from '../../../vue_shared/mixins/tooltip';
+
+ /**
+ * Renders the dropdown for the pipeline graph.
+ *
+ * The following object should be provided as `job`:
+ *
+ * {
+ * "id": 4256,
+ * "name": "test",
+ * "status": {
+ * "icon": "icon_status_success",
+ * "text": "passed",
+ * "label": "passed",
+ * "group": "success",
+ * "details_path": "/root/ci-mock/builds/4256",
+ * "action": {
+ * "icon": "icon_action_retry",
+ * "title": "Retry",
+ * "path": "/root/ci-mock/builds/4256/retry",
+ * "method": "post"
+ * }
+ * }
+ * }
+ */
+ export default {
+ props: {
+ job: {
+ type: Object,
+ required: true,
+ },
+ },
+
+ mixins: [
+ tooltipMixin,
+ ],
+
+ components: {
+ jobComponent,
+ jobNameComponent,
+ },
+
+ computed: {
+ tooltipText() {
+ return `${this.job.name} - ${this.job.status.label}`;
+ },
+ },
+ };
+</script>
+<template>
+ <div>
+ <button
+ type="button"
+ data-toggle="dropdown"
+ data-container="body"
+ class="dropdown-menu-toggle build-content"
+ :title="tooltipText"
+ ref="tooltip">
+
+ <job-name-component
+ :name="job.name"
+ :status="job.status" />
+
+ <span class="dropdown-counter-badge">
+ {{job.size}}
+ </span>
+ </button>
+
+ <ul class="dropdown-menu big-pipeline-graph-dropdown-menu js-grouped-pipeline-dropdown">
+ <li class="scrollable-menu">
+ <ul>
+ <li v-for="item in job.jobs">
+ <job-component
+ :job="item"
+ :is-dropdown="true"
+ css-class-job-name="mini-pipeline-graph-dropdown-item"
+ />
+ </li>
+ </ul>
+ </li>
+ </ul>
+ </div>
+</template>
diff --git a/app/assets/javascripts/pipelines/components/graph/graph_component.vue b/app/assets/javascripts/pipelines/components/graph/graph_component.vue
new file mode 100644
index 00000000000..a84161ef5e7
--- /dev/null
+++ b/app/assets/javascripts/pipelines/components/graph/graph_component.vue
@@ -0,0 +1,92 @@
+<script>
+ /* global Flash */
+ import Visibility from 'visibilityjs';
+ import Poll from '../../../lib/utils/poll';
+ import PipelineService from '../../services/pipeline_service';
+ import PipelineStore from '../../stores/pipeline_store';
+ import stageColumnComponent from './stage_column_component.vue';
+ import '../../../flash';
+
+ export default {
+ components: {
+ stageColumnComponent,
+ },
+
+ data() {
+ const DOMdata = document.getElementById('js-pipeline-graph-vue').dataset;
+ const store = new PipelineStore();
+
+ return {
+ isLoading: false,
+ endpoint: DOMdata.endpoint,
+ store,
+ state: store.state,
+ };
+ },
+
+ created() {
+ this.service = new PipelineService(this.endpoint);
+
+ const poll = new Poll({
+ resource: this.service,
+ method: 'getPipeline',
+ successCallback: this.successCallback,
+ errorCallback: this.errorCallback,
+ });
+
+ if (!Visibility.hidden()) {
+ this.isLoading = true;
+ poll.makeRequest();
+ }
+
+ Visibility.change(() => {
+ if (!Visibility.hidden()) {
+ poll.restart();
+ } else {
+ poll.stop();
+ }
+ });
+ },
+
+ methods: {
+ successCallback(response) {
+ const data = response.json();
+
+ this.isLoading = false;
+ this.store.storeGraph(data.details.stages);
+ },
+
+ errorCallback() {
+ this.isLoading = false;
+ return new Flash('An error occurred while fetching the pipeline.');
+ },
+
+ capitalizeStageName(name) {
+ return name.charAt(0).toUpperCase() + name.slice(1);
+ },
+ },
+ };
+</script>
+<template>
+ <div class="build-content middle-block js-pipeline-graph">
+ <div class="pipeline-visualization pipeline-graph">
+ <div class="text-center">
+ <i
+ v-if="isLoading"
+ class="loading-icon fa fa-spin fa-spinner fa-3x"
+ aria-label="Loading"
+ aria-hidden="true" />
+ </div>
+
+ <ul
+ v-if="!isLoading"
+ class="stage-column-list">
+ <stage-column-component
+ v-for="stage in state.graph"
+ :title="capitalizeStageName(stage.name)"
+ :jobs="stage.groups"
+ :key="stage.name"/>
+ </ul>
+ </div>
+ </div>
+</template>
diff --git a/app/assets/javascripts/pipelines/components/graph/job_component.vue b/app/assets/javascripts/pipelines/components/graph/job_component.vue
new file mode 100644
index 00000000000..b39c936101e
--- /dev/null
+++ b/app/assets/javascripts/pipelines/components/graph/job_component.vue
@@ -0,0 +1,124 @@
+<script>
+ import actionComponent from './action_component.vue';
+ import dropdownActionComponent from './dropdown_action_component.vue';
+ import jobNameComponent from './job_name_component.vue';
+ import tooltipMixin from '../../../vue_shared/mixins/tooltip';
+
+ /**
+ * Renders the badge for the pipeline graph and the job's dropdown.
+ *
+ * The following object should be provided as `job`:
+ *
+ * {
+ * "id": 4256,
+ * "name": "test",
+ * "status": {
+ * "icon": "icon_status_success",
+ * "text": "passed",
+ * "label": "passed",
+ * "group": "success",
+ * "details_path": "/root/ci-mock/builds/4256",
+ * "action": {
+ * "icon": "icon_action_retry",
+ * "title": "Retry",
+ * "path": "/root/ci-mock/builds/4256/retry",
+ * "method": "post"
+ * }
+ * }
+ * }
+ */
+
+ export default {
+ props: {
+ job: {
+ type: Object,
+ required: true,
+ },
+
+ cssClassJobName: {
+ type: String,
+ required: false,
+ default: '',
+ },
+
+ isDropdown: {
+ type: Boolean,
+ required: false,
+ default: false,
+ },
+ },
+
+ components: {
+ actionComponent,
+ dropdownActionComponent,
+ jobNameComponent,
+ },
+
+ mixins: [
+ tooltipMixin,
+ ],
+
+ computed: {
+ tooltipText() {
+ return `${this.job.name} - ${this.job.status.label}`;
+ },
+
+ /**
+ * Verifies if the provided job has an action path
+ *
+ * @return {Boolean}
+ */
+ hasAction() {
+ return this.job.status && this.job.status.action && this.job.status.action.path;
+ },
+ },
+ };
+</script>
+<template>
+ <div>
+ <a
+ v-if="job.status.details_path"
+ :href="job.status.details_path"
+ :title="tooltipText"
+ :class="cssClassJobName"
+ ref="tooltip"
+ data-toggle="tooltip"
+ data-container="body">
+
+ <job-name-component
+ :name="job.name"
+ :status="job.status"
+ />
+ </a>
+
+ <div
+ v-else
+ :title="tooltipText"
+ :class="cssClassJobName"
+ ref="tooltip"
+ data-toggle="tooltip"
+ data-container="body">
+
+ <job-name-component
+ :name="job.name"
+ :status="job.status"
+ />
+ </div>
+
+ <action-component
+ v-if="hasAction && !isDropdown"
+ :tooltip-text="job.status.action.title"
+ :link="job.status.action.path"
+ :action-icon="job.status.action.icon"
+ :action-method="job.status.action.method"
+ />
+
+ <dropdown-action-component
+ v-if="hasAction && isDropdown"
+ :tooltip-text="job.status.action.title"
+ :link="job.status.action.path"
+ :action-icon="job.status.action.icon"
+ :action-method="job.status.action.method"
+ />
+ </div>
+</template>
diff --git a/app/assets/javascripts/pipelines/components/graph/job_name_component.vue b/app/assets/javascripts/pipelines/components/graph/job_name_component.vue
new file mode 100644
index 00000000000..d8856e10668
--- /dev/null
+++ b/app/assets/javascripts/pipelines/components/graph/job_name_component.vue
@@ -0,0 +1,37 @@
+<script>
+ import ciIcon from '../../../vue_shared/components/ci_icon.vue';
+
+ /**
+ * Component that renders both the CI icon status and the job name.
+ * Used in
+ * - Badge component
+ * - Dropdown badge components
+ */
+ export default {
+ props: {
+ name: {
+ type: String,
+ required: true,
+ },
+
+ status: {
+ type: Object,
+ required: true,
+ },
+ },
+
+ components: {
+ ciIcon,
+ },
+ };
+</script>
+<template>
+ <span>
+ <ci-icon
+ :status="status" />
+
+ <span class="ci-status-text">
+ {{name}}
+ </span>
+ </span>
+</template>
diff --git a/app/assets/javascripts/pipelines/components/graph/stage_column_component.vue b/app/assets/javascripts/pipelines/components/graph/stage_column_component.vue
new file mode 100644
index 00000000000..b7da185e280
--- /dev/null
+++ b/app/assets/javascripts/pipelines/components/graph/stage_column_component.vue
@@ -0,0 +1,64 @@
+<script>
+import jobComponent from './job_component.vue';
+import dropdownJobComponent from './dropdown_job_component.vue';
+
+export default {
+ props: {
+ title: {
+ type: String,
+ required: true,
+ },
+
+ jobs: {
+ type: Array,
+ required: true,
+ },
+ },
+
+ components: {
+ jobComponent,
+ dropdownJobComponent,
+ },
+
+ methods: {
+ firstJob(list) {
+ return list[0];
+ },
+
+ jobId(job) {
+ return `ci-badge-${job.name}`;
+ },
+ },
+};
+</script>
+<template>
+ <li class="stage-column">
+ <div class="stage-name">
+ {{title}}
+ </div>
+ <div class="builds-container">
+ <ul>
+ <li
+ v-for="job in jobs"
+ :key="job.id"
+ class="build"
+ :id="jobId(job)">
+
+ <div class="curve"></div>
+
+ <job-component
+ v-if="job.size === 1"
+ :job="job"
+ css-class-job-name="build-content"
+ />
+
+ <dropdown-job-component
+ v-if="job.size > 1"
+ :job="job"
+ />
+
+ </li>
+ </ul>
+ </div>
+ </li>
+</template>
diff --git a/app/assets/javascripts/pipelines/components/stage.vue b/app/assets/javascripts/pipelines/components/stage.vue
index 2e485f951a1..dc42223269d 100644
--- a/app/assets/javascripts/pipelines/components/stage.vue
+++ b/app/assets/javascripts/pipelines/components/stage.vue
@@ -14,7 +14,7 @@
*/
/* global Flash */
-import StatusIconEntityMap from '../../ci_status_icons';
+import { statusCssClasses, borderlessStatusIconEntityMap } from '../../vue_shared/ci_status_icons';
export default {
props: {
@@ -109,11 +109,11 @@ export default {
},
triggerButtonClass() {
- return `ci-status-icon-${this.stage.status.group}`;
+ return `ci-status-icon-${statusCssClasses[this.stage.status.icon]}`;
},
svgIcon() {
- return StatusIconEntityMap[this.stage.status.icon];
+ return borderlessStatusIconEntityMap[this.stage.status.icon];
},
},
};
diff --git a/app/assets/javascripts/pipelines/graph_bundle.js b/app/assets/javascripts/pipelines/graph_bundle.js
new file mode 100644
index 00000000000..b7a6b5d8479
--- /dev/null
+++ b/app/assets/javascripts/pipelines/graph_bundle.js
@@ -0,0 +1,10 @@
+import Vue from 'vue';
+import pipelineGraph from './components/graph/graph_component.vue';
+
+document.addEventListener('DOMContentLoaded', () => new Vue({
+ el: '#js-pipeline-graph-vue',
+ components: {
+ pipelineGraph,
+ },
+ render: createElement => createElement('pipeline-graph'),
+}));
diff --git a/app/assets/javascripts/pipelines/services/pipeline_service.js b/app/assets/javascripts/pipelines/services/pipeline_service.js
new file mode 100644
index 00000000000..f1cc60c1ee0
--- /dev/null
+++ b/app/assets/javascripts/pipelines/services/pipeline_service.js
@@ -0,0 +1,14 @@
+import Vue from 'vue';
+import VueResource from 'vue-resource';
+
+Vue.use(VueResource);
+
+export default class PipelineService {
+ constructor(endpoint) {
+ this.pipeline = Vue.resource(endpoint);
+ }
+
+ getPipeline() {
+ return this.pipeline.get();
+ }
+}
diff --git a/app/assets/javascripts/pipelines/stores/pipeline_store.js b/app/assets/javascripts/pipelines/stores/pipeline_store.js
new file mode 100644
index 00000000000..86ab50d8f1e
--- /dev/null
+++ b/app/assets/javascripts/pipelines/stores/pipeline_store.js
@@ -0,0 +1,11 @@
+export default class PipelineStore {
+ constructor() {
+ this.state = {};
+
+ this.state.graph = [];
+ }
+
+ storeGraph(graph = []) {
+ this.state.graph = graph;
+ }
+}