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/failure_widget/failed_jobs_list.vue')
-rw-r--r--app/assets/javascripts/pipelines/components/pipelines_list/failure_widget/failed_jobs_list.vue166
1 files changed, 166 insertions, 0 deletions
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
new file mode 100644
index 00000000000..36687129cdd
--- /dev/null
+++ b/app/assets/javascripts/pipelines/components/pipelines_list/failure_widget/failed_jobs_list.vue
@@ -0,0 +1,166 @@
+<script>
+import { GlLoadingIcon } from '@gitlab/ui';
+import { createAlert } from '~/alert';
+import { __, s__, sprintf } from '~/locale';
+import { getQueryHeaders } from '~/pipelines/components/graph/utils';
+import getPipelineFailedJobs from '../../../graphql/queries/get_pipeline_failed_jobs.query.graphql';
+import { graphqlEtagPipelinePath, sortJobsByStatus } from './utils';
+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 STAGE_HEADER = __('Stage');
+
+export default {
+ components: {
+ GlLoadingIcon,
+ FailedJobDetails,
+ },
+ inject: ['fullPath', 'graphqlPath'],
+ props: {
+ isPipelineActive: {
+ required: true,
+ type: Boolean,
+ },
+ pipelineIid: {
+ type: Number,
+ required: true,
+ },
+ },
+ data() {
+ return {
+ failedJobs: [],
+ isActive: false,
+ isLoadingMore: false,
+ };
+ },
+ apollo: {
+ failedJobs: {
+ context() {
+ return getQueryHeaders(this.graphqlResourceEtag);
+ },
+ query: getPipelineFailedJobs,
+ pollInterval: POLL_INTERVAL,
+ variables() {
+ return {
+ fullPath: this.fullPath,
+ pipelineIid: this.pipelineIid,
+ };
+ },
+ update(data) {
+ const jobs = data?.project?.pipeline?.jobs?.nodes || [];
+ return sortJobsByStatus(jobs);
+ },
+ result({ data }) {
+ const pipeline = data?.project?.pipeline;
+
+ if (pipeline?.jobs?.count) {
+ this.$emit('failed-jobs-count', pipeline.jobs.count);
+ this.isActive = pipeline.active;
+ }
+ },
+ error(e) {
+ createAlert({ message: e?.message || this.$options.i18n.fetchError, variant: 'danger' });
+ },
+ },
+ },
+ computed: {
+ graphqlResourceEtag() {
+ return graphqlEtagPipelinePath(this.graphqlPath, this.pipelineIid);
+ },
+ hasFailedJobs() {
+ return this.failedJobs.length > 0;
+ },
+ isInitialLoading() {
+ return this.isLoading && !this.isLoadingMore;
+ },
+ isLoading() {
+ return this.$apollo.queries.failedJobs.loading;
+ },
+ },
+ watch: {
+ isPipelineActive(flag) {
+ // Turn polling on and off based on REST actions
+ // By refetching jobs, we will get the graphql `active`
+ // field to update properly and cascade the polling changes
+ this.refetchJobs();
+ this.handlePolling(flag);
+ },
+ isActive(flag) {
+ this.handlePolling(flag);
+ },
+ },
+ mounted() {
+ if (!this.isActive && !this.isPipelineActive) {
+ this.handlePolling(false);
+ }
+ },
+ methods: {
+ handlePolling(isActive) {
+ // If the pipeline status has changed and the widget is not expanded,
+ // We start polling.
+ if (isActive) {
+ this.$apollo.queries.failedJobs.startPolling(POLL_INTERVAL);
+ } else {
+ this.$apollo.queries.failedJobs.stopPolling();
+ }
+ },
+ async retryJob(jobName) {
+ await this.refetchJobs();
+
+ this.$toast.show(sprintf(this.$options.i18n.retriedJobsSuccess, { jobName }));
+ },
+ async refetchJobs() {
+ this.isLoadingMore = true;
+
+ try {
+ await this.$apollo.queries.failedJobs.refetch();
+ } catch {
+ createAlert(this.$options.i18n.fetchError);
+ } finally {
+ this.isLoadingMore = false;
+ }
+ },
+ },
+ columns: [
+ { 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'),
+ noFailedJobs: s__('Pipeline|No failed jobs in this pipeline 🎉'),
+ retriedJobsSuccess: __('%{jobName} job is being retried'),
+ },
+};
+</script>
+
+<template>
+ <div>
+ <gl-loading-icon v-if="isInitialLoading" />
+ <div v-else-if="!hasFailedJobs">{{ $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
+ v-for="col in $options.columns"
+ :key="col.text"
+ class="gl-font-weight-bold gl-text-left"
+ :class="col.class"
+ data-testid="header"
+ >
+ {{ col.text }}
+ </div>
+ </div>
+ </div>
+ <failed-job-details
+ v-for="job in failedJobs"
+ :key="job.id"
+ :job="job"
+ @job-retried="retryJob"
+ />
+ </div>
+</template>