diff options
21 files changed, 1727 insertions, 674 deletions
diff --git a/app/assets/javascripts/job.js b/app/assets/javascripts/job.js index 0e71e705c13..854445bd2a4 100644 --- a/app/assets/javascripts/job.js +++ b/app/assets/javascripts/job.js @@ -24,7 +24,6 @@ export default class Job extends LogOutputBehaviours { this.$document = $(document); this.$window = $(window); this.logBytes = 0; - this.updateDropdown = this.updateDropdown.bind(this); this.$buildTrace = $('#build-trace'); this.$buildRefreshAnimation = $('.js-build-refresh'); @@ -35,18 +34,12 @@ export default class Job extends LogOutputBehaviours { clearTimeout(this.timeout); this.initSidebar(); - this.populateJobs(this.buildStage); - this.updateStageDropdownText(this.buildStage); this.sidebarOnResize(); this.$document .off('click', '.js-sidebar-build-toggle') .on('click', '.js-sidebar-build-toggle', this.sidebarOnClick.bind(this)); - this.$document - .off('click', '.stage-item') - .on('click', '.stage-item', this.updateDropdown); - this.scrollThrottled = _.throttle(this.toggleScroll.bind(this), 100); this.$window @@ -194,20 +187,4 @@ export default class Job extends LogOutputBehaviours { if (this.shouldHideSidebarForViewport()) this.toggleSidebar(); } - // eslint-disable-next-line class-methods-use-this - populateJobs(stage) { - $('.build-job').hide(); - $(`.build-job[data-stage="${stage}"]`).show(); - } - // eslint-disable-next-line class-methods-use-this - updateStageDropdownText(stage) { - $('.stage-selection').text(stage); - } - - updateDropdown(e) { - e.preventDefault(); - const stage = e.currentTarget.text; - this.updateStageDropdownText(stage); - this.populateJobs(stage); - } } diff --git a/app/assets/javascripts/jobs/components/jobs_container.vue b/app/assets/javascripts/jobs/components/jobs_container.vue index 93e2292ff84..271b7790d75 100644 --- a/app/assets/javascripts/jobs/components/jobs_container.vue +++ b/app/assets/javascripts/jobs/components/jobs_container.vue @@ -1,4 +1,5 @@ <script> + import _ from 'underscore'; import CiIcon from '~/vue_shared/components/ci_icon.vue'; import Icon from '~/vue_shared/components/icon.vue'; import tooltip from '~/vue_shared/directives/tooltip'; @@ -16,26 +17,39 @@ type: Array, required: true, }, + jobId: { + type: Number, + required: true, + }, + }, + methods: { + isJobActive(currentJobId) { + return this.jobId === currentJobId; + }, + tooltipText(job) { + return `${_.escape(job.name)} - ${job.status.tooltip}`; + }, }, }; </script> <template> - <div class="builds-container"> + <div class="js-jobs-container builds-container"> <div + v-for="job in jobs" + :key="job.id" class="build-job" + :class="{ retried: job.retried, active: isJobActive(job.id) }" > <a - v-for="job in jobs" - :key="job.id" v-tooltip - :href="job.path" - :title="job.tooltip" - :class="{ active: job.active, retried: job.retried }" + :href="job.status.details_path" + :title="tooltipText(job)" + data-container="body" > <icon - v-if="job.active" + v-if="isJobActive(job.id)" name="arrow-right" - class="js-arrow-right" + class="js-arrow-right icon-arrow-right" /> <ci-icon :status="job.status" /> diff --git a/app/assets/javascripts/jobs/components/sidebar.vue b/app/assets/javascripts/jobs/components/sidebar.vue new file mode 100644 index 00000000000..22bcd402e72 --- /dev/null +++ b/app/assets/javascripts/jobs/components/sidebar.vue @@ -0,0 +1,297 @@ +<script> + import _ from 'underscore'; + import { mapActions, mapState } from 'vuex'; + import timeagoMixin from '~/vue_shared/mixins/timeago'; + import { timeIntervalInWords } from '~/lib/utils/datetime_utility'; + import Icon from '~/vue_shared/components/icon.vue'; + import DetailRow from './sidebar_detail_row.vue'; + import ArtifactsBlock from './artifacts_block.vue'; + import TriggerBlock from './trigger_block.vue'; + import CommitBlock from './commit_block.vue'; + import StagesDropdown from './stages_dropdown.vue'; + import JobsContainer from './jobs_container.vue'; + + export default { + name: 'JobSidebar', + components: { + ArtifactsBlock, + CommitBlock, + DetailRow, + Icon, + TriggerBlock, + StagesDropdown, + JobsContainer, + }, + mixins: [timeagoMixin], + props: { + runnerHelpUrl: { + type: String, + required: false, + default: '', + }, + terminalPath: { + type: String, + required: false, + default: null, + }, + }, + computed: { + ...mapState(['job', 'isLoading', 'stages', 'jobs']), + coverage() { + return `${this.job.coverage}%`; + }, + duration() { + return timeIntervalInWords(this.job.duration); + }, + queued() { + return timeIntervalInWords(this.job.queued); + }, + runnerId() { + return `${this.job.runner.description} (#${this.job.runner.id})`; + }, + retryButtonClass() { + let className = + 'js-retry-button float-right btn btn-retry d-none d-md-block d-lg-block d-xl-block'; + className += + this.job.status && this.job.recoverable ? ' btn-primary' : ' btn-inverted-secondary'; + return className; + }, + hasTimeout() { + return this.job.metadata != null && this.job.metadata.timeout_human_readable !== null; + }, + timeout() { + if (this.job.metadata == null) { + return ''; + } + + let t = this.job.metadata.timeout_human_readable; + if (this.job.metadata.timeout_source !== '') { + t += ` (from ${this.job.metadata.timeout_source})`; + } + + return t; + }, + renderBlock() { + return ( + this.job.merge_request || + this.job.duration || + this.job.finished_data || + this.job.erased_at || + this.job.queued || + this.job.runner || + this.job.coverage || + this.job.tags.length || + this.job.cancel_path + ); + }, + hasArtifact() { + return !_.isEmpty(this.job.artifact); + }, + hasTriggers() { + return !_.isEmpty(this.job.trigger); + }, + hasStages() { + return ( + (this.job && + this.job.pipeline && + this.job.pipeline.stages && + this.job.pipeline.stages.length > 0) || + false + ); + }, + commit() { + return this.job.pipeline.commit || {}; + }, + }, + methods: { + ...mapActions(['fetchJobsForStage']), + }, + }; +</script> +<template> + <aside + class="right-sidebar right-sidebar-expanded build-sidebar" + data-offset-top="101" + data-spy="affix" + > + <div class="sidebar-container"> + <div class="blocks-container"> + <template v-if="!isLoading"> + <div class="block"> + <strong class="inline prepend-top-8"> + {{ job.name }} + </strong> + <a + v-if="job.retry_path" + :class="retryButtonClass" + :href="job.retry_path" + data-method="post" + rel="nofollow" + > + {{ __('Retry') }} + </a> + <a + v-if="terminalPath" + :href="terminalPath" + class="js-terminal-link pull-right btn btn-primary + btn-inverted visible-md-block visible-lg-block" + target="_blank" + > + {{ __('Debug') }} + <icon name="external-link" /> + </a> + <button + :aria-label="__('Toggle Sidebar')" + type="button" + class="btn btn-blank gutter-toggle + float-right d-block d-md-none js-sidebar-build-toggle" + > + <i + aria-hidden="true" + data-hidden="true" + class="fa fa-angle-double-right" + ></i> + </button> + </div> + <div + v-if="job.retry_path || job.new_issue_path" + class="block retry-link" + > + <a + v-if="job.new_issue_path" + :href="job.new_issue_path" + class="js-new-issue btn btn-success btn-inverted" + > + {{ __('New issue') }} + </a> + <a + v-if="job.retry_path" + :href="job.retry_path" + class="js-retry-job btn btn-inverted-secondary" + data-method="post" + rel="nofollow" + > + {{ __('Retry') }} + </a> + </div> + <div :class="{ block : renderBlock }"> + <p + v-if="job.merge_request" + class="build-detail-row js-job-mr" + > + <span class="build-light-text"> + {{ __('Merge Request:') }} + </span> + <a :href="job.merge_request.path"> + !{{ job.merge_request.iid }} + </a> + </p> + + <detail-row + v-if="job.duration" + :value="duration" + class="js-job-duration" + title="Duration" + /> + <detail-row + v-if="job.finished_at" + :value="timeFormated(job.finished_at)" + class="js-job-finished" + title="Finished" + /> + <detail-row + v-if="job.erased_at" + :value="timeFormated(job.erased_at)" + class="js-job-erased" + title="Erased" + /> + <detail-row + v-if="job.queued" + :value="queued" + class="js-job-queued" + title="Queued" + /> + <detail-row + v-if="hasTimeout" + :help-url="runnerHelpUrl" + :value="timeout" + class="js-job-timeout" + title="Timeout" + /> + <detail-row + v-if="job.runner" + :value="runnerId" + class="js-job-runner" + title="Runner" + /> + <detail-row + v-if="job.coverage" + :value="coverage" + class="js-job-coverage" + title="Coverage" + /> + <p + v-if="job.tags.length" + class="build-detail-row js-job-tags" + > + <span class="build-light-text"> + {{ __('Tags:') }} + </span> + <span + v-for="(tag, i) in job.tags" + :key="i" + class="label label-primary"> + {{ tag }} + </span> + </p> + + <div + v-if="job.cancel_path" + class="btn-group prepend-top-5" + role="group"> + <a + :href="job.cancel_path" + class="js-cancel-job btn btn-sm btn-default" + data-method="post" + rel="nofollow" + > + {{ __('Cancel') }} + </a> + </div> + </div> + <artifacts-block + v-if="hasArtifact" + :artifact="job.artifact" + /> + <trigger-block + v-if="hasTriggers" + :trigger="job.trigger" + /> + <commit-block + :is-last-block="hasStages" + :commit="commit" + :merge-request="job.merge_request" + /> + + <stages-dropdown + :stages="stages" + :pipeline="job.pipeline" + @requestSidebarStageDropdown="fetchJobsForStage" + /> + + </template> + <gl-loading-icon + v-else + :size="2" + class="prepend-top-10" + /> + </div> + + <jobs-container + v-if="!isLoading && jobs.length" + :jobs="jobs" + :job-id="job.id" + /> + </div> + </aside> +</template> diff --git a/app/assets/javascripts/jobs/components/sidebar_details_block.vue b/app/assets/javascripts/jobs/components/sidebar_details_block.vue deleted file mode 100644 index a591fcfb482..00000000000 --- a/app/assets/javascripts/jobs/components/sidebar_details_block.vue +++ /dev/null @@ -1,276 +0,0 @@ -<script> - import _ from 'underscore'; - import timeagoMixin from '~/vue_shared/mixins/timeago'; - import { timeIntervalInWords } from '~/lib/utils/datetime_utility'; - import Icon from '~/vue_shared/components/icon.vue'; - import DetailRow from './sidebar_detail_row.vue'; - import ArtifactsBlock from './artifacts_block.vue'; - import TriggerBlock from './trigger_block.vue'; - import CommitBlock from './commit_block.vue'; - - export default { - name: 'SidebarDetailsBlock', - components: { - ArtifactsBlock, - CommitBlock, - DetailRow, - Icon, - TriggerBlock, - }, - mixins: [timeagoMixin], - props: { - job: { - type: Object, - required: true, - }, - isLoading: { - type: Boolean, - required: true, - }, - runnerHelpUrl: { - type: String, - required: false, - default: '', - }, - terminalPath: { - type: String, - required: false, - default: null, - }, - }, - computed: { - shouldRenderContent() { - return !this.isLoading && Object.keys(this.job).length > 0; - }, - coverage() { - return `${this.job.coverage}%`; - }, - duration() { - return timeIntervalInWords(this.job.duration); - }, - queued() { - return timeIntervalInWords(this.job.queued); - }, - runnerId() { - return `${this.job.runner.description} (#${this.job.runner.id})`; - }, - retryButtonClass() { - let className = - 'js-retry-button float-right btn btn-retry d-none d-md-block d-lg-block d-xl-block'; - className += - this.job.status && this.job.recoverable ? ' btn-primary' : ' btn-inverted-secondary'; - return className; - }, - hasTimeout() { - return this.job.metadata != null && this.job.metadata.timeout_human_readable !== null; - }, - timeout() { - if (this.job.metadata == null) { - return ''; - } - - let t = this.job.metadata.timeout_human_readable; - if (this.job.metadata.timeout_source !== '') { - t += ` (from ${this.job.metadata.timeout_source})`; - } - - return t; - }, - renderBlock() { - return ( - this.job.merge_request || - this.job.duration || - this.job.finished_data || - this.job.erased_at || - this.job.queued || - this.job.runner || - this.job.coverage || - this.job.tags.length || - this.job.cancel_path - ); - }, - hasArtifact() { - return !_.isEmpty(this.job.artifact); - }, - hasTriggers() { - return !_.isEmpty(this.job.trigger); - }, - hasStages() { - return ( - this.job && - this.job.pipeline && - this.job.pipeline.stages && - this.job.pipeline.stages.length > 0 - ) || false; - }, - commit() { - return this.job.pipeline.commit || {}; - }, - }, - }; -</script> -<template> - <div> - <div class="block"> - <strong class="inline prepend-top-8"> - {{ job.name }} - </strong> - <a - v-if="job.retry_path" - :class="retryButtonClass" - :href="job.retry_path" - data-method="post" - rel="nofollow" - > - {{ __('Retry') }} - </a> - <a - v-if="terminalPath" - :href="terminalPath" - class="js-terminal-link pull-right btn btn-primary - btn-inverted visible-md-block visible-lg-block" - target="_blank" - > - {{ __('Debug') }} - <icon name="external-link" /> - </a> - <button - :aria-label="__('Toggle Sidebar')" - type="button" - class="btn btn-blank gutter-toggle float-right d-block d-md-none js-sidebar-build-toggle" - > - <i - aria-hidden="true" - data-hidden="true" - class="fa fa-angle-double-right" - ></i> - </button> - </div> - <template v-if="shouldRenderContent"> - <div - v-if="job.retry_path || job.new_issue_path" - class="block retry-link" - > - <a - v-if="job.new_issue_path" - :href="job.new_issue_path" - class="js-new-issue btn btn-success btn-inverted" - > - {{ __('New issue') }} - </a> - <a - v-if="job.retry_path" - :href="job.retry_path" - class="js-retry-job btn btn-inverted-secondary" - data-method="post" - rel="nofollow" - > - {{ __('Retry') }} - </a> - </div> - <div :class="{block : renderBlock }"> - <p - v-if="job.merge_request" - class="build-detail-row js-job-mr" - > - <span class="build-light-text"> - {{ __('Merge Request:') }} - </span> - <a :href="job.merge_request.path"> - !{{ job.merge_request.iid }} - </a> - </p> - - <detail-row - v-if="job.duration" - :value="duration" - class="js-job-duration" - title="Duration" - /> - <detail-row - v-if="job.finished_at" - :value="timeFormated(job.finished_at)" - class="js-job-finished" - title="Finished" - /> - <detail-row - v-if="job.erased_at" - :value="timeFormated(job.erased_at)" - class="js-job-erased" - title="Erased" - /> - <detail-row - v-if="job.queued" - :value="queued" - class="js-job-queued" - title="Queued" - /> - <detail-row - v-if="hasTimeout" - :help-url="runnerHelpUrl" - :value="timeout" - class="js-job-timeout" - title="Timeout" - /> - <detail-row - v-if="job.runner" - :value="runnerId" - class="js-job-runner" - title="Runner" - /> - <detail-row - v-if="job.coverage" - :value="coverage" - class="js-job-coverage" - title="Coverage" - /> - <p - v-if="job.tags.length" - class="build-detail-row js-job-tags" - > - <span class="build-light-text"> - {{ __('Tags:') }} - </span> - <span - v-for="(tag, i) in job.tags" - :key="i" - class="label label-primary"> - {{ tag }} - </span> - </p> - - <div - v-if="job.cancel_path" - class="btn-group prepend-top-5" - role="group"> - <a - :href="job.cancel_path" - class="js-cancel-job btn btn-sm btn-default" - data-method="post" - rel="nofollow" - > - {{ __('Cancel') }} - </a> - </div> - </div> - <artifacts-block - v-if="hasArtifact" - :artifact="job.artifact" - /> - <trigger-block - v-if="hasTriggers" - :trigger="job.trigger" - /> - <commit-block - :is-last-block="hasStages" - :commit="commit" - :merge-request="job.merge_request" - /> - </template> - <gl-loading-icon - v-if="isLoading" - :size="2" - class="prepend-top-10" - /> - </div> -</template> diff --git a/app/assets/javascripts/jobs/components/stages_dropdown.vue b/app/assets/javascripts/jobs/components/stages_dropdown.vue index d6d64fa32f7..1c15af55a8b 100644 --- a/app/assets/javascripts/jobs/components/stages_dropdown.vue +++ b/app/assets/javascripts/jobs/components/stages_dropdown.vue @@ -1,8 +1,8 @@ <script> + import _ from 'underscore'; import CiIcon from '~/vue_shared/components/ci_icon.vue'; import Icon from '~/vue_shared/components/icon.vue'; - - import { sprintf, __ } from '~/locale'; + import { __ } from '~/locale'; export default { components: { @@ -10,30 +10,14 @@ Icon, }, props: { - pipelineId: { - type: Number, - required: true, - }, - pipelinePath: { - type: String, - required: true, - }, - pipelineRef: { - type: String, - required: true, - }, - pipelineRefPath: { - type: String, + pipeline: { + type: Object, required: true, }, stages: { type: Array, required: true, }, - pipelineStatus: { - type: Object, - required: true, - }, }, data() { return { @@ -41,57 +25,73 @@ }; }, computed: { - pipelineLink() { - return sprintf(__('Pipeline %{pipelineLinkStart} #%{pipelineId} %{pipelineLinkEnd} from %{pipelineLinkRefStart} %{pipelineRef} %{pipelineLinkRefEnd}'), { - pipelineLinkStart: `<a href=${this.pipelinePath} class="js-pipeline-path link-commit">`, - pipelineId: this.pipelineId, - pipelineLinkEnd: '</a>', - pipelineLinkRefStart: `<a href=${this.pipelineRefPath} class="link-commit ref-name">`, - pipelineRef: this.pipelineRef, - pipelineLinkRefEnd: '</a>', - }, false); + hasRef() { + return !_.isEmpty(this.pipeline.ref); + }, + }, + watch: { + // When the component is initially mounted it may start with an empty stages array. + // Once the prop is updated, we set the first stage as the selected one + stages(newVal) { + if (newVal.length) { + this.selectedStage = newVal[0].name; + } }, }, methods: { onStageClick(stage) { - // todo: consider moving into store - this.selectedStage = stage.name; - - // update dropdown with jobs - // jobs container is a new component. this.$emit('requestSidebarStageDropdown', stage); + this.selectedStage = stage.name; }, }, }; </script> <template> - <div class="block-last"> - <ci-icon :status="pipelineStatus" /> + <div class="block-last dropdown"> + <ci-icon + :status="pipeline.details.status" + class="vertical-align-middle" + /> + + {{ __('Pipeline') }} + <a + :href="pipeline.path" + class="js-pipeline-path link-commit" + > + #{{ pipeline.id }} + </a> + <template v-if="hasRef"> + {{ __('from') }} + <a + :href="pipeline.ref.path" + class="link-commit ref-name" + > + {{ pipeline.ref.name }} + </a> + </template> - <p v-html="pipelineLink"></p> + <button + type="button" + data-toggle="dropdown" + class="js-selected-stage dropdown-menu-toggle prepend-top-8" + > + {{ selectedStage }} + <i class="fa fa-chevron-down" ></i> + </button> - <div class="dropdown"> - <button - type="button" - data-toggle="dropdown" + <ul class="dropdown-menu"> + <li + v-for="stage in stages" + :key="stage.name" > - {{ selectedStage }} - <icon name="chevron-down" /> - </button> - <ul class="dropdown-menu"> - <li - v-for="(stage, index) in stages" - :key="index" + <button + type="button" + class="js-stage-item stage-item" + @click="onStageClick(stage)" > - <button - type="button" - class="stage-item" - @click="onStageClick(stage)" - > - {{ stage.name }} - </button> - </li> - </ul> - </div> + {{ stage.name }} + </button> + </li> + </ul> </div> </template> diff --git a/app/assets/javascripts/jobs/job_details_bundle.js b/app/assets/javascripts/jobs/job_details_bundle.js index 0136ec4d194..ae40f4cdf3b 100644 --- a/app/assets/javascripts/jobs/job_details_bundle.js +++ b/app/assets/javascripts/jobs/job_details_bundle.js @@ -1,8 +1,9 @@ -import { mapState } from 'vuex'; +import _ from 'underscore'; +import { mapState, mapActions } from 'vuex'; import Vue from 'vue'; import Job from '../job'; import JobHeader from './components/header.vue'; -import DetailsBlock from './components/sidebar_details_block.vue'; +import Sidebar from './components/sidebar.vue'; import createStore from './store'; export default () => { @@ -13,6 +14,7 @@ export default () => { const store = createStore(); store.dispatch('setJobEndpoint', dataset.endpoint); + store.dispatch('fetchJob'); // Header @@ -43,17 +45,25 @@ export default () => { new Vue({ el: detailsBlockElement, components: { - DetailsBlock, + Sidebar, }, - store, computed: { - ...mapState(['job', 'isLoading']), + ...mapState(['job']), + }, + watch: { + job(newVal, oldVal) { + if (_.isEmpty(oldVal) && !_.isEmpty(newVal.pipeline)) { + this.fetchStages(); + } + }, }, + methods: { + ...mapActions(['fetchStages']), + }, + store, render(createElement) { - return createElement('details-block', { + return createElement('sidebar', { props: { - isLoading: this.isLoading, - job: this.job, runnerHelpUrl: dataset.runnerHelpUrl, terminalPath: detailsBlockDataset.terminalPath, }, diff --git a/app/assets/javascripts/jobs/store/actions.js b/app/assets/javascripts/jobs/store/actions.js index 7f5406d6f43..298367c9342 100644 --- a/app/assets/javascripts/jobs/store/actions.js +++ b/app/assets/javascripts/jobs/store/actions.js @@ -62,7 +62,9 @@ export const fetchJob = ({ state, dispatch }) => { }); }; -export const receiveJobSuccess = ({ commit }, data) => commit(types.RECEIVE_JOB_SUCCESS, data); +export const receiveJobSuccess = ({ commit }, data) => { + commit(types.RECEIVE_JOB_SUCCESS, data); +}; export const receiveJobError = ({ commit }) => { commit(types.RECEIVE_JOB_ERROR); flash(__('An error occurred while fetching the job.')); @@ -137,8 +139,11 @@ export const fetchStages = ({ state, dispatch }) => { dispatch('requestStages'); axios - .get(state.stagesEndpoint) - .then(({ data }) => dispatch('receiveStagesSuccess', data)) + .get(state.job.pipeline.path) + .then(({ data }) => { + dispatch('receiveStagesSuccess', data.details.stages); + dispatch('fetchJobsForStage', data.details.stages[0]); + }) .catch(() => dispatch('receiveStagesError')); }; export const receiveStagesSuccess = ({ commit }, data) => @@ -152,16 +157,23 @@ export const receiveStagesError = ({ commit }) => { * Jobs list on sidebar - depend on stages dropdown */ export const requestJobsForStage = ({ commit }) => commit(types.REQUEST_JOBS_FOR_STAGE); -export const setSelectedStage = ({ commit }, stage) => commit(types.SET_SELECTED_STAGE, stage); // On stage click, set selected stage + fetch job -export const fetchJobsForStage = ({ state, dispatch }, stage) => { - dispatch('setSelectedStage', stage); +export const fetchJobsForStage = ({ dispatch }, stage) => { dispatch('requestJobsForStage'); axios - .get(state.stageJobsEndpoint) - .then(({ data }) => dispatch('receiveJobsForStageSuccess', data)) + .get(stage.dropdown_path, { + params: { + retried: 1, + }, + }) + .then(({ data }) => { + const retriedJobs = data.retried.map(job => Object.assign({}, job, { retried: true })); + const jobs = data.latest_statuses.concat(retriedJobs); + + dispatch('receiveJobsForStageSuccess', jobs); + }) .catch(() => dispatch('receiveJobsForStageError')); }; export const receiveJobsForStageSuccess = ({ commit }, data) => diff --git a/app/assets/stylesheets/pages/builds.scss b/app/assets/stylesheets/pages/builds.scss index 14ba8b1df83..ed877f625b5 100644 --- a/app/assets/stylesheets/pages/builds.scss +++ b/app/assets/stylesheets/pages/builds.scss @@ -328,23 +328,6 @@ } } - .build-dropdown { - margin: $gl-padding 0; - padding: 0; - - .dropdown-menu-toggle { - margin-top: #{$gl-padding / 2}; - } - - svg { - position: relative; - top: 3px; - margin-right: 3px; - width: 14px; - height: 14px; - } - } - .builds-container { background-color: $white-light; border-top: 1px solid $border-color; @@ -381,15 +364,11 @@ position: absolute; left: 15px; top: 20px; - display: none; + display: block; } &.active { font-weight: $gl-font-weight-bold; - - .icon-arrow-right { - display: block; - } } &.retried { diff --git a/app/views/projects/jobs/_sidebar.html.haml b/app/views/projects/jobs/_sidebar.html.haml deleted file mode 100644 index 66a3b8b8fd1..00000000000 --- a/app/views/projects/jobs/_sidebar.html.haml +++ /dev/null @@ -1,38 +0,0 @@ -%aside.right-sidebar.right-sidebar-expanded.build-sidebar.js-build-sidebar.js-right-sidebar{ data: { "offset-top" => "101", "spy" => "affix" } } - .sidebar-container - .blocks-container - #js-details-block-vue{ data: { terminal_path: can?(current_user, :create_build_terminal, @build) && @build.has_terminal? ? terminal_project_job_path(@project, @build) : nil } } - - - if @build.pipeline.stages_count > 1 - .block-last.dropdown.build-dropdown - %div - %span{ class: "ci-status-icon-#{@build.pipeline.status}" } - = ci_icon_for_status(@build.pipeline.status) - Pipeline - = link_to "##{@build.pipeline.id}", project_pipeline_path(@project, @build.pipeline), class: 'link-commit' - from - = link_to "#{@build.pipeline.ref}", project_ref_path(@project, @build.pipeline.ref), class: 'link-commit ref-name' - %button.dropdown-menu-toggle{ type: 'button', 'data-toggle' => 'dropdown' } - %span.stage-selection More - = icon('chevron-down') - %ul.dropdown-menu - - @build.pipeline.legacy_stages.each do |stage| - %li - %a.stage-item= stage.name - - .builds-container - - HasStatus::ORDERED_STATUSES.each do |build_status| - - builds.select{|build| build.status == build_status}.each do |build| - .build-job{ class: sidebar_build_class(build, @build), data: { stage: build.stage } } - - tooltip = sanitize(build.tooltip_message.dup) - = link_to(project_job_path(@project, build), data: { toggle: 'tooltip', title: tooltip, container: 'body' }) do - = sprite_icon('arrow-right', size:16, css_class: 'icon-arrow-right') - %span{ class: "ci-status-icon-#{build.status}" } - = ci_icon_for_status(build.status) - %span - - if build.name - = build.name - - else - = build.id - - if build.retried? - = sprite_icon('retry', size:16, css_class: 'icon-retry') diff --git a/app/views/projects/jobs/show.html.haml b/app/views/projects/jobs/show.html.haml index 5321bc46e73..db62de80bf3 100644 --- a/app/views/projects/jobs/show.html.haml +++ b/app/views/projects/jobs/show.html.haml @@ -93,7 +93,7 @@ - else = render "empty_states" - = render "sidebar", builds: @builds + #js-details-block-vue{ data: { terminal_path: can?(current_user, :create_build_terminal, @build) && @build.has_terminal? ? terminal_project_job_path(@project, @build) : nil } } .js-build-options{ data: javascript_build_options } diff --git a/locale/gitlab.pot b/locale/gitlab.pot index a256d87dfa8..09b83bf15c1 100644 --- a/locale/gitlab.pot +++ b/locale/gitlab.pot @@ -4310,9 +4310,6 @@ msgstr "" msgid "Pipeline" msgstr "" -msgid "Pipeline %{pipelineLinkStart} #%{pipelineId} %{pipelineLinkEnd} from %{pipelineLinkRefStart} %{pipelineRef} %{pipelineLinkRefEnd}" -msgstr "" - msgid "Pipeline Health" msgstr "" @@ -7039,6 +7036,9 @@ msgstr "" msgid "for this project" msgstr "" +msgid "from" +msgstr "" + msgid "here" msgstr "" diff --git a/spec/features/projects/environments/environment_spec.rb b/spec/features/projects/environments/environment_spec.rb index 4c5dda29fee..70e0879dd81 100644 --- a/spec/features/projects/environments/environment_spec.rb +++ b/spec/features/projects/environments/environment_spec.rb @@ -60,7 +60,7 @@ describe 'Environment' do context 'with manual action' do let(:action) do create(:ci_build, :manual, pipeline: pipeline, - name: 'deploy to production') + name: 'deploy to production', environment: environment.name) end context 'when user has ability to trigger deployment' do @@ -73,12 +73,16 @@ describe 'Environment' do expect(page).to have_link(action.name.humanize) end - it 'does allow to play manual action' do + it 'does allow to play manual action', :js do expect(action).to be_manual + find('button.dropdown').click + expect { click_link(action.name.humanize) } .not_to change { Ci::Pipeline.count } + wait_for_all_requests + expect(page).to have_content(action.name) expect(action.reload).to be_pending end @@ -165,10 +169,10 @@ describe 'Environment' do name: action.ref, project: project) end - it 'allows to stop environment' do + it 'allows to stop environment', :js do click_button('Stop') click_button('Stop environment') # confirm modal - + wait_for_all_requests expect(page).to have_content('close_app') end end diff --git a/spec/features/projects/jobs/user_browses_job_spec.rb b/spec/features/projects/jobs/user_browses_job_spec.rb index 2d791947ee9..7be6d23af65 100644 --- a/spec/features/projects/jobs/user_browses_job_spec.rb +++ b/spec/features/projects/jobs/user_browses_job_spec.rb @@ -38,9 +38,10 @@ describe 'User browses a job', :js do let!(:build) { create(:ci_build, :failed, :trace_artifact, pipeline: pipeline) } it 'displays the failure reason' do + wait_for_all_requests within('.builds-container') do build_link = first('.build-job > a') - expect(build_link['data-title']).to eq('test - failed - (unknown failure)') + expect(build_link['data-original-title']).to eq('test - failed - (unknown failure)') end end end @@ -49,9 +50,10 @@ describe 'User browses a job', :js do let!(:build) { create(:ci_build, :failed, :retried, :trace_artifact, pipeline: pipeline) } it 'displays the failure reason and retried label' do + wait_for_all_requests within('.builds-container') do build_link = first('.build-job > a') - expect(build_link['data-title']).to eq('test - failed - (unknown failure) (retried)') + expect(build_link['data-original-title']).to eq('test - failed - (unknown failure) (retried)') end end end diff --git a/spec/features/projects/jobs_spec.rb b/spec/features/projects/jobs_spec.rb index d0bf4975b81..a95140f0fa4 100644 --- a/spec/features/projects/jobs_spec.rb +++ b/spec/features/projects/jobs_spec.rb @@ -134,23 +134,25 @@ describe 'Jobs', :clean_gitlab_redis_shared_state do expect(page).to have_content pipeline.commit.title end - it 'shows active job' do + it 'shows active job', :js do visit project_job_path(project, job) + wait_for_requests expect(page).to have_selector('.build-job.active') end end - context 'sidebar' do + context 'sidebar', :js do let(:job) { create(:ci_build, :success, :trace_live, pipeline: pipeline, name: '<img src=x onerror=alert(document.domain)>') } before do visit project_job_path(project, job) + wait_for_requests end it 'renders escaped tooltip name' do page.within('aside.right-sidebar') do - expect(find('.active.build-job a')['data-title']).to eq('<img src="x"> - passed') + expect(find('.active.build-job a')['data-original-title']).to eq('<img src=x onerror=alert(document.domain)> - passed') end end end diff --git a/spec/javascripts/job_spec.js b/spec/javascripts/job_spec.js index 2fcb5566ebc..d6b5dec9e47 100644 --- a/spec/javascripts/job_spec.js +++ b/spec/javascripts/job_spec.js @@ -57,25 +57,6 @@ describe('Job', () => { expect(job.buildStage).toBe('test'); expect(job.state).toBe(''); }); - - it('only shows the jobs matching the current stage', () => { - expect($('.build-job[data-stage="build"]').is(':visible')).toBe(false); - expect($('.build-job[data-stage="test"]').is(':visible')).toBe(true); - expect($('.build-job[data-stage="deploy"]').is(':visible')).toBe(false); - }); - - it('selects the current stage in the build dropdown menu', () => { - expect($('.stage-selection').text()).toBe('test'); - }); - - it('updates the jobs when the build dropdown changes', () => { - $('.stage-item:contains("build")').click(); - - expect($('.stage-selection').text()).toBe('build'); - expect($('.build-job[data-stage="build"]').is(':visible')).toBe(true); - expect($('.build-job[data-stage="test"]').is(':visible')).toBe(false); - expect($('.build-job[data-stage="deploy"]').is(':visible')).toBe(false); - }); }); describe('running build', () => { diff --git a/spec/javascripts/jobs/components/jobs_container_spec.js b/spec/javascripts/jobs/components/jobs_container_spec.js index f3f8ff0d031..fa3a2c4c266 100644 --- a/spec/javascripts/jobs/components/jobs_container_spec.js +++ b/spec/javascripts/jobs/components/jobs_container_spec.js @@ -2,7 +2,7 @@ import Vue from 'vue'; import component from '~/jobs/components/jobs_container.vue'; import mountComponent from '../../helpers/vue_mount_component_helper'; -describe('Artifacts block', () => { +describe('Jobs List block', () => { const Component = Vue.extend(component); let vm; @@ -16,8 +16,7 @@ describe('Artifacts block', () => { text: 'passed', tooltip: 'passed', }, - path: 'job/233432756', - id: '233432756', + id: 233432756, tooltip: 'build - passed', retried: true, }; @@ -33,8 +32,7 @@ describe('Artifacts block', () => { text: 'passed', tooltip: 'passed', }, - path: 'job/2322756', - id: '2322756', + id: 2322756, tooltip: 'build - passed', active: true, }; @@ -50,8 +48,7 @@ describe('Artifacts block', () => { text: 'passed', tooltip: 'passed', }, - path: 'job/232153', - id: '232153', + id: 232153, tooltip: 'build - passed', }; @@ -62,14 +59,16 @@ describe('Artifacts block', () => { it('renders list of jobs', () => { vm = mountComponent(Component, { jobs: [job, retried, active], + jobId: 12313, }); expect(vm.$el.querySelectorAll('a').length).toEqual(3); }); - it('renders arrow right when job is active', () => { + it('renders arrow right when job id matches `jobId`', () => { vm = mountComponent(Component, { jobs: [active], + jobId: active.id, }); expect(vm.$el.querySelector('a .js-arrow-right')).not.toBeNull(); @@ -78,6 +77,7 @@ describe('Artifacts block', () => { it('does not render arrow right when job is not active', () => { vm = mountComponent(Component, { jobs: [job], + jobId: active.id, }); expect(vm.$el.querySelector('a .js-arrow-right')).toBeNull(); @@ -86,6 +86,7 @@ describe('Artifacts block', () => { it('renders job name when present', () => { vm = mountComponent(Component, { jobs: [job], + jobId: active.id, }); expect(vm.$el.querySelector('a').textContent.trim()).toContain(job.name); @@ -95,6 +96,7 @@ describe('Artifacts block', () => { it('renders job id when job name is not available', () => { vm = mountComponent(Component, { jobs: [retried], + jobId: active.id, }); expect(vm.$el.querySelector('a').textContent.trim()).toContain(retried.id); @@ -103,14 +105,16 @@ describe('Artifacts block', () => { it('links to the job page', () => { vm = mountComponent(Component, { jobs: [job], + jobId: active.id, }); - expect(vm.$el.querySelector('a').getAttribute('href')).toEqual(job.path); + expect(vm.$el.querySelector('a').getAttribute('href')).toEqual(job.status.details_path); }); it('renders retry icon when job was retried', () => { vm = mountComponent(Component, { jobs: [retried], + jobId: active.id, }); expect(vm.$el.querySelector('.js-retry-icon')).not.toBeNull(); @@ -119,6 +123,7 @@ describe('Artifacts block', () => { it('does not render retry icon when job was not retried', () => { vm = mountComponent(Component, { jobs: [job], + jobId: active.id, }); expect(vm.$el.querySelector('.js-retry-icon')).toBeNull(); diff --git a/spec/javascripts/jobs/components/sidebar_details_block_spec.js b/spec/javascripts/jobs/components/sidebar_details_block_spec.js deleted file mode 100644 index ba19534dac2..00000000000 --- a/spec/javascripts/jobs/components/sidebar_details_block_spec.js +++ /dev/null @@ -1,139 +0,0 @@ -import Vue from 'vue'; -import sidebarDetailsBlock from '~/jobs/components/sidebar_details_block.vue'; -import job from '../mock_data'; -import mountComponent from '../../helpers/vue_mount_component_helper'; - -describe('Sidebar details block', () => { - let SidebarComponent; - let vm; - - function trimWhitespace(element) { - return element.textContent.replace(/\s+/g, ' ').trim(); - } - - beforeEach(() => { - SidebarComponent = Vue.extend(sidebarDetailsBlock); - }); - - afterEach(() => { - vm.$destroy(); - }); - - describe('when it is loading', () => { - it('should render a loading spinner', () => { - vm = mountComponent(SidebarComponent, { - job: {}, - isLoading: true, - }); - expect(vm.$el.querySelector('.fa-spinner')).toBeDefined(); - }); - }); - - describe('when there is no retry path retry', () => { - it('should not render a retry button', () => { - vm = mountComponent(SidebarComponent, { - job: {}, - isLoading: false, - }); - - expect(vm.$el.querySelector('.js-retry-job')).toBeNull(); - }); - }); - - describe('without terminal path', () => { - it('does not render terminal link', () => { - vm = mountComponent(SidebarComponent, { - job, - isLoading: false, - }); - - expect(vm.$el.querySelector('.js-terminal-link')).toBeNull(); - }); - }); - - describe('with terminal path', () => { - it('renders terminal link', () => { - vm = mountComponent(SidebarComponent, { - job, - isLoading: false, - terminalPath: 'job/43123/terminal', - }); - - expect(vm.$el.querySelector('.js-terminal-link')).not.toBeNull(); - }); - }); - - beforeEach(() => { - vm = mountComponent(SidebarComponent, { - job, - isLoading: false, - }); - }); - - describe('actions', () => { - it('should render link to new issue', () => { - expect(vm.$el.querySelector('.js-new-issue').getAttribute('href')).toEqual( - job.new_issue_path, - ); - expect(vm.$el.querySelector('.js-new-issue').textContent.trim()).toEqual('New issue'); - }); - - it('should render link to retry job', () => { - expect(vm.$el.querySelector('.js-retry-job').getAttribute('href')).toEqual(job.retry_path); - }); - - it('should render link to cancel job', () => { - expect(vm.$el.querySelector('.js-cancel-job').getAttribute('href')).toEqual(job.cancel_path); - }); - }); - - describe('information', () => { - it('should render merge request link', () => { - expect(trimWhitespace(vm.$el.querySelector('.js-job-mr'))).toEqual('Merge Request: !2'); - - expect(vm.$el.querySelector('.js-job-mr a').getAttribute('href')).toEqual( - job.merge_request.path, - ); - }); - - it('should render job duration', () => { - expect(trimWhitespace(vm.$el.querySelector('.js-job-duration'))).toEqual( - 'Duration: 6 seconds', - ); - }); - - it('should render erased date', () => { - expect(trimWhitespace(vm.$el.querySelector('.js-job-erased'))).toEqual('Erased: 3 weeks ago'); - }); - - it('should render finished date', () => { - expect(trimWhitespace(vm.$el.querySelector('.js-job-finished'))).toEqual( - 'Finished: 3 weeks ago', - ); - }); - - it('should render queued date', () => { - expect(trimWhitespace(vm.$el.querySelector('.js-job-queued'))).toEqual('Queued: 9 seconds'); - }); - - it('should render runner ID', () => { - expect(trimWhitespace(vm.$el.querySelector('.js-job-runner'))).toEqual( - 'Runner: local ci runner (#1)', - ); - }); - - it('should render timeout information', () => { - expect(trimWhitespace(vm.$el.querySelector('.js-job-timeout'))).toEqual( - 'Timeout: 1m 40s (from runner)', - ); - }); - - it('should render coverage', () => { - expect(trimWhitespace(vm.$el.querySelector('.js-job-coverage'))).toEqual('Coverage: 20%'); - }); - - it('should render tags', () => { - expect(trimWhitespace(vm.$el.querySelector('.js-job-tags'))).toEqual('Tags: tag'); - }); - }); -}); diff --git a/spec/javascripts/jobs/components/sidebar_spec.js b/spec/javascripts/jobs/components/sidebar_spec.js new file mode 100644 index 00000000000..2f5c4245ced --- /dev/null +++ b/spec/javascripts/jobs/components/sidebar_spec.js @@ -0,0 +1,196 @@ +import Vue from 'vue'; +import sidebarDetailsBlock from '~/jobs/components/sidebar.vue'; +import createStore from '~/jobs/store'; +import job, { stages, jobsInStage } from '../mock_data'; +import { mountComponentWithStore } from '../../helpers/vue_mount_component_helper'; +import { trimText } from '../../helpers/vue_component_helper'; + +describe('Sidebar details block', () => { + const SidebarComponent = Vue.extend(sidebarDetailsBlock); + let vm; + let store; + + beforeEach(() => { + store = createStore(); + }); + + afterEach(() => { + vm.$destroy(); + }); + + describe('when it is loading', () => { + it('should render a loading spinner', () => { + store.dispatch('requestJob'); + vm = mountComponentWithStore(SidebarComponent, { store }); + + expect(vm.$el.querySelector('.fa-spinner')).toBeDefined(); + }); + }); + + describe('when there is no retry path retry', () => { + it('should not render a retry button', () => { + const copy = Object.assign({}, job); + delete copy.retry_path; + + store.dispatch('receiveJobSuccess', copy); + vm = mountComponentWithStore(SidebarComponent, { + store, + }); + + expect(vm.$el.querySelector('.js-retry-job')).toBeNull(); + }); + }); + + describe('without terminal path', () => { + it('does not render terminal link', () => { + store.dispatch('receiveJobSuccess', job); + vm = mountComponentWithStore(SidebarComponent, { store }); + + expect(vm.$el.querySelector('.js-terminal-link')).toBeNull(); + }); + }); + + describe('with terminal path', () => { + it('renders terminal link', () => { + store.dispatch('receiveJobSuccess', job); + vm = mountComponentWithStore(SidebarComponent, { + store, + props: { + terminalPath: 'job/43123/terminal', + }, + }); + + expect(vm.$el.querySelector('.js-terminal-link')).not.toBeNull(); + }); + }); + + beforeEach(() => { + store.dispatch('receiveJobSuccess', job); + vm = mountComponentWithStore(SidebarComponent, { store }); + }); + + describe('actions', () => { + it('should render link to new issue', () => { + expect(vm.$el.querySelector('.js-new-issue').getAttribute('href')).toEqual( + job.new_issue_path, + ); + expect(vm.$el.querySelector('.js-new-issue').textContent.trim()).toEqual('New issue'); + }); + + it('should render link to retry job', () => { + expect(vm.$el.querySelector('.js-retry-job').getAttribute('href')).toEqual(job.retry_path); + }); + + it('should render link to cancel job', () => { + expect(vm.$el.querySelector('.js-cancel-job').getAttribute('href')).toEqual(job.cancel_path); + }); + }); + + describe('information', () => { + it('should render merge request link', () => { + expect(trimText(vm.$el.querySelector('.js-job-mr').textContent)).toEqual('Merge Request: !2'); + + expect(vm.$el.querySelector('.js-job-mr a').getAttribute('href')).toEqual( + job.merge_request.path, + ); + }); + + it('should render job duration', () => { + expect(trimText(vm.$el.querySelector('.js-job-duration').textContent)).toEqual( + 'Duration: 6 seconds', + ); + }); + + it('should render erased date', () => { + expect(trimText(vm.$el.querySelector('.js-job-erased').textContent)).toEqual( + 'Erased: 3 weeks ago', + ); + }); + + it('should render finished date', () => { + expect(trimText(vm.$el.querySelector('.js-job-finished').textContent)).toEqual( + 'Finished: 3 weeks ago', + ); + }); + + it('should render queued date', () => { + expect(trimText(vm.$el.querySelector('.js-job-queued').textContent)).toEqual( + 'Queued: 9 seconds', + ); + }); + + it('should render runner ID', () => { + expect(trimText(vm.$el.querySelector('.js-job-runner').textContent)).toEqual( + 'Runner: local ci runner (#1)', + ); + }); + + it('should render timeout information', () => { + expect(trimText(vm.$el.querySelector('.js-job-timeout').textContent)).toEqual( + 'Timeout: 1m 40s (from runner)', + ); + }); + + it('should render coverage', () => { + expect(trimText(vm.$el.querySelector('.js-job-coverage').textContent)).toEqual( + 'Coverage: 20%', + ); + }); + + it('should render tags', () => { + expect(trimText(vm.$el.querySelector('.js-job-tags').textContent)).toEqual('Tags: tag'); + }); + }); + + describe('stages dropdown', () => { + beforeEach(() => { + store.dispatch('receiveJobSuccess', job); + }); + + describe('while fetching stages', () => { + it('renders dropdown with More label', () => { + vm = mountComponentWithStore(SidebarComponent, { store }); + + expect(vm.$el.querySelector('.js-selected-stage').textContent.trim()).toEqual('More'); + }); + }); + + describe('with stages', () => { + beforeEach(() => { + store.dispatch('receiveStagesSuccess', stages); + vm = mountComponentWithStore(SidebarComponent, { store }); + }); + + it('renders first stage as selected', () => { + expect(vm.$el.querySelector('.js-selected-stage').textContent.trim()).toEqual( + stages[0].name, + ); + }); + }); + + describe('without jobs for stages', () => { + beforeEach(() => { + store.dispatch('receiveJobSuccess', job); + store.dispatch('receiveStagesSuccess', stages); + vm = mountComponentWithStore(SidebarComponent, { store }); + }); + + it('does not render job container', () => { + expect(vm.$el.querySelector('.js-jobs-container')).toBeNull(); + }); + }); + + describe('with jobs for stages', () => { + beforeEach(() => { + store.dispatch('receiveJobSuccess', job); + store.dispatch('receiveStagesSuccess', stages); + store.dispatch('receiveJobsForStageSuccess', jobsInStage.latest_statuses); + vm = mountComponentWithStore(SidebarComponent, { store }); + }); + + it('renders list of jobs', () => { + expect(vm.$el.querySelector('.js-jobs-container')).not.toBeNull(); + }); + }); + }); +}); diff --git a/spec/javascripts/jobs/components/stages_dropdown_spec.js b/spec/javascripts/jobs/components/stages_dropdown_spec.js index 402289345aa..aa6cc0f1b1a 100644 --- a/spec/javascripts/jobs/components/stages_dropdown_spec.js +++ b/spec/javascripts/jobs/components/stages_dropdown_spec.js @@ -8,10 +8,25 @@ describe('Artifacts block', () => { beforeEach(() => { vm = mountComponent(Component, { - pipelineId: 28029444, - pipelinePath: 'pipeline/28029444', - pipelineRef: '50101-truncated-job-information', - pipelineRefPath: 'commits/50101-truncated-job-information', + pipeline: { + id: 28029444, + details: { + status: { + details_path: '/gitlab-org/gitlab-ce/pipelines/28029444', + group: 'success', + has_details: true, + icon: 'status_success', + label: 'passed', + text: 'passed', + tooltip: 'passed', + }, + }, + path: 'pipeline/28029444', + }, + ref: { + path: 'commits/50101-truncated-job-information', + name: '50101-truncated-job-information', + }, stages: [ { name: 'build', @@ -20,15 +35,6 @@ describe('Artifacts block', () => { name: 'test', }, ], - pipelineStatus: { - details_path: '/gitlab-org/gitlab-ce/pipelines/28029444', - group: 'success', - has_details: true, - icon: 'status_success', - label: 'passed', - text: 'passed', - tooltip: 'passed', - }, }); }); diff --git a/spec/javascripts/jobs/mock_data.js b/spec/javascripts/jobs/mock_data.js index 8fdd9b309b7..4269b42e8b6 100644 --- a/spec/javascripts/jobs/mock_data.js +++ b/spec/javascripts/jobs/mock_data.js @@ -20,7 +20,8 @@ export default { group: 'success', has_details: true, details_path: '/root/ci-mock/-/jobs/4757', - favicon: '/assets/ci_favicons/favicon_status_success-308b4fc054cdd1b68d0865e6cfb7b02e92e3472f201507418f8eddb74ac11a59.png', + favicon: + '/assets/ci_favicons/favicon_status_success-308b4fc054cdd1b68d0865e6cfb7b02e92e3472f201507418f8eddb74ac11a59.png', action: { icon: 'retry', title: 'Retry', @@ -37,7 +38,8 @@ export default { username: 'root', id: 1, state: 'active', - avatar_url: 'https://www.gravatar.com/avatar/e64c7d89f26bd1972efa854d13d7dd61?s=80\u0026d=identicon', + avatar_url: + 'https://www.gravatar.com/avatar/e64c7d89f26bd1972efa854d13d7dd61?s=80\u0026d=identicon', web_url: 'http://localhost:3000/root', }, erase_path: '/root/ci-mock/-/jobs/4757/erase', @@ -54,7 +56,8 @@ export default { username: 'root', id: 1, state: 'active', - avatar_url: 'https://www.gravatar.com/avatar/e64c7d89f26bd1972efa854d13d7dd61?s=80\u0026d=identicon', + avatar_url: + 'https://www.gravatar.com/avatar/e64c7d89f26bd1972efa854d13d7dd61?s=80\u0026d=identicon', web_url: 'http://localhost:3000/root', }, active: false, @@ -78,7 +81,8 @@ export default { group: 'success', has_details: true, details_path: '/root/ci-mock/pipelines/140', - favicon: '/assets/ci_favicons/favicon_status_success-308b4fc054cdd1b68d0865e6cfb7b02e92e3472f201507418f8eddb74ac11a59.png', + favicon: + '/assets/ci_favicons/favicon_status_success-308b4fc054cdd1b68d0865e6cfb7b02e92e3472f201507418f8eddb74ac11a59.png', }, duration: 6, finished_at: '2017-06-01T17:32:00.042Z', @@ -107,11 +111,14 @@ export default { username: 'root', id: 1, state: 'active', - avatar_url: 'https://www.gravatar.com/avatar/e64c7d89f26bd1972efa854d13d7dd61?s=80\u0026d=identicon', + avatar_url: + 'https://www.gravatar.com/avatar/e64c7d89f26bd1972efa854d13d7dd61?s=80\u0026d=identicon', web_url: 'http://localhost:3000/root', }, - author_gravatar_url: 'https://www.gravatar.com/avatar/e64c7d89f26bd1972efa854d13d7dd61?s=80\u0026d=identicon', - commit_url: 'http://localhost:3000/root/ci-mock/commit/c58647773a6b5faf066d4ad6ff2c9fbba5f180f6', + author_gravatar_url: + 'https://www.gravatar.com/avatar/e64c7d89f26bd1972efa854d13d7dd61?s=80\u0026d=identicon', + commit_url: + 'http://localhost:3000/root/ci-mock/commit/c58647773a6b5faf066d4ad6ff2c9fbba5f180f6', commit_path: '/root/ci-mock/commit/c58647773a6b5faf066d4ad6ff2c9fbba5f180f6', }, }, @@ -125,3 +132,1029 @@ export default { }, raw_path: '/root/ci-mock/builds/4757/raw', }; + +export const stages = [ + { + name: 'build', + title: 'build: running', + groups: [ + { + name: 'build:linux', + size: 1, + status: { + icon: 'status_pending', + text: 'pending', + label: 'pending', + group: 'pending', + tooltip: 'pending', + has_details: true, + details_path: '/gitlab-org/gitlab-shell/-/jobs/1180', + illustration: { + image: 'illustrations/pending_job_empty.svg', + size: 'svg-430', + title: 'This job has not started yet', + content: 'This job is in pending state and is waiting to be picked by a runner', + }, + favicon: + '/assets/ci_favicons/favicon_status_pending-5bdf338420e5221ca24353b6bff1c9367189588750632e9a871b7af09ff6a2ae.png', + action: { + icon: 'cancel', + title: 'Cancel', + path: '/gitlab-org/gitlab-shell/-/jobs/1180/cancel', + method: 'post', + }, + }, + jobs: [ + { + id: 1180, + name: 'build:linux', + started: false, + build_path: '/gitlab-org/gitlab-shell/-/jobs/1180', + cancel_path: '/gitlab-org/gitlab-shell/-/jobs/1180/cancel', + playable: false, + created_at: '2018-09-28T11:09:57.229Z', + updated_at: '2018-09-28T11:09:57.503Z', + status: { + icon: 'status_pending', + text: 'pending', + label: 'pending', + group: 'pending', + tooltip: 'pending', + has_details: true, + details_path: '/gitlab-org/gitlab-shell/-/jobs/1180', + illustration: { + image: 'illustrations/pending_job_empty.svg', + size: 'svg-430', + title: 'This job has not started yet', + content: 'This job is in pending state and is waiting to be picked by a runner', + }, + favicon: + '/assets/ci_favicons/favicon_status_pending-5bdf338420e5221ca24353b6bff1c9367189588750632e9a871b7af09ff6a2ae.png', + action: { + icon: 'cancel', + title: 'Cancel', + path: '/gitlab-org/gitlab-shell/-/jobs/1180/cancel', + method: 'post', + }, + }, + }, + ], + }, + { + name: 'build:osx', + size: 1, + status: { + icon: 'status_success', + text: 'passed', + label: 'passed', + group: 'success', + tooltip: 'passed', + has_details: true, + details_path: '/gitlab-org/gitlab-shell/-/jobs/444', + illustration: { + image: 'illustrations/skipped-job_empty.svg', + size: 'svg-430', + title: 'This job does not have a trace.', + }, + favicon: + '/assets/ci_favicons/favicon_status_success-8451333011eee8ce9f2ab25dc487fe24a8758c694827a582f17f42b0a90446a2.png', + action: { + icon: 'retry', + title: 'Retry', + path: '/gitlab-org/gitlab-shell/-/jobs/444/retry', + method: 'post', + }, + }, + jobs: [ + { + id: 444, + name: 'build:osx', + started: '2018-05-18T05:32:20.655Z', + build_path: '/gitlab-org/gitlab-shell/-/jobs/444', + retry_path: '/gitlab-org/gitlab-shell/-/jobs/444/retry', + playable: false, + created_at: '2018-05-18T15:32:54.364Z', + updated_at: '2018-05-18T15:32:54.364Z', + status: { + icon: 'status_success', + text: 'passed', + label: 'passed', + group: 'success', + tooltip: 'passed', + has_details: true, + details_path: '/gitlab-org/gitlab-shell/-/jobs/444', + illustration: { + image: 'illustrations/skipped-job_empty.svg', + size: 'svg-430', + title: 'This job does not have a trace.', + }, + favicon: + '/assets/ci_favicons/favicon_status_success-8451333011eee8ce9f2ab25dc487fe24a8758c694827a582f17f42b0a90446a2.png', + action: { + icon: 'retry', + title: 'Retry', + path: '/gitlab-org/gitlab-shell/-/jobs/444/retry', + method: 'post', + }, + }, + }, + ], + }, + ], + status: { + icon: 'status_running', + text: 'running', + label: 'running', + group: 'running', + tooltip: 'running', + has_details: true, + details_path: '/gitlab-org/gitlab-shell/pipelines/27#build', + illustration: null, + favicon: + '/assets/ci_favicons/favicon_status_running-9c635b2419a8e1ec991c993061b89cc5aefc0743bb238ecd0c381e7741a70e8c.png', + }, + path: '/gitlab-org/gitlab-shell/pipelines/27#build', + dropdown_path: '/gitlab-org/gitlab-shell/pipelines/27/stage.json?stage=build', + }, + { + name: 'test', + title: 'test: passed with warnings', + groups: [ + { + name: 'jenkins', + size: 1, + status: { + icon: 'status_success', + text: 'passed', + label: null, + group: 'success', + tooltip: null, + has_details: false, + details_path: null, + illustration: null, + favicon: + '/assets/ci_favicons/favicon_status_success-8451333011eee8ce9f2ab25dc487fe24a8758c694827a582f17f42b0a90446a2.png', + }, + jobs: [ + { + id: 459, + name: 'jenkins', + started: '2018-05-18T09:32:20.658Z', + build_path: '/gitlab-org/gitlab-shell/-/jobs/459', + playable: false, + created_at: '2018-05-18T15:32:55.330Z', + updated_at: '2018-05-18T15:32:55.330Z', + status: { + icon: 'status_success', + text: 'passed', + label: null, + group: 'success', + tooltip: null, + has_details: false, + details_path: null, + illustration: null, + favicon: + '/assets/ci_favicons/favicon_status_success-8451333011eee8ce9f2ab25dc487fe24a8758c694827a582f17f42b0a90446a2.png', + }, + }, + ], + }, + { + name: 'rspec:linux', + size: 3, + status: { + icon: 'status_success', + text: 'passed', + label: 'passed', + group: 'success', + tooltip: 'passed', + has_details: false, + details_path: null, + illustration: null, + favicon: + '/assets/ci_favicons/favicon_status_success-8451333011eee8ce9f2ab25dc487fe24a8758c694827a582f17f42b0a90446a2.png', + }, + jobs: [ + { + id: 445, + name: 'rspec:linux 0 3', + started: '2018-05-18T07:32:20.655Z', + build_path: '/gitlab-org/gitlab-shell/-/jobs/445', + retry_path: '/gitlab-org/gitlab-shell/-/jobs/445/retry', + playable: false, + created_at: '2018-05-18T15:32:54.425Z', + updated_at: '2018-05-18T15:32:54.425Z', + status: { + icon: 'status_success', + text: 'passed', + label: 'passed', + group: 'success', + tooltip: 'passed', + has_details: true, + details_path: '/gitlab-org/gitlab-shell/-/jobs/445', + illustration: { + image: 'illustrations/skipped-job_empty.svg', + size: 'svg-430', + title: 'This job does not have a trace.', + }, + favicon: + '/assets/ci_favicons/favicon_status_success-8451333011eee8ce9f2ab25dc487fe24a8758c694827a582f17f42b0a90446a2.png', + action: { + icon: 'retry', + title: 'Retry', + path: '/gitlab-org/gitlab-shell/-/jobs/445/retry', + method: 'post', + }, + }, + }, + { + id: 446, + name: 'rspec:linux 1 3', + started: '2018-05-18T07:32:20.655Z', + build_path: '/gitlab-org/gitlab-shell/-/jobs/446', + retry_path: '/gitlab-org/gitlab-shell/-/jobs/446/retry', + playable: false, + created_at: '2018-05-18T15:32:54.506Z', + updated_at: '2018-05-18T15:32:54.506Z', + status: { + icon: 'status_success', + text: 'passed', + label: 'passed', + group: 'success', + tooltip: 'passed', + has_details: true, + details_path: '/gitlab-org/gitlab-shell/-/jobs/446', + illustration: { + image: 'illustrations/skipped-job_empty.svg', + size: 'svg-430', + title: 'This job does not have a trace.', + }, + favicon: + '/assets/ci_favicons/favicon_status_success-8451333011eee8ce9f2ab25dc487fe24a8758c694827a582f17f42b0a90446a2.png', + action: { + icon: 'retry', + title: 'Retry', + path: '/gitlab-org/gitlab-shell/-/jobs/446/retry', + method: 'post', + }, + }, + }, + { + id: 447, + name: 'rspec:linux 2 3', + started: '2018-05-18T07:32:20.656Z', + build_path: '/gitlab-org/gitlab-shell/-/jobs/447', + retry_path: '/gitlab-org/gitlab-shell/-/jobs/447/retry', + playable: false, + created_at: '2018-05-18T15:32:54.572Z', + updated_at: '2018-05-18T15:32:54.572Z', + status: { + icon: 'status_success', + text: 'passed', + label: 'passed', + group: 'success', + tooltip: 'passed', + has_details: true, + details_path: '/gitlab-org/gitlab-shell/-/jobs/447', + illustration: { + image: 'illustrations/skipped-job_empty.svg', + size: 'svg-430', + title: 'This job does not have a trace.', + }, + favicon: + '/assets/ci_favicons/favicon_status_success-8451333011eee8ce9f2ab25dc487fe24a8758c694827a582f17f42b0a90446a2.png', + action: { + icon: 'retry', + title: 'Retry', + path: '/gitlab-org/gitlab-shell/-/jobs/447/retry', + method: 'post', + }, + }, + }, + ], + }, + { + name: 'rspec:osx', + size: 1, + status: { + icon: 'status_success', + text: 'passed', + label: 'passed', + group: 'success', + tooltip: 'passed', + has_details: true, + details_path: '/gitlab-org/gitlab-shell/-/jobs/452', + illustration: { + image: 'illustrations/skipped-job_empty.svg', + size: 'svg-430', + title: 'This job does not have a trace.', + }, + favicon: + '/assets/ci_favicons/favicon_status_success-8451333011eee8ce9f2ab25dc487fe24a8758c694827a582f17f42b0a90446a2.png', + action: { + icon: 'retry', + title: 'Retry', + path: '/gitlab-org/gitlab-shell/-/jobs/452/retry', + method: 'post', + }, + }, + jobs: [ + { + id: 452, + name: 'rspec:osx', + started: '2018-05-18T07:32:20.657Z', + build_path: '/gitlab-org/gitlab-shell/-/jobs/452', + retry_path: '/gitlab-org/gitlab-shell/-/jobs/452/retry', + playable: false, + created_at: '2018-05-18T15:32:54.920Z', + updated_at: '2018-05-18T15:32:54.920Z', + status: { + icon: 'status_success', + text: 'passed', + label: 'passed', + group: 'success', + tooltip: 'passed', + has_details: true, + details_path: '/gitlab-org/gitlab-shell/-/jobs/452', + illustration: { + image: 'illustrations/skipped-job_empty.svg', + size: 'svg-430', + title: 'This job does not have a trace.', + }, + favicon: + '/assets/ci_favicons/favicon_status_success-8451333011eee8ce9f2ab25dc487fe24a8758c694827a582f17f42b0a90446a2.png', + action: { + icon: 'retry', + title: 'Retry', + path: '/gitlab-org/gitlab-shell/-/jobs/452/retry', + method: 'post', + }, + }, + }, + ], + }, + { + name: 'rspec:windows', + size: 3, + status: { + icon: 'status_success', + text: 'passed', + label: 'passed', + group: 'success', + tooltip: 'passed', + has_details: false, + details_path: null, + illustration: null, + favicon: + '/assets/ci_favicons/favicon_status_success-8451333011eee8ce9f2ab25dc487fe24a8758c694827a582f17f42b0a90446a2.png', + }, + jobs: [ + { + id: 448, + name: 'rspec:windows 0 3', + started: '2018-05-18T07:32:20.656Z', + build_path: '/gitlab-org/gitlab-shell/-/jobs/448', + retry_path: '/gitlab-org/gitlab-shell/-/jobs/448/retry', + playable: false, + created_at: '2018-05-18T15:32:54.639Z', + updated_at: '2018-05-18T15:32:54.639Z', + status: { + icon: 'status_success', + text: 'passed', + label: 'passed', + group: 'success', + tooltip: 'passed', + has_details: true, + details_path: '/gitlab-org/gitlab-shell/-/jobs/448', + illustration: { + image: 'illustrations/skipped-job_empty.svg', + size: 'svg-430', + title: 'This job does not have a trace.', + }, + favicon: + '/assets/ci_favicons/favicon_status_success-8451333011eee8ce9f2ab25dc487fe24a8758c694827a582f17f42b0a90446a2.png', + action: { + icon: 'retry', + title: 'Retry', + path: '/gitlab-org/gitlab-shell/-/jobs/448/retry', + method: 'post', + }, + }, + }, + { + id: 449, + name: 'rspec:windows 1 3', + started: '2018-05-18T07:32:20.656Z', + build_path: '/gitlab-org/gitlab-shell/-/jobs/449', + retry_path: '/gitlab-org/gitlab-shell/-/jobs/449/retry', + playable: false, + created_at: '2018-05-18T15:32:54.703Z', + updated_at: '2018-05-18T15:32:54.703Z', + status: { + icon: 'status_success', + text: 'passed', + label: 'passed', + group: 'success', + tooltip: 'passed', + has_details: true, + details_path: '/gitlab-org/gitlab-shell/-/jobs/449', + illustration: { + image: 'illustrations/skipped-job_empty.svg', + size: 'svg-430', + title: 'This job does not have a trace.', + }, + favicon: + '/assets/ci_favicons/favicon_status_success-8451333011eee8ce9f2ab25dc487fe24a8758c694827a582f17f42b0a90446a2.png', + action: { + icon: 'retry', + title: 'Retry', + path: '/gitlab-org/gitlab-shell/-/jobs/449/retry', + method: 'post', + }, + }, + }, + { + id: 451, + name: 'rspec:windows 2 3', + started: '2018-05-18T07:32:20.657Z', + build_path: '/gitlab-org/gitlab-shell/-/jobs/451', + retry_path: '/gitlab-org/gitlab-shell/-/jobs/451/retry', + playable: false, + created_at: '2018-05-18T15:32:54.853Z', + updated_at: '2018-05-18T15:32:54.853Z', + status: { + icon: 'status_success', + text: 'passed', + label: 'passed', + group: 'success', + tooltip: 'passed', + has_details: true, + details_path: '/gitlab-org/gitlab-shell/-/jobs/451', + illustration: { + image: 'illustrations/skipped-job_empty.svg', + size: 'svg-430', + title: 'This job does not have a trace.', + }, + favicon: + '/assets/ci_favicons/favicon_status_success-8451333011eee8ce9f2ab25dc487fe24a8758c694827a582f17f42b0a90446a2.png', + action: { + icon: 'retry', + title: 'Retry', + path: '/gitlab-org/gitlab-shell/-/jobs/451/retry', + method: 'post', + }, + }, + }, + ], + }, + { + name: 'spinach:linux', + size: 1, + status: { + icon: 'status_success', + text: 'passed', + label: 'passed', + group: 'success', + tooltip: 'passed', + has_details: true, + details_path: '/gitlab-org/gitlab-shell/-/jobs/453', + illustration: { + image: 'illustrations/skipped-job_empty.svg', + size: 'svg-430', + title: 'This job does not have a trace.', + }, + favicon: + '/assets/ci_favicons/favicon_status_success-8451333011eee8ce9f2ab25dc487fe24a8758c694827a582f17f42b0a90446a2.png', + action: { + icon: 'retry', + title: 'Retry', + path: '/gitlab-org/gitlab-shell/-/jobs/453/retry', + method: 'post', + }, + }, + jobs: [ + { + id: 453, + name: 'spinach:linux', + started: '2018-05-18T07:32:20.657Z', + build_path: '/gitlab-org/gitlab-shell/-/jobs/453', + retry_path: '/gitlab-org/gitlab-shell/-/jobs/453/retry', + playable: false, + created_at: '2018-05-18T15:32:54.993Z', + updated_at: '2018-05-18T15:32:54.993Z', + status: { + icon: 'status_success', + text: 'passed', + label: 'passed', + group: 'success', + tooltip: 'passed', + has_details: true, + details_path: '/gitlab-org/gitlab-shell/-/jobs/453', + illustration: { + image: 'illustrations/skipped-job_empty.svg', + size: 'svg-430', + title: 'This job does not have a trace.', + }, + favicon: + '/assets/ci_favicons/favicon_status_success-8451333011eee8ce9f2ab25dc487fe24a8758c694827a582f17f42b0a90446a2.png', + action: { + icon: 'retry', + title: 'Retry', + path: '/gitlab-org/gitlab-shell/-/jobs/453/retry', + method: 'post', + }, + }, + }, + ], + }, + { + name: 'spinach:osx', + size: 1, + status: { + icon: 'status_warning', + text: 'failed', + label: 'failed (allowed to fail)', + group: 'failed_with_warnings', + tooltip: 'failed - (unknown failure) (allowed to fail)', + has_details: true, + details_path: '/gitlab-org/gitlab-shell/-/jobs/454', + illustration: { + image: 'illustrations/skipped-job_empty.svg', + size: 'svg-430', + title: 'This job does not have a trace.', + }, + favicon: + '/assets/ci_favicons/favicon_status_failed-41304d7f7e3828808b0c26771f0309e55296819a9beea3ea9fbf6689d9857c12.png', + action: { + icon: 'retry', + title: 'Retry', + path: '/gitlab-org/gitlab-shell/-/jobs/454/retry', + method: 'post', + }, + }, + jobs: [ + { + id: 454, + name: 'spinach:osx', + started: '2018-05-18T07:32:20.657Z', + build_path: '/gitlab-org/gitlab-shell/-/jobs/454', + retry_path: '/gitlab-org/gitlab-shell/-/jobs/454/retry', + playable: false, + created_at: '2018-05-18T15:32:55.053Z', + updated_at: '2018-05-18T15:32:55.053Z', + status: { + icon: 'status_warning', + text: 'failed', + label: 'failed (allowed to fail)', + group: 'failed_with_warnings', + tooltip: 'failed - (unknown failure) (allowed to fail)', + has_details: true, + details_path: '/gitlab-org/gitlab-shell/-/jobs/454', + illustration: { + image: 'illustrations/skipped-job_empty.svg', + size: 'svg-430', + title: 'This job does not have a trace.', + }, + favicon: + '/assets/ci_favicons/favicon_status_failed-41304d7f7e3828808b0c26771f0309e55296819a9beea3ea9fbf6689d9857c12.png', + action: { + icon: 'retry', + title: 'Retry', + path: '/gitlab-org/gitlab-shell/-/jobs/454/retry', + method: 'post', + }, + }, + callout_message: 'There is an unknown failure, please try again', + recoverable: true, + }, + ], + }, + ], + status: { + icon: 'status_warning', + text: 'passed', + label: 'passed with warnings', + group: 'success_with_warnings', + tooltip: 'passed', + has_details: true, + details_path: '/gitlab-org/gitlab-shell/pipelines/27#test', + illustration: null, + favicon: + '/assets/ci_favicons/favicon_status_success-8451333011eee8ce9f2ab25dc487fe24a8758c694827a582f17f42b0a90446a2.png', + }, + path: '/gitlab-org/gitlab-shell/pipelines/27#test', + dropdown_path: '/gitlab-org/gitlab-shell/pipelines/27/stage.json?stage=test', + }, + { + name: 'deploy', + title: 'deploy: running', + groups: [ + { + name: 'production', + size: 1, + status: { + icon: 'status_created', + text: 'created', + label: 'created', + group: 'created', + tooltip: 'created', + has_details: true, + details_path: '/gitlab-org/gitlab-shell/-/jobs/457', + illustration: { + image: 'illustrations/job_not_triggered.svg', + size: 'svg-306', + title: 'This job has not been triggered yet', + content: + 'This job depends on upstream jobs that need to succeed in order for this job to be triggered', + }, + favicon: + '/assets/ci_favicons/favicon_status_created-4b975aa976d24e5a3ea7cd9a5713e6ce2cd9afd08b910415e96675de35f64955.png', + action: { + icon: 'cancel', + title: 'Cancel', + path: '/gitlab-org/gitlab-shell/-/jobs/457/cancel', + method: 'post', + }, + }, + jobs: [ + { + id: 457, + name: 'production', + started: false, + build_path: '/gitlab-org/gitlab-shell/-/jobs/457', + cancel_path: '/gitlab-org/gitlab-shell/-/jobs/457/cancel', + playable: false, + created_at: '2018-05-18T15:32:55.259Z', + updated_at: '2018-09-28T11:09:57.454Z', + status: { + icon: 'status_created', + text: 'created', + label: 'created', + group: 'created', + tooltip: 'created', + has_details: true, + details_path: '/gitlab-org/gitlab-shell/-/jobs/457', + illustration: { + image: 'illustrations/job_not_triggered.svg', + size: 'svg-306', + title: 'This job has not been triggered yet', + content: + 'This job depends on upstream jobs that need to succeed in order for this job to be triggered', + }, + favicon: + '/assets/ci_favicons/favicon_status_created-4b975aa976d24e5a3ea7cd9a5713e6ce2cd9afd08b910415e96675de35f64955.png', + action: { + icon: 'cancel', + title: 'Cancel', + path: '/gitlab-org/gitlab-shell/-/jobs/457/cancel', + method: 'post', + }, + }, + }, + ], + }, + { + name: 'staging', + size: 1, + status: { + icon: 'status_success', + text: 'passed', + label: 'passed', + group: 'success', + tooltip: 'passed', + has_details: true, + details_path: '/gitlab-org/gitlab-shell/-/jobs/455', + illustration: { + image: 'illustrations/skipped-job_empty.svg', + size: 'svg-430', + title: 'This job does not have a trace.', + }, + favicon: + '/assets/ci_favicons/favicon_status_success-8451333011eee8ce9f2ab25dc487fe24a8758c694827a582f17f42b0a90446a2.png', + action: { + icon: 'retry', + title: 'Retry', + path: '/gitlab-org/gitlab-shell/-/jobs/455/retry', + method: 'post', + }, + }, + jobs: [ + { + id: 455, + name: 'staging', + started: '2018-05-18T09:32:20.658Z', + build_path: '/gitlab-org/gitlab-shell/-/jobs/455', + retry_path: '/gitlab-org/gitlab-shell/-/jobs/455/retry', + playable: false, + created_at: '2018-05-18T15:32:55.119Z', + updated_at: '2018-05-18T15:32:55.119Z', + status: { + icon: 'status_success', + text: 'passed', + label: 'passed', + group: 'success', + tooltip: 'passed', + has_details: true, + details_path: '/gitlab-org/gitlab-shell/-/jobs/455', + illustration: { + image: 'illustrations/skipped-job_empty.svg', + size: 'svg-430', + title: 'This job does not have a trace.', + }, + favicon: + '/assets/ci_favicons/favicon_status_success-8451333011eee8ce9f2ab25dc487fe24a8758c694827a582f17f42b0a90446a2.png', + action: { + icon: 'retry', + title: 'Retry', + path: '/gitlab-org/gitlab-shell/-/jobs/455/retry', + method: 'post', + }, + }, + }, + ], + }, + { + name: 'stop staging', + size: 1, + status: { + icon: 'status_created', + text: 'created', + label: 'created', + group: 'created', + tooltip: 'created', + has_details: true, + details_path: '/gitlab-org/gitlab-shell/-/jobs/456', + illustration: { + image: 'illustrations/job_not_triggered.svg', + size: 'svg-306', + title: 'This job has not been triggered yet', + content: + 'This job depends on upstream jobs that need to succeed in order for this job to be triggered', + }, + favicon: + '/assets/ci_favicons/favicon_status_created-4b975aa976d24e5a3ea7cd9a5713e6ce2cd9afd08b910415e96675de35f64955.png', + action: { + icon: 'cancel', + title: 'Cancel', + path: '/gitlab-org/gitlab-shell/-/jobs/456/cancel', + method: 'post', + }, + }, + jobs: [ + { + id: 456, + name: 'stop staging', + started: false, + build_path: '/gitlab-org/gitlab-shell/-/jobs/456', + cancel_path: '/gitlab-org/gitlab-shell/-/jobs/456/cancel', + playable: false, + created_at: '2018-05-18T15:32:55.205Z', + updated_at: '2018-09-28T11:09:57.396Z', + status: { + icon: 'status_created', + text: 'created', + label: 'created', + group: 'created', + tooltip: 'created', + has_details: true, + details_path: '/gitlab-org/gitlab-shell/-/jobs/456', + illustration: { + image: 'illustrations/job_not_triggered.svg', + size: 'svg-306', + title: 'This job has not been triggered yet', + content: + 'This job depends on upstream jobs that need to succeed in order for this job to be triggered', + }, + favicon: + '/assets/ci_favicons/favicon_status_created-4b975aa976d24e5a3ea7cd9a5713e6ce2cd9afd08b910415e96675de35f64955.png', + action: { + icon: 'cancel', + title: 'Cancel', + path: '/gitlab-org/gitlab-shell/-/jobs/456/cancel', + method: 'post', + }, + }, + }, + ], + }, + ], + status: { + icon: 'status_running', + text: 'running', + label: 'running', + group: 'running', + tooltip: 'running', + has_details: true, + details_path: '/gitlab-org/gitlab-shell/pipelines/27#deploy', + illustration: null, + favicon: + '/assets/ci_favicons/favicon_status_running-9c635b2419a8e1ec991c993061b89cc5aefc0743bb238ecd0c381e7741a70e8c.png', + }, + path: '/gitlab-org/gitlab-shell/pipelines/27#deploy', + dropdown_path: '/gitlab-org/gitlab-shell/pipelines/27/stage.json?stage=deploy', + }, + { + name: 'notify', + title: 'notify: manual action', + groups: [ + { + name: 'slack', + size: 1, + status: { + icon: 'status_manual', + text: 'manual', + label: 'manual play action', + group: 'manual', + tooltip: 'manual action', + has_details: true, + details_path: '/gitlab-org/gitlab-shell/-/jobs/458', + illustration: { + image: 'illustrations/manual_action.svg', + size: 'svg-394', + title: 'This job requires a manual action', + content: + 'This job depends on a user to trigger its process. Often they are used to deploy code to production environments', + }, + favicon: + '/assets/ci_favicons/favicon_status_manual-829a0804612cef47d9efc1618dba38325483657c847dba0546c3b9f0295bb36c.png', + action: { + icon: 'play', + title: 'Play', + path: '/gitlab-org/gitlab-shell/-/jobs/458/play', + method: 'post', + }, + }, + jobs: [ + { + id: 458, + name: 'slack', + started: null, + build_path: '/gitlab-org/gitlab-shell/-/jobs/458', + play_path: '/gitlab-org/gitlab-shell/-/jobs/458/play', + playable: true, + created_at: '2018-05-18T15:32:55.303Z', + updated_at: '2018-05-18T15:34:08.535Z', + status: { + icon: 'status_manual', + text: 'manual', + label: 'manual play action', + group: 'manual', + tooltip: 'manual action', + has_details: true, + details_path: '/gitlab-org/gitlab-shell/-/jobs/458', + illustration: { + image: 'illustrations/manual_action.svg', + size: 'svg-394', + title: 'This job requires a manual action', + content: + 'This job depends on a user to trigger its process. Often they are used to deploy code to production environments', + }, + favicon: + '/assets/ci_favicons/favicon_status_manual-829a0804612cef47d9efc1618dba38325483657c847dba0546c3b9f0295bb36c.png', + action: { + icon: 'play', + title: 'Play', + path: '/gitlab-org/gitlab-shell/-/jobs/458/play', + method: 'post', + }, + }, + }, + ], + }, + ], + status: { + icon: 'status_manual', + text: 'manual', + label: 'manual action', + group: 'manual', + tooltip: 'manual action', + has_details: true, + details_path: '/gitlab-org/gitlab-shell/pipelines/27#notify', + illustration: null, + favicon: + '/assets/ci_favicons/favicon_status_manual-829a0804612cef47d9efc1618dba38325483657c847dba0546c3b9f0295bb36c.png', + }, + path: '/gitlab-org/gitlab-shell/pipelines/27#notify', + dropdown_path: '/gitlab-org/gitlab-shell/pipelines/27/stage.json?stage=notify', + }, +]; + +export const jobsInStage = { + name: 'build', + title: 'build: running', + latest_statuses: [ + { + id: 1180, + name: 'build:linux', + started: false, + build_path: '/gitlab-org/gitlab-shell/-/jobs/1180', + cancel_path: '/gitlab-org/gitlab-shell/-/jobs/1180/cancel', + playable: false, + created_at: '2018-09-28T11:09:57.229Z', + updated_at: '2018-09-28T11:09:57.503Z', + status: { + icon: 'status_pending', + text: 'pending', + label: 'pending', + group: 'pending', + tooltip: 'pending', + has_details: true, + details_path: '/gitlab-org/gitlab-shell/-/jobs/1180', + illustration: { + image: 'illustrations/pending_job_empty.svg', + size: 'svg-430', + title: 'This job has not started yet', + content: 'This job is in pending state and is waiting to be picked by a runner', + }, + favicon: + '/assets/ci_favicons/favicon_status_pending-5bdf338420e5221ca24353b6bff1c9367189588750632e9a871b7af09ff6a2ae.png', + action: { + icon: 'cancel', + title: 'Cancel', + path: '/gitlab-org/gitlab-shell/-/jobs/1180/cancel', + method: 'post', + }, + }, + }, + { + id: 444, + name: 'build:osx', + started: '2018-05-18T05:32:20.655Z', + build_path: '/gitlab-org/gitlab-shell/-/jobs/444', + retry_path: '/gitlab-org/gitlab-shell/-/jobs/444/retry', + playable: false, + created_at: '2018-05-18T15:32:54.364Z', + updated_at: '2018-05-18T15:32:54.364Z', + status: { + icon: 'status_success', + text: 'passed', + label: 'passed', + group: 'success', + tooltip: 'passed', + has_details: true, + details_path: '/gitlab-org/gitlab-shell/-/jobs/444', + illustration: { + image: 'illustrations/skipped-job_empty.svg', + size: 'svg-430', + title: 'This job does not have a trace.', + }, + favicon: + '/assets/ci_favicons/favicon_status_success-8451333011eee8ce9f2ab25dc487fe24a8758c694827a582f17f42b0a90446a2.png', + action: { + icon: 'retry', + title: 'Retry', + path: '/gitlab-org/gitlab-shell/-/jobs/444/retry', + method: 'post', + }, + }, + }, + ], + retried: [ + { + id: 443, + name: 'build:linux', + started: '2018-05-18T06:32:20.655Z', + build_path: '/gitlab-org/gitlab-shell/-/jobs/443', + retry_path: '/gitlab-org/gitlab-shell/-/jobs/443/retry', + playable: false, + created_at: '2018-05-18T15:32:54.296Z', + updated_at: '2018-05-18T15:32:54.296Z', + status: { + icon: 'status_success', + text: 'passed', + label: 'passed', + group: 'success', + tooltip: 'passed (retried)', + has_details: true, + details_path: '/gitlab-org/gitlab-shell/-/jobs/443', + illustration: { + image: 'illustrations/skipped-job_empty.svg', + size: 'svg-430', + title: 'This job does not have a trace.', + }, + favicon: + '/assets/ci_favicons/favicon_status_success-8451333011eee8ce9f2ab25dc487fe24a8758c694827a582f17f42b0a90446a2.png', + action: { + icon: 'retry', + title: 'Retry', + path: '/gitlab-org/gitlab-shell/-/jobs/443/retry', + method: 'post', + }, + }, + }, + ], + status: { + icon: 'status_running', + text: 'running', + label: 'running', + group: 'running', + tooltip: 'running', + has_details: true, + details_path: '/gitlab-org/gitlab-shell/pipelines/27#build', + illustration: null, + favicon: + '/assets/ci_favicons/favicon_status_running-9c635b2419a8e1ec991c993061b89cc5aefc0743bb238ecd0c381e7741a70e8c.png', + }, + path: '/gitlab-org/gitlab-shell/pipelines/27#build', + dropdown_path: '/gitlab-org/gitlab-shell/pipelines/27/stage.json?stage=build', +}; diff --git a/spec/javascripts/jobs/store/actions_spec.js b/spec/javascripts/jobs/store/actions_spec.js index 5042718dfa0..5ab1f75d0d6 100644 --- a/spec/javascripts/jobs/store/actions_spec.js +++ b/spec/javascripts/jobs/store/actions_spec.js @@ -27,7 +27,6 @@ import { receiveStagesSuccess, receiveStagesError, requestJobsForStage, - setSelectedStage, fetchJobsForStage, receiveJobsForStageSuccess, receiveJobsForStageError, @@ -236,7 +235,8 @@ describe('Job State actions', () => { }, { payload: { - html: 'I, [2018-08-17T22:57:45.707325 #1841] INFO -- :', complete: true, + html: 'I, [2018-08-17T22:57:45.707325 #1841] INFO -- :', + complete: true, }, type: 'receiveTraceSuccess', }, @@ -421,7 +421,9 @@ describe('Job State actions', () => { let mock; beforeEach(() => { - mockedState.stagesEndpoint = `${TEST_HOST}/endpoint.json`; + mockedState.job.pipeline = { + path: `${TEST_HOST}/endpoint.json/stages`, + }; mock = new MockAdapter(axios); }); @@ -430,8 +432,10 @@ describe('Job State actions', () => { }); describe('success', () => { - it('dispatches requestStages and receiveStagesSuccess ', done => { - mock.onGet(`${TEST_HOST}/endpoint.json`).replyOnce(200, [{ id: 121212, name: 'build' }]); + it('dispatches requestStages and receiveStagesSuccess, fetchJobsForStage ', done => { + mock + .onGet(`${TEST_HOST}/endpoint.json/stages`) + .replyOnce(200, { details: { stages: [{ id: 121212, name: 'build' }] } }); testAction( fetchStages, @@ -446,6 +450,10 @@ describe('Job State actions', () => { payload: [{ id: 121212, name: 'build' }], type: 'receiveStagesSuccess', }, + { + payload: { id: 121212, name: 'build' }, + type: 'fetchJobsForStage', + }, ], done, ); @@ -516,24 +524,10 @@ describe('Job State actions', () => { }); }); - describe('setSelectedStage', () => { - it('should commit SET_SELECTED_STAGE mutation ', done => { - testAction( - setSelectedStage, - { name: 'build' }, - mockedState, - [{ type: types.SET_SELECTED_STAGE, payload: { name: 'build' } }], - [], - done, - ); - }); - }); - describe('fetchJobsForStage', () => { let mock; beforeEach(() => { - mockedState.stageJobsEndpoint = `${TEST_HOST}/endpoint.json`; mock = new MockAdapter(axios); }); @@ -542,20 +536,18 @@ describe('Job State actions', () => { }); describe('success', () => { - it('dispatches setSelectedStage, requestJobsForStage and receiveJobsForStageSuccess ', done => { - mock.onGet(`${TEST_HOST}/endpoint.json`).replyOnce(200, [{ id: 121212, name: 'build' }]); + it('dispatches requestJobsForStage and receiveJobsForStageSuccess ', done => { + mock + .onGet(`${TEST_HOST}/jobs.json`) + .replyOnce(200, { latest_statuses: [{ id: 121212, name: 'build' }], retried: [] }); testAction( fetchJobsForStage, - null, + { dropdown_path: `${TEST_HOST}/jobs.json` }, mockedState, [], [ { - type: 'setSelectedStage', - payload: null, - }, - { type: 'requestJobsForStage', }, { @@ -570,21 +562,17 @@ describe('Job State actions', () => { describe('error', () => { beforeEach(() => { - mock.onGet(`${TEST_HOST}/endpoint.json`).reply(500); + mock.onGet(`${TEST_HOST}/jobs.json`).reply(500); }); - it('dispatches setSelectedStage, requestJobsForStage and receiveJobsForStageError', done => { + it('dispatches requestJobsForStage and receiveJobsForStageError', done => { testAction( fetchJobsForStage, - null, + { dropdown_path: `${TEST_HOST}/jobs.json` }, mockedState, [], [ { - payload: null, - type: 'setSelectedStage', - }, - { type: 'requestJobsForStage', }, { |