-
{{ error }}
+
+
+ {{ error }}
+
-
- {{ __('Set up shared runner availability') }}
-
-
-
-
-
-
-
- {{ __('Enable shared runners for all projects and subgroups in this group.') }}
-
-
-
-
-
-
-
- {{ __('Allows projects or subgroups in this group to override the global setting.') }}
-
-
-
-
+
{{ __('Shared runners are disabled for the parent group') }}
-
+
+
+
+
+
diff --git a/app/assets/javascripts/group_settings/constants.js b/app/assets/javascripts/group_settings/constants.js
index 4067b6b52a3..ab5c0db45ba 100644
--- a/app/assets/javascripts/group_settings/constants.js
+++ b/app/assets/javascripts/group_settings/constants.js
@@ -1,6 +1,3 @@
import { __ } from '~/locale';
-// Debounce delay in milliseconds
-export const DEBOUNCE_TOGGLE_DELAY = 1000;
-
export const ERROR_MESSAGE = __('Refresh the page and try again.');
diff --git a/app/assets/javascripts/group_settings/mount_shared_runners.js b/app/assets/javascripts/group_settings/mount_shared_runners.js
index 21a2373e2b1..aeb6d57a11a 100644
--- a/app/assets/javascripts/group_settings/mount_shared_runners.js
+++ b/app/assets/javascripts/group_settings/mount_shared_runners.js
@@ -6,22 +6,22 @@ export default (containerId = 'update-shared-runners-form') => {
const {
updatePath,
- sharedRunnersAvailability,
- parentSharedRunnersAvailability,
- runnerEnabled,
- runnerDisabled,
- runnerAllowOverride,
+ sharedRunnersSetting,
+ parentSharedRunnersSetting,
+ runnerEnabledValue,
+ runnerDisabledValue,
+ runnerAllowOverrideValue,
} = containerEl.dataset;
return new Vue({
el: containerEl,
provide: {
updatePath,
- sharedRunnersAvailability,
- parentSharedRunnersAvailability,
- runnerEnabled,
- runnerDisabled,
- runnerAllowOverride,
+ sharedRunnersSetting,
+ parentSharedRunnersSetting,
+ runnerEnabledValue,
+ runnerDisabledValue,
+ runnerAllowOverrideValue,
},
render(createElement) {
return createElement(UpdateSharedRunnersForm);
diff --git a/app/assets/javascripts/pages/projects/wikis/edit/index.js b/app/assets/javascripts/pages/projects/wikis/edit/index.js
deleted file mode 100644
index b2288c2655c..00000000000
--- a/app/assets/javascripts/pages/projects/wikis/edit/index.js
+++ /dev/null
@@ -1,3 +0,0 @@
-import { mountApplications } from '~/pages/shared/wikis/edit';
-
-mountApplications();
diff --git a/app/assets/javascripts/pages/projects/wikis/index.js b/app/assets/javascripts/pages/projects/wikis/index.js
index 83fcd348ddf..692baee383b 100644
--- a/app/assets/javascripts/pages/projects/wikis/index.js
+++ b/app/assets/javascripts/pages/projects/wikis/index.js
@@ -1,3 +1,6 @@
import Wikis from '~/pages/shared/wikis/wikis';
+import { mountApplications } from '~/pages/shared/wikis/async_edit';
+
+mountApplications();
export default new Wikis();
diff --git a/app/assets/javascripts/pages/projects/wikis/show/index.js b/app/assets/javascripts/pages/projects/wikis/show/index.js
index 7ca5f6964cd..288f6b616cc 100644
--- a/app/assets/javascripts/pages/projects/wikis/show/index.js
+++ b/app/assets/javascripts/pages/projects/wikis/show/index.js
@@ -1,5 +1,3 @@
import { mountApplications } from '~/pages/shared/wikis/show';
-import { mountApplications as mountEditApplications } from '~/pages/shared/wikis/async_edit';
mountApplications();
-mountEditApplications();
diff --git a/app/assets/javascripts/pipeline_editor/components/file_nav/pipeline_editor_file_nav.vue b/app/assets/javascripts/pipeline_editor/components/file_nav/pipeline_editor_file_nav.vue
index 4ac28b3b9c0..998db653e0c 100644
--- a/app/assets/javascripts/pipeline_editor/components/file_nav/pipeline_editor_file_nav.vue
+++ b/app/assets/javascripts/pipeline_editor/components/file_nav/pipeline_editor_file_nav.vue
@@ -3,11 +3,13 @@ import { GlButton } from '@gitlab/ui';
import getAppStatus from '~/pipeline_editor/graphql/queries/client/app_status.query.graphql';
import glFeatureFlagMixin from '~/vue_shared/mixins/gl_feature_flags_mixin';
import { EDITOR_APP_STATUS_EMPTY } from '../../constants';
+import FileTreePopover from '../popovers/file_tree_popover.vue';
import BranchSwitcher from './branch_switcher.vue';
export default {
components: {
BranchSwitcher,
+ FileTreePopover,
GlButton,
},
mixins: [glFeatureFlagMixin()],
@@ -56,11 +58,13 @@ export default {
+
+import { GlPopover, GlOutsideDirective as Outside } from '@gitlab/ui';
+import { s__, __ } from '~/locale';
+import { FILE_TREE_POPOVER_DISMISSED_KEY } from '../../constants';
+
+export default {
+ name: 'PipelineEditorFileTreePopover',
+ directives: { Outside },
+ i18n: {
+ description: s__(
+ 'pipelineEditorWalkthrough|You can use the file tree to view your pipeline configuration files.',
+ ),
+ learnMore: __('Learn more'),
+ },
+ components: {
+ GlPopover,
+ },
+ data() {
+ return {
+ showPopover: false,
+ };
+ },
+ mounted() {
+ this.showPopover = localStorage.getItem(FILE_TREE_POPOVER_DISMISSED_KEY) !== 'true';
+ },
+ methods: {
+ closePopover() {
+ this.showPopover = false;
+ },
+ dismissPermanently() {
+ this.closePopover();
+ localStorage.setItem(FILE_TREE_POPOVER_DISMISSED_KEY, 'true');
+ },
+ },
+};
+
+
+
+
+
+
{{ $options.i18n.description }}
+
+
+
diff --git a/app/assets/javascripts/pipeline_editor/components/walkthrough_popover.vue b/app/assets/javascripts/pipeline_editor/components/popovers/walkthrough_popover.vue
similarity index 100%
rename from app/assets/javascripts/pipeline_editor/components/walkthrough_popover.vue
rename to app/assets/javascripts/pipeline_editor/components/popovers/walkthrough_popover.vue
diff --git a/app/assets/javascripts/pipeline_editor/constants.js b/app/assets/javascripts/pipeline_editor/constants.js
index 9b4732b26d2..0484da8641d 100644
--- a/app/assets/javascripts/pipeline_editor/constants.js
+++ b/app/assets/javascripts/pipeline_editor/constants.js
@@ -49,6 +49,9 @@ export const BRANCH_PAGINATION_LIMIT = 20;
export const BRANCH_SEARCH_DEBOUNCE = '500';
export const SOURCE_EDITOR_DEBOUNCE = 500;
+export const FILE_TREE_DISPLAY_KEY = 'pipeline_editor_file_tree_display';
+export const FILE_TREE_POPOVER_DISMISSED_KEY = 'pipeline_editor_file_tree_popover_dismissed';
+
export const STARTER_TEMPLATE_NAME = 'Getting-Started';
export const pipelineEditorTrackingOptions = {
diff --git a/app/assets/javascripts/pipeline_editor/pipeline_editor_app.vue b/app/assets/javascripts/pipeline_editor/pipeline_editor_app.vue
index 3c6d5b1d3ad..3fd31edec2c 100644
--- a/app/assets/javascripts/pipeline_editor/pipeline_editor_app.vue
+++ b/app/assets/javascripts/pipeline_editor/pipeline_editor_app.vue
@@ -382,7 +382,7 @@ export default {
-
+
[Introduced](https://gitlab.com/gitlab-org/gitlab/-/issues/299109) in GitLab 14.5.
+> - [Introduced](https://gitlab.com/gitlab-org/gitlab/-/issues/299109) in GitLab 14.5 as an **Alpha** release.
+> - [Changed](https://gitlab.com/gitlab-com/gl-infra/production/-/issues/6321) to a **Beta** release in GitLab 15.0.
-WARNING:
-`gitlab-sshd` is in [**Alpha**](../../policy/alpha-beta-support.md#alpha-features).
-It is not ready for production use.
+NOTE:
+`gitlab-sshd` is in [**Beta**](../../policy/alpha-beta-support.md#beta-features).
`gitlab-sshd` is [a standalone SSH server](https://gitlab.com/gitlab-org/gitlab-shell/-/tree/main/internal/sshd)
- written in Go. It is provided as a part of `gitlab-shell` package. It has a lower memory
- use as a OpenSSH alternative and supports
- [group access restriction by IP address](../../user/group/index.md) for applications
- running behind the proxy.
+written in Go. It is provided as a part of the `gitlab-shell` package. It has a lower memory
+use as a OpenSSH alternative, and supports
+[group access restriction by IP address](../../user/group/index.md) for applications
+running behind the proxy.
+
+`gitlab-sshd` is a lightweight alternative to OpenSSH for providing
+[SSH operations](https://gitlab.com/gitlab-org/gitlab-shell/-/blob/71a7f34a476f778e62f8fe7a453d632d395eaf8f/doc/features.md).
+While OpenSSH uses a restricted shell approach, `gitlab-sshd` behaves more like a
+modern multi-threaded server application, responding to incoming requests. The major
+difference is that OpenSSH uses SSH as a transport protocol while `gitlab-sshd` uses Remote Procedure Calls (RPCs).
+
+The capabilities of GitLab Shell are not limited to Git operations.
If you are considering switching from OpenSSH to `gitlab-sshd`, consider these concerns:
- The `gitlab-sshd` component is only available for
[Cloud Native Helm Charts](https://docs.gitlab.com/charts/) deployments.
- `gitlab-sshd` supports the PROXY protocol. It can run behind proxy servers that rely
- on it, such as HAProxy.
-- `gitlab-sshd` does not share a SSH port with the system administrator's OpenSSH,
- and requires a bind to port 22.
-- `gitlab-sshd` **does not** support SSH certificates.
+ on it, such as HAProxy. The PROXY protocol not enabled by default, but can be enabled with a Helm chart setting.
+- By default, `gitlab-sshd` binds to port 22, but you can configure a different port in the Helm chart.
+- `gitlab-sshd` **does not** support SSH certificates. For more details, read
+ [issue #495](https://gitlab.com/gitlab-org/gitlab-shell/-/issues/495).
To switch from OpenSSH to `gitlab-sshd`:
@@ -178,7 +184,11 @@ GitLab supports `authorized_keys` database lookups with [SELinux](https://en.wik
Because the SELinux policy is static, GitLab doesn't support the ability to change
internal webserver ports at the moment. Administrators would have to create a special `.te`
-file for the environment, since it isn't generated dynamically.
+file for the environment, as it isn't generated dynamically.
+
+### Additional documentation
+
+Additional technical documentation for `gitlab-sshd` may be found on the [GitLab Shell](https://gitlab.com/gitlab-org/gitlab-shell/-/blob/main/README.md) documentation page.
## Troubleshooting
@@ -187,7 +197,8 @@ or causing high CPU load, be sure to check the size of `/var/log/btmp`, and ensu
If this file is very large, GitLab SSH fast lookup can cause the bottleneck to be hit more frequently, thus decreasing performance even further.
If you are able to, you may consider disabling [`UsePAM` in your `sshd_config`](https://linux.die.net/man/5/sshd_config) to avoid reading `/var/log/btmp` altogether.
-Running `strace` and `lsof` on a running `sshd: git` process can return useful debugging information. To get an `strace` on an in-progress Git over SSH connection for IP `x.x.x.x`, run:
+Running `strace` and `lsof` on a running `sshd: git` process returns debugging information.
+To get an `strace` on an in-progress Git over SSH connection for IP `x.x.x.x`, run:
```plaintext
sudo strace -s 10000 -p $(sudo netstat -tp | grep x.x.x.x | egrep 'ssh.*: git' | sed -e 's/.*ESTABLISHED *//' -e 's#/.*##')
diff --git a/doc/administration/raketasks/storage.md b/doc/administration/raketasks/storage.md
index 912cf260a03..0b7bef5b9a0 100644
--- a/doc/administration/raketasks/storage.md
+++ b/doc/administration/raketasks/storage.md
@@ -169,3 +169,99 @@ Any error or warning is logged in Sidekiq's log file.
If you have a Geo setup, the rollback is not reflected automatically
on the **secondary** node. You may need to wait for a backfill operation to kick-in and remove
the remaining repositories from the special `@hashed/` folder manually.
+
+## Troubleshooting
+
+The Rake task might not be able to complete the migration to hashed storage.
+Checks on the instance will continue to report that there is legacy data:
+
+```plaintext
+* Found 1 projects using Legacy Storage
+- janedoe/testproject (id: 1234)
+```
+
+If you have a subscription, [raise a ticket with GitLab support](https://support.gitlab.com)
+as most of the fixes are relatively high risk, involving running code on the Rails console.
+
+### Read only projects
+
+If you have [set projects read only](../troubleshooting/gitlab_rails_cheat_sheet.md#make-a-project-read-only-can-only-be-done-in-the-console)
+they might fail to migrate.
+
+1. [Start a Rails console](../operations/rails_console.md#starting-a-rails-console-session).
+
+1. Check if the project is read only:
+
+ ```ruby
+ project = Project.find_by_full_path('janedoe/testproject')
+ project.repository_read_only
+ ```
+
+1. If it returns `true` (not `nil` or `false`), set it writable:
+
+ ```ruby
+ project.update!(repository_read_only: false)
+ ```
+
+1. [Re-run the migration Rake task](#migrate-to-hashed-storage).
+
+1. Set the project read-only again:
+
+ ```ruby
+ project.update!(repository_read_only: true)
+ ```
+
+### Projects pending deletion
+
+Check the project details in the admin area. If deleting the project failed
+it will show as `Marked For Deletion At ..`, `Scheduled Deletion At ..` and
+`pending removal`, but the dates will not be recent.
+
+Delete the project using the Rails console:
+
+1. [Start a Rails console](../operations/rails_console.md#starting-a-rails-console-session).
+
+1. With the following code, select the project to be deleted and account to action it:
+
+ ```ruby
+ project = Project.find_by_full_path('janedoe/testproject')
+ user = User.find_by_username('admin_handle')
+ puts "\nproject selected for deletion is:\nID: #{project.id}\nPATH: #{project.full_path}\nNAME: #{project.name}\n\n"
+ ```
+
+ - Replace `janedoe/testproject` with your project path from the Rake take output or from the admin area.
+ - Replace `admin_handle` with the handle of an instance administrator or with `root`.
+ - Verify the output before proceeding. **There are no other checks performed**.
+
+1. [Destroy the project](../troubleshooting/gitlab_rails_cheat_sheet.md#destroy-a-project) **immediately**:
+
+ ```ruby
+ Projects::DestroyService.new(project, user).execute
+ ```
+
+If destroying the project generates a stack trace relating to encryption or the error `OpenSSL::Cipher::CipherError`:
+
+1. [Verify your GitLab secrets](check.md#verify-database-values-can-be-decrypted-using-the-current-secrets).
+
+1. If the affected projects have secrets that cannot be decrypted it will be necessary to remove those specific secrets.
+ [Our documentation for dealing with lost secrets](../../raketasks/backup_restore.md#when-the-secrets-file-is-lost)
+ is for loss of all secrets, but it's possible for specific projects to be affected. For example,
+ to [reset specific runner registration tokens](../../raketasks/backup_restore.md#reset-runner-registration-tokens)
+ for a specific project ID:
+
+ ```sql
+ UPDATE projects SET runners_token = null, runners_token_encrypted = null where id = 1234;
+ ```
+
+### `Repository cannot be moved from` errors in Sidekiq log
+
+Projects might fail to migrate with errors in the Sidekiq log:
+
+```shell
+# grep 'Repository cannot be moved' /var/log/gitlab/sidekiq/current
+{"severity":"ERROR","time":"2021-02-29T02:29:02.021Z","message":"Repository cannot be moved from 'janedoe/testproject' to '@hashed' (PROJECT_ID=1234)"}
+```
+
+This might be caused by [a bug](https://gitlab.com/gitlab-org/gitlab/-/issues/259605) in the original code for hashed storage migration.
+
+[There is a workaround for projects still affected by this issue](https://gitlab.com/-/snippets/2039252).
diff --git a/doc/ci/environments/index.md b/doc/ci/environments/index.md
index e0777a27b19..bd7daaf3b26 100644
--- a/doc/ci/environments/index.md
+++ b/doc/ci/environments/index.md
@@ -652,7 +652,7 @@ To delete a stopped environment in the GitLab UI:
You can define a job that accesses an environment for various purposes, such as verification or preparation. This
effectively bypasses deployment creation, so that you can adjust your CD workflow more accurately.
-To do so, add either `action: prepare` or `action: verify` to the `environment` section of your job:
+To do so, add either `action: prepare`, `action: verify`, or `action: access` to the `environment` section of your job:
```yaml
build:
diff --git a/doc/ci/yaml/index.md b/doc/ci/yaml/index.md
index 3f8c17e9c11..f3906692f63 100644
--- a/doc/ci/yaml/index.md
+++ b/doc/ci/yaml/index.md
@@ -1559,7 +1559,7 @@ environment.
#### `environment:action`
-Use the `action` keyword to specify jobs that prepare, start, stop, or verify environments.
+Use the `action` keyword to specify how the job interacts with the environment.
**Keyword type**: Job keyword. You can use it only as part of a job.
@@ -1571,6 +1571,7 @@ Use the `action` keyword to specify jobs that prepare, start, stop, or verify en
| `prepare` | Indicates that the job is only preparing the environment. It does not trigger deployments. [Read more about preparing environments](../environments/index.md#access-an-environment-for-preparation-or-verification-purposes). |
| `stop` | Indicates that the job stops a deployment. For more detail, read [Stop an environment](../environments/index.md#stop-an-environment). |
| `verify` | Indicates that the job is only verifying the environment. It does not trigger deployments. [Read more about verifying environments](../environments/index.md#access-an-environment-for-preparation-or-verification-purposes). |
+| `access` | Indicates that the job is only accessing the environment. It does not trigger deployments. [Read more about accessing environments](../environments/index.md#access-an-environment-for-preparation-or-verification-purposes). |
**Example of `environment:action`**:
diff --git a/doc/development/feature_flags/index.md b/doc/development/feature_flags/index.md
index 33010c06b1e..be20b89653c 100644
--- a/doc/development/feature_flags/index.md
+++ b/doc/development/feature_flags/index.md
@@ -7,7 +7,7 @@ info: "See the Technical Writers assigned to Development Guidelines: https://abo
# Feature flags in the development of GitLab
-**NOTE**:
+NOTE:
The documentation below covers feature flags used by GitLab to deploy its own features, which **is not** the same
as the [feature flags offered as part of the product](../../operations/feature_flags.md).
@@ -451,6 +451,22 @@ Feature.enabled?(:a_feature, project) && Feature.disabled?(:a_feature_override,
/chatops run feature set --project=gitlab-org/gitlab a_feature_override true
```
+#### Use actors for verifying in production
+
+WARNING:
+Using production as a testing environment is not recommended. Use our testing
+environments for testing features that are not production-ready.
+
+While the staging environment provides a way to test features in an environment
+that resembles production, it doesn't allow you to compare before-and-after
+performance metrics specific to production environment. It can be useful to have a
+project in production with your development feature flag enabled, to allow tools
+like Sitespeed reports to reveal the metrics of the new code under a feature flag.
+
+This approach is even more useful if you're already tracking the old codebase in
+Sitespeed, enabling you to compare performance accurately before and after the
+feature flag's rollout.
+
### Enable additional objects as actors
To use feature gates based on actors, the model needs to respond to
diff --git a/doc/development/gitlab_flavored_markdown/specification_guide/index.md b/doc/development/gitlab_flavored_markdown/specification_guide/index.md
index c5e9b63aea3..d1de666cb0f 100644
--- a/doc/development/gitlab_flavored_markdown/specification_guide/index.md
+++ b/doc/development/gitlab_flavored_markdown/specification_guide/index.md
@@ -426,8 +426,11 @@ end
#### `update-example-snapshots.rb` script
-The `scripts/glfm/update-example-snapshots.rb` script uses the GLFM `spec.txt` specification
-file to update example snapshots:
+The `scripts/glfm/update-example-snapshots.rb` script uses the GLFM
+`glfm_specification/output/spec.txt` specification file and the
+`glfm_specification/input/gitlab_flavored_markdown/glfm_example_status.yml`
+file to create and update the [example snapshot](#example-snapshot-files)
+YAML files:
```mermaid
graph LR
@@ -436,6 +439,7 @@ subgraph script:
end
subgraph input:
input specification file
B[spec.txt] --> A
+ C[glfm_example_status.yml] --> A
end
subgraph output:
example snapshot files
A --> E[examples_index.yml]
diff --git a/doc/subscriptions/gitlab_dedicated/index.md b/doc/subscriptions/gitlab_dedicated/index.md
index 909a053fb1d..7c768d1b403 100644
--- a/doc/subscriptions/gitlab_dedicated/index.md
+++ b/doc/subscriptions/gitlab_dedicated/index.md
@@ -9,12 +9,12 @@ info: To determine the technical writer assigned to the Stage/Group associated w
NOTE:
GitLab Dedicated is currently in limited availability. Please [contact us](#contact-us) if you are interested.
-GitLab Dedicated is a fully isolated, single-tenant GitLab instance that is:
+GitLab Dedicated is a fully isolated, single-tenant SaaS service that is:
- Hosted and managed by GitLab, Inc.
-- Deployed in a region of choice in AWS.
+- Deployed in a region of choice on AWS.
-GitLab Dedicated enables you to offload the operational overhead of managing the DevOps Platform. It offers a high level of tenant isolation and deployment customization, ideal for enterprises in highly-regulated industries. By deploying your GitLab instance onto a separate Cloud Infrastructure from other tenants, GitLab Dedicated helps you better meet your security and compliance requirements.
+GitLab Dedicated enables you to offload the operational overhead of managing the DevOps Platform. It offers a high level of tenant isolation and deployment customization, ideal for enterprises in highly-regulated industries. By deploying your GitLab instance onto separate Cloud Infrastructure from other tenants, GitLab Dedicated helps you better meet your security and compliance requirements.
## Available features
@@ -25,9 +25,9 @@ GitLab Dedicated enables you to offload the operational overhead of managing the
You can specify an AWS IAM Principal and preferred Availability Zones during onboarding to enable this functionality.
- Upgrade strategy:
- Monthly upgrades tracking one release behind the latest (n-1), with the latest security release.
- - Out of band security patches provided for high severity items.
+ - Out of band security patches provided for high severity releases.
- Backup strategy: regular backups taken and tested.
-- Choice of Cloud Region: upon onboarding, choose the cloud region where you want to deploy your instance. Some AWS regions have limited features and as a result, we are not able to deploy production instances to those regions. See below for the [full list of regions](#aws-regions-not-supported) not currently supported.
+- Choice of cloud region: upon onboarding, choose the cloud region where you want to deploy your instance. Some AWS regions have limited features and as a result, we are not able to deploy production instances to those regions. See below for the [full list of regions](#aws-regions-not-supported) not currently supported.
- Security: Data encrypted at rest and in transit using latest encryption standards.
- Application: Self-managed [Ultimate feature set](https://about.gitlab.com/pricing/self-managed/feature-comparison/) with the exception of the unsupported features [listed below](#features-not-available-at-launch).
diff --git a/doc/subscriptions/index.md b/doc/subscriptions/index.md
index 824fe0bacde..58af5787f2b 100644
--- a/doc/subscriptions/index.md
+++ b/doc/subscriptions/index.md
@@ -30,6 +30,7 @@ GitLab SaaS or GitLab self-managed:
- [GitLab SaaS](gitlab_com/index.md): The GitLab software-as-a-service offering.
You don't need to install anything to use GitLab SaaS, you only need to
[sign up](https://gitlab.com/users/sign_up) and start using GitLab straight away.
+- [GitLab Dedicated](gitlab_dedicated/index.md): a single-tenant SaaS service for highly regulated and large enterprises.
- [GitLab self-managed](self_managed/index.md): Install, administer, and maintain
your own GitLab instance.
diff --git a/doc/update/deprecations.md b/doc/update/deprecations.md
index 37fa9f6e898..c69efe0ee81 100644
--- a/doc/update/deprecations.md
+++ b/doc/update/deprecations.md
@@ -61,6 +61,15 @@ be present during the 16.x cycle to avoid breaking the API signature, and will b
**Planned removal milestone: 16.0 (2023-05-22)**
+### Vulnerability Report sort by State
+
+The ability to sort the Vulnerability Report by the `State` column was disabled and put behind a feature flag in GitLab 14.10 due to a refactor
+of the underlying data model. The feature flag has remained off by default as further refactoring will be required to ensure sorting
+by this value remains performant. Due to very low usage of the `State` column for sorting, the feature flag will instead be removed in
+GitLab 15.2 to simplify the codebase and prevent any unwanted performance degradation.
+
+**Planned removal milestone: 15.2 (2022-07-22)**
+
## 14.10
### Dependency Scanning default Java version changed to 17
diff --git a/doc/update/index.md b/doc/update/index.md
index 5e82dbfcfd6..b3c5532aae6 100644
--- a/doc/update/index.md
+++ b/doc/update/index.md
@@ -630,6 +630,16 @@ for how to proceed.
### 14.0.0
+Prerequisites:
+
+- The [GitLab 14.0 release post contains several important notes](https://about.gitlab.com/releases/2021/06/22/gitlab-14-0-released/#upgrade)
+ about pre-requisites including [using Patroni instead of repmgr](../administration/postgresql/replication_and_failover.md#switching-from-repmgr-to-patroni),
+ migrating [to hashed storage](../administration/raketasks/storage.md#migrate-to-hashed-storage),
+ and [to Puma](../administration/operations/puma.md).
+- The support of PostgreSQL 11 [has been dropped](../install/requirements.md#database). Make sure to [update your database](https://docs.gitlab.com/omnibus/settings/database.html#upgrade-packaged-postgresql-server) to version 12 before updating to GitLab 14.0.
+
+Long running batched background database migrations:
+
- Database changes made by the upgrade to GitLab 14.0 can take hours or days to complete on larger GitLab instances.
These [batched background migrations](#batched-background-migrations) update whole database tables to mitigate primary key overflow and must be finished before upgrading to GitLab 14.2 or higher.
- Due to an issue where `BatchedBackgroundMigrationWorkers` were
@@ -650,13 +660,12 @@ for how to proceed.
See how to [resolve this error](../user/admin_area/monitoring/background_migrations.md#database-migrations-failing-because-of-batched-background-migration-not-finished).
+Other issues:
+
- In GitLab 13.3 some [pipeline processing methods were deprecated](https://gitlab.com/gitlab-org/gitlab/-/issues/218536)
and this code was completely removed in GitLab 14.0. If you plan to upgrade from
- **GitLab 13.2 or older** directly to 14.0 ([unsupported](#upgrading-to-a-new-major-version)), you should not have any pipelines running
- when you upgrade or the pipelines might report the wrong status when the upgrade completes.
+ **GitLab 13.2 or older** directly to 14.0, this is [unsupported](#upgrading-to-a-new-major-version).
You should instead follow a [supported upgrade path](#upgrade-paths).
-- The support of PostgreSQL 11 [has been dropped](../install/requirements.md#database). Make sure to [update your database](https://docs.gitlab.com/omnibus/settings/database.html#upgrade-packaged-postgresql-server) to version 12 before updating to GitLab 14.0.
-
- See [Maintenance mode issue in GitLab 13.9 to 14.4](#maintenance-mode-issue-in-gitlab-139-to-144).
- See [Custom Rack Attack initializers](#custom-rack-attack-initializers) if you persist your own custom Rack Attack
initializers during upgrades.
@@ -673,7 +682,16 @@ for how to proceed.
### 13.12.0
-See [Maintenance mode issue in GitLab 13.9 to 14.4](#maintenance-mode-issue-in-gitlab-139-to-144).
+- See [Maintenance mode issue in GitLab 13.9 to 14.4](#maintenance-mode-issue-in-gitlab-139-to-144).
+
+- Check the GitLab database [has no references to legacy storage](../administration/raketasks/storage.md#on-legacy-storage).
+ The GitLab 14.0 pre-install check will cause the package update to fail if there is unmigrated data:
+
+ ```plaintext
+ Checking for unmigrated data on legacy storage
+
+ Legacy storage is no longer supported. Please migrate your data to hashed storage.
+ ```
### 13.11.0
diff --git a/lib/gitlab/ci/config/entry/environment.rb b/lib/gitlab/ci/config/entry/environment.rb
index 7897b54a69f..bc39abfe977 100644
--- a/lib/gitlab/ci/config/entry/environment.rb
+++ b/lib/gitlab/ci/config/entry/environment.rb
@@ -44,7 +44,7 @@ module Gitlab
validates :action,
type: String,
- inclusion: { in: %w[start stop prepare verify], message: 'should be start, stop, prepare, or verify' },
+ inclusion: { in: %w[start stop prepare verify access], message: 'should be start, stop, prepare, verify, or access' },
allow_nil: true
validates :deployment_tier,
diff --git a/lib/gitlab/database/gitlab_schemas.yml b/lib/gitlab/database/gitlab_schemas.yml
index b1869574a2c..f769201464f 100644
--- a/lib/gitlab/database/gitlab_schemas.yml
+++ b/lib/gitlab/database/gitlab_schemas.yml
@@ -348,6 +348,7 @@ operations_strategies: :gitlab_main
operations_strategies_user_lists: :gitlab_main
operations_user_lists: :gitlab_main
packages_build_infos: :gitlab_main
+packages_cleanup_policies: :gitlab_main
packages_composer_cache_files: :gitlab_main
packages_composer_metadata: :gitlab_main
packages_conan_file_metadata: :gitlab_main
diff --git a/locale/gitlab.pot b/locale/gitlab.pot
index 14b9a67a672..a361fb733a2 100644
--- a/locale/gitlab.pot
+++ b/locale/gitlab.pot
@@ -17455,6 +17455,12 @@ msgstr ""
msgid "GitLabPages|Remove"
msgstr ""
+msgid "GitLabPages|Remove certificate"
+msgstr ""
+
+msgid "GitLabPages|Remove domain"
+msgstr ""
+
msgid "GitLabPages|Remove pages"
msgstr ""
@@ -35028,9 +35034,6 @@ msgstr ""
msgid "Set up new password"
msgstr ""
-msgid "Set up shared runner availability"
-msgstr ""
-
msgid "Set up your project to automatically push and/or pull changes to/from another repository. Branches, tags, and commits will be synced automatically."
msgstr ""
@@ -45710,6 +45713,9 @@ msgstr ""
msgid "pipelineEditorWalkthrough|Use the %{boldStart}commit changes%{boldEnd} button at the bottom of the page to run the pipeline."
msgstr ""
+msgid "pipelineEditorWalkthrough|You can use the file tree to view your pipeline configuration files."
+msgstr ""
+
msgid "pod_name can contain only lowercase letters, digits, '-', and '.' and must start and end with an alphanumeric character"
msgstr ""
diff --git a/qa/qa/page/project/pipeline_editor/show.rb b/qa/qa/page/project/pipeline_editor/show.rb
index 16bdbb54f95..78f16a8a65c 100644
--- a/qa/qa/page/project/pipeline_editor/show.rb
+++ b/qa/qa/page/project/pipeline_editor/show.rb
@@ -5,6 +5,10 @@ module QA
module Project
module PipelineEditor
class Show < QA::Page::Base
+ view 'app/assets/javascripts/pipeline_editor/pipeline_editor_app.vue' do
+ element :pipeline_editor_app, required: true
+ end
+
view 'app/assets/javascripts/pipeline_editor/components/file_nav/branch_switcher.vue' do
element :branch_selector_button, required: true
element :branch_menu_item_button
@@ -46,6 +50,21 @@ module QA
element :file_editor_container
end
+ view 'app/assets/javascripts/pipeline_editor/components/popovers/file_tree_popover.vue' do
+ element :file_tree_popover
+ end
+
+ def initialize
+ dismiss_file_tree_popover if has_element?(:file_tree_popover)
+
+ super
+ end
+
+ def dismiss_file_tree_popover
+ # clicking outside the popover will dismiss it
+ click_element(:pipeline_editor_app)
+ end
+
def open_branch_selector_dropdown
click_element(:branch_selector_button)
end
diff --git a/spec/controllers/groups_controller_spec.rb b/spec/controllers/groups_controller_spec.rb
index be30011905c..1a2c2b646a4 100644
--- a/spec/controllers/groups_controller_spec.rb
+++ b/spec/controllers/groups_controller_spec.rb
@@ -71,6 +71,17 @@ RSpec.describe GroupsController, factory_default: :keep do
context 'when the group is not importing' do
it_behaves_like 'details view'
+
+ it 'tracks page views', :snowplow do
+ subject
+
+ expect_snowplow_event(
+ category: 'group_overview',
+ action: 'render',
+ user: user,
+ namespace: group
+ )
+ end
end
context 'when the group is importing' do
@@ -81,6 +92,17 @@ RSpec.describe GroupsController, factory_default: :keep do
it 'redirects to the import status page' do
expect(subject).to redirect_to group_import_path(group)
end
+
+ it 'does not track page views', :snowplow do
+ subject
+
+ expect_no_snowplow_event(
+ category: 'group_overview',
+ action: 'render',
+ user: user,
+ namespace: group
+ )
+ end
end
end
diff --git a/spec/controllers/projects_controller_spec.rb b/spec/controllers/projects_controller_spec.rb
index 07bd198137a..f4f3530639b 100644
--- a/spec/controllers/projects_controller_spec.rb
+++ b/spec/controllers/projects_controller_spec.rb
@@ -309,6 +309,35 @@ RSpec.describe ProjectsController do
expect(response.body).to have_content('LICENSE') # would be 'MIT license' if stub not works
end
+ describe 'tracking events', :snowplow do
+ before do
+ allow(controller).to receive(:current_user).and_return(user)
+ get_show
+ end
+
+ it 'tracks page views' do
+ expect_snowplow_event(
+ category: 'project_overview',
+ action: 'render',
+ user: user,
+ project: public_project
+ )
+ end
+
+ context 'when the project is importing' do
+ let_it_be(:public_project) { create(:project, :public, :import_scheduled) }
+
+ it 'does not track page views' do
+ expect_no_snowplow_event(
+ category: 'project_overview',
+ action: 'render',
+ user: user,
+ project: public_project
+ )
+ end
+ end
+ end
+
describe "PUC highlighting" do
render_views
diff --git a/spec/factories/packages/cleanup/policies.rb b/spec/factories/packages/cleanup/policies.rb
new file mode 100644
index 00000000000..80baa2f78bd
--- /dev/null
+++ b/spec/factories/packages/cleanup/policies.rb
@@ -0,0 +1,16 @@
+# frozen_string_literal: true
+
+FactoryBot.define do
+ factory :packages_cleanup_policy, class: 'Packages::Cleanup::Policy' do
+ project
+
+ keep_n_duplicated_package_files { '10' }
+
+ trait :runnable do
+ after(:create) do |policy|
+ # next_run_at will be set before_save to Time.now + cadence, so this ensures the policy is active
+ policy.update_column(:next_run_at, Time.zone.now - 1.day)
+ end
+ end
+ end
+end
diff --git a/spec/features/groups/settings/ci_cd_spec.rb b/spec/features/groups/settings/ci_cd_spec.rb
index ceab9fc2384..50c481c115c 100644
--- a/spec/features/groups/settings/ci_cd_spec.rb
+++ b/spec/features/groups/settings/ci_cd_spec.rb
@@ -17,7 +17,7 @@ RSpec.describe 'Group CI/CD settings' do
end
describe 'Runners section' do
- let(:shared_runners_toggle) { page.find('[data-testid="enable-runners-toggle"]') }
+ let(:shared_runners_toggle) { page.find('[data-testid="shared-runners-toggle"]') }
before do
visit group_settings_ci_cd_path(group)
@@ -31,6 +31,16 @@ RSpec.describe 'Group CI/CD settings' do
it 'has "Enable shared runners for this group" toggle', :js do
expect(shared_runners_toggle).to have_content(_('Enable shared runners for this group'))
end
+
+ it 'clicks on toggle to enable setting', :js do
+ expect(group.shared_runners_setting).to be(Namespace::SR_ENABLED)
+
+ shared_runners_toggle.find('button').click
+ wait_for_requests
+
+ group.reload
+ expect(group.shared_runners_setting).to be(Namespace::SR_DISABLED_AND_UNOVERRIDABLE)
+ end
end
describe 'Auto DevOps form' do
diff --git a/spec/features/projects/ci/editor_spec.rb b/spec/features/projects/ci/editor_spec.rb
index 6fcdffeb801..2f960c09936 100644
--- a/spec/features/projects/ci/editor_spec.rb
+++ b/spec/features/projects/ci/editor_spec.rb
@@ -12,6 +12,8 @@ RSpec.describe 'Pipeline Editor', :js do
let(:other_branch) { 'test' }
before do
+ stub_feature_flags(pipeline_editor_file_tree: false)
+
sign_in(user)
project.add_developer(user)
@@ -22,11 +24,7 @@ RSpec.describe 'Pipeline Editor', :js do
wait_for_requests
end
- it 'user sees the Pipeline Editor page' do
- expect(page).to have_content('Pipeline Editor')
- end
-
- describe 'Branch switcher' do
+ shared_examples 'default branch switcher behavior' do
def switch_to_branch(branch)
find('[data-testid="branch-selector"]').click
@@ -68,6 +66,28 @@ RSpec.describe 'Pipeline Editor', :js do
end
end
+ it 'user sees the Pipeline Editor page' do
+ expect(page).to have_content('Pipeline Editor')
+ end
+
+ describe 'Branch Switcher (pipeline_editor_file_tree disabled)' do
+ it_behaves_like 'default branch switcher behavior'
+ end
+
+ describe 'Branch Switcher (pipeline_editor_file_tree enabled)' do
+ before do
+ stub_feature_flags(pipeline_editor_file_tree: true)
+
+ visit project_ci_pipeline_editor_path(project)
+ wait_for_requests
+
+ # close button for the popover
+ find('[data-testid="close-button"]').click
+ end
+
+ it_behaves_like 'default branch switcher behavior'
+ end
+
describe 'Editor navigation' do
context 'when no change is made' do
it 'user can navigate away without a browser alert' do
diff --git a/spec/frontend/group_settings/components/shared_runners_form_spec.js b/spec/frontend/group_settings/components/shared_runners_form_spec.js
index 26e9cd39cfd..70a22c86e62 100644
--- a/spec/frontend/group_settings/components/shared_runners_form_spec.js
+++ b/spec/frontend/group_settings/components/shared_runners_form_spec.js
@@ -1,47 +1,46 @@
-import { GlLoadingIcon, GlAlert } from '@gitlab/ui';
-import { shallowMount } from '@vue/test-utils';
+import { GlAlert } from '@gitlab/ui';
import MockAxiosAdapter from 'axios-mock-adapter';
import { nextTick } from 'vue';
+import { shallowMountExtended } from 'helpers/vue_test_utils_helper';
import waitForPromises from 'helpers/wait_for_promises';
import SharedRunnersForm from '~/group_settings/components/shared_runners_form.vue';
import axios from '~/lib/utils/axios_utils';
-const provide = {
- updatePath: '/test/update',
- sharedRunnersAvailability: 'enabled',
- parentSharedRunnersAvailability: null,
- runnerDisabled: 'disabled',
- runnerEnabled: 'enabled',
- runnerAllowOverride: 'allow_override',
-};
-
-jest.mock('~/flash');
+const UPDATE_PATH = '/test/update';
+const RUNNER_ENABLED_VALUE = 'enabled';
+const RUNNER_DISABLED_VALUE = 'disabled_and_unoverridable';
+const RUNNER_ALLOW_OVERRIDE_VALUE = 'disabled_with_override';
describe('group_settings/components/shared_runners_form', () => {
let wrapper;
let mock;
- const createComponent = (provides = {}) => {
- wrapper = shallowMount(SharedRunnersForm, {
+ const createComponent = (provide = {}) => {
+ wrapper = shallowMountExtended(SharedRunnersForm, {
provide: {
+ updatePath: UPDATE_PATH,
+ sharedRunnersSetting: RUNNER_ENABLED_VALUE,
+ parentSharedRunnersSetting: null,
+ runnerEnabledValue: RUNNER_ENABLED_VALUE,
+ runnerDisabledValue: RUNNER_DISABLED_VALUE,
+ runnerAllowOverrideValue: RUNNER_ALLOW_OVERRIDE_VALUE,
...provide,
- ...provides,
},
});
};
- const findLoadingIcon = () => wrapper.find(GlLoadingIcon);
- const findErrorAlert = () => wrapper.find(GlAlert);
- const findEnabledToggle = () => wrapper.find('[data-testid="enable-runners-toggle"]');
- const findOverrideToggle = () => wrapper.find('[data-testid="override-runners-toggle"]');
- const changeToggle = (toggle) => toggle.vm.$emit('change', !toggle.props('value'));
+ const findAlert = (variant) =>
+ wrapper
+ .findAllComponents(GlAlert)
+ .filter((w) => w.props('variant') === variant)
+ .at(0);
+ const findSharedRunnersToggle = () => wrapper.findByTestId('shared-runners-toggle');
+ const findOverrideToggle = () => wrapper.findByTestId('override-runners-toggle');
const getSharedRunnersSetting = () => JSON.parse(mock.history.put[0].data).shared_runners_setting;
- const isLoadingIconVisible = () => findLoadingIcon().exists();
beforeEach(() => {
mock = new MockAxiosAdapter(axios);
-
- mock.onPut(provide.updatePath).reply(200);
+ mock.onPut(UPDATE_PATH).reply(200);
});
afterEach(() => {
@@ -51,102 +50,122 @@ describe('group_settings/components/shared_runners_form', () => {
mock.restore();
});
- describe('with default', () => {
+ describe('default state', () => {
beforeEach(() => {
createComponent();
});
- it('loading icon does not exist', () => {
- expect(isLoadingIconVisible()).toBe(false);
+ it('"Enable shared runners" toggle is enabled', () => {
+ expect(findSharedRunnersToggle().props()).toMatchObject({
+ isLoading: false,
+ disabled: false,
+ });
});
- it('enabled toggle exists', () => {
- expect(findEnabledToggle().exists()).toBe(true);
- });
-
- it('override toggle does not exist', () => {
- expect(findOverrideToggle().exists()).toBe(false);
+ it('"Override the group setting" is disabled', () => {
+ expect(findOverrideToggle().props()).toMatchObject({
+ isLoading: false,
+ disabled: true,
+ });
});
});
- describe('loading icon', () => {
- it('shows and hides the loading icon on request', async () => {
+ describe('When group disabled shared runners', () => {
+ it(`toggles are not disabled with setting ${RUNNER_DISABLED_VALUE}`, () => {
+ createComponent({ sharedRunnersSetting: RUNNER_DISABLED_VALUE });
+
+ expect(findSharedRunnersToggle().props('disabled')).toBe(false);
+ expect(findOverrideToggle().props('disabled')).toBe(false);
+ });
+ });
+
+ describe('When parent group disabled shared runners', () => {
+ it('toggles are disabled', () => {
+ createComponent({
+ sharedRunnersSetting: RUNNER_DISABLED_VALUE,
+ parentSharedRunnersSetting: RUNNER_DISABLED_VALUE,
+ });
+
+ expect(findSharedRunnersToggle().props('disabled')).toBe(true);
+ expect(findOverrideToggle().props('disabled')).toBe(true);
+ expect(findAlert('warning').exists()).toBe(true);
+ });
+ });
+
+ describe('loading state', () => {
+ beforeEach(() => {
createComponent();
+ });
- expect(isLoadingIconVisible()).toBe(false);
-
- findEnabledToggle().vm.$emit('change', true);
+ it('is not loading by default', () => {
+ expect(findSharedRunnersToggle().props('isLoading')).toBe(false);
+ expect(findOverrideToggle().props('isLoading')).toBe(false);
+ });
+ it('is loading immediately after request', async () => {
+ findSharedRunnersToggle().vm.$emit('change', true);
await nextTick();
- expect(isLoadingIconVisible()).toBe(true);
+ expect(findSharedRunnersToggle().props('isLoading')).toBe(true);
+ expect(findOverrideToggle().props('isLoading')).toBe(true);
+ });
+ it('does not update settings while loading', async () => {
+ findSharedRunnersToggle().vm.$emit('change', true);
+ findSharedRunnersToggle().vm.$emit('change', false);
await waitForPromises();
- expect(isLoadingIconVisible()).toBe(false);
+ expect(mock.history.put.length).toBe(1);
+ });
+
+ it('is not loading state after completed request', async () => {
+ findSharedRunnersToggle().vm.$emit('change', true);
+ await waitForPromises();
+
+ expect(findSharedRunnersToggle().props('isLoading')).toBe(false);
+ expect(findOverrideToggle().props('isLoading')).toBe(false);
});
});
- describe('enable toggle', () => {
+ describe('"Enable shared runners" toggle', () => {
beforeEach(() => {
createComponent();
});
- it('enabling the toggle sends correct payload', async () => {
- findEnabledToggle().vm.$emit('change', true);
-
+ it('sends correct payload when turned on', async () => {
+ findSharedRunnersToggle().vm.$emit('change', true);
await waitForPromises();
- expect(getSharedRunnersSetting()).toEqual(provide.runnerEnabled);
- expect(findOverrideToggle().exists()).toBe(false);
+ expect(getSharedRunnersSetting()).toEqual(RUNNER_ENABLED_VALUE);
+ expect(findOverrideToggle().props('disabled')).toBe(true);
});
- it('disabling the toggle sends correct payload', async () => {
- findEnabledToggle().vm.$emit('change', false);
-
+ it('sends correct payload when turned off', async () => {
+ findSharedRunnersToggle().vm.$emit('change', false);
await waitForPromises();
- expect(getSharedRunnersSetting()).toEqual(provide.runnerDisabled);
- expect(findOverrideToggle().exists()).toBe(true);
+ expect(getSharedRunnersSetting()).toEqual(RUNNER_DISABLED_VALUE);
+ expect(findOverrideToggle().props('disabled')).toBe(false);
});
});
- describe('override toggle', () => {
+ describe('"Override the group setting" toggle', () => {
beforeEach(() => {
- createComponent({ sharedRunnersAvailability: provide.runnerAllowOverride });
+ createComponent({ sharedRunnersSetting: RUNNER_ALLOW_OVERRIDE_VALUE });
});
it('enabling the override toggle sends correct payload', async () => {
findOverrideToggle().vm.$emit('change', true);
-
await waitForPromises();
- expect(getSharedRunnersSetting()).toEqual(provide.runnerAllowOverride);
+ expect(getSharedRunnersSetting()).toEqual(RUNNER_ALLOW_OVERRIDE_VALUE);
});
it('disabling the override toggle sends correct payload', async () => {
findOverrideToggle().vm.$emit('change', false);
-
await waitForPromises();
- expect(getSharedRunnersSetting()).toEqual(provide.runnerDisabled);
- });
- });
-
- describe('toggle disabled state', () => {
- it(`toggles are not disabled with setting ${provide.runnerDisabled}`, () => {
- createComponent({ sharedRunnersAvailability: provide.runnerDisabled });
- expect(findEnabledToggle().props('disabled')).toBe(false);
- expect(findOverrideToggle().props('disabled')).toBe(false);
- });
-
- it('toggles are disabled', () => {
- createComponent({
- sharedRunnersAvailability: provide.runnerDisabled,
- parentSharedRunnersAvailability: provide.runnerDisabled,
- });
- expect(findEnabledToggle().props('disabled')).toBe(true);
- expect(findOverrideToggle().props('disabled')).toBe(true);
+ expect(getSharedRunnersSetting()).toEqual(RUNNER_DISABLED_VALUE);
});
});
@@ -156,16 +175,16 @@ describe('group_settings/components/shared_runners_form', () => {
${{ error: 'Undefined error' }} | ${'Undefined error Refresh the page and try again.'}
`(`with error $errorObj`, ({ errorObj, message }) => {
beforeEach(async () => {
- mock.onPut(provide.updatePath).reply(500, errorObj);
+ mock.onPut(UPDATE_PATH).reply(500, errorObj);
createComponent();
- changeToggle(findEnabledToggle());
+ findSharedRunnersToggle().vm.$emit('change', false);
await waitForPromises();
});
it('error should be shown', () => {
- expect(findErrorAlert().text()).toBe(message);
+ expect(findAlert('danger').text()).toBe(message);
});
});
});
diff --git a/spec/frontend/pipeline_editor/components/file-nav/pipeline_editor_file_nav_spec.js b/spec/frontend/pipeline_editor/components/file-nav/pipeline_editor_file_nav_spec.js
index cccea77a243..cbd9c86aabd 100644
--- a/spec/frontend/pipeline_editor/components/file-nav/pipeline_editor_file_nav_spec.js
+++ b/spec/frontend/pipeline_editor/components/file-nav/pipeline_editor_file_nav_spec.js
@@ -5,6 +5,7 @@ import { extendedWrapper } from 'helpers/vue_test_utils_helper';
import createMockApollo from 'helpers/mock_apollo_helper';
import BranchSwitcher from '~/pipeline_editor/components/file_nav/branch_switcher.vue';
import PipelineEditorFileNav from '~/pipeline_editor/components/file_nav/pipeline_editor_file_nav.vue';
+import FileTreePopover from '~/pipeline_editor/components/popovers/file_tree_popover.vue';
import getAppStatus from '~/pipeline_editor/graphql/queries/client/app_status.query.graphql';
import { EDITOR_APP_STATUS_EMPTY, EDITOR_APP_STATUS_VALID } from '~/pipeline_editor/constants';
@@ -47,6 +48,7 @@ describe('Pipeline editor file nav', () => {
const findBranchSwitcher = () => wrapper.findComponent(BranchSwitcher);
const findFileTreeBtn = () => wrapper.findByTestId('file-tree-toggle');
+ const findPopoverContainer = () => wrapper.findComponent(FileTreePopover);
afterEach(() => {
wrapper.destroy();
@@ -64,31 +66,47 @@ describe('Pipeline editor file nav', () => {
it('does not render the file tree button', () => {
expect(findFileTreeBtn().exists()).toBe(false);
});
+
+ it('does not render the file tree popover', () => {
+ expect(findPopoverContainer().exists()).toBe(false);
+ });
});
describe('with pipelineEditorFileTree feature flag ON', () => {
describe('when editor is in the empty state', () => {
- it('does not render the file tree button', () => {
+ beforeEach(() => {
createComponent({
appStatus: EDITOR_APP_STATUS_EMPTY,
isNewCiConfigFile: false,
pipelineEditorFileTree: true,
});
+ });
+ it('does not render the file tree button', () => {
expect(findFileTreeBtn().exists()).toBe(false);
});
+
+ it('does not render the file tree popover', () => {
+ expect(findPopoverContainer().exists()).toBe(false);
+ });
});
describe('when user is about to create their config file for the first time', () => {
- it('does not render the file tree button', () => {
+ beforeEach(() => {
createComponent({
appStatus: EDITOR_APP_STATUS_VALID,
isNewCiConfigFile: true,
pipelineEditorFileTree: true,
});
+ });
+ it('does not render the file tree button', () => {
expect(findFileTreeBtn().exists()).toBe(false);
});
+
+ it('does not render the file tree popover', () => {
+ expect(findPopoverContainer().exists()).toBe(false);
+ });
});
describe('when editor has a non-empty config file open', () => {
@@ -105,6 +123,10 @@ describe('Pipeline editor file nav', () => {
expect(findFileTreeBtn().props('icon')).toBe('file-tree');
});
+ it('renders the file tree popover', () => {
+ expect(findPopoverContainer().exists()).toBe(true);
+ });
+
it('file tree button emits toggle-file-tree event', () => {
expect(wrapper.emitted('toggle-file-tree')).toBe(undefined);
diff --git a/spec/frontend/pipeline_editor/components/pipeline_editor_tabs_spec.js b/spec/frontend/pipeline_editor/components/pipeline_editor_tabs_spec.js
index 6dffb7e5470..d159a20a8d6 100644
--- a/spec/frontend/pipeline_editor/components/pipeline_editor_tabs_spec.js
+++ b/spec/frontend/pipeline_editor/components/pipeline_editor_tabs_spec.js
@@ -3,7 +3,7 @@ import { shallowMount, mount } from '@vue/test-utils';
import Vue, { nextTick } from 'vue';
import setWindowLocation from 'helpers/set_window_location_helper';
import CiConfigMergedPreview from '~/pipeline_editor/components/editor/ci_config_merged_preview.vue';
-import WalkthroughPopover from '~/pipeline_editor/components/walkthrough_popover.vue';
+import WalkthroughPopover from '~/pipeline_editor/components/popovers/walkthrough_popover.vue';
import CiLint from '~/pipeline_editor/components/lint/ci_lint.vue';
import PipelineEditorTabs from '~/pipeline_editor/components/pipeline_editor_tabs.vue';
import EditorTab from '~/pipeline_editor/components/ui/editor_tab.vue';
diff --git a/spec/frontend/pipeline_editor/components/popovers/file_tree_popover_spec.js b/spec/frontend/pipeline_editor/components/popovers/file_tree_popover_spec.js
new file mode 100644
index 00000000000..28f3b68b3f7
--- /dev/null
+++ b/spec/frontend/pipeline_editor/components/popovers/file_tree_popover_spec.js
@@ -0,0 +1,44 @@
+import { nextTick } from 'vue';
+import { GlPopover } from '@gitlab/ui';
+import { shallowMount } from '@vue/test-utils';
+import FileTreePopover from '~/pipeline_editor/components/popovers/file_tree_popover.vue';
+import { FILE_TREE_POPOVER_DISMISSED_KEY } from '~/pipeline_editor/constants';
+
+describe('FileTreePopover component', () => {
+ let wrapper;
+
+ const findPopover = () => wrapper.findComponent(GlPopover);
+
+ const createComponent = (mountFn = shallowMount) => {
+ wrapper = mountFn(FileTreePopover);
+ };
+
+ afterEach(() => {
+ localStorage.clear();
+ wrapper.destroy();
+ });
+
+ describe('default', () => {
+ beforeEach(async () => {
+ createComponent();
+ });
+
+ it('renders dismissable popover', async () => {
+ expect(findPopover().exists()).toBe(true);
+
+ findPopover().vm.$emit('close-button-clicked');
+ await nextTick();
+
+ expect(findPopover().exists()).toBe(false);
+ });
+ });
+
+ describe('when popover has already been dismissed before', () => {
+ it('does not render popover', async () => {
+ localStorage.setItem(FILE_TREE_POPOVER_DISMISSED_KEY, 'true');
+ createComponent();
+
+ expect(findPopover().exists()).toBe(false);
+ });
+ });
+});
diff --git a/spec/frontend/pipeline_editor/components/walkthrough_popover_spec.js b/spec/frontend/pipeline_editor/components/popovers/walkthrough_popover_spec.js
similarity index 88%
rename from spec/frontend/pipeline_editor/components/walkthrough_popover_spec.js
rename to spec/frontend/pipeline_editor/components/popovers/walkthrough_popover_spec.js
index a9ce89ff521..8d172a8462a 100644
--- a/spec/frontend/pipeline_editor/components/walkthrough_popover_spec.js
+++ b/spec/frontend/pipeline_editor/components/popovers/walkthrough_popover_spec.js
@@ -1,6 +1,6 @@
import { mount, shallowMount } from '@vue/test-utils';
import Vue from 'vue';
-import WalkthroughPopover from '~/pipeline_editor/components/walkthrough_popover.vue';
+import WalkthroughPopover from '~/pipeline_editor/components/popovers/walkthrough_popover.vue';
import { extendedWrapper } from 'helpers/vue_test_utils_helper';
Vue.config.ignoredElements = ['gl-emoji'];
diff --git a/spec/frontend/pipeline_editor/pipeline_editor_home_spec.js b/spec/frontend/pipeline_editor/pipeline_editor_home_spec.js
index ed818cebcf9..bf0f7fd8c9f 100644
--- a/spec/frontend/pipeline_editor/pipeline_editor_home_spec.js
+++ b/spec/frontend/pipeline_editor/pipeline_editor_home_spec.js
@@ -10,7 +10,13 @@ import PipelineEditorFileTree from '~/pipeline_editor/components/file_tree/conta
import BranchSwitcher from '~/pipeline_editor/components/file_nav/branch_switcher.vue';
import PipelineEditorHeader from '~/pipeline_editor/components/header/pipeline_editor_header.vue';
import PipelineEditorTabs from '~/pipeline_editor/components/pipeline_editor_tabs.vue';
-import { MERGED_TAB, VISUALIZE_TAB, CREATE_TAB, LINT_TAB } from '~/pipeline_editor/constants';
+import {
+ MERGED_TAB,
+ VISUALIZE_TAB,
+ CREATE_TAB,
+ LINT_TAB,
+ FILE_TREE_DISPLAY_KEY,
+} from '~/pipeline_editor/constants';
import PipelineEditorHome from '~/pipeline_editor/pipeline_editor_home.vue';
import { mockLintResponse, mockCiYml } from './mock_data';
@@ -55,6 +61,7 @@ describe('Pipeline editor home wrapper', () => {
const findHelpBtn = () => wrapper.findByTestId('drawer-toggle');
afterEach(() => {
+ localStorage.clear();
wrapper.destroy();
});
@@ -252,34 +259,69 @@ describe('Pipeline editor home wrapper', () => {
});
describe('with pipelineEditorFileTree feature flag ON', () => {
- beforeEach(() => {
- createComponent({
- glFeatures: {
- pipelineEditorFileTree: true,
- },
- stubs: {
- GlButton,
- PipelineEditorFileNav,
- },
+ describe('button toggle', () => {
+ beforeEach(() => {
+ createComponent({
+ glFeatures: {
+ pipelineEditorFileTree: true,
+ },
+ stubs: {
+ GlButton,
+ PipelineEditorFileNav,
+ },
+ });
+ });
+
+ it('shows button toggle', () => {
+ expect(findFileTreeBtn().exists()).toBe(true);
+ });
+
+ it('toggles the drawer on button click', async () => {
+ await toggleFileTree();
+
+ expect(findPipelineEditorFileTree().exists()).toBe(true);
+
+ await toggleFileTree();
+
+ expect(findPipelineEditorFileTree().exists()).toBe(false);
+ });
+
+ it('sets the display state in local storage', async () => {
+ await toggleFileTree();
+
+ expect(localStorage.getItem(FILE_TREE_DISPLAY_KEY)).toBe('true');
+
+ await toggleFileTree();
+
+ expect(localStorage.getItem(FILE_TREE_DISPLAY_KEY)).toBe('false');
});
});
- it('shows button toggle', () => {
- expect(findFileTreeBtn().exists()).toBe(true);
+ describe('when file tree display state is saved in local storage', () => {
+ beforeEach(() => {
+ localStorage.setItem(FILE_TREE_DISPLAY_KEY, 'true');
+ createComponent({
+ glFeatures: { pipelineEditorFileTree: true },
+ stubs: { PipelineEditorFileNav },
+ });
+ });
+
+ it('shows the file tree by default', () => {
+ expect(findPipelineEditorFileTree().exists()).toBe(true);
+ });
});
- it('hides the file tree by default', () => {
- expect(findPipelineEditorFileTree().exists()).toBe(false);
- });
+ describe('when file tree display state is not saved in local storage', () => {
+ beforeEach(() => {
+ createComponent({
+ glFeatures: { pipelineEditorFileTree: true },
+ stubs: { PipelineEditorFileNav },
+ });
+ });
- it('toggles the drawer on button click', async () => {
- await toggleFileTree();
-
- expect(findPipelineEditorFileTree().exists()).toBe(true);
-
- await toggleFileTree();
-
- expect(findPipelineEditorFileTree().exists()).toBe(false);
+ it('hides the file tree by default', () => {
+ expect(findPipelineEditorFileTree().exists()).toBe(false);
+ });
});
});
});
diff --git a/spec/helpers/ci/runners_helper_spec.rb b/spec/helpers/ci/runners_helper_spec.rb
index 0046d481282..cf62579338f 100644
--- a/spec/helpers/ci/runners_helper_spec.rb
+++ b/spec/helpers/ci/runners_helper_spec.rb
@@ -99,17 +99,17 @@ RSpec.describe Ci::RunnersHelper do
let(:runner_constants) do
{
- runner_enabled: Namespace::SR_ENABLED,
- runner_disabled: Namespace::SR_DISABLED_AND_UNOVERRIDABLE,
- runner_allow_override: Namespace::SR_DISABLED_WITH_OVERRIDE
+ runner_enabled_value: Namespace::SR_ENABLED,
+ runner_disabled_value: Namespace::SR_DISABLED_AND_UNOVERRIDABLE,
+ runner_allow_override_value: Namespace::SR_DISABLED_WITH_OVERRIDE
}
end
it 'returns group data for top level group' do
result = {
update_path: "/api/v4/groups/#{parent.id}",
- shared_runners_availability: Namespace::SR_ENABLED,
- parent_shared_runners_availability: nil
+ shared_runners_setting: Namespace::SR_ENABLED,
+ parent_shared_runners_setting: nil
}.merge(runner_constants)
expect(helper.group_shared_runners_settings_data(parent)).to eq result
@@ -118,8 +118,8 @@ RSpec.describe Ci::RunnersHelper do
it 'returns group data for child group' do
result = {
update_path: "/api/v4/groups/#{group.id}",
- shared_runners_availability: Namespace::SR_DISABLED_AND_UNOVERRIDABLE,
- parent_shared_runners_availability: Namespace::SR_ENABLED
+ shared_runners_setting: Namespace::SR_DISABLED_AND_UNOVERRIDABLE,
+ parent_shared_runners_setting: Namespace::SR_ENABLED
}.merge(runner_constants)
expect(helper.group_shared_runners_settings_data(group)).to eq result
diff --git a/spec/lib/gitlab/ci/config/entry/environment_spec.rb b/spec/lib/gitlab/ci/config/entry/environment_spec.rb
index af3c157d857..36c26c8ee4f 100644
--- a/spec/lib/gitlab/ci/config/entry/environment_spec.rb
+++ b/spec/lib/gitlab/ci/config/entry/environment_spec.rb
@@ -92,35 +92,18 @@ RSpec.describe Gitlab::Ci::Config::Entry::Environment do
end
context 'when valid action is used' do
- let(:config) do
- { name: 'production',
- action: 'start' }
+ where(:action) do
+ %w(start stop prepare verify access)
end
- it 'is valid' do
- expect(entry).to be_valid
- end
- end
+ with_them do
+ let(:config) do
+ { name: 'production', action: action }
+ end
- context 'when prepare action is used' do
- let(:config) do
- { name: 'production',
- action: 'prepare' }
- end
-
- it 'is valid' do
- expect(entry).to be_valid
- end
- end
-
- context 'when verify action is used' do
- let(:config) do
- { name: 'production',
- action: 'verify' }
- end
-
- it 'is valid' do
- expect(entry).to be_valid
+ it 'is valid' do
+ expect(entry).to be_valid
+ end
end
end
@@ -159,7 +142,7 @@ RSpec.describe Gitlab::Ci::Config::Entry::Environment do
describe '#errors' do
it 'contains error about invalid action' do
expect(entry.errors)
- .to include 'environment action should be start, stop, prepare, or verify'
+ .to include 'environment action should be start, stop, prepare, verify, or access'
end
end
end
diff --git a/spec/lib/gitlab/ci/pipeline/seed/deployment_spec.rb b/spec/lib/gitlab/ci/pipeline/seed/deployment_spec.rb
index cb6a77ce37f..51185be3e74 100644
--- a/spec/lib/gitlab/ci/pipeline/seed/deployment_spec.rb
+++ b/spec/lib/gitlab/ci/pipeline/seed/deployment_spec.rb
@@ -90,42 +90,22 @@ RSpec.describe Gitlab::Ci::Pipeline::Seed::Deployment do
end
end
- context 'when job has environment attribute with stop action' do
- let(:attributes) do
- {
- environment: 'production',
- options: { environment: { name: 'production', action: 'stop' } }
- }
+ context 'when job does not start environment' do
+ where(:action) do
+ %w(stop prepare verify access)
end
- it 'returns nothing' do
- is_expected.to be_nil
- end
- end
+ with_them do
+ let(:attributes) do
+ {
+ environment: 'production',
+ options: { environment: { name: 'production', action: action } }
+ }
+ end
- context 'when job has environment attribute with prepare action' do
- let(:attributes) do
- {
- environment: 'production',
- options: { environment: { name: 'production', action: 'prepare' } }
- }
- end
-
- it 'returns nothing' do
- is_expected.to be_nil
- end
- end
-
- context 'when job has environment attribute with verify action' do
- let(:attributes) do
- {
- environment: 'production',
- options: { environment: { name: 'production', action: 'verify' } }
- }
- end
-
- it 'returns nothing' do
- is_expected.to be_nil
+ it 'returns nothing' do
+ is_expected.to be_nil
+ end
end
end
diff --git a/spec/lib/gitlab/import_export/all_models.yml b/spec/lib/gitlab/import_export/all_models.yml
index 730f9035293..1546b6a26c8 100644
--- a/spec/lib/gitlab/import_export/all_models.yml
+++ b/spec/lib/gitlab/import_export/all_models.yml
@@ -550,6 +550,7 @@ project:
- project_registry
- packages
- package_files
+- packages_cleanup_policy
- tracing_setting
- alerting_setting
- project_setting
diff --git a/spec/models/concerns/schedulable_spec.rb b/spec/models/concerns/schedulable_spec.rb
index 62acd12e267..b98dcf1c174 100644
--- a/spec/models/concerns/schedulable_spec.rb
+++ b/spec/models/concerns/schedulable_spec.rb
@@ -57,6 +57,16 @@ RSpec.describe Schedulable do
it_behaves_like '.runnable_schedules'
end
+ context 'for a packages cleanup policy' do
+ # let! is used to reset the next_run_at value before each spec
+ let(:object) { create(:packages_cleanup_policy, :runnable) }
+ let(:non_runnable_object) { create(:packages_cleanup_policy) }
+
+ it_behaves_like '#schedule_next_run!'
+ it_behaves_like 'before_save callback'
+ it_behaves_like '.runnable_schedules'
+ end
+
describe '#next_run_at' do
let(:schedulable_instance) do
Class.new(ActiveRecord::Base) do
diff --git a/spec/models/packages/cleanup/policy_spec.rb b/spec/models/packages/cleanup/policy_spec.rb
new file mode 100644
index 00000000000..972071aa0ad
--- /dev/null
+++ b/spec/models/packages/cleanup/policy_spec.rb
@@ -0,0 +1,28 @@
+# frozen_string_literal: true
+
+require 'spec_helper'
+
+RSpec.describe Packages::Cleanup::Policy, type: :model do
+ describe 'relationships' do
+ it { is_expected.to belong_to(:project) }
+ end
+
+ describe 'validations' do
+ it { is_expected.to validate_presence_of(:project) }
+ it do
+ is_expected
+ .to validate_inclusion_of(:keep_n_duplicated_package_files)
+ .in_array(described_class::KEEP_N_DUPLICATED_PACKAGE_FILES_VALUES)
+ .with_message('keep_n_duplicated_package_files is invalid')
+ end
+ end
+
+ describe '.active' do
+ let_it_be(:active_policy) { create(:packages_cleanup_policy) }
+ let_it_be(:inactive_policy) { create(:packages_cleanup_policy, keep_n_duplicated_package_files: 'all') }
+
+ subject { described_class.active }
+
+ it { is_expected.to contain_exactly(active_policy) }
+ end
+end
diff --git a/spec/models/project_spec.rb b/spec/models/project_spec.rb
index 0bb584845c2..6066822f4f1 100644
--- a/spec/models/project_spec.rb
+++ b/spec/models/project_spec.rb
@@ -135,6 +135,7 @@ RSpec.describe Project, factory_default: :keep do
it { is_expected.to have_many(:packages).class_name('Packages::Package') }
it { is_expected.to have_many(:package_files).class_name('Packages::PackageFile') }
it { is_expected.to have_many(:debian_distributions).class_name('Packages::Debian::ProjectDistribution').dependent(:destroy) }
+ it { is_expected.to have_one(:packages_cleanup_policy).class_name('Packages::Cleanup::Policy').inverse_of(:project) }
it { is_expected.to have_many(:pipeline_artifacts).dependent(:restrict_with_error) }
it { is_expected.to have_many(:terraform_states).class_name('Terraform::State').inverse_of(:project) }
it { is_expected.to have_many(:timelogs) }
diff --git a/spec/support/shared_examples/features/wiki/user_creates_wiki_page_shared_examples.rb b/spec/support/shared_examples/features/wiki/user_creates_wiki_page_shared_examples.rb
index 41b1964cff0..8081c51577a 100644
--- a/spec/support/shared_examples/features/wiki/user_creates_wiki_page_shared_examples.rb
+++ b/spec/support/shared_examples/features/wiki/user_creates_wiki_page_shared_examples.rb
@@ -233,6 +233,23 @@ RSpec.shared_examples 'User creates wiki page' do
.and have_content("Last edited by #{user.name}")
.and have_content("My awesome wiki!")
end
+
+ context 'when a server side validation error is returned' do
+ it "still displays edit form", :js do
+ click_link("New page")
+
+ page.within(".wiki-form") do
+ fill_in(:wiki_title, with: "home")
+ fill_in(:wiki_content, with: "My awesome home page!")
+ end
+
+ # Submits page with a name already in use to trigger a validation error
+ click_button("Create page")
+
+ expect(page).to have_field(:wiki_title)
+ expect(page).to have_field(:wiki_content)
+ end
+ end
end
it "shows the emoji autocompletion dropdown", :js do