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
diff options
context:
space:
mode:
authorGitLab Bot <gitlab-bot@gitlab.com>2023-10-31 03:16:38 +0300
committerGitLab Bot <gitlab-bot@gitlab.com>2023-10-31 03:16:38 +0300
commit453bb35fc81741c1dd7178cde1f64df2c9ec7252 (patch)
tree44edb488772b5fc89967865e4e0e70c8feab832f
parent892e58c41fd07cc59b9e06b2a856ee61790d5bc4 (diff)
Add latest changes from gitlab-org/gitlab@master
-rw-r--r--app/assets/javascripts/helpers/init_simple_app_helper.js29
-rw-r--r--app/assets/javascripts/members/components/avatars/group_avatar.vue48
-rw-r--r--app/assets/javascripts/members/components/icons/private_icon.vue19
-rw-r--r--app/assets/javascripts/members/components/table/member_source.vue20
-rw-r--r--app/assets/javascripts/members/components/table/members_table.vue1
-rw-r--r--app/assets/javascripts/pages/projects/ml/models/show/index.js2
-rw-r--r--app/helpers/colors_helper.rb12
-rw-r--r--app/helpers/environment_helper.rb48
-rw-r--r--app/helpers/environments_helper.rb9
-rw-r--r--app/helpers/graph_helper.rb7
-rw-r--r--app/helpers/members_helper.rb11
-rw-r--r--app/helpers/nav_helper.rb8
-rw-r--r--app/policies/group_group_link_policy.rb8
-rw-r--r--app/policies/project_group_link_policy.rb4
-rw-r--r--app/serializers/group_link/group_group_link_entity.rb2
-rw-r--r--app/serializers/group_link/group_link_entity.rb22
-rw-r--r--app/serializers/group_link/project_group_link_entity.rb2
-rw-r--r--app/views/projects/tree/_tree_header.html.haml3
-rw-r--r--doc/administration/settings/gitaly_timeouts.md1
-rw-r--r--doc/ci/pipelines/settings.md1
-rw-r--r--doc/user/group/manage.md5
-rw-r--r--doc/user/packages/maven_repository/index.md2
-rw-r--r--doc/user/project/members/share_project_with_groups.md5
-rw-r--r--doc/user/project/merge_requests/index.md28
-rw-r--r--locale/gitlab.pot24
-rw-r--r--spec/fixtures/api/schemas/group_link/group_link.json5
-rw-r--r--spec/frontend/helpers/init_simple_app_helper_spec.js37
-rw-r--r--spec/frontend/members/components/avatars/group_avatar_spec.js25
-rw-r--r--spec/frontend/members/components/icons/private_icon_spec.js30
-rw-r--r--spec/frontend/members/components/table/member_source_spec.js15
-rw-r--r--spec/frontend/members/components/table/members_table_spec.js19
-rw-r--r--spec/frontend/members/mock_data.js13
-rw-r--r--spec/helpers/colors_helper_spec.rb47
-rw-r--r--spec/helpers/environment_helper_spec.rb39
-rw-r--r--spec/helpers/environments_helper_spec.rb13
-rw-r--r--spec/helpers/groups/group_members_helper_spec.rb12
-rw-r--r--spec/helpers/members_helper_spec.rb15
-rw-r--r--spec/helpers/users/callouts_helper_spec.rb2
-rw-r--r--spec/policies/group_group_link_policy_spec.rb63
-rw-r--r--spec/policies/project_group_link_policy_spec.rb95
-rw-r--r--spec/serializers/group_link/group_group_link_entity_spec.rb65
-rw-r--r--spec/serializers/group_link/project_group_link_entity_spec.rb62
42 files changed, 546 insertions, 332 deletions
diff --git a/app/assets/javascripts/helpers/init_simple_app_helper.js b/app/assets/javascripts/helpers/init_simple_app_helper.js
index 695fc455f13..f7bef8c563e 100644
--- a/app/assets/javascripts/helpers/init_simple_app_helper.js
+++ b/app/assets/javascripts/helpers/init_simple_app_helper.js
@@ -1,4 +1,26 @@
import Vue from 'vue';
+import VueApollo from 'vue-apollo';
+import createDefaultClient from '~/lib/graphql';
+
+/**
+ * @param {boolean|VueApollo} apolloProviderOption
+ * @returns {undefined | VueApollo}
+ */
+const getApolloProvider = (apolloProviderOption) => {
+ if (apolloProviderOption === true) {
+ Vue.use(VueApollo);
+
+ return new VueApollo({
+ defaultClient: createDefaultClient(),
+ });
+ }
+
+ if (apolloProviderOption instanceof VueApollo) {
+ return apolloProviderOption;
+ }
+
+ return undefined;
+};
/**
* Initializes a component as a simple vue app, passing the necessary props. If the element
@@ -8,6 +30,8 @@ import Vue from 'vue';
*
* @param {string} selector css selector for where to build
* @param {Vue.component} component The Vue compoment to be built as the root of the app
+ * @param {{withApolloProvider: boolean|VueApollo}} options. extra options to be passed to the vue app
+ * withApolloProvider: if true, instantiates a default apolloProvider. Also accepts and instance of VueApollo
*
* @example
* ```html
@@ -15,13 +39,13 @@ import Vue from 'vue';
* ```
*
* ```javascript
- * initSimpleApp('#mount-here', MyApp)
+ * initSimpleApp('#mount-here', MyApp, { withApolloProvider: true })
* ```
*
* This will mount MyApp as root on '#mount-here'. It will receive {'some': 'object'} as it's
* view model prop.
*/
-export const initSimpleApp = (selector, component) => {
+export const initSimpleApp = (selector, component, { withApolloProvider } = {}) => {
const element = document.querySelector(selector);
if (!element) {
@@ -32,6 +56,7 @@ export const initSimpleApp = (selector, component) => {
return new Vue({
el: element,
+ apolloProvider: getApolloProvider(withApolloProvider),
render(h) {
return h(component, { props });
},
diff --git a/app/assets/javascripts/members/components/avatars/group_avatar.vue b/app/assets/javascripts/members/components/avatars/group_avatar.vue
index 3b176bf2b43..83b5855492b 100644
--- a/app/assets/javascripts/members/components/avatars/group_avatar.vue
+++ b/app/assets/javascripts/members/components/avatars/group_avatar.vue
@@ -1,11 +1,18 @@
<script>
-import { GlAvatarLink, GlAvatarLabeled } from '@gitlab/ui';
+import { GlAvatarLink, GlAvatarLabeled, GlTooltipDirective } from '@gitlab/ui';
+import { __ } from '~/locale';
+import PrivateIcon from '../icons/private_icon.vue';
import { AVATAR_SIZE } from '../../constants';
export default {
name: 'GroupAvatar',
- avatarSize: AVATAR_SIZE,
- components: { GlAvatarLink, GlAvatarLabeled },
+ components: { GlAvatarLink, GlAvatarLabeled, PrivateIcon },
+ directives: {
+ GlTooltip: GlTooltipDirective,
+ },
+ i18n: {
+ private: __('Private'),
+ },
props: {
member: {
type: Object,
@@ -16,19 +23,36 @@ export default {
group() {
return this.member.sharedWithGroup;
},
+ isPrivate() {
+ return this.member.isSharedWithGroupPrivate;
+ },
+ avatarLabeledProps() {
+ const label = this.isPrivate ? this.$options.i18n.private : this.group.fullName;
+
+ return {
+ label,
+ src: this.group.avatarUrl,
+ alt: label,
+ size: AVATAR_SIZE,
+ entityName: this.isPrivate ? this.$options.i18n.private : this.group.name,
+ entityId: this.group.id,
+ };
+ },
},
};
</script>
<template>
- <gl-avatar-link :href="group.webUrl">
- <gl-avatar-labeled
- :label="group.fullName"
- :src="group.avatarUrl"
- :alt="group.fullName"
- :size="$options.avatarSize"
- :entity-name="group.name"
- :entity-id="group.id"
- />
+ <div v-if="isPrivate">
+ <gl-avatar-labeled v-bind="avatarLabeledProps">
+ <template #meta>
+ <div class="gl-p-1">
+ <private-icon />
+ </div>
+ </template>
+ </gl-avatar-labeled>
+ </div>
+ <gl-avatar-link v-else :href="group.webUrl">
+ <gl-avatar-labeled v-bind="avatarLabeledProps" />
</gl-avatar-link>
</template>
diff --git a/app/assets/javascripts/members/components/icons/private_icon.vue b/app/assets/javascripts/members/components/icons/private_icon.vue
new file mode 100644
index 00000000000..6168ea955f3
--- /dev/null
+++ b/app/assets/javascripts/members/components/icons/private_icon.vue
@@ -0,0 +1,19 @@
+<script>
+import { GlIcon, GlTooltipDirective } from '@gitlab/ui';
+import { s__ } from '~/locale';
+
+export default {
+ name: 'GroupAvatar',
+ components: { GlIcon },
+ directives: {
+ GlTooltip: GlTooltipDirective,
+ },
+ i18n: {
+ tooltip: s__('Members|Private group information is only accessible to its members.'),
+ },
+};
+</script>
+
+<template>
+ <gl-icon v-gl-tooltip="$options.i18n.tooltip" name="eye-slash" />
+</template>
diff --git a/app/assets/javascripts/members/components/table/member_source.vue b/app/assets/javascripts/members/components/table/member_source.vue
index ed1971d020b..f1a1c4cecaa 100644
--- a/app/assets/javascripts/members/components/table/member_source.vue
+++ b/app/assets/javascripts/members/components/table/member_source.vue
@@ -1,10 +1,12 @@
<script>
import { GlSprintf, GlTooltipDirective } from '@gitlab/ui';
import { s__, __ } from '~/locale';
+import PrivateIcon from '../icons/private_icon.vue';
export default {
name: 'MemberSource',
i18n: {
+ private: __('Private'),
inherited: __('Inherited'),
directMember: __('Direct member'),
directMemberWithCreatedBy: s__('Members|Direct member by %{createdBy}'),
@@ -13,16 +15,24 @@ export default {
directives: {
GlTooltip: GlTooltipDirective,
},
- components: { GlSprintf },
+ components: { GlSprintf, PrivateIcon },
props: {
memberSource: {
type: Object,
- required: true,
+ required: false,
+ default() {
+ return {};
+ },
},
isDirectMember: {
type: Boolean,
required: true,
},
+ isSharedWithGroupPrivate: {
+ type: Boolean,
+ required: false,
+ default: false,
+ },
createdBy: {
type: Object,
required: false,
@@ -43,7 +53,11 @@ export default {
</script>
<template>
- <span v-if="showCreatedBy">
+ <div v-if="isSharedWithGroupPrivate" class="gl-display-flex gl-column-gap-2">
+ <span>{{ $options.i18n.private }}</span>
+ <private-icon />
+ </div>
+ <span v-else-if="showCreatedBy">
<gl-sprintf :message="messageWithCreatedBy">
<template #group>
<a v-gl-tooltip.hover="$options.i18n.inherited" :href="memberSource.webUrl">{{
diff --git a/app/assets/javascripts/members/components/table/members_table.vue b/app/assets/javascripts/members/components/table/members_table.vue
index 68f624e9a3d..2b3294c1c79 100644
--- a/app/assets/javascripts/members/components/table/members_table.vue
+++ b/app/assets/javascripts/members/components/table/members_table.vue
@@ -270,6 +270,7 @@ export default {
:is-direct-member="isDirectMember"
:member-source="member.source"
:created-by="member.createdBy"
+ :is-shared-with-group-private="member.isSharedWithGroupPrivate"
/>
</members-table-cell>
</template>
diff --git a/app/assets/javascripts/pages/projects/ml/models/show/index.js b/app/assets/javascripts/pages/projects/ml/models/show/index.js
index 87ee5c851f6..c8e25e0f0e8 100644
--- a/app/assets/javascripts/pages/projects/ml/models/show/index.js
+++ b/app/assets/javascripts/pages/projects/ml/models/show/index.js
@@ -1,4 +1,4 @@
import { initSimpleApp } from '~/helpers/init_simple_app_helper';
import { ShowMlModel } from '~/ml/model_registry/apps';
-initSimpleApp('#js-mount-show-ml-model', ShowMlModel);
+initSimpleApp('#js-mount-show-ml-model', ShowMlModel, { withApolloProvider: true });
diff --git a/app/helpers/colors_helper.rb b/app/helpers/colors_helper.rb
index 3cd7263c39e..34b18b80be4 100644
--- a/app/helpers/colors_helper.rb
+++ b/app/helpers/colors_helper.rb
@@ -10,16 +10,4 @@ module ColorsHelper
hex_color.length == 7 ? hex_color[1, 7].scan(/.{2}/).map(&:hex) : hex_color[1, 4].scan(/./).map { |v| (v * 2).hex }
end
-
- def rgb_array_to_hex_color(rgb_array)
- raise ArgumentError, "invalid RGB array `#{rgb_array}`" unless rgb_array_valid?(rgb_array)
-
- "##{rgb_array.map{ "%02x" % _1 }.join}"
- end
-
- private
-
- def rgb_array_valid?(rgb_array)
- rgb_array.is_a?(Array) && rgb_array.length == 3 && rgb_array.all?{ _1 >= 0 && _1 <= 255 }
- end
end
diff --git a/app/helpers/environment_helper.rb b/app/helpers/environment_helper.rb
index 6e9379a5926..fa47a12a72c 100644
--- a/app/helpers/environment_helper.rb
+++ b/app/helpers/environment_helper.rb
@@ -9,15 +9,6 @@ module EnvironmentHelper
end
# rubocop: enable CodeReuse/ActiveRecord
- def environment_link_for_build(project, build)
- environment = environment_for_build(project, build)
- if environment
- link_to environment.name, project_environment_path(project, environment)
- else
- content_tag :span, build.expanded_environment_name
- end
- end
-
def deployment_path(deployment)
[deployment.project, deployment.deployable]
end
@@ -30,45 +21,6 @@ module EnvironmentHelper
link_to link_label, deployment_path(deployment)
end
- def last_deployment_link_for_environment_build(project, build)
- environment = environment_for_build(project, build)
- return unless environment
-
- deployment_link(environment.last_deployment)
- end
-
- def render_deployment_status(deployment)
- status = deployment.status
-
- status_text =
- case status
- when 'created'
- s_('Deployment|created')
- when 'running'
- s_('Deployment|running')
- when 'success'
- s_('Deployment|success')
- when 'failed'
- s_('Deployment|failed')
- when 'canceled'
- s_('Deployment|canceled')
- when 'skipped'
- s_('Deployment|skipped')
- when 'blocked'
- s_('Deployment|blocked')
- end
-
- ci_icon_utilities = "gl-display-inline-flex gl-align-items-center gl-line-height-0 gl-px-3 gl-py-2 gl-rounded-base"
- klass = "ci-status ci-#{status.dasherize} #{ci_icon_utilities}"
- text = "#{ci_icon_for_status(status)} <span class=\"gl-ml-2\">#{status_text}</span>".html_safe
-
- if deployment.deployable.instance_of?(::Ci::Build)
- link_to(text, deployment_path(deployment), class: klass)
- else
- content_tag(:span, text, class: klass)
- end
- end
-
def environments_detail_data(user, project, environment)
{
name: environment.name,
diff --git a/app/helpers/environments_helper.rb b/app/helpers/environments_helper.rb
index 80a56493653..6d48dac5c6d 100644
--- a/app/helpers/environments_helper.rb
+++ b/app/helpers/environments_helper.rb
@@ -33,15 +33,6 @@ module EnvironmentsHelper
metrics_data
end
- def environment_logs_data(project, environment)
- {
- "environment_name": environment.name,
- "environments_path": api_v4_projects_environments_path(id: project.id),
- "environment_id": environment.id,
- "clusters_path": project_clusters_path(project, format: :json)
- }
- end
-
def can_destroy_environment?(environment)
can?(current_user, :destroy_environment, environment)
end
diff --git a/app/helpers/graph_helper.rb b/app/helpers/graph_helper.rb
index ab72442857b..e74005cf77b 100644
--- a/app/helpers/graph_helper.rb
+++ b/app/helpers/graph_helper.rb
@@ -18,13 +18,6 @@ module GraphHelper
ids.zip(parent_spaces)
end
- def success_ratio(counts)
- return 100 if counts[:failed] == 0
-
- ratio = (counts[:success].to_f / (counts[:success] + counts[:failed])) * 100
- ratio.to_i
- end
-
def should_render_dora_charts
false
end
diff --git a/app/helpers/members_helper.rb b/app/helpers/members_helper.rb
index e4c1d7932aa..600e5f06c61 100644
--- a/app/helpers/members_helper.rb
+++ b/app/helpers/members_helper.rb
@@ -30,22 +30,11 @@ module MembersHelper
"#{text} #{action} the #{member.source.human_name} #{source_text(member)}?"
end
- def remove_member_title(member)
- action = member.request? ? 'Deny access request' : 'Remove user'
-
- "#{action} from #{source_text(member)}"
- end
-
def leave_confirmation_message(member_source)
"Are you sure you want to leave the " \
"\"#{member_source.human_name}\" #{member_source.model_name.to_s.humanize(capitalize: false)}?"
end
- def filter_group_project_member_path(options = {})
- options = params.slice(:search, :sort).merge(options).permit!
- "#{request.path}?#{options.to_param}"
- end
-
def member_path(member)
if member.is_a?(GroupMember)
group_group_member_path(member.source, member)
diff --git a/app/helpers/nav_helper.rb b/app/helpers/nav_helper.rb
index 4d0d44b3318..21c81eefce2 100644
--- a/app/helpers/nav_helper.rb
+++ b/app/helpers/nav_helper.rb
@@ -57,10 +57,6 @@ module NavHelper
end
end
- def nav_control_class
- "nav-control" if current_user
- end
-
def user_dropdown_class
class_names = []
class_names << 'header-user-dropdown-toggle'
@@ -82,10 +78,6 @@ module NavHelper
%w[system_info background_migrations background_jobs health_check]
end
- def admin_analytics_nav_links
- %w[dev_ops_report usage_trends]
- end
-
def show_super_sidebar?(user = current_user)
# The new sidebar is not enabled for anonymous use
# Once we enable the new sidebar by default, this
diff --git a/app/policies/group_group_link_policy.rb b/app/policies/group_group_link_policy.rb
new file mode 100644
index 00000000000..0108f0b7fca
--- /dev/null
+++ b/app/policies/group_group_link_policy.rb
@@ -0,0 +1,8 @@
+# frozen_string_literal: true
+
+class GroupGroupLinkPolicy < ::BasePolicy # rubocop:disable Gitlab/NamespacedClass
+ condition(:can_read_shared_with_group) { can?(:read_group, @subject.shared_with_group) }
+ condition(:group_member) { @subject.shared_group.member?(@user) }
+
+ rule { can_read_shared_with_group | group_member }.enable :read_shared_with_group
+end
diff --git a/app/policies/project_group_link_policy.rb b/app/policies/project_group_link_policy.rb
index 00bb246d70b..7ad2985ecc5 100644
--- a/app/policies/project_group_link_policy.rb
+++ b/app/policies/project_group_link_policy.rb
@@ -2,9 +2,13 @@
class ProjectGroupLinkPolicy < BasePolicy # rubocop:disable Gitlab/NamespacedClass
condition(:group_owner_or_project_admin) { group_owner? || project_admin? }
+ condition(:can_read_group) { can?(:read_group, @subject.group) }
+ condition(:project_member) { @subject.project.member?(@user) }
rule { group_owner_or_project_admin }.enable :admin_project_group_link
+ rule { can_read_group | project_member }.enable :read_shared_with_group
+
private
def group_owner?
diff --git a/app/serializers/group_link/group_group_link_entity.rb b/app/serializers/group_link/group_group_link_entity.rb
index d5d7eea74ea..f855d89f593 100644
--- a/app/serializers/group_link/group_group_link_entity.rb
+++ b/app/serializers/group_link/group_group_link_entity.rb
@@ -4,7 +4,7 @@ module GroupLink
class GroupGroupLinkEntity < GroupLink::GroupLinkEntity
include RequestAwareEntity
- expose :source do |group_link|
+ expose :source, if: ->(group_link) { can_read_shared_group?(group_link) } do |group_link|
GroupEntity.represent(group_link.shared_from, only: [:id, :full_name, :web_url])
end
diff --git a/app/serializers/group_link/group_link_entity.rb b/app/serializers/group_link/group_link_entity.rb
index 4cc7e9f3c8c..66645e736a9 100644
--- a/app/serializers/group_link/group_link_entity.rb
+++ b/app/serializers/group_link/group_link_entity.rb
@@ -19,16 +19,28 @@ module GroupLink
group_link.class.access_options
end
+ expose :is_shared_with_group_private do |group_link|
+ !can_read_shared_group?(group_link)
+ end
+
expose :shared_with_group do
- expose :avatar_url do |group_link|
+ expose :avatar_url, if: ->(group_link) { can_read_shared_group?(group_link) } do |group_link|
group_link.shared_with_group.avatar_url(only_path: false, size: Member::AVATAR_SIZE)
end
- expose :web_url do |group_link|
+ expose :web_url, if: ->(group_link) { can_read_shared_group?(group_link) } do |group_link|
group_link.shared_with_group.web_url
end
- expose :shared_with_group, merge: true, using: GroupBasicEntity
+ # We have to expose shared_with_group.id because we use this to get distinct
+ # with ancestors
+ expose :shared_with_group, merge: true do |group_link|
+ if can_read_shared_group?(group_link)
+ GroupBasicEntity.represent(group_link.shared_with_group)
+ else
+ GroupBasicEntity.represent(group_link.shared_with_group, only: [:id])
+ end
+ end
end
expose :can_update do |group_link, options|
@@ -45,6 +57,10 @@ module GroupLink
private
+ def can_read_shared_group?(group_link)
+ can?(current_user, :read_shared_with_group, group_link)
+ end
+
def current_user
options[:current_user]
end
diff --git a/app/serializers/group_link/project_group_link_entity.rb b/app/serializers/group_link/project_group_link_entity.rb
index d246bff1c58..fbad69bf2c5 100644
--- a/app/serializers/group_link/project_group_link_entity.rb
+++ b/app/serializers/group_link/project_group_link_entity.rb
@@ -4,7 +4,7 @@ module GroupLink
class ProjectGroupLinkEntity < GroupLink::GroupLinkEntity
include RequestAwareEntity
- expose :source do |group_link|
+ expose :source, if: ->(group_link) { can_read_shared_group?(group_link) } do |group_link|
ProjectEntity.represent(group_link.shared_from, only: [:id, :full_name])
end
diff --git a/app/views/projects/tree/_tree_header.html.haml b/app/views/projects/tree/_tree_header.html.haml
index 37f27aa7caf..bed37d9cb63 100644
--- a/app/views/projects/tree/_tree_header.html.haml
+++ b/app/views/projects/tree/_tree_header.html.haml
@@ -10,9 +10,10 @@
.tree-controls
.d-block.d-sm-flex.flex-wrap.align-items-start.gl-children-ml-sm-3.gl-first-child-ml-sm-0<
= render_if_exists 'projects/tree/lock_link'
+ = render 'projects/buttons/compare', project: @project, ref: @ref, root_ref: @repository&.root_ref
+
#js-tree-history-link{ data: { history_link: project_commits_path(@project, @ref) } }
- = render 'projects/buttons/compare', project: @project, ref: @ref, root_ref: @repository&.root_ref
= render 'projects/find_file_link'
= render 'shared/web_ide_button', blob: nil
= render 'projects/buttons/download', project: @project, ref: @ref
diff --git a/doc/administration/settings/gitaly_timeouts.md b/doc/administration/settings/gitaly_timeouts.md
index 3304db3d148..edb44249aab 100644
--- a/doc/administration/settings/gitaly_timeouts.md
+++ b/doc/administration/settings/gitaly_timeouts.md
@@ -25,3 +25,4 @@ The following timeouts are available.
| Default | 55 seconds | Timeout for most Gitaly calls (not enforced for `git` `fetch` and `push` operations, or Sidekiq jobs). For example, checking if a repository exists on disk. Makes sure that Gitaly calls made within a web request cannot exceed the entire request timeout. It should be shorter than the [worker timeout](../operations/puma.md#change-the-worker-timeout) that can be configured for [Puma](../../install/requirements.md#puma-settings). If a Gitaly call timeout exceeds the worker timeout, the remaining time from the worker timeout is used to avoid having to terminate the worker. |
| Fast | 10 seconds | Timeout for fast Gitaly operations used within requests, sometimes multiple times. For example, checking if a repository exists on disk. If fast operations exceed this threshold, there may be a problem with a storage shard. Failing fast can help maintain the stability of the GitLab instance. |
| Medium | 30 seconds | Timeout for Gitaly operations that should be fast (possibly within requests) but preferably not used multiple times within a request. For example, loading blobs. Timeout that should be set between Default and Fast. |
+You can also [configure negotiation timeouts](../gitaly/configure_gitaly.md#configure-negotiation-timeouts).
diff --git a/doc/ci/pipelines/settings.md b/doc/ci/pipelines/settings.md
index 265fd674190..3baaba091d3 100644
--- a/doc/ci/pipelines/settings.md
+++ b/doc/ci/pipelines/settings.md
@@ -204,6 +204,7 @@ You can define how long a job can run before it times out.
1. Expand **General pipelines**.
1. In the **Timeout** field, enter the number of minutes, or a human-readable value like `2 hours`.
Must be 10 minutes or more, and less than one month. Default is 60 minutes.
+ Pending jobs are dropped after 24 hours of inactivity.
Jobs that exceed the timeout are marked as failed.
diff --git a/doc/user/group/manage.md b/doc/user/group/manage.md
index d671b0434b6..d67e52a6fc3 100644
--- a/doc/user/group/manage.md
+++ b/doc/user/group/manage.md
@@ -130,6 +130,11 @@ After sharing the `Frontend` group with the `Engineering` group:
- The **Groups** tab lists the `Engineering` group.
- The **Groups** tab lists a group regardless of whether it is a public or private group.
+- From [GitLab 16.6](https://gitlab.com/gitlab-org/gitlab/-/merge_requests/134623),
+ the invited group's name and membership source will be hidden unless:
+ - the invited group is public, or
+ - the current user is a member of the invited group, or
+ - the current user is a member of the current group.
- All direct members of the `Engineering` group have access to the `Frontend` group. The least access is granted between the access in the `Engineering` group and the access in the `Frontend` group.
- If `Member1` has the Maintainer role in `Engineering` and `Engineering` is added to `Frontend` with the Developer role, `Member1` has the Developer role in `Frontend`.
- If `Member2` has the Guest role in `Engineering` and `Engineering` is added to `Frontend` with the Developer role, `Member2` has the Guest role in `Frontend`.
diff --git a/doc/user/packages/maven_repository/index.md b/doc/user/packages/maven_repository/index.md
index 6765aa2cbb1..2c395538455 100644
--- a/doc/user/packages/maven_repository/index.md
+++ b/doc/user/packages/maven_repository/index.md
@@ -24,7 +24,7 @@ Supported clients:
### Authenticate to the Package Registry
-You need an token to publish a package. There are different tokens available depending on what you're trying to achieve. For more information, review the [guidance on tokens](../package_registry/index.md#authenticate-with-the-registry).
+You need a token to publish a package. There are different tokens available depending on what you're trying to achieve. For more information, review the [guidance on tokens](../package_registry/index.md#authenticate-with-the-registry).
Create a token and save it to use later in the process.
diff --git a/doc/user/project/members/share_project_with_groups.md b/doc/user/project/members/share_project_with_groups.md
index deefe9040fa..4474cb55929 100644
--- a/doc/user/project/members/share_project_with_groups.md
+++ b/doc/user/project/members/share_project_with_groups.md
@@ -76,6 +76,11 @@ In addition:
- On the group's page, the project is listed on the **Shared projects** tab.
- On the project's **Members** page, the group is listed on the **Groups** tab.
+- From [GitLab 16.6](https://gitlab.com/gitlab-org/gitlab/-/merge_requests/134623),
+ the invited group's name and membership source will be hidden unless:
+ - the group is public, or
+ - the current user is a member of the group, or
+ - the current user is a member of the project.
- Each user is assigned a maximum role.
- Members who have the **Project Invite** badge next to their profile on the usage quota page count towards the billable members of the shared project's top-level group.
diff --git a/doc/user/project/merge_requests/index.md b/doc/user/project/merge_requests/index.md
index 22cd8f9b89e..58cfd90c13f 100644
--- a/doc/user/project/merge_requests/index.md
+++ b/doc/user/project/merge_requests/index.md
@@ -489,3 +489,31 @@ p = Project.find_by_full_path('<namespace/project>')
m = p.merge_requests.find_by(iid: <iid>)
Issuable::DestroyService.new(container: m.project, current_user: u).execute(m)
```
+
+### Merge request pre-receive hook failed
+
+If a merge request times out, you might see messages that indicate a Puma worker
+timeout problem:
+
+- In the GitLab UI:
+
+ ```plaintext
+ Something went wrong during merge pre-receive hook.
+ 500 Internal Server Error. Try again.
+ ```
+
+- In the `gitlab-rails/api_json.log` log file:
+
+ ```plaintext
+ Rack::Timeout::RequestTimeoutException
+ Request ran for longer than 60000ms
+ ```
+
+This error can happen if your merge request:
+
+- Contains many diffs.
+- Is many commits behind the target branch.
+
+Users in self-managed installations can request an administrator review server logs
+to determine the cause of the error. GitLab SaaS users should
+[contact Support](https://about.gitlab.com/support/#contact-support) for help.
diff --git a/locale/gitlab.pot b/locale/gitlab.pot
index 48df831f014..8de70afd7fa 100644
--- a/locale/gitlab.pot
+++ b/locale/gitlab.pot
@@ -16846,27 +16846,6 @@ msgstr ""
msgid "Deployment|Waiting"
msgstr ""
-msgid "Deployment|blocked"
-msgstr ""
-
-msgid "Deployment|canceled"
-msgstr ""
-
-msgid "Deployment|created"
-msgstr ""
-
-msgid "Deployment|failed"
-msgstr ""
-
-msgid "Deployment|running"
-msgstr ""
-
-msgid "Deployment|skipped"
-msgstr ""
-
-msgid "Deployment|success"
-msgstr ""
-
msgid "Deprecated API rate limits"
msgstr ""
@@ -29553,6 +29532,9 @@ msgstr ""
msgid "Members|Membership"
msgstr ""
+msgid "Members|Private group information is only accessible to its members."
+msgstr ""
+
msgid "Members|Remove \"%{groupName}\""
msgstr ""
diff --git a/spec/fixtures/api/schemas/group_link/group_link.json b/spec/fixtures/api/schemas/group_link/group_link.json
index 885ed6d18e0..4db38952ecc 100644
--- a/spec/fixtures/api/schemas/group_link/group_link.json
+++ b/spec/fixtures/api/schemas/group_link/group_link.json
@@ -44,6 +44,9 @@
"valid_roles": {
"type": "object"
},
+ "is_shared_with_group_private": {
+ "type": "boolean"
+ },
"shared_with_group": {
"type": "object",
"required": [
@@ -89,4 +92,4 @@
"type": "boolean"
}
}
-} \ No newline at end of file
+}
diff --git a/spec/frontend/helpers/init_simple_app_helper_spec.js b/spec/frontend/helpers/init_simple_app_helper_spec.js
index 7938e3851d0..de39a6f9d70 100644
--- a/spec/frontend/helpers/init_simple_app_helper_spec.js
+++ b/spec/frontend/helpers/init_simple_app_helper_spec.js
@@ -1,7 +1,9 @@
import { createWrapper } from '@vue/test-utils';
import Vue from 'vue';
+import VueApollo from 'vue-apollo';
import { setHTMLFixture, resetHTMLFixture } from 'helpers/fixtures';
import { initSimpleApp } from '~/helpers/init_simple_app_helper';
+import createDefaultClient from '~/lib/graphql';
const MockComponent = Vue.component('MockComponent', {
props: {
@@ -25,10 +27,10 @@ const findMock = () => wrapper.findComponent(MockComponent);
const didCreateApp = () => wrapper !== undefined;
-const initMock = (html, props = {}) => {
+const initMock = (html, options = {}) => {
setHTMLFixture(html);
- const app = initSimpleApp('#mount-here', MockComponent, { props });
+ const app = initSimpleApp('#mount-here', MockComponent, options);
wrapper = app ? createWrapper(app) : undefined;
};
@@ -58,4 +60,35 @@ describe('helpers/init_simple_app_helper/initSimpleApp', () => {
count: 123,
});
});
+
+ describe('options', () => {
+ describe('withApolloProvider', () => {
+ describe('if not true or not VueApollo', () => {
+ it('apolloProvider not created', () => {
+ initMock('<div id="mount-here"></div>', { withApolloProvider: false });
+
+ expect(wrapper.vm.$apollo).toBeUndefined();
+ });
+ });
+
+ describe('if true, creates default provider', () => {
+ it('creates a default apolloProvider', () => {
+ initMock('<div id="mount-here"></div>', { withApolloProvider: true });
+
+ expect(wrapper.vm.$apollo).not.toBeUndefined();
+ });
+ });
+
+ describe('if VueApollo, sets as default provider', () => {
+ it('uses the provided apolloClient', () => {
+ Vue.use(VueApollo);
+ const apolloProvider = new VueApollo({ defaultClient: createDefaultClient() });
+
+ initMock('<div id="mount-here"></div>', { withApolloProvider: apolloProvider });
+
+ expect(wrapper.vm.$apolloProvider).toBe(apolloProvider);
+ });
+ });
+ });
+ });
});
diff --git a/spec/frontend/members/components/avatars/group_avatar_spec.js b/spec/frontend/members/components/avatars/group_avatar_spec.js
index 8e4263f88fe..1463aa5ae59 100644
--- a/spec/frontend/members/components/avatars/group_avatar_spec.js
+++ b/spec/frontend/members/components/avatars/group_avatar_spec.js
@@ -1,8 +1,9 @@
-import { GlAvatarLink } from '@gitlab/ui';
+import { GlAvatarLabeled, GlAvatarLink } from '@gitlab/ui';
import { getByText as getByTextHelper } from '@testing-library/dom';
import { mount, createWrapper } from '@vue/test-utils';
import GroupAvatar from '~/members/components/avatars/group_avatar.vue';
-import { group as member } from '../../mock_data';
+import PrivateIcon from '~/members/components/icons/private_icon.vue';
+import { group as member, privateGroup as privateMember } from '../../mock_data';
describe('MemberList', () => {
let wrapper;
@@ -21,11 +22,9 @@ describe('MemberList', () => {
const getByText = (text, options) =>
createWrapper(getByTextHelper(wrapper.element, text, options));
- beforeEach(() => {
+ it('renders link to group', () => {
createComponent();
- });
- it('renders link to group', () => {
const link = wrapper.findComponent(GlAvatarLink);
expect(link.exists()).toBe(true);
@@ -33,10 +32,26 @@ describe('MemberList', () => {
});
it("renders group's full name", () => {
+ createComponent();
+
expect(getByText(group.fullName).exists()).toBe(true);
});
it("renders group's avatar", () => {
+ createComponent();
+
expect(wrapper.find('img').attributes('src')).toBe(group.avatarUrl);
});
+
+ describe('when group is private', () => {
+ beforeEach(() => {
+ createComponent({ member: privateMember });
+ });
+
+ it('renders private avatar with icon', () => {
+ expect(wrapper.findComponent(GlAvatarLink).exists()).toBe(false);
+ expect(wrapper.findComponent(GlAvatarLabeled).props('label')).toBe('Private');
+ expect(wrapper.findComponent(PrivateIcon).exists()).toBe(true);
+ });
+ });
});
diff --git a/spec/frontend/members/components/icons/private_icon_spec.js b/spec/frontend/members/components/icons/private_icon_spec.js
new file mode 100644
index 00000000000..ea2b65e3307
--- /dev/null
+++ b/spec/frontend/members/components/icons/private_icon_spec.js
@@ -0,0 +1,30 @@
+import { GlIcon } from '@gitlab/ui';
+import { mountExtended } from 'helpers/vue_test_utils_helper';
+import { createMockDirective, getBinding } from 'helpers/vue_mock_directive';
+import PrivateIcon from '~/members/components/icons/private_icon.vue';
+
+describe('PrivateIcon', () => {
+ let wrapper;
+
+ const createComponent = () => {
+ wrapper = mountExtended(PrivateIcon, {
+ directives: {
+ GlTooltip: createMockDirective('gl-tooltip'),
+ },
+ });
+ };
+
+ beforeEach(() => {
+ createComponent();
+ });
+
+ it('renders private icon with tooltip', () => {
+ const icon = wrapper.findComponent(GlIcon);
+ const tooltipDirective = getBinding(icon.element, 'gl-tooltip');
+
+ expect(icon.props('name')).toBe('eye-slash');
+ expect(tooltipDirective.value).toBe(
+ 'Private group information is only accessible to its members.',
+ );
+ });
+});
diff --git a/spec/frontend/members/components/table/member_source_spec.js b/spec/frontend/members/components/table/member_source_spec.js
index bbfbb19fd92..16b8d239944 100644
--- a/spec/frontend/members/components/table/member_source_spec.js
+++ b/spec/frontend/members/components/table/member_source_spec.js
@@ -1,6 +1,7 @@
import { mountExtended } from 'helpers/vue_test_utils_helper';
import { createMockDirective, getBinding } from 'helpers/vue_mock_directive';
import MemberSource from '~/members/components/table/member_source.vue';
+import PrivateIcon from '~/members/components/icons/private_icon.vue';
describe('MemberSource', () => {
let wrapper;
@@ -30,6 +31,20 @@ describe('MemberSource', () => {
const getTooltipDirective = (elementWrapper) => getBinding(elementWrapper.element, 'gl-tooltip');
+ describe('when source is private', () => {
+ beforeEach(() => {
+ createComponent({
+ isSharedWithGroupPrivate: true,
+ isDirectMember: false,
+ });
+ });
+
+ it('displays private with icon', () => {
+ expect(wrapper.findByText('Private').exists()).toBe(true);
+ expect(wrapper.findComponent(PrivateIcon).exists()).toBe(true);
+ });
+ });
+
describe('direct member', () => {
describe('when created by is available', () => {
it('displays "Direct member by <user name>"', () => {
diff --git a/spec/frontend/members/components/table/members_table_spec.js b/spec/frontend/members/components/table/members_table_spec.js
index 4539478bf9a..791155fcd1b 100644
--- a/spec/frontend/members/components/table/members_table_spec.js
+++ b/spec/frontend/members/components/table/members_table_spec.js
@@ -27,6 +27,7 @@ import {
directMember,
invite,
accessRequest,
+ privateGroup,
pagination,
} from '../../mock_data';
@@ -245,6 +246,24 @@ describe('MembersTable', () => {
});
});
});
+
+ describe('Source field', () => {
+ beforeEach(() => {
+ createComponent({
+ members: [privateGroup],
+ tableFields: ['source'],
+ });
+ });
+
+ it('passes correct props to `MemberSource` component', () => {
+ expect(wrapper.findComponent(MemberSource).props()).toMatchObject({
+ memberSource: {},
+ isDirectMember: true,
+ isSharedWithGroupPrivate: true,
+ createdBy: null,
+ });
+ });
+ });
});
describe('when `members` is an empty array', () => {
diff --git a/spec/frontend/members/mock_data.js b/spec/frontend/members/mock_data.js
index 4598c74757e..4fb0b76fa49 100644
--- a/spec/frontend/members/mock_data.js
+++ b/spec/frontend/members/mock_data.js
@@ -70,6 +70,19 @@ export const group = {
validRoles: { Guest: 10, Reporter: 20, Developer: 30, Maintainer: 40, Owner: 50 },
};
+export const privateGroup = {
+ accessLevel: { integerValue: 10, stringValue: 'Guest' },
+ isSharedWithGroupPrivate: true,
+ sharedWithGroup: {
+ id: 24,
+ },
+ id: 3,
+ isDirectMember: true,
+ createdAt: '2020-08-06T15:31:07.662Z',
+ expiresAt: null,
+ validRoles: { Guest: 10, Reporter: 20, Developer: 30, Maintainer: 40, Owner: 50 },
+};
+
export const modalData = {
isAccessRequest: true,
isInvite: true,
diff --git a/spec/helpers/colors_helper_spec.rb b/spec/helpers/colors_helper_spec.rb
index ca5cafb7ebe..328796ef1ae 100644
--- a/spec/helpers/colors_helper_spec.rb
+++ b/spec/helpers/colors_helper_spec.rb
@@ -39,51 +39,4 @@ RSpec.describe ColorsHelper do
end
end
end
-
- describe '#rgb_array_to_hex_color' do
- context 'valid RGB array' do
- where(:rgb_array, :hex_color) do
- [0, 0, 0] | '#000000'
- [0, 0, 255] | '#0000ff'
- [0, 255, 0] | '#00ff00'
- [255, 0, 0] | '#ff0000'
- [12, 34, 56] | '#0c2238'
- [222, 111, 88] | '#de6f58'
- [255, 255, 255] | '#ffffff'
- end
-
- with_them do
- it 'returns correct hex color' do
- expect(helper.rgb_array_to_hex_color(rgb_array)).to eq(hex_color)
- end
- end
- end
-
- context 'invalid RGB array' do
- where(:rgb_array) do
- [
- '',
- '#000000',
- 0,
- nil,
- [],
- [0],
- [0, 0],
- [0, 0, 0, 0],
- [-1, 0, 0],
- [0, -1, 0],
- [0, 0, -1],
- [256, 0, 0],
- [0, 256, 0],
- [0, 0, 256]
- ]
- end
-
- with_them do
- it 'raise ArgumentError' do
- expect { helper.rgb_array_to_hex_color(rgb_array) }.to raise_error(ArgumentError)
- end
- end
- end
- end
end
diff --git a/spec/helpers/environment_helper_spec.rb b/spec/helpers/environment_helper_spec.rb
index 1383bf34881..0c04417fca6 100644
--- a/spec/helpers/environment_helper_spec.rb
+++ b/spec/helpers/environment_helper_spec.rb
@@ -3,45 +3,6 @@
require 'spec_helper'
RSpec.describe EnvironmentHelper, feature_category: :environment_management do
- describe '#render_deployment_status' do
- context 'when using a manual deployment' do
- it 'renders a span tag' do
- deploy = build(:deployment, deployable: nil, status: :success)
- html = helper.render_deployment_status(deploy)
-
- expect(html).to have_css('span.ci-status.ci-success')
- end
- end
-
- context 'when using a deployment from a build' do
- it 'renders a link tag' do
- deploy = build(:deployment, status: :success)
- html = helper.render_deployment_status(deploy)
-
- expect(html).to have_css('a.ci-status.ci-success')
- end
- end
-
- context 'when deploying from a bridge' do
- it 'renders a span tag' do
- deploy = build(:deployment, deployable: create(:ci_bridge), status: :success)
- html = helper.render_deployment_status(deploy)
-
- expect(html).to have_css('span.ci-status.ci-success')
- end
- end
-
- context 'for a blocked deployment' do
- subject { helper.render_deployment_status(deployment) }
-
- let(:deployment) { build(:deployment, :blocked) }
-
- it 'indicates the status' do
- expect(subject).to have_text('blocked')
- end
- end
- end
-
describe '#environments_detail_data_json' do
subject { helper.environments_detail_data_json(user, project, environment) }
diff --git a/spec/helpers/environments_helper_spec.rb b/spec/helpers/environments_helper_spec.rb
index 6624404bc49..997f9e6eac7 100644
--- a/spec/helpers/environments_helper_spec.rb
+++ b/spec/helpers/environments_helper_spec.rb
@@ -95,17 +95,4 @@ RSpec.describe EnvironmentsHelper, feature_category: :environment_management do
expect(subject).to eq(true)
end
end
-
- describe '#environment_logs_data' do
- it 'returns logs data' do
- expected_data = {
- "environment_name": environment.name,
- "environments_path": api_v4_projects_environments_path(id: project.id),
- "environment_id": environment.id,
- "clusters_path": project_clusters_path(project, format: :json)
- }
-
- expect(helper.environment_logs_data(project, environment)).to eq(expected_data)
- end
- end
end
diff --git a/spec/helpers/groups/group_members_helper_spec.rb b/spec/helpers/groups/group_members_helper_spec.rb
index a9c6822e2c1..3a7c852b683 100644
--- a/spec/helpers/groups/group_members_helper_spec.rb
+++ b/spec/helpers/groups/group_members_helper_spec.rb
@@ -19,14 +19,9 @@ RSpec.describe Groups::GroupMembersHelper do
let(:members_collection) { members }
before do
- allow(helper).to receive(:can?).with(current_user, :export_group_memberships, group).and_return(false)
- allow(helper).to receive(:can?).with(current_user, :owner_access, group).and_return(true)
allow(helper).to receive(:current_user).and_return(current_user)
- allow(helper).to receive(:can?).with(current_user, :export_group_memberships, shared_group).and_return(true)
allow(helper).to receive(:group_group_member_path).with(shared_group, ':id').and_return('/groups/foo-bar/-/group_members/:id')
allow(helper).to receive(:group_group_link_path).with(shared_group, ':id').and_return('/groups/foo-bar/-/group_links/:id')
- allow(helper).to receive(:can?).with(current_user, :admin_group_member, shared_group).and_return(true)
- allow(helper).to receive(:can?).with(current_user, :admin_member_access_request, shared_group).and_return(true)
end
subject do
@@ -54,8 +49,8 @@ RSpec.describe Groups::GroupMembersHelper do
it 'returns expected json' do
expected = {
source_id: shared_group.id,
- can_manage_members: true,
- can_manage_access_requests: true,
+ can_manage_members: be_in([true, false]),
+ can_manage_access_requests: be_in([true, false]),
group_name: shared_group.name,
group_path: shared_group.full_path
}
@@ -102,9 +97,6 @@ RSpec.describe Groups::GroupMembersHelper do
before do
allow(helper).to receive(:group_group_member_path).with(sub_shared_group, ':id').and_return('/groups/foo-bar/-/group_members/:id')
allow(helper).to receive(:group_group_link_path).with(sub_shared_group, ':id').and_return('/groups/foo-bar/-/group_links/:id')
- allow(helper).to receive(:can?).with(current_user, :admin_group_member, sub_shared_group).and_return(true)
- allow(helper).to receive(:can?).with(current_user, :admin_member_access_request, sub_shared_group).and_return(true)
- allow(helper).to receive(:can?).with(current_user, :export_group_memberships, sub_shared_group).and_return(true)
end
subject do
diff --git a/spec/helpers/members_helper_spec.rb b/spec/helpers/members_helper_spec.rb
index 68a12d8dbf7..d2d758cdc7c 100644
--- a/spec/helpers/members_helper_spec.rb
+++ b/spec/helpers/members_helper_spec.rb
@@ -45,21 +45,6 @@ RSpec.describe MembersHelper do
end
end
- describe '#remove_member_title' do
- let(:requester) { create(:user) }
- let(:project) { create(:project, :public) }
- let(:project_member) { build(:project_member, project: project) }
- let(:project_member_request) { project.request_access(requester) }
- let(:group) { create(:group) }
- let(:group_member) { build(:group_member, group: group) }
- let(:group_member_request) { group.request_access(requester) }
-
- it { expect(remove_member_title(project_member)).to eq 'Remove user from project' }
- it { expect(remove_member_title(project_member_request)).to eq 'Deny access request from project' }
- it { expect(remove_member_title(group_member)).to eq 'Remove user from group and any subresources' }
- it { expect(remove_member_title(group_member_request)).to eq 'Deny access request from group' }
- end
-
describe '#leave_confirmation_message' do
let(:project) { build_stubbed(:project) }
let(:group) { build_stubbed(:group) }
diff --git a/spec/helpers/users/callouts_helper_spec.rb b/spec/helpers/users/callouts_helper_spec.rb
index 10f021330db..2a0ba1279b0 100644
--- a/spec/helpers/users/callouts_helper_spec.rb
+++ b/spec/helpers/users/callouts_helper_spec.rb
@@ -2,7 +2,7 @@
require "spec_helper"
-RSpec.describe Users::CalloutsHelper do
+RSpec.describe Users::CalloutsHelper, feature_category: :navigation do
let_it_be(:user, refind: true) { create(:user) }
before do
diff --git a/spec/policies/group_group_link_policy_spec.rb b/spec/policies/group_group_link_policy_spec.rb
new file mode 100644
index 00000000000..34bc1bc3bec
--- /dev/null
+++ b/spec/policies/group_group_link_policy_spec.rb
@@ -0,0 +1,63 @@
+# frozen_string_literal: true
+
+require 'spec_helper'
+
+RSpec.describe GroupGroupLinkPolicy, feature_category: :system_access do
+ let_it_be(:user) { create(:user) }
+ let_it_be(:group) { create(:group, :private) }
+ let_it_be(:group2) { create(:group, :private) }
+
+ let(:group_group_link) do
+ create(:group_group_link, shared_group: group, shared_with_group: group2)
+ end
+
+ subject(:policy) { described_class.new(user, group_group_link) }
+
+ describe 'read_shared_with_group' do
+ context 'when the user is a shared_group member' do
+ before_all do
+ group.add_guest(user)
+ end
+
+ it 'can read_shared_with_group' do
+ expect(policy).to be_allowed(:read_shared_with_group)
+ end
+ end
+
+ context 'when the user is not a shared_group member' do
+ context 'when user is not a shared_with_group member' do
+ context 'when the shared_with_group is private' do
+ it 'cannot read_shared_with_group' do
+ expect(policy).to be_disallowed(:read_shared_with_group)
+ end
+
+ context 'when the shared group is public' do
+ let_it_be(:group) { create(:group, :public) }
+
+ it 'cannot read_shared_with_group' do
+ expect(policy).to be_disallowed(:read_shared_with_group)
+ end
+ end
+ end
+
+ context 'when the shared_with_group is public' do
+ let_it_be(:group2) { create(:group, :public) }
+
+ it 'can read_shared_with_group' do
+ expect(policy).to be_allowed(:read_shared_with_group)
+ end
+ end
+ end
+
+ context 'when user is a shared_with_group member' do
+ before_all do
+ group2.add_developer(user)
+ end
+
+ it 'can read_shared_with_group' do
+ expect(policy).to be_allowed(:read_shared_with_group)
+ end
+ end
+ end
+ end
+end
diff --git a/spec/policies/project_group_link_policy_spec.rb b/spec/policies/project_group_link_policy_spec.rb
index 9461f33decb..1047d3acb1e 100644
--- a/spec/policies/project_group_link_policy_spec.rb
+++ b/spec/policies/project_group_link_policy_spec.rb
@@ -4,9 +4,8 @@ require 'spec_helper'
RSpec.describe ProjectGroupLinkPolicy, feature_category: :system_access do
let_it_be(:user) { create(:user) }
- let_it_be(:group) { create(:group, :private) }
let_it_be(:group2) { create(:group, :private) }
- let_it_be(:project) { create(:project, :private, group: group) }
+ let_it_be(:project) { create(:project, :private) }
let(:project_group_link) do
create(:project_group_link, project: project, group: group2, group_access: Gitlab::Access::DEVELOPER)
@@ -14,42 +13,92 @@ RSpec.describe ProjectGroupLinkPolicy, feature_category: :system_access do
subject(:policy) { described_class.new(user, project_group_link) }
- context 'when the user is a group owner' do
- before do
- project_group_link.group.add_owner(user)
- end
+ describe 'admin_project_group_link' do
+ context 'when the user is a group owner' do
+ before_all do
+ group2.add_owner(user)
+ end
- context 'when user is not project maintainer' do
- it 'can admin group_project_link' do
- expect(policy).to be_allowed(:admin_project_group_link)
+ context 'when user is not project maintainer' do
+ it 'can admin group_project_link' do
+ expect(policy).to be_allowed(:admin_project_group_link)
+ end
+ end
+
+ context 'when user is a project maintainer' do
+ before do
+ project_group_link.project.add_maintainer(user)
+ end
+
+ it 'can admin group_project_link' do
+ expect(policy).to be_allowed(:admin_project_group_link)
+ end
end
end
- context 'when user is a project maintainer' do
- before do
- project_group_link.project.add_maintainer(user)
+ context 'when user is not a group owner' do
+ context 'when user is a project maintainer' do
+ it 'can admin group_project_link' do
+ project_group_link.project.add_maintainer(user)
+
+ expect(policy).to be_allowed(:admin_project_group_link)
+ end
end
- it 'can admin group_project_link' do
- expect(policy).to be_allowed(:admin_project_group_link)
+ context 'when user is not a project maintainer' do
+ it 'cannot admin group_project_link' do
+ project_group_link.project.add_developer(user)
+
+ expect(policy).to be_disallowed(:admin_project_group_link)
+ end
end
end
end
- context 'when user is not a group owner' do
- context 'when user is a project maintainer' do
- it 'can admin group_project_link' do
- project_group_link.project.add_maintainer(user)
+ describe 'read_shared_with_group' do
+ context 'when the user is a project member' do
+ before_all do
+ project.add_guest(user)
+ end
- expect(policy).to be_allowed(:admin_project_group_link)
+ it 'can read_shared_with_group' do
+ expect(policy).to be_allowed(:read_shared_with_group)
end
end
- context 'when user is not a project maintainer' do
- it 'cannot admin group_project_link' do
- project_group_link.project.add_developer(user)
+ context 'when the user is not a project member' do
+ context 'when user is not a group member' do
+ context 'when the group is private' do
+ it 'cannot read_shared_with_group' do
+ expect(policy).to be_disallowed(:read_shared_with_group)
+ end
+
+ context 'when the project is public' do
+ let_it_be(:project) { create(:project, :public) }
+
+ it 'cannot read_shared_with_group' do
+ expect(policy).to be_disallowed(:read_shared_with_group)
+ end
+ end
+ end
+
+ context 'when the group is public' do
+ let_it_be(:group2) { create(:group, :public) }
+
+ it 'can read_shared_with_group' do
+ expect(policy).to be_allowed(:read_shared_with_group)
+ end
+ end
+ end
+
+ context 'when user is a group member' do
+ before_all do
+ group2.add_guest(user)
+ end
- expect(policy).to be_disallowed(:admin_project_group_link)
+ it 'can read_shared_with_group' do
+ expect(policy).to be_allowed(:read_shared_with_group)
+ end
end
end
end
diff --git a/spec/serializers/group_link/group_group_link_entity_spec.rb b/spec/serializers/group_link/group_group_link_entity_spec.rb
index 502cdc5c048..8f31c53e841 100644
--- a/spec/serializers/group_link/group_group_link_entity_spec.rb
+++ b/spec/serializers/group_link/group_group_link_entity_spec.rb
@@ -9,8 +9,8 @@ RSpec.describe GroupLink::GroupGroupLinkEntity do
let(:entity) { described_class.new(group_group_link, { current_user: current_user, source: shared_group }) }
- before do
- allow(entity).to receive(:current_user).and_return(current_user)
+ subject(:as_json) do
+ entity.as_json
end
it 'matches json schema' do
@@ -19,7 +19,7 @@ RSpec.describe GroupLink::GroupGroupLinkEntity do
context 'source' do
it 'exposes `source`' do
- expect(entity.as_json[:source]).to include(
+ expect(as_json[:source]).to include(
id: shared_group.id,
full_name: shared_group.full_name,
web_url: shared_group.web_url
@@ -38,9 +38,9 @@ RSpec.describe GroupLink::GroupGroupLinkEntity do
end
end
- context 'when current user has `:admin_group_member` permissions' do
- before do
- allow(entity).to receive(:can?).with(current_user, :admin_group_member, shared_group).and_return(true)
+ context 'when current user has owner permissions for the shared group' do
+ before_all do
+ shared_group.add_owner(current_user)
end
context 'when direct_member? is true' do
@@ -49,10 +49,8 @@ RSpec.describe GroupLink::GroupGroupLinkEntity do
end
it 'exposes `can_update` and `can_remove` as `true`' do
- json = entity.as_json
-
- expect(json[:can_update]).to be true
- expect(json[:can_remove]).to be true
+ expect(as_json[:can_update]).to be true
+ expect(as_json[:can_remove]).to be true
end
end
@@ -62,10 +60,51 @@ RSpec.describe GroupLink::GroupGroupLinkEntity do
end
it 'exposes `can_update` and `can_remove` as `true`' do
- json = entity.as_json
+ expect(as_json[:can_update]).to be false
+ expect(as_json[:can_remove]).to be false
+ end
+ end
+ end
+
+ context 'when current user is not a group member' do
+ context 'when shared with group is public' do
+ it 'does expose shared_with_group details' do
+ expect(as_json[:shared_with_group].keys).to include(:id, :avatar_url, :web_url, :name)
+ end
+
+ it 'does expose source details' do
+ expect(as_json[:source].keys).to include(:id, :full_name)
+ end
+
+ it 'sets is_shared_with_group_private to false' do
+ expect(as_json[:is_shared_with_group_private]).to be false
+ end
+ end
+
+ context 'when shared with group is private' do
+ let_it_be(:shared_with_group) { create(:group, :private) }
+
+ let_it_be(:group_group_link) do
+ create(
+ :group_group_link,
+ {
+ shared_group: shared_group,
+ shared_with_group: shared_with_group,
+ expires_at: '2020-05-12'
+ }
+ )
+ end
+
+ it 'does not expose shared_with_group details' do
+ expect(as_json[:shared_with_group].keys).to contain_exactly(:id)
+ end
+
+ it 'does not expose source details' do
+ expect(as_json[:source]).to be_nil
+ end
- expect(json[:can_update]).to be false
- expect(json[:can_remove]).to be false
+ it 'sets is_shared_with_group_private to true' do
+ expect(as_json[:is_shared_with_group_private]).to be true
end
end
end
diff --git a/spec/serializers/group_link/project_group_link_entity_spec.rb b/spec/serializers/group_link/project_group_link_entity_spec.rb
index 1a8fcb2cfd3..00bfc43f17e 100644
--- a/spec/serializers/group_link/project_group_link_entity_spec.rb
+++ b/spec/serializers/group_link/project_group_link_entity_spec.rb
@@ -8,51 +8,69 @@ RSpec.describe GroupLink::ProjectGroupLinkEntity do
let(:entity) { described_class.new(project_group_link, { current_user: current_user, source: project_group_link.project }) }
- before do
- allow(entity).to receive(:current_user).and_return(current_user)
+ subject(:as_json) do
+ entity.as_json
end
it 'matches json schema' do
expect(entity.to_json).to match_schema('group_link/project_group_link')
end
- context 'when current user has `admin_project_member` permissions' do
- before do
- allow(entity).to receive(:can?).with(current_user, :admin_project_group_link, project_group_link).and_return(false)
- allow(entity).to receive(:can?).with(current_user, :admin_project_member, project_group_link.project).and_return(true)
+ context 'when current user is a project maintainer' do
+ before_all do
+ project_group_link.project.add_maintainer(current_user)
end
it 'exposes `can_update` and `can_remove` as `true`' do
- json = entity.as_json
-
- expect(json[:can_update]).to be true
- expect(json[:can_remove]).to be false
+ expect(as_json[:can_update]).to be true
+ expect(as_json[:can_remove]).to be true
end
end
context 'when current user is a group owner' do
- before do
- allow(entity).to receive(:can?).with(current_user, :admin_project_group_link, project_group_link).and_return(true)
- allow(entity).to receive(:can?).with(current_user, :admin_project_member, project_group_link.project).and_return(false)
+ before_all do
+ project_group_link.group.add_owner(current_user)
end
it 'exposes `can_remove` as true' do
- json = entity.as_json
-
- expect(json[:can_remove]).to be true
+ expect(as_json[:can_remove]).to be true
end
end
context 'when current user is not a group owner' do
- before do
- allow(entity).to receive(:can?).with(current_user, :admin_project_group_link, project_group_link).and_return(false)
- allow(entity).to receive(:can?).with(current_user, :admin_project_member, project_group_link.project).and_return(false)
+ it 'exposes `can_remove` as false' do
+ expect(as_json[:can_remove]).to be false
end
- it 'exposes `can_remove` as false' do
- json = entity.as_json
+ context 'when group is public' do
+ it 'does expose shared_with_group details' do
+ expect(as_json[:shared_with_group].keys).to include(:id, :avatar_url, :web_url, :name)
+ end
+
+ it 'does expose source details' do
+ expect(as_json[:source].keys).to include(:id, :full_name)
+ end
+
+ it 'sets is_shared_with_group_private to false' do
+ expect(as_json[:is_shared_with_group_private]).to be false
+ end
+ end
+
+ context 'when group is private' do
+ let_it_be(:private_group) { create(:group, :private) }
+ let_it_be(:project_group_link) { create(:project_group_link, group: private_group) }
+
+ it 'does not expose shared_with_group details' do
+ expect(as_json[:shared_with_group].keys).to contain_exactly(:id)
+ end
+
+ it 'does not expose source details' do
+ expect(as_json[:source]).to be_nil
+ end
- expect(json[:can_remove]).to be false
+ it 'sets is_shared_with_group_private to true' do
+ expect(as_json[:is_shared_with_group_private]).to be true
+ end
end
end
end