Commit 5049eb42 authored by Rémy Coutable's avatar Rémy Coutable

Merge remote-tracking branch 'origin/master' into ce-to-ee-2017-10-11

Signed-off-by: default avatarRémy Coutable <remy@rymai.me>
parents edb14ef4 bd7ba308
......@@ -20,7 +20,9 @@ class GeoNodeStatus {
this.$repositoriesSynced = $('.js-repositories-synced', this.$status);
this.$repositoriesFailed = $('.js-repositories-failed', this.$status);
this.$lfsObjectsSynced = $('.js-lfs-objects-synced', this.$status);
this.$lfsObjectsFailed = $('.js-lfs-objects-failed', this.$status);
this.$attachmentsSynced = $('.js-attachments-synced', this.$status);
this.$attachmentsFailed = $('.js-attachments-failed', this.$status);
this.$lastEventSeen = $('.js-last-event-seen', this.$status);
this.$lastCursorEvent = $('.js-last-cursor-event', this.$status);
this.$health = $('.js-health', this.$status);
......@@ -78,15 +80,21 @@ class GeoNodeStatus {
status.lfs_objects_count,
status.lfs_objects_synced_in_percentage);
const lfsFailedText = gl.text.addDelimiter(status.lfs_objects_failed_count);
const attachmentText = GeoNodeStatus.formatCountAndPercentage(
status.attachments_synced_count,
status.attachments_count,
status.attachments_synced_in_percentage);
const attachmentFailedText = gl.text.addDelimiter(status.attachments_failed_count);
this.$repositoriesSynced.text(repoText);
this.$repositoriesFailed.text(repoFailedText);
this.$lfsObjectsSynced.text(lfsText);
this.$lfsObjectsFailed.text(lfsFailedText);
this.$attachmentsSynced.text(attachmentText);
this.$attachmentsFailed.text(attachmentFailedText);
const eventDate = gl.utils.formatDate(new Date(status.last_event_date));
const cursorDate = gl.utils.formatDate(new Date(status.cursor_last_event_date));
......
......@@ -285,7 +285,7 @@ import CreateLabelDropdown from './create_label';
},
hidden: function() {
var isIssueIndex, isMRIndex, page, selectedLabels;
page = $('body').data('page');
page = $('body').attr('data-page');
isIssueIndex = page === 'projects:issues:index';
isMRIndex = page === 'projects:merge_requests:index';
$selectbox.hide();
......@@ -325,7 +325,7 @@ import CreateLabelDropdown from './create_label';
$loading.fadeOut();
};
page = $('body').data('page');
page = $('body').attr('data-page');
isIssueIndex = page === 'projects:issues:index';
isMRIndex = page === 'projects:merge_requests:index';
......
export const getPagePath = (index = 0) => $('body').data('page').split(':')[index];
export const getPagePath = (index = 0) => $('body').attr('data-page').split(':')[index];
export const isInGroupsPage = () => getPagePath() === 'groups';
......
......@@ -170,7 +170,7 @@ import _ from 'underscore';
var data, isIssueIndex, isMRIndex, isSelecting, page, boardsStore;
if (!selected) return;
page = $('body').data('page');
page = $('body').attr('data-page');
isIssueIndex = page === 'projects:issues:index';
isMRIndex = (page === page && page === 'projects:merge_requests:index');
isSelecting = (selected.name !== selectedMilestone);
......
......@@ -1257,7 +1257,7 @@ export default class Notes {
}
static checkMergeRequestStatus() {
if (getPagePath(1) === 'merge_requests') {
if (getPagePath(1) === 'merge_requests' && gl.mrWidget) {
gl.mrWidget.checkStatus();
}
}
......
......@@ -424,7 +424,7 @@ function UsersSelect(currentUser, els) {
}
var isIssueIndex, isMRIndex, page, selected;
page = $('body').data('page');
page = $('body').attr('data-page');
isIssueIndex = page === 'projects:issues:index';
isMRIndex = (page === page && page === 'projects:merge_requests:index');
if ($dropdown.hasClass('js-filter-bulk-update') || $dropdown.hasClass('js-issuable-form-dropdown')) {
......
......@@ -132,22 +132,6 @@
}
}
.disabled-comment .issuable-note-warning {
border: none;
border-radius: $label-border-radius;
padding-top: $gl-vert-padding;
padding-bottom: $gl-vert-padding;
.icon svg {
position: relative;
top: 2px;
margin-right: $btn-xs-side-margin;
width: $gl-font-size;
height: $gl-font-size;
fill: $orange-600;
}
}
.sidebar-item-value {
.fa {
background-color: inherit;
......
......@@ -558,6 +558,17 @@ a.deploy-project-label {
}
}
.fork-thumbnail-container {
display: flex;
flex-wrap: wrap;
margin-left: -$gl-padding;
margin-right: -$gl-padding;
> h5 {
width: 100%;
}
}
.project-template {
> .form-group {
margin-bottom: 0;
......@@ -646,8 +657,15 @@ a.deploy-project-label {
margin-bottom: 0;
}
.import-btn-container {
margin-bottom: 0;
}
.toggle-import-form {
padding-bottom: 10px;
}
.import-buttons {
padding-left: 0;
display: -webkit-flex;
display: flex;
-webkit-flex-wrap: wrap;
......
class Geo::FileRegistry < Geo::BaseRegistry
scope :failed, -> { where(success: false) }
scope :synced, -> { where(success: true) }
end
......@@ -100,7 +100,7 @@ class GeoNodeStatus
def lfs_objects_synced_count
@lfs_objects_synced_count ||= begin
relation = Geo::FileRegistry.where(file_type: :lfs)
relation = Geo::FileRegistry.synced.where(file_type: :lfs)
if Gitlab::Geo.current_node.restricted_project_ids
relation = relation.where(file_id: lfs_objects.pluck(:id))
......@@ -114,6 +114,14 @@ class GeoNodeStatus
@lfs_objects_synced_count = value.to_i
end
def lfs_objects_failed_count
@lfs_objects_failed_count ||= Geo::FileRegistry.failed.where(file_type: :lfs).count
end
def lfs_objects_failed_count=(value)
@lfs_objects_failed_count = value.to_i
end
def lfs_objects_synced_in_percentage
sync_percentage(lfs_objects_count, lfs_objects_synced_count)
end
......@@ -129,7 +137,7 @@ class GeoNodeStatus
def attachments_synced_count
@attachments_synced_count ||= begin
upload_ids = attachments.pluck(:id)
synced_ids = Geo::FileRegistry.where(file_type: [:attachment, :avatar, :file]).pluck(:file_id)
synced_ids = Geo::FileRegistry.synced.where(file_type: Geo::FileService::DEFAULT_OBJECT_TYPES).pluck(:file_id)
(synced_ids & upload_ids).length
end
......@@ -139,6 +147,14 @@ class GeoNodeStatus
@attachments_synced_count = value.to_i
end
def attachments_failed_count
@attachments_failed_count ||= Geo::FileRegistry.failed.where(file_type: Geo::FileService::DEFAULT_OBJECT_TYPES).count
end
def attachments_failed_count=(value)
@attachments_failed_count = value.to_i
end
def attachments_synced_in_percentage
sync_percentage(attachments_count, attachments_synced_count)
end
......
......@@ -10,6 +10,7 @@ class GeoNodeStatusEntity < Grape::Entity
expose :attachments_count
expose :attachments_synced_count
expose :attachments_failed_count
expose :attachments_synced_in_percentage do |node|
number_to_percentage(node.attachments_synced_in_percentage, precision: 2)
end
......@@ -18,6 +19,7 @@ class GeoNodeStatusEntity < Grape::Entity
expose :lfs_objects_count
expose :lfs_objects_synced_count
expose :lfs_objects_failed_count
expose :lfs_objects_synced_in_percentage do |node|
number_to_percentage(node.lfs_objects_synced_in_percentage, precision: 2)
end
......
......@@ -11,7 +11,7 @@ module Geo
success: success,
bytes_downloaded: bytes_downloaded,
download_time_s: (Time.now - start_time).to_f.round(3))
update_registry(bytes_downloaded) if success
update_registry(bytes_downloaded, success: success)
end
end
......@@ -37,13 +37,14 @@ module Geo
end
end
def update_registry(bytes_downloaded)
def update_registry(bytes_downloaded, success:)
transfer = Geo::FileRegistry.find_or_initialize_by(
file_type: object_type,
file_id: object_db_id
)
transfer.bytes = bytes_downloaded
transfer.success = success
transfer.save
end
......
......@@ -62,10 +62,18 @@
%span.help-block
LFS objects synced:
%strong.node-info.js-lfs-objects-synced
%p
%span.help-block
LFS objects failed:
%strong.node-info.js-lfs-objects-failed
%p
%span.help-block
Attachments synced:
%strong.node-info.js-attachments-synced
%p
%span.help-block
Attachments failed:
%strong.node-info.js-attachments-failed
%p
.advanced-geo-node-status-container
.advanced-status.hidden
......
......@@ -48,52 +48,54 @@
.tab-pane.import-project-pane{ id: 'import-project-pane', role: 'tabpanel' }
= form_for @project, html: { class: 'new_project' } do |f|
- if import_sources_enabled?
.project-import
.form-group.clearfix
= f.label :visibility_level, class: 'label-light' do #the label here seems wrong
Import project from
.col-sm-12.import-buttons
%div
- if github_import_enabled?
= link_to new_import_github_path, class: 'btn import_github' do
= icon('github', text: 'GitHub')
%div
- if bitbucket_import_enabled?
= link_to status_import_bitbucket_path, class: "btn import_bitbucket #{'how_to_import_link' unless bitbucket_import_configured?}" do
= icon('bitbucket', text: 'Bitbucket')
- unless bitbucket_import_configured?
= render 'bitbucket_import_modal'
%div
- if gitlab_import_enabled?
= link_to status_import_gitlab_path, class: "btn import_gitlab #{'how_to_import_link' unless gitlab_import_configured?}" do
= icon('gitlab', text: 'GitLab.com')
- unless gitlab_import_configured?
= render 'gitlab_import_modal'
%div
- if google_code_import_enabled?
= link_to new_import_google_code_path, class: 'btn import_google_code' do
= icon('google', text: 'Google Code')
%div
- if fogbugz_import_enabled?
= link_to new_import_fogbugz_path, class: 'btn import_fogbugz' do
= icon('bug', text: 'Fogbugz')
%div
- if gitea_import_enabled?
= link_to new_import_gitea_url, class: 'btn import_gitea' do
= custom_icon('go_logo')
Gitea
%div
- if git_import_enabled?
%button.btn.js-toggle-button.import_git{ type: "button" }
= icon('git', text: 'Repo by URL')
- if gitlab_project_import_enabled?
.import_gitlab_project.has-tooltip{ data: { container: 'body' } }
= link_to new_import_gitlab_project_path, class: 'btn btn_import_gitlab_project project-submit' do
= icon('gitlab', text: 'GitLab export')
.col-lg-12
.js-toggle-content.hide
%hr
= render "shared/import_form", f: f
.project-import.row
.col-sm-12
.form-group.import-btn-container.clearfix
= f.label :visibility_level, class: 'label-light' do #the label here seems wrong
Import project from
.import-buttons
%div
- if github_import_enabled?
= link_to new_import_github_path, class: 'btn import_github' do
= icon('github', text: 'GitHub')
%div
- if bitbucket_import_enabled?
= link_to status_import_bitbucket_path, class: "btn import_bitbucket #{'how_to_import_link' unless bitbucket_import_configured?}" do
= icon('bitbucket', text: 'Bitbucket')
- unless bitbucket_import_configured?
= render 'bitbucket_import_modal'
%div
- if gitlab_import_enabled?
= link_to status_import_gitlab_path, class: "btn import_gitlab #{'how_to_import_link' unless gitlab_import_configured?}" do
= icon('gitlab', text: 'GitLab.com')
- unless gitlab_import_configured?
= render 'gitlab_import_modal'
%div
- if google_code_import_enabled?
= link_to new_import_google_code_path, class: 'btn import_google_code' do
= icon('google', text: 'Google Code')
%div
- if fogbugz_import_enabled?
= link_to new_import_fogbugz_path, class: 'btn import_fogbugz' do
= icon('bug', text: 'Fogbugz')
%div
- if gitea_import_enabled?
= link_to new_import_gitea_url, class: 'btn import_gitea' do
= custom_icon('go_logo')
Gitea
%div
- if git_import_enabled?
%button.btn.js-toggle-button.import_git{ type: "button" }
= icon('git', text: 'Repo by URL')
- if gitlab_project_import_enabled?
.import_gitlab_project.has-tooltip{ data: { container: 'body' } }
= link_to new_import_gitlab_project_path, class: 'btn btn_import_gitlab_project project-submit' do
= icon('gitlab', text: 'GitLab export')
.col-lg-12
.js-toggle-content.hide.toggle-import-form
%hr
= render "shared/import_form", f: f
= render 'new_project_fields', f: f, project_name_id: "import-url-name"
.save-project-loader.hide
.center
......
......@@ -18,8 +18,9 @@
.light
- if can?(current_user, :admin_project_member, @project)
%ul.nav-links.gitlab-tabs{ role: 'tablist' }
%li.active{ role: 'presentation' }
%a{ href: '#add-member-pane', id: 'add-member-tab', data: { toggle: 'tab' }, role: 'tab' } Add member
- if !membership_locked?
%li.active{ role: 'presentation' }
%a{ href: '#add-member-pane', id: 'add-member-tab', data: { toggle: 'tab' }, role: 'tab' } Add member
- if @project.allowed_to_share_with_group?
%li{ role: 'presentation', class: ('active' if membership_locked?) }
%a{ href: '#share-with-group-pane', id: 'share-with-group-tab', data: { toggle: 'tab' }, role: 'tab' } Share with group
......
......@@ -9,33 +9,47 @@ module Geo
end
def load_pending_resources
unsynced = find_unsynced_objects
failed = find_failed_objects
interleave(unsynced, failed)
end
def find_unsynced_objects
lfs_object_ids = find_lfs_object_ids
objects_ids = find_object_ids
interleave(lfs_object_ids, objects_ids)
end
def find_object_ids
downloaded_ids = find_downloaded_ids(Geo::FileService::DEFAULT_OBJECT_TYPES)
def find_failed_objects
Geo::FileRegistry
.failed
.limit(db_retrieve_batch_size)
.pluck(:file_id, :file_type)
end
unsynched_downloads = filter_downloaded_ids(
current_node.uploads, downloaded_ids, Upload.table_name)
def find_object_ids
unsynced_downloads = filter_registry_ids(
current_node.uploads,
Geo::FileService::DEFAULT_OBJECT_TYPES,
Upload.table_name
)
unsynched_downloads
.order(created_at: :desc)
unsynced_downloads
.limit(db_retrieve_batch_size)
.pluck(:id, :uploader)
.map { |id, uploader| [id, uploader.sub(/Uploader\z/, '').underscore] }
end
def find_lfs_object_ids
downloaded_ids = find_downloaded_ids([:lfs])
unsynced_downloads = filter_registry_ids(
current_node.lfs_objects,
[:lfs],
LfsObject.table_name
)
unsynched_downloads = filter_downloaded_ids(
current_node.lfs_objects, downloaded_ids, LfsObject.table_name)
unsynched_downloads
.order(created_at: :desc)
unsynced_downloads
.limit(db_retrieve_batch_size)
.pluck(:id)
.map { |id| [id, :lfs] }
......@@ -45,12 +59,14 @@ module Geo
# plucks a list of file IDs from one into the other. This will not scale
# well with the number of synchronized files--the query will increase
# linearly in size--so this should be replaced with postgres_fdw ASAP.
def filter_downloaded_ids(objects, downloaded_ids, table_name)
return objects if downloaded_ids.empty?
def filter_registry_ids(objects, file_types, table_name)
registry_ids = pluck_registry_ids(Geo::FileRegistry, file_types)
return objects if registry_ids.empty?
joined_relation = objects.joins(<<~SQL)
LEFT OUTER JOIN
(VALUES #{downloaded_ids.map { |id| "(#{id}, 't')" }.join(',')})
(VALUES #{registry_ids.map { |id| "(#{id}, 't')" }.join(',')})
file_registry(file_id, registry_present)
ON #{table_name}.id = file_registry.file_id
SQL
......@@ -58,9 +74,9 @@ module Geo
joined_relation.where(file_registry: { registry_present: [nil, false] })
end
def find_downloaded_ids(file_types)
downloaded_ids = Geo::FileRegistry.where(file_type: file_types).pluck(:file_id)
(downloaded_ids + scheduled_file_ids(file_types)).uniq
def pluck_registry_ids(relation, file_types)
ids = relation.where(file_type: file_types).pluck(:file_id)
(ids + scheduled_file_ids(file_types)).uniq
end
def scheduled_file_ids(types)
......
---
title: Prevent failed file syncs from stalling Geo backfill
merge_request: 3101
author:
type: fixed
---
title: Move group boards routes under - and remove "boards" from reserved paths
merge_request:
author:
type: fixed
---
title: Cleanup data-page attribute after each Karma test
merge_request: 14742
author:
type: fixed
......@@ -4,84 +4,87 @@ resources :groups, only: [:index, :new, :create] do
post :preview_markdown
end
scope(path: 'groups/*group_id',
module: :groups,
as: :group,
constraints: { group_id: Gitlab::PathRegex.full_namespace_route_regex }) do
## EE-specific
resource :analytics, only: [:show]
resource :ldap, only: [] do
member do
put :sync
end
constraints(GroupUrlConstrainer.new) do
scope(path: 'groups/*id',
controller: :groups,
constraints: { id: Gitlab::PathRegex.full_namespace_route_regex, format: /(html|json|atom)/ }) do
get :edit, as: :edit_group
get :issues, as: :issues_group
get :merge_requests, as: :merge_requests_group
get :projects, as: :projects_group
get :activity, as: :activity_group
get :subgroups, as: :subgroups_group
get '/', action: :show, as: :group_canonical
end
resources :ldap_group_links, only: [:index, :create, :destroy]
## EE-specific
resources :group_members, only: [:index, :create, :update, :destroy], concerns: :access_requestable do
post :resend_invite, on: :member
delete :leave, on: :collection
scope(path: 'groups/*group_id',
module: :groups,
as: :group,
constraints: { group_id: Gitlab::PathRegex.full_namespace_route_regex }) do
## EE-specific
patch :override, on: :member
resource :analytics, only: [:show]
resource :ldap, only: [] do
member do
put :sync
end
end
resources :ldap_group_links, only: [:index, :create, :destroy]
## EE-specific
end
resource :avatar, only: [:destroy]
resources :milestones, constraints: { id: /[^\/]+/ }, only: [:index, :show, :edit, :update, :new, :create] do
member do
get :merge_requests
get :participants
get :labels
end
end
resources :group_members, only: [:index, :create, :update, :destroy], concerns: :access_requestable do
post :resend_invite, on: :member
delete :leave, on: :collection
## EE-specific
resource :notification_setting, only: [:update]
resources :audit_events, only: [:index]
resources :pipeline_quota, only: [:index]
## EE-specific
## EE-specific
patch :override, on: :member
## EE-specific
end
## EE-specific
resources :hooks, only: [:index, :create, :destroy], constraints: { id: /\d+/ } do
member do
get :test
resource :avatar, only: [:destroy]
resources :milestones, constraints: { id: /[^\/]+/ }, only: [:index, :show, :edit, :update, :new, :create] do
member do
get :merge_requests
get :participants
get :labels
end
end
end
## EE-specific
resources :labels, except: [:show] do
post :toggle_subscription, on: :member
end
## EE-specific
resource :notification_setting, only: [:update]
resources :audit_events, only: [:index]
resources :pipeline_quota, only: [:index]
## EE-specific
scope path: '-' do
namespace :settings do
resource :ci_cd, only: [:show], controller: 'ci_cd'
## EE-specific
resources :hooks, only: [:index, :create, :destroy], constraints: { id: /\d+/ } do
member do
get :test
end
end
## EE-specific
resources :variables, only: [:index, :show, :update, :create, :destroy]
resources :billings, only: [:index]
end
resources :labels, except: [:show] do
post :toggle_subscription, on: :member
end
## EE-specific
resources :boards, only: [:index, :show, :create, :update, :destroy]
end
scope path: '-' do
namespace :settings do
resource :ci_cd, only: [:show], controller: 'ci_cd'
end
scope(path: 'groups/*id',
controller: :groups,
constraints: { id: Gitlab::PathRegex.full_namespace_route_regex, format: /(html|json|atom)/ }) do
get :edit, as: :edit_group
get :issues, as: :issues_group
get :merge_requests, as: :merge_requests_group
get :projects, as: :projects_group
get :activity, as: :activity_group
get :subgroups, as: :subgroups_group
get '/', action: :show, as: :group_canonical
end
resources :variables, only: [:index, :show, :update, :create, :destroy]
resources :billings, only: [:index]
## EE-specific
resources :boards, only: [:index, :show, :create, :update, :destroy]
end
## EE-specific
get :boards, to: redirect('/groups/%{group_id}/-/boards')
end
constraints(GroupUrlConstrainer.new) do
scope(path: '*id',
as: :group,
constraints: { id: Gitlab::PathRegex.full_namespace_route_regex, format: /(html|json|atom)/ },
......
class AddFileRegistrySuccess < ActiveRecord::Migration
include Gitlab::Database::MigrationHelpers
DOWNTIME = false
disable_ddl_transaction!
def up
# Ensure existing rows are recorded as successes
add_column_with_default :file_registry, :success, :boolean, default: true, allow_null: false
change_column :file_registry, :success, :boolean, default: false
end
def down
# Prevent failures from being converted into successes
false_value = Arel::Nodes::False.new.to_sql(Geo::BaseRegistry)
connection.execute("DELETE FROM file_registry WHERE success = #{false_value}")
remove_column :file_registry, :success
end
end
class AddFileRegistrySuccessIndex < ActiveRecord::Migration
include Gitlab::Database::MigrationHelpers
DOWNTIME = false
disable_ddl_transaction!
def up
add_concurrent_index :file_registry, :success
end
def down
remove_concurrent_index :file_registry, :success
end
end
......@@ -11,7 +11,7 @@
#
# It's strongly recommended that you check this file into your version control system.
ActiveRecord::Schema.define(version: 20171005045404) do
ActiveRecord::Schema.define(version: 20171009162209) do
# These are extensions that must be enabled in order to support this database
enable_extension "plpgsql"
......@@ -25,10 +25,12 @@ ActiveRecord::Schema.define(version: 20171005045404) do
t.integer "bytes", limit: 8
t.string "sha256"
t.datetime "created_at", null: false
t.boolean "success", default: false, null: false
end
add_index "file_registry", ["file_type", "file_id"], name: "index_file_registry_on_file_type_and_file_id", unique: true, using: :btree
add_index "file_registry", ["file_type"], name: "index_file_registry_on_file_type", using: :btree
add_index "file_registry", ["success"], name: "index_file_registry_on_success", using: :btree
create_table "project_registry", force: :cascade do |t|
t.integer "project_id", null: false
......
......@@ -22,7 +22,6 @@
## Backend guides
- [Testing standards and style guidelines](testing_guide/index.md)
- [API styleguide](api_styleguide.md) Use this styleguide if you are
contributing to the API.
- [Sidekiq guidelines](sidekiq_style_guide.md) for working with Sidekiq workers
......@@ -68,6 +67,11 @@
- [Ordering table columns](ordering_table_columns.md)
- [Verifying database capabilities](verifying_database_capabilities.md)
## Testing guides
- [Testing standards and style guidelines](testing_guide/index.md)
- [Frontend testing standards and style guidelines](testing_guide/frontend_testing.md)
## Documentation guides
- [Documentation styleguide](doc_styleguide.md): Use this styleguide if you are
......
......@@ -84,6 +84,7 @@ test should be re-implemented using RSpec instead.
[^1]: /ci/yaml/README.html#dependencies
[rails]: http://rubyonrails.org/
[RSpec]: https://github.com/rspec/rspec-rails#feature-specs
[Capybara]: https://github.com/teamcapybara/capybara
[Karma]: http://karma-runner.github.io/
......
# Google OAuth2 OmniAuth Provider
To enable the Google OAuth2 OmniAuth provider you must register your application with Google. Google will generate a client ID and secret key for you to use.
1. Sign in to the [Google Developers Console](https://console.developers.google.com/) with the Google account you want to use to register GitLab.
1. Select "Create Project".
1. Provide the project information
- Project name: 'GitLab' works just fine here.
- Project ID: Must be unique to all Google Developer registered applications. Google provides a randomly generated Project ID by default. You can use the randomly generated ID or choose a new one.
1. Refresh the page. You should now see your new project in the list. Click on the project.
1. Select the "Google APIs" tab in the Overview.
1. Select and enable the following Google APIs - listed under "Popular APIs"
- Enable `Contacts API`
- Enable `Google+ API`
To enable the Google OAuth2 OmniAuth provider you must register your application
with Google. Google will generate a client ID and secret key for you to use.
## Enabling Google OAuth
In Google's side:
1. Navigate to the [cloud resource manager](https://console.cloud.google.com/cloud-resource-manager) page
1. Select **Create Project**
1. Provide the project information:
- **Project name** - "GitLab" works just fine here.
- **Project ID** - Must be unique to all Google Developer registered applications.
Google provides a randomly generated Project ID by default. You can use
the randomly generated ID or choose a new one.
1. Refresh the page and you should see your new project in the list
1. Go to the [Google API Console](https://console.developers.google.com/apis/dashboard)
1. Select the previously created project form the upper left corner
1. Select **Credentials** from the sidebar
1. Select **OAuth consent screen** and fill the form with the required information
1. In the **Credentials** tab, select **Create credentials > OAuth client ID**
1. Fill in the required information
- **Application type** - Choose "Web Application"
- **Name** - Use the default one or provide your own
- **Authorized JavaScript origins** -This isn't really used by GitLab but go
ahead and put `https://gitlab.example.com`
- **Authorized redirect URIs** - Enter your domain name followed by the
callback URIs one at a time:
1. Select "Credentials" in the submenu.
```
https://gitlab.example.com/users/auth/google_oauth2/callback
https://gitlab.exampl.com/-/google_api/auth/callback
```
1. Select "Create New Client ID".
1. You should now be able to see a Client ID and Client secret. Note them down
or keep this page open as you will need them later.
1. From the **Dashboard** select **ENABLE APIS AND SERVICES > Google Cloud APIs > Container Engine API > Enable**
1. Fill in the required information
- Application type: "Web Application"
- Authorized JavaScript origins: This isn't really used by GitLab but go ahead and put 'https://gitlab.example.com' here.
- Authorized redirect URI: 'https://gitlab.example.com/users/auth/google_oauth2/callback'
1. Under the heading "Client ID for web application" you should see a Client ID and Client secret (see screenshot). Keep this page open as you continue configuration. ![Google app](img/google_app.png)
On your GitLab server:
1. On your GitLab server, open the configuration file.
1. Open the configuration file.
For omnibus package:
For Omnibus GitLab:
```sh
sudo editor /etc/gitlab/gitlab.rb
sudo editor /etc/gitlab/gitlab.rb
```
For installations from source:
```sh
cd /home/git/gitlab
sudo -u git -H editor config/gitlab.yml
cd /home/git/gitlab
sudo -u git -H editor config/gitlab.yml
```
1. See [Initial OmniAuth Configuration](omniauth.md#initial-omniauth-configuration) for initial settings.
1. Add the provider configuration:
1. See [Initial OmniAuth Configuration](omniauth.md#initial-omniauth-configuration) for initial settings.
1. Add the provider configuration:
For omnibus package:
For Omnibus GitLab:
```ruby
gitlab_rails['omniauth_providers'] = [
{
"name" => "google_oauth2",
"app_id" => "YOUR_APP_ID",
"app_secret" => "YOUR_APP_SECRET",
"args" => { "access_type" => "offline", "approval_prompt" => '' }
}
]
gitlab_rails['omniauth_providers'] = [
{
"name" => "google_oauth2",
"app_id" => "YOUR_APP_ID",
"app_secret" => "YOUR_APP_SECRET",
"args" => { "access_type" => "offline", "approval_prompt" => '' }
}
]
```
For installations from source:
```
- { name: 'google_oauth2', app_id: 'YOUR_APP_ID',
app_secret: 'YOUR_APP_SECRET',
args: { access_type: 'offline', approval_prompt: '' } }
- { name: 'google_oauth2', app_id: 'YOUR_APP_ID',
app_secret: 'YOUR_APP_SECRET',
args: { access_type: 'offline', approval_prompt: '' } }
```
1. Change 'YOUR_APP_ID' to the client ID from the Google Developer page from step 10.
1. Change 'YOUR_APP_SECRET' to the client secret from the Google Developer page from step 10.
1. Make sure that you configure GitLab to use an FQDN as Google will not accept raw IP addresses.
1. Change `YOUR_APP_ID` to the client ID from the Google Developer page
1. Similarly, change `YOUR_APP_SECRET` to the client secret
1. Make sure that you configure GitLab to use an FQDN as Google will not accept
raw IP addresses.
For Omnibus packages:
```ruby
external_url 'https://gitlab.example.com'
external_url 'https://gitlab.example.com'
```
For installations from source:
......@@ -88,21 +97,32 @@ To enable the Google OAuth2 OmniAuth provider you must register your application
```
1. Save the configuration file.
1. [Reconfigure][] or [restart GitLab][] for the changes to take effect if you
installed GitLab via Omnibus or from source respectively.
On the sign in page there should now be a Google icon below the regular sign in form. Click the icon to begin the authentication process. Google will ask the user to sign in and authorize the GitLab application. If everything goes well the user will be returned to GitLab and will be signed in.
On the sign in page there should now be a Google icon below the regular sign in
form. Click the icon to begin the authentication process. Google will ask the
user to sign in and authorize the GitLab application. If everything goes well
the user will be returned to GitLab and will be signed in.
## Further Configuration
This further configuration is not required for Google authentication to function but it is strongly recommended. Taking these steps will increase usability for users by providing a little more recognition and branding.
At this point, when users first try to authenticate to your GitLab installation with Google they will see a generic application name on the prompt screen. The prompt informs the user that "Project Default Service Account" would like to access their account. "Project Default Service Account" isn't very recognizable and may confuse or cause users to be concerned. This is easily changeable.
1. Select 'Consent screen' in the left menu. (See steps 1, 4 and 5 above for instructions on how to get here if you closed your window).
1. Scroll down until you find "Product Name". Change the product name to something more descriptive.
1. Add any additional information as you wish - homepage, logo, privacy policy, etc. None of this is required, but it may help your users.
This further configuration is not required for Google authentication to function
but it is strongly recommended. Taking these steps will increase usability for
users by providing a little more recognition and branding.
At this point, when users first try to authenticate to your GitLab installation
with Google they will see a generic application name on the prompt screen. The
prompt informs the user that "Project Default Service Account" would like to
access their account. "Project Default Service Account" isn't very recognizable
and may confuse or cause users to be concerned. This is easily changeable:
1. Select 'Consent screen' in the left menu. (See steps 1, 4 and 5 above for
instructions on how to get here if you closed your window).
1. Scroll down until you find "Product Name". Change the product name to
something more descriptive.
1. Add any additional information as you wish - homepage, logo, privacy policy,
etc. None of this is required, but it may help your users.
[reconfigure]: ../administration/restart_gitlab.md#omnibus-gitlab-reconfigure
[restart GitLab]: ../administration/restart_gitlab.md#installations-from-source
# From Community Edition 10.1 to Enterprise Edition 10.1
This guide assumes you have a correctly configured and tested installation of
GitLab Community Edition 10.1. If you run into any trouble or if you have any
questions please contact us at [support@gitlab.com].
### 0. Backup
Make a backup just in case something goes wrong:
```bash
cd /home/git/gitlab
sudo -u git -H bundle exec rake gitlab:backup:create RAILS_ENV=production
```
For installations using MySQL, this may require granting "LOCK TABLES"
privileges to the GitLab user on the database version.
### 1. Stop server
```bash
sudo service gitlab stop
```
### 2. Get the EE code
```bash
cd /home/git/gitlab
sudo -u git -H git remote add -f ee https://gitlab.com/gitlab-org/gitlab-ee.git
sudo -u git -H git checkout 10-1-stable-ee
```
### 3. Install libs, migrations, etc.
```bash
cd /home/git/gitlab
# MySQL installations (note: the line below states '--without postgres')
sudo -u git -H bundle install --without postgres development test --deployment
# PostgreSQL installations (note: the line below states '--without mysql')
sudo -u git -H bundle install --without mysql development test --deployment
# Run database migrations
sudo -u git -H bundle exec rake db:migrate RAILS_ENV=production
# Clean up assets and cache
sudo -u git -H bundle exec rake assets:clean assets:precompile cache:clear RAILS_ENV=production
```
### 4. Start application
```bash
sudo service gitlab start
sudo service nginx restart
```
### 5. Check application status
Check if GitLab and its environment are configured correctly:
```bash
sudo -u git -H bundle exec rake gitlab:env:info RAILS_ENV=production
```
To make sure you didn't miss anything run a more thorough check with:
```bash
sudo -u git -H bundle exec rake gitlab:check RAILS_ENV=production
```
If all items are green, then congratulations upgrade complete!
## Things went south? Revert to previous version (Community Edition 10.1)
### 1. Revert the code to the previous version
```bash
cd /home/git/gitlab
sudo -u git -H git checkout 10-1-stable
```
### 2. Restore from the backup
```bash
cd /home/git/gitlab
sudo -u git -H bundle exec rake gitlab:backup:restore RAILS_ENV=production
```
[support@gitlab.com]: mailto:support@gitlab.com
......@@ -190,6 +190,43 @@ load and will have a corresponding badge counter to match the counter on the ima
![Image resolved discussion](img/image_resolved_discussion.png)
## Locking discussions
> [Introduced][ce-14531] in GitLab 10.1.
Sometimes a discussion is revolved around an image. With image discussions,
you can easily target a specific coordinate of an image and start a discussion
around it. Image discussions are available in merge requests and commit detail views.
To start an image discussion, hover your mouse over the image. Your mouse pointer
should convert into an icon, indicating that the image is available for commenting.
Simply click anywhere on the image to create a new discussion.
![Start image discussion](img/start_image_discussion.gif)
After you click on the image, a comment form will be displayed that would be the start
of your discussion. Once you save your comment, you will see a new badge displayed on
top of your image. This badge represents your discussion.
>**Note:**
This discussion badge is typically associated with a number that is only used as a visual
reference for each discussion. In the merge request discussion tab,
this badge will be indicated with a comment icon since each discussion will render a new
image section.
Image discussions also work on diffs that replace an existing image. In this diff view
mode, you can toggle the different view modes and still see the discussion point badges.
| 2-up | Swipe | Onion Skin |
| :-----------: | :----------: | :----------: |
| ![2-up view](img/two_up_view.png) | ![swipe view](img/swipe_view.png) | ![onion skin view](img/onion_skin_view.png) |
Image discussions also work well with resolvable discussions. Resolved discussions
on diffs (not on the merge request discussion tab) will appear collapsed on page
load and will have a corresponding badge counter to match the counter on the image.
![Image resolved discussion](img/image_resolved_discussion.png)
## Lock discussions
> [Introduced][ce-14531] in GitLab 10.1.
......
......@@ -78,6 +78,7 @@ The following table depicts the various user permission levels in a project.
| Force push to protected branches [^4] | | | | | |
| Remove protected branches [^4] | | | | | |
| Remove pages | | | | | ✓ |
| Manage clusters | | | | ✓ | ✓ |
## Project features permissions
......
# Connecting GitLab with GKE
> [Introduced](https://gitlab.com/gitlab-org/gitlab-ce/issues/35954) in 10.1.
CAUTION: **Warning:**
The Cluster integration is currently in **Beta**.
Connect your project to Google Container Engine (GKE) in a few steps.
With a cluster associated to your project, you can use Review Apps, deploy your
applications, run your pipelines, and much more in an easy way.
NOTE: **Note:**
The Cluster integration will eventually supersede the
[Kubernetes integration](../integrations/kubernetes.md). For the moment,
you can create only one cluster.
## Prerequisites
In order to be able to manage your GKE cluster through GitLab, the following
prerequisites must be met:
- The [Google authentication integration](../../../integration/google.md) must
be enabled in GitLab at the instance level. If that's not the case, ask your
administrator to enable it.
- Your associated Google account must have the right privileges to manage
clusters on GKE. That would mean that a
[billing account](https://cloud.google.com/billing/docs/how-to/manage-billing-account)
must be set up.
- You must have Master [permissions] in order to be able to access the **Cluster**
page.
If all of the above requirements are met, you can proceed to add a new cluster.
## Adding a cluster
NOTE: **Note:**
You need Master [permissions] and above to add a cluster.
To add a new cluster:
1. Navigate to your project's **CI/CD > Cluster** page.
1. Connect your Google account if you haven't done already by clicking the
"Sign-in with Google" button.
1. Fill in the requested values:
- **Cluster name** (required) - The name you wish to give the cluster.
- **GCP project ID** (required) - The ID of the project you created in your GCP
console that will host the Kubernetes cluster. This must **not** be confused
with the project name. Learn more about [Google Cloud Platform projects](https://cloud.google.com/resource-manager/docs/creating-managing-projects).
- **Zone** - The zone under which the cluster will be created. Read more about
[the available zones](https://cloud.google.com/compute/docs/regions-zones/).
- **Number of nodes** - The number of nodes you wish the cluster to have.
- **Machine type** - The machine type of the Virtual Machine instance that
the cluster will be based on. Read more about [the available machine types](https://cloud.google.com/compute/docs/machine-types).
- **Project namespace** - The unique namespace for this project. By default you
don't have to fill it in; by leaving it blank, GitLab will create one for you.
1. Click the **Create cluster** button.
After a few moments your cluster should be created. If something goes wrong,
you will be notified.
Now, you can proceed to [enable the Cluster integration](#enabling-or-disabling-the-cluster-integration).
## Enabling or disabling the Cluster integration
After you have successfully added your cluster information, you can enable the
Cluster integration:
1. Click the "Enabled/Disabled" switch
1. Hit **Save** for the changes to take effect
You can now start using your Kubernetes cluster for your deployments.
To disable the Cluster integration, follow the same procedure.
## Removing the Cluster integration
NOTE: **Note:**
You need Master [permissions] and above to remove a cluster integration.
NOTE: **Note:**
When you remove a cluster, you only remove its relation to GitLab, not the
cluster itself. To remove the cluster, you can do so by visiting the GKE
dashboard or using `kubectl`.
To remove the Cluster integration from your project, simply click on the
**Remove integration** button. You will then be able to follow the procedure
and [add a cluster](#adding-a-cluster) again.
[permissions]: ../../permissions.md
......@@ -63,6 +63,8 @@ common actions on issues or merge requests
browse, and download job artifacts
- [Pipeline settings](pipelines/settings.md): Set up Git strategy (choose the default way your repository is fetched from GitLab in a job),
timeout (defines the maximum amount of time in minutes that a job is able run), custom path for `.gitlab-ci.yml`, test coverage parsing, pipeline's visibility, and much more
- [GKE cluster integration](clusters/index.md): Connecting your GitLab project
with Google Container Engine
- [GitLab Pages](pages/index.md): Build, test, and deploy your static
website with GitLab Pages
......
# Reducing the repository size using Git
A GitLab Entrerprise Edition administrator can set a [repository size limit][admin-repo-size]
A GitLab Enterprise Edition administrator can set a [repository size limit][admin-repo-size]
which will prevent you to exceed it.
When a project has reached its size limit, you will not be able to push to it,
......
......@@ -1018,8 +1018,10 @@ module API
expose :repositories_failed_count
expose :lfs_objects_count
expose :lfs_objects_synced_count
expose :lfs_objects_failed_count
expose :attachments_count
expose :attachments_synced_count
expose :attachments_failed_count
expose :last_event_id
expose :last_event_date
expose :cursor_last_event_id
......
......@@ -119,7 +119,6 @@ module Gitlab
analytics
audit_events
avatar
boards
edit
group_members
hooks
......
......@@ -25,9 +25,9 @@ module Gitlab
end
TEMPLATES_TABLE = [
ProjectTemplate.new('rails', 'Ruby on Rails', 'Includes a MVC structure, gemfile, rakefile, and .gitlab-ci.yml file, along with many others, to help you get started.', 'https://gitlab.com/gitlab-org/project-templates/rails'),
ProjectTemplate.new('spring', 'Spring', 'Includes a MVC structure, mvnw, pom.xml, and .gitlab-ci.yml file to help you get started.', 'https://gitlab.com/gitlab-org/project-templates/spring'),
ProjectTemplate.new('express', 'NodeJS Express', 'Includes a MVC structure, and .gitlab-ci.yml file to help you get started.', 'https://gitlab.com/gitlab-org/project-templates/express')
ProjectTemplate.new('rails', 'Ruby on Rails', 'Includes an MVC structure, gemfile, rakefile, and .gitlab-ci.yml file, along with many others, to help you get started.', 'https://gitlab.com/gitlab-org/project-templates/rails'),
ProjectTemplate.new('spring', 'Spring', 'Includes an MVC structure, mvnw, pom.xml, and .gitlab-ci.yml file to help you get started.', 'https://gitlab.com/gitlab-org/project-templates/spring'),
ProjectTemplate.new('express', 'NodeJS Express', 'Includes an MVC structure and .gitlab-ci.yml file to help you get started.', 'https://gitlab.com/gitlab-org/project-templates/express')
].freeze
class << self
......
......@@ -264,8 +264,10 @@ describe Admin::GeoNodesController, :postgresql do
id: 1,
health: nil,
attachments_count: 329,
attachments_failed_count: 13,
attachments_synced_count: 141,
lfs_objects_count: 256,
lfs_objects_failed_count: 12,
lfs_objects_synced_count: 123,
repositories_count: 10,
repositories_synced_count: 5,
......
......@@ -2,6 +2,7 @@ FactoryGirl.define do
factory :geo_file_registry, class: Geo::FileRegistry do
sequence(:file_id)
file_type :file
success true
trait :avatar do
file_type :avatar
......
......@@ -5,8 +5,10 @@
"healthy",
"health",
"attachments_count",
"attachments_failed_count",
"attachments_synced_count",
"lfs_objects_count",
"lfs_objects_failed_count",
"lfs_objects_synced_count",
"db_replication_lag",
"repositories_count",
......@@ -22,10 +24,12 @@
"healthy": { "type": "boolean" },
"health": { "type": "string" },
"attachments_count": { "type": "integer" },
"attachments_failed_count": { "type": "integer" },
"attachments_synced_count": { "type": "integer" },
"attachments_synced_in_percentage": { "type": "string" },
"db_replication_lag": { "type": ["integer", "null"] },
"lfs_objects_count": { "type": "integer" },
"lfs_objects_failed_count": { "type": "integer" },
"lfs_objects_synced_count": { "type": "integer" },
"lfs_objects_synced_in_percentage": { "type": "string" },
"repositories_count": { "type": "integer" },
......
......@@ -28,7 +28,7 @@ import '~/lib/utils/common_utils';
preloadFixtures('merge_requests/diff_comment.html.raw');
beforeEach(function(done) {
loadFixtures('merge_requests/diff_comment.html.raw');
$('body').data('page', 'projects:merge_requests:show');
$('body').attr('data-page', 'projects:merge_requests:show');
loadAwardsHandler(true).then((obj) => {
awardsHandler = obj;
spyOn(awardsHandler, 'postEmoji').and.callFake((button, url, emoji, cb) => cb());
......@@ -55,6 +55,9 @@ import '~/lib/utils/common_utils';
// restore original url root value
gon.relative_url_root = urlRoot;
// Undo what we did to the shared <body>
$('body').removeAttr('data-page');
awardsHandler.destroy();
});
describe('::showEmojiMenu', function() {
......
......@@ -19,6 +19,11 @@ describe('Quick Submit behavior', () => {
this.textarea = $('.js-quick-submit textarea').first();
});
afterEach(() => {
// Undo what we did to the shared <body>
$('body').removeAttr('data-page');
});
it('does not respond to other keyCodes', () => {
this.textarea.trigger(keydownEvent({
keyCode: 32,
......
......@@ -23,12 +23,17 @@ describe('Merge request notes', () => {
loadFixtures(discussionTabFixture);
gl.utils.disableButtonIfEmptyField = _.noop;
window.project_uploads_path = 'http://test.host/uploads';
$('body').data('page', 'projects:merge_requests:show');
$('body').attr('data-page', 'projects:merge_requests:show');
window.gon.current_user_id = $('.note:last').data('author-id');
return new Notes('', []);
});
afterEach(() => {
// Undo what we did to the shared <body>
$('body').removeAttr('data-page');
});
describe('up arrow', () => {
it('edits last comment when triggered in main form', () => {
const upArrowEvent = $.Event('keydown');
......@@ -71,12 +76,17 @@ describe('Merge request notes', () => {
<textarea class="js-note-text"></textarea>
</form>`;
setFixtures(diffsResponse.html + noteFormHtml);
$('body').data('page', 'projects:merge_requests:show');
$('body').attr('data-page', 'projects:merge_requests:show');
window.gon.current_user_id = $('.note:last').data('author-id');
return new Notes('', []);
});
afterEach(() => {
// Undo what we did to the shared <body>
$('body').removeAttr('data-page');
});
describe('up arrow', () => {
it('edits last comment in discussion when triggered in discussion form', (done) => {
const upArrowEvent = $.Event('keydown');
......
......@@ -277,7 +277,7 @@ import 'vendor/jquery.scrollTo';
describe('loadDiff', function () {
beforeEach(() => {
loadFixtures('merge_requests/diff_comment.html.raw');
spyOn(window.gl.utils, 'getPagePath').and.returnValue('merge_requests');
$('body').attr('data-page', 'projects:merge_requests:show');
window.gl.ImageFile = () => {};
window.notes = new Notes('', []);
spyOn(window.notes, 'toggleDiffNote').and.callThrough();
......@@ -286,6 +286,9 @@ import 'vendor/jquery.scrollTo';
afterEach(() => {
delete window.gl.ImageFile;
delete window.notes;
// Undo what we did to the shared <body>
$('body').removeAttr('data-page');
});
it('requires an absolute pathname', function () {
......
......@@ -39,7 +39,12 @@ import '~/notes';
loadFixtures(commentsTemplate);
gl.utils.disableButtonIfEmptyField = _.noop;
window.project_uploads_path = 'http://test.host/uploads';
$('body').data('page', 'projects:merge_requets:show');
$('body').attr('data-page', 'projects:merge_requets:show');
});
afterEach(() => {
// Undo what we did to the shared <body>
$('body').removeAttr('data-page');
});
describe('task lists', function() {
......
......@@ -6,7 +6,7 @@ import '~/lib/utils/common_utils';
import 'vendor/fuzzaldrin-plus';
(function() {
var addBodyAttributes, assertLinks, dashboardIssuesPath, dashboardMRsPath, groupIssuesPath, groupMRsPath, groupName, mockDashboardOptions, mockGroupOptions, mockProjectOptions, projectIssuesPath, projectMRsPath, projectName, userId, widget;
var assertLinks, dashboardIssuesPath, dashboardMRsPath, groupIssuesPath, groupMRsPath, groupName, mockDashboardOptions, mockGroupOptions, mockProjectOptions, projectIssuesPath, projectMRsPath, projectName, userId, widget;
var userName = 'root';
widget = null;
......@@ -29,25 +29,31 @@ import 'vendor/fuzzaldrin-plus';
groupName = 'Gitlab Org';
const removeBodyAttributes = function() {
const $body = $('body');
$body.removeAttr('data-page');
$body.removeAttr('data-project');
$body.removeAttr('data-group');
};
// Add required attributes to body before starting the test.
// section would be dashboard|group|project
addBodyAttributes = function(section) {
var $body;
const addBodyAttributes = function(section) {
if (section == null) {
section = 'dashboard';
}
$body = $('body');
$body.removeAttr('data-page');
$body.removeAttr('data-project');
$body.removeAttr('data-group');
const $body = $('body');
removeBodyAttributes();
switch (section) {
case 'dashboard':
return $body.data('page', 'root:index');
return $body.attr('data-page', 'root:index');
case 'group':
$body.data('page', 'groups:show');
$body.attr('data-page', 'groups:show');
return $body.data('group', 'gitlab-org');
case 'project':
$body.data('page', 'projects:show');
$body.attr('data-page', 'projects:show');
return $body.data('project', 'gitlab-ce');
}
};
......@@ -108,7 +114,7 @@ import 'vendor/fuzzaldrin-plus';
preloadFixtures('static/search_autocomplete.html.raw');
beforeEach(function() {
loadFixtures('static/search_autocomplete.html.raw');
widget = new gl.SearchAutocomplete;
// Prevent turbolinks from triggering within gl_dropdown
spyOn(window.gl.utils, 'visitUrl').and.returnValue(true);
......@@ -120,6 +126,8 @@ import 'vendor/fuzzaldrin-plus';
});
afterEach(function() {
// Undo what we did to the shared <body>
removeBodyAttributes();
window.gon = {};
});
it('should show Dashboard specific dropdown menu', function() {
......
......@@ -72,7 +72,14 @@ describe Gitlab::PathRegex do
route_set = Rails.application.routes
routes_collection = route_set.routes
routes_array = routes_collection.routes
routes_array.map { |route| route.path.spec.to_s }
non_deprecated_redirect_routes = routes_array.reject do |route|
app = route.app
# `app.app` is either another app, or `self`. We want to find the final app.
app = app.app while app.try(:app) && app.app != app
app.is_a?(ActionDispatch::Routing::PathRedirect) && app.block.include?('/-/')
end
non_deprecated_redirect_routes.map { |route| route.path.spec.to_s }
end
let(:routes_without_format) { all_routes.map { |path| without_format(path) } }
......
require 'spec_helper'
describe Geo::FileRegistry do
set(:failed) { create(:geo_file_registry, success: false) }
set(:synced) { create(:geo_file_registry, success: true) }
describe '.failed' do
it 'returns registries in the failed state' do
expect(described_class.failed).to contain_exactly(failed)
end
end
describe '.synced' do
it 'returns registries in the synced state' do
expect(described_class.synced).to contain_exactly(synced)
end
end
end
......@@ -39,6 +39,17 @@ describe GeoNodeStatus do
end
describe '#attachments_synced_count' do
it 'only counts successful syncs' do
create_list(:user, 3, avatar: fixture_file_upload(Rails.root + 'spec/fixtures/dk.png', 'image/png'))
uploads = Upload.all.pluck(:id)
create(:geo_file_registry, :avatar, file_id: uploads[0])
create(:geo_file_registry, :avatar, file_id: uploads[1])
create(:geo_file_registry, :avatar, file_id: uploads[2], success: false)
expect(subject.attachments_synced_count).to eq(2)
end
it 'does not count synced files that were replaced' do
user = create(:user, avatar: fixture_file_upload(Rails.root + 'spec/fixtures/dk.png', 'image/png'))
......@@ -68,6 +79,21 @@ describe GeoNodeStatus do
end
end
describe '#attachments_failed_count' do
it 'counts failed avatars, attachment, personal snippets and files' do
# These two should be ignored
create(:geo_file_registry, :lfs, success: false)
create(:geo_file_registry)
create(:geo_file_registry, file_type: :personal_file, success: false)
create(:geo_file_registry, file_type: :attachment, success: false)
create(:geo_file_registry, :avatar, success: false)
create(:geo_file_registry, success: false)
expect(subject.attachments_failed_count).to eq(4)
end
end
describe '#attachments_synced_in_percentage' do
let(:avatar) { fixture_file_upload(Rails.root.join('spec/fixtures/dk.png')) }
let(:upload_1) { create(:upload, model: group, path: avatar) }
......@@ -113,6 +139,20 @@ describe GeoNodeStatus do
end
end
describe '#lfs_objects_failed' do
it 'counts failed LFS objects' do
# These four should be ignored
create(:geo_file_registry, success: false)
create(:geo_file_registry, :avatar, success: false)
create(:geo_file_registry, file_type: :attachment, success: false)
create(:geo_file_registry, :lfs)
create(:geo_file_registry, :lfs, success: false)
expect(subject.lfs_objects_failed_count).to eq(1)
end
end
describe '#lfs_objects_synced_in_percentage' do
let(:lfs_object_project) { create(:lfs_objects_project, project: project_1) }
......@@ -218,8 +258,10 @@ describe GeoNodeStatus do
allow(Gitlab::Geo::HealthCheck).to receive(:db_replication_lag).and_return(nil)
subject.attachments_count = nil
subject.attachments_synced_count = nil
subject.attachments_failed_count = nil
subject.lfs_objects_count = nil
subject.lfs_objects_synced_count = nil
subject.lfs_objects_failed_count = nil
subject.repositories_count = nil
subject.repositories_synced_count = nil
subject.repositories_failed_count = nil
......@@ -235,9 +277,11 @@ describe GeoNodeStatus do
expect(subject.repositories_failed_count).to be_zero
expect(subject.lfs_objects_count).to be_zero
expect(subject.lfs_objects_synced_count).to be_zero
expect(subject.lfs_objects_failed_count).to be_zero
expect(subject.lfs_objects_synced_in_percentage).to be_zero
expect(subject.attachments_count).to be_zero
expect(subject.attachments_synced_count).to be_zero
expect(subject.attachments_failed_count).to be_zero
expect(subject.attachments_synced_in_percentage).to be_zero
expect(subject.last_event_id).to be_nil
expect(subject.last_event_date).to be_nil
......
......@@ -264,12 +264,6 @@ describe Repository do
expect_to_raise_storage_error { broken_repository.find_commits_by_message('s') }
end
end
describe 'when storage is broken', :broken_storage do
it 'should raise a storage error' do
expect_to_raise_storage_error { broken_repository.find_commits_by_message('s') }
end
end
end
describe '#blob_at' do
......
require 'spec_helper'
describe 'Deprecated boards paths' do
it 'redirects to boards page' do
group = create :group, name: 'gitlabhq'
get('/groups/gitlabhq/boards')
expect(response).to redirect_to(group_boards_path(group))
end
end
require 'spec_helper'
describe 'Group routing' do
describe 'subgroup "boards"' do
it 'shows group show page' do
allow(Group).to receive(:find_by_full_path).with('gitlabhq/boards', any_args).and_return(true)
expect(get('/groups/gitlabhq/boards')).to route_to('groups#show', id: 'gitlabhq/boards')
end
it 'shows boards index page' do
allow(Group).to receive(:find_by_full_path).with('gitlabhq', any_args).and_return(true)
expect(get('/groups/gitlabhq/-/boards')).to route_to('groups/boards#index', group_id: 'gitlabhq')
end
end
end
......@@ -285,17 +285,15 @@ end
describe "Groups", "routing" do
let(:name) { 'complex.group-namegit' }
before do
allow_any_instance_of(GroupUrlConstrainer).to receive(:matches?).and_return(true)
end
let!(:group) { create(:group, name: name) }
it "to #show" do
expect(get("/groups/#{name}")).to route_to('groups#show', id: name)
end
it "also supports nested groups" do
expect(get("/#{name}/#{name}")).to route_to('groups#show', id: "#{name}/#{name}")
nested_group = create(:group, parent: group)
expect(get("/#{name}/#{nested_group.name}")).to route_to('groups#show', id: "#{name}/#{nested_group.name}")
end
it "also display group#show on the short path" do
......@@ -313,10 +311,6 @@ describe "Groups", "routing" do
it "to #members" do
expect(get("/groups/#{name}/group_members")).to route_to('groups/group_members#index', group_id: name)
end
it "also display group#show with slash in the path" do
expect(get('/group/subgroup')).to route_to('groups#show', id: 'group/subgroup')
end
end
describe HealthCheckController, 'routing' do
......
......@@ -6,8 +6,10 @@ describe GeoNodeStatusEntity, :postgresql do
id: 1,
health: '',
attachments_count: 329,
attachments_failed_count: 25,
attachments_synced_count: 141,
lfs_objects_count: 256,
lfs_objects_failed_count: 12,
lfs_objects_synced_count: 123,
repositories_count: 10,
repositories_synced_count: 5,
......@@ -29,9 +31,11 @@ describe GeoNodeStatusEntity, :postgresql do
it { is_expected.to have_key(:healthy) }
it { is_expected.to have_key(:health) }
it { is_expected.to have_key(:attachments_count) }
it { is_expected.to have_key(:attachments_failed_count) }
it { is_expected.to have_key(:attachments_synced_count) }
it { is_expected.to have_key(:attachments_synced_in_percentage) }
it { is_expected.to have_key(:lfs_objects_count) }
it { is_expected.to have_key(:lfs_objects_failed_count) }
it { is_expected.to have_key(:lfs_objects_synced_count) }
it { is_expected.to have_key(:lfs_objects_synced_in_percentage) }
it { is_expected.to have_key(:repositories_count) }
......
......@@ -8,6 +8,8 @@ describe Geo::FileDownloadService do
before do
stub_current_geo_node(secondary)
allow_any_instance_of(Gitlab::ExclusiveLease).to receive(:try_obtain).and_return(true)
end
describe '#execute' do
......@@ -15,15 +17,18 @@ describe Geo::FileDownloadService do
let(:user) { create(:user, avatar: fixture_file_upload(Rails.root + 'spec/fixtures/dk.png', 'image/png')) }
let(:upload) { Upload.find_by(model: user, uploader: 'AvatarUploader') }
subject { described_class.new(:avatar, upload.id) }
subject(:execute!) { described_class.new(:avatar, upload.id).execute }
it 'downloads a user avatar' do
allow_any_instance_of(Gitlab::ExclusiveLease)
.to receive(:try_obtain).and_return(true)
allow_any_instance_of(Gitlab::Geo::FileTransfer)
.to receive(:download_from_primary).and_return(100)
stub_transfer(Gitlab::Geo::FileTransfer, 100)
expect { execute! }.to change { Geo::FileRegistry.synced.count }.by(1)
end
it 'registers when the download fails' do
stub_transfer(Gitlab::Geo::FileTransfer, -1)
expect { subject.execute }.to change { Geo::FileRegistry.count }.by(1)
expect { execute! }.to change { Geo::FileRegistry.failed.count }.by(1)
end
end
......@@ -31,15 +36,18 @@ describe Geo::FileDownloadService do
let(:group) { create(:group, avatar: fixture_file_upload(Rails.root + 'spec/fixtures/dk.png', 'image/png')) }
let(:upload) { Upload.find_by(model: group, uploader: 'AvatarUploader') }
subject { described_class.new(:avatar, upload.id) }
subject(:execute!) { described_class.new(:avatar, upload.id).execute }
it 'downloads a group avatar' do
allow_any_instance_of(Gitlab::ExclusiveLease)
.to receive(:try_obtain).and_return(true)
allow_any_instance_of(Gitlab::Geo::FileTransfer)
.to receive(:download_from_primary).and_return(100)
stub_transfer(Gitlab::Geo::FileTransfer, 100)
expect { execute! }.to change { Geo::FileRegistry.synced.count }.by(1)
end
it 'registers when the download fails' do
stub_transfer(Gitlab::Geo::FileTransfer, -1)
expect { subject.execute }.to change { Geo::FileRegistry.count }.by(1)
expect { execute! }.to change { Geo::FileRegistry.failed.count }.by(1)
end
end
......@@ -47,15 +55,18 @@ describe Geo::FileDownloadService do
let(:project) { create(:project, avatar: fixture_file_upload(Rails.root + 'spec/fixtures/dk.png', 'image/png')) }
let(:upload) { Upload.find_by(model: project, uploader: 'AvatarUploader') }
subject { described_class.new(:avatar, upload.id) }
subject(:execute!) { described_class.new(:avatar, upload.id).execute }
it 'downloads a project avatar' do
allow_any_instance_of(Gitlab::ExclusiveLease)
.to receive(:try_obtain).and_return(true)
allow_any_instance_of(Gitlab::Geo::FileTransfer)
.to receive(:download_from_primary).and_return(100)
stub_transfer(Gitlab::Geo::FileTransfer, 100)
expect { subject.execute }.to change { Geo::FileRegistry.count }.by(1)
expect { execute! }.to change { Geo::FileRegistry.synced.count }.by(1)
end
it 'registers when the download fails' do
stub_transfer(Gitlab::Geo::FileTransfer, -1)
expect { execute! }.to change { Geo::FileRegistry.failed.count }.by(1)
end
end
......@@ -63,30 +74,36 @@ describe Geo::FileDownloadService do
let(:note) { create(:note, :with_attachment) }
let(:upload) { Upload.find_by(model: note, uploader: 'AttachmentUploader') }
subject { described_class.new(:attachment, upload.id) }
subject(:execute!) { described_class.new(:attachment, upload.id).execute }
it 'downloads the attachment' do
allow_any_instance_of(Gitlab::ExclusiveLease)
.to receive(:try_obtain).and_return(true)
allow_any_instance_of(Gitlab::Geo::FileTransfer)
.to receive(:download_from_primary).and_return(100)
stub_transfer(Gitlab::Geo::FileTransfer, 100)
expect { subject.execute }.to change { Geo::FileRegistry.count }.by(1)
expect { execute! }.to change { Geo::FileRegistry.synced.count }.by(1)
end
it 'registers when the download fails' do
stub_transfer(Gitlab::Geo::FileTransfer, -1)
expect { execute! }.to change { Geo::FileRegistry.failed.count }.by(1)
end
end
context 'with a snippet' do
let(:upload) { create(:upload, :personal_snippet) }
subject { described_class.new(:personal_file, upload.id) }
subject(:execute!) { described_class.new(:personal_file, upload.id).execute }
it 'downloads the file' do
allow_any_instance_of(Gitlab::ExclusiveLease)
.to receive(:try_obtain).and_return(true)
allow_any_instance_of(Gitlab::Geo::FileTransfer)
.to receive(:download_from_primary).and_return(100)
stub_transfer(Gitlab::Geo::FileTransfer, 100)
expect { subject.execute }.to change { Geo::FileRegistry.count }.by(1)
expect { execute! }.to change { Geo::FileRegistry.synced.count }.by(1)
end
it 'registers when the download fails' do
stub_transfer(Gitlab::Geo::FileTransfer, -1)
expect { execute! }.to change { Geo::FileRegistry.failed.count }.by(1)
end
end
......@@ -101,12 +118,15 @@ describe Geo::FileDownloadService do
end
it 'downloads the file' do
allow_any_instance_of(Gitlab::ExclusiveLease)
.to receive(:try_obtain).and_return(true)
allow_any_instance_of(Gitlab::Geo::FileTransfer)
.to receive(:download_from_primary).and_return(100)
stub_transfer(Gitlab::Geo::FileTransfer, 100)
expect { subject.execute }.to change { Geo::FileRegistry.count }.by(1)
expect { subject.execute }.to change { Geo::FileRegistry.synced.count }.by(1)
end
it 'registers when the download fails' do
stub_transfer(Gitlab::Geo::FileTransfer, -1)
expect { subject.execute }.to change { Geo::FileRegistry.failed.count }.by(1)
end
end
......@@ -115,18 +135,21 @@ describe Geo::FileDownloadService do
subject { described_class.new(:lfs, lfs_object.id) }
before do
allow_any_instance_of(Gitlab::ExclusiveLease)
.to receive(:try_obtain).and_return(true)
allow_any_instance_of(Gitlab::Geo::LfsTransfer)
.to receive(:download_from_primary).and_return(100)
it 'downloads an LFS object' do
stub_transfer(Gitlab::Geo::LfsTransfer, 100)
expect { subject.execute }.to change { Geo::FileRegistry.synced.count }.by(1)
end
it 'downloads an LFS object' do
expect { subject.execute }.to change { Geo::FileRegistry.count }.by(1)
it 'registers when the download fails' do
stub_transfer(Gitlab::Geo::LfsTransfer, -1)
expect { subject.execute }.to change { Geo::FileRegistry.failed.count }.by(1)
end
it 'logs a message' do
stub_transfer(Gitlab::Geo::LfsTransfer, 100)
expect(Gitlab::Geo::Logger).to receive(:info).with(hash_including(:message, :download_time_s, success: true, bytes_downloaded: 100)).and_call_original
subject.execute
......@@ -138,5 +161,10 @@ describe Geo::FileDownloadService do
expect { described_class.new(:bad, 1).execute }.to raise_error(NameError)
end
end
def stub_transfer(kls, result)
instance = double("(instance of #{kls})", download_from_primary: result)
allow(kls).to receive(:new).and_return(instance)
end
end
end
......@@ -84,6 +84,27 @@ describe Geo::FileDownloadDispatchWorker, :postgresql do
end
end
context 'with a failed file' do
let!(:failed_registry) { create(:geo_file_registry, :lfs, file_id: 999, success: false) }
it 'does not stall backfill' do
unsynced = create(:lfs_object, :with_file)
stub_const('Geo::BaseSchedulerWorker::DB_RETRIEVE_BATCH_SIZE', 1)
expect(GeoFileDownloadWorker).not_to receive(:perform_async).with(:lfs, failed_registry.file_id)
expect(GeoFileDownloadWorker).to receive(:perform_async).with(:lfs, unsynced.id)
subject.perform
end
it 'retries failed files' do
expect(GeoFileDownloadWorker).to receive(:perform_async).with('lfs', failed_registry.file_id)
subject.perform
end
end
context 'when node has namespace restrictions' do
let(:synced_group) { create(:group) }
let!(:project_in_synced_group) { create(:project, group: synced_group) }
......
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