Add latest changes from gitlab-org/gitlab@master

This commit is contained in:
GitLab Bot 2020-02-12 06:09:05 +00:00
parent 500626a5c9
commit 8c9dc985b9
45 changed files with 772 additions and 137 deletions

View File

@ -1,5 +1,6 @@
<script> <script>
import { mapState, mapGetters, mapActions } from 'vuex'; import { mapState, mapGetters, mapActions } from 'vuex';
import { getParameterByName, parseBoolean } from '~/lib/utils/common_utils';
import Icon from '~/vue_shared/components/icon.vue'; import Icon from '~/vue_shared/components/icon.vue';
import DiffGutterAvatars from './diff_gutter_avatars.vue'; import DiffGutterAvatars from './diff_gutter_avatars.vue';
import { LINE_POSITION_RIGHT } from '../constants'; import { LINE_POSITION_RIGHT } from '../constants';
@ -98,7 +99,8 @@ export default {
return this.showCommentButton && this.hasDiscussions; return this.showCommentButton && this.hasDiscussions;
}, },
shouldRenderCommentButton() { shouldRenderCommentButton() {
return this.isLoggedIn && this.showCommentButton; const isDiffHead = parseBoolean(getParameterByName('diff_head'));
return !isDiffHead && this.isLoggedIn && this.showCommentButton;
}, },
}, },
methods: { methods: {
@ -130,6 +132,7 @@ export default {
</button> </button>
<a <a
v-if="lineNumber" v-if="lineNumber"
ref="lineNumberRef"
:data-linenumber="lineNumber" :data-linenumber="lineNumber"
:href="lineHref" :href="lineHref"
@click="setHighlightedRow(lineCode)" @click="setHighlightedRow(lineCode)"

View File

@ -327,7 +327,10 @@ export const getSelectedFragment = restrictToNode => {
documentFragment.originalNodes.push(range.commonAncestorContainer); documentFragment.originalNodes.push(range.commonAncestorContainer);
} }
} }
if (documentFragment.textContent.length === 0) return null;
if (documentFragment.textContent.length === 0 && documentFragment.children.length === 0) {
return null;
}
return documentFragment; return documentFragment;
}; };

View File

@ -8,7 +8,6 @@ module Ci
include Importable include Importable
include AfterCommitQueue include AfterCommitQueue
include HasRef include HasRef
include Gitlab::Utils::StrongMemoize
InvalidBridgeTypeError = Class.new(StandardError) InvalidBridgeTypeError = Class.new(StandardError)

View File

@ -10,7 +10,6 @@ module Ci
include ObjectStorage::BackgroundMove include ObjectStorage::BackgroundMove
include Presentable include Presentable
include Importable include Importable
include Gitlab::Utils::StrongMemoize
include HasRef include HasRef
include IgnorableColumns include IgnorableColumns

View File

@ -2,10 +2,14 @@
module Ci module Ci
class Processable < ::CommitStatus class Processable < ::CommitStatus
include Gitlab::Utils::StrongMemoize
has_many :needs, class_name: 'Ci::BuildNeed', foreign_key: :build_id, inverse_of: :build has_many :needs, class_name: 'Ci::BuildNeed', foreign_key: :build_id, inverse_of: :build
accepts_nested_attributes_for :needs accepts_nested_attributes_for :needs
enum scheduling_type: { stage: 0, dag: 1 }, _prefix: true
scope :preload_needs, -> { preload(:needs) } scope :preload_needs, -> { preload(:needs) }
def self.select_with_aggregated_needs(project) def self.select_with_aggregated_needs(project)
@ -23,6 +27,7 @@ module Ci
end end
validates :type, presence: true validates :type, presence: true
validates :scheduling_type, presence: true, on: :create, if: :validate_scheduling_type?
def aggregated_needs_names def aggregated_needs_names
read_attribute(:aggregated_needs_names) read_attribute(:aggregated_needs_names)
@ -47,5 +52,19 @@ module Ci
def scoped_variables_hash def scoped_variables_hash
raise NotImplementedError raise NotImplementedError
end end
# scheduling_type column of previous builds/bridges have not been populated,
# so we calculate this value on runtime when we need it.
def find_legacy_scheduling_type
strong_memoize(:find_legacy_scheduling_type) do
needs.exists? ? :dag : :stage
end
end
private
def validate_scheduling_type?
!importing? && Feature.enabled?(:validate_scheduling_type_of_processables, project)
end
end end
end end

View File

@ -30,7 +30,8 @@ module Clusters
version: VERSION, version: VERSION,
rbac: cluster.platform_kubernetes_rbac?, rbac: cluster.platform_kubernetes_rbac?,
chart: chart, chart: chart,
files: files files: files,
postinstall: post_install_script
) )
end end
@ -43,6 +44,10 @@ module Clusters
) )
end end
def files
super.merge('wait-for-elasticsearch.sh': File.read("#{Rails.root}/vendor/elastic_stack/wait-for-elasticsearch.sh"))
end
def elasticsearch_client def elasticsearch_client
strong_memoize(:elasticsearch_client) do strong_memoize(:elasticsearch_client) do
next unless kube_client next unless kube_client
@ -69,10 +74,16 @@ module Clusters
private private
def post_install_script
[
"timeout -t60 sh /data/helm/elastic-stack/config/wait-for-elasticsearch.sh http://elastic-stack-elasticsearch-client:9200"
]
end
def post_delete_script def post_delete_script
[ [
Gitlab::Kubernetes::KubectlCmd.delete("pvc", "--selector", "release=elastic-stack") Gitlab::Kubernetes::KubectlCmd.delete("pvc", "--selector", "release=elastic-stack")
].compact ]
end end
def kube_client def kube_client

View File

@ -5,7 +5,7 @@ module Ci
CLONE_ACCESSORS = %i[pipeline project ref tag options name CLONE_ACCESSORS = %i[pipeline project ref tag options name
allow_failure stage stage_id stage_idx trigger_request allow_failure stage stage_id stage_idx trigger_request
yaml_variables when environment coverage_regex yaml_variables when environment coverage_regex
description tag_list protected needs resource_group].freeze description tag_list protected needs resource_group scheduling_type].freeze
def execute(build) def execute(build)
reprocess!(build).tap do |new_build| reprocess!(build).tap do |new_build|
@ -27,9 +27,10 @@ module Ci
attributes = CLONE_ACCESSORS.map do |attribute| attributes = CLONE_ACCESSORS.map do |attribute|
[attribute, build.public_send(attribute)] # rubocop:disable GitlabSecurity/PublicSend [attribute, build.public_send(attribute)] # rubocop:disable GitlabSecurity/PublicSend
end end.to_h
attributes.push([:user, current_user]) attributes[:user] = current_user
attributes[:scheduling_type] ||= build.find_legacy_scheduling_type
Ci::Build.transaction do Ci::Build.transaction do
# mark all other builds of that name as retried # mark all other builds of that name as retried
@ -49,7 +50,7 @@ module Ci
private private
def create_build!(attributes) def create_build!(attributes)
build = project.builds.new(Hash[attributes]) build = project.builds.new(attributes)
build.deployment = ::Gitlab::Ci::Pipeline::Seed::Deployment.new(build).to_resource build.deployment = ::Gitlab::Ci::Pipeline::Seed::Deployment.new(build).to_resource
build.retried = false build.retried = false
build.save! build.save!

View File

@ -29,7 +29,7 @@
":title" => '(list.assignee && list.assignee.username || "")' } ":title" => '(list.assignee && list.assignee.username || "")' }
@{{ list.assignee.username }} @{{ list.assignee.username }}
%span.has-tooltip.badge.color-label.title{ "v-if": "list.type === \"label\"", %span.has-tooltip.badge.color-label.title.d-inline-block.mw-100.text-truncate.align-middle{ "v-if": "list.type === \"label\"",
":title" => '(list.label ? list.label.description : "")', ":title" => '(list.label ? list.label.description : "")',
data: { container: "body", placement: "bottom" }, data: { container: "body", placement: "bottom" },
":style" => "{ backgroundColor: (list.label && list.label.color ? list.label.color : null), color: (list.label && list.label.textColor ? list.label.textColor : \"#2e2e2e\") }" } ":style" => "{ backgroundColor: (list.label && list.label.color ? list.label.color : null), color: (list.label && list.label.textColor ? list.label.textColor : \"#2e2e2e\") }" }

View File

@ -0,0 +1,5 @@
---
title: Fix copy markdown with elements with no text content
merge_request: 24020
author:
type: fixed

View File

@ -0,0 +1,5 @@
---
title: Implement support of allow_failure keyword for CI rules
merge_request: 24605
author:
type: added

View File

@ -0,0 +1,5 @@
---
title: Wait for elasticsearch to be green on install
merge_request: 24489
author:
type: added

View File

@ -0,0 +1,5 @@
---
title: Hide comment button if on diff HEAD
merge_request: 24207
author:
type: changed

View File

@ -0,0 +1,5 @@
---
title: Add styles for board list labels when text is too long
merge_request: 24627
author:
type: fixed

View File

@ -165,9 +165,10 @@ class Gitlab::Seeder::Pipelines
end end
def job_attributes(pipeline, opts) def job_attributes(pipeline, opts)
{ name: 'test build', stage: 'test', stage_idx: stage_index(opts[:stage]), {
name: 'test build', stage: 'test', stage_idx: stage_index(opts[:stage]),
ref: pipeline.ref, tag: false, user: build_user, project: @project, pipeline: pipeline, ref: pipeline.ref, tag: false, user: build_user, project: @project, pipeline: pipeline,
created_at: Time.now, updated_at: Time.now scheduling_type: :stage, created_at: Time.now, updated_at: Time.now
}.merge(opts) }.merge(opts)
end end

View File

@ -0,0 +1,11 @@
# frozen_string_literal: true
class AddSchedulingTypeToCiBuilds < ActiveRecord::Migration[5.2]
include Gitlab::Database::MigrationHelpers
DOWNTIME = false
def change
add_column :ci_builds, :scheduling_type, :integer, limit: 2
end
end

View File

@ -677,6 +677,7 @@ ActiveRecord::Schema.define(version: 2020_02_11_152410) do
t.bigint "resource_group_id" t.bigint "resource_group_id"
t.datetime_with_timezone "waiting_for_resource_at" t.datetime_with_timezone "waiting_for_resource_at"
t.boolean "processed" t.boolean "processed"
t.integer "scheduling_type", limit: 2
t.index ["artifacts_expire_at"], name: "index_ci_builds_on_artifacts_expire_at", where: "(artifacts_file <> ''::text)" t.index ["artifacts_expire_at"], name: "index_ci_builds_on_artifacts_expire_at", where: "(artifacts_file <> ''::text)"
t.index ["auto_canceled_by_id"], name: "index_ci_builds_on_auto_canceled_by_id" t.index ["auto_canceled_by_id"], name: "index_ci_builds_on_auto_canceled_by_id"
t.index ["commit_id", "artifacts_expire_at", "id"], name: "index_ci_builds_on_commit_id_and_artifacts_expireatandidpartial", where: "(((type)::text = 'Ci::Build'::text) AND ((retried = false) OR (retried IS NULL)) AND ((name)::text = ANY (ARRAY[('sast'::character varying)::text, ('dependency_scanning'::character varying)::text, ('sast:container'::character varying)::text, ('container_scanning'::character varying)::text, ('dast'::character varying)::text])))" t.index ["commit_id", "artifacts_expire_at", "id"], name: "index_ci_builds_on_commit_id_and_artifacts_expireatandidpartial", where: "(((type)::text = 'Ci::Build'::text) AND ((retried = false) OR (retried IS NULL)) AND ((name)::text = ANY (ARRAY[('sast'::character varying)::text, ('dependency_scanning'::character varying)::text, ('sast:container'::character varying)::text, ('container_scanning'::character varying)::text, ('dast'::character varying)::text])))"

View File

@ -851,7 +851,7 @@ In this example, if the first rule:
- Matches, the job will be given the `when:always` attribute. - Matches, the job will be given the `when:always` attribute.
- Does not match, the second and third rules will be evaluated sequentially - Does not match, the second and third rules will be evaluated sequentially
until a match is found. That is, the job will be given either the: until a match is found. That is, the job will be given either the:
- `when: manual` attribute if the second rule matches. - `when: manual` attribute if the second rule matches. **The stage will not complete until this manual job is triggered and completes successfully.**
- `when: on_success` attribute if the second rule does not match. The third - `when: on_success` attribute if the second rule does not match. The third
rule will always match when reached because it has no conditional clauses. rule will always match when reached because it has no conditional clauses.
@ -937,6 +937,25 @@ NOTE: **Note:**
For performance reasons, using `exists` with patterns is limited to 10000 For performance reasons, using `exists` with patterns is limited to 10000
checks. After the 10000th check, rules with patterned globs will always match. checks. After the 10000th check, rules with patterned globs will always match.
#### `rules:allow_failure`
> [Introduced](https://gitlab.com/gitlab-org/gitlab/issues/30235) in GitLab 12.8.
You can use [`allow_failure: true`](#allow_failure) within `rules:` to allow a job to fail, or a manual job to
wait for action, without stopping the pipeline itself. All jobs using `rules:` default to `allow_failure: false`
if `allow_failure:` is not defined.
```yaml
job:
script: "echo Hello, Rules!"
rules:
- if: '$CI_MERGE_REQUEST_TARGET_BRANCH_NAME == "master"'
when: manual
allow_failure: true
```
In this example, if the first rule matches, then the job will have `when: manual` and `allow_failure: true`.
#### Complex rule clauses #### Complex rule clauses
To conjoin `if`, `changes`, and `exists` clauses with an AND, use them in the To conjoin `if`, `changes`, and `exists` clauses with an AND, use them in the
@ -976,6 +995,7 @@ The only job attributes currently set by `rules` are:
- `when`. - `when`.
- `start_in`, if `when` is set to `delayed`. - `start_in`, if `when` is set to `delayed`.
- `allow_failure`.
A job will be included in a pipeline if `when` is evaluated to any value A job will be included in a pipeline if `when` is evaluated to any value
except `never`. except `never`.

View File

@ -464,8 +464,8 @@ chart is used to install this application with a
file. file.
NOTE: **Note:** NOTE: **Note:**
The chart will deploy 4 Elasticsearch nodes: 2 masters, 1 data and 1 client node, The chart will deploy 5 Elasticsearch nodes: 2 masters, 2 data and 1 client node,
with resource requests totalling 0.1 CPU and 3GB RAM. Each data node requests 1.5GB of memory, with resource requests totalling 0.125 CPU and 4.5GB RAM. Each data node requests 1.5GB of memory,
which makes it incompatible with clusters of `f1-micro` and `g1-small` instance types. which makes it incompatible with clusters of `f1-micro` and `g1-small` instance types.
## Install using GitLab CI (alpha) ## Install using GitLab CI (alpha)

View File

@ -241,9 +241,10 @@ and give all group members access to the project at once.
Alternatively, you can [lock the sharing with group feature](#share-with-group-lock). Alternatively, you can [lock the sharing with group feature](#share-with-group-lock).
## Sharing a group with another group ## Sharing a group with another group **(CORE ONLY)**
> [Introduced](https://gitlab.com/gitlab-org/gitlab/issues/18328) in GitLab 12.7. > [Introduced](https://gitlab.com/gitlab-org/gitlab/issues/18328) in GitLab 12.7.
> This feature has been [disabled on GitLab.com](https://gitlab.com/gitlab-com/gl-infra/production/issues/1635).
Similarly to [sharing a project with a group](#sharing-a-project-with-a-group), Similarly to [sharing a project with a group](#sharing-a-project-with-a-group),
you can share a group with another group to give direct group members access you can share a group with another group to give direct group members access

View File

@ -6,11 +6,12 @@ module Gitlab
class Rules class Rules
include ::Gitlab::Utils::StrongMemoize include ::Gitlab::Utils::StrongMemoize
Result = Struct.new(:when, :start_in) do Result = Struct.new(:when, :start_in, :allow_failure) do
def build_attributes def build_attributes
{ {
when: self.when, when: self.when,
options: { start_in: start_in }.compact options: { start_in: start_in }.compact,
allow_failure: allow_failure
}.compact }.compact
end end
@ -30,7 +31,8 @@ module Gitlab
elsif matched_rule = match_rule(pipeline, context) elsif matched_rule = match_rule(pipeline, context)
Result.new( Result.new(
matched_rule.attributes[:when] || @default_when, matched_rule.attributes[:when] || @default_when,
matched_rule.attributes[:start_in] matched_rule.attributes[:start_in],
matched_rule.attributes[:allow_failure]
) )
else else
Result.new('never') Result.new('never')

View File

@ -132,7 +132,8 @@ module Gitlab
variables: (variables_value if variables_defined?), variables: (variables_value if variables_defined?),
rules: (rules_value if has_rules?), rules: (rules_value if has_rules?),
only: only_value, only: only_value,
except: except_value }.compact except: except_value,
scheduling_type: needs_defined? && !bridge_needs ? :dag : :stage }.compact
end end
def bridge_needs def bridge_needs

View File

@ -258,7 +258,8 @@ module Gitlab
after_script: after_script_value, after_script: after_script_value,
ignore: ignored?, ignore: ignored?,
needs: needs_defined? ? needs_value : nil, needs: needs_defined? ? needs_value : nil,
resource_group: resource_group } resource_group: resource_group,
scheduling_type: needs_defined? ? :dag : :stage }
end end
end end
end end

View File

@ -9,10 +9,10 @@ module Gitlab
include ::Gitlab::Config::Entry::Attributable include ::Gitlab::Config::Entry::Attributable
CLAUSES = %i[if changes exists].freeze CLAUSES = %i[if changes exists].freeze
ALLOWED_KEYS = %i[if changes exists when start_in].freeze ALLOWED_KEYS = %i[if changes exists when start_in allow_failure].freeze
ALLOWABLE_WHEN = %w[on_success on_failure always never manual delayed].freeze ALLOWABLE_WHEN = %w[on_success on_failure always never manual delayed].freeze
attributes :if, :changes, :exists, :when, :start_in attributes :if, :changes, :exists, :when, :start_in, :allow_failure
validations do validations do
validates :config, presence: true validates :config, presence: true
@ -26,6 +26,7 @@ module Gitlab
validates :if, expression: true validates :if, expression: true
validates :changes, :exists, array_of_strings: true, length: { maximum: 50 } validates :changes, :exists, array_of_strings: true, length: { maximum: 50 }
validates :when, allowed_values: { in: ALLOWABLE_WHEN } validates :when, allowed_values: { in: ALLOWABLE_WHEN }
validates :allow_failure, boolean: true
end end
validate do validate do

View File

@ -65,6 +65,7 @@ module Gitlab
rules: job[:rules], rules: job[:rules],
cache: job[:cache], cache: job[:cache],
resource_group_key: job[:resource_group], resource_group_key: job[:resource_group],
scheduling_type: job[:scheduling_type],
options: { options: {
image: job[:image], image: job[:image],
services: job[:services], services: job[:services],

View File

@ -9,6 +9,7 @@ FactoryBot.define do
tag { false } tag { false }
created_at { 'Di 29. Okt 09:50:00 CET 2013' } created_at { 'Di 29. Okt 09:50:00 CET 2013' }
status { :created } status { :created }
scheduling_type { 'stage' }
pipeline factory: :ci_pipeline pipeline factory: :ci_pipeline

View File

@ -11,6 +11,7 @@ FactoryBot.define do
tag { false } tag { false }
add_attribute(:protected) { false } add_attribute(:protected) { false }
created_at { 'Di 29. Okt 09:50:00 CET 2013' } created_at { 'Di 29. Okt 09:50:00 CET 2013' }
scheduling_type { 'stage' }
pending pending
options do options do

View File

@ -1,105 +1,195 @@
import Vue from 'vue'; import Vuex from 'vuex';
import { createComponentWithStore } from 'helpers/vue_mount_component_helper'; import { shallowMount, createLocalVue } from '@vue/test-utils';
import DiffLineGutterContent from '~/diffs/components/diff_line_gutter_content.vue'; import DiffLineGutterContent from '~/diffs/components/diff_line_gutter_content.vue';
import DiffGutterAvatars from '~/diffs/components/diff_gutter_avatars.vue';
import { LINE_POSITION_RIGHT } from '~/diffs/constants';
import { createStore } from '~/mr_notes/stores'; import { createStore } from '~/mr_notes/stores';
import { TEST_HOST } from 'helpers/test_constants';
import discussionsMockData from '../mock_data/diff_discussions'; import discussionsMockData from '../mock_data/diff_discussions';
import diffFileMockData from '../mock_data/diff_file'; import diffFileMockData from '../mock_data/diff_file';
const localVue = createLocalVue();
localVue.use(Vuex);
const TEST_USER_ID = 'abc123';
const TEST_USER = { id: TEST_USER_ID };
const TEST_LINE_NUMBER = 1;
const TEST_LINE_CODE = 'LC_42';
const TEST_FILE_HASH = diffFileMockData.file_hash;
describe('DiffLineGutterContent', () => { describe('DiffLineGutterContent', () => {
const getDiffFileMock = () => Object.assign({}, diffFileMockData); let wrapper;
const createComponent = (options = {}) => { let line;
const cmp = Vue.extend(DiffLineGutterContent); let store;
const props = Object.assign({}, options);
props.line = { beforeEach(() => {
line_code: 'LC_42', store = createStore();
store.state.notes.userData = TEST_USER;
line = {
line_code: TEST_LINE_CODE,
type: 'new', type: 'new',
old_line: null, old_line: null,
new_line: 1, new_line: 1,
discussions: [{ ...discussionsMockData }], discussions: [{ ...discussionsMockData }],
discussionsExpanded: true,
text: '+<span id="LC1" class="line" lang="plaintext"> - Bad dates</span>\n', text: '+<span id="LC1" class="line" lang="plaintext"> - Bad dates</span>\n',
rich_text: '+<span id="LC1" class="line" lang="plaintext"> - Bad dates</span>\n', rich_text: '+<span id="LC1" class="line" lang="plaintext"> - Bad dates</span>\n',
meta_data: null, meta_data: null,
}; };
props.fileHash = getDiffFileMock().file_hash; });
props.contextLinesPath = '/context/lines/path';
return createComponentWithStore(cmp, createStore(), props).$mount(); afterEach(() => {
wrapper.destroy();
});
const setWindowLocation = value => {
Object.defineProperty(window, 'location', {
writable: true,
value,
});
}; };
const createComponent = (props = {}) => {
wrapper = shallowMount(DiffLineGutterContent, {
localVue,
store,
propsData: {
line,
fileHash: TEST_FILE_HASH,
contextLinesPath: '/context/lines/path',
...props,
},
});
};
const findNoteButton = () => wrapper.find('.js-add-diff-note-button');
const findLineNumber = () => wrapper.find({ ref: 'lineNumberRef' });
const findAvatars = () => wrapper.find(DiffGutterAvatars);
describe('computed', () => { describe('comment button', () => {
describe('lineHref', () => { it.each`
it('should prepend # to lineCode', () => { showCommentButton | userData | query | expectation
const lineCode = 'LC_42'; ${true} | ${TEST_USER} | ${'diff_head=false'} | ${true}
const component = createComponent(); ${true} | ${TEST_USER} | ${'diff_head=true'} | ${false}
${false} | ${TEST_USER} | ${'bogus'} | ${false}
${true} | ${null} | ${''} | ${false}
`(
'exists is $expectation - with showCommentButton ($showCommentButton) userData ($userData) query ($query)',
({ showCommentButton, userData, query, expectation }) => {
store.state.notes.userData = userData;
setWindowLocation({ href: `${TEST_HOST}?${query}` });
createComponent({ showCommentButton });
expect(component.lineHref).toEqual(`#${lineCode}`); expect(findNoteButton().exists()).toBe(expectation);
}); },
);
it('should return # if there is no lineCode', () => { it.each`
const component = createComponent(); isHover | otherProps | discussions | expectation
component.line.line_code = ''; ${true} | ${{}} | ${[]} | ${true}
${false} | ${{}} | ${[]} | ${false}
${true} | ${{ isMatchLine: true }} | ${[]} | ${false}
${true} | ${{ isContextLine: true }} | ${[]} | ${false}
${true} | ${{ isMetaLine: true }} | ${[]} | ${false}
${true} | ${{}} | ${[{}]} | ${false}
`(
'visible is $expectation - with isHover ($isHover), discussions ($discussions), otherProps ($otherProps)',
({ isHover, otherProps, discussions, expectation }) => {
line.discussions = discussions;
createComponent({
showCommentButton: true,
isHover,
...otherProps,
});
expect(component.lineHref).toEqual('#'); expect(findNoteButton().isVisible()).toBe(expectation);
},
);
});
describe('line number', () => {
describe('without lineNumber prop', () => {
it('does not render', () => {
createComponent();
expect(findLineNumber().exists()).toBe(false);
}); });
}); });
describe('discussions, hasDiscussions, shouldShowAvatarsOnGutter', () => { describe('with lineNumber prop', () => {
it('should return empty array when there is no discussion', () => { describe.each`
const component = createComponent(); lineProps | expectedHref | expectedClickArg
component.line.discussions = []; ${{ line_code: TEST_LINE_CODE }} | ${`#${TEST_LINE_CODE}`} | ${TEST_LINE_CODE}
${{ line_code: undefined }} | ${'#'} | ${undefined}
${{ line_code: undefined, left: { line_code: TEST_LINE_CODE } }} | ${'#'} | ${TEST_LINE_CODE}
${{ line_code: undefined, right: { line_code: TEST_LINE_CODE } }} | ${'#'} | ${TEST_LINE_CODE}
`('with line ($lineProps)', ({ lineProps, expectedHref, expectedClickArg }) => {
beforeEach(() => {
jest.spyOn(store, 'dispatch').mockImplementation();
Object.assign(line, lineProps);
createComponent({ lineNumber: TEST_LINE_NUMBER });
});
expect(component.hasDiscussions).toEqual(false); it('renders', () => {
expect(component.shouldShowAvatarsOnGutter).toEqual(false); expect(findLineNumber().exists()).toBe(true);
}); expect(findLineNumber().attributes()).toEqual({
href: expectedHref,
'data-linenumber': TEST_LINE_NUMBER.toString(),
});
});
it('should return discussions for the given lineCode', () => { it('on click, dispatches setHighlightedRow', () => {
const cmp = Vue.extend(DiffLineGutterContent); expect(store.dispatch).not.toHaveBeenCalled();
const props = {
line: getDiffFileMock().highlighted_diff_lines[1],
fileHash: getDiffFileMock().file_hash,
showCommentButton: true,
contextLinesPath: '/context/lines/path',
};
props.line.discussions = [Object.assign({}, discussionsMockData)];
const component = createComponentWithStore(cmp, createStore(), props).$mount();
expect(component.hasDiscussions).toEqual(true); findLineNumber().trigger('click');
expect(component.shouldShowAvatarsOnGutter).toEqual(true);
expect(store.dispatch).toHaveBeenCalledWith('diffs/setHighlightedRow', expectedClickArg);
});
}); });
}); });
}); });
describe('template', () => { describe('diff-gutter-avatars', () => {
it('should render comment button', () => { describe('with showCommentButton', () => {
const component = createComponent({ beforeEach(() => {
showCommentButton: true, jest.spyOn(store, 'dispatch').mockImplementation();
});
Object.defineProperty(component, 'isLoggedIn', { createComponent({ showCommentButton: true });
get() {
return true;
},
}); });
expect(component.$el.querySelector('.js-add-diff-note-button')).toBeDefined(); it('renders', () => {
}); expect(findAvatars().props()).toEqual({
discussions: line.discussions,
it('should render line link', () => { discussionsExpanded: line.discussionsExpanded,
const lineNumber = 42; });
const lineCode = `LC_${lineNumber}`;
const component = createComponent({ lineNumber, lineCode });
const link = component.$el.querySelector('a');
expect(link.href.indexOf(`#${lineCode}`)).toBeGreaterThan(-1);
expect(link.dataset.linenumber).toEqual(lineNumber.toString());
});
it('should render user avatars', () => {
const component = createComponent({
showCommentButton: true,
lineCode: getDiffFileMock().highlighted_diff_lines[1].line_code,
}); });
expect(component.$el.querySelector('.diff-comment-avatar-holders')).not.toBe(null); it('toggles line discussion', () => {
expect(store.dispatch).not.toHaveBeenCalled();
findAvatars().vm.$emit('toggleLineDiscussions');
expect(store.dispatch).toHaveBeenCalledWith('diffs/toggleLineDiscussions', {
lineCode: TEST_LINE_CODE,
fileHash: TEST_FILE_HASH,
expanded: !line.discussionsExpanded,
});
});
}); });
it.each`
props | lineProps | expectation
${{ showCommentButton: true }} | ${{}} | ${true}
${{ showCommentButton: false }} | ${{}} | ${false}
${{ showCommentButton: true, linePosition: LINE_POSITION_RIGHT }} | ${{ type: null }} | ${false}
${{ showCommentButton: true }} | ${{ discussions: [] }} | ${false}
`(
'exists is $expectation - with props ($props), line ($lineProps)',
({ props, lineProps, expectation }) => {
Object.assign(line, lineProps);
createComponent(props);
expect(findAvatars().exists()).toBe(expectation);
},
);
}); });
}); });

View File

@ -52,6 +52,7 @@ describe('ShortcutsIssuable', function() {
return documentFragment; return documentFragment;
}); });
}; };
describe('with empty selection', () => { describe('with empty selection', () => {
it('does not return an error', () => { it('does not return an error', () => {
ShortcutsIssuable.replyWithSelectedText(true); ShortcutsIssuable.replyWithSelectedText(true);
@ -297,5 +298,18 @@ describe('ShortcutsIssuable', function() {
}); });
}); });
}); });
describe('with a valid selection with no text content', () => {
it('returns the proper markdown', done => {
stubSelection('<img src="foo" alt="image" />');
ShortcutsIssuable.replyWithSelectedText(true);
setTimeout(() => {
expect($(FORM_SELECTOR).val()).toBe('> ![image](http://localhost:9876/foo)\n\n');
done();
});
});
});
}); });
}); });

View File

@ -102,9 +102,9 @@ describe Gitlab::Ci::Build::Rules do
end end
context 'with one rule without any clauses' do context 'with one rule without any clauses' do
let(:rule_list) { [{ when: 'manual' }] } let(:rule_list) { [{ when: 'manual', allow_failure: true }] }
it { is_expected.to eq(described_class::Result.new('manual')) } it { is_expected.to eq(described_class::Result.new('manual', nil, true)) }
end end
context 'with one matching rule' do context 'with one matching rule' do
@ -166,5 +166,51 @@ describe Gitlab::Ci::Build::Rules do
end end
end end
end end
context 'with only allow_failure' do
context 'with matching rule' do
let(:rule_list) { [{ if: '$VAR == null', allow_failure: true }] }
it { is_expected.to eq(described_class::Result.new('on_success', nil, true)) }
end
context 'with non-matching rule' do
let(:rule_list) { [{ if: '$VAR != null', allow_failure: true }] }
it { is_expected.to eq(described_class::Result.new('never')) }
end
end
end
describe 'Gitlab::Ci::Build::Rules::Result' do
let(:when_value) { 'on_success' }
let(:start_in) { nil }
let(:allow_failure) { nil }
subject { Gitlab::Ci::Build::Rules::Result.new(when_value, start_in, allow_failure) }
describe '#build_attributes' do
it 'compacts nil values' do
expect(subject.build_attributes).to eq(options: {}, when: 'on_success')
end
end
describe '#pass?' do
context "'when' is 'never'" do
let!(:when_value) { 'never' }
it 'returns false' do
expect(subject.pass?).to eq(false)
end
end
context "'when' is 'on_success'" do
let!(:when_value) { 'on_success' }
it 'returns true' do
expect(subject.pass?).to eq(true)
end
end
end
end end
end end

View File

@ -105,7 +105,8 @@ describe Gitlab::Ci::Config::Entry::Bridge do
trigger: { project: 'some/project' }, trigger: { project: 'some/project' },
ignore: false, ignore: false,
stage: 'test', stage: 'test',
only: { refs: %w[branches tags] }) only: { refs: %w[branches tags] },
scheduling_type: :stage)
end end
end end
end end
@ -126,7 +127,8 @@ describe Gitlab::Ci::Config::Entry::Bridge do
branch: 'feature' }, branch: 'feature' },
ignore: false, ignore: false,
stage: 'test', stage: 'test',
only: { refs: %w[branches tags] }) only: { refs: %w[branches tags] },
scheduling_type: :stage)
end end
end end
end end

View File

@ -110,6 +110,10 @@ describe Gitlab::Ci::Config::Entry::Job do
it { expect(entry).to be_valid } it { expect(entry).to be_valid }
it "returns scheduling_type as :dag" do
expect(entry.value[:scheduling_type]).to eq(:dag)
end
context 'when has dependencies' do context 'when has dependencies' do
let(:config) do let(:config) do
{ {
@ -598,7 +602,8 @@ describe Gitlab::Ci::Config::Entry::Job do
ignore: false, ignore: false,
after_script: %w[cleanup], after_script: %w[cleanup],
only: { refs: %w[branches tags] }, only: { refs: %w[branches tags] },
variables: {}) variables: {},
scheduling_type: :stage)
end end
end end
end end

View File

@ -98,7 +98,8 @@ describe Gitlab::Ci::Config::Entry::Jobs do
name: :my_trigger, name: :my_trigger,
only: { refs: %w[branches tags] }, only: { refs: %w[branches tags] },
stage: 'test', stage: 'test',
trigger: { project: 'my/project' } trigger: { project: 'my/project' },
scheduling_type: :stage
}, },
regular_job: { regular_job: {
ignore: false, ignore: false,
@ -106,7 +107,8 @@ describe Gitlab::Ci::Config::Entry::Jobs do
only: { refs: %w[branches tags] }, only: { refs: %w[branches tags] },
script: ['something'], script: ['something'],
stage: 'test', stage: 'test',
variables: {} variables: {},
scheduling_type: :stage
}) })
end end
end end

View File

@ -130,7 +130,8 @@ describe Gitlab::Ci::Config::Entry::Root do
variables: {}, variables: {},
ignore: false, ignore: false,
after_script: ['make clean'], after_script: ['make clean'],
only: { refs: %w[branches tags] } } only: { refs: %w[branches tags] },
scheduling_type: :stage }
) )
expect(root.jobs_value[:spinach]).to eq( expect(root.jobs_value[:spinach]).to eq(
{ name: :spinach, { name: :spinach,
@ -143,7 +144,8 @@ describe Gitlab::Ci::Config::Entry::Root do
variables: {}, variables: {},
ignore: false, ignore: false,
after_script: ['make clean'], after_script: ['make clean'],
only: { refs: %w[branches tags] } } only: { refs: %w[branches tags] },
scheduling_type: :stage }
) )
expect(root.jobs_value[:release]).to eq( expect(root.jobs_value[:release]).to eq(
{ name: :release, { name: :release,
@ -157,7 +159,8 @@ describe Gitlab::Ci::Config::Entry::Root do
only: { refs: %w(branches tags) }, only: { refs: %w(branches tags) },
variables: {}, variables: {},
after_script: [], after_script: [],
ignore: false } ignore: false,
scheduling_type: :stage }
) )
end end
end end
@ -203,7 +206,8 @@ describe Gitlab::Ci::Config::Entry::Root do
variables: {}, variables: {},
ignore: false, ignore: false,
after_script: ['make clean'], after_script: ['make clean'],
only: { refs: %w[branches tags] } }, only: { refs: %w[branches tags] },
scheduling_type: :stage },
spinach: { name: :spinach, spinach: { name: :spinach,
before_script: [], before_script: [],
script: %w[spinach], script: %w[spinach],
@ -214,7 +218,8 @@ describe Gitlab::Ci::Config::Entry::Root do
variables: { 'VAR' => 'AA' }, variables: { 'VAR' => 'AA' },
ignore: false, ignore: false,
after_script: ['make clean'], after_script: ['make clean'],
only: { refs: %w[branches tags] } } only: { refs: %w[branches tags] },
scheduling_type: :stage }
) )
end end
end end

View File

@ -27,8 +27,14 @@ describe Gitlab::Ci::Config::Entry::Rules::Rule do
it { is_expected.to be_valid } it { is_expected.to be_valid }
end end
context 'with an allow_failure: value but no clauses' do
let(:config) { { allow_failure: true } }
it { is_expected.to be_valid }
end
context 'when specifying an if: clause' do context 'when specifying an if: clause' do
let(:config) { { if: '$THIS || $THAT', when: 'manual' } } let(:config) { { if: '$THIS || $THAT', when: 'manual', allow_failure: true } }
it { is_expected.to be_valid } it { is_expected.to be_valid }
@ -37,6 +43,12 @@ describe Gitlab::Ci::Config::Entry::Rules::Rule do
it { is_expected.to eq('manual') } it { is_expected.to eq('manual') }
end end
describe '#allow_failure' do
subject { entry.allow_failure }
it { is_expected.to eq(true) }
end
end end
context 'using a list of multiple expressions' do context 'using a list of multiple expressions' do
@ -328,16 +340,43 @@ describe Gitlab::Ci::Config::Entry::Rules::Rule do
end end
end end
end end
context 'allow_failure: validation' do
context 'with an invalid string allow_failure:' do
let(:config) do
{ if: '$THIS == "that"', allow_failure: 'always' }
end
it { is_expected.to be_a(described_class) }
it { is_expected.not_to be_valid }
it 'returns an error about invalid allow_failure:' do
expect(subject.errors).to include(/rule allow failure should be a boolean value/)
end
context 'when composed' do
before do
subject.compose!
end
it { is_expected.not_to be_valid }
it 'returns an error about invalid allow_failure:' do
expect(subject.errors).to include(/rule allow failure should be a boolean value/)
end
end
end
end
end end
describe '#value' do describe '#value' do
subject { entry.value } subject { entry.value }
context 'when specifying an if: clause' do context 'when specifying an if: clause' do
let(:config) { { if: '$THIS || $THAT', when: 'manual' } } let(:config) { { if: '$THIS || $THAT', when: 'manual', allow_failure: true } }
it 'stores the expression as "if"' do it 'stores the expression as "if"' do
expect(subject).to eq(if: '$THIS || $THAT', when: 'manual') expect(subject).to eq(if: '$THIS || $THAT', when: 'manual', allow_failure: true)
end end
end end

View File

@ -6,7 +6,7 @@ describe Gitlab::Ci::Pipeline::Seed::Build do
let(:project) { create(:project, :repository) } let(:project) { create(:project, :repository) }
let(:head_sha) { project.repository.head_commit.id } let(:head_sha) { project.repository.head_commit.id }
let(:pipeline) { create(:ci_empty_pipeline, project: project, sha: head_sha) } let(:pipeline) { create(:ci_empty_pipeline, project: project, sha: head_sha) }
let(:attributes) { { name: 'rspec', ref: 'master' } } let(:attributes) { { name: 'rspec', ref: 'master', scheduling_type: :stage } }
let(:previous_stages) { [] } let(:previous_stages) { [] }
let(:seed_build) { described_class.new(pipeline, attributes, previous_stages) } let(:seed_build) { described_class.new(pipeline, attributes, previous_stages) }
@ -244,7 +244,9 @@ describe Gitlab::Ci::Pipeline::Seed::Build do
context 'when job is a bridge' do context 'when job is a bridge' do
let(:attributes) do let(:attributes) do
{ name: 'rspec', ref: 'master', options: { trigger: 'my/project' } } {
name: 'rspec', ref: 'master', options: { trigger: 'my/project' }, scheduling_type: :stage
}
end end
it { is_expected.to be_a(::Ci::Bridge) } it { is_expected.to be_a(::Ci::Bridge) }

View File

@ -10,9 +10,9 @@ describe Gitlab::Ci::Pipeline::Seed::Stage do
let(:attributes) do let(:attributes) do
{ name: 'test', { name: 'test',
index: 0, index: 0,
builds: [{ name: 'rspec' }, builds: [{ name: 'rspec', scheduling_type: :stage },
{ name: 'spinach' }, { name: 'spinach', scheduling_type: :stage },
{ name: 'deploy', only: { refs: ['feature'] } }] } { name: 'deploy', only: { refs: ['feature'] } }], scheduling_type: :stage }
end end
subject do subject do

View File

@ -36,7 +36,8 @@ module Gitlab
interruptible: true, interruptible: true,
allow_failure: false, allow_failure: false,
when: "on_success", when: "on_success",
yaml_variables: [] yaml_variables: [],
scheduling_type: :stage
}) })
end end
end end
@ -66,7 +67,8 @@ module Gitlab
], ],
allow_failure: false, allow_failure: false,
when: 'on_success', when: 'on_success',
yaml_variables: [] yaml_variables: [],
scheduling_type: :stage
}) })
end end
end end
@ -126,7 +128,8 @@ module Gitlab
interruptible: true, interruptible: true,
allow_failure: false, allow_failure: false,
when: "on_success", when: "on_success",
yaml_variables: [] yaml_variables: [],
scheduling_type: :stage
}) })
end end
end end
@ -282,6 +285,7 @@ module Gitlab
allow_failure: false, allow_failure: false,
when: "on_success", when: "on_success",
yaml_variables: [], yaml_variables: [],
scheduling_type: :stage,
options: { script: ["rspec"] }, options: { script: ["rspec"] },
only: { refs: ["branches"] } }] }, only: { refs: ["branches"] } }] },
{ name: "deploy", { name: "deploy",
@ -293,6 +297,7 @@ module Gitlab
allow_failure: false, allow_failure: false,
when: "on_success", when: "on_success",
yaml_variables: [], yaml_variables: [],
scheduling_type: :stage,
options: { script: ["cap prod"] }, options: { script: ["cap prod"] },
only: { refs: ["tags"] } }] }, only: { refs: ["tags"] } }] },
{ name: ".post", { name: ".post",
@ -642,7 +647,8 @@ module Gitlab
}, },
allow_failure: false, allow_failure: false,
when: "on_success", when: "on_success",
yaml_variables: [] yaml_variables: [],
scheduling_type: :stage
}) })
end end
@ -674,7 +680,8 @@ module Gitlab
}, },
allow_failure: false, allow_failure: false,
when: "on_success", when: "on_success",
yaml_variables: [] yaml_variables: [],
scheduling_type: :stage
}) })
end end
end end
@ -702,7 +709,8 @@ module Gitlab
}, },
allow_failure: false, allow_failure: false,
when: "on_success", when: "on_success",
yaml_variables: [] yaml_variables: [],
scheduling_type: :stage
}) })
end end
@ -728,7 +736,8 @@ module Gitlab
}, },
allow_failure: false, allow_failure: false,
when: "on_success", when: "on_success",
yaml_variables: [] yaml_variables: [],
scheduling_type: :stage
}) })
end end
end end
@ -1250,7 +1259,8 @@ module Gitlab
}, },
when: "on_success", when: "on_success",
allow_failure: false, allow_failure: false,
yaml_variables: [] yaml_variables: [],
scheduling_type: :stage
}) })
end end
@ -1604,7 +1614,8 @@ module Gitlab
}, },
when: "on_success", when: "on_success",
allow_failure: false, allow_failure: false,
yaml_variables: [] yaml_variables: [],
scheduling_type: :stage
) )
expect(subject.builds[4]).to eq( expect(subject.builds[4]).to eq(
stage: "test", stage: "test",
@ -1618,7 +1629,8 @@ module Gitlab
], ],
when: "on_success", when: "on_success",
allow_failure: false, allow_failure: false,
yaml_variables: [] yaml_variables: [],
scheduling_type: :dag
) )
end end
end end
@ -1644,7 +1656,8 @@ module Gitlab
}, },
when: "on_success", when: "on_success",
allow_failure: false, allow_failure: false,
yaml_variables: [] yaml_variables: [],
scheduling_type: :stage
) )
expect(subject.builds[4]).to eq( expect(subject.builds[4]).to eq(
stage: "test", stage: "test",
@ -1660,7 +1673,8 @@ module Gitlab
], ],
when: "on_success", when: "on_success",
allow_failure: false, allow_failure: false,
yaml_variables: [] yaml_variables: [],
scheduling_type: :dag
) )
end end
end end
@ -1682,7 +1696,8 @@ module Gitlab
], ],
when: "on_success", when: "on_success",
allow_failure: false, allow_failure: false,
yaml_variables: [] yaml_variables: [],
scheduling_type: :dag
) )
end end
end end
@ -1712,7 +1727,8 @@ module Gitlab
], ],
when: "on_success", when: "on_success",
allow_failure: false, allow_failure: false,
yaml_variables: [] yaml_variables: [],
scheduling_type: :dag
) )
end end
end end
@ -1849,7 +1865,8 @@ module Gitlab
}, },
when: "on_success", when: "on_success",
allow_failure: false, allow_failure: false,
yaml_variables: [] yaml_variables: [],
scheduling_type: :stage
}) })
end end
end end
@ -1895,7 +1912,8 @@ module Gitlab
}, },
when: "on_success", when: "on_success",
allow_failure: false, allow_failure: false,
yaml_variables: [] yaml_variables: [],
scheduling_type: :stage
}) })
expect(subject.second).to eq({ expect(subject.second).to eq({
stage: "build", stage: "build",
@ -1907,7 +1925,8 @@ module Gitlab
}, },
when: "on_success", when: "on_success",
allow_failure: false, allow_failure: false,
yaml_variables: [] yaml_variables: [],
scheduling_type: :stage
}) })
end end
end end

View File

@ -360,6 +360,7 @@ CommitStatus:
- upstream_pipeline_id - upstream_pipeline_id
- interruptible - interruptible
- processed - processed
- scheduling_type
Ci::Variable: Ci::Variable:
- id - id
- project_id - project_id

View File

@ -3007,7 +3007,8 @@ describe Ci::Build do
stage: 'test', stage: 'test',
ref: 'feature', ref: 'feature',
project: project, project: project,
pipeline: pipeline pipeline: pipeline,
scheduling_type: :stage
) )
end end

View File

@ -52,4 +52,72 @@ describe Ci::Processable do
end end
end end
end end
describe 'validate presence of scheduling_type' do
context 'on create' do
let(:processable) do
build(
:ci_build, :created, project: project, pipeline: pipeline,
importing: importing, scheduling_type: nil
)
end
context 'when importing' do
let(:importing) { true }
context 'when validate_scheduling_type_of_processables is true' do
before do
stub_feature_flags(validate_scheduling_type_of_processables: true)
end
it 'does not validate' do
expect(processable).to be_valid
end
end
context 'when validate_scheduling_type_of_processables is false' do
before do
stub_feature_flags(validate_scheduling_type_of_processables: false)
end
it 'does not validate' do
expect(processable).to be_valid
end
end
end
context 'when not importing' do
let(:importing) { false }
context 'when validate_scheduling_type_of_processables is true' do
before do
stub_feature_flags(validate_scheduling_type_of_processables: true)
end
it 'validates' do
expect(processable).not_to be_valid
end
end
context 'when validate_scheduling_type_of_processables is false' do
before do
stub_feature_flags(validate_scheduling_type_of_processables: false)
end
it 'does not validate' do
expect(processable).to be_valid
end
end
end
end
context 'on update' do
let(:processable) { create(:ci_build, :created, project: project, pipeline: pipeline) }
it 'does not validate' do
processable.scheduling_type = nil
expect(processable).to be_valid
end
end
end
end end

View File

@ -131,6 +131,10 @@ describe Ci::CreatePipelineService do
) )
end end
end end
it "sets scheduling_type as 'dag'" do
expect(test_a_build.scheduling_type).to eq('dag')
end
end end
context 'with an invalid config' do context 'with an invalid config' do

View File

@ -1750,9 +1750,9 @@ describe Ci::CreatePipelineService do
let(:ref_name) { 'refs/heads/master' } let(:ref_name) { 'refs/heads/master' }
let(:pipeline) { execute_service } let(:pipeline) { execute_service }
let(:build_names) { pipeline.builds.pluck(:name) } let(:build_names) { pipeline.builds.pluck(:name) }
let(:regular_job) { pipeline.builds.find_by(name: 'regular-job') } let(:regular_job) { find_job('regular-job') }
let(:rules_job) { pipeline.builds.find_by(name: 'rules-job') } let(:rules_job) { find_job('rules-job') }
let(:delayed_job) { pipeline.builds.find_by(name: 'delayed-job') } let(:delayed_job) { find_job('delayed-job') }
shared_examples 'rules jobs are excluded' do shared_examples 'rules jobs are excluded' do
it 'only persists the job without rules' do it 'only persists the job without rules' do
@ -1763,6 +1763,10 @@ describe Ci::CreatePipelineService do
end end
end end
def find_job(name)
pipeline.builds.find_by(name: name)
end
before do before do
stub_ci_pipeline_yaml_file(config) stub_ci_pipeline_yaml_file(config)
allow_any_instance_of(Ci::BuildScheduleWorker).to receive(:perform).and_return(true) allow_any_instance_of(Ci::BuildScheduleWorker).to receive(:perform).and_return(true)
@ -1782,6 +1786,12 @@ describe Ci::CreatePipelineService do
- if: $CI_COMMIT_REF_NAME =~ /master/ - if: $CI_COMMIT_REF_NAME =~ /master/
when: manual when: manual
negligible-job:
script: "exit 1"
rules:
- if: $CI_COMMIT_REF_NAME =~ /master/
allow_failure: true
delayed-job: delayed-job:
script: "echo See you later, World!" script: "echo See you later, World!"
rules: rules:
@ -1800,11 +1810,23 @@ describe Ci::CreatePipelineService do
context 'with matches' do context 'with matches' do
it 'creates a pipeline with the vanilla and manual jobs' do it 'creates a pipeline with the vanilla and manual jobs' do
expect(pipeline).to be_persisted expect(pipeline).to be_persisted
expect(build_names).to contain_exactly('regular-job', 'delayed-job', 'master-job') expect(build_names).to contain_exactly(
'regular-job', 'delayed-job', 'master-job', 'negligible-job'
)
end end
it 'assigns job:when values to the builds' do it 'assigns job:when values to the builds' do
expect(pipeline.builds.pluck(:when)).to contain_exactly('on_success', 'delayed', 'manual') expect(find_job('regular-job').when).to eq('on_success')
expect(find_job('master-job').when).to eq('manual')
expect(find_job('negligible-job').when).to eq('on_success')
expect(find_job('delayed-job').when).to eq('delayed')
end
it 'assigns job:allow_failure values to the builds' do
expect(find_job('regular-job').allow_failure).to eq(false)
expect(find_job('master-job').allow_failure).to eq(false)
expect(find_job('negligible-job').allow_failure).to eq(true)
expect(find_job('delayed-job').allow_failure).to eq(false)
end end
it 'assigns start_in for delayed jobs' do it 'assigns start_in for delayed jobs' do
@ -1827,6 +1849,7 @@ describe Ci::CreatePipelineService do
rules: rules:
- if: $VAR == 'present' && $OTHER || $CI_COMMIT_REF_NAME - if: $VAR == 'present' && $OTHER || $CI_COMMIT_REF_NAME
when: manual when: manual
allow_failure: true
EOY EOY
end end
@ -1834,6 +1857,7 @@ describe Ci::CreatePipelineService do
expect(pipeline).to be_persisted expect(pipeline).to be_persisted
expect(build_names).to contain_exactly('regular-job') expect(build_names).to contain_exactly('regular-job')
expect(regular_job.when).to eq('manual') expect(regular_job.when).to eq('manual')
expect(regular_job.allow_failure).to eq(true)
end end
end end
@ -1860,6 +1884,13 @@ describe Ci::CreatePipelineService do
- README.md - README.md
when: delayed when: delayed
start_in: 4 hours start_in: 4 hours
negligible-job:
script: "can be failed sometimes"
rules:
- changes:
- README.md
allow_failure: true
EOY EOY
end end
@ -1872,7 +1903,7 @@ describe Ci::CreatePipelineService do
it 'creates two jobs' do it 'creates two jobs' do
expect(pipeline).to be_persisted expect(pipeline).to be_persisted
expect(build_names) expect(build_names)
.to contain_exactly('regular-job', 'rules-job', 'delayed-job') .to contain_exactly('regular-job', 'rules-job', 'delayed-job', 'negligible-job')
end end
it 'sets when: for all jobs' do it 'sets when: for all jobs' do
@ -1881,6 +1912,10 @@ describe Ci::CreatePipelineService do
expect(delayed_job.when).to eq('delayed') expect(delayed_job.when).to eq('delayed')
expect(delayed_job.options[:start_in]).to eq('4 hours') expect(delayed_job.options[:start_in]).to eq('4 hours')
end end
it 'sets allow_failure: for negligible job' do
expect(find_job('negligible-job').allow_failure).to eq(true)
end
end end
context 'and matches the second rule' do context 'and matches the second rule' do
@ -1922,12 +1957,14 @@ describe Ci::CreatePipelineService do
rules-job: rules-job:
script: "echo hello world, $CI_COMMIT_REF_NAME" script: "echo hello world, $CI_COMMIT_REF_NAME"
allow_failure: true
rules: rules:
- changes: - changes:
- README.md - README.md
when: manual when: manual
- if: $CI_COMMIT_REF_NAME == "master" - if: $CI_COMMIT_REF_NAME == "master"
when: on_success when: on_success
allow_failure: false
delayed-job: delayed-job:
script: "echo See you later, World!" script: "echo See you later, World!"
@ -1936,6 +1973,7 @@ describe Ci::CreatePipelineService do
- README.md - README.md
when: delayed when: delayed
start_in: 4 hours start_in: 4 hours
allow_failure: true
- if: $CI_COMMIT_REF_NAME == "master" - if: $CI_COMMIT_REF_NAME == "master"
when: delayed when: delayed
start_in: 1 hour start_in: 1 hour
@ -1960,6 +1998,12 @@ describe Ci::CreatePipelineService do
expect(delayed_job.when).to eq('delayed') expect(delayed_job.when).to eq('delayed')
expect(delayed_job.options[:start_in]).to eq('4 hours') expect(delayed_job.options[:start_in]).to eq('4 hours')
end end
it 'sets allow_failure: for all jobs' do
expect(regular_job.allow_failure).to eq(false)
expect(rules_job.allow_failure).to eq(true)
expect(delayed_job.allow_failure).to eq(true)
end
end end
context 'and if: matches after changes' do context 'and if: matches after changes' do
@ -1999,6 +2043,7 @@ describe Ci::CreatePipelineService do
- if: $CI_COMMIT_REF_NAME =~ /master/ - if: $CI_COMMIT_REF_NAME =~ /master/
changes: [README.md] changes: [README.md]
when: on_success when: on_success
allow_failure: true
- if: $CI_COMMIT_REF_NAME =~ /master/ - if: $CI_COMMIT_REF_NAME =~ /master/
changes: [app.rb] changes: [app.rb]
when: manual when: manual
@ -2016,6 +2061,7 @@ describe Ci::CreatePipelineService do
expect(regular_job).to be_persisted expect(regular_job).to be_persisted
expect(rules_job).to be_persisted expect(rules_job).to be_persisted
expect(rules_job.when).to eq('manual') expect(rules_job.when).to eq('manual')
expect(rules_job.allow_failure).to eq(false)
end end
end end
@ -2040,6 +2086,150 @@ describe Ci::CreatePipelineService do
it_behaves_like 'rules jobs are excluded' it_behaves_like 'rules jobs are excluded'
end end
end end
context 'with complex if: allow_failure usages' do
let(:config) do
<<-EOY
job-1:
script: "exit 1"
allow_failure: true
rules:
- if: $CI_COMMIT_REF_NAME =~ /master/
allow_failure: false
job-2:
script: "exit 1"
allow_failure: true
rules:
- if: $CI_COMMIT_REF_NAME =~ /nonexistant-branch/
allow_failure: false
job-3:
script: "exit 1"
rules:
- if: $CI_COMMIT_REF_NAME =~ /nonexistant-branch/
allow_failure: true
job-4:
script: "exit 1"
rules:
- if: $CI_COMMIT_REF_NAME =~ /master/
allow_failure: false
job-5:
script: "exit 1"
allow_failure: false
rules:
- if: $CI_COMMIT_REF_NAME =~ /master/
allow_failure: true
job-6:
script: "exit 1"
rules:
- if: $CI_COMMIT_REF_NAME =~ /nonexistant-branch/
allow_failure: false
- allow_failure: true
EOY
end
it 'creates a pipeline' do
expect(pipeline).to be_persisted
expect(build_names).to contain_exactly('job-1', 'job-4', 'job-5', 'job-6')
end
it 'assigns job:allow_failure values to the builds' do
expect(find_job('job-1').allow_failure).to eq(false)
expect(find_job('job-4').allow_failure).to eq(false)
expect(find_job('job-5').allow_failure).to eq(true)
expect(find_job('job-6').allow_failure).to eq(true)
end
end
context 'with complex if: allow_failure & when usages' do
let(:config) do
<<-EOY
job-1:
script: "exit 1"
rules:
- if: $CI_COMMIT_REF_NAME =~ /master/
when: manual
job-2:
script: "exit 1"
rules:
- if: $CI_COMMIT_REF_NAME =~ /master/
when: manual
allow_failure: true
job-3:
script: "exit 1"
allow_failure: true
rules:
- if: $CI_COMMIT_REF_NAME =~ /master/
when: manual
job-4:
script: "exit 1"
allow_failure: true
rules:
- if: $CI_COMMIT_REF_NAME =~ /master/
when: manual
allow_failure: false
job-5:
script: "exit 1"
rules:
- if: $CI_COMMIT_REF_NAME =~ /nonexistant-branch/
when: manual
allow_failure: false
- when: always
allow_failure: true
job-6:
script: "exit 1"
allow_failure: false
rules:
- if: $CI_COMMIT_REF_NAME =~ /master/
when: manual
job-7:
script: "exit 1"
allow_failure: false
rules:
- if: $CI_COMMIT_REF_NAME =~ /nonexistant-branch/
when: manual
- when: :on_failure
allow_failure: true
EOY
end
it 'creates a pipeline' do
expect(pipeline).to be_persisted
expect(build_names).to contain_exactly(
'job-1', 'job-2', 'job-3', 'job-4', 'job-5', 'job-6', 'job-7'
)
end
it 'assigns job:allow_failure values to the builds' do
expect(find_job('job-1').allow_failure).to eq(false)
expect(find_job('job-2').allow_failure).to eq(true)
expect(find_job('job-3').allow_failure).to eq(true)
expect(find_job('job-4').allow_failure).to eq(false)
expect(find_job('job-5').allow_failure).to eq(true)
expect(find_job('job-6').allow_failure).to eq(false)
expect(find_job('job-7').allow_failure).to eq(true)
end
it 'assigns job:when values to the builds' do
expect(find_job('job-1').when).to eq('manual')
expect(find_job('job-2').when).to eq('manual')
expect(find_job('job-3').when).to eq('manual')
expect(find_job('job-4').when).to eq('manual')
expect(find_job('job-5').when).to eq('always')
expect(find_job('job-6').when).to eq('manual')
expect(find_job('job-7').when).to eq('on_failure')
end
end
end end
end end

View File

@ -222,6 +222,28 @@ describe Ci::RetryBuildService do
expect { new_build }.to change { Deployment.count }.by(1) expect { new_build }.to change { Deployment.count }.by(1)
end end
end end
context 'when scheduling_type of build is nil' do
before do
build.update_columns(scheduling_type: nil)
end
context 'when build has not needs' do
it 'sets scheduling_type as :stage' do
expect(new_build.scheduling_type).to eq('stage')
end
end
context 'when build has needs' do
before do
create(:ci_build_need, build: build)
end
it 'sets scheduling_type as :dag' do
expect(new_build.scheduling_type).to eq('dag')
end
end
end
end end
context 'when user does not have ability to execute build' do context 'when user does not have ability to execute build' do

View File

@ -8,7 +8,7 @@ elasticsearch:
client: client:
replicas: 1 replicas: 1
data: data:
replicas: 1 replicas: 2
kibana: kibana:
enabled: false enabled: false

View File

@ -0,0 +1,23 @@
#!/bin/bash
# http://redsymbol.net/articles/unofficial-bash-strict-mode/
IFS=$'\n\t'
set -euo pipefail
HOST="$1"
printf 'Waiting for ES to be reachable ...'
until $(wget -O- -q "$HOST" &>/dev/null); do
printf '.'
sleep 1
done
echo " OK!"
printf 'Waiting for ES to be healthy ...'
while : ; do
HEALTH="$(wget -O- -q "$HOST/_cat/health?h=status" 2> /dev/null)"
HEALTH="$(echo "$HEALTH" | sed -r 's/^[[:space:]]+|[[:space:]]+$//g')" # trim whitespace (otherwise we'll have "green ")
([ "$HEALTH" != "green" ] && printf '.' && sleep 1) || break
done
echo " OK!"
echo "Elastic Search is up!"