Add latest changes from gitlab-org/gitlab@master
|
@ -200,10 +200,7 @@ graphql-schema-dump as-if-foss:
|
|||
# Disable warnings in browserslist which can break on backports
|
||||
# https://github.com/browserslist/browserslist/blob/a287ec6/node.js#L367-L384
|
||||
BROWSERSLIST_IGNORE_OLD_DATA: "true"
|
||||
USE_BUNDLE_INSTALL: "false"
|
||||
SETUP_DB: "false"
|
||||
before_script:
|
||||
- !reference [.default-before_script, before_script]
|
||||
- *yarn-install
|
||||
stage: test
|
||||
|
||||
|
@ -239,17 +236,6 @@ jest minimal:
|
|||
script:
|
||||
- run_timed_command "yarn jest:ci:minimal"
|
||||
|
||||
jest foss-impact:
|
||||
extends:
|
||||
- .jest-base
|
||||
- .frontend:rules:jest:foss-impact
|
||||
- .as-if-foss
|
||||
needs:
|
||||
- "rspec-all frontend_fixture as-if-foss"
|
||||
- "detect-tests"
|
||||
script:
|
||||
- run_timed_command "yarn jest:ci:minimal"
|
||||
|
||||
jest-integration:
|
||||
extends:
|
||||
- .frontend-test-base
|
||||
|
|
|
@ -262,7 +262,6 @@
|
|||
- "config/**/*.js"
|
||||
- "vendor/assets/**/*"
|
||||
- "{,ee/,jh/}{app/assets,app/helpers,app/presenters,app/views,locale,public,symbol}/**/*"
|
||||
- "spec/frontend/**/*"
|
||||
|
||||
.controllers-patterns: &controllers-patterns
|
||||
- "{,ee/,jh/}{app/controllers}/**/*"
|
||||
|
@ -829,20 +828,6 @@
|
|||
- <<: *if-merge-request
|
||||
changes: *code-backstage-patterns
|
||||
|
||||
.frontend:rules:jest:foss-impact:
|
||||
rules:
|
||||
- !reference [".strict-ee-only-rules", rules]
|
||||
- <<: *if-merge-request-labels-as-if-foss
|
||||
when: never
|
||||
- <<: *if-merge-request-labels-run-all-jest
|
||||
when: never
|
||||
- changes: *core-frontend-patterns
|
||||
when: never
|
||||
- <<: *if-merge-request
|
||||
changes: *ci-patterns
|
||||
- <<: *if-merge-request
|
||||
changes: *frontend-patterns
|
||||
|
||||
.frontend:rules:eslint-as-if-foss:
|
||||
rules:
|
||||
- !reference [".strict-ee-only-rules", rules]
|
||||
|
|
|
@ -123,6 +123,7 @@ proper-names:
|
|||
"SAML",
|
||||
"Sendmail",
|
||||
"Sentry",
|
||||
"Service Desk",
|
||||
"Sidekiq",
|
||||
"Shibboleth",
|
||||
"Slack",
|
||||
|
|
|
@ -1 +1 @@
|
|||
43cb85d43809733551d9ad682987d89a2f4afb36
|
||||
8e3eafce11e3b48177872c28c58614226ae18602
|
||||
|
|
|
@ -32,19 +32,27 @@ module Members
|
|||
emails, users, existing_members = parse_users_list(source, invitees)
|
||||
|
||||
Member.transaction do
|
||||
(emails + users).map! do |invitee|
|
||||
new(source,
|
||||
invitee,
|
||||
access_level,
|
||||
common_arguments = {
|
||||
source: source,
|
||||
access_level: access_level,
|
||||
existing_members: existing_members,
|
||||
current_user: current_user,
|
||||
expires_at: expires_at,
|
||||
tasks_to_be_done: tasks_to_be_done,
|
||||
tasks_project_id: tasks_project_id,
|
||||
ldap: ldap,
|
||||
blocking_refresh: blocking_refresh)
|
||||
.execute
|
||||
blocking_refresh: blocking_refresh
|
||||
}
|
||||
|
||||
members = emails.map do |email|
|
||||
new(invitee: email, builder: InviteMemberBuilder, **common_arguments).execute
|
||||
end
|
||||
|
||||
members += users.map do |user|
|
||||
new(invitee: user, **common_arguments).execute
|
||||
end
|
||||
|
||||
members
|
||||
end
|
||||
end
|
||||
|
||||
|
@ -113,11 +121,11 @@ module Members
|
|||
end
|
||||
end
|
||||
|
||||
def initialize(source, invitee, access_level, **args)
|
||||
@source = source
|
||||
def initialize(invitee:, builder: StandardMemberBuilder, **args)
|
||||
@invitee = invitee
|
||||
@access_level = self.class.parsed_access_level(access_level)
|
||||
@builder = builder
|
||||
@args = args
|
||||
@access_level = self.class.parsed_access_level(args[:access_level])
|
||||
end
|
||||
|
||||
private_class_method :new
|
||||
|
@ -133,7 +141,7 @@ module Members
|
|||
private
|
||||
|
||||
delegate :new_record?, to: :member
|
||||
attr_reader :source, :invitee, :access_level, :member, :args
|
||||
attr_reader :invitee, :access_level, :member, :args, :builder
|
||||
|
||||
def assign_member_attributes
|
||||
member.attributes = member_attributes
|
||||
|
@ -182,14 +190,14 @@ module Members
|
|||
def commit_changes
|
||||
if member.request?
|
||||
approve_request
|
||||
else
|
||||
elsif member.changed?
|
||||
# Calling #save triggers callbacks even if there is no change on object.
|
||||
# This previously caused an incident due to the hard to predict
|
||||
# behaviour caused by the large number of callbacks.
|
||||
# See https://gitlab.com/gitlab-com/gl-infra/production/-/issues/6351
|
||||
# and https://gitlab.com/gitlab-org/gitlab/-/merge_requests/80920#note_911569038
|
||||
# for details.
|
||||
member.save if member.changed?
|
||||
member.save
|
||||
end
|
||||
end
|
||||
|
||||
|
@ -241,40 +249,19 @@ module Members
|
|||
end
|
||||
|
||||
def find_or_build_member
|
||||
@member = if invitee.is_a?(User)
|
||||
find_or_initialize_member_by_user
|
||||
else
|
||||
find_or_initialize_member_with_email
|
||||
end
|
||||
@member = builder.new(source, invitee, existing_members).execute
|
||||
|
||||
@member.blocking_refresh = args[:blocking_refresh]
|
||||
end
|
||||
|
||||
# This method is used to find users that have been entered into the "Add members" field.
|
||||
# These can be the User objects directly, their IDs, their emails, or new emails to be invited.
|
||||
def find_or_initialize_member_with_email
|
||||
if user_by_email
|
||||
find_or_initialize_member_by_user(user_id: user_by_email.id)
|
||||
else
|
||||
source.members_and_requesters.find_or_initialize_by(invite_email: invitee) # rubocop:disable CodeReuse/ActiveRecord
|
||||
end
|
||||
end
|
||||
|
||||
def user_by_email
|
||||
source.users_by_emails([invitee])[invitee]
|
||||
end
|
||||
|
||||
def find_or_initialize_member_by_user(user_id: invitee.id)
|
||||
# We have to use `members_and_requesters` here since the given `members` is modified in the models
|
||||
# to act more like a scope(removing the requested_at members) and therefore ActiveRecord has issues with that
|
||||
# on build and refreshing that relation.
|
||||
existing_members[user_id] || source.members_and_requesters.build(user_id: user_id) # rubocop:disable CodeReuse/ActiveRecord
|
||||
end
|
||||
|
||||
def ldap
|
||||
args[:ldap] || false
|
||||
end
|
||||
|
||||
def source
|
||||
args[:source]
|
||||
end
|
||||
|
||||
def existing_members
|
||||
args[:existing_members] || {}
|
||||
end
|
||||
|
|
|
@ -0,0 +1,19 @@
|
|||
# frozen_string_literal: true
|
||||
|
||||
module Members
|
||||
class InviteMemberBuilder < StandardMemberBuilder
|
||||
def execute
|
||||
if user_by_email
|
||||
find_or_initialize_member_by_user(user_by_email.id)
|
||||
else
|
||||
source.members_and_requesters.find_or_initialize_by(invite_email: invitee) # rubocop:disable CodeReuse/ActiveRecord
|
||||
end
|
||||
end
|
||||
|
||||
private
|
||||
|
||||
def user_by_email
|
||||
source.users_by_emails([invitee])[invitee]
|
||||
end
|
||||
end
|
||||
end
|
|
@ -0,0 +1,23 @@
|
|||
# frozen_string_literal: true
|
||||
|
||||
module Members
|
||||
class StandardMemberBuilder
|
||||
def initialize(source, invitee, existing_members)
|
||||
@source = source
|
||||
@invitee = invitee
|
||||
@existing_members = existing_members
|
||||
end
|
||||
|
||||
def execute
|
||||
find_or_initialize_member_by_user(invitee.id)
|
||||
end
|
||||
|
||||
private
|
||||
|
||||
attr_reader :source, :invitee, :existing_members
|
||||
|
||||
def find_or_initialize_member_by_user(user_id)
|
||||
existing_members[user_id] || source.members_and_requesters.build(user_id: user_id) # rubocop:disable CodeReuse/ActiveRecord
|
||||
end
|
||||
end
|
||||
end
|
|
@ -4,5 +4,5 @@ introduced_by_url: https://gitlab.com/gitlab-org/gitlab/-/merge_requests/81834
|
|||
rollout_issue_url: https://gitlab.com/gitlab-org/gitlab/-/issues/353921
|
||||
milestone: '14.9'
|
||||
type: ops
|
||||
group: group::conversion
|
||||
group: group::acquisition
|
||||
default_enabled: true
|
||||
|
|
Before Width: | Height: | Size: 314 KiB |
After Width: | Height: | Size: 12 KiB |
|
@ -274,7 +274,7 @@ gitlab_rails['ldap_servers'] = {
|
|||
|
||||
This example results in the following sign-in page:
|
||||
|
||||
![Multiple LDAP servers sign in](img/multi_login.gif)
|
||||
![Multiple LDAP servers sign in](img/multi_login.png)
|
||||
|
||||
### Set up LDAP user filter
|
||||
|
||||
|
|
|
@ -111,7 +111,7 @@ To change the worker timeout to 600 seconds:
|
|||
WARNING:
|
||||
This is an experimental [Alpha feature](../../policy/alpha-beta-support.md#alpha-features) and subject to change without notice. The feature
|
||||
is not ready for production use. If you want to use this feature, we recommend testing
|
||||
with non-production data first. See the [known issues](#puma-single-mode-known-issues)
|
||||
outside of production first. See the [known issues](#puma-single-mode-known-issues)
|
||||
for additional details.
|
||||
|
||||
In a memory-constrained environment with less than 4GB of RAM available, consider disabling Puma
|
||||
|
@ -137,6 +137,10 @@ For details on Puma worker and thread settings, see the [Puma requirements](../.
|
|||
The downside of running Puma in this configuration is the reduced throughput, which can be
|
||||
considered a fair tradeoff in a memory-constrained environment.
|
||||
|
||||
Remember to have sufficient swap available to avoid out of memory (OOM)
|
||||
conditions. View the [Memory requirements](../../install/requirements.md#memory)
|
||||
for details.
|
||||
|
||||
### Puma single mode known issues
|
||||
|
||||
When running Puma in single mode, some features are not supported:
|
||||
|
|
|
@ -93,7 +93,8 @@ swap on your server, even if you currently have enough available memory. Having
|
|||
swap helps to reduce the chance of errors occurring if your available memory
|
||||
changes. We also recommend configuring the kernel's swappiness setting to a
|
||||
lower value (such as `10`) to make the most of your memory, while still having
|
||||
the swap available when needed.
|
||||
the swap available when needed. View the
|
||||
[Memory requirements](../../install/requirements.md#memory) for details.
|
||||
|
||||
## Setup instructions
|
||||
|
||||
|
|
After Width: | Height: | Size: 17 KiB |
|
@ -145,8 +145,9 @@ GitLab does not parse very [large nodes](https://nokogiri.org/tutorials/parsing_
|
|||
> - [Introduced](https://gitlab.com/gitlab-org/gitlab/-/issues/202114) in GitLab 13.0 behind the `:junit_pipeline_screenshots_view` feature flag, disabled by default.
|
||||
> - [Feature flag removed](https://gitlab.com/gitlab-org/gitlab/-/issues/216979) in GitLab 13.12.
|
||||
|
||||
Upload your screenshots as [artifacts](../yaml/artifacts_reports.md#artifactsreportsjunit) to GitLab. If JUnit
|
||||
report format XML files contain an `attachment` tag, GitLab parses the attachment. Note that:
|
||||
You can upload your screenshots as [artifacts](../yaml/artifacts_reports.md#artifactsreportsjunit) to GitLab.
|
||||
If JUnit report format XML files contain an `attachment` tag, GitLab parses the attachment.
|
||||
When uploading screenshot artifacts:
|
||||
|
||||
- The `attachment` tag **must** contain the relative path to `$CI_PROJECT_DIR` of the screenshots you uploaded. For
|
||||
example:
|
||||
|
@ -161,8 +162,10 @@ report format XML files contain an `attachment` tag, GitLab parses the attachmen
|
|||
[`artifacts:when: always`](../yaml/index.md#artifactswhen) so that it still uploads a screenshot
|
||||
when a test fails.
|
||||
|
||||
A link to the test case attachment appears in the test case details in
|
||||
[the pipeline test report](#view-unit-test-reports-on-gitlab).
|
||||
After the attachment is uploaded, [the pipeline test report](#view-unit-test-reports-on-gitlab)
|
||||
contains a link to the screenshot, for example:
|
||||
|
||||
![Unit test report screenshot example](img/unit_test_report_screenshot_v13_12.png)
|
||||
|
||||
## Troubleshooting
|
||||
|
||||
|
|
|
@ -486,6 +486,7 @@ You can refer to these guidelines to decide which approach to use:
|
|||
- If your field is stable and its definition doesn't change, even after the flag is
|
||||
removed, [toggle the return value](#toggle-the-value-of-a-field) of the field instead. Note that
|
||||
[all fields should be nullable](#nullable-fields) anyway.
|
||||
- If your field will be accessed from frontend using the `@include` or `@skip` directive, [do not use the `feature_flag` property](#frontend-and-backend-feature-flag-strategies).
|
||||
|
||||
### `feature_flag` property
|
||||
|
||||
|
@ -517,6 +518,20 @@ field :test_field, type: GraphQL::Types::String,
|
|||
feature_flag: :my_feature_flag
|
||||
```
|
||||
|
||||
### Frontend and Backend feature flag strategies
|
||||
|
||||
#### Directives
|
||||
|
||||
When feature flags are used in the frontend to control the `@include` and `@skip` directives, do not use the `feature_flag`
|
||||
property on the server-side. For the accepted backend workaround, see [Toggle the value of a field](#toggle-the-value-of-a-field). It is recommended that the feature flag used in this approach be the same for frontend and backend.
|
||||
|
||||
Even if the frontend directives evaluate to `@include:false` / `@skip:true`, the guarded entity is sent to the backend and matched
|
||||
against the GraphQL schema. We would then get an exception due to a schema mismatch. See the [frontend GraphQL guide](../development/fe_guide/graphql.md#the-include-directive) for more guidance.
|
||||
|
||||
#### Different versions of a query
|
||||
|
||||
See the guide frontend GraphQL guide for [different versions of a query](../development/fe_guide/graphql.md#different-versions-of-a-query), and [why it is not the preferred approach](../development/fe_guide/graphql.md#avoiding-multiple-query-versions)
|
||||
|
||||
### Toggle the value of a field
|
||||
|
||||
This method of using feature flags for fields is to toggle the
|
||||
|
|
|
@ -597,7 +597,7 @@ export default {
|
|||
Note that, even if the directive evaluates to `false`, the guarded entity is sent to the backend and
|
||||
matched against the GraphQL schema. So this approach requires that the feature-flagged entity
|
||||
exists in the schema, even if the feature flag is disabled. When the feature flag is turned off, it
|
||||
is recommended that the resolver returns `null` at the very least.
|
||||
is recommended that the resolver returns `null` at the very least using the same feature flag as the frontend. See the [API GraphQL guide](../api_graphql_styleguide.md#frontend-and-backend-feature-flag-strategies).
|
||||
|
||||
##### Different versions of a query
|
||||
|
||||
|
@ -617,8 +617,10 @@ export default {
|
|||
};
|
||||
```
|
||||
|
||||
This approach is not recommended as it results in bigger merge requests and requires maintaining
|
||||
two similar queries for as long as the feature flag exists. This can be used in cases where the new
|
||||
##### Avoiding multiple query versions
|
||||
|
||||
The multiple version approach is not recommended as it results in bigger merge requests and requires maintaining
|
||||
two similar queries for as long as the feature flag exists. Multiple versions can be used in cases where the new
|
||||
GraphQL entities are not yet part of the schema, or if they are feature-flagged at the schema level
|
||||
(`new_entity: :feature_flag`).
|
||||
|
||||
|
|
|
@ -94,6 +94,14 @@ if your available memory changes. We also recommend configuring the kernel's swa
|
|||
to a low value like `10` to make the most of your RAM while still having the swap
|
||||
available when needed.
|
||||
|
||||
NOTE:
|
||||
Although excessive swapping is undesired and degrades performance, it is an
|
||||
extremely important last resort against out-of-memory conditions. During
|
||||
unexpected system load, such as OS updates or other services on the same host,
|
||||
peak memory load spikes could be much higher than average. Having plenty of swap
|
||||
helps avoid the Linux OOM killer unsafely terminating a potentially critical
|
||||
process, such as PostgreSQL, which can have disastrous consequences.
|
||||
|
||||
## Database
|
||||
|
||||
PostgreSQL is the only supported database, which is bundled with the Omnibus GitLab package.
|
||||
|
|
Before Width: | Height: | Size: 203 KiB |
|
@ -98,8 +98,6 @@ This comment can also be a thread.
|
|||
|
||||
An icon is displayed on the image and a comment field is displayed.
|
||||
|
||||
![Start image thread](img/start_image_discussion.gif)
|
||||
|
||||
## Reply to a comment by sending email
|
||||
|
||||
If you have ["reply by email"](../../administration/reply_by_email.md) configured,
|
||||
|
|
Before Width: | Height: | Size: 936 KiB |
Before Width: | Height: | Size: 107 KiB |
Before Width: | Height: | Size: 857 KiB |
|
@ -385,8 +385,6 @@ To close an issue, you can do the following:
|
|||
- At the top of the issue, select **Close issue**.
|
||||
- In an [issue board](../issue_board.md), drag an issue card from its list into the **Closed** list.
|
||||
|
||||
![close issue from the issue board](img/close_issue_from_board.gif)
|
||||
|
||||
### Reopen a closed issue
|
||||
|
||||
Prerequisites:
|
||||
|
|
|
@ -4,39 +4,22 @@ group: Project Management
|
|||
info: To determine the technical writer assigned to the Stage/Group associated with this page, see https://about.gitlab.com/handbook/engineering/ux/technical-writing/#assignments
|
||||
---
|
||||
|
||||
# Multiple Assignees for Issues **(PREMIUM)**
|
||||
# Multiple assignees for issues **(PREMIUM)**
|
||||
|
||||
> Moved to GitLab Premium in 13.9.
|
||||
> Moved from Starter to Premium in GitLab 13.9.
|
||||
|
||||
In large teams, where there is shared ownership of an issue, it can be difficult
|
||||
to track who is working on it, who already completed their contributions, who
|
||||
didn't even start yet.
|
||||
In large teams with shared ownership, it can be difficult
|
||||
to track who is working on an issue, who's already done, or who hasn't started yet.
|
||||
|
||||
You can also select multiple [assignees](managing_issues.md#assignee) for an issue, making it easier to
|
||||
You can add multiple [assignees](managing_issues.md#assignee) to an issue, making it easier to
|
||||
track, and making clearer who is accountable for it.
|
||||
|
||||
![multiple assignees for issues](img/multiple_assignees_for_issues.png)
|
||||
|
||||
## Use cases
|
||||
|
||||
Consider a team formed by frontend developers, backend developers,
|
||||
UX designers, QA testers, and a product manager working together to bring an idea to
|
||||
market.
|
||||
|
||||
Multiple Assignees for Issues makes collaboration smoother,
|
||||
Multiple assignees for issues makes collaboration smoother,
|
||||
and allows shared responsibilities to be clearly displayed.
|
||||
All assignees are shown across your team's workflows and receive notifications (as they
|
||||
would as single assignees), simplifying communication and ownership.
|
||||
|
||||
Once an assignee had their work completed, they would remove themselves as assignees, making
|
||||
it clear that their role is complete.
|
||||
After an assignee completes their work, they remove themselves as an assignee, making
|
||||
it clear that their task is complete.
|
||||
|
||||
## How it works
|
||||
|
||||
From an opened issue, expand the right sidebar, locate the assignees entry,
|
||||
and select **Edit**. From the dropdown menu, select as many users as you want
|
||||
to assign the issue to.
|
||||
|
||||
![adding multiple assignees](img/multiple_assignees.gif)
|
||||
|
||||
To remove an assignee, clear them from the same dropdown menu.
|
||||
![multiple assignees for issues](img/multiple_assignees_for_issues.png)
|
||||
|
|
|
@ -441,8 +441,6 @@ This label now appears at the top of the label list, under **Prioritized Labels*
|
|||
To change the relative priority of these labels, drag them up and down the list.
|
||||
The labels higher in the list get higher priority.
|
||||
|
||||
![Drag to change label priority](img/labels_drag_priority_v12_1.gif)
|
||||
|
||||
To learn what happens when you sort by priority or label priority, see
|
||||
[Sorting and ordering issue lists](issues/sorting_issue_lists.md).
|
||||
|
||||
|
|
Before Width: | Height: | Size: 156 KiB |
After Width: | Height: | Size: 22 KiB |
|
@ -116,7 +116,7 @@ You can download the source code that's stored in a repository.
|
|||
For the default branch of each repository, GitLab determines which programming languages
|
||||
are used. This information is displayed on the **Project information** page.
|
||||
|
||||
![Repository Languages bar](img/repository_languages_v12_2.gif)
|
||||
![Repository Languages bar](img/repository_languages_v15_2.png)
|
||||
|
||||
When new files are added, this information can take up to five minutes to update.
|
||||
|
||||
|
|
|
@ -302,3 +302,12 @@ fail nor succeed. They also do not leave a clear log. To check for this problem:
|
|||
1. After you run the command, the [background jobs page](../../../admin_area/index.md#background-jobs)
|
||||
should show new mirroring jobs being scheduled, especially when
|
||||
[triggered manually](#update-a-mirror).
|
||||
|
||||
### Invalid URL
|
||||
|
||||
If you receive this error while setting up mirroring over [SSH](#ssh-authentication), make sure the URL is in a valid format.
|
||||
|
||||
Mirroring does not support the short version of SSH clone URLs (`git@gitlab.com:gitlab-org/gitlab.git`)
|
||||
and requires the full version including the protocol (`ssh://git@gitlab.com/gitlab-org/gitlab.git`).
|
||||
|
||||
Make sure that host and project path are separated using `/` instead of `:`.
|
||||
|
|
|
@ -0,0 +1,44 @@
|
|||
# frozen_string_literal: true
|
||||
|
||||
require 'spec_helper'
|
||||
|
||||
RSpec.describe Members::InviteMemberBuilder do
|
||||
let_it_be(:source) { create(:group) }
|
||||
let_it_be(:existing_member) { create(:group_member) }
|
||||
|
||||
let(:existing_members) { { existing_member.user.id => existing_member } }
|
||||
|
||||
describe '#execute' do
|
||||
context 'when user record found by email' do
|
||||
it 'returns member from existing members hash' do
|
||||
expect(described_class.new(source, existing_member.user.email, existing_members).execute).to eq existing_member
|
||||
end
|
||||
|
||||
it 'builds a new member' do
|
||||
user = create(:user)
|
||||
|
||||
member = described_class.new(source, user.email, existing_members).execute
|
||||
|
||||
expect(member).to be_new_record
|
||||
expect(member.user).to eq user
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
context 'when no existing users found by the email' do
|
||||
it 'finds existing member' do
|
||||
member = create(:group_member, :invited, source: source)
|
||||
|
||||
expect(described_class.new(source, member.invite_email, existing_members).execute).to eq member
|
||||
end
|
||||
|
||||
it 'builds a new member' do
|
||||
email = 'test@example.com'
|
||||
|
||||
member = described_class.new(source, email, existing_members).execute
|
||||
|
||||
expect(member).to be_new_record
|
||||
expect(member.invite_email).to eq email
|
||||
end
|
||||
end
|
||||
end
|
|
@ -0,0 +1,25 @@
|
|||
# frozen_string_literal: true
|
||||
|
||||
require 'spec_helper'
|
||||
|
||||
RSpec.describe Members::StandardMemberBuilder do
|
||||
let_it_be(:source) { create(:group) }
|
||||
let_it_be(:existing_member) { create(:group_member) }
|
||||
|
||||
let(:existing_members) { { existing_member.user.id => existing_member } }
|
||||
|
||||
describe '#execute' do
|
||||
it 'returns member from existing members hash' do
|
||||
expect(described_class.new(source, existing_member.user, existing_members).execute).to eq existing_member
|
||||
end
|
||||
|
||||
it 'builds a new member' do
|
||||
user = create(:user)
|
||||
|
||||
member = described_class.new(source, user, existing_members).execute
|
||||
|
||||
expect(member).to be_new_record
|
||||
expect(member.user).to eq user
|
||||
end
|
||||
end
|
||||
end
|