diff options
38 files changed, 394 insertions, 235 deletions
diff --git a/app/assets/javascripts/pipelines/stores/test_reports/utils.js b/app/assets/javascripts/pipelines/stores/test_reports/utils.js index 95466587d6b..16fa6935cbe 100644 --- a/app/assets/javascripts/pipelines/stores/test_reports/utils.js +++ b/app/assets/javascripts/pipelines/stores/test_reports/utils.js @@ -1,7 +1,7 @@ import { TestStatus } from '~/pipelines/constants'; import { formatTime, secondsToMilliseconds } from '~/lib/utils/datetime_utility'; -function iconForTestStatus(status) { +export function iconForTestStatus(status) { switch (status) { case 'success': return 'status_success_borderless'; diff --git a/app/models/concerns/issuable.rb b/app/models/concerns/issuable.rb index 6ea12e1cd59..71338fedbe9 100644 --- a/app/models/concerns/issuable.rb +++ b/app/models/concerns/issuable.rb @@ -173,7 +173,7 @@ module Issuable private def milestone_is_valid - errors.add(:milestone_id, message: "is invalid") if milestone_id.present? && !milestone_available? + errors.add(:milestone_id, message: "is invalid") if respond_to?(:milestone_id) && milestone_id.present? && !milestone_available? end def description_max_length_for_new_records_is_valid diff --git a/app/models/epic.rb b/app/models/epic.rb index 46723462590..01ef8bd100e 100644 --- a/app/models/epic.rb +++ b/app/models/epic.rb @@ -3,6 +3,8 @@ # Placeholder class for model that is implemented in EE # It reserves '&' as a reference prefix, but the table does not exists in CE class Epic < ApplicationRecord + self.ignored_columns += %i[milestone_id] + def self.link_reference_pattern nil end diff --git a/app/models/project_services/prometheus_service.rb b/app/models/project_services/prometheus_service.rb index a0273fe0e5a..3e0606fd34a 100644 --- a/app/models/project_services/prometheus_service.rb +++ b/app/models/project_services/prometheus_service.rb @@ -22,6 +22,8 @@ class PrometheusService < MonitoringService after_save :clear_reactive_cache! + after_commit :track_events + def initialize_properties if properties.nil? self.properties = {} @@ -116,4 +118,22 @@ class PrometheusService < MonitoringService true end + + def track_events + if enabled_manual_prometheus? + Gitlab::Tracking.event('cluster:services:prometheus', 'enabled_manual_prometheus') + elsif disabled_manual_prometheus? + Gitlab::Tracking.event('cluster:services:prometheus', 'disabled_manual_prometheus') + end + + true + end + + def enabled_manual_prometheus? + manual_configuration_changed? && manual_configuration? + end + + def disabled_manual_prometheus? + manual_configuration_changed? && !manual_configuration? + end end diff --git a/app/services/issuable/clone/attributes_rewriter.rb b/app/services/issuable/clone/attributes_rewriter.rb index 10c89c62bf1..1f5d83917cc 100644 --- a/app/services/issuable/clone/attributes_rewriter.rb +++ b/app/services/issuable/clone/attributes_rewriter.rb @@ -10,7 +10,13 @@ module Issuable end def execute - new_entity.update(milestone: cloneable_milestone, labels: cloneable_labels) + update_attributes = { labels: cloneable_labels } + + milestone = cloneable_milestone + update_attributes[:milestone] = milestone if milestone.present? + + new_entity.update(update_attributes) + copy_resource_label_events end diff --git a/changelogs/unreleased/34157-apm_snowplow_events.yml b/changelogs/unreleased/34157-apm_snowplow_events.yml new file mode 100644 index 00000000000..6dfa7ffce5c --- /dev/null +++ b/changelogs/unreleased/34157-apm_snowplow_events.yml @@ -0,0 +1,5 @@ +--- +title: Add snowplow events for APM +merge_request: 19463 +author: +type: added diff --git a/changelogs/unreleased/36611-gitlab-container-registry-repository-names-regex-is-not-at-parity-w.yml b/changelogs/unreleased/36611-gitlab-container-registry-repository-names-regex-is-not-at-parity-w.yml new file mode 100644 index 00000000000..b8f2c402d90 --- /dev/null +++ b/changelogs/unreleased/36611-gitlab-container-registry-repository-names-regex-is-not-at-parity-w.yml @@ -0,0 +1,5 @@ +--- +title: Update Container Registry naming restrictions to allow for sequential '-' +merge_request: 20318 +author: +type: fixed diff --git a/changelogs/unreleased/remove_milestone_id_from_epics.yml b/changelogs/unreleased/remove_milestone_id_from_epics.yml new file mode 100644 index 00000000000..f124a01c9dd --- /dev/null +++ b/changelogs/unreleased/remove_milestone_id_from_epics.yml @@ -0,0 +1,5 @@ +--- +title: Remove milestone_id from epics +merge_request: 20187 +author: Lee Tickett +type: other diff --git a/config/pseudonymizer.yml b/config/pseudonymizer.yml index 1c06366c237..1f4ed9a8421 100644 --- a/config/pseudonymizer.yml +++ b/config/pseudonymizer.yml @@ -47,7 +47,6 @@ tables: epics: whitelist: - id - - milestone_id - group_id - author_id - assignee_id diff --git a/doc/api/graphql/getting_started.md b/doc/api/graphql/getting_started.md index cebedf9fd69..aab8c26ae99 100644 --- a/doc/api/graphql/getting_started.md +++ b/doc/api/graphql/getting_started.md @@ -97,7 +97,7 @@ query { ### The root node -Any field defined in [`QueryType`](app/graphql/types/query_type.rb) will be exposed as a root node. +Any field defined in [`QueryType`](https://gitlab.com/gitlab-org/gitlab/tree/master/app/graphql/types/query_type.rb) will be exposed as a root node. When retrieving child nodes use: - the `edges { node { } }` syntax. diff --git a/doc/user/application_security/sast/analyzers.md b/doc/user/application_security/sast/analyzers.md index c766ca4cffc..a42cf7f09ff 100644 --- a/doc/user/application_security/sast/analyzers.md +++ b/doc/user/application_security/sast/analyzers.md @@ -18,6 +18,7 @@ SAST supports the following official analyzers: - [`eslint`](https://gitlab.com/gitlab-org/security-products/analyzers/eslint) (ESLint (JavaScript and React)) - [`flawfinder`](https://gitlab.com/gitlab-org/security-products/analyzers/flawfinder) (Flawfinder) - [`gosec`](https://gitlab.com/gitlab-org/security-products/analyzers/gosec) (Gosec) +- [`kubesec`](https://gitlab.com/gitlab-org/security-products/analyzers/kubesec) (Kubesec) - [`nodejs-scan`](https://gitlab.com/gitlab-org/security-products/analyzers/nodejs-scan) (NodeJsScan) - [`phpcs-security-audit`](https://gitlab.com/gitlab-org/security-products/analyzers/phpcs-security-audit) (PHP CS security-audit) - [`pmd-apex`](https://gitlab.com/gitlab-org/security-products/analyzers/pmd-apex) (PMD (Apex only)) @@ -116,24 +117,24 @@ Custom analyzers are not spawned automatically when [Docker In Docker](index.md# ## Analyzers Data -| Property \ Tool | Apex | Bandit | Brakeman | ESLint security | Find Sec Bugs | Flawfinder | Go AST Scanner | NodeJsScan | Php CS Security Audit | Security code Scan (.NET) | TSLint Security | Sobelow | -| --------------------------------------- | :------------------: | :------------------: | :------------------: | :------------------: | :------------------: | :------------------: | :------------------: | :------------------: | :---------------------: | :-------------------------: | :-------------: | :----------------: | -| Severity | ✓ | ✓ | 𐄂 | 𐄂 | ✓ | 𐄂 | ✓ | 𐄂 | ✓ | 𐄂 | ✓ | 𐄂 | -| Title | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ | -| Description | ✓ | 𐄂 | 𐄂 | ✓ | ✓ | 𐄂 | 𐄂 | ✓ | 𐄂 | 𐄂 | ✓ | ✓ | -| File | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ | -| Start line | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ | -| End line | ✓ | ✓ | 𐄂 | ✓ | ✓ | 𐄂 | 𐄂 | 𐄂 | 𐄂 | 𐄂 | ✓ | 𐄂 | -| Start column | ✓ | 𐄂 | 𐄂 | ✓ | ✓ | ✓ | ✓ | 𐄂 | ✓ | ✓ | ✓ | 𐄂 | -| End column | ✓ | 𐄂 | 𐄂 | ✓ | ✓ | 𐄂 | 𐄂 | 𐄂 | 𐄂 | 𐄂 | ✓ | 𐄂 | -| External id (e.g. CVE) | 𐄂 | 𐄂 | ⚠ | 𐄂 | ⚠ | ✓ | 𐄂 | 𐄂 | 𐄂 | 𐄂 | 𐄂 | 𐄂 | -| URLs | ✓ | 𐄂 | ✓ | 𐄂 | ⚠ | 𐄂 | ⚠ | 𐄂 | 𐄂 | 𐄂 | 𐄂 | 𐄂 | 𐄂 | -| Internal doc/explanation | ✓ | ⚠ | ✓ | 𐄂 | ✓ | 𐄂 | 𐄂 | 𐄂 | 𐄂 | 𐄂 | 𐄂 | ✓ | -| Solution | ✓ | 𐄂 | 𐄂 | 𐄂 | ⚠ | ✓ | 𐄂 | 𐄂 | 𐄂 | 𐄂 | 𐄂 | 𐄂 | -| Confidence | 𐄂 | ✓ | ✓ | 𐄂 | ✓ | ✓ | ✓ | 𐄂 | 𐄂 | 𐄂 | 𐄂 | ✓ | -| Affected item (e.g. class or package) | ✓ | 𐄂 | ✓ | 𐄂 | ✓ | ✓ | 𐄂 | 𐄂 | 𐄂 | 𐄂 | 𐄂 | 𐄂 | -| Source code extract | 𐄂 | ✓ | ✓ | ✓ | 𐄂 | ✓ | ✓ | 𐄂 | 𐄂 | 𐄂 | 𐄂 | 𐄂 | -| Internal ID | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ | 𐄂 | ✓ | ✓ | ✓ | ✓ | +| Property \ Tool | Apex | Bandit | Brakeman | ESLint security | Find Sec Bugs | Flawfinder | Go AST Scanner | Kubesec Scanner | NodeJsScan | Php CS Security Audit | Security code Scan (.NET) | Sobelow | TSLint Security | +| --------------------------------------- | :------------------: | :------------------: | :------------------: | :------------------: | :------------------: | :------------------: | :------------------: | :------------------: | :------------------: | :---------------------: | :-------------------------: | :----------------: | :-------------: | +| Severity | ✓ | ✓ | 𐄂 | 𐄂 | ✓ | 𐄂 | ✓ | ✓ | 𐄂 | ✓ | 𐄂 | 𐄂 | ✓ | +| Title | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ | +| Description | ✓ | 𐄂 | 𐄂 | ✓ | ✓ | 𐄂 | 𐄂 | ✓ | ✓ | 𐄂 | 𐄂 | ✓ | ✓ | +| File | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ | +| Start line | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ | 𐄂 | ✓ | ✓ | ✓ | ✓ | ✓ | +| End line | ✓ | ✓ | 𐄂 | ✓ | ✓ | 𐄂 | 𐄂 | 𐄂 | 𐄂 | 𐄂 | 𐄂 | 𐄂 | ✓ | +| Start column | ✓ | 𐄂 | 𐄂 | ✓ | ✓ | ✓ | ✓ | 𐄂 | 𐄂 | ✓ | ✓ | 𐄂 | ✓ | +| End column | ✓ | 𐄂 | 𐄂 | ✓ | ✓ | 𐄂 | 𐄂 | 𐄂 | 𐄂 | 𐄂 | 𐄂 | 𐄂 | ✓ | +| External id (e.g. CVE) | 𐄂 | 𐄂 | ⚠ | 𐄂 | ⚠ | ✓ | 𐄂 | 𐄂 | 𐄂 | 𐄂 | 𐄂 | 𐄂 | 𐄂 | +| URLs | ✓ | 𐄂 | ✓ | 𐄂 | ⚠ | 𐄂 | ⚠ | 𐄂 | 𐄂 | 𐄂 | 𐄂 | 𐄂 | 𐄂 | +| Internal doc/explanation | ✓ | ⚠ | ✓ | 𐄂 | ✓ | 𐄂 | 𐄂 | 𐄂 | 𐄂 | 𐄂 | 𐄂 | ✓ | 𐄂 | +| Solution | ✓ | 𐄂 | 𐄂 | 𐄂 | ⚠ | ✓ | 𐄂 | 𐄂 | 𐄂 | 𐄂 | 𐄂 | 𐄂 | 𐄂 | +| Affected item (e.g. class or package) | ✓ | 𐄂 | ✓ | 𐄂 | ✓ | ✓ | 𐄂 | ✓ | 𐄂 | 𐄂 | 𐄂 | 𐄂 | 𐄂 | +| Confidence | 𐄂 | ✓ | ✓ | 𐄂 | ✓ | ✓ | ✓ | ✓ | 𐄂 | 𐄂 | 𐄂 | ✓ | 𐄂 | +| Source code extract | 𐄂 | ✓ | ✓ | ✓ | 𐄂 | ✓ | ✓ | 𐄂 | 𐄂 | 𐄂 | 𐄂 | 𐄂 | 𐄂 | +| Internal ID | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ | 𐄂 | 𐄂 | ✓ | ✓ | ✓ | ✓ | - ✓ => we have that data - ⚠ => we have that data but it's partially reliable, or we need to extract it from unstructured content diff --git a/doc/user/application_security/sast/index.md b/doc/user/application_security/sast/index.md index 8b8d578a78e..c0885e27da6 100644 --- a/doc/user/application_security/sast/index.md +++ b/doc/user/application_security/sast/index.md @@ -73,6 +73,7 @@ The following table shows which languages, package managers and frameworks are s | Groovy ([Ant](https://ant.apache.org/), [Gradle](https://gradle.org/), [Maven](https://maven.apache.org/) and [SBT](https://www.scala-sbt.org/)) | [SpotBugs](https://spotbugs.github.io/) with the [find-sec-bugs](https://find-sec-bugs.github.io/) plugin | 11.3 (Gradle) & 11.9 (Ant, Maven, SBT) | | Java ([Ant](https://ant.apache.org/), [Gradle](https://gradle.org/), [Maven](https://maven.apache.org/) and [SBT](https://www.scala-sbt.org/)) | [SpotBugs](https://spotbugs.github.io/) with the [find-sec-bugs](https://find-sec-bugs.github.io/) plugin | 10.6 (Maven), 10.8 (Gradle) & 11.9 (Ant, SBT) | | JavaScript | [ESLint security plugin](https://github.com/nodesecurity/eslint-plugin-security) | 11.8 | +| Kubernetes manifests | [Kubesec](https://github.com/controlplaneio/kubesec) | 12.6 | | Node.js | [NodeJsScan](https://github.com/ajinabraham/NodeJsScan) | 11.1 | | PHP | [phpcs-security-audit](https://github.com/FloeDesignTechnologies/phpcs-security-audit) | 10.8 | | Python ([pip](https://pip.pypa.io/en/stable/)) | [bandit](https://github.com/PyCQA/bandit) | 10.3 | @@ -185,6 +186,22 @@ variables: This will create individual `<analyzer-name>-sast` jobs for each analyzer that runs in your CI/CD pipeline. +#### Enabling kubesec analyzer + +> [Introduced](https://gitlab.com/gitlab-org/gitlab/issues/12752) in GitLab Ultimate 12.6. + +When [Docker in Docker is disabled](#disabling-docker-in-docker-for-sast), +you will need to set `SCAN_KUBERNETES_MANIFESTS` to `"true"` to enable the +kubesec analyzer. In `.gitlab-ci.yml`, define: + +```yaml +include: + template: SAST.gitlab-ci.yml + +variables: + SCAN_KUBERNETES_MANIFESTS: "true" +``` + ### Available variables SAST can be [configured](#customizing-the-sast-settings) using environment variables. @@ -232,19 +249,20 @@ Timeout variables are not applicable for setups with [disabled Docker In Docker] Some analyzers can be customized with environment variables. -| Environment variable | Analyzer | Description | -|-------------------------|----------|----------| -| `ANT_HOME` | spotbugs | The `ANT_HOME` environment variable. | -| `ANT_PATH` | spotbugs | Path to the `ant` executable. | -| `GRADLE_PATH` | spotbugs | Path to the `gradle` executable. | -| `JAVA_OPTS` | spotbugs | Additional arguments for the `java` executable. | -| `JAVA_PATH` | spotbugs | Path to the `java` executable. | -| `SAST_JAVA_VERSION` | spotbugs | Which Java version to use. Supported versions are `8` and `11`. Defaults to `8`. | -| `MAVEN_CLI_OPTS` | spotbugs | Additional arguments for the `mvn` or `mvnw` executable. | -| `MAVEN_PATH` | spotbugs | Path to the `mvn` executable. | -| `MAVEN_REPO_PATH` | spotbugs | Path to the Maven local repository (shortcut for the `maven.repo.local` property). | -| `SBT_PATH` | spotbugs | Path to the `sbt` executable. | -| `FAIL_NEVER` | spotbugs | Set to `1` to ignore compilation failure. | +| Environment variable | Analyzer | Description | +|-----------------------------|----------|-------------| +| `SCAN_KUBERNETES_MANIFESTS` | kubesec | Set to `"true"` to scan Kubernetes manifests when [Docker in Docker](#disabling-docker-in-docker-for-sast) is disabled. | +| `ANT_HOME` | spotbugs | The `ANT_HOME` environment variable. | +| `ANT_PATH` | spotbugs | Path to the `ant` executable. | +| `GRADLE_PATH` | spotbugs | Path to the `gradle` executable. | +| `JAVA_OPTS` | spotbugs | Additional arguments for the `java` executable. | +| `JAVA_PATH` | spotbugs | Path to the `java` executable. | +| `SAST_JAVA_VERSION` | spotbugs | Which Java version to use. Supported versions are `8` and `11`. Defaults to `8`. | +| `MAVEN_CLI_OPTS` | spotbugs | Additional arguments for the `mvn` or `mvnw` executable. | +| `MAVEN_PATH` | spotbugs | Path to the `mvn` executable. | +| `MAVEN_REPO_PATH` | spotbugs | Path to the Maven local repository (shortcut for the `maven.repo.local` property). | +| `SBT_PATH` | spotbugs | Path to the `sbt` executable. | +| `FAIL_NEVER` | spotbugs | Set to `1` to ignore compilation failure. | #### Custom environment variables diff --git a/lib/gitlab/database_importers/self_monitoring/project/create_service.rb b/lib/gitlab/database_importers/self_monitoring/project/create_service.rb index 8cd9694b741..fbf252b7ec3 100644 --- a/lib/gitlab/database_importers/self_monitoring/project/create_service.rb +++ b/lib/gitlab/database_importers/self_monitoring/project/create_service.rb @@ -29,10 +29,11 @@ module Gitlab def execute! result = execute_steps - if result[:status] == :success + ::Gitlab::Tracking.event("self_monitoring", "project_created") result elsif STEPS_ALLOWED_TO_FAIL.include?(result[:last_step]) + ::Gitlab::Tracking.event("self_monitoring", "project_created") success else raise StandardError, result[:message] diff --git a/lib/gitlab/regex.rb b/lib/gitlab/regex.rb index e3a434dfe35..d9300da38a5 100644 --- a/lib/gitlab/regex.rb +++ b/lib/gitlab/regex.rb @@ -19,7 +19,7 @@ module Gitlab # See https://github.com/docker/distribution/blob/master/reference/regexp.go. # def container_repository_name_regex - @container_repository_regex ||= %r{\A[a-z0-9]+((?:[._/]|__|[-])[a-z0-9]+)*\Z} + @container_repository_regex ||= %r{\A[a-z0-9]+((?:[._/]|__|[-]{0,10})[a-z0-9]+)*\Z} end ## diff --git a/package.json b/package.json index e55b525753a..96d443758d6 100644 --- a/package.json +++ b/package.json @@ -41,7 +41,7 @@ "@gitlab/ui": "7.15.2", "@gitlab/visual-review-tools": "1.2.0", "@sentry/browser": "^5.7.1", - "@sourcegraph/code-host-integration": "^0.0.13", + "@sourcegraph/code-host-integration": "^0.0.14", "apollo-cache-inmemory": "^1.6.3", "apollo-client": "^2.6.4", "apollo-link": "^1.2.11", diff --git a/spec/controllers/projects/tags_controller_spec.rb b/spec/controllers/projects/tags_controller_spec.rb index f077b4c99fc..7e5237facf6 100644 --- a/spec/controllers/projects/tags_controller_spec.rb +++ b/spec/controllers/projects/tags_controller_spec.rb @@ -13,7 +13,7 @@ describe Projects::TagsController do end it 'returns the tags for the page' do - expect(assigns(:tags).map(&:name)).to eq(['v1.1.0', 'v1.0.0']) + expect(assigns(:tags).map(&:name)).to include('v1.1.0', 'v1.0.0') end it 'returns releases matching those tags' do diff --git a/spec/controllers/projects_controller_spec.rb b/spec/controllers/projects_controller_spec.rb index ff0259cd40d..d16201fff5a 100644 --- a/spec/controllers/projects_controller_spec.rb +++ b/spec/controllers/projects_controller_spec.rb @@ -837,8 +837,7 @@ describe ProjectsController do get :refs, params: { namespace_id: project.namespace, id: project, sort: 'updated_desc' } expect(json_response['Branches']).to include('master') - expect(json_response['Tags'].first).to eq('v1.1.0') - expect(json_response['Tags'].last).to eq('v1.0.0') + expect(json_response['Tags']).to include('v1.0.0') expect(json_response['Commits']).to be_nil end diff --git a/spec/features/projects/tags/user_edits_tags_spec.rb b/spec/features/projects/tags/user_edits_tags_spec.rb index 63f97eeb4e0..b1cb7685f63 100644 --- a/spec/features/projects/tags/user_edits_tags_spec.rb +++ b/spec/features/projects/tags/user_edits_tags_spec.rb @@ -21,23 +21,21 @@ describe 'Project > Tags', :js do context 'page with tags list' do it 'shows tag name' do - page.within first('.tags > .content-list > li') do - expect(page.find('.row-main-content')).to have_content 'v1.1.0 Version 1.1.0' - end + expect(page).to have_content 'v1.1.0 Version 1.1.0' end it 'shows tag edit button' do - page.within first('.tags > .content-list > li') do - edit_btn = page.find('.row-fixed-content.controls a.btn-edit') + page.within '.tags > .content-list' do + edit_btn = page.find("li > .row-fixed-content.controls a.btn-edit[href='/#{project.full_path}/-/tags/v1.1.0/release/edit']") - expect(edit_btn['href']).to have_content '/tags/v1.1.0/release/edit' + expect(edit_btn['href']).to end_with("/#{project.full_path}/-/tags/v1.1.0/release/edit") end end end context 'edit tag release notes' do before do - find('.tags > .content-list > li:first-child .row-fixed-content.controls a.btn-edit').click + page.find("li > .row-fixed-content.controls a.btn-edit[href='/#{project.full_path}/-/tags/v1.1.0/release/edit']").click end it 'shows tag name header' do diff --git a/spec/features/tags/developer_deletes_tag_spec.rb b/spec/features/tags/developer_deletes_tag_spec.rb index 0fc62a578f9..50eac8ddaed 100644 --- a/spec/features/tags/developer_deletes_tag_spec.rb +++ b/spec/features/tags/developer_deletes_tag_spec.rb @@ -17,7 +17,7 @@ describe 'Developer deletes tag' do it 'deletes the tag' do expect(page).to have_content 'v1.1.0' - delete_first_tag + delete_tag 'v1.1.0' expect(page).not_to have_content 'v1.1.0' end @@ -46,15 +46,15 @@ describe 'Developer deletes tag' do end it 'shows the error message' do - delete_first_tag + delete_tag 'v1.1.0' expect(page).to have_content('Do not delete tags') end end - def delete_first_tag + def delete_tag(tag) page.within('.content') do - accept_confirm { first('.btn-remove').click } + accept_confirm { find("li > .row-fixed-content.controls a.btn-remove[href='/#{project.full_path}/-/tags/#{tag}']").click } end end end diff --git a/spec/features/tags/developer_updates_tag_spec.rb b/spec/features/tags/developer_updates_tag_spec.rb index 0cdd953b9ae..167079c3f31 100644 --- a/spec/features/tags/developer_updates_tag_spec.rb +++ b/spec/features/tags/developer_updates_tag_spec.rb @@ -15,9 +15,7 @@ describe 'Developer updates tag' do context 'from the tags list page' do it 'updates the release notes' do - page.within(first('.content-list .controls')) do - click_link 'Edit release notes' - end + find("li > .row-fixed-content.controls a.btn-edit[href='/#{project.full_path}/-/tags/v1.1.0/release/edit']").click fill_in 'release_description', with: 'Awesome release notes' click_button 'Save changes' diff --git a/spec/finders/tags_finder_spec.rb b/spec/finders/tags_finder_spec.rb index e9f29ab2441..582d82bbf79 100644 --- a/spec/finders/tags_finder_spec.rb +++ b/spec/finders/tags_finder_spec.rb @@ -95,24 +95,25 @@ describe TagsFinder do end context 'filter and sort' do - it 'filters tags by name and sorts by recently_updated' do - params = { sort: 'updated_desc', search: 'v1' } - tags_finder = described_class.new(repository, params) + let(:tags_to_compare) { %w[v1.0.0 v1.1.0] } + subject { described_class.new(repository, params).execute.select { |tag| tags_to_compare.include?(tag.name) } } - result = tags_finder.execute + context 'when sort by updated_desc' do + let(:params) { { sort: 'updated_desc', search: 'v1' } } - expect(result.first.name).to eq('v1.1.0') - expect(result.count).to eq(2) + it 'filters tags by name' do + expect(subject.first.name).to eq('v1.1.0') + expect(subject.count).to eq(2) + end end - it 'filters tags by name and sorts by last_updated' do - params = { sort: 'updated_asc', search: 'v1' } - tags_finder = described_class.new(repository, params) - - result = tags_finder.execute + context 'when sort by updated_asc' do + let(:params) { { sort: 'updated_asc', search: 'v1' } } - expect(result.first.name).to eq('v1.0.0') - expect(result.count).to eq(2) + it 'filters tags by name' do + expect(subject.first.name).to eq('v1.0.0') + expect(subject.count).to eq(2) + end end end end diff --git a/spec/frontend/fixtures/test_report.rb b/spec/frontend/fixtures/test_report.rb new file mode 100644 index 00000000000..d26bba9b9d0 --- /dev/null +++ b/spec/frontend/fixtures/test_report.rb @@ -0,0 +1,30 @@ +# frozen_string_literal: true + +require "spec_helper" + +describe Projects::PipelinesController, "(JavaScript fixtures)", type: :controller do + include JavaScriptFixturesHelpers + + let(:namespace) { create(:namespace, name: "frontend-fixtures") } + let(:project) { create(:project, :repository, namespace: namespace, path: "pipelines-project") } + let(:commit) { create(:commit, project: project) } + let(:user) { create(:user, developer_projects: [project], email: commit.author_email) } + let(:pipeline) { create(:ci_pipeline, :with_test_reports, project: project, user: user) } + + render_views + + before do + sign_in(user) + stub_feature_flags(junit_pipeline_view: true) + end + + it "pipelines/test_report.json" do + get :test_report, params: { + namespace_id: project.namespace, + project_id: project, + id: pipeline.id + }, format: :json + + expect(response).to be_successful + end +end diff --git a/spec/frontend/pipelines/test_reports/mock_data.js b/spec/frontend/pipelines/test_reports/mock_data.js index b0f22bc63fb..1d03f0b655f 100644 --- a/spec/frontend/pipelines/test_reports/mock_data.js +++ b/spec/frontend/pipelines/test_reports/mock_data.js @@ -1,41 +1,6 @@ -import { formatTime } from '~/lib/utils/datetime_utility'; import { TestStatus } from '~/pipelines/constants'; -export const testCases = [ - { - classname: 'spec.test_spec', - execution_time: 0.000748, - name: 'Test#subtract when a is 1 and b is 2 raises an error', - stack_trace: null, - status: TestStatus.SUCCESS, - system_output: null, - }, - { - classname: 'spec.test_spec', - execution_time: 0.000064, - name: 'Test#subtract when a is 2 and b is 1 returns correct result', - stack_trace: null, - status: TestStatus.SUCCESS, - system_output: null, - }, - { - classname: 'spec.test_spec', - execution_time: 0.009292, - name: 'Test#sum when a is 1 and b is 2 returns summary', - stack_trace: null, - status: TestStatus.FAILED, - system_output: - "Failure/Error: is_expected.to eq(3)\n\n expected: 3\n got: -1\n\n (compared using ==)\n./spec/test_spec.rb:12:in `block (4 levels) in <top (required)>'", - }, - { - classname: 'spec.test_spec', - execution_time: 0.00018, - name: 'Test#sum when a is 100 and b is 200 returns summary', - stack_trace: null, - status: TestStatus.FAILED, - system_output: - "Failure/Error: is_expected.to eq(300)\n\n expected: 300\n got: -100\n\n (compared using ==)\n./spec/test_spec.rb:21:in `block (4 levels) in <top (required)>'", - }, +export default [ { classname: 'spec.test_spec', execution_time: 0, @@ -45,79 +10,3 @@ export const testCases = [ system_output: null, }, ]; - -export const testCasesFormatted = [ - { - ...testCases[2], - icon: 'status_failed_borderless', - formattedTime: formatTime(testCases[0].execution_time * 1000), - }, - { - ...testCases[3], - icon: 'status_failed_borderless', - formattedTime: formatTime(testCases[1].execution_time * 1000), - }, - { - ...testCases[4], - icon: 'status_skipped_borderless', - formattedTime: formatTime(testCases[2].execution_time * 1000), - }, - { - ...testCases[0], - icon: 'status_success_borderless', - formattedTime: formatTime(testCases[3].execution_time * 1000), - }, - { - ...testCases[1], - icon: 'status_success_borderless', - formattedTime: formatTime(testCases[4].execution_time * 1000), - }, -]; - -export const testSuites = [ - { - error_count: 0, - failed_count: 2, - name: 'rspec:osx', - skipped_count: 0, - success_count: 2, - test_cases: testCases, - total_count: 4, - total_time: 60, - }, - { - error_count: 0, - failed_count: 10, - name: 'rspec:osx', - skipped_count: 0, - success_count: 50, - test_cases: [], - total_count: 60, - total_time: 0.010284, - }, -]; - -export const testSuitesFormatted = testSuites.map(x => ({ - ...x, - formattedTime: formatTime(x.total_time * 1000), -})); - -export const testReports = { - error_count: 0, - failed_count: 2, - skipped_count: 0, - success_count: 2, - test_suites: testSuites, - total_count: 4, - total_time: 0.010284, -}; - -export const testReportsWithNoSuites = { - error_count: 0, - failed_count: 2, - skipped_count: 0, - success_count: 2, - test_suites: [], - total_count: 4, - total_time: 0.010284, -}; diff --git a/spec/frontend/pipelines/test_reports/stores/actions_spec.js b/spec/frontend/pipelines/test_reports/stores/actions_spec.js index c1721e12234..d7007eb7631 100644 --- a/spec/frontend/pipelines/test_reports/stores/actions_spec.js +++ b/spec/frontend/pipelines/test_reports/stores/actions_spec.js @@ -2,10 +2,10 @@ import MockAdapter from 'axios-mock-adapter'; import axios from '~/lib/utils/axios_utils'; import * as actions from '~/pipelines/stores/test_reports/actions'; import * as types from '~/pipelines/stores/test_reports/mutation_types'; +import { getJSONFixture } from 'helpers/fixtures'; import { TEST_HOST } from '../../../helpers/test_constants'; import testAction from '../../../helpers/vuex_action_helper'; import createFlash from '~/flash'; -import { testReports } from '../mock_data'; jest.mock('~/flash.js'); @@ -13,6 +13,8 @@ describe('Actions TestReports Store', () => { let mock; let state; + const testReports = getJSONFixture('pipelines/test_report.json'); + const endpoint = `${TEST_HOST}/test_reports.json`; const defaultState = { endpoint, diff --git a/spec/frontend/pipelines/test_reports/stores/getters_spec.js b/spec/frontend/pipelines/test_reports/stores/getters_spec.js index e630a005409..cfd0ecdcb30 100644 --- a/spec/frontend/pipelines/test_reports/stores/getters_spec.js +++ b/spec/frontend/pipelines/test_reports/stores/getters_spec.js @@ -1,9 +1,12 @@ import * as getters from '~/pipelines/stores/test_reports/getters'; -import { testReports, testSuitesFormatted, testCasesFormatted } from '../mock_data'; +import { iconForTestStatus } from '~/pipelines/stores/test_reports/utils'; +import { getJSONFixture } from 'helpers/fixtures'; describe('Getters TestReports Store', () => { let state; + const testReports = getJSONFixture('pipelines/test_report.json'); + const defaultState = { testReports, selectedSuite: testReports.test_suites[0], @@ -28,7 +31,13 @@ describe('Getters TestReports Store', () => { it('should return the test suites', () => { setupState(); - expect(getters.getTestSuites(state)).toEqual(testSuitesFormatted); + const suites = getters.getTestSuites(state); + const expected = testReports.test_suites.map(x => ({ + ...x, + formattedTime: '00:00:00', + })); + + expect(suites).toEqual(expected); }); it('should return an empty array when testReports is empty', () => { @@ -42,7 +51,14 @@ describe('Getters TestReports Store', () => { it('should return the test cases inside the suite', () => { setupState(); - expect(getters.getSuiteTests(state)).toEqual(testCasesFormatted); + const cases = getters.getSuiteTests(state); + const expected = testReports.test_suites[0].test_cases.map(x => ({ + ...x, + formattedTime: '00:00:00', + icon: iconForTestStatus(x.status), + })); + + expect(cases).toEqual(expected); }); it('should return an empty array when testReports is empty', () => { diff --git a/spec/frontend/pipelines/test_reports/stores/mutations_spec.js b/spec/frontend/pipelines/test_reports/stores/mutations_spec.js index ad5b7f91163..b891415f705 100644 --- a/spec/frontend/pipelines/test_reports/stores/mutations_spec.js +++ b/spec/frontend/pipelines/test_reports/stores/mutations_spec.js @@ -1,10 +1,12 @@ import * as types from '~/pipelines/stores/test_reports/mutation_types'; import mutations from '~/pipelines/stores/test_reports/mutations'; -import { testReports, testSuites } from '../mock_data'; +import { getJSONFixture } from 'helpers/fixtures'; describe('Mutations TestReports Store', () => { let mockState; + const testReports = getJSONFixture('pipelines/test_report.json'); + const defaultState = { endpoint: '', testReports: {}, @@ -27,7 +29,7 @@ describe('Mutations TestReports Store', () => { describe('set reports', () => { it('should set testReports', () => { - const expectedState = Object.assign({}, mockState, { testReports }); + const expectedState = { ...mockState, testReports }; mutations[types.SET_REPORTS](mockState, testReports); expect(mockState.testReports).toEqual(expectedState.testReports); @@ -36,10 +38,10 @@ describe('Mutations TestReports Store', () => { describe('set selected suite', () => { it('should set selectedSuite', () => { - const expectedState = Object.assign({}, mockState, { selectedSuite: testSuites[0] }); - mutations[types.SET_SELECTED_SUITE](mockState, testSuites[0]); + const selectedSuite = testReports.test_suites[0]; + mutations[types.SET_SELECTED_SUITE](mockState, selectedSuite); - expect(mockState.selectedSuite).toEqual(expectedState.selectedSuite); + expect(mockState.selectedSuite).toEqual(selectedSuite); }); }); diff --git a/spec/frontend/pipelines/test_reports/test_reports_spec.js b/spec/frontend/pipelines/test_reports/test_reports_spec.js index 4d6422745a9..033c3300098 100644 --- a/spec/frontend/pipelines/test_reports/test_reports_spec.js +++ b/spec/frontend/pipelines/test_reports/test_reports_spec.js @@ -1,13 +1,15 @@ import Vuex from 'vuex'; import TestReports from '~/pipelines/components/test_reports/test_reports.vue'; import { shallowMount } from '@vue/test-utils'; -import { testReports } from './mock_data'; import * as actions from '~/pipelines/stores/test_reports/actions'; +import { getJSONFixture } from 'helpers/fixtures'; describe('Test reports app', () => { let wrapper; let store; + const testReports = getJSONFixture('pipelines/test_report.json'); + const loadingSpinner = () => wrapper.find('.js-loading-spinner'); const testsDetail = () => wrapper.find('.js-tests-detail'); const noTestsToShow = () => wrapper.find('.js-no-tests-to-show'); diff --git a/spec/frontend/pipelines/test_reports/test_suite_table_spec.js b/spec/frontend/pipelines/test_reports/test_suite_table_spec.js index b4305719ea8..bc5d8647d6a 100644 --- a/spec/frontend/pipelines/test_reports/test_suite_table_spec.js +++ b/spec/frontend/pipelines/test_reports/test_suite_table_spec.js @@ -3,18 +3,26 @@ import SuiteTable from '~/pipelines/components/test_reports/test_suite_table.vue import * as getters from '~/pipelines/stores/test_reports/getters'; import { TestStatus } from '~/pipelines/constants'; import { shallowMount } from '@vue/test-utils'; -import { testSuites, testCases } from './mock_data'; +import { getJSONFixture } from 'helpers/fixtures'; +import skippedTestCases from './mock_data'; describe('Test reports suite table', () => { let wrapper; let store; + const { + test_suites: [testSuite], + } = getJSONFixture('pipelines/test_report.json'); + + testSuite.test_cases = [...testSuite.test_cases, ...skippedTestCases]; + const testCases = testSuite.test_cases; + const noCasesMessage = () => wrapper.find('.js-no-test-cases'); const allCaseRows = () => wrapper.findAll('.js-case-row'); const findCaseRowAtIndex = index => wrapper.findAll('.js-case-row').at(index); const findIconForRow = (row, status) => row.find(`.ci-status-icon-${status}`); - const createComponent = (suite = testSuites[0]) => { + const createComponent = (suite = testSuite) => { store = new Vuex.Store({ state: { selectedSuite: suite, diff --git a/spec/frontend/pipelines/test_reports/test_summary_spec.js b/spec/frontend/pipelines/test_reports/test_summary_spec.js index 19a7755dbdc..864c7b6f4de 100644 --- a/spec/frontend/pipelines/test_reports/test_summary_spec.js +++ b/spec/frontend/pipelines/test_reports/test_summary_spec.js @@ -1,10 +1,14 @@ import Summary from '~/pipelines/components/test_reports/test_summary.vue'; import { mount } from '@vue/test-utils'; -import { testSuites } from './mock_data'; +import { getJSONFixture } from 'helpers/fixtures'; describe('Test reports summary', () => { let wrapper; + const { + test_suites: [testSuite], + } = getJSONFixture('pipelines/test_report.json'); + const backButton = () => wrapper.find('.js-back-button'); const totalTests = () => wrapper.find('.js-total-tests'); const failedTests = () => wrapper.find('.js-failed-tests'); @@ -13,7 +17,7 @@ describe('Test reports summary', () => { const duration = () => wrapper.find('.js-duration'); const defaultProps = { - report: testSuites[0], + report: testSuite, showBack: false, }; @@ -72,7 +76,7 @@ describe('Test reports summary', () => { }); it('displays the correctly formatted duration', () => { - expect(duration().text()).toBe('00:01:00'); + expect(duration().text()).toBe('00:00:00'); }); }); }); diff --git a/spec/frontend/pipelines/test_reports/test_summary_table_spec.js b/spec/frontend/pipelines/test_reports/test_summary_table_spec.js index e7599d5cdbc..7d06d96fe75 100644 --- a/spec/frontend/pipelines/test_reports/test_summary_table_spec.js +++ b/spec/frontend/pipelines/test_reports/test_summary_table_spec.js @@ -2,7 +2,7 @@ import Vuex from 'vuex'; import SummaryTable from '~/pipelines/components/test_reports/test_summary_table.vue'; import * as getters from '~/pipelines/stores/test_reports/getters'; import { mount, createLocalVue } from '@vue/test-utils'; -import { testReports, testReportsWithNoSuites } from './mock_data'; +import { getJSONFixture } from 'helpers/fixtures'; const localVue = createLocalVue(); localVue.use(Vuex); @@ -11,6 +11,8 @@ describe('Test reports summary table', () => { let wrapper; let store; + const testReports = getJSONFixture('pipelines/test_report.json'); + const allSuitesRows = () => wrapper.findAll('.js-suite-row'); const noSuitesToShow = () => wrapper.find('.js-no-tests-suites'); @@ -44,7 +46,7 @@ describe('Test reports summary table', () => { describe('when there are no test suites', () => { beforeEach(() => { - createComponent({ testReportsWithNoSuites }); + createComponent({ test_suites: [] }); }); it('displays the no suites to show message', () => { diff --git a/spec/frontend/vue_shared/components/time_ago_tooltip_spec.js b/spec/frontend/vue_shared/components/time_ago_tooltip_spec.js index 536bb57b946..f1f231c1a29 100644 --- a/spec/frontend/vue_shared/components/time_ago_tooltip_spec.js +++ b/spec/frontend/vue_shared/components/time_ago_tooltip_spec.js @@ -1,44 +1,40 @@ -import Vue from 'vue'; -import timeagoTooltip from '~/vue_shared/components/time_ago_tooltip.vue'; +import { shallowMount, createLocalVue } from '@vue/test-utils'; +import TimeAgoTooltip from '~/vue_shared/components/time_ago_tooltip.vue'; import { formatDate, getTimeago } from '~/lib/utils/datetime_utility'; describe('Time ago with tooltip component', () => { - let TimeagoTooltip; let vm; - beforeEach(() => { - TimeagoTooltip = Vue.extend(timeagoTooltip); - }); + const buildVm = (propsData = {}) => { + vm = shallowMount(TimeAgoTooltip, { + attachToDocument: true, + sync: false, + propsData, + localVue: createLocalVue(), + }); + }; + const timestamp = '2017-05-08T14:57:39.781Z'; afterEach(() => { - vm.$destroy(); + vm.destroy(); }); it('should render timeago with a bootstrap tooltip', () => { - vm = new TimeagoTooltip({ - propsData: { - time: '2017-05-08T14:57:39.781Z', - }, - }).$mount(); - - expect(vm.$el.tagName).toEqual('TIME'); - expect(vm.$el.getAttribute('data-original-title')).toEqual( - formatDate('2017-05-08T14:57:39.781Z'), - ); - + buildVm({ + time: timestamp, + }); const timeago = getTimeago(); - expect(vm.$el.textContent.trim()).toEqual(timeago.format('2017-05-08T14:57:39.781Z')); + expect(vm.attributes('data-original-title')).toEqual(formatDate(timestamp)); + expect(vm.text()).toEqual(timeago.format(timestamp)); }); it('should render provided html class', () => { - vm = new TimeagoTooltip({ - propsData: { - time: '2017-05-08T14:57:39.781Z', - cssClass: 'foo', - }, - }).$mount(); + buildVm({ + time: timestamp, + cssClass: 'foo', + }); - expect(vm.$el.classList.contains('foo')).toEqual(true); + expect(vm.classes()).toContain('foo'); }); }); diff --git a/spec/lib/gitlab/ci/templates/auto_devops_gitlab_ci_yaml_spec.rb b/spec/lib/gitlab/ci/templates/auto_devops_gitlab_ci_yaml_spec.rb new file mode 100644 index 00000000000..c2f9930056a --- /dev/null +++ b/spec/lib/gitlab/ci/templates/auto_devops_gitlab_ci_yaml_spec.rb @@ -0,0 +1,110 @@ +# frozen_string_literal: true + +require 'spec_helper' + +describe 'Auto-DevOps.gitlab-ci.yml' do + subject(:template) { Gitlab::Template::GitlabCiYmlTemplate.find('Auto-DevOps') } + + describe 'the created pipeline' do + let(:user) { create(:admin) } + let(:default_branch) { 'master' } + let(:pipeline_branch) { default_branch } + let(:project) { create(:project, :custom_repo, files: { 'README.md' => '' }) } + let(:service) { Ci::CreatePipelineService.new(project, user, ref: pipeline_branch ) } + let(:pipeline) { service.execute!(:push) } + let(:build_names) { pipeline.builds.pluck(:name) } + + before do + stub_ci_pipeline_yaml_file(template.content) + allow_any_instance_of(Ci::BuildScheduleWorker).to receive(:perform).and_return(true) + allow(project).to receive(:default_branch).and_return(default_branch) + end + + it 'creates a build and a test job' do + expect(build_names).to include('build', 'test') + end + + context 'when the project has no active cluster' do + it 'only creates a build and a test stage' do + expect(pipeline.stages_names).to eq(%w(build test)) + end + + it 'does not create any deployment-related builds' do + expect(build_names).not_to include('production') + expect(build_names).not_to include('production_manual') + expect(build_names).not_to include('staging') + expect(build_names).not_to include('canary') + expect(build_names).not_to include('review') + expect(build_names).not_to include(a_string_matching(/rollout \d+%/)) + end + end + + context 'when the project has an active cluster' do + let(:cluster) { create(:cluster, :project, :provided_by_gcp, projects: [project]) } + + before do + allow(cluster).to receive(:active?).and_return(true) + end + + describe 'deployment-related builds' do + context 'on default branch' do + it 'does not include rollout jobs besides production' do + expect(build_names).to include('production') + expect(build_names).not_to include('production_manual') + expect(build_names).not_to include('staging') + expect(build_names).not_to include('canary') + expect(build_names).not_to include('review') + expect(build_names).not_to include(a_string_matching(/rollout \d+%/)) + end + + context 'when STAGING_ENABLED=1' do + before do + create(:ci_variable, project: project, key: 'STAGING_ENABLED', value: '1') + end + + it 'includes a staging job and a production_manual job' do + expect(build_names).not_to include('production') + expect(build_names).to include('production_manual') + expect(build_names).to include('staging') + expect(build_names).not_to include('canary') + expect(build_names).not_to include('review') + expect(build_names).not_to include(a_string_matching(/rollout \d+%/)) + end + end + + context 'when CANARY_ENABLED=1' do + before do + create(:ci_variable, project: project, key: 'CANARY_ENABLED', value: '1') + end + + it 'includes a canary job and a production_manual job' do + expect(build_names).not_to include('production') + expect(build_names).to include('production_manual') + expect(build_names).not_to include('staging') + expect(build_names).to include('canary') + expect(build_names).not_to include('review') + expect(build_names).not_to include(a_string_matching(/rollout \d+%/)) + end + end + end + + context 'outside of default branch' do + let(:pipeline_branch) { 'patch-1' } + + before do + project.repository.create_branch(pipeline_branch) + end + + it 'does not include rollout jobs besides review' do + expect(build_names).not_to include('production') + expect(build_names).not_to include('production_manual') + expect(build_names).not_to include('staging') + expect(build_names).not_to include('canary') + expect(build_names).to include('review') + expect(build_names).not_to include(a_string_matching(/rollout \d+%/)) + end + end + end + end + end +end diff --git a/spec/lib/gitlab/database_importers/self_monitoring/project/create_service_spec.rb b/spec/lib/gitlab/database_importers/self_monitoring/project/create_service_spec.rb index 5b1a17e734d..ee3c99afdf1 100644 --- a/spec/lib/gitlab/database_importers/self_monitoring/project/create_service_spec.rb +++ b/spec/lib/gitlab/database_importers/self_monitoring/project/create_service_spec.rb @@ -279,5 +279,11 @@ describe Gitlab::DatabaseImporters::SelfMonitoring::Project::CreateService do end end end + + it "tracks successful install" do + expect(Gitlab::Tracking).to receive(:event).with("self_monitoring", "project_created") + + result + end end end diff --git a/spec/lib/gitlab/regex_spec.rb b/spec/lib/gitlab/regex_spec.rb index 1397add9f5a..c580b46cf8d 100644 --- a/spec/lib/gitlab/regex_spec.rb +++ b/spec/lib/gitlab/regex_spec.rb @@ -61,6 +61,12 @@ describe Gitlab::Regex do it { is_expected.to match('my/image') } it { is_expected.to match('my/awesome/image-1') } it { is_expected.to match('my/awesome/image.test') } + it { is_expected.to match('my/awesome/image--test') } + # docker distribution allows for infinite `-` + # https://github.com/docker/distribution/blob/master/reference/regexp.go#L13 + # but we have a range of 0,10 to add a reasonable limit. + it { is_expected.not_to match('my/image-----------test') } + it { is_expected.not_to match('my/image-.test') } it { is_expected.not_to match('.my/image') } it { is_expected.not_to match('my/image.') } end diff --git a/spec/models/project_services/prometheus_service_spec.rb b/spec/models/project_services/prometheus_service_spec.rb index bc22818ede7..73748b39922 100644 --- a/spec/models/project_services/prometheus_service_spec.rb +++ b/spec/models/project_services/prometheus_service_spec.rb @@ -262,4 +262,28 @@ describe PrometheusService, :use_clean_rails_memory_store_caching do end end end + + describe '#track_events after_commit callback' do + before do + allow(service).to receive(:prometheus_available?).and_return(true) + end + + context "enabling manual_configuration" do + it "tracks enable event" do + service.update!(manual_configuration: false) + + expect(Gitlab::Tracking).to receive(:event).with('cluster:services:prometheus', 'enabled_manual_prometheus') + + service.update!(manual_configuration: true) + end + + it "tracks disable event" do + service.update!(manual_configuration: true) + + expect(Gitlab::Tracking).to receive(:event).with('cluster:services:prometheus', 'disabled_manual_prometheus') + + service.update!(manual_configuration: false) + end + end + end end diff --git a/spec/models/repository_spec.rb b/spec/models/repository_spec.rb index 92450bfdaff..9cb3229aeb1 100644 --- a/spec/models/repository_spec.rb +++ b/spec/models/repository_spec.rb @@ -66,14 +66,16 @@ describe Repository do end describe 'tags_sorted_by' do + let(:tags_to_compare) { %w[v1.0.0 v1.1.0] } + context 'name_desc' do - subject { repository.tags_sorted_by('name_desc').map(&:name) } + subject { repository.tags_sorted_by('name_desc').map(&:name) & tags_to_compare } it { is_expected.to eq(['v1.1.0', 'v1.0.0']) } end context 'name_asc' do - subject { repository.tags_sorted_by('name_asc').map(&:name) } + subject { repository.tags_sorted_by('name_asc').map(&:name) & tags_to_compare } it { is_expected.to eq(['v1.0.0', 'v1.1.0']) } end @@ -115,7 +117,7 @@ describe Repository do context 'annotated tag pointing to a blob' do let(:annotated_tag_name) { 'annotated-tag' } - subject { repository.tags_sorted_by('updated_asc').map(&:name) } + subject { repository.tags_sorted_by('updated_asc').map(&:name) & (tags_to_compare + [annotated_tag_name]) } before do options = { message: 'test tag message\n', diff --git a/spec/requests/api/tags_spec.rb b/spec/requests/api/tags_spec.rb index 3c6ec631664..dca87d5e4ce 100644 --- a/spec/requests/api/tags_spec.rb +++ b/spec/requests/api/tags_spec.rb @@ -7,6 +7,7 @@ describe API::Tags do let(:guest) { create(:user).tap { |u| project.add_guest(u) } } let(:project) { create(:project, :repository, creator: user, path: 'my.project') } let(:tag_name) { project.repository.find_tag('v1.1.0').name } + let(:tag_message) { project.repository.find_tag('v1.1.0').message } let(:project_id) { project.id } let(:current_user) { nil } @@ -75,7 +76,7 @@ describe API::Tags do expect(response).to have_gitlab_http_status(200) expect(response).to match_response_schema('public_api/v4/tags') expect(response).to include_pagination_headers - expect(json_response.first['name']).to eq(tag_name) + expect(json_response.map { |r| r['name'] }).to include(tag_name) end context 'when repository is disabled' do @@ -135,9 +136,10 @@ describe API::Tags do expect(response).to have_gitlab_http_status(200) expect(response).to match_response_schema('public_api/v4/tags') expect(response).to include_pagination_headers - expect(json_response.first['name']).to eq(tag_name) - expect(json_response.first['message']).to eq('Version 1.1.0') - expect(json_response.first['release']['description']).to eq(description) + + expected_tag = json_response.find { |r| r['name'] == tag_name } + expect(expected_tag['message']).to eq(tag_message) + expect(expected_tag['release']['description']).to eq(description) end end end diff --git a/yarn.lock b/yarn.lock index 4f66345fed0..ab929647c4b 100644 --- a/yarn.lock +++ b/yarn.lock @@ -970,10 +970,10 @@ "@sentry/types" "5.7.1" tslib "^1.9.3" -"@sourcegraph/code-host-integration@^0.0.13": - version "0.0.13" - resolved "https://registry.yarnpkg.com/@sourcegraph/code-host-integration/-/code-host-integration-0.0.13.tgz#4fd5fe1e0088c63b2a26be231c5a2a4ca79b1596" - integrity sha512-IjF9gb9e8dG8p12DKg5Z7UMOVQO/ClH3AyMCPfX/qH7DH/0b55WH6stYVqZu6y776quFonO4Z9gWYM8pQZjzKw== +"@sourcegraph/code-host-integration@^0.0.14": + version "0.0.14" + resolved "https://registry.yarnpkg.com/@sourcegraph/code-host-integration/-/code-host-integration-0.0.14.tgz#e12b08371dc37bf4a468450b008c6e167705e1a8" + integrity sha512-S4+K+3RKFd49Btl1D9LOdWXROgXevUwOBwp+vDUuGgzT2d6Y+qjalUJ0t8CjbYzdBdJun+2/Zi1+SXfm+S+xVg== "@types/anymatch@*": version "1.3.0" |