From e1b9b92a49eea88ea7c3b101aec0315e64e94678 Mon Sep 17 00:00:00 2001 From: GitLab Bot Date: Thu, 28 Jan 2021 18:09:27 +0000 Subject: [PATCH] Add latest changes from gitlab-org/gitlab@master --- .../components/table/members_table.vue | 6 +- .../components/table/members_table_cell.vue | 8 +- app/assets/javascripts/members/utils.js | 14 ++- .../merge_requests/application_controller.rb | 2 +- app/finders/autocomplete/users_finder.rb | 10 +- app/helpers/groups/group_members_helper.rb | 2 +- app/helpers/issuables_helper.rb | 1 + .../projects/project_members_helper.rb | 2 +- app/models/issue.rb | 2 +- app/models/terraform/state.rb | 1 + .../metric_presenter.rb | 0 .../concerns/user_status_tooltip.rb | 4 + app/serializers/member_entity.rb | 4 + app/serializers/merge_request_user_entity.rb | 2 + .../terraform/remote_state_handler.rb | 22 +++-- app/views/admin/runners/index.html.haml | 2 +- .../authentication/_authenticate.html.haml | 2 +- app/views/authentication/_register.html.haml | 2 +- app/views/profiles/emails/index.html.haml | 2 +- .../296943-expose-user-availability.yml | 5 + .../298703-terraform-name-validation.yml | 5 + .../unreleased/gl-button-utf-try-again.yml | 5 + .../unreleased/gl-form-input-user-email.yml | 5 + doc/README.md | 94 +++++++++---------- doc/ci/pipeline_editor/index.md | 4 +- doc/development/stage_group_dashboards.md | 4 + doc/operations/feature_flags.md | 8 +- doc/user/operations_dashboard/index.md | 7 +- doc/user/project/settings/index.md | 39 ++++++++ locale/gitlab.pot | 12 +++ .../user_views_open_merge_request_spec.rb | 16 ++++ .../finders/autocomplete/users_finder_spec.rb | 5 + .../fixtures/api/schemas/entities/member.json | 4 +- .../api/schemas/entities/member_user.json | 1 + .../table/members_table_cell_spec.js | 20 ++-- .../components/table/members_table_spec.js | 7 +- spec/frontend/members/mock_data.js | 4 + spec/frontend/members/utils_spec.js | 62 ++++++------ spec/models/terraform/state_spec.rb | 3 + spec/serializers/member_entity_spec.rb | 36 ++++++- spec/serializers/member_serializer_spec.rb | 4 +- .../merge_request_user_entity_spec.rb | 18 ++++ 42 files changed, 317 insertions(+), 139 deletions(-) rename app/presenters/{dev_ops_score => dev_ops_report}/metric_presenter.rb (100%) create mode 100644 changelogs/unreleased/296943-expose-user-availability.yml create mode 100644 changelogs/unreleased/298703-terraform-name-validation.yml create mode 100644 changelogs/unreleased/gl-button-utf-try-again.yml create mode 100644 changelogs/unreleased/gl-form-input-user-email.yml diff --git a/app/assets/javascripts/members/components/table/members_table.vue b/app/assets/javascripts/members/components/table/members_table.vue index 82728a27b8f..2a27b0358a0 100644 --- a/app/assets/javascripts/members/components/table/members_table.vue +++ b/app/assets/javascripts/members/components/table/members_table.vue @@ -32,7 +32,7 @@ export default { import('ee_component/members/components/ldap/ldap_override_confirmation_modal.vue'), }, computed: { - ...mapState(['members', 'tableFields', 'tableAttrs', 'currentUserId', 'sourceId']), + ...mapState(['members', 'tableFields', 'tableAttrs', 'currentUserId']), filteredFields() { return FIELDS.filter( (field) => this.tableFields.includes(field.key) && this.showField(field), @@ -55,9 +55,9 @@ export default { methods: { hasActionButtons(member) { return ( - canRemove(member, this.sourceId) || + canRemove(member) || canResend(member) || - canUpdate(member, this.currentUserId, this.sourceId) || + canUpdate(member, this.currentUserId) || canOverride(member) ); }, diff --git a/app/assets/javascripts/members/components/table/members_table_cell.vue b/app/assets/javascripts/members/components/table/members_table_cell.vue index 20aa01b96bc..1f537740f94 100644 --- a/app/assets/javascripts/members/components/table/members_table_cell.vue +++ b/app/assets/javascripts/members/components/table/members_table_cell.vue @@ -19,7 +19,7 @@ export default { }, }, computed: { - ...mapState(['sourceId', 'currentUserId']), + ...mapState(['currentUserId']), isGroup() { return isGroup(this.member); }, @@ -41,19 +41,19 @@ export default { return MEMBER_TYPES.user; }, isDirectMember() { - return isDirectMember(this.member, this.sourceId); + return isDirectMember(this.member); }, isCurrentUser() { return isCurrentUser(this.member, this.currentUserId); }, canRemove() { - return canRemove(this.member, this.sourceId); + return canRemove(this.member); }, canResend() { return canResend(this.member); }, canUpdate() { - return canUpdate(this.member, this.currentUserId, this.sourceId); + return canUpdate(this.member, this.currentUserId); }, }, render() { diff --git a/app/assets/javascripts/members/utils.js b/app/assets/javascripts/members/utils.js index 723b371ccb0..bac83533214 100644 --- a/app/assets/javascripts/members/utils.js +++ b/app/assets/javascripts/members/utils.js @@ -35,26 +35,24 @@ export const isGroup = (member) => { return Boolean(member.sharedWithGroup); }; -export const isDirectMember = (member, sourceId) => { - return isGroup(member) || member.source?.id === sourceId; +export const isDirectMember = (member) => { + return isGroup(member) || member.isDirectMember; }; export const isCurrentUser = (member, currentUserId) => { return member.user?.id === currentUserId; }; -export const canRemove = (member, sourceId) => { - return isDirectMember(member, sourceId) && member.canRemove; +export const canRemove = (member) => { + return isDirectMember(member) && member.canRemove; }; export const canResend = (member) => { return Boolean(member.invite?.canResend); }; -export const canUpdate = (member, currentUserId, sourceId) => { - return ( - !isCurrentUser(member, currentUserId) && isDirectMember(member, sourceId) && member.canUpdate - ); +export const canUpdate = (member, currentUserId) => { + return !isCurrentUser(member, currentUserId) && isDirectMember(member) && member.canUpdate; }; export const parseSortParam = (sortableFields) => { diff --git a/app/controllers/projects/merge_requests/application_controller.rb b/app/controllers/projects/merge_requests/application_controller.rb index 9cac9f37eb7..e74717a44ab 100644 --- a/app/controllers/projects/merge_requests/application_controller.rb +++ b/app/controllers/projects/merge_requests/application_controller.rb @@ -20,7 +20,7 @@ class Projects::MergeRequests::ApplicationController < Projects::ApplicationCont end def preloadable_mr_relations - [:metrics, :assignees, { author: :status }] + [:metrics, { assignees: :status }, { author: :status }] end def merge_request_params diff --git a/app/finders/autocomplete/users_finder.rb b/app/finders/autocomplete/users_finder.rb index 8dc3c2320ed..ff5d9ea7d19 100644 --- a/app/finders/autocomplete/users_finder.rb +++ b/app/finders/autocomplete/users_finder.rb @@ -38,7 +38,9 @@ module Autocomplete end end - items.uniq + items.uniq.tap do |unique_items| + preload_associations(unique_items) + end end private @@ -91,6 +93,12 @@ module Autocomplete User.none end end + + # rubocop: disable CodeReuse/ActiveRecord + def preload_associations(items) + ActiveRecord::Associations::Preloader.new.preload(items, :status) + end + # rubocop: enable CodeReuse/ActiveRecord end end diff --git a/app/helpers/groups/group_members_helper.rb b/app/helpers/groups/group_members_helper.rb index 0f2c7ba2630..3e7d6febabf 100644 --- a/app/helpers/groups/group_members_helper.rb +++ b/app/helpers/groups/group_members_helper.rb @@ -18,7 +18,7 @@ module Groups::GroupMembersHelper end def members_data_json(group, members) - MemberSerializer.new.represent(members, { current_user: current_user, group: group }).to_json + MemberSerializer.new.represent(members, { current_user: current_user, group: group, source: group }).to_json end # Overridden in `ee/app/helpers/ee/groups/group_members_helper.rb` diff --git a/app/helpers/issuables_helper.rb b/app/helpers/issuables_helper.rb index da142cbed0e..79e7a279479 100644 --- a/app/helpers/issuables_helper.rb +++ b/app/helpers/issuables_helper.rb @@ -346,6 +346,7 @@ module IssuablesHelper def assignee_sidebar_data(assignee, merge_request: nil) { avatar_url: assignee.avatar_url, name: assignee.name, username: assignee.username }.tap do |data| data[:can_merge] = merge_request.can_be_merged_by?(assignee) if merge_request + data[:availability] = assignee.status.availability if assignee.association(:status).loaded? && assignee.status&.availability end end diff --git a/app/helpers/projects/project_members_helper.rb b/app/helpers/projects/project_members_helper.rb index 7c6989c0830..99c1b742da4 100644 --- a/app/helpers/projects/project_members_helper.rb +++ b/app/helpers/projects/project_members_helper.rb @@ -32,7 +32,7 @@ module Projects::ProjectMembersHelper end def project_members_data_json(project, members) - MemberSerializer.new.represent(members, { current_user: current_user, group: project.group }).to_json + MemberSerializer.new.represent(members, { current_user: current_user, group: project.group, source: project }).to_json end def project_members_list_data_attributes(project, members) diff --git a/app/models/issue.rb b/app/models/issue.rb index 5da9f67f6ef..79d0229a281 100644 --- a/app/models/issue.rb +++ b/app/models/issue.rb @@ -132,7 +132,7 @@ class Issue < ApplicationRecord scope :counts_by_state, -> { reorder(nil).group(:state_id).count } scope :service_desk, -> { where(author: ::User.support_bot) } - scope :inc_relations_for_view, -> { includes(author: :status) } + scope :inc_relations_for_view, -> { includes(author: :status, assignees: :status) } # An issue can be uniquely identified by project_id and iid # Takes one or more sets of composite IDs, expressed as hash-like records of diff --git a/app/models/terraform/state.rb b/app/models/terraform/state.rb index a301c4a9689..eb7d465d585 100644 --- a/app/models/terraform/state.rb +++ b/app/models/terraform/state.rb @@ -24,6 +24,7 @@ module Terraform scope :ordered_by_name, -> { order(:name) } scope :with_name, -> (name) { where(name: name) } + validates :name, presence: true, uniqueness: { scope: :project_id } validates :project_id, presence: true validates :uuid, presence: true, uniqueness: true, length: { is: UUID_LENGTH }, format: { with: HEX_REGEXP, message: 'only allows hex characters' } diff --git a/app/presenters/dev_ops_score/metric_presenter.rb b/app/presenters/dev_ops_report/metric_presenter.rb similarity index 100% rename from app/presenters/dev_ops_score/metric_presenter.rb rename to app/presenters/dev_ops_report/metric_presenter.rb diff --git a/app/serializers/concerns/user_status_tooltip.rb b/app/serializers/concerns/user_status_tooltip.rb index fcf6700cb59..ca2854224a7 100644 --- a/app/serializers/concerns/user_status_tooltip.rb +++ b/app/serializers/concerns/user_status_tooltip.rb @@ -16,6 +16,10 @@ module UserStatusTooltip status_loaded? && show_status_emoji?(user.status) end + expose :availability, if: -> (*) { status_loaded? } do |user| + user.status&.availability + end + private def status_loaded? diff --git a/app/serializers/member_entity.rb b/app/serializers/member_entity.rb index 584ba4c62de..e8f2bb28d60 100644 --- a/app/serializers/member_entity.rb +++ b/app/serializers/member_entity.rb @@ -23,6 +23,10 @@ class MemberEntity < Grape::Entity member.can_remove? end + expose :is_direct_member do |member, options| + member.source == options[:source] + end + expose :access_level do expose :human_access, as: :string_value expose :access_level, as: :integer_value diff --git a/app/serializers/merge_request_user_entity.rb b/app/serializers/merge_request_user_entity.rb index 604c9cabd50..82ec4be5895 100644 --- a/app/serializers/merge_request_user_entity.rb +++ b/app/serializers/merge_request_user_entity.rb @@ -1,6 +1,8 @@ # frozen_string_literal: true class MergeRequestUserEntity < ::API::Entities::UserBasic + include UserStatusTooltip + expose :can_merge do |reviewer, options| options[:merge_request]&.can_be_merged_by?(reviewer) end diff --git a/app/services/terraform/remote_state_handler.rb b/app/services/terraform/remote_state_handler.rb index 7e79cb9e007..9500a821071 100644 --- a/app/services/terraform/remote_state_handler.rb +++ b/app/services/terraform/remote_state_handler.rb @@ -68,12 +68,14 @@ module Terraform find_params = { project: project, name: params[:name] } - if find_only - Terraform::State.find_by(find_params) || # rubocop: disable CodeReuse/ActiveRecord - raise(ActiveRecord::RecordNotFound.new("Couldn't find state")) - else - Terraform::State.create_or_find_by(find_params) - end + return find_state!(find_params) if find_only + + state = Terraform::State.create_or_find_by(find_params) + + # https://github.com/rails/rails/issues/36027 + return state unless state.errors.of_kind? :name, :taken + + find_state(find_params) end def lock_matches?(state) @@ -86,5 +88,13 @@ module Terraform def can_modify_state? current_user.can?(:admin_terraform_state, project) end + + def find_state(find_params) + Terraform::State.find_by(find_params) # rubocop: disable CodeReuse/ActiveRecord + end + + def find_state!(find_params) + find_state(find_params) || raise(ActiveRecord::RecordNotFound.new("Couldn't find state")) + end end end diff --git a/app/views/admin/runners/index.html.haml b/app/views/admin/runners/index.html.haml index 5ddb03bcc04..8e62dae6c4d 100644 --- a/app/views/admin/runners/index.html.haml +++ b/app/views/admin/runners/index.html.haml @@ -50,7 +50,7 @@ .filtered-search-box = dropdown_tag(_('Recent searches'), options: { wrapper_class: 'filtered-search-history-dropdown-wrapper', - toggle_class: 'gl-button btn filtered-search-history-dropdown-toggle-button', + toggle_class: 'btn filtered-search-history-dropdown-toggle-button', dropdown_class: 'filtered-search-history-dropdown', content_class: 'filtered-search-history-dropdown-content' }) do .js-filtered-search-history-dropdown{ data: { full_path: admin_runners_path } } diff --git a/app/views/authentication/_authenticate.html.haml b/app/views/authentication/_authenticate.html.haml index 17e855dbddd..2d8948ae9aa 100644 --- a/app/views/authentication/_authenticate.html.haml +++ b/app/views/authentication/_authenticate.html.haml @@ -7,7 +7,7 @@ %script#js-authenticate-token-2fa-error{ type: "text/template" } %div %p <%= error_message %> (<%= error_name %>) - %a.btn.btn-block.btn-warning#js-token-2fa-try-again= _("Try again?") + %a.btn.gl-button.btn-block.btn-warning#js-token-2fa-try-again= _("Try again?") %script#js-authenticate-token-2fa-authenticated{ type: "text/template" } %div diff --git a/app/views/authentication/_register.html.haml b/app/views/authentication/_register.html.haml index 912aca99d6a..9b66072869a 100644 --- a/app/views/authentication/_register.html.haml +++ b/app/views/authentication/_register.html.haml @@ -21,7 +21,7 @@ %div %p %span <%= error_message %> (<%= error_name %>) - %a.btn.btn-warning#js-token-2fa-try-again= _("Try again?") + %a.btn.gl-button.btn-warning#js-token-2fa-try-again= _("Try again?") %script#js-register-token-2fa-registered{ type: "text/template" } .row.gl-mb-3 diff --git a/app/views/profiles/emails/index.html.haml b/app/views/profiles/emails/index.html.haml index 0a1b08f3c68..89198b0a65b 100644 --- a/app/views/profiles/emails/index.html.haml +++ b/app/views/profiles/emails/index.html.haml @@ -13,7 +13,7 @@ = form_for 'email', url: profile_emails_path do |f| .form-group = f.label :email, _('Email'), class: 'label-bold' - = f.text_field :email, class: 'form-control', data: { qa_selector: 'email_address_field' } + = f.text_field :email, class: 'form-control gl-form-input', data: { qa_selector: 'email_address_field' } .gl-mt-3 = f.submit _('Add email address'), class: 'gl-button btn btn-success', data: { qa_selector: 'add_email_address_button' } %hr diff --git a/changelogs/unreleased/296943-expose-user-availability.yml b/changelogs/unreleased/296943-expose-user-availability.yml new file mode 100644 index 00000000000..a12629468de --- /dev/null +++ b/changelogs/unreleased/296943-expose-user-availability.yml @@ -0,0 +1,5 @@ +--- +title: Expose user availablility data on issuable pages +merge_request: 52333 +author: +type: other diff --git a/changelogs/unreleased/298703-terraform-name-validation.yml b/changelogs/unreleased/298703-terraform-name-validation.yml new file mode 100644 index 00000000000..678bb2e48e8 --- /dev/null +++ b/changelogs/unreleased/298703-terraform-name-validation.yml @@ -0,0 +1,5 @@ +--- +title: Add name validation to Terraform state +merge_request: 52102 +author: +type: changed diff --git a/changelogs/unreleased/gl-button-utf-try-again.yml b/changelogs/unreleased/gl-button-utf-try-again.yml new file mode 100644 index 00000000000..f6d93f2d49d --- /dev/null +++ b/changelogs/unreleased/gl-button-utf-try-again.yml @@ -0,0 +1,5 @@ +--- +title: Apply new GitLab UI class for U2F try again button +merge_request: 52759 +author: Yogi (@yo) +type: other diff --git a/changelogs/unreleased/gl-form-input-user-email.yml b/changelogs/unreleased/gl-form-input-user-email.yml new file mode 100644 index 00000000000..4bc1552100a --- /dev/null +++ b/changelogs/unreleased/gl-form-input-user-email.yml @@ -0,0 +1,5 @@ +--- +title: Apply new GitLab UI for input field in user email settings +merge_request: 52427 +author: Yogi (@yo) +type: other diff --git a/doc/README.md b/doc/README.md index 46e57617cf3..4202da762d1 100644 --- a/doc/README.md +++ b/doc/README.md @@ -23,32 +23,32 @@ Here you can access the complete documentation for GitLab, the single applicatio No matter how you use GitLab, we have documentation for you. -| Essential documentation | Essential documentation | -|:---------------------------------------------------------------------------------------------------------------------|:---------------------------------------------------------------------------------------------------------------------------| +| Essential documentation | Essential documentation | +|:------------------------|:------------------------| | [**User documentation**](user/index.md)
Discover features and concepts for GitLab users. | [**Administrator documentation**](administration/index.md)
Everything GitLab self-managed administrators need to know. | -| [**Contributing to GitLab**](#contributing-to-gitlab)
At GitLab, everyone can contribute! | [**New to Git and GitLab?**](#new-to-git-and-gitlab)
We have the resources to get you started. | -| [**Build an integration with GitLab**](#build-an-integration-with-gitlab)
Consult our integration documentation. | [**Coming to GitLab from another platform?**](#coming-to-gitlab-from-another-platform)
Consult our guides. | -| [**Install GitLab**](https://about.gitlab.com/install/)
Installation options for different platforms. | [**Customers**](subscriptions/index.md)
Information for new and existing customers. | -| [**Update GitLab**](update/README.md)
Update your GitLab self-managed instance to the latest version. | [**Reference Architectures**](administration/reference_architectures/index.md)
GitLab reference architectures | -| [**GitLab releases**](https://about.gitlab.com/releases/)
What's new in GitLab. | | +| [**Contributing to GitLab**](#contributing-to-gitlab)
At GitLab, everyone can contribute! | [**New to Git and GitLab?**](#new-to-git-and-gitlab)
We have the resources to get you started. | +| [**Build an integration with GitLab**](#build-an-integration-with-gitlab)
Consult our integration documentation. | [**Coming to GitLab from another platform?**](#coming-to-gitlab-from-another-platform)
Consult our guides. | +| [**Install GitLab**](https://about.gitlab.com/install/)
Installation options for different platforms. | [**Customers**](subscriptions/index.md)
Information for new and existing customers. | +| [**Update GitLab**](update/README.md)
Update your GitLab self-managed instance to the latest version. | [**Reference Architectures**](administration/reference_architectures/index.md)
GitLab reference architectures. | +| [**GitLab releases**](https://about.gitlab.com/releases/)
What's new in GitLab. | | ## Popular topics Have a look at some of our most popular topics: -| Popular topic | Description | -|:-----------------------------------------------------------------------------------------------------------|:---------------------------------------------------------------------------| -| [Two-factor authentication](user/profile/account/two_factor_authentication.md) | Improve the security of your GitLab account. | -| [GitLab groups](user/group/index.md) | Manage projects together. | -| [GitLab CI/CD pipeline configuration reference](ci/yaml/README.md) | Available configuration options for `.gitlab-ci.yml` files. | -| [Activate GitLab EE with a license](user/admin_area/license.md) **(STARTER ONLY)** | Activate GitLab Enterprise Edition functionality with a license. | -| [Back up and restore GitLab](raketasks/backup_restore.md) **(FREE SELF)** | Rake tasks for backing up and restoring GitLab self-managed instances. | -| [GitLab release and maintenance policy](policy/maintenance.md) | Policies for version naming and cadence, and also upgrade recommendations. | -| [Elasticsearch integration](integration/elasticsearch.md) **(STARTER ONLY)** | Integrate Elasticsearch with GitLab to enable advanced searching. | -| [Omnibus GitLab database settings](https://docs.gitlab.com/omnibus/settings/database.html) **(FREE SELF)** | Database settings for Omnibus GitLab self-managed instances. | -| [Omnibus GitLab NGINX settings](https://docs.gitlab.com/omnibus/settings/nginx.html) **(FREE SELF)** | NGINX settings for Omnibus GitLab self-managed instances. | -| [Omnibus GitLab SSL configuration](https://docs.gitlab.com/omnibus/settings/ssl.html) **(FREE SELF)** | SSL settings for Omnibus GitLab self-managed instances. | -| [GitLab.com settings](user/gitlab_com/index.md) | Settings used for GitLab.com. | +| Popular topic | Description | +|:-------------------------------------------------------------------------------------------|:------------| +| [Two-factor authentication](user/profile/account/two_factor_authentication.md) | Improve the security of your GitLab account. | +| [GitLab groups](user/group/index.md) | Manage projects together. | +| [GitLab CI/CD pipeline configuration reference](ci/yaml/README.md) | Available configuration options for `.gitlab-ci.yml` files. | +| [Activate GitLab EE with a license](user/admin_area/license.md) | Activate GitLab Enterprise Edition functionality with a license. | +| [Back up and restore GitLab](raketasks/backup_restore.md) | Rake tasks for backing up and restoring GitLab self-managed instances. | +| [GitLab release and maintenance policy](policy/maintenance.md) | Policies for version naming and cadence, and also upgrade recommendations. | +| [Elasticsearch integration](integration/elasticsearch.md) | Integrate Elasticsearch with GitLab to enable advanced searching. | +| [Omnibus GitLab database settings](https://docs.gitlab.com/omnibus/settings/database.html) | Database settings for Omnibus GitLab self-managed instances. | +| [Omnibus GitLab NGINX settings](https://docs.gitlab.com/omnibus/settings/nginx.html) | NGINX settings for Omnibus GitLab self-managed instances. | +| [Omnibus GitLab SSL configuration](https://docs.gitlab.com/omnibus/settings/ssl.html) | SSL settings for Omnibus GitLab self-managed instances. | +| [GitLab.com settings](user/gitlab_com/index.md) | Settings used for GitLab.com. | ## The entire DevOps lifecycle @@ -64,53 +64,53 @@ Working with new systems can be daunting. We have the following documentation to rapidly uplift your GitLab knowledge: -| Topic | Description | -|:--------------------------------------------------------------------------------------------------|:---------------------------------------------------------------| -| [GitLab basics guides](gitlab-basics/README.md) | Start working on the command line and with GitLab. | -| [GitLab workflow overview](https://about.gitlab.com/blog/2016/10/25/gitlab-workflow-an-overview/) | Enhance your workflow with the best of GitLab Workflow. | -| [Get started with GitLab CI/CD](ci/quick_start/README.md) | Quickly implement GitLab CI/CD. | -| [Auto DevOps](topics/autodevops/index.md) | Learn more about Auto DevOps in GitLab. | -| [GitLab Markdown](user/markdown.md) | Advanced formatting system (GitLab Flavored Markdown) | +| Topic | Description | +|:--------------------------------------------------------------------------------------------------|:------------| +| [GitLab basics guides](gitlab-basics/README.md) | Start working on the command line and with GitLab. | +| [GitLab workflow overview](https://about.gitlab.com/blog/2016/10/25/gitlab-workflow-an-overview/) | Enhance your workflow with the best of GitLab Workflow. | +| [Get started with GitLab CI/CD](ci/quick_start/README.md) | Quickly implement GitLab CI/CD. | +| [Auto DevOps](topics/autodevops/index.md) | Learn more about Auto DevOps in GitLab. | +| [GitLab Markdown](user/markdown.md) | Advanced formatting system (GitLab Flavored Markdown). | ### User account Learn more about GitLab account management: -| Topic | Description | -|:-----------------------------------------------------------|:---------------------------------------------------------------------------------------------------------------------------| -| [User account](user/profile/index.md) | Manage your account. | +| Topic | Description | +|:-----------------------------------------------------------|:------------| +| [User account](user/profile/index.md) | Manage your account. | | [Authentication](topics/authentication/index.md) | Account security with two-factor authentication, set up your SSH keys, and deploy keys for secure access to your projects. | -| [Profile settings](user/profile/index.md#profile-settings) | Manage your profile settings, two factor authentication, and more. | -| [User permissions](user/permissions.md) | Learn what each role in a project can do. | +| [Profile settings](user/profile/index.md#profile-settings) | Manage your profile settings, two factor authentication, and more. | +| [User permissions](user/permissions.md) | Learn what each role in a project can do. | ### Git and GitLab Learn more about using Git, and using Git with GitLab: -| Topic | Description | -|:-----------------------------------------------------------------------------|:---------------------------------------------------------------------------| +| Topic | Description | +|:-----------------------------------------------------------------------------|:------------| | [Git](topics/git/index.md) | Getting started with Git, branching strategies, Git LFS, and advanced use. | -| [Git cheat sheet](https://about.gitlab.com/images/press/git-cheat-sheet.pdf) | Download a PDF describing the most used Git operations. | -| [GitLab Flow](topics/gitlab_flow.md) | Explore the best of Git with the GitLab Flow strategy. | +| [Git cheat sheet](https://about.gitlab.com/images/press/git-cheat-sheet.pdf) | Download a PDF describing the most used Git operations. | +| [GitLab Flow](topics/gitlab_flow.md) | Explore the best of Git with the GitLab Flow strategy. | ## Coming to GitLab from another platform If you are coming to GitLab from another platform, the following information is useful: -| Topic | Description | -|:----------------------------------------------------|:---------------------------------------------------------------------------------------| +| Topic | Description | +|:----------------------------------------------------|:------------| | [Importing to GitLab](user/project/import/index.md) | Import your projects from GitHub, Bitbucket, GitLab.com, FogBugz, and SVN into GitLab. | -| [Migrating from SVN](user/project/import/svn.md) | Convert a SVN repository to Git and GitLab. | +| [Migrating from SVN](user/project/import/svn.md) | Convert a SVN repository to Git and GitLab. | ## Build an integration with GitLab There are many ways to integrate with GitLab, including: -| Topic | Description | -|:-------------------------------------------|:---------------------------------------------| -| [GitLab REST API](api/README.md) | Integrate with GitLab using our REST API. | +| Topic | Description | +|:-------------------------------------------|:------------| +| [GitLab REST API](api/README.md) | Integrate with GitLab using our REST API. | | [GitLab GraphQL API](api/graphql/index.md) | Integrate with GitLab using our GraphQL API. | -| [Integrations](integration/README.md) | Integrations with third-party products. | +| [Integrations](integration/README.md) | Integrations with third-party products. | ## Contributing to GitLab @@ -119,8 +119,8 @@ and GitLab Enterprise Edition is [open-core](https://gitlab.com/gitlab-org/gitla Learn how to contribute to GitLab with the following resources: -| Topic | Description | -|:------------------------------------------------------------|:-----------------------------------------| +| Topic | Description | +|:------------------------------------------------------------|:------------| | [Development](development/README.md) | How to contribute to GitLab development. | -| [Legal](legal/README.md) | Contributor license agreements. | -| [Writing documentation](development/documentation/index.md) | How to contribute to GitLab Docs. | +| [Legal](legal/README.md) | Contributor license agreements. | +| [Writing documentation](development/documentation/index.md) | How to contribute to GitLab Docs. | diff --git a/doc/ci/pipeline_editor/index.md b/doc/ci/pipeline_editor/index.md index 52871e57176..4ddaa16d7c5 100644 --- a/doc/ci/pipeline_editor/index.md +++ b/doc/ci/pipeline_editor/index.md @@ -28,8 +28,8 @@ From the pipeline editor page you can: - [Commit](#commit-changes-to-ci-configuration) the changes to a specific branch. NOTE: -You must have already [created a CI/CD configuration file](../quick_start/README.md#create-a-gitlab-ciyml-file) -to use the editor. +You must already have [a `.gitlab-ci.yml` file](../quick_start/README.md#create-a-gitlab-ciyml-file) +on the default branch (usually "master") of your project to use the editor. ## Validate CI configuration diff --git a/doc/development/stage_group_dashboards.md b/doc/development/stage_group_dashboards.md index 453d71411c3..8b717638552 100644 --- a/doc/development/stage_group_dashboards.md +++ b/doc/development/stage_group_dashboards.md @@ -145,4 +145,8 @@ stageGroupDashboards.dashboard('source_code') ![Stage Group Dashboard Customization](img/stage_group_dashboards_time_customization.png) + +If you want to see the workflow in action, we've recorded a pairing session on customizing a dashboard, +available on [GitLab Unfiltered](https://youtu.be/shEd_eiUjdI). + For deeper customization and more complicated metrics, visit the [Grafonnet lib](https://github.com/grafana/grafonnet-lib) project and the [GitLab Prometheus Metrics](../administration/monitoring/prometheus/gitlab_metrics.md#gitlab-prometheus-metrics) documentation. diff --git a/doc/operations/feature_flags.md b/doc/operations/feature_flags.md index fcf178cc99e..b44df03360a 100644 --- a/doc/operations/feature_flags.md +++ b/doc/operations/feature_flags.md @@ -7,7 +7,6 @@ info: To determine the technical writer assigned to the Stage/Group associated w # Feature Flags **(FREE)** > - [Introduced](https://gitlab.com/gitlab-org/gitlab/-/merge_requests/7433) in GitLab 11.4. -> - [Moved](https://gitlab.com/gitlab-org/gitlab/-/issues/212318) to [GitLab Starter](https://about.gitlab.com/pricing/) in 13.4. > - [Moved](https://gitlab.com/gitlab-org/gitlab/-/issues/212318) to [GitLab Core](https://about.gitlab.com/pricing/) in 13.5. With Feature Flags, you can deploy your application's new features to production in smaller batches. @@ -61,14 +60,13 @@ next to any feature flag in the list. > [Introduced](https://gitlab.com/gitlab-org/gitlab/-/issues/254379) in GitLab 13.5. The maximum number of feature flags per project on self-managed GitLab instances -is 200. On GitLab.com, the maximum number is determined by [GitLab.com tier](https://about.gitlab.com/pricing/): +is 200. For GitLab SaaS, the maximum number is determined by [tier](https://about.gitlab.com/pricing/): | Tier | Number of feature flags per project | |----------|-------------------------------------| | Free | 50 | -| Bronze | 100 | -| Silver | 150 | -| Gold | 200 | +| Premium | 150 | +| Ultimate | 200 | ## Feature flag strategies diff --git a/doc/user/operations_dashboard/index.md b/doc/user/operations_dashboard/index.md index bc0b03b9493..be3454dbd02 100644 --- a/doc/user/operations_dashboard/index.md +++ b/doc/user/operations_dashboard/index.md @@ -6,19 +6,20 @@ info: To determine the technical writer assigned to the Stage/Group associated w # Operations Dashboard **(PREMIUM)** -> [Introduced](https://gitlab.com/gitlab-org/gitlab/-/issues/5781) in [GitLab Ultimate](https://about.gitlab.com/pricing/) 11.5. [Moved](https://gitlab.com/gitlab-org/gitlab/-/issues/9218) to [GitLab Premium](https://about.gitlab.com/pricing/) in 11.10. +> - [Introduced](https://gitlab.com/gitlab-org/gitlab/-/issues/5781) in [GitLab Ultimate](https://about.gitlab.com/pricing/) 11.5. +> - [Moved](https://gitlab.com/gitlab-org/gitlab/-/issues/9218) to [GitLab Premium](https://about.gitlab.com/pricing/) in 11.10. The Operations Dashboard provides a summary of each project's operational health, including pipeline and alert status. -The dashboard can be accessed via the top bar, by clicking **More > Operations**. +The dashboard can be accessed from the top bar, by clicking **More > Operations**. ## Adding a project to the dashboard NOTE: For GitLab.com, you can add your project to the Operations Dashboard for free if your project is public. If your project is private, the group it belongs to must -have a [Silver](https://about.gitlab.com/pricing/) plan. +have a [GitLab Premium](https://about.gitlab.com/pricing/) plan. To add a project to the dashboard: diff --git a/doc/user/project/settings/index.md b/doc/user/project/settings/index.md index bf78069385c..ff1d108313c 100644 --- a/doc/user/project/settings/index.md +++ b/doc/user/project/settings/index.md @@ -40,6 +40,26 @@ You can select a framework label to identify that your project has certain compl NOTE: Compliance framework labels do not affect your project settings. +#### Custom compliance frameworks + +> - [Introduced](https://gitlab.com/gitlab-org/gitlab/-/issues/276221) in GitLab 13.9. +> - It's [deployed behind a feature flag](../../feature_flags.md), disabled by default. +> - It's disabled on GitLab.com. +> - It's not recommended for production use. +> - To use it in GitLab self-managed instances, ask a GitLab administrator to [enable it](#enable-or-disable-custom-compliance-frameworks). **(PREMIUM ONLY)** + +WARNING: +This feature might not be available to you. Check the **version history** note above for details. + +GitLab 13.8 introduces custom compliance frameworks at the group-level. A group owner can create a compliance framework label +and assign it to any number of projects within that group or sub-groups. When this feature is enabled, projects can only +be assigned compliance framework labels that already exist within that group. + +If existing [Compliance frameworks](#compliance-framework) are not sufficient, you can now create +your own. + +New compliance framework labels can be created and updated using GraphQL. + ### Sharing and permissions For your repository, you can set up features such as public access, repository features, @@ -299,3 +319,22 @@ Add the URL of a Jaeger server to allow your users to [easily access the Jaeger [Add Storage credentials](../../../operations/incident_management/status_page.md#sync-incidents-to-the-status-page) to enable the syncing of public Issues to a [deployed status page](../../../operations/incident_management/status_page.md#create-a-status-page-project). + +### Enable or disable custom compliance frameworks **(PREMIUM ONLY)** + +Enabling or disabling custom compliance frameworks is under development and not ready for production use. It is +deployed behind a feature flag that is **disabled by default**. +[GitLab administrators with access to the GitLab Rails console](../../../administration/feature_flags.md) +can enable it. + +To enable it: + +```ruby +Feature.enable(:ff_custom_compliance_frameworks) +``` + +To disable it: + +```ruby +Feature.disable(:ff_custom_compliance_frameworks) +``` diff --git a/locale/gitlab.pot b/locale/gitlab.pot index 301974ff619..a73e5bef04c 100644 --- a/locale/gitlab.pot +++ b/locale/gitlab.pot @@ -1253,6 +1253,9 @@ msgstr "" msgid "A deleted user" msgstr "" +msgid "A description is required" +msgstr "" + msgid "A file has been changed." msgstr "" @@ -1346,6 +1349,9 @@ msgstr "" msgid "A string appended to the project path to form the Service Desk email address." msgstr "" +msgid "A title is required" +msgstr "" + msgid "A user can only participate in a rotation once" msgstr "" @@ -7326,6 +7332,9 @@ msgstr "" msgid "ComplianceFrameworks|There are no compliance frameworks set up yet" msgstr "" +msgid "ComplianceFrameworks|Use %{codeStart}::%{codeEnd} to create a %{linkStart}scoped set%{linkEnd} (eg. %{codeStart}SOX::AWS%{codeEnd})" +msgstr "" + msgid "ComplianceFramework|GDPR" msgstr "" @@ -19318,6 +19327,9 @@ msgstr "" msgid "No commits present here" msgstr "" +msgid "No compliance frameworks are in use. Create one using the GraphQL API." +msgstr "" + msgid "No connection could be made to a Gitaly Server, please check your logs!" msgstr "" diff --git a/spec/features/merge_request/user_views_open_merge_request_spec.rb b/spec/features/merge_request/user_views_open_merge_request_spec.rb index e8998f9457a..fdf29d32836 100644 --- a/spec/features/merge_request/user_views_open_merge_request_spec.rb +++ b/spec/features/merge_request/user_views_open_merge_request_spec.rb @@ -107,5 +107,21 @@ RSpec.describe 'User views an open merge request' do end end end + + context 'when the assignee\'s availability set' do + before do + merge_request.author.create_status(availability: 'busy') + merge_request.assignees << merge_request.author + + visit(merge_request_path(merge_request)) + end + + it 'exposes the availability in the data-availability attribute' do + assignees_data = find_all("input[name='merge_request[assignee_ids][]']", visible: false) + + expect(assignees_data.size).to eq(1) + expect(assignees_data.first['data-availability']).to eq('busy') + end + end end end diff --git a/spec/finders/autocomplete/users_finder_spec.rb b/spec/finders/autocomplete/users_finder_spec.rb index 357b6dfcea2..28bd7e12916 100644 --- a/spec/finders/autocomplete/users_finder_spec.rb +++ b/spec/finders/autocomplete/users_finder_spec.rb @@ -118,5 +118,10 @@ RSpec.describe Autocomplete::UsersFinder do it { is_expected.to match_array([user1, external_user, omniauth_user, current_user]) } end + + it 'preloads the status association' do + associations = subject.map { |user| user.association(:status) } + expect(associations).to all(be_loaded) + end end end diff --git a/spec/fixtures/api/schemas/entities/member.json b/spec/fixtures/api/schemas/entities/member.json index e8b40745803..03b1872632e 100644 --- a/spec/fixtures/api/schemas/entities/member.json +++ b/spec/fixtures/api/schemas/entities/member.json @@ -9,7 +9,8 @@ "source", "valid_roles", "can_update", - "can_remove" + "can_remove", + "is_direct_member" ], "properties": { "id": { "type": "integer" }, @@ -18,6 +19,7 @@ "requested_at": { "type": ["date-time", "null"] }, "can_update": { "type": "boolean" }, "can_remove": { "type": "boolean" }, + "is_direct_member": { "type": "boolean" }, "access_level": { "type": "object", "required": ["integer_value", "string_value"], diff --git a/spec/fixtures/api/schemas/entities/member_user.json b/spec/fixtures/api/schemas/entities/member_user.json index 983cdb7b9d9..ebd26bfaaaa 100644 --- a/spec/fixtures/api/schemas/entities/member_user.json +++ b/spec/fixtures/api/schemas/entities/member_user.json @@ -9,6 +9,7 @@ "web_url": { "type": "string" }, "blocked": { "type": "boolean" }, "two_factor_enabled": { "type": "boolean" }, + "availability": { "type": ["string", "null"] }, "status": { "type": "object", "required": ["emoji"], diff --git a/spec/frontend/members/components/table/members_table_cell_spec.js b/spec/frontend/members/components/table/members_table_cell_spec.js index 4e176dac725..30e6013aab2 100644 --- a/spec/frontend/members/components/table/members_table_cell_spec.js +++ b/spec/frontend/members/components/table/members_table_cell_spec.js @@ -1,7 +1,14 @@ import { mount, createLocalVue } from '@vue/test-utils'; import Vuex from 'vuex'; import { MEMBER_TYPES } from '~/members/constants'; -import { member as memberMock, group, invite, accessRequest } from '../../mock_data'; +import { + member as memberMock, + directMember, + inheritedMember, + group, + invite, + accessRequest, +} from '../../mock_data'; import MembersTableCell from '~/members/components/table/members_table_cell.vue'; describe('MembersTableCell', () => { @@ -75,19 +82,12 @@ describe('MembersTableCell', () => { const createComponentWithDirectMember = (member = {}) => { createComponent({ - member: { - ...memberMock, - source: { - ...memberMock.source, - id: 1, - }, - ...member, - }, + member: { ...directMember, ...member }, }); }; const createComponentWithInheritedMember = (member = {}) => { createComponent({ - member: { ...memberMock, ...member }, + member: { ...inheritedMember, ...member }, }); }; diff --git a/spec/frontend/members/components/table/members_table_spec.js b/spec/frontend/members/components/table/members_table_spec.js index 476d7118c46..729b65f37aa 100644 --- a/spec/frontend/members/components/table/members_table_spec.js +++ b/spec/frontend/members/components/table/members_table_spec.js @@ -15,7 +15,7 @@ import RoleDropdown from '~/members/components/table/role_dropdown.vue'; import ExpirationDatepicker from '~/members/components/table/expiration_datepicker.vue'; import MemberActionButtons from '~/members/components/table/member_action_buttons.vue'; import * as initUserPopovers from '~/user_popovers'; -import { member as memberMock, invite, accessRequest } from '../../mock_data'; +import { member as memberMock, directMember, invite, accessRequest } from '../../mock_data'; const localVue = createLocalVue(); localVue.use(Vuex); @@ -74,11 +74,6 @@ describe('MembersTable', () => { }); describe('fields', () => { - const directMember = { - ...memberMock, - source: { ...memberMock.source, id: 1 }, - }; - const memberCanUpdate = { ...directMember, canUpdate: true, diff --git a/spec/frontend/members/mock_data.js b/spec/frontend/members/mock_data.js index 5c00745a590..fa324ce1cf9 100644 --- a/spec/frontend/members/mock_data.js +++ b/spec/frontend/members/mock_data.js @@ -4,6 +4,7 @@ export const member = { canRemove: false, canOverride: false, isOverridden: false, + isDirectMember: false, accessLevel: { integerValue: 50, stringValue: 'Owner' }, source: { id: 178, @@ -71,3 +72,6 @@ export const accessRequest = { export const members = [member]; export const membersJsonString = JSON.stringify(members); + +export const directMember = { ...member, isDirectMember: true }; +export const inheritedMember = { ...member, isDirectMember: false }; diff --git a/spec/frontend/members/utils_spec.js b/spec/frontend/members/utils_spec.js index f123b2c6a91..5c9484fe128 100644 --- a/spec/frontend/members/utils_spec.js +++ b/spec/frontend/members/utils_spec.js @@ -13,10 +13,16 @@ import { groupLinkRequestFormatter, } from '~/members/utils'; import { DEFAULT_SORT } from '~/members/constants'; -import { member as memberMock, group, invite, membersJsonString, members } from './mock_data'; +import { + member as memberMock, + directMember, + inheritedMember, + group, + invite, + membersJsonString, + members, +} from './mock_data'; -const DIRECT_MEMBER_ID = 178; -const INHERITED_MEMBER_ID = 179; const IS_CURRENT_USER_ID = 123; const IS_NOT_CURRENT_USER_ID = 124; const URL_HOST = 'https://localhost/'; @@ -59,11 +65,11 @@ describe('Members Utils', () => { describe('isDirectMember', () => { test.each` - sourceId | expected - ${DIRECT_MEMBER_ID} | ${true} - ${INHERITED_MEMBER_ID} | ${false} - `('returns $expected', ({ sourceId, expected }) => { - expect(isDirectMember(memberMock, sourceId)).toBe(expected); + member | expected + ${directMember} | ${true} + ${inheritedMember} | ${false} + `('returns $expected', ({ member, expected }) => { + expect(isDirectMember(member)).toBe(expected); }); }); @@ -78,18 +84,13 @@ describe('Members Utils', () => { }); describe('canRemove', () => { - const memberCanRemove = { - ...memberMock, - canRemove: true, - }; - test.each` - member | sourceId | expected - ${memberCanRemove} | ${DIRECT_MEMBER_ID} | ${true} - ${memberCanRemove} | ${INHERITED_MEMBER_ID} | ${false} - ${memberMock} | ${INHERITED_MEMBER_ID} | ${false} - `('returns $expected', ({ member, sourceId, expected }) => { - expect(canRemove(member, sourceId)).toBe(expected); + member | expected + ${{ ...directMember, canRemove: true }} | ${true} + ${{ ...inheritedMember, canRemove: true }} | ${false} + ${{ ...memberMock, canRemove: false }} | ${false} + `('returns $expected', ({ member, expected }) => { + expect(canRemove(member)).toBe(expected); }); }); @@ -98,25 +99,20 @@ describe('Members Utils', () => { member | expected ${invite} | ${true} ${{ ...invite, invite: { ...invite.invite, canResend: false } }} | ${false} - `('returns $expected', ({ member, sourceId, expected }) => { - expect(canResend(member, sourceId)).toBe(expected); + `('returns $expected', ({ member, expected }) => { + expect(canResend(member)).toBe(expected); }); }); describe('canUpdate', () => { - const memberCanUpdate = { - ...memberMock, - canUpdate: true, - }; - test.each` - member | currentUserId | sourceId | expected - ${memberCanUpdate} | ${IS_NOT_CURRENT_USER_ID} | ${DIRECT_MEMBER_ID} | ${true} - ${memberCanUpdate} | ${IS_CURRENT_USER_ID} | ${DIRECT_MEMBER_ID} | ${false} - ${memberCanUpdate} | ${IS_CURRENT_USER_ID} | ${INHERITED_MEMBER_ID} | ${false} - ${memberMock} | ${IS_NOT_CURRENT_USER_ID} | ${DIRECT_MEMBER_ID} | ${false} - `('returns $expected', ({ member, currentUserId, sourceId, expected }) => { - expect(canUpdate(member, currentUserId, sourceId)).toBe(expected); + member | currentUserId | expected + ${{ ...directMember, canUpdate: true }} | ${IS_NOT_CURRENT_USER_ID} | ${true} + ${{ ...directMember, canUpdate: true }} | ${IS_CURRENT_USER_ID} | ${false} + ${{ ...inheritedMember, canUpdate: true }} | ${IS_CURRENT_USER_ID} | ${false} + ${{ ...directMember, canUpdate: false }} | ${IS_NOT_CURRENT_USER_ID} | ${false} + `('returns $expected', ({ member, currentUserId, expected }) => { + expect(canUpdate(member, currentUserId)).toBe(expected); }); }); diff --git a/spec/models/terraform/state_spec.rb b/spec/models/terraform/state_spec.rb index 4eb03554378..1319e2adb03 100644 --- a/spec/models/terraform/state_spec.rb +++ b/spec/models/terraform/state_spec.rb @@ -8,8 +8,11 @@ RSpec.describe Terraform::State do it { is_expected.to belong_to(:project) } it { is_expected.to belong_to(:locked_by_user).class_name('User') } + it { is_expected.to validate_presence_of(:name) } it { is_expected.to validate_presence_of(:project_id) } + it { is_expected.to validate_uniqueness_of(:name).scoped_to(:project_id) } + describe 'scopes' do describe '.ordered_by_name' do let_it_be(:project) { create(:project) } diff --git a/spec/serializers/member_entity_spec.rb b/spec/serializers/member_entity_spec.rb index f34434188c1..883cb511abc 100644 --- a/spec/serializers/member_entity_spec.rb +++ b/spec/serializers/member_entity_spec.rb @@ -4,7 +4,7 @@ require 'spec_helper' RSpec.describe MemberEntity do let_it_be(:current_user) { create(:user) } - let(:entity) { described_class.new(member, { current_user: current_user, group: group }) } + let(:entity) { described_class.new(member, { current_user: current_user, group: group, source: source }) } let(:entity_hash) { entity.as_json } shared_examples 'member.json' do @@ -40,8 +40,27 @@ RSpec.describe MemberEntity do end end + shared_examples 'is_direct_member' do + context 'when `source` is the same as `member.source`' do + let(:source) { direct_member_source } + + it 'exposes `is_direct_member` as `true`' do + expect(entity_hash[:is_direct_member]).to be(true) + end + end + + context 'when `source` is not the same as `member.source`' do + let(:source) { inherited_member_source } + + it 'exposes `is_direct_member` as `false`' do + expect(entity_hash[:is_direct_member]).to be(false) + end + end + end + context 'group member' do let(:group) { create(:group) } + let(:source) { group } let(:member) { GroupMemberPresenter.new(create(:group_member, group: group), current_user: current_user) } it_behaves_like 'member.json' @@ -52,11 +71,19 @@ RSpec.describe MemberEntity do it_behaves_like 'member.json' it_behaves_like 'invite' end + + context 'is_direct_member' do + let(:direct_member_source) { group } + let(:inherited_member_source) { create(:group) } + + it_behaves_like 'is_direct_member' + end end context 'project member' do let(:project) { create(:project) } let(:group) { project.group } + let(:source) { project } let(:member) { ProjectMemberPresenter.new(create(:project_member, project: project), current_user: current_user) } it_behaves_like 'member.json' @@ -67,5 +94,12 @@ RSpec.describe MemberEntity do it_behaves_like 'member.json' it_behaves_like 'invite' end + + context 'is_direct_member' do + let(:direct_member_source) { project } + let(:inherited_member_source) { group } + + it_behaves_like 'is_direct_member' + end end end diff --git a/spec/serializers/member_serializer_spec.rb b/spec/serializers/member_serializer_spec.rb index d3ec45fe9c4..af209c0191f 100644 --- a/spec/serializers/member_serializer_spec.rb +++ b/spec/serializers/member_serializer_spec.rb @@ -7,7 +7,7 @@ RSpec.describe MemberSerializer do let_it_be(:current_user) { create(:user) } - subject { described_class.new.represent(members, { current_user: current_user, group: group }) } + subject { described_class.new.represent(members, { current_user: current_user, group: group, source: source }) } shared_examples 'members.json' do it 'matches json schema' do @@ -17,6 +17,7 @@ RSpec.describe MemberSerializer do context 'group member' do let(:group) { create(:group) } + let(:source) { group } let(:members) { present_members(create_list(:group_member, 1, group: group)) } it_behaves_like 'members.json' @@ -24,6 +25,7 @@ RSpec.describe MemberSerializer do context 'project member' do let(:project) { create(:project) } + let(:source) { project } let(:group) { project.group } let(:members) { present_members(create_list(:project_member, 1, project: project)) } diff --git a/spec/serializers/merge_request_user_entity_spec.rb b/spec/serializers/merge_request_user_entity_spec.rb index a2ad8e72845..dcd4ef6acfb 100644 --- a/spec/serializers/merge_request_user_entity_spec.rb +++ b/spec/serializers/merge_request_user_entity_spec.rb @@ -17,5 +17,23 @@ RSpec.describe MergeRequestUserEntity do it 'exposes needed attributes' do expect(subject).to include(:id, :name, :username, :state, :avatar_url, :web_url, :can_merge) end + + context 'when `status` is not preloaded' do + it 'does not expose the availability attribute' do + expect(subject).not_to include(:availability) + end + end + + context 'when `status` is preloaded' do + before do + user.create_status!(availability: :busy) + + user.status # make sure `status` is loaded + end + + it 'exposes the availibility attribute' do + expect(subject[:availability]).to eq('busy') + end + end end end