diff --git a/.gitlab/ci/frontend.gitlab-ci.yml b/.gitlab/ci/frontend.gitlab-ci.yml index 5a5a58644b9..a7c103bae13 100644 --- a/.gitlab/ci/frontend.gitlab-ci.yml +++ b/.gitlab/ci/frontend.gitlab-ci.yml @@ -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 diff --git a/.gitlab/ci/rules.gitlab-ci.yml b/.gitlab/ci/rules.gitlab-ci.yml index 11513098e07..47e0b96daa1 100644 --- a/.gitlab/ci/rules.gitlab-ci.yml +++ b/.gitlab/ci/rules.gitlab-ci.yml @@ -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] diff --git a/.markdownlint.yml b/.markdownlint.yml index 9d83bd2e1da..a5f74890815 100644 --- a/.markdownlint.yml +++ b/.markdownlint.yml @@ -123,6 +123,7 @@ proper-names: "SAML", "Sendmail", "Sentry", + "Service Desk", "Sidekiq", "Shibboleth", "Slack", diff --git a/GITALY_SERVER_VERSION b/GITALY_SERVER_VERSION index fb8d337ddee..cb758d9ac48 100644 --- a/GITALY_SERVER_VERSION +++ b/GITALY_SERVER_VERSION @@ -1 +1 @@ -43cb85d43809733551d9ad682987d89a2f4afb36 +8e3eafce11e3b48177872c28c58614226ae18602 diff --git a/app/services/members/creator_service.rb b/app/services/members/creator_service.rb index cc69a1973af..f59a3ed77eb 100644 --- a/app/services/members/creator_service.rb +++ b/app/services/members/creator_service.rb @@ -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, - 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 + 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 + } + + 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 diff --git a/app/services/members/invite_member_builder.rb b/app/services/members/invite_member_builder.rb new file mode 100644 index 00000000000..e925121bb1e --- /dev/null +++ b/app/services/members/invite_member_builder.rb @@ -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 diff --git a/app/services/members/standard_member_builder.rb b/app/services/members/standard_member_builder.rb new file mode 100644 index 00000000000..24e71f80d7e --- /dev/null +++ b/app/services/members/standard_member_builder.rb @@ -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 diff --git a/config/feature_flags/ops/gitlab_experiment.yml b/config/feature_flags/ops/gitlab_experiment.yml index bd676fc7c9c..faf55130c33 100644 --- a/config/feature_flags/ops/gitlab_experiment.yml +++ b/config/feature_flags/ops/gitlab_experiment.yml @@ -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 diff --git a/doc/administration/auth/ldap/img/multi_login.gif b/doc/administration/auth/ldap/img/multi_login.gif deleted file mode 100644 index 5aee6090793..00000000000 Binary files a/doc/administration/auth/ldap/img/multi_login.gif and /dev/null differ diff --git a/doc/administration/auth/ldap/img/multi_login.png b/doc/administration/auth/ldap/img/multi_login.png new file mode 100644 index 00000000000..512f403a442 Binary files /dev/null and b/doc/administration/auth/ldap/img/multi_login.png differ diff --git a/doc/administration/auth/ldap/index.md b/doc/administration/auth/ldap/index.md index 55b57193bf3..3bf2fff898d 100644 --- a/doc/administration/auth/ldap/index.md +++ b/doc/administration/auth/ldap/index.md @@ -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 diff --git a/doc/administration/operations/puma.md b/doc/administration/operations/puma.md index 6b4577ad651..66aa6acaa09 100644 --- a/doc/administration/operations/puma.md +++ b/doc/administration/operations/puma.md @@ -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: diff --git a/doc/administration/reference_architectures/1k_users.md b/doc/administration/reference_architectures/1k_users.md index c356c7224cb..e3c5928389e 100644 --- a/doc/administration/reference_architectures/1k_users.md +++ b/doc/administration/reference_architectures/1k_users.md @@ -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 diff --git a/doc/ci/testing/img/unit_test_report_screenshot_v13_12.png b/doc/ci/testing/img/unit_test_report_screenshot_v13_12.png new file mode 100644 index 00000000000..d5b1775b4ca Binary files /dev/null and b/doc/ci/testing/img/unit_test_report_screenshot_v13_12.png differ diff --git a/doc/ci/testing/unit_test_reports.md b/doc/ci/testing/unit_test_reports.md index 1328a8a0d0f..c8e0d6135df 100644 --- a/doc/ci/testing/unit_test_reports.md +++ b/doc/ci/testing/unit_test_reports.md @@ -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 diff --git a/doc/development/api_graphql_styleguide.md b/doc/development/api_graphql_styleguide.md index 6c5d91af9ac..37de7044765 100644 --- a/doc/development/api_graphql_styleguide.md +++ b/doc/development/api_graphql_styleguide.md @@ -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 diff --git a/doc/development/fe_guide/graphql.md b/doc/development/fe_guide/graphql.md index 67b53fa0299..10db332d64c 100644 --- a/doc/development/fe_guide/graphql.md +++ b/doc/development/fe_guide/graphql.md @@ -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`). diff --git a/doc/install/requirements.md b/doc/install/requirements.md index 1d4b7fe85e7..f7c1bf431a6 100644 --- a/doc/install/requirements.md +++ b/doc/install/requirements.md @@ -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. diff --git a/doc/user/discussions/img/start_image_discussion.gif b/doc/user/discussions/img/start_image_discussion.gif deleted file mode 100644 index 18b2a4701cc..00000000000 Binary files a/doc/user/discussions/img/start_image_discussion.gif and /dev/null differ diff --git a/doc/user/discussions/index.md b/doc/user/discussions/index.md index a0649a61905..5b7707cbd2d 100644 --- a/doc/user/discussions/index.md +++ b/doc/user/discussions/index.md @@ -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, diff --git a/doc/user/project/img/labels_drag_priority_v12_1.gif b/doc/user/project/img/labels_drag_priority_v12_1.gif deleted file mode 100644 index a568490da5f..00000000000 Binary files a/doc/user/project/img/labels_drag_priority_v12_1.gif and /dev/null differ diff --git a/doc/user/project/issues/img/close_issue_from_board.gif b/doc/user/project/issues/img/close_issue_from_board.gif deleted file mode 100644 index 4814b42687b..00000000000 Binary files a/doc/user/project/issues/img/close_issue_from_board.gif and /dev/null differ diff --git a/doc/user/project/issues/img/multiple_assignees.gif b/doc/user/project/issues/img/multiple_assignees.gif deleted file mode 100644 index 055a0efd9ae..00000000000 Binary files a/doc/user/project/issues/img/multiple_assignees.gif and /dev/null differ diff --git a/doc/user/project/issues/managing_issues.md b/doc/user/project/issues/managing_issues.md index fbdce211295..15d8da7f544 100644 --- a/doc/user/project/issues/managing_issues.md +++ b/doc/user/project/issues/managing_issues.md @@ -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: diff --git a/doc/user/project/issues/multiple_assignees_for_issues.md b/doc/user/project/issues/multiple_assignees_for_issues.md index 105dcd529c8..db160b6cfe8 100644 --- a/doc/user/project/issues/multiple_assignees_for_issues.md +++ b/doc/user/project/issues/multiple_assignees_for_issues.md @@ -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) diff --git a/doc/user/project/labels.md b/doc/user/project/labels.md index 160dade87bb..333b073ee40 100644 --- a/doc/user/project/labels.md +++ b/doc/user/project/labels.md @@ -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). diff --git a/doc/user/project/repository/img/repository_languages_v12_2.gif b/doc/user/project/repository/img/repository_languages_v12_2.gif deleted file mode 100644 index d0a0e57c919..00000000000 Binary files a/doc/user/project/repository/img/repository_languages_v12_2.gif and /dev/null differ diff --git a/doc/user/project/repository/img/repository_languages_v15_2.png b/doc/user/project/repository/img/repository_languages_v15_2.png new file mode 100644 index 00000000000..94cfa1cc161 Binary files /dev/null and b/doc/user/project/repository/img/repository_languages_v15_2.png differ diff --git a/doc/user/project/repository/index.md b/doc/user/project/repository/index.md index 7caf88f1faa..a8937d4f705 100644 --- a/doc/user/project/repository/index.md +++ b/doc/user/project/repository/index.md @@ -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. diff --git a/doc/user/project/repository/mirror/index.md b/doc/user/project/repository/mirror/index.md index 193bae0a3f9..4537f8520cd 100644 --- a/doc/user/project/repository/mirror/index.md +++ b/doc/user/project/repository/mirror/index.md @@ -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 `:`. diff --git a/spec/services/members/invite_member_builder_spec.rb b/spec/services/members/invite_member_builder_spec.rb new file mode 100644 index 00000000000..52de65364c4 --- /dev/null +++ b/spec/services/members/invite_member_builder_spec.rb @@ -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 diff --git a/spec/services/members/standard_member_builder_spec.rb b/spec/services/members/standard_member_builder_spec.rb new file mode 100644 index 00000000000..16daff53d31 --- /dev/null +++ b/spec/services/members/standard_member_builder_spec.rb @@ -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