Add latest changes from gitlab-org/gitlab@master
This commit is contained in:
parent
892e58c41f
commit
453bb35fc8
|
|
@ -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 });
|
||||
},
|
||||
|
|
|
|||
|
|
@ -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>
|
||||
|
|
|
|||
|
|
@ -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>
|
||||
|
|
@ -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">{{
|
||||
|
|
|
|||
|
|
@ -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>
|
||||
|
|
|
|||
|
|
@ -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 });
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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,
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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)
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
@ -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?
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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).
|
||||
|
|
|
|||
|
|
@ -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.
|
||||
|
||||
|
|
|
|||
|
|
@ -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`.
|
||||
|
|
|
|||
|
|
@ -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.
|
||||
|
||||
|
|
|
|||
|
|
@ -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.
|
||||
|
||||
|
|
|
|||
|
|
@ -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.
|
||||
|
|
|
|||
|
|
@ -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 ""
|
||||
|
||||
|
|
|
|||
|
|
@ -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"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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);
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
||||
|
|
|
|||
|
|
@ -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(() => {
|
||||
createComponent();
|
||||
});
|
||||
|
||||
it('renders link to group', () => {
|
||||
createComponent();
|
||||
|
||||
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);
|
||||
});
|
||||
});
|
||||
});
|
||||
|
|
|
|||
|
|
@ -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.',
|
||||
);
|
||||
});
|
||||
});
|
||||
|
|
@ -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>"', () => {
|
||||
|
|
|
|||
|
|
@ -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', () => {
|
||||
|
|
|
|||
|
|
@ -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,
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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) }
|
||||
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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) }
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
@ -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
|
||||
|
||||
expect(policy).to be_disallowed(:admin_project_group_link)
|
||||
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
|
||||
|
||||
it 'can read_shared_with_group' do
|
||||
expect(policy).to be_allowed(:read_shared_with_group)
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
||||
expect(json[:can_update]).to be false
|
||||
expect(json[:can_remove]).to be false
|
||||
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
|
||||
|
||||
it 'sets is_shared_with_group_private to true' do
|
||||
expect(as_json[:is_shared_with_group_private]).to be true
|
||||
end
|
||||
end
|
||||
end
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
||||
expect(json[:can_remove]).to be false
|
||||
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
|
||||
|
||||
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
|
||||
|
|
|
|||
Loading…
Reference in New Issue