Commit 8c9dc985 authored by GitLab Bot's avatar GitLab Bot

Add latest changes from gitlab-org/gitlab@master

parent 500626a5
<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 { ...@@ -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 { ...@@ -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)"
......
...@@ -327,7 +327,10 @@ export const getSelectedFragment = restrictToNode => { ...@@ -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;
}; };
......
...@@ -8,7 +8,6 @@ module Ci ...@@ -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)
......
...@@ -10,7 +10,6 @@ module Ci ...@@ -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
......
...@@ -2,10 +2,14 @@ ...@@ -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 ...@@ -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 ...@@ -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
...@@ -30,7 +30,8 @@ module Clusters ...@@ -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 ...@@ -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 ...@@ -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
......
...@@ -5,7 +5,7 @@ module Ci ...@@ -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 ...@@ -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 ...@@ -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!
......
...@@ -29,7 +29,7 @@ ...@@ -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\") }" }
......
---
title: Fix copy markdown with elements with no text content
merge_request: 24020
author:
type: fixed
---
title: Implement support of allow_failure keyword for CI rules
merge_request: 24605
author:
type: added
---
title: Wait for elasticsearch to be green on install
merge_request: 24489
author:
type: added
---
title: Hide comment button if on diff HEAD
merge_request: 24207
author:
type: changed
---
title: Add styles for board list labels when text is too long
merge_request: 24627
author:
type: fixed
...@@ -165,9 +165,10 @@ class Gitlab::Seeder::Pipelines ...@@ -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
......
# 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
...@@ -677,6 +677,7 @@ ActiveRecord::Schema.define(version: 2020_02_11_152410) do ...@@ -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])))"
......
...@@ -851,7 +851,7 @@ In this example, if the first rule: ...@@ -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:** ...@@ -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: ...@@ -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`.
......
...@@ -464,8 +464,8 @@ chart is used to install this application with a ...@@ -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)
......
...@@ -241,9 +241,10 @@ and give all group members access to the project at once. ...@@ -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
......
...@@ -6,11 +6,12 @@ module Gitlab ...@@ -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 ...@@ -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')
......
...@@ -132,7 +132,8 @@ module Gitlab ...@@ -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
......
...@@ -258,7 +258,8 @@ module Gitlab ...@@ -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
......
...@@ -9,10 +9,10 @@ module Gitlab ...@@ -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 ...@@ -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
......
...@@ -65,6 +65,7 @@ module Gitlab ...@@ -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],
......
...@@ -9,6 +9,7 @@ FactoryBot.define do ...@@ -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
......
...@@ -11,6 +11,7 @@ FactoryBot.define do ...@@ -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
......
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();
});
describe('computed', () => { const setWindowLocation = value => {
describe('lineHref', () => { Object.defineProperty(window, 'location', {
it('should prepend # to lineCode', () => { writable: true,
const lineCode = 'LC_42'; value,
const component = createComponent(); });
};
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('comment button', () => {
it.each`
showCommentButton | userData | query | expectation
${true} | ${TEST_USER} | ${'diff_head=false'} | ${true}
${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(findNoteButton().exists()).toBe(expectation);
},
);
it.each`
isHover | otherProps | discussions | expectation
${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(`#${lineCode}`); expect(findNoteButton().isVisible()).toBe(expectation);
},
);
}); });
it('should return # if there is no lineCode', () => { describe('line number', () => {
const component = createComponent(); describe('without lineNumber prop', () => {
component.line.line_code = ''; it('does not render', () => {
createComponent();
expect(component.lineHref).toEqual('#'); 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], findLineNumber().trigger('click');
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); expect(store.dispatch).toHaveBeenCalledWith('diffs/setHighlightedRow', expectedClickArg);
expect(component.shouldShowAvatarsOnGutter).toEqual(true);
}); });
}); });
}); });
describe('template', () => {
it('should render comment button', () => {
const component = createComponent({
showCommentButton: true,
}); });
Object.defineProperty(component, 'isLoggedIn', {
get() { describe('diff-gutter-avatars', () => {
return true; describe('with showCommentButton', () => {
}, beforeEach(() => {
jest.spyOn(store, 'dispatch').mockImplementation();
createComponent({ showCommentButton: true });
}); });
expect(component.$el.querySelector('.js-add-diff-note-button')).toBeDefined(); it('renders', () => {
expect(findAvatars().props()).toEqual({
discussions: line.discussions,
discussionsExpanded: line.discussionsExpanded,
});
}); });
it('should render line link', () => { it('toggles line discussion', () => {
const lineNumber = 42; expect(store.dispatch).not.toHaveBeenCalled();
const lineCode = `LC_${lineNumber}`;
const component = createComponent({ lineNumber, lineCode });
const link = component.$el.querySelector('a');
expect(link.href.indexOf(`#${lineCode}`)).toBeGreaterThan(-1); findAvatars().vm.$emit('toggleLineDiscussions');
expect(link.dataset.linenumber).toEqual(lineNumber.toString());
});
it('should render user avatars', () => { expect(store.dispatch).toHaveBeenCalledWith('diffs/toggleLineDiscussions', {
const component = createComponent({ lineCode: TEST_LINE_CODE,
showCommentButton: true, fileHash: TEST_FILE_HASH,
lineCode: getDiffFileMock().highlighted_diff_lines[1].line_code, expanded: !line.discussionsExpanded,
});
}); });
expect(component.$el.querySelector('.diff-comment-avatar-holders')).not.toBe(null);
}); });
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);
},
);
}); });
}); });
...@@ -52,6 +52,7 @@ describe('ShortcutsIssuable', function() { ...@@ -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() { ...@@ -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();
});
});
});
}); });
}); });
...@@ -102,9 +102,9 @@ describe Gitlab::Ci::Build::Rules do ...@@ -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 ...@@ -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
...@@ -105,7 +105,8 @@ describe Gitlab::Ci::Config::Entry::Bridge do ...@@ -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 ...@@ -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
......
...@@ -110,6 +110,10 @@ describe Gitlab::Ci::Config::Entry::Job do ...@@ -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 ...@@ -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
......
...@@ -98,7 +98,8 @@ describe Gitlab::Ci::Config::Entry::Jobs do ...@@ -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 ...@@ -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
......
...@@ -130,7 +130,8 @@ describe Gitlab::Ci::Config::Entry::Root do ...@@ -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 ...@@ -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 ...@@ -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 ...@@ -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 ...@@ -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
......
...@@ -27,8 +27,14 @@ describe Gitlab::Ci::Config::Entry::Rules::Rule do ...@@ -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 ...@@ -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 ...@@ -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
......
...@@ -6,7 +6,7 @@ describe Gitlab::Ci::Pipeline::Seed::Build do ...@@ -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 ...@@ -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) }
......
...@@ -10,9 +10,9 @@ describe Gitlab::Ci::Pipeline::Seed::Stage do ...@@ -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
......
...@@ -36,7 +36,8 @@ module Gitlab ...@@ -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 ...@@ -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 ...@@ -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 ...@@ -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 ...@@ -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 ...@@ -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 ...@@ -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 ...@@ -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 ...@@ -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 ...@@ -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 ...@@ -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 ...@@ -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 ...@@ -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 ...@@ -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 ...@@ -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 ...@@ -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 ...@@ -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 ...@@ -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 ...@@ -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
......
...@@ -360,6 +360,7 @@ CommitStatus: ...@@ -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
......
...@@ -3007,7 +3007,8 @@ describe Ci::Build do ...@@ -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
......
...@@ -52,4 +52,72 @@ describe Ci::Processable do ...@@ -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
...@@ -131,6 +131,10 @@ describe Ci::CreatePipelineService do ...@@ -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
......
...@@ -1750,9 +1750,9 @@ describe Ci::CreatePipelineService do ...@@ -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 ...@@ -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 ...@@ -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 ...@@ -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 ...@@ -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 ...@@ -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 ...@@ -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 ...@@ -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 ...@@ -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 ...@@ -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 ...@@ -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 ...@@ -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 ...@@ -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 ...@@ -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 ...@@ -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
......
...@@ -222,6 +222,28 @@ describe Ci::RetryBuildService do ...@@ -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
......
...@@ -8,7 +8,7 @@ elasticsearch: ...@@ -8,7 +8,7 @@ elasticsearch:
client: client:
replicas: 1 replicas: 1
data: data:
replicas: 1 replicas: 2
kibana: kibana:
enabled: false enabled: false
......
#!/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!"
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