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-07-15 00:09:44 +0300
committerGitLab Bot <gitlab-bot@gitlab.com>2021-07-15 00:09:44 +0300
commitdc60045db7aab599453799c75190b93692d91b7c (patch)
treec566543274529c4427b5721a2de3c7230e03c198 /app
parent06ac12d53c3f0b7cee2755a1254bf1af05d55044 (diff)
Add latest changes from gitlab-org/gitlab@master
Diffstat (limited to 'app')
-rw-r--r--app/assets/javascripts/search/store/getters.js9
-rw-r--r--app/assets/javascripts/search/store/index.js2
-rw-r--r--app/assets/javascripts/search/store/utils.js24
-rw-r--r--app/assets/javascripts/search/topbar/components/group_filter.vue4
-rw-r--r--app/assets/javascripts/search/topbar/components/project_filter.vue4
-rw-r--r--app/assets/javascripts/search/topbar/components/searchable_dropdown.vue33
-rw-r--r--app/controllers/profiles/gpg_keys_controller.rb2
-rw-r--r--app/graphql/types/ci/build_need_type.rb2
-rw-r--r--app/graphql/types/ci/detailed_status_type.rb7
-rw-r--r--app/graphql/types/ci/group_type.rb8
-rw-r--r--app/graphql/types/ci/stage_type.rb16
-rw-r--r--app/graphql/types/ci/status_action_type.rb7
-rw-r--r--app/models/ci/group.rb5
-rw-r--r--app/models/namespace_setting.rb2
-rw-r--r--app/models/user_callout.rb4
-rw-r--r--app/policies/group_policy.rb1
-rw-r--r--app/services/gpg_keys/create_service.rb10
-rw-r--r--app/services/gpg_keys/destroy_service.rb2
-rw-r--r--app/services/namespace_settings/update_service.rb24
-rw-r--r--app/services/snippets/create_service.rb14
-rw-r--r--app/services/snippets/update_service.rb14
21 files changed, 147 insertions, 47 deletions
diff --git a/app/assets/javascripts/search/store/getters.js b/app/assets/javascripts/search/store/getters.js
new file mode 100644
index 00000000000..650af5fa55a
--- /dev/null
+++ b/app/assets/javascripts/search/store/getters.js
@@ -0,0 +1,9 @@
+import { GROUPS_LOCAL_STORAGE_KEY, PROJECTS_LOCAL_STORAGE_KEY } from './constants';
+
+export const frequentGroups = (state) => {
+ return state.frequentItems[GROUPS_LOCAL_STORAGE_KEY];
+};
+
+export const frequentProjects = (state) => {
+ return state.frequentItems[PROJECTS_LOCAL_STORAGE_KEY];
+};
diff --git a/app/assets/javascripts/search/store/index.js b/app/assets/javascripts/search/store/index.js
index 1923c8b96ab..4fa88822722 100644
--- a/app/assets/javascripts/search/store/index.js
+++ b/app/assets/javascripts/search/store/index.js
@@ -1,6 +1,7 @@
import Vue from 'vue';
import Vuex from 'vuex';
import * as actions from './actions';
+import * as getters from './getters';
import mutations from './mutations';
import createState from './state';
@@ -8,6 +9,7 @@ Vue.use(Vuex);
export const getStoreConfig = ({ query }) => ({
actions,
+ getters,
mutations,
state: createState({ query }),
});
diff --git a/app/assets/javascripts/search/store/utils.js b/app/assets/javascripts/search/store/utils.js
index 9f058efeaa7..60c09221ca9 100644
--- a/app/assets/javascripts/search/store/utils.js
+++ b/app/assets/javascripts/search/store/utils.js
@@ -24,7 +24,15 @@ export const setFrequentItemToLS = (key, data, itemData) => {
return;
}
- const keyList = ['id', 'avatar_url', 'name', 'full_name', 'name_with_namespace', 'frequency'];
+ const keyList = [
+ 'id',
+ 'avatar_url',
+ 'name',
+ 'full_name',
+ 'name_with_namespace',
+ 'frequency',
+ 'lastUsed',
+ ];
try {
const frequentItems = data[key].map((obj) => extractKeys(obj, keyList));
@@ -35,17 +43,25 @@ export const setFrequentItemToLS = (key, data, itemData) => {
// Up the frequency (Max 5)
const currentFrequency = frequentItems[existingItemIndex].frequency;
frequentItems[existingItemIndex].frequency = Math.min(currentFrequency + 1, MAX_FREQUENCY);
+ frequentItems[existingItemIndex].lastUsed = new Date().getTime();
} else {
// Only store a max of 5 items
if (frequentItems.length >= MAX_FREQUENT_ITEMS) {
frequentItems.pop();
}
- frequentItems.push({ ...item, frequency: 1 });
+ frequentItems.push({ ...item, frequency: 1, lastUsed: new Date().getTime() });
}
- // Sort by frequency
- frequentItems.sort((a, b) => b.frequency - a.frequency);
+ // Sort by frequency and lastUsed
+ frequentItems.sort((a, b) => {
+ if (a.frequency > b.frequency) {
+ return -1;
+ } else if (a.frequency < b.frequency) {
+ return 1;
+ }
+ return b.lastUsed - a.lastUsed;
+ });
// Note we do not need to commit a mutation here as immediately after this we refresh the page to
// update the search results.
diff --git a/app/assets/javascripts/search/topbar/components/group_filter.vue b/app/assets/javascripts/search/topbar/components/group_filter.vue
index 2040d2ca173..45a6ae73fac 100644
--- a/app/assets/javascripts/search/topbar/components/group_filter.vue
+++ b/app/assets/javascripts/search/topbar/components/group_filter.vue
@@ -1,6 +1,6 @@
<script>
import { isEmpty } from 'lodash';
-import { mapState, mapActions } from 'vuex';
+import { mapState, mapActions, mapGetters } from 'vuex';
import { visitUrl, setUrlParams } from '~/lib/utils/url_utility';
import { ANY_OPTION, GROUP_DATA, PROJECT_DATA } from '../constants';
import SearchableDropdown from './searchable_dropdown.vue';
@@ -19,6 +19,7 @@ export default {
},
computed: {
...mapState(['groups', 'fetchingGroups']),
+ ...mapGetters(['frequentGroups']),
selectedGroup() {
return isEmpty(this.initialData) ? ANY_OPTION : this.initialData;
},
@@ -49,6 +50,7 @@ export default {
:loading="fetchingGroups"
:selected-item="selectedGroup"
:items="groups"
+ :frequent-items="frequentGroups"
@first-open="loadFrequentGroups"
@search="fetchGroups"
@change="handleGroupChange"
diff --git a/app/assets/javascripts/search/topbar/components/project_filter.vue b/app/assets/javascripts/search/topbar/components/project_filter.vue
index 8589276e9f3..1ca31db61e5 100644
--- a/app/assets/javascripts/search/topbar/components/project_filter.vue
+++ b/app/assets/javascripts/search/topbar/components/project_filter.vue
@@ -1,5 +1,5 @@
<script>
-import { mapState, mapActions } from 'vuex';
+import { mapState, mapActions, mapGetters } from 'vuex';
import { visitUrl, setUrlParams } from '~/lib/utils/url_utility';
import { ANY_OPTION, GROUP_DATA, PROJECT_DATA } from '../constants';
import SearchableDropdown from './searchable_dropdown.vue';
@@ -18,6 +18,7 @@ export default {
},
computed: {
...mapState(['projects', 'fetchingProjects']),
+ ...mapGetters(['frequentProjects']),
selectedProject() {
return this.initialData ? this.initialData : ANY_OPTION;
},
@@ -52,6 +53,7 @@ export default {
:loading="fetchingProjects"
:selected-item="selectedProject"
:items="projects"
+ :frequent-items="frequentProjects"
@first-open="loadFrequentProjects"
@search="fetchProjects"
@change="handleProjectChange"
diff --git a/app/assets/javascripts/search/topbar/components/searchable_dropdown.vue b/app/assets/javascripts/search/topbar/components/searchable_dropdown.vue
index 488fee90fca..5653cddda60 100644
--- a/app/assets/javascripts/search/topbar/components/searchable_dropdown.vue
+++ b/app/assets/javascripts/search/topbar/components/searchable_dropdown.vue
@@ -2,6 +2,7 @@
import {
GlDropdown,
GlDropdownItem,
+ GlDropdownSectionHeader,
GlSearchBoxByType,
GlLoadingIcon,
GlIcon,
@@ -16,11 +17,13 @@ import SearchableDropdownItem from './searchable_dropdown_item.vue';
export default {
i18n: {
clearLabel: __('Clear'),
+ frequentlySearched: __('Frequently searched'),
},
name: 'SearchableDropdown',
components: {
GlDropdown,
GlDropdownItem,
+ GlDropdownSectionHeader,
GlSearchBoxByType,
GlLoadingIcon,
GlIcon,
@@ -61,6 +64,11 @@ export default {
required: false,
default: () => [],
},
+ frequentItems: {
+ type: Array,
+ required: false,
+ default: () => [],
+ },
},
data() {
return {
@@ -68,6 +76,11 @@ export default {
hasBeenOpened: false,
};
},
+ computed: {
+ showFrequentItems() {
+ return !this.searchText && this.frequentItems.length > 0;
+ },
+ },
methods: {
isSelected(selected) {
return selected.id === this.selectedItem.id;
@@ -139,6 +152,25 @@ export default {
<span data-testid="item-title">{{ $options.ANY_OPTION.name }}</span>
</gl-dropdown-item>
</div>
+ <div
+ v-if="showFrequentItems"
+ class="gl-border-b-solid gl-border-b-gray-100 gl-border-b-1 gl-pb-2 gl-mb-2"
+ >
+ <gl-dropdown-section-header>{{
+ $options.i18n.frequentlySearched
+ }}</gl-dropdown-section-header>
+ <searchable-dropdown-item
+ v-for="item in frequentItems"
+ :key="item.id"
+ :item="item"
+ :selected-item="selectedItem"
+ :search-text="searchText"
+ :name="name"
+ :full-name="fullName"
+ data-testid="frequent-items"
+ @change="updateDropdown"
+ />
+ </div>
<div v-if="!loading">
<searchable-dropdown-item
v-for="item in items"
@@ -148,6 +180,7 @@ export default {
:search-text="searchText"
:name="name"
:full-name="fullName"
+ data-testid="searchable-items"
@change="updateDropdown"
/>
</div>
diff --git a/app/controllers/profiles/gpg_keys_controller.rb b/app/controllers/profiles/gpg_keys_controller.rb
index 7f04927f517..9e16d195b00 100644
--- a/app/controllers/profiles/gpg_keys_controller.rb
+++ b/app/controllers/profiles/gpg_keys_controller.rb
@@ -22,7 +22,7 @@ class Profiles::GpgKeysController < Profiles::ApplicationController
end
def destroy
- @gpg_key.destroy
+ GpgKeys::DestroyService.new(current_user).execute(@gpg_key)
respond_to do |format|
format.html { redirect_to profile_gpg_keys_url, status: :found }
diff --git a/app/graphql/types/ci/build_need_type.rb b/app/graphql/types/ci/build_need_type.rb
index 3bd81f8fa8f..19ff758ad1d 100644
--- a/app/graphql/types/ci/build_need_type.rb
+++ b/app/graphql/types/ci/build_need_type.rb
@@ -7,6 +7,8 @@ module Types
class BuildNeedType < BaseObject
graphql_name 'CiBuildNeed'
+ field :id, GraphQL::ID_TYPE, null: false,
+ description: 'ID of the job we need to complete.'
field :name, GraphQL::STRING_TYPE, null: true,
description: 'Name of the job we need to complete.'
end
diff --git a/app/graphql/types/ci/detailed_status_type.rb b/app/graphql/types/ci/detailed_status_type.rb
index 0b643a6b676..6310a62a103 100644
--- a/app/graphql/types/ci/detailed_status_type.rb
+++ b/app/graphql/types/ci/detailed_status_type.rb
@@ -6,6 +6,9 @@ module Types
class DetailedStatusType < BaseObject
graphql_name 'DetailedStatus'
+ field :id, GraphQL::STRING_TYPE, null: false,
+ description: 'ID for a detailed status.',
+ extras: [:parent]
field :group, GraphQL::STRING_TYPE, null: true,
description: 'Group of the status.'
field :icon, GraphQL::STRING_TYPE, null: true,
@@ -29,6 +32,10 @@ module Types
calls_gitaly: true,
description: 'Action information for the status. This includes method, button title, icon, path, and title.'
+ def id(parent:)
+ "#{object.id}-#{parent.object.object.id}"
+ end
+
def action
if object.has_action?
{
diff --git a/app/graphql/types/ci/group_type.rb b/app/graphql/types/ci/group_type.rb
index d6d4252e8d7..3da183cb842 100644
--- a/app/graphql/types/ci/group_type.rb
+++ b/app/graphql/types/ci/group_type.rb
@@ -6,12 +6,14 @@ module Types
class GroupType < BaseObject
graphql_name 'CiGroup'
+ field :id, GraphQL::STRING_TYPE, null: false,
+ description: 'ID for a group.'
field :name, GraphQL::STRING_TYPE, null: true,
- description: 'Name of the job group.'
+ description: 'Name of the job group.'
field :size, GraphQL::INT_TYPE, null: true,
- description: 'Size of the group.'
+ description: 'Size of the group.'
field :jobs, Ci::JobType.connection_type, null: true,
- description: 'Jobs in group.'
+ description: 'Jobs in group.'
field :detailed_status, Types::Ci::DetailedStatusType, null: true,
description: 'Detailed status of the group.'
diff --git a/app/graphql/types/ci/stage_type.rb b/app/graphql/types/ci/stage_type.rb
index a9499e51124..ce3edb6c54f 100644
--- a/app/graphql/types/ci/stage_type.rb
+++ b/app/graphql/types/ci/stage_type.rb
@@ -6,20 +6,16 @@ module Types
graphql_name 'CiStage'
authorize :read_commit_status
- field :name,
- type: GraphQL::STRING_TYPE,
- null: true,
+ field :id, GraphQL::ID_TYPE, null: false,
+ description: 'ID of the stage.'
+ field :name, type: GraphQL::STRING_TYPE, null: true,
description: 'Name of the stage.'
- field :groups,
- type: Ci::GroupType.connection_type,
- null: true,
+ field :groups, type: Ci::GroupType.connection_type, null: true,
extras: [:lookahead],
description: 'Group of jobs for the stage.'
- field :detailed_status, Types::Ci::DetailedStatusType,
- null: true,
+ field :detailed_status, Types::Ci::DetailedStatusType, null: true,
description: 'Detailed status of the stage.'
- field :jobs, Ci::JobType.connection_type,
- null: true,
+ field :jobs, Ci::JobType.connection_type, null: true,
description: 'Jobs for the stage.',
method: 'latest_statuses'
field :status, GraphQL::STRING_TYPE,
diff --git a/app/graphql/types/ci/status_action_type.rb b/app/graphql/types/ci/status_action_type.rb
index 9f7299c0270..a06b09735b3 100644
--- a/app/graphql/types/ci/status_action_type.rb
+++ b/app/graphql/types/ci/status_action_type.rb
@@ -5,6 +5,9 @@ module Types
class StatusActionType < BaseObject
graphql_name 'StatusAction'
+ field :id, GraphQL::STRING_TYPE, null: false,
+ description: 'ID for a status action.',
+ extras: [:parent]
field :button_title, GraphQL::STRING_TYPE, null: true,
description: 'Title for the button, for example: Retry this job.'
field :icon, GraphQL::STRING_TYPE, null: true,
@@ -17,6 +20,10 @@ module Types
field :title, GraphQL::STRING_TYPE, null: true,
description: 'Title for the action, for example: Retry.'
+ def id(parent:)
+ "#{parent.parent.object.object.class.name}-#{parent.object.object.id}"
+ end
+
def action_method
object[:method]
end
diff --git a/app/models/ci/group.rb b/app/models/ci/group.rb
index 47b91fcf2ce..e5cb2026503 100644
--- a/app/models/ci/group.rb
+++ b/app/models/ci/group.rb
@@ -10,6 +10,7 @@ module Ci
class Group
include StaticModel
include Gitlab::Utils::StrongMemoize
+ include GlobalID::Identification
attr_reader :project, :stage, :name, :jobs
@@ -22,6 +23,10 @@ module Ci
@jobs = jobs
end
+ def id
+ "#{stage.id}-#{name}"
+ end
+
def ==(other)
other.present? && other.is_a?(self.class) &&
project == other.project &&
diff --git a/app/models/namespace_setting.rb b/app/models/namespace_setting.rb
index 600abc33471..fc890bf687c 100644
--- a/app/models/namespace_setting.rb
+++ b/app/models/namespace_setting.rb
@@ -15,7 +15,7 @@ class NamespaceSetting < ApplicationRecord
NAMESPACE_SETTINGS_PARAMS = [:default_branch_name, :delayed_project_removal,
:lock_delayed_project_removal, :resource_access_token_creation_allowed,
- :prevent_sharing_groups_outside_hierarchy].freeze
+ :prevent_sharing_groups_outside_hierarchy, :new_user_signups_cap].freeze
self.primary_key = :namespace_id
diff --git a/app/models/user_callout.rb b/app/models/user_callout.rb
index 63727e45a5c..e14ba035cc8 100644
--- a/app/models/user_callout.rb
+++ b/app/models/user_callout.rb
@@ -32,7 +32,9 @@ class UserCallout < ApplicationRecord
pipeline_needs_hover_tip: 30,
web_ide_ci_environments_guidance: 31,
security_configuration_upgrade_banner: 32,
- cloud_licensing_subscription_activation_banner: 33 # EE-only
+ cloud_licensing_subscription_activation_banner: 33, # EE-only
+ trial_status_reminder_d14: 34, # EE-only
+ trial_status_reminder_d3: 35 # EE-only
}
validates :user, presence: true
diff --git a/app/policies/group_policy.rb b/app/policies/group_policy.rb
index ba06b98e906..0b0edc7c452 100644
--- a/app/policies/group_policy.rb
+++ b/app/policies/group_policy.rb
@@ -156,6 +156,7 @@ class GroupPolicy < BasePolicy
enable :set_note_created_at
enable :set_emails_disabled
enable :change_prevent_sharing_groups_outside_hierarchy
+ enable :change_new_user_signups_cap
enable :update_default_branch_protection
enable :create_deploy_token
enable :destroy_deploy_token
diff --git a/app/services/gpg_keys/create_service.rb b/app/services/gpg_keys/create_service.rb
index e41444b2a82..ab8b12732d7 100644
--- a/app/services/gpg_keys/create_service.rb
+++ b/app/services/gpg_keys/create_service.rb
@@ -3,9 +3,17 @@
module GpgKeys
class CreateService < Keys::BaseService
def execute
- key = user.gpg_keys.create(params)
+ key = create(params)
notification_service.new_gpg_key(key) if key.persisted?
key
end
+
+ private
+
+ def create(params)
+ user.gpg_keys.create(params)
+ end
end
end
+
+GpgKeys::CreateService.prepend_mod
diff --git a/app/services/gpg_keys/destroy_service.rb b/app/services/gpg_keys/destroy_service.rb
index cecbfe26611..2e82509897e 100644
--- a/app/services/gpg_keys/destroy_service.rb
+++ b/app/services/gpg_keys/destroy_service.rb
@@ -7,3 +7,5 @@ module GpgKeys
end
end
end
+
+GpgKeys::DestroyService.prepend_mod
diff --git a/app/services/namespace_settings/update_service.rb b/app/services/namespace_settings/update_service.rb
index c71f015b9d4..25525265e1c 100644
--- a/app/services/namespace_settings/update_service.rb
+++ b/app/services/namespace_settings/update_service.rb
@@ -14,7 +14,15 @@ module NamespaceSettings
def execute
validate_resource_access_token_creation_allowed_param
- validate_prevent_sharing_groups_outside_hierarchy_param
+
+ validate_settings_param_for_root_group(
+ param_key: :prevent_sharing_groups_outside_hierarchy,
+ user_policy: :change_prevent_sharing_groups_outside_hierarchy
+ )
+ validate_settings_param_for_root_group(
+ param_key: :new_user_signups_cap,
+ user_policy: :change_new_user_signups_cap
+ )
if group.namespace_settings
group.namespace_settings.attributes = settings_params
@@ -34,17 +42,17 @@ module NamespaceSettings
end
end
- def validate_prevent_sharing_groups_outside_hierarchy_param
- return if settings_params[:prevent_sharing_groups_outside_hierarchy].nil?
+ def validate_settings_param_for_root_group(param_key:, user_policy:)
+ return if settings_params[param_key].nil?
- unless can?(current_user, :change_prevent_sharing_groups_outside_hierarchy, group)
- settings_params.delete(:prevent_sharing_groups_outside_hierarchy)
- group.namespace_settings.errors.add(:prevent_sharing_groups_outside_hierarchy, _('can only be changed by a group admin.'))
+ unless can?(current_user, user_policy, group)
+ settings_params.delete(param_key)
+ group.namespace_settings.errors.add(param_key, _('can only be changed by a group admin.'))
end
unless group.root?
- settings_params.delete(:prevent_sharing_groups_outside_hierarchy)
- group.namespace_settings.errors.add(:prevent_sharing_groups_outside_hierarchy, _('only available on top-level groups.'))
+ settings_params.delete(param_key)
+ group.namespace_settings.errors.add(param_key, _('only available on top-level groups.'))
end
end
end
diff --git a/app/services/snippets/create_service.rb b/app/services/snippets/create_service.rb
index d9a46890c45..6d3b63de9fd 100644
--- a/app/services/snippets/create_service.rb
+++ b/app/services/snippets/create_service.rb
@@ -20,14 +20,12 @@ module Snippets
@snippet.author = current_user
- if Feature.enabled?(:snippet_spam)
- Spam::SpamActionService.new(
- spammable: @snippet,
- spam_params: spam_params,
- user: current_user,
- action: :create
- ).execute
- end
+ Spam::SpamActionService.new(
+ spammable: @snippet,
+ spam_params: spam_params,
+ user: current_user,
+ action: :create
+ ).execute
if save_and_commit
UserAgentDetailService.new(spammable: @snippet, spam_params: spam_params).create
diff --git a/app/services/snippets/update_service.rb b/app/services/snippets/update_service.rb
index f8374cc88bb..d83b21271c0 100644
--- a/app/services/snippets/update_service.rb
+++ b/app/services/snippets/update_service.rb
@@ -23,14 +23,12 @@ module Snippets
update_snippet_attributes(snippet)
- if Feature.enabled?(:snippet_spam)
- Spam::SpamActionService.new(
- spammable: snippet,
- spam_params: spam_params,
- user: current_user,
- action: :update
- ).execute
- end
+ Spam::SpamActionService.new(
+ spammable: snippet,
+ spam_params: spam_params,
+ user: current_user,
+ action: :update
+ ).execute
if save_and_commit(snippet)
Gitlab::UsageDataCounters::SnippetCounter.count(:update)