summaryrefslogtreecommitdiff
path: root/app
diff options
context:
space:
mode:
authorGitLab Bot <gitlab-bot@gitlab.com>2020-02-20 09:09:13 +0000
committerGitLab Bot <gitlab-bot@gitlab.com>2020-02-20 09:09:13 +0000
commit1ac794623a8be5dee111716a44dd04ff708f3541 (patch)
tree6c18f9fbe0bd9978bd3e8d9b083d3a0ca180686e /app
parent5247fe0bef72fa922841a79d5dbefb47d95112fa (diff)
downloadgitlab-ce-1ac794623a8be5dee111716a44dd04ff708f3541.tar.gz
Add latest changes from gitlab-org/gitlab@master
Diffstat (limited to 'app')
-rw-r--r--app/assets/javascripts/frequent_items/components/frequent_items_list_item.vue4
-rw-r--r--app/assets/javascripts/frequent_items/components/frequent_items_search_input.vue4
-rw-r--r--app/assets/javascripts/frequent_items/utils.js4
-rw-r--r--app/assets/javascripts/issuable_suggestions/components/app.vue3
-rw-r--r--app/assets/javascripts/issuable_suggestions/components/item.vue6
-rw-r--r--app/assets/javascripts/issue_show/stores/index.js3
-rw-r--r--app/assets/javascripts/issue_show/utils/update_description.js4
-rw-r--r--app/assets/javascripts/releases/components/app_edit.vue4
-rw-r--r--app/assets/javascripts/releases/components/release_block.vue4
-rw-r--r--app/controllers/projects/forks_controller.rb27
-rw-r--r--app/finders/fork_targets_finder.rb20
-rw-r--r--app/models/namespace.rb6
-rw-r--r--app/services/projects/fork_service.rb49
13 files changed, 91 insertions, 47 deletions
diff --git a/app/assets/javascripts/frequent_items/components/frequent_items_list_item.vue b/app/assets/javascripts/frequent_items/components/frequent_items_list_item.vue
index 6188d41ae96..3276d85f1cd 100644
--- a/app/assets/javascripts/frequent_items/components/frequent_items_list_item.vue
+++ b/app/assets/javascripts/frequent_items/components/frequent_items_list_item.vue
@@ -1,6 +1,6 @@
<script>
/* eslint-disable vue/require-default-prop */
-import _ from 'underscore';
+import { isEmpty, isString } from 'lodash';
import Identicon from '~/vue_shared/components/identicon.vue';
import highlight from '~/lib/utils/highlight';
import { truncateNamespace } from '~/lib/utils/text_utility';
@@ -39,7 +39,7 @@ export default {
},
computed: {
hasAvatar() {
- return _.isString(this.avatarUrl) && !_.isEmpty(this.avatarUrl);
+ return isString(this.avatarUrl) && !isEmpty(this.avatarUrl);
},
truncatedNamespace() {
return truncateNamespace(this.namespace);
diff --git a/app/assets/javascripts/frequent_items/components/frequent_items_search_input.vue b/app/assets/javascripts/frequent_items/components/frequent_items_search_input.vue
index c69e1b792dc..40add09f25d 100644
--- a/app/assets/javascripts/frequent_items/components/frequent_items_search_input.vue
+++ b/app/assets/javascripts/frequent_items/components/frequent_items_search_input.vue
@@ -1,5 +1,5 @@
<script>
-import _ from 'underscore';
+import { debounce } from 'lodash';
import { mapActions } from 'vuex';
import Icon from '~/vue_shared/components/icon.vue';
import eventHub from '../event_hub';
@@ -21,7 +21,7 @@ export default {
},
},
watch: {
- searchQuery: _.debounce(function debounceSearchQuery() {
+ searchQuery: debounce(function debounceSearchQuery() {
this.setSearchQuery(this.searchQuery);
}, 500),
},
diff --git a/app/assets/javascripts/frequent_items/utils.js b/app/assets/javascripts/frequent_items/utils.js
index 5188d6118ac..a992480c22b 100644
--- a/app/assets/javascripts/frequent_items/utils.js
+++ b/app/assets/javascripts/frequent_items/utils.js
@@ -1,4 +1,4 @@
-import _ from 'underscore';
+import { take } from 'lodash';
import { GlBreakpointInstance as bp } from '@gitlab/ui/dist/utils';
import sanitize from 'sanitize-html';
import { FREQUENT_ITEMS, HOUR_IN_MS } from './constants';
@@ -31,7 +31,7 @@ export const getTopFrequentItems = items => {
return 0;
});
- return _.first(frequentItems, frequentItemsCount);
+ return take(frequentItems, frequentItemsCount);
};
export const updateExistingFrequentItem = (frequentItem, item) => {
diff --git a/app/assets/javascripts/issuable_suggestions/components/app.vue b/app/assets/javascripts/issuable_suggestions/components/app.vue
index d435460e38f..67d10b797fb 100644
--- a/app/assets/javascripts/issuable_suggestions/components/app.vue
+++ b/app/assets/javascripts/issuable_suggestions/components/app.vue
@@ -1,5 +1,4 @@
<script>
-import _ from 'underscore';
import { GlTooltipDirective } from '@gitlab/ui';
import { __ } from '~/locale';
import Icon from '~/vue_shared/components/icon.vue';
@@ -48,7 +47,7 @@ export default {
},
computed: {
isSearchEmpty() {
- return _.isEmpty(this.search);
+ return !this.search.length;
},
showSuggestions() {
return !this.isSearchEmpty && this.issues.length && !this.loading;
diff --git a/app/assets/javascripts/issuable_suggestions/components/item.vue b/app/assets/javascripts/issuable_suggestions/components/item.vue
index 66a4cc44d51..9f3508fb937 100644
--- a/app/assets/javascripts/issuable_suggestions/components/item.vue
+++ b/app/assets/javascripts/issuable_suggestions/components/item.vue
@@ -1,6 +1,6 @@
<script>
/* eslint-disable @gitlab/vue-i18n/no-bare-strings */
-import _ from 'underscore';
+import { uniqueId } from 'lodash';
import { GlLink, GlTooltip, GlTooltipDirective } from '@gitlab/ui';
import { __ } from '~/locale';
import Icon from '~/vue_shared/components/icon.vue';
@@ -36,13 +36,13 @@ export default {
counts() {
return [
{
- id: _.uniqueId(),
+ id: uniqueId(),
icon: 'thumb-up',
tooltipTitle: __('Upvotes'),
count: this.suggestion.upvotes,
},
{
- id: _.uniqueId(),
+ id: uniqueId(),
icon: 'comment',
tooltipTitle: __('Comments'),
count: this.suggestion.userNotesCount,
diff --git a/app/assets/javascripts/issue_show/stores/index.js b/app/assets/javascripts/issue_show/stores/index.js
index 688ba7b268d..0cd094243b9 100644
--- a/app/assets/javascripts/issue_show/stores/index.js
+++ b/app/assets/javascripts/issue_show/stores/index.js
@@ -1,4 +1,3 @@
-import _ from 'underscore';
import { convertObjectPropsToCamelCase } from '~/lib/utils/common_utils';
import updateDescription from '../utils/update_description';
@@ -26,7 +25,7 @@ export default class Store {
'.detail-page-description.content-block',
);
const details =
- !_.isNull(descriptionSection) && descriptionSection.getElementsByTagName('details');
+ descriptionSection != null && descriptionSection.getElementsByTagName('details');
this.state.descriptionHtml = updateDescription(data.description, details);
this.state.titleHtml = data.title;
diff --git a/app/assets/javascripts/issue_show/utils/update_description.js b/app/assets/javascripts/issue_show/utils/update_description.js
index 315f6c23b02..c5811290e61 100644
--- a/app/assets/javascripts/issue_show/utils/update_description.js
+++ b/app/assets/javascripts/issue_show/utils/update_description.js
@@ -1,5 +1,3 @@
-import _ from 'underscore';
-
/**
* Function that replaces the open attribute for the <details> element.
*
@@ -10,7 +8,7 @@ import _ from 'underscore';
const updateDescription = (descriptionHtml = '', details) => {
let detailNodes = details;
- if (_.isEmpty(details)) {
+ if (!details.length) {
detailNodes = [];
}
diff --git a/app/assets/javascripts/releases/components/app_edit.vue b/app/assets/javascripts/releases/components/app_edit.vue
index bdc2b3abb8c..f6a4d00692e 100644
--- a/app/assets/javascripts/releases/components/app_edit.vue
+++ b/app/assets/javascripts/releases/components/app_edit.vue
@@ -1,7 +1,7 @@
<script>
import { mapState, mapActions } from 'vuex';
import { GlButton, GlFormInput, GlFormGroup } from '@gitlab/ui';
-import _ from 'underscore';
+import { escape as esc } from 'lodash';
import { __, sprintf } from '~/locale';
import MarkdownField from '~/vue_shared/components/markdown/field.vue';
import autofocusonshow from '~/vue_shared/directives/autofocusonshow';
@@ -50,7 +50,7 @@ export default {
'Changing a Release tag is only supported via Releases API. %{linkStart}More information%{linkEnd}',
),
{
- linkStart: `<a href="${_.escape(
+ linkStart: `<a href="${esc(
this.updateReleaseApiDocsPath,
)}" target="_blank" rel="noopener noreferrer">`,
linkEnd: '</a>',
diff --git a/app/assets/javascripts/releases/components/release_block.vue b/app/assets/javascripts/releases/components/release_block.vue
index e6bb5325120..bc3f2c3bf30 100644
--- a/app/assets/javascripts/releases/components/release_block.vue
+++ b/app/assets/javascripts/releases/components/release_block.vue
@@ -1,5 +1,5 @@
<script>
-import _ from 'underscore';
+import { isEmpty } from 'lodash';
import $ from 'jquery';
import { slugify } from '~/lib/utils/text_utility';
import { getLocationHash } from '~/lib/utils/url_utility';
@@ -64,7 +64,7 @@ export default {
return !this.glFeatures.releaseIssueSummary;
},
shouldRenderMilestoneInfo() {
- return Boolean(this.glFeatures.releaseIssueSummary && !_.isEmpty(this.release.milestones));
+ return Boolean(this.glFeatures.releaseIssueSummary && !isEmpty(this.release.milestones));
},
},
diff --git a/app/controllers/projects/forks_controller.rb b/app/controllers/projects/forks_controller.rb
index 9806b91c7e8..e0e8fb177ba 100644
--- a/app/controllers/projects/forks_controller.rb
+++ b/app/controllers/projects/forks_controller.rb
@@ -3,6 +3,7 @@
class Projects::ForksController < Projects::ApplicationController
include ContinueParams
include RendersMemberAccess
+ include Gitlab::Utils::StrongMemoize
# Authorize
before_action :whitelist_query_limiting, only: [:create]
@@ -10,6 +11,7 @@ class Projects::ForksController < Projects::ApplicationController
before_action :authorize_download_code!
before_action :authenticate_user!, only: [:new, :create]
before_action :authorize_fork_project!, only: [:new, :create]
+ before_action :authorize_fork_namespace!, only: [:create]
# rubocop: disable CodeReuse/ActiveRecord
def index
@@ -37,18 +39,15 @@ class Projects::ForksController < Projects::ApplicationController
# rubocop: enable CodeReuse/ActiveRecord
def new
- @namespaces = current_user.manageable_namespaces
- @namespaces.delete(@project.namespace)
+ @namespaces = fork_service.valid_fork_targets
end
# rubocop: disable CodeReuse/ActiveRecord
def create
- namespace = Namespace.find(params[:namespace_key])
-
- @forked_project = namespace.projects.find_by(path: project.path)
+ @forked_project = fork_namespace.projects.find_by(path: project.path)
@forked_project = nil unless @forked_project && @forked_project.forked_from_project == project
- @forked_project ||= ::Projects::ForkService.new(project, current_user, namespace: namespace).execute
+ @forked_project ||= fork_service.execute
if !@forked_project.saved? || !@forked_project.forked?
render :error
@@ -64,6 +63,22 @@ class Projects::ForksController < Projects::ApplicationController
private
+ def fork_service
+ strong_memoize(:fork_service) do
+ ::Projects::ForkService.new(project, current_user, namespace: fork_namespace)
+ end
+ end
+
+ def fork_namespace
+ strong_memoize(:fork_namespace) do
+ Namespace.find(params[:namespace_key]) if params[:namespace_key].present?
+ end
+ end
+
+ def authorize_fork_namespace!
+ access_denied! unless fork_namespace && fork_service.valid_fork_target?
+ end
+
def whitelist_query_limiting
Gitlab::QueryLimiting.whitelist('https://gitlab.com/gitlab-org/gitlab-foss/issues/42335')
end
diff --git a/app/finders/fork_targets_finder.rb b/app/finders/fork_targets_finder.rb
new file mode 100644
index 00000000000..9003c593757
--- /dev/null
+++ b/app/finders/fork_targets_finder.rb
@@ -0,0 +1,20 @@
+# frozen_string_literal: true
+
+class ForkTargetsFinder
+ def initialize(project, user)
+ @project = project
+ @user = user
+ end
+
+ # rubocop: disable CodeReuse/ActiveRecord
+ def execute
+ ::Namespace.where(id: user.manageable_namespaces).where.not(id: project.namespace).sort_by_type
+ end
+ # rubocop: enable CodeReuse/ActiveRecord
+
+ private
+
+ attr_reader :project, :user
+end
+
+ForkTargetsFinder.prepend_if_ee('EE::ForkTargetsFinder')
diff --git a/app/models/namespace.rb b/app/models/namespace.rb
index efe14a3e614..99212d09b8e 100644
--- a/app/models/namespace.rb
+++ b/app/models/namespace.rb
@@ -68,6 +68,7 @@ class Namespace < ApplicationRecord
after_destroy :rm_dir
scope :for_user, -> { where('type IS NULL') }
+ scope :sort_by_type, -> { order(Gitlab::Database.nulls_first_order(:type)) }
scope :with_statistics, -> do
joins('LEFT JOIN project_statistics ps ON ps.namespace_id = namespaces.id')
@@ -326,7 +327,10 @@ class Namespace < ApplicationRecord
end
def pages_virtual_domain
- Pages::VirtualDomain.new(all_projects_with_pages, trim_prefix: full_path)
+ Pages::VirtualDomain.new(
+ all_projects_with_pages.includes(:route, :project_feature),
+ trim_prefix: full_path
+ )
end
def closest_setting(name)
diff --git a/app/services/projects/fork_service.rb b/app/services/projects/fork_service.rb
index fcfea567885..6ac53b15ef9 100644
--- a/app/services/projects/fork_service.rb
+++ b/app/services/projects/fork_service.rb
@@ -3,24 +3,25 @@
module Projects
class ForkService < BaseService
def execute(fork_to_project = nil)
- forked_project =
- if fork_to_project
- link_existing_project(fork_to_project)
- else
- fork_new_project
- end
+ forked_project = fork_to_project ? link_existing_project(fork_to_project) : fork_new_project
refresh_forks_count if forked_project&.saved?
forked_project
end
- private
+ def valid_fork_targets
+ @valid_fork_targets ||= ForkTargetsFinder.new(@project, current_user).execute
+ end
- def allowed_fork?
- current_user.can?(:fork_project, @project)
+ def valid_fork_target?
+ return true if current_user.admin?
+
+ valid_fork_targets.include?(target_namespace)
end
+ private
+
def link_existing_project(fork_to_project)
return if fork_to_project.forked?
@@ -30,6 +31,21 @@ module Projects
end
def fork_new_project
+ new_project = CreateService.new(current_user, new_fork_params).execute
+ return new_project unless new_project.persisted?
+
+ # Set the forked_from_project relation after saving to avoid having to
+ # reload the project to reset the association information and cause an
+ # extra query.
+ new_project.forked_from_project = @project
+
+ builds_access_level = @project.project_feature.builds_access_level
+ new_project.project_feature.update(builds_access_level: builds_access_level)
+
+ new_project
+ end
+
+ def new_fork_params
new_params = {
visibility_level: allowed_visibility_level,
description: @project.description,
@@ -57,18 +73,11 @@ module Projects
new_params.merge!(@project.object_pool_params)
- new_project = CreateService.new(current_user, new_params).execute
- return new_project unless new_project.persisted?
-
- # Set the forked_from_project relation after saving to avoid having to
- # reload the project to reset the association information and cause an
- # extra query.
- new_project.forked_from_project = @project
-
- builds_access_level = @project.project_feature.builds_access_level
- new_project.project_feature.update(builds_access_level: builds_access_level)
+ new_params
+ end
- new_project
+ def allowed_fork?
+ current_user.can?(:fork_project, @project)
end
def fork_network