Commit 5348daad authored by Lin Jen-Shin (godfat)'s avatar Lin Jen-Shin (godfat)

Merge branch 'ce-to-ee-2017-09-29' into 'master'

CE upstream: Friday

Closes gitaly#601, gitaly#562, #3573, #3118, and gitlab-ce#38507

See merge request gitlab-org/gitlab-ee!3036
parents 3f8c0079 f78b006f
...@@ -8,4 +8,4 @@ ...@@ -8,4 +8,4 @@
karma.config.js karma.config.js
webpack.config.js webpack.config.js
svg.config.js svg.config.js
/app/assets/javascripts/locale/**/*.js /app/assets/javascripts/locale/**/app.js
...@@ -40,10 +40,10 @@ export default () => { ...@@ -40,10 +40,10 @@ export default () => {
class="text-center" class="text-center"
v-if="error"> v-if="error">
<span v-if="loadError"> <span v-if="loadError">
An error occured whilst loading the file. Please try again later. An error occurred whilst loading the file. Please try again later.
</span> </span>
<span v-else> <span v-else>
An error occured whilst parsing the file. An error occurred whilst parsing the file.
</span> </span>
</p> </p>
</div> </div>
......
...@@ -48,10 +48,10 @@ export default () => { ...@@ -48,10 +48,10 @@ export default () => {
class="text-center" class="text-center"
v-if="error"> v-if="error">
<span v-if="loadError"> <span v-if="loadError">
An error occured whilst loading the file. Please try again later. An error occurred whilst loading the file. Please try again later.
</span> </span>
<span v-else> <span v-else>
An error occured whilst decoding the file. An error occurred whilst decoding the file.
</span> </span>
</p> </p>
</div> </div>
......
...@@ -96,7 +96,7 @@ export default { ...@@ -96,7 +96,7 @@ export default {
<div class="flash-container" <div class="flash-container"
v-if="error"> v-if="error">
<div class="flash-alert"> <div class="flash-alert">
An error occured. Please try again. An error occurred. Please try again.
</div> </div>
</div> </div>
<label class="label-light" <label class="label-light"
......
...@@ -167,7 +167,7 @@ window.Build = (function () { ...@@ -167,7 +167,7 @@ window.Build = (function () {
Build.prototype.getBuildTrace = function () { Build.prototype.getBuildTrace = function () {
return $.ajax({ return $.ajax({
url: `${this.pageUrl}/trace.json`, url: `${this.pageUrl}/trace.json`,
data: this.state, data: { state: this.state },
}) })
.done((log) => { .done((log) => {
setCiStatusFavicon(`${this.pageUrl}/status.json`); setCiStatusFavicon(`${this.pageUrl}/status.json`);
......
...@@ -191,7 +191,7 @@ export default { ...@@ -191,7 +191,7 @@ export default {
this.service.postAction(endpoint) this.service.postAction(endpoint)
.then(() => this.fetchEnvironments()) .then(() => this.fetchEnvironments())
.catch(() => new Flash('An error occured while making the request.')); .catch(() => new Flash('An error occurred while making the request.'));
} }
}, },
......
...@@ -170,7 +170,7 @@ export default { ...@@ -170,7 +170,7 @@ export default {
this.service.postAction(endpoint) this.service.postAction(endpoint)
.then(() => this.fetchEnvironments()) .then(() => this.fetchEnvironments())
.catch(() => new Flash('An error occured while making the request.')); .catch(() => new Flash('An error occurred while making the request.'));
} }
}, },
}, },
......
...@@ -7,6 +7,8 @@ ...@@ -7,6 +7,8 @@
* causes reflows, visit https://gist.github.com/paulirish/5d52fb081b3570c81e3a * causes reflows, visit https://gist.github.com/paulirish/5d52fb081b3570c81e3a
*/ */
import Cookies from 'js-cookie';
const LINE_NUMBER_CLASS = 'diff-line-num'; const LINE_NUMBER_CLASS = 'diff-line-num';
const UNFOLDABLE_LINE_CLASS = 'js-unfold'; const UNFOLDABLE_LINE_CLASS = 'js-unfold';
const NO_COMMENT_CLASS = 'no-comment-btn'; const NO_COMMENT_CLASS = 'no-comment-btn';
...@@ -27,9 +29,7 @@ export default { ...@@ -27,9 +29,7 @@ export default {
this.userCanCreateNote = $diffFile.closest(DIFF_CONTAINER_SELECTOR).data('can-create-note') === ''; this.userCanCreateNote = $diffFile.closest(DIFF_CONTAINER_SELECTOR).data('can-create-note') === '';
} }
if (typeof notes !== 'undefined' && !this.isParallelView) { this.isParallelView = Cookies.get('diff_view') === 'parallel';
this.isParallelView = notes.isParallelView && notes.isParallelView();
}
if (this.userCanCreateNote) { if (this.userCanCreateNote) {
$diffFile.on('mouseover', LINE_COLUMN_CLASSES, e => this.showButton(this.isParallelView, e)) $diffFile.on('mouseover', LINE_COLUMN_CLASSES, e => this.showButton(this.isParallelView, e))
......
...@@ -14,7 +14,7 @@ class DropdownEmoji extends gl.FilteredSearchDropdown { ...@@ -14,7 +14,7 @@ class DropdownEmoji extends gl.FilteredSearchDropdown {
loadingTemplate: this.loadingTemplate, loadingTemplate: this.loadingTemplate,
onError() { onError() {
/* eslint-disable no-new */ /* eslint-disable no-new */
new Flash('An error occured fetching the dropdown data.'); new Flash('An error occurred fetching the dropdown data.');
/* eslint-enable no-new */ /* eslint-enable no-new */
}, },
}, },
......
...@@ -17,7 +17,7 @@ class DropdownNonUser extends gl.FilteredSearchDropdown { ...@@ -17,7 +17,7 @@ class DropdownNonUser extends gl.FilteredSearchDropdown {
preprocessing, preprocessing,
onError() { onError() {
/* eslint-disable no-new */ /* eslint-disable no-new */
new Flash('An error occured fetching the dropdown data.'); new Flash('An error occurred fetching the dropdown data.');
/* eslint-enable no-new */ /* eslint-enable no-new */
}, },
}, },
......
...@@ -27,7 +27,7 @@ class DropdownUser extends gl.FilteredSearchDropdown { ...@@ -27,7 +27,7 @@ class DropdownUser extends gl.FilteredSearchDropdown {
}, },
onError() { onError() {
/* eslint-disable no-new */ /* eslint-disable no-new */
new Flash('An error occured fetching the dropdown data.'); new Flash('An error occurred fetching the dropdown data.');
/* eslint-enable no-new */ /* eslint-enable no-new */
}, },
}, },
......
...@@ -44,7 +44,7 @@ class FilteredSearchManager { ...@@ -44,7 +44,7 @@ class FilteredSearchManager {
.catch((error) => { .catch((error) => {
if (error.name === 'RecentSearchesServiceError') return undefined; if (error.name === 'RecentSearchesServiceError') return undefined;
// eslint-disable-next-line no-new // eslint-disable-next-line no-new
new window.Flash('An error occured while parsing recent searches'); new window.Flash('An error occurred while parsing recent searches');
// Gracefully fail to empty array // Gracefully fail to empty array
return []; return [];
}) })
......
...@@ -16,9 +16,8 @@ const locales = allLocales.reduce((d, obj) => { ...@@ -16,9 +16,8 @@ const locales = allLocales.reduce((d, obj) => {
return data; return data;
}, {}); }, {});
let lang = document.querySelector('html').getAttribute('lang') || 'en'; const langAttribute = document.querySelector('html').getAttribute('lang');
lang = lang.replace(/-/g, '_'); const lang = (langAttribute || 'en').replace(/-/g, '_');
const locale = new Jed(locales[lang]); const locale = new Jed(locales[lang]);
/** /**
......
...@@ -311,7 +311,10 @@ $(function () { ...@@ -311,7 +311,10 @@ $(function () {
return $container.remove(); return $container.remove();
// Commit show suppressed diff // Commit show suppressed diff
}); });
$('.navbar-toggle').on('click', () => $('.header-content').toggleClass('menu-expanded')); $('.navbar-toggle').on('click', () => {
$('.header-content').toggleClass('menu-expanded');
gl.lazyLoader.loadCheck();
});
// Show/hide comments on diff // Show/hide comments on diff
$body.on('click', '.js-toggle-diff-comments', function (e) { $body.on('click', '.js-toggle-diff-comments', function (e) {
var $this = $(this); var $this = $(this);
......
...@@ -97,7 +97,7 @@ export default { ...@@ -97,7 +97,7 @@ export default {
postAction(endpoint) { postAction(endpoint) {
this.service.postAction(endpoint) this.service.postAction(endpoint)
.then(() => eventHub.$emit('refreshPipelines')) .then(() => eventHub.$emit('refreshPipelines'))
.catch(() => new Flash('An error occured while making the request.')); .catch(() => new Flash('An error occurred while making the request.'));
}, },
}, },
}; };
...@@ -73,7 +73,8 @@ import _ from 'underscore'; ...@@ -73,7 +73,8 @@ import _ from 'underscore';
aspectRatio: 1, aspectRatio: 1,
modal: true, modal: true,
scalable: false, scalable: false,
rotatable: false, rotatable: true,
checkOrientation: true,
zoomable: true, zoomable: true,
dragMode: 'move', dragMode: 'move',
guides: false, guides: false,
......
...@@ -37,14 +37,14 @@ export default { ...@@ -37,14 +37,14 @@ export default {
content: f.newContent, content: f.newContent,
})); }));
const payload = { const payload = {
branch: Store.targetBranch, branch: Store.currentBranch,
commit_message: commitMessage, commit_message: commitMessage,
actions, actions,
}; };
Store.submitCommitsLoading = true; Store.submitCommitsLoading = true;
Service.commitFiles(payload) Service.commitFiles(payload)
.then(this.resetCommitState) .then(this.resetCommitState)
.catch(() => Flash('An error occured while committing your changes')); .catch(() => Flash('An error occurred while committing your changes'));
}, },
resetCommitState() { resetCommitState() {
...@@ -105,7 +105,7 @@ export default { ...@@ -105,7 +105,7 @@ export default {
</label> </label>
<div class="col-md-6"> <div class="col-md-6">
<span class="help-block"> <span class="help-block">
{{targetBranch}} {{currentBranch}}
</span> </span>
</div> </div>
</div> </div>
......
...@@ -26,16 +26,6 @@ export default { ...@@ -26,16 +26,6 @@ export default {
this.editMode = !this.editMode; this.editMode = !this.editMode;
Store.toggleBlobView(); Store.toggleBlobView();
}, },
toggleProjectRefsForm() {
$('.project-refs-form').toggleClass('disabled', this.editMode);
$('.js-tree-ref-target-holder').toggle(this.editMode);
},
},
watch: {
editMode() {
this.toggleProjectRefsForm();
},
}, },
}; };
</script> </script>
......
...@@ -49,7 +49,7 @@ export default { ...@@ -49,7 +49,7 @@ export default {
v-else v-else
class="vertical-center render-error"> class="vertical-center render-error">
<p class="text-center"> <p class="text-center">
The source could not be displayed because a rendering error occured. You can <a :href="activeFile.raw_path">download</a> it instead. The source could not be displayed because a rendering error occurred. You can <a :href="activeFile.raw_path">download</a> it instead.
</p> </p>
</div> </div>
</div> </div>
......
...@@ -37,9 +37,15 @@ export default { ...@@ -37,9 +37,15 @@ export default {
let file = clickedFile; let file = clickedFile;
if (file.loading) return; if (file.loading) return;
file.loading = true; file.loading = true;
if (file.type === 'tree' && file.opened) { if (file.type === 'tree' && file.opened) {
file = Store.removeChildFilesOfTree(file); file = Store.removeChildFilesOfTree(file);
file.loading = false; file.loading = false;
} else {
const openFile = Helper.getFileFromPath(file.url);
if (openFile) {
file.loading = false;
Store.setActiveFiles(openFile);
} else { } else {
Service.url = file.url; Service.url = file.url;
Helper.getContent(file) Helper.getContent(file)
...@@ -49,6 +55,7 @@ export default { ...@@ -49,6 +55,7 @@ export default {
}) })
.catch(Helper.loadingError); .catch(Helper.loadingError);
} }
}
}, },
goToPreviousDirectoryClicked(prevURL) { goToPreviousDirectoryClicked(prevURL) {
......
...@@ -263,6 +263,10 @@ const RepoHelper = { ...@@ -263,6 +263,10 @@ const RepoHelper = {
return Store.openedFiles.find(openedFile => Store.activeFile.url === openedFile.url); return Store.openedFiles.find(openedFile => Store.activeFile.url === openedFile.url);
}, },
getFileFromPath(path) {
return Store.openedFiles.find(file => file.url === path);
},
loadingError() { loadingError() {
Flash('Unable to load this content at this time.'); Flash('Unable to load this content at this time.');
}, },
......
...@@ -11,10 +11,6 @@ function initDropdowns() { ...@@ -11,10 +11,6 @@ function initDropdowns() {
} }
function addEventsForNonVueEls() { function addEventsForNonVueEls() {
$(document).on('change', '.dropdown', () => {
Store.targetBranch = $('.project-refs-target-form input[name="ref"]').val();
});
window.onbeforeunload = function confirmUnload(e) { window.onbeforeunload = function confirmUnload(e) {
const hasChanged = Store.openedFiles const hasChanged = Store.openedFiles
.some(file => file.changed); .some(file => file.changed);
......
...@@ -32,7 +32,6 @@ const RepoStore = { ...@@ -32,7 +32,6 @@ const RepoStore = {
isCommitable: false, isCommitable: false,
binary: false, binary: false,
currentBranch: '', currentBranch: '',
targetBranch: 'new-branch',
commitMessage: '', commitMessage: '',
binaryTypes: { binaryTypes: {
png: false, png: false,
......
...@@ -43,6 +43,8 @@ import Cookies from 'js-cookie'; ...@@ -43,6 +43,8 @@ import Cookies from 'js-cookie';
$allGutterToggleIcons.removeClass('fa-angle-double-left').addClass('fa-angle-double-right'); $allGutterToggleIcons.removeClass('fa-angle-double-left').addClass('fa-angle-double-right');
$('aside.right-sidebar').removeClass('right-sidebar-collapsed').addClass('right-sidebar-expanded'); $('aside.right-sidebar').removeClass('right-sidebar-collapsed').addClass('right-sidebar-expanded');
$('.page-with-sidebar').removeClass('right-sidebar-collapsed').addClass('right-sidebar-expanded'); $('.page-with-sidebar').removeClass('right-sidebar-collapsed').addClass('right-sidebar-expanded');
if (gl.lazyLoader) gl.lazyLoader.loadCheck();
} }
if (!triggered) { if (!triggered) {
return Cookies.set("collapsed_gutter", $('.right-sidebar').hasClass('right-sidebar-collapsed')); return Cookies.set("collapsed_gutter", $('.right-sidebar').hasClass('right-sidebar-collapsed'));
......
...@@ -38,7 +38,7 @@ class SidebarMoveIssue { ...@@ -38,7 +38,7 @@ class SidebarMoveIssue {
data: (searchTerm, callback) => { data: (searchTerm, callback) => {
this.mediator.fetchAutocompleteProjects(searchTerm) this.mediator.fetchAutocompleteProjects(searchTerm)
.then(callback) .then(callback)
.catch(() => new Flash('An error occured while fetching projects autocomplete.')); .catch(() => new Flash('An error occurred while fetching projects autocomplete.'));
}, },
renderRow: project => ` renderRow: project => `
<li> <li>
...@@ -73,7 +73,7 @@ class SidebarMoveIssue { ...@@ -73,7 +73,7 @@ class SidebarMoveIssue {
this.mediator.moveIssue() this.mediator.moveIssue()
.catch(() => { .catch(() => {
Flash('An error occured while moving the issue.'); Flash('An error occurred while moving the issue.');
this.$confirmButton this.$confirmButton
.enable() .enable()
.removeClass('is-loading'); .removeClass('is-loading');
......
...@@ -41,7 +41,7 @@ export default class SidebarMediator { ...@@ -41,7 +41,7 @@ export default class SidebarMediator {
this.store.setAssigneeData(data); this.store.setAssigneeData(data);
this.store.setTimeTrackingData(data); this.store.setTimeTrackingData(data);
}) })
.catch(() => new Flash('Error occured when fetching sidebar data')); .catch(() => new Flash('Error occurred when fetching sidebar data'));
} }
fetchAutocompleteProjects(searchTerm) { fetchAutocompleteProjects(searchTerm) {
......
...@@ -535,7 +535,6 @@ ...@@ -535,7 +535,6 @@
} }
.diff-notes-collapse { .diff-notes-collapse {
position: relative;
width: 19px; width: 19px;
height: 19px; height: 19px;
padding: 0; padding: 0;
...@@ -543,11 +542,7 @@ ...@@ -543,11 +542,7 @@
z-index: 100; z-index: 100;
svg { svg {
position: absolute; vertical-align: text-top;
left: 50%;
top: 50%;
margin-left: -5.5px;
margin-top: -5.5px;
} }
path { path {
......
module CustomAttributesFilter
def by_custom_attributes(items)
return items unless params[:custom_attributes].is_a?(Hash)
return items unless Ability.allowed?(current_user, :read_custom_attribute)
association = items.reflect_on_association(:custom_attributes)
attributes_table = association.klass.arel_table
attributable_table = items.model.arel_table
custom_attributes = association.klass.select('true').where(
attributes_table[association.foreign_key]
.eq(attributable_table[association.association_primary_key])
)
# perform a subquery for each attribute to be filtered
params[:custom_attributes].inject(items) do |scope, (key, value)|
scope.where('EXISTS (?)', custom_attributes.where(key: key, value: value))
end
end
end
...@@ -16,6 +16,7 @@ ...@@ -16,6 +16,7 @@
# #
class UsersFinder class UsersFinder
include CreatedAtFilter include CreatedAtFilter
include CustomAttributesFilter
attr_accessor :current_user, :params attr_accessor :current_user, :params
...@@ -33,6 +34,7 @@ class UsersFinder ...@@ -33,6 +34,7 @@ class UsersFinder
users = by_external_identity(users) users = by_external_identity(users)
users = by_external(users) users = by_external(users)
users = by_created_at(users) users = by_created_at(users)
users = by_custom_attributes(users)
users = by_non_ldap(users) users = by_non_ldap(users)
users users
......
...@@ -10,12 +10,8 @@ module BreadcrumbsHelper ...@@ -10,12 +10,8 @@ module BreadcrumbsHelper
def breadcrumb_title_link def breadcrumb_title_link
return @breadcrumb_link if @breadcrumb_link return @breadcrumb_link if @breadcrumb_link
if controller.available_action?(:index)
url_for(action: "index")
else
request.path request.path
end end
end
def breadcrumb_title(title) def breadcrumb_title(title)
return if defined?(@breadcrumb_title) return if defined?(@breadcrumb_title)
......
...@@ -249,16 +249,25 @@ module IssuablesHelper ...@@ -249,16 +249,25 @@ module IssuablesHelper
Gitlab::IssuablesCountForState.new(finder)[state] Gitlab::IssuablesCountForState.new(finder)[state]
end end
def close_issuable_url(issuable) def close_issuable_path(issuable)
issuable_url(issuable, close_reopen_params(issuable, :close)) issuable_path(issuable, close_reopen_params(issuable, :close))
end end
def reopen_issuable_url(issuable) def reopen_issuable_path(issuable)
issuable_url(issuable, close_reopen_params(issuable, :reopen)) issuable_path(issuable, close_reopen_params(issuable, :reopen))
end end
def close_reopen_issuable_url(issuable, should_inverse = false) def close_reopen_issuable_path(issuable, should_inverse = false)
issuable.closed? ^ should_inverse ? reopen_issuable_url(issuable) : close_issuable_url(issuable) issuable.closed? ^ should_inverse ? reopen_issuable_path(issuable) : close_issuable_path(issuable)
end
def issuable_path(issuable, *options)
case issuable
when Issue
issue_path(issuable, *options)
when MergeRequest
merge_request_path(issuable, *options)
end
end end
def issuable_url(issuable, *options) def issuable_url(issuable, *options)
......
...@@ -496,13 +496,7 @@ class Repository ...@@ -496,13 +496,7 @@ class Repository
def exists? def exists?
return false unless full_path return false unless full_path
Gitlab::GitalyClient.migrate(:repository_exists) do |enabled|
if enabled
raw_repository.exists? raw_repository.exists?
else
refs_directory_exists?
end
end
end end
cache_method :exists? cache_method :exists?
...@@ -1143,12 +1137,6 @@ class Repository ...@@ -1143,12 +1137,6 @@ class Repository
blob.data blob.data
end end
def refs_directory_exists?
circuit_breaker.perform do
File.exist?(File.join(path_to_repo, 'refs'))
end
end
def cache def cache
# TODO: should we use UUIDs here? We could move repositories without clearing this cache # TODO: should we use UUIDs here? We could move repositories without clearing this cache
@cache ||= RepositoryCache.new(full_path, @project.id) @cache ||= RepositoryCache.new(full_path, @project.id)
...@@ -1200,10 +1188,6 @@ class Repository ...@@ -1200,10 +1188,6 @@ class Repository
Gitlab::Git::Repository.new(project.repository_storage, disk_path + '.git', Gitlab::GlRepository.gl_repository(project, false)) Gitlab::Git::Repository.new(project.repository_storage, disk_path + '.git', Gitlab::GlRepository.gl_repository(project, false))
end end
def circuit_breaker
@circuit_breaker ||= Gitlab::Git::Storage::CircuitBreaker.for_storage(project.repository_storage)
end
def find_commits_by_message_by_shelling_out(query, ref, path, limit, offset) def find_commits_by_message_by_shelling_out(query, ref, path, limit, offset)
ref ||= root_ref ref ||= root_ref
......
...@@ -133,6 +133,8 @@ class User < ActiveRecord::Base ...@@ -133,6 +133,8 @@ class User < ActiveRecord::Base
has_many :assigned_issues, class_name: "Issue", through: :issue_assignees, source: :issue has_many :assigned_issues, class_name: "Issue", through: :issue_assignees, source: :issue
has_many :assigned_merge_requests, dependent: :nullify, foreign_key: :assignee_id, class_name: "MergeRequest" # rubocop:disable Cop/ActiveRecordDependent has_many :assigned_merge_requests, dependent: :nullify, foreign_key: :assignee_id, class_name: "MergeRequest" # rubocop:disable Cop/ActiveRecordDependent
has_many :custom_attributes, class_name: 'UserCustomAttribute'
# #
# Validations # Validations
# #
......
class UserCustomAttribute < ActiveRecord::Base
belongs_to :user
validates :user_id, :key, :value, presence: true
validates :key, uniqueness: { scope: [:user_id] }
end
...@@ -47,4 +47,9 @@ class GlobalPolicy < BasePolicy ...@@ -47,4 +47,9 @@ class GlobalPolicy < BasePolicy
rule { ~(anonymous & restricted_public_level) }.policy do rule { ~(anonymous & restricted_public_level) }.policy do
enable :read_users_list enable :read_users_list
end end
rule { admin }.policy do
enable :read_custom_attribute
enable :update_custom_attribute
end
end end
...@@ -14,6 +14,7 @@ module MergeRequests ...@@ -14,6 +14,7 @@ module MergeRequests
notification_service.merge_mr(merge_request, current_user) notification_service.merge_mr(merge_request, current_user)
execute_hooks(merge_request, 'merge') execute_hooks(merge_request, 'merge')
invalidate_cache_counts(merge_request, users: merge_request.assignees) invalidate_cache_counts(merge_request, users: merge_request.assignees)
merge_request.update_project_counter_caches
end end
private private
......
...@@ -11,7 +11,7 @@ module Tags ...@@ -11,7 +11,7 @@ module Tags
begin begin
new_tag = repository.add_tag(current_user, tag_name, target, message) new_tag = repository.add_tag(current_user, tag_name, target, message)
rescue Rugged::TagError rescue Gitlab::Git::Repository::TagExistsError
return error("Tag #{tag_name} already exists") return error("Tag #{tag_name} already exists")
rescue Gitlab::Git::HooksService::PreReceiveError => ex rescue Gitlab::Git::HooksService::PreReceiveError => ex
return error(ex.message) return error(ex.message)
......
...@@ -12,10 +12,10 @@ module Users ...@@ -12,10 +12,10 @@ module Users
def execute(validate: true, &block) def execute(validate: true, &block)
yield(@user) if block_given? yield(@user) if block_given?
assign_attributes(&block)
user_exists = @user.persisted? user_exists = @user.persisted?
assign_attributes(&block)
if @user.save(validate: validate) if @user.save(validate: validate)
notify_success(user_exists) notify_success(user_exists)
else else
...@@ -31,7 +31,7 @@ module Users ...@@ -31,7 +31,7 @@ module Users
true true
end end
protected private
def notify_success(user_exists) def notify_success(user_exists)
notify_new_user(@user, nil) unless user_exists notify_new_user(@user, nil) unless user_exists
...@@ -39,8 +39,6 @@ module Users ...@@ -39,8 +39,6 @@ module Users
success success
end end
private
def assign_attributes(&block) def assign_attributes(&block)
if @user.user_synced_attributes_metadata if @user.user_synced_attributes_metadata
params.except!(*@user.user_synced_attributes_metadata.read_only_attributes) params.except!(*@user.user_synced_attributes_metadata.read_only_attributes)
......
- confirmation_link = confirmation_url(@resource, confirmation_token: @token)
- if @resource.unconfirmed_email.present? - if @resource.unconfirmed_email.present?
#content #content
= email_default_heading(@resource.unconfirmed_email) = email_default_heading(@resource.unconfirmed_email)
%p Click the link below to confirm your email address. %p Click the link below to confirm your email address.
#cta #cta
= link_to 'Confirm your email address', confirmation_url(@resource, confirmation_token: @token) = link_to confirmation_link, confirmation_link
- else - else
#content #content
- if Gitlab.com? - if Gitlab.com?
...@@ -12,4 +13,4 @@ ...@@ -12,4 +13,4 @@
= email_default_heading("Welcome, #{@resource.name}!") = email_default_heading("Welcome, #{@resource.name}!")
%p To get started, click the link below to confirm your account. %p To get started, click the link below to confirm your account.
#cta #cta
= link_to 'Confirm your account', confirmation_url(@resource, confirmation_token: @token) = link_to confirmation_link, confirmation_link
...@@ -26,19 +26,19 @@ ...@@ -26,19 +26,19 @@
= link_to search_path, title: 'Search', aria: { label: "Search" }, data: {toggle: 'tooltip', placement: 'bottom', container: 'body'} do = link_to search_path, title: 'Search', aria: { label: "Search" }, data: {toggle: 'tooltip', placement: 'bottom', container: 'body'} do
= icon('search') = icon('search')
- if current_user - if current_user
%li.user-counter = nav_link(path: 'dashboard#issues', html_options: { class: "user-counter" }) do
= link_to assigned_issues_dashboard_path, title: 'Issues', class: 'dashboard-shortcuts-issues', aria: { label: "Issues" }, data: {toggle: 'tooltip', placement: 'bottom', container: 'body'} do = link_to assigned_issues_dashboard_path, title: 'Issues', class: 'dashboard-shortcuts-issues', aria: { label: "Issues" }, data: {toggle: 'tooltip', placement: 'bottom', container: 'body'} do
= custom_icon('issues') = custom_icon('issues')
- issues_count = assigned_issuables_count(:issues) - issues_count = assigned_issuables_count(:issues)
%span.badge.issues-count{ class: ('hidden' if issues_count.zero?) } %span.badge.issues-count{ class: ('hidden' if issues_count.zero?) }
= number_with_delimiter(issues_count) = number_with_delimiter(issues_count)
%li.user-counter = nav_link(path: 'dashboard#merge_requests', html_options: { class: "user-counter" }) do
= link_to assigned_mrs_dashboard_path, title: 'Merge requests', class: 'dashboard-shortcuts-merge_requests', aria: { label: "Merge requests" }, data: {toggle: 'tooltip', placement: 'bottom', container: 'body'} do = link_to assigned_mrs_dashboard_path, title: 'Merge requests', class: 'dashboard-shortcuts-merge_requests', aria: { label: "Merge requests" }, data: {toggle: 'tooltip', placement: 'bottom', container: 'body'} do
= custom_icon('mr_bold') = custom_icon('mr_bold')
- merge_requests_count = assigned_issuables_count(:merge_requests) - merge_requests_count = assigned_issuables_count(:merge_requests)
%span.badge.merge-requests-count{ class: ('hidden' if merge_requests_count.zero?) } %span.badge.merge-requests-count{ class: ('hidden' if merge_requests_count.zero?) }
= number_with_delimiter(merge_requests_count) = number_with_delimiter(merge_requests_count)
%li.user-counter = nav_link(controller: 'dashboard/todos', html_options: { class: "user-counter" }) do
= link_to dashboard_todos_path, title: 'Todos', aria: { label: "Todos" }, class: 'shortcuts-todos', data: {toggle: 'tooltip', placement: 'bottom', container: 'body'} do = link_to dashboard_todos_path, title: 'Todos', aria: { label: "Todos" }, class: 'shortcuts-todos', data: {toggle: 'tooltip', placement: 'bottom', container: 'body'} do
= custom_icon('todo_done') = custom_icon('todo_done')
%span.badge.todos-count{ class: ('hidden' if todos_pending_count.zero?) } %span.badge.todos-count{ class: ('hidden' if todos_pending_count.zero?) }
......
...@@ -16,5 +16,5 @@ ...@@ -16,5 +16,5 @@
= breadcrumb_list_item link_to(extra[:text], extra[:link]) = breadcrumb_list_item link_to(extra[:text], extra[:link])
= render "layouts/nav/breadcrumbs/collapsed_dropdown", location: :after = render "layouts/nav/breadcrumbs/collapsed_dropdown", location: :after
%li %li
%h2.breadcrumbs-sub-title= @breadcrumb_title %h2.breadcrumbs-sub-title= link_to @breadcrumb_title, breadcrumb_title_link
= yield :header_content = yield :header_content
- access = note_max_access_for_user(note) - access = note_max_access_for_user(note)
- if note.has_special_role?(Note::SpecialRole::FIRST_TIME_CONTRIBUTOR) - if note.has_special_role?(Note::SpecialRole::FIRST_TIME_CONTRIBUTOR)
%span.note-role.note-role-special.has-tooltip{ title: _("This is the author's first Merge Request to this project. Handle with care.") } %span.note-role.note-role-special.has-tooltip{ title: _("This is the author's first Merge Request to this project.") }
= issuable_first_contribution_icon = issuable_first_contribution_icon
- if access.nonzero? - if access.nonzero?
%span.note-role.note-role-access= Gitlab::Access.human_access(access) %span.note-role.note-role-access= Gitlab::Access.human_access(access)
......
.tree-ref-container .tree-ref-container
.tree-ref-holder .tree-ref-holder
= render 'shared/ref_switcher', destination: 'tree', path: @path = render 'shared/ref_switcher', destination: 'tree', path: @path
- if show_new_repo?
.tree-ref-target-holder.js-tree-ref-target-holder
= icon('long-arrow-right', title: 'to target branch')
= render 'shared/target_switcher', destination: 'tree', path: @path
- unless show_new_repo? - unless show_new_repo?
= render 'projects/tree/old_tree_header' = render 'projects/tree/old_tree_header'
......
- dropdown_toggle_text = @ref || @project.default_branch
= form_tag nil, method: :get, class: "project-refs-form project-refs-target-form" do
= hidden_field_tag :destination, destination
- if defined?(path)
= hidden_field_tag :path, path
- @options && @options.each do |key, value|
= hidden_field_tag key, value, id: nil
.dropdown
= dropdown_toggle dropdown_toggle_text, { toggle: "dropdown", selected: dropdown_toggle_text, ref: @ref, refs_url: refs_project_path(@project, find: ['branches']), field_name: 'ref', input_field_name: 'new-branch', submit_form_on_click: true, visit: false }, { toggle_class: "js-project-refs-dropdown" }
.dropdown-menu.dropdown-menu-selectable.git-revision-dropdown{ class: ("dropdown-menu-align-right" if local_assigns[:align_right]) }
= dropdown_title _("Create a new branch")
= dropdown_input _("Create a new branch")
= dropdown_title _("Select existing branch"), options: {close: false}
= dropdown_filter _("Search branches and tags")
= dropdown_content
= dropdown_loading
...@@ -3,9 +3,9 @@ ...@@ -3,9 +3,9 @@
- button_method = issuable_close_reopen_button_method(issuable) - button_method = issuable_close_reopen_button_method(issuable)
- if can_update && is_current_user - if can_update && is_current_user
= link_to "Close #{display_issuable_type}", close_issuable_url(issuable), method: button_method, = link_to "Close #{display_issuable_type}", close_issuable_path(issuable), method: button_method,
class: "hidden-xs hidden-sm btn btn-grouped btn-close js-btn-issue-action #{issuable_button_visibility(issuable, true)}", title: "Close #{display_issuable_type}" class: "hidden-xs hidden-sm btn btn-grouped btn-close js-btn-issue-action #{issuable_button_visibility(issuable, true)}", title: "Close #{display_issuable_type}"
= link_to "Reopen #{display_issuable_type}", reopen_issuable_url(issuable), method: button_method, = link_to "Reopen #{display_issuable_type}", reopen_issuable_path(issuable), method: button_method,
class: "hidden-xs hidden-sm btn btn-grouped btn-reopen js-btn-issue-action #{issuable_button_visibility(issuable, false)}", title: "Reopen #{display_issuable_type}" class: "hidden-xs hidden-sm btn btn-grouped btn-reopen js-btn-issue-action #{issuable_button_visibility(issuable, false)}", title: "Reopen #{display_issuable_type}"
- elsif can_update && !is_current_user - elsif can_update && !is_current_user
= render 'shared/issuable/close_reopen_report_toggle', issuable: issuable = render 'shared/issuable/close_reopen_report_toggle', issuable: issuable
......
...@@ -7,7 +7,7 @@ ...@@ -7,7 +7,7 @@
- button_method = issuable_close_reopen_button_method(issuable) - button_method = issuable_close_reopen_button_method(issuable)
.pull-left.btn-group.prepend-left-10.issuable-close-dropdown.droplab-dropdown.js-issuable-close-dropdown .pull-left.btn-group.prepend-left-10.issuable-close-dropdown.droplab-dropdown.js-issuable-close-dropdown
= link_to "#{display_button_action} #{display_issuable_type}", close_reopen_issuable_url(issuable), = link_to "#{display_button_action} #{display_issuable_type}", close_reopen_issuable_path(issuable),
method: button_method, class: "#{button_class} btn-#{button_action}", title: "#{display_button_action} #{display_issuable_type}" method: button_method, class: "#{button_class} btn-#{button_action}", title: "#{display_button_action} #{display_issuable_type}"
= button_tag type: 'button', class: "#{toggle_class} btn-#{button_action}-color", = button_tag type: 'button', class: "#{toggle_class} btn-#{button_action}-color",
...@@ -16,7 +16,7 @@ ...@@ -16,7 +16,7 @@
%ul#issuable-close-menu.js-issuable-close-menu.dropdown-menu{ class: button_responsive_class, data: { dropdown: true } } %ul#issuable-close-menu.js-issuable-close-menu.dropdown-menu{ class: button_responsive_class, data: { dropdown: true } }
%li.close-item{ class: "#{issuable_button_visibility(issuable, true) || 'droplab-item-selected'}", %li.close-item{ class: "#{issuable_button_visibility(issuable, true) || 'droplab-item-selected'}",
data: { text: "Close #{display_issuable_type}", url: close_issuable_url(issuable), data: { text: "Close #{display_issuable_type}", url: close_issuable_path(issuable),
button_class: "#{button_class} btn-close", toggle_class: "#{toggle_class} btn-close-color", method: button_method } } button_class: "#{button_class} btn-close", toggle_class: "#{toggle_class} btn-close-color", method: button_method } }
%button.btn.btn-transparent %button.btn.btn-transparent
= icon('check', class: 'icon') = icon('check', class: 'icon')
...@@ -26,7 +26,7 @@ ...@@ -26,7 +26,7 @@
= display_issuable_type = display_issuable_type
%li.reopen-item{ class: "#{issuable_button_visibility(issuable, false) || 'droplab-item-selected'}", %li.reopen-item{ class: "#{issuable_button_visibility(issuable, false) || 'droplab-item-selected'}",
data: { text: "Reopen #{display_issuable_type}", url: reopen_issuable_url(issuable), data: { text: "Reopen #{display_issuable_type}", url: reopen_issuable_path(issuable),
button_class: "#{button_class} btn-reopen", toggle_class: "#{toggle_class} btn-reopen-color", method: button_method } } button_class: "#{button_class} btn-reopen", toggle_class: "#{toggle_class} btn-reopen-color", method: button_method } }
%button.btn.btn-transparent %button.btn.btn-transparent
= icon('check', class: 'icon') = icon('check', class: 'icon')
......
---
title: Confirmation email shows link as text instead of human readable text
merge_request: 14243
author: bitsapien
type: changed
---
title: Add active states to nav bar counters
merge_request:
author:
type: changed
---
title: Fix 500 error on merged merge requests when GitLab is restored from a backup
merge_request:
author:
type: fixed
---
title: Fix notes type created from import. This should fix some missing notes issues
from imported projects
merge_request: 14524
author:
type: fixed
---
title: Adjust MRs being stuck on "process of being merged" for more than 2 hours
merge_request:
author:
type: fixed
---
title: Fixes data parameter not being sent in ajax request for jobs log
merge_request:
author:
type: fixed
---
title: Add index for merge_requests.merge_commit_sha
merge_request:
author:
type: other
---
title: Fixed issue/merge request breadcrumb titles not having links
merge_request:
author:
type: fixed
---
title: Fix CSRF validation issue when closing/opening merge requests from the UI
merge_request: 14555
author:
type: fixed
---
title: Fixed commenting on side-by-side commit diff
merge_request:
author:
type: fixed
---
title: Make sure API responds with 401 when invalid authentication info is provided
merge_request:
author:
type: fixed
---
title: Clarify artifact download via the API only accepts branch or tag name for ref
merge_request:
author:
type: other
---
title: Change recommended MySQL version to 5.6
merge_request:
author:
type: other
---
title: Support custom attributes on users
merge_request: 13038
author: Markus Koller
---
title: Fix merge request counter updates after merge
merge_request:
author:
type: fixed
---
title: Fix profile image orientation based on EXIF data gvieira37
merge_request: 14461
author: gvieira37
type: fixed
---
title: Gitaly RepositoryExists remains opt-in for all method calls
merge_request:
author:
type: fixed
class CreateUserCustomAttributes < ActiveRecord::Migration
include Gitlab::Database::MigrationHelpers
DOWNTIME = false
def change
create_table :user_custom_attributes do |t|
t.timestamps_with_timezone null: false
t.references :user, null: false, foreign_key: { on_delete: :cascade }
t.string :key, null: false
t.string :value, null: false
t.index [:user_id, :key], unique: true
t.index [:key, :value]
end
end
end
# See http://doc.gitlab.com/ce/development/migration_style_guide.html
# for more information on how to write migrations for GitLab.
class AddCompositeIndexOnMergeRequestsMergeCommitSha < ActiveRecord::Migration
include Gitlab::Database::MigrationHelpers
# Set this constant to true if this migration requires downtime.
DOWNTIME = false
# The default index name is too long for PostgreSQL and would thus be
# truncated.
INDEX_NAME = 'index_merge_requests_on_tp_id_and_merge_commit_sha_and_id'
COLUMNS = [:target_project_id, :merge_commit_sha, :id]
disable_ddl_transaction!
def up
return if index_is_present?
add_concurrent_index(:merge_requests, COLUMNS, name: INDEX_NAME)
end
def down
return unless index_is_present?
remove_concurrent_index(:merge_requests, COLUMNS, name: INDEX_NAME)
end
def index_is_present?
index_exists?(:merge_requests, COLUMNS, name: INDEX_NAME)
end
end
class UpdateLegacyDiffNotesTypeForImport < ActiveRecord::Migration
include Gitlab::Database::MigrationHelpers
DOWNTIME = false
disable_ddl_transaction!
def up
update_column_in_batches(:notes, :type, 'LegacyDiffNote') do |table, query|
query.where(table[:type].eq('Github::Import::LegacyDiffNote'))
end
end
def down
end
end
class UpdateNotesTypeForImport < ActiveRecord::Migration
include Gitlab::Database::MigrationHelpers
DOWNTIME = false
disable_ddl_transaction!
def up
update_column_in_batches(:notes, :type, 'Note') do |table, query|
query.where(table[:type].eq('Github::Import::Note'))
end
end
def down
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: 20170921115009) do ActiveRecord::Schema.define(version: 20170928100231) 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"
...@@ -1123,6 +1123,7 @@ ActiveRecord::Schema.define(version: 20170921115009) do ...@@ -1123,6 +1123,7 @@ ActiveRecord::Schema.define(version: 20170921115009) do
add_index "merge_requests", ["source_project_id", "source_branch"], name: "index_merge_requests_on_source_project_id_and_source_branch", using: :btree add_index "merge_requests", ["source_project_id", "source_branch"], name: "index_merge_requests_on_source_project_id_and_source_branch", using: :btree
add_index "merge_requests", ["target_branch"], name: "index_merge_requests_on_target_branch", using: :btree add_index "merge_requests", ["target_branch"], name: "index_merge_requests_on_target_branch", using: :btree
add_index "merge_requests", ["target_project_id", "iid"], name: "index_merge_requests_on_target_project_id_and_iid", unique: true, using: :btree add_index "merge_requests", ["target_project_id", "iid"], name: "index_merge_requests_on_target_project_id_and_iid", unique: true, using: :btree
add_index "merge_requests", ["target_project_id", "merge_commit_sha", "id"], name: "index_merge_requests_on_tp_id_and_merge_commit_sha_and_id", using: :btree
add_index "merge_requests", ["title"], name: "index_merge_requests_on_title", using: :btree add_index "merge_requests", ["title"], name: "index_merge_requests_on_title", using: :btree
add_index "merge_requests", ["title"], name: "index_merge_requests_on_title_trigram", using: :gin, opclasses: {"title"=>"gin_trgm_ops"} add_index "merge_requests", ["title"], name: "index_merge_requests_on_title_trigram", using: :gin, opclasses: {"title"=>"gin_trgm_ops"}
...@@ -1890,6 +1891,17 @@ ActiveRecord::Schema.define(version: 20170921115009) do ...@@ -1890,6 +1891,17 @@ ActiveRecord::Schema.define(version: 20170921115009) do
add_index "user_agent_details", ["subject_id", "subject_type"], name: "index_user_agent_details_on_subject_id_and_subject_type", using: :btree add_index "user_agent_details", ["subject_id", "subject_type"], name: "index_user_agent_details_on_subject_id_and_subject_type", using: :btree
create_table "user_custom_attributes", force: :cascade do |t|
t.datetime "created_at", null: false
t.datetime "updated_at", null: false
t.integer "user_id", null: false
t.string "key", null: false
t.string "value", null: false
end
add_index "user_custom_attributes", ["key", "value"], name: "index_user_custom_attributes_on_key_and_value", using: :btree
add_index "user_custom_attributes", ["user_id", "key"], name: "index_user_custom_attributes_on_user_id_and_key", unique: true, using: :btree
create_table "user_synced_attributes_metadata", force: :cascade do |t| create_table "user_synced_attributes_metadata", force: :cascade do |t|
t.boolean "name_synced", default: false t.boolean "name_synced", default: false
t.boolean "email_synced", default: false t.boolean "email_synced", default: false
...@@ -2160,6 +2172,7 @@ ActiveRecord::Schema.define(version: 20170921115009) do ...@@ -2160,6 +2172,7 @@ ActiveRecord::Schema.define(version: 20170921115009) do
add_foreign_key "todos", "projects", name: "fk_45054f9c45", on_delete: :cascade add_foreign_key "todos", "projects", name: "fk_45054f9c45", on_delete: :cascade
add_foreign_key "trending_projects", "projects", on_delete: :cascade add_foreign_key "trending_projects", "projects", on_delete: :cascade
add_foreign_key "u2f_registrations", "users" add_foreign_key "u2f_registrations", "users"
add_foreign_key "user_custom_attributes", "users", on_delete: :cascade
add_foreign_key "user_synced_attributes_metadata", "users", on_delete: :cascade add_foreign_key "user_synced_attributes_metadata", "users", on_delete: :cascade
add_foreign_key "users_star_projects", "projects", name: "fk_22cd27ddfc", on_delete: :cascade add_foreign_key "users_star_projects", "projects", name: "fk_22cd27ddfc", on_delete: :cascade
add_foreign_key "web_hook_logs", "web_hooks", on_delete: :cascade add_foreign_key "web_hook_logs", "web_hooks", on_delete: :cascade
......
...@@ -14,6 +14,7 @@ following locations: ...@@ -14,6 +14,7 @@ following locations:
- [Project-level Variables](project_level_variables.md) - [Project-level Variables](project_level_variables.md)
- [Group-level Variables](group_level_variables.md) - [Group-level Variables](group_level_variables.md)
- [Commits](commits.md) - [Commits](commits.md)
- [Custom Attributes](custom_attributes.md)
- [Deployments](deployments.md) - [Deployments](deployments.md)
- [Deploy Keys](deploy_keys.md) - [Deploy Keys](deploy_keys.md)
- [Environments](environments.md) - [Environments](environments.md)
......
# Custom Attributes API
Every API call to custom attributes must be authenticated as administrator.
## List custom attributes
Get all custom attributes on a user.
```
GET /users/:id/custom_attributes
```
| Attribute | Type | Required | Description |
| --------- | ---- | -------- | ----------- |
| `id` | integer | yes | The ID of a user |
```bash
curl --header "PRIVATE-TOKEN: 9koXpg98eAheJpvBs5tK" https://gitlab.example.com/api/v4/users/42/custom_attributes
```
Example response:
```json
[
{
"key": "location",
"value": "Antarctica"
},
{
"key": "role",
"value": "Developer"
}
]
```
## Single custom attribute
Get a single custom attribute on a user.
```
GET /users/:id/custom_attributes/:key
```
| Attribute | Type | Required | Description |
| --------- | ---- | -------- | ----------- |
| `id` | integer | yes | The ID of a user |
| `key` | string | yes | The key of the custom attribute |
```bash
curl --header "PRIVATE-TOKEN: 9koXpg98eAheJpvBs5tK" https://gitlab.example.com/api/v4/users/42/custom_attributes/location
```
Example response:
```json
{
"key": "location",
"value": "Antarctica"
}
```
## Set custom attribute
Set a custom attribute on a user. The attribute will be updated if it already exists,
or newly created otherwise.
```
PUT /users/:id/custom_attributes/:key
```
| Attribute | Type | Required | Description |
| --------- | ---- | -------- | ----------- |
| `id` | integer | yes | The ID of a user |
| `key` | string | yes | The key of the custom attribute |
| `value` | string | yes | The value of the custom attribute |
```bash
curl --request PUT --header "PRIVATE-TOKEN: 9koXpg98eAheJpvBs5tK" --data "value=Greenland" https://gitlab.example.com/api/v4/users/42/custom_attributes/location
```
Example response:
```json
{
"key": "location",
"value": "Greenland"
}
```
## Delete custom attribute
Delete a custom attribute on a user.
```
DELETE /users/:id/custom_attributes/:key
```
| Attribute | Type | Required | Description |
| --------- | ---- | -------- | ----------- |
| `id` | integer | yes | The ID of a user |
| `key` | string | yes | The key of the custom attribute |
```bash
curl --request DELETE --header "PRIVATE-TOKEN: 9koXpg98eAheJpvBs5tK" https://gitlab.example.com/api/v4/users/42/custom_attributes/location
```
...@@ -359,7 +359,7 @@ Parameters ...@@ -359,7 +359,7 @@ Parameters
| Attribute | Type | Required | Description | | Attribute | Type | Required | Description |
|-------------|---------|----------|-------------------------- | |-------------|---------|----------|-------------------------- |
| `id` | integer/string | yes | The ID or [URL-encoded path of the project](README.md#namespaced-path-encoding) owned by the authenticated user | | `id` | integer/string | yes | The ID or [URL-encoded path of the project](README.md#namespaced-path-encoding) owned by the authenticated user |
| `ref_name` | string | yes | The ref from a repository | | `ref_name` | string | yes | The ref from a repository (can only be branch or tag name, not HEAD or SHA) |
| `job` | string | yes | The name of the job | | `job` | string | yes | The name of the job |
| `job_token` | string | no | To be used with [triggers] for multi-project pipelines. Is should be invoked only inside `.gitlab-ci.yml`. Its value is always `$CI_JOB_TOKEN`. | | `job_token` | string | no | To be used with [triggers] for multi-project pipelines. Is should be invoked only inside `.gitlab-ci.yml`. Its value is always `$CI_JOB_TOKEN`. |
......
...@@ -156,6 +156,12 @@ You can search users by creation date time range with: ...@@ -156,6 +156,12 @@ You can search users by creation date time range with:
GET /users?created_before=2001-01-02T00:00:00.060Z&created_after=1999-01-02T00:00:00.060 GET /users?created_before=2001-01-02T00:00:00.060Z&created_after=1999-01-02T00:00:00.060
``` ```
You can filter by [custom attributes](custom_attributes.md) with:
```
GET /users?custom_attributes[key]=value&custom_attributes[other_key]=other_value
```
## Single user ## Single user
Get a single user. Get a single user.
......
...@@ -250,6 +250,8 @@ By default, when using `docker:dind`, Docker uses the `vfs` storage driver which ...@@ -250,6 +250,8 @@ By default, when using `docker:dind`, Docker uses the `vfs` storage driver which
copies the filesystem on every run. This is a very disk-intensive operation copies the filesystem on every run. This is a very disk-intensive operation
which can be avoided if a different driver is used, for example `overlay2`. which can be avoided if a different driver is used, for example `overlay2`.
### Requirements
1. Make sure a recent kernel is used, preferably `>= 4.2`. 1. Make sure a recent kernel is used, preferably `>= 4.2`.
1. Check whether the `overlay` module is loaded: 1. Check whether the `overlay` module is loaded:
...@@ -271,14 +273,27 @@ which can be avoided if a different driver is used, for example `overlay2`. ...@@ -271,14 +273,27 @@ which can be avoided if a different driver is used, for example `overlay2`.
overlay overlay
``` ```
1. Use the driver by defining a variable at the top of your `.gitlab-ci.yml`: ### Use driver per project
``` You can enable the driver for each project individually by editing the project's `.gitlab-ci.yml`:
variables:
```
variables:
DOCKER_DRIVER: overlay2 DOCKER_DRIVER: overlay2
``` ```
> **Note:** ### Use driver for every project
To enable the driver for every project, you can set the environment variable for every build by adding `environment` in the `[[runners]]` section of `config.toml`:
```toml
environment = ["DOCKER_DRIVER=overlay2"]
```
If you're running multiple Runners you will have to modify all configuration files.
> **Notes:**
- More information about the Runner configuration is available in the [Runner documentation](https://docs.gitlab.com/runner/configuration/).
- For more information about using OverlayFS with Docker, you can read - For more information about using OverlayFS with Docker, you can read
[Use the OverlayFS storage driver](https://docs.docker.com/engine/userguide/storagedriver/overlayfs-driver/). [Use the OverlayFS storage driver](https://docs.docker.com/engine/userguide/storagedriver/overlayfs-driver/).
......
...@@ -154,6 +154,16 @@ always in-sync with the codebase. ...@@ -154,6 +154,16 @@ always in-sync with the codebase.
EE-specific tests follows the same organization, but under the `spec/ee` folder. EE-specific tests follows the same organization, but under the `spec/ee` folder.
## Test for what should not be there
This is particularly important for permission calls and might be called a
negative assertion: make sure only the bare minimum is returned and nothing else.
See an issue about [leaking tokens] as an example of a vulnerability that is
captured by such a test.
[leaking tokens]: https://gitlab.com/gitlab-org/gitlab-ce/issues/37948
## How to test at the correct level? ## How to test at the correct level?
As many things in life, deciding what to test at each level of testing is a As many things in life, deciding what to test at each level of testing is a
......
# Illustrations
The illustrations should always align with topics and goals in specific context.
## Principles
#### Be simple.
- For clarity, we use simple and specific elements to create our illustrations.
#### Be optimistic.
- We are an open-minded, optimistic, and friendly team. We should reflect those values in our illustrations to connect with our brand experience.
#### Be gentle.
- Our illustrations assist users in understanding context and guide users in the right direction. Illustrations are supportive, so they should be obvious but not aggressive.
## Style
#### Shapes
- All illustrations are geometric rather than organic.
- The illustrations are made by circles, rectangles, squares, and triangles.
<img src="img/illustrations-geometric.png" width=224px alt="Example for geometric" />
#### Stroke
- Standard border thickness: **4px**
- Depending on the situation, border thickness can be changed to **3px**. For example, when the illustration size is small, an illustration with 4px border thickness would look tight. In this case, the border thickness can be changed to 3px.
- We use **rounded caps** and **rounded corner**.
| Do | Don't |
| -------- | -------- |
| <img src="img/illustrations-caps-do.png" width= 133px alt="Do: caps and corner" /> | <img src="img/illustrations-caps-don't.png" width= 133px alt="Don't: caps and corner"/> |
#### Radius
- Standard corner radius: **10px**
- Depending on the situation, corner radius can be changed to **5px**. For example, when the illustration size is small, an illustration with 10px corner radius would be over-rounded. In this case, the corner radius can be changed to 5px.
<img src="img/illustrations-border-radius.png" width= 464px alt="Example for border radius"/>
#### Size
Depends on the situation, the illustration size can be the 3 types below:
**Large**
* Use case: Empty states, error pages(e.g. 404, 403)
* For vertical layout, the illustration should not larger than **430*300 px**.
* For horizontal layout, the illustration should not larger than **430*380 px**.
| Vertical layout | Horizontal layout |
| --------------- | ----------------- |
| <img src="img/illustration-size-large-vertical.png" /> | <img src="img/illustration-size-large-horizontal.png" />
**Medium**
* Use case: Banner
* The illustration should not larger than **240*160 px**
* The illustration should keep simple and clear. We recommend not including too many elements in the medium size illustration.
<img src="img/illustration-size-medium.png" width=983px />
**Small**
* Use case: Graphics for explanatory text, graphics for status.
* The illustration should not larger than **160*90 px**.
* The illustration should keep simple and clear. We recommend not including too many elements in the small size illustration.
<img src="img/illustration-size-small.png" width=983px />
**Illustration on mobile**
- Keep the proportions in original ratio.
#### Colors palette
For consistency, we recommend choosing colors from our color palette.
| Orange | Purple | Grey |
| -------- | -------- | -------- |
| <img src="img/illustrations-color-orange.png" width= 160px alt="Orange" /> | <img src="img/illustrations-color-purple.png" width= 160px alt="Purple" /> | <img src="img/illustrations-color-grey.png" width= 160px alt="Grey" /> |
| #FC6D26 | #6B4FBB | #EEEEEE |
#### Don't
- Don't include the typography in the illustration.
- Don't include tanuki in the illustration. If necessary, we recommend having tanuki monochromatic.
---
| Orange | Purple |
| -------- | -------- |
| <img src="img/illustrations-palette-oragne.png" width= 160px alt="Palette - Orange" /> | <img src="img/illustrations-palette-purple.png" width= 160px alt="Palette - Purple" /> |
...@@ -21,6 +21,11 @@ Guidance on the timing, curving and motion for GitLab. ...@@ -21,6 +21,11 @@ Guidance on the timing, curving and motion for GitLab.
--- ---
### [Illustrations](illustrations.md)
Guidelines for principals and styles related to illustrations for GitLab.
---
### [Copy](copy.md) ### [Copy](copy.md)
Conventions on text and messaging within labels, buttons, and other components. Conventions on text and messaging within labels, buttons, and other components.
......
# Writing documentation # Writing documentation
- **General Documentation**: written by the developers responsible by creating features. Should be submitted in the same merge request containing code. Feature proposals (by GitLab contributors) should also be accompanied by its respective documentation. They can be later improved by PMs and Technical Writers. - **General Documentation**: written by the [developers responsible by creating features](#contributing-to-docs). Should be submitted in the same merge request containing code. Feature proposals (by GitLab contributors) should also be accompanied by its respective documentation. They can be later improved by PMs and Technical Writers.
- **Technical Articles**: written by any [GitLab Team](https://about.gitlab.com/team/) member, GitLab contributors, or [Community Writers](https://about.gitlab.com/handbook/product/technical-writing/community-writers/). - **Technical Articles**: written by any [GitLab Team](https://about.gitlab.com/team/) member, GitLab contributors, or [Community Writers](https://about.gitlab.com/handbook/product/technical-writing/community-writers/).
- **Indexes per topic**: initially prepared by the Technical Writing Team, and kept up-to-date by developers and PMs in the same merge request containing code. They gather all resources for that topic in a single page (user and admin documentation, articles, and third-party docs). - **Indexes per topic**: initially prepared by the Technical Writing Team, and kept up-to-date by developers and PMs in the same merge request containing code. They gather all resources for that topic in a single page (user and admin documentation, articles, and third-party docs).
...@@ -69,6 +69,51 @@ Use the [writing method](https://about.gitlab.com/handbook/product/technical-wri ...@@ -69,6 +69,51 @@ Use the [writing method](https://about.gitlab.com/handbook/product/technical-wri
All the docs follow the same [styleguide](doc_styleguide.md). All the docs follow the same [styleguide](doc_styleguide.md).
### Contributing to docs
Whenever a feature is changed, updated, introduced, or deprecated, the merge
request introducing these changes must be accompanied by the documentation
(either updating existing ones or creating new ones). This is also valid when
changes are introduced to the UI.
The one resposible for writing the first piece of documentation is the developer who
wrote the code. It's the job of the Product Manager to ensure all features are
shipped with its docs, whether is a small or big change. At the pace GitLab evolves,
this is the only way to keep the docs up-to-date. If you have any questions about it,
please ask a Technical Writer. Otherwise, when your content is ready, assign one of
them to review it for you.
We use the [monthly release blog post](https://about.gitlab.com/handbook/marketing/blog/release-posts/#monthly-releases) as a changelog checklist to ensure everything
is documented.
### Feature overview and use cases
Every major feature (regardless if present in GitLab Community or Enterprise editions)
should present, at the beginning of the document, two main sections: **overview** and
**use cases**. Every GitLab EE-only feature should also contain these sections.
**Overview**: at the name suggests, the goal here is to provide an overview of the feature.
Describe what is it, what it does, why it is important/cool/nice-to-have,
what problem it solves, and what you can do with this feature that you couldn't
do before.
**Use cases**: provide at least two, ideally three, use cases for every major feature.
You should answer this question: what can you do with this feature/change? Use cases
are examples of how this feauture or change can be used in real life.
Examples:
- CE and EE: [Issues](../user/project/issues/index.md#use-cases)
- CE and EE: [Merge Requests](../user/project/merge_requests/index.md#overview)
- EE-only: [Geo](https://docs.gitlab.com/ee/gitlab-geo/README.html#overview)
- EE-only: [Jenkins integration](https://docs.gitlab.com/ee/integration/jenkins.md#overview)
Note that if you don't have anything to add between the doc title (`<h1>`) and
the header `## Overview`, you can omit the header, but keep the content of the
overview there.
> **Overview** and **use cases** are required to **every** Enterprise Edition feature,
and for every **major** feature present in Community Edition.
### Markdown ### Markdown
Currently GitLab docs use Redcarpet as [markdown](../user/markdown.md) engine, but there's an [open discussion](https://gitlab.com/gitlab-com/gitlab-docs/issues/50) for implementing Kramdown in the near future. Currently GitLab docs use Redcarpet as [markdown](../user/markdown.md) engine, but there's an [open discussion](https://gitlab.com/gitlab-com/gitlab-docs/issues/50) for implementing Kramdown in the near future.
......
# Database MySQL # Database MySQL
>**Note:** >**Note:**
We do not recommend using MySQL due to various issues. For example, case - We do not recommend using MySQL due to various issues. For example, case
[(in)sensitivity](https://dev.mysql.com/doc/refman/5.0/en/case-sensitivity.html) [(in)sensitivity](https://dev.mysql.com/doc/refman/5.0/en/case-sensitivity.html)
and [problems](https://bugs.mysql.com/bug.php?id=65830) that and [problems](https://bugs.mysql.com/bug.php?id=65830) that
[suggested](https://bugs.mysql.com/bug.php?id=50909) [suggested](https://bugs.mysql.com/bug.php?id=50909)
[fixes](https://bugs.mysql.com/bug.php?id=65830) [have](https://bugs.mysql.com/bug.php?id=63164). [fixes](https://bugs.mysql.com/bug.php?id=65830) [have](https://bugs.mysql.com/bug.php?id=63164).
- We recommend using MySQL version 5.6 or later. Please see the following [issue][ce-38152].
## Initial database setup ## Initial database setup
...@@ -13,7 +14,7 @@ and [problems](https://bugs.mysql.com/bug.php?id=65830) that ...@@ -13,7 +14,7 @@ and [problems](https://bugs.mysql.com/bug.php?id=65830) that
# Install the database packages # Install the database packages
sudo apt-get install -y mysql-server mysql-client libmysqlclient-dev sudo apt-get install -y mysql-server mysql-client libmysqlclient-dev
# Ensure you have MySQL version 5.5.14 or later # Ensure you have MySQL version 5.6 or later
mysql --version mysql --version
# Pick a MySQL root password (can be anything), type it and press enter # Pick a MySQL root password (can be anything), type it and press enter
...@@ -293,3 +294,4 @@ Details can be found in the [PostgreSQL][postgres-text-type] and ...@@ -293,3 +294,4 @@ Details can be found in the [PostgreSQL][postgres-text-type] and
[postgres-text-type]: http://www.postgresql.org/docs/9.2/static/datatype-character.html [postgres-text-type]: http://www.postgresql.org/docs/9.2/static/datatype-character.html
[mysql-text-types]: http://dev.mysql.com/doc/refman/5.7/en/string-type-overview.html [mysql-text-types]: http://dev.mysql.com/doc/refman/5.7/en/string-type-overview.html
[ce-38152]: https://gitlab.com/gitlab-org/gitlab-ce/issues/38152
---
last_updated: 2017-09-25
---
# GitLab Kubernetes / OpenShift integration # GitLab Kubernetes / OpenShift integration
GitLab can be configured to interact with Kubernetes, or other systems using the GitLab can be configured to interact with Kubernetes, or other systems using the
...@@ -6,10 +10,6 @@ Kubernetes API (such as OpenShift). ...@@ -6,10 +10,6 @@ Kubernetes API (such as OpenShift).
Each project can be configured to connect to a different Kubernetes cluster, see Each project can be configured to connect to a different Kubernetes cluster, see
the [configuration](#configuration) section. the [configuration](#configuration) section.
If you have a single cluster that you want to use for all your projects,
you can pre-fill the settings page with a default template. To configure the
template, see the [Services Templates](services_templates.md) document.
## Configuration ## Configuration
Navigate to the [Integrations page](project_services.md#accessing-the-project-services) Navigate to the [Integrations page](project_services.md#accessing-the-project-services)
...@@ -47,30 +47,77 @@ The Kubernetes service takes the following parameters: ...@@ -47,30 +47,77 @@ The Kubernetes service takes the following parameters:
[Kubernetes dashboard](https://kubernetes.io/docs/tasks/access-application-cluster/web-ui-dashboard/#config) [Kubernetes dashboard](https://kubernetes.io/docs/tasks/access-application-cluster/web-ui-dashboard/#config)
(under **Config > Secrets**). (under **Config > Secrets**).
[namespace]: https://kubernetes.io/docs/user-guide/namespaces/ TIP: **Tip:**
If you have a single cluster that you want to use for all your projects,
you can pre-fill the settings page with a default template. To configure the
template, see [Services Templates](services_templates.md).
## Deployment variables ## Deployment variables
The Kubernetes service exposes following The Kubernetes service exposes the following
[deployment variables](../../../ci/variables/README.md#deployment-variables) in the [deployment variables](../../../ci/variables/README.md#deployment-variables) in the
GitLab CI build environment: GitLab CI/CD build environment:
- `KUBE_URL` - equal to the API URL - `KUBE_URL` - Equal to the API URL.
- `KUBE_TOKEN` - `KUBE_TOKEN` - The Kubernetes token.
- `KUBE_NAMESPACE` - The Kubernetes namespace is auto-generated if not specified. - `KUBE_NAMESPACE` - The Kubernetes namespace is auto-generated if not specified.
The default value is `<project_name>-<project_id>`. You can overwrite it to The default value is `<project_name>-<project_id>`. You can overwrite it to
use different one if needed, otherwise the `KUBE_NAMESPACE` variable will use different one if needed, otherwise the `KUBE_NAMESPACE` variable will
receive the default value. receive the default value.
- `KUBE_CA_PEM_FILE` - only present if a custom CA bundle was specified. Path - `KUBE_CA_PEM_FILE` - Only present if a custom CA bundle was specified. Path
to a file containing PEM data. to a file containing PEM data.
- `KUBE_CA_PEM` (deprecated)- only if a custom CA bundle was specified. Raw PEM data. - `KUBE_CA_PEM` (deprecated) - Only if a custom CA bundle was specified. Raw PEM data.
- `KUBECONFIG` - Path to a file containing kubeconfig for this deployment. CA bundle would be embedded if specified. - `KUBECONFIG` - Path to a file containing `kubeconfig` for this deployment.
CA bundle would be embedded if specified.
## What you can get with the Kubernetes integration
Here's what you can do with GitLab if you enable the Kubernetes integration.
### Deploy Boards (EEP)
> Available in [GitLab Enterprise Edition Premium][ee].
GitLab's Deploy Boards offer a consolidated view of the current health and
status of each CI [environment](../../../ci/environments.md) running on Kubernetes,
displaying the status of the pods in the deployment. Developers and other
teammates can view the progress and status of a rollout, pod by pod, in the
workflow they already use without any need to access Kubernetes.
[> Read more about Deploy Boards](https://docs.gitlab.com/ee/user/project/deploy_boards.html)
## Web terminals ### Canary Deployments (EEP)
> Available in [GitLab Enterprise Edition Premium][ee].
Leverage [Kubernetes' Canary deployments](https://kubernetes.io/docs/concepts/cluster-administration/manage-deployment/#canary-deployments)
and visualize your canary deployments right inside the Deploy Board, without
the need to leave GitLab.
[> Read more about Canary Deployments](https://docs.gitlab.com/ee/user/project/canary_deployments.html)
### Kubernetes monitoring
Automatically detect and monitor Kubernetes metrics. Automatic monitoring of
[NGINX ingress](./prometheus_library/nginx.md) is also supported.
[> Read more about Kubernetes monitoring](prometheus_library/kubernetes.md)
### Auto DevOps
Auto DevOps automatically detects, builds, tests, deploys, and monitors your
applications.
To make full use of Auto DevOps(Auto Deploy, Auto Review Apps, and Auto Monitoring)
you will need the Kubernetes project integration enabled.
[> Read more about Auto DevOps](../../../topics/autodevops/index.md)
### Web terminals
NOTE: **Note:** NOTE: **Note:**
Added in GitLab 8.15. You must be the project owner or have `master` permissions Introduced in GitLab 8.15. You must be the project owner or have `master` permissions
to use terminals. Support is currently limited to the first container in the to use terminals. Support is limited to the first container in the
first pod of your environment. first pod of your environment.
When enabled, the Kubernetes service adds [web terminal](../../../ci/environments.md#web-terminals) When enabled, the Kubernetes service adds [web terminal](../../../ci/environments.md#web-terminals)
...@@ -79,3 +126,5 @@ Docker and Kubernetes, so you get a new shell session within your existing ...@@ -79,3 +126,5 @@ Docker and Kubernetes, so you get a new shell session within your existing
containers. To use this integration, you should deploy to Kubernetes using containers. To use this integration, you should deploy to Kubernetes using
the deployment variables above, ensuring any pods you create are labelled with the deployment variables above, ensuring any pods you create are labelled with
`app=$CI_ENVIRONMENT_SLUG`. GitLab will do the rest! `app=$CI_ENVIRONMENT_SLUG`. GitLab will do the rest!
[ee]: https://about.gitlab.com/gitlab-ee/
# Monitoring AWS Resources # Monitoring AWS Resources
> [Introduced](https://gitlab.com/gitlab-org/gitlab-ce/merge_requests/12621) in GitLab 9.4 > [Introduced](https://gitlab.com/gitlab-org/gitlab-ce/merge_requests/12621) in GitLab 9.4
GitLab has support for automatically detecting and monitoring AWS resources, starting with the [Elastic Load Balancer](https://aws.amazon.com/elasticloadbalancing/). This is provided by leveraging the official [Cloudwatch exporter](https://github.com/prometheus/cloudwatch_exporter), which translates [Cloudwatch metrics](https://aws.amazon.com/cloudwatch/) into a Prometheus readable form. GitLab has support for automatically detecting and monitoring AWS resources, starting with the [Elastic Load Balancer](https://aws.amazon.com/elasticloadbalancing/). This is provided by leveraging the official [Cloudwatch exporter](https://github.com/prometheus/cloudwatch_exporter), which translates [Cloudwatch metrics](https://aws.amazon.com/cloudwatch/) into a Prometheus readable form.
## Requirements
The [Prometheus service](../prometheus/index.md) must be enabled.
## Metrics supported ## Metrics supported
| Name | Query | | Name | Query |
......
...@@ -3,6 +3,10 @@ ...@@ -3,6 +3,10 @@
GitLab has support for automatically detecting and monitoring HAProxy. This is provided by leveraging the [HAProxy Exporter](https://github.com/prometheus/haproxy_exporter), which translates HAProxy statistics into a Prometheus readable form. GitLab has support for automatically detecting and monitoring HAProxy. This is provided by leveraging the [HAProxy Exporter](https://github.com/prometheus/haproxy_exporter), which translates HAProxy statistics into a Prometheus readable form.
## Requirements
The [Prometheus service](../prometheus/index.md) must be enabled.
## Metrics supported ## Metrics supported
| Name | Query | | Name | Query |
......
# Monitoring Kubernetes # Monitoring Kubernetes
> [Introduced](https://gitlab.com/gitlab-org/gitlab-ce/merge_requests/8935) in GitLab 9.0 > [Introduced](https://gitlab.com/gitlab-org/gitlab-ce/merge_requests/8935) in GitLab 9.0
GitLab has support for automatically detecting and monitoring Kubernetes metrics. Kubernetes exposes Node level metrics out of the box via the built-in [Prometheus metrics support in cAdvisor](https://github.com/google/cadvisor). No additional services or exporters are needed. GitLab has support for automatically detecting and monitoring Kubernetes metrics.
## Requirements
The [Prometheus](../prometheus.md) and [Kubernetes](../kubernetes.md)
integration services must be enabled.
## Metrics supported ## Metrics supported
...@@ -23,4 +29,4 @@ Prometheus server up and running. You have two options here: ...@@ -23,4 +29,4 @@ Prometheus server up and running. You have two options here:
In order to isolate and only display relevant metrics for a given environment In order to isolate and only display relevant metrics for a given environment
however, GitLab needs a method to detect which labels are associated. To do this, GitLab will [look for an `environment` label](metrics.md#identifying-environments). however, GitLab needs a method to detect which labels are associated. To do this, GitLab will [look for an `environment` label](metrics.md#identifying-environments).
If you are using [GitLab Auto-Deploy][../../../ci/autodeploy/index.md] and one of the two [provided Kubernetes monitoring solutions](../prometheus.md#getting-started-with-prometheus-monitoring), the `environment` label will be automatically added. If you are using [GitLab Auto-Deploy](../../../../ci/autodeploy/index.md) and one of the two [provided Kubernetes monitoring solutions](../prometheus.md#getting-started-with-prometheus-monitoring), the `environment` label will be automatically added.
# Monitoring NGINX # Monitoring NGINX
> [Introduced](https://gitlab.com/gitlab-org/gitlab-ce/merge_requests/12621) in GitLab 9.4 > [Introduced](https://gitlab.com/gitlab-org/gitlab-ce/merge_requests/12621) in GitLab 9.4
GitLab has support for automatically detecting and monitoring NGINX. This is provided by leveraging the [NGINX VTS exporter](https://github.com/hnlq715/nginx-vts-exporter), which translates [VTS statistics](https://github.com/vozlt/nginx-module-vts) into a Prometheus readable form. GitLab has support for automatically detecting and monitoring NGINX. This is provided by leveraging the [NGINX VTS exporter](https://github.com/hnlq715/nginx-vts-exporter), which translates [VTS statistics](https://github.com/vozlt/nginx-module-vts) into a Prometheus readable form.
## Requirements
The [Prometheus service](../prometheus/index.md) must be enabled.
## Metrics supported ## Metrics supported
| Name | Query | | Name | Query |
......
# Monitoring NGINX Ingress Controller # Monitoring NGINX Ingress Controller
> [Introduced](https://gitlab.com/gitlab-org/gitlab-ce/merge_requests/13438) in GitLab 9.5 > [Introduced](https://gitlab.com/gitlab-org/gitlab-ce/merge_requests/13438) in GitLab 9.5
GitLab has support for automatically detecting and monitoring the Kubernetes NGINX ingress controller. This is provided by leveraging the built in Prometheus metrics included in [version 0.9.0](https://github.com/kubernetes/ingress/blob/master/controllers/nginx/Changelog.md#09-beta1) of the ingress. GitLab has support for automatically detecting and monitoring the Kubernetes NGINX ingress controller. This is provided by leveraging the built in Prometheus metrics included in [version 0.9.0](https://github.com/kubernetes/ingress/blob/master/controllers/nginx/Changelog.md#09-beta1) of the ingress.
## Requirements
The [Prometheus service](../prometheus/index.md) must be enabled.
## Metrics supported ## Metrics supported
| Name | Query | | Name | Query |
......
...@@ -62,7 +62,7 @@ which is highly recommendable and much faster than hardcoding. ...@@ -62,7 +62,7 @@ which is highly recommendable and much faster than hardcoding.
If you set up a GitLab Pages project on GitLab.com, If you set up a GitLab Pages project on GitLab.com,
it will automatically be accessible under a it will automatically be accessible under a
[subdomain of `namespace.pages.io`](https://docs.gitlab.com/ce/user/project/pages/). [subdomain of `namespace.pages.io`](introduction.md#gitlab-pages-on-gitlab-com).
The `namespace` is defined by your username on GitLab.com, The `namespace` is defined by your username on GitLab.com,
or the group name you created this project under. or the group name you created this project under.
...@@ -73,6 +73,8 @@ Pages wildcard domain. This guide is valid for any GitLab instance, ...@@ -73,6 +73,8 @@ Pages wildcard domain. This guide is valid for any GitLab instance,
you just need to replace Pages wildcard domain on GitLab.com you just need to replace Pages wildcard domain on GitLab.com
(`*.gitlab.io`) with your own. (`*.gitlab.io`) with your own.
Learn more about [namespaces](../../group/index.md#namespaces).
### Practical examples ### Practical examples
#### Project Websites #### Project Websites
......
---
last_updated: 2017-09-28
---
# GitLab Pages from A to Z: Part 3 # GitLab Pages from A to Z: Part 3
> **Article [Type](../../../development/writing_documentation.html#types-of-technical-articles)**: user guide || > **[Article Type](../../../development/writing_documentation.md#types-of-technical-articles)**: user guide ||
> **Level**: beginner || > **Level**: beginner ||
> **Author**: [Marcia Ramos](https://gitlab.com/marcia) || > **Author**: [Marcia Ramos](https://gitlab.com/marcia) ||
> **Publication date:** 2017/02/22 > **Publication date:** 2017-02-22 ||
> **Last updated**: 2017-09-28
- [Part 1: Static sites and GitLab Pages domains](getting_started_part_one.md) - [Part 1: Static sites and GitLab Pages domains](getting_started_part_one.md)
- [Part 2: Quick start guide - Setting up GitLab Pages](getting_started_part_two.md) - [Part 2: Quick start guide - Setting up GitLab Pages](getting_started_part_two.md)
...@@ -16,6 +21,21 @@ As described in the previous part of this series, setting up GitLab Pages with c ...@@ -16,6 +21,21 @@ As described in the previous part of this series, setting up GitLab Pages with c
These steps assume you've already [set your site up](getting_started_part_two.md) and and it's served under the default Pages domain `namespace.gitlab.io`, or `namespace.gitlab.io/project-name`. These steps assume you've already [set your site up](getting_started_part_two.md) and and it's served under the default Pages domain `namespace.gitlab.io`, or `namespace.gitlab.io/project-name`.
### Adding your custom domain to GitLab Pages
To use one or more custom domain with your Pages site, there are two things
you should consider first, which we'll cover in this guide:
1. Either if you're adding a **root domain** or a **subdomain**, for which
you'll need to set up [DNS records](#dns-records)
1. Whether you want to add an [SSL/TLS certificate](#ssl-tls-certificates) or not
To finish the association, you need to [add your domain to your project's Pages settings](#add-your-custom-domain-to-gitlab-pages-settings).
Let's start from the beginning with [DNS records](#dns-records).
If you already know how they work and want to skip the introduction to DNS,
you may be interested in skipping it until the [TL;DR](#tl-dr) section below.
### DNS Records ### DNS Records
A Domain Name System (DNS) web service routes visitors to websites A Domain Name System (DNS) web service routes visitors to websites
...@@ -99,6 +119,29 @@ domain. E.g., **do not** point your `subdomain.domain.com` to ...@@ -99,6 +119,29 @@ domain. E.g., **do not** point your `subdomain.domain.com` to
`namespace.gitlab.io.` or `namespace.gitlab.io/`. `namespace.gitlab.io.` or `namespace.gitlab.io/`.
> - GitLab Pages IP on GitLab.com [has been changed](https://about.gitlab.com/2017/03/06/we-are-changing-the-ip-of-gitlab-pages-on-gitlab-com/) from `104.208.235.32` to `52.167.214.135`. > - GitLab Pages IP on GitLab.com [has been changed](https://about.gitlab.com/2017/03/06/we-are-changing-the-ip-of-gitlab-pages-on-gitlab-com/) from `104.208.235.32` to `52.167.214.135`.
### Add your custom domain to GitLab Pages settings
Once you've set the DNS record, you'll need navigate to your project's
**Setting > Pages** and click **+ New domain** to add your custom domain to
GitLab Pages. You can choose whether to add an [SSL/TLS certificate](#ssl-tls-certificates)
to make your website accessible under HTTPS or leave it blank. If don't add a certificate,
your site will be accessible only via HTTP:
![Add new domain](img/add_certificate_to_pages.png)
You can add more than one alias (custom domains and subdomains) to the same project.
An alias can be understood as having many doors leading to the same room.
All the aliases you've set to your site will be listed on **Setting > Pages**.
From that page, you can view, add, and remove them.
Note that [DNS propagation may take some time (up to 24h)](http://www.inmotionhosting.com/support/domain-names/dns-nameserver-changes/domain-names-dns-changes),
although it's usually a matter of minutes to complete. Until it does, visit attempts
to your domain will respond with a 404.
Read through the [general documentation on GitLab Pages](introduction.md#add-a-custom-domain-to-your-pages-website) to learn more about adding
custom domains to GitLab Pages sites.
### SSL/TLS Certificates ### SSL/TLS Certificates
Every GitLab Pages project on GitLab.com will be available under Every GitLab Pages project on GitLab.com will be available under
......
...@@ -28,7 +28,8 @@ In general there are two types of pages one might create: ...@@ -28,7 +28,8 @@ In general there are two types of pages one might create:
- Pages per project (`username.example.io/projectname` or `groupname.example.io/projectname`) - Pages per project (`username.example.io/projectname` or `groupname.example.io/projectname`)
In GitLab, usernames and groupnames are unique and we often refer to them In GitLab, usernames and groupnames are unique and we often refer to them
as namespaces. There can be only one namespace in a GitLab instance. Below you as [namespaces](../../group/index.md#namespaces). There can be only one namespace
in a GitLab instance. Below you
can see the connection between the type of GitLab Pages, what the project name can see the connection between the type of GitLab Pages, what the project name
that is created on GitLab looks like and the website URL it will be ultimately that is created on GitLab looks like and the website URL it will be ultimately
be served on. be served on.
...@@ -98,6 +99,9 @@ The steps to create a project page for a user or a group are identical: ...@@ -98,6 +99,9 @@ The steps to create a project page for a user or a group are identical:
A user's project will be served under `http(s)://username.example.io/projectname` A user's project will be served under `http(s)://username.example.io/projectname`
whereas a group's project under `http(s)://groupname.example.io/projectname`. whereas a group's project under `http(s)://groupname.example.io/projectname`.
For practical examples for group and project Pages, read through the guide
[GitLab Pages from A to Z: Part 1 - Static sites and GitLab Pages domains](getting_started_part_one.md#practical-examples).
## Quick Start ## Quick Start
Read through [GitLab Pages Quick Start Guide][pages-quick] or watch the video tutorial on Read through [GitLab Pages Quick Start Guide][pages-quick] or watch the video tutorial on
...@@ -111,6 +115,9 @@ The key thing about GitLab Pages is the `.gitlab-ci.yml` file, something that ...@@ -111,6 +115,9 @@ The key thing about GitLab Pages is the `.gitlab-ci.yml` file, something that
gives you absolute control over the build process. You can actually watch your gives you absolute control over the build process. You can actually watch your
website being built live by following the CI job traces. website being built live by following the CI job traces.
For a simplified user guide on setting up GitLab CI/CD for Pages, read through
the article [GitLab Pages from A to Z: Part 4 - Creating and Tweaking `.gitlab-ci.yml` for GitLab Pages](getting_started_part_four.md#creating-and-tweaking-gitlab-ci-yml-for-gitlab-pages)
> **Note:** > **Note:**
> Before reading this section, make sure you familiarize yourself with GitLab CI > Before reading this section, make sure you familiarize yourself with GitLab CI
> and the specific syntax of[`.gitlab-ci.yml`][yaml] by > and the specific syntax of[`.gitlab-ci.yml`][yaml] by
...@@ -311,6 +318,9 @@ Visit the GitLab Pages group for a full list of example projects: ...@@ -311,6 +318,9 @@ Visit the GitLab Pages group for a full list of example projects:
### Add a custom domain to your Pages website ### Add a custom domain to your Pages website
For a complete guide on Pages domains, read through the article
[GitLab Pages from A to Z: Part 3 - Setting Up Custom Domains - DNS Records and SSL/TLS Certificates](getting_started_part_three.md#setting-up-custom-domains-dns-records-and-ssl-tls-certificates)
If this setting is enabled by your GitLab administrator, you should be able to If this setting is enabled by your GitLab administrator, you should be able to
see the **New Domain** button when visiting your project's settings through the see the **New Domain** button when visiting your project's settings through the
gear icon in the top right and then navigating to **Pages**. gear icon in the top right and then navigating to **Pages**.
...@@ -349,6 +359,9 @@ private key when adding a new domain. ...@@ -349,6 +359,9 @@ private key when adding a new domain.
![Pages upload cert](img/pages_upload_cert.png) ![Pages upload cert](img/pages_upload_cert.png)
For a complete guide on Pages domains, read through the article
[GitLab Pages from A to Z: Part 3 - Setting Up Custom Domains - DNS Records and SSL/TLS Certificates](getting_started_part_three.md#setting-up-custom-domains-dns-records-and-ssl-tls-certificates)
### Custom error codes pages ### Custom error codes pages
You can provide your own 403 and 404 error pages by creating the `403.html` and You can provide your own 403 and 404 error pages by creating the `403.html` and
...@@ -387,6 +400,8 @@ If you are using GitLab.com to host your website, then: ...@@ -387,6 +400,8 @@ If you are using GitLab.com to host your website, then:
The rest of the guide still applies. The rest of the guide still applies.
See also: [GitLab Pages from A to Z: Part 1 - Static sites and GitLab Pages domains](getting_started_part_one.md#gitlab-pages-domain).
## Limitations ## Limitations
When using Pages under the general domain of a GitLab instance (`*.example.io`), When using Pages under the general domain of a GitLab instance (`*.example.io`),
......
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
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