Add latest changes from gitlab-org/gitlab@master
This commit is contained in:
parent
3c53fbc50b
commit
6c5f3e7b4e
|
|
@ -76,14 +76,13 @@ export default {
|
|||
<template v-for="(action, index) in actions">
|
||||
<gl-dropdown-item
|
||||
:key="action.key"
|
||||
class="gl-dropdown-item-deprecated-adapter"
|
||||
:is-check-item="true"
|
||||
:is-checked="action.key === selectedAction.key"
|
||||
:secondary-text="action.secondaryText"
|
||||
:data-testid="`action_${action.key}`"
|
||||
@click="handleItemClick(action)"
|
||||
>
|
||||
{{ action.text }}
|
||||
<span class="gl-font-weight-bold">{{ action.text }}</span>
|
||||
</gl-dropdown-item>
|
||||
<gl-dropdown-divider v-if="index != actions.length - 1" :key="action.key + '_divider'" />
|
||||
</template>
|
||||
|
|
|
|||
|
|
@ -1093,17 +1093,3 @@ header.header-content .dropdown-menu.frequent-items-dropdown-menu {
|
|||
width: $gl-dropdown-width-wide;
|
||||
}
|
||||
}
|
||||
|
||||
.gl-dropdown-item-deprecated-adapter {
|
||||
.dropdown-item {
|
||||
align-items: flex-start;
|
||||
|
||||
.gl-new-dropdown-item-text-primary {
|
||||
@include gl-font-weight-bold;
|
||||
}
|
||||
|
||||
.gl-new-dropdown-item-text-secondary {
|
||||
color: inherit;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -516,28 +516,8 @@
|
|||
left: auto;
|
||||
max-height: $dropdown-max-height-lg;
|
||||
|
||||
li.current-user {
|
||||
padding: $dropdown-item-padding-y $dropdown-item-padding-x;
|
||||
|
||||
.user-name {
|
||||
display: block;
|
||||
}
|
||||
|
||||
.user-status {
|
||||
margin-right: 0;
|
||||
max-width: 240px;
|
||||
font-size: $gl-font-size-small;
|
||||
|
||||
gl-emoji {
|
||||
font-size: $gl-font-size-small;
|
||||
}
|
||||
|
||||
.user-status-emoji {
|
||||
gl-emoji {
|
||||
font-size: $gl-font-size;
|
||||
}
|
||||
}
|
||||
}
|
||||
.user-status {
|
||||
max-width: 240px;
|
||||
}
|
||||
|
||||
svg {
|
||||
|
|
|
|||
|
|
@ -74,14 +74,10 @@
|
|||
justify-content: flex-end;
|
||||
}
|
||||
|
||||
.encoding-selector,
|
||||
.soft-wrap-toggle {
|
||||
display: inline-block;
|
||||
vertical-align: top;
|
||||
font-family: $regular-font;
|
||||
}
|
||||
|
||||
.soft-wrap-toggle {
|
||||
margin: 0 $btn-side-margin;
|
||||
|
||||
.soft-wrap {
|
||||
|
|
@ -128,15 +124,6 @@
|
|||
margin: 3px 0;
|
||||
}
|
||||
|
||||
.encoding-selector {
|
||||
display: block;
|
||||
margin: 3px 0;
|
||||
|
||||
button {
|
||||
width: 100%;
|
||||
}
|
||||
}
|
||||
|
||||
@media(max-width: map-get($grid-breakpoints, md)-1) {
|
||||
clear: both;
|
||||
}
|
||||
|
|
|
|||
|
|
@ -159,8 +159,8 @@ class JiraService < IssueTrackerService
|
|||
# support any events.
|
||||
end
|
||||
|
||||
def find_issue(issue_key)
|
||||
jira_request { client.Issue.find(issue_key) }
|
||||
def find_issue(issue_key, options = {})
|
||||
jira_request { client.Issue.find(issue_key, options) }
|
||||
end
|
||||
|
||||
def close_issue(entity, external_issue, current_user)
|
||||
|
|
|
|||
|
|
@ -2,18 +2,12 @@
|
|||
|
||||
%ul
|
||||
%li.current-user
|
||||
.user-name.gl-font-weight-bold
|
||||
= current_user.name
|
||||
- if current_user&.status && user_status_set_to_busy?(current_user.status)
|
||||
%span.gl-font-weight-normal.gl-text-gray-500= s_("UserProfile|(Busy)")
|
||||
= current_user.to_reference
|
||||
- if current_user.status
|
||||
.user-status.d-flex.align-items-center.gl-mt-2.has-tooltip{ title: current_user.status.message_html, data: { html: 'true', placement: 'bottom' } }
|
||||
- if show_status_emoji?(current_user.status)
|
||||
.user-status-emoji.d-flex.align-items-center
|
||||
= emoji_icon current_user.status.emoji
|
||||
%span.user-status-message.str-truncated
|
||||
= current_user.status.message_html.html_safe
|
||||
- if current_user_menu?(:profile)
|
||||
= link_to current_user, class: 'gl-line-height-20!', data: { user: current_user.username, testid: 'user-profile-link' } do
|
||||
= render 'layouts/header/current_user_dropdown_item'
|
||||
- else
|
||||
.gl-py-3.gl-px-4
|
||||
= render 'layouts/header/current_user_dropdown_item'
|
||||
%li.divider
|
||||
- if can?(current_user, :update_user_status, current_user)
|
||||
%li
|
||||
|
|
@ -22,9 +16,6 @@
|
|||
= s_('SetStatusModal|Edit status')
|
||||
- else
|
||||
= s_('SetStatusModal|Set status')
|
||||
- if current_user_menu?(:profile)
|
||||
%li
|
||||
= link_to s_("CurrentUser|Profile"), current_user, class: 'profile-link', data: { user: current_user.username }
|
||||
- if current_user_menu?(:start_trial)
|
||||
%li
|
||||
%a.trial-link{ href: trials_link_url }
|
||||
|
|
@ -32,7 +23,9 @@
|
|||
= emoji_icon('rocket')
|
||||
- if current_user_menu?(:settings)
|
||||
%li
|
||||
= link_to s_("CurrentUser|Settings"), profile_path, data: { qa_selector: 'settings_link' }
|
||||
= link_to s_("CurrentUser|Edit profile"), profile_path, data: { qa_selector: 'edit_profile_link' }
|
||||
%li
|
||||
= link_to s_("CurrentUser|Preferences"), profile_preferences_path
|
||||
= render_if_exists 'layouts/header/buy_pipeline_minutes', project: @project, namespace: @group
|
||||
= render_if_exists 'layouts/header/upgrade'
|
||||
|
||||
|
|
|
|||
|
|
@ -0,0 +1,12 @@
|
|||
.gl-font-weight-bold
|
||||
= current_user.name
|
||||
- if current_user&.status && user_status_set_to_busy?(current_user.status)
|
||||
%span.gl-font-weight-normal.gl-text-gray-500= s_("UserProfile|(Busy)")
|
||||
= current_user.to_reference
|
||||
- if current_user.status
|
||||
.user-status.d-flex.align-items-center.gl-mt-2.gl-mr-0.gl-font-sm.has-tooltip{ title: current_user.status.message_html, data: { html: 'true', placement: 'bottom' } }
|
||||
- if show_status_emoji?(current_user.status)
|
||||
.user-status-emoji.d-flex.align-items-center
|
||||
= emoji_icon current_user.status.emoji
|
||||
%span.user-status-message.str-truncated
|
||||
= current_user.status.message_html.html_safe
|
||||
|
|
@ -36,8 +36,6 @@
|
|||
%span.soft-wrap
|
||||
= custom_icon('icon_soft_wrap')
|
||||
Soft wrap
|
||||
.encoding-selector
|
||||
= select_tag :encoding, options_for_select([ "base64", "text" ], "text"), class: 'select2', tabindex: '-1'
|
||||
|
||||
.file-editor.code
|
||||
.js-edit-mode-pane.qa-editor#editor{ data: { 'editor-loading': true } }<
|
||||
|
|
|
|||
|
|
@ -1,7 +1,7 @@
|
|||
- breadcrumb_title _("Graph")
|
||||
- page_title _("Graph"), @ref
|
||||
= render "head"
|
||||
%div{ class: container_class }
|
||||
.gl-mt-5
|
||||
.project-network.gl-border-1.gl-border-solid.gl-border-gray-300
|
||||
.controls.gl-bg-gray-50.gl-p-2.gl-font-base.gl-text-gray-400.gl-border-b-1.gl-border-b-solid.gl-border-b-gray-300
|
||||
= form_tag project_network_path(@project, @id), method: :get, class: 'form-inline network-form' do |f|
|
||||
|
|
|
|||
|
|
@ -0,0 +1,5 @@
|
|||
---
|
||||
title: Improve user dropdown items
|
||||
merge_request: 53175
|
||||
author:
|
||||
type: changed
|
||||
|
|
@ -0,0 +1,5 @@
|
|||
---
|
||||
title: Removed unused Text dropdown
|
||||
merge_request: 53464
|
||||
author:
|
||||
type: removed
|
||||
|
|
@ -0,0 +1,5 @@
|
|||
---
|
||||
title: Add margin and remove padding in project graph page
|
||||
merge_request: 53557
|
||||
author: Yogi (@yo)
|
||||
type: other
|
||||
|
|
@ -0,0 +1,5 @@
|
|||
---
|
||||
title: Change secondary text color on Gitpod editor dropdown button
|
||||
merge_request: 53437
|
||||
author:
|
||||
type: other
|
||||
|
|
@ -143,6 +143,7 @@ exceptions:
|
|||
- SVG
|
||||
- SVN
|
||||
- TCP
|
||||
- TIFF
|
||||
- TIP
|
||||
- TLD
|
||||
- TLS
|
||||
|
|
|
|||
|
|
@ -80,7 +80,7 @@ Learn more about GitLab account management:
|
|||
|:-----------------------------------------------------------|:------------|
|
||||
| [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 settings](user/profile/index.md#user-settings) | Manage your user settings, two factor authentication, and more. |
|
||||
| [User permissions](user/permissions.md) | Learn what each role in a project can do. |
|
||||
|
||||
### Git and GitLab
|
||||
|
|
|
|||
|
|
@ -1,8 +0,0 @@
|
|||
---
|
||||
redirect_to: '../index.md'
|
||||
---
|
||||
|
||||
This document was moved to [another location](../index.md).
|
||||
|
||||
<!-- This redirect file can be deleted after February 1, 2021. -->
|
||||
<!-- Before deletion, see: https://docs.gitlab.com/ee/development/documentation/#move-or-rename-a-page -->
|
||||
|
|
@ -339,7 +339,7 @@ an expiry for the artifacts, they are marked for deletion right after that date
|
|||
Otherwise, they expire per the [default artifacts expiration setting](../user/admin_area/settings/continuous_integration.md).
|
||||
|
||||
Artifacts are cleaned up by the `expire_build_artifacts_worker` cron job which Sidekiq
|
||||
runs every hour at 50 minutes (`50 * * * *`).
|
||||
runs every 7 minutes (`*/7 * * * *`).
|
||||
|
||||
To change the default schedule on which the artifacts are expired, follow the
|
||||
steps below.
|
||||
|
|
@ -350,7 +350,7 @@ steps below.
|
|||
your schedule in cron syntax:
|
||||
|
||||
```ruby
|
||||
gitlab_rails['expire_build_artifacts_worker_cron'] = "50 * * * *"
|
||||
gitlab_rails['expire_build_artifacts_worker_cron'] = "*/7 * * * *"
|
||||
```
|
||||
|
||||
1. Save the file and [reconfigure GitLab](restart_gitlab.md#omnibus-gitlab-reconfigure) for the changes to take effect.
|
||||
|
|
@ -362,7 +362,7 @@ steps below.
|
|||
|
||||
```yaml
|
||||
expire_build_artifacts_worker:
|
||||
cron: "50 * * * *"
|
||||
cron: "*/7 * * * *"
|
||||
```
|
||||
|
||||
1. Save the file and [restart GitLab](restart_gitlab.md#installations-from-source) for the changes to take effect.
|
||||
|
|
|
|||
|
|
@ -235,7 +235,7 @@ separate Rails process to debug the issue:
|
|||
|
||||
1. Log in to your GitLab account.
|
||||
1. Copy the URL that is causing problems (e.g. `https://gitlab.com/ABC`).
|
||||
1. Create a Personal Access Token for your user (Profile Settings -> Access Tokens).
|
||||
1. Create a Personal Access Token for your user (User Settings -> Access Tokens).
|
||||
1. Bring up the [GitLab Rails console.](../operations/rails_console.md#starting-a-rails-console-session)
|
||||
1. At the Rails console, run:
|
||||
|
||||
|
|
|
|||
|
|
@ -10,7 +10,7 @@ info: To determine the technical writer assigned to the Stage/Group associated w
|
|||
|
||||
## Get a single avatar URL
|
||||
|
||||
Get a single [avatar](../user/profile/index.md#profile-settings) URL for a user with the given email address.
|
||||
Get a single [avatar](../user/profile/index.md#user-settings) URL for a user with the given email address.
|
||||
|
||||
If:
|
||||
|
||||
|
|
|
|||
|
|
@ -13,6 +13,7 @@ This document outlines the style guide for the GitLab [GraphQL API](../api/graph
|
|||
<!-- vale gitlab.Spelling = NO -->
|
||||
We use the [GraphQL Ruby gem](https://graphql-ruby.org/) written by [Robert Mosolgo](https://github.com/rmosolgo/).
|
||||
<!-- vale gitlab.Spelling = YES -->
|
||||
In addition, we have a subscription to [GraphQL Pro](https://www.graphql.pro). For details see [GraphQL Pro subscription](graphql_guide/graphql_pro.md).
|
||||
|
||||
All GraphQL queries are directed to a single endpoint
|
||||
([`app/controllers/graphql_controller.rb#execute`](https://gitlab.com/gitlab-org/gitlab/blob/master/app%2Fcontrollers%2Fgraphql_controller.rb)),
|
||||
|
|
|
|||
|
|
@ -0,0 +1,21 @@
|
|||
---
|
||||
stage: Plan
|
||||
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
|
||||
---
|
||||
|
||||
# GraphQL Pro
|
||||
|
||||
GraphQL has become a key technology in GitLab and is implemented using the
|
||||
[GraphQL Ruby gem](https://graphql-ruby.org). As such, we've purchased a subscription to
|
||||
[GraphQL Pro](https://graphql.pro).
|
||||
|
||||
The main purpose is for support. Per the website:
|
||||
|
||||
> As a GraphQL::Pro customer, you get direct access to the GraphQL Ruby gem
|
||||
> creator and maintainer. Get prioritized support for issues and requests.
|
||||
|
||||
Note that we **cannot** use the Pro version directly in our product, since we are
|
||||
an Open Core product - we can not require customers to purchase the Pro version, nor can we ship it.
|
||||
|
||||
Details on the billing account and gem licensing can be found in the Engineering 1Password vault.
|
||||
|
|
@ -1,6 +1,6 @@
|
|||
---
|
||||
stage: none
|
||||
group: unassigned
|
||||
stage: Plan
|
||||
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
|
||||
---
|
||||
|
||||
|
|
@ -17,3 +17,6 @@ feedback, and suggestions.
|
|||
- [GraphQL API documentation style guide](../documentation/graphql_styleguide.md): documentation
|
||||
style guide for GraphQL.
|
||||
- [GraphQL API](../../api/graphql/index.md): user documentation for the GitLab GraphQL API.
|
||||
- [GraphQL BatchLoader](batchloader.md): development documentation on the batchloader.
|
||||
- [GraphQL pagination](pagination.md): development documentation on pagination.
|
||||
- [GraphQL Pro](graphql_pro.md): information on our GraphQL Pro subscription.
|
||||
|
|
|
|||
|
|
@ -1,6 +1,6 @@
|
|||
---
|
||||
stage: none
|
||||
group: unassigned
|
||||
stage: Plan
|
||||
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
|
||||
---
|
||||
|
||||
|
|
|
|||
|
|
@ -179,7 +179,9 @@ If you're getting the message `Signing in using your GitHub account without a pr
|
|||
GitLab account is not allowed. Create a GitLab account first, and then connect it to your
|
||||
GitHub account` when signing in, in GitLab:
|
||||
|
||||
1. Go to your **Profile > Account**.
|
||||
1. Under the "Social sign-in" section, click **Connect** near the GitHub icon.
|
||||
1. In the top-right corner, select your avatar.
|
||||
1. Select **Edit profile**.
|
||||
1. In the left sidebar, select **Account**.
|
||||
1. In the **Social sign-in** section, select **Connect to GitHub**.
|
||||
|
||||
After that, you should be able to sign in via GitHub successfully.
|
||||
|
|
|
|||
|
|
@ -12,11 +12,9 @@ To enable the GitLab.com OmniAuth provider you must register your application wi
|
|||
GitLab.com generates an application ID and secret key for you to use.
|
||||
|
||||
1. Sign in to GitLab.com.
|
||||
|
||||
1. On the upper right corner, click on your avatar and go to your **Settings**.
|
||||
|
||||
1. Select **Applications** in the left menu.
|
||||
|
||||
1. In the top-right corner, select your avatar.
|
||||
1. Select **Edit profile**.
|
||||
1. In the left sidebar, select **Applications**.
|
||||
1. Provide the required details for **Add new application**.
|
||||
- Name: This can be anything. Consider something like `<Organization>'s GitLab` or `<Your Name>'s GitLab` or something else descriptive.
|
||||
- Redirect URI:
|
||||
|
|
@ -29,10 +27,8 @@ GitLab.com generates an application ID and secret key for you to use.
|
|||
The first link is required for the importer and second for the authorization.
|
||||
|
||||
1. Select **Save application**.
|
||||
|
||||
1. You should now see an **Application ID** and **Secret**. Keep this page open as you continue
|
||||
configuration.
|
||||
|
||||
1. On your GitLab server, open the configuration file.
|
||||
|
||||
For Omnibus package:
|
||||
|
|
@ -50,10 +46,9 @@ GitLab.com generates an application ID and secret key for you to use.
|
|||
```
|
||||
|
||||
1. See [Initial OmniAuth Configuration](omniauth.md#initial-omniauth-configuration) for initial settings.
|
||||
|
||||
1. Add the provider configuration:
|
||||
|
||||
For Omnibus package:
|
||||
For Omnibus installations:
|
||||
|
||||
```ruby
|
||||
gitlab_rails['omniauth_providers'] = [
|
||||
|
|
@ -76,16 +71,13 @@ GitLab.com generates an application ID and secret key for you to use.
|
|||
```
|
||||
|
||||
1. Change `'YOUR_APP_ID'` to the Application ID from the GitLab.com application page.
|
||||
|
||||
1. Change `'YOUR_APP_SECRET'` to the secret from the GitLab.com application page.
|
||||
|
||||
1. Save the configuration file.
|
||||
|
||||
1. Based on how GitLab was installed, implement these changes by using
|
||||
the appropriate method:
|
||||
|
||||
- Omnibus GitLab: [Reconfigure GitLab](../administration/restart_gitlab.md#omnibus-gitlab-reconfigure).
|
||||
- Source: [Restart GitLab](../administration/restart_gitlab.md#installations-from-source).
|
||||
- Omnibus GitLab: [reconfigure GitLab](../administration/restart_gitlab.md#omnibus-gitlab-reconfigure).
|
||||
- Source: [restart GitLab](../administration/restart_gitlab.md#installations-from-source).
|
||||
|
||||
On the sign-in page, there should now be a GitLab.com icon following the
|
||||
regular sign-in form. Select the icon to begin the authentication process.
|
||||
|
|
|
|||
|
|
@ -33,7 +33,9 @@ To learn more about Gitpod, see their [features](https://www.gitpod.io/features/
|
|||
|
||||
With the Gitpod integration enabled for your GitLab instance, to enable it for yourself:
|
||||
|
||||
1. Select your avatar in the top-right corner, then select **Settings > Preferences**.
|
||||
1. In the top-right corner, select your avatar.
|
||||
1. Select **Edit profile**.
|
||||
1. In the left sidebar, select **Account**.
|
||||
1. Under **Integrations**, locate the **Gitpod** section.
|
||||
1. Check the **Enable Gitpod integration** checkbox and select the **Save changes** button.
|
||||
|
||||
|
|
|
|||
|
|
@ -70,9 +70,10 @@ Grant a GitLab user access to the select GitLab projects.
|
|||
|
||||
Create a personal access token to authorize Jenkins' access to GitLab.
|
||||
|
||||
1. Log in to GitLab as the user to be used with Jenkins.
|
||||
1. Click your avatar, then **Settings**.
|
||||
1. Click **Access Tokens** in the sidebar.
|
||||
1. Sign in to GitLab as the user to be used with Jenkins.
|
||||
1. In the top-right corner, select your avatar.
|
||||
1. Select **Edit profile**.
|
||||
1. In the left sidebar, select **Access Tokens**.
|
||||
1. Create a personal access token with the **API** scope checkbox checked. For more details, see
|
||||
[Personal access tokens](../user/profile/personal_access_tokens.md).
|
||||
1. Record the personal access token's value, because it's required in [Configure the Jenkins server](#configure-the-jenkins-server) section.
|
||||
|
|
|
|||
|
|
@ -77,10 +77,10 @@ To ensure that regular user account maintenance doesn't impact your integration,
|
|||
create and use a single-purpose `jira` user in GitLab.
|
||||
|
||||
1. In GitLab, create a new application to allow Jira to connect with your GitLab account.
|
||||
|
||||
1. Sign in to the GitLab account that you want Jira to use to connect to GitLab.
|
||||
1. In the top right corner, click your profile avatar.
|
||||
1. Click **Settings > Applications** to display the form to create a new application.
|
||||
1. In the top-right corner, select your avatar.
|
||||
1. Select **Edit profile**.
|
||||
1. In the left sidebar, select **Applications**.
|
||||
1. In the **Name** field, enter a descriptive name for the integration, such as `Jira`.
|
||||
1. In the **Redirect URI** field, enter `https://<gitlab.example.com>/login/oauth/callback`,
|
||||
replacing `<gitlab.example.com>` with your GitLab instance domain. For example, if you are using GitLab.com,
|
||||
|
|
@ -94,8 +94,7 @@ create and use a single-purpose `jira` user in GitLab.
|
|||
|
||||

|
||||
|
||||
1. Check **API** in the Scopes section, and uncheck any other checkboxes.
|
||||
|
||||
1. Check **API** in the **Scopes** section, and clear any other checkboxes.
|
||||
1. Click **Save application**. GitLab displays the generated **Application ID**
|
||||
and **Secret** values. Copy these values, which you use in Jira.
|
||||
|
||||
|
|
|
|||
|
|
@ -115,9 +115,10 @@ existing GitLab account. To do so:
|
|||
|
||||
If you're not an administrator:
|
||||
|
||||
1. Select your avatar in the upper-right corner, and select **Settings**.
|
||||
1. Select Account. In the **Social sign-in** section, select
|
||||
**Connect Kerberos SPNEGO**.
|
||||
1. In the top-right corner, select your avatar.
|
||||
1. Select **Edit profile**.
|
||||
1. In the left sidebar, select **Account**.
|
||||
1. In the **Social sign-in** section, select **Connect Kerberos SPNEGO**.
|
||||
If you don't see a **Social sign-in** Kerberos option, follow the
|
||||
requirements in [Enable single sign-on](#enable-single-sign-on).
|
||||
|
||||
|
|
|
|||
|
|
@ -43,16 +43,17 @@ levels. The default callback URL is `http://your-gitlab.example.com/users/auth/g
|
|||
|
||||
## Add an application through the profile
|
||||
|
||||
To add a new application via your profile, select your avatar in the top right, and then select
|
||||
**Settings > Applications**. You can then enter a **Name**, **Redirect URI** and
|
||||
OAuth 2 scopes as defined in [Authorized Applications](#authorized-applications).
|
||||
To add a new application via your profile:
|
||||
|
||||
- The **Redirect URI** is the URL where users are sent after they authorize with GitLab.
|
||||
1. In the top-right corner, select your avatar.
|
||||
1. Select **Edit profile**.
|
||||
1. In the left sidebar, select **Applications**.
|
||||
1. Enter a **Name**, **Redirect URI** and OAuth 2 scopes as defined in [Authorized Applications](#authorized-applications).
|
||||
The **Redirect URI** is the URL where users are sent after they authorize with GitLab.
|
||||
1. Select **Save application**. GitLab displays:
|
||||
|
||||
When you select **Save application**, GitLab displays:
|
||||
|
||||
- Application ID: OAuth 2 Client ID.
|
||||
- Secret: OAuth 2 Client Secret.
|
||||
- Application ID: OAuth 2 Client ID.
|
||||
- Secret: OAuth 2 Client Secret.
|
||||
|
||||
## OAuth applications in the Admin Area
|
||||
|
||||
|
|
|
|||
|
|
@ -138,9 +138,10 @@ provider such as Twitter can be enabled. Follow the steps below to enable an
|
|||
OmniAuth provider for an existing user.
|
||||
|
||||
1. Sign in normally - whether standard sign in, LDAP, or another OmniAuth provider.
|
||||
1. Go to profile settings (the silhouette icon in the top right corner).
|
||||
1. Select the "Account" tab.
|
||||
1. Under "Connected Accounts" select the desired OmniAuth provider, such as Twitter.
|
||||
1. In the top-right corner, select your avatar.
|
||||
1. Select **Edit profile**.
|
||||
1. In the left sidebar, select **Account**.
|
||||
1. In the **Connected Accounts** section, select the desired OmniAuth provider, such as Twitter.
|
||||
1. The user is redirected to the provider. After the user authorizes GitLab,
|
||||
they are redirected back to GitLab.
|
||||
|
||||
|
|
|
|||
|
|
@ -87,9 +87,12 @@ You can skip this step if you already have your GitLab repositories searchable i
|
|||
|
||||
If a GitLab administrator has enabled Sourcegraph, you can enable this feature in your user preferences.
|
||||
|
||||
1. In GitLab, click your avatar in the top-right corner, then click **Settings**. On the left-hand nav, click **Preferences**.
|
||||
1. Under **Integrations**, find the **Sourcegraph** section.
|
||||
1. Check **Enable Sourcegraph**.
|
||||
In GitLab:
|
||||
|
||||
1. In the top-right corner, select your avatar.
|
||||
1. Select **Preferences**.
|
||||
1. In the **Integrations** section, select the checkbox under **Sourcegraph**.
|
||||
1. Select **Save changes**.
|
||||
|
||||

|
||||
|
||||
|
|
|
|||
|
|
@ -38,8 +38,11 @@ If your instance's URL is `https://example.com`, your API URL is `https://exampl
|
|||
Your GitLab personal access token enables your GitLab account to be accessed
|
||||
from Trello.
|
||||
|
||||
> Find it in GitLab by clicking on your avatar (upright corner), from which you access
|
||||
your user **Settings** > **Access Tokens**.
|
||||
To find it in GitLab:
|
||||
|
||||
1. In the top-right corner, select your avatar.
|
||||
1. Select **Edit profile**.
|
||||
1. In the left sidebar, select **Access Tokens**.
|
||||
|
||||
Learn more about generating a personal access token in the
|
||||
[Personal Access Token Documentation](../user/profile/personal_access_tokens.md).
|
||||
|
|
|
|||
|
|
@ -18,12 +18,15 @@ The following assumes you already have Vault installed and running.
|
|||
|
||||
1. **Get the OpenID Connect client ID and secret from GitLab:**
|
||||
|
||||
First you must create a GitLab application to obtain an application ID and secret for authenticating into Vault. To do this, sign in to GitLab and follow these steps:
|
||||
First you must create a GitLab application to obtain an application ID and secret for authenticating into Vault.
|
||||
To do this, sign in to GitLab and follow these steps:
|
||||
|
||||
1. On GitLab, click your avatar on the top-right corner, and select your user **Settings > Applications**.
|
||||
1. Fill out the application **Name** and [**Redirect URI**](https://www.vaultproject.io/docs/auth/jwt#redirect-uris),
|
||||
making sure to select the **OpenID** scope.
|
||||
1. Save application.
|
||||
1. In the top-right corner, select your avatar.
|
||||
1. Select **Edit profile**.
|
||||
1. In the left sidebar, select **Applications**.
|
||||
1. Fill out the application **Name** and [**Redirect URI**](https://www.vaultproject.io/docs/auth/jwt#redirect-uris).
|
||||
1. Select the **OpenID** scope.
|
||||
1. Select **Save application**.
|
||||
1. Copy client ID and secret, or keep the page open for reference.
|
||||
|
||||

|
||||
|
|
|
|||
|
|
@ -15,7 +15,13 @@ This command enables the namespaces feature introduced in GitLab 4.0. It moves e
|
|||
The **repository location changes as part of this task**, so you must **update all your Git URLs** to
|
||||
point to the new location.
|
||||
|
||||
The username can be changed at **Profile > Account**.
|
||||
To change your username:
|
||||
|
||||
1. In the top-right corner, select your avatar.
|
||||
1. Select **Edit profile**.
|
||||
1. In the left sidebar, select **Account**.
|
||||
1. In the **Change username** section, type the new username.
|
||||
1. Select **Update username**.
|
||||
|
||||
For example:
|
||||
|
||||
|
|
|
|||
|
|
@ -110,9 +110,10 @@ Each scenario can be a third-level heading, e.g. `### Getting error message X`.
|
|||
If you have none to add when creating a doc, leave this section in place
|
||||
but commented out to help encourage others to add to it in the future. -->
|
||||
|
||||
## Two-factor Authentication (2FA) for Git over SSH operations
|
||||
## Two-factor Authentication (2FA) for Git over SSH operations **(PREMIUM)**
|
||||
|
||||
> - [Introduced](https://gitlab.com/gitlab-org/gitlab/-/issues/270554) in GitLab 13.7.
|
||||
> - [Moved](https://gitlab.com/gitlab-org/gitlab/-/issues/299088) from GitLab Free to GitLab Premium in 13.9.
|
||||
> - It's [deployed behind a feature flag](../user/feature_flags.md), disabled by default.
|
||||
> - It's disabled on GitLab.com.
|
||||
> - It's not recommended for production use.
|
||||
|
|
|
|||
|
|
@ -182,8 +182,9 @@ Now you can copy the SSH key you created to your GitLab account.
|
|||
If you're using an RSA key, substitute accordingly.
|
||||
|
||||
1. Navigate to `https://gitlab.com` or your local GitLab instance URL and sign in.
|
||||
1. Select your avatar in the upper right corner, and click **Settings**
|
||||
1. Click **SSH Keys**.
|
||||
1. In the top-right corner, select your avatar.
|
||||
1. Select **Edit profile**.
|
||||
1. In the left sidebar, select **SSH Keys**.
|
||||
1. Paste the public key that you copied into the **Key** text box.
|
||||
1. Make sure your key includes a descriptive name in the **Title** text box, such as _Work Laptop_ or
|
||||
_Home Workstation_.
|
||||
|
|
|
|||
|
|
@ -93,7 +93,7 @@ The **Seat usage** page lists all users occupying seats. Details for each user i
|
|||
|
||||
- Full name
|
||||
- Username
|
||||
- Public email address (if they have provided one in their [profile settings](../../user/profile/index.md#profile-settings))
|
||||
- Public email address (if they have provided one in their [user settings](../../user/profile/index.md#user-settings))
|
||||
|
||||
The Seat usage listing is updated live, but the usage statistics on the billing page are updated
|
||||
only once per day. For this reason there can be a minor difference between the seat usage listing
|
||||
|
|
@ -247,8 +247,11 @@ Quotas apply to:
|
|||
subgroups, and nested projects. To view the group's usage, navigate to the group,
|
||||
then **Settings > Usage Quotas**.
|
||||
- Your personal account, where the minutes are available for your personal projects.
|
||||
To view and buy personal minutes, click your avatar, then
|
||||
**Settings > [Usage Quotas](https://gitlab.com/profile/usage_quotas#pipelines-quota-tab)**.
|
||||
To view and buy personal minutes:
|
||||
|
||||
1. In the top-right corner, select your avatar.
|
||||
1. Select **Edit profile**.
|
||||
1. In the left sidebar, select **[Usage Quotas](https://gitlab.com/profile/usage_quotas#pipelines-quota-tab)**.
|
||||
|
||||
Only pipeline minutes for GitLab shared runners are restricted. If you have a
|
||||
specific runner set up for your projects, there is no limit to your build time on GitLab SaaS.
|
||||
|
|
@ -282,10 +285,12 @@ To purchase additional minutes for your group on GitLab SaaS:
|
|||
|
||||
To purchase additional minutes for your personal namespace:
|
||||
|
||||
1. Click your avatar, then go to **Settings > Usage Quotas**.
|
||||
1. In the top-right corner, select your avatar.
|
||||
1. Select **Edit profile**.
|
||||
1. In the left sidebar, select **Usage Quotas**.
|
||||
1. Select **Buy additional minutes** and GitLab redirects you to the Customers Portal.
|
||||
1. Locate the subscription card that's linked to your personal namespace on GitLab SaaS, click **Buy more CI minutes**, and complete the details about the transaction. Once we have processed your payment, the extra CI minutes are synced to your personal namespace.
|
||||
1. To confirm the available CI minutes for your personal projects, click your avatar, then go to **Settings > Usage Quotas**.
|
||||
1. To confirm the available CI minutes for your personal projects, go to the **Usage Quotas** settings again.
|
||||
|
||||
The **Additional minutes** displayed now includes the purchased additional CI minutes, plus any minutes rolled over from last month.
|
||||
|
||||
|
|
|
|||
|
|
@ -22,7 +22,7 @@ SAML SSO is not supported at the subgroup level.
|
|||
|
||||
## Configuring your Identity Provider
|
||||
|
||||
1. Navigate to the group and click **Settings > SAML SSO**.
|
||||
1. Navigate to the group and select **Settings > SAML SSO**.
|
||||
1. Configure your SAML server using the **Assertion consumer service URL**, **Identifier**, and **GitLab single sign-on URL**. Alternatively GitLab provides [metadata XML configuration](#metadata-configuration). See [specific identity provider documentation](#providers) for more details.
|
||||
1. Configure the SAML response to include a NameID that uniquely identifies each user.
|
||||
1. Configure [required assertions](group_managed_accounts.md#assertions) if using [Group Managed Accounts](group_managed_accounts.md).
|
||||
|
|
@ -57,7 +57,7 @@ We recommend setting the NameID format to `Persistent` unless using a field (suc
|
|||
|
||||
GitLab provides metadata XML that can be used to configure your Identity Provider.
|
||||
|
||||
1. Navigate to the group and click **Settings > SAML SSO**.
|
||||
1. Navigate to the group and select **Settings > SAML SSO**.
|
||||
1. Copy the provided **GitLab metadata URL**.
|
||||
1. Follow your Identity Provider's documentation and paste the metadata URL when it's requested.
|
||||
|
||||
|
|
@ -69,8 +69,8 @@ After you set up your identity provider to work with GitLab, you must configure
|
|||
1. Find the SSO URL from your Identity Provider and enter it the **Identity provider single sign-on URL** field.
|
||||
1. Find and enter the fingerprint for the SAML token signing certificate in the **Certificate** field.
|
||||
1. Select the access level to be applied to newly added users in the **Default membership role** field. The default access level is 'Guest'.
|
||||
1. Click the **Enable SAML authentication for this group** toggle switch.
|
||||
1. Click the **Save changes** button.
|
||||
1. Select the **Enable SAML authentication for this group** toggle switch.
|
||||
1. Select the **Save changes** button.
|
||||
|
||||

|
||||
|
||||
|
|
@ -216,7 +216,7 @@ To link SAML to your existing GitLab.com account:
|
|||
|
||||
1. Sign in to your GitLab.com account.
|
||||
1. Locate and visit the **GitLab single sign-on URL** for the group you're signing in to. A group owner can find this on the group's **Settings > SAML SSO** page. If the sign-in URL is configured, users can connect to the GitLab app from the Identity Provider.
|
||||
1. Click **Authorize**.
|
||||
1. Select **Authorize**.
|
||||
1. Enter your credentials on the Identity Provider if prompted.
|
||||
1. You are then redirected back to GitLab.com and should now have access to the group. In the future, you can use SAML to sign in to GitLab.com.
|
||||
|
||||
|
|
@ -225,7 +225,7 @@ On subsequent visits, you should be able to go [sign in to GitLab.com with SAML]
|
|||
### Signing in to GitLab.com with SAML
|
||||
|
||||
1. Sign in to your identity provider.
|
||||
1. From the list of apps, click on the "GitLab.com" app (The name is set by the administrator of the identity provider).
|
||||
1. From the list of apps, select the "GitLab.com" app. (The name is set by the administrator of the identity provider.)
|
||||
1. You are then signed in to GitLab.com and redirected to the group.
|
||||
|
||||
### Configure user settings from SAML response
|
||||
|
|
@ -293,10 +293,15 @@ Users can unlink SAML for a group from their profile page. This can be helpful i
|
|||
- Your SAML NameID has changed and so GitLab can no longer find your user.
|
||||
|
||||
WARNING:
|
||||
Unlinking an account removes all roles assigned to that user within the group.
|
||||
Unlinking an account removes all roles assigned to that user in the group.
|
||||
If a user re-links their account, roles need to be reassigned.
|
||||
|
||||
For example, to unlink the `MyOrg` account, the following **Disconnect** button is available under **Profile > Accounts**:
|
||||
For example, to unlink the `MyOrg` account:
|
||||
|
||||
1. In the top-right corner, select your avatar.
|
||||
1. Select **Edit profile**.
|
||||
1. In the left sidebar, select **Account**.
|
||||
1. In the **Social sign-in** section, select **Disconnect** next to the connected account.
|
||||
|
||||

|
||||
|
||||
|
|
|
|||
|
|
@ -17,20 +17,21 @@ Deleting a user will delete all projects in that user namespace.
|
|||
|
||||
## As a user
|
||||
|
||||
As a user, you can delete your own account by:
|
||||
As a user, to delete your own account:
|
||||
|
||||
1. Clicking on your avatar.
|
||||
1. Navigating to **Settings > Account**.
|
||||
1. Selecting **Delete account**.
|
||||
1. In the top-right corner, select your avatar.
|
||||
1. Select **Edit profile**.
|
||||
1. In the left sidebar, select **Account**.
|
||||
1. Select **Delete account**.
|
||||
|
||||
## As an administrator
|
||||
|
||||
As an administrator, you can delete a user account by:
|
||||
As an administrator, to delete a user account:
|
||||
|
||||
1. Navigating to **Admin Area > Overview > Users**.
|
||||
1. Selecting a user.
|
||||
1. Under the **Account** tab, clicking:
|
||||
- **Delete user** to delete only the user but maintaining their
|
||||
1. Go to **Admin Area > Overview > Users**.
|
||||
1. Select a user.
|
||||
1. Under the **Account** tab, select:
|
||||
- **Delete user** to delete only the user but maintain their
|
||||
[associated records](#associated-records).
|
||||
- **Delete user and contributions** to delete the user and
|
||||
their associated records.
|
||||
|
|
|
|||
|
|
@ -1,8 +0,0 @@
|
|||
---
|
||||
redirect_to: '../index.md#profile-settings'
|
||||
---
|
||||
|
||||
This document was moved to [../index.md#profile-settings](../index.md#profile-settings).
|
||||
|
||||
<!-- This redirect file can be deleted after February 1, 2021. -->
|
||||
<!-- Before deletion, see: https://docs.gitlab.com/ee/development/documentation/#move-or-rename-a-page -->
|
||||
|
|
@ -44,7 +44,7 @@ To enable 2FA:
|
|||
|
||||
1. **In GitLab:**
|
||||
1. Sign in to your GitLab account.
|
||||
1. Go to your [**Profile settings**](../index.md#profile-settings).
|
||||
1. Go to your [**User settings**](../index.md#user-settings).
|
||||
1. Go to **Account**.
|
||||
1. Select **Enable Two-factor Authentication**.
|
||||
1. **On your device (usually your phone):**
|
||||
|
|
@ -246,7 +246,7 @@ Search for `security.webauth.u2f` and double click on it to toggle to `true`.
|
|||
To set up 2FA with a U2F device:
|
||||
|
||||
1. Sign in to your GitLab account.
|
||||
1. Go to your [**Profile settings**](../index.md#profile-settings).
|
||||
1. Go to your [**User settings**](../index.md#user-settings).
|
||||
1. Go to **Account**.
|
||||
1. Click **Enable Two-Factor Authentication**.
|
||||
1. Connect your U2F device.
|
||||
|
|
@ -282,7 +282,7 @@ and the following mobile browsers:
|
|||
To set up 2FA with a WebAuthn compatible device:
|
||||
|
||||
1. Sign in to your GitLab account.
|
||||
1. Go to your [**Profile settings**](../index.md#profile-settings).
|
||||
1. Go to your [**User settings**](../index.md#user-settings).
|
||||
1. Go to **Account**.
|
||||
1. Select **Enable Two-Factor Authentication**.
|
||||
1. Plug in your WebAuthn device.
|
||||
|
|
@ -349,7 +349,7 @@ request and you're automatically signed in.
|
|||
If you ever need to disable 2FA:
|
||||
|
||||
1. Sign in to your GitLab account.
|
||||
1. Go to your [**Profile settings**](../index.md#profile-settings).
|
||||
1. Go to your [**User settings**](../index.md#user-settings).
|
||||
1. Go to **Account**.
|
||||
1. Click **Disable**, under **Two-Factor Authentication**.
|
||||
|
||||
|
|
@ -434,7 +434,7 @@ a new set of recovery codes with SSH:
|
|||
When prompted for a two-factor code, enter one of the recovery codes obtained
|
||||
from the command-line output.
|
||||
|
||||
After signing in, visit your **Profile settings > Account** immediately to set
|
||||
After signing in, visit your **User settings > Account** immediately to set
|
||||
up two-factor authentication with a new device.
|
||||
|
||||
### Regenerate 2FA recovery codes
|
||||
|
|
@ -443,8 +443,8 @@ To regenerate 2FA recovery codes, you need access to a desktop browser:
|
|||
|
||||
1. Navigate to GitLab.
|
||||
1. Sign in to your GitLab account.
|
||||
1. Go to your [**Profile settings**](../index.md#profile-settings).
|
||||
1. Select **{account}** **Account > Two-Factor Authentication (2FA)**.
|
||||
1. Go to your [**User settings**](../index.md#user-settings).
|
||||
1. Select **Account > Two-Factor Authentication (2FA)**.
|
||||
1. If you've already configured 2FA, click **Manage two-factor authentication**.
|
||||
1. In the **Register Two-Factor Authenticator** pane, click **Regenerate recovery codes**.
|
||||
|
||||
|
|
|
|||
|
|
@ -14,9 +14,11 @@ review the sessions, and revoke any you don't recognize.
|
|||
|
||||
## Listing all active sessions
|
||||
|
||||
1. Click your avatar.
|
||||
1. Select **Settings**.
|
||||
1. Click **Active Sessions** in the sidebar.
|
||||
To list all active sessions:
|
||||
|
||||
1. In the top-right corner, select your avatar.
|
||||
1. Select **Edit profile**.
|
||||
1. In the left sidebar, select **Active Sessions**.
|
||||
|
||||

|
||||
|
||||
|
|
@ -29,8 +31,12 @@ exceeds 100, the oldest ones are deleted.
|
|||
|
||||
## Revoking a session
|
||||
|
||||
1. Use the previous steps to navigate to **Active Sessions**.
|
||||
1. Click on **Revoke** besides a session. The current session cannot be revoked, as this would sign you out of GitLab.
|
||||
To revoke an active session:
|
||||
|
||||
1. In the top-right corner, select your avatar.
|
||||
1. Select **Edit profile**.
|
||||
1. In the left sidebar, select **Active Sessions**.
|
||||
1. Select **Revoke** next to a session. The current session cannot be revoked, as this would sign you out of GitLab.
|
||||
|
||||
NOTE:
|
||||
When any session is revoked all **Remember me** tokens for all
|
||||
|
|
|
|||
Binary file not shown.
|
Before Width: | Height: | Size: 16 KiB |
Binary file not shown.
|
Before Width: | Height: | Size: 6.8 KiB |
|
|
@ -8,7 +8,7 @@ info: To determine the technical writer assigned to the Stage/Group associated w
|
|||
# User account
|
||||
|
||||
Each GitLab account has a user profile, and settings. Your [profile](#user-profile)
|
||||
contains information about you, and your GitLab activity. Your [settings](#profile-settings)
|
||||
contains information about you, and your GitLab activity. Your [settings](#user-settings)
|
||||
allow you to customize some aspects of GitLab to suit yourself.
|
||||
|
||||
## Creating users
|
||||
|
|
@ -29,8 +29,8 @@ See [Unknown Sign-In Notification](unknown_sign_in_notification.md) for more det
|
|||
|
||||
To access your profile:
|
||||
|
||||
1. Click on your avatar.
|
||||
1. Select **Profile**.
|
||||
1. In the top-right corner, select your avatar.
|
||||
1. Select your name or username.
|
||||
|
||||
On your profile page, you can see the following information:
|
||||
|
||||
|
|
@ -42,12 +42,12 @@ On your profile page, you can see the following information:
|
|||
- Starred projects: projects you starred
|
||||
- Snippets: your personal code [snippets](../snippets.md#personal-snippets)
|
||||
|
||||
## Profile settings
|
||||
## User settings
|
||||
|
||||
To access your profile settings:
|
||||
To access your user settings:
|
||||
|
||||
1. Click on your avatar.
|
||||
1. Select **Settings**.
|
||||
1. In the top-right corner, select your avatar.
|
||||
1. Select **Edit profile**.
|
||||
|
||||
From there, you can:
|
||||
|
||||
|
|
@ -78,11 +78,12 @@ From there, you can:
|
|||
|
||||
## Changing your password
|
||||
|
||||
1. Navigate to your [profile's](#profile-settings) **Settings > Password**.
|
||||
1. Enter your current password in the 'Current password' field.
|
||||
1. Enter your desired new password twice, once in the 'New password' field and
|
||||
once in the 'Password confirmation' field.
|
||||
1. Click the 'Save password' button.
|
||||
1. Go to your [user settings](#user-settings).
|
||||
1. In the left sidebar, select **Password**.
|
||||
1. Enter your current password in the **Current password** field.
|
||||
1. Enter your desired new password twice, once in the **New password** field and
|
||||
once in the **Password confirmation** field.
|
||||
1. Select **Save password**.
|
||||
|
||||
If you don't know your current password, select the 'I forgot my password' link.
|
||||
|
||||
|
|
@ -97,12 +98,13 @@ before proceeding.
|
|||
|
||||
To change your `username`:
|
||||
|
||||
1. Navigate to your [profile's](#profile-settings) **Settings > Account**.
|
||||
1. Navigate to your [user settings](#user-settings).
|
||||
1. In the left sidebar, select **Account**.
|
||||
1. Enter a new username under **Change username**.
|
||||
1. Click **Update username**.
|
||||
1. Select **Update username**.
|
||||
|
||||
WARNING:
|
||||
It is currently not possible to change your username if it contains a
|
||||
It is not possible to change your username if it contains a
|
||||
project with [Container Registry](../packages/container_registry/index.md) tags,
|
||||
because the project cannot be moved.
|
||||
|
||||
|
|
@ -127,33 +129,31 @@ The following information is hidden from the user profile page (`https://gitlab.
|
|||
- Starred projects tab
|
||||
- Snippets tab
|
||||
|
||||
To enable private profile:
|
||||
To make your profile private:
|
||||
|
||||
1. Click your avatar.
|
||||
1. Select **Profile**.
|
||||
1. Click **Edit profile** (pencil icon).
|
||||
1. Check the **Private profile** option in the **Main settings** section.
|
||||
1. Click **Update profile settings**.
|
||||
1. In the top-right corner, select your avatar.
|
||||
1. Select **Edit profile**.
|
||||
1. Select the **Private profile** checkbox.
|
||||
1. Select **Update profile settings**.
|
||||
|
||||
NOTE:
|
||||
All your profile information can be seen by yourself, and GitLab admins, even if
|
||||
All your profile information can be seen by yourself and GitLab administrators even if
|
||||
the **Private profile** option is enabled.
|
||||
|
||||
## Add details of external accounts
|
||||
|
||||
GitLab allows you to add links to certain other external accounts you might have, like Skype and Twitter. They can help other users connect with you on other platforms.
|
||||
You can add links to certain other external accounts you might have, like Skype and Twitter.
|
||||
They can help other users connect with you on other platforms.
|
||||
|
||||
To add links to other accounts:
|
||||
|
||||
1. Click your avatar.
|
||||
1. Select **Profile**.
|
||||
1. Click **Edit profile** (pencil icon).
|
||||
1. Complete the desired fields for external accounts, in the **Main settings**
|
||||
section:
|
||||
1. In the top-right corner, select your avatar.
|
||||
1. Select **Edit profile**.
|
||||
1. Edit the desired fields for external accounts:
|
||||
- Skype
|
||||
- Twitter
|
||||
- LinkedIn
|
||||
1. Click **Update profile settings**.
|
||||
- Twitter
|
||||
1. Select **Update profile settings**.
|
||||
|
||||
## Private contributions
|
||||
|
||||
|
|
@ -163,11 +163,10 @@ Enabling private contributions includes contributions to private projects, in th
|
|||
|
||||
To enable private contributions:
|
||||
|
||||
1. Click on your avatar.
|
||||
1. Select **Profile**.
|
||||
1. Click **Edit profile** (pencil icon).
|
||||
1. Check the **Private contributions** option.
|
||||
1. Click **Update profile settings**.
|
||||
1. In the top-right corner, select your avatar.
|
||||
1. Select **Edit profile**.
|
||||
1. Select the **Private contributions** checkbox.
|
||||
1. Select **Update profile settings**.
|
||||
|
||||
## Current status
|
||||
|
||||
|
|
@ -183,23 +182,23 @@ They may however contain emoji codes such as `I'm on vacation :palm_tree:`.
|
|||
|
||||
To set your current status:
|
||||
|
||||
1. Click your avatar.
|
||||
1. Click **Set status**, or **Edit status** if you have already set a status.
|
||||
1. Set the desired emoji and/or status message.
|
||||
1. Click **Set status**. Alternatively, you can click **Remove status** to remove your user status entirely.
|
||||
1. In the top-right corner, select your avatar.
|
||||
1. Select **Set status**, or **Edit status** if you have already set a status.
|
||||
1. Set the desired emoji and status message.
|
||||
1. Select **Set status**. Alternatively, you can select **Remove status** to remove your user status entirely.
|
||||
|
||||
or
|
||||
|
||||
1. Click your avatar.
|
||||
1. Select **Profile**.
|
||||
1. Click **Edit profile** (pencil icon).
|
||||
1. In the top-right corner, select your avatar.
|
||||
1. Select your name or username.
|
||||
1. Select the Edit profile icon (**{pencil}**).
|
||||
1. Enter your status message in the **Your status** text field.
|
||||
1. Click **Add status emoji** (smiley face), and select the desired emoji.
|
||||
1. Click **Update profile settings**.
|
||||
1. Select Add status emoji icon (**{slight-smile}**), and select the desired emoji.
|
||||
1. Select **Update profile settings**.
|
||||
|
||||
You can also set your current status [using the API](../../api/users.md#user-status).
|
||||
|
||||
If you previously selected the "Busy" checkbox, remember to deselect it when you become available again.
|
||||
If you previously selected the **Busy** checkbox, remember to deselect it when you become available again.
|
||||
|
||||
## Busy status indicator
|
||||
|
||||
|
|
@ -210,7 +209,7 @@ If you previously selected the "Busy" checkbox, remember to deselect it when you
|
|||
> - It's not recommended for production use.
|
||||
> - For GitLab self-managed instances, GitLab administrators can opt to [disable it](#disable-busy-status-feature).
|
||||
|
||||
To indicate to others that you are busy, you can set an indicator
|
||||
To indicate to others that you are busy, you can set an indicator.
|
||||
|
||||

|
||||
|
||||
|
|
@ -218,16 +217,15 @@ To set the busy status indicator, either:
|
|||
|
||||
- Set it directly:
|
||||
|
||||
1. Click your avatar.
|
||||
1. Click **Set status**, or **Edit status** if you have already set a status.
|
||||
1. Select the **Busy** checkbox
|
||||
1. In the top-right corner, select your avatar.
|
||||
1. Select **Set status**, or **Edit status** if you have already set a status.
|
||||
1. Select the **Busy** checkbox.
|
||||
|
||||
- Set it on your profile:
|
||||
|
||||
1. Click your avatar.
|
||||
1. Select **Profile**.
|
||||
1. Click **Edit profile** (**{pencil}**).
|
||||
1. Select the **Busy** checkbox
|
||||
1. In the top-right corner, select your avatar.
|
||||
1. Select **Edit profile**.
|
||||
1. Select the **Busy** checkbox.
|
||||
|
||||
### Disable busy status feature
|
||||
|
||||
|
|
@ -256,12 +254,11 @@ Any of your own verified email addresses can be used as the commit email.
|
|||
|
||||
To change your commit email:
|
||||
|
||||
1. Click your avatar.
|
||||
1. Select **Profile**.
|
||||
1. Click **Edit profile** (pencil icon).
|
||||
1. Click **Commit email** dropdown.
|
||||
1. In the top-right corner, select your avatar.
|
||||
1. Select **Edit profile**.
|
||||
1. Select the **Commit email** dropdown.
|
||||
1. Select any of the verified emails.
|
||||
1. Click **Update profile settings**.
|
||||
1. Select **Update profile settings**.
|
||||
|
||||
### Private commit email
|
||||
|
||||
|
|
@ -272,14 +269,13 @@ which allows the user to keep their email information private.
|
|||
|
||||
To enable this option:
|
||||
|
||||
1. Click your avatar.
|
||||
1. Select **Profile**.
|
||||
1. Click **Edit profile** (pencil icon).
|
||||
1. Click **Commit email** dropdown.
|
||||
1. In the top-right corner, select your avatar.
|
||||
1. Select **Edit profile**.
|
||||
1. Select the **Commit email** dropdown.
|
||||
1. Select **Use a private email** option.
|
||||
1. Click **Update profile settings**.
|
||||
1. Select **Update profile settings**.
|
||||
|
||||
Once this option is enabled, every Git-related action is performed using the private commit email.
|
||||
After this option is enabled, every Git-related action is performed using the private commit email.
|
||||
|
||||
To stay fully anonymous, you can also copy this private commit email
|
||||
and configure it on your local machine using the following command:
|
||||
|
|
@ -298,7 +294,7 @@ and expires after "Application settings -> Session duration (minutes)"/`session_
|
|||
(defaults to `10080` minutes = 7 days) of no activity.
|
||||
|
||||
When signing in to the main GitLab application, you can also check the
|
||||
"Remember me" option which sets the `remember_user_token`
|
||||
**Remember me** option which sets the `remember_user_token`
|
||||
cookie (via [`devise`](https://github.com/heartcombo/devise)).
|
||||
`remember_user_token` expires after
|
||||
`config/initializers/devise.rb` -> `config.remember_for` (defaults to 2 weeks).
|
||||
|
|
@ -326,7 +322,8 @@ GitLab uses both session and persistent cookies:
|
|||
- Session cookie: Session cookies are normally removed at the end of the browser session when
|
||||
the browser is closed. The `_gitlab_session` cookie has no fixed expiration date. However,
|
||||
it expires based on its [`session_expire_delay`](#why-do-i-keep-getting-signed-out).
|
||||
- Persistent cookie: The `remember_user_token` is a cookie with an expiration date of two weeks. GitLab activates this cookie if you click Remember Me when you sign in.
|
||||
- Persistent cookie: The `remember_user_token` is a cookie with an expiration date of two weeks.
|
||||
GitLab activates this cookie if you select **Remember Me** when you sign in.
|
||||
|
||||
By default, the server sets a time-to-live (TTL) of 1-week on any session that is used.
|
||||
|
||||
|
|
@ -336,15 +333,3 @@ The server continues to reset the TTL for that session, independent of whether 2
|
|||
If you close your browser and open it up again, the `remember_user_token` cookie allows your user to reauthenticate itself.
|
||||
|
||||
Without the `config.extend_remember_period` flag, you would be forced to sign in again after two weeks.
|
||||
|
||||
<!-- ## Troubleshooting
|
||||
|
||||
Include any troubleshooting steps that you can foresee. If you know beforehand what issues
|
||||
one might have when setting this up, or when something is changed, or on upgrading, it's
|
||||
important to describe those, too. Think of things that may go wrong and include them here.
|
||||
This is important to minimize requests for support, and to avoid doc comments with
|
||||
questions that you know someone might ask.
|
||||
|
||||
Each scenario can be a third-level heading, e.g. `### Getting error message X`.
|
||||
If you have none to add when creating a doc, leave this section in place
|
||||
but commented out to help encourage others to add to it in the future. -->
|
||||
|
|
|
|||
|
|
@ -33,13 +33,14 @@ You can create as many personal access tokens as you like from your GitLab
|
|||
profile.
|
||||
|
||||
1. Sign in to GitLab.
|
||||
1. In the upper-right corner, click your avatar and select **Settings**.
|
||||
1. On the **User Settings** menu, select **Access Tokens**.
|
||||
1. In the top-right corner, select your avatar.
|
||||
1. Select **Edit profile**.
|
||||
1. In the left sidebar, select **Access Tokens**.
|
||||
1. Choose a name and optional expiry date for the token.
|
||||
1. Choose the [desired scopes](#limiting-scopes-of-a-personal-access-token).
|
||||
1. Click the **Create personal access token** button.
|
||||
1. Select **Create personal access token**.
|
||||
1. Save the personal access token somewhere safe. If you navigate away or refresh
|
||||
your page, and you did not save the token, you must create a new one.
|
||||
your page, and you did not save the token, you must create a new one.
|
||||
|
||||
### Revoking a personal access token
|
||||
|
||||
|
|
|
|||
|
|
@ -12,9 +12,8 @@ of GitLab to their liking.
|
|||
|
||||
To navigate to your profile's preferences:
|
||||
|
||||
1. Click your avatar.
|
||||
1. Select **Settings**.
|
||||
1. Click **Preferences** in the sidebar.
|
||||
1. In the top-right corner, select your avatar.
|
||||
1. Select **Preferences**.
|
||||
|
||||
## Navigation theme
|
||||
|
||||
|
|
@ -36,8 +35,7 @@ The default theme is Indigo. You can choose between 10 themes:
|
|||
- Light Red
|
||||
- Dark
|
||||
- Light
|
||||
|
||||

|
||||
- [Dark Mode](#dark-mode)
|
||||
|
||||
## Dark mode
|
||||
|
||||
|
|
@ -47,7 +45,8 @@ GitLab has started work on dark mode! The dark mode Alpha release is available i
|
|||
spirit of iteration and the lower expectations of
|
||||
[Alpha versions](https://about.gitlab.com/handbook/product/gitlab-the-product/#alpha).
|
||||
|
||||
Progress on dark mode is tracked in the [Dark theme epic](https://gitlab.com/groups/gitlab-org/-/epics/2902). See the epic for:
|
||||
Progress on dark mode is tracked in the [Dark theme epic](https://gitlab.com/groups/gitlab-org/-/epics/2902).
|
||||
See the epic for:
|
||||
|
||||
- A list of known issues.
|
||||
- Our planned direction and next steps.
|
||||
|
|
@ -60,14 +59,15 @@ the future, we plan to make it configurable in its own section along with suppor
|
|||
[different navigation themes](https://gitlab.com/gitlab-org/gitlab/-/issues/219512).
|
||||
|
||||
NOTE:
|
||||
Dark theme currently only works with the 'Dark' syntax highlighting.
|
||||
Dark theme only works with the **Dark** syntax highlighting theme.
|
||||
|
||||
## Syntax highlighting theme
|
||||
|
||||
NOTE:
|
||||
GitLab uses the [rouge Ruby library](http://rouge.jneen.net/ "Rouge website")
|
||||
for syntax highlighting outside of any Editor context. The WebIDE (like Snippets)
|
||||
uses [Monaco Editor](https://microsoft.github.io/monaco-editor/) and it's provided [Monarch](https://microsoft.github.io/monaco-editor/monarch.html) library for
|
||||
uses [Monaco Editor](https://microsoft.github.io/monaco-editor/) and it's provided
|
||||
[Monarch](https://microsoft.github.io/monaco-editor/monarch.html) library for
|
||||
syntax highlighting. For a list of supported languages, visit the documentation of
|
||||
the respective libraries.
|
||||
|
||||
|
|
@ -121,7 +121,7 @@ You have 8 options here that you can use for your default dashboard view:
|
|||
- Your projects' activity
|
||||
- Starred projects' activity
|
||||
- Your groups
|
||||
- Your [to-dos](../todos.md)
|
||||
- Your [To-Do List](../todos.md)
|
||||
- Assigned Issues
|
||||
- Assigned Merge Requests
|
||||
- Operations Dashboard **(PREMIUM)**
|
||||
|
|
|
|||
|
|
@ -13,7 +13,7 @@ functionality to GitLab.
|
|||
## Accessing integrations
|
||||
|
||||
You can find the available integrations under your project's
|
||||
**Settings ➔ Integrations** page.
|
||||
**Settings > Integrations** page.
|
||||
|
||||
There are more than 20 integrations to integrate with. Click on the one that you
|
||||
want to configure.
|
||||
|
|
|
|||
|
|
@ -201,7 +201,7 @@ issue's description are listed in the [issue history](#issue-history). **(PREMIU
|
|||
|
||||
You can mention a user or a group present in your GitLab instance with `@username` or
|
||||
`@groupname`. All mentioned users are notified via to-do items and emails,
|
||||
unless they have disabled all notifications in their profile settings.
|
||||
unless they have disabled all [notifications](#notifications) in their user settings.
|
||||
This is controlled in the [notification settings](../../profile/notifications.md).
|
||||
|
||||
Mentions for yourself (the current logged in user) are highlighted
|
||||
|
|
@ -245,8 +245,8 @@ Also:
|
|||
|
||||
- You can mention a user or a group present in your GitLab instance with
|
||||
`@username` or `@groupname` and they are notified via to-do items
|
||||
and emails, unless they have [disabled all notifications](#notifications)
|
||||
in their profile settings.
|
||||
and emails, unless they have disabled all [notifications](#notifications)
|
||||
in their user settings.
|
||||
- Mentions for yourself (the current logged-in user) are highlighted
|
||||
in a different color, which allows you to quickly see which comments involve you.
|
||||
|
||||
|
|
|
|||
|
|
@ -82,11 +82,10 @@ Click **Expand file** on any file to view the changes for that file.
|
|||
|
||||
For larger merge requests, consider reviewing one file at a time. To enable this feature:
|
||||
|
||||
1. In the top right corner of the navigation bar, click your user avatar.
|
||||
1. Click **Settings**.
|
||||
1. In the left sidebar, go to **Preferences**.
|
||||
1. In the top-right corner, select your avatar.
|
||||
1. Select **Preferences**.
|
||||
1. Scroll to the **Behavior** section and select **Show one file at a time on merge request's Changes tab**.
|
||||
1. Click **Save changes** to apply.
|
||||
1. Select **Save changes**.
|
||||
|
||||
After you enable this setting, GitLab displays only one file at a time in the **Changes** tab when you review merge requests. You can click **Prev** and **Next** to view other changed files.
|
||||
|
||||
|
|
@ -97,7 +96,7 @@ this behavior, you can do so from your **User preferences** (as explained above)
|
|||
merge request:
|
||||
|
||||
1. Go to the merge request's **Changes** tab.
|
||||
1. Click the cog icon (**{settings}**) to reveal the merge request's settings dropdown.
|
||||
1. Select the cog icon (**{settings}**) to reveal the merge request's settings dropdown.
|
||||
1. Select or deselect the checkbox **Show one file at a time** to change the setting accordingly.
|
||||
|
||||
This change overrides the choice you made in your user preferences and persists until you clear your
|
||||
|
|
@ -109,11 +108,11 @@ browser's cookies or change this behavior again.
|
|||
|
||||
To seamlessly navigate among commits in a merge request:
|
||||
|
||||
1. Click the **Commits** tab.
|
||||
1. Click a commit to open it in the single-commit view.
|
||||
1. Select the **Commits** tab.
|
||||
1. Select a commit to open it in the single-commit view.
|
||||
1. Navigate through the commits by either:
|
||||
|
||||
- Clicking **Prev** and **Next** buttons on the top-right of the page.
|
||||
- Selecting **Prev** and **Next** buttons on the top-right of the page.
|
||||
- Using the <kbd>X</kbd> and <kbd>C</kbd> keyboard shortcuts.
|
||||
|
||||

|
||||
|
|
|
|||
|
|
@ -138,27 +138,25 @@ started:
|
|||
gpg --armor --export 30F2B65B9246B6CA
|
||||
```
|
||||
|
||||
1. Finally, copy the public key and [add it in your profile settings](#adding-a-gpg-key-to-your-account)
|
||||
1. Finally, copy the public key and [add it in your user settings](#adding-a-gpg-key-to-your-account)
|
||||
|
||||
## Adding a GPG key to your account
|
||||
|
||||
NOTE:
|
||||
Once you add a key, you cannot edit it, only remove it. In case the paste
|
||||
didn't work, you'll have to remove the offending key and re-add it.
|
||||
After you add a key, you cannot edit it, only remove it. In case the paste
|
||||
didn't work, you have to remove the offending key and re-add it.
|
||||
|
||||
You can add a GPG key in your profile's settings:
|
||||
You can add a GPG key in your user settings:
|
||||
|
||||
1. On the upper right corner, click on your avatar and go to your **Settings**.
|
||||
|
||||

|
||||
|
||||
1. Navigate to the **GPG keys** tab and paste your _public_ key in the 'Key'
|
||||
box.
|
||||
1. In the top-right corner, select your avatar.
|
||||
1. Select **Edit profile**.
|
||||
1. In the left sidebar, select **GPG Keys**.
|
||||
1. Paste your _public_ key in the **Key** text box.
|
||||
|
||||

|
||||
|
||||
1. Finally, click on **Add key** to add it to GitLab. You will be able to see
|
||||
its fingerprint, the corresponding email address and creation date.
|
||||
1. Select **Add key** to add it to GitLab. You can see the key's fingerprint, the corresponding
|
||||
email address, and creation date.
|
||||
|
||||

|
||||
|
||||
|
|
@ -248,22 +246,24 @@ in case your key has been compromised.
|
|||
|
||||
To revoke a GPG key:
|
||||
|
||||
1. On the upper right corner, click on your avatar and go to your **Settings**.
|
||||
1. Navigate to the **GPG keys** tab.
|
||||
1. Click on **Revoke** besides the GPG key you want to delete.
|
||||
1. In the top-right corner, select your avatar.
|
||||
1. Select **Edit profile**.
|
||||
1. In the left sidebar, select **GPG Keys**.
|
||||
1. Select **Revoke** next to the GPG key you want to delete.
|
||||
|
||||
## Removing a GPG key
|
||||
|
||||
Removing a key **does not unverify** already signed commits. Commits that were
|
||||
verified by using this key will stay verified. Only unpushed commits will stay
|
||||
unverified once you remove this key. To unverify already signed commits, you need
|
||||
verified by using this key stay verified. Only unpushed commits stay
|
||||
unverified after you remove this key. To unverify already signed commits, you need
|
||||
to [revoke the associated GPG key](#revoking-a-gpg-key) from your account.
|
||||
|
||||
To remove a GPG key from your account:
|
||||
|
||||
1. On the upper right corner, click on your avatar and go to your **Settings**.
|
||||
1. Navigate to the **GPG keys** tab.
|
||||
1. Click on the trash icon besides the GPG key you want to delete.
|
||||
1. In the top-right corner, select your avatar.
|
||||
1. Select **Edit profile**.
|
||||
1. In the left sidebar, select **GPG Keys**.
|
||||
1. Select the trash icon (**{remove}**) next to the GPG key you want to delete.
|
||||
|
||||
## Rejecting commits that are not signed **(PREMIUM)**
|
||||
|
||||
|
|
|
|||
|
|
@ -116,7 +116,7 @@ LDAP Users remain confirmed if all of the following conditions are met:
|
|||
|
||||
- The ["User email confirmation at sign-up" option](../security/user_email_confirmation.md) is set to false.
|
||||
- The first sign-in is based on user LDAP credentials.
|
||||
- The user has added and verified [a secondary email address](profile/index.md#profile-settings) some time later.
|
||||
- The user has added and verified [a secondary email address](profile/index.md#user-settings) some time later.
|
||||
|
||||
NOTE:
|
||||
Confirmation timestamps (primary vs. secondary) are different.
|
||||
|
|
@ -124,6 +124,6 @@ Confirmation timestamps (primary vs. secondary) are different.
|
|||
Users remain unconfirmed by the background migration if any of the following conditions are met:
|
||||
|
||||
- They [create an account through GitLab](profile/account/create_accounts.md).
|
||||
- They [swap their primary email address](profile/index.md#profile-settings) and verify it.
|
||||
- They [swap their primary email address](profile/index.md#user-settings) and verify it.
|
||||
- If they have two email addresses with the same `confirmed_at` timestamp due to the linked [security issue](https://gitlab.com/gitlab-org/gitlab/-/issues/121664).
|
||||
- [LDAP is introduced](../administration/auth/ldap/index.md), and users' primary email address matches that in LDAP.
|
||||
|
|
|
|||
|
|
@ -116,6 +116,10 @@ module API
|
|||
|
||||
'Could not find a user for the given key' unless actor.user
|
||||
end
|
||||
|
||||
def two_factor_otp_check
|
||||
{ success: false, message: 'Feature is not available' }
|
||||
end
|
||||
end
|
||||
|
||||
namespace 'internal' do
|
||||
|
|
@ -278,6 +282,11 @@ module API
|
|||
present response, with: Entities::InternalPostReceive::Response
|
||||
end
|
||||
|
||||
# This endpoint was added in https://gitlab.com/gitlab-org/gitlab/-/issues/212308
|
||||
# It was added with the plan to be used by GitLab PAM module but we
|
||||
# decided to pursue a different approach, so it's currently not used.
|
||||
# We might revive the PAM module though as it provides better user
|
||||
# flow.
|
||||
post '/two_factor_config', feature_category: :authentication_and_authorization do
|
||||
status 200
|
||||
|
||||
|
|
@ -303,28 +312,7 @@ module API
|
|||
post '/two_factor_otp_check', feature_category: :authentication_and_authorization do
|
||||
status 200
|
||||
|
||||
break { success: false, message: 'Feature flag is disabled' } unless Feature.enabled?(:two_factor_for_cli)
|
||||
|
||||
actor.update_last_used_at!
|
||||
user = actor.user
|
||||
|
||||
error_message = validate_actor_key(actor, params[:key_id])
|
||||
|
||||
break { success: false, message: error_message } if error_message
|
||||
|
||||
break { success: false, message: 'Deploy keys cannot be used for Two Factor' } if actor.key.is_a?(DeployKey)
|
||||
|
||||
break { success: false, message: 'Two-factor authentication is not enabled for this user' } unless user.two_factor_enabled?
|
||||
|
||||
otp_validation_result = ::Users::ValidateOtpService.new(user).execute(params.fetch(:otp_attempt))
|
||||
|
||||
if otp_validation_result[:status] == :success
|
||||
::Gitlab::Auth::Otp::SessionEnforcer.new(actor.key).update_session
|
||||
|
||||
{ success: true }
|
||||
else
|
||||
{ success: false, message: 'Invalid OTP' }
|
||||
end
|
||||
two_factor_otp_check
|
||||
end
|
||||
end
|
||||
end
|
||||
|
|
|
|||
|
|
@ -1,39 +0,0 @@
|
|||
# frozen_string_literal: true
|
||||
|
||||
module Gitlab
|
||||
module Auth
|
||||
module Otp
|
||||
class SessionEnforcer
|
||||
OTP_SESSIONS_NAMESPACE = 'session:otp'
|
||||
|
||||
def initialize(key)
|
||||
@key = key
|
||||
end
|
||||
|
||||
def update_session
|
||||
Gitlab::Redis::SharedState.with do |redis|
|
||||
redis.setex(key_name, session_expiry_in_seconds, true)
|
||||
end
|
||||
end
|
||||
|
||||
def access_restricted?
|
||||
Gitlab::Redis::SharedState.with do |redis|
|
||||
!redis.get(key_name)
|
||||
end
|
||||
end
|
||||
|
||||
private
|
||||
|
||||
attr_reader :key
|
||||
|
||||
def key_name
|
||||
@key_name ||= "#{OTP_SESSIONS_NAMESPACE}:#{key.id}"
|
||||
end
|
||||
|
||||
def session_expiry_in_seconds
|
||||
Gitlab::CurrentSettings.git_two_factor_session_expiry.minutes.to_i
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
|
@ -77,7 +77,6 @@ module Gitlab
|
|||
check_authentication_abilities!
|
||||
check_command_disabled!
|
||||
check_command_existence!
|
||||
check_otp_session!
|
||||
|
||||
custom_action = check_custom_action
|
||||
return custom_action if custom_action
|
||||
|
|
@ -255,31 +254,6 @@ module Gitlab
|
|||
end
|
||||
end
|
||||
|
||||
def check_otp_session!
|
||||
return unless ssh?
|
||||
return if !key? || deploy_key?
|
||||
return unless Feature.enabled?(:two_factor_for_cli)
|
||||
return unless user.two_factor_enabled?
|
||||
|
||||
if ::Gitlab::Auth::Otp::SessionEnforcer.new(actor).access_restricted?
|
||||
message = "OTP verification is required to access the repository.\n\n"\
|
||||
" Use: #{build_ssh_otp_verify_command}"
|
||||
|
||||
raise ForbiddenError, message
|
||||
end
|
||||
end
|
||||
|
||||
def build_ssh_otp_verify_command
|
||||
user = "#{Gitlab.config.gitlab_shell.ssh_user}@" unless Gitlab.config.gitlab_shell.ssh_user.empty?
|
||||
user_host = "#{user}#{Gitlab.config.gitlab_shell.ssh_host}"
|
||||
|
||||
if Gitlab.config.gitlab_shell.ssh_port != 22
|
||||
"ssh #{user_host} -p #{Gitlab.config.gitlab_shell.ssh_port} 2fa_verify"
|
||||
else
|
||||
"ssh #{user_host} 2fa_verify"
|
||||
end
|
||||
end
|
||||
|
||||
def check_db_accessibility!
|
||||
return unless receive_pack?
|
||||
|
||||
|
|
|
|||
|
|
@ -8664,13 +8664,13 @@ msgstr ""
|
|||
msgid "CurrentUser|Buy Pipeline minutes"
|
||||
msgstr ""
|
||||
|
||||
msgid "CurrentUser|Edit profile"
|
||||
msgstr ""
|
||||
|
||||
msgid "CurrentUser|One of your groups is running out"
|
||||
msgstr ""
|
||||
|
||||
msgid "CurrentUser|Profile"
|
||||
msgstr ""
|
||||
|
||||
msgid "CurrentUser|Settings"
|
||||
msgid "CurrentUser|Preferences"
|
||||
msgstr ""
|
||||
|
||||
msgid "CurrentUser|Start an Ultimate trial"
|
||||
|
|
|
|||
|
|
@ -6,7 +6,7 @@ module QA
|
|||
class Menu < Page::Base
|
||||
view 'app/views/layouts/header/_current_user_dropdown.html.haml' do
|
||||
element :sign_out_link
|
||||
element :settings_link
|
||||
element :edit_profile_link
|
||||
end
|
||||
|
||||
view 'app/views/layouts/header/_default.html.haml' do
|
||||
|
|
@ -115,10 +115,10 @@ module QA
|
|||
sign_out if signed_in?
|
||||
end
|
||||
|
||||
def click_settings_link
|
||||
def click_edit_profile_link
|
||||
retry_until(reload: false) do
|
||||
within_user_menu do
|
||||
click_link 'Settings'
|
||||
click_element(:edit_profile_link)
|
||||
end
|
||||
|
||||
has_text?('User Settings')
|
||||
|
|
|
|||
|
|
@ -15,7 +15,7 @@ module QA
|
|||
end
|
||||
|
||||
def fabricate!
|
||||
Page::Main::Menu.perform(&:click_settings_link)
|
||||
Page::Main::Menu.perform(&:click_edit_profile_link)
|
||||
Page::Profile::Menu.perform(&:click_access_tokens)
|
||||
|
||||
Page::Profile::PersonalAccessTokens.perform do |token_page|
|
||||
|
|
|
|||
|
|
@ -22,7 +22,7 @@ module QA
|
|||
end
|
||||
|
||||
def fabricate!
|
||||
Page::Main::Menu.perform(&:click_settings_link)
|
||||
Page::Main::Menu.perform(&:click_edit_profile_link)
|
||||
Page::Profile::Menu.perform(&:click_ssh_keys)
|
||||
|
||||
Page::Profile::SSHKeys.perform do |profile_page|
|
||||
|
|
|
|||
|
|
@ -48,7 +48,7 @@ module QA
|
|||
|
||||
def enable_2fa_for_user(user)
|
||||
Flow::Login.while_signed_in(as: user) do
|
||||
Page::Main::Menu.perform(&:click_settings_link)
|
||||
Page::Main::Menu.perform(&:click_edit_profile_link)
|
||||
Page::Profile::Menu.perform(&:click_account)
|
||||
Page::Profile::Accounts::Show.perform(&:click_enable_2fa_button)
|
||||
|
||||
|
|
|
|||
|
|
@ -58,7 +58,7 @@ module QA
|
|||
# this is the only test that exercise this UI.
|
||||
# Other tests should use the API for this purpose.
|
||||
Flow::Login.sign_in(as: user)
|
||||
Page::Main::Menu.perform(&:click_settings_link)
|
||||
Page::Main::Menu.perform(&:click_edit_profile_link)
|
||||
Page::Profile::Menu.perform(&:click_account)
|
||||
Page::Profile::Accounts::Show.perform do |show|
|
||||
show.delete_account(user.password)
|
||||
|
|
|
|||
|
|
@ -21,7 +21,7 @@ module QA
|
|||
# Note this context ensures that the example it contains is executed after the example above. Be aware of the order of execution if you add new examples in either context.
|
||||
context 'after adding an ssh key' do
|
||||
it 'can delete an ssh key', testcase: 'https://gitlab.com/gitlab-org/quality/testcases/-/issues/930' do
|
||||
Page::Main::Menu.perform(&:click_settings_link)
|
||||
Page::Main::Menu.perform(&:click_edit_profile_link)
|
||||
Page::Profile::Menu.perform(&:click_ssh_keys)
|
||||
Page::Profile::SSHKeys.perform do |ssh_keys|
|
||||
ssh_keys.remove_key(key.title)
|
||||
|
|
|
|||
|
|
@ -27,7 +27,7 @@ module QA
|
|||
|
||||
Runtime::Browser.visit(ENV['GITLAB_ADDRESS'], Page::Main::Login)
|
||||
Page::Main::Login.perform(&:sign_in_using_credentials)
|
||||
Page::Main::Menu.perform(&:click_settings_link)
|
||||
Page::Main::Menu.perform(&:click_edit_profile_link)
|
||||
Page::Profile::Menu.perform(&:click_access_tokens)
|
||||
|
||||
token_name = 'api-test-token'
|
||||
|
|
|
|||
|
|
@ -3,6 +3,7 @@
|
|||
require 'spec_helper'
|
||||
|
||||
RSpec.describe "Admin::Projects" do
|
||||
include Spec::Support::Helpers::Features::MembersHelpers
|
||||
include Select2Helper
|
||||
|
||||
let(:user) { create :user }
|
||||
|
|
@ -10,8 +11,6 @@ RSpec.describe "Admin::Projects" do
|
|||
let(:current_user) { create(:admin) }
|
||||
|
||||
before do
|
||||
stub_feature_flags(vue_project_members_list: false)
|
||||
|
||||
sign_in(current_user)
|
||||
gitlab_enable_admin_mode_sign_in(current_user)
|
||||
end
|
||||
|
|
@ -93,46 +92,97 @@ RSpec.describe "Admin::Projects" do
|
|||
end
|
||||
end
|
||||
|
||||
describe 'add admin himself to a project' do
|
||||
before do
|
||||
project.add_maintainer(user)
|
||||
stub_feature_flags(invite_members_group_modal: false)
|
||||
end
|
||||
|
||||
it 'adds admin a to a project as developer', :js do
|
||||
visit project_project_members_path(project)
|
||||
|
||||
page.within '.invite-users-form' do
|
||||
select2(current_user.id, from: '#user_ids', multiple: true)
|
||||
select 'Developer', from: 'access_level'
|
||||
context 'when `vue_project_members_list` feature flag is enabled', :js do
|
||||
describe 'admin adds themselves to the project' do
|
||||
before do
|
||||
project.add_maintainer(user)
|
||||
stub_feature_flags(invite_members_group_modal: false)
|
||||
end
|
||||
|
||||
click_button 'Invite'
|
||||
it 'adds admin to the project as developer', :js do
|
||||
visit project_project_members_path(project)
|
||||
|
||||
page.within '.content-list' do
|
||||
expect(page).to have_content(current_user.name)
|
||||
expect(page).to have_content('Developer')
|
||||
page.within '.invite-users-form' do
|
||||
select2(current_user.id, from: '#user_ids', multiple: true)
|
||||
select 'Developer', from: 'access_level'
|
||||
end
|
||||
|
||||
click_button 'Invite'
|
||||
|
||||
expect(find_member_row(current_user)).to have_content('Developer')
|
||||
end
|
||||
end
|
||||
|
||||
describe 'admin removes themselves from the project' do
|
||||
before do
|
||||
project.add_maintainer(user)
|
||||
project.add_developer(current_user)
|
||||
end
|
||||
|
||||
it 'removes admin from the project' do
|
||||
visit project_project_members_path(project)
|
||||
|
||||
expect(find_member_row(current_user)).to have_content('Developer')
|
||||
|
||||
page.within find_member_row(current_user) do
|
||||
click_button 'Leave'
|
||||
end
|
||||
|
||||
page.within('[role="dialog"]') do
|
||||
click_button('Leave')
|
||||
end
|
||||
|
||||
expect(current_path).to match dashboard_projects_path
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
describe 'admin remove himself from a project' do
|
||||
context 'when `vue_project_members_list` feature flag is disabled' do
|
||||
before do
|
||||
project.add_maintainer(user)
|
||||
project.add_developer(current_user)
|
||||
stub_feature_flags(vue_project_members_list: false)
|
||||
end
|
||||
|
||||
it 'removes admin from the project' do
|
||||
visit project_project_members_path(project)
|
||||
|
||||
page.within '.content-list' do
|
||||
expect(page).to have_content(current_user.name)
|
||||
expect(page).to have_content('Developer')
|
||||
describe 'admin adds themselves to the project' do
|
||||
before do
|
||||
project.add_maintainer(user)
|
||||
stub_feature_flags(invite_members_group_modal: false)
|
||||
end
|
||||
|
||||
find(:css, '.content-list li', text: current_user.name).find(:css, 'a.btn-danger').click
|
||||
it 'adds admin to the project as developer', :js do
|
||||
visit project_project_members_path(project)
|
||||
|
||||
expect(page).not_to have_selector(:css, '.content-list')
|
||||
page.within '.invite-users-form' do
|
||||
select2(current_user.id, from: '#user_ids', multiple: true)
|
||||
select 'Developer', from: 'access_level'
|
||||
end
|
||||
|
||||
click_button 'Invite'
|
||||
|
||||
page.within '.content-list' do
|
||||
expect(page).to have_content(current_user.name)
|
||||
expect(page).to have_content('Developer')
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
describe 'admin removes themselves from the project' do
|
||||
before do
|
||||
project.add_maintainer(user)
|
||||
project.add_developer(current_user)
|
||||
end
|
||||
|
||||
it 'removes admin from the project' do
|
||||
visit project_project_members_path(project)
|
||||
|
||||
page.within '.content-list' do
|
||||
expect(page).to have_content(current_user.name)
|
||||
expect(page).to have_content('Developer')
|
||||
end
|
||||
|
||||
find(:css, '.content-list li', text: current_user.name).find(:css, 'a.btn-danger').click
|
||||
|
||||
expect(page).not_to have_selector(:css, '.content-list')
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
|
|
|||
|
|
@ -171,7 +171,7 @@ RSpec.describe 'Admin::Users::User' do
|
|||
it 'logs in as the user when impersonate is clicked' do
|
||||
subject
|
||||
|
||||
expect(page.find(:css, '.header-user .profile-link')['data-user']).to eql(another_user.username)
|
||||
expect(page.find(:css, '[data-testid="user-profile-link"]')['data-user']).to eql(another_user.username)
|
||||
end
|
||||
|
||||
it 'sees impersonation log out icon' do
|
||||
|
|
@ -205,7 +205,7 @@ RSpec.describe 'Admin::Users::User' do
|
|||
it 'logs out of impersonated user back to original user' do
|
||||
subject
|
||||
|
||||
expect(page.find(:css, '.header-user .profile-link')['data-user']).to eq(current_user.username)
|
||||
expect(page.find(:css, '[data-testid="user-profile-link"]')['data-user']).to eq(current_user.username)
|
||||
end
|
||||
|
||||
it 'is redirected back to the impersonated users page in the admin after stopping' do
|
||||
|
|
|
|||
|
|
@ -39,7 +39,7 @@ RSpec.describe 'User visits their profile' do
|
|||
find(:css, '.header-user-dropdown-toggle').click
|
||||
|
||||
page.within ".header-user" do
|
||||
click_link "Profile"
|
||||
click_link user.username
|
||||
end
|
||||
end
|
||||
|
||||
|
|
|
|||
|
|
@ -3,22 +3,36 @@
|
|||
require 'spec_helper'
|
||||
|
||||
RSpec.describe 'Projects > Members > Anonymous user sees members' do
|
||||
include Spec::Support::Helpers::Features::MembersHelpers
|
||||
|
||||
let(:user) { create(:user) }
|
||||
let(:group) { create(:group, :public) }
|
||||
let(:project) { create(:project, :public) }
|
||||
|
||||
before do
|
||||
stub_feature_flags(vue_project_members_list: false)
|
||||
|
||||
project.add_maintainer(user)
|
||||
create(:project_group_link, project: project, group: group)
|
||||
end
|
||||
|
||||
it "anonymous user visits the project's members page and sees the list of members" do
|
||||
visit project_project_members_path(project)
|
||||
context 'when `vue_project_members_list` feature flag is enabled', :js do
|
||||
it "anonymous user visits the project's members page and sees the list of members" do
|
||||
visit project_project_members_path(project)
|
||||
|
||||
expect(current_path).to eq(
|
||||
project_project_members_path(project))
|
||||
expect(page).to have_content(user.name)
|
||||
expect(find_member_row(user)).to have_content(user.name)
|
||||
end
|
||||
end
|
||||
|
||||
context 'when `vue_project_members_list` feature flag is disabled' do
|
||||
before do
|
||||
stub_feature_flags(vue_project_members_list: false)
|
||||
end
|
||||
|
||||
it "anonymous user visits the project's members page and sees the list of members" do
|
||||
visit project_project_members_path(project)
|
||||
|
||||
expect(current_path).to eq(
|
||||
project_project_members_path(project))
|
||||
expect(page).to have_content(user.name)
|
||||
end
|
||||
end
|
||||
end
|
||||
|
|
|
|||
|
|
@ -3,6 +3,8 @@
|
|||
require 'spec_helper'
|
||||
|
||||
RSpec.describe 'Projects members', :js do
|
||||
include Spec::Support::Helpers::Features::MembersHelpers
|
||||
|
||||
let(:user) { create(:user) }
|
||||
let(:developer) { create(:user) }
|
||||
let(:group) { create(:group, :public) }
|
||||
|
|
@ -13,124 +15,223 @@ RSpec.describe 'Projects members', :js do
|
|||
let(:group_requester) { create(:user) }
|
||||
|
||||
before do
|
||||
stub_feature_flags(vue_project_members_list: false)
|
||||
|
||||
project.add_developer(developer)
|
||||
group.add_owner(user)
|
||||
sign_in(user)
|
||||
end
|
||||
|
||||
context 'with a group invitee' do
|
||||
context 'when `vue_project_members_list` feature flag is enabled' do
|
||||
context 'with a group invitee' do
|
||||
before do
|
||||
group_invitee
|
||||
visit project_project_members_path(project)
|
||||
end
|
||||
|
||||
it 'does not appear in the project members page' do
|
||||
expect(members_table).not_to have_content('test2@abc.com')
|
||||
end
|
||||
end
|
||||
|
||||
context 'with a group' do
|
||||
it 'shows group and project members by default' do
|
||||
visit project_project_members_path(project)
|
||||
|
||||
expect(members_table).to have_content(developer.name)
|
||||
expect(members_table).to have_content(user.name)
|
||||
expect(members_table).to have_content(group.name)
|
||||
end
|
||||
|
||||
it 'shows project members only if requested' do
|
||||
visit project_project_members_path(project, with_inherited_permissions: 'exclude')
|
||||
|
||||
expect(members_table).to have_content(developer.name)
|
||||
expect(members_table).not_to have_content(user.name)
|
||||
expect(members_table).not_to have_content(group.name)
|
||||
end
|
||||
|
||||
it 'shows group members only if requested' do
|
||||
visit project_project_members_path(project, with_inherited_permissions: 'only')
|
||||
|
||||
expect(members_table).not_to have_content(developer.name)
|
||||
expect(members_table).to have_content(user.name)
|
||||
expect(members_table).to have_content(group.name)
|
||||
end
|
||||
end
|
||||
|
||||
context 'with a group, a project invitee, and a project requester' do
|
||||
before do
|
||||
group.request_access(group_requester)
|
||||
project.request_access(project_requester)
|
||||
group_invitee
|
||||
project_invitee
|
||||
visit project_project_members_path(project)
|
||||
end
|
||||
|
||||
it 'shows the group owner' do
|
||||
expect(members_table).to have_content(user.name)
|
||||
expect(members_table).to have_content(group.name)
|
||||
end
|
||||
|
||||
it 'shows the project developer' do
|
||||
expect(members_table).to have_content(developer.name)
|
||||
end
|
||||
|
||||
it 'shows the project invitee' do
|
||||
click_link 'Invited'
|
||||
|
||||
expect(members_table).to have_content('test1@abc.com')
|
||||
expect(members_table).not_to have_content('test2@abc.com')
|
||||
end
|
||||
|
||||
it 'shows the project requester' do
|
||||
click_link 'Access requests'
|
||||
|
||||
expect(members_table).to have_content(project_requester.name)
|
||||
expect(members_table).not_to have_content(group_requester.name)
|
||||
end
|
||||
end
|
||||
|
||||
context 'with a group requester' do
|
||||
before do
|
||||
stub_feature_flags(invite_members_group_modal: false)
|
||||
group.request_access(group_requester)
|
||||
visit project_project_members_path(project)
|
||||
end
|
||||
|
||||
it 'does not appear in the project members page' do
|
||||
expect(page).not_to have_link('Access requests')
|
||||
expect(members_table).not_to have_content(group_requester.name)
|
||||
end
|
||||
end
|
||||
|
||||
context 'showing status of members' do
|
||||
it 'shows the status' do
|
||||
create(:user_status, user: user, emoji: 'smirk', message: 'Authoring this object')
|
||||
|
||||
visit project_project_members_path(project)
|
||||
|
||||
expect(first_row).to have_selector('gl-emoji[data-name="smirk"]')
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
context 'when `vue_project_members_list` feature flag is disabled' do
|
||||
before do
|
||||
group_invitee
|
||||
visit project_project_members_path(project)
|
||||
stub_feature_flags(vue_project_members_list: false)
|
||||
end
|
||||
|
||||
it 'does not appear in the project members page' do
|
||||
page.within first('.content-list') do
|
||||
expect(page).not_to have_content('test2@abc.com')
|
||||
context 'with a group invitee' do
|
||||
before do
|
||||
group_invitee
|
||||
visit project_project_members_path(project)
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
context 'with a group' do
|
||||
it 'shows group and project members by default' do
|
||||
visit project_project_members_path(project)
|
||||
|
||||
page.within first('.content-list') do
|
||||
expect(page).to have_content(developer.name)
|
||||
|
||||
expect(page).to have_content(user.name)
|
||||
expect(page).to have_content(group.name)
|
||||
it 'does not appear in the project members page' do
|
||||
page.within first('.content-list') do
|
||||
expect(page).not_to have_content('test2@abc.com')
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
it 'shows project members only if requested' do
|
||||
visit project_project_members_path(project, with_inherited_permissions: 'exclude')
|
||||
context 'with a group' do
|
||||
it 'shows group and project members by default' do
|
||||
visit project_project_members_path(project)
|
||||
|
||||
page.within first('.content-list') do
|
||||
expect(page).to have_content(developer.name)
|
||||
page.within first('.content-list') do
|
||||
expect(page).to have_content(developer.name)
|
||||
|
||||
expect(page).not_to have_content(user.name)
|
||||
expect(page).not_to have_content(group.name)
|
||||
expect(page).to have_content(user.name)
|
||||
expect(page).to have_content(group.name)
|
||||
end
|
||||
end
|
||||
|
||||
it 'shows project members only if requested' do
|
||||
visit project_project_members_path(project, with_inherited_permissions: 'exclude')
|
||||
|
||||
page.within first('.content-list') do
|
||||
expect(page).to have_content(developer.name)
|
||||
|
||||
expect(page).not_to have_content(user.name)
|
||||
expect(page).not_to have_content(group.name)
|
||||
end
|
||||
end
|
||||
|
||||
it 'shows group members only if requested' do
|
||||
visit project_project_members_path(project, with_inherited_permissions: 'only')
|
||||
|
||||
page.within first('.content-list') do
|
||||
expect(page).not_to have_content(developer.name)
|
||||
|
||||
expect(page).to have_content(user.name)
|
||||
expect(page).to have_content(group.name)
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
it 'shows group members only if requested' do
|
||||
visit project_project_members_path(project, with_inherited_permissions: 'only')
|
||||
|
||||
page.within first('.content-list') do
|
||||
expect(page).not_to have_content(developer.name)
|
||||
|
||||
expect(page).to have_content(user.name)
|
||||
expect(page).to have_content(group.name)
|
||||
context 'with a group, a project invitee, and a project requester' do
|
||||
before do
|
||||
group.request_access(group_requester)
|
||||
project.request_access(project_requester)
|
||||
group_invitee
|
||||
project_invitee
|
||||
visit project_project_members_path(project)
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
context 'with a group, a project invitee, and a project requester' do
|
||||
before do
|
||||
group.request_access(group_requester)
|
||||
project.request_access(project_requester)
|
||||
group_invitee
|
||||
project_invitee
|
||||
visit project_project_members_path(project)
|
||||
end
|
||||
it 'shows the group owner' do
|
||||
page.within first('.content-list') do
|
||||
# Group owner
|
||||
expect(page).to have_content(user.name)
|
||||
expect(page).to have_content(group.name)
|
||||
end
|
||||
end
|
||||
|
||||
it 'shows the group owner' do
|
||||
page.within first('.content-list') do
|
||||
# Group owner
|
||||
expect(page).to have_content(user.name)
|
||||
expect(page).to have_content(group.name)
|
||||
it 'shows the project developer' do
|
||||
page.within first('.content-list') do
|
||||
# Project developer
|
||||
expect(page).to have_content(developer.name)
|
||||
end
|
||||
end
|
||||
|
||||
it 'shows the project invitee' do
|
||||
click_link 'Invited'
|
||||
|
||||
page.within first('.content-list') do
|
||||
expect(page).to have_content('test1@abc.com')
|
||||
expect(page).not_to have_content('test2@abc.com')
|
||||
end
|
||||
end
|
||||
|
||||
it 'shows the project requester' do
|
||||
click_link 'Access requests'
|
||||
|
||||
page.within first('.content-list') do
|
||||
expect(page).to have_content(project_requester.name)
|
||||
expect(page).not_to have_content(group_requester.name)
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
it 'shows the project developer' do
|
||||
page.within first('.content-list') do
|
||||
# Project developer
|
||||
expect(page).to have_content(developer.name)
|
||||
context 'with a group requester' do
|
||||
before do
|
||||
stub_feature_flags(invite_members_group_modal: false)
|
||||
group.request_access(group_requester)
|
||||
visit project_project_members_path(project)
|
||||
end
|
||||
|
||||
it 'does not appear in the project members page' do
|
||||
expect(page).not_to have_link('Access requests')
|
||||
page.within first('.content-list') do
|
||||
expect(page).not_to have_content(group_requester.name)
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
it 'shows the project invitee' do
|
||||
click_link 'Invited'
|
||||
context 'showing status of members' do
|
||||
it_behaves_like 'showing user status' do
|
||||
let(:user_with_status) { developer }
|
||||
|
||||
page.within first('.content-list') do
|
||||
expect(page).to have_content('test1@abc.com')
|
||||
expect(page).not_to have_content('test2@abc.com')
|
||||
subject { visit project_project_members_path(project) }
|
||||
end
|
||||
end
|
||||
|
||||
it 'shows the project requester' do
|
||||
click_link 'Access requests'
|
||||
|
||||
page.within first('.content-list') do
|
||||
expect(page).to have_content(project_requester.name)
|
||||
expect(page).not_to have_content(group_requester.name)
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
context 'with a group requester' do
|
||||
before do
|
||||
stub_feature_flags(invite_members_group_modal: false)
|
||||
group.request_access(group_requester)
|
||||
visit project_project_members_path(project)
|
||||
end
|
||||
|
||||
it 'does not appear in the project members page' do
|
||||
expect(page).not_to have_link('Access requests')
|
||||
page.within first('.content-list') do
|
||||
expect(page).not_to have_content(group_requester.name)
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
describe 'showing status of members' do
|
||||
it_behaves_like 'showing user status' do
|
||||
let(:user_with_status) { developer }
|
||||
|
||||
subject { visit project_project_members_path(project) }
|
||||
end
|
||||
end
|
||||
end
|
||||
|
|
|
|||
|
|
@ -3,6 +3,8 @@
|
|||
require 'spec_helper'
|
||||
|
||||
RSpec.describe 'Projects > Members > Groups with access list', :js do
|
||||
include Spec::Support::Helpers::Features::MembersHelpers
|
||||
|
||||
let_it_be(:user) { create(:user) }
|
||||
let_it_be(:group) { create(:group, :public) }
|
||||
let_it_be(:project) { create(:project, :public) }
|
||||
|
|
@ -11,92 +13,176 @@ RSpec.describe 'Projects > Members > Groups with access list', :js do
|
|||
let!(:group_link) { create(:project_group_link, project: project, group: group, **additional_link_attrs) }
|
||||
|
||||
before do
|
||||
stub_feature_flags(vue_project_members_list: false)
|
||||
|
||||
travel_to Time.now.utc.beginning_of_day
|
||||
|
||||
project.add_maintainer(user)
|
||||
sign_in(user)
|
||||
visit project_project_members_path(project)
|
||||
click_groups_tab
|
||||
end
|
||||
|
||||
it 'updates group access level' do
|
||||
click_button group_link.human_access
|
||||
|
||||
page.within '.dropdown-menu' do
|
||||
click_link 'Guest'
|
||||
context 'when `vue_project_members_list` feature flag is enabled' do
|
||||
before do
|
||||
visit project_project_members_path(project)
|
||||
click_groups_tab
|
||||
end
|
||||
|
||||
wait_for_requests
|
||||
it 'updates group access level' do
|
||||
click_button group_link.human_access
|
||||
click_button 'Guest'
|
||||
|
||||
visit project_project_members_path(project)
|
||||
wait_for_requests
|
||||
|
||||
click_groups_tab
|
||||
visit project_project_members_path(project)
|
||||
|
||||
expect(first('.group_member')).to have_content('Guest')
|
||||
end
|
||||
click_groups_tab
|
||||
|
||||
it 'updates expiry date' do
|
||||
expires_at_field = "member_expires_at_#{group.id}"
|
||||
fill_in expires_at_field, with: 3.days.from_now.to_date
|
||||
|
||||
find_field(expires_at_field).native.send_keys :enter
|
||||
wait_for_requests
|
||||
|
||||
page.within(find('li.group_member')) do
|
||||
expect(page).to have_content('Expires in 3 days')
|
||||
expect(find_group_row(group)).to have_content('Guest')
|
||||
end
|
||||
end
|
||||
|
||||
context 'when link has expiry date set' do
|
||||
let(:additional_link_attrs) { { expires_at: 3.days.from_now.to_date } }
|
||||
|
||||
it 'clears expiry date' do
|
||||
page.within(find('li.group_member')) do
|
||||
expect(page).to have_content('Expires in 3 days')
|
||||
|
||||
page.within(find('.js-edit-member-form')) do
|
||||
find('.js-clear-input').click
|
||||
end
|
||||
it 'updates expiry date' do
|
||||
page.within find_group_row(group) do
|
||||
fill_in 'Expiration date', with: 5.days.from_now.to_date
|
||||
find_field('Expiration date').native.send_keys :enter
|
||||
|
||||
wait_for_requests
|
||||
|
||||
expect(page).not_to have_content('Expires in')
|
||||
expect(page).to have_content(/in \d days/)
|
||||
end
|
||||
end
|
||||
|
||||
context 'when link has expiry date set' do
|
||||
let(:additional_link_attrs) { { expires_at: 5.days.from_now.to_date } }
|
||||
|
||||
it 'clears expiry date' do
|
||||
page.within find_group_row(group) do
|
||||
expect(page).to have_content(/in \d days/)
|
||||
|
||||
find('[data-testid="clear-button"]').click
|
||||
|
||||
wait_for_requests
|
||||
|
||||
expect(page).to have_content('No expiration set')
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
it 'deletes group link' do
|
||||
expect(page).to have_content(group.full_name)
|
||||
|
||||
page.within find_group_row(group) do
|
||||
click_button 'Remove group'
|
||||
end
|
||||
|
||||
page.within('[role="dialog"]') do
|
||||
click_button('Remove group')
|
||||
end
|
||||
|
||||
expect(page).not_to have_content(group.full_name)
|
||||
end
|
||||
|
||||
context 'search in existing members' do
|
||||
it 'finds no results' do
|
||||
fill_in_filtered_search 'Search groups', with: 'testing 123'
|
||||
|
||||
click_groups_tab
|
||||
|
||||
expect(page).not_to have_content(group.full_name)
|
||||
end
|
||||
|
||||
it 'finds results' do
|
||||
fill_in_filtered_search 'Search groups', with: group.full_name
|
||||
|
||||
click_groups_tab
|
||||
|
||||
expect(members_table).to have_content(group.full_name)
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
it 'deletes group link' do
|
||||
page.within(first('.group_member')) do
|
||||
accept_confirm { find('.btn-danger').click }
|
||||
context 'when `vue_project_members_list` feature flag is disabled' do
|
||||
before do
|
||||
stub_feature_flags(vue_project_members_list: false)
|
||||
|
||||
visit project_project_members_path(project)
|
||||
click_groups_tab
|
||||
end
|
||||
wait_for_requests
|
||||
|
||||
expect(page).not_to have_selector('.group_member')
|
||||
end
|
||||
it 'updates group access level' do
|
||||
click_button group_link.human_access
|
||||
|
||||
context 'search in existing members' do
|
||||
it 'finds no results' do
|
||||
page.within '.user-search-form' do
|
||||
fill_in 'search_groups', with: 'testing 123'
|
||||
find('.user-search-btn').click
|
||||
page.within '.dropdown-menu' do
|
||||
click_link 'Guest'
|
||||
end
|
||||
|
||||
wait_for_requests
|
||||
|
||||
visit project_project_members_path(project)
|
||||
|
||||
click_groups_tab
|
||||
|
||||
expect(first('.group_member')).to have_content('Guest')
|
||||
end
|
||||
|
||||
it 'updates expiry date' do
|
||||
expires_at_field = "member_expires_at_#{group.id}"
|
||||
fill_in expires_at_field, with: 3.days.from_now.to_date
|
||||
|
||||
find_field(expires_at_field).native.send_keys :enter
|
||||
wait_for_requests
|
||||
|
||||
page.within(find('li.group_member')) do
|
||||
expect(page).to have_content('Expires in 3 days')
|
||||
end
|
||||
end
|
||||
|
||||
context 'when link has expiry date set' do
|
||||
let(:additional_link_attrs) { { expires_at: 3.days.from_now.to_date } }
|
||||
|
||||
it 'clears expiry date' do
|
||||
page.within(find('li.group_member')) do
|
||||
expect(page).to have_content('Expires in 3 days')
|
||||
|
||||
page.within(find('.js-edit-member-form')) do
|
||||
find('.js-clear-input').click
|
||||
end
|
||||
|
||||
wait_for_requests
|
||||
|
||||
expect(page).not_to have_content('Expires in')
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
it 'deletes group link' do
|
||||
page.within(first('.group_member')) do
|
||||
accept_confirm { find('.btn-danger').click }
|
||||
end
|
||||
wait_for_requests
|
||||
|
||||
expect(page).not_to have_selector('.group_member')
|
||||
end
|
||||
|
||||
it 'finds results' do
|
||||
page.within '.user-search-form' do
|
||||
fill_in 'search_groups', with: group.name
|
||||
find('.user-search-btn').click
|
||||
context 'search in existing members' do
|
||||
it 'finds no results' do
|
||||
page.within '.user-search-form' do
|
||||
fill_in 'search_groups', with: 'testing 123'
|
||||
find('.user-search-btn').click
|
||||
end
|
||||
|
||||
click_groups_tab
|
||||
|
||||
expect(page).not_to have_selector('.group_member')
|
||||
end
|
||||
|
||||
click_groups_tab
|
||||
it 'finds results' do
|
||||
page.within '.user-search-form' do
|
||||
fill_in 'search_groups', with: group.name
|
||||
find('.user-search-btn').click
|
||||
end
|
||||
|
||||
expect(page).to have_selector('.group_member', count: 1)
|
||||
click_groups_tab
|
||||
|
||||
expect(page).to have_selector('.group_member', count: 1)
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
|
|
|
|||
|
|
@ -5,12 +5,12 @@ require 'spec_helper'
|
|||
RSpec.describe 'Project > Members > Invite group', :js do
|
||||
include Select2Helper
|
||||
include ActionView::Helpers::DateHelper
|
||||
include Spec::Support::Helpers::Features::MembersHelpers
|
||||
|
||||
let(:maintainer) { create(:user) }
|
||||
|
||||
before do
|
||||
stub_feature_flags(invite_members_group_modal: false)
|
||||
stub_feature_flags(vue_project_members_list: false)
|
||||
end
|
||||
|
||||
describe 'Share with group lock' do
|
||||
|
|
@ -41,21 +41,45 @@ RSpec.describe 'Project > Members > Invite group', :js do
|
|||
context 'when the group has "Share with group lock" disabled' do
|
||||
it_behaves_like 'the project can be shared with groups'
|
||||
|
||||
it 'the project can be shared with another group' do
|
||||
visit project_project_members_path(project)
|
||||
context 'when `vue_project_members_list` feature flag is enabled' do
|
||||
it 'the project can be shared with another group' do
|
||||
visit project_project_members_path(project)
|
||||
|
||||
expect(page).not_to have_link 'Groups'
|
||||
expect(page).not_to have_link 'Groups'
|
||||
|
||||
click_on 'invite-group-tab'
|
||||
click_on 'invite-group-tab'
|
||||
|
||||
select2 group_to_share_with.id, from: '#link_group_id'
|
||||
page.find('body').click
|
||||
find('.btn-success').click
|
||||
select2 group_to_share_with.id, from: '#link_group_id'
|
||||
page.find('body').click
|
||||
find('.btn-success').click
|
||||
|
||||
click_link 'Groups'
|
||||
click_link 'Groups'
|
||||
|
||||
page.within('[data-testid="project-member-groups"]') do
|
||||
expect(page).to have_content(group_to_share_with.name)
|
||||
expect(members_table).to have_content(group_to_share_with.name)
|
||||
end
|
||||
end
|
||||
|
||||
context 'when `vue_project_members_list` feature flag is disabled' do
|
||||
before do
|
||||
stub_feature_flags(vue_project_members_list: false)
|
||||
end
|
||||
|
||||
it 'the project can be shared with another group' do
|
||||
visit project_project_members_path(project)
|
||||
|
||||
expect(page).not_to have_link 'Groups'
|
||||
|
||||
click_on 'invite-group-tab'
|
||||
|
||||
select2 group_to_share_with.id, from: '#link_group_id'
|
||||
page.find('body').click
|
||||
find('.btn-success').click
|
||||
|
||||
click_link 'Groups'
|
||||
|
||||
page.within('[data-testid="project-member-groups"]') do
|
||||
expect(page).to have_content(group_to_share_with.name)
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
|
@ -122,7 +146,7 @@ RSpec.describe 'Project > Members > Invite group', :js do
|
|||
freeze_time { example.run }
|
||||
end
|
||||
|
||||
before do
|
||||
def setup
|
||||
project.add_maintainer(maintainer)
|
||||
group.add_guest(maintainer)
|
||||
sign_in(maintainer)
|
||||
|
|
@ -133,20 +157,37 @@ RSpec.describe 'Project > Members > Invite group', :js do
|
|||
|
||||
select2 group.id, from: '#link_group_id'
|
||||
|
||||
fill_in 'expires_at_groups', with: (Time.now + 4.5.days).strftime('%Y-%m-%d')
|
||||
fill_in 'expires_at_groups', with: 5.days.from_now.strftime('%Y-%m-%d')
|
||||
click_on 'invite-group-tab'
|
||||
find('.btn-success').click
|
||||
end
|
||||
|
||||
it 'the group link shows the expiration time with a warning class' do
|
||||
click_link 'Groups'
|
||||
context 'when `vue_project_members_list` feature flag is enabled' do
|
||||
it 'the group link shows the expiration time with a warning class' do
|
||||
setup
|
||||
click_link 'Groups'
|
||||
|
||||
page.within('[data-testid="project-member-groups"]') do
|
||||
# Using distance_of_time_in_words_to_now because it is not the same as
|
||||
# subtraction, and this way avoids time zone issues as well
|
||||
expires_in_text = distance_of_time_in_words_to_now(project.project_group_links.first.expires_at)
|
||||
expect(page).to have_content(expires_in_text)
|
||||
expect(page).to have_selector('.text-warning')
|
||||
expect(find_group_row(group)).to have_content(/in \d days/)
|
||||
expect(find_group_row(group)).to have_selector('.gl-text-orange-500')
|
||||
end
|
||||
end
|
||||
|
||||
context 'when `vue_project_members_list` feature flag is disabled' do
|
||||
before do
|
||||
stub_feature_flags(vue_project_members_list: false)
|
||||
end
|
||||
|
||||
it 'the group link shows the expiration time with a warning class' do
|
||||
setup
|
||||
click_link 'Groups'
|
||||
|
||||
page.within('[data-testid="project-member-groups"]') do
|
||||
# Using distance_of_time_in_words_to_now because it is not the same as
|
||||
# subtraction, and this way avoids time zone issues as well
|
||||
expires_in_text = distance_of_time_in_words_to_now(project.project_group_links.first.expires_at)
|
||||
expect(page).to have_content(expires_in_text)
|
||||
expect(page).to have_selector('.text-warning')
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
|
|
|||
|
|
@ -4,7 +4,6 @@ require 'spec_helper'
|
|||
|
||||
RSpec.describe 'Project members list' do
|
||||
include Select2Helper
|
||||
include Spec::Support::Helpers::Features::ListRowsHelpers
|
||||
|
||||
let(:user1) { create(:user, name: 'John Doe') }
|
||||
let(:user2) { create(:user, name: 'Mary Jane') }
|
||||
|
|
@ -13,110 +12,215 @@ RSpec.describe 'Project members list' do
|
|||
|
||||
before do
|
||||
stub_feature_flags(invite_members_group_modal: false)
|
||||
stub_feature_flags(vue_project_members_list: false)
|
||||
|
||||
sign_in(user1)
|
||||
group.add_owner(user1)
|
||||
end
|
||||
|
||||
it 'pushes `vue_project_members_list` feature flag to the frontend' do
|
||||
visit_members_page
|
||||
context 'when `vue_project_members_list` feature flag is enabled', :js do
|
||||
include Spec::Support::Helpers::Features::MembersHelpers
|
||||
|
||||
expect(page).to have_pushed_frontend_feature_flags(vueProjectMembersList: false)
|
||||
end
|
||||
it 'pushes `vue_project_members_list` feature flag to the frontend' do
|
||||
visit_members_page
|
||||
|
||||
it 'show members from project and group' do
|
||||
project.add_developer(user2)
|
||||
|
||||
visit_members_page
|
||||
|
||||
expect(first_row.text).to include(user1.name)
|
||||
expect(second_row.text).to include(user2.name)
|
||||
end
|
||||
|
||||
it 'show user once if member of both group and project' do
|
||||
project.add_developer(user1)
|
||||
|
||||
visit_members_page
|
||||
|
||||
expect(first_row.text).to include(user1.name)
|
||||
expect(second_row).to be_blank
|
||||
end
|
||||
|
||||
it 'update user access level', :js do
|
||||
project.add_developer(user2)
|
||||
|
||||
visit_members_page
|
||||
|
||||
page.within(second_row) do
|
||||
click_button('Developer')
|
||||
click_link('Reporter')
|
||||
|
||||
expect(page).to have_button('Reporter')
|
||||
end
|
||||
end
|
||||
|
||||
it 'add user to project', :js do
|
||||
visit_members_page
|
||||
|
||||
add_user(user2.id, 'Reporter')
|
||||
|
||||
page.within(second_row) do
|
||||
expect(page).to have_content(user2.name)
|
||||
expect(page).to have_button('Reporter')
|
||||
end
|
||||
end
|
||||
|
||||
it 'remove user from project', :js do
|
||||
other_user = create(:user)
|
||||
project.add_developer(other_user)
|
||||
|
||||
visit_members_page
|
||||
|
||||
# Open modal
|
||||
find(:css, 'li.project_member', text: other_user.name).find(:css, 'button.btn-danger').click
|
||||
|
||||
expect(page).to have_unchecked_field 'Also unassign this user from related issues and merge requests'
|
||||
|
||||
click_on('Remove member')
|
||||
|
||||
wait_for_requests
|
||||
|
||||
expect(page).not_to have_content(other_user.name)
|
||||
expect(project.users).not_to include(other_user)
|
||||
end
|
||||
|
||||
it 'invite user to project', :js do
|
||||
visit_members_page
|
||||
|
||||
add_user('test@example.com', 'Reporter')
|
||||
|
||||
click_link 'Invited'
|
||||
|
||||
page.within(first_row) do
|
||||
expect(page).to have_content('test@example.com')
|
||||
expect(page).to have_content('Invited')
|
||||
expect(page).to have_button('Reporter')
|
||||
end
|
||||
end
|
||||
|
||||
context 'project bots' do
|
||||
let(:project_bot) { create(:user, :project_bot, name: 'project_bot') }
|
||||
|
||||
before do
|
||||
project.add_maintainer(project_bot)
|
||||
expect(page).to have_pushed_frontend_feature_flags(vueProjectMembersList: true)
|
||||
end
|
||||
|
||||
it 'does not show form used to change roles and "Expiration date" or the remove user button' do
|
||||
project_member = project.project_members.find_by(user_id: project_bot.id)
|
||||
it 'show members from project and group' do
|
||||
project.add_developer(user2)
|
||||
|
||||
visit_members_page
|
||||
|
||||
expect(page).not_to have_selector("#edit_project_member_#{project_member.id}")
|
||||
expect(page).to have_no_selector("#project_member_#{project_member.id} .btn-danger")
|
||||
expect(first_row).to have_content(user1.name)
|
||||
expect(second_row).to have_content(user2.name)
|
||||
end
|
||||
|
||||
it 'show user once if member of both group and project' do
|
||||
project.add_developer(user1)
|
||||
|
||||
visit_members_page
|
||||
|
||||
expect(first_row).to have_content(user1.name)
|
||||
expect(second_row).to be_blank
|
||||
end
|
||||
|
||||
it 'update user access level', :js do
|
||||
project.add_developer(user2)
|
||||
|
||||
visit_members_page
|
||||
|
||||
page.within find_member_row(user2) do
|
||||
click_button('Developer')
|
||||
click_button('Reporter')
|
||||
|
||||
expect(page).to have_button('Reporter')
|
||||
end
|
||||
end
|
||||
|
||||
it 'add user to project', :js do
|
||||
visit_members_page
|
||||
|
||||
add_user(user2.id, 'Reporter')
|
||||
|
||||
page.within find_member_row(user2) do
|
||||
expect(page).to have_button('Reporter')
|
||||
end
|
||||
end
|
||||
|
||||
it 'remove user from project', :js do
|
||||
other_user = create(:user)
|
||||
project.add_developer(other_user)
|
||||
|
||||
visit_members_page
|
||||
|
||||
# Open modal
|
||||
page.within find_member_row(other_user) do
|
||||
click_button 'Remove member'
|
||||
end
|
||||
|
||||
page.within('[role="dialog"]') do
|
||||
expect(page).to have_unchecked_field 'Also unassign this user from related issues and merge requests'
|
||||
click_button('Remove member')
|
||||
end
|
||||
|
||||
wait_for_requests
|
||||
|
||||
expect(members_table).not_to have_content(other_user.name)
|
||||
end
|
||||
|
||||
it 'invite user to project', :js do
|
||||
visit_members_page
|
||||
|
||||
add_user('test@example.com', 'Reporter')
|
||||
|
||||
click_link 'Invited'
|
||||
|
||||
page.within find_invited_member_row('test@example.com') do
|
||||
expect(page).to have_button('Reporter')
|
||||
end
|
||||
end
|
||||
|
||||
context 'project bots' do
|
||||
let(:project_bot) { create(:user, :project_bot, name: 'project_bot') }
|
||||
|
||||
before do
|
||||
project.add_maintainer(project_bot)
|
||||
end
|
||||
|
||||
it 'does not show form used to change roles and "Expiration date" or the remove user button' do
|
||||
visit_members_page
|
||||
|
||||
page.within find_member_row(project_bot) do
|
||||
expect(page).not_to have_button('Maintainer')
|
||||
expect(page).to have_field('Expiration date', disabled: true)
|
||||
expect(page).not_to have_button('Remove member')
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
context 'when `vue_project_members_list` feature flag is disabled' do
|
||||
include Spec::Support::Helpers::Features::ListRowsHelpers
|
||||
|
||||
before do
|
||||
stub_feature_flags(vue_project_members_list: false)
|
||||
end
|
||||
|
||||
it 'show members from project and group' do
|
||||
project.add_developer(user2)
|
||||
|
||||
visit_members_page
|
||||
|
||||
expect(first_row.text).to include(user1.name)
|
||||
expect(second_row.text).to include(user2.name)
|
||||
end
|
||||
|
||||
it 'show user once if member of both group and project' do
|
||||
project.add_developer(user1)
|
||||
|
||||
visit_members_page
|
||||
|
||||
expect(first_row.text).to include(user1.name)
|
||||
expect(second_row).to be_blank
|
||||
end
|
||||
|
||||
it 'update user access level', :js do
|
||||
project.add_developer(user2)
|
||||
|
||||
visit_members_page
|
||||
|
||||
page.within(second_row) do
|
||||
click_button('Developer')
|
||||
click_link('Reporter')
|
||||
|
||||
expect(page).to have_button('Reporter')
|
||||
end
|
||||
end
|
||||
|
||||
it 'add user to project', :js do
|
||||
visit_members_page
|
||||
|
||||
add_user(user2.id, 'Reporter')
|
||||
|
||||
page.within(second_row) do
|
||||
expect(page).to have_content(user2.name)
|
||||
expect(page).to have_button('Reporter')
|
||||
end
|
||||
end
|
||||
|
||||
it 'remove user from project', :js do
|
||||
other_user = create(:user)
|
||||
project.add_developer(other_user)
|
||||
|
||||
visit_members_page
|
||||
|
||||
# Open modal
|
||||
find(:css, 'li.project_member', text: other_user.name).find(:css, 'button.btn-danger').click
|
||||
|
||||
expect(page).to have_unchecked_field 'Also unassign this user from related issues and merge requests'
|
||||
|
||||
click_on('Remove member')
|
||||
|
||||
wait_for_requests
|
||||
|
||||
expect(page).not_to have_content(other_user.name)
|
||||
expect(project.users).not_to include(other_user)
|
||||
end
|
||||
|
||||
it 'invite user to project', :js do
|
||||
visit_members_page
|
||||
|
||||
add_user('test@example.com', 'Reporter')
|
||||
|
||||
click_link 'Invited'
|
||||
|
||||
page.within(first_row) do
|
||||
expect(page).to have_content('test@example.com')
|
||||
expect(page).to have_content('Invited')
|
||||
expect(page).to have_button('Reporter')
|
||||
end
|
||||
end
|
||||
|
||||
context 'project bots' do
|
||||
let(:project_bot) { create(:user, :project_bot, name: 'project_bot') }
|
||||
|
||||
before do
|
||||
project.add_maintainer(project_bot)
|
||||
end
|
||||
|
||||
it 'does not show form used to change roles and "Expiration date" or the remove user button' do
|
||||
project_member = project.project_members.find_by(user_id: project_bot.id)
|
||||
|
||||
visit_members_page
|
||||
|
||||
expect(page).not_to have_selector("#edit_project_member_#{project_member.id}")
|
||||
expect(page).to have_no_selector("#project_member_#{project_member.id} .btn-danger")
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
private
|
||||
|
||||
def add_user(id, role)
|
||||
page.within ".invite-users-form" do
|
||||
select2(id, from: "#user_ids", multiple: true)
|
||||
|
|
|
|||
|
|
@ -5,65 +5,120 @@ require 'spec_helper'
|
|||
RSpec.describe 'Projects > Members > Maintainer adds member with expiration date', :js do
|
||||
include Select2Helper
|
||||
include ActiveSupport::Testing::TimeHelpers
|
||||
include Spec::Support::Helpers::Features::MembersHelpers
|
||||
|
||||
let_it_be(:maintainer) { create(:user) }
|
||||
let_it_be(:project) { create(:project) }
|
||||
let(:new_member) { create(:user) }
|
||||
|
||||
before do
|
||||
stub_feature_flags(vue_project_members_list: false)
|
||||
|
||||
travel_to Time.now.utc.beginning_of_day
|
||||
|
||||
project.add_maintainer(maintainer)
|
||||
sign_in(maintainer)
|
||||
end
|
||||
|
||||
it 'expiration date is displayed in the members list' do
|
||||
stub_feature_flags(invite_members_group_modal: false)
|
||||
context 'when `vue_project_members_list` feature flag is enabled' do
|
||||
it 'expiration date is displayed in the members list' do
|
||||
stub_feature_flags(invite_members_group_modal: false)
|
||||
|
||||
visit project_project_members_path(project)
|
||||
visit project_project_members_path(project)
|
||||
|
||||
page.within '.invite-users-form' do
|
||||
select2(new_member.id, from: '#user_ids', multiple: true)
|
||||
page.within '.invite-users-form' do
|
||||
select2(new_member.id, from: '#user_ids', multiple: true)
|
||||
|
||||
fill_in 'expires_at', with: 3.days.from_now.to_date
|
||||
find_field('expires_at').native.send_keys :enter
|
||||
fill_in 'expires_at', with: 5.days.from_now.to_date
|
||||
find_field('expires_at').native.send_keys :enter
|
||||
|
||||
click_on 'Invite'
|
||||
click_on 'Invite'
|
||||
end
|
||||
|
||||
page.within find_member_row(new_member) do
|
||||
expect(page).to have_content(/in \d days/)
|
||||
end
|
||||
end
|
||||
|
||||
page.within "#project_member_#{project_member_id}" do
|
||||
expect(page).to have_content('Expires in 3 days')
|
||||
it 'changes expiration date' do
|
||||
project.team.add_users([new_member.id], :developer, expires_at: 3.days.from_now.to_date)
|
||||
visit project_project_members_path(project)
|
||||
|
||||
page.within find_member_row(new_member) do
|
||||
fill_in 'Expiration date', with: 5.days.from_now.to_date
|
||||
find_field('Expiration date').native.send_keys :enter
|
||||
|
||||
wait_for_requests
|
||||
|
||||
expect(page).to have_content(/in \d days/)
|
||||
end
|
||||
end
|
||||
|
||||
it 'clears expiration date' do
|
||||
project.team.add_users([new_member.id], :developer, expires_at: 5.days.from_now.to_date)
|
||||
visit project_project_members_path(project)
|
||||
|
||||
page.within find_member_row(new_member) do
|
||||
expect(page).to have_content(/in \d days/)
|
||||
|
||||
find('[data-testid="clear-button"]').click
|
||||
|
||||
wait_for_requests
|
||||
|
||||
expect(page).to have_content('No expiration set')
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
it 'changes expiration date' do
|
||||
project.team.add_users([new_member.id], :developer, expires_at: Date.today.to_date)
|
||||
visit project_project_members_path(project)
|
||||
|
||||
page.within "#project_member_#{project_member_id}" do
|
||||
fill_in 'Expiration date', with: 3.days.from_now.to_date
|
||||
find_field('Expiration date').native.send_keys :enter
|
||||
|
||||
wait_for_requests
|
||||
|
||||
expect(page).to have_content('Expires in 3 days')
|
||||
context 'when `vue_project_members_list` feature flag is disabled' do
|
||||
before do
|
||||
stub_feature_flags(vue_project_members_list: false)
|
||||
end
|
||||
end
|
||||
|
||||
it 'clears expiration date' do
|
||||
project.team.add_users([new_member.id], :developer, expires_at: 3.days.from_now.to_date)
|
||||
visit project_project_members_path(project)
|
||||
it 'expiration date is displayed in the members list' do
|
||||
stub_feature_flags(invite_members_group_modal: false)
|
||||
|
||||
page.within "#project_member_#{project_member_id}" do
|
||||
expect(page).to have_content('Expires in 3 days')
|
||||
visit project_project_members_path(project)
|
||||
|
||||
find('.js-clear-input').click
|
||||
page.within '.invite-users-form' do
|
||||
select2(new_member.id, from: '#user_ids', multiple: true)
|
||||
|
||||
wait_for_requests
|
||||
fill_in 'expires_at', with: 3.days.from_now.to_date
|
||||
find_field('expires_at').native.send_keys :enter
|
||||
|
||||
expect(page).not_to have_content('Expires in')
|
||||
click_on 'Invite'
|
||||
end
|
||||
|
||||
page.within "#project_member_#{project_member_id}" do
|
||||
expect(page).to have_content('Expires in 3 days')
|
||||
end
|
||||
end
|
||||
|
||||
it 'changes expiration date' do
|
||||
project.team.add_users([new_member.id], :developer, expires_at: 1.day.from_now.to_date)
|
||||
visit project_project_members_path(project)
|
||||
|
||||
page.within "#project_member_#{project_member_id}" do
|
||||
fill_in 'Expiration date', with: 3.days.from_now.to_date
|
||||
find_field('Expiration date').native.send_keys :enter
|
||||
|
||||
wait_for_requests
|
||||
|
||||
expect(page).to have_content('Expires in 3 days')
|
||||
end
|
||||
end
|
||||
|
||||
it 'clears expiration date' do
|
||||
project.team.add_users([new_member.id], :developer, expires_at: 3.days.from_now.to_date)
|
||||
visit project_project_members_path(project)
|
||||
|
||||
page.within "#project_member_#{project_member_id}" do
|
||||
expect(page).to have_content('Expires in 3 days')
|
||||
|
||||
find('.js-clear-input').click
|
||||
|
||||
wait_for_requests
|
||||
|
||||
expect(page).not_to have_content('Expires in')
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
|
|
|
|||
|
|
@ -3,89 +3,180 @@
|
|||
require 'spec_helper'
|
||||
|
||||
RSpec.describe 'Projects > Members > Sorting' do
|
||||
include Spec::Support::Helpers::Features::MembersHelpers
|
||||
|
||||
let(:maintainer) { create(:user, name: 'John Doe') }
|
||||
let(:developer) { create(:user, name: 'Mary Jane', last_sign_in_at: 5.days.ago) }
|
||||
let(:project) { create(:project, namespace: maintainer.namespace, creator: maintainer) }
|
||||
|
||||
before do
|
||||
stub_feature_flags(vue_project_members_list: false)
|
||||
|
||||
create(:project_member, :developer, user: developer, project: project, created_at: 3.days.ago)
|
||||
|
||||
sign_in(maintainer)
|
||||
end
|
||||
|
||||
it 'sorts alphabetically by default' do
|
||||
visit_members_list(sort: nil)
|
||||
context 'when `vue_project_members_list` feature flag is enabled', :js do
|
||||
it 'sorts by account by default' do
|
||||
visit_members_list(sort: nil)
|
||||
|
||||
expect(first_member).to include(maintainer.name)
|
||||
expect(second_member).to include(developer.name)
|
||||
expect(page).to have_css('.qa-user-sort-dropdown .dropdown-toggle-text', text: 'Name, ascending')
|
||||
expect(first_row).to have_content(maintainer.name)
|
||||
expect(second_row).to have_content(developer.name)
|
||||
|
||||
expect_sort_by('Account', :asc)
|
||||
end
|
||||
|
||||
it 'sorts by max role ascending' do
|
||||
visit_members_list(sort: :access_level_asc)
|
||||
|
||||
expect(first_row).to have_content(developer.name)
|
||||
expect(second_row).to have_content(maintainer.name)
|
||||
|
||||
expect_sort_by('Max role', :asc)
|
||||
end
|
||||
|
||||
it 'sorts by max role descending' do
|
||||
visit_members_list(sort: :access_level_desc)
|
||||
|
||||
expect(first_row).to have_content(maintainer.name)
|
||||
expect(second_row).to have_content(developer.name)
|
||||
|
||||
expect_sort_by('Max role', :desc)
|
||||
end
|
||||
|
||||
it 'sorts by access granted ascending' do
|
||||
visit_members_list(sort: :last_joined)
|
||||
|
||||
expect(first_row).to have_content(maintainer.name)
|
||||
expect(second_row).to have_content(developer.name)
|
||||
|
||||
expect_sort_by('Access granted', :asc)
|
||||
end
|
||||
|
||||
it 'sorts by access granted descending' do
|
||||
visit_members_list(sort: :oldest_joined)
|
||||
|
||||
expect(first_row).to have_content(developer.name)
|
||||
expect(second_row).to have_content(maintainer.name)
|
||||
|
||||
expect_sort_by('Access granted', :desc)
|
||||
end
|
||||
|
||||
it 'sorts by account ascending' do
|
||||
visit_members_list(sort: :name_asc)
|
||||
|
||||
expect(first_row).to have_content(maintainer.name)
|
||||
expect(second_row).to have_content(developer.name)
|
||||
|
||||
expect_sort_by('Account', :asc)
|
||||
end
|
||||
|
||||
it 'sorts by account descending' do
|
||||
visit_members_list(sort: :name_desc)
|
||||
|
||||
expect(first_row).to have_content(developer.name)
|
||||
expect(second_row).to have_content(maintainer.name)
|
||||
|
||||
expect_sort_by('Account', :desc)
|
||||
end
|
||||
|
||||
it 'sorts by last sign-in ascending', :clean_gitlab_redis_shared_state do
|
||||
visit_members_list(sort: :recent_sign_in)
|
||||
|
||||
expect(first_row).to have_content(maintainer.name)
|
||||
expect(second_row).to have_content(developer.name)
|
||||
|
||||
expect_sort_by('Last sign-in', :asc)
|
||||
end
|
||||
|
||||
it 'sorts by last sign-in descending', :clean_gitlab_redis_shared_state do
|
||||
visit_members_list(sort: :oldest_sign_in)
|
||||
|
||||
expect(first_row).to have_content(developer.name)
|
||||
expect(second_row).to have_content(maintainer.name)
|
||||
|
||||
expect_sort_by('Last sign-in', :desc)
|
||||
end
|
||||
end
|
||||
|
||||
it 'sorts by access level ascending' do
|
||||
visit_members_list(sort: :access_level_asc)
|
||||
context 'when `vue_project_members_list` feature flag is disabled' do
|
||||
before do
|
||||
stub_feature_flags(vue_project_members_list: false)
|
||||
end
|
||||
|
||||
expect(first_member).to include(developer.name)
|
||||
expect(second_member).to include(maintainer.name)
|
||||
expect(page).to have_css('.qa-user-sort-dropdown .dropdown-toggle-text', text: 'Access level, ascending')
|
||||
it 'sorts alphabetically by default' do
|
||||
visit_members_list(sort: nil)
|
||||
|
||||
expect(first_member).to include(maintainer.name)
|
||||
expect(second_member).to include(developer.name)
|
||||
expect(page).to have_css('.qa-user-sort-dropdown .dropdown-toggle-text', text: 'Name, ascending')
|
||||
end
|
||||
|
||||
it 'sorts by access level ascending' do
|
||||
visit_members_list(sort: :access_level_asc)
|
||||
|
||||
expect(first_member).to include(developer.name)
|
||||
expect(second_member).to include(maintainer.name)
|
||||
expect(page).to have_css('.qa-user-sort-dropdown .dropdown-toggle-text', text: 'Access level, ascending')
|
||||
end
|
||||
|
||||
it 'sorts by access level descending' do
|
||||
visit_members_list(sort: :access_level_desc)
|
||||
|
||||
expect(first_member).to include(maintainer.name)
|
||||
expect(second_member).to include(developer.name)
|
||||
expect(page).to have_css('.qa-user-sort-dropdown .dropdown-toggle-text', text: 'Access level, descending')
|
||||
end
|
||||
|
||||
it 'sorts by last joined' do
|
||||
visit_members_list(sort: :last_joined)
|
||||
|
||||
expect(first_member).to include(maintainer.name)
|
||||
expect(second_member).to include(developer.name)
|
||||
expect(page).to have_css('.qa-user-sort-dropdown .dropdown-toggle-text', text: 'Last joined')
|
||||
end
|
||||
|
||||
it 'sorts by oldest joined' do
|
||||
visit_members_list(sort: :oldest_joined)
|
||||
|
||||
expect(first_member).to include(developer.name)
|
||||
expect(second_member).to include(maintainer.name)
|
||||
expect(page).to have_css('.qa-user-sort-dropdown .dropdown-toggle-text', text: 'Oldest joined')
|
||||
end
|
||||
|
||||
it 'sorts by name ascending' do
|
||||
visit_members_list(sort: :name_asc)
|
||||
|
||||
expect(first_member).to include(maintainer.name)
|
||||
expect(second_member).to include(developer.name)
|
||||
expect(page).to have_css('.qa-user-sort-dropdown .dropdown-toggle-text', text: 'Name, ascending')
|
||||
end
|
||||
|
||||
it 'sorts by name descending' do
|
||||
visit_members_list(sort: :name_desc)
|
||||
|
||||
expect(first_member).to include(developer.name)
|
||||
expect(second_member).to include(maintainer.name)
|
||||
expect(page).to have_css('.qa-user-sort-dropdown .dropdown-toggle-text', text: 'Name, descending')
|
||||
end
|
||||
|
||||
it 'sorts by recent sign in', :clean_gitlab_redis_shared_state do
|
||||
visit_members_list(sort: :recent_sign_in)
|
||||
|
||||
expect(first_member).to include(maintainer.name)
|
||||
expect(second_member).to include(developer.name)
|
||||
expect(page).to have_css('.qa-user-sort-dropdown .dropdown-toggle-text', text: 'Recent sign in')
|
||||
end
|
||||
|
||||
it 'sorts by oldest sign in', :clean_gitlab_redis_shared_state do
|
||||
visit_members_list(sort: :oldest_sign_in)
|
||||
|
||||
expect(first_member).to include(developer.name)
|
||||
expect(second_member).to include(maintainer.name)
|
||||
expect(page).to have_css('.qa-user-sort-dropdown .dropdown-toggle-text', text: 'Oldest sign in')
|
||||
end
|
||||
end
|
||||
|
||||
it 'sorts by access level descending' do
|
||||
visit_members_list(sort: :access_level_desc)
|
||||
|
||||
expect(first_member).to include(maintainer.name)
|
||||
expect(second_member).to include(developer.name)
|
||||
expect(page).to have_css('.qa-user-sort-dropdown .dropdown-toggle-text', text: 'Access level, descending')
|
||||
end
|
||||
|
||||
it 'sorts by last joined' do
|
||||
visit_members_list(sort: :last_joined)
|
||||
|
||||
expect(first_member).to include(maintainer.name)
|
||||
expect(second_member).to include(developer.name)
|
||||
expect(page).to have_css('.qa-user-sort-dropdown .dropdown-toggle-text', text: 'Last joined')
|
||||
end
|
||||
|
||||
it 'sorts by oldest joined' do
|
||||
visit_members_list(sort: :oldest_joined)
|
||||
|
||||
expect(first_member).to include(developer.name)
|
||||
expect(second_member).to include(maintainer.name)
|
||||
expect(page).to have_css('.qa-user-sort-dropdown .dropdown-toggle-text', text: 'Oldest joined')
|
||||
end
|
||||
|
||||
it 'sorts by name ascending' do
|
||||
visit_members_list(sort: :name_asc)
|
||||
|
||||
expect(first_member).to include(maintainer.name)
|
||||
expect(second_member).to include(developer.name)
|
||||
expect(page).to have_css('.qa-user-sort-dropdown .dropdown-toggle-text', text: 'Name, ascending')
|
||||
end
|
||||
|
||||
it 'sorts by name descending' do
|
||||
visit_members_list(sort: :name_desc)
|
||||
|
||||
expect(first_member).to include(developer.name)
|
||||
expect(second_member).to include(maintainer.name)
|
||||
expect(page).to have_css('.qa-user-sort-dropdown .dropdown-toggle-text', text: 'Name, descending')
|
||||
end
|
||||
|
||||
it 'sorts by recent sign in', :clean_gitlab_redis_shared_state do
|
||||
visit_members_list(sort: :recent_sign_in)
|
||||
|
||||
expect(first_member).to include(maintainer.name)
|
||||
expect(second_member).to include(developer.name)
|
||||
expect(page).to have_css('.qa-user-sort-dropdown .dropdown-toggle-text', text: 'Recent sign in')
|
||||
end
|
||||
|
||||
it 'sorts by oldest sign in', :clean_gitlab_redis_shared_state do
|
||||
visit_members_list(sort: :oldest_sign_in)
|
||||
|
||||
expect(first_member).to include(developer.name)
|
||||
expect(second_member).to include(maintainer.name)
|
||||
expect(page).to have_css('.qa-user-sort-dropdown .dropdown-toggle-text', text: 'Oldest sign in')
|
||||
end
|
||||
private
|
||||
|
||||
def visit_members_list(sort:)
|
||||
visit project_project_members_path(project, sort: sort)
|
||||
|
|
@ -98,4 +189,11 @@ RSpec.describe 'Projects > Members > Sorting' do
|
|||
def second_member
|
||||
page.all('ul.content-list > li').last.text
|
||||
end
|
||||
|
||||
def expect_sort_by(text, sort_direction)
|
||||
within('[data-testid="members-sort-dropdown"]') do
|
||||
expect(page).to have_css('button[aria-haspopup="true"]', text: text)
|
||||
expect(page).to have_button("Sorting Direction: #{sort_direction == :asc ? 'Ascending' : 'Descending'}")
|
||||
end
|
||||
end
|
||||
end
|
||||
|
|
|
|||
|
|
@ -3,6 +3,7 @@
|
|||
require 'spec_helper'
|
||||
|
||||
RSpec.describe 'Projects > Members > Tabs' do
|
||||
include Spec::Support::Helpers::Features::MembersHelpers
|
||||
using RSpec::Parameterized::TableSyntax
|
||||
|
||||
let_it_be(:user) { create(:user) }
|
||||
|
|
@ -19,56 +20,93 @@ RSpec.describe 'Projects > Members > Tabs' do
|
|||
end
|
||||
end
|
||||
|
||||
before do
|
||||
stub_feature_flags(vue_project_members_list: false)
|
||||
allow(Kaminari.config).to receive(:default_per_page).and_return(1)
|
||||
|
||||
sign_in(user)
|
||||
visit project_project_members_path(project)
|
||||
end
|
||||
|
||||
where(:tab, :count) do
|
||||
'Members' | 3
|
||||
'Invited' | 2
|
||||
'Groups' | 2
|
||||
'Access requests' | 2
|
||||
end
|
||||
|
||||
with_them do
|
||||
it "renders #{params[:tab]} tab" do
|
||||
expect(page).to have_selector('.nav-link', text: "#{tab} #{count}")
|
||||
end
|
||||
end
|
||||
|
||||
context 'displays "Members" tab by default' do
|
||||
it_behaves_like 'active "Members" tab'
|
||||
end
|
||||
|
||||
context 'when searching "Groups"', :js do
|
||||
context 'tabs' do
|
||||
before do
|
||||
click_link 'Groups'
|
||||
sign_in(user)
|
||||
visit project_project_members_path(project)
|
||||
end
|
||||
|
||||
page.within '[data-testid="group-link-search-form"]' do
|
||||
fill_in 'search_groups', with: 'group'
|
||||
find('button[type="submit"]').click
|
||||
where(:tab, :count) do
|
||||
'Members' | 3
|
||||
'Invited' | 2
|
||||
'Groups' | 2
|
||||
'Access requests' | 2
|
||||
end
|
||||
|
||||
with_them do
|
||||
it "renders #{params[:tab]} tab" do
|
||||
expect(page).to have_selector('.nav-link', text: "#{tab} #{count}")
|
||||
end
|
||||
end
|
||||
|
||||
it 'displays "Groups" tab' do
|
||||
expect(page).to have_selector('.nav-link.active', text: 'Groups')
|
||||
context 'displays "Members" tab by default' do
|
||||
it_behaves_like 'active "Members" tab'
|
||||
end
|
||||
end
|
||||
|
||||
context 'when `vue_project_members_list` feature flag is enabled' do
|
||||
before do
|
||||
sign_in(user)
|
||||
visit project_project_members_path(project)
|
||||
end
|
||||
|
||||
context 'and then searching "Members"' do
|
||||
context 'when searching "Groups"', :js do
|
||||
before do
|
||||
click_link 'Members 3'
|
||||
click_link 'Groups'
|
||||
|
||||
page.within '[data-testid="user-search-form"]' do
|
||||
fill_in 'search', with: 'user'
|
||||
fill_in_filtered_search 'Search groups', with: 'group'
|
||||
end
|
||||
|
||||
it 'displays "Groups" tab' do
|
||||
expect(page).to have_selector('.nav-link.active', text: 'Groups')
|
||||
end
|
||||
|
||||
context 'and then searching "Members"' do
|
||||
before do
|
||||
click_link 'Members 3'
|
||||
|
||||
fill_in_filtered_search 'Filter members', with: 'user'
|
||||
end
|
||||
|
||||
it_behaves_like 'active "Members" tab'
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
context 'when `vue_project_members_list` feature flag is disabled' do
|
||||
before do
|
||||
stub_feature_flags(vue_project_members_list: false)
|
||||
|
||||
sign_in(user)
|
||||
visit project_project_members_path(project)
|
||||
end
|
||||
|
||||
context 'when searching "Groups"', :js do
|
||||
before do
|
||||
click_link 'Groups'
|
||||
|
||||
page.within '[data-testid="group-link-search-form"]' do
|
||||
fill_in 'search_groups', with: 'group'
|
||||
find('button[type="submit"]').click
|
||||
end
|
||||
end
|
||||
|
||||
it_behaves_like 'active "Members" tab'
|
||||
it 'displays "Groups" tab' do
|
||||
expect(page).to have_selector('.nav-link.active', text: 'Groups')
|
||||
end
|
||||
|
||||
context 'and then searching "Members"' do
|
||||
before do
|
||||
click_link 'Members 3'
|
||||
|
||||
page.within '[data-testid="user-search-form"]' do
|
||||
fill_in 'search', with: 'user'
|
||||
find('button[type="submit"]').click
|
||||
end
|
||||
end
|
||||
|
||||
it_behaves_like 'active "Members" tab'
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
|
|
|||
|
|
@ -3,6 +3,9 @@
|
|||
require 'spec_helper'
|
||||
|
||||
RSpec.describe 'Projects > Settings > User manages project members' do
|
||||
include Spec::Support::Helpers::Features::MembersHelpers
|
||||
include Select2Helper
|
||||
|
||||
let(:group) { create(:group, name: 'OpenSource') }
|
||||
let(:project) { create(:project) }
|
||||
let(:project2) { create(:project) }
|
||||
|
|
@ -11,71 +14,128 @@ RSpec.describe 'Projects > Settings > User manages project members' do
|
|||
let(:user_mike) { create(:user, name: 'Mike') }
|
||||
|
||||
before do
|
||||
stub_feature_flags(vue_project_members_list: false)
|
||||
|
||||
project.add_maintainer(user)
|
||||
project.add_developer(user_dmitriy)
|
||||
sign_in(user)
|
||||
end
|
||||
|
||||
it 'cancels a team member', :js do
|
||||
visit(project_project_members_path(project))
|
||||
context 'when `vue_project_members_list` feature flag is enabled' do
|
||||
it 'cancels a team member', :js do
|
||||
visit(project_project_members_path(project))
|
||||
|
||||
project_member = project.project_members.find_by(user_id: user_dmitriy.id)
|
||||
page.within find_member_row(user_dmitriy) do
|
||||
click_button 'Remove member'
|
||||
end
|
||||
|
||||
page.within("#project_member_#{project_member.id}") do
|
||||
# Open modal
|
||||
click_on('Remove user from project')
|
||||
page.within('[role="dialog"]') do
|
||||
expect(page).to have_unchecked_field 'Also unassign this user from related issues and merge requests'
|
||||
click_button('Remove member')
|
||||
end
|
||||
|
||||
visit(project_project_members_path(project))
|
||||
|
||||
expect(members_table).not_to have_content(user_dmitriy.name)
|
||||
expect(members_table).not_to have_content(user_dmitriy.username)
|
||||
end
|
||||
|
||||
expect(page).to have_unchecked_field 'Also unassign this user from related issues and merge requests'
|
||||
it 'imports a team from another project', :js do
|
||||
stub_feature_flags(invite_members_group_modal: false)
|
||||
|
||||
click_on('Remove member')
|
||||
project2.add_maintainer(user)
|
||||
project2.add_reporter(user_mike)
|
||||
|
||||
visit(project_project_members_path(project))
|
||||
visit(project_project_members_path(project))
|
||||
|
||||
expect(page).not_to have_content(user_dmitriy.name)
|
||||
expect(page).not_to have_content(user_dmitriy.username)
|
||||
end
|
||||
page.within('.invite-users-form') do
|
||||
click_link('Import')
|
||||
end
|
||||
|
||||
it 'imports a team from another project' do
|
||||
stub_feature_flags(invite_members_group_modal: false)
|
||||
select2(project2.id, from: '#source_project_id')
|
||||
click_button('Import project members')
|
||||
|
||||
project2.add_maintainer(user)
|
||||
project2.add_reporter(user_mike)
|
||||
|
||||
visit(project_project_members_path(project))
|
||||
|
||||
page.within('.invite-users-form') do
|
||||
click_link('Import')
|
||||
expect(find_member_row(user_mike)).to have_content('Reporter')
|
||||
end
|
||||
|
||||
select(project2.full_name, from: 'source_project_id')
|
||||
click_button('Import')
|
||||
it 'shows all members of project shared group', :js do
|
||||
group.add_owner(user)
|
||||
group.add_developer(user_dmitriy)
|
||||
|
||||
project_member = project.project_members.find_by(user_id: user_mike.id)
|
||||
share_link = project.project_group_links.new(group_access: Gitlab::Access::MAINTAINER)
|
||||
share_link.group_id = group.id
|
||||
share_link.save!
|
||||
|
||||
page.within("#project_member_#{project_member.id}") do
|
||||
expect(page).to have_content('Mike')
|
||||
expect(page).to have_content('Reporter')
|
||||
visit(project_project_members_path(project))
|
||||
|
||||
click_link 'Groups'
|
||||
|
||||
expect(find_group_row(group)).to have_content('Maintainer')
|
||||
end
|
||||
end
|
||||
|
||||
it 'shows all members of project shared group', :js do
|
||||
group.add_owner(user)
|
||||
group.add_developer(user_dmitriy)
|
||||
context 'when `vue_project_members_list` feature flag is disabled' do
|
||||
before do
|
||||
stub_feature_flags(vue_project_members_list: false)
|
||||
end
|
||||
|
||||
share_link = project.project_group_links.new(group_access: Gitlab::Access::MAINTAINER)
|
||||
share_link.group_id = group.id
|
||||
share_link.save!
|
||||
it 'cancels a team member', :js do
|
||||
visit(project_project_members_path(project))
|
||||
|
||||
visit(project_project_members_path(project))
|
||||
project_member = project.project_members.find_by(user_id: user_dmitriy.id)
|
||||
|
||||
click_link 'Groups'
|
||||
page.within("#project_member_#{project_member.id}") do
|
||||
# Open modal
|
||||
click_on('Remove user from project')
|
||||
end
|
||||
|
||||
page.within('[data-testid="project-member-groups"]') do
|
||||
expect(page).to have_content('OpenSource')
|
||||
expect(first('.group_member')).to have_content('Maintainer')
|
||||
expect(page).to have_unchecked_field 'Also unassign this user from related issues and merge requests'
|
||||
|
||||
click_on('Remove member')
|
||||
|
||||
visit(project_project_members_path(project))
|
||||
|
||||
expect(page).not_to have_content(user_dmitriy.name)
|
||||
expect(page).not_to have_content(user_dmitriy.username)
|
||||
end
|
||||
|
||||
it 'imports a team from another project' do
|
||||
stub_feature_flags(invite_members_group_modal: false)
|
||||
|
||||
project2.add_maintainer(user)
|
||||
project2.add_reporter(user_mike)
|
||||
|
||||
visit(project_project_members_path(project))
|
||||
|
||||
page.within('.invite-users-form') do
|
||||
click_link('Import')
|
||||
end
|
||||
|
||||
select(project2.full_name, from: 'source_project_id')
|
||||
click_button('Import')
|
||||
|
||||
project_member = project.project_members.find_by(user_id: user_mike.id)
|
||||
|
||||
page.within("#project_member_#{project_member.id}") do
|
||||
expect(page).to have_content('Mike')
|
||||
expect(page).to have_content('Reporter')
|
||||
end
|
||||
end
|
||||
|
||||
it 'shows all members of project shared group', :js do
|
||||
group.add_owner(user)
|
||||
group.add_developer(user_dmitriy)
|
||||
|
||||
share_link = project.project_group_links.new(group_access: Gitlab::Access::MAINTAINER)
|
||||
share_link.group_id = group.id
|
||||
share_link.save!
|
||||
|
||||
visit(project_project_members_path(project))
|
||||
|
||||
click_link 'Groups'
|
||||
|
||||
page.within('[data-testid="project-member-groups"]') do
|
||||
expect(page).to have_content('OpenSource')
|
||||
expect(first('.group_member')).to have_content('Maintainer')
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
|
|
|||
|
|
@ -1,42 +0,0 @@
|
|||
# frozen_string_literal: true
|
||||
|
||||
require 'spec_helper'
|
||||
|
||||
RSpec.describe Gitlab::Auth::Otp::SessionEnforcer, :clean_gitlab_redis_shared_state do
|
||||
let_it_be(:key) { create(:key)}
|
||||
|
||||
describe '#update_session' do
|
||||
it 'registers a session in Redis' do
|
||||
redis = double(:redis)
|
||||
expect(Gitlab::Redis::SharedState).to receive(:with).and_yield(redis)
|
||||
session_expiry_in_seconds = Gitlab::CurrentSettings.git_two_factor_session_expiry.minutes.to_i
|
||||
|
||||
expect(redis).to(
|
||||
receive(:setex)
|
||||
.with("#{described_class::OTP_SESSIONS_NAMESPACE}:#{key.id}",
|
||||
session_expiry_in_seconds,
|
||||
true)
|
||||
.once)
|
||||
|
||||
described_class.new(key).update_session
|
||||
end
|
||||
end
|
||||
|
||||
describe '#access_restricted?' do
|
||||
subject { described_class.new(key).access_restricted? }
|
||||
|
||||
context 'with existing session' do
|
||||
before do
|
||||
Gitlab::Redis::SharedState.with do |redis|
|
||||
redis.set("#{described_class::OTP_SESSIONS_NAMESPACE}:#{key.id}", true )
|
||||
end
|
||||
end
|
||||
|
||||
it { is_expected.to be_falsey }
|
||||
end
|
||||
|
||||
context 'without an existing session' do
|
||||
it { is_expected.to be_truthy }
|
||||
end
|
||||
end
|
||||
end
|
||||
|
|
@ -388,161 +388,6 @@ RSpec.describe Gitlab::GitAccess do
|
|||
end
|
||||
end
|
||||
|
||||
describe '#check_otp_session!' do
|
||||
let_it_be(:user) { create(:user, :two_factor_via_otp)}
|
||||
let_it_be(:key) { create(:key, user: user) }
|
||||
let_it_be(:actor) { key }
|
||||
|
||||
before do
|
||||
project.add_developer(user)
|
||||
stub_feature_flags(two_factor_for_cli: true)
|
||||
end
|
||||
|
||||
context 'with an OTP session', :clean_gitlab_redis_shared_state do
|
||||
before do
|
||||
Gitlab::Redis::SharedState.with do |redis|
|
||||
redis.set("#{Gitlab::Auth::Otp::SessionEnforcer::OTP_SESSIONS_NAMESPACE}:#{key.id}", true)
|
||||
end
|
||||
end
|
||||
|
||||
it 'allows push and pull access' do
|
||||
aggregate_failures do
|
||||
expect { push_access_check }.not_to raise_error
|
||||
expect { pull_access_check }.not_to raise_error
|
||||
end
|
||||
end
|
||||
|
||||
context 'based on the duration set by the `git_two_factor_session_expiry` setting' do
|
||||
let_it_be(:git_two_factor_session_expiry) { 20 }
|
||||
let_it_be(:redis_key_expiry_at) { git_two_factor_session_expiry.minutes.from_now }
|
||||
|
||||
before do
|
||||
stub_application_setting(git_two_factor_session_expiry: git_two_factor_session_expiry)
|
||||
end
|
||||
|
||||
def value_of_key
|
||||
key_expired = Time.current > redis_key_expiry_at
|
||||
return if key_expired
|
||||
|
||||
true
|
||||
end
|
||||
|
||||
def stub_redis
|
||||
redis = double(:redis)
|
||||
expect(Gitlab::Redis::SharedState).to receive(:with).at_most(:twice).and_yield(redis)
|
||||
|
||||
expect(redis).to(
|
||||
receive(:get)
|
||||
.with("#{Gitlab::Auth::Otp::SessionEnforcer::OTP_SESSIONS_NAMESPACE}:#{key.id}"))
|
||||
.at_most(:twice)
|
||||
.and_return(value_of_key)
|
||||
end
|
||||
|
||||
context 'at a time before the stipulated expiry' do
|
||||
it 'allows push and pull access' do
|
||||
travel_to(10.minutes.from_now) do
|
||||
stub_redis
|
||||
|
||||
aggregate_failures do
|
||||
expect { push_access_check }.not_to raise_error
|
||||
expect { pull_access_check }.not_to raise_error
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
context 'at a time after the stipulated expiry' do
|
||||
it 'does not allow push and pull access' do
|
||||
travel_to(30.minutes.from_now) do
|
||||
stub_redis
|
||||
|
||||
aggregate_failures do
|
||||
expect { push_access_check }.to raise_error
|
||||
expect { pull_access_check }.to raise_error
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
context 'without OTP session' do
|
||||
it 'does not allow push or pull access' do
|
||||
user = 'jane.doe'
|
||||
host = 'fridge.ssh'
|
||||
port = 42
|
||||
|
||||
stub_config(
|
||||
gitlab_shell: {
|
||||
ssh_user: user,
|
||||
ssh_host: host,
|
||||
ssh_port: port
|
||||
}
|
||||
)
|
||||
|
||||
error_message = "OTP verification is required to access the repository.\n\n"\
|
||||
" Use: ssh #{user}@#{host} -p #{port} 2fa_verify"
|
||||
|
||||
aggregate_failures do
|
||||
expect { push_access_check }.to raise_forbidden(error_message)
|
||||
expect { pull_access_check }.to raise_forbidden(error_message)
|
||||
end
|
||||
end
|
||||
|
||||
context 'when protocol is HTTP' do
|
||||
let(:protocol) { 'http' }
|
||||
|
||||
it 'allows push and pull access' do
|
||||
aggregate_failures do
|
||||
expect { push_access_check }.not_to raise_error
|
||||
expect { pull_access_check }.not_to raise_error
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
context 'when actor is not an SSH key' do
|
||||
let(:deploy_key) { create(:deploy_key, user: user) }
|
||||
let(:actor) { deploy_key }
|
||||
|
||||
before do
|
||||
deploy_key.deploy_keys_projects.create(project: project, can_push: true)
|
||||
end
|
||||
|
||||
it 'allows push and pull access' do
|
||||
aggregate_failures do
|
||||
expect { push_access_check }.not_to raise_error
|
||||
expect { pull_access_check }.not_to raise_error
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
context 'when 2FA is not enabled for the user' do
|
||||
let(:user) { create(:user)}
|
||||
let(:actor) { create(:key, user: user) }
|
||||
|
||||
it 'allows push and pull access' do
|
||||
aggregate_failures do
|
||||
expect { push_access_check }.not_to raise_error
|
||||
expect { pull_access_check }.not_to raise_error
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
context 'when feature flag is disabled' do
|
||||
before do
|
||||
stub_feature_flags(two_factor_for_cli: false)
|
||||
end
|
||||
|
||||
it 'allows push and pull access' do
|
||||
aggregate_failures do
|
||||
expect { push_access_check }.not_to raise_error
|
||||
expect { pull_access_check }.not_to raise_error
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
describe '#check_db_accessibility!' do
|
||||
context 'when in a read-only GitLab instance' do
|
||||
before do
|
||||
|
|
|
|||
|
|
@ -458,6 +458,16 @@ RSpec.describe JiraService do
|
|||
|
||||
expect(WebMock).to have_requested(:get, issue_url)
|
||||
end
|
||||
|
||||
context 'with options' do
|
||||
let(:issue_url) { "#{url}/rest/api/2/issue/#{issue_key}?expand=renderedFields" }
|
||||
|
||||
it 'calls the Jira API with the options to get the issue' do
|
||||
jira_service.find_issue(issue_key, { expand: 'renderedFields' })
|
||||
|
||||
expect(WebMock).to have_requested(:get, issue_url)
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
describe '#close_issue' do
|
||||
|
|
|
|||
|
|
@ -50,41 +50,6 @@ RSpec.describe API::Internal::Base do
|
|||
end
|
||||
end
|
||||
|
||||
shared_examples 'actor key validations' do
|
||||
context 'key id is not provided' do
|
||||
let(:key_id) { nil }
|
||||
|
||||
it 'returns an error message' do
|
||||
subject
|
||||
|
||||
expect(json_response['success']).to be_falsey
|
||||
expect(json_response['message']).to eq('Could not find a user without a key')
|
||||
end
|
||||
end
|
||||
|
||||
context 'key does not exist' do
|
||||
let(:key_id) { non_existing_record_id }
|
||||
|
||||
it 'returns an error message' do
|
||||
subject
|
||||
|
||||
expect(json_response['success']).to be_falsey
|
||||
expect(json_response['message']).to eq('Could not find the given key')
|
||||
end
|
||||
end
|
||||
|
||||
context 'key without user' do
|
||||
let(:key_id) { create(:key, user: nil).id }
|
||||
|
||||
it 'returns an error message' do
|
||||
subject
|
||||
|
||||
expect(json_response['success']).to be_falsey
|
||||
expect(json_response['message']).to eq('Could not find a user for the given key')
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
describe 'GET /internal/two_factor_recovery_codes' do
|
||||
let(:key_id) { key.id }
|
||||
|
||||
|
|
@ -1406,10 +1371,6 @@ RSpec.describe API::Internal::Base do
|
|||
let(:key_id) { key.id }
|
||||
let(:otp) { '123456'}
|
||||
|
||||
before do
|
||||
stub_feature_flags(two_factor_for_cli: true)
|
||||
end
|
||||
|
||||
subject do
|
||||
post api('/internal/two_factor_otp_check'),
|
||||
params: {
|
||||
|
|
@ -1419,76 +1380,10 @@ RSpec.describe API::Internal::Base do
|
|||
}
|
||||
end
|
||||
|
||||
it_behaves_like 'actor key validations'
|
||||
it 'is not available' do
|
||||
subject
|
||||
|
||||
context 'when the key is a deploy key' do
|
||||
let(:key_id) { create(:deploy_key).id }
|
||||
|
||||
it 'returns an error message' do
|
||||
subject
|
||||
|
||||
expect(json_response['success']).to be_falsey
|
||||
expect(json_response['message']).to eq('Deploy keys cannot be used for Two Factor')
|
||||
end
|
||||
end
|
||||
|
||||
context 'when the two factor is enabled' do
|
||||
before do
|
||||
allow_any_instance_of(User).to receive(:two_factor_enabled?).and_return(true)
|
||||
end
|
||||
|
||||
context 'when the OTP is valid' do
|
||||
it 'registers a new OTP session and returns success' do
|
||||
allow_any_instance_of(Users::ValidateOtpService).to receive(:execute).with(otp).and_return(status: :success)
|
||||
|
||||
expect_next_instance_of(::Gitlab::Auth::Otp::SessionEnforcer) do |session_enforcer|
|
||||
expect(session_enforcer).to receive(:update_session).once
|
||||
end
|
||||
|
||||
subject
|
||||
|
||||
expect(json_response['success']).to be_truthy
|
||||
end
|
||||
end
|
||||
|
||||
context 'when the OTP is invalid' do
|
||||
it 'is not success' do
|
||||
allow_any_instance_of(Users::ValidateOtpService).to receive(:execute).with(otp).and_return(status: :error)
|
||||
|
||||
subject
|
||||
|
||||
expect(json_response['success']).to be_falsey
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
context 'when the two factor is disabled' do
|
||||
before do
|
||||
allow_any_instance_of(User).to receive(:two_factor_enabled?).and_return(false)
|
||||
end
|
||||
|
||||
it 'returns an error message' do
|
||||
subject
|
||||
|
||||
expect(json_response['success']).to be_falsey
|
||||
expect(json_response['message']).to eq 'Two-factor authentication is not enabled for this user'
|
||||
end
|
||||
end
|
||||
|
||||
context 'two_factor_for_cli feature is disabled' do
|
||||
before do
|
||||
stub_feature_flags(two_factor_for_cli: false)
|
||||
end
|
||||
|
||||
context 'when two-factor is enabled for the user' do
|
||||
it 'returns user two factor config' do
|
||||
allow_any_instance_of(User).to receive(:two_factor_enabled?).and_return(true)
|
||||
|
||||
subject
|
||||
|
||||
expect(json_response['success']).to be_falsey
|
||||
end
|
||||
end
|
||||
expect(json_response['success']).to be_falsey
|
||||
end
|
||||
end
|
||||
|
||||
|
|
|
|||
|
|
@ -30,6 +30,32 @@ module Spec
|
|||
def invite_users_form
|
||||
page.find('[data-testid="invite-users-form"]')
|
||||
end
|
||||
|
||||
def find_row(name)
|
||||
page.within(members_table) do
|
||||
page.find('tbody > tr', text: name)
|
||||
end
|
||||
end
|
||||
|
||||
def find_member_row(user)
|
||||
find_row(user.name)
|
||||
end
|
||||
|
||||
def find_invited_member_row(email)
|
||||
find_row(email)
|
||||
end
|
||||
|
||||
def find_group_row(group)
|
||||
find_row(group.full_name)
|
||||
end
|
||||
|
||||
def fill_in_filtered_search(label, with:)
|
||||
page.within '[data-testid="members-filtered-search-bar"]' do
|
||||
find_field(label).click
|
||||
find('input').native.send_keys(with)
|
||||
click_button 'Search'
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
|
|
|||
|
|
@ -0,0 +1,36 @@
|
|||
# frozen_string_literal: true
|
||||
|
||||
RSpec.shared_examples 'actor key validations' do
|
||||
context 'key id is not provided' do
|
||||
let(:key_id) { nil }
|
||||
|
||||
it 'returns an error message' do
|
||||
subject
|
||||
|
||||
expect(json_response['success']).to be_falsey
|
||||
expect(json_response['message']).to eq('Could not find a user without a key')
|
||||
end
|
||||
end
|
||||
|
||||
context 'key does not exist' do
|
||||
let(:key_id) { non_existing_record_id }
|
||||
|
||||
it 'returns an error message' do
|
||||
subject
|
||||
|
||||
expect(json_response['success']).to be_falsey
|
||||
expect(json_response['message']).to eq('Could not find the given key')
|
||||
end
|
||||
end
|
||||
|
||||
context 'key without user' do
|
||||
let(:key_id) { create(:key, user: nil).id }
|
||||
|
||||
it 'returns an error message' do
|
||||
subject
|
||||
|
||||
expect(json_response['success']).to be_falsey
|
||||
expect(json_response['message']).to eq('Could not find a user for the given key')
|
||||
end
|
||||
end
|
||||
end
|
||||
Loading…
Reference in New Issue