state.diffs.projectPath,
- }),
+ ...mapState('diffs', ['projectPath']),
...mapGetters('diffs', [
'isInlineView',
'isParallelView',
diff --git a/app/assets/javascripts/diffs/components/diff_file.vue b/app/assets/javascripts/diffs/components/diff_file.vue
index d9887c2ce05..ce867dbb9e0 100644
--- a/app/assets/javascripts/diffs/components/diff_file.vue
+++ b/app/assets/javascripts/diffs/components/diff_file.vue
@@ -83,7 +83,7 @@ export default {
computed: {
...mapState('diffs', ['currentDiffFileId', 'codequalityDiff']),
...mapGetters(['isNotesFetched']),
- ...mapGetters('diffs', ['getDiffFileDiscussions']),
+ ...mapGetters('diffs', ['getDiffFileDiscussions', 'isVirtualScrollingEnabled']),
viewBlobHref() {
return escape(this.file.view_path);
},
@@ -290,6 +290,7 @@ export default {
'is-active': currentDiffFileId === file.file_hash,
'comments-disabled': Boolean(file.brokenSymlink),
'has-body': showBody,
+ 'is-virtual-scrolling': isVirtualScrollingEnabled,
}"
:data-path="file.new_path"
class="diff-file file-holder gl-border-none"
diff --git a/app/assets/javascripts/diffs/store/getters.js b/app/assets/javascripts/diffs/store/getters.js
index dec3f87b03e..0a9623c13a3 100644
--- a/app/assets/javascripts/diffs/store/getters.js
+++ b/app/assets/javascripts/diffs/store/getters.js
@@ -170,3 +170,6 @@ export function suggestionCommitMessage(state, _, rootState) {
},
});
}
+
+export const isVirtualScrollingEnabled = (state) =>
+ !state.viewDiffsFileByFile && window.gon?.features?.diffsVirtualScrolling;
diff --git a/app/assets/stylesheets/framework/diffs.scss b/app/assets/stylesheets/framework/diffs.scss
index dd0c0bd9b60..a07e0b48cff 100644
--- a/app/assets/stylesheets/framework/diffs.scss
+++ b/app/assets/stylesheets/framework/diffs.scss
@@ -729,7 +729,7 @@ table.code {
}
.files {
- .diff-file:last-child {
+ .diff-file:not(.is-virtual-scrolling):last-child {
margin-bottom: 0;
}
}
diff --git a/app/assets/stylesheets/page_bundles/merge_requests.scss b/app/assets/stylesheets/page_bundles/merge_requests.scss
index 9fdc30359f8..5e9dd883635 100644
--- a/app/assets/stylesheets/page_bundles/merge_requests.scss
+++ b/app/assets/stylesheets/page_bundles/merge_requests.scss
@@ -7,6 +7,10 @@
.diff-files-holder {
flex: 1;
min-width: 0;
+
+ .vue-recycle-scroller__item-wrapper {
+ overflow: visible;
+ }
}
.with-system-header {
diff --git a/app/controllers/projects/merge_requests_controller.rb b/app/controllers/projects/merge_requests_controller.rb
index d1d83f35d5f..613faa200d1 100644
--- a/app/controllers/projects/merge_requests_controller.rb
+++ b/app/controllers/projects/merge_requests_controller.rb
@@ -42,6 +42,7 @@ class Projects::MergeRequestsController < Projects::MergeRequests::ApplicationCo
push_frontend_feature_flag(:confidential_notes, @project, default_enabled: :yaml)
push_frontend_feature_flag(:usage_data_i_testing_summary_widget_total, @project, default_enabled: :yaml)
push_frontend_feature_flag(:improved_emoji_picker, project, default_enabled: :yaml)
+ push_frontend_feature_flag(:diffs_virtual_scrolling, project, default_enabled: :yaml)
# Usage data feature flags
push_frontend_feature_flag(:users_expanding_widgets_usage_data, @project, default_enabled: :yaml)
diff --git a/app/models/project.rb b/app/models/project.rb
index cd7e49d2c4a..8f6c81485ba 100644
--- a/app/models/project.rb
+++ b/app/models/project.rb
@@ -128,8 +128,41 @@ class Project < ApplicationRecord
after_initialize :use_hashed_storage
after_create :check_repository_absence!
- acts_as_ordered_taggable
- alias_method :topics, :tag_list
+ acts_as_ordered_taggable_on :topics
+ # The 'tag_list' alias and the 'has_many' associations are required during the 'tags -> topics' migration
+ # TODO: eliminate 'tag_list', 'topic_taggings' and 'tags' in the further process of the migration
+ # https://gitlab.com/gitlab-org/gitlab/-/issues/331081
+ alias_attribute :tag_list, :topic_list
+ has_many :topic_taggings, -> { includes(:tag).order("#{ActsAsTaggableOn::Tagging.table_name}.id") },
+ as: :taggable,
+ class_name: 'ActsAsTaggableOn::Tagging',
+ after_add: :dirtify_tag_list,
+ after_remove: :dirtify_tag_list
+ has_many :topics, -> { order("#{ActsAsTaggableOn::Tagging.table_name}.id") },
+ class_name: 'ActsAsTaggableOn::Tag',
+ through: :topic_taggings,
+ source: :tag
+ has_many :tags, -> { order("#{ActsAsTaggableOn::Tagging.table_name}.id") },
+ class_name: 'ActsAsTaggableOn::Tag',
+ through: :topic_taggings,
+ source: :tag
+
+ # Overwriting 'topic_list' and 'topic_list=' is necessary to ensure functionality during the background migration [1].
+ # [1] https://gitlab.com/gitlab-org/gitlab/-/merge_requests/61237
+ # TODO: remove 'topic_list' and 'topic_list=' once the background migration is complete
+ # https://gitlab.com/gitlab-org/gitlab/-/issues/331081
+ def topic_list
+ # Return both old topics (context 'tags') and new topics (context 'topics')
+ tag_list_on('tags') + tag_list_on('topics')
+ end
+
+ def topic_list=(new_tags)
+ # Old topics with context 'tags' are added as new topics with context 'topics'
+ super(new_tags)
+
+ # Remove old topics with context 'tags'
+ set_tag_list_on('tags', '')
+ end
attr_accessor :old_path_with_namespace
attr_accessor :template_name
diff --git a/app/presenters/project_presenter.rb b/app/presenters/project_presenter.rb
index 3726238474f..4f803ba34f4 100644
--- a/app/presenters/project_presenter.rb
+++ b/app/presenters/project_presenter.rb
@@ -401,16 +401,16 @@ class ProjectPresenter < Gitlab::View::Presenter::Delegated
end
def topics_to_show
- project.topics.take(MAX_TOPICS_TO_SHOW) # rubocop: disable CodeReuse/ActiveRecord
+ project.topic_list.take(MAX_TOPICS_TO_SHOW) # rubocop: disable CodeReuse/ActiveRecord
end
def topics_not_shown
- project.topics - topics_to_show
+ project.topic_list - topics_to_show
end
def count_of_extra_topics_not_shown
- if project.topics.count > MAX_TOPICS_TO_SHOW
- project.topics.count - MAX_TOPICS_TO_SHOW
+ if project.topic_list.count > MAX_TOPICS_TO_SHOW
+ project.topic_list.count - MAX_TOPICS_TO_SHOW
else
0
end
diff --git a/changelogs/unreleased/project-topics-data-migration.yml b/changelogs/unreleased/project-topics-data-migration.yml
new file mode 100644
index 00000000000..4e77d4ea247
--- /dev/null
+++ b/changelogs/unreleased/project-topics-data-migration.yml
@@ -0,0 +1,5 @@
+---
+title: Migrate 'tags' to 'topics' for project in the database context
+merge_request: 61237
+author: Jonas Wälter @wwwjon
+type: changed
diff --git a/config/feature_flags/development/diffs_virtual_scrolling.yml b/config/feature_flags/development/diffs_virtual_scrolling.yml
new file mode 100644
index 00000000000..2a36c3f0a8b
--- /dev/null
+++ b/config/feature_flags/development/diffs_virtual_scrolling.yml
@@ -0,0 +1,8 @@
+---
+name: diffs_virtual_scrolling
+introduced_by_url:
+rollout_issue_url:
+milestone: '13.12'
+type: development
+group: group::code review
+default_enabled: false
diff --git a/db/post_migrate/20210511095657_add_temporary_index_for_project_topics_to_taggings.rb b/db/post_migrate/20210511095657_add_temporary_index_for_project_topics_to_taggings.rb
new file mode 100644
index 00000000000..0d807df614c
--- /dev/null
+++ b/db/post_migrate/20210511095657_add_temporary_index_for_project_topics_to_taggings.rb
@@ -0,0 +1,19 @@
+# frozen_string_literal: true
+
+class AddTemporaryIndexForProjectTopicsToTaggings < ActiveRecord::Migration[6.0]
+ include Gitlab::Database::MigrationHelpers
+
+ INDEX_NAME = 'tmp_index_taggings_on_id_where_taggable_type_project_and_tags'
+ INDEX_CONDITION = "taggable_type = 'Project' AND context = 'tags'"
+
+ disable_ddl_transaction!
+
+ def up
+ # this index is used in 20210511095658_schedule_migrate_project_taggings_context_from_tags_to_topics
+ add_concurrent_index :taggings, :id, where: INDEX_CONDITION, name: INDEX_NAME
+ end
+
+ def down
+ remove_concurrent_index_by_name :taggings, INDEX_NAME
+ end
+end
diff --git a/db/post_migrate/20210511095658_schedule_migrate_project_taggings_context_from_tags_to_topics.rb b/db/post_migrate/20210511095658_schedule_migrate_project_taggings_context_from_tags_to_topics.rb
new file mode 100644
index 00000000000..25d23b771d5
--- /dev/null
+++ b/db/post_migrate/20210511095658_schedule_migrate_project_taggings_context_from_tags_to_topics.rb
@@ -0,0 +1,30 @@
+# frozen_string_literal: true
+
+class ScheduleMigrateProjectTaggingsContextFromTagsToTopics < ActiveRecord::Migration[6.0]
+ include Gitlab::Database::MigrationHelpers
+
+ DOWNTIME = false
+ BATCH_SIZE = 30_000
+ DELAY_INTERVAL = 2.minutes
+ MIGRATION = 'MigrateProjectTaggingsContextFromTagsToTopics'
+
+ disable_ddl_transaction!
+
+ class Tagging < ActiveRecord::Base
+ include ::EachBatch
+
+ self.table_name = 'taggings'
+ end
+
+ def up
+ queue_background_migration_jobs_by_range_at_intervals(
+ Tagging.where(taggable_type: 'Project', context: 'tags'),
+ MIGRATION,
+ DELAY_INTERVAL,
+ batch_size: BATCH_SIZE
+ )
+ end
+
+ def down
+ end
+end
diff --git a/db/post_migrate/20210517075444_remove_temporary_index_for_project_topics_to_taggings.rb b/db/post_migrate/20210517075444_remove_temporary_index_for_project_topics_to_taggings.rb
new file mode 100644
index 00000000000..bfd09653695
--- /dev/null
+++ b/db/post_migrate/20210517075444_remove_temporary_index_for_project_topics_to_taggings.rb
@@ -0,0 +1,19 @@
+# frozen_string_literal: true
+
+class RemoveTemporaryIndexForProjectTopicsToTaggings < ActiveRecord::Migration[6.0]
+ include Gitlab::Database::MigrationHelpers
+
+ INDEX_NAME = 'tmp_index_taggings_on_id_where_taggable_type_project_and_tags'
+ INDEX_CONDITION = "taggable_type = 'Project' AND context = 'tags'"
+
+ disable_ddl_transaction!
+
+ def up
+ # this index was used in 20210511095658_schedule_migrate_project_taggings_context_from_tags_to_topics
+ remove_concurrent_index_by_name :taggings, INDEX_NAME
+ end
+
+ def down
+ add_concurrent_index :taggings, :id, where: INDEX_CONDITION, name: INDEX_NAME
+ end
+end
diff --git a/db/schema_migrations/20210511095657 b/db/schema_migrations/20210511095657
new file mode 100644
index 00000000000..503ccfb459a
--- /dev/null
+++ b/db/schema_migrations/20210511095657
@@ -0,0 +1 @@
+4d11cdf876786db5e827ea1a50b70e2d5b3814fd7c0b0c083ab61adad9685364
\ No newline at end of file
diff --git a/db/schema_migrations/20210511095658 b/db/schema_migrations/20210511095658
new file mode 100644
index 00000000000..d3fa692768d
--- /dev/null
+++ b/db/schema_migrations/20210511095658
@@ -0,0 +1 @@
+7387c23bbbc376e26c057179ebe2796be183462acb1fc509d451f0fede13ed93
\ No newline at end of file
diff --git a/db/schema_migrations/20210517075444 b/db/schema_migrations/20210517075444
new file mode 100644
index 00000000000..4b4aee8710c
--- /dev/null
+++ b/db/schema_migrations/20210517075444
@@ -0,0 +1 @@
+ec08c18ac37f2ae7298650df58345755eada20aaa5b7ed3dfd54ee5cea88ebdd
\ No newline at end of file
diff --git a/doc/.vale/gitlab/spelling-exceptions.txt b/doc/.vale/gitlab/spelling-exceptions.txt
index 4c4bbe96a96..05d0e5d4b85 100644
--- a/doc/.vale/gitlab/spelling-exceptions.txt
+++ b/doc/.vale/gitlab/spelling-exceptions.txt
@@ -563,6 +563,7 @@ smartcard
smartcards
snapshotting
Sobelow
+Solargraph
Solarized
Sourcegraph
sparkline
diff --git a/doc/ci/cloud_deployment/ecs/quick_start_guide.md b/doc/ci/cloud_deployment/ecs/quick_start_guide.md
index 2cd9127206e..a801be549df 100644
--- a/doc/ci/cloud_deployment/ecs/quick_start_guide.md
+++ b/doc/ci/cloud_deployment/ecs/quick_start_guide.md
@@ -77,9 +77,9 @@ and [Container Registry](../../../user/packages/container_registry/index.md).
1. Click **Commit Changes**. It automatically triggers a new pipeline. In this pipeline, the `build`
job containerizes the application and pushes the image to [GitLab Container Registry](../../../user/packages/container_registry/index.md).
-
+
![Create project](img/initial-pipeline.png)
-
+
1. Visit **Packages & Registries > Container Registry**. Make sure the application image has been
pushed.
@@ -232,7 +232,7 @@ These variables are injected into the pipeline jobs and can access the ECS API.
Change a file in the project and see if it's reflected in the demo application on ECS:
1. Go to **ecs-demo** project on GitLab.
-1. Open the file at **app > views > welcome > index.html.erb**.
+1. Open the file at **app > views > welcome > `index.html.erb`**.
1. Click **Edit**.
1. Change the text to `You're on ECS!`.
1. Click **Commit Changes**. This automatically triggers a new pipeline. Wait until it finishes.
diff --git a/doc/development/agent/local.md b/doc/development/agent/local.md
index 670315db3a8..50959b5c450 100644
--- a/doc/development/agent/local.md
+++ b/doc/development/agent/local.md
@@ -116,7 +116,7 @@ Before performing any of these tests, if you have a `k3s` instance running, make
stop it manually before running them. Otherwise, the tests might fail with the message
`failed to remove k3s cluster`.
-You might need to specify the correct Agent image version that matches the `kas` image version. You can use the `GITLAB_AGENTK_VERSION` local env for this.
+You might need to specify the correct Agent image version that matches the `kas` image version. You can use the `GITLAB_AGENTK_VERSION` local environment for this.
### Against `staging`
@@ -124,7 +124,7 @@ You might need to specify the correct Agent image version that matches the `kas`
[this line](https://gitlab.com/gitlab-org/gitlab/-/blob/5b15540ea78298a106150c3a1d6ed26416109b9d/qa/qa/service/cluster_provider/k3s.rb#L8) and
[this line](https://gitlab.com/gitlab-org/gitlab/-/blob/5b15540ea78298a106150c3a1d6ed26416109b9d/qa/qa/service/cluster_provider/k3s.rb#L36).
We don't allow local connections on `staging` as they require an admin user.
-1. Ensure you don't have an `EE_LICENSE` env var set as this would force an admin login.
+1. Ensure you don't have an `EE_LICENSE` environment variable set as this would force an admin login.
1. Go to your GDK root folder and `cd gitlab/qa`.
1. Login with your user in staging and create a group to be used as sandbox.
Something like: `username-qa-sandbox`.
diff --git a/doc/development/contributing/style_guides.md b/doc/development/contributing/style_guides.md
index 6ec840d27b9..20e47b501e6 100644
--- a/doc/development/contributing/style_guides.md
+++ b/doc/development/contributing/style_guides.md
@@ -101,7 +101,7 @@ You can check for any offenses locally with `bundle exec rubocop --parallel`.
On the CI, this is automatically checked by the `static-analysis` jobs.
In addition, you can [integrate RuboCop](../developing_with_solargraph.md) into
-supported IDEs using the [solargraph](https://github.com/castwide/solargraph) gem.
+supported IDEs using the [Solargraph](https://github.com/castwide/solargraph) gem.
For RuboCop rules that we have not taken a decision on yet, we follow the
[Ruby Style Guide](https://github.com/rubocop-hq/ruby-style-guide),
diff --git a/doc/development/developing_with_solargraph.md b/doc/development/developing_with_solargraph.md
index 78c3a076a76..877fbad8ab2 100644
--- a/doc/development/developing_with_solargraph.md
+++ b/doc/development/developing_with_solargraph.md
@@ -8,21 +8,21 @@ info: To determine the technical writer assigned to the Stage/Group associated w
Gemfile packages [Solargraph](https://github.com/castwide/solargraph) language server for additional IntelliSense and code formatting capabilities with editors that support it.
-Example configuration for solargraph can be found in [.solargraph.yml.example](https://gitlab.com/gitlab-org/gitlab/-/blob/master/.solargraph.yml.example) file. Copy the contents of this file to `.solargraph.yml` file for language server to pick this configuration up. Since `.solargraph.yml` configuration file is ignored by Git, it's possible to adjust configuration according to your needs.
+Example configuration for Solargraph can be found in [.solargraph.yml.example](https://gitlab.com/gitlab-org/gitlab/-/blob/master/.solargraph.yml.example) file. Copy the contents of this file to `.solargraph.yml` file for language server to pick this configuration up. Since `.solargraph.yml` configuration file is ignored by Git, it's possible to adjust configuration according to your needs.
-Refer to particular IDE plugin documentation on how to integrate it with solargraph language server:
+Refer to particular IDE plugin documentation on how to integrate it with Solargraph language server:
- **Visual Studio Code**
- - GitHub: [vscode-solargraph](https://github.com/castwide/vscode-solargraph)
+ - GitHub: [`vscode-solargraph`](https://github.com/castwide/vscode-solargraph)
- **Atom**
- - GitHub: [atom-solargraph](https://github.com/castwide/atom-solargraph)
+ - GitHub: [`atom-solargraph`](https://github.com/castwide/atom-solargraph)
- **Vim**
- - GitHub: [LanguageClient-neovim](https://github.com/autozimu/LanguageClient-neovim)
+ - GitHub: [`LanguageClient-neovim`](https://github.com/autozimu/LanguageClient-neovim)
- **Emacs**
- - GitHub: [emacs-solargraph](https://github.com/guskovd/emacs-solargraph)
+ - GitHub: [`emacs-solargraph`](https://github.com/guskovd/emacs-solargraph)
- **Eclipse**
- - GitHub: [eclipse-solargraph](https://github.com/PyvesB/eclipse-solargraph)
+ - GitHub: [`eclipse-solargraph`](https://github.com/PyvesB/eclipse-solargraph)
diff --git a/doc/development/experiment_guide/index.md b/doc/development/experiment_guide/index.md
index 15430831f4a..0d534a974a1 100644
--- a/doc/development/experiment_guide/index.md
+++ b/doc/development/experiment_guide/index.md
@@ -59,4 +59,4 @@ feature flags, and there is currently no strong suggestion to use one over the o
Historical Context: `Experimentation Module` was built iteratively with the needs that
appeared while implementing Growth sub-department experiments, while GLEX was built
-with the learnings of the team and an easier to use API.
+with the findings of the team and an easier to use API.
diff --git a/doc/development/fe_guide/graphql.md b/doc/development/fe_guide/graphql.md
index ce51ce65104..49c511c2b85 100644
--- a/doc/development/fe_guide/graphql.md
+++ b/doc/development/fe_guide/graphql.md
@@ -782,7 +782,7 @@ While the Apollo client has support for simple polling, for performance reasons,
Once the backend is set up, there are a few changes to make on the frontend.
-First, get your resource Etag path from the backend. In the example of the pipelines graph, this is called the `graphql_resource_etag`. This will be used to create new headers to add to the Apollo context:
+First, get your resource ETag path from the backend. In the example of the pipelines graph, this is called the `graphql_resource_etag`. This will be used to create new headers to add to the Apollo context:
```javascript
/* pipelines/components/graph/utils.js */
@@ -817,7 +817,7 @@ apollo: {
},
```
-Then, because Etags depend on the request being a `GET` instead of GraphQL's usual `POST`, but our default link library does not support `GET` we need to let our default Apollo client know to use a different library.
+Then, because ETags depend on the request being a `GET` instead of GraphQL's usual `POST`, but our default link library does not support `GET` we need to let our default Apollo client know to use a different library.
```javascript
/* componentMountIndex.js */
diff --git a/doc/development/gemfile.md b/doc/development/gemfile.md
index fcb317e1e88..e071aa4ffd9 100644
--- a/doc/development/gemfile.md
+++ b/doc/development/gemfile.md
@@ -115,7 +115,7 @@ operator](https://thoughtbot.com/blog/rubys-pessimistic-operator))
making it possible to upgrade `license_finder` or any other gem to a
version that depends on `thor 1.2`.
-Simlarly, if `license_finder` had a vulnerability fixed in 6.0.1, we
+Similarly, if `license_finder` had a vulnerability fixed in 6.0.1, we
should add:
```ruby
@@ -127,7 +127,7 @@ still depend on a newer version of `thor`, such as `6.0.2`, but would
not be able to depend on the vulnerable version `6.0.0`.
A downgrade like that could happen if we introduced a new dependency
-that also relied on thor but had its version pinned to a vulnerable
+that also relied on `thor` but had its version pinned to a vulnerable
one. These changes are easy to miss in the `Gemfile.lock`. Pinning the
version would result in a conflict that would need to be solved.
diff --git a/doc/development/go_guide/index.md b/doc/development/go_guide/index.md
index a4ecf216101..1513b65d19f 100644
--- a/doc/development/go_guide/index.md
+++ b/doc/development/go_guide/index.md
@@ -501,7 +501,7 @@ up to run `goimports -local gitlab.com/gitlab-org` so that it's applied to every
### Analyzer Tests
-The conventional Secure [analyzer](https://gitlab.com/gitlab-org/security-products/analyzers/) has a [`convert` function](https://gitlab.com/gitlab-org/security-products/analyzers/command/-/blob/main/convert.go#L15-17) that converts SAST/DAST scanner reports into [GitLab Security Reports](https://gitlab.com/gitlab-org/security-products/security-report-schemas). When writing tests for the `convert` function, we should make use of [test fixtures](https://dave.cheney.net/2016/05/10/test-fixtures-in-go) using a `testdata` directory at the root of the analyzer's repo. The `testdata` directory should contain two subdirectories: `expect` and `reports`. The `reports` directory should contain sample SAST/DAST scanner reports which are passed into the `convert` function during the test setup. The `expect` directory should contain the expected GitLab Security Report that the `convert` returns. See Secret Detection for an [example](https://gitlab.com/gitlab-org/security-products/analyzers/secrets/-/blob/160424589ef1eed7b91b59484e019095bc7233bd/convert_test.go#L13-66).
+The conventional Secure [analyzer](https://gitlab.com/gitlab-org/security-products/analyzers/) has a [`convert` function](https://gitlab.com/gitlab-org/security-products/analyzers/command/-/blob/main/convert.go#L15-17) that converts SAST/DAST scanner reports into [GitLab Security Reports](https://gitlab.com/gitlab-org/security-products/security-report-schemas). When writing tests for the `convert` function, we should make use of [test fixtures](https://dave.cheney.net/2016/05/10/test-fixtures-in-go) using a `testdata` directory at the root of the analyzer's repository. The `testdata` directory should contain two subdirectories: `expect` and `reports`. The `reports` directory should contain sample SAST/DAST scanner reports which are passed into the `convert` function during the test setup. The `expect` directory should contain the expected GitLab Security Report that the `convert` returns. See Secret Detection for an [example](https://gitlab.com/gitlab-org/security-products/analyzers/secrets/-/blob/160424589ef1eed7b91b59484e019095bc7233bd/convert_test.go#L13-66).
If the scanner report is small, less than 35 lines, then feel free to [inline the report](https://gitlab.com/gitlab-org/security-products/analyzers/sobelow/-/blob/8bd2428a/convert/convert_test.go#L13-77) rather than use a `testdata` directory.
diff --git a/doc/development/stage_group_dashboards.md b/doc/development/stage_group_dashboards.md
index d6d607df2dd..44c738092ac 100644
--- a/doc/development/stage_group_dashboards.md
+++ b/doc/development/stage_group_dashboards.md
@@ -25,7 +25,7 @@ The dashboards for stage groups are at a very early stage. All contributions are
Read more about how we are using error budgets overall in our
[handbook](https://about.gitlab.com/handbook/engineering/error-budgets/).
-By default, the first row of panels on the dashbhoard will show the [error
+By default, the first row of panels on the dashboard will show the [error
budget for the stage
group](https://about.gitlab.com/handbook/engineering/error-budgets/#budget-spend-by-stage-group). This
row shows how the features owned by
diff --git a/doc/development/testing_guide/frontend_testing.md b/doc/development/testing_guide/frontend_testing.md
index 67d698096f3..911fbd43989 100644
--- a/doc/development/testing_guide/frontend_testing.md
+++ b/doc/development/testing_guide/frontend_testing.md
@@ -1302,7 +1302,7 @@ A good guideline to follow: the more complex the component you may want to steer
- To capture large data structures just to have something
- To just have some kind of test written
-- To capture highly volatile ui elements without stubbing them (Think of GitLab UI version updates)
+- To capture highly volatile UI elements without stubbing them (Think of GitLab UI version updates)
---
diff --git a/doc/development/usage_ping/product_intelligence_review.md b/doc/development/usage_ping/product_intelligence_review.md
index 3a8f9143b70..0e86a116bca 100644
--- a/doc/development/usage_ping/product_intelligence_review.md
+++ b/doc/development/usage_ping/product_intelligence_review.md
@@ -48,7 +48,7 @@ Product Intelligence files.
[Metrics Dictionary](https://gitlab.com/gitlab-org/gitlab/-/blob/master/doc/development/usage_ping/dictionary.md) if it is needed.
- Add a changelog [according to guidelines](../changelog.md).
-##### When adding or modifiying Snowplow events
+##### When adding or modifying Snowplow events
- For frontend events, when relevant, add a screenshot of the event in
the [testing tool](../snowplow/index.md#developing-and-testing-snowplow) used.
@@ -81,7 +81,7 @@ Any of the Product Intelligence engineers can be assigned for the Product Intell
- Check if a [feature flag is needed](index.md#recommendations).
- For tracking with Snowplow:
- Check that the [event taxonomy](../snowplow/index.md#structured-event-taxonomy) is correct.
- - Check the [usage recomendations](../snowplow/index.md#usage-recommendations).
+ - Check the [usage recommendations](../snowplow/index.md#usage-recommendations).
- Metrics YAML definitions:
- Check the metric `description`.
- Check the metrics `key_path`.
diff --git a/doc/integration/elasticsearch.md b/doc/integration/elasticsearch.md
index dcda23b6337..68e3f6c76c3 100644
--- a/doc/integration/elasticsearch.md
+++ b/doc/integration/elasticsearch.md
@@ -378,7 +378,7 @@ low may lead the reindexing process to take a very long time to complete.
The best value for this will depend on your cluster size, whether you're willing
to accept some degraded search performance during reindexing, and how important
-it is for the reindex to finish quickly and unpause indexing.
+it is for the reindex to finish quickly and resume indexing.
### Mark the most recent reindex job as failed and resume the indexing
diff --git a/doc/update/index.md b/doc/update/index.md
index 5c6f47d2b9c..4c4dfe79d03 100644
--- a/doc/update/index.md
+++ b/doc/update/index.md
@@ -148,7 +148,7 @@ If you upgrade your GitLab instance while the GitLab Runner is processing jobs,
As for the artifacts, the GitLab Runner will attempt to upload them three times, after which the job will eventually fail.
-To address the above two scenario's, it is adviced to do the following prior to upgrading:
+To address the above two scenario's, it is advised to do the following prior to upgrading:
1. Plan your maintenance.
1. Pause your runners.
diff --git a/doc/user/analytics/ci_cd_analytics.md b/doc/user/analytics/ci_cd_analytics.md
index d2295662356..284e87e9b35 100644
--- a/doc/user/analytics/ci_cd_analytics.md
+++ b/doc/user/analytics/ci_cd_analytics.md
@@ -50,7 +50,7 @@ The following table shows the supported metrics, at which level they are support
| Metric | Level | API version | Chart (UI) version | Comments |
| --------------- | ----------- | --------------- | ---------- | ------- |
-| `deployment_frequency` | Project-level | [13.7+](../../api/dora/metrics.md) | [13.8+](#deployment-frequency-charts) | The [old API endopint](../../api/dora4_project_analytics.md) was [deprecated](https://gitlab.com/gitlab-org/gitlab/-/issues/323713) in 13.10. |
+| `deployment_frequency` | Project-level | [13.7+](../../api/dora/metrics.md) | [13.8+](#deployment-frequency-charts) | The [old API endpoint](../../api/dora4_project_analytics.md) was [deprecated](https://gitlab.com/gitlab-org/gitlab/-/issues/323713) in 13.10. |
| `deployment_frequency` | Group-level | [13.10+](../../api/dora/metrics.md) | To be supported | |
| `lead_time_for_changes` | Project-level | [13.10+](../../api/dora/metrics.md) | [13.11+](#lead-time-charts) | Unit in seconds. Aggregation method is median. |
| `lead_time_for_changes` | Group-level | [13.10+](../../api/dora/metrics.md) | To be supported | Unit in seconds. Aggregation method is median. |
diff --git a/doc/user/application_security/container_scanning/index.md b/doc/user/application_security/container_scanning/index.md
index 10d276bd224..5ab56634d29 100644
--- a/doc/user/application_security/container_scanning/index.md
+++ b/doc/user/application_security/container_scanning/index.md
@@ -324,7 +324,7 @@ To allowlist specific vulnerabilities, follow these steps:
1. Set `GIT_STRATEGY: fetch` in your `.gitlab-ci.yml` file by following the instructions in
[overriding the container scanning template](#overriding-the-container-scanning-template).
1. Define the allowlisted vulnerabilities in a YAML file named `vulnerability-allowlist.yml`. This must use
- the format described in [vulnerability-allowlist.yml data format](#vulnerability-allowlistyml-data-format).
+ the format described in [`vulnerability-allowlist.yml` data format](#vulnerability-allowlistyml-data-format).
1. Add the `vulnerability-allowlist.yml` file to the root folder of your project's Git repository.
#### vulnerability-allowlist.yml data format
@@ -365,9 +365,9 @@ This example excludes from `gl-container-scanning-report.json`:
You can specify container image in multiple ways:
- - as image name only (ie. `centos`).
- - as full image name with registry hostname (ie. `your.private.registry:5000/centos`).
- - as full image name with registry hostname and sha256 label (ie. `registry.gitlab.com/gitlab-org/security-products/dast/webgoat-8.0@sha256`).
+ - as image name only (such as `centos`).
+ - as full image name with registry hostname (such as `your.private.registry:5000/centos`).
+ - as full image name with registry hostname and sha256 label (such as `registry.gitlab.com/gitlab-org/security-products/dast/webgoat-8.0@sha256`).
NOTE:
The string after CVE ID (`cups` and `libxml2` in the previous example) is an optional comment format. It has **no impact** on the handling of vulnerabilities. You can include comments to describe the vulnerability.
diff --git a/doc/user/application_security/dast_api/index.md b/doc/user/application_security/dast_api/index.md
index 8596b31f82b..36f34916921 100644
--- a/doc/user/application_security/dast_api/index.md
+++ b/doc/user/application_security/dast_api/index.md
@@ -594,7 +594,7 @@ can be added, removed, and modified by creating a custom configuration.
- Application Information Check
- Cleartext Authentication Check
- FrameworkDebugModeCheck
-- Html Injection Check
+- HTML Injection Check
- Insecure Http Methods Check
- JSON Hijacking Check
- JSON Injection Check
@@ -602,16 +602,16 @@ can be added, removed, and modified by creating a custom configuration.
- Session Cookie Check
- SQL Injection Check
- Token Check
-- Xml Injection Check
+- XML Injection Check
##### Full
- Application Information Check
- Cleartext AuthenticationCheck
-- Cors Check
-- Dns Rebinding Check
+- CORS Check
+- DNS Rebinding Check
- Framework Debug Mode Check
-- Html Injection Check
+- HTML Injection Check
- Insecure Http Methods Check
- JSON Hijacking Check
- JSON Injection Check
@@ -620,9 +620,9 @@ can be added, removed, and modified by creating a custom configuration.
- Sensitive Information Check
- Session Cookie Check
- SQL Injection Check
-- Tls Configuration Check
+- TLS Configuration Check
- Token Check
-- Xml Injection Check
+- XML Injection Check
### Available CI/CD variables
diff --git a/doc/user/application_security/dependency_scanning/index.md b/doc/user/application_security/dependency_scanning/index.md
index 6e290060a1c..8e23db89dfd 100644
--- a/doc/user/application_security/dependency_scanning/index.md
+++ b/doc/user/application_security/dependency_scanning/index.md
@@ -510,7 +510,7 @@ ensure that it can reach your private repository. Here is an example configurati
## Hosting a copy of the gemnasium_db advisory database
-The [gemnasium_db](https://gitlab.com/gitlab-org/security-products/gemnasium-db) Git repository is
+The [`gemnasium_db`](https://gitlab.com/gitlab-org/security-products/gemnasium-db) Git repository is
used by `gemnasium`, `gemnasium-maven`, and `gemnasium-python` as the source of vulnerability data.
This repository updates at scan time to fetch the latest advisories. However, due to a restricted
networking environment, running this update is sometimes not possible. In this case, a user can do
diff --git a/doc/user/application_security/sast/index.md b/doc/user/application_security/sast/index.md
index 23860843ec8..886726d5d67 100644
--- a/doc/user/application_security/sast/index.md
+++ b/doc/user/application_security/sast/index.md
@@ -710,7 +710,7 @@ documentation for instructions.
## Running SAST in SELinux
-By default SAST analyzers are supported in GitLab instances hosted on SELinux. Adding a `before_script` in an [overriden SAST job](#overriding-sast-jobs) may not work as runners hosted on SELinux have restricted permissions.
+By default SAST analyzers are supported in GitLab instances hosted on SELinux. Adding a `before_script` in an [overridden SAST job](#overriding-sast-jobs) may not work as runners hosted on SELinux have restricted permissions.
## Troubleshooting
diff --git a/doc/user/clusters/agent/index.md b/doc/user/clusters/agent/index.md
index ecd35c5b167..5e272f2a816 100644
--- a/doc/user/clusters/agent/index.md
+++ b/doc/user/clusters/agent/index.md
@@ -172,7 +172,7 @@ the Agent in subsequent steps. You can create an Agent record with GraphQL:
WARNING:
GraphQL only displays the token and ids **one time** after creating it. Make sure to write down the `secret`, `clusterAgentId`, and `clusterAgentTokenId`; you'll need them later.
-
+
If you are new to using the GitLab GraphQL API, refer to the
[Getting started with the GraphQL API page](../../../api/graphql/getting_started.md),
or the [GraphQL Explorer](https://gitlab.com/-/graphql-explorer).
@@ -562,7 +562,7 @@ is unknown to the agent. One approach to fixing it is to present the CA certific
via a Kubernetes `configmap` and mount the file in the agent `/etc/ssl/certs` directory from where it
will be picked up automatically.
-For example, if your internal CA certifciate is `myCA.pem`:
+For example, if your internal CA certificate is `myCA.pem`:
```plaintext
kubectl -n gitlab-kubernetes-agent create configmap ca-pemstore --from-file=myCA.pem
@@ -632,7 +632,7 @@ Alternatively, you can mount the certificate file at a different location and in
mutation deleteAgent {
clusterAgentDelete(input: { id: "
" } ) {
errors
- }
+ }
}
mutation deleteToken {
@@ -645,7 +645,7 @@ Alternatively, you can mount the certificate file at a different location and in
1. Verify whether the removal occurred successfully. If the output in the Pod logs includes `unauthenticated`, it means that the agent was successfully removed:
```json
- {"level":"warn","time":"2021-04-29T23:44:07.598Z","msg":"GetConfiguration.Recv failed","error":"rpc error:
+ {"level":"warn","time":"2021-04-29T23:44:07.598Z","msg":"GetConfiguration.Recv failed","error":"rpc error:
code = Unauthenticated desc = unauthenticated"}
```
diff --git a/doc/user/group/devops_adoption/index.md b/doc/user/group/devops_adoption/index.md
index 006778cbe00..5a23e1559bc 100644
--- a/doc/user/group/devops_adoption/index.md
+++ b/doc/user/group/devops_adoption/index.md
@@ -114,7 +114,7 @@ To disable it:
Feature.disable(:group_devops_adoption)
```
-To reenable it:
+To re-enable it:
```ruby
Feature.enable(:group_devops_adoption)
diff --git a/doc/user/packages/generic_packages/index.md b/doc/user/packages/generic_packages/index.md
index 57d6245dd96..e20ac57e64f 100644
--- a/doc/user/packages/generic_packages/index.md
+++ b/doc/user/packages/generic_packages/index.md
@@ -69,22 +69,6 @@ Example response:
}
```
-Example request using a deploy token:
-
-```shell
-curl --header "DEPLOY-TOKEN: " \
- --upload-file path/to/file.txt \
- "https://gitlab.example.com/api/v4/projects/24/packages/generic/my_package/0.0.1/file.txt?status=hidden"
-```
-
-Example response:
-
-```json
-{
- "message":"201 Created"
-}
-```
-
## Download package file
Download a package file.
diff --git a/doc/user/project/clusters/serverless/aws.md b/doc/user/project/clusters/serverless/aws.md
index f9423c0be2d..5d6fb8252bb 100644
--- a/doc/user/project/clusters/serverless/aws.md
+++ b/doc/user/project/clusters/serverless/aws.md
@@ -86,7 +86,7 @@ Put the following code in the file:
service: gitlab-example
provider:
name: aws
- runtime: nodejs10.x
+ runtime: nodejs14.x
functions:
hello:
diff --git a/doc/user/project/deploy_keys/index.md b/doc/user/project/deploy_keys/index.md
index 45c42c78ab7..a6b54474a9e 100644
--- a/doc/user/project/deploy_keys/index.md
+++ b/doc/user/project/deploy_keys/index.md
@@ -167,7 +167,7 @@ If the key is **publicly accessible**, it will be removed from the project, but
If the key is **privately accessible** and only in use by this project, it will deleted.
-If the key is **privately accessible** and in use by other projects, it will be removed from the project, but still available under **Privately accesible deploy keys**.
+If the key is **privately accessible** and in use by other projects, it will be removed from the project, but still available under **Privately accessible deploy keys**.
## Troubleshooting
diff --git a/doc/user/project/deploy_tokens/index.md b/doc/user/project/deploy_tokens/index.md
index 842f167f6ec..e2fa63ce519 100644
--- a/doc/user/project/deploy_tokens/index.md
+++ b/doc/user/project/deploy_tokens/index.md
@@ -130,20 +130,12 @@ To pull packages in the GitLab package registry, you must:
1. For the [package type of your choice](../../packages/index.md), follow the
authentication instructions for deploy tokens.
-Example request publishing a generic package using a deploy token:
+Example request publishing a NuGet package using a deploy token:
```shell
-curl --header "DEPLOY-TOKEN: " \
- --upload-file path/to/file.txt \
- "https://gitlab.example.com/api/v4/projects/24/packages/generic/my_package/0.0.1/file.txt?status=hidden"
-```
+nuget source Add -Name GitLab -Source "https://gitlab.example.com/api/v4/projects/10/packages/nuget/index.json" -UserName deploy-token-username -Password 12345678asdf
-Example response:
-
-```json
-{
- "message":"201 Created"
-}
+nuget push mypkg.nupkg -Source GitLab
```
### Push or upload packages
diff --git a/doc/user/project/integrations/hangouts_chat.md b/doc/user/project/integrations/hangouts_chat.md
index 6d6ef823173..0668e5dd88f 100644
--- a/doc/user/project/integrations/hangouts_chat.md
+++ b/doc/user/project/integrations/hangouts_chat.md
@@ -15,7 +15,7 @@ Hangouts).
## How it works
To enable this integration, first you need to create a webhook for the room in
-Google Chat where you want to receive the nofications from your project.
+Google Chat where you want to receive the notifications from your project.
After that, enable the integration in GitLab and choose the events you want to
be notified about in your Google Chat room.
diff --git a/doc/user/project/repository/branches/index.md b/doc/user/project/repository/branches/index.md
index 1a21616e3a5..91858ff9a65 100644
--- a/doc/user/project/repository/branches/index.md
+++ b/doc/user/project/repository/branches/index.md
@@ -103,7 +103,7 @@ Sometimes when you have hundreds of branches you may want a more flexible matchi
![Before swap revisions](img/swap_revisions_before_v13_12.png)
-The Swap revisions feature allows you to swap the Source and Target revisions. When the Swap revisions button is clicked, the selected revisions for Source and Targed will be swapped.
+The Swap revisions feature allows you to swap the Source and Target revisions. When the Swap revisions button is clicked, the selected revisions for Source and Target will be swapped.
![After swap revisions](img/swap_revisions_after_v13_12.png)
diff --git a/lib/gitlab/background_migration/migrate_project_taggings_context_from_tags_to_topics.rb b/lib/gitlab/background_migration/migrate_project_taggings_context_from_tags_to_topics.rb
new file mode 100644
index 00000000000..68bbd3cfebb
--- /dev/null
+++ b/lib/gitlab/background_migration/migrate_project_taggings_context_from_tags_to_topics.rb
@@ -0,0 +1,21 @@
+# frozen_string_literal: true
+
+module Gitlab
+ module BackgroundMigration
+ # The class to migrate the context of project taggings from `tags` to `topics`
+ class MigrateProjectTaggingsContextFromTagsToTopics
+ # Temporary AR table for taggings
+ class Tagging < ActiveRecord::Base
+ include EachBatch
+
+ self.table_name = 'taggings'
+ end
+
+ def perform(start_id, stop_id)
+ Tagging.where(taggable_type: 'Project', context: 'tags', id: start_id..stop_id).each_batch(of: 500) do |relation|
+ relation.update_all(context: 'topics')
+ end
+ end
+ end
+ end
+end
diff --git a/lib/gitlab/import_export/project/import_export.yml b/lib/gitlab/import_export/project/import_export.yml
index 447d4db865e..d000c331b6d 100644
--- a/lib/gitlab/import_export/project/import_export.yml
+++ b/lib/gitlab/import_export/project/import_export.yml
@@ -153,6 +153,7 @@ excluded_attributes:
- :bfg_object_map
- :detected_repository_languages
- :tag_list
+ - :topic_list
- :mirror_user_id
- :mirror_trigger_builds
- :only_mirror_protected_branches
diff --git a/package.json b/package.json
index c64270f5bba..d2eddd5af0f 100644
--- a/package.json
+++ b/package.json
@@ -152,6 +152,7 @@
"prosemirror-model": "^1.13.3",
"raphael": "^2.2.7",
"raw-loader": "^4.0.2",
+ "scrollparent": "^2.0.1",
"select2": "3.5.2-browserify",
"smooshpack": "^0.0.62",
"sortablejs": "^1.10.2",
@@ -171,6 +172,8 @@
"vue": "^2.6.12",
"vue-apollo": "^3.0.3",
"vue-loader": "^15.9.6",
+ "vue-observe-visibility": "^1.0.0",
+ "vue-resize": "^1.0.1",
"vue-router": "3.4.9",
"vue-template-compiler": "^2.6.12",
"vue-virtual-scroll-list": "^1.4.7",
diff --git a/spec/features/invites_spec.rb b/spec/features/invites_spec.rb
index fd6718ddb9b..a72cf033d61 100644
--- a/spec/features/invites_spec.rb
+++ b/spec/features/invites_spec.rb
@@ -7,15 +7,12 @@ RSpec.describe 'Group or Project invitations', :aggregate_failures do
let_it_be(:group) { create(:group, name: 'Owned') }
let_it_be(:project) { create(:project, :repository, namespace: group) }
- let(:user) { create(:user, email: 'user@example.com') }
let(:group_invite) { group.group_members.invite.last }
before do
stub_application_setting(require_admin_approval_after_user_signup: false)
project.add_maintainer(owner)
group.add_owner(owner)
- group.add_developer('user@example.com', owner)
- group_invite.generate_invite_token!
end
def confirm_email(new_user)
@@ -45,45 +42,128 @@ RSpec.describe 'Group or Project invitations', :aggregate_failures do
click_button 'Get started!'
end
- context 'when signed out' do
+ context 'when inviting a registered user' do
+ let(:invite_email) { 'user@example.com' }
+
before do
- visit invite_path(group_invite.raw_invite_token)
+ group.add_developer(invite_email, owner)
+ group_invite.generate_invite_token!
end
- it 'renders sign up page with sign up notice' do
- expect(current_path).to eq(new_user_registration_path)
- expect(page).to have_content('To accept this invitation, create an account or sign in')
- end
+ context 'when signed out' do
+ context 'when analyzing the redirects and forms from invite link click' do
+ before do
+ visit invite_path(group_invite.raw_invite_token)
+ end
- it 'pre-fills the "Username or email" field on the sign in box with the invite_email from the invite' do
- click_link 'Sign in'
+ it 'renders sign up page with sign up notice' do
+ expect(current_path).to eq(new_user_registration_path)
+ expect(page).to have_content('To accept this invitation, create an account or sign in')
+ end
- expect(find_field('Username or email').value).to eq(group_invite.invite_email)
- end
+ it 'pre-fills the "Username or email" field on the sign in box with the invite_email from the invite' do
+ click_link 'Sign in'
- it 'pre-fills the Email field on the sign up box with the invite_email from the invite' do
- expect(find_field('Email').value).to eq(group_invite.invite_email)
- end
+ expect(find_field('Username or email').value).to eq(group_invite.invite_email)
+ end
- it 'sign in, grants access and redirects to group activity page' do
- click_link 'Sign in'
+ it 'pre-fills the Email field on the sign up box with the invite_email from the invite' do
+ expect(find_field('Email').value).to eq(group_invite.invite_email)
+ end
+ end
- fill_in_sign_in_form(user)
+ context 'when invite is sent before account is created - ldap or social sign in for manual acceptance edge case' do
+ let(:user) { create(:user, email: 'user@example.com') }
- expect(current_path).to eq(activity_group_path(group))
- end
- end
+ context 'when invite clicked and not signed in' do
+ before do
+ visit invite_path(group_invite.raw_invite_token)
+ end
- context 'when signed in as an existing member' do
- before do
- sign_in(owner)
- end
+ it 'sign in, grants access and redirects to group activity page' do
+ click_link 'Sign in'
- it 'shows message user already a member' do
- visit invite_path(group_invite.raw_invite_token)
+ fill_in_sign_in_form(user)
- expect(page).to have_link(owner.name, href: user_url(owner))
- expect(page).to have_content('However, you are already a member of this group.')
+ expect(current_path).to eq(activity_group_path(group))
+ end
+ end
+
+ context 'when signed in and an invite link is clicked' do
+ context 'when an invite email is a secondary email for the user' do
+ let(:invite_email) { 'user_secondary@example.com' }
+
+ before do
+ sign_in(user)
+ visit invite_path(group_invite.raw_invite_token)
+ end
+
+ it 'sends user to the invite url and allows them to decline' do
+ expect(current_path).to eq(invite_path(group_invite.raw_invite_token))
+ expect(page).to have_content("Note that this invitation was sent to #{invite_email}")
+ expect(page).to have_content("but you are signed in as #{user.to_reference} with email #{user.email}")
+
+ click_link('Decline')
+
+ expect(page).to have_content('You have declined the invitation')
+ expect(current_path).to eq(dashboard_projects_path)
+ expect { group_invite.reload }.to raise_error ActiveRecord::RecordNotFound
+ end
+
+ it 'sends uer to the invite url and allows them to accept' do
+ expect(current_path).to eq(invite_path(group_invite.raw_invite_token))
+ expect(page).to have_content("Note that this invitation was sent to #{invite_email}")
+ expect(page).to have_content("but you are signed in as #{user.to_reference} with email #{user.email}")
+
+ click_link('Accept invitation')
+
+ expect(page).to have_content('You have been granted')
+ expect(current_path).to eq(activity_group_path(group))
+ end
+ end
+
+ context 'when user is an existing member' do
+ before do
+ sign_in(owner)
+ visit invite_path(group_invite.raw_invite_token)
+ end
+
+ it 'shows message user already a member' do
+ expect(current_path).to eq(invite_path(group_invite.raw_invite_token))
+ expect(page).to have_link(owner.name, href: user_url(owner))
+ expect(page).to have_content('However, you are already a member of this group.')
+ end
+ end
+ end
+
+ context 'when declining the invitation from invitation reminder email' do
+ context 'when signed in' do
+ before do
+ sign_in(user)
+ visit decline_invite_path(group_invite.raw_invite_token)
+ end
+
+ it 'declines application and redirects to dashboard' do
+ expect(current_path).to eq(dashboard_projects_path)
+ expect(page).to have_content('You have declined the invitation to join group Owned.')
+ expect { group_invite.reload }.to raise_error ActiveRecord::RecordNotFound
+ end
+ end
+
+ context 'when signed out with signup onboarding' do
+ before do
+ visit decline_invite_path(group_invite.raw_invite_token)
+ end
+
+ it 'declines application and redirects to sign in page' do
+ expect(current_path).to eq(decline_invite_path(group_invite.raw_invite_token))
+ expect(page).not_to have_content('You have declined the invitation to join')
+ expect(page).to have_content('You successfully declined the invitation')
+ expect { group_invite.reload }.to raise_error ActiveRecord::RecordNotFound
+ end
+ end
+ end
+ end
end
end
@@ -243,63 +323,13 @@ RSpec.describe 'Group or Project invitations', :aggregate_failures do
end
end
- context 'when declining the invitation' do
- context 'as an existing user' do
- let(:group_invite) { create(:group_member, user: user, group: group, created_by: owner) }
+ context 'when declining the invitation from invitation reminder email' do
+ it 'declines application and shows a decline page' do
+ visit decline_invite_path(group_invite.raw_invite_token)
- context 'when signed in' do
- before do
- sign_in(user)
- visit decline_invite_path(group_invite.raw_invite_token)
- end
-
- it 'declines application and redirects to dashboard' do
- expect(current_path).to eq(dashboard_projects_path)
- expect(page).to have_content('You have declined the invitation to join group Owned.')
- expect { group_invite.reload }.to raise_error ActiveRecord::RecordNotFound
- end
- end
-
- context 'when signed out' do
- before do
- visit decline_invite_path(group_invite.raw_invite_token)
- end
-
- it 'declines application and redirects to sign in page' do
- expect(current_path).to eq(new_user_session_path)
- expect(page).to have_content('You have declined the invitation to join group Owned.')
- expect { group_invite.reload }.to raise_error ActiveRecord::RecordNotFound
- end
- end
- end
-
- context 'as a non-existing user' do
- before do
- visit decline_invite_path(group_invite.raw_invite_token)
- end
-
- it 'declines application and shows a decline page' do
- expect(current_path).to eq(decline_invite_path(group_invite.raw_invite_token))
- expect(page).to have_content('You successfully declined the invitation')
- expect { group_invite.reload }.to raise_error ActiveRecord::RecordNotFound
- end
- end
- end
-
- context 'when accepting the invitation as an existing user' do
- before do
- sign_in(user)
- visit invite_path(group_invite.raw_invite_token)
- end
-
- it 'grants access and redirects to the group activity page' do
- expect(group.users.include?(user)).to be false
-
- page.click_link 'Accept invitation'
-
- expect(current_path).to eq(activity_group_path(group))
- expect(page).to have_content('You have been granted Owner access to group Owned.')
- expect(group.users.include?(user)).to be true
+ expect(current_path).to eq(decline_invite_path(group_invite.raw_invite_token))
+ expect(page).to have_content('You successfully declined the invitation')
+ expect { group_invite.reload }.to raise_error ActiveRecord::RecordNotFound
end
end
end
diff --git a/spec/finders/projects_finder_spec.rb b/spec/finders/projects_finder_spec.rb
index a178261e899..364e5de4ece 100644
--- a/spec/finders/projects_finder_spec.rb
+++ b/spec/finders/projects_finder_spec.rb
@@ -139,7 +139,7 @@ RSpec.describe ProjectsFinder do
describe 'filter by tags' do
before do
- public_project.tag_list.add('foo')
+ public_project.tag_list = 'foo'
public_project.save!
end
diff --git a/spec/lib/gitlab/background_migration/migrate_project_taggings_context_from_tags_to_topics_spec.rb b/spec/lib/gitlab/background_migration/migrate_project_taggings_context_from_tags_to_topics_spec.rb
new file mode 100644
index 00000000000..5e2f32c54be
--- /dev/null
+++ b/spec/lib/gitlab/background_migration/migrate_project_taggings_context_from_tags_to_topics_spec.rb
@@ -0,0 +1,29 @@
+# frozen_string_literal: true
+
+require 'spec_helper'
+
+RSpec.describe Gitlab::BackgroundMigration::MigrateProjectTaggingsContextFromTagsToTopics, schema: 20210511095658 do
+ it 'correctly migrates project taggings context from tags to topics' do
+ taggings = table(:taggings)
+
+ project_old_tagging_1 = taggings.create!(taggable_type: 'Project', context: 'tags')
+ project_new_tagging_1 = taggings.create!(taggable_type: 'Project', context: 'topics')
+ project_other_context_tagging_1 = taggings.create!(taggable_type: 'Project', context: 'other')
+ project_old_tagging_2 = taggings.create!(taggable_type: 'Project', context: 'tags')
+ project_old_tagging_3 = taggings.create!(taggable_type: 'Project', context: 'tags')
+
+ subject.perform(project_old_tagging_1.id, project_old_tagging_2.id)
+
+ project_old_tagging_1.reload
+ project_new_tagging_1.reload
+ project_other_context_tagging_1.reload
+ project_old_tagging_2.reload
+ project_old_tagging_3.reload
+
+ expect(project_old_tagging_1.context).to eq('topics')
+ expect(project_new_tagging_1.context).to eq('topics')
+ expect(project_other_context_tagging_1.context).to eq('other')
+ expect(project_old_tagging_2.context).to eq('topics')
+ expect(project_old_tagging_3.context).to eq('tags')
+ end
+end
diff --git a/spec/lib/gitlab/import_export/all_models.yml b/spec/lib/gitlab/import_export/all_models.yml
index 8253cb90ce5..84743e57594 100644
--- a/spec/lib/gitlab/import_export/all_models.yml
+++ b/spec/lib/gitlab/import_export/all_models.yml
@@ -343,8 +343,9 @@ project:
- external_approval_rules
- taggings
- base_tags
-- tag_taggings
- tags
+- topic_taggings
+- topics
- chat_services
- cluster
- clusters
diff --git a/spec/models/project_spec.rb b/spec/models/project_spec.rb
index 43f2c625e74..272beca7daf 100644
--- a/spec/models/project_spec.rb
+++ b/spec/models/project_spec.rb
@@ -6964,6 +6964,55 @@ RSpec.describe Project, factory_default: :keep do
end
end
+ describe 'topics' do
+ let_it_be(:project) { create(:project, tag_list: 'topic1, topic2, topic3') }
+
+ it 'topic_list returns correct string array' do
+ expect(project.topic_list).to match_array(%w[topic1 topic2 topic3])
+ end
+
+ it 'topics returns correct tag records' do
+ expect(project.topics.first.class.name).to eq('ActsAsTaggableOn::Tag')
+ expect(project.topics.map(&:name)).to match_array(%w[topic1 topic2 topic3])
+ end
+
+ context 'aliases' do
+ it 'tag_list returns correct string array' do
+ expect(project.tag_list).to match_array(%w[topic1 topic2 topic3])
+ end
+
+ it 'tags returns correct tag records' do
+ expect(project.tags.first.class.name).to eq('ActsAsTaggableOn::Tag')
+ expect(project.tags.map(&:name)).to match_array(%w[topic1 topic2 topic3])
+ end
+ end
+
+ context 'intermediate state during background migration' do
+ before do
+ project.taggings.first.update!(context: 'tags')
+ project.instance_variable_set("@tag_list", nil)
+ project.reload
+ end
+
+ it 'tag_list returns string array including old and new topics' do
+ expect(project.tag_list).to match_array(%w[topic1 topic2 topic3])
+ end
+
+ it 'tags returns old and new tag records' do
+ expect(project.tags.first.class.name).to eq('ActsAsTaggableOn::Tag')
+ expect(project.tags.map(&:name)).to match_array(%w[topic1 topic2 topic3])
+ expect(project.taggings.map(&:context)).to match_array(%w[tags topics topics])
+ end
+
+ it 'update tag_list adds new topics and removes old topics' do
+ project.update!(tag_list: 'topic1, topic2, topic3, topic4')
+
+ expect(project.tags.map(&:name)).to match_array(%w[topic1 topic2 topic3 topic4])
+ expect(project.taggings.map(&:context)).to match_array(%w[topics topics topics topics])
+ end
+ end
+ end
+
def finish_job(export_job)
export_job.start
export_job.finish
diff --git a/spec/requests/api/project_attributes.yml b/spec/requests/api/project_attributes.yml
index f9eb9de94db..d28442bd692 100644
--- a/spec/requests/api/project_attributes.yml
+++ b/spec/requests/api/project_attributes.yml
@@ -41,6 +41,7 @@ itself: # project
- reset_approvals_on_push
- runners_token_encrypted
- storage_version
+ - topic_list
- updated_at
remapped_attributes:
avatar: avatar_url
@@ -67,6 +68,7 @@ itself: # project
- readme_url
- shared_with_groups
- ssh_url_to_repo
+ - tag_list
- web_url
build_auto_devops: # auto_devops
diff --git a/spec/controllers/groups/autocomplete_sources_controller_spec.rb b/spec/requests/groups/autocomplete_sources_spec.rb
similarity index 89%
rename from spec/controllers/groups/autocomplete_sources_controller_spec.rb
rename to spec/requests/groups/autocomplete_sources_spec.rb
index 6e814bebf39..d053e0fe773 100644
--- a/spec/controllers/groups/autocomplete_sources_controller_spec.rb
+++ b/spec/requests/groups/autocomplete_sources_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-RSpec.describe Groups::AutocompleteSourcesController do
+RSpec.describe 'groups autocomplete' do
let_it_be(:user) { create(:user) }
let_it_be_with_reload(:group) { create(:group, :private) }
@@ -35,9 +35,8 @@ RSpec.describe Groups::AutocompleteSourcesController do
with_them do
it 'returns the correct response', :aggregate_failures do
issues = Array(expected).flat_map { |sym| public_send(sym) }
- params = { group_id: group, issue_types: issue_types }.compact
- get :issues, params: params
+ get issues_group_autocomplete_sources_path(group, issue_types: issue_types)
expect(response).to have_gitlab_http_status(:ok)
expect(json_response).to be_an(Array)
@@ -57,7 +56,7 @@ RSpec.describe Groups::AutocompleteSourcesController do
create(:milestone, group: sub_group)
group_milestone = create(:milestone, group: group)
- get :milestones, params: { group_id: group }
+ get milestones_group_autocomplete_sources_path(group)
expect(response).to have_gitlab_http_status(:ok)
expect(json_response.count).to eq(1)
diff --git a/spec/spec_helper.rb b/spec/spec_helper.rb
index bca5614fe27..c59daa6c919 100644
--- a/spec/spec_helper.rb
+++ b/spec/spec_helper.rb
@@ -268,6 +268,7 @@ RSpec.configure do |config|
stub_feature_flags(file_identifier_hash: false)
stub_feature_flags(unified_diff_components: false)
+ stub_feature_flags(diffs_virtual_scrolling: false)
# The following `vue_issues_list`/`vue_issuables_list` stubs can be removed
# once the Vue issues page has feature parity with the current Haml page
diff --git a/vendor/assets/javascripts/vue-virtual-scroller/package.json b/vendor/assets/javascripts/vue-virtual-scroller/package.json
new file mode 100644
index 00000000000..0c6eec36ea5
--- /dev/null
+++ b/vendor/assets/javascripts/vue-virtual-scroller/package.json
@@ -0,0 +1,40 @@
+{
+ "name": "vue-virtual-scroller",
+ "description": "Smooth scrolling for any amount of data",
+ "version": "1.0.10",
+ "author": {
+ "name": "Guillaume Chau",
+ "email": "guillaume.b.chau@gmail.com"
+ },
+ "keywords": [
+ "vue",
+ "vuejs",
+ "plugin"
+ ],
+ "license": "MIT",
+ "main": "src/index.js",
+ "scripts": {},
+ "repository": {
+ "type": "git",
+ "url": "git+https://github.com/Akryum/vue-virtual-scroller.git"
+ },
+ "bugs": {
+ "url": "https://github.com/Akryum/vue-virtual-scroller/issues"
+ },
+ "homepage": "https://github.com/Akryum/vue-virtual-scroller#readme",
+ "dependencies": {
+ "scrollparent": "^2.0.1",
+ "vue-observe-visibility": "^0.4.4",
+ "vue-resize": "^0.4.5"
+ },
+ "peerDependencies": {
+ "vue": "^2.6.11"
+ },
+ "devDependencies": {
+ },
+ "browserslist": [
+ "> 1%",
+ "last 2 versions",
+ "not ie <= 8"
+ ]
+}
diff --git a/vendor/assets/javascripts/vue-virtual-scroller/src/components/DynamicScroller.vue b/vendor/assets/javascripts/vue-virtual-scroller/src/components/DynamicScroller.vue
new file mode 100644
index 00000000000..e9f3acea9d8
--- /dev/null
+++ b/vendor/assets/javascripts/vue-virtual-scroller/src/components/DynamicScroller.vue
@@ -0,0 +1,212 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/vendor/assets/javascripts/vue-virtual-scroller/src/components/DynamicScrollerItem.vue b/vendor/assets/javascripts/vue-virtual-scroller/src/components/DynamicScrollerItem.vue
new file mode 100644
index 00000000000..3db24018ad0
--- /dev/null
+++ b/vendor/assets/javascripts/vue-virtual-scroller/src/components/DynamicScrollerItem.vue
@@ -0,0 +1,218 @@
+
diff --git a/vendor/assets/javascripts/vue-virtual-scroller/src/components/RecycleScroller.vue b/vendor/assets/javascripts/vue-virtual-scroller/src/components/RecycleScroller.vue
new file mode 100644
index 00000000000..5e9661a53c8
--- /dev/null
+++ b/vendor/assets/javascripts/vue-virtual-scroller/src/components/RecycleScroller.vue
@@ -0,0 +1,657 @@
+
+
+
+
+
+
+
diff --git a/vendor/assets/javascripts/vue-virtual-scroller/src/components/common.js b/vendor/assets/javascripts/vue-virtual-scroller/src/components/common.js
new file mode 100644
index 00000000000..2121942152e
--- /dev/null
+++ b/vendor/assets/javascripts/vue-virtual-scroller/src/components/common.js
@@ -0,0 +1,21 @@
+export const props = {
+ items: {
+ type: Array,
+ required: true,
+ },
+
+ keyField: {
+ type: String,
+ default: 'id',
+ },
+
+ direction: {
+ type: String,
+ default: 'vertical',
+ validator: (value) => ['vertical', 'horizontal'].includes(value),
+ },
+}
+
+export function simpleArray () {
+ return this.items.length && typeof this.items[0] !== 'object'
+}
diff --git a/vendor/assets/javascripts/vue-virtual-scroller/src/config.js b/vendor/assets/javascripts/vue-virtual-scroller/src/config.js
new file mode 100644
index 00000000000..898ca7e027d
--- /dev/null
+++ b/vendor/assets/javascripts/vue-virtual-scroller/src/config.js
@@ -0,0 +1,3 @@
+export default {
+ itemsLimit: 1000,
+}
diff --git a/vendor/assets/javascripts/vue-virtual-scroller/src/index.js b/vendor/assets/javascripts/vue-virtual-scroller/src/index.js
new file mode 100644
index 00000000000..aa9733338f6
--- /dev/null
+++ b/vendor/assets/javascripts/vue-virtual-scroller/src/index.js
@@ -0,0 +1,60 @@
+/**
+ * See https://gitlab.com/gitlab-org/gitlab/-/issues/331267 for more information on this vendored
+ * dependency
+ */
+
+import config from './config'
+
+import RecycleScroller from './components/RecycleScroller.vue'
+import DynamicScroller from './components/DynamicScroller.vue'
+import DynamicScrollerItem from './components/DynamicScrollerItem.vue'
+
+export { default as IdState } from './mixins/IdState'
+
+export {
+ RecycleScroller,
+ DynamicScroller,
+ DynamicScrollerItem,
+}
+
+function registerComponents (Vue, prefix) {
+ Vue.component(`${prefix}recycle-scroller`, RecycleScroller)
+ Vue.component(`${prefix}RecycleScroller`, RecycleScroller)
+ Vue.component(`${prefix}dynamic-scroller`, DynamicScroller)
+ Vue.component(`${prefix}DynamicScroller`, DynamicScroller)
+ Vue.component(`${prefix}dynamic-scroller-item`, DynamicScrollerItem)
+ Vue.component(`${prefix}DynamicScrollerItem`, DynamicScrollerItem)
+}
+
+const plugin = {
+ // eslint-disable-next-line no-undef
+ install (Vue, options) {
+ const finalOptions = Object.assign({}, {
+ installComponents: true,
+ componentsPrefix: '',
+ }, options)
+
+ for (const key in finalOptions) {
+ if (typeof finalOptions[key] !== 'undefined') {
+ config[key] = finalOptions[key]
+ }
+ }
+
+ if (finalOptions.installComponents) {
+ registerComponents(Vue, finalOptions.componentsPrefix)
+ }
+ },
+}
+
+export default plugin
+
+// Auto-install
+let GlobalVue = null
+if (typeof window !== 'undefined') {
+ GlobalVue = window.Vue
+} else if (typeof global !== 'undefined') {
+ GlobalVue = global.Vue
+}
+if (GlobalVue) {
+ GlobalVue.use(plugin)
+}
diff --git a/vendor/assets/javascripts/vue-virtual-scroller/src/mixins/IdState.js b/vendor/assets/javascripts/vue-virtual-scroller/src/mixins/IdState.js
new file mode 100644
index 00000000000..9b5bc57ab92
--- /dev/null
+++ b/vendor/assets/javascripts/vue-virtual-scroller/src/mixins/IdState.js
@@ -0,0 +1,79 @@
+import Vue from 'vue'
+
+export default function ({
+ idProp = vm => vm.item.id,
+} = {}) {
+ const store = {}
+ const vm = new Vue({
+ data () {
+ return {
+ store,
+ }
+ },
+ })
+
+ // @vue/component
+ return {
+ data () {
+ return {
+ idState: null,
+ }
+ },
+
+ created () {
+ this.$_id = null
+ if (typeof idProp === 'function') {
+ this.$_getId = () => idProp.call(this, this)
+ } else {
+ this.$_getId = () => this[idProp]
+ }
+ this.$watch(this.$_getId, {
+ handler (value) {
+ this.$nextTick(() => {
+ this.$_id = value
+ })
+ },
+ immediate: true,
+ })
+ this.$_updateIdState()
+ },
+
+ beforeUpdate () {
+ this.$_updateIdState()
+ },
+
+ methods: {
+ /**
+ * Initialize an idState
+ * @param {number|string} id Unique id for the data
+ */
+ $_idStateInit (id) {
+ const factory = this.$options.idState
+ if (typeof factory === 'function') {
+ const data = factory.call(this, this)
+ vm.$set(store, id, data)
+ this.$_id = id
+ return data
+ } else {
+ throw new Error('[mixin IdState] Missing `idState` function on component definition.')
+ }
+ },
+
+ /**
+ * Ensure idState is created and up-to-date
+ */
+ $_updateIdState () {
+ const id = this.$_getId()
+ if (id == null) {
+ console.warn(`No id found for IdState with idProp: '${idProp}'.`)
+ }
+ if (id !== this.$_id) {
+ if (!store[id]) {
+ this.$_idStateInit(id)
+ }
+ this.idState = store[id]
+ }
+ },
+ },
+ }
+}
diff --git a/vendor/assets/javascripts/vue-virtual-scroller/src/utils.js b/vendor/assets/javascripts/vue-virtual-scroller/src/utils.js
new file mode 100644
index 00000000000..40da6793e67
--- /dev/null
+++ b/vendor/assets/javascripts/vue-virtual-scroller/src/utils.js
@@ -0,0 +1,13 @@
+export let supportsPassive = false
+
+if (typeof window !== 'undefined') {
+ supportsPassive = false
+ try {
+ var opts = Object.defineProperty({}, 'passive', {
+ get () {
+ supportsPassive = true
+ },
+ })
+ window.addEventListener('test', null, opts)
+ } catch (e) {}
+}
diff --git a/yarn.lock b/yarn.lock
index 9baa4b10661..ef7d8021ca9 100644
--- a/yarn.lock
+++ b/yarn.lock
@@ -784,10 +784,10 @@
core-js-pure "^3.0.0"
regenerator-runtime "^0.13.4"
-"@babel/runtime@^7.10.2", "@babel/runtime@^7.10.3", "@babel/runtime@^7.8.4", "@babel/runtime@^7.9.2":
- version "7.11.2"
- resolved "https://registry.yarnpkg.com/@babel/runtime/-/runtime-7.11.2.tgz#f549c13c754cc40b87644b9fa9f09a6a95fe0736"
- integrity sha512-TeWkU52so0mPtDcaCTxNBI/IHiz0pZgr8VEFqXFtZWpYD08ZB6FaSwVAS8MKRQAP3bYKiVjwysOJgMFY28o6Tw==
+"@babel/runtime@^7.10.2", "@babel/runtime@^7.10.3", "@babel/runtime@^7.13.10", "@babel/runtime@^7.8.4", "@babel/runtime@^7.9.2":
+ version "7.14.0"
+ resolved "https://registry.yarnpkg.com/@babel/runtime/-/runtime-7.14.0.tgz#46794bc20b612c5f75e62dd071e24dfd95f1cbe6"
+ integrity sha512-JELkvo/DlpNdJ7dlyw/eY7E0suy5i5GQH+Vlxaq1nsNJ+H7f4Vtv3jMeCEgRhZZQFXTjldYfQgv2qmM6M1v5wA==
dependencies:
regenerator-runtime "^0.13.4"
@@ -10359,6 +10359,11 @@ schema-utils@^3.0.0:
ajv "^6.12.5"
ajv-keywords "^3.5.2"
+scrollparent@^2.0.1:
+ version "2.0.1"
+ resolved "https://registry.yarnpkg.com/scrollparent/-/scrollparent-2.0.1.tgz#715d5b9cc57760fb22bdccc3befb5bfe06b1a317"
+ integrity sha1-cV1bnMV3YPsivczDvvtb/gaxoxc=
+
select-hose@^2.0.0:
version "2.0.0"
resolved "https://registry.yarnpkg.com/select-hose/-/select-hose-2.0.0.tgz#625d8658f865af43ec962bfc376a37359a4994ca"
@@ -12126,6 +12131,18 @@ vue-loader@^15.9.6:
vue-hot-reload-api "^2.3.0"
vue-style-loader "^4.1.0"
+vue-observe-visibility@^1.0.0:
+ version "1.0.0"
+ resolved "https://registry.yarnpkg.com/vue-observe-visibility/-/vue-observe-visibility-1.0.0.tgz#17cf1b2caf74022f0f3c95371468ddf2b9573152"
+ integrity sha512-s5TFh3s3h3Mhd3jaz3zGzkVHKHnc/0C/gNr30olO99+yw2hl3WBhK3ng3/f9OF+qkW4+l7GkmwfAzDAcY3lCFg==
+
+vue-resize@^1.0.1:
+ version "1.0.1"
+ resolved "https://registry.yarnpkg.com/vue-resize/-/vue-resize-1.0.1.tgz#c120bed4e09938771d622614f57dbcf58a5147ee"
+ integrity sha512-z5M7lJs0QluJnaoMFTIeGx6dIkYxOwHThlZDeQnWZBizKblb99GSejPnK37ZbNE/rVwDcYcHY+Io+AxdpY952w==
+ dependencies:
+ "@babel/runtime" "^7.13.10"
+
vue-router@3.4.9:
version "3.4.9"
resolved "https://registry.yarnpkg.com/vue-router/-/vue-router-3.4.9.tgz#c016f42030ae2932f14e4748b39a1d9a0e250e66"