summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--app/assets/javascripts/behaviors/autosize.js45
-rw-r--r--app/assets/javascripts/behaviors/details_behavior.js45
-rw-r--r--app/assets/javascripts/behaviors/index.js9
-rw-r--r--app/assets/javascripts/behaviors/quick_submit.js109
-rw-r--r--app/assets/javascripts/behaviors/requires_input.js90
-rw-r--r--app/assets/javascripts/behaviors/toggler_behavior.js83
-rw-r--r--app/assets/javascripts/dispatcher.js6
-rw-r--r--app/assets/javascripts/group.js21
-rw-r--r--app/assets/javascripts/main.js9
-rw-r--r--app/controllers/admin/application_controller.rb2
-rw-r--r--app/controllers/admin/impersonations_controller.rb2
-rw-r--r--app/helpers/visibility_level_helper.rb2
-rw-r--r--app/models/user.rb4
-rw-r--r--app/policies/ci/runner_policy.rb2
-rw-r--r--app/services/users/create_service.rb6
-rw-r--r--app/uploaders/file_uploader.rb4
-rw-r--r--app/views/layouts/header/_default.html.haml2
-rw-r--r--app/views/notify/project_was_exported_email.html.haml2
-rw-r--r--app/views/shared/_group_form.html.haml18
-rw-r--r--changelogs/unreleased/add-field-for-group-name.yml4
-rw-r--r--changelogs/unreleased/clean_carrierwave_tempfiles.yml4
-rw-r--r--changelogs/unreleased/remove_is_admin.yml4
-rw-r--r--changelogs/unreleased/spec_for_schema.yml4
-rw-r--r--config/webpack.config.js1
-rw-r--r--db/post_migrate/20170408033905_remove_old_cache_directories.rb23
-rw-r--r--db/schema.rb2
-rw-r--r--doc/gitlab-basics/create-group.md2
-rw-r--r--doc/gitlab-basics/img/create_new_group_info.pngbin20321 -> 105173 bytes
-rw-r--r--doc/workflow/groups.md4
-rw-r--r--doc/workflow/groups/new_group_form.pngbin27263 -> 114515 bytes
-rw-r--r--lib/api/entities.rb6
-rw-r--r--lib/api/groups.rb2
-rw-r--r--lib/api/helpers.rb4
-rw-r--r--lib/api/notes.rb2
-rw-r--r--lib/api/runners.rb8
-rw-r--r--lib/api/services.rb4
-rw-r--r--lib/api/users.rb6
-rw-r--r--lib/api/v3/groups.rb2
-rw-r--r--lib/api/v3/notes.rb2
-rw-r--r--lib/api/v3/runners.rb2
-rw-r--r--lib/api/v3/services.rb2
-rw-r--r--lib/gitlab/etag_caching/middleware.rb36
-rw-r--r--lib/gitlab/etag_caching/router.rb39
-rw-r--r--lib/gitlab/import_export/relation_factory.rb2
-rw-r--r--lib/gitlab/visibility_level.rb2
-rw-r--r--spec/features/admin/admin_groups_spec.rb39
-rw-r--r--spec/features/admin/admin_users_spec.rb2
-rw-r--r--spec/features/dashboard/group_spec.rb14
-rw-r--r--spec/features/groups_spec.rb2
-rw-r--r--spec/lib/gitlab/etag_caching/router_spec.rb83
-rw-r--r--spec/migrations/schema_spec.rb23
-rw-r--r--spec/models/user_spec.rb4
-rw-r--r--spec/requests/api/session_spec.rb6
-rw-r--r--spec/services/notification_service_spec.rb16
54 files changed, 509 insertions, 308 deletions
diff --git a/app/assets/javascripts/behaviors/autosize.js b/app/assets/javascripts/behaviors/autosize.js
index f7f41d55b52..3bea460dcc6 100644
--- a/app/assets/javascripts/behaviors/autosize.js
+++ b/app/assets/javascripts/behaviors/autosize.js
@@ -1,28 +1,23 @@
-/* eslint-disable func-names, space-before-function-paren, prefer-arrow-callback, no-var, consistent-return, max-len */
-/* global autosize */
+import autosize from 'vendor/autosize';
-var autosize = require('vendor/autosize');
+$(() => {
+ const $fields = $('.js-autosize');
-(function() {
- $(function() {
- var $fields;
- $fields = $('.js-autosize');
- $fields.on('autosize:resized', function() {
- var $field;
- $field = $(this);
- return $field.data('height', $field.outerHeight());
- });
- $fields.on('resize.autosize', function() {
- var $field;
- $field = $(this);
- if ($field.data('height') !== $field.outerHeight()) {
- $field.data('height', $field.outerHeight());
- autosize.destroy($field);
- return $field.css('max-height', window.outerHeight);
- }
- });
- autosize($fields);
- autosize.update($fields);
- return $fields.css('resize', 'vertical');
+ $fields.on('autosize:resized', function resized() {
+ const $field = $(this);
+ $field.data('height', $field.outerHeight());
});
-}).call(window);
+
+ $fields.on('resize.autosize', function resize() {
+ const $field = $(this);
+ if ($field.data('height') !== $field.outerHeight()) {
+ $field.data('height', $field.outerHeight());
+ autosize.destroy($field);
+ $field.css('max-height', window.outerHeight);
+ }
+ });
+
+ autosize($fields);
+ autosize.update($fields);
+ $fields.css('resize', 'vertical');
+});
diff --git a/app/assets/javascripts/behaviors/details_behavior.js b/app/assets/javascripts/behaviors/details_behavior.js
index fd0840fa117..7c9dbcc8d6e 100644
--- a/app/assets/javascripts/behaviors/details_behavior.js
+++ b/app/assets/javascripts/behaviors/details_behavior.js
@@ -1,26 +1,23 @@
-/* eslint-disable func-names, space-before-function-paren, prefer-arrow-callback, quotes, no-var, vars-on-top, max-len */
-(function() {
- $(function() {
- $("body").on("click", ".js-details-target", function() {
- var container;
- container = $(this).closest(".js-details-container");
- return container.toggleClass("open");
- });
- // Show details content. Hides link after click.
- //
- // %div
- // %a.js-details-expand
- // %div.js-details-content
- //
- return $("body").on("click", ".js-details-expand", function(e) {
- $(this).next('.js-details-content').removeClass("hide");
- $(this).hide();
- var truncatedItem = $(this).siblings('.js-details-short');
- if (truncatedItem.length) {
- truncatedItem.addClass("hide");
- }
- return e.preventDefault();
- });
+$(() => {
+ $('body').on('click', '.js-details-target', function target() {
+ $(this).closest('.js-details-container').toggleClass('open');
});
-}).call(window);
+
+ // Show details content. Hides link after click.
+ //
+ // %div
+ // %a.js-details-expand
+ // %div.js-details-content
+ //
+ $('body').on('click', '.js-details-expand', function expand(e) {
+ e.preventDefault();
+ $(this).next('.js-details-content').removeClass('hide');
+ $(this).hide();
+
+ const truncatedItem = $(this).siblings('.js-details-short');
+ if (truncatedItem.length) {
+ truncatedItem.addClass('hide');
+ }
+ });
+});
diff --git a/app/assets/javascripts/behaviors/index.js b/app/assets/javascripts/behaviors/index.js
new file mode 100644
index 00000000000..5b931e6cfa6
--- /dev/null
+++ b/app/assets/javascripts/behaviors/index.js
@@ -0,0 +1,9 @@
+import './autosize';
+import './bind_in_out';
+import './details_behavior';
+import { installGlEmojiElement } from './gl_emoji';
+import './quick_submit';
+import './requires_input';
+import './toggler_behavior';
+
+installGlEmojiElement();
diff --git a/app/assets/javascripts/behaviors/quick_submit.js b/app/assets/javascripts/behaviors/quick_submit.js
index 626f3503c91..3d162b24413 100644
--- a/app/assets/javascripts/behaviors/quick_submit.js
+++ b/app/assets/javascripts/behaviors/quick_submit.js
@@ -1,4 +1,4 @@
-/* eslint-disable func-names, space-before-function-paren, one-var, no-var, one-var-declaration-per-line, prefer-arrow-callback, camelcase, consistent-return, quotes, object-shorthand, comma-dangle, max-len */
+import '../commons/bootstrap';
// Quick Submit behavior
//
@@ -6,9 +6,6 @@
// "Meta+Enter" (Mac) or "Ctrl+Enter" (Linux/Windows) key combination, the form
// is submitted.
//
-import '../commons/bootstrap';
-
-//
// ### Example Markup
//
// <form action="/foo" class="js-quick-submit">
@@ -17,61 +14,59 @@ import '../commons/bootstrap';
// <input type="submit" value="Submit" />
// </form>
//
-(function() {
- var isMac, keyCodeIs;
- isMac = function() {
- return navigator.userAgent.match(/Macintosh/);
- };
+function isMac() {
+ return navigator.userAgent.match(/Macintosh/);
+}
- keyCodeIs = function(e, keyCode) {
- if ((e.originalEvent && e.originalEvent.repeat) || e.repeat) {
- return false;
- }
- return e.keyCode === keyCode;
- };
+function keyCodeIs(e, keyCode) {
+ if ((e.originalEvent && e.originalEvent.repeat) || e.repeat) {
+ return false;
+ }
+ return e.keyCode === keyCode;
+}
- $(document).on('keydown.quick_submit', '.js-quick-submit', function(e) {
- var $form, $submit_button;
- // Enter
- if (!keyCodeIs(e, 13)) {
- return;
- }
- if (!((e.metaKey && !e.altKey && !e.ctrlKey && !e.shiftKey) || (e.ctrlKey && !e.altKey && !e.metaKey && !e.shiftKey))) {
- return;
- }
- e.preventDefault();
- $form = $(e.target).closest('form');
- $submit_button = $form.find('input[type=submit], button[type=submit]');
- if ($submit_button.attr('disabled')) {
- return;
- }
- $submit_button.disable();
- return $form.submit();
- });
+$(document).on('keydown.quick_submit', '.js-quick-submit', (e) => {
+ // Enter
+ if (!keyCodeIs(e, 13)) {
+ return;
+ }
+
+ const onlyMeta = e.metaKey && !e.altKey && !e.ctrlKey && !e.shiftKey;
+ const onlyCtrl = e.ctrlKey && !e.altKey && !e.metaKey && !e.shiftKey;
+ if (!onlyMeta && !onlyCtrl) {
+ return;
+ }
+
+ e.preventDefault();
+ const $form = $(e.target).closest('form');
+ const $submitButton = $form.find('input[type=submit], button[type=submit]');
+
+ if (!$submitButton.attr('disabled')) {
+ $submitButton.disable();
+ $form.submit();
+ }
+});
+
+// If the user tabs to a submit button on a `js-quick-submit` form, display a
+// tooltip to let them know they could've used the hotkey
+$(document).on('keyup.quick_submit', '.js-quick-submit input[type=submit], .js-quick-submit button[type=submit]', function displayTooltip(e) {
+ // Tab
+ if (!keyCodeIs(e, 9)) {
+ return;
+ }
+
+ const $this = $(this);
+ const title = isMac() ?
+ 'You can also press &#8984;-Enter' :
+ 'You can also press Ctrl-Enter';
- // If the user tabs to a submit button on a `js-quick-submit` form, display a
- // tooltip to let them know they could've used the hotkey
- $(document).on('keyup.quick_submit', '.js-quick-submit input[type=submit], .js-quick-submit button[type=submit]', function(e) {
- var $this, title;
- // Tab
- if (!keyCodeIs(e, 9)) {
- return;
- }
- if (isMac()) {
- title = "You can also press &#8984;-Enter";
- } else {
- title = "You can also press Ctrl-Enter";
- }
- $this = $(this);
- return $this.tooltip({
- container: 'body',
- html: 'true',
- placement: 'auto top',
- title: title,
- trigger: 'manual'
- }).tooltip('show').one('blur', function() {
- return $this.tooltip('hide');
- });
+ $this.tooltip({
+ container: 'body',
+ html: 'true',
+ placement: 'auto top',
+ title,
+ trigger: 'manual',
});
-}).call(window);
+ $this.tooltip('show').one('blur', () => $this.tooltip('hide'));
+});
diff --git a/app/assets/javascripts/behaviors/requires_input.js b/app/assets/javascripts/behaviors/requires_input.js
index eb7143f5b1a..b20d108aa25 100644
--- a/app/assets/javascripts/behaviors/requires_input.js
+++ b/app/assets/javascripts/behaviors/requires_input.js
@@ -1,12 +1,10 @@
-/* eslint-disable func-names, space-before-function-paren, one-var, no-var, one-var-declaration-per-line, quotes, prefer-template, prefer-arrow-callback, no-else-return, consistent-return, max-len */
+import '../commons/bootstrap';
+
// Requires Input behavior
//
// When called on a form with input fields with the `required` attribute, the
// form's submit button will be disabled until all required fields have values.
//
-import '../commons/bootstrap';
-
-//
// ### Example Markup
//
// <form class="js-requires-input">
@@ -14,49 +12,43 @@ import '../commons/bootstrap';
// <input type="submit" value="Submit">
// </form>
//
-(function() {
- $.fn.requiresInput = function() {
- var $button, $form, fieldSelector, requireInput, required;
- $form = $(this);
- $button = $('button[type=submit], input[type=submit]', $form);
- required = '[required=required]';
- fieldSelector = "input" + required + ", select" + required + ", textarea" + required;
- requireInput = function() {
- var values;
- values = _.map($(fieldSelector, $form), function(field) {
- // Collect the input values of *all* required fields
- return field.value;
- });
- // Disable the button if any required fields are empty
- if (values.length && _.any(values, _.isEmpty)) {
- return $button.disable();
- } else {
- return $button.enable();
- }
- };
- // Set initial button state
- requireInput();
- return $form.on('change input', fieldSelector, requireInput);
- };
- $(function() {
- var $form, hideOrShowHelpBlock;
- $form = $('form.js-requires-input');
- $form.requiresInput();
- // Hide or Show the help block when creating a new project
- // based on the option selected
- hideOrShowHelpBlock = function(form) {
- var selected;
- selected = $('.js-select-namespace option:selected');
- if (selected.length && selected.data('options-parent') === 'groups') {
- return form.find('.help-block').hide();
- } else if (selected.length) {
- return form.find('.help-block').show();
- }
- };
- hideOrShowHelpBlock($form);
- return $('.select2.js-select-namespace').change(function() {
- return hideOrShowHelpBlock($form);
- });
- });
-}).call(window);
+$.fn.requiresInput = function requiresInput() {
+ const $form = $(this);
+ const $button = $('button[type=submit], input[type=submit]', $form);
+ const fieldSelector = 'input[required=required], select[required=required], textarea[required=required]';
+
+ function requireInput() {
+ // Collect the input values of *all* required fields
+ const values = _.map($(fieldSelector, $form), field => field.value);
+
+ // Disable the button if any required fields are empty
+ if (values.length && _.any(values, _.isEmpty)) {
+ $button.disable();
+ } else {
+ $button.enable();
+ }
+ }
+
+ // Set initial button state
+ requireInput();
+ $form.on('change input', fieldSelector, requireInput);
+};
+
+// Hide or Show the help block when creating a new project
+// based on the option selected
+function hideOrShowHelpBlock(form) {
+ const selected = $('.js-select-namespace option:selected');
+ if (selected.length && selected.data('options-parent') === 'groups') {
+ form.find('.help-block').hide();
+ } else if (selected.length) {
+ form.find('.help-block').show();
+ }
+}
+
+$(() => {
+ const $form = $('form.js-requires-input');
+ $form.requiresInput();
+ hideOrShowHelpBlock($form);
+ $('.select2.js-select-namespace').change(() => hideOrShowHelpBlock($form));
+});
diff --git a/app/assets/javascripts/behaviors/toggler_behavior.js b/app/assets/javascripts/behaviors/toggler_behavior.js
index 576b8a0425f..4c9ad128e6c 100644
--- a/app/assets/javascripts/behaviors/toggler_behavior.js
+++ b/app/assets/javascripts/behaviors/toggler_behavior.js
@@ -1,44 +1,43 @@
-/* eslint-disable wrap-iife, func-names, space-before-function-paren, prefer-arrow-callback, vars-on-top, no-var, max-len */
-(function(w) {
- $(function() {
- var toggleContainer = function(container, /* optional */toggleState) {
- var $container = $(container);
-
- $container
- .find('.js-toggle-button .fa')
- .toggleClass('fa-chevron-up', toggleState)
- .toggleClass('fa-chevron-down', toggleState !== undefined ? !toggleState : undefined);
-
- $container
- .find('.js-toggle-content')
- .toggle(toggleState);
- };
-
- // Toggle button. Show/hide content inside parent container.
- // Button does not change visibility. If button has icon - it changes chevron style.
- //
- // %div.js-toggle-container
- // %button.js-toggle-button
- // %div.js-toggle-content
- //
- $('body').on('click', '.js-toggle-button', function(e) {
- toggleContainer($(this).closest('.js-toggle-container'));
-
- const targetTag = e.currentTarget.tagName.toLowerCase();
- if (targetTag === 'a' || targetTag === 'button') {
- e.preventDefault();
- }
- });
-
- // If we're accessing a permalink, ensure it is not inside a
- // closed js-toggle-container!
- var hash = w.gl.utils.getLocationHash();
- var anchor = hash && document.getElementById(hash);
- var container = anchor && $(anchor).closest('.js-toggle-container');
-
- if (container) {
- toggleContainer(container, true);
- anchor.scrollIntoView();
+
+// Toggle button. Show/hide content inside parent container.
+// Button does not change visibility. If button has icon - it changes chevron style.
+//
+// %div.js-toggle-container
+// %button.js-toggle-button
+// %div.js-toggle-content
+//
+
+$(() => {
+ function toggleContainer(container, toggleState) {
+ const $container = $(container);
+
+ $container
+ .find('.js-toggle-button .fa')
+ .toggleClass('fa-chevron-up', toggleState)
+ .toggleClass('fa-chevron-down', toggleState !== undefined ? !toggleState : undefined);
+
+ $container
+ .find('.js-toggle-content')
+ .toggle(toggleState);
+ }
+
+ $('body').on('click', '.js-toggle-button', function toggleButton(e) {
+ toggleContainer($(this).closest('.js-toggle-container'));
+
+ const targetTag = e.currentTarget.tagName.toLowerCase();
+ if (targetTag === 'a' || targetTag === 'button') {
+ e.preventDefault();
}
});
-})(window);
+
+ // If we're accessing a permalink, ensure it is not inside a
+ // closed js-toggle-container!
+ const hash = window.gl.utils.getLocationHash();
+ const anchor = hash && document.getElementById(hash);
+ const container = anchor && $(anchor).closest('.js-toggle-container');
+
+ if (container) {
+ toggleContainer(container, true);
+ anchor.scrollIntoView();
+ }
+});
diff --git a/app/assets/javascripts/dispatcher.js b/app/assets/javascripts/dispatcher.js
index 4e68f9c77e9..f277e1dddc7 100644
--- a/app/assets/javascripts/dispatcher.js
+++ b/app/assets/javascripts/dispatcher.js
@@ -37,6 +37,7 @@
import Issue from './issue';
import BindInOut from './behaviors/bind_in_out';
+import Group from './group';
import GroupName from './group_name';
import GroupsList from './groups_list';
import ProjectsList from './projects_list';
@@ -271,8 +272,9 @@ const ShortcutsBlob = require('./shortcuts_blob');
case 'groups:create':
case 'admin:groups:create':
BindInOut.initAll();
- case 'groups:new':
- case 'admin:groups:new':
+ new Group();
+ new GroupAvatar();
+ break;
case 'groups:edit':
case 'admin:groups:edit':
new GroupAvatar();
diff --git a/app/assets/javascripts/group.js b/app/assets/javascripts/group.js
new file mode 100644
index 00000000000..7732edde1e7
--- /dev/null
+++ b/app/assets/javascripts/group.js
@@ -0,0 +1,21 @@
+export default class Group {
+ constructor() {
+ this.groupPath = $('#group_path');
+ this.groupName = $('#group_name');
+ this.updateHandler = this.update.bind(this);
+ this.resetHandler = this.reset.bind(this);
+ if (this.groupName.val() === '') {
+ this.groupPath.on('keyup', this.updateHandler);
+ this.groupName.on('keydown', this.resetHandler);
+ }
+ }
+
+ update() {
+ this.groupName.val(this.groupPath.val());
+ }
+
+ reset() {
+ this.groupPath.off('keyup', this.updateHandler);
+ this.groupName.off('keydown', this.resetHandler);
+ }
+}
diff --git a/app/assets/javascripts/main.js b/app/assets/javascripts/main.js
index 3f92d4ee6cf..c50ec24c818 100644
--- a/app/assets/javascripts/main.js
+++ b/app/assets/javascripts/main.js
@@ -37,14 +37,7 @@ import './shortcuts_issuable';
import './shortcuts_network';
// behaviors
-import './behaviors/autosize';
-import './behaviors/details_behavior';
-import './behaviors/quick_submit';
-import './behaviors/requires_input';
-import './behaviors/toggler_behavior';
-import './behaviors/bind_in_out';
-import { installGlEmojiElement } from './behaviors/gl_emoji';
-installGlEmojiElement();
+import './behaviors/';
// blob
import './blob/create_branch_dropdown';
diff --git a/app/controllers/admin/application_controller.rb b/app/controllers/admin/application_controller.rb
index cf795d977ce..a4648b33cfa 100644
--- a/app/controllers/admin/application_controller.rb
+++ b/app/controllers/admin/application_controller.rb
@@ -6,6 +6,6 @@ class Admin::ApplicationController < ApplicationController
layout 'admin'
def authenticate_admin!
- render_404 unless current_user.is_admin?
+ render_404 unless current_user.admin?
end
end
diff --git a/app/controllers/admin/impersonations_controller.rb b/app/controllers/admin/impersonations_controller.rb
index 9433da02f64..8e7adc06584 100644
--- a/app/controllers/admin/impersonations_controller.rb
+++ b/app/controllers/admin/impersonations_controller.rb
@@ -21,6 +21,6 @@ class Admin::ImpersonationsController < Admin::ApplicationController
end
def authenticate_impersonator!
- render_404 unless impersonator && impersonator.is_admin? && !impersonator.blocked?
+ render_404 unless impersonator && impersonator.admin? && !impersonator.blocked?
end
end
diff --git a/app/helpers/visibility_level_helper.rb b/app/helpers/visibility_level_helper.rb
index 169cedeb796..b4aaf498068 100644
--- a/app/helpers/visibility_level_helper.rb
+++ b/app/helpers/visibility_level_helper.rb
@@ -85,7 +85,7 @@ module VisibilityLevelHelper
end
def restricted_visibility_levels(show_all = false)
- return [] if current_user.is_admin? && !show_all
+ return [] if current_user.admin? && !show_all
current_application_settings.restricted_visibility_levels || []
end
diff --git a/app/models/user.rb b/app/models/user.rb
index 87eeee204f8..31e975b8e53 100644
--- a/app/models/user.rb
+++ b/app/models/user.rb
@@ -555,10 +555,6 @@ class User < ActiveRecord::Base
authorized_projects(Gitlab::Access::REPORTER).non_archived.with_issues_enabled
end
- def is_admin?
- admin
- end
-
def require_ssh_key?
keys.count == 0 && Gitlab::ProtocolAccess.allowed?('ssh')
end
diff --git a/app/policies/ci/runner_policy.rb b/app/policies/ci/runner_policy.rb
index 7edd383530d..416d93ffe63 100644
--- a/app/policies/ci/runner_policy.rb
+++ b/app/policies/ci/runner_policy.rb
@@ -3,7 +3,7 @@ module Ci
def rules
return unless @user
- can! :assign_runner if @user.is_admin?
+ can! :assign_runner if @user.admin?
return if @subject.is_shared? || @subject.locked?
diff --git a/app/services/users/create_service.rb b/app/services/users/create_service.rb
index a847a71a66a..93ca7b1141a 100644
--- a/app/services/users/create_service.rb
+++ b/app/services/users/create_service.rb
@@ -11,7 +11,7 @@ module Users
user = User.new(build_user_params)
- if current_user&.is_admin?
+ if current_user&.admin?
if params[:reset_password]
@reset_token = user.generate_reset_token
params[:force_random_password] = true
@@ -47,7 +47,7 @@ module Users
private
def can_create_user?
- (current_user.nil? && current_application_settings.signup_enabled?) || current_user&.is_admin?
+ (current_user.nil? && current_application_settings.signup_enabled?) || current_user&.admin?
end
# Allowed params for creating a user (admins only)
@@ -94,7 +94,7 @@ module Users
end
def build_user_params
- if current_user&.is_admin?
+ if current_user&.admin?
user_params = params.slice(*admin_create_params)
user_params[:created_by_id] = current_user&.id
diff --git a/app/uploaders/file_uploader.rb b/app/uploaders/file_uploader.rb
index d6ccf0dc92c..d2783ce5b2f 100644
--- a/app/uploaders/file_uploader.rb
+++ b/app/uploaders/file_uploader.rb
@@ -38,10 +38,6 @@ class FileUploader < GitlabUploader
File.join(dynamic_path_segment, @secret)
end
- def cache_dir
- File.join(base_dir, 'tmp', @project.path_with_namespace, @secret)
- end
-
def model
project
end
diff --git a/app/views/layouts/header/_default.html.haml b/app/views/layouts/header/_default.html.haml
index 17e553aeef0..a9893dea68f 100644
--- a/app/views/layouts/header/_default.html.haml
+++ b/app/views/layouts/header/_default.html.haml
@@ -31,7 +31,7 @@
%li.impersonation
= link_to admin_impersonation_path, method: :delete, title: "Stop impersonation", aria: { label: 'Stop impersonation' }, data: { toggle: 'tooltip', placement: 'bottom', container: 'body' } do
= icon('user-secret fw')
- - if current_user.is_admin?
+ - if current_user.admin?
%li
= link_to admin_root_path, title: 'Admin area', aria: { label: "Admin area" }, data: {toggle: 'tooltip', placement: 'bottom', container: 'body'} do
= icon('wrench fw')
diff --git a/app/views/notify/project_was_exported_email.html.haml b/app/views/notify/project_was_exported_email.html.haml
index 76440926a2b..3def26342a1 100644
--- a/app/views/notify/project_was_exported_email.html.haml
+++ b/app/views/notify/project_was_exported_email.html.haml
@@ -2,7 +2,7 @@
Project #{@project.name} was exported successfully.
%p
The project export can be downloaded from:
- = link_to download_export_namespace_project_url(@project.namespace, @project), rel: 'nofollow', download: '', do
+ = link_to download_export_namespace_project_url(@project.namespace, @project), rel: 'nofollow', download: '' do
= @project.name_with_namespace + " export"
%p
The download link will expire in 24 hours.
diff --git a/app/views/shared/_group_form.html.haml b/app/views/shared/_group_form.html.haml
index 8869d510aef..90ae3f06a98 100644
--- a/app/views/shared/_group_form.html.haml
+++ b/app/views/shared/_group_form.html.haml
@@ -1,12 +1,8 @@
+- content_for :page_specific_javascripts do
+ = page_specific_javascript_bundle_tag('group')
- parent = GroupFinder.new(current_user).execute(id: params[:parent_id] || @group.parent_id)
- group_path = root_url
- group_path << parent.full_path + '/' if parent
-- if @group.persisted?
- .form-group
- = f.label :name, class: 'control-label' do
- Group name
- .col-sm-10
- = f.text_field :name, placeholder: 'open-source', class: 'form-control'
.form-group
= f.label :path, class: 'control-label' do
@@ -20,7 +16,7 @@
= f.text_field :path, placeholder: 'open-source', class: 'form-control',
autofocus: local_assigns[:autofocus] || false, required: true,
pattern: Gitlab::Regex::NAMESPACE_REGEX_STR_JS,
- title: 'Please choose a group name with no special characters.',
+ title: 'Please choose a group path with no special characters.',
"data-bind-in" => "#{'create_chat_team' if Gitlab.config.mattermost.enabled}"
- if parent
= f.hidden_field :parent_id, value: parent.id
@@ -33,6 +29,14 @@
%li It will change web url for access group and group projects.
%li It will change the git path to repositories under this group.
+.form-group.group-name-holder
+ = f.label :name, class: 'control-label' do
+ Group name
+ .col-sm-10
+ = f.text_field :name, class: 'form-control',
+ required: true,
+ title: 'You can choose a descriptive name different from the path.'
+
.form-group.group-description-holder
= f.label :description, class: 'control-label'
.col-sm-10
diff --git a/changelogs/unreleased/add-field-for-group-name.yml b/changelogs/unreleased/add-field-for-group-name.yml
new file mode 100644
index 00000000000..0fe511a4fa1
--- /dev/null
+++ b/changelogs/unreleased/add-field-for-group-name.yml
@@ -0,0 +1,4 @@
+---
+title: Add a name field to the group form
+merge_request: 9891
+author: Douglas Lovell
diff --git a/changelogs/unreleased/clean_carrierwave_tempfiles.yml b/changelogs/unreleased/clean_carrierwave_tempfiles.yml
new file mode 100644
index 00000000000..53fa69700ff
--- /dev/null
+++ b/changelogs/unreleased/clean_carrierwave_tempfiles.yml
@@ -0,0 +1,4 @@
+---
+title: Periodically clean up temporary upload files to recover storage space
+merge_request: 9466
+author: blackst0ne
diff --git a/changelogs/unreleased/remove_is_admin.yml b/changelogs/unreleased/remove_is_admin.yml
new file mode 100644
index 00000000000..f6baf1942de
--- /dev/null
+++ b/changelogs/unreleased/remove_is_admin.yml
@@ -0,0 +1,4 @@
+---
+title: Remove the User#is_admin? method
+merge_request: 10520
+author: blackst0ne
diff --git a/changelogs/unreleased/spec_for_schema.yml b/changelogs/unreleased/spec_for_schema.yml
new file mode 100644
index 00000000000..7ea0b8672ce
--- /dev/null
+++ b/changelogs/unreleased/spec_for_schema.yml
@@ -0,0 +1,4 @@
+---
+title: Add spec for schema.rb
+merge_request: 10580
+author: blackst0ne
diff --git a/config/webpack.config.js b/config/webpack.config.js
index 987344f4eaa..e3bc939d578 100644
--- a/config/webpack.config.js
+++ b/config/webpack.config.js
@@ -49,6 +49,7 @@ var config = {
users: './users/users_bundle.js',
vue_pipelines: './vue_pipelines_index/index.js',
issue_show: './issue_show/index.js',
+ group: './group.js',
},
output: {
diff --git a/db/post_migrate/20170408033905_remove_old_cache_directories.rb b/db/post_migrate/20170408033905_remove_old_cache_directories.rb
new file mode 100644
index 00000000000..b23b52896b9
--- /dev/null
+++ b/db/post_migrate/20170408033905_remove_old_cache_directories.rb
@@ -0,0 +1,23 @@
+# See http://doc.gitlab.com/ce/development/migration_style_guide.html
+# for more information on how to write migrations for GitLab.
+
+# Remove all files from old custom carrierwave's cache directories.
+# See https://gitlab.com/gitlab-org/gitlab-ce/merge_requests/9466
+
+class RemoveOldCacheDirectories < ActiveRecord::Migration
+ include Gitlab::Database::MigrationHelpers
+
+ DOWNTIME = false
+
+ disable_ddl_transaction!
+
+ def up
+ # FileUploader cache.
+ FileUtils.rm_rf(Dir[Rails.root.join('public', 'uploads', 'tmp', '*')])
+ end
+
+ def down
+ # Old cache is not supposed to be recoverable.
+ # So the down method is empty.
+ end
+end
diff --git a/db/schema.rb b/db/schema.rb
index d400c823387..3422847d729 100644
--- a/db/schema.rb
+++ b/db/schema.rb
@@ -11,7 +11,7 @@
#
# It's strongly recommended that you check this file into your version control system.
-ActiveRecord::Schema.define(version: 20170407140450) do
+ActiveRecord::Schema.define(version: 20170408033905) do
# These are extensions that must be enabled in order to support this database
enable_extension "plpgsql"
enable_extension "pg_trgm"
diff --git a/doc/gitlab-basics/create-group.md b/doc/gitlab-basics/create-group.md
index 64274ccd5eb..b4889bb8818 100644
--- a/doc/gitlab-basics/create-group.md
+++ b/doc/gitlab-basics/create-group.md
@@ -25,6 +25,8 @@ To create a group:
1. Set the "Group path" which will be the namespace under which your projects
will be hosted (path can contain only letters, digits, underscores, dashes
and dots; it cannot start with dashes or end in dot).
+ 1. The "Group name" will populate with the path. Optionally, you can change
+ it. This is the name that will display in the group views.
1. Optionally, you can add a description so that others can briefly understand
what this group is about.
1. Optionally, choose and avatar for your project.
diff --git a/doc/gitlab-basics/img/create_new_group_info.png b/doc/gitlab-basics/img/create_new_group_info.png
index 020b4ac00d6..8d2501d9f7a 100644
--- a/doc/gitlab-basics/img/create_new_group_info.png
+++ b/doc/gitlab-basics/img/create_new_group_info.png
Binary files differ
diff --git a/doc/workflow/groups.md b/doc/workflow/groups.md
index 6237a5d5e18..882747e14e9 100644
--- a/doc/workflow/groups.md
+++ b/doc/workflow/groups.md
@@ -11,9 +11,9 @@ You can create a group by going to the 'Groups' tab of the GitLab dashboard and
![Click the 'New group' button in the 'Groups' tab](groups/new_group_button.png)
-Next, enter the name (required) and the optional description and group avatar.
+Next, enter the path and name (required) and the optional description and group avatar.
-![Fill in the name for your new group](groups/new_group_form.png)
+![Fill in the path for your new group](groups/new_group_form.png)
When your group has been created you are presented with the group dashboard feed, which will be empty.
diff --git a/doc/workflow/groups/new_group_form.png b/doc/workflow/groups/new_group_form.png
index 0d798cd4b84..91727ab5336 100644
--- a/doc/workflow/groups/new_group_form.png
+++ b/doc/workflow/groups/new_group_form.png
Binary files differ
diff --git a/lib/api/entities.rb b/lib/api/entities.rb
index 45625e00f7d..9919762cd82 100644
--- a/lib/api/entities.rb
+++ b/lib/api/entities.rb
@@ -14,7 +14,7 @@ module API
class User < UserBasic
expose :created_at
- expose :is_admin?, as: :is_admin
+ expose :admin?, as: :is_admin
expose :bio, :location, :skype, :linkedin, :twitter, :website_url, :organization
end
@@ -611,9 +611,9 @@ module API
expose :locked
expose :version, :revision, :platform, :architecture
expose :contacted_at
- expose :token, if: lambda { |runner, options| options[:current_user].is_admin? || !runner.is_shared? }
+ expose :token, if: lambda { |runner, options| options[:current_user].admin? || !runner.is_shared? }
expose :projects, with: Entities::BasicProjectDetails do |runner, options|
- if options[:current_user].is_admin?
+ if options[:current_user].admin?
runner.projects
else
options[:current_user].authorized_projects.where(id: runner.projects)
diff --git a/lib/api/groups.rb b/lib/api/groups.rb
index 605769eddde..32bbf956d7f 100644
--- a/lib/api/groups.rb
+++ b/lib/api/groups.rb
@@ -56,7 +56,7 @@ module API
groups = groups.where.not(id: params[:skip_groups]) if params[:skip_groups].present?
groups = groups.reorder(params[:order_by] => params[:sort])
- present_groups groups, statistics: params[:statistics] && current_user.is_admin?
+ present_groups groups, statistics: params[:statistics] && current_user.admin?
end
desc 'Create a group. Available only for users who can create groups.' do
diff --git a/lib/api/helpers.rb b/lib/api/helpers.rb
index 61527c1e20b..ddff3c8c1e8 100644
--- a/lib/api/helpers.rb
+++ b/lib/api/helpers.rb
@@ -118,7 +118,7 @@ module API
def authenticated_as_admin!
authenticate!
- forbidden! unless current_user.is_admin?
+ forbidden! unless current_user.admin?
end
def authorize!(action, subject = :global)
@@ -358,7 +358,7 @@ module API
return unless sudo_identifier
return unless initial_current_user
- unless initial_current_user.is_admin?
+ unless initial_current_user.admin?
forbidden!('Must be admin to use sudo')
end
diff --git a/lib/api/notes.rb b/lib/api/notes.rb
index de39e579ac3..e281e3230fd 100644
--- a/lib/api/notes.rb
+++ b/lib/api/notes.rb
@@ -78,7 +78,7 @@ module API
}
if can?(current_user, noteable_read_ability_name(noteable), noteable)
- if params[:created_at] && (current_user.is_admin? || user_project.owner == current_user)
+ if params[:created_at] && (current_user.admin? || user_project.owner == current_user)
opts[:created_at] = params[:created_at]
end
diff --git a/lib/api/runners.rb b/lib/api/runners.rb
index a77c876a749..db6c7c59092 100644
--- a/lib/api/runners.rb
+++ b/lib/api/runners.rb
@@ -161,18 +161,18 @@ module API
end
def authenticate_show_runner!(runner)
- return if runner.is_shared || current_user.is_admin?
+ return if runner.is_shared || current_user.admin?
forbidden!("No access granted") unless user_can_access_runner?(runner)
end
def authenticate_update_runner!(runner)
- return if current_user.is_admin?
+ return if current_user.admin?
forbidden!("Runner is shared") if runner.is_shared?
forbidden!("No access granted") unless user_can_access_runner?(runner)
end
def authenticate_delete_runner!(runner)
- return if current_user.is_admin?
+ return if current_user.admin?
forbidden!("Runner is shared") if runner.is_shared?
forbidden!("Runner associated with more than one project") if runner.projects.count > 1
forbidden!("No access granted") unless user_can_access_runner?(runner)
@@ -181,7 +181,7 @@ module API
def authenticate_enable_runner!(runner)
forbidden!("Runner is shared") if runner.is_shared?
forbidden!("Runner is locked") if runner.locked?
- return if current_user.is_admin?
+ return if current_user.admin?
forbidden!("No access granted") unless user_can_access_runner?(runner)
end
diff --git a/lib/api/services.rb b/lib/api/services.rb
index 65f86caaa51..23ef62c2258 100644
--- a/lib/api/services.rb
+++ b/lib/api/services.rb
@@ -642,7 +642,7 @@ module API
service_params = declared_params(include_missing: false).merge(active: true)
if service.update_attributes(service_params)
- present service, with: Entities::ProjectService, include_passwords: current_user.is_admin?
+ present service, with: Entities::ProjectService, include_passwords: current_user.admin?
else
render_api_error!('400 Bad Request', 400)
end
@@ -673,7 +673,7 @@ module API
end
get ":id/services/:service_slug" do
service = user_project.find_or_initialize_service(params[:service_slug].underscore)
- present service, with: Entities::ProjectService, include_passwords: current_user.is_admin?
+ present service, with: Entities::ProjectService, include_passwords: current_user.admin?
end
end
diff --git a/lib/api/users.rb b/lib/api/users.rb
index 992a751b37d..6f40f92240a 100644
--- a/lib/api/users.rb
+++ b/lib/api/users.rb
@@ -56,10 +56,10 @@ module API
users = users.active if params[:active]
users = users.search(params[:search]) if params[:search].present?
users = users.blocked if params[:blocked]
- users = users.external if params[:external] && current_user.is_admin?
+ users = users.external if params[:external] && current_user.admin?
end
- entity = current_user.is_admin? ? Entities::UserPublic : Entities::UserBasic
+ entity = current_user.admin? ? Entities::UserPublic : Entities::UserBasic
present paginate(users), with: entity
end
@@ -73,7 +73,7 @@ module API
user = User.find_by(id: params[:id])
not_found!('User') unless user
- if current_user && current_user.is_admin?
+ if current_user && current_user.admin?
present user, with: Entities::UserPublic
elsif can?(current_user, :read_user, user)
present user, with: Entities::User
diff --git a/lib/api/v3/groups.rb b/lib/api/v3/groups.rb
index 9b27411ae21..63d464b926b 100644
--- a/lib/api/v3/groups.rb
+++ b/lib/api/v3/groups.rb
@@ -54,7 +54,7 @@ module API
groups = groups.where.not(id: params[:skip_groups]) if params[:skip_groups].present?
groups = groups.reorder(params[:order_by] => params[:sort])
- present_groups groups, statistics: params[:statistics] && current_user.is_admin?
+ present_groups groups, statistics: params[:statistics] && current_user.admin?
end
desc 'Get list of owned groups for authenticated user' do
diff --git a/lib/api/v3/notes.rb b/lib/api/v3/notes.rb
index 4f8e0eff4ff..009ec5c6bbd 100644
--- a/lib/api/v3/notes.rb
+++ b/lib/api/v3/notes.rb
@@ -79,7 +79,7 @@ module API
noteable = user_project.send(noteables_str.to_sym).find(params[:noteable_id])
if can?(current_user, noteable_read_ability_name(noteable), noteable)
- if params[:created_at] && (current_user.is_admin? || user_project.owner == current_user)
+ if params[:created_at] && (current_user.admin? || user_project.owner == current_user)
opts[:created_at] = params[:created_at]
end
diff --git a/lib/api/v3/runners.rb b/lib/api/v3/runners.rb
index 1934d6e578c..faa265f3314 100644
--- a/lib/api/v3/runners.rb
+++ b/lib/api/v3/runners.rb
@@ -50,7 +50,7 @@ module API
helpers do
def authenticate_delete_runner!(runner)
- return if current_user.is_admin?
+ return if current_user.admin?
forbidden!("Runner is shared") if runner.is_shared?
forbidden!("Runner associated with more than one project") if runner.projects.count > 1
forbidden!("No access granted") unless user_can_access_runner?(runner)
diff --git a/lib/api/v3/services.rb b/lib/api/v3/services.rb
index bbe07ed4212..61629a04174 100644
--- a/lib/api/v3/services.rb
+++ b/lib/api/v3/services.rb
@@ -602,7 +602,7 @@ module API
end
get ":id/services/:service_slug" do
service = user_project.find_or_initialize_service(params[:service_slug].underscore)
- present service, with: Entities::ProjectService, include_passwords: current_user.is_admin?
+ present service, with: Entities::ProjectService, include_passwords: current_user.admin?
end
end
diff --git a/lib/gitlab/etag_caching/middleware.rb b/lib/gitlab/etag_caching/middleware.rb
index 11167632e07..270d67dd50c 100644
--- a/lib/gitlab/etag_caching/middleware.rb
+++ b/lib/gitlab/etag_caching/middleware.rb
@@ -1,40 +1,12 @@
module Gitlab
module EtagCaching
class Middleware
- RESERVED_WORDS = NamespaceValidator::WILDCARD_ROUTES.map { |word| "/#{word}/" }.join('|')
- ROUTES = [
- {
- regexp: %r(^(?!.*(#{RESERVED_WORDS})).*/noteable/issue/\d+/notes\z),
- name: 'issue_notes'
- },
- {
- regexp: %r(^(?!.*(#{RESERVED_WORDS})).*/issues/\d+/rendered_title\z),
- name: 'issue_title'
- },
- {
- regexp: %r(^(?!.*(#{RESERVED_WORDS})).*/pipelines\.json\z),
- name: 'project_pipelines'
- },
- {
- regexp: %r(^(?!.*(#{RESERVED_WORDS})).*/commit/\s+/pipelines\.json\z),
- name: 'commit_pipelines'
- },
- {
- regexp: %r(^(?!.*(#{RESERVED_WORDS})).*/merge_requests/new\.json\z),
- name: 'new_merge_request_pipelines'
- },
- {
- regexp: %r(^(?!.*(#{RESERVED_WORDS})).*/merge_requests/\d+/pipelines\.json\z),
- name: 'merge_request_pipelines'
- }
- ].freeze
-
def initialize(app)
@app = app
end
def call(env)
- route = match_current_route(env)
+ route = Gitlab::EtagCaching::Router.match(env)
return @app.call(env) unless route
track_event(:etag_caching_middleware_used, route)
@@ -55,10 +27,6 @@ module Gitlab
private
- def match_current_route(env)
- ROUTES.find { |route| route[:regexp].match(env['PATH_INFO']) }
- end
-
def get_etag(env)
cache_key = env['PATH_INFO']
store = Gitlab::EtagCaching::Store.new
@@ -95,7 +63,7 @@ module Gitlab
end
def track_event(name, route)
- Gitlab::Metrics.add_event(name, endpoint: route[:name])
+ Gitlab::Metrics.add_event(name, endpoint: route.name)
end
end
end
diff --git a/lib/gitlab/etag_caching/router.rb b/lib/gitlab/etag_caching/router.rb
new file mode 100644
index 00000000000..f6e4f279c06
--- /dev/null
+++ b/lib/gitlab/etag_caching/router.rb
@@ -0,0 +1,39 @@
+module Gitlab
+ module EtagCaching
+ class Router
+ Route = Struct.new(:regexp, :name)
+
+ RESERVED_WORDS = NamespaceValidator::WILDCARD_ROUTES.map { |word| "/#{word}/" }.join('|')
+ ROUTES = [
+ Gitlab::EtagCaching::Router::Route.new(
+ %r(^(?!.*(#{RESERVED_WORDS})).*/noteable/issue/\d+/notes\z),
+ 'issue_notes'
+ ),
+ Gitlab::EtagCaching::Router::Route.new(
+ %r(^(?!.*(#{RESERVED_WORDS})).*/issues/\d+/rendered_title\z),
+ 'issue_title'
+ ),
+ Gitlab::EtagCaching::Router::Route.new(
+ %r(^(?!.*(#{RESERVED_WORDS})).*/commit/\S+/pipelines\.json\z),
+ 'commit_pipelines'
+ ),
+ Gitlab::EtagCaching::Router::Route.new(
+ %r(^(?!.*(#{RESERVED_WORDS})).*/merge_requests/new\.json\z),
+ 'new_merge_request_pipelines'
+ ),
+ Gitlab::EtagCaching::Router::Route.new(
+ %r(^(?!.*(#{RESERVED_WORDS})).*/merge_requests/\d+/pipelines\.json\z),
+ 'merge_request_pipelines'
+ ),
+ Gitlab::EtagCaching::Router::Route.new(
+ %r(^(?!.*(#{RESERVED_WORDS})).*/pipelines\.json\z),
+ 'project_pipelines'
+ )
+ ].freeze
+
+ def self.match(env)
+ ROUTES.find { |route| route.regexp.match(env['PATH_INFO']) }
+ end
+ end
+ end
+end
diff --git a/lib/gitlab/import_export/relation_factory.rb b/lib/gitlab/import_export/relation_factory.rb
index 71811be6f50..4a54e7ef2e7 100644
--- a/lib/gitlab/import_export/relation_factory.rb
+++ b/lib/gitlab/import_export/relation_factory.rb
@@ -186,7 +186,7 @@ module Gitlab
end
def admin_user?
- @user.is_admin?
+ @user.admin?
end
def parsed_relation_hash
diff --git a/lib/gitlab/visibility_level.rb b/lib/gitlab/visibility_level.rb
index 8f1d1fdc02e..2e31f4462f9 100644
--- a/lib/gitlab/visibility_level.rb
+++ b/lib/gitlab/visibility_level.rb
@@ -63,7 +63,7 @@ module Gitlab
end
def allowed_for?(user, level)
- user.is_admin? || allowed_level?(level.to_i)
+ user.admin? || allowed_level?(level.to_i)
end
# Return true if the specified level is allowed for the current user.
diff --git a/spec/features/admin/admin_groups_spec.rb b/spec/features/admin/admin_groups_spec.rb
index 56d5c7c327e..d5f595894d6 100644
--- a/spec/features/admin/admin_groups_spec.rb
+++ b/spec/features/admin/admin_groups_spec.rb
@@ -25,13 +25,22 @@ feature 'Admin Groups', feature: true do
visit admin_groups_path
click_link "New group"
- fill_in 'group_path', with: 'gitlab'
- fill_in 'group_description', with: 'Group description'
+ path_component = 'gitlab'
+ group_name = 'GitLab group name'
+ group_description = 'Description of group for GitLab'
+ fill_in 'group_path', with: path_component
+ fill_in 'group_name', with: group_name
+ fill_in 'group_description', with: group_description
click_button "Create group"
- expect(current_path).to eq admin_group_path(Group.find_by(path: 'gitlab'))
- expect(page).to have_content('Group: gitlab')
- expect(page).to have_content('Group description')
+ expect(current_path).to eq admin_group_path(Group.find_by(path: path_component))
+ content = page.find('div#content-body')
+ h3_texts = content.all('h3').collect(&:text).join("\n")
+ expect(h3_texts).to match group_name
+ li_texts = content.all('li').collect(&:text).join("\n")
+ expect(li_texts).to match group_name
+ expect(li_texts).to match path_component
+ expect(li_texts).to match group_description
end
scenario 'shows the visibility level radio populated with the default value' do
@@ -39,6 +48,15 @@ feature 'Admin Groups', feature: true do
expect_selected_visibility(internal)
end
+
+ scenario 'when entered in group path, it auto filled the group name', js: true do
+ visit admin_groups_path
+ click_link "New group"
+ group_path = 'gitlab'
+ fill_in 'group_path', with: group_path
+ name_field = find('input#group_name')
+ expect(name_field.value).to eq group_path
+ end
end
describe 'show a group' do
@@ -59,6 +77,17 @@ feature 'Admin Groups', feature: true do
expect_selected_visibility(group.visibility_level)
end
+
+ scenario 'edit group path does not change group name', js: true do
+ group = create(:group, :private)
+
+ visit admin_group_edit_path(group)
+ name_field = find('input#group_name')
+ original_name = name_field.value
+ fill_in 'group_path', with: 'this-new-path'
+
+ expect(name_field.value).to eq original_name
+ end
end
describe 'add user into a group', js: true do
diff --git a/spec/features/admin/admin_users_spec.rb b/spec/features/admin/admin_users_spec.rb
index c0807b8c507..f6c3bc6a58d 100644
--- a/spec/features/admin/admin_users_spec.rb
+++ b/spec/features/admin/admin_users_spec.rb
@@ -223,7 +223,7 @@ describe "Admin::Users", feature: true do
it "changes user entry" do
user.reload
expect(user.name).to eq('Big Bang')
- expect(user.is_admin?).to be_truthy
+ expect(user.admin?).to be_truthy
expect(user.password_expires_at).to be <= Time.now
end
end
diff --git a/spec/features/dashboard/group_spec.rb b/spec/features/dashboard/group_spec.rb
index 19f5d1b0f30..1d4b86ed4b4 100644
--- a/spec/features/dashboard/group_spec.rb
+++ b/spec/features/dashboard/group_spec.rb
@@ -5,16 +5,18 @@ RSpec.describe 'Dashboard Group', feature: true do
login_as(:user)
end
- it 'creates new grpup' do
+ it 'creates new group', js: true do
visit dashboard_groups_path
click_link 'New group'
+ new_path = 'Samurai'
+ new_description = 'Tokugawa Shogunate'
- fill_in 'group_path', with: 'Samurai'
- fill_in 'group_description', with: 'Tokugawa Shogunate'
+ fill_in 'group_path', with: new_path
+ fill_in 'group_description', with: new_description
click_button 'Create group'
- expect(current_path).to eq group_path(Group.find_by(name: 'Samurai'))
- expect(page).to have_content('Samurai')
- expect(page).to have_content('Tokugawa Shogunate')
+ expect(current_path).to eq group_path(Group.find_by(name: new_path))
+ expect(page).to have_content(new_path)
+ expect(page).to have_content(new_description)
end
end
diff --git a/spec/features/groups_spec.rb b/spec/features/groups_spec.rb
index 7670c4caea4..3d32c47bf09 100644
--- a/spec/features/groups_spec.rb
+++ b/spec/features/groups_spec.rb
@@ -83,7 +83,7 @@ feature 'Group', feature: true do
end
end
- describe 'create a nested group' do
+ describe 'create a nested group', js: true do
let(:group) { create(:group, path: 'foo') }
context 'as admin' do
diff --git a/spec/lib/gitlab/etag_caching/router_spec.rb b/spec/lib/gitlab/etag_caching/router_spec.rb
new file mode 100644
index 00000000000..f3dacb4ef04
--- /dev/null
+++ b/spec/lib/gitlab/etag_caching/router_spec.rb
@@ -0,0 +1,83 @@
+require 'spec_helper'
+
+describe Gitlab::EtagCaching::Router do
+ it 'matches issue notes endpoint' do
+ env = build_env(
+ '/my-group/and-subgroup/here-comes-the-project/noteable/issue/1/notes'
+ )
+
+ result = described_class.match(env)
+
+ expect(result).to be_present
+ expect(result.name).to eq 'issue_notes'
+ end
+
+ it 'matches issue title endpoint' do
+ env = build_env(
+ '/my-group/my-project/issues/123/rendered_title'
+ )
+
+ result = described_class.match(env)
+
+ expect(result).to be_present
+ expect(result.name).to eq 'issue_title'
+ end
+
+ it 'matches project pipelines endpoint' do
+ env = build_env(
+ '/my-group/my-project/pipelines.json'
+ )
+
+ result = described_class.match(env)
+
+ expect(result).to be_present
+ expect(result.name).to eq 'project_pipelines'
+ end
+
+ it 'matches commit pipelines endpoint' do
+ env = build_env(
+ '/my-group/my-project/commit/aa8260d253a53f73f6c26c734c72fdd600f6e6d4/pipelines.json'
+ )
+
+ result = described_class.match(env)
+
+ expect(result).to be_present
+ expect(result.name).to eq 'commit_pipelines'
+ end
+
+ it 'matches new merge request pipelines endpoint' do
+ env = build_env(
+ '/my-group/my-project/merge_requests/new.json'
+ )
+
+ result = described_class.match(env)
+
+ expect(result).to be_present
+ expect(result.name).to eq 'new_merge_request_pipelines'
+ end
+
+ it 'matches merge request pipelines endpoint' do
+ env = build_env(
+ '/my-group/my-project/merge_requests/234/pipelines.json'
+ )
+
+ result = described_class.match(env)
+
+ expect(result).to be_present
+ expect(result.name).to eq 'merge_request_pipelines'
+ end
+
+ it 'does not match blob with confusing name' do
+ env = build_env(
+ '/my-group/my-project/blob/master/pipelines.json'
+ )
+
+ result = described_class.match(env)
+
+ expect(result).to be_blank
+ end
+
+ def build_env(path)
+ { 'PATH_INFO' => path }
+ end
+end
diff --git a/spec/migrations/schema_spec.rb b/spec/migrations/schema_spec.rb
new file mode 100644
index 00000000000..e132529d8d8
--- /dev/null
+++ b/spec/migrations/schema_spec.rb
@@ -0,0 +1,23 @@
+require 'spec_helper'
+
+# Check consistency of db/schema.rb version, migrations' timestamps, and the latest migration timestamp
+# stored in the database's schema_migrations table.
+
+describe ActiveRecord::Schema do
+ let(:latest_migration_timestamp) do
+ migrations = Dir[Rails.root.join('db', 'migrate', '*'), Rails.root.join('db', 'post_migrate', '*')]
+ migrations.map { |migration| File.basename(migration).split('_').first.to_i }.max
+ end
+
+ it '> schema version equals last migration timestamp' do
+ defined_schema_version = File.open(Rails.root.join('db', 'schema.rb')) do |file|
+ file.find { |line| line =~ /ActiveRecord::Schema.define/ }
+ end.match(/(\d+)/)[0].to_i
+
+ expect(defined_schema_version).to eq(latest_migration_timestamp)
+ end
+
+ it '> schema version should equal the latest migration timestamp stored in schema_migrations table' do
+ expect(latest_migration_timestamp).to eq(ActiveRecord::Migrator.current_version.to_i)
+ end
+end
diff --git a/spec/models/user_spec.rb b/spec/models/user_spec.rb
index 6f7b9c2388a..9de16c41e94 100644
--- a/spec/models/user_spec.rb
+++ b/spec/models/user_spec.rb
@@ -315,7 +315,7 @@ describe User, models: true do
end
describe "Respond to" do
- it { is_expected.to respond_to(:is_admin?) }
+ it { is_expected.to respond_to(:admin?) }
it { is_expected.to respond_to(:name) }
it { is_expected.to respond_to(:private_token) }
it { is_expected.to respond_to(:external?) }
@@ -586,7 +586,7 @@ describe User, models: true do
describe 'normal user' do
let(:user) { create(:user, name: 'John Smith') }
- it { expect(user.is_admin?).to be_falsey }
+ it { expect(user.admin?).to be_falsey }
it { expect(user.require_ssh_key?).to be_truthy }
it { expect(user.can_create_group?).to be_truthy }
it { expect(user.can_create_project?).to be_truthy }
diff --git a/spec/requests/api/session_spec.rb b/spec/requests/api/session_spec.rb
index 28fab2011a5..393bf076616 100644
--- a/spec/requests/api/session_spec.rb
+++ b/spec/requests/api/session_spec.rb
@@ -13,7 +13,7 @@ describe API::Session, api: true do
expect(json_response['email']).to eq(user.email)
expect(json_response['private_token']).to eq(user.private_token)
- expect(json_response['is_admin']).to eq(user.is_admin?)
+ expect(json_response['is_admin']).to eq(user.admin?)
expect(json_response['can_create_project']).to eq(user.can_create_project?)
expect(json_response['can_create_group']).to eq(user.can_create_group?)
end
@@ -37,7 +37,7 @@ describe API::Session, api: true do
expect(json_response['email']).to eq user.email
expect(json_response['private_token']).to eq user.private_token
- expect(json_response['is_admin']).to eq user.is_admin?
+ expect(json_response['is_admin']).to eq user.admin?
expect(json_response['can_create_project']).to eq user.can_create_project?
expect(json_response['can_create_group']).to eq user.can_create_group?
end
@@ -50,7 +50,7 @@ describe API::Session, api: true do
expect(json_response['email']).to eq user.email
expect(json_response['private_token']).to eq user.private_token
- expect(json_response['is_admin']).to eq user.is_admin?
+ expect(json_response['is_admin']).to eq user.admin?
expect(json_response['can_create_project']).to eq user.can_create_project?
expect(json_response['can_create_group']).to eq user.can_create_group?
end
diff --git a/spec/services/notification_service_spec.rb b/spec/services/notification_service_spec.rb
index 617c8eaf3d5..989fd90cda9 100644
--- a/spec/services/notification_service_spec.rb
+++ b/spec/services/notification_service_spec.rb
@@ -1181,6 +1181,22 @@ describe NotificationService, services: true do
should_not_email(@u_disabled)
end
end
+
+ describe '#project_exported' do
+ it do
+ notification.project_exported(project, @u_disabled)
+
+ should_only_email(@u_disabled)
+ end
+ end
+
+ describe '#project_not_exported' do
+ it do
+ notification.project_not_exported(project, @u_disabled, ['error'])
+
+ should_only_email(@u_disabled)
+ end
+ end
end
describe 'GroupMember' do