-
+
+
+
+ {{ $options.returnToSiteBtnText }}
+
+ {{ updatedFileDescription }}
+
+
+
+
+
+ {{ $options.mergeRequestInstructionsHeading }}
+ {{ $options.addTitleInstruction }}
+ {{ $options.addDescriptionInstruction }}
+ {{ $options.assignMergeRequestInstruction }}
+
+
diff --git a/app/assets/javascripts/vue_shared/components/file_icon/file_icon_map.js b/app/assets/javascripts/vue_shared/components/file_icon/file_icon_map.js
index 9ecae87c1a9..b70f093e930 100644
--- a/app/assets/javascripts/vue_shared/components/file_icon/file_icon_map.js
+++ b/app/assets/javascripts/vue_shared/components/file_icon/file_icon_map.js
@@ -586,5 +586,16 @@ const fileNameIcons = {
};
export default function getIconForFile(name) {
- return fileNameIcons[name] || fileExtensionIcons[name ? name.split('.').pop() : ''] || '';
+ return (
+ fileNameIcons[name] ||
+ fileExtensionIcons[
+ name
+ ? name
+ .split('.')
+ .pop()
+ .toLowerCase()
+ : ''
+ ] ||
+ ''
+ );
}
diff --git a/app/views/projects/ci/builds/_build.html.haml b/app/views/projects/ci/builds/_build.html.haml
index 250abd7fc08..23f9a6a8f6c 100644
--- a/app/views/projects/ci/builds/_build.html.haml
+++ b/app/views/projects/ci/builds/_build.html.haml
@@ -97,7 +97,7 @@
%td
.float-right
- if can?(current_user, :read_build, job) && job.artifacts?
- = link_to download_project_job_artifacts_path(job.project, job), rel: 'nofollow', download: '', title: _('Download artifacts'), class: 'btn btn-build' do
+ = link_to download_project_job_artifacts_path(job.project, job), rel: 'nofollow', download: '', title: _('Download artifacts'), class: 'btn btn-build gl-button btn-icon btn-svg' do
= sprite_icon('download')
- if can?(current_user, :update_build, job)
- if job.active?
diff --git a/app/views/projects/issues/_discussion.html.haml b/app/views/projects/issues/_discussion.html.haml
index bcc74e8d1d9..4273130bbc2 100644
--- a/app/views/projects/issues/_discussion.html.haml
+++ b/app/views/projects/issues/_discussion.html.haml
@@ -1,3 +1,5 @@
+- add_page_startup_api_call discussions_path(@issue)
+
- @gfm_form = true
- content_for :note_actions do
diff --git a/app/views/projects/issues/show.html.haml b/app/views/projects/issues/show.html.haml
index 2a0dc5e30b9..a52f4d151c9 100644
--- a/app/views/projects/issues/show.html.haml
+++ b/app/views/projects/issues/show.html.haml
@@ -9,6 +9,7 @@
- can_reopen_issue = can?(current_user, :reopen_issue, @issue)
- can_report_spam = @issue.submittable_as_spam_by?(current_user)
- can_create_issue = show_new_issue_link?(@project)
+- related_branches_path = related_branches_project_issue_path(@project, @issue)
= render_if_exists "projects/issues/alert_blocked", issue: @issue, current_user: current_user
= render "projects/issues/alert_moved_from_service_desk", issue: @issue
@@ -82,7 +83,8 @@
#js-related-merge-requests{ data: { endpoint: expose_path(api_v4_projects_issues_related_merge_requests_path(id: @project.id, issue_iid: @issue.iid)), project_namespace: @project.namespace.path, project_path: @project.path } }
- if can?(current_user, :download_code, @project)
- #related-branches{ data: { url: related_branches_project_issue_path(@project, @issue) } }
+ - add_page_startup_api_call related_branches_path
+ #related-branches{ data: { url: related_branches_path } }
-# This element is filled in using JavaScript.
.content-block.emoji-block.emoji-block-sticky
diff --git a/app/views/projects/static_site_editor/show.html.haml b/app/views/projects/static_site_editor/show.html.haml
index 8d2649be588..2d817912335 100644
--- a/app/views/projects/static_site_editor/show.html.haml
+++ b/app/views/projects/static_site_editor/show.html.haml
@@ -1 +1 @@
-#static-site-editor{ data: @config.payload }
+#static-site-editor{ data: @config.payload.merge({ merge_requests_illustration_path: image_path('illustrations/merge_requests.svg') }) }
diff --git a/changelogs/unreleased/216868-improve-success-screen.yml b/changelogs/unreleased/216868-improve-success-screen.yml
new file mode 100644
index 00000000000..d2aaf21508e
--- /dev/null
+++ b/changelogs/unreleased/216868-improve-success-screen.yml
@@ -0,0 +1,5 @@
+---
+title: Improve the IA and styling of the Success screen in the Static Site Editor
+merge_request: 37475
+author:
+type: changed
diff --git a/changelogs/unreleased/225888-fix-download-icon.yml b/changelogs/unreleased/225888-fix-download-icon.yml
new file mode 100644
index 00000000000..9a4185d601a
--- /dev/null
+++ b/changelogs/unreleased/225888-fix-download-icon.yml
@@ -0,0 +1,5 @@
+---
+title: Fix misalignment of download icon on jobs page
+merge_request: 37966
+author:
+type: other
diff --git a/changelogs/unreleased/230689-remove-feature-flag-for-reference-filter.yml b/changelogs/unreleased/230689-remove-feature-flag-for-reference-filter.yml
new file mode 100644
index 00000000000..9e67c13ceb5
--- /dev/null
+++ b/changelogs/unreleased/230689-remove-feature-flag-for-reference-filter.yml
@@ -0,0 +1,5 @@
+---
+title: Improve performance of Banzai reference filters
+merge_request: 37465
+author:
+type: performance
diff --git a/changelogs/unreleased/231488-file-icons-case-insensitive-extension.yml b/changelogs/unreleased/231488-file-icons-case-insensitive-extension.yml
new file mode 100644
index 00000000000..edda2f791af
--- /dev/null
+++ b/changelogs/unreleased/231488-file-icons-case-insensitive-extension.yml
@@ -0,0 +1,5 @@
+---
+title: Make file icons extension detection be case-insensitive
+merge_request: 37817
+author:
+type: fixed
diff --git a/changelogs/unreleased/doc-iam-role-ambiguity.yml b/changelogs/unreleased/doc-iam-role-ambiguity.yml
new file mode 100644
index 00000000000..754be38f597
--- /dev/null
+++ b/changelogs/unreleased/doc-iam-role-ambiguity.yml
@@ -0,0 +1,5 @@
+---
+title: Adds clarifying documentation on EKS IAM roles
+merge_request: 37870
+author:
+type: added
diff --git a/config/initializers/elastic_client_setup.rb b/config/initializers/elastic_client_setup.rb
index 21745bd81d8..5b8d81265ad 100644
--- a/config/initializers/elastic_client_setup.rb
+++ b/config/initializers/elastic_client_setup.rb
@@ -13,6 +13,7 @@ Gitlab.ee do
Elasticsearch::Model::Adapter::Multiple::Records.prepend GemExtensions::Elasticsearch::Model::Adapter::Multiple::Records
Elasticsearch::Model::Indexing::InstanceMethods.prepend GemExtensions::Elasticsearch::Model::Indexing::InstanceMethods
Elasticsearch::Model::Adapter::ActiveRecord::Importing.prepend GemExtensions::Elasticsearch::Model::Adapter::ActiveRecord::Importing
+ Elasticsearch::Model::Adapter::ActiveRecord::Records.prepend GemExtensions::Elasticsearch::Model::Adapter::ActiveRecord::Records
Elasticsearch::Model::Client::InstanceMethods.prepend GemExtensions::Elasticsearch::Model::Client
Elasticsearch::Model::Client::ClassMethods.prepend GemExtensions::Elasticsearch::Model::Client
Elasticsearch::Model::ClassMethods.prepend GemExtensions::Elasticsearch::Model::Client
diff --git a/doc/ci/pipelines/job_artifacts.md b/doc/ci/pipelines/job_artifacts.md
index c4457d17dc2..a099dc371d2 100644
--- a/doc/ci/pipelines/job_artifacts.md
+++ b/doc/ci/pipelines/job_artifacts.md
@@ -294,7 +294,7 @@ marked as Satisfied.
> - From GitLab 9.2, PDFs, images, videos, and other formats can be previewed directly in the job artifacts browser without the need to download them.
> - Introduced in [GitLab 10.1](https://gitlab.com/gitlab-org/gitlab-foss/-/merge_requests/14399), HTML files in a public project can be previewed directly in a new tab without the need to download them when [GitLab Pages](../../administration/pages/index.md) is enabled. The same applies for textual formats (currently supported extensions: `.txt`, `.json`, and `.log`).
-> - Introduced in [GitLab 12.4](https://gitlab.com/gitlab-org/gitlab/-/merge_requests/16675), artifacts in private projects can be previewed when [GitLab Pages access control](../../administration/pages/index.md#access-control) is enabled.
+> - Introduced in [GitLab 12.4](https://gitlab.com/gitlab-org/gitlab/-/merge_requests/16675), artifacts in internal and private projects can be previewed when [GitLab Pages access control](../../administration/pages/index.md#access-control) is enabled.
After a job finishes, if you visit the job's specific page, there are three
buttons. You can download the artifacts archive or browse its contents, whereas
@@ -311,6 +311,8 @@ Below you can see what browsing looks like. In this case we have browsed inside
the archive and at this point there is one directory, a couple files, and
one HTML file that you can view directly online when
[GitLab Pages](../../administration/pages/index.md) is enabled (opens in a new tab).
+Select artifacts in internal and private projects can only be previewed when
+[GitLab Pages access control](../../administration/pages/index.md#access-control) is enabled.
![Job artifacts browser](img/job_artifacts_browser.png)
diff --git a/doc/ci/quick_start/README.md b/doc/ci/quick_start/README.md
index f6fdd382ee5..8be43d77a0e 100644
--- a/doc/ci/quick_start/README.md
+++ b/doc/ci/quick_start/README.md
@@ -7,24 +7,6 @@ type: reference
# Getting started with GitLab CI/CD
-NOTE: **Note:**
-Starting from version 8.0, GitLab [Continuous Integration](https://about.gitlab.com/stages-devops-lifecycle/continuous-integration/) (CI)
-is fully integrated into GitLab itself and is [enabled](../enable_or_disable_ci.md) by default on all
-projects.
-
-NOTE: **Note:**
-Please keep in mind that only project Maintainers and Admin users have
-the permissions to access a project's settings.
-
-NOTE: **Note:**
-Coming over to GitLab from Jenkins? Check out our [reference](../jenkins/index.md)
-for converting your pre-existing pipelines over to our format.
-
-NOTE: **Note:**
-There are a few different [basic pipeline architectures](../pipelines/pipeline_architectures.md)
-that you can consider for use in your project. You may want to familiarize
-yourself with these prior to getting started.
-
GitLab offers a [continuous integration](https://about.gitlab.com/stages-devops-lifecycle/continuous-integration/) service. For each commit or push to trigger your CI
[pipeline](../pipelines/index.md), you must:
@@ -49,7 +31,11 @@ something.
It's also common to use pipelines to automatically deploy
tested code to staging and production environments.
----
+If you're already familiar with general CI/CD concepts, you can review which
+[pipeline architectures](../pipelines/pipeline_architectures.md) can be used
+in your projects. If you're coming over to GitLab from Jenkins, you can check out
+our [reference](../migration/jenkins.md) for converting your pre-existing pipelines
+over to our format.
This guide assumes that you have:
diff --git a/doc/integration/jenkins.md b/doc/integration/jenkins.md
index ddcfc3b1e9e..3ebb68ebeaa 100644
--- a/doc/integration/jenkins.md
+++ b/doc/integration/jenkins.md
@@ -3,7 +3,7 @@
NOTE: **Note:**
This documentation focuses only on how to **configure** a Jenkins *integration* with
GitLab. Learn how to **migrate** from Jenkins to GitLab CI/CD in our
-[Migrating from Jenkins](../ci/jenkins/index.md) documentation.
+[Migrating from Jenkins](../ci/migration/jenkins.md) documentation.
From GitLab, you can trigger a Jenkins build when you push code to a repository, or when a merge
request is created. In return, Jenkins shows the pipeline status on merge requests widgets and
diff --git a/doc/user/clusters/applications.md b/doc/user/clusters/applications.md
index 90f4782e0be..d2e0d88490d 100644
--- a/doc/user/clusters/applications.md
+++ b/doc/user/clusters/applications.md
@@ -965,6 +965,7 @@ documentation:
- [Google GKE](https://docs.cilium.io/en/stable/gettingstarted/k8s-install-gke/#deploy-cilium)
- [AWS EKS](https://docs.cilium.io/en/stable/gettingstarted/k8s-install-eks/#deploy-cilium)
+- [Azure AKS](https://docs.cilium.io/en/stable/gettingstarted/k8s-install-aks/#deploy-cilium)
You can customize Cilium's Helm variables by defining the
`.gitlab/managed-apps/cilium/values.yaml` file in your cluster
diff --git a/doc/user/project/clusters/add_eks_clusters.md b/doc/user/project/clusters/add_eks_clusters.md
index b11483a7446..f0b6fe81c18 100644
--- a/doc/user/project/clusters/add_eks_clusters.md
+++ b/doc/user/project/clusters/add_eks_clusters.md
@@ -62,6 +62,11 @@ To create and add a new Kubernetes cluster to your project, group, or instance:
1. Click **Add Kubernetes cluster**.
1. Under the **Create new cluster** tab, click **Amazon EKS**. You will be provided with an
`Account ID` and `External ID` to use in the next step.
+1. In the [IAM Management Console](https://console.aws.amazon.com/iam/home), create an EKS management IAM role.
+ To do so, follow the [Amazon EKS cluster IAM role](https://docs.aws.amazon.com/eks/latest/userguide/service_IAM_role.html) instructions
+ to create a IAM role suitable for managing the AWS EKS cluster's resources on your behalf.
+ In addition to the policies that guide suggests, you must also include the `AmazonEKSServicePolicy`
+ policy for this role in order for GitLab to manage the EKS cluster correctly.
1. In the [IAM Management Console](https://console.aws.amazon.com/iam/home), create an IAM role:
1. From the left panel, select **Roles**.
1. Click **Create role**.
@@ -137,9 +142,15 @@ To create and add a new Kubernetes cluster to your project, group, or instance:
- **Kubernetes cluster name** - The name you wish to give the cluster.
- **Environment scope** - The [associated environment](index.md#setting-the-environment-scope-premium) to this cluster.
- **Kubernetes version** - The Kubernetes version to use. Currently the only version supported is 1.14.
- - **Role name** - Select the [IAM role](https://docs.aws.amazon.com/eks/latest/userguide/service_IAM_role.html)
- to allow Amazon EKS and the Kubernetes control plane to manage AWS resources on your behalf. This IAM role is separate
- to the IAM role created above, you will need to create it if it does not yet exist.
+ - **Role name** - Select the **EKS IAM role** you created earlier to allow Amazon EKS
+ and the Kubernetes control plane to manage AWS resources on your behalf.
+
+ NOTE: **Note:**
+ This IAM role is _not_ the IAM role you created in the previous step. It should be
+ the one you created much earlier by following the
+ [Amazon EKS cluster IAM role](https://docs.aws.amazon.com/eks/latest/userguide/service_IAM_role.html)
+ guide.
+
- **Region** - The [region](https://docs.aws.amazon.com/AWSEC2/latest/UserGuide/using-regions-availability-zones.html)
in which the cluster will be created.
- **Key pair name** - Select the [key pair](https://docs.aws.amazon.com/AWSEC2/latest/UserGuide/ec2-key-pairs.html)
@@ -194,10 +205,10 @@ If the `Cluster` resource failed with the error
the role specified in **Role name** is not configured correctly.
NOTE: **Note:**
-This role should not be the same as the one created above. If you don't have an
-existing
-[EKS cluster IAM role](https://docs.aws.amazon.com/eks/latest/userguide/service_IAM_role.html),
-you must create one.
+This role should be the role you created by following the
+[EKS cluster IAM role](https://docs.aws.amazon.com/eks/latest/userguide/service_IAM_role.html) guide.
+In addition to the policies that guide suggests, you must also include the
+`AmazonEKSServicePolicy` policy for this role in order for GitLab to manage the EKS cluster correctly.
## Existing EKS cluster
diff --git a/doc/user/project/clusters/securing.md b/doc/user/project/clusters/securing.md
index 12836d9153b..5b9f776080b 100644
--- a/doc/user/project/clusters/securing.md
+++ b/doc/user/project/clusters/securing.md
@@ -9,7 +9,7 @@ info: To determine the technical writer assigned to the Stage/Group associated w
GitLab makes it easy to secure applications deployed in [connected Kubernetes clusters](index.md).
You can benefit from the protection of a [Web Application Firewall](../../../topics/web_application_firewall/quick_start_guide.md),
[Network Policies](../../../topics/autodevops/stages.md#network-policy),
-or even [Container Host Security](../../clusters/applications.md#install-falco-using-gitlab-cicd).
+and [Container Host Security](../../clusters/applications.md#install-falco-using-gitlab-cicd).
This page contains full end-to-end steps and instructions to connect your cluster to GitLab and
install these features, whether or not your applications are deployed through GitLab CI/CD. If you
@@ -25,7 +25,7 @@ At a high level, the required steps include the following:
- Connect the cluster to GitLab.
- Set up one or more runners.
- Set up a cluster management project.
-- Install a Web Application Firewall, Network Policies, and/or Container Host
+- Install a Web Application Firewall, and/or Network Policies, and/or Container Host
Security.
- Install Prometheus to get statistics and metrics in the
[threat monitoring](../../application_security/threat_monitoring/)
@@ -57,7 +57,7 @@ uses Sidekiq (a background processing service) to facilitate this.
```
Although this installation method is easier because it's a point-and-click action in the user
-interface, it's inflexible and hard to debug. When something goes wrong, you can't see the
+interface, it's inflexible and harder to debug. If something goes wrong, you can't see the
deployment logs. The Web Application Firewall feature uses this installation method.
However, the next generation of GitLab Managed Apps V2 ([CI/CD-based GitLab Managed Apps](https://gitlab.com/groups/gitlab-org/-/epics/2103))
@@ -75,10 +75,10 @@ sequenceDiagram
```
Debugging is easier because you have access to the raw logs of these jobs (the Helm Tiller output is
-available as an artifact in case of failure) and the flexibility is much better. Since these
+available as an artifact in case of failure), and the flexibility is much better. Since these
deployments are only triggered when a pipeline is running (most likely when there's a new commit in
the cluster management repository), every action has a paper trail and follows the classic merge
-request workflow (approvals, merge, deploy). The Network Policy (Cilium) Managed App and Container
+request workflow (approvals, merge, deploy). The Network Policy (Cilium) Managed App, and Container
Host Security (Falco) are deployed with this model.
## Connect the cluster to GitLab
diff --git a/doc/user/upgrade_email_bypass.md b/doc/user/upgrade_email_bypass.md
index 5f1e02b1383..bdc49e09916 100644
--- a/doc/user/upgrade_email_bypass.md
+++ b/doc/user/upgrade_email_bypass.md
@@ -66,7 +66,7 @@ Your account has been blocked. Fatal: Could not read from remote repository
You can assure your users that they have not been [Blocked](admin_area/blocking_unblocking_users.md) by an administrator.
When affected users see this message, they must confirm their email address before they can commit code.
-## What do I need to know as an administrator of a GitLab Self-Managed Instance?
+## What do I need to know as an administrator of a GitLab self-managed Instance?
You have the following options to help your users:
@@ -87,6 +87,19 @@ admin.confirmed_at = Time.zone.now
admin.save!
```
+## How do I force-confirm all users on my self-managed instance?
+
+If you are an administrator and would like to force-confirm all users on your system, sign in to your GitLab
+instance with a [Rails console session](../administration/troubleshooting/navigating_gitlab_via_rails_console.md#starting-a-rails-console-session).
+Once connected, run the following commands to confirm all user accounts:
+
+```ruby
+User.where('LENGTH(confirmation_token) = 32').where(confirmed_at: nil).find_each { |u| u.confirmed_at = Time.now; u.save }
+```
+
+CAUTION: **Caution:**
+The command described in this section may activate users who have not properly confirmed their email addresses.
+
## What about LDAP users?
LDAP users should NOT be affected.
diff --git a/lib/banzai/filter/reference_filter.rb b/lib/banzai/filter/reference_filter.rb
index 9032ca6ddc6..9afcfee2fe8 100644
--- a/lib/banzai/filter/reference_filter.rb
+++ b/lib/banzai/filter/reference_filter.rb
@@ -25,14 +25,12 @@ module Banzai
def initialize(doc, context = nil, result = nil)
super
- if update_nodes_enabled?
- @new_nodes = {}
- @nodes = self.result[:reference_filter_nodes]
- end
+ @new_nodes = {}
+ @nodes = self.result[:reference_filter_nodes]
end
def call_and_update_nodes
- update_nodes_enabled? ? with_update_nodes { call } : call
+ with_update_nodes { call }
end
# Returns a data attribute String to attach to a reference link
@@ -165,11 +163,7 @@ module Banzai
end
def replace_text_with_html(node, index, html)
- if update_nodes_enabled?
- replace_and_update_new_nodes(node, index, html)
- else
- node.replace(html)
- end
+ replace_and_update_new_nodes(node, index, html)
end
def replace_and_update_new_nodes(node, index, html)
@@ -209,10 +203,6 @@ module Banzai
end
result[:reference_filter_nodes] = nodes
end
-
- def update_nodes_enabled?
- Feature.enabled?(:update_nodes_for_banzai_reference_filter, project)
- end
end
end
end
diff --git a/lib/gitlab/usage_data.rb b/lib/gitlab/usage_data.rb
index baa760bdc92..c34030063d6 100644
--- a/lib/gitlab/usage_data.rb
+++ b/lib/gitlab/usage_data.rb
@@ -10,7 +10,6 @@
# alt_usage_data { Gitlab::VERSION }
# redis_usage_data(Gitlab::UsageDataCounters::WikiPageCounter)
# redis_usage_data { ::Gitlab::UsageCounters::PodLogs.usage_totals[:total] }
-
module Gitlab
class UsageData
BATCH_SIZE = 100
@@ -84,9 +83,11 @@ module Gitlab
auto_devops_enabled: count(::ProjectAutoDevops.enabled),
auto_devops_disabled: count(::ProjectAutoDevops.disabled),
deploy_keys: count(DeployKey),
+ # rubocop: disable UsageData/LargeTable:
deployments: deployment_count(Deployment),
successful_deployments: deployment_count(Deployment.success),
failed_deployments: deployment_count(Deployment.failed),
+ # rubocop: enable UsageData/LargeTable:
environments: count(::Environment),
clusters: count(::Clusters::Cluster),
clusters_enabled: count(::Clusters::Cluster.enabled),
@@ -171,9 +172,11 @@ module Gitlab
def system_usage_data_monthly
{
counts_monthly: {
+ # rubocop: disable UsageData/LargeTable:
deployments: deployment_count(Deployment.where(last_28_days_time_period)),
successful_deployments: deployment_count(Deployment.success.where(last_28_days_time_period)),
failed_deployments: deployment_count(Deployment.failed.where(last_28_days_time_period)),
+ # rubocop: enable UsageData/LargeTable:
personal_snippets: count(PersonalSnippet.where(last_28_days_time_period)),
project_snippets: count(ProjectSnippet.where(last_28_days_time_period))
}.tap do |data|
@@ -332,14 +335,18 @@ module Gitlab
finish = ::Project.maximum(:id)
results[:projects_with_expiration_policy_disabled] = distinct_count(::ContainerExpirationPolicy.where(enabled: false), :project_id, start: start, finish: finish)
+ # rubocop: disable UsageData/LargeTable
base = ::ContainerExpirationPolicy.active
+ # rubocop: enable UsageData/LargeTable
results[:projects_with_expiration_policy_enabled] = distinct_count(base, :project_id, start: start, finish: finish)
+ # rubocop: disable UsageData/LargeTable
%i[keep_n cadence older_than].each do |option|
::ContainerExpirationPolicy.public_send("#{option}_options").keys.each do |value| # rubocop: disable GitlabSecurity/PublicSend
results["projects_with_expiration_policy_enabled_with_#{option}_set_to_#{value}".to_sym] = distinct_count(base.where(option => value), :project_id, start: start, finish: finish)
end
end
+ # rubocop: enable UsageData/LargeTable
results[:projects_with_expiration_policy_enabled_with_keep_n_unset] = distinct_count(base.where(keep_n: nil), :project_id, start: start, finish: finish)
results[:projects_with_expiration_policy_enabled_with_older_than_unset] = distinct_count(base.where(older_than: nil), :project_id, start: start, finish: finish)
@@ -350,9 +357,11 @@ module Gitlab
# rubocop: disable CodeReuse/ActiveRecord
def services_usage
+ # rubocop: disable UsageData/LargeTable:
Service.available_services_names.without('jira').each_with_object({}) do |service_name, response|
response["projects_#{service_name}_active".to_sym] = count(Service.active.where(template: false, type: "#{service_name}_service".camelize))
end.merge(jira_usage).merge(jira_import_usage)
+ # rubocop: enable UsageData/LargeTable:
end
def jira_usage
@@ -365,6 +374,7 @@ module Gitlab
projects_jira_active: 0
}
+ # rubocop: disable UsageData/LargeTable:
JiraService.active.includes(:jira_tracker_data).find_in_batches(batch_size: BATCH_SIZE) do |services|
counts = services.group_by do |service|
# TODO: Simplify as part of https://gitlab.com/gitlab-org/gitlab/issues/29404
@@ -376,21 +386,24 @@ module Gitlab
results[:projects_jira_cloud_active] += counts[:cloud].size if counts[:cloud]
results[:projects_jira_active] += services.size
end
-
+ # rubocop: enable UsageData/LargeTable:
results
rescue ActiveRecord::StatementInvalid
{ projects_jira_server_active: FALLBACK, projects_jira_cloud_active: FALLBACK, projects_jira_active: FALLBACK }
end
+ # rubocop: disable UsageData/LargeTable
def successful_deployments_with_cluster(scope)
scope
.joins(cluster: :deployments)
.merge(Clusters::Cluster.enabled)
.merge(Deployment.success)
end
+ # rubocop: enable UsageData/LargeTable
# rubocop: enable CodeReuse/ActiveRecord
def jira_import_usage
+ # rubocop: disable UsageData/LargeTable
finished_jira_imports = JiraImportState.finished
{
@@ -398,6 +411,7 @@ module Gitlab
jira_imports_projects_count: distinct_count(finished_jira_imports, :project_id),
jira_imports_total_imported_issues_count: alt_usage_data { JiraImportState.finished_imports_count }
}
+ # rubocop: enable UsageData/LargeTable
end
def user_preferences_usage
@@ -406,13 +420,8 @@ module Gitlab
# rubocop: disable CodeReuse/ActiveRecord
def merge_requests_users(time_period)
- query =
- Event
- .where(target_type: Event::TARGET_TYPES[:merge_request].to_s)
- .where(time_period)
-
distinct_count(
- query,
+ Event.where(target_type: Event::TARGET_TYPES[:merge_request].to_s).where(time_period),
:author_id,
start: user_minimum_id,
finish: user_maximum_id
@@ -450,6 +459,7 @@ module Gitlab
end
# rubocop: disable CodeReuse/ActiveRecord
+ # rubocop: disable UsageData/LargeTable
def usage_activity_by_stage_configure(time_period)
{
clusters_applications_cert_managers: cluster_applications_user_distinct_count(::Clusters::Applications::CertManager, time_period),
@@ -470,6 +480,7 @@ module Gitlab
project_clusters_enabled: clusters_user_distinct_count(::Clusters::Cluster.enabled.project_type, time_period)
}
end
+ # rubocop: enable UsageData/LargeTable
# rubocop: enable CodeReuse/ActiveRecord
# rubocop: disable CodeReuse/ActiveRecord
@@ -628,8 +639,9 @@ module Gitlab
# rubocop: disable CodeReuse/ActiveRecord
def service_desk_counts
+ # rubocop: disable UsageData/LargeTable:
projects_with_service_desk = ::Project.where(service_desk_enabled: true)
-
+ # rubocop: enable UsageData/LargeTable:
{
service_desk_enabled_projects: count(projects_with_service_desk),
service_desk_issues: count(
diff --git a/locale/gitlab.pot b/locale/gitlab.pot
index fd739397a27..fb84eeab17b 100644
--- a/locale/gitlab.pot
+++ b/locale/gitlab.pot
@@ -22720,6 +22720,15 @@ msgstr ""
msgid "Static Application Security Testing (SAST)"
msgstr ""
+msgid "StaticSiteEditor|1. Add a clear title to describe the change."
+msgstr ""
+
+msgid "StaticSiteEditor|2. Add a description to explain why the change is being made."
+msgstr ""
+
+msgid "StaticSiteEditor|3. Assign a person to review and accept the merge request."
+msgstr ""
+
msgid "StaticSiteEditor|An error occurred while submitting your changes."
msgstr ""
@@ -22741,34 +22750,19 @@ msgstr ""
msgid "StaticSiteEditor|Static site editor"
msgstr ""
-msgid "StaticSiteEditor|Success!"
-msgstr ""
-
-msgid "StaticSiteEditor|Summary of changes"
-msgstr ""
-
msgid "StaticSiteEditor|The Static Site Editor is currently configured to only edit Markdown content on pages generated from Middleman. Visit the documentation to learn more about configuring your site to use the Static Site Editor."
msgstr ""
+msgid "StaticSiteEditor|To see your changes live you will need to do the following things:"
+msgstr ""
+
msgid "StaticSiteEditor|Update %{sourcePath} file"
msgstr ""
msgid "StaticSiteEditor|View documentation"
msgstr ""
-msgid "StaticSiteEditor|View merge request"
-msgstr ""
-
-msgid "StaticSiteEditor|You added a commit:"
-msgstr ""
-
-msgid "StaticSiteEditor|You created a merge request:"
-msgstr ""
-
-msgid "StaticSiteEditor|You created a new branch:"
-msgstr ""
-
-msgid "StaticSiteEditor|Your changes have been submitted and a merge request has been created. The changes won’t be visible on the site until the merge request has been accepted."
+msgid "StaticSiteEditor|Your merge request has been created"
msgstr ""
msgid "Statistics"
@@ -25689,6 +25683,9 @@ msgstr ""
msgid "Update"
msgstr ""
+msgid "Update %{sourcePath} file"
+msgstr ""
+
msgid "Update all"
msgstr ""
@@ -26425,6 +26422,9 @@ msgstr ""
msgid "View log"
msgstr ""
+msgid "View merge request"
+msgstr ""
+
msgid "View open merge request"
msgstr ""
diff --git a/rubocop/cop/usage_data/large_table.rb b/rubocop/cop/usage_data/large_table.rb
new file mode 100644
index 00000000000..d9d44f74d26
--- /dev/null
+++ b/rubocop/cop/usage_data/large_table.rb
@@ -0,0 +1,87 @@
+# frozen_string_literal: true
+
+module RuboCop
+ module Cop
+ module UsageData
+ class LargeTable < RuboCop::Cop::Cop
+ # This cop checks that batch count and distinct_count are used in usage_data.rb files in metrics based on ActiveRecord models.
+ #
+ # @example
+ #
+ # # bad
+ # Issue.count
+ # List.assignee.count
+ # ::Ci::Pipeline.auto_devops_source.count
+ # ZoomMeeting.distinct.count(:issue_id)
+ #
+ # # Good
+ # count(Issue)
+ # count(List.assignee)
+ # count(::Ci::Pipeline.auto_devops_source)
+ # distinct_count(ZoomMeeting, :issue_id)
+ MSG = 'Use one of the %{count_methods} methods for counting on %{class_name}'
+
+ # Match one level const as Issue, Gitlab
+ def_node_matcher :one_level_node, <<~PATTERN
+ (send
+ (const {nil? cbase} $...)
+ $...)
+ PATTERN
+
+ # Match two level const as ::Clusters::Cluster, ::Ci::Pipeline
+ def_node_matcher :two_level_node, <<~PATTERN
+ (send
+ (const
+ (const {nil? cbase} $...)
+ $...)
+ $...)
+ PATTERN
+
+ def on_send(node)
+ one_level_matches = one_level_node(node)
+ two_level_matches = two_level_node(node)
+
+ return unless Array(one_level_matches).any? || Array(two_level_matches).any?
+
+ if one_level_matches
+ class_name = one_level_matches[0].first
+ method_used = one_level_matches[1]&.first
+ else
+ class_name = "#{two_level_matches[0].first}::#{two_level_matches[1].first}".to_sym
+ method_used = two_level_matches[2]&.first
+ end
+
+ return if non_related?(class_name) || allowed_methods.include?(method_used)
+
+ counters_used = node.ancestors.any? { |ancestor| allowed_method?(ancestor) }
+
+ unless counters_used
+ add_offense(node, location: :expression, message: format(MSG, count_methods: count_methods.join(', '), class_name: class_name))
+ end
+ end
+
+ private
+
+ def count_methods
+ cop_config['CountMethods'] || []
+ end
+
+ def allowed_methods
+ cop_config['AllowedMethods'] || []
+ end
+
+ def non_related_classes
+ cop_config['NonRelatedClasses'] || []
+ end
+
+ def non_related?(class_name)
+ non_related_classes.include?(class_name)
+ end
+
+ def allowed_method?(ancestor)
+ ancestor.send_type? && !ancestor.dot? && count_methods.include?(ancestor.method_name)
+ end
+ end
+ end
+ end
+end
diff --git a/rubocop/rubocop-usage-data.yml b/rubocop/rubocop-usage-data.yml
new file mode 100644
index 00000000000..887d7aa9427
--- /dev/null
+++ b/rubocop/rubocop-usage-data.yml
@@ -0,0 +1,32 @@
+UsageData/LargeTable:
+ Enabled: true
+ Include:
+ - 'lib/gitlab/usage_data.rb'
+ - 'ee/lib/ee/gitlab/usage_data.rb'
+ NonRelatedClasses:
+ - :Feature
+ - :Gitlab
+ - :Gitlab::AppLogger
+ - :Gitlab::Auth
+ - :Gitlab::CurrentSettings
+ - :Gitlab::Database
+ - :Gitlab::ErrorTracking
+ - :Gitlab::Geo
+ - :Gitlab::Git
+ - :Gitlab::IncomingEmail
+ - :Gitlab::Metrics
+ - :Gitlab::Runtime
+ - :Gitaly::Server
+ - :Gitlab::UsageData
+ - :License
+ - :Rails
+ - :Time
+ - :SECURE_PRODUCT_TYPES
+ - :Settings
+ CountMethods:
+ - :count
+ - :distinct_count
+ AllowedMethods:
+ - :arel_table
+ - :minimum
+ - :maximum
diff --git a/spec/frontend/projects/settings/access_dropdown_spec.js b/spec/frontend/projects/settings/access_dropdown_spec.js
new file mode 100644
index 00000000000..6d323b0408b
--- /dev/null
+++ b/spec/frontend/projects/settings/access_dropdown_spec.js
@@ -0,0 +1,140 @@
+import $ from 'jquery';
+import '~/gl_dropdown';
+import AccessDropdown from '~/projects/settings/access_dropdown';
+import { LEVEL_TYPES } from '~/projects/settings/constants';
+
+describe('AccessDropdown', () => {
+ const defaultLabel = 'dummy default label';
+ let dropdown;
+
+ beforeEach(() => {
+ setFixtures(`
+
+
+
+ `);
+ const $dropdown = $('#dummy-dropdown');
+ $dropdown.data('defaultLabel', defaultLabel);
+ const options = {
+ $dropdown,
+ accessLevelsData: {
+ roles: [
+ {
+ id: 42,
+ text: 'Dummy Role',
+ },
+ ],
+ },
+ };
+ dropdown = new AccessDropdown(options);
+ });
+
+ describe('toggleLabel', () => {
+ let $dropdownToggleText;
+ const dummyItems = [
+ { type: LEVEL_TYPES.ROLE, access_level: 42 },
+ { type: LEVEL_TYPES.USER },
+ { type: LEVEL_TYPES.USER },
+ { type: LEVEL_TYPES.GROUP },
+ { type: LEVEL_TYPES.GROUP },
+ { type: LEVEL_TYPES.GROUP },
+ ];
+
+ beforeEach(() => {
+ $dropdownToggleText = $('.dropdown-toggle-text');
+ });
+
+ it('displays number of items', () => {
+ dropdown.setSelectedItems(dummyItems);
+ $dropdownToggleText.addClass('is-default');
+
+ const label = dropdown.toggleLabel();
+
+ expect(label).toBe('1 role, 2 users, 3 groups');
+ expect($dropdownToggleText).not.toHaveClass('is-default');
+ });
+
+ describe('without selected items', () => {
+ beforeEach(() => {
+ dropdown.setSelectedItems([]);
+ });
+
+ it('falls back to default label', () => {
+ const label = dropdown.toggleLabel();
+
+ expect(label).toBe(defaultLabel);
+ expect($dropdownToggleText).toHaveClass('is-default');
+ });
+ });
+
+ describe('with only role', () => {
+ beforeEach(() => {
+ dropdown.setSelectedItems(dummyItems.filter(item => item.type === LEVEL_TYPES.ROLE));
+ $dropdownToggleText.addClass('is-default');
+ });
+
+ it('displays the role name', () => {
+ const label = dropdown.toggleLabel();
+
+ expect(label).toBe('Dummy Role');
+ expect($dropdownToggleText).not.toHaveClass('is-default');
+ });
+ });
+
+ describe('with only users', () => {
+ beforeEach(() => {
+ dropdown.setSelectedItems(dummyItems.filter(item => item.type === LEVEL_TYPES.USER));
+ $dropdownToggleText.addClass('is-default');
+ });
+
+ it('displays number of users', () => {
+ const label = dropdown.toggleLabel();
+
+ expect(label).toBe('2 users');
+ expect($dropdownToggleText).not.toHaveClass('is-default');
+ });
+ });
+
+ describe('with only groups', () => {
+ beforeEach(() => {
+ dropdown.setSelectedItems(dummyItems.filter(item => item.type === LEVEL_TYPES.GROUP));
+ $dropdownToggleText.addClass('is-default');
+ });
+
+ it('displays number of groups', () => {
+ const label = dropdown.toggleLabel();
+
+ expect(label).toBe('3 groups');
+ expect($dropdownToggleText).not.toHaveClass('is-default');
+ });
+ });
+
+ describe('with users and groups', () => {
+ beforeEach(() => {
+ const selectedTypes = [LEVEL_TYPES.GROUP, LEVEL_TYPES.USER];
+ dropdown.setSelectedItems(dummyItems.filter(item => selectedTypes.includes(item.type)));
+ $dropdownToggleText.addClass('is-default');
+ });
+
+ it('displays number of groups', () => {
+ const label = dropdown.toggleLabel();
+
+ expect(label).toBe('2 users, 3 groups');
+ expect($dropdownToggleText).not.toHaveClass('is-default');
+ });
+ });
+ });
+
+ describe('userRowHtml', () => {
+ it('escapes users name', () => {
+ const user = {
+ avatar_url: '',
+ name: '
',
+ username: 'test',
+ };
+ const template = dropdown.userRowHtml(user);
+
+ expect(template).not.toContain(user.name);
+ });
+ });
+});
diff --git a/spec/frontend/static_site_editor/components/app_spec.js b/spec/frontend/static_site_editor/components/app_spec.js
new file mode 100644
index 00000000000..bbdffeae68f
--- /dev/null
+++ b/spec/frontend/static_site_editor/components/app_spec.js
@@ -0,0 +1,34 @@
+import { shallowMount } from '@vue/test-utils';
+import App from '~/static_site_editor/components/app.vue';
+
+describe('static_site_editor/components/app', () => {
+ const mergeRequestsIllustrationPath = 'illustrations/merge_requests.svg';
+ const RouterView = {
+ template: '
',
+ };
+ let wrapper;
+
+ const buildWrapper = () => {
+ wrapper = shallowMount(App, {
+ stubs: {
+ RouterView,
+ },
+ propsData: {
+ mergeRequestsIllustrationPath,
+ },
+ });
+ };
+
+ afterEach(() => {
+ wrapper.destroy();
+ wrapper = null;
+ });
+
+ it('passes merge request illustration path to the router view component', () => {
+ buildWrapper();
+
+ expect(wrapper.find(RouterView).attributes()).toMatchObject({
+ 'merge-requests-illustration-path': mergeRequestsIllustrationPath,
+ });
+ });
+});
diff --git a/spec/frontend/static_site_editor/components/saved_changes_message_spec.js b/spec/frontend/static_site_editor/components/saved_changes_message_spec.js
deleted file mode 100644
index a63c3a83395..00000000000
--- a/spec/frontend/static_site_editor/components/saved_changes_message_spec.js
+++ /dev/null
@@ -1,56 +0,0 @@
-import { shallowMount } from '@vue/test-utils';
-
-import SavedChangesMessage from '~/static_site_editor/components/saved_changes_message.vue';
-
-import { returnUrl, savedContentMeta } from '../mock_data';
-
-describe('~/static_site_editor/components/saved_changes_message.vue', () => {
- let wrapper;
- const { branch, commit, mergeRequest } = savedContentMeta;
- const props = {
- branch,
- commit,
- mergeRequest,
- returnUrl,
- };
- const findReturnToSiteButton = () => wrapper.find({ ref: 'returnToSiteButton' });
- const findMergeRequestButton = () => wrapper.find({ ref: 'mergeRequestButton' });
- const findBranchLink = () => wrapper.find({ ref: 'branchLink' });
- const findCommitLink = () => wrapper.find({ ref: 'commitLink' });
- const findMergeRequestLink = () => wrapper.find({ ref: 'mergeRequestLink' });
-
- beforeEach(() => {
- wrapper = shallowMount(SavedChangesMessage, {
- propsData: props,
- });
- });
-
- afterEach(() => {
- wrapper.destroy();
- });
-
- it.each`
- text | findEl | url
- ${'Return to site'} | ${findReturnToSiteButton} | ${props.returnUrl}
- ${'View merge request'} | ${findMergeRequestButton} | ${props.mergeRequest.url}
- `('renders "$text" button link', ({ text, findEl, url }) => {
- const btn = findEl();
-
- expect(btn.exists()).toBe(true);
- expect(btn.text()).toBe(text);
- expect(btn.attributes('href')).toBe(url);
- });
-
- it.each`
- desc | findEl | prop
- ${'branch'} | ${findBranchLink} | ${props.branch}
- ${'commit'} | ${findCommitLink} | ${props.commit}
- ${'merge request'} | ${findMergeRequestLink} | ${props.mergeRequest}
- `('renders $desc link', ({ findEl, prop }) => {
- const el = findEl();
-
- expect(el.exists()).toBe(true);
- expect(el.text()).toBe(prop.label);
- expect(el.attributes('href')).toBe(prop.url);
- });
-});
diff --git a/spec/frontend/static_site_editor/pages/success_spec.js b/spec/frontend/static_site_editor/pages/success_spec.js
index d62b67bfa83..3e19e2413e7 100644
--- a/spec/frontend/static_site_editor/pages/success_spec.js
+++ b/spec/frontend/static_site_editor/pages/success_spec.js
@@ -1,17 +1,12 @@
-import Vuex from 'vuex';
-import { shallowMount, createLocalVue } from '@vue/test-utils';
+import { shallowMount } from '@vue/test-utils';
+import { GlEmptyState, GlButton } from '@gitlab/ui';
import Success from '~/static_site_editor/pages/success.vue';
-import SavedChangesMessage from '~/static_site_editor/components/saved_changes_message.vue';
-import { savedContentMeta, returnUrl } from '../mock_data';
+import { savedContentMeta, returnUrl, sourcePath } from '../mock_data';
import { HOME_ROUTE } from '~/static_site_editor/router/constants';
-const localVue = createLocalVue();
-
-localVue.use(Vuex);
-
describe('static_site_editor/pages/success', () => {
+ const mergeRequestsIllustrationPath = 'illustrations/merge_requests.svg';
let wrapper;
- let store;
let router;
const buildRouter = () => {
@@ -22,16 +17,22 @@ describe('static_site_editor/pages/success', () => {
const buildWrapper = (data = {}) => {
wrapper = shallowMount(Success, {
- localVue,
- store,
mocks: {
$router: router,
},
+ stubs: {
+ GlEmptyState,
+ GlButton,
+ },
+ propsData: {
+ mergeRequestsIllustrationPath,
+ },
data() {
return {
savedContentMeta,
appData: {
returnUrl,
+ sourcePath,
},
...data,
};
@@ -39,7 +40,8 @@ describe('static_site_editor/pages/success', () => {
});
};
- const findSavedChangesMessage = () => wrapper.find(SavedChangesMessage);
+ const findEmptyState = () => wrapper.find(GlEmptyState);
+ const findReturnUrlButton = () => wrapper.find(GlButton);
beforeEach(() => {
buildRouter();
@@ -50,29 +52,50 @@ describe('static_site_editor/pages/success', () => {
wrapper = null;
});
- it('renders saved changes message', () => {
+ it('renders empty state with a link to the created merge request', () => {
buildWrapper();
- expect(findSavedChangesMessage().exists()).toBe(true);
+ expect(findEmptyState().exists()).toBe(true);
+ expect(findEmptyState().props()).toMatchObject({
+ primaryButtonText: 'View merge request',
+ primaryButtonLink: savedContentMeta.mergeRequest.url,
+ title: 'Your merge request has been created',
+ svgPath: mergeRequestsIllustrationPath,
+ });
});
- it('passes returnUrl to the saved changes message', () => {
+ it('displays merge request instructions in the empty state', () => {
buildWrapper();
- expect(findSavedChangesMessage().props('returnUrl')).toBe(returnUrl);
+ expect(findEmptyState().text()).toContain(
+ 'To see your changes live you will need to do the following things:',
+ );
+ expect(findEmptyState().text()).toContain('1. Add a clear title to describe the change.');
+ expect(findEmptyState().text()).toContain(
+ '2. Add a description to explain why the change is being made.',
+ );
+ expect(findEmptyState().text()).toContain(
+ '3. Assign a person to review and accept the merge request.',
+ );
});
- it('passes saved content metadata to the saved changes message', () => {
+ it('displays return to site button', () => {
buildWrapper();
- expect(findSavedChangesMessage().props('branch')).toBe(savedContentMeta.branch);
- expect(findSavedChangesMessage().props('commit')).toBe(savedContentMeta.commit);
- expect(findSavedChangesMessage().props('mergeRequest')).toBe(savedContentMeta.mergeRequest);
+ expect(findReturnUrlButton().text()).toBe('Return to site');
+ expect(findReturnUrlButton().attributes().href).toBe(returnUrl);
+ });
+
+ it('displays source path', () => {
+ buildWrapper();
+
+ expect(wrapper.text()).toContain(`Update ${sourcePath} file`);
});
it('redirects to the HOME route when content has not been submitted', () => {
buildWrapper({ savedContentMeta: null });
expect(router.push).toHaveBeenCalledWith(HOME_ROUTE);
+ expect(wrapper.html()).toBe('');
});
});
diff --git a/spec/frontend/vue_shared/components/file_icon_spec.js b/spec/frontend/vue_shared/components/file_icon_spec.js
index adf0da21f9f..e55449dc684 100644
--- a/spec/frontend/vue_shared/components/file_icon_spec.js
+++ b/spec/frontend/vue_shared/components/file_icon_spec.js
@@ -36,6 +36,9 @@ describe('File Icon component', () => {
fileName | iconName
${'test.js'} | ${'javascript'}
${'test.png'} | ${'image'}
+ ${'test.PNG'} | ${'image'}
+ ${'.npmrc'} | ${'npm'}
+ ${'.Npmrc'} | ${'file'}
${'webpack.js'} | ${'webpack'}
`('should render a $iconName icon based on file ending', ({ fileName, iconName }) => {
createComponent({ fileName });
diff --git a/spec/lib/banzai/filter/reference_filter_spec.rb b/spec/lib/banzai/filter/reference_filter_spec.rb
index d5978db13c0..2888965dbc4 100644
--- a/spec/lib/banzai/filter/reference_filter_spec.rb
+++ b/spec/lib/banzai/filter/reference_filter_spec.rb
@@ -110,20 +110,6 @@ RSpec.describe Banzai::Filter::ReferenceFilter do
expect(filter.instance_variable_get(:@new_nodes)).to eq({ index => [filter.each_node.to_a[index]] })
end
-
- context "with update_nodes_for_banzai_reference_filter feature flag disabled" do
- before do
- stub_feature_flags(update_nodes_for_banzai_reference_filter: false)
- end
-
- it 'does not call replace_and_update_new_nodes' do
- expect(filter).not_to receive(:replace_and_update_new_nodes).with(filter.nodes[index], index, html)
-
- filter.send(method_name, *args) do
- html
- end
- end
- end
end
end
@@ -198,49 +184,20 @@ RSpec.describe Banzai::Filter::ReferenceFilter do
end
describe "#call_and_update_nodes" do
- context "with update_nodes_for_banzai_reference_filter feature flag enabled" do
- include_context 'new nodes'
- let(:document) { Nokogiri::HTML.fragment('
foo') }
- let(:filter) { described_class.new(document, project: project) }
+ include_context 'new nodes'
+ let(:document) { Nokogiri::HTML.fragment('
foo') }
+ let(:filter) { described_class.new(document, project: project) }
- before do
- stub_feature_flags(update_nodes_for_banzai_reference_filter: true)
- end
+ it "updates all new nodes", :aggregate_failures do
+ filter.instance_variable_set('@nodes', nodes)
- it "updates all new nodes", :aggregate_failures do
- filter.instance_variable_set('@nodes', nodes)
+ expect(filter).to receive(:call) { filter.instance_variable_set('@new_nodes', new_nodes) }
+ expect(filter).to receive(:with_update_nodes).and_call_original
+ expect(filter).to receive(:update_nodes!).and_call_original
- expect(filter).to receive(:call) { filter.instance_variable_set('@new_nodes', new_nodes) }
- expect(filter).to receive(:with_update_nodes).and_call_original
- expect(filter).to receive(:update_nodes!).and_call_original
+ filter.call_and_update_nodes
- filter.call_and_update_nodes
-
- expect(filter.result[:reference_filter_nodes]).to eq(expected_nodes)
- end
- end
-
- context "with update_nodes_for_banzai_reference_filter feature flag disabled" do
- include_context 'new nodes'
-
- before do
- stub_feature_flags(update_nodes_for_banzai_reference_filter: false)
- end
-
- it "does not change nodes", :aggregate_failures do
- document = Nokogiri::HTML.fragment('
foo')
- filter = described_class.new(document, project: project)
- filter.instance_variable_set('@nodes', nodes)
-
- expect(filter).to receive(:call) { filter.instance_variable_set('@new_nodes', new_nodes) }
- expect(filter).not_to receive(:with_update_nodes)
- expect(filter).not_to receive(:update_nodes!)
-
- filter.call_and_update_nodes
-
- expect(filter.nodes).to eq(nodes)
- expect(filter.result[:reference_filter_nodes]).to be nil
- end
+ expect(filter.result[:reference_filter_nodes]).to eq(expected_nodes)
end
end
@@ -251,10 +208,6 @@ RSpec.describe Banzai::Filter::ReferenceFilter do
let(:result) { { reference_filter_nodes: nodes } }
- before do
- stub_feature_flags(update_nodes_for_banzai_reference_filter: true)
- end
-
it "updates all nodes", :aggregate_failures do
expect_next_instance_of(described_class) do |filter|
expect(filter).to receive(:call_and_update_nodes).and_call_original
@@ -267,26 +220,5 @@ RSpec.describe Banzai::Filter::ReferenceFilter do
expect(result[:reference_filter_nodes]).to eq(expected_nodes)
end
-
- context "with update_nodes_for_banzai_reference_filter feature flag disabled" do
- let(:result) { {} }
-
- before do
- stub_feature_flags(update_nodes_for_banzai_reference_filter: false)
- end
-
- it "updates all nodes", :aggregate_failures do
- expect_next_instance_of(described_class) do |filter|
- expect(filter).to receive(:call_and_update_nodes).and_call_original
- expect(filter).not_to receive(:with_update_nodes)
- expect(filter).to receive(:call) { filter.instance_variable_set('@new_nodes', new_nodes) }
- expect(filter).not_to receive(:update_nodes!)
- end
-
- described_class.call(document, { project: project }, result)
-
- expect(result[:reference_filter_nodes]).to be nil
- end
- end
end
end
diff --git a/spec/lib/banzai/pipeline/gfm_pipeline_spec.rb b/spec/lib/banzai/pipeline/gfm_pipeline_spec.rb
index beb760637b0..247f4591632 100644
--- a/spec/lib/banzai/pipeline/gfm_pipeline_spec.rb
+++ b/spec/lib/banzai/pipeline/gfm_pipeline_spec.rb
@@ -30,34 +30,6 @@ RSpec.describe Banzai::Pipeline::GfmPipeline do
described_class.call(markdown, project: project)
end
- context "with update_nodes_for_banzai_reference_filter feature flag disabled" do
- before do
- stub_feature_flags(update_nodes_for_banzai_reference_filter: false)
- end
-
- context 'when shorthand pattern #ISSUE_ID is used' do
- it 'links an internal issues and doesnt store nodes in result[:reference_filter_nodes]', :aggregate_failures do
- issue = create(:issue, project: project)
- markdown = "text #{issue.to_reference(project, full: true)}"
- result = described_class.call(markdown, project: project)
- link = result[:output].css('a').first
-
- expect(link['href']).to eq(Gitlab::Routing.url_helpers.project_issue_path(project, issue))
- expect(result[:reference_filter_nodes]).to eq nil
- end
- end
-
- it 'execute :each_node for each reference_filter', :aggregate_failures do
- issue = create(:issue, project: project)
- markdown = "text #{issue.to_reference(project, full: true)}"
- described_class.reference_filters do |reference_filter|
- expect_any_instance_of(reference_filter).to receive(:each_node).once
- end
-
- described_class.call(markdown, project: project)
- end
- end
-
context 'when shorthand pattern #ISSUE_ID is used' do
it 'links an internal issue if it exists' do
issue = create(:issue, project: project)
diff --git a/spec/policies/design_management/design_policy_spec.rb b/spec/policies/design_management/design_policy_spec.rb
index 5dde5f896c9..50e1c86dc6b 100644
--- a/spec/policies/design_management/design_policy_spec.rb
+++ b/spec/policies/design_management/design_policy_spec.rb
@@ -1,22 +1,32 @@
# frozen_string_literal: true
-require 'spec_helper'
+require "spec_helper"
RSpec.describe DesignManagement::DesignPolicy do
include DesignManagementTestHelpers
- include_context 'ProjectPolicy context'
-
let(:guest_design_abilities) { %i[read_design] }
- let(:developer_design_abilities) do
- %i[create_design destroy_design]
- end
+ let(:developer_design_abilities) { %i[create_design destroy_design] }
let(:design_abilities) { guest_design_abilities + developer_design_abilities }
- let(:issue) { create(:issue, project: project) }
+ let_it_be(:guest) { create(:user) }
+ let_it_be(:reporter) { create(:user) }
+ let_it_be(:developer) { create(:user) }
+ let_it_be(:maintainer) { create(:user) }
+ let_it_be(:owner) { create(:user) }
+ let_it_be(:admin) { create(:admin) }
+ let_it_be(:project) { create(:project, :public, namespace: owner.namespace) }
+ let_it_be(:issue) { create(:issue, project: project) }
let(:design) { create(:design, issue: issue) }
subject(:design_policy) { described_class.new(current_user, design) }
+ before_all do
+ project.add_guest(guest)
+ project.add_maintainer(maintainer)
+ project.add_developer(developer)
+ project.add_reporter(reporter)
+ end
+
shared_examples_for "design abilities not available" do
context "for owners" do
let(:current_user) { owner }
@@ -71,11 +81,11 @@ RSpec.describe DesignManagement::DesignPolicy do
context "for admins" do
let(:current_user) { admin }
- context 'when admin mode enabled', :enable_admin_mode do
+ context "when admin mode enabled", :enable_admin_mode do
it { is_expected.to be_allowed(*design_abilities) }
end
- context 'when admin mode disabled' do
+ context "when admin mode disabled" do
it { is_expected.to be_allowed(*guest_design_abilities) }
it { is_expected.to be_disallowed(*developer_design_abilities) }
end
@@ -122,7 +132,7 @@ RSpec.describe DesignManagement::DesignPolicy do
it_behaves_like "design abilities available for members"
context "for guests in private projects" do
- let(:project) { create(:project, :private) }
+ let_it_be(:project) { create(:project, :private) }
let(:current_user) { guest }
it { is_expected.to be_allowed(*guest_design_abilities) }
@@ -137,7 +147,7 @@ RSpec.describe DesignManagement::DesignPolicy do
end
context "when the issue is confidential" do
- let(:issue) { create(:issue, :confidential, project: project) }
+ let_it_be(:issue) { create(:issue, :confidential, project: project) }
it_behaves_like "design abilities available for members"
@@ -155,26 +165,24 @@ RSpec.describe DesignManagement::DesignPolicy do
end
context "when the issue is locked" do
+ let_it_be(:issue) { create(:issue, :locked, project: project) }
let(:current_user) { owner }
- let(:issue) { create(:issue, :locked, project: project) }
it_behaves_like "read-only design abilities"
end
context "when the issue has moved" do
+ let_it_be(:issue) { create(:issue, project: project, moved_to: create(:issue)) }
let(:current_user) { owner }
- let(:issue) { create(:issue, project: project, moved_to: create(:issue)) }
it_behaves_like "read-only design abilities"
end
context "when the project is archived" do
+ let_it_be(:project) { create(:project, :public, :archived) }
+ let_it_be(:issue) { create(:issue, project: project) }
let(:current_user) { owner }
- before do
- project.update!(archived: true)
- end
-
it_behaves_like "read-only design abilities"
end
end
diff --git a/spec/rubocop/cop/usage_data/large_table_spec.rb b/spec/rubocop/cop/usage_data/large_table_spec.rb
new file mode 100644
index 00000000000..de6fb9c17e2
--- /dev/null
+++ b/spec/rubocop/cop/usage_data/large_table_spec.rb
@@ -0,0 +1,90 @@
+# frozen_string_literal: true
+
+require 'fast_spec_helper'
+
+require 'rubocop'
+require 'rubocop/rspec/support'
+
+require_relative '../../../../rubocop/cop/usage_data/large_table'
+
+RSpec.describe RuboCop::Cop::UsageData::LargeTable, type: :rubocop do
+ include CopHelper
+
+ let(:large_tables) { %i[Rails Time] }
+ let(:count_methods) { %i[count distinct_count] }
+ let(:allowed_methods) { %i[minimum maximum] }
+
+ let(:config) do
+ RuboCop::Config.new('UsageData/LargeTable' => {
+ 'NonRelatedClasses' => large_tables,
+ 'CountMethods' => count_methods,
+ 'AllowedMethods' => allowed_methods
+ })
+ end
+
+ subject(:cop) { described_class.new(config) }
+
+ context 'when in usage_data files' do
+ before do
+ allow(cop).to receive(:usage_data_files?).and_return(true)
+ end
+
+ context 'with large tables' do
+ context 'when calling Issue.count' do
+ it 'register an offence' do
+ inspect_source('Issue.count')
+
+ expect(cop.offenses.size).to eq(1)
+ end
+ end
+
+ context 'when calling Issue.active.count' do
+ it 'register an offence' do
+ inspect_source('Issue.active.count')
+
+ expect(cop.offenses.size).to eq(1)
+ end
+ end
+
+ context 'when calling count(Issue)' do
+ it 'does not register an offence' do
+ inspect_source('count(Issue)')
+
+ expect(cop.offenses).to be_empty
+ end
+ end
+
+ context 'when calling count(Ci::Build.active)' do
+ it 'does not register an offence' do
+ inspect_source('count(Ci::Build.active)')
+
+ expect(cop.offenses).to be_empty
+ end
+ end
+
+ context 'when calling Ci::Build.active.count' do
+ it 'register an offence' do
+ inspect_source('Ci::Build.active.count')
+
+ expect(cop.offenses.size).to eq(1)
+ end
+ end
+
+ context 'when using allowed methods' do
+ it 'does not register an offence' do
+ inspect_source('Issue.minimum')
+
+ expect(cop.offenses).to be_empty
+ end
+ end
+ end
+
+ context 'with non related class' do
+ it 'does not register an offence' do
+ inspect_source('Rails.count')
+
+ expect(cop.offenses).to be_empty
+ end
+ end
+ end
+end
diff --git a/spec/support/protected_branch_helpers.rb b/spec/support/protected_branch_helpers.rb
index ede16d1c1e2..b34b9ec4641 100644
--- a/spec/support/protected_branch_helpers.rb
+++ b/spec/support/protected_branch_helpers.rb
@@ -27,4 +27,9 @@ module ProtectedBranchHelpers
set_allowed_to('merge')
set_allowed_to('push')
end
+
+ def click_on_protect
+ click_on "Protect"
+ wait_for_requests
+ end
end
diff --git a/spec/support/shared_examples/features/protected_branches_access_control_ce_shared_examples.rb b/spec/support/shared_examples/features/protected_branches_access_control_ce_shared_examples.rb
index 65db082505a..a46382bc292 100644
--- a/spec/support/shared_examples/features/protected_branches_access_control_ce_shared_examples.rb
+++ b/spec/support/shared_examples/features/protected_branches_access_control_ce_shared_examples.rb
@@ -22,7 +22,7 @@ RSpec.shared_examples "protected branches > access control > CE" do
end
end
- click_on "Protect"
+ click_on_protect
expect(ProtectedBranch.count).to eq(1)
expect(ProtectedBranch.last.push_access_levels.map(&:access_level)).to eq([access_type_id])
@@ -45,7 +45,7 @@ RSpec.shared_examples "protected branches > access control > CE" do
find(:link, 'No one').click
end
- click_on "Protect"
+ click_on_protect
expect(ProtectedBranch.count).to eq(1)
@@ -85,7 +85,7 @@ RSpec.shared_examples "protected branches > access control > CE" do
find(:link, 'No one').click
end
- click_on "Protect"
+ click_on_protect
expect(ProtectedBranch.count).to eq(1)
expect(ProtectedBranch.last.merge_access_levels.map(&:access_level)).to eq([access_type_id])
@@ -108,7 +108,7 @@ RSpec.shared_examples "protected branches > access control > CE" do
find(:link, 'No one').click
end
- click_on "Protect"
+ click_on_protect
expect(ProtectedBranch.count).to eq(1)