Commit a55da0a9 authored by Filipa Lacerda's avatar Filipa Lacerda

[ci skip] Merge branch 'master' into 46381-dropdown-mr-widget

* master: (31 commits)
  Remove docker pull prefix from registry clipboard feature
  Document the 3h timeout for GitLab.com shared Runners
  Fix bug with long strings in issue boards
  Fix setting Gitlab metrics content types
  Add documentation for bulk editing of issues and merge requests
  Remove authentication for readonly endpoints in issues API
  Improve testing best practices guidelines
  Add a unique and not null constraint on the project_features.project_id column
  fix typos Frontend Guide, development_process.md
  Add index on runner_type for ci_runners
  Update docs describing `fast_spec_helper` best practices
  Display help text below auto devops domain with nip.io domain name (#45561)
  Add CHANGELOG
  Does not log failed sign-in attempts when database is in read-only mode
  Simplified priority, performance impact should be severity
  Ref contains oid check done by Gitaly
  code example formatting
  more typos
  typo in `endpoint`
  clarify that the function export should be default
  ...
parents 3593b83a 75f94fc3
...@@ -31,8 +31,8 @@ _This notice should stay as the first item in the CONTRIBUTING.md file._ ...@@ -31,8 +31,8 @@ _This notice should stay as the first item in the CONTRIBUTING.md file._
- [Subject labels (~wiki, ~"container registry", ~ldap, ~api, etc.)](#subject-labels-wiki-container-registry-ldap-api-etc) - [Subject labels (~wiki, ~"container registry", ~ldap, ~api, etc.)](#subject-labels-wiki-container-registry-ldap-api-etc)
- [Team labels (~"CI/CD", ~Discussion, ~Quality, ~Platform, etc.)](#team-labels-cicd-discussion-quality-platform-etc) - [Team labels (~"CI/CD", ~Discussion, ~Quality, ~Platform, etc.)](#team-labels-cicd-discussion-quality-platform-etc)
- [Milestone labels (~Deliverable, ~Stretch, ~"Next Patch Release")](#milestone-labels-deliverable-stretch-next-patch-release) - [Milestone labels (~Deliverable, ~Stretch, ~"Next Patch Release")](#milestone-labels-deliverable-stretch-next-patch-release)
- [Priority labels (~Deliverable, ~Stretch, ~"Next Patch Release")](#bug-priority-labels-p1-p2-p3-etc) - [Priority labels (~P1, ~P2, ~P3 , ~P4)](#bug-priority-labels-p1-p2-p3-p4)
- [Severity labels (~Deliverable, ~Stretch, ~"Next Patch Release")](#bug-severity-labels-s1-s2-s3-etc) - [Severity labels (~S1, ~S2, ~S3 , ~S4)](#bug-severity-labels-s1-s2-s3-s4)
- [Label for community contributors (~"Accepting Merge Requests")](#label-for-community-contributors-accepting-merge-requests) - [Label for community contributors (~"Accepting Merge Requests")](#label-for-community-contributors-accepting-merge-requests)
- [Implement design & UI elements](#implement-design--ui-elements) - [Implement design & UI elements](#implement-design--ui-elements)
- [Issue tracker](#issue-tracker) - [Issue tracker](#issue-tracker)
...@@ -211,9 +211,10 @@ Each issue scheduled for the current milestone should be labeled ~Deliverable ...@@ -211,9 +211,10 @@ Each issue scheduled for the current milestone should be labeled ~Deliverable
or ~"Stretch". Any open issue for a previous milestone should be labeled or ~"Stretch". Any open issue for a previous milestone should be labeled
~"Next Patch Release", or otherwise rescheduled to a different milestone. ~"Next Patch Release", or otherwise rescheduled to a different milestone.
### Bug Priority labels (~P1, ~P2, ~P3 & etc.) ### Bug Priority labels (~P1, ~P2, ~P3, ~P4)
Bug Priority labels help us define the time a ~bug fix should be completed. Priority determines how quickly the defect turnaround time must be. If there are multiple defects, the priority decides which defect has to be fixed immediately versus later. Bug Priority labels help us define the time a ~bug fix should be completed. Priority determines how quickly the defect turnaround time must be.
If there are multiple defects, the priority decides which defect has to be fixed immediately versus later.
This label documents the planned timeline & urgency which is used to measure against our actual SLA on delivering ~bug fixes. This label documents the planned timeline & urgency which is used to measure against our actual SLA on delivering ~bug fixes.
| Label | Meaning | Estimate time to fix | Guidance | | Label | Meaning | Estimate time to fix | Guidance |
...@@ -223,16 +224,7 @@ This label documents the planned timeline & urgency which is used to measure aga ...@@ -223,16 +224,7 @@ This label documents the planned timeline & urgency which is used to measure aga
| ~P3 | Medium Priority | Within the next 3 releases (approx one quarter) | | | ~P3 | Medium Priority | Within the next 3 releases (approx one quarter) | |
| ~P4 | Low Priority | Anything outside the next 3 releases (approx beyond one quarter) | The issue is prominent but does not impact user workflow and a workaround is documented | | ~P4 | Low Priority | Anything outside the next 3 releases (approx beyond one quarter) | The issue is prominent but does not impact user workflow and a workaround is documented |
#### Specific Priority guidance ### Bug Severity labels (~S1, ~S2, ~S3, ~S4)
| Label | Availability / Performance |
|-------|--------------------------------------------------------------|
| ~P1 | |
| ~P2 | The issue is (almost) guaranteed to occur in the near future |
| ~P3 | The issue is likely to occur in the near future |
| ~P4 | The issue _may_ occur but it's not likely |
### Bug Severity labels (~S1, ~S2, ~S3 & etc.)
Severity labels help us clearly communicate the impact of a ~bug on users. Severity labels help us clearly communicate the impact of a ~bug on users.
...@@ -243,14 +235,14 @@ Severity labels help us clearly communicate the impact of a ~bug on users. ...@@ -243,14 +235,14 @@ Severity labels help us clearly communicate the impact of a ~bug on users.
| ~S3 | Major Severity | Broken Feature, workaround acceptable | Can create merge requests only from the Merge Requests page, not through the Issue. | | ~S3 | Major Severity | Broken Feature, workaround acceptable | Can create merge requests only from the Merge Requests page, not through the Issue. |
| ~S4 | Low Severity | Functionality inconvenience or cosmetic issue | Label colors are incorrect / not being displayed. | | ~S4 | Low Severity | Functionality inconvenience or cosmetic issue | Label colors are incorrect / not being displayed. |
#### Specific Severity guidance #### Severity impact guidance
| Label | Security Impact | | Label | Security Impact | Availability / Performance Impact |
|-------|-------------------------------------------------------------------| |-------|---------------------------------------------------------------------|--------------------------------------------------------------|
| ~S1 | >50% customers impacted (possible company extinction level event) | | ~S1 | >50% users impacted (possible company extinction level event) | |
| ~S2 | Multiple customers impacted (but not apocalyptic) | | ~S2 | Many users or multiple paid customers impacted (but not apocalyptic)| The issue is (almost) guaranteed to occur in the near future |
| ~S3 | A single customer impacted | | ~S3 | A few users or a single paid customer impacted | The issue is likely to occur in the near future |
| ~S4 | No customer impact, or expected impact within 30 days | | ~S4 | No paid users/customer impacted, or expected impact within 30 days | The issue _may_ occur but it's not likely |
### Label for community contributors (~"Accepting Merge Requests") ### Label for community contributors (~"Accepting Merge Requests")
......
...@@ -28,11 +28,6 @@ ...@@ -28,11 +28,6 @@
isOpen: false, isOpen: false,
}; };
}, },
computed: {
clipboardText() {
return `docker pull ${this.repo.location}`;
},
},
methods: { methods: {
...mapActions([ ...mapActions([
'fetchRepos', 'fetchRepos',
...@@ -84,7 +79,7 @@ ...@@ -84,7 +79,7 @@
<clipboard-button <clipboard-button
v-if="repo.location" v-if="repo.location"
:text="clipboardText" :text="repo.location"
:title="repo.location" :title="repo.location"
css-class="btn-default btn-transparent btn-clipboard" css-class="btn-default btn-transparent btn-clipboard"
/> />
......
...@@ -56,10 +56,6 @@ ...@@ -56,10 +56,6 @@
.catch(() => this.showError(errorMessagesTypes.FETCH_REGISTRY)); .catch(() => this.showError(errorMessagesTypes.FETCH_REGISTRY));
}, },
clipboardText(text) {
return `docker pull ${text}`;
},
showError(message) { showError(message) {
Flash(errorMessages[message]); Flash(errorMessages[message]);
}, },
...@@ -89,7 +85,7 @@ ...@@ -89,7 +85,7 @@
<clipboard-button <clipboard-button
v-if="item.location" v-if="item.location"
:title="item.location" :title="item.location"
:text="clipboardText(item.location)" :text="item.location"
css-class="btn-default btn-transparent btn-clipboard" css-class="btn-default btn-transparent btn-clipboard"
/> />
</td> </td>
......
...@@ -210,3 +210,15 @@ ...@@ -210,3 +210,15 @@
margin-left: -$size; margin-left: -$size;
} }
} }
/*
* Mixin that fixes wrapping issues with long strings (e.g. URLs)
*
* Note: the width needs to be set for it to work in Firefox
*/
@mixin overflow-break-word {
overflow-wrap: break-word;
word-wrap: break-word;
word-break: break-word;
max-width: 100%;
}
...@@ -284,6 +284,9 @@ ...@@ -284,6 +284,9 @@
box-shadow: 0 1px 2px $issue-boards-card-shadow; box-shadow: 0 1px 2px $issue-boards-card-shadow;
list-style: none; list-style: none;
// as a fallback, hide overflow content so that dragging and dropping still works
overflow: hidden;
&:not(:last-child) { &:not(:last-child) {
margin-bottom: 5px; margin-bottom: 5px;
} }
...@@ -310,14 +313,13 @@ ...@@ -310,14 +313,13 @@
} }
.card-title { .card-title {
@include overflow-break-word();
margin: 0 30px 0 0; margin: 0 30px 0 0;
font-size: 1em; font-size: 1em;
line-height: inherit; line-height: inherit;
a { a {
color: $gl-text-color; color: $gl-text-color;
word-wrap: break-word;
word-break: break-word;
margin-right: 2px; margin-right: 2px;
} }
} }
...@@ -462,8 +464,8 @@ ...@@ -462,8 +464,8 @@
} }
.issuable-header-text { .issuable-header-text {
@include overflow-break-word();
padding-right: 35px; padding-right: 35px;
word-break: break-word;
> strong { > strong {
font-weight: $gl-font-weight-bold; font-weight: $gl-font-weight-bold;
......
...@@ -24,6 +24,15 @@ module AutoDevopsHelper ...@@ -24,6 +24,15 @@ module AutoDevopsHelper
end end
end end
def cluster_ingress_ip(project)
project
.cluster_ingresses
.where("external_ip is not null")
.limit(1)
.pluck(:external_ip)
.first
end
private private
def missing_auto_devops_domain?(project) def missing_auto_devops_domain?(project)
......
...@@ -217,6 +217,7 @@ class Project < ActiveRecord::Base ...@@ -217,6 +217,7 @@ class Project < ActiveRecord::Base
has_one :cluster_project, class_name: 'Clusters::Project' has_one :cluster_project, class_name: 'Clusters::Project'
has_many :clusters, through: :cluster_project, class_name: 'Clusters::Cluster' has_many :clusters, through: :cluster_project, class_name: 'Clusters::Cluster'
has_many :cluster_ingresses, through: :clusters, source: :application_ingress, class_name: 'Clusters::Applications::Ingress'
# Container repositories need to remove data from the container registry, # Container repositories need to remove data from the container registry,
# which is not managed by the DB. Hence we're still using dependent: :destroy # which is not managed by the DB. Hence we're still using dependent: :destroy
......
...@@ -1097,8 +1097,11 @@ class User < ActiveRecord::Base ...@@ -1097,8 +1097,11 @@ class User < ActiveRecord::Base
# <https://github.com/plataformatec/devise/blob/v4.0.0/lib/devise/models/lockable.rb#L92> # <https://github.com/plataformatec/devise/blob/v4.0.0/lib/devise/models/lockable.rb#L92>
# #
def increment_failed_attempts! def increment_failed_attempts!
return if ::Gitlab::Database.read_only?
self.failed_attempts ||= 0 self.failed_attempts ||= 0
self.failed_attempts += 1 self.failed_attempts += 1
if attempts_exceeded? if attempts_exceeded?
lock_access! unless access_locked? lock_access! unless access_locked?
else else
......
...@@ -35,7 +35,9 @@ ...@@ -35,7 +35,9 @@
= _('Domain') = _('Domain')
= form.text_field :domain, class: 'form-control', placeholder: 'domain.com' = form.text_field :domain, class: 'form-control', placeholder: 'domain.com'
.help-block .help-block
= s_('CICD|You need to specify a domain if you want to use Auto Review Apps and Auto Deploy stages.') = s_('CICD|A domain is required to use Auto Review Apps and Auto Deploy Stages.')
- if cluster_ingress_ip = cluster_ingress_ip(@project)
= s_('%{nip_domain} can be used as an alternative to a custom domain.').html_safe % { nip_domain: "<code>#{cluster_ingress_ip}.nip.io</code>".html_safe }
= link_to icon('question-circle'), help_page_path('topics/autodevops/index.md', anchor: 'auto-devops-base-domain'), target: '_blank' = link_to icon('question-circle'), help_page_path('topics/autodevops/index.md', anchor: 'auto-devops-base-domain'), target: '_blank'
= f.submit 'Save changes', class: "btn btn-success prepend-top-15" = f.submit 'Save changes', class: "btn btn-success prepend-top-15"
---
title: made listing and showing public issue apis available without authentication
merge_request: 18638
author: haseebeqx
type: changed
---
title: Fix issue board bug with long strings in titles
merge_request: 18924
author:
type: fixed
---
title: Display help text below auto devops domain with nip.io domain name (#45561)
merge_request: 18496
author:
type: added
---
title: Add index on runner_type for ci_runners
merge_request: 18897
author:
type: performance
---
title: Does not log failed sign-in attempts when the database is in read-only mode
merge_request: 18957
author:
type: fixed
---
title: Fix setting Gitlab metrics content types
merge_request:
author:
type: fixed
---
title: Remove docker pull prefix from registry clipboard feature
merge_request: 18933
author: Lars Greiss
type: changed
---
title: Add a unique and not null constraint on the project_features.project_id column
merge_request:
author:
type: fixed
---
title: Refs containting sha checks are done by Gitaly
merge_request:
author:
type: other
class AddIndexOnCiRunnersRunnerType < ActiveRecord::Migration
include Gitlab::Database::MigrationHelpers
DOWNTIME = false
disable_ddl_transaction!
def up
add_concurrent_index :ci_runners, :runner_type
end
def down
remove_index :ci_runners, :runner_type
end
end
class AddUniqueConstraintToProjectFeaturesProjectId < ActiveRecord::Migration
include Gitlab::Database::MigrationHelpers
DOWNTIME = false
disable_ddl_transaction!
class ProjectFeature < ActiveRecord::Base
self.table_name = 'project_features'
include EachBatch
end
def up
remove_duplicates
add_concurrent_index :project_features, :project_id, unique: true, name: 'index_project_features_on_project_id_unique'
remove_concurrent_index_by_name :project_features, 'index_project_features_on_project_id'
rename_index :project_features, 'index_project_features_on_project_id_unique', 'index_project_features_on_project_id'
end
def down
rename_index :project_features, 'index_project_features_on_project_id', 'index_project_features_on_project_id_old'
add_concurrent_index :project_features, :project_id
remove_concurrent_index_by_name :project_features, 'index_project_features_on_project_id_old'
end
private
def remove_duplicates
features = ProjectFeature
.select('MAX(id) AS max, COUNT(id), project_id')
.group(:project_id)
.having('COUNT(id) > 1')
features.each do |feature|
ProjectFeature
.where(project_id: feature['project_id'])
.where('id <> ?', feature['max'])
.each_batch { |batch| batch.delete_all }
end
end
end
class AddNotNullConstraintToProjectFeaturesProjectId < ActiveRecord::Migration
include Gitlab::Database::MigrationHelpers
DOWNTIME = false
class ProjectFeature < ActiveRecord::Base
include EachBatch
self.table_name = 'project_features'
end
def up
ProjectFeature.where(project_id: nil).delete_all
change_column_null :project_features, :project_id, false
end
def down
change_column_null :project_features, :project_id, true
end
end
...@@ -11,7 +11,7 @@ ...@@ -11,7 +11,7 @@
# #
# It's strongly recommended that you check this file into your version control system. # It's strongly recommended that you check this file into your version control system.
ActiveRecord::Schema.define(version: 20180508135515) do ActiveRecord::Schema.define(version: 20180512061621) do
# These are extensions that must be enabled in order to support this database # These are extensions that must be enabled in order to support this database
enable_extension "plpgsql" enable_extension "plpgsql"
...@@ -503,6 +503,7 @@ ActiveRecord::Schema.define(version: 20180508135515) do ...@@ -503,6 +503,7 @@ ActiveRecord::Schema.define(version: 20180508135515) do
add_index "ci_runners", ["contacted_at"], name: "index_ci_runners_on_contacted_at", using: :btree add_index "ci_runners", ["contacted_at"], name: "index_ci_runners_on_contacted_at", using: :btree
add_index "ci_runners", ["is_shared"], name: "index_ci_runners_on_is_shared", using: :btree add_index "ci_runners", ["is_shared"], name: "index_ci_runners_on_is_shared", using: :btree
add_index "ci_runners", ["locked"], name: "index_ci_runners_on_locked", using: :btree add_index "ci_runners", ["locked"], name: "index_ci_runners_on_locked", using: :btree
add_index "ci_runners", ["runner_type"], name: "index_ci_runners_on_runner_type", using: :btree
add_index "ci_runners", ["token"], name: "index_ci_runners_on_token", using: :btree add_index "ci_runners", ["token"], name: "index_ci_runners_on_token", using: :btree
create_table "ci_stages", force: :cascade do |t| create_table "ci_stages", force: :cascade do |t|
...@@ -1493,7 +1494,7 @@ ActiveRecord::Schema.define(version: 20180508135515) do ...@@ -1493,7 +1494,7 @@ ActiveRecord::Schema.define(version: 20180508135515) do
add_index "project_deploy_tokens", ["project_id", "deploy_token_id"], name: "index_project_deploy_tokens_on_project_id_and_deploy_token_id", unique: true, using: :btree add_index "project_deploy_tokens", ["project_id", "deploy_token_id"], name: "index_project_deploy_tokens_on_project_id_and_deploy_token_id", unique: true, using: :btree
create_table "project_features", force: :cascade do |t| create_table "project_features", force: :cascade do |t|
t.integer "project_id" t.integer "project_id", null: false
t.integer "merge_requests_access_level" t.integer "merge_requests_access_level"
t.integer "issues_access_level" t.integer "issues_access_level"
t.integer "wiki_access_level" t.integer "wiki_access_level"
...@@ -1504,7 +1505,7 @@ ActiveRecord::Schema.define(version: 20180508135515) do ...@@ -1504,7 +1505,7 @@ ActiveRecord::Schema.define(version: 20180508135515) do
t.integer "repository_access_level", default: 20, null: false t.integer "repository_access_level", default: 20, null: false
end end
add_index "project_features", ["project_id"], name: "index_project_features_on_project_id", using: :btree add_index "project_features", ["project_id"], name: "index_project_features_on_project_id", unique: true, using: :btree
create_table "project_group_links", force: :cascade do |t| create_table "project_group_links", force: :cascade do |t|
t.integer "project_id", null: false t.integer "project_id", null: false
......
# NFS # NFS
You can view information and options set for each of the mounted NFS file You can view information and options set for each of the mounted NFS file
systems by running `sudo nfsstat -m`. systems by running `nfsstat -m` and `cat /etc/fstab`.
## NFS Server features ## NFS Server features
......
...@@ -151,7 +151,8 @@ The next step is to configure a Runner so that it picks the pending jobs. ...@@ -151,7 +151,8 @@ The next step is to configure a Runner so that it picks the pending jobs.
In GitLab, Runners run the jobs that you define in `.gitlab-ci.yml`. A Runner In GitLab, Runners run the jobs that you define in `.gitlab-ci.yml`. A Runner
can be a virtual machine, a VPS, a bare-metal machine, a docker container or can be a virtual machine, a VPS, a bare-metal machine, a docker container or
even a cluster of containers. GitLab and the Runners communicate through an API, even a cluster of containers. GitLab and the Runners communicate through an API,
so the only requirement is that the Runner's machine has [Internet] access. so the only requirement is that the Runner's machine has network access to the
GitLab server.
A Runner can be specific to a certain project or serve multiple projects in A Runner can be specific to a certain project or serve multiple projects in
GitLab. If it serves all projects it's called a _Shared Runner_. GitLab. If it serves all projects it's called a _Shared Runner_.
...@@ -226,4 +227,3 @@ CI with various languages. ...@@ -226,4 +227,3 @@ CI with various languages.
[enabled]: ../enable_or_disable_ci.md [enabled]: ../enable_or_disable_ci.md
[stages]: ../yaml/README.md#stages [stages]: ../yaml/README.md#stages
[pipeline]: ../pipelines.md [pipeline]: ../pipelines.md
[internet]: https://about.gitlab.com/images/theinternet.png
...@@ -22,9 +22,9 @@ Please use your best judgement when to use it and please contribute new points t ...@@ -22,9 +22,9 @@ Please use your best judgement when to use it and please contribute new points t
- [ ] Are all [departments](https://about.gitlab.com/handbook/engineering/#engineering-teams) that are needed from your perspective already involved in the issue? (For example is UX missing?) - [ ] Are all [departments](https://about.gitlab.com/handbook/engineering/#engineering-teams) that are needed from your perspective already involved in the issue? (For example is UX missing?)
- [ ] Is the specification complete? Are you missing decisions? How about error handling/defaults/edge cases? Take your time to understand the needed implementation and go through its flow. - [ ] Is the specification complete? Are you missing decisions? How about error handling/defaults/edge cases? Take your time to understand the needed implementation and go through its flow.
- [ ] Are all necessary UX specifications available that you will need in order to implement? Are there new UX components/patterns in the designs? Then contact the UI component team early on. How should error messages or validation be handled? - [ ] Are all necessary UX specifications available that you will need in order to implement? Are there new UX components/patterns in the designs? Then contact the UI component team early on. How should error messages or validation be handled?
- [ ] **Library usage** Use Vuex as soon as you have even a medium state to manage, use Vue router if you need to have different views internally and want to link from the outside. Check what libraries we already have for which occassions. - [ ] **Library usage** Use Vuex as soon as you have even a medium state to manage, use Vue router if you need to have different views internally and want to link from the outside. Check what libraries we already have for which occasions.
- [ ] **Plan your implementation:** - [ ] **Plan your implementation:**
- [ ] **Architecture plan:** Create a plan aligned with GitLab's architecture, how you are going to do the implementation, for example Vue application setup and its components (through [onion skinning](https://gitlab.com/gitlab-org/gitlab-ce/issues/35873#note_39994091)), Store structure and data flow, which existing Vue components can you reuse. Its a good idea to go through your plan with another engineer to refine it. - [ ] **Architecture plan:** Create a plan aligned with GitLab's architecture, how you are going to do the implementation, for example Vue application setup and its components (through [onion skinning](https://gitlab.com/gitlab-org/gitlab-ce/issues/35873#note_39994091)), Store structure and data flow, which existing Vue components can you reuse. It's a good idea to go through your plan with another engineer to refine it.
- [ ] **Backend:** The best way is to kickoff the implementation in a call and discuss with the assigned Backend engineer what you will need from the backend and also when. Can you reuse existing API's? How is the performance with the planned architecture? Maybe create together a JSON mock object to already start with development. - [ ] **Backend:** The best way is to kickoff the implementation in a call and discuss with the assigned Backend engineer what you will need from the backend and also when. Can you reuse existing API's? How is the performance with the planned architecture? Maybe create together a JSON mock object to already start with development.
- [ ] **Communication:** It also makes sense to have for bigger features an own slack channel (normally called #f_{feature_name}) and even weekly demo calls with all people involved. - [ ] **Communication:** It also makes sense to have for bigger features an own slack channel (normally called #f_{feature_name}) and even weekly demo calls with all people involved.
- [ ] **Dependency Plan:** Are there big dependencies in the plan between you and others, then maybe create an execution diagram to show what is blocking which part and the order of the different parts. - [ ] **Dependency Plan:** Are there big dependencies in the plan between you and others, then maybe create an execution diagram to show what is blocking which part and the order of the different parts.
...@@ -56,7 +56,7 @@ Please use your best judgement when to use it and please contribute new points t ...@@ -56,7 +56,7 @@ Please use your best judgement when to use it and please contribute new points t
- [ ] If you have multiple MR's then also smoke test against the final merge. - [ ] If you have multiple MR's then also smoke test against the final merge.
- [ ] Are there any big changes on how and especially how frequently we use the API then let production know about it - [ ] Are there any big changes on how and especially how frequently we use the API then let production know about it
- [ ] Smoke test of the RC on dev., staging., canary deployments and .com - [ ] Smoke test of the RC on dev., staging., canary deployments and .com
- [ ] Follow up on issues that came out of the review. Create isssues for discovered edge cases that should be covered in future iterations. - [ ] Follow up on issues that came out of the review. Create issues for discovered edge cases that should be covered in future iterations.
--- ---
......
...@@ -68,10 +68,10 @@ Often we need to provide data from haml to our Vue application. Let's store it i ...@@ -68,10 +68,10 @@ Often we need to provide data from haml to our Vue application. Let's store it i
You can use `mapState` to access state properties in the components. You can use `mapState` to access state properties in the components.
### `actions.js` ### `actions.js`
An action is a playload of information to send data from our application to our store. An action is a payload of information to send data from our application to our store.
An action is usually composed by a `type` and a `payload` and they describe what happened. An action is usually composed by a `type` and a `payload` and they describe what happened.
Enforcing that every change is described as an action lets us have a clear understanting of what is going on in the app. Enforcing that every change is described as an action lets us have a clear understanding of what is going on in the app.
In this file, we will write the actions that will call the respective mutations: In this file, we will write the actions that will call the respective mutations:
...@@ -87,7 +87,7 @@ In this file, we will write the actions that will call the respective mutations: ...@@ -87,7 +87,7 @@ In this file, we will write the actions that will call the respective mutations:
export const fetchUsers = ({ state, dispatch }) => { export const fetchUsers = ({ state, dispatch }) => {
dispatch('requestUsers'); dispatch('requestUsers');
axios.get(state.endoint) axios.get(state.endpoint)
.then(({ data }) => dispatch('receiveUsersSuccess', data)) .then(({ data }) => dispatch('receiveUsersSuccess', data))
.catch((error) => { .catch((error) => {
dispatch('receiveUsersError', error) dispatch('receiveUsersError', error)
...@@ -102,7 +102,7 @@ In this file, we will write the actions that will call the respective mutations: ...@@ -102,7 +102,7 @@ In this file, we will write the actions that will call the respective mutations:
export const addUser = ({ state, dispatch }, user) => { export const addUser = ({ state, dispatch }, user) => {
dispatch('requestAddUser'); dispatch('requestAddUser');
axios.post(state.endoint, user) axios.post(state.endpoint, user)
.then(({ data }) => dispatch('receiveAddUserSuccess', data)) .then(({ data }) => dispatch('receiveAddUserSuccess', data))
.catch((error) => dispatch('receiveAddUserError', error)); .catch((error) => dispatch('receiveAddUserError', error));
} }
...@@ -126,7 +126,7 @@ The component MUST only dispatch the `fetchNamespace` action. Actions namespaced ...@@ -126,7 +126,7 @@ The component MUST only dispatch the `fetchNamespace` action. Actions namespaced
The `fetch` action will be responsible to dispatch `requestNamespace`, `receiveNamespaceSuccess` and `receiveNamespaceError` The `fetch` action will be responsible to dispatch `requestNamespace`, `receiveNamespaceSuccess` and `receiveNamespaceError`
By following this pattern we guarantee: By following this pattern we guarantee:
1. All aplications follow the same pattern, making it easier for anyone to maintain the code 1. All applications follow the same pattern, making it easier for anyone to maintain the code
1. All data in the application follows the same lifecycle pattern 1. All data in the application follows the same lifecycle pattern
1. Actions are contained and human friendly 1. Actions are contained and human friendly
1. Unit tests are easier 1. Unit tests are easier
...@@ -149,7 +149,7 @@ import { mapActions } from 'vuex'; ...@@ -149,7 +149,7 @@ import { mapActions } from 'vuex';
}; };
``` ```
#### `mutations.js` ### `mutations.js`
The mutations specify how the application state changes in response to actions sent to the store. The mutations specify how the application state changes in response to actions sent to the store.
The only way to change state in a Vuex store should be by committing a mutation. The only way to change state in a Vuex store should be by committing a mutation.
...@@ -175,7 +175,7 @@ Remember that actions only describe that something happened, they don't describe ...@@ -175,7 +175,7 @@ Remember that actions only describe that something happened, they don't describe
state.isLoading = false; state.isLoading = false;
}, },
[types.REQUEST_ADD_USER](state, user) { [types.REQUEST_ADD_USER](state, user) {
state.isAddingUser = true; state.isAddingUser = true;
}, },
[types.RECEIVE_ADD_USER_SUCCESS](state, user) { [types.RECEIVE_ADD_USER_SUCCESS](state, user) {
state.isAddingUser = false; state.isAddingUser = false;
...@@ -183,12 +183,12 @@ Remember that actions only describe that something happened, they don't describe ...@@ -183,12 +183,12 @@ Remember that actions only describe that something happened, they don't describe
}, },
[types.REQUEST_ADD_USER_ERROR](state, error) { [types.REQUEST_ADD_USER_ERROR](state, error) {
state.isAddingUser = true; state.isAddingUser = true;
state.errorAddingUser = error; state.errorAddingUser = error;
}, },
}; };
``` ```
#### `getters.js` ### `getters.js`
Sometimes we may need to get derived state based on store state, like filtering for a specific prop. Sometimes we may need to get derived state based on store state, like filtering for a specific prop.
Using a getter will also cache the result based on dependencies due to [how computed props work](https://vuejs.org/v2/guide/computed.html#Computed-Caching-vs-Methods) Using a getter will also cache the result based on dependencies due to [how computed props work](https://vuejs.org/v2/guide/computed.html#Computed-Caching-vs-Methods)
This can be done through the `getters`: This can be done through the `getters`:
...@@ -213,7 +213,7 @@ import { mapGetters } from 'vuex'; ...@@ -213,7 +213,7 @@ import { mapGetters } from 'vuex';
}; };
``` ```
#### `mutations_types.js` ### `mutations_types.js`
From [vuex mutations docs][vuex-mutations]: From [vuex mutations docs][vuex-mutations]:
> It is a commonly seen pattern to use constants for mutation types in various Flux implementations. This allows the code to take advantage of tooling like linters, and putting all constants in a single file allows your collaborators to get an at-a-glance view of what mutations are possible in the entire application. > It is a commonly seen pattern to use constants for mutation types in various Flux implementations. This allows the code to take advantage of tooling like linters, and putting all constants in a single file allows your collaborators to get an at-a-glance view of what mutations are possible in the entire application.
...@@ -289,7 +289,7 @@ export default { ...@@ -289,7 +289,7 @@ export default {
``` ```
### Vuex Gotchas ### Vuex Gotchas
1. Do not call a mutation directly. Always use an action to commit a mutation. Doing so will keep consistency through out the application. From Vuex docs: 1. Do not call a mutation directly. Always use an action to commit a mutation. Doing so will keep consistency throughout the application. From Vuex docs:
> why don't we just call store.commit('action') directly? Well, remember that mutations must be synchronous? Actions aren't. We can perform asynchronous operations inside an action. > why don't we just call store.commit('action') directly? Well, remember that mutations must be synchronous? Actions aren't. We can perform asynchronous operations inside an action.
...@@ -342,7 +342,7 @@ describe('component', () => { ...@@ -342,7 +342,7 @@ describe('component', () => {
}; };
// populate the store // populate the store
store.dipatch('addUser', user); store.dispatch('addUser', user);
vm = new Component({ vm = new Component({
store, store,
...@@ -352,6 +352,18 @@ describe('component', () => { ...@@ -352,6 +352,18 @@ describe('component', () => {
}); });
``` ```
#### Testing Vuex actions and getters
Because we're currently using [`babel-plugin-rewire`](https://github.com/speedskater/babel-plugin-rewire), you may encounter the following error when testing your Vuex actions and getters:
`[vuex] actions should be function or object with "handler" function`
To prevent this error from happening, you need to export an empty function as `default`:
```
// getters.js or actions.js
// prevent babel-plugin-rewire from generating an invalid default during karma tests
export default () => {};
```
[vuex-docs]: https://vuex.vuejs.org [vuex-docs]: https://vuex.vuejs.org
[vuex-structure]: https://vuex.vuejs.org/en/structure.html [vuex-structure]: https://vuex.vuejs.org/en/structure.html
[vuex-mutations]: https://vuex.vuejs.org/en/mutations.html [vuex-mutations]: https://vuex.vuejs.org/en/mutations.html
......
...@@ -2,7 +2,7 @@ ...@@ -2,7 +2,7 @@
QueryRecorder is a tool for detecting the [N+1 queries problem](http://guides.rubyonrails.org/active_record_querying.html#eager-loading-associations) from tests. QueryRecorder is a tool for detecting the [N+1 queries problem](http://guides.rubyonrails.org/active_record_querying.html#eager-loading-associations) from tests.
> Implemented in [spec/support/query_recorder.rb](https://gitlab.com/gitlab-org/gitlab-ce/blob/master/spec/support/query_recorder.rb) via [9c623e3e](https://gitlab.com/gitlab-org/gitlab-ce/commit/9c623e3e5d7434f2e30f7c389d13e5af4ede770a) > Implemented in [spec/support/query_recorder.rb](https://gitlab.com/gitlab-org/gitlab-ce/blob/master/spec/support/helpers/query_recorder.rb) via [9c623e3e](https://gitlab.com/gitlab-org/gitlab-ce/commit/9c623e3e5d7434f2e30f7c389d13e5af4ede770a)
As a rule, merge requests [should not increase query counts](merge_request_performance_guidelines.md#query-counts). If you find yourself adding something like `.includes(:author, :assignee)` to avoid having `N+1` queries, consider using QueryRecorder to enforce this with a test. Without this, a new feature which causes an additional model to be accessed will silently reintroduce the problem. As a rule, merge requests [should not increase query counts](merge_request_performance_guidelines.md#query-counts). If you find yourself adding something like `.includes(:author, :assignee)` to avoid having `N+1` queries, consider using QueryRecorder to enforce this with a test. Without this, a new feature which causes an additional model to be accessed will silently reintroduce the problem.
......
...@@ -133,11 +133,24 @@ really fast since: ...@@ -133,11 +133,24 @@ really fast since:
- gitlab-shell and Gitaly setup are skipped - gitlab-shell and Gitaly setup are skipped
- Test repositories setup are skipped - Test repositories setup are skipped
Note that in some cases, you might have to add some `require_dependency 'foo'` `fast_spec_helper` also support autoloading classes that are located inside the
in your file under test since Rails autoloading is not available in these cases. `lib/` directory. It means that as long as your class / module is using only
code from the `lib/` directory you will not need to explicitly load any
This shouldn't be a problem since explicitely listing dependencies should be dependencies. `fast_spec_helper` also loads all ActiveSupport extensions,
considered a good practice anyway. including core extensions that are commonly used in the Rails environment.
Note that in some cases, you might still have to load some dependencies using
`require_dependency` when a code is using gems or a dependency is not located
in `lib/`.
For example, if you want to test your code that is calling the
`Gitlab::UntrustedRegexp` class, which under the hood uses `re2` library, you
should either add `require_dependency 're2'` to files in your library that
need `re2` gem, to make this requirement explicit, or you can add it to the
spec itself, but the former is preferred.
It takes around one second to load tests that are using `fast_spec_helper`
instead of 30+ seconds in case of a regular `spec_helper`.
### `let` variables ### `let` variables
......
...@@ -75,7 +75,6 @@ Shared Runners on GitLab.com run in [autoscale mode] and powered by ...@@ -75,7 +75,6 @@ Shared Runners on GitLab.com run in [autoscale mode] and powered by
Google Cloud Platform and DigitalOcean. Autoscaling means reduced Google Cloud Platform and DigitalOcean. Autoscaling means reduced
waiting times to spin up CI/CD jobs, and isolated VMs for each project, waiting times to spin up CI/CD jobs, and isolated VMs for each project,
thus maximizing security. thus maximizing security.
They're free to use for public open source projects and limited to 2000 CI They're free to use for public open source projects and limited to 2000 CI
minutes per month per group for private projects. Read about all minutes per month per group for private projects. Read about all
[GitLab.com plans](https://about.gitlab.com/pricing/). [GitLab.com plans](https://about.gitlab.com/pricing/).
...@@ -90,6 +89,10 @@ ephemeral instances with 3.75GB of RAM, CoreOS and the latest Docker Engine ...@@ -90,6 +89,10 @@ ephemeral instances with 3.75GB of RAM, CoreOS and the latest Docker Engine
installed. Instances provide 1 vCPU and 25GB of HDD disk space. The default installed. Instances provide 1 vCPU and 25GB of HDD disk space. The default
region of the VMs is US East1. region of the VMs is US East1.
Jobs handled by the shared Runners on GitLab.com (`shared-runners-manager-X.gitlab.com`),
**will be timed out after 3 hours**, regardless of the timeout configured in a
project. Check the issues [4010] and [4070] for the reference.
Below are the shared Runners settings. Below are the shared Runners settings.
| Setting | GitLab.com | Default | | Setting | GitLab.com | Default |
...@@ -340,3 +343,5 @@ High Performance TCP/HTTP Load Balancer: ...@@ -340,3 +343,5 @@ High Performance TCP/HTTP Load Balancer:
[mailgun]: https://www.mailgun.com/ "Mailgun website" [mailgun]: https://www.mailgun.com/ "Mailgun website"
[sidekiq]: http://sidekiq.org/ "Sidekiq website" [sidekiq]: http://sidekiq.org/ "Sidekiq website"
[unicorn-worker-killer]: https://rubygems.org/gems/unicorn-worker-killer "unicorn-worker-killer" [unicorn-worker-killer]: https://rubygems.org/gems/unicorn-worker-killer "unicorn-worker-killer"
[4010]: https://gitlab.com/gitlab-com/infrastructure/issues/4010 "Find a good value for maximum timeout for Shared Runners"
[4070]: https://gitlab.com/gitlab-com/infrastructure/issues/4070 "Configure per-runner timeout for shared-runners-manager-X on GitLab.com"
# Bulk Editing
>**Note:**
- A permission level of `Reporter` or higher is required in order to manage
issues.
- A permission level of `Developer` or higher is required in order to manage
merge requests.
Fields across multiple issues or merge requests can be updated simutaneously by using the bulk edit feature.
>**Note:**
- Bulk editing of issues and merge requests is only available at the project level.
To access the feature, navigate to either the issue or merge request list for the project and click 'Edit Issues' or 'Edit Merge Requests'. This will cause a sidebar to be shown on the right-hand side of the screen, where the available, editable fields are displayed. Checkboxes will also appear to the left-hand side of each issue or merge request, ready to be selected.
Once all items have been selected, choose the appropriate fields and their values from the sidebar and click 'Update All' to apply these changes.
...@@ -151,3 +151,7 @@ or Bugzilla. ...@@ -151,3 +151,7 @@ or Bugzilla.
### Issue's API ### Issue's API
Read through the [API documentation](../../../api/issues.md). Read through the [API documentation](../../../api/issues.md).
### Bulk editing issues
Find out about [bulk editing issues](../../project/bulk_editing.md).
...@@ -233,6 +233,10 @@ all your changes will be available to preview by anyone with the Review Apps lin ...@@ -233,6 +233,10 @@ all your changes will be available to preview by anyone with the Review Apps lin
[Read more about Review Apps.](../../../ci/review_apps/index.md) [Read more about Review Apps.](../../../ci/review_apps/index.md)
## Bulk editing merge requests
Find out about [bulk editing merge requests](../../project/bulk_editing.md).
## Tips ## Tips
Here are some tips that will help you be more efficient with merge requests in Here are some tips that will help you be more efficient with merge requests in
......
...@@ -13,15 +13,27 @@ and from merge requests. ...@@ -13,15 +13,27 @@ and from merge requests.
![Open Web IDE](img/open_web_ide.png) ![Open Web IDE](img/open_web_ide.png)
## Commit changes ## File finder
Changed files are shown on the right in the commit panel. All changes are > [Introduced in](https://gitlab.com/gitlab-org/gitlab-ce/merge_requests/18323) [GitLab Core][ce] 10.8.
automatically staged. To commit your changes, add a commit message and click
the 'Commit Button'. The file finder allows you to quickly open files in the current branch by
searching. The file finder is launched using the keyboard shortcut `Command-p`,
`Control-p`, or `t` (when editor is not in focus). Type the filename or
file path fragments to start seeing results.
## Stage and commit changes
After making your changes, click the Commit button in the bottom left to
review the list of changed files. Click on each file to review the changes and
click the tick icon to stage the file.
Once you have staged some changes, you can add a commit message and commit the
staged changes. Unstaged changes will not be commited.
![Commit changes](img/commit_changes.png) ![Commit changes](img/commit_changes.png)
## Comparing changes ## Reviewing changes
Before you commit your changes, you can compare them with the previous commit Before you commit your changes, you can compare them with the previous commit
by switching to the review mode or selecting the file from the staged files by switching to the review mode or selecting the file from the staged files
...@@ -30,4 +42,5 @@ list. ...@@ -30,4 +42,5 @@ list.
An additional review mode is available when you open a merge request, which An additional review mode is available when you open a merge request, which
shows you a preview of the merge request diff if you commit your changes. shows you a preview of the merge request diff if you commit your changes.
[ce]: https://about.gitlab.com/pricing/
[ee]: https://about.gitlab.com/pricing/ [ee]: https://about.gitlab.com/pricing/
...@@ -2,7 +2,7 @@ module API ...@@ -2,7 +2,7 @@ module API
class Issues < Grape::API class Issues < Grape::API
include PaginationParams include PaginationParams
before { authenticate! } before { authenticate_non_get! }
helpers ::Gitlab::IssuableMetadata helpers ::Gitlab::IssuableMetadata
...@@ -70,6 +70,7 @@ module API ...@@ -70,6 +70,7 @@ module API
desc: 'Return issues for the given scope: `created-by-me`, `assigned-to-me` or `all`' desc: 'Return issues for the given scope: `created-by-me`, `assigned-to-me` or `all`'
end end
get do get do
authenticate! unless params[:scope] == 'all'
issues = paginate(find_issues) issues = paginate(find_issues)
options = { options = {
......
...@@ -1467,25 +1467,11 @@ module Gitlab ...@@ -1467,25 +1467,11 @@ module Gitlab
end end
def branch_names_contains_sha(sha) def branch_names_contains_sha(sha)
gitaly_migrate(:branch_names_contains_sha, gitaly_ref_client.branch_names_contains_sha(sha)
status: Gitlab::GitalyClient::MigrationStatus::OPT_OUT) do |is_enabled|
if is_enabled
gitaly_ref_client.branch_names_contains_sha(sha)
else
refs_contains_sha('refs/heads/', sha)
end
end
end end
def tag_names_contains_sha(sha) def tag_names_contains_sha(sha)
gitaly_migrate(:tag_names_contains_sha, gitaly_ref_client.tag_names_contains_sha(sha)
status: Gitlab::GitalyClient::MigrationStatus::OPT_OUT) do |is_enabled|
if is_enabled
gitaly_ref_client.tag_names_contains_sha(sha)
else
refs_contains_sha('refs/tags/', sha)
end
end
end end
def search_files_by_content(query, ref) def search_files_by_content(query, ref)
...@@ -1620,27 +1606,6 @@ module Gitlab ...@@ -1620,27 +1606,6 @@ module Gitlab
end end
end end
def refs_contains_sha(refs_prefix, sha)
refs_prefix << "/" unless refs_prefix.ends_with?('/')
# By forcing the output to %(refname) each line wiht a ref will start with
# the ref prefix. All other lines can be discarded.
args = %W(for-each-ref --contains=#{sha} --format=%(refname) #{refs_prefix})
names, code = run_git(args)
return [] unless code.zero?
refs = []
left_slice_count = refs_prefix.length
names.lines.each do |line|
next unless line.start_with?(refs_prefix)
refs << encode_utf8(line.rstrip[left_slice_count..-1])
end
refs
end
def rugged_write_config(full_path:) def rugged_write_config(full_path:)
rugged.config['gitlab.fullpath'] = full_path rugged.config['gitlab.fullpath'] = full_path
end end
......
...@@ -28,7 +28,7 @@ module Gitlab ...@@ -28,7 +28,7 @@ module Gitlab
IMPORTED_OBJECT_MAX_RETRIES = 5.freeze IMPORTED_OBJECT_MAX_RETRIES = 5.freeze
EXISTING_OBJECT_CHECK = %i[milestone milestones label labels project_label project_labels group_label group_labels].freeze EXISTING_OBJECT_CHECK = %i[milestone milestones label labels project_label project_labels group_label group_labels project_feature].freeze
TOKEN_RESET_MODELS = %w[Ci::Trigger Ci::Build ProjectHook].freeze TOKEN_RESET_MODELS = %w[Ci::Trigger Ci::Build ProjectHook].freeze
......
...@@ -4,18 +4,6 @@ module Gitlab ...@@ -4,18 +4,6 @@ module Gitlab
CONTROLLER_KEY = 'action_controller.instance'.freeze CONTROLLER_KEY = 'action_controller.instance'.freeze
ENDPOINT_KEY = 'api.endpoint'.freeze ENDPOINT_KEY = 'api.endpoint'.freeze
CONTENT_TYPES = {
'text/html' => :html,
'text/plain' => :txt,
'application/json' => :json,
'text/js' => :js,
'application/atom+xml' => :atom,
'image/png' => :png,
'image/jpeg' => :jpeg,
'image/gif' => :gif,
'image/svg+xml' => :svg
}.freeze
def initialize(env) def initialize(env)
super() super()
@env = env @env = env
...@@ -40,7 +28,7 @@ module Gitlab ...@@ -40,7 +28,7 @@ module Gitlab
controller = @env[CONTROLLER_KEY] controller = @env[CONTROLLER_KEY]
action = "#{controller.action_name}" action = "#{controller.action_name}"
suffix = CONTENT_TYPES[controller.content_type] suffix = controller.request_format
if suffix && suffix != :html if suffix && suffix != :html
action += ".#{suffix}" action += ".#{suffix}"
......
...@@ -8,8 +8,8 @@ msgid "" ...@@ -8,8 +8,8 @@ msgid ""
msgstr "" msgstr ""
"Project-Id-Version: gitlab 1.0.0\n" "Project-Id-Version: gitlab 1.0.0\n"
"Report-Msgid-Bugs-To: \n" "Report-Msgid-Bugs-To: \n"
"POT-Creation-Date: 2018-05-09 09:24+0200\n" "POT-Creation-Date: 2018-05-14 10:49+0200\n"
"PO-Revision-Date: 2018-05-09 09:24+0200\n" "PO-Revision-Date: 2018-05-14 10:49+0200\n"
"Last-Translator: FULL NAME <EMAIL@ADDRESS>\n" "Last-Translator: FULL NAME <EMAIL@ADDRESS>\n"
"Language-Team: LANGUAGE <LL@li.org>\n" "Language-Team: LANGUAGE <LL@li.org>\n"
"Language: \n" "Language: \n"
...@@ -72,6 +72,9 @@ msgstr[1] "" ...@@ -72,6 +72,9 @@ msgstr[1] ""
msgid "%{loadingIcon} Started" msgid "%{loadingIcon} Started"
msgstr "" msgstr ""
msgid "%{nip_domain} can be used as an alternative to a custom domain."
msgstr ""
msgid "%{number_commits_behind} commits behind %{default_branch}, %{number_commits_ahead} commits ahead" msgid "%{number_commits_behind} commits behind %{default_branch}, %{number_commits_ahead} commits ahead"
msgstr "" msgstr ""
...@@ -737,6 +740,9 @@ msgstr "" ...@@ -737,6 +740,9 @@ msgstr ""
msgid "CI/CD settings" msgid "CI/CD settings"
msgstr "" msgstr ""
msgid "CICD|A domain is required to use Auto Review Apps and Auto Deploy Stages."
msgstr ""
msgid "CICD|An explicit %{ci_file} needs to be specified before you can begin using Continuous Integration and Delivery." msgid "CICD|An explicit %{ci_file} needs to be specified before you can begin using Continuous Integration and Delivery."
msgstr "" msgstr ""
...@@ -767,9 +773,6 @@ msgstr "" ...@@ -767,9 +773,6 @@ msgstr ""
msgid "CICD|The Auto DevOps pipeline configuration will be used when there is no %{ci_file} in the project." msgid "CICD|The Auto DevOps pipeline configuration will be used when there is no %{ci_file} in the project."
msgstr "" msgstr ""
msgid "CICD|You need to specify a domain if you want to use Auto Review Apps and Auto Deploy stages."
msgstr ""
msgid "Can run untagged jobs" msgid "Can run untagged jobs"
msgstr "" msgstr ""
......
...@@ -3,14 +3,8 @@ require 'bundler/setup' ...@@ -3,14 +3,8 @@ require 'bundler/setup'
ENV['GITLAB_ENV'] = 'test' ENV['GITLAB_ENV'] = 'test'
ENV['IN_MEMORY_APPLICATION_SETTINGS'] = 'true' ENV['IN_MEMORY_APPLICATION_SETTINGS'] = 'true'
unless Object.respond_to?(:require_dependency)
class Object
alias_method :require_dependency, :require
end
end
# Defines Settings and Gitlab.config which are at the center of the app
require_relative '../config/settings' require_relative '../config/settings'
require_relative '../lib/gitlab' unless defined?(Gitlab.config)
require_relative 'support/rspec' require_relative 'support/rspec'
require 'active_support/all'
ActiveSupport::Dependencies.autoload_paths << 'lib'
...@@ -75,6 +75,29 @@ describe "Projects > Settings > Pipelines settings" do ...@@ -75,6 +75,29 @@ describe "Projects > Settings > Pipelines settings" do
expect(project.auto_devops).not_to be_enabled expect(project.auto_devops).not_to be_enabled
expect(project.auto_devops.domain).to eq('test.com') expect(project.auto_devops.domain).to eq('test.com')
end end
context 'when there is a cluster with ingress and external_ip' do
before do
cluster = create(:cluster, projects: [project])
cluster.create_application_ingress!(external_ip: '192.168.1.100')
end
it 'shows the help text with the nip.io domain as an alternative to custom domain' do
visit project_settings_ci_cd_path(project)
expect(page).to have_content('192.168.1.100.nip.io can be used as an alternative to a custom domain')
end
end
context 'when there is no ingress' do
before do
create(:cluster, projects: [project])
end
it 'alternative to custom domain is not shown' do
visit project_settings_ci_cd_path(project)
expect(page).not_to have_content('can be used as an alternative to a custom domain')
end
end
end end
end end
end end
...@@ -615,32 +615,22 @@ describe Gitlab::Git::Repository, seed_helper: true do ...@@ -615,32 +615,22 @@ describe Gitlab::Git::Repository, seed_helper: true do
end end
describe '#branch_names_contains_sha' do describe '#branch_names_contains_sha' do
shared_examples 'returning the right branches' do let(:head_id) { repository.rugged.head.target.oid }
let(:head_id) { repository.rugged.head.target.oid } let(:new_branch) { head_id }
let(:new_branch) { head_id } let(:utf8_branch) { 'branch-é' }
let(:utf8_branch) { 'branch-é' }
before do before do
repository.create_branch(new_branch, 'master') repository.create_branch(new_branch, 'master')
repository.create_branch(utf8_branch, 'master') repository.create_branch(utf8_branch, 'master')
end
after do
repository.delete_branch(new_branch)
repository.delete_branch(utf8_branch)
end
it 'displays that branch' do
expect(repository.branch_names_contains_sha(head_id)).to include('master', new_branch, utf8_branch)
end
end end
context 'when Gitaly is enabled' do after do
it_behaves_like 'returning the right branches' repository.delete_branch(new_branch)
repository.delete_branch(utf8_branch)
end end
context 'when Gitaly is disabled', :disable_gitaly do it 'displays that branch' do
it_behaves_like 'returning the right branches' expect(repository.branch_names_contains_sha(head_id)).to include('master', new_branch, utf8_branch)
end end
end end
......
...@@ -185,6 +185,7 @@ project: ...@@ -185,6 +185,7 @@ project:
- cluster - cluster
- clusters - clusters
- cluster_project - cluster_project
- cluster_ingresses
- creator - creator
- group - group
- namespace - namespace
......
...@@ -180,11 +180,11 @@ describe Gitlab::Metrics::WebTransaction do ...@@ -180,11 +180,11 @@ describe Gitlab::Metrics::WebTransaction do
end end
context 'when request goes to ActionController' do context 'when request goes to ActionController' do
let(:content_type) { 'text/html' } let(:request_format) { :html }
before do before do
klass = double(:klass, name: 'TestController') klass = double(:klass, name: 'TestController')
controller = double(:controller, class: klass, action_name: 'show', content_type: content_type) controller = double(:controller, class: klass, action_name: 'show', request_format: request_format)
env['action_controller.instance'] = controller env['action_controller.instance'] = controller
end end
...@@ -195,7 +195,7 @@ describe Gitlab::Metrics::WebTransaction do ...@@ -195,7 +195,7 @@ describe Gitlab::Metrics::WebTransaction do
end end
context 'when the response content type is not :html' do context 'when the response content type is not :html' do
let(:content_type) { 'application/json' } let(:request_format) { :json }
it 'appends the mime type to the transaction action' do it 'appends the mime type to the transaction action' do
expect(transaction.labels).to eq({ controller: 'TestController', action: 'show.json' }) expect(transaction.labels).to eq({ controller: 'TestController', action: 'show.json' })
......
require 'spec_helper'
require Rails.root.join('db', 'post_migrate', '20180511174224_add_unique_constraint_to_project_features_project_id.rb')
describe AddUniqueConstraintToProjectFeaturesProjectId, :migration do
let(:namespaces) { table(:namespaces) }
let(:projects) { table(:projects) }
let(:features) { table(:project_features) }
let(:migration) { described_class.new }
describe '#up' do
before do
(1..3).each do |i|
namespaces.create(id: i, name: "ns-test-#{i}", path: "ns-test-i#{i}")
projects.create!(id: i, name: "test-#{i}", path: "test-#{i}", namespace_id: i)
end
features.create!(id: 1, project_id: 1)
features.create!(id: 2, project_id: 1)
features.create!(id: 3, project_id: 2)
features.create!(id: 4, project_id: 2)
features.create!(id: 5, project_id: 2)
features.create!(id: 6, project_id: 3)
end
it 'creates a unique index and removes duplicates' do
expect(migration.index_exists?(:project_features, :project_id, unique: false, name: 'index_project_features_on_project_id')).to be true
expect { migration.up }.to change { features.count }.from(6).to(3)
expect(migration.index_exists?(:project_features, :project_id, unique: true, name: 'index_project_features_on_project_id')).to be true
expect(migration.index_exists?(:project_features, :project_id, name: 'index_project_features_on_project_id_unique')).to be false
project_1_features = features.where(project_id: 1)
expect(project_1_features.count).to eq(1)
expect(project_1_features.first.id).to eq(2)
project_2_features = features.where(project_id: 2)
expect(project_2_features.count).to eq(1)
expect(project_2_features.first.id).to eq(5)
project_3_features = features.where(project_id: 3)
expect(project_3_features.count).to eq(1)
expect(project_3_features.first.id).to eq(6)
end
end
describe '#down' do
it 'restores the original index' do
migration.up
expect(migration.index_exists?(:project_features, :project_id, unique: true, name: 'index_project_features_on_project_id')).to be true
migration.down
expect(migration.index_exists?(:project_features, :project_id, unique: false, name: 'index_project_features_on_project_id')).to be true
expect(migration.index_exists?(:project_features, :project_id, name: 'index_project_features_on_project_id_old')).to be false
end
end
end
require 'spec_helper' require 'spec_helper'
describe Guest do describe Guest do
let(:public_project) { build_stubbed(:project, :public) } set(:public_project) { create(:project, :public) }
let(:private_project) { build_stubbed(:project, :private) } set(:private_project) { create(:project, :private) }
let(:internal_project) { build_stubbed(:project, :internal) } set(:internal_project) { create(:project, :internal) }
describe '.can_pull?' do describe '.can_pull?' do
context 'when project is private' do context 'when project is private' do
......
...@@ -2755,4 +2755,18 @@ describe User do ...@@ -2755,4 +2755,18 @@ describe User do
it { is_expected.to be_truthy } it { is_expected.to be_truthy }
end end
end end
describe '#increment_failed_attempts!' do
subject(:user) { create(:user, failed_attempts: 0) }
it 'logs failed sign-in attempts' do
expect { user.increment_failed_attempts! }.to change(user, :failed_attempts).from(0).to(1)
end
it 'does not log failed sign-in attempts when in a GitLab read-only instance' do
allow(Gitlab::Database).to receive(:read_only?) { true }
expect { user.increment_failed_attempts! }.not_to change(user, :failed_attempts)
end
end
end end
...@@ -64,12 +64,32 @@ describe API::Issues do ...@@ -64,12 +64,32 @@ describe API::Issues do
describe "GET /issues" do describe "GET /issues" do
context "when unauthenticated" do context "when unauthenticated" do
it "returns authentication error" do it "returns an array of all issues" do
get api("/issues"), scope: 'all'
expect(response).to have_http_status(200)
expect(json_response).to be_an Array
end
it "returns authentication error without any scope" do
get api("/issues") get api("/issues")
expect(response).to have_gitlab_http_status(401) expect(response).to have_http_status(401)
end
it "returns authentication error when scope is assigned-to-me" do
get api("/issues"), scope: 'assigned-to-me'
expect(response).to have_http_status(401)
end
it "returns authentication error when scope is created-by-me" do
get api("/issues"), scope: 'created-by-me'
expect(response).to have_http_status(401)
end end
end end
context "when authenticated" do context "when authenticated" do
let(:first_issue) { json_response.first } let(:first_issue) { json_response.first }
...@@ -379,9 +399,6 @@ describe API::Issues do ...@@ -379,9 +399,6 @@ describe API::Issues do
end end
let!(:group_note) { create(:note_on_issue, author: user, project: group_project, noteable: group_issue) } let!(:group_note) { create(:note_on_issue, author: user, project: group_project, noteable: group_issue) }
before do
group_project.add_reporter(user)
end
let(:base_url) { "/groups/#{group.id}/issues" } let(:base_url) { "/groups/#{group.id}/issues" }
context 'when group has subgroups', :nested_groups do context 'when group has subgroups', :nested_groups do
...@@ -408,178 +425,201 @@ describe API::Issues do ...@@ -408,178 +425,201 @@ describe API::Issues do
end end
end end
it 'returns all group issues (including opened and closed)' do context 'when user is unauthenticated' do
get api(base_url, admin) it 'lists all issues in public projects' do
get api(base_url)
expect_paginated_array_response(size: 3) expect_paginated_array_response(size: 2)
end
end end
it 'returns group issues without confidential issues for non project members' do context 'when user is a group member' do
get api("#{base_url}?state=opened", non_member) before do
group_project.add_reporter(user)
end
expect_paginated_array_response(size: 1) it 'returns all group issues (including opened and closed)' do
expect(json_response.first['title']).to eq(group_issue.title) get api(base_url, admin)
end
it 'returns group confidential issues for author' do expect_paginated_array_response(size: 3)
get api("#{base_url}?state=opened", author) end
expect_paginated_array_response(size: 2) it 'returns group issues without confidential issues for non project members' do
end get api("#{base_url}?state=opened", non_member)
it 'returns group confidential issues for assignee' do expect_paginated_array_response(size: 1)
get api("#{base_url}?state=opened", assignee) expect(json_response.first['title']).to eq(group_issue.title)
end
expect_paginated_array_response(size: 2) it 'returns group confidential issues for author' do
end get api("#{base_url}?state=opened", author)
it 'returns group issues with confidential issues for project members' do expect_paginated_array_response(size: 2)
get api("#{base_url}?state=opened", user) end
expect_paginated_array_response(size: 2) it 'returns group confidential issues for assignee' do
end get api("#{base_url}?state=opened", assignee)
it 'returns group confidential issues for admin' do expect_paginated_array_response(size: 2)
get api("#{base_url}?state=opened", admin) end
expect_paginated_array_response(size: 2) it 'returns group issues with confidential issues for project members' do
end get api("#{base_url}?state=opened", user)
it 'returns an array of labeled group issues' do expect_paginated_array_response(size: 2)
get api("#{base_url}?labels=#{group_label.title}", user) end
expect_paginated_array_response(size: 1) it 'returns group confidential issues for admin' do
expect(json_response.first['labels']).to eq([group_label.title]) get api("#{base_url}?state=opened", admin)
end
it 'returns an array of labeled group issues where all labels match' do expect_paginated_array_response(size: 2)
get api("#{base_url}?labels=#{group_label.title},foo,bar", user) end
expect_paginated_array_response(size: 0) it 'returns an array of labeled group issues' do
end get api("#{base_url}?labels=#{group_label.title}", user)
it 'returns issues matching given search string for title' do expect_paginated_array_response(size: 1)
get api("#{base_url}?search=#{group_issue.title}", user) expect(json_response.first['labels']).to eq([group_label.title])
end
expect_paginated_array_response(size: 1) it 'returns an array of labeled group issues where all labels match' do
expect(json_response.first['id']).to eq(group_issue.id) get api("#{base_url}?labels=#{group_label.title},foo,bar", user)
end
it 'returns issues matching given search string for description' do expect_paginated_array_response(size: 0)
get api("#{base_url}?search=#{group_issue.description}", user) end
expect_paginated_array_response(size: 1) it 'returns issues matching given search string for title' do
expect(json_response.first['id']).to eq(group_issue.id) get api("#{base_url}?search=#{group_issue.title}", user)
end
it 'returns an array of labeled issues when all labels matches' do expect_paginated_array_response(size: 1)
label_b = create(:label, title: 'foo', project: group_project) expect(json_response.first['id']).to eq(group_issue.id)
label_c = create(:label, title: 'bar', project: group_project) end
create(:label_link, label: label_b, target: group_issue) it 'returns issues matching given search string for description' do
create(:label_link, label: label_c, target: group_issue) get api("#{base_url}?search=#{group_issue.description}", user)
get api("#{base_url}", user), labels: "#{group_label.title},#{label_b.title},#{label_c.title}" expect_paginated_array_response(size: 1)
expect(json_response.first['id']).to eq(group_issue.id)
end
expect_paginated_array_response(size: 1) it 'returns an array of labeled issues when all labels matches' do
expect(json_response.first['labels']).to eq([label_c.title, label_b.title, group_label.title]) label_b = create(:label, title: 'foo', project: group_project)
end label_c = create(:label, title: 'bar', project: group_project)
it 'returns an array of issues found by iids' do create(:label_link, label: label_b, target: group_issue)
get api(base_url, user), iids: [group_issue.iid] create(:label_link, label: label_c, target: group_issue)
expect_paginated_array_response(size: 1) get api("#{base_url}", user), labels: "#{group_label.title},#{label_b.title},#{label_c.title}"
expect(json_response.first['id']).to eq(group_issue.id)
end
it 'returns an empty array if iid does not exist' do expect_paginated_array_response(size: 1)
get api(base_url, user), iids: [99999] expect(json_response.first['labels']).to eq([label_c.title, label_b.title, group_label.title])
end
expect_paginated_array_response(size: 0) it 'returns an array of issues found by iids' do
end get api(base_url, user), iids: [group_issue.iid]
it 'returns an empty array if no group issue matches labels' do expect_paginated_array_response(size: 1)
get api("#{base_url}?labels=foo,bar", user) expect(json_response.first['id']).to eq(group_issue.id)
end
expect_paginated_array_response(size: 0) it 'returns an empty array if iid does not exist' do
end get api(base_url, user), iids: [99999]
it 'returns an empty array if no issue matches milestone' do expect_paginated_array_response(size: 0)
get api("#{base_url}?milestone=#{group_empty_milestone.title}", user) end
expect_paginated_array_response(size: 0) it 'returns an empty array if no group issue matches labels' do
end get api("#{base_url}?labels=foo,bar", user)
it 'returns an empty array if milestone does not exist' do expect_paginated_array_response(size: 0)
get api("#{base_url}?milestone=foo", user) end
expect_paginated_array_response(size: 0) it 'returns an empty array if no issue matches milestone' do
end get api("#{base_url}?milestone=#{group_empty_milestone.title}", user)
it 'returns an array of issues in given milestone' do expect_paginated_array_response(size: 0)
get api("#{base_url}?state=opened&milestone=#{group_milestone.title}", user) end
expect_paginated_array_response(size: 1) it 'returns an empty array if milestone does not exist' do
expect(json_response.first['id']).to eq(group_issue.id) get api("#{base_url}?milestone=foo", user)
end
it 'returns an array of issues matching state in milestone' do expect_paginated_array_response(size: 0)
get api("#{base_url}?milestone=#{group_milestone.title}"\ end
'&state=closed', user)
expect_paginated_array_response(size: 1) it 'returns an array of issues in given milestone' do
expect(json_response.first['id']).to eq(group_closed_issue.id) get api("#{base_url}?state=opened&milestone=#{group_milestone.title}", user)
end
it 'returns an array of issues with no milestone' do expect_paginated_array_response(size: 1)
get api("#{base_url}?milestone=#{no_milestone_title}", user) expect(json_response.first['id']).to eq(group_issue.id)
end
expect(response).to have_gitlab_http_status(200) it 'returns an array of issues matching state in milestone' do
get api("#{base_url}?milestone=#{group_milestone.title}"\
'&state=closed', user)
expect_paginated_array_response(size: 1) expect_paginated_array_response(size: 1)
expect(json_response.first['id']).to eq(group_confidential_issue.id) expect(json_response.first['id']).to eq(group_closed_issue.id)
end end
it 'sorts by created_at descending by default' do it 'returns an array of issues with no milestone' do
get api(base_url, user) get api("#{base_url}?milestone=#{no_milestone_title}", user)
response_dates = json_response.map { |issue| issue['created_at'] } expect(response).to have_gitlab_http_status(200)
expect_paginated_array_response(size: 3) expect_paginated_array_response(size: 1)
expect(response_dates).to eq(response_dates.sort.reverse) expect(json_response.first['id']).to eq(group_confidential_issue.id)
end end
it 'sorts ascending when requested' do it 'sorts by created_at descending by default' do
get api("#{base_url}?sort=asc", user) get api(base_url, user)
response_dates = json_response.map { |issue| issue['created_at'] } response_dates = json_response.map { |issue| issue['created_at'] }
expect_paginated_array_response(size: 3) expect_paginated_array_response(size: 3)
expect(response_dates).to eq(response_dates.sort) expect(response_dates).to eq(response_dates.sort.reverse)
end end
it 'sorts by updated_at descending when requested' do it 'sorts ascending when requested' do
get api("#{base_url}?order_by=updated_at", user) get api("#{base_url}?sort=asc", user)
response_dates = json_response.map { |issue| issue['updated_at'] } response_dates = json_response.map { |issue| issue['created_at'] }
expect_paginated_array_response(size: 3) expect_paginated_array_response(size: 3)
expect(response_dates).to eq(response_dates.sort.reverse) expect(response_dates).to eq(response_dates.sort)
end end
it 'sorts by updated_at ascending when requested' do it 'sorts by updated_at descending when requested' do
get api("#{base_url}?order_by=updated_at&sort=asc", user) get api("#{base_url}?order_by=updated_at", user)
response_dates = json_response.map { |issue| issue['updated_at'] } response_dates = json_response.map { |issue| issue['updated_at'] }
expect_paginated_array_response(size: 3) expect_paginated_array_response(size: 3)
expect(response_dates).to eq(response_dates.sort) expect(response_dates).to eq(response_dates.sort.reverse)
end
it 'sorts by updated_at ascending when requested' do
get api("#{base_url}?order_by=updated_at&sort=asc", user)
response_dates = json_response.map { |issue| issue['updated_at'] }
expect_paginated_array_response(size: 3)
expect(response_dates).to eq(response_dates.sort)
end
end end
end end
describe "GET /projects/:id/issues" do describe "GET /projects/:id/issues" do
let(:base_url) { "/projects/#{project.id}" } let(:base_url) { "/projects/#{project.id}" }
context 'when unauthenticated' do
it 'returns public project issues' do
get api("/projects/#{project.id}/issues")
expect_paginated_array_response(size: 2)
expect(json_response.first['title']).to eq(issue.title)
end
end
it 'avoids N+1 queries' do it 'avoids N+1 queries' do
control_count = ActiveRecord::QueryRecorder.new do control_count = ActiveRecord::QueryRecorder.new do
get api("/projects/#{project.id}/issues", user) get api("/projects/#{project.id}/issues", user)
...@@ -789,6 +829,14 @@ describe API::Issues do ...@@ -789,6 +829,14 @@ describe API::Issues do
end end
describe "GET /projects/:id/issues/:issue_iid" do describe "GET /projects/:id/issues/:issue_iid" do
context 'when unauthenticated' do
it 'returns public issues' do
get api("/projects/#{project.id}/issues/#{issue.iid}")
expect(response).to have_gitlab_http_status(200)
end
end
it 'exposes known attributes' do it 'exposes known attributes' do
get api("/projects/#{project.id}/issues/#{issue.iid}", user) get api("/projects/#{project.id}/issues/#{issue.iid}", user)
...@@ -1581,6 +1629,14 @@ describe API::Issues do ...@@ -1581,6 +1629,14 @@ describe API::Issues do
create(:merge_requests_closing_issues, issue: issue, merge_request: merge_request) create(:merge_requests_closing_issues, issue: issue, merge_request: merge_request)
end end
context 'when unauthenticated' do
it 'return public project issues' do
get api("/projects/#{project.id}/issues/#{issue.iid}/closed_by")
expect_paginated_array_response(size: 1)
end
end
it 'returns merge requests that will close issue on merge' do it 'returns merge requests that will close issue on merge' do
get api("/projects/#{project.id}/issues/#{issue.iid}/closed_by", user) get api("/projects/#{project.id}/issues/#{issue.iid}/closed_by", user)
...@@ -1605,6 +1661,14 @@ describe API::Issues do ...@@ -1605,6 +1661,14 @@ describe API::Issues do
describe "GET /projects/:id/issues/:issue_iid/user_agent_detail" do describe "GET /projects/:id/issues/:issue_iid/user_agent_detail" do
let!(:user_agent_detail) { create(:user_agent_detail, subject: issue) } let!(:user_agent_detail) { create(:user_agent_detail, subject: issue) }
context 'when unauthenticated' do
it "returns unautorized" do
get api("/projects/#{project.id}/issues/#{issue.iid}/user_agent_detail")
expect(response).to have_gitlab_http_status(401)
end
end
it 'exposes known attributes' do it 'exposes known attributes' do
get api("/projects/#{project.id}/issues/#{issue.iid}/user_agent_detail", admin) get api("/projects/#{project.id}/issues/#{issue.iid}/user_agent_detail", admin)
......
...@@ -159,7 +159,9 @@ describe 'OpenID Connect requests' do ...@@ -159,7 +159,9 @@ describe 'OpenID Connect requests' do
get '/.well-known/openid-configuration' get '/.well-known/openid-configuration'
expect(response).to have_gitlab_http_status(200) expect(response).to have_gitlab_http_status(200)
expect(json_response).to have_key('issuer') expect(json_response['issuer']).to eq('http://localhost')
expect(json_response['jwks_uri']).to eq('http://www.example.com/oauth/discovery/keys')
expect(json_response['scopes_supported']).to eq(%w[api read_user sudo read_repository openid])
end end
end end
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