diff --git a/.gitlab/issue_templates/Geo Replicate a new Git repository type.md b/.gitlab/issue_templates/Geo Replicate a new Git repository type.md
index b39356e74bd..bfcf7aca7b5 100644
--- a/.gitlab/issue_templates/Geo Replicate a new Git repository type.md
+++ b/.gitlab/issue_templates/Geo Replicate a new Git repository type.md
@@ -303,12 +303,6 @@ That's all of the required database changes.
git_access_class.error_message(:no_repo)
end
- # The feature flag follows the format `geo_#{replicable_name}_replication`,
- # so here it would be `geo_cool_widget_replication`
- def self.replication_enabled_by_default?
- false
- end
-
override :verification_feature_flag_enabled?
def self.verification_feature_flag_enabled?
# We are adding verification at the same time as replication, so we
@@ -715,27 +709,6 @@ As illustrated by the above two examples, batch destroy logic cannot be handled
- [ ] Add a step to `Test replication and verification of Cool Widgets on a non-GDK-deployment. For example, using GitLab Environment Toolkit`.
- [ ] Add a step to `Ping the Geo PM and EM to coordinate testing`. For example, you might add steps to generate Cool Widgets, and then a Geo engineer may take it from there.
- [ ] In `ee/config/feature_flags/development/geo_cool_widget_replication.yml`, set `default_enabled: true`
-
-- [ ] In `ee/app/replicators/geo/cool_widget_replicator.rb`, delete the `self.replication_enabled_by_default?` method:
-
- ```ruby
- module Geo
- class CoolWidgetReplicator < Gitlab::Geo::Replicator
- ...
- # REMOVE THIS LINE IF IT IS NO LONGER NEEDED
- extend ::Gitlab::Utils::Override
-
- # REMOVE THIS METHOD
- def self.replication_enabled_by_default?
- false
- end
- # REMOVE THIS METHOD
-
- ...
- end
- end
- ```
-
- [ ] In `ee/app/graphql/types/geo/geo_node_type.rb`, remove the `feature_flag` option for the released type:
```ruby
diff --git a/.gitlab/issue_templates/Geo Replicate a new blob type.md b/.gitlab/issue_templates/Geo Replicate a new blob type.md
index d005e0cdf43..ff678666191 100644
--- a/.gitlab/issue_templates/Geo Replicate a new blob type.md
+++ b/.gitlab/issue_templates/Geo Replicate a new blob type.md
@@ -291,12 +291,6 @@ That's all of the required database changes.
model_record.file
end
- # The feature flag follows the format `geo_#{replicable_name}_replication`,
- # so here it would be `geo_cool_widget_replication`
- def self.replication_enabled_by_default?
- false
- end
-
override :verification_feature_flag_enabled?
def self.verification_feature_flag_enabled?
# We are adding verification at the same time as replication, so we
@@ -680,28 +674,6 @@ As illustrated by the above two examples, batch destroy logic cannot be handled
- [ ] Add a step to `Test replication and verification of Cool Widgets on a non-GDK-deployment. For example, using GitLab Environment Toolkit`.
- [ ] Add a step to `Ping the Geo PM and EM to coordinate testing`. For example, you might add steps to generate Cool Widgets, and then a Geo engineer may take it from there.
- [ ] In `ee/config/feature_flags/development/geo_cool_widget_replication.yml`, set `default_enabled: true`
-
-- [ ] In `ee/app/replicators/geo/cool_widget_replicator.rb`, delete the `self.replication_enabled_by_default?` method:
-
- ```ruby
- module Geo
- class CoolWidgetReplicator < Gitlab::Geo::Replicator
- ...
- # REMOVE THIS LINE IF IT IS NO LONGER NEEDED
- extend ::Gitlab::Utils::Override
-
- ...
- # REMOVE THIS METHOD
- def self.replication_enabled_by_default?
- false
- end
- # REMOVE THIS METHOD
-
- ...
- end
- end
- ```
-
- [ ] In `ee/app/graphql/types/geo/geo_node_type.rb`, remove the `feature_flag` option for the released type:
```ruby
diff --git a/.gitlab/merge_request_templates/Documentation.md b/.gitlab/merge_request_templates/Documentation.md
index 49d1d0f79bf..cdc33b8aacb 100644
--- a/.gitlab/merge_request_templates/Documentation.md
+++ b/.gitlab/merge_request_templates/Documentation.md
@@ -8,7 +8,7 @@
## Author's checklist
-- [ ] Optional. Consider taking [the GitLab Technical Writing Fundamentals course](https://gitlab.edcast.com/pathways/ECL-02528ee2-c334-4e16-abf3-e9d8b8260de4).
+- [ ] Optional. Consider taking [the GitLab Technical Writing Fundamentals course](https://about.gitlab.com/handbook/engineering/ux/technical-writing/fundamentals/).
- [ ] Follow the:
- [Documentation process](https://docs.gitlab.com/ee/development/documentation/workflow.html).
- [Documentation guidelines](https://docs.gitlab.com/ee/development/documentation/).
diff --git a/app/assets/javascripts/clusters_list/components/agent_table.vue b/app/assets/javascripts/clusters_list/components/agent_table.vue
index b878a6835b2..496baf8cb08 100644
--- a/app/assets/javascripts/clusters_list/components/agent_table.vue
+++ b/app/assets/javascripts/clusters_list/components/agent_table.vue
@@ -39,7 +39,7 @@ export default {
configHelpLink: helpPagePath('user/clusters/agent/install/index', {
anchor: 'create-an-agent-configuration-file',
}),
- inject: ['gitlabVersion'],
+ inject: ['gitlabVersion', 'kasVersion'],
props: {
agents: {
required: true,
@@ -102,6 +102,9 @@ export default {
return { ...agent, versions };
});
},
+ serverVersion() {
+ return this.kasVersion || this.gitlabVersion;
+ },
},
methods: {
getStatusCellId(item) {
@@ -135,12 +138,12 @@ export default {
if (!agent.versions.length) return false;
const [agentMajorVersion, agentMinorVersion] = this.getAgentVersionString(agent).split('.');
- const [gitlabMajorVersion, gitlabMinorVersion] = this.gitlabVersion.split('.');
+ const [serverMajorVersion, serverMinorVersion] = this.serverVersion.split('.');
- const majorVersionMismatch = agentMajorVersion !== gitlabMajorVersion;
+ const majorVersionMismatch = agentMajorVersion !== serverMajorVersion;
// We should warn user if their current GitLab and agent versions are more than 1 minor version apart:
- const minorVersionMismatch = Math.abs(agentMinorVersion - gitlabMinorVersion) > 1;
+ const minorVersionMismatch = Math.abs(agentMinorVersion - serverMinorVersion) > 1;
return majorVersionMismatch || minorVersionMismatch;
},
@@ -240,7 +243,7 @@ export default {
- {{ gitlabVersion }}
+ {{ serverVersion }}
{{ $options.i18n.viewDocsText }}
- {{ gitlabVersion }}
+ {{ serverVersion }}
{{ $options.i18n.viewDocsText }} {
canAddCluster,
canAdminCluster,
gitlabVersion,
+ kasVersion,
displayClusterAgents,
certificateBasedClustersEnabled,
} = el.dataset;
@@ -48,6 +49,7 @@ export default () => {
canAddCluster: parseBoolean(canAddCluster),
canAdminCluster: parseBoolean(canAdminCluster),
gitlabVersion,
+ kasVersion,
displayClusterAgents: parseBoolean(displayClusterAgents),
certificateBasedClustersEnabled: parseBoolean(certificateBasedClustersEnabled),
},
diff --git a/app/assets/javascripts/diffs/components/diff_line_note_form.vue b/app/assets/javascripts/diffs/components/diff_line_note_form.vue
index 7a30740e31b..a2f0e2c2653 100644
--- a/app/assets/javascripts/diffs/components/diff_line_note_form.vue
+++ b/app/assets/javascripts/diffs/components/diff_line_note_form.vue
@@ -1,6 +1,6 @@
@@ -253,6 +262,9 @@ export default {
+
+ {{ $options.i18n.bridgeBadgeText }}
+
`db:test:load:geo`
- `geo:db:test:purge` -> `db:test:purge:geo`
+### Elasticsearch 6.8.x in GitLab 15.0
+
+WARNING:
+This feature was changed or removed in 15.0
+as a [breaking change](https://docs.gitlab.com/ee/development/contributing/#breaking-changes).
+Before updating GitLab, review the details carefully to determine if you need to make any
+changes to your code, settings, or workflow.
+
+Elasticsearch 6.8 support has been removed in GitLab 15.0. Elasticsearch 6.8 has reached [end of life](https://www.elastic.co/support/eol).
+If you use Elasticsearch 6.8, **you must upgrade your Elasticsearch version to 7.x** prior to upgrading to GitLab 15.0.
+You should not upgrade to Elasticsearch 8 until you have completed the GitLab 15.0 upgrade.
+
+View the [version requirements](https://docs.gitlab.com/ee/integration/elasticsearch.html#version-requirements) for details.
+
### GitLab Serverless
WARNING:
diff --git a/doc/user/application_security/cluster_image_scanning/index.md b/doc/user/application_security/cluster_image_scanning/index.md
index 97fcd2cdcff..aba28a5ca89 100644
--- a/doc/user/application_security/cluster_image_scanning/index.md
+++ b/doc/user/application_security/cluster_image_scanning/index.md
@@ -292,14 +292,12 @@ scan images from within your Kubernetes cluster and record the vulnerabilities i
### Prerequisites
-- [Starboard Operator](https://aquasecurity.github.io/starboard/v0.10.3/operator/installation/kubectl/)
- installed and configured in your cluster.
- [GitLab agent](../../clusters/agent/install/index.md)
set up in GitLab, installed in your cluster, and configured using a configuration repository.
### Configuration
-The agent runs the cluster image scanning once the `cluster_image_scanning`
+The agent runs the cluster image scanning once the `starboard`
directive is added to your [agent's configuration repository](../../clusters/agent/vulnerabilities.md).
## Security Dashboard
diff --git a/doc/user/application_security/dependency_scanning/index.md b/doc/user/application_security/dependency_scanning/index.md
index 909f1353221..4ec6b0ccf3d 100644
--- a/doc/user/application_security/dependency_scanning/index.md
+++ b/doc/user/application_security/dependency_scanning/index.md
@@ -244,7 +244,7 @@ table.supported-languages ul {
Python |
- 3.6 |
+ 3.9 |
setuptools |
setup.py |
Gemnasium |
@@ -915,9 +915,9 @@ import the following default dependency scanning analyzer images from `registry.
your [local Docker container registry](../../packages/container_registry/index.md):
```plaintext
-registry.gitlab.com/security-products/gemnasium:2
-registry.gitlab.com/security-products/gemnasium-maven:2
-registry.gitlab.com/security-products/gemnasium-python:2
+registry.gitlab.com/security-products/gemnasium:3
+registry.gitlab.com/security-products/gemnasium-maven:3
+registry.gitlab.com/security-products/gemnasium-python:3
```
The process for importing Docker images into a local offline Docker registry depends on
@@ -1219,5 +1219,4 @@ To work around this error, downgrade the analyzer's version of `setuptools` (e.g
gemnasium-python-dependency_scanning:
before_script:
- pip install setuptools==57.5.0
- image: registry.gitlab.com/gitlab-org/security-products/analyzers/gemnasium-python:2-python-3.9
```
diff --git a/doc/user/application_security/policies/scan-execution-policies.md b/doc/user/application_security/policies/scan-execution-policies.md
index 168c76ab710..9f68d2002b8 100644
--- a/doc/user/application_security/policies/scan-execution-policies.md
+++ b/doc/user/application_security/policies/scan-execution-policies.md
@@ -87,6 +87,13 @@ This rule enforces the defined actions and schedules a scan on the provided date
| `cadence` | `string` | CRON expression (for example, `0 0 * * *`) | A whitespace-separated string containing five fields that represents the scheduled time. |
| `clusters` | `object` | | The cluster where the given policy enforces running selected scans (only for `container_scanning`/`cluster_image_scanning` scans). The key of the object is the name of the Kubernetes cluster configured for your project in GitLab. In the optionally provided value of the object, you can precisely select Kubernetes resources that are scanned. |
+GitLab supports the following types of CRON syntax for the `cadence` field:
+
+- A daily cadence of once per hour at a specified hour, for example: `0 18 * * *`
+- A weekly cadence of once per week on a specified day and at a specified hour, for example: `0 13 * * 0`
+
+It is possible that other elements of the CRON syntax will work in the cadence field, however, GitLab does not officially test or support them.
+
### `cluster` schema
Use this schema to define `clusters` objects in the [`schedule` rule type](#schedule-rule-type).
diff --git a/doc/user/clusters/agent/troubleshooting.md b/doc/user/clusters/agent/troubleshooting.md
index c5c7e46c078..0932e9179f9 100644
--- a/doc/user/clusters/agent/troubleshooting.md
+++ b/doc/user/clusters/agent/troubleshooting.md
@@ -11,7 +11,7 @@ When you are using the GitLab agent for Kubernetes, you might experience issues
You can start by viewing the service logs:
```shell
-kubectl logs -f -l=app=gitlab-agent -n gitlab-kubernetes-agent
+kubectl logs -f -l=app=gitlab-agent -n gitlab-agent
```
If you are a GitLab administrator, you can also view the [GitLab agent server logs](../../../administration/clusters/kas.md#troubleshooting).
@@ -113,14 +113,14 @@ will be picked up automatically.
For example, if your internal CA certificate is `myCA.pem`:
```plaintext
-kubectl -n gitlab-kubernetes-agent create configmap ca-pemstore --from-file=myCA.pem
+kubectl -n gitlab-agent create configmap ca-pemstore --from-file=myCA.pem
```
Then in `resources.yml`:
```yaml
spec:
- serviceAccountName: gitlab-kubernetes-agent
+ serviceAccountName: gitlab-agent
containers:
- name: agent
image: "registry.gitlab.com/gitlab-org/cluster-integration/gitlab-agent/agentk:"
@@ -140,7 +140,7 @@ Then in `resources.yml`:
volumes:
- name: token-volume
secret:
- secretName: gitlab-kubernetes-agent-token
+ secretName: gitlab-agent-token
- name: ca-pemstore-volume
configMap:
name: ca-pemstore
diff --git a/doc/user/clusters/agent/vulnerabilities.md b/doc/user/clusters/agent/vulnerabilities.md
index 480b09ff2ab..69f5b1d9063 100644
--- a/doc/user/clusters/agent/vulnerabilities.md
+++ b/doc/user/clusters/agent/vulnerabilities.md
@@ -34,80 +34,34 @@ You can use [cluster image scanning](../../application_security/cluster_image_sc
to scan container images in your cluster for security vulnerabilities.
To begin scanning all resources in your cluster, add a `starboard`
-configuration block to your agent configuration file with no `filters`:
+configuration block to your agent configuration with a `cadence` field
+containing a CRON expression for when the scans will be run.
```yaml
starboard:
vulnerability_report:
- filters: []
+ cadence: '0 0 * * *' # Daily at 00:00 (Kubernetes cluster time)
```
-The namespaces that are able to be scanned depend on the [Starboard Operator install mode](https://aquasecurity.github.io/starboard/latest/operator/configuration/#install-modes).
-By default, the Starboard Operator only scans resources in the `default` namespace. To change this
-behavior, edit the `STARBOARD_OPERATOR` environment variable in the `starboard-operator` deployment
-definition.
+The `cadence` field is required. GitLab supports the following types of CRON syntax for the cadence field:
-By adding filters, you can limit scans by:
+- A daily cadence of once per hour at a specified hour, for example: `0 18 * * *`
+- A weekly cadence of once per week on a specified day and at a specified hour, for example: `0 13 * * 0`
-- Resource name
-- Kind
-- Container name
-- Namespace
-
-```yaml
-starboard:
- vulnerability_report:
- filters:
- - namespaces:
- - staging
- - production
- kinds:
- - Deployment
- - DaemonSet
- containers:
- - ruby
- - postgres
- - nginx
- resources:
- - my-app-name
- - postgres
- - ingress-nginx
-```
-
-A resource is scanned if the resource matches any of the given names and all of the given filter
-types (`namespaces`, `kinds`, `containers`, `resources`). If a filter type is omitted, then all
-names are scanned. In this example, a resource isn't scanned unless it has a container named `ruby`,
-`postgres`, or `nginx`, and it's a `Deployment`:
-
-```yaml
-starboard:
- vulnerability_report:
- filters:
- - kinds:
- - Deployment
- containers:
- - ruby
- - postgres
- - nginx
-```
-
-There is also a global `namespaces` field that applies to all filters:
+It is possible that other elements of the CRON syntax will work in the cadence field, however, GitLab does not officially test or support them.
+
+By default, cluster image scanning will attempt to scan the workloads in all
+namespaces for vulnerabilities. The `vulnerability_report` block has a `namespaces`
+field which can be used to restrict which namespaces are scanned. For example,
+if you would like to scan only the `development`, `staging`, and `production`
+namespaces, you can use this configuration:
```yaml
starboard:
vulnerability_report:
+ cadence: '0 0 * * *'
namespaces:
- - production
- filters:
- - kinds:
- - Deployment
- - kinds:
- - DaemonSet
- resources:
- - log-collector
+ - development
+ - staging
+ - production
```
-
-In this example, the following resources are scanned:
-
-- All deployments (`Deployment`) in the `production` namespace.
-- All daemon sets (`DaemonSet`) named `log-collector` in the `production` namespace.
diff --git a/lefthook.yml b/lefthook.yml
index 8bfa5d015c7..ff57725dac6 100644
--- a/lefthook.yml
+++ b/lefthook.yml
@@ -31,7 +31,7 @@ pre-push:
rubocop:
tags: backend style
files: git diff --name-only --diff-filter=d $(git merge-base origin/master HEAD)..HEAD
- glob: '*.rb'
+ glob: '*.{rb,rake}'
run: REVEAL_RUBOCOP_TODO=0 bundle exec rubocop --parallel --force-exclusion {files}
graphql_docs:
tags: documentation
diff --git a/lib/gitlab/ci/templates/Jobs/Dependency-Scanning.gitlab-ci.yml b/lib/gitlab/ci/templates/Jobs/Dependency-Scanning.gitlab-ci.yml
index f56e017796a..6dd55d00ae9 100644
--- a/lib/gitlab/ci/templates/Jobs/Dependency-Scanning.gitlab-ci.yml
+++ b/lib/gitlab/ci/templates/Jobs/Dependency-Scanning.gitlab-ci.yml
@@ -14,7 +14,7 @@ variables:
SECURE_ANALYZERS_PREFIX: "registry.gitlab.com/security-products"
DS_EXCLUDED_ANALYZERS: ""
DS_EXCLUDED_PATHS: "spec, test, tests, tmp"
- DS_MAJOR_VERSION: 2
+ DS_MAJOR_VERSION: 3
dependency_scanning:
stage: test
@@ -82,9 +82,6 @@ gemnasium-maven-dependency_scanning:
- .cyclone-dx-reports
variables:
DS_ANALYZER_NAME: "gemnasium-maven"
- # Stop reporting Gradle as "maven".
- # See https://gitlab.com/gitlab-org/gitlab/-/issues/338252
- DS_REPORT_PACKAGE_MANAGER_MAVEN_WHEN_JAVA: "false"
rules:
- if: $DEPENDENCY_SCANNING_DISABLED
when: never
@@ -104,9 +101,6 @@ gemnasium-python-dependency_scanning:
- .cyclone-dx-reports
variables:
DS_ANALYZER_NAME: "gemnasium-python"
- # Stop reporting Pipenv and Setuptools as "pip".
- # See https://gitlab.com/gitlab-org/gitlab/-/issues/338252
- DS_REPORT_PACKAGE_MANAGER_PIP_WHEN_PYTHON: "false"
rules:
- if: $DEPENDENCY_SCANNING_DISABLED
when: never
diff --git a/lib/gitlab/kas.rb b/lib/gitlab/kas.rb
index ed7787ffc49..bf7b7f2d089 100644
--- a/lib/gitlab/kas.rb
+++ b/lib/gitlab/kas.rb
@@ -33,6 +33,10 @@ module Gitlab
@_version ||= Rails.root.join(VERSION_FILE).read.chomp
end
+ def version_info
+ Gitlab::VersionInfo.parse(version)
+ end
+
# Return GitLab KAS external_url
#
# @return [String] external_url
diff --git a/locale/gitlab.pot b/locale/gitlab.pot
index 0f19cae937a..872f32c67f4 100644
--- a/locale/gitlab.pot
+++ b/locale/gitlab.pot
@@ -3258,6 +3258,9 @@ msgstr ""
msgid "Advanced export options"
msgstr ""
+msgid "AdvancedSearch|Elasticsearch version not compatible"
+msgstr ""
+
msgid "AdvancedSearch|Reindex required"
msgstr ""
@@ -27187,6 +27190,9 @@ msgstr ""
msgid "Pause"
msgstr ""
+msgid "Pause indexing and upgrade Elasticsearch to a supported version."
+msgstr ""
+
msgid "Pause time (ms)"
msgstr ""
@@ -39757,6 +39763,9 @@ msgstr ""
msgid "Trigger cluster reindexing. Only use this with an index that was created in GitLab 13.0 or later."
msgstr ""
+msgid "Trigger job"
+msgstr ""
+
msgid "Trigger manual job"
msgstr ""
diff --git a/spec/controllers/projects/branches_controller_spec.rb b/spec/controllers/projects/branches_controller_spec.rb
index 1580ad9361d..ed11d5936b0 100644
--- a/spec/controllers/projects/branches_controller_spec.rb
+++ b/spec/controllers/projects/branches_controller_spec.rb
@@ -307,17 +307,36 @@ RSpec.describe Projects::BranchesController do
sign_in(developer)
end
- it 'returns 303' do
- post :destroy,
- format: :html,
- params: {
- id: 'foo/bar/baz',
- namespace_id: project.namespace,
- project_id: project
- }
+ subject(:post_request) do
+ post :destroy, format: :html, params: {
+ id: 'foo/bar/baz',
+ namespace_id: project.namespace,
+ project_id: project
+ }
+ end
+ it "returns response code 303" do
+ post_request
expect(response).to have_gitlab_http_status(:see_other)
end
+
+ context 'with http referer' do
+ before do
+ request.env['HTTP_REFERER'] = '/'
+ end
+
+ it "redirects to the referer path" do
+ post_request
+ expect(response).to redirect_to('/')
+ end
+ end
+
+ context 'without http referer' do
+ it "redirects to the project branches path" do
+ post_request
+ expect(response).to redirect_to(project_branches_path(project))
+ end
+ end
end
describe "POST destroy" do
diff --git a/spec/features/issues/gfm_autocomplete_spec.rb b/spec/features/issues/gfm_autocomplete_spec.rb
index 6f4a13c5fad..8732e2ecff2 100644
--- a/spec/features/issues/gfm_autocomplete_spec.rb
+++ b/spec/features/issues/gfm_autocomplete_spec.rb
@@ -445,7 +445,7 @@ RSpec.describe 'GFM autocomplete', :js do
click_button('Cancel')
page.within('.modal') do
- click_button('OK', match: :first)
+ click_button('Discard changes', match: :first)
end
wait_for_requests
diff --git a/spec/frontend/clusters_list/components/agent_table_spec.js b/spec/frontend/clusters_list/components/agent_table_spec.js
index a466a35428a..2a43b45a2f5 100644
--- a/spec/frontend/clusters_list/components/agent_table_spec.js
+++ b/spec/frontend/clusters_list/components/agent_table_spec.js
@@ -13,6 +13,7 @@ const defaultConfigHelpUrl =
const provideData = {
gitlabVersion: '14.8',
+ kasVersion: '14.8',
};
const propsData = {
agents: clusterAgents,
@@ -26,7 +27,7 @@ const outdatedTitle = I18N_AGENT_TABLE.versionOutdatedTitle;
const mismatchTitle = I18N_AGENT_TABLE.versionMismatchTitle;
const mismatchOutdatedTitle = I18N_AGENT_TABLE.versionMismatchOutdatedTitle;
const outdatedText = sprintf(I18N_AGENT_TABLE.versionOutdatedText, {
- version: provideData.gitlabVersion,
+ version: provideData.kasVersion,
});
const mismatchText = I18N_AGENT_TABLE.versionMismatchText;
diff --git a/spec/frontend/pipelines/__snapshots__/utils_spec.js.snap b/spec/frontend/pipelines/__snapshots__/utils_spec.js.snap
index 2d2e5db598a..724ec7366d3 100644
--- a/spec/frontend/pipelines/__snapshots__/utils_spec.js.snap
+++ b/spec/frontend/pipelines/__snapshots__/utils_spec.js.snap
@@ -11,6 +11,7 @@ Array [
Object {
"__typename": "CiJob",
"id": "6",
+ "kind": "BUILD",
"name": "build_a_nlfjkdnlvskfnksvjknlfdjvlvnjdkjdf_nvjkenjkrlngjeknjkl",
"needs": Array [],
"previousStageJobsOrNeeds": Array [],
@@ -53,6 +54,7 @@ Array [
Object {
"__typename": "CiJob",
"id": "11",
+ "kind": "BUILD",
"name": "build_b",
"needs": Array [],
"previousStageJobsOrNeeds": Array [],
@@ -95,6 +97,7 @@ Array [
Object {
"__typename": "CiJob",
"id": "16",
+ "kind": "BUILD",
"name": "build_c",
"needs": Array [],
"previousStageJobsOrNeeds": Array [],
@@ -137,6 +140,7 @@ Array [
Object {
"__typename": "CiJob",
"id": "21",
+ "kind": "BUILD",
"name": "build_d 1/3",
"needs": Array [],
"previousStageJobsOrNeeds": Array [],
@@ -163,6 +167,7 @@ Array [
Object {
"__typename": "CiJob",
"id": "24",
+ "kind": "BUILD",
"name": "build_d 2/3",
"needs": Array [],
"previousStageJobsOrNeeds": Array [],
@@ -189,6 +194,7 @@ Array [
Object {
"__typename": "CiJob",
"id": "27",
+ "kind": "BUILD",
"name": "build_d 3/3",
"needs": Array [],
"previousStageJobsOrNeeds": Array [],
@@ -231,6 +237,7 @@ Array [
Object {
"__typename": "CiJob",
"id": "59",
+ "kind": "BUILD",
"name": "test_c",
"needs": Array [],
"previousStageJobsOrNeeds": Array [],
@@ -275,6 +282,7 @@ Array [
Object {
"__typename": "CiJob",
"id": "34",
+ "kind": "BUILD",
"name": "test_a",
"needs": Array [
"build_c",
@@ -325,6 +333,7 @@ Array [
Object {
"__typename": "CiJob",
"id": "42",
+ "kind": "BUILD",
"name": "test_b 1/2",
"needs": Array [
"build_d 3/3",
@@ -363,6 +372,7 @@ Array [
Object {
"__typename": "CiJob",
"id": "67",
+ "kind": "BUILD",
"name": "test_b 2/2",
"needs": Array [
"build_d 3/3",
@@ -417,6 +427,7 @@ Array [
Object {
"__typename": "CiJob",
"id": "53",
+ "kind": "BUILD",
"name": "test_d",
"needs": Array [
"build_b",
diff --git a/spec/frontend/pipelines/graph/job_item_spec.js b/spec/frontend/pipelines/graph/job_item_spec.js
index 23e7ed7ebb4..4f0da09fec6 100644
--- a/spec/frontend/pipelines/graph/job_item_spec.js
+++ b/spec/frontend/pipelines/graph/job_item_spec.js
@@ -1,89 +1,34 @@
import { mount } from '@vue/test-utils';
import { nextTick } from 'vue';
+import { GlBadge } from '@gitlab/ui';
import JobItem from '~/pipelines/components/graph/job_item.vue';
+import { extendedWrapper } from 'helpers/vue_test_utils_helper';
+import {
+ delayedJob,
+ mockJob,
+ mockJobWithoutDetails,
+ mockJobWithUnauthorizedAction,
+ triggerJob,
+} from './mock_data';
describe('pipeline graph job item', () => {
let wrapper;
- const findJobWithoutLink = () => wrapper.find('[data-testid="job-without-link"]');
- const findJobWithLink = () => wrapper.find('[data-testid="job-with-link"]');
- const findActionComponent = () => wrapper.find('[data-testid="ci-action-component"]');
+ const findJobWithoutLink = () => wrapper.findByTestId('job-without-link');
+ const findJobWithLink = () => wrapper.findByTestId('job-with-link');
+ const findActionComponent = () => wrapper.findByTestId('ci-action-component');
+ const findBadge = () => wrapper.findComponent(GlBadge);
const createWrapper = (propsData) => {
- wrapper = mount(JobItem, {
- propsData,
- });
+ wrapper = extendedWrapper(
+ mount(JobItem, {
+ propsData,
+ }),
+ );
};
const triggerActiveClass = 'gl-shadow-x0-y0-b3-s1-blue-500';
- const delayedJob = {
- __typename: 'CiJob',
- name: 'delayed job',
- scheduledAt: '2015-07-03T10:01:00.000Z',
- needs: [],
- status: {
- __typename: 'DetailedStatus',
- icon: 'status_scheduled',
- tooltip: 'delayed manual action (%{remainingTime})',
- hasDetails: true,
- detailsPath: '/root/kinder-pipe/-/jobs/5339',
- group: 'scheduled',
- action: {
- __typename: 'StatusAction',
- icon: 'time-out',
- title: 'Unschedule',
- path: '/frontend-fixtures/builds-project/-/jobs/142/unschedule',
- buttonTitle: 'Unschedule job',
- },
- },
- };
-
- const mockJob = {
- id: 4256,
- name: 'test',
- status: {
- icon: 'status_success',
- text: 'passed',
- label: 'passed',
- tooltip: 'passed',
- group: 'success',
- detailsPath: '/root/ci-mock/builds/4256',
- hasDetails: true,
- action: {
- icon: 'retry',
- title: 'Retry',
- path: '/root/ci-mock/builds/4256/retry',
- method: 'post',
- },
- },
- };
- const mockJobWithoutDetails = {
- id: 4257,
- name: 'job_without_details',
- status: {
- icon: 'status_success',
- text: 'passed',
- label: 'passed',
- group: 'success',
- detailsPath: '/root/ci-mock/builds/4257',
- hasDetails: false,
- },
- };
- const mockJobWithUnauthorizedAction = {
- id: 4258,
- name: 'stop-environment',
- status: {
- icon: 'status_manual',
- label: 'manual stop action (not allowed)',
- tooltip: 'manual action',
- group: 'manual',
- detailsPath: '/root/ci-mock/builds/4258',
- hasDetails: true,
- action: null,
- },
- };
-
afterEach(() => {
wrapper.destroy();
});
@@ -148,13 +93,25 @@ describe('pipeline graph job item', () => {
});
});
- it('should render provided class name', () => {
- createWrapper({
- job: mockJob,
- cssClassJobName: 'css-class-job-name',
+ describe('job style', () => {
+ beforeEach(() => {
+ createWrapper({
+ job: mockJob,
+ cssClassJobName: 'css-class-job-name',
+ });
});
- expect(wrapper.find('a').classes()).toContain('css-class-job-name');
+ it('should render provided class name', () => {
+ expect(wrapper.find('a').classes()).toContain('css-class-job-name');
+ });
+
+ it('does not show a badge on the job item', () => {
+ expect(findBadge().exists()).toBe(false);
+ });
+
+ it('does not apply the trigger job class', () => {
+ expect(findJobWithLink().classes()).not.toContain('gl-rounded-lg');
+ });
});
describe('status label', () => {
@@ -201,34 +158,51 @@ describe('pipeline graph job item', () => {
});
});
- describe('trigger job highlighting', () => {
- it.each`
- job | jobName | expanded | link
- ${mockJob} | ${mockJob.name} | ${true} | ${true}
- ${mockJobWithoutDetails} | ${mockJobWithoutDetails.name} | ${true} | ${false}
- `(
- `trigger job should stay highlighted when downstream is expanded`,
- ({ job, jobName, expanded, link }) => {
- createWrapper({ job, pipelineExpanded: { jobName, expanded } });
- const findJobEl = link ? findJobWithLink : findJobWithoutLink;
+ describe('trigger job', () => {
+ describe('card', () => {
+ beforeEach(() => {
+ createWrapper({ job: triggerJob });
+ });
- expect(findJobEl().classes()).toContain(triggerActiveClass);
- },
- );
+ it('shows a badge on the job item', () => {
+ expect(findBadge().exists()).toBe(true);
+ expect(findBadge().text()).toBe('Trigger job');
+ });
- it.each`
- job | jobName | expanded | link
- ${mockJob} | ${mockJob.name} | ${false} | ${true}
- ${mockJobWithoutDetails} | ${mockJobWithoutDetails.name} | ${false} | ${false}
- `(
- `trigger job should not be highlighted when downstream is not expanded`,
- ({ job, jobName, expanded, link }) => {
- createWrapper({ job, pipelineExpanded: { jobName, expanded } });
- const findJobEl = link ? findJobWithLink : findJobWithoutLink;
+ it('applies a rounded corner style instead of the usual pill shape', () => {
+ expect(findJobWithoutLink().classes()).toContain('gl-rounded-lg');
+ });
+ });
- expect(findJobEl().classes()).not.toContain(triggerActiveClass);
- },
- );
+ describe('highlighting', () => {
+ it.each`
+ job | jobName | expanded | link
+ ${mockJob} | ${mockJob.name} | ${true} | ${true}
+ ${mockJobWithoutDetails} | ${mockJobWithoutDetails.name} | ${true} | ${false}
+ `(
+ `trigger job should stay highlighted when downstream is expanded`,
+ ({ job, jobName, expanded, link }) => {
+ createWrapper({ job, pipelineExpanded: { jobName, expanded } });
+ const findJobEl = link ? findJobWithLink : findJobWithoutLink;
+
+ expect(findJobEl().classes()).toContain(triggerActiveClass);
+ },
+ );
+
+ it.each`
+ job | jobName | expanded | link
+ ${mockJob} | ${mockJob.name} | ${false} | ${true}
+ ${mockJobWithoutDetails} | ${mockJobWithoutDetails.name} | ${false} | ${false}
+ `(
+ `trigger job should not be highlighted when downstream is not expanded`,
+ ({ job, jobName, expanded, link }) => {
+ createWrapper({ job, pipelineExpanded: { jobName, expanded } });
+ const findJobEl = link ? findJobWithLink : findJobWithoutLink;
+
+ expect(findJobEl().classes()).not.toContain(triggerActiveClass);
+ },
+ );
+ });
});
describe('job classes', () => {
diff --git a/spec/frontend/pipelines/graph/mock_data.js b/spec/frontend/pipelines/graph/mock_data.js
index 94915d4ce7b..6124d67af09 100644
--- a/spec/frontend/pipelines/graph/mock_data.js
+++ b/spec/frontend/pipelines/graph/mock_data.js
@@ -1,4 +1,5 @@
import { unwrapPipelineData } from '~/pipelines/components/graph/utils';
+import { BUILD_KIND, BRIDGE_KIND } from '~/pipelines/components/graph/constants';
export const mockPipelineResponse = {
data: {
@@ -50,6 +51,7 @@ export const mockPipelineResponse = {
{
__typename: 'CiJob',
id: '6',
+ kind: BUILD_KIND,
name: 'build_a_nlfjkdnlvskfnksvjknlfdjvlvnjdkjdf_nvjkenjkrlngjeknjkl',
scheduledAt: null,
status: {
@@ -101,6 +103,7 @@ export const mockPipelineResponse = {
__typename: 'CiJob',
id: '11',
name: 'build_b',
+ kind: BUILD_KIND,
scheduledAt: null,
status: {
__typename: 'DetailedStatus',
@@ -151,6 +154,7 @@ export const mockPipelineResponse = {
__typename: 'CiJob',
id: '16',
name: 'build_c',
+ kind: BUILD_KIND,
scheduledAt: null,
status: {
__typename: 'DetailedStatus',
@@ -200,6 +204,7 @@ export const mockPipelineResponse = {
{
__typename: 'CiJob',
id: '21',
+ kind: BUILD_KIND,
name: 'build_d 1/3',
scheduledAt: null,
status: {
@@ -232,6 +237,7 @@ export const mockPipelineResponse = {
{
__typename: 'CiJob',
id: '24',
+ kind: BUILD_KIND,
name: 'build_d 2/3',
scheduledAt: null,
status: {
@@ -264,6 +270,7 @@ export const mockPipelineResponse = {
{
__typename: 'CiJob',
id: '27',
+ kind: BUILD_KIND,
name: 'build_d 3/3',
scheduledAt: null,
status: {
@@ -329,6 +336,7 @@ export const mockPipelineResponse = {
{
__typename: 'CiJob',
id: '34',
+ kind: BUILD_KIND,
name: 'test_a',
scheduledAt: null,
status: {
@@ -413,6 +421,7 @@ export const mockPipelineResponse = {
{
__typename: 'CiJob',
id: '42',
+ kind: BUILD_KIND,
name: 'test_b 1/2',
scheduledAt: null,
status: {
@@ -499,6 +508,7 @@ export const mockPipelineResponse = {
{
__typename: 'CiJob',
id: '67',
+ kind: BUILD_KIND,
name: 'test_b 2/2',
scheduledAt: null,
status: {
@@ -603,6 +613,7 @@ export const mockPipelineResponse = {
{
__typename: 'CiJob',
id: '59',
+ kind: BUILD_KIND,
name: 'test_c',
scheduledAt: null,
status: {
@@ -646,6 +657,7 @@ export const mockPipelineResponse = {
{
__typename: 'CiJob',
id: '53',
+ kind: BUILD_KIND,
name: 'test_d',
scheduledAt: null,
status: {
@@ -871,6 +883,7 @@ export const wrappedPipelineReturn = {
{
__typename: 'CiJob',
id: '83',
+ kind: BUILD_KIND,
name: 'build_n',
scheduledAt: null,
needs: {
@@ -941,3 +954,87 @@ export const mockCalloutsResponse = (mappedCallouts) => ({
},
},
});
+
+export const delayedJob = {
+ __typename: 'CiJob',
+ kind: BUILD_KIND,
+ name: 'delayed job',
+ scheduledAt: '2015-07-03T10:01:00.000Z',
+ needs: [],
+ status: {
+ __typename: 'DetailedStatus',
+ icon: 'status_scheduled',
+ tooltip: 'delayed manual action (%{remainingTime})',
+ hasDetails: true,
+ detailsPath: '/root/kinder-pipe/-/jobs/5339',
+ group: 'scheduled',
+ action: {
+ __typename: 'StatusAction',
+ icon: 'time-out',
+ title: 'Unschedule',
+ path: '/frontend-fixtures/builds-project/-/jobs/142/unschedule',
+ buttonTitle: 'Unschedule job',
+ },
+ },
+};
+
+export const mockJob = {
+ id: 4256,
+ name: 'test',
+ kind: BUILD_KIND,
+ status: {
+ icon: 'status_success',
+ text: 'passed',
+ label: 'passed',
+ tooltip: 'passed',
+ group: 'success',
+ detailsPath: '/root/ci-mock/builds/4256',
+ hasDetails: true,
+ action: {
+ icon: 'retry',
+ title: 'Retry',
+ path: '/root/ci-mock/builds/4256/retry',
+ method: 'post',
+ },
+ },
+};
+
+export const mockJobWithoutDetails = {
+ id: 4257,
+ name: 'job_without_details',
+ status: {
+ icon: 'status_success',
+ text: 'passed',
+ label: 'passed',
+ group: 'success',
+ detailsPath: '/root/ci-mock/builds/4257',
+ hasDetails: false,
+ },
+};
+
+export const mockJobWithUnauthorizedAction = {
+ id: 4258,
+ name: 'stop-environment',
+ status: {
+ icon: 'status_manual',
+ label: 'manual stop action (not allowed)',
+ tooltip: 'manual action',
+ group: 'manual',
+ detailsPath: '/root/ci-mock/builds/4258',
+ hasDetails: true,
+ action: null,
+ },
+};
+
+export const triggerJob = {
+ id: 4259,
+ name: 'trigger',
+ kind: BRIDGE_KIND,
+ status: {
+ icon: 'status_success',
+ text: 'passed',
+ label: 'passed',
+ group: 'success',
+ action: null,
+ },
+};
diff --git a/spec/helpers/clusters_helper_spec.rb b/spec/helpers/clusters_helper_spec.rb
index 4111b39aa41..9a3cd5fd18d 100644
--- a/spec/helpers/clusters_helper_spec.rb
+++ b/spec/helpers/clusters_helper_spec.rb
@@ -90,6 +90,10 @@ RSpec.describe ClustersHelper do
expect(subject[:gitlab_version]).to eq(Gitlab.version_info)
end
+ it 'displays KAS version' do
+ expect(subject[:kas_version]).to eq(Gitlab::Kas.version_info)
+ end
+
context 'user has no permissions to create a cluster' do
it 'displays that user can\'t add cluster' do
expect(subject[:can_add_cluster]).to eq("false")
diff --git a/spec/models/preloaders/user_max_access_level_in_projects_preloader_spec.rb b/spec/models/preloaders/user_max_access_level_in_projects_preloader_spec.rb
index 16e699b7e0e..eefe5bfc6c4 100644
--- a/spec/models/preloaders/user_max_access_level_in_projects_preloader_spec.rb
+++ b/spec/models/preloaders/user_max_access_level_in_projects_preloader_spec.rb
@@ -9,29 +9,47 @@ RSpec.describe Preloaders::UserMaxAccessLevelInProjectsPreloader do
let_it_be(:project_3) { create(:project) }
let(:projects) { [project_1, project_2, project_3] }
+ let(:query) { projects.each { |project| user.can?(:read_project, project) } }
before do
project_1.add_developer(user)
project_2.add_developer(user)
end
- context 'preload maximum access level to avoid querying project_authorizations', :request_store do
- it 'avoids N+1 queries', :request_store do
- Preloaders::UserMaxAccessLevelInProjectsPreloader.new(projects, user).execute
+ context 'without preloader' do
+ it 'runs N queries' do
+ expect { query }.to make_queries(projects.size)
+ end
+ end
- query_count = ActiveRecord::QueryRecorder.new do
- projects.each { |project| user.can?(:read_project, project) }
- end.count
+ describe '#execute', :request_store do
+ let(:projects_arg) { projects }
- expect(query_count).to eq(0)
+ before do
+ Preloaders::UserMaxAccessLevelInProjectsPreloader.new(projects_arg, user).execute
end
- it 'runs N queries without preloading' do
- query_count = ActiveRecord::QueryRecorder.new do
- projects.each { |project| user.can?(:read_project, project) }
- end.count
+ it 'avoids N+1 queries' do
+ expect { query }.not_to make_queries
+ end
- expect(query_count).to eq(projects.size)
+ context 'when projects is an array of IDs' do
+ let(:projects_arg) { [project_1.id, project_2.id, project_3.id] }
+
+ it 'avoids N+1 queries' do
+ expect { query }.not_to make_queries
+ end
+ end
+
+ # Test for handling of SQL table name clashes.
+ context 'when projects is a relation including project_authorizations' do
+ let(:projects_arg) do
+ Project.where(id: ProjectAuthorization.where(project_id: projects).select(:project_id))
+ end
+
+ it 'avoids N+1 queries' do
+ expect { query }.not_to make_queries
+ end
end
end
end
diff --git a/spec/services/users/destroy_service_spec.rb b/spec/services/users/destroy_service_spec.rb
index 918251bb649..45dbe83b496 100644
--- a/spec/services/users/destroy_service_spec.rb
+++ b/spec/services/users/destroy_service_spec.rb
@@ -336,35 +336,24 @@ RSpec.describe Users::DestroyService do
context 'batched nullify' do
let(:other_user) { create(:user) }
- context 'when :nullify_in_batches_on_user_deletion feature flag is enabled' do
- it 'nullifies related associations in batches' do
- expect(other_user).to receive(:nullify_dependent_associations_in_batches).and_call_original
+ it 'nullifies related associations in batches' do
+ expect(other_user).to receive(:nullify_dependent_associations_in_batches).and_call_original
- described_class.new(user).execute(other_user, skip_authorization: true)
- end
-
- it 'nullifies last_updated_issues and closed_issues' do
- issue = create(:issue, closed_by: other_user, updated_by: other_user)
-
- described_class.new(user).execute(other_user, skip_authorization: true)
-
- issue.reload
-
- expect(issue.closed_by).to be_nil
- expect(issue.updated_by).to be_nil
- end
+ described_class.new(user).execute(other_user, skip_authorization: true)
end
- context 'when :nullify_in_batches_on_user_deletion feature flag is disabled' do
- before do
- stub_feature_flags(nullify_in_batches_on_user_deletion: false)
- end
+ it 'nullifies last_updated_issues, closed_issues, resource_label_events' do
+ issue = create(:issue, closed_by: other_user, updated_by: other_user)
+ resource_label_event = create(:resource_label_event, user: other_user)
- it 'does not use batching' do
- expect(other_user).not_to receive(:nullify_dependent_associations_in_batches)
+ described_class.new(user).execute(other_user, skip_authorization: true)
- described_class.new(user).execute(other_user, skip_authorization: true)
- end
+ issue.reload
+ resource_label_event.reload
+
+ expect(issue.closed_by).to be_nil
+ expect(issue.updated_by).to be_nil
+ expect(resource_label_event.user).to be_nil
end
end
end
diff --git a/spec/support/matchers/make_queries.rb b/spec/support/matchers/make_queries.rb
new file mode 100644
index 00000000000..19c69240a40
--- /dev/null
+++ b/spec/support/matchers/make_queries.rb
@@ -0,0 +1,31 @@
+# frozen_string_literal: true
+
+RSpec::Matchers.define :make_queries do |expected_count = nil|
+ supports_block_expectations
+
+ match do |block|
+ @recorder = ActiveRecord::QueryRecorder.new(&block)
+ @counter = @recorder.count
+ if expected_count
+ @counter == expected_count
+ else
+ @counter > 0
+ end
+ end
+
+ failure_message do |_|
+ if expected_count
+ "expected to make #{expected_count} queries but made #{@counter} queries"
+ else
+ "expected to make queries but did not make any"
+ end
+ end
+
+ failure_message_when_negated do |_|
+ if expected_count
+ "expected not to make #{expected_count} queries but received #{@counter} queries"
+ else
+ "expected not to make queries but received #{@counter} queries"
+ end
+ end
+end
diff --git a/spec/views/devise/shared/_signin_box.html.haml_spec.rb b/spec/views/devise/shared/_signin_box.html.haml_spec.rb
index 8b1af1866dc..e2aa0bb9870 100644
--- a/spec/views/devise/shared/_signin_box.html.haml_spec.rb
+++ b/spec/views/devise/shared/_signin_box.html.haml_spec.rb
@@ -28,6 +28,20 @@ RSpec.describe 'devise/shared/_signin_box' do
end
end
+ describe 'Base form' do
+ before do
+ stub_devise
+ allow(view).to receive(:captcha_enabled?).and_return(false)
+ allow(view).to receive(:captcha_on_login_required?).and_return(false)
+ end
+
+ it 'renders user_login label' do
+ render
+
+ expect(rendered).to have_content(_('Username or email'))
+ end
+ end
+
def stub_devise
allow(view).to receive(:devise_mapping).and_return(Devise.mappings[:user])
allow(view).to receive(:resource).and_return(spy)
diff --git a/workhorse/internal/upload/artifacts_uploader.go b/workhorse/internal/upload/artifacts_uploader.go
index 3c2d89e976f..c1c49638e21 100644
--- a/workhorse/internal/upload/artifacts_uploader.go
+++ b/workhorse/internal/upload/artifacts_uploader.go
@@ -35,7 +35,6 @@ var zipSubcommandsErrorsCounter = promauto.NewCounterVec(
}, []string{"error"})
type artifactsUploadProcessor struct {
- opts *destination.UploadOpts
format string
SavedFileTracker
@@ -52,7 +51,7 @@ func Artifacts(myAPI *api.API, h http.Handler, p Preparer) http.Handler {
format := r.URL.Query().Get(ArtifactFormatKey)
- mg := &artifactsUploadProcessor{opts: opts, format: format, SavedFileTracker: SavedFileTracker{Request: r}}
+ mg := &artifactsUploadProcessor{format: format, SavedFileTracker: SavedFileTracker{Request: r}}
interceptMultipartFiles(w, r, h, a, mg, opts)
}, "/authorize")
}
@@ -62,12 +61,9 @@ func (a *artifactsUploadProcessor) generateMetadataFromZip(ctx context.Context,
defer metaWriter.Close()
metaOpts := &destination.UploadOpts{
- LocalTempPath: a.opts.LocalTempPath,
+ LocalTempPath: os.TempDir(),
TempFilePrefix: "metadata.gz",
}
- if metaOpts.LocalTempPath == "" {
- metaOpts.LocalTempPath = os.TempDir()
- }
fileName := file.LocalPath
if fileName == "" {