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:
authorGitLab Bot <gitlab-bot@gitlab.com>2021-05-19 18:44:42 +0300
committerGitLab Bot <gitlab-bot@gitlab.com>2021-05-19 18:44:42 +0300
commit4555e1b21c365ed8303ffb7a3325d773c9b8bf31 (patch)
tree5423a1c7516cffe36384133ade12572cf709398d /app/assets/javascripts/jobs
parente570267f2f6b326480d284e0164a6464ba4081bc (diff)
Add latest changes from gitlab-org/gitlab@13-12-stable-eev13.12.0-rc42
Diffstat (limited to 'app/assets/javascripts/jobs')
-rw-r--r--app/assets/javascripts/jobs/components/job_app.vue26
-rw-r--r--app/assets/javascripts/jobs/components/job_container_item.vue27
-rw-r--r--app/assets/javascripts/jobs/components/job_log_controllers.vue7
-rw-r--r--app/assets/javascripts/jobs/components/table/cells/actions_cell.vue14
-rw-r--r--app/assets/javascripts/jobs/components/table/cells/duration_cell.vue49
-rw-r--r--app/assets/javascripts/jobs/components/table/cells/job_cell.vue163
-rw-r--r--app/assets/javascripts/jobs/components/table/cells/pipeline_cell.vue50
-rw-r--r--app/assets/javascripts/jobs/components/table/graphql/queries/get_jobs.query.graphql17
-rw-r--r--app/assets/javascripts/jobs/components/table/index.js10
-rw-r--r--app/assets/javascripts/jobs/components/table/jobs_table.vue85
-rw-r--r--app/assets/javascripts/jobs/components/table/jobs_table_app.vue14
-rw-r--r--app/assets/javascripts/jobs/components/table/jobs_table_empty_state.vue35
-rw-r--r--app/assets/javascripts/jobs/components/table/jobs_table_tabs.vue2
-rw-r--r--app/assets/javascripts/jobs/constants.js2
-rw-r--r--app/assets/javascripts/jobs/index.js2
15 files changed, 479 insertions, 24 deletions
diff --git a/app/assets/javascripts/jobs/components/job_app.vue b/app/assets/javascripts/jobs/components/job_app.vue
index 91ab68d5f39..be95001a396 100644
--- a/app/assets/javascripts/jobs/components/job_app.vue
+++ b/app/assets/javascripts/jobs/components/job_app.vue
@@ -3,6 +3,7 @@ import { GlLoadingIcon, GlIcon, GlSafeHtmlDirective as SafeHtml, GlAlert } from
import { GlBreakpointInstance as bp } from '@gitlab/ui/dist/utils';
import { throttle, isEmpty } from 'lodash';
import { mapGetters, mapState, mapActions } from 'vuex';
+import CodeQualityWalkthrough from '~/code_quality_walkthrough/components/step.vue';
import { isScrolledToBottom } from '~/lib/utils/scroll_utils';
import { sprintf } from '~/locale';
import CiHeader from '~/vue_shared/components/header_ci_component.vue';
@@ -32,6 +33,7 @@ export default {
GlLoadingIcon,
SharedRunner: () => import('ee_component/jobs/components/shared_runner_limit_block.vue'),
GlAlert,
+ CodeQualityWalkthrough,
},
directives: {
SafeHtml,
@@ -72,6 +74,11 @@ export default {
required: false,
default: null,
},
+ codeQualityHelpUrl: {
+ type: String,
+ required: false,
+ default: null,
+ },
},
computed: {
...mapState([
@@ -120,6 +127,10 @@ export default {
shouldRenderHeaderCallout() {
return this.shouldRenderCalloutMessage && !this.hasUnmetPrerequisitesFailure;
},
+
+ shouldRenderCodeQualityWalkthrough() {
+ return this.job.status.group === 'failed-with-warnings';
+ },
},
watch: {
// Once the job log is loaded,
@@ -190,7 +201,7 @@ export default {
</script>
<template>
<div>
- <gl-loading-icon v-if="isLoading" size="lg" class="qa-loading-animation prepend-top-20" />
+ <gl-loading-icon v-if="isLoading" size="lg" class="qa-loading-animation gl-mt-6" />
<template v-else-if="shouldRenderContent">
<div class="build-page" data-testid="job-content">
@@ -216,6 +227,11 @@ export default {
>
<div v-safe-html="job.callout_message"></div>
</gl-alert>
+ <code-quality-walkthrough
+ v-if="shouldRenderCodeQualityWalkthrough"
+ step="troubleshoot_job"
+ :link="codeQualityHelpUrl"
+ />
</header>
<!-- EO Header Section -->
@@ -256,17 +272,17 @@ export default {
<div
v-if="job.archived"
- class="gl-mt-3 archived-job"
- :class="{ 'sticky-top border-bottom-0': hasTrace }"
+ class="gl-mt-3 gl-py-2 gl-px-3 gl-align-items-center gl-z-index-1 gl-m-auto archived-job"
+ :class="{ 'sticky-top gl-border-bottom-0': hasTrace }"
data-testid="archived-job"
>
- <gl-icon name="lock" class="align-text-bottom" />
+ <gl-icon name="lock" class="gl-vertical-align-bottom" />
{{ __('This job is archived. Only the complete pipeline can be retried.') }}
</div>
<!-- job log -->
<div
v-if="hasTrace"
- class="build-trace-container position-relative"
+ class="build-trace-container gl-relative"
:class="{ 'gl-mt-3': !job.archived }"
>
<log-top-bar
diff --git a/app/assets/javascripts/jobs/components/job_container_item.vue b/app/assets/javascripts/jobs/components/job_container_item.vue
index 00a570fe2f8..c08ac0317b8 100644
--- a/app/assets/javascripts/jobs/components/job_container_item.vue
+++ b/app/assets/javascripts/jobs/components/job_container_item.vue
@@ -35,33 +35,40 @@ export default {
return text;
},
+ jobName() {
+ return this.job.name ? this.job.name : this.job.id;
+ },
+ classes() {
+ return {
+ retried: this.job.retried,
+ 'gl-font-weight-bold': this.isActive,
+ };
+ },
+ dataTestId() {
+ return this.isActive ? 'active-job' : null;
+ },
},
};
</script>
<template>
- <div
- class="build-job position-relative"
- :class="{
- retried: job.retried,
- active: isActive,
- }"
- >
+ <div class="build-job gl-relative" :class="classes">
<gl-link
v-gl-tooltip:tooltip-container.left
:href="job.status.details_path"
:title="tooltipText"
- class="js-job-link gl-display-flex gl-align-items-center"
+ class="gl-display-flex gl-align-items-center"
+ :data-testid="dataTestId"
>
<gl-icon
v-if="isActive"
name="arrow-right"
- class="js-arrow-right icon-arrow-right position-absolute d-block"
+ class="icon-arrow-right gl-absolute gl-display-block"
/>
<ci-icon :status="job.status" />
- <span class="text-truncate w-100">{{ job.name ? job.name : job.id }}</span>
+ <span class="gl-text-truncate gl-w-full">{{ jobName }}</span>
<gl-icon v-if="job.retried" name="retry" />
</gl-link>
diff --git a/app/assets/javascripts/jobs/components/job_log_controllers.vue b/app/assets/javascripts/jobs/components/job_log_controllers.vue
index ea50a11bed6..957e8243f33 100644
--- a/app/assets/javascripts/jobs/components/job_log_controllers.vue
+++ b/app/assets/javascripts/jobs/components/job_log_controllers.vue
@@ -69,7 +69,10 @@ export default {
<template>
<div class="top-bar">
<!-- truncate information -->
- <div class="truncated-info d-none d-sm-block float-left" data-testid="log-truncated-info">
+ <div
+ class="truncated-info gl-display-none gl-sm-display-block gl-float-left"
+ data-testid="log-truncated-info"
+ >
<template v-if="isTraceSizeVisible">
{{ jobLogSize }}
<gl-link
@@ -83,7 +86,7 @@ export default {
</div>
<!-- eo truncate information -->
- <div class="controllers float-right">
+ <div class="controllers gl-float-right">
<!-- links -->
<gl-button
v-if="rawPath"
diff --git a/app/assets/javascripts/jobs/components/table/cells/actions_cell.vue b/app/assets/javascripts/jobs/components/table/cells/actions_cell.vue
new file mode 100644
index 00000000000..376482b0319
--- /dev/null
+++ b/app/assets/javascripts/jobs/components/table/cells/actions_cell.vue
@@ -0,0 +1,14 @@
+<script>
+export default {
+ props: {
+ job: {
+ type: Object,
+ required: true,
+ },
+ },
+};
+</script>
+
+<template>
+ <div></div>
+</template>
diff --git a/app/assets/javascripts/jobs/components/table/cells/duration_cell.vue b/app/assets/javascripts/jobs/components/table/cells/duration_cell.vue
new file mode 100644
index 00000000000..ba5732d3d43
--- /dev/null
+++ b/app/assets/javascripts/jobs/components/table/cells/duration_cell.vue
@@ -0,0 +1,49 @@
+<script>
+import { GlIcon, GlTooltipDirective } from '@gitlab/ui';
+import timeagoMixin from '~/vue_shared/mixins/timeago';
+
+export default {
+ iconSize: 12,
+ directives: {
+ GlTooltip: GlTooltipDirective,
+ },
+ components: {
+ GlIcon,
+ },
+ mixins: [timeagoMixin],
+ props: {
+ job: {
+ type: Object,
+ required: true,
+ },
+ },
+ computed: {
+ finishedTime() {
+ return this.job?.finishedAt;
+ },
+ duration() {
+ return this.job?.duration;
+ },
+ },
+};
+</script>
+
+<template>
+ <div>
+ <div v-if="duration" data-testid="job-duration">
+ <gl-icon name="timer" :size="$options.iconSize" data-testid="duration-icon" />
+ {{ durationTimeFormatted(duration) }}
+ </div>
+ <div v-if="finishedTime" data-testid="job-finished-time">
+ <gl-icon name="calendar" :size="$options.iconSize" data-testid="finished-time-icon" />
+ <time
+ v-gl-tooltip
+ :title="tooltipTitle(finishedTime)"
+ data-placement="top"
+ data-container="body"
+ >
+ {{ timeFormatted(finishedTime) }}
+ </time>
+ </div>
+ </div>
+</template>
diff --git a/app/assets/javascripts/jobs/components/table/cells/job_cell.vue b/app/assets/javascripts/jobs/components/table/cells/job_cell.vue
new file mode 100644
index 00000000000..88a9f73258f
--- /dev/null
+++ b/app/assets/javascripts/jobs/components/table/cells/job_cell.vue
@@ -0,0 +1,163 @@
+<script>
+import { GlBadge, GlIcon, GlLink, GlTooltipDirective } from '@gitlab/ui';
+import { getIdFromGraphQLId } from '~/graphql_shared/utils';
+import { s__ } from '~/locale';
+import { SUCCESS_STATUS } from '../../../constants';
+
+export default {
+ iconSize: 12,
+ badgeSize: 'sm',
+ i18n: {
+ stuckText: s__('Jobs|Job is stuck. Check runners.'),
+ },
+ directives: {
+ GlTooltip: GlTooltipDirective,
+ },
+ components: {
+ GlBadge,
+ GlIcon,
+ GlLink,
+ },
+ props: {
+ job: {
+ type: Object,
+ required: true,
+ },
+ },
+ computed: {
+ jobId() {
+ const id = getIdFromGraphQLId(this.job.id);
+ return `#${id}`;
+ },
+ jobPath() {
+ return this.job.detailedStatus?.detailsPath;
+ },
+ jobRef() {
+ return this.job?.refName;
+ },
+ jobRefPath() {
+ return this.job?.refPath;
+ },
+ jobTags() {
+ return this.job.tags;
+ },
+ createdByTag() {
+ return this.job.createdByTag;
+ },
+ triggered() {
+ return this.job.triggered;
+ },
+ isManualJob() {
+ return this.job.manualJob;
+ },
+ successfulJob() {
+ return this.job.status === SUCCESS_STATUS;
+ },
+ showAllowedToFailBadge() {
+ return this.job.allowFailure && !this.successfulJob;
+ },
+ isScheduledJob() {
+ return Boolean(this.job.scheduledAt);
+ },
+ canReadJob() {
+ return this.job?.userPermissions?.readBuild;
+ },
+ jobStuck() {
+ return this.job?.stuck;
+ },
+ },
+};
+</script>
+
+<template>
+ <div>
+ <div class="gl-text-truncate">
+ <gl-link
+ v-if="canReadJob"
+ class="gl-text-gray-500!"
+ :href="jobPath"
+ data-testid="job-id-link"
+ >
+ {{ jobId }}
+ </gl-link>
+
+ <span v-else data-testid="job-id-limited-access">{{ jobId }}</span>
+
+ <gl-icon
+ v-if="jobStuck"
+ v-gl-tooltip="$options.i18n.stuckText"
+ name="warning"
+ :size="$options.iconSize"
+ data-testid="stuck-icon"
+ />
+
+ <div
+ class="gl-display-flex gl-align-items-center gl-lg-justify-content-start gl-justify-content-end"
+ >
+ <div v-if="jobRef" class="gl-max-w-15 gl-text-truncate">
+ <gl-icon
+ v-if="createdByTag"
+ name="label"
+ :size="$options.iconSize"
+ data-testid="label-icon"
+ />
+ <gl-icon v-else name="fork" :size="$options.iconSize" data-testid="fork-icon" />
+ <gl-link
+ class="gl-font-weight-bold gl-text-gray-500!"
+ :href="job.refPath"
+ data-testid="job-ref"
+ >{{ job.refName }}</gl-link
+ >
+ </div>
+
+ <span v-else>{{ __('none') }}</span>
+
+ <gl-icon class="gl-mx-2" name="commit" :size="$options.iconSize" />
+
+ <gl-link :href="job.commitPath" data-testid="job-sha">{{ job.shortSha }}</gl-link>
+ </div>
+ </div>
+
+ <div>
+ <gl-badge
+ v-for="tag in jobTags"
+ :key="tag"
+ variant="info"
+ :size="$options.badgeSize"
+ data-testid="job-tag-badge"
+ >
+ {{ tag }}
+ </gl-badge>
+
+ <gl-badge
+ v-if="triggered"
+ variant="info"
+ :size="$options.badgeSize"
+ data-testid="triggered-job-badge"
+ >{{ s__('Job|triggered') }}
+ </gl-badge>
+ <gl-badge
+ v-if="showAllowedToFailBadge"
+ variant="warning"
+ :size="$options.badgeSize"
+ data-testid="fail-job-badge"
+ >{{ s__('Job|allowed to fail') }}
+ </gl-badge>
+ <gl-badge
+ v-if="isScheduledJob"
+ variant="info"
+ :size="$options.badgeSize"
+ data-testid="delayed-job-badge"
+ >{{ s__('Job|delayed') }}
+ </gl-badge>
+ <gl-badge
+ v-if="isManualJob"
+ variant="info"
+ :size="$options.badgeSize"
+ data-testid="manual-job-badge"
+ >
+ {{ s__('Job|manual') }}
+ </gl-badge>
+ </div>
+ </div>
+</template>
diff --git a/app/assets/javascripts/jobs/components/table/cells/pipeline_cell.vue b/app/assets/javascripts/jobs/components/table/cells/pipeline_cell.vue
new file mode 100644
index 00000000000..71f9397f5f5
--- /dev/null
+++ b/app/assets/javascripts/jobs/components/table/cells/pipeline_cell.vue
@@ -0,0 +1,50 @@
+<script>
+import { GlAvatar, GlLink } from '@gitlab/ui';
+import { getIdFromGraphQLId } from '~/graphql_shared/utils';
+
+export default {
+ components: {
+ GlAvatar,
+ GlLink,
+ },
+ props: {
+ job: {
+ type: Object,
+ required: true,
+ },
+ },
+ computed: {
+ pipelineId() {
+ const id = getIdFromGraphQLId(this.job.pipeline.id);
+ return `#${id}`;
+ },
+ pipelinePath() {
+ return this.job.pipeline?.path;
+ },
+ pipelineUserAvatar() {
+ return this.job.pipeline?.user?.avatarUrl;
+ },
+ userPath() {
+ return this.job.pipeline?.user?.webPath;
+ },
+ showAvatar() {
+ return this.job.pipeline?.user;
+ },
+ },
+};
+</script>
+
+<template>
+ <div class="gl-text-truncate">
+ <gl-link class="gl-text-gray-500!" :href="pipelinePath" data-testid="pipeline-id">
+ {{ pipelineId }}
+ </gl-link>
+ <div>
+ <span>{{ __('created by') }}</span>
+ <gl-link v-if="showAvatar" :href="userPath" data-testid="pipeline-user-link">
+ <gl-avatar :src="pipelineUserAvatar" :size="16" />
+ </gl-link>
+ <span v-else>{{ __('API') }}</span>
+ </div>
+ </div>
+</template>
diff --git a/app/assets/javascripts/jobs/components/table/graphql/queries/get_jobs.query.graphql b/app/assets/javascripts/jobs/components/table/graphql/queries/get_jobs.query.graphql
index d9e51b0345a..c2104754bad 100644
--- a/app/assets/javascripts/jobs/components/table/graphql/queries/get_jobs.query.graphql
+++ b/app/assets/javascripts/jobs/components/table/graphql/queries/get_jobs.query.graphql
@@ -8,7 +8,20 @@ query getJobs($fullPath: ID!, $statuses: [CiJobStatus!]) {
startCursor
}
nodes {
+ artifacts {
+ nodes {
+ downloadPath
+ }
+ }
+ allowFailure
+ status
+ scheduledAt
+ manualJob
+ triggered
+ createdByTag
detailedStatus {
+ detailsPath
+ group
icon
label
text
@@ -46,6 +59,10 @@ query getJobs($fullPath: ID!, $statuses: [CiJobStatus!]) {
playable
cancelable
active
+ stuck
+ userPermissions {
+ readBuild
+ }
}
}
}
diff --git a/app/assets/javascripts/jobs/components/table/index.js b/app/assets/javascripts/jobs/components/table/index.js
index b6b3bb6d379..05d6ebfd6d6 100644
--- a/app/assets/javascripts/jobs/components/table/index.js
+++ b/app/assets/javascripts/jobs/components/table/index.js
@@ -16,13 +16,21 @@ export default (containerId = 'js-jobs-table') => {
return false;
}
- const { fullPath, jobCounts, jobStatuses } = containerEl.dataset;
+ const {
+ fullPath,
+ jobCounts,
+ jobStatuses,
+ pipelineEditorPath,
+ emptyStateSvgPath,
+ } = containerEl.dataset;
return new Vue({
el: containerEl,
apolloProvider,
provide: {
+ emptyStateSvgPath,
fullPath,
+ pipelineEditorPath,
jobStatuses: JSON.parse(jobStatuses),
jobCounts: JSON.parse(jobCounts),
},
diff --git a/app/assets/javascripts/jobs/components/table/jobs_table.vue b/app/assets/javascripts/jobs/components/table/jobs_table.vue
index 32b26d45dfe..4fe5bbf79cd 100644
--- a/app/assets/javascripts/jobs/components/table/jobs_table.vue
+++ b/app/assets/javascripts/jobs/components/table/jobs_table.vue
@@ -1,57 +1,81 @@
<script>
import { GlTable } from '@gitlab/ui';
-import { __ } from '~/locale';
+import { s__, __ } from '~/locale';
+import CiBadge from '~/vue_shared/components/ci_badge_link.vue';
+import ActionsCell from './cells/actions_cell.vue';
+import DurationCell from './cells/duration_cell.vue';
+import JobCell from './cells/job_cell.vue';
+import PipelineCell from './cells/pipeline_cell.vue';
const defaultTableClasses = {
tdClass: 'gl-p-5!',
thClass: 'gl-bg-transparent! gl-border-b-solid! gl-border-b-gray-100! gl-p-5! gl-border-b-1!',
};
+// eslint-disable-next-line @gitlab/require-i18n-strings
+const coverageTdClasses = `${defaultTableClasses.tdClass} gl-display-none! gl-lg-display-table-cell!`;
export default {
+ i18n: {
+ emptyText: s__('Jobs|No jobs to show'),
+ },
fields: [
{
key: 'status',
label: __('Status'),
...defaultTableClasses,
+ columnClass: 'gl-w-10p',
},
{
key: 'job',
label: __('Job'),
...defaultTableClasses,
+ columnClass: 'gl-w-20p',
},
{
key: 'pipeline',
label: __('Pipeline'),
...defaultTableClasses,
+ columnClass: 'gl-w-10p',
},
{
key: 'stage',
label: __('Stage'),
...defaultTableClasses,
+ columnClass: 'gl-w-10p',
},
{
key: 'name',
label: __('Name'),
...defaultTableClasses,
+ columnClass: 'gl-w-15p',
},
{
key: 'duration',
label: __('Duration'),
...defaultTableClasses,
+ columnClass: 'gl-w-15p',
},
{
key: 'coverage',
label: __('Coverage'),
- ...defaultTableClasses,
+ tdClass: coverageTdClasses,
+ thClass: defaultTableClasses.thClass,
+ columnClass: 'gl-w-10p',
},
{
key: 'actions',
label: '',
...defaultTableClasses,
+ columnClass: 'gl-w-10p',
},
],
components: {
+ ActionsCell,
+ CiBadge,
+ DurationCell,
GlTable,
+ JobCell,
+ PipelineCell,
},
props: {
jobs: {
@@ -59,9 +83,64 @@ export default {
required: true,
},
},
+ methods: {
+ formatCoverage(coverage) {
+ return coverage ? `${coverage}%` : '';
+ },
+ },
};
</script>
<template>
- <gl-table :items="jobs" :fields="$options.fields" />
+ <gl-table
+ :items="jobs"
+ :fields="$options.fields"
+ :tbody-tr-attr="{ 'data-testid': 'jobs-table-row' }"
+ :empty-text="$options.i18n.emptyText"
+ show-empty
+ stacked="lg"
+ fixed
+ >
+ <template #table-colgroup="{ fields }">
+ <col v-for="field in fields" :key="field.key" :class="field.columnClass" />
+ </template>
+
+ <template #cell(status)="{ item }">
+ <ci-badge :status="item.detailedStatus" />
+ </template>
+
+ <template #cell(job)="{ item }">
+ <job-cell :job="item" />
+ </template>
+
+ <template #cell(pipeline)="{ item }">
+ <pipeline-cell :job="item" />
+ </template>
+
+ <template #cell(stage)="{ item }">
+ <div class="gl-text-truncate">
+ <span data-testid="job-stage-name">{{ item.stage.name }}</span>
+ </div>
+ </template>
+
+ <template #cell(name)="{ item }">
+ <div class="gl-text-truncate">
+ <span data-testid="job-name">{{ item.name }}</span>
+ </div>
+ </template>
+
+ <template #cell(duration)="{ item }">
+ <duration-cell :job="item" />
+ </template>
+
+ <template #cell(coverage)="{ item }">
+ <span v-if="item.coverage" data-testid="job-coverage">{{
+ formatCoverage(item.coverage)
+ }}</span>
+ </template>
+
+ <template #cell(actions)="{ item }">
+ <actions-cell :job="item" />
+ </template>
+ </gl-table>
</template>
diff --git a/app/assets/javascripts/jobs/components/table/jobs_table_app.vue b/app/assets/javascripts/jobs/components/table/jobs_table_app.vue
index 55954e31654..cf7970f41b1 100644
--- a/app/assets/javascripts/jobs/components/table/jobs_table_app.vue
+++ b/app/assets/javascripts/jobs/components/table/jobs_table_app.vue
@@ -3,6 +3,7 @@ import { GlAlert, GlSkeletonLoader } from '@gitlab/ui';
import { __ } from '~/locale';
import GetJobs from './graphql/queries/get_jobs.query.graphql';
import JobsTable from './jobs_table.vue';
+import JobsTableEmptyState from './jobs_table_empty_state.vue';
import JobsTableTabs from './jobs_table_tabs.vue';
export default {
@@ -13,6 +14,7 @@ export default {
GlAlert,
GlSkeletonLoader,
JobsTable,
+ JobsTableEmptyState,
JobsTableTabs,
},
inject: {
@@ -29,7 +31,7 @@ export default {
};
},
update({ project }) {
- return project?.jobs;
+ return project?.jobs?.nodes || [];
},
error() {
this.hasError = true;
@@ -41,15 +43,21 @@ export default {
jobs: null,
hasError: false,
isAlertDismissed: false,
+ scope: null,
};
},
computed: {
shouldShowAlert() {
return this.hasError && !this.isAlertDismissed;
},
+ showEmptyState() {
+ return this.jobs.length === 0 && !this.scope;
+ },
},
methods: {
fetchJobsByStatus(scope) {
+ this.scope = scope;
+
this.$apollo.queries.jobs.refetch({ statuses: scope });
},
},
@@ -80,6 +88,8 @@ export default {
/>
</div>
- <jobs-table v-else :jobs="jobs.nodes" />
+ <jobs-table-empty-state v-else-if="showEmptyState" />
+
+ <jobs-table v-else :jobs="jobs" />
</div>
</template>
diff --git a/app/assets/javascripts/jobs/components/table/jobs_table_empty_state.vue b/app/assets/javascripts/jobs/components/table/jobs_table_empty_state.vue
new file mode 100644
index 00000000000..fcdd52b719c
--- /dev/null
+++ b/app/assets/javascripts/jobs/components/table/jobs_table_empty_state.vue
@@ -0,0 +1,35 @@
+<script>
+import { GlEmptyState } from '@gitlab/ui';
+import { s__ } from '~/locale';
+
+export default {
+ i18n: {
+ title: s__('Jobs|Use jobs to automate your tasks'),
+ description: s__(
+ 'Jobs|Jobs are the building blocks of a GitLab CI/CD pipeline. Each job has a specific task, like testing code. To set up jobs in a CI/CD pipeline, add a CI/CD configuration file to your project.',
+ ),
+ buttonText: s__('Jobs|Create CI/CD configuration file'),
+ },
+ components: {
+ GlEmptyState,
+ },
+ inject: {
+ pipelineEditorPath: {
+ default: '',
+ },
+ emptyStateSvgPath: {
+ default: '',
+ },
+ },
+};
+</script>
+
+<template>
+ <gl-empty-state
+ :title="$options.i18n.title"
+ :description="$options.i18n.description"
+ :svg-path="emptyStateSvgPath"
+ :primary-button-link="pipelineEditorPath"
+ :primary-button-text="$options.i18n.buttonText"
+ />
+</template>
diff --git a/app/assets/javascripts/jobs/components/table/jobs_table_tabs.vue b/app/assets/javascripts/jobs/components/table/jobs_table_tabs.vue
index 95d265fce60..26791e4284d 100644
--- a/app/assets/javascripts/jobs/components/table/jobs_table_tabs.vue
+++ b/app/assets/javascripts/jobs/components/table/jobs_table_tabs.vue
@@ -50,7 +50,7 @@ export default {
</script>
<template>
- <gl-tabs>
+ <gl-tabs content-class="gl-pb-0">
<gl-tab
v-for="tab in tabs"
:key="tab.text"
diff --git a/app/assets/javascripts/jobs/constants.js b/app/assets/javascripts/jobs/constants.js
index d0d625d794d..3040d4e2379 100644
--- a/app/assets/javascripts/jobs/constants.js
+++ b/app/assets/javascripts/jobs/constants.js
@@ -22,3 +22,5 @@ export const JOB_RETRY_FORWARD_DEPLOYMENT_MODAL = {
primaryText: __('Retry job'),
title: s__('Jobs|Are you sure you want to retry this job?'),
};
+
+export const SUCCESS_STATUS = 'SUCCESS';
diff --git a/app/assets/javascripts/jobs/index.js b/app/assets/javascripts/jobs/index.js
index 3e00056ee81..260190f5043 100644
--- a/app/assets/javascripts/jobs/index.js
+++ b/app/assets/javascripts/jobs/index.js
@@ -13,6 +13,7 @@ export default () => {
const {
artifactHelpUrl,
deploymentHelpUrl,
+ codeQualityHelpUrl,
runnerSettingsUrl,
variablesSettingsUrl,
subscriptionsMoreMinutesUrl,
@@ -38,6 +39,7 @@ export default () => {
props: {
artifactHelpUrl,
deploymentHelpUrl,
+ codeQualityHelpUrl,
runnerSettingsUrl,
variablesSettingsUrl,
subscriptionsMoreMinutesUrl,