diff options
author | GitLab Bot <gitlab-bot@gitlab.com> | 2020-12-15 21:10:06 +0300 |
---|---|---|
committer | GitLab Bot <gitlab-bot@gitlab.com> | 2020-12-15 21:10:06 +0300 |
commit | b07852468f800d751ddecc9e327119d7295f538e (patch) | |
tree | d1169f95ea3725d609c796a1ca87d5766646852c | |
parent | 0ff373dc416216d02760c7c162ee23382eb1f4a3 (diff) |
Add latest changes from gitlab-org/gitlab@master
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" |