diff options
author | GitLab Bot <gitlab-bot@gitlab.com> | 2020-02-20 09:09:13 +0000 |
---|---|---|
committer | GitLab Bot <gitlab-bot@gitlab.com> | 2020-02-20 09:09:13 +0000 |
commit | 1ac794623a8be5dee111716a44dd04ff708f3541 (patch) | |
tree | 6c18f9fbe0bd9978bd3e8d9b083d3a0ca180686e /app | |
parent | 5247fe0bef72fa922841a79d5dbefb47d95112fa (diff) | |
download | gitlab-ce-1ac794623a8be5dee111716a44dd04ff708f3541.tar.gz |
Add latest changes from gitlab-org/gitlab@master
Diffstat (limited to 'app')
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 |