提交 a98649b7 编写于 作者: G GitLab Bot

Add latest changes from gitlab-org/gitlab@master

上级 a4484fd2
...@@ -94,11 +94,11 @@ setup-test-env: ...@@ -94,11 +94,11 @@ setup-test-env:
rspec unit pg9: rspec unit pg9:
extends: .rspec-base-pg9 extends: .rspec-base-pg9
parallel: 20 parallel: 24
rspec unit pg9-foss: rspec unit pg9-foss:
extends: .rspec-base-pg9-foss extends: .rspec-base-pg9-foss
parallel: 20 parallel: 24
rspec integration pg9: rspec integration pg9:
extends: .rspec-base-pg9 extends: .rspec-base-pg9
......
Please view this file on the master branch, on stable branches it's out of date. Please view this file on the master branch, on stable branches it's out of date.
## 12.5.1
### Security (6 changes)
- Protect Jira integration endpoints from guest users.
- Fix private comment Elasticsearch leak on project search scope.
- Filter snippet search results by feature visibility.
- Hide AWS secret on Admin Integration page.
- Fail pull mirror when mirror user is blocked.
- Prevent IDOR when adding users to protected environments.
## 12.5.0 ## 12.5.0
### Security (5 changes) ### Security (5 changes)
...@@ -224,6 +236,18 @@ Please view this file on the master branch, on stable branches it's out of date. ...@@ -224,6 +236,18 @@ Please view this file on the master branch, on stable branches it's out of date.
- Docs for protected branch code owner approval API. !17132 - Docs for protected branch code owner approval API. !17132
## 12.3.7
### Security (6 changes)
- Protect Jira integration endpoints from guest users.
- Fix private comment Elasticsearch leak on project search scope.
- Filter snippet search results by feature visibility.
- Hide AWS secret on Admin Integration page.
- Fail pull mirror when mirror user is blocked.
- Prevent IDOR when adding users to protected environments.
## 12.3.4 ## 12.3.4
### Fixed (2 changes) ### Fixed (2 changes)
......
...@@ -4,16 +4,19 @@ entry. ...@@ -4,16 +4,19 @@ entry.
## 12.5.1 ## 12.5.1
### Security (8 changes) ### Security (11 changes)
- Check permissions before showing a forked project's source. - Do not create todos for approvers without access. !1442
- Hide commit counts from guest users in Cycle Analytics.
- Encrypt application setting tokens. - Encrypt application setting tokens.
- Update Workhorse and Gitaly to fix a security issue. - Update Workhorse and Gitaly to fix a security issue.
- Hide commit counts from guest users in Cycle Analytics. - Add maven file_name regex validation on incoming files.
- Check permissions before showing a forked project's source.
- Limit potential for DNS rebind SSRF in chat notifications. - Limit potential for DNS rebind SSRF in chat notifications.
- Ensure are cleaned by ImportExport::AttributeCleaner. - Ensure are cleaned by ImportExport::AttributeCleaner.
- Remove notes regarding Related Branches from Issue activity feeds for guest users. - Remove notes regarding Related Branches from Issue activity feeds for guest users.
- Escape namespace in label references to prevent XSS. - Escape namespace in label references to prevent XSS.
- Add authorization to using filter vulnerable in Dependency List.
## 12.5.0 ## 12.5.0
...@@ -367,21 +370,6 @@ entry. ...@@ -367,21 +370,6 @@ entry.
- Change selects from default browser style to custom style. - Change selects from default browser style to custom style.
## 12.4.4
### Security (9 changes)
- Check permissions before showing a forked project's source.
- Encrypt application setting tokens.
- Update Workhorse and Gitaly to fix a security issue.
- Hide commit counts from guest users in Cycle Analytics.
- Limit potential for DNS rebind SSRF in chat notifications.
- Fix 500 error caused by invalid byte sequences in links.
- Ensure are cleaned by ImportExport::AttributeCleaner.
- Remove notes regarding Related Branches from Issue activity feeds for guest users.
- Escape namespace in label references to prevent XSS.
## 12.4.3 ## 12.4.3
### Fixed (2 changes) ### Fixed (2 changes)
...@@ -752,17 +740,20 @@ entry. ...@@ -752,17 +740,20 @@ entry.
## 12.3.7 ## 12.3.7
### Security (9 changes) ### Security (12 changes)
- Check permissions before showing a forked project's source. - Do not create todos for approvers without access. !1442
- Limit potential for DNS rebind SSRF in chat notifications.
- Encrypt application setting tokens. - Encrypt application setting tokens.
- Update Workhorse and Gitaly to fix a security issue. - Update Workhorse and Gitaly to fix a security issue.
- Add maven file_name regex validation on incoming files.
- Hide commit counts from guest users in Cycle Analytics. - Hide commit counts from guest users in Cycle Analytics.
- Limit potential for DNS rebind SSRF in chat notifications. - Check permissions before showing a forked project's source.
- Fix 500 error caused by invalid byte sequences in links. - Fix 500 error caused by invalid byte sequences in links.
- Ensure are cleaned by ImportExport::AttributeCleaner. - Ensure are cleaned by ImportExport::AttributeCleaner.
- Remove notes regarding Related Branches from Issue activity feeds for guest users. - Remove notes regarding Related Branches from Issue activity feeds for guest users.
- Escape namespace in label references to prevent XSS. - Escape namespace in label references to prevent XSS.
- Add authorization to using filter vulnerable in Dependency List.
## 12.3.4 ## 12.3.4
......
<script> <script>
/* eslint-disable @gitlab/vue-i18n/no-bare-strings */ /* eslint-disable @gitlab/vue-i18n/no-bare-strings */
import { __, sprintf } from '~/locale'; import { __, sprintf } from '~/locale';
import Timeago from 'timeago.js'; import { format } from 'timeago.js';
import _ from 'underscore'; import _ from 'underscore';
import { GlTooltipDirective } from '@gitlab/ui'; import { GlTooltipDirective } from '@gitlab/ui';
import UserAvatarLink from '~/vue_shared/components/user_avatar/user_avatar_link.vue'; import UserAvatarLink from '~/vue_shared/components/user_avatar/user_avatar_link.vue';
...@@ -23,7 +23,6 @@ import { convertObjectPropsToCamelCase } from '~/lib/utils/common_utils'; ...@@ -23,7 +23,6 @@ import { convertObjectPropsToCamelCase } from '~/lib/utils/common_utils';
* *
* Renders a table row for each environment. * Renders a table row for each environment.
*/ */
const timeagoInstance = new Timeago();
export default { export default {
components: { components: {
...@@ -123,7 +122,7 @@ export default { ...@@ -123,7 +122,7 @@ export default {
*/ */
deployedDate() { deployedDate() {
if (this.canShowDate) { if (this.canShowDate) {
return timeagoInstance.format(this.model.last_deployment.deployed_at); return format(this.model.last_deployment.deployed_at);
} }
return ''; return '';
}, },
......
import $ from 'jquery'; import $ from 'jquery';
import _ from 'underscore'; import _ from 'underscore';
import timeago from 'timeago.js'; import * as timeago from 'timeago.js';
import dateFormat from 'dateformat'; import dateFormat from 'dateformat';
import { languageCode, s__, __, n__ } from '../../locale'; import { languageCode, s__, __, n__ } from '../../locale';
...@@ -92,90 +92,80 @@ export const formatDate = (datetime, format = 'mmm d, yyyy h:MMtt Z') => { ...@@ -92,90 +92,80 @@ export const formatDate = (datetime, format = 'mmm d, yyyy h:MMtt Z') => {
*/ */
const timeagoLanguageCode = languageCode().replace(/-/g, '_'); const timeagoLanguageCode = languageCode().replace(/-/g, '_');
let timeagoInstance;
/** /**
* Sets a timeago Instance * Registers timeago locales
*/ */
export const getTimeago = () => { const memoizedLocaleRemaining = () => {
if (!timeagoInstance) { const cache = [];
const memoizedLocaleRemaining = () => {
const cache = []; const timeAgoLocaleRemaining = [
() => [s__('Timeago|just now'), s__('Timeago|right now')],
const timeAgoLocaleRemaining = [ () => [s__('Timeago|just now'), s__('Timeago|%s seconds remaining')],
() => [s__('Timeago|just now'), s__('Timeago|right now')], () => [s__('Timeago|1 minute ago'), s__('Timeago|1 minute remaining')],
() => [s__('Timeago|just now'), s__('Timeago|%s seconds remaining')], () => [s__('Timeago|%s minutes ago'), s__('Timeago|%s minutes remaining')],
() => [s__('Timeago|1 minute ago'), s__('Timeago|1 minute remaining')], () => [s__('Timeago|1 hour ago'), s__('Timeago|1 hour remaining')],
() => [s__('Timeago|%s minutes ago'), s__('Timeago|%s minutes remaining')], () => [s__('Timeago|%s hours ago'), s__('Timeago|%s hours remaining')],
() => [s__('Timeago|1 hour ago'), s__('Timeago|1 hour remaining')], () => [s__('Timeago|1 day ago'), s__('Timeago|1 day remaining')],
() => [s__('Timeago|%s hours ago'), s__('Timeago|%s hours remaining')], () => [s__('Timeago|%s days ago'), s__('Timeago|%s days remaining')],
() => [s__('Timeago|1 day ago'), s__('Timeago|1 day remaining')], () => [s__('Timeago|1 week ago'), s__('Timeago|1 week remaining')],
() => [s__('Timeago|%s days ago'), s__('Timeago|%s days remaining')], () => [s__('Timeago|%s weeks ago'), s__('Timeago|%s weeks remaining')],
() => [s__('Timeago|1 week ago'), s__('Timeago|1 week remaining')], () => [s__('Timeago|1 month ago'), s__('Timeago|1 month remaining')],
() => [s__('Timeago|%s weeks ago'), s__('Timeago|%s weeks remaining')], () => [s__('Timeago|%s months ago'), s__('Timeago|%s months remaining')],
() => [s__('Timeago|1 month ago'), s__('Timeago|1 month remaining')], () => [s__('Timeago|1 year ago'), s__('Timeago|1 year remaining')],
() => [s__('Timeago|%s months ago'), s__('Timeago|%s months remaining')], () => [s__('Timeago|%s years ago'), s__('Timeago|%s years remaining')],
() => [s__('Timeago|1 year ago'), s__('Timeago|1 year remaining')], ];
() => [s__('Timeago|%s years ago'), s__('Timeago|%s years remaining')],
]; return (number, index) => {
if (cache[index]) {
return (number, index) => { return cache[index];
if (cache[index]) { }
return cache[index]; cache[index] = timeAgoLocaleRemaining[index] && timeAgoLocaleRemaining[index]();
} return cache[index];
cache[index] = timeAgoLocaleRemaining[index] && timeAgoLocaleRemaining[index](); };
return cache[index]; };
};
};
const memoizedLocale = () => {
const cache = [];
const timeAgoLocale = [
() => [s__('Timeago|just now'), s__('Timeago|right now')],
() => [s__('Timeago|just now'), s__('Timeago|in %s seconds')],
() => [s__('Timeago|1 minute ago'), s__('Timeago|in 1 minute')],
() => [s__('Timeago|%s minutes ago'), s__('Timeago|in %s minutes')],
() => [s__('Timeago|1 hour ago'), s__('Timeago|in 1 hour')],
() => [s__('Timeago|%s hours ago'), s__('Timeago|in %s hours')],
() => [s__('Timeago|1 day ago'), s__('Timeago|in 1 day')],
() => [s__('Timeago|%s days ago'), s__('Timeago|in %s days')],
() => [s__('Timeago|1 week ago'), s__('Timeago|in 1 week')],
() => [s__('Timeago|%s weeks ago'), s__('Timeago|in %s weeks')],
() => [s__('Timeago|1 month ago'), s__('Timeago|in 1 month')],
() => [s__('Timeago|%s months ago'), s__('Timeago|in %s months')],
() => [s__('Timeago|1 year ago'), s__('Timeago|in 1 year')],
() => [s__('Timeago|%s years ago'), s__('Timeago|in %s years')],
];
return (number, index) => {
if (cache[index]) {
return cache[index];
}
cache[index] = timeAgoLocale[index] && timeAgoLocale[index]();
return cache[index];
};
};
timeago.register(timeagoLanguageCode, memoizedLocale());
timeago.register(`${timeagoLanguageCode}-remaining`, memoizedLocaleRemaining());
timeagoInstance = timeago();
}
return timeagoInstance; const memoizedLocale = () => {
const cache = [];
const timeAgoLocale = [
() => [s__('Timeago|just now'), s__('Timeago|right now')],
() => [s__('Timeago|just now'), s__('Timeago|in %s seconds')],
() => [s__('Timeago|1 minute ago'), s__('Timeago|in 1 minute')],
() => [s__('Timeago|%s minutes ago'), s__('Timeago|in %s minutes')],
() => [s__('Timeago|1 hour ago'), s__('Timeago|in 1 hour')],
() => [s__('Timeago|%s hours ago'), s__('Timeago|in %s hours')],
() => [s__('Timeago|1 day ago'), s__('Timeago|in 1 day')],
() => [s__('Timeago|%s days ago'), s__('Timeago|in %s days')],
() => [s__('Timeago|1 week ago'), s__('Timeago|in 1 week')],
() => [s__('Timeago|%s weeks ago'), s__('Timeago|in %s weeks')],
() => [s__('Timeago|1 month ago'), s__('Timeago|in 1 month')],
() => [s__('Timeago|%s months ago'), s__('Timeago|in %s months')],
() => [s__('Timeago|1 year ago'), s__('Timeago|in 1 year')],
() => [s__('Timeago|%s years ago'), s__('Timeago|in %s years')],
];
return (number, index) => {
if (cache[index]) {
return cache[index];
}
cache[index] = timeAgoLocale[index] && timeAgoLocale[index]();
return cache[index];
};
}; };
timeago.register(timeagoLanguageCode, memoizedLocale());
timeago.register(`${timeagoLanguageCode}-remaining`, memoizedLocaleRemaining());
export const getTimeago = () => timeago;
/** /**
* For the given elements, sets a tooltip with a formatted date. * For the given elements, sets a tooltip with a formatted date.
* @param {JQuery} $timeagoEls * @param {JQuery} $timeagoEls
* @param {Boolean} setTimeago * @param {Boolean} setTimeago
*/ */
export const localTimeAgo = ($timeagoEls, setTimeago = true) => { export const localTimeAgo = ($timeagoEls, setTimeago = true) => {
getTimeago();
$timeagoEls.each((i, el) => { $timeagoEls.each((i, el) => {
$(el).text(timeagoInstance.format($(el).attr('datetime'), timeagoLanguageCode)); $(el).text(timeago.format($(el).attr('datetime'), timeagoLanguageCode));
}); });
if (!setTimeago) { if (!setTimeago) {
...@@ -207,9 +197,7 @@ export const timeFor = (time, expiredLabel) => { ...@@ -207,9 +197,7 @@ export const timeFor = (time, expiredLabel) => {
if (new Date(time) < new Date()) { if (new Date(time) < new Date()) {
return expiredLabel || s__('Timeago|Past due'); return expiredLabel || s__('Timeago|Past due');
} }
return getTimeago() return timeago.format(time, `${timeagoLanguageCode}-remaining`).trim();
.format(time, `${timeagoLanguageCode}-remaining`)
.trim();
}; };
export const getDayDifference = (a, b) => { export const getDayDifference = (a, b) => {
......
import Timeago from 'timeago.js'; import { format } from 'timeago.js';
import _ from 'underscore'; import _ from 'underscore';
import getStateKey from 'ee_else_ce/vue_merge_request_widget/stores/get_state_key'; import getStateKey from 'ee_else_ce/vue_merge_request_widget/stores/get_state_key';
import { stateKey } from './state_maps'; import { stateKey } from './state_maps';
...@@ -213,9 +213,7 @@ export default class MergeRequestStore { ...@@ -213,9 +213,7 @@ export default class MergeRequestStore {
return ''; return '';
} }
const timeagoInstance = new Timeago(); return format(date);
return timeagoInstance.format(date);
} }
static getPreferredAutoMergeStrategy(availableAutoMergeStrategies) { static getPreferredAutoMergeStrategy(availableAutoMergeStrategies) {
......
...@@ -53,6 +53,7 @@ module Resolvers ...@@ -53,6 +53,7 @@ module Resolvers
# https://gitlab.com/gitlab-org/gitlab-foss/issues/54520 # https://gitlab.com/gitlab-org/gitlab-foss/issues/54520
args[:project_id] = project.id args[:project_id] = project.id
args[:iids] ||= [args[:iid]].compact args[:iids] ||= [args[:iid]].compact
args[:attempt_project_search_optimizations] = args[:search].present?
IssuesFinder.new(context[:current_user], args).execute IssuesFinder.new(context[:current_user], args).execute
end end
......
...@@ -764,7 +764,7 @@ module Ci ...@@ -764,7 +764,7 @@ module Ci
# find all jobs that are needed # find all jobs that are needed
if Feature.enabled?(:ci_dag_support, project, default_enabled: true) && needs.exists? if Feature.enabled?(:ci_dag_support, project, default_enabled: true) && needs.exists?
depended_jobs = depended_jobs.where(name: needs.select(:name)) depended_jobs = depended_jobs.where(name: needs.artifacts.select(:name))
end end
# find all jobs that are dependent on # find all jobs that are dependent on
...@@ -772,6 +772,8 @@ module Ci ...@@ -772,6 +772,8 @@ module Ci
depended_jobs = depended_jobs.where(name: options[:dependencies]) depended_jobs = depended_jobs.where(name: options[:dependencies])
end end
# if both needs and dependencies are used,
# the end result will be an intersection between them
depended_jobs depended_jobs
end end
......
...@@ -10,5 +10,6 @@ module Ci ...@@ -10,5 +10,6 @@ module Ci
validates :name, presence: true, length: { maximum: 128 } validates :name, presence: true, length: { maximum: 128 }
scope :scoped_build, -> { where('ci_builds.id=ci_build_needs.build_id') } scope :scoped_build, -> { where('ci_builds.id=ci_build_needs.build_id') }
scope :artifacts, -> { where(artifacts: true) }
end end
end end
...@@ -6,9 +6,6 @@ class MilestoneRelease < ApplicationRecord ...@@ -6,9 +6,6 @@ class MilestoneRelease < ApplicationRecord
validate :same_project_between_milestone_and_release validate :same_project_between_milestone_and_release
# Keep until 2019-11-29
self.ignored_columns += %i[id]
private private
def same_project_between_milestone_and_release def same_project_between_milestone_and_release
......
---
title: Update timeago to the latest release
merge_request: 19407
author:
type: other
---
title: Create a license info rake task
merge_request: 20501
author: Jason Colyer
type: added
---
title: Control passing artifacts from CI DAG needs
merge_request: 19943
author:
type: added
---
title: Increase upper limit of start_in attribute to 1 week
merge_request: 20323
author: Will Layton
type: changed
---
title: Fix change to default foreground and backgorund colors in job log
merge_request: 20787
author:
type: fixed
---
title: Improve issues search performance on GraphQL
merge_request: 20784
author:
type: performance
---
title: Add maven file_name regex validation on incoming files
merge_request:
author:
type: security
# frozen_string_literal: true
class AddArtifactsToCiBuildNeed < ActiveRecord::Migration[5.2]
include Gitlab::Database::MigrationHelpers
DOWNTIME = false
disable_ddl_transaction!
def up
add_column_with_default(:ci_build_needs, :artifacts,
:boolean,
default: true,
allow_null: false)
end
def down
remove_column(:ci_build_needs, :artifacts)
end
end
...@@ -601,6 +601,7 @@ ActiveRecord::Schema.define(version: 2019_11_24_150431) do ...@@ -601,6 +601,7 @@ ActiveRecord::Schema.define(version: 2019_11_24_150431) do
create_table "ci_build_needs", id: :serial, force: :cascade do |t| create_table "ci_build_needs", id: :serial, force: :cascade do |t|
t.integer "build_id", null: false t.integer "build_id", null: false
t.text "name", null: false t.text "name", null: false
t.boolean "artifacts", default: true, null: false
t.index ["build_id", "name"], name: "index_ci_build_needs_on_build_id_and_name", unique: true t.index ["build_id", "name"], name: "index_ci_build_needs_on_build_id_and_name", unique: true
end end
......
...@@ -1245,11 +1245,12 @@ Delayed job are for executing scripts after a certain period. ...@@ -1245,11 +1245,12 @@ Delayed job are for executing scripts after a certain period.
This is useful if you want to avoid jobs entering `pending` state immediately. This is useful if you want to avoid jobs entering `pending` state immediately.
You can set the period with `start_in` key. The value of `start_in` key is an elapsed time in seconds, unless a unit is You can set the period with `start_in` key. The value of `start_in` key is an elapsed time in seconds, unless a unit is
provided. `start_in` key must be less than or equal to one hour. Examples of valid values include: provided. `start_in` key must be less than or equal to one week. Examples of valid values include:
- `10 seconds` - `10 seconds`
- `30 minutes` - `30 minutes`
- `1 hour` - `1 day`
- `1 week`
When there is a delayed job in a stage, the pipeline will not progress until the delayed job has finished. When there is a delayed job in a stage, the pipeline will not progress until the delayed job has finished.
This means this keyword can also be used for inserting delays between different stages. This means this keyword can also be used for inserting delays between different stages.
...@@ -2232,6 +2233,49 @@ This example creates three paths of execution: ...@@ -2232,6 +2233,49 @@ This example creates three paths of execution:
- Related to the above, stages must be explicitly defined for all jobs - Related to the above, stages must be explicitly defined for all jobs
that have the keyword `needs:` or are referred to by one. that have the keyword `needs:` or are referred to by one.
#### Artifact downloads with `needs`
> [Introduced](https://gitlab.com/gitlab-org/gitlab/issues/14311) in GitLab v12.6.
When using `needs`, artifact downloads are controlled with `artifacts: true` or `artifacts: false`.
The `dependencies` keyword should not be used with `needs`, as this is deprecated since GitLab 12.6.
In the example below, the `rspec` job will download the `build_job` artifacts, while the
`rubocop` job will not:
```yaml
build_job:
stage: build
artifacts:
paths:
- binaries/
rspec:
stage: test
needs:
- job: build_job
artifacts: true
rubocop:
stage: test
needs:
- job: build_job
artifacts: false
```
Additionally, in the three syntax examples below, the `rspec` job will download the artifacts
from all three `build_jobs`, as `artifacts` is true for `build_job_1`, and will
**default** to true for both `build_job_2` and `build_job_3`.
```yaml
rspec:
needs:
- job: build_job_1
artifacts: true
- job: build_job_2
- build_job_3
```
### `coverage` ### `coverage`
> [Introduced][ce-7447] in GitLab 8.17. > [Introduced][ce-7447] in GitLab 8.17.
......
...@@ -94,7 +94,7 @@ module Gitlab ...@@ -94,7 +94,7 @@ module Gitlab
def on_38(stack) { fg: fg_color_256(stack) } end def on_38(stack) { fg: fg_color_256(stack) } end
def on_39(_) { fg: fg_color(9) } end def on_39(_) { fg: nil } end
def on_40(_) { bg: bg_color(0) } end def on_40(_) { bg: bg_color(0) } end
...@@ -114,8 +114,7 @@ module Gitlab ...@@ -114,8 +114,7 @@ module Gitlab
def on_48(stack) { bg: bg_color_256(stack) } end def on_48(stack) { bg: bg_color_256(stack) } end
# TODO: all the x9 never get called? def on_49(_) { bg: nil } end
def on_49(_) { fg: fg_color(9) } end
def on_90(_) { fg: fg_color(0, 'l') } end def on_90(_) { fg: fg_color(0, 'l') } end
......
...@@ -61,9 +61,9 @@ module Gitlab ...@@ -61,9 +61,9 @@ module Gitlab
case case
when changes[:reset] when changes[:reset]
reset! reset!
when changes[:fg] when changes.key?(:fg)
@fg = changes[:fg] @fg = changes[:fg]
when changes[:bg] when changes.key?(:bg)
@bg = changes[:bg] @bg = changes[:bg]
when changes[:enable] when changes[:enable]
@mask |= changes[:enable] @mask |= changes[:enable]
......
...@@ -51,7 +51,7 @@ module Gitlab ...@@ -51,7 +51,7 @@ module Gitlab
validates :rules, array_of_hashes: true validates :rules, array_of_hashes: true
end end
validates :start_in, duration: { limit: '1 day' }, if: :delayed? validates :start_in, duration: { limit: '1 week' }, if: :delayed?
validates :start_in, absence: true, if: -> { has_rules? || !delayed? } validates :start_in, absence: true, if: -> { has_rules? || !delayed? }
validate do validate do
......
...@@ -5,9 +5,10 @@ module Gitlab ...@@ -5,9 +5,10 @@ module Gitlab
class Config class Config
module Entry module Entry
class Need < ::Gitlab::Config::Entry::Simplifiable class Need < ::Gitlab::Config::Entry::Simplifiable
strategy :Job, if: -> (config) { config.is_a?(String) } strategy :JobString, if: -> (config) { config.is_a?(String) }
strategy :JobHash, if: -> (config) { config.is_a?(Hash) && config.key?(:job) }
class Job < ::Gitlab::Config::Entry::Node class JobString < ::Gitlab::Config::Entry::Node
include ::Gitlab::Config::Entry::Validatable include ::Gitlab::Config::Entry::Validatable
validations do validations do
...@@ -20,7 +21,30 @@ module Gitlab ...@@ -20,7 +21,30 @@ module Gitlab
end end
def value def value
{ name: @config } { name: @config, artifacts: true }
end
end
class JobHash < ::Gitlab::Config::Entry::Node
include ::Gitlab::Config::Entry::Validatable
include ::Gitlab::Config::Entry::Attributable
ALLOWED_KEYS = %i[job artifacts].freeze
attributes :job, :artifacts
validations do
validates :config, presence: true
validates :config, allowed_keys: ALLOWED_KEYS
validates :job, type: String, presence: true
validates :artifacts, boolean: true, allow_nil: true
end
def type
:job
end
def value
{ name: job, artifacts: artifacts || artifacts.nil? }
end end
end end
......
...@@ -44,7 +44,7 @@ module Gitlab ...@@ -44,7 +44,7 @@ module Gitlab
if all_job_names = parallelized_jobs[job_need_name] if all_job_names = parallelized_jobs[job_need_name]
all_job_names.map do |job_name| all_job_names.map do |job_name|
{ name: job_name } job_need.merge(name: job_name)
end end
else else
job_need job_need
......
...@@ -6452,6 +6452,9 @@ msgstr "" ...@@ -6452,6 +6452,9 @@ msgstr ""
msgid "Enter merge request URLs" msgid "Enter merge request URLs"
msgstr "" msgstr ""
msgid "Enter new AWS Secret Access Key"
msgstr ""
msgid "Enter the issue description" msgid "Enter the issue description"
msgstr "" msgstr ""
......
import { __, s__ } from '~/locale';
import * as datetimeUtility from '~/lib/utils/datetime_utility'; import * as datetimeUtility from '~/lib/utils/datetime_utility';
describe('Date time utils', () => { describe('Date time utils', () => {
describe('timeFor', () => { describe('timeFor', () => {
it('returns `past due` when in past', () => { it('returns localize `past due` when in past', () => {
const date = new Date(); const date = new Date();
date.setFullYear(date.getFullYear() - 1); date.setFullYear(date.getFullYear() - 1);
expect(datetimeUtility.timeFor(date)).toBe('Past due'); expect(datetimeUtility.timeFor(date)).toBe(s__('Timeago|Past due'));
}); });
it('returns remaining time when in the future', () => { it('returns localized remaining time when in the future', () => {
const date = new Date(); const date = new Date();
date.setFullYear(date.getFullYear() + 1); date.setFullYear(date.getFullYear() + 1);
...@@ -17,51 +18,51 @@ describe('Date time utils', () => { ...@@ -17,51 +18,51 @@ describe('Date time utils', () => {
// short of a full year, timeFor will return '11 months remaining' // short of a full year, timeFor will return '11 months remaining'
date.setDate(date.getDate() + 1); date.setDate(date.getDate() + 1);
expect(datetimeUtility.timeFor(date)).toBe('1 year remaining'); expect(datetimeUtility.timeFor(date)).toBe(s__('Timeago|1 year remaining'));
}); });
}); });
describe('get day name', () => { describe('get localized day name', () => {
it('should return Sunday', () => { it('should return Sunday', () => {
const day = datetimeUtility.getDayName(new Date('07/17/2016')); const day = datetimeUtility.getDayName(new Date('07/17/2016'));
expect(day).toBe('Sunday'); expect(day).toBe(__('Sunday'));
}); });
it('should return Monday', () => { it('should return Monday', () => {
const day = datetimeUtility.getDayName(new Date('07/18/2016')); const day = datetimeUtility.getDayName(new Date('07/18/2016'));
expect(day).toBe('Monday'); expect(day).toBe(__('Monday'));
}); });
it('should return Tuesday', () => { it('should return Tuesday', () => {
const day = datetimeUtility.getDayName(new Date('07/19/2016')); const day = datetimeUtility.getDayName(new Date('07/19/2016'));
expect(day).toBe('Tuesday'); expect(day).toBe(__('Tuesday'));
}); });
it('should return Wednesday', () => { it('should return Wednesday', () => {
const day = datetimeUtility.getDayName(new Date('07/20/2016')); const day = datetimeUtility.getDayName(new Date('07/20/2016'));
expect(day).toBe('Wednesday'); expect(day).toBe(__('Wednesday'));
}); });
it('should return Thursday', () => { it('should return Thursday', () => {
const day = datetimeUtility.getDayName(new Date('07/21/2016')); const day = datetimeUtility.getDayName(new Date('07/21/2016'));
expect(day).toBe('Thursday'); expect(day).toBe(__('Thursday'));
}); });
it('should return Friday', () => { it('should return Friday', () => {
const day = datetimeUtility.getDayName(new Date('07/22/2016')); const day = datetimeUtility.getDayName(new Date('07/22/2016'));
expect(day).toBe('Friday'); expect(day).toBe(__('Friday'));
}); });
it('should return Saturday', () => { it('should return Saturday', () => {
const day = datetimeUtility.getDayName(new Date('07/23/2016')); const day = datetimeUtility.getDayName(new Date('07/23/2016'));
expect(day).toBe('Saturday'); expect(day).toBe(__('Saturday'));
}); });
}); });
...@@ -114,10 +115,10 @@ describe('Date time utils', () => { ...@@ -114,10 +115,10 @@ describe('Date time utils', () => {
describe('timeIntervalInWords', () => { describe('timeIntervalInWords', () => {
it('should return string with number of minutes and seconds', () => { it('should return string with number of minutes and seconds', () => {
expect(datetimeUtility.timeIntervalInWords(9.54)).toEqual('9 seconds'); expect(datetimeUtility.timeIntervalInWords(9.54)).toEqual(s__('Timeago|9 seconds'));
expect(datetimeUtility.timeIntervalInWords(1)).toEqual('1 second'); expect(datetimeUtility.timeIntervalInWords(1)).toEqual(s__('Timeago|1 second'));
expect(datetimeUtility.timeIntervalInWords(200)).toEqual('3 minutes 20 seconds'); expect(datetimeUtility.timeIntervalInWords(200)).toEqual(s__('Timeago|3 minutes 20 seconds'));
expect(datetimeUtility.timeIntervalInWords(6008)).toEqual('100 minutes 8 seconds'); expect(datetimeUtility.timeIntervalInWords(6008)).toEqual(s__('Timeago|100 minutes 8 seconds'));
}); });
}); });
...@@ -125,15 +126,15 @@ describe('dateInWords', () => { ...@@ -125,15 +126,15 @@ describe('dateInWords', () => {
const date = new Date('07/01/2016'); const date = new Date('07/01/2016');
it('should return date in words', () => { it('should return date in words', () => {
expect(datetimeUtility.dateInWords(date)).toEqual('July 1, 2016'); expect(datetimeUtility.dateInWords(date)).toEqual(s__('July 1, 2016'));
}); });
it('should return abbreviated month name', () => { it('should return abbreviated month name', () => {
expect(datetimeUtility.dateInWords(date, true)).toEqual('Jul 1, 2016'); expect(datetimeUtility.dateInWords(date, true)).toEqual(s__('Jul 1, 2016'));
}); });
it('should return date in words without year', () => { it('should return date in words without year', () => {
expect(datetimeUtility.dateInWords(date, true, true)).toEqual('Jul 1'); expect(datetimeUtility.dateInWords(date, true, true)).toEqual(s__('Jul 1'));
}); });
}); });
...@@ -141,11 +142,11 @@ describe('monthInWords', () => { ...@@ -141,11 +142,11 @@ describe('monthInWords', () => {
const date = new Date('2017-01-20'); const date = new Date('2017-01-20');
it('returns month name from provided date', () => { it('returns month name from provided date', () => {
expect(datetimeUtility.monthInWords(date)).toBe('January'); expect(datetimeUtility.monthInWords(date)).toBe(s__('January'));
}); });
it('returns abbreviated month name from provided date', () => { it('returns abbreviated month name from provided date', () => {
expect(datetimeUtility.monthInWords(date, true)).toBe('Jan'); expect(datetimeUtility.monthInWords(date, true)).toBe(s__('Jan'));
}); });
}); });
......
...@@ -68,8 +68,22 @@ describe Resolvers::IssuesResolver do ...@@ -68,8 +68,22 @@ describe Resolvers::IssuesResolver do
end end
end end
it 'searches issues' do context 'when searching issues' do
expect(resolve_issues(search: 'foo')).to contain_exactly(issue2) it 'returns correct issues' do
expect(resolve_issues(search: 'foo')).to contain_exactly(issue2)
end
it 'uses project search optimization' do
expected_arguments = {
search: 'foo',
attempt_project_search_optimizations: true,
iids: [],
project_id: project.id
}
expect(IssuesFinder).to receive(:new).with(anything, expected_arguments).and_call_original
resolve_issues(search: 'foo')
end
end end
describe 'sorting' do describe 'sorting' do
......
import 'timeago.js'; import { format } from 'timeago.js';
import Vue from 'vue'; import Vue from 'vue';
import environmentItemComp from '~/environments/components/environment_item.vue'; import environmentItemComp from '~/environments/components/environment_item.vue';
...@@ -139,8 +139,7 @@ describe('Environment item', () => { ...@@ -139,8 +139,7 @@ describe('Environment item', () => {
}); });
it('should render last deployment date', () => { it('should render last deployment date', () => {
const timeagoInstance = new timeago(); // eslint-disable-line const formatedDate = format(environment.last_deployment.deployed_at);
const formatedDate = timeagoInstance.format(environment.last_deployment.deployed_at);
expect( expect(
component.$el.querySelector('.environment-created-date-timeago').textContent, component.$el.querySelector('.environment-created-date-timeago').textContent,
......
...@@ -147,6 +147,10 @@ describe Gitlab::Ci::Ansi2json::Style do ...@@ -147,6 +147,10 @@ describe Gitlab::Ci::Ansi2json::Style do
[%w[1], %w[0], '', 'resets style from format bold'], [%w[1], %w[0], '', 'resets style from format bold'],
[%w[1 3], %w[0], '', 'resets style from format bold and italic'], [%w[1 3], %w[0], '', 'resets style from format bold and italic'],
[%w[1 3 term-fg-l-red term-bg-yellow], %w[0], '', 'resets all formats and colors'], [%w[1 3 term-fg-l-red term-bg-yellow], %w[0], '', 'resets all formats and colors'],
# default foreground
[%w[31 42], %w[39], 'term-bg-green', 'set foreground from red to default leaving background unchanged'],
# default background
[%w[31 42], %w[49], 'term-fg-red', 'set background from green to default leaving foreground unchanged'],
# misc # misc
[[], %w[1 30 42 3], 'term-fg-l-black term-bg-green term-bold term-italic', 'adds fg color, bg color and formats from no style'], [[], %w[1 30 42 3], 'term-fg-l-black term-bg-green term-bold term-italic', 'adds fg color, bg color and formats from no style'],
[%w[3 31], %w[23 1 43], 'term-fg-l-red term-bg-yellow term-bold', 'replaces format italic with bold and adds a yellow background'] [%w[3 31], %w[23 1 43], 'term-fg-l-red term-bg-yellow term-bold', 'replaces format italic with bold and adds a yellow background']
......
...@@ -93,7 +93,7 @@ describe Gitlab::Ci::Config::Entry::Job do ...@@ -93,7 +93,7 @@ describe Gitlab::Ci::Config::Entry::Job do
context 'when delayed job' do context 'when delayed job' do
context 'when start_in is specified' do context 'when start_in is specified' do
let(:config) { { script: 'echo', when: 'delayed', start_in: '1 day' } } let(:config) { { script: 'echo', when: 'delayed', start_in: '1 week' } }
it { expect(entry).to be_valid } it { expect(entry).to be_valid }
end end
...@@ -232,11 +232,9 @@ describe Gitlab::Ci::Config::Entry::Job do ...@@ -232,11 +232,9 @@ describe Gitlab::Ci::Config::Entry::Job do
context 'when delayed job' do context 'when delayed job' do
context 'when start_in is specified' do context 'when start_in is specified' do
let(:config) { { script: 'echo', when: 'delayed', start_in: '1 day' } } let(:config) { { script: 'echo', when: 'delayed', start_in: '1 week' } }
it 'returns error about invalid type' do it { expect(entry).to be_valid }
expect(entry).to be_valid
end
end end
context 'when start_in is empty' do context 'when start_in is empty' do
...@@ -257,8 +255,8 @@ describe Gitlab::Ci::Config::Entry::Job do ...@@ -257,8 +255,8 @@ describe Gitlab::Ci::Config::Entry::Job do
end end
end end
context 'when start_in is longer than one day' do context 'when start_in is longer than one week' do
let(:config) { { when: 'delayed', start_in: '2 days' } } let(:config) { { when: 'delayed', start_in: '8 days' } }
it 'returns error about exceeding the limit' do it 'returns error about exceeding the limit' do
expect(entry).not_to be_valid expect(entry).not_to be_valid
......
...@@ -5,31 +5,177 @@ require 'spec_helper' ...@@ -5,31 +5,177 @@ require 'spec_helper'
describe ::Gitlab::Ci::Config::Entry::Need do describe ::Gitlab::Ci::Config::Entry::Need do
subject(:need) { described_class.new(config) } subject(:need) { described_class.new(config) }
context 'when job is specified' do shared_examples 'job type' do
let(:config) { 'job_name' } describe '#type' do
subject(:need_type) { need.type }
describe '#valid?' do it { is_expected.to eq(:job) }
it { is_expected.to be_valid } end
end
context 'with simple config' do
context 'when job is specified' do
let(:config) { 'job_name' }
describe '#valid?' do
it { is_expected.to be_valid }
end
describe '#value' do
it 'returns job needs configuration' do
expect(need.value).to eq(name: 'job_name', artifacts: true)
end
end
it_behaves_like 'job type'
end
context 'when need is empty' do
let(:config) { '' }
describe '#valid?' do
it { is_expected.not_to be_valid }
end
describe '#errors' do
it 'is returns an error about an empty config' do
expect(need.errors)
.to contain_exactly("job string config can't be blank")
end
end
it_behaves_like 'job type'
end end
end
context 'with complex config' do
context 'with job name and artifacts true' do
let(:config) { { job: 'job_name', artifacts: true } }
describe '#valid?' do
it { is_expected.to be_valid }
end
describe '#value' do
it 'returns job needs configuration' do
expect(need.value).to eq(name: 'job_name', artifacts: true)
end
end
it_behaves_like 'job type'
end
context 'with job name and artifacts false' do
let(:config) { { job: 'job_name', artifacts: false } }
describe '#valid?' do
it { is_expected.to be_valid }
end
describe '#value' do
it 'returns job needs configuration' do
expect(need.value).to eq(name: 'job_name', artifacts: false)
end
end
it_behaves_like 'job type'
end
context 'with job name and artifacts nil' do
let(:config) { { job: 'job_name', artifacts: nil } }
describe '#value' do describe '#valid?' do
it 'returns job needs configuration' do it { is_expected.to be_valid }
expect(need.value).to eq(name: 'job_name')
end end
describe '#value' do
it 'returns job needs configuration' do
expect(need.value).to eq(name: 'job_name', artifacts: true)
end
end
it_behaves_like 'job type'
end
context 'without artifacts key' do
let(:config) { { job: 'job_name' } }
describe '#valid?' do
it { is_expected.to be_valid }
end
describe '#value' do
it 'returns job needs configuration' do
expect(need.value).to eq(name: 'job_name', artifacts: true)
end
end
it_behaves_like 'job type'
end
context 'when job name is empty' do
let(:config) { { job: '', artifacts: true } }
describe '#valid?' do
it { is_expected.not_to be_valid }
end
describe '#errors' do
it 'is returns an error about an empty config' do
expect(need.errors)
.to contain_exactly("job hash job can't be blank")
end
end
it_behaves_like 'job type'
end
context 'when job name is not a string' do
let(:config) { { job: :job_name, artifacts: false } }
describe '#valid?' do
it { is_expected.not_to be_valid }
end
describe '#errors' do
it 'is returns an error about job type' do
expect(need.errors)
.to contain_exactly('job hash job should be a string')
end
end
it_behaves_like 'job type'
end
context 'when job has unknown keys' do
let(:config) { { job: 'job_name', artifacts: false, some: :key } }
describe '#valid?' do
it { is_expected.not_to be_valid }
end
describe '#errors' do
it 'is returns an error about job type' do
expect(need.errors)
.to contain_exactly('job hash config contains unknown keys: some')
end
end
it_behaves_like 'job type'
end end
end end
context 'when need is empty' do context 'when need config is not a string or a hash' do
let(:config) { '' } let(:config) { :job_name }
describe '#valid?' do describe '#valid?' do
it { is_expected.not_to be_valid } it { is_expected.not_to be_valid }
end end
describe '#errors' do describe '#errors' do
it 'is returns an error about an empty config' do it 'is returns an error about job type' do
expect(need.errors) expect(need.errors)
.to contain_exactly("job config can't be blank") .to contain_exactly('unknown strategy has an unsupported type')
end end
end end
end end
......
...@@ -51,9 +51,34 @@ describe ::Gitlab::Ci::Config::Entry::Needs do ...@@ -51,9 +51,34 @@ describe ::Gitlab::Ci::Config::Entry::Needs do
end end
end end
end end
context 'when wrong needs type is used' do
let(:config) { [{ job: 'job_name', artifacts: true, some: :key }] }
describe '#valid?' do
it { is_expected.not_to be_valid }
end
describe '#errors' do
it 'returns error about incorrect type' do
expect(needs.errors).to contain_exactly(
'need config contains unknown keys: some')
end
end
end
end end
describe '.compose!' do describe '.compose!' do
shared_examples 'entry with descendant nodes' do
describe '#descendants' do
it 'creates valid descendant nodes' do
expect(needs.descendants.count).to eq 2
expect(needs.descendants)
.to all(be_an_instance_of(::Gitlab::Ci::Config::Entry::Need))
end
end
end
context 'when valid job entries composed' do context 'when valid job entries composed' do
let(:config) { %w[first_job_name second_job_name] } let(:config) { %w[first_job_name second_job_name] }
...@@ -65,18 +90,80 @@ describe ::Gitlab::Ci::Config::Entry::Needs do ...@@ -65,18 +90,80 @@ describe ::Gitlab::Ci::Config::Entry::Needs do
it 'returns key value' do it 'returns key value' do
expect(needs.value).to eq( expect(needs.value).to eq(
job: [ job: [
{ name: 'first_job_name' }, { name: 'first_job_name', artifacts: true },
{ name: 'second_job_name' } { name: 'second_job_name', artifacts: true }
] ]
) )
end end
end end
describe '#descendants' do it_behaves_like 'entry with descendant nodes'
it 'creates valid descendant nodes' do end
expect(needs.descendants.count).to eq 2
expect(needs.descendants) context 'with complex job entries composed' do
.to all(be_an_instance_of(::Gitlab::Ci::Config::Entry::Need)) let(:config) do
[
{ job: 'first_job_name', artifacts: true },
{ job: 'second_job_name', artifacts: false }
]
end
before do
needs.compose!
end
describe '#value' do
it 'returns key value' do
expect(needs.value).to eq(
job: [
{ name: 'first_job_name', artifacts: true },
{ name: 'second_job_name', artifacts: false }
]
)
end
end
it_behaves_like 'entry with descendant nodes'
end
context 'with mixed job entries composed' do
let(:config) do
[
'first_job_name',
{ job: 'second_job_name', artifacts: false }
]
end
before do
needs.compose!
end
describe '#value' do
it 'returns key value' do
expect(needs.value).to eq(
job: [
{ name: 'first_job_name', artifacts: true },
{ name: 'second_job_name', artifacts: false }
]
)
end
end
it_behaves_like 'entry with descendant nodes'
end
context 'with empty config' do
let(:config) do
[]
end
before do
needs.compose!
end
describe '#value' do
it 'returns empty value' do
expect(needs.value).to eq({})
end end
end end
end end
......
...@@ -105,7 +105,7 @@ describe Gitlab::Ci::Config::Normalizer do ...@@ -105,7 +105,7 @@ describe Gitlab::Ci::Config::Normalizer do
context 'for needs' do context 'for needs' do
let(:expanded_job_attributes) do let(:expanded_job_attributes) do
expanded_job_names.map do |job_name| expanded_job_names.map do |job_name|
{ name: job_name } { name: job_name, extra: :key }
end end
end end
...@@ -117,7 +117,7 @@ describe Gitlab::Ci::Config::Normalizer do ...@@ -117,7 +117,7 @@ describe Gitlab::Ci::Config::Normalizer do
script: 'echo 1', script: 'echo 1',
needs: { needs: {
job: [ job: [
{ name: job_name.to_s } { name: job_name.to_s, extra: :key }
] ]
} }
} }
...@@ -140,8 +140,8 @@ describe Gitlab::Ci::Config::Normalizer do ...@@ -140,8 +140,8 @@ describe Gitlab::Ci::Config::Normalizer do
script: 'echo 1', script: 'echo 1',
needs: { needs: {
job: [ job: [
{ name: job_name.to_s }, { name: job_name.to_s, extra: :key },
{ name: "other_job" } { name: "other_job", extra: :key }
] ]
} }
} }
...@@ -153,7 +153,7 @@ describe Gitlab::Ci::Config::Normalizer do ...@@ -153,7 +153,7 @@ describe Gitlab::Ci::Config::Normalizer do
end end
it "includes the regular job in dependencies" do it "includes the regular job in dependencies" do
expect(subject.dig(:final_job, :needs, :job)).to include(name: 'other_job') expect(subject.dig(:final_job, :needs, :job)).to include(name: 'other_job', extra: :key)
end end
end end
end end
......
...@@ -1525,8 +1525,48 @@ module Gitlab ...@@ -1525,8 +1525,48 @@ module Gitlab
name: "test1", name: "test1",
options: { script: ["test"] }, options: { script: ["test"] },
needs_attributes: [ needs_attributes: [
{ name: "build1" }, { name: "build1", artifacts: true },
{ name: "build2" } { name: "build2", artifacts: true }
],
when: "on_success",
allow_failure: false,
yaml_variables: []
)
end
end
context 'needs two builds' do
let(:needs) do
[
{ job: 'parallel', artifacts: false },
{ job: 'build1', artifacts: true },
'build2'
]
end
it "does create jobs with valid specification" do
expect(subject.builds.size).to eq(7)
expect(subject.builds[0]).to eq(
stage: "build",
stage_idx: 1,
name: "build1",
options: {
script: ["test"]
},
when: "on_success",
allow_failure: false,
yaml_variables: []
)
expect(subject.builds[4]).to eq(
stage: "test",
stage_idx: 2,
name: "test1",
options: { script: ["test"] },
needs_attributes: [
{ name: "parallel 1/2", artifacts: false },
{ name: "parallel 2/2", artifacts: false },
{ name: "build1", artifacts: true },
{ name: "build2", artifacts: true }
], ],
when: "on_success", when: "on_success",
allow_failure: false, allow_failure: false,
...@@ -1546,8 +1586,37 @@ module Gitlab ...@@ -1546,8 +1586,37 @@ module Gitlab
name: "test1", name: "test1",
options: { script: ["test"] }, options: { script: ["test"] },
needs_attributes: [ needs_attributes: [
{ name: "parallel 1/2" }, { name: "parallel 1/2", artifacts: true },
{ name: "parallel 2/2" } { name: "parallel 2/2", artifacts: true }
],
when: "on_success",
allow_failure: false,
yaml_variables: []
)
end
end
context 'needs dependencies artifacts' do
let(:needs) do
[
"build1",
{ job: "build2" },
{ job: "parallel", artifacts: true }
]
end
it "does create jobs with valid specification" do
expect(subject.builds.size).to eq(7)
expect(subject.builds[4]).to eq(
stage: "test",
stage_idx: 2,
name: "test1",
options: { script: ["test"] },
needs_attributes: [
{ name: "build1", artifacts: true },
{ name: "build2", artifacts: true },
{ name: "parallel 1/2", artifacts: true },
{ name: "parallel 2/2", artifacts: true }
], ],
when: "on_success", when: "on_success",
allow_failure: false, allow_failure: false,
......
...@@ -10,4 +10,11 @@ describe Ci::BuildNeed, model: true do ...@@ -10,4 +10,11 @@ describe Ci::BuildNeed, model: true do
it { is_expected.to validate_presence_of(:build) } it { is_expected.to validate_presence_of(:build) }
it { is_expected.to validate_presence_of(:name) } it { is_expected.to validate_presence_of(:name) }
it { is_expected.to validate_length_of(:name).is_at_most(128) } it { is_expected.to validate_length_of(:name).is_at_most(128) }
describe '.artifacts' do
let_it_be(:with_artifacts) { create(:ci_build_need, artifacts: true) }
let_it_be(:without_artifacts) { create(:ci_build_need, artifacts: false) }
it { expect(described_class.artifacts).to contain_exactly(with_artifacts) }
end
end end
...@@ -741,20 +741,26 @@ describe Ci::Build do ...@@ -741,20 +741,26 @@ describe Ci::Build do
before do before do
needs.to_a.each do |need| needs.to_a.each do |need|
create(:ci_build_need, build: final, name: need) create(:ci_build_need, build: final, **need)
end end
end end
subject { final.dependencies } subject { final.dependencies }
context 'when depedencies are defined' do context 'when dependencies are defined' do
let(:dependencies) { %w(rspec staging) } let(:dependencies) { %w(rspec staging) }
it { is_expected.to contain_exactly(rspec_test, staging) } it { is_expected.to contain_exactly(rspec_test, staging) }
end end
context 'when needs are defined' do context 'when needs are defined' do
let(:needs) { %w(build rspec staging) } let(:needs) do
[
{ name: 'build', artifacts: true },
{ name: 'rspec', artifacts: true },
{ name: 'staging', artifacts: true }
]
end
it { is_expected.to contain_exactly(build, rspec_test, staging) } it { is_expected.to contain_exactly(build, rspec_test, staging) }
...@@ -767,13 +773,44 @@ describe Ci::Build do ...@@ -767,13 +773,44 @@ describe Ci::Build do
end end
end end
context 'when need artifacts are defined' do
let(:needs) do
[
{ name: 'build', artifacts: true },
{ name: 'rspec', artifacts: false },
{ name: 'staging', artifacts: true }
]
end
it { is_expected.to contain_exactly(build, staging) }
end
context 'when needs and dependencies are defined' do context 'when needs and dependencies are defined' do
let(:dependencies) { %w(rspec staging) } let(:dependencies) { %w(rspec staging) }
let(:needs) { %w(build rspec staging) } let(:needs) do
[
{ name: 'build', artifacts: true },
{ name: 'rspec', artifacts: true },
{ name: 'staging', artifacts: true }
]
end
it { is_expected.to contain_exactly(rspec_test, staging) } it { is_expected.to contain_exactly(rspec_test, staging) }
end end
context 'when needs and dependencies contradict' do
let(:dependencies) { %w(rspec staging) }
let(:needs) do
[
{ name: 'build', artifacts: true },
{ name: 'rspec', artifacts: false },
{ name: 'staging', artifacts: true }
]
end
it { is_expected.to contain_exactly(staging) }
end
context 'when nor dependencies or needs are defined' do context 'when nor dependencies or needs are defined' do
it { is_expected.to contain_exactly(build, rspec_test, rubocop_test, staging) } it { is_expected.to contain_exactly(build, rspec_test, rubocop_test, staging) }
end end
......
# frozen_string_literal: true
require 'spec_helper'
describe Ci::CreatePipelineService do
context 'needs' do
let_it_be(:user) { create(:admin) }
let_it_be(:project) { create(:project, :repository, creator: user) }
let(:ref) { 'refs/heads/master' }
let(:source) { :push }
let(:service) { described_class.new(project, user, { ref: ref }) }
let(:pipeline) { service.execute(source) }
before do
stub_ci_pipeline_yaml_file(config)
end
context 'with a valid config' do
let(:config) do
<<~YAML
build_a:
stage: build
script:
- make
artifacts:
paths:
- binaries/
build_b:
stage: build
script:
- make
artifacts:
paths:
- other_binaries/
build_c:
stage: build
script:
- make
build_d:
stage: build
script:
- make
parallel: 3
test_a:
stage: test
script:
- ls
needs:
- build_a
- job: build_b
artifacts: true
- job: build_c
artifacts: false
dependencies:
- build_a
test_b:
stage: test
script:
- ls
parallel: 2
needs:
- build_a
- job: build_b
artifacts: true
- job: build_d
artifacts: false
test_c:
stage: test
script:
- ls
needs:
- build_a
- job: build_b
- job: build_c
artifacts: true
YAML
end
let(:test_a_build) { pipeline.builds.find_by!(name: 'test_a') }
it 'creates a pipeline with builds' do
expected_builds = [
'build_a', 'build_b', 'build_c', 'build_d 1/3', 'build_d 2/3',
'build_d 3/3', 'test_a', 'test_b 1/2', 'test_b 2/2', 'test_c'
]
expect(pipeline).to be_persisted
expect(pipeline.builds.pluck(:name)).to contain_exactly(*expected_builds)
end
it 'saves needs' do
expect(test_a_build.needs.map(&:attributes))
.to contain_exactly(
a_hash_including('name' => 'build_a', 'artifacts' => true),
a_hash_including('name' => 'build_b', 'artifacts' => true),
a_hash_including('name' => 'build_c', 'artifacts' => false)
)
end
it 'saves dependencies' do
expect(test_a_build.options)
.to match(a_hash_including('dependencies' => ['build_a']))
end
it 'artifacts default to true' do
test_job = pipeline.builds.find_by!(name: 'test_c')
expect(test_job.needs.map(&:attributes))
.to contain_exactly(
a_hash_including('name' => 'build_a', 'artifacts' => true),
a_hash_including('name' => 'build_b', 'artifacts' => true),
a_hash_including('name' => 'build_c', 'artifacts' => true)
)
end
it 'saves parallel jobs' do
['1/2', '2/2'].each do |part|
test_job = pipeline.builds.find_by(name: "test_b #{part}")
expect(test_job.needs.map(&:attributes))
.to contain_exactly(
a_hash_including('name' => 'build_a', 'artifacts' => true),
a_hash_including('name' => 'build_b', 'artifacts' => true),
a_hash_including('name' => 'build_d 1/3', 'artifacts' => false),
a_hash_including('name' => 'build_d 2/3', 'artifacts' => false),
a_hash_including('name' => 'build_d 3/3', 'artifacts' => false)
)
end
end
end
context 'with an invalid config' do
let(:config) do
<<~YAML
build_a:
stage: build
script:
- make
artifacts:
paths:
- binaries/
build_b:
stage: build
script:
- make
artifacts:
paths:
- other_binaries/
test_a:
stage: test
script:
- ls
needs:
- build_a
- job: build_b
artifacts: string
YAML
end
it { expect(pipeline).to be_persisted }
it { expect(pipeline.builds.any?).to be_falsey }
it 'assigns an error to the pipeline' do
expect(pipeline.yaml_errors)
.to eq('jobs:test_a:needs:need artifacts should be a boolean value')
end
end
end
end
...@@ -1047,11 +1047,6 @@ ...@@ -1047,11 +1047,6 @@
"@types/istanbul-lib-coverage" "*" "@types/istanbul-lib-coverage" "*"
"@types/istanbul-lib-report" "*" "@types/istanbul-lib-report" "*"
"@types/jquery@^2.0.40":
version "2.0.48"
resolved "https://registry.yarnpkg.com/@types/jquery/-/jquery-2.0.48.tgz#3e90d8cde2d29015e5583017f7830cb3975b2eef"
integrity sha512-nNLzUrVjaRV/Ds1eHZLYTd7IZxs38cwwLSaqMJj8OTXY8xNUbxSK69bi9cMLvQ7dm/IBeQ1wHwQ0S1uYa0rd2w==
"@types/minimatch@*": "@types/minimatch@*":
version "3.0.3" version "3.0.3"
resolved "https://registry.yarnpkg.com/@types/minimatch/-/minimatch-3.0.3.tgz#3dca0e3f33b200fc7d1139c0cd96c1268cadfd9d" resolved "https://registry.yarnpkg.com/@types/minimatch/-/minimatch-3.0.3.tgz#3dca0e3f33b200fc7d1139c0cd96c1268cadfd9d"
...@@ -10877,12 +10872,10 @@ thunky@^0.1.0: ...@@ -10877,12 +10872,10 @@ thunky@^0.1.0:
resolved "https://registry.yarnpkg.com/thunky/-/thunky-0.1.0.tgz#bf30146824e2b6e67b0f2d7a4ac8beb26908684e" resolved "https://registry.yarnpkg.com/thunky/-/thunky-0.1.0.tgz#bf30146824e2b6e67b0f2d7a4ac8beb26908684e"
integrity sha1-vzAUaCTituZ7Dy16Ssi+smkIaE4= integrity sha1-vzAUaCTituZ7Dy16Ssi+smkIaE4=
timeago.js@^3.0.2: timeago.js@^4.0.1:
version "3.0.2" version "4.0.1"
resolved "https://registry.yarnpkg.com/timeago.js/-/timeago.js-3.0.2.tgz#32a67e7c0d887ea42ca588d3aae26f77de5e76cc" resolved "https://registry.yarnpkg.com/timeago.js/-/timeago.js-4.0.1.tgz#4be4aa19565ceaeb0da31fe14e01ce6ca4742da6"
integrity sha1-MqZ+fA2IfqQspYjTquJvd95edsw= integrity sha512-ePzZuMoJqUc44hJbUYtY1qtzU7IammxooDCcFKogLkS5Nj+iCabR0ZlmNOFX8Dm1r5EpvR5q/PotOJli/mEPew==
dependencies:
"@types/jquery" "^2.0.40"
timed-out@^4.0.0: timed-out@^4.0.0:
version "4.0.1" version "4.0.1"
......
Markdown is supported
0% .
You are about to add 0 people to the discussion. Proceed with caution.
先完成此消息的编辑!
想要评论请 注册