Commit e6aa1c52 authored by Bryce Johnson's avatar Bryce Johnson

Merge branch 'master' into repository-page-ui-issues

parents db5244ab 433d8a10
See the general Documentation guidelines http://docs.gitlab.com/ce/development/doc_styleguide.html.
## What does this MR do?
(briefly describe what this MR is about)
## Moving docs to a new location?
See the guidelines: http://docs.gitlab.com/ce/development/doc_styleguide.html#changing-document-location
- [ ] Make sure the old link is not removed and has its contents replaced with a link to the new location.
- [ ] Make sure internal links pointing to the document in question are not broken.
- [ ] Search and replace any links referring to old docs in GitLab Rails app, specifically under the `app/views/` directory.
- [ ] If working on CE, submit an MR to EE with the changes as well.
Please view this file on the master branch, on stable branches it's out of date.
v 8.12.0 (unreleased)
- Filter tags by name !6121
- Make push events have equal vertical spacing.
- Add two-factor recovery endpoint to internal API !5510
- Remove vendor prefixes for linear-gradient CSS (ClemMakesApps)
......@@ -11,15 +12,18 @@ v 8.12.0 (unreleased)
- Reduce contributions calendar data payload (ClemMakesApps)
- Add `web_url` field to issue, merge request, and snippet API objects (Ben Boeckel)
- Set path for all JavaScript cookies to honor GitLab's subdirectory setting !5627 (Mike Greiling)
- Fix bug where pagination is still displayed despite all todos marked as done (ClemMakesApps)
- Shorten task status phrase (ClemMakesApps)
- Add hover color to emoji icon (ClemMakesApps)
- Fix branches page dropdown sort alignment (ClemMakesApps)
- Add white background for no readme container (ClemMakesApps)
- API: Expose issue confidentiality flag. (Robert Schilling)
- Optimistic locking for Issues and Merge Requests (title and description overriding prevention)
- Add `wiki_page_events` to project hook APIs (Ben Boeckel)
- Remove Gitorious import
- Fix inconsistent background color for filter input field (ClemMakesApps)
- Add Sentry logging to API calls
- Add BroadcastMessage API
- Automatically expand hidden discussions when accessed by a permalink !5585 (Mike Greiling)
- Remove unused mixins (ClemMakesApps)
- Add search to all issue board lists
......@@ -29,11 +33,14 @@ v 8.12.0 (unreleased)
- Add last commit time to repo view (ClemMakesApps)
- Added project specific enable/disable setting for LFS !5997
- Don't expose a user's token in the `/api/v3/user` API (!6047)
- Remove redundant js-timeago-pending from user activity log (ClemMakesApps)
- Added tests for diff notes
- Add a button to download latest successful artifacts for branches and tags !5142
- Remove redundant pipeline tooltips (ClemMakesApps)
- Expire commit info views after one day, instead of two weeks, to allow for user email updates
- Add delimiter to project stars and forks count (ClemMakesApps)
- Fix badge count alignment (ClemMakesApps)
- Fix repo title alignment (ClemMakesApps)
- Fix branch title trailing space on hover (ClemMakesApps)
- Award emoji tooltips containing more than 10 usernames are now truncated !4780 (jlogandavison)
- Fix duplicate "me" in award emoji tooltip !5218 (jlogandavison)
......@@ -53,17 +60,18 @@ v 8.12.0 (unreleased)
- Fix hover leading space bug in pipeline graph !5980
- User can edit closed MR with deleted fork (Katarzyna Kobierska Ula Budziszewska) !5496
- Fix repository page ui issues
- Fixed invisible scroll controls on build page on iPhone
v 8.11.4 (unreleased)
- Fix broken gitlab:backup:restore because of bad permissions on repo storage !6098 (Dirk Hörner)
- Fix sorting issues by "last updated" doesn't work after import from GitHub
- GitHub importer use default project visibility for non-private projects
- Creating an issue through our API now emails label subscribers !5720
- Block concurrent updates for Pipeline
- Fix resolving conflicts on forks
- Fix diff commenting on merge requests created prior to 8.10
v 8.11.4 (unreleased)
- Fix issue boards leak private label names and descriptions
v 8.11.3 (unreleased)
v 8.11.3
- Do not enforce using hash with hidden key in CI configuration. !6079
- Allow system info page to handle case where info is unavailable
......
......@@ -97,9 +97,6 @@ gem 'fog-rackspace', '~> 0.1.1'
# for aws storage
gem 'unf', '~> 0.1.4'
# Authorization
gem 'six', '~> 0.2.0'
# Seed data
gem 'seed-fu', '~> 2.3.5'
......
......@@ -683,7 +683,6 @@ GEM
rack (~> 1.5)
rack-protection (~> 1.4)
tilt (>= 1.3, < 3)
six (0.2.0)
slack-notifier (1.2.1)
slop (3.6.0)
spinach (0.8.10)
......@@ -954,7 +953,6 @@ DEPENDENCIES
sidekiq-cron (~> 0.4.0)
simplecov (= 0.12.0)
sinatra (~> 1.4.4)
six (~> 0.2.0)
slack-notifier (~> 1.2.0)
spinach-rails (~> 0.2.1)
spinach-rerun-reporter (~> 0.0.2)
......
......@@ -50,7 +50,7 @@ etc.).
The most important thing is making sure valid issues receive feedback from the
development team. Therefore the priority is mentioning developers that can help
on those issue. Please select someone with relevant experience from
on those issues. Please select someone with relevant experience from
[GitLab core team][core-team]. If there is nobody mentioned with that expertise
look in the commit history for the affected files to find someone. Avoid
mentioning the lead developer, this is the person that is least likely to give a
......
......@@ -12,7 +12,7 @@
}
Activities.prototype.updateTooltips = function() {
return gl.utils.localTimeAgo($('.js-timeago', '#activity'));
return gl.utils.localTimeAgo($('.js-timeago', '.content_list'));
};
Activities.prototype.reloadActivities = function() {
......
(function(w) {
$(function() {
$('.js-toggle-button').on('click', function(e) {
$('body').on('click', '.js-toggle-button', function(e) {
e.preventDefault();
$(this)
.find('.fa')
......
......@@ -556,7 +556,7 @@
if (isInput) {
field = $(this.el);
} else {
field = this.dropdown.parent().find("input[name='" + fieldName + "'][value='" + value + "']");
field = this.dropdown.parent().find("input[name='" + fieldName + "'][value='" + escape(value) + "']");
}
if (el.hasClass(ACTIVE_CLASS)) {
el.removeClass(ACTIVE_CLASS);
......
......@@ -164,7 +164,7 @@
instance.addInput(this.fieldName, label.id);
}
}
if ($form.find("input[type='hidden'][name='" + ($dropdown.data('fieldName')) + "'][value='" + (this.id(label)) + "']").length) {
if ($form.find("input[type='hidden'][name='" + ($dropdown.data('fieldName')) + "'][value='" + escape(this.id(label)) + "']").length) {
selectedClass.push('is-active');
}
if ($dropdown.hasClass('js-multiselect') && removesAll) {
......
(function() {
Turbolinks.enableProgressBar();
start = function() {
$(document).on('page:fetch', function() {
$('.tanuki-logo').addClass('animate');
};
});
stop = function() {
$(document).on('page:change', function() {
$('.tanuki-logo').removeClass('animate');
};
$(document).on('page:fetch', start);
$(document).on('page:change', stop);
});
}).call(this);
......@@ -66,7 +66,7 @@
success: (function(_this) {
return function(data) {
$this.remove();
$('.js-todos-list').remove();
$('.prepend-top-default').html('<div class="nothing-here-block">You\'re all done!</div>');
return _this.updateBadges(data);
};
})(this)
......
......@@ -84,7 +84,7 @@ header {
.side-nav-toggle {
position: absolute;
left: -10px;
margin: 6px 0;
margin: 7px 0;
font-size: 18px;
padding: 6px 10px;
border: none;
......
......@@ -36,6 +36,7 @@
&.affix {
right: 30px;
bottom: 15px;
z-index: 1;
@media (min-width: $screen-md-min) {
right: 26%;
......
......@@ -24,7 +24,7 @@ class ApplicationController < ActionController::Base
protect_from_forgery with: :exception
helper_method :abilities, :can?, :current_application_settings
helper_method :can?, :current_application_settings
helper_method :import_sources_enabled?, :github_import_enabled?, :github_import_configured?, :gitlab_import_enabled?, :gitlab_import_configured?, :bitbucket_import_enabled?, :bitbucket_import_configured?, :google_code_import_enabled?, :fogbugz_import_enabled?, :git_import_enabled?, :gitlab_project_import_enabled?
rescue_from Encoding::CompatibilityError do |exception|
......@@ -97,12 +97,8 @@ class ApplicationController < ActionController::Base
current_application_settings.after_sign_out_path.presence || new_user_session_path
end
def abilities
Ability.abilities
end
def can?(object, action, subject)
abilities.allowed?(object, action, subject)
Ability.allowed?(object, action, subject)
end
def access_denied!
......
......@@ -14,7 +14,7 @@ class NamespacesController < ApplicationController
if user
redirect_to user_path(user)
elsif group && can?(current_user, :read_group, namespace)
elsif group && can?(current_user, :read_group, group)
redirect_to group_path(group)
elsif current_user.nil?
authenticate_user!
......
class Projects::TagsController < Projects::ApplicationController
include SortingHelper
# Authorize
before_action :require_non_empty_project
before_action :authorize_download_code!
......@@ -6,8 +8,10 @@ class Projects::TagsController < Projects::ApplicationController
before_action :authorize_admin_project!, only: [:destroy]
def index
@sort = params[:sort] || 'name'
@tags = @repository.tags_sorted_by(@sort)
params[:sort] = params[:sort].presence || 'name'
@sort = params[:sort]
@tags = TagsFinder.new(@repository, params).execute
@tags = Kaminari.paginate_array(@tags).page(params[:page])
@releases = project.releases.where(tag: @tags.map(&:name))
......
......@@ -64,7 +64,7 @@ class IssuableFinder
if project?
@project = Project.find(params[:project_id])
unless Ability.abilities.allowed?(current_user, :read_project, @project)
unless Ability.allowed?(current_user, :read_project, @project)
@project = nil
end
else
......
class TagsFinder
def initialize(repository, params)
@repository = repository
@params = params
end
def execute
tags = @repository.tags_sorted_by(sort)
filter_by_name(tags)
end
private
def sort
@params[:sort].presence
end
def search
@params[:search].presence
end
def filter_by_name(tags)
if search
tags.select { |tag| tag.name.include?(search) }
else
tags
end
end
end
......@@ -83,7 +83,7 @@ class TodosFinder
if project?
@project = Project.find(params[:project_id])
unless Ability.abilities.allowed?(current_user, :read_project, @project)
unless Ability.allowed?(current_user, :read_project, @project)
@project = nil
end
else
......
module SentryHelper
def sentry_enabled?
Rails.env.production? && current_application_settings.sentry_enabled?
Gitlab::Sentry.enabled?
end
def sentry_context
return unless sentry_enabled?
if current_user
Raven.user_context(
id: current_user.id,
email: current_user.email,
username: current_user.username,
)
end
Raven.tags_context(program: sentry_program_context)
end
def sentry_program_context
if Sidekiq.server?
'sidekiq'
else
'rails'
end
Gitlab::Sentry.context(current_user)
end
end
......@@ -3,6 +3,16 @@ module TagsHelper
"/tags/#{tag}"
end
def filter_tags_path(options = {})
exist_opts = {
search: params[:search],
sort: params[:sort]
}
options = exist_opts.merge(options)
namespace_project_tags_path(@project.namespace, @project, @id, options)
end
def tag_list(project)
html = ''
project.tag_list.each do |tag|
......
......@@ -9,7 +9,7 @@ class BaseMailer < ActionMailer::Base
default reply_to: Proc.new { default_reply_to_address.format }
def can?
Ability.abilities.allowed?(current_user, action, subject)
Ability.allowed?(current_user, action, subject)
end
private
......
This diff is collapsed.
......@@ -65,7 +65,7 @@ class Event < ActiveRecord::Base
elsif created_project?
true
elsif issue? || issue_note?
Ability.abilities.allowed?(user, :read_issue, note? ? note_target : target)
Ability.allowed?(user, :read_issue, note? ? note_target : target)
else
((merge_request? || note?) && target.present?) || milestone?
end
......
......@@ -411,7 +411,7 @@ class MergeRequest < ActiveRecord::Base
def can_remove_source_branch?(current_user)
!source_project.protected_branch?(source_branch) &&
!source_project.root_ref?(source_branch) &&
Ability.abilities.allowed?(current_user, :push_code, source_project) &&
Ability.allowed?(current_user, :push_code, source_project) &&
diff_head_commit == source_branch_head
end
......
......@@ -460,16 +460,12 @@ class User < ActiveRecord::Base
can?(:create_group, nil)
end
def abilities
Ability.abilities
end
def can_select_namespace?
several_namespaces? || admin
end
def can?(action, subject)
abilities.allowed?(self, action, subject)
Ability.allowed?(self, action, subject)
end
def first_name
......
class BasePolicy
class RuleSet
attr_reader :can_set, :cannot_set
def initialize(can_set, cannot_set)
@can_set = can_set
@cannot_set = cannot_set
end
def size
to_set.size
end
def self.empty
new(Set.new, Set.new)
end
def can?(ability)
@can_set.include?(ability) && !@cannot_set.include?(ability)
end
def include?(ability)
can?(ability)
end
def to_set
@can_set - @cannot_set
end
def merge(other)
@can_set.merge(other.can_set)
@cannot_set.merge(other.cannot_set)
end
def can!(*abilities)
@can_set.merge(abilities)
end
def cannot!(*abilities)
@cannot_set.merge(abilities)
end
def freeze
@can_set.freeze
@cannot_set.freeze
super
end
end
def self.abilities(user, subject)
new(user, subject).abilities
end
def self.class_for(subject)
return GlobalPolicy if subject.nil?
subject.class.ancestors.each do |klass|
next unless klass.name
begin
policy_class = "#{klass.name}Policy".constantize
# NOTE: the < operator here tests whether policy_class
# inherits from BasePolicy
return policy_class if policy_class < BasePolicy
rescue NameError
nil
end
end
raise "no policy for #{subject.class.name}"
end
attr_reader :user, :subject
def initialize(user, subject)
@user = user
@subject = subject
end
def abilities
return RuleSet.empty if @user && @user.blocked?
return anonymous_abilities if @user.nil?
collect_rules { rules }
end
def anonymous_abilities
collect_rules { anonymous_rules }
end
def anonymous_rules
rules
end
def delegate!(new_subject)
@rule_set.merge(Ability.allowed(@user, new_subject))
end
def can?(rule)
@rule_set.can?(rule)
end
def can!(*rules)
@rule_set.can!(*rules)
end
def cannot!(*rules)
@rule_set.cannot!(*rules)
end
private
def collect_rules(&b)
@rule_set = RuleSet.empty
yield
@rule_set
end
end
module Ci
class BuildPolicy < CommitStatusPolicy
def rules
super
# If we can't read build we should also not have that
# ability when looking at this in context of commit_status
%w(read create update admin).each do |rule|
cannot! :"#{rule}_commit_status" unless can? :"#{rule}_build"
end
end
end
end
module Ci
class RunnerPolicy < BasePolicy
def rules
return unless @user
can! :assign_runner if @user.is_admin?
return if @subject.is_shared? || @subject.locked?
can! :assign_runner if @user.ci_authorized_runners.include?(@subject)
end
end
end
class CommitStatusPolicy < BasePolicy
def rules
delegate! @subject.project
end
end
class DeploymentPolicy < BasePolicy
def rules
delegate! @subject.project
end
end
class EnvironmentPolicy < BasePolicy
def rules
delegate! @subject.project
end
end
class ExternalIssuePolicy < BasePolicy
def rules
delegate! @subject.project
end
end
class GlobalPolicy < BasePolicy
def rules
return unless @user
can! :create_group if @user.can_create_group
can! :read_users_list
end
end
class GroupMemberPolicy < BasePolicy
def rules
return unless @user
target_user = @subject.user
group = @subject.group
return if group.last_owner?(target_user)
can_manage = Ability.allowed?(@user, :admin_group_member, group)
if can_manage
can! :update_group_member
can! :destroy_group_member
elsif @user == target_user
can! :destroy_group_member
end
end
end
class GroupPolicy < BasePolicy
def rules
can! :read_group if @subject.public?
return unless @user
globally_viewable = @subject.public? || (@subject.internal? && !@user.external?)
member = @subject.users.include?(@user)
owner = @user.admin? || @subject.has_owner?(@user)
master = owner || @subject.has_master?(@user)
can_read = false
can_read ||= globally_viewable
can_read ||= member
can_read ||= @user.admin?
can_read ||= GroupProjectsFinder.new(@subject).execute(@user).any?
can! :read_group if can_read
# Only group masters and group owners can create new projects
if master
can! :create_projects
can! :admin_milestones
end
# Only group owner and administrators can admin group
if owner
can! :admin_group
can! :admin_namespace
can! :admin_group_member
can! :change_visibility_level
end
if globally_viewable && @subject.request_access_enabled && !member
can! :request_access
end
end
def can_read_group?
return true if @subject.public?
return true if @user.admin?
return true if @subject.internal? && !@user.external?
return true if @subject.users.include?(@user)
GroupProjectsFinder.new(@subject).execute(@user).any?
end
end
class IssuablePolicy < BasePolicy
def action_name
@subject.class.name.underscore
end
def rules
if @user && (@subject.author == @user || @subject.assignee == @user)
can! :"read_#{action_name}"
can! :"update_#{action_name}"
end
delegate! @subject.project
end
end
class IssuePolicy < IssuablePolicy
def issue
@subject
end
def rules
super
if @subject.confidential? && !can_read_confidential?
cannot! :read_issue
cannot! :admin_issue
cannot! :update_issue
cannot! :read_issue
end
end
private
def can_read_confidential?
return false unless @user
return true if @user.admin?
return true if @subject.author == @user
return true if @subject.assignee == @user
return true if @subject.project.team.member?(@user, Gitlab::Access::REPORTER)
false
end
end
class MergeRequestPolicy < IssuablePolicy
# pass
end
class NamespacePolicy < BasePolicy
def rules
return unless @user
if @subject.owner == @user || @user.admin?
can! :create_projects
can! :admin_namespace
end
end
end
class NotePolicy < BasePolicy
def rules
delegate! @subject.project
return unless @user
if @subject.author == @user
can! :read_note
can! :update_note
can! :admin_note
can! :resolve_note
end
if @subject.for_merge_request? &&
@subject.noteable.author == @user
can! :resolve_note
end
end
end
class PersonalSnippetPolicy < BasePolicy
def rules
can! :read_personal_snippet if @subject.public?
return unless @user
if @subject.author == @user
can! :read_personal_snippet
can! :update_personal_snippet
can! :admin_personal_snippet
end
if @subject.internal? && !@user.external?
can! :read_personal_snippet
end
end
end
class ProjectMemberPolicy < BasePolicy
def rules
# anonymous users have no abilities here
return unless @user
target_user = @subject.user
project = @subject.project
return if target_user == project.owner
can_manage = Ability.allowed?(@user, :admin_project_member, project)
if can_manage
can! :update_project_member
can! :destroy_project_member
end
if @user == target_user
can! :destroy_project_member
end
end
end
class ProjectPolicy < BasePolicy
def rules
team_access!(user)
owner = user.admin? ||
project.owner == user ||
(project.group && project.group.has_owner?(user))
owner_access! if owner
if project.public? || (project.internal? && !user.external?)
guest_access!
public_access!
# Allow to read builds for internal projects
can! :read_build if project.public_builds?
if project.request_access_enabled &&
!(owner || project.team.member?(user) || project_group_member?(user))
can! :request_access
end
end
archived_access! if project.archived?
disabled_features!
end
def project
@subject
end
def guest_access!
can! :read_project
can! :read_board
can! :read_list
can! :read_wiki
can! :read_issue
can! :read_label
can! :read_milestone
can! :read_project_snippet
can! :read_project_member
can! :read_merge_request
can! :read_note
can! :create_project
can! :create_issue
can! :create_note
can! :upload_file
end
def reporter_access!
can! :download_code
can! :fork_project
can! :create_project_snippet
can! :update_issue
can! :admin_issue
can! :admin_label
can! :admin_list
can! :read_commit_status
can! :read_build
can! :read_container_image
can! :read_pipeline
can! :read_environment
can! :read_deployment
end
def developer_access!
can! :admin_merge_request
can! :update_merge_request
can! :create_commit_status
can! :update_commit_status
can! :create_build
can! :update_build
can! :create_pipeline
can! :update_pipeline
can! :create_merge_request
can! :create_wiki
can! :push_code
can! :resolve_note
can! :create_container_image
can! :update_container_image
can! :create_environment
can! :create_deployment
end
def master_access!
can! :push_code_to_protected_branches
can! :update_project_snippet
can! :update_environment
can! :update_deployment
can! :admin_milestone
can! :admin_project_snippet
can! :admin_project_member
can! :admin_merge_request
can! :admin_note
can! :admin_wiki
can! :admin_project
can! :admin_commit_status
can! :admin_build
can! :admin_container_image
can! :admin_pipeline
can! :admin_environment
can! :admin_deployment
end
def public_access!
can! :download_code
can! :fork_project
can! :read_commit_status
can! :read_pipeline
can! :read_container_image
end
def owner_access!
guest_access!
reporter_access!
developer_access!
master_access!
can! :change_namespace
can! :change_visibility_level
can! :rename_project
can! :remove_project
can! :archive_project
can! :remove_fork_project
can! :destroy_merge_request
can! :destroy_issue
end
# Push abilities on the users team role
def team_access!(user)
access = project.team.max_member_access(user.id)
guest_access! if access >= Gitlab::Access::GUEST
reporter_access! if access >= Gitlab::Access::REPORTER
developer_access! if access >= Gitlab::Access::DEVELOPER
master_access! if access >= Gitlab::Access::MASTER
end
def archived_access!
cannot! :create_merge_request
cannot! :push_code
cannot! :push_code_to_protected_branches
cannot! :update_merge_request
cannot! :admin_merge_request
end
def disabled_features!
unless project.issues_enabled
cannot!(*named_abilities(:issue))
end
unless project.merge_requests_enabled
cannot!(*named_abilities(:merge_request))
end
unless project.issues_enabled || project.merge_requests_enabled
cannot!(*named_abilities(:label))
cannot!(*named_abilities(:milestone))
end
unless project.snippets_enabled
cannot!(*named_abilities(:project_snippet))
end
unless project.has_wiki?
cannot!(*named_abilities(:wiki))
end
unless project.builds_enabled
cannot!(*named_abilities(:build))
cannot!(*named_abilities(:pipeline))
cannot!(*named_abilities(:environment))
cannot!(*named_abilities(:deployment))
end
unless project.container_registry_enabled
cannot!(*named_abilities(:container_image))
end
end
def anonymous_rules
return unless project.public?
can! :read_project
can! :read_board
can! :read_list
can! :read_wiki
can! :read_label
can! :read_milestone
can! :read_project_snippet
can! :read_project_member
can! :read_merge_request
can! :read_note
can! :read_pipeline
can! :read_commit_status
can! :read_container_image
can! :download_code
# NOTE: may be overridden by IssuePolicy
can! :read_issue
# Allow to read builds by anonymous user if guests are allowed
can! :read_build if project.public_builds?
disabled_features!
end
def project_group_member?(user)
project.group &&
(
project.group.members.exists?(user_id: user.id) ||
project.group.requesters.exists?(user_id: user.id)
)
end
def named_abilities(name)
[
:"read_#{name}",
:"create_#{name}",
:"update_#{name}",
:"admin_#{name}"
]
end
end
class ProjectSnippetPolicy < BasePolicy
def rules
can! :read_project_snippet if @subject.public?
return unless @user
if @user && @subject.author == @user || @user.admin?
can! :read_project_snippet
can! :update_project_snippet
can! :admin_project_snippet
end
if @subject.internal? && !@user.external?
can! :read_project_snippet
end
if @subject.private? && @subject.project.team.member?(@user)
can! :read_project_snippet
end
end
end
class UserPolicy < BasePolicy
include Gitlab::CurrentSettings
def rules
can! :read_user if @user || !restricted_public_level?
end
def restricted_public_level?
current_application_settings.restricted_visibility_levels.include?(Gitlab::VisibilityLevel::PUBLIC)
end
end
......@@ -7,12 +7,8 @@ class BaseService
@project, @current_user, @params = project, user, params.dup
end
def abilities
Ability.abilities
end
def can?(object, action, subject)
abilities.allowed?(object, action, subject)
Ability.allowed?(object, action, subject)
end
def notification_service
......
......@@ -10,6 +10,7 @@ module Ci
create_builds!
end
@pipeline.with_lock do
new_builds =
stage_indexes_of_created_builds.map do |index|
process_stage(index)
......@@ -18,6 +19,7 @@ module Ci
# Return a flag if a when builds got enqueued
new_builds.flatten.any?
end
end
private
......
......@@ -66,7 +66,7 @@
- if @todos.any?
.js-todos-options{ data: {per_page: @todos.limit_value, current_page: @todos.current_page, total_pages: @todos.total_pages} }
- @todos.group_by(&:project).each do |group|
.panel.panel-default.panel-small.js-todos-list
.panel.panel-default.panel-small
- project = group[0]
.panel-heading
= link_to project.name_with_namespace, namespace_project_path(project.namespace, project)
......
- if event.visible_to_user?(current_user)
.event-item{ class: event_row_class(event) }
.event-item-timestamp
#{time_ago_with_tooltip(event.created_at)}
#{time_ago_with_tooltip(event.created_at, skip_js: true)}
= cache [event, current_application_settings, "v2.2"] do
= author_avatar(event, size: 40)
......
= render 'layouts/nav/group_settings'
.scrolling-tabs-container{ class: nav_control_class }
= render 'layouts/nav/group_settings'
.fade-left
= icon('angle-left')
.fade-right
......
- if current_user
- can_admin_group = can?(current_user, :admin_group, @group)
- can_edit = can?(current_user, :admin_group, @group)
- member = @group.members.find_by(user_id: current_user.id)
- can_leave = member && can?(current_user, :destroy_group_member, member)
- if can_admin_group || can_edit || can_leave
.controls
.dropdown.group-settings-dropdown
%a.dropdown-new.btn.btn-default#group-settings-button{href: '#', 'data-toggle' => 'dropdown'}
= icon('cog')
= icon('caret-down')
%ul.dropdown-menu.dropdown-menu-align-right
- if can_admin_group
= nav_link(path: 'groups#projects') do
= link_to 'Projects', projects_group_path(@group), title: 'Projects'
- if can_edit || can_leave
%li.divider
- if can_edit
%li
......
......@@ -7,7 +7,7 @@
- cache_key = [project.path_with_namespace, commit.id, current_application_settings, note_count]
- cache_key.push(commit.status) if commit.status
= cache(cache_key) do
= cache(cache_key, expires_in: 1.day) do
%li.commit.js-toggle-container{ id: "commit-#{commit.short_id}" }
= author_avatar(commit, size: 36)
......
......@@ -8,21 +8,24 @@
Tags give the ability to mark specific points in history as being important
.nav-controls
- if can? current_user, :push_code, @project
= link_to new_namespace_project_tag_path(@project.namespace, @project), class: 'btn btn-create new-tag-btn' do
New tag
= form_tag(filter_tags_path, method: :get) do
= search_field_tag :search, params[:search], { placeholder: 'Filter by tag name', id: 'tag-search', class: 'form-control search-text-input input-short', spellcheck: false }
.dropdown.inline
%button.dropdown-toggle.btn{ type: 'button', data: { toggle: 'dropdown'} }
%span.light= @sort.humanize
%span.light
= @sort.humanize
%b.caret
%ul.dropdown-menu.dropdown-menu-align-right
%li
= link_to namespace_project_tags_path(sort: nil) do
= link_to filter_tags_path(sort: nil) do
Name
= link_to namespace_project_tags_path(sort: sort_value_recently_updated) do
= link_to filter_tags_path(sort: sort_value_recently_updated) do
= sort_title_recently_updated
= link_to namespace_project_tags_path(sort: sort_value_oldest_updated) do
= link_to filter_tags_path(sort: sort_value_oldest_updated) do
= sort_title_oldest_updated
- if can?(current_user, :push_code, @project)
= link_to new_namespace_project_tag_path(@project.namespace, @project), class: 'btn btn-create new-tag-btn' do
New tag
.tags
- if @tags.any?
......
......@@ -12,7 +12,7 @@
- if params[:label_name].present?
- if params[:label_name].respond_to?('any?')
- params[:label_name].each do |label|
= hidden_field_tag "label_name[]", label, id: nil
= hidden_field_tag "label_name[]", u(label), id: nil
.dropdown
%button.dropdown-menu-toggle.js-label-select.js-multiselect{class: classes.join(' '), type: "button", data: dropdown_data}
%span.dropdown-toggle-text
......
......@@ -18,6 +18,7 @@ if Rails.env.production?
# Sanitize fields based on those sanitized from Rails.
config.sanitize_fields = Rails.application.config.filter_parameters.map(&:to_s)
config.tags = { program: Gitlab::Sentry.program_context }
end
end
end
# Broadcast Messages
> **Note:** This feature was introduced in GitLab 8.12.
The broadcast message API is only accessible to administrators. All requests by
guests will respond with `401 Unauthorized`, and all requests by normal users
will respond with `403 Forbidden`.
## Get all broadcast messages
```
GET /broadcast_messages
```
```bash
curl --header "PRIVATE-TOKEN: 9koXpg98eAheJpvBs5tK" https://gitlab.example.com/api/v3/broadcast_messages
```
Example response:
```json
[
{
"message":"Example broadcast message",
"starts_at":"2016-08-24T23:21:16.078Z",
"ends_at":"2016-08-26T23:21:16.080Z",
"color":"#E75E40",
"font":"#FFFFFF",
"id":1,
"active": false
}
]
```
## Get a specific broadcast message
```
GET /broadcast_messages/:id
```
| Attribute | Type | Required | Description |
| ----------- | -------- | -------- | ------------------------- |
| `id` | integer | yes | Broadcast message ID |
```bash
curl --header "PRIVATE-TOKEN: 9koXpg98eAheJpvBs5tK" https://gitlab.example.com/api/v3/broadcast_messages/1
```
Example response:
```json
{
"message":"Deploy in progress",
"starts_at":"2016-08-24T23:21:16.078Z",
"ends_at":"2016-08-26T23:21:16.080Z",
"color":"#cecece",
"font":"#FFFFFF",
"id":1,
"active":false
}
```
## Create a broadcast message
Responds with `400 Bad request` when the `message` parameter is missing or the
`color` or `font` values are invalid, and `201 Created` when the broadcast
message was successfully created.
```
POST /broadcast_messages
```
| Attribute | Type | Required | Description |
| ----------- | -------- | -------- | ---------------------------------------------------- |
| `message` | string | yes | Message to display |
| `starts_at` | datetime | no | Starting time (defaults to current time) |
| `ends_at` | datetime | no | Ending time (defaults to one hour from current time) |
| `color` | string | no | Background color hex code |
| `font` | string | no | Foreground color hex code |
```bash
curl --data "message=Deploy in progress&color=#cecece" --header "PRIVATE-TOKEN: 9koXpg98eAheJpvBs5tK" https://gitlab.example.com/api/v3/broadcast_messages
```
Example response:
```json
{
"message":"Deploy in progress",
"starts_at":"2016-08-26T00:41:35.060Z",
"ends_at":"2016-08-26T01:41:35.060Z",
"color":"#cecece",
"font":"#FFFFFF",
"id":1,
"active": true
}
```
## Update a broadcast message
```
PUT /broadcast_messages/:id
```
| Attribute | Type | Required | Description |
| ----------- | -------- | -------- | ------------------------- |
| `id` | integer | yes | Broadcast message ID |
| `message` | string | no | Message to display |
| `starts_at` | datetime | no | Starting time |
| `ends_at` | datetime | no | Ending time |
| `color` | string | no | Background color hex code |
| `font` | string | no | Foreground color hex code |
```bash
curl --request PUT --data "message=Update message&color=#000" --header "PRIVATE-TOKEN: 9koXpg98eAheJpvBs5tK" https://gitlab.example.com/api/v3/broadcast_messages/1
```
Example response:
```json
{
"message":"Update message",
"starts_at":"2016-08-26T00:41:35.060Z",
"ends_at":"2016-08-26T01:41:35.060Z",
"color":"#000",
"font":"#FFFFFF",
"id":1,
"active": true
}
```
## Delete a broadcast message
```
DELETE /broadcast_messages/:id
```
| Attribute | Type | Required | Description |
| ----------- | -------- | -------- | ------------------------- |
| `id` | integer | yes | Broadcast message ID |
```bash
curl --request DELETE --header "PRIVATE-TOKEN: 9koXpg98eAheJpvBs5tK" https://gitlab.example.com/api/v3/broadcast_messages/1
```
Example response:
```json
{
"message":"Update message",
"starts_at":"2016-08-26T00:41:35.060Z",
"ends_at":"2016-08-26T01:41:35.060Z",
"color":"#000",
"font":"#FFFFFF",
"id":1,
"active": true
}
```
......@@ -10,7 +10,7 @@ GET /projects/:id/repository/commits
| Attribute | Type | Required | Description |
| --------- | ---- | -------- | ----------- |
| `id` | integer | yes | The ID of a project or NAMESPACE/PROJECT_NAME owned by the authenticated user
| `id` | integer/string | yes | The ID of a project or NAMESPACE/PROJECT_NAME owned by the authenticated user
| `ref_name` | string | no | The name of a repository branch or tag or if not given the default branch |
| `since` | string | no | Only commits after or in this date will be returned in ISO 8601 format YYYY-MM-DDTHH:MM:SSZ |
| `until` | string | no | Only commits before or in this date will be returned in ISO 8601 format YYYY-MM-DDTHH:MM:SSZ |
......@@ -58,7 +58,7 @@ Parameters:
| Attribute | Type | Required | Description |
| --------- | ---- | -------- | ----------- |
| `id` | integer | yes | The ID of a project or NAMESPACE/PROJECT_NAME owned by the authenticated user
| `id` | integer/string | yes | The ID of a project or NAMESPACE/PROJECT_NAME owned by the authenticated user
| `sha` | string | yes | The commit hash or name of a repository branch or tag |
```bash
......@@ -102,7 +102,7 @@ Parameters:
| Attribute | Type | Required | Description |
| --------- | ---- | -------- | ----------- |
| `id` | integer | yes | The ID of a project or NAMESPACE/PROJECT_NAME owned by the authenticated user
| `id` | integer/string | yes | The ID of a project or NAMESPACE/PROJECT_NAME owned by the authenticated user
| `sha` | string | yes | The commit hash or name of a repository branch or tag |
```bash
......@@ -138,7 +138,7 @@ Parameters:
| Attribute | Type | Required | Description |
| --------- | ---- | -------- | ----------- |
| `id` | integer | yes | The ID of a project or NAMESPACE/PROJECT_NAME owned by the authenticated user
| `id` | integer/string | yes | The ID of a project or NAMESPACE/PROJECT_NAME owned by the authenticated user
| `sha` | string | yes | The commit hash or name of a repository branch or tag |
```bash
......@@ -187,7 +187,7 @@ POST /projects/:id/repository/commits/:sha/comments
| Attribute | Type | Required | Description |
| --------- | ---- | -------- | ----------- |
| `id` | integer | yes | The ID of a project or NAMESPACE/PROJECT_NAME owned by the authenticated user
| `id` | integer/string | yes | The ID of a project or NAMESPACE/PROJECT_NAME owned by the authenticated user
| `sha` | string | yes | The commit SHA or name of a repository branch or tag |
| `note` | string | yes | The text of the comment |
| `path` | string | no | The file path relative to the repository |
......@@ -232,7 +232,7 @@ GET /projects/:id/repository/commits/:sha/statuses
| Attribute | Type | Required | Description |
| --------- | ---- | -------- | ----------- |
| `id` | integer | yes | The ID of a project or NAMESPACE/PROJECT_NAME owned by the authenticated user
| `id` | integer/string | yes | The ID of a project or NAMESPACE/PROJECT_NAME owned by the authenticated user
| `sha` | string | yes | The commit SHA
| `ref_name`| string | no | The name of a repository branch or tag or, if not given, the default branch
| `stage` | string | no | Filter by [build stage](../ci/yaml/README.md#stages), e.g., `test`
......@@ -306,7 +306,7 @@ POST /projects/:id/statuses/:sha
| Attribute | Type | Required | Description |
| --------- | ---- | -------- | ----------- |
| `id` | integer | yes | The ID of a project or NAMESPACE/PROJECT_NAME owned by the authenticated user
| `id` | integer/string | yes | The ID of a project or NAMESPACE/PROJECT_NAME owned by the authenticated user
| `sha` | string | yes | The commit SHA
| `state` | string | yes | The state of the status. Can be one of the following: `pending`, `running`, `success`, `failed`, `canceled`
| `ref` | string | no | The `ref` (branch or tag) to which the status refers
......
......@@ -80,7 +80,8 @@ Example response:
"subscribed" : false,
"user_notes_count": 1,
"due_date": "2016-07-22",
"web_url": "http://example.com/example/example/issues/6"
"web_url": "http://example.com/example/example/issues/6",
"confidential": false
}
]
```
......@@ -158,7 +159,8 @@ Example response:
"subscribed" : false,
"user_notes_count": 1,
"due_date": null,
"web_url": "http://example.com/example/example/issues/1"
"web_url": "http://example.com/example/example/issues/1",
"confidential": false
}
]
```
......@@ -238,7 +240,8 @@ Example response:
"subscribed" : false,
"user_notes_count": 1,
"due_date": "2016-07-22",
"web_url": "http://example.com/example/example/issues/1"
"web_url": "http://example.com/example/example/issues/1",
"confidential": false
}
]
```
......@@ -303,7 +306,8 @@ Example response:
"subscribed": false,
"user_notes_count": 1,
"due_date": null,
"web_url": "http://example.com/example/example/issues/1"
"web_url": "http://example.com/example/example/issues/1",
"confidential": false
}
```
......@@ -324,6 +328,7 @@ POST /projects/:id/issues
| `id` | integer | yes | The ID of a project |
| `title` | string | yes | The title of an issue |
| `description` | string | no | The description of an issue |
| `confidential` | boolean | no | Set an issue to be confidential. Default is `false`. |
| `assignee_id` | integer | no | The ID of a user to assign issue |
| `milestone_id` | integer | no | The ID of a milestone to assign issue |
| `labels` | string | no | Comma-separated label names for an issue |
......@@ -362,7 +367,8 @@ Example response:
"subscribed" : true,
"user_notes_count": 0,
"due_date": null,
"web_url": "http://example.com/example/example/issues/14"
"web_url": "http://example.com/example/example/issues/14",
"confidential": false
}
```
......@@ -385,6 +391,7 @@ PUT /projects/:id/issues/:issue_id
| `issue_id` | integer | yes | The ID of a project's issue |
| `title` | string | no | The title of an issue |
| `description` | string | no | The description of an issue |
| `confidential` | boolean | no | Updates an issue to be confidential |
| `assignee_id` | integer | no | The ID of a user to assign the issue to |
| `milestone_id` | integer | no | The ID of a milestone to assign the issue to |
| `labels` | string | no | Comma-separated label names for an issue |
......@@ -424,7 +431,8 @@ Example response:
"subscribed" : true,
"user_notes_count": 0,
"due_date": "2016-07-22",
"web_url": "http://example.com/example/example/issues/15"
"web_url": "http://example.com/example/example/issues/15",
"confidential": false
}
```
......@@ -503,7 +511,8 @@ Example response:
"web_url": "https://gitlab.example.com/u/solon.cremin"
},
"due_date": null,
"web_url": "http://example.com/example/example/issues/11"
"web_url": "http://example.com/example/example/issues/11",
"confidential": false
}
```
......@@ -559,7 +568,8 @@ Example response:
"web_url": "https://gitlab.example.com/u/solon.cremin"
},
"due_date": null,
"web_url": "http://example.com/example/example/issues/11"
"web_url": "http://example.com/example/example/issues/11",
"confidential": false
}
```
......@@ -616,7 +626,8 @@ Example response:
},
"subscribed": false,
"due_date": null,
"web_url": "http://example.com/example/example/issues/12"
"web_url": "http://example.com/example/example/issues/12",
"confidential": false
}
```
......@@ -704,7 +715,8 @@ Example response:
"upvotes": 0,
"downvotes": 0,
"due_date": null,
"web_url": "http://example.com/example/example/issues/110"
"web_url": "http://example.com/example/example/issues/110",
"confidential": false
},
"target_url": "https://gitlab.example.com/gitlab-org/gitlab-ci/issues/10",
"body": "Vel voluptas atque dicta mollitia adipisci qui at.",
......
......@@ -535,7 +535,7 @@ POST /projects/:id/star
| Attribute | Type | Required | Description |
| --------- | ---- | -------- | ----------- |
| `id` | integer | yes | The ID of the project or NAMESPACE/PROJECT_NAME |
| `id` | integer/string | yes | The ID of the project or NAMESPACE/PROJECT_NAME |
```bash
curl --request POST --header "PRIVATE-TOKEN: 9koXpg98eAheJpvBs5tK" "https://gitlab.example.com/api/v3/projects/5/star"
......@@ -602,7 +602,7 @@ DELETE /projects/:id/star
| Attribute | Type | Required | Description |
| --------- | ---- | -------- | ----------- |
| `id` | integer | yes | The ID of the project or NAMESPACE/PROJECT_NAME |
| `id` | integer/string | yes | The ID of the project or NAMESPACE/PROJECT_NAME |
```bash
curl --request DELETE --header "PRIVATE-TOKEN: 9koXpg98eAheJpvBs5tK" "https://gitlab.example.com/api/v3/projects/5/star"
......@@ -673,7 +673,7 @@ POST /projects/:id/archive
| Attribute | Type | Required | Description |
| --------- | ---- | -------- | ----------- |
| `id` | integer | yes | The ID of the project or NAMESPACE/PROJECT_NAME |
| `id` | integer/string | yes | The ID of the project or NAMESPACE/PROJECT_NAME |
```bash
curl --request POST --header "PRIVATE-TOKEN: 9koXpg98eAheJpvBs5tK" "https://gitlab.example.com/api/v3/projects/archive"
......@@ -760,7 +760,7 @@ POST /projects/:id/unarchive
| Attribute | Type | Required | Description |
| --------- | ---- | -------- | ----------- |
| `id` | integer | yes | The ID of the project or NAMESPACE/PROJECT_NAME |
| `id` | integer/string | yes | The ID of the project or NAMESPACE/PROJECT_NAME |
```bash
curl --request POST --header "PRIVATE-TOKEN: 9koXpg98eAheJpvBs5tK" "https://gitlab.example.com/api/v3/projects/unarchive"
......
......@@ -54,6 +54,7 @@ class Spinach::Features::DashboardTodos < Spinach::FeatureSteps
page.within('.todos-pending-count') { expect(page).to have_content '0' }
expect(page).to have_content 'To do 0'
expect(page).to have_content 'Done 4'
expect(page).to have_content "You're all done!"
expect(page).not_to have_link project.name_with_namespace
should_not_see_todo "John Doe assigned you merge request #{merge_request.to_reference}"
should_not_see_todo "John Doe mentioned you on issue #{issue.to_reference}"
......
......@@ -31,6 +31,7 @@ module API
mount ::API::AccessRequests
mount ::API::AwardEmoji
mount ::API::Branches
mount ::API::BroadcastMessages
mount ::API::Builds
mount ::API::CommitStatuses
mount ::API::Commits
......
module API
class BroadcastMessages < Grape::API
before { authenticate! }
before { authenticated_as_admin! }
resource :broadcast_messages do
helpers do
def find_message
BroadcastMessage.find(params[:id])
end
end
desc 'Get all broadcast messages' do
detail 'This feature was introduced in GitLab 8.12.'
success Entities::BroadcastMessage
end
params do
optional :page, type: Integer, desc: 'Current page number'
optional :per_page, type: Integer, desc: 'Number of messages per page'
end
get do
messages = BroadcastMessage.all
present paginate(messages), with: Entities::BroadcastMessage
end
desc 'Create a broadcast message' do
detail 'This feature was introduced in GitLab 8.12.'
success Entities::BroadcastMessage
end
params do
requires :message, type: String, desc: 'Message to display'
optional :starts_at, type: DateTime, desc: 'Starting time', default: -> { Time.zone.now }
optional :ends_at, type: DateTime, desc: 'Ending time', default: -> { 1.hour.from_now }
optional :color, type: String, desc: 'Background color'
optional :font, type: String, desc: 'Foreground color'
end
post do
create_params = declared(params, include_missing: false).to_h
message = BroadcastMessage.create(create_params)
if message.persisted?
present message, with: Entities::BroadcastMessage
else
render_validation_error!(message)
end
end
desc 'Get a specific broadcast message' do
detail 'This feature was introduced in GitLab 8.12.'
success Entities::BroadcastMessage
end
params do
requires :id, type: Integer, desc: 'Broadcast message ID'
end
get ':id' do
message = find_message
present message, with: Entities::BroadcastMessage
end
desc 'Update a broadcast message' do
detail 'This feature was introduced in GitLab 8.12.'
success Entities::BroadcastMessage
end
params do
requires :id, type: Integer, desc: 'Broadcast message ID'
optional :message, type: String, desc: 'Message to display'
optional :starts_at, type: DateTime, desc: 'Starting time'
optional :ends_at, type: DateTime, desc: 'Ending time'
optional :color, type: String, desc: 'Background color'
optional :font, type: String, desc: 'Foreground color'
end
put ':id' do
message = find_message
update_params = declared(params, include_missing: false).to_h
if message.update(update_params)
present message, with: Entities::BroadcastMessage
else
render_validation_error!(message)
end
end
desc 'Delete a broadcast message' do
detail 'This feature was introduced in GitLab 8.12.'
success Entities::BroadcastMessage
end
params do
requires :id, type: Integer, desc: 'Broadcast message ID'
end
delete ':id' do
message = find_message
present message.destroy, with: Entities::BroadcastMessage
end
end
end
end
......@@ -211,6 +211,7 @@ module API
expose :user_notes_count
expose :upvotes, :downvotes
expose :due_date
expose :confidential
expose :web_url do |issue, options|
Gitlab::UrlBuilder.build(issue)
......@@ -574,5 +575,10 @@ module API
class Template < Grape::Entity
expose :name, :content
end
class BroadcastMessage < Grape::Entity
expose :id, :message, :starts_at, :ends_at, :color, :font
expose :active?, as: :active
end
end
end
......@@ -30,7 +30,7 @@ module API
# Example Request:
# POST /groups
post do
authorize! :create_group, current_user
authorize! :create_group
required_attributes! [:name, :path]
attrs = attributes_for_keys [:name, :path, :description, :visibility_level]
......
......@@ -129,7 +129,7 @@ module API
forbidden! unless current_user.is_admin?
end
def authorize!(action, subject)
def authorize!(action, subject = nil)
forbidden! unless can?(current_user, action, subject)
end
......@@ -148,7 +148,7 @@ module API
end
def can?(object, action, subject)
abilities.allowed?(object, action, subject)
Ability.allowed?(object, action, subject)
end
# Checks the occurrences of required attributes, each attribute must be present in the params hash
......@@ -408,14 +408,6 @@ module API
links.join(', ')
end
def abilities
@abilities ||= begin
abilities = Six.new
abilities << Ability
abilities
end
end
def secret_token
File.read(Gitlab.config.gitlab_shell.secret_file).chomp
end
......
......@@ -140,12 +140,13 @@ module API
# labels (optional) - The labels of an issue
# created_at (optional) - Date time string, ISO 8601 formatted
# due_date (optional) - Date time string in the format YEAR-MONTH-DAY
# confidential (optional) - Boolean parameter if the issue should be confidential
# Example Request:
# POST /projects/:id/issues
post ':id/issues' do
required_attributes! [:title]
keys = [:title, :description, :assignee_id, :milestone_id, :due_date]
keys = [:title, :description, :assignee_id, :milestone_id, :due_date, :confidential]
keys << :created_at if current_user.admin? || user_project.owner == current_user
attrs = attributes_for_keys(keys)
......@@ -156,6 +157,10 @@ module API
attrs[:labels] = params[:labels] if params[:labels]
# Convert and filter out invalid confidential flags
attrs['confidential'] = to_boolean(attrs['confidential'])
attrs.delete('confidential') if attrs['confidential'].nil?
issue = ::Issues::CreateService.new(user_project, current_user, attrs.merge(request: request, api: true)).execute
if issue.spam?
......@@ -182,12 +187,13 @@ module API
# state_event (optional) - The state event of an issue (close|reopen)
# updated_at (optional) - Date time string, ISO 8601 formatted
# due_date (optional) - Date time string in the format YEAR-MONTH-DAY
# confidential (optional) - Boolean parameter if the issue should be confidential
# Example Request:
# PUT /projects/:id/issues/:issue_id
put ':id/issues/:issue_id' do
issue = user_project.issues.find(params[:issue_id])
authorize! :update_issue, issue
keys = [:title, :description, :assignee_id, :milestone_id, :state_event, :due_date]
keys = [:title, :description, :assignee_id, :milestone_id, :state_event, :due_date, :confidential]
keys << :updated_at if current_user.admin? || user_project.owner == current_user
attrs = attributes_for_keys(keys)
......@@ -198,6 +204,10 @@ module API
attrs[:labels] = params[:labels] if params[:labels]
# Convert and filter out invalid confidential flags
attrs['confidential'] = to_boolean(attrs['confidential'])
attrs.delete('confidential') if attrs['confidential'].nil?
issue = ::Issues::UpdateService.new(user_project, current_user, attrs).execute(issue)
if issue.valid?
......
......@@ -211,7 +211,7 @@ module Banzai
end
def can?(user, permission, subject)
Ability.abilities.allowed?(user, permission, subject)
Ability.allowed?(user, permission, subject)
end
def find_projects_for_hash_keys(hash)
......
......@@ -152,6 +152,7 @@ module Gitlab
end
def create_comments(issuable, comments)
ActiveRecord::Base.no_touching do
comments.each do |raw|
begin
comment = CommentFormatter.new(project, raw)
......@@ -161,6 +162,7 @@ module Gitlab
end
end
end
end
def import_wiki
unless project.wiki_enabled?
......
......@@ -12,7 +12,7 @@ module Gitlab
author_id: author_id,
assignee_id: assignee_id,
created_at: raw_data.created_at,
updated_at: updated_at
updated_at: raw_data.updated_at
}
end
......@@ -69,10 +69,6 @@ module Gitlab
def state
raw_data.state == 'closed' ? 'closed' : 'opened'
end
def updated_at
state == 'closed' ? raw_data.closed_at : raw_data.updated_at
end
end
end
end
......@@ -3,14 +3,14 @@ module Gitlab
class MilestoneFormatter < BaseFormatter
def attributes
{
iid: number,
iid: raw_data.number,
project: project,
title: title,
description: description,
due_date: due_date,
title: raw_data.title,
description: raw_data.description,
due_date: raw_data.due_on,
state: state,
created_at: created_at,
updated_at: updated_at
created_at: raw_data.created_at,
updated_at: raw_data.updated_at
}
end
......@@ -20,33 +20,9 @@ module Gitlab
private
def number
raw_data.number
end
def title
raw_data.title
end
def description
raw_data.description
end
def due_date
raw_data.due_on
end
def state
raw_data.state == 'closed' ? 'closed' : 'active'
end
def created_at
raw_data.created_at
end
def updated_at
state == 'closed' ? raw_data.closed_at : raw_data.updated_at
end
end
end
end
......@@ -17,7 +17,7 @@ module Gitlab
path: repo.name,
description: repo.description,
namespace_id: namespace.id,
visibility_level: repo.private ? Gitlab::VisibilityLevel::PRIVATE : Gitlab::VisibilityLevel::PUBLIC,
visibility_level: repo.private ? Gitlab::VisibilityLevel::PRIVATE : ApplicationSetting.current.default_project_visibility,
import_type: "github",
import_source: repo.full_name,
import_url: repo.clone_url.sub("https://", "https://#{@session_data[:github_access_token]}@"),
......
......@@ -20,7 +20,7 @@ module Gitlab
author_id: author_id,
assignee_id: assignee_id,
created_at: raw_data.created_at,
updated_at: updated_at
updated_at: raw_data.updated_at
}
end
......@@ -103,15 +103,6 @@ module Gitlab
'opened'
end
end
def updated_at
case state
when 'merged' then raw_data.merged_at
when 'closed' then raw_data.closed_at
else
raw_data.updated_at
end
end
end
end
end
module Gitlab
module Sentry
def self.enabled?
Rails.env.production? && current_application_settings.sentry_enabled?
end
def self.context(current_user = nil)
return unless self.enabled?
if current_user
Raven.user_context(
id: current_user.id,
email: current_user.email,
username: current_user.username,
)
end
end
def self.program_context
if Sidekiq.server?
'sidekiq'
else
'rails'
end
end
end
end
......@@ -41,8 +41,8 @@ describe Projects::Boards::IssuesController do
context 'with unauthorized user' do
before do
allow(Ability.abilities).to receive(:allowed?).with(user, :read_project, project).and_return(true)
allow(Ability.abilities).to receive(:allowed?).with(user, :read_issue, project).and_return(false)
allow(Ability).to receive(:allowed?).with(user, :read_project, project).and_return(true)
allow(Ability).to receive(:allowed?).with(user, :read_issue, project).and_return(false)
end
it 'returns a successful 403 response' do
......
......@@ -35,8 +35,8 @@ describe Projects::Boards::ListsController do
context 'with unauthorized user' do
before do
allow(Ability.abilities).to receive(:allowed?).with(user, :read_project, project).and_return(true)
allow(Ability.abilities).to receive(:allowed?).with(user, :read_list, project).and_return(false)
allow(Ability).to receive(:allowed?).with(user, :read_project, project).and_return(true)
allow(Ability).to receive(:allowed?).with(user, :read_list, project).and_return(false)
end
it 'returns a forbidden 403 response' do
......
......@@ -23,8 +23,8 @@ describe Projects::BoardsController do
context 'with unauthorized user' do
before do
allow(Ability.abilities).to receive(:allowed?).with(user, :read_project, project).and_return(true)
allow(Ability.abilities).to receive(:allowed?).with(user, :read_board, project).and_return(false)
allow(Ability).to receive(:allowed?).with(user, :read_project, project).and_return(true)
allow(Ability).to receive(:allowed?).with(user, :read_board, project).and_return(false)
end
it 'returns a successful 404 response' do
......
......@@ -8,6 +8,7 @@ describe 'Filter issues', feature: true do
let!(:milestone) { create(:milestone, project: project) }
let!(:label) { create(:label, project: project) }
let!(:issue1) { create(:issue, project: project) }
let!(:wontfix) { create(:label, project: project, title: "Won't fix") }
before do
project.team << [user, :master]
......@@ -107,6 +108,15 @@ describe 'Filter issues', feature: true do
end
expect(find('.js-label-select .dropdown-toggle-text')).to have_content(label.title)
end
it 'filters by wont fix labels' do
find('.dropdown-menu-labels a', text: label.title).click
page.within '.labels-filter' do
expect(page).to have_content wontfix.title
click_link wontfix.title
end
expect(find('.js-label-select .dropdown-toggle-text')).to have_content(wontfix.title)
end
end
describe 'Filter issues for assignee and label from issues#index' do
......
......@@ -118,6 +118,20 @@ describe 'Dashboard Todos', feature: true do
expect(page).to have_css("#todo_#{Todo.first.id}")
end
end
describe 'mark all as done', js: true do
before do
visit dashboard_todos_path
click_link('Mark all as done')
end
it 'shows "All done" message!' do
within('.todos-pending-count') { expect(page).to have_content '0' }
expect(page).to have_content 'To do 0'
expect(page).to have_content "You're all done!"
expect(page).not_to have_selector('.gl-pagination')
end
end
end
context 'User has a Todo in a project pending deletion' do
......
require 'spec_helper'
describe TagsFinder do
let(:user) { create(:user) }
let(:project) { create(:project) }
let(:repository) { project.repository }
describe '#execute' do
context 'sort only' do
it 'sorts by name' do
tags_finder = described_class.new(repository, {})
result = tags_finder.execute
expect(result.first.name).to eq("v1.0.0")
end
it 'sorts by recently_updated' do
tags_finder = described_class.new(repository, { sort: 'updated_desc' })
result = tags_finder.execute
recently_updated_tag = repository.tags.max do |a, b|
repository.commit(a.target).committed_date <=> repository.commit(b.target).committed_date
end
expect(result.first.name).to eq(recently_updated_tag.name)
end
it 'sorts by last_updated' do
tags_finder = described_class.new(repository, { sort: 'updated_asc' })
result = tags_finder.execute
expect(result.first.name).to eq('v1.0.0')
end
end
context 'filter only' do
it 'filters tags by name' do
tags_finder = described_class.new(repository, { search: '1.0.0' })
result = tags_finder.execute
expect(result.first.name).to eq('v1.0.0')
expect(result.count).to eq(1)
end
it 'does not find any tags with that name' do
tags_finder = described_class.new(repository, { search: 'hey' })
result = tags_finder.execute
expect(result.count).to eq(0)
end
end
context 'filter and sort' do
it 'filters tags by name and sorts by recently_updated' do
params = { sort: 'updated_desc', search: 'v1' }
tags_finder = described_class.new(repository, params)
result = tags_finder.execute
expect(result.first.name).to eq('v1.1.0')
expect(result.count).to eq(2)
end
it 'filters tags by name and sorts by last_updated' do
params = { sort: 'updated_asc', search: 'v1' }
tags_finder = described_class.new(repository, params)
result = tags_finder.execute
expect(result.first.name).to eq('v1.0.0')
expect(result.count).to eq(2)
end
end
end
end
......@@ -13,17 +13,21 @@
gl.utils.preventDisabledButtons();
isClicked = false;
$button = $('#test-button');
expect($button).toExist();
$button.click(function() {
return isClicked = true;
});
$button.trigger('click');
return expect(isClicked).toBe(false);
});
return it('should be on the same page if a disabled link clicked', function() {
var locationBeforeLinkClick;
it('should be on the same page if a disabled link clicked', function() {
var locationBeforeLinkClick, $link;
locationBeforeLinkClick = window.location.href;
gl.utils.preventDisabledButtons();
$('#test-link').click();
$link = $('#test-link');
expect($link).toExist();
$link.click();
return expect(window.location.href).toBe(locationBeforeLinkClick);
});
});
......
......@@ -30,7 +30,7 @@ describe Banzai::ReferenceParser::BaseParser, lib: true do
it 'returns the nodes if the attribute value equals the current project ID' do
link['data-project'] = project.id.to_s
expect(Ability.abilities).not_to receive(:allowed?)
expect(Ability).not_to receive(:allowed?)
expect(subject.nodes_visible_to_user(user, [link])).to eq([link])
end
......@@ -39,7 +39,7 @@ describe Banzai::ReferenceParser::BaseParser, lib: true do
link['data-project'] = other_project.id.to_s
expect(Ability.abilities).to receive(:allowed?).
expect(Ability).to receive(:allowed?).
with(user, :read_project, other_project).
and_return(true)
......@@ -57,7 +57,7 @@ describe Banzai::ReferenceParser::BaseParser, lib: true do
link['data-project'] = other_project.id.to_s
expect(Ability.abilities).to receive(:allowed?).
expect(Ability).to receive(:allowed?).
with(user, :read_project, other_project).
and_return(false)
......@@ -221,7 +221,7 @@ describe Banzai::ReferenceParser::BaseParser, lib: true do
it 'delegates the permissions check to the Ability class' do
user = double(:user)
expect(Ability.abilities).to receive(:allowed?).
expect(Ability).to receive(:allowed?).
with(user, :read_project, project)
subject.can?(user, :read_project, project)
......
......@@ -82,7 +82,7 @@ describe Banzai::ReferenceParser::UserParser, lib: true do
end
it 'returns the nodes if the user can read the group' do
expect(Ability.abilities).to receive(:allowed?).
expect(Ability).to receive(:allowed?).
with(user, :read_group, group).
and_return(true)
......@@ -90,7 +90,7 @@ describe Banzai::ReferenceParser::UserParser, lib: true do
end
it 'returns an empty Array if the user can not read the group' do
expect(Ability.abilities).to receive(:allowed?).
expect(Ability).to receive(:allowed?).
with(user, :read_group, group).
and_return(false)
......@@ -103,7 +103,7 @@ describe Banzai::ReferenceParser::UserParser, lib: true do
it 'returns the nodes if the attribute value equals the current project ID' do
link['data-project'] = project.id.to_s
expect(Ability.abilities).not_to receive(:allowed?)
expect(Ability).not_to receive(:allowed?)
expect(subject.nodes_visible_to_user(user, [link])).to eq([link])
end
......@@ -113,7 +113,7 @@ describe Banzai::ReferenceParser::UserParser, lib: true do
link['data-project'] = other_project.id.to_s
expect(Ability.abilities).to receive(:allowed?).
expect(Ability).to receive(:allowed?).
with(user, :read_project, other_project).
and_return(true)
......@@ -125,7 +125,7 @@ describe Banzai::ReferenceParser::UserParser, lib: true do
link['data-project'] = other_project.id.to_s
expect(Ability.abilities).to receive(:allowed?).
expect(Ability).to receive(:allowed?).
with(user, :read_project, other_project).
and_return(false)
......
......@@ -48,8 +48,7 @@ describe Gitlab::GithubImport::IssueFormatter, lib: true do
end
context 'when issue is closed' do
let(:closed_at) { DateTime.strptime('2011-01-28T19:01:12Z') }
let(:raw_data) { double(base_data.merge(state: 'closed', closed_at: closed_at)) }
let(:raw_data) { double(base_data.merge(state: 'closed')) }
it 'returns formatted attributes' do
expected = {
......@@ -62,7 +61,7 @@ describe Gitlab::GithubImport::IssueFormatter, lib: true do
author_id: project.creator_id,
assignee_id: nil,
created_at: created_at,
updated_at: closed_at
updated_at: updated_at
}
expect(issue.attributes).to eq(expected)
......
......@@ -40,8 +40,7 @@ describe Gitlab::GithubImport::MilestoneFormatter, lib: true do
end
context 'when milestone is closed' do
let(:closed_at) { DateTime.strptime('2011-01-28T19:01:12Z') }
let(:raw_data) { double(base_data.merge(state: 'closed', closed_at: closed_at)) }
let(:raw_data) { double(base_data.merge(state: 'closed')) }
it 'returns formatted attributes' do
expected = {
......@@ -52,7 +51,7 @@ describe Gitlab::GithubImport::MilestoneFormatter, lib: true do
state: 'closed',
due_date: nil,
created_at: created_at,
updated_at: closed_at
updated_at: updated_at
}
expect(formatter.attributes).to eq(expected)
......
......@@ -2,33 +2,59 @@ require 'spec_helper'
describe Gitlab::GithubImport::ProjectCreator, lib: true do
let(:user) { create(:user) }
let(:namespace) { create(:group, owner: user) }
let(:repo) do
OpenStruct.new(
login: 'vim',
name: 'vim',
private: true,
full_name: 'asd/vim',
clone_url: "https://gitlab.com/asd/vim.git",
owner: OpenStruct.new(login: "john")
clone_url: 'https://gitlab.com/asd/vim.git'
)
end
let(:namespace) { create(:group, owner: user) }
let(:token) { "asdffg" }
let(:access_params) { { github_access_token: token } }
subject(:service) { described_class.new(repo, namespace, user, github_access_token: 'asdffg') }
before do
namespace.add_owner(user)
allow_any_instance_of(Project).to receive(:add_import_job)
end
it 'creates project' do
allow_any_instance_of(Project).to receive(:add_import_job)
describe '#execute' do
it 'creates a project' do
expect { service.execute }.to change(Project, :count).by(1)
end
it 'handle GitHub credentials' do
project = service.execute
expect(project.import_url).to eq('https://asdffg@gitlab.com/asd/vim.git')
expect(project.safe_import_url).to eq('https://*****@gitlab.com/asd/vim.git')
expect(project.import_data.credentials).to eq(user: 'asdffg', password: nil)
end
context 'when Github project is private' do
it 'sets project visibility to private' do
repo.private = true
project_creator = Gitlab::GithubImport::ProjectCreator.new(repo, namespace, user, access_params)
project = project_creator.execute
project = service.execute
expect(project.import_url).to eq("https://asdffg@gitlab.com/asd/vim.git")
expect(project.safe_import_url).to eq("https://*****@gitlab.com/asd/vim.git")
expect(project.import_data.credentials).to eq(user: "asdffg", password: nil)
expect(project.visibility_level).to eq(Gitlab::VisibilityLevel::PRIVATE)
end
end
context 'when Github project is public' do
before do
allow_any_instance_of(ApplicationSetting).to receive(:default_project_visibility).and_return(Gitlab::VisibilityLevel::INTERNAL)
end
it 'sets project visibility to the default project visibility' do
repo.private = false
project = service.execute
expect(project.visibility_level).to eq(Gitlab::VisibilityLevel::INTERNAL)
end
end
end
end
......@@ -62,8 +62,7 @@ describe Gitlab::GithubImport::PullRequestFormatter, lib: true do
end
context 'when pull request is closed' do
let(:closed_at) { DateTime.strptime('2011-01-28T19:01:12Z') }
let(:raw_data) { double(base_data.merge(state: 'closed', closed_at: closed_at)) }
let(:raw_data) { double(base_data.merge(state: 'closed')) }
it 'returns formatted attributes' do
expected = {
......@@ -81,7 +80,7 @@ describe Gitlab::GithubImport::PullRequestFormatter, lib: true do
author_id: project.creator_id,
assignee_id: nil,
created_at: created_at,
updated_at: closed_at
updated_at: updated_at
}
expect(pull_request.attributes).to eq(expected)
......@@ -108,7 +107,7 @@ describe Gitlab::GithubImport::PullRequestFormatter, lib: true do
author_id: project.creator_id,
assignee_id: nil,
created_at: created_at,
updated_at: merged_at
updated_at: updated_at
}
expect(pull_request.attributes).to eq(expected)
......
......@@ -171,70 +171,6 @@ describe Ability, lib: true do
end
end
shared_examples_for ".project_abilities" do |enable_request_store|
before do
RequestStore.begin! if enable_request_store
end
after do
if enable_request_store
RequestStore.end!
RequestStore.clear!
end
end
describe '.project_abilities' do
let!(:project) { create(:empty_project, :public) }
let!(:user) { create(:user) }
it 'returns permissions for admin user' do
admin = create(:admin)
results = described_class.project_abilities(admin, project)
expect(results.count).to eq(68)
end
it 'returns permissions for an owner' do
results = described_class.project_abilities(project.owner, project)
expect(results.count).to eq(68)
end
it 'returns permissions for a master' do
project.team << [user, :master]
results = described_class.project_abilities(user, project)
expect(results.count).to eq(60)
end
it 'returns permissions for a developer' do
project.team << [user, :developer]
results = described_class.project_abilities(user, project)
expect(results.count).to eq(44)
end
it 'returns permissions for a guest' do
project.team << [user, :guest]
results = described_class.project_abilities(user, project)
expect(results.count).to eq(21)
end
end
end
describe '.project_abilities with RequestStore' do
it_behaves_like ".project_abilities", true
end
describe '.project_abilities without RequestStore' do
it_behaves_like ".project_abilities", false
end
describe '.issues_readable_by_user' do
context 'with an admin user' do
it 'returns all given issues' do
......@@ -286,12 +222,12 @@ describe Ability, lib: true do
describe '.project_disabled_features_rules' do
let(:project) { build(:project) }
subject { described_class.project_disabled_features_rules(project) }
subject { described_class.allowed(project.owner, project) }
context 'wiki named abilities' do
it 'disables wiki abilities if the project has no wiki' do
expect(project).to receive(:has_wiki?).and_return(false)
expect(subject).to include(:read_wiki, :create_wiki, :update_wiki, :admin_wiki)
expect(subject).not_to include(:read_wiki, :create_wiki, :update_wiki, :admin_wiki)
end
end
end
......
......@@ -71,9 +71,6 @@ describe ProjectMember, models: true do
describe :import_team do
before do
@abilities = Six.new
@abilities << Ability
@project_1 = create :project
@project_2 = create :project
......@@ -92,8 +89,8 @@ describe ProjectMember, models: true do
it { expect(@project_2.users).to include(@user_1) }
it { expect(@project_2.users).to include(@user_2) }
it { expect(@abilities.allowed?(@user_1, :create_project, @project_2)).to be_truthy }
it { expect(@abilities.allowed?(@user_2, :read_project, @project_2)).to be_truthy }
it { expect(Ability.allowed?(@user_1, :create_project, @project_2)).to be_truthy }
it { expect(Ability.allowed?(@user_2, :read_project, @project_2)).to be_truthy }
end
describe 'project 1 should not be changed' do
......
......@@ -85,8 +85,6 @@ describe Note, models: true do
@u1 = create(:user)
@u2 = create(:user)
@u3 = create(:user)
@abilities = Six.new
@abilities << Ability
end
describe 'read' do
......@@ -95,9 +93,9 @@ describe Note, models: true do
@p2.project_members.create(user: @u3, access_level: ProjectMember::GUEST)
end
it { expect(@abilities.allowed?(@u1, :read_note, @p1)).to be_falsey }
it { expect(@abilities.allowed?(@u2, :read_note, @p1)).to be_truthy }
it { expect(@abilities.allowed?(@u3, :read_note, @p1)).to be_falsey }
it { expect(Ability.allowed?(@u1, :read_note, @p1)).to be_falsey }
it { expect(Ability.allowed?(@u2, :read_note, @p1)).to be_truthy }
it { expect(Ability.allowed?(@u3, :read_note, @p1)).to be_falsey }
end
describe 'write' do
......@@ -106,9 +104,9 @@ describe Note, models: true do
@p2.project_members.create(user: @u3, access_level: ProjectMember::DEVELOPER)
end
it { expect(@abilities.allowed?(@u1, :create_note, @p1)).to be_falsey }
it { expect(@abilities.allowed?(@u2, :create_note, @p1)).to be_truthy }
it { expect(@abilities.allowed?(@u3, :create_note, @p1)).to be_falsey }
it { expect(Ability.allowed?(@u1, :create_note, @p1)).to be_falsey }
it { expect(Ability.allowed?(@u2, :create_note, @p1)).to be_truthy }
it { expect(Ability.allowed?(@u3, :create_note, @p1)).to be_falsey }
end
describe 'admin' do
......@@ -118,9 +116,9 @@ describe Note, models: true do
@p2.project_members.create(user: @u3, access_level: ProjectMember::MASTER)
end
it { expect(@abilities.allowed?(@u1, :admin_note, @p1)).to be_falsey }
it { expect(@abilities.allowed?(@u2, :admin_note, @p1)).to be_truthy }
it { expect(@abilities.allowed?(@u3, :admin_note, @p1)).to be_falsey }
it { expect(Ability.allowed?(@u1, :admin_note, @p1)).to be_falsey }
it { expect(Ability.allowed?(@u2, :admin_note, @p1)).to be_truthy }
it { expect(Ability.allowed?(@u3, :admin_note, @p1)).to be_falsey }
end
end
......
require 'spec_helper'
describe Project, models: true do
describe 'authorization' do
before do
@p1 = create(:project)
@u1 = create(:user)
@u2 = create(:user)
@u3 = create(:user)
@u4 = @p1.owner
@abilities = Six.new
@abilities << Ability
end
let(:guest_actions) { Ability.project_guest_rules }
let(:report_actions) { Ability.project_report_rules }
let(:dev_actions) { Ability.project_dev_rules }
let(:master_actions) { Ability.project_master_rules }
let(:owner_actions) { Ability.project_owner_rules }
describe "Non member rules" do
it "denies for non-project users any actions" do
owner_actions.each do |action|
expect(@abilities.allowed?(@u1, action, @p1)).to be_falsey
end
end
end
describe "Guest Rules" do
before do
@p1.project_members.create(project: @p1, user: @u2, access_level: ProjectMember::GUEST)
end
it "allows for project user any guest actions" do
guest_actions.each do |action|
expect(@abilities.allowed?(@u2, action, @p1)).to be_truthy
end
end
end
describe "Report Rules" do
before do
@p1.project_members.create(project: @p1, user: @u2, access_level: ProjectMember::REPORTER)
end
it "allows for project user any report actions" do
report_actions.each do |action|
expect(@abilities.allowed?(@u2, action, @p1)).to be_truthy
end
end
end
describe "Developer Rules" do
before do
@p1.project_members.create(project: @p1, user: @u2, access_level: ProjectMember::REPORTER)
@p1.project_members.create(project: @p1, user: @u3, access_level: ProjectMember::DEVELOPER)
end
it "denies for developer master-specific actions" do
[dev_actions - report_actions].each do |action|
expect(@abilities.allowed?(@u2, action, @p1)).to be_falsey
end
end
it "allows for project user any dev actions" do
dev_actions.each do |action|
expect(@abilities.allowed?(@u3, action, @p1)).to be_truthy
end
end
end
describe "Master Rules" do
before do
@p1.project_members.create(project: @p1, user: @u2, access_level: ProjectMember::DEVELOPER)
@p1.project_members.create(project: @p1, user: @u3, access_level: ProjectMember::MASTER)
end
it "denies for developer master-specific actions" do
[master_actions - dev_actions].each do |action|
expect(@abilities.allowed?(@u2, action, @p1)).to be_falsey
end
end
it "allows for project user any master actions" do
master_actions.each do |action|
expect(@abilities.allowed?(@u3, action, @p1)).to be_truthy
end
end
end
describe "Owner Rules" do
before do
@p1.project_members.create(project: @p1, user: @u2, access_level: ProjectMember::DEVELOPER)
@p1.project_members.create(project: @p1, user: @u3, access_level: ProjectMember::MASTER)
end
it "denies for masters admin-specific actions" do
[owner_actions - master_actions].each do |action|
expect(@abilities.allowed?(@u2, action, @p1)).to be_falsey
end
end
it "allows for project owner any admin actions" do
owner_actions.each do |action|
expect(@abilities.allowed?(@u4, action, @p1)).to be_truthy
end
end
end
end
end
require 'spec_helper'
describe ProjectPolicy, models: true do
let(:project) { create(:empty_project, :public) }
let(:guest) { create(:user) }
let(:reporter) { create(:user) }
let(:dev) { create(:user) }
let(:master) { create(:user) }
let(:owner) { create(:user) }
let(:admin) { create(:admin) }
let(:users_ordered_by_permissions) do
[nil, guest, reporter, dev, master, owner, admin]
end
let(:users_permissions) do
users_ordered_by_permissions.map { |u| Ability.allowed(u, project).size }
end
before do
project.team << [guest, :guest]
project.team << [master, :master]
project.team << [dev, :developer]
project.team << [reporter, :reporter]
group = create(:group)
project.project_group_links.create(
group: group,
group_access: Gitlab::Access::MASTER)
group.add_owner(owner)
end
it 'returns increasing permissions for each level' do
expect(users_permissions).to eq(users_permissions.sort.uniq)
end
end
require 'spec_helper'
describe API::BroadcastMessages, api: true do
include ApiHelpers
let(:user) { create(:user) }
let(:admin) { create(:admin) }
describe 'GET /broadcast_messages' do
it 'returns a 401 for anonymous users' do
get api('/broadcast_messages')
expect(response).to have_http_status(401)
end
it 'returns a 403 for users' do
get api('/broadcast_messages', user)
expect(response).to have_http_status(403)
end
it 'returns an Array of BroadcastMessages for admins' do
create(:broadcast_message)
get api('/broadcast_messages', admin)
expect(response).to have_http_status(200)
expect(json_response).to be_kind_of(Array)
expect(json_response.first.keys)
.to match_array(%w(id message starts_at ends_at color font active))
end
end
describe 'GET /broadcast_messages/:id' do
let!(:message) { create(:broadcast_message) }
it 'returns a 401 for anonymous users' do
get api("/broadcast_messages/#{message.id}")
expect(response).to have_http_status(401)
end
it 'returns a 403 for users' do
get api("/broadcast_messages/#{message.id}", user)
expect(response).to have_http_status(403)
end
it 'returns the specified message for admins' do
get api("/broadcast_messages/#{message.id}", admin)
expect(response).to have_http_status(200)
expect(json_response['id']).to eq message.id
expect(json_response.keys)
.to match_array(%w(id message starts_at ends_at color font active))
end
end
describe 'POST /broadcast_messages' do
it 'returns a 401 for anonymous users' do
post api('/broadcast_messages'), attributes_for(:broadcast_message)
expect(response).to have_http_status(401)
end
it 'returns a 403 for users' do
post api('/broadcast_messages', user), attributes_for(:broadcast_message)
expect(response).to have_http_status(403)
end
context 'as an admin' do
it 'requires the `message` parameter' do
attrs = attributes_for(:broadcast_message)
attrs.delete(:message)
post api('/broadcast_messages', admin), attrs
expect(response).to have_http_status(400)
expect(json_response['error']).to eq 'message is missing'
end
it 'defines sane default start and end times' do
time = Time.zone.parse('2016-07-02 10:11:12')
travel_to(time) do
post api('/broadcast_messages', admin), message: 'Test message'
expect(response).to have_http_status(201)
expect(json_response['starts_at']).to eq '2016-07-02T10:11:12.000Z'
expect(json_response['ends_at']).to eq '2016-07-02T11:11:12.000Z'
end
end
it 'accepts a custom background and foreground color' do
attrs = attributes_for(:broadcast_message, color: '#000000', font: '#cecece')
post api('/broadcast_messages', admin), attrs
expect(response).to have_http_status(201)
expect(json_response['color']).to eq attrs[:color]
expect(json_response['font']).to eq attrs[:font]
end
end
end
describe 'PUT /broadcast_messages/:id' do
let!(:message) { create(:broadcast_message) }
it 'returns a 401 for anonymous users' do
put api("/broadcast_messages/#{message.id}"),
attributes_for(:broadcast_message)
expect(response).to have_http_status(401)
end
it 'returns a 403 for users' do
put api("/broadcast_messages/#{message.id}", user),
attributes_for(:broadcast_message)
expect(response).to have_http_status(403)
end
context 'as an admin' do
it 'accepts new background and foreground colors' do
attrs = { color: '#000000', font: '#cecece' }
put api("/broadcast_messages/#{message.id}", admin), attrs
expect(response).to have_http_status(200)
expect(json_response['color']).to eq attrs[:color]
expect(json_response['font']).to eq attrs[:font]
end
it 'accepts new start and end times' do
time = Time.zone.parse('2016-07-02 10:11:12')
travel_to(time) do
attrs = { starts_at: Time.zone.now, ends_at: 3.hours.from_now }
put api("/broadcast_messages/#{message.id}", admin), attrs
expect(response).to have_http_status(200)
expect(json_response['starts_at']).to eq '2016-07-02T10:11:12.000Z'
expect(json_response['ends_at']).to eq '2016-07-02T13:11:12.000Z'
end
end
it 'accepts a new message' do
attrs = { message: 'new message' }
put api("/broadcast_messages/#{message.id}", admin), attrs
expect(response).to have_http_status(200)
expect { message.reload }.to change { message.message }.to('new message')
end
end
end
describe 'DELETE /broadcast_messages/:id' do
let!(:message) { create(:broadcast_message) }
it 'returns a 401 for anonymous users' do
delete api("/broadcast_messages/#{message.id}"),
attributes_for(:broadcast_message)
expect(response).to have_http_status(401)
end
it 'returns a 403 for users' do
delete api("/broadcast_messages/#{message.id}", user),
attributes_for(:broadcast_message)
expect(response).to have_http_status(403)
end
it 'deletes the broadcast message for admins' do
expect { delete api("/broadcast_messages/#{message.id}", admin) }
.to change { BroadcastMessage.count }.by(-1)
end
end
end
......@@ -405,6 +405,7 @@ describe API::API, api: true do
expect(json_response['milestone']).to be_a Hash
expect(json_response['assignee']).to be_a Hash
expect(json_response['author']).to be_a Hash
expect(json_response['confidential']).to be_falsy
end
it "returns a project issue by id" do
......@@ -470,13 +471,51 @@ describe API::API, api: true do
end
describe "POST /projects/:id/issues" do
it "creates a new project issue" do
it 'creates a new project issue' do
post api("/projects/#{project.id}/issues", user),
title: 'new issue', labels: 'label, label2'
expect(response).to have_http_status(201)
expect(json_response['title']).to eq('new issue')
expect(json_response['description']).to be_nil
expect(json_response['labels']).to eq(['label', 'label2'])
expect(json_response['confidential']).to be_falsy
end
it 'creates a new confidential project issue' do
post api("/projects/#{project.id}/issues", user),
title: 'new issue', confidential: true
expect(response).to have_http_status(201)
expect(json_response['title']).to eq('new issue')
expect(json_response['confidential']).to be_truthy
end
it 'creates a new confidential project issue with a different param' do
post api("/projects/#{project.id}/issues", user),
title: 'new issue', confidential: 'y'
expect(response).to have_http_status(201)
expect(json_response['title']).to eq('new issue')
expect(json_response['confidential']).to be_truthy
end
it 'creates a public issue when confidential param is false' do
post api("/projects/#{project.id}/issues", user),
title: 'new issue', confidential: false
expect(response).to have_http_status(201)
expect(json_response['title']).to eq('new issue')
expect(json_response['confidential']).to be_falsy
end
it 'creates a public issue when confidential param is invalid' do
post api("/projects/#{project.id}/issues", user),
title: 'new issue', confidential: 'foo'
expect(response).to have_http_status(201)
expect(json_response['title']).to eq('new issue')
expect(json_response['confidential']).to be_falsy
end
it "sends notifications for subscribers of newly added labels" do
......@@ -632,6 +671,30 @@ describe API::API, api: true do
expect(response).to have_http_status(200)
expect(json_response['title']).to eq('updated title')
end
it 'sets an issue to confidential' do
put api("/projects/#{project.id}/issues/#{issue.id}", user),
confidential: true
expect(response).to have_http_status(200)
expect(json_response['confidential']).to be_truthy
end
it 'makes a confidential issue public' do
put api("/projects/#{project.id}/issues/#{confidential_issue.id}", user),
confidential: false
expect(response).to have_http_status(200)
expect(json_response['confidential']).to be_falsy
end
it 'does not update a confidential issue with wrong confidential flag' do
put api("/projects/#{project.id}/issues/#{confidential_issue.id}", user),
confidential: 'foo'
expect(response).to have_http_status(200)
expect(json_response['confidential']).to be_truthy
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