Commit e0b84f4b authored by GitLab Bot's avatar GitLab Bot

Add latest changes from gitlab-org/gitlab@master

parent 73391dcc
...@@ -86,4 +86,4 @@ jsdoc/ ...@@ -86,4 +86,4 @@ jsdoc/
.projections.json .projections.json
/qa/.rakeTasks /qa/.rakeTasks
webpack-dev-server.json webpack-dev-server.json
.nvimrc /.nvimrc
...@@ -3,11 +3,13 @@ import { mapGetters } from 'vuex'; ...@@ -3,11 +3,13 @@ import { mapGetters } from 'vuex';
import Icon from '~/vue_shared/components/icon.vue'; import Icon from '~/vue_shared/components/icon.vue';
import store from '~/pipelines/stores/test_reports'; import store from '~/pipelines/stores/test_reports';
import { __ } from '~/locale'; import { __ } from '~/locale';
import SmartVirtualList from '~/vue_shared/components/smart_virtual_list.vue';
export default { export default {
name: 'TestsSuiteTable', name: 'TestsSuiteTable',
components: { components: {
Icon, Icon,
SmartVirtualList,
}, },
store, store,
props: { props: {
...@@ -23,6 +25,8 @@ export default { ...@@ -23,6 +25,8 @@ export default {
return this.getSuiteTests.length > 0; return this.getSuiteTests.length > 0;
}, },
}, },
maxShownRows: 30,
typicalRowHeight: 75,
}; };
</script> </script>
...@@ -34,7 +38,7 @@ export default { ...@@ -34,7 +38,7 @@ export default {
</div> </div>
</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="row" class="gl-responsive-table-row table-row-header font-weight-bold fgray">
<div role="rowheader" class="table-section section-20"> <div role="rowheader" class="table-section section-20">
{{ __('Class') }} {{ __('Class') }}
...@@ -53,52 +57,58 @@ export default { ...@@ -53,52 +57,58 @@ export default {
</div> </div>
</div> </div>
<div <smart-virtual-list
v-for="(testCase, index) in getSuiteTests" :length="getSuiteTests.length"
:key="index" :remain="$options.maxShownRows"
class="gl-responsive-table-row rounded align-items-md-start mt-sm-3 js-case-row" :size="$options.typicalRowHeight"
> >
<div class="table-section section-20 section-wrap"> <div
<div role="rowheader" class="table-mobile-header">{{ __('Class') }}</div> v-for="(testCase, index) in getSuiteTests"
<div class="table-mobile-content pr-md-1">{{ testCase.classname }}</div> :key="index"
</div> 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 class="table-section section-20 section-wrap">
<div role="rowheader" class="table-mobile-header">{{ __('Name') }}</div> <div role="rowheader" class="table-mobile-header">{{ __('Name') }}</div>
<div class="table-mobile-content">{{ testCase.name }}</div> <div class="table-mobile-content">{{ testCase.name }}</div>
</div> </div>
<div class="table-section section-10 section-wrap"> <div class="table-section section-10 section-wrap">
<div role="rowheader" class="table-mobile-header">{{ __('Status') }}</div> <div role="rowheader" class="table-mobile-header">{{ __('Status') }}</div>
<div class="table-mobile-content text-center"> <div class="table-mobile-content text-center">
<div <div
class="add-border ci-status-icon d-flex align-items-center justify-content-end justify-content-md-center" class="add-border ci-status-icon d-flex align-items-center justify-content-end justify-content-md-center"
:class="`ci-status-icon-${testCase.status}`" :class="`ci-status-icon-${testCase.status}`"
> >
<icon :size="24" :name="testCase.icon" /> <icon :size="24" :name="testCase.icon" />
</div>
</div> </div>
</div> </div>
</div>
<div class="table-section flex-grow-1"> <div class="table-section flex-grow-1">
<div role="rowheader" class="table-mobile-header">{{ __('Trace'), }}</div> <div role="rowheader" class="table-mobile-header">{{ __('Trace'), }}</div>
<div class="table-mobile-content"> <div class="table-mobile-content">
<pre <pre
v-if="testCase.system_output" v-if="testCase.system_output"
class="build-trace build-trace-rounded text-left" class="build-trace build-trace-rounded text-left"
><code class="bash p-0">{{testCase.system_output}}</code></pre> ><code class="bash p-0">{{testCase.system_output}}</code></pre>
</div>
</div> </div>
</div>
<div class="table-section section-10 section-wrap"> <div class="table-section section-10 section-wrap">
<div role="rowheader" class="table-mobile-header"> <div role="rowheader" class="table-mobile-header">
{{ __('Duration') }} {{ __('Duration') }}
</div> </div>
<div class="table-mobile-content text-right"> <div class="table-mobile-content text-right pr-sm-1">
{{ testCase.formattedTime }} {{ testCase.formattedTime }}
</div>
</div> </div>
</div> </div>
</div> </smart-virtual-list>
</div> </div>
<div v-else> <div v-else>
......
...@@ -2,9 +2,13 @@ ...@@ -2,9 +2,13 @@
import { mapGetters } from 'vuex'; import { mapGetters } from 'vuex';
import { s__ } from '~/locale'; import { s__ } from '~/locale';
import store from '~/pipelines/stores/test_reports'; import store from '~/pipelines/stores/test_reports';
import SmartVirtualList from '~/vue_shared/components/smart_virtual_list.vue';
export default { export default {
name: 'TestsSummaryTable', name: 'TestsSummaryTable',
components: {
SmartVirtualList,
},
store, store,
props: { props: {
heading: { heading: {
...@@ -24,6 +28,8 @@ export default { ...@@ -24,6 +28,8 @@ export default {
this.$emit('row-click', suite); this.$emit('row-click', suite);
}, },
}, },
maxShownRows: 20,
typicalRowHeight: 55,
}; };
</script> </script>
...@@ -35,7 +41,7 @@ export default { ...@@ -35,7 +41,7 @@ export default {
</div> </div>
</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="row" class="gl-responsive-table-row table-row-header font-weight-bold">
<div role="rowheader" class="table-section section-25 pl-3"> <div role="rowheader" class="table-section section-25 pl-3">
{{ __('Suite') }} {{ __('Suite') }}
...@@ -60,66 +66,72 @@ export default { ...@@ -60,66 +66,72 @@ export default {
</div> </div>
</div> </div>
<div <smart-virtual-list
v-for="(testSuite, index) in getTestSuites" :length="getTestSuites.length"
:key="index" :remain="$options.maxShownRows"
role="row" :size="$options.typicalRowHeight"
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
<div role="rowheader" class="table-mobile-header font-weight-bold"> v-for="(testSuite, index) in getTestSuites"
{{ __('Suite') }} :key="index"
</div> role="row"
<div class="table-mobile-content underline cgray pl-3"> class="gl-responsive-table-row gl-responsive-table-row-clickable test-reports-summary-row rounded cursor-pointer js-suite-row"
{{ testSuite.name }} @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>
</div>
<div class="table-section section-25"> <div class="table-section section-25">
<div role="rowheader" class="table-mobile-header font-weight-bold"> <div role="rowheader" class="table-mobile-header font-weight-bold">
{{ __('Duration') }} {{ __('Duration') }}
</div>
<div class="table-mobile-content text-md-left">
{{ testSuite.formattedTime }}
</div>
</div> </div>
<div class="table-mobile-content text-md-left">
{{ testSuite.formattedTime }}
</div>
</div>
<div class="table-section section-10 text-center"> <div class="table-section section-10 text-center">
<div role="rowheader" class="table-mobile-header font-weight-bold"> <div role="rowheader" class="table-mobile-header font-weight-bold">
{{ __('Failed') }} {{ __('Failed') }}
</div>
<div class="table-mobile-content">{{ testSuite.failed_count }}</div>
</div> </div>
<div class="table-mobile-content">{{ testSuite.failed_count }}</div>
</div>
<div class="table-section section-10 text-center"> <div class="table-section section-10 text-center">
<div role="rowheader" class="table-mobile-header font-weight-bold"> <div role="rowheader" class="table-mobile-header font-weight-bold">
{{ __('Errors') }} {{ __('Errors') }}
</div>
<div class="table-mobile-content">{{ testSuite.error_count }}</div>
</div> </div>
<div class="table-mobile-content">{{ testSuite.error_count }}</div>
</div>
<div class="table-section section-10 text-center"> <div class="table-section section-10 text-center">
<div role="rowheader" class="table-mobile-header font-weight-bold"> <div role="rowheader" class="table-mobile-header font-weight-bold">
{{ __('Skipped') }} {{ __('Skipped') }}
</div>
<div class="table-mobile-content">{{ testSuite.skipped_count }}</div>
</div> </div>
<div class="table-mobile-content">{{ testSuite.skipped_count }}</div>
</div>
<div class="table-section section-10 text-center"> <div class="table-section section-10 text-center">
<div role="rowheader" class="table-mobile-header font-weight-bold"> <div role="rowheader" class="table-mobile-header font-weight-bold">
{{ __('Passed') }} {{ __('Passed') }}
</div>
<div class="table-mobile-content">{{ testSuite.success_count }}</div>
</div> </div>
<div class="table-mobile-content">{{ testSuite.success_count }}</div>
</div>
<div class="table-section section-10 text-right pr-md-3"> <div class="table-section section-10 text-right pr-md-3">
<div role="rowheader" class="table-mobile-header font-weight-bold"> <div role="rowheader" class="table-mobile-header font-weight-bold">
{{ __('Total') }} {{ __('Total') }}
</div>
<div class="table-mobile-content">{{ testSuite.total_count }}</div>
</div> </div>
<div class="table-mobile-content">{{ testSuite.total_count }}</div>
</div> </div>
</div> </smart-virtual-list>
</div> </div>
<div v-else> <div v-else>
......
...@@ -57,13 +57,55 @@ class GitlabSchema < GraphQL::Schema ...@@ -57,13 +57,55 @@ class GitlabSchema < GraphQL::Schema
object.to_global_id object.to_global_id
end 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 = {}) def object_from_id(global_id, ctx = {})
gid = parse_gid(global_id, ctx)
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)
gid.model_class.lazy_find(gid.model_id)
else
gid.find
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] expected_type = ctx[:expected_type]
gid = GlobalID.parse(global_id) gid = GlobalID.parse(global_id)
unless gid raise Gitlab::Graphql::Errors::ArgumentError, "#{global_id} is not a valid GitLab id." 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) if expected_type && !gid.model_class.ancestors.include?(expected_type)
vars = { global_id: global_id, expected_type: expected_type } vars = { global_id: global_id, expected_type: expected_type }
...@@ -71,13 +113,7 @@ class GitlabSchema < GraphQL::Schema ...@@ -71,13 +113,7 @@ class GitlabSchema < GraphQL::Schema
raise Gitlab::Graphql::Errors::ArgumentError, msg raise Gitlab::Graphql::Errors::ArgumentError, msg
end end
if gid.model_class < ApplicationRecord gid
Gitlab::Graphql::Loaders::BatchModelLoader.new(gid.model_class, gid.model_id).find
elsif gid.model_class.respond_to?(:lazy_find)
gid.model_class.lazy_find(gid.model_id)
else
gid.find
end
end end
private private
......
...@@ -5,7 +5,7 @@ require 'securerandom' ...@@ -5,7 +5,7 @@ require 'securerandom'
module Clusters module Clusters
module Applications module Applications
class Jupyter < ApplicationRecord class Jupyter < ApplicationRecord
VERSION = '0.9-174bbd5' VERSION = '0.9.0-beta.2'
self.table_name = 'clusters_applications_jupyter' self.table_name = 'clusters_applications_jupyter'
......
...@@ -19,15 +19,20 @@ class ExternalWikiService < Service ...@@ -19,15 +19,20 @@ class ExternalWikiService < Service
def fields 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 end
def execute(_data) def execute(_data)
@response = Gitlab::HTTP.get(properties['external_wiki_url'], verify: true) rescue nil response = Gitlab::HTTP.get(properties['external_wiki_url'], verify: true)
if @response != 200 response.body if response.code == 200
nil rescue
end nil
end end
def self.supported_events def self.supported_events
......
---
title: Container expiration policies can be updated with the project api
merge_request: 22180
author:
type: added
---
title: Check both DEPENDENCY_SCANNING_DISABLED and DS_DISABLE_DIND when executing Dependency Scanning job template
merge_request: 22172
author:
type: fixed
---
title: Added smart virtual list component to test reports to enhance rendering performance
merge_request: 22381
author:
type: performance
---
title: Document MAVEN_CLI_OPTS defaults for maven project dependency scanning and update when the variable is used
merge_request: 22126
author:
type: added
---
title: Update jupyterhub chart
merge_request: 22127
author:
type: changed
...@@ -25,15 +25,22 @@ The most common architecture for Praefect is simplified in the diagram below: ...@@ -25,15 +25,22 @@ The most common architecture for Praefect is simplified in the diagram below:
```mermaid ```mermaid
graph TB graph TB
GitLab --> Praefect; GitLab --> Praefect;
Praefect --> Gitaly-1; Praefect --- PostgreSQL;
Praefect --> Gitaly-2; Praefect --> Gitaly1;
Praefect --> Gitaly-3; Praefect --> Gitaly2;
Praefect --> Gitaly3;
``` ```
Where `GitLab` is the collection of clients that can request Git operations. Where `GitLab` is the collection of clients that can request Git operations.
The Praefect node has three storage nodes attached. Praefect itself doesn't 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`. 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. 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 In the example below we will use a separate server, but the optimal configuration
for Praefect is still being determined. for Praefect is still being determined.
...@@ -62,6 +69,53 @@ We need to manage the following secrets and make them match across hosts: ...@@ -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 `PRAEFECT_EXTERNAL_TOKEN` because Gitaly clients must not be able to
access internal nodes of the Praefect cluster directly; that could access internal nodes of the Praefect cluster directly; that could
lead to data loss. 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 #### Praefect
...@@ -118,10 +172,39 @@ praefect['virtual_storages'] = { ...@@ -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). 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 #### Gitaly
Next we will configure each Gitaly server assigned to Praefect. Configuration for these Next we will configure each Gitaly server assigned to Praefect. Configuration for these
......
...@@ -631,7 +631,7 @@ mounting the docker-daemon and setting `privileged = false` in the Runner's ...@@ -631,7 +631,7 @@ mounting the docker-daemon and setting `privileged = false` in the Runner's
```toml ```toml
[runners.docker] [runners.docker]
image = "ruby:2.1" image = "ruby:2.6"
privileged = false privileged = false
volumes = ["/var/run/docker.sock:/var/run/docker.sock", "/cache"] volumes = ["/var/run/docker.sock:/var/run/docker.sock", "/cache"]
``` ```
......
...@@ -529,9 +529,9 @@ type CreateSnippetPayload { ...@@ -529,9 +529,9 @@ type CreateSnippetPayload {
snippet: Snippet snippet: Snippet
} }
type Design implements Noteable { type Design implements DesignFields & Noteable {
""" """
Diff refs of the design The diff refs for this design
""" """
diffRefs: DiffRefs! diffRefs: DiffRefs!
...@@ -561,33 +561,32 @@ type Design implements Noteable { ...@@ -561,33 +561,32 @@ type Design implements Noteable {
): DiscussionConnection! ): DiscussionConnection!
""" """
Type of change made to the design at the version specified by the `atVersion` How this design was changed in the current version
argument if supplied. Defaults to the latest version
""" """
event: DesignVersionEvent! event: DesignVersionEvent!
""" """
Filename of the design file The filename of the design
""" """
filename: String! filename: String!
""" """
Full path of the design file The full path to the design file
""" """
fullPath: String! fullPath: String!
""" """
ID of the design The ID of this design
""" """
id: ID! id: ID!
""" """
Image of the design The URL of the image
""" """
image: String! image: String!
""" """
Issue associated with the design The issue the design belongs to
""" """
issue: Issue! issue: Issue!
...@@ -617,17 +616,17 @@ type Design implements Noteable { ...@@ -617,17 +616,17 @@ type Design implements Noteable {
): NoteConnection! ): NoteConnection!
""" """
Total count of user-created notes for the design The total count of user-created notes for this design
""" """
notesCount: Int! notesCount: Int!
""" """
Project associated with the design The project the design belongs to
""" """
project: Project! project: Project!
""" """
All versions related to the design, ordered newest first All versions related to this design ordered newest first
""" """
versions( versions(
""" """
...@@ -765,6 +764,53 @@ type DesignEdge { ...@@ -765,6 +764,53 @@ type DesignEdge {
node: Design 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 Autogenerated input type of DesignManagementDelete
""" """
......
...@@ -10350,7 +10350,7 @@ ...@@ -10350,7 +10350,7 @@
"fields": [ "fields": [
{ {
"name": "diffRefs", "name": "diffRefs",
"description": "Diff refs of the design", "description": "The diff refs for this design",
"args": [ "args": [
], ],
...@@ -10425,7 +10425,7 @@ ...@@ -10425,7 +10425,7 @@
}, },
{ {
"name": "event", "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": [ "args": [
], ],
...@@ -10443,7 +10443,7 @@ ...@@ -10443,7 +10443,7 @@
}, },
{ {
"name": "filename", "name": "filename",
"description": "Filename of the design file", "description": "The filename of the design",
"args": [ "args": [
], ],
...@@ -10461,7 +10461,7 @@ ...@@ -10461,7 +10461,7 @@
}, },
{ {
"name": "fullPath", "name": "fullPath",
"description": "Full path of the design file", "description": "The full path to the design file",
"args": [ "args": [
], ],
...@@ -10479,7 +10479,7 @@ ...@@ -10479,7 +10479,7 @@
}, },
{ {
"name": "id", "name": "id",
"description": "ID of the design", "description": "The ID of this design",
"args": [ "args": [
], ],
...@@ -10497,7 +10497,7 @@ ...@@ -10497,7 +10497,7 @@
}, },
{ {
"name": "image", "name": "image",
"description": "Image of the design", "description": "The URL of the image",
"args": [ "args": [
], ],
...@@ -10515,7 +10515,7 @@ ...@@ -10515,7 +10515,7 @@
}, },
{ {
"name": "issue", "name": "issue",
"description": "Issue associated with the design", "description": "The issue the design belongs to",
"args": [ "args": [
], ],
...@@ -10590,7 +10590,7 @@ ...@@ -10590,7 +10590,7 @@
}, },
{ {
"name": "notesCount", "name": "notesCount",
"description": "Total count of user-created notes for the design", "description": "The total count of user-created notes for this design",
"args": [ "args": [
], ],
...@@ -10608,7 +10608,7 @@ ...@@ -10608,7 +10608,7 @@
}, },
{ {
"name": "project", "name": "project",
"description": "Project associated with the design", "description": "The project the design belongs to",
"args": [ "args": [
], ],
...@@ -10626,7 +10626,7 @@ ...@@ -10626,7 +10626,7 @@
}, },
{ {
"name": "versions", "name": "versions",
"description": "All versions related to the design, ordered newest first", "description": "All versions related to this design ordered newest first",
"args": [ "args": [
{ {
"name": "after", "name": "after",
...@@ -10688,11 +10688,195 @@ ...@@ -10688,11 +10688,195 @@
"kind": "INTERFACE", "kind": "INTERFACE",
"name": "Noteable", "name": "Noteable",
"ofType": null "ofType": null
},
{
"kind": "INTERFACE",
"name": "DesignFields",
"ofType": null
} }
], ],
"enumValues": null, "enumValues": null,
"possibleTypes": 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", "kind": "ENUM",
"name": "DesignVersionEvent", "name": "DesignVersionEvent",
......
...@@ -104,15 +104,15 @@ The API can be explored interactively using the [GraphiQL IDE](../index.md#graph ...@@ -104,15 +104,15 @@ The API can be explored interactively using the [GraphiQL IDE](../index.md#graph
| Name | Type | Description | | Name | Type | Description |
| --- | ---- | ---------- | | --- | ---- | ---------- |
| `id` | ID! | ID of the design | | `id` | ID! | The ID of this design |
| `project` | Project! | Project associated with the design | | `project` | Project! | The project the design belongs to |
| `issue` | Issue! | Issue associated with the design | | `issue` | Issue! | The issue the design belongs to |
| `notesCount` | Int! | Total count of user-created notes for the design | | `filename` | String! | The filename of the design |
| `filename` | String! | Filename of the design file | | `fullPath` | String! | The full path to the design file |
| `fullPath` | String! | Full path of the design file | | `image` | String! | The URL of the image |
| `event` | DesignVersionEvent! | Type of change made to the design at the version specified by the `atVersion` argument if supplied. Defaults to the latest version | | `diffRefs` | DiffRefs! | The diff refs for this design |
| `image` | String! | Image of the design | | `event` | DesignVersionEvent! | How this design was changed in the current version |
| `diffRefs` | DiffRefs! | Diff refs of the design | | `notesCount` | Int! | The total count of user-created notes for this design |
### DesignCollection ### DesignCollection
......
...@@ -761,6 +761,14 @@ GET /projects/:id ...@@ -761,6 +761,14 @@ GET /projects/:id
"snippets_enabled": false, "snippets_enabled": false,
"resolve_outdated_diff_discussions": false, "resolve_outdated_diff_discussions": false,
"container_registry_enabled": 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", "created_at": "2013-09-30T13:46:02Z",
"last_activity_at": "2013-09-30T13:46:02Z", "last_activity_at": "2013-09-30T13:46:02Z",
"creator_id": 3, "creator_id": 3,
...@@ -986,6 +994,7 @@ POST /projects ...@@ -986,6 +994,7 @@ POST /projects
| `snippets_access_level` | string | no | One of `disabled`, `private` or `enabled` | | `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 | | `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_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 | | `shared_runners_enabled` | boolean | no | Enable shared runners for this project |
| `visibility` | string | no | See [project visibility level](#project-visibility-level) | | `visibility` | string | no | See [project visibility level](#project-visibility-level) |
| `import_url` | string | no | URL to import repository from | | `import_url` | string | no | URL to import repository from |
...@@ -1115,6 +1124,7 @@ PUT /projects/:id ...@@ -1115,6 +1124,7 @@ PUT /projects/:id
| `snippets_access_level` | string | no | One of `disabled`, `private` or `enabled` | | `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 | | `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_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 | | `shared_runners_enabled` | boolean | no | Enable shared runners for this project |
| `visibility` | string | no | See [project visibility level](#project-visibility-level) | | `visibility` | string | no | See [project visibility level](#project-visibility-level) |
| `import_url` | string | no | URL to import repository from | | `import_url` | string | no | URL to import repository from |
......
...@@ -32,14 +32,14 @@ A one-line example can be seen below: ...@@ -32,14 +32,14 @@ A one-line example can be seen below:
sudo gitlab-runner register \ sudo gitlab-runner register \
--url "https://gitlab.example.com/" \ --url "https://gitlab.example.com/" \
--registration-token "PROJECT_REGISTRATION_TOKEN" \ --registration-token "PROJECT_REGISTRATION_TOKEN" \
--description "docker-ruby-2.1" \ --description "docker-ruby:2.6" \
--executor "docker" \ --executor "docker" \
--docker-image ruby:2.1 \ --docker-image ruby:2.6 \
--docker-services postgres:latest \ --docker-services postgres:latest \
--docker-services mysql: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 services, `postgres:latest` and `mysql:latest`, both of which will be
accessible during the build process. accessible during the build process.
...@@ -194,7 +194,7 @@ services that you want to use during build time: ...@@ -194,7 +194,7 @@ services that you want to use during build time:
```yaml ```yaml
default: default:
image: ruby:2.2 image: ruby:2.6
services: services:
- postgres:9.3 - postgres:9.3
...@@ -214,15 +214,15 @@ default: ...@@ -214,15 +214,15 @@ default:
before_script: before_script:
- bundle install - bundle install
test:2.1: test:2.6:
image: ruby:2.1 image: ruby:2.6
services: services:
- postgres:9.3 - postgres:9.3
script: script:
- bundle exec rake spec - bundle exec rake spec
test:2.2: test:2.7:
image: ruby:2.2 image: ruby:2.7
services: services:
- postgres:9.4 - postgres:9.4
script: script:
...@@ -235,7 +235,7 @@ for `image` and `services`: ...@@ -235,7 +235,7 @@ for `image` and `services`:
```yaml ```yaml
default: default:
image: image:
name: ruby:2.2 name: ruby:2.6
entrypoint: ["/bin/bash"] entrypoint: ["/bin/bash"]
services: services:
...@@ -277,7 +277,7 @@ services: ...@@ -277,7 +277,7 @@ services:
command: ["postgres"] command: ["postgres"]
image: image:
name: ruby:2.2 name: ruby:2.6
entrypoint: ["/bin/bash"] entrypoint: ["/bin/bash"]
before_script: before_script:
...@@ -773,7 +773,7 @@ time. ...@@ -773,7 +773,7 @@ time.
1. Create any service container: `mysql`, `postgresql`, `mongodb`, `redis`. 1. Create any service container: `mysql`, `postgresql`, `mongodb`, `redis`.
1. Create cache container to store all volumes as defined in `config.toml` and 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. Create build container and link any service container to build container.
1. Start build container and send job script to the container. 1. Start build container and send job script to the container.
1. Run job script. 1. Run job script.
...@@ -818,11 +818,11 @@ Finally, create a build container by executing the `build_script` file we ...@@ -818,11 +818,11 @@ Finally, create a build container by executing the `build_script` file we
created earlier: created earlier:
```sh ```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 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 piped using STDIN to the bash interpreter which in turn executes the
`build_script` in the `build` container. `build_script` in the `build` container.
......
...@@ -71,12 +71,12 @@ gitlab-runner register \ ...@@ -71,12 +71,12 @@ gitlab-runner register \
--non-interactive \ --non-interactive \
--url "https://gitlab.com/" \ --url "https://gitlab.com/" \
--registration-token "PROJECT_REGISTRATION_TOKEN" \ --registration-token "PROJECT_REGISTRATION_TOKEN" \
--description "ruby-2.2" \ --description "ruby:2.6" \
--executor "docker" \ --executor "docker" \
--docker-image ruby:2.2 \ --docker-image ruby:2.6 \
--docker-postgres latest --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. To access the PostgreSQL database, connect to `host: postgres` as user `postgres` with no password.
...@@ -3645,7 +3645,7 @@ having their own custom `script` defined: ...@@ -3645,7 +3645,7 @@ having their own custom `script` defined:
```yaml ```yaml
.job_template: &job_definition # Hidden key that defines an anchor named 'job_definition' .job_template: &job_definition # Hidden key that defines an anchor named 'job_definition'
image: ruby:2.1 image: ruby:2.6
services: services:
- postgres - postgres
- redis - redis
...@@ -3667,13 +3667,13 @@ given hash into the current one", and `*` includes the named anchor ...@@ -3667,13 +3667,13 @@ given hash into the current one", and `*` includes the named anchor
```yaml ```yaml
.job_template: .job_template:
image: ruby:2.1 image: ruby:2.6
services: services:
- postgres - postgres
- redis - redis
test1: test1:
image: ruby:2.1 image: ruby:2.6
services: services:
- postgres - postgres
- redis - redis
...@@ -3681,7 +3681,7 @@ test1: ...@@ -3681,7 +3681,7 @@ test1:
- test1 project - test1 project
test2: test2:
image: ruby:2.1 image: ruby:2.6
services: services:
- postgres - postgres
- redis - redis
......
...@@ -382,7 +382,7 @@ end ...@@ -382,7 +382,7 @@ end
## String Freezing ## String Freezing
In recent Ruby versions calling `freeze` on a String leads to it being allocated 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: "foo" String once:
```ruby ```ruby
......
...@@ -146,7 +146,7 @@ using environment variables. ...@@ -146,7 +146,7 @@ using environment variables.
| `PIP_INDEX_URL` | Base URL of Python Package Index (default `https://pypi.org/simple`). | | `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_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. | | `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.| | `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 ### Using private Maven repos
......
--- ---
last_updated: 2019-06-04 last_updated: 2020-01-06
type: reference, howto type: reference, howto
--- ---
...@@ -158,7 +158,7 @@ first thing GitLab Runner will look for in your `.gitlab-ci.yml` is a ...@@ -158,7 +158,7 @@ first thing GitLab Runner will look for in your `.gitlab-ci.yml` is a
your container to run that script: your container to run that script:
```yaml ```yaml
image: ruby:2.3 image: ruby:2.7
pages: pages:
script: script:
...@@ -170,9 +170,9 @@ pages: ...@@ -170,9 +170,9 @@ pages:
``` ```
In this case, you're telling the Runner to pull this image, which 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 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 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 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 ...@@ -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`: to only perform that _job_ called `pages` on the `master` branch `only`:
```yaml ```yaml
image: ruby:2.3 image: ruby:2.6
pages: pages:
script: script:
...@@ -221,7 +221,7 @@ and deploy. To specify which stage your _job_ is running, ...@@ -221,7 +221,7 @@ and deploy. To specify which stage your _job_ is running,
simply add another line to your CI: simply add another line to your CI:
```yaml ```yaml
image: ruby:2.3 image: ruby:2.6
pages: pages:
stage: deploy stage: deploy
...@@ -244,7 +244,7 @@ let's add another task (_job_) to our CI, telling it to ...@@ -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: test every push to other branches, `except` the `master` branch:
```yaml ```yaml
image: ruby:2.3 image: ruby:2.6
pages: pages:
stage: deploy stage: deploy
...@@ -294,7 +294,7 @@ every single _job_. In our example, notice that we run ...@@ -294,7 +294,7 @@ every single _job_. In our example, notice that we run
We don't need to repeat it: We don't need to repeat it:
```yaml ```yaml
image: ruby:2.3 image: ruby:2.6
before_script: before_script:
- bundle install - bundle install
...@@ -329,7 +329,7 @@ cache Jekyll dependencies in a `vendor` directory ...@@ -329,7 +329,7 @@ cache Jekyll dependencies in a `vendor` directory
when we run `bundle install`: when we run `bundle install`:
```yaml ```yaml
image: ruby:2.3 image: ruby:2.6
cache: cache:
paths: paths:
......
--- ---
type: reference type: reference
last_updated: 2018-06-04 last_updated: 2020-01-06
--- ---
# Exploring GitLab Pages # Exploring GitLab Pages
...@@ -156,7 +156,7 @@ Below is a copy of `.gitlab-ci.yml` where the most significant line is the last ...@@ -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: one, specifying to execute everything in the `pages` branch:
``` ```
image: ruby:2.1 image: ruby:2.6
pages: pages:
script: script:
......
...@@ -178,6 +178,15 @@ module API ...@@ -178,6 +178,15 @@ module API
expose :only_protected_branches expose :only_protected_branches
end 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 class ProjectImportStatus < ProjectIdentity
expose :import_status expose :import_status
...@@ -276,6 +285,8 @@ module API ...@@ -276,6 +285,8 @@ module API
expose :owner, using: Entities::UserBasic, unless: ->(project, options) { project.group } expose :owner, using: Entities::UserBasic, unless: ->(project, options) { project.group }
expose :resolve_outdated_diff_discussions expose :resolve_outdated_diff_discussions
expose :container_registry_enabled 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 # Expose old field names with the new permissions methods to keep API compatible
# TODO: remove in API v5, replaced by *_access_level # TODO: remove in API v5, replaced by *_access_level
...@@ -341,6 +352,7 @@ module API ...@@ -341,6 +352,7 @@ module API
# MR describing the solution: https://gitlab.com/gitlab-org/gitlab-foss/merge_requests/20555 # MR describing the solution: https://gitlab.com/gitlab-org/gitlab-foss/merge_requests/20555
super(projects_relation).preload(:group) super(projects_relation).preload(:group)
.preload(:ci_cd_settings) .preload(:ci_cd_settings)
.preload(:container_expiration_policy)
.preload(:auto_devops) .preload(:auto_devops)
.preload(project_group_links: { group: :route }, .preload(project_group_links: { group: :route },
fork_network: :root_project, fork_network: :root_project,
......
...@@ -32,6 +32,9 @@ module API ...@@ -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 :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 :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_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 :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 :visibility, type: String, values: Gitlab::VisibilityLevel.string_values, desc: 'The visibility of the project.'
optional :public_builds, type: Boolean, desc: 'Perform public builds' optional :public_builds, type: Boolean, desc: 'Perform public builds'
...@@ -72,6 +75,14 @@ module API ...@@ -72,6 +75,14 @@ module API
params :optional_update_params_ee do params :optional_update_params_ee do
end 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 def self.update_params_at_least_one_of
[ [
:auto_devops_enabled, :auto_devops_enabled,
...@@ -84,6 +95,7 @@ module API ...@@ -84,6 +95,7 @@ module API
:ci_config_path, :ci_config_path,
:ci_default_git_depth, :ci_default_git_depth,
:container_registry_enabled, :container_registry_enabled,
:container_expiration_policy_attributes,
:default_branch, :default_branch,
:description, :description,
:autoclose_referenced_issues, :autoclose_referenced_issues,
......
...@@ -77,6 +77,7 @@ dependency_scanning: ...@@ -77,6 +77,7 @@ dependency_scanning:
services: [] services: []
except: except:
variables: variables:
- $DEPENDENCY_SCANNING_DISABLED
- $DS_DISABLE_DIND == 'false' - $DS_DISABLE_DIND == 'false'
script: script:
- /analyzer run - /analyzer run
......
# 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
...@@ -5121,6 +5121,9 @@ msgstr "" ...@@ -5121,6 +5121,9 @@ msgstr ""
msgid "Could not delete chat nickname %{chat_name}." msgid "Could not delete chat nickname %{chat_name}."
msgstr "" msgstr ""
msgid "Could not delete project ID"
msgstr ""
msgid "Could not fetch projects" msgid "Could not fetch projects"
msgstr "" msgstr ""
...@@ -7122,6 +7125,9 @@ msgstr "" ...@@ -7122,6 +7125,9 @@ msgstr ""
msgid "Error deleting %{issuableType}" msgid "Error deleting %{issuableType}"
msgstr "" msgstr ""
msgid "Error deleting project. Check logs for error details."
msgstr ""
msgid "Error details" msgid "Error details"
msgstr "" msgstr ""
...@@ -16262,6 +16268,9 @@ 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." 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 "" msgstr ""
msgid "Self monitoring project does not exist"
msgstr ""
msgid "Self-monitoring is not enabled on this GitLab server, contact your administrator." msgid "Self-monitoring is not enabled on this GitLab server, contact your administrator."
msgstr "" msgstr ""
......
...@@ -124,14 +124,26 @@ describe GitlabSchema do ...@@ -124,14 +124,26 @@ describe GitlabSchema do
describe '.object_from_id' do describe '.object_from_id' do
context 'for subclasses of `ApplicationRecord`' do context 'for subclasses of `ApplicationRecord`' do
it 'returns the correct record' do let_it_be(:user) { create(:user) }
user = create(:user)
it 'returns the correct record' do
result = described_class.object_from_id(user.to_global_id.to_s) result = described_class.object_from_id(user.to_global_id.to_s)
expect(result.sync).to eq(user) expect(result.sync).to eq(user)
end 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 it 'batchloads the queries' do
user1 = create(:user) user1 = create(:user)
user2 = create(:user) user2 = create(:user)
......
...@@ -7,7 +7,16 @@ describe GitlabSchema.types['Query'] do ...@@ -7,7 +7,16 @@ describe GitlabSchema.types['Query'] do
expect(described_class.graphql_name).to eq('Query') expect(described_class.graphql_name).to eq('Query')
end 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 describe 'namespace field' do
subject { described_class.fields['namespace'] } subject { described_class.fields['namespace'] }
......
# 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
...@@ -57,7 +57,8 @@ describe Clusters::Applications::Jupyter do ...@@ -57,7 +57,8 @@ describe Clusters::Applications::Jupyter do
it 'is initialized with 4 arguments' do it 'is initialized with 4 arguments' do
expect(subject.name).to eq('jupyter') expect(subject.name).to eq('jupyter')
expect(subject.chart).to eq('jupyter/jupyterhub') 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).to be_rbac
expect(subject.repository).to eq('https://jupyterhub.github.io/helm-chart/') expect(subject.repository).to eq('https://jupyterhub.github.io/helm-chart/')
expect(subject.files).to eq(jupyter.files) expect(subject.files).to eq(jupyter.files)
...@@ -75,7 +76,7 @@ describe Clusters::Applications::Jupyter do ...@@ -75,7 +76,7 @@ describe Clusters::Applications::Jupyter do
let(:jupyter) { create(:clusters_applications_jupyter, :errored, version: '0.0.1') } let(:jupyter) { create(:clusters_applications_jupyter, :errored, version: '0.0.1') }
it 'is initialized with the locked version' do 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 end
end end
......
...@@ -5,11 +5,12 @@ require 'spec_helper' ...@@ -5,11 +5,12 @@ require 'spec_helper'
describe 'CycleAnalytics#code' do describe 'CycleAnalytics#code' do
extend CycleAnalyticsHelpers::TestGeneration extend CycleAnalyticsHelpers::TestGeneration
let(:project) { create(:project, :repository) } let_it_be(:project) { create(:project, :repository) }
let(:from_date) { 10.days.ago } let_it_be(:from_date) { 10.days.ago }
let(:user) { create(:user, :admin) } 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 context 'with deployment' do
generate_cycle_analytics_spec( generate_cycle_analytics_spec(
...@@ -24,8 +25,6 @@ describe 'CycleAnalytics#code' do ...@@ -24,8 +25,6 @@ describe 'CycleAnalytics#code' do
context.create_merge_request_closing_issue(context.user, context.project, data[:issue]) context.create_merge_request_closing_issue(context.user, context.project, data[:issue])
end]], end]],
post_fn: -> (context, data) do 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) end)
context "when a regular merge request (that doesn't close the issue) is created" do context "when a regular merge request (that doesn't close the issue) is created" do
...@@ -56,7 +55,6 @@ describe 'CycleAnalytics#code' do ...@@ -56,7 +55,6 @@ describe 'CycleAnalytics#code' do
context.create_merge_request_closing_issue(context.user, context.project, data[:issue]) context.create_merge_request_closing_issue(context.user, context.project, data[:issue])
end]], end]],
post_fn: -> (context, data) do post_fn: -> (context, data) do
context.merge_merge_requests_closing_issue(context.user, context.project, data[:issue])
end) end)
context "when a regular merge request (that doesn't close the issue) is created" do context "when a regular merge request (that doesn't close the issue) is created" do
......
...@@ -3,12 +3,12 @@ ...@@ -3,12 +3,12 @@
require 'spec_helper' require 'spec_helper'
describe CycleAnalytics::GroupLevel do describe CycleAnalytics::GroupLevel do
let(:group) { create(:group)} let_it_be(:group) { create(:group)}
let(:project) { create(:project, :repository, namespace: group) } let_it_be(:project) { create(:project, :repository, namespace: group) }
let(:from_date) { 10.days.ago } let_it_be(:from_date) { 10.days.ago }
let(:user) { create(:user, :admin) } let_it_be(:user) { create(:user, :admin) }
let(:issue) { create(:issue, project: project, created_at: 2.days.ago) } 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(: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) } let(:pipeline) { create(:ci_empty_pipeline, status: 'created', project: project, ref: mr.source_branch, sha: mr.source_branch_sha, head_pipeline_of: mr) }
......
...@@ -5,11 +5,12 @@ require 'spec_helper' ...@@ -5,11 +5,12 @@ require 'spec_helper'
describe 'CycleAnalytics#issue' do describe 'CycleAnalytics#issue' do
extend CycleAnalyticsHelpers::TestGeneration extend CycleAnalyticsHelpers::TestGeneration
let(:project) { create(:project, :repository) } let_it_be(:project) { create(:project, :repository) }
let(:from_date) { 10.days.ago } let_it_be(:from_date) { 10.days.ago }
let(:user) { create(:user, :admin) } 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( generate_cycle_analytics_spec(
phase: :issue, phase: :issue,
...@@ -28,10 +29,6 @@ describe 'CycleAnalytics#issue' do ...@@ -28,10 +29,6 @@ describe 'CycleAnalytics#issue' do
end end
end]], end]],
post_fn: -> (context, data) do 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) end)
context "when a regular label (instead of a list label) is added to the issue" do context "when a regular label (instead of a list label) is added to the issue" do
......
...@@ -5,17 +5,18 @@ require 'spec_helper' ...@@ -5,17 +5,18 @@ require 'spec_helper'
describe 'CycleAnalytics#plan' do describe 'CycleAnalytics#plan' do
extend CycleAnalyticsHelpers::TestGeneration extend CycleAnalyticsHelpers::TestGeneration
let(:project) { create(:project, :repository) } let_it_be(:project) { create(:project, :repository) }
let(:from_date) { 10.days.ago } let_it_be(:from_date) { 10.days.ago }
let(:user) { create(:user, :admin) } 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( generate_cycle_analytics_spec(
phase: :plan, phase: :plan,
data_fn: -> (context) do data_fn: -> (context) do
{ {
issue: context.create(:issue, project: context.project), issue: context.build(:issue, project: context.project),
branch_name: context.generate(:branch) branch_name: context.generate(:branch)
} }
end, end,
...@@ -32,8 +33,6 @@ describe 'CycleAnalytics#plan' do ...@@ -32,8 +33,6 @@ describe 'CycleAnalytics#plan' do
context.create_commit_referencing_issue(data[:issue], branch_name: data[:branch_name]) context.create_commit_referencing_issue(data[:issue], branch_name: data[:branch_name])
end]], end]],
post_fn: -> (context, data) do 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) end)
context "when a regular label (instead of a list label) is added to the issue" do context "when a regular label (instead of a list label) is added to the issue" do
......
...@@ -5,11 +5,12 @@ require 'spec_helper' ...@@ -5,11 +5,12 @@ require 'spec_helper'
describe 'CycleAnalytics#production' do describe 'CycleAnalytics#production' do
extend CycleAnalyticsHelpers::TestGeneration extend CycleAnalyticsHelpers::TestGeneration
let(:project) { create(:project, :repository) } let_it_be(:project) { create(:project, :repository) }
let(:from_date) { 10.days.ago } let_it_be(:from_date) { 10.days.ago }
let(:user) { create(:user, :admin) } 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( generate_cycle_analytics_spec(
phase: :production, phase: :production,
...@@ -24,13 +25,7 @@ describe 'CycleAnalytics#production' do ...@@ -24,13 +25,7 @@ describe 'CycleAnalytics#production' do
["production deploy happens after merge request is merged (along with other changes)", ["production deploy happens after merge request is merged (along with other changes)",
lambda do |context, data| lambda do |context, data|
# Make other changes on master # Make other changes on master
sha = context.project.repository.create_file( context.project.repository.commit("sha_that_does_not_matter")
context.user,
context.generate(:branch),
'content',
message: 'commit message',
branch_name: 'master')
context.project.repository.commit(sha)
context.deploy_master(context.user, context.project) context.deploy_master(context.user, context.project)
end]]) end]])
...@@ -47,7 +42,7 @@ describe 'CycleAnalytics#production' do ...@@ -47,7 +42,7 @@ describe 'CycleAnalytics#production' do
context "when the deployment happens to a non-production environment" do context "when the deployment happens to a non-production environment" do
it "returns nil" 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) merge_request = create_merge_request_closing_issue(user, project, issue)
MergeRequests::MergeService.new(project, user).execute(merge_request) MergeRequests::MergeService.new(project, user).execute(merge_request)
deploy_master(user, project, environment: 'staging') deploy_master(user, project, environment: 'staging')
......
...@@ -3,11 +3,11 @@ ...@@ -3,11 +3,11 @@
require 'spec_helper' require 'spec_helper'
describe CycleAnalytics::ProjectLevel do describe CycleAnalytics::ProjectLevel do
let(:project) { create(:project, :repository) } let_it_be(:project) { create(:project, :repository) }
let(:from_date) { 10.days.ago } let_it_be(:from_date) { 10.days.ago }
let(:user) { create(:user, :admin) } let_it_be(:user) { create(:user, :admin) }
let(:issue) { create(:issue, project: project, created_at: 2.days.ago) } let_it_be(: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(: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) } let(:pipeline) { create(:ci_empty_pipeline, status: 'created', project: project, ref: mr.source_branch, sha: mr.source_branch_sha, head_pipeline_of: mr) }
......
...@@ -5,9 +5,9 @@ require 'spec_helper' ...@@ -5,9 +5,9 @@ require 'spec_helper'
describe 'CycleAnalytics#review' do describe 'CycleAnalytics#review' do
extend CycleAnalyticsHelpers::TestGeneration extend CycleAnalyticsHelpers::TestGeneration
let(:project) { create(:project, :repository) } let_it_be(:project) { create(:project, :repository) }
let(:from_date) { 10.days.ago } let_it_be(:from_date) { 10.days.ago }
let(:user) { create(:user, :admin) } let_it_be(:user) { create(:user, :admin) }
subject { CycleAnalytics::ProjectLevel.new(project, options: { from: from_date }) } subject { CycleAnalytics::ProjectLevel.new(project, options: { from: from_date }) }
......
...@@ -5,11 +5,12 @@ require 'spec_helper' ...@@ -5,11 +5,12 @@ require 'spec_helper'
describe 'CycleAnalytics#staging' do describe 'CycleAnalytics#staging' do
extend CycleAnalyticsHelpers::TestGeneration extend CycleAnalyticsHelpers::TestGeneration
let(:project) { create(:project, :repository) } let_it_be(:project) { create(:project, :repository) }
let(:from_date) { 10.days.ago } let_it_be(:from_date) { 10.days.ago }
let(:user) { create(:user, :admin) } 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( generate_cycle_analytics_spec(
phase: :staging, phase: :staging,
...@@ -28,14 +29,7 @@ describe 'CycleAnalytics#staging' do ...@@ -28,14 +29,7 @@ describe 'CycleAnalytics#staging' do
["production deploy happens after merge request is merged (along with other changes)", ["production deploy happens after merge request is merged (along with other changes)",
lambda do |context, data| lambda do |context, data|
# Make other changes on master # Make other changes on master
sha = context.project.repository.create_file( context.project.repository.commit("this_sha_apparently_does_not_matter")
context.user,
context.generate(:branch),
'content',
message: 'commit message',
branch_name: 'master')
context.project.repository.commit(sha)
context.deploy_master(context.user, context.project) context.deploy_master(context.user, context.project)
end]]) end]])
......
...@@ -5,16 +5,19 @@ require 'spec_helper' ...@@ -5,16 +5,19 @@ require 'spec_helper'
describe 'CycleAnalytics#test' do describe 'CycleAnalytics#test' do
extend CycleAnalyticsHelpers::TestGeneration extend CycleAnalyticsHelpers::TestGeneration
let(:project) { create(:project, :repository) } let_it_be(:project) { create(:project, :repository) }
let(:from_date) { 10.days.ago } let_it_be(:from_date) { 10.days.ago }
let(:user) { create(:user, :admin) } 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( generate_cycle_analytics_spec(
phase: :test, phase: :test,
data_fn: lambda do |context| 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) 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 = 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 } { pipeline: pipeline, issue: issue }
...@@ -22,20 +25,15 @@ describe 'CycleAnalytics#test' do ...@@ -22,20 +25,15 @@ describe 'CycleAnalytics#test' do
start_time_conditions: [["pipeline is started", -> (context, data) { data[:pipeline].run! }]], start_time_conditions: [["pipeline is started", -> (context, data) { data[:pipeline].run! }]],
end_time_conditions: [["pipeline is finished", -> (context, data) { data[:pipeline].succeed! }]], end_time_conditions: [["pipeline is finished", -> (context, data) { data[:pipeline].succeed! }]],
post_fn: -> (context, data) do post_fn: -> (context, data) do
context.merge_merge_requests_closing_issue(context.user, context.project, data[:issue])
end) end)
context "when the pipeline is for a regular merge request (that doesn't close an issue)" do context "when the pipeline is for a regular merge request (that doesn't close an issue)" do
it "returns nil" 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 = create(:ci_pipeline, ref: "refs/heads/#{merge_request.source_branch}", sha: merge_request.diff_head_sha)
pipeline.run! pipeline.run!
pipeline.succeed! pipeline.succeed!
merge_merge_requests_closing_issue(user, project, issue)
expect(subject[:test].project_median).to be_nil expect(subject[:test].project_median).to be_nil
end end
end end
...@@ -53,30 +51,22 @@ describe 'CycleAnalytics#test' do ...@@ -53,30 +51,22 @@ describe 'CycleAnalytics#test' do
context "when the pipeline is dropped (failed)" do context "when the pipeline is dropped (failed)" do
it "returns nil" 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 = create(:ci_pipeline, ref: "refs/heads/#{merge_request.source_branch}", sha: merge_request.diff_head_sha)
pipeline.run! pipeline.run!
pipeline.drop! pipeline.drop!
merge_merge_requests_closing_issue(user, project, issue)
expect(subject[:test].project_median).to be_nil expect(subject[:test].project_median).to be_nil
end end
end end
context "when the pipeline is cancelled" do context "when the pipeline is cancelled" do
it "returns nil" 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 = create(:ci_pipeline, ref: "refs/heads/#{merge_request.source_branch}", sha: merge_request.diff_head_sha)
pipeline.run! pipeline.run!
pipeline.cancel! pipeline.cancel!
merge_merge_requests_closing_issue(user, project, issue)
expect(subject[:test].project_median).to be_nil expect(subject[:test].project_median).to be_nil
end end
end end
......
...@@ -26,4 +26,34 @@ describe ExternalWikiService do ...@@ -26,4 +26,34 @@ describe ExternalWikiService do
it { is_expected.not_to validate_presence_of(:external_wiki_url) } it { is_expected.not_to validate_presence_of(:external_wiki_url) }
end end
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 end
...@@ -2251,6 +2251,22 @@ describe API::Projects do ...@@ -2251,6 +2251,22 @@ describe API::Projects do
put api("/projects/#{project3.id}", user4), params: project_param put api("/projects/#{project3.id}", user4), params: project_param
expect(response).to have_gitlab_http_status(403) expect(response).to have_gitlab_http_status(403)
end 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 end
context 'when authenticated as project developer' do context 'when authenticated as project developer' do
......
...@@ -27,6 +27,8 @@ module CycleAnalyticsHelpers ...@@ -27,6 +27,8 @@ module CycleAnalyticsHelpers
scenarios = combinations_of_start_time_conditions.product(combinations_of_end_time_conditions) scenarios = combinations_of_start_time_conditions.product(combinations_of_end_time_conditions)
scenarios.each do |start_time_conditions, 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 "start condition: #{start_time_conditions.map(&:first).to_sentence}" do
context "end condition: #{end_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 it "finds the median of available durations between the two conditions", :sidekiq_might_not_need_inline do
...@@ -56,8 +58,6 @@ module CycleAnalyticsHelpers ...@@ -56,8 +58,6 @@ module CycleAnalyticsHelpers
end end
context "when the data belongs to another project" do context "when the data belongs to another project" do
let(:other_project) { create(:project, :repository) }
it "returns nil" do it "returns nil" do
# Use a stub to "trick" the data/condition functions # Use a stub to "trick" the data/condition functions
# into using another project. This saves us from having to # into using another project. This saves us from having to
......
...@@ -105,12 +105,15 @@ module GraphqlHelpers ...@@ -105,12 +105,15 @@ module GraphqlHelpers
end end
def query_graphql_field(name, attributes = {}, fields = nil) def query_graphql_field(name, attributes = {}, fields = nil)
fields ||= all_graphql_fields_for(name.classify) field_params = if attributes.present?
attributes = attributes_to_graphql(attributes) "(#{attributes_to_graphql(attributes)})"
attributes = "(#{attributes})" if attributes.present? else
''
end
<<~QUERY <<~QUERY
#{name}#{attributes} #{GraphqlHelpers.fieldnamerize(name.to_s)}#{field_params}
#{wrap_fields(fields)} #{wrap_fields(fields || all_graphql_fields_for(name.to_s.classify))}
QUERY QUERY
end end
...@@ -301,6 +304,17 @@ module GraphqlHelpers ...@@ -301,6 +304,17 @@ module GraphqlHelpers
def global_id_of(model) def global_id_of(model)
model.to_global_id.to_s model.to_global_id.to_s
end 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 end
# This warms our schema, doing this as part of loading the helpers to avoid # This warms our schema, doing this as part of loading the helpers to avoid
......
...@@ -11,8 +11,22 @@ RSpec::Matchers.define :have_graphql_fields do |*expected| ...@@ -11,8 +11,22 @@ RSpec::Matchers.define :have_graphql_fields do |*expected|
Array.wrap(expected).map { |name| GraphqlHelpers.fieldnamerize(name) } Array.wrap(expected).map { |name| GraphqlHelpers.fieldnamerize(name) }
end end
@allow_extra = false
chain :only do
@allow_extra = false
end
chain :at_least do
@allow_extra = true
end
match do |kls| 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 end
failure_message do |kls| failure_message do |kls|
...@@ -22,7 +36,7 @@ RSpec::Matchers.define :have_graphql_fields do |*expected| ...@@ -22,7 +36,7 @@ RSpec::Matchers.define :have_graphql_fields do |*expected|
message = [] message = []
message << "is missing fields: <#{missing.inspect}>" if missing.any? 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") message.join("\n")
end end
......
# 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
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment