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-08-24 00:11:23 +0300
committerGitLab Bot <gitlab-bot@gitlab.com>2021-08-24 00:11:23 +0300
commit0510f42da38bb0eb1d06aeb82b4d16ed71135776 (patch)
treeb83ce9d25ff8bf1938c44e51916fd5bd86e5ee8b /app
parent5e9fe672fa0eda6322bd392e8502d1886804bd07 (diff)
Add latest changes from gitlab-org/gitlab@master
Diffstat (limited to 'app')
-rw-r--r--app/assets/javascripts/pipelines/components/graph/accessors.js25
-rw-r--r--app/assets/javascripts/pipelines/components/graph/constants.js3
-rw-r--r--app/assets/javascripts/pipelines/components/graph/job_item.vue14
-rw-r--r--app/assets/javascripts/pipelines/components/graph/linked_pipeline.vue29
-rw-r--r--app/assets/javascripts/pipelines/components/graph/stage_column_component.vue4
-rw-r--r--app/assets/javascripts/pipelines/pipeline_details_graph.js2
-rw-r--r--app/finders/groups/user_groups_finder.rb60
-rw-r--r--app/graphql/resolvers/base_resolver.rb10
-rw-r--r--app/graphql/resolvers/users/groups_resolver.rb44
-rw-r--r--app/graphql/types/permission_types/group.rb2
-rw-r--r--app/graphql/types/permission_types/group_enum.rb12
-rw-r--r--app/graphql/types/user_interface.rb3
-rw-r--r--app/helpers/issues_helper.rb6
-rw-r--r--app/models/preloaders/user_max_access_level_in_groups_preloader.rb27
-rw-r--r--app/policies/user_policy.rb1
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