Commit fe43272f authored by Kirill Smelkov's avatar Kirill Smelkov

Sync with upstream 8-4-stable

* origin/8-4-stable:
  Version 8.4.4
  Merge branch 'variables-build-log' into 'master'
  Update CHANGELOG
  Merge branch 'backup-database-timeout-fix' into 'master'
  Fix build/permissions.feature tests adding missing steps
  Limit guest access builds
  Merge branch 'omniauth-saml-update' into 'master'
  Version 8.4.3
  Merge branch 'rs-relax-autosize' into 'master'
parents 6acfb924 9c31cc6f
Please view this file on the master branch, on stable branches it's out of date.
v 8.4.4
- Update omniauth-saml gem to 1.4.2
- Prevent long-running backup tasks from timing out the database connection
- Add a Project setting to allow guests to view build logs (defaults to true)
v 8.4.3
- Increase lfs_objects size column to 8-byte integer to allow files larger
than 2.1GB
......@@ -7,6 +12,9 @@ v 8.4.3
- Fix highlighting in blame view
- Update sentry-raven gem to prevent "Not a git repository" console output
when running certain commands
- Add instrumentation to additional Gitlab::Git and Rugged methods for
performance monitoring
- Allow autosize textareas to also be manually resized
v 8.4.2
- Bump required gitlab-workhorse version to bring in a fix for missing
......
......@@ -30,7 +30,7 @@ gem 'omniauth-github', '~> 1.1.1'
gem 'omniauth-gitlab', '~> 1.0.0'
gem 'omniauth-google-oauth2', '~> 0.2.0'
gem 'omniauth-kerberos', '~> 0.3.0', group: :kerberos
gem 'omniauth-saml', '~> 1.4.0'
gem 'omniauth-saml', '~> 1.4.2'
gem 'omniauth-shibboleth', '~> 1.2.0'
gem 'omniauth-twitter', '~> 1.2.0'
gem 'omniauth_crowd', '~> 2.2.0'
......
......@@ -534,9 +534,9 @@ GEM
omniauth-oauth2 (1.3.1)
oauth2 (~> 1.0)
omniauth (~> 1.2)
omniauth-saml (1.4.1)
omniauth-saml (1.4.2)
omniauth (~> 1.1)
ruby-saml (~> 1.0.0)
ruby-saml (~> 1.1, >= 1.1.1)
omniauth-shibboleth (1.2.1)
omniauth (>= 1.0.0)
omniauth-twitter (1.2.1)
......@@ -692,7 +692,7 @@ GEM
ruby-fogbugz (0.2.1)
crack (~> 0.4)
ruby-progressbar (1.7.5)
ruby-saml (1.0.0)
ruby-saml (1.1.1)
nokogiri (>= 1.5.10)
uuid (~> 2.3)
ruby2ruby (2.2.0)
......@@ -975,7 +975,7 @@ DEPENDENCIES
omniauth-gitlab (~> 1.0.0)
omniauth-google-oauth2 (~> 0.2.0)
omniauth-kerberos (~> 0.3.0)
omniauth-saml (~> 1.4.0)
omniauth-saml (~> 1.4.2)
omniauth-shibboleth (~> 1.2.0)
omniauth-twitter (~> 1.2.0)
omniauth_crowd (~> 2.2.0)
......
8.4.2
\ No newline at end of file
8.4.4
\ No newline at end of file
#= require jquery.ba-resize
#= require autosize
$ ->
autosize($('.js-autosize'))
$fields = $('.js-autosize')
$fields.on 'autosize:resized', ->
$field = $(@)
$field.data('height', $field.outerHeight())
$fields.on 'resize.autosize', ->
$field = $(@)
if $field.data('height') != $field.outerHeight()
$field.data('height', $field.outerHeight())
autosize.destroy($field)
$field.css('max-height', window.outerHeight)
autosize($fields)
autosize.update($fields)
$fields.css('resize', 'vertical')
......@@ -83,7 +83,7 @@
background: #FFF;
border: 1px solid #ddd;
min-height: 140px;
max-height: 430px;
max-height: 500px;
padding: 5px;
box-shadow: none;
width: 100%;
......
......@@ -147,7 +147,7 @@
.edit_note {
.markdown-area {
min-height: 140px;
max-height: 430px;
max-height: 500px;
}
.note-form-actions {
background: transparent;
......
......@@ -3,52 +3,5 @@ module Ci
def self.railtie_helpers_paths
"app/helpers/ci"
end
private
def authorize_access_project!
unless can?(current_user, :read_project, project)
return page_404
end
end
def authorize_manage_builds!
unless can?(current_user, :manage_builds, project)
return page_404
end
end
def authenticate_admin!
return render_404 unless current_user.is_admin?
end
def authorize_manage_project!
unless can?(current_user, :admin_project, project)
return page_404
end
end
def page_404
render file: "#{Rails.root}/public/404.html", status: 404, layout: false
end
def default_headers
headers['X-Frame-Options'] = 'DENY'
headers['X-XSS-Protection'] = '1; mode=block'
end
# JSON for infinite scroll via Pager object
def pager_json(partial, count)
html = render_to_string(
partial,
layout: false,
formats: [:html]
)
render json: {
html: html,
count: count
}
end
end
end
module Ci
class ProjectsController < Ci::ApplicationController
before_action :project, except: [:index]
before_action :authenticate_user!, except: [:index, :build, :badge]
before_action :authorize_access_project!, except: [:index, :badge]
before_action :project
before_action :authorize_read_project!, except: [:badge]
before_action :no_cache, only: [:badge]
protect_from_forgery
......
class Projects::ArtifactsController < Projects::ApplicationController
layout 'project'
before_action :authorize_read_build_artifacts!
before_action :authorize_read_build!
def download
unless artifacts_file.file_storage?
......@@ -43,14 +43,4 @@ class Projects::ArtifactsController < Projects::ApplicationController
def artifacts_file
@artifacts_file ||= build.artifacts_file
end
def authorize_read_build_artifacts!
unless can?(current_user, :read_build_artifacts, @project)
if current_user.nil?
return authenticate_user!
else
return render_404
end
end
end
end
class Projects::BuildsController < Projects::ApplicationController
before_action :build, except: [:index, :cancel_all]
before_action :authorize_manage_builds!, except: [:index, :show, :status]
before_action :authorize_read_build!, except: [:cancel, :cancel_all, :retry]
before_action :authorize_update_build!, except: [:index, :show, :status]
layout "project"
......@@ -69,10 +70,4 @@ class Projects::BuildsController < Projects::ApplicationController
def build_path(build)
namespace_project_build_path(build.project.namespace, build.project, build)
end
def authorize_manage_builds!
unless can?(current_user, :manage_builds, project)
return render_404
end
end
end
......@@ -4,10 +4,10 @@
class Projects::CommitController < Projects::ApplicationController
# Authorize
before_action :require_non_empty_project
before_action :authorize_download_code!, except: [:cancel_builds]
before_action :authorize_manage_builds!, only: [:cancel_builds]
before_action :authorize_download_code!, except: [:cancel_builds, :retry_builds]
before_action :authorize_update_build!, only: [:cancel_builds, :retry_builds]
before_action :authorize_read_commit_status!, only: [:builds]
before_action :commit
before_action :authorize_manage_builds!, only: [:cancel_builds, :retry_builds]
before_action :define_show_vars, only: [:show, :builds]
def show
......@@ -77,10 +77,4 @@ class Projects::CommitController < Projects::ApplicationController
@statuses = ci_commit.statuses if ci_commit
end
def authorize_manage_builds!
unless can?(current_user, :manage_builds, project)
return render_404
end
end
end
class Projects::RunnerProjectsController < Projects::ApplicationController
before_action :authorize_admin_project!
before_action :authorize_admin_build!
layout 'project_settings'
......
class Projects::RunnersController < Projects::ApplicationController
before_action :authorize_admin_build!
before_action :set_runner, only: [:edit, :update, :destroy, :pause, :resume, :show]
before_action :authorize_admin_project!
layout 'project_settings'
......
class Projects::TriggersController < Projects::ApplicationController
before_action :authorize_admin_project!
before_action :authorize_admin_build!
layout 'project_settings'
......
class Projects::VariablesController < Projects::ApplicationController
before_action :authorize_admin_project!
before_action :authorize_admin_build!
layout 'project_settings'
......
......@@ -223,6 +223,7 @@ class ProjectsController < ApplicationController
:issues_enabled, :merge_requests_enabled, :snippets_enabled, :issues_tracker_id, :default_branch,
:wiki_enabled, :visibility_level, :import_url, :last_activity_at, :namespace_id, :avatar,
:builds_enabled, :build_allow_git_fetch, :build_timeout_in_minutes, :build_coverage_regex,
:public_builds,
)
end
......
......@@ -126,7 +126,7 @@ module ProjectsHelper
nav_tabs << :merge_requests
end
if project.builds_enabled? && can?(current_user, :read_build, project)
if can?(current_user, :read_build, project)
nav_tabs << :builds
end
......
......@@ -5,17 +5,18 @@ class Ability
return [] unless user.is_a?(User)
return [] if user.blocked?
case subject.class.name
when "Project" then project_abilities(user, subject)
when "Issue" then issue_abilities(user, subject)
when "Note" then note_abilities(user, subject)
when "ProjectSnippet" then project_snippet_abilities(user, subject)
when "PersonalSnippet" then personal_snippet_abilities(user, subject)
when "MergeRequest" then merge_request_abilities(user, subject)
when "Group" then group_abilities(user, subject)
when "Namespace" then namespace_abilities(user, subject)
when "GroupMember" then group_member_abilities(user, subject)
when "ProjectMember" then project_member_abilities(user, subject)
case subject
when CommitStatus then commit_status_abilities(user, subject)
when Project then project_abilities(user, subject)
when Issue then issue_abilities(user, subject)
when Note then note_abilities(user, subject)
when ProjectSnippet then project_snippet_abilities(user, subject)
when PersonalSnippet then personal_snippet_abilities(user, subject)
when MergeRequest then merge_request_abilities(user, subject)
when Group then group_abilities(user, subject)
when Namespace then namespace_abilities(user, subject)
when GroupMember then group_member_abilities(user, subject)
when ProjectMember then project_member_abilities(user, subject)
else []
end.concat(global_abilities(user))
end
......@@ -25,6 +26,8 @@ class Ability
case true
when subject.is_a?(PersonalSnippet)
anonymous_personal_snippet_abilities(subject)
when subject.is_a?(CommitStatus)
anonymous_commit_status_abilities(subject)
when subject.is_a?(Project) || subject.respond_to?(:project)
anonymous_project_abilities(subject)
when subject.is_a?(Group) || subject.respond_to?(:group)
......@@ -52,16 +55,26 @@ class Ability
:read_project_member,
:read_merge_request,
:read_note,
:read_build,
:read_commit_status,
:download_code
]
# Allow to read builds by anonymous user if guests are allowed
rules << :read_build if project.public_builds?
rules - project_disabled_features_rules(project)
else
[]
end
end
def anonymous_commit_status_abilities(subject)
rules = anonymous_project_abilities(subject.project)
# If subject is Ci::Build which inherits from CommitStatus filter the abilities
rules = filter_build_abilities(rules) if subject.is_a?(Ci::Build)
rules
end
def anonymous_group_abilities(subject)
group = if subject.is_a?(Group)
subject
......@@ -113,6 +126,9 @@ class Ability
if project.public? || project.internal?
rules.push(*public_project_rules)
# Allow to read builds for internal projects
rules << :read_build if project.public_builds?
end
if project.owner == user || user.admin?
......@@ -134,7 +150,8 @@ class Ability
def public_project_rules
@public_project_rules ||= project_guest_rules + [
:download_code,
:fork_project
:fork_project,
:read_commit_status,
]
end
......@@ -149,7 +166,6 @@ class Ability
:read_project_member,
:read_merge_request,
:read_note,
:read_build,
:create_project,
:create_issue,
:create_note
......@@ -158,24 +174,26 @@ class Ability
def project_report_rules
@project_report_rules ||= project_guest_rules + [
:create_commit_status,
:read_commit_statuses,
:read_build_artifacts,
:download_code,
:fork_project,
:create_project_snippet,
:update_issue,
:admin_issue,
:admin_label
:admin_label,
:read_commit_status,
:read_build,
]
end
def project_dev_rules
@project_dev_rules ||= project_report_rules + [
:admin_merge_request,
:create_commit_status,
:update_commit_status,
:create_build,
:update_build,
:create_merge_request,
:create_wiki,
:manage_builds,
:push_code
]
end
......@@ -201,7 +219,9 @@ class Ability
:admin_merge_request,
:admin_note,
:admin_wiki,
:admin_project
:admin_project,
:admin_commit_status,
:admin_build
]
end
......@@ -240,6 +260,10 @@ class Ability
rules += named_abilities('wiki')
end
unless project.builds_enabled
rules += named_abilities('build')
end
rules
end
......@@ -376,6 +400,22 @@ class Ability
rules
end
def commit_status_abilities(user, subject)
rules = project_abilities(user, subject.project)
# If subject is Ci::Build which inherits from CommitStatus filter the abilities
rules = filter_build_abilities(rules) if subject.is_a?(Ci::Build)
rules
end
def filter_build_abilities(rules)
# 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|
rules.delete(:"#{rule}_commit_status") unless rules.include?(:"#{rule}_build")
end
rules
end
def abilities
@abilities ||= begin
abilities = Six.new
......
......@@ -4,7 +4,7 @@
= ci_status_with_icon(build.status)
%td.build-link
- if build.target_url
- if can?(current_user, :read_build, project) && build.target_url
= link_to build.target_url do
%strong Build ##{build.id}
- else
......@@ -60,10 +60,10 @@
%td
.pull-right
- if current_user && can?(current_user, :read_build_artifacts, project) && build.artifacts?
- if can?(current_user, :read_build, project) && build.artifacts?
= link_to build.artifacts_download_url, title: 'Download artifacts' do
%i.fa.fa-download
- if current_user && can?(current_user, :manage_builds, build.project)
- if can?(current_user, :update_build, build.project)
- if build.active?
- if build.cancel_url
= link_to build.cancel_url, method: :post, title: 'Cancel' do
......
......@@ -3,7 +3,7 @@
.project-issuable-filter
.controls
- if can?(current_user, :manage_builds, @project)
- if can?(current_user, :update_build, @project)
.pull-left.hidden-xs
- if @all_builds.running_or_pending.any?
= link_to 'Cancel running', cancel_all_namespace_project_builds_path(@project.namespace, @project), data: { confirm: 'Are you sure?' }, class: 'btn btn-danger', method: :post
......
......@@ -89,8 +89,7 @@
Test coverage
%h1 #{@build.coverage}%
- if current_user && can?(current_user, :read_build_artifacts, @project) && @build.artifacts?
- if can?(current_user, :read_build, @project) && @build.artifacts?
.build-widget.artifacts
%h4.title Build artifacts
.center
......@@ -102,7 +101,7 @@
.build-widget
%h4.title
Build ##{@build.id}
- if current_user && can?(current_user, :manage_builds, @project)
- if can?(current_user, :update_build, @project)
.pull-right
- if @build.cancel_url
= link_to "Cancel", @build.cancel_url, class: 'btn btn-sm btn-danger', method: :post
......
.gray-content-block.middle-block
.pull-right
- if can?(current_user, :manage_builds, @ci_commit.project)
- if can?(current_user, :update_build, @ci_commit.project)
- if @ci_commit.builds.latest.failed.any?(&:retryable?)
= link_to "Retry failed", retry_builds_namespace_project_commit_path(@ci_commit.project.namespace, @ci_commit.project, @ci_commit.sha), class: 'btn btn-grouped btn-primary', method: :post
......
%tr.commit_status
%td.status
- if commit_status.target_url
- if can?(current_user, :read_commit_status, commit_status) && commit_status.target_url
= link_to commit_status.target_url, class: "ci-status ci-#{commit_status.status}" do
= ci_icon_for_status(commit_status.status)
= commit_status.status
......@@ -8,7 +8,7 @@
= ci_status_with_icon(commit_status.status)
%td.commit_status-link
- if commit_status.target_url
- if can?(current_user, :read_commit_status, commit_status) && commit_status.target_url
= link_to commit_status.target_url do
%strong ##{commit_status.id}
- else
......@@ -66,10 +66,10 @@
%td
.pull-right
- if current_user && can?(current_user, :read_build_artifacts, commit_status.project) && commit_status.artifacts_download_url
- if can?(current_user, :read_commit_status, commit_status) && commit_status.artifacts_download_url
= link_to commit_status.artifacts_download_url, title: 'Download artifacts' do
%i.fa.fa-download
- if current_user && can?(current_user, :manage_builds, commit_status.project)
- if can?(current_user, :update_commit_status, commit_status)
- if commit_status.active?
- if commit_status.cancel_url
= link_to commit_status.cancel_url, method: :post, title: 'Cancel' do
......
......@@ -130,6 +130,7 @@
%strong git fetch
%br
%span.descr Faster
.form-group
= f.label :build_timeout_in_minutes, 'Timeout', class: 'control-label'
.col-sm-10
......@@ -158,6 +159,13 @@
phpunit --coverage-text --colors=never (PHP) -
%code ^\s*Lines:\s*\d+.\d+\%
.form-group
.col-sm-offset-2.col-sm-10
.checkbox
= f.label :public_builds do
= f.check_box :public_builds
%strong Public builds
.help-block Allow everyone to access builds for Public and Internal projects
%fieldset.features
%legend
......
......@@ -3,9 +3,11 @@
Secret Variables
%p.light
These variables will be set to environment by the runner and will be hidden in the build log.
These variables will be set to environment by the runner.
%br
So you can use them for passwords, secret keys or whatever you want.
%br
The value of the variable can be visible in build log if explicitly asked to do so.
%hr
......
......@@ -31,7 +31,7 @@ module Gitlab
config.encoding = "utf-8"
# Configure sensitive parameters which will be filtered from the log file.
config.filter_parameters.push(:password, :password_confirmation, :private_token, :otp_attempt)
config.filter_parameters.push(:password, :password_confirmation, :private_token, :otp_attempt, :variables)
# Enable escaping HTML in JSON.
config.active_support.escape_html_entities_in_json = true
......
class AddAllowGuestToAccessBuildsProject < ActiveRecord::Migration
def change
add_column :projects, :public_builds, :boolean, default: true, null: false
end
end
......@@ -11,7 +11,7 @@
#
# It's strongly recommended that you check this file into your version control system.
ActiveRecord::Schema.define(version: 20160128233227) do
ActiveRecord::Schema.define(version: 20160202164642) do
# These are extensions that must be enabled in order to support this database
enable_extension "plpgsql"
......@@ -679,6 +679,7 @@ ActiveRecord::Schema.define(version: 20160128233227) do
t.string "build_coverage_regex"
t.boolean "build_allow_git_fetch", default: true, null: false
t.integer "build_timeout", default: 3600, null: false
t.boolean "public_builds", default: true, null: false
end
add_index "projects", ["builds_enabled", "shared_runners_enabled"], name: "index_projects_on_builds_enabled_and_shared_runners_enabled", using: :btree
......
......@@ -79,7 +79,9 @@ Parameters:
"avatar_url": "http://example.com/uploads/project/avatar/4/uploads/avatar.png",
"shared_runners_enabled": true,
"forks_count": 0,
"star_count": 0
"star_count": 0,
"runners_token": "b8547b1dc37721d05889db52fa2f02",
"public_builds": true
},
{
"id": 6,
......@@ -136,7 +138,8 @@ Parameters:
"shared_runners_enabled": true,
"forks_count": 0,
"star_count": 0,
"runners_token": "b8547b1dc37721d05889db52fa2f02"
"runners_token": "b8547b1dc37721d05889db52fa2f02",
"public_builds": true
}
]
```
......@@ -420,6 +423,7 @@ Parameters:
- `public` (optional) - if `true` same as setting visibility_level = 20
- `visibility_level` (optional)
- `import_url` (optional)
- `public_builds` (optional)
### Create project for user
......@@ -442,6 +446,7 @@ Parameters:
- `public` (optional) - if `true` same as setting visibility_level = 20
- `visibility_level` (optional)
- `import_url` (optional)
- `public_builds` (optional)
### Edit project
......@@ -465,6 +470,7 @@ Parameters:
- `snippets_enabled` (optional)
- `public` (optional) - if `true` same as setting visibility_level = 20
- `visibility_level` (optional)
- `public_builds` (optional)
On success, method returns 200 with the updated project. If parameters are
invalid, 400 is returned.
......
......@@ -77,9 +77,12 @@ More information about Docker integration can be found in [Using Docker Images](
GitLab CI allows you to define per-project **Secure Variables** that are set in build environment.
The secure variables are stored out of the repository (the `.gitlab-ci.yml`).
These variables are securely stored in GitLab CI database and are hidden in the build log.
The variables are securely passed to GitLab Runner and are available in build environment.
It's desired method to use them for storing passwords, secret keys or whatever you want.
**The value of the variable can be shown in build log if explicitly asked to do so.**
If your project is public or internal you can make the builds private.
Secure Variables can added by going to `Project > Variables > Add Variable`.
They will be available for all subsequent builds.
......
......@@ -18,6 +18,9 @@ documentation](../workflow/add-user/add-user.md).
|---------------------------------------|---------|------------|-------------|----------|--------|
| Create new issue | ✓ | ✓ | ✓ | ✓ | ✓ |
| Leave comments | ✓ | ✓ | ✓ | ✓ | ✓ |
| See a list of builds | ✓ [^1] | ✓ | ✓ | ✓ | ✓ |
| See a build log | ✓ [^1] | ✓ | ✓ | ✓ | ✓ |
| Download and browse build artifacts | ✓ [^1] | ✓ | ✓ | ✓ | ✓ |
| Pull project code | | ✓ | ✓ | ✓ | ✓ |
| Download project | | ✓ | ✓ | ✓ | ✓ |
| Create code snippets | | ✓ | ✓ | ✓ | ✓ |
......@@ -31,6 +34,7 @@ documentation](../workflow/add-user/add-user.md).
| Remove non-protected branches | | | ✓ | ✓ | ✓ |
| Add tags | | | ✓ | ✓ | ✓ |
| Write a wiki | | | ✓ | ✓ | ✓ |
| Cancel and retry builds | | | ✓ | ✓ | ✓ |
| Create new milestones | | | | ✓ | ✓ |
| Add new team members | | | | ✓ | ✓ |
| Push to protected branches | | | | ✓ | ✓ |
......@@ -40,12 +44,17 @@ documentation](../workflow/add-user/add-user.md).
| Edit project | | | | ✓ | ✓ |
| Add deploy keys to project | | | | ✓ | ✓ |
| Configure project hooks | | | | ✓ | ✓ |
| Manage runners | | | | ✓ | ✓ |
| Manage build triggers | | | | ✓ | ✓ |
| Manage variables | | | | ✓ | ✓ |
| Switch visibility level | | | | | ✓ |
| Transfer project to another namespace | | | | | ✓ |
| Remove project | | | | | ✓ |
| Force push to protected branches | | | | | |
| Remove protected branches | | | | | |
[^1]: If **Allow guest to access builds** is enabled in CI settings
## Group
In order for a group to appear as public and be browsable, it must contain at
......
......@@ -5,6 +5,41 @@ Feature: Project Builds Permissions
And project has CI enabled
And project has a recent build
Scenario: I try to visit build details as guest
Given I am member of a project with a guest role
When I visit recent build summary page
Then page status code should be 404
Scenario: I try to visit project builds page as guest
Given I am member of a project with a guest role
When I visit project builds page
Then page status code should be 404
Scenario: I try to visit build details of internal project without access to builds
Given The project is internal
And public access for builds is disabled
When I visit recent build summary page
Then page status code should be 404
Scenario: I try to visit internal project builds page without access to builds
Given The project is internal
And public access for builds is disabled
When I visit project builds page
Then page status code should be 404
Scenario: I try to visit build details of internal project with access to builds
Given The project is internal
And public access for builds is enabled
When I visit recent build summary page
Then I see details of a build
And I see build trace
Scenario: I try to visit internal project builds page with access to builds
Given The project is internal
And public access for builds is enabled
When I visit project builds page
Then I see the build
Scenario: I try to download build artifacts as guest
Given I am member of a project with a guest role
And recent build has artifacts available
......
......@@ -14,6 +14,10 @@ module SharedBuilds
visit namespace_project_build_path(@project.namespace, @project, @build)
end
step 'I visit project builds page' do
visit namespace_project_builds_path(@project.namespace, @project)
end
step 'recent build has artifacts available' do
artifacts = Rails.root + 'spec/fixtures/ci_build_artifacts.zip'
archive = fixture_file_upload(artifacts, 'application/zip')
......@@ -34,4 +38,21 @@ module SharedBuilds
step 'I access artifacts download page' do
visit download_namespace_project_build_artifacts_path(@project.namespace, @project, @build)
end
step 'I see details of a build' do
expect(page).to have_content "Build ##{@build.id}"
end
step 'I see build trace' do
expect(page).to have_css '#build-trace'
end
step 'I see the build' do
page.within('.commit_status') do
expect(page).to have_content "##{@build.id}"
expect(page).to have_content @build.sha[0..7]
expect(page).to have_content @build.ref
expect(page).to have_content @build.name
end
end
end
......@@ -240,6 +240,18 @@ module SharedProject
end
end
step 'The project is internal' do
@project.update(visibility_level: Gitlab::VisibilityLevel::INTERNAL)
end
step 'public access for builds is enabled' do
@project.update(public_builds: true)
end
step 'public access for builds is disabled' do
@project.update(public_builds: false)
end
def user_owns_project(user_name:, project_name:, visibility: :private)
user = user_exists(user_name, username: user_name.gsub(/\s/, '').underscore)
project = Project.find_by(name: project_name)
......
......@@ -13,11 +13,12 @@ module API
# Example Request:
# GET /projects/:id/builds
get ':id/builds' do
builds = user_project.builds.order('id DESC')
builds = filter_builds(builds, params[:scope])
present paginate(builds), with: Entities::Build,
user_can_download_artifacts: can?(current_user, :download_build_artifacts, user_project)
user_can_download_artifacts: can?(current_user, :read_build, user_project)
end
# Get builds for a specific commit of a project
......@@ -30,6 +31,8 @@ module API
# Example Request:
# GET /projects/:id/repository/commits/:sha/builds
get ':id/repository/commits/:sha/builds' do
authorize_read_builds!
commit = user_project.ci_commits.find_by_sha(params[:sha])
return not_found! unless commit
......@@ -37,7 +40,7 @@ module API
builds = filter_builds(builds, params[:scope])
present paginate(builds), with: Entities::Build,
user_can_download_artifacts: can?(current_user, :download_build_artifacts, user_project)
user_can_download_artifacts: can?(current_user, :read_build, user_project)
end
# Get a specific build of a project
......@@ -48,11 +51,13 @@ module API
# Example Request:
# GET /projects/:id/builds/:build_id
get ':id/builds/:build_id' do
authorize_read_builds!
build = get_build(params[:build_id])
return not_found!(build) unless build
present build, with: Entities::Build,
user_can_download_artifacts: can?(current_user, :download_build_artifacts, user_project)
user_can_download_artifacts: can?(current_user, :read_build, user_project)
end
# Get a trace of a specific build of a project
......@@ -67,6 +72,8 @@ module API
# is saved in the DB instead of file). But before that, we need to consider how to replace the value of
# `runners_token` with some mask (like `xxxxxx`) when sending trace file directly by workhorse.
get ':id/builds/:build_id/trace' do
authorize_read_builds!
build = get_build(params[:build_id])
return not_found!(build) unless build
......@@ -86,7 +93,7 @@ module API
# example request:
# post /projects/:id/build/:build_id/cancel
post ':id/builds/:build_id/cancel' do
authorize_manage_builds!
authorize_update_builds!
build = get_build(params[:build_id])
return not_found!(build) unless build
......@@ -94,7 +101,7 @@ module API
build.cancel
present build, with: Entities::Build,
user_can_download_artifacts: can?(current_user, :download_build_artifacts, user_project)
user_can_download_artifacts: can?(current_user, :read_build, user_project)
end
# Retry a specific build of a project
......@@ -105,7 +112,7 @@ module API
# example request:
# post /projects/:id/build/:build_id/retry
post ':id/builds/:build_id/retry' do
authorize_manage_builds!
authorize_update_builds!
build = get_build(params[:build_id])
return forbidden!('Build is not retryable') unless build && build.retryable?
......@@ -113,7 +120,7 @@ module API
build = Ci::Build.retry(build)
present build, with: Entities::Build,
user_can_download_artifacts: can?(current_user, :download_build_artifacts, user_project)
user_can_download_artifacts: can?(current_user, :read_build, user_project)
end
end
......@@ -141,8 +148,12 @@ module API
builds.where(status: available_statuses && scope)
end
def authorize_manage_builds!
authorize! :manage_builds, user_project
def authorize_read_builds!
authorize! :read_build, user_project
end
def authorize_update_builds!
authorize! :update_build, user_project
end
end
end
......
......@@ -18,7 +18,7 @@ module API
# Examples:
# GET /projects/:id/repository/commits/:sha/statuses
get ':id/repository/commits/:sha/statuses' do
authorize! :read_commit_statuses, user_project
authorize! :read_commit_status, user_project
sha = params[:sha]
ci_commit = user_project.ci_commit(sha)
not_found! 'Commit' unless ci_commit
......
......@@ -72,6 +72,7 @@ module API
expose :star_count, :forks_count
expose :open_issues_count, if: lambda { |project, options| project.issues_enabled? && project.default_issues_tracker? }
expose :runners_token, if: lambda { |_project, options| options[:user_can_admin_project] }
expose :public_builds
end
class ProjectMember < UserBasic
......@@ -383,7 +384,7 @@ module API
# for downloading of artifacts (see: https://gitlab.com/gitlab-org/gitlab-ce/issues/4255)
expose :download_url do |repo_obj, options|
if options[:user_can_download_artifacts]
repo_obj.download_url
repo_obj.artifacts_download_url
end
end
expose :commit, with: RepoCommit do |repo_obj, _options|
......
......@@ -99,6 +99,7 @@ module API
# public (optional) - if true same as setting visibility_level = 20
# visibility_level (optional) - 0 by default
# import_url (optional)
# public_builds (optional)
# Example Request
# POST /projects
post do
......@@ -115,7 +116,8 @@ module API
:namespace_id,
:public,
:visibility_level,
:import_url]
:import_url,
:public_builds]
attrs = map_public_to_visibility_level(attrs)
@project = ::Projects::CreateService.new(current_user, attrs).execute
if @project.saved?
......@@ -145,6 +147,7 @@ module API
# public (optional) - if true same as setting visibility_level = 20
# visibility_level (optional)
# import_url (optional)
# public_builds (optional)
# Example Request
# POST /projects/user/:user_id
post "user/:user_id" do
......@@ -161,7 +164,8 @@ module API
:shared_runners_enabled,
:public,
:visibility_level,
:import_url]
:import_url,
:public_builds]
attrs = map_public_to_visibility_level(attrs)
@project = ::Projects::CreateService.new(user, attrs).execute
if @project.saved?
......@@ -205,6 +209,7 @@ module API
# shared_runners_enabled (optional)
# public (optional) - if true same as setting visibility_level = 20
# visibility_level (optional) - visibility level of a project
# public_builds (optional)
# Example Request
# PUT /projects/:id
put ':id' do
......@@ -219,7 +224,8 @@ module API
:snippets_enabled,
:shared_runners_enabled,
:public,
:visibility_level]
:visibility_level,
:public_builds]
attrs = map_public_to_visibility_level(attrs)
authorize_admin_project
authorize! :rename_project, user_project if attrs[:name].present?
......
......@@ -54,7 +54,7 @@ module API
# GET /projects/:id/triggers
get ':id/triggers' do
authenticate!
authorize_admin_project
authorize! :admin_build, user_project
triggers = user_project.triggers.includes(:trigger_requests)
triggers = paginate(triggers)
......@@ -71,7 +71,7 @@ module API
# GET /projects/:id/triggers/:token
get ':id/triggers/:token' do
authenticate!
authorize_admin_project
authorize! :admin_build, user_project
trigger = user_project.triggers.find_by(token: params[:token].to_s)
return not_found!('Trigger') unless trigger
......@@ -87,7 +87,7 @@ module API
# POST /projects/:id/triggers
post ':id/triggers' do
authenticate!
authorize_admin_project
authorize! :admin_build, user_project
trigger = user_project.triggers.create
......@@ -103,7 +103,7 @@ module API
# DELETE /projects/:id/triggers/:token
delete ':id/triggers/:token' do
authenticate!
authorize_admin_project
authorize! :admin_build, user_project
trigger = user_project.triggers.find_by(token: params[:token].to_s)
return not_found!('Trigger') unless trigger
......
......@@ -2,7 +2,7 @@ module API
# Projects variables API
class Variables < Grape::API
before { authenticate! }
before { authorize_admin_project }
before { authorize! :admin_build, user_project }
resource :projects do
# Get project variables
......
module Backup
class Manager
def pack
# Make sure there is a connection
ActiveRecord::Base.connection.reconnect!
# saving additional informations
s = {}
s[:db_version] = "#{ActiveRecord::Migrator.current_version}"
......
......@@ -8,7 +8,7 @@ describe "Builds" do
@commit = FactoryGirl.create :ci_commit
@build = FactoryGirl.create :ci_build, commit: @commit
@project = @commit.project
@project.team << [@user, :master]
@project.team << [@user, :developer]
end
describe "GET /:project/builds" do
......
......@@ -8,7 +8,6 @@ describe 'Commits' do
describe 'CI' do
before do
login_as :user
project.team << [@user, :master]
stub_ci_commit_to_return_yaml_file
end
......@@ -19,6 +18,10 @@ describe 'Commits' do
context 'commit status is Generic Commit Status' do
let!(:status) { FactoryGirl.create :generic_commit_status, commit: commit }
before do
project.team << [@user, :reporter]
end
describe 'Commit builds' do
before do
visit ci_status_path(commit)
......@@ -37,85 +40,126 @@ describe 'Commits' do
context 'commit status is Ci Build' do
let!(:build) { FactoryGirl.create :ci_build, commit: commit }
let(:artifacts_file) { fixture_file_upload(Rails.root + 'spec/fixtures/banana_sample.gif', 'image/gif') }
describe 'Project commits' do
context 'when logged as developer' do
before do
visit namespace_project_commits_path(project.namespace, project, :master)
project.team << [@user, :developer]
end
it 'should show build status' do
page.within("//li[@id='commit-#{commit.short_sha}']") do
expect(page).to have_css(".ci-status-link")
describe 'Project commits' do
before do
visit namespace_project_commits_path(project.namespace, project, :master)
end
end
end
describe 'Commit builds' do
before do
visit ci_status_path(commit)
it 'should show build status' do
page.within("//li[@id='commit-#{commit.short_sha}']") do
expect(page).to have_css(".ci-status-link")
end
end
end
it { expect(page).to have_content commit.sha[0..7] }
it { expect(page).to have_content commit.git_commit_message }
it { expect(page).to have_content commit.git_author_name }
end
context 'Download artifacts' do
let(:artifacts_file) { fixture_file_upload(Rails.root + 'spec/fixtures/banana_sample.gif', 'image/gif') }
before do
build.update_attributes(artifacts_file: artifacts_file)
end
describe 'Commit builds' do
before do
visit ci_status_path(commit)
end
it do
visit ci_status_path(commit)
click_on 'Download artifacts'
expect(page.response_headers['Content-Type']).to eq(artifacts_file.content_type)
it { expect(page).to have_content commit.sha[0..7] }
it { expect(page).to have_content commit.git_commit_message }
it { expect(page).to have_content commit.git_author_name }
end
end
describe 'Cancel all builds' do
it 'cancels commit' do
visit ci_status_path(commit)
click_on 'Cancel running'
expect(page).to have_content 'canceled'
end
end
context 'Download artifacts' do
before do
build.update_attributes(artifacts_file: artifacts_file)
end
describe 'Cancel build' do
it 'cancels build' do
visit ci_status_path(commit)
click_on 'Cancel'
expect(page).to have_content 'canceled'
it do
visit ci_status_path(commit)
click_on 'Download artifacts'
expect(page.response_headers['Content-Type']).to eq(artifacts_file.content_type)
end
end
end
describe '.gitlab-ci.yml not found warning' do
context 'ci builds enabled' do
it "does not show warning" do
describe 'Cancel all builds' do
it 'cancels commit' do
visit ci_status_path(commit)
expect(page).not_to have_content '.gitlab-ci.yml not found in this commit'
click_on 'Cancel running'
expect(page).to have_content 'canceled'
end
end
it 'shows warning' do
stub_ci_commit_yaml_file(nil)
describe 'Cancel build' do
it 'cancels build' do
visit ci_status_path(commit)
expect(page).to have_content '.gitlab-ci.yml not found in this commit'
click_on 'Cancel'
expect(page).to have_content 'canceled'
end
end
context 'ci builds disabled' do
before do
stub_ci_builds_disabled
stub_ci_commit_yaml_file(nil)
visit ci_status_path(commit)
describe '.gitlab-ci.yml not found warning' do
context 'ci builds enabled' do
it "does not show warning" do
visit ci_status_path(commit)
expect(page).not_to have_content '.gitlab-ci.yml not found in this commit'
end
it 'shows warning' do
stub_ci_commit_yaml_file(nil)
visit ci_status_path(commit)
expect(page).to have_content '.gitlab-ci.yml not found in this commit'
end
end
it 'does not show warning' do
expect(page).not_to have_content '.gitlab-ci.yml not found in this commit'
context 'ci builds disabled' do
before do
stub_ci_builds_disabled
stub_ci_commit_yaml_file(nil)
visit ci_status_path(commit)
end
it 'does not show warning' do
expect(page).not_to have_content '.gitlab-ci.yml not found in this commit'
end
end
end
end
context "when logged as reporter" do
before do
project.team << [@user, :reporter]
build.update_attributes(artifacts_file: artifacts_file)
visit ci_status_path(commit)
end
it do
expect(page).to have_content commit.sha[0..7]
expect(page).to have_content commit.git_commit_message
expect(page).to have_content commit.git_author_name
expect(page).to have_link('Download artifacts')
expect(page).to_not have_link('Cancel running')
expect(page).to_not have_link('Retry failed')
end
end
context 'when accessing internal project with disallowed access' do
before do
project.update(
visibility_level: Gitlab::VisibilityLevel::INTERNAL,
public_builds: false)
build.update_attributes(artifacts_file: artifacts_file)
visit ci_status_path(commit)
end
it do
expect(page).to have_content commit.sha[0..7]
expect(page).to have_content commit.git_commit_message
expect(page).to have_content commit.git_author_name
expect(page).to_not have_link('Download artifacts')
expect(page).to_not have_link('Cancel running')
expect(page).to_not have_link('Retry failed')
end
end
end
end
end
......@@ -96,6 +96,60 @@ describe "Public Project Access", feature: true do
it { is_expected.to be_denied_for :visitor }
end
describe "GET /:project_path/builds" do
subject { namespace_project_builds_path(project.namespace, project) }
context "when allowed for public" do
before { project.update(public_builds: true) }
it { is_expected.to be_allowed_for master }
it { is_expected.to be_allowed_for reporter }
it { is_expected.to be_allowed_for :admin }
it { is_expected.to be_allowed_for guest }
it { is_expected.to be_allowed_for :user }
it { is_expected.to be_allowed_for :visitor }
end
context "when disallowed for public" do
before { project.update(public_builds: false) }
it { is_expected.to be_allowed_for master }
it { is_expected.to be_allowed_for reporter }
it { is_expected.to be_allowed_for :admin }
it { is_expected.to be_denied_for guest }
it { is_expected.to be_denied_for :user }
it { is_expected.to be_denied_for :visitor }
end
end
describe "GET /:project_path/builds/:id" do
let(:commit) { create(:ci_commit, project: project) }
let(:build) { create(:ci_build, commit: commit) }
subject { namespace_project_build_path(project.namespace, project, build.id) }
context "when allowed for public" do
before { project.update(public_builds: true) }
it { is_expected.to be_allowed_for master }
it { is_expected.to be_allowed_for reporter }
it { is_expected.to be_allowed_for :admin }
it { is_expected.to be_allowed_for guest }
it { is_expected.to be_allowed_for :user }
it { is_expected.to be_allowed_for :visitor }
end
context "when disallowed for public" do
before { project.update(public_builds: false) }
it { is_expected.to be_allowed_for master }
it { is_expected.to be_allowed_for reporter }
it { is_expected.to be_allowed_for :admin }
it { is_expected.to be_denied_for guest }
it { is_expected.to be_denied_for :user }
it { is_expected.to be_denied_for :visitor }
end
end
describe "GET /:project_path/blob" do
before do
commit = project.repository.commit
......
#= require behaviors/autosize
describe 'Autosize behavior', ->
beforeEach ->
fixture.set('<textarea class="js-autosize" style="resize: vertical"></textarea>')
it 'does not overwrite the resize property', ->
load()
expect($('textarea')).toHaveCss(resize: 'vertical')
load = -> $(document).trigger('page:load')
......@@ -113,7 +113,7 @@ describe API::API, api: true do
describe 'POST /projects/:id/builds/:build_id/cancel' do
context 'authorized user' do
context 'user with :manage_builds persmission' do
context 'user with :update_build persmission' do
it 'should cancel running or pending build' do
post api("/projects/#{project.id}/builds/#{build.id}/cancel", user)
......@@ -122,7 +122,7 @@ describe API::API, api: true do
end
end
context 'user without :manage_builds permission' do
context 'user without :update_build permission' do
it 'should not cancel build' do
post api("/projects/#{project.id}/builds/#{build.id}/cancel", user2)
......@@ -142,7 +142,7 @@ describe API::API, api: true do
describe 'POST /projects/:id/builds/:build_id/retry' do
context 'authorized user' do
context 'user with :manage_builds persmission' do
context 'user with :update_build persmission' do
it 'should retry non-running build' do
post api("/projects/#{project.id}/builds/#{build_canceled.id}/retry", user)
......@@ -152,7 +152,7 @@ describe API::API, api: true do
end
end
context 'user without :manage_builds permission' do
context 'user without :update_build permission' do
it 'should not retry build' do
post api("/projects/#{project.id}/builds/#{build_canceled.id}/retry", user2)
......
......@@ -2,18 +2,17 @@ require 'spec_helper'
describe API::CommitStatus, api: true do
include ApiHelpers
let(:user) { create(:user) }
let(:user2) { create(:user) }
let!(:project) { create(:project, creator_id: user.id) }
let!(:reporter) { create(:project_member, user: user, project: project, access_level: ProjectMember::REPORTER) }
let!(:guest) { create(:project_member, user: user2, project: project, access_level: ProjectMember::GUEST) }
let!(:project) { create(:project) }
let(:commit) { project.repository.commit }
let!(:ci_commit) { project.ensure_ci_commit(commit.id) }
let(:commit_status) { create(:commit_status, commit: ci_commit) }
let(:guest) { create_user(ProjectMember::GUEST) }
let(:reporter) { create_user(ProjectMember::REPORTER) }
let(:developer) { create_user(ProjectMember::DEVELOPER) }
describe "GET /projects/:id/repository/commits/:sha/statuses" do
it_behaves_like 'a paginated resources' do
let(:request) { get api("/projects/#{project.id}/repository/commits/#{commit.id}/statuses", user) }
let(:request) { get api("/projects/#{project.id}/repository/commits/#{commit.id}/statuses", reporter) }
end
context "reporter user" do
......@@ -29,7 +28,7 @@ describe API::CommitStatus, api: true do
end
it "should return latest commit statuses" do
get api("/projects/#{project.id}/repository/commits/#{commit.id}/statuses", user)
get api("/projects/#{project.id}/repository/commits/#{commit.id}/statuses", reporter)
expect(response.status).to eq(200)
expect(json_response).to be_an Array
......@@ -39,7 +38,7 @@ describe API::CommitStatus, api: true do
end
it "should return all commit statuses" do
get api("/projects/#{project.id}/repository/commits/#{commit.id}/statuses?all=1", user)
get api("/projects/#{project.id}/repository/commits/#{commit.id}/statuses?all=1", reporter)
expect(response.status).to eq(200)
expect(json_response).to be_an Array
......@@ -47,7 +46,7 @@ describe API::CommitStatus, api: true do
end
it "should return latest commit statuses for specific ref" do
get api("/projects/#{project.id}/repository/commits/#{commit.id}/statuses?ref=develop", user)
get api("/projects/#{project.id}/repository/commits/#{commit.id}/statuses?ref=develop", reporter)
expect(response.status).to eq(200)
expect(json_response).to be_an Array
......@@ -55,7 +54,7 @@ describe API::CommitStatus, api: true do
end
it "should return latest commit statuses for specific name" do
get api("/projects/#{project.id}/repository/commits/#{commit.id}/statuses?name=coverage", user)
get api("/projects/#{project.id}/repository/commits/#{commit.id}/statuses?name=coverage", reporter)
expect(response.status).to eq(200)
expect(json_response).to be_an Array
......@@ -65,7 +64,7 @@ describe API::CommitStatus, api: true do
context "guest user" do
it "should not return project commits" do
get api("/projects/#{project.id}/repository/commits/#{commit.id}/statuses", user2)
get api("/projects/#{project.id}/repository/commits/#{commit.id}/statuses", guest)
expect(response.status).to eq(403)
end
end
......@@ -81,10 +80,10 @@ describe API::CommitStatus, api: true do
describe 'POST /projects/:id/statuses/:sha' do
let(:post_url) { "/projects/#{project.id}/statuses/#{commit.id}" }
context 'reporter user' do
context 'developer user' do
context 'should create commit status' do
it 'with only required parameters' do
post api(post_url, user), state: 'success'
post api(post_url, developer), state: 'success'
expect(response.status).to eq(201)
expect(json_response['sha']).to eq(commit.id)
expect(json_response['status']).to eq('success')
......@@ -95,7 +94,7 @@ describe API::CommitStatus, api: true do
end
it 'with all optional parameters' do
post api(post_url, user), state: 'success', context: 'coverage', ref: 'develop', target_url: 'url', description: 'test'
post api(post_url, developer), state: 'success', context: 'coverage', ref: 'develop', target_url: 'url', description: 'test'
expect(response.status).to eq(201)
expect(json_response['sha']).to eq(commit.id)
expect(json_response['status']).to eq('success')
......@@ -108,25 +107,32 @@ describe API::CommitStatus, api: true do
context 'should not create commit status' do
it 'with invalid state' do
post api(post_url, user), state: 'invalid'
post api(post_url, developer), state: 'invalid'
expect(response.status).to eq(400)
end
it 'without state' do
post api(post_url, user)
post api(post_url, developer)
expect(response.status).to eq(400)
end
it 'invalid commit' do
post api("/projects/#{project.id}/statuses/invalid_sha", user), state: 'running'
post api("/projects/#{project.id}/statuses/invalid_sha", developer), state: 'running'
expect(response.status).to eq(404)
end
end
end
context 'reporter user' do
it 'should not create commit status' do
post api(post_url, reporter)
expect(response.status).to eq(403)
end
end
context 'guest user' do
it 'should not create commit status' do
post api(post_url, user2)
post api(post_url, guest)
expect(response.status).to eq(403)
end
end
......@@ -138,4 +144,10 @@ describe API::CommitStatus, api: true do
end
end
end
def create_user(access_level)
user = create(:user)
create(:project_member, user: user, project: project, access_level: access_level)
user
end
end
/*!
* jQuery resize event - v1.1 - 3/14/2010
* http://benalman.com/projects/jquery-resize-plugin/
*
* Copyright (c) 2010 "Cowboy" Ben Alman
* Dual licensed under the MIT and GPL licenses.
* http://benalman.com/about/license/
*/
// Script: jQuery resize event
//
// *Version: 1.1, Last updated: 3/14/2010*
//
// Project Home - http://benalman.com/projects/jquery-resize-plugin/
// GitHub - http://github.com/cowboy/jquery-resize/
// Source - http://github.com/cowboy/jquery-resize/raw/master/jquery.ba-resize.js
// (Minified) - http://github.com/cowboy/jquery-resize/raw/master/jquery.ba-resize.min.js (1.0kb)
//
// About: License
//
// Copyright (c) 2010 "Cowboy" Ben Alman,
// Dual licensed under the MIT and GPL licenses.
// http://benalman.com/about/license/
//
// About: Examples
//
// This working example, complete with fully commented code, illustrates a few
// ways in which this plugin can be used.
//
// resize event - http://benalman.com/code/projects/jquery-resize/examples/resize/
//
// About: Support and Testing
//
// Information about what version or versions of jQuery this plugin has been
// tested with, what browsers it has been tested in, and where the unit tests
// reside (so you can test it yourself).
//
// jQuery Versions - 1.3.2, 1.4.1, 1.4.2
// Browsers Tested - Internet Explorer 6-8, Firefox 2-3.6, Safari 3-4, Chrome, Opera 9.6-10.1.
// Unit Tests - http://benalman.com/code/projects/jquery-resize/unit/
//
// About: Release History
//
// 1.1 - (3/14/2010) Fixed a minor bug that was causing the event to trigger
// immediately after bind in some circumstances. Also changed $.fn.data
// to $.data to improve performance.
// 1.0 - (2/10/2010) Initial release
(function($,window,undefined){
'$:nomunge'; // Used by YUI compressor.
// A jQuery object containing all non-window elements to which the resize
// event is bound.
var elems = $([]),
// Extend $.resize if it already exists, otherwise create it.
jq_resize = $.resize = $.extend( $.resize, {} ),
timeout_id,
// Reused strings.
str_setTimeout = 'setTimeout',
str_resize = 'resize',
str_data = str_resize + '-special-event',
str_delay = 'delay',
str_throttle = 'throttleWindow';
// Property: jQuery.resize.delay
//
// The numeric interval (in milliseconds) at which the resize event polling
// loop executes. Defaults to 250.
jq_resize[ str_delay ] = 250;
// Property: jQuery.resize.throttleWindow
//
// Throttle the native window object resize event to fire no more than once
// every <jQuery.resize.delay> milliseconds. Defaults to true.
//
// Because the window object has its own resize event, it doesn't need to be
// provided by this plugin, and its execution can be left entirely up to the
// browser. However, since certain browsers fire the resize event continuously
// while others do not, enabling this will throttle the window resize event,
// making event behavior consistent across all elements in all browsers.
//
// While setting this property to false will disable window object resize
// event throttling, please note that this property must be changed before any
// window object resize event callbacks are bound.
jq_resize[ str_throttle ] = true;
// Event: resize event
//
// Fired when an element's width or height changes. Because browsers only
// provide this event for the window element, for other elements a polling
// loop is initialized, running every <jQuery.resize.delay> milliseconds
// to see if elements' dimensions have changed. You may bind with either
// .resize( fn ) or .bind( "resize", fn ), and unbind with .unbind( "resize" ).
//
// Usage:
//
// > jQuery('selector').bind( 'resize', function(e) {
// > // element's width or height has changed!
// > ...
// > });
//
// Additional Notes:
//
// * The polling loop is not created until at least one callback is actually
// bound to the 'resize' event, and this single polling loop is shared
// across all elements.
//
// Double firing issue in jQuery 1.3.2:
//
// While this plugin works in jQuery 1.3.2, if an element's event callbacks
// are manually triggered via .trigger( 'resize' ) or .resize() those
// callbacks may double-fire, due to limitations in the jQuery 1.3.2 special
// events system. This is not an issue when using jQuery 1.4+.
//
// > // While this works in jQuery 1.4+
// > $(elem).css({ width: new_w, height: new_h }).resize();
// >
// > // In jQuery 1.3.2, you need to do this:
// > var elem = $(elem);
// > elem.css({ width: new_w, height: new_h });
// > elem.data( 'resize-special-event', { width: elem.width(), height: elem.height() } );
// > elem.resize();
$.event.special[ str_resize ] = {
// Called only when the first 'resize' event callback is bound per element.
setup: function() {
// Since window has its own native 'resize' event, return false so that
// jQuery will bind the event using DOM methods. Since only 'window'
// objects have a .setTimeout method, this should be a sufficient test.
// Unless, of course, we're throttling the 'resize' event for window.
if ( !jq_resize[ str_throttle ] && this[ str_setTimeout ] ) { return false; }
var elem = $(this);
// Add this element to the list of internal elements to monitor.
elems = elems.add( elem );
// Initialize data store on the element.
$.data( this, str_data, { w: elem.width(), h: elem.height() } );
// If this is the first element added, start the polling loop.
if ( elems.length === 1 ) {
loopy();
}
},
// Called only when the last 'resize' event callback is unbound per element.
teardown: function() {
// Since window has its own native 'resize' event, return false so that
// jQuery will unbind the event using DOM methods. Since only 'window'
// objects have a .setTimeout method, this should be a sufficient test.
// Unless, of course, we're throttling the 'resize' event for window.
if ( !jq_resize[ str_throttle ] && this[ str_setTimeout ] ) { return false; }
var elem = $(this);
// Remove this element from the list of internal elements to monitor.
elems = elems.not( elem );
// Remove any data stored on the element.
elem.removeData( str_data );
// If this is the last element removed, stop the polling loop.
if ( !elems.length ) {
clearTimeout( timeout_id );
}
},
// Called every time a 'resize' event callback is bound per element (new in
// jQuery 1.4).
add: function( handleObj ) {
// Since window has its own native 'resize' event, return false so that
// jQuery doesn't modify the event object. Unless, of course, we're
// throttling the 'resize' event for window.
if ( !jq_resize[ str_throttle ] && this[ str_setTimeout ] ) { return false; }
var old_handler;
// The new_handler function is executed every time the event is triggered.
// This is used to update the internal element data store with the width
// and height when the event is triggered manually, to avoid double-firing
// of the event callback. See the "Double firing issue in jQuery 1.3.2"
// comments above for more information.
function new_handler( e, w, h ) {
var elem = $(this),
data = $.data( this, str_data );
// If called from the polling loop, w and h will be passed in as
// arguments. If called manually, via .trigger( 'resize' ) or .resize(),
// those values will need to be computed.
data.w = w !== undefined ? w : elem.width();
data.h = h !== undefined ? h : elem.height();
old_handler.apply( this, arguments );
};
// This may seem a little complicated, but it normalizes the special event
// .add method between jQuery 1.4/1.4.1 and 1.4.2+
if ( $.isFunction( handleObj ) ) {
// 1.4, 1.4.1
old_handler = handleObj;
return new_handler;
} else {
// 1.4.2+
old_handler = handleObj.handler;
handleObj.handler = new_handler;
}
}
};
function loopy() {
// Start the polling loop, asynchronously.
timeout_id = window[ str_setTimeout ](function(){
// Iterate over all elements to which the 'resize' event is bound.
elems.each(function(){
var elem = $(this),
width = elem.width(),
height = elem.height(),
data = $.data( this, str_data );
// If element size has changed since the last time, update the element
// data store and trigger the 'resize' event.
if ( width !== data.w || height !== data.h ) {
elem.trigger( str_resize, [ data.w = width, data.h = height ] );
}
});
// Loop.
loopy();
}, jq_resize[ str_delay ] );
};
})(jQuery,this);
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