Add latest changes from gitlab-org/gitlab@master

This commit is contained in:
GitLab Bot 2020-01-08 21:08:08 +00:00
parent 73391dcc36
commit e0b84f4ba4
48 changed files with 912 additions and 264 deletions

2
.gitignore vendored
View File

@ -86,4 +86,4 @@ jsdoc/
.projections.json
/qa/.rakeTasks
webpack-dev-server.json
.nvimrc
/.nvimrc

View File

@ -3,11 +3,13 @@ import { mapGetters } from 'vuex';
import Icon from '~/vue_shared/components/icon.vue';
import store from '~/pipelines/stores/test_reports';
import { __ } from '~/locale';
import SmartVirtualList from '~/vue_shared/components/smart_virtual_list.vue';
export default {
name: 'TestsSuiteTable',
components: {
Icon,
SmartVirtualList,
},
store,
props: {
@ -23,6 +25,8 @@ export default {
return this.getSuiteTests.length > 0;
},
},
maxShownRows: 30,
typicalRowHeight: 75,
};
</script>
@ -34,7 +38,7 @@ export default {
</div>
</div>
<div v-if="hasSuites" class="test-reports-table js-test-cases-table">
<div v-if="hasSuites" class="test-reports-table append-bottom-default js-test-cases-table">
<div role="row" class="gl-responsive-table-row table-row-header font-weight-bold fgray">
<div role="rowheader" class="table-section section-20">
{{ __('Class') }}
@ -53,52 +57,58 @@ export default {
</div>
</div>
<div
v-for="(testCase, index) in getSuiteTests"
:key="index"
class="gl-responsive-table-row rounded align-items-md-start mt-sm-3 js-case-row"
<smart-virtual-list
:length="getSuiteTests.length"
:remain="$options.maxShownRows"
:size="$options.typicalRowHeight"
>
<div class="table-section section-20 section-wrap">
<div role="rowheader" class="table-mobile-header">{{ __('Class') }}</div>
<div class="table-mobile-content pr-md-1">{{ testCase.classname }}</div>
</div>
<div
v-for="(testCase, index) in getSuiteTests"
:key="index"
class="gl-responsive-table-row rounded align-items-md-start mt-xs-3 js-case-row"
>
<div class="table-section section-20 section-wrap">
<div role="rowheader" class="table-mobile-header">{{ __('Class') }}</div>
<div class="table-mobile-content pr-md-1 text-truncate">{{ testCase.classname }}</div>
</div>
<div class="table-section section-20 section-wrap">
<div role="rowheader" class="table-mobile-header">{{ __('Name') }}</div>
<div class="table-mobile-content">{{ testCase.name }}</div>
</div>
<div class="table-section section-20 section-wrap">
<div role="rowheader" class="table-mobile-header">{{ __('Name') }}</div>
<div class="table-mobile-content">{{ testCase.name }}</div>
</div>
<div class="table-section section-10 section-wrap">
<div role="rowheader" class="table-mobile-header">{{ __('Status') }}</div>
<div class="table-mobile-content text-center">
<div
class="add-border ci-status-icon d-flex align-items-center justify-content-end justify-content-md-center"
:class="`ci-status-icon-${testCase.status}`"
>
<icon :size="24" :name="testCase.icon" />
<div class="table-section section-10 section-wrap">
<div role="rowheader" class="table-mobile-header">{{ __('Status') }}</div>
<div class="table-mobile-content text-center">
<div
class="add-border ci-status-icon d-flex align-items-center justify-content-end justify-content-md-center"
:class="`ci-status-icon-${testCase.status}`"
>
<icon :size="24" :name="testCase.icon" />
</div>
</div>
</div>
<div class="table-section flex-grow-1">
<div role="rowheader" class="table-mobile-header">{{ __('Trace'), }}</div>
<div class="table-mobile-content">
<pre
v-if="testCase.system_output"
class="build-trace build-trace-rounded text-left"
><code class="bash p-0">{{testCase.system_output}}</code></pre>
</div>
</div>
<div class="table-section section-10 section-wrap">
<div role="rowheader" class="table-mobile-header">
{{ __('Duration') }}
</div>
<div class="table-mobile-content text-right pr-sm-1">
{{ testCase.formattedTime }}
</div>
</div>
</div>
<div class="table-section flex-grow-1">
<div role="rowheader" class="table-mobile-header">{{ __('Trace'), }}</div>
<div class="table-mobile-content">
<pre
v-if="testCase.system_output"
class="build-trace build-trace-rounded text-left"
><code class="bash p-0">{{testCase.system_output}}</code></pre>
</div>
</div>
<div class="table-section section-10 section-wrap">
<div role="rowheader" class="table-mobile-header">
{{ __('Duration') }}
</div>
<div class="table-mobile-content text-right">
{{ testCase.formattedTime }}
</div>
</div>
</div>
</smart-virtual-list>
</div>
<div v-else>

View File

@ -2,9 +2,13 @@
import { mapGetters } from 'vuex';
import { s__ } from '~/locale';
import store from '~/pipelines/stores/test_reports';
import SmartVirtualList from '~/vue_shared/components/smart_virtual_list.vue';
export default {
name: 'TestsSummaryTable',
components: {
SmartVirtualList,
},
store,
props: {
heading: {
@ -24,6 +28,8 @@ export default {
this.$emit('row-click', suite);
},
},
maxShownRows: 20,
typicalRowHeight: 55,
};
</script>
@ -35,7 +41,7 @@ export default {
</div>
</div>
<div v-if="hasSuites" class="test-reports-table js-test-suites-table">
<div v-if="hasSuites" class="test-reports-table append-bottom-default js-test-suites-table">
<div role="row" class="gl-responsive-table-row table-row-header font-weight-bold">
<div role="rowheader" class="table-section section-25 pl-3">
{{ __('Suite') }}
@ -60,66 +66,72 @@ export default {
</div>
</div>
<div
v-for="(testSuite, index) in getTestSuites"
:key="index"
role="row"
class="gl-responsive-table-row gl-responsive-table-row-clickable test-reports-summary-row rounded cursor-pointer js-suite-row"
@click="tableRowClick(testSuite)"
<smart-virtual-list
:length="getTestSuites.length"
:remain="$options.maxShownRows"
:size="$options.typicalRowHeight"
>
<div class="table-section section-25">
<div role="rowheader" class="table-mobile-header font-weight-bold">
{{ __('Suite') }}
<div
v-for="(testSuite, index) in getTestSuites"
:key="index"
role="row"
class="gl-responsive-table-row gl-responsive-table-row-clickable test-reports-summary-row rounded cursor-pointer js-suite-row"
@click="tableRowClick(testSuite)"
>
<div class="table-section section-25">
<div role="rowheader" class="table-mobile-header font-weight-bold">
{{ __('Suite') }}
</div>
<div class="table-mobile-content underline cgray pl-3">
{{ testSuite.name }}
</div>
</div>
<div class="table-mobile-content underline cgray pl-3">
{{ testSuite.name }}
</div>
</div>
<div class="table-section section-25">
<div role="rowheader" class="table-mobile-header font-weight-bold">
{{ __('Duration') }}
<div class="table-section section-25">
<div role="rowheader" class="table-mobile-header font-weight-bold">
{{ __('Duration') }}
</div>
<div class="table-mobile-content text-md-left">
{{ testSuite.formattedTime }}
</div>
</div>
<div class="table-mobile-content text-md-left">
{{ testSuite.formattedTime }}
</div>
</div>
<div class="table-section section-10 text-center">
<div role="rowheader" class="table-mobile-header font-weight-bold">
{{ __('Failed') }}
<div class="table-section section-10 text-center">
<div role="rowheader" class="table-mobile-header font-weight-bold">
{{ __('Failed') }}
</div>
<div class="table-mobile-content">{{ testSuite.failed_count }}</div>
</div>
<div class="table-mobile-content">{{ testSuite.failed_count }}</div>
</div>
<div class="table-section section-10 text-center">
<div role="rowheader" class="table-mobile-header font-weight-bold">
{{ __('Errors') }}
<div class="table-section section-10 text-center">
<div role="rowheader" class="table-mobile-header font-weight-bold">
{{ __('Errors') }}
</div>
<div class="table-mobile-content">{{ testSuite.error_count }}</div>
</div>
<div class="table-mobile-content">{{ testSuite.error_count }}</div>
</div>
<div class="table-section section-10 text-center">
<div role="rowheader" class="table-mobile-header font-weight-bold">
{{ __('Skipped') }}
<div class="table-section section-10 text-center">
<div role="rowheader" class="table-mobile-header font-weight-bold">
{{ __('Skipped') }}
</div>
<div class="table-mobile-content">{{ testSuite.skipped_count }}</div>
</div>
<div class="table-mobile-content">{{ testSuite.skipped_count }}</div>
</div>
<div class="table-section section-10 text-center">
<div role="rowheader" class="table-mobile-header font-weight-bold">
{{ __('Passed') }}
<div class="table-section section-10 text-center">
<div role="rowheader" class="table-mobile-header font-weight-bold">
{{ __('Passed') }}
</div>
<div class="table-mobile-content">{{ testSuite.success_count }}</div>
</div>
<div class="table-mobile-content">{{ testSuite.success_count }}</div>
</div>
<div class="table-section section-10 text-right pr-md-3">
<div role="rowheader" class="table-mobile-header font-weight-bold">
{{ __('Total') }}
<div class="table-section section-10 text-right pr-md-3">
<div role="rowheader" class="table-mobile-header font-weight-bold">
{{ __('Total') }}
</div>
<div class="table-mobile-content">{{ testSuite.total_count }}</div>
</div>
<div class="table-mobile-content">{{ testSuite.total_count }}</div>
</div>
</div>
</smart-virtual-list>
</div>
<div v-else>

View File

@ -57,20 +57,24 @@ class GitlabSchema < GraphQL::Schema
object.to_global_id
end
# Find an object by looking it up from its global ID, passed as a string.
#
# This is the composition of 'parse_gid' and 'find_by_gid', see these
# methods for further documentation.
def object_from_id(global_id, ctx = {})
expected_type = ctx[:expected_type]
gid = GlobalID.parse(global_id)
gid = parse_gid(global_id, ctx)
unless gid
raise Gitlab::Graphql::Errors::ArgumentError, "#{global_id} is not a valid GitLab id."
end
if expected_type && !gid.model_class.ancestors.include?(expected_type)
vars = { global_id: global_id, expected_type: expected_type }
msg = _('%{global_id} is not a valid id for %{expected_type}.') % vars
raise Gitlab::Graphql::Errors::ArgumentError, msg
end
find_by_gid(gid)
end
# Find an object by looking it up from its 'GlobalID'.
#
# * For `ApplicationRecord`s, this is equivalent to
# `global_id.model_class.find(gid.model_id)`, but more efficient.
# * For classes that implement `.lazy_find(global_id)`, this class method
# will be called.
# * All other classes will use `GlobalID#find`
def find_by_gid(gid)
if gid.model_class < ApplicationRecord
Gitlab::Graphql::Loaders::BatchModelLoader.new(gid.model_class, gid.model_id).find
elsif gid.model_class.respond_to?(:lazy_find)
@ -80,6 +84,38 @@ class GitlabSchema < GraphQL::Schema
end
end
# Parse a string to a GlobalID, raising ArgumentError if there are problems
# with it.
#
# Problems that may occur:
# * it may not be syntactically valid
# * it may not match the expected type (see below)
#
# Options:
# * :expected_type [Class] - the type of object this GlobalID should refer to.
#
# e.g.
#
# ```
# gid = GitlabSchema.parse_gid(my_string, expected_type: ::Project)
# project_id = gid.model_id
# gid.model_class == ::Project
# ```
def parse_gid(global_id, ctx = {})
expected_type = ctx[:expected_type]
gid = GlobalID.parse(global_id)
raise Gitlab::Graphql::Errors::ArgumentError, "#{global_id} is not a valid GitLab id." unless gid
if expected_type && !gid.model_class.ancestors.include?(expected_type)
vars = { global_id: global_id, expected_type: expected_type }
msg = _('%{global_id} is not a valid id for %{expected_type}.') % vars
raise Gitlab::Graphql::Errors::ArgumentError, msg
end
gid
end
private
def max_query_complexity(ctx)

View File

@ -5,7 +5,7 @@ require 'securerandom'
module Clusters
module Applications
class Jupyter < ApplicationRecord
VERSION = '0.9-174bbd5'
VERSION = '0.9.0-beta.2'
self.table_name = 'clusters_applications_jupyter'

View File

@ -19,15 +19,20 @@ class ExternalWikiService < Service
def fields
[
{ type: 'text', name: 'external_wiki_url', placeholder: s_('ExternalWikiService|The URL of the external Wiki'), required: true }
{
type: 'text',
name: 'external_wiki_url',
placeholder: s_('ExternalWikiService|The URL of the external Wiki'),
required: true
}
]
end
def execute(_data)
@response = Gitlab::HTTP.get(properties['external_wiki_url'], verify: true) rescue nil
if @response != 200
nil
end
response = Gitlab::HTTP.get(properties['external_wiki_url'], verify: true)
response.body if response.code == 200
rescue
nil
end
def self.supported_events

View File

@ -0,0 +1,5 @@
---
title: Container expiration policies can be updated with the project api
merge_request: 22180
author:
type: added

View File

@ -0,0 +1,5 @@
---
title: Check both DEPENDENCY_SCANNING_DISABLED and DS_DISABLE_DIND when executing Dependency Scanning job template
merge_request: 22172
author:
type: fixed

View File

@ -0,0 +1,5 @@
---
title: Added smart virtual list component to test reports to enhance rendering performance
merge_request: 22381
author:
type: performance

View File

@ -0,0 +1,5 @@
---
title: Document MAVEN_CLI_OPTS defaults for maven project dependency scanning and update when the variable is used
merge_request: 22126
author:
type: added

View File

@ -0,0 +1,5 @@
---
title: Update jupyterhub chart
merge_request: 22127
author:
type: changed

View File

@ -25,15 +25,22 @@ The most common architecture for Praefect is simplified in the diagram below:
```mermaid
graph TB
GitLab --> Praefect;
Praefect --> Gitaly-1;
Praefect --> Gitaly-2;
Praefect --> Gitaly-3;
Praefect --- PostgreSQL;
Praefect --> Gitaly1;
Praefect --> Gitaly2;
Praefect --> Gitaly3;
```
Where `GitLab` is the collection of clients that can request Git operations.
The Praefect node has three storage nodes attached. Praefect itself doesn't
store data, but connects to three Gitaly nodes, `Gitaly-1`, `Gitaly-2`, and `Gitaly-3`.
In order to keep track of replication state, Praefect relies on a
PostgreSQL database. This database is a single point of failure so you
should use a highly available PostgreSQL server for this. GitLab
itself needs a HA PostgreSQL server too, so you could optionally co-locate the Praefect
SQL database on the PostgreSQL server you use for the rest of GitLab.
Praefect may be enabled on its own node or can be run on the GitLab server.
In the example below we will use a separate server, but the optimal configuration
for Praefect is still being determined.
@ -62,6 +69,53 @@ We need to manage the following secrets and make them match across hosts:
`PRAEFECT_EXTERNAL_TOKEN` because Gitaly clients must not be able to
access internal nodes of the Praefect cluster directly; that could
lead to data loss.
1. `PRAEFECT_SQL_PASSWORD`: this password is used by Praefect to connect to
PostgreSQL.
#### Network addresses
1. `POSTGRESQL_SERVER`: the host name or IP address of your PostgreSQL server
#### PostgreSQL
To set up a Praefect cluster you need a highly available PostgreSQL
server. You need PostgreSQL 9.6 or newer. Praefect needs to have a SQL
user with the right to create databases.
In the instructions below we assume you have administrative access to
your PostgreSQL server via `psql`. Depending on your environment, you
may also be able to do this via the web interface of your cloud
platform, or via your configuration management system, etc.
Below we assume that you have administrative access as the `postgres`
user. First open a `psql` session as the `postgres` user:
```shell
psql -h POSTGRESQL_SERVER -U postgres -d template1
```
Once you are connected, run the following command. Replace
`PRAEFECT_SQL_PASSWORD` with the actual (random) password you
generated for the `praefect` SQL user:
```sql
CREATE ROLE praefect WITH LOGIN CREATEDB PASSWORD 'PRAEFECT_SQL_PASSWORD';
\q # exit psql
```
Now connect as the `praefect` user to create the database. This has
the side effect of verifying that you have access:
```shell
psql -h POSTGRESQL_SERVER -U praefect -d template1
```
Once you have connected as the `praefect` user, run:
```sql
CREATE DATABASE praefect_production WITH ENCODING=UTF8;
\q # quit psql
```
#### Praefect
@ -118,10 +172,39 @@ praefect['virtual_storages'] = {
}
}
}
praefect['database_host'] = 'POSTGRESQL_SERVER'
praefect['database_port'] = 5432
praefect['database_user'] = 'praefect'
praefect['database_password'] = 'PRAEFECT_SQL_PASSWORD'
praefect['database_dbname'] = 'praefect_production'
# Uncomment the line below if you do not want to use an encrypted
# connection to PostgreSQL
# praefect['database_sslmode'] = 'disable'
# Uncomment and modify these lines if you are using a TLS client
# certificate to connect to PostgreSQL
# praefect['database_sslcert'] = '/path/to/client-cert'
# praefect['database_sslkey'] = '/path/to/client-key'
# Uncomment and modify this line if your PostgreSQL server uses a custom
# CA
# praefect['database_sslrootcert'] = '/path/to/rootcert'
```
Save the file and [reconfigure Praefect](../restart_gitlab.md#omnibus-gitlab-reconfigure).
After you reconfigure, verify that Praefect can reach PostgreSQL:
```shell
sudo -u git /opt/gitlab/embedded/bin/praefect -config /var/opt/gitlab/praefect/config.toml sql-ping
```
If the check fails, make sure you have followed the steps correctly. If you edit `/etc/gitlab/gitlab.rb`,
remember to run `sudo gitlab-ctl reconfigure` again before trying the
`sql-ping` command.
#### Gitaly
Next we will configure each Gitaly server assigned to Praefect. Configuration for these

View File

@ -631,7 +631,7 @@ mounting the docker-daemon and setting `privileged = false` in the Runner's
```toml
[runners.docker]
image = "ruby:2.1"
image = "ruby:2.6"
privileged = false
volumes = ["/var/run/docker.sock:/var/run/docker.sock", "/cache"]
```

View File

@ -529,9 +529,9 @@ type CreateSnippetPayload {
snippet: Snippet
}
type Design implements Noteable {
type Design implements DesignFields & Noteable {
"""
Diff refs of the design
The diff refs for this design
"""
diffRefs: DiffRefs!
@ -561,33 +561,32 @@ type Design implements Noteable {
): DiscussionConnection!
"""
Type of change made to the design at the version specified by the `atVersion`
argument if supplied. Defaults to the latest version
How this design was changed in the current version
"""
event: DesignVersionEvent!
"""
Filename of the design file
The filename of the design
"""
filename: String!
"""
Full path of the design file
The full path to the design file
"""
fullPath: String!
"""
ID of the design
The ID of this design
"""
id: ID!
"""
Image of the design
The URL of the image
"""
image: String!
"""
Issue associated with the design
The issue the design belongs to
"""
issue: Issue!
@ -617,17 +616,17 @@ type Design implements Noteable {
): NoteConnection!
"""
Total count of user-created notes for the design
The total count of user-created notes for this design
"""
notesCount: Int!
"""
Project associated with the design
The project the design belongs to
"""
project: Project!
"""
All versions related to the design, ordered newest first
All versions related to this design ordered newest first
"""
versions(
"""
@ -765,6 +764,53 @@ type DesignEdge {
node: Design
}
interface DesignFields {
"""
The diff refs for this design
"""
diffRefs: DiffRefs!
"""
How this design was changed in the current version
"""
event: DesignVersionEvent!
"""
The filename of the design
"""
filename: String!
"""
The full path to the design file
"""
fullPath: String!
"""
The ID of this design
"""
id: ID!
"""
The URL of the image
"""
image: String!
"""
The issue the design belongs to
"""
issue: Issue!
"""
The total count of user-created notes for this design
"""
notesCount: Int!
"""
The project the design belongs to
"""
project: Project!
}
"""
Autogenerated input type of DesignManagementDelete
"""

View File

@ -10350,7 +10350,7 @@
"fields": [
{
"name": "diffRefs",
"description": "Diff refs of the design",
"description": "The diff refs for this design",
"args": [
],
@ -10425,7 +10425,7 @@
},
{
"name": "event",
"description": "Type of change made to the design at the version specified by the `atVersion` argument if supplied. Defaults to the latest version",
"description": "How this design was changed in the current version",
"args": [
],
@ -10443,7 +10443,7 @@
},
{
"name": "filename",
"description": "Filename of the design file",
"description": "The filename of the design",
"args": [
],
@ -10461,7 +10461,7 @@
},
{
"name": "fullPath",
"description": "Full path of the design file",
"description": "The full path to the design file",
"args": [
],
@ -10479,7 +10479,7 @@
},
{
"name": "id",
"description": "ID of the design",
"description": "The ID of this design",
"args": [
],
@ -10497,7 +10497,7 @@
},
{
"name": "image",
"description": "Image of the design",
"description": "The URL of the image",
"args": [
],
@ -10515,7 +10515,7 @@
},
{
"name": "issue",
"description": "Issue associated with the design",
"description": "The issue the design belongs to",
"args": [
],
@ -10590,7 +10590,7 @@
},
{
"name": "notesCount",
"description": "Total count of user-created notes for the design",
"description": "The total count of user-created notes for this design",
"args": [
],
@ -10608,7 +10608,7 @@
},
{
"name": "project",
"description": "Project associated with the design",
"description": "The project the design belongs to",
"args": [
],
@ -10626,7 +10626,7 @@
},
{
"name": "versions",
"description": "All versions related to the design, ordered newest first",
"description": "All versions related to this design ordered newest first",
"args": [
{
"name": "after",
@ -10688,11 +10688,195 @@
"kind": "INTERFACE",
"name": "Noteable",
"ofType": null
},
{
"kind": "INTERFACE",
"name": "DesignFields",
"ofType": null
}
],
"enumValues": null,
"possibleTypes": null
},
{
"kind": "INTERFACE",
"name": "DesignFields",
"description": null,
"fields": [
{
"name": "diffRefs",
"description": "The diff refs for this design",
"args": [
],
"type": {
"kind": "NON_NULL",
"name": null,
"ofType": {
"kind": "OBJECT",
"name": "DiffRefs",
"ofType": null
}
},
"isDeprecated": false,
"deprecationReason": null
},
{
"name": "event",
"description": "How this design was changed in the current version",
"args": [
],
"type": {
"kind": "NON_NULL",
"name": null,
"ofType": {
"kind": "ENUM",
"name": "DesignVersionEvent",
"ofType": null
}
},
"isDeprecated": false,
"deprecationReason": null
},
{
"name": "filename",
"description": "The filename of the design",
"args": [
],
"type": {
"kind": "NON_NULL",
"name": null,
"ofType": {
"kind": "SCALAR",
"name": "String",
"ofType": null
}
},
"isDeprecated": false,
"deprecationReason": null
},
{
"name": "fullPath",
"description": "The full path to the design file",
"args": [
],
"type": {
"kind": "NON_NULL",
"name": null,
"ofType": {
"kind": "SCALAR",
"name": "String",
"ofType": null
}
},
"isDeprecated": false,
"deprecationReason": null
},
{
"name": "id",
"description": "The ID of this design",
"args": [
],
"type": {
"kind": "NON_NULL",
"name": null,
"ofType": {
"kind": "SCALAR",
"name": "ID",
"ofType": null
}
},
"isDeprecated": false,
"deprecationReason": null
},
{
"name": "image",
"description": "The URL of the image",
"args": [
],
"type": {
"kind": "NON_NULL",
"name": null,
"ofType": {
"kind": "SCALAR",
"name": "String",
"ofType": null
}
},
"isDeprecated": false,
"deprecationReason": null
},
{
"name": "issue",
"description": "The issue the design belongs to",
"args": [
],
"type": {
"kind": "NON_NULL",
"name": null,
"ofType": {
"kind": "OBJECT",
"name": "Issue",
"ofType": null
}
},
"isDeprecated": false,
"deprecationReason": null
},
{
"name": "notesCount",
"description": "The total count of user-created notes for this design",
"args": [
],
"type": {
"kind": "NON_NULL",
"name": null,
"ofType": {
"kind": "SCALAR",
"name": "Int",
"ofType": null
}
},
"isDeprecated": false,
"deprecationReason": null
},
{
"name": "project",
"description": "The project the design belongs to",
"args": [
],
"type": {
"kind": "NON_NULL",
"name": null,
"ofType": {
"kind": "OBJECT",
"name": "Project",
"ofType": null
}
},
"isDeprecated": false,
"deprecationReason": null
}
],
"inputFields": null,
"interfaces": null,
"enumValues": null,
"possibleTypes": [
{
"kind": "OBJECT",
"name": "Design",
"ofType": null
}
]
},
{
"kind": "ENUM",
"name": "DesignVersionEvent",

View File

@ -104,15 +104,15 @@ The API can be explored interactively using the [GraphiQL IDE](../index.md#graph
| Name | Type | Description |
| --- | ---- | ---------- |
| `id` | ID! | ID of the design |
| `project` | Project! | Project associated with the design |
| `issue` | Issue! | Issue associated with the design |
| `notesCount` | Int! | Total count of user-created notes for the design |
| `filename` | String! | Filename of the design file |
| `fullPath` | String! | Full path of the design file |
| `event` | DesignVersionEvent! | Type of change made to the design at the version specified by the `atVersion` argument if supplied. Defaults to the latest version |
| `image` | String! | Image of the design |
| `diffRefs` | DiffRefs! | Diff refs of the design |
| `id` | ID! | The ID of this design |
| `project` | Project! | The project the design belongs to |
| `issue` | Issue! | The issue the design belongs to |
| `filename` | String! | The filename of the design |
| `fullPath` | String! | The full path to the design file |
| `image` | String! | The URL of the image |
| `diffRefs` | DiffRefs! | The diff refs for this design |
| `event` | DesignVersionEvent! | How this design was changed in the current version |
| `notesCount` | Int! | The total count of user-created notes for this design |
### DesignCollection

View File

@ -761,6 +761,14 @@ GET /projects/:id
"snippets_enabled": false,
"resolve_outdated_diff_discussions": false,
"container_registry_enabled": false,
"container_expiration_policy": {
"cadence": "7d",
"enabled": false,
"keep_n": null,
"older_than": null,
"name_regex": null,
"next_run_at": "2020-01-07T21:42:58.658Z"
},
"created_at": "2013-09-30T13:46:02Z",
"last_activity_at": "2013-09-30T13:46:02Z",
"creator_id": 3,
@ -986,6 +994,7 @@ POST /projects
| `snippets_access_level` | string | no | One of `disabled`, `private` or `enabled` |
| `resolve_outdated_diff_discussions` | boolean | no | Automatically resolve merge request diffs discussions on lines changed with a push |
| `container_registry_enabled` | boolean | no | Enable container registry for this project |
| `container_expiration_policy_attributes` | hash | no | Update the container expiration policy for this project. Accepts: `cadence` (string), `keep_n` (string), `older_than` (string), `name_regex` (string), `enabled` (boolean) |
| `shared_runners_enabled` | boolean | no | Enable shared runners for this project |
| `visibility` | string | no | See [project visibility level](#project-visibility-level) |
| `import_url` | string | no | URL to import repository from |
@ -1115,6 +1124,7 @@ PUT /projects/:id
| `snippets_access_level` | string | no | One of `disabled`, `private` or `enabled` |
| `resolve_outdated_diff_discussions` | boolean | no | Automatically resolve merge request diffs discussions on lines changed with a push |
| `container_registry_enabled` | boolean | no | Enable container registry for this project |
| `container_expiration_policy_attributes` | hash | no | Update the container expiration policy for this project. Accepts: `cadence` (string), `keep_n` (string), `older_than` (string), `name_regex` (string), `enabled` (boolean) |
| `shared_runners_enabled` | boolean | no | Enable shared runners for this project |
| `visibility` | string | no | See [project visibility level](#project-visibility-level) |
| `import_url` | string | no | URL to import repository from |

View File

@ -32,14 +32,14 @@ A one-line example can be seen below:
sudo gitlab-runner register \
--url "https://gitlab.example.com/" \
--registration-token "PROJECT_REGISTRATION_TOKEN" \
--description "docker-ruby-2.1" \
--description "docker-ruby:2.6" \
--executor "docker" \
--docker-image ruby:2.1 \
--docker-image ruby:2.6 \
--docker-services postgres:latest \
--docker-services mysql:latest
```
The registered runner will use the `ruby:2.1` Docker image and will run two
The registered runner will use the `ruby:2.6` Docker image and will run two
services, `postgres:latest` and `mysql:latest`, both of which will be
accessible during the build process.
@ -194,7 +194,7 @@ services that you want to use during build time:
```yaml
default:
image: ruby:2.2
image: ruby:2.6
services:
- postgres:9.3
@ -214,15 +214,15 @@ default:
before_script:
- bundle install
test:2.1:
image: ruby:2.1
test:2.6:
image: ruby:2.6
services:
- postgres:9.3
script:
- bundle exec rake spec
test:2.2:
image: ruby:2.2
test:2.7:
image: ruby:2.7
services:
- postgres:9.4
script:
@ -235,7 +235,7 @@ for `image` and `services`:
```yaml
default:
image:
name: ruby:2.2
name: ruby:2.6
entrypoint: ["/bin/bash"]
services:
@ -277,7 +277,7 @@ services:
command: ["postgres"]
image:
name: ruby:2.2
name: ruby:2.6
entrypoint: ["/bin/bash"]
before_script:
@ -773,7 +773,7 @@ time.
1. Create any service container: `mysql`, `postgresql`, `mongodb`, `redis`.
1. Create cache container to store all volumes as defined in `config.toml` and
`Dockerfile` of build image (`ruby:2.1` as in above example).
`Dockerfile` of build image (`ruby:2.6` as in above example).
1. Create build container and link any service container to build container.
1. Start build container and send job script to the container.
1. Run job script.
@ -818,11 +818,11 @@ Finally, create a build container by executing the `build_script` file we
created earlier:
```sh
docker run --name build -i --link=service-mysql:mysql --link=service-postgres:postgres ruby:2.1 /bin/bash < build_script
docker run --name build -i --link=service-mysql:mysql --link=service-postgres:postgres ruby:2.6 /bin/bash < build_script
```
The above command will create a container named `build` that is spawned from
the `ruby:2.1` image and has two services linked to it. The `build_script` is
the `ruby:2.6` image and has two services linked to it. The `build_script` is
piped using STDIN to the bash interpreter which in turn executes the
`build_script` in the `build` container.

View File

@ -71,12 +71,12 @@ gitlab-runner register \
--non-interactive \
--url "https://gitlab.com/" \
--registration-token "PROJECT_REGISTRATION_TOKEN" \
--description "ruby-2.2" \
--description "ruby:2.6" \
--executor "docker" \
--docker-image ruby:2.2 \
--docker-image ruby:2.6 \
--docker-postgres latest
```
With the command above, you create a Runner that uses the [ruby:2.2](https://hub.docker.com/_/ruby) image and uses a [postgres](https://hub.docker.com/_/postgres) database.
With the command above, you create a Runner that uses the [ruby:2.6](https://hub.docker.com/_/ruby) image and uses a [postgres](https://hub.docker.com/_/postgres) database.
To access the PostgreSQL database, connect to `host: postgres` as user `postgres` with no password.

View File

@ -3645,7 +3645,7 @@ having their own custom `script` defined:
```yaml
.job_template: &job_definition # Hidden key that defines an anchor named 'job_definition'
image: ruby:2.1
image: ruby:2.6
services:
- postgres
- redis
@ -3667,13 +3667,13 @@ given hash into the current one", and `*` includes the named anchor
```yaml
.job_template:
image: ruby:2.1
image: ruby:2.6
services:
- postgres
- redis
test1:
image: ruby:2.1
image: ruby:2.6
services:
- postgres
- redis
@ -3681,7 +3681,7 @@ test1:
- test1 project
test2:
image: ruby:2.1
image: ruby:2.6
services:
- postgres
- redis

View File

@ -382,7 +382,7 @@ end
## String Freezing
In recent Ruby versions calling `freeze` on a String leads to it being allocated
only once and re-used. For example, on Ruby 2.3 this will only allocate the
only once and re-used. For example, on Ruby 2.3 or later this will only allocate the
"foo" String once:
```ruby

View File

@ -146,7 +146,7 @@ using environment variables.
| `PIP_INDEX_URL` | Base URL of Python Package Index (default `https://pypi.org/simple`). |
| `PIP_EXTRA_INDEX_URL` | Array of [extra URLs](https://pip.pypa.io/en/stable/reference/pip_install/#cmdoption-extra-index-url) of package indexes to use in addition to `PIP_INDEX_URL`. Comma separated. |
| `PIP_REQUIREMENTS_FILE` | Pip requirements file to be scanned. |
| `MAVEN_CLI_OPTS` | List of command line arguments that will be passed to the maven analyzer during the project's build phase (see example for [using private repos](#using-private-maven-repos)). |
| `MAVEN_CLI_OPTS` | List of command line arguments that will be passed to `maven` by the analyzer. The default is `"-DskipTests --batch-mode"`. See an example for [using private repos](#using-private-maven-repos). |
| `BUNDLER_AUDIT_UPDATE_DISABLED` | Disable automatic updates for the `bundler-audit` analyzer (default: `"false"`). Useful if you're running Dependency Scanning in an offline, air-gapped environment.|
### Using private Maven repos

View File

@ -1,5 +1,5 @@
---
last_updated: 2019-06-04
last_updated: 2020-01-06
type: reference, howto
---
@ -158,7 +158,7 @@ first thing GitLab Runner will look for in your `.gitlab-ci.yml` is a
your container to run that script:
```yaml
image: ruby:2.3
image: ruby:2.7
pages:
script:
@ -170,9 +170,9 @@ pages:
```
In this case, you're telling the Runner to pull this image, which
contains Ruby 2.3 as part of its file system. When you don't specify
contains Ruby 2.7 as part of its file system. When you don't specify
this image in your configuration, the Runner will use a default
image, which is Ruby 2.1.
image, which is Ruby 2.6.
If your SSG needs [NodeJS](https://nodejs.org/) to build, you'll
need to specify which image you want to use, and this image should
@ -198,7 +198,7 @@ To do that, we need to add another line to our CI, telling the Runner
to only perform that _job_ called `pages` on the `master` branch `only`:
```yaml
image: ruby:2.3
image: ruby:2.6
pages:
script:
@ -221,7 +221,7 @@ and deploy. To specify which stage your _job_ is running,
simply add another line to your CI:
```yaml
image: ruby:2.3
image: ruby:2.6
pages:
stage: deploy
@ -244,7 +244,7 @@ let's add another task (_job_) to our CI, telling it to
test every push to other branches, `except` the `master` branch:
```yaml
image: ruby:2.3
image: ruby:2.6
pages:
stage: deploy
@ -294,7 +294,7 @@ every single _job_. In our example, notice that we run
We don't need to repeat it:
```yaml
image: ruby:2.3
image: ruby:2.6
before_script:
- bundle install
@ -329,7 +329,7 @@ cache Jekyll dependencies in a `vendor` directory
when we run `bundle install`:
```yaml
image: ruby:2.3
image: ruby:2.6
cache:
paths:

View File

@ -1,6 +1,6 @@
---
type: reference
last_updated: 2018-06-04
last_updated: 2020-01-06
---
# Exploring GitLab Pages
@ -156,7 +156,7 @@ Below is a copy of `.gitlab-ci.yml` where the most significant line is the last
one, specifying to execute everything in the `pages` branch:
```
image: ruby:2.1
image: ruby:2.6
pages:
script:

View File

@ -178,6 +178,15 @@ module API
expose :only_protected_branches
end
class ContainerExpirationPolicy < Grape::Entity
expose :cadence
expose :enabled
expose :keep_n
expose :older_than
expose :name_regex
expose :next_run_at
end
class ProjectImportStatus < ProjectIdentity
expose :import_status
@ -276,6 +285,8 @@ module API
expose :owner, using: Entities::UserBasic, unless: ->(project, options) { project.group }
expose :resolve_outdated_diff_discussions
expose :container_registry_enabled
expose :container_expiration_policy, using: Entities::ContainerExpirationPolicy,
if: -> (project, _) { project.container_expiration_policy }
# Expose old field names with the new permissions methods to keep API compatible
# TODO: remove in API v5, replaced by *_access_level
@ -341,6 +352,7 @@ module API
# MR describing the solution: https://gitlab.com/gitlab-org/gitlab-foss/merge_requests/20555
super(projects_relation).preload(:group)
.preload(:ci_cd_settings)
.preload(:container_expiration_policy)
.preload(:auto_devops)
.preload(project_group_links: { group: :route },
fork_network: :root_project,

View File

@ -32,6 +32,9 @@ module API
optional :resolve_outdated_diff_discussions, type: Boolean, desc: 'Automatically resolve merge request diffs discussions on lines changed with a push'
optional :remove_source_branch_after_merge, type: Boolean, desc: 'Remove the source branch by default after merge'
optional :container_registry_enabled, type: Boolean, desc: 'Flag indication if the container registry is enabled for that project'
optional :container_expiration_policy_attributes, type: Hash do
use :optional_container_expiration_policy_params
end
optional :lfs_enabled, type: Boolean, desc: 'Flag indication if Git LFS is enabled for that project'
optional :visibility, type: String, values: Gitlab::VisibilityLevel.string_values, desc: 'The visibility of the project.'
optional :public_builds, type: Boolean, desc: 'Perform public builds'
@ -72,6 +75,14 @@ module API
params :optional_update_params_ee do
end
params :optional_container_expiration_policy_params do
optional :cadence, type: String, desc: 'Container expiration policy cadence for recurring job'
optional :keep_n, type: String, desc: 'Container expiration policy number of images to keep'
optional :older_than, type: String, desc: 'Container expiration policy remove images older than value'
optional :name_regex, type: String, desc: 'Container expiration policy regex for image removal'
optional :enabled, type: Boolean, desc: 'Flag indication if container expiration policy is enabled'
end
def self.update_params_at_least_one_of
[
:auto_devops_enabled,
@ -84,6 +95,7 @@ module API
:ci_config_path,
:ci_default_git_depth,
:container_registry_enabled,
:container_expiration_policy_attributes,
:default_branch,
:description,
:autoclose_referenced_issues,

View File

@ -77,6 +77,7 @@ dependency_scanning:
services: []
except:
variables:
- $DEPENDENCY_SCANNING_DISABLED
- $DS_DISABLE_DIND == 'false'
script:
- /analyzer run

View File

@ -0,0 +1,60 @@
# frozen_string_literal: true
module Gitlab
module DatabaseImporters
module SelfMonitoring
module Project
class DeleteService < ::BaseService
include Stepable
include SelfMonitoring::Helpers
steps :validate_self_monitoring_project_exists,
:destroy_project_owner,
:delete_project_id
def initialize
super(nil)
end
def execute
execute_steps
end
private
def validate_self_monitoring_project_exists(result)
unless project_created? || self_monitoring_project_id.present?
return error(_('Self monitoring project does not exist'))
end
success(result)
end
def destroy_project_owner(result)
return success(result) unless project_created?
if self_monitoring_project.owner.destroy
success(result)
else
log_error(self_monitoring_project.errors.full_messages)
error(_('Error deleting project. Check logs for error details.'))
end
end
def delete_project_id(result)
update_result = application_settings.update(
instance_administration_project_id: nil
)
if update_result
success(result)
else
log_error("Could not delete self monitoring project ID, errors: %{errors}" % { errors: application_settings.errors.full_messages })
error(_('Could not delete project ID'))
end
end
end
end
end
end
end

View File

@ -5121,6 +5121,9 @@ msgstr ""
msgid "Could not delete chat nickname %{chat_name}."
msgstr ""
msgid "Could not delete project ID"
msgstr ""
msgid "Could not fetch projects"
msgstr ""
@ -7122,6 +7125,9 @@ msgstr ""
msgid "Error deleting %{issuableType}"
msgstr ""
msgid "Error deleting project. Check logs for error details."
msgstr ""
msgid "Error details"
msgstr ""
@ -16262,6 +16268,9 @@ msgstr ""
msgid "Selecting a GitLab user will add a link to the GitLab user in the descriptions of issues and comments (e.g. \"By <a href=\"#\">@johnsmith</a>\"). It will also associate and/or assign these issues and comments with the selected user."
msgstr ""
msgid "Self monitoring project does not exist"
msgstr ""
msgid "Self-monitoring is not enabled on this GitLab server, contact your administrator."
msgstr ""

View File

@ -124,14 +124,26 @@ describe GitlabSchema do
describe '.object_from_id' do
context 'for subclasses of `ApplicationRecord`' do
it 'returns the correct record' do
user = create(:user)
let_it_be(:user) { create(:user) }
it 'returns the correct record' do
result = described_class.object_from_id(user.to_global_id.to_s)
expect(result.sync).to eq(user)
end
it 'returns the correct record, of the expected type' do
result = described_class.object_from_id(user.to_global_id.to_s, expected_type: ::User)
expect(result.sync).to eq(user)
end
it 'fails if the type does not match' do
expect do
described_class.object_from_id(user.to_global_id.to_s, expected_type: ::Project)
end.to raise_error(Gitlab::Graphql::Errors::ArgumentError)
end
it 'batchloads the queries' do
user1 = create(:user)
user2 = create(:user)

View File

@ -7,7 +7,16 @@ describe GitlabSchema.types['Query'] do
expect(described_class.graphql_name).to eq('Query')
end
it { is_expected.to have_graphql_fields(:project, :namespace, :group, :echo, :metadata, :current_user, :snippets) }
it do
is_expected.to have_graphql_fields(:project,
:namespace,
:group,
:echo,
:metadata,
:current_user,
:snippets
).at_least
end
describe 'namespace field' do
subject { described_class.fields['namespace'] }

View File

@ -0,0 +1,57 @@
# frozen_string_literal: true
require 'spec_helper'
describe Gitlab::DatabaseImporters::SelfMonitoring::Project::DeleteService do
describe '#execute' do
let(:result) { subject.execute }
let(:application_setting) { Gitlab::CurrentSettings.current_application_settings }
before do
allow(ApplicationSetting).to receive(:current_without_cache) { application_setting }
end
context 'when project does not exist' do
it 'returns error' do
expect(result).to eq(
status: :error,
message: 'Self monitoring project does not exist',
last_step: :validate_self_monitoring_project_exists
)
end
end
context 'with project destroyed but ID still present in application settings' do
before do
application_setting.instance_administration_project_id = 1
end
it 'deletes project ID from application settings' do
subject.execute
expect(application_setting.instance_administration_project_id).to be_nil
end
end
context 'when self monitoring project exists' do
let(:group) { create(:group) }
let(:project) { create(:project, namespace: group) }
before do
application_setting.instance_administration_project = project
end
it 'destroys project' do
subject.execute
expect { project.reload }.to raise_error(ActiveRecord::RecordNotFound)
end
it 'deletes project ID from application settings' do
subject.execute
expect(application_setting.instance_administration_project_id).to be_nil
end
end
end
end

View File

@ -57,7 +57,8 @@ describe Clusters::Applications::Jupyter do
it 'is initialized with 4 arguments' do
expect(subject.name).to eq('jupyter')
expect(subject.chart).to eq('jupyter/jupyterhub')
expect(subject.version).to eq('0.9-174bbd5')
expect(subject.version).to eq('0.9.0-beta.2')
expect(subject).to be_rbac
expect(subject.repository).to eq('https://jupyterhub.github.io/helm-chart/')
expect(subject.files).to eq(jupyter.files)
@ -75,7 +76,7 @@ describe Clusters::Applications::Jupyter do
let(:jupyter) { create(:clusters_applications_jupyter, :errored, version: '0.0.1') }
it 'is initialized with the locked version' do
expect(subject.version).to eq('0.9-174bbd5')
expect(subject.version).to eq('0.9.0-beta.2')
end
end
end

View File

@ -5,11 +5,12 @@ require 'spec_helper'
describe 'CycleAnalytics#code' do
extend CycleAnalyticsHelpers::TestGeneration
let(:project) { create(:project, :repository) }
let(:from_date) { 10.days.ago }
let(:user) { create(:user, :admin) }
let_it_be(:project) { create(:project, :repository) }
let_it_be(:from_date) { 10.days.ago }
let_it_be(:user) { create(:user, :admin) }
let_it_be(:project_level) { CycleAnalytics::ProjectLevel.new(project, options: { from: from_date }) }
subject { CycleAnalytics::ProjectLevel.new(project, options: { from: from_date }) }
subject { project_level }
context 'with deployment' do
generate_cycle_analytics_spec(
@ -24,8 +25,6 @@ describe 'CycleAnalytics#code' do
context.create_merge_request_closing_issue(context.user, context.project, data[:issue])
end]],
post_fn: -> (context, data) do
context.merge_merge_requests_closing_issue(context.user, context.project, data[:issue])
context.deploy_master(context.user, context.project)
end)
context "when a regular merge request (that doesn't close the issue) is created" do
@ -56,7 +55,6 @@ describe 'CycleAnalytics#code' do
context.create_merge_request_closing_issue(context.user, context.project, data[:issue])
end]],
post_fn: -> (context, data) do
context.merge_merge_requests_closing_issue(context.user, context.project, data[:issue])
end)
context "when a regular merge request (that doesn't close the issue) is created" do

View File

@ -3,12 +3,12 @@
require 'spec_helper'
describe CycleAnalytics::GroupLevel do
let(:group) { create(:group)}
let(:project) { create(:project, :repository, namespace: group) }
let(:from_date) { 10.days.ago }
let(:user) { create(:user, :admin) }
let_it_be(:group) { create(:group)}
let_it_be(:project) { create(:project, :repository, namespace: group) }
let_it_be(:from_date) { 10.days.ago }
let_it_be(:user) { create(:user, :admin) }
let(:issue) { create(:issue, project: project, created_at: 2.days.ago) }
let(:milestone) { create(:milestone, project: project) }
let_it_be(:milestone) { create(:milestone, project: project) }
let(:mr) { create_merge_request_closing_issue(user, project, issue, commit_message: "References #{issue.to_reference}") }
let(:pipeline) { create(:ci_empty_pipeline, status: 'created', project: project, ref: mr.source_branch, sha: mr.source_branch_sha, head_pipeline_of: mr) }

View File

@ -5,11 +5,12 @@ require 'spec_helper'
describe 'CycleAnalytics#issue' do
extend CycleAnalyticsHelpers::TestGeneration
let(:project) { create(:project, :repository) }
let(:from_date) { 10.days.ago }
let(:user) { create(:user, :admin) }
let_it_be(:project) { create(:project, :repository) }
let_it_be(:from_date) { 10.days.ago }
let_it_be(:user) { create(:user, :admin) }
let_it_be(:project_level) { CycleAnalytics::ProjectLevel.new(project, options: { from: from_date }) }
subject { CycleAnalytics::ProjectLevel.new(project, options: { from: from_date }) }
subject { project_level }
generate_cycle_analytics_spec(
phase: :issue,
@ -28,10 +29,6 @@ describe 'CycleAnalytics#issue' do
end
end]],
post_fn: -> (context, data) do
if data[:issue].persisted?
context.create_merge_request_closing_issue(context.user, context.project, data[:issue].reload)
context.merge_merge_requests_closing_issue(context.user, context.project, data[:issue])
end
end)
context "when a regular label (instead of a list label) is added to the issue" do

View File

@ -5,17 +5,18 @@ require 'spec_helper'
describe 'CycleAnalytics#plan' do
extend CycleAnalyticsHelpers::TestGeneration
let(:project) { create(:project, :repository) }
let(:from_date) { 10.days.ago }
let(:user) { create(:user, :admin) }
let_it_be(:project) { create(:project, :repository) }
let_it_be(:from_date) { 10.days.ago }
let_it_be(:user) { create(:user, :admin) }
let_it_be(:project_level) { CycleAnalytics::ProjectLevel.new(project, options: { from: from_date }) }
subject { CycleAnalytics::ProjectLevel.new(project, options: { from: from_date }) }
subject { project_level }
generate_cycle_analytics_spec(
phase: :plan,
data_fn: -> (context) do
{
issue: context.create(:issue, project: context.project),
issue: context.build(:issue, project: context.project),
branch_name: context.generate(:branch)
}
end,
@ -32,8 +33,6 @@ describe 'CycleAnalytics#plan' do
context.create_commit_referencing_issue(data[:issue], branch_name: data[:branch_name])
end]],
post_fn: -> (context, data) do
context.create_merge_request_closing_issue(context.user, context.project, data[:issue], source_branch: data[:branch_name])
context.merge_merge_requests_closing_issue(context.user, context.project, data[:issue])
end)
context "when a regular label (instead of a list label) is added to the issue" do

View File

@ -5,11 +5,12 @@ require 'spec_helper'
describe 'CycleAnalytics#production' do
extend CycleAnalyticsHelpers::TestGeneration
let(:project) { create(:project, :repository) }
let(:from_date) { 10.days.ago }
let(:user) { create(:user, :admin) }
let_it_be(:project) { create(:project, :repository) }
let_it_be(:from_date) { 10.days.ago }
let_it_be(:user) { create(:user, :admin) }
let_it_be(:project_level) { CycleAnalytics::ProjectLevel.new(project, options: { from: from_date }) }
subject { CycleAnalytics::ProjectLevel.new(project, options: { from: from_date }) }
subject { project_level }
generate_cycle_analytics_spec(
phase: :production,
@ -24,13 +25,7 @@ describe 'CycleAnalytics#production' do
["production deploy happens after merge request is merged (along with other changes)",
lambda do |context, data|
# Make other changes on master
sha = context.project.repository.create_file(
context.user,
context.generate(:branch),
'content',
message: 'commit message',
branch_name: 'master')
context.project.repository.commit(sha)
context.project.repository.commit("sha_that_does_not_matter")
context.deploy_master(context.user, context.project)
end]])
@ -47,7 +42,7 @@ describe 'CycleAnalytics#production' do
context "when the deployment happens to a non-production environment" do
it "returns nil" do
issue = create(:issue, project: project)
issue = build(:issue, project: project)
merge_request = create_merge_request_closing_issue(user, project, issue)
MergeRequests::MergeService.new(project, user).execute(merge_request)
deploy_master(user, project, environment: 'staging')

View File

@ -3,11 +3,11 @@
require 'spec_helper'
describe CycleAnalytics::ProjectLevel do
let(:project) { create(:project, :repository) }
let(:from_date) { 10.days.ago }
let(:user) { create(:user, :admin) }
let(:issue) { create(:issue, project: project, created_at: 2.days.ago) }
let(:milestone) { create(:milestone, project: project) }
let_it_be(:project) { create(:project, :repository) }
let_it_be(:from_date) { 10.days.ago }
let_it_be(:user) { create(:user, :admin) }
let_it_be(:issue) { create(:issue, project: project, created_at: 2.days.ago) }
let_it_be(:milestone) { create(:milestone, project: project) }
let(:mr) { create_merge_request_closing_issue(user, project, issue, commit_message: "References #{issue.to_reference}") }
let(:pipeline) { create(:ci_empty_pipeline, status: 'created', project: project, ref: mr.source_branch, sha: mr.source_branch_sha, head_pipeline_of: mr) }

View File

@ -5,9 +5,9 @@ require 'spec_helper'
describe 'CycleAnalytics#review' do
extend CycleAnalyticsHelpers::TestGeneration
let(:project) { create(:project, :repository) }
let(:from_date) { 10.days.ago }
let(:user) { create(:user, :admin) }
let_it_be(:project) { create(:project, :repository) }
let_it_be(:from_date) { 10.days.ago }
let_it_be(:user) { create(:user, :admin) }
subject { CycleAnalytics::ProjectLevel.new(project, options: { from: from_date }) }

View File

@ -5,11 +5,12 @@ require 'spec_helper'
describe 'CycleAnalytics#staging' do
extend CycleAnalyticsHelpers::TestGeneration
let(:project) { create(:project, :repository) }
let(:from_date) { 10.days.ago }
let(:user) { create(:user, :admin) }
let_it_be(:project) { create(:project, :repository) }
let_it_be(:from_date) { 10.days.ago }
let_it_be(:user) { create(:user, :admin) }
let_it_be(:project_level) { CycleAnalytics::ProjectLevel.new(project, options: { from: from_date }) }
subject { CycleAnalytics::ProjectLevel.new(project, options: { from: from_date }) }
subject { project_level }
generate_cycle_analytics_spec(
phase: :staging,
@ -28,14 +29,7 @@ describe 'CycleAnalytics#staging' do
["production deploy happens after merge request is merged (along with other changes)",
lambda do |context, data|
# Make other changes on master
sha = context.project.repository.create_file(
context.user,
context.generate(:branch),
'content',
message: 'commit message',
branch_name: 'master')
context.project.repository.commit(sha)
context.project.repository.commit("this_sha_apparently_does_not_matter")
context.deploy_master(context.user, context.project)
end]])

View File

@ -5,16 +5,19 @@ require 'spec_helper'
describe 'CycleAnalytics#test' do
extend CycleAnalyticsHelpers::TestGeneration
let(:project) { create(:project, :repository) }
let(:from_date) { 10.days.ago }
let(:user) { create(:user, :admin) }
let_it_be(:project) { create(:project, :repository) }
let_it_be(:from_date) { 10.days.ago }
let_it_be(:user) { create(:user, :admin) }
let_it_be(:issue) { create(:issue, project: project) }
let_it_be(:project_level) { CycleAnalytics::ProjectLevel.new(project, options: { from: from_date }) }
let!(:merge_request) { create_merge_request_closing_issue(user, project, issue) }
subject { CycleAnalytics::ProjectLevel.new(project, options: { from: from_date }) }
subject { project_level }
generate_cycle_analytics_spec(
phase: :test,
data_fn: lambda do |context|
issue = context.create(:issue, project: context.project)
issue = context.issue
merge_request = context.create_merge_request_closing_issue(context.user, context.project, issue)
pipeline = context.create(:ci_pipeline, ref: merge_request.source_branch, sha: merge_request.diff_head_sha, project: context.project, head_pipeline_of: merge_request)
{ pipeline: pipeline, issue: issue }
@ -22,20 +25,15 @@ describe 'CycleAnalytics#test' do
start_time_conditions: [["pipeline is started", -> (context, data) { data[:pipeline].run! }]],
end_time_conditions: [["pipeline is finished", -> (context, data) { data[:pipeline].succeed! }]],
post_fn: -> (context, data) do
context.merge_merge_requests_closing_issue(context.user, context.project, data[:issue])
end)
context "when the pipeline is for a regular merge request (that doesn't close an issue)" do
it "returns nil" do
issue = create(:issue, project: project)
merge_request = create_merge_request_closing_issue(user, project, issue)
pipeline = create(:ci_pipeline, ref: "refs/heads/#{merge_request.source_branch}", sha: merge_request.diff_head_sha)
pipeline.run!
pipeline.succeed!
merge_merge_requests_closing_issue(user, project, issue)
expect(subject[:test].project_median).to be_nil
end
end
@ -53,30 +51,22 @@ describe 'CycleAnalytics#test' do
context "when the pipeline is dropped (failed)" do
it "returns nil" do
issue = create(:issue, project: project)
merge_request = create_merge_request_closing_issue(user, project, issue)
pipeline = create(:ci_pipeline, ref: "refs/heads/#{merge_request.source_branch}", sha: merge_request.diff_head_sha)
pipeline.run!
pipeline.drop!
merge_merge_requests_closing_issue(user, project, issue)
expect(subject[:test].project_median).to be_nil
end
end
context "when the pipeline is cancelled" do
it "returns nil" do
issue = create(:issue, project: project)
merge_request = create_merge_request_closing_issue(user, project, issue)
pipeline = create(:ci_pipeline, ref: "refs/heads/#{merge_request.source_branch}", sha: merge_request.diff_head_sha)
pipeline.run!
pipeline.cancel!
merge_merge_requests_closing_issue(user, project, issue)
expect(subject[:test].project_median).to be_nil
end
end

View File

@ -26,4 +26,34 @@ describe ExternalWikiService do
it { is_expected.not_to validate_presence_of(:external_wiki_url) }
end
end
describe 'test' do
before do
subject.properties['external_wiki_url'] = url
end
let(:url) { 'http://foo' }
let(:data) { nil }
let(:result) { subject.test(data) }
context 'the URL is not reachable' do
before do
WebMock.stub_request(:get, url).to_return(status: 404, body: 'not a page')
end
it 'is not successful' do
expect(result[:success]).to be_falsey
end
end
context 'the URL is reachable' do
before do
WebMock.stub_request(:get, url).to_return(status: 200, body: 'foo')
end
it 'is successful' do
expect(result[:success]).to be_truthy
end
end
end
end

View File

@ -2251,6 +2251,22 @@ describe API::Projects do
put api("/projects/#{project3.id}", user4), params: project_param
expect(response).to have_gitlab_http_status(403)
end
it 'updates container_expiration_policy' do
project_param = {
container_expiration_policy_attributes: {
cadence: '1month',
keep_n: 1
}
}
put api("/projects/#{project3.id}", user4), params: project_param
expect(response).to have_gitlab_http_status(200)
expect(json_response['container_expiration_policy']['cadence']).to eq('1month')
expect(json_response['container_expiration_policy']['keep_n']).to eq(1)
end
end
context 'when authenticated as project developer' do

View File

@ -27,6 +27,8 @@ module CycleAnalyticsHelpers
scenarios = combinations_of_start_time_conditions.product(combinations_of_end_time_conditions)
scenarios.each do |start_time_conditions, end_time_conditions|
let_it_be(:other_project) { create(:project, :repository) }
context "start condition: #{start_time_conditions.map(&:first).to_sentence}" do
context "end condition: #{end_time_conditions.map(&:first).to_sentence}" do
it "finds the median of available durations between the two conditions", :sidekiq_might_not_need_inline do
@ -56,8 +58,6 @@ module CycleAnalyticsHelpers
end
context "when the data belongs to another project" do
let(:other_project) { create(:project, :repository) }
it "returns nil" do
# Use a stub to "trick" the data/condition functions
# into using another project. This saves us from having to

View File

@ -105,12 +105,15 @@ module GraphqlHelpers
end
def query_graphql_field(name, attributes = {}, fields = nil)
fields ||= all_graphql_fields_for(name.classify)
attributes = attributes_to_graphql(attributes)
attributes = "(#{attributes})" if attributes.present?
field_params = if attributes.present?
"(#{attributes_to_graphql(attributes)})"
else
''
end
<<~QUERY
#{name}#{attributes}
#{wrap_fields(fields)}
#{GraphqlHelpers.fieldnamerize(name.to_s)}#{field_params}
#{wrap_fields(fields || all_graphql_fields_for(name.to_s.classify))}
QUERY
end
@ -301,6 +304,17 @@ module GraphqlHelpers
def global_id_of(model)
model.to_global_id.to_s
end
def missing_required_argument(path, argument)
a_hash_including(
'path' => ['query'].concat(path),
'extensions' => a_hash_including('code' => 'missingRequiredArguments', 'arguments' => argument.to_s)
)
end
def custom_graphql_error(path, msg)
a_hash_including('path' => path, 'message' => msg)
end
end
# This warms our schema, doing this as part of loading the helpers to avoid

View File

@ -11,8 +11,22 @@ RSpec::Matchers.define :have_graphql_fields do |*expected|
Array.wrap(expected).map { |name| GraphqlHelpers.fieldnamerize(name) }
end
@allow_extra = false
chain :only do
@allow_extra = false
end
chain :at_least do
@allow_extra = true
end
match do |kls|
expect(kls.fields.keys).to contain_exactly(*expected_field_names)
if @allow_extra
expect(kls.fields.keys).to include(*expected_field_names)
else
expect(kls.fields.keys).to contain_exactly(*expected_field_names)
end
end
failure_message do |kls|
@ -22,7 +36,7 @@ RSpec::Matchers.define :have_graphql_fields do |*expected|
message = []
message << "is missing fields: <#{missing.inspect}>" if missing.any?
message << "contained unexpected fields: <#{extra.inspect}>" if extra.any?
message << "contained unexpected fields: <#{extra.inspect}>" if extra.any? && !@allow_extra
message.join("\n")
end

View File

@ -0,0 +1,17 @@
# frozen_string_literal: true
require 'spec_helper'
# Shared example for legal queries that are expected to return nil.
# Requires the following let bindings to be defined:
# - post_query: action to send the query
# - path: array of keys from query root to the result
shared_examples 'a failure to find anything' do
it 'finds nothing' do
post_query
data = graphql_data.dig(*path)
expect(data).to be_nil
end
end