Add latest changes from gitlab-org/gitlab@master
This commit is contained in:
parent
a4484fd22d
commit
a98649b710
|
@ -94,11 +94,11 @@ setup-test-env:
|
|||
|
||||
rspec unit pg9:
|
||||
extends: .rspec-base-pg9
|
||||
parallel: 20
|
||||
parallel: 24
|
||||
|
||||
rspec unit pg9-foss:
|
||||
extends: .rspec-base-pg9-foss
|
||||
parallel: 20
|
||||
parallel: 24
|
||||
|
||||
rspec integration pg9:
|
||||
extends: .rspec-base-pg9
|
||||
|
|
|
@ -1,5 +1,17 @@
|
|||
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
|
||||
|
||||
### Security (5 changes)
|
||||
|
@ -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
|
||||
|
||||
|
||||
## 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
|
||||
|
||||
### Fixed (2 changes)
|
||||
|
|
33
CHANGELOG.md
33
CHANGELOG.md
|
@ -4,16 +4,19 @@ entry.
|
|||
|
||||
## 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.
|
||||
- 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.
|
||||
- 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.
|
||||
- Add authorization to using filter vulnerable in Dependency List.
|
||||
|
||||
|
||||
## 12.5.0
|
||||
|
@ -367,21 +370,6 @@ entry.
|
|||
- 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
|
||||
|
||||
### Fixed (2 changes)
|
||||
|
@ -752,17 +740,20 @@ entry.
|
|||
|
||||
## 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.
|
||||
- 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.
|
||||
- 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.
|
||||
- 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.
|
||||
- Add authorization to using filter vulnerable in Dependency List.
|
||||
|
||||
|
||||
## 12.3.4
|
||||
|
|
|
@ -1,7 +1,7 @@
|
|||
<script>
|
||||
/* eslint-disable @gitlab/vue-i18n/no-bare-strings */
|
||||
import { __, sprintf } from '~/locale';
|
||||
import Timeago from 'timeago.js';
|
||||
import { format } from 'timeago.js';
|
||||
import _ from 'underscore';
|
||||
import { GlTooltipDirective } from '@gitlab/ui';
|
||||
import UserAvatarLink from '~/vue_shared/components/user_avatar/user_avatar_link.vue';
|
||||
|
@ -23,7 +23,6 @@ import { convertObjectPropsToCamelCase } from '~/lib/utils/common_utils';
|
|||
*
|
||||
* Renders a table row for each environment.
|
||||
*/
|
||||
const timeagoInstance = new Timeago();
|
||||
|
||||
export default {
|
||||
components: {
|
||||
|
@ -123,7 +122,7 @@ export default {
|
|||
*/
|
||||
deployedDate() {
|
||||
if (this.canShowDate) {
|
||||
return timeagoInstance.format(this.model.last_deployment.deployed_at);
|
||||
return format(this.model.last_deployment.deployed_at);
|
||||
}
|
||||
return '';
|
||||
},
|
||||
|
|
|
@ -1,6 +1,6 @@
|
|||
import $ from 'jquery';
|
||||
import _ from 'underscore';
|
||||
import timeago from 'timeago.js';
|
||||
import * as timeago from 'timeago.js';
|
||||
import dateFormat from 'dateformat';
|
||||
import { languageCode, s__, __, n__ } from '../../locale';
|
||||
|
||||
|
@ -92,90 +92,80 @@ export const formatDate = (datetime, format = 'mmm d, yyyy h:MMtt Z') => {
|
|||
*/
|
||||
const timeagoLanguageCode = languageCode().replace(/-/g, '_');
|
||||
|
||||
let timeagoInstance;
|
||||
|
||||
/**
|
||||
* Sets a timeago Instance
|
||||
* Registers timeago locales
|
||||
*/
|
||||
export const getTimeago = () => {
|
||||
if (!timeagoInstance) {
|
||||
const memoizedLocaleRemaining = () => {
|
||||
const cache = [];
|
||||
const memoizedLocaleRemaining = () => {
|
||||
const cache = [];
|
||||
|
||||
const timeAgoLocaleRemaining = [
|
||||
() => [s__('Timeago|just now'), s__('Timeago|right now')],
|
||||
() => [s__('Timeago|just now'), s__('Timeago|%s seconds remaining')],
|
||||
() => [s__('Timeago|1 minute ago'), s__('Timeago|1 minute remaining')],
|
||||
() => [s__('Timeago|%s minutes ago'), s__('Timeago|%s minutes remaining')],
|
||||
() => [s__('Timeago|1 hour ago'), s__('Timeago|1 hour remaining')],
|
||||
() => [s__('Timeago|%s hours ago'), s__('Timeago|%s hours remaining')],
|
||||
() => [s__('Timeago|1 day ago'), s__('Timeago|1 day remaining')],
|
||||
() => [s__('Timeago|%s days ago'), s__('Timeago|%s days remaining')],
|
||||
() => [s__('Timeago|1 week ago'), s__('Timeago|1 week remaining')],
|
||||
() => [s__('Timeago|%s weeks ago'), s__('Timeago|%s weeks remaining')],
|
||||
() => [s__('Timeago|1 month ago'), s__('Timeago|1 month remaining')],
|
||||
() => [s__('Timeago|%s months ago'), s__('Timeago|%s months remaining')],
|
||||
() => [s__('Timeago|1 year ago'), s__('Timeago|1 year remaining')],
|
||||
() => [s__('Timeago|%s years ago'), s__('Timeago|%s years remaining')],
|
||||
];
|
||||
const timeAgoLocaleRemaining = [
|
||||
() => [s__('Timeago|just now'), s__('Timeago|right now')],
|
||||
() => [s__('Timeago|just now'), s__('Timeago|%s seconds remaining')],
|
||||
() => [s__('Timeago|1 minute ago'), s__('Timeago|1 minute remaining')],
|
||||
() => [s__('Timeago|%s minutes ago'), s__('Timeago|%s minutes remaining')],
|
||||
() => [s__('Timeago|1 hour ago'), s__('Timeago|1 hour remaining')],
|
||||
() => [s__('Timeago|%s hours ago'), s__('Timeago|%s hours remaining')],
|
||||
() => [s__('Timeago|1 day ago'), s__('Timeago|1 day remaining')],
|
||||
() => [s__('Timeago|%s days ago'), s__('Timeago|%s days remaining')],
|
||||
() => [s__('Timeago|1 week ago'), s__('Timeago|1 week remaining')],
|
||||
() => [s__('Timeago|%s weeks ago'), s__('Timeago|%s weeks remaining')],
|
||||
() => [s__('Timeago|1 month ago'), s__('Timeago|1 month remaining')],
|
||||
() => [s__('Timeago|%s months ago'), s__('Timeago|%s months 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 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;
|
||||
return (number, index) => {
|
||||
if (cache[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());
|
||||
|
||||
export const getTimeago = () => timeago;
|
||||
|
||||
/**
|
||||
* For the given elements, sets a tooltip with a formatted date.
|
||||
* @param {JQuery} $timeagoEls
|
||||
* @param {Boolean} setTimeago
|
||||
*/
|
||||
export const localTimeAgo = ($timeagoEls, setTimeago = true) => {
|
||||
getTimeago();
|
||||
|
||||
$timeagoEls.each((i, el) => {
|
||||
$(el).text(timeagoInstance.format($(el).attr('datetime'), timeagoLanguageCode));
|
||||
$(el).text(timeago.format($(el).attr('datetime'), timeagoLanguageCode));
|
||||
});
|
||||
|
||||
if (!setTimeago) {
|
||||
|
@ -207,9 +197,7 @@ export const timeFor = (time, expiredLabel) => {
|
|||
if (new Date(time) < new Date()) {
|
||||
return expiredLabel || s__('Timeago|Past due');
|
||||
}
|
||||
return getTimeago()
|
||||
.format(time, `${timeagoLanguageCode}-remaining`)
|
||||
.trim();
|
||||
return timeago.format(time, `${timeagoLanguageCode}-remaining`).trim();
|
||||
};
|
||||
|
||||
export const getDayDifference = (a, b) => {
|
||||
|
|
|
@ -1,4 +1,4 @@
|
|||
import Timeago from 'timeago.js';
|
||||
import { format } from 'timeago.js';
|
||||
import _ from 'underscore';
|
||||
import getStateKey from 'ee_else_ce/vue_merge_request_widget/stores/get_state_key';
|
||||
import { stateKey } from './state_maps';
|
||||
|
@ -213,9 +213,7 @@ export default class MergeRequestStore {
|
|||
return '';
|
||||
}
|
||||
|
||||
const timeagoInstance = new Timeago();
|
||||
|
||||
return timeagoInstance.format(date);
|
||||
return format(date);
|
||||
}
|
||||
|
||||
static getPreferredAutoMergeStrategy(availableAutoMergeStrategies) {
|
||||
|
|
|
@ -53,6 +53,7 @@ module Resolvers
|
|||
# https://gitlab.com/gitlab-org/gitlab-foss/issues/54520
|
||||
args[:project_id] = project.id
|
||||
args[:iids] ||= [args[:iid]].compact
|
||||
args[:attempt_project_search_optimizations] = args[:search].present?
|
||||
|
||||
IssuesFinder.new(context[:current_user], args).execute
|
||||
end
|
||||
|
|
|
@ -764,7 +764,7 @@ module Ci
|
|||
|
||||
# find all jobs that are needed
|
||||
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
|
||||
|
||||
# find all jobs that are dependent on
|
||||
|
@ -772,6 +772,8 @@ module Ci
|
|||
depended_jobs = depended_jobs.where(name: options[:dependencies])
|
||||
end
|
||||
|
||||
# if both needs and dependencies are used,
|
||||
# the end result will be an intersection between them
|
||||
depended_jobs
|
||||
end
|
||||
|
||||
|
|
|
@ -10,5 +10,6 @@ module Ci
|
|||
validates :name, presence: true, length: { maximum: 128 }
|
||||
|
||||
scope :scoped_build, -> { where('ci_builds.id=ci_build_needs.build_id') }
|
||||
scope :artifacts, -> { where(artifacts: true) }
|
||||
end
|
||||
end
|
||||
|
|
|
@ -6,9 +6,6 @@ class MilestoneRelease < ApplicationRecord
|
|||
|
||||
validate :same_project_between_milestone_and_release
|
||||
|
||||
# Keep until 2019-11-29
|
||||
self.ignored_columns += %i[id]
|
||||
|
||||
private
|
||||
|
||||
def same_project_between_milestone_and_release
|
||||
|
|
|
@ -0,0 +1,5 @@
|
|||
---
|
||||
title: Update timeago to the latest release
|
||||
merge_request: 19407
|
||||
author:
|
||||
type: other
|
|
@ -0,0 +1,5 @@
|
|||
---
|
||||
title: Create a license info rake task
|
||||
merge_request: 20501
|
||||
author: Jason Colyer
|
||||
type: added
|
|
@ -0,0 +1,5 @@
|
|||
---
|
||||
title: Control passing artifacts from CI DAG needs
|
||||
merge_request: 19943
|
||||
author:
|
||||
type: added
|
|
@ -0,0 +1,5 @@
|
|||
---
|
||||
title: Increase upper limit of start_in attribute to 1 week
|
||||
merge_request: 20323
|
||||
author: Will Layton
|
||||
type: changed
|
|
@ -0,0 +1,5 @@
|
|||
---
|
||||
title: Fix change to default foreground and backgorund colors in job log
|
||||
merge_request: 20787
|
||||
author:
|
||||
type: fixed
|
|
@ -0,0 +1,5 @@
|
|||
---
|
||||
title: Improve issues search performance on GraphQL
|
||||
merge_request: 20784
|
||||
author:
|
||||
type: performance
|
|
@ -0,0 +1,5 @@
|
|||
---
|
||||
title: Add maven file_name regex validation on incoming files
|
||||
merge_request:
|
||||
author:
|
||||
type: security
|
|
@ -0,0 +1,20 @@
|
|||
# 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
|
|||
create_table "ci_build_needs", id: :serial, force: :cascade do |t|
|
||||
t.integer "build_id", 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
|
||||
end
|
||||
|
||||
|
|
|
@ -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.
|
||||
|
||||
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`
|
||||
- `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.
|
||||
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:
|
|||
- Related to the above, stages must be explicitly defined for all jobs
|
||||
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`
|
||||
|
||||
> [Introduced][ce-7447] in GitLab 8.17.
|
||||
|
|
|
@ -94,7 +94,7 @@ module Gitlab
|
|||
|
||||
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
|
||||
|
||||
|
@ -114,8 +114,7 @@ module Gitlab
|
|||
|
||||
def on_48(stack) { bg: bg_color_256(stack) } end
|
||||
|
||||
# TODO: all the x9 never get called?
|
||||
def on_49(_) { fg: fg_color(9) } end
|
||||
def on_49(_) { bg: nil } end
|
||||
|
||||
def on_90(_) { fg: fg_color(0, 'l') } end
|
||||
|
||||
|
|
|
@ -61,9 +61,9 @@ module Gitlab
|
|||
case
|
||||
when changes[:reset]
|
||||
reset!
|
||||
when changes[:fg]
|
||||
when changes.key?(:fg)
|
||||
@fg = changes[:fg]
|
||||
when changes[:bg]
|
||||
when changes.key?(:bg)
|
||||
@bg = changes[:bg]
|
||||
when changes[:enable]
|
||||
@mask |= changes[:enable]
|
||||
|
|
|
@ -51,7 +51,7 @@ module Gitlab
|
|||
validates :rules, array_of_hashes: true
|
||||
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? }
|
||||
|
||||
validate do
|
||||
|
|
|
@ -5,9 +5,10 @@ module Gitlab
|
|||
class Config
|
||||
module Entry
|
||||
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
|
||||
|
||||
validations do
|
||||
|
@ -20,7 +21,30 @@ module Gitlab
|
|||
end
|
||||
|
||||
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
|
||||
|
||||
|
|
|
@ -44,7 +44,7 @@ module Gitlab
|
|||
|
||||
if all_job_names = parallelized_jobs[job_need_name]
|
||||
all_job_names.map do |job_name|
|
||||
{ name: job_name }
|
||||
job_need.merge(name: job_name)
|
||||
end
|
||||
else
|
||||
job_need
|
||||
|
|
|
@ -6452,6 +6452,9 @@ msgstr ""
|
|||
msgid "Enter merge request URLs"
|
||||
msgstr ""
|
||||
|
||||
msgid "Enter new AWS Secret Access Key"
|
||||
msgstr ""
|
||||
|
||||
msgid "Enter the issue description"
|
||||
msgstr ""
|
||||
|
||||
|
|
|
@ -116,7 +116,7 @@
|
|||
"three": "^0.84.0",
|
||||
"three-orbit-controls": "^82.1.0",
|
||||
"three-stl-loader": "^1.0.4",
|
||||
"timeago.js": "^3.0.2",
|
||||
"timeago.js": "^4.0.1",
|
||||
"tiptap": "^1.8.0",
|
||||
"tiptap-commands": "^1.4.0",
|
||||
"tiptap-extensions": "^1.8.0",
|
||||
|
|
|
@ -1,15 +1,16 @@
|
|||
import { __, s__ } from '~/locale';
|
||||
import * as datetimeUtility from '~/lib/utils/datetime_utility';
|
||||
|
||||
describe('Date time utils', () => {
|
||||
describe('timeFor', () => {
|
||||
it('returns `past due` when in past', () => {
|
||||
it('returns localize `past due` when in past', () => {
|
||||
const date = new Date();
|
||||
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();
|
||||
date.setFullYear(date.getFullYear() + 1);
|
||||
|
||||
|
@ -17,51 +18,51 @@ describe('Date time utils', () => {
|
|||
// short of a full year, timeFor will return '11 months remaining'
|
||||
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', () => {
|
||||
const day = datetimeUtility.getDayName(new Date('07/17/2016'));
|
||||
|
||||
expect(day).toBe('Sunday');
|
||||
expect(day).toBe(__('Sunday'));
|
||||
});
|
||||
|
||||
it('should return Monday', () => {
|
||||
const day = datetimeUtility.getDayName(new Date('07/18/2016'));
|
||||
|
||||
expect(day).toBe('Monday');
|
||||
expect(day).toBe(__('Monday'));
|
||||
});
|
||||
|
||||
it('should return Tuesday', () => {
|
||||
const day = datetimeUtility.getDayName(new Date('07/19/2016'));
|
||||
|
||||
expect(day).toBe('Tuesday');
|
||||
expect(day).toBe(__('Tuesday'));
|
||||
});
|
||||
|
||||
it('should return Wednesday', () => {
|
||||
const day = datetimeUtility.getDayName(new Date('07/20/2016'));
|
||||
|
||||
expect(day).toBe('Wednesday');
|
||||
expect(day).toBe(__('Wednesday'));
|
||||
});
|
||||
|
||||
it('should return Thursday', () => {
|
||||
const day = datetimeUtility.getDayName(new Date('07/21/2016'));
|
||||
|
||||
expect(day).toBe('Thursday');
|
||||
expect(day).toBe(__('Thursday'));
|
||||
});
|
||||
|
||||
it('should return Friday', () => {
|
||||
const day = datetimeUtility.getDayName(new Date('07/22/2016'));
|
||||
|
||||
expect(day).toBe('Friday');
|
||||
expect(day).toBe(__('Friday'));
|
||||
});
|
||||
|
||||
it('should return Saturday', () => {
|
||||
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', () => {
|
|||
|
||||
describe('timeIntervalInWords', () => {
|
||||
it('should return string with number of minutes and seconds', () => {
|
||||
expect(datetimeUtility.timeIntervalInWords(9.54)).toEqual('9 seconds');
|
||||
expect(datetimeUtility.timeIntervalInWords(1)).toEqual('1 second');
|
||||
expect(datetimeUtility.timeIntervalInWords(200)).toEqual('3 minutes 20 seconds');
|
||||
expect(datetimeUtility.timeIntervalInWords(6008)).toEqual('100 minutes 8 seconds');
|
||||
expect(datetimeUtility.timeIntervalInWords(9.54)).toEqual(s__('Timeago|9 seconds'));
|
||||
expect(datetimeUtility.timeIntervalInWords(1)).toEqual(s__('Timeago|1 second'));
|
||||
expect(datetimeUtility.timeIntervalInWords(200)).toEqual(s__('Timeago|3 minutes 20 seconds'));
|
||||
expect(datetimeUtility.timeIntervalInWords(6008)).toEqual(s__('Timeago|100 minutes 8 seconds'));
|
||||
});
|
||||
});
|
||||
|
||||
|
@ -125,15 +126,15 @@ describe('dateInWords', () => {
|
|||
const date = new Date('07/01/2016');
|
||||
|
||||
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', () => {
|
||||
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', () => {
|
||||
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', () => {
|
|||
const date = new Date('2017-01-20');
|
||||
|
||||
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', () => {
|
||||
expect(datetimeUtility.monthInWords(date, true)).toBe('Jan');
|
||||
expect(datetimeUtility.monthInWords(date, true)).toBe(s__('Jan'));
|
||||
});
|
||||
});
|
||||
|
||||
|
|
|
@ -68,8 +68,22 @@ describe Resolvers::IssuesResolver do
|
|||
end
|
||||
end
|
||||
|
||||
it 'searches issues' do
|
||||
expect(resolve_issues(search: 'foo')).to contain_exactly(issue2)
|
||||
context 'when searching issues' do
|
||||
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
|
||||
|
||||
describe 'sorting' do
|
||||
|
|
|
@ -1,4 +1,4 @@
|
|||
import 'timeago.js';
|
||||
import { format } from 'timeago.js';
|
||||
import Vue from 'vue';
|
||||
import environmentItemComp from '~/environments/components/environment_item.vue';
|
||||
|
||||
|
@ -139,8 +139,7 @@ describe('Environment item', () => {
|
|||
});
|
||||
|
||||
it('should render last deployment date', () => {
|
||||
const timeagoInstance = new timeago(); // eslint-disable-line
|
||||
const formatedDate = timeagoInstance.format(environment.last_deployment.deployed_at);
|
||||
const formatedDate = format(environment.last_deployment.deployed_at);
|
||||
|
||||
expect(
|
||||
component.$el.querySelector('.environment-created-date-timeago').textContent,
|
||||
|
|
|
@ -147,6 +147,10 @@ describe Gitlab::Ci::Ansi2json::Style do
|
|||
[%w[1], %w[0], '', 'resets style from format bold'],
|
||||
[%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'],
|
||||
# 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
|
||||
[[], %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']
|
||||
|
|
|
@ -93,7 +93,7 @@ describe Gitlab::Ci::Config::Entry::Job do
|
|||
|
||||
context 'when delayed job' 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 }
|
||||
end
|
||||
|
@ -232,11 +232,9 @@ describe Gitlab::Ci::Config::Entry::Job do
|
|||
|
||||
context 'when delayed job' 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
|
||||
expect(entry).to be_valid
|
||||
end
|
||||
it { expect(entry).to be_valid }
|
||||
end
|
||||
|
||||
context 'when start_in is empty' do
|
||||
|
@ -257,8 +255,8 @@ describe Gitlab::Ci::Config::Entry::Job do
|
|||
end
|
||||
end
|
||||
|
||||
context 'when start_in is longer than one day' do
|
||||
let(:config) { { when: 'delayed', start_in: '2 days' } }
|
||||
context 'when start_in is longer than one week' do
|
||||
let(:config) { { when: 'delayed', start_in: '8 days' } }
|
||||
|
||||
it 'returns error about exceeding the limit' do
|
||||
expect(entry).not_to be_valid
|
||||
|
|
|
@ -5,31 +5,177 @@ require 'spec_helper'
|
|||
describe ::Gitlab::Ci::Config::Entry::Need do
|
||||
subject(:need) { described_class.new(config) }
|
||||
|
||||
context 'when job is specified' do
|
||||
let(:config) { 'job_name' }
|
||||
shared_examples 'job type' do
|
||||
describe '#type' do
|
||||
subject(:need_type) { need.type }
|
||||
|
||||
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')
|
||||
end
|
||||
it { is_expected.to eq(:job) }
|
||||
end
|
||||
end
|
||||
|
||||
context 'when need is empty' do
|
||||
let(:config) { '' }
|
||||
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
|
||||
|
||||
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 '#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 '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
|
||||
|
||||
context 'when need config is not a string or a hash' do
|
||||
let(:config) { :job_name }
|
||||
|
||||
describe '#valid?' do
|
||||
it { is_expected.not_to be_valid }
|
||||
end
|
||||
|
||||
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)
|
||||
.to contain_exactly("job config can't be blank")
|
||||
.to contain_exactly('unknown strategy has an unsupported type')
|
||||
end
|
||||
end
|
||||
end
|
||||
|
|
|
@ -51,9 +51,34 @@ describe ::Gitlab::Ci::Config::Entry::Needs do
|
|||
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
|
||||
|
||||
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
|
||||
let(:config) { %w[first_job_name second_job_name] }
|
||||
|
||||
|
@ -65,18 +90,80 @@ describe ::Gitlab::Ci::Config::Entry::Needs do
|
|||
it 'returns key value' do
|
||||
expect(needs.value).to eq(
|
||||
job: [
|
||||
{ name: 'first_job_name' },
|
||||
{ name: 'second_job_name' }
|
||||
{ name: 'first_job_name', artifacts: true },
|
||||
{ name: 'second_job_name', artifacts: true }
|
||||
]
|
||||
)
|
||||
end
|
||||
end
|
||||
|
||||
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))
|
||||
it_behaves_like 'entry with descendant nodes'
|
||||
end
|
||||
|
||||
context 'with complex job entries composed' do
|
||||
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
|
||||
|
|
|
@ -105,7 +105,7 @@ describe Gitlab::Ci::Config::Normalizer do
|
|||
context 'for needs' do
|
||||
let(:expanded_job_attributes) do
|
||||
expanded_job_names.map do |job_name|
|
||||
{ name: job_name }
|
||||
{ name: job_name, extra: :key }
|
||||
end
|
||||
end
|
||||
|
||||
|
@ -117,7 +117,7 @@ describe Gitlab::Ci::Config::Normalizer do
|
|||
script: 'echo 1',
|
||||
needs: {
|
||||
job: [
|
||||
{ name: job_name.to_s }
|
||||
{ name: job_name.to_s, extra: :key }
|
||||
]
|
||||
}
|
||||
}
|
||||
|
@ -140,8 +140,8 @@ describe Gitlab::Ci::Config::Normalizer do
|
|||
script: 'echo 1',
|
||||
needs: {
|
||||
job: [
|
||||
{ name: job_name.to_s },
|
||||
{ name: "other_job" }
|
||||
{ name: job_name.to_s, extra: :key },
|
||||
{ name: "other_job", extra: :key }
|
||||
]
|
||||
}
|
||||
}
|
||||
|
@ -153,7 +153,7 @@ describe Gitlab::Ci::Config::Normalizer do
|
|||
end
|
||||
|
||||
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
|
||||
|
|
|
@ -1525,8 +1525,48 @@ module Gitlab
|
|||
name: "test1",
|
||||
options: { script: ["test"] },
|
||||
needs_attributes: [
|
||||
{ name: "build1" },
|
||||
{ name: "build2" }
|
||||
{ name: "build1", artifacts: true },
|
||||
{ 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",
|
||||
allow_failure: false,
|
||||
|
@ -1546,8 +1586,37 @@ module Gitlab
|
|||
name: "test1",
|
||||
options: { script: ["test"] },
|
||||
needs_attributes: [
|
||||
{ name: "parallel 1/2" },
|
||||
{ name: "parallel 2/2" }
|
||||
{ name: "parallel 1/2", artifacts: true },
|
||||
{ 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",
|
||||
allow_failure: false,
|
||||
|
|
|
@ -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(:name) }
|
||||
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
|
||||
|
|
|
@ -741,20 +741,26 @@ describe Ci::Build do
|
|||
|
||||
before do
|
||||
needs.to_a.each do |need|
|
||||
create(:ci_build_need, build: final, name: need)
|
||||
create(:ci_build_need, build: final, **need)
|
||||
end
|
||||
end
|
||||
|
||||
subject { final.dependencies }
|
||||
|
||||
context 'when depedencies are defined' do
|
||||
context 'when dependencies are defined' do
|
||||
let(:dependencies) { %w(rspec staging) }
|
||||
|
||||
it { is_expected.to contain_exactly(rspec_test, staging) }
|
||||
end
|
||||
|
||||
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) }
|
||||
|
||||
|
@ -767,13 +773,44 @@ describe Ci::Build do
|
|||
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
|
||||
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) }
|
||||
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
|
||||
it { is_expected.to contain_exactly(build, rspec_test, rubocop_test, staging) }
|
||||
end
|
||||
|
|
|
@ -0,0 +1,175 @@
|
|||
# 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
|
15
yarn.lock
15
yarn.lock
|
@ -1047,11 +1047,6 @@
|
|||
"@types/istanbul-lib-coverage" "*"
|
||||
"@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@*":
|
||||
version "3.0.3"
|
||||
resolved "https://registry.yarnpkg.com/@types/minimatch/-/minimatch-3.0.3.tgz#3dca0e3f33b200fc7d1139c0cd96c1268cadfd9d"
|
||||
|
@ -10877,12 +10872,10 @@ thunky@^0.1.0:
|
|||
resolved "https://registry.yarnpkg.com/thunky/-/thunky-0.1.0.tgz#bf30146824e2b6e67b0f2d7a4ac8beb26908684e"
|
||||
integrity sha1-vzAUaCTituZ7Dy16Ssi+smkIaE4=
|
||||
|
||||
timeago.js@^3.0.2:
|
||||
version "3.0.2"
|
||||
resolved "https://registry.yarnpkg.com/timeago.js/-/timeago.js-3.0.2.tgz#32a67e7c0d887ea42ca588d3aae26f77de5e76cc"
|
||||
integrity sha1-MqZ+fA2IfqQspYjTquJvd95edsw=
|
||||
dependencies:
|
||||
"@types/jquery" "^2.0.40"
|
||||
timeago.js@^4.0.1:
|
||||
version "4.0.1"
|
||||
resolved "https://registry.yarnpkg.com/timeago.js/-/timeago.js-4.0.1.tgz#4be4aa19565ceaeb0da31fe14e01ce6ca4742da6"
|
||||
integrity sha512-ePzZuMoJqUc44hJbUYtY1qtzU7IammxooDCcFKogLkS5Nj+iCabR0ZlmNOFX8Dm1r5EpvR5q/PotOJli/mEPew==
|
||||
|
||||
timed-out@^4.0.0:
|
||||
version "4.0.1"
|
||||
|
|
Loading…
Reference in New Issue