Commit 68b6846f authored by GitLab Bot's avatar GitLab Bot

Add latest changes from gitlab-org/gitlab@master

parent 4db9eeb4
import { TestStatus } from '~/pipelines/constants'; import { TestStatus } from '~/pipelines/constants';
import { formatTime, secondsToMilliseconds } from '~/lib/utils/datetime_utility'; import { formatTime, secondsToMilliseconds } from '~/lib/utils/datetime_utility';
function iconForTestStatus(status) { export function iconForTestStatus(status) {
switch (status) { switch (status) {
case 'success': case 'success':
return 'status_success_borderless'; return 'status_success_borderless';
......
...@@ -173,7 +173,7 @@ module Issuable ...@@ -173,7 +173,7 @@ module Issuable
private private
def milestone_is_valid 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 end
def description_max_length_for_new_records_is_valid def description_max_length_for_new_records_is_valid
......
...@@ -3,6 +3,8 @@ ...@@ -3,6 +3,8 @@
# Placeholder class for model that is implemented in EE # Placeholder class for model that is implemented in EE
# It reserves '&' as a reference prefix, but the table does not exists in CE # It reserves '&' as a reference prefix, but the table does not exists in CE
class Epic < ApplicationRecord class Epic < ApplicationRecord
self.ignored_columns += %i[milestone_id]
def self.link_reference_pattern def self.link_reference_pattern
nil nil
end end
......
...@@ -22,6 +22,8 @@ class PrometheusService < MonitoringService ...@@ -22,6 +22,8 @@ class PrometheusService < MonitoringService
after_save :clear_reactive_cache! after_save :clear_reactive_cache!
after_commit :track_events
def initialize_properties def initialize_properties
if properties.nil? if properties.nil?
self.properties = {} self.properties = {}
...@@ -116,4 +118,22 @@ class PrometheusService < MonitoringService ...@@ -116,4 +118,22 @@ class PrometheusService < MonitoringService
true true
end 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 end
...@@ -10,7 +10,13 @@ module Issuable ...@@ -10,7 +10,13 @@ module Issuable
end end
def execute 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 copy_resource_label_events
end end
......
---
title: Add snowplow events for APM
merge_request: 19463
author:
type: added
---
title: Update Container Registry naming restrictions to allow for sequential '-'
merge_request: 20318
author:
type: fixed
---
title: Remove milestone_id from epics
merge_request: 20187
author: Lee Tickett
type: other
...@@ -47,7 +47,6 @@ tables: ...@@ -47,7 +47,6 @@ tables:
epics: epics:
whitelist: whitelist:
- id - id
- milestone_id
- group_id - group_id
- author_id - author_id
- assignee_id - assignee_id
......
...@@ -97,7 +97,7 @@ query { ...@@ -97,7 +97,7 @@ query {
### The root node ### 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: When retrieving child nodes use:
- the `edges { node { } }` syntax. - the `edges { node { } }` syntax.
......
...@@ -73,6 +73,7 @@ The following table shows which languages, package managers and frameworks are s ...@@ -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) | | 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) | | 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 | | 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 | | Node.js | [NodeJsScan](https://github.com/ajinabraham/NodeJsScan) | 11.1 |
| PHP | [phpcs-security-audit](https://github.com/FloeDesignTechnologies/phpcs-security-audit) | 10.8 | | 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 | | Python ([pip](https://pip.pypa.io/en/stable/)) | [bandit](https://github.com/PyCQA/bandit) | 10.3 |
...@@ -185,6 +186,22 @@ variables: ...@@ -185,6 +186,22 @@ variables:
This will create individual `<analyzer-name>-sast` jobs for each analyzer that runs in your CI/CD pipeline. 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 ### Available variables
SAST can be [configured](#customizing-the-sast-settings) using environment variables. SAST can be [configured](#customizing-the-sast-settings) using environment variables.
...@@ -233,7 +250,8 @@ Timeout variables are not applicable for setups with [disabled Docker In Docker] ...@@ -233,7 +250,8 @@ Timeout variables are not applicable for setups with [disabled Docker In Docker]
Some analyzers can be customized with environment variables. Some analyzers can be customized with environment variables.
| Environment variable | Analyzer | Description | | 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_HOME` | spotbugs | The `ANT_HOME` environment variable. |
| `ANT_PATH` | spotbugs | Path to the `ant` executable. | | `ANT_PATH` | spotbugs | Path to the `ant` executable. |
| `GRADLE_PATH` | spotbugs | Path to the `gradle` executable. | | `GRADLE_PATH` | spotbugs | Path to the `gradle` executable. |
......
...@@ -29,10 +29,11 @@ module Gitlab ...@@ -29,10 +29,11 @@ module Gitlab
def execute! def execute!
result = execute_steps result = execute_steps
if result[:status] == :success if result[:status] == :success
::Gitlab::Tracking.event("self_monitoring", "project_created")
result result
elsif STEPS_ALLOWED_TO_FAIL.include?(result[:last_step]) elsif STEPS_ALLOWED_TO_FAIL.include?(result[:last_step])
::Gitlab::Tracking.event("self_monitoring", "project_created")
success success
else else
raise StandardError, result[:message] raise StandardError, result[:message]
......
...@@ -19,7 +19,7 @@ module Gitlab ...@@ -19,7 +19,7 @@ module Gitlab
# See https://github.com/docker/distribution/blob/master/reference/regexp.go. # See https://github.com/docker/distribution/blob/master/reference/regexp.go.
# #
def container_repository_name_regex 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 end
## ##
......
...@@ -13,7 +13,7 @@ describe Projects::TagsController do ...@@ -13,7 +13,7 @@ describe Projects::TagsController do
end end
it 'returns the tags for the page' do 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 end
it 'returns releases matching those tags' do it 'returns releases matching those tags' do
......
...@@ -837,8 +837,7 @@ describe ProjectsController do ...@@ -837,8 +837,7 @@ describe ProjectsController do
get :refs, params: { namespace_id: project.namespace, id: project, sort: 'updated_desc' } get :refs, params: { namespace_id: project.namespace, id: project, sort: 'updated_desc' }
expect(json_response['Branches']).to include('master') expect(json_response['Branches']).to include('master')
expect(json_response['Tags'].first).to eq('v1.1.0') expect(json_response['Tags']).to include('v1.0.0')
expect(json_response['Tags'].last).to eq('v1.0.0')
expect(json_response['Commits']).to be_nil expect(json_response['Commits']).to be_nil
end end
......
...@@ -21,23 +21,21 @@ describe 'Project > Tags', :js do ...@@ -21,23 +21,21 @@ describe 'Project > Tags', :js do
context 'page with tags list' do context 'page with tags list' do
it 'shows tag name' do it 'shows tag name' do
page.within first('.tags > .content-list > li') do expect(page).to have_content 'v1.1.0 Version 1.1.0'
expect(page.find('.row-main-content')).to have_content 'v1.1.0 Version 1.1.0'
end
end end
it 'shows tag edit button' do it 'shows tag edit button' do
page.within first('.tags > .content-list > li') do page.within '.tags > .content-list' do
edit_btn = page.find('.row-fixed-content.controls a.btn-edit') 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 end
end end
context 'edit tag release notes' do context 'edit tag release notes' do
before 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 end
it 'shows tag name header' do it 'shows tag name header' do
......
...@@ -17,7 +17,7 @@ describe 'Developer deletes tag' do ...@@ -17,7 +17,7 @@ describe 'Developer deletes tag' do
it 'deletes the tag' do it 'deletes the tag' do
expect(page).to have_content 'v1.1.0' 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' expect(page).not_to have_content 'v1.1.0'
end end
...@@ -46,15 +46,15 @@ describe 'Developer deletes tag' do ...@@ -46,15 +46,15 @@ describe 'Developer deletes tag' do
end end
it 'shows the error message' do it 'shows the error message' do
delete_first_tag delete_tag 'v1.1.0'
expect(page).to have_content('Do not delete tags') expect(page).to have_content('Do not delete tags')
end end
end end
def delete_first_tag def delete_tag(tag)
page.within('.content') do 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 end
end end
...@@ -15,9 +15,7 @@ describe 'Developer updates tag' do ...@@ -15,9 +15,7 @@ describe 'Developer updates tag' do
context 'from the tags list page' do context 'from the tags list page' do
it 'updates the release notes' do it 'updates the release notes' do
page.within(first('.content-list .controls')) do find("li > .row-fixed-content.controls a.btn-edit[href='/#{project.full_path}/-/tags/v1.1.0/release/edit']").click
click_link 'Edit release notes'
end
fill_in 'release_description', with: 'Awesome release notes' fill_in 'release_description', with: 'Awesome release notes'
click_button 'Save changes' click_button 'Save changes'
......
...@@ -95,24 +95,25 @@ describe TagsFinder do ...@@ -95,24 +95,25 @@ describe TagsFinder do
end end
context 'filter and sort' do context 'filter and sort' do
it 'filters tags by name and sorts by recently_updated' do let(:tags_to_compare) { %w[v1.0.0 v1.1.0] }
params = { sort: 'updated_desc', search: 'v1' } subject { described_class.new(repository, params).execute.select { |tag| tags_to_compare.include?(tag.name) } }
tags_finder = described_class.new(repository, params)
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') it 'filters tags by name' do
expect(result.count).to eq(2) expect(subject.first.name).to eq('v1.1.0')
expect(subject.count).to eq(2)
end
end end
it 'filters tags by name and sorts by last_updated' do context 'when sort by updated_asc' do
params = { sort: 'updated_asc', search: 'v1' } let(:params) { { sort: 'updated_asc', search: 'v1' } }
tags_finder = described_class.new(repository, params)
result = tags_finder.execute
expect(result.first.name).to eq('v1.0.0') it 'filters tags by name' do
expect(result.count).to eq(2) expect(subject.first.name).to eq('v1.0.0')
expect(subject.count).to eq(2)
end
end end
end end
end end
......
# 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
import { formatTime } from '~/lib/utils/datetime_utility';
import { TestStatus } from '~/pipelines/constants'; import { TestStatus } from '~/pipelines/constants';
export const testCases = [ export default [
{
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)>'",
},
{ {
classname: 'spec.test_spec', classname: 'spec.test_spec',
execution_time: 0, execution_time: 0,
...@@ -45,79 +10,3 @@ export const testCases = [ ...@@ -45,79 +10,3 @@ export const testCases = [
system_output: null, 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'; ...@@ -2,10 +2,10 @@ import MockAdapter from 'axios-mock-adapter';
import axios from '~/lib/utils/axios_utils'; import axios from '~/lib/utils/axios_utils';
import * as actions from '~/pipelines/stores/test_reports/actions'; import * as actions from '~/pipelines/stores/test_reports/actions';
import * as types from '~/pipelines/stores/test_reports/mutation_types'; import * as types from '~/pipelines/stores/test_reports/mutation_types';
import { getJSONFixture } from 'helpers/fixtures';
import { TEST_HOST } from '../../../helpers/test_constants'; import { TEST_HOST } from '../../../helpers/test_constants';
import testAction from '../../../helpers/vuex_action_helper'; import testAction from '../../../helpers/vuex_action_helper';
import createFlash from '~/flash'; import createFlash from '~/flash';
import { testReports } from '../mock_data';
jest.mock('~/flash.js'); jest.mock('~/flash.js');
...@@ -13,6 +13,8 @@ describe('Actions TestReports Store', () => { ...@@ -13,6 +13,8 @@ describe('Actions TestReports Store', () => {
let mock; let mock;
let state; let state;
const testReports = getJSONFixture('pipelines/test_report.json');
const endpoint = `${TEST_HOST}/test_reports.json`; const endpoint = `${TEST_HOST}/test_reports.json`;
const defaultState = { const defaultState = {
endpoint, endpoint,
......
import * as getters from '~/pipelines/stores/test_reports/getters'; 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', () => { describe('Getters TestReports Store', () => {
let state; let state;
const testReports = getJSONFixture('pipelines/test_report.json');
const defaultState = { const defaultState = {
testReports, testReports,
selectedSuite: testReports.test_suites[0], selectedSuite: testReports.test_suites[0],
...@@ -28,7 +31,13 @@ describe('Getters TestReports Store', () => { ...@@ -28,7 +31,13 @@ describe('Getters TestReports Store', () => {
it('should return the test suites', () => { it('should return the test suites', () => {
setupState(); 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', () => { it('should return an empty array when testReports is empty', () => {
...@@ -42,7 +51,14 @@ describe('Getters TestReports Store', () => { ...@@ -42,7 +51,14 @@ describe('Getters TestReports Store', () => {
it('should return the test cases inside the suite', () => { it('should return the test cases inside the suite', () => {
setupState(); 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', () => { it('should return an empty array when testReports is empty', () => {
......
import * as types from '~/pipelines/stores/test_reports/mutation_types'; import * as types from '~/pipelines/stores/test_reports/mutation_types';
import mutations from '~/pipelines/stores/test_reports/mutations'; import mutations from '~/pipelines/stores/test_reports/mutations';
import { testReports, testSuites } from '../mock_data'; import { getJSONFixture } from 'helpers/fixtures';
describe('Mutations TestReports Store', () => { describe('Mutations TestReports Store', () => {
let mockState; let mockState;
const testReports = getJSONFixture('pipelines/test_report.json');
const defaultState = { const defaultState = {
endpoint: '', endpoint: '',
testReports: {}, testReports: {},
...@@ -27,7 +29,7 @@ describe('Mutations TestReports Store', () => { ...@@ -27,7 +29,7 @@ describe('Mutations TestReports Store', () => {
describe('set reports', () => { describe('set reports', () => {
it('should set testReports', () => { it('should set testReports', () => {
const expectedState = Object.assign({}, mockState, { testReports }); const expectedState = { ...mockState, testReports };
mutations[types.SET_REPORTS](mockState, testReports); mutations[types.SET_REPORTS](mockState, testReports);
expect(mockState.testReports).toEqual(expectedState.testReports); expect(mockState.testReports).toEqual(expectedState.testReports);
...@@ -36,10 +38,10 @@ describe('Mutations TestReports Store', () => { ...@@ -36,10 +38,10 @@ describe('Mutations TestReports Store', () => {
describe('set selected suite', () => { describe('set selected suite', () => {
it('should set selectedSuite', () => { it('should set selectedSuite', () => {
const expectedState = Object.assign({}, mockState, { selectedSuite: testSuites[0] }); const selectedSuite = testReports.test_suites[0];
mutations[types.SET_SELECTED_SUITE](mockState, testSuites[0]); mutations[types.SET_SELECTED_SUITE](mockState, selectedSuite);
expect(mockState.selectedSuite).toEqual(expectedState.selectedSuite); expect(mockState.selectedSuite).toEqual(selectedSuite);
}); });
}); });
......
import Vuex from 'vuex'; import Vuex from 'vuex';
import TestReports from '~/pipelines/components/test_reports/test_reports.vue'; import TestReports from '~/pipelines/components/test_reports/test_reports.vue';
import { shallowMount } from '@vue/test-utils'; import { shallowMount } from '@vue/test-utils';
import { testReports } from './mock_data';
import * as actions from '~/pipelines/stores/test_reports/actions'; import * as actions from '~/pipelines/stores/test_reports/actions';
import { getJSONFixture } from 'helpers/fixtures';
describe('Test reports app', () => { describe('Test reports app', () => {
let wrapper; let wrapper;
let store; let store;
const testReports = getJSONFixture('pipelines/test_report.json');
const loadingSpinner = () => wrapper.find('.js-loading-spinner'); const loadingSpinner = () => wrapper.find('.js-loading-spinner');
const testsDetail = () => wrapper.find('.js-tests-detail'); const testsDetail = () => wrapper.find('.js-tests-detail');
const noTestsToShow = () => wrapper.find('.js-no-tests-to-show'); const noTestsToShow = () => wrapper.find('.js-no-tests-to-show');
......
...@@ -3,18 +3,26 @@ import SuiteTable from '~/pipelines/components/test_reports/test_suite_table.vue ...@@ -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 * as getters from '~/pipelines/stores/test_reports/getters';
import { TestStatus } from '~/pipelines/constants'; import { TestStatus } from '~/pipelines/constants';
import { shallowMount } from '@vue/test-utils'; 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', () => { describe('Test reports suite table', () => {
let wrapper; let wrapper;
let store; 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 noCasesMessage = () => wrapper.find('.js-no-test-cases');
const allCaseRows = () => wrapper.findAll('.js-case-row'); const allCaseRows = () => wrapper.findAll('.js-case-row');
const findCaseRowAtIndex = index => wrapper.findAll('.js-case-row').at(index); const findCaseRowAtIndex = index => wrapper.findAll('.js-case-row').at(index);
const findIconForRow = (row, status) => row.find(`.ci-status-icon-${status}`); const findIconForRow = (row, status) => row.find(`.ci-status-icon-${status}`);
const createComponent = (suite = testSuites[0]) => { const createComponent = (suite = testSuite) => {
store = new Vuex.Store({ store = new Vuex.Store({
state: { state: {
selectedSuite: suite, selectedSuite: suite,
......
import Summary from '~/pipelines/components/test_reports/test_summary.vue'; import Summary from '~/pipelines/components/test_reports/test_summary.vue';
import { mount } from '@vue/test-utils'; import { mount } from '@vue/test-utils';
import { testSuites } from './mock_data'; import { getJSONFixture } from 'helpers/fixtures';
describe('Test reports summary', () => { describe('Test reports summary', () => {
let wrapper; let wrapper;
const {
test_suites: [testSuite],
} = getJSONFixture('pipelines/test_report.json');
const backButton = () => wrapper.find('.js-back-button'); const backButton = () => wrapper.find('.js-back-button');
const totalTests = () => wrapper.find('.js-total-tests'); const totalTests = () => wrapper.find('.js-total-tests');
const failedTests = () => wrapper.find('.js-failed-tests'); const failedTests = () => wrapper.find('.js-failed-tests');
...@@ -13,7 +17,7 @@ describe('Test reports summary', () => { ...@@ -13,7 +17,7 @@ describe('Test reports summary', () => {
const duration = () => wrapper.find('.js-duration'); const duration = () => wrapper.find('.js-duration');
const defaultProps = { const defaultProps = {
report: testSuites[0], report: testSuite,
showBack: false, showBack: false,
}; };
...@@ -72,7 +76,7 @@ describe('Test reports summary', () => { ...@@ -72,7 +76,7 @@ describe('Test reports summary', () => {
}); });
it('displays the correctly formatted duration', () => { 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'; ...@@ -2,7 +2,7 @@ import Vuex from 'vuex';
import SummaryTable from '~/pipelines/components/test_reports/test_summary_table.vue'; import SummaryTable from '~/pipelines/components/test_reports/test_summary_table.vue';
import * as getters from '~/pipelines/stores/test_reports/getters'; import * as getters from '~/pipelines/stores/test_reports/getters';
import { mount, createLocalVue } from '@vue/test-utils'; import { mount, createLocalVue } from '@vue/test-utils';
import { testReports, testReportsWithNoSuites } from './mock_data'; import { getJSONFixture } from 'helpers/fixtures';
const localVue = createLocalVue(); const localVue = createLocalVue();
localVue.use(Vuex); localVue.use(Vuex);
...@@ -11,6 +11,8 @@ describe('Test reports summary table', () => { ...@@ -11,6 +11,8 @@ describe('Test reports summary table', () => {
let wrapper; let wrapper;
let store; let store;
const testReports = getJSONFixture('pipelines/test_report.json');
const allSuitesRows = () => wrapper.findAll('.js-suite-row'); const allSuitesRows = () => wrapper.findAll('.js-suite-row');
const noSuitesToShow = () => wrapper.find('.js-no-tests-suites'); const noSuitesToShow = () => wrapper.find('.js-no-tests-suites');
...@@ -44,7 +46,7 @@ describe('Test reports summary table', () => { ...@@ -44,7 +46,7 @@ describe('Test reports summary table', () => {
describe('when there are no test suites', () => { describe('when there are no test suites', () => {
beforeEach(() => { beforeEach(() => {
createComponent({ testReportsWithNoSuites }); createComponent({ test_suites: [] });
}); });
it('displays the no suites to show message', () => { it('displays the no suites to show message', () => {
......
import Vue from 'vue'; import { shallowMount, createLocalVue } from '@vue/test-utils';
import timeagoTooltip from '~/vue_shared/components/time_ago_tooltip.vue'; import TimeAgoTooltip from '~/vue_shared/components/time_ago_tooltip.vue';
import { formatDate, getTimeago } from '~/lib/utils/datetime_utility'; import { formatDate, getTimeago } from '~/lib/utils/datetime_utility';
describe('Time ago with tooltip component', () => { describe('Time ago with tooltip component', () => {
let TimeagoTooltip;
let vm; let vm;
beforeEach(() => { const buildVm = (propsData = {}) => {
TimeagoTooltip = Vue.extend(timeagoTooltip); vm = shallowMount(TimeAgoTooltip, {
attachToDocument: true,
sync: false,
propsData,
localVue: createLocalVue(),
}); });
};
const timestamp = '2017-05-08T14:57:39.781Z';
afterEach(() => { afterEach(() => {
vm.$destroy(); vm.destroy();
}); });
it('should render timeago with a bootstrap tooltip', () => { it('should render timeago with a bootstrap tooltip', () => {
vm = new TimeagoTooltip({ buildVm({
propsData: { time: timestamp,
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'),
);
const timeago = getTimeago(); 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', () => { it('should render provided html class', () => {
vm = new TimeagoTooltip({ buildVm({
propsData: { time: timestamp,
time: '2017-05-08T14:57:39.781Z',
cssClass: 'foo', cssClass: 'foo',
}, });
}).$mount();
expect(vm.$el.classList.contains('foo')).toEqual(true); expect(vm.classes()).toContain('foo');
}); });
}); });
# 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 ...@@ -279,5 +279,11 @@ describe Gitlab::DatabaseImporters::SelfMonitoring::Project::CreateService do
end end
end end
end end
it "tracks successful install" do
expect(Gitlab::Tracking).to receive(:event).with("self_monitoring", "project_created")
result
end
end end
end end
...@@ -61,6 +61,12 @@ describe Gitlab::Regex do ...@@ -61,6 +61,12 @@ describe Gitlab::Regex do
it { is_expected.to match('my/image') } it { is_expected.to match('my/image') }
it { is_expected.to match('my/awesome/image-1') } 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') }
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') }
it { is_expected.not_to match('my/image.') } it { is_expected.not_to match('my/image.') }
end end
......
...@@ -262,4 +262,28 @@ describe PrometheusService, :use_clean_rails_memory_store_caching do ...@@ -262,4 +262,28 @@ describe PrometheusService, :use_clean_rails_memory_store_caching do
end end
end 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 end
...@@ -66,14 +66,16 @@ describe Repository do ...@@ -66,14 +66,16 @@ describe Repository do
end end
describe 'tags_sorted_by' do describe 'tags_sorted_by' do
let(:tags_to_compare) { %w[v1.0.0 v1.1.0] }
context 'name_desc' do 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']) } it { is_expected.to eq(['v1.1.0', 'v1.0.0']) }
end end
context 'name_asc' do 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']) } it { is_expected.to eq(['v1.0.0', 'v1.1.0']) }
end end
...@@ -115,7 +117,7 @@ describe Repository do ...@@ -115,7 +117,7 @@ describe Repository do
context 'annotated tag pointing to a blob' do context 'annotated tag pointing to a blob' do
let(:annotated_tag_name) { 'annotated-tag' } 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 before do
options = { message: 'test tag message\n', options = { message: 'test tag message\n',
......
...@@ -7,6 +7,7 @@ describe API::Tags do ...@@ -7,6 +7,7 @@ describe API::Tags do
let(:guest) { create(:user).tap { |u| project.add_guest(u) } } let(:guest) { create(:user).tap { |u| project.add_guest(u) } }
let(:project) { create(:project, :repository, creator: user, path: 'my.project') } let(:project) { create(:project, :repository, creator: user, path: 'my.project') }
let(:tag_name) { project.repository.find_tag('v1.1.0').name } 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(:project_id) { project.id }
let(:current_user) { nil } let(:current_user) { nil }
...@@ -75,7 +76,7 @@ describe API::Tags do ...@@ -75,7 +76,7 @@ describe API::Tags do
expect(response).to have_gitlab_http_status(200) expect(response).to have_gitlab_http_status(200)
expect(response).to match_response_schema('public_api/v4/tags') expect(response).to match_response_schema('public_api/v4/tags')
expect(response).to include_pagination_headers 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 end
context 'when repository is disabled' do context 'when repository is disabled' do
...@@ -135,9 +136,10 @@ describe API::Tags do ...@@ -135,9 +136,10 @@ describe API::Tags do
expect(response).to have_gitlab_http_status(200) expect(response).to have_gitlab_http_status(200)
expect(response).to match_response_schema('public_api/v4/tags') expect(response).to match_response_schema('public_api/v4/tags')
expect(response).to include_pagination_headers 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') expected_tag = json_response.find { |r| r['name'] == tag_name }
expect(json_response.first['release']['description']).to eq(description) expect(expected_tag['message']).to eq(tag_message)
expect(expected_tag['release']['description']).to eq(description)
end end
end end
end end
......
...@@ -970,10 +970,10 @@ ...@@ -970,10 +970,10 @@
"@sentry/types" "5.7.1" "@sentry/types" "5.7.1"
tslib "^1.9.3" tslib "^1.9.3"
"@sourcegraph/code-host-integration@^0.0.13": "@sourcegraph/code-host-integration@^0.0.14":
version "0.0.13" version "0.0.14"
resolved "https://registry.yarnpkg.com/@sourcegraph/code-host-integration/-/code-host-integration-0.0.13.tgz#4fd5fe1e0088c63b2a26be231c5a2a4ca79b1596" resolved "https://registry.yarnpkg.com/@sourcegraph/code-host-integration/-/code-host-integration-0.0.14.tgz#e12b08371dc37bf4a468450b008c6e167705e1a8"
integrity sha512-IjF9gb9e8dG8p12DKg5Z7UMOVQO/ClH3AyMCPfX/qH7DH/0b55WH6stYVqZu6y776quFonO4Z9gWYM8pQZjzKw== integrity sha512-S4+K+3RKFd49Btl1D9LOdWXROgXevUwOBwp+vDUuGgzT2d6Y+qjalUJ0t8CjbYzdBdJun+2/Zi1+SXfm+S+xVg==
"@types/anymatch@*": "@types/anymatch@*":
version "1.3.0" version "1.3.0"
......
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment