diff options
author | GitLab Bot <gitlab-bot@gitlab.com> | 2021-02-24 15:11:10 +0000 |
---|---|---|
committer | GitLab Bot <gitlab-bot@gitlab.com> | 2021-02-24 15:11:10 +0000 |
commit | 958d8a85d32fece017eac7d99bf28860b01a49d8 (patch) | |
tree | 3c84c8447a8d9a6f7ccf7401eeb3cd268570c94f /app | |
parent | 35c5f0c35c83f3c5f8d33fb61713495e29bdec4d (diff) | |
download | gitlab-ce-958d8a85d32fece017eac7d99bf28860b01a49d8.tar.gz |
Add latest changes from gitlab-org/gitlab@master
Diffstat (limited to 'app')
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 } } |