summaryrefslogtreecommitdiff
path: root/app
diff options
context:
space:
mode:
authorGitLab Bot <gitlab-bot@gitlab.com>2021-02-24 15:11:10 +0000
committerGitLab Bot <gitlab-bot@gitlab.com>2021-02-24 15:11:10 +0000
commit958d8a85d32fece017eac7d99bf28860b01a49d8 (patch)
tree3c84c8447a8d9a6f7ccf7401eeb3cd268570c94f /app
parent35c5f0c35c83f3c5f8d33fb61713495e29bdec4d (diff)
downloadgitlab-ce-958d8a85d32fece017eac7d99bf28860b01a49d8.tar.gz
Add latest changes from gitlab-org/gitlab@master
Diffstat (limited to 'app')
-rw-r--r--app/assets/javascripts/graphql_shared/queries/project_user_members_search.query.graphql14
-rw-r--r--app/assets/javascripts/import_entities/components/import_status.vue2
-rw-r--r--app/assets/javascripts/import_entities/import_groups/components/import_table.vue7
-rw-r--r--app/assets/javascripts/import_entities/import_groups/components/import_table_row.vue159
-rw-r--r--app/assets/javascripts/import_entities/import_groups/graphql/queries/group.query.graphql5
-rw-r--r--app/assets/javascripts/import_entities/import_groups/index.js5
-rw-r--r--app/assets/stylesheets/page_bundles/import.scss7
-rw-r--r--app/finders/users_finder.rb9
-rw-r--r--app/graphql/mutations/boards/lists/update.rb2
-rw-r--r--app/graphql/types/board_list_type.rb2
-rw-r--r--app/graphql/types/issuable_state_enum.rb8
-rw-r--r--app/models/concerns/boards/listable.rb20
-rw-r--r--app/models/list.rb20
-rw-r--r--app/models/user.rb1
-rw-r--r--app/services/security/vulnerability_uuid.rb9
-rw-r--r--app/views/admin/dashboard/index.html.haml2
-rw-r--r--app/views/import/bulk_imports/status.html.haml3
17 files changed, 193 insertions, 82 deletions
diff --git a/app/assets/javascripts/graphql_shared/queries/project_user_members_search.query.graphql b/app/assets/javascripts/graphql_shared/queries/project_user_members_search.query.graphql
new file mode 100644
index 00000000000..1d9497d65ce
--- /dev/null
+++ b/app/assets/javascripts/graphql_shared/queries/project_user_members_search.query.graphql
@@ -0,0 +1,14 @@
+query searchProjectMembers($fullPath: ID!, $search: String) {
+ project(fullPath: $fullPath) {
+ projectMembers(search: $search) {
+ nodes {
+ user {
+ id
+ name
+ username
+ avatarUrl
+ }
+ }
+ }
+ }
+}
diff --git a/app/assets/javascripts/import_entities/components/import_status.vue b/app/assets/javascripts/import_entities/components/import_status.vue
index 9e3347a657f..8df51ef7f9b 100644
--- a/app/assets/javascripts/import_entities/components/import_status.vue
+++ b/app/assets/javascripts/import_entities/components/import_status.vue
@@ -34,7 +34,7 @@ export default {
</script>
<template>
- <div>
+ <div class="gl-display-flex gl-h-7 gl-align-items-center">
<gl-loading-icon
v-if="mappedStatus.loadingIcon"
:inline="true"
diff --git a/app/assets/javascripts/import_entities/import_groups/components/import_table.vue b/app/assets/javascripts/import_entities/import_groups/components/import_table.vue
index c0fd076ee6b..d0a0a3463c0 100644
--- a/app/assets/javascripts/import_entities/import_groups/components/import_table.vue
+++ b/app/assets/javascripts/import_entities/import_groups/components/import_table.vue
@@ -33,6 +33,10 @@ export default {
type: String,
required: true,
},
+ groupPathRegex: {
+ type: RegExp,
+ required: true,
+ },
},
data() {
@@ -165,12 +169,13 @@ export default {
<th class="gl-py-4 import-jobs-status-col">{{ __('Status') }}</th>
<th class="gl-py-4 import-jobs-cta-col"></th>
</thead>
- <tbody>
+ <tbody class="gl-vertical-align-top">
<template v-for="group in bulkImportSourceGroups.nodes">
<import-table-row
:key="group.id"
:group="group"
:available-namespaces="availableNamespaces"
+ :group-path-regex="groupPathRegex"
@update-target-namespace="updateTargetNamespace(group.id, $event)"
@update-new-name="updateNewName(group.id, $event)"
@import-group="importGroup(group.id)"
diff --git a/app/assets/javascripts/import_entities/import_groups/components/import_table_row.vue b/app/assets/javascripts/import_entities/import_groups/components/import_table_row.vue
index 3b8abeae122..aed879e75fb 100644
--- a/app/assets/javascripts/import_entities/import_groups/components/import_table_row.vue
+++ b/app/assets/javascripts/import_entities/import_groups/components/import_table_row.vue
@@ -1,16 +1,29 @@
<script>
-import { GlButton, GlIcon, GlLink, GlFormInput } from '@gitlab/ui';
+import {
+ GlButton,
+ GlDropdown,
+ GlDropdownDivider,
+ GlDropdownItem,
+ GlDropdownSectionHeader,
+ GlIcon,
+ GlLink,
+ GlFormInput,
+} from '@gitlab/ui';
import { joinPaths } from '~/lib/utils/url_utility';
-import { s__ } from '~/locale';
-import Select2Select from '~/vue_shared/components/select2_select.vue';
import ImportStatus from '../../components/import_status.vue';
import { STATUSES } from '../../constants';
+import groupQuery from '../graphql/queries/group.query.graphql';
+
+const DEBOUNCE_INTERVAL = 300;
export default {
components: {
- Select2Select,
ImportStatus,
GlButton,
+ GlDropdown,
+ GlDropdownDivider,
+ GlDropdownItem,
+ GlDropdownSectionHeader,
GlLink,
GlIcon,
GlFormInput,
@@ -24,82 +37,131 @@ export default {
type: Array,
required: true,
},
+ groupPathRegex: {
+ type: RegExp,
+ required: true,
+ },
},
- computed: {
- isDisabled() {
- return this.group.status !== STATUSES.NONE;
+
+ apollo: {
+ existingGroup: {
+ query: groupQuery,
+ debounce: DEBOUNCE_INTERVAL,
+ variables() {
+ return {
+ fullPath: this.fullPath,
+ };
+ },
+ skip() {
+ return !this.isNameValid || this.isAlreadyImported;
+ },
},
+ },
- isFinished() {
- return this.group.status === STATUSES.FINISHED;
+ computed: {
+ importTarget() {
+ return this.group.import_target;
},
- select2Options() {
- const availableNamespacesData = this.availableNamespaces.map((namespace) => ({
- id: namespace.full_path,
- text: namespace.full_path,
- }));
+ isInvalid() {
+ return Boolean(!this.isNameValid || this.existingGroup);
+ },
- const select2Config = {
- data: [{ id: '', text: s__('BulkImport|No parent') }],
- };
+ isNameValid() {
+ return this.groupPathRegex.test(this.importTarget.new_name);
+ },
- if (availableNamespacesData.length) {
- select2Config.data.push({
- text: s__('BulkImport|Existing groups'),
- children: availableNamespacesData,
- });
- }
+ isAlreadyImported() {
+ return this.group.status !== STATUSES.NONE;
+ },
- return select2Config;
+ isFinished() {
+ return this.group.status === STATUSES.FINISHED;
},
- },
- methods: {
- getPath(group) {
- return `${group.import_target.target_namespace}/${group.import_target.new_name}`;
+
+ fullPath() {
+ return `${this.importTarget.target_namespace}/${this.importTarget.new_name}`;
},
- getFullPath(group) {
- return joinPaths(gon.relative_url_root || '/', this.getPath(group));
+ absolutePath() {
+ return joinPaths(gon.relative_url_root || '/', this.fullPath);
},
},
};
</script>
<template>
- <tr class="gl-border-gray-200 gl-border-0 gl-border-b-1">
+ <tr class="gl-border-gray-200 gl-border-0 gl-border-b-1 gl-border-solid">
<td class="gl-p-4">
- <gl-link :href="group.web_url" target="_blank">
+ <gl-link
+ :href="group.web_url"
+ target="_blank"
+ class="gl-display-flex gl-align-items-center gl-h-7"
+ >
{{ group.full_path }} <gl-icon name="external-link" />
</gl-link>
</td>
<td class="gl-p-4">
- <gl-link v-if="isFinished" :href="getFullPath(group)">{{ getPath(group) }}</gl-link>
+ <gl-link
+ v-if="isFinished"
+ class="gl-display-flex gl-align-items-center gl-h-7"
+ :href="absolutePath"
+ >
+ {{ fullPath }}
+ </gl-link>
<div
v-else
class="import-entities-target-select gl-display-flex gl-align-items-stretch"
:class="{
- disabled: isDisabled,
+ disabled: isAlreadyImported,
}"
>
- <select2-select
- :disabled="isDisabled"
- :options="select2Options"
- :value="group.import_target.target_namespace"
- @input="$emit('update-target-namespace', $event)"
- />
+ <gl-dropdown
+ :text="importTarget.target_namespace"
+ :disabled="isAlreadyImported"
+ toggle-class="gl-rounded-top-right-none! gl-rounded-bottom-right-none!"
+ class="import-entities-namespace-dropdown gl-h-7 gl-flex-fill-1"
+ >
+ <gl-dropdown-item @click="$emit('update-target-namespace', '')">{{
+ s__('BulkImport|No parent')
+ }}</gl-dropdown-item>
+ <template v-if="availableNamespaces.length">
+ <gl-dropdown-divider />
+ <gl-dropdown-section-header>
+ {{ s__('BulkImport|Existing groups') }}
+ </gl-dropdown-section-header>
+ <gl-dropdown-item
+ v-for="ns in availableNamespaces"
+ :key="ns.full_path"
+ @click="$emit('update-target-namespace', ns.full_path)"
+ >
+ {{ ns.full_path }}
+ </gl-dropdown-item>
+ </template>
+ </gl-dropdown>
<div
- class="import-entities-target-select-separator gl-px-3 gl-display-flex gl-align-items-center gl-border-solid gl-border-0 gl-border-t-1 gl-border-b-1"
+ class="import-entities-target-select-separator gl-h-7 gl-px-3 gl-display-flex gl-align-items-center gl-border-solid gl-border-0 gl-border-t-1 gl-border-b-1"
>
/
</div>
- <gl-form-input
- class="gl-rounded-top-left-none gl-rounded-bottom-left-none"
- :disabled="isDisabled"
- :value="group.import_target.new_name"
- @input="$emit('update-new-name', $event)"
- />
+ <div class="gl-flex-fill-1">
+ <gl-form-input
+ class="gl-rounded-top-left-none gl-rounded-bottom-left-none"
+ :class="{ 'is-invalid': isInvalid && !isAlreadyImported }"
+ :disabled="isAlreadyImported"
+ :value="importTarget.new_name"
+ @input="$emit('update-new-name', $event)"
+ />
+ <p v-if="isInvalid" class="gl-text-red-500 gl-m-0 gl-mt-2">
+ <template v-if="!isNameValid">
+ {{ __('Please choose a group URL with no special characters.') }}
+ </template>
+ <template v-else-if="existingGroup">
+ {{ s__('BulkImport|Name already exists.') }}
+ </template>
+ </p>
+ </div>
</div>
</td>
<td class="gl-p-4 gl-white-space-nowrap">
@@ -107,7 +169,8 @@ export default {
</td>
<td class="gl-p-4">
<gl-button
- v-if="!isDisabled"
+ v-if="!isAlreadyImported"
+ :disabled="isInvalid"
variant="success"
category="secondary"
@click="$emit('import-group')"
diff --git a/app/assets/javascripts/import_entities/import_groups/graphql/queries/group.query.graphql b/app/assets/javascripts/import_entities/import_groups/graphql/queries/group.query.graphql
new file mode 100644
index 00000000000..52df3581ac4
--- /dev/null
+++ b/app/assets/javascripts/import_entities/import_groups/graphql/queries/group.query.graphql
@@ -0,0 +1,5 @@
+query group($fullPath: ID!) {
+ existingGroup: group(fullPath: $fullPath) {
+ id
+ }
+}
diff --git a/app/assets/javascripts/import_entities/import_groups/index.js b/app/assets/javascripts/import_entities/import_groups/index.js
index cf56bb804d4..cc60c8cbdb0 100644
--- a/app/assets/javascripts/import_entities/import_groups/index.js
+++ b/app/assets/javascripts/import_entities/import_groups/index.js
@@ -1,6 +1,5 @@
import Vue from 'vue';
import VueApollo from 'vue-apollo';
-import { parseBoolean } from '~/lib/utils/common_utils';
import Translate from '~/vue_shared/translate';
import ImportTable from './components/import_table.vue';
import { createApolloClient } from './graphql/client_factory';
@@ -17,7 +16,7 @@ export function mountImportGroupsApp(mountElement) {
createBulkImportPath,
jobsPath,
sourceUrl,
- canCreateGroup,
+ groupPathRegex,
} = mountElement.dataset;
const apolloProvider = new VueApollo({
defaultClient: createApolloClient({
@@ -38,7 +37,7 @@ export function mountImportGroupsApp(mountElement) {
return createElement(ImportTable, {
props: {
sourceUrl,
- canCreateGroup: parseBoolean(canCreateGroup),
+ groupPathRegex: new RegExp(`^(${groupPathRegex})$`),
},
});
},
diff --git a/app/assets/stylesheets/page_bundles/import.scss b/app/assets/stylesheets/page_bundles/import.scss
index 453b810196b..525481638f3 100644
--- a/app/assets/stylesheets/page_bundles/import.scss
+++ b/app/assets/stylesheets/page_bundles/import.scss
@@ -1,5 +1,12 @@
@import 'mixins_and_variables_and_functions';
+// Fixing double scrollbar issue
+// See https://gitlab.com/gitlab-org/gitlab-ui/-/issues/1156 and
+// https://gitlab.com/gitlab-org/gitlab/-/merge_requests/54837
+.import-entities-namespace-dropdown.show.dropdown .dropdown-menu {
+ max-height: initial;
+}
+
.import-jobs-to-col {
width: 39%;
}
diff --git a/app/finders/users_finder.rb b/app/finders/users_finder.rb
index 42042406f3f..5ac905e0dd4 100644
--- a/app/finders/users_finder.rb
+++ b/app/finders/users_finder.rb
@@ -14,6 +14,7 @@
# active: boolean
# blocked: boolean
# external: boolean
+# non_external: boolean
# without_projects: boolean
# sort: string
# id: integer
@@ -40,6 +41,7 @@ class UsersFinder
users = by_active(users)
users = by_external_identity(users)
users = by_external(users)
+ users = by_non_external(users)
users = by_2fa(users)
users = by_created_at(users)
users = by_without_projects(users)
@@ -97,13 +99,18 @@ class UsersFinder
# rubocop: disable CodeReuse/ActiveRecord
def by_external(users)
- return users = users.where.not(external: true) unless current_user&.admin?
return users unless params[:external]
users.external
end
# rubocop: enable CodeReuse/ActiveRecord
+ def by_non_external(users)
+ return users unless params[:non_external]
+
+ users.non_external
+ end
+
def by_2fa(users)
case params[:two_factor]
when 'enabled'
diff --git a/app/graphql/mutations/boards/lists/update.rb b/app/graphql/mutations/boards/lists/update.rb
index d30d1d89bb2..0a025a21353 100644
--- a/app/graphql/mutations/boards/lists/update.rb
+++ b/app/graphql/mutations/boards/lists/update.rb
@@ -17,7 +17,7 @@ module Mutations
argument :collapsed, GraphQL::BOOLEAN_TYPE,
required: false,
- description: 'Indicates if list is collapsed for this user.'
+ description: 'Indicates if the list is collapsed for this user.'
field :list,
Types::BoardListType,
diff --git a/app/graphql/types/board_list_type.rb b/app/graphql/types/board_list_type.rb
index 46b49c5d8a4..b367aa946ae 100644
--- a/app/graphql/types/board_list_type.rb
+++ b/app/graphql/types/board_list_type.rb
@@ -19,7 +19,7 @@ module Types
field :label, Types::LabelType, null: true,
description: 'Label of the list.'
field :collapsed, GraphQL::BOOLEAN_TYPE, null: true,
- description: 'Indicates if list is collapsed for this user.'
+ description: 'Indicates if the list is collapsed for this user.'
field :issues_count, GraphQL::INT_TYPE, null: true,
description: 'Count of issues in the list.'
diff --git a/app/graphql/types/issuable_state_enum.rb b/app/graphql/types/issuable_state_enum.rb
index 543b7f8e5b2..96a72b6a972 100644
--- a/app/graphql/types/issuable_state_enum.rb
+++ b/app/graphql/types/issuable_state_enum.rb
@@ -5,9 +5,9 @@ module Types
graphql_name 'IssuableState'
description 'State of a GitLab issue or merge request'
- value 'opened'
- value 'closed'
- value 'locked'
- value 'all'
+ value 'opened', description: 'In open state'
+ value 'closed', description: 'In closed state'
+ value 'locked', description: 'Discussion has been locked'
+ value 'all', description: 'All available'
end
end
diff --git a/app/models/concerns/boards/listable.rb b/app/models/concerns/boards/listable.rb
index b7c0a8b3489..d6863e87261 100644
--- a/app/models/concerns/boards/listable.rb
+++ b/app/models/concerns/boards/listable.rb
@@ -13,6 +13,14 @@ module Boards
scope :ordered, -> { order(:list_type, :position) }
scope :destroyable, -> { where(list_type: list_types.slice(*destroyable_types).values) }
scope :movable, -> { where(list_type: list_types.slice(*movable_types).values) }
+
+ class << self
+ def preload_preferences_for_user(lists, user)
+ return unless user
+
+ lists.each { |list| list.preferences_for(user) }
+ end
+ end
end
class_methods do
@@ -33,6 +41,18 @@ module Boards
self.class.movable_types.include?(list_type&.to_sym)
end
+ def collapsed?(user)
+ preferences = preferences_for(user)
+
+ preferences.collapsed?
+ end
+
+ def update_preferences_for(user, preferences = {})
+ return unless user
+
+ preferences_for(user).update(preferences)
+ end
+
def title
if label?
label.name
diff --git a/app/models/list.rb b/app/models/list.rb
index 5bd00a1d7ef..e1954ed72c4 100644
--- a/app/models/list.rb
+++ b/app/models/list.rb
@@ -18,14 +18,6 @@ class List < ApplicationRecord
alias_method :preferences, :list_user_preferences
- class << self
- def preload_preferences_for_user(lists, user)
- return unless user
-
- lists.each { |list| list.preferences_for(user) }
- end
- end
-
def preferences_for(user)
return preferences.build unless user
@@ -39,18 +31,6 @@ class List < ApplicationRecord
end
end
- def update_preferences_for(user, preferences = {})
- return unless user
-
- preferences_for(user).update(preferences)
- end
-
- def collapsed?(user)
- preferences = preferences_for(user)
-
- preferences.collapsed?
- end
-
def as_json(options = {})
super(options).tap do |json|
json[:collapsed] = false
diff --git a/app/models/user.rb b/app/models/user.rb
index d91fc3ebce4..4c2bef2c725 100644
--- a/app/models/user.rb
+++ b/app/models/user.rb
@@ -360,6 +360,7 @@ class User < ApplicationRecord
scope :blocked, -> { with_states(:blocked, :ldap_blocked) }
scope :blocked_pending_approval, -> { with_states(:blocked_pending_approval) }
scope :external, -> { where(external: true) }
+ scope :non_external, -> { where(external: false) }
scope :confirmed, -> { where.not(confirmed_at: nil) }
scope :active, -> { with_state(:active).non_internal }
scope :active_without_ghosts, -> { with_state(:active).without_ghosts }
diff --git a/app/services/security/vulnerability_uuid.rb b/app/services/security/vulnerability_uuid.rb
new file mode 100644
index 00000000000..3eab0f3dad6
--- /dev/null
+++ b/app/services/security/vulnerability_uuid.rb
@@ -0,0 +1,9 @@
+# frozen_string_literal: true
+
+module Security
+ class VulnerabilityUUID
+ def self.generate(report_type:, primary_identifier_fingerprint:, location_fingerprint:, project_id:)
+ Gitlab::UUID.v5("#{report_type}-#{primary_identifier_fingerprint}-#{location_fingerprint}-#{project_id}")
+ end
+ end
+end
diff --git a/app/views/admin/dashboard/index.html.haml b/app/views/admin/dashboard/index.html.haml
index f6ebc4c465d..f16158d5656 100644
--- a/app/views/admin/dashboard/index.html.haml
+++ b/app/views/admin/dashboard/index.html.haml
@@ -60,7 +60,7 @@
.d-flex.align-items-center
= sprite_icon('group', size: 16, css_class: 'gl-text-gray-700')
%h3.gl-m-0.gl-ml-3= approximate_count_with_delimiters(@counts, Group)
- .gl-mt-3.text-uppercase= s_('AdminArea|Projects')
+ .gl-mt-3.text-uppercase= s_('AdminArea|Groups')
= link_to(s_('AdminArea|New group'), new_admin_group_path, class: "btn gl-button btn-default")
.gl-card-footer.gl-bg-transparent
.d-flex.align-items-center
diff --git a/app/views/import/bulk_imports/status.html.haml b/app/views/import/bulk_imports/status.html.haml
index 778bc1ef1a4..917d88af75a 100644
--- a/app/views/import/bulk_imports/status.html.haml
+++ b/app/views/import/bulk_imports/status.html.haml
@@ -9,4 +9,5 @@
available_namespaces_path: import_available_namespaces_path(format: :json),
create_bulk_import_path: import_bulk_imports_path(format: :json),
jobs_path: realtime_changes_import_bulk_imports_path(format: :json),
- source_url: @source_url } }
+ source_url: @source_url,
+ group_path_regex: Gitlab::PathRegex::NAMESPACE_FORMAT_REGEX_JS } }