Welcome to mirror list, hosted at ThFree Co, Russian Federation.

gitlab.com/gitlab-org/gitlab-foss.git - Unnamed repository; edit this file 'description' to name the repository.
summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
Diffstat (limited to 'app/assets/javascripts/pipelines/components/pipelines_list')
-rw-r--r--app/assets/javascripts/pipelines/components/pipelines_list/failure_widget/failed_job_details.vue49
-rw-r--r--app/assets/javascripts/pipelines/components/pipelines_list/failure_widget/failed_jobs_list.vue31
-rw-r--r--app/assets/javascripts/pipelines/components/pipelines_list/failure_widget/pipeline_failed_jobs_widget.vue78
-rw-r--r--app/assets/javascripts/pipelines/components/pipelines_list/pipeline_multi_actions.vue36
-rw-r--r--app/assets/javascripts/pipelines/components/pipelines_list/pipelines.vue1
-rw-r--r--app/assets/javascripts/pipelines/components/pipelines_list/pipelines_table.vue12
6 files changed, 137 insertions, 70 deletions
diff --git a/app/assets/javascripts/pipelines/components/pipelines_list/failure_widget/failed_job_details.vue b/app/assets/javascripts/pipelines/components/pipelines_list/failure_widget/failed_job_details.vue
index 6b5e3d77b92..edf4cc87a87 100644
--- a/app/assets/javascripts/pipelines/components/pipelines_list/failure_widget/failed_job_details.vue
+++ b/app/assets/javascripts/pipelines/components/pipelines_list/failure_widget/failed_job_details.vue
@@ -1,17 +1,17 @@
<script>
-import { GlButton, GlCollapse, GlIcon, GlLink, GlTooltip } from '@gitlab/ui';
+import { GlButton, GlIcon, GlLink, GlTooltip } from '@gitlab/ui';
import { createAlert } from '~/alert';
import { __, s__, sprintf } from '~/locale';
import { getIdFromGraphQLId } from '~/graphql_shared/utils';
import CiIcon from '~/vue_shared/components/ci_icon.vue';
import SafeHtml from '~/vue_shared/directives/safe_html';
+import { BRIDGE_KIND } from '~/pipelines/components/graph/constants';
import RetryMrFailedJobMutation from '../../../graphql/mutations/retry_mr_failed_job.mutation.graphql';
export default {
components: {
CiIcon,
GlButton,
- GlCollapse,
GlIcon,
GlLink,
GlTooltip,
@@ -33,17 +33,14 @@ export default {
};
},
computed: {
- activeClass() {
- return this.isHovered ? 'gl-bg-gray-50' : '';
- },
canReadBuild() {
return this.job.userPermissions.readBuild;
},
canRetryJob() {
- return this.job.retryable && this.job.userPermissions.updateBuild;
+ return this.job.retryable && this.job.userPermissions.updateBuild && !this.isBridgeJob;
},
- isVisibleId() {
- return `log-${this.isJobLogVisible ? 'is-visible' : 'is-hidden'}`;
+ isBridgeJob() {
+ return this.job.kind === BRIDGE_KIND;
},
jobChevronName() {
return this.isJobLogVisible ? 'chevron-down' : 'chevron-right';
@@ -58,6 +55,11 @@ export default {
parsedJobId() {
return getIdFromGraphQLId(this.job.id);
},
+ tooltipErrorText() {
+ return this.isBridgeJob
+ ? this.$options.i18n.cannotRetryTrigger
+ : this.$options.i18n.cannotRetry;
+ },
tooltipText() {
return sprintf(this.$options.i18n.jobActionTooltipText, { jobName: this.job.name });
},
@@ -102,8 +104,9 @@ export default {
},
},
i18n: {
- cannotReadBuild: s__("Job|You do not have permission to read this job's log"),
- cannotRetry: s__('Job|You do not have permission to retry this job'),
+ cannotReadBuild: s__("Job|You do not have permission to read this job's log."),
+ cannotRetry: s__('Job|You do not have permission to run this job again.'),
+ cannotRetryTrigger: s__('Job|You cannot rerun trigger jobs from this list.'),
jobActionTooltipText: s__('Pipelines|Retry %{jobName} Job'),
noTraceText: s__('Job|No job log'),
retry: __('Retry'),
@@ -114,8 +117,7 @@ export default {
<template>
<div class="container-fluid gl-grid-tpl-rows-auto">
<div
- class="row gl-py-4 gl-cursor-pointer gl-display-flex gl-align-items-center"
- :class="activeClass"
+ class="row gl-my-3 gl-cursor-pointer gl-display-flex gl-align-items-center"
:aria-pressed="isJobLogVisible"
role="button"
tabindex="0"
@@ -127,22 +129,23 @@ export default {
@mouseout="resetActiveRow"
>
<div class="col-6 gl-text-gray-900 gl-font-weight-bold gl-text-left">
- <gl-icon :name="jobChevronName" class="gl-fill-blue-500" />
+ <gl-icon :name="jobChevronName" />
<ci-icon :status="job.detailedStatus" />
{{ job.name }}
</div>
<div class="col-2 gl-text-left">{{ job.stage.name }}</div>
<div class="col-2 gl-text-left">
- <gl-link :href="job.webPath">#{{ parsedJobId }}</gl-link>
+ <gl-link :href="job.detailedStatus.detailsPath">#{{ parsedJobId }}</gl-link>
</div>
<gl-tooltip v-if="!canRetryJob" :target="() => $refs.retryBtn" placement="top">
- {{ $options.i18n.cannotRetry }}
+ {{ tooltipErrorText }}
</gl-tooltip>
- <div class="col-2 gl-text-left">
+ <div class="col-2 gl-text-right">
<span ref="retryBtn">
<gl-button
:disabled="!canRetryJob"
icon="retry"
+ category="tertiary"
:loading="isLoadingAction"
:title="$options.i18n.retry"
:aria-label="$options.i18n.retry"
@@ -151,14 +154,12 @@ export default {
</span>
</div>
</div>
- <div class="row">
- <gl-collapse :visible="isJobLogVisible" class="gl-w-full">
- <pre
- v-safe-html="jobTrace"
- class="gl-bg-gray-900 gl-text-white"
- :data-testid="isVisibleId"
- ></pre>
- </gl-collapse>
+ <div v-if="isJobLogVisible" class="row">
+ <pre
+ v-safe-html="jobTrace"
+ class="gl-bg-gray-900 gl-text-white gl-w-full"
+ data-testid="job-log"
+ ></pre>
</div>
</div>
</template>
diff --git a/app/assets/javascripts/pipelines/components/pipelines_list/failure_widget/failed_jobs_list.vue b/app/assets/javascripts/pipelines/components/pipelines_list/failure_widget/failed_jobs_list.vue
index 36687129cdd..2c5aa84bc4f 100644
--- a/app/assets/javascripts/pipelines/components/pipelines_list/failure_widget/failed_jobs_list.vue
+++ b/app/assets/javascripts/pipelines/components/pipelines_list/failure_widget/failed_jobs_list.vue
@@ -9,9 +9,8 @@ import FailedJobDetails from './failed_job_details.vue';
const POLL_INTERVAL = 10000;
-const JOB_ACTION_HEADER = __('Actions');
-const JOB_ID_HEADER = __('Job ID');
-const JOB_NAME_HEADER = __('Job name');
+const JOB_ID_HEADER = __('ID');
+const JOB_NAME_HEADER = __('Name');
const STAGE_HEADER = __('Stage');
export default {
@@ -19,8 +18,12 @@ export default {
GlLoadingIcon,
FailedJobDetails,
},
- inject: ['fullPath', 'graphqlPath'],
+ inject: ['graphqlPath'],
props: {
+ failedJobsCount: {
+ required: true,
+ type: Number,
+ },
isPipelineActive: {
required: true,
type: Boolean,
@@ -29,6 +32,10 @@ export default {
type: Number,
required: true,
},
+ projectPath: {
+ type: String,
+ required: true,
+ },
},
data() {
return {
@@ -46,7 +53,7 @@ export default {
pollInterval: POLL_INTERVAL,
variables() {
return {
- fullPath: this.fullPath,
+ fullPath: this.projectPath,
pipelineIid: this.pipelineIid,
};
},
@@ -92,6 +99,13 @@ export default {
isActive(flag) {
this.handlePolling(flag);
},
+ failedJobsCount(count) {
+ // If the REST data is updated first, we force a refetch
+ // to keep them in sync
+ if (this.failedJobs.length !== count) {
+ this.$apollo.queries.failedJobs.refetch();
+ }
+ },
},
mounted() {
if (!this.isActive && !this.isPipelineActive) {
@@ -129,7 +143,6 @@ export default {
{ text: JOB_NAME_HEADER, class: 'col-6' },
{ text: STAGE_HEADER, class: 'col-2' },
{ text: JOB_ID_HEADER, class: 'col-2' },
- { text: JOB_ACTION_HEADER, class: 'col-2' },
],
i18n: {
fetchError: __('There was a problem fetching failed jobs'),
@@ -141,10 +154,10 @@ export default {
<template>
<div>
- <gl-loading-icon v-if="isInitialLoading" />
- <div v-else-if="!hasFailedJobs">{{ $options.i18n.noFailedJobs }}</div>
+ <gl-loading-icon v-if="isInitialLoading" class="gl-p-4" />
+ <div v-else-if="!hasFailedJobs" class="gl-p-4">{{ $options.i18n.noFailedJobs }}</div>
<div v-else class="container-fluid gl-grid-tpl-rows-auto">
- <div class="row gl-mb-6 gl-text-gray-900">
+ <div class="row gl-my-4 gl-text-gray-900">
<div
v-for="col in $options.columns"
:key="col.text"
diff --git a/app/assets/javascripts/pipelines/components/pipelines_list/failure_widget/pipeline_failed_jobs_widget.vue b/app/assets/javascripts/pipelines/components/pipelines_list/failure_widget/pipeline_failed_jobs_widget.vue
index 5e49c05f47d..60c429459bf 100644
--- a/app/assets/javascripts/pipelines/components/pipelines_list/failure_widget/pipeline_failed_jobs_widget.vue
+++ b/app/assets/javascripts/pipelines/components/pipelines_list/failure_widget/pipeline_failed_jobs_widget.vue
@@ -1,12 +1,12 @@
<script>
-import { GlButton, GlCollapse, GlIcon, GlLink, GlPopover, GlSprintf } from '@gitlab/ui';
+import { GlButton, GlCard, GlIcon, GlLink, GlPopover, GlSprintf } from '@gitlab/ui';
import { __, s__, sprintf } from '~/locale';
import FailedJobsList from './failed_jobs_list.vue';
export default {
components: {
GlButton,
- GlCollapse,
+ GlCard,
GlIcon,
GlLink,
GlPopover,
@@ -31,6 +31,10 @@ export default {
required: true,
type: String,
},
+ projectPath: {
+ required: true,
+ type: String,
+ },
},
data() {
return {
@@ -44,7 +48,7 @@ export default {
return this.isExpanded ? '' : 'gl-display-none';
},
failedJobsCountText() {
- return sprintf(this.$options.i18n.showFailedJobs, { count: this.currentFailedJobsCount });
+ return sprintf(this.$options.i18n.failedJobsLabel, { count: this.currentFailedJobsCount });
},
iconName() {
return this.isExpanded ? 'chevron-down' : 'chevron-right';
@@ -71,37 +75,47 @@ export default {
'Pipelines|You will see a maximum of 100 jobs in this list. To view all failed jobs, %{linkStart}go to the details page%{linkEnd} of this pipeline.',
),
additionalInfoTitle: __('Limitation on this view'),
- showFailedJobs: __('Show failed jobs (%{count})'),
+ failedJobsLabel: __('Failed jobs (%{count})'),
},
};
</script>
<template>
- <div class="gl-border-none!">
- <gl-button variant="link" @click="toggleWidget">
- <gl-icon :name="iconName" />
- {{ failedJobsCountText }}
- <gl-icon :id="popoverId" name="information-o" />
- <gl-popover :target="popoverId" placement="top">
- <template #title> {{ $options.i18n.additionalInfoTitle }} </template>
- <slot>
- <gl-sprintf :message="$options.i18n.additionalInfoPopover">
- <template #link="{ content }">
- <gl-link class="gl-font-sm" :href="pipelinePath"> {{ content }}</gl-link>
- </template>
- </gl-sprintf>
- </slot>
- </gl-popover>
- </gl-button>
- <gl-collapse
- v-model="isExpanded"
- class="gl-bg-gray-10 gl-border-1 gl-border-t gl-border-color-gray-100 gl-mt-4 gl-pt-3"
- >
- <failed-jobs-list
- v-if="isExpanded"
- :is-pipeline-active="isPipelineActive"
- :pipeline-iid="pipelineIid"
- @failed-jobs-count="setFailedJobsCount"
- />
- </gl-collapse>
- </div>
+ <gl-card
+ class="gl-new-card"
+ :class="{ 'gl-border-white gl-hover-border-gray-100': !isExpanded }"
+ header-class="gl-new-card-header gl-px-3 gl-py-3"
+ body-class="gl-new-card-body"
+ data-testid="failed-jobs-card"
+ :aria-expanded="isExpanded.toString()"
+ >
+ <template #header>
+ <gl-button
+ variant="link"
+ class="gl-text-gray-700! gl-font-weight-semibold"
+ @click="toggleWidget"
+ >
+ <gl-icon :name="iconName" />
+ {{ failedJobsCountText }}
+ <gl-icon :id="popoverId" name="information-o" class="gl-ml-2" />
+ <gl-popover :target="popoverId" placement="top">
+ <template #title> {{ $options.i18n.additionalInfoTitle }} </template>
+ <slot>
+ <gl-sprintf :message="$options.i18n.additionalInfoPopover">
+ <template #link="{ content }">
+ <gl-link class="gl-font-sm" :href="pipelinePath">{{ content }}</gl-link>
+ </template>
+ </gl-sprintf>
+ </slot>
+ </gl-popover>
+ </gl-button>
+ </template>
+ <failed-jobs-list
+ v-if="isExpanded"
+ :failed-jobs-count="failedJobsCount"
+ :is-pipeline-active="isPipelineActive"
+ :pipeline-iid="pipelineIid"
+ :project-path="projectPath"
+ @failed-jobs-count="setFailedJobsCount"
+ />
+ </gl-card>
</template>
diff --git a/app/assets/javascripts/pipelines/components/pipelines_list/pipeline_multi_actions.vue b/app/assets/javascripts/pipelines/components/pipelines_list/pipeline_multi_actions.vue
index 73a255f392b..747d94d92f2 100644
--- a/app/assets/javascripts/pipelines/components/pipelines_list/pipeline_multi_actions.vue
+++ b/app/assets/javascripts/pipelines/components/pipelines_list/pipeline_multi_actions.vue
@@ -16,6 +16,9 @@ import { TRACKING_CATEGORIES } from '../../constants';
export const i18n = {
downloadArtifacts: __('Download artifacts'),
artifactsFetchErrorMessage: s__('Pipelines|Could not load artifacts.'),
+ artifactsFetchWarningMessage: s__(
+ 'Pipelines|Failed to update. Please reload page to update the list of artifacts.',
+ ),
emptyArtifactsMessage: __('No artifacts found'),
};
@@ -52,6 +55,7 @@ export default {
hasError: false,
isLoading: false,
searchQuery: '',
+ isNewPipeline: false,
};
},
computed: {
@@ -64,13 +68,24 @@ export default {
: this.artifacts;
},
},
+ watch: {
+ pipelineId() {
+ this.isNewPipeline = true;
+ },
+ },
methods: {
fetchArtifacts() {
// refactor tracking based on action once this dropdown supports
// actions other than artifacts
this.track('click_artifacts_dropdown', { label: TRACKING_CATEGORIES.table });
+ // Preserve the last good list and present it if a request fails
+ const oldArtifacts = [...this.artifacts];
+ this.artifacts = [];
+
+ this.hasError = false;
this.isLoading = true;
+
// Replace the placeholder with the ID of the pipeline we are viewing
const endpoint = this.artifactsEndpoint.replace(
this.artifactsEndpointPlaceholder,
@@ -80,9 +95,13 @@ export default {
.get(endpoint)
.then(({ data }) => {
this.artifacts = data.artifacts;
+ this.isNewPipeline = false;
})
.catch(() => {
this.hasError = true;
+ if (!this.isNewPipeline) {
+ this.artifacts = oldArtifacts;
+ }
})
.finally(() => {
this.isLoading = false;
@@ -108,10 +127,10 @@ export default {
right
lazy
text-sr-only
- @show.once="fetchArtifacts"
+ @show="fetchArtifacts"
@shown="handleDropdownShown"
>
- <gl-alert v-if="hasError" variant="danger" :dismissible="false">
+ <gl-alert v-if="hasError && !hasArtifacts" variant="danger" :dismissible="false">
{{ $options.i18n.artifactsFetchErrorMessage }}
</gl-alert>
@@ -136,5 +155,18 @@ export default {
>
{{ artifact.name }}
</gl-dropdown-item>
+
+ <template #footer>
+ <gl-dropdown-item
+ v-if="hasError && hasArtifacts"
+ class="gl-list-style-none"
+ disabled
+ data-testid="artifacts-fetch-warning"
+ >
+ <span class="gl-font-sm">
+ {{ $options.i18n.artifactsFetchWarningMessage }}
+ </span>
+ </gl-dropdown-item>
+ </template>
</gl-dropdown>
</template>
diff --git a/app/assets/javascripts/pipelines/components/pipelines_list/pipelines.vue b/app/assets/javascripts/pipelines/components/pipelines_list/pipelines.vue
index 7d41700c492..574d291a767 100644
--- a/app/assets/javascripts/pipelines/components/pipelines_list/pipelines.vue
+++ b/app/assets/javascripts/pipelines/components/pipelines_list/pipelines.vue
@@ -1,3 +1,4 @@
+<!-- eslint-disable vue/multi-word-component-names -->
<script>
import { GlEmptyState, GlIcon, GlLoadingIcon, GlCollapsibleListbox } from '@gitlab/ui';
import { isEqual } from 'lodash';
diff --git a/app/assets/javascripts/pipelines/components/pipelines_list/pipelines_table.vue b/app/assets/javascripts/pipelines/components/pipelines_list/pipelines_table.vue
index dbb0b443235..c03085e6419 100644
--- a/app/assets/javascripts/pipelines/components/pipelines_list/pipelines_table.vue
+++ b/app/assets/javascripts/pipelines/components/pipelines_list/pipelines_table.vue
@@ -1,10 +1,11 @@
<script>
import { GlTableLite, GlTooltipDirective } from '@gitlab/ui';
+import { cleanLeadingSeparator } from '~/lib/utils/url_utility';
import { s__, __ } from '~/locale';
import Tracking from '~/tracking';
import glFeatureFlagMixin from '~/vue_shared/mixins/gl_feature_flags_mixin';
import { keepLatestDownstreamPipelines } from '~/pipelines/components/parsing_utils';
-import PipelineMiniGraph from '~/pipelines/components/pipeline_mini_graph/pipeline_mini_graph.vue';
+import LegacyPipelineMiniGraph from '~/pipelines/components/pipeline_mini_graph/legacy_pipeline_mini_graph.vue';
import PipelineFailedJobsWidget from '~/pipelines/components/pipelines_list/failure_widget/pipeline_failed_jobs_widget.vue';
import eventHub from '../../event_hub';
import { TRACKING_CATEGORIES } from '../../constants';
@@ -21,8 +22,8 @@ const DEFAULT_TH_CLASSES =
export default {
components: {
GlTableLite,
+ LegacyPipelineMiniGraph,
PipelineFailedJobsWidget,
- PipelineMiniGraph,
PipelineOperations,
PipelinesStatusBadge,
PipelineStopModal,
@@ -146,6 +147,9 @@ export default {
const downstream = pipeline.triggered;
return keepLatestDownstreamPipelines(downstream);
},
+ getProjectPath(item) {
+ return cleanLeadingSeparator(item.project.full_path);
+ },
failedJobsCount(pipeline) {
return pipeline?.failed_builds?.length || 0;
},
@@ -204,7 +208,7 @@ export default {
</template>
<template #cell(stages)="{ item }">
- <pipeline-mini-graph
+ <legacy-pipeline-mini-graph
:downstream-pipelines="getDownstreamPipelines(item)"
:pipeline-path="item.path"
:stages="item.details.stages"
@@ -225,6 +229,8 @@ export default {
:is-pipeline-active="item.active"
:pipeline-iid="item.iid"
:pipeline-path="item.path"
+ :project-path="getProjectPath(item)"
+ class="gl-ml-n4 gl-mt-n3 gl-mb-n1"
/>
</template>
</gl-table-lite>