Commit cca92420 authored by Grzegorz Bizon's avatar Grzegorz Bizon

Merge commit 'a8a4ca17' into 32815--Add-Custom-CI-Config-Path

* commit 'a8a4ca17':
  Remove IIFEs around several javascript classes
  Handles realtime with 2 states for environments table
  Revert "Merge branch '18000-remember-me-for-oauth-login' into 'master'"
  Disable Flipper memoizer in tests to avoid transient failures
  fix sidebar padding for full-width items (Time Tracking help)
  Replace 'snippets/snippets.feature' spinach with rspec
  32838 Add wells to admin dashboard overview to fix spacing problems
parents 1a581a6a a8a4ca17
......@@ -56,7 +56,6 @@ import GfmAutoComplete from './gfm_auto_complete';
import ShortcutsBlob from './shortcuts_blob';
import initSettingsPanels from './settings_panels';
import initExperimentalFlags from './experimental_flags';
import OAuthRememberMe from './oauth_remember_me';
(function() {
var Dispatcher;
......@@ -128,7 +127,6 @@ import OAuthRememberMe from './oauth_remember_me';
case 'sessions:new':
new UsernameValidator();
new ActiveTabMemoizer();
new OAuthRememberMe({ container: $(".omniauth-container") }).bindEvents();
break;
case 'projects:boards:show':
case 'projects:boards:index':
......
......@@ -32,7 +32,6 @@ export default {
state: store.state,
visibility: 'available',
isLoading: false,
isLoadingFolderContent: false,
cssContainerClass: environmentsData.cssClass,
endpoint: environmentsData.environmentsDataEndpoint,
canCreateDeployment: environmentsData.canCreateDeployment,
......@@ -86,9 +85,6 @@ export default {
errorCallback: this.errorCallback,
notificationCallback: (isMakingRequest) => {
this.isMakingRequest = isMakingRequest;
// We need to verify if any folder is open to also fecth it
this.openFolders = this.store.getOpenFolders();
},
});
......@@ -119,7 +115,7 @@ export default {
this.store.toggleFolder(folder);
if (!folder.isOpen) {
this.fetchChildEnvironments(folder, folderUrl);
this.fetchChildEnvironments(folder, folderUrl, true);
}
},
......@@ -147,19 +143,17 @@ export default {
.catch(this.errorCallback);
},
fetchChildEnvironments(folder, folderUrl) {
this.isLoadingFolderContent = true;
fetchChildEnvironments(folder, folderUrl, showLoader = false) {
this.store.updateEnvironmentProp(folder, 'isLoadingFolderContent', showLoader);
this.service.getFolderContent(folderUrl)
.then(resp => resp.json())
.then((response) => {
this.store.setfolderContent(folder, response.environments);
this.isLoadingFolderContent = false;
})
.then(response => this.store.setfolderContent(folder, response.environments))
.then(() => this.store.updateEnvironmentProp(folder, 'isLoadingFolderContent', false))
.catch(() => {
this.isLoadingFolderContent = false;
// eslint-disable-next-line no-new
new Flash('An error occurred while fetching the environments.');
this.store.updateEnvironmentProp(folder, 'isLoadingFolderContent', false);
});
},
......@@ -176,13 +170,13 @@ export default {
successCallback(resp) {
this.saveData(resp);
// If folders are open while polling we need to open them again
if (this.openFolders.length) {
this.openFolders.map((folder) => {
// We need to verify if any folder is open to also update it
const openFolders = this.store.getOpenFolders();
if (openFolders.length) {
openFolders.forEach((folder) => {
// TODO - Move this to the backend
const folderUrl = `${window.location.pathname}/folders/${folder.folderName}`;
this.store.updateFolder(folder, 'isOpen', true);
return this.fetchChildEnvironments(folder, folderUrl);
});
}
......@@ -267,7 +261,7 @@ export default {
:environments="state.environments"
:can-create-deployment="canCreateDeploymentParsed"
:can-read-environment="canReadEnvironmentParsed"
:is-loading-folder-content="isLoadingFolderContent" />
/>
</div>
<table-pagination
......
......@@ -29,12 +29,6 @@ export default {
required: false,
default: false,
},
isLoadingFolderContent: {
type: Boolean,
required: false,
default: false,
},
},
methods: {
......@@ -74,7 +68,7 @@ export default {
/>
<template v-if="model.isFolder && model.isOpen && model.children && model.children.length > 0">
<div v-if="isLoadingFolderContent">
<div v-if="model.isLoadingFolderContent">
<loading-icon size="2" />
</div>
......
......@@ -35,14 +35,18 @@ export default class EnvironmentsStore {
*/
storeEnvironments(environments = []) {
const filteredEnvironments = environments.map((env) => {
const oldEnvironmentState = this.state.environments
.find(element => element.id === env.latest.id) || {};
let filtered = {};
if (env.size > 1) {
filtered = Object.assign({}, env, {
isFolder: true,
isLoadingFolderContent: oldEnvironmentState.isLoading || false,
folderName: env.name,
isOpen: false,
children: [],
isOpen: oldEnvironmentState.isOpen || false,
children: oldEnvironmentState.children || [],
});
}
......@@ -98,7 +102,7 @@ export default class EnvironmentsStore {
* @return {Array}
*/
toggleFolder(folder) {
return this.updateFolder(folder, 'isOpen', !folder.isOpen);
return this.updateEnvironmentProp(folder, 'isOpen', !folder.isOpen);
}
/**
......@@ -125,23 +129,23 @@ export default class EnvironmentsStore {
return updated;
});
return this.updateFolder(folder, 'children', updatedEnvironments);
return this.updateEnvironmentProp(folder, 'children', updatedEnvironments);
}
/**
* Given a folder a prop and a new value updates the correct folder.
* Given a environment, a prop and a new value updates the correct environment.
*
* @param {Object} folder
* @param {Object} environment
* @param {String} prop
* @param {String|Boolean|Object|Array} newValue
* @return {Array}
*/
updateFolder(folder, prop, newValue) {
updateEnvironmentProp(environment, prop, newValue) {
const environments = this.state.environments;
const updatedEnvironments = environments.map((env) => {
const updateEnv = Object.assign({}, env);
if (env.isFolder && env.id === folder.id) {
if (env.id === environment.id) {
updateEnv[prop] = newValue;
}
......@@ -149,8 +153,6 @@ export default class EnvironmentsStore {
});
this.state.environments = updatedEnvironments;
return updatedEnvironments;
}
getOpenFolders() {
......
/**
* OAuth-based login buttons have a separate "remember me" checkbox.
*
* Toggling this checkbox adds/removes a `remember_me` parameter to the
* login buttons' href, which is passed on to the omniauth callback.
**/
export default class OAuthRememberMe {
constructor(opts = {}) {
this.container = opts.container || '';
this.loginLinkSelector = '.oauth-login';
}
bindEvents() {
$('#remember_me', this.container).on('click', this.toggleRememberMe);
}
// eslint-disable-next-line class-methods-use-this
toggleRememberMe(event) {
const rememberMe = $(event.target).is(':checked');
$('.oauth-login', this.container).each((i, element) => {
const href = $(element).attr('href');
if (rememberMe) {
$(element).attr('href', `${href}?remember_me=1`);
} else {
$(element).attr('href', href.replace('?remember_me=1', ''));
}
});
}
}
......@@ -2,12 +2,11 @@
/* eslint no-new: "off" */
import AccessorUtilities from './lib/utils/accessor';
((global) => {
/**
/**
* Memorize the last selected tab after reloading a page.
* Does that setting the current selected tab in the localStorage
*/
class ActiveTabMemoizer {
class ActiveTabMemoizer {
constructor({ currentTabKey = 'current_signin_tab', tabSelector = 'ul.nav-tabs' } = {}) {
this.currentTabKey = currentTabKey;
this.tabSelector = tabSelector;
......@@ -51,7 +50,6 @@ import AccessorUtilities from './lib/utils/accessor';
return window.localStorage.getItem(this.currentTabKey);
}
}
}
global.ActiveTabMemoizer = ActiveTabMemoizer;
})(window);
window.ActiveTabMemoizer = ActiveTabMemoizer;
......@@ -2,8 +2,7 @@
import FilesCommentButton from './files_comment_button';
(function() {
window.SingleFileDiff = (function() {
window.SingleFileDiff = (function() {
var COLLAPSED_HTML, ERROR_HTML, LOADING_HTML, WRAPPER;
WRAPPER = '<div class="diff-content"></div>';
......@@ -88,13 +87,12 @@ import FilesCommentButton from './files_comment_button';
};
return SingleFileDiff;
})();
})();
$.fn.singleFileDiff = function() {
$.fn.singleFileDiff = function() {
return this.each(function() {
if (!$.data(this, 'singleFileDiff')) {
return $.data(this, 'singleFileDiff', new window.SingleFileDiff(this));
}
});
};
}).call(window);
};
/*
* Instances of SmartInterval extend the functionality of `setInterval`, make it configurable
* and controllable by a public API.
*
* */
(() => {
class SmartInterval {
/**
* Instances of SmartInterval extend the functionality of `setInterval`, make it configurable
* and controllable by a public API.
*/
class SmartInterval {
/**
* @param { function } opts.callback Function to be called on each iteration (required)
* @param { milliseconds } opts.startingInterval `currentInterval` is set to this initially
......@@ -37,6 +35,7 @@
this.initInterval();
}
/* public */
start() {
......@@ -153,6 +152,6 @@
state.intervalId = window.clearInterval(state.intervalId);
}
}
gl.SmartInterval = SmartInterval;
})(window.gl || (window.gl = {}));
}
window.gl.SmartInterval = SmartInterval;
/* eslint-disable arrow-parens, no-param-reassign, space-before-function-paren, func-names, no-var, max-len */
(global => {
global.gl = global.gl || {};
gl.SnippetsList = function() {
window.gl.SnippetsList = function() {
var $holder = $('.snippets-list-holder');
$holder.find('.pagination').on('ajax:success', (e, data) => {
$holder.replaceWith(data.html);
});
};
})(window);
};
/* eslint-disable func-names, space-before-function-paren, wrap-iife, no-unused-vars, one-var, no-var, one-var-declaration-per-line, prefer-arrow-callback, no-new, max-len */
/* global Flash */
(function() {
this.Star = (function() {
window.Star = (function() {
function Star() {
$('.project-home-panel .toggle-star').on('ajax:success', function(e, data, status, xhr) {
var $starIcon, $starSpan, $this, toggleStar;
......@@ -26,5 +25,4 @@
}
return Star;
})();
}).call(window);
})();
(() => {
class Subscription {
class Subscription {
constructor(containerElm) {
this.containerElm = containerElm;
......@@ -40,8 +39,7 @@
static bindAll(selector) {
[].forEach.call(document.querySelectorAll(selector), elm => new Subscription(elm));
}
}
}
window.gl = window.gl || {};
window.gl.Subscription = Subscription;
})();
window.gl = window.gl || {};
window.gl.Subscription = Subscription;
/* eslint-disable func-names, space-before-function-paren, wrap-iife, no-var, quotes, object-shorthand, no-unused-vars, no-shadow, one-var, one-var-declaration-per-line, comma-dangle, max-len */
(function() {
this.SubscriptionSelect = (function() {
window.SubscriptionSelect = (function() {
function SubscriptionSelect() {
$('.js-subscription-event').each(function(i, el) {
var fieldName;
......@@ -30,5 +30,4 @@
}
return SubscriptionSelect;
})();
}).call(window);
})();
......@@ -9,8 +9,8 @@
//
// <div class="js-syntax-highlight"></div>
//
(function() {
$.fn.syntaxHighlight = function() {
$.fn.syntaxHighlight = function() {
var $children;
if ($(this).hasClass('js-syntax-highlight')) {
......@@ -23,5 +23,4 @@
return $children.syntaxHighlight();
}
}
};
}).call(window);
};
/* eslint-disable func-names, space-before-function-paren, wrap-iife, max-len, quotes, consistent-return, no-var, one-var, one-var-declaration-per-line, no-else-return, prefer-arrow-callback, max-len */
(function() {
this.TreeView = (function() {
window.TreeView = (function() {
function TreeView() {
this.initKeyNav();
// Code browser tree slider
......@@ -64,5 +63,4 @@
};
return TreeView;
})();
}).call(window);
})();
......@@ -2,8 +2,7 @@
import Cookies from 'js-cookie';
((global) => {
global.User = class {
class User {
constructor({ action }) {
this.action = action;
this.placeProfileAvatarsToTop();
......@@ -18,7 +17,7 @@ import Cookies from 'js-cookie';
}
initTabs() {
return new global.UserTabs({
return new window.gl.UserTabs({
parentEl: '.user-profile',
action: this.action
});
......@@ -31,5 +30,7 @@ import Cookies from 'js-cookie';
$(this).parents('.project-limit-message').remove();
});
}
};
})(window.gl || (window.gl = {}));
}
window.gl = window.gl || {};
window.gl.User = User;
......@@ -59,8 +59,8 @@ content on the Users#show page.
</div>
</div>
*/
((global) => {
class UserTabs {
class UserTabs {
constructor ({ defaultAction, action, parentEl }) {
this.loaded = {};
this.defaultAction = defaultAction || 'activity';
......@@ -170,6 +170,7 @@ content on the Users#show page.
getCurrentAction() {
return this.$parentEl.find('.nav-links .active a').data('action');
}
}
global.UserTabs = UserTabs;
})(window.gl || (window.gl = {}));
}
window.gl = window.gl || {};
window.gl.UserTabs = UserTabs;
/* eslint-disable comma-dangle, consistent-return, class-methods-use-this, arrow-parens, no-param-reassign, max-len */
((global) => {
const debounceTimeoutDuration = 1000;
const invalidInputClass = 'gl-field-error-outline';
const successInputClass = 'gl-field-success-outline';
const unavailableMessageSelector = '.username .validation-error';
const successMessageSelector = '.username .validation-success';
const pendingMessageSelector = '.username .validation-pending';
const invalidMessageSelector = '.username .gl-field-error';
class UsernameValidator {
const debounceTimeoutDuration = 1000;
const invalidInputClass = 'gl-field-error-outline';
const successInputClass = 'gl-field-success-outline';
const unavailableMessageSelector = '.username .validation-error';
const successMessageSelector = '.username .validation-success';
const pendingMessageSelector = '.username .validation-pending';
const invalidMessageSelector = '.username .gl-field-error';
class UsernameValidator {
constructor() {
this.inputElement = $('#new_user_username');
this.inputDomElement = this.inputElement.get(0);
......@@ -129,7 +128,6 @@
this.inputElement.addClass(invalidInputClass).removeClass(successInputClass);
$inputErrorMessage.show();
}
}
}
global.UsernameValidator = UsernameValidator;
})(window);
window.UsernameValidator = UsernameValidator;
(() => {
const gl = window.gl || (window.gl = {});
class VisibilitySelect {
class VisibilitySelect {
constructor(container) {
if (!container) throw new Error('VisibilitySelect requires a container element as argument 1');
this.container = container;
......@@ -21,7 +18,7 @@
updateHelpText() {
this.helpBlock.textContent = this.select.querySelector('option:checked').dataset.description;
}
}
}
gl.VisibilitySelect = VisibilitySelect;
})();
window.gl = window.gl || {};
window.gl.VisibilitySelect = VisibilitySelect;
......@@ -4,8 +4,7 @@
import 'vendor/jquery.nicescroll';
import './breakpoints';
((global) => {
class Wikis {
class Wikis {
constructor() {
this.bp = Breakpoints.get();
this.sidebarEl = document.querySelector('.js-wiki-sidebar');
......@@ -63,7 +62,7 @@ import './breakpoints';
classList.remove('right-sidebar-expanded');
}
}
}
}
global.Wikis = Wikis;
})(window.gl || (window.gl = {}));
window.gl = window.gl || {};
window.gl.Wikis = Wikis;
......@@ -34,8 +34,8 @@ window.Dropzone = Dropzone;
// **Cancelable** No
// **Target** a.js-zen-leave
//
(function() {
this.ZenMode = (function() {
window.ZenMode = (function() {
function ZenMode() {
this.active_backdrop = null;
this.active_textarea = null;
......@@ -94,5 +94,4 @@ window.Dropzone = Dropzone;
};
return ZenMode;
})();
}).call(window);
})();
......@@ -92,7 +92,7 @@
@mixin maintain-sidebar-dimensions {
display: block;
width: $gutter-width;
padding: 10px 20px;
padding: 10px 0;
}
.issues-bulk-update.right-sidebar {
......
......@@ -3,6 +3,7 @@
color: $gl-text-color;
border: 1px solid $border-color;
border-radius: $border-radius-default;
margin-bottom: $gl-padding;
.well-segment {
padding: $gl-padding;
......@@ -21,6 +22,11 @@
font-size: 12px;
}
}
&.admin-well h4 {
border-bottom: 1px solid $border-color;
padding-bottom: 8px;
}
}
.icon-container {
......@@ -53,6 +59,14 @@
padding: 15px;
}
.dark-well {
background-color: $gray-normal;
.btn {
width: 100%;
}
}
.well-centered {
h1 {
font-weight: normal;
......
......@@ -200,7 +200,6 @@
right: 0;
transition: width .3s;
background: $gray-light;
padding: 0 20px;
z-index: 200;
overflow: hidden;
......@@ -224,6 +223,10 @@
}
}
.issuable-sidebar {
padding: 0 20px;
}
.issuable-sidebar-header {
padding-top: 10px;
}
......
class OmniauthCallbacksController < Devise::OmniauthCallbacksController
include AuthenticatesWithTwoFactor
include Devise::Controllers::Rememberable
protect_from_forgery except: [:kerberos, :saml, :cas3]
......@@ -116,10 +115,8 @@ class OmniauthCallbacksController < Devise::OmniauthCallbacksController
if @user.persisted? && @user.valid?
log_audit_event(@user, with: oauth['provider'])
if @user.two_factor_enabled?
params[:remember_me] = '1' if remember_me?
prompt_for_two_factor(@user)
else
remember_me(@user) if remember_me?
sign_in_and_redirect(@user)
end
else
......@@ -150,9 +147,4 @@ class OmniauthCallbacksController < Devise::OmniauthCallbacksController
AuditEventService.new(user, user, options)
.for_authentication.security_event
end
def remember_me?
request_params = request.env['omniauth.params']
(request_params['remember_me'] == '1') if request_params.present?
end
end
......@@ -5,8 +5,9 @@
.admin-dashboard.prepend-top-default
.row
.col-md-4
.info-well
.well-segment.admin-well
%h4 Statistics
%hr
%p
Forks
%span.light.pull-right
......@@ -40,9 +41,9 @@
%span.light.pull-right
= number_with_delimiter(User.active.count)
.col-md-4
%h4
Features
%hr
.info-well
.well-segment.admin-well
%h4 Features
- sign_up = "Sign up"
%p{ "aria-label" => "#{sign_up}: status " + (signup_enabled? ? "on" : "off") }
= sign_up
......@@ -85,15 +86,14 @@
= gitlab_shared_runners
%span.light.pull-right
= boolean_to_icon gitlab_shared_runners_enabled
.col-md-4
.info-well
.well-segment.admin-well
%h4
Components
- if current_application_settings.version_check_enabled
.pull-right
= version_status_badge
%hr
%p
GitLab
%span.pull-right
......@@ -118,66 +118,66 @@
Ruby
%span.pull-right
#{RUBY_VERSION}p#{RUBY_PATCHLEVEL}
%p
Rails
%span.pull-right
#{Rails::VERSION::STRING}
%p
= Gitlab::Database.adapter_name
%span.pull-right
= Gitlab::Database.version
%hr
.row
.col-sm-4
.light-well.well-centered
%h4 Projects
.data
.info-well.dark-well
.well-segment.well-centered
= link_to admin_projects_path do
%h1= number_with_delimiter(Project.cached_count)
%h3.text-center
Projects:
= number_with_delimiter(Project.cached_count)
%hr
= link_to('New project', new_project_path, class: "btn btn-new")
.col-sm-4
.light-well.well-centered
%h4 Users
.data
.info-well.dark-well
.well-segment.well-centered
= link_to admin_users_path do
%h1= number_with_delimiter(User.count)
%h3.text-center
Users:
= number_with_delimiter(User.count)
%hr
= link_to 'New user', new_admin_user_path, class: "btn btn-new"
.col-sm-4
.light-well.well-centered
%h4 Groups
.data
.info-well.dark-well
.well-segment.well-centered
= link_to admin_groups_path do
%h1= number_with_delimiter(Group.count)
%h3.text-center
Groups
= number_with_delimiter(Group.count)
%hr
= link_to 'New group', new_admin_group_path, class: "btn btn-new"
.row.prepend-top-10
.row
.col-md-4
.info-well
.well-segment.admin-well
%h4 Latest projects
%hr
- @projects.each do |project|
%p
= link_to project.name_with_namespace, [:admin, project.namespace.becomes(Namespace), project], class: 'str-truncated-60'
%span.light.pull-right
#{time_ago_with_tooltip(project.created_at)}
.col-md-4
.info-well
.well-segment.admin-well
%h4 Latest users
%hr
- @users.each do |user|
%p
= link_to [:admin, user], class: 'str-truncated-60' do
= user.name
%span.light.pull-right
#{time_ago_with_tooltip(user.created_at)}
.col-md-4
.info-well
.well-segment.admin-well
%h4 Latest groups
%hr
- @groups.each do |group|
%p
= link_to [:admin, group], class: 'str-truncated-60' do
......
......@@ -6,7 +6,4 @@
- providers.each do |provider|
%span.light
- has_icon = provider_has_icon?(provider)
= link_to provider_image_tag(provider), omniauth_authorize_path(:user, provider), method: :post, class: 'oauth-login' + (has_icon ? ' oauth-image-link' : ' btn'), id: "oauth-login-#{provider}"
%fieldset
= check_box_tag :remember_me
= label_tag :remember_me, 'Remember Me'
= link_to provider_image_tag(provider), omniauth_authorize_path(:user, provider), method: :post, class: (has_icon ? 'oauth-image-link' : 'btn')
---
title: Honor the "Remember me" parameter for OAuth-based login
merge_request: 11963
author:
---
title: Replace 'snippets/snippets.feature' spinach with rspec
merge_request: 12385
author: Alexander Randa @randaalex
---
title: Add wells to admin dashboard overview to fix spacing problems
merge_request:
author:
---
title: fix left & right padding on sidebar
merge_request:
author:
......@@ -619,53 +619,6 @@ test:
title: "JIRA"
url: https://sample_company.atlassian.net
project_key: PROJECT
omniauth:
enabled: true
allow_single_sign_on: true
external_providers: []
providers:
- { name: 'cas3',
label: 'cas3',
args: { url: 'https://sso.example.com',
disable_ssl_verification: false,
login_url: '/cas/login',
service_validate_url: '/cas/p3/serviceValidate',
logout_url: '/cas/logout'} }
- { name: 'authentiq',
app_id: 'YOUR_CLIENT_ID',
app_secret: 'YOUR_CLIENT_SECRET',
args: { scope: 'aq:name email~rs address aq:push' } }
- { name: 'github',
app_id: 'YOUR_APP_ID',
app_secret: 'YOUR_APP_SECRET',
url: "https://github.com/",
verify_ssl: false,
args: { scope: 'user:email' } }
- { name: 'bitbucket',
app_id: 'YOUR_APP_ID',
app_secret: 'YOUR_APP_SECRET' }
- { name: 'gitlab',
app_id: 'YOUR_APP_ID',
app_secret: 'YOUR_APP_SECRET',
args: { scope: 'api' } }
- { name: 'google_oauth2',
app_id: 'YOUR_APP_ID',
app_secret: 'YOUR_APP_SECRET',
args: { access_type: 'offline', approval_prompt: '' } }
- { name: 'facebook',
app_id: 'YOUR_APP_ID',
app_secret: 'YOUR_APP_SECRET' }
- { name: 'twitter',
app_id: 'YOUR_APP_ID',
app_secret: 'YOUR_APP_SECRET' }
- { name: 'auth0',
args: {
client_id: 'YOUR_AUTH0_CLIENT_ID',
client_secret: 'YOUR_AUTH0_CLIENT_SECRET',
namespace: 'YOUR_AUTH0_DOMAIN' } }
ldap:
enabled: false
servers:
......
require 'flipper/middleware/memoizer'
Rails.application.config.middleware.use Flipper::Middleware::Memoizer,
unless Rails.env.test?
Rails.application.config.middleware.use Flipper::Middleware::Memoizer,
lambda { Feature.flipper }
end
@snippets
Feature: Snippets
Background:
Given I sign in as a user
And I have public "Personal snippet one" snippet
And I have private "Personal snippet private" snippet
@javascript
Scenario: I create new snippet
Given I visit new snippet page
And I submit new snippet "Personal snippet three"
Then I should see snippet "Personal snippet three"
Scenario: I update "Personal snippet one"
Given I visit snippet page "Personal snippet one"
And I click link "Edit"
And I submit new title "Personal snippet new title"
Then I should see "Personal snippet new title"
Scenario: Set "Personal snippet one" public
Given I visit snippet page "Personal snippet one"
And I click link "Edit"
And I uncheck "Private" checkbox
Then I should see "Personal snippet one" public
Scenario: I destroy "Personal snippet one"
Given I visit snippet page "Personal snippet one"
And I click link "Delete"
Then I should not see "Personal snippet one" in snippets
Scenario: I create new internal snippet
Given I logout directly
And I sign in as an admin
Then I visit new snippet page
And I submit new internal snippet
Then I visit snippet page "Internal personal snippet one"
And I logout directly
Then I sign in as a user
Given I visit new snippet page
Then I visit snippet page "Internal personal snippet one"
......@@ -487,10 +487,6 @@ module SharedPaths
visit explore_snippets_path
end
step 'I visit new snippet page' do
visit new_snippet_path
end
def root_ref
@project.repository.root_ref
end
......
module SharedSnippet
include Spinach::DSL
step 'I have public "Personal snippet one" snippet' do
create(:personal_snippet,
title: "Personal snippet one",
content: "Test content",
file_name: "snippet.rb",
visibility_level: Snippet::PUBLIC,
author: current_user)
end
step 'I have private "Personal snippet private" snippet' do
create(:personal_snippet,
title: "Personal snippet private",
content: "Provate content",
file_name: "private_snippet.rb",
visibility_level: Snippet::PRIVATE,
author: current_user)
end
step 'I have internal "Personal snippet internal" snippet' do
create(:personal_snippet,
title: "Personal snippet internal",
content: "Provate content",
file_name: "internal_snippet.rb",
visibility_level: Snippet::INTERNAL,
author: current_user)
end
step 'I have a public many lined snippet' do
create(:personal_snippet,
title: 'Many lined snippet',
content: <<-END.gsub(/^\s+\|/, ''),
|line one
|line two
|line three
|line four
|line five
|line six
|line seven
|line eight
|line nine
|line ten
|line eleven
|line twelve
|line thirteen
|line fourteen
END
file_name: 'many_lined_snippet.rb',
visibility_level: Snippet::PUBLIC,
author: current_user)
end
step 'There is public "Personal snippet one" snippet' do
create(:personal_snippet,
title: "Personal snippet one",
content: "Test content",
file_name: "snippet.rb",
visibility_level: Snippet::PUBLIC,
author: create(:user))
end
end
class Spinach::Features::Snippets < Spinach::FeatureSteps
include SharedAuthentication
include SharedPaths
include SharedProject
include SharedSnippet
include WaitForRequests
step 'I click link "Personal snippet one"' do
click_link "Personal snippet one"
end
step 'I should not see "Personal snippet one" in snippets' do
expect(page).not_to have_content "Personal snippet one"
end
step 'I click link "Edit"' do
page.within ".detail-page-header" do
first(:link, "Edit").click
end
end
step 'I click link "Delete"' do
first(:link, "Delete").click
end
step 'I submit new snippet "Personal snippet three"' do
fill_in "personal_snippet_title", with: "Personal snippet three"
fill_in "personal_snippet_file_name", with: "my_snippet.rb"
page.within('.file-editor') do
find('.ace_editor').native.send_keys 'Content of snippet three'
end
click_button "Create snippet"
wait_for_requests
end
step 'I submit new internal snippet' do
fill_in "personal_snippet_title", with: "Internal personal snippet one"
fill_in "personal_snippet_file_name", with: "my_snippet.rb"
choose 'personal_snippet_visibility_level_10'
page.within('.file-editor') do
find(:xpath, "//input[@id='personal_snippet_content']").set 'Content of internal snippet'
end
click_button "Create snippet"
end
step 'I should see snippet "Personal snippet three"' do
expect(page).to have_content "Personal snippet three"
expect(page).to have_content "Content of snippet three"
end
step 'I submit new title "Personal snippet new title"' do
fill_in "personal_snippet_title", with: "Personal snippet new title"
click_button "Save"
end
step 'I should see "Personal snippet new title"' do
expect(page).to have_content "Personal snippet new title"
end
step 'I uncheck "Private" checkbox' do
choose "Internal"
click_button "Save"
end
step 'I should see "Personal snippet one" public' do
expect(page).to have_no_xpath("//i[@class='public-snippet']")
end
step 'I visit snippet page "Personal snippet one"' do
visit snippet_path(snippet)
end
step 'I visit snippet page "Internal personal snippet one"' do
visit snippet_path(internal_snippet)
end
def snippet
@snippet ||= PersonalSnippet.find_by!(title: "Personal snippet one")
end
def internal_snippet
@snippet ||= PersonalSnippet.find_by!(title: "Internal personal snippet one")
end
end
......@@ -42,7 +42,8 @@ namespace :gitlab do
http_clone_url = project.http_url_to_repo
ssh_clone_url = project.ssh_url_to_repo
omniauth_providers = Gitlab.config.omniauth.providers.map { |provider| provider['name'] }
omniauth_providers = Gitlab.config.omniauth.providers
omniauth_providers.map! { |provider| provider['name'] }
puts ""
puts "GitLab information".color(:yellow)
......
FactoryGirl.define do
factory :personal_snippet, parent: :snippet, class: :PersonalSnippet do
end
end
FactoryGirl.define do
factory :project_snippet, parent: :snippet, class: :ProjectSnippet do
project factory: :empty_project
end
end
......@@ -18,4 +18,11 @@ FactoryGirl.define do
visibility_level Snippet::PRIVATE
end
end
factory :project_snippet, parent: :snippet, class: :ProjectSnippet do
project factory: :empty_project
end
factory :personal_snippet, parent: :snippet, class: :PersonalSnippet do
end
end
require 'spec_helper'
feature 'OAuth Login', js: true do
def enter_code(code)
fill_in 'user_otp_attempt', with: code
click_button 'Verify code'
end
def stub_omniauth_config(provider)
OmniAuth.config.add_mock(provider, OmniAuth::AuthHash.new(provider: provider.to_s, uid: "12345"))
Rails.application.env_config['devise.mapping'] = Devise.mappings[:user]
Rails.application.env_config['omniauth.auth'] = OmniAuth.config.mock_auth[provider]
end
providers = [:github, :twitter, :bitbucket, :gitlab, :google_oauth2,
:facebook, :authentiq, :cas3, :auth0]
before(:all) do
# The OmniAuth `full_host` parameter doesn't get set correctly (it gets set to something like `http://localhost`
# here), and causes integration tests to fail with 404s. We set the `full_host` by removing the request path (and
# anything after it) from the request URI.
@omniauth_config_full_host = OmniAuth.config.full_host
OmniAuth.config.full_host = ->(request) { request['REQUEST_URI'].sub(/#{request['REQUEST_PATH']}.*/, '') }
end
after(:all) do
OmniAuth.config.full_host = @omniauth_config_full_host
end
providers.each do |provider|
context "when the user logs in using the #{provider} provider" do
context 'when two-factor authentication is disabled' do
it 'logs the user in' do
stub_omniauth_config(provider)
user = create(:omniauth_user, extern_uid: 'my-uid', provider: provider.to_s)
login_via(provider.to_s, user, 'my-uid')
expect(current_path).to eq root_path
end
end
context 'when two-factor authentication is enabled' do
it 'logs the user in' do
stub_omniauth_config(provider)
user = create(:omniauth_user, :two_factor, extern_uid: 'my-uid', provider: provider.to_s)
login_via(provider.to_s, user, 'my-uid')
enter_code(user.current_otp)
expect(current_path).to eq root_path
end
end
context 'when "remember me" is checked' do
context 'when two-factor authentication is disabled' do
it 'remembers the user after a browser restart' do
stub_omniauth_config(provider)
user = create(:omniauth_user, extern_uid: 'my-uid', provider: provider.to_s)
login_via(provider.to_s, user, 'my-uid', remember_me: true)
clear_browser_session
visit(root_path)
expect(current_path).to eq root_path
end
end
context 'when two-factor authentication is enabled' do
it 'remembers the user after a browser restart' do
stub_omniauth_config(provider)
user = create(:omniauth_user, :two_factor, extern_uid: 'my-uid', provider: provider.to_s)
login_via(provider.to_s, user, 'my-uid', remember_me: true)
enter_code(user.current_otp)
clear_browser_session
visit(root_path)
expect(current_path).to eq root_path
end
end
end
context 'when "remember me" is not checked' do
context 'when two-factor authentication is disabled' do
it 'does not remember the user after a browser restart' do
stub_omniauth_config(provider)
user = create(:omniauth_user, extern_uid: 'my-uid', provider: provider.to_s)
login_via(provider.to_s, user, 'my-uid', remember_me: false)
clear_browser_session
visit(root_path)
expect(current_path).to eq new_user_session_path
end
end
context 'when two-factor authentication is enabled' do
it 'does not remember the user after a browser restart' do
stub_omniauth_config(provider)
user = create(:omniauth_user, :two_factor, extern_uid: 'my-uid', provider: provider.to_s)
login_via(provider.to_s, user, 'my-uid', remember_me: false)
enter_code(user.current_otp)
clear_browser_session
visit(root_path)
expect(current_path).to eq new_user_session_path
end
end
end
end
end
end
require 'rails_helper'
feature 'Create Snippet', :js, feature: true do
feature 'User creates snippet', :js, feature: true do
include DropzoneHelper
let(:user) { create(:user) }
before do
gitlab_sign_in :user
sign_in(user)
visit new_snippet_path
end
......
require 'rails_helper'
feature 'User deletes snippet', feature: true do
let(:user) { create(:user) }
let(:content) { 'puts "test"' }
let(:snippet) { create(:personal_snippet, :public, content: content, author: user) }
before do
sign_in(user)
visit snippet_path(snippet)
end
it 'deletes the snippet' do
first(:link, 'Delete').click
expect(page).not_to have_content(snippet.title)
end
end
require 'rails_helper'
feature 'Edit Snippet', :js, feature: true do
feature 'User edits snippet', :js, feature: true do
include DropzoneHelper
let(:file_name) { 'test.rb' }
......@@ -10,7 +10,7 @@ feature 'Edit Snippet', :js, feature: true do
let(:snippet) { create(:personal_snippet, :public, file_name: file_name, content: content, author: user) }
before do
gitlab_sign_in(user)
sign_in(user)
visit edit_snippet_path(snippet)
wait_for_requests
......@@ -27,7 +27,7 @@ feature 'Edit Snippet', :js, feature: true do
it 'updates the snippet with files attached' do
dropzone_file Rails.root.join('spec', 'fixtures', 'banana_sample.gif')
expect(page.find_field("personal_snippet_description").value).to have_content('banana_sample')
expect(page.find_field('personal_snippet_description').value).to have_content('banana_sample')
click_button('Save changes')
wait_for_requests
......@@ -35,4 +35,24 @@ feature 'Edit Snippet', :js, feature: true do
link = find('a.no-attachment-icon img[alt="banana_sample"]')['src']
expect(link).to match(%r{/uploads/personal_snippet/#{snippet.id}/\h{32}/banana_sample\.gif\z})
end
it 'updates the snippet to make it internal' do
choose 'Internal'
click_button 'Save changes'
wait_for_requests
expect(page).to have_no_xpath("//i[@class='fa fa-lock']")
expect(page).to have_xpath("//i[@class='fa fa-shield']")
end
it 'updates the snippet to make it public' do
choose 'Public'
click_button 'Save changes'
wait_for_requests
expect(page).to have_no_xpath("//i[@class='fa fa-lock']")
expect(page).to have_xpath("//i[@class='fa fa-globe']")
end
end
......@@ -86,6 +86,16 @@ describe('Store', () => {
store.toggleFolder(store.state.environments[1]);
expect(store.state.environments[1].isOpen).toEqual(false);
});
it('should keep folder open when environments are updated', () => {
store.storeEnvironments(serverData);
store.toggleFolder(store.state.environments[1]);
expect(store.state.environments[1].isOpen).toEqual(true);
store.storeEnvironments(serverData);
expect(store.state.environments[1].isOpen).toEqual(true);
});
});
describe('setfolderContent', () => {
......@@ -97,6 +107,17 @@ describe('Store', () => {
expect(store.state.environments[1].children.length).toEqual(serverData.length);
expect(store.state.environments[1].children[0].isChildren).toEqual(true);
});
it('should keep folder content when environments are updated', () => {
store.storeEnvironments(serverData);
store.setfolderContent(store.state.environments[1], serverData);
expect(store.state.environments[1].children.length).toEqual(serverData.length);
// poll
store.storeEnvironments(serverData);
expect(store.state.environments[1].children.length).toEqual(serverData.length);
});
});
describe('store pagination', () => {
......
#oauth-container
%input#remember_me{ type: "checkbox" }
%a.oauth-login.twitter{ href: "http://example.com/" }
%a.oauth-login.github{ href: "http://example.com/" }
import OAuthRememberMe from '~/oauth_remember_me';
describe('OAuthRememberMe', () => {
preloadFixtures('static/oauth_remember_me.html.raw');
beforeEach(() => {
loadFixtures('static/oauth_remember_me.html.raw');
new OAuthRememberMe({ container: $('#oauth-container') }).bindEvents();
});
it('adds the "remember_me" query parameter to all OAuth login buttons', () => {
$('#oauth-container #remember_me').click();
expect($('#oauth-container .oauth-login.twitter').attr('href')).toBe('http://example.com/?remember_me=1');
expect($('#oauth-container .oauth-login.github').attr('href')).toBe('http://example.com/?remember_me=1');
});
it('removes the "remember_me" query parameter from all OAuth login buttons', () => {
$('#oauth-container #remember_me').click();
$('#oauth-container #remember_me').click();
expect($('#oauth-container .oauth-login.twitter').attr('href')).toBe('http://example.com/');
expect($('#oauth-container .oauth-login.github').attr('href')).toBe('http://example.com/');
});
});
......@@ -35,11 +35,6 @@ module CapybaraHelpers
visit 'about:blank'
visit url
end
# Simulate a browser restart by clearing the session cookie.
def clear_browser_session
page.driver.remove_cookie('_gitlab_session')
end
end
RSpec.configure do |config|
......
......@@ -62,16 +62,6 @@ module LoginHelpers
Thread.current[:current_user] = user
end
def login_via(provider, user, uid, remember_me: false)
mock_auth_hash(provider, uid, user.email)
visit new_user_session_path
expect(page).to have_content('Sign in with')
check 'Remember Me' if remember_me
click_link "oauth-login-#{provider}"
end
def mock_auth_hash(provider, uid, email)
# The mock_auth configuration allows you to set per-provider (or default)
# authentication hashes to return during integration testing.
......@@ -118,7 +108,6 @@ module LoginHelpers
end
allow(Gitlab::OAuth::Provider).to receive_messages(providers: [:saml], config_for: mock_saml_config)
stub_omniauth_setting(messages)
allow_any_instance_of(Object).to receive(:user_saml_omniauth_authorize_path).and_return('/users/auth/saml')
allow_any_instance_of(Object).to receive(:omniauth_authorize_path).with(:user, "saml").and_return('/users/auth/saml')
expect_any_instance_of(Object).to receive(:omniauth_authorize_path).with(:user, "saml").and_return('/users/auth/saml')
end
end
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment