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
path: root/app
diff options
context:
space:
mode:
authorGitLab Bot <gitlab-bot@gitlab.com>2023-10-24 21:11:45 +0300
committerGitLab Bot <gitlab-bot@gitlab.com>2023-10-24 21:11:45 +0300
commit4bb797f25563205cf495f4dd5366e037e88831ab (patch)
treea345ddbd0e2464067323d3c6fd34960607ef4f44 /app
parent40a4f37126bb1a1dd6b6f4b3c0ebb414a3e3908a (diff)
Add latest changes from gitlab-org/gitlab@master
Diffstat (limited to 'app')
-rw-r--r--app/assets/javascripts/batch_comments/components/submit_dropdown.vue39
-rw-r--r--app/assets/javascripts/batch_comments/queries/can_approve.query.graphql11
-rw-r--r--app/assets/javascripts/ci/common/private/job_action_component.vue14
-rw-r--r--app/assets/javascripts/ci/common/private/job_links_layer.vue10
-rw-r--r--app/assets/javascripts/ci/pipeline_details/graph/components/graph_component.vue19
-rw-r--r--app/assets/javascripts/ci/pipeline_details/graph/components/graph_view_selector.vue20
-rw-r--r--app/assets/javascripts/ci/pipeline_details/graph/components/linked_graph_wrapper.vue14
-rw-r--r--app/assets/javascripts/ci/pipeline_details/graph/components/linked_pipeline.vue20
-rw-r--r--app/assets/javascripts/ci/pipeline_details/graph/components/linked_pipelines_column.vue39
-rw-r--r--app/assets/javascripts/ci/pipeline_details/graph/components/root_graph_layout.vue42
-rw-r--r--app/assets/javascripts/ci/pipeline_details/graph/components/stage_column_component.vue58
-rw-r--r--app/assets/javascripts/diffs/components/app.vue21
-rw-r--r--app/assets/javascripts/diffs/constants.js1
-rw-r--r--app/assets/javascripts/diffs/store/actions.js3
-rw-r--r--app/assets/javascripts/search/sidebar/components/blobs_filters.vue7
-rw-r--r--app/assets/javascripts/vue_merge_request_widget/components/checks/constants.js6
-rw-r--r--app/assets/javascripts/vue_merge_request_widget/components/checks/unresolved_discussions.vue37
-rw-r--r--app/assets/javascripts/vue_merge_request_widget/components/merge_checks.vue9
-rw-r--r--app/assets/javascripts/vue_merge_request_widget/components/widget/dynamic_content.vue9
-rw-r--r--app/assets/stylesheets/framework/variables.scss2
-rw-r--r--app/assets/stylesheets/page_bundles/pipeline.scss77
-rw-r--r--app/controllers/projects/group_links_controller.rb51
-rw-r--r--app/helpers/visibility_level_helper.rb10
-rw-r--r--app/models/ci/sources/pipeline.rb2
-rw-r--r--app/models/integration.rb19
-rw-r--r--app/models/integrations/apple_app_store.rb6
-rw-r--r--app/models/integrations/asana.rb6
-rw-r--r--app/models/integrations/assembla.rb4
-rw-r--r--app/models/integrations/bamboo.rb6
-rw-r--r--app/models/integrations/base_chat_notification.rb4
-rw-r--r--app/models/integrations/base_slack_notification.rb2
-rw-r--r--app/models/integrations/bugzilla.rb6
-rw-r--r--app/models/integrations/buildkite.rb12
-rw-r--r--app/models/integrations/campfire.rb6
-rw-r--r--app/models/integrations/clickup.rb6
-rw-r--r--app/models/integrations/confluence.rb4
-rw-r--r--app/models/integrations/custom_issue_tracker.rb6
-rw-r--r--app/models/integrations/datadog.rb6
-rw-r--r--app/models/integrations/discord.rb14
-rw-r--r--app/models/integrations/drone_ci.rb12
-rw-r--r--app/models/integrations/emails_on_push.rb4
-rw-r--r--app/models/integrations/ewm.rb6
-rw-r--r--app/models/integrations/external_wiki.rb14
-rw-r--r--app/models/integrations/gitlab_slack_application.rb4
-rw-r--r--app/models/integrations/google_play.rb6
-rw-r--r--app/models/integrations/hangouts_chat.rb6
-rw-r--r--app/models/integrations/harbor.rb26
-rw-r--r--app/models/integrations/irker.rb38
-rw-r--r--app/models/integrations/jenkins.rb6
-rw-r--r--app/models/integrations/jira.rb20
-rw-r--r--app/models/integrations/mattermost.rb6
-rw-r--r--app/models/integrations/mattermost_slash_commands.rb4
-rw-r--r--app/models/integrations/microsoft_teams.rb6
-rw-r--r--app/models/integrations/mock_ci.rb4
-rw-r--r--app/models/integrations/mock_monitoring.rb4
-rw-r--r--app/models/integrations/packagist.rb4
-rw-r--r--app/models/integrations/pipelines_email.rb4
-rw-r--r--app/models/integrations/pivotaltracker.rb6
-rw-r--r--app/models/integrations/prometheus.rb4
-rw-r--r--app/models/integrations/pumble.rb6
-rw-r--r--app/models/integrations/pushover.rb4
-rw-r--r--app/models/integrations/redmine.rb6
-rw-r--r--app/models/integrations/shimo.rb4
-rw-r--r--app/models/integrations/slack.rb4
-rw-r--r--app/models/integrations/slack_slash_commands.rb4
-rw-r--r--app/models/integrations/squash_tm.rb6
-rw-r--r--app/models/integrations/teamcity.rb6
-rw-r--r--app/models/integrations/telegram.rb6
-rw-r--r--app/models/integrations/unify_circuit.rb6
-rw-r--r--app/models/integrations/webex_teams.rb6
-rw-r--r--app/models/integrations/youtrack.rb6
-rw-r--r--app/models/integrations/zentao.rb8
-rw-r--r--app/serializers/merge_request_noteable_entity.rb4
-rw-r--r--app/services/projects/group_links/destroy_service.rb33
-rw-r--r--app/services/projects/group_links/update_service.rb8
-rw-r--r--app/workers/remove_expired_group_links_worker.rb2
76 files changed, 642 insertions, 289 deletions
diff --git a/app/assets/javascripts/batch_comments/components/submit_dropdown.vue b/app/assets/javascripts/batch_comments/components/submit_dropdown.vue
index 0527b56b464..4b9fe01e997 100644
--- a/app/assets/javascripts/batch_comments/components/submit_dropdown.vue
+++ b/app/assets/javascripts/batch_comments/components/submit_dropdown.vue
@@ -6,11 +6,29 @@ import { __ } from '~/locale';
import { createAlert } from '~/alert';
import MarkdownEditor from '~/vue_shared/components/markdown/markdown_editor.vue';
import { scrollToElement } from '~/lib/utils/common_utils';
+import { fetchPolicies } from '~/lib/graphql';
import { CLEAR_AUTOSAVE_ENTRY_EVENT } from '~/vue_shared/constants';
import markdownEditorEventHub from '~/vue_shared/components/markdown/eventhub';
import { trackSavedUsingEditor } from '~/vue_shared/components/markdown/tracking';
+import userCanApproveQuery from '../queries/can_approve.query.graphql';
export default {
+ apollo: {
+ userPermissions: {
+ fetchPolicy: fetchPolicies.NETWORK_ONLY,
+ query: userCanApproveQuery,
+ variables() {
+ return {
+ projectPath: this.projectPath.replace(/^\//, ''),
+ iid: `${this.getNoteableData.iid}`,
+ };
+ },
+ update: (data) => data.project?.mergeRequest?.userPermissions,
+ skip() {
+ return !this.dropdownVisible;
+ },
+ },
+ },
components: {
GlDisclosureDropdown,
GlButton,
@@ -28,6 +46,7 @@ export default {
data() {
return {
isSubmitting: false,
+ dropdownVisible: false,
noteData: {
noteable_type: '',
noteable_id: '',
@@ -42,11 +61,13 @@ export default {
'aria-label': __('Comment'),
'data-testid': 'comment-textarea',
},
+ userPermissions: {},
};
},
computed: {
...mapGetters(['getNotesData', 'getNoteableData', 'noteableType', 'getCurrentUserLastNote']),
...mapState('batchComments', ['shouldAnimateReviewButton']),
+ ...mapState('diffs', ['projectPath']),
autocompleteDataSources() {
return gl.GfmAutoComplete?.dataSources;
},
@@ -60,6 +81,17 @@ export default {
this.repositionDropdown();
});
},
+ dropdownVisible(val) {
+ if (!val) {
+ this.userPermissions = {};
+ }
+ },
+ userPermissions: {
+ handler() {
+ this.repositionDropdown();
+ },
+ deep: true,
+ },
},
mounted() {
this.noteData.noteable_type = this.noteableType;
@@ -112,6 +144,9 @@ export default {
preventDefault();
}
},
+ setDropdownVisible(val) {
+ this.dropdownVisible = val;
+ },
},
restrictedToolbarItems: ['full-screen'],
};
@@ -126,6 +161,8 @@ export default {
data-testid="submit-review-dropdown"
fluid-width
@beforeClose="onBeforeClose"
+ @shown="setDropdownVisible(true)"
+ @hidden="setDropdownVisible(false)"
>
<template #toggle>
<gl-button variant="info" category="primary">
@@ -171,7 +208,7 @@ export default {
@keydown.ctrl.enter="submitReview"
/>
</div>
- <template v-if="getNoteableData.current_user.can_approve">
+ <template v-if="userPermissions.canApprove">
<gl-form-checkbox
v-model="noteData.approve"
data-testid="approve_merge_request"
diff --git a/app/assets/javascripts/batch_comments/queries/can_approve.query.graphql b/app/assets/javascripts/batch_comments/queries/can_approve.query.graphql
new file mode 100644
index 00000000000..f0c9ef7b3c8
--- /dev/null
+++ b/app/assets/javascripts/batch_comments/queries/can_approve.query.graphql
@@ -0,0 +1,11 @@
+query userCanApprove($projectPath: ID!, $iid: String!) {
+ project(fullPath: $projectPath) {
+ id
+ mergeRequest(iid: $iid) {
+ id
+ userPermissions {
+ canApprove
+ }
+ }
+ }
+}
diff --git a/app/assets/javascripts/ci/common/private/job_action_component.vue b/app/assets/javascripts/ci/common/private/job_action_component.vue
index b0fa724d450..c266e061513 100644
--- a/app/assets/javascripts/ci/common/private/job_action_component.vue
+++ b/app/assets/javascripts/ci/common/private/job_action_component.vue
@@ -119,6 +119,7 @@ export default {
ref="button"
:class="cssClass"
:disabled="isDisabled"
+ size="small"
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-button"
@click.stop="onClickAction"
@@ -129,8 +130,17 @@ export default {
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" />
+ <gl-loading-icon
+ v-if="isLoading"
+ size="sm"
+ class="gl-button-icon gl-m-2 js-action-icon-loading"
+ />
+ <gl-icon
+ v-else
+ :name="actionIcon"
+ class="gl-button-icon gl-p-1 gl-mr-0!"
+ :aria-label="actionIcon"
+ />
</div>
</gl-button>
</template>
diff --git a/app/assets/javascripts/ci/common/private/job_links_layer.vue b/app/assets/javascripts/ci/common/private/job_links_layer.vue
index 59260ca3f81..9b3647e9c55 100644
--- a/app/assets/javascripts/ci/common/private/job_links_layer.vue
+++ b/app/assets/javascripts/ci/common/private/job_links_layer.vue
@@ -1,5 +1,6 @@
<script>
import { memoize } from 'lodash';
+import glFeatureFlagMixin from '~/vue_shared/mixins/gl_feature_flags_mixin';
import { reportToSentry } from '~/ci/utils';
import { parseData } from '~/ci/pipeline_details/utils/parsing_utils';
import LinksInner from '~/ci/pipeline_details/graph/components/links_inner.vue';
@@ -16,6 +17,7 @@ export default {
components: {
LinksInner,
},
+ mixins: [glFeatureFlagMixin()],
props: {
containerMeasurements: {
type: Object,
@@ -50,6 +52,9 @@ export default {
showLinkedLayers() {
return this.showLinks && !this.containerZero;
},
+ isNewPipelineGraph() {
+ return this.glFeatures.newPipelineGraph;
+ },
},
errorCaptured(err, _vm, info) {
reportToSentry(this.$options.name, `error: ${err}, info: ${info}`);
@@ -68,7 +73,10 @@ export default {
<slot></slot>
</links-inner>
<div v-else>
- <div class="gl-display-flex gl-relative">
+ <div
+ class="gl-display-flex gl-relative"
+ :class="{ 'gl-flex-wrap gl-sm-flex-nowrap': isNewPipelineGraph }"
+ >
<slot></slot>
</div>
</div>
diff --git a/app/assets/javascripts/ci/pipeline_details/graph/components/graph_component.vue b/app/assets/javascripts/ci/pipeline_details/graph/components/graph_component.vue
index f098d790736..fce7aabf0cf 100644
--- a/app/assets/javascripts/ci/pipeline_details/graph/components/graph_component.vue
+++ b/app/assets/javascripts/ci/pipeline_details/graph/components/graph_component.vue
@@ -4,6 +4,7 @@ import {
generateColumnsFromLayersListMemoized,
keepLatestDownstreamPipelines,
} from '~/ci/pipeline_details/utils/parsing_utils';
+import glFeatureFlagMixin from '~/vue_shared/mixins/gl_feature_flags_mixin';
import LinksLayer from '../../../common/private/job_links_layer.vue';
import { DOWNSTREAM, MAIN, UPSTREAM, ONE_COL_WIDTH, STAGE_VIEW } from '../constants';
import { validateConfigPaths } from '../utils';
@@ -19,6 +20,7 @@ export default {
LinkedPipelinesColumn,
StageColumnComponent,
},
+ mixins: [glFeatureFlagMixin()],
props: {
configPaths: {
type: Object,
@@ -132,6 +134,9 @@ export default {
upstreamPipelines() {
return this.hasUpstreamPipelines ? this.pipeline.upstream : [];
},
+ isNewPipelineGraph() {
+ return this.glFeatures.newPipelineGraph;
+ },
},
errorCaptured(err, _vm, info) {
reportToSentry(this.$options.name, `error: ${err}, info: ${info}`);
@@ -178,10 +183,15 @@ export default {
<div class="js-pipeline-graph">
<div
ref="mainPipelineContainer"
- class="gl-display-flex gl-position-relative gl-bg-gray-10 gl-white-space-nowrap"
+ class="pipeline-graph gl-display-flex gl-position-relative gl-white-space-nowrap gl-rounded-lg"
:class="{
- 'gl-pipeline-min-h gl-py-5 gl-overflow-auto': !isLinkedPipeline,
+ 'gl-bg-gray-10': !isNewPipelineGraph,
+ 'gl-pipeline-min-h gl-py-5 gl-overflow-auto': !isNewPipelineGraph && !isLinkedPipeline,
+ 'pipeline-graph-container gl-bg-gray-10 gl-pipeline-min-h gl-align-items-flex-start gl-pt-3 gl-pb-8 gl-mt-3 gl-overflow-auto':
+ isNewPipelineGraph && !isLinkedPipeline,
+ 'gl-bg-gray-50 gl-sm-ml-5': isNewPipelineGraph && isLinkedPipeline,
}"
+ data-testid="pipeline-container"
>
<linked-graph-wrapper>
<template #upstream>
@@ -199,7 +209,7 @@ export default {
/>
</template>
<template #main>
- <div :id="containerId" :ref="containerId">
+ <div :id="containerId" :ref="containerId" class="pipeline-links-container">
<links-layer
:pipeline-data="layout"
:pipeline-id="pipeline.id"
@@ -238,7 +248,8 @@ export default {
<template #downstream>
<linked-pipelines-column
v-if="showDownstreamPipelines"
- class="gl-mr-6"
+ class="gl-mr-5"
+ :class="{ 'gl-sm-ml-3': isNewPipelineGraph }"
:config-paths="configPaths"
:linked-pipelines="downstreamPipelines"
:column-title="__('Downstream')"
diff --git a/app/assets/javascripts/ci/pipeline_details/graph/components/graph_view_selector.vue b/app/assets/javascripts/ci/pipeline_details/graph/components/graph_view_selector.vue
index fb7dcb300f1..114b224fbe7 100644
--- a/app/assets/javascripts/ci/pipeline_details/graph/components/graph_view_selector.vue
+++ b/app/assets/javascripts/ci/pipeline_details/graph/components/graph_view_selector.vue
@@ -1,11 +1,11 @@
<script>
import { GlAlert, GlButton, GlButtonGroup, GlLoadingIcon, GlToggle } from '@gitlab/ui';
+import glFeatureFlagMixin from '~/vue_shared/mixins/gl_feature_flags_mixin';
import { __, s__ } from '~/locale';
import { STAGE_VIEW, LAYER_VIEW } from '../constants';
export default {
name: 'GraphViewSelector',
-
components: {
GlAlert,
GlButton,
@@ -13,7 +13,7 @@ export default {
GlLoadingIcon,
GlToggle,
},
-
+ mixins: [glFeatureFlagMixin()],
props: {
showLinks: {
type: Boolean,
@@ -77,6 +77,9 @@ export default {
};
});
},
+ isNewPipelineGraph() {
+ return this.glFeatures.newPipelineGraph;
+ },
},
watch: {
/*
@@ -138,7 +141,13 @@ export default {
<template>
<div>
- <div class="gl-relative gl-display-flex gl-align-items-center gl-w-max-content gl-my-4">
+ <div
+ class="gl-relative gl-display-flex gl-align-items-center gl-my-4"
+ :class="{
+ 'gl-w-max-content': !isNewPipelineGraph,
+ 'gl-flex-wrap gl-sm-flex-nowrap': isNewPipelineGraph,
+ }"
+ >
<gl-loading-icon
v-if="isSwitcherLoading"
data-testid="switcher-loading-state"
@@ -161,7 +170,10 @@ export default {
<gl-toggle
v-model="showLinksActive"
data-testid="show-links-toggle"
- class="gl-mx-4"
+ :class="{
+ 'gl-mx-4': !isNewPipelineGraph,
+ 'gl-sm-ml-4 gl-mt-4 gl-sm-mt-0': isNewPipelineGraph,
+ }"
:label="$options.i18n.linksLabelText"
:is-loading="isToggleLoading"
label-position="left"
diff --git a/app/assets/javascripts/ci/pipeline_details/graph/components/linked_graph_wrapper.vue b/app/assets/javascripts/ci/pipeline_details/graph/components/linked_graph_wrapper.vue
index fb2280d971a..9bd0ec6d793 100644
--- a/app/assets/javascripts/ci/pipeline_details/graph/components/linked_graph_wrapper.vue
+++ b/app/assets/javascripts/ci/pipeline_details/graph/components/linked_graph_wrapper.vue
@@ -1,5 +1,17 @@
+<script>
+import glFeatureFlagMixin from '~/vue_shared/mixins/gl_feature_flags_mixin';
+
+export default {
+ mixins: [glFeatureFlagMixin()],
+ computed: {
+ isNewPipelineGraph() {
+ return this.glFeatures.newPipelineGraph;
+ },
+ },
+};
+</script>
<template>
- <div class="gl-display-flex">
+ <div class="gl-display-flex" :class="{ 'gl-flex-wrap gl-w-full': isNewPipelineGraph }">
<slot name="upstream"></slot>
<slot name="main"></slot>
<slot name="downstream"></slot>
diff --git a/app/assets/javascripts/ci/pipeline_details/graph/components/linked_pipeline.vue b/app/assets/javascripts/ci/pipeline_details/graph/components/linked_pipeline.vue
index 511eac79fb5..26521f87426 100644
--- a/app/assets/javascripts/ci/pipeline_details/graph/components/linked_pipeline.vue
+++ b/app/assets/javascripts/ci/pipeline_details/graph/components/linked_pipeline.vue
@@ -7,6 +7,7 @@ import {
GlTooltip,
GlTooltipDirective,
} from '@gitlab/ui';
+import glFeatureFlagMixin from '~/vue_shared/mixins/gl_feature_flags_mixin';
import { TYPENAME_CI_PIPELINE } from '~/graphql_shared/constants';
import { convertToGraphQLId } from '~/graphql_shared/utils';
import { BV_HIDE_TOOLTIP } from '~/lib/utils/constants';
@@ -29,6 +30,7 @@ export default {
GlLoadingIcon,
GlTooltip,
},
+ mixins: [glFeatureFlagMixin()],
styles: {
actionSizeClasses: ['gl-h-7 gl-w-7'],
flatLeftBorder: ['gl-rounded-bottom-left-none!', 'gl-rounded-top-left-none!'],
@@ -115,9 +117,6 @@ export default {
downstreamTitle() {
return this.childPipeline ? this.sourceJobName : this.pipeline.project.name;
},
- flexDirection() {
- return this.isUpstream ? 'gl-flex-direction-row-reverse' : 'gl-flex-direction-row';
- },
graphqlPipelineId() {
return convertToGraphQLId(TYPENAME_CI_PIPELINE, this.pipeline.id);
},
@@ -176,6 +175,9 @@ export default {
return `${this.downstreamTitle} #${this.pipeline.id} - ${this.pipelineStatus.label} -
${this.sourceJobInfo}`;
},
+ isNewPipelineGraph() {
+ return this.glFeatures.newPipelineGraph;
+ },
},
errorCaptured(err, _vm, info) {
reportToSentry('linked_pipeline', `error: ${err}, info: ${info}`);
@@ -231,9 +233,15 @@ export default {
<template>
<div
ref="linkedPipeline"
- class="gl-h-full gl-display-flex! gl-px-2"
- :class="flexDirection"
+ class="linked-pipeline-container gl-h-full gl-display-flex!"
+ :class="{
+ 'gl-flex-direction-row-reverse': isUpstream,
+ 'gl-flex-direction-row': !isUpstream,
+ 'gl-px-2': !isNewPipelineGraph,
+ 'gl-w-full gl-sm-w-auto': isNewPipelineGraph,
+ }"
data-testid="linked-pipeline-container"
+ :aria-expanded="expanded"
@mouseover="onDownstreamHovered"
@mouseleave="onDownstreamHoverLeave"
>
@@ -250,7 +258,7 @@ export default {
/>
<div v-else class="gl-pr-3"><gl-loading-icon size="sm" inline /></div>
<div
- class="gl-display-flex gl-downstream-pipeline-job-width gl-flex-direction-column gl-line-height-normal"
+ class="gl-display-flex gl-flex-direction-column gl-line-height-normal gl-downstream-pipeline-job-width"
>
<span class="gl-text-truncate" data-testid="downstream-title-content">
{{ downstreamTitle }}
diff --git a/app/assets/javascripts/ci/pipeline_details/graph/components/linked_pipelines_column.vue b/app/assets/javascripts/ci/pipeline_details/graph/components/linked_pipelines_column.vue
index 2de7e43c9b1..67918ea8d1a 100644
--- a/app/assets/javascripts/ci/pipeline_details/graph/components/linked_pipelines_column.vue
+++ b/app/assets/javascripts/ci/pipeline_details/graph/components/linked_pipelines_column.vue
@@ -1,4 +1,5 @@
<script>
+import glFeatureFlagMixin from '~/vue_shared/mixins/gl_feature_flags_mixin';
import getPipelineDetails from 'shared_queries/pipelines/get_pipeline_details.query.graphql';
import { reportToSentry } from '~/ci/utils';
import { LOAD_FAILURE } from '../../constants';
@@ -18,6 +19,7 @@ export default {
LinkedPipeline,
PipelineGraph: () => import('./graph_component.vue'),
},
+ mixins: [glFeatureFlagMixin()],
props: {
columnTitle: {
type: String,
@@ -63,23 +65,30 @@ export default {
'gl-pipeline-job-width',
'gl-text-truncate',
'gl-line-height-36',
- 'gl-pl-3',
- 'gl-mb-5',
],
minWidth: `${ONE_COL_WIDTH}px`,
computed: {
columnClass() {
- const positionValues = {
+ const positionValuesOld = {
right: 'gl-ml-6',
left: 'gl-mx-6',
};
+ const positionValues = {
+ right: 'gl-ml-5',
+ left: 'gl-mx-4 gl-flex-basis-full',
+ };
+ const usePositionValues = this.isNewPipelineGraph ? positionValues : positionValuesOld;
- return `graph-position-${this.graphPosition} ${positionValues[this.graphPosition]}`;
+ return `graph-position-${this.graphPosition} ${usePositionValues[this.graphPosition]}`;
},
computedTitleClasses() {
const positionalClasses = this.isUpstream ? ['gl-w-full', 'gl-linked-pipeline-padding'] : [];
- return [...this.$options.titleClasses, ...positionalClasses];
+ return [
+ ...this.$options.titleClasses,
+ !this.isNewPipelineGraph ?? ['gl-pl-3', 'gl-mb-5'],
+ ...positionalClasses,
+ ];
},
graphPosition() {
return this.isUpstream ? 'left' : 'right';
@@ -93,6 +102,9 @@ export default {
minWidth() {
return this.isUpstream ? 0 : this.$options.minWidth;
},
+ isNewPipelineGraph() {
+ return this.glFeatures.newPipelineGraph;
+ },
},
methods: {
getPipelineData(pipeline) {
@@ -197,7 +209,7 @@ export default {
</script>
<template>
- <div class="gl-display-flex">
+ <div class="gl-display-flex" :class="{ 'gl-w-full': isNewPipelineGraph }">
<div :class="columnClass" class="linked-pipelines-column">
<div data-testid="linked-column-title" :class="computedTitleClasses">
{{ columnTitle }}
@@ -206,8 +218,12 @@ export default {
<li
v-for="pipeline in linkedPipelines"
:key="pipeline.id"
- class="gl-display-flex gl-mb-3"
- :class="{ 'gl-flex-direction-row-reverse': isUpstream }"
+ class="gl-display-flex"
+ :class="{
+ 'gl-mb-3': !isNewPipelineGraph,
+ 'gl-flex-wrap gl-sm-flex-nowrap gl-mb-6': isNewPipelineGraph,
+ 'gl-flex-direction-row-reverse': !isNewPipelineGraph && isUpstream,
+ }"
>
<linked-pipeline
class="gl-display-inline-block"
@@ -224,12 +240,15 @@ export default {
<div
v-if="showContainer(pipeline.id)"
:style="{ minWidth }"
- class="gl-display-inline-block"
+ class="gl-display-inline-block pipeline-show-container"
>
<pipeline-graph
v-if="isExpanded(pipeline.id)"
:type="type"
- class="gl-inline-block gl-mt-n2"
+ class="gl-inline-block"
+ :class="{
+ 'gl-mt-n2': !isNewPipelineGraph,
+ }"
:config-paths="configPaths"
:pipeline="currentPipeline"
:computed-pipeline-info="getPipelineLayers(pipeline.id)"
diff --git a/app/assets/javascripts/ci/pipeline_details/graph/components/root_graph_layout.vue b/app/assets/javascripts/ci/pipeline_details/graph/components/root_graph_layout.vue
index bcd7705669e..7c07591d0de 100644
--- a/app/assets/javascripts/ci/pipeline_details/graph/components/root_graph_layout.vue
+++ b/app/assets/javascripts/ci/pipeline_details/graph/components/root_graph_layout.vue
@@ -1,5 +1,12 @@
<script>
+import { GlCard } from '@gitlab/ui';
+import glFeatureFlagMixin from '~/vue_shared/mixins/gl_feature_flags_mixin';
+
export default {
+ components: {
+ GlCard,
+ },
+ mixins: [glFeatureFlagMixin()],
props: {
stageClasses: {
type: String,
@@ -12,18 +19,37 @@ export default {
default: '',
},
},
+ computed: {
+ isNewPipelineGraph() {
+ return this.glFeatures.newPipelineGraph;
+ },
+ },
};
</script>
<template>
<div>
- <div class="gl-display-flex gl-align-items-center gl-w-full gl-mb-5" :class="stageClasses">
- <slot name="stages"> </slot>
- </div>
- <div
- class="gl-display-flex gl-flex-direction-column gl-align-items-center gl-w-full"
- :class="jobClasses"
+ <gl-card
+ v-if="isNewPipelineGraph"
+ class="gl-rounded-lg"
+ header-class="gl-rounded-lg gl-px-0 gl-py-0 gl-bg-white gl-border-b-0"
+ body-class="gl-pt-2 gl-pb-0 gl-px-2"
>
- <slot name="jobs"> </slot>
- </div>
+ <template #header>
+ <slot name="stages"></slot>
+ </template>
+
+ <slot name="jobs"></slot>
+ </gl-card>
+ <template v-else>
+ <div class="gl-display-flex gl-align-items-center gl-w-full" :class="stageClasses">
+ <slot name="stages"> </slot>
+ </div>
+ <div
+ class="gl-display-flex gl-flex-direction-column gl-align-items-center gl-w-full"
+ :class="jobClasses"
+ >
+ <slot name="jobs"> </slot>
+ </div>
+ </template>
</div>
</template>
diff --git a/app/assets/javascripts/ci/pipeline_details/graph/components/stage_column_component.vue b/app/assets/javascripts/ci/pipeline_details/graph/components/stage_column_component.vue
index 6030adc96ad..e144b9aab0c 100644
--- a/app/assets/javascripts/ci/pipeline_details/graph/components/stage_column_component.vue
+++ b/app/assets/javascripts/ci/pipeline_details/graph/components/stage_column_component.vue
@@ -68,7 +68,7 @@ export default {
required: true,
},
},
- jobClasses: [
+ legacyJobClasses: [
'gl-p-3',
'gl-border-gray-100',
'gl-border-solid',
@@ -82,18 +82,43 @@ export default {
'gl-hover-border-gray-200',
'gl-focus-border-gray-200',
],
- titleClasses: [
+ jobClasses: [
+ 'gl-p-3',
+ 'gl-border-0',
+ 'gl-bg-transparent',
+ 'gl-rounded-base',
+ 'gl-hover-bg-gray-50',
+ 'gl-focus-bg-gray-50',
+ 'gl-hover-text-gray-900',
+ 'gl-focus-text-gray-900',
+ ],
+ legacyTitleClasses: [
'gl-font-weight-bold',
'gl-pipeline-job-width',
'gl-text-truncate',
'gl-line-height-36',
'gl-pl-3',
],
+ titleClasses: [
+ 'gl-font-weight-bold',
+ 'gl-pipeline-job-width',
+ 'gl-text-truncate',
+ 'gl-line-height-36',
+ 'gl-pl-4',
+ 'gl-mb-n2',
+ ],
computed: {
canUpdatePipeline() {
return this.userPermissions.updatePipeline;
},
columnSpacingClass() {
+ if (this.isNewPipelineGraph) {
+ const baseClasses = 'stage-column gl-relative gl-flex-basis-full';
+ return this.isStageView
+ ? `${baseClasses} is-stage-view gl-m-5`
+ : `${baseClasses} gl-my-5 gl-mx-7`;
+ }
+
return this.isStageView ? 'gl-px-6' : 'gl-px-9';
},
hasAction() {
@@ -102,6 +127,17 @@ export default {
showStageName() {
return !this.isStageView;
},
+ isNewPipelineGraph() {
+ return this.glFeatures.newPipelineGraph;
+ },
+ jobClasses() {
+ return this.isNewPipelineGraph ? this.$options.jobClasses : this.$options.legacyJobClasses;
+ },
+ titleClasses() {
+ return this.isNewPipelineGraph
+ ? this.$options.titleClasses
+ : this.$options.legacyTitleClasses;
+ },
},
errorCaptured(err, _vm, info) {
reportToSentry('stage_column_component', `error: ${err}, info: ${info}`);
@@ -135,12 +171,16 @@ export default {
};
</script>
<template>
- <root-graph-layout :class="columnSpacingClass" data-testid="stage-column">
+ <root-graph-layout
+ :class="columnSpacingClass"
+ class="stage-column gl-relative gl-flex-basis-full"
+ data-testid="stage-column"
+ >
<template #stages>
<div
data-testid="stage-column-title"
class="gl-display-flex gl-justify-content-space-between gl-relative"
- :class="$options.titleClasses"
+ :class="titleClasses"
>
<span :title="name" class="gl-text-truncate gl-pr-3 gl-w-85p">
{{ name }}
@@ -161,7 +201,11 @@ export default {
:id="groupId(group)"
:key="getGroupId(group)"
data-testid="stage-column-group"
- class="gl-relative gl-mb-3 gl-white-space-normal gl-pipeline-job-width"
+ class="gl-relative gl-white-space-normal gl-pipeline-job-width"
+ :class="{
+ 'gl-mb-3': !isNewPipelineGraph,
+ 'gl-mb-2': isNewPipelineGraph,
+ }"
@mouseenter="$emit('jobHover', group.name)"
@mouseleave="$emit('jobHover', '')"
>
@@ -174,7 +218,7 @@ export default {
:pipeline-expanded="pipelineExpanded"
:pipeline-id="pipelineId"
:stage-name="showStageName ? group.stageName : ''"
- :css-class-job-name="$options.jobClasses"
+ :css-class-job-name="jobClasses"
:class="[
{ 'gl-opacity-3': isFadedOut(group.name) },
'gl-transition-duration-slow gl-transition-timing-function-ease',
@@ -188,7 +232,7 @@ export default {
:group="group"
:stage-name="showStageName ? group.stageName : ''"
:pipeline-id="pipelineId"
- :css-class-job-name="$options.jobClasses"
+ :css-class-job-name="jobClasses"
/>
</div>
</div>
diff --git a/app/assets/javascripts/diffs/components/app.vue b/app/assets/javascripts/diffs/components/app.vue
index a389ab8e355..7c3d6dc8c42 100644
--- a/app/assets/javascripts/diffs/components/app.vue
+++ b/app/assets/javascripts/diffs/components/app.vue
@@ -1,6 +1,7 @@
<script>
import { GlLoadingIcon, GlPagination, GlSprintf, GlAlert } from '@gitlab/ui';
import { GlBreakpointInstance as bp } from '@gitlab/ui/dist/utils';
+import { debounce } from 'lodash';
// eslint-disable-next-line no-restricted-imports
import { mapState, mapGetters, mapActions } from 'vuex';
import glFeatureFlagsMixin from '~/vue_shared/mixins/gl_feature_flags_mixin';
@@ -16,7 +17,8 @@ import {
import { createAlert } from '~/alert';
import { isSingleViewStyle } from '~/helpers/diffs_helper';
import { helpPagePath } from '~/helpers/help_page_helper';
-import { parseBoolean } from '~/lib/utils/common_utils';
+import { parseBoolean, handleLocationHash } from '~/lib/utils/common_utils';
+import { DEFAULT_DEBOUNCE_AND_THROTTLE_MS } from '~/lib/utils/constants';
import { Mousetrap } from '~/lib/mousetrap';
import { updateHistory } from '~/lib/utils/url_utility';
import { __ } from '~/locale';
@@ -39,6 +41,7 @@ import {
TRACKING_SINGLE_FILE_MODE,
TRACKING_MULTIPLE_FILES_MODE,
EVT_MR_PREPARED,
+ EVT_DISCUSSIONS_ASSIGNED,
} from '../constants';
import { isCollapsed } from '../utils/diff_file';
@@ -136,6 +139,7 @@ export default {
diffFilesLength: 0,
virtualScrollCurrentIndex: -1,
subscribedToVirtualScrollingEvents: false,
+ autoScrolled: false,
};
},
apollo: {
@@ -358,6 +362,10 @@ export default {
this.adjustView();
this.subscribeToEvents();
+ this.slowHashHandler = debounce(() => {
+ handleLocationHash();
+ this.autoScrolled = true;
+ }, DEFAULT_DEBOUNCE_AND_THROTTLE_MS);
this.unwatchDiscussions = this.$watch(
() => `${this.flatBlobsList.length}:${this.$store.state.notes.discussions.length}`,
() => {
@@ -420,8 +428,10 @@ export default {
diffsEventHub.$on('diffFilesModified', this.setDiscussions);
diffsEventHub.$on('doneLoadingBatches', this.autoScroll);
diffsEventHub.$on(EVT_MR_PREPARED, this.fetchData);
+ diffsEventHub.$on(EVT_DISCUSSIONS_ASSIGNED, this.handleHash);
},
unsubscribeFromEvents() {
+ diffsEventHub.$off(EVT_DISCUSSIONS_ASSIGNED, this.handleHash);
diffsEventHub.$off(EVT_MR_PREPARED, this.fetchData);
diffsEventHub.$off('doneLoadingBatches', this.autoScroll);
diffsEventHub.$off('diffFilesModified', this.setDiscussions);
@@ -449,6 +459,15 @@ export default {
.catch(() => {});
}
},
+ handleHash() {
+ if (this.viewDiffsFileByFile && !this.autoScrolled) {
+ const file = this.diffs[0];
+
+ if (file && !file.isLoadingFullFile) {
+ requestIdleCallback(() => this.slowHashHandler());
+ }
+ }
+ },
navigateToDiffFileNumber(number) {
this.navigateToDiffFileIndex(number - 1);
},
diff --git a/app/assets/javascripts/diffs/constants.js b/app/assets/javascripts/diffs/constants.js
index 575cd05ceb8..e48eb10753c 100644
--- a/app/assets/javascripts/diffs/constants.js
+++ b/app/assets/javascripts/diffs/constants.js
@@ -82,6 +82,7 @@ export const RENAMED_DIFF_TRANSITIONS = {
// MR Diffs known events
export const EVT_MR_PREPARED = 'mr:asyncPreparationFinished';
export const EVT_EXPAND_ALL_FILES = 'mr:diffs:expandAllFiles';
+export const EVT_DISCUSSIONS_ASSIGNED = 'mr:diffs:discussionsAssigned';
export const EVT_PERF_MARK_FILE_TREE_START = 'mr:diffs:perf:fileTreeStart';
export const EVT_PERF_MARK_FILE_TREE_END = 'mr:diffs:perf:fileTreeEnd';
export const EVT_PERF_MARK_DIFF_FILES_START = 'mr:diffs:perf:filesStart';
diff --git a/app/assets/javascripts/diffs/store/actions.js b/app/assets/javascripts/diffs/store/actions.js
index ed8ae795bda..d86a88f97b8 100644
--- a/app/assets/javascripts/diffs/store/actions.js
+++ b/app/assets/javascripts/diffs/store/actions.js
@@ -49,6 +49,7 @@ import {
TRACKING_MULTIPLE_FILES_MODE,
EVT_MR_PREPARED,
FILE_DIFF_POSITION_TYPE,
+ EVT_DISCUSSIONS_ASSIGNED,
} from '../constants';
import {
DISCUSSION_SINGLE_DIFF_FAILED,
@@ -413,7 +414,7 @@ export const assignDiscussionsToDiff = (
}
Vue.nextTick(() => {
- notesEventHub.$emit('scrollToDiscussion');
+ eventHub.$emit(EVT_DISCUSSIONS_ASSIGNED);
});
};
diff --git a/app/assets/javascripts/search/sidebar/components/blobs_filters.vue b/app/assets/javascripts/search/sidebar/components/blobs_filters.vue
index ac36ae6b366..0ed2c24efba 100644
--- a/app/assets/javascripts/search/sidebar/components/blobs_filters.vue
+++ b/app/assets/javascripts/search/sidebar/components/blobs_filters.vue
@@ -18,11 +18,8 @@ export default {
computed: {
...mapGetters(['currentScope']),
...mapState(['useSidebarNavigation', 'searchType']),
- showArchivedFilter() {
- return this.glFeatures.searchBlobsHideArchivedProjects;
- },
showDivider() {
- return !this.useSidebarNavigation && this.showArchivedFilter;
+ return !this.useSidebarNavigation;
},
hrClasses() {
return [...HR_DEFAULT_CLASSES, 'gl-display-none', 'gl-md-display-block'];
@@ -35,6 +32,6 @@ export default {
<filters-template>
<language-filter class="gl-mb-5" />
<hr v-if="showDivider" :class="hrClasses" />
- <archived-filter v-if="showArchivedFilter" class="gl-mb-5" />
+ <archived-filter class="gl-mb-5" />
</filters-template>
</template>
diff --git a/app/assets/javascripts/vue_merge_request_widget/components/checks/constants.js b/app/assets/javascripts/vue_merge_request_widget/components/checks/constants.js
new file mode 100644
index 00000000000..e15987d7280
--- /dev/null
+++ b/app/assets/javascripts/vue_merge_request_widget/components/checks/constants.js
@@ -0,0 +1,6 @@
+export const COMPONENTS = {
+ conflicts: () => import('./conflicts.vue'),
+ unresolved_discussions: () => import('./unresolved_discussions.vue'),
+ rebase: () => import('./rebase.vue'),
+ default: () => import('./message.vue'),
+};
diff --git a/app/assets/javascripts/vue_merge_request_widget/components/checks/unresolved_discussions.vue b/app/assets/javascripts/vue_merge_request_widget/components/checks/unresolved_discussions.vue
new file mode 100644
index 00000000000..c8dba36700c
--- /dev/null
+++ b/app/assets/javascripts/vue_merge_request_widget/components/checks/unresolved_discussions.vue
@@ -0,0 +1,37 @@
+<script>
+import { s__ } from '~/locale';
+import notesEventHub from '~/notes/event_hub';
+import ActionButtons from '../action_buttons.vue';
+import MergeChecksMessage from './message.vue';
+
+export default {
+ name: 'MergeChecksUnresolvedDiscussions',
+ components: {
+ MergeChecksMessage,
+ ActionButtons,
+ },
+ props: {
+ check: {
+ type: Object,
+ required: true,
+ },
+ },
+ computed: {
+ tertiaryActionsButtons() {
+ return [
+ {
+ text: s__('mrWidget|Go to first unresolved thread'),
+ category: 'default',
+ onClick: () => notesEventHub.$emit('jumpToFirstUnresolvedDiscussion'),
+ },
+ ];
+ },
+ },
+};
+</script>
+
+<template>
+ <merge-checks-message :check="check">
+ <action-buttons v-if="check.result === 'failed'" :tertiary-buttons="tertiaryActionsButtons" />
+ </merge-checks-message>
+</template>
diff --git a/app/assets/javascripts/vue_merge_request_widget/components/merge_checks.vue b/app/assets/javascripts/vue_merge_request_widget/components/merge_checks.vue
index 5652b81386f..ecf2987307c 100644
--- a/app/assets/javascripts/vue_merge_request_widget/components/merge_checks.vue
+++ b/app/assets/javascripts/vue_merge_request_widget/components/merge_checks.vue
@@ -1,17 +1,12 @@
<script>
import { GlSkeletonLoader } from '@gitlab/ui';
-import { n__, __, sprintf } from '~/locale';
+import { __, n__, sprintf } from '~/locale';
+import { COMPONENTS } from '~/vue_merge_request_widget/components/checks/constants';
import mergeRequestQueryVariablesMixin from '../mixins/merge_request_query_variables';
import mergeChecksQuery from '../queries/merge_checks.query.graphql';
import StateContainer from './state_container.vue';
import BoldText from './bold_text.vue';
-const COMPONENTS = {
- conflicts: () => import('./checks/conflicts.vue'),
- rebase: () => import('./checks/rebase.vue'),
- default: () => import('./checks/message.vue'),
-};
-
export default {
apollo: {
state: {
diff --git a/app/assets/javascripts/vue_merge_request_widget/components/widget/dynamic_content.vue b/app/assets/javascripts/vue_merge_request_widget/components/widget/dynamic_content.vue
index 72c041759d9..d4375690ad1 100644
--- a/app/assets/javascripts/vue_merge_request_widget/components/widget/dynamic_content.vue
+++ b/app/assets/javascripts/vue_merge_request_widget/components/widget/dynamic_content.vue
@@ -1,5 +1,5 @@
<script>
-import { GlBadge, GlLink } from '@gitlab/ui';
+import { GlBadge, GlLink, GlTooltipDirective } from '@gitlab/ui';
import SafeHtml from '~/vue_shared/directives/safe_html';
import { generateText } from '../extensions/utils';
import ContentRow from './widget_content_row.vue';
@@ -15,6 +15,7 @@ export default {
},
directives: {
SafeHtml,
+ GlTooltip: GlTooltipDirective,
},
props: {
data: {
@@ -78,7 +79,11 @@ export default {
<div class="gl-display-flex gl-flex-grow-1">
<div class="gl-display-flex gl-flex-grow-1 gl-align-items-baseline">
<div>
- <p v-safe-html="generatedText" class="gl-mb-0 gl-mr-1"></p>
+ <p
+ v-gl-tooltip="{ title: data.tooltipText, boundary: 'viewport' }"
+ v-safe-html="generatedText"
+ class="gl-mb-0 gl-mr-1"
+ ></p>
<gl-link v-if="data.link" :href="data.link.href">{{ data.link.text }}</gl-link>
<p
v-if="data.supportingText"
diff --git a/app/assets/stylesheets/framework/variables.scss b/app/assets/stylesheets/framework/variables.scss
index 31d45ad3a28..9a1faf27143 100644
--- a/app/assets/stylesheets/framework/variables.scss
+++ b/app/assets/stylesheets/framework/variables.scss
@@ -811,7 +811,7 @@ $ci-action-icon-size: 22px;
$ci-action-icon-size-lg: 24px;
$pipeline-dropdown-line-height: 20px;
$ci-action-dropdown-button-size: 24px;
-$ci-action-dropdown-svg-size: 12px;
+$ci-action-dropdown-svg-size: 16px;
/*
CI variable lists
diff --git a/app/assets/stylesheets/page_bundles/pipeline.scss b/app/assets/stylesheets/page_bundles/pipeline.scss
index 98e9e2b3c27..dcd8f90ab1c 100644
--- a/app/assets/stylesheets/page_bundles/pipeline.scss
+++ b/app/assets/stylesheets/page_bundles/pipeline.scss
@@ -125,21 +125,27 @@
// They are here to still access a variable or because they use magic values.
// scoped to the graph. Do not add other styles.
.gl-pipeline-min-h {
- min-height: $dropdown-max-height-lg;
+ min-height: calc(#{$dropdown-max-height-lg} + #{$gl-spacing-scale-6});
}
.gl-pipeline-job-width {
width: 100%;
- max-width: 400px;
}
.gl-pipeline-job-width\! {
width: 100% !important;
- max-width: 400px !important;
}
.gl-downstream-pipeline-job-width {
width: 8rem;
+
+ .pipeline-graph-container & {
+ width: 100%;
+
+ @media (min-width: $breakpoint-sm) {
+ width: 8rem;
+ }
+ }
}
.gl-linked-pipeline-padding {
@@ -154,8 +160,8 @@
// Action Icons in big pipeline-graph nodes
&.ci-action-icon-wrapper {
- height: 30px;
- width: 30px;
+ height: 24px;
+ width: 24px;
border-radius: 100%;
display: block;
padding: 0;
@@ -242,3 +248,64 @@
}
}
}
+
+.pipeline-graph-container {
+ .stage-column.is-stage-view:not(:last-of-type)::after {
+ content: "";
+ position: absolute;
+ top: 100%;
+ left: $gl-spacing-scale-6;
+ width: 2px;
+ height: $gl-spacing-scale-5 * 2;
+ background-color: $gray-200;
+
+ @media (min-width: $breakpoint-sm) {
+ top: 1.25rem;
+ left: 100%;
+ width: $gl-spacing-scale-5 * 2;
+ height: 2px;
+ }
+ }
+
+ .stage-column,
+ .stage-column.is-stage-view {
+ @media (min-width: $breakpoint-sm) {
+ &:first-of-type {
+ margin-left: $gl-spacing-scale-6;
+ }
+ }
+ }
+
+ .linked-pipeline-container[aria-expanded=true] {
+ @media (max-width: $breakpoint-sm) {
+ width: 100%;
+
+ > div {
+ border-bottom-left-radius: 0;
+ }
+
+ > div > button {
+ border-bottom-right-radius: 0 !important;
+ }
+ }
+ }
+
+ .linked-pipelines-column,
+ .pipeline-show-container,
+ .pipeline-links-container {
+ @media (max-width: $breakpoint-sm) {
+ width: 100%;
+ }
+ }
+
+ .pipeline-graph {
+ @media (max-width: $breakpoint-sm) {
+ border-top-left-radius: 0;
+ border-top-right-radius: 0;
+ }
+ }
+
+ .pipeline-graph .pipeline-graph {
+ background-color: $gray-100;
+ }
+}
diff --git a/app/controllers/projects/group_links_controller.rb b/app/controllers/projects/group_links_controller.rb
index 60300f78bbb..5f8bf423219 100644
--- a/app/controllers/projects/group_links_controller.rb
+++ b/app/controllers/projects/group_links_controller.rb
@@ -9,30 +9,47 @@ class Projects::GroupLinksController < Projects::ApplicationController
feature_category :groups_and_projects
def update
- Projects::GroupLinks::UpdateService.new(group_link, current_user).execute(group_link_params)
+ result = Projects::GroupLinks::UpdateService.new(group_link, current_user).execute(group_link_params)
- if group_link.expires?
- render json: {
- expires_in: helpers.time_ago_with_tooltip(group_link.expires_at),
- expires_soon: group_link.expires_soon?
- }
- else
- render json: {}
+ if result.success?
+ if group_link.expires?
+ render json: {
+ expires_in: helpers.time_ago_with_tooltip(group_link.expires_at),
+ expires_soon: group_link.expires_soon?
+ }
+ else
+ render json: {}
+ end
+ elsif result.reason == :not_found
+ render json: { message: result.message }, status: :not_found
end
end
def destroy
- ::Projects::GroupLinks::DestroyService.new(project, current_user).execute(group_link)
-
- respond_to do |format|
- format.html do
- if can?(current_user, :admin_group, group_link.group)
- redirect_to group_path(group_link.group), status: :found
- elsif can?(current_user, :admin_project, group_link.project)
- redirect_to project_project_members_path(project), status: :found
+ result = ::Projects::GroupLinks::DestroyService.new(project, current_user).execute(group_link)
+
+ if result.success?
+ respond_to do |format|
+ format.html do
+ if can?(current_user, :admin_group, group_link.group)
+ redirect_to group_path(group_link.group), status: :found
+ elsif can?(current_user, :admin_project, group_link.project)
+ redirect_to project_project_members_path(project), status: :found
+ end
+ end
+ format.js { head :ok }
+ end
+ else
+ respond_to do |format|
+ format.html do
+ redirect_to project_project_members_path(project, tab: :groups), status: :found,
+ alert: _('The project-group link could not be removed.')
+ end
+
+ format.js do
+ render json: { message: result.message }, status: :not_found if result.reason == :not_found
end
end
- format.js { head :ok }
end
end
diff --git a/app/helpers/visibility_level_helper.rb b/app/helpers/visibility_level_helper.rb
index 68b15f7e042..cddfc48c649 100644
--- a/app/helpers/visibility_level_helper.rb
+++ b/app/helpers/visibility_level_helper.rb
@@ -76,16 +76,6 @@ module VisibilityLevelHelper
end
end
- def visibility_level_options(form_model)
- available_visibility_levels(form_model).map do |level|
- {
- level: level,
- label: visibility_level_label(level),
- description: visibility_level_description(level, form_model)
- }
- end
- end
-
def snippets_selected_visibility_level(visibility_levels, selected)
visibility_levels.find { |level| level == selected } || visibility_levels.min
end
diff --git a/app/models/ci/sources/pipeline.rb b/app/models/ci/sources/pipeline.rb
index 5b6946b04fd..475d57ee4c8 100644
--- a/app/models/ci/sources/pipeline.rb
+++ b/app/models/ci/sources/pipeline.rb
@@ -12,7 +12,7 @@ module Ci
:pipeline_id_convert_to_bigint, :source_pipeline_id_convert_to_bigint
], remove_with: '16.6', remove_after: '2023-10-22'
- columns_changing_default :partition_id
+ columns_changing_default :partition_id, :source_partition_id
self.table_name = "ci_sources_pipelines"
diff --git a/app/models/integration.rb b/app/models/integration.rb
index b4408301c6d..9b268e10cc6 100644
--- a/app/models/integration.rb
+++ b/app/models/integration.rb
@@ -237,6 +237,18 @@ class Integration < ApplicationRecord
end
private_class_method :boolean_accessor
+ def self.title
+ raise NotImplementedError
+ end
+
+ def self.description
+ raise NotImplementedError
+ end
+
+ def self.help
+ # no-op
+ end
+
def self.to_param
raise NotImplementedError
end
@@ -447,19 +459,18 @@ class Integration < ApplicationRecord
end
def title
- # implement inside child
+ self.class.title
end
def description
- # implement inside child
+ self.class.description
end
def help
- # implement inside child
+ self.class.help
end
def to_param
- # implement inside child
self.class.to_param
end
diff --git a/app/models/integrations/apple_app_store.rb b/app/models/integrations/apple_app_store.rb
index ef12fc6bf6f..f8fddf8a457 100644
--- a/app/models/integrations/apple_app_store.rb
+++ b/app/models/integrations/apple_app_store.rb
@@ -37,15 +37,15 @@ module Integrations
title: -> { s_('AppleAppStore|Protected branches and tags only') },
checkbox_label: -> { s_('AppleAppStore|Only set variables on protected branches and tags') }
- def title
+ def self.title
'Apple App Store Connect'
end
- def description
+ def self.description
s_('AppleAppStore|Use GitLab to build and release an app in the Apple App Store.')
end
- def help
+ def self.help
variable_list = [
'<code>APP_STORE_CONNECT_API_KEY_ISSUER_ID</code>',
'<code>APP_STORE_CONNECT_API_KEY_KEY_ID</code>',
diff --git a/app/models/integrations/asana.rb b/app/models/integrations/asana.rb
index 77555996cd9..39407acd6c9 100644
--- a/app/models/integrations/asana.rb
+++ b/app/models/integrations/asana.rb
@@ -20,15 +20,15 @@ module Integrations
title: -> { s_('Integrations|Restrict to branch (optional)') },
help: -> { s_('AsanaService|Comma-separated list of branches to be automatically inspected. Leave blank to include all branches.') }
- def title
+ def self.title
'Asana'
end
- def description
+ def self.description
s_('AsanaService|Add commit messages as comments to Asana tasks.')
end
- def help
+ def self.help
docs_link = ActionController::Base.helpers.link_to _('Learn more.'), Rails.application.routes.url_helpers.help_page_url('user/project/integrations/asana'), target: '_blank', rel: 'noopener noreferrer'
s_('Add commit messages as comments to Asana tasks. %{docs_link}').html_safe % { docs_link: docs_link.html_safe }
end
diff --git a/app/models/integrations/assembla.rb b/app/models/integrations/assembla.rb
index 1d3616b4c3b..bbdd0e183f2 100644
--- a/app/models/integrations/assembla.rb
+++ b/app/models/integrations/assembla.rb
@@ -15,11 +15,11 @@ module Integrations
exposes_secrets: true,
placeholder: ''
- def title
+ def self.title
'Assembla'
end
- def description
+ def self.description
_('Manage projects.')
end
diff --git a/app/models/integrations/bamboo.rb b/app/models/integrations/bamboo.rb
index 9f15532a0b0..9fe73f86be3 100644
--- a/app/models/integrations/bamboo.rb
+++ b/app/models/integrations/bamboo.rb
@@ -38,15 +38,15 @@ module Integrations
attr_accessor :response
- def title
+ def self.title
s_('BambooService|Atlassian Bamboo')
end
- def description
+ def self.description
s_('BambooService|Run CI/CD pipelines with Atlassian Bamboo.')
end
- def help
+ def self.help
docs_link = ActionController::Base.helpers.link_to(
_('Learn more.'),
Rails.application.routes.url_helpers.help_page_url('user/project/integrations/bamboo'),
diff --git a/app/models/integrations/base_chat_notification.rb b/app/models/integrations/base_chat_notification.rb
index b75801335bd..167bc210349 100644
--- a/app/models/integrations/base_chat_notification.rb
+++ b/app/models/integrations/base_chat_notification.rb
@@ -136,10 +136,6 @@ module Integrations
raise NotImplementedError
end
- def help
- raise NotImplementedError
- end
-
# With some integrations the webhook is already tied to a specific channel,
# for others the channels are configurable for each event.
def configurable_channels?
diff --git a/app/models/integrations/base_slack_notification.rb b/app/models/integrations/base_slack_notification.rb
index 09a0c9ba361..33dd9d9d387 100644
--- a/app/models/integrations/base_slack_notification.rb
+++ b/app/models/integrations/base_slack_notification.rb
@@ -36,7 +36,7 @@ module Integrations
true
end
- def help
+ def self.help
# noop
end
diff --git a/app/models/integrations/bugzilla.rb b/app/models/integrations/bugzilla.rb
index 74e282f6848..3ca348e42a1 100644
--- a/app/models/integrations/bugzilla.rb
+++ b/app/models/integrations/bugzilla.rb
@@ -6,15 +6,15 @@ module Integrations
validates :project_url, :issues_url, :new_issue_url, presence: true, public_url: true, if: :activated?
- def title
+ def self.title
'Bugzilla'
end
- def description
+ def self.description
s_("IssueTracker|Use Bugzilla as this project's issue tracker.")
end
- def help
+ def self.help
docs_link = ActionController::Base.helpers.link_to _('Learn more.'), Rails.application.routes.url_helpers.help_page_url('user/project/integrations/bugzilla'), target: '_blank', rel: 'noopener noreferrer'
s_("IssueTracker|Use Bugzilla as this project's issue tracker. %{docs_link}").html_safe % { docs_link: docs_link.html_safe }
end
diff --git a/app/models/integrations/buildkite.rb b/app/models/integrations/buildkite.rb
index 82a5142e8c2..aab0cdf2134 100644
--- a/app/models/integrations/buildkite.rb
+++ b/app/models/integrations/buildkite.rb
@@ -75,20 +75,20 @@ module Integrations
"#{project_url}/builds?commit=#{sha}"
end
- def title
+ def self.title
'Buildkite'
end
- def description
+ def self.description
'Run CI/CD pipelines with Buildkite.'
end
- def self.to_param
- 'buildkite'
+ def self.help
+ s_('ProjectService|Run CI/CD pipelines with Buildkite.')
end
- def help
- s_('ProjectService|Run CI/CD pipelines with Buildkite.')
+ def self.to_param
+ 'buildkite'
end
def calculate_reactive_cache(sha, ref)
diff --git a/app/models/integrations/campfire.rb b/app/models/integrations/campfire.rb
index 8b5797a9d24..18268ed18f4 100644
--- a/app/models/integrations/campfire.rb
+++ b/app/models/integrations/campfire.rb
@@ -36,15 +36,15 @@ module Integrations
placeholder: '123456',
help: -> { s_('CampfireService|From the end of the room URL.') }
- def title
+ def self.title
'Campfire'
end
- def description
+ def self.description
'Send notifications about push events to Campfire chat rooms.'
end
- def help
+ def self.help
docs_link = ActionController::Base.helpers.link_to(
_('Learn more.'),
Rails.application.routes.url_helpers.help_page_url('api/integrations', anchor: 'campfire'),
diff --git a/app/models/integrations/clickup.rb b/app/models/integrations/clickup.rb
index 7cc05d41e14..25287b53300 100644
--- a/app/models/integrations/clickup.rb
+++ b/app/models/integrations/clickup.rb
@@ -10,15 +10,15 @@ module Integrations
@reference_pattern ||= /((#|CU-)(?<issue>[a-z0-9]+)|(?<issue>[A-Z0-9_]{2,10}-\d+))\b/
end
- def title
+ def self.title
'ClickUp'
end
- def description
+ def self.description
s_("IssueTracker|Use Clickup as this project's issue tracker.")
end
- def help
+ def self.help
docs_link = ActionController::Base.helpers.link_to _('Learn more.'),
Rails.application.routes.url_helpers.help_page_url('user/project/integrations/clickup'),
target: '_blank',
diff --git a/app/models/integrations/confluence.rb b/app/models/integrations/confluence.rb
index eda8c37fc72..f97f1fd25c9 100644
--- a/app/models/integrations/confluence.rb
+++ b/app/models/integrations/confluence.rb
@@ -22,11 +22,11 @@ module Integrations
'confluence'
end
- def title
+ def self.title
s_('ConfluenceService|Confluence Workspace')
end
- def description
+ def self.description
s_('ConfluenceService|Link to a Confluence Workspace from the sidebar.')
end
diff --git a/app/models/integrations/custom_issue_tracker.rb b/app/models/integrations/custom_issue_tracker.rb
index 3770e813eaa..fe0d01d60bd 100644
--- a/app/models/integrations/custom_issue_tracker.rb
+++ b/app/models/integrations/custom_issue_tracker.rb
@@ -6,15 +6,15 @@ module Integrations
validates :project_url, :issues_url, :new_issue_url, presence: true, public_url: true, if: :activated?
- def title
+ def self.title
s_('IssueTracker|Custom issue tracker')
end
- def description
+ def self.description
s_("IssueTracker|Use a custom issue tracker as this project's issue tracker.")
end
- def help
+ def self.help
docs_link = ActionController::Base.helpers.link_to _('Learn more.'), Rails.application.routes.url_helpers.help_page_url('user/project/integrations/custom_issue_tracker'), target: '_blank', rel: 'noopener noreferrer'
s_('IssueTracker|Use a custom issue tracker that is not in the integration list. %{docs_link}').html_safe % { docs_link: docs_link.html_safe }
end
diff --git a/app/models/integrations/datadog.rb b/app/models/integrations/datadog.rb
index b1f1361afcd..5682fc2b139 100644
--- a/app/models/integrations/datadog.rb
+++ b/app/models/integrations/datadog.rb
@@ -117,15 +117,15 @@ module Integrations
# archive_trace is opt-in but we handle it with a more detailed field below
end
- def title
+ def self.title
'Datadog'
end
- def description
+ def self.description
s_('DatadogIntegration|Trace your GitLab pipelines with Datadog.')
end
- def help
+ def self.help
docs_link = ActionController::Base.helpers.link_to(
s_('DatadogIntegration|How do I set up this integration?'),
Rails.application.routes.url_helpers.help_page_url('integration/datadog'),
diff --git a/app/models/integrations/discord.rb b/app/models/integrations/discord.rb
index 33b2b52fa62..7ce597389f0 100644
--- a/app/models/integrations/discord.rb
+++ b/app/models/integrations/discord.rb
@@ -21,23 +21,23 @@ module Integrations
title: -> { s_('Integrations|Branches for which notifications are to be sent') },
choices: -> { branch_choices }
- def title
+ def self.title
s_("DiscordService|Discord Notifications")
end
- def description
+ def self.description
s_("DiscordService|Send notifications about project events to a Discord channel.")
end
- def self.to_param
- "discord"
- end
-
- def help
+ def self.help
docs_link = ActionController::Base.helpers.link_to _('How do I set up this service?'), Rails.application.routes.url_helpers.help_page_url('user/project/integrations/discord_notifications'), target: '_blank', rel: 'noopener noreferrer'
s_('Send notifications about project events to a Discord channel. %{docs_link}').html_safe % { docs_link: docs_link.html_safe }
end
+ def self.to_param
+ "discord"
+ end
+
def default_channel_placeholder
s_('DiscordService|Override the default webhook (e.g. https://discord.com/api/webhooks/…)')
end
diff --git a/app/models/integrations/drone_ci.rb b/app/models/integrations/drone_ci.rb
index f6a12c4bb1a..b59e504c98f 100644
--- a/app/models/integrations/drone_ci.rb
+++ b/app/models/integrations/drone_ci.rb
@@ -87,20 +87,20 @@ module Integrations
"gitlab/#{project.full_path}/redirect/commits/#{sha}?branch=#{Addressable::URI.encode_component(ref.to_s)}")
end
- def title
+ def self.title
'Drone'
end
- def description
+ def self.description
s_('ProjectService|Run CI/CD pipelines with Drone.')
end
- def self.to_param
- 'drone_ci'
+ def self.help
+ s_('ProjectService|Run CI/CD pipelines with Drone.')
end
- def help
- s_('ProjectService|Run CI/CD pipelines with Drone.')
+ def self.to_param
+ 'drone_ci'
end
override :hook_url
diff --git a/app/models/integrations/emails_on_push.rb b/app/models/integrations/emails_on_push.rb
index 144d1a07b04..77be8f5db45 100644
--- a/app/models/integrations/emails_on_push.rb
+++ b/app/models/integrations/emails_on_push.rb
@@ -39,11 +39,11 @@ module Integrations
recipients.split.grep(Devise.email_regexp).uniq(&:downcase)
end
- def title
+ def self.title
s_('EmailsOnPushService|Emails on push')
end
- def description
+ def self.description
s_('EmailsOnPushService|Email the commits and diff of each push to a list of recipients.')
end
diff --git a/app/models/integrations/ewm.rb b/app/models/integrations/ewm.rb
index 003c896704a..9d6f4c2a56c 100644
--- a/app/models/integrations/ewm.rb
+++ b/app/models/integrations/ewm.rb
@@ -10,15 +10,15 @@ module Integrations
@reference_pattern ||= %r{(?<issue>\b(bug|task|work item|workitem|rtcwi|defect)\b\s+\d+)}i
end
- def title
+ def self.title
'EWM'
end
- def description
+ def self.description
s_("IssueTracker|Use IBM Engineering Workflow Management as this project's issue tracker.")
end
- def help
+ def self.help
docs_link = ActionController::Base.helpers.link_to _('Learn more.'), Rails.application.routes.url_helpers.help_page_url('user/project/integrations/ewm'), target: '_blank', rel: 'noopener noreferrer'
s_("IssueTracker|Use IBM Engineering Workflow Management as this project's issue tracker. %{docs_link}").html_safe % { docs_link: docs_link.html_safe }
end
diff --git a/app/models/integrations/external_wiki.rb b/app/models/integrations/external_wiki.rb
index acacab2528e..7408f86d231 100644
--- a/app/models/integrations/external_wiki.rb
+++ b/app/models/integrations/external_wiki.rb
@@ -11,24 +11,24 @@ module Integrations
help: -> { s_('ExternalWikiService|Enter the URL to the external wiki.') },
required: true
- def title
+ def self.title
s_('ExternalWikiService|External wiki')
end
- def description
+ def self.description
s_('ExternalWikiService|Link to an external wiki from the sidebar.')
end
- def self.to_param
- 'external_wiki'
- end
-
- def help
+ def self.help
docs_link = ActionController::Base.helpers.link_to _('Learn more.'), Rails.application.routes.url_helpers.help_page_url('user/project/wiki/index', anchor: 'link-an-external-wiki'), target: '_blank', rel: 'noopener noreferrer'
s_('Link an external wiki from the project\'s sidebar. %{docs_link}').html_safe % { docs_link: docs_link.html_safe }
end
+ def self.to_param
+ 'external_wiki'
+ end
+
def sections
[
{
diff --git a/app/models/integrations/gitlab_slack_application.rb b/app/models/integrations/gitlab_slack_application.rb
index 2d520eaf7e7..d008a28a226 100644
--- a/app/models/integrations/gitlab_slack_application.rb
+++ b/app/models/integrations/gitlab_slack_application.rb
@@ -26,11 +26,11 @@ module Integrations
update(active: !!slack_integration)
end
- def title
+ def self.title
s_('Integrations|GitLab for Slack app')
end
- def description
+ def self.description
s_('Integrations|Enable slash commands and notifications for a Slack workspace.')
end
diff --git a/app/models/integrations/google_play.rb b/app/models/integrations/google_play.rb
index 5389e8dfa81..746f68fdc4c 100644
--- a/app/models/integrations/google_play.rb
+++ b/app/models/integrations/google_play.rb
@@ -32,15 +32,15 @@ module Integrations
title: -> { s_('GooglePlayStore|Protected branches and tags only') },
checkbox_label: -> { s_('GooglePlayStore|Only set variables on protected branches and tags') }
- def title
+ def self.title
s_('GooglePlay|Google Play')
end
- def description
+ def self.description
s_('GooglePlay|Use GitLab to build and release an app in Google Play.')
end
- def help
+ def self.help
variable_list = [
'<code>SUPPLY_PACKAGE_NAME</code>',
'<code>SUPPLY_JSON_KEY_DATA</code>'
diff --git a/app/models/integrations/hangouts_chat.rb b/app/models/integrations/hangouts_chat.rb
index 6e4753470a3..6a9d603e6e5 100644
--- a/app/models/integrations/hangouts_chat.rb
+++ b/app/models/integrations/hangouts_chat.rb
@@ -17,11 +17,11 @@ module Integrations
title: -> { s_('Integrations|Branches for which notifications are to be sent') },
choices: -> { branch_choices }
- def title
+ def self.title
'Google Chat'
end
- def description
+ def self.description
'Send notifications from GitLab to a room in Google Chat.'
end
@@ -29,7 +29,7 @@ module Integrations
'hangouts_chat'
end
- def help
+ def self.help
docs_link = ActionController::Base.helpers.link_to(_('How do I set up a Google Chat webhook?'),
Rails.application.routes.url_helpers.help_page_url('user/project/integrations/hangouts_chat'),
target: '_blank', rel: 'noopener noreferrer')
diff --git a/app/models/integrations/harbor.rb b/app/models/integrations/harbor.rb
index 559e48afd10..cc570e49e36 100644
--- a/app/models/integrations/harbor.rb
+++ b/app/models/integrations/harbor.rb
@@ -32,34 +32,32 @@ module Integrations
non_empty_password_help: -> { s_('HarborIntegration|Leave blank to use your current password.') },
required: true
- def title
+ def self.title
'Harbor'
end
- def description
+ def self.description
s_("HarborIntegration|Use Harbor as this project's container registry.")
end
- def help
+ def self.help
s_("HarborIntegration|After the Harbor integration is activated, global variables `$HARBOR_USERNAME`, `$HARBOR_HOST`, `$HARBOR_OCI`, `$HARBOR_PASSWORD`, `$HARBOR_URL` and `$HARBOR_PROJECT` will be created for CI/CD use.")
end
+ def self.to_param
+ name.demodulize.downcase
+ end
+
def hostname
Gitlab::Utils.parse_url(url).hostname
end
- class << self
- def to_param
- name.demodulize.downcase
- end
-
- def supported_events
- []
- end
+ def self.supported_events
+ []
+ end
- def supported_event_actions
- []
- end
+ def self.supported_event_actions
+ []
end
def test(*_args)
diff --git a/app/models/integrations/irker.rb b/app/models/integrations/irker.rb
index a54946f074a..a1ce0877957 100644
--- a/app/models/integrations/irker.rb
+++ b/app/models/integrations/irker.rb
@@ -53,14 +53,31 @@ module Integrations
# in the UI or API.
prop_accessor :channels
- def title
+ def self.title
s_('IrkerService|irker (IRC gateway)')
end
- def description
+ def self.description
s_('IrkerService|Send update messages to an irker server.')
end
+ def self.help
+ docs_link = ActionController::Base.helpers.link_to(
+ _('Learn more.'),
+ Rails.application.routes.url_helpers.help_page_url(
+ 'user/project/integrations/irker',
+ anchor: 'set-up-an-irker-daemon'
+ ),
+ target: '_blank',
+ rel: 'noopener noreferrer'
+ )
+
+ format(s_(
+ 'IrkerService|Send update messages to an irker server. ' \
+ 'Before you can use this, you need to set up the irker daemon. %{docs_link}'
+ ).html_safe, docs_link: docs_link.html_safe)
+ end
+
def self.to_param
'irker'
end
@@ -85,23 +102,6 @@ module Integrations
}
end
- def help
- docs_link = ActionController::Base.helpers.link_to(
- _('Learn more.'),
- Rails.application.routes.url_helpers.help_page_url(
- 'user/project/integrations/irker',
- anchor: 'set-up-an-irker-daemon'
- ),
- target: '_blank',
- rel: 'noopener noreferrer'
- )
-
- format(s_(
- 'IrkerService|Send update messages to an irker server. ' \
- 'Before you can use this, you need to set up the irker daemon. %{docs_link}'
- ).html_safe, docs_link: docs_link.html_safe)
- end
-
private
def get_channels
diff --git a/app/models/integrations/jenkins.rb b/app/models/integrations/jenkins.rb
index 0683c8408bc..a2f5667eaee 100644
--- a/app/models/integrations/jenkins.rb
+++ b/app/models/integrations/jenkins.rb
@@ -69,15 +69,15 @@ module Integrations
%w[push merge_request tag_push]
end
- def title
+ def self.title
'Jenkins'
end
- def description
+ def self.description
s_('Run CI/CD pipelines with Jenkins.')
end
- def help
+ def self.help
docs_link = ActionController::Base.helpers.link_to _('Learn more.'), Rails.application.routes.url_helpers.help_page_url('integration/jenkins'), target: '_blank', rel: 'noopener noreferrer'
s_('Run CI/CD pipelines with Jenkins when you push to a repository, or when a merge request is created, updated, or merged. %{docs_link}').html_safe % { docs_link: docs_link.html_safe }
end
diff --git a/app/models/integrations/jira.rb b/app/models/integrations/jira.rb
index f6e99454cb1..37735b956fc 100644
--- a/app/models/integrations/jira.rb
+++ b/app/models/integrations/jira.rb
@@ -191,9 +191,17 @@ module Integrations
end
end
- def help
+ def self.title
+ 'Jira'
+ end
+
+ def self.description
+ s_("JiraService|Use Jira as this project's issue tracker.")
+ end
+
+ def self.help
jira_doc_link_start = format('<a href="%{url}" target="_blank" rel="noopener noreferrer">'.html_safe,
- url: help_page_path('integration/jira/index'))
+ url: Gitlab::Routing.url_helpers.help_page_path('integration/jira/index'))
format(
s_("JiraService|You must configure Jira before enabling this integration. " \
"%{jira_doc_link_start}Learn more.%{link_end}"),
@@ -201,14 +209,6 @@ module Integrations
link_end: '</a>'.html_safe)
end
- def title
- 'Jira'
- end
-
- def description
- s_("JiraService|Use Jira as this project's issue tracker.")
- end
-
def self.to_param
'jira'
end
diff --git a/app/models/integrations/mattermost.rb b/app/models/integrations/mattermost.rb
index 7e391b11d82..361ff4afce8 100644
--- a/app/models/integrations/mattermost.rb
+++ b/app/models/integrations/mattermost.rb
@@ -5,11 +5,11 @@ module Integrations
include SlackMattermostNotifier
include SlackMattermostFields
- def title
+ def self.title
_('Mattermost notifications')
end
- def description
+ def self.description
s_('Send notifications about project events to Mattermost channels.')
end
@@ -17,7 +17,7 @@ module Integrations
'mattermost'
end
- def help
+ def self.help
docs_link = ActionController::Base.helpers.link_to _('Learn more.'), Rails.application.routes.url_helpers.help_page_url('user/project/integrations/mattermost'), target: '_blank', rel: 'noopener noreferrer'
s_('Send notifications about project events to Mattermost channels. %{docs_link}').html_safe % { docs_link: docs_link.html_safe }
end
diff --git a/app/models/integrations/mattermost_slash_commands.rb b/app/models/integrations/mattermost_slash_commands.rb
index 73cddd163e0..9554dec4168 100644
--- a/app/models/integrations/mattermost_slash_commands.rb
+++ b/app/models/integrations/mattermost_slash_commands.rb
@@ -14,11 +14,11 @@ module Integrations
false
end
- def title
+ def self.title
s_('Integrations|Mattermost slash commands')
end
- def description
+ def self.description
s_('Integrations|Perform common tasks with slash commands.')
end
diff --git a/app/models/integrations/microsoft_teams.rb b/app/models/integrations/microsoft_teams.rb
index 208172d6303..3a7c848d411 100644
--- a/app/models/integrations/microsoft_teams.rb
+++ b/app/models/integrations/microsoft_teams.rb
@@ -18,11 +18,11 @@ module Integrations
title: -> { s_('Integrations|Branches for which notifications are to be sent') },
choices: -> { branch_choices }
- def title
+ def self.title
'Microsoft Teams notifications'
end
- def description
+ def self.description
'Send notifications about project events to Microsoft Teams.'
end
@@ -30,7 +30,7 @@ module Integrations
'microsoft_teams'
end
- def help
+ def self.help
'<p>Use this service to send notifications about events in GitLab projects to your Microsoft Teams channels. <a href="https://docs.gitlab.com/ee/user/project/integrations/microsoft_teams.html" target="_blank" rel="noopener noreferrer">How do I configure this integration?</a></p>'
end
diff --git a/app/models/integrations/mock_ci.rb b/app/models/integrations/mock_ci.rb
index 2d8e26d409f..9c129ca727c 100644
--- a/app/models/integrations/mock_ci.rb
+++ b/app/models/integrations/mock_ci.rb
@@ -14,11 +14,11 @@ module Integrations
validates :mock_service_url, presence: true, public_url: true, if: :activated?
- def title
+ def self.title
'MockCI'
end
- def description
+ def self.description
'Mock an external CI'
end
diff --git a/app/models/integrations/mock_monitoring.rb b/app/models/integrations/mock_monitoring.rb
index 72bb292edaa..9e474078b28 100644
--- a/app/models/integrations/mock_monitoring.rb
+++ b/app/models/integrations/mock_monitoring.rb
@@ -2,11 +2,11 @@
module Integrations
class MockMonitoring < BaseMonitoring
- def title
+ def self.title
'Mock monitoring'
end
- def description
+ def self.description
'Mock monitoring service'
end
diff --git a/app/models/integrations/packagist.rb b/app/models/integrations/packagist.rb
index c0acb6c87b4..f027afe0381 100644
--- a/app/models/integrations/packagist.rb
+++ b/app/models/integrations/packagist.rb
@@ -29,11 +29,11 @@ module Integrations
validates :username, presence: true, if: :activated?
validates :token, presence: true, if: :activated?
- def title
+ def self.title
'Packagist'
end
- def description
+ def self.description
s_('Integrations|Keep your PHP dependencies updated on Packagist.')
end
diff --git a/app/models/integrations/pipelines_email.rb b/app/models/integrations/pipelines_email.rb
index 01efbc3e4a4..c7a93d48825 100644
--- a/app/models/integrations/pipelines_email.rb
+++ b/app/models/integrations/pipelines_email.rb
@@ -44,11 +44,11 @@ module Integrations
end
end
- def title
+ def self.title
_('Pipeline status emails')
end
- def description
+ def self.description
_('Email the pipeline status to a list of recipients.')
end
diff --git a/app/models/integrations/pivotaltracker.rb b/app/models/integrations/pivotaltracker.rb
index b3cbc988dd6..97e6e3e09d1 100644
--- a/app/models/integrations/pivotaltracker.rb
+++ b/app/models/integrations/pivotaltracker.rb
@@ -20,15 +20,15 @@ module Integrations
'automatically inspect. Leave blank to include all branches.')
end
- def title
+ def self.title
'Pivotal Tracker'
end
- def description
+ def self.description
s_('PivotalTrackerService|Add commit messages as comments to Pivotal Tracker stories.')
end
- def help
+ def self.help
docs_link = ActionController::Base.helpers.link_to _('Learn more.'), Rails.application.routes.url_helpers.help_page_url('user/project/integrations/pivotal_tracker'), target: '_blank', rel: 'noopener noreferrer'
s_('Add commit messages as comments to Pivotal Tracker stories. %{docs_link}').html_safe % { docs_link: docs_link.html_safe }
end
diff --git a/app/models/integrations/prometheus.rb b/app/models/integrations/prometheus.rb
index ff8d07a1b4c..de923bbbdd5 100644
--- a/app/models/integrations/prometheus.rb
+++ b/app/models/integrations/prometheus.rb
@@ -51,11 +51,11 @@ module Integrations
false
end
- def title
+ def self.title
'Prometheus'
end
- def description
+ def self.description
s_('PrometheusService|Monitor application health with Prometheus metrics and dashboards')
end
diff --git a/app/models/integrations/pumble.rb b/app/models/integrations/pumble.rb
index 09e011023ed..36ff5189b0f 100644
--- a/app/models/integrations/pumble.rb
+++ b/app/models/integrations/pumble.rb
@@ -18,11 +18,11 @@ module Integrations
title: -> { s_('Integrations|Branches for which notifications are to be sent') },
choices: -> { branch_choices }
- def title
+ def self.title
'Pumble'
end
- def description
+ def self.description
s_("PumbleIntegration|Send notifications about project events to Pumble.")
end
@@ -30,7 +30,7 @@ module Integrations
'pumble'
end
- def help
+ def self.help
docs_link = ActionController::Base.helpers.link_to(
_('Learn more.'),
Rails.application.routes.url_helpers.help_page_url('user/project/integrations/pumble'),
diff --git a/app/models/integrations/pushover.rb b/app/models/integrations/pushover.rb
index 2feae29f627..b2c4e06e71f 100644
--- a/app/models/integrations/pushover.rb
+++ b/app/models/integrations/pushover.rb
@@ -71,11 +71,11 @@ module Integrations
]
end
- def title
+ def self.title
'Pushover'
end
- def description
+ def self.description
s_('PushoverService|Get real-time notifications on your device.')
end
diff --git a/app/models/integrations/redmine.rb b/app/models/integrations/redmine.rb
index bc2a64b0848..11eda7c69f7 100644
--- a/app/models/integrations/redmine.rb
+++ b/app/models/integrations/redmine.rb
@@ -6,15 +6,15 @@ module Integrations
validates :project_url, :issues_url, :new_issue_url, presence: true, public_url: true, if: :activated?
- def title
+ def self.title
'Redmine'
end
- def description
+ def self.description
s_("IssueTracker|Use Redmine as this project's issue tracker.")
end
- def help
+ def self.help
docs_link = ActionController::Base.helpers.link_to _('Learn more.'), Rails.application.routes.url_helpers.help_page_url('user/project/integrations/redmine'), target: '_blank', rel: 'noopener noreferrer'
s_('IssueTracker|Use Redmine as the issue tracker. %{docs_link}').html_safe % { docs_link: docs_link.html_safe }
end
diff --git a/app/models/integrations/shimo.rb b/app/models/integrations/shimo.rb
index 227fdca5c91..1d004356469 100644
--- a/app/models/integrations/shimo.rb
+++ b/app/models/integrations/shimo.rb
@@ -16,11 +16,11 @@ module Integrations
valid? && activated?
end
- def title
+ def self.title
s_('Shimo|Shimo')
end
- def description
+ def self.description
s_('Shimo|Link to a Shimo Workspace from the sidebar.')
end
diff --git a/app/models/integrations/slack.rb b/app/models/integrations/slack.rb
index f70376e2f0d..9f9614a84fd 100644
--- a/app/models/integrations/slack.rb
+++ b/app/models/integrations/slack.rb
@@ -5,11 +5,11 @@ module Integrations
include SlackMattermostNotifier
include SlackMattermostFields
- def title
+ def self.title
'Slack notifications'
end
- def description
+ def self.description
'Send notifications about project events to Slack.'
end
diff --git a/app/models/integrations/slack_slash_commands.rb b/app/models/integrations/slack_slash_commands.rb
index b209f37ee7c..c5ea6f22951 100644
--- a/app/models/integrations/slack_slash_commands.rb
+++ b/app/models/integrations/slack_slash_commands.rb
@@ -10,11 +10,11 @@ module Integrations
non_empty_password_help: -> { s_('ProjectService|Leave blank to use your current token.') },
placeholder: ''
- def title
+ def self.title
'Slack slash commands'
end
- def description
+ def self.description
"Perform common operations in Slack."
end
diff --git a/app/models/integrations/squash_tm.rb b/app/models/integrations/squash_tm.rb
index bf3f391564f..1b4ab152b1d 100644
--- a/app/models/integrations/squash_tm.rb
+++ b/app/models/integrations/squash_tm.rb
@@ -22,15 +22,15 @@ module Integrations
validates :token, length: { maximum: 255 }, allow_blank: true
end
- def title
+ def self.title
'Squash TM'
end
- def description
+ def self.description
s_("SquashTmIntegration|Update Squash TM requirements when GitLab issues are modified.")
end
- def help
+ def self.help
docs_link = ActionController::Base.helpers.link_to(
_('Learn more.'),
Rails.application.routes.url_helpers.help_page_url('user/project/integrations/squash_tm'),
diff --git a/app/models/integrations/teamcity.rb b/app/models/integrations/teamcity.rb
index 575c3b8a334..913242ef9ac 100644
--- a/app/models/integrations/teamcity.rb
+++ b/app/models/integrations/teamcity.rb
@@ -47,15 +47,15 @@ module Integrations
end
end
- def title
+ def self.title
'JetBrains TeamCity'
end
- def description
+ def self.description
s_('ProjectService|Run CI/CD pipelines with JetBrains TeamCity.')
end
- def help
+ def self.help
s_('To run CI/CD pipelines with JetBrains TeamCity, input the GitLab project details in the TeamCity project Version Control Settings.')
end
diff --git a/app/models/integrations/telegram.rb b/app/models/integrations/telegram.rb
index 71fe6f8d6ef..8eb1a7ad0ea 100644
--- a/app/models/integrations/telegram.rb
+++ b/app/models/integrations/telegram.rb
@@ -38,11 +38,11 @@ module Integrations
before_validation :set_webhook
- def title
+ def self.title
'Telegram'
end
- def description
+ def self.description
s_("TelegramIntegration|Send notifications about project events to Telegram.")
end
@@ -50,7 +50,7 @@ module Integrations
'telegram'
end
- def help
+ def self.help
docs_link = ActionController::Base.helpers.link_to(
_('Learn more.'),
Rails.application.routes.url_helpers.help_page_url('user/project/integrations/telegram'),
diff --git a/app/models/integrations/unify_circuit.rb b/app/models/integrations/unify_circuit.rb
index 3b4bcfa28d3..6ee95c1173b 100644
--- a/app/models/integrations/unify_circuit.rb
+++ b/app/models/integrations/unify_circuit.rb
@@ -17,11 +17,11 @@ module Integrations
title: -> { s_('Integrations|Branches for which notifications are to be sent') },
choices: -> { branch_choices }
- def title
+ def self.title
'Unify Circuit'
end
- def description
+ def self.description
s_('Integrations|Send notifications about project events to Unify Circuit.')
end
@@ -29,7 +29,7 @@ module Integrations
'unify_circuit'
end
- def help
+ def self.help
docs_link = ActionController::Base.helpers.link_to _('How do I set up this service?'), Rails.application.routes.url_helpers.help_page_url('user/project/integrations/unify_circuit'), target: '_blank', rel: 'noopener noreferrer'
s_('Integrations|Send notifications about project events to a Unify Circuit conversation. %{docs_link}').html_safe % { docs_link: docs_link.html_safe }
end
diff --git a/app/models/integrations/webex_teams.rb b/app/models/integrations/webex_teams.rb
index 3ef8ab39352..5f8cc195544 100644
--- a/app/models/integrations/webex_teams.rb
+++ b/app/models/integrations/webex_teams.rb
@@ -17,11 +17,11 @@ module Integrations
title: -> { s_('Integrations|Branches for which notifications are to be sent') },
choices: -> { branch_choices }
- def title
+ def self.title
s_("WebexTeamsService|Webex Teams")
end
- def description
+ def self.description
s_("WebexTeamsService|Send notifications about project events to Webex Teams.")
end
@@ -29,7 +29,7 @@ module Integrations
'webex_teams'
end
- def help
+ def self.help
docs_link = ActionController::Base.helpers.link_to _('Learn more.'), Rails.application.routes.url_helpers.help_page_url('user/project/integrations/webex_teams'), target: '_blank', rel: 'noopener noreferrer'
s_("WebexTeamsService|Send notifications about project events to a Webex Teams conversation. %{docs_link}") % { docs_link: docs_link.html_safe }
end
diff --git a/app/models/integrations/youtrack.rb b/app/models/integrations/youtrack.rb
index 15246a37aa7..932e588a829 100644
--- a/app/models/integrations/youtrack.rb
+++ b/app/models/integrations/youtrack.rb
@@ -14,15 +14,15 @@ module Integrations
@reference_pattern = /(?<issue>\b[A-Za-z][A-Za-z0-9_]*-\d+\b)#{regex_suffix if only_long}/
end
- def title
+ def self.title
'YouTrack'
end
- def description
+ def self.description
s_("IssueTracker|Use YouTrack as this project's issue tracker.")
end
- def help
+ def self.help
docs_link = ActionController::Base.helpers.link_to _('Learn more.'), Rails.application.routes.url_helpers.help_page_url('user/project/integrations/youtrack'), target: '_blank', rel: 'noopener noreferrer'
s_("IssueTracker|Use YouTrack as this project's issue tracker. %{docs_link}").html_safe % { docs_link: docs_link.html_safe }
end
diff --git a/app/models/integrations/zentao.rb b/app/models/integrations/zentao.rb
index 58ec4abf30c..2aec0c1e871 100644
--- a/app/models/integrations/zentao.rb
+++ b/app/models/integrations/zentao.rb
@@ -57,18 +57,18 @@ module Integrations
data_fields.api_url ||= issues_tracker['api_url']
end
- def title
+ def self.title
'ZenTao'
end
- def description
+ def self.description
s_("ZentaoIntegration|Use ZenTao as this project's issue tracker.")
end
- def help
+ def self.help
s_("ZentaoIntegration|Before you enable this integration, you must configure ZenTao. For more details, read the %{link_start}ZenTao integration documentation%{link_end}.") % {
link_start: '<a href="%{url}" target="_blank" rel="noopener noreferrer">'
- .html_safe % { url: help_page_url('user/project/integrations/zentao') },
+ .html_safe % { url: Rails.application.routes.url_helpers.help_page_url('user/project/integrations/zentao') },
link_end: '</a>'.html_safe
}
end
diff --git a/app/serializers/merge_request_noteable_entity.rb b/app/serializers/merge_request_noteable_entity.rb
index aac90c20b53..44f51f43998 100644
--- a/app/serializers/merge_request_noteable_entity.rb
+++ b/app/serializers/merge_request_noteable_entity.rb
@@ -49,10 +49,6 @@ class MergeRequestNoteableEntity < IssuableEntity
expose :can_update do |merge_request|
can?(current_user, :update_merge_request, merge_request)
end
-
- expose :can_approve do |merge_request|
- merge_request.eligible_for_approval_by?(current_user)
- end
end
expose :locked_discussion_docs_path, if: -> (merge_request) { merge_request.discussion_locked? } do |merge_request|
diff --git a/app/services/projects/group_links/destroy_service.rb b/app/services/projects/group_links/destroy_service.rb
index a2307bfebf0..e0218ae087e 100644
--- a/app/services/projects/group_links/destroy_service.rb
+++ b/app/services/projects/group_links/destroy_service.rb
@@ -3,8 +3,10 @@
module Projects
module GroupLinks
class DestroyService < BaseService
- def execute(group_link)
- return false unless group_link
+ def execute(group_link, skip_authorization: false)
+ unless valid_to_destroy?(group_link, skip_authorization)
+ return ServiceResponse.error(message: 'Not found', reason: :not_found)
+ end
if group_link.project.private?
TodosDestroyer::ProjectPrivateWorker.perform_in(Todo::WAIT_FOR_DELETE, project.id)
@@ -12,20 +14,29 @@ module Projects
TodosDestroyer::ConfidentialIssueWorker.perform_in(Todo::WAIT_FOR_DELETE, nil, project.id)
end
- group_link.destroy.tap do |link|
- refresh_project_authorizations_asynchronously(link.project)
+ link = group_link.destroy
- # Until we compare the inconsistency rates of the new specialized worker and
- # the old approach, we still run AuthorizedProjectsWorker
- # but with some delay and lower urgency as a safety net.
- link.group.refresh_members_authorized_projects(
- priority: UserProjectAccessChangedService::LOW_PRIORITY
- )
- end
+ refresh_project_authorizations_asynchronously(link.project)
+
+ # Until we compare the inconsistency rates of the new specialized worker and
+ # the old approach, we still run AuthorizedProjectsWorker
+ # but with some delay and lower urgency as a safety net.
+ link.group.refresh_members_authorized_projects(
+ priority: UserProjectAccessChangedService::LOW_PRIORITY
+ )
+
+ ServiceResponse.success(payload: { link: link })
end
private
+ def valid_to_destroy?(group_link, skip_authorization)
+ return false unless group_link
+ return true if skip_authorization
+
+ current_user.can?(:admin_project_group_link, group_link)
+ end
+
def refresh_project_authorizations_asynchronously(project)
AuthorizedProjectUpdate::ProjectRecalculateWorker.perform_async(project.id)
end
diff --git a/app/services/projects/group_links/update_service.rb b/app/services/projects/group_links/update_service.rb
index 9b2565adaca..04f1552d929 100644
--- a/app/services/projects/group_links/update_service.rb
+++ b/app/services/projects/group_links/update_service.rb
@@ -10,15 +10,23 @@ module Projects
end
def execute(group_link_params)
+ return ServiceResponse.error(message: 'Not found', reason: :not_found) unless allowed_to_update?
+
group_link.update!(group_link_params)
refresh_authorizations if requires_authorization_refresh?(group_link_params)
+
+ ServiceResponse.success
end
private
attr_reader :group_link
+ def allowed_to_update?
+ current_user.can?(:admin_project_member, project)
+ end
+
def refresh_authorizations
AuthorizedProjectUpdate::ProjectRecalculateWorker.perform_async(project.id)
diff --git a/app/workers/remove_expired_group_links_worker.rb b/app/workers/remove_expired_group_links_worker.rb
index f1da5f37945..0bac595f0c4 100644
--- a/app/workers/remove_expired_group_links_worker.rb
+++ b/app/workers/remove_expired_group_links_worker.rb
@@ -11,7 +11,7 @@ class RemoveExpiredGroupLinksWorker # rubocop:disable Scalability/IdempotentWork
def perform
ProjectGroupLink.expired.find_each do |link|
- Projects::GroupLinks::DestroyService.new(link.project, nil).execute(link)
+ Projects::GroupLinks::DestroyService.new(link.project, nil).execute(link, skip_authorization: true)
end
GroupGroupLink.expired.find_in_batches do |link_batch|