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-12-20 16:37:47 +0300
committerGitLab Bot <gitlab-bot@gitlab.com>2021-12-20 16:37:47 +0300
commitaee0a117a889461ce8ced6fcf73207fe017f1d99 (patch)
tree891d9ef189227a8445d83f35c1b0fc99573f4380 /app/assets/javascripts/jobs
parent8d46af3258650d305f53b819eabf7ab18d22f59e (diff)
Add latest changes from gitlab-org/gitlab@14-6-stable-eev14.6.0-rc42
Diffstat (limited to 'app/assets/javascripts/jobs')
-rw-r--r--app/assets/javascripts/jobs/bridge/app.vue20
-rw-r--r--app/assets/javascripts/jobs/bridge/components/constants.js1
-rw-r--r--app/assets/javascripts/jobs/bridge/components/empty_state.vue45
-rw-r--r--app/assets/javascripts/jobs/bridge/components/sidebar.vue98
-rw-r--r--app/assets/javascripts/jobs/components/job_log_controllers.vue2
-rw-r--r--app/assets/javascripts/jobs/components/sidebar.vue2
-rw-r--r--app/assets/javascripts/jobs/components/sidebar_job_details_container.vue5
-rw-r--r--app/assets/javascripts/jobs/components/table/cells/actions_cell.vue16
-rw-r--r--app/assets/javascripts/jobs/components/table/cells/pipeline_cell.vue10
-rw-r--r--app/assets/javascripts/jobs/components/table/constants.js67
-rw-r--r--app/assets/javascripts/jobs/components/table/graphql/queries/get_jobs.query.graphql7
-rw-r--r--app/assets/javascripts/jobs/components/table/jobs_table.vue69
-rw-r--r--app/assets/javascripts/jobs/index.js39
13 files changed, 307 insertions, 74 deletions
diff --git a/app/assets/javascripts/jobs/bridge/app.vue b/app/assets/javascripts/jobs/bridge/app.vue
new file mode 100644
index 00000000000..67c22712776
--- /dev/null
+++ b/app/assets/javascripts/jobs/bridge/app.vue
@@ -0,0 +1,20 @@
+<script>
+import BridgeEmptyState from './components/empty_state.vue';
+import BridgeSidebar from './components/sidebar.vue';
+
+export default {
+ name: 'BridgePageApp',
+ components: {
+ BridgeEmptyState,
+ BridgeSidebar,
+ },
+};
+</script>
+<template>
+ <div>
+ <!-- TODO: get job details and show CI header -->
+ <!-- TODO: add downstream pipeline path -->
+ <bridge-empty-state downstream-pipeline-path="#" />
+ <bridge-sidebar />
+ </div>
+</template>
diff --git a/app/assets/javascripts/jobs/bridge/components/constants.js b/app/assets/javascripts/jobs/bridge/components/constants.js
new file mode 100644
index 00000000000..33310b3157a
--- /dev/null
+++ b/app/assets/javascripts/jobs/bridge/components/constants.js
@@ -0,0 +1 @@
+export const SIDEBAR_COLLAPSE_BREAKPOINTS = ['xs', 'sm'];
diff --git a/app/assets/javascripts/jobs/bridge/components/empty_state.vue b/app/assets/javascripts/jobs/bridge/components/empty_state.vue
new file mode 100644
index 00000000000..bd07d863719
--- /dev/null
+++ b/app/assets/javascripts/jobs/bridge/components/empty_state.vue
@@ -0,0 +1,45 @@
+<script>
+import { GlButton } from '@gitlab/ui';
+import { __ } from '~/locale';
+
+export default {
+ name: 'BridgeEmptyState',
+ i18n: {
+ title: __('This job triggers a downstream pipeline'),
+ linkBtnText: __('View downstream pipeline'),
+ },
+ components: {
+ GlButton,
+ },
+ inject: {
+ emptyStateIllustrationPath: {
+ type: String,
+ require: true,
+ },
+ },
+ props: {
+ downstreamPipelinePath: {
+ type: String,
+ required: false,
+ default: undefined,
+ },
+ },
+};
+</script>
+
+<template>
+ <div class="gl-display-flex gl-flex-direction-column gl-align-items-center gl-mt-11">
+ <img :src="emptyStateIllustrationPath" />
+ <h1 class="gl-font-size-h1">{{ $options.i18n.title }}</h1>
+ <gl-button
+ v-if="downstreamPipelinePath"
+ class="gl-mt-3"
+ category="secondary"
+ variant="confirm"
+ size="medium"
+ :href="downstreamPipelinePath"
+ >
+ {{ $options.i18n.linkBtnText }}
+ </gl-button>
+ </div>
+</template>
diff --git a/app/assets/javascripts/jobs/bridge/components/sidebar.vue b/app/assets/javascripts/jobs/bridge/components/sidebar.vue
new file mode 100644
index 00000000000..68b767408f0
--- /dev/null
+++ b/app/assets/javascripts/jobs/bridge/components/sidebar.vue
@@ -0,0 +1,98 @@
+<script>
+import { GlButton, GlDropdown, GlDropdownItem } from '@gitlab/ui';
+import { GlBreakpointInstance as bp } from '@gitlab/ui/dist/utils';
+import { __ } from '~/locale';
+import TooltipOnTruncate from '~/vue_shared/components/tooltip_on_truncate/tooltip_on_truncate.vue';
+import { JOB_SIDEBAR } from '../../constants';
+import { SIDEBAR_COLLAPSE_BREAKPOINTS } from './constants';
+
+export default {
+ styles: {
+ top: '75px',
+ width: '290px',
+ },
+ name: 'BridgeSidebar',
+ i18n: {
+ ...JOB_SIDEBAR,
+ retryButton: __('Retry'),
+ retryTriggerJob: __('Retry the trigger job'),
+ retryDownstreamPipeline: __('Retry the downstream pipeline'),
+ },
+ borderTopClass: ['gl-border-t-solid', 'gl-border-t-1', 'gl-border-t-gray-100'],
+ components: {
+ GlButton,
+ GlDropdown,
+ GlDropdownItem,
+ TooltipOnTruncate,
+ },
+ inject: {
+ buildName: {
+ type: String,
+ default: '',
+ },
+ },
+ data() {
+ return {
+ isSidebarExpanded: true,
+ };
+ },
+ 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>
+ <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,
+ }"
+ >
+ <div class="gl-py-5 gl-display-flex gl-align-items-center">
+ <tooltip-on-truncate :title="buildName" truncate-target="child"
+ ><h4 class="gl-mb-0 gl-mr-2 gl-text-truncate">
+ {{ buildName }}
+ </h4>
+ </tooltip-on-truncate>
+ <!-- TODO: implement retry actions -->
+ <div class="gl-flex-grow-1 gl-flex-shrink-0 gl-text-right">
+ <gl-dropdown
+ :text="$options.i18n.retryButton"
+ category="primary"
+ variant="confirm"
+ right
+ size="medium"
+ >
+ <gl-dropdown-item>{{ $options.i18n.retryTriggerJob }}</gl-dropdown-item>
+ <gl-dropdown-item>{{ $options.i18n.retryDownstreamPipeline }}</gl-dropdown-item>
+ </gl-dropdown>
+ </div>
+ <gl-button
+ :aria-label="$options.i18n.toggleSidebar"
+ data-testid="sidebar-expansion-toggle"
+ category="tertiary"
+ class="gl-md-display-none gl-ml-2"
+ icon="chevron-double-lg-right"
+ @click="toggleSidebar"
+ />
+ </div>
+ <!-- TODO: get job details and show commit block, stage dropdown, jobs list -->
+ </aside>
+</template>
diff --git a/app/assets/javascripts/jobs/components/job_log_controllers.vue b/app/assets/javascripts/jobs/components/job_log_controllers.vue
index 6105299e15c..97141a27a5e 100644
--- a/app/assets/javascripts/jobs/components/job_log_controllers.vue
+++ b/app/assets/javascripts/jobs/components/job_log_controllers.vue
@@ -5,7 +5,7 @@ import { __, s__, sprintf } from '~/locale';
export default {
i18n: {
- eraseLogButtonLabel: s__('Job|Erase job log'),
+ eraseLogButtonLabel: s__('Job|Erase job log and artifacts'),
scrollToBottomButtonLabel: s__('Job|Scroll to bottom'),
scrollToTopButtonLabel: s__('Job|Scroll to top'),
showRawButtonLabel: s__('Job|Show complete raw'),
diff --git a/app/assets/javascripts/jobs/components/sidebar.vue b/app/assets/javascripts/jobs/components/sidebar.vue
index 1b50006239c..9aa1503c7c3 100644
--- a/app/assets/javascripts/jobs/components/sidebar.vue
+++ b/app/assets/javascripts/jobs/components/sidebar.vue
@@ -2,7 +2,7 @@
import { GlButton, GlIcon } from '@gitlab/ui';
import { isEmpty } from 'lodash';
import { mapActions, mapGetters, mapState } from 'vuex';
-import TooltipOnTruncate from '~/vue_shared/components/tooltip_on_truncate.vue';
+import TooltipOnTruncate from '~/vue_shared/components/tooltip_on_truncate/tooltip_on_truncate.vue';
import { JOB_SIDEBAR } from '../constants';
import ArtifactsBlock from './artifacts_block.vue';
import CommitBlock from './commit_block.vue';
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 d90377029c5..5451cd21c14 100644
--- a/app/assets/javascripts/jobs/components/sidebar_job_details_container.vue
+++ b/app/assets/javascripts/jobs/components/sidebar_job_details_container.vue
@@ -20,6 +20,9 @@ export default {
duration() {
return timeIntervalInWords(this.job.duration);
},
+ durationTitle() {
+ return this.job.finished_at ? __('Duration') : __('Elapsed time');
+ },
erasedAt() {
return this.timeFormatted(this.job.erased_at);
},
@@ -76,7 +79,7 @@ export default {
<template>
<div v-if="shouldRenderBlock">
- <detail-row v-if="job.duration" :value="duration" title="Duration" />
+ <detail-row v-if="job.duration" :value="duration" :title="durationTitle" />
<detail-row
v-if="job.finished_at"
:value="finishedAt"
diff --git a/app/assets/javascripts/jobs/components/table/cells/actions_cell.vue b/app/assets/javascripts/jobs/components/table/cells/actions_cell.vue
index 51251c0cacc..7dfa963a857 100644
--- a/app/assets/javascripts/jobs/components/table/cells/actions_cell.vue
+++ b/app/assets/javascripts/jobs/components/table/cells/actions_cell.vue
@@ -12,6 +12,7 @@ import {
JOB_SCHEDULED,
PLAY_JOB_CONFIRMATION_MESSAGE,
RUN_JOB_NOW_HEADER_TITLE,
+ FILE_TYPE_ARCHIVE,
} from '../constants';
import eventHub from '../event_hub';
import cancelJobMutation from '../graphql/mutations/job_cancel.mutation.graphql';
@@ -58,12 +59,21 @@ export default {
},
},
computed: {
+ hasArtifacts() {
+ return this.job.artifacts.nodes.find((artifact) => artifact.fileType === FILE_TYPE_ARCHIVE);
+ },
artifactDownloadPath() {
- return this.job.artifacts?.nodes[0]?.downloadPath;
+ return this.hasArtifacts.downloadPath;
},
canReadJob() {
return this.job.userPermissions?.readBuild;
},
+ canUpdateJob() {
+ return this.job.userPermissions?.updateBuild;
+ },
+ canReadArtifacts() {
+ return this.job.userPermissions?.readJobArtifacts;
+ },
isActive() {
return this.job.active;
},
@@ -86,7 +96,7 @@ export default {
return this.job.detailedStatus?.action?.method;
},
shouldDisplayArtifacts() {
- return this.job.userPermissions?.readJobArtifacts && this.job.artifacts?.nodes.length > 0;
+ return this.canReadArtifacts && this.hasArtifacts;
},
},
methods: {
@@ -139,7 +149,7 @@ export default {
<template>
<gl-button-group>
- <template v-if="canReadJob">
+ <template v-if="canReadJob && canUpdateJob">
<gl-button
v-if="isActive"
data-testid="cancel-button"
diff --git a/app/assets/javascripts/jobs/components/table/cells/pipeline_cell.vue b/app/assets/javascripts/jobs/components/table/cells/pipeline_cell.vue
index 71f9397f5f5..1a6d1a341b0 100644
--- a/app/assets/javascripts/jobs/components/table/cells/pipeline_cell.vue
+++ b/app/assets/javascripts/jobs/components/table/cells/pipeline_cell.vue
@@ -35,10 +35,12 @@ export default {
</script>
<template>
- <div class="gl-text-truncate">
- <gl-link class="gl-text-gray-500!" :href="pipelinePath" data-testid="pipeline-id">
- {{ pipelineId }}
- </gl-link>
+ <div>
+ <div class="gl-text-truncate">
+ <gl-link class="gl-text-gray-500!" :href="pipelinePath" data-testid="pipeline-id">
+ {{ pipelineId }}
+ </gl-link>
+ </div>
<div>
<span>{{ __('created by') }}</span>
<gl-link v-if="showAvatar" :href="userPath" data-testid="pipeline-user-link">
diff --git a/app/assets/javascripts/jobs/components/table/constants.js b/app/assets/javascripts/jobs/components/table/constants.js
index e5d1bc01cbf..962979ba573 100644
--- a/app/assets/javascripts/jobs/components/table/constants.js
+++ b/app/assets/javascripts/jobs/components/table/constants.js
@@ -1,4 +1,5 @@
import { s__, __ } from '~/locale';
+import { DEFAULT_TH_CLASSES } from '~/lib/utils/constants';
export const GRAPHQL_PAGE_SIZE = 30;
@@ -17,6 +18,9 @@ export const DEFAULT = 'default';
/* Job Status Constants */
export const JOB_SCHEDULED = 'SCHEDULED';
+/* Artifact file types */
+export const FILE_TYPE_ARCHIVE = 'ARCHIVE';
+
/* i18n */
export const ACTIONS_DOWNLOAD_ARTIFACTS = __('Download artifacts');
export const ACTIONS_START_NOW = s__('DelayedJobs|Start now');
@@ -30,3 +34,66 @@ export const PLAY_JOB_CONFIRMATION_MESSAGE = s__(
`DelayedJobs|Are you sure you want to run %{job_name} immediately? This job will run automatically after its timer finishes.`,
);
export const RUN_JOB_NOW_HEADER_TITLE = s__('DelayedJobs|Run the delayed job now?');
+
+/* Table constants */
+
+const defaultTableClasses = {
+ tdClass: 'gl-p-5!',
+ thClass: DEFAULT_TH_CLASSES,
+};
+// eslint-disable-next-line @gitlab/require-i18n-strings
+const coverageTdClasses = `${defaultTableClasses.tdClass} gl-display-none! gl-lg-display-table-cell!`;
+
+export const DEFAULT_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'),
+ tdClass: coverageTdClasses,
+ thClass: defaultTableClasses.thClass,
+ columnClass: 'gl-w-10p',
+ },
+ {
+ key: 'actions',
+ label: '',
+ ...defaultTableClasses,
+ columnClass: 'gl-w-10p',
+ },
+];
+
+export const JOBS_TAB_FIELDS = DEFAULT_FIELDS.filter((field) => field.key !== 'pipeline');
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 c8763d4767e..88937185a8c 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
@@ -7,6 +7,7 @@ query getJobs(
$statuses: [CiJobStatus!]
) {
project(fullPath: $fullPath) {
+ id
jobs(after: $after, before: $before, first: $first, last: $last, statuses: $statuses) {
pageInfo {
endCursor
@@ -18,6 +19,7 @@ query getJobs(
artifacts {
nodes {
downloadPath
+ fileType
}
}
allowFailure
@@ -27,6 +29,7 @@ query getJobs(
triggered
createdByTag
detailedStatus {
+ id
detailsPath
group
icon
@@ -34,6 +37,7 @@ query getJobs(
text
tooltip
action {
+ id
buttonTitle
icon
method
@@ -51,11 +55,13 @@ query getJobs(
id
path
user {
+ id
webPath
avatarUrl
}
}
stage {
+ id
name
}
name
@@ -70,6 +76,7 @@ query getJobs(
userPermissions {
readBuild
readJobArtifacts
+ updateBuild
}
}
}
diff --git a/app/assets/javascripts/jobs/components/table/jobs_table.vue b/app/assets/javascripts/jobs/components/table/jobs_table.vue
index 298c99c4162..f513d2090fa 100644
--- a/app/assets/javascripts/jobs/components/table/jobs_table.vue
+++ b/app/assets/javascripts/jobs/components/table/jobs_table.vue
@@ -1,75 +1,17 @@
<script>
import { GlTable } from '@gitlab/ui';
-import { DEFAULT_TH_CLASSES } from '~/lib/utils/constants';
-import { s__, __ } 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: DEFAULT_TH_CLASSES,
-};
-// eslint-disable-next-line @gitlab/require-i18n-strings
-const coverageTdClasses = `${defaultTableClasses.tdClass} gl-display-none! gl-lg-display-table-cell!`;
+import { DEFAULT_FIELDS } from './constants';
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'),
- tdClass: coverageTdClasses,
- thClass: defaultTableClasses.thClass,
- columnClass: 'gl-w-10p',
- },
- {
- key: 'actions',
- label: '',
- ...defaultTableClasses,
- columnClass: 'gl-w-10p',
- },
- ],
components: {
ActionsCell,
CiBadge,
@@ -83,6 +25,11 @@ export default {
type: Array,
required: true,
},
+ tableFields: {
+ type: Array,
+ required: false,
+ default: () => DEFAULT_FIELDS,
+ },
},
methods: {
formatCoverage(coverage) {
@@ -95,7 +42,7 @@ export default {
<template>
<gl-table
:items="jobs"
- :fields="$options.fields"
+ :fields="tableFields"
:tbody-tr-attr="{ 'data-testid': 'jobs-table-row' }"
:empty-text="$options.i18n.emptyText"
show-empty
diff --git a/app/assets/javascripts/jobs/index.js b/app/assets/javascripts/jobs/index.js
index 1fb6a6f9850..e078a6c2319 100644
--- a/app/assets/javascripts/jobs/index.js
+++ b/app/assets/javascripts/jobs/index.js
@@ -1,10 +1,11 @@
import Vue from 'vue';
+import VueApollo from 'vue-apollo';
+import createDefaultClient from '~/lib/graphql';
+import BridgeApp from './bridge/app.vue';
import JobApp from './components/job_app.vue';
import createStore from './store';
-export default () => {
- const element = document.getElementById('js-job-vue-app');
-
+const initializeJobPage = (element) => {
const store = createStore();
// Let's start initializing the store (i.e. fetching data) right away
@@ -51,3 +52,35 @@ export default () => {
},
});
};
+
+const initializeBridgePage = (el) => {
+ const { buildName, emptyStateIllustrationPath } = el.dataset;
+
+ Vue.use(VueApollo);
+ const apolloProvider = new VueApollo({
+ defaultClient: createDefaultClient(),
+ });
+
+ return new Vue({
+ el,
+ apolloProvider,
+ provide: {
+ buildName,
+ emptyStateIllustrationPath,
+ },
+ render(h) {
+ return h(BridgeApp);
+ },
+ });
+};
+
+export default () => {
+ const jobElement = document.getElementById('js-job-page');
+ const bridgeElement = document.getElementById('js-bridge-page');
+
+ if (jobElement) {
+ initializeJobPage(jobElement);
+ } else {
+ initializeBridgePage(bridgeElement);
+ }
+};