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/
.projections.json
/qa/.rakeTasks
webpack-dev-server.json
.nvimrc
/.nvimrc
......@@ -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>
<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 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>
<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 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>
</smart-virtual-list>
</div>
<div v-else>
......
......@@ -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>
<div class="table-mobile-content underline cgray pl-3">
{{ testSuite.name }}
<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>
<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>
......
......@@ -57,13 +57,55 @@ 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 = {})
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]
gid = GlobalID.parse(global_id)
unless gid
raise Gitlab::Graphql::Errors::ArgumentError, "#{global_id} is not a valid GitLab id."
end
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 }
......@@ -71,13 +113,7 @@ class GitlabSchema < GraphQL::Schema
raise Gitlab::Graphql::Errors::ArgumentError, msg
end
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
gid
end
private
......
......@@ -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'
......
......@@ -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
......
---
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:
```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
......
......@@ -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"]
```
......
......@@ -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
"""
......
......@@ -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",
......
......@@ -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
......
......@@ -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 |
......
......@@ -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.
......
......@@ -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.
......@@ -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
......
......@@ -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
......
......@@ -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
......
---
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:
......
---
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:
......
......@@ -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,
......
......@@ -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,
......
......@@ -77,6 +77,7 @@ dependency_scanning:
services: []
except:
variables:
- $DEPENDENCY_SCANNING_DISABLED
- $DS_DISABLE_DIND == 'false'
script:
- /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 ""
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 ""
......
......@@ -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)
......
......@@ -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'] }
......
# 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
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
......
......@@ -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
......
......@@ -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) }
......
......@@ -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
......
......@@ -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
......
......@@ -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')
......
......@@ -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) }
......
......@@ -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 }) }
......
......@@ -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]])
......
......@@ -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
......
......@@ -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
......@@ -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
......
......@@ -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
......
......@@ -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
......
......@@ -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
......
# 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