diff --git a/app/controllers/projects/group_links_controller.rb b/app/controllers/projects/group_links_controller.rb
index 83b509bcae2..a30c455a7e4 100644
--- a/app/controllers/projects/group_links_controller.rb
+++ b/app/controllers/projects/group_links_controller.rb
@@ -22,8 +22,7 @@ class Projects::GroupLinksController < Projects::ApplicationController
def update
@group_link = @project.project_group_links.find(params[:id])
-
- @group_link.update(group_link_params)
+ Projects::GroupLinks::UpdateService.new(@group_link).execute(group_link_params)
end
def destroy
diff --git a/app/models/design_management/design.rb b/app/models/design_management/design.rb
index 6434d7cfa57..057bbab1c83 100644
--- a/app/models/design_management/design.rb
+++ b/app/models/design_management/design.rb
@@ -22,7 +22,7 @@ module DesignManagement
validates :project, :filename, presence: true
validates :issue, presence: true, unless: :importing?
- validates :filename, uniqueness: { scope: :issue_id }
+ validates :filename, uniqueness: { scope: :issue_id }, length: { maximum: 255 }
validate :validate_file_is_image
alias_attribute :title, :filename
diff --git a/app/models/project_group_link.rb b/app/models/project_group_link.rb
index f1c491d1a05..f065246e8af 100644
--- a/app/models/project_group_link.rb
+++ b/app/models/project_group_link.rb
@@ -15,8 +15,6 @@ class ProjectGroupLink < ApplicationRecord
scope :non_guests, -> { where('group_access > ?', Gitlab::Access::GUEST) }
- after_commit :refresh_group_members_authorized_projects
-
alias_method :shared_with_group, :group
def self.access_options
@@ -49,10 +47,6 @@ class ProjectGroupLink < ApplicationRecord
errors.add(:base, _("Project cannot be shared with the group it is in or one of its ancestors."))
end
end
-
- def refresh_group_members_authorized_projects
- group.refresh_members_authorized_projects
- end
end
ProjectGroupLink.prepend_if_ee('EE::ProjectGroupLink')
diff --git a/app/services/projects/group_links/create_service.rb b/app/services/projects/group_links/create_service.rb
index 241948b335b..2ba3cd6694f 100644
--- a/app/services/projects/group_links/create_service.rb
+++ b/app/services/projects/group_links/create_service.rb
@@ -13,6 +13,7 @@ module Projects
)
if link.save
+ group.refresh_members_authorized_projects
success(link: link)
else
error(link.errors.full_messages.to_sentence, 409)
diff --git a/app/services/projects/group_links/destroy_service.rb b/app/services/projects/group_links/destroy_service.rb
index ea7d05551fd..229191e41f6 100644
--- a/app/services/projects/group_links/destroy_service.rb
+++ b/app/services/projects/group_links/destroy_service.rb
@@ -12,7 +12,9 @@ module Projects
TodosDestroyer::ConfidentialIssueWorker.perform_in(Todo::WAIT_FOR_DELETE, nil, project.id)
end
- group_link.destroy
+ group_link.destroy.tap do |link|
+ link.group.refresh_members_authorized_projects
+ end
end
end
end
diff --git a/app/services/projects/group_links/update_service.rb b/app/services/projects/group_links/update_service.rb
new file mode 100644
index 00000000000..7de4b7a211d
--- /dev/null
+++ b/app/services/projects/group_links/update_service.rb
@@ -0,0 +1,29 @@
+# frozen_string_literal: true
+
+module Projects
+ module GroupLinks
+ class UpdateService < BaseService
+ def initialize(group_link, user = nil)
+ super(group_link.project, user)
+
+ @group_link = group_link
+ end
+
+ def execute(group_link_params)
+ group_link.update!(group_link_params)
+
+ if requires_authorization_refresh?(group_link_params)
+ group_link.group.refresh_members_authorized_projects
+ end
+ end
+
+ private
+
+ attr_reader :group_link
+
+ def requires_authorization_refresh?(params)
+ params.include?(:group_access)
+ end
+ end
+ end
+end
diff --git a/app/views/projects/wikis/_main_links.html.haml b/app/views/projects/wikis/_main_links.html.haml
index 2e1e176c42a..8419fb00608 100644
--- a/app/views/projects/wikis/_main_links.html.haml
+++ b/app/views/projects/wikis/_main_links.html.haml
@@ -1,9 +1,9 @@
- if (@page && @page.persisted?)
- if can?(current_user, :create_wiki, @project)
- = link_to project_wikis_new_path(@project), class: "add-new-wiki btn btn-success", role: "button" do
+ = link_to project_wikis_new_path(@project), class: "add-new-wiki btn btn-success", role: "button", data: { qa_selector: 'new_page_button' } do
= s_("Wiki|New page")
- = link_to project_wiki_history_path(@project, @page), class: "btn", role: "button" do
+ = link_to project_wiki_history_path(@project, @page), class: "btn", role: "button", data: { qa_selector: 'page_history_button' } do
= s_("Wiki|Page history")
- if can?(current_user, :create_wiki, @project) && @page.latest? && @valid_encoding
- = link_to project_wiki_edit_path(@project, @page), class: "btn js-wiki-edit", role: "button" do
+ = link_to project_wiki_edit_path(@project, @page), class: "btn js-wiki-edit", role: "button", data: { qa_selector: 'edit_page_button' } do
= _("Edit")
diff --git a/app/views/projects/wikis/_sidebar.html.haml b/app/views/projects/wikis/_sidebar.html.haml
index 2b8da83b126..6069fad6a99 100644
--- a/app/views/projects/wikis/_sidebar.html.haml
+++ b/app/views/projects/wikis/_sidebar.html.haml
@@ -5,7 +5,7 @@
= icon('angle-double-right')
- git_access_url = project_wikis_git_access_path(@project)
- = link_to git_access_url, class: active_nav_link?(path: 'wikis#git_access') ? 'active' : '' do
+ = link_to git_access_url, class: active_nav_link?(path: 'wikis#git_access') ? 'active' : '', data: { qa_selector: 'clone_repository_link' } do
= icon('cloud-download', class: 'append-right-5')
%span= _("Clone repository")
diff --git a/app/views/projects/wikis/show.html.haml b/app/views/projects/wikis/show.html.haml
index 74798311c2e..ae32f43785d 100644
--- a/app/views/projects/wikis/show.html.haml
+++ b/app/views/projects/wikis/show.html.haml
@@ -9,7 +9,7 @@
= icon('angle-double-left')
.nav-text.flex-fill
- %h2.wiki-page-title= @page.human_title
+ %h2.wiki-page-title{ data: { qa_selector: 'wiki_page_title' } }= @page.human_title
%span.wiki-last-edit-by
- if @page.last_version
= (_("Last edited by %{name}") % { name: "#{@page.last_version.author_name}" }).html_safe
diff --git a/app/workers/all_queues.yml b/app/workers/all_queues.yml
index 8f3f14c75f0..bcb3679a8c5 100644
--- a/app/workers/all_queues.yml
+++ b/app/workers/all_queues.yml
@@ -163,6 +163,14 @@
:weight: 1
:idempotent:
:tags: []
+- :name: cronjob:metrics_dashboard_schedule_annotations_prune
+ :feature_category: :metrics
+ :has_external_dependencies:
+ :urgency: :low
+ :resource_boundary: :unknown
+ :weight: 1
+ :idempotent: true
+ :tags: []
- :name: cronjob:namespaces_prune_aggregation_schedules
:feature_category: :source_code_management
:has_external_dependencies:
@@ -1355,6 +1363,14 @@
:weight: 1
:idempotent: true
:tags: []
+- :name: metrics_dashboard_prune_old_annotations
+ :feature_category: :metrics
+ :has_external_dependencies:
+ :urgency: :low
+ :resource_boundary: :unknown
+ :weight: 1
+ :idempotent: true
+ :tags: []
- :name: migrate_external_diffs
:feature_category: :source_code_management
:has_external_dependencies:
diff --git a/app/workers/metrics/dashboard/prune_old_annotations_worker.rb b/app/workers/metrics/dashboard/prune_old_annotations_worker.rb
new file mode 100644
index 00000000000..2a9ce3bb8e6
--- /dev/null
+++ b/app/workers/metrics/dashboard/prune_old_annotations_worker.rb
@@ -0,0 +1,23 @@
+# frozen_string_literal: true
+
+module Metrics
+ module Dashboard
+ class PruneOldAnnotationsWorker
+ include ApplicationWorker
+
+ DELETE_LIMIT = 10_000
+ DEFAULT_CUT_OFF_PERIOD = 2.weeks
+
+ feature_category :metrics
+
+ idempotent! # in the scope of 24 hours
+
+ def perform
+ stale_annotations = ::Metrics::Dashboard::Annotation.ending_before(DEFAULT_CUT_OFF_PERIOD.ago.beginning_of_day)
+ stale_annotations.delete_with_limit(DELETE_LIMIT)
+
+ self.class.perform_async if stale_annotations.exists?
+ end
+ end
+ end
+end
diff --git a/app/workers/metrics/dashboard/schedule_annotations_prune_worker.rb b/app/workers/metrics/dashboard/schedule_annotations_prune_worker.rb
new file mode 100644
index 00000000000..cbdd69c6e8c
--- /dev/null
+++ b/app/workers/metrics/dashboard/schedule_annotations_prune_worker.rb
@@ -0,0 +1,23 @@
+# frozen_string_literal: true
+
+module Metrics
+ module Dashboard
+ class ScheduleAnnotationsPruneWorker
+ include ApplicationWorker
+ # rubocop:disable Scalability/CronWorkerContext
+ # This worker does not perform work scoped to a context
+ include CronjobQueue
+ # rubocop:enable Scalability/CronWorkerContext
+
+ feature_category :metrics
+
+ idempotent! # PruneOldAnnotationsWorker worker is idempotent in the scope of 24 hours
+
+ def perform
+ # Process is split into two jobs to avoid long running jobs, which are more prone to be disrupted
+ # mid work, which may cause some data not be delete, especially because cronjobs has retry option disabled
+ PruneOldAnnotationsWorker.perform_async
+ end
+ end
+ end
+end
diff --git a/app/workers/remove_expired_group_links_worker.rb b/app/workers/remove_expired_group_links_worker.rb
index 0dc46cd2b9b..4c6eb821dfe 100644
--- a/app/workers/remove_expired_group_links_worker.rb
+++ b/app/workers/remove_expired_group_links_worker.rb
@@ -7,7 +7,9 @@ class RemoveExpiredGroupLinksWorker # rubocop:disable Scalability/IdempotentWork
feature_category :authentication_and_authorization
def perform
- ProjectGroupLink.expired.destroy_all # rubocop: disable Cop/DestroyAll
+ ProjectGroupLink.expired.find_each do |link|
+ Projects::GroupLinks::DestroyService.new(link.project, nil).execute(link)
+ end
GroupGroupLink.expired.find_in_batches do |link_batch|
Groups::GroupLinks::DestroyService.new(nil, nil).execute(link_batch)
diff --git a/changelogs/unreleased/mwaw-211433-metrics-dashboard-annotations-reaper-job-worker.yml b/changelogs/unreleased/mwaw-211433-metrics-dashboard-annotations-reaper-job-worker.yml
new file mode 100644
index 00000000000..4dfeb792c5a
--- /dev/null
+++ b/changelogs/unreleased/mwaw-211433-metrics-dashboard-annotations-reaper-job-worker.yml
@@ -0,0 +1,5 @@
+---
+title: Remove metrics dashboard annotations attached to time periods older than two weeks.
+merge_request: 32838
+author:
+type: added
diff --git a/config/initializers/1_settings.rb b/config/initializers/1_settings.rb
index 6cdd3fd2436..166649fc24a 100644
--- a/config/initializers/1_settings.rb
+++ b/config/initializers/1_settings.rb
@@ -481,6 +481,9 @@ Settings.cron_jobs['issue_due_scheduler_worker']['job_class'] = 'IssueDueSchedul
Settings.cron_jobs['prune_web_hook_logs_worker'] ||= Settingslogic.new({})
Settings.cron_jobs['prune_web_hook_logs_worker']['cron'] ||= '0 */1 * * *'
Settings.cron_jobs['prune_web_hook_logs_worker']['job_class'] = 'PruneWebHookLogsWorker'
+Settings.cron_jobs['metrics_dashboard_schedule_annotations_prune_worker'] ||= Settingslogic.new({})
+Settings.cron_jobs['metrics_dashboard_schedule_annotations_prune_worker']['cron'] ||= '0 1 * * *'
+Settings.cron_jobs['metrics_dashboard_schedule_annotations_prune_worker']['job_class'] = 'Metrics::Dashboard::ScheduleAnnotationsPruneWorker'
Settings.cron_jobs['schedule_migrate_external_diffs_worker'] ||= Settingslogic.new({})
Settings.cron_jobs['schedule_migrate_external_diffs_worker']['cron'] ||= '15 * * * *'
Settings.cron_jobs['schedule_migrate_external_diffs_worker']['job_class'] = 'ScheduleMigrateExternalDiffsWorker'
diff --git a/config/sidekiq_queues.yml b/config/sidekiq_queues.yml
index 92b0e9d2495..0fa165554d9 100644
--- a/config/sidekiq_queues.yml
+++ b/config/sidekiq_queues.yml
@@ -150,6 +150,8 @@
- 5
- - merge_request_mergeability_check
- 1
+- - metrics_dashboard_prune_old_annotations
+ - 1
- - migrate_external_diffs
- 1
- - namespaceless_project_destroy
diff --git a/db/migrate/20200519074709_update_resource_state_events_constraint_to_support_epic_id.rb b/db/migrate/20200519074709_update_resource_state_events_constraint_to_support_epic_id.rb
new file mode 100644
index 00000000000..40ade22f321
--- /dev/null
+++ b/db/migrate/20200519074709_update_resource_state_events_constraint_to_support_epic_id.rb
@@ -0,0 +1,24 @@
+# frozen_string_literal: true
+
+class UpdateResourceStateEventsConstraintToSupportEpicId < ActiveRecord::Migration[6.0]
+ include Gitlab::Database::MigrationHelpers
+
+ DOWNTIME = false
+
+ disable_ddl_transaction!
+
+ OLD_CONSTRAINT = 'resource_state_events_must_belong_to_issue_or_merge_request'
+ CONSTRAINT_NAME = 'resource_state_events_must_belong_to_issue_or_merge_request_or_epic'
+
+ def up
+ remove_check_constraint :resource_state_events, OLD_CONSTRAINT
+ add_check_constraint :resource_state_events, "(issue_id != NULL AND merge_request_id IS NULL AND epic_id IS NULL) OR " \
+ "(issue_id IS NULL AND merge_request_id != NULL AND epic_id IS NULL) OR" \
+ "(issue_id IS NULL AND merge_request_id IS NULL AND epic_id != NULL)", CONSTRAINT_NAME
+ end
+
+ def down
+ remove_check_constraint :resource_state_events, CONSTRAINT_NAME
+ add_check_constraint :resource_state_events, '(issue_id != NULL AND merge_request_id IS NULL) OR (merge_request_id != NULL AND issue_id IS NULL)', OLD_CONSTRAINT
+ end
+end
diff --git a/db/structure.sql b/db/structure.sql
index 8cb5559aed3..b19bfa134e6 100644
--- a/db/structure.sql
+++ b/db/structure.sql
@@ -5895,7 +5895,7 @@ CREATE TABLE public.resource_state_events (
created_at timestamp with time zone NOT NULL,
state smallint NOT NULL,
epic_id integer,
- CONSTRAINT resource_state_events_must_belong_to_issue_or_merge_request CHECK ((((issue_id <> NULL::bigint) AND (merge_request_id IS NULL)) OR ((merge_request_id <> NULL::bigint) AND (issue_id IS NULL))))
+ CONSTRAINT resource_state_events_must_belong_to_issue_or_merge_request_or_ CHECK ((((issue_id <> NULL::bigint) AND (merge_request_id IS NULL) AND (epic_id IS NULL)) OR ((issue_id IS NULL) AND (merge_request_id <> NULL::bigint) AND (epic_id IS NULL)) OR ((issue_id IS NULL) AND (merge_request_id IS NULL) AND (epic_id <> NULL::integer))))
);
CREATE SEQUENCE public.resource_state_events_id_seq
@@ -14026,6 +14026,7 @@ COPY "schema_migrations" (version) FROM STDIN;
20200515155620
20200518091745
20200518133123
+20200519074709
20200519101002
20200519115908
20200519171058
diff --git a/doc/.vale/gitlab/spelling-exceptions.txt b/doc/.vale/gitlab/spelling-exceptions.txt
index c1784e33164..adc51243d9d 100644
--- a/doc/.vale/gitlab/spelling-exceptions.txt
+++ b/doc/.vale/gitlab/spelling-exceptions.txt
@@ -1,6 +1,9 @@
Akismet
Alertmanager
Algolia
+allowlist
+allowlisting
+allowlists
Ansible
Anthos
API
@@ -29,6 +32,7 @@ autoscaling
awardable
Axios
Azure
+B-tree
backport
backported
backporting
@@ -57,6 +61,7 @@ CAS
CentOS
Chatops
Citrix
+Citus
clonable
Cloudwatch
Cobertura
@@ -83,6 +88,9 @@ deduplicated
deduplicates
deduplicating
deduplication
+denylist
+denylisting
+denylists
deprovision
deprovisioned
deprovisioning
@@ -114,6 +122,7 @@ Fluentd
Forgerock
Gantt
Gemnasium
+gettext
Git
Gitaly
Gitea
@@ -129,6 +138,7 @@ Gradle
Grafana
gravatar
Gzip
+Haml
hardcode
hardcoded
hardcodes
@@ -168,6 +178,7 @@ kanbans
Karma
Kerberos
Kibana
+Kinesis
Knative
Kramdown
Kubernetes
@@ -190,6 +201,10 @@ Markdown
markdownlint
Mattermost
mbox
+memoization
+memoize
+memoized
+memoizing
mergeable
Microsoft
middleware
@@ -204,6 +219,8 @@ misconfiguration
misconfigurations
misconfiguring
mitigations
+mixin
+mixins
mockup
mockups
ModSecurity
@@ -224,6 +241,7 @@ offboarded
offboarding
offboards
OmniAuth
+onboarding
OpenID
OpenShift
Packagist
@@ -235,6 +253,8 @@ Pipfiles
Piwik
PgBouncer
plaintext
+Poedit
+pooler
PostgreSQL
precompile
preconfigure
@@ -299,6 +319,7 @@ reverified
reverifies
reverify
Rubix
+Rubocop
runbook
runbooks
runit
@@ -306,11 +327,13 @@ runtime
runtimes
Salesforce
SAML
+sandboxing
sbt
Sendmail
Sentry
serverless
Sidekiq
+Sisense
sharding
shfmt
Shibboleth
@@ -330,6 +353,7 @@ spidering
Splunk
SpotBugs
SSH
+Stackdriver
storable
strace
strikethrough
diff --git a/doc/ci/junit_test_reports.md b/doc/ci/junit_test_reports.md
index a9b0b992c8a..43523bbd507 100644
--- a/doc/ci/junit_test_reports.md
+++ b/doc/ci/junit_test_reports.md
@@ -245,6 +245,9 @@ following command:
```ruby
Feature.enable(:junit_pipeline_view)
+
+# Enable the feature for a specific project
+Feature.enable(:junit_pipeline_view, Project.find())
```
## Viewing JUnit screenshots on GitLab
diff --git a/doc/development/geo.md b/doc/development/geo.md
index bf56340f8ec..5c781a60bac 100644
--- a/doc/development/geo.md
+++ b/doc/development/geo.md
@@ -94,7 +94,7 @@ projects that need updating. Those projects can be:
[Geo admin panel](../user/admin_area/geo_nodes.md).
When we fail to fetch a repository on the secondary `RETRIES_BEFORE_REDOWNLOAD`
-times, Geo does a so-called _redownload_. It will do a clean clone
+times, Geo does a so-called _re-download_. It will do a clean clone
into the `@geo-temporary` directory in the root of the storage. When
it's successful, we replace the main repo with the newly cloned one.
@@ -218,7 +218,7 @@ the performance of many synchronization operations.
FDW is a PostgreSQL extension ([`postgres_fdw`](https://www.postgresql.org/docs/11/postgres-fdw.html)) that is enabled within
the Geo Tracking Database (on a **secondary** node), which allows it
-to connect to the readonly database replica and perform queries and filter
+to connect to the read-only database replica and perform queries and filter
data from both instances.
This persistent connection is configured as an FDW server
@@ -226,7 +226,7 @@ named `gitlab_secondary`. This configuration exists within the database's user
context only. To access the `gitlab_secondary`, GitLab needs to use the
same database user that had previously been configured.
-The Geo Tracking Database accesses the readonly database replica via FDW as a regular user,
+The Geo Tracking Database accesses the read-only database replica via FDW as a regular user,
limited by its own restrictions. The credentials are configured as a
`USER MAPPING` associated with the `SERVER` mapped previously
(`gitlab_secondary`).
diff --git a/doc/development/gitaly.md b/doc/development/gitaly.md
index 17ab84a9562..417c96a44dd 100644
--- a/doc/development/gitaly.md
+++ b/doc/development/gitaly.md
@@ -54,7 +54,7 @@ The process for adding new Gitaly features is:
These steps often overlap. It is possible to use an unreleased version
of Gitaly and `gitaly-proto` during testing and development.
-- See the [Gitaly repo](https://gitlab.com/gitlab-org/gitaly/blob/master/CONTRIBUTING.md#development-and-testing-with-a-custom-gitaly-proto) for instructions on writing server side code with an unreleased protocol.
+- See the [Gitaly repository](https://gitlab.com/gitlab-org/gitaly/blob/master/CONTRIBUTING.md#development-and-testing-with-a-custom-gitaly-proto) for instructions on writing server side code with an unreleased protocol.
- See [below](#running-tests-with-a-locally-modified-version-of-gitaly) for instructions on running GitLab CE tests with a modified version of Gitaly.
- In GDK run `gdk install` and restart `gdk run` (or `gdk run app`) to use a locally modified Gitaly version for development
@@ -67,7 +67,7 @@ This should make it easier to contribute for developers who are less
comfortable writing Go code.
There is documentation for this approach in [the Gitaly
-repo](https://gitlab.com/gitlab-org/gitaly/blob/master/doc/ruby_endpoint.md).
+repository](https://gitlab.com/gitlab-org/gitaly/blob/master/doc/ruby_endpoint.md).
## Gitaly-Related Test Failures
@@ -323,8 +323,8 @@ the integration by using GDK:
1. Navigate to GDK's root directory.
1. Make sure you have the proper branch checked out for Gitaly.
1. Recompile it with `make gitaly-setup` and restart the service with `gdk restart gitaly`.
- 1. Make sure your setup is runnig: `gdk status | grep praefect`.
- 1. Check what config file is used: `cat ./services/praefect/run | grep praefect` value of the `-config` flag
+ 1. Make sure your setup is running: `gdk status | grep praefect`.
+ 1. Check what configuration file is used: `cat ./services/praefect/run | grep praefect` value of the `-config` flag
1. Uncomment `prometheus_listen_addr` in the configuration file and run `gdk restart gitaly`.
1. Make sure that the flag is not enabled yet:
diff --git a/doc/development/gotchas.md b/doc/development/gotchas.md
index 8f077e613b7..7e08da8162b 100644
--- a/doc/development/gotchas.md
+++ b/doc/development/gotchas.md
@@ -155,7 +155,7 @@ refresh_service.execute(oldrev, newrev, ref)
See ["Why is it bad style to `rescue Exception => e` in Ruby?"](https://stackoverflow.com/questions/10048173/why-is-it-bad-style-to-rescue-exception-e-in-ruby).
_**Note:** This rule is [enforced automatically by
-Rubocop](https://gitlab.com/gitlab-org/gitlab-foss/blob/8-4-stable/.rubocop.yml#L911-914)._
+RuboCop](https://gitlab.com/gitlab-org/gitlab-foss/blob/8-4-stable/.rubocop.yml#L911-914)._
## Do not use inline JavaScript in views
diff --git a/doc/development/hash_indexes.md b/doc/development/hash_indexes.md
index bc962ac0cd6..1ed76e35f69 100644
--- a/doc/development/hash_indexes.md
+++ b/doc/development/hash_indexes.md
@@ -1,6 +1,6 @@
# Hash Indexes
-PostgreSQL supports hash indexes besides the regular btree
+PostgreSQL supports hash indexes besides the regular B-tree
indexes. Hash indexes however are to be avoided at all costs. While they may
_sometimes_ provide better performance the cost of rehashing can be very high.
More importantly: at least until PostgreSQL 10.0 hash indexes are not
@@ -17,4 +17,4 @@ documentation:
RuboCop is configured to register an offense when it detects the use of a hash
index.
-Instead of using hash indexes you should use regular btree indexes.
+Instead of using hash indexes you should use regular B-tree indexes.
diff --git a/doc/development/i18n/proofreader.md b/doc/development/i18n/proofreader.md
index bbce5e38fee..839d8f0a214 100644
--- a/doc/development/i18n/proofreader.md
+++ b/doc/development/i18n/proofreader.md
@@ -5,6 +5,7 @@ are very appreciative of the work done by translators and proofreaders!
## Proofreaders
+
- Albanian
- Proofreaders needed.
- Amharic
@@ -104,6 +105,7 @@ are very appreciative of the work done by translators and proofreaders!
- Andrew Vityuk - [GitLab](https://gitlab.com/3_1_3_u), [CrowdIn](https://crowdin.com/profile/andruwa13)
- Welsh
- Proofreaders needed.
+
## Become a proofreader
diff --git a/doc/development/i18n/translation.md b/doc/development/i18n/translation.md
index f08e3a44f2c..ed205c20c0d 100644
--- a/doc/development/i18n/translation.md
+++ b/doc/development/i18n/translation.md
@@ -79,8 +79,10 @@ ethnicity.
In languages which distinguish between a male and female form, use both or
choose a neutral formulation.
+
For example in German, the word "user" can be translated into "Benutzer" (male) or "Benutzerin" (female).
Therefore "create a new user" would translate into "Benutzer(in) anlegen".
+
### Updating the glossary
@@ -91,6 +93,8 @@ To propose additions to the glossary please
### Inclusive language in French
+
In French, the "écriture inclusive" is now over (see on [Legifrance](https://www.legifrance.gouv.fr/affichTexte.do?cidTexte=JORFTEXT000036068906&categorieLien=id)).
So, to include both genders, write “Utilisateurs et utilisatrices” instead of “Utilisateur·rice·s”.
When space is missing, the male gender should be used alone.
+
diff --git a/doc/development/instrumentation.md b/doc/development/instrumentation.md
index d72e1c6635e..ee1aab1456e 100644
--- a/doc/development/instrumentation.md
+++ b/doc/development/instrumentation.md
@@ -119,9 +119,9 @@ without measuring anything.
Three values are measured for a block:
-- The real time elapsed, stored in NAME_real_time.
-- The CPU time elapsed, stored in NAME_cpu_time.
-- The call count, stored in NAME_call_count.
+- The real time elapsed, stored in `NAME_real_time`.
+- The CPU time elapsed, stored in `NAME_cpu_time`.
+- The call count, stored in `NAME_call_count`.
Both the real and CPU timings are measured in milliseconds.
diff --git a/doc/development/integrations/secure.md b/doc/development/integrations/secure.md
index 6f92dad22ef..6630d230089 100644
--- a/doc/development/integrations/secure.md
+++ b/doc/development/integrations/secure.md
@@ -15,7 +15,7 @@ scanner, as well as requirements and guidelines for the Docker image.
## Job definition
-This section desribes several important fields to add to the security scanner's job
+This section describes several important fields to add to the security scanner's job
definition file. Full documentation on these and other available fields can be viewed
in the [CI documentation](../../ci/yaml/README.md#image).
@@ -89,9 +89,9 @@ for variables such as `DEPENDENCY_SCANNING_DISABLED`, `CONTAINER_SCANNING_DISABL
disable running the custom scanner.
GitLab also defines a `CI_PROJECT_REPOSITORY_LANGUAGES` variable, which provides the list of
-languages in the repo. Depending on this value, your scanner may or may not do something different.
+languages in the repository. Depending on this value, your scanner may or may not do something different.
Language detection currently relies on the [`linguist`](https://github.com/github/linguist) Ruby gem.
-See [GitLab CI/CD prefined variables](../../ci/variables/predefined_variables.md#variables-reference).
+See [GitLab CI/CD predefined variables](../../ci/variables/predefined_variables.md#variables-reference).
#### Policy checking example
diff --git a/doc/development/integrations/secure_partner_integration.md b/doc/development/integrations/secure_partner_integration.md
index 759b904e97c..388256e16b1 100644
--- a/doc/development/integrations/secure_partner_integration.md
+++ b/doc/development/integrations/secure_partner_integration.md
@@ -54,7 +54,7 @@ best place to integrate your own product and its results into GitLab.
## How to onboard
This section describes the steps you need to complete to onboard as a partner
-and complete an intgration with the Secure stage.
+and complete an integration with the Secure stage.
1. Read about our [partnerships](https://about.gitlab.com/partners/integrate/).
1. [Create an issue](https://gitlab.com/gitlab-com/alliances/alliances/-/issues/new?issuable_template=new_partner)
diff --git a/doc/development/internal_api.md b/doc/development/internal_api.md
index 731325d930c..d220a2d46fb 100644
--- a/doc/development/internal_api.md
+++ b/doc/development/internal_api.md
@@ -47,7 +47,7 @@ POST /internal/allowed
| `protocol` | string | yes | SSH when called from GitLab-shell, HTTP or SSH when called from Gitaly |
| `action` | string | yes | Git command being run (`git-upload-pack`, `git-receive-pack`, `git-upload-archive`) |
| `changes` | string | yes | ` ` when called from Gitaly, The magic string `_any` when called from GitLab Shell |
-| `check_ip` | string | no | Ip address from which call to GitLab Shell was made |
+| `check_ip` | string | no | IP address from which call to GitLab Shell was made |
Example request:
diff --git a/doc/development/logging.md b/doc/development/logging.md
index afd3aa14866..ccd4adf7cef 100644
--- a/doc/development/logging.md
+++ b/doc/development/logging.md
@@ -359,7 +359,7 @@ end
1. If you add a new file, submit an issue to the [production
tracker](https://gitlab.com/gitlab-com/gl-infra/production/-/issues) or
- a merge request to the [gitlab_fluentd](https://gitlab.com/gitlab-cookbooks/gitlab_fluentd)
+ a merge request to the [`gitlab_fluentd`](https://gitlab.com/gitlab-cookbooks/gitlab_fluentd)
project. See [this example](https://gitlab.com/gitlab-cookbooks/gitlab_fluentd/-/merge_requests/51/diffs).
1. Be sure to update the [GitLab CE/EE documentation](../administration/logs.md) and the [GitLab.com
diff --git a/doc/development/migration_style_guide.md b/doc/development/migration_style_guide.md
index 4e10f1657b7..5f9eaff0df0 100644
--- a/doc/development/migration_style_guide.md
+++ b/doc/development/migration_style_guide.md
@@ -35,7 +35,7 @@ and post-deployment migrations (`db/post_migrate`) are run after the deployment
## Schema Changes
-Changes to the schema should be commited to `db/structure.sql`. This
+Changes to the schema should be committed to `db/structure.sql`. This
file is automatically generated by Rails, so you normally should not
edit this file by hand. If your migration is adding a column to a
table, that column will be added at the bottom. Please do not reorder
@@ -49,7 +49,7 @@ regenerate a clean `db/structure.sql` for the migrations you're
adding. This script will apply all migrations found in `db/migrate`
or `db/post_migrate`, so if there are any migrations you don't want to
commit to the schema, rename or remove them. If your branch is not
-targetting `master` you can set the `TARGET` environment variable.
+targeting `master` you can set the `TARGET` environment variable.
```shell
# Regenerate schema against `master`
@@ -343,7 +343,7 @@ def up
end
```
-The RuboCop rule generally allows standard Rails migration methods, listed below. This example will cause a rubocop offense:
+The RuboCop rule generally allows standard Rails migration methods, listed below. This example will cause a Rubocop offense:
```ruby
disabled_ddl_transaction!
diff --git a/doc/development/new_fe_guide/development/accessibility.md b/doc/development/new_fe_guide/development/accessibility.md
index aa76a9fec07..f76fd72d4dc 100644
--- a/doc/development/new_fe_guide/development/accessibility.md
+++ b/doc/development/new_fe_guide/development/accessibility.md
@@ -1,4 +1,4 @@
-# Accessiblity
+# Accessibility
Using semantic HTML plays a key role when it comes to accessibility.
@@ -37,7 +37,7 @@ In forms we should use the `for` attribute in the label statement:
## Testing
1. On MacOS you can use [VoiceOver](https://www.apple.com/accessibility/mac/vision/) by pressing `cmd+F5`.
-1. On Windows you can use [Narrator](https://www.microsoft.com/en-us/accessibility/windows) by pressing Windows logo key + Ctrl + Enter.
+1. On Windows you can use [Narrator](https://www.microsoft.com/en-us/accessibility/windows) by pressing Windows logo key + Control + Enter.
## Online resources
diff --git a/doc/development/new_fe_guide/development/performance.md b/doc/development/new_fe_guide/development/performance.md
index 4a85c04d8cf..edb2eed982d 100644
--- a/doc/development/new_fe_guide/development/performance.md
+++ b/doc/development/new_fe_guide/development/performance.md
@@ -5,7 +5,7 @@
We have a performance dashboard available in one of our [Grafana instances](https://dashboards.gitlab.net/d/1EBTz3Dmz/sitespeed-page-summary?orgId=1). This dashboard automatically aggregates metric data from [sitespeed.io](https://www.sitespeed.io/) every 6 hours. These changes are displayed after a set number of pages are aggregated.
These pages can be found inside a text file in the [`gitlab-build-images` repository](https://gitlab.com/gitlab-org/gitlab-build-images) called [`gitlab.txt`](https://gitlab.com/gitlab-org/gitlab-build-images/blob/master/scripts/gitlab.txt)
-Any frontend engineer can contribute to this dashboard. They can contribute by adding or removing urls of pages from this text file. Please have a [frontend monitoring expert](https://about.gitlab.com/company/team/) review your changes before assigning to a maintainer of the `gitlab-build-images` project. The changes will go live on the next scheduled run after the changes are merged into `master`.
+Any frontend engineer can contribute to this dashboard. They can contribute by adding or removing URLs of pages from this text file. Please have a [frontend monitoring expert](https://about.gitlab.com/company/team/) review your changes before assigning to a maintainer of the `gitlab-build-images` project. The changes will go live on the next scheduled run after the changes are merged into `master`.
There are 3 recommended high impact metrics to review on each page:
diff --git a/doc/development/omnibus.md b/doc/development/omnibus.md
index 28ca500f21a..deaf72d2ecf 100644
--- a/doc/development/omnibus.md
+++ b/doc/development/omnibus.md
@@ -24,7 +24,7 @@ and write it to the Rails root. In the Omnibus packages, reconfigure writes the
The Omnibus design separates code (read-only, under `/opt/gitlab`) from data
(read/write, under `/var/opt/gitlab`) and logs (read/write, under
`/var/log/gitlab`). To make this happen the reconfigure script sets custom
-paths where it can in GitLab config files, and where there are no path
+paths where it can in GitLab configuration files, and where there are no path
settings, it uses symlinks.
For example, `config/gitlab.yml` is treated as data so that file is a symlink.
diff --git a/doc/development/permissions.md b/doc/development/permissions.md
index 0772389bf9e..7db188d17c9 100644
--- a/doc/development/permissions.md
+++ b/doc/development/permissions.md
@@ -14,7 +14,7 @@ Groups and projects can have the following visibility levels:
- private (`0`) - an entity is visible only to the approved members of the entity
The visibility level of a group can be changed only if all subgroups and
-subprojects have the same or lower visibility level. (e.g., a group can be set
+sub-projects have the same or lower visibility level. (e.g., a group can be set
to internal only if all subgroups and projects are internal or private).
Visibility levels can be found in the `Gitlab::VisibilityLevel` module.
@@ -92,10 +92,10 @@ into different features like Merge Requests and CI flow.
| Activity level | Resource | Locations |Permission dependency|
|----------------|----------|-----------|-----|
-| View | License information | Dependency list, License Compliance | Can view repo |
-| View | Dependency information | Dependency list, License Compliance | Can view repo |
+| View | License information | Dependency list, License Compliance | Can view repository |
+| View | Dependency information | Dependency list, License Compliance | Can view repository |
| View | Vulnerabilities information | Dependency list | Can view security findings |
-| View | Black/Whitelisted licenses for the project | License Compliance, Merge request | Can view repo |
+| View | Black/Whitelisted licenses for the project | License Compliance, Merge request | Can view repository |
| View | Security findings | Merge Request, CI job page, Pipeline security tab | Can read the project and CI jobs |
| View | Vulnerability feedback | Merge Request | Can read security findings |
| View | Dependency List page | Project | Can access Dependency information |
diff --git a/doc/development/pipelines.md b/doc/development/pipelines.md
index 2fa2af17b03..c90873b969d 100644
--- a/doc/development/pipelines.md
+++ b/doc/development/pipelines.md
@@ -416,7 +416,7 @@ of the `gitlab-org/gitlab-foss` project. These jobs are only created in the foll
- `master` commits (pushes and scheduled pipelines).
- `gitlab-org/security/gitlab` merge requests.
- Merge requests which include `RUN AS-IF-FOSS` in their title.
-- Merge requests that changes the CI config.
+- Merge requests that changes the CI configuration.
The `* as-if-foss` jobs have the `FOSS_ONLY='1'` variable set and gets their EE-specific
folders removed before the tests start running.
@@ -546,19 +546,19 @@ The current stages are:
- `post-qa`: This stage includes jobs that build reports or gather data from
the `qa` stage's jobs (e.g. Review App performance report).
- `pages`: This stage includes a job that deploys the various reports as
- GitLab Pages (e.g. ,
- ,
- ).
+ GitLab Pages (e.g. [`coverage-ruby`](https://gitlab-org.gitlab.io/gitlab/coverage-ruby/),
+ [`coverage-javascript`](https://gitlab-org.gitlab.io/gitlab/coverage-javascript/),
+ [`webpack-report`](https://gitlab-org.gitlab.io/gitlab/webpack-report/).
### Default image
-The default image is defined in .
+The default image is defined in [`.gitlab-ci.yml`](https://gitlab.com/gitlab-org/gitlab/blob/master/.gitlab-ci.yml).
It includes Ruby, Go, Git, Git LFS, Chrome, Node, Yarn, PostgreSQL, and Graphics Magick.
The images used in our pipelines are configured in the
[`gitlab-org/gitlab-build-images`](https://gitlab.com/gitlab-org/gitlab-build-images)
-project, which is push-mirrored to
+project, which is push-mirrored to [`gitlab/gitlab-build-images`](https://dev.gitlab.org/gitlab/gitlab-build-images)
for redundancy.
The current version of the build images can be found in the
@@ -600,7 +600,7 @@ then included in individual jobs via [`extends`](../ci/yaml/README.md#extends).
The `rules` definitions are composed of `if:` conditions and `changes:` patterns,
which are also defined in
-
+[`rules.gitlab-ci.yml`](https://gitlab.com/gitlab-org/gitlab/-/blob/master/.gitlab/ci/rules.gitlab-ci.yml)
and included in `rules` definitions via [YAML anchors](../ci/yaml/README.md#anchors)
#### `if:` conditions
diff --git a/doc/development/redis.md b/doc/development/redis.md
index ec3ad4fa678..6782ea96448 100644
--- a/doc/development/redis.md
+++ b/doc/development/redis.md
@@ -18,7 +18,7 @@ database.
Redis is a flat namespace with no hierarchy, which means we must pay attention
to key names to avoid collisions. Typically we use colon-separated elements to
-provide a semblence of structure at application level. An example might be
+provide a semblance of structure at application level. An example might be
`projects:1:somekey`.
Although we split our Redis usage into three separate purposes, and those may
diff --git a/doc/development/refactoring_guide/index.md b/doc/development/refactoring_guide/index.md
index 3c38f03f525..a9ff9556aed 100644
--- a/doc/development/refactoring_guide/index.md
+++ b/doc/development/refactoring_guide/index.md
@@ -69,7 +69,7 @@ expect(cleanForSnapshot(wrapper.element)).toMatchSnapshot();
### Examples
-- [Pinning test in a haml to vue refactor](https://gitlab.com/gitlab-org/gitlab/-/merge_requests/27691#pinning-tests)
+- [Pinning test in a Haml to Vue refactor](https://gitlab.com/gitlab-org/gitlab/-/merge_requests/27691#pinning-tests)
- [Pinning test in isolating a bug](https://gitlab.com/gitlab-org/gitlab-foss/-/merge_requests/32198#note_212736225)
- [Pinning test in refactoring dropdown](https://gitlab.com/gitlab-org/gitlab/-/merge_requests/28173)
- [Pinning test in refactoring vulnerability_details.vue](https://gitlab.com/gitlab-org/gitlab/-/merge_requests/25830/commits)
diff --git a/doc/development/renaming_features.md b/doc/development/renaming_features.md
index 6a196921a5d..daf437027db 100644
--- a/doc/development/renaming_features.md
+++ b/doc/development/renaming_features.md
@@ -16,7 +16,7 @@ The more of the following that are true, the more likely you should choose the f
- You are not confident the new name is permanent.
- The feature is susceptible to bugs (large, complex, needing refactor, etc).
-- The renaming will be difficult to review (feature spans many lines/files/repos).
+- The renaming will be difficult to review (feature spans many lines, files, or repositories).
- The renaming will be disruptive in some way (database table renaming).
## Consider a façade-first approach
diff --git a/doc/development/scalability.md b/doc/development/scalability.md
index 083a23610c5..69d56d37529 100644
--- a/doc/development/scalability.md
+++ b/doc/development/scalability.md
@@ -52,10 +52,10 @@ maintain and support one database with tables with many rows.
There are two ways to deal with this:
-- Partioning. Locally split up tables data.
+- Partitioning. Locally split up tables data.
- Sharding. Distribute data across multiple databases.
-Partioning is a built-in PostgreSQL feature and requires minimal changes
+Partitioning is a built-in PostgreSQL feature and requires minimal changes
in the application. However, it [requires PostgreSQL
11](https://www.2ndquadrant.com/en/blog/partitioning-evolution-postgresql-11/).
@@ -246,9 +246,9 @@ lifting of many activities, including:
- Processing CI builds and pipelines.
The full list of jobs can be found in the
-[app/workers](https://gitlab.com/gitlab-org/gitlab/tree/master/app/workers)
+[`app/workers`](https://gitlab.com/gitlab-org/gitlab/tree/master/app/workers)
and
-[ee/app/workers](https://gitlab.com/gitlab-org/gitlab/tree/master/ee/app/workers)
+[`ee/app/workers`](https://gitlab.com/gitlab-org/gitlab/tree/master/ee/app/workers)
directories in the GitLab code base.
#### Runaway Queues
@@ -281,7 +281,7 @@ in a timely manner:
benefits.
From the Sidekiq logs, it's possible to see which jobs run the most
-frequently and/or take the longest. For example, theis Kibana
+frequently and/or take the longest. For example, these Kibana
visualizations show the jobs that consume the most total time:
![Most time-consuming Sidekiq jobs](img/sidekiq_most_time_consuming_jobs.png)
diff --git a/doc/development/secure_coding_guidelines.md b/doc/development/secure_coding_guidelines.md
index 8fbb303abc2..d7cd3d3cd2a 100644
--- a/doc/development/secure_coding_guidelines.md
+++ b/doc/development/secure_coding_guidelines.md
@@ -52,7 +52,7 @@ Some example of well implemented access controls and tests:
1. [example2](https://dev.gitlab.org/gitlab/gitlabhq/-/merge_requests/2511/diffs#ed3aaab1510f43b032ce345909a887e5b167e196_142_155)
1. [example3](https://dev.gitlab.org/gitlab/gitlabhq/-/merge_requests/3170/diffs?diff_id=17494)
-**NB:** any input from development team is welcome, e.g. about rubocop rules.
+**NB:** any input from development team is welcome, e.g. about Rubocop rules.
## Regular Expressions guidelines
@@ -67,7 +67,7 @@ matches = re.findall("^bar$",text)
print(matches)
```
-The Python example will output an emtpy array (`[]`) as the matcher considers the whole string `foo\nbar` including the newline (`\n`). In contrast Ruby's Regular Expression engine acts differently:
+The Python example will output an empty array (`[]`) as the matcher considers the whole string `foo\nbar` including the newline (`\n`). In contrast Ruby's Regular Expression engine acts differently:
```ruby
text = "foo\nbar"
@@ -111,7 +111,7 @@ or controls the regular expression (regex) used, and is able to enter user input
### Impact
-The resource, for example Unicorn, Puma, or Sidekiq, can be made to hang as it takes a long time to evaulate the bad regex match.
+The resource, for example Unicorn, Puma, or Sidekiq, can be made to hang as it takes a long time to evaluate the bad regex match.
### Examples
@@ -140,9 +140,9 @@ class Email < ApplicationRecord
GitLab has `Gitlab::UntrustedRegexp` which internally uses the [`re2`](https://github.com/google/re2/wiki/Syntax) library.
By utilizing `re2`, we get a strict limit on total execution time, and a smaller subset of available regex features.
-All user-provided regexes should use `Gitlab::UntrustedRegexp`.
+All user-provided regular expressions should use `Gitlab::UntrustedRegexp`.
-For other regexes, here are a few guidelines:
+For other regular expressions, here are a few guidelines:
- Remove unnecessary backtracking.
- Avoid nested quantifiers if possible.
@@ -206,14 +206,14 @@ The [GitLab::HTTP](https://gitlab.com/gitlab-org/gitlab/-/blob/master/lib/gitlab
`Outbound requests` options that allow instance administrators to block all internal connections, or limit the networks to which connections can be made.
In some cases, it has been possible to configure GitLab::HTTP as the HTTP
-connection library for 3rd-party gems. This is preferrable over re-implementing
+connection library for 3rd-party gems. This is preferable over re-implementing
the mitigations for a new feature.
- [More details](https://dev.gitlab.org/gitlab/gitlabhq/-/merge_requests/2530/diffs)
#### Feature-specific Mitigations
-For situtions in which an allowlist or GitLab:HTTP cannot be used, it will be necessary to implement mitigations directly in the feature. It is best to validate the destination IP addresses themselves, not just domain names, as DNS can be controlled by the attacker. Below are a list of mitigations that should be implemented.
+For situations in which an allowlist or GitLab:HTTP cannot be used, it will be necessary to implement mitigations directly in the feature. It is best to validate the destination IP addresses themselves, not just domain names, as DNS can be controlled by the attacker. Below are a list of mitigations that should be implemented.
**Important Note:** There are many tricks to bypass common SSRF validations. If feature-specific mitigations are necessary, they should be reviewed by the AppSec team, or a developer who has worked on SSRF mitigations previously.
@@ -230,7 +230,7 @@ For situtions in which an allowlist or GitLab:HTTP cannot be used, it will be ne
- For HTTP connections: Disable redirects or validate the redirect destination
- To mitigate DNS rebinding attacks, validate and use the first IP address received
-See [url_blocker_spec.rb](https://gitlab.com/gitlab-org/gitlab/-/blob/master/spec/lib/gitlab/url_blocker_spec.rb) for examples of SSRF payloads
+See [`url_blocker_spec.rb`](https://gitlab.com/gitlab-org/gitlab/-/blob/master/spec/lib/gitlab/url_blocker_spec.rb) for examples of SSRF payloads
## XSS guidelines
diff --git a/doc/development/telemetry/snowplow.md b/doc/development/telemetry/snowplow.md
index 862291680a6..b7090ee4d20 100644
--- a/doc/development/telemetry/snowplow.md
+++ b/doc/development/telemetry/snowplow.md
@@ -127,7 +127,7 @@ Below is an example of `data-track-*` attributes assigned to a button:
/>
```
-Event listeners are bound at the document level to handle click events on or within elements with these data attributes. This allows for them to be properly handled on rerendering and changes to the DOM, but it's important to know that because of the way these events are bound, click events shouldn't be stopped from propagating up the DOM tree. If for any reason click events are being stopped from propagating, you'll need to implement your own listeners and follow the instructions in [Tracking in raw JavaScript](#tracking-in-raw-javascript).
+Event listeners are bound at the document level to handle click events on or within elements with these data attributes. This allows for them to be properly handled on re-rendering and changes to the DOM, but it's important to know that because of the way these events are bound, click events shouldn't be stopped from propagating up the DOM tree. If for any reason click events are being stopped from propagating, you'll need to implement your own listeners and follow the instructions in [Tracking in raw JavaScript](#tracking-in-raw-javascript).
Below is a list of supported `data-track-*` attributes:
@@ -219,7 +219,7 @@ button.addEventListener('click', () => {
### Tests and test helpers
-In Jest particularly in vue tests, you can use the following:
+In Jest particularly in Vue tests, you can use the following:
```javascript
import { mockTracking } from 'helpers/tracking_helper';
@@ -339,7 +339,7 @@ Snowplow Micro is a very small version of a full Snowplow data collection pipeli
Snowplow Micro is a Docker-based solution for testing frontend and backend events in a local development environment. You need to modify GDK using the instructions below to set this up.
- Read [Introducing Snowplow Micro](https://snowplowanalytics.com/blog/2019/07/17/introducing-snowplow-micro/)
-- Look at the [Snowplow Micro repo](https://github.com/snowplow-incubator/snowplow-micro)
+- Look at the [Snowplow Micro repository](https://github.com/snowplow-incubator/snowplow-micro)
- Watch our [installation guide recording](https://www.youtube.com/watch?v=OX46fo_A0Ag)
1. Install [Snowplow Micro](https://github.com/snowplow-incubator/snowplow-micro)
diff --git a/doc/user/group/index.md b/doc/user/group/index.md
index 0a59def89fb..ae12235daae 100644
--- a/doc/user/group/index.md
+++ b/doc/user/group/index.md
@@ -513,7 +513,7 @@ underlying projects, issues, etc, by IP address. This can help ensure that
particular content doesn't leave the premises, while not blocking off access to
the entire instance.
-Add one or more whitelisted IP subnets using CIDR notation in comma separated format to the group settings and anyone
+Add one or more allowed IP subnets using CIDR notation in comma separated format to the group settings and anyone
coming from a different IP address won't be able to access the restricted
content.
@@ -533,7 +533,7 @@ the group regardless of the IP restriction.
You can restrict access to groups by
allowing only users with email addresses in particular domains to be added to the group.
-Add email domains you want to whitelist and users with emails from different
+Add email domains you want to allow and users with emails from different
domains won't be allowed to be added to this group.
Some domains cannot be restricted. These are the most popular public email domains, such as:
diff --git a/doc/user/permissions.md b/doc/user/permissions.md
index 8cc88afe214..7a3e882ceb7 100644
--- a/doc/user/permissions.md
+++ b/doc/user/permissions.md
@@ -249,11 +249,12 @@ group.
| View/manage group-level Kubernetes cluster | | | | ✓ | ✓ |
| Create subgroup | | | | ✓ (1) | ✓ |
| Edit epic comments (posted by any user) **(ULTIMATE)** | | | | ✓ (2) | ✓ (2) |
-| Edit group | | | | | ✓ |
+| Edit group settings | | | | | ✓ |
| Manage group level CI/CD variables | | | | | ✓ |
| Manage group members | | | | | ✓ |
-| Remove group | | | | | ✓ |
+| Delete group | | | | | ✓ |
| Delete group epic **(ULTIMATE)** | | | | | ✓ |
+| Edit SAML SSO Billing **(SILVER ONLY)** | ✓ | ✓ | ✓ | ✓ | ✓ (4) |
| View group Audit Events | | | | | ✓ |
| Disable notification emails | | | | | ✓ |
| View Contribution analytics | ✓ | ✓ | ✓ | ✓ | ✓ |
@@ -261,6 +262,8 @@ group.
| View Issues analytics **(PREMIUM)** | ✓ | ✓ | ✓ | ✓ | ✓ |
| View Productivity analytics **(PREMIUM)** | | ✓ | ✓ | ✓ | ✓ |
| View Value Stream analytics | ✓ | ✓ | ✓ | ✓ | ✓ |
+| View Billing **(FREE ONLY)** | ✓ | ✓ | ✓ | ✓ | ✓ (4) |
+| View Usage Quotas **(FREE ONLY)** | ✓ | ✓ | ✓ | ✓ | ✓ (4) |
1. Groups can be set to [allow either Owners or Owners and
Maintainers to create subgroups](group/subgroups/index.md#creating-a-subgroup)
@@ -268,6 +271,7 @@ group.
1. Default project creation role can be changed at:
- The [instance level](admin_area/settings/visibility_and_access_controls.md#default-project-creation-protection).
- The [group level](group/index.md#default-project-creation-level).
+1. Does not apply to subgroups.
### Subgroup permissions
diff --git a/doc/user/project/integrations/prometheus.md b/doc/user/project/integrations/prometheus.md
index 67b4343d26e..14a054f7365 100644
--- a/doc/user/project/integrations/prometheus.md
+++ b/doc/user/project/integrations/prometheus.md
@@ -827,6 +827,14 @@ You can create annotations by making requests to the
![Annotations UI](img/metrics_dashboard_annotations_ui_v13.0.png)
+#### Retention policy
+
+> [Introduced](https://gitlab.com/gitlab-org/gitlab/issues/211433) in GitLab 13.01.
+
+To avoid excessive storage space consumption by stale annotations, records attached
+to time periods older than two weeks are removed daily. This recurring background
+job runs at 1:00 a.m. local server time.
+
### Expand panel
> [Introduced](https://gitlab.com/groups/gitlab-org/-/epics/3100) in GitLab 13.0.
diff --git a/lib/api/projects.rb b/lib/api/projects.rb
index f305da681c4..e00fb61f478 100644
--- a/lib/api/projects.rb
+++ b/lib/api/projects.rb
@@ -502,7 +502,9 @@ module API
link = user_project.project_group_links.find_by(group_id: params[:group_id])
not_found!('Group Link') unless link
- destroy_conditionally!(link)
+ destroy_conditionally!(link) do
+ ::Projects::GroupLinks::DestroyService.new(user_project, current_user).execute(link)
+ end
end
# rubocop: enable CodeReuse/ActiveRecord
diff --git a/qa/qa.rb b/qa/qa.rb
index 1a51ab8b2dd..c387a0e38d2 100644
--- a/qa/qa.rb
+++ b/qa/qa.rb
@@ -80,7 +80,6 @@ module QA
autoload :User, 'qa/resource/user'
autoload :ProjectMilestone, 'qa/resource/project_milestone'
autoload :Members, 'qa/resource/members'
- autoload :Wiki, 'qa/resource/wiki'
autoload :File, 'qa/resource/file'
autoload :Fork, 'qa/resource/fork'
autoload :SSHKey, 'qa/resource/ssh_key'
@@ -112,6 +111,10 @@ module QA
module Settings
autoload :HashedStorage, 'qa/resource/settings/hashed_storage'
end
+
+ module Wiki
+ autoload :ProjectPage, 'qa/resource/wiki/project_page'
+ end
end
##
@@ -327,7 +330,6 @@ module QA
module Wiki
autoload :Edit, 'qa/page/project/wiki/edit'
- autoload :New, 'qa/page/project/wiki/new'
autoload :Show, 'qa/page/project/wiki/show'
autoload :GitAccess, 'qa/page/project/wiki/git_access'
end
@@ -439,6 +441,10 @@ module QA
autoload :Common, 'qa/page/component/issuable/common'
end
+ module IssueBoard
+ autoload :Show, 'qa/page/component/issue_board/show'
+ end
+
module WebIDE
autoload :Alert, 'qa/page/component/web_ide/alert'
diff --git a/qa/qa/page/component/issue_board/show.rb b/qa/qa/page/component/issue_board/show.rb
new file mode 100644
index 00000000000..0c840eba7ce
--- /dev/null
+++ b/qa/qa/page/component/issue_board/show.rb
@@ -0,0 +1,138 @@
+# frozen_string_literal: true
+
+module QA
+ module Page
+ module Component
+ module IssueBoard
+ class Show < QA::Page::Base
+ view 'app/assets/javascripts/boards/components/board_card.vue' do
+ element :board_card
+ end
+
+ view 'app/assets/javascripts/boards/components/board_form.vue' do
+ element :board_name_field
+ end
+
+ view 'app/assets/javascripts/boards/components/board_list.vue' do
+ element :board_list_cards_area
+ end
+
+ view 'app/assets/javascripts/boards/components/boards_selector.vue' do
+ element :boards_dropdown
+ element :boards_dropdown_content
+ element :create_new_board_button
+ end
+
+ view 'app/assets/javascripts/vue_shared/components/deprecated_modal.vue' do
+ element :save_changes_button
+ end
+
+ view 'app/assets/javascripts/vue_shared/components/sidebar/labels_select/base.vue' do
+ element :labels_dropdown_content
+ end
+
+ view 'app/assets/javascripts/vue_shared/components/sidebar/labels_select/dropdown_title.vue' do
+ element :labels_edit_button
+ end
+
+ view 'app/views/shared/boards/_show.html.haml' do
+ element :boards_list
+ end
+
+ view 'app/views/shared/boards/components/_board.html.haml' do
+ element :board_list
+ element :board_list_header
+ end
+
+ view 'app/assets/javascripts/boards/toggle_focus.js' do
+ element :focus_mode_button
+ end
+
+ # The `focused_board` method does not use `find_element` with an element defined
+ # with the attribute `data-qa-selector` since such element is not unique when the
+ # `is-focused` class is not set, and it was not possible to find a better solution.
+ def focused_board
+ find('.issue-boards-content.js-focus-mode-board.is-focused')
+ end
+
+ def boards_dropdown
+ find_element(:boards_dropdown)
+ end
+
+ def boards_dropdown_content
+ find_element(:boards_dropdown_content)
+ end
+
+ def boards_list_cards_area_with_index(index)
+ wait_boards_list_finish_loading do
+ within_element_by_index(:board_list, index) do
+ find_element(:board_list_cards_area)
+ end
+ end
+ end
+
+ def boards_list_header_with_index(index)
+ wait_boards_list_finish_loading do
+ within_element_by_index(:board_list, index) do
+ find_element(:board_list_header)
+ end
+ end
+ end
+
+ def card_of_list_with_index(index)
+ wait_boards_list_finish_loading do
+ within_element_by_index(:board_list, index) do
+ find_element(:board_card)
+ end
+ end
+ end
+
+ def click_boards_dropdown_button
+ # The dropdown button comes from the `GlDropdown` component of `@gitlab/ui`,
+ # so it wasn't possible to add a `data-qa-selector` to it.
+ find_element(:boards_dropdown).find('button').click
+ end
+
+ def click_focus_mode_button
+ click_element(:focus_mode_button)
+ end
+
+ def configure_by_label(label)
+ click_boards_config_button
+ click_element(:labels_edit_button)
+ find_element(:labels_dropdown_content).find('li', text: label).click
+ click_element(:save_changes_button)
+ wait_boards_list_finish_loading
+ end
+
+ def create_new_board(board_name)
+ click_boards_dropdown_button
+ click_element(:create_new_board_button)
+ set_name(board_name)
+ end
+
+ def has_modal_board_name_field?
+ has_element?(:board_name_field, wait: 1)
+ end
+
+ def set_name(name)
+ find_element(:board_name_field).set(name)
+ click_element(:save_changes_button)
+ end
+
+ private
+
+ def wait_boards_list_finish_loading
+ within_element(:boards_list) do
+ wait_until(reload: false, max_duration: 5, sleep_interval: 1) do
+ finished_loading? && (block_given? ? yield : true)
+ end
+ end
+ end
+ end
+ end
+ end
+ end
+end
+
+QA::Page::Component::IssueBoard::Show.prepend_if_ee('QA::EE::Page::Component::IssueBoard::Show')
diff --git a/qa/qa/page/project/wiki/edit.rb b/qa/qa/page/project/wiki/edit.rb
index f6edc28c41a..5d39a084d85 100644
--- a/qa/qa/page/project/wiki/edit.rb
+++ b/qa/qa/page/project/wiki/edit.rb
@@ -5,14 +5,32 @@ module QA
module Project
module Wiki
class Edit < Page::Base
- view 'app/views/projects/wikis/_main_links.html.haml' do
- element :new_page_link, 'New page' # rubocop:disable QA/ElementWithPattern
- element :page_history_link, 'Page history' # rubocop:disable QA/ElementWithPattern
- element :edit_page_link, 'Edit' # rubocop:disable QA/ElementWithPattern
+ view 'app/views/projects/wikis/_form.html.haml' do
+ element :wiki_title_textbox
+ element :wiki_content_textarea
+ element :wiki_message_textbox
+ element :save_changes_button
+ element :create_page_button
end
- def click_edit
- click_on 'Edit'
+ def set_title(title)
+ fill_element :wiki_title_textbox, title
+ end
+
+ def set_content(content)
+ fill_element :wiki_content_textarea, content
+ end
+
+ def set_message(message)
+ fill_element :wiki_message_textbox, message
+ end
+
+ def click_save_changes
+ click_element :save_changes_button
+ end
+
+ def click_create_page
+ click_element :create_page_button
end
end
end
diff --git a/qa/qa/page/project/wiki/new.rb b/qa/qa/page/project/wiki/new.rb
deleted file mode 100644
index 792eba4bab7..00000000000
--- a/qa/qa/page/project/wiki/new.rb
+++ /dev/null
@@ -1,61 +0,0 @@
-# frozen_string_literal: true
-
-module QA
- module Page
- module Project
- module Wiki
- class New < Page::Base
- include Component::LazyLoader
-
- view 'app/views/projects/wikis/_form.html.haml' do
- element :wiki_title_textbox
- element :wiki_content_textarea
- element :wiki_message_textbox
- element :save_changes_button
- element :create_page_button
- end
-
- view 'app/views/shared/empty_states/_wikis.html.haml' do
- element :create_first_page_link
- end
-
- view 'app/views/shared/empty_states/_wikis_layout.html.haml' do
- element :svg_content
- end
-
- def click_create_your_first_page_button
- # The svg takes a fraction of a second to load after which the
- # "Create your first page" button shifts up a bit. This can cause
- # webdriver to miss the hit so we wait for the svg to load before
- # clicking the button.
- within_element(:svg_content) do
- has_element? :js_lazy_loaded
- end
-
- click_element :create_first_page_link
- end
-
- def set_title(title)
- fill_element :wiki_title_textbox, title
- end
-
- def set_content(content)
- fill_element :wiki_content_textarea, content
- end
-
- def set_message(message)
- fill_element :wiki_message_textbox, message
- end
-
- def save_changes
- click_element :save_changes_button
- end
-
- def create_new_page
- click_element :create_page_button
- end
- end
- end
- end
- end
-end
diff --git a/qa/qa/page/project/wiki/show.rb b/qa/qa/page/project/wiki/show.rb
index 44619d177b1..fef839f9119 100644
--- a/qa/qa/page/project/wiki/show.rb
+++ b/qa/qa/page/project/wiki/show.rb
@@ -5,23 +5,70 @@ module QA
module Project
module Wiki
class Show < Page::Base
- include Page::Component::LegacyClonePanel
+ include Component::LazyLoader
- view 'app/views/projects/wikis/pages.html.haml' do
- element :clone_repository_link, 'Clone repository' # rubocop:disable QA/ElementWithPattern
+ view 'app/views/projects/wikis/_sidebar.html.haml' do
+ element :clone_repository_link
end
view 'app/views/projects/wikis/show.html.haml' do
+ element :wiki_page_title
element :wiki_page_content
end
+ view 'app/views/projects/wikis/_main_links.html.haml' do
+ element :new_page_button
+ element :page_history_button
+ element :edit_page_button
+ end
+
+ view 'app/views/shared/empty_states/_wikis.html.haml' do
+ element :create_first_page_link
+ end
+
+ view 'app/views/shared/empty_states/_wikis_layout.html.haml' do
+ element :svg_content
+ end
+
+ def click_create_your_first_page
+ # The svg takes a fraction of a second to load after which the
+ # "Create your first page" button shifts up a bit. This can cause
+ # webdriver to miss the hit so we wait for the svg to load before
+ # clicking the button.
+ within_element(:svg_content) do
+ has_element? :js_lazy_loaded
+ end
+
+ click_element :create_first_page_link
+ end
+
+ def click_new_page
+ click_element(:new_page_button)
+ end
+
+ def click_page_history
+ click_element(:page_history_button)
+ end
+
+ def click_edit
+ click_element(:edit_page_button)
+ end
+
def click_clone_repository
- click_on 'Clone repository'
+ click_element(:clone_repository_link)
end
def wiki_text
find_element(:wiki_page_content).text
end
+
+ def has_title?(title)
+ has_element?(:wiki_page_title, title)
+ end
+
+ def has_content?(content)
+ has_element?(:wiki_page_content, content)
+ end
end
end
end
diff --git a/qa/qa/resource/project.rb b/qa/qa/resource/project.rb
index 8052d55c769..2e7ee549ab4 100644
--- a/qa/qa/resource/project.rb
+++ b/qa/qa/resource/project.rb
@@ -56,6 +56,8 @@ module QA
@auto_devops_enabled = false
@visibility = :public
@template_name = nil
+
+ self.name = "the_awesome_project"
end
def name=(raw_name)
diff --git a/qa/qa/resource/repository/wiki_push.rb b/qa/qa/resource/repository/wiki_push.rb
index e926c00d380..a3df5edcbf2 100644
--- a/qa/qa/resource/repository/wiki_push.rb
+++ b/qa/qa/resource/repository/wiki_push.rb
@@ -5,17 +5,17 @@ module QA
module Repository
class WikiPush < Repository::Push
attribute :wiki do
- Wiki.fabricate! do |resource|
+ # We are using the project based wiki as a standard.
+ Wiki::ProjectPage.fabricate_via_api! do |resource|
resource.title = 'Home'
resource.content = '# My First Wiki Content'
- resource.message = 'Update home'
end
end
def initialize
@file_name = 'Home.md'
- @file_content = '# Welcome to My Wiki'
- @commit_message = 'Updating Home Page'
+ @file_content = 'This line was created using git push'
+ @commit_message = 'Updating using git push'
@branch_name = 'master'
@new_branch = false
end
@@ -28,9 +28,10 @@ module QA
@repository_ssh_uri ||= wiki.repository_ssh_location.uri
end
- def fabricate!
- super
- wiki.visit!
+ def web_url
+ # TODO
+ # workaround
+ repository_http_uri.to_s.gsub(".wiki.git", "/-/wikis/#{@file_name.gsub('.md', '')}")
end
end
end
diff --git a/qa/qa/resource/wiki.rb b/qa/qa/resource/wiki.rb
deleted file mode 100644
index 45d5da9346d..00000000000
--- a/qa/qa/resource/wiki.rb
+++ /dev/null
@@ -1,48 +0,0 @@
-# frozen_string_literal: true
-
-module QA
- module Resource
- class Wiki < Base
- attr_accessor :title, :content, :message
-
- attribute :project do
- Project.fabricate! do |resource|
- resource.name = 'project-for-wikis'
- resource.description = 'project for adding wikis'
- end
- end
-
- attribute :repository_http_location do
- Page::Project::Wiki::Show.perform(&:click_clone_repository)
-
- Page::Project::Wiki::GitAccess.perform do |git_access|
- git_access.choose_repository_clone_http
- git_access.repository_location
- end
- end
-
- attribute :repository_ssh_location do
- Page::Project::Wiki::Show.perform(&:click_clone_repository)
-
- Page::Project::Wiki::GitAccess.perform do |git_access|
- git_access.choose_repository_clone_ssh
- git_access.repository_location
- end
- end
-
- def fabricate!
- project.visit!
-
- Page::Project::Menu.perform { |menu_side| menu_side.click_wiki }
-
- Page::Project::Wiki::New.perform do |wiki_new|
- wiki_new.click_create_your_first_page_button
- wiki_new.set_title(@title)
- wiki_new.set_content(@content)
- wiki_new.set_message(@message)
- wiki_new.create_new_page
- end
- end
- end
- end
-end
diff --git a/qa/qa/resource/wiki/project_page.rb b/qa/qa/resource/wiki/project_page.rb
new file mode 100644
index 00000000000..df3667ffbff
--- /dev/null
+++ b/qa/qa/resource/wiki/project_page.rb
@@ -0,0 +1,66 @@
+# frozen_string_literal: true
+
+module QA
+ module Resource
+ module Wiki
+ class ProjectPage < Base
+ attribute :title
+ attribute :content
+ attribute :slug
+ attribute :format
+
+ attribute :project do
+ Project.fabricate_via_api! do |project|
+ project.name = 'wiki_testing'
+ project.description = 'project for testing wikis'
+ end
+ end
+
+ attribute :repository_http_location do
+ switching_to_wiki_url project.repository_http_location.git_uri
+ end
+
+ attribute :repository_ssh_location do
+ switching_to_wiki_url project.repository_ssh_location.git_uri
+ end
+
+ def initialize
+ @title = 'Home'
+ @content = 'This wiki page is created by the API'
+ end
+
+ def resource_web_url(resource)
+ super
+ rescue ResourceURLMissingError
+ # TODO
+ # workaround
+ project.web_url.concat("/-/wikis/#{slug}")
+ end
+
+ def api_get_path
+ "/projects/#{project.id}/wikis/#{slug}"
+ end
+
+ def api_post_path
+ "/projects/#{project.id}/wikis"
+ end
+
+ def api_post_body
+ {
+ id: project.id,
+ content: content,
+ title: title
+ }
+ end
+
+ private
+
+ def switching_to_wiki_url(url)
+ # TODO
+ # workaround
+ Git::Location.new(url.to_s.gsub('.git', '.wiki.git'))
+ end
+ end
+ end
+ end
+end
diff --git a/qa/qa/specs/features/browser_ui/2_plan/issue_boards/focus_mode_spec.rb b/qa/qa/specs/features/browser_ui/2_plan/issue_boards/focus_mode_spec.rb
index 409c7d321f0..c81a6f9281c 100644
--- a/qa/qa/specs/features/browser_ui/2_plan/issue_boards/focus_mode_spec.rb
+++ b/qa/qa/specs/features/browser_ui/2_plan/issue_boards/focus_mode_spec.rb
@@ -17,7 +17,7 @@ module QA
project.visit!
Page::Project::Menu.perform(&:go_to_boards)
- EE::Page::Component::IssueBoard::Show.perform do |show|
+ Page::Component::IssueBoard::Show.perform do |show|
show.click_focus_mode_button
expect(show.focused_board).to be_visible
diff --git a/qa/qa/specs/features/browser_ui/3_create/wiki/create_edit_clone_push_wiki_spec.rb b/qa/qa/specs/features/browser_ui/3_create/wiki/create_edit_clone_push_wiki_spec.rb
deleted file mode 100644
index 185d10a64ed..00000000000
--- a/qa/qa/specs/features/browser_ui/3_create/wiki/create_edit_clone_push_wiki_spec.rb
+++ /dev/null
@@ -1,42 +0,0 @@
-# frozen_string_literal: true
-
-module QA
- context 'Create' do
- describe 'Wiki management' do
- it 'user creates, edits, clones, and pushes to the wiki' do
- Flow::Login.sign_in
-
- wiki = Resource::Wiki.fabricate_via_browser_ui! do |resource|
- resource.title = 'Home'
- resource.content = '# My First Wiki Content'
- resource.message = 'Update home'
- end
-
- validate_content('My First Wiki Content')
-
- Page::Project::Wiki::Edit.perform(&:click_edit)
- Page::Project::Wiki::New.perform do |wiki|
- wiki.set_content("My Second Wiki Content")
- wiki.save_changes
- end
-
- validate_content('My Second Wiki Content')
-
- Resource::Repository::WikiPush.fabricate! do |push|
- push.wiki = wiki
- push.file_name = 'Home.md'
- push.file_content = '# My Third Wiki Content'
- push.commit_message = 'Update Home.md'
- end
- Page::Project::Menu.perform(&:click_wiki)
-
- expect(page).to have_content('My Third Wiki Content')
- end
-
- def validate_content(content)
- expect(page).to have_content('Wiki was successfully updated')
- expect(page).to have_content(/#{content}/)
- end
- end
- end
-end
diff --git a/qa/qa/specs/features/browser_ui/3_create/wiki/project_based_content_creation_spec.rb b/qa/qa/specs/features/browser_ui/3_create/wiki/project_based_content_creation_spec.rb
new file mode 100644
index 00000000000..1d0c8ee60d4
--- /dev/null
+++ b/qa/qa/specs/features/browser_ui/3_create/wiki/project_based_content_creation_spec.rb
@@ -0,0 +1,93 @@
+# frozen_string_literal: true
+
+module QA
+ context 'Create' do
+ context 'Wiki' do
+ describe 'testing wiki content creation inside a project' do
+ let(:new_wiki_title) { "just_another_wiki_page" }
+ let(:new_wiki_content) { "this content is changed or added" }
+ let(:commit_message) { "this is a new addition to the wiki" }
+
+ let(:project) { Resource::Project.fabricate_via_api! }
+ let(:wiki) { Resource::Wiki::ProjectPage.fabricate_via_api! }
+
+ before do
+ Flow::Login.sign_in
+ end
+
+ it 'by adding a home page to the wiki' do
+ project.visit!
+
+ Page::Project::Menu.perform(&:click_wiki)
+ Page::Project::Wiki::Show.perform(&:click_create_your_first_page)
+
+ Page::Project::Wiki::Edit.perform do |edit|
+ edit.set_title new_wiki_title
+ edit.set_content new_wiki_content
+ edit.set_message commit_message
+ end
+
+ Page::Project::Wiki::Edit.perform(&:click_create_page)
+
+ Page::Project::Wiki::Show.perform do |wiki|
+ expect(wiki).to have_title new_wiki_title
+ expect(wiki).to have_content new_wiki_content
+ end
+ end
+
+ it 'by adding a second page to the wiki' do
+ wiki.visit!
+
+ Page::Project::Wiki::Show.perform(&:click_new_page)
+
+ Page::Project::Wiki::Edit.perform do |edit|
+ edit.set_title new_wiki_title
+ edit.set_content new_wiki_content
+ edit.set_message commit_message
+ end
+
+ Page::Project::Wiki::Edit.perform(&:click_create_page)
+
+ Page::Project::Wiki::Show.perform do |wiki|
+ expect(wiki).to have_title new_wiki_title
+ expect(wiki).to have_content new_wiki_content
+ end
+ end
+
+ it 'by adding a home page to the wiki using git push' do
+ empty_wiki = Resource::Wiki::ProjectPage.new do |empty_wiki|
+ empty_wiki.project = project
+ end
+
+ Resource::Repository::WikiPush.fabricate! do |push|
+ push.file_name = "#{new_wiki_title}.md"
+ push.file_content = new_wiki_content
+ push.commit_message = commit_message
+ push.wiki = empty_wiki
+ push.new_branch = true
+ end.visit!
+
+ Page::Project::Wiki::Show.perform do |wiki|
+ expect(wiki).to have_title new_wiki_title
+ expect(wiki).to have_content new_wiki_content
+ end
+ end
+
+ it 'by adding a second page to the wiki using git push' do
+ Resource::Repository::WikiPush.fabricate! do |push|
+ push.file_name = "#{new_wiki_title}.md"
+ push.file_content = new_wiki_content
+ push.commit_message = commit_message
+ push.wiki = wiki
+ push.new_branch = false
+ end.visit!
+
+ Page::Project::Wiki::Show.perform do |wiki|
+ expect(wiki).to have_title new_wiki_title
+ expect(wiki).to have_content new_wiki_content
+ end
+ end
+ end
+ end
+ end
+end
diff --git a/qa/qa/specs/features/browser_ui/3_create/wiki/project_based_content_manipulation_spec.rb b/qa/qa/specs/features/browser_ui/3_create/wiki/project_based_content_manipulation_spec.rb
new file mode 100644
index 00000000000..10370c80476
--- /dev/null
+++ b/qa/qa/specs/features/browser_ui/3_create/wiki/project_based_content_manipulation_spec.rb
@@ -0,0 +1,51 @@
+# frozen_string_literal: true
+
+module QA
+ context 'Create' do
+ context 'Wiki' do
+ describe 'testing wiki content manipulation inside a project' do
+ let(:new_wiki_title) { "just_another_wiki_page" }
+ let(:new_wiki_content) { "this content is changed or added" }
+ let(:commit_message) { "this is a new addition to the wiki" }
+
+ let(:wiki) { Resource::Wiki::ProjectPage.fabricate_via_api! }
+
+ before do
+ Flow::Login.sign_in
+ end
+
+ it 'by manipulating content on the page' do
+ wiki.visit!
+
+ Page::Project::Wiki::Show.perform(&:click_edit)
+
+ Page::Project::Wiki::Edit.perform do |edit|
+ edit.set_title new_wiki_title
+ edit.set_content new_wiki_content
+ edit.set_message commit_message
+ end
+
+ Page::Project::Wiki::Edit.perform(&:click_save_changes)
+
+ Page::Project::Wiki::Show.perform do |wiki|
+ expect(wiki).to have_title new_wiki_title
+ expect(wiki).to have_content new_wiki_content
+ end
+ end
+
+ it 'by manipulating content on the page using git push' do
+ Resource::Repository::WikiPush.fabricate! do |push|
+ push.file_content = new_wiki_content
+ push.commit_message = commit_message
+ push.wiki = wiki
+ push.new_branch = false
+ end.visit!
+
+ Page::Project::Wiki::Show.perform do |wiki|
+ expect(wiki).to have_content new_wiki_content
+ end
+ end
+ end
+ end
+ end
+end
diff --git a/spec/factories/project_group_links.rb b/spec/factories/project_group_links.rb
index b9119a5788b..5e3e83f18c1 100644
--- a/spec/factories/project_group_links.rb
+++ b/spec/factories/project_group_links.rb
@@ -5,10 +5,15 @@ FactoryBot.define do
project
group
expires_at { nil }
+ group_access { Gitlab::Access::DEVELOPER }
trait(:guest) { group_access { Gitlab::Access::GUEST } }
trait(:reporter) { group_access { Gitlab::Access::REPORTER } }
trait(:developer) { group_access { Gitlab::Access::DEVELOPER } }
trait(:maintainer) { group_access { Gitlab::Access::MAINTAINER } }
+
+ after(:create) do |project_group_link, evaluator|
+ project_group_link.group.refresh_members_authorized_projects
+ end
end
end
diff --git a/spec/models/design_management/design_spec.rb b/spec/models/design_management/design_spec.rb
index fa8215dbc78..bc1f54f057e 100644
--- a/spec/models/design_management/design_spec.rb
+++ b/spec/models/design_management/design_spec.rb
@@ -27,6 +27,7 @@ describe DesignManagement::Design do
it { is_expected.to validate_presence_of(:project) }
it { is_expected.to validate_presence_of(:issue) }
it { is_expected.to validate_presence_of(:filename) }
+ it { is_expected.to validate_length_of(:filename).is_at_most(255) }
it { is_expected.to validate_uniqueness_of(:filename).scoped_to(:issue_id) }
it "validates that the extension is an image" do
diff --git a/spec/models/project_group_link_spec.rb b/spec/models/project_group_link_spec.rb
index 41fb956b4a5..8ef29e8a876 100644
--- a/spec/models/project_group_link_spec.rb
+++ b/spec/models/project_group_link_spec.rb
@@ -49,22 +49,6 @@ describe ProjectGroupLink do
end
end
- describe "destroying a record", :delete do
- it "refreshes group users' authorized projects" do
- project = create(:project, :private)
- group = create(:group)
- reporter = create(:user)
- group_users = group.users
-
- group.add_reporter(reporter)
- project.project_group_links.create(group: group)
- group_users.each { |user| expect(user.authorized_projects).to include(project) }
-
- project.project_group_links.destroy_all # rubocop: disable Cop/DestroyAll
- group_users.each { |user| expect(user.authorized_projects).not_to include(project) }
- end
- end
-
describe 'search by group name' do
let_it_be(:project_group_link) { create(:project_group_link) }
let_it_be(:group) { project_group_link.group }
diff --git a/spec/models/project_team_spec.rb b/spec/models/project_team_spec.rb
index d62fa58739a..24652a1d706 100644
--- a/spec/models/project_team_spec.rb
+++ b/spec/models/project_team_spec.rb
@@ -96,11 +96,9 @@ describe ProjectTeam do
it 'returns invited members of a group' do
group_member = create(:group_member)
-
- project.project_group_links.create!(
- group: group_member.group,
- group_access: Gitlab::Access::GUEST
- )
+ create(:project_group_link, group: group_member.group,
+ project: project,
+ group_access: Gitlab::Access::GUEST)
expect(project.team.members)
.to contain_exactly(group_member.user, project.owner)
@@ -108,11 +106,9 @@ describe ProjectTeam do
it 'returns invited members of a group of a specified level' do
group_member = create(:group_member)
-
- project.project_group_links.create!(
- group: group_member.group,
- group_access: Gitlab::Access::REPORTER
- )
+ create(:project_group_link, group: group_member.group,
+ project: project,
+ group_access: Gitlab::Access::REPORTER)
expect(project.team.guests).to be_empty
expect(project.team.reporters).to contain_exactly(group_member.user)
diff --git a/spec/models/user_spec.rb b/spec/models/user_spec.rb
index cb78e1015ea..a8936be8118 100644
--- a/spec/models/user_spec.rb
+++ b/spec/models/user_spec.rb
@@ -2845,10 +2845,10 @@ describe User do
it "includes projects shared with user's group" do
user = create(:user)
project = create(:project, :private)
- group = create(:group)
-
- group.add_reporter(user)
- project.project_group_links.create(group: group)
+ group = create(:group) do |group|
+ group.add_reporter(user)
+ end
+ create(:project_group_link, group: group, project: project)
expect(user.authorized_projects).to include(project)
end
diff --git a/spec/requests/api/projects_spec.rb b/spec/requests/api/projects_spec.rb
index 39847451439..8a1c68916e4 100644
--- a/spec/requests/api/projects_spec.rb
+++ b/spec/requests/api/projects_spec.rb
@@ -2055,10 +2055,12 @@ describe API::Projects do
end
describe "POST /projects/:id/share" do
- let(:group) { create(:group) }
+ let_it_be(:group) { create(:group, :private) }
+ let_it_be(:group_user) { create(:user) }
before do
group.add_developer(user)
+ group.add_developer(group_user)
end
it "shares project with group" do
@@ -2074,6 +2076,14 @@ describe API::Projects do
expect(json_response['expires_at']).to eq(expires_at.to_s)
end
+ it 'updates project authorization' do
+ expect do
+ post api("/projects/#{project.id}/share", user), params: { group_id: group.id, group_access: Gitlab::Access::DEVELOPER }
+ end.to(
+ change { group_user.can?(:read_project, project) }.from(false).to(true)
+ )
+ end
+
it "returns a 400 error when group id is not given" do
post api("/projects/#{project.id}/share", user), params: { group_access: Gitlab::Access::DEVELOPER }
expect(response).to have_gitlab_http_status(:bad_request)
@@ -2123,9 +2133,12 @@ describe API::Projects do
describe 'DELETE /projects/:id/share/:group_id' do
context 'for a valid group' do
- let(:group) { create(:group, :public) }
+ let_it_be(:group) { create(:group, :private) }
+ let_it_be(:group_user) { create(:user) }
before do
+ group.add_developer(group_user)
+
create(:project_group_link, group: group, project: project)
end
@@ -2136,6 +2149,14 @@ describe API::Projects do
expect(project.project_group_links).to be_empty
end
+ it 'updates project authorization' do
+ expect do
+ delete api("/projects/#{project.id}/share/#{group.id}", user)
+ end.to(
+ change { group_user.can?(:read_project, project) }.from(true).to(false)
+ )
+ end
+
it_behaves_like '412 response' do
let(:request) { api("/projects/#{project.id}/share/#{group.id}", user) }
end
diff --git a/spec/services/projects/group_links/create_service_spec.rb b/spec/services/projects/group_links/create_service_spec.rb
index 92667184be8..22f7c8bdcb4 100644
--- a/spec/services/projects/group_links/create_service_spec.rb
+++ b/spec/services/projects/group_links/create_service_spec.rb
@@ -3,16 +3,17 @@
require 'spec_helper'
describe Projects::GroupLinks::CreateService, '#execute' do
- let(:user) { create :user }
- let(:group) { create :group }
- let(:project) { create :project }
+ let_it_be(:user) { create :user }
+ let_it_be(:group) { create :group }
+ let_it_be(:project) { create :project }
let(:opts) do
{
link_group_access: '30',
expires_at: nil
}
end
- let(:subject) { described_class.new(project, user, opts) }
+
+ subject { described_class.new(project, user, opts) }
before do
group.add_developer(user)
@@ -22,6 +23,12 @@ describe Projects::GroupLinks::CreateService, '#execute' do
expect { subject.execute(group) }.to change { project.project_group_links.count }.from(0).to(1)
end
+ it 'updates authorization' do
+ expect { subject.execute(group) }.to(
+ change { Ability.allowed?(user, :read_project, project) }
+ .from(false).to(true))
+ end
+
it 'returns false if group is blank' do
expect { subject.execute(nil) }.not_to change { project.project_group_links.count }
end
diff --git a/spec/services/projects/group_links/destroy_service_spec.rb b/spec/services/projects/group_links/destroy_service_spec.rb
index 0fd1fcfe1a5..0a8c9580e70 100644
--- a/spec/services/projects/group_links/destroy_service_spec.rb
+++ b/spec/services/projects/group_links/destroy_service_spec.rb
@@ -3,15 +3,25 @@
require 'spec_helper'
describe Projects::GroupLinks::DestroyService, '#execute' do
- let(:project) { create(:project, :private) }
- let!(:group_link) { create(:project_group_link, project: project) }
- let(:user) { create :user }
- let(:subject) { described_class.new(project, user) }
+ let_it_be(:user) { create :user }
+ let_it_be(:project) { create(:project, :private) }
+ let_it_be(:group) { create(:group) }
+ let!(:group_link) { create(:project_group_link, project: project, group: group) }
+
+ subject { described_class.new(project, user) }
it 'removes group from project' do
expect { subject.execute(group_link) }.to change { project.project_group_links.count }.from(1).to(0)
end
+ it 'updates authorization' do
+ group.add_maintainer(user)
+
+ expect { subject.execute(group_link) }.to(
+ change { Ability.allowed?(user, :read_project, project) }
+ .from(true).to(false))
+ end
+
it 'returns false if group_link is blank' do
expect { subject.execute(nil) }.not_to change { project.project_group_links.count }
end
diff --git a/spec/services/projects/group_links/update_service_spec.rb b/spec/services/projects/group_links/update_service_spec.rb
new file mode 100644
index 00000000000..5be2ae1e0f7
--- /dev/null
+++ b/spec/services/projects/group_links/update_service_spec.rb
@@ -0,0 +1,56 @@
+# frozen_string_literal: true
+
+require 'spec_helper'
+
+describe Projects::GroupLinks::UpdateService, '#execute' do
+ let_it_be(:user) { create :user }
+ let_it_be(:group) { create :group }
+ let_it_be(:project) { create :project }
+ let!(:link) { create(:project_group_link, project: project, group: group) }
+
+ let(:expiry_date) { 1.month.from_now.to_date }
+ let(:group_link_params) do
+ { group_access: Gitlab::Access::GUEST,
+ expires_at: expiry_date }
+ end
+
+ subject { described_class.new(link).execute(group_link_params) }
+
+ before do
+ group.add_developer(user)
+ end
+
+ it 'updates existing link' do
+ expect(link.group_access).to eq(Gitlab::Access::DEVELOPER)
+ expect(link.expires_at).to be_nil
+
+ subject
+
+ link.reload
+
+ expect(link.group_access).to eq(Gitlab::Access::GUEST)
+ expect(link.expires_at).to eq(expiry_date)
+ end
+
+ it 'updates project permissions' do
+ expect { subject }.to change { user.can?(:create_release, project) }.from(true).to(false)
+ end
+
+ it 'executes UserProjectAccessChangedService' do
+ expect_next_instance_of(UserProjectAccessChangedService) do |service|
+ expect(service).to receive(:execute)
+ end
+
+ subject
+ end
+
+ context 'with only param not requiring authorization refresh' do
+ let(:group_link_params) { { expires_at: Date.tomorrow } }
+
+ it 'does not execute UserProjectAccessChangedService' do
+ expect(UserProjectAccessChangedService).not_to receive(:new)
+
+ subject
+ end
+ end
+end
diff --git a/spec/workers/metrics/dashboard/prune_old_annotations_worker_spec.rb b/spec/workers/metrics/dashboard/prune_old_annotations_worker_spec.rb
new file mode 100644
index 00000000000..bab5a5d8740
--- /dev/null
+++ b/spec/workers/metrics/dashboard/prune_old_annotations_worker_spec.rb
@@ -0,0 +1,40 @@
+# frozen_string_literal: true
+
+require 'spec_helper'
+
+describe Metrics::Dashboard::PruneOldAnnotationsWorker do
+ let_it_be(:now) { DateTime.parse('2020-06-02T00:12:00Z') }
+ let_it_be(:two_weeks_old_annotation) { create(:metrics_dashboard_annotation, starting_at: now.advance(weeks: -2)) }
+ let_it_be(:one_day_old_annotation) { create(:metrics_dashboard_annotation, starting_at: now.advance(days: -1)) }
+ let_it_be(:month_old_annotation) { create(:metrics_dashboard_annotation, starting_at: now.advance(months: -1)) }
+
+ describe '#perform' do
+ it 'removes all annotations older than cut off', :aggregate_failures do
+ Timecop.freeze(now) do
+ described_class.new.perform
+
+ expect(Metrics::Dashboard::Annotation.all).to match_array([one_day_old_annotation, two_weeks_old_annotation])
+
+ # is idempotent in the scope of 24h
+ expect { described_class.new.perform }.not_to change { Metrics::Dashboard::Annotation.all.to_a }
+ Timecop.travel(24.hours.from_now) do
+ described_class.new.perform
+ expect(Metrics::Dashboard::Annotation.all).to match_array([one_day_old_annotation])
+ end
+ end
+ end
+
+ context 'batch to be deleted is bigger than upper limit' do
+ it 'schedules second job to clear remaining records' do
+ Timecop.freeze(now) do
+ create(:metrics_dashboard_annotation, starting_at: 1.month.ago)
+ stub_const("#{described_class}::DELETE_LIMIT", 1)
+
+ expect(described_class).to receive(:perform_async)
+
+ described_class.new.perform
+ end
+ end
+ end
+ end
+end
diff --git a/spec/workers/metrics/dashboard/schedule_annotations_prune_worker_spec.rb b/spec/workers/metrics/dashboard/schedule_annotations_prune_worker_spec.rb
new file mode 100644
index 00000000000..bfe6fe3a90e
--- /dev/null
+++ b/spec/workers/metrics/dashboard/schedule_annotations_prune_worker_spec.rb
@@ -0,0 +1,13 @@
+# frozen_string_literal: true
+
+require 'spec_helper'
+
+describe Metrics::Dashboard::ScheduleAnnotationsPruneWorker do
+ describe '#perform' do
+ it 'schedules annotations prune job with default cut off date' do
+ expect(Metrics::Dashboard::PruneOldAnnotationsWorker).to receive(:perform_async)
+
+ described_class.new.perform
+ end
+ end
+end
diff --git a/spec/workers/remove_expired_group_links_worker_spec.rb b/spec/workers/remove_expired_group_links_worker_spec.rb
index 9557aa3086c..b637802cd78 100644
--- a/spec/workers/remove_expired_group_links_worker_spec.rb
+++ b/spec/workers/remove_expired_group_links_worker_spec.rb
@@ -23,30 +23,53 @@ describe RemoveExpiredGroupLinksWorker do
subject.perform
expect(non_expiring_project_group_link.reload).to be_present
end
+
+ it 'removes project authorization' do
+ user = create(:user)
+
+ project = expired_project_group_link.project
+ group = expired_project_group_link.group
+
+ group.add_maintainer(user)
+
+ expect { subject.perform }.to(
+ change { user.can?(:read_project, project) }.from(true).to(false))
+ end
end
context 'GroupGroupLinks' do
- let(:mock_destroy_service) { instance_double(Groups::GroupLinks::DestroyService) }
-
- before do
- allow(Groups::GroupLinks::DestroyService).to(
- receive(:new).and_return(mock_destroy_service))
- end
-
context 'expired GroupGroupLink exists' do
- before do
- create(:group_group_link, expires_at: 1.hour.ago)
- end
+ let!(:group_group_link) { create(:group_group_link, expires_at: 1.hour.ago) }
it 'calls Groups::GroupLinks::DestroyService' do
+ mock_destroy_service = instance_double(Groups::GroupLinks::DestroyService)
+ allow(Groups::GroupLinks::DestroyService).to(
+ receive(:new).and_return(mock_destroy_service))
+
expect(mock_destroy_service).to receive(:execute).once
subject.perform
end
+
+ it 'removes project authorization' do
+ shared_group = group_group_link.shared_group
+ shared_with_group = group_group_link.shared_with_group
+ project = create(:project, group: shared_group)
+
+ user = create(:user)
+ shared_with_group.add_maintainer(user)
+
+ expect { subject.perform }.to(
+ change { user.can?(:read_project, project) }.from(true).to(false))
+ end
end
context 'expired GroupGroupLink does not exist' do
it 'does not call Groups::GroupLinks::DestroyService' do
+ mock_destroy_service = instance_double(Groups::GroupLinks::DestroyService)
+ allow(Groups::GroupLinks::DestroyService).to(
+ receive(:new).and_return(mock_destroy_service))
+
expect(mock_destroy_service).not_to receive(:execute)
subject.perform