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>2021-10-11 09:13:09 +0300
committerGitLab Bot <gitlab-bot@gitlab.com>2021-10-11 09:13:09 +0300
commitbe7d70b884e6fa66c52862f38bf0f39b0631868b (patch)
tree235616671718bf2f39855f663677b61a55a8d68c /app
parent848ba57883b4ea9164bcb56a16c0fcb2b55b56e6 (diff)
Add latest changes from gitlab-org/gitlab@master
Diffstat (limited to 'app')
-rw-r--r--app/assets/javascripts/behaviors/shortcuts/keybindings.js7
-rw-r--r--app/assets/javascripts/behaviors/shortcuts/shortcuts_navigation.js14
-rw-r--r--app/assets/javascripts/lib/utils/url_utility.js27
-rw-r--r--app/assets/javascripts/repository/components/breadcrumbs.vue45
-rw-r--r--app/assets/javascripts/repository/components/new_directory_modal.vue183
-rw-r--r--app/assets/javascripts/repository/constants.js3
-rw-r--r--app/assets/javascripts/repository/index.js1
-rw-r--r--app/assets/javascripts/repository/router.js21
-rw-r--r--app/assets/javascripts/vue_merge_request_widget/components/mr_widget_header.vue12
-rw-r--r--app/controllers/projects/tree_controller.rb1
-rw-r--r--app/controllers/projects_controller.rb1
-rw-r--r--app/graphql/resolvers/project_pipelines_resolver.rb2
-rw-r--r--app/models/merge_request.rb12
-rw-r--r--app/services/merge_requests/mergeability/check_base_service.rb36
-rw-r--r--app/services/merge_requests/mergeability/check_ci_status_service.rb22
-rw-r--r--app/services/merge_requests/mergeability/run_checks_service.rb54
-rw-r--r--app/services/merge_requests/update_service.rb2
-rw-r--r--app/views/layouts/_startup_js.html.haml1
-rw-r--r--app/views/projects/_files.html.haml3
-rw-r--r--app/views/projects/blob/show.html.haml1
-rw-r--r--app/views/projects/merge_requests/_widget.html.haml2
-rw-r--r--app/views/projects/merge_requests/show.html.haml1
-rw-r--r--app/views/projects/tree/show.html.haml1
-rw-r--r--app/views/shared/_web_ide_path.html.haml4
24 files changed, 435 insertions, 21 deletions
diff --git a/app/assets/javascripts/behaviors/shortcuts/keybindings.js b/app/assets/javascripts/behaviors/shortcuts/keybindings.js
index ebf2ab0381e..b27dccabdf8 100644
--- a/app/assets/javascripts/behaviors/shortcuts/keybindings.js
+++ b/app/assets/javascripts/behaviors/shortcuts/keybindings.js
@@ -306,6 +306,12 @@ export const GO_TO_PROJECT_WIKI = {
defaultKeys: ['g w'], // eslint-disable-line @gitlab/require-i18n-strings
};
+export const GO_TO_PROJECT_WEBIDE = {
+ id: 'project.goToWebIDE',
+ description: __('Open in Web IDE'),
+ defaultKeys: ['.'],
+};
+
export const PROJECT_FILES_MOVE_SELECTION_UP = {
id: 'projectFiles.moveSelectionUp',
description: __('Move selection up'),
@@ -549,6 +555,7 @@ export const PROJECT_SHORTCUTS_GROUP = {
GO_TO_PROJECT_KUBERNETES,
GO_TO_PROJECT_SNIPPETS,
GO_TO_PROJECT_WIKI,
+ GO_TO_PROJECT_WEBIDE,
],
};
diff --git a/app/assets/javascripts/behaviors/shortcuts/shortcuts_navigation.js b/app/assets/javascripts/behaviors/shortcuts/shortcuts_navigation.js
index b188d3b0ec3..7d8e4dd490c 100644
--- a/app/assets/javascripts/behaviors/shortcuts/shortcuts_navigation.js
+++ b/app/assets/javascripts/behaviors/shortcuts/shortcuts_navigation.js
@@ -1,4 +1,5 @@
import Mousetrap from 'mousetrap';
+import { visitUrl, constructWebIDEPath } from '~/lib/utils/url_utility';
import findAndFollowLink from '../../lib/utils/navigation_utility';
import {
keysFor,
@@ -18,6 +19,7 @@ import {
GO_TO_PROJECT_KUBERNETES,
GO_TO_PROJECT_ENVIRONMENTS,
GO_TO_PROJECT_METRICS,
+ GO_TO_PROJECT_WEBIDE,
NEW_ISSUE,
} from './keybindings';
import Shortcuts from './shortcuts';
@@ -58,6 +60,18 @@ export default class ShortcutsNavigation extends Shortcuts {
findAndFollowLink('.shortcuts-environments'),
);
Mousetrap.bind(keysFor(GO_TO_PROJECT_METRICS), () => findAndFollowLink('.shortcuts-metrics'));
+ Mousetrap.bind(keysFor(GO_TO_PROJECT_WEBIDE), ShortcutsNavigation.navigateToWebIDE);
Mousetrap.bind(keysFor(NEW_ISSUE), () => findAndFollowLink('.shortcuts-new-issue'));
}
+
+ static navigateToWebIDE() {
+ const path = constructWebIDEPath({
+ sourceProjectFullPath: window.gl.mrWidgetData?.source_project_full_path,
+ targetProjectFullPath: window.gl.mrWidgetData?.target_project_full_path,
+ iid: window.gl.mrWidgetData?.iid,
+ });
+ if (path) {
+ visitUrl(path);
+ }
+ }
}
diff --git a/app/assets/javascripts/lib/utils/url_utility.js b/app/assets/javascripts/lib/utils/url_utility.js
index 6580a028e4a..1c22d21a313 100644
--- a/app/assets/javascripts/lib/utils/url_utility.js
+++ b/app/assets/javascripts/lib/utils/url_utility.js
@@ -590,3 +590,30 @@ export function isSameOriginUrl(url) {
return false;
}
}
+
+/**
+ * Returns a URL to WebIDE considering the current user's position in
+ * repository's tree. If not MR `iid` has been passed, the URL is fetched
+ * from the global `gl.webIDEPath`.
+ *
+ * @param sourceProjectFullPath Source project's full path. Used in MRs
+ * @param targetProjectFullPath Target project's full path. Used in MRs
+ * @param iid MR iid
+ * @returns {string}
+ */
+
+export function constructWebIDEPath({
+ sourceProjectFullPath,
+ targetProjectFullPath = '',
+ iid,
+} = {}) {
+ if (!iid || !sourceProjectFullPath) {
+ return window.gl?.webIDEPath;
+ }
+ return mergeUrlParams(
+ {
+ target_project: sourceProjectFullPath !== targetProjectFullPath ? targetProjectFullPath : '',
+ },
+ webIDEUrl(`/${sourceProjectFullPath}/merge_requests/${iid}`),
+ );
+}
diff --git a/app/assets/javascripts/repository/components/breadcrumbs.vue b/app/assets/javascripts/repository/components/breadcrumbs.vue
index db84e2b5912..d3717f10ec7 100644
--- a/app/assets/javascripts/repository/components/breadcrumbs.vue
+++ b/app/assets/javascripts/repository/components/breadcrumbs.vue
@@ -9,11 +9,13 @@ import {
} from '@gitlab/ui';
import permissionsQuery from 'shared_queries/repository/permissions.query.graphql';
import { joinPaths, escapeFileUrl } from '~/lib/utils/url_utility';
+import glFeatureFlagsMixin from '~/vue_shared/mixins/gl_feature_flags_mixin';
import { __ } from '../../locale';
import getRefMixin from '../mixins/get_ref';
import projectPathQuery from '../queries/project_path.query.graphql';
import projectShortPathQuery from '../queries/project_short_path.query.graphql';
import UploadBlobModal from './upload_blob_modal.vue';
+import NewDirectoryModal from './new_directory_modal.vue';
const ROW_TYPES = {
header: 'header',
@@ -21,6 +23,7 @@ const ROW_TYPES = {
};
const UPLOAD_BLOB_MODAL_ID = 'modal-upload-blob';
+const NEW_DIRECTORY_MODAL_ID = 'modal-new-directory';
export default {
components: {
@@ -30,6 +33,7 @@ export default {
GlDropdownItem,
GlIcon,
UploadBlobModal,
+ NewDirectoryModal,
},
apollo: {
projectShortPath: {
@@ -54,7 +58,7 @@ export default {
directives: {
GlModal: GlModalDirective,
},
- mixins: [getRefMixin],
+ mixins: [getRefMixin, glFeatureFlagsMixin()],
props: {
currentPath: {
type: String,
@@ -121,8 +125,14 @@ export default {
required: false,
default: '',
},
+ newDirPath: {
+ type: String,
+ required: false,
+ default: '',
+ },
},
uploadBlobModalId: UPLOAD_BLOB_MODAL_ID,
+ newDirectoryModalId: NEW_DIRECTORY_MODAL_ID,
data() {
return {
projectShortPath: '',
@@ -160,6 +170,13 @@ export default {
showUploadModal() {
return this.canEditTree && !this.$apollo.queries.userPermissions.loading;
},
+ showNewDirectoryModal() {
+ return (
+ this.glFeatures.newDirModal &&
+ this.canEditTree &&
+ !this.$apollo.queries.userPermissions.loading
+ );
+ },
dropdownItems() {
const items = [];
@@ -185,15 +202,26 @@ export default {
text: __('Upload file'),
modalId: UPLOAD_BLOB_MODAL_ID,
},
- {
+ );
+
+ if (this.glFeatures.newDirModal) {
+ items.push({
+ attrs: {
+ href: '#modal-create-new-dir',
+ },
+ text: __('New directory'),
+ modalId: NEW_DIRECTORY_MODAL_ID,
+ });
+ } else {
+ items.push({
attrs: {
href: '#modal-create-new-dir',
'data-target': '#modal-create-new-dir',
'data-toggle': 'modal',
},
text: __('New directory'),
- },
- );
+ });
+ }
} else if (this.canCreateMrFromFork) {
items.push(
{
@@ -306,5 +334,14 @@ export default {
:can-push-code="canPushCode"
:path="uploadPath"
/>
+ <new-directory-modal
+ v-if="showNewDirectoryModal"
+ :can-push-code="canPushCode"
+ :modal-id="$options.newDirectoryModalId"
+ :commit-message="__('Add new directory')"
+ :target-branch="selectedBranch"
+ :original-branch="originalBranch"
+ :path="newDirPath"
+ />
</nav>
</template>
diff --git a/app/assets/javascripts/repository/components/new_directory_modal.vue b/app/assets/javascripts/repository/components/new_directory_modal.vue
new file mode 100644
index 00000000000..6c5797bf5b2
--- /dev/null
+++ b/app/assets/javascripts/repository/components/new_directory_modal.vue
@@ -0,0 +1,183 @@
+<script>
+import {
+ GlAlert,
+ GlForm,
+ GlModal,
+ GlFormGroup,
+ GlFormInput,
+ GlFormTextarea,
+ GlToggle,
+} from '@gitlab/ui';
+import createFlash from '~/flash';
+import axios from '~/lib/utils/axios_utils';
+import { visitUrl } from '~/lib/utils/url_utility';
+import { __ } from '~/locale';
+import {
+ SECONDARY_OPTIONS_TEXT,
+ COMMIT_LABEL,
+ TARGET_BRANCH_LABEL,
+ TOGGLE_CREATE_MR_LABEL,
+ NEW_BRANCH_IN_FORK,
+} from '../constants';
+
+const MODAL_TITLE = __('Create New Directory');
+const PRIMARY_OPTIONS_TEXT = __('Create directory');
+const DIR_LABEL = __('Directory name');
+const ERROR_MESSAGE = __('Error creating new directory. Please try again.');
+
+export default {
+ components: {
+ GlAlert,
+ GlModal,
+ GlForm,
+ GlFormGroup,
+ GlFormInput,
+ GlFormTextarea,
+ GlToggle,
+ },
+ i18n: {
+ DIR_LABEL,
+ COMMIT_LABEL,
+ TARGET_BRANCH_LABEL,
+ TOGGLE_CREATE_MR_LABEL,
+ NEW_BRANCH_IN_FORK,
+ PRIMARY_OPTIONS_TEXT,
+ ERROR_MESSAGE,
+ },
+ props: {
+ modalTitle: {
+ type: String,
+ default: MODAL_TITLE,
+ required: false,
+ },
+ modalId: {
+ type: String,
+ required: true,
+ },
+ primaryBtnText: {
+ type: String,
+ default: PRIMARY_OPTIONS_TEXT,
+ required: false,
+ },
+ commitMessage: {
+ type: String,
+ required: true,
+ },
+ targetBranch: {
+ type: String,
+ required: true,
+ },
+ originalBranch: {
+ type: String,
+ required: true,
+ },
+ path: {
+ type: String,
+ required: true,
+ },
+ canPushCode: {
+ type: Boolean,
+ required: true,
+ },
+ },
+ data() {
+ return {
+ dir: null,
+ commit: this.commitMessage,
+ target: this.targetBranch,
+ createNewMr: true,
+ loading: false,
+ };
+ },
+ computed: {
+ primaryOptions() {
+ return {
+ text: this.primaryBtnText,
+ attributes: [
+ {
+ variant: 'confirm',
+ loading: this.loading,
+ disabled: !this.formCompleted || this.loading,
+ },
+ ],
+ };
+ },
+ cancelOptions() {
+ return {
+ text: SECONDARY_OPTIONS_TEXT,
+ attributes: [
+ {
+ disabled: this.loading,
+ },
+ ],
+ };
+ },
+ showCreateNewMrToggle() {
+ return this.canPushCode;
+ },
+ formCompleted() {
+ return this.dir && this.commit && this.target;
+ },
+ },
+ methods: {
+ submitForm() {
+ this.loading = true;
+
+ const formData = new FormData();
+ formData.append('dir_name', this.dir);
+ formData.append('commit_message', this.commit);
+ formData.append('branch_name', this.target);
+ formData.append('original_branch', this.originalBranch);
+
+ if (this.createNewMr) {
+ formData.append('create_merge_request', this.createNewMr);
+ }
+
+ return axios
+ .post(this.path, formData)
+ .then((response) => {
+ visitUrl(response.data.filePath);
+ })
+ .catch(() => {
+ this.loading = false;
+ createFlash({ message: ERROR_MESSAGE });
+ });
+ },
+ },
+};
+</script>
+
+<template>
+ <gl-form>
+ <gl-modal
+ :modal-id="modalId"
+ :title="modalTitle"
+ :action-primary="primaryOptions"
+ :action-cancel="cancelOptions"
+ @primary.prevent="submitForm"
+ >
+ <gl-form-group :label="$options.i18n.DIR_LABEL" label-for="dir_name">
+ <gl-form-input v-model="dir" :disabled="loading" name="dir_name" />
+ </gl-form-group>
+ <gl-form-group :label="$options.i18n.COMMIT_LABEL" label-for="commit_message">
+ <gl-form-textarea v-model="commit" name="commit_message" :disabled="loading" />
+ </gl-form-group>
+ <gl-form-group
+ v-if="canPushCode"
+ :label="$options.i18n.TARGET_BRANCH_LABEL"
+ label-for="branch_name"
+ >
+ <gl-form-input v-model="target" :disabled="loading" name="branch_name" />
+ </gl-form-group>
+ <gl-toggle
+ v-if="showCreateNewMrToggle"
+ v-model="createNewMr"
+ :disabled="loading"
+ :label="$options.i18n.TOGGLE_CREATE_MR_LABEL"
+ />
+ <gl-alert v-if="!canPushCode" variant="info" :dismissible="false" class="gl-mt-3">
+ {{ $options.i18n.NEW_BRANCH_IN_FORK }}
+ </gl-alert>
+ </gl-modal>
+ </gl-form>
+</template>
diff --git a/app/assets/javascripts/repository/constants.js b/app/assets/javascripts/repository/constants.js
index 70952c8413b..152fabbd7cc 100644
--- a/app/assets/javascripts/repository/constants.js
+++ b/app/assets/javascripts/repository/constants.js
@@ -10,6 +10,9 @@ export const SECONDARY_OPTIONS_TEXT = __('Cancel');
export const COMMIT_LABEL = __('Commit message');
export const TARGET_BRANCH_LABEL = __('Target branch');
export const TOGGLE_CREATE_MR_LABEL = __('Start a new merge request with these changes');
+export const NEW_BRANCH_IN_FORK = __(
+ 'A new branch will be created in your fork and a new merge request will be started.',
+);
export const COMMIT_MESSAGE_SUBJECT_MAX_LENGTH = 52;
export const COMMIT_MESSAGE_BODY_MAX_LENGTH = 72;
diff --git a/app/assets/javascripts/repository/index.js b/app/assets/javascripts/repository/index.js
index 60a1a0443f7..45e026ad695 100644
--- a/app/assets/javascripts/repository/index.js
+++ b/app/assets/javascripts/repository/index.js
@@ -120,6 +120,7 @@ export default function setupVueRepositoryList() {
forkNewDirectoryPath,
forkUploadBlobPath,
uploadPath,
+ newDirPath,
},
});
},
diff --git a/app/assets/javascripts/repository/router.js b/app/assets/javascripts/repository/router.js
index 6637d03a7a4..0a675e14eb5 100644
--- a/app/assets/javascripts/repository/router.js
+++ b/app/assets/javascripts/repository/router.js
@@ -1,7 +1,7 @@
import { escapeRegExp } from 'lodash';
import Vue from 'vue';
import VueRouter from 'vue-router';
-import { joinPaths } from '../lib/utils/url_utility';
+import { joinPaths, webIDEUrl } from '~/lib/utils/url_utility';
import BlobPage from './pages/blob.vue';
import IndexPage from './pages/index.vue';
import TreePage from './pages/tree.vue';
@@ -24,7 +24,7 @@ export default function createRouter(base, baseRef) {
}),
};
- return new VueRouter({
+ const router = new VueRouter({
mode: 'history',
base: joinPaths(gon.relative_url_root || '', base),
routes: [
@@ -59,4 +59,21 @@ export default function createRouter(base, baseRef) {
},
],
});
+
+ router.afterEach((to) => {
+ const needsClosingSlash = !to.name.includes('blobPath');
+ window.gl.webIDEPath = webIDEUrl(
+ joinPaths(
+ '/',
+ base,
+ 'edit',
+ decodeURI(baseRef),
+ '-',
+ to.params.path || '',
+ needsClosingSlash && '/',
+ ),
+ );
+ });
+
+ return router;
}
diff --git a/app/assets/javascripts/vue_merge_request_widget/components/mr_widget_header.vue b/app/assets/javascripts/vue_merge_request_widget/components/mr_widget_header.vue
index 966262944ad..ecabe5007e6 100644
--- a/app/assets/javascripts/vue_merge_request_widget/components/mr_widget_header.vue
+++ b/app/assets/javascripts/vue_merge_request_widget/components/mr_widget_header.vue
@@ -10,7 +10,7 @@ import {
GlSafeHtmlDirective as SafeHtml,
GlSprintf,
} from '@gitlab/ui';
-import { mergeUrlParams, webIDEUrl } from '~/lib/utils/url_utility';
+import { constructWebIDEPath } from '~/lib/utils/url_utility';
import { s__ } from '~/locale';
import clipboardButton from '~/vue_shared/components/clipboard_button.vue';
import TooltipOnTruncate from '~/vue_shared/components/tooltip_on_truncate.vue';
@@ -58,15 +58,7 @@ export default {
});
},
webIdePath() {
- return mergeUrlParams(
- {
- target_project:
- this.mr.sourceProjectFullPath !== this.mr.targetProjectFullPath
- ? this.mr.targetProjectFullPath
- : '',
- },
- webIDEUrl(`/${this.mr.sourceProjectFullPath}/merge_requests/${this.mr.iid}`),
- );
+ return constructWebIDEPath(this.mr);
},
isFork() {
return this.mr.sourceProjectFullPath !== this.mr.targetProjectFullPath;
diff --git a/app/controllers/projects/tree_controller.rb b/app/controllers/projects/tree_controller.rb
index cb0e1900e48..a76d45411dd 100644
--- a/app/controllers/projects/tree_controller.rb
+++ b/app/controllers/projects/tree_controller.rb
@@ -18,6 +18,7 @@ class Projects::TreeController < Projects::ApplicationController
before_action do
push_frontend_feature_flag(:lazy_load_commits, @project, default_enabled: :yaml)
push_frontend_feature_flag(:paginated_tree_graphql_query, @project, default_enabled: :yaml)
+ push_frontend_feature_flag(:new_dir_modal, @project, default_enabled: :yaml)
end
feature_category :source_code_management
diff --git a/app/controllers/projects_controller.rb b/app/controllers/projects_controller.rb
index 7c7e6457020..26da0436dd8 100644
--- a/app/controllers/projects_controller.rb
+++ b/app/controllers/projects_controller.rb
@@ -38,6 +38,7 @@ class ProjectsController < Projects::ApplicationController
push_frontend_feature_flag(:refactor_text_viewer, @project, default_enabled: :yaml)
push_frontend_feature_flag(:increase_page_size_exponentially, @project, default_enabled: :yaml)
push_frontend_feature_flag(:paginated_tree_graphql_query, @project, default_enabled: :yaml)
+ push_frontend_feature_flag(:new_dir_modal, @project, default_enabled: :yaml)
end
layout :determine_layout
diff --git a/app/graphql/resolvers/project_pipelines_resolver.rb b/app/graphql/resolvers/project_pipelines_resolver.rb
index 0171473a77f..5a1e92efc96 100644
--- a/app/graphql/resolvers/project_pipelines_resolver.rb
+++ b/app/graphql/resolvers/project_pipelines_resolver.rb
@@ -26,3 +26,5 @@ module Resolvers
end
end
# rubocop: enable Graphql/ResolverType
+
+Resolvers::ProjectPipelinesResolver.prepend_mod
diff --git a/app/models/merge_request.rb b/app/models/merge_request.rb
index 7b890a630cc..7cb821eab0b 100644
--- a/app/models/merge_request.rb
+++ b/app/models/merge_request.rb
@@ -1111,15 +1111,23 @@ class MergeRequest < ApplicationRecord
can_be_merged? && !should_be_rebased?
end
+ # rubocop: disable CodeReuse/ServiceClass
def mergeable_state?(skip_ci_check: false, skip_discussions_check: false)
return false unless open?
return false if work_in_progress?
return false if broken?
- return false unless skip_ci_check || mergeable_ci_state?
return false unless skip_discussions_check || mergeable_discussions_state?
- true
+ if Feature.enabled?(:improved_mergeability_checks, self.project, default_enabled: :yaml)
+ additional_checks = MergeRequests::Mergeability::RunChecksService.new(merge_request: self, params: { skip_ci_check: skip_ci_check })
+ additional_checks.execute.all?(&:success?)
+ else
+ return false unless skip_ci_check || mergeable_ci_state?
+
+ true
+ end
end
+ # rubocop: enable CodeReuse/ServiceClass
def ff_merge_possible?
project.repository.ancestor?(target_branch_sha, diff_head_sha)
diff --git a/app/services/merge_requests/mergeability/check_base_service.rb b/app/services/merge_requests/mergeability/check_base_service.rb
new file mode 100644
index 00000000000..d5ddcb4b828
--- /dev/null
+++ b/app/services/merge_requests/mergeability/check_base_service.rb
@@ -0,0 +1,36 @@
+# frozen_string_literal: true
+module MergeRequests
+ module Mergeability
+ class CheckBaseService
+ attr_reader :merge_request, :params
+
+ def initialize(merge_request:, params:)
+ @merge_request = merge_request
+ @params = params
+ end
+
+ def skip?
+ raise NotImplementedError
+ end
+
+ # When this method is true, we need to implement a cache_key
+ def cacheable?
+ raise NotImplementedError
+ end
+
+ def cache_key
+ raise NotImplementedError
+ end
+
+ private
+
+ def success(*args)
+ Gitlab::MergeRequests::Mergeability::CheckResult.success(*args)
+ end
+
+ def failure(*args)
+ Gitlab::MergeRequests::Mergeability::CheckResult.failed(*args)
+ end
+ end
+ end
+end
diff --git a/app/services/merge_requests/mergeability/check_ci_status_service.rb b/app/services/merge_requests/mergeability/check_ci_status_service.rb
new file mode 100644
index 00000000000..c0ef5ba1c30
--- /dev/null
+++ b/app/services/merge_requests/mergeability/check_ci_status_service.rb
@@ -0,0 +1,22 @@
+# frozen_string_literal: true
+module MergeRequests
+ module Mergeability
+ class CheckCiStatusService < CheckBaseService
+ def execute
+ if merge_request.mergeable_ci_state?
+ success
+ else
+ failure
+ end
+ end
+
+ def skip?
+ params[:skip_ci_check].present?
+ end
+
+ def cacheable?
+ false
+ end
+ end
+ end
+end
diff --git a/app/services/merge_requests/mergeability/run_checks_service.rb b/app/services/merge_requests/mergeability/run_checks_service.rb
new file mode 100644
index 00000000000..c1d65fb65cc
--- /dev/null
+++ b/app/services/merge_requests/mergeability/run_checks_service.rb
@@ -0,0 +1,54 @@
+# frozen_string_literal: true
+module MergeRequests
+ module Mergeability
+ class RunChecksService
+ include Gitlab::Utils::StrongMemoize
+
+ # We want to have the cheapest checks first in the list,
+ # that way we can fail fast before running the more expensive ones
+ CHECKS = [
+ CheckCiStatusService
+ ].freeze
+
+ def initialize(merge_request:, params:)
+ @merge_request = merge_request
+ @params = params
+ end
+
+ def execute
+ CHECKS.each_with_object([]) do |check_class, results|
+ check = check_class.new(merge_request: merge_request, params: params)
+
+ next if check.skip?
+
+ check_result = run_check(check)
+ results << check_result
+
+ break results if check_result.failed?
+ end
+ end
+
+ private
+
+ attr_reader :merge_request, :params
+
+ def run_check(check)
+ return check.execute unless Feature.enabled?(:mergeability_caching, merge_request.project, default_enabled: :yaml)
+ return check.execute unless check.cacheable?
+
+ cached_result = results.read(merge_check: check)
+ return cached_result if cached_result.respond_to?(:status)
+
+ check.execute.tap do |result|
+ results.write(merge_check: check, result_hash: result.to_hash)
+ end
+ end
+
+ def results
+ strong_memoize(:results) do
+ Gitlab::MergeRequests::Mergeability::ResultsStore.new(merge_request: merge_request)
+ end
+ end
+ end
+ end
+end
diff --git a/app/services/merge_requests/update_service.rb b/app/services/merge_requests/update_service.rb
index af041de5596..c5395138902 100644
--- a/app/services/merge_requests/update_service.rb
+++ b/app/services/merge_requests/update_service.rb
@@ -248,7 +248,7 @@ module MergeRequests
def merge_from_quick_action(merge_request)
last_diff_sha = params.delete(:merge)
- MergeRequests::MergeOrchestrationService
+ ::MergeRequests::MergeOrchestrationService
.new(project, current_user, { sha: last_diff_sha })
.execute(merge_request)
end
diff --git a/app/views/layouts/_startup_js.html.haml b/app/views/layouts/_startup_js.html.haml
index b7dd3a9556c..0d5f6bbe25b 100644
--- a/app/views/layouts/_startup_js.html.haml
+++ b/app/views/layouts/_startup_js.html.haml
@@ -25,6 +25,7 @@
headers: {
"Content-Type": "application/json",
...headers,
+ }
};
gl.startup_graphql_calls = gl.startup_graphql_calls.map(call => ({
diff --git a/app/views/projects/_files.html.haml b/app/views/projects/_files.html.haml
index 597a22bf34a..cdcc98552f9 100644
--- a/app/views/projects/_files.html.haml
+++ b/app/views/projects/_files.html.haml
@@ -20,5 +20,6 @@
= render 'stat_anchor_list', anchors: @project.statistics_buttons(show_auto_devops_callout: show_auto_devops_callout), project_buttons: true
#js-tree-list{ data: vue_file_list_data(project, ref) }
- - if can_edit_tree?
+ - if !Feature.enabled?(:new_dir_modal, default_enabled: :yaml) && can_edit_tree?
= render 'projects/blob/new_dir'
+
diff --git a/app/views/projects/blob/show.html.haml b/app/views/projects/blob/show.html.haml
index 66e9badbafb..168b240c657 100644
--- a/app/views/projects/blob/show.html.haml
+++ b/app/views/projects/blob/show.html.haml
@@ -18,3 +18,4 @@
= render 'projects/blob/upload', title: title, placeholder: title, button_title: 'Replace file', form_path: project_update_blob_path(@project, @id), method: :put
= render partial: 'pipeline_tour_success' if show_suggest_pipeline_creation_celebration?
+= render 'shared/web_ide_path'
diff --git a/app/views/projects/merge_requests/_widget.html.haml b/app/views/projects/merge_requests/_widget.html.haml
index 47a0d05fc65..459742c3b81 100644
--- a/app/views/projects/merge_requests/_widget.html.haml
+++ b/app/views/projects/merge_requests/_widget.html.haml
@@ -19,6 +19,6 @@
window.gl.mrWidgetData.pipelines_empty_svg_path = '#{image_path('illustrations/pipelines_empty.svg')}';
window.gl.mrWidgetData.codequality_help_path = '#{help_page_path("user/project/merge_requests/code_quality", anchor: "code-quality-reports")}';
window.gl.mrWidgetData.false_positive_doc_url = '#{help_page_path('user/application_security/vulnerabilities/index')}';
- window.gl.mrWidgetData.can_view_false_positive = '#{(Feature.enabled?(:vulnerability_flags, default_enabled: :yaml) && @merge_request.project.licensed_feature_available?(:sast_fp_reduction)).to_s}';
+ window.gl.mrWidgetData.can_view_false_positive = '#{@merge_request.project.licensed_feature_available?(:sast_fp_reduction).to_s}';
#js-vue-mr-widget.mr-widget
diff --git a/app/views/projects/merge_requests/show.html.haml b/app/views/projects/merge_requests/show.html.haml
index ff5582f2627..2154ef6b596 100644
--- a/app/views/projects/merge_requests/show.html.haml
+++ b/app/views/projects/merge_requests/show.html.haml
@@ -99,3 +99,4 @@
= render 'projects/invite_members_modal', project: @project
- if Gitlab::CurrentSettings.gitpod_enabled && !current_user&.gitpod_enabled
= render 'shared/gitpod/enable_gitpod_modal'
+= render 'shared/web_ide_path'
diff --git a/app/views/projects/tree/show.html.haml b/app/views/projects/tree/show.html.haml
index 2d0c4cc20a0..1553eda1cfb 100644
--- a/app/views/projects/tree/show.html.haml
+++ b/app/views/projects/tree/show.html.haml
@@ -11,3 +11,4 @@
= render 'projects/last_push'
= render 'projects/files', commit: @last_commit, project: @project, ref: @ref, content_url: project_tree_path(@project, @id)
+= render 'shared/web_ide_path'
diff --git a/app/views/shared/_web_ide_path.html.haml b/app/views/shared/_web_ide_path.html.haml
new file mode 100644
index 00000000000..73d00bcd408
--- /dev/null
+++ b/app/views/shared/_web_ide_path.html.haml
@@ -0,0 +1,4 @@
+= javascript_tag do
+ :plain
+ window.gl = window.gl || {};
+ window.gl.webIDEPath = '#{web_ide_url}'