Commit c83d0973 authored by Dmitriy Zaporozhets's avatar Dmitriy Zaporozhets

Merge remote-tracking branch 'ce-com/master' into ce-to-ee

parents 8e3e1d43 ef8eb3f6
......@@ -56,13 +56,18 @@ const Api = {
// Return projects list. Filtered by query
projects(query, options, callback) {
const url = Api.buildUrl(Api.projectsPath);
const defaults = {
search: query,
per_page: 20,
};
if (gon.current_user_id) {
defaults.membership = true;
}
return $.ajax({
url,
data: Object.assign({
search: query,
per_page: 20,
membership: true,
}, options),
data: Object.assign(defaults, options),
dataType: 'json',
})
.done(projects => callback(projects));
......
......@@ -29,12 +29,14 @@ showTooltip = function(target, title) {
var $target = $(target);
var originalTitle = $target.data('original-title');
$target
.attr('title', 'Copied')
.tooltip('fixTitle')
.tooltip('show')
.attr('title', originalTitle)
.tooltip('fixTitle');
if (!$target.data('hideTooltip')) {
$target
.attr('title', 'Copied')
.tooltip('fixTitle')
.tooltip('show')
.attr('title', originalTitle)
.tooltip('fixTitle');
}
};
$(function() {
......
......@@ -16,6 +16,14 @@ body.modal-open {
overflow: hidden;
}
.modal-no-backdrop {
@extend .modal-dialog;
.modal-content {
box-shadow: none;
}
}
@media (min-width: $screen-md-min) {
.modal-dialog {
width: 860px;
......
......@@ -182,7 +182,6 @@
padding: 5px 10px;
position: relative;
border-top: 1px solid $white-normal;
margin-top: -5px;
}
#binary-viewer {
......
# GroupsFinder
#
# Used to filter Groups by a set of params
#
# Arguments:
# current_user - which user is requesting groups
# params:
# owned: boolean
# parent: Group
# all_available: boolean (defaults to true)
#
# Users with full private access can see all groups. The `owned` and `parent`
# params can be used to restrict the groups that are returned.
#
# Anonymous users will never return any `owned` groups. They will return all
# public groups instead, even if `all_available` is set to false.
class GroupsFinder < UnionFinder
def initialize(current_user = nil, params = {})
@current_user = current_user
......@@ -16,13 +32,13 @@ class GroupsFinder < UnionFinder
attr_reader :current_user, :params
def all_groups
groups = []
if current_user
groups << Gitlab::GroupHierarchy.new(groups_for_ancestors, groups_for_descendants).all_groups
end
groups << Group.unscoped.public_to_user(current_user)
return [owned_groups] if params[:owned]
return [Group.all] if current_user&.full_private_access?
groups = []
groups << Gitlab::GroupHierarchy.new(groups_for_ancestors, groups_for_descendants).all_groups if current_user
groups << Group.unscoped.public_to_user(current_user) if include_public_groups?
groups << Group.none if groups.empty?
groups
end
......@@ -39,4 +55,12 @@ class GroupsFinder < UnionFinder
groups.where(parent: params[:parent])
end
def owned_groups
current_user&.groups || Group.none
end
def include_public_groups?
current_user.nil? || params.fetch(:all_available, true)
end
end
......@@ -20,6 +20,9 @@ module ButtonHelper
def clipboard_button(data = {})
css_class = data[:class] || 'btn-clipboard btn-transparent'
title = data[:title] || 'Copy to clipboard'
button_text = data[:button_text] || ''
hide_tooltip = data[:hide_tooltip] || false
hide_button_icon = data[:hide_button_icon] || false
# This supports code in app/assets/javascripts/copy_to_clipboard.js that
# works around ClipboardJS limitations to allow the context-specific copy/pasting of plain text or GFM.
......@@ -35,17 +38,22 @@ module ButtonHelper
target = data.delete(:target)
data[:clipboard_target] = target if target
data = { toggle: 'tooltip', placement: 'bottom', container: 'body' }.merge(data)
unless hide_tooltip
data = { toggle: 'tooltip', placement: 'bottom', container: 'body' }.merge(data)
end
content_tag :button,
icon('clipboard', 'aria-hidden': 'true'),
button_attributes = {
class: "btn #{css_class}",
data: data,
type: :button,
title: title,
aria: {
label: title
}
aria: { label: title }
}
content_tag :button, button_attributes do
concat(icon('clipboard', 'aria-hidden': 'true')) unless hide_button_icon
concat(button_text)
end
end
def http_clone_button(project, placement = 'right', append_link: true)
......
module Users
module NewUserNotifier
def notify_new_user(user, reset_token)
log_info("User \"#{user.name}\" (#{user.email}) was created")
notification_service.new_user(user, reset_token) if reset_token
system_hook_service.execute_hooks_for(user, :create)
end
end
end
module Users
class CreateService < BaseService
include NewUserNotifier
def initialize(current_user, params = {})
@current_user = current_user
@params = params.dup
......@@ -10,11 +12,7 @@ module Users
@reset_token = user.generate_reset_token if user.recently_sent_password_reset?
if user.save
log_info("User \"#{user.name}\" (#{user.email}) was created")
notification_service.new_user(user, @reset_token) if @reset_token
system_hook_service.execute_hooks_for(user, :create)
end
notify_new_user(user, @reset_token) if user.save
user
end
......
module Users
class UpdateService < BaseService
include NewUserNotifier
def initialize(user, params = {})
@user = user
@params = params.dup
......@@ -10,7 +12,11 @@ module Users
assign_attributes(&block)
user_exists = @user.persisted?
if @user.save(validate: validate)
notify_new_user(@user, nil) unless user_exists
success
else
error(@user.errors.full_messages.uniq.join('. '))
......
%h3.page-title Authorization required
%main{ :role => "main" }
%p.h4
Authorize
%strong.text-info= @pre_auth.client.name
to use your account?
.modal-no-backdrop
.modal-content
.modal-header
%h3.page-title
Authorize
= link_to @pre_auth.client.name, @pre_auth.redirect_uri, target: '_blank', rel: 'noopener noreferrer'
to use your account?
- if current_user.admin?
.text-warning.prepend-top-20
%p
= icon("exclamation-triangle fw")
You are an admin, which means granting access to
%strong= @pre_auth.client.name
will allow them to interact with GitLab as an admin as well. Proceed with caution.
- if @pre_auth.scopes
#oauth-permissions
%p This application will be able to:
%ul.text-info
- @pre_auth.scopes.each do |scope|
%li= t scope, scope: [:doorkeeper, :scopes]
%hr/
.actions
= form_tag oauth_authorization_path, method: :post do
= hidden_field_tag :client_id, @pre_auth.client.uid
= hidden_field_tag :redirect_uri, @pre_auth.redirect_uri
= hidden_field_tag :state, @pre_auth.state
= hidden_field_tag :response_type, @pre_auth.response_type
= hidden_field_tag :scope, @pre_auth.scope
= hidden_field_tag :nonce, @pre_auth.nonce
= submit_tag "Authorize", class: "btn btn-success wide pull-left"
= form_tag oauth_authorization_path, method: :delete do
= hidden_field_tag :client_id, @pre_auth.client.uid
= hidden_field_tag :redirect_uri, @pre_auth.redirect_uri
= hidden_field_tag :state, @pre_auth.state
= hidden_field_tag :response_type, @pre_auth.response_type
= hidden_field_tag :scope, @pre_auth.scope
= hidden_field_tag :nonce, @pre_auth.nonce
= submit_tag "Deny", class: "btn btn-danger prepend-left-10"
.modal-body
- if current_user.admin?
.text-warning
%p
= icon("exclamation-triangle fw")
You are an admin, which means granting access to
%strong= @pre_auth.client.name
will allow them to interact with GitLab as an admin as well. Proceed with caution.
%p
You are about to authorize
= link_to @pre_auth.client.name, @pre_auth.redirect_uri, target: '_blank', rel: 'noopener noreferrer'
to use your account.
- if @pre_auth.scopes
This application will be able to:
%ul
- @pre_auth.scopes.each do |scope|
%li= t scope, scope: [:doorkeeper, :scopes]
.form-actions.text-right
= form_tag oauth_authorization_path, method: :delete, class: 'inline' do
= hidden_field_tag :client_id, @pre_auth.client.uid
= hidden_field_tag :redirect_uri, @pre_auth.redirect_uri
= hidden_field_tag :state, @pre_auth.state
= hidden_field_tag :response_type, @pre_auth.response_type
= hidden_field_tag :scope, @pre_auth.scope
= hidden_field_tag :nonce, @pre_auth.nonce
= submit_tag "Deny", class: "btn btn-danger"
= form_tag oauth_authorization_path, method: :post, class: 'inline' do
= hidden_field_tag :client_id, @pre_auth.client.uid
= hidden_field_tag :redirect_uri, @pre_auth.redirect_uri
= hidden_field_tag :state, @pre_auth.state
= hidden_field_tag :response_type, @pre_auth.response_type
= hidden_field_tag :scope, @pre_auth.scope
= hidden_field_tag :nonce, @pre_auth.nonce
= submit_tag "Authorize", class: "btn btn-success prepend-left-10"
......@@ -35,7 +35,8 @@
%ul
- if can_update_issue
%li= link_to 'Edit', edit_project_issue_path(@project, @issue)
- unless current_user == @issue.author
/ TODO: simplify condition back #36860
- if @issue.author && current_user != @issue.author
%li= link_to 'Report abuse', new_abuse_report_path(user_id: @issue.author.id, ref_url: issue_url(@issue))
- if can_update_issue
%li= link_to 'Close issue', issue_path(@issue, issue: { state_event: :close }, format: 'json'), class: "btn-close #{issue_button_visibility(@issue, true)}", title: 'Close issue'
......
......@@ -6,6 +6,8 @@
%span.icon
= custom_icon('ellipsis_v')
%ul.dropdown-menu.more-actions-dropdown.dropdown-open-left
%li
= clipboard_button(text: noteable_note_url(note), title: "Copy reference to clipboard", button_text: 'Copy link', hide_tooltip: true, hide_button_icon: true)
- unless is_current_user
%li
= link_to new_abuse_report_path(user_id: note.author.id, ref_url: noteable_note_url(note)) do
......
......@@ -11,7 +11,7 @@
%span.sr-only
Clear search
- unless params[:snippets].eql? 'true'
= render 'filter' if current_user
= render 'filter'
= button_tag "Search", class: "btn btn-success btn-search"
- if current_application_settings.elasticsearch_search?
.help-block
......
......@@ -9,6 +9,7 @@
class: "hidden-xs hidden-sm btn btn-grouped btn-reopen #{issuable_button_visibility(issuable, false)}", title: "Reopen #{display_issuable_type}"
- elsif can_update && !is_current_user
= render 'shared/issuable/close_reopen_report_toggle', issuable: issuable
- else
- elsif issuable.author
/ TODO: change back to else #36860
= link_to 'Report abuse', new_abuse_report_path(user_id: issuable.author.id, ref_url: issuable_url(issuable)),
class: 'hidden-xs hidden-sm btn btn-grouped btn-close-color', title: 'Report abuse'
......@@ -37,13 +37,15 @@
%li.divider.droplab-item-ignore
%li.report-item{ data: { text: 'Report abuse', url: new_abuse_report_path(user_id: issuable.author.id, ref_url: issuable_url(issuable)),
button_class: "#{button_class} btn-close-color", toggle_class: "#{toggle_class} btn-close-color", method: '' } }
%button.btn.btn-transparent
= icon('check', class: 'icon')
.description
%strong.title Report abuse
%p.text
Report
= display_issuable_type.pluralize
that are abusive, inappropriate or spam.
/ TODO: remove condition #36860
- if issuable.author
%li.report-item{ data: { text: 'Report abuse', url: new_abuse_report_path(user_id: issuable.author.id, ref_url: issuable_url(issuable)),
button_class: "#{button_class} btn-close-color", toggle_class: "#{toggle_class} btn-close-color", method: '' } }
%button.btn.btn-transparent
= icon('check', class: 'icon')
.description
%strong.title Report abuse
%p.text
Report
= display_issuable_type.pluralize
that are abusive, inappropriate or spam.
# Concern for enabling a few lines of exception backtraces in Sidekiq
module ExceptionBacktrace
extend ActiveSupport::Concern
included do
sidekiq_options backtrace: 5
end
end
class GroupDestroyWorker
include Sidekiq::Worker
include DedicatedSidekiqQueue
include ExceptionBacktrace
def perform(group_id, user_id)
begin
......
......@@ -7,6 +7,7 @@
class NamespacelessProjectDestroyWorker
include Sidekiq::Worker
include DedicatedSidekiqQueue
include ExceptionBacktrace
def self.bulk_perform_async(args_list)
Sidekiq::Client.push_bulk('class' => self, 'queue' => sidekiq_options['queue'], 'args' => args_list)
......
class ProjectDestroyWorker
include Sidekiq::Worker
include DedicatedSidekiqQueue
include ExceptionBacktrace
def perform(project_id, user_id, params)
project = Project.find(project_id)
......
class ProjectExportWorker
include Sidekiq::Worker
include DedicatedSidekiqQueue
include ExceptionBacktrace
sidekiq_options retry: 3
......
......@@ -3,6 +3,7 @@ class RepositoryImportWorker
include Sidekiq::Worker
include DedicatedSidekiqQueue
include ExceptionBacktrace
sidekiq_options status_expiration: StuckImportJobsWorker::IMPORT_JOBS_EXPIRATION
......
---
title: Fix group and project search for anonymous users
merge_request: 13745
author:
type: fixed
---
title: restyling of OAuth authorization confirmation
merge_request:
author: Jacopo Beschi @jacopo-beschi
type: changed
---
title: Add support for copying permalink to notes via more actions dropdown
merge_request: 13299
author:
type: added
---
title: Fix failure when issue is authored by a deleted user
merge_request: 13807
author:
type: fixed
---
title: Document version Group Milestones API introduced
merge_request:
author:
type: changed
---
title: Replace 'source/search_code.feature' spinach test with an rspec analog
merge_request: 13697
author: blackst0ne
type: other
---
title: Fire system hooks when a user is created via LDAP
merge_request:
author:
type: fixed
......@@ -177,7 +177,7 @@ var config = {
if (chunk.name) {
return chunk.name;
}
return chunk.modules.map((m) => {
return chunk.mapModules((m) => {
var chunkPath = m.request.split('!').pop();
return path.relative(m.context, chunkPath);
}).join('_');
......
......@@ -6,7 +6,7 @@ class MigrateStagesStatuses < ActiveRecord::Migration
disable_ddl_transaction!
BATCH_SIZE = 10000
RANGE_SIZE = 1000
RANGE_SIZE = 100
MIGRATION = 'MigrateStageStatus'.freeze
class Stage < ActiveRecord::Base
......@@ -17,10 +17,10 @@ class MigrateStagesStatuses < ActiveRecord::Migration
def up
Stage.where(status: nil).each_batch(of: BATCH_SIZE) do |relation, index|
relation.each_batch(of: RANGE_SIZE) do |batch|
range = relation.pluck('MIN(id)', 'MAX(id)').first
schedule = index * 5.minutes
range = batch.pluck('MIN(id)', 'MAX(id)').first
delay = index * 5.minutes
BackgroundMigrationWorker.perform_in(schedule, MIGRATION, range)
BackgroundMigrationWorker.perform_in(delay, MIGRATION, range)
end
end
end
......
......@@ -4,7 +4,7 @@ To enable the Authentiq OmniAuth provider for passwordless authentication you mu
Authentiq will generate a Client ID and the accompanying Client Secret for you to use.
1. Get your Client credentials (Client ID and Client Secret) at [Authentiq](https://www.authentiq.com/register).
1. Get your Client credentials (Client ID and Client Secret) at [Authentiq](https://www.authentiq.com/developers).
2. On your GitLab server, open the configuration file:
......
# Group milestones API
> **Notes:**
> [Introduced][ce-12819] in GitLab 9.5.
## List group milestones
Returns a list of group milestones.
......@@ -118,3 +121,5 @@ Parameters:
- `id` (required) - The ID or [URL-encoded path of the group](README.md#namespaced-path-encoding) owned by the authenticated user
- `milestone_id` (required) - The ID of a group milestone
[ce-12819]: https://gitlab.com/gitlab-org/gitlab-ce/merge_requests/12819
......@@ -2,7 +2,8 @@
## List groups
Get a list of groups. (As user: my groups or all available, as admin: all groups).
Get a list of visible groups for the authenticated user. When accessed without
authentication, only public groups are returned.
Parameters:
......@@ -43,7 +44,8 @@ You can search for groups by name or path, see below.
## List a group's projects
Get a list of projects in this group.
Get a list of projects in this group. When accessed without authentication, only
public projects are returned.
```
GET /groups/:id/projects
......@@ -109,7 +111,8 @@ Example response:
## Details of a group
Get all details of a group.
Get all details of a group. This endpoint can be accessed without authentication
if the group is publicly accessible.
```
GET /groups/:id
......
......@@ -87,6 +87,11 @@ To follow conventions of naming across GitLab, and to futher move away from the
`build` term and toward `job` CI variables have been renamed for the 9.0
release.
>**Note:**
Starting with GitLab 9.0, we have deprecated the `$CI_BUILD_*` variables. **You are
strongly advised to use the new variables as we will remove the old ones in
future GitLab releases.**
| 8.x name | 9.0+ name |
| --------------------- |------------------------ |
| `CI_BUILD_ID` | `CI_JOB_ID` |
......
......@@ -1065,6 +1065,8 @@ a list of all previous jobs from which the artifacts should be downloaded.
You can only define jobs from stages that are executed before the current one.
An error will be shown if you define jobs from the current stage or next ones.
Defining an empty array will skip downloading any artifacts for that job.
The status of the previous job is not considered when using `dependencies`, so
if it failed or it is a manual job that was not run, no error occurs.
---
......
......@@ -17,7 +17,7 @@ the hardware requirements.
- [Installation from source](installation.md) - Install GitLab from source.
Useful for unsupported systems like *BSD. For an overview of the directory
structure, read the [structure documentation](structure.md).
- [Docker](https://docs.gitlab.com/omnibus/docker/) - Install GitLab using Docker.
- [Docker](docker.md) - Install GitLab using Docker.
## Install GitLab on cloud providers
......
......@@ -10,7 +10,7 @@ like Ubuntu, Red Hat Enterprise Linux, and of course - GitLab! This means that y
pre-configured GitLab VM and have your very own private GitLab up and running in around 30 minutes.
Let's get started.
### Getting started
## Getting started
First, you'll need an account on Azure. There are three ways to do this:
......@@ -25,7 +25,7 @@ This is a great way to try out Azure and cloud computing, and you can
subscription gives you recurring Azure credits every month, so why not put those credits to use and
try out GitLab right now?
### Working with Azure
## Working with Azure
Once you have an Azure account, you can get started. Login to Azure using
[portal.azure.com](https://portal.azure.com) and the first thing you will see is the Dashboard:
......@@ -35,7 +35,7 @@ Once you have an Azure account, you can get started. Login to Azure using
The Dashboard gives you a quick overview of Azure resources, and from here you you can build VMs,
create SQL Databases, author websites, and perform lots of other cloud tasks.
### Create New VM
## Create New VM
The [Azure Marketplace][Azure-Marketplace] is an online store for pre-configured applications and
services which have been optimized for the cloud by software vendors like GitLab, and both
......@@ -56,7 +56,7 @@ Click **"Create"** and you will be presented with the "Create virtual machine" b
![Azure - Create Virtual Machine - Basics](img/azure-create-virtual-machine-basics.png)
### Basics
## Basics
The first items we need to configure are the basic settings of the underlying virtual machine:
......@@ -84,7 +84,7 @@ Here are the settings we've used:
Check the settings you have entered, and then click **"OK"** when you're ready to proceed.
### Size
## Size
Next, you need to choose the size of your VM - selecting features such as the number of CPU cores,
the amount of RAM, the size of storage (and its speed), etc.
......@@ -108,7 +108,7 @@ free trial credits, you'll likely want to learn
Go ahead and click your chosen size, then click **"Select"** when you're ready to proceed to the
next step.
### Settings
## Settings
On the next blade, you're asked to configure the Storage, Network and Extension settings.
We've gone with the default settings as they're sufficient for test-driving GitLab, but please
......@@ -118,7 +118,7 @@ choose the settings which best meet your own requirements:
Review the settings and then click **"OK"** when you're ready to proceed to the last step.
### Purchase
## Purchase
The Purchase page is the last step and here you will be presented with the price per hour for your
new VM. You'll be billed only for the VM itself (e.g. "Standard DS1 v2") because the
......@@ -131,7 +131,7 @@ previous steps, just click on any of the four steps to re-open them.
When you have read and agreed to the terms of use and are ready to proceed, click **"Purchase"**.
### Deployment
## Deployment
At this point, Azure will begin deploying your new VM. The deployment process will take a few
minutes to complete, with progress displayed on the **"Deployment"** blade:
......@@ -146,7 +146,7 @@ on the Azure Dashboard (you may need to refresh the page):
The new VM can also be accessed by clicking the `All resources` or `Virtual machines` icons in the
Azure Portal sidebar navigation menu.
### Setup a domain name
## Setup a domain name
The VM will have a public IP address (static by default), but Azure allows us to assign a friendly
DNS name to the VM, so let's go ahead and do that.
......@@ -174,7 +174,7 @@ to make sure your VM is configured to use a _static_ public IP address (i.e. not
or you will have to reconfigure the DNS `A` record each time Azure reassigns your VM a new public IP
address. Read [IP address types and allocation methods in Azure][Azure-IP-Address-Types] to learn more.
### Let's open some ports!
## Let's open some ports!
At this stage you should have a running and fully operational VM. However, none of the services on
your VM (e.g. GitLab) will be publicly accessible via the internet until you have opened up the
......@@ -202,7 +202,7 @@ Next, click **"Add"**:
![Azure - Network security group - Inbound security rules - Add](img/azure-nsg-inbound-sec-rules-add-highlight.png)
#### Which ports to open?
### Which ports to open?
Like all servers, our VM will be running many services. However, we want to open up the correct
ports to enable public internet access to two services in particular:
......@@ -213,7 +213,7 @@ public access to the instance of GitLab running on our VM.
allowing public access (with authentication) to remote terminal sessions
_(you'll see why we need [SSH] access to our VM [later on in this tutorial](#maintaining-your-gitlab-instance))_
#### Open HTTP on Port 80
### Open HTTP on Port 80
In the **"Add inbound security rule"** blade, let's open port 80 so that our VM will accept HTTP
connections:
......@@ -225,7 +225,7 @@ connections:
1. Make sure the `Action` is set to **Allow**
1. Click **"OK"**
#### Open SSH on Port 22
### Open SSH on Port 22
Repeat the above process, adding a second Inbound security rule to open port 22, enabling our VM to
accept [SSH] connections:
......
# GitLab Docker images
[Docker](https://www.docker.com) and container technology have been revolutionizing the software world for the past few years. They combine the performance and efficiency of native execution with the abstraction, security, and immutability of virtualization.
GitLab provides official Docker images to allowing you to easily take advantage of the benefits of containerization while operating your GitLab instance.
## Omnibus GitLab based images
GitLab maintains a set of [official Docker images](https://hub.docker.com/r/gitlab) based on our [Omnibus GitLab package](https://docs.gitlab.com/omnibus/README.html). These images include:
* [GitLab Community Edition](https://hub.docker.com/r/gitlab/gitlab-ce/)
* [GitLab Enterprise Edition](https://hub.docker.com/r/gitlab/gitlab-ee/)
* [GitLab Runner](https://hub.docker.com/r/gitlab/gitlab-runner/)
A [complete usage guide](https://docs.gitlab.com/omnibus/docker/) to these images is available, as well as the [Dockerfile used for building the images](https://gitlab.com/gitlab-org/omnibus-gitlab/tree/master/docker).
## Cloud native images
GitLab is also working towards a [cloud native set of containers](https://gitlab.com/charts/helm.gitlab.io#docker-container-images), with a single image for each component service. We intend for these images to eventually replace the [Omnibus GitLab based images](#omnibus-gitlab-based-images).
# GitLab Docker images
* The official GitLab Community Edition Docker image is [available on Docker Hub](https://hub.docker.com/r/gitlab/gitlab-ce/).
* The official GitLab Enterprise Edition Docker image is [available on Docker Hub](https://hub.docker.com/r/gitlab/gitlab-ee/).
* The complete usage guide can be found in [Using GitLab Docker images](https://docs.gitlab.com/omnibus/docker/)
* The Dockerfile used for building public images is in [Omnibus Repository](https://gitlab.com/gitlab-org/omnibus-gitlab/tree/master/docker)
* Check the guide for [creating Omnibus-based Docker Image](https://docs.gitlab.com/omnibus/build/README.html#build-docker-image)
This content has been moved to [our documentation site](https://docs.gitlab.com/ce/install/docker.html).
Feature: Project Source Search Code
Background:
Given I sign in as a user
Scenario: Search for term "coffee"
Given I own project "Shop"
And I visit project source page
When I search for term "coffee"
Then I should see files from repository containing "coffee"
Scenario: Search on empty project
Given I own an empty project
And I visit my project's home page
When I search for term "coffee"
Then I should see empty result
class Spinach::Features::ProjectSourceSearchCode < Spinach::FeatureSteps
include SharedAuthentication
include SharedProject
include SharedPaths
step 'I search for term "coffee"' do
fill_in "search", with: "coffee"
click_button "Go"
end
step 'I should see files from repository containing "coffee"' do
expect(page).to have_content 'coffee'
expect(page).to have_content 'CONTRIBUTING.md'
end
step 'I should see empty result' do
expect(page).to have_content "We couldn't find any"
end
end
......@@ -328,10 +328,6 @@ module SharedPaths
visit project_commits_path(@project, 'stable', { limit: 5 })
end
step 'I visit project source page' do
visit project_tree_path(@project, root_ref)
end
step 'I visit blob file from repo' do
visit project_blob_path(@project, File.join(sample_commit.id, sample_blob.path))
end
......
......@@ -2,7 +2,7 @@ module API
class Groups < Grape::API
include PaginationParams
before { authenticate! }
before { authenticate_non_get! }
helpers do
params :optional_params_ce do
......@@ -56,16 +56,8 @@ module API
use :pagination
end
get do
groups = if params[:owned]
current_user.owned_groups
elsif current_user.admin
Group.all
elsif params[:all_available]
GroupsFinder.new(current_user).execute
else
current_user.groups
end
find_params = { all_available: params[:all_available], owned: params[:owned] }
groups = GroupsFinder.new(current_user, find_params).execute
groups = groups.search(params[:search]) if params[:search].present?
groups = groups.where.not(id: params[:skip_groups]) if params[:skip_groups].present?
groups = groups.reorder(params[:order_by] => params[:sort])
......
......@@ -40,4 +40,18 @@ feature 'Issue Detail', :js do
end
end
end
context 'when authored by a user who is later deleted' do
before do
issue.update_attribute(:author_id, nil)
sign_in(user)
visit project_issue_path(project, issue)
end
it 'shows the issue' do
page.within('.issuable-details') do
expect(find('h2')).to have_content(issue.title)
end
end
end
end
require 'spec_helper'
feature 'Find files button in the tree header' do
given(:user) { create(:user) }
given(:project) { create(:project, :repository) }
background do
sign_in(user)
project.team << [user, :developer]
end
scenario 'project main screen' do
visit project_path(project)
expect(page).to have_selector('.tree-controls .shortcuts-find-file')
end
scenario 'project tree screen' do
visit project_tree_path(project, project.default_branch)
expect(page).to have_selector('.tree-controls .shortcuts-find-file')
end
end
require 'spec_helper'
describe 'User searches for files' do
let(:user) { create(:user) }
let(:project) { create(:project, :repository) }
before do
sign_in(user)
end
describe 'project main screen' do
context 'when project is empty' do
let(:empty_project) { create(:project) }
before do
empty_project.add_developer(user)
visit project_path(empty_project)
end
it 'does not show any result' do
fill_in('search', with: 'coffee')
click_button('Go')
expect(page).to have_content("We couldn't find any")
end
end
context 'when project is not empty' do
before do
project.add_developer(user)
visit project_path(project)
end
it 'shows "Find file" button' do
expect(page).to have_selector('.tree-controls .shortcuts-find-file')
end
end
end
describe 'project tree screen' do
before do
project.add_developer(user)
visit project_tree_path(project, project.default_branch)
end
it 'shows "Find file" button' do
expect(page).to have_selector('.tree-controls .shortcuts-find-file')
end
it 'shows found files' do
fill_in('search', with: 'coffee')
click_button('Go')
expect(page).to have_content('coffee')
expect(page).to have_content('CONTRIBUTING.md')
end
end
end
......@@ -281,4 +281,30 @@ describe "Search" do
expect(page).to have_selector('.commit-row-description', count: 9)
end
end
context 'anonymous user' do
let(:project) { create(:project, :public) }
before do
sign_out(user)
end
it 'preserves the group being searched in' do
visit search_path(group_id: project.namespace.id)
fill_in 'search', with: 'foo'
click_button 'Search'
expect(find('#group_id').value).to eq(project.namespace.id.to_s)
end
it 'preserves the project being searched in' do
visit search_path(project_id: project.id)
fill_in 'search', with: 'foo'
click_button 'Search'
expect(find('#project_id').value).to eq(project.id.to_s)
end
end
end
......@@ -62,4 +62,67 @@ describe ButtonHelper do
end
end
end
describe 'clipboard_button' do
let(:user) { create(:user) }
let(:project) { build_stubbed(:project) }
def element(data = {})
element = helper.clipboard_button(data)
Nokogiri::HTML::DocumentFragment.parse(element).first_element_child
end
before do
allow(helper).to receive(:current_user).and_return(user)
end
context 'with default options' do
context 'when no `text` attribute is not provided' do
it 'shows copy to clipboard button with default configuration and no text set to copy' do
expect(element.attr('class')).to eq('btn btn-clipboard btn-transparent')
expect(element.attr('type')).to eq('button')
expect(element.attr('aria-label')).to eq('Copy to clipboard')
expect(element.attr('data-toggle')).to eq('tooltip')
expect(element.attr('data-placement')).to eq('bottom')
expect(element.attr('data-container')).to eq('body')
expect(element.attr('data-clipboard-text')).to eq(nil)
expect(element.inner_text).to eq("")
expect(element).to have_selector('.fa.fa-clipboard')
end
end
context 'when `text` attribute is provided' do
it 'shows copy to clipboard button with provided `text` to copy' do
expect(element(text: 'Hello World!').attr('data-clipboard-text')).to eq('Hello World!')
end
end
context 'when `title` attribute is provided' do
it 'shows copy to clipboard button with provided `title` as tooltip' do
expect(element(title: 'Copy to my clipboard!').attr('aria-label')).to eq('Copy to my clipboard!')
end
end
end
context 'with `button_text` attribute provided' do
it 'shows copy to clipboard button with provided `button_text` as button label' do
expect(element(button_text: 'Copy text').inner_text).to eq('Copy text')
end
end
context 'with `hide_tooltip` attribute provided' do
it 'shows copy to clipboard button without tooltip support' do
expect(element(hide_tooltip: true).attr('data-placement')).to eq(nil)
expect(element(hide_tooltip: true).attr('data-toggle')).to eq(nil)
expect(element(hide_tooltip: true).attr('data-container')).to eq(nil)
end
end
context 'with `hide_button_icon` attribute provided' do
it 'shows copy to clipboard button without tooltip support' do
expect(element(hide_button_icon: true)).not_to have_selector('.fa.fa-clipboard')
end
end
end
end
......@@ -17,7 +17,7 @@ describe('Api', () => {
beforeEach(() => {
originalGon = window.gon;
window.gon = dummyGon;
window.gon = Object.assign({}, dummyGon);
});
afterEach(() => {
......@@ -98,10 +98,11 @@ describe('Api', () => {
});
describe('projects', () => {
it('fetches projects', (done) => {
it('fetches projects with membership when logged in', (done) => {
const query = 'dummy query';
const options = { unused: 'option' };
const expectedUrl = `${dummyUrlRoot}/api/${dummyApiVersion}/projects.json?simple=true`;
window.gon.current_user_id = 1;
const expectedData = Object.assign({
search: query,
per_page: 20,
......@@ -119,6 +120,27 @@ describe('Api', () => {
done();
});
});
it('fetches projects without membership when not logged in', (done) => {
const query = 'dummy query';
const options = { unused: 'option' };
const expectedUrl = `${dummyUrlRoot}/api/${dummyApiVersion}/projects.json?simple=true`;
const expectedData = Object.assign({
search: query,
per_page: 20,
}, options);
spyOn(jQuery, 'ajax').and.callFake((request) => {
expect(request.url).toEqual(expectedUrl);
expect(request.dataType).toEqual('json');
expect(request.data).toEqual(expectedData);
return sendDummyResponse();
});
Api.projects(query, options, (response) => {
expect(response).toBe(dummyResponse);
done();
});
});
});
describe('newLabel', () => {
......
......@@ -7,6 +7,7 @@ import '~/project_select';
import '~/project';
describe('Project Title', () => {
const dummyApiVersion = 'v3000';
preloadFixtures('issues/open-issue.html.raw');
loadJSONFixtures('projects.json');
......@@ -14,7 +15,7 @@ describe('Project Title', () => {
loadFixtures('issues/open-issue.html.raw');
window.gon = {};
window.gon.api_version = 'v3';
window.gon.api_version = dummyApiVersion;
// eslint-disable-next-line no-new
new Project();
......@@ -37,9 +38,10 @@ describe('Project Title', () => {
it('toggles dropdown', () => {
const $menu = $('.js-dropdown-menu-projects');
window.gon.current_user_id = 1;
$('.js-projects-dropdown-toggle').click();
expect($menu).toHaveClass('open');
expect(reqUrl).toBe('/api/v3/projects.json?simple=true');
expect(reqUrl).toBe(`/api/${dummyApiVersion}/projects.json?simple=true`);
expect(reqData).toEqual({
search: '',
order_by: 'last_activity_at',
......
......@@ -12,7 +12,7 @@ describe MigrateStagesStatuses, :migration do
before do
stub_const("#{described_class.name}::BATCH_SIZE", 2)
stub_const("#{described_class.name}::RANGE_SIZE", 2)
stub_const("#{described_class.name}::RANGE_SIZE", 1)
projects.create!(id: 1, name: 'gitlab1', path: 'gitlab1')
projects.create!(id: 2, name: 'gitlab2', path: 'gitlab2')
......@@ -50,9 +50,10 @@ describe MigrateStagesStatuses, :migration do
Timecop.freeze do
migrate!
expect(described_class::MIGRATION).to be_scheduled_migration(5.minutes, 1, 2)
expect(described_class::MIGRATION).to be_scheduled_migration(5.minutes, 1, 1)
expect(described_class::MIGRATION).to be_scheduled_migration(5.minutes, 2, 2)
expect(described_class::MIGRATION).to be_scheduled_migration(10.minutes, 3, 3)
expect(BackgroundMigrationWorker.jobs.size).to eq 2
expect(BackgroundMigrationWorker.jobs.size).to eq 3
end
end
end
......
......@@ -21,10 +21,15 @@ describe API::Groups do
describe "GET /groups" do
context "when unauthenticated" do
it "returns authentication error" do
it "returns public groups" do
get api("/groups")
expect(response).to have_http_status(401)
expect(response).to have_http_status(200)
expect(response).to include_pagination_headers
expect(json_response).to be_an Array
expect(json_response.length).to eq(1)
expect(json_response)
.to satisfy_one { |group| group['name'] == group1.name }
end
end
......@@ -179,6 +184,18 @@ describe API::Groups do
end
describe "GET /groups/:id" do
context 'when unauthenticated' do
it 'returns 404 for a private group' do
get api("/groups/#{group2.id}")
expect(response).to have_http_status(404)
end
it 'returns 200 for a public group' do
get api("/groups/#{group1.id}")
expect(response).to have_http_status(200)
end
end
context "when authenticated as user" do
it "returns one of user1's groups" do
project = create(:project, namespace: group2, path: 'Foo')
......
......@@ -37,7 +37,10 @@ describe Users::UpdateService do
describe '#execute!' do
it 'updates the name' do
result = update_user(user, name: 'New Name')
service = described_class.new(user, name: 'New Name')
expect(service).not_to receive(:notify_new_user)
result = service.execute!
expect(result).to be true
expect(user.name).to eq('New Name')
......@@ -49,6 +52,18 @@ describe Users::UpdateService do
end.to raise_error(ActiveRecord::RecordInvalid)
end
it 'fires system hooks when a new user is saved' do
system_hook_service = spy(:system_hook_service)
user = build(:user)
service = described_class.new(user, name: 'John Doe')
expect(service).to receive(:notify_new_user).and_call_original
expect(service).to receive(:system_hook_service).and_return(system_hook_service)
service.execute
expect(system_hook_service).to have_received(:execute_hooks_for).with(user, :create)
end
def update_user(user, opts)
described_class.new(user, opts).execute!
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