diff options
Diffstat (limited to 'app/assets/javascripts/jobs')
6 files changed, 223 insertions, 41 deletions
diff --git a/app/assets/javascripts/jobs/bridge/app.vue b/app/assets/javascripts/jobs/bridge/app.vue index 67c22712776..c639e49083b 100644 --- a/app/assets/javascripts/jobs/bridge/app.vue +++ b/app/assets/javascripts/jobs/bridge/app.vue @@ -1,20 +1,118 @@ <script> +import { GlLoadingIcon } from '@gitlab/ui'; +import { GlBreakpointInstance as bp } from '@gitlab/ui/dist/utils'; +import { getIdFromGraphQLId } from '~/graphql_shared/utils'; +import { __, sprintf } from '~/locale'; +import CiHeader from '~/vue_shared/components/header_ci_component.vue'; +import getPipelineQuery from './graphql/queries/pipeline.query.graphql'; import BridgeEmptyState from './components/empty_state.vue'; import BridgeSidebar from './components/sidebar.vue'; +import { SIDEBAR_COLLAPSE_BREAKPOINTS } from './components/constants'; export default { name: 'BridgePageApp', components: { BridgeEmptyState, BridgeSidebar, + CiHeader, + GlLoadingIcon, + }, + inject: ['buildId', 'projectFullPath', 'pipelineIid'], + apollo: { + pipeline: { + query: getPipelineQuery, + variables() { + return { + fullPath: this.projectFullPath, + iid: this.pipelineIid, + }; + }, + update(data) { + if (!data?.project?.pipeline) { + return null; + } + + const { pipeline } = data.project; + const stages = pipeline?.stages.edges.map((edge) => edge.node) || []; + const jobs = stages.map((stage) => stage.jobs.nodes).flat(); + + return { + ...pipeline, + commit: { + ...pipeline.commit, + commit_path: pipeline.commit.webPath, + short_id: pipeline.commit.shortId, + }, + id: getIdFromGraphQLId(pipeline.id), + jobs, + stages, + }; + }, + }, + }, + data() { + return { + isSidebarExpanded: true, + pipeline: {}, + }; + }, + computed: { + bridgeJob() { + return ( + this.pipeline.jobs?.filter( + (job) => getIdFromGraphQLId(job.id) === Number(this.buildId), + )[0] || {} + ); + }, + bridgeName() { + return sprintf(__('Job %{jobName}'), { jobName: this.bridgeJob.name }); + }, + isPipelineLoading() { + return this.$apollo.queries.pipeline.loading; + }, + }, + created() { + window.addEventListener('resize', this.onResize); + }, + mounted() { + this.onResize(); + }, + methods: { + toggleSidebar() { + this.isSidebarExpanded = !this.isSidebarExpanded; + }, + onResize() { + const breakpoint = bp.getBreakpointSize(); + if (SIDEBAR_COLLAPSE_BREAKPOINTS.includes(breakpoint)) { + this.isSidebarExpanded = false; + } else if (!this.isSidebarExpanded) { + this.isSidebarExpanded = true; + } + }, }, }; </script> <template> <div> - <!-- TODO: get job details and show CI header --> - <!-- TODO: add downstream pipeline path --> - <bridge-empty-state downstream-pipeline-path="#" /> - <bridge-sidebar /> + <gl-loading-icon v-if="isPipelineLoading" size="lg" class="gl-mt-4" /> + <div v-else> + <ci-header + class="gl-border-b-1 gl-border-b-solid gl-border-b-gray-100" + :status="bridgeJob.detailedStatus" + :time="bridgeJob.createdAt" + :user="pipeline.user" + :has-sidebar-button="true" + :item-name="bridgeName" + @clickedSidebarButton="toggleSidebar" + /> + <bridge-empty-state :downstream-pipeline-path="bridgeJob.downstreamPipeline.path" /> + <bridge-sidebar + v-if="isSidebarExpanded" + :bridge-job="bridgeJob" + :commit="pipeline.commit" + :is-sidebar-expanded="isSidebarExpanded" + @toggleSidebar="toggleSidebar" + /> + </div> </div> </template> diff --git a/app/assets/javascripts/jobs/bridge/components/sidebar.vue b/app/assets/javascripts/jobs/bridge/components/sidebar.vue index 68b767408f0..3ba07cf55d1 100644 --- a/app/assets/javascripts/jobs/bridge/components/sidebar.vue +++ b/app/assets/javascripts/jobs/bridge/components/sidebar.vue @@ -1,14 +1,13 @@ <script> import { GlButton, GlDropdown, GlDropdownItem } from '@gitlab/ui'; -import { GlBreakpointInstance as bp } from '@gitlab/ui/dist/utils'; import { __ } from '~/locale'; +import glFeatureFlagsMixin from '~/vue_shared/mixins/gl_feature_flags_mixin'; import TooltipOnTruncate from '~/vue_shared/components/tooltip_on_truncate/tooltip_on_truncate.vue'; import { JOB_SIDEBAR } from '../../constants'; -import { SIDEBAR_COLLAPSE_BREAKPOINTS } from './constants'; +import CommitBlock from '../../components/commit_block.vue'; export default { styles: { - top: '75px', width: '290px', }, name: 'BridgeSidebar', @@ -18,40 +17,47 @@ export default { retryTriggerJob: __('Retry the trigger job'), retryDownstreamPipeline: __('Retry the downstream pipeline'), }, - borderTopClass: ['gl-border-t-solid', 'gl-border-t-1', 'gl-border-t-gray-100'], + sectionClass: ['gl-border-t-solid', 'gl-border-t-1', 'gl-border-t-gray-100', 'gl-py-5'], components: { + CommitBlock, GlButton, GlDropdown, GlDropdownItem, TooltipOnTruncate, }, - inject: { - buildName: { - type: String, - default: '', + mixins: [glFeatureFlagsMixin()], + props: { + bridgeJob: { + type: Object, + required: true, + }, + commit: { + type: Object, + required: true, }, }, data() { return { - isSidebarExpanded: true, + topPosition: 0, }; }, - created() { - window.addEventListener('resize', this.onResize); + computed: { + rootStyle() { + return { ...this.$options.styles, top: `${this.topPosition}px` }; + }, }, mounted() { - this.onResize(); + this.setTopPosition(); }, methods: { - toggleSidebar() { - this.isSidebarExpanded = !this.isSidebarExpanded; + onSidebarButtonClick() { + this.$emit('toggleSidebar'); }, - onResize() { - const breakpoint = bp.getBreakpointSize(); - if (SIDEBAR_COLLAPSE_BREAKPOINTS.includes(breakpoint)) { - this.isSidebarExpanded = false; - } else if (!this.isSidebarExpanded) { - this.isSidebarExpanded = true; + setTopPosition() { + const navbarEl = document.querySelector('.js-navbar'); + + if (navbarEl) { + this.topPosition = navbarEl.getBoundingClientRect().bottom; } }, }, @@ -60,19 +66,19 @@ export default { <template> <aside class="gl-fixed gl-right-0 gl-px-5 gl-bg-gray-10 gl-h-full gl-border-l-solid gl-border-1 gl-border-gray-100 gl-z-index-200 gl-overflow-hidden" - :style="this.$options.styles" - :class="{ - 'gl-display-none': !isSidebarExpanded, - }" + :style="rootStyle" > <div class="gl-py-5 gl-display-flex gl-align-items-center"> - <tooltip-on-truncate :title="buildName" truncate-target="child" + <tooltip-on-truncate :title="bridgeJob.name" truncate-target="child" ><h4 class="gl-mb-0 gl-mr-2 gl-text-truncate"> - {{ buildName }} + {{ bridgeJob.name }} </h4> </tooltip-on-truncate> <!-- TODO: implement retry actions --> - <div class="gl-flex-grow-1 gl-flex-shrink-0 gl-text-right"> + <div + v-if="glFeatures.triggerJobRetryAction" + class="gl-flex-grow-1 gl-flex-shrink-0 gl-text-right" + > <gl-dropdown :text="$options.i18n.retryButton" category="primary" @@ -90,9 +96,10 @@ export default { category="tertiary" class="gl-md-display-none gl-ml-2" icon="chevron-double-lg-right" - @click="toggleSidebar" + @click="onSidebarButtonClick" /> </div> - <!-- TODO: get job details and show commit block, stage dropdown, jobs list --> + <commit-block :commit="commit" :class="$options.sectionClass" /> + <!-- TODO: show stage dropdown, jobs list --> </aside> </template> diff --git a/app/assets/javascripts/jobs/bridge/graphql/queries/pipeline.query.graphql b/app/assets/javascripts/jobs/bridge/graphql/queries/pipeline.query.graphql new file mode 100644 index 00000000000..338ca9f16c7 --- /dev/null +++ b/app/assets/javascripts/jobs/bridge/graphql/queries/pipeline.query.graphql @@ -0,0 +1,70 @@ +query getPipelineData($fullPath: ID!, $iid: ID!) { + project(fullPath: $fullPath) { + id + pipeline(iid: $iid) { + id + iid + path + sha + ref + refPath + commit { + id + shortId + title + webPath + } + detailedStatus { + id + icon + group + } + stages { + edges { + node { + id + name + jobs { + nodes { + id + createdAt + name + scheduledAt + startedAt + status + triggered + detailedStatus { + id + detailsPath + icon + group + text + tooltip + } + downstreamPipeline { + id + path + } + stage { + id + name + } + } + } + } + } + } + user { + id + avatarUrl + name + username + webPath + webUrl + status { + message + } + } + } + } +} diff --git a/app/assets/javascripts/jobs/components/job_log_controllers.vue b/app/assets/javascripts/jobs/components/job_log_controllers.vue index 97141a27a5e..8e35fd91481 100644 --- a/app/assets/javascripts/jobs/components/job_log_controllers.vue +++ b/app/assets/javascripts/jobs/components/job_log_controllers.vue @@ -107,6 +107,7 @@ export default { :data-confirm="__('Are you sure you want to erase this build?')" class="gl-ml-3" data-testid="job-log-erase-link" + data-confirm-btn-variant="danger" data-method="post" icon="remove" /> diff --git a/app/assets/javascripts/jobs/components/sidebar_job_details_container.vue b/app/assets/javascripts/jobs/components/sidebar_job_details_container.vue index 5451cd21c14..5428f657252 100644 --- a/app/assets/javascripts/jobs/components/sidebar_job_details_container.vue +++ b/app/assets/javascripts/jobs/components/sidebar_job_details_container.vue @@ -1,5 +1,6 @@ <script> import { mapState } from 'vuex'; +import { GlBadge } from '@gitlab/ui'; import { helpPagePath } from '~/helpers/help_page_helper'; import { timeIntervalInWords } from '~/lib/utils/datetime_utility'; import { __, sprintf } from '~/locale'; @@ -10,6 +11,7 @@ export default { name: 'JobSidebarDetailsContainer', components: { DetailRow, + GlBadge, }, mixins: [timeagoMixin], computed: { @@ -100,12 +102,7 @@ export default { <p v-if="hasTags" class="build-detail-row" data-testid="job-tags"> <span class="font-weight-bold">{{ __('Tags:') }}</span> - <span - v-for="(tag, i) in job.tags" - :key="i" - class="badge badge-pill badge-primary gl-badge sm" - >{{ tag }}</span - > + <gl-badge v-for="(tag, i) in job.tags" :key="i" variant="info">{{ tag }}</gl-badge> </p> </div> </template> diff --git a/app/assets/javascripts/jobs/index.js b/app/assets/javascripts/jobs/index.js index e078a6c2319..6e958ea1842 100644 --- a/app/assets/javascripts/jobs/index.js +++ b/app/assets/javascripts/jobs/index.js @@ -54,7 +54,13 @@ const initializeJobPage = (element) => { }; const initializeBridgePage = (el) => { - const { buildName, emptyStateIllustrationPath } = el.dataset; + const { + buildId, + downstreamPipelinePath, + emptyStateIllustrationPath, + pipelineIid, + projectFullPath, + } = el.dataset; Vue.use(VueApollo); const apolloProvider = new VueApollo({ @@ -65,8 +71,11 @@ const initializeBridgePage = (el) => { el, apolloProvider, provide: { - buildName, + buildId, + downstreamPipelinePath, emptyStateIllustrationPath, + pipelineIid, + projectFullPath, }, render(h) { return h(BridgeApp); |