Welcome to mirror list, hosted at ThFree Co, Russian Federation.

gitlab.com/gitlab-org/gitlab-foss.git - Unnamed repository; edit this file 'description' to name the repository.
summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorGitLab Bot <gitlab-bot@gitlab.com>2020-12-15 21:10:06 +0300
committerGitLab Bot <gitlab-bot@gitlab.com>2020-12-15 21:10:06 +0300
commitb07852468f800d751ddecc9e327119d7295f538e (patch)
treed1169f95ea3725d609c796a1ca87d5766646852c
parent0ff373dc416216d02760c7c162ee23382eb1f4a3 (diff)
Add latest changes from gitlab-org/gitlab@master
-rw-r--r--app/assets/javascripts/import_entities/import_groups/graphql/client_factory.js29
-rw-r--r--app/assets/javascripts/import_entities/import_groups/graphql/services/status_poller.js68
-rw-r--r--app/assets/javascripts/lib/utils/keycodes.js1
-rw-r--r--app/assets/javascripts/pipeline_editor/graphql/queries/ci_config.graphql4
-rw-r--r--app/assets/javascripts/pipeline_editor/pipeline_editor_app.vue7
-rw-r--r--app/assets/javascripts/pipelines/graphql/queries/pipeline_stages.fragment.graphql12
-rw-r--r--app/assets/javascripts/pipelines/graphql/queries/pipeline_stages_connection.fragment.graphql20
-rw-r--r--app/assets/javascripts/vue_shared/components/gfm_autocomplete/gfm_autocomplete.vue6
-rw-r--r--app/models/ci/build_dependencies.rb2
-rw-r--r--app/models/user.rb4
-rw-r--r--app/services/jira/requests/base.rb7
-rw-r--r--app/views/import/bulk_imports/status.html.haml4
-rw-r--r--app/views/profiles/accounts/show.html.haml5
-rw-r--r--changelogs/unreleased/293629-add-expires_at-param-to-groupmemberbuilder-data.yml5
-rw-r--r--changelogs/unreleased/enable-ci-cross-pipeline-artifacts-download.yml5
-rw-r--r--changelogs/unreleased/xanf-import-one-group-frontend.yml5
-rw-r--r--config/feature_flags/development/ci_cross_pipeline_artifacts_download.yml2
-rw-r--r--config/initializers/active_record_table_definition.rb4
-rw-r--r--doc/administration/gitaly/index.md8
-rw-r--r--doc/administration/integration/terminal.md12
-rw-r--r--doc/administration/reference_architectures/index.md5
-rw-r--r--doc/administration/troubleshooting/gitlab_rails_cheat_sheet.md20
-rw-r--r--doc/api/graphql/reference/gitlab_schema.graphql4
-rw-r--r--doc/api/graphql/reference/gitlab_schema.json4
-rw-r--r--doc/api/graphql/reference/index.md4
-rw-r--r--doc/ci/yaml/README.md49
-rw-r--r--doc/development/database_review.md2
-rw-r--r--doc/development/documentation/styleguide/index.md2
-rw-r--r--doc/operations/incident_management/alert_integrations.md2
-rw-r--r--doc/user/admin_area/analytics/dev_ops_report.md2
-rw-r--r--doc/user/admin_area/settings/user_and_ip_rate_limits.md35
-rw-r--r--doc/user/group/epics/index.md6
-rw-r--r--doc/user/packages/package_registry/index.md1
-rw-r--r--doc/user/packages/workflows/monorepo.md121
-rw-r--r--doc/user/project/integrations/webhooks.md1
-rw-r--r--doc/user/project/issues/index.md13
-rw-r--r--doc/user/project/issues/sorting_issue_lists.md41
-rw-r--r--lib/banzai/filter/merge_request_reference_filter.rb2
-rw-r--r--lib/gitlab/hook_data/group_member_builder.rb4
-rw-r--r--lib/gitlab/import_export/project/sample/relation_tree_restorer.rb4
-rw-r--r--lib/gitlab/import_export/relation_tree_restorer.rb2
-rw-r--r--lib/gitlab/rack_attack.rb3
-rw-r--r--lib/gitlab/sanitizers/exif.rb2
-rw-r--r--lib/tasks/gitlab/db.rake16
-rw-r--r--locale/gitlab.pot33
-rw-r--r--package.json2
-rw-r--r--spec/factories/projects.rb6
-rw-r--r--spec/features/issues/gfm_autocomplete_spec.rb112
-rw-r--r--spec/frontend/import_entities/import_groups/graphql/client_factory_spec.js43
-rw-r--r--spec/frontend/import_entities/import_groups/graphql/services/status_poller_spec.js213
-rw-r--r--spec/frontend/vue_shared/components/gfm_autocomplete/gfm_autocomplete_spec.js2
-rw-r--r--spec/lib/gitlab/hook_data/group_member_builder_spec.rb5
-rw-r--r--spec/models/user_spec.rb8
-rw-r--r--spec/services/jira/requests/projects/list_service_spec.rb27
-rw-r--r--spec/tasks/gitlab/db_rake_spec.rb14
-rw-r--r--yarn.lock10
56 files changed, 735 insertions, 295 deletions
diff --git a/app/assets/javascripts/import_entities/import_groups/graphql/client_factory.js b/app/assets/javascripts/import_entities/import_groups/graphql/client_factory.js
index 23f4190c2d0..4fcaa1b55fc 100644
--- a/app/assets/javascripts/import_entities/import_groups/graphql/client_factory.js
+++ b/app/assets/javascripts/import_entities/import_groups/graphql/client_factory.js
@@ -1,8 +1,11 @@
import axios from '~/lib/utils/axios_utils';
import createDefaultClient from '~/lib/graphql';
+import { s__ } from '~/locale';
+import createFlash from '~/flash';
import { STATUSES } from '../../constants';
import availableNamespacesQuery from './queries/available_namespaces.query.graphql';
import { SourceGroupsManager } from './services/source_groups_manager';
+import { StatusPoller } from './services/status_poller';
export const clientTypenames = {
BulkImportSourceGroup: 'ClientBulkImportSourceGroup',
@@ -10,6 +13,8 @@ export const clientTypenames = {
};
export function createResolvers({ endpoints }) {
+ let statusPoller;
+
return {
Query: {
async bulkImportSourceGroups(_, __, { client }) {
@@ -57,6 +62,30 @@ export function createResolvers({ endpoints }) {
const groupManager = new SourceGroupsManager({ client });
const group = groupManager.findById(sourceGroupId);
groupManager.setImportStatus(group, STATUSES.SCHEDULING);
+ try {
+ await axios.post(endpoints.createBulkImport, {
+ bulk_import: [
+ {
+ source_type: 'group_entity',
+ source_full_path: group.full_path,
+ destination_namespace: group.import_target.target_namespace,
+ destination_name: group.import_target.new_name,
+ },
+ ],
+ });
+ groupManager.setImportStatus(group, STATUSES.STARTED);
+ if (!statusPoller) {
+ statusPoller = new StatusPoller({ client, interval: 3000 });
+ statusPoller.startPolling();
+ }
+ } catch (e) {
+ createFlash({
+ message: s__('BulkImport|Importing the group failed'),
+ });
+
+ groupManager.setImportStatus(group, STATUSES.NONE);
+ throw e;
+ }
},
},
};
diff --git a/app/assets/javascripts/import_entities/import_groups/graphql/services/status_poller.js b/app/assets/javascripts/import_entities/import_groups/graphql/services/status_poller.js
new file mode 100644
index 00000000000..5d2922b0ba8
--- /dev/null
+++ b/app/assets/javascripts/import_entities/import_groups/graphql/services/status_poller.js
@@ -0,0 +1,68 @@
+import gql from 'graphql-tag';
+import createFlash from '~/flash';
+import { s__ } from '~/locale';
+import bulkImportSourceGroupsQuery from '../queries/bulk_import_source_groups.query.graphql';
+import { STATUSES } from '../../../constants';
+import { SourceGroupsManager } from './source_groups_manager';
+
+const groupId = i => `group${i}`;
+
+function generateGroupsQuery(groups) {
+ return gql`{
+ ${groups
+ .map(
+ (g, idx) =>
+ `${groupId(idx)}: group(fullPath: "${g.import_target.target_namespace}/${
+ g.import_target.new_name
+ }") { id }`,
+ )
+ .join('\n')}
+ }`;
+}
+
+export class StatusPoller {
+ constructor({ client, interval }) {
+ this.client = client;
+ this.interval = interval;
+ this.timeoutId = null;
+ this.groupManager = new SourceGroupsManager({ client });
+ }
+
+ startPolling() {
+ if (this.timeoutId) {
+ return;
+ }
+
+ this.checkPendingImports();
+ }
+
+ stopPolling() {
+ clearTimeout(this.timeoutId);
+ this.timeoutId = null;
+ }
+
+ async checkPendingImports() {
+ try {
+ const { bulkImportSourceGroups } = this.client.readQuery({
+ query: bulkImportSourceGroupsQuery,
+ });
+ const groupsInProgress = bulkImportSourceGroups.filter(g => g.status === STATUSES.STARTED);
+ if (groupsInProgress.length) {
+ const { data: results } = await this.client.query({
+ query: generateGroupsQuery(groupsInProgress),
+ fetchPolicy: 'no-cache',
+ });
+ const completedGroups = groupsInProgress.filter((_, idx) => Boolean(results[groupId(idx)]));
+ completedGroups.forEach(group => {
+ this.groupManager.setImportStatus(group, STATUSES.FINISHED);
+ });
+ }
+ } catch (e) {
+ createFlash({
+ message: s__('BulkImport|Update of import statuses with realtime changes failed'),
+ });
+ } finally {
+ this.timeoutId = setTimeout(() => this.checkPendingImports(), this.interval);
+ }
+ }
+}
diff --git a/app/assets/javascripts/lib/utils/keycodes.js b/app/assets/javascripts/lib/utils/keycodes.js
index 618266f7a09..6f5cd7460f8 100644
--- a/app/assets/javascripts/lib/utils/keycodes.js
+++ b/app/assets/javascripts/lib/utils/keycodes.js
@@ -2,6 +2,7 @@
// See: https://gitlab.com/gitlab-org/gitlab/-/issues/216102
export const BACKSPACE_KEY_CODE = 8;
+export const TAB_KEY_CODE = 9;
export const ENTER_KEY_CODE = 13;
export const ESC_KEY_CODE = 27;
export const UP_KEY_CODE = 38;
diff --git a/app/assets/javascripts/pipeline_editor/graphql/queries/ci_config.graphql b/app/assets/javascripts/pipeline_editor/graphql/queries/ci_config.graphql
index 149cb256ced..d65d9892260 100644
--- a/app/assets/javascripts/pipeline_editor/graphql/queries/ci_config.graphql
+++ b/app/assets/javascripts/pipeline_editor/graphql/queries/ci_config.graphql
@@ -1,11 +1,11 @@
-#import "~/pipelines/graphql/queries/pipeline_stages.fragment.graphql"
+#import "~/pipelines/graphql/queries/pipeline_stages_connection.fragment.graphql"
query getCiConfigData($content: String!) {
ciConfig(content: $content) {
errors
status
stages {
- ...PipelineStagesData
+ ...PipelineStagesConnection
}
}
}
diff --git a/app/assets/javascripts/pipeline_editor/pipeline_editor_app.vue b/app/assets/javascripts/pipeline_editor/pipeline_editor_app.vue
index b1c52ffa920..8a57c9b1970 100644
--- a/app/assets/javascripts/pipeline_editor/pipeline_editor_app.vue
+++ b/app/assets/javascripts/pipeline_editor/pipeline_editor_app.vue
@@ -10,6 +10,7 @@ import TextEditor from './components/text_editor.vue';
import commitCiFileMutation from './graphql/mutations/commit_ci_file.mutation.graphql';
import getBlobContent from './graphql/queries/blob_content.graphql';
import getCiConfigData from './graphql/queries/ci_config.graphql';
+import { unwrapStagesWithNeeds } from '~/pipelines/components/unwrapping_utils';
const MR_SOURCE_BRANCH = 'merge_request[source_branch]';
const MR_TARGET_BRANCH = 'merge_request[target_branch]';
@@ -99,7 +100,11 @@ export default {
};
},
update(data) {
- return data?.ciConfig ?? {};
+ const { ciConfigData } = data || {};
+ const stageNodes = ciConfigData?.stages?.nodes || [];
+ const stages = unwrapStagesWithNeeds(stageNodes);
+
+ return { ...ciConfigData, stages };
},
error() {
this.reportFailure(LOAD_FAILURE_UNKNOWN);
diff --git a/app/assets/javascripts/pipelines/graphql/queries/pipeline_stages.fragment.graphql b/app/assets/javascripts/pipelines/graphql/queries/pipeline_stages.fragment.graphql
deleted file mode 100644
index 0aef2fdfd7f..00000000000
--- a/app/assets/javascripts/pipelines/graphql/queries/pipeline_stages.fragment.graphql
+++ /dev/null
@@ -1,12 +0,0 @@
-fragment PipelineStagesData on CiConfigStage {
- name
- groups {
- name
- jobs {
- name
- needs {
- name
- }
- }
- }
-}
diff --git a/app/assets/javascripts/pipelines/graphql/queries/pipeline_stages_connection.fragment.graphql b/app/assets/javascripts/pipelines/graphql/queries/pipeline_stages_connection.fragment.graphql
new file mode 100644
index 00000000000..1da4fa0a72b
--- /dev/null
+++ b/app/assets/javascripts/pipelines/graphql/queries/pipeline_stages_connection.fragment.graphql
@@ -0,0 +1,20 @@
+fragment PipelineStagesConnection on CiConfigStageConnection {
+ nodes {
+ name
+ groups {
+ nodes {
+ name
+ jobs {
+ nodes {
+ name
+ needs {
+ nodes {
+ name
+ }
+ }
+ }
+ }
+ }
+ }
+ }
+}
diff --git a/app/assets/javascripts/vue_shared/components/gfm_autocomplete/gfm_autocomplete.vue b/app/assets/javascripts/vue_shared/components/gfm_autocomplete/gfm_autocomplete.vue
index fb61c13983f..1ad0ca36bf8 100644
--- a/app/assets/javascripts/vue_shared/components/gfm_autocomplete/gfm_autocomplete.vue
+++ b/app/assets/javascripts/vue_shared/components/gfm_autocomplete/gfm_autocomplete.vue
@@ -1,5 +1,5 @@
<script>
-import Tribute from 'tributejs';
+import Tribute from '@gitlab/tributejs';
import {
GfmAutocompleteType,
tributeConfig,
@@ -29,6 +29,10 @@ export default {
config() {
return this.autocompleteTypes.map(type => ({
...tributeConfig[type].config,
+ loadingItemTemplate: `<span class="gl-spinner gl-vertical-align-text-bottom gl-ml-3 gl-mr-2"></span>${__(
+ 'Loading',
+ )}`,
+ requireLeadingSpace: true,
values: this.getValues(type),
}));
},
diff --git a/app/models/ci/build_dependencies.rb b/app/models/ci/build_dependencies.rb
index 2c6a9a63bdb..a6abeb517c1 100644
--- a/app/models/ci/build_dependencies.rb
+++ b/app/models/ci/build_dependencies.rb
@@ -143,7 +143,7 @@ module Ci
def specified_cross_pipeline_dependencies
strong_memoize(:specified_cross_pipeline_dependencies) do
- next [] unless Feature.enabled?(:ci_cross_pipeline_artifacts_download, processable.project, default_enabled: false)
+ next [] unless Feature.enabled?(:ci_cross_pipeline_artifacts_download, processable.project, default_enabled: true)
specified_cross_dependencies.select { |dep| dep[:pipeline] && dep[:artifacts] }
end
diff --git a/app/models/user.rb b/app/models/user.rb
index f1c7644901a..c735f20b92c 100644
--- a/app/models/user.rb
+++ b/app/models/user.rb
@@ -1493,6 +1493,10 @@ class User < ApplicationRecord
!solo_owned_groups.present?
end
+ def can_remove_self?
+ true
+ end
+
def ci_owned_runners
@ci_owned_runners ||= begin
project_runners = Ci::RunnerProject
diff --git a/app/services/jira/requests/base.rb b/app/services/jira/requests/base.rb
index 4ed8df0f235..098aae9284c 100644
--- a/app/services/jira/requests/base.rb
+++ b/app/services/jira/requests/base.rb
@@ -18,14 +18,19 @@ module Jira
request
end
+ # We have to add the context_path here because the Jira client is not taking it into account
def base_api_url
- "/rest/api/#{api_version}"
+ "#{context_path}/rest/api/#{api_version}"
end
private
attr_reader :jira_service, :project
+ def context_path
+ client.options[:context_path].to_s
+ end
+
# override this method in the specific request class implementation if a differnt API version is required
def api_version
JIRA_API_VERSION
diff --git a/app/views/import/bulk_imports/status.html.haml b/app/views/import/bulk_imports/status.html.haml
index 80b96a25ebb..6757c32d1e1 100644
--- a/app/views/import/bulk_imports/status.html.haml
+++ b/app/views/import/bulk_imports/status.html.haml
@@ -3,9 +3,9 @@
- breadcrumb_title _('Import groups')
%h1.gl-my-0.gl-py-4.gl-font-size-h1.gl-border-solid.gl-border-gray-200.gl-border-0.gl-border-b-1
- = s_('ImportGroups|Import groups from GitLab')
+ = s_('BulkImport|Import groups from GitLab')
%p.gl-my-0.gl-py-5.gl-border-solid.gl-border-gray-200.gl-border-0.gl-border-b-1
- = s_('ImportGroups|Importing groups from %{link}').html_safe % { link: external_link(@source_url, @source_url) }
+ = s_('BulkImport|Importing groups from %{link}').html_safe % { link: external_link(@source_url, @source_url) }
#import-groups-mount-element{ data: { status_path: status_import_bulk_imports_path(format: :json),
available_namespaces_path: import_available_namespaces_path(format: :json),
diff --git a/app/views/profiles/accounts/show.html.haml b/app/views/profiles/accounts/show.html.haml
index fe00420c86c..ca64c5f57b3 100644
--- a/app/views/profiles/accounts/show.html.haml
+++ b/app/views/profiles/accounts/show.html.haml
@@ -79,6 +79,11 @@
%strong= current_user.solo_owned_groups.map(&:name).join(', ')
%p
= s_('Profiles|You must transfer ownership or delete these groups before you can delete your account.')
+ - elsif !current_user.can_remove_self?
+ %p
+ = s_('Profiles|GitLab is unable to verify your identity automatically.')
+ %p
+ = s_('Profiles|Please email %{data_request} to begin the account deletion process.').html_safe % { data_request: mail_to('personal-data-request@gitlab.com') }
- else
%p
= s_("Profiles|You don't have access to delete this user.")
diff --git a/changelogs/unreleased/293629-add-expires_at-param-to-groupmemberbuilder-data.yml b/changelogs/unreleased/293629-add-expires_at-param-to-groupmemberbuilder-data.yml
new file mode 100644
index 00000000000..92c9c8b4220
--- /dev/null
+++ b/changelogs/unreleased/293629-add-expires_at-param-to-groupmemberbuilder-data.yml
@@ -0,0 +1,5 @@
+---
+title: Add expires_at param to GroupMemberBuilder data
+merge_request: 49981
+author:
+type: changed
diff --git a/changelogs/unreleased/enable-ci-cross-pipeline-artifacts-download.yml b/changelogs/unreleased/enable-ci-cross-pipeline-artifacts-download.yml
new file mode 100644
index 00000000000..fdbba417912
--- /dev/null
+++ b/changelogs/unreleased/enable-ci-cross-pipeline-artifacts-download.yml
@@ -0,0 +1,5 @@
+---
+title: Allow job to download artifacts in parent-child pipeline hierarchy
+merge_request: 49837
+author:
+type: added
diff --git a/changelogs/unreleased/xanf-import-one-group-frontend.yml b/changelogs/unreleased/xanf-import-one-group-frontend.yml
new file mode 100644
index 00000000000..0e7c56dc375
--- /dev/null
+++ b/changelogs/unreleased/xanf-import-one-group-frontend.yml
@@ -0,0 +1,5 @@
+---
+title: Introduce frontend for group migration MVC
+merge_request: 49709
+author:
+type: added
diff --git a/config/feature_flags/development/ci_cross_pipeline_artifacts_download.yml b/config/feature_flags/development/ci_cross_pipeline_artifacts_download.yml
index 83fe049db8e..f50d71fc71f 100644
--- a/config/feature_flags/development/ci_cross_pipeline_artifacts_download.yml
+++ b/config/feature_flags/development/ci_cross_pipeline_artifacts_download.yml
@@ -5,4 +5,4 @@ rollout_issue_url: https://gitlab.com/gitlab-org/gitlab/-/issues/287622
milestone: '13.7'
type: development
group: group::continuous integration
-default_enabled: false
+default_enabled: true
diff --git a/config/initializers/active_record_table_definition.rb b/config/initializers/active_record_table_definition.rb
index 81a8e5906f4..9220620da41 100644
--- a/config/initializers/active_record_table_definition.rb
+++ b/config/initializers/active_record_table_definition.rb
@@ -16,7 +16,7 @@ module ActiveRecord
options[:null] = false if options[:null].nil?
[:created_at, :updated_at].each do |column_name|
- column(column_name, :datetime_with_timezone, options)
+ column(column_name, :datetime_with_timezone, **options)
end
end
@@ -27,7 +27,7 @@ module ActiveRecord
# t.datetime_with_timezone :did_something_at
# end
def datetime_with_timezone(column_name, **options)
- column(column_name, :datetime_with_timezone, options)
+ column(column_name, :datetime_with_timezone, **options)
end
# Disable timestamp alias to datetime
diff --git a/doc/administration/gitaly/index.md b/doc/administration/gitaly/index.md
index a7385b88feb..a7735caa428 100644
--- a/doc/administration/gitaly/index.md
+++ b/doc/administration/gitaly/index.md
@@ -22,11 +22,15 @@ In the Gitaly documentation:
GitLab end users do not have direct access to Gitaly. Gitaly only manages Git
repository access for GitLab. Other types of GitLab data aren't accessed using Gitaly.
+<!-- vale gitlab.FutureTense = NO -->
+
WARNING:
From GitLab 13.0, Gitaly support for NFS is deprecated. As of GitLab 14.0, NFS-related issues
with Gitaly will no longer be addressed. Upgrade to [Gitaly Cluster](praefect.md) as soon as
-possible. Watch for [tools to enable bulk move](https://gitlab.com/groups/gitlab-org/-/epics/4916)
-of projects to Gitaly Cluster.
+possible. Tools to [enable bulk moves](https://gitlab.com/groups/gitlab-org/-/epics/4916)
+of projects to Gitaly Cluster are planned.
+
+<!-- vale gitlab.FutureTense = YES -->
## Architecture
diff --git a/doc/administration/integration/terminal.md b/doc/administration/integration/terminal.md
index 8fb732ef9c8..f4c242b6e72 100644
--- a/doc/administration/integration/terminal.md
+++ b/doc/administration/integration/terminal.md
@@ -27,7 +27,7 @@ In brief:
- When a user navigates to the terminal page for an environment, they are served
a JavaScript application that opens a WebSocket connection back to GitLab.
- The WebSocket is handled in [Workhorse](https://gitlab.com/gitlab-org/gitlab-workhorse),
- rather than the Rails application server.
+ rather than the Rails application server.
- Workhorse queries Rails for connection details and user permissions. Rails
queries Kubernetes for them in the background using [Sidekiq](../troubleshooting/sidekiq.md).
- Workhorse acts as a proxy server between the user's browser and the Kubernetes
@@ -44,7 +44,7 @@ everything protected with authorization guards. This is described in more
detail below.
- Interactive web terminals are completely disabled unless [`[session_server]`](https://docs.gitlab.com/runner/configuration/advanced-configuration.html#the-session_server-section) is configured.
-- Every time the runner starts, it will generate an `x509` certificate that will be used for a `wss` (Web Socket Secure) connection.
+- Every time the runner starts, it generates an `x509` certificate that is used for a `wss` (Web Socket Secure) connection.
- For every created job, a random URL is generated which is discarded at the end of the job. This URL is used to establish a web socket connection. The URL for the session is in the format `(IP|HOST):PORT/session/$SOME_HASH`, where the `IP/HOST` and `PORT` are the configured [`listen_address`](https://docs.gitlab.com/runner/configuration/advanced-configuration.html#the-session_server-section).
- Every session URL that is created has an authorization header that needs to be sent, to establish a `wss` connection.
- The session URL is not exposed to the users in any way. GitLab holds all the state internally and proxies accordingly.
@@ -72,7 +72,7 @@ guides document the necessary steps for a selection of popular reverse proxies:
- [HAProxy](https://www.haproxy.com/blog/websockets-load-balancing-with-haproxy/)
- [Varnish](https://varnish-cache.org/docs/4.1/users-guide/vcl-example-websockets.html)
-Workhorse won't let WebSocket requests through to non-WebSocket endpoints, so
+Workhorse doesn't let WebSocket requests through to non-WebSocket endpoints, so
it's safe to enable support for these headers globally. If you'd rather had a
narrower set of rules, you can restrict it to URLs ending with `/terminal.ws`
(although this may still have a few false positives).
@@ -85,7 +85,7 @@ document for more details.
If you'd like to disable web terminal support in GitLab, just stop passing
the `Connection` and `Upgrade` hop-by-hop headers in the *first* HTTP reverse
-proxy in the chain. For most users, this will be the NGINX server bundled with
+proxy in the chain. For most users, this is the NGINX server bundled with
Omnibus GitLab, in which case, you need to:
- Find the `nginx['proxy_set_headers']` section of your `gitlab.rb` file
@@ -95,9 +95,9 @@ Omnibus GitLab, in which case, you need to:
For your own load balancer, just reverse the configuration changes recommended
by the above guides.
-When these headers are not passed through, Workhorse will return a
+When these headers are not passed through, Workhorse returns a
`400 Bad Request` response to users attempting to use a web terminal. In turn,
-they will receive a `Connection failed` message.
+they receive a `Connection failed` message.
## Limiting WebSocket connection time
diff --git a/doc/administration/reference_architectures/index.md b/doc/administration/reference_architectures/index.md
index 498b9d4216b..f269febf182 100644
--- a/doc/administration/reference_architectures/index.md
+++ b/doc/administration/reference_architectures/index.md
@@ -68,6 +68,11 @@ The following reference architectures are available:
- [Up to 25,000 users](25k_users.md)
- [Up to 50,000 users](50k_users.md)
+A GitLab [Premium or Ultimate](https://about.gitlab.com/pricing/#self-managed) license is required
+to get assistance from Support with troubleshooting the [2,000 users](2k_users.md)
+and higher reference architectures.
+[Read more about our definition of scaled architectures](https://about.gitlab.com/support/#definition-of-scaled-architecture).
+
## Availability Components
GitLab comes with the following components for your use, listed from least to
diff --git a/doc/administration/troubleshooting/gitlab_rails_cheat_sheet.md b/doc/administration/troubleshooting/gitlab_rails_cheat_sheet.md
index c2878cee968..2482a4fe7ad 100644
--- a/doc/administration/troubleshooting/gitlab_rails_cheat_sheet.md
+++ b/doc/administration/troubleshooting/gitlab_rails_cheat_sheet.md
@@ -374,6 +374,26 @@ Clear the cache:
sudo gitlab-rake cache:clear
```
+### Export a repository
+
+It's typically recommended to export a project through [the web interface](../../user/project/settings/import_export.md#exporting-a-project-and-its-data) or through [the API](../../api/project_import_export.md). In situations where this is not working as expected, it may be preferable to export a project directly via the Rails console:
+
+```ruby
+user = User.find_by_username('USERNAME')
+project = Project.find_by_full_path('PROJECT_PATH')
+Projects::ImportExport::ExportService.new(project, user).execute
+```
+
+If the project you wish to export is available at `https://gitlab.example.com/baltig/pipeline-templates`, the value to use for `PROJECT_PATH` would be `baltig/pipeline-templates`.
+
+If this all runs successfully, you will see output like the following before being returned to the Rails console prompt:
+
+```ruby
+=> nil
+```
+
+The exported project will be located within a `.tar.gz` file in `/var/opt/gitlab/gitlab-rails/uploads/-/system/import_export_upload/export_file/`.
+
## Repository
### Search sequence of pushes to a repository
diff --git a/doc/api/graphql/reference/gitlab_schema.graphql b/doc/api/graphql/reference/gitlab_schema.graphql
index 7d9f79483a2..858ec8f0bf5 100644
--- a/doc/api/graphql/reference/gitlab_schema.graphql
+++ b/doc/api/graphql/reference/gitlab_schema.graphql
@@ -8383,7 +8383,7 @@ type EpicIssue implements CurrentUserTodos & Noteable {
epicIssueId: ID!
"""
- Current health status. Returns null if `save_issuable_health_status` feature flag is disabled.
+ Current health status.
"""
healthStatus: HealthStatus
@@ -11154,7 +11154,7 @@ type Issue implements CurrentUserTodos & Noteable {
epic: Epic
"""
- Current health status. Returns null if `save_issuable_health_status` feature flag is disabled.
+ Current health status.
"""
healthStatus: HealthStatus
diff --git a/doc/api/graphql/reference/gitlab_schema.json b/doc/api/graphql/reference/gitlab_schema.json
index 8dadfcc5da9..4b4ef9c63d5 100644
--- a/doc/api/graphql/reference/gitlab_schema.json
+++ b/doc/api/graphql/reference/gitlab_schema.json
@@ -23462,7 +23462,7 @@
},
{
"name": "healthStatus",
- "description": "Current health status. Returns null if `save_issuable_health_status` feature flag is disabled.",
+ "description": "Current health status.",
"args": [
],
@@ -30725,7 +30725,7 @@
},
{
"name": "healthStatus",
- "description": "Current health status. Returns null if `save_issuable_health_status` feature flag is disabled.",
+ "description": "Current health status.",
"args": [
],
diff --git a/doc/api/graphql/reference/index.md b/doc/api/graphql/reference/index.md
index 85718f57aab..83155402703 100644
--- a/doc/api/graphql/reference/index.md
+++ b/doc/api/graphql/reference/index.md
@@ -1435,7 +1435,7 @@ Relationship between an epic and an issue.
| `emailsDisabled` | Boolean! | Indicates if a project has email notifications disabled: `true` if email notifications are disabled |
| `epic` | Epic | Epic to which this issue belongs. |
| `epicIssueId` | ID! | ID of the epic-issue relation |
-| `healthStatus` | HealthStatus | Current health status. Returns null if `save_issuable_health_status` feature flag is disabled. |
+| `healthStatus` | HealthStatus | Current health status. |
| `humanTimeEstimate` | String | Human-readable time estimate of the issue |
| `humanTotalTimeSpent` | String | Human-readable total time reported as spent on the issue |
| `id` | ID | Global ID of the epic-issue relation |
@@ -1751,7 +1751,7 @@ Represents a recorded measurement (object count) for the Admins.
| `dueDate` | Time | Due date of the issue |
| `emailsDisabled` | Boolean! | Indicates if a project has email notifications disabled: `true` if email notifications are disabled |
| `epic` | Epic | Epic to which this issue belongs. |
-| `healthStatus` | HealthStatus | Current health status. Returns null if `save_issuable_health_status` feature flag is disabled. |
+| `healthStatus` | HealthStatus | Current health status. |
| `humanTimeEstimate` | String | Human-readable time estimate of the issue |
| `humanTotalTimeSpent` | String | Human-readable total time reported as spent on the issue |
| `id` | ID! | ID of the issue |
diff --git a/doc/ci/yaml/README.md b/doc/ci/yaml/README.md
index 9fdf489eefe..226d1f680f4 100644
--- a/doc/ci/yaml/README.md
+++ b/doc/ci/yaml/README.md
@@ -2088,12 +2088,59 @@ build_job:
needs:
- project: $CI_PROJECT_PATH
job: $DEPENDENCY_JOB_NAME
- ref: $CI_COMMIT_BRANCH
+ ref: $ARTIFACTS_DOWNLOAD_REF
artifacts: true
```
Downloading artifacts from jobs that are run in [`parallel:`](#parallel) is not supported.
+To download artifacts between [parent-child pipelines](../parent_child_pipelines.md) use [`needs:pipeline`](#artifact-downloads-to-child-pipelines).
+Downloading artifacts from the same ref as the currently running pipeline is not
+recommended because artifacts could be overridden by concurrent pipelines running
+on the same ref.
+
+##### Artifact downloads to child pipelines
+
+> [Introduced](https://gitlab.com/gitlab-org/gitlab/-/issues/255983) in GitLab v13.7.
+
+A [child pipeline](../parent_child_pipelines.md) can download artifacts from a job in
+its parent pipeline or another child pipeline in the same parent-child pipeline hierarchy.
+
+For example, with the following parent pipeline that has a job that creates some artifacts:
+
+```yaml
+create-artifact:
+ stage: build
+ script: echo 'sample artifact' > artifact.txt
+ artifacts:
+ paths: [artifact.txt]
+
+child-pipeline:
+ stage: test
+ trigger:
+ include: child.yml
+ strategy: depend
+ variables:
+ PARENT_PIPELINE_ID: $CI_PIPELINE_ID
+```
+
+A job in the child pipeline can download artifacts from the `create-artifact` job in
+the parent pipeline:
+
+```yaml
+use-artifact:
+ script: cat artifact.txt
+ needs:
+ - pipeline: $PARENT_PIPELINE_ID
+ job: create-artifact
+```
+
+The `pipeline` attribute accepts a pipeline ID and it must be a pipeline present
+in the same parent-child pipeline hierarchy of the given pipeline.
+
+The `pipeline` attribute does not accept the current pipeline ID (`$CI_PIPELINE_ID`).
+To download artifacts from a job in the current pipeline, use the basic form of [`needs`](#artifact-downloads-with-needs).
+
### `tags`
Use `tags` to select a specific runner from the list of all runners that are
diff --git a/doc/development/database_review.md b/doc/development/database_review.md
index f3422e8cfd3..f0c265df9ab 100644
--- a/doc/development/database_review.md
+++ b/doc/development/database_review.md
@@ -48,7 +48,7 @@ If new migrations are introduced, in the MR **you are required to provide**:
If new queries have been introduced or existing queries have been updated, **you are required to provide**:
- [Query plans](#query-plans) for each raw SQL query included in the merge request along with the link to the query plan following each raw SQL snippet.
-- [Raw SQL](#raw-sql) for all queries (as translated from ActiveRecord queries).
+- [Raw SQL](#raw-sql) for all changed or added queries (as translated from ActiveRecord queries).
- In case of updating an existing query, the raw SQL of both the old and the new version of the query should be provided together with their query plans.
Refer to [Preparation when adding or modifying queries](#preparation-when-adding-or-modifying-queries) for how to provide this information.
diff --git a/doc/development/documentation/styleguide/index.md b/doc/development/documentation/styleguide/index.md
index f9e63d6b00b..84df0f82a3e 100644
--- a/doc/development/documentation/styleguide/index.md
+++ b/doc/development/documentation/styleguide/index.md
@@ -526,7 +526,7 @@ You can use the following fake tokens as examples:
### Usage list
<!-- vale off -->
-| {::nomarkdown}<div style="width:140px">Usage</div>{:/} | Guidance |
+| Usage | Guidance |
|-----------------------|-----|
| and/or | Use **or** instead, or another sensible construction. |
| currently | Do not use when talking about the product or its features. The documentation describes the product as it is today. |
diff --git a/doc/operations/incident_management/alert_integrations.md b/doc/operations/incident_management/alert_integrations.md
index 0bd423570f5..70c4e7f2f29 100644
--- a/doc/operations/incident_management/alert_integrations.md
+++ b/doc/operations/incident_management/alert_integrations.md
@@ -63,7 +63,7 @@ and you can [customize the payload](#customize-the-alert-payload-outside-of-gitl
for the webhook configuration. You must also input the URL and Authorization Key
in your external service.
1. _(Optional)_ To generate a test alert to test the new integration, enter a
- sample payload, then click **Save and test alert payload**.Valid JSON is required.
+ sample payload, then click **Save and test alert payload**. Valid JSON is required.
1. Click **Save Integration**.
The new HTTP Endpoint displays in the [integrations list](#integrations-list).
diff --git a/doc/user/admin_area/analytics/dev_ops_report.md b/doc/user/admin_area/analytics/dev_ops_report.md
index 578b8315625..c240c940363 100644
--- a/doc/user/admin_area/analytics/dev_ops_report.md
+++ b/doc/user/admin_area/analytics/dev_ops_report.md
@@ -50,7 +50,7 @@ The DevOps Adoption tab shows you which segments of your organization are using
- Deploys
- Scanning
-Segments are arbitrary collections of GitLab groups and projects that you define. You might define a segment to represent a small team, a large department, or a whole organization. You are limited to creating a maximum of 20 segments, and each segment is limited to a maximum of 20 groups. Buttons to manage your segments appear in the DevOps Adoption section of the page.
+Segments are arbitrary collections of GitLab groups that you define. You might define a segment to represent a small team, a large department, or a whole organization. You are limited to creating a maximum of 20 segments, and each segment is limited to a maximum of 20 groups. Buttons to manage your segments appear in the DevOps Adoption section of the page.
DevOps Adoption allows you to:
diff --git a/doc/user/admin_area/settings/user_and_ip_rate_limits.md b/doc/user/admin_area/settings/user_and_ip_rate_limits.md
index 61ff93ee0a5..3f0d75dc682 100644
--- a/doc/user/admin_area/settings/user_and_ip_rate_limits.md
+++ b/doc/user/admin_area/settings/user_and_ip_rate_limits.md
@@ -82,6 +82,41 @@ are marked with `"throttle_safelist":"throttle_user_allowlist"` in
At application startup, the allowlist is logged in [`auth.log`](../../../administration/logs.md#authlog).
+## Trying out throttling settings before enforcing them
+
+> [Introduced](https://gitlab.com/gitlab-com/gl-infra/scalability/-/issues/629) in GitLab 13.6.
+
+Trying out throttling settings can be done by setting the
+`GITLAB_THROTTLE_DRY_RUN` environment variable to a comma-separated
+list of throttle names.
+
+The possible names are:
+
+- `throttle_unauthenticated`
+- `throttle_authenticated_api`
+- `throttle_authenticated_web`
+- `throttle_unauthenticated_protected_paths`
+- `throttle_authenticated_protected_paths_api`
+- `throttle_authenticated_protected_paths_web`
+
+For example: trying out throttles for all authenticated requests to
+non-protected paths could be done by setting
+`GITLAB_THROTTLE_DRY_RUN='throttle_authenticated_web,throttle_authenticated_api'`.
+
+To enable the dry-run mode for all throttles, the variable can be set
+to `*`.
+
+Setting a throttle to dry-run mode will log a message to the
+[`auth.log`](../../../administration/logs.md#authlog) when it would
+hit the limit, while letting the request continue as normal. The log
+message will contain an `env` field set to `track`. The `matched`
+field will contain the name of throttle that was hit.
+
+It is important to set the environment variable **before** enabling
+the rate limiting in the settings. The settings in the admin panel
+take effect immediately, while setting the environment variable
+requires a restart of all the Puma processes.
+
<!-- ## Troubleshooting
Include any troubleshooting steps that you can foresee. If you know beforehand what issues
diff --git a/doc/user/group/epics/index.md b/doc/user/group/epics/index.md
index 9bfa9fa9191..c76b07481b2 100644
--- a/doc/user/group/epics/index.md
+++ b/doc/user/group/epics/index.md
@@ -79,14 +79,10 @@ to:
> - [Introduced](https://gitlab.com/gitlab-org/gitlab/-/issues/199184) in [GitLab Ultimate](https://about.gitlab.com/pricing/) 12.10.
> - The health status of a closed issue [is hidden](https://gitlab.com/gitlab-org/gitlab/-/issues/220867) in [GitLab Ultimate](https://about.gitlab.com/pricing/) 13.3 or later.
+> - [Feature flag removed](https://gitlab.com/gitlab-org/gitlab/-/issues/213567) in GitLab 13.7.
Report or respond to the health of issues and epics by setting a red, amber, or green [health status](../../project/issues/index.md#health-status), which then appears on your Epic tree.
-### Disable Issue health status in Epic tree
-
-This feature comes with a feature flag enabled by default. For steps to disable it, see
-[Disable issue health status](../../project/issues/index.md#disable-issue-health-status).
-
## Multi-level child epics **(ULTIMATE)**
> [Introduced](https://gitlab.com/gitlab-org/gitlab/-/issues/8333) in GitLab Ultimate 11.7.
diff --git a/doc/user/packages/package_registry/index.md b/doc/user/packages/package_registry/index.md
index 0ff64368b81..5876ef19ad9 100644
--- a/doc/user/packages/package_registry/index.md
+++ b/doc/user/packages/package_registry/index.md
@@ -94,4 +94,3 @@ The **Packages & Registries > Package Registry** entry is removed from the sideb
Learn how to use the GitLab Package Registry to build your own custom package workflow.
- [Use a project as a package registry](../workflows/project_registry.md) to publish all of your packages to one project.
-- Publish multiple different packages from one [monorepo project](../workflows/monorepo.md).
diff --git a/doc/user/packages/workflows/monorepo.md b/doc/user/packages/workflows/monorepo.md
index 009b372ad17..abba9df6ec2 100644
--- a/doc/user/packages/workflows/monorepo.md
+++ b/doc/user/packages/workflows/monorepo.md
@@ -1,120 +1,9 @@
---
-stage: Package
-group: Package
-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
+redirect_to: '../npm_registry/index.md'
+disqus_identifier: 'https://docs.gitlab.com/ee/user/packages/workflows/monorepo.html'
---
-# Monorepo package management workflows
+This document was moved to [another location](../npm_registry/index.md).
-Oftentimes, one project or Git repository may contain multiple different
-sub-projects or submodules that all get packaged and published individually.
-
-## Publishing different packages to the parent project
-
-The number and name of packages you can publish to one project is not limited.
-You can accomplish this by setting up different configuration files for each
-package. See the documentation for the package manager of your choice since
-each has its own specific files and instructions to follow to publish
-a given package.
-
-Here, we take a walk through how to do this with [NPM](../npm_registry/index.md).
-
-Let us say we have a project structure like so:
-
-```plaintext
-MyProject/
- |- src/
- | |- components/
- | |- Foo/
- |- package.json
-```
-
-`MyProject` is the parent project, which contains a sub-project `Foo` in the
-`components` directory. We would like to publish packages for both `MyProject`
-as well as `Foo`.
-
-Following the instructions in the
-[GitLab NPM registry documentation](../npm_registry/index.md),
-publishing `MyProject` consists of modifying the `package.json` file with a
-`publishConfig` section, as well as either modifying your local NPM configuration with
-CLI commands like `npm config set`, or saving a `.npmrc` file in the root of the
-project specifying these configuration settings.
-
-If you follow the instructions you can publish `MyProject` by running
-`npm publish` from the root directory.
-
-Publishing `Foo` is almost exactly the same, you simply have to follow the steps
-while in the `Foo` directory. `Foo` needs its own `package.json` file,
-which can be added manually or using `npm init`. It also needs its own
-configuration settings. Since you are publishing to the same place, if you
-used `npm config set` to set the registry for the parent project, then no
-additional setup is necessary. If you used a `.npmrc` file, you need an
-additional `.npmrc` file in the `Foo` directory (be sure to add `.npmrc` files
-to the `.gitignore` file or use environment variables in place of your access
-tokens to prevent them from being exposed). It can be identical to the
-one you used in `MyProject`. You can now run `npm publish` from the `Foo`
-directory and you can publish `Foo` separately from `MyProject`
-
-A similar process could be followed for Conan packages, instead of dealing with
-`.npmrc` and `package.json`, you just deal with `conanfile.py` in
-multiple locations within the project.
-
-## Publishing to other projects
-
-A package is associated with a project on GitLab, but the package does not
-need to be associated with the code in that project. Notice when configuring
-NPM or Maven, you only use the `Project ID` to set the registry URL that the
-package is to be uploaded to. If you set this to any project that you have
-access to and update any other configuration similarly depending on the package type,
-your packages are published to that project. This means you can publish
-multiple packages to one project, even if their code does not exist in the same
-place. See the [project registry workflow documentation](project_registry.md)
-for more details.
-
-## CI workflows for automating packaging
-
-CI pipelines open an entire world of possibilities for dealing with the patterns
-described in the previous sections. A common desire would be to publish
-specific packages only if changes were made to those directories.
-
-Using the example project above, this `gitlab-ci.yml` file publishes
-`Foo` anytime changes are made to the `Foo` directory on the `master` branch,
-and publish `MyPackage` anytime changes are made to anywhere _except_ the `Foo`
-directory on the `master` branch.
-
-```yaml
-image: node:latest
-
-stages:
- - build
-
-build-foo-package:
- stage: build
- variables:
- PACKAGE: "Foo"
- script:
- - cd src/components/Foo
- - echo "Building $PACKAGE"
- - npm publish
- only:
- refs:
- - master
- - merge_requests
- changes:
- - "src/components/Foo/**/*"
-
-build-my-project-package:
- stage: build
- variables:
- PACKAGE: "MyPackage"
- script:
- - echo "Building $PACKAGE"
- - npm publish
- only:
- refs:
- - master
- - merge_requests
- except:
- changes:
- - "src/components/Foo/**/*"
-```
+<!-- This redirect file can be deleted after <2021-02-14>. -->
+<!-- Before deletion, see: https://docs.gitlab.com/ee/development/documentation/#move-or-rename-a-page -->
diff --git a/doc/user/project/integrations/webhooks.md b/doc/user/project/integrations/webhooks.md
index e01934897d4..2ab7067713d 100644
--- a/doc/user/project/integrations/webhooks.md
+++ b/doc/user/project/integrations/webhooks.md
@@ -1387,6 +1387,7 @@ X-Gitlab-Event: Member Hook
"user_id": 64,
"group_access": "Guest",
"group_plan": null,
+ "expires_at": "2020-12-14T00:00:00Z",
"event_name": "user_add_to_group"
}
```
diff --git a/doc/user/project/issues/index.md b/doc/user/project/issues/index.md
index 3016ea9cc14..16208c9196e 100644
--- a/doc/user/project/issues/index.md
+++ b/doc/user/project/issues/index.md
@@ -4,7 +4,7 @@ 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
---
-# Issues
+# Issues **(CORE)**
Issues are the fundamental medium for collaborating on ideas and planning work in GitLab.
@@ -191,6 +191,7 @@ requires [GraphQL](../../../api/graphql/index.md) to be enabled.
> - [Introduced](https://gitlab.com/gitlab-org/gitlab/-/issues/36427) in [GitLab Ultimate](https://about.gitlab.com/pricing/) 12.10.
> - Health status of closed issues [can't be edited](https://gitlab.com/gitlab-org/gitlab/-/issues/220867) in [GitLab Ultimate](https://about.gitlab.com/pricing/) 13.4 and later.
> - Issue health status visible in issue lists [introduced](https://gitlab.com/gitlab-org/gitlab/-/merge_requests/45141) in [GitLab Ultimate](https://about.gitlab.com/pricing/) 13.6.
+> - [Feature flag removed](https://gitlab.com/gitlab-org/gitlab/-/issues/213567) in GitLab 13.7.
To help you track the status of your issues, you can assign a status to each issue to flag work
that's progressing as planned or needs attention to keep on schedule:
@@ -207,16 +208,6 @@ until the issue is reopened.
You can then see issue statuses in the [issue list](#issues-list) and the
[Epic tree](../../group/epics/index.md#issue-health-status-in-epic-tree).
-#### Disable issue health status
-
-This feature comes with the `:save_issuable_health_status` feature flag enabled by default. However, in some cases
-this feature is incompatible with old configuration. To turn off the feature while configuration is
-migrated, ask a GitLab administrator with Rails console access to run the following command:
-
-```ruby
-Feature.disable(:save_issuable_health_status)
-```
-
## Other Issue actions
- [Create an issue from a template](../../project/description_templates.md#using-the-templates)
diff --git a/doc/user/project/issues/sorting_issue_lists.md b/doc/user/project/issues/sorting_issue_lists.md
index 27aa5231a2e..b4cb1c383ba 100644
--- a/doc/user/project/issues/sorting_issue_lists.md
+++ b/doc/user/project/issues/sorting_issue_lists.md
@@ -4,10 +4,21 @@ 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
---
-# Sorting and ordering issue lists
+# Sorting and ordering issue lists **(CORE)**
-You can sort a list of issues several ways, including by issue creation date, milestone due date,
-etc. The available sorting options can change based on the context of the list.
+You can sort a list of issues several ways, including by:
+
+- Blocking
+- Created date
+- Due date
+- Label priority
+- Last updated
+- Milestone due date
+- Popularity
+- Priority
+- Weight
+
+The available sorting options can change based on the context of the list.
For sorting by issue priority, see [Label Priority](../labels.md#label-priority).
In group and project issue lists, it is also possible to order issues manually,
@@ -18,19 +29,25 @@ similar to [issue boards](../issue_board.md#how-gitlab-orders-issues-in-a-list).
> [Introduced](https://gitlab.com/gitlab-org/gitlab-foss/-/issues/62178) in GitLab 12.2.
When you select **Manual** sorting, you can change
-the order by dragging and dropping the issues. The changed order will persist. Everyone who visits the same list will see the reordered list, with some exceptions.
+the order by dragging and dropping the issues. The changed order persists, and
+everyone who visits the same list sees the updated issue order, with some exceptions.
Each issue is assigned a relative order value, representing its relative
-order with respect to the other issues in the list. When you drag-and-drop reorder
-an issue, its relative order value changes accordingly.
+order with respect to the other issues on the list. When you drag-and-drop reorder
+an issue, its relative order value changes.
-In addition, any time that issue appears in a manually sorted list,
-the updated relative order value will be used for the ordering. This means that
-if issue `A` is drag-and-drop reordered to be above issue `B` by any user in
-a given list inside your GitLab instance, any time those two issues are subsequently
-loaded in any list in the same instance (could be a different project issue list or a
-different group issue list, for example), that ordering will be maintained.
+In addition, any time an issue appears in a manually sorted list,
+the updated relative order value is used for the ordering.
+So, if anyone drags issue `A` above issue `B` in your GitLab instance,
+this ordering is maintained whenever they appear together in any list.
This ordering also affects [issue boards](../issue_board.md#how-gitlab-orders-issues-in-a-list).
Changing the order in an issue list changes the ordering in an issue board,
and vice versa.
+
+## Sorting by blocking issues
+
+> [Introduced](https://gitlab.com/gitlab-org/gitlab/-/issues/34247/) in GitLab 13.7.
+
+When you select to sort by **Blocking**, the issue list changes to sort descending by the
+number of issues each issue is blocking. You can use this to determine the critical path for your backlog.
diff --git a/lib/banzai/filter/merge_request_reference_filter.rb b/lib/banzai/filter/merge_request_reference_filter.rb
index f05902078dc..0b8bd17a71b 100644
--- a/lib/banzai/filter/merge_request_reference_filter.rb
+++ b/lib/banzai/filter/merge_request_reference_filter.rb
@@ -59,7 +59,7 @@ module Banzai
super(object_sym, tooltip: false)
end
- def data_attributes_for(text, parent, object, data = {})
+ def data_attributes_for(text, parent, object, **data)
super.merge(project_path: parent.full_path, iid: object.iid, mr_title: object.title)
end
diff --git a/lib/gitlab/hook_data/group_member_builder.rb b/lib/gitlab/hook_data/group_member_builder.rb
index ecc83b66327..32cfd032ffe 100644
--- a/lib/gitlab/hook_data/group_member_builder.rb
+++ b/lib/gitlab/hook_data/group_member_builder.rb
@@ -19,6 +19,7 @@ module Gitlab
# :group_access=>"Guest",
# :created_at=>"2020-11-04T10:12:10Z",
# :updated_at=>"2020-11-04T10:12:10Z",
+ # :expires_at=>"2020-12-04T10:12:10Z"
# }
def build(event)
@@ -40,7 +41,8 @@ module Gitlab
user_name: group_member.user.name,
user_email: group_member.user.email,
user_id: group_member.user.id,
- group_access: group_member.human_access
+ group_access: group_member.human_access,
+ expires_at: group_member.expires_at&.xmlschema
}
end
diff --git a/lib/gitlab/import_export/project/sample/relation_tree_restorer.rb b/lib/gitlab/import_export/project/sample/relation_tree_restorer.rb
index 44ccb67a531..4db92b12968 100644
--- a/lib/gitlab/import_export/project/sample/relation_tree_restorer.rb
+++ b/lib/gitlab/import_export/project/sample/relation_tree_restorer.rb
@@ -5,8 +5,8 @@ module Gitlab
module Project
module Sample
class RelationTreeRestorer < ImportExport::RelationTreeRestorer
- def initialize(*args)
- super
+ def initialize(...)
+ super(...)
@date_calculator = Gitlab::ImportExport::Project::Sample::DateCalculator.new(dates)
end
diff --git a/lib/gitlab/import_export/relation_tree_restorer.rb b/lib/gitlab/import_export/relation_tree_restorer.rb
index 919be394ea6..8bc87ecb071 100644
--- a/lib/gitlab/import_export/relation_tree_restorer.rb
+++ b/lib/gitlab/import_export/relation_tree_restorer.rb
@@ -155,7 +155,7 @@ module Gitlab
transform_sub_relations!(data_hash, sub_relation_key, sub_relation_definition, relation_index)
end
- relation = @relation_factory.create(relation_factory_params(relation_key, data_hash))
+ relation = @relation_factory.create(**relation_factory_params(relation_key, data_hash))
if relation && !relation.valid?
@shared.logger.warn(
diff --git a/lib/gitlab/rack_attack.rb b/lib/gitlab/rack_attack.rb
index 390d9d753e6..7c336153e32 100644
--- a/lib/gitlab/rack_attack.rb
+++ b/lib/gitlab/rack_attack.rb
@@ -1,5 +1,8 @@
# frozen_string_literal: true
+# When adding new user-configurable throttles, remember to update the documentation
+# in doc/user/admin_area/settings/user_and_ip_rate_limits.md
+#
# Integration specs for throttling can be found in:
# spec/requests/rack_attack_global_spec.rb
module Gitlab
diff --git a/lib/gitlab/sanitizers/exif.rb b/lib/gitlab/sanitizers/exif.rb
index 78c517c49d8..ed3e32f3e79 100644
--- a/lib/gitlab/sanitizers/exif.rb
+++ b/lib/gitlab/sanitizers/exif.rb
@@ -67,7 +67,7 @@ module Gitlab
batch_size: 1000
}
- relation.find_each(find_params) do |upload|
+ relation.find_each(**find_params) do |upload|
clean(upload.retrieve_uploader, dry_run: dry_run)
sleep sleep_time if sleep_time
rescue => err
diff --git a/lib/tasks/gitlab/db.rake b/lib/tasks/gitlab/db.rake
index 4e5f80cf743..901e349ea31 100644
--- a/lib/tasks/gitlab/db.rake
+++ b/lib/tasks/gitlab/db.rake
@@ -192,11 +192,17 @@ namespace :gitlab do
exit
end
- indexes = if args[:index_name]
- [Gitlab::Database::PostgresIndex.by_identifier(args[:index_name])]
- else
- Gitlab::Database::Reindexing.candidate_indexes
- end
+ indexes = Gitlab::Database::Reindexing.candidate_indexes
+
+ if identifier = args[:index_name]
+ raise ArgumentError, "Index name is not fully qualified with a schema: #{identifier}" unless identifier =~ /^\w+\.\w+$/
+
+ indexes = indexes.where(identifier: identifier)
+
+ raise "Index not found or not supported: #{args[:index_name]}" if indexes.empty?
+ end
+
+ ActiveRecord::Base.logger = Logger.new(STDOUT) if Gitlab::Utils.to_boolean(ENV['LOG_QUERIES_TO_CONSOLE'], default: false)
Gitlab::Database::Reindexing.perform(indexes)
rescue => e
diff --git a/locale/gitlab.pot b/locale/gitlab.pot
index 4401ee1193a..fa308ca4be3 100644
--- a/locale/gitlab.pot
+++ b/locale/gitlab.pot
@@ -4777,9 +4777,21 @@ msgstr ""
msgid "BulkImport|From source group"
msgstr ""
+msgid "BulkImport|Import groups from GitLab"
+msgstr ""
+
+msgid "BulkImport|Importing groups from %{link}"
+msgstr ""
+
+msgid "BulkImport|Importing the group failed"
+msgstr ""
+
msgid "BulkImport|To new group"
msgstr ""
+msgid "BulkImport|Update of import statuses with realtime changes failed"
+msgstr ""
+
msgid "BulkImport|expected an associated Group but has an associated Project"
msgstr ""
@@ -9421,9 +9433,6 @@ msgstr ""
msgid "Describe the goal of the changes and what reviewers should be aware of."
msgstr ""
-msgid "Describe the requirement here"
-msgstr ""
-
msgid "Description"
msgstr ""
@@ -14476,12 +14485,6 @@ msgstr ""
msgid "ImportButtons|Connect repositories from"
msgstr ""
-msgid "ImportGroups|Import groups from GitLab"
-msgstr ""
-
-msgid "ImportGroups|Importing groups from %{link}"
-msgstr ""
-
msgid "ImportProjects|%{provider} rate limit exceeded. Try again later"
msgstr ""
@@ -21088,6 +21091,9 @@ msgstr ""
msgid "Profiles|@username"
msgstr ""
+msgid "Profiles|Account could not be deleted. GitLab was unable to verify your identity."
+msgstr ""
+
msgid "Profiles|Account scheduled for removal."
msgstr ""
@@ -21190,6 +21196,9 @@ msgstr ""
msgid "Profiles|Full name"
msgstr ""
+msgid "Profiles|GitLab is unable to verify your identity automatically."
+msgstr ""
+
msgid "Profiles|Give your individual key a title."
msgstr ""
@@ -21238,6 +21247,9 @@ msgstr ""
msgid "Profiles|Path"
msgstr ""
+msgid "Profiles|Please email %{data_request} to begin the account deletion process."
+msgstr ""
+
msgid "Profiles|Position and size your new avatar"
msgstr ""
@@ -23567,9 +23579,6 @@ msgstr ""
msgid "Requirement %{reference} has been updated"
msgstr ""
-msgid "Requirement title"
-msgstr ""
-
msgid "Requirement title cannot have more than %{limit} characters."
msgstr ""
diff --git a/package.json b/package.json
index fe2017b3ffe..c536c61a8d2 100644
--- a/package.json
+++ b/package.json
@@ -43,6 +43,7 @@
"@babel/preset-env": "^7.10.1",
"@gitlab/at.js": "1.5.5",
"@gitlab/svgs": "1.177.0",
+ "@gitlab/tributejs": "1.0.0",
"@gitlab/ui": "24.8.0",
"@gitlab/visual-review-tools": "1.6.1",
"@rails/actioncable": "^6.0.3-3",
@@ -139,7 +140,6 @@
"tiptap": "^1.8.0",
"tiptap-commands": "^1.4.0",
"tiptap-extensions": "^1.8.0",
- "tributejs": "5.1.3",
"url-loader": "^3.0.0",
"uuid": "8.1.0",
"visibilityjs": "^1.2.4",
diff --git a/spec/factories/projects.rb b/spec/factories/projects.rb
index 413244acaa1..8666a4289e8 100644
--- a/spec/factories/projects.rb
+++ b/spec/factories/projects.rb
@@ -37,6 +37,8 @@ FactoryBot.define do
# we can't assign the delegated `#ci_cd_settings` attributes directly, as the
# `#ci_cd_settings` relation needs to be created first
group_runners_enabled { nil }
+ merge_pipelines_enabled { nil }
+ merge_trains_enabled { nil }
import_status { nil }
import_jid { nil }
import_correlation_id { nil }
@@ -77,7 +79,9 @@ FactoryBot.define do
project.group&.refresh_members_authorized_projects
# assign the delegated `#ci_cd_settings` attributes after create
- project.reload.group_runners_enabled = evaluator.group_runners_enabled unless evaluator.group_runners_enabled.nil?
+ project.group_runners_enabled = evaluator.group_runners_enabled unless evaluator.group_runners_enabled.nil?
+ project.merge_pipelines_enabled = evaluator.merge_pipelines_enabled unless evaluator.merge_pipelines_enabled.nil?
+ project.merge_trains_enabled = evaluator.merge_trains_enabled unless evaluator.merge_trains_enabled.nil?
if evaluator.import_status
import_state = project.import_state || project.build_import_state
diff --git a/spec/features/issues/gfm_autocomplete_spec.rb b/spec/features/issues/gfm_autocomplete_spec.rb
index 06f79f94e8d..07bf821a590 100644
--- a/spec/features/issues/gfm_autocomplete_spec.rb
+++ b/spec/features/issues/gfm_autocomplete_spec.rb
@@ -418,6 +418,46 @@ RSpec.describe 'GFM autocomplete', :js do
end
end
+ context 'when other notes are destroyed' do
+ let!(:discussion) { create(:discussion_note_on_issue, noteable: issue, project: issue.project) }
+
+ # This is meant to protect against this issue https://gitlab.com/gitlab-org/gitlab/-/issues/228729
+ it 'keeps autocomplete key listeners' do
+ visit project_issue_path(project, issue)
+ note = find('#note-body')
+
+ start_comment_with_emoji(note)
+
+ start_and_cancel_discussion
+
+ note.fill_in(with: '')
+ start_comment_with_emoji(note)
+ note.native.send_keys(:enter)
+
+ expect(note.value).to eql('Hello :100: ')
+ end
+
+ def start_comment_with_emoji(note)
+ note.native.send_keys('Hello :10')
+
+ wait_for_requests
+
+ find('.atwho-view li', text: '100')
+ end
+
+ def start_and_cancel_discussion
+ click_button('Reply...')
+
+ fill_in('note_note', with: 'Whoops!')
+
+ page.accept_alert 'Are you sure you want to cancel creating this comment?' do
+ click_button('Cancel')
+ end
+
+ wait_for_requests
+ end
+ end
+
shared_examples 'autocomplete suggestions' do
it 'suggests objects correctly' do
page.within '.timeline-content-form' do
@@ -550,6 +590,15 @@ RSpec.describe 'GFM autocomplete', :js do
expect(find('.tribute-container ul', visible: true)).to have_text('alert milestone')
end
+ it 'does not open autocomplete menu when trigger character is prefixed with text' do
+ page.within '.timeline-content-form' do
+ find('#note-body').native.send_keys('testing')
+ find('#note-body').native.send_keys('@')
+ end
+
+ expect(page).not_to have_selector('.tribute-container', visible: true)
+ end
+
it 'selects the first item for assignee dropdowns' do
page.within '.timeline-content-form' do
find('#note-body').native.send_keys('@')
@@ -618,21 +667,6 @@ RSpec.describe 'GFM autocomplete', :js do
expect(page).to have_selector('.tribute-container', visible: true)
end
- it "does not show dropdown when preceded with a special character" do
- note = find('#note-body')
- page.within '.timeline-content-form' do
- note.native.send_keys("@")
- end
-
- expect(page).to have_selector('.tribute-container', visible: true)
-
- page.within '.timeline-content-form' do
- note.native.send_keys("@")
- end
-
- expect(page).not_to have_selector('.tribute-container')
- end
-
it "does not throw an error if no labels exist" do
note = find('#note-body')
page.within '.timeline-content-form' do
@@ -653,14 +687,6 @@ RSpec.describe 'GFM autocomplete', :js do
expect_to_wrap(false, user_item, note, user.username)
end
- it 'doesn\'t open autocomplete after non-word character' do
- page.within '.timeline-content-form' do
- find('#note-body').native.send_keys("@#{user.username[0..2]}!")
- end
-
- expect(page).not_to have_selector('.tribute-container')
- end
-
it 'triggers autocomplete after selecting a quick action' do
note = find('#note-body')
page.within '.timeline-content-form' do
@@ -848,46 +874,6 @@ RSpec.describe 'GFM autocomplete', :js do
it_behaves_like 'autocomplete suggestions'
end
-
- context 'when other notes are destroyed' do
- let!(:discussion) { create(:discussion_note_on_issue, noteable: issue, project: issue.project) }
-
- # This is meant to protect against this issue https://gitlab.com/gitlab-org/gitlab/-/issues/228729
- it 'keeps autocomplete key listeners' do
- visit project_issue_path(project, issue)
- note = find('#note-body')
-
- start_comment_with_emoji(note)
-
- start_and_cancel_discussion
-
- note.fill_in(with: '')
- start_comment_with_emoji(note)
- note.native.send_keys(:enter)
-
- expect(note.value).to eql('Hello :100: ')
- end
-
- def start_comment_with_emoji(note)
- note.native.send_keys('Hello :10')
-
- wait_for_requests
-
- find('.atwho-view li', text: '100')
- end
-
- def start_and_cancel_discussion
- click_button('Reply...')
-
- fill_in('note_note', with: 'Whoops!')
-
- page.accept_alert 'Are you sure you want to cancel creating this comment?' do
- click_button('Cancel')
- end
-
- wait_for_requests
- end
- end
end
private
diff --git a/spec/frontend/import_entities/import_groups/graphql/client_factory_spec.js b/spec/frontend/import_entities/import_groups/graphql/client_factory_spec.js
index e10231dd68b..cacbe358a62 100644
--- a/spec/frontend/import_entities/import_groups/graphql/client_factory_spec.js
+++ b/spec/frontend/import_entities/import_groups/graphql/client_factory_spec.js
@@ -7,6 +7,7 @@ import {
clientTypenames,
createResolvers,
} from '~/import_entities/import_groups/graphql/client_factory';
+import { StatusPoller } from '~/import_entities/import_groups/graphql/services/status_poller';
import { STATUSES } from '~/import_entities/constants';
import bulkImportSourceGroupsQuery from '~/import_entities/import_groups/graphql/queries/bulk_import_source_groups.query.graphql';
@@ -17,6 +18,12 @@ import importGroupMutation from '~/import_entities/import_groups/graphql/mutatio
import httpStatus from '~/lib/utils/http_status';
import { statusEndpointFixture, availableNamespacesFixture } from './fixtures';
+jest.mock('~/import_entities/import_groups/graphql/services/status_poller', () => ({
+ StatusPoller: jest.fn().mockImplementation(function mock() {
+ this.startPolling = jest.fn();
+ }),
+}));
+
const FAKE_ENDPOINTS = {
status: '/fake_status_url',
availableNamespaces: '/fake_available_namespaces',
@@ -173,6 +180,42 @@ describe('Bulk import resolvers', () => {
expect(intermediateResults[0].status).toBe(STATUSES.SCHEDULING);
});
+
+ it('sets group status to STARTED when request completes', async () => {
+ axiosMockAdapter.onPost(FAKE_ENDPOINTS.createBulkImport).reply(httpStatus.OK);
+ await client.mutate({
+ mutation: importGroupMutation,
+ variables: { sourceGroupId: GROUP_ID },
+ });
+
+ expect(results[0].status).toBe(STATUSES.STARTED);
+ });
+
+ it('starts polling when request completes', async () => {
+ axiosMockAdapter.onPost(FAKE_ENDPOINTS.createBulkImport).reply(httpStatus.OK);
+ await client.mutate({
+ mutation: importGroupMutation,
+ variables: { sourceGroupId: GROUP_ID },
+ });
+ const [statusPoller] = StatusPoller.mock.instances;
+ expect(statusPoller.startPolling).toHaveBeenCalled();
+ });
+
+ it('resets status to NONE if request fails', async () => {
+ axiosMockAdapter
+ .onPost(FAKE_ENDPOINTS.createBulkImport)
+ .reply(httpStatus.INTERNAL_SERVER_ERROR);
+
+ client
+ .mutate({
+ mutation: importGroupMutation,
+ variables: { sourceGroupId: GROUP_ID },
+ })
+ .catch(() => {});
+ await waitForPromises();
+
+ expect(results[0].status).toBe(STATUSES.NONE);
+ });
});
});
});
diff --git a/spec/frontend/import_entities/import_groups/graphql/services/status_poller_spec.js b/spec/frontend/import_entities/import_groups/graphql/services/status_poller_spec.js
new file mode 100644
index 00000000000..8eb1ffb3cd0
--- /dev/null
+++ b/spec/frontend/import_entities/import_groups/graphql/services/status_poller_spec.js
@@ -0,0 +1,213 @@
+import { createMockClient } from 'mock-apollo-client';
+import { InMemoryCache } from 'apollo-cache-inmemory';
+import waitForPromises from 'helpers/wait_for_promises';
+
+import createFlash from '~/flash';
+import { StatusPoller } from '~/import_entities/import_groups/graphql/services/status_poller';
+import bulkImportSourceGroupsQuery from '~/import_entities/import_groups/graphql/queries/bulk_import_source_groups.query.graphql';
+import { STATUSES } from '~/import_entities/constants';
+import { SourceGroupsManager } from '~/import_entities/import_groups/graphql/services/source_groups_manager';
+import { generateFakeEntry } from '../fixtures';
+
+jest.mock('~/flash');
+jest.mock('~/import_entities/import_groups/graphql/services/source_groups_manager', () => ({
+ SourceGroupsManager: jest.fn().mockImplementation(function mock() {
+ this.setImportStatus = jest.fn();
+ }),
+}));
+
+const TEST_POLL_INTERVAL = 1000;
+
+describe('Bulk import status poller', () => {
+ let poller;
+ let clientMock;
+
+ const listQueryCacheCalls = () =>
+ clientMock.readQuery.mock.calls.filter(call => call[0].query === bulkImportSourceGroupsQuery);
+
+ beforeEach(() => {
+ clientMock = createMockClient({
+ cache: new InMemoryCache({
+ fragmentMatcher: { match: () => true },
+ }),
+ });
+
+ jest.spyOn(clientMock, 'readQuery');
+
+ poller = new StatusPoller({
+ client: clientMock,
+ interval: TEST_POLL_INTERVAL,
+ });
+ });
+
+ describe('general behavior', () => {
+ beforeEach(() => {
+ clientMock.cache.writeQuery({
+ query: bulkImportSourceGroupsQuery,
+ data: { bulkImportSourceGroups: [] },
+ });
+ });
+
+ it('does not perform polling when constructed', () => {
+ jest.runOnlyPendingTimers();
+ expect(listQueryCacheCalls()).toHaveLength(0);
+ });
+
+ it('immediately start polling when requested', async () => {
+ await poller.startPolling();
+ expect(listQueryCacheCalls()).toHaveLength(1);
+ });
+
+ it('constantly polls when started', async () => {
+ poller.startPolling();
+ expect(listQueryCacheCalls()).toHaveLength(1);
+
+ jest.advanceTimersByTime(TEST_POLL_INTERVAL);
+ expect(listQueryCacheCalls()).toHaveLength(2);
+
+ jest.advanceTimersByTime(TEST_POLL_INTERVAL);
+ expect(listQueryCacheCalls()).toHaveLength(3);
+ });
+
+ it('does not start polling when requested multiple times', async () => {
+ poller.startPolling();
+ expect(listQueryCacheCalls()).toHaveLength(1);
+
+ poller.startPolling();
+ expect(listQueryCacheCalls()).toHaveLength(1);
+ });
+
+ it('stops polling when requested', async () => {
+ poller.startPolling();
+ expect(listQueryCacheCalls()).toHaveLength(1);
+
+ poller.stopPolling();
+ jest.runOnlyPendingTimers();
+ expect(listQueryCacheCalls()).toHaveLength(1);
+ });
+
+ it('does not query server when list is empty', async () => {
+ jest.spyOn(clientMock, 'query');
+ poller.startPolling();
+ expect(clientMock.query).not.toHaveBeenCalled();
+ });
+ });
+
+ it('does not query server when no groups have STARTED status', async () => {
+ clientMock.cache.writeQuery({
+ query: bulkImportSourceGroupsQuery,
+ data: {
+ bulkImportSourceGroups: [STATUSES.NONE, STATUSES.FINISHED].map((status, idx) =>
+ generateFakeEntry({ status, id: idx }),
+ ),
+ },
+ });
+
+ jest.spyOn(clientMock, 'query');
+ poller.startPolling();
+ expect(clientMock.query).not.toHaveBeenCalled();
+ });
+
+ describe('when there are groups which have STARTED status', () => {
+ const TARGET_NAMESPACE = 'root';
+
+ const STARTED_GROUP_1 = {
+ status: STATUSES.STARTED,
+ id: 'started1',
+ import_target: {
+ target_namespace: TARGET_NAMESPACE,
+ new_name: 'group1',
+ },
+ };
+
+ const STARTED_GROUP_2 = {
+ status: STATUSES.STARTED,
+ id: 'started2',
+ import_target: {
+ target_namespace: TARGET_NAMESPACE,
+ new_name: 'group2',
+ },
+ };
+
+ const NOT_STARTED_GROUP = {
+ status: STATUSES.NONE,
+ id: 'not_started',
+ import_target: {
+ target_namespace: TARGET_NAMESPACE,
+ new_name: 'group3',
+ },
+ };
+
+ it('query server only for groups with STATUSES.STARTED', async () => {
+ clientMock.cache.writeQuery({
+ query: bulkImportSourceGroupsQuery,
+ data: {
+ bulkImportSourceGroups: [STARTED_GROUP_1, NOT_STARTED_GROUP, STARTED_GROUP_2].map(group =>
+ generateFakeEntry(group),
+ ),
+ },
+ });
+
+ clientMock.query = jest.fn().mockResolvedValue({ data: {} });
+ poller.startPolling();
+
+ expect(clientMock.query).toHaveBeenCalledTimes(1);
+ await waitForPromises();
+ const [[doc]] = clientMock.query.mock.calls;
+ const { selections } = doc.query.definitions[0].selectionSet;
+ expect(selections.every(field => field.name.value === 'group')).toBeTruthy();
+ expect(selections).toHaveLength(2);
+ expect(selections.map(sel => sel.arguments[0].value.value)).toStrictEqual([
+ `${TARGET_NAMESPACE}/${STARTED_GROUP_1.import_target.new_name}`,
+ `${TARGET_NAMESPACE}/${STARTED_GROUP_2.import_target.new_name}`,
+ ]);
+ });
+
+ it('updates statuses only for groups in response', async () => {
+ clientMock.cache.writeQuery({
+ query: bulkImportSourceGroupsQuery,
+ data: {
+ bulkImportSourceGroups: [STARTED_GROUP_1, STARTED_GROUP_2].map(group =>
+ generateFakeEntry(group),
+ ),
+ },
+ });
+
+ clientMock.query = jest.fn().mockResolvedValue({ data: { group0: {} } });
+ poller.startPolling();
+ await waitForPromises();
+ const [managerInstance] = SourceGroupsManager.mock.instances;
+ expect(managerInstance.setImportStatus).toHaveBeenCalledTimes(1);
+ expect(managerInstance.setImportStatus).toHaveBeenCalledWith(
+ expect.objectContaining({ id: STARTED_GROUP_1.id }),
+ STATUSES.FINISHED,
+ );
+ });
+
+ describe('when error occurs', () => {
+ beforeEach(() => {
+ clientMock.cache.writeQuery({
+ query: bulkImportSourceGroupsQuery,
+ data: {
+ bulkImportSourceGroups: [STARTED_GROUP_1, STARTED_GROUP_2].map(group =>
+ generateFakeEntry(group),
+ ),
+ },
+ });
+
+ clientMock.query = jest.fn().mockRejectedValue(new Error('dummy error'));
+ poller.startPolling();
+ return waitForPromises();
+ });
+
+ it('reports an error', () => {
+ expect(createFlash).toHaveBeenCalled();
+ });
+
+ it('continues polling', async () => {
+ jest.advanceTimersByTime(TEST_POLL_INTERVAL);
+ expect(listQueryCacheCalls()).toHaveLength(2);
+ });
+ });
+ });
+});
diff --git a/spec/frontend/vue_shared/components/gfm_autocomplete/gfm_autocomplete_spec.js b/spec/frontend/vue_shared/components/gfm_autocomplete/gfm_autocomplete_spec.js
index d12faacca75..b4002fdf4ec 100644
--- a/spec/frontend/vue_shared/components/gfm_autocomplete/gfm_autocomplete_spec.js
+++ b/spec/frontend/vue_shared/components/gfm_autocomplete/gfm_autocomplete_spec.js
@@ -1,5 +1,5 @@
+import Tribute from '@gitlab/tributejs';
import { shallowMount } from '@vue/test-utils';
-import Tribute from 'tributejs';
import GfmAutocomplete from '~/vue_shared/components/gfm_autocomplete/gfm_autocomplete.vue';
describe('GfmAutocomplete', () => {
diff --git a/spec/lib/gitlab/hook_data/group_member_builder_spec.rb b/spec/lib/gitlab/hook_data/group_member_builder_spec.rb
index bc8de140709..78c62fd23c7 100644
--- a/spec/lib/gitlab/hook_data/group_member_builder_spec.rb
+++ b/spec/lib/gitlab/hook_data/group_member_builder_spec.rb
@@ -4,14 +4,14 @@ require 'spec_helper'
RSpec.describe Gitlab::HookData::GroupMemberBuilder do
let_it_be(:group) { create(:group) }
- let_it_be(:group_member) { create(:group_member, :developer, group: group) }
+ let_it_be(:group_member) { create(:group_member, :developer, group: group, expires_at: 1.day.from_now) }
describe '#build' do
let(:data) { described_class.new(group_member).build(event) }
let(:event_name) { data[:event_name] }
let(:attributes) do
[
- :event_name, :created_at, :updated_at, :group_name, :group_path,
+ :event_name, :created_at, :updated_at, :expires_at, :group_name, :group_path,
:group_id, :user_id, :user_username, :user_name, :user_email, :group_access
]
end
@@ -31,6 +31,7 @@ RSpec.describe Gitlab::HookData::GroupMemberBuilder do
expect(data[:group_access]).to eq('Developer')
expect(data[:created_at]).to eq(group_member.created_at&.xmlschema)
expect(data[:updated_at]).to eq(group_member.updated_at&.xmlschema)
+ expect(data[:expires_at]).to eq(group_member.expires_at&.xmlschema)
end
end
diff --git a/spec/models/user_spec.rb b/spec/models/user_spec.rb
index f548732645c..fb05c9e8052 100644
--- a/spec/models/user_spec.rb
+++ b/spec/models/user_spec.rb
@@ -3098,6 +3098,14 @@ RSpec.describe User do
end
end
+ describe '#can_remove_self?' do
+ let(:user) { create(:user) }
+
+ it 'returns true' do
+ expect(user.can_remove_self?).to eq true
+ end
+ end
+
describe "#recent_push" do
let(:user) { build(:user) }
let(:project) { build(:project) }
diff --git a/spec/services/jira/requests/projects/list_service_spec.rb b/spec/services/jira/requests/projects/list_service_spec.rb
index f7bcfa997df..0fff51b1226 100644
--- a/spec/services/jira/requests/projects/list_service_spec.rb
+++ b/spec/services/jira/requests/projects/list_service_spec.rb
@@ -3,6 +3,8 @@
require 'spec_helper'
RSpec.describe Jira::Requests::Projects::ListService do
+ include AfterNextHelpers
+
let(:jira_service) { create(:jira_service) }
let(:params) { {} }
@@ -33,15 +35,17 @@ RSpec.describe Jira::Requests::Projects::ListService do
context 'with jira_service' do
context 'when validations and params are ok' do
- let(:client) { double(options: { site: 'https://jira.example.com' }) }
+ let(:response_headers) { { 'content-type' => 'application/json' } }
+ let(:response_body) { [].to_json }
+ let(:expected_url_pattern) { /.*jira.example.com\/rest\/api\/2\/project/ }
before do
- expect(service).to receive(:client).at_least(:once).and_return(client)
+ stub_request(:get, expected_url_pattern).to_return(status: 200, body: response_body, headers: response_headers)
end
context 'when the request to Jira returns an error' do
before do
- expect(client).to receive(:get).and_raise(Timeout::Error)
+ expect_next(JIRA::Client).to receive(:get).and_raise(Timeout::Error)
end
it 'returns an error response' do
@@ -54,10 +58,17 @@ RSpec.describe Jira::Requests::Projects::ListService do
end
end
- context 'when the request does not return any values' do
- before do
- expect(client).to receive(:get).and_return([])
+ context 'when jira runs on a subpath' do
+ let(:jira_service) { create(:jira_service, url: 'http://jira.example.com/jira') }
+ let(:expected_url_pattern) { /.*jira.example.com\/jira\/rest\/api\/2\/project/ }
+
+ it 'takes the subpath into account' do
+ expect(subject.success?).to be_truthy
end
+ end
+
+ context 'when the request does not return any values' do
+ let(:response_body) { [].to_json }
it 'returns a paylod with no projects returned' do
payload = subject.payload
@@ -69,9 +80,7 @@ RSpec.describe Jira::Requests::Projects::ListService do
end
context 'when the request returns values' do
- before do
- expect(client).to receive(:get).and_return([{ 'key' => 'pr1', 'name' => 'First Project' }, { 'key' => 'pr2', 'name' => 'Second Project' }])
- end
+ let(:response_body) { [{ 'key' => 'pr1', 'name' => 'First Project' }, { 'key' => 'pr2', 'name' => 'Second Project' }].to_json }
it 'returns a paylod with Jira projects' do
payload = subject.payload
diff --git a/spec/tasks/gitlab/db_rake_spec.rb b/spec/tasks/gitlab/db_rake_spec.rb
index cdff5577157..edfdb44022b 100644
--- a/spec/tasks/gitlab/db_rake_spec.rb
+++ b/spec/tasks/gitlab/db_rake_spec.rb
@@ -246,17 +246,25 @@ RSpec.describe 'gitlab:db namespace rake task' do
context 'with index name given' do
let(:index) { double('index') }
+ before do
+ allow(Gitlab::Database::Reindexing).to receive(:candidate_indexes).and_return(indexes)
+ end
+
it 'calls the index rebuilder with the proper arguments' do
- expect(Gitlab::Database::PostgresIndex).to receive(:by_identifier).with('public.foo_idx').and_return(index)
+ allow(indexes).to receive(:where).with(identifier: 'public.foo_idx').and_return([index])
expect(Gitlab::Database::Reindexing).to receive(:perform).with([index])
run_rake_task('gitlab:db:reindex', '[public.foo_idx]')
end
it 'raises an error if the index does not exist' do
- expect(Gitlab::Database::PostgresIndex).to receive(:by_identifier).with('public.absent_index').and_raise(ActiveRecord::RecordNotFound)
+ allow(indexes).to receive(:where).with(identifier: 'public.absent_index').and_return([])
+
+ expect { run_rake_task('gitlab:db:reindex', '[public.absent_index]') }.to raise_error(/Index not found/)
+ end
- expect { run_rake_task('gitlab:db:reindex', '[public.absent_index]') }.to raise_error(ActiveRecord::RecordNotFound)
+ it 'raises an error if the index is not fully qualified with a schema' do
+ expect { run_rake_task('gitlab:db:reindex', '[foo_idx]') }.to raise_error(/Index name is not fully qualified/)
end
end
end
diff --git a/yarn.lock b/yarn.lock
index 5d69f586cf6..ebf083dad5b 100644
--- a/yarn.lock
+++ b/yarn.lock
@@ -866,6 +866,11 @@
resolved "https://registry.yarnpkg.com/@gitlab/svgs/-/svgs-1.177.0.tgz#e481ed327a11d3834c8b1668d7485b9eefef97f5"
integrity sha512-L7DggusgkbubNFCRIYtCuYiLx+t5Hp8y/XIxJ3RM5mqAfxkTR1KxALNLDP9CT7xWieHDhNvgcXAdamGoi0ofDQ==
+"@gitlab/tributejs@1.0.0":
+ version "1.0.0"
+ resolved "https://registry.yarnpkg.com/@gitlab/tributejs/-/tributejs-1.0.0.tgz#672befa222aeffc83e7d799b0500a7a4418e59b8"
+ integrity sha512-nmKw1+hB6MHvlmPz63yPwVs1qQkycHwsKgxpEbzmky16Y6mL4EJMk3w1b8QlOAF/AIAzjCERPhe/R4MJiohbZw==
+
"@gitlab/ui@24.8.0":
version "24.8.0"
resolved "https://registry.yarnpkg.com/@gitlab/ui/-/ui-24.8.0.tgz#dc7daf941ba691e702d607e0a31377a374fdb136"
@@ -11749,11 +11754,6 @@ tr46@^2.0.2:
dependencies:
punycode "^2.1.1"
-tributejs@5.1.3:
- version "5.1.3"
- resolved "https://registry.yarnpkg.com/tributejs/-/tributejs-5.1.3.tgz#980600fc72865be5868893078b4bfde721129eae"
- integrity sha512-B5CXihaVzXw+1UHhNFyAwUTMDk1EfoLP5Tj1VhD9yybZ1I8DZJEv8tZ1l0RJo0t0tk9ZhR8eG5tEsaCvRigmdQ==
-
trim-newlines@^1.0.0:
version "1.0.0"
resolved "https://registry.yarnpkg.com/trim-newlines/-/trim-newlines-1.0.0.tgz#5887966bb582a4503a41eb524f7d35011815a613"