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>2022-02-18 12:45:46 +0300
committerGitLab Bot <gitlab-bot@gitlab.com>2022-02-18 12:45:46 +0300
commita7b3560714b4d9cc4ab32dffcd1f74a284b93580 (patch)
tree7452bd5c3545c2fa67a28aa013835fb4fa071baf /app/assets/javascripts/pipelines
parentee9173579ae56a3dbfe5afe9f9410c65bb327ca7 (diff)
Add latest changes from gitlab-org/gitlab@14-8-stable-eev14.8.0-rc42
Diffstat (limited to 'app/assets/javascripts/pipelines')
-rw-r--r--app/assets/javascripts/pipelines/components/graph/job_group_dropdown.vue11
-rw-r--r--app/assets/javascripts/pipelines/components/graph/job_item.vue30
-rw-r--r--app/assets/javascripts/pipelines/components/graph/linked_pipeline.vue98
-rw-r--r--app/assets/javascripts/pipelines/components/header_component.vue22
-rw-r--r--app/assets/javascripts/pipelines/components/jobs/jobs_app.vue5
-rw-r--r--app/assets/javascripts/pipelines/components/jobs_shared/action_component.vue14
-rw-r--r--app/assets/javascripts/pipelines/components/notification/deprecated_type_keyword_notification.vue102
-rw-r--r--app/assets/javascripts/pipelines/components/pipelines_list/pipeline_operations.vue18
-rw-r--r--app/assets/javascripts/pipelines/components/pipelines_list/pipeline_triggerer.vue2
-rw-r--r--app/assets/javascripts/pipelines/components/pipelines_list/pipeline_url.vue167
-rw-r--r--app/assets/javascripts/pipelines/components/pipelines_list/pipelines_manual_actions.vue10
-rw-r--r--app/assets/javascripts/pipelines/components/pipelines_list/pipelines_status_badge.vue9
-rw-r--r--app/assets/javascripts/pipelines/components/pipelines_list/pipelines_table.vue25
-rw-r--r--app/assets/javascripts/pipelines/components/pipelines_list/time_ago.vue6
-rw-r--r--app/assets/javascripts/pipelines/constants.js9
-rw-r--r--app/assets/javascripts/pipelines/graphql/fragmentTypes.json1
-rw-r--r--app/assets/javascripts/pipelines/graphql/queries/get_pipeline_warnings.query.graphql12
-rw-r--r--app/assets/javascripts/pipelines/mixins/pipelines_mixin.js18
-rw-r--r--app/assets/javascripts/pipelines/pipeline_details_bundle.js10
-rw-r--r--app/assets/javascripts/pipelines/pipeline_details_notification.js31
-rw-r--r--app/assets/javascripts/pipelines/pipeline_shared_client.js9
21 files changed, 508 insertions, 101 deletions
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 12c3f9a7f40..795ba91a164 100644
--- a/app/assets/javascripts/pipelines/components/graph/job_group_dropdown.vue
+++ b/app/assets/javascripts/pipelines/components/graph/job_group_dropdown.vue
@@ -59,7 +59,11 @@ export default {
</script>
<template>
<!-- eslint-disable @gitlab/vue-no-data-toggle -->
- <div :id="computedJobId" class="ci-job-dropdown-container dropdown dropright">
+ <div
+ :id="computedJobId"
+ class="ci-job-dropdown-container dropdown dropright"
+ data-qa-selector="job_dropdown_container"
+ >
<button
type="button"
data-toggle="dropdown"
@@ -79,7 +83,10 @@ export default {
</div>
</button>
- <ul class="dropdown-menu big-pipeline-graph-dropdown-menu js-grouped-pipeline-dropdown">
+ <ul
+ class="dropdown-menu big-pipeline-graph-dropdown-menu js-grouped-pipeline-dropdown"
+ data-qa-selector="jobs_dropdown_menu"
+ >
<li class="scrollable-menu">
<ul>
<li v-for="job in group.jobs" :key="job.id">
diff --git a/app/assets/javascripts/pipelines/components/graph/job_item.vue b/app/assets/javascripts/pipelines/components/graph/job_item.vue
index ee58dcc4882..795b95421c7 100644
--- a/app/assets/javascripts/pipelines/components/graph/job_item.vue
+++ b/app/assets/javascripts/pipelines/components/graph/job_item.vue
@@ -2,7 +2,7 @@
import { GlTooltipDirective, GlLink } from '@gitlab/ui';
import delayedJobMixin from '~/jobs/mixins/delayed_job_mixin';
import { BV_HIDE_TOOLTIP } from '~/lib/utils/constants';
-import { sprintf } from '~/locale';
+import { sprintf, __ } from '~/locale';
import CiIcon from '~/vue_shared/components/ci_icon.vue';
import { reportToSentry } from '../../utils';
import ActionComponent from '../jobs_shared/action_component.vue';
@@ -160,6 +160,21 @@ export default {
hasAction() {
return this.job.status && this.job.status.action && this.job.status.action.path;
},
+ hasUnauthorizedManualAction() {
+ return (
+ !this.hasAction &&
+ this.job.status?.group === 'manual' &&
+ this.job.status?.label?.includes('(not allowed)')
+ );
+ },
+ unauthorizedManualActionIcon() {
+ /*
+ The action object is not available when the user cannot run the action.
+ So we can show the correct icon, extract the action name from the label instead:
+ "manual play action (not allowed)" or "manual stop action (not allowed)"
+ */
+ return this.job.status?.label?.split(' ')[1];
+ },
relatedDownstreamHovered() {
return this.job.name === this.sourceJobHovered;
},
@@ -198,6 +213,9 @@ export default {
this.$emit('pipelineActionRequestComplete');
},
},
+ i18n: {
+ unauthorizedTooltip: __('You are not authorized to run this manual job'),
+ },
};
</script>
<template>
@@ -242,8 +260,16 @@ export default {
:link="status.action.path"
:action-icon="status.action.icon"
class="gl-mr-1"
- data-qa-selector="action_button"
+ data-qa-selector="job_action_button"
@pipelineActionRequestComplete="pipelineActionRequestComplete"
/>
+ <action-component
+ v-if="hasUnauthorizedManualAction"
+ disabled
+ :tooltip-text="$options.i18n.unauthorizedTooltip"
+ :action-icon="unauthorizedManualActionIcon"
+ :link="`unauthorized-${computedJobId}`"
+ class="gl-mr-1"
+ />
</div>
</template>
diff --git a/app/assets/javascripts/pipelines/components/graph/linked_pipeline.vue b/app/assets/javascripts/pipelines/components/graph/linked_pipeline.vue
index e0c1dcc5be5..c59f56fc68f 100644
--- a/app/assets/javascripts/pipelines/components/graph/linked_pipeline.vue
+++ b/app/assets/javascripts/pipelines/components/graph/linked_pipeline.vue
@@ -1,5 +1,5 @@
<script>
-import { GlTooltipDirective, GlButton, GlLink, GlLoadingIcon, GlBadge } from '@gitlab/ui';
+import { GlBadge, GlButton, GlLink, GlLoadingIcon, GlTooltipDirective } from '@gitlab/ui';
import { BV_HIDE_TOOLTIP } from '~/lib/utils/constants';
import { __, sprintf } from '~/locale';
import CiStatus from '~/vue_shared/components/ci_icon.vue';
@@ -12,10 +12,10 @@ export default {
},
components: {
CiStatus,
+ GlBadge,
GlButton,
GlLink,
GlLoadingIcon,
- GlBadge,
},
props: {
columnTitle: {
@@ -26,6 +26,10 @@ export default {
type: Boolean,
required: true,
},
+ isLoading: {
+ type: Boolean,
+ required: true,
+ },
pipeline: {
type: Object,
required: true,
@@ -34,33 +38,40 @@ export default {
type: String,
required: true,
},
- isLoading: {
- type: Boolean,
- required: true,
- },
},
computed: {
- tooltipText() {
- return `${this.downstreamTitle} #${this.pipeline.id} - ${this.pipelineStatus.label} -
- ${this.sourceJobInfo}`;
+ buttonBorderClass() {
+ return this.isUpstream ? 'gl-border-r-1!' : 'gl-border-l-1!';
},
buttonId() {
return `js-linked-pipeline-${this.pipeline.id}`;
},
- pipelineStatus() {
- return this.pipeline.status;
+ cardSpacingClass() {
+ return this.isDownstream ? 'gl-pr-0' : '';
},
- projectName() {
- return this.pipeline.project.name;
+ expandedIcon() {
+ if (this.isUpstream) {
+ return this.expanded ? 'angle-right' : 'angle-left';
+ }
+ return this.expanded ? 'angle-left' : 'angle-right';
+ },
+ childPipeline() {
+ return this.isDownstream && this.isSameProject;
},
downstreamTitle() {
return this.childPipeline ? this.sourceJobName : this.pipeline.project.name;
},
- parentPipeline() {
- return this.isUpstream && this.isSameProject;
+ flexDirection() {
+ return this.isUpstream ? 'gl-flex-direction-row-reverse' : 'gl-flex-direction-row';
},
- childPipeline() {
- return this.isDownstream && this.isSameProject;
+ isDownstream() {
+ return this.type === DOWNSTREAM;
+ },
+ isSameProject() {
+ return !this.pipeline.multiproject;
+ },
+ isUpstream() {
+ return this.type === UPSTREAM;
},
label() {
if (this.parentPipeline) {
@@ -70,17 +81,17 @@ export default {
}
return __('Multi-project');
},
+ parentPipeline() {
+ return this.isUpstream && this.isSameProject;
+ },
pipelineIsLoading() {
return Boolean(this.isLoading || this.pipeline.isLoading);
},
- isDownstream() {
- return this.type === DOWNSTREAM;
- },
- isUpstream() {
- return this.type === UPSTREAM;
+ pipelineStatus() {
+ return this.pipeline.status;
},
- isSameProject() {
- return !this.pipeline.multiproject;
+ projectName() {
+ return this.pipeline.project.name;
},
sourceJobName() {
return this.pipeline.sourceJob?.name ?? '';
@@ -88,28 +99,23 @@ export default {
sourceJobInfo() {
return this.isDownstream ? sprintf(__('Created by %{job}'), { job: this.sourceJobName }) : '';
},
- expandedIcon() {
- if (this.isUpstream) {
- return this.expanded ? 'angle-right' : 'angle-left';
- }
- return this.expanded ? 'angle-left' : 'angle-right';
- },
- expandButtonPosition() {
- return this.isUpstream ? 'gl-left-0 gl-border-r-1!' : 'gl-right-0 gl-border-l-1!';
+ tooltipText() {
+ return `${this.downstreamTitle} #${this.pipeline.id} - ${this.pipelineStatus.label} -
+ ${this.sourceJobInfo}`;
},
},
errorCaptured(err, _vm, info) {
reportToSentry('linked_pipeline', `error: ${err}, info: ${info}`);
},
methods: {
+ hideTooltips() {
+ this.$root.$emit(BV_HIDE_TOOLTIP);
+ },
onClickLinkedPipeline() {
this.hideTooltips();
this.$emit('pipelineClicked', this.$refs.linkedPipeline);
this.$emit('pipelineExpandToggle', this.sourceJobName, !this.expanded);
},
- hideTooltips() {
- this.$root.$emit(BV_HIDE_TOOLTIP);
- },
onDownstreamHovered() {
this.$emit('downstreamHovered', this.sourceJobName);
},
@@ -124,27 +130,23 @@ export default {
<div
ref="linkedPipeline"
v-gl-tooltip
- class="gl-downstream-pipeline-job-width"
+ class="gl-h-full gl-display-flex! gl-border-solid gl-border-gray-100 gl-border-1"
+ :class="flexDirection"
:title="tooltipText"
data-qa-selector="child_pipeline"
@mouseover="onDownstreamHovered"
@mouseleave="onDownstreamHoverLeave"
>
- <div
- 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 gl-pr-7 gl-pipeline-job-width">
+ <div class="gl-w-full gl-bg-white gl-p-3" :class="cardSpacingClass">
+ <div class="gl-display-flex gl-pr-3">
<ci-status
v-if="!pipelineIsLoading"
:status="pipelineStatus"
:size="24"
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-pipeline-job-width gl-text-truncate"
- >
+ <div v-else class="gl-pr-3"><gl-loading-icon size="sm" inline /></div>
+ <div class="gl-display-flex gl-flex-direction-column gl-downstream-pipeline-job-width">
<span class="gl-text-truncate" data-testid="downstream-title">
{{ downstreamTitle }}
</span>
@@ -160,10 +162,12 @@ export default {
{{ label }}
</gl-badge>
</div>
+ </div>
+ <div class="gl-display-flex">
<gl-button
:id="buttonId"
- class="gl-absolute gl-top-0 gl-bottom-0 gl-shadow-none! gl-rounded-0!"
- :class="`js-pipeline-expand-${pipeline.id} ${expandButtonPosition}`"
+ class="gl-shadow-none! gl-rounded-0!"
+ :class="`js-pipeline-expand-${pipeline.id} ${buttonBorderClass}`"
:icon="expandedIcon"
:aria-label="__('Expand pipeline')"
data-testid="expand-pipeline-button"
diff --git a/app/assets/javascripts/pipelines/components/header_component.vue b/app/assets/javascripts/pipelines/components/header_component.vue
index 8088858f381..6a4d1bb44f2 100644
--- a/app/assets/javascripts/pipelines/components/header_component.vue
+++ b/app/assets/javascripts/pipelines/components/header_component.vue
@@ -1,9 +1,22 @@
<script>
-import { GlAlert, GlButton, GlLoadingIcon, GlModal, GlModalDirective } from '@gitlab/ui';
+import {
+ GlAlert,
+ GlButton,
+ GlLoadingIcon,
+ GlModal,
+ GlModalDirective,
+ GlTooltipDirective,
+} from '@gitlab/ui';
import { setUrlFragment, redirectTo } from '~/lib/utils/url_utility';
import { __ } from '~/locale';
import ciHeader from '~/vue_shared/components/header_ci_component.vue';
-import { LOAD_FAILURE, POST_FAILURE, DELETE_FAILURE, DEFAULT } from '../constants';
+import {
+ LOAD_FAILURE,
+ POST_FAILURE,
+ DELETE_FAILURE,
+ DEFAULT,
+ BUTTON_TOOLTIP_RETRY,
+} from '../constants';
import cancelPipelineMutation from '../graphql/mutations/cancel_pipeline.mutation.graphql';
import deletePipelineMutation from '../graphql/mutations/delete_pipeline.mutation.graphql';
import retryPipelineMutation from '../graphql/mutations/retry_pipeline.mutation.graphql';
@@ -15,6 +28,7 @@ const POLL_INTERVAL = 10000;
export default {
name: 'PipelineHeaderSection',
+ BUTTON_TOOLTIP_RETRY,
pipelineCancel: 'pipelineCancel',
pipelineRetry: 'pipelineRetry',
finishedStatuses: ['FAILED', 'SUCCESS', 'CANCELED'],
@@ -27,6 +41,7 @@ export default {
},
directives: {
GlModal: GlModalDirective,
+ GlTooltip: GlTooltipDirective,
},
errorTexts: {
[LOAD_FAILURE]: __('We are currently unable to fetch data for the pipeline header.'),
@@ -225,6 +240,9 @@ export default {
>
<gl-button
v-if="canRetryPipeline"
+ v-gl-tooltip
+ :aria-label="$options.BUTTON_TOOLTIP_RETRY"
+ :title="$options.BUTTON_TOOLTIP_RETRY"
:loading="isRetrying"
:disabled="isRetrying"
category="secondary"
diff --git a/app/assets/javascripts/pipelines/components/jobs/jobs_app.vue b/app/assets/javascripts/pipelines/components/jobs/jobs_app.vue
index e11073aee33..99fb5c146ba 100644
--- a/app/assets/javascripts/pipelines/components/jobs/jobs_app.vue
+++ b/app/assets/javascripts/pipelines/components/jobs/jobs_app.vue
@@ -36,10 +36,13 @@ export default {
return data.project?.pipeline?.jobs?.nodes || [];
},
result({ data }) {
+ if (!data) {
+ return;
+ }
this.jobsPageInfo = data.project?.pipeline?.jobs?.pageInfo || {};
},
error() {
- createFlash({ message: __('An error occured while fetching the pipelines jobs.') });
+ createFlash({ message: __('An error occurred while fetching the pipelines jobs.') });
},
},
},
diff --git a/app/assets/javascripts/pipelines/components/jobs_shared/action_component.vue b/app/assets/javascripts/pipelines/components/jobs_shared/action_component.vue
index efad43ddd4f..ca2537ca4f4 100644
--- a/app/assets/javascripts/pipelines/components/jobs_shared/action_component.vue
+++ b/app/assets/javascripts/pipelines/components/jobs_shared/action_component.vue
@@ -92,14 +92,20 @@ export default {
<template>
<gl-button
:id="`js-ci-action-${link}`"
- v-gl-tooltip="{ boundary: 'viewport' }"
- :title="tooltipText"
:class="cssClass"
:disabled="isDisabled"
class="js-ci-action gl-ci-action-icon-container ci-action-icon-container ci-action-icon-wrapper gl-display-flex gl-align-items-center gl-justify-content-center"
+ data-testid="ci-action-component"
@click.stop="onClickAction"
>
- <gl-loading-icon v-if="isLoading" size="sm" class="js-action-icon-loading" />
- <gl-icon v-else :name="actionIcon" class="gl-mr-0!" :aria-label="actionIcon" />
+ <div
+ v-gl-tooltip.viewport
+ :title="tooltipText"
+ class="gl-display-flex gl-align-items-center gl-justify-content-center gl-h-full"
+ data-testid="ci-action-icon-tooltip-wrapper"
+ >
+ <gl-loading-icon v-if="isLoading" size="sm" class="js-action-icon-loading" />
+ <gl-icon v-else :name="actionIcon" class="gl-mr-0!" :aria-label="actionIcon" />
+ </div>
</gl-button>
</template>
diff --git a/app/assets/javascripts/pipelines/components/notification/deprecated_type_keyword_notification.vue b/app/assets/javascripts/pipelines/components/notification/deprecated_type_keyword_notification.vue
new file mode 100644
index 00000000000..b8f9f84c217
--- /dev/null
+++ b/app/assets/javascripts/pipelines/components/notification/deprecated_type_keyword_notification.vue
@@ -0,0 +1,102 @@
+<script>
+import { GlAlert, GlLink, GlSprintf } from '@gitlab/ui';
+import { __ } from '~/locale';
+import getPipelineWarnings from '../../graphql/queries/get_pipeline_warnings.query.graphql';
+
+export default {
+ // eslint-disable-next-line @gitlab/require-i18n-strings
+ expectedMessage: 'will be removed in',
+ i18n: {
+ title: __('Found warning in your .gitlab-ci.yml'),
+ rootTypesWarning: __(
+ '%{codeStart}types%{codeEnd} is deprecated and will be removed in 15.0. Use %{codeStart}stages%{codeEnd} instead. %{linkStart}Learn More %{linkEnd}',
+ ),
+ typeWarning: __(
+ '%{codeStart}type%{codeEnd} is deprecated and will be removed in 15.0. Use %{codeStart}stage%{codeEnd} instead. %{linkStart}Learn More %{linkEnd}',
+ ),
+ },
+ components: {
+ GlAlert,
+ GlLink,
+ GlSprintf,
+ },
+ inject: ['deprecatedKeywordsDocPath', 'fullPath', 'pipelineIid'],
+ apollo: {
+ warnings: {
+ query: getPipelineWarnings,
+ variables() {
+ return {
+ fullPath: this.fullPath,
+ iid: this.pipelineIid,
+ };
+ },
+ update(data) {
+ return data?.project?.pipeline?.warningMessages || [];
+ },
+ error() {
+ this.hasError = true;
+ },
+ },
+ },
+ data() {
+ return {
+ warnings: [],
+ hasError: false,
+ };
+ },
+ computed: {
+ deprecationWarnings() {
+ return this.warnings.filter(({ content }) => {
+ return content.includes(this.$options.expectedMessage);
+ });
+ },
+ formattedWarnings() {
+ // The API doesn't have a mechanism currently to return a
+ // type instead of just the error message. To work around this,
+ // we check if the deprecation message is found within the warnings
+ // and show a FE version of that message with the link to the documentation
+ // and translated. We can have only 2 types of warnings: root types and individual
+ // type. If the word `root` is present, then we know it's the root type deprecation
+ // and if not, it's the normal type. This has to be deleted in 15.0.
+ // Follow-up issue: https://gitlab.com/gitlab-org/gitlab/-/issues/350810
+ return this.deprecationWarnings.map(({ content }) => {
+ if (content.includes('root')) {
+ return this.$options.i18n.rootTypesWarning;
+ }
+ return this.$options.i18n.typeWarning;
+ });
+ },
+ hasDeprecationWarning() {
+ return this.formattedWarnings.length > 0;
+ },
+ showWarning() {
+ return (
+ !this.$apollo.queries.warnings?.loading && !this.hasError && this.hasDeprecationWarning
+ );
+ },
+ },
+};
+</script>
+<template>
+ <div>
+ <gl-alert
+ v-if="showWarning"
+ :title="$options.i18n.title"
+ variant="warning"
+ :dismissible="false"
+ >
+ <ul class="gl-mb-0">
+ <li v-for="warning in formattedWarnings" :key="warning">
+ <gl-sprintf :message="warning">
+ <template #code="{ content }">
+ <code> {{ content }}</code>
+ </template>
+ <template #link="{ content }">
+ <gl-link :href="deprecatedKeywordsDocPath" target="_blank"> {{ content }}</gl-link>
+ </template>
+ </gl-sprintf>
+ </li>
+ </ul>
+ </gl-alert>
+ </div>
+</template>
diff --git a/app/assets/javascripts/pipelines/components/pipelines_list/pipeline_operations.vue b/app/assets/javascripts/pipelines/components/pipelines_list/pipeline_operations.vue
index b6c178d20b0..fa0e153b2af 100644
--- a/app/assets/javascripts/pipelines/components/pipelines_list/pipeline_operations.vue
+++ b/app/assets/javascripts/pipelines/components/pipelines_list/pipeline_operations.vue
@@ -1,15 +1,13 @@
<script>
import { GlButton, GlTooltipDirective, GlModalDirective } from '@gitlab/ui';
-import { __ } from '~/locale';
import eventHub from '../../event_hub';
+import { BUTTON_TOOLTIP_RETRY, BUTTON_TOOLTIP_CANCEL } from '../../constants';
import PipelineMultiActions from './pipeline_multi_actions.vue';
import PipelinesManualActions from './pipelines_manual_actions.vue';
export default {
- i18n: {
- cancelTitle: __('Cancel'),
- redeployTitle: __('Retry'),
- },
+ BUTTON_TOOLTIP_RETRY,
+ BUTTON_TOOLTIP_CANCEL,
directives: {
GlTooltip: GlTooltipDirective,
GlModalDirective,
@@ -75,12 +73,13 @@ export default {
<gl-button
v-if="pipeline.flags.retryable"
v-gl-tooltip.hover
- :aria-label="$options.i18n.redeployTitle"
- :title="$options.i18n.redeployTitle"
+ :aria-label="$options.BUTTON_TOOLTIP_RETRY"
+ :title="$options.BUTTON_TOOLTIP_RETRY"
:disabled="isRetrying"
:loading="isRetrying"
class="js-pipelines-retry-button"
data-qa-selector="pipeline_retry_button"
+ data-testid="pipelines-retry-button"
icon="repeat"
variant="default"
category="secondary"
@@ -91,14 +90,15 @@ export default {
v-if="pipeline.flags.cancelable"
v-gl-tooltip.hover
v-gl-modal-directive="'confirmation-modal'"
- :aria-label="$options.i18n.cancelTitle"
- :title="$options.i18n.cancelTitle"
+ :aria-label="$options.BUTTON_TOOLTIP_CANCEL"
+ :title="$options.BUTTON_TOOLTIP_CANCEL"
:loading="isCancelling"
:disabled="isCancelling"
icon="cancel"
variant="danger"
category="primary"
class="js-pipelines-cancel-button gl-ml-1"
+ data-testid="pipelines-cancel-button"
@click="handleCancelClick"
/>
diff --git a/app/assets/javascripts/pipelines/components/pipelines_list/pipeline_triggerer.vue b/app/assets/javascripts/pipelines/components/pipelines_list/pipeline_triggerer.vue
index 0528e4c147c..b29c8426301 100644
--- a/app/assets/javascripts/pipelines/components/pipelines_list/pipeline_triggerer.vue
+++ b/app/assets/javascripts/pipelines/components/pipelines_list/pipeline_triggerer.vue
@@ -26,7 +26,7 @@ export default {
v-if="user"
:link-href="user.path"
:img-src="user.avatar_url"
- :img-size="26"
+ :img-size="32"
:tooltip-text="user.name"
class="gl-ml-3 js-pipeline-url-user"
/>
diff --git a/app/assets/javascripts/pipelines/components/pipelines_list/pipeline_url.vue b/app/assets/javascripts/pipelines/components/pipelines_list/pipeline_url.vue
index e2f30d5a8e6..52da4d01468 100644
--- a/app/assets/javascripts/pipelines/components/pipelines_list/pipeline_url.vue
+++ b/app/assets/javascripts/pipelines/components/pipelines_list/pipeline_url.vue
@@ -1,15 +1,19 @@
<script>
-import { GlLink, GlPopover, GlSprintf, GlTooltipDirective, GlBadge } from '@gitlab/ui';
+import { GlIcon, GlLink, GlPopover, GlSprintf, GlTooltipDirective, GlBadge } from '@gitlab/ui';
+import { __, sprintf } from '~/locale';
import { helpPagePath } from '~/helpers/help_page_helper';
import glFeatureFlagMixin from '~/vue_shared/mixins/gl_feature_flags_mixin';
-import { SCHEDULE_ORIGIN } from '../../constants';
+import TooltipOnTruncate from '~/vue_shared/components/tooltip_on_truncate/tooltip_on_truncate.vue';
+import { SCHEDULE_ORIGIN, ICONS } from '../../constants';
export default {
components: {
+ GlIcon,
GlLink,
GlPopover,
GlSprintf,
GlBadge,
+ TooltipOnTruncate,
},
directives: {
GlTooltip: GlTooltipDirective,
@@ -33,11 +37,12 @@ export default {
type: String,
required: true,
},
+ viewType: {
+ type: String,
+ required: true,
+ },
},
computed: {
- user() {
- return this.pipeline.user;
- },
isScheduled() {
return this.pipeline.source === SCHEDULE_ORIGIN;
},
@@ -53,12 +58,160 @@ export default {
autoDevopsHelpPath() {
return helpPagePath('topics/autodevops/index.md');
},
+ mergeRequestRef() {
+ return this.pipeline?.merge_request;
+ },
+ commitRef() {
+ return this.pipeline?.ref;
+ },
+ commitTag() {
+ return this.commitRef?.tag;
+ },
+ commitUrl() {
+ return this.pipeline?.commit?.commit_path;
+ },
+ commitShortSha() {
+ return this.pipeline?.commit?.short_id;
+ },
+ refUrl() {
+ return this.commitRef?.ref_url || this.commitRef?.path;
+ },
+ tooltipTitle() {
+ return this.mergeRequestRef?.title || this.commitRef?.name;
+ },
+ commitAuthor() {
+ let commitAuthorInformation;
+ const pipelineCommit = this.pipeline?.commit;
+ const pipelineCommitAuthor = pipelineCommit?.author;
+
+ if (!pipelineCommit) {
+ return null;
+ }
+
+ // 1. person who is an author of a commit might be a GitLab user
+ if (pipelineCommitAuthor) {
+ // 2. if person who is an author of a commit is a GitLab user
+ // they can have a GitLab avatar
+ if (pipelineCommitAuthor?.avatar_url) {
+ commitAuthorInformation = pipelineCommitAuthor;
+
+ // 3. If GitLab user does not have avatar, they might have a Gravatar
+ } else if (pipelineCommit.author_gravatar_url) {
+ commitAuthorInformation = {
+ ...pipelineCommitAuthor,
+ avatar_url: pipelineCommit.author_gravatar_url,
+ };
+ }
+ // 4. If committer is not a GitLab User, they can have a Gravatar
+ } else {
+ commitAuthorInformation = {
+ avatar_url: pipelineCommit.author_gravatar_url,
+ path: `mailto:${pipelineCommit.author_email}`,
+ username: pipelineCommit.author_name,
+ };
+ }
+
+ return commitAuthorInformation;
+ },
+ commitIcon() {
+ let name = '';
+
+ if (this.commitTag) {
+ name = ICONS.TAG;
+ } else if (this.mergeRequestRef) {
+ name = ICONS.MR;
+ } else {
+ name = ICONS.BRANCH;
+ }
+
+ return name;
+ },
+ commitIconTooltipTitle() {
+ switch (this.commitIcon) {
+ case ICONS.TAG:
+ return __('Tag');
+ case ICONS.MR:
+ return __('Merge Request');
+ default:
+ return __('Branch');
+ }
+ },
+ commitTitleText() {
+ return this.pipeline?.commit?.title || __("Can't find HEAD commit for this branch");
+ },
+ hasAuthor() {
+ return (
+ this.commitAuthor?.avatar_url && this.commitAuthor?.path && this.commitAuthor?.username
+ );
+ },
+ userImageAltDescription() {
+ return this.commitAuthor?.username
+ ? sprintf(__("%{username}'s avatar"), { username: this.commitAuthor.username })
+ : null;
+ },
+ rearrangePipelinesTable() {
+ return this.glFeatures?.rearrangePipelinesTable;
+ },
},
};
</script>
<template>
<div class="pipeline-tags" data-testid="pipeline-url-table-cell">
+ <template v-if="rearrangePipelinesTable">
+ <div class="commit-title gl-mb-2" data-testid="commit-title-container">
+ <span class="gl-display-flex">
+ <tooltip-on-truncate :title="commitTitleText" class="flex-truncate-child gl-flex-grow-1">
+ <gl-link
+ :href="pipeline.path"
+ class="commit-row-message gl-text-blue-600!"
+ data-testid="commit-title"
+ data-qa-selector="pipeline_url_link"
+ >{{ commitTitleText }}</gl-link
+ >
+ </tooltip-on-truncate>
+ </span>
+ </div>
+ <div class="gl-mb-2">
+ <span class="gl-font-weight-bold gl-text-gray-500" data-testid="pipeline-identifier">
+ #{{ pipeline[pipelineKey] }}
+ </span>
+ <!--Commit row-->
+ <div class="icon-container gl-display-inline-block">
+ <gl-icon
+ v-gl-tooltip
+ :name="commitIcon"
+ :title="commitIconTooltipTitle"
+ data-testid="commit-icon-type"
+ />
+ </div>
+ <tooltip-on-truncate :title="tooltipTitle" truncate-target="child" placement="top">
+ <gl-link
+ v-if="mergeRequestRef"
+ :href="mergeRequestRef.path"
+ class="ref-name"
+ data-testid="merge-request-ref"
+ >{{ mergeRequestRef.iid }}</gl-link
+ >
+ <gl-link v-else :href="refUrl" class="ref-name" data-testid="commit-ref-name">{{
+ commitRef.name
+ }}</gl-link>
+ </tooltip-on-truncate>
+ <gl-icon
+ v-gl-tooltip
+ name="commit"
+ class="commit-icon"
+ :title="__('Commit')"
+ data-testid="commit-icon"
+ />
+
+ <gl-link :href="commitUrl" class="commit-sha mr-0" data-testid="commit-short-sha">{{
+ commitShortSha
+ }}</gl-link>
+ <!--End of commit row-->
+ </div>
+ </template>
<gl-link
+ v-if="!rearrangePipelinesTable"
:href="pipeline.path"
class="gl-text-decoration-underline"
data-testid="pipeline-url-link"
@@ -66,7 +219,7 @@ export default {
>
#{{ pipeline[pipelineKey] }}
</gl-link>
- <div class="label-container">
+ <div class="label-container gl-mt-1">
<gl-badge
v-if="isScheduled"
v-gl-tooltip
@@ -163,7 +316,7 @@ export default {
v-gl-tooltip
:title="
__(
- 'Pipelines for merge requests are configured. A detached pipeline runs in the context of the merge request, and not against the merged result. Learn more in the documentation for Pipelines for Merged Results.',
+ 'Merge request pipelines are configured. A detached pipeline runs in the context of the merge request, and not against the merged result. Learn more in the documentation for merge request pipelines.',
)
"
variant="info"
diff --git a/app/assets/javascripts/pipelines/components/pipelines_list/pipelines_manual_actions.vue b/app/assets/javascripts/pipelines/components/pipelines_list/pipelines_manual_actions.vue
index b94f1a42039..47fffa8a6b2 100644
--- a/app/assets/javascripts/pipelines/components/pipelines_list/pipelines_manual_actions.vue
+++ b/app/assets/javascripts/pipelines/components/pipelines_list/pipelines_manual_actions.vue
@@ -2,6 +2,7 @@
import { GlDropdown, GlDropdownItem, GlIcon, GlTooltipDirective } from '@gitlab/ui';
import createFlash from '~/flash';
import axios from '~/lib/utils/axios_utils';
+import { confirmAction } from '~/lib/utils/confirm_via_gl_modal/confirm_via_gl_modal';
import { s__, __, sprintf } from '~/locale';
import GlCountdown from '~/vue_shared/components/gl_countdown.vue';
import eventHub from '../../event_hub';
@@ -28,7 +29,7 @@ export default {
};
},
methods: {
- onClickAction(action) {
+ async onClickAction(action) {
if (action.scheduled_at) {
const confirmationMessage = sprintf(
s__(
@@ -36,9 +37,10 @@ export default {
),
{ jobName: action.name },
);
- // https://gitlab.com/gitlab-org/gitlab-foss/issues/52156
- // eslint-disable-next-line no-alert
- if (!window.confirm(confirmationMessage)) {
+
+ const confirmed = await confirmAction(confirmationMessage);
+
+ if (!confirmed) {
return;
}
}
diff --git a/app/assets/javascripts/pipelines/components/pipelines_list/pipelines_status_badge.vue b/app/assets/javascripts/pipelines/components/pipelines_list/pipelines_status_badge.vue
index f56457a4162..54901c2d13f 100644
--- a/app/assets/javascripts/pipelines/components/pipelines_list/pipelines_status_badge.vue
+++ b/app/assets/javascripts/pipelines/components/pipelines_list/pipelines_status_badge.vue
@@ -3,12 +3,16 @@ import CodeQualityWalkthrough from '~/code_quality_walkthrough/components/step.v
import { PIPELINE_STATUSES } from '~/code_quality_walkthrough/constants';
import { CHILD_VIEW } from '~/pipelines/constants';
import CiBadge from '~/vue_shared/components/ci_badge_link.vue';
+import glFeatureFlagsMixin from '~/vue_shared/mixins/gl_feature_flags_mixin';
+import PipelinesTimeago from './time_ago.vue';
export default {
components: {
CodeQualityWalkthrough,
CiBadge,
+ PipelinesTimeago,
},
+ mixins: [glFeatureFlagsMixin()],
props: {
pipeline: {
type: Object,
@@ -40,6 +44,9 @@ export default {
codeQualityBuildPath() {
return this.pipeline?.details?.code_quality_build_path;
},
+ rearrangePipelinesTable() {
+ return this.glFeatures?.rearrangePipelinesTable;
+ },
},
};
</script>
@@ -48,11 +55,13 @@ export default {
<div>
<ci-badge
id="js-code-quality-walkthrough"
+ class="gl-mb-3"
:status="pipelineStatus"
:show-text="!isChildView"
:icon-classes="'gl-vertical-align-middle!'"
data-qa-selector="pipeline_commit_status"
/>
+ <pipelines-timeago v-if="rearrangePipelinesTable" class="gl-mt-3" :pipeline="pipeline" />
<code-quality-walkthrough
v-if="shouldRenderCodeQualityWalkthrough"
:step="codeQualityStep"
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 d64decc81ec..9919a18cb99 100644
--- a/app/assets/javascripts/pipelines/components/pipelines_list/pipelines_table.vue
+++ b/app/assets/javascripts/pipelines/components/pipelines_list/pipelines_table.vue
@@ -1,6 +1,7 @@
<script>
import { GlTableLite, GlTooltipDirective } from '@gitlab/ui';
-import { s__ } from '~/locale';
+import { s__, __ } from '~/locale';
+import glFeatureFlagMixin from '~/vue_shared/mixins/gl_feature_flags_mixin';
import eventHub from '../../event_hub';
import PipelineMiniGraph from './pipeline_mini_graph.vue';
import PipelineOperations from './pipeline_operations.vue';
@@ -33,6 +34,7 @@ export default {
directives: {
GlTooltip: GlTooltipDirective,
},
+ mixins: [glFeatureFlagMixin()],
props: {
pipelines: {
type: Array,
@@ -72,16 +74,18 @@ export default {
key: 'status',
label: s__('Pipeline|Status'),
thClass: DEFAULT_TH_CLASSES,
- columnClass: 'gl-w-10p',
+ columnClass: this.rearrangePipelinesTable ? 'gl-w-15p' : 'gl-w-10p',
tdClass: DEFAULT_TD_CLASS,
thAttr: { 'data-testid': 'status-th' },
},
{
key: 'pipeline',
- label: this.pipelineKeyOption.label,
+ label: this.rearrangePipelinesTable ? __('Pipeline') : this.pipelineKeyOption.label,
thClass: DEFAULT_TH_CLASSES,
- tdClass: `${DEFAULT_TD_CLASS} ${HIDE_TD_ON_MOBILE}`,
- columnClass: 'gl-w-10p',
+ tdClass: this.rearrangePipelinesTable
+ ? `${DEFAULT_TD_CLASS}`
+ : `${DEFAULT_TD_CLASS} ${HIDE_TD_ON_MOBILE}`,
+ columnClass: this.rearrangePipelinesTable ? 'gl-w-30p' : 'gl-w-10p',
thAttr: { 'data-testid': 'pipeline-th' },
},
{
@@ -113,7 +117,7 @@ export default {
label: s__('Pipeline|Duration'),
thClass: DEFAULT_TH_CLASSES,
tdClass: DEFAULT_TD_CLASS,
- columnClass: 'gl-w-15p',
+ columnClass: this.rearrangePipelinesTable ? 'gl-w-5p' : 'gl-w-15p',
thAttr: { 'data-testid': 'timeago-th' },
},
{
@@ -124,7 +128,13 @@ export default {
thAttr: { 'data-testid': 'actions-th' },
},
];
- return fields;
+
+ return !this.rearrangePipelinesTable
+ ? fields
+ : fields.filter((field) => !['commit', 'timeago'].includes(field.key));
+ },
+ rearrangePipelinesTable() {
+ return this.glFeatures?.rearrangePipelinesTable;
},
},
watch: {
@@ -182,6 +192,7 @@ export default {
:pipeline="item"
:pipeline-schedule-url="pipelineScheduleUrl"
:pipeline-key="pipelineKeyOption.key"
+ :view-type="viewType"
/>
</template>
diff --git a/app/assets/javascripts/pipelines/components/pipelines_list/time_ago.vue b/app/assets/javascripts/pipelines/components/pipelines_list/time_ago.vue
index e6b03751350..c45e3f24567 100644
--- a/app/assets/javascripts/pipelines/components/pipelines_list/time_ago.vue
+++ b/app/assets/javascripts/pipelines/components/pipelines_list/time_ago.vue
@@ -54,11 +54,14 @@ export default {
showSkipped() {
return !this.duration && !this.finishedTime && this.skipped;
},
+ shouldDisplayAsBlock() {
+ return this.glFeatures?.rearrangePipelinesTable;
+ },
},
};
</script>
<template>
- <div>
+ <div class="{ 'gl-display-block': shouldDisplayAsBlock }">
<span v-if="showInProgress" data-testid="pipeline-in-progress">
<gl-icon v-if="stuck" name="warning" class="gl-mr-2" :size="12" data-testid="warning-icon" />
<gl-icon
@@ -87,6 +90,7 @@ export default {
<time
v-gl-tooltip
:title="tooltipTitle(finishedTime)"
+ :datetime="finishedTime"
data-placement="top"
data-container="body"
>
diff --git a/app/assets/javascripts/pipelines/constants.js b/app/assets/javascripts/pipelines/constants.js
index 410fc7b82cd..36f708ff2af 100644
--- a/app/assets/javascripts/pipelines/constants.js
+++ b/app/assets/javascripts/pipelines/constants.js
@@ -10,6 +10,12 @@ export const SCHEDULE_ORIGIN = 'schedule';
export const NEEDS_PROPERTY = 'needs';
export const EXPLICIT_NEEDS_PROPERTY = 'previousStageJobsOrNeeds';
+export const ICONS = {
+ TAG: 'tag',
+ MR: 'git-merge',
+ BRANCH: 'branch',
+};
+
export const TestStatus = {
FAILED: 'failed',
SKIPPED: 'skipped',
@@ -53,3 +59,6 @@ export const PipelineKeyOptions = [
];
export const TOAST_MESSAGE = s__('Pipeline|Creating pipeline.');
+
+export const BUTTON_TOOLTIP_RETRY = __('Retry failed jobs');
+export const BUTTON_TOOLTIP_CANCEL = __('Cancel');
diff --git a/app/assets/javascripts/pipelines/graphql/fragmentTypes.json b/app/assets/javascripts/pipelines/graphql/fragmentTypes.json
deleted file mode 100644
index 4601b74b5c1..00000000000
--- a/app/assets/javascripts/pipelines/graphql/fragmentTypes.json
+++ /dev/null
@@ -1 +0,0 @@
-{"__schema":{"types":[{"kind":"UNION","name":"JobNeedUnion","possibleTypes":[{"name":"CiBuildNeed"},{"name":"CiJob"}]}]}} \ No newline at end of file
diff --git a/app/assets/javascripts/pipelines/graphql/queries/get_pipeline_warnings.query.graphql b/app/assets/javascripts/pipelines/graphql/queries/get_pipeline_warnings.query.graphql
new file mode 100644
index 00000000000..cd1d2b62a3d
--- /dev/null
+++ b/app/assets/javascripts/pipelines/graphql/queries/get_pipeline_warnings.query.graphql
@@ -0,0 +1,12 @@
+query getPipelineWarnings($fullPath: ID!, $iid: ID!) {
+ project(fullPath: $fullPath) {
+ id
+ pipeline(iid: $iid) {
+ id
+ warningMessages {
+ content
+ id
+ }
+ }
+ }
+}
diff --git a/app/assets/javascripts/pipelines/mixins/pipelines_mixin.js b/app/assets/javascripts/pipelines/mixins/pipelines_mixin.js
index 3201f88a9e3..c4f7665c91d 100644
--- a/app/assets/javascripts/pipelines/mixins/pipelines_mixin.js
+++ b/app/assets/javascripts/pipelines/mixins/pipelines_mixin.js
@@ -1,6 +1,7 @@
import Visibility from 'visibilityjs';
import createFlash from '~/flash';
import { historyPushState, buildUrlWithCurrentLocation } from '~/lib/utils/common_utils';
+import httpStatusCodes from '~/lib/utils/http_status';
import Poll from '~/lib/utils/poll';
import { __ } from '~/locale';
import { validateParams } from '~/pipelines/utils';
@@ -195,11 +196,20 @@ export default {
this.$toast.show(TOAST_MESSAGE);
this.updateTable();
})
- .catch(() => {
+ .catch((e) => {
+ const unauthorized = e.response.status === httpStatusCodes.UNAUTHORIZED;
+ const badRequest = e.response.status === httpStatusCodes.BAD_REQUEST;
+
+ let errorMessage = __(
+ 'An error occurred while trying to run a new pipeline for this merge request.',
+ );
+
+ if (unauthorized || badRequest) {
+ errorMessage = __('You do not have permission to run a pipeline on this branch.');
+ }
+
createFlash({
- message: __(
- 'An error occurred while trying to run a new pipeline for this merge request.',
- ),
+ message: errorMessage,
});
})
.finally(() => this.store.toggleIsRunningPipeline(false));
diff --git a/app/assets/javascripts/pipelines/pipeline_details_bundle.js b/app/assets/javascripts/pipelines/pipeline_details_bundle.js
index ae8b2503c79..bfb95e5ab0c 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 { createPipelineNotificationApp } from './pipeline_details_notification';
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_DETAILS: '.js-pipeline-details-vue',
PIPELINE_GRAPH: '#js-pipeline-graph-vue',
PIPELINE_HEADER: '#js-pipeline-header-vue',
+ PIPELINE_NOTIFICATION: '#js-pipeline-notification',
PIPELINE_TESTS: '#js-pipeline-tests-detail',
PIPELINE_JOBS: '#js-pipeline-jobs-vue',
};
@@ -43,6 +45,14 @@ export default async function initPipelineDetailsBundle() {
}
try {
+ createPipelineNotificationApp(SELECTORS.PIPELINE_NOTIFICATION, apolloProvider);
+ } catch {
+ createFlash({
+ message: __('An error occurred while loading a section of this page.'),
+ });
+ }
+
+ try {
createDagApp(apolloProvider);
} catch {
createFlash({
diff --git a/app/assets/javascripts/pipelines/pipeline_details_notification.js b/app/assets/javascripts/pipelines/pipeline_details_notification.js
new file mode 100644
index 00000000000..0061be843c5
--- /dev/null
+++ b/app/assets/javascripts/pipelines/pipeline_details_notification.js
@@ -0,0 +1,31 @@
+import Vue from 'vue';
+import VueApollo from 'vue-apollo';
+import DeprecatedKeywordNotification from './components/notification/deprecated_type_keyword_notification.vue';
+
+Vue.use(VueApollo);
+
+export const createPipelineNotificationApp = (elSelector, apolloProvider) => {
+ const el = document.querySelector(elSelector);
+
+ if (!el) {
+ return;
+ }
+
+ const { deprecatedKeywordsDocPath, fullPath, pipelineIid } = el?.dataset;
+ // eslint-disable-next-line no-new
+ new Vue({
+ el,
+ components: {
+ DeprecatedKeywordNotification,
+ },
+ provide: {
+ deprecatedKeywordsDocPath,
+ fullPath,
+ pipelineIid,
+ },
+ apolloProvider,
+ render(createElement) {
+ return createElement('deprecated-keyword-notification');
+ },
+ });
+};
diff --git a/app/assets/javascripts/pipelines/pipeline_shared_client.js b/app/assets/javascripts/pipelines/pipeline_shared_client.js
index 84276588d6a..c3be487caae 100644
--- a/app/assets/javascripts/pipelines/pipeline_shared_client.js
+++ b/app/assets/javascripts/pipelines/pipeline_shared_client.js
@@ -1,19 +1,10 @@
import VueApollo from 'vue-apollo';
-import { IntrospectionFragmentMatcher } from 'apollo-cache-inmemory';
import createDefaultClient from '~/lib/graphql';
-import introspectionQueryResultData from './graphql/fragmentTypes.json';
-
-export const fragmentMatcher = new IntrospectionFragmentMatcher({
- introspectionQueryResultData,
-});
export const apolloProvider = new VueApollo({
defaultClient: createDefaultClient(
{},
{
- cacheConfig: {
- fragmentMatcher,
- },
useGet: true,
},
),