diff options
Diffstat (limited to 'app/assets/javascripts/pipelines/components')
18 files changed, 698 insertions, 651 deletions
diff --git a/app/assets/javascripts/pipelines/components/async_button.vue b/app/assets/javascripts/pipelines/components/async_button.vue index 16cc0761fc1..77553ca67cc 100644 --- a/app/assets/javascripts/pipelines/components/async_button.vue +++ b/app/assets/javascripts/pipelines/components/async_button.vue @@ -1,67 +1,67 @@ <script> -/* eslint-disable no-new, no-alert */ + /* eslint-disable no-alert */ -import eventHub from '../event_hub'; -import loadingIcon from '../../vue_shared/components/loading_icon.vue'; -import tooltip from '../../vue_shared/directives/tooltip'; + import eventHub from '../event_hub'; + import loadingIcon from '../../vue_shared/components/loading_icon.vue'; + import icon from '../../vue_shared/components/icon.vue'; + import tooltip from '../../vue_shared/directives/tooltip'; -export default { - props: { - endpoint: { - type: String, - required: true, + export default { + directives: { + tooltip, }, - title: { - type: String, - required: true, + components: { + loadingIcon, + icon, }, - icon: { - type: String, - required: true, + props: { + endpoint: { + type: String, + required: true, + }, + title: { + type: String, + required: true, + }, + icon: { + type: String, + required: true, + }, + cssClass: { + type: String, + required: true, + }, + confirmActionMessage: { + type: String, + required: false, + default: '', + }, }, - cssClass: { - type: String, - required: true, + data() { + return { + isLoading: false, + }; }, - confirmActionMessage: { - type: String, - required: false, + computed: { + buttonClass() { + return `btn ${this.cssClass}`; + }, }, - }, - directives: { - tooltip, - }, - components: { - loadingIcon, - }, - data() { - return { - isLoading: false, - }; - }, - computed: { - iconClass() { - return `fa fa-${this.icon}`; - }, - buttonClass() { - return `btn ${this.cssClass}`; - }, - }, - methods: { - onClick() { - if (this.confirmActionMessage && confirm(this.confirmActionMessage)) { - this.makeRequest(); - } else if (!this.confirmActionMessage) { - this.makeRequest(); - } - }, - makeRequest() { - this.isLoading = true; + methods: { + onClick() { + if (this.confirmActionMessage !== '' && confirm(this.confirmActionMessage)) { + this.makeRequest(); + } else if (this.confirmActionMessage === '') { + this.makeRequest(); + } + }, + makeRequest() { + this.isLoading = true; - eventHub.$emit('postAction', this.endpoint); + eventHub.$emit('postAction', this.endpoint); + }, }, - }, -}; + }; </script> <template> @@ -75,10 +75,9 @@ export default { data-container="body" data-placement="top" :disabled="isLoading"> - <i - :class="iconClass" - aria-hidden="true"> - </i> + <icon + :name="icon" + /> <loading-icon v-if="isLoading" /> </button> </template> diff --git a/app/assets/javascripts/pipelines/components/empty_state.vue b/app/assets/javascripts/pipelines/components/empty_state.vue index 78322f30685..dfaa2574091 100644 --- a/app/assets/javascripts/pipelines/components/empty_state.vue +++ b/app/assets/javascripts/pipelines/components/empty_state.vue @@ -26,13 +26,15 @@ {{ s__("Pipelines|Build with confidence") }} </h4> <p> - {{ s__("Pipelines|Continous Integration can help catch bugs by running your tests automatically, while Continuous Deployment can help you deliver code to your product environment.") }} + {{ s__(`Pipelines|Continous Integration can help +catch bugs by running your tests automatically, +while Continuous Deployment can help you deliver code to your product environment.`) }} </p> <div class="text-center"> <a :href="helpPagePath" class="btn btn-info" - > + > {{ s__("Pipelines|Get started with Pipelines") }} </a> </div> diff --git a/app/assets/javascripts/pipelines/components/graph/action_component.vue b/app/assets/javascripts/pipelines/components/graph/action_component.vue index 19d8e1f49cf..d7effb27bff 100644 --- a/app/assets/javascripts/pipelines/components/graph/action_component.vue +++ b/app/assets/javascripts/pipelines/components/graph/action_component.vue @@ -7,6 +7,14 @@ * TODO: Remove UJS from here and use an async request instead. */ export default { + components: { + icon, + }, + + directives: { + tooltip, + }, + props: { tooltipText: { type: String, @@ -29,14 +37,6 @@ }, }, - components: { - icon, - }, - - directives: { - tooltip, - }, - computed: { cssClass() { const actionIconDash = dasherize(this.actionIcon); @@ -53,7 +53,8 @@ :href="link" class="ci-action-icon-container ci-action-icon-wrapper" :class="cssClass" - data-container="body"> - <icon :name="actionIcon"/> + data-container="body" + > + <icon :name="actionIcon" /> </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 index 1c0944d45fc..7c4fd65e36f 100644 --- a/app/assets/javascripts/pipelines/components/graph/dropdown_action_component.vue +++ b/app/assets/javascripts/pipelines/components/graph/dropdown_action_component.vue @@ -7,6 +7,13 @@ * TODO: Remove UJS from here and use an async request instead. */ export default { + components: { + icon, + }, + + directives: { + tooltip, + }, props: { tooltipText: { type: String, @@ -28,14 +35,6 @@ required: true, }, }, - - components: { - icon, - }, - - directives: { - tooltip, - }, }; </script> <template> @@ -47,7 +46,8 @@ rel="nofollow" class="ci-action-icon-wrapper js-ci-status-icon" data-container="body" - aria-label="Job's action"> - <icon :name="actionIcon"/> + aria-label="Job's action" + > + <icon :name="actionIcon" /> </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 index 7006d05e7b2..b86e95f0b4a 100644 --- a/app/assets/javascripts/pipelines/components/graph/dropdown_job_component.vue +++ b/app/assets/javascripts/pipelines/components/graph/dropdown_job_component.vue @@ -27,13 +27,6 @@ * } */ export default { - props: { - job: { - type: Object, - required: true, - }, - }, - directives: { tooltip, }, @@ -43,12 +36,23 @@ jobNameComponent, }, + props: { + job: { + type: Object, + required: true, + }, + }, + computed: { tooltipText() { return `${this.job.name} - ${this.job.status.label}`; }, }, + mounted() { + this.stopDropdownClickPropagation(); + }, + methods: { /** * When the user right clicks or cmd/ctrl + click in the job name @@ -59,16 +63,13 @@ * target the click event of this component. */ stopDropdownClickPropagation() { - $(this.$el.querySelectorAll('.js-grouped-pipeline-dropdown a.mini-pipeline-graph-dropdown-item')) + $(this.$el + .querySelectorAll('.js-grouped-pipeline-dropdown a.mini-pipeline-graph-dropdown-item')) .on('click', (e) => { e.stopPropagation(); }); }, }, - - mounted() { - this.stopDropdownClickPropagation(); - }, }; </script> <template> @@ -83,22 +84,25 @@ <job-name-component :name="job.name" - :status="job.status" /> + :status="job.status" + /> <span class="dropdown-counter-badge"> - {{job.size}} + {{ 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"> + <li + v-for="(item, i) in job.jobs" + :key="i"> <job-component :job="item" :is-dropdown="true" css-class-job-name="mini-pipeline-graph-dropdown-item" - /> + /> </li> </ul> </li> diff --git a/app/assets/javascripts/pipelines/components/graph/graph_component.vue b/app/assets/javascripts/pipelines/components/graph/graph_component.vue index 66bc1d1979c..a1f58580318 100644 --- a/app/assets/javascripts/pipelines/components/graph/graph_component.vue +++ b/app/assets/javascripts/pipelines/components/graph/graph_component.vue @@ -1,9 +1,13 @@ <script> import loadingIcon from '~/vue_shared/components/loading_icon.vue'; - import '~/flash'; import stageColumnComponent from './stage_column_component.vue'; export default { + components: { + stageColumnComponent, + loadingIcon, + }, + props: { isLoading: { type: Boolean, @@ -15,11 +19,6 @@ }, }, - components: { - stageColumnComponent, - loadingIcon, - }, - computed: { graph() { return this.pipeline.details && this.pipeline.details.stages; @@ -58,7 +57,7 @@ <loading-icon v-if="isLoading" size="3" - /> + /> </div> <ul @@ -70,7 +69,8 @@ :jobs="stage.groups" :key="stage.name" :stage-connector-class="stageConnectorClass(index, stage)" - :is-first-column="isFirstColumn(index)"/> + :is-first-column="isFirstColumn(index)" + /> </ul> </div> </div> diff --git a/app/assets/javascripts/pipelines/components/graph/job_component.vue b/app/assets/javascripts/pipelines/components/graph/job_component.vue index b01c799643c..9b136573135 100644 --- a/app/assets/javascripts/pipelines/components/graph/job_component.vue +++ b/app/assets/javascripts/pipelines/components/graph/job_component.vue @@ -29,6 +29,15 @@ */ export default { + components: { + actionComponent, + dropdownActionComponent, + jobNameComponent, + }, + + directives: { + tooltip, + }, props: { job: { type: Object, @@ -48,16 +57,6 @@ }, }, - components: { - actionComponent, - dropdownActionComponent, - jobNameComponent, - }, - - directives: { - tooltip, - }, - computed: { status() { return this.job && this.job.status ? this.job.status : {}; @@ -102,12 +101,12 @@ :class="cssClassJobName" data-container="body" class="js-pipeline-graph-job-link" - > + > <job-name-component :name="job.name" :status="job.status" - /> + /> </a> <div @@ -117,12 +116,12 @@ :title="tooltipText" :class="cssClassJobName" data-container="body" - > + > <job-name-component :name="job.name" :status="job.status" - /> + /> </div> <action-component @@ -131,7 +130,7 @@ :link="status.action.path" :action-icon="status.action.icon" :action-method="status.action.method" - /> + /> <dropdown-action-component v-if="hasAction && isDropdown" @@ -139,6 +138,6 @@ :link="status.action.path" :action-icon="status.action.icon" :action-method="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 index f46d21bd6d7..14f4964a406 100644 --- a/app/assets/javascripts/pipelines/components/graph/job_name_component.vue +++ b/app/assets/javascripts/pipelines/components/graph/job_name_component.vue @@ -8,6 +8,9 @@ * - Dropdown badge components */ export default { + components: { + ciIcon, + }, props: { name: { type: String, @@ -19,19 +22,14 @@ required: true, }, }, - - components: { - ciIcon, - }, }; </script> <template> <span class="ci-job-name-component"> - <ci-icon - :status="status" /> + <ci-icon :status="status" /> <span class="ci-status-text"> - {{name}} + {{ 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 index 9b1bbb0906f..e027f08ff5c 100644 --- a/app/assets/javascripts/pipelines/components/graph/stage_column_component.vue +++ b/app/assets/javascripts/pipelines/components/graph/stage_column_component.vue @@ -1,58 +1,58 @@ <script> -import jobComponent from './job_component.vue'; -import dropdownJobComponent from './dropdown_job_component.vue'; + import jobComponent from './job_component.vue'; + import dropdownJobComponent from './dropdown_job_component.vue'; -export default { - props: { - title: { - type: String, - required: true, + export default { + components: { + jobComponent, + dropdownJobComponent, }, - jobs: { - type: Array, - required: true, - }, - - isFirstColumn: { - type: Boolean, - required: false, - default: false, - }, + props: { + title: { + type: String, + required: true, + }, - stageConnectorClass: { - type: String, - required: false, - default: '', - }, - }, + jobs: { + type: Array, + required: true, + }, - components: { - jobComponent, - dropdownJobComponent, - }, + isFirstColumn: { + type: Boolean, + required: false, + default: false, + }, - methods: { - firstJob(list) { - return list[0]; + stageConnectorClass: { + type: String, + required: false, + default: '', + }, }, - jobId(job) { - return `ci-badge-${job.name}`; - }, + methods: { + firstJob(list) { + return list[0]; + }, + + jobId(job) { + return `ci-badge-${job.name}`; + }, - buildConnnectorClass(index) { - return index === 0 && !this.isFirstColumn ? 'left-connector' : ''; + buildConnnectorClass(index) { + return index === 0 && !this.isFirstColumn ? 'left-connector' : ''; + }, }, - }, -}; + }; </script> <template> <li class="stage-column" :class="stageConnectorClass"> <div class="stage-name"> - {{title}} + {{ title }} </div> <div class="builds-container"> <ul> @@ -61,7 +61,8 @@ export default { :key="job.id" class="build" :class="buildConnnectorClass(index)" - :id="jobId(job)"> + :id="jobId(job)" + > <div class="curve"></div> @@ -69,12 +70,12 @@ export default { 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> diff --git a/app/assets/javascripts/pipelines/components/header_component.vue b/app/assets/javascripts/pipelines/components/header_component.vue index 2a1ecac3707..e08c2092680 100644 --- a/app/assets/javascripts/pipelines/components/header_component.vue +++ b/app/assets/javascripts/pipelines/components/header_component.vue @@ -1,82 +1,81 @@ <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'; + 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, + export default { + name: 'PipelineHeaderSection', + components: { + ciHeader, + loadingIcon, }, - isLoading: { - type: Boolean, - required: true, + 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; + data() { + return { + actions: this.getActions(), + }; }, - shouldRenderContent() { - return !this.isLoading && Object.keys(this.pipeline).length; + + 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); + watch: { + pipeline() { + this.actions = this.getActions(); + }, + }, - this.$set(this.actions[index], 'isLoading', true); + methods: { + postAction(action) { + const index = this.actions.indexOf(action); - eventHub.$emit('headerPostAction', action); - }, + this.$set(this.actions[index], 'isLoading', true); - getActions() { - const actions = []; + eventHub.$emit('headerPostAction', action); + }, - 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, - }); - } + getActions() { + const actions = []; - 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, - }); - } + 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, + }); + } - return actions; - }, - }, + 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, + }); + } - watch: { - pipeline() { - this.actions = this.getActions(); + return actions; + }, }, - }, -}; + }; </script> <template> <div class="pipeline-header-container"> @@ -89,9 +88,11 @@ export default { :user="pipeline.user" :actions="actions" @actionClicked="postAction" - /> + /> <loading-icon v-if="isLoading" - size="2"/> + size="2" + class="prepend-top-default append-bottom-default" + /> </div> </template> diff --git a/app/assets/javascripts/pipelines/components/pipeline_url.vue b/app/assets/javascripts/pipelines/components/pipeline_url.vue index 9da0aac50a1..ceb4d9ca604 100644 --- a/app/assets/javascripts/pipelines/components/pipeline_url.vue +++ b/app/assets/javascripts/pipelines/components/pipeline_url.vue @@ -4,6 +4,13 @@ import popover from '../../vue_shared/directives/popover'; export default { + components: { + userAvatarLink, + }, + directives: { + tooltip, + popover, + }, props: { pipeline: { type: Object, @@ -14,13 +21,6 @@ required: true, }, }, - components: { - userAvatarLink, - }, - directives: { - tooltip, - popover, - }, computed: { user() { return this.pipeline.user; @@ -30,8 +30,16 @@ html: true, trigger: 'focus', placement: 'top', - title: '<div class="autodevops-title">This pipeline makes use of a predefined CI/CD configuration enabled by <b>Auto DevOps.</b></div>', - content: `<a class="autodevops-link" href="${this.autoDevopsHelpPath}" target="_blank" rel="noopener noreferrer nofollow">Learn more about Auto DevOps</a>`, + title: `<div class="autodevops-title"> + This pipeline makes use of a predefined CI/CD configuration enabled by <b>Auto DevOps.</b> + </div>`, + content: `<a + class="autodevops-link" + href="${this.autoDevopsHelpPath}" + target="_blank" + rel="noopener noreferrer nofollow"> + Learn more about Auto DevOps + </a>`, }; }, }, @@ -42,7 +50,7 @@ <a :href="pipeline.path" class="js-pipeline-url-link"> - <span class="pipeline-id">#{{pipeline.id}}</span> + <span class="pipeline-id">#{{ pipeline.id }}</span> </a> <span>by</span> <user-avatar-link diff --git a/app/assets/javascripts/pipelines/components/pipelines.vue b/app/assets/javascripts/pipelines/components/pipelines.vue index 8fa416168e7..90930d5ff44 100644 --- a/app/assets/javascripts/pipelines/components/pipelines.vue +++ b/app/assets/javascripts/pipelines/components/pipelines.vue @@ -13,6 +13,15 @@ import CIPaginationMixin from '../../vue_shared/mixins/ci_pagination_api_mixin'; export default { + components: { + tablePagination, + navigationTabs, + navigationControls, + }, + mixins: [ + pipelinesMixin, + CIPaginationMixin, + ], props: { store: { type: Object, @@ -28,15 +37,6 @@ default: 'root', }, }, - components: { - tablePagination, - navigationTabs, - navigationControls, - }, - mixins: [ - pipelinesMixin, - CIPaginationMixin, - ], data() { const pipelinesData = document.querySelector('#pipelines-list-vue').dataset; @@ -197,7 +197,8 @@ <div class="pipelines-container"> <div class="top-area scrolling-tabs-container inner-page-scroll-tabs" - v-if="!shouldRenderEmptyState"> + v-if="!shouldRenderEmptyState" + > <div class="fade-left"> <i class="fa fa-angle-left" @@ -215,16 +216,16 @@ :tabs="tabs" @onChangeTab="onChangeTab" scope="pipelines" - /> + /> <navigation-controls :new-pipeline-path="newPipelinePath" :has-ci-enabled="hasCiEnabled" :help-page-path="helpPagePath" - :resetCachePath="resetCachePath" + :reset-cache-path="resetCachePath" :ci-lint-path="ciLintPath" :can-create-pipeline="canCreatePipelineParsed " - /> + /> </div> <div class="content-list pipelines"> @@ -234,22 +235,23 @@ size="3" v-if="isLoading" class="prepend-top-20" - /> + /> <empty-state v-if="shouldRenderEmptyState" :help-page-path="helpPagePath" :empty-state-svg-path="emptyStateSvgPath" - /> + /> <error-state v-if="shouldRenderErrorState" :error-state-svg-path="errorStateSvgPath" - /> + /> <div class="blank-state-row" - v-if="shouldRenderNoPipelinesMessage"> + v-if="shouldRenderNoPipelinesMessage" + > <div class="blank-state-center"> <h2 class="blank-state-title js-blank-state-title">No pipelines to show.</h2> </div> @@ -257,21 +259,22 @@ <div class="table-holder" - v-if="shouldRenderTable"> + v-if="shouldRenderTable" + > <pipelines-table-component :pipelines="state.pipelines" :update-graph-dropdown="updateGraphDropdown" :auto-devops-help-path="autoDevopsPath" :view-type="viewType" - /> + /> </div> <table-pagination v-if="shouldRenderPagination" :change="onChangePage" :page-info="state.pageInfo" - /> + /> </div> </div> </template> diff --git a/app/assets/javascripts/pipelines/components/pipelines_actions.vue b/app/assets/javascripts/pipelines/components/pipelines_actions.vue index f3c0aca17ba..3297af7bde4 100644 --- a/app/assets/javascripts/pipelines/components/pipelines_actions.vue +++ b/app/assets/javascripts/pipelines/components/pipelines_actions.vue @@ -1,25 +1,25 @@ <script> - import playIconSvg from 'icons/_icon_play.svg'; import eventHub from '../event_hub'; import loadingIcon from '../../vue_shared/components/loading_icon.vue'; + import icon from '../../vue_shared/components/icon.vue'; import tooltip from '../../vue_shared/directives/tooltip'; export default { - props: { - actions: { - type: Array, - required: true, - }, - }, directives: { tooltip, }, components: { loadingIcon, + icon, + }, + props: { + actions: { + type: Array, + required: true, + }, }, data() { return { - playIconSvg, isLoading: false, }; }, @@ -50,8 +50,12 @@ data-toggle="dropdown" data-placement="top" aria-label="Manual job" - :disabled="isLoading"> - <span v-html="playIconSvg"></span> + :disabled="isLoading" + > + <icon + name="play" + class="icon-play" + /> <i class="fa fa-caret-down" aria-hidden="true"> @@ -60,14 +64,18 @@ </button> <ul class="dropdown-menu dropdown-menu-align-right"> - <li v-for="action in actions"> + <li + v-for="(action, i) in actions" + :key="i" + > <button type="button" class="js-pipeline-action-link no-btn btn" @click="onClickAction(action.path)" :class="{ disabled: isActionDisabled(action) }" - :disabled="isActionDisabled(action)"> - {{action.name}} + :disabled="isActionDisabled(action)" + > + {{ action.name }} </button> </li> </ul> diff --git a/app/assets/javascripts/pipelines/components/pipelines_artifacts.vue b/app/assets/javascripts/pipelines/components/pipelines_artifacts.vue index 831aa92ac4f..1b9e0f917a4 100644 --- a/app/assets/javascripts/pipelines/components/pipelines_artifacts.vue +++ b/app/assets/javascripts/pipelines/components/pipelines_artifacts.vue @@ -3,46 +3,50 @@ import icon from '../../vue_shared/components/icon.vue'; export default { - props: { - artifacts: { - type: Array, - required: true, - }, - }, directives: { tooltip, }, components: { icon, }, + props: { + artifacts: { + type: Array, + required: true, + }, + }, }; </script> <template> <div class="btn-group" - role="group"> + role="group" + > <button v-tooltip class="dropdown-toggle btn btn-default build-artifacts js-pipeline-dropdown-download" title="Artifacts" data-placement="top" data-toggle="dropdown" - aria-label="Artifacts"> - <icon - name="download"> - </icon> + aria-label="Artifacts" + > + <icon name="download" /> <i class="fa fa-caret-down" - aria-hidden="true"> + aria-hidden="true" + > </i> </button> <ul class="dropdown-menu dropdown-menu-align-right"> - <li v-for="artifact in artifacts"> + <li + v-for="(artifact, i) in artifacts" + :key="i"> <a rel="nofollow" download - :href="artifact.path"> - Download {{artifact.name}} artifacts + :href="artifact.path" + > + Download {{ artifact.name }} artifacts </a> </li> </ul> diff --git a/app/assets/javascripts/pipelines/components/pipelines_table.vue b/app/assets/javascripts/pipelines/components/pipelines_table.vue index 16a705cbaff..6681b89e629 100644 --- a/app/assets/javascripts/pipelines/components/pipelines_table.vue +++ b/app/assets/javascripts/pipelines/components/pipelines_table.vue @@ -7,6 +7,9 @@ * Given an array of objects, renders a table. */ export default { + components: { + pipelinesTableRowComponent, + }, props: { pipelines: { type: Array, @@ -26,34 +29,36 @@ required: true, }, }, - components: { - pipelinesTableRowComponent, - }, }; </script> <template> <div class="ci-table"> <div class="gl-responsive-table-row table-row-header" - role="row"> + role="row" + > <div class="table-section section-10 js-pipeline-status pipeline-status" - role="rowheader"> + role="rowheader" + > Status </div> <div class="table-section section-15 js-pipeline-info pipeline-info" - role="rowheader"> + role="rowheader" + > Pipeline </div> <div - class="table-section section-25 js-pipeline-commit pipeline-commit" - role="rowheader"> + class="table-section section-20 js-pipeline-commit pipeline-commit" + role="rowheader" + > Commit </div> <div - class="table-section section-15 js-pipeline-stages pipeline-stages" - role="rowheader"> + class="table-section section-20 js-pipeline-stages pipeline-stages" + role="rowheader" + > Stages </div> </div> diff --git a/app/assets/javascripts/pipelines/components/pipelines_table_row.vue b/app/assets/javascripts/pipelines/components/pipelines_table_row.vue index 33fbce993b2..d0e4cf7ff40 100644 --- a/app/assets/javascripts/pipelines/components/pipelines_table_row.vue +++ b/app/assets/javascripts/pipelines/components/pipelines_table_row.vue @@ -1,227 +1,228 @@ <script> -/* eslint-disable no-param-reassign */ -import asyncButtonComponent from './async_button.vue'; -import pipelinesActionsComponent from './pipelines_actions.vue'; -import pipelinesArtifactsComponent from './pipelines_artifacts.vue'; -import ciBadge from '../../vue_shared/components/ci_badge_link.vue'; -import pipelineStage from './stage.vue'; -import pipelineUrl from './pipeline_url.vue'; -import pipelinesTimeago from './time_ago.vue'; -import commitComponent from '../../vue_shared/components/commit.vue'; + /* eslint-disable no-param-reassign */ + import asyncButtonComponent from './async_button.vue'; + import pipelinesActionsComponent from './pipelines_actions.vue'; + import pipelinesArtifactsComponent from './pipelines_artifacts.vue'; + import ciBadge from '../../vue_shared/components/ci_badge_link.vue'; + import pipelineStage from './stage.vue'; + import pipelineUrl from './pipeline_url.vue'; + import pipelinesTimeago from './time_ago.vue'; + import commitComponent from '../../vue_shared/components/commit.vue'; -/** - * Pipeline table row. - * - * Given the received object renders a table row in the pipelines' table. - */ -export default { - props: { - pipeline: { - type: Object, - required: true, + /** + * Pipeline table row. + * + * Given the received object renders a table row in the pipelines' table. + */ + export default { + components: { + asyncButtonComponent, + pipelinesActionsComponent, + pipelinesArtifactsComponent, + commitComponent, + pipelineStage, + pipelineUrl, + ciBadge, + pipelinesTimeago, }, - updateGraphDropdown: { - type: Boolean, - required: false, - default: false, + props: { + pipeline: { + type: Object, + required: true, + }, + updateGraphDropdown: { + type: Boolean, + required: false, + default: false, + }, + autoDevopsHelpPath: { + type: String, + required: true, + }, + viewType: { + type: String, + required: true, + }, }, - autoDevopsHelpPath: { - type: String, - required: true, - }, - viewType: { - type: String, - required: true, - }, - }, - components: { - asyncButtonComponent, - pipelinesActionsComponent, - pipelinesArtifactsComponent, - commitComponent, - pipelineStage, - pipelineUrl, - ciBadge, - pipelinesTimeago, - }, - computed: { - /** - * If provided, returns the commit tag. - * Needed to render the commit component column. - * - * This field needs a lot of verification, because of different possible cases: - * - * 1. person who is an author of a commit might be a GitLab user - * 2. if person who is an author of a commit is a GitLab user he/she can have a GitLab avatar - * 3. If GitLab user does not have avatar he/she might have a Gravatar - * 4. If committer is not a GitLab User he/she can have a Gravatar - * 5. We do not have consistent API object in this case - * 6. We should improve API and the code - * - * @returns {Object|Undefined} - */ - commitAuthor() { - let commitAuthorInformation; + computed: { + /** + * If provided, returns the commit tag. + * Needed to render the commit component column. + * + * This field needs a lot of verification, because of different possible cases: + * + * 1. person who is an author of a commit might be a GitLab user + * 2. if person who is an author of a commit is a GitLab user he/she can have a GitLab avatar + * 3. If GitLab user does not have avatar he/she might have a Gravatar + * 4. If committer is not a GitLab User he/she can have a Gravatar + * 5. We do not have consistent API object in this case + * 6. We should improve API and the code + * + * @returns {Object|Undefined} + */ + commitAuthor() { + let commitAuthorInformation; - if (!this.pipeline || !this.pipeline.commit) { - return null; - } + if (!this.pipeline || !this.pipeline.commit) { + return null; + } - // 1. person who is an author of a commit might be a GitLab user - if (this.pipeline.commit.author) { - // 2. if person who is an author of a commit is a GitLab user - // he/she can have a GitLab avatar - if (this.pipeline.commit.author.avatar_url) { - commitAuthorInformation = this.pipeline.commit.author; + // 1. person who is an author of a commit might be a GitLab user + if (this.pipeline.commit.author) { + // 2. if person who is an author of a commit is a GitLab user + // he/she can have a GitLab avatar + if (this.pipeline.commit.author.avatar_url) { + commitAuthorInformation = this.pipeline.commit.author; - // 3. If GitLab user does not have avatar he/she might have a Gravatar - } else if (this.pipeline.commit.author_gravatar_url) { - commitAuthorInformation = Object.assign({}, this.pipeline.commit.author, { + // 3. If GitLab user does not have avatar he/she might have a Gravatar + } else if (this.pipeline.commit.author_gravatar_url) { + commitAuthorInformation = Object.assign({}, this.pipeline.commit.author, { + avatar_url: this.pipeline.commit.author_gravatar_url, + }); + } + // 4. If committer is not a GitLab User he/she can have a Gravatar + } else { + commitAuthorInformation = { avatar_url: this.pipeline.commit.author_gravatar_url, - }); + path: `mailto:${this.pipeline.commit.author_email}`, + username: this.pipeline.commit.author_name, + }; } - // 4. If committer is not a GitLab User he/she can have a Gravatar - } else { - commitAuthorInformation = { - avatar_url: this.pipeline.commit.author_gravatar_url, - path: `mailto:${this.pipeline.commit.author_email}`, - username: this.pipeline.commit.author_name, - }; - } - return commitAuthorInformation; - }, + return commitAuthorInformation; + }, - /** - * If provided, returns the commit tag. - * Needed to render the commit component column. - * - * @returns {String|Undefined} - */ - commitTag() { - if (this.pipeline.ref && - this.pipeline.ref.tag) { - return this.pipeline.ref.tag; - } - return undefined; - }, + /** + * If provided, returns the commit tag. + * Needed to render the commit component column. + * + * @returns {String|Undefined} + */ + commitTag() { + if (this.pipeline.ref && + this.pipeline.ref.tag) { + return this.pipeline.ref.tag; + } + return undefined; + }, - /** - * If provided, returns the commit ref. - * Needed to render the commit component column. - * - * Matches `path` prop sent in the API to `ref_url` prop needed - * in the commit component. - * - * @returns {Object|Undefined} - */ - commitRef() { - if (this.pipeline.ref) { - return Object.keys(this.pipeline.ref).reduce((accumulator, prop) => { - if (prop === 'path') { - accumulator.ref_url = this.pipeline.ref[prop]; - } else { - accumulator[prop] = this.pipeline.ref[prop]; - } - return accumulator; - }, {}); - } + /** + * If provided, returns the commit ref. + * Needed to render the commit component column. + * + * Matches `path` prop sent in the API to `ref_url` prop needed + * in the commit component. + * + * @returns {Object|Undefined} + */ + commitRef() { + if (this.pipeline.ref) { + return Object.keys(this.pipeline.ref).reduce((accumulator, prop) => { + if (prop === 'path') { + accumulator.ref_url = this.pipeline.ref[prop]; + } else { + accumulator[prop] = this.pipeline.ref[prop]; + } + return accumulator; + }, {}); + } - return undefined; - }, + return undefined; + }, - /** - * If provided, returns the commit url. - * Needed to render the commit component column. - * - * @returns {String|Undefined} - */ - commitUrl() { - if (this.pipeline.commit && - this.pipeline.commit.commit_path) { - return this.pipeline.commit.commit_path; - } - return undefined; - }, + /** + * If provided, returns the commit url. + * Needed to render the commit component column. + * + * @returns {String|Undefined} + */ + commitUrl() { + if (this.pipeline.commit && + this.pipeline.commit.commit_path) { + return this.pipeline.commit.commit_path; + } + return undefined; + }, - /** - * If provided, returns the commit short sha. - * Needed to render the commit component column. - * - * @returns {String|Undefined} - */ - commitShortSha() { - if (this.pipeline.commit && - this.pipeline.commit.short_id) { - return this.pipeline.commit.short_id; - } - return undefined; - }, + /** + * If provided, returns the commit short sha. + * Needed to render the commit component column. + * + * @returns {String|Undefined} + */ + commitShortSha() { + if (this.pipeline.commit && + this.pipeline.commit.short_id) { + return this.pipeline.commit.short_id; + } + return undefined; + }, - /** - * If provided, returns the commit title. - * Needed to render the commit component column. - * - * @returns {String|Undefined} - */ - commitTitle() { - if (this.pipeline.commit && - this.pipeline.commit.title) { - return this.pipeline.commit.title; - } - return undefined; - }, + /** + * If provided, returns the commit title. + * Needed to render the commit component column. + * + * @returns {String|Undefined} + */ + commitTitle() { + if (this.pipeline.commit && + this.pipeline.commit.title) { + return this.pipeline.commit.title; + } + return undefined; + }, - /** - * Timeago components expects a number - * - * @return {type} description - */ - pipelineDuration() { - if (this.pipeline.details && this.pipeline.details.duration) { - return this.pipeline.details.duration; - } + /** + * Timeago components expects a number + * + * @return {type} description + */ + pipelineDuration() { + if (this.pipeline.details && this.pipeline.details.duration) { + return this.pipeline.details.duration; + } - return 0; - }, + return 0; + }, - /** - * Timeago component expects a String. - * - * @return {String} - */ - pipelineFinishedAt() { - if (this.pipeline.details && this.pipeline.details.finished_at) { - return this.pipeline.details.finished_at; - } + /** + * Timeago component expects a String. + * + * @return {String} + */ + pipelineFinishedAt() { + if (this.pipeline.details && this.pipeline.details.finished_at) { + return this.pipeline.details.finished_at; + } - return ''; - }, + return ''; + }, - pipelineStatus() { - if (this.pipeline.details && this.pipeline.details.status) { - return this.pipeline.details.status; - } - return {}; - }, + pipelineStatus() { + if (this.pipeline.details && this.pipeline.details.status) { + return this.pipeline.details.status; + } + return {}; + }, - displayPipelineActions() { - return this.pipeline.flags.retryable || - this.pipeline.flags.cancelable || - this.pipeline.details.manual_actions.length || - this.pipeline.details.artifacts.length; - }, + displayPipelineActions() { + return this.pipeline.flags.retryable || + this.pipeline.flags.cancelable || + this.pipeline.details.manual_actions.length || + this.pipeline.details.artifacts.length; + }, - isChildView() { - return this.viewType === 'child'; + isChildView() { + return this.viewType === 'child'; + }, }, - }, -}; + }; </script> <template> <div class="commit gl-responsive-table-row"> <div class="table-section section-10 commit-link"> - <div class="table-mobile-header" + <div + class="table-mobile-header" role="rowheader"> Status </div> @@ -229,16 +230,16 @@ export default { <ci-badge :status="pipelineStatus" :show-text="!isChildView" - /> + /> </div> </div> <pipeline-url :pipeline="pipeline" :auto-devops-help-path="autoDevopsHelpPath" - /> + /> - <div class="table-section section-25"> + <div class="table-section section-20"> <div class="table-mobile-header" role="rowheader"> @@ -253,32 +254,35 @@ export default { :title="commitTitle" :author="commitAuthor" :show-branch="!isChildView" - /> + /> </div> </div> - <div class="table-section section-wrap section-15 stage-cell"> + <div class="table-section section-wrap section-20 stage-cell"> <div class="table-mobile-header" role="rowheader"> Stages </div> <div class="table-mobile-content"> - <div class="stage-container dropdown js-mini-pipeline-graph" - v-if="pipeline.details.stages.length > 0" - v-for="stage in pipeline.details.stages"> - <pipeline-stage - :stage="stage" - :update-dropdown="updateGraphDropdown" + <template v-if="pipeline.details.stages.length > 0"> + <div + class="stage-container dropdown js-mini-pipeline-graph" + v-for="(stage, index) in pipeline.details.stages" + :key="index"> + <pipeline-stage + :stage="stage" + :update-dropdown="updateGraphDropdown" /> - </div> + </div> + </template> </div> </div> <pipelines-timeago :duration="pipelineDuration" :finished-time="pipelineFinishedAt" - /> + /> <div v-if="displayPipelineActions" @@ -287,13 +291,13 @@ export default { <pipelines-actions-component v-if="pipeline.details.manual_actions.length" :actions="pipeline.details.manual_actions" - /> + /> <pipelines-artifacts-component v-if="pipeline.details.artifacts.length" class="hidden-xs hidden-sm" :artifacts="pipeline.details.artifacts" - /> + /> <async-button-component v-if="pipeline.flags.retryable" @@ -301,16 +305,16 @@ export default { css-class="js-pipelines-retry-button btn-default btn-retry" title="Retry" icon="repeat" - /> + /> <async-button-component v-if="pipeline.flags.cancelable" :endpoint="pipeline.cancel_path" css-class="js-pipelines-cancel-button btn-remove" title="Cancel" - icon="remove" + icon="close" confirm-action-message="Are you sure you want to cancel this pipeline?" - /> + /> </div> </div> </div> diff --git a/app/assets/javascripts/pipelines/components/stage.vue b/app/assets/javascripts/pipelines/components/stage.vue index ac9d9c901ca..58806aa114a 100644 --- a/app/assets/javascripts/pipelines/components/stage.vue +++ b/app/assets/javascripts/pipelines/components/stage.vue @@ -1,133 +1,135 @@ <script> -/** - * Renders each stage of the pipeline mini graph. - * - * Given the provided endpoint will make a request to - * fetch the dropdown data when the stage is clicked. - * - * Request is made inside this component to make it reusable between: - * 1. Pipelines main table - * 2. Pipelines table in commit and Merge request views - * 3. Merge request widget - * 4. Commit widget - */ - -import Flash from '../../flash'; -import icon from '../../vue_shared/components/icon.vue'; -import loadingIcon from '../../vue_shared/components/loading_icon.vue'; -import tooltip from '../../vue_shared/directives/tooltip'; - -export default { - props: { - stage: { - type: Object, - required: true, + /** + * Renders each stage of the pipeline mini graph. + * + * Given the provided endpoint will make a request to + * fetch the dropdown data when the stage is clicked. + * + * Request is made inside this component to make it reusable between: + * 1. Pipelines main table + * 2. Pipelines table in commit and Merge request views + * 3. Merge request widget + * 4. Commit widget + */ + + import Flash from '../../flash'; + import icon from '../../vue_shared/components/icon.vue'; + import loadingIcon from '../../vue_shared/components/loading_icon.vue'; + import tooltip from '../../vue_shared/directives/tooltip'; + + export default { + components: { + loadingIcon, + icon, }, - updateDropdown: { - type: Boolean, - required: false, - default: false, + directives: { + tooltip, }, - }, - - directives: { - tooltip, - }, - - data() { - return { - isLoading: false, - dropdownContent: '', - }; - }, - - components: { - loadingIcon, - icon, - }, - - updated() { - if (this.dropdownContent.length > 0) { - this.stopDropdownClickPropagation(); - } - }, - - watch: { - updateDropdown() { - if (this.updateDropdown && - this.isDropdownOpen() && - !this.isLoading) { - this.fetchJobs(); - } - }, - }, - methods: { - onClickStage() { - if (!this.isDropdownOpen()) { - this.isLoading = true; - this.fetchJobs(); - } + props: { + stage: { + type: Object, + required: true, + }, + + updateDropdown: { + type: Boolean, + required: false, + default: false, + }, }, - fetchJobs() { - this.$http.get(this.stage.dropdown_path) - .then(response => response.json()) - .then((data) => { - this.dropdownContent = data.html; - this.isLoading = false; - }) - .catch(() => { - this.closeDropdown(); - this.isLoading = false; - - const flash = new Flash('Something went wrong on our end.'); - return flash; - }); + data() { + return { + isLoading: false, + dropdownContent: '', + }; }, - /** - * When the user right clicks or cmd/ctrl + click in the job name - * the dropdown should not be closed and the link should open in another tab, - * so we stop propagation of the click event inside the dropdown. - * - * Since this component is rendered multiple times per page we need to guarantee we only - * target the click event of this component. - */ - stopDropdownClickPropagation() { - $(this.$el.querySelectorAll('.js-builds-dropdown-list a.mini-pipeline-graph-dropdown-item')) - .on('click', (e) => { - e.stopPropagation(); - }); - }, + computed: { + dropdownClass() { + return this.dropdownContent.length > 0 ? + 'js-builds-dropdown-container' : + 'js-builds-dropdown-loading'; + }, - closeDropdown() { - if (this.isDropdownOpen()) { - $(this.$refs.dropdown).dropdown('toggle'); - } - }, + triggerButtonClass() { + return `ci-status-icon-${this.stage.status.group}`; + }, - isDropdownOpen() { - return this.$el.classList.contains('open'); + borderlessIcon() { + return `${this.stage.status.icon}_borderless`; + }, }, - }, - computed: { - dropdownClass() { - return this.dropdownContent.length > 0 ? 'js-builds-dropdown-container' : 'js-builds-dropdown-loading'; + watch: { + updateDropdown() { + if (this.updateDropdown && + this.isDropdownOpen() && + !this.isLoading) { + this.fetchJobs(); + } + }, }, - triggerButtonClass() { - return `ci-status-icon-${this.stage.status.group}`; + updated() { + if (this.dropdownContent.length > 0) { + this.stopDropdownClickPropagation(); + } }, - borderlessIcon() { - return `${this.stage.status.icon}_borderless`; + methods: { + onClickStage() { + if (!this.isDropdownOpen()) { + this.isLoading = true; + this.fetchJobs(); + } + }, + + fetchJobs() { + this.$http.get(this.stage.dropdown_path) + .then(response => response.json()) + .then((data) => { + this.dropdownContent = data.html; + this.isLoading = false; + }) + .catch(() => { + this.closeDropdown(); + this.isLoading = false; + + const flash = new Flash('Something went wrong on our end.'); + return flash; + }); + }, + + /** + * When the user right clicks or cmd/ctrl + click in the job name + * the dropdown should not be closed and the link should open in another tab, + * so we stop propagation of the click event inside the dropdown. + * + * Since this component is rendered multiple times per page we need to guarantee we only + * target the click event of this component. + */ + stopDropdownClickPropagation() { + $(this.$el.querySelectorAll('.js-builds-dropdown-list a.mini-pipeline-graph-dropdown-item')) + .on('click', (e) => { + e.stopPropagation(); + }); + }, + + closeDropdown() { + if (this.isDropdownOpen()) { + $(this.$refs.dropdown).dropdown('toggle'); + } + }, + + isDropdownOpen() { + return this.$el.classList.contains('open'); + }, }, - }, -}; + }; </script> <template> @@ -143,36 +145,41 @@ export default { type="button" id="stageDropdown" aria-haspopup="true" - aria-expanded="false"> + aria-expanded="false" + > <span aria-hidden="true" - :aria-label="stage.title"> - <icon - :name="borderlessIcon"/> + :aria-label="stage.title" + > + <icon :name="borderlessIcon" /> </span> <i class="fa fa-caret-down" - aria-hidden="true"> + aria-hidden="true" + > </i> </button> <ul class="dropdown-menu mini-pipeline-graph-dropdown-menu js-builds-dropdown-container" - aria-labelledby="stageDropdown"> + aria-labelledby="stageDropdown" + > <li :class="dropdownClass" - class="js-builds-dropdown-list scrollable-menu"> + class="js-builds-dropdown-list scrollable-menu" + > <loading-icon v-if="isLoading"/> <ul v-else - v-html="dropdownContent"> + v-html="dropdownContent" + > </ul> </li> </ul> </div> -</script> +</template> diff --git a/app/assets/javascripts/pipelines/components/time_ago.vue b/app/assets/javascripts/pipelines/components/time_ago.vue index 037684b4e72..cd54d26c9d3 100644 --- a/app/assets/javascripts/pipelines/components/time_ago.vue +++ b/app/assets/javascripts/pipelines/components/time_ago.vue @@ -5,6 +5,12 @@ import timeagoMixin from '../../vue_shared/mixins/timeago'; export default { + directives: { + tooltip, + }, + mixins: [ + timeagoMixin, + ], props: { finishedTime: { type: String, @@ -15,12 +21,6 @@ required: true, }, }, - mixins: [ - timeagoMixin, - ], - directives: { - tooltip, - }, data() { return { iconTimerSvg, @@ -60,26 +60,29 @@ <div class="table-section section-15 pipelines-time-ago"> <div class="table-mobile-header" - role="rowheader"> + role="rowheader" + > Duration </div> <div class="table-mobile-content"> <p class="duration" - v-if="hasDuration"> - <span - v-html="iconTimerSvg"> + v-if="hasDuration" + > + <span v-html="iconTimerSvg"> </span> - {{durationFormated}} + {{ durationFormated }} </p> <p class="finished-at hidden-xs hidden-sm" - v-if="hasFinishedTime"> + v-if="hasFinishedTime" + > <i class="fa fa-calendar" - aria-hidden="true"> + aria-hidden="true" + > </i> <time @@ -87,9 +90,9 @@ data-placement="top" data-container="body" :title="tooltipTitle(finishedTime)"> - {{timeFormated(finishedTime)}} + {{ timeFormated(finishedTime) }} </time> </p> </div> </div> -</script> +</template> |