GitLab wurde aktualisiert. Dank regelmäßiger Updates bleibt das THM GitLab sicher und Sie profitieren von den neuesten Funktionen. Vielen Dank für Ihre Geduld.

Commit cf26610f authored by Phil Hughes's avatar Phil Hughes
Browse files

Merge branch '50904-stages-sidebar' into 'master'

Moves stages dropdown into the new vue app

See merge request gitlab-org/gitlab-ce!21971
parents c4d9f402 f72a1bf0
......@@ -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);
}
}
<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" />
......
<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';
......@@ -7,26 +8,22 @@
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: 'SidebarDetailsBlock',
name: 'JobSidebar',
components: {
ArtifactsBlock,
CommitBlock,
DetailRow,
Icon,
TriggerBlock,
StagesDropdown,
JobsContainer,
},
mixins: [timeagoMixin],
props: {
job: {
type: Object,
required: true,
},
isLoading: {
type: Boolean,
required: true,
},
runnerHelpUrl: {
type: String,
required: false,
......@@ -39,9 +36,7 @@
},
},
computed: {
shouldRenderContent() {
return !this.isLoading && Object.keys(this.job).length > 0;
},
...mapState(['job', 'isLoading', 'stages', 'jobs']),
coverage() {
return `${this.job.coverage}%`;
},
......@@ -97,180 +92,206 @@
},
hasStages() {
return (
this.job &&
this.job.pipeline &&
this.job.pipeline.stages &&
this.job.pipeline.stages.length > 0
) || false;
(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>
<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>
<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>
<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
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>
<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"
<jobs-container
v-if="!isLoading && jobs.length"
:jobs="jobs"
:job-id="job.id"
/>
</template>
<gl-loading-icon
v-if="isLoading"
:size="2"
class="prepend-top-10"
/>
</div>
</div>
</aside>
</template>
<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>