Add latest changes from gitlab-org/gitlab@master
This commit is contained in:
parent
4db9eeb44a
commit
68b6846fa6
38 changed files with 394 additions and 235 deletions
|
@ -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';
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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
|
||||
|
||||
|
|
5
changelogs/unreleased/34157-apm_snowplow_events.yml
Normal file
5
changelogs/unreleased/34157-apm_snowplow_events.yml
Normal file
|
@ -0,0 +1,5 @@
|
|||
---
|
||||
title: Add snowplow events for APM
|
||||
merge_request: 19463
|
||||
author:
|
||||
type: added
|
|
@ -0,0 +1,5 @@
|
|||
---
|
||||
title: Update Container Registry naming restrictions to allow for sequential '-'
|
||||
merge_request: 20318
|
||||
author:
|
||||
type: fixed
|
5
changelogs/unreleased/remove_milestone_id_from_epics.yml
Normal file
5
changelogs/unreleased/remove_milestone_id_from_epics.yml
Normal file
|
@ -0,0 +1,5 @@
|
|||
---
|
||||
title: Remove milestone_id from epics
|
||||
merge_request: 20187
|
||||
author: Lee Tickett
|
||||
type: other
|
|
@ -47,7 +47,6 @@ tables:
|
|||
epics:
|
||||
whitelist:
|
||||
- id
|
||||
- milestone_id
|
||||
- group_id
|
||||
- author_id
|
||||
- assignee_id
|
||||
|
|
|
@ -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.
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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
|
||||
|
||||
|
|
|
@ -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]
|
||||
|
|
|
@ -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
|
||||
|
||||
##
|
||||
|
|
|
@ -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",
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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
|
||||
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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'
|
||||
|
|
|
@ -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)
|
||||
context 'when sort by updated_asc' do
|
||||
let(:params) { { sort: 'updated_asc', search: 'v1' } }
|
||||
|
||||
result = tags_finder.execute
|
||||
|
||||
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
|
||||
|
|
30
spec/frontend/fixtures/test_report.rb
Normal file
30
spec/frontend/fixtures/test_report.rb
Normal file
|
@ -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
|
|
@ -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,
|
||||
};
|
||||
|
|
|
@ -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,
|
||||
|
|
|
@ -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', () => {
|
||||
|
|
|
@ -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);
|
||||
});
|
||||
});
|
||||
|
||||
|
|
|
@ -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');
|
||||
|
|
|
@ -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,
|
||||
|
|
|
@ -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');
|
||||
});
|
||||
});
|
||||
});
|
||||
|
|
|
@ -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', () => {
|
||||
|
|
|
@ -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');
|
||||
});
|
||||
});
|
||||
|
|
110
spec/lib/gitlab/ci/templates/auto_devops_gitlab_ci_yaml_spec.rb
Normal file
110
spec/lib/gitlab/ci/templates/auto_devops_gitlab_ci_yaml_spec.rb
Normal file
|
@ -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
|
|
@ -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
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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',
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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"
|
||||
|
|
Loading…
Reference in a new issue