diff options
author | GitLab Bot <gitlab-bot@gitlab.com> | 2021-12-20 16:37:47 +0300 |
---|---|---|
committer | GitLab Bot <gitlab-bot@gitlab.com> | 2021-12-20 16:37:47 +0300 |
commit | aee0a117a889461ce8ced6fcf73207fe017f1d99 (patch) | |
tree | 891d9ef189227a8445d83f35c1b0fc99573f4380 /app/assets/javascripts/pipelines | |
parent | 8d46af3258650d305f53b819eabf7ab18d22f59e (diff) |
Add latest changes from gitlab-org/gitlab@14-6-stable-eev14.6.0-rc42
Diffstat (limited to 'app/assets/javascripts/pipelines')
14 files changed, 287 insertions, 28 deletions
diff --git a/app/assets/javascripts/pipelines/components/graph/graph_view_selector.vue b/app/assets/javascripts/pipelines/components/graph/graph_view_selector.vue index 3c78b655dc7..1920fed84ec 100644 --- a/app/assets/javascripts/pipelines/components/graph/graph_view_selector.vue +++ b/app/assets/javascripts/pipelines/components/graph/graph_view_selector.vue @@ -1,5 +1,5 @@ <script> -import { GlAlert, GlLoadingIcon, GlSegmentedControl, GlToggle } from '@gitlab/ui'; +import { GlAlert, GlButton, GlButtonGroup, GlLoadingIcon, GlToggle } from '@gitlab/ui'; import { __, s__ } from '~/locale'; import { STAGE_VIEW, LAYER_VIEW } from './constants'; @@ -7,8 +7,9 @@ export default { name: 'GraphViewSelector', components: { GlAlert, + GlButton, + GlButtonGroup, GlLoadingIcon, - GlSegmentedControl, GlToggle, }, props: { @@ -96,6 +97,9 @@ export default { this.hoverTipDismissed = true; this.$emit('dismissHoverTip'); }, + isCurrentType(type) { + return this.segmentSelectedType === type; + }, /* In both toggle methods, we use setTimeout so that the loading indicator displays, then the work is done to update the DOM. The process is: @@ -110,11 +114,14 @@ export default { See https://www.hesselinkwebdesign.nl/2019/nexttick-vs-settimeout-in-vue/ for more details. */ - toggleView(type) { - this.isSwitcherLoading = true; - setTimeout(() => { - this.$emit('updateViewType', type); - }); + setViewType(type) { + if (!this.isCurrentType(type)) { + this.isSwitcherLoading = true; + this.segmentSelectedType = type; + setTimeout(() => { + this.$emit('updateViewType', type); + }); + } }, toggleShowLinksActive(val) { this.isToggleLoading = true; @@ -136,14 +143,16 @@ export default { size="lg" /> <span class="gl-font-weight-bold">{{ $options.i18n.viewLabelText }}</span> - <gl-segmented-control - v-model="segmentSelectedType" - :options="viewTypesList" - :disabled="isSwitcherLoading" - data-testid="pipeline-view-selector" - class="gl-mx-4" - @input="toggleView" - /> + <gl-button-group class="gl-mx-4"> + <gl-button + v-for="viewType in viewTypesList" + :key="viewType.value" + :selected="isCurrentType(viewType.value)" + @click="setViewType(viewType.value)" + > + {{ viewType.text }} + </gl-button> + </gl-button-group> <div v-if="showLinksToggle" class="gl-display-flex gl-align-items-center"> <gl-toggle diff --git a/app/assets/javascripts/pipelines/components/graph/job_group_dropdown.vue b/app/assets/javascripts/pipelines/components/graph/job_group_dropdown.vue index 6f4360649ff..12c3f9a7f40 100644 --- a/app/assets/javascripts/pipelines/components/graph/job_group_dropdown.vue +++ b/app/assets/javascripts/pipelines/components/graph/job_group_dropdown.vue @@ -67,7 +67,7 @@ export default { :class="cssClassJobName" class="dropdown-menu-toggle gl-pipeline-job-width! gl-pr-4!" > - <div class="gl-display-flex gl-align-items-center gl-justify-content-space-between"> + <div class="gl-display-flex gl-align-items-stretch gl-justify-content-space-between"> <job-item :type="$options.jobItemTypes.jobDropdown" :group-tooltip="tooltipText" diff --git a/app/assets/javascripts/pipelines/components/graph/job_item.vue b/app/assets/javascripts/pipelines/components/graph/job_item.vue index 0216b2717ed..ee58dcc4882 100644 --- a/app/assets/javascripts/pipelines/components/graph/job_item.vue +++ b/app/assets/javascripts/pipelines/components/graph/job_item.vue @@ -203,7 +203,7 @@ export default { <template> <div :id="computedJobId" - class="ci-job-component gl-display-flex gl-align-items-center gl-justify-content-space-between gl-w-full" + class="ci-job-component gl-display-flex gl-justify-content-space-between gl-pipeline-job-width" data-qa-selector="job_item_container" > <component @@ -223,12 +223,12 @@ export default { > <div class="ci-job-name-component gl-display-flex gl-align-items-center"> <ci-icon :size="24" :status="job.status" class="gl-line-height-0" /> - <div class="gl-pl-3 gl-display-flex gl-flex-direction-column gl-w-full"> - <div class="gl-text-truncate gl-w-70p gl-line-height-normal">{{ job.name }}</div> + <div class="gl-pl-3 gl-pr-3 gl-display-flex gl-flex-direction-column gl-pipeline-job-width"> + <div class="gl-text-truncate gl-pr-9 gl-line-height-normal">{{ job.name }}</div> <div v-if="showStageName" data-testid="stage-name-in-job" - class="gl-text-truncate gl-w-70p gl-font-sm gl-text-gray-500 gl-line-height-normal" + class="gl-text-truncate gl-pr-9 gl-font-sm gl-text-gray-500 gl-line-height-normal" > {{ stageName }} </div> diff --git a/app/assets/javascripts/pipelines/components/graph/linked_pipeline.vue b/app/assets/javascripts/pipelines/components/graph/linked_pipeline.vue index be47799868b..e0c1dcc5be5 100644 --- a/app/assets/javascripts/pipelines/components/graph/linked_pipeline.vue +++ b/app/assets/javascripts/pipelines/components/graph/linked_pipeline.vue @@ -124,7 +124,7 @@ export default { <div ref="linkedPipeline" v-gl-tooltip - class="gl-pipeline-job-width" + class="gl-downstream-pipeline-job-width" :title="tooltipText" data-qa-selector="child_pipeline" @mouseover="onDownstreamHovered" @@ -134,7 +134,7 @@ export default { class="gl-relative gl-bg-white gl-p-3 gl-border-solid gl-border-gray-100 gl-border-1" :class="{ 'gl-pl-9': isUpstream }" > - <div class="gl-display-flex"> + <div class="gl-display-flex gl-pr-7 gl-pipeline-job-width"> <ci-status v-if="!pipelineIsLoading" :status="pipelineStatus" @@ -142,7 +142,9 @@ export default { css-classes="gl-top-0 gl-pr-2" /> <div v-else class="gl-pr-2"><gl-loading-icon size="sm" inline /></div> - <div class="gl-display-flex gl-flex-direction-column gl-w-13"> + <div + class="gl-display-flex gl-flex-direction-column gl-pipeline-job-width gl-text-truncate" + > <span class="gl-text-truncate" data-testid="downstream-title"> {{ downstreamTitle }} </span> diff --git a/app/assets/javascripts/pipelines/components/jobs/jobs_app.vue b/app/assets/javascripts/pipelines/components/jobs/jobs_app.vue new file mode 100644 index 00000000000..ffac8206b58 --- /dev/null +++ b/app/assets/javascripts/pipelines/components/jobs/jobs_app.vue @@ -0,0 +1,121 @@ +<script> +import { GlIntersectionObserver, GlLoadingIcon, GlSkeletonLoader } from '@gitlab/ui'; +import produce from 'immer'; +import createFlash from '~/flash'; +import { __ } from '~/locale'; +import eventHub from '~/jobs/components/table/event_hub'; +import JobsTable from '~/jobs/components/table/jobs_table.vue'; +import { JOBS_TAB_FIELDS } from '~/jobs/components/table/constants'; +import getPipelineJobs from '../../graphql/queries/get_pipeline_jobs.query.graphql'; + +export default { + fields: JOBS_TAB_FIELDS, + components: { + GlIntersectionObserver, + GlLoadingIcon, + GlSkeletonLoader, + JobsTable, + }, + inject: { + fullPath: { + default: '', + }, + pipelineIid: { + default: '', + }, + }, + apollo: { + jobs: { + query: getPipelineJobs, + variables() { + return { + ...this.queryVariables, + }; + }, + update(data) { + return data.project?.pipeline?.jobs?.nodes || []; + }, + result({ data }) { + this.jobsPageInfo = data.project?.pipeline?.jobs?.pageInfo || {}; + }, + error() { + createFlash({ message: __('An error occured while fetching the pipelines jobs.') }); + }, + }, + }, + data() { + return { + jobs: [], + jobsPageInfo: {}, + firstLoad: true, + }; + }, + computed: { + queryVariables() { + return { + fullPath: this.fullPath, + iid: this.pipelineIid, + }; + }, + }, + mounted() { + eventHub.$on('jobActionPerformed', this.handleJobAction); + }, + beforeDestroy() { + eventHub.$off('jobActionPerformed', this.handleJobAction); + }, + methods: { + handleJobAction() { + this.firstLoad = true; + + this.$apollo.queries.jobs.refetch(); + }, + fetchMoreJobs() { + this.firstLoad = false; + + this.$apollo.queries.jobs.fetchMore({ + variables: { + ...this.queryVariables, + after: this.jobsPageInfo.endCursor, + }, + updateQuery: (previousResult, { fetchMoreResult }) => { + const results = produce(fetchMoreResult, (draftData) => { + draftData.project.pipeline.jobs.nodes = [ + ...previousResult.project.pipeline.jobs.nodes, + ...draftData.project.pipeline.jobs.nodes, + ]; + }); + return results; + }, + }); + }, + }, +}; +</script> + +<template> + <div> + <div v-if="$apollo.loading && firstLoad" class="gl-mt-5"> + <gl-skeleton-loader :width="1248" :height="73"> + <circle cx="748.031" cy="37.7193" r="15.0307" /> + <circle cx="787.241" cy="37.7193" r="15.0307" /> + <circle cx="827.759" cy="37.7193" r="15.0307" /> + <circle cx="866.969" cy="37.7193" r="15.0307" /> + <circle cx="380" cy="37" r="18" /> + <rect x="432" y="19" width="126.587" height="15" /> + <rect x="432" y="41" width="247" height="15" /> + <rect x="158" y="19" width="86.1" height="15" /> + <rect x="158" y="41" width="168" height="15" /> + <rect x="22" y="19" width="96" height="36" /> + <rect x="924" y="30" width="96" height="15" /> + <rect x="1057" y="20" width="166" height="35" /> + </gl-skeleton-loader> + </div> + + <jobs-table v-else :jobs="jobs" :table-fields="$options.fields" /> + + <gl-intersection-observer v-if="jobsPageInfo.hasNextPage" @appear="fetchMoreJobs"> + <gl-loading-icon v-if="$apollo.loading" size="md" /> + </gl-intersection-observer> + </div> +</template> diff --git a/app/assets/javascripts/pipelines/components/pipeline_graph/job_pill.vue b/app/assets/javascripts/pipelines/components/pipeline_graph/job_pill.vue index 836333c8bde..793e343a02a 100644 --- a/app/assets/javascripts/pipelines/components/pipeline_graph/job_pill.vue +++ b/app/assets/javascripts/pipelines/components/pipeline_graph/job_pill.vue @@ -1,5 +1,5 @@ <script> -import tooltipOnTruncate from '~/vue_shared/components/tooltip_on_truncate.vue'; +import tooltipOnTruncate from '~/vue_shared/components/tooltip_on_truncate/tooltip_on_truncate.vue'; export default { components: { diff --git a/app/assets/javascripts/pipelines/components/pipeline_graph/pipeline_graph.vue b/app/assets/javascripts/pipelines/components/pipeline_graph/pipeline_graph.vue index 78771b6a072..64210576b29 100644 --- a/app/assets/javascripts/pipelines/components/pipeline_graph/pipeline_graph.vue +++ b/app/assets/javascripts/pipelines/components/pipeline_graph/pipeline_graph.vue @@ -25,7 +25,7 @@ export default { // The max width and the width make sure the ellipsis to work and the min width // is for when there is less text than the stage column width (which the width 100% does not fix) jobWrapperClasses: - 'gl-display-flex gl-flex-direction-column gl-align-items-center gl-w-full gl-px-8 gl-min-w-full gl-max-w-15', + 'gl-display-flex gl-flex-direction-column gl-align-items-stretch gl-w-full gl-px-8 gl-min-w-full gl-max-w-15', props: { pipelineData: { required: true, diff --git a/app/assets/javascripts/pipelines/components/pipeline_graph/stage_name.vue b/app/assets/javascripts/pipelines/components/pipeline_graph/stage_name.vue index 367a18af248..e485b38ce11 100644 --- a/app/assets/javascripts/pipelines/components/pipeline_graph/stage_name.vue +++ b/app/assets/javascripts/pipelines/components/pipeline_graph/stage_name.vue @@ -1,6 +1,6 @@ <script> import { capitalize, escape } from 'lodash'; -import tooltipOnTruncate from '~/vue_shared/components/tooltip_on_truncate.vue'; +import tooltipOnTruncate from '~/vue_shared/components/tooltip_on_truncate/tooltip_on_truncate.vue'; export default { components: { diff --git a/app/assets/javascripts/pipelines/components/pipelines_list/pipeline_stage.vue b/app/assets/javascripts/pipelines/components/pipelines_list/pipeline_stage.vue index 7552ddb61dc..afcb04cd7eb 100644 --- a/app/assets/javascripts/pipelines/components/pipelines_list/pipeline_stage.vue +++ b/app/assets/javascripts/pipelines/components/pipelines_list/pipeline_stage.vue @@ -15,7 +15,7 @@ import { GlDropdown, GlLoadingIcon, GlTooltipDirective, GlIcon } from '@gitlab/ui'; import createFlash from '~/flash'; import axios from '~/lib/utils/axios_utils'; -import { __ } from '~/locale'; +import { __, sprintf } from '~/locale'; import eventHub from '../../event_hub'; import JobItem from './job_item.vue'; @@ -98,6 +98,9 @@ export default { // warn the pipelines table to update this.$emit('pipelineActionRequestComplete'); }, + stageAriaLabel(title) { + return sprintf(__('View Stage: %{title}'), { title }); + }, }, }; </script> @@ -106,9 +109,10 @@ export default { <gl-dropdown ref="dropdown" v-gl-tooltip.hover.ds0 + v-gl-tooltip="stage.title" data-testid="mini-pipeline-graph-dropdown" - :title="stage.title" variant="link" + :aria-label="stageAriaLabel(stage.title)" :lazy="true" :popper-opts="{ placement: 'bottom' }" :toggle-class="['mini-pipeline-graph-dropdown-toggle', triggerButtonClass]" diff --git a/app/assets/javascripts/pipelines/graphql/queries/get_dag_vis_data.query.graphql b/app/assets/javascripts/pipelines/graphql/queries/get_dag_vis_data.query.graphql index 887c217da41..2a0b13dd0cc 100644 --- a/app/assets/javascripts/pipelines/graphql/queries/get_dag_vis_data.query.graphql +++ b/app/assets/javascripts/pipelines/graphql/queries/get_dag_vis_data.query.graphql @@ -1,19 +1,24 @@ query getDagVisData($projectPath: ID!, $iid: ID!) { project(fullPath: $projectPath) { + id pipeline(iid: $iid) { id stages { nodes { + id name groups { nodes { + id name size jobs { nodes { + id name needs { nodes { + id name } } diff --git a/app/assets/javascripts/pipelines/graphql/queries/get_pipeline_header_data.query.graphql b/app/assets/javascripts/pipelines/graphql/queries/get_pipeline_header_data.query.graphql index 8fcae9dbad8..47bc167ca52 100644 --- a/app/assets/javascripts/pipelines/graphql/queries/get_pipeline_header_data.query.graphql +++ b/app/assets/javascripts/pipelines/graphql/queries/get_pipeline_header_data.query.graphql @@ -1,5 +1,6 @@ query getPipelineHeaderData($fullPath: ID!, $iid: ID!) { project(fullPath: $fullPath) { + id pipeline(iid: $iid) { id iid @@ -11,6 +12,7 @@ query getPipelineHeaderData($fullPath: ID!, $iid: ID!) { updatePipeline } detailedStatus { + id detailsPath icon group diff --git a/app/assets/javascripts/pipelines/graphql/queries/get_pipeline_jobs.query.graphql b/app/assets/javascripts/pipelines/graphql/queries/get_pipeline_jobs.query.graphql new file mode 100644 index 00000000000..5fe47e09d9c --- /dev/null +++ b/app/assets/javascripts/pipelines/graphql/queries/get_pipeline_jobs.query.graphql @@ -0,0 +1,70 @@ +#import "~/graphql_shared/fragments/pageInfo.fragment.graphql" + +query getPipelineJobs($fullPath: ID!, $iid: ID!, $after: String) { + project(fullPath: $fullPath) { + id + pipeline(iid: $iid) { + id + jobs(after: $after, first: 20) { + pageInfo { + ...PageInfo + } + nodes { + artifacts { + nodes { + downloadPath + fileType + } + } + allowFailure + status + scheduledAt + manualJob + triggered + createdByTag + detailedStatus { + id + detailsPath + group + icon + label + text + tooltip + action { + id + buttonTitle + icon + method + path + title + } + } + id + refName + refPath + tags + shortSha + commitPath + stage { + id + name + } + name + duration + finishedAt + coverage + retryable + playable + cancelable + active + stuck + userPermissions { + readBuild + readJobArtifacts + updateBuild + } + } + } + } + } +} diff --git a/app/assets/javascripts/pipelines/pipeline_details_bundle.js b/app/assets/javascripts/pipelines/pipeline_details_bundle.js index ee9560e36c4..ae8b2503c79 100644 --- a/app/assets/javascripts/pipelines/pipeline_details_bundle.js +++ b/app/assets/javascripts/pipelines/pipeline_details_bundle.js @@ -3,6 +3,7 @@ import { __ } from '~/locale'; import createDagApp from './pipeline_details_dag'; import { createPipelinesDetailApp } from './pipeline_details_graph'; import { createPipelineHeaderApp } from './pipeline_details_header'; +import { createPipelineJobsApp } from './pipeline_details_jobs'; import { apolloProvider } from './pipeline_shared_client'; import { createTestDetails } from './pipeline_test_details'; @@ -11,6 +12,7 @@ const SELECTORS = { PIPELINE_GRAPH: '#js-pipeline-graph-vue', PIPELINE_HEADER: '#js-pipeline-header-vue', PIPELINE_TESTS: '#js-pipeline-tests-detail', + PIPELINE_JOBS: '#js-pipeline-jobs-vue', }; export default async function initPipelineDetailsBundle() { @@ -55,4 +57,14 @@ export default async function initPipelineDetailsBundle() { message: __('An error occurred while loading the Test Reports tab.'), }); } + + try { + if (gon.features?.jobsTabVue) { + createPipelineJobsApp(SELECTORS.PIPELINE_JOBS); + } + } catch { + createFlash({ + message: __('An error occurred while loading the Jobs tab.'), + }); + } } diff --git a/app/assets/javascripts/pipelines/pipeline_details_jobs.js b/app/assets/javascripts/pipelines/pipeline_details_jobs.js new file mode 100644 index 00000000000..a1294a484f0 --- /dev/null +++ b/app/assets/javascripts/pipelines/pipeline_details_jobs.js @@ -0,0 +1,34 @@ +import { GlToast } from '@gitlab/ui'; +import Vue from 'vue'; +import VueApollo from 'vue-apollo'; +import createDefaultClient from '~/lib/graphql'; +import JobsApp from './components/jobs/jobs_app.vue'; + +Vue.use(VueApollo); +Vue.use(GlToast); + +const apolloProvider = new VueApollo({ + defaultClient: createDefaultClient(), +}); + +export const createPipelineJobsApp = (selector) => { + const containerEl = document.querySelector(selector); + + if (!containerEl) { + return false; + } + + const { fullPath, pipelineIid } = containerEl.dataset; + + return new Vue({ + el: containerEl, + apolloProvider, + provide: { + fullPath, + pipelineIid, + }, + render(createElement) { + return createElement(JobsApp); + }, + }); +}; |