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