diff options
author | GitLab Bot <gitlab-bot@gitlab.com> | 2021-08-24 00:11:23 +0300 |
---|---|---|
committer | GitLab Bot <gitlab-bot@gitlab.com> | 2021-08-24 00:11:23 +0300 |
commit | 0510f42da38bb0eb1d06aeb82b4d16ed71135776 (patch) | |
tree | b83ce9d25ff8bf1938c44e51916fd5bd86e5ee8b /app | |
parent | 5e9fe672fa0eda6322bd392e8502d1886804bd07 (diff) |
Add latest changes from gitlab-org/gitlab@master
Diffstat (limited to 'app')
15 files changed, 173 insertions, 69 deletions
diff --git a/app/assets/javascripts/pipelines/components/graph/accessors.js b/app/assets/javascripts/pipelines/components/graph/accessors.js deleted file mode 100644 index 6ece855bcd8..00000000000 --- a/app/assets/javascripts/pipelines/components/graph/accessors.js +++ /dev/null @@ -1,25 +0,0 @@ -import { get } from 'lodash'; -import { REST, GRAPHQL } from './constants'; - -const accessors = { - [REST]: { - detailsPath: 'details_path', - groupId: 'id', - hasDetails: 'has_details', - pipelineStatus: ['details', 'status'], - sourceJob: ['source_job', 'name'], - }, - [GRAPHQL]: { - detailsPath: 'detailsPath', - groupId: 'name', - hasDetails: 'hasDetails', - pipelineStatus: 'status', - sourceJob: ['sourceJob', 'name'], - }, -}; - -const accessValue = (dataMethod, prop, item) => { - return get(item, accessors[dataMethod][prop]); -}; - -export { accessors, accessValue }; diff --git a/app/assets/javascripts/pipelines/components/graph/constants.js b/app/assets/javascripts/pipelines/components/graph/constants.js index dd9cdae518f..0b59612b25c 100644 --- a/app/assets/javascripts/pipelines/components/graph/constants.js +++ b/app/assets/javascripts/pipelines/components/graph/constants.js @@ -8,9 +8,6 @@ export const UPSTREAM = 'upstream'; */ export const ONE_COL_WIDTH = 180; -export const REST = 'rest'; -export const GRAPHQL = 'graphql'; - export const STAGE_VIEW = 'stage'; export const LAYER_VIEW = 'layer'; export const VIEW_TYPE_KEY = 'pipeline_graph_view_type'; diff --git a/app/assets/javascripts/pipelines/components/graph/job_item.vue b/app/assets/javascripts/pipelines/components/graph/job_item.vue index 6584d89d87c..55088c70c33 100644 --- a/app/assets/javascripts/pipelines/components/graph/job_item.vue +++ b/app/assets/javascripts/pipelines/components/graph/job_item.vue @@ -7,8 +7,7 @@ import CiIcon from '~/vue_shared/components/ci_icon.vue'; import { reportToSentry } from '../../utils'; import ActionComponent from '../jobs_shared/action_component.vue'; import JobNameComponent from '../jobs_shared/job_name_component.vue'; -import { accessValue } from './accessors'; -import { REST, SINGLE_JOB } from './constants'; +import { SINGLE_JOB } from './constants'; /** * Renders the badge for the pipeline graph and the job's dropdown. @@ -47,11 +46,6 @@ export default { GlTooltip: GlTooltipDirective, }, mixins: [delayedJobMixin], - inject: { - dataMethod: { - default: REST, - }, - }, props: { job: { type: Object, @@ -111,10 +105,10 @@ export default { return this.pipelineId > -1 ? `${this.job.name}-${this.pipelineId}` : ''; }, detailsPath() { - return accessValue(this.dataMethod, 'detailsPath', this.status); + return this.status.detailsPath; }, hasDetails() { - return accessValue(this.dataMethod, 'hasDetails', this.status); + return this.status.hasDetails; }, isSingleItem() { return this.type === SINGLE_JOB; @@ -189,7 +183,7 @@ export default { if (this.isSingleItem) { /* This is so the jobDropdown still toggles. Issue to refactor: - https://gitlab.com/gitlab-org/gitlab/-/issues/267117 + https://gitlab.com/gitlab-org/gitlab/-/issues/267117 */ evt.stopPropagation(); } diff --git a/app/assets/javascripts/pipelines/components/graph/linked_pipeline.vue b/app/assets/javascripts/pipelines/components/graph/linked_pipeline.vue index dd8a354511a..ac038575387 100644 --- a/app/assets/javascripts/pipelines/components/graph/linked_pipeline.vue +++ b/app/assets/javascripts/pipelines/components/graph/linked_pipeline.vue @@ -4,8 +4,7 @@ import { BV_HIDE_TOOLTIP } from '~/lib/utils/constants'; import { __, sprintf } from '~/locale'; import CiStatus from '~/vue_shared/components/ci_icon.vue'; import { reportToSentry } from '../../utils'; -import { accessValue } from './accessors'; -import { DOWNSTREAM, REST, UPSTREAM } from './constants'; +import { DOWNSTREAM, UPSTREAM } from './constants'; export default { directives: { @@ -18,11 +17,6 @@ export default { GlLoadingIcon, GlBadge, }, - inject: { - dataMethod: { - default: REST, - }, - }, props: { columnTitle: { type: String, @@ -40,20 +34,9 @@ export default { type: String, required: true, }, - /* - The next two props will be removed or required - once the graph transition is done. - See: https://gitlab.com/gitlab-org/gitlab/-/issues/291043 - */ isLoading: { type: Boolean, - required: false, - default: false, - }, - projectId: { - type: Number, - required: false, - default: -1, + required: true, }, }, computed: { @@ -65,7 +48,7 @@ export default { return `js-linked-pipeline-${this.pipeline.id}`; }, pipelineStatus() { - return accessValue(this.dataMethod, 'pipelineStatus', this.pipeline); + return this.pipeline.status; }, projectName() { return this.pipeline.project.name; @@ -97,12 +80,10 @@ export default { return this.type === UPSTREAM; }, isSameProject() { - return this.projectId > -1 - ? this.projectId === this.pipeline.project.id - : !this.pipeline.multiproject; + return !this.pipeline.multiproject; }, sourceJobName() { - return accessValue(this.dataMethod, 'sourceJob', this.pipeline); + return this.pipeline.sourceJob?.name ?? ''; }, sourceJobInfo() { return this.isDownstream ? sprintf(__('Created by %{job}'), { job: this.sourceJobName }) : ''; diff --git a/app/assets/javascripts/pipelines/components/graph/stage_column_component.vue b/app/assets/javascripts/pipelines/components/graph/stage_column_component.vue index d34ae8036ed..c91e88dbd5b 100644 --- a/app/assets/javascripts/pipelines/components/graph/stage_column_component.vue +++ b/app/assets/javascripts/pipelines/components/graph/stage_column_component.vue @@ -4,8 +4,6 @@ import glFeatureFlagMixin from '~/vue_shared/mixins/gl_feature_flags_mixin'; import { reportToSentry } from '../../utils'; import MainGraphWrapper from '../graph_shared/main_graph_wrapper.vue'; import ActionComponent from '../jobs_shared/action_component.vue'; -import { accessValue } from './accessors'; -import { GRAPHQL } from './constants'; import JobGroupDropdown from './job_group_dropdown.vue'; import JobItem from './job_item.vue'; @@ -97,7 +95,7 @@ export default { }, methods: { getGroupId(group) { - return accessValue(GRAPHQL, 'groupId', group); + return group.name; }, groupId(group) { return `ci-badge-${escape(group.name)}`; diff --git a/app/assets/javascripts/pipelines/pipeline_details_graph.js b/app/assets/javascripts/pipelines/pipeline_details_graph.js index 39c3c2ea5c5..9dd5cd7b281 100644 --- a/app/assets/javascripts/pipelines/pipeline_details_graph.js +++ b/app/assets/javascripts/pipelines/pipeline_details_graph.js @@ -1,6 +1,5 @@ import Vue from 'vue'; import VueApollo from 'vue-apollo'; -import { GRAPHQL } from './components/graph/constants'; import PipelineGraphWrapper from './components/graph/graph_component_wrapper.vue'; import { reportToSentry } from './utils'; @@ -23,7 +22,6 @@ const createPipelinesDetailApp = ( pipelineProjectPath, pipelineIid, graphqlResourceEtag, - dataMethod: GRAPHQL, }, errorCaptured(err, _vm, info) { reportToSentry('pipeline_details_graph', `error: ${err}, info: ${info}`); diff --git a/app/finders/groups/user_groups_finder.rb b/app/finders/groups/user_groups_finder.rb new file mode 100644 index 00000000000..5946e3a8933 --- /dev/null +++ b/app/finders/groups/user_groups_finder.rb @@ -0,0 +1,60 @@ +# frozen_string_literal: true + +# Groups::UserGroupsFinder +# +# Used to filter Groups where a user is member +# +# Arguments: +# current_user - user requesting group info on target user +# target_user - user for which groups will be found +# params: +# permissions: string (see Types::Groups::UserPermissionsEnum) +# search: string used for search on path and group name +# +# Initially created to filter user groups and descendants where the user can create projects +module Groups + class UserGroupsFinder + def initialize(current_user, target_user, params = {}) + @current_user = current_user + @target_user = target_user + @params = params + end + + def execute + return Group.none unless current_user&.can?(:read_user_groups, target_user) + return Group.none if target_user.blank? + + items = by_permission_scope + items = by_search(items) + + sort(items) + end + + private + + attr_reader :current_user, :target_user, :params + + def sort(items) + items.order(path: :asc, id: :asc) # rubocop: disable CodeReuse/ActiveRecord + end + + def by_search(items) + return items if params[:search].blank? + + items.search(params[:search]) + end + + def by_permission_scope + if permission_scope_create_projects? + target_user.manageable_groups(include_groups_with_developer_maintainer_access: true) + else + target_user.groups + end + end + + def permission_scope_create_projects? + params[:permission_scope] == :create_projects && + Feature.enabled?(:paginatable_namespace_drop_down_for_project_creation, current_user, default_enabled: :yaml) + end + end +end diff --git a/app/graphql/resolvers/base_resolver.rb b/app/graphql/resolvers/base_resolver.rb index 48563633d11..20ed089d159 100644 --- a/app/graphql/resolvers/base_resolver.rb +++ b/app/graphql/resolvers/base_resolver.rb @@ -124,6 +124,16 @@ module Resolvers [args[:iid], args[:iids]].any? ? 0 : 0.01 end + def self.before_connection_authorization(&block) + @before_connection_authorization_block = block + end + + # rubocop: disable Style/TrivialAccessors + def self.before_connection_authorization_block + @before_connection_authorization_block + end + # rubocop: enable Style/TrivialAccessors + def offset_pagination(relation) ::Gitlab::Graphql::Pagination::OffsetPaginatedRelation.new(relation) end diff --git a/app/graphql/resolvers/users/groups_resolver.rb b/app/graphql/resolvers/users/groups_resolver.rb new file mode 100644 index 00000000000..0899b08e19c --- /dev/null +++ b/app/graphql/resolvers/users/groups_resolver.rb @@ -0,0 +1,44 @@ +# frozen_string_literal: true + +module Resolvers + module Users + class GroupsResolver < BaseResolver + include Gitlab::Graphql::Authorize::AuthorizeResource + include LooksAhead + + type Types::GroupType.connection_type, null: true + + authorize :read_user_groups + authorizes_object! + + argument :search, GraphQL::Types::String, + required: false, + description: 'Search by group name or path.' + argument :permission_scope, + ::Types::PermissionTypes::GroupEnum, + required: false, + description: 'Filter by permissions the user has on groups.' + + before_connection_authorization do |nodes, current_user| + Preloaders::UserMaxAccessLevelInGroupsPreloader.new(nodes, current_user).execute + end + + def resolve_with_lookahead(**args) + return unless Feature.enabled?(:paginatable_namespace_drop_down_for_project_creation, current_user, default_enabled: :yaml) + + apply_lookahead(Groups::UserGroupsFinder.new(current_user, object, args).execute) + end + + private + + def preloads + { + path: [:route], + full_path: [:route] + } + end + end + end +end + +Resolvers::Users::GroupsResolver.prepend_mod_with('Resolvers::Users::GroupsResolver') diff --git a/app/graphql/types/permission_types/group.rb b/app/graphql/types/permission_types/group.rb index 29833993ce6..6a1031e2532 100644 --- a/app/graphql/types/permission_types/group.rb +++ b/app/graphql/types/permission_types/group.rb @@ -5,7 +5,7 @@ module Types class Group < BasePermissionType graphql_name 'GroupPermissions' - abilities :read_group + abilities :read_group, :create_projects end end end diff --git a/app/graphql/types/permission_types/group_enum.rb b/app/graphql/types/permission_types/group_enum.rb new file mode 100644 index 00000000000..cc4f5e9f1f0 --- /dev/null +++ b/app/graphql/types/permission_types/group_enum.rb @@ -0,0 +1,12 @@ +# frozen_string_literal: true + +module Types + module PermissionTypes + class GroupEnum < BaseEnum + graphql_name 'GroupPermission' + description 'User permission on groups' + + value 'CREATE_PROJECTS', value: :create_projects, description: 'Groups where the user can create projects.' + end + end +end diff --git a/app/graphql/types/user_interface.rb b/app/graphql/types/user_interface.rb index 71c6b7f3019..47a25e09c7d 100644 --- a/app/graphql/types/user_interface.rb +++ b/app/graphql/types/user_interface.rb @@ -59,6 +59,9 @@ module Types type: Types::GroupMemberType.connection_type, null: true, description: 'Group memberships of the user.' + field :groups, + resolver: Resolvers::Users::GroupsResolver, + description: 'Groups where the user has access.' field :group_count, resolver: Resolvers::Users::GroupCountResolver, description: 'Group count for the user.' diff --git a/app/helpers/issues_helper.rb b/app/helpers/issues_helper.rb index bbafdac9a7f..152d813daea 100644 --- a/app/helpers/issues_helper.rb +++ b/app/helpers/issues_helper.rb @@ -174,7 +174,11 @@ module IssuesHelper end def issue_header_actions_data(project, issuable, current_user) - new_issuable_params = ({ issuable_template: 'incident', issue: { issue_type: 'incident' } } if issuable.incident?) + new_issuable_params = { issue: { description: _('Related to #%{issue_id}.') % { issue_id: issuable.iid } + "\n\n" } } + if issuable.incident? + new_issuable_params[:issuable_template] = 'incident' + new_issuable_params[:issue][:issue_type] = 'incident' + end { can_create_issue: show_new_issue_link?(project).to_s, diff --git a/app/models/preloaders/user_max_access_level_in_groups_preloader.rb b/app/models/preloaders/user_max_access_level_in_groups_preloader.rb new file mode 100644 index 00000000000..14f1d271572 --- /dev/null +++ b/app/models/preloaders/user_max_access_level_in_groups_preloader.rb @@ -0,0 +1,27 @@ +# frozen_string_literal: true + +module Preloaders + # This class preloads the max access level (role) for the user within the given groups and + # stores the values in requests store. + # Will only be able to preload max access level for groups where the user is a direct member + class UserMaxAccessLevelInGroupsPreloader + include BulkMemberAccessLoad + + def initialize(groups, user) + @groups = groups + @user = user + end + + def execute + group_memberships = GroupMember.active_without_invites_and_requests + .non_minimal_access + .where(user: @user, source_id: @groups) + .group(:source_id) + .maximum(:access_level) + + group_memberships.each do |group_id, max_access_level| + merge_value_to_request_store(User, @user.id, group_id, max_access_level) + end + end + end +end diff --git a/app/policies/user_policy.rb b/app/policies/user_policy.rb index 067f0f6a9d2..018c061af9f 100644 --- a/app/policies/user_policy.rb +++ b/app/policies/user_policy.rb @@ -25,6 +25,7 @@ class UserPolicy < BasePolicy enable :update_user_status enable :read_user_personal_access_tokens enable :read_group_count + enable :read_user_groups end rule { default }.enable :read_user_profile |