Commit 15ace6a9 authored by Grzegorz Bizon's avatar Grzegorz Bizon

Merge commit '2be34630' into...

Merge commit '2be34630' into backstage/gb/rename-ci-cd-processing-sidekiq-queues

* commit '2be34630':
  Common Docker Documentation Location in `gitlab-ce`
  fix deprecation warning present during webpack compiles
  Enable 5 lines of Sidekiq backtrace lines to aid in debugging
  Add support for copying permalink to notes via more actions dropdown
  Handle creating a nested group on MySQL correctly
  Decrease statuses batch size even more in a migration
  Fix repo editor scrollbar
  Replace 'source/search_code.feature' spinach test with an rspec analog
  Authorizations regarding OAuth - style confirmation
  Update README.md
  Refactor complicated API group finding rules into GroupsFinder
  Fix group and project search for anonymous users
  Document version Group Milestones API introduced
  Allow v4 API GET requests for groups to be unauthenticated
  Adjust a range and a size in stages statuses migration
  Update README.md
  Point to /developers on docs/administration/authentiq.md
  Indexes GFM markdown guide
  use inline links instead of referenced
  Add index on ci_runners.contacted_at
parents e984a8a3 2be34630
......@@ -55,13 +55,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)
......
......@@ -15,6 +15,10 @@ module Groups
return group
end
if group_path.include?('/') && !Group.supports_nested_groups?
raise 'Nested groups are not supported on MySQL'
end
create_group_path
end
......
%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"
......@@ -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,5 +11,5 @@
%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"
# 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: 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
......@@ -170,7 +170,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
......
......@@ -86,6 +86,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.
---
......
......@@ -113,13 +113,12 @@ merge request.
## Links
- If a link makes the paragraph to span across multiple lines, do not use
the regular Markdown approach: `[Text](https://example.com)`. Instead use
`[Text][identifier]` and at the very bottom of the document add:
`[identifier]: https://example.com`. This is another way to create Markdown
links which keeps the document clear and concise. Bonus points if you also
add an alternative text: `[identifier]: https://example.com "Alternative text"`
that appears when hovering your mouse on a link
- Use the regular inline link markdown markup `[Text](https://example.com)`.
It's easier to read, review, and maintain.
- If there's a link that repeats several times through the same document,
you can use `[Text][identifier]` and at the bottom of the section or the
document add: `[identifier]: https://example.com`, in which case, we do
encourage you to also add an alternative text: `[identifier]: https://example.com "Alternative text"` that appears when hovering your mouse on a link.
### Linking to inline docs
......
......@@ -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
......
# 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).
......@@ -119,6 +119,13 @@ When performing inline reviews to implementations
to your codebase through merge requests you can
gather feedback through [resolvable discussions](discussions/index.md#resolvable-discussions).
### GitLab Flavored Markdown (GFM)
Read through the [GFM documentation](markdown.md) to learn how to apply
the best of GitLab Flavored Markdown in your discussions, comments,
issues and merge requests descriptions, and everywhere else GMF is
supported.
## Todos
Never forget to reply to your collaborators. [GitLab Todos](../workflow/todos.md)
......
# 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
......@@ -304,10 +304,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
......@@ -47,16 +47,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])
......
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',
......
......@@ -2,67 +2,99 @@ require 'spec_helper'
describe Gitlab::BareRepositoryImporter, repository: true do
subject(:importer) { described_class.new('default', project_path) }
let(:project_path) { 'a-group/a-sub-group/a-project' }
let!(:admin) { create(:admin) }
before do
allow(described_class).to receive(:log)
end
describe '.execute' do
it 'creates a project for a repository in storage' do
FileUtils.mkdir_p(File.join(TestEnv.repos_path, "#{project_path}.git"))
fake_importer = double
shared_examples 'importing a repository' do
describe '.execute' do
it 'creates a project for a repository in storage' do
FileUtils.mkdir_p(File.join(TestEnv.repos_path, "#{project_path}.git"))
fake_importer = double
expect(described_class).to receive(:new).with('default', project_path)
.and_return(fake_importer)
expect(fake_importer).to receive(:create_project_if_needed)
expect(described_class).to receive(:new).with('default', project_path)
.and_return(fake_importer)
expect(fake_importer).to receive(:create_project_if_needed)
described_class.execute
end
described_class.execute
end
it 'skips wiki repos' do
FileUtils.mkdir_p(File.join(TestEnv.repos_path, 'the-group', 'the-project.wiki.git'))
it 'skips wiki repos' do
FileUtils.mkdir_p(File.join(TestEnv.repos_path, 'the-group', 'the-project.wiki.git'))
expect(described_class).to receive(:log).with(' * Skipping wiki repo')
expect(described_class).not_to receive(:new)
expect(described_class).to receive(:log).with(' * Skipping wiki repo')
expect(described_class).not_to receive(:new)
described_class.execute
described_class.execute
end
end
end
describe '#initialize' do
context 'without admin users' do
let(:admin) { nil }
describe '#initialize' do
context 'without admin users' do
let(:admin) { nil }
it 'raises an error' do
expect { importer }.to raise_error(Gitlab::BareRepositoryImporter::NoAdminError)
it 'raises an error' do
expect { importer }.to raise_error(Gitlab::BareRepositoryImporter::NoAdminError)
end
end
end
end
describe '#create_project_if_needed' do
it 'starts an import for a project that did not exist' do
expect(importer).to receive(:create_project)
describe '#create_project_if_needed' do
it 'starts an import for a project that did not exist' do
expect(importer).to receive(:create_project)
importer.create_project_if_needed
end
it 'skips importing when the project already exists' do
project = create(:project, path: 'a-project', namespace: existing_group)
expect(importer).not_to receive(:create_project)
expect(importer).to receive(:log).with(" * #{project.name} (#{project_path}) exists")
importer.create_project_if_needed
end
it 'creates a project with the correct path in the database' do
importer.create_project_if_needed
importer.create_project_if_needed
expect(Project.find_by_full_path(project_path)).not_to be_nil
end
end
end
context 'with subgroups', :nested_groups do
let(:project_path) { 'a-group/a-sub-group/a-project' }
it 'skips importing when the project already exists' do
let(:existing_group) do
group = create(:group, path: 'a-group')
subgroup = create(:group, path: 'a-sub-group', parent: group)
project = create(:project, path: 'a-project', namespace: subgroup)
create(:group, path: 'a-sub-group', parent: group)
end
expect(importer).not_to receive(:create_project)
expect(importer).to receive(:log).with(" * #{project.name} (a-group/a-sub-group/a-project) exists")
it_behaves_like 'importing a repository'
end
importer.create_project_if_needed
end
context 'without subgroups' do
let(:project_path) { 'a-group/a-project' }
let(:existing_group) { create(:group, path: 'a-group') }
it 'creates a project with the correct path in the database' do
importer.create_project_if_needed
it_behaves_like 'importing a repository'
end
context 'when subgroups are not available' do
let(:project_path) { 'a-group/a-sub-group/a-project' }
before do
expect(Group).to receive(:supports_nested_groups?) { false }
end
expect(Project.find_by_full_path(project_path)).not_to be_nil
describe '#create_project_if_needed' do
it 'raises an error' do
expect { importer.create_project_if_needed }.to raise_error('Nested groups are not supported on MySQL')
end
end
end
end
......@@ -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
......
......@@ -20,10 +20,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
......@@ -165,6 +170,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')
......
......@@ -2,52 +2,87 @@ require 'spec_helper'
describe Groups::NestedCreateService do
let(:user) { create(:user) }
let(:params) { { group_path: 'a-group/a-sub-group' } }
subject(:service) { described_class.new(user, params) }
describe "#execute" do
it 'returns the group if it already existed' do
parent = create(:group, path: 'a-group', owner: user)
child = create(:group, path: 'a-sub-group', parent: parent, owner: user)
shared_examples 'with a visibility level' do
it 'creates the group with correct visibility level' do
allow(Gitlab::CurrentSettings.current_application_settings)
.to receive(:default_group_visibility) { Gitlab::VisibilityLevel::INTERNAL }
group = service.execute
expect(service.execute).to eq(child)
expect(group.visibility_level).to eq(Gitlab::VisibilityLevel::INTERNAL)
end
it 'reuses a parent if it already existed', :nested_groups do
parent = create(:group, path: 'a-group')
parent.add_owner(user)
context 'adding a visibility level ' do
it 'overwrites the visibility level' do
service = described_class.new(user, params.merge(visibility_level: Gitlab::VisibilityLevel::PRIVATE))
group = service.execute
expect(service.execute.parent).to eq(parent)
expect(group.visibility_level).to eq(Gitlab::VisibilityLevel::PRIVATE)
end
end
end
describe 'without subgroups' do
let(:params) { { group_path: 'a-group' } }
it 'creates group and subgroup in the database', :nested_groups do
service.execute
before do
allow(Group).to receive(:supports_nested_groups?) { false }
end
parent = Group.find_by_full_path('a-group')
child = parent.children.find_by(path: 'a-sub-group')
it 'creates the group' do
group = service.execute
expect(parent).not_to be_nil
expect(child).not_to be_nil
expect(group).to be_persisted
end
it 'creates the group with correct visibility level' do
allow(Gitlab::CurrentSettings.current_application_settings)
.to receive(:default_group_visibility) { Gitlab::VisibilityLevel::INTERNAL }
it 'returns the group if it already existed' do
existing_group = create(:group, path: 'a-group')
group = service.execute
expect(service.execute).to eq(existing_group)
end
expect(group.visibility_level).to eq(Gitlab::VisibilityLevel::INTERNAL)
it 'raises an error when tring to create a subgroup' do
service = described_class.new(user, group_path: 'a-group/a-sub-group')
expect { service.execute }.to raise_error('Nested groups are not supported on MySQL')
end
context 'adding a visibility level ' do
let(:params) { { group_path: 'a-group/a-sub-group', visibility_level: Gitlab::VisibilityLevel::PRIVATE } }
it_behaves_like 'with a visibility level'
end
it 'overwrites the visibility level' do
group = service.execute
describe 'with subgroups', :nested_groups do
let(:params) { { group_path: 'a-group/a-sub-group' } }
expect(group.visibility_level).to eq(Gitlab::VisibilityLevel::PRIVATE)
describe "#execute" do
it 'returns the group if it already existed' do
parent = create(:group, path: 'a-group', owner: user)
child = create(:group, path: 'a-sub-group', parent: parent, owner: user)
expect(service.execute).to eq(child)
end
it 'reuses a parent if it already existed' do
parent = create(:group, path: 'a-group')
parent.add_owner(user)
expect(service.execute.parent).to eq(parent)
end
it 'creates group and subgroup in the database' do
service.execute
parent = Group.find_by_full_path('a-group')
child = parent.children.find_by(path: 'a-sub-group')
expect(parent).not_to be_nil
expect(child).not_to be_nil
end
it_behaves_like 'with a visibility level'
end
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