diff options
author | James Lopez <james@jameslopez.es> | 2016-06-16 12:59:07 +0200 |
---|---|---|
committer | James Lopez <james@jameslopez.es> | 2016-06-16 12:59:07 +0200 |
commit | 452c076a34cc11cc97f4b1c3113e86ce4367e055 (patch) | |
tree | a503b33dc6dd181e2cf56d5965fc1eb822b4d9f9 | |
parent | 13e37a3ee5c943525a99481b855d654e97e8597c (diff) | |
download | gitlab-ce-452c076a34cc11cc97f4b1c3113e86ce4367e055.tar.gz |
Revert "squashed merge and fixed conflicts"
This reverts commit 13e37a3ee5c943525a99481b855d654e97e8597c.
403 files changed, 3046 insertions, 16738 deletions
diff --git a/.gitlab-ci.yml b/.gitlab-ci.yml index 83a906932d0..3dc48a89463 100644 --- a/.gitlab-ci.yml +++ b/.gitlab-ci.yml @@ -7,8 +7,7 @@ services: cache: key: "ruby21" paths: - - vendor/apt - - vendor/ruby + - vendor variables: MYSQL_ALLOW_EMPTY_PASSWORD: "1" @@ -92,7 +91,9 @@ update-knapsack: - export KNAPSACK_REPORT_PATH=knapsack/spinach_node_${CI_NODE_INDEX}_${CI_NODE_TOTAL}_report.json - export KNAPSACK_GENERATE_REPORT=true - cp knapsack/spinach_report.json ${KNAPSACK_REPORT_PATH} - - knapsack spinach "-r rerun" || retry '[ ! -e tmp/spinach-rerun.txt ] || bundle exec spinach -r rerun $(cat tmp/spinach-rerun.txt)' + - knapsack spinach "-r rerun" + # retry failed tests 3 times + - retry '[ ! -e tmp/spinach-rerun.txt ] || bin/spinach -r rerun $(cat tmp/spinach-rerun.txt)' artifacts: paths: - knapsack/ diff --git a/.rubocop.yml b/.rubocop.yml index dbdabbb9d4c..c637f5e12f5 100644 --- a/.rubocop.yml +++ b/.rubocop.yml @@ -349,7 +349,7 @@ Style/MultilineArrayBraceLayout: # Avoid multi-line chains of blocks. Style/MultilineBlockChain: - Enabled: true + Enabled: false # Ensures newlines after multiline block do statements. Style/MultilineBlockLayout: diff --git a/CHANGELOG b/CHANGELOG index fa0960b2847..364690286e1 100644 --- a/CHANGELOG +++ b/CHANGELOG @@ -2,18 +2,14 @@ Please view this file on the master branch, on stable branches it's out of date. v 8.9.0 (unreleased) - Fix Error 500 when using closes_issues API with an external issue tracker - - Add more information into RSS feed for issues (Alexander Matyushentsev) - Bulk assign/unassign labels to issues. - Ability to prioritize labels !4009 / !3205 (Thijs Wouters) - Fix endless redirections when accessing user OAuth applications when they are disabled - Allow enabling wiki page events from Webhook management UI - Bump rouge to 1.11.0 - Fix issue with arrow keys not working in search autocomplete dropdown - - Fix an issue where note polling stopped working if a window was in the - background during a refresh. - Make EmailsOnPushWorker use Sidekiq mailers queue - Fix wiki page events' webhook to point to the wiki repository - - Don't show tags for revert and cherry-pick operations - Fix issue todo not remove when leave project !4150 (Long Nguyen) - Allow customisable text on the 'nearly there' page after a user signs up - Bump recaptcha gem to 3.0.0 to remove deprecated stoken support @@ -22,19 +18,11 @@ v 8.9.0 (unreleased) - Added descriptions to notification settings dropdown - Improve note validation to prevent errors when creating invalid note via API - Reduce number of fog gem dependencies - - Implement a fair usage of shared runners - Remove project notification settings associated with deleted projects - Fix 404 page when viewing TODOs that contain milestones or labels in different projects - - Add a metric for the number of new Redis connections created by a transaction - - Fix Error 500 when viewing a blob with binary characters after the 1024-byte mark - Redesign navigation for project pages - - Added shortcut 'y' for copying a files content hash URL #14470 - Fix groups API to list only user's accessible projects - - Fix horizontal scrollbar for long commit message. - - Add Environments and Deployments - Redesign account and email confirmation emails - - Don't fail builds for projects that are deleted - - Support Docker Registry manifest v1 - `git clone https://host/namespace/project` now works, in addition to using the `.git` suffix - Bump nokogiri to 1.6.8 - Use gitlab-shell v3.0.0 @@ -45,20 +33,14 @@ v 8.9.0 (unreleased) - Add DB index on users.state - Add rake task 'gitlab:db:configure' for conditionally seeding or migrating the database - Changed the Slack build message to use the singular duration if necessary (Aran Koning) - - Fix race condition on merge when build succeeds - Links from a wiki page to other wiki pages should be rewritten as expected - Add option to project to only allow merge requests to be merged if the build succeeds (Rui Santos) - - Added navigation shortcuts to the project pipelines, milestones, builds and forks page. !4393 - Fix issues filter when ordering by milestone - - Added artifacts:when to .gitlab-ci.yml - this requires GitLab Runner 1.3 - - Bamboo Service: Fix missing credentials & URL handling when base URL contains a path (Benjamin Schmid) - - TeamCity Service: Fix URL handling when base URL contains a path - Todos will display target state if issuable target is 'Closed' or 'Merged' - Fix bug when sorting issues by milestone due date and filtering by two or more labels - Add support for using Yubikeys (U2F) for two-factor authentication - Link to blank group icon doesn't throw a 404 anymore - Remove 'main language' feature - - Toggle whitespace button now available for compare branches diffs #17881 - Pipelines can be canceled only when there are running builds - Use downcased path to container repository as this is expected path by Docker - Projects pending deletion will render a 404 page @@ -70,59 +52,34 @@ v 8.9.0 (unreleased) - Use Knapsack only in CI environment - Cache project build count in sidebar nav - Add milestone expire date to the right sidebar - - Manually mark a issue or merge request as a todo - Fix markdown_spec to use before instead of before(:all) to properly cleanup database after testing - Reduce number of queries needed to render issue labels in the sidebar - Improve error handling importing projects - Remove duplicated notification settings - Put project Files and Commits tabs under Code tab - - Decouple global notification level from user model - Replace Colorize with Rainbow for coloring console output in Rake tasks. - Add workhorse controller and API helpers - An indicator is now displayed at the top of the comment field for confidential issues. - - Show categorised search queries in the search autocomplete - RepositoryCheck::SingleRepositoryWorker public and private methods are now instrumented - Improve issuables APIs performance when accessing notes !4471 - External links now open in a new tab - - Prevent default actions of disabled buttons and links - Markdown editor now correctly resets the input value on edit cancellation !4175 - Toggling a task list item in a issue/mr description does not creates a Todo for mentions - Improved UX of date pickers on issue & milestone forms - Cache on the database if a project has an active external issue tracker. - Put project Labels and Milestones pages links under Issues and Merge Requests tabs as subnav - - All classes in the Banzai::ReferenceParser namespace are now instrumented - - Remove deprecated issues_tracker and issues_tracker_id from project model - - Allow users to create confidential issues in private projects - - Measure CPU time for instrumented methods - - Instrument private methods and private instance methods by default instead just public methods - - Only show notes through JSON on confidential issues that the user has access to - - Updated the allocations Gem to version 1.0.5 - - The background sampler now ignores classes without names - - Update design for `Close` buttons - - New custom icons for navigation - - Horizontally scrolling navigation on project, group, and profile settings pages - - Hide global side navigation by default - - Fix project Star/Unstar project button tooltip - - Remove tanuki logo from side navigation; center on top nav - - Include user relationships when retrieving award_emoji - - Various associations are now eager loaded when parsing issue references to reduce the number of queries executed - - Set inverse_of for Project/Service association to reduce the number of queries - -v 8.8.5 - - Import GitHub repositories respecting the API rate limit !4166 - - Fix todos page throwing errors when you have a project pending deletion !4300 - - Disable Webhooks before proceeding with the GitHub import !4470 - - Fix importer for GitHub comments on diff !4488 - - Adjust the SAML control flow to allow LDAP identities to be added to an existing SAML user !4498 - - Fix incremental trace upload API when using multi-byte UTF-8 chars in trace !4541 - - Prevent unauthorized access for projects build traces - - Forbid scripting for wiki files - - Only show notes through JSON on confidential issues that the user has access to + +v 8.8.5 (unreleased) + - Ensure branch cleanup regardless of whether the GitHub import process succeeds + - Fix todos page throwing errors when you have a project pending deletion + - Reduce number of SQL queries when rendering user references + - Import GitHub repositories respecting the API rate limit + - Fix importer for GitHub comments on diff + - Disable Webhooks before proceeding with the GitHub import + - Fix incremental trace upload API when using multi-byte UTF-8 chars in trace v 8.8.4 - Fix LDAP-based login for users with 2FA enabled. !4493 - - Added descriptions to notification settings dropdown - - Due date can be removed from milestones v 8.8.3 - Fix 404 page when viewing TODOs that contain milestones or labels in different projects. !4312 @@ -238,9 +195,6 @@ v 8.8.0 v 8.7.7 - Fix import by `Any Git URL` broken if the URL contains a space - - Prevent unauthorized access to other projects build traces - - Forbid scripting for wiki files - - Only show notes through JSON on confidential issues that the user has access to v 8.7.6 - Fix links on wiki pages for relative url setups. !4131 (Artem Sidorenko) @@ -403,11 +357,6 @@ v 8.7.0 - Add RAW build trace output and button on build page - Add incremental build trace update into CI API -v 8.6.9 - - Prevent unauthorized access to other projects build traces - - Forbid scripting for wiki files - - Only show notes through JSON on confidential issues that the user has access to - v 8.6.8 - Prevent privilege escalation via "impersonate" feature - Prevent privilege escalation via notes API @@ -562,10 +511,6 @@ v 8.6.0 - Trigger a todo for mentions on commits page - Let project owners and admins soft delete issues and merge requests -v 8.5.13 - - Prevent unauthorized access to other projects build traces - - Forbid scripting for wiki files - v 8.5.12 - Prevent privilege escalation via "impersonate" feature - Prevent privilege escalation via notes API @@ -727,10 +672,6 @@ v 8.5.0 - Show label row when filtering issues or merge requests by label (Nuttanart Pornprasitsakul) - Add Todos -v 8.4.11 - - Prevent unauthorized access to other projects build traces - - Forbid scripting for wiki files - v 8.4.10 - Prevent privilege escalation via "impersonate" feature - Prevent privilege escalation via notes API @@ -867,10 +808,6 @@ v 8.4.0 - Add IP check against DNSBLs at account sign-up - Added cache:key to .gitlab-ci.yml allowing to fine tune the caching -v 8.3.10 - - Prevent unauthorized access to other projects build traces - - Forbid scripting for wiki files - v 8.3.9 - Prevent privilege escalation via "impersonate" feature - Prevent privilege escalation via notes API @@ -989,10 +926,6 @@ v 8.3.0 - Expose Git's version in the admin area - Show "New Merge Request" buttons on canonical repos when you have a fork (Josh Frye) -v 8.2.6 - - Prevent unauthorized access to other projects build traces - - Forbid scripting for wiki files - v 8.2.5 - Prevent privilege escalation via "impersonate" feature - Prevent privilege escalation via notes API @@ -210,9 +210,6 @@ gem 'mousetrap-rails', '~> 1.4.6' # Detect and convert string character encoding gem 'charlock_holmes', '~> 0.7.3' -# Parse duration -gem 'chronic_duration', '~> 0.10.6' - gem "sass-rails", '~> 5.0.0' gem "coffee-rails", '~> 4.1.0' gem "uglifier", '~> 2.7.2' @@ -227,6 +224,7 @@ gem 'gon', '~> 6.0.1' gem 'jquery-atwho-rails', '~> 1.3.2' gem 'jquery-rails', '~> 4.1.0' gem 'jquery-ui-rails', '~> 5.0.0' +gem 'raphael-rails', '~> 2.1.2' gem 'request_store', '~> 1.3.0' gem 'select2-rails', '~> 3.5.9' gem 'virtus', '~> 1.0.1' @@ -247,7 +245,7 @@ end group :development do gem "foreman" - gem 'brakeman', '~> 3.3.0', require: false + gem 'brakeman', '~> 3.2.0', require: false gem 'letter_opener_web', '~> 1.3.0' gem 'quiet_assets', '~> 1.0.2' diff --git a/Gemfile.lock b/Gemfile.lock index a22bd1b1f52..dfc15700494 100644 --- a/Gemfile.lock +++ b/Gemfile.lock @@ -50,7 +50,7 @@ GEM after_commit_queue (1.3.0) activerecord (>= 3.0) akismet (2.0.0) - allocations (1.0.5) + allocations (1.0.4) arel (6.0.3) asana (0.4.0) faraday (~> 0.9) @@ -97,7 +97,16 @@ GEM bootstrap-sass (3.3.6) autoprefixer-rails (>= 5.2.1) sass (>= 3.3.4) - brakeman (3.3.2) + brakeman (3.2.1) + erubis (~> 2.6) + haml (>= 3.0, < 5.0) + highline (>= 1.6.20, < 2.0) + ruby2ruby (~> 2.3.0) + ruby_parser (~> 3.8.1) + safe_yaml (>= 1.0) + sass (~> 3.0) + slim (>= 1.3.6, < 4.0) + terminal-table (~> 1.4) browser (2.0.3) builder (3.2.2) bullet (5.0.0) @@ -124,8 +133,6 @@ GEM mime-types (>= 1.16) cause (0.1) charlock_holmes (0.7.3) - chronic_duration (0.10.6) - numerizer (~> 0.1.1) chunky_png (1.3.5) cliver (0.3.2) coderay (1.1.0) @@ -277,7 +284,7 @@ GEM posix-spawn (~> 0.3) gitlab_emoji (0.3.1) gemojione (~> 2.2, >= 2.2.1) - gitlab_git (10.1.3) + gitlab_git (10.1.0) activesupport (~> 4.0) charlock_holmes (~> 0.7.3) github-linguist (~> 4.7.0) @@ -331,6 +338,7 @@ GEM hashie (3.4.3) health_check (1.5.1) rails (>= 2.3.0) + highline (1.7.8) hipchat (1.5.2) httparty mimemagic @@ -400,7 +408,7 @@ GEM mime-types (>= 1.16, < 4) mail_room (0.7.0) method_source (0.8.2) - mime-types (2.99.2) + mime-types (2.99.1) mimemagic (0.3.0) mini_portile2 (2.1.0) minitest (5.7.0) @@ -416,7 +424,6 @@ GEM nokogiri (1.6.8) mini_portile2 (~> 2.1.0) pkg-config (~> 1.1.7) - numerizer (0.1.1) oauth (0.4.7) oauth2 (1.0.0) faraday (>= 0.8, < 0.10) @@ -556,6 +563,7 @@ GEM rainbow (2.1.0) raindrops (0.15.0) rake (10.5.0) + raphael-rails (2.1.2) rb-fsevent (0.9.6) rb-inotify (0.9.5) ffi (>= 0.5.0) @@ -634,7 +642,10 @@ GEM ruby-saml (1.1.2) nokogiri (>= 1.5.10) uuid (~> 2.3) - ruby_parser (3.8.2) + ruby2ruby (2.3.0) + ruby_parser (~> 3.1) + sexp_processor (~> 4.0) + ruby_parser (3.8.1) sexp_processor (~> 4.1) rubyntlm (0.5.2) rubypants (0.2.0) @@ -644,7 +655,7 @@ GEM safe_yaml (1.0.4) sanitize (2.1.0) nokogiri (>= 1.4.4) - sass (3.4.22) + sass (3.4.21) sass-rails (5.0.4) railties (>= 4.0.0, < 5.0) sass (~> 3.1) @@ -693,6 +704,9 @@ GEM tilt (>= 1.3, < 3) six (0.2.0) slack-notifier (1.2.1) + slim (3.0.6) + temple (~> 0.7.3) + tilt (>= 1.3.3, < 2.1) slop (3.6.0) spinach (0.8.10) colorize @@ -733,8 +747,10 @@ GEM railties (>= 3.2.5, < 6) teaspoon-jasmine (2.2.0) teaspoon (>= 1.0.0) + temple (0.7.6) term-ansicolor (1.3.2) tins (~> 1.0) + terminal-table (1.5.2) test_after_commit (0.4.2) activerecord (>= 3.2) thin (1.6.4) @@ -743,7 +759,7 @@ GEM rack (~> 1.0) thor (0.19.1) thread_safe (0.3.5) - tilt (2.0.5) + tilt (2.0.2) timecop (0.8.1) timfel-krb5-auth (0.8.3) tinder (1.10.1) @@ -832,7 +848,7 @@ DEPENDENCIES better_errors (~> 1.0.1) binding_of_caller (~> 0.7.2) bootstrap-sass (~> 3.3.0) - brakeman (~> 3.3.0) + brakeman (~> 3.2.0) browser (~> 2.0.3) bullet bundler-audit @@ -841,7 +857,6 @@ DEPENDENCIES capybara-screenshot (~> 1.0.0) carrierwave (~> 0.10.0) charlock_holmes (~> 0.7.3) - chronic_duration (~> 0.10.6) coffee-rails (~> 4.1.0) connection_pool (~> 2.0) coveralls (~> 0.8.2) @@ -937,6 +952,7 @@ DEPENDENCIES rails (= 4.2.6) rails-deprecated_sanitizer (~> 1.0.3) rainbow (~> 2.1.0) + raphael-rails (~> 2.1.2) rblineprof rdoc (~> 3.6) recaptcha (~> 3.0) diff --git a/app/assets/javascripts/LabelManager.js.coffee b/app/assets/javascripts/LabelManager.js.coffee index b06bcf0fcbf..365a062bb81 100644 --- a/app/assets/javascripts/LabelManager.js.coffee +++ b/app/assets/javascripts/LabelManager.js.coffee @@ -42,10 +42,10 @@ class @LabelManager $from = @prioritizedLabels if $from.find('li').length is 1 - $from.find('.empty-message').removeClass('hidden') + $from.find('.empty-message').show() if not $target.find('li').length - $target.find('.empty-message').addClass('hidden') + $target.find('.empty-message').hide() $label.detach().appendTo($target) @@ -54,9 +54,6 @@ class @LabelManager if action is 'remove' xhr = $.ajax url: url, type: 'DELETE' - - # Restore empty message - $from.find('.empty-message').removeClass('hidden') unless $from.find('li').length else xhr = @savePrioritySort($label, action) diff --git a/app/assets/javascripts/application.js.coffee b/app/assets/javascripts/application.js.coffee index 2f9f6c3ef5b..69d4c4f5dd3 100644 --- a/app/assets/javascripts/application.js.coffee +++ b/app/assets/javascripts/application.js.coffee @@ -32,6 +32,10 @@ #= require bootstrap/tooltip #= require bootstrap/popover #= require select2 +#= require raphael +#= require g.raphael +#= require g.bar +#= require branch-graph #= require ace/ace #= require ace/ext-searchbox #= require underscore @@ -121,10 +125,9 @@ window.onload = -> setTimeout shiftWindow, 100 $ -> - gl.utils.preventDisabledButtons() bootstrapBreakpoint = bp.getBreakpointSize() - $(".nav-sidebar").niceScroll(cursoropacitymax: '0.4', cursorcolor: '#FFF', cursorborder: "1px solid #FFF") + $(".nicescroll").niceScroll(cursoropacitymax: '0.4', cursorcolor: '#FFF', cursorborder: "1px solid #FFF") # Click a .js-select-on-focus field, select the contents $(".js-select-on-focus").on "focusin", -> @@ -254,31 +257,3 @@ $ -> gl.awardsHandler = new AwardsHandler() checkInitialSidebarSize() new Aside() - - # Sidenav pinning - if $(window).width() < 1440 and $.cookie('pin_nav') is 'true' - $.cookie('pin_nav', 'false') - $('.page-with-sidebar') - .toggleClass('page-sidebar-collapsed page-sidebar-expanded') - .removeClass('page-sidebar-pinned') - $('.navbar-fixed-top').removeClass('header-pinned-nav') - - $(document) - .off 'click', '.js-nav-pin' - .on 'click', '.js-nav-pin', (e) -> - e.preventDefault() - - $(this).toggleClass 'is-active' - - if $.cookie('pin_nav') is 'true' - $.cookie 'pin_nav', 'false' - $('.page-with-sidebar') - .removeClass('page-sidebar-pinned') - .toggleClass('page-sidebar-collapsed page-sidebar-expanded') - $('.navbar-fixed-top') - .removeClass('header-pinned-nav') - .toggleClass('header-collapsed header-expanded') - else - $.cookie 'pin_nav', 'true' - $('.page-with-sidebar').addClass('page-sidebar-pinned') - $('.navbar-fixed-top').addClass('header-pinned-nav') diff --git a/app/assets/javascripts/awards_handler.coffee b/app/assets/javascripts/awards_handler.coffee index 030f1564862..136db8ee14d 100644 --- a/app/assets/javascripts/awards_handler.coffee +++ b/app/assets/javascripts/awards_handler.coffee @@ -40,7 +40,7 @@ class @AwardsHandler $menu = $ '.emoji-menu' if $addBtn.hasClass 'js-note-emoji' - $addBtn.closest('.note').find('.js-awards-block').addClass 'current' + $addBtn.parents('.note').find('.js-awards-block').addClass 'current' else $addBtn.closest('.js-awards-block').addClass 'current' diff --git a/app/assets/javascripts/network/branch-graph.js.coffee b/app/assets/javascripts/branch-graph.js.coffee index f2fd2a775a4..f2fd2a775a4 100644 --- a/app/assets/javascripts/network/branch-graph.js.coffee +++ b/app/assets/javascripts/branch-graph.js.coffee diff --git a/app/assets/javascripts/ci/build.coffee b/app/assets/javascripts/ci/build.coffee index 2d515d7efa2..f763ba96e33 100644 --- a/app/assets/javascripts/ci/build.coffee +++ b/app/assets/javascripts/ci/build.coffee @@ -17,8 +17,6 @@ class @CiBuild .off 'resize.build' .on 'resize.build', @hideSidebar - @updateArtifactRemoveDate() - if $('#build-trace').length @getInitialBuildTrace() @initScrollButtonAffix() @@ -105,10 +103,3 @@ class @CiBuild $('.js-build-sidebar') .removeClass 'right-sidebar-collapsed' .addClass 'right-sidebar-expanded' - - updateArtifactRemoveDate: -> - $date = $('.js-artifacts-remove') - - if $date.length - date = $date.text() - $date.text $.timefor(new Date(date), ' ') diff --git a/app/assets/javascripts/dispatcher.js.coffee b/app/assets/javascripts/dispatcher.js.coffee index b560500cce6..29ac0f70b30 100644 --- a/app/assets/javascripts/dispatcher.js.coffee +++ b/app/assets/javascripts/dispatcher.js.coffee @@ -29,7 +29,6 @@ class Dispatcher new Todos() when 'projects:milestones:new', 'projects:milestones:edit' new ZenMode() - new DueDateSelect() new GLForm($('.milestone-form')) when 'groups:milestones:new' new ZenMode() @@ -54,13 +53,9 @@ class Dispatcher new Diff() shortcut_handler = new ShortcutsIssuable(true) new ZenMode() - new MergedButtons() - when 'projects:merge_requests:commits', 'projects:merge_requests:builds' - new MergedButtons() when "projects:merge_requests:diffs" new Diff() new ZenMode() - new MergedButtons() when 'projects:merge_requests:index' shortcut_handler = new ShortcutsNavigation() Issuable.init() @@ -73,7 +68,9 @@ class Dispatcher new Diff() new ZenMode() shortcut_handler = new ShortcutsNavigation() - when 'projects:commits:show', 'projects:activity' + when 'projects:commits:show' + shortcut_handler = new ShortcutsNavigation() + when 'projects:activity' shortcut_handler = new ShortcutsNavigation() when 'projects:show' shortcut_handler = new ShortcutsNavigation() @@ -99,7 +96,6 @@ class Dispatcher when 'projects:blob:show', 'projects:blame:show' new LineHighlighter() shortcut_handler = new ShortcutsNavigation() - new ShortcutsBlob true when 'projects:labels:new', 'projects:labels:edit' new Labels() when 'projects:labels:index' @@ -133,11 +129,15 @@ class Dispatcher new Project() new ProjectAvatar() switch path[1] + when 'compare' + shortcut_handler = new ShortcutsNavigation() when 'edit' shortcut_handler = new ShortcutsNavigation() new ProjectNew() - when 'new', 'show' + when 'new' new ProjectNew() + when 'show' + new ProjectShow() when 'wikis' new Wikis() shortcut_handler = new ShortcutsNavigation() @@ -146,9 +146,9 @@ class Dispatcher when 'snippets' shortcut_handler = new ShortcutsNavigation() new ZenMode() if path[2] == 'show' - when 'labels', 'graphs', 'compare', 'pipelines', 'forks', \ - 'milestones', 'project_members', 'deploy_keys', 'builds', \ - 'hooks', 'services', 'protected_branches' + when 'labels', 'graphs' + shortcut_handler = new ShortcutsNavigation() + when 'project_members', 'deploy_keys', 'hooks', 'services', 'protected_branches' shortcut_handler = new ShortcutsNavigation() # If we haven't installed a custom shortcut handler, install the default one diff --git a/app/assets/javascripts/due_date_select.js.coffee b/app/assets/javascripts/due_date_select.js.coffee index d65c018dad5..3d009a96d05 100644 --- a/app/assets/javascripts/due_date_select.js.coffee +++ b/app/assets/javascripts/due_date_select.js.coffee @@ -1,21 +1,5 @@ class @DueDateSelect constructor: -> - # Milestone edit/new form - $datePicker = $('.datepicker') - - if $datePicker.length - $dueDate = $('#milestone_due_date') - $datePicker.datepicker - dateFormat: 'yy-mm-dd' - onSelect: (dateText, inst) -> - $dueDate.val(dateText) - .datepicker('setDate', $.datepicker.parseDate('yy-mm-dd', $dueDate.val())) - - $('.js-clear-due-date').on 'click', (e) -> - e.preventDefault() - $.datepicker._clearDate($datePicker) - - # Issuable sidebar $loading = $('.js-issuable-update .due_date') .find('.block-loading') .hide() @@ -48,7 +32,7 @@ class @DueDateSelect date = new Date value.replace(new RegExp('-', 'g'), ',') mediumDate = $.datepicker.formatDate 'M d, yy', date else - mediumDate = 'No due date' + mediumDate = 'None' data = {} data[abilityName] = {} @@ -66,8 +50,7 @@ class @DueDateSelect $selectbox.hide() $value.css('display', '') - cssClass = if Date.parse(mediumDate) then 'bold' else 'no-value' - $valueContent.html("<span class='#{cssClass}'>#{mediumDate}</span>") + $valueContent.html(mediumDate) $sidebarValue.html(mediumDate) if value isnt '' diff --git a/app/assets/javascripts/issuable.js.coffee b/app/assets/javascripts/issuable.js.coffee index d0901be1509..c2447120033 100644 --- a/app/assets/javascripts/issuable.js.coffee +++ b/app/assets/javascripts/issuable.js.coffee @@ -56,6 +56,13 @@ issuable_created = false Issuable.filterResults $('.filter-form') $('.js-label-select').trigger('update.label') + toggleLabelFilters: -> + $filteredLabels = $('.filtered-labels') + if $filteredLabels.find('.label-row').length > 0 + $filteredLabels.removeClass('hidden') + else + $filteredLabels.addClass('hidden') + filterResults: (form) => formData = form.serialize() @@ -64,16 +71,58 @@ issuable_created = false issuesUrl = formAction issuesUrl += ("#{if formAction.indexOf('?') < 0 then '?' else '&'}") issuesUrl += formData + $.ajax + type: 'GET' + url: formAction + data: formData + complete: -> + $('.issues-holder, .merge-requests-holder').css('opacity', '1.0') + success: (data) -> + $('.issues-holder, .merge-requests-holder').html(data.html) + # Change url so if user reload a page - search results are saved + history.replaceState {page: issuesUrl}, document.title, issuesUrl + Issuable.reload() + Issuable.updateStateFilters() + $filteredLabels = $('.filtered-labels') + + if typeof Issuable.labelRow is 'function' + $filteredLabels.html(Issuable.labelRow(data)) - Turbolinks.visit(issuesUrl); + Issuable.toggleLabelFilters() + + dataType: "json" + + reload: -> + if Issuable.created + Issuable.initChecks() + + $('#filter_issue_search').val($('#issue_search').val()) initChecks: -> - $('.check_all_issues').off('click').on('click', -> + $('.check_all_issues').on 'click', -> $('.selected_issue').prop('checked', @checked) Issuable.checkChanged() - ) - $('.selected_issue').off('change').on('change', Issuable.checkChanged) + $('.selected_issue').on 'change', Issuable.checkChanged + + updateStateFilters: -> + stateFilters = $('.issues-state-filters, .dropdown-menu-sort') + newParams = {} + paramKeys = ['author_id', 'milestone_title', 'assignee_id', 'issue_search', 'issue_search'] + + for paramKey in paramKeys + newParams[paramKey] = gl.utils.getParameterValues(paramKey)[0] or '' + + if stateFilters.length + stateFilters.find('a').each -> + initialUrl = gl.utils.removeParamQueryString($(this).attr('href'), 'label_name[]') + labelNameValues = gl.utils.getParameterValues('label_name[]') + if labelNameValues + labelNameQueryString = ("label_name[]=#{value}" for value in labelNameValues).join('&') + newUrl = "#{gl.utils.mergeUrlParams(newParams, initialUrl)}&#{labelNameQueryString}" + else + newUrl = gl.utils.mergeUrlParams(newParams, initialUrl) + $(this).attr 'href', newUrl checkChanged: -> checked_issues = $('.selected_issue:checked') diff --git a/app/assets/javascripts/issuable_form.js.coffee b/app/assets/javascripts/issuable_form.js.coffee index 5b7a4831dfc..898506fde32 100644 --- a/app/assets/javascripts/issuable_form.js.coffee +++ b/app/assets/javascripts/issuable_form.js.coffee @@ -102,10 +102,6 @@ class @IssuableForm return { results: data } - data: (query) -> - { - search: query - } formatResult: (project) -> project.name_with_namespace formatSelection: (project) -> diff --git a/app/assets/javascripts/issues-bulk-assignment.js.coffee b/app/assets/javascripts/issues-bulk-assignment.js.coffee index b454f9389dd..16d023dd391 100644 --- a/app/assets/javascripts/issues-bulk-assignment.js.coffee +++ b/app/assets/javascripts/issues-bulk-assignment.js.coffee @@ -9,9 +9,6 @@ class @IssuableBulkActions @bindEvents() - # Fixes bulk-assign not working when navigating through pages - Issuable.initChecks(); - getElement: (selector) -> @container.find selector @@ -100,22 +97,13 @@ class @IssuableBulkActions $labels = @form.find('.labels-filter input[name="update[label_ids][]"]') $labels.each (k, label) -> - labelIds.push parseInt($(label).val()) if label + labelIds.push $(label).val() if label labelIds ###* - * Returns Label IDs that will be removed from issue selection - * @return {Array} Array of labels IDs + * Just an alias of @getUnmarkedIndeterminedLabels + * @return {Array} Array of labels ### getLabelsToRemove: -> - result = [] - indeterminatedLabels = @getUnmarkedIndeterminedLabels() - labelsToApply = @getLabelsToApply() - - indeterminatedLabels.map (id) -> - # We need to exclude label IDs that will be applied - # By not doing this will cause issues from selection to not add labels at all - result.push(id) if labelsToApply.indexOf(id) is -1 - - result + @getUnmarkedIndeterminedLabels() diff --git a/app/assets/javascripts/labels_select.js.coffee b/app/assets/javascripts/labels_select.js.coffee index d350a7c0e7f..9ca88f1226e 100644 --- a/app/assets/javascripts/labels_select.js.coffee +++ b/app/assets/javascripts/labels_select.js.coffee @@ -39,7 +39,7 @@ class @LabelsSelect </a> <% }); %>' ) - labelNoneHTMLTemplate = '<span class="no-value">None</span>' + labelNoneHTMLTemplate = _.template('<div class="light">None</div>') if newLabelField.length @@ -145,7 +145,7 @@ class @LabelsSelect template = labelHTMLTemplate(data) labelCount = data.labels.length else - template = labelNoneHTMLTemplate + template = labelNoneHTMLTemplate() $value .removeAttr('style') .html(template) diff --git a/app/assets/javascripts/layout_nav.js.coffee b/app/assets/javascripts/layout_nav.js.coffee index f8f0aea427e..6adac6dac97 100644 --- a/app/assets/javascripts/layout_nav.js.coffee +++ b/app/assets/javascripts/layout_nav.js.coffee @@ -1,25 +1,14 @@ -hideEndFade = ($scrollingTabs) -> - $scrollingTabs.each -> - $this = $(@) - - $this - .find('.fade-right') - .toggleClass('end-scroll', $this.width() is $this.prop('scrollWidth')) - -$ -> - $('.fade-left').addClass('end-scroll') - - hideEndFade($('.scrolling-tabs')) - - $(window) - .off 'resize.nav' - .on 'resize.nav', -> - hideEndFade($('.scrolling-tabs')) - - $('.scrolling-tabs').on 'scroll', (event) -> - $this = $(this) - currentPosition = $this.scrollLeft() - maxPosition = $this.prop('scrollWidth') - $this.outerWidth() - - $this.find('.fade-left').toggleClass('end-scroll', currentPosition is 0) - $this.find('.fade-right').toggleClass('end-scroll', currentPosition is maxPosition) +class @LayoutNav + $ -> + $('.fade-left').addClass('end-scroll') + $('.scrolling-tabs').on 'scroll', (event) -> + $this = $(this) + $el = $(event.target) + currentPosition = $this.scrollLeft() + size = bp.getBreakpointSize() + controlBtnWidth = $('.controls').width() + maxPosition = $this.get(0).scrollWidth - $this.parent().width() + maxPosition += controlBtnWidth if size isnt 'xs' and $('.nav-control').length + + $el.find('.fade-left').toggleClass('end-scroll', currentPosition is 0) + $el.find('.fade-right').toggleClass('end-scroll', currentPosition is maxPosition) diff --git a/app/assets/javascripts/lib/common_utils.js.coffee b/app/assets/javascripts/lib/common_utils.js.coffee index e39dcb2daa9..0000e99a650 100644 --- a/app/assets/javascripts/lib/common_utils.js.coffee +++ b/app/assets/javascripts/lib/common_utils.js.coffee @@ -1,46 +1,5 @@ ((w) -> - w.gl or= {} - w.gl.utils or= {} - - w.gl.utils.isInGroupsPage = -> - - return $('body').data('page').split(':')[0] is 'groups' - - - w.gl.utils.isInProjectPage = -> - - return $('body').data('page').split(':')[0] is 'projects' - - - w.gl.utils.getProjectSlug = -> - - return if @isInProjectPage() then $('body').data 'project' else null - - - w.gl.utils.getGroupSlug = -> - - return if @isInGroupsPage() then $('body').data 'group' else null - - - - gl.utils.updateTooltipTitle = ($tooltipEl, newTitle) -> - - $tooltipEl - .tooltip 'destroy' - .attr 'title', newTitle - .tooltip 'fixTitle' - - - gl.utils.preventDisabledButtons = -> - - $('.btn').click (e) -> - if $(this).hasClass 'disabled' - e.preventDefault() - e.stopImmediatePropagation() - return false - - jQuery.timefor = (time, suffix, expiredLabel) -> return '' unless time diff --git a/app/assets/javascripts/logo.js.coffee b/app/assets/javascripts/logo.js.coffee index dc2590a0355..9fdc27a9787 100644 --- a/app/assets/javascripts/logo.js.coffee +++ b/app/assets/javascripts/logo.js.coffee @@ -42,3 +42,9 @@ work = -> $(document).on('page:fetch', start) $(document).on('page:change', stop) + +$ -> + # Make logo clickable as part of a workaround for Safari visited + # link behaviour (See !2690). + $('#logo').on 'click', -> + Turbolinks.visit('/') diff --git a/app/assets/javascripts/merged_buttons.js.coffee b/app/assets/javascripts/merged_buttons.js.coffee deleted file mode 100644 index 4929295c10b..00000000000 --- a/app/assets/javascripts/merged_buttons.js.coffee +++ /dev/null @@ -1,30 +0,0 @@ -class @MergedButtons - constructor: -> - @$removeBranchWidget = $('.remove_source_branch_widget') - @$removeBranchProgress = $('.remove_source_branch_in_progress') - @$removeBranchFailed = $('.remove_source_branch_widget.failed') - - @cleanEventListeners() - @initEventListeners() - - cleanEventListeners: -> - $(document).off 'click', '.remove_source_branch' - $(document).off 'ajax:success', '.remove_source_branch' - $(document).off 'ajax:error', '.remove_source_branch' - - initEventListeners: -> - $(document).on 'click', '.remove_source_branch', @removeSourceBranch - $(document).on 'ajax:success', '.remove_source_branch', @removeBranchSuccess - $(document).on 'ajax:error', '.remove_source_branch', @removeBranchError - - removeSourceBranch: => - @$removeBranchWidget.hide() - @$removeBranchProgress.show() - - removeBranchSuccess: -> - location.reload() - - removeBranchError: -> - @$removeBranchWidget.hide() - @$removeBranchProgress.hide() - @$removeBranchFailed.show() diff --git a/app/assets/javascripts/milestone_select.js.coffee b/app/assets/javascripts/milestone_select.js.coffee index 02480f3a025..648e1f3bde0 100644 --- a/app/assets/javascripts/milestone_select.js.coffee +++ b/app/assets/javascripts/milestone_select.js.coffee @@ -24,10 +24,14 @@ class @MilestoneSelect if issueUpdateURL milestoneLinkTemplate = _.template( - '<a href="/<%= namespace %>/<%= path %>/milestones/<%= iid %>" class="bold has-tooltip" data-container="body" title="<%= remaining %>"><%= _.escape(title) %></a>' + '<a href="/<%= namespace %>/<%= path %>/milestones/<%= iid %>"> + <span class="has-tooltip" data-container="body" title="<%= remaining %>"> + <%= _.escape(title) %> + </span> + </a>' ) - milestoneLinkNoneTemplate = '<span class="no-value">None</span>' + milestoneLinkNoneTemplate = '<div class="light">None</div>' collapsedSidebarLabelTemplate = _.template( '<span class="has-tooltip" data-container="body" title="<%= remaining %>" data-placement="left"> @@ -112,7 +116,7 @@ class @MilestoneSelect .val() data = {} data[abilityName] = {} - data[abilityName].milestone_id = if selected? then selected else null + data[abilityName].milestone_id = selected $loading .fadeIn() $dropdown.trigger('loading.gl.dropdown') diff --git a/app/assets/javascripts/network/network.js.coffee b/app/assets/javascripts/network.js.coffee index f4ef07a50a7..f4ef07a50a7 100644 --- a/app/assets/javascripts/network/network.js.coffee +++ b/app/assets/javascripts/network.js.coffee diff --git a/app/assets/javascripts/network/application.js.coffee b/app/assets/javascripts/network/application.js.coffee deleted file mode 100644 index cb9eead855b..00000000000 --- a/app/assets/javascripts/network/application.js.coffee +++ /dev/null @@ -1,20 +0,0 @@ -# This is a manifest file that'll be compiled into including all the files listed below. -# Add new JavaScript/Coffee code in separate files in this directory and they'll automatically -# be included in the compiled file accessible from http://example.com/assets/application.js -# It's not advisable to add code directly here, but if you do, it'll appear at the bottom of the -# the compiled file. -# -#= require raphael -#= require g.raphael -#= require g.bar -#= require_tree . - -$ -> - network_graph = new Network({ - url: $(".network-graph").attr('data-url'), - commit_url: $(".network-graph").attr('data-commit-url'), - ref: $(".network-graph").attr('data-ref'), - commit_id: $(".network-graph").attr('data-commit-id') - }) - - new ShortcutsNetwork(network_graph.branch_graph) diff --git a/app/assets/javascripts/notes.js.coffee b/app/assets/javascripts/notes.js.coffee index e2d3241437b..ad216910c8d 100644 --- a/app/assets/javascripts/notes.js.coffee +++ b/app/assets/javascripts/notes.js.coffee @@ -115,14 +115,12 @@ class @Notes , @pollingInterval refresh: => + return if @refreshing is true + @refreshing = true if not document.hidden and document.URL.indexOf(@noteable_url) is 0 @getContent() getContent: -> - return if @refreshing - - @refreshing = true - $.ajax url: @notes_url data: "last_fetched_at=" + @last_fetched_at diff --git a/app/assets/javascripts/right_sidebar.js.coffee b/app/assets/javascripts/right_sidebar.js.coffee index 8eb005b0a22..c9cb0f4bb32 100644 --- a/app/assets/javascripts/right_sidebar.js.coffee +++ b/app/assets/javascripts/right_sidebar.js.coffee @@ -43,55 +43,6 @@ class @Sidebar $('.right-sidebar') .hasClass('right-sidebar-collapsed'), { path: '/' }) - $(document) - .off 'click', '.js-issuable-todo' - .on 'click', '.js-issuable-todo', @toggleTodo - - toggleTodo: (e) => - $this = $(e.currentTarget) - $todoLoading = $('.js-issuable-todo-loading') - $btnText = $('.js-issuable-todo-text', $this) - ajaxType = if $this.attr('data-id') then 'PATCH' else 'POST' - ajaxUrlExtra = if $this.attr('data-id') then "/#{$this.attr('data-id')}" else '' - - $.ajax( - url: "#{$this.data('url')}#{ajaxUrlExtra}" - type: ajaxType - dataType: 'json' - data: - issuable_id: $this.data('issuable') - issuable_type: $this.data('issuable-type') - beforeSend: => - @beforeTodoSend($this, $todoLoading) - ).done (data) => - @todoUpdateDone(data, $this, $btnText, $todoLoading) - - beforeTodoSend: ($btn, $todoLoading) -> - $btn.disable() - $todoLoading.removeClass 'hidden' - - todoUpdateDone: (data, $btn, $btnText, $todoLoading) -> - $todoPendingCount = $('.todos-pending-count') - $todoPendingCount.text data.count - - $btn.enable() - $todoLoading.addClass 'hidden' - - if data.count is 0 - $todoPendingCount.addClass 'hidden' - else - $todoPendingCount.removeClass 'hidden' - - if data.todo? - $btn - .attr 'aria-label', $btn.data('mark-text') - .attr 'data-id', data.todo.id - $btnText.text $btn.data('mark-text') - else - $btn - .attr 'aria-label', $btn.data('todo-text') - .removeAttr 'data-id' - $btnText.text $btn.data('todo-text') sidebarDropdownLoading: (e) -> $sidebarCollapsedIcon = $(@).closest('.block').find('.sidebar-collapsed-icon') @@ -166,3 +117,5 @@ class @Sidebar getBlock: (name) -> @sidebar.find(".block.#{name}") + + diff --git a/app/assets/javascripts/search_autocomplete.js.coffee b/app/assets/javascripts/search_autocomplete.js.coffee index 421328554b8..5eb915a51ea 100644 --- a/app/assets/javascripts/search_autocomplete.js.coffee +++ b/app/assets/javascripts/search_autocomplete.js.coffee @@ -67,12 +67,8 @@ class @SearchAutocomplete getData: (term, callback) -> _this = @ - unless term - if contents = @getCategoryContents() - @searchInput.data('glDropdown').filter.options.callback contents - @enableAutocomplete() - - return + # Do not trigger request if input is empty + return if @searchInput.val() is '' # Prevent multiple ajax calls return if @loadingSuggestions @@ -126,37 +122,6 @@ class @SearchAutocomplete ).always -> _this.loadingSuggestions = false - - getCategoryContents: -> - - userId = gon.current_user_id - { utils, projectOptions, groupOptions, dashboardOptions } = gl - - if utils.isInGroupsPage() and groupOptions - options = groupOptions[utils.getGroupSlug()] - - else if utils.isInProjectPage() and projectOptions - options = projectOptions[utils.getProjectSlug()] - - else if dashboardOptions - options = dashboardOptions - - { issuesPath, mrPath, name } = options - - items = [ - { header: "#{name}" } - { text: 'Issues assigned to me', url: "#{issuesPath}/?assignee_id=#{userId}" } - { text: "Issues I've created", url: "#{issuesPath}/?author_id=#{userId}" } - 'separator' - { text: 'Merge requests assigned to me', url: "#{mrPath}/?assignee_id=#{userId}" } - { text: "Merge requests I've created", url: "#{mrPath}/?author_id=#{userId}" } - ] - - items.splice 0, 1 unless name - - return items - - serializeState: -> { # Search Criteria @@ -244,12 +209,6 @@ class @SearchAutocomplete @isFocused = true @wrap.addClass('search-active') - @getData() if @getValue() is '' - - - getValue: -> return @searchInput.val() - - onClearInputClick: (e) => e.preventDefault() @searchInput.val('').focus() @@ -270,10 +229,6 @@ class @SearchAutocomplete @locationBadgeEl.text(badgeText).show() @wrap.addClass('has-location-badge') - - hasLocationBadge: -> return @wrap.is '.has-location-badge' - - restoreOriginalState: -> inputs = Object.keys @originalState @@ -302,14 +257,13 @@ class @SearchAutocomplete @getElement("##{input}").val('') - removeLocationBadge: -> - @locationBadgeEl.hide() + + # Reset state @resetSearchState() - @wrap.removeClass('has-location-badge') - @disableAutocomplete() + @wrap.removeClass('has-location-badge') disableAutocomplete: -> @searchInput.addClass('disabled') diff --git a/app/assets/javascripts/shortcuts.js.coffee b/app/assets/javascripts/shortcuts.js.coffee index c03877e9b06..f3d66004138 100644 --- a/app/assets/javascripts/shortcuts.js.coffee +++ b/app/assets/javascripts/shortcuts.js.coffee @@ -1,7 +1,7 @@ class @Shortcuts - constructor: (skipResetBindings) -> + constructor: -> @enabledHelp = [] - Mousetrap.reset() if not skipResetBindings + Mousetrap.reset() Mousetrap.bind('?', @onToggleHelp) Mousetrap.bind('s', Shortcuts.focusSearch) Mousetrap.bind(['ctrl+shift+p', 'command+shift+p'], @toggleMarkdownPreview) diff --git a/app/assets/javascripts/shortcuts_blob.coffee b/app/assets/javascripts/shortcuts_blob.coffee deleted file mode 100644 index 6d21e5d1150..00000000000 --- a/app/assets/javascripts/shortcuts_blob.coffee +++ /dev/null @@ -1,10 +0,0 @@ -#= require shortcuts - -class @ShortcutsBlob extends Shortcuts - constructor: (skipResetBindings) -> - super skipResetBindings - Mousetrap.bind('y', ShortcutsBlob.copyToClipboard) - - @copyToClipboard: -> - clipboardButton = $('.btn-clipboard') - clipboardButton.click() if clipboardButton diff --git a/app/assets/javascripts/sidebar.js.coffee b/app/assets/javascripts/sidebar.js.coffee index 68009e58645..2ce63c16428 100644 --- a/app/assets/javascripts/sidebar.js.coffee +++ b/app/assets/javascripts/sidebar.js.coffee @@ -3,33 +3,13 @@ expanded = 'page-sidebar-expanded' toggleSidebar = -> $('.page-with-sidebar').toggleClass("#{collapsed} #{expanded}") - $('.navbar-fixed-top').toggleClass("header-collapsed header-expanded") - - if $.cookie('pin_nav') is 'true' - $('.navbar-fixed-top').toggleClass('header-pinned-nav') - $('.page-with-sidebar').toggleClass('page-sidebar-pinned') + $('header').toggleClass("header-collapsed header-expanded") setTimeout ( -> - niceScrollBars = $('.nav-sidebar').niceScroll(); + niceScrollBars = $('.nicescroll').niceScroll(); niceScrollBars.updateScrollBar(); ), 300 -$(document) - .off 'click', 'body' - .on 'click', 'body', (e) -> - unless $.cookie('pin_nav') is 'true' - $target = $(e.target) - $nav = $target.closest('.sidebar-wrapper') - pageExpanded = $('.page-with-sidebar').hasClass('page-sidebar-expanded') - $toggle = $target.closest('.toggle-nav-collapse, .side-nav-toggle') - - if $nav.length is 0 and pageExpanded and $toggle.length is 0 - $('.page-with-sidebar') - .toggleClass('page-sidebar-collapsed page-sidebar-expanded') - - $('.navbar-fixed-top') - .toggleClass('header-collapsed header-expanded') - $(document).on("click", '.toggle-nav-collapse, .side-nav-toggle', (e) -> e.preventDefault() diff --git a/app/assets/javascripts/star.js.coffee b/app/assets/javascripts/star.js.coffee index 01b28171f72..f27780dda93 100644 --- a/app/assets/javascripts/star.js.coffee +++ b/app/assets/javascripts/star.js.coffee @@ -9,11 +9,9 @@ class @Star $this.parent().find('.star-count').text data.star_count if isStarred $starSpan.removeClass('starred').text 'Star' - gl.utils.updateTooltipTitle $this, 'Star project' $starIcon.removeClass('fa-star').addClass 'fa-star-o' else $starSpan.addClass('starred').text 'Unstar' - gl.utils.updateTooltipTitle $this, 'Unstar project' $starIcon.removeClass('fa-star-o').addClass 'fa-star' return diff --git a/app/assets/javascripts/users_select.js.coffee b/app/assets/javascripts/users_select.js.coffee index 2548efb2186..88246b0feb8 100644 --- a/app/assets/javascripts/users_select.js.coffee +++ b/app/assets/javascripts/users_select.js.coffee @@ -31,7 +31,7 @@ class @UsersSelect assignTo = (selected) -> data = {} data[abilityName] = {} - data[abilityName].assignee_id = if selected? then selected else null + data[abilityName].assignee_id = selected $loading .fadeIn() $dropdown.trigger('loading.gl.dropdown') @@ -72,7 +72,7 @@ class @UsersSelect assigneeTemplate = _.template( '<% if (username) { %> - <a class="author_link bold" href="/u/<%= username %>"> + <a class="author_link " href="/u/<%= username %>"> <% if( avatar ) { %> <img width="32" class="avatar avatar-inline s32" alt="" src="<%= avatar %>"> <% } %> @@ -82,7 +82,7 @@ class @UsersSelect </span> </a> <% } else { %> - <span class="no-value assign-yourself"> + <span class="assign-yourself"> No assignee - <a href="#" class="js-assign-yourself"> assign yourself diff --git a/app/assets/stylesheets/framework/gitlab-theme.scss b/app/assets/stylesheets/framework/gitlab-theme.scss index 0a8603b6702..408d4a68e1e 100644 --- a/app/assets/stylesheets/framework/gitlab-theme.scss +++ b/app/assets/stylesheets/framework/gitlab-theme.scss @@ -8,8 +8,8 @@ */ @mixin gitlab-theme($color-light, $color, $color-darker, $color-dark) { .page-with-sidebar { - .toggle-nav-collapse, - .pin-nav-btn { + + .collapse-nav a { color: $color-light; background: $color; diff --git a/app/assets/stylesheets/framework/header.scss b/app/assets/stylesheets/framework/header.scss index dca4dbb9f7d..63996ea44f6 100644 --- a/app/assets/stylesheets/framework/header.scss +++ b/app/assets/stylesheets/framework/header.scss @@ -3,7 +3,7 @@ * */ header { - transition: padding $sidebar-transition-duration; + transition-duration: .3s; &.navbar-empty { height: $header-height; @@ -79,9 +79,14 @@ header { &.header-collapsed { padding: 0 16px; + + .side-nav-toggle { + display: block; + } } .side-nav-toggle { + display: none; position: absolute; left: -10px; margin: 6px 0; @@ -103,7 +108,9 @@ header { .header-content { position: relative; height: $header-height; + padding-right: 40px; padding-left: 30px; + transition-duration: .3s; @media (min-width: $screen-sm-min) { padding-right: 0; @@ -191,6 +198,18 @@ header { } } +.header-collapsed { + margin-left: 0; + + .header-content { + + @media (min-width: $screen-sm-max) { + padding-left: 30px; + transition-duration: .3s; + } + } +} + .tanuki-shape { transition: all 0.8s; diff --git a/app/assets/stylesheets/framework/lists.scss b/app/assets/stylesheets/framework/lists.scss index a12c0bba44a..b34ec16cdba 100644 --- a/app/assets/stylesheets/framework/lists.scss +++ b/app/assets/stylesheets/framework/lists.scss @@ -159,7 +159,7 @@ ul.content-list { background-color: $gray-light; border: dotted 1px $gray-dark; margin: 1px 0; - min-height: 52px; + min-height: 30px; } } } diff --git a/app/assets/stylesheets/framework/nav.scss b/app/assets/stylesheets/framework/nav.scss index a55918f8711..4de89daeb36 100644 --- a/app/assets/stylesheets/framework/nav.scss +++ b/app/assets/stylesheets/framework/nav.scss @@ -74,7 +74,6 @@ .container-fluid { background-color: $background-color; - margin-bottom: 0; } li { @@ -242,12 +241,6 @@ } } } - - &.adjust { - .nav-text, .nav-controls { - width: auto; - } - } } .layout-nav { @@ -257,7 +250,7 @@ z-index: 11; background: $background-color; border-bottom: 1px solid $border-color; - transition: padding $sidebar-transition-duration; + transition-duration: .3s; text-align: center; .container-fluid { @@ -287,10 +280,11 @@ } .dropdown { - position: absolute; - top: 7px; - right: 15px; - z-index: 2; + margin-left: 7px; + + @media (max-width: $screen-xs-min) { + margin-left: 0; + } li.active { font-weight: bold; @@ -353,12 +347,6 @@ .badge { color: $gl-icon-color; } - - &:hover { - a, i { - color: $black; - } - } } } diff --git a/app/assets/stylesheets/framework/sidebar.scss b/app/assets/stylesheets/framework/sidebar.scss index 281c0a0e1e9..b7ec3f70bfb 100644 --- a/app/assets/stylesheets/framework/sidebar.scss +++ b/app/assets/stylesheets/framework/sidebar.scss @@ -1,31 +1,26 @@ .page-with-sidebar { padding-top: $header-height; - transition: padding $sidebar-transition-duration; + transition-duration: .3s; .sidebar-wrapper { position: fixed; top: 0; bottom: 0; + overflow-y: auto; + overflow-x: hidden; left: 0; height: 100%; - overflow: hidden; - transition: width $sidebar-transition-duration; + transition-duration: .3s; } } .sidebar-wrapper { z-index: 1000; background: $background-color; - - .nicescroll-rails-hr { - // TODO: Figure out why nicescroll doesn't hide horizontal bar - display: none!important; - } } .content-wrapper { width: 100%; - transition: padding $sidebar-transition-duration; .container-fluid { background: #fff; @@ -39,19 +34,22 @@ } } -.sidebar-user { - padding: 15px; - position: absolute; - left: 0; - bottom: 0; - width: $sidebar_width; - overflow: hidden; - font-size: 16px; - line-height: 36px; - transition: width $sidebar-transition-duration, padding $sidebar-transition-duration; +.sidebar-wrapper { - @media (min-width: $sidebar-breakpoint) { - bottom: 50px; + .sidebar-user { + padding: 15px 22px; + position: fixed; + bottom: 0; + width: $sidebar_width; + overflow: hidden; + transition-duration: .3s; + + .username { + margin-left: 10px; + width: $sidebar_width - 2 * 10px; + font-size: 16px; + line-height: 34px; + } } } @@ -67,46 +65,39 @@ .nav-sidebar { - position: absolute; - top: 50px; - bottom: 65px; - width: $sidebar_width; - overflow-y: auto; - overflow-x: hidden; - - @media (min-width: $sidebar-breakpoint) { - bottom: 115px; - } + margin-top: 22 + $header-height; + margin-bottom: 116px; + transition-duration: .3s; + list-style: none; + overflow: hidden; &.navbar-collapse { padding: 0 !important; } li { + width: $sidebar_width; + &.separate-item { padding-top: 10px; margin-top: 10px; } - .icon-container { - width: 34px; - display: inline-block; - text-align: center; - } - a { - padding: 7px 15px 7px 12px; + width: $sidebar_width; + padding: 7px 15px 7px 23px; font-size: $gl-font-size; line-height: 24px; display: block; text-decoration: none; font-weight: normal; outline: none; - white-space: nowrap; - &:hover, - &:active, - &:focus { + &:hover { + text-decoration: none; + } + + &:active, &:focus { text-decoration: none; } @@ -118,6 +109,10 @@ svg { margin-right: 13px; } + + &.back-link i { + transition-duration: .3s; + } } } @@ -128,50 +123,37 @@ } } -.toggle-nav-collapse { +.sidebar-subnav { + margin-left: 0; + padding-left: 0; + + li { + list-style: none; + } +} + +.collapse-nav a { width: $sidebar_width; - position: absolute; + position: fixed; top: 0; left: 0; - min-height: 50px; padding: 5px 0; font-size: 18px; - line-height: 30px; -} - -.nav-header-btn { - padding: 10px 5px; - color: inherit; + background: transparent; + height: 50px; + text-align: center; + line-height: 40px; transition-duration: .3s; + outline: none; - &:hover, - &:focus { - color: $white-light; + &:hover { text-decoration: none; } } -.pin-nav-btn { - display: none; - position: absolute; - left: 0; - bottom: 0; - height: 50px; - width: $sidebar_width; - line-height: 30px; - - @media (min-width: $sidebar-breakpoint) { - display: block; - } - - .fa { - transition: transform .15s; - } - - &.is-active { - .fa { - transform: rotate(90deg); - } +.sidebar-wrapper { + &.hidden-nav { + width: 0; } } @@ -180,34 +162,62 @@ .sidebar-wrapper { width: 0; + + .nav-sidebar { + width: 0; + + li { + width: auto; + + a { + span { + display: none; + } + } + } + } + + .collapse-nav a { + width: 0; + + i { + display: none; + } + } + + .sidebar-user { + width: 0; + padding-left: 0; + padding-right: 0; + + .username { + display: none; + } + } } } .page-sidebar-expanded { - .sidebar-wrapper { - width: $sidebar_width; - } -} -.page-sidebar-pinned { - .content-wrapper, - .layout-nav { - @media (min-width: $sidebar-breakpoint) { - padding-left: $sidebar_width; - } + @media (max-width: $screen-sm-max) { + padding-left: 0; } -} -header.header-pinned-nav { - @media (min-width: $sidebar-breakpoint) { - padding-left: ($sidebar_width + $gl-padding); + .sidebar-wrapper { + width: $sidebar_width; - .side-nav-toggle { - display: none; + .nav-sidebar { + width: $sidebar_width; } - .header-content { - padding-left: 0; + .nav-sidebar li a { + width: $sidebar_width; + + &.back-link { + i { + opacity: 0; + } + } } } } diff --git a/app/assets/stylesheets/framework/variables.scss b/app/assets/stylesheets/framework/variables.scss index acada1f16a0..752d8ec8788 100644 --- a/app/assets/stylesheets/framework/variables.scss +++ b/app/assets/stylesheets/framework/variables.scss @@ -6,8 +6,6 @@ $sidebar_width: 220px; $gutter_collapsed_width: 62px; $gutter_width: 290px; $gutter_inner_width: 258px; -$sidebar-transition-duration: .15s; -$sidebar-breakpoint: 1440px; /* * UI elements diff --git a/app/assets/stylesheets/pages/commits.scss b/app/assets/stylesheets/pages/commits.scss index 761e33f0df7..c8c6bbde084 100644 --- a/app/assets/stylesheets/pages/commits.scss +++ b/app/assets/stylesheets/pages/commits.scss @@ -7,111 +7,84 @@ margin-right: 9px; } -.commit-header { - padding: 5px 10px; - background-color: $background-color; - border-top: 1px solid #eee; - border-bottom: 1px solid #eee; - font-size: 14px; - - &:first-child { - border-top-width: 0; - } +.lists-separator { + margin: 10px 0; + border-color: #ddd; } -.commit-row-title { - line-height: 1; - margin-bottom: 7px; - - .notes_count { - float: right; - margin-right: 10px; - } +.commits-row { + ul { + margin: 0; - .str-truncated { - max-width: 70%; - } - - .commit-row-message { - color: $gl-dark-link-color; + li.commit { + padding: 8px 0; + } } - .text-expander { - display: inline-block; - background: $gray-light; - color: $gl-placeholder-color; - padding: 0 5px; - cursor: pointer; - border: 1px solid $border-gray-dark; - border-radius: $border-radius-default; - margin-left: 5px; - - &:hover { - background-color: darken($gray-light, 10%); - text-decoration: none; - } + .commits-row-date { + font-size: 15px; + line-height: 20px; + margin-bottom: 5px; } } -.commit-actions { - @media (min-width: $screen-sm-min) { - float: right; - margin-left: $gl-padding; - margin-top: 2px; - font-size: 0; - } +li.commit { + list-style: none; - .btn-transparent { - padding-left: 0; - padding-right: 0; - } + .commit-row-title { + font-size: $list-font-size; + line-height: 20px; + margin-bottom: 2px; - .btn { - &:not(:first-child) { - margin-left: $gl-padding; + .btn-clipboard { + margin-top: -1px; } - } -} -.commit-short-id { - font-family: $monospace_font; - font-weight: 600; -} + .notes_count { + float: right; + margin-right: 10px; + } -.commit { - padding: 10px 0; + .commit_short_id { + min-width: 65px; + color: $gl-dark-link-color; + font-family: $monospace_font; + } - @media (min-width: $screen-sm-min) { - padding-left: 46px; - } + .str-truncated { + max-width: 70%; + } - &:not(:last-child) { - border-bottom: 1px solid #eee; - } + .commit-row-message { + color: $gl-dark-link-color; - a, - button { - color: $gl-dark-link-color; - vertical-align: baseline; - } + &:hover { + text-decoration: underline; + } + } - .avatar { - margin-left: -46px; + .text-expander { + background: #eee; + color: #555; + padding: 0 5px; + cursor: pointer; + margin-left: 4px; + &:hover { + background-color: #ddd; + } + } } .item-title { display: inline-block; - - @media (min-width: $screen-sm-min) { - max-width: 70%; - } + max-width: 70%; } .commit-row-description { font-size: 14px; border-left: 1px solid #eee; padding: 10px 15px; - margin: 10px 0; + margin: 5px 0 10px 5px; background: #f9f9f9; display: none; @@ -120,7 +93,6 @@ background: inherit; padding: 0; margin: 0; - white-space: pre-wrap; } a { @@ -130,7 +102,7 @@ .commit-row-info { color: $gl-gray; - line-height: 1; + line-height: 24px; a { color: $gl-gray; @@ -139,6 +111,10 @@ .avatar { margin-right: 8px; } + + .committed_ago { + display: inline-block; + } } &.inline-commit { diff --git a/app/assets/stylesheets/pages/environments.scss b/app/assets/stylesheets/pages/environments.scss deleted file mode 100644 index e160d676e35..00000000000 --- a/app/assets/stylesheets/pages/environments.scss +++ /dev/null @@ -1,5 +0,0 @@ -.environments { - .commit-title { - margin: 0; - } -} diff --git a/app/assets/stylesheets/pages/groups.scss b/app/assets/stylesheets/pages/groups.scss index ac7721cbe15..ec6c099df5b 100644 --- a/app/assets/stylesheets/pages/groups.scss +++ b/app/assets/stylesheets/pages/groups.scss @@ -39,20 +39,3 @@ } } } - -.groups-cover-block { - - .container-fluid { - position: relative; - } - - .access-request-button { - @include btn-gray; - position: absolute; - right: 16px; - bottom: 32px; - padding: 3px 10px; - text-transform: none; - background-color: $background-color; - } -} diff --git a/app/assets/stylesheets/pages/issuable.scss b/app/assets/stylesheets/pages/issuable.scss index 687117233f6..ea453ce356a 100644 --- a/app/assets/stylesheets/pages/issuable.scss +++ b/app/assets/stylesheets/pages/issuable.scss @@ -34,10 +34,6 @@ color: inherit; } - .issuable-header-text { - margin-top: 7px; - } - .block { @include clearfix; padding: $gl-padding 0; @@ -64,6 +60,10 @@ margin-top: 0; } + .issuable-count { + margin-top: 7px; + } + .gutter-toggle { margin-left: 20px; padding-left: 10px; @@ -145,6 +145,7 @@ .assign-yourself { margin-top: 10px; + font-weight: normal; display: block; } } @@ -157,10 +158,6 @@ font-weight: normal; } - .no-value { - color: $gl-placeholder-color; - } - .sidebar-collapsed-icon { display: none; } @@ -251,16 +248,11 @@ padding-bottom: 0; margin-bottom: 10px; } - - .issuable-header-btn { - display: none; - } } - .issuable-header-btn { + .issuable-pager { background: $gray-normal; border: 1px solid $border-gray-normal; - &:hover { background: $gray-dark; border: 1px solid $border-gray-dark; @@ -271,7 +263,7 @@ } } - a { + a:not(.issuable-pager) { &:hover { color: $md-link-color; text-decoration: none; @@ -330,7 +322,7 @@ margin-left: 5px; a { - color: $gl-placeholder-color; + color: #8c8c8c; } } diff --git a/app/assets/stylesheets/pages/labels.scss b/app/assets/stylesheets/pages/labels.scss index 046c38aba44..bc65404a741 100644 --- a/app/assets/stylesheets/pages/labels.scss +++ b/app/assets/stylesheets/pages/labels.scss @@ -115,13 +115,6 @@ } } -.draggable-handler { - display: inline-block; - opacity: 0; - transition: opacity .3s; - color: $gray-darkest; -} - .prioritized-labels { margin-bottom: 30px; @@ -129,13 +122,6 @@ display: none; color: $gray-light; } - - li:hover { - .draggable-handler { - display: inline-block; - opacity: 1; - } - } } .other-labels { diff --git a/app/assets/stylesheets/pages/merge_requests.scss b/app/assets/stylesheets/pages/merge_requests.scss index 53bff508c72..a47f2580aa3 100644 --- a/app/assets/stylesheets/pages/merge_requests.scss +++ b/app/assets/stylesheets/pages/merge_requests.scss @@ -313,13 +313,3 @@ } } } - -.merged-buttons { - .btn { - float: left; - - &:not(:last-child) { - margin-right: 10px; - } - } -} diff --git a/app/assets/stylesheets/pages/notes.scss b/app/assets/stylesheets/pages/notes.scss index 35d728aec83..0c084118753 100644 --- a/app/assets/stylesheets/pages/notes.scss +++ b/app/assets/stylesheets/pages/notes.scss @@ -139,12 +139,6 @@ ul.notes { @media (min-width: $screen-sm-min) { padding-right: 0; } - - @media (max-width: $screen-xs-min) { - .inline { - display: block; - } - } } .note-emoji-button { @@ -264,11 +258,7 @@ ul.notes { position: absolute; right: 0; top: 0; - - .note-action-button { - margin-left: 10px; - } - + @media (min-width: $screen-sm-min) { position: relative; } diff --git a/app/assets/stylesheets/pages/projects.scss b/app/assets/stylesheets/pages/projects.scss index c85d23a31f0..bb250904255 100644 --- a/app/assets/stylesheets/pages/projects.scss +++ b/app/assets/stylesheets/pages/projects.scss @@ -5,12 +5,10 @@ font-weight: normal; } } - .no-ssh-key-message, .project-limit-message { background-color: #f28d35; margin-bottom: 0; } - .new_project, .edit-project { fieldset.features { @@ -20,6 +18,13 @@ } } +.project-name-holder { + .help-inline { + vertical-align: top; + padding: 7px; + } +} + .project-home-panel { background: $white-light; text-align: left; @@ -224,20 +229,13 @@ right: 16px; bottom: 0; - @media (max-width: $screen-lg-min) { - top: 0; + .btn { + padding: 3px 10px; + background-color: $background-color; } - .access-request-button { - position: absolute; - right: 0; - bottom: 61px; - - @media (max-width: $screen-lg-min) { - position: relative; - bottom: 0; - margin-right: 10px; - } + @media (max-width: 1304px) { + top: 0; } } @@ -288,6 +286,10 @@ color: #555; } +.project_member_row form { + margin: 0; +} + .transfer-project .select2-container { min-width: 200px; } @@ -371,7 +373,6 @@ a.deploy-project-label { .project-import .btn { float: left; - margin-bottom: 10px; margin-right: 10px; } diff --git a/app/assets/stylesheets/pages/tree.scss b/app/assets/stylesheets/pages/tree.scss index 99c9e81ddb9..f16fc7f388f 100644 --- a/app/assets/stylesheets/pages/tree.scss +++ b/app/assets/stylesheets/pages/tree.scss @@ -101,7 +101,7 @@ margin: 0; .commit { - padding: 0 0 0 55px; + padding: 0; .commit-row-title { .commit-row-message { @@ -129,6 +129,4 @@ .tree-controls { float: right; margin-top: 11px; - position: relative; - z-index: 2; } diff --git a/app/controllers/autocomplete_controller.rb b/app/controllers/autocomplete_controller.rb index c89678cf2d8..3865b2d61fd 100644 --- a/app/controllers/autocomplete_controller.rb +++ b/app/controllers/autocomplete_controller.rb @@ -35,7 +35,6 @@ class AutocompleteController < ApplicationController project = Project.find_by_id(params[:project_id]) projects = current_user.authorized_projects - projects = projects.search(params[:search]) if params[:search].present? projects = projects.select do |project| current_user.can?(:admin_issue, project) end diff --git a/app/controllers/concerns/membership_actions.rb b/app/controllers/concerns/membership_actions.rb deleted file mode 100644 index a24273fad0b..00000000000 --- a/app/controllers/concerns/membership_actions.rb +++ /dev/null @@ -1,58 +0,0 @@ -module MembershipActions - extend ActiveSupport::Concern - include MembersHelper - - def request_access - membershipable.request_access(current_user) - - redirect_to polymorphic_path(membershipable), - notice: 'Your request for access has been queued for review.' - end - - def approve_access_request - @member = membershipable.members.request.find(params[:id]) - - return render_403 unless can?(current_user, action_member_permission(:update, @member), @member) - - @member.accept_request - - redirect_to polymorphic_url([membershipable, :members]) - end - - def leave - @member = membershipable.members.find_by(user_id: current_user) - return render_403 unless @member - - source_type = @member.real_source_type.humanize(capitalize: false) - - if can?(current_user, action_member_permission(:destroy, @member), @member) - notice = - if @member.request? - "Your access request to the #{source_type} has been withdrawn." - else - "You left the \"#{@member.source.human_name}\" #{source_type}." - end - @member.destroy - - redirect_to [:dashboard, @member.real_source_type.tableize], notice: notice - else - if cannot_leave? - alert = "You can not leave the \"#{@member.source.human_name}\" #{source_type}." - alert << " Transfer or delete the #{source_type}." - redirect_to polymorphic_url(membershipable), alert: alert - else - render_403 - end - end - end - - protected - - def membershipable - raise NotImplementedError - end - - def cannot_leave? - raise NotImplementedError - end -end diff --git a/app/controllers/groups/group_members_controller.rb b/app/controllers/groups/group_members_controller.rb index d0f2e2949f0..48dbf656e84 100644 --- a/app/controllers/groups/group_members_controller.rb +++ b/app/controllers/groups/group_members_controller.rb @@ -1,13 +1,11 @@ class Groups::GroupMembersController < Groups::ApplicationController - include MembershipActions - # Authorize - before_action :authorize_admin_group_member!, except: [:index, :leave, :request_access] + before_action :authorize_admin_group_member!, except: [:index, :leave] def index @project = @group.projects.find(params[:project_id]) if params[:project_id] @members = @group.group_members - @members = @members.non_pending unless can?(current_user, :admin_group, @group) + @members = @members.non_invite unless can?(current_user, :admin_group, @group) if params[:search].present? users = @group.users.search(params[:search]).to_a @@ -60,16 +58,25 @@ class Groups::GroupMembersController < Groups::ApplicationController end end - protected + def leave + @group_member = @group.group_members.find_by(user_id: current_user) - def member_params - params.require(:group_member).permit(:access_level, :user_id) + if can?(current_user, :destroy_group_member, @group_member) + @group_member.destroy + + redirect_to(dashboard_groups_path, notice: "You left #{group.name} group.") + else + if @group.last_owner?(current_user) + redirect_to(dashboard_groups_path, alert: "You can not leave #{group.name} group because you're the last owner. Transfer or delete the group.") + else + return render_403 + end + end end - # MembershipActions concern - alias_method :membershipable, :group + protected - def cannot_leave? - @group.last_owner?(current_user) + def member_params + params.require(:group_member).permit(:access_level, :user_id) end end diff --git a/app/controllers/jwt_controller.rb b/app/controllers/jwt_controller.rb index 014b9b43ff2..131a16dad9b 100644 --- a/app/controllers/jwt_controller.rb +++ b/app/controllers/jwt_controller.rb @@ -42,7 +42,7 @@ class JwtController < ApplicationController end def authenticate_user(login, password) - user = Gitlab::Auth.find_with_user_password(login, password) + user = Gitlab::Auth.find_in_gitlab_or_ldap(login, password) Gitlab::Auth.rate_limit!(request.ip, success: user.present?, login: login) user end diff --git a/app/controllers/profiles/notifications_controller.rb b/app/controllers/profiles/notifications_controller.rb index 40d1906a53f..18ee55c839a 100644 --- a/app/controllers/profiles/notifications_controller.rb +++ b/app/controllers/profiles/notifications_controller.rb @@ -1,13 +1,12 @@ class Profiles::NotificationsController < Profiles::ApplicationController def show - @user = current_user - @group_notifications = current_user.notification_settings.for_groups - @project_notifications = current_user.notification_settings.for_projects - @global_notification_setting = current_user.global_notification_setting + @user = current_user + @group_notifications = current_user.notification_settings.for_groups + @project_notifications = current_user.notification_settings.for_projects end def update - if current_user.update_attributes(user_params) && update_notification_settings + if current_user.update_attributes(user_params) flash[:notice] = "Notification settings saved" else flash[:alert] = "Failed to save new settings" @@ -17,18 +16,6 @@ class Profiles::NotificationsController < Profiles::ApplicationController end def user_params - params.require(:user).permit(:notification_email) - end - - def global_notification_setting_params - params.require(:global_notification_setting).permit(:level) - end - - private - - def update_notification_settings - return true unless global_notification_setting_params - - current_user.global_notification_setting.update_attributes(global_notification_setting_params) + params.require(:user).permit(:notification_email, :notification_level) end end diff --git a/app/controllers/projects/artifacts_controller.rb b/app/controllers/projects/artifacts_controller.rb index f11c8321464..832d7deb57d 100644 --- a/app/controllers/projects/artifacts_controller.rb +++ b/app/controllers/projects/artifacts_controller.rb @@ -1,18 +1,22 @@ class Projects::ArtifactsController < Projects::ApplicationController layout 'project' before_action :authorize_read_build! - before_action :authorize_update_build!, only: [:keep] - before_action :validate_artifacts! def download unless artifacts_file.file_storage? return redirect_to artifacts_file.url end + unless artifacts_file.exists? + return render_404 + end + send_file artifacts_file.path, disposition: 'attachment' end def browse + return render_404 unless build.artifacts? + directory = params[:path] ? "#{params[:path]}/" : '' @entry = build.artifacts_metadata_entry(directory) @@ -30,17 +34,8 @@ class Projects::ArtifactsController < Projects::ApplicationController end end - def keep - build.keep_artifacts! - redirect_to namespace_project_build_path(project.namespace, project, build) - end - private - def validate_artifacts! - render_404 unless build.artifacts? - end - def build @build ||= project.builds.find_by!(id: params[:build_id]) end diff --git a/app/controllers/projects/builds_controller.rb b/app/controllers/projects/builds_controller.rb index ef3051d7519..14c82826342 100644 --- a/app/controllers/projects/builds_controller.rb +++ b/app/controllers/projects/builds_controller.rb @@ -51,7 +51,7 @@ class Projects::BuildsController < Projects::ApplicationController return render_404 end - build = Ci::Build.retry(@build, current_user) + build = Ci::Build.retry(@build) redirect_to build_path(build) end diff --git a/app/controllers/projects/commit_controller.rb b/app/controllers/projects/commit_controller.rb index 6751737d15e..20637fa46fe 100644 --- a/app/controllers/projects/commit_controller.rb +++ b/app/controllers/projects/commit_controller.rb @@ -46,7 +46,7 @@ class Projects::CommitController < Projects::ApplicationController def retry_builds ci_builds.latest.failed.each do |build| if build.retryable? - Ci::Build.retry(build, current_user) + Ci::Build.retry(build) end end diff --git a/app/controllers/projects/environments_controller.rb b/app/controllers/projects/environments_controller.rb deleted file mode 100644 index 4b433796161..00000000000 --- a/app/controllers/projects/environments_controller.rb +++ /dev/null @@ -1,49 +0,0 @@ -class Projects::EnvironmentsController < Projects::ApplicationController - layout 'project' - before_action :authorize_read_environment! - before_action :authorize_create_environment!, only: [:new, :create] - before_action :authorize_update_environment!, only: [:destroy] - before_action :environment, only: [:show, :destroy] - - def index - @environments = project.environments - end - - def show - @deployments = environment.deployments.order(id: :desc).page(params[:page]) - end - - def new - @environment = project.environments.new - end - - def create - @environment = project.environments.create(create_params) - - if @environment.persisted? - redirect_to namespace_project_environment_path(project.namespace, project, @environment) - else - render 'new' - end - end - - def destroy - if @environment.destroy - flash[:notice] = 'Environment was successfully removed.' - else - flash[:alert] = 'Failed to remove environment.' - end - - redirect_to namespace_project_environments_path(project.namespace, project) - end - - private - - def create_params - params.require(:environment).permit(:name) - end - - def environment - @environment ||= project.environments.find(params[:id]) - end -end diff --git a/app/controllers/projects/git_http_controller.rb b/app/controllers/projects/git_http_controller.rb index f907d63258b..348d6cf4d96 100644 --- a/app/controllers/projects/git_http_controller.rb +++ b/app/controllers/projects/git_http_controller.rb @@ -43,7 +43,7 @@ class Projects::GitHttpController < Projects::ApplicationController return if project && project.public? && upload_pack? authenticate_or_request_with_http_basic do |login, password| - auth_result = Gitlab::Auth.find_for_git_client(login, password, project: project, ip: request.ip) + auth_result = Gitlab::Auth.find(login, password, project: project, ip: request.ip) if auth_result.type == :ci && upload_pack? @ci = true diff --git a/app/controllers/projects/merge_requests_controller.rb b/app/controllers/projects/merge_requests_controller.rb index 851822d805a..67e7187c10d 100644 --- a/app/controllers/projects/merge_requests_controller.rb +++ b/app/controllers/projects/merge_requests_controller.rb @@ -204,19 +204,10 @@ class Projects::MergeRequestsController < Projects::ApplicationController @merge_request.update(merge_error: nil) - if params[:merge_when_build_succeeds].present? - if @merge_request.pipeline && @merge_request.pipeline.active? - MergeRequests::MergeWhenBuildSucceedsService.new(@project, current_user, merge_params) - .execute(@merge_request) - @status = :merge_when_build_succeeds - elsif @merge_request.pipeline.success? - # This can be triggered when a user clicks the auto merge button while - # the tests finish at about the same time - MergeWorker.perform_async(@merge_request.id, current_user.id, params) - @status = :success - else - @status = :failed - end + if params[:merge_when_build_succeeds].present? && @merge_request.pipeline && @merge_request.pipeline.active? + MergeRequests::MergeWhenBuildSucceedsService.new(@project, current_user, merge_params) + .execute(@merge_request) + @status = :merge_when_build_succeeds else MergeWorker.perform_async(@merge_request.id, current_user.id, params) @status = :success diff --git a/app/controllers/projects/pipelines_controller.rb b/app/controllers/projects/pipelines_controller.rb index 127bd1a4318..cac440ae53e 100644 --- a/app/controllers/projects/pipelines_controller.rb +++ b/app/controllers/projects/pipelines_controller.rb @@ -32,7 +32,7 @@ class Projects::PipelinesController < Projects::ApplicationController end def retry - pipeline.retry_failed(current_user) + pipeline.retry_failed redirect_back_or_default default: namespace_project_pipelines_path(project.namespace, project) end diff --git a/app/controllers/projects/project_members_controller.rb b/app/controllers/projects/project_members_controller.rb index 35d067cd029..cdea5f0b776 100644 --- a/app/controllers/projects/project_members_controller.rb +++ b/app/controllers/projects/project_members_controller.rb @@ -1,12 +1,10 @@ class Projects::ProjectMembersController < Projects::ApplicationController - include MembershipActions - # Authorize - before_action :authorize_admin_project_member!, except: [:index, :leave, :request_access] + before_action :authorize_admin_project_member!, except: [:leave, :index] def index @project_members = @project.project_members - @project_members = @project_members.non_pending unless can?(current_user, :admin_project, @project) + @project_members = @project_members.non_invite unless can?(current_user, :admin_project, @project) if params[:search].present? users = @project.users.search(params[:search]).to_a @@ -16,10 +14,9 @@ class Projects::ProjectMembersController < Projects::ApplicationController @project_members = @project_members.order('access_level DESC') @group = @project.group - if @group @group_members = @group.group_members - @group_members = @group_members.non_pending unless can?(current_user, :admin_group, @group) + @group_members = @group_members.non_invite unless can?(current_user, :admin_group, @group) if params[:search].present? users = @group.users.search(params[:search]).to_a @@ -76,6 +73,26 @@ class Projects::ProjectMembersController < Projects::ApplicationController end end + def leave + @project_member = @project.project_members.find_by(user_id: current_user) + + if can?(current_user, :destroy_project_member, @project_member) + @project_member.destroy + + respond_to do |format| + format.html { redirect_to dashboard_projects_path, notice: "You left the project." } + format.js { head :ok } + end + else + if current_user == @project.owner + message = 'You can not leave your own project. Transfer or delete the project.' + redirect_back_or_default(default: { action: 'index' }, options: { alert: message }) + else + render_403 + end + end + end + def apply_import source_project = Project.find(params[:source_project_id]) @@ -95,11 +112,4 @@ class Projects::ProjectMembersController < Projects::ApplicationController def member_params params.require(:project_member).permit(:user_id, :access_level) end - - # MembershipActions concern - alias_method :membershipable, :project - - def cannot_leave? - current_user == @project.owner - end end diff --git a/app/controllers/projects/todos_controller.rb b/app/controllers/projects/todos_controller.rb deleted file mode 100644 index a51bd5e2b49..00000000000 --- a/app/controllers/projects/todos_controller.rb +++ /dev/null @@ -1,31 +0,0 @@ -class Projects::TodosController < Projects::ApplicationController - def create - todos = TodoService.new.mark_todo(issuable, current_user) - - render json: { - todo: todos, - count: current_user.todos.pending.count, - } - end - - def update - current_user.todos.find_by_id(params[:id]).update(state: :done) - - render json: { - count: current_user.todos.pending.count, - } - end - - private - - def issuable - @issuable ||= begin - case params[:issuable_type] - when "issue" - @project.issues.find(params[:issuable_id]) - when "merge_request" - @project.merge_requests.find(params[:issuable_id]) - end - end - end -end diff --git a/app/controllers/projects/wikis_controller.rb b/app/controllers/projects/wikis_controller.rb index 7ec1e73b3be..2aa6bed0724 100644 --- a/app/controllers/projects/wikis_controller.rb +++ b/app/controllers/projects/wikis_controller.rb @@ -16,9 +16,6 @@ class Projects::WikisController < Projects::ApplicationController if @page render 'show' elsif file = @project_wiki.find_file(params[:id], params[:version_id]) - response.headers['Content-Security-Policy'] = "default-src 'none'" - response.headers['X-Content-Security-Policy'] = "default-src 'none'" - if file.on_disk? send_file file.on_disk_path, disposition: 'inline' else diff --git a/app/controllers/sessions_controller.rb b/app/controllers/sessions_controller.rb index 17aed816cbd..dae8f7b1447 100644 --- a/app/controllers/sessions_controller.rb +++ b/app/controllers/sessions_controller.rb @@ -40,7 +40,7 @@ class SessionsController < Devise::SessionsController # Handle an "initial setup" state, where there's only one user, it's an admin, # and they require a password change. def check_initial_setup - return unless User.limit(2).count == 1 # Count as much 2 to know if we have exactly one + return unless User.count == 1 user = User.admins.last diff --git a/app/finders/notes_finder.rb b/app/finders/notes_finder.rb index 0b7832e6583..ee14ac60fb4 100644 --- a/app/finders/notes_finder.rb +++ b/app/finders/notes_finder.rb @@ -12,7 +12,7 @@ class NotesFinder when "commit" project.notes.for_commit_id(target_id).non_diff_notes when "issue" - project.issues.visible_to_user(current_user).find(target_id).notes.inc_author + project.issues.find(target_id).notes.inc_author when "merge_request" project.merge_requests.find(target_id).mr_and_commit_notes.inc_author when "snippet", "project_snippet" diff --git a/app/finders/snippets_finder.rb b/app/finders/snippets_finder.rb index 00ff1611039..01cbf91c658 100644 --- a/app/finders/snippets_finder.rb +++ b/app/finders/snippets_finder.rb @@ -51,7 +51,7 @@ class SnippetsFinder snippets = project.snippets.fresh if current_user - if project.team.member?(current_user) || current_user.admin? + if project.team.member?(current_user.id) || current_user.admin? snippets else snippets.public_and_internal diff --git a/app/finders/todos_finder.rb b/app/finders/todos_finder.rb index aa47c6c157e..1d88116d7d2 100644 --- a/app/finders/todos_finder.rb +++ b/app/finders/todos_finder.rb @@ -36,7 +36,7 @@ class TodosFinder private def action_id? - action_id.present? && [Todo::ASSIGNED, Todo::MENTIONED, Todo::BUILD_FAILED, Todo::MARKED].include?(action_id.to_i) + action_id.present? && [Todo::ASSIGNED, Todo::MENTIONED, Todo::BUILD_FAILED].include?(action_id.to_i) end def action_id diff --git a/app/helpers/blob_helper.rb b/app/helpers/blob_helper.rb index 85559fbc5f5..cec2dc753fe 100644 --- a/app/helpers/blob_helper.rb +++ b/app/helpers/blob_helper.rb @@ -116,7 +116,7 @@ module BlobHelper end def blob_text_viewable?(blob) - blob && blob.text? && !blob.lfs_pointer? && !blob.only_display_raw? + blob && blob.text? && !blob.lfs_pointer? end def blob_size(blob) diff --git a/app/helpers/branches_helper.rb b/app/helpers/branches_helper.rb index 3ee3fc74f0c..e39548e17e1 100644 --- a/app/helpers/branches_helper.rb +++ b/app/helpers/branches_helper.rb @@ -14,8 +14,4 @@ module BranchesHelper ::Gitlab::GitAccess.new(current_user, project).can_push_to_branch?(branch_name) end - - def project_branches - options_for_select(@project.repository.branch_names, @project.default_branch) - end end diff --git a/app/helpers/button_helper.rb b/app/helpers/button_helper.rb index 07a3f452460..f742922d926 100644 --- a/app/helpers/button_helper.rb +++ b/app/helpers/button_helper.rb @@ -17,15 +17,7 @@ module ButtonHelper def clipboard_button(data = {}) content_tag :button, icon('clipboard'), - class: "btn", - data: data, - type: :button - end - - def clipboard_button_with_class(data = {}, css_class: 'btn-clipboard') - content_tag :button, - icon('clipboard'), - class: "btn #{css_class}", + class: 'btn btn-clipboard', data: data, type: :button end diff --git a/app/helpers/ci_status_helper.rb b/app/helpers/ci_status_helper.rb index 8e4ae1e6aec..07e5c146844 100644 --- a/app/helpers/ci_status_helper.rb +++ b/app/helpers/ci_status_helper.rb @@ -38,10 +38,10 @@ module CiStatusHelper icon(icon_name + ' fw') end - def render_commit_status(commit, tooltip_placement: 'auto left', cssclass: '') + def render_commit_status(commit, tooltip_placement: 'auto left') project = commit.project path = builds_namespace_project_commit_path(project.namespace, project, commit) - render_status_with_link('commit', commit.status, path, tooltip_placement, cssclass: cssclass) + render_status_with_link('commit', commit.status, path, tooltip_placement) end def render_pipeline_status(pipeline, tooltip_placement: 'auto left') @@ -57,10 +57,10 @@ module CiStatusHelper private - def render_status_with_link(type, status, path, tooltip_placement, cssclass: '') + def render_status_with_link(type, status, path, tooltip_placement) link_to ci_icon_for_status(status), path, - class: "ci-status-link ci-status-icon-#{status.dasherize} #{cssclass}", + class: "ci-status-link ci-status-icon-#{status.dasherize}", title: "#{type.titleize}: #{ci_label_for_status(status)}", data: { toggle: 'tooltip', placement: tooltip_placement } end diff --git a/app/helpers/commits_helper.rb b/app/helpers/commits_helper.rb index 474041eccbb..d328f56c80c 100644 --- a/app/helpers/commits_helper.rb +++ b/app/helpers/commits_helper.rb @@ -16,16 +16,6 @@ module CommitsHelper commit_person_link(commit, options.merge(source: :committer)) end - def commit_author_avatar(commit, options = {}) - options = options.merge(source: :author) - user = commit.send(options[:source]) - - source_email = clean(commit.send "#{options[:source]}_email".to_sym) - person_email = user.try(:email) || source_email - - image_tag(avatar_icon(person_email, options[:size]), class: "avatar #{"s#{options[:size]}" if options[:size]} hidden-xs", width: options[:size], alt: "") - end - def image_diff_class(diff) if diff.deleted_file "deleted" @@ -112,24 +102,24 @@ module CommitsHelper if current_controller?(:projects, :commits) if @repo.blob_at(commit.id, @path) return link_to( - "Browse File", + "Browse File »", namespace_project_blob_path(project.namespace, project, tree_join(commit.id, @path)), - class: "btn btn-default" + class: "pull-right" ) elsif @path.present? return link_to( - "Browse Directory", + "Browse Directory »", namespace_project_tree_path(project.namespace, project, tree_join(commit.id, @path)), - class: "btn btn-default" + class: "pull-right" ) end end link_to( "Browse Files", namespace_project_tree_path(project.namespace, project, commit), - class: "btn btn-default" + class: "pull-right" ) end @@ -139,7 +129,7 @@ module CommitsHelper tooltip = "Revert this #{commit.change_type_title} in a new merge request" if has_tooltip if can_collaborate_with_project? - btn_class = "btn btn-warning btn-#{btn_class}" unless btn_class.nil? + btn_class = "btn btn-grouped btn-close btn-#{btn_class}" unless btn_class.nil? link_to 'Revert', '#modal-revert-commit', 'data-toggle' => 'modal', 'data-container' => 'body', title: (tooltip if has_tooltip), class: "#{btn_class} #{'has-tooltip' if has_tooltip}" elsif can?(current_user, :fork_project, @project) continue_params = { @@ -151,7 +141,7 @@ module CommitsHelper namespace_key: current_user.namespace.id, continue: continue_params) - btn_class = "btn btn-grouped btn-warning" unless btn_class.nil? + btn_class = "btn btn-grouped btn-close" unless btn_class.nil? link_to 'Revert', fork_path, class: btn_class, method: :post, 'data-toggle' => 'tooltip', 'data-container' => 'body', title: (tooltip if has_tooltip) end @@ -163,7 +153,7 @@ module CommitsHelper tooltip = "Cherry-pick this #{commit.change_type_title} in a new merge request" if can_collaborate_with_project? - btn_class = "btn btn-default btn-#{btn_class}" unless btn_class.nil? + btn_class = "btn btn-default btn-grouped btn-#{btn_class}" unless btn_class.nil? link_to 'Cherry-pick', '#modal-cherry-pick-commit', 'data-toggle' => 'modal', 'data-container' => 'body', title: (tooltip if has_tooltip), class: "#{btn_class} #{'has-tooltip' if has_tooltip}" elsif can?(current_user, :fork_project, @project) continue_params = { @@ -197,10 +187,12 @@ module CommitsHelper source_email = clean(commit.send "#{options[:source]}_email".to_sym) person_name = user.try(:name) || source_name + person_email = user.try(:email) || source_email text = if options[:avatar] - %Q{<span class="commit-#{options[:source]}-name">#{person_name}</span>} + avatar = image_tag(avatar_icon(person_email, options[:size]), class: "avatar #{"s#{options[:size]}" if options[:size]}", width: options[:size], alt: "") + %Q{#{avatar} <span class="commit-#{options[:source]}-name">#{person_name}</span>} else person_name end diff --git a/app/helpers/diff_helper.rb b/app/helpers/diff_helper.rb index e22dce59d0f..cbe47176831 100644 --- a/app/helpers/diff_helper.rb +++ b/app/helpers/diff_helper.rb @@ -135,11 +135,6 @@ module DiffHelper toggle_whitespace_link(url, options) end - def diff_compare_whitespace_link(project, from, to, options) - url = namespace_project_compare_path(project.namespace, project, from, to, params_with_whitespace) - toggle_whitespace_link(url, options) - end - private def hide_whitespace? diff --git a/app/helpers/gitlab_routing_helper.rb b/app/helpers/gitlab_routing_helper.rb index 5386ddadc62..2ce2d4e694f 100644 --- a/app/helpers/gitlab_routing_helper.rb +++ b/app/helpers/gitlab_routing_helper.rb @@ -13,23 +13,10 @@ # merge_request_path(merge_request) # module GitlabRoutingHelper - # Project def project_path(project, *args) namespace_project_path(project.namespace, project, *args) end - def project_url(project, *args) - namespace_project_url(project.namespace, project, *args) - end - - def edit_project_path(project, *args) - edit_namespace_project_path(project.namespace, project, *args) - end - - def edit_project_url(project, *args) - edit_namespace_project_url(project.namespace, project, *args) - end - def project_files_path(project, *args) namespace_project_tree_path(project.namespace, project, @ref || project.repository.root_ref) end @@ -42,10 +29,6 @@ module GitlabRoutingHelper namespace_project_pipelines_path(project.namespace, project, *args) end - def project_environments_path(project, *args) - namespace_project_environments_path(project.namespace, project, *args) - end - def project_builds_path(project, *args) namespace_project_builds_path(project.namespace, project, *args) end @@ -58,6 +41,10 @@ module GitlabRoutingHelper activity_namespace_project_path(project.namespace, project, *args) end + def edit_project_path(project, *args) + edit_namespace_project_path(project.namespace, project, *args) + end + def runners_path(project, *args) namespace_project_runners_path(project.namespace, project, *args) end @@ -78,6 +65,14 @@ module GitlabRoutingHelper namespace_project_milestone_path(entity.project.namespace, entity.project, entity, *args) end + def project_url(project, *args) + namespace_project_url(project.namespace, project, *args) + end + + def edit_project_url(project, *args) + edit_namespace_project_url(project.namespace, project, *args) + end + def issue_url(entity, *args) namespace_project_issue_url(entity.project.namespace, entity.project, entity, *args) end @@ -97,56 +92,4 @@ module GitlabRoutingHelper toggle_subscription_namespace_project_merge_request_path(entity.project.namespace, entity.project, entity) end end - - ## Members - def project_members_url(project, *args) - namespace_project_project_members_url(project.namespace, project) - end - - def project_member_path(project_member, *args) - namespace_project_project_member_path(project_member.source.namespace, project_member.source, project_member) - end - - def request_access_project_members_path(project, *args) - request_access_namespace_project_project_members_path(project.namespace, project) - end - - def leave_project_members_path(project, *args) - leave_namespace_project_project_members_path(project.namespace, project) - end - - def approve_access_request_project_member_path(project_member, *args) - approve_access_request_namespace_project_project_member_path(project_member.source.namespace, project_member.source, project_member) - end - - def resend_invite_project_member_path(project_member, *args) - resend_invite_namespace_project_project_member_path(project_member.source.namespace, project_member.source, project_member) - end - - # Groups - - ## Members - def group_members_url(group, *args) - group_group_members_url(group, *args) - end - - def group_member_path(group_member, *args) - group_group_member_path(group_member.source, group_member) - end - - def request_access_group_members_path(group, *args) - request_access_group_group_members_path(group) - end - - def leave_group_members_path(group, *args) - leave_group_group_members_path(group) - end - - def approve_access_request_group_member_path(group_member, *args) - approve_access_request_group_group_member_path(group_member.source, group_member) - end - - def resend_invite_group_member_path(group_member, *args) - resend_invite_group_group_member_path(group_member.source, group_member) - end end diff --git a/app/helpers/groups_helper.rb b/app/helpers/groups_helper.rb index b9211e88473..4cac69c6795 100644 --- a/app/helpers/groups_helper.rb +++ b/app/helpers/groups_helper.rb @@ -1,4 +1,24 @@ module GroupsHelper + def remove_user_from_group_message(group, member) + if member.user + "Are you sure you want to remove \"#{member.user.name}\" from \"#{group.name}\"?" + else + "Are you sure you want to revoke the invitation for \"#{member.invite_email}\" to join \"#{group.name}\"?" + end + end + + def leave_group_message(group) + "Are you sure you want to leave \"#{group}\" group?" + end + + def should_user_see_group_roles?(user, group) + if user + user.is_admin? || group.members.exists?(user_id: user.id) + else + false + end + end + def can_change_group_visibility_level?(group) can?(current_user, :change_visibility_level, group) end diff --git a/app/helpers/issuables_helper.rb b/app/helpers/issuables_helper.rb index 8dbc51a689f..40d8ce8a1d3 100644 --- a/app/helpers/issuables_helper.rb +++ b/app/helpers/issuables_helper.rb @@ -67,12 +67,6 @@ module IssuablesHelper end end - def has_todo(issuable) - unless current_user.nil? - current_user.todos.find_by(target_id: issuable.id, state: :pending) - end - end - private def sidebar_gutter_collapsed? diff --git a/app/helpers/members_helper.rb b/app/helpers/members_helper.rb deleted file mode 100644 index 877c77050be..00000000000 --- a/app/helpers/members_helper.rb +++ /dev/null @@ -1,39 +0,0 @@ -module MembersHelper - # Returns a `<action>_<source>_member` association, e.g.: - # - admin_project_member, update_project_member, destroy_project_member - # - admin_group_member, update_group_member, destroy_group_member - def action_member_permission(action, member) - "#{action}_#{member.type.underscore}".to_sym - end - - def remove_member_message(member, user: nil) - user = current_user if defined?(current_user) - - text = 'Are you sure you want to ' - action = - if member.request? - if member.user == user - 'withdraw your access request for' - else - "deny #{member.user.name}'s request to join" - end - elsif member.invite? - "revoke the invitation for #{member.invite_email} to join" - else - "remove #{member.user.name} from" - end - - text << action << " the #{member.source.human_name} #{member.real_source_type.humanize(capitalize: false)}?" - end - - def remove_member_title(member) - text = " from #{member.real_source_type.humanize(capitalize: false)}" - - text.prepend(member.request? ? 'Deny access request' : 'Remove user') - end - - def leave_confirmation_message(member_source) - "Are you sure you want to leave the " \ - "\"#{member_source.human_name}\" #{member_source.class.to_s.humanize(capitalize: false)}?" - end -end diff --git a/app/helpers/nav_helper.rb b/app/helpers/nav_helper.rb index 3ff8be5e284..469accf3142 100644 --- a/app/helpers/nav_helper.rb +++ b/app/helpers/nav_helper.rb @@ -12,10 +12,10 @@ module NavHelper end def page_sidebar_class - if pinned_nav? - "page-sidebar-expanded page-sidebar-pinned" - else + if nav_menu_collapsed? "page-sidebar-collapsed" + else + "page-sidebar-expanded" end end @@ -36,15 +36,7 @@ module NavHelper end def nav_header_class - class_name = '' - class_name << " with-horizontal-nav" if defined?(nav) && nav - - if pinned_nav? - class_name << " header-expanded header-pinned-nav" - else - class_name << " header-collapsed" - end - + class_name = " with-horizontal-nav" if defined?(nav) && nav class_name end @@ -55,8 +47,4 @@ module NavHelper def nav_control_class "nav-control" if current_user end - - def pinned_nav? - cookies[:pin_nav] == 'true' - end end diff --git a/app/helpers/notifications_helper.rb b/app/helpers/notifications_helper.rb index 50c21fc0d49..b8e64b3890a 100644 --- a/app/helpers/notifications_helper.rb +++ b/app/helpers/notifications_helper.rb @@ -61,23 +61,4 @@ module NotificationsHelper end end end - - def notification_level_radio_buttons - html = "" - - NotificationSetting.levels.each_key do |level| - level = level.to_sym - next if level == :global - - html << content_tag(:div, class: "radio") do - content_tag(:label, { value: level }) do - radio_button_tag(:"global_notification_setting[level]", level, @global_notification_setting.level.to_sym == level) + - content_tag(:div, level.to_s.capitalize, class: "level-title") + - content_tag(:p, notification_description(level)) - end - end - end - - html.html_safe - end end diff --git a/app/helpers/projects_helper.rb b/app/helpers/projects_helper.rb index d91e3332e48..5e5d170a9f3 100644 --- a/app/helpers/projects_helper.rb +++ b/app/helpers/projects_helper.rb @@ -1,4 +1,12 @@ module ProjectsHelper + def remove_from_project_team_message(project, member) + if member.user + "You are going to remove #{member.user.name} from #{project.name} project team. Are you sure?" + else + "You are going to revoke the invitation for #{member.invite_email} to join #{project.name} project team. Are you sure?" + end + end + def link_to_project(project) link_to [project.namespace.becomes(Namespace), project], title: h(project.name) do title = content_tag(:span, project.name, class: 'project-name') @@ -41,7 +49,7 @@ module ProjectsHelper author_html = author_html.html_safe if opts[:name] - link_to(author_html, user_path(author), class: "author_link #{"#{opts[:extra_class]}" if opts[:extra_class]} #{"#{opts[:mobile_classes]}" if opts[:mobile_classes]}").html_safe + link_to(author_html, user_path(author), class: "author_link #{"#{opts[:mobile_classes]}" if opts[:mobile_classes]}").html_safe else title = opts[:title].sub(":name", sanitize(author.name)) link_to(author_html, user_path(author), class: "author_link has-tooltip", title: title, data: { container: 'body' } ).html_safe @@ -107,6 +115,14 @@ module ProjectsHelper end end + def user_max_access_in_project(user_id, project) + level = project.team.max_member_access(user_id) + + if level + Gitlab::Access.options_with_owner.key(level) + end + end + def license_short_name(project) return 'LICENSE' if project.repository.license_key.nil? @@ -140,10 +156,6 @@ module ProjectsHelper nav_tabs << :container_registry end - if can?(current_user, :read_environment, project) - nav_tabs << :environments - end - if can?(current_user, :admin_project, project) nav_tabs << :settings end @@ -274,6 +286,10 @@ module ProjectsHelper end end + def leave_project_message(project) + "Are you sure you want to leave \"#{project.name}\" project?" + end + def new_readme_path ref = @repository.root_ref if @repository ref ||= 'master' diff --git a/app/helpers/time_helper.rb b/app/helpers/time_helper.rb index b04b0a5114c..8142f733e76 100644 --- a/app/helpers/time_helper.rb +++ b/app/helpers/time_helper.rb @@ -20,6 +20,7 @@ module TimeHelper end end + def date_from_to(from, to) "#{from.to_s(:short)} - #{to.to_s(:short)}" end diff --git a/app/helpers/todos_helper.rb b/app/helpers/todos_helper.rb index 9adf5ef29f7..b4923fbb138 100644 --- a/app/helpers/todos_helper.rb +++ b/app/helpers/todos_helper.rb @@ -12,7 +12,6 @@ module TodosHelper when Todo::ASSIGNED then 'assigned you' when Todo::MENTIONED then 'mentioned you on' when Todo::BUILD_FAILED then 'The build failed for your' - when Todo::MARKED then 'marked this as a Todo for' end end diff --git a/app/mailers/emails/groups.rb b/app/mailers/emails/groups.rb new file mode 100644 index 00000000000..1c43f95dc8c --- /dev/null +++ b/app/mailers/emails/groups.rb @@ -0,0 +1,52 @@ +module Emails + module Groups + def group_access_granted_email(group_member_id) + @group_member = GroupMember.find(group_member_id) + @group = @group_member.group + + @target_url = group_url(@group) + @current_user = @group_member.user + + mail(to: @group_member.user.notification_email, + subject: subject("Access to group was granted")) + end + + def group_member_invited_email(group_member_id, token) + @group_member = GroupMember.find group_member_id + @group = @group_member.group + @token = token + + @target_url = group_url(@group) + @current_user = @group_member.user + + mail(to: @group_member.invite_email, + subject: "Invitation to join group #{@group.name}") + end + + def group_invite_accepted_email(group_member_id) + @group_member = GroupMember.find group_member_id + return if @group_member.created_by.nil? + + @group = @group_member.group + + @target_url = group_url(@group) + @current_user = @group_member.created_by + + mail(to: @group_member.created_by.notification_email, + subject: subject("Invitation accepted")) + end + + def group_invite_declined_email(group_id, invite_email, access_level, created_by_id) + return if created_by_id.nil? + + @group = Group.find(group_id) + @current_user = @created_by = User.find(created_by_id) + @access_level = access_level + @invite_email = invite_email + + @target_url = group_url(@group) + mail(to: @created_by.notification_email, + subject: subject("Invitation declined")) + end + end +end diff --git a/app/mailers/emails/members.rb b/app/mailers/emails/members.rb deleted file mode 100644 index 6dde2e9847d..00000000000 --- a/app/mailers/emails/members.rb +++ /dev/null @@ -1,81 +0,0 @@ -module Emails - module Members - extend ActiveSupport::Concern - include MembersHelper - - included do - helper_method :member_source, :member - end - - def member_access_requested_email(member_source_type, member_id) - @member_source_type = member_source_type - @member_id = member_id - - admins = member_source.members.owners_and_masters.includes(:user).pluck(:notification_email) - - mail(to: admins, - subject: subject("Request to join the #{member_source.human_name} #{member_source.model_name.singular}")) - end - - def member_access_granted_email(member_source_type, member_id) - @member_source_type = member_source_type - @member_id = member_id - - mail(to: member.user.notification_email, - subject: subject("Access to the #{member_source.human_name} #{member_source.model_name.singular} was granted")) - end - - def member_access_denied_email(member_source_type, source_id, user_id) - @member_source_type = member_source_type - @member_source = member_source_class.find(source_id) - requester = User.find(user_id) - - mail(to: requester.notification_email, - subject: subject("Access to the #{member_source.human_name} #{member_source.model_name.singular} was denied")) - end - - def member_invited_email(member_source_type, member_id, token) - @member_source_type = member_source_type - @member_id = member_id - @token = token - - mail(to: member.invite_email, - subject: "Invitation to join the #{member_source.human_name} #{member_source.model_name.singular}") - end - - def member_invite_accepted_email(member_source_type, member_id) - @member_source_type = member_source_type - @member_id = member_id - return unless member.created_by - - mail(to: member.created_by.notification_email, - subject: subject('Invitation accepted')) - end - - def member_invite_declined_email(member_source_type, source_id, invite_email, created_by_id) - return unless created_by_id - - @member_source_type = member_source_type - @member_source = member_source_class.find(source_id) - @invite_email = invite_email - inviter = User.find(created_by_id) - - mail(to: inviter.notification_email, - subject: subject('Invitation declined')) - end - - def member - @member ||= Member.find(@member_id) - end - - def member_source - @member_source ||= member.source - end - - private - - def member_source_class - @member_source_type.classify.constantize - end - end -end diff --git a/app/mailers/emails/projects.rb b/app/mailers/emails/projects.rb index 689fb3e0ffb..fdf1e9f5afc 100644 --- a/app/mailers/emails/projects.rb +++ b/app/mailers/emails/projects.rb @@ -1,5 +1,55 @@ module Emails module Projects + def project_access_granted_email(project_member_id) + @project_member = ProjectMember.find project_member_id + @project = @project_member.project + + @target_url = namespace_project_url(@project.namespace, @project) + @current_user = @project_member.user + + mail(to: @project_member.user.notification_email, + subject: subject("Access to project was granted")) + end + + def project_member_invited_email(project_member_id, token) + @project_member = ProjectMember.find project_member_id + @project = @project_member.project + @token = token + + @target_url = namespace_project_url(@project.namespace, @project) + @current_user = @project_member.user + + mail(to: @project_member.invite_email, + subject: "Invitation to join project #{@project.name_with_namespace}") + end + + def project_invite_accepted_email(project_member_id) + @project_member = ProjectMember.find project_member_id + return if @project_member.created_by.nil? + + @project = @project_member.project + + @target_url = namespace_project_url(@project.namespace, @project) + @current_user = @project_member.created_by + + mail(to: @project_member.created_by.notification_email, + subject: subject("Invitation accepted")) + end + + def project_invite_declined_email(project_id, invite_email, access_level, created_by_id) + return if created_by_id.nil? + + @project = Project.find(project_id) + @current_user = @created_by = User.find(created_by_id) + @access_level = access_level + @invite_email = invite_email + + @target_url = namespace_project_url(@project.namespace, @project) + + mail(to: @created_by.notification_email, + subject: subject("Invitation declined")) + end + def project_was_moved_email(project_id, user_id, old_path_with_namespace) @current_user = @user = User.find user_id @project = Project.find project_id diff --git a/app/mailers/notify.rb b/app/mailers/notify.rb index 0cc709f68e4..1c663bdd521 100644 --- a/app/mailers/notify.rb +++ b/app/mailers/notify.rb @@ -6,15 +6,13 @@ class Notify < BaseMailer include Emails::Notes include Emails::Projects include Emails::Profile + include Emails::Groups include Emails::Builds - include Emails::Members add_template_helper MergeRequestsHelper add_template_helper DiffHelper add_template_helper BlobHelper add_template_helper EmailsHelper - add_template_helper MembersHelper - add_template_helper GitlabRoutingHelper def test_email(recipient_email, subject, body) mail(to: recipient_email, diff --git a/app/models/ability.rb b/app/models/ability.rb index 9c58b956007..44515550d9e 100644 --- a/app/models/ability.rb +++ b/app/models/ability.rb @@ -9,6 +9,7 @@ class Ability when CommitStatus then commit_status_abilities(user, subject) when Project then project_abilities(user, subject) when Issue then issue_abilities(user, subject) + when ExternalIssue then external_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) @@ -18,7 +19,6 @@ class Ability when GroupMember then group_member_abilities(user, subject) when ProjectMember then project_member_abilities(user, subject) when User then user_abilities - when ExternalIssue, Deployment, Environment then project_abilities(user, subject.project) else [] end.concat(global_abilities(user)) end @@ -187,8 +187,6 @@ class Ability project_report_rules elsif team.guest?(user) project_guest_rules - else - [] end end @@ -230,8 +228,6 @@ class Ability :read_build, :read_container_image, :read_pipeline, - :read_environment, - :read_deployment ] end @@ -250,8 +246,6 @@ class Ability :push_code, :create_container_image, :update_container_image, - :create_environment, - :create_deployment ] end @@ -269,8 +263,6 @@ class Ability @project_master_rules ||= project_dev_rules + [ :push_code_to_protected_branches, :update_project_snippet, - :update_environment, - :update_deployment, :admin_milestone, :admin_project_snippet, :admin_project_member, @@ -281,9 +273,7 @@ class Ability :admin_commit_status, :admin_build, :admin_container_image, - :admin_pipeline, - :admin_environment, - :admin_deployment + :admin_pipeline ] end @@ -327,8 +317,6 @@ class Ability unless project.builds_enabled rules += named_abilities('build') rules += named_abilities('pipeline') - rules += named_abilities('environment') - rules += named_abilities('deployment') end unless project.container_registry_enabled @@ -523,6 +511,10 @@ class Ability end end + def external_issue_abilities(user, subject) + project_abilities(user, subject.project) + end + private def restricted_public_level? @@ -541,7 +533,7 @@ class Ability def filter_confidential_issues_abilities(user, issue, rules) return rules if user.admin? || !issue.confidential? - unless issue.author == user || issue.assignee == user || issue.project.team.member?(user, Gitlab::Access::REPORTER) + unless issue.author == user || issue.assignee == user || issue.project.team.member?(user.id) rules.delete(:admin_issue) rules.delete(:read_issue) rules.delete(:update_issue) diff --git a/app/models/blob.rb b/app/models/blob.rb index 4279ea2ce57..0fea6b7f576 100644 --- a/app/models/blob.rb +++ b/app/models/blob.rb @@ -24,7 +24,7 @@ class Blob < SimpleDelegator end def only_display_raw? - size && truncated? + size && size > 5.megabytes end def svg? diff --git a/app/models/ci/build.rb b/app/models/ci/build.rb index 764d8e4e136..6a64ca451f7 100644 --- a/app/models/ci/build.rb +++ b/app/models/ci/build.rb @@ -11,8 +11,6 @@ module Ci scope :unstarted, ->() { where(runner_id: nil) } scope :ignore_failures, ->() { where(allow_failure: false) } - scope :with_artifacts, ->() { where.not(artifacts_file: nil) } - scope :with_expired_artifacts, ->() { with_artifacts.where('artifacts_expire_at < ?', Time.now) } mount_uploader :artifacts_file, ArtifactUploader mount_uploader :artifacts_metadata, ArtifactUploader @@ -40,7 +38,7 @@ module Ci new_build.save end - def retry(build, user = nil) + def retry(build) new_build = Ci::Build.new(status: 'pending') new_build.ref = build.ref new_build.tag = build.tag @@ -54,7 +52,6 @@ module Ci new_build.stage = build.stage new_build.stage_idx = build.stage_idx new_build.trigger_request = build.trigger_request - new_build.user = user new_build.save MergeRequests::AddTodoWhenBuildFailsService.new(build.project, nil).close(new_build) new_build @@ -76,17 +73,6 @@ module Ci build.update_coverage build.execute_hooks end - - after_transition any => [:success] do |build| - if build.environment.present? - service = CreateDeploymentService.new(build.project, build.user, - environment: build.environment, - sha: build.sha, - ref: build.ref, - tag: build.tag) - service.execute(build) - end - end end def retryable? @@ -97,6 +83,10 @@ module Ci !self.pipeline.statuses.latest.include?(self) end + def retry + Ci::Build.retry(self) + end + def depends_on_builds # Get builds of the same type latest_builds = self.pipeline.builds.latest @@ -327,7 +317,7 @@ module Ci end def artifacts? - !artifacts_expired? && artifacts_file.exists? + artifacts_file.exists? end def artifacts_metadata? @@ -338,15 +328,11 @@ module Ci Gitlab::Ci::Build::Artifacts::Metadata.new(artifacts_metadata.path, path, **options).to_entry end - def erase_artifacts! - remove_artifacts_file! - remove_artifacts_metadata! - end - def erase(opts = {}) return false unless erasable? - erase_artifacts! + remove_artifacts_file! + remove_artifacts_metadata! erase_trace! update_erased!(opts[:erased_by]) end @@ -359,25 +345,6 @@ module Ci !self.erased_at.nil? end - def artifacts_expired? - artifacts_expire_at && artifacts_expire_at < Time.now - end - - def artifacts_expire_in - artifacts_expire_at - Time.now if artifacts_expire_at - end - - def artifacts_expire_in=(value) - self.artifacts_expire_at = - if value - Time.now + ChronicDuration.parse(value) - end - end - - def keep_artifacts! - self.update(artifacts_expire_at: nil) - end - private def erase_trace! @@ -385,7 +352,7 @@ module Ci end def update_erased!(user = nil) - self.update(erased_by: user, erased_at: Time.now, artifacts_expire_at: nil) + self.update(erased_by: user, erased_at: Time.now) end def yaml_variables diff --git a/app/models/ci/pipeline.rb b/app/models/ci/pipeline.rb index 13be5b0fa5d..d780467034e 100644 --- a/app/models/ci/pipeline.rb +++ b/app/models/ci/pipeline.rb @@ -76,10 +76,8 @@ module Ci builds.running_or_pending.each(&:cancel) end - def retry_failed(user) - builds.latest.failed.select(&:retryable?).each do |build| - Ci::Build.retry(build, user) - end + def retry_failed + builds.latest.failed.select(&:retryable?).each(&:retry) end def latest? @@ -163,10 +161,6 @@ module Ci git_commit_message =~ /(\[ci skip\])/ if git_commit_message end - def environments - builds.where.not(environment: nil).success.pluck(:environment).uniq - end - def notes Note.for_commit_id(sha) end diff --git a/app/models/concerns/access_requestable.rb b/app/models/concerns/access_requestable.rb deleted file mode 100644 index eedd32a729f..00000000000 --- a/app/models/concerns/access_requestable.rb +++ /dev/null @@ -1,16 +0,0 @@ -# == AccessRequestable concern -# -# Contains functionality related to objects that can receive request for access. -# -# Used by Project, and Group. -# -module AccessRequestable - extend ActiveSupport::Concern - - def request_access(user) - members.create( - access_level: Gitlab::Access::DEVELOPER, - user: user, - requested_at: Time.now.utc) - end -end diff --git a/app/models/concerns/awardable.rb b/app/models/concerns/awardable.rb index 539c7c31e30..aa4b4201250 100644 --- a/app/models/concerns/awardable.rb +++ b/app/models/concerns/awardable.rb @@ -5,7 +5,7 @@ module Awardable has_many :award_emoji, as: :awardable, dependent: :destroy if self < Participable - participant :award_emoji_with_associations + participant :award_emoji end end @@ -34,12 +34,8 @@ module Awardable end end - def award_emoji_with_associations - award_emoji.includes(:user) - end - def grouped_awards(with_thumbs: true) - awards = award_emoji_with_associations.group_by(&:name) + awards = award_emoji.group_by(&:name) if with_thumbs awards[AwardEmoji::UPVOTE_NAME] ||= [] diff --git a/app/models/deployment.rb b/app/models/deployment.rb deleted file mode 100644 index e498ca96e3c..00000000000 --- a/app/models/deployment.rb +++ /dev/null @@ -1,29 +0,0 @@ -class Deployment < ActiveRecord::Base - include InternalId - - belongs_to :project, required: true, validate: true - belongs_to :environment, required: true, validate: true - belongs_to :user - belongs_to :deployable, polymorphic: true - - validates :sha, presence: true - validates :ref, presence: true - - delegate :name, to: :environment, prefix: true - - def commit - project.commit(sha) - end - - def commit_title - commit.try(:title) - end - - def short_sha - Commit.truncate_sha(sha) - end - - def last? - self == environment.last_deployment - end -end diff --git a/app/models/environment.rb b/app/models/environment.rb deleted file mode 100644 index ac3a571a1f3..00000000000 --- a/app/models/environment.rb +++ /dev/null @@ -1,16 +0,0 @@ -class Environment < ActiveRecord::Base - belongs_to :project, required: true, validate: true - - has_many :deployments - - validates :name, - presence: true, - uniqueness: { scope: :project_id }, - length: { within: 0..255 }, - format: { with: Gitlab::Regex.environment_name_regex, - message: Gitlab::Regex.environment_name_regex_message } - - def last_deployment - deployments.last - end -end diff --git a/app/models/group.rb b/app/models/group.rb index e66e04371b2..aec92e335e6 100644 --- a/app/models/group.rb +++ b/app/models/group.rb @@ -3,18 +3,11 @@ require 'carrierwave/orm/activerecord' class Group < Namespace include Gitlab::ConfigHelper include Gitlab::VisibilityLevel - include AccessRequestable include Referable has_many :group_members, dependent: :destroy, as: :source, class_name: 'GroupMember' alias_method :members, :group_members - has_many :users, -> { where(members: { requested_at: nil }) }, through: :group_members - - has_many :owners, - -> { where(members: { access_level: Gitlab::Access::OWNER }) }, - through: :group_members, - source: :user - + has_many :users, through: :group_members has_many :project_group_links, dependent: :destroy has_many :shared_projects, through: :project_group_links, source: :project has_many :notification_settings, dependent: :destroy, as: :source @@ -65,10 +58,6 @@ class Group < Namespace "#{self.class.reference_prefix}#{name}" end - def web_url - Gitlab::Routing.url_helpers.group_url(self) - end - def human_name name end @@ -94,6 +83,10 @@ class Group < Namespace end end + def owners + @owners ||= group_members.owners.includes(:user).map(&:user) + end + def add_users(user_ids, access_level, current_user = nil) user_ids.each do |user_id| Member.add_user(self.group_members, user_id, access_level, current_user) diff --git a/app/models/issue.rb b/app/models/issue.rb index 1bdf9c011b2..235922710ad 100644 --- a/app/models/issue.rb +++ b/app/models/issue.rb @@ -51,18 +51,10 @@ class Issue < ActiveRecord::Base end def self.visible_to_user(user) - return where('issues.confidential IS NULL OR issues.confidential IS FALSE') if user.blank? + return where(confidential: false) if user.blank? return all if user.admin? - where(' - issues.confidential IS NULL - OR issues.confidential IS FALSE - OR (issues.confidential = TRUE - AND (issues.author_id = :user_id - OR issues.assignee_id = :user_id - OR issues.project_id IN(:project_ids)))', - user_id: user.id, - project_ids: user.authorized_projects(Gitlab::Access::REPORTER).select(:id)) + where('issues.confidential = false OR (issues.confidential = true AND (issues.author_id = :user_id OR issues.assignee_id = :user_id OR issues.project_id IN(:project_ids)))', user_id: user.id, project_ids: user.authorized_projects.select(:id)) end def self.reference_prefix diff --git a/app/models/member.rb b/app/models/member.rb index cea6d259760..d3060f07fc0 100644 --- a/app/models/member.rb +++ b/app/models/member.rb @@ -26,28 +26,20 @@ class Member < ActiveRecord::Base allow_nil: true } - scope :invite, -> { where.not(invite_token: nil) } - scope :non_invite, -> { where(invite_token: nil) } - scope :request, -> { where.not(requested_at: nil) } - scope :non_request, -> { where(requested_at: nil) } - scope :non_pending, -> { non_request.non_invite } - + scope :invite, -> { where(user_id: nil) } + scope :non_invite, -> { where("user_id IS NOT NULL") } scope :guests, -> { where(access_level: GUEST) } scope :reporters, -> { where(access_level: REPORTER) } scope :developers, -> { where(access_level: DEVELOPER) } scope :masters, -> { where(access_level: MASTER) } scope :owners, -> { where(access_level: OWNER) } - scope :owners_and_masters, -> { where(access_level: [OWNER, MASTER]) } before_validation :generate_invite_token, on: :create, if: -> (member) { member.invite_email.present? } - after_create :send_invite, if: :invite? - after_create :send_request, if: :request? - after_create :create_notification_setting, unless: :pending? - after_create :post_create_hook, unless: :pending? - after_update :post_update_hook, unless: :pending? - after_destroy :post_destroy_hook, unless: :pending? - after_destroy :post_decline_request, if: :request? + after_create :create_notification_setting, unless: :invite? + after_create :post_create_hook, unless: :invite? + after_update :post_update_hook, unless: :invite? + after_destroy :post_destroy_hook, unless: :invite? delegate :name, :username, :email, to: :user, prefix: true @@ -104,31 +96,10 @@ class Member < ActiveRecord::Base end end - def real_source_type - source_type - end - def invite? self.invite_token.present? end - def request? - requested_at.present? - end - - def pending? - invite? || request? - end - - def accept_request - return false unless request? - - updated = self.update(requested_at: nil) - after_accept_request if updated - - updated - end - def accept_invite!(new_user) return false unless invite? @@ -186,10 +157,6 @@ class Member < ActiveRecord::Base # override in subclass end - def send_request - # override in subclass - end - def post_create_hook system_hook_service.execute_hooks_for(self, :create) end @@ -210,14 +177,6 @@ class Member < ActiveRecord::Base # override in subclass end - def after_accept_request - post_create_hook - end - - def post_decline_request - # override in subclass - end - def system_hook_service SystemHooksService.new end diff --git a/app/models/members/group_member.rb b/app/models/members/group_member.rb index 363db877968..f63a0debf1a 100644 --- a/app/models/members/group_member.rb +++ b/app/models/members/group_member.rb @@ -8,6 +8,9 @@ class GroupMember < Member validates_format_of :source_type, with: /\ANamespace\z/ default_scope { where(source_type: SOURCE_TYPE) } + scope :with_group, ->(group) { where(source_id: group.id) } + scope :with_user, ->(user) { where(user_id: user.id) } + def self.access_level_roles Gitlab::Access.options_with_owner end @@ -20,11 +23,6 @@ class GroupMember < Member access_level end - # Because source_type is `Namespace`... - def real_source_type - 'Group' - end - private def send_invite @@ -33,12 +31,6 @@ class GroupMember < Member super end - def send_request - notification_service.new_group_access_request(self) - - super - end - def post_create_hook notification_service.new_group_member(self) @@ -64,10 +56,4 @@ class GroupMember < Member super end - - def post_decline_request - notification_service.decline_group_access_request(self) - - super - end end diff --git a/app/models/members/project_member.rb b/app/models/members/project_member.rb index 250ee04fd1d..46955b430f3 100644 --- a/app/models/members/project_member.rb +++ b/app/models/members/project_member.rb @@ -11,6 +11,8 @@ class ProjectMember < Member default_scope { where(source_type: SOURCE_TYPE) } scope :in_project, ->(project) { where(source_id: project.id) } + scope :in_projects, ->(projects) { where(source_id: projects.pluck(:id)) } + scope :with_user, ->(user) { where(user_id: user.id) } before_destroy :delete_member_todos @@ -82,7 +84,7 @@ class ProjectMember < Member Gitlab::Access.sym_options end - def access_level_roles + def access_roles Gitlab::Access.options end end @@ -111,12 +113,6 @@ class ProjectMember < Member super end - def send_request - notification_service.new_project_access_request(self) - - super - end - def post_create_hook unless owner? event_service.join_project(self.project, self.user) @@ -152,12 +148,6 @@ class ProjectMember < Member super end - def post_decline_request - notification_service.decline_project_access_request(self) - - super - end - def event_service EventCreateService.new end diff --git a/app/models/note.rb b/app/models/note.rb index 4b6748053ff..585d8c4ad84 100644 --- a/app/models/note.rb +++ b/app/models/note.rb @@ -88,9 +88,22 @@ class Note < ActiveRecord::Base table = arel_table pattern = "%#{query}%" - Note.joins('LEFT JOIN issues ON issues.id = noteable_id'). - where(table[:note].matches(pattern)). - merge(Issue.visible_to_user(as_user)) + found_notes = joins('LEFT JOIN issues ON issues.id = noteable_id'). + where(table[:note].matches(pattern)) + + if as_user + found_notes.where(' + issues.confidential IS NULL + OR issues.confidential IS FALSE + OR (issues.confidential IS TRUE + AND (issues.author_id = :user_id + OR issues.assignee_id = :user_id + OR issues.project_id IN(:project_ids)))', + user_id: as_user.id, + project_ids: as_user.authorized_projects.select(:id)) + else + found_notes.where('issues.confidential IS NULL OR issues.confidential IS FALSE') + end end end @@ -187,10 +200,6 @@ class Note < ActiveRecord::Base award_emoji_supported? && contains_emoji_only? end - def emoji_awardable? - !system? - end - def clear_blank_line_code! self.line_code = nil if self.line_code.blank? end diff --git a/app/models/notification_setting.rb b/app/models/notification_setting.rb index 0ce87968e46..17fb15b08df 100644 --- a/app/models/notification_setting.rb +++ b/app/models/notification_setting.rb @@ -7,6 +7,7 @@ class NotificationSetting < ActiveRecord::Base belongs_to :source, polymorphic: true validates :user, presence: true + validates :source, presence: true validates :level, presence: true validates :user_id, uniqueness: { scope: [:source_type, :source_id], message: "already exists in source", diff --git a/app/models/project.rb b/app/models/project.rb index 0ede6c6b139..9dca63f124b 100644 --- a/app/models/project.rb +++ b/app/models/project.rb @@ -5,7 +5,6 @@ class Project < ActiveRecord::Base include Gitlab::ShellAdapter include Gitlab::VisibilityLevel include Gitlab::CurrentSettings - include AccessRequestable include Referable include Sortable include AfterCommitQueue @@ -81,7 +80,7 @@ class Project < ActiveRecord::Base has_one :jira_service, dependent: :destroy has_one :redmine_service, dependent: :destroy has_one :custom_issue_tracker_service, dependent: :destroy - has_one :gitlab_issue_tracker_service, dependent: :destroy, inverse_of: :project + has_one :gitlab_issue_tracker_service, dependent: :destroy has_one :external_wiki_service, dependent: :destroy has_one :forked_project_link, dependent: :destroy, foreign_key: "forked_to_project_id" @@ -103,9 +102,8 @@ class Project < ActiveRecord::Base has_many :snippets, dependent: :destroy, class_name: 'ProjectSnippet' has_many :hooks, dependent: :destroy, class_name: 'ProjectHook' has_many :protected_branches, dependent: :destroy - has_many :project_members, dependent: :destroy, as: :source, class_name: 'ProjectMember' - alias_method :members, :project_members - has_many :users, -> { where(members: { requested_at: nil }) }, through: :project_members + has_many :project_members, dependent: :destroy, as: :source, class_name: 'ProjectMember' + has_many :users, through: :project_members has_many :deploy_keys_projects, dependent: :destroy has_many :deploy_keys, through: :deploy_keys_projects has_many :users_star_projects, dependent: :destroy @@ -127,8 +125,6 @@ class Project < ActiveRecord::Base has_many :runners, through: :runner_projects, source: :runner, class_name: 'Ci::Runner' has_many :variables, dependent: :destroy, class_name: 'Ci::Variable', foreign_key: :gl_project_id has_many :triggers, dependent: :destroy, class_name: 'Ci::Trigger', foreign_key: :gl_project_id - has_many :environments, dependent: :destroy - has_many :deployments, dependent: :destroy accepts_nested_attributes_for :variables, allow_destroy: true @@ -150,6 +146,7 @@ class Project < ActiveRecord::Base message: Gitlab::Regex.project_path_regex_message } validates :issues_enabled, :merge_requests_enabled, :wiki_enabled, inclusion: { in: [true, false] } + validates :issues_tracker_id, length: { maximum: 255 }, allow_blank: true validates :namespace, presence: true validates_uniqueness_of :name, scope: :namespace_id validates_uniqueness_of :path, scope: :namespace_id @@ -592,6 +589,10 @@ class Project < ActiveRecord::Base update_column(:has_external_issue_tracker, services.external_issue_trackers.any?) end + def can_have_issues_tracker_id? + self.issues_enabled && !self.default_issues_tracker? + end + def build_missing_services services_templates = Service.where(template: true) @@ -684,6 +685,16 @@ class Project < ActiveRecord::Base end end + def project_member_by_name_or_email(name = nil, email = nil) + user = users.find_by('name like ? or email like ?', name, email) + project_members.where(user: user) if user + end + + # Get Team Member record by user id + def project_member_by_id(user_id) + project_members.find_by(user_id: user_id) + end + def name_with_namespace @name_with_namespace ||= begin if namespace @@ -693,7 +704,6 @@ class Project < ActiveRecord::Base end end end - alias_method :human_name, :name_with_namespace def path_with_namespace if namespace diff --git a/app/models/project_services/bamboo_service.rb b/app/models/project_services/bamboo_service.rb index b5c76e4d4fe..1d1780dcfbf 100644 --- a/app/models/project_services/bamboo_service.rb +++ b/app/models/project_services/bamboo_service.rb @@ -1,4 +1,6 @@ class BambooService < CiService + include HTTParty + prop_accessor :bamboo_url, :build_key, :username, :password validates :bamboo_url, presence: true, url: true, if: :activated? @@ -59,7 +61,18 @@ class BambooService < CiService end def build_info(sha) - @response = get_path("rest/api/latest/result?label=#{sha}") + url = URI.join(bamboo_url, "/rest/api/latest/result?label=#{sha}").to_s + + if username.blank? && password.blank? + @response = HTTParty.get(url, verify: false) + else + url << '&os_authType=basic' + auth = { + username: username, + password: password + } + @response = HTTParty.get(url, verify: false, basic_auth: auth) + end end def build_page(sha, ref) @@ -67,11 +80,11 @@ class BambooService < CiService if @response.code != 200 || @response['results']['results']['size'] == '0' # If actual build link can't be determined, send user to build summary page. - URI.join("#{bamboo_url}/", "browse/#{build_key}").to_s + URI.join(bamboo_url, "/browse/#{build_key}").to_s else # If actual build link is available, go to build result page. result_key = @response['results']['results']['result']['planResultKey']['key'] - URI.join("#{bamboo_url}/", "browse/#{result_key}").to_s + URI.join(bamboo_url, "/browse/#{result_key}").to_s end end @@ -99,27 +112,8 @@ class BambooService < CiService def execute(data) return unless supported_events.include?(data[:object_kind]) - get_path("updateAndBuild.action?buildKey=#{build_key}") - end - - private - - def build_url(path) - URI.join("#{bamboo_url}/", path).to_s - end - - def get_path(path) - url = build_url(path) - - if username.blank? && password.blank? - HTTParty.get(url, verify: false) - else - url << '&os_authType=basic' - HTTParty.get(url, verify: false, - basic_auth: { - username: username, - password: password - }) - end + # Bamboo requires a GET and does not take any data. + url = URI.join(bamboo_url, "/updateAndBuild.action?buildKey=#{build_key}").to_s + self.class.get(url, verify: false) end end diff --git a/app/models/project_services/issue_tracker_service.rb b/app/models/project_services/issue_tracker_service.rb index 87ecb3b8b86..6ae9b16d3ce 100644 --- a/app/models/project_services/issue_tracker_service.rb +++ b/app/models/project_services/issue_tracker_service.rb @@ -38,9 +38,9 @@ class IssueTrackerService < Service if enabled_in_gitlab_config self.properties = { title: issues_tracker['title'], - project_url: issues_tracker['project_url'], - issues_url: issues_tracker['issues_url'], - new_issue_url: issues_tracker['new_issue_url'] + project_url: add_issues_tracker_id(issues_tracker['project_url']), + issues_url: add_issues_tracker_id(issues_tracker['issues_url']), + new_issue_url: add_issues_tracker_id(issues_tracker['new_issue_url']) } else self.properties = {} @@ -83,4 +83,16 @@ class IssueTrackerService < Service def issues_tracker Gitlab.config.issues_tracker[to_param] end + + def add_issues_tracker_id(url) + if self.project + id = self.project.issues_tracker_id + + if id + url = url.gsub(":issues_tracker_id", id) + end + end + + url + end end diff --git a/app/models/project_services/teamcity_service.rb b/app/models/project_services/teamcity_service.rb index a4a967c9bc9..b0dcb52eba1 100644 --- a/app/models/project_services/teamcity_service.rb +++ b/app/models/project_services/teamcity_service.rb @@ -1,4 +1,6 @@ class TeamcityService < CiService + include HTTParty + prop_accessor :teamcity_url, :build_type, :username, :password validates :teamcity_url, presence: true, url: true, if: :activated? @@ -62,7 +64,15 @@ class TeamcityService < CiService end def build_info(sha) - @response = get_path("httpAuth/app/rest/builds/branch:unspecified:any,number:#{sha}") + url = URI.join( + teamcity_url, + "/httpAuth/app/rest/builds/branch:unspecified:any,number:#{sha}" + ).to_s + auth = { + username: username, + password: password + } + @response = HTTParty.get(url, verify: false, basic_auth: auth) end def build_page(sha, ref) @@ -71,11 +81,14 @@ class TeamcityService < CiService if @response.code != 200 # If actual build link can't be determined, # send user to build summary page. - build_url("viewLog.html?buildTypeId=#{build_type}") + URI.join(teamcity_url, "/viewLog.html?buildTypeId=#{build_type}").to_s else # If actual build link is available, go to build result page. built_id = @response['build']['id'] - build_url("viewLog.html?buildId=#{built_id}&buildTypeId=#{build_type}") + URI.join( + teamcity_url, + "/viewLog.html?buildId=#{built_id}&buildTypeId=#{build_type}" + ).to_s end end @@ -110,8 +123,8 @@ class TeamcityService < CiService branch = Gitlab::Git.ref_name(data[:ref]) - HTTParty.post( - build_url('httpAuth/app/rest/buildQueue'), + self.class.post( + URI.join(teamcity_url, '/httpAuth/app/rest/buildQueue').to_s, body: "<build branchName=\"#{branch}\">"\ "<buildType id=\"#{build_type}\"/>"\ '</build>', @@ -119,18 +132,4 @@ class TeamcityService < CiService basic_auth: auth ) end - - private - - def build_url(path) - URI.join("#{teamcity_url}/", path).to_s - end - - def get_path(path) - HTTParty.get(build_url(path), verify: false, - basic_auth: { - username: username, - password: password - }) - end end diff --git a/app/models/project_team.rb b/app/models/project_team.rb index 73e736820af..70a8bbaba65 100644 --- a/app/models/project_team.rb +++ b/app/models/project_team.rb @@ -21,13 +21,23 @@ class ProjectTeam end end + def find(user_id) + user = project.users.find_by(id: user_id) + + if group + user ||= group.users.find_by(id: user_id) + end + + user + end + def find_member(user_id) - member = project.members.non_request.find_by(user_id: user_id) + member = project.project_members.find_by(user_id: user_id) # If user is not in project members # we should check for group membership if group && !member - member = group.members.non_request.find_by(user_id: user_id) + member = group.group_members.find_by(user_id: user_id) end member @@ -51,10 +61,13 @@ class ProjectTeam ProjectMember.truncate_team(project) end + def users + members + end + def members @members ||= fetch_members end - alias_method :users, :members def guests @guests ||= fetch_members(:guests) @@ -118,14 +131,8 @@ class ProjectTeam max_member_access(user.id) == Gitlab::Access::MASTER end - def member?(user, min_member_access = nil) - member = !!find_member(user.id) - - if min_member_access - member && max_member_access(user.id) >= min_member_access - else - member - end + def member?(user_id) + !!find_member(user_id) end def human_max_access(user_id) @@ -137,7 +144,7 @@ class ProjectTeam def max_member_access(user_id) access = [] - project.members.non_request.each do |member| + project.project_members.each do |member| if member.user_id == user_id access << member.access_field if member.access_field break @@ -145,7 +152,7 @@ class ProjectTeam end if group - group.members.non_request.each do |member| + group.group_members.each do |member| if member.user_id == user_id access << member.access_field if member.access_field break @@ -160,7 +167,6 @@ class ProjectTeam access.compact.max end - private def max_invited_level(user_id) project.project_group_links.map do |group_link| @@ -177,15 +183,17 @@ class ProjectTeam end.compact.max end + private + def fetch_members(level = nil) - project_members = project.members.non_request - group_members = group ? group.members.non_request : [] + project_members = project.project_members + group_members = group ? group.group_members : [] invited_members = [] if project.invited_groups.any? && project.allowed_to_share_with_group? project.project_group_links.each do |group_link| invited_group = group_link.group - im = invited_group.members.non_request + im = invited_group.group_members if level int_level = GroupMember.access_level_roles[level.to_s.singularize.titleize] diff --git a/app/models/repository.rb b/app/models/repository.rb index e5b277cb198..1ab163510bf 100644 --- a/app/models/repository.rb +++ b/app/models/repository.rb @@ -446,7 +446,7 @@ class Repository def blob_at(sha, path) unless Gitlab::Git.blank_ref?(sha) - Blob.decorate(Gitlab::Git::Blob.find(self, sha, path)) + Gitlab::Git::Blob.find(self, sha, path) end end diff --git a/app/models/service.rb b/app/models/service.rb index 40d39933ad8..bf352397509 100644 --- a/app/models/service.rb +++ b/app/models/service.rb @@ -18,7 +18,7 @@ class Service < ActiveRecord::Base after_commit :reset_updated_properties after_commit :cache_project_has_external_issue_tracker - belongs_to :project, inverse_of: :services + belongs_to :project has_one :service_hook validates :project_id, presence: true, unless: Proc.new { |service| service.template? } diff --git a/app/models/todo.rb b/app/models/todo.rb index 2792fa9b9a8..3a091373329 100644 --- a/app/models/todo.rb +++ b/app/models/todo.rb @@ -2,7 +2,6 @@ class Todo < ActiveRecord::Base ASSIGNED = 1 MENTIONED = 2 BUILD_FAILED = 3 - MARKED = 4 belongs_to :author, class_name: "User" belongs_to :note diff --git a/app/models/user.rb b/app/models/user.rb index 8d0427da5ab..e0987e07e1f 100644 --- a/app/models/user.rb +++ b/app/models/user.rb @@ -10,8 +10,6 @@ class User < ActiveRecord::Base include CaseSensitivity include TokenAuthenticatable - DEFAULT_NOTIFICATION_LEVEL = :participating - add_authentication_token_field :authentication_token default_value_for :admin, false @@ -56,7 +54,8 @@ class User < ActiveRecord::Base # Groups has_many :members, dependent: :destroy - has_many :group_members, dependent: :destroy, source: 'GroupMember' + has_many :project_members, source: 'ProjectMember' + has_many :group_members, source: 'GroupMember' has_many :groups, through: :group_members has_many :owned_groups, -> { where members: { access_level: Gitlab::Access::OWNER } }, through: :group_members, source: :group has_many :masters_groups, -> { where members: { access_level: Gitlab::Access::MASTER } }, through: :group_members, source: :group @@ -64,13 +63,13 @@ class User < ActiveRecord::Base # Projects has_many :groups_projects, through: :groups, source: :projects has_many :personal_projects, through: :namespace, source: :projects - has_many :project_members, dependent: :destroy, class_name: 'ProjectMember' has_many :projects, through: :project_members has_many :created_projects, foreign_key: :creator_id, class_name: 'Project' has_many :users_star_projects, dependent: :destroy has_many :starred_projects, through: :users_star_projects, source: :project has_many :snippets, dependent: :destroy, foreign_key: :author_id, class_name: "Snippet" + has_many :project_members, dependent: :destroy, class_name: 'ProjectMember' has_many :issues, dependent: :destroy, foreign_key: :author_id has_many :notes, dependent: :destroy, foreign_key: :author_id has_many :merge_requests, dependent: :destroy, foreign_key: :author_id @@ -100,6 +99,7 @@ class User < ActiveRecord::Base presence: true, uniqueness: { case_sensitive: false } + validates :notification_level, presence: true validate :namespace_uniq, if: ->(user) { user.username_changed? } validate :avatar_type, if: ->(user) { user.avatar.present? && user.avatar_changed? } validate :unique_email, if: ->(user) { user.email_changed? } @@ -133,6 +133,13 @@ class User < ActiveRecord::Base # Note: When adding an option, it MUST go on the end of the array. enum project_view: [:readme, :activity, :files] + # Notification level + # Note: When adding an option, it MUST go on the end of the array. + # + # TODO: Add '_prefix: :notification' to enum when update to Rails 5. https://github.com/rails/rails/pull/19813 + # Because user.notification_disabled? is much better than user.disabled? + enum notification_level: [:disabled, :participating, :watch, :global, :mention] + alias_attribute :private_token, :authentication_token delegate :path, to: :namespace, allow_nil: true, prefix: true @@ -404,8 +411,8 @@ class User < ActiveRecord::Base end # Returns projects user is authorized to access. - def authorized_projects(min_access_level = nil) - Project.where("projects.id IN (#{projects_union(min_access_level).to_sql})") + def authorized_projects + Project.where("projects.id IN (#{projects_union.to_sql})") end def viewable_starred_projects @@ -793,17 +800,6 @@ class User < ActiveRecord::Base notification_settings.find_or_initialize_by(source: source) end - # Lazy load global notification setting - # Initializes User setting with Participating level if setting not persisted - def global_notification_setting - return @global_notification_setting if defined?(@global_notification_setting) - - @global_notification_setting = notification_settings.find_or_initialize_by(source: nil) - @global_notification_setting.update_attributes(level: NotificationSetting.levels[DEFAULT_NOTIFICATION_LEVEL]) unless @global_notification_setting.persisted? - - @global_notification_setting - end - def assigned_open_merge_request_count(force: false) Rails.cache.fetch(['users', id, 'assigned_open_merge_request_count'], force: force) do assigned_merge_requests.opened.count @@ -823,19 +819,11 @@ class User < ActiveRecord::Base private - def projects_union(min_access_level = nil) - relations = [personal_projects.select(:id), - groups_projects.select(:id), - projects.select(:id), - groups.joins(:shared_projects).select(:project_id)] - - - if min_access_level - scope = { access_level: Gitlab::Access.values.select { |access| access >= min_access_level } } - relations = [relations.shift] + relations.map { |relation| relation.where(members: scope) } - end - - Gitlab::SQL::Union.new(relations) + def projects_union + Gitlab::SQL::Union.new([personal_projects.select(:id), + groups_projects.select(:id), + projects.select(:id), + groups.joins(:shared_projects).select(:project_id)]) end def ci_projects_union diff --git a/app/services/ci/create_builds_service.rb b/app/services/ci/create_builds_service.rb index 3a74ae094e8..64bcdac5c65 100644 --- a/app/services/ci/create_builds_service.rb +++ b/app/services/ci/create_builds_service.rb @@ -29,8 +29,7 @@ module Ci :options, :allow_failure, :stage, - :stage_idx, - :environment) + :stage_idx) build_attrs.merge!(ref: @pipeline.ref, tag: @pipeline.tag, diff --git a/app/services/ci/register_build_service.rb b/app/services/ci/register_build_service.rb index f0ed09a629a..4ff268a6f06 100644 --- a/app/services/ci/register_build_service.rb +++ b/app/services/ci/register_build_service.rb @@ -7,19 +7,15 @@ module Ci builds = if current_runner.shared? - builds. - # don't run projects which have not enabled shared runners - joins(:project).where(projects: { builds_enabled: true, shared_runners_enabled: true }). - - # this returns builds that are ordered by number of running builds - # we prefer projects that don't use shared runners at all - joins("LEFT JOIN (#{running_builds_for_shared_runners.to_sql}) AS project_builds ON ci_builds.gl_project_id=project_builds.gl_project_id"). - order('COALESCE(project_builds.running_builds, 0) ASC', 'ci_builds.id ASC') + # don't run projects which have not enables shared runners + builds.joins(:project).where(projects: { builds_enabled: true, shared_runners_enabled: true }) else - # do run projects which are only assigned to this runner (FIFO) - builds.where(project: current_runner.projects.where(builds_enabled: true)).order('created_at ASC') + # do run projects which are only assigned to this runner + builds.where(project: current_runner.projects.where(builds_enabled: true)) end + builds = builds.order('created_at ASC') + build = builds.find do |build| build.can_be_served?(current_runner) end @@ -39,12 +35,5 @@ module Ci rescue StateMachines::InvalidTransition nil end - - private - - def running_builds_for_shared_runners - Ci::Build.running.where(runner: Ci::Runner.shared). - group(:gl_project_id).select(:gl_project_id, 'count(*) AS running_builds') - end end end diff --git a/app/services/create_deployment_service.rb b/app/services/create_deployment_service.rb deleted file mode 100644 index efeb9df9527..00000000000 --- a/app/services/create_deployment_service.rb +++ /dev/null @@ -1,18 +0,0 @@ -require_relative 'base_service' - -class CreateDeploymentService < BaseService - def execute(deployable = nil) - environment = project.environments.find_or_create_by( - name: params[:environment] - ) - - project.deployments.create( - environment: environment, - ref: params[:ref], - tag: params[:tag], - sha: params[:sha], - user: current_user, - deployable: deployable - ) - end -end diff --git a/app/services/git_hooks_service.rb b/app/services/git_hooks_service.rb index d7a0c25a044..8f5c3393dfc 100644 --- a/app/services/git_hooks_service.rb +++ b/app/services/git_hooks_service.rb @@ -3,7 +3,7 @@ class GitHooksService def execute(user, repo_path, oldrev, newrev, ref) @repo_path = repo_path - @user = Gitlab::GlId.gl_id(user) + @user = Gitlab::ShellEnv.gl_id(user) @oldrev = oldrev @newrev = newrev @ref = ref diff --git a/app/services/notification_service.rb b/app/services/notification_service.rb index f804ac171c4..91ca82ed3b7 100644 --- a/app/services/notification_service.rb +++ b/app/services/notification_service.rb @@ -173,26 +173,16 @@ class NotificationService end end - # Project access request - def new_project_access_request(project_member) - mailer.member_access_requested_email(project_member.real_source_type, project_member.id).deliver_later - end - - def decline_project_access_request(project_member) - mailer.member_access_denied_email(project_member.real_source_type, project_member.project.id, project_member.user.id).deliver_later - end - def invite_project_member(project_member, token) - mailer.member_invited_email(project_member.real_source_type, project_member.id, token).deliver_later + mailer.project_member_invited_email(project_member.id, token).deliver_later end def accept_project_invite(project_member) - mailer.member_invite_accepted_email(project_member.real_source_type, project_member.id).deliver_later + mailer.project_invite_accepted_email(project_member.id).deliver_later end def decline_project_invite(project_member) - mailer.member_invite_declined_email( - project_member.real_source_type, + mailer.project_invite_declined_email( project_member.project.id, project_member.invite_email, project_member.access_level, @@ -201,33 +191,23 @@ class NotificationService end def new_project_member(project_member) - mailer.member_access_granted_email(project_member.real_source_type, project_member.id).deliver_later + mailer.project_access_granted_email(project_member.id).deliver_later end def update_project_member(project_member) - mailer.member_access_granted_email(project_member.real_source_type, project_member.id).deliver_later - end - - # Group access request - def new_group_access_request(group_member) - mailer.member_access_requested_email(group_member.real_source_type, group_member.id).deliver_later - end - - def decline_group_access_request(group_member) - mailer.member_access_denied_email(group_member.real_source_type, group_member.group.id, group_member.user.id).deliver_later + mailer.project_access_granted_email(project_member.id).deliver_later end def invite_group_member(group_member, token) - mailer.member_invited_email(group_member.real_source_type, group_member.id, token).deliver_later + mailer.group_member_invited_email(group_member.id, token).deliver_later end def accept_group_invite(group_member) - mailer.member_invite_accepted_email(group_member.id).deliver_later + mailer.group_invite_accepted_email(group_member.id).deliver_later end def decline_group_invite(group_member) - mailer.member_invite_declined_email( - group_member.real_source_type, + mailer.group_invite_declined_email( group_member.group.id, group_member.invite_email, group_member.access_level, @@ -236,11 +216,11 @@ class NotificationService end def new_group_member(group_member) - mailer.member_access_granted_email(group_member.real_source_type, group_member.id).deliver_later + mailer.group_access_granted_email(group_member.id).deliver_later end def update_group_member(group_member) - mailer.member_access_granted_email(group_member.real_source_type, group_member.id).deliver_later + mailer.group_access_granted_email(group_member.id).deliver_later end def project_was_moved(project, old_path_with_namespace) @@ -299,11 +279,10 @@ class NotificationService end def users_with_global_level_watch(ids) - NotificationSetting.where( - user_id: ids, - source_type: nil, - level: NotificationSetting.levels[:watch] - ).pluck(:user_id) + User.where( + id: ids, + notification_level: NotificationSetting.levels[:watch] + ).pluck(:id) end # Build a list of users based on project notifcation settings @@ -373,9 +352,7 @@ class NotificationService users = users.reject(&:blocked?) users.reject do |user| - global_notification_setting = user.global_notification_setting - - next global_notification_setting.level == level unless project + next user.notification_level == level unless project setting = user.notification_settings_for(project) @@ -384,13 +361,13 @@ class NotificationService end # reject users who globally set mention notification and has no setting per project/group - next global_notification_setting.level == level unless setting + next user.notification_level == level unless setting # reject users who set mention notification in project next true if setting.level == level # reject users who have mention level in project and disabled in global settings - setting.global? && global_notification_setting.level == level + setting.global? && user.notification_level == level end end @@ -479,6 +456,7 @@ class NotificationService def build_recipients(target, project, current_user, action: nil, previous_assignee: nil) recipients = target.participants(current_user) + recipients = add_project_watchers(recipients, project) recipients = reject_mention_users(recipients, project) diff --git a/app/services/todo_service.rb b/app/services/todo_service.rb index e1f9ea64dc4..8e03ff8ddde 100644 --- a/app/services/todo_service.rb +++ b/app/services/todo_service.rb @@ -139,16 +139,10 @@ class TodoService pending_todos(user, attributes).update_all(state: :done) end - # When user marks an issue as todo - def mark_todo(issuable, current_user) - attributes = attributes_for_todo(issuable.project, issuable, current_user, Todo::MARKED) - create_todos(current_user, attributes) - end - private def create_todos(users, attributes) - Array(users).map do |user| + Array(users).each do |user| next if pending_todos(user, attributes).exists? Todo.create(attributes.merge(user_id: user.id)) end diff --git a/app/views/admin/background_jobs/_head.html.haml b/app/views/admin/background_jobs/_head.html.haml deleted file mode 100644 index d78682532ed..00000000000 --- a/app/views/admin/background_jobs/_head.html.haml +++ /dev/null @@ -1,14 +0,0 @@ -.nav-links.sub-nav - %ul{ class: (container_class) } - = nav_link(controller: :background_jobs) do - = link_to admin_background_jobs_path, title: 'Background Jobs' do - %span - Background Jobs - = nav_link(controller: :logs) do - = link_to admin_logs_path, title: 'Logs' do - %span - Logs - = nav_link(controller: :health_check) do - = link_to admin_health_check_path, title: 'Health Check' do - %span - Health Check diff --git a/app/views/admin/background_jobs/show.html.haml b/app/views/admin/background_jobs/show.html.haml index 654d261aa99..de5bc050cf0 100644 --- a/app/views/admin/background_jobs/show.html.haml +++ b/app/views/admin/background_jobs/show.html.haml @@ -1,50 +1,46 @@ -- @no_container = true - page_title "Background Jobs" -= render 'admin/background_jobs/head' +%h3.page-title Background Jobs +%p.light GitLab uses #{link_to "sidekiq", "http://sidekiq.org/"} library for async job processing -%div{ class: (container_class) } - %h3.page-title Background Jobs - %p.light GitLab uses #{link_to "sidekiq", "http://sidekiq.org/"} library for async job processing +%hr - %hr +.panel.panel-default + .panel-heading Sidekiq running processes + .panel-body + - if @sidekiq_processes.empty? + %h4.cred + %i.fa.fa-exclamation-triangle + There are no running sidekiq processes. Please restart GitLab + - else + .table-holder + %table.table + %thead + %th USER + %th PID + %th CPU + %th MEM + %th STATE + %th START + %th COMMAND + %tbody + - @sidekiq_processes.each do |process| + - next unless process.match(/(sidekiq \d+\.\d+\.\d+.+$)/) + - data = process.strip.split(' ') + %tr + %td= gitlab_config.user + - 5.times do + %td= data.shift + %td= data.join(' ') - .panel.panel-default - .panel-heading Sidekiq running processes - .panel-body - - if @sidekiq_processes.empty? - %h4.cred - %i.fa.fa-exclamation-triangle - There are no running sidekiq processes. Please restart GitLab - - else - .table-holder - %table.table - %thead - %th USER - %th PID - %th CPU - %th MEM - %th STATE - %th START - %th COMMAND - %tbody - - @sidekiq_processes.each do |process| - - next unless process.match(/(sidekiq \d+\.\d+\.\d+.+$)/) - - data = process.strip.split(' ') - %tr - %td= gitlab_config.user - - 5.times do - %td= data.shift - %td= data.join(' ') + .clearfix + %p + %i.fa.fa-exclamation-circle + If '[25 of 25 busy]' is shown, restart GitLab with 'sudo service gitlab reload'. + %p + %i.fa.fa-exclamation-circle + If more than one sidekiq process is listed, stop GitLab, kill the remaining sidekiq processes (sudo pkill -u #{gitlab_config.user} -f sidekiq) and restart GitLab. - .clearfix - %p - %i.fa.fa-exclamation-circle - If '[25 of 25 busy]' is shown, restart GitLab with 'sudo service gitlab reload'. - %p - %i.fa.fa-exclamation-circle - If more than one sidekiq process is listed, stop GitLab, kill the remaining sidekiq processes (sudo pkill -u #{gitlab_config.user} -f sidekiq) and restart GitLab. - - .panel.panel-default - %iframe{src: sidekiq_path, width: '100%', height: 970, style: "border: none"} +.panel.panel-default + %iframe{src: sidekiq_path, width: '100%', height: 970, style: "border: none"} diff --git a/app/views/admin/builds/index.html.haml b/app/views/admin/builds/index.html.haml index efd5b12cfeb..d74cf8598e8 100644 --- a/app/views/admin/builds/index.html.haml +++ b/app/views/admin/builds/index.html.haml @@ -1,54 +1,49 @@ -- @no_container = true -= render "admin/dashboard/head" - -%div{ class: (container_class) } - - .top-area - %ul.nav-links - %li{class: ('active' if @scope.nil?)} - = link_to admin_builds_path do - All - %span.badge.js-totalbuilds-count= @all_builds.count(:id) - - %li{class: ('active' if @scope == 'running')} - = link_to admin_builds_path(scope: :running) do - Running - %span.badge.js-running-count= number_with_delimiter(@all_builds.running_or_pending.count(:id)) - - %li{class: ('active' if @scope == 'finished')} - = link_to admin_builds_path(scope: :finished) do - Finished - %span.badge.js-running-count= number_with_delimiter(@all_builds.finished.count(:id)) - - .nav-controls - - if @all_builds.running_or_pending.any? - = link_to 'Cancel all', cancel_all_admin_builds_path, data: { confirm: 'Are you sure?' }, class: 'btn btn-danger', method: :post - - .row-content-block.second-block - #{(@scope || 'all').capitalize} builds - - %ul.content-list - - if @builds.blank? - %li - .nothing-here-block No builds to show - - else - .table-holder - %table.table.builds - %thead - %tr - %th Status - %th Build ID - %th Project - %th Commit - %th Ref - %th Runner - %th Name - %th Tags - %th Duration - %th Finished at - %th - - - @builds.each do |build| - = render "admin/builds/build", build: build - - = paginate @builds, theme: 'gitlab' +.top-area + %ul.nav-links + %li{class: ('active' if @scope.nil?)} + = link_to admin_builds_path do + All + %span.badge.js-totalbuilds-count= @all_builds.count(:id) + + %li{class: ('active' if @scope == 'running')} + = link_to admin_builds_path(scope: :running) do + Running + %span.badge.js-running-count= number_with_delimiter(@all_builds.running_or_pending.count(:id)) + + %li{class: ('active' if @scope == 'finished')} + = link_to admin_builds_path(scope: :finished) do + Finished + %span.badge.js-running-count= number_with_delimiter(@all_builds.finished.count(:id)) + + .nav-controls + - if @all_builds.running_or_pending.any? + = link_to 'Cancel all', cancel_all_admin_builds_path, data: { confirm: 'Are you sure?' }, class: 'btn btn-danger', method: :post + +.row-content-block.second-block + #{(@scope || 'all').capitalize} builds + +%ul.content-list + - if @builds.blank? + %li + .nothing-here-block No builds to show + - else + .table-holder + %table.table.builds + %thead + %tr + %th Status + %th Build ID + %th Project + %th Commit + %th Ref + %th Runner + %th Name + %th Tags + %th Duration + %th Finished at + %th + + - @builds.each do |build| + = render "admin/builds/build", build: build + + = paginate @builds, theme: 'gitlab' diff --git a/app/views/admin/dashboard/_head.html.haml b/app/views/admin/dashboard/_head.html.haml deleted file mode 100644 index 7b3f88c24df..00000000000 --- a/app/views/admin/dashboard/_head.html.haml +++ /dev/null @@ -1,22 +0,0 @@ -.nav-links.sub-nav - %ul{ class: (container_class) } - = nav_link(controller: :dashboard, html_options: {class: 'home'}) do - = link_to admin_root_path, title: 'Overview' do - %span - Overview - = nav_link(controller: [:admin, :projects]) do - = link_to admin_namespaces_projects_path, title: 'Projects' do - %span - Projects - = nav_link(controller: :users) do - = link_to admin_users_path, title: 'Users' do - %span - Users - = nav_link(controller: :groups) do - = link_to admin_groups_path, title: 'Groups' do - %span - Groups - = nav_link path: 'builds#index' do - = link_to admin_builds_path, title: 'Builds' do - %span - Builds diff --git a/app/views/admin/dashboard/index.html.haml b/app/views/admin/dashboard/index.html.haml index 4682016a886..6dd2fef395d 100644 --- a/app/views/admin/dashboard/index.html.haml +++ b/app/views/admin/dashboard/index.html.haml @@ -1,159 +1,155 @@ -- @no_container = true -= render "admin/dashboard/head" +.admin-dashboard.prepend-top-default + .row + .col-md-4 + %h4 Statistics + %hr + %p + Forks + %span.light.pull-right + = number_with_delimiter(ForkedProjectLink.count) + %p + Issues + %span.light.pull-right + = number_with_delimiter(Issue.count) + %p + Merge Requests + %span.light.pull-right + = number_with_delimiter(MergeRequest.count) + %p + Notes + %span.light.pull-right + = number_with_delimiter(Note.count) + %p + Snippets + %span.light.pull-right + = number_with_delimiter(Snippet.count) + %p + SSH Keys + %span.light.pull-right + = number_with_delimiter(Key.count) + %p + Milestones + %span.light.pull-right + = number_with_delimiter(Milestone.count) + %p + Active Users + %span.light.pull-right + = number_with_delimiter(User.active.count) + .col-md-4 + %h4 + Features + %hr + %p + Sign up + %span.light.pull-right + = boolean_to_icon signup_enabled? + %p + LDAP + %span.light.pull-right + = boolean_to_icon Gitlab.config.ldap.enabled + %p + Gravatar + %span.light.pull-right + = boolean_to_icon gravatar_enabled? + %p + OmniAuth + %span.light.pull-right + = boolean_to_icon Gitlab.config.omniauth.enabled + %p + Reply by email + %span.light.pull-right + = boolean_to_icon Gitlab::IncomingEmail.enabled? + .col-md-4 + %h4 + Components + - if current_application_settings.version_check_enabled + .pull-right + = version_status_badge -%div{ class: (container_class) } - .admin-dashboard.prepend-top-default - .row - .col-md-4 - %h4 Statistics - %hr - %p - Forks - %span.light.pull-right - = number_with_delimiter(ForkedProjectLink.count) - %p - Issues - %span.light.pull-right - = number_with_delimiter(Issue.count) - %p - Merge Requests - %span.light.pull-right - = number_with_delimiter(MergeRequest.count) - %p - Notes - %span.light.pull-right - = number_with_delimiter(Note.count) - %p - Snippets - %span.light.pull-right - = number_with_delimiter(Snippet.count) - %p - SSH Keys - %span.light.pull-right - = number_with_delimiter(Key.count) - %p - Milestones - %span.light.pull-right - = number_with_delimiter(Milestone.count) - %p - Active Users - %span.light.pull-right - = number_with_delimiter(User.active.count) - .col-md-4 - %h4 - Features - %hr - %p - Sign up - %span.light.pull-right - = boolean_to_icon signup_enabled? - %p - LDAP - %span.light.pull-right - = boolean_to_icon Gitlab.config.ldap.enabled - %p - Gravatar - %span.light.pull-right - = boolean_to_icon gravatar_enabled? - %p - OmniAuth - %span.light.pull-right - = boolean_to_icon Gitlab.config.omniauth.enabled - %p - Reply by email - %span.light.pull-right - = boolean_to_icon Gitlab::IncomingEmail.enabled? - .col-md-4 - %h4 - Components - - if current_application_settings.version_check_enabled - .pull-right - = version_status_badge + %hr + %p + GitLab + %span.pull-right + = Gitlab::VERSION + %p + GitLab Shell + %span.pull-right + = Gitlab::Shell.new.version + %p + GitLab API + %span.pull-right + = API::API::version + %p + Git + %span.pull-right + = Gitlab::Git.version + %p + Ruby + %span.pull-right + #{RUBY_VERSION}p#{RUBY_PATCHLEVEL} - %hr - %p - GitLab - %span.pull-right - = Gitlab::VERSION - %p - GitLab Shell - %span.pull-right - = Gitlab::Shell.new.version - %p - GitLab API - %span.pull-right - = API::API::version - %p - Git - %span.pull-right - = Gitlab::Git.version - %p - Ruby - %span.pull-right - #{RUBY_VERSION}p#{RUBY_PATCHLEVEL} + %p + Rails + %span.pull-right + #{Rails::VERSION::STRING} - %p - Rails - %span.pull-right - #{Rails::VERSION::STRING} + %p + = Gitlab::Database.adapter_name + %span.pull-right + = Gitlab::Database.version + %hr + .row + .col-sm-4 + .light-well + %h4 Projects + .data + = link_to admin_namespaces_projects_path do + %h1= number_with_delimiter(Project.count) + %hr + = link_to('New Project', new_project_path, class: "btn btn-new") + .col-sm-4 + .light-well + %h4 Users + .data + = link_to admin_users_path do + %h1= number_with_delimiter(User.count) + %hr + = link_to 'New User', new_admin_user_path, class: "btn btn-new" + .col-sm-4 + .light-well + %h4 Groups + .data + = link_to admin_groups_path do + %h1= number_with_delimiter(Group.count) + %hr + = link_to 'New Group', new_admin_group_path, class: "btn btn-new" + .row.prepend-top-10 + .col-md-4 + %h4 Latest projects + %hr + - @projects.each do |project| %p - = Gitlab::Database.adapter_name - %span.pull-right - = Gitlab::Database.version - %hr - .row - .col-sm-4 - .light-well - %h4 Projects - .data - = link_to admin_namespaces_projects_path do - %h1= number_with_delimiter(Project.count) - %hr - = link_to('New Project', new_project_path, class: "btn btn-new") - .col-sm-4 - .light-well - %h4 Users - .data - = link_to admin_users_path do - %h1= number_with_delimiter(User.count) - %hr - = link_to 'New User', new_admin_user_path, class: "btn btn-new" - .col-sm-4 - .light-well - %h4 Groups - .data - = link_to admin_groups_path do - %h1= number_with_delimiter(Group.count) - %hr - = link_to 'New Group', new_admin_group_path, class: "btn btn-new" - - .row.prepend-top-10 - .col-md-4 - %h4 Latest projects - %hr - - @projects.each do |project| - %p - = link_to project.name_with_namespace, [:admin, project.namespace.becomes(Namespace), project], class: 'str-truncated' - %span.light.pull-right - #{time_ago_with_tooltip(project.created_at)} + = link_to project.name_with_namespace, [:admin, project.namespace.becomes(Namespace), project], class: 'str-truncated' + %span.light.pull-right + #{time_ago_with_tooltip(project.created_at)} - .col-md-4 - %h4 Latest users - %hr - - @users.each do |user| - %p - = link_to [:admin, user], class: 'str-truncated' do - = user.name - %span.light.pull-right - #{time_ago_with_tooltip(user.created_at)} + .col-md-4 + %h4 Latest users + %hr + - @users.each do |user| + %p + = link_to [:admin, user], class: 'str-truncated' do + = user.name + %span.light.pull-right + #{time_ago_with_tooltip(user.created_at)} - .col-md-4 - %h4 Latest groups - %hr - - @groups.each do |group| - %p - = link_to [:admin, group], class: 'str-truncated' do - = group.name - %span.light.pull-right - #{time_ago_with_tooltip(group.created_at)} + .col-md-4 + %h4 Latest groups + %hr + - @groups.each do |group| + %p + = link_to [:admin, group], class: 'str-truncated' do + = group.name + %span.light.pull-right + #{time_ago_with_tooltip(group.created_at)} diff --git a/app/views/admin/groups/index.html.haml b/app/views/admin/groups/index.html.haml index 4f1996ef7ab..775072a7441 100644 --- a/app/views/admin/groups/index.html.haml +++ b/app/views/admin/groups/index.html.haml @@ -1,45 +1,41 @@ -- @no_container = true - page_title "Groups" -= render "admin/dashboard/head" +%h3.page-title + Groups (#{number_with_delimiter(@groups.total_count)}) -%div{ class: (container_class) } - %h3.page-title - Groups (#{number_with_delimiter(@groups.total_count)}) +%p.light + Group allows you to keep projects organized. + Use groups for uniting related projects. - %p.light - Group allows you to keep projects organized. - Use groups for uniting related projects. +.top-area + .nav-search + = form_tag admin_groups_path, method: :get, class: 'form-inline' do + = hidden_field_tag :sort, @sort + = text_field_tag :name, params[:name], class: "form-control" + = button_tag "Search", class: "btn submit btn-primary" - .top-area - .nav-search - = form_tag admin_groups_path, method: :get, class: 'form-inline' do - = hidden_field_tag :sort, @sort - = text_field_tag :name, params[:name], class: "form-control" - = button_tag "Search", class: "btn submit btn-primary" - - .nav-controls - .dropdown.inline - %a.dropdown-toggle.btn{href: '#', "data-toggle" => "dropdown"} - %span.light - - if @sort.present? - = sort_options_hash[@sort] - - else + .nav-controls + .dropdown.inline + %a.dropdown-toggle.btn{href: '#', "data-toggle" => "dropdown"} + %span.light + - if @sort.present? + = sort_options_hash[@sort] + - else + = sort_title_recently_created + %b.caret + %ul.dropdown-menu + %li + = link_to admin_groups_path(sort: sort_value_recently_created) do = sort_title_recently_created - %b.caret - %ul.dropdown-menu - %li - = link_to admin_groups_path(sort: sort_value_recently_created) do - = sort_title_recently_created - = link_to admin_groups_path(sort: sort_value_oldest_created) do - = sort_title_oldest_created - = link_to admin_groups_path(sort: sort_value_recently_updated) do - = sort_title_recently_updated - = link_to admin_groups_path(sort: sort_value_oldest_updated) do - = sort_title_oldest_updated - = link_to 'New Group', new_admin_group_path, class: "btn btn-new" + = link_to admin_groups_path(sort: sort_value_oldest_created) do + = sort_title_oldest_created + = link_to admin_groups_path(sort: sort_value_recently_updated) do + = sort_title_recently_updated + = link_to admin_groups_path(sort: sort_value_oldest_updated) do + = sort_title_oldest_updated + = link_to 'New Group', new_admin_group_path, class: "btn btn-new" - %ul.content-list - - @groups.each do |group| - = render 'group', group: group +%ul.content-list + - @groups.each do |group| + = render 'group', group: group - = paginate @groups, theme: "gitlab" += paginate @groups, theme: "gitlab" diff --git a/app/views/admin/groups/show.html.haml b/app/views/admin/groups/show.html.haml index 5b8a0262ea0..f309e80a39a 100644 --- a/app/views/admin/groups/show.html.haml +++ b/app/views/admin/groups/show.html.haml @@ -109,7 +109,7 @@ %span.pull-right.light = member.human_access - if can?(current_user, :destroy_group_member, member) - = link_to group_group_member_path(@group, member), data: { confirm: remove_member_message(member) }, method: :delete, remote: true, class: "btn-xs btn btn-remove", title: 'Remove user from group' do + = link_to group_group_member_path(@group, member), data: { confirm: remove_user_from_group_message(@group, member) }, method: :delete, remote: true, class: "btn-xs btn btn-remove", title: 'Remove user from group' do %i.fa.fa-minus.fa-inverse .panel-footer = paginate @members, param_name: 'members_page', theme: 'gitlab' diff --git a/app/views/admin/health_check/show.html.haml b/app/views/admin/health_check/show.html.haml index 7b8407f9152..c2313986a7f 100644 --- a/app/views/admin/health_check/show.html.haml +++ b/app/views/admin/health_check/show.html.haml @@ -1,52 +1,49 @@ -- @no_container = true - page_title "Health Check" -= render 'admin/background_jobs/head' -%div{ class: (container_class) } - %h3.page-title - Health Check - .bs-callout.clearfix - .pull-left - %p - Access token is - %code#health-check-token= current_application_settings.health_check_access_token - = button_to reset_health_check_token_admin_application_settings_path, - method: :put, class: 'btn btn-default', - data: { confirm: 'Are you sure you want to reset the health check token?' } do - = icon('refresh') - Reset health check access token - %p.light - Health information can be retrieved as plain text, JSON, or XML using: - %ul - %li - %code= health_check_url(token: current_application_settings.health_check_access_token) - %li - %code= health_check_url(token: current_application_settings.health_check_access_token, format: :json) - %li - %code= health_check_url(token: current_application_settings.health_check_access_token, format: :xml) +%h3.page-title + Health Check +.bs-callout.clearfix + .pull-left + %p + Access token is + %code#health-check-token= current_application_settings.health_check_access_token + = button_to reset_health_check_token_admin_application_settings_path, + method: :put, class: 'btn btn-default', + data: { confirm: 'Are you sure you want to reset the health check token?' } do + = icon('refresh') + Reset health check access token +%p.light + Health information can be retrieved as plain text, JSON, or XML using: + %ul + %li + %code= health_check_url(token: current_application_settings.health_check_access_token) + %li + %code= health_check_url(token: current_application_settings.health_check_access_token, format: :json) + %li + %code= health_check_url(token: current_application_settings.health_check_access_token, format: :xml) - %p.light - You can also ask for the status of specific services: - %ul - %li - %code= health_check_url(token: current_application_settings.health_check_access_token, checks: :cache) - %li - %code= health_check_url(token: current_application_settings.health_check_access_token, checks: :database) - %li - %code= health_check_url(token: current_application_settings.health_check_access_token, checks: :migrations) +%p.light + You can also ask for the status of specific services: + %ul + %li + %code= health_check_url(token: current_application_settings.health_check_access_token, checks: :cache) + %li + %code= health_check_url(token: current_application_settings.health_check_access_token, checks: :database) + %li + %code= health_check_url(token: current_application_settings.health_check_access_token, checks: :migrations) - %hr - .panel.panel-default - .panel-heading - Current Status: - - if @errors.blank? - = icon('circle', class: 'cgreen') - Healthy - - else - = icon('warning', class: 'cred') - Unhealthy - .panel-body - - if @errors.blank? - No Health Problems Detected - - else - = @errors +%hr +.panel.panel-default + .panel-heading + Current Status: + - if @errors.blank? + = icon('circle', class: 'cgreen') + Healthy + - else + = icon('warning', class: 'cred') + Unhealthy + .panel-body + - if @errors.blank? + No Health Problems Detected + - else + = @errors diff --git a/app/views/admin/logs/show.html.haml b/app/views/admin/logs/show.html.haml index 5ddc3b9ea85..698feb571ac 100644 --- a/app/views/admin/logs/show.html.haml +++ b/app/views/admin/logs/show.html.haml @@ -1,32 +1,28 @@ -- @no_container = true - page_title "Logs" - loggers = [Gitlab::GitLogger, Gitlab::AppLogger, Gitlab::ProductionLogger, Gitlab::SidekiqLogger, Gitlab::RepositoryCheckLogger] -= render 'admin/background_jobs/head' - -%div{ class: (container_class) } - %ul.nav-links.log-tabs - - loggers.each do |klass| - %li{ class: (klass == Gitlab::GitLogger ? 'active' : '') } - = link_to klass::file_name, "##{klass::file_name_noext}", - 'data-toggle' => 'tab' - .row-content-block - To prevent performance issues admin logs output the last 2000 lines - .tab-content - - loggers.each do |klass| - .tab-pane{ class: (klass == Gitlab::GitLogger ? 'active' : ''), - id: klass::file_name_noext } - .file-holder#README - .file-title - %i.fa.fa-file - = klass::file_name - .pull-right - = link_to '#', class: 'log-bottom' do - %i.fa.fa-arrow-down - Scroll down - .file-content.logs - %ol - - klass.read_latest.each do |line| - %li - %p= line +%ul.nav-links.log-tabs + - loggers.each do |klass| + %li{ class: (klass == Gitlab::GitLogger ? 'active' : '') } + = link_to klass::file_name, "##{klass::file_name_noext}", + 'data-toggle' => 'tab' +.row-content-block + To prevent performance issues admin logs output the last 2000 lines +.tab-content + - loggers.each do |klass| + .tab-pane{ class: (klass == Gitlab::GitLogger ? 'active' : ''), + id: klass::file_name_noext } + .file-holder#README + .file-title + %i.fa.fa-file + = klass::file_name + .pull-right + = link_to '#', class: 'log-bottom' do + %i.fa.fa-arrow-down + Scroll down + .file-content.logs + %ol + - klass.read_latest.each do |line| + %li + %p= line diff --git a/app/views/admin/projects/index.html.haml b/app/views/admin/projects/index.html.haml index 4822cb693c2..aa07afa0d62 100644 --- a/app/views/admin/projects/index.html.haml +++ b/app/views/admin/projects/index.html.haml @@ -1,97 +1,94 @@ -- @no_container = true - page_title "Projects" = render 'shared/show_aside' -= render "admin/dashboard/head" -%div{ class: (container_class) } - .row.prepend-top-default - %aside.col-md-3 - .panel.admin-filter - = form_tag admin_namespaces_projects_path, method: :get, class: '' do - .form-group - = label_tag :name, 'Name:' - = text_field_tag :name, params[:name], class: "form-control" +.row.prepend-top-default + %aside.col-md-3 + .panel.admin-filter + = form_tag admin_namespaces_projects_path, method: :get, class: '' do + .form-group + = label_tag :name, 'Name:' + = text_field_tag :name, params[:name], class: "form-control" - .form-group - = label_tag :namespace_id, "Namespace" - = namespace_select_tag :namespace_id, selected: params[:namespace_id], class: 'input-large' + .form-group + = label_tag :namespace_id, "Namespace" + = namespace_select_tag :namespace_id, selected: params[:namespace_id], class: 'input-large' - .form-group - %strong Activity - .checkbox - = label_tag :with_push do - = check_box_tag :with_push, 1, params[:with_push] - %span Projects with push events - .checkbox - = label_tag :abandoned do - = check_box_tag :abandoned, 1, params[:abandoned] - %span No activity over 6 month - .checkbox - = label_tag :with_archived do - = check_box_tag :with_archived, 1, params[:with_archived] - %span Show archived projects + .form-group + %strong Activity + .checkbox + = label_tag :with_push do + = check_box_tag :with_push, 1, params[:with_push] + %span Projects with push events + .checkbox + = label_tag :abandoned do + = check_box_tag :abandoned, 1, params[:abandoned] + %span No activity over 6 month + .checkbox + = label_tag :with_archived do + = check_box_tag :with_archived, 1, params[:with_archived] + %span Show archived projects - %fieldset - %strong Visibility level: - .visibility-levels - - Project.visibility_levels.each do |label, level| - .checkbox - %label - = check_box_tag 'visibility_levels[]', level, params[:visibility_levels].present? && params[:visibility_levels].include?(level.to_s) - %span.descr - = visibility_level_icon(level) - = label - %fieldset - %strong Problems - .checkbox - = label_tag :last_repository_check_failed do - = check_box_tag :last_repository_check_failed, 1, params[:last_repository_check_failed] - %span Last repository check failed + %fieldset + %strong Visibility level: + .visibility-levels + - Project.visibility_levels.each do |label, level| + .checkbox + %label + = check_box_tag 'visibility_levels[]', level, params[:visibility_levels].present? && params[:visibility_levels].include?(level.to_s) + %span.descr + = visibility_level_icon(level) + = label + %fieldset + %strong Problems + .checkbox + = label_tag :last_repository_check_failed do + = check_box_tag :last_repository_check_failed, 1, params[:last_repository_check_failed] + %span Last repository check failed - = hidden_field_tag :sort, params[:sort] - = button_tag "Search", class: "btn submit btn-primary" - = link_to "Reset", admin_namespaces_projects_path, class: "btn btn-cancel" + = hidden_field_tag :sort, params[:sort] + = button_tag "Search", class: "btn submit btn-primary" + = link_to "Reset", admin_namespaces_projects_path, class: "btn btn-cancel" - %section.col-md-9 - .panel.panel-default - .panel-heading - Projects (#{@projects.total_count}) - .controls - .dropdown.inline - %button.dropdown-toggle.btn.btn-sm{type: 'button', 'data-toggle' => 'dropdown'} - %span.light - - if @sort.present? - = sort_options_hash[@sort] - - else + %section.col-md-9 + .panel.panel-default + .panel-heading + Projects (#{@projects.total_count}) + .controls + .dropdown.inline + %button.dropdown-toggle.btn.btn-sm{type: 'button', 'data-toggle' => 'dropdown'} + %span.light + - if @sort.present? + = sort_options_hash[@sort] + - else + = sort_title_recently_created + %b.caret + %ul.dropdown-menu + %li + = link_to admin_namespaces_projects_path(sort: sort_value_recently_created) do = sort_title_recently_created - %b.caret - %ul.dropdown-menu - %li - = link_to admin_namespaces_projects_path(sort: sort_value_recently_created) do - = sort_title_recently_created - = link_to admin_namespaces_projects_path(sort: sort_value_oldest_created) do - = sort_title_oldest_created - = link_to admin_namespaces_projects_path(sort: sort_value_recently_updated) do - = sort_title_recently_updated - = link_to admin_namespaces_projects_path(sort: sort_value_oldest_updated) do - = sort_title_oldest_updated - = link_to admin_namespaces_projects_path(sort: sort_value_largest_repo) do - = sort_title_largest_repo - = link_to 'New Project', new_project_path, class: "btn btn-sm btn-success" - %ul.well-list - - @projects.each do |project| - %li - .list-item-name - %span{ class: visibility_level_color(project.visibility_level) } - = visibility_level_icon(project.visibility_level) - = link_to project.name_with_namespace, [:admin, project.namespace.becomes(Namespace), project] - .pull-right - - if project.archived - %span.label.label-warning archived - %span.label.label-gray - = repository_size(project) - = link_to 'Edit', edit_namespace_project_path(project.namespace, project), id: "edit_#{dom_id(project)}", class: "btn btn-sm" - = link_to 'Destroy', [project.namespace.becomes(Namespace), project], data: { confirm: remove_project_message(project) }, method: :delete, class: "btn btn-sm btn-remove" - - if @projects.blank? - .nothing-here-block 0 projects matches - = paginate @projects, theme: "gitlab" + = link_to admin_namespaces_projects_path(sort: sort_value_oldest_created) do + = sort_title_oldest_created + = link_to admin_namespaces_projects_path(sort: sort_value_recently_updated) do + = sort_title_recently_updated + = link_to admin_namespaces_projects_path(sort: sort_value_oldest_updated) do + = sort_title_oldest_updated + = link_to admin_namespaces_projects_path(sort: sort_value_largest_repo) do + = sort_title_largest_repo + = link_to 'New Project', new_project_path, class: "btn btn-sm btn-success" + %ul.well-list + - @projects.each do |project| + %li + .list-item-name + %span{ class: visibility_level_color(project.visibility_level) } + = visibility_level_icon(project.visibility_level) + = link_to project.name_with_namespace, [:admin, project.namespace.becomes(Namespace), project] + .pull-right + - if project.archived + %span.label.label-warning archived + %span.label.label-gray + = repository_size(project) + = link_to 'Edit', edit_namespace_project_path(project.namespace, project), id: "edit_#{dom_id(project)}", class: "btn btn-sm" + = link_to 'Destroy', [project.namespace.becomes(Namespace), project], data: { confirm: remove_project_message(project) }, method: :delete, class: "btn btn-sm btn-remove" + - if @projects.blank? + .nothing-here-block 0 projects matches + = paginate @projects, theme: "gitlab" diff --git a/app/views/admin/projects/show.html.haml b/app/views/admin/projects/show.html.haml index 9e55a562e18..73986d21bcf 100644 --- a/app/views/admin/projects/show.html.haml +++ b/app/views/admin/projects/show.html.haml @@ -142,7 +142,7 @@ %i.fa.fa-pencil-square-o %ul.well-list - @group_members.each do |member| - = render 'shared/members/member', member: member, show_controls: false + = render 'groups/group_members/group_member', member: member, show_controls: false .panel-footer = paginate @group_members, param_name: 'group_members_page', theme: 'gitlab' @@ -172,7 +172,7 @@ %span.light Owner - else %span.light= project_member.human_access - = link_to namespace_project_project_member_path(@project.namespace, @project, project_member), data: { confirm: remove_member_message(project_member)}, method: :delete, remote: true, class: "btn btn-sm btn-remove" do + = link_to namespace_project_project_member_path(@project.namespace, @project, project_member), data: { confirm: remove_from_project_team_message(@project, project_member)}, method: :delete, remote: true, class: "btn btn-sm btn-remove" do %i.fa.fa-times .panel-footer = paginate @project_members, param_name: 'project_members_page', theme: 'gitlab' diff --git a/app/views/admin/users/groups.html.haml b/app/views/admin/users/groups.html.haml index b0a709a568a..dbecb7bbfd6 100644 --- a/app/views/admin/users/groups.html.haml +++ b/app/views/admin/users/groups.html.haml @@ -13,7 +13,7 @@ .pull-right %span.light= group_member.human_access - unless group_member.owner? - = link_to group_group_member_path(group, group_member), data: { confirm: remove_member_message(group_member) }, method: :delete, remote: true, class: "btn-xs btn btn-remove", title: 'Remove user from group' do + = link_to group_group_member_path(group, group_member), data: { confirm: remove_user_from_group_message(group, group_member) }, method: :delete, remote: true, class: "btn-xs btn btn-remove", title: 'Remove user from group' do %i.fa.fa-times.fa-inverse - else .nothing-here-block This user has no groups. diff --git a/app/views/admin/users/index.html.haml b/app/views/admin/users/index.html.haml index d0a696da64b..d6743081c8e 100644 --- a/app/views/admin/users/index.html.haml +++ b/app/views/admin/users/index.html.haml @@ -1,110 +1,107 @@ -- @no_container = true - page_title "Users" = render 'shared/show_aside' -= render "admin/dashboard/head" -%div{ class: (container_class) } - .admin-filter - %ul.nav-links - %li{class: "#{'active' unless params[:filter]}"} - = link_to admin_users_path do - Active - %small.badge= number_with_delimiter(User.active.count) - %li{class: "#{'active' if params[:filter] == "admins"}"} - = link_to admin_users_path(filter: "admins") do - Admins - %small.badge= number_with_delimiter(User.admins.count) - %li.filter-two-factor-enabled{class: "#{'active' if params[:filter] == 'two_factor_enabled'}"} - = link_to admin_users_path(filter: 'two_factor_enabled') do - 2FA Enabled - %small.badge= number_with_delimiter(User.with_two_factor.count) - %li.filter-two-factor-disabled{class: "#{'active' if params[:filter] == 'two_factor_disabled'}"} - = link_to admin_users_path(filter: 'two_factor_disabled') do - 2FA Disabled - %small.badge= number_with_delimiter(User.without_two_factor.count) - %li.filter-external{class: "#{'active' if params[:filter] == 'external'}"} - = link_to admin_users_path(filter: 'external') do - External - %small.badge= number_with_delimiter(User.external.count) - %li{class: "#{'active' if params[:filter] == "blocked"}"} - = link_to admin_users_path(filter: "blocked") do - Blocked - %small.badge= number_with_delimiter(User.blocked.count) - %li{class: "#{'active' if params[:filter] == "wop"}"} - = link_to admin_users_path(filter: "wop") do - Without projects - %small.badge= number_with_delimiter(User.without_projects.count) +.admin-filter + %ul.nav-links + %li{class: "#{'active' unless params[:filter]}"} + = link_to admin_users_path do + Active + %small.badge= number_with_delimiter(User.active.count) + %li{class: "#{'active' if params[:filter] == "admins"}"} + = link_to admin_users_path(filter: "admins") do + Admins + %small.badge= number_with_delimiter(User.admins.count) + %li.filter-two-factor-enabled{class: "#{'active' if params[:filter] == 'two_factor_enabled'}"} + = link_to admin_users_path(filter: 'two_factor_enabled') do + 2FA Enabled + %small.badge= number_with_delimiter(User.with_two_factor.count) + %li.filter-two-factor-disabled{class: "#{'active' if params[:filter] == 'two_factor_disabled'}"} + = link_to admin_users_path(filter: 'two_factor_disabled') do + 2FA Disabled + %small.badge= number_with_delimiter(User.without_two_factor.count) + %li.filter-external{class: "#{'active' if params[:filter] == 'external'}"} + = link_to admin_users_path(filter: 'external') do + External + %small.badge= number_with_delimiter(User.external.count) + %li{class: "#{'active' if params[:filter] == "blocked"}"} + = link_to admin_users_path(filter: "blocked") do + Blocked + %small.badge= number_with_delimiter(User.blocked.count) + %li{class: "#{'active' if params[:filter] == "wop"}"} + = link_to admin_users_path(filter: "wop") do + Without projects + %small.badge= number_with_delimiter(User.without_projects.count) - .row-content-block.second-block - .pull-right - .dropdown.inline - %a.dropdown-toggle.btn{href: '#', "data-toggle" => "dropdown"} - %span.light - - if @sort.present? - = sort_options_hash[@sort] - - else + .row-content-block.second-block + .pull-right + .dropdown.inline + %a.dropdown-toggle.btn{href: '#', "data-toggle" => "dropdown"} + %span.light + - if @sort.present? + = sort_options_hash[@sort] + - else + = sort_title_name + %b.caret + %ul.dropdown-menu + %li + = link_to admin_users_path(sort: sort_value_name, filter: params[:filter]) do = sort_title_name - %b.caret - %ul.dropdown-menu - %li - = link_to admin_users_path(sort: sort_value_name, filter: params[:filter]) do - = sort_title_name - = link_to admin_users_path(sort: sort_value_recently_signin, filter: params[:filter]) do - = sort_title_recently_signin - = link_to admin_users_path(sort: sort_value_oldest_signin, filter: params[:filter]) do - = sort_title_oldest_signin - = link_to admin_users_path(sort: sort_value_recently_created, filter: params[:filter]) do - = sort_title_recently_created - = link_to admin_users_path(sort: sort_value_oldest_created, filter: params[:filter]) do - = sort_title_oldest_created - = link_to admin_users_path(sort: sort_value_recently_updated, filter: params[:filter]) do - = sort_title_recently_updated - = link_to admin_users_path(sort: sort_value_oldest_updated, filter: params[:filter]) do - = sort_title_oldest_updated + = link_to admin_users_path(sort: sort_value_recently_signin, filter: params[:filter]) do + = sort_title_recently_signin + = link_to admin_users_path(sort: sort_value_oldest_signin, filter: params[:filter]) do + = sort_title_oldest_signin + = link_to admin_users_path(sort: sort_value_recently_created, filter: params[:filter]) do + = sort_title_recently_created + = link_to admin_users_path(sort: sort_value_oldest_created, filter: params[:filter]) do + = sort_title_oldest_created + = link_to admin_users_path(sort: sort_value_recently_updated, filter: params[:filter]) do + = sort_title_recently_updated + = link_to admin_users_path(sort: sort_value_oldest_updated, filter: params[:filter]) do + = sort_title_oldest_updated - = link_to 'New User', new_admin_user_path, class: "btn btn-new" - = form_tag admin_users_path, method: :get, class: 'form-inline' do - .form-group - = search_field_tag :name, params[:name], placeholder: 'Name, email or username', class: 'form-control', spellcheck: false - = hidden_field_tag "filter", params[:filter] - = button_tag class: 'btn btn-primary' do - %i.fa.fa-search + = link_to 'New User', new_admin_user_path, class: "btn btn-new" + = form_tag admin_users_path, method: :get, class: 'form-inline' do + .form-group + = search_field_tag :name, params[:name], placeholder: 'Name, email or username', class: 'form-control', spellcheck: false + = hidden_field_tag "filter", params[:filter] + = button_tag class: 'btn btn-primary' do + %i.fa.fa-search - .panel.panel-default - %ul.well-list - - @users.each do |user| - %li - .list-item-name - - if user.blocked? - = icon("lock", class: "cred") - - else - = icon("user", class: "cgreen") - = link_to user.name, [:admin, user] - - if user.admin? - %strong.cred (Admin) - - if user.external? - %strong.cred (External) - - if user == current_user - %span.cred It's you! +.panel.panel-default + %ul.well-list + - @users.each do |user| + %li + .list-item-name + - if user.blocked? + = icon("lock", class: "cred") + - else + = icon("user", class: "cgreen") + = link_to user.name, [:admin, user] + - if user.admin? + %strong.cred (Admin) + - if user.external? + %strong.cred (External) + - if user == current_user + %span.cred It's you! + .pull-right + %span.light + %i.fa.fa-envelope + = mail_to user.email, user.email, class: 'light' + .pull-right - %span.light - %i.fa.fa-envelope - = mail_to user.email, user.email, class: 'light' - - .pull-right - = link_to 'Edit', edit_admin_user_path(user), id: "edit_#{dom_id(user)}", class: 'btn-grouped btn btn-xs' - - unless user == current_user - - if user.ldap_blocked? - = link_to '#', title: 'Cannot unblock LDAP blocked users', data: {toggle: 'tooltip'}, class: 'btn-grouped btn btn-xs btn-success disabled' do - %i.fa.fa-lock - Unblock - - elsif user.blocked? - = link_to 'Unblock', unblock_admin_user_path(user), method: :put, class: 'btn-grouped btn btn-xs btn-success' - - else - = link_to 'Block', block_admin_user_path(user), data: {confirm: 'USER WILL BE BLOCKED! Are you sure?'}, method: :put, class: 'btn-grouped btn btn-xs btn-warning' - - if user.access_locked? - = link_to 'Unlock', unlock_admin_user_path(user), method: :put, class: 'btn-grouped btn btn-xs btn-success', data: { confirm: 'Are you sure?' } - - if user.can_be_removed? - = link_to 'Destroy', [:admin, user], data: { confirm: "USER #{user.name} WILL BE REMOVED! All issues, merge requests and groups linked to this user will also be removed! Maybe block the user instead? Are you sure?" }, method: :delete, class: 'btn-grouped btn btn-xs btn-remove' - = paginate @users, theme: "gitlab" + = link_to 'Edit', edit_admin_user_path(user), id: "edit_#{dom_id(user)}", class: 'btn-grouped btn btn-xs' + - unless user == current_user + - if user.ldap_blocked? + = link_to '#', title: 'Cannot unblock LDAP blocked users', data: {toggle: 'tooltip'}, class: 'btn-grouped btn btn-xs btn-success disabled' do + %i.fa.fa-lock + Unblock + - elsif user.blocked? + = link_to 'Unblock', unblock_admin_user_path(user), method: :put, class: 'btn-grouped btn btn-xs btn-success' + - else + = link_to 'Block', block_admin_user_path(user), data: {confirm: 'USER WILL BE BLOCKED! Are you sure?'}, method: :put, class: 'btn-grouped btn btn-xs btn-warning' + - if user.access_locked? + = link_to 'Unlock', unlock_admin_user_path(user), method: :put, class: 'btn-grouped btn btn-xs btn-success', data: { confirm: 'Are you sure?' } + - if user.can_be_removed? + = link_to 'Destroy', [:admin, user], data: { confirm: "USER #{user.name} WILL BE REMOVED! All issues, merge requests and groups linked to this user will also be removed! Maybe block the user instead? Are you sure?" }, method: :delete, class: 'btn-grouped btn btn-xs btn-remove' += paginate @users, theme: "gitlab" diff --git a/app/views/admin/users/projects.html.haml b/app/views/admin/users/projects.html.haml index 84b9ceb23b3..b655b2a15f5 100644 --- a/app/views/admin/users/projects.html.haml +++ b/app/views/admin/users/projects.html.haml @@ -38,5 +38,6 @@ %span.light= member.human_access - if member.respond_to? :project - = link_to namespace_project_project_member_path(project.namespace, project, member), data: { confirm: remove_member_message(member) }, remote: true, method: :delete, class: "btn-xs btn btn-remove", title: 'Remove user from project' do + = link_to namespace_project_project_member_path(project.namespace, project, member), data: { confirm: remove_from_project_team_message(project, member) }, remote: true, method: :delete, class: "btn-xs btn btn-remove", title: 'Remove user from project' do %i.fa.fa-times + diff --git a/app/views/dashboard/issues.atom.builder b/app/views/dashboard/issues.atom.builder index 0404d0728ea..83c0c6da21b 100644 --- a/app/views/dashboard/issues.atom.builder +++ b/app/views/dashboard/issues.atom.builder @@ -4,7 +4,7 @@ xml.feed "xmlns" => "http://www.w3.org/2005/Atom", "xmlns:media" => "http://sear xml.link href: issues_dashboard_url(format: :atom, private_token: current_user.try(:private_token)), rel: "self", type: "application/atom+xml" xml.link href: issues_dashboard_url, rel: "alternate", type: "text/html" xml.id issues_dashboard_url - xml.updated @issues.first.created_at.xmlschema if @issues.reorder(nil).any? + xml.updated @issues.first.created_at.xmlschema if @issues.any? - xml << render(partial: 'issues/issue', collection: @issues) if @issues.reorder(nil).any? + xml << render(partial: 'issues/issue', collection: @issues) if @issues.any? end diff --git a/app/views/groups/group_members/_group_member.html.haml b/app/views/groups/group_members/_group_member.html.haml new file mode 100644 index 00000000000..6bb542e658d --- /dev/null +++ b/app/views/groups/group_members/_group_member.html.haml @@ -0,0 +1,57 @@ +- user = member.user +- return unless user || member.invite? +- show_roles = local_assigns.fetch(:show_roles, true) + +%li{class: "#{dom_class(member)} js-toggle-container", id: dom_id(member)} + %span{class: ("list-item-name" if show_controls)} + - if member.user + = image_tag avatar_icon(user, 24), class: "avatar s24", alt: '' + %strong + = link_to user.name, user_path(user) + %span.cgray= user.username + - if user == current_user + %span.label.label-success It's you + - if user.blocked? + %label.label.label-danger + %strong Blocked + - else + = image_tag avatar_icon(member.invite_email, 24), class: "avatar s24", alt: '' + %strong + = member.invite_email + %span.cgray + invited + - if member.created_by + by + = link_to member.created_by.name, user_path(member.created_by) + = time_ago_with_tooltip(member.created_at) + + - if show_controls && can?(current_user, :admin_group_member, @group) + = link_to resend_invite_group_group_member_path(@group, member), method: :post, class: "btn-xs btn", title: 'Resend invite' do + Resend invite + + - if show_roles && should_user_see_group_roles?(current_user, @group) + %span.pull-right + %strong.member-access-level= member.human_access + - if show_controls + - if can?(current_user, :update_group_member, member) + = button_tag class: "btn-xs btn btn-grouped inline js-toggle-button", + title: 'Edit access level', type: 'button' do + = icon('pencil') + + - if can?(current_user, :destroy_group_member, member) + + - if current_user == user + = link_to leave_group_group_members_path(@group), data: { confirm: leave_group_message(@group.name)}, method: :delete, class: "btn-xs btn btn-remove", title: 'Remove user from group' do + = icon("sign-out") + Leave + - else + = link_to group_group_member_path(@group, member), data: { confirm: remove_user_from_group_message(@group, member) }, method: :delete, remote: true, class: "btn-xs btn btn-remove", title: 'Remove user from group' do + = icon('trash') + + .edit-member.hide.js-toggle-content + %br + = form_for [@group, member], remote: true do |f| + .prepend-top-10 + = f.select :access_level, options_for_select(GroupMember.access_level_roles, member.access_level), {}, class: 'form-control' + .prepend-top-10 + = f.submit 'Save', class: 'btn btn-save btn-sm' diff --git a/app/views/groups/group_members/index.html.haml b/app/views/groups/group_members/index.html.haml index a36531e095a..0eb6bbd4420 100644 --- a/app/views/groups/group_members/index.html.haml +++ b/app/views/groups/group_members/index.html.haml @@ -6,13 +6,12 @@ .panel-heading Add new user to group .panel-body - %p.light - Members of group have access to all group projects. + - if should_user_see_group_roles?(current_user, @group) + %p.light + Members of group have access to all group projects. .new-group-member-holder = render "new_group_member" - = render 'shared/members/requests', membership_source: @group, members: @members.request - .panel.panel-default .panel-heading %strong #{@group.name} @@ -26,8 +25,9 @@ = button_tag class: 'btn', title: 'Search' do = icon("search") %ul.content-list - = render partial: 'shared/members/member', collection: @members.non_request, as: :member - = paginate @members.non_request, theme: 'gitlab' + - @members.each do |member| + = render 'groups/group_members/group_member', member: member, show_controls: true + = paginate @members, theme: 'gitlab' :javascript $('form.member-search-form').on('submit', function(event) { diff --git a/app/views/groups/group_members/update.js.haml b/app/views/groups/group_members/update.js.haml index da71de4cd1e..df726e2b2b9 100644 --- a/app/views/groups/group_members/update.js.haml +++ b/app/views/groups/group_members/update.js.haml @@ -1,2 +1,2 @@ :plain - $("##{dom_id(@group_member)}").replaceWith('#{escape_javascript(render('shared/members/member', member: @group_member))}'); + $("##{dom_id(@group_member)}").replaceWith('#{escape_javascript(render(@group_member, member: @group_member, show_controls: true))}'); diff --git a/app/views/groups/issues.atom.builder b/app/views/groups/issues.atom.builder index b1628040325..c19671295af 100644 --- a/app/views/groups/issues.atom.builder +++ b/app/views/groups/issues.atom.builder @@ -4,7 +4,7 @@ xml.feed "xmlns" => "http://www.w3.org/2005/Atom", "xmlns:media" => "http://sear xml.link href: issues_group_url(format: :atom, private_token: current_user.try(:private_token)), rel: "self", type: "application/atom+xml" xml.link href: issues_group_url, rel: "alternate", type: "text/html" xml.id issues_group_url - xml.updated @issues.first.created_at.xmlschema if @issues.reorder(nil).any? + xml.updated @issues.first.created_at.xmlschema if @issues.any? - xml << render(partial: 'issues/issue', collection: @issues) if @issues.reorder(nil).any? + xml << render(partial: 'issues/issue', collection: @issues) if @issues.any? end diff --git a/app/views/groups/show.html.haml b/app/views/groups/show.html.haml index 62ebd69485c..85635bc4616 100644 --- a/app/views/groups/show.html.haml +++ b/app/views/groups/show.html.haml @@ -19,9 +19,6 @@ .cover-desc.description = markdown(@group.description, pipeline: :description) - - if current_user - = render 'shared/members/access_request_buttons', source: @group - %div{ class: container_class } .top-area %ul.nav-links diff --git a/app/views/issues/_issue.atom.builder b/app/views/issues/_issue.atom.builder index 96831874144..68a2d19e58d 100644 --- a/app/views/issues/_issue.atom.builder +++ b/app/views/issues/_issue.atom.builder @@ -5,28 +5,10 @@ xml.entry do xml.updated issue.created_at.xmlschema xml.media :thumbnail, width: "40", height: "40", url: image_url(avatar_icon(issue.author_email)) - xml.author do + xml.author do |author| xml.name issue.author_name xml.email issue.author_email end xml.summary issue.title - xml.description issue.description if issue.description - xml.milestone issue.milestone.title if issue.milestone - xml.due_date issue.due_date if issue.due_date - - unless issue.labels.empty? - xml.labels do - issue.labels.each do |label| - xml.label label.name - end - end - end - - if issue.assignee - xml.assignee do - xml.name issue.assignee.name - xml.email issue.assignee.email - end - end end diff --git a/app/views/layouts/_collapse_button.html.haml b/app/views/layouts/_collapse_button.html.haml index 8c140a5943e..e4fab897377 100644 --- a/app/views/layouts/_collapse_button.html.haml +++ b/app/views/layouts/_collapse_button.html.haml @@ -1,3 +1 @@ -= link_to '#', class: 'nav-header-btn text-center toggle-nav-collapse', title: "Open/Close" do - %span.sr-only Toggle navigation - = icon('bars') += link_to icon('bars'), '#', class: 'toggle-nav-collapse', title: "Open/Close" diff --git a/app/views/layouts/_page.html.haml b/app/views/layouts/_page.html.haml index 199ab3c38c3..f89e8582792 100644 --- a/app/views/layouts/_page.html.haml +++ b/app/views/layouts/_page.html.haml @@ -1,6 +1,6 @@ -.page-with-sidebar{ class: "#{page_sidebar_class} #{page_gutter_class}" } +.page-with-sidebar.page-sidebar-collapsed{ class: "#{page_gutter_class}" } .sidebar-wrapper.nicescroll{ class: nav_sidebar_class } - = render partial: 'layouts/collapse_button' + - if defined?(sidebar) && sidebar = render "layouts/nav/#{sidebar}" - elsif current_user @@ -8,14 +8,13 @@ - else = render 'layouts/nav/explore' + .collapse-nav + = render partial: 'layouts/collapse_button' - if current_user = link_to current_user, class: 'sidebar-user', title: "Profile", data: {user: current_user.username} do = image_tag avatar_icon(current_user, 60), alt: 'Profile', class: 'avatar avatar s36' .username = current_user.username - = link_to '#', class: "nav-header-btn text-center pin-nav-btn #{'is-active' if pinned_nav?} js-nav-pin", title: 'Pin/Unpin navigation' do - %span.sr-only Toggle navigation pinning - = icon('thumb-tack') - if defined?(nav) && nav .layout-nav .container-fluid diff --git a/app/views/layouts/_search.html.haml b/app/views/layouts/_search.html.haml index 245b9c3b4d4..b49207fc315 100644 --- a/app/views/layouts/_search.html.haml +++ b/app/views/layouts/_search.html.haml @@ -36,31 +36,6 @@ - else = hidden_field_tag :search_code, true - :javascript - gl.projectOptions = gl.projectOptions || {}; - gl.projectOptions["#{j(@project.path)}"] = { - issuesPath: "#{namespace_project_issues_path(@project.namespace, @project)}", - mrPath: "#{namespace_project_merge_requests_path(@project.namespace, @project)}", - name: "#{j(@project.name)}" - }; - - - if @group and @group.path - :javascript - gl.groupOptions = gl.groupOptions || {}; - gl.groupOptions["#{j(@group.path)}"] = { - name: "#{j(@group.name)}", - issuesPath: "#{issues_group_path(j(@group.path))}", - mrPath: "#{merge_requests_group_path(j(@group.path))}" - }; - - - :javascript - gl.dashboardOptions = { - issuesPath: "#{issues_dashboard_url}", - mrPath: "#{merge_requests_dashboard_url}" - }; - - - if @snippet || @snippets = hidden_field_tag :snippets, true = hidden_field_tag :repository_ref, @ref diff --git a/app/views/layouts/admin.html.haml b/app/views/layouts/admin.html.haml index 87064cc9b3f..6591c52bdbd 100644 --- a/app/views/layouts/admin.html.haml +++ b/app/views/layouts/admin.html.haml @@ -1,5 +1,5 @@ - page_title "Admin Area" - header_title "Admin Area", admin_root_path -- nav "admin" +- sidebar "admin" = render template: "layouts/application" diff --git a/app/views/layouts/application.html.haml b/app/views/layouts/application.html.haml index 33cedaaf2ee..2b86b289bbe 100644 --- a/app/views/layouts/application.html.haml +++ b/app/views/layouts/application.html.haml @@ -1,7 +1,7 @@ !!! 5 %html{ lang: "en"} = render "layouts/head" - %body{class: "#{user_application_theme}", data: {page: body_data_page, project: "#{@project.path if @project}", group: "#{@group.path if @group}"}} + %body{class: "#{user_application_theme}", 'data-page' => body_data_page} = Gon::Base.render_data -# Ideally this would be inside the head, but turbolinks only evaluates page-specific JS in the body. diff --git a/app/views/layouts/header/_default.html.haml b/app/views/layouts/header/_default.html.haml index 40a2c81eebd..ad30a367fc5 100644 --- a/app/views/layouts/header/_default.html.haml +++ b/app/views/layouts/header/_default.html.haml @@ -1,4 +1,4 @@ -%header.navbar.navbar-fixed-top.navbar-gitlab{ class: nav_header_class } +%header.navbar.navbar-fixed-top.navbar-gitlab.header-collapsed{ class: nav_header_class } %div{ class: fluid_layout ? "container-fluid" : "container-fluid" } .header-content %button.side-nav-toggle{type: 'button'} @@ -27,8 +27,9 @@ %li = link_to dashboard_todos_path, title: 'Todos', data: {toggle: 'tooltip', placement: 'bottom', container: 'body'} do = icon('bell fw') - %span.badge.todos-pending-count{ class: ("hidden" if todos_pending_count == 0) } - = todos_pending_count + - unless todos_pending_count == 0 + %span.badge.todos-pending-count + = todos_pending_count - if current_user.can_create_project? %li = link_to new_project_path, title: 'New project', data: {toggle: 'tooltip', placement: 'bottom', container: 'body'} do @@ -50,7 +51,7 @@ %h1.title= title .header-logo - = link_to root_path, class: 'home', title: 'Dashboard', id: 'logo' do + #logo = brand_header_logo = yield :header_content diff --git a/app/views/layouts/nav/_admin.html.haml b/app/views/layouts/nav/_admin.html.haml index 54aa34bee0b..f292730fe45 100644 --- a/app/views/layouts/nav/_admin.html.haml +++ b/app/views/layouts/nav/_admin.html.haml @@ -1,64 +1,107 @@ -%ul.nav-links.scrolling-tabs - .fade-left - = nav_link(controller: %w(dashboard admin projects users groups builds), html_options: {class: 'home'}) do - = link_to admin_root_path, title: 'Overview', class: 'shortcuts-tree' do +%ul.nav.nav-sidebar + = nav_link(controller: :dashboard, html_options: {class: 'home'}) do + = link_to admin_root_path, title: 'Overview' do + = icon('dashboard fw') %span Overview - = nav_link(controller: %w(background_jobs logs health_check)) do - = link_to admin_background_jobs_path, title: 'Monitoring' do + = nav_link(controller: [:admin, :projects]) do + = link_to admin_namespaces_projects_path, title: 'Projects' do + = icon('cube fw') %span - Monitoring + Projects + = nav_link(controller: :users) do + = link_to admin_users_path, title: 'Users' do + = icon('user fw') + %span + Users + = nav_link(controller: :groups) do + = link_to admin_groups_path, title: 'Groups' do + = icon('group fw') + %span + Groups = nav_link(controller: :deploy_keys) do = link_to admin_deploy_keys_path, title: 'Deploy Keys' do + = icon('key fw') %span Deploy Keys = nav_link path: ['runners#index', 'runners#show'] do = link_to admin_runners_path, title: 'Runners' do + = icon('cog fw') %span Runners + %span.count= number_with_delimiter(Ci::Runner.count(:all)) + = nav_link path: 'builds#index' do + = link_to admin_builds_path, title: 'Builds' do + = icon('link fw') + %span + Builds + %span.count= number_with_delimiter(Ci::Build.count(:all)) + = nav_link(controller: :logs) do + = link_to admin_logs_path, title: 'Logs' do + = icon('file-text fw') + %span + Logs + = nav_link(controller: :health_check) do + = link_to admin_health_check_path, title: 'Health Check' do + = icon('medkit fw') + %span + Health Check = nav_link(controller: :broadcast_messages) do = link_to admin_broadcast_messages_path, title: 'Messages' do + = icon('bullhorn fw') %span Messages = nav_link(controller: :hooks) do = link_to admin_hooks_path, title: 'Hooks' do + = icon('external-link fw') %span Hooks - + = nav_link(controller: :background_jobs) do + = link_to admin_background_jobs_path, title: 'Background Jobs' do + = icon('cog fw') + %span + Background Jobs = nav_link(controller: :appearances) do = link_to admin_appearances_path, title: 'Appearances' do + = icon('image') %span Appearance = nav_link(controller: :applications) do = link_to admin_applications_path, title: 'Applications' do + = icon('cloud fw') %span Applications = nav_link(controller: :services) do = link_to admin_application_settings_services_path, title: 'Service Templates' do + = icon('copy fw') %span Service Templates = nav_link(controller: :labels) do = link_to admin_labels_path, title: 'Labels' do + = icon('tags fw') %span Labels = nav_link(controller: :abuse_reports) do = link_to admin_abuse_reports_path, title: "Abuse Reports" do + = icon('exclamation-circle fw') %span Abuse Reports - %span.badge.count= number_with_delimiter(AbuseReport.count(:all)) + %span.count= number_with_delimiter(AbuseReport.count(:all)) - if askimet_enabled? = nav_link(controller: :spam_logs) do = link_to admin_spam_logs_path, title: "Spam Logs" do + = icon('exclamation-triangle fw') %span Spam Logs + %span.count= number_with_delimiter(SpamLog.count(:all)) = nav_link(controller: :application_settings, html_options: { class: 'separate-item'}) do = link_to admin_application_settings_path, title: 'Settings' do + = icon('cogs fw') %span Settings - .fade-right diff --git a/app/views/layouts/nav/_dashboard.html.haml b/app/views/layouts/nav/_dashboard.html.haml index 52e41b1a857..18cae5bf87f 100644 --- a/app/views/layouts/nav/_dashboard.html.haml +++ b/app/views/layouts/nav/_dashboard.html.haml @@ -1,64 +1,54 @@ %ul.nav.nav-sidebar = nav_link(path: ['root#index', 'projects#trending', 'projects#starred', 'dashboard/projects#index'], html_options: {class: "#{project_tab_class} home"}) do = link_to dashboard_projects_path, title: 'Projects', class: 'dashboard-shortcuts-projects' do - .icon-container - = navbar_icon('project') + = navbar_icon('project') %span Projects = nav_link(controller: :todos) do = link_to dashboard_todos_path, title: 'Todos' do - .icon-container - = icon('bell fw') + = icon('bell fw') %span Todos %span.count= number_with_delimiter(todos_pending_count) = nav_link(path: 'dashboard#activity') do = link_to activity_dashboard_path, class: 'dashboard-shortcuts-activity', title: 'Activity' do - .icon-container - = navbar_icon('activity') + = navbar_icon('activity') %span Activity = nav_link(controller: [:groups, 'groups/milestones', 'groups/group_members']) do = link_to dashboard_groups_path, title: 'Groups' do - .icon-container - = navbar_icon('group') + = navbar_icon('group') %span Groups = nav_link(controller: 'dashboard/milestones') do = link_to dashboard_milestones_path, title: 'Milestones' do - .icon-container - = navbar_icon('milestones') + = navbar_icon('milestones') %span Milestones = nav_link(path: 'dashboard#issues') do = link_to assigned_issues_dashboard_path, title: 'Issues', class: 'dashboard-shortcuts-issues' do - .icon-container - = navbar_icon('issues') + = navbar_icon('issues') %span Issues %span.count= number_with_delimiter(current_user.assigned_issues.opened.count) = nav_link(path: 'dashboard#merge_requests') do = link_to assigned_mrs_dashboard_path, title: 'Merge Requests', class: 'dashboard-shortcuts-merge_requests' do - .icon-container - = navbar_icon('mr') + = navbar_icon('mr') %span Merge Requests %span.count= number_with_delimiter(current_user.assigned_merge_requests.opened.count) = nav_link(controller: :snippets) do = link_to dashboard_snippets_path, title: 'Snippets' do - .icon-container - = icon('clipboard fw') + = icon('clipboard fw') %span Snippets = nav_link(controller: :help) do = link_to help_path, title: 'Help' do - .icon-container - = icon('question-circle fw') + = icon('question-circle fw') %span Help = nav_link(html_options: {class: profile_tab_class}) do = link_to profile_path, title: 'Profile Settings', data: {placement: 'bottom'} do - .icon-container - = icon('user fw') + = icon('user fw') %span Profile Settings diff --git a/app/views/layouts/nav/_group_settings.html.haml b/app/views/layouts/nav/_group_settings.html.haml index dac46648b9f..0b2673f1a82 100644 --- a/app/views/layouts/nav/_group_settings.html.haml +++ b/app/views/layouts/nav/_group_settings.html.haml @@ -14,3 +14,7 @@ %li = link_to edit_group_path(@group) do Edit Group + %li + = link_to leave_group_group_members_path(@group), + data: { confirm: leave_group_message(@group.name) }, method: :delete, title: 'Leave group' do + Leave Group diff --git a/app/views/layouts/nav/_project.html.haml b/app/views/layouts/nav/_project.html.haml index a851cae4b56..53d1fcc30a6 100644 --- a/app/views/layouts/nav/_project.html.haml +++ b/app/views/layouts/nav/_project.html.haml @@ -1,26 +1,23 @@ - if current_user .controls + - access = user_max_access_in_project(current_user.id, @project) + - can_edit = can?(current_user, :admin_project, @project) .dropdown.project-settings-dropdown %a.dropdown-new.btn.btn-default#project-settings-button{href: '#', 'data-toggle' => 'dropdown'} = icon('cog') = icon('caret-down') %ul.dropdown-menu.dropdown-menu-align-right - - access = @project.team.max_member_access(current_user.id) - - can_edit = can?(current_user, :admin_project, @project) - - = render 'layouts/nav/project_settings', access: access, can_edit: can_edit - - - if can_edit || access - %li.divider - - if can_edit - %li - = link_to edit_project_path(@project) do - Edit Project - - if access - %li - = link_to polymorphic_path([:leave, @project, :members]), - data: { confirm: leave_confirmation_message(@project) }, method: :delete, title: 'Leave project' do - Leave Project + = render 'layouts/nav/project_settings' + %li.divider + - if can_edit + %li + = link_to edit_project_path(@project) do + Edit Project + - if access + %li + = link_to leave_namespace_project_project_members_path(@project.namespace, @project), + data: { confirm: leave_project_message(@project) }, method: :delete, title: 'Leave project' do + Leave Project %div{ class: nav_control_class } %ul.nav-links.scrolling-tabs @@ -42,7 +39,7 @@ Code - if project_nav_tab? :pipelines - = nav_link(controller: [:pipelines, :builds, :environments]) do + = nav_link(controller: :pipelines) do = link_to project_pipelines_path(@project), title: 'Pipelines', class: 'shortcuts-pipelines' do %span Pipelines diff --git a/app/views/layouts/nav/_project_settings.html.haml b/app/views/layouts/nav/_project_settings.html.haml index 13d32bd1354..885e78d38c6 100644 --- a/app/views/layouts/nav/_project_settings.html.haml +++ b/app/views/layouts/nav/_project_settings.html.haml @@ -3,43 +3,43 @@ = link_to namespace_project_project_members_path(@project.namespace, @project), title: 'Members', class: 'team-tab tab' do %span Members -- if access && can_edit - - if @project.allowed_to_share_with_group? - = nav_link(controller: :group_links) do - = link_to namespace_project_group_links_path(@project.namespace, @project), title: "Groups" do - %span - Groups - = nav_link(controller: :deploy_keys) do - = link_to namespace_project_deploy_keys_path(@project.namespace, @project), title: 'Deploy Keys' do + +- if @project.allowed_to_share_with_group? + = nav_link(controller: :group_links) do + = link_to namespace_project_group_links_path(@project.namespace, @project), title: "Groups" do + %span + Groups += nav_link(controller: :deploy_keys) do + = link_to namespace_project_deploy_keys_path(@project.namespace, @project), title: 'Deploy Keys' do + %span + Deploy Keys += nav_link(controller: :hooks) do + = link_to namespace_project_hooks_path(@project.namespace, @project), title: 'Webhooks' do + %span + Webhooks += nav_link(controller: :services) do + = link_to namespace_project_services_path(@project.namespace, @project), title: 'Services' do + %span + Services += nav_link(controller: :protected_branches) do + = link_to namespace_project_protected_branches_path(@project.namespace, @project), title: 'Protected Branches' do + %span + Protected Branches + +- if @project.builds_enabled? + = nav_link(controller: :runners) do + = link_to namespace_project_runners_path(@project.namespace, @project), title: 'Runners' do %span - Deploy Keys - = nav_link(controller: :hooks) do - = link_to namespace_project_hooks_path(@project.namespace, @project), title: 'Webhooks' do + Runners + = nav_link(controller: :variables) do + = link_to namespace_project_variables_path(@project.namespace, @project), title: 'Variables' do %span - Webhooks - = nav_link(controller: :services) do - = link_to namespace_project_services_path(@project.namespace, @project), title: 'Services' do + Variables + = nav_link(controller: :triggers) do + = link_to namespace_project_triggers_path(@project.namespace, @project), title: 'Triggers' do %span - Services - = nav_link(controller: :protected_branches) do - = link_to namespace_project_protected_branches_path(@project.namespace, @project), title: 'Protected Branches' do + Triggers + = nav_link(controller: :badges) do + = link_to namespace_project_badges_path(@project.namespace, @project), title: 'Badges' do %span - Protected Branches - - - if @project.builds_enabled? - = nav_link(controller: :runners) do - = link_to namespace_project_runners_path(@project.namespace, @project), title: 'Runners' do - %span - Runners - = nav_link(controller: :variables) do - = link_to namespace_project_variables_path(@project.namespace, @project), title: 'Variables' do - %span - Variables - = nav_link(controller: :triggers) do - = link_to namespace_project_triggers_path(@project.namespace, @project), title: 'Triggers' do - %span - Triggers - = nav_link(controller: :badges) do - = link_to namespace_project_badges_path(@project.namespace, @project), title: 'Badges' do - %span - Badges + Badges diff --git a/app/views/notify/group_access_granted_email.html.haml b/app/views/notify/group_access_granted_email.html.haml new file mode 100644 index 00000000000..f1916d624b6 --- /dev/null +++ b/app/views/notify/group_access_granted_email.html.haml @@ -0,0 +1,4 @@ +%p + = "You have been granted #{@group_member.human_access} access to group" + = link_to group_url(@group) do + = @group.name diff --git a/app/views/notify/group_access_granted_email.text.erb b/app/views/notify/group_access_granted_email.text.erb new file mode 100644 index 00000000000..ef9617bfc16 --- /dev/null +++ b/app/views/notify/group_access_granted_email.text.erb @@ -0,0 +1,4 @@ + +You have been granted <%= @group_member.human_access %> access to group <%= @group.name %> + +<%= url_for(group_url(@group)) %> diff --git a/app/views/notify/group_invite_accepted_email.html.haml b/app/views/notify/group_invite_accepted_email.html.haml new file mode 100644 index 00000000000..55efad384a7 --- /dev/null +++ b/app/views/notify/group_invite_accepted_email.html.haml @@ -0,0 +1,6 @@ +%p + #{@group_member.invite_email}, now known as + #{link_to @group_member.user.name, user_url(@group_member.user)}, + has accepted your invitation to join group + #{link_to @group.name, group_url(@group)}. + diff --git a/app/views/notify/group_invite_accepted_email.text.erb b/app/views/notify/group_invite_accepted_email.text.erb new file mode 100644 index 00000000000..f8b70f7a5a6 --- /dev/null +++ b/app/views/notify/group_invite_accepted_email.text.erb @@ -0,0 +1,3 @@ +<%= @group_member.invite_email %>, now known as <%= @group_member.user.name %>, has accepted your invitation to join group <%= @group.name %>. + +<%= group_url(@group) %> diff --git a/app/views/notify/group_invite_declined_email.html.haml b/app/views/notify/group_invite_declined_email.html.haml new file mode 100644 index 00000000000..f9525d84fac --- /dev/null +++ b/app/views/notify/group_invite_declined_email.html.haml @@ -0,0 +1,5 @@ +%p + #{@invite_email} + has declined your invitation to join group + #{link_to @group.name, group_url(@group)}. + diff --git a/app/views/notify/group_invite_declined_email.text.erb b/app/views/notify/group_invite_declined_email.text.erb new file mode 100644 index 00000000000..6c19a288d15 --- /dev/null +++ b/app/views/notify/group_invite_declined_email.text.erb @@ -0,0 +1,3 @@ +<%= @invite_email %> has declined your invitation to join group <%= @group.name %>. + +<%= group_url(@group) %> diff --git a/app/views/notify/group_member_invited_email.html.haml b/app/views/notify/group_member_invited_email.html.haml new file mode 100644 index 00000000000..163e88bfea3 --- /dev/null +++ b/app/views/notify/group_member_invited_email.html.haml @@ -0,0 +1,14 @@ +%p + You have been invited + - if inviter = @group_member.created_by + by + = link_to inviter.name, user_url(inviter) + to join group + = link_to @group.name, group_url(@group) + as #{@group_member.human_access}. + +%p + = link_to 'Accept invitation', invite_url(@token) + or + = link_to 'decline', decline_invite_url(@token) + diff --git a/app/views/notify/group_member_invited_email.text.erb b/app/views/notify/group_member_invited_email.text.erb new file mode 100644 index 00000000000..28ce4819b14 --- /dev/null +++ b/app/views/notify/group_member_invited_email.text.erb @@ -0,0 +1,4 @@ +You have been invited <%= "by #{@group_member.created_by.name} " if @group_member.created_by %>to join group <%= @group.name %> as <%= @group_member.human_access %>. + +Accept invitation: <%= invite_url(@token) %> +Decline invitation: <%= decline_invite_url(@token) %> diff --git a/app/views/notify/member_access_denied_email.html.haml b/app/views/notify/member_access_denied_email.html.haml deleted file mode 100644 index 71c9c50071a..00000000000 --- a/app/views/notify/member_access_denied_email.html.haml +++ /dev/null @@ -1,4 +0,0 @@ -%p - Your request to join the - #{link_to member_source.human_name, member_source.web_url} #{member_source.model_name.singular} - has been denied. diff --git a/app/views/notify/member_access_denied_email.text.erb b/app/views/notify/member_access_denied_email.text.erb deleted file mode 100644 index 87f2ef817ee..00000000000 --- a/app/views/notify/member_access_denied_email.text.erb +++ /dev/null @@ -1,3 +0,0 @@ -Your request to join the <%= member_source.human_name %> <%= member_source.model_name.singular %> has been denied. - -<%= member_source.web_url %> diff --git a/app/views/notify/member_access_granted_email.html.haml b/app/views/notify/member_access_granted_email.html.haml deleted file mode 100644 index 18dec806539..00000000000 --- a/app/views/notify/member_access_granted_email.html.haml +++ /dev/null @@ -1,3 +0,0 @@ -%p - You have been granted #{member.human_access} access to the - #{link_to member_source.human_name, member_source.web_url} #{member_source.model_name.singular}. diff --git a/app/views/notify/member_access_granted_email.text.erb b/app/views/notify/member_access_granted_email.text.erb deleted file mode 100644 index a9fb3a589a5..00000000000 --- a/app/views/notify/member_access_granted_email.text.erb +++ /dev/null @@ -1,3 +0,0 @@ -You have been granted <%= member.human_access %> access to the <%= member_source.human_name %> <%= member_source.model_name.singular %>. - -<%= member_source.web_url %> diff --git a/app/views/notify/member_access_requested_email.html.haml b/app/views/notify/member_access_requested_email.html.haml deleted file mode 100644 index 76f1f08a0cb..00000000000 --- a/app/views/notify/member_access_requested_email.html.haml +++ /dev/null @@ -1,3 +0,0 @@ -%p - #{link_to member.user.name, member.user} requested #{member.human_access} - access to the #{link_to member_source.human_name, polymorphic_url([member_source, :members])} #{member_source.model_name.singular}. diff --git a/app/views/notify/member_access_requested_email.text.erb b/app/views/notify/member_access_requested_email.text.erb deleted file mode 100644 index 9c5ee0eaf26..00000000000 --- a/app/views/notify/member_access_requested_email.text.erb +++ /dev/null @@ -1,3 +0,0 @@ -<%= member.user.name %> (<%= user_url(member.user) %>) requested <%= member.human_access %> access to the <%= member_source.human_name %> <%= member_source.model_name.singular %>. - -<%= polymorphic_url([member_source, :members]) %> diff --git a/app/views/notify/member_invite_accepted_email.html.haml b/app/views/notify/member_invite_accepted_email.html.haml deleted file mode 100644 index 2d1d40881eb..00000000000 --- a/app/views/notify/member_invite_accepted_email.html.haml +++ /dev/null @@ -1,5 +0,0 @@ -%p - #{member.invite_email}, now known as - #{link_to member.user.name, user_url(member.user)}, - has accepted your invitation to join the - #{link_to member_source.human_name, member_source.web_url} #{member_source.model_name.singular}. diff --git a/app/views/notify/member_invite_accepted_email.text.erb b/app/views/notify/member_invite_accepted_email.text.erb deleted file mode 100644 index cef87101427..00000000000 --- a/app/views/notify/member_invite_accepted_email.text.erb +++ /dev/null @@ -1,3 +0,0 @@ -<%= member.invite_email %>, now known as <%= member.user.name %>, has accepted your invitation to join the <%= member_source.human_name %> <%= member_source.model_name.singular %>. - -<%= member_source.web_url %> diff --git a/app/views/notify/member_invite_declined_email.html.haml b/app/views/notify/member_invite_declined_email.html.haml deleted file mode 100644 index aa1b373d1a6..00000000000 --- a/app/views/notify/member_invite_declined_email.html.haml +++ /dev/null @@ -1,4 +0,0 @@ -%p - #{@invite_email} - has declined your invitation to join the - #{link_to member_source.human_name, member_source.web_url} #{member_source.model_name.singular}. diff --git a/app/views/notify/member_invite_declined_email.text.erb b/app/views/notify/member_invite_declined_email.text.erb deleted file mode 100644 index 8bc305910c4..00000000000 --- a/app/views/notify/member_invite_declined_email.text.erb +++ /dev/null @@ -1,3 +0,0 @@ -<%= @invite_email %> has declined your invitation to join the <%= member_source.human_name %> <%= member_source.model_name.singular %>. - -<%= member_source.web_url %> diff --git a/app/views/notify/member_invited_email.html.haml b/app/views/notify/member_invited_email.html.haml deleted file mode 100644 index b8b75da3f2f..00000000000 --- a/app/views/notify/member_invited_email.html.haml +++ /dev/null @@ -1,13 +0,0 @@ -%p - You have been invited - - if member.created_by - by - = link_to member.created_by.name, user_url(member.created_by) - to join the - = link_to member_source.human_name, member_source.web_url - #{member_source.model_name.singular} as #{member.human_access}. - -%p - = link_to 'Accept invitation', invite_url(@token) - or - = link_to 'decline', decline_invite_url(@token) diff --git a/app/views/notify/member_invited_email.text.erb b/app/views/notify/member_invited_email.text.erb deleted file mode 100644 index 0a6393355be..00000000000 --- a/app/views/notify/member_invited_email.text.erb +++ /dev/null @@ -1,4 +0,0 @@ -You have been invited <%= "by #{member.created_by.name} " if member.created_by %>to join the <%= member_source.human_name %> <%= member_source.model_name.singular %> as <%= member.human_access %>. - -Accept invitation: <%= invite_url(@token) %> -Decline invitation: <%= decline_invite_url(@token) %> diff --git a/app/views/notify/project_access_granted_email.html.haml b/app/views/notify/project_access_granted_email.html.haml new file mode 100644 index 00000000000..dfc30a2d360 --- /dev/null +++ b/app/views/notify/project_access_granted_email.html.haml @@ -0,0 +1,5 @@ +%p + = "You have been granted #{@project_member.human_access} access to project" +%p + = link_to namespace_project_url(@project.namespace, @project) do + = @project.name_with_namespace diff --git a/app/views/notify/project_access_granted_email.text.erb b/app/views/notify/project_access_granted_email.text.erb new file mode 100644 index 00000000000..68eb1611ba7 --- /dev/null +++ b/app/views/notify/project_access_granted_email.text.erb @@ -0,0 +1,4 @@ + +You have been granted <%= @project_member.human_access %> access to project <%= @project.name_with_namespace %> + +<%= url_for(namespace_project_url(@project.namespace, @project)) %> diff --git a/app/views/notify/project_invite_accepted_email.html.haml b/app/views/notify/project_invite_accepted_email.html.haml new file mode 100644 index 00000000000..7e58d30b10a --- /dev/null +++ b/app/views/notify/project_invite_accepted_email.html.haml @@ -0,0 +1,6 @@ +%p + #{@project_member.invite_email}, now known as + #{link_to @project_member.user.name, user_url(@project_member.user)}, + has accepted your invitation to join project + #{link_to @project.name_with_namespace, namespace_project_url(@project.namespace, @project)}. + diff --git a/app/views/notify/project_invite_accepted_email.text.erb b/app/views/notify/project_invite_accepted_email.text.erb new file mode 100644 index 00000000000..fcbe752114d --- /dev/null +++ b/app/views/notify/project_invite_accepted_email.text.erb @@ -0,0 +1,3 @@ +<%= @project_member.invite_email %>, now known as <%= @project_member.user.name %>, has accepted your invitation to join project <%= @project.name_with_namespace %>. + +<%= namespace_project_url(@project.namespace, @project) %> diff --git a/app/views/notify/project_invite_declined_email.html.haml b/app/views/notify/project_invite_declined_email.html.haml new file mode 100644 index 00000000000..c2d7e6f6e3a --- /dev/null +++ b/app/views/notify/project_invite_declined_email.html.haml @@ -0,0 +1,5 @@ +%p + #{@invite_email} + has declined your invitation to join project + #{link_to @project.name_with_namespace, namespace_project_url(@project.namespace, @project)}. + diff --git a/app/views/notify/project_invite_declined_email.text.erb b/app/views/notify/project_invite_declined_email.text.erb new file mode 100644 index 00000000000..484687fa51c --- /dev/null +++ b/app/views/notify/project_invite_declined_email.text.erb @@ -0,0 +1,3 @@ +<%= @invite_email %> has declined your invitation to join project <%= @project.name_with_namespace %>. + +<%= namespace_project_url(@project.namespace, @project) %> diff --git a/app/views/notify/project_member_invited_email.html.haml b/app/views/notify/project_member_invited_email.html.haml new file mode 100644 index 00000000000..79eb89616de --- /dev/null +++ b/app/views/notify/project_member_invited_email.html.haml @@ -0,0 +1,13 @@ +%p + You have been invited + - if inviter = @project_member.created_by + by + = link_to inviter.name, user_url(inviter) + to join project + = link_to @project.name_with_namespace, namespace_project_url(@project.namespace, @project) + as #{@project_member.human_access}. + +%p + = link_to 'Accept invitation', invite_url(@token) + or + = link_to 'decline', decline_invite_url(@token) diff --git a/app/views/notify/project_member_invited_email.text.erb b/app/views/notify/project_member_invited_email.text.erb new file mode 100644 index 00000000000..e0706272115 --- /dev/null +++ b/app/views/notify/project_member_invited_email.text.erb @@ -0,0 +1,4 @@ +You have been invited <%= "by #{@project_member.created_by.name} " if @project_member.created_by %>to join project <%= @project.name_with_namespace %> as <%= @project_member.human_access %>. + +Accept invitation: <%= invite_url(@token) %> +Decline invitation: <%= decline_invite_url(@token) %> diff --git a/app/views/profiles/notifications/_group_settings.html.haml b/app/views/profiles/notifications/_group_settings.html.haml index f0cf82afe83..89ae7ffda2b 100644 --- a/app/views/profiles/notifications/_group_settings.html.haml +++ b/app/views/profiles/notifications/_group_settings.html.haml @@ -1,7 +1,7 @@ %li.notification-list-item %span.notification.fa.fa-holder.append-right-5 - if setting.global? - = notification_icon(current_user.global_notification_setting.level) + = notification_icon(current_user.notification_level) - else = notification_icon(setting.level) diff --git a/app/views/profiles/notifications/_project_settings.html.haml b/app/views/profiles/notifications/_project_settings.html.haml index e0fad555c09..17c097154da 100644 --- a/app/views/profiles/notifications/_project_settings.html.haml +++ b/app/views/profiles/notifications/_project_settings.html.haml @@ -1,7 +1,7 @@ %li.notification-list-item %span.notification.fa.fa-holder.append-right-5 - if setting.global? - = notification_icon(current_user.global_notification_setting.level) + = notification_icon(current_user.notification_level) - else = notification_icon(setting.level) diff --git a/app/views/profiles/notifications/show.html.haml b/app/views/profiles/notifications/show.html.haml index f2659ac14b5..7696f112bb3 100644 --- a/app/views/profiles/notifications/show.html.haml +++ b/app/views/profiles/notifications/show.html.haml @@ -26,7 +26,33 @@ = f.select :notification_email, @user.all_emails, { include_blank: false }, class: "select2" .form-group = f.label :notification_level, class: 'label-light' - = notification_level_radio_buttons + .radio + = f.label :notification_level, value: :disabled do + = f.radio_button :notification_level, :disabled + .level-title + Disabled + %p You will not get any notifications via email + + .radio + = f.label :notification_level, value: :mention do + = f.radio_button :notification_level, :mention + .level-title + On Mention + %p You will receive notifications only for comments in which you were @mentioned + + .radio + = f.label :notification_level, value: :participating do + = f.radio_button :notification_level, :participating + .level-title + Participating + %p You will only receive notifications from related resources (e.g. from your commits or assigned issues) + + .radio + = f.label :notification_level, value: :watch do + = f.radio_button :notification_level, :watch + .level-title + Watch + %p You will receive notifications for any activity .prepend-top-default = f.submit 'Update settings', class: "btn btn-create" diff --git a/app/views/profiles/two_factor_auths/show.html.haml b/app/views/profiles/two_factor_auths/show.html.haml index 593be2617c1..ce76cb73c9c 100644 --- a/app/views/profiles/two_factor_auths/show.html.haml +++ b/app/views/profiles/two_factor_auths/show.html.haml @@ -51,9 +51,9 @@ %p Use a hardware device to add the second factor of authentication. %p - As U2F devices are only supported by a few browsers, we require that you set up a - two-factor authentication app before a U2F device. That way you'll always be able to - log in - even when you're using an unsupported browser. + As U2F devices are only supported by a few browsers, it's recommended that you set up a + two-factor authentication app as well as a U2F device so you'll always be able to log in + using an unsupported browser. .col-lg-9 %p - if @registration_key_handles.present? diff --git a/app/views/projects/_home_panel.html.haml b/app/views/projects/_home_panel.html.haml index 2b19ee93eea..f5bc1b4e409 100644 --- a/app/views/projects/_home_panel.html.haml +++ b/app/views/projects/_home_panel.html.haml @@ -29,13 +29,10 @@ .project-clone-holder = render "shared/clone_panel" - .project-repo-buttons.project-right-buttons - - if current_user - = render 'shared/members/access_request_buttons', source: @project - .btn-group - = render "projects/buttons/download" - = render 'projects/buttons/dropdown' - = render 'projects/buttons/notifications' + .project-repo-buttons.btn-group.project-right-buttons + = render "projects/buttons/download" + = render 'projects/buttons/dropdown' + = render 'projects/buttons/notifications' :javascript new Star(); diff --git a/app/views/projects/builds/_sidebar.html.haml b/app/views/projects/builds/_sidebar.html.haml index cab21f0cf19..5d931389dfb 100644 --- a/app/views/projects/builds/_sidebar.html.haml +++ b/app/views/projects/builds/_sidebar.html.haml @@ -11,33 +11,19 @@ %p.build-detail-row #{@build.coverage}% - - if can?(current_user, :read_build, @project) && (@build.artifacts? || @build.artifacts_expired?) + - if can?(current_user, :read_build, @project) && @build.artifacts? .block{ class: ("block-first" if !@build.coverage) } .title Build artifacts - - if @build.artifacts_expired? - %p.build-detail-row - The artifacts were removed - #{time_ago_with_tooltip(@build.artifacts_expire_at)} - - elsif @build.artifacts_expire_at - %p.build-detail-row - The artifacts will be removed in - %span.js-artifacts-remove= @build.artifacts_expire_at + .btn-group.btn-group-justified{ role: :group } + = link_to download_namespace_project_build_artifacts_path(@project.namespace, @project, @build), class: 'btn btn-sm btn-default' do + Download - - if @build.artifacts? - .btn-group.btn-group-justified{ role: :group } - - if @build.artifacts_expire_at - = link_to keep_namespace_project_build_artifacts_path(@project.namespace, @project, @build), class: 'btn btn-sm btn-default', method: :post do - Keep + - if @build.artifacts_metadata? + = link_to browse_namespace_project_build_artifacts_path(@project.namespace, @project, @build), class: 'btn btn-sm btn-default' do + Browse - = link_to download_namespace_project_build_artifacts_path(@project.namespace, @project, @build), class: 'btn btn-sm btn-default' do - Download - - - if @build.artifacts_metadata? - = link_to browse_namespace_project_build_artifacts_path(@project.namespace, @project, @build), class: 'btn btn-sm btn-default' do - Browse - - .block{ class: ("block-first" if !@build.coverage && !(can?(current_user, :read_build, @project) && (@build.artifacts? || @build.artifacts_expired?))) } + .block{ class: ("block-first" if !@build.coverage && !(can?(current_user, :read_build, @project) && @build.artifacts?)) } .title Build details - if @build.retryable? diff --git a/app/views/projects/buttons/_notifications.html.haml b/app/views/projects/buttons/_notifications.html.haml index a7a97181096..3b97dc9328f 100644 --- a/app/views/projects/buttons/_notifications.html.haml +++ b/app/views/projects/buttons/_notifications.html.haml @@ -1,7 +1,7 @@ - if @notification_setting = form_for @notification_setting, url: namespace_project_notification_setting_path(@project.namespace.becomes(Namespace), @project), method: :patch, remote: true, html: { class: 'inline', id: 'notification-form' } do |f| = f.hidden_field :level - .dropdown.hidden-sm + .dropdown %button.btn.btn-default.notifications-btn#notifications-button{ data: { toggle: "dropdown" }, aria: { haspopup: "true", expanded: "false" } } = icon('bell') = notification_title(@notification_setting.level) diff --git a/app/views/projects/buttons/_star.html.haml b/app/views/projects/buttons/_star.html.haml index 71cf5582a4c..02dbb2985a4 100644 --- a/app/views/projects/buttons/_star.html.haml +++ b/app/views/projects/buttons/_star.html.haml @@ -1,5 +1,5 @@ - if current_user - = link_to toggle_star_namespace_project_path(@project.namespace, @project), { class: 'btn star-btn toggle-star has-tooltip', method: :post, remote: true, title: current_user.starred?(@project) ? 'Unstar project' : 'Star project' } do + = link_to toggle_star_namespace_project_path(@project.namespace, @project), class: 'btn star-btn toggle-star has-tooltip', method: :post, remote: true, title: "Star project" do - if current_user.starred?(@project) = icon('star fw') %span.starred Unstar diff --git a/app/views/projects/ci/pipelines/_pipeline.html.haml b/app/views/projects/ci/pipelines/_pipeline.html.haml index b8d8758fd2b..a0ffa065067 100644 --- a/app/views/projects/ci/pipelines/_pipeline.html.haml +++ b/app/views/projects/ci/pipelines/_pipeline.html.haml @@ -60,7 +60,7 @@ %li = link_to download_namespace_project_build_artifacts_path(@project.namespace, @project, build), rel: 'nofollow' do = icon("download") - %span Download '#{build.name}' artifacts + %span #{build.name} - if can?(current_user, :update_pipeline, @project) - if pipeline.retryable? diff --git a/app/views/projects/commit/_change.html.haml b/app/views/projects/commit/_change.html.haml index d9b800a4ded..44ef1fdbbe3 100644 --- a/app/views/projects/commit/_change.html.haml +++ b/app/views/projects/commit/_change.html.haml @@ -17,7 +17,7 @@ .form-group.branch = label_tag 'target_branch', target_label, class: 'control-label' .col-sm-10 - = select_tag "target_branch", project_branches, class: "select2 select2-sm js-target-branch" + = select_tag "target_branch", grouped_options_refs, class: "select2 select2-sm js-target-branch" - if can?(current_user, :push_code, @project) .js-create-merge-request-container .checkbox diff --git a/app/views/projects/commits/_commit.html.haml b/app/views/projects/commits/_commit.html.haml index a959b34a539..367027182b6 100644 --- a/app/views/projects/commits/_commit.html.haml +++ b/app/views/projects/commits/_commit.html.haml @@ -9,30 +9,26 @@ = cache(cache_key) do %li.commit.js-toggle-container{ id: "commit-#{commit.short_id}" } - = commit_author_avatar(commit, size: 36) .commit-row-title %span.item-title = link_to_gfm commit.title, namespace_project_commit_path(project.namespace, project, commit.id), class: "commit-row-message" - %span.commit-row-message.visible-xs-inline - · - = commit.short_id - - if commit.status - = render_commit_status(commit, cssclass: 'visible-xs-inline') - if commit.description? - %a.text-expander.hidden-xs.js-toggle-button ... + %a.text-expander.js-toggle-button ... - .commit-actions.hidden-xs + .pull-right - if commit.status - = render_commit_status(commit, cssclass: 'btn btn-transparent') - = clipboard_button_with_class({ clipboard_text: commit.id }, css_class: 'btn-transparent') - = link_to commit.short_id, namespace_project_commit_path(project.namespace, project, commit), class: "commit-short-id btn btn-transparent" - = link_to_browse_code(project, commit) + = render_commit_status(commit) + = clipboard_button(clipboard_text: commit.id) + = link_to commit.short_id, namespace_project_commit_path(project.namespace, project, commit), class: "commit_short_id" - if commit.description? - %pre.commit-row-description.js-toggle-content - = preserve(markdown(escape_once(commit.description), pipeline: :single_line, author: commit.author)) + .commit-row-description.js-toggle-content + %pre + = preserve(markdown(escape_once(commit.description), pipeline: :single_line, author: commit.author)) .commit-row-info - = commit_author_link(commit, avatar: false, size: 24) - authored - #{time_ago_with_tooltip(commit.committed_date)} + by + = commit_author_link(commit, avatar: true, size: 24) + .committed_ago + #{time_ago_with_tooltip(commit.committed_date)} + = link_to_browse_code(project, commit) diff --git a/app/views/projects/commits/_commits.html.haml b/app/views/projects/commits/_commits.html.haml index dd12eae8f7e..7283a78a64e 100644 --- a/app/views/projects/commits/_commits.html.haml +++ b/app/views/projects/commits/_commits.html.haml @@ -4,11 +4,18 @@ - commits, hidden = limited_commits(@commits) - commits.chunk { |c| c.committed_date.in_time_zone.to_date }.each do |day, commits| - %li.commit-header= "#{day.strftime('%d %b, %Y')} #{pluralize(commits.count, 'commit')}" - %li.commits-row - %ul.list-unstyled.commit-list - = render commits, project: project + .row.commits-row + .col-md-2.hidden-xs.hidden-sm + %h5.commits-row-date + %i.fa.fa-calendar + %span= day.strftime('%d %b, %Y') + .light + = pluralize(commits.count, 'commit') + .col-md-10.col-sm-12 + %ul.content-list + = render commits, project: project + %hr.lists-separator - if hidden > 0 - %li.alert.alert-warning + .alert.alert-warning #{number_with_delimiter(hidden)} additional commits have been omitted to prevent performance issues. diff --git a/app/views/projects/commits/_head.html.haml b/app/views/projects/commits/_head.html.haml index c8aa849c217..a72e8ba73ad 100644 --- a/app/views/projects/commits/_head.html.haml +++ b/app/views/projects/commits/_head.html.haml @@ -1,6 +1,6 @@ .scrolling-tabs-container - .nav-links.sub-nav.scrolling-tabs - %ul{ class: (container_class) } + %ul.nav-links.sub-nav.scrolling-tabs + %div{ class: (container_class) } .fade-left = nav_link(controller: %w(tree blob blame edit_tree new_tree find_file)) do = link_to project_files_path(@project) do diff --git a/app/views/projects/commits/show.html.haml b/app/views/projects/commits/show.html.haml index 51ca4eb903e..76ba0bea36d 100644 --- a/app/views/projects/commits/show.html.haml +++ b/app/views/projects/commits/show.html.haml @@ -23,18 +23,21 @@ Create Merge Request .control - = form_tag(namespace_project_commits_path(@project.namespace, @project, @id), method: :get, class: 'commits-search-form') do - = search_field_tag :search, params[:search], { placeholder: 'Filter by commit message', id: 'commits-search', class: 'form-control search-text-input input-short', spellcheck: false } + = form_tag(namespace_project_commits_path(@project.namespace, @project, @id), method: :get, class: 'pull-left commits-search-form') do + = search_field_tag :search, params[:search], { placeholder: 'Filter by commit message', id: 'commits-search', class: 'form-control search-text-input', spellcheck: false } + - if current_user && current_user.private_token .control = link_to namespace_project_commits_path(@project.namespace, @project, @ref, {format: :atom, private_token: current_user.private_token}), title: "Commits Feed", class: 'btn' do = icon("rss") + + %ul.breadcrumb.repo-breadcrumb = commits_breadcrumbs %div{id: dom_id(@project)} - %ol#commits-list.list-unstyled.content_list - = render "commits", project: @project + #commits-list.content_list= render "commits", project: @project + .clear = spinner :javascript diff --git a/app/views/projects/container_registry/_tag.html.haml b/app/views/projects/container_registry/_tag.html.haml index f35faa6afb5..4e9f936539b 100644 --- a/app/views/projects/container_registry/_tag.html.haml +++ b/app/views/projects/container_registry/_tag.html.haml @@ -9,19 +9,11 @@ - else \- %td - - if tag.total_size - = number_to_human_size(tag.total_size) - · - = pluralize(tag.layers.size, "layer") - - else - .light - \- + = number_to_human_size(tag.total_size) + · + = pluralize(tag.layers.size, "layer") %td - - if tag.created_at - = time_ago_in_words(tag.created_at) - - else - .light - \- + = time_ago_in_words(tag.created_at) - if can?(current_user, :update_container_image, @project) %td.content .controls.hidden-xs.pull-right diff --git a/app/views/projects/deployments/_commit.html.haml b/app/views/projects/deployments/_commit.html.haml deleted file mode 100644 index 0f9d9512d88..00000000000 --- a/app/views/projects/deployments/_commit.html.haml +++ /dev/null @@ -1,12 +0,0 @@ -%div.branch-commit - - if deployment.ref - = link_to deployment.ref, namespace_project_commits_path(@project.namespace, @project, deployment.ref), class: "monospace" - · - = link_to deployment.short_sha, namespace_project_commit_path(@project.namespace, @project, deployment.sha), class: "commit-id monospace" - - %p.commit-title - %span - - if commit_title = deployment.commit_title - = link_to_gfm commit_title, namespace_project_commit_path(@project.namespace, @project, deployment.sha), class: "commit-row-message" - - else - Cant find HEAD commit for this branch diff --git a/app/views/projects/deployments/_deployment.html.haml b/app/views/projects/deployments/_deployment.html.haml deleted file mode 100644 index d08dd92f1f6..00000000000 --- a/app/views/projects/deployments/_deployment.html.haml +++ /dev/null @@ -1,23 +0,0 @@ -%tr.deployment - %td - %strong= "##{deployment.iid}" - - %td - = render 'projects/deployments/commit', deployment: deployment - - %td - - if deployment.deployable - = link_to namespace_project_build_path(@project.namespace, @project, deployment.deployable) do - = "#{deployment.deployable.name} (##{deployment.deployable.id})" - - %td - #{time_ago_with_tooltip(deployment.created_at)} - - %td - - if can?(current_user, :create_deployment, deployment) && deployment.deployable - .pull-right - = link_to retry_namespace_project_build_path(@project.namespace, @project, deployment.deployable), method: :post, class: 'btn btn-build' do - - if deployment.last? - Retry - - else - Rollback diff --git a/app/views/projects/diffs/_diffs.html.haml b/app/views/projects/diffs/_diffs.html.haml index f18bc8c41b3..d9c4b410d32 100644 --- a/app/views/projects/diffs/_diffs.html.haml +++ b/app/views/projects/diffs/_diffs.html.haml @@ -11,8 +11,6 @@ = commit_diff_whitespace_link(@project, @commit, class: 'hidden-xs') - elsif current_controller?(:merge_requests) = diff_merge_request_whitespace_link(@project, @merge_request, class: 'hidden-xs') - - elsif current_controller?(:compare) - = diff_compare_whitespace_link(@project, params[:from], params[:to], class: 'hidden-xs') .btn-group = inline_diff_btn = parallel_diff_btn @@ -26,7 +24,6 @@ - diff_commit = commit_for_diff(diff_file) - blob = project.repository.blob_for_diff(diff_commit, diff_file) - next unless blob - - blob.load_all_data!(project.repository) unless blob.only_display_raw? = render 'projects/diffs/file', i: index, project: project, diff_file: diff_file, diff_commit: diff_commit, blob: blob, diff_refs: diff_refs diff --git a/app/views/projects/diffs/_file.html.haml b/app/views/projects/diffs/_file.html.haml index 2395ea3c275..e5983c58039 100644 --- a/app/views/projects/diffs/_file.html.haml +++ b/app/views/projects/diffs/_file.html.haml @@ -49,8 +49,6 @@ = render "projects/diffs/parallel_view", diff_file: diff_file, project: project, blob: blob, index: i - else = render "projects/diffs/text_file", diff_file: diff_file, index: i - - elsif blob.only_display_raw? - .nothing-here-block This file is too large to display. - elsif blob.image? - old_file = project.repository.prev_blob_for_diff(diff_commit, diff_file) = render "projects/diffs/image", diff_file: diff_file, old_file: old_file, file: blob, index: i, diff_refs: diff_refs diff --git a/app/views/projects/environments/_environment.html.haml b/app/views/projects/environments/_environment.html.haml deleted file mode 100644 index eafa246d05f..00000000000 --- a/app/views/projects/environments/_environment.html.haml +++ /dev/null @@ -1,17 +0,0 @@ -- last_deployment = environment.last_deployment - -%tr.environment - %td - %strong - = link_to environment.name, namespace_project_environment_path(@project.namespace, @project, environment) - - %td - - if last_deployment - = render 'projects/deployments/commit', deployment: last_deployment - - else - %p.commit-title - No deployments yet - - %td - - if last_deployment - #{time_ago_with_tooltip(last_deployment.created_at)} diff --git a/app/views/projects/environments/_form.html.haml b/app/views/projects/environments/_form.html.haml deleted file mode 100644 index c07f4bd510c..00000000000 --- a/app/views/projects/environments/_form.html.haml +++ /dev/null @@ -1,7 +0,0 @@ -= form_for @environment, url: namespace_project_environments_path(@project.namespace, @project), html: { class: 'col-lg-9' } do |f| - = form_errors(@environment) - .form-group - = f.label :name, 'Name', class: 'label-light' - = f.text_field :name, required: true, class: 'form-control' - = f.submit 'Create environment', class: 'btn btn-create' - = link_to 'Cancel', namespace_project_environments_path(@project.namespace, @project), class: 'btn btn-cancel' diff --git a/app/views/projects/environments/_header_title.html.haml b/app/views/projects/environments/_header_title.html.haml deleted file mode 100644 index e056fccad5d..00000000000 --- a/app/views/projects/environments/_header_title.html.haml +++ /dev/null @@ -1 +0,0 @@ -- header_title project_title(@project, "Environments", project_environments_path(@project)) diff --git a/app/views/projects/environments/index.html.haml b/app/views/projects/environments/index.html.haml deleted file mode 100644 index ae9e77e7d89..00000000000 --- a/app/views/projects/environments/index.html.haml +++ /dev/null @@ -1,23 +0,0 @@ -- @no_container = true -- page_title "Environments" -= render "projects/pipelines/head" - -%div{ class: (container_class) } - - if can?(current_user, :create_environment, @project) - .top-area - .nav-controls - = link_to new_namespace_project_environment_path(@project.namespace, @project), class: 'btn btn-create' do - New environment - - - if @environments.blank? - %ul.content-list.environments - %li.nothing-here-block - No environments to show - - else - .table-holder - %table.table.environments - %tbody - %th Environment - %th Last deployment - %th Date - = render @environments diff --git a/app/views/projects/environments/new.html.haml b/app/views/projects/environments/new.html.haml deleted file mode 100644 index 54465828ba9..00000000000 --- a/app/views/projects/environments/new.html.haml +++ /dev/null @@ -1,9 +0,0 @@ -- page_title 'New Environment' - -.row.prepend-top-default.append-bottom-default - .col-lg-3 - %h4.prepend-top-0 - New Environment - %p Environments allow you to track deployments of your application - - = render 'form' diff --git a/app/views/projects/environments/show.html.haml b/app/views/projects/environments/show.html.haml deleted file mode 100644 index 069b77b5adf..00000000000 --- a/app/views/projects/environments/show.html.haml +++ /dev/null @@ -1,33 +0,0 @@ -- @no_container = true -- page_title "Environments" -= render "projects/pipelines/head" - -%div{ class: (container_class) } - .top-area - .col-md-9 - %h3.page-title= @environment.name.titleize - - .col-md-3 - .nav-controls - - if can?(current_user, :update_environment, @environment) - = link_to 'Destroy', namespace_project_environment_path(@project.namespace, @project, @environment), data: { confirm: 'Are you sure you want to delete this environment?' }, class: 'btn btn-danger', method: :delete - - - if @deployments.blank? - %ul.content-list.environments - %li.nothing-here-block - No deployments for - %strong= @environment.name - - else - .table-holder - %table.table.environments - %thead - %tr - %th ID - %th Commit - %th Build - %th Date - %th - - = render @deployments - - = paginate @deployments, theme: 'gitlab' diff --git a/app/views/projects/issues/_head.html.haml b/app/views/projects/issues/_head.html.haml index 403adb7426b..166dae248b6 100644 --- a/app/views/projects/issues/_head.html.haml +++ b/app/views/projects/issues/_head.html.haml @@ -1,5 +1,5 @@ -.nav-links.sub-nav - %ul{ class: (container_class) } +%ul.nav-links.sub-nav + %div{ class: (container_class) } - if project_nav_tab?(:issues) && !current_controller?(:merge_requests) = nav_link(controller: :issues) do = link_to url_for_project_issues(@project, only_path: true), title: 'Issues' do diff --git a/app/views/projects/issues/_merge_requests.html.haml b/app/views/projects/issues/_merge_requests.html.haml index d8075371853..75f36579b11 100644 --- a/app/views/projects/issues/_merge_requests.html.haml +++ b/app/views/projects/issues/_merge_requests.html.haml @@ -24,6 +24,8 @@ MERGED - elsif merge_request.closed? CLOSED + %li + = render partial: 'projects/issues/closed_by_box', locals: {merge_request_count: @merge_requests.count} - if @closed_by_merge_requests.present? %li = render partial: 'projects/issues/closed_by_box', locals: {merge_request_count: @merge_requests.count} diff --git a/app/views/projects/issues/_related_branches.html.haml b/app/views/projects/issues/_related_branches.html.haml index c6fc499a7b8..b9bb6fe559d 100644 --- a/app/views/projects/issues/_related_branches.html.haml +++ b/app/views/projects/issues/_related_branches.html.haml @@ -6,7 +6,7 @@ %li - sha = @project.repository.find_branch(branch).target - pipeline = @project.pipeline(sha, branch) if sha - - if pipeline + - if ci_copipelinemmit %span.related-branch-ci-status = render_pipeline_status(pipeline) %span.related-branch-info diff --git a/app/views/projects/issues/index.atom.builder b/app/views/projects/issues/index.atom.builder index 36957560de0..7ad7c9c87e8 100644 --- a/app/views/projects/issues/index.atom.builder +++ b/app/views/projects/issues/index.atom.builder @@ -4,7 +4,7 @@ xml.feed "xmlns" => "http://www.w3.org/2005/Atom", "xmlns:media" => "http://sear xml.link href: namespace_project_issues_url(@project.namespace, @project, format: :atom, private_token: current_user.try(:private_token)), rel: "self", type: "application/atom+xml" xml.link href: namespace_project_issues_url(@project.namespace, @project), rel: "alternate", type: "text/html" xml.id namespace_project_issues_url(@project.namespace, @project) - xml.updated @issues.first.created_at.xmlschema if @issues.reorder(nil).any? + xml.updated @issues.first.created_at.xmlschema if @issues.any? - xml << render(partial: 'issues/issue', collection: @issues) if @issues.reorder(nil).any? + xml << render(partial: 'issues/issue', collection: @issues) if @issues.any? end diff --git a/app/views/projects/labels/index.html.haml b/app/views/projects/labels/index.html.haml index aa4d69550ec..6e1baa46b05 100644 --- a/app/views/projects/labels/index.html.haml +++ b/app/views/projects/labels/index.html.haml @@ -4,10 +4,9 @@ = render "projects/issues/head" %div{ class: (container_class) } - .top-area.adjust + .top-area .nav-text - Labels can be applied to issues and merge requests. Star a label to make it a priority label. Order the prioritized labels to change their relative priority, by dragging. - + Labels can be applied to issues and merge requests. .nav-controls - if can?(current_user, :admin_label, @project) = link_to new_namespace_project_label_path(@project.namespace, @project), class: "btn btn-new" do @@ -20,9 +19,10 @@ .prioritized-labels{ class: ('hide' if hide) } %h5 Prioritized Labels %ul.content-list.manage-labels-list.js-prioritized-labels{ "data-url" => set_priorities_namespace_project_labels_path(@project.namespace, @project) } - %p.empty-message{ class: ('hidden' unless @prioritized_labels.empty?) } No prioritized labels yet - if @prioritized_labels.present? = render @prioritized_labels + - else + %p.empty-message No prioritized labels yet .other-labels - if can?(current_user, :admin_label, @project) %h5{ class: ('hide' if hide) } Other Labels diff --git a/app/views/projects/merge_requests/show/_commits.html.haml b/app/views/projects/merge_requests/show/_commits.html.haml index 0b05785430b..a8f09f855d4 100644 --- a/app/views/projects/merge_requests/show/_commits.html.haml +++ b/app/views/projects/merge_requests/show/_commits.html.haml @@ -2,5 +2,4 @@ = icon("sort-amount-desc") Most recent commits displayed first -%ol#commits-list.list-unstyled - = render "projects/commits/commits", project: @merge_request.project += render "projects/commits/commits", project: @merge_request.project diff --git a/app/views/projects/merge_requests/widget/_merged.html.haml b/app/views/projects/merge_requests/widget/_merged.html.haml index 19b5d0ff066..ec4beae9727 100644 --- a/app/views/projects/merge_requests/widget/_merged.html.haml +++ b/app/views/projects/merge_requests/widget/_merged.html.haml @@ -6,29 +6,46 @@ - if @merge_request.merge_event by #{link_to_member(@project, @merge_request.merge_event.author, avatar: true)} #{time_ago_with_tooltip(@merge_request.merge_event.created_at)} - - if !@merge_request.source_branch_exists? || (params[:delete_source] == 'true') - %p - The changes were merged into - #{link_to @merge_request.target_branch, namespace_project_commits_path(@project.namespace, @project, @merge_request.target_branch), class: "label-branch"}. - The source branch has been removed. - = render 'projects/merge_requests/widget/merged_buttons' - - elsif @merge_request.can_remove_source_branch?(current_user) - .remove_source_branch_widget + %div + - if !@merge_request.source_branch_exists? || (params[:delete_source] == 'true') %p The changes were merged into #{link_to @merge_request.target_branch, namespace_project_commits_path(@project.namespace, @project, @merge_request.target_branch), class: "label-branch"}. - You can remove the source branch now. - = render 'projects/merge_requests/widget/merged_buttons', source_branch_exists: true - .remove_source_branch_widget.failed.hide - %p - Failed to remove source branch '#{@merge_request.source_branch}'. + The source branch has been removed. + = render 'projects/merge_requests/widget/merged_buttons' + - elsif @merge_request.can_remove_source_branch?(current_user) + .remove_source_branch_widget + %p + The changes were merged into + #{link_to @merge_request.target_branch, namespace_project_commits_path(@project.namespace, @project, @merge_request.target_branch), class: "label-branch"}. + You can remove the source branch now. + = render 'projects/merge_requests/widget/merged_buttons', source_branch_exists: true + .remove_source_branch_widget.failed.hide + %p + Failed to remove source branch '#{@merge_request.source_branch}'. + + .remove_source_branch_in_progress.hide + %p + = icon('spinner spin') + Removing source branch '#{@merge_request.source_branch}'. Please wait, this page will be automatically reloaded. + + :javascript + $('.remove_source_branch').on('click', function() { + $('.remove_source_branch_widget').hide(); + $('.remove_source_branch_in_progress').show(); + }); - .remove_source_branch_in_progress.hide + $(".remove_source_branch").on("ajax:success", function (e, data, status, xhr) { + location.reload(); + }); + + $(".remove_source_branch").on("ajax:error", function (e, data, status, xhr) { + $('.remove_source_branch_widget').hide(); + $('.remove_source_branch_in_progress').hide(); + $('.remove_source_branch_widget.failed').show(); + }); + - else %p - = icon('spinner spin') - Removing source branch '#{@merge_request.source_branch}'. Please wait, this page will be automatically reloaded. - - else - %p - The changes were merged into - #{link_to @merge_request.target_branch, namespace_project_commits_path(@project.namespace, @project, @merge_request.target_branch), class: "label-branch"}. - = render 'projects/merge_requests/widget/merged_buttons' + The changes were merged into + #{link_to @merge_request.target_branch, namespace_project_commits_path(@project.namespace, @project, @merge_request.target_branch), class: "label-branch"}. + = render 'projects/merge_requests/widget/merged_buttons' diff --git a/app/views/projects/merge_requests/widget/_merged_buttons.haml b/app/views/projects/merge_requests/widget/_merged_buttons.haml index d836a253507..56167509af9 100644 --- a/app/views/projects/merge_requests/widget/_merged_buttons.haml +++ b/app/views/projects/merge_requests/widget/_merged_buttons.haml @@ -3,9 +3,9 @@ - mr_can_be_cherry_picked = @merge_request.can_be_cherry_picked? - if can_remove_source_branch || mr_can_be_reverted || mr_can_be_cherry_picked - .clearfix.merged-buttons + .btn-group - if can_remove_source_branch - = link_to namespace_project_branch_path(@merge_request.source_project.namespace, @merge_request.source_project, @merge_request.source_branch), remote: true, method: :delete, class: "btn btn-default btn-sm remove_source_branch" do + = link_to namespace_project_branch_path(@merge_request.source_project.namespace, @merge_request.source_project, @merge_request.source_branch), remote: true, method: :delete, class: "btn btn-default btn-grouped btn-sm remove_source_branch" do = icon('trash-o') Remove Source Branch - if mr_can_be_reverted diff --git a/app/views/projects/milestones/_form.html.haml b/app/views/projects/milestones/_form.html.haml index cbf1ba04170..f5e2b927da8 100644 --- a/app/views/projects/milestones/_form.html.haml +++ b/app/views/projects/milestones/_form.html.haml @@ -19,7 +19,6 @@ = f.label :due_date, "Due Date", class: "control-label" .col-sm-10 = f.text_field :due_date, class: "datepicker form-control", placeholder: "Select due date" - %a.inline.prepend-top-5.js-clear-due-date{ href: "#" } Clear due date .form-actions - if @milestone.new_record? @@ -28,3 +27,10 @@ -else = f.submit 'Save changes', class: "btn-save btn" = link_to "Cancel", namespace_project_milestone_path(@project.namespace, @project, @milestone), class: "btn btn-cancel" + + +:javascript + $(".datepicker").datepicker({ + dateFormat: "yy-mm-dd", + onSelect: function(dateText, inst) { $("#milestone_due_date").val(dateText) } + }).datepicker("setDate", $.datepicker.parseDate('yy-mm-dd', $('#milestone_due_date').val())); diff --git a/app/views/projects/network/show.html.haml b/app/views/projects/network/show.html.haml index e4ab064eda8..bf9baaea889 100644 --- a/app/views/projects/network/show.html.haml +++ b/app/views/projects/network/show.html.haml @@ -1,5 +1,4 @@ - page_title "Network", @ref -- page_specific_javascripts asset_path("network/application.js") = render "projects/commits/head" = render "head" %div{ class: (container_class) } @@ -15,5 +14,14 @@ = check_box_tag :filter_ref, 1, @options[:filter_ref] %span Begin with the selected commit - .network-graph{ data: { url: '#{escape_javascript(@url)}', commit_url: '#{escape_javascript(@commit_url)}', ref: '#{escape_javascript(@ref)}', commit_id: '#{escape_javascript(@commit.id)}' } } + .network-graph = spinner nil, true + +:javascript + network_graph = new Network({ + url: "#{escape_javascript(@url)}", + commit_url: "#{escape_javascript(@commit_url)}", + ref: "#{escape_javascript(@ref)}", + commit_id: '#{@commit.id}' + }) + new ShortcutsNetwork(network_graph.branch_graph) diff --git a/app/views/projects/new.html.haml b/app/views/projects/new.html.haml index 7e8b8f83467..f9ac16b32f3 100644 --- a/app/views/projects/new.html.haml +++ b/app/views/projects/new.html.haml @@ -11,22 +11,26 @@ .project-edit-content = form_for @project, html: { class: 'new_project form-horizontal js-requires-input' } do |f| - .form-group + .form-group.project-name-holder = f.label :path, class: 'control-label' do - Project owner + Project path .col-sm-10 - = f.select :namespace_id, namespaces_options(:current_user), {}, {class: 'select2 js-select-namespace', tabindex: 1} - + .input-group + - if current_user.can_select_namespace? + .input-group-addon + = root_url + = f.select :namespace_id, namespaces_options(params[:namespace_id] || :current_user, display_path: true), {}, {class: 'select2 js-select-namespace', tabindex: 1} + .input-group-addon + \/ + - else + .input-group-addon + #{root_url}#{current_user.username}/ + = f.text_field :path, placeholder: "my-awesome-project", class: "form-control", tabindex: 2, autofocus: true, required: true + - if current_user.can_create_group? .help-block Want to house several dependent projects under the same namespace? = link_to "Create a group", new_group_path - - .form-group - = f.label :path, class: 'control-label' do - Project name - .col-sm-10 - = f.text_field :path, placeholder: "my-awesome-project", class: "form-control", tabindex: 2, autofocus: true, required: true - if import_sources_enabled? .project-import.js-toggle-container diff --git a/app/views/projects/pipelines/_head.html.haml b/app/views/projects/pipelines/_head.html.haml index d65faf86d4e..f278d4e0538 100644 --- a/app/views/projects/pipelines/_head.html.haml +++ b/app/views/projects/pipelines/_head.html.haml @@ -1,19 +1,15 @@ -.nav-links.sub-nav - %ul{ class: (container_class) } +%ul.nav-links.sub-nav + %div{ class: (container_class) } - if project_nav_tab? :pipelines = nav_link(controller: :pipelines) do = link_to project_pipelines_path(@project), title: 'Pipelines', class: 'shortcuts-pipelines' do %span Pipelines + %span.badge.count.ci_counter= number_with_delimiter(@project.pipelines.running_or_pending.count) - if project_nav_tab? :builds = nav_link(controller: %w(builds)) do = link_to project_builds_path(@project), title: 'Builds', class: 'shortcuts-builds' do %span Builds - - - if project_nav_tab? :environments - = nav_link(controller: %w(environments)) do - = link_to project_environments_path(@project), title: 'Environments', class: 'shortcuts-environments' do - %span - Environments + %span.badge.count.builds_counter= number_with_delimiter(@project.running_or_pending_build_count) diff --git a/app/views/projects/project_members/_group_members.html.haml b/app/views/projects/project_members/_group_members.html.haml index cb6136c215a..6671ee2c6d6 100644 --- a/app/views/projects/project_members/_group_members.html.haml +++ b/app/views/projects/project_members/_group_members.html.haml @@ -6,14 +6,11 @@ (#{members.count}) - if can?(current_user, :admin_group_member, @group) .controls - = link_to 'Manage group members', - group_group_members_path(@group), - class: 'btn' + = link_to group_group_members_path(@group), class: 'btn' do + Manage group members %ul.content-list - = render partial: 'shared/members/member', - collection: members.limit(20), - as: :member, - locals: { show_controls: false } - - if members.size > 20 + - members.limit(20).each do |member| + = render 'groups/group_members/group_member', member: member, show_controls: false + - if members.count > 20 %li and #{members.count - 20} more. For full list visit #{link_to 'group members page', group_group_members_path(@group)} diff --git a/app/views/projects/project_members/_new_project_member.html.haml b/app/views/projects/project_members/_new_project_member.html.haml index 82892a33358..f0f3bb3c177 100644 --- a/app/views/projects/project_members/_new_project_member.html.haml +++ b/app/views/projects/project_members/_new_project_member.html.haml @@ -9,7 +9,7 @@ .form-group = f.label :access_level, "Project Access", class: 'control-label' .col-sm-10 - = select_tag :access_level, options_for_select(ProjectMember.access_level_roles, @project_member.access_level), class: "project-access-select select2" + = select_tag :access_level, options_for_select(ProjectMember.access_roles, @project_member.access_level), class: "project-access-select select2" .help-block Read more about role permissions %strong= link_to "here", help_page_path("permissions", "permissions"), class: "vlink" diff --git a/app/views/projects/project_members/_project_member.html.haml b/app/views/projects/project_members/_project_member.html.haml new file mode 100644 index 00000000000..268f140d7db --- /dev/null +++ b/app/views/projects/project_members/_project_member.html.haml @@ -0,0 +1,55 @@ +- user = member.user +- return unless user || member.invite? + +%li{class: "#{dom_class(member)} js-toggle-container project_member_row access-#{member.human_access.downcase}", id: dom_id(member)} + %span.list-item-name + - if member.user + = image_tag avatar_icon(user, 24), class: "avatar s24", alt: '' + %strong + = link_to user.name, user_path(user) + %span.cgray= user.username + - if user == current_user + %span.label.label-success It's you + - if user.blocked? + %label.label.label-danger + %strong Blocked + - else + = image_tag avatar_icon(member.invite_email, 24), class: "avatar s24", alt: '' + %strong + = member.invite_email + %span.cgray + invited + - if member.created_by + by + = link_to member.created_by.name, user_path(member.created_by) + = time_ago_with_tooltip(member.created_at) + + - if can?(current_user, :admin_project_member, @project) + = link_to resend_invite_namespace_project_project_member_path(@project.namespace, @project, member), method: :post, class: "btn-xs btn", title: 'Resend invite' do + Resend invite + + - if can?(current_user, :admin_project_member, @project) + .pull-right + %strong= member.human_access + - if can?(current_user, :update_project_member, member) + = button_tag class: "btn-xs btn-grouped inline btn js-toggle-button", + title: 'Edit access level', type: 'button' do + = icon('pencil') + + - if can?(current_user, :destroy_project_member, member) + + - if current_user == user + = link_to leave_namespace_project_project_members_path(@project.namespace, @project), data: { confirm: leave_project_message(@project) }, method: :delete, class: "btn-xs btn btn-remove", title: 'Leave project' do + = icon("sign-out") + Leave + - else + = link_to namespace_project_project_member_path(@project.namespace, @project, member), data: { confirm: remove_from_project_team_message(@project, member) }, method: :delete, remote: true, class: "btn-xs btn btn-remove", title: 'Remove user from team' do + = icon('trash') + + .edit-member.hide.js-toggle-content + %br + = form_for member, as: :project_member, url: namespace_project_project_member_path(@project.namespace, @project, member), remote: true do |f| + .prepend-top-10 + = f.select :access_level, options_for_select(ProjectMember.access_roles, member.access_level), {}, class: 'form-control' + .prepend-top-10 + = f.submit 'Save', class: 'btn btn-save' diff --git a/app/views/projects/project_members/_shared_group_members.html.haml b/app/views/projects/project_members/_shared_group_members.html.haml index 952844acefc..ae13f8428f0 100644 --- a/app/views/projects/project_members/_shared_group_members.html.haml +++ b/app/views/projects/project_members/_shared_group_members.html.haml @@ -14,10 +14,8 @@ %i.fa.fa-pencil-square-o Edit group members %ul.content-list - = render partial: 'shared/members/member', - collection: shared_group.group_members.order(access_level: :desc).limit(20), - as: :member, - locals: { show_controls: false, show_roles: false } + - shared_group.group_members.order('access_level DESC').limit(20).each do |member| + = render 'groups/group_members/group_member', member: member, show_controls: false, show_roles: false - if shared_group_users_count > 20 %li and #{shared_group_users_count - 20} more. For full list visit #{link_to 'group members page', group_group_members_path(shared_group)} diff --git a/app/views/projects/project_members/_team.html.haml b/app/views/projects/project_members/_team.html.haml index 03207614258..e8dce30425f 100644 --- a/app/views/projects/project_members/_team.html.haml +++ b/app/views/projects/project_members/_team.html.haml @@ -11,7 +11,8 @@ = button_tag class: 'btn', title: 'Search' do = icon("search") %ul.content-list - = render partial: 'shared/members/member', collection: members, as: :member + - members.each do |project_member| + = render 'project_member', member: project_member :javascript $('form.member-search-form').on('submit', function (event) { diff --git a/app/views/projects/project_members/index.html.haml b/app/views/projects/project_members/index.html.haml index 357ccccaf1d..15dc064e7ea 100644 --- a/app/views/projects/project_members/index.html.haml +++ b/app/views/projects/project_members/index.html.haml @@ -13,9 +13,7 @@ Users with access to this project are listed below. = render "new_project_member" - = render 'shared/members/requests', membership_source: @project, members: @project_members.request - - = render 'team', members: @project_members.non_request + = render "team", members: @project_members - if @group = render "group_members", members: @group_members diff --git a/app/views/projects/project_members/update.js.haml b/app/views/projects/project_members/update.js.haml index 45f8ef89060..2fb3a41d541 100644 --- a/app/views/projects/project_members/update.js.haml +++ b/app/views/projects/project_members/update.js.haml @@ -1,2 +1,2 @@ :plain - $("##{dom_id(@project_member)}").replaceWith('#{escape_javascript(render('shared/members/member', member: @project_member))}'); + $("##{dom_id(@project_member)}").replaceWith('#{escape_javascript(render("project_member", member: @project_member))}'); diff --git a/app/views/shared/_label_row.html.haml b/app/views/shared/_label_row.html.haml index 77676454b57..478c04318c6 100644 --- a/app/views/shared/_label_row.html.haml +++ b/app/views/shared/_label_row.html.haml @@ -1,7 +1,5 @@ %span.label-row - if can?(current_user, :admin_label, @project) - .draggable-handler - = icon('bars') .js-toggle-priority.toggle-priority{ data: { url: remove_priority_namespace_project_label_path(@project.namespace, @project, label), dom_id: dom_id(label) } } %button.add-priority.btn.has-tooltip{ title: 'Prioritize', :'data-placement' => 'top' } diff --git a/app/views/shared/_merge_requests.html.haml b/app/views/shared/_merge_requests.html.haml index ca3178395c1..e74fc36c797 100644 --- a/app/views/shared/_merge_requests.html.haml +++ b/app/views/shared/_merge_requests.html.haml @@ -1,4 +1,4 @@ -- if @merge_requests.reorder(nil).any? +- if @merge_requests.any? - @merge_requests.group_by(&:target_project).each do |group| .panel.panel-default.panel-small - project = group[0] diff --git a/app/views/shared/groups/_group.html.haml b/app/views/shared/groups/_group.html.haml index 1ad95351005..a25365a94f2 100644 --- a/app/views/shared/groups/_group.html.haml +++ b/app/views/shared/groups/_group.html.haml @@ -9,7 +9,7 @@ = link_to edit_group_path(group), class: "btn" do = icon('cogs') - = link_to leave_group_group_members_path(group), data: { confirm: leave_confirmation_message(group) }, method: :delete, class: "btn", title: 'Leave this group' do + = link_to leave_group_group_members_path(group), data: { confirm: leave_group_message(group.name) }, method: :delete, class: "btn", title: 'Leave this group' do = icon('sign-out') .stats diff --git a/app/views/shared/issuable/_form.html.haml b/app/views/shared/issuable/_form.html.haml index c30bdb0ae91..17e2a7e9290 100644 --- a/app/views/shared/issuable/_form.html.haml +++ b/app/views/shared/issuable/_form.html.haml @@ -35,13 +35,13 @@ .clearfix .error-alert -- if issuable.is_a?(Issue) +- if issuable.is_a?(Issue) && !issuable.project.private? .form-group .col-sm-offset-2.col-sm-10 .checkbox = f.label :confidential do = f.check_box :confidential - This issue is confidential and should only be visible to team members with at least Reporter access. + This issue is confidential and should only be visible to team members - if can?(current_user, :"admin_#{issuable.to_ability_name}", issuable.project) - has_due_date = issuable.has_attribute?(:due_date) diff --git a/app/views/shared/issuable/_sidebar.html.haml b/app/views/shared/issuable/_sidebar.html.haml index 210c9b9aab5..fb906de829a 100644 --- a/app/views/shared/issuable/_sidebar.html.haml +++ b/app/views/shared/issuable/_sidebar.html.haml @@ -1,21 +1,9 @@ -- todo = has_todo(issuable) %aside.right-sidebar{ class: sidebar_gutter_collapsed_class } .issuable-sidebar - can_edit_issuable = can?(current_user, :"admin_#{issuable.to_ability_name}", @project) .block.issuable-sidebar-header - - if current_user - %span.issuable-header-text.hide-collapsed.pull-left - Todo - %a.gutter-toggle.pull-right.js-sidebar-toggle{ role: "button", href: "#", aria: { label: "Toggle sidebar" } } + %a.gutter-toggle.pull-right.js-sidebar-toggle{href: '#'} = sidebar_gutter_toggle_icon - - if current_user - %button.btn.btn-default.issuable-header-btn.pull-right.js-issuable-todo{ type: "button", aria: { label: (todo.nil? ? "Add Todo" : "Mark Done") }, data: { todo_text: "Add Todo", mark_text: "Mark Done", id: (todo.id unless todo.nil?), issuable: issuable.id, issuable_type: issuable.class.name.underscore, url: namespace_project_todos_path(@project.namespace, @project) } } - %span.js-issuable-todo-text - - if todo.nil? - Add Todo - - else - Mark Done - = icon('spin spinner', class: 'hidden js-issuable-todo-loading') = form_for [@project.namespace.becomes(Namespace), @project, issuable], remote: true, format: :json, html: {class: 'issuable-context-form inline-update js-issuable-update'} do |f| .block.assignee @@ -29,21 +17,20 @@ = icon('spinner spin', class: 'block-loading') - if can_edit_issuable = link_to 'Edit', '#', class: 'edit-link pull-right' - .value.hide-collapsed + .value.bold.hide-collapsed - if issuable.assignee - = link_to_member(@project, issuable.assignee, size: 32, extra_class: 'bold') do + = link_to_member(@project, issuable.assignee, size: 32) do - if issuable.instance_of?(MergeRequest) && !issuable.can_be_merged_by?(issuable.assignee) %span.pull-right.cannot-be-merged{ data: { toggle: 'tooltip', placement: 'left' }, title: 'Not allowed to merge' } = icon('exclamation-triangle') %span.username = issuable.assignee.to_reference - else - %span.assign-yourself.no-value + %span.assign-yourself No assignee - if can_edit_issuable - \- %a.js-assign-yourself{ href: '#' } - assign yourself + \- assign yourself .selectbox.hide-collapsed = f.hidden_field 'assignee_id', value: issuable.assignee_id, id: 'issue_assignee_id' @@ -63,11 +50,13 @@ = icon('spinner spin', class: 'block-loading') - if can_edit_issuable = link_to 'Edit', '#', class: 'edit-link pull-right' - .value.hide-collapsed + .value.bold.hide-collapsed - if issuable.milestone - = link_to issuable.milestone.title, namespace_project_milestone_path(@project.namespace, @project, issuable.milestone), class: "bold has-tooltip", title: milestone_remaining_days(issuable.milestone), data: { container: "body", html: 1 } + = link_to namespace_project_milestone_path(@project.namespace, @project, issuable.milestone) do + %span.has-tooltip{title: milestone_remaining_days(issuable.milestone), data: {container: 'body', html: 1}} + = issuable.milestone.title - else - %span.no-value None + .light None .selectbox.hide-collapsed = f.hidden_field 'milestone_id', value: issuable.milestone_id, id: nil @@ -84,14 +73,14 @@ = icon('spinner spin', class: 'block-loading') - if can?(current_user, :"admin_#{issuable.to_ability_name}", @project) = link_to 'Edit', '#', class: 'edit-link pull-right' - .value.hide-collapsed + .value.bold.hide-collapsed %span.value-content - if issuable.due_date - %span.bold= issuable.due_date.to_s(:medium) + = issuable.due_date.to_s(:medium) - else - %span.no-value No due date + None - if can?(current_user, :"admin_#{issuable.to_ability_name}", @project) - %span.no-value.js-remove-due-date-holder{ class: ("hidden" if issuable.due_date.nil?) } + %span.light.js-remove-due-date-holder{ class: ("hidden" if issuable.due_date.nil?) } \- %a.js-remove-due-date{ href: "#", role: "button" } remove due date @@ -123,7 +112,7 @@ - issuable.labels_array.each do |label| = link_to_label(label, type: issuable.to_ability_name) - else - %span.no-value None + .light None .selectbox.hide-collapsed - issuable.labels_array.each do |label| = hidden_field_tag "#{issuable.to_ability_name}[label_names][]", label.id, id: nil diff --git a/app/views/shared/members/_access_request_buttons.html.haml b/app/views/shared/members/_access_request_buttons.html.haml deleted file mode 100644 index ed0a6ebcf84..00000000000 --- a/app/views/shared/members/_access_request_buttons.html.haml +++ /dev/null @@ -1,12 +0,0 @@ -- member = source.members.find_by(user_id: current_user.id) - -- if member - - if member.request? - = link_to 'Withdraw Access Request', polymorphic_path([:leave, source, :members]), - method: :delete, - data: { confirm: remove_member_message(member) }, - class: 'btn access-request-button hidden-xs' -- else - = link_to 'Request Access', polymorphic_path([:request_access, source, :members]), - method: :post, - class: 'btn access-request-button hidden-xs' diff --git a/app/views/shared/members/_member.html.haml b/app/views/shared/members/_member.html.haml deleted file mode 100644 index 0191814849a..00000000000 --- a/app/views/shared/members/_member.html.haml +++ /dev/null @@ -1,78 +0,0 @@ -- default_show_roles = can?(current_user, action_member_permission(:update, member), member) || can?(current_user, action_member_permission(:destroy, member), member) -- show_roles = local_assigns.fetch(:show_roles, default_show_roles) -- show_controls = local_assigns.fetch(:show_controls, true) -- user = member.user - -%li.js-toggle-container{ class: dom_class(member), id: dom_id(member) } - %span{ class: ("list-item-name" if show_controls) } - - if user - = image_tag avatar_icon(user, 24), class: "avatar s24", alt: '' - %strong - = link_to user.name, user_path(user) - %span.cgray= user.username - - - if user == current_user - %span.label.label-success It's you - - - if user.blocked? - %label.label.label-danger - %strong Blocked - - - if member.request? - %span.cgray - – Requested - = time_ago_with_tooltip(member.requested_at) - - else - = image_tag avatar_icon(member.invite_email, 24), class: "avatar s24", alt: '' - %strong= member.invite_email - %span.cgray - – Invited - - if member.created_by - by - = link_to member.created_by.name, user_path(member.created_by) - = time_ago_with_tooltip(member.created_at) - - - if show_controls && can?(current_user, action_member_permission(:admin, member), member.source) - = link_to 'Resend invite', polymorphic_path([:resend_invite, member]), - method: :post, - class: 'btn-xs btn' - - - if show_roles - %span.pull-right - %strong= member.human_access - - if show_controls - - if can?(current_user, action_member_permission(:update, member), member) - = button_tag icon('pencil'), - type: 'button', - class: 'btn-xs btn btn-grouped inline js-toggle-button', - title: 'Edit access level' - - - if member.request? - - = link_to icon('check inverse'), polymorphic_path([:approve_access_request, member]), - method: :post, - class: 'btn-xs btn btn-success', - title: 'Grant access' - - - if can?(current_user, action_member_permission(:destroy, member), member) - - - if current_user == user - = link_to icon('sign-out', text: 'Leave'), polymorphic_path([:leave, member.source, :members]), - method: :delete, - data: { confirm: leave_confirmation_message(member.source) }, - class: 'btn-xs btn btn-remove' - - else - = link_to icon('trash'), member, - remote: true, - method: :delete, - data: { confirm: remove_member_message(member) }, - class: 'btn-xs btn btn-remove', - title: remove_member_title(member) - - .edit-member.hide.js-toggle-content - %br - = form_for member, remote: true do |f| - .prepend-top-10 - = f.select :access_level, options_for_select(member.class.access_level_roles, member.access_level), {}, class: 'form-control' - .prepend-top-10 - = f.submit 'Save', class: 'btn btn-save btn-sm' diff --git a/app/views/shared/members/_requests.html.haml b/app/views/shared/members/_requests.html.haml deleted file mode 100644 index b5963876034..00000000000 --- a/app/views/shared/members/_requests.html.haml +++ /dev/null @@ -1,8 +0,0 @@ -- if members.any? - .panel.panel-default - .panel-heading - %strong= membership_source.name - access requests - %small= "(#{members.size})" - %ul.content-list - = render partial: 'shared/members/member', collection: members, as: :member diff --git a/app/views/shared/milestones/_merge_requests_tab.haml b/app/views/shared/milestones/_merge_requests_tab.haml index 9c193f901e2..c29d8ee6737 100644 --- a/app/views/shared/milestones/_merge_requests_tab.haml +++ b/app/views/shared/milestones/_merge_requests_tab.haml @@ -3,10 +3,10 @@ .row.prepend-top-default .col-md-3 - = render 'shared/milestones/issuables', args.merge(title: 'Work in progress (open and unassigned)', issuables: merge_requests.opened.unassigned, id: 'unassigned', show_counter: true) + = render 'shared/milestones/issuables', args.merge(title: 'Work in progress (open and unassigned)', issuables: merge_requests.opened.unassigned, id: 'unassigned') .col-md-3 - = render 'shared/milestones/issuables', args.merge(title: 'Waiting for merge (open and assigned)', issuables: merge_requests.opened.assigned, id: 'ongoing', show_counter: true) + = render 'shared/milestones/issuables', args.merge(title: 'Waiting for merge (open and assigned)', issuables: merge_requests.opened.assigned, id: 'ongoing') .col-md-3 - = render 'shared/milestones/issuables', args.merge(title: 'Rejected (closed)', issuables: merge_requests.closed, id: 'closed', show_counter: true) + = render 'shared/milestones/issuables', args.merge(title: 'Rejected (closed)', issuables: merge_requests.closed, id: 'closed') .col-md-3 - = render 'shared/milestones/issuables', args.merge(title: 'Merged', issuables: merge_requests.merged, id: 'merged', primary: true, show_counter: true) + = render 'shared/milestones/issuables', args.merge(title: 'Merged', issuables: merge_requests.merged, id: 'merged', primary: true) diff --git a/app/views/u2f/_register.html.haml b/app/views/u2f/_register.html.haml index cbb8dfb7829..46af591fc43 100644 --- a/app/views/u2f/_register.html.haml +++ b/app/views/u2f/_register.html.haml @@ -4,18 +4,11 @@ %p Your browser doesn't support U2F. Please use Google Chrome desktop (version 41 or newer). %script#js-register-u2f-setup{ type: "text/template" } - - if current_user.two_factor_otp_enabled? - .row.append-bottom-10 - .col-md-3 - %button#js-setup-u2f-device.btn.btn-info Setup New U2F Device - .col-md-9 - %p Your U2F device needs to be set up. Plug it in (if not already) and click the button on the left. - - else - .row.append-bottom-10 - .col-md-3 - %button#js-setup-u2f-device.btn.btn-info{ disabled: true } Setup New U2F Device - .col-md-9 - %p.text-warning You need to register a two-factor authentication app before you can set up a U2F device. + .row.append-bottom-10 + .col-md-3 + %a#js-setup-u2f-device.btn.btn-info{ href: 'javascript:void(0)' } Setup New U2F Device + .col-md-9 + %p Your U2F device needs to be set up. Plug it in (if not already) and click the button on the left. %script#js-register-u2f-in-progress{ type: "text/template" } %p Trying to communicate with your device. Plug it in (if you haven't already) and press the button on the device now. diff --git a/app/workers/expire_build_artifacts_worker.rb b/app/workers/expire_build_artifacts_worker.rb deleted file mode 100644 index c64ea108d52..00000000000 --- a/app/workers/expire_build_artifacts_worker.rb +++ /dev/null @@ -1,13 +0,0 @@ -class ExpireBuildArtifactsWorker - include Sidekiq::Worker - - def perform - Rails.logger.info 'Cleaning old build artifacts' - - builds = Ci::Build.with_expired_artifacts - builds.find_each(batch_size: 50).each do |build| - Rails.logger.debug "Removing artifacts build #{build.id}..." - build.erase_artifacts! - end - end -end diff --git a/app/workers/stuck_ci_builds_worker.rb b/app/workers/stuck_ci_builds_worker.rb index 6828013b377..ca594e77e7c 100644 --- a/app/workers/stuck_ci_builds_worker.rb +++ b/app/workers/stuck_ci_builds_worker.rb @@ -6,7 +6,7 @@ class StuckCiBuildsWorker def perform Rails.logger.info 'Cleaning stuck builds' - builds = Ci::Build.joins(:project).running_or_pending.where('ci_builds.updated_at < ?', BUILD_STUCK_TIMEOUT.ago) + builds = Ci::Build.running_or_pending.where('updated_at < ?', BUILD_STUCK_TIMEOUT.ago) builds.find_each(batch_size: 50).each do |build| Rails.logger.debug "Dropping stuck #{build.status} build #{build.id} for runner #{build.runner_id}" build.drop diff --git a/config/application.rb b/config/application.rb index 05fec995ed3..49d4d3ba555 100644 --- a/config/application.rb +++ b/config/application.rb @@ -83,7 +83,6 @@ module Gitlab config.assets.precompile << "mailers/*.css" config.assets.precompile << "graphs/application.js" config.assets.precompile << "users/application.js" - config.assets.precompile << "network/application.js" # Version of your assets, change this if you want to expire all your assets config.assets.version = '1.0' diff --git a/config/gitlab.yml.example b/config/gitlab.yml.example index 75e1a3c1093..1048ef6e243 100644 --- a/config/gitlab.yml.example +++ b/config/gitlab.yml.example @@ -164,9 +164,6 @@ production: &base # Flag stuck CI builds as failed stuck_ci_builds_worker: cron: "0 0 * * *" - # Remove expired build artifacts - expire_build_artifacts_worker: - cron: "50 * * * *" # Periodically run 'git fsck' on all repositories. If started more than # once per hour you will have concurrent 'git fsck' jobs. repository_check_worker: diff --git a/config/initializers/1_settings.rb b/config/initializers/1_settings.rb index 916fd33e767..436751b9d16 100644 --- a/config/initializers/1_settings.rb +++ b/config/initializers/1_settings.rb @@ -279,9 +279,6 @@ Settings['cron_jobs'] ||= Settingslogic.new({}) Settings.cron_jobs['stuck_ci_builds_worker'] ||= Settingslogic.new({}) Settings.cron_jobs['stuck_ci_builds_worker']['cron'] ||= '0 0 * * *' Settings.cron_jobs['stuck_ci_builds_worker']['job_class'] = 'StuckCiBuildsWorker' -Settings.cron_jobs['expire_build_artifacts_worker'] ||= Settingslogic.new({}) -Settings.cron_jobs['expire_build_artifacts_worker']['cron'] ||= '50 * * * *' -Settings.cron_jobs['expire_build_artifacts_worker']['job_class'] = 'ExpireBuildArtifactsWorker' Settings.cron_jobs['repository_check_worker'] ||= Settingslogic.new({}) Settings.cron_jobs['repository_check_worker']['cron'] ||= '20 * * * *' Settings.cron_jobs['repository_check_worker']['job_class'] = 'RepositoryCheck::BatchWorker' diff --git a/config/initializers/chronic_duration.rb b/config/initializers/chronic_duration.rb deleted file mode 100644 index b65b06c813a..00000000000 --- a/config/initializers/chronic_duration.rb +++ /dev/null @@ -1 +0,0 @@ -ChronicDuration.raise_exceptions = true diff --git a/config/initializers/doorkeeper.rb b/config/initializers/doorkeeper.rb index 618dba74151..8dc8e270afc 100644 --- a/config/initializers/doorkeeper.rb +++ b/config/initializers/doorkeeper.rb @@ -12,7 +12,7 @@ Doorkeeper.configure do end resource_owner_from_credentials do |routes| - Gitlab::Auth.find_with_user_password(params[:username], params[:password]) + Gitlab::Auth.find_in_gitlab_or_ldap(params[:username], params[:password]) end # If you want to restrict access to the web interface for adding oauth authorized applications, you need to declare the block below. diff --git a/config/initializers/metrics.rb b/config/initializers/metrics.rb index d159f4eded2..2673093b96a 100644 --- a/config/initializers/metrics.rb +++ b/config/initializers/metrics.rb @@ -96,18 +96,13 @@ if Gitlab::Metrics.enabled? config.instrument_instance_methods(const) end - # Instruments all Banzai filters and reference parsers - { - Filter: Rails.root.join('lib', 'banzai', 'filter', '*.rb'), - ReferenceParser: Rails.root.join('lib', 'banzai', 'reference_parser', '*.rb') - }.each do |const_name, path| - Dir[path].each do |file| - klass = File.basename(file, File.extname(file)).camelize - const = Banzai.const_get(const_name).const_get(klass) + # Instruments all Banzai filters + Dir[Rails.root.join('lib', 'banzai', 'filter', '*.rb')].each do |file| + klass = File.basename(file, File.extname(file)).camelize + const = Banzai::Filter.const_get(klass) - config.instrument_methods(const) - config.instrument_instance_methods(const) - end + config.instrument_methods(const) + config.instrument_instance_methods(const) end config.instrument_methods(Banzai::Renderer) @@ -128,25 +123,14 @@ if Gitlab::Metrics.enabled? config.instrument_instance_methods(API::Helpers) config.instrument_instance_methods(RepositoryCheck::SingleRepositoryWorker) + # Iterate over each non-super private instance method to keep up to date if + # internals change + RepositoryCheck::SingleRepositoryWorker.private_instance_methods(false).each do |method| + config.instrument_instance_method(RepositoryCheck::SingleRepositoryWorker, method) + end end GC::Profiler.enable Gitlab::Metrics::Sampler.new.start - - module TrackNewRedisConnections - def connect(*args) - val = super - - if current_transaction = Gitlab::Metrics::Transaction.current - current_transaction.increment(:new_redis_connections, 1) - end - - val - end - end - - class ::Redis::Client - prepend TrackNewRedisConnections - end end diff --git a/config/routes.rb b/config/routes.rb index d52cbb22428..95fbe7dd9df 100644 --- a/config/routes.rb +++ b/config/routes.rb @@ -30,11 +30,6 @@ Rails.application.routes.draw do mount LetterOpenerWeb::Engine, at: '/rails/letter_opener' end - concern :access_requestable do - post :request_access, on: :collection - post :approve_access_request, on: :member - end - namespace :ci do # CI API Ci::API::API.logger Rails.logger @@ -414,7 +409,7 @@ Rails.application.routes.draw do end scope module: :groups do - resources :group_members, only: [:index, :create, :update, :destroy], concerns: :access_requestable do + resources :group_members, only: [:index, :create, :update, :destroy] do post :resend_invite, on: :member delete :leave, on: :collection end @@ -709,8 +704,6 @@ Rails.application.routes.draw do end end - resources :environments, only: [:index, :show, :new, :create, :destroy] - resources :builds, only: [:index, :show], constraints: { id: /\d+/ } do collection do post :cancel_all @@ -729,7 +722,6 @@ Rails.application.routes.draw do get :download get :browse, path: 'browse(/*path)', format: false get :file, path: 'file/*path', format: false - post :keep end end @@ -773,7 +765,7 @@ Rails.application.routes.draw do end end - resources :project_members, except: [:new, :edit], constraints: { id: /[a-zA-Z.\/0-9_\-#%+]+/ }, concerns: :access_requestable do + resources :project_members, except: [:new, :edit], constraints: { id: /[a-zA-Z.\/0-9_\-#%+]+/ } do collection do delete :leave @@ -797,8 +789,6 @@ Rails.application.routes.draw do end end - resources :todos, only: [:create, :update], constraints: { id: /\d+/ } - resources :uploads, only: [:create] do collection do get ":secret/:filename", action: :show, as: :show, constraints: { filename: /[^\/]+/ } diff --git a/db/fixtures/development/15_award_emoji.rb b/db/fixtures/development/15_award_emoji.rb deleted file mode 100644 index baac32f2d10..00000000000 --- a/db/fixtures/development/15_award_emoji.rb +++ /dev/null @@ -1,33 +0,0 @@ -Gitlab::Seeder.quiet do - emoji = Gitlab::AwardEmoji.emojis.keys - - Issue.order(Gitlab::Database.random).limit(Issue.count / 2).each do |issue| - project = issue.project - - project.team.users.sample(2).each do |user| - issue.create_award_emoji(emoji.sample, user) - - issue.notes.sample(2).each do |note| - next if note.system? - note.create_award_emoji(emoji.sample, user) - end - - print '.' - end - end - - MergeRequest.order(Gitlab::Database.random).limit(MergeRequest.count / 2).each do |mr| - project = mr.project - - project.team.users.sample(2).each do |user| - mr.create_award_emoji(emoji.sample, user) - - mr.notes.sample(2).each do |note| - next if note.system? - note.create_award_emoji(emoji.sample, user) - end - - print '.' - end - end -end diff --git a/db/migrate/20160314114439_add_requested_at_to_members.rb b/db/migrate/20160314114439_add_requested_at_to_members.rb deleted file mode 100644 index 273819d4cd8..00000000000 --- a/db/migrate/20160314114439_add_requested_at_to_members.rb +++ /dev/null @@ -1,5 +0,0 @@ -class AddRequestedAtToMembers < ActiveRecord::Migration - def change - add_column :members, :requested_at, :datetime - end -end diff --git a/db/migrate/20160416182152_convert_award_note_to_emoji_award.rb b/db/migrate/20160416182152_convert_award_note_to_emoji_award.rb index 95ee03611d9..c226bc11f6c 100644 --- a/db/migrate/20160416182152_convert_award_note_to_emoji_award.rb +++ b/db/migrate/20160416182152_convert_award_note_to_emoji_award.rb @@ -1,37 +1,10 @@ # rubocop:disable all class ConvertAwardNoteToEmojiAward < ActiveRecord::Migration - disable_ddl_transaction! - - def up - if Gitlab::Database.postgresql? - migrate_postgresql - else - migrate_mysql - end - end - - def down - add_column :notes, :is_award, :boolean - - # This migration does NOT move the awards on notes, if the table is dropped in another migration, these notes will be lost. - execute "INSERT INTO notes (noteable_type, noteable_id, author_id, note, created_at, updated_at, is_award) (SELECT awardable_type, awardable_id, user_id, name, created_at, updated_at, TRUE FROM award_emoji)" - end - - def migrate_postgresql - connection.transaction do - execute 'LOCK notes IN EXCLUSIVE MODE' + def change + def up execute "INSERT INTO award_emoji (awardable_type, awardable_id, user_id, name, created_at, updated_at) (SELECT noteable_type, noteable_id, author_id, note, created_at, updated_at FROM notes WHERE is_award = true)" + execute "DELETE FROM notes WHERE is_award = true" - remove_column :notes, :is_award, :boolean end end - - def migrate_mysql - execute 'LOCK TABLES notes WRITE, award_emoji WRITE;' - execute 'INSERT INTO award_emoji (awardable_type, awardable_id, user_id, name, created_at, updated_at) (SELECT noteable_type, noteable_id, author_id, note, created_at, updated_at FROM notes WHERE is_award = true);' - execute "DELETE FROM notes WHERE is_award = true" - remove_column :notes, :is_award, :boolean - ensure - execute 'UNLOCK TABLES' - end end diff --git a/db/migrate/20160416190505_remove_note_is_award.rb b/db/migrate/20160416190505_remove_note_is_award.rb new file mode 100644 index 00000000000..dd24917feb9 --- /dev/null +++ b/db/migrate/20160416190505_remove_note_is_award.rb @@ -0,0 +1,6 @@ +# rubocop:disable all +class RemoveNoteIsAward < ActiveRecord::Migration + def change + remove_column :notes, :is_award, :boolean + end +end diff --git a/db/migrate/20160518200441_add_artifacts_expire_date_to_ci_builds.rb b/db/migrate/20160518200441_add_artifacts_expire_date_to_ci_builds.rb deleted file mode 100644 index 915167b038d..00000000000 --- a/db/migrate/20160518200441_add_artifacts_expire_date_to_ci_builds.rb +++ /dev/null @@ -1,5 +0,0 @@ -class AddArtifactsExpireDateToCiBuilds < ActiveRecord::Migration - def change - add_column :ci_builds, :artifacts_expire_at, :timestamp - end -end diff --git a/db/migrate/20160610140403_remove_notification_setting_not_null_constraints.rb b/db/migrate/20160610140403_remove_notification_setting_not_null_constraints.rb deleted file mode 100644 index 259abb08e47..00000000000 --- a/db/migrate/20160610140403_remove_notification_setting_not_null_constraints.rb +++ /dev/null @@ -1,11 +0,0 @@ -class RemoveNotificationSettingNotNullConstraints < ActiveRecord::Migration - def up - change_column :notification_settings, :source_type, :string, null: true - change_column :notification_settings, :source_id, :integer, null: true - end - - def down - change_column :notification_settings, :source_type, :string, null: false - change_column :notification_settings, :source_id, :integer, null: false - end -end diff --git a/db/migrate/20160610194713_remove_deprecated_issues_tracker_columns_from_projects.rb b/db/migrate/20160610194713_remove_deprecated_issues_tracker_columns_from_projects.rb deleted file mode 100644 index 477b2106dea..00000000000 --- a/db/migrate/20160610194713_remove_deprecated_issues_tracker_columns_from_projects.rb +++ /dev/null @@ -1,6 +0,0 @@ -class RemoveDeprecatedIssuesTrackerColumnsFromProjects < ActiveRecord::Migration - def change - remove_column :projects, :issues_tracker, :string, default: 'gitlab', null: false - remove_column :projects, :issues_tracker_id, :string - end -end diff --git a/db/migrate/20160610201627_migrate_users_notification_level.rb b/db/migrate/20160610201627_migrate_users_notification_level.rb deleted file mode 100644 index 760b766828e..00000000000 --- a/db/migrate/20160610201627_migrate_users_notification_level.rb +++ /dev/null @@ -1,21 +0,0 @@ -class MigrateUsersNotificationLevel < ActiveRecord::Migration - # Migrates only users who changed their default notification level :participating - # creating a new record on notification settings table - - def up - execute(%Q{ - INSERT INTO notification_settings - (user_id, level, created_at, updated_at) - (SELECT id, notification_level, created_at, updated_at FROM users WHERE notification_level != 1) - }) - end - - # Migrates from notification settings back to user notification_level - # If no value is found the default level of 1 will be used - def down - execute(%Q{ - UPDATE users u SET - notification_level = COALESCE((SELECT level FROM notification_settings WHERE user_id = u.id AND source_type IS NULL), 1) - }) - end -end diff --git a/db/migrate/20160610204157_add_deployments.rb b/db/migrate/20160610204157_add_deployments.rb deleted file mode 100644 index cb144ea8a6d..00000000000 --- a/db/migrate/20160610204157_add_deployments.rb +++ /dev/null @@ -1,27 +0,0 @@ -# See http://doc.gitlab.com/ce/development/migration_style_guide.html -# for more information on how to write migrations for GitLab. - -class AddDeployments < ActiveRecord::Migration - include Gitlab::Database::MigrationHelpers - - def change - create_table :deployments, force: true do |t| - t.integer :iid, null: false - t.integer :project_id, null: false - t.integer :environment_id, null: false - t.string :ref, null: false - t.boolean :tag, null: false - t.string :sha, null: false - t.integer :user_id - t.integer :deployable_id - t.string :deployable_type - t.datetime :created_at - t.datetime :updated_at - end - - add_index :deployments, :project_id - add_index :deployments, [:project_id, :iid], unique: true - add_index :deployments, [:project_id, :environment_id] - add_index :deployments, [:project_id, :environment_id, :iid] - end -end diff --git a/db/migrate/20160610204158_add_environments.rb b/db/migrate/20160610204158_add_environments.rb deleted file mode 100644 index e1c71d173c4..00000000000 --- a/db/migrate/20160610204158_add_environments.rb +++ /dev/null @@ -1,17 +0,0 @@ -# See http://doc.gitlab.com/ce/development/migration_style_guide.html -# for more information on how to write migrations for GitLab. - -class AddEnvironments < ActiveRecord::Migration - include Gitlab::Database::MigrationHelpers - - def change - create_table :environments, force: true do |t| - t.integer :project_id, null: false - t.string :name, null: false - t.datetime :created_at - t.datetime :updated_at - end - - add_index :environments, [:project_id, :name] - end -end diff --git a/db/migrate/20160610211845_add_environment_to_builds.rb b/db/migrate/20160610211845_add_environment_to_builds.rb deleted file mode 100644 index 990e445ac55..00000000000 --- a/db/migrate/20160610211845_add_environment_to_builds.rb +++ /dev/null @@ -1,10 +0,0 @@ -# See http://doc.gitlab.com/ce/development/migration_style_guide.html -# for more information on how to write migrations for GitLab. - -class AddEnvironmentToBuilds < ActiveRecord::Migration - include Gitlab::Database::MigrationHelpers - - def change - add_column :ci_builds, :environment, :string - end -end diff --git a/db/migrate/20160610301627_remove_notification_level_from_users.rb b/db/migrate/20160610301627_remove_notification_level_from_users.rb deleted file mode 100644 index 8afb14df2cf..00000000000 --- a/db/migrate/20160610301627_remove_notification_level_from_users.rb +++ /dev/null @@ -1,7 +0,0 @@ -class RemoveNotificationLevelFromUsers < ActiveRecord::Migration - include Gitlab::Database::MigrationHelpers - - def change - remove_column :users, :notification_level, :integer - end -end diff --git a/db/migrate/20160615142710_add_index_on_requested_at_to_members.rb b/db/migrate/20160615142710_add_index_on_requested_at_to_members.rb deleted file mode 100644 index 63f7392e54f..00000000000 --- a/db/migrate/20160615142710_add_index_on_requested_at_to_members.rb +++ /dev/null @@ -1,9 +0,0 @@ -class AddIndexOnRequestedAtToMembers < ActiveRecord::Migration - include Gitlab::Database::MigrationHelpers - - disable_ddl_transaction! - - def change - add_concurrent_index :members, :requested_at - end -end diff --git a/db/migrate/20160616084004_change_project_of_environment.rb b/db/migrate/20160616084004_change_project_of_environment.rb deleted file mode 100644 index cc1daf9b621..00000000000 --- a/db/migrate/20160616084004_change_project_of_environment.rb +++ /dev/null @@ -1,21 +0,0 @@ -# See http://doc.gitlab.com/ce/development/migration_style_guide.html -# for more information on how to write migrations for GitLab. - -class ChangeProjectOfEnvironment < ActiveRecord::Migration - include Gitlab::Database::MigrationHelpers - - # When using the methods "add_concurrent_index" or "add_column_with_default" - # you must disable the use of transactions as these methods can not run in an - # existing transaction. When using "add_concurrent_index" make sure that this - # method is the _only_ method called in the migration, any other changes - # should go in a separate migration. This ensures that upon failure _only_ the - # index creation fails and can be retried or reverted easily. - # - # To disable transactions uncomment the following line and remove these - # comments: - # disable_ddl_transaction! - - def change - change_column_null :environments, :project_id, true - end -end diff --git a/db/schema.rb b/db/schema.rb index d6a542a89fd..aac327797e7 100644 --- a/db/schema.rb +++ b/db/schema.rb @@ -11,7 +11,7 @@ # # It's strongly recommended that you check this file into your version control system. -ActiveRecord::Schema.define(version: 20160616084004) do +ActiveRecord::Schema.define(version: 20160608155312) do # These are extensions that must be enabled in order to support this database enable_extension "plpgsql" @@ -144,9 +144,9 @@ ActiveRecord::Schema.define(version: 20160616084004) do t.text "commands" t.integer "job_id" t.string "name" - t.boolean "deploy", default: false + t.boolean "deploy", default: false t.text "options" - t.boolean "allow_failure", default: false, null: false + t.boolean "allow_failure", default: false, null: false t.string "stage" t.integer "trigger_request_id" t.integer "stage_idx" @@ -161,8 +161,6 @@ ActiveRecord::Schema.define(version: 20160616084004) do t.text "artifacts_metadata" t.integer "erased_by_id" t.datetime "erased_at" - t.string "environment" - t.datetime "artifacts_expire_at" end add_index "ci_builds", ["commit_id", "stage_idx", "created_at"], name: "index_ci_builds_on_commit_id_and_stage_idx_and_created_at", using: :btree @@ -383,25 +381,6 @@ ActiveRecord::Schema.define(version: 20160616084004) do add_index "deploy_keys_projects", ["project_id"], name: "index_deploy_keys_projects_on_project_id", using: :btree - create_table "deployments", force: :cascade do |t| - t.integer "iid", null: false - t.integer "project_id", null: false - t.integer "environment_id", null: false - t.string "ref", null: false - t.boolean "tag", null: false - t.string "sha", null: false - t.integer "user_id" - t.integer "deployable_id" - t.string "deployable_type" - t.datetime "created_at" - t.datetime "updated_at" - end - - add_index "deployments", ["project_id", "environment_id", "iid"], name: "index_deployments_on_project_id_and_environment_id_and_iid", using: :btree - add_index "deployments", ["project_id", "environment_id"], name: "index_deployments_on_project_id_and_environment_id", using: :btree - add_index "deployments", ["project_id", "iid"], name: "index_deployments_on_project_id_and_iid", unique: true, using: :btree - add_index "deployments", ["project_id"], name: "index_deployments_on_project_id", using: :btree - create_table "emails", force: :cascade do |t| t.integer "user_id", null: false t.string "email", null: false @@ -412,15 +391,6 @@ ActiveRecord::Schema.define(version: 20160616084004) do add_index "emails", ["email"], name: "index_emails_on_email", unique: true, using: :btree add_index "emails", ["user_id"], name: "index_emails_on_user_id", using: :btree - create_table "environments", force: :cascade do |t| - t.integer "project_id" - t.string "name", null: false - t.datetime "created_at" - t.datetime "updated_at" - end - - add_index "environments", ["project_id", "name"], name: "index_environments_on_project_id_and_name", using: :btree - create_table "events", force: :cascade do |t| t.string "target_type" t.integer "target_id" @@ -566,13 +536,11 @@ ActiveRecord::Schema.define(version: 20160616084004) do t.string "invite_email" t.string "invite_token" t.datetime "invite_accepted_at" - t.datetime "requested_at" end add_index "members", ["access_level"], name: "index_members_on_access_level", using: :btree add_index "members", ["created_at", "id"], name: "index_members_on_created_at_and_id", using: :btree add_index "members", ["invite_token"], name: "index_members_on_invite_token", unique: true, using: :btree - add_index "members", ["requested_at"], name: "index_members_on_requested_at", using: :btree add_index "members", ["source_id", "source_type"], name: "index_members_on_source_id_and_source_type", using: :btree add_index "members", ["type"], name: "index_members_on_type", using: :btree add_index "members", ["user_id"], name: "index_members_on_user_id", using: :btree @@ -702,8 +670,8 @@ ActiveRecord::Schema.define(version: 20160616084004) do create_table "notification_settings", force: :cascade do |t| t.integer "user_id", null: false - t.integer "source_id" - t.string "source_type" + t.integer "source_id", null: false + t.string "source_type", null: false t.integer "level", default: 0, null: false t.datetime "created_at", null: false t.datetime "updated_at", null: false @@ -779,37 +747,39 @@ ActiveRecord::Schema.define(version: 20160616084004) do t.datetime "created_at" t.datetime "updated_at" t.integer "creator_id" - t.boolean "issues_enabled", default: true, null: false - t.boolean "merge_requests_enabled", default: true, null: false - t.boolean "wiki_enabled", default: true, null: false + t.boolean "issues_enabled", default: true, null: false + t.boolean "merge_requests_enabled", default: true, null: false + t.boolean "wiki_enabled", default: true, null: false t.integer "namespace_id" - t.boolean "snippets_enabled", default: true, null: false + t.string "issues_tracker", default: "gitlab", null: false + t.string "issues_tracker_id" + t.boolean "snippets_enabled", default: true, null: false t.datetime "last_activity_at" t.string "import_url" - t.integer "visibility_level", default: 0, null: false - t.boolean "archived", default: false, null: false + t.integer "visibility_level", default: 0, null: false + t.boolean "archived", default: false, null: false t.string "avatar" t.string "import_status" t.float "repository_size", default: 0.0 - t.integer "star_count", default: 0, null: false + t.integer "star_count", default: 0, null: false t.string "import_type" t.string "import_source" t.integer "commit_count", default: 0 t.text "import_error" t.integer "ci_id" - t.boolean "builds_enabled", default: true, null: false - t.boolean "shared_runners_enabled", default: true, null: false + t.boolean "builds_enabled", default: true, null: false + t.boolean "shared_runners_enabled", default: true, null: false t.string "runners_token" 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 "build_allow_git_fetch", default: true, null: false + t.integer "build_timeout", default: 3600, null: false t.boolean "pending_delete", default: false - t.boolean "public_builds", default: true, null: false + t.boolean "public_builds", default: true, null: false t.integer "pushes_since_gc", default: 0 t.boolean "last_repository_check_failed" t.datetime "last_repository_check_at" t.boolean "container_registry_enabled" - t.boolean "only_allow_merge_if_build_succeeds", default: false, null: false + t.boolean "only_allow_merge_if_build_succeeds", default: false, null: false t.boolean "has_external_issue_tracker" end @@ -1018,6 +988,7 @@ ActiveRecord::Schema.define(version: 20160616084004) do t.boolean "can_create_team", default: true, null: false t.string "state" t.integer "color_scheme_id", default: 1, null: false + t.integer "notification_level", default: 1, null: false t.datetime "password_expires_at" t.integer "created_by_id" t.datetime "last_credential_check_at" diff --git a/doc/README.md b/doc/README.md index 5d89d0c9821..d1345ab2493 100644 --- a/doc/README.md +++ b/doc/README.md @@ -28,7 +28,7 @@ - [Integration](integration/README.md) How to integrate with systems such as JIRA, Redmine, Twitter. - [Issue closing](customization/issue_closing.md) Customize how to close an issue from commit messages. - [Libravatar](customization/libravatar.md) Use Libravatar for user avatars. -- [Log system](administration/logs.md) Log system. +- [Log system](logs/logs.md) Log system. - [Environment Variables](administration/environment_variables.md) to configure GitLab. - [Operations](operations/README.md) Keeping GitLab up and running - [Raketasks](raketasks/README.md) Backups, maintenance, automatic webhook setup and the importing of projects. diff --git a/doc/administration/logs.md b/doc/administration/logs.md deleted file mode 100644 index 737b39db16c..00000000000 --- a/doc/administration/logs.md +++ /dev/null @@ -1,137 +0,0 @@ -## Log system - -GitLab has an advanced log system where everything is logged so that you -can analyze your instance using various system log files. In addition to -system log files, GitLab Enterprise Edition comes with Audit Events. -Find more about them [in Audit Events -documentation](http://docs.gitlab.com/ee/administration/audit_events.html) - -System log files are typically plain text in a standard log file format. -This guide talks about how to read and use these system log files. - -### production.log - -This file lives in `/var/log/gitlab/gitlab-rails/production.log` for -omnibus package or in `/home/git/gitlab/log/production.log` for -installations from source. - -It contains information about all performed requests. You can see the -URL and type of request, IP address and what exactly parts of code were -involved to service this particular request. Also you can see all SQL -request that have been performed and how much time it took. This task is -more useful for GitLab contributors and developers. Use part of this log -file when you are going to report bug. For example: - -``` -Started GET "/gitlabhq/yaml_db/tree/master" for 168.111.56.1 at 2015-02-12 19:34:53 +0200 -Processing by Projects::TreeController#show as HTML - Parameters: {"project_id"=>"gitlabhq/yaml_db", "id"=>"master"} - - ... [CUT OUT] - - Namespaces"."created_at" DESC, "namespaces"."id" DESC LIMIT 1 [["id", 26]] - CACHE (0.0ms) SELECT "members".* FROM "members" WHERE "members"."source_type" = 'Project' AND "members"."type" IN ('ProjectMember') AND "members"."source_id" = $1 AND "members"."source_type" = $2 AND "members"."user_id" = 1 ORDER BY "members"."created_at" DESC, "members"."id" DESC LIMIT 1 [["source_id", 18], ["source_type", "Project"]] - CACHE (0.0ms) SELECT "members".* FROM "members" WHERE "members"."source_type" = 'Project' AND "members". - (1.4ms) SELECT COUNT(*) FROM "merge_requests" WHERE "merge_requests"."target_project_id" = $1 AND ("merge_requests"."state" IN ('opened','reopened')) [["target_project_id", 18]] - Rendered layouts/nav/_project.html.haml (28.0ms) - Rendered layouts/_collapse_button.html.haml (0.2ms) - Rendered layouts/_flash.html.haml (0.1ms) - Rendered layouts/_page.html.haml (32.9ms) -Completed 200 OK in 166ms (Views: 117.4ms | ActiveRecord: 27.2ms) -``` - -In this example we can see that server processed an HTTP request with URL -`/gitlabhq/yaml_db/tree/master` from IP 168.111.56.1 at 2015-02-12 -19:34:53 +0200. Also we can see that request was processed by -`Projects::TreeController`. - -### application.log - -This file lives in `/var/log/gitlab/gitlab-rails/application.log` for -omnibus package or in `/home/git/gitlab/log/application.log` for -installations from source. - -It helps you discover events happening in your instance such as user creation, -project removing and so on. For example: - -``` -October 06, 2014 11:56: User "Administrator" (admin@example.com) was created -October 06, 2014 11:56: Documentcloud created a new project "Documentcloud / Underscore" -October 06, 2014 11:56: Gitlab Org created a new project "Gitlab Org / Gitlab Ce" -October 07, 2014 11:25: User "Claudie Hodkiewicz" (nasir_stehr@olson.co.uk) was removed -October 07, 2014 11:25: Project "project133" was removed -``` - -### githost.log - -This file lives in `/var/log/gitlab/gitlab-rails/githost.log` for -omnibus package or in `/home/git/gitlab/log/githost.log` for -installations from source. - -GitLab has to interact with Git repositories but in some rare cases -something can go wrong and in this case you will know what exactly -happened. This log file contains all failed requests from GitLab to Git -repositories. In the majority of cases this file will be useful for developers -only. For example: - -``` -December 03, 2014 13:20 -> ERROR -> Command failed [1]: /usr/bin/git --git-dir=/Users/vsizov/gitlab-development-kit/gitlab/tmp/tests/gitlab-satellites/group184/gitlabhq/.git --work-tree=/Users/vsizov/gitlab-development-kit/gitlab/tmp/tests/gitlab-satellites/group184/gitlabhq merge --no-ff -mMerge branch 'feature_conflict' into 'feature' source/feature_conflict - -error: failed to push some refs to '/Users/vsizov/gitlab-development-kit/repositories/gitlabhq/gitlab_git.git' -``` - -### sidekiq.log - -This file lives in `/var/log/gitlab/gitlab-rails/sidekiq.log` for -omnibus package or in `/home/git/gitlab/log/sidekiq.log` for -installations from source. - -GitLab uses background jobs for processing tasks which can take a long -time. All information about processing these jobs are written down to -this file. For example: - -``` -2014-06-10T07:55:20Z 2037 TID-tm504 ERROR: /opt/bitnami/apps/discourse/htdocs/vendor/bundle/ruby/1.9.1/gems/redis-3.0.7/lib/redis/client.rb:228:in `read' -2014-06-10T18:18:26Z 14299 TID-55uqo INFO: Booting Sidekiq 3.0.0 with redis options {:url=>"redis://localhost:6379/0", :namespace=>"sidekiq"} -``` - -### gitlab-shell.log - -This file lives in `/var/log/gitlab/gitlab-shell/gitlab-shell.log` for -omnibus package or in `/home/git/gitlab-shell/gitlab-shell.log` for -installations from source. - -GitLab shell is used by Gitlab for executing Git commands and provide -SSH access to Git repositories. For example: - -``` -I, [2015-02-13T06:17:00.671315 #9291] INFO -- : Adding project root/example.git at </var/opt/gitlab/git-data/repositories/root/dcdcdcdcd.git>. -I, [2015-02-13T06:17:00.679433 #9291] INFO -- : Moving existing hooks directory and symlinking global hooks directory for /var/opt/gitlab/git-data/repositories/root/example.git. -``` - -### unicorn\_stderr.log - -This file lives in `/var/log/gitlab/unicorn/unicorn_stderr.log` for -omnibus package or in `/home/git/gitlab/log/unicorn_stderr.log` for -installations from source. - -Unicorn is a high-performance forking Web server which is used for -serving the GitLab application. You can look at this log if, for -example, your application does not respond. This log contains all -information about the state of unicorn processes at any given time. - -``` -I, [2015-02-13T06:14:46.680381 #9047] INFO -- : Refreshing Gem list -I, [2015-02-13T06:14:56.931002 #9047] INFO -- : listening on addr=127.0.0.1:8080 fd=12 -I, [2015-02-13T06:14:56.931381 #9047] INFO -- : listening on addr=/var/opt/gitlab/gitlab-rails/sockets/gitlab.socket fd=13 -I, [2015-02-13T06:14:56.936638 #9047] INFO -- : master process ready -I, [2015-02-13T06:14:56.946504 #9092] INFO -- : worker=0 spawned pid=9092 -I, [2015-02-13T06:14:56.946943 #9092] INFO -- : worker=0 ready -I, [2015-02-13T06:14:56.947892 #9094] INFO -- : worker=1 spawned pid=9094 -I, [2015-02-13T06:14:56.948181 #9094] INFO -- : worker=1 ready -W, [2015-02-13T07:16:01.312916 #9094] WARN -- : #<Unicorn::HttpServer:0x0000000208f618>: worker (pid: 9094) exceeds memory limit (320626688 bytes > 247066940 bytes) -W, [2015-02-13T07:16:01.313000 #9094] WARN -- : Unicorn::WorkerKiller send SIGQUIT (pid: 9094) alive: 3621 sec (trial 1) -I, [2015-02-13T07:16:01.530733 #9047] INFO -- : reaped #<Process::Status: pid 9094 exit 0> worker=1 -I, [2015-02-13T07:16:01.534501 #13379] INFO -- : worker=1 spawned pid=13379 -I, [2015-02-13T07:16:01.534848 #13379] INFO -- : worker=1 ready -``` diff --git a/doc/administration/troubleshooting/sidekiq.md b/doc/administration/troubleshooting/sidekiq.md index b71f8fabbc8..a776cd3f05e 100644 --- a/doc/administration/troubleshooting/sidekiq.md +++ b/doc/administration/troubleshooting/sidekiq.md @@ -147,8 +147,7 @@ bt To output a backtrace from all threads at once: ``` -set pagination off -thread apply all bt +apply all thread bt ``` Once you're done debugging with `gdb`, be sure to detach from the process and diff --git a/doc/api/README.md b/doc/api/README.md index e3fc5a09f21..27c5962decf 100644 --- a/doc/api/README.md +++ b/doc/api/README.md @@ -8,39 +8,32 @@ under [`/lib/api`](https://gitlab.com/gitlab-org/gitlab-ce/tree/master/lib/api). Documentation for various API resources can be found separately in the following locations: -- [Branches](branches.md) -- [Builds](builds.md) -- [Build triggers](build_triggers.md) -- [Build Variables](build_variables.md) +- [Users](users.md) +- [Session](session.md) +- [Projects](projects.md) including setting Webhooks +- [Project Snippets](project_snippets.md) +- [Services](services.md) +- [Repositories](repositories.md) +- [Repository Files](repository_files.md) - [Commits](commits.md) -- [Deploy Keys](deploy_keys.md) -- [Groups](groups.md) +- [Tags](tags.md) +- [Branches](branches.md) +- [Merge Requests](merge_requests.md) - [Issues](issues.md) -- [Keys](keys.md) - [Labels](labels.md) -- [Merge Requests](merge_requests.md) - [Milestones](milestones.md) -- [Open source license templates](licenses.md) -- [Namespaces](namespaces.md) - [Notes](notes.md) (comments) -- [Projects](projects.md) including setting Webhooks -- [Project Snippets](project_snippets.md) -- [Repositories](repositories.md) -- [Repository Files](repository_files.md) -- [Runners](runners.md) -- [Services](services.md) -- [Session](session.md) -- [Settings](settings.md) +- [Deploy Keys](deploy_keys.md) - [System Hooks](system_hooks.md) -- [Tags](tags.md) -- [Users](users.md) - -### Internal CI API - -The following documentation is for the [internal CI API](ci/README.md): - -- [Builds](ci/builds.md) -- [Runners](ci/runners.md) +- [Groups](groups.md) +- [Namespaces](namespaces.md) +- [Settings](settings.md) +- [Keys](keys.md) +- [Builds](builds.md) +- [Build triggers](build_triggers.md) +- [Build Variables](build_variables.md) +- [Runners](runners.md) +- [Open source license templates](licenses.md) ## Authentication diff --git a/doc/api/builds.md b/doc/api/builds.md index de998944352..5669bd0cdda 100644 --- a/doc/api/builds.md +++ b/doc/api/builds.md @@ -21,85 +21,85 @@ Example of response ```json [ - { - "commit": { - "author_email": "admin@example.com", - "author_name": "Administrator", - "created_at": "2015-12-24T16:51:14.000+01:00", - "id": "0ff3ae198f8601a285adcf5c0fff204ee6fba5fd", - "message": "Test the CI integration.", - "short_id": "0ff3ae19", - "title": "Test the CI integration." - }, - "coverage": null, - "created_at": "2015-12-24T15:51:21.802Z", - "artifacts_file": { - "filename": "artifacts.zip", - "size": 1000 + { + "commit": { + "author_email": "admin@example.com", + "author_name": "Administrator", + "created_at": "2015-12-24T16:51:14.000+01:00", + "id": "0ff3ae198f8601a285adcf5c0fff204ee6fba5fd", + "message": "Test the CI integration.", + "short_id": "0ff3ae19", + "title": "Test the CI integration." + }, + "coverage": null, + "created_at": "2015-12-24T15:51:21.802Z", + "artifacts_file": { + "filename": "artifacts.zip", + "size": 1000 + }, + "finished_at": "2015-12-24T17:54:27.895Z", + "id": 7, + "name": "teaspoon", + "ref": "master", + "runner": null, + "stage": "test", + "started_at": "2015-12-24T17:54:27.722Z", + "status": "failed", + "tag": false, + "user": { + "avatar_url": "http://www.gravatar.com/avatar/e64c7d89f26bd1972efa854d13d7dd61?s=80&d=identicon", + "bio": null, + "created_at": "2015-12-21T13:14:24.077Z", + "id": 1, + "is_admin": true, + "linkedin": "", + "name": "Administrator", + "skype": "", + "state": "active", + "twitter": "", + "username": "root", + "web_url": "http://gitlab.dev/u/root", + "website_url": "" + } }, - "finished_at": "2015-12-24T17:54:27.895Z", - "id": 7, - "name": "teaspoon", - "ref": "master", - "runner": null, - "stage": "test", - "started_at": "2015-12-24T17:54:27.722Z", - "status": "failed", - "tag": false, - "user": { - "avatar_url": "http://www.gravatar.com/avatar/e64c7d89f26bd1972efa854d13d7dd61?s=80&d=identicon", - "bio": null, - "created_at": "2015-12-21T13:14:24.077Z", - "id": 1, - "is_admin": true, - "linkedin": "", - "name": "Administrator", - "skype": "", - "state": "active", - "twitter": "", - "username": "root", - "web_url": "http://gitlab.dev/u/root", - "website_url": "" - } - }, - { - "commit": { - "author_email": "admin@example.com", - "author_name": "Administrator", - "created_at": "2015-12-24T16:51:14.000+01:00", - "id": "0ff3ae198f8601a285adcf5c0fff204ee6fba5fd", - "message": "Test the CI integration.", - "short_id": "0ff3ae19", - "title": "Test the CI integration." - }, - "coverage": null, - "created_at": "2015-12-24T15:51:21.727Z", - "artifacts_file": null, - "finished_at": "2015-12-24T17:54:24.921Z", - "id": 6, - "name": "spinach:other", - "ref": "master", - "runner": null, - "stage": "test", - "started_at": "2015-12-24T17:54:24.729Z", - "status": "failed", - "tag": false, - "user": { - "avatar_url": "http://www.gravatar.com/avatar/e64c7d89f26bd1972efa854d13d7dd61?s=80&d=identicon", - "bio": null, - "created_at": "2015-12-21T13:14:24.077Z", - "id": 1, - "is_admin": true, - "linkedin": "", - "name": "Administrator", - "skype": "", - "state": "active", - "twitter": "", - "username": "root", - "web_url": "http://gitlab.dev/u/root", - "website_url": "" + { + "commit": { + "author_email": "admin@example.com", + "author_name": "Administrator", + "created_at": "2015-12-24T16:51:14.000+01:00", + "id": "0ff3ae198f8601a285adcf5c0fff204ee6fba5fd", + "message": "Test the CI integration.", + "short_id": "0ff3ae19", + "title": "Test the CI integration." + }, + "coverage": null, + "created_at": "2015-12-24T15:51:21.727Z", + "artifacts_file": null, + "finished_at": "2015-12-24T17:54:24.921Z", + "id": 6, + "name": "spinach:other", + "ref": "master", + "runner": null, + "stage": "test", + "started_at": "2015-12-24T17:54:24.729Z", + "status": "failed", + "tag": false, + "user": { + "avatar_url": "http://www.gravatar.com/avatar/e64c7d89f26bd1972efa854d13d7dd61?s=80&d=identicon", + "bio": null, + "created_at": "2015-12-21T13:14:24.077Z", + "id": 1, + "is_admin": true, + "linkedin": "", + "name": "Administrator", + "skype": "", + "state": "active", + "twitter": "", + "username": "root", + "web_url": "http://gitlab.dev/u/root", + "website_url": "" + } } - } ] ``` @@ -125,68 +125,68 @@ Example of response ```json [ - { - "commit": { - "author_email": "admin@example.com", - "author_name": "Administrator", - "created_at": "2015-12-24T16:51:14.000+01:00", - "id": "0ff3ae198f8601a285adcf5c0fff204ee6fba5fd", - "message": "Test the CI integration.", - "short_id": "0ff3ae19", - "title": "Test the CI integration." + { + "commit": { + "author_email": "admin@example.com", + "author_name": "Administrator", + "created_at": "2015-12-24T16:51:14.000+01:00", + "id": "0ff3ae198f8601a285adcf5c0fff204ee6fba5fd", + "message": "Test the CI integration.", + "short_id": "0ff3ae19", + "title": "Test the CI integration." + }, + "coverage": null, + "created_at": "2016-01-11T10:13:33.506Z", + "artifacts_file": null, + "finished_at": "2016-01-11T10:14:09.526Z", + "id": 69, + "name": "rubocop", + "ref": "master", + "runner": null, + "stage": "test", + "started_at": null, + "status": "canceled", + "tag": false, + "user": null }, - "coverage": null, - "created_at": "2016-01-11T10:13:33.506Z", - "artifacts_file": null, - "finished_at": "2016-01-11T10:14:09.526Z", - "id": 69, - "name": "rubocop", - "ref": "master", - "runner": null, - "stage": "test", - "started_at": null, - "status": "canceled", - "tag": false, - "user": null - }, - { - "commit": { - "author_email": "admin@example.com", - "author_name": "Administrator", - "created_at": "2015-12-24T16:51:14.000+01:00", - "id": "0ff3ae198f8601a285adcf5c0fff204ee6fba5fd", - "message": "Test the CI integration.", - "short_id": "0ff3ae19", - "title": "Test the CI integration." - }, - "coverage": null, - "created_at": "2015-12-24T15:51:21.957Z", - "artifacts_file": null, - "finished_at": "2015-12-24T17:54:33.913Z", - "id": 9, - "name": "brakeman", - "ref": "master", - "runner": null, - "stage": "test", - "started_at": "2015-12-24T17:54:33.727Z", - "status": "failed", - "tag": false, - "user": { - "avatar_url": "http://www.gravatar.com/avatar/e64c7d89f26bd1972efa854d13d7dd61?s=80&d=identicon", - "bio": null, - "created_at": "2015-12-21T13:14:24.077Z", - "id": 1, - "is_admin": true, - "linkedin": "", - "name": "Administrator", - "skype": "", - "state": "active", - "twitter": "", - "username": "root", - "web_url": "http://gitlab.dev/u/root", - "website_url": "" + { + "commit": { + "author_email": "admin@example.com", + "author_name": "Administrator", + "created_at": "2015-12-24T16:51:14.000+01:00", + "id": "0ff3ae198f8601a285adcf5c0fff204ee6fba5fd", + "message": "Test the CI integration.", + "short_id": "0ff3ae19", + "title": "Test the CI integration." + }, + "coverage": null, + "created_at": "2015-12-24T15:51:21.957Z", + "artifacts_file": null, + "finished_at": "2015-12-24T17:54:33.913Z", + "id": 9, + "name": "brakeman", + "ref": "master", + "runner": null, + "stage": "test", + "started_at": "2015-12-24T17:54:33.727Z", + "status": "failed", + "tag": false, + "user": { + "avatar_url": "http://www.gravatar.com/avatar/e64c7d89f26bd1972efa854d13d7dd61?s=80&d=identicon", + "bio": null, + "created_at": "2015-12-21T13:14:24.077Z", + "id": 1, + "is_admin": true, + "linkedin": "", + "name": "Administrator", + "skype": "", + "state": "active", + "twitter": "", + "username": "root", + "web_url": "http://gitlab.dev/u/root", + "website_url": "" + } } - } ] ``` @@ -211,42 +211,42 @@ Example of response ```json { - "commit": { - "author_email": "admin@example.com", - "author_name": "Administrator", - "created_at": "2015-12-24T16:51:14.000+01:00", - "id": "0ff3ae198f8601a285adcf5c0fff204ee6fba5fd", - "message": "Test the CI integration.", - "short_id": "0ff3ae19", - "title": "Test the CI integration." - }, - "coverage": null, - "created_at": "2015-12-24T15:51:21.880Z", - "artifacts_file": null, - "finished_at": "2015-12-24T17:54:31.198Z", - "id": 8, - "name": "rubocop", - "ref": "master", - "runner": null, - "stage": "test", - "started_at": "2015-12-24T17:54:30.733Z", - "status": "failed", - "tag": false, - "user": { - "avatar_url": "http://www.gravatar.com/avatar/e64c7d89f26bd1972efa854d13d7dd61?s=80&d=identicon", - "bio": null, - "created_at": "2015-12-21T13:14:24.077Z", - "id": 1, - "is_admin": true, - "linkedin": "", - "name": "Administrator", - "skype": "", - "state": "active", - "twitter": "", - "username": "root", - "web_url": "http://gitlab.dev/u/root", - "website_url": "" - } + "commit": { + "author_email": "admin@example.com", + "author_name": "Administrator", + "created_at": "2015-12-24T16:51:14.000+01:00", + "id": "0ff3ae198f8601a285adcf5c0fff204ee6fba5fd", + "message": "Test the CI integration.", + "short_id": "0ff3ae19", + "title": "Test the CI integration." + }, + "coverage": null, + "created_at": "2015-12-24T15:51:21.880Z", + "artifacts_file": null, + "finished_at": "2015-12-24T17:54:31.198Z", + "id": 8, + "name": "rubocop", + "ref": "master", + "runner": null, + "stage": "test", + "started_at": "2015-12-24T17:54:30.733Z", + "status": "failed", + "tag": false, + "user": { + "avatar_url": "http://www.gravatar.com/avatar/e64c7d89f26bd1972efa854d13d7dd61?s=80&d=identicon", + "bio": null, + "created_at": "2015-12-21T13:14:24.077Z", + "id": 1, + "is_admin": true, + "linkedin": "", + "name": "Administrator", + "skype": "", + "state": "active", + "twitter": "", + "username": "root", + "web_url": "http://gitlab.dev/u/root", + "website_url": "" + } } ``` @@ -323,28 +323,28 @@ Example of response ```json { - "commit": { - "author_email": "admin@example.com", - "author_name": "Administrator", - "created_at": "2015-12-24T16:51:14.000+01:00", - "id": "0ff3ae198f8601a285adcf5c0fff204ee6fba5fd", - "message": "Test the CI integration.", - "short_id": "0ff3ae19", - "title": "Test the CI integration." - }, - "coverage": null, - "created_at": "2016-01-11T10:13:33.506Z", - "artifacts_file": null, - "finished_at": "2016-01-11T10:14:09.526Z", - "id": 69, - "name": "rubocop", - "ref": "master", - "runner": null, - "stage": "test", - "started_at": null, - "status": "canceled", - "tag": false, - "user": null + "commit": { + "author_email": "admin@example.com", + "author_name": "Administrator", + "created_at": "2015-12-24T16:51:14.000+01:00", + "id": "0ff3ae198f8601a285adcf5c0fff204ee6fba5fd", + "message": "Test the CI integration.", + "short_id": "0ff3ae19", + "title": "Test the CI integration." + }, + "coverage": null, + "created_at": "2016-01-11T10:13:33.506Z", + "artifacts_file": null, + "finished_at": "2016-01-11T10:14:09.526Z", + "id": 69, + "name": "rubocop", + "ref": "master", + "runner": null, + "stage": "test", + "started_at": null, + "status": "canceled", + "tag": false, + "user": null } ``` @@ -369,28 +369,28 @@ Example of response ```json { - "commit": { - "author_email": "admin@example.com", - "author_name": "Administrator", - "created_at": "2015-12-24T16:51:14.000+01:00", - "id": "0ff3ae198f8601a285adcf5c0fff204ee6fba5fd", - "message": "Test the CI integration.", - "short_id": "0ff3ae19", - "title": "Test the CI integration." - }, - "coverage": null, - "created_at": "2016-01-11T10:13:33.506Z", - "artifacts_file": null, - "finished_at": null, - "id": 69, - "name": "rubocop", - "ref": "master", - "runner": null, - "stage": "test", - "started_at": null, - "status": "pending", - "tag": false, - "user": null + "commit": { + "author_email": "admin@example.com", + "author_name": "Administrator", + "created_at": "2015-12-24T16:51:14.000+01:00", + "id": "0ff3ae198f8601a285adcf5c0fff204ee6fba5fd", + "message": "Test the CI integration.", + "short_id": "0ff3ae19", + "title": "Test the CI integration." + }, + "coverage": null, + "created_at": "2016-01-11T10:13:33.506Z", + "artifacts_file": null, + "finished_at": null, + "id": 69, + "name": "rubocop", + "ref": "master", + "runner": null, + "stage": "test", + "started_at": null, + "status": "pending", + "tag": false, + "user": null } ``` @@ -419,77 +419,27 @@ Example of response ```json { - "commit": { - "author_email": "admin@example.com", - "author_name": "Administrator", - "created_at": "2015-12-24T16:51:14.000+01:00", - "id": "0ff3ae198f8601a285adcf5c0fff204ee6fba5fd", - "message": "Test the CI integration.", - "short_id": "0ff3ae19", - "title": "Test the CI integration." - }, - "coverage": null, - "download_url": null, - "id": 69, - "name": "rubocop", - "ref": "master", - "runner": null, - "stage": "test", - "created_at": "2016-01-11T10:13:33.506Z", - "started_at": "2016-01-11T10:13:33.506Z", - "finished_at": "2016-01-11T10:15:10.506Z", - "status": "failed", - "tag": false, - "user": null -} -``` - -## Keep artifacts - -Prevents artifacts from being deleted when expiration is set. - -``` -POST /projects/:id/builds/:build_id/artifacts/keep -``` - -Parameters - -| Attribute | Type | required | Description | -|-------------|---------|----------|---------------------| -| `id` | integer | yes | The ID of a project | -| `build_id` | integer | yes | The ID of a build | - -Example request: - -``` -curl -X POST -H "PRIVATE-TOKEN: 9koXpg98eAheJpvBs5tK" "https://gitlab.example.com/api/v3/projects/1/builds/1/artifacts/keep" -``` - -Example response: - -```json -{ - "commit": { - "author_email": "admin@example.com", - "author_name": "Administrator", - "created_at": "2015-12-24T16:51:14.000+01:00", - "id": "0ff3ae198f8601a285adcf5c0fff204ee6fba5fd", - "message": "Test the CI integration.", - "short_id": "0ff3ae19", - "title": "Test the CI integration." - }, - "coverage": null, - "download_url": null, - "id": 69, - "name": "rubocop", - "ref": "master", - "runner": null, - "stage": "test", - "created_at": "2016-01-11T10:13:33.506Z", - "started_at": "2016-01-11T10:13:33.506Z", - "finished_at": "2016-01-11T10:15:10.506Z", - "status": "failed", - "tag": false, - "user": null + "commit": { + "author_email": "admin@example.com", + "author_name": "Administrator", + "created_at": "2015-12-24T16:51:14.000+01:00", + "id": "0ff3ae198f8601a285adcf5c0fff204ee6fba5fd", + "message": "Test the CI integration.", + "short_id": "0ff3ae19", + "title": "Test the CI integration." + }, + "coverage": null, + "download_url": null, + "id": 69, + "name": "rubocop", + "ref": "master", + "runner": null, + "stage": "test", + "created_at": "2016-01-11T10:13:33.506Z", + "started_at": "2016-01-11T10:13:33.506Z", + "finished_at": "2016-01-11T10:15:10.506Z", + "status": "failed", + "tag": false, + "user": null } ``` diff --git a/doc/api/ci/README.md b/doc/api/ci/README.md deleted file mode 100644 index 96a281e27c8..00000000000 --- a/doc/api/ci/README.md +++ /dev/null @@ -1,24 +0,0 @@ -# GitLab CI API - -## Purpose - -The main purpose of GitLab CI API is to provide the necessary data and context -for GitLab CI Runners. - -All relevant information about the consumer API can be found in a -[separate document](../../api/README.md). - -## API Prefix - -The current CI API prefix is `/ci/api/v1`. - -You need to prepend this prefix to all examples in this documentation, like: - -```bash -GET /ci/api/v1/builds/:id/artifacts -``` - -## Resources - -- [Builds](builds.md) -- [Runners](runners.md) diff --git a/doc/api/ci/builds.md b/doc/api/ci/builds.md deleted file mode 100644 index d779463fd8c..00000000000 --- a/doc/api/ci/builds.md +++ /dev/null @@ -1,138 +0,0 @@ -# Builds API - -API used by runners to receive and update builds. - ->**Note:** -This API is intended to be used only by Runners as their own -communication channel. For the consumer API see the -[Builds API](../builds.md). - -## Authentication - -This API uses two types of authentication: - -1. Unique Runner's token which is the token assigned to the Runner after it - has been registered. - -2. Using the build authorization token. - This is project's CI token that can be found under the **Builds** section of - a project's settings. The build authorization token can be passed as a - parameter or a value of `BUILD-TOKEN` header. - -These two methods of authentication are interchangeable. - -## Builds - -### Runs oldest pending build by runner - -``` -POST /ci/api/v1/builds/register -``` - -| Attribute | Type | Required | Description | -|-----------|---------|----------|---------------------| -| `token` | string | yes | Unique runner token | - - -``` -curl -X POST "https://gitlab.example.com/ci/api/v1/builds/register" -F "token=t0k3n" -``` - -### Update details of an existing build - -``` -PUT /ci/api/v1/builds/:id -``` - -| Attribute | Type | Required | Description | -|-----------|---------|----------|----------------------| -| `id` | integer | yes | The ID of a project | -| `token` | string | yes | Unique runner token | -| `state` | string | no | The state of a build | -| `trace` | string | no | The trace of a build | - -``` -curl -X PUT "https://gitlab.example.com/ci/api/v1/builds/1234" -F "token=t0k3n" -F "state=running" -F "trace=Running git clone...\n" -``` - -### Incremental build trace update - -Using this method you need to send trace content as a request body. You also need to provide the `Content-Range` header -with a range of sent trace part. Note that you need to send parts in the proper order, so the begining of the part -must start just after the end of the previous part. If you provide the wrong part, then GitLab CI API will return `416 -Range Not Satisfiable` response with a header `Range: 0-X`, where `X` is the current trace length. - -For example, if you receive `Range: 0-11` in the response, then your next part must contain a `Content-Range: 11-...` -header and a trace part covered by this range. - -For a valid update API will return `202` response with: -* `Build-Status: {status}` header containing current status of the build, -* `Range: 0-{length}` header with the current trace length. - -``` -PATCH /ci/api/v1/builds/:id/trace.txt -``` - -Parameters: - -| Attribute | Type | Required | Description | -|-----------|---------|----------|----------------------| -| `id` | integer | yes | The ID of a build | - -Headers: - -| Attribute | Type | Required | Description | -|-----------------|---------|----------|-----------------------------------| -| `BUILD-TOKEN` | string | yes | The build authorization token | -| `Content-Range` | string | yes | Bytes range of trace that is sent | - -``` -curl -X PATCH "https://gitlab.example.com/ci/api/v1/builds/1234/trace.txt" -H "BUILD-TOKEN=build_t0k3n" -H "Content-Range=0-21" -d "Running git clone...\n" -``` - - -### Upload artifacts to build - -``` -POST /ci/api/v1/builds/:id/artifacts -``` - -| Attribute | Type | Required | Description | -|-----------|---------|----------|-------------------------------| -| `id` | integer | yes | The ID of a build | -| `token` | string | yes | The build authorization token | -| `file` | mixed | yes | Artifacts file | - -``` -curl -X POST "https://gitlab.example.com/ci/api/v1/builds/1234/artifacts" -F "token=build_t0k3n" -F "file=@/path/to/file" -``` - -### Download the artifacts file from build - -``` -GET /ci/api/v1/builds/:id/artifacts -``` - -| Attribute | Type | Required | Description | -|-----------|---------|----------|-------------------------------| -| `id` | integer | yes | The ID of a build | -| `token` | string | yes | The build authorization token | - -``` -curl "https://gitlab.example.com/ci/api/v1/builds/1234/artifacts" -F "token=build_t0k3n" -``` - -### Remove the artifacts file from build - -``` -DELETE /ci/api/v1/builds/:id/artifacts -``` - -| Attribute | Type | Required | Description | -|-----------|---------|----------|-------------------------------| -| ` id` | integer | yes | The ID of a build | -| `token` | string | yes | The build authorization token | - -``` -curl -X DELETE "https://gitlab.example.com/ci/api/v1/builds/1234/artifacts" -F "token=build_t0k3n" -``` diff --git a/doc/api/ci/runners.md b/doc/api/ci/runners.md deleted file mode 100644 index 96b3c42f773..00000000000 --- a/doc/api/ci/runners.md +++ /dev/null @@ -1,57 +0,0 @@ -# Runners API - -API used by Runners to register and delete themselves. - ->**Note:** -This API is intended to be used only by Runners as their own -communication channel. For the consumer API see the -[new Runners API](../runners.md). - -## Authentication - -This API uses two types of authentication: - -1. Unique Runner's token, which is the token assigned to the Runner after it - has been registered. - -2. Using Runners' registration token. - This is a token that can be found in project's settings. - It can also be found in the **Admin > Runners** settings area. - There are two types of tokens you can pass: shared Runner registration - token or project specific registration token. - -## Register a new runner - -Used to make GitLab CI aware of available runners. - -```sh -POST /ci/api/v1/runners/register -``` - -| Attribute | Type | Required | Description | -| --------- | ------- | --------- | ----------- | -| `token` | string | yes | Runner's registration token | - -Example request: - -```sh -curl -X POST "https://gitlab.example.com/ci/api/v1/runners/register" -F "token=t0k3n" -``` - -## Delete a Runner - -Used to remove a Runner. - -```sh -DELETE /ci/api/v1/runners/delete -``` - -| Attribute | Type | Required | Description | -| --------- | ------- | --------- | ----------- | -| `token` | string | yes | Runner's registration token | - -Example request: - -```sh -curl -X DELETE "https://gitlab.example.com/ci/api/v1/runners/delete" -F "token=t0k3n" -``` diff --git a/doc/api/issues.md b/doc/api/issues.md index 0bc82ef9edb..fc7a7ae0c0c 100644 --- a/doc/api/issues.md +++ b/doc/api/issues.md @@ -365,9 +365,6 @@ target project is not found, error `404` is returned. If the target project equals the source project or the user has insufficient permissions to move an issue, error `400` together with an explaining error message is returned. -If a given label and/or milestone with the same name also exists in the target -project, it will then be assigned to the issue that is being moved. - ``` POST /projects/:id/issues/:issue_id/move ``` diff --git a/doc/ci/README.md b/doc/ci/README.md index ef72df97ce6..4abc45bf9bb 100644 --- a/doc/ci/README.md +++ b/doc/ci/README.md @@ -14,5 +14,5 @@ - [Trigger builds through the API](triggers/README.md) - [Build artifacts](build_artifacts/README.md) - [User permissions](permissions/README.md) -- [API](../../api/ci/README.md) +- [API](api/README.md) - [CI services (linked docker containers)](services/README.md) diff --git a/doc/ci/api/README.md b/doc/ci/api/README.md index 4ca8d92d7cc..aea808007fc 100644 --- a/doc/ci/api/README.md +++ b/doc/ci/api/README.md @@ -1,3 +1,22 @@ # GitLab CI API -This document was moved to a [new location](../../api/ci/README.md). +## Purpose + +Main purpose of GitLab CI API is to provide necessary data and context for +GitLab CI Runners. + +For consumer API take a look at this [documentation](../../api/README.md) where +you will find all relevant information. + +## API Prefix + +Current CI API prefix is `/ci/api/v1`. + +You need to prepend this prefix to all examples in this documentation, like: + + GET /ci/api/v1/builds/:id/artifacts + +## Resources + +- [Builds](builds.md) +- [Runners](runners.md) diff --git a/doc/ci/api/builds.md b/doc/ci/api/builds.md index f5bd3181c02..79761a893da 100644 --- a/doc/ci/api/builds.md +++ b/doc/ci/api/builds.md @@ -1,3 +1,139 @@ # Builds API -This document was moved to a [new location](../../api/ci/builds.md). +API used by runners to receive and update builds. + +_**Note:** This API is intended to be used only by Runners as their own +communication channel. For the consumer API see the +[Builds API](../../api/builds.md)._ + +## Authentication + +This API uses two types of authentication: + +1. Unique runner's token + + Token assigned to runner after it has been registered. + +2. Using build authorization token + + This is project's CI token that can be found in Continuous Integration + project settings. + + Build authorization token can be passed as a parameter or a value of + `BUILD-TOKEN` header. This method are interchangeable. + +## Builds + +### Runs oldest pending build by runner + +``` +POST /ci/api/v1/builds/register +``` + +| Attribute | Type | Required | Description | +|-----------|---------|----------|---------------------| +| `token` | string | yes | Unique runner token | + + +``` +curl -X POST "https://gitlab.example.com/ci/api/v1/builds/register" -F "token=t0k3n" +``` + +### Update details of an existing build + +``` +PUT /ci/api/v1/builds/:id +``` + +| Attribute | Type | Required | Description | +|-----------|---------|----------|----------------------| +| `id` | integer | yes | The ID of a project | +| `token` | string | yes | Unique runner token | +| `state` | string | no | The state of a build | +| `trace` | string | no | The trace of a build | + +``` +curl -X PUT "https://gitlab.example.com/ci/api/v1/builds/1234" -F "token=t0k3n" -F "state=running" -F "trace=Running git clone...\n" +``` + +### Incremental build trace update + +Using this method you need to send trace content as a request body. You also need to provide the `Content-Range` header +with a range of sent trace part. Note that you need to send parts in the proper order, so the begining of the part +must start just after the end of the previous part. If you provide the wrong part, then GitLab CI API will return `416 +Range Not Satisfiable` response with a header `Range: 0-X`, where `X` is the current trace length. + +For example, if you receive `Range: 0-11` in the response, then your next part must contain a `Content-Range: 11-...` +header and a trace part covered by this range. + +For a valid update API will return `202` response with: +* `Build-Status: {status}` header containing current status of the build, +* `Range: 0-{length}` header with the current trace length. + +``` +PATCH /ci/api/v1/builds/:id/trace.txt +``` + +Parameters: + +| Attribute | Type | Required | Description | +|-----------|---------|----------|----------------------| +| `id` | integer | yes | The ID of a build | + +Headers: + +| Attribute | Type | Required | Description | +|-----------------|---------|----------|-----------------------------------| +| `BUILD-TOKEN` | string | yes | The build authorization token | +| `Content-Range` | string | yes | Bytes range of trace that is sent | + +``` +curl -X PATCH "https://gitlab.example.com/ci/api/v1/builds/1234/trace.txt" -H "BUILD-TOKEN=build_t0k3n" -H "Content-Range=0-21" -d "Running git clone...\n" +``` + + +### Upload artifacts to build + +``` +POST /ci/api/v1/builds/:id/artifacts +``` + +| Attribute | Type | Required | Description | +|-----------|---------|----------|-------------------------------| +| `id` | integer | yes | The ID of a build | +| `token` | string | yes | The build authorization token | +| `file` | mixed | yes | Artifacts file | + +``` +curl -X POST "https://gitlab.example.com/ci/api/v1/builds/1234/artifacts" -F "token=build_t0k3n" -F "file=@/path/to/file" +``` + +### Download the artifacts file from build + +``` +GET /ci/api/v1/builds/:id/artifacts +``` + +| Attribute | Type | Required | Description | +|-----------|---------|----------|-------------------------------| +| `id` | integer | yes | The ID of a build | +| `token` | string | yes | The build authorization token | + +``` +curl "https://gitlab.example.com/ci/api/v1/builds/1234/artifacts" -F "token=build_t0k3n" +``` + +### Remove the artifacts file from build + +``` +DELETE /ci/api/v1/builds/:id/artifacts +``` + +| Attribute | Type | Required | Description | +|-----------|---------|----------|-------------------------------| +| ` id` | integer | yes | The ID of a build | +| `token` | string | yes | The build authorization token | + +``` +curl -X DELETE "https://gitlab.example.com/ci/api/v1/builds/1234/artifacts" -F "token=build_t0k3n" +``` diff --git a/doc/ci/api/runners.md b/doc/ci/api/runners.md index b14ea99db76..2f01da4bd76 100644 --- a/doc/ci/api/runners.md +++ b/doc/ci/api/runners.md @@ -1,3 +1,46 @@ # Runners API -This document was moved to a [new location](../../api/ci/runners.md). +API used by runners to register and delete themselves. + +_**Note:** This API is intended to be used only by Runners as their own +communication channel. For the consumer API see the +[new Runners API](../../api/runners.md)._ + +## Authentication + +This API uses two types of authentication: + +1. Unique runner's token + + Token assigned to runner after it has been registered. + +2. Using runners' registration token + + This is a token that can be found in project's settings. + It can be also found in Admin area » Runners settings. + + There are two types of tokens you can pass - shared runner registration + token or project specific registration token. + +## Runners + +### Register a new runner + +Used to make GitLab CI aware of available runners. + + POST /ci/api/v1/runners/register + +Parameters: + + * `token` (required) - Registration token + + +### Delete a runner + +Used to remove runner. + + DELETE /ci/api/v1/runners/delete + +Parameters: + + * `token` (required) - Unique runner token diff --git a/doc/ci/docker/using_docker_build.md b/doc/ci/docker/using_docker_build.md index 7f83f846454..ca52a483a59 100644 --- a/doc/ci/docker/using_docker_build.md +++ b/doc/ci/docker/using_docker_build.md @@ -4,14 +4,14 @@ GitLab CI allows you to use Docker Engine to build and test docker-based project **This also allows to you to use `docker-compose` and other docker-enabled tools.** -One of the new trends in Continuous Integration/Deployment is to: +This is one of new trends in Continuous Integration/Deployment to: -1. create an application image, -1. run tests against the created image, -1. push image to a remote registry, and -1. deploy to a server from the pushed image. +1. create application image, +1. run test against created image, +1. push image to remote registry, +1. deploy server from pushed image -It's also useful when your application already has the `Dockerfile` that can be used to create and test an image: +It's also useful in case when your application already has the `Dockerfile` that can be used to create and test image: ```bash $ docker build -t my-image dockerfiles/ $ docker run my-docker-image /script/to/run/tests @@ -19,25 +19,24 @@ $ docker tag my-image my-registry:5000/my-image $ docker push my-registry:5000/my-image ``` -This requires special configuration of GitLab Runner to enable `docker` support during builds. +However, this requires special configuration of GitLab Runner to enable `docker` support during build. +**This requires running GitLab Runner in privileged mode which can be harmful when untrusted code is run.** -## Runner Configuration +There are two methods to enable the use of `docker build` and `docker run` during build. -There are three methods to enable the use of `docker build` and `docker run` during builds; each with their own tradeoffs. - -### Use shell executor +## 1. Use shell executor The simplest approach is to install GitLab Runner in `shell` execution mode. -GitLab Runner then executes build scripts as the `gitlab-runner` user. +GitLab Runner then executes build scripts as `gitlab-runner` user. 1. Install [GitLab Runner](https://gitlab.com/gitlab-org/gitlab-ci-multi-runner/#installation). 1. During GitLab Runner installation select `shell` as method of executing build scripts or use command: ```bash - $ sudo gitlab-ci-multi-runner register -n \ + $ sudo gitlab-runner register -n \ --url https://gitlab.com/ci \ - --registration-token REGISTRATION_TOKEN \ + --token RUNNER_TOKEN \ --executor shell --description "My Runner" ``` @@ -71,18 +70,16 @@ GitLab Runner then executes build scripts as the `gitlab-runner` user. 5. You can now use `docker` command and install `docker-compose` if needed. -> **Note:** -* By adding `gitlab-runner` to the `docker` group you are effectively granting `gitlab-runner` full root permissions. -For more information please read [On Docker security: `docker` group considered harmful](https://www.andreas-jung.com/contents/on-docker-security-docker-group-considered-harmful). +6. However, by adding `gitlab-runner` to `docker` group you are effectively granting `gitlab-runner` full root permissions. +For more information please checkout [On Docker security: `docker` group considered harmful](https://www.andreas-jung.com/contents/on-docker-security-docker-group-considered-harmful). -### Use docker-in-docker executor +## 2. Use docker-in-docker executor -The second approach is to use the special docker-in-docker (dind) -[Docker image](https://hub.docker.com/_/docker/) with all tools installed +The second approach is to use the special Docker image with all tools installed (`docker` and `docker-compose`) and run the build script in context of that image in privileged mode. -In order to do that, follow the steps: +In order to do that follow the steps: 1. Install [GitLab Runner](https://gitlab.com/gitlab-org/gitlab-ci-multi-runner/#installation). @@ -90,9 +87,9 @@ In order to do that, follow the steps: mode: ```bash - sudo gitlab-ci-multi-runner register -n \ + sudo gitlab-runner register -n \ --url https://gitlab.com/ci \ - --registration-token REGISTRATION_TOKEN \ + --token RUNNER_TOKEN \ --executor docker \ --description "My Docker Runner" \ --docker-image "docker:latest" \ @@ -122,7 +119,11 @@ In order to do that, follow the steps: Insecure = false ``` -1. You can now use `docker` in the build script (note the inclusion of the `docker:dind` service): + If you want to use the Shared Runners available on your GitLab CE/EE + installation in order to build Docker images, then make sure that your + Shared Runners configuration has the `privileged` mode set to `true`. + +1. You can now use `docker` from build script: ```yaml image: docker:latest @@ -140,177 +141,14 @@ In order to do that, follow the steps: - docker run my-docker-image /script/to/run/tests ``` -Docker-in-Docker works well, and is the recommended configuration, but it is not without its own challenges: -* By enabling `--docker-privileged`, you are effectively disabling all of -the security mechanisms of containers and exposing your host to privilege -escalation which can lead to container breakout. For more information, check out the official Docker documentation on -[Runtime privilege and Linux capabilities][docker-cap]. -* Using docker-in-docker, each build is in a clean environment without the past -history. Concurrent builds work fine because every build gets it's own instance of docker engine so they won't conflict with each other. But this also means builds can be slower because there's no caching of layers. -* By default, `docker:dind` uses `--storage-driver vfs` which is the slowest form -offered. - -An example project using this approach can be found here: https://gitlab.com/gitlab-examples/docker. - -### Use Docker socket binding - -The third approach is to bind-mount `/var/run/docker.sock` into the container so that docker is available in the context of that image. - -In order to do that, follow the steps: - -1. Install [GitLab Runner](https://gitlab.com/gitlab-org/gitlab-ci-multi-runner/#installation). - -1. Register GitLab Runner from the command line to use `docker` and share `/var/run/docker.sock`: - - ```bash - sudo gitlab-ci-multi-runner register -n \ - --url https://gitlab.com/ci \ - --registration-token REGISTRATION_TOKEN \ - --executor docker \ - --description "My Docker Runner" \ - --docker-image "docker:latest" \ - --docker-volumes /var/run/docker.sock:/var/run/docker.sock - ``` - - The above command will register a new Runner to use the special - `docker:latest` image which is provided by Docker. **Notice that it's using - the Docker daemon of the Runner itself, and any containers spawned by docker commands will be siblings of the Runner rather than children of the runner.** This may have complications and limitations that are unsuitable for your workflow. - - The above command will create a `config.toml` entry similar to this: - - ``` - [[runners]] - url = "https://gitlab.com/ci" - token = REGISTRATION_TOKEN - executor = "docker" - [runners.docker] - tls_verify = false - image = "docker:latest" - privileged = false - disable_cache = false - volumes = ["/var/run/docker.sock", "/cache"] - [runners.cache] - Insecure = false - ``` - -1. You can now use `docker` in the build script (note that you don't need to include the `docker:dind` service as when using the Docker in Docker executor): - - ```yaml - image: docker:latest - - before_script: - - docker info - - build: - stage: build - script: - - docker build -t my-docker-image . - - docker run my-docker-image /script/to/run/tests - ``` - -While the above method avoids using Docker in privileged mode, you should be aware of the following implications: -* By sharing the docker daemon, you are effectively disabling all -the security mechanisms of containers and exposing your host to privilege -escalation which can lead to container breakout. For example, if a project -ran `docker rm -f $(docker ps -a -q)` it would remove the GitLab Runner -containers. -* Concurrent builds may not work; if your tests -create containers with specific names, they may conflict with each other. -* Sharing files and directories from the source repo into containers may not -work as expected since volume mounting is done in the context of the host -machine, not the build container. -e.g. `docker run --rm -t -i -v $(pwd)/src:/home/app/src test-image:latest run_app_tests` - -## Using the GitLab Container Registry - -> **Note:** -This feature requires GitLab 8.8 and GitLab Runner 1.2. - -Once you've built a Docker image, you can push it up to the built-in [GitLab Container Registry](../../container_registry/README.md). For example, if you're using -docker-in-docker on your runners, this is how your `.gitlab-ci.yml` could look: - - -```yaml - build: - image: docker:latest - services: - - docker:dind - stage: build - script: - - docker login -u gitlab-ci-token -p $CI_BUILD_TOKEN registry.example.com - - docker build -t registry.example.com/group/project:latest . - - docker push registry.example.com/group/project:latest -``` +1. However, by enabling `--docker-privileged` you are effectively disabling all + the security mechanisms of containers and exposing your host to privilege + escalation which can lead to container breakout. -You have to use the credentials of the special `gitlab-ci-token` user with its -password stored in `$CI_BUILD_TOKEN` in order to push to the Registry connected -to your project. This allows you to automate building and deployment of your -Docker images. - -Here's a more elaborate example that splits up the tasks into 4 pipeline stages, -including two tests that run in parallel. The build is stored in the container -registry and used by subsequent stages, downloading the image -when needed. Changes to `master` also get tagged as `latest` and deployed using -an application-specific deploy script: - -```yaml -image: docker:latest -services: -- docker:dind - -stages: -- build -- test -- release -- deploy - -variables: - CONTAINER_TEST_IMAGE: registry.example.com/my-group/my-project:$CI_BUILD_REF_NAME - CONTAINER_RELEASE_IMAGE: registry.example.com/my-group/my-project:latest - -before_script: - - docker login -u gitlab-ci-token -p $CI_BUILD_TOKEN registry.example.com - -build: - stage: build - script: - - docker build --pull -t $CONTAINER_TEST_IMAGE . - - docker push $CONTAINER_TEST_IMAGE - -test1: - stage: test - script: - - docker pull $CONTAINER_TEST_IMAGE - - docker run $CONTAINER_TEST_IMAGE /script/to/run/tests - -test2: - stage: test - script: - - docker pull $CONTAINER_TEST_IMAGE - - docker run $CONTAINER_TEST_IMAGE /script/to/run/another/test - -release-image: - stage: release - script: - - docker pull $CONTAINER_TEST_IMAGE - - docker tag $CONTAINER_TEST_IMAGE $CONTAINER_RELEASE_IMAGE - - docker push $CONTAINER_RELEASE_IMAGE - only: - - master - -deploy: - stage: deploy - script: - - ./deploy.sh - only: - - master -``` + For more information, check out the official Docker documentation on + [Runtime privilege and Linux capabilities][docker-cap]. -Some things you should be aware of when using the Container Registry: -* You must log in to the container registry before running commands. Putting this in `before_script` will run it before each build job. -* Using `docker build --pull` makes sure that Docker fetches any changes to base images before building just in case your cache is stale. It takes slightly longer, but means you don’t get stuck without security patches to base images. -* Doing an explicit `docker pull` before each `docker run` makes sure to fetch the latest image that was just built. This is especially important if you are using multiple runners that cache images locally. Using the git SHA in your image tag makes this less necessary since each build will be unique and you shouldn't ever have a stale image, but it's still possible if you re-build a given commit after a dependency has changed. -* You don't want to build directly to `latest` in case there are multiple builds happening simultaneously. +An example project using this approach can be found here: https://gitlab.com/gitlab-examples/docker. [docker-in-docker]: https://blog.docker.com/2013/09/docker-can-now-run-within-docker/ [docker-cap]: https://docs.docker.com/engine/reference/run/#runtime-privilege-and-linux-capabilities diff --git a/doc/ci/docker/using_docker_images.md b/doc/ci/docker/using_docker_images.md index a849905ac6b..56ac2195c49 100644 --- a/doc/ci/docker/using_docker_images.md +++ b/doc/ci/docker/using_docker_images.md @@ -23,7 +23,7 @@ To use GitLab Runner with docker you need to register a new runner to use the `docker` executor: ```bash -gitlab-ci-multi-runner register \ +gitlab-runner register \ --url "https://gitlab.com/" \ --registration-token "PROJECT_REGISTRATION_TOKEN" \ --description "docker-ruby-2.1" \ diff --git a/doc/ci/examples/php.md b/doc/ci/examples/php.md index 17e1c64bb8a..26953014502 100644 --- a/doc/ci/examples/php.md +++ b/doc/ci/examples/php.md @@ -263,10 +263,10 @@ terminal execute: ```bash # Check using docker executor -gitlab-ci-multi-runner exec docker test:app +gitlab-runner exec docker test:app # Check using shell executor -gitlab-ci-multi-runner exec shell test:app +gitlab-runner exec shell test:app ``` ## Example project diff --git a/doc/ci/runners/README.md b/doc/ci/runners/README.md index 400784da617..b42d7a62ebc 100644 --- a/doc/ci/runners/README.md +++ b/doc/ci/runners/README.md @@ -63,10 +63,10 @@ instance. Now simply register the runner as any runner: ``` -sudo gitlab-ci-multi-runner register +sudo gitlab-runner register ``` -Shared runners are enabled by default as of GitLab 8.2, but can be disabled with the +Shared runners are enabled by default as of GitLab 8.2, but can be disabled with the `DISABLE SHARED RUNNERS` button. Previous versions of GitLab defaulted shared runners to disabled. @@ -93,7 +93,7 @@ setup a specific runner for this project. To register the runner, run the command below and follow instructions: ``` -sudo gitlab-ci-multi-runner register +sudo gitlab-runner register ``` ### Making an existing Shared Runner Specific diff --git a/doc/ci/variables/README.md b/doc/ci/variables/README.md index 137b080a8f7..70fb81492d6 100644 --- a/doc/ci/variables/README.md +++ b/doc/ci/variables/README.md @@ -34,7 +34,6 @@ The `API_TOKEN` will take the Secure Variable value: `SECURE`. | **CI_BUILD_ID** | all | The unique id of the current build that GitLab CI uses internally | | **CI_BUILD_REPO** | all | The URL to clone the Git repository | | **CI_BUILD_TRIGGERED** | 0.5 | The flag to indicate that build was [triggered] | -| **CI_BUILD_TOKEN** | 1.2 | Token used for authenticating with the GitLab Container Registry | | **CI_PROJECT_ID** | all | The unique id of the current project that GitLab CI uses internally | | **CI_PROJECT_DIR** | all | The full path where the repository is cloned and where the build is ran | @@ -51,7 +50,6 @@ export CI_BUILD_TAG="1.0.0" export CI_BUILD_NAME="spec:other" export CI_BUILD_STAGE="test" export CI_BUILD_TRIGGERED="true" -export CI_BUILD_TOKEN="abcde-1234ABCD5678ef" export CI_PROJECT_DIR="/builds/gitlab-org/gitlab-ce" export CI_PROJECT_ID="34" export CI_SERVER="yes" diff --git a/doc/ci/yaml/README.md b/doc/ci/yaml/README.md index 9c98f9c98c6..a3481f58c6c 100644 --- a/doc/ci/yaml/README.md +++ b/doc/ci/yaml/README.md @@ -28,11 +28,8 @@ If you want a quick introduction to GitLab CI, follow our - [only and except](#only-and-except) - [tags](#tags) - [when](#when) - - [environment](#environment) - [artifacts](#artifacts) - [artifacts:name](#artifacts-name) - - [artifacts:when](#artifacts-when) - - [artifacts:expire_in](#artifacts-expire_in) - [dependencies](#dependencies) - [before_script and after_script](#before_script-and-after_script) - [Hidden jobs](#hidden-jobs) @@ -355,7 +352,6 @@ job_name: | cache | no | Define list of files that should be cached between subsequent runs | | before_script | no | Override a set of commands that are executed before build | | after_script | no | Override a set of commands that are executed after build | -| environment | no | Defines a name of environment to which deployment is done by this build | ### script @@ -527,31 +523,6 @@ The above script will: 1. Execute `cleanup_build_job` only when `build_job` fails 2. Always execute `cleanup_job` as the last step in pipeline. -### environment - ->**Note:** -Introduced in GitLab v8.9.0. - -`environment` is used to define that job does deployment to specific environment. -This allows to easily track all deployments to your environments straight from GitLab. - -If `environment` is specified and no environment under that name does exist a new one will be created automatically. - -The `environment` name must contain only letters, digits, '-' and '_'. - ---- - -**Example configurations** - -``` -deploy to production: - stage: deploy - script: git push production HEAD:master - environment: production -``` - -The `deploy to production` job will be marked as doing deployment to `production` environment. - ### artifacts >**Notes:** @@ -680,66 +651,6 @@ job: untracked: true ``` -#### artifacts:when - ->**Note:** -Introduced in GitLab 8.9 and GitLab Runner v1.3.0. - -`artifacts:when` is used to upload artifacts on build failure or despite the -failure. - -`artifacts:when` can be set to one of the following values: - -1. `on_success` - upload artifacts only when build succeeds. This is the default -1. `on_failure` - upload artifacts only when build fails -1. `always` - upload artifacts despite the build status - ---- - -**Example configurations** - -To upload artifacts only when build fails. - -```yaml -job: - artifacts: - when: on_failure -``` - -#### artifacts:expire_in - ->**Note:** -Introduced in GitLab 8.9 and GitLab Runner v1.3.0. - -`artifacts:expire_in` is used to remove uploaded artifacts after specified time. -By default artifacts are stored on GitLab forver. -`expire_in` allows to specify after what time the artifacts should be removed. -The artifacts will expire counting from the moment when they are uploaded and stored on GitLab. - -After artifacts uploading you can use the **Keep** button on build page to keep the artifacts forever. - -Artifacts are removed every hour, but they are not accessible after expire date. - -The value of `expire_in` is a elapsed time. The example of parsable values: -- '3 mins 4 sec' -- '2 hrs 20 min' -- '2h20min' -- '6 mos 1 day' -- '47 yrs 6 mos and 4d' -- '3 weeks and 2 days' - ---- - -**Example configurations** - -To expire artifacts after 1 week from the moment that they are uploaded: - -```yaml -job: - artifacts: - expire_in: 1 week -``` - ### dependencies >**Note:** diff --git a/doc/container_registry/README.md b/doc/container_registry/README.md index 1b465434498..4df24ef13cc 100644 --- a/doc/container_registry/README.md +++ b/doc/container_registry/README.md @@ -79,8 +79,27 @@ delete them. This feature requires GitLab 8.8 and GitLab Runner 1.2. Make sure that your GitLab Runner is configured to allow building docker images. -You have to check the [Using Docker Build documentation](../ci/docker/using_docker_build.md). -Then see the CI documentation on [Using the GitLab Container Registry](../ci/docker/using_docker_build.md#using-the-gitlab-container-registry). +You have to check the [Using Docker Build documentation](../../ci/docker/using_docker_build.md). + +You can use [docker:dind](https://hub.docker.com/_/docker/) to build your images, +and this is how `.gitlab-ci.yml` should look like: + +``` + build_image: + image: docker:git + services: + - docker:dind + stage: build + script: + - docker login -u gitlab-ci-token -p $CI_BUILD_TOKEN registry.example.com + - docker build -t registry.example.com/group/project:latest . + - docker push registry.example.com/group/project:latest +``` + +You have to use the credentials of the special `gitlab-ci-token` user with its +password stored in `$CI_BUILD_TOKEN` in order to push to the Registry connected +to your project. This allows you to automated building and deployment of your +Docker images. ## Limitations diff --git a/doc/development/doc_styleguide.md b/doc/development/doc_styleguide.md index f5d97179f8a..8292b393757 100644 --- a/doc/development/doc_styleguide.md +++ b/doc/development/doc_styleguide.md @@ -103,14 +103,14 @@ Inside the document: - Every piece of documentation that comes with a new feature should declare the GitLab version that feature got introduced. Right below the heading add a - note: `>**Note:** This feature was introduced in GitLab 8.3` + note: `_**Note:** This feature was introduced in GitLab 8.3_` - If possible every feature should have a link to the MR that introduced it. The above note would be then transformed to: - `>**Note:** This feature was [introduced][ce-1242] in GitLab 8.3`, where + `_**Note:** This feature was [introduced][ce-1242] in GitLab 8.3_`, where the [link identifier](#links) is named after the repository (CE) and the MR number - If the feature is only in GitLab EE, don't forget to mention it, like: - `>**Note:** This feature was introduced in GitLab EE 8.3`. Otherwise, leave + `_**Note:** This feature was introduced in GitLab EE 8.3_`. Otherwise, leave this mention out ## References @@ -141,48 +141,6 @@ Inside the document: [ruby-dl]: https://www.ruby-lang.org/en/downloads/ "Ruby download website" -## Changing document location - -Changing a document's location is not to be taken lightly. Remember that the -documentation is available to all installations under `help/` and not only to -GitLab.com or http://docs.gitlab.com. Make sure this is discussed with the -Documentation team beforehand. - -If you indeed need to change a document's location, do NOT remove the old -document, but rather put a text in it that points to the new location, like: - -``` -This document was moved to [path/to/new_doc.md](path/to/new_doc.md). -``` - -where `path/to/new_doc.md` is the relative path to the root directory `doc/`. - ---- - -For example, if you were to move `doc/workflow/lfs/lfs_administration.md` to -`doc/administration/lfs.md`, then the steps would be: - -1. Copy `doc/workflow/lfs/lfs_administration.md` to `doc/administration/lfs.md` -1. Replace the contents of `doc/workflow/lfs/lfs_administration.md` with: - - ``` - This document was moved to [administration/lfs.md](../../administration/lfs.md). - ``` - -1. Find and replace any occurrences of the old location with the new one. - A quick way to find them is to use `grep`: - - ``` - grep -nR "lfs_administration.md" doc/ - ``` - - The above command will search in the `doc/` directory for - `lfs_administration.md` recursively and will print the file and the line - where this file is mentioned. Note that we used just the filename - (`lfs_administration.md`) and not the whole the relative path - (`workflow/lfs/lfs_administration.md`). - - ## API Here is a list of must-have items. Use them in the exact order that appears @@ -264,8 +222,8 @@ curl --data "name=foo" -H "PRIVATE-TOKEN: 9koXpg98eAheJpvBs5tK" "https://gitlab. #### Post data using JSON content -> **Note:** In this example we create a new group. Watch carefully the single -and double quotes. +_**Note:** In this example we create a new group. Watch carefully the single +and double quotes._ ```bash curl -X POST -H "PRIVATE-TOKEN: 9koXpg98eAheJpvBs5tK" -H "Content-Type: application/json" --data '{"path": "my-group", "name": "My group"}' https://gitlab.example.com/api/v3/groups diff --git a/doc/development/instrumentation.md b/doc/development/instrumentation.md index 6cd9b274d11..9168c70945a 100644 --- a/doc/development/instrumentation.md +++ b/doc/development/instrumentation.md @@ -15,8 +15,8 @@ instrument code: * `instrument_instance_method`: instruments a single instance method. * `instrument_class_hierarchy`: given a Class this method will recursively instrument all sub-classes (both class and instance methods). -* `instrument_methods`: instruments all public and private class methods of a Module. -* `instrument_instance_methods`: instruments all public and private instance methods of a +* `instrument_methods`: instruments all public class methods of a Module. +* `instrument_instance_methods`: instruments all public instance methods of a Module. To remove the need for typing the full `Gitlab::Metrics::Instrumentation` @@ -97,16 +97,15 @@ def #{name}(#{args_signature}) trans = Gitlab::Metrics::Instrumentation.transaction if trans - start = Time.now - cpu_start = Gitlab::Metrics::System.cpu_time - retval = super - duration = (Time.now - start) * 1000.0 + start = Time.now + retval = super + duration = (Time.now - start) * 1000.0 if duration >= Gitlab::Metrics.method_call_threshold - cpu_duration = Gitlab::Metrics::System.cpu_time - cpu_start + trans.increment(:method_duration, duration) trans.add_metric(Gitlab::Metrics::Instrumentation::SERIES, - { duration: duration, cpu_duration: cpu_duration }, + { duration: duration }, method: #{label.inspect}) end diff --git a/doc/development/migration_style_guide.md b/doc/development/migration_style_guide.md index 8a7547e5322..02e024ca15a 100644 --- a/doc/development/migration_style_guide.md +++ b/doc/development/migration_style_guide.md @@ -34,15 +34,6 @@ First, you need to provide information on whether the migration can be applied: 3. online with errors on new instances while migrating 4. offline (needs to happen without app servers to prevent db corruption) -For example: - -``` -# rubocop:disable all -# Migration type: online without errors (works on previous version and new one) -class MyMigration < ActiveRecord::Migration -... -``` - It is always preferable to have a migration run online. If you expect the migration to take particularly long (for instance, if it loops through all notes), this is valuable information to add. @@ -57,6 +48,7 @@ be possible to downgrade in case of a vulnerability or bugs. In your migration, add a comment describing how the reversibility of the migration was tested. + ## Removing indices If you need to remove index, please add a condition like in following example: @@ -78,7 +70,6 @@ so: ``` class MyMigration < ActiveRecord::Migration - include Gitlab::Database::MigrationHelpers disable_ddl_transaction! def change @@ -99,11 +90,8 @@ value of `10` you'd write the following: ``` class MyMigration < ActiveRecord::Migration - include Gitlab::Database::MigrationHelpers - disable_ddl_transaction! - def up - add_column_with_default(:projects, :foo, :integer, default: 10) + add_column_with_default(:projects, :foo, :integer, 10) end def down diff --git a/doc/logs/logs.md b/doc/logs/logs.md index a2eca62d691..f84060b8d07 100644 --- a/doc/logs/logs.md +++ b/doc/logs/logs.md @@ -1 +1,92 @@ -This document was moved to [administration/logs.md](../administration/logs.md). +## Log system +GitLab has an advanced log system where everything is logged so that you can analyze your instance using various system log files. +In addition to system log files, GitLab Enterprise Edition comes with Audit Events. Find more about them [in Audit Events documentation](http://docs.gitlab.com/ee/administration/audit_events.html) + +System log files are typically plain text in a standard log file format. This guide talks about how to read and use these system log files. + +#### production.log +This file lives in `/var/log/gitlab/gitlab-rails/production.log` for omnibus package or in `/home/git/gitlab/log/production.log` for installations from the source. + +This file contains information about all performed requests. You can see url and type of request, IP address and what exactly parts of code were involved to service this particular request. Also you can see all SQL request that have been performed and how much time it took. +This task is more useful for GitLab contributors and developers. Use part of this log file when you are going to report bug. + +``` +Started GET "/gitlabhq/yaml_db/tree/master" for 168.111.56.1 at 2015-02-12 19:34:53 +0200 +Processing by Projects::TreeController#show as HTML + Parameters: {"project_id"=>"gitlabhq/yaml_db", "id"=>"master"} + + ... [CUT OUT] + + amespaces"."created_at" DESC, "namespaces"."id" DESC LIMIT 1[0m [["id", 26]] + [1m[35mCACHE (0.0ms)[0m SELECT "members".* FROM "members" WHERE "members"."source_type" = 'Project' AND "members"."type" IN ('ProjectMember') AND "members"."source_id" = $1 AND "members"."source_type" = $2 AND "members"."user_id" = 1 ORDER BY "members"."created_at" DESC, "members"."id" DESC LIMIT 1 [["source_id", 18], ["source_type", "Project"]] + [1m[36mCACHE (0.0ms)[0m [1mSELECT "members".* FROM "members" WHERE "members"."source_type" = 'Project' AND "members". + [1m[36m (1.4ms)[0m [1mSELECT COUNT(*) FROM "merge_requests" WHERE "merge_requests"."target_project_id" = $1 AND ("merge_requests"."state" IN ('opened','reopened'))[0m [["target_project_id", 18]] + Rendered layouts/nav/_project.html.haml (28.0ms) + Rendered layouts/_collapse_button.html.haml (0.2ms) + Rendered layouts/_flash.html.haml (0.1ms) + Rendered layouts/_page.html.haml (32.9ms) +Completed 200 OK in 166ms (Views: 117.4ms | ActiveRecord: 27.2ms) +``` +In this example we can see that server processed HTTP request with url `/gitlabhq/yaml_db/tree/master` from IP 168.111.56.1 at 2015-02-12 19:34:53 +0200. Also we can see that request was processed by Projects::TreeController. + +#### application.log +This file lives in `/var/log/gitlab/gitlab-rails/application.log` for omnibus package or in `/home/git/gitlab/log/application.log` for installations from the source. + +This log file helps you discover events happening in your instance such as user creation, project removing and so on. + +``` +October 06, 2014 11:56: User "Administrator" (admin@example.com) was created +October 06, 2014 11:56: Documentcloud created a new project "Documentcloud / Underscore" +October 06, 2014 11:56: Gitlab Org created a new project "Gitlab Org / Gitlab Ce" +October 07, 2014 11:25: User "Claudie Hodkiewicz" (nasir_stehr@olson.co.uk) was removed +October 07, 2014 11:25: Project "project133" was removed +``` +#### githost.log +This file lives in `/var/log/gitlab/gitlab-rails/githost.log` for omnibus package or in `/home/git/gitlab/log/githost.log` for installations from the source. + +The GitLab has to interact with git repositories but in some rare cases something can go wrong and in this case you will know what exactly happened. This log file contains all failed requests from GitLab to git repository. In majority of cases this file will be useful for developers only. +``` +December 03, 2014 13:20 -> ERROR -> Command failed [1]: /usr/bin/git --git-dir=/Users/vsizov/gitlab-development-kit/gitlab/tmp/tests/gitlab-satellites/group184/gitlabhq/.git --work-tree=/Users/vsizov/gitlab-development-kit/gitlab/tmp/tests/gitlab-satellites/group184/gitlabhq merge --no-ff -mMerge branch 'feature_conflict' into 'feature' source/feature_conflict + +error: failed to push some refs to '/Users/vsizov/gitlab-development-kit/repositories/gitlabhq/gitlab_git.git' +``` + +#### sidekiq.log +This file lives in `/var/log/gitlab/gitlab-rails/sidekiq.log` for omnibus package or in `/home/git/gitlab/log/sidekiq.log` for installations from the source. + +GitLab uses background jobs for processing tasks which can take a long time. All information about processing these jobs are writing down to this file. +``` +2014-06-10T07:55:20Z 2037 TID-tm504 ERROR: /opt/bitnami/apps/discourse/htdocs/vendor/bundle/ruby/1.9.1/gems/redis-3.0.7/lib/redis/client.rb:228:in `read' +2014-06-10T18:18:26Z 14299 TID-55uqo INFO: Booting Sidekiq 3.0.0 with redis options {:url=>"redis://localhost:6379/0", :namespace=>"sidekiq"} +``` + +#### gitlab-shell.log +This file lives in `/var/log/gitlab/gitlab-shell/gitlab-shell.log` for omnibus package or in `/home/git/gitlab-shell/gitlab-shell.log` for installations from the source. + +gitlab-shell is using by Gitlab for executing git commands and provide ssh access to git repositories. + +``` +I, [2015-02-13T06:17:00.671315 #9291] INFO -- : Adding project root/example.git at </var/opt/gitlab/git-data/repositories/root/dcdcdcdcd.git>. +I, [2015-02-13T06:17:00.679433 #9291] INFO -- : Moving existing hooks directory and symlinking global hooks directory for /var/opt/gitlab/git-data/repositories/root/example.git. +``` + +#### unicorn_stderr.log +This file lives in `/var/log/gitlab/unicorn/unicorn_stderr.log` for omnibus package or in `/home/git/gitlab/log/unicorn_stderr.log` for installations from the source. + +Unicorn is a high-performance forking Web server which is used for serving the GitLab application. You can look at this log if, for example, your application does not respond. This log contains all information about the state of unicorn processes at any given time. + +``` +I, [2015-02-13T06:14:46.680381 #9047] INFO -- : Refreshing Gem list +I, [2015-02-13T06:14:56.931002 #9047] INFO -- : listening on addr=127.0.0.1:8080 fd=12 +I, [2015-02-13T06:14:56.931381 #9047] INFO -- : listening on addr=/var/opt/gitlab/gitlab-rails/sockets/gitlab.socket fd=13 +I, [2015-02-13T06:14:56.936638 #9047] INFO -- : master process ready +I, [2015-02-13T06:14:56.946504 #9092] INFO -- : worker=0 spawned pid=9092 +I, [2015-02-13T06:14:56.946943 #9092] INFO -- : worker=0 ready +I, [2015-02-13T06:14:56.947892 #9094] INFO -- : worker=1 spawned pid=9094 +I, [2015-02-13T06:14:56.948181 #9094] INFO -- : worker=1 ready +W, [2015-02-13T07:16:01.312916 #9094] WARN -- : #<Unicorn::HttpServer:0x0000000208f618>: worker (pid: 9094) exceeds memory limit (320626688 bytes > 247066940 bytes) +W, [2015-02-13T07:16:01.313000 #9094] WARN -- : Unicorn::WorkerKiller send SIGQUIT (pid: 9094) alive: 3621 sec (trial 1) +I, [2015-02-13T07:16:01.530733 #9047] INFO -- : reaped #<Process::Status: pid 9094 exit 0> worker=1 +I, [2015-02-13T07:16:01.534501 #13379] INFO -- : worker=1 spawned pid=13379 +I, [2015-02-13T07:16:01.534848 #13379] INFO -- : worker=1 ready +``` diff --git a/doc/permissions/permissions.md b/doc/permissions/permissions.md index 963b35de3a0..b76ce31cbad 100644 --- a/doc/permissions/permissions.md +++ b/doc/permissions/permissions.md @@ -28,7 +28,6 @@ documentation](../workflow/add-user/add-user.md). | Manage labels | | ✓ | ✓ | ✓ | ✓ | | See a commit status | | ✓ | ✓ | ✓ | ✓ | | See a container registry | | ✓ | ✓ | ✓ | ✓ | -| See environments | | ✓ | ✓ | ✓ | ✓ | | Manage merge requests | | | ✓ | ✓ | ✓ | | Create new merge request | | | ✓ | ✓ | ✓ | | Create new branches | | | ✓ | ✓ | ✓ | @@ -41,7 +40,6 @@ documentation](../workflow/add-user/add-user.md). | Create or update commit status | | | ✓ | ✓ | ✓ | | Update a container registry | | | ✓ | ✓ | ✓ | | Remove a container registry image | | | ✓ | ✓ | ✓ | -| Create new environments | | | ✓ | ✓ | ✓ | | Create new milestones | | | | ✓ | ✓ | | Add new team members | | | | ✓ | ✓ | | Push to protected branches | | | | ✓ | ✓ | @@ -54,7 +52,6 @@ documentation](../workflow/add-user/add-user.md). | Manage runners | | | | ✓ | ✓ | | Manage build triggers | | | | ✓ | ✓ | | Manage variables | | | | ✓ | ✓ | -| Delete environments | | | | ✓ | ✓ | | Switch visibility level | | | | | ✓ | | Transfer project to another namespace | | | | | ✓ | | Remove project | | | | | ✓ | diff --git a/features/admin/active_tab.feature b/features/admin/active_tab.feature index f5bb06dea7d..5de07e90e28 100644 --- a/features/admin/active_tab.feature +++ b/features/admin/active_tab.feature @@ -5,36 +5,28 @@ Feature: Admin Active Tab Scenario: On Admin Home Given I visit admin page - Then the active main tab should be Overview + Then the active main tab should be Home And no other main tabs should be active Scenario: On Admin Projects Given I visit admin projects page - Then the active main tab should be Overview - And the active sub tab should be Projects + Then the active main tab should be Projects And no other main tabs should be active - And no other sub tabs should be active Scenario: On Admin Groups Given I visit admin groups page - Then the active main tab should be Overview - And the active sub tab should be Groups + Then the active main tab should be Groups And no other main tabs should be active - And no other sub tabs should be active Scenario: On Admin Users Given I visit admin users page - Then the active main tab should be Overview - And the active sub tab should be Users + Then the active main tab should be Users And no other main tabs should be active - And no other sub tabs should be active Scenario: On Admin Logs Given I visit admin logs page - Then the active main tab should be Monitoring - And the active sub tab should be Logs + Then the active main tab should be Logs And no other main tabs should be active - And no other sub tabs should be active Scenario: On Admin Messages Given I visit admin messages page @@ -48,7 +40,5 @@ Feature: Admin Active Tab Scenario: On Admin Resque Given I visit admin Resque page - Then the active main tab should be Monitoring - And the active sub tab should be Resque + Then the active main tab should be Resque And no other main tabs should be active - And no other sub tabs should be active diff --git a/features/steps/admin/active_tab.rb b/features/steps/admin/active_tab.rb index 9b1689a8198..f2db1801389 100644 --- a/features/steps/admin/active_tab.rb +++ b/features/steps/admin/active_tab.rb @@ -1,41 +1,45 @@ class Spinach::Features::AdminActiveTab < Spinach::FeatureSteps include SharedAuthentication include SharedPaths - include SharedActiveTab + include SharedSidebarActiveTab - step 'the active main tab should be Overview' do + step 'the active main tab should be Home' do ensure_active_main_tab('Overview') end - step 'the active sub tab should be Projects' do - ensure_active_sub_tab('Projects') + step 'the active main tab should be Projects' do + ensure_active_main_tab('Projects') end - step 'the active sub tab should be Groups' do - ensure_active_sub_tab('Groups') + step 'the active main tab should be Groups' do + ensure_active_main_tab('Groups') end - step 'the active sub tab should be Users' do - ensure_active_sub_tab('Users') + step 'the active main tab should be Users' do + ensure_active_main_tab('Users') + end + + step 'the active main tab should be Logs' do + ensure_active_main_tab('Logs') end step 'the active main tab should be Hooks' do ensure_active_main_tab('Hooks') end - step 'the active main tab should be Monitoring' do - ensure_active_main_tab('Monitoring') + step 'the active main tab should be Resque' do + ensure_active_main_tab('Background Jobs') end - step 'the active sub tab should be Resque' do - ensure_active_sub_tab('Background Jobs') + step 'the active main tab should be Messages' do + ensure_active_main_tab('Messages') end - step 'the active sub tab should be Logs' do - ensure_active_sub_tab('Logs') + step 'no other main tabs should be active' do + expect(page).to have_selector('.nav-sidebar > li.active', count: 1) end - step 'the active main tab should be Messages' do - ensure_active_main_tab('Messages') + def ensure_active_main_tab(content) + expect(find('.nav-sidebar > li.active')).to have_content(content) end end diff --git a/features/steps/dashboard/group.rb b/features/steps/dashboard/group.rb index 9b79a3be49b..0c6a0ae3725 100644 --- a/features/steps/dashboard/group.rb +++ b/features/steps/dashboard/group.rb @@ -62,6 +62,6 @@ class Spinach::Features::DashboardGroup < Spinach::FeatureSteps end step 'I should see the "Can not leave message"' do - expect(page).to have_content "You can not leave the \"Owned\" group." + expect(page).to have_content "You can not leave Owned group because you're the last owner" end end diff --git a/features/steps/dashboard/new_project.rb b/features/steps/dashboard/new_project.rb index 5308e77fb19..a0aad66184d 100644 --- a/features/steps/dashboard/new_project.rb +++ b/features/steps/dashboard/new_project.rb @@ -10,8 +10,7 @@ class Spinach::Features::NewProject < Spinach::FeatureSteps end step 'I see "New Project" page' do - expect(page).to have_content('Project owner') - expect(page).to have_content('Project name') + expect(page).to have_content('Project path') end step 'I see all possible import optios' do diff --git a/features/steps/group/members.rb b/features/steps/group/members.rb index dfa2fa75def..0706df3aec5 100644 --- a/features/steps/group/members.rb +++ b/features/steps/group/members.rb @@ -53,7 +53,7 @@ class Spinach::Features::GroupMembers < Spinach::FeatureSteps step 'I should see "sjobs@apple.com" in team list as invited "Reporter"' do page.within '.content-list' do expect(page).to have_content('sjobs@apple.com') - expect(page).to have_content('Invited') + expect(page).to have_content('invited') expect(page).to have_content('Reporter') end end @@ -116,9 +116,11 @@ class Spinach::Features::GroupMembers < Spinach::FeatureSteps member = mary_jane_member page.within "#group_member_#{member.id}" do - click_button "Edit access level" - select 'Developer', from: 'group_member_access_level' - click_on 'Save' + find(".js-toggle-button").click + page.within "#edit_group_member_#{member.id}" do + select 'Developer', from: 'group_member_access_level' + click_on 'Save' + end end end @@ -126,7 +128,9 @@ class Spinach::Features::GroupMembers < Spinach::FeatureSteps member = mary_jane_member page.within "#group_member_#{member.id}" do - expect(page).to have_content "Developer" + page.within '.member-access-level' do + expect(page).to have_content "Developer" + end end end diff --git a/features/steps/project/source/browse_files.rb b/features/steps/project/source/browse_files.rb index 79a3ed8197e..2c0498de3b9 100644 --- a/features/steps/project/source/browse_files.rb +++ b/features/steps/project/source/browse_files.rb @@ -202,8 +202,8 @@ class Spinach::Features::ProjectSourceBrowseFiles < Spinach::FeatureSteps end step 'I see Browse dir link' do - expect(page).to have_link 'Browse Directory' - expect(page).not_to have_link 'Browse Code' + expect(page).to have_link 'Browse Directory »' + expect(page).not_to have_link 'Browse Code »' end step 'I click on readme file' do @@ -219,7 +219,7 @@ class Spinach::Features::ProjectSourceBrowseFiles < Spinach::FeatureSteps step 'I see Browse code link' do expect(page).to have_link 'Browse Files' - expect(page).not_to have_link 'Browse Directory' + expect(page).not_to have_link 'Browse Directory »' end step 'I click on Permalink' do diff --git a/features/steps/project/team_management.rb b/features/steps/project/team_management.rb index f32576d2cb1..c6ced747370 100644 --- a/features/steps/project/team_management.rb +++ b/features/steps/project/team_management.rb @@ -26,11 +26,8 @@ class Spinach::Features::ProjectTeamManagement < Spinach::FeatureSteps end step 'I should see "Mike" in team list as "Reporter"' do - user = User.find_by(name: 'Mike') - project_member = project.project_members.find_by(user_id: user.id) - page.within "#project_member_#{project_member.id}" do + page.within ".access-reporter" do expect(page).to have_content('Mike') - expect(page).to have_content('Reporter') end end @@ -43,20 +40,16 @@ class Spinach::Features::ProjectTeamManagement < Spinach::FeatureSteps end step 'I should see "sjobs@apple.com" in team list as invited "Reporter"' do - project_member = project.project_members.find_by(invite_email: 'sjobs@apple.com') - page.within "#project_member_#{project_member.id}" do + page.within ".access-reporter" do expect(page).to have_content('sjobs@apple.com') - expect(page).to have_content('Invited') + expect(page).to have_content('invited') expect(page).to have_content('Reporter') end end step 'I should see "Dmitriy" in team list as "Developer"' do - user = User.find_by(name: 'Dmitriy') - project_member = project.project_members.find_by(user_id: user.id) - page.within "#project_member_#{project_member.id}" do + page.within ".access-developer" do expect(page).to have_content('Dmitriy') - expect(page).to have_content('Developer') end end @@ -72,14 +65,15 @@ class Spinach::Features::ProjectTeamManagement < Spinach::FeatureSteps end step 'I should see "Dmitriy" in team list as "Reporter"' do - user = User.find_by(name: 'Dmitriy') - project_member = project.project_members.find_by(user_id: user.id) - page.within "#project_member_#{project_member.id}" do + page.within ".access-reporter" do expect(page).to have_content('Dmitriy') - expect(page).to have_content('Reporter') end end + step 'I click link "Remove from team"' do + click_link "Remove from team" + end + step 'I should not see "Dmitriy" in team list' do user = User.find_by(name: "Dmitriy") expect(page).not_to have_content(user.name) @@ -126,7 +120,7 @@ class Spinach::Features::ProjectTeamManagement < Spinach::FeatureSteps user = User.find_by(name: 'Dmitriy') project_member = project.project_members.find_by(user_id: user.id) page.within "#project_member_#{project_member.id}" do - click_link('Remove user from project') + click_link('Remove user from team') end end diff --git a/lib/api/builds.rb b/lib/api/builds.rb index 979328efe0e..0ff8fa74a84 100644 --- a/lib/api/builds.rb +++ b/lib/api/builds.rb @@ -142,7 +142,7 @@ module API return not_found!(build) unless build return forbidden!('Build is not retryable') unless build.retryable? - build = Ci::Build.retry(build, current_user) + build = Ci::Build.retry(build) present build, with: Entities::Build, user_can_download_artifacts: can?(current_user, :read_build, user_project) @@ -166,26 +166,6 @@ module API present build, with: Entities::Build, user_can_download_artifacts: can?(current_user, :download_build_artifacts, user_project) end - - # Keep the artifacts to prevent them from being deleted - # - # Parameters: - # id (required) - the id of a project - # build_id (required) - The ID of a build - # Example Request: - # POST /projects/:id/builds/:build_id/artifacts/keep - post ':id/builds/:build_id/artifacts/keep' do - authorize_update_builds! - - build = get_build(params[:build_id]) - return not_found!(build) unless build && build.artifacts? - - build.keep_artifacts! - - status 200 - present build, with: Entities::Build, - user_can_download_artifacts: can?(current_user, :read_build, user_project) - end end helpers do diff --git a/lib/api/entities.rb b/lib/api/entities.rb index cc29c7ef428..14370ac218d 100644 --- a/lib/api/entities.rb +++ b/lib/api/entities.rb @@ -88,7 +88,10 @@ module API class Group < Grape::Entity expose :id, :name, :path, :description, :visibility_level expose :avatar_url - expose :web_url + + expose :web_url do |group, options| + Gitlab::Routing.url_helpers.group_url(group) + end end class GroupDetail < Group diff --git a/lib/api/project_members.rb b/lib/api/project_members.rb index b703da0557a..4aefdf319c6 100644 --- a/lib/api/project_members.rb +++ b/lib/api/project_members.rb @@ -46,7 +46,7 @@ module API required_attributes! [:user_id, :access_level] # either the user is already a team member or a new one - project_member = user_project.project_member(params[:user_id]) + project_member = user_project.project_member_by_id(params[:user_id]) if project_member.nil? project_member = user_project.project_members.new( user_id: params[:user_id], diff --git a/lib/api/session.rb b/lib/api/session.rb index 56c202f1294..56e69b2366f 100644 --- a/lib/api/session.rb +++ b/lib/api/session.rb @@ -11,7 +11,7 @@ module API # Example Request: # POST /session post "/session" do - user = Gitlab::Auth.find_with_user_password(params[:email] || params[:login], params[:password]) + user = Gitlab::Auth.find_in_gitlab_or_ldap(params[:email] || params[:login], params[:password]) return unauthorized! unless user present user, with: Entities::UserLogin diff --git a/lib/banzai/pipeline/description_pipeline.rb b/lib/banzai/pipeline/description_pipeline.rb index 042fb2e6e14..f2395867658 100644 --- a/lib/banzai/pipeline/description_pipeline.rb +++ b/lib/banzai/pipeline/description_pipeline.rb @@ -1,16 +1,23 @@ module Banzai module Pipeline class DescriptionPipeline < FullPipeline - WHITELIST = Banzai::Filter::SanitizationFilter::LIMITED.deep_dup.merge( - elements: Banzai::Filter::SanitizationFilter::LIMITED[:elements] - %w(pre code img ol ul li) - ) - def self.transform_context(context) super(context).merge( # SanitizationFilter - whitelist: WHITELIST + whitelist: whitelist ) end + + private + + def self.whitelist + # Descriptions are more heavily sanitized, allowing only a few elements. + # See http://git.io/vkuAN + whitelist = Banzai::Filter::SanitizationFilter::LIMITED + whitelist[:elements] -= %w(pre code img ol ul li) + + whitelist + end end end end diff --git a/lib/banzai/reference_parser/issue_parser.rb b/lib/banzai/reference_parser/issue_parser.rb index f306079d833..24076e3d9ec 100644 --- a/lib/banzai/reference_parser/issue_parser.rb +++ b/lib/banzai/reference_parser/issue_parser.rb @@ -25,21 +25,7 @@ module Banzai def issues_for_nodes(nodes) @issues_for_nodes ||= grouped_objects_for_nodes( nodes, - Issue.all.includes( - :author, - :assignee, - { - # These associations are primarily used for checking permissions. - # Eager loading these ensures we don't end up running dozens of - # queries in this process. - project: [ - { namespace: :owner }, - { group: [:owners, :group_members] }, - :invited_groups, - :project_members - ] - } - ), + Issue.all.includes(:author, :assignee, :project), self.class.data_attribute ) end diff --git a/lib/ci/api/builds.rb b/lib/ci/api/builds.rb index 9f270f7b387..607359769d1 100644 --- a/lib/ci/api/builds.rb +++ b/lib/ci/api/builds.rb @@ -114,7 +114,6 @@ module Ci # id (required) - The ID of a build # token (required) - The build authorization token # file (required) - Artifacts file - # expire_in (optional) - Specify when artifacts should expire (ex. 7d) # Parameters (accelerated by GitLab Workhorse): # file.path - path to locally stored body (generated by Workhorse) # file.name - real filename as send in Content-Disposition @@ -146,7 +145,6 @@ module Ci build.artifacts_file = artifacts build.artifacts_metadata = metadata - build.artifacts_expire_in = params['expire_in'] if build.save present(build, with: Entities::BuildDetails) diff --git a/lib/ci/api/entities.rb b/lib/ci/api/entities.rb index 3f5bdaba3f5..a902ced35d7 100644 --- a/lib/ci/api/entities.rb +++ b/lib/ci/api/entities.rb @@ -20,7 +20,7 @@ module Ci expose :name, :token, :stage expose :project_id expose :project_name - expose :artifacts_file, using: ArtifactFile, if: ->(build, _) { build.artifacts? } + expose :artifacts_file, using: ArtifactFile, if: lambda { |build, opts| build.artifacts? } end class BuildDetails < Build @@ -29,7 +29,6 @@ module Ci expose :before_sha expose :allow_git_fetch expose :token - expose :artifacts_expire_at, if: ->(build, _) { build.artifacts? } expose :options do |model| model.options diff --git a/lib/ci/gitlab_ci_yaml_processor.rb b/lib/ci/gitlab_ci_yaml_processor.rb index 68246497e90..130f5b0892e 100644 --- a/lib/ci/gitlab_ci_yaml_processor.rb +++ b/lib/ci/gitlab_ci_yaml_processor.rb @@ -2,24 +2,17 @@ module Ci class GitlabCiYamlProcessor class ValidationError < StandardError; end - include Gitlab::Ci::Config::Node::ValidationHelpers - DEFAULT_STAGES = %w(build test deploy) DEFAULT_STAGE = 'test' ALLOWED_YAML_KEYS = [:before_script, :after_script, :image, :services, :types, :stages, :variables, :cache] ALLOWED_JOB_KEYS = [:tags, :script, :only, :except, :type, :image, :services, :allow_failure, :type, :stage, :when, :artifacts, :cache, - :dependencies, :before_script, :after_script, :variables, - :environment] - ALLOWED_CACHE_KEYS = [:key, :untracked, :paths] - ALLOWED_ARTIFACTS_KEYS = [:name, :untracked, :paths, :when, :expire_in] + :dependencies, :before_script, :after_script, :variables] - attr_reader :after_script, :image, :services, :path, :cache + attr_reader :before_script, :after_script, :image, :services, :path, :cache def initialize(config, path = nil) - @ci_config = Gitlab::Ci::Config.new(config) - @config = @ci_config.to_hash - + @config = Gitlab::Ci::Config.new(config).to_hash @path = path initial_parsing @@ -57,6 +50,7 @@ module Ci private def initial_parsing + @before_script = @config[:before_script] || [] @after_script = @config[:after_script] @image = @config[:image] @services = @config[:services] @@ -84,14 +78,13 @@ module Ci { stage_idx: stages.index(job[:stage]), stage: job[:stage], - commands: [job[:before_script] || [@ci_config.before_script], job[:script]].flatten.compact.join("\n"), + commands: [job[:before_script] || @before_script, job[:script]].flatten.join("\n"), tag_list: job[:tags] || [], name: name, only: job[:only], except: job[:except], allow_failure: job[:allow_failure] || false, when: job[:when] || 'on_success', - environment: job[:environment], options: { image: job[:image] || @image, services: job[:services] || @services, @@ -104,10 +97,6 @@ module Ci end def validate! - unless @ci_config.valid? - raise ValidationError, @ci_config.errors.first - end - validate_global! @jobs.each do |name, job| @@ -118,6 +107,10 @@ module Ci end def validate_global! + unless validate_array_of_strings(@before_script) + raise ValidationError, "before_script should be an array of strings" + end + unless @after_script.nil? || validate_array_of_strings(@after_script) raise ValidationError, "after_script should be an array of strings" end @@ -142,12 +135,6 @@ module Ci end def validate_global_cache! - @cache.keys.each do |key| - unless ALLOWED_CACHE_KEYS.include? key - raise ValidationError, "#{name} cache unknown parameter #{key}" - end - end - if @cache[:key] && !validate_string(@cache[:key]) raise ValidationError, "cache:key parameter should be a string" end @@ -213,13 +200,9 @@ module Ci raise ValidationError, "#{name} job: allow_failure parameter should be an boolean" end - if job[:when] && !job[:when].in?(%w[on_success on_failure always]) + if job[:when] && !job[:when].in?(%w(on_success on_failure always)) raise ValidationError, "#{name} job: when parameter should be on_success, on_failure or always" end - - if job[:environment] && !validate_environment(job[:environment]) - raise ValidationError, "#{name} job: environment parameter #{Gitlab::Regex.environment_name_regex_message}" - end end def validate_job_script!(name, job) @@ -250,12 +233,6 @@ module Ci end def validate_job_cache!(name, job) - job[:cache].keys.each do |key| - unless ALLOWED_CACHE_KEYS.include? key - raise ValidationError, "#{name} job: cache unknown parameter #{key}" - end - end - if job[:cache][:key] && !validate_string(job[:cache][:key]) raise ValidationError, "#{name} job: cache:key parameter should be a string" end @@ -270,12 +247,6 @@ module Ci end def validate_job_artifacts!(name, job) - job[:artifacts].keys.each do |key| - unless ALLOWED_ARTIFACTS_KEYS.include? key - raise ValidationError, "#{name} job: artifacts unknown parameter #{key}" - end - end - if job[:artifacts][:name] && !validate_string(job[:artifacts][:name]) raise ValidationError, "#{name} job: artifacts:name parameter should be a string" end @@ -287,14 +258,6 @@ module Ci if job[:artifacts][:paths] && !validate_array_of_strings(job[:artifacts][:paths]) raise ValidationError, "#{name} job: artifacts:paths parameter should be an array of strings" end - - if job[:artifacts][:when] && !job[:artifacts][:when].in?(%w[on_success on_failure always]) - raise ValidationError, "#{name} job: artifacts:when parameter should be on_success, on_failure or always" - end - - if job[:artifacts][:expire_in] && !validate_duration(job[:artifacts][:expire_in]) - raise ValidationError, "#{name} job: artifacts:expire_in parameter should be a duration" - end end def validate_job_dependencies!(name, job) @@ -313,6 +276,22 @@ module Ci end end + def validate_array_of_strings(values) + values.is_a?(Array) && values.all? { |value| validate_string(value) } + end + + def validate_variables(variables) + variables.is_a?(Hash) && variables.all? { |key, value| validate_string(key) && validate_string(value) } + end + + def validate_string(value) + value.is_a?(String) || value.is_a?(Symbol) + end + + def validate_boolean(value) + value.in?([true, false]) + end + def process?(only_params, except_params, ref, tag, trigger_request) if only_params.present? return false unless matching?(only_params, ref, tag, trigger_request) diff --git a/lib/container_registry/blob.rb b/lib/container_registry/blob.rb index eb5a2596177..4e20dc4f875 100644 --- a/lib/container_registry/blob.rb +++ b/lib/container_registry/blob.rb @@ -18,7 +18,7 @@ module ContainerRegistry end def digest - config['digest'] || config['blobSum'] + config['digest'] end def type diff --git a/lib/container_registry/client.rb b/lib/container_registry/client.rb index e0b3f14d384..4d726692f45 100644 --- a/lib/container_registry/client.rb +++ b/lib/container_registry/client.rb @@ -47,9 +47,7 @@ module ContainerRegistry conn.request :json conn.headers['Accept'] = MANIFEST_VERSION - conn.response :json, content_type: 'application/vnd.docker.distribution.manifest.v1+prettyjws' - conn.response :json, content_type: 'application/vnd.docker.distribution.manifest.v1+json' - conn.response :json, content_type: 'application/vnd.docker.distribution.manifest.v2+json' + conn.response :json, content_type: /\bjson$/ if options[:user] && options[:password] conn.request(:basic_auth, options[:user].to_s, options[:password].to_s) diff --git a/lib/container_registry/tag.rb b/lib/container_registry/tag.rb index 7a0929d774e..43f8d6dc8c2 100644 --- a/lib/container_registry/tag.rb +++ b/lib/container_registry/tag.rb @@ -12,14 +12,6 @@ module ContainerRegistry manifest.present? end - def v1? - manifest && manifest['schemaVersion'] == 1 - end - - def v2? - manifest && manifest['schemaVersion'] == 2 - end - def manifest return @manifest if defined?(@manifest) @@ -65,9 +57,7 @@ module ContainerRegistry return @layers if defined?(@layers) return unless manifest - layers = manifest['layers'] || manifest['fsLayers'] - - @layers = layers.map do |layer| + @layers = manifest['layers'].map do |layer| repository.blob(layer) end end @@ -75,7 +65,7 @@ module ContainerRegistry def total_size return unless layers - layers.map(&:size).sum if v2? + layers.map(&:size).sum end def delete diff --git a/lib/gitlab/auth.rb b/lib/gitlab/auth.rb index db1704af75e..076e2af7d38 100644 --- a/lib/gitlab/auth.rb +++ b/lib/gitlab/auth.rb @@ -3,14 +3,14 @@ module Gitlab Result = Struct.new(:user, :type) class << self - def find_for_git_client(login, password, project:, ip:) + def find(login, password, project:, ip:) raise "Must provide an IP for rate limiting" if ip.nil? result = Result.new if valid_ci_request?(login, password, project) result.type = :ci - elsif result.user = find_with_user_password(login, password) + elsif result.user = find_in_gitlab_or_ldap(login, password) result.type = :gitlab_or_ldap elsif result.user = oauth_access_token_check(login, password) result.type = :oauth @@ -20,7 +20,7 @@ module Gitlab result end - def find_with_user_password(login, password) + def find_in_gitlab_or_ldap(login, password) user = User.by_login(login) # If no user is found, or it's an LDAP server, try LDAP. diff --git a/lib/gitlab/backend/grack_auth.rb b/lib/gitlab/backend/grack_auth.rb index 7e3f5abba62..9e09d2e118d 100644 --- a/lib/gitlab/backend/grack_auth.rb +++ b/lib/gitlab/backend/grack_auth.rb @@ -1,3 +1,5 @@ +require_relative 'shell_env' + module Grack class AuthSpawner def self.call(env) @@ -59,6 +61,11 @@ module Grack end @user = authenticate_user(login, password) + + if @user + Gitlab::ShellEnv.set_env(@user) + @env['REMOTE_USER'] = @auth.username + end end def ci_request?(login, password) @@ -88,7 +95,7 @@ module Grack end def authenticate_user(login, password) - user = Gitlab::Auth.find_with_user_password(login, password) + user = Gitlab::Auth.find_in_gitlab_or_ldap(login, password) unless user user = oauth_access_token_check(login, password) diff --git a/lib/gitlab/backend/shell_env.rb b/lib/gitlab/backend/shell_env.rb new file mode 100644 index 00000000000..9f5adee594a --- /dev/null +++ b/lib/gitlab/backend/shell_env.rb @@ -0,0 +1,28 @@ +module Gitlab + # This module provide 2 methods + # to set specific ENV variables for GitLab Shell + module ShellEnv + extend self + + def set_env(user) + # Set GL_ID env variable + if user + ENV['GL_ID'] = gl_id(user) + end + end + + def reset_env + # Reset GL_ID env variable + ENV['GL_ID'] = nil + end + + def gl_id(user) + if user.present? + "user-#{user.id}" + else + # This empty string is used in the render_grack_auth_ok method + "" + end + end + end +end diff --git a/lib/gitlab/ci/config.rb b/lib/gitlab/ci/config.rb index b48d3592f16..ffe633d4b63 100644 --- a/lib/gitlab/ci/config.rb +++ b/lib/gitlab/ci/config.rb @@ -1,21 +1,11 @@ module Gitlab module Ci - ## - # Base GitLab CI Configuration facade - # class Config - delegate :valid?, :errors, to: :@global - - ## - # Temporary delegations that should be removed after refactoring - # - delegate :before_script, to: :@global + class LoaderError < StandardError; end def initialize(config) - @config = Loader.new(config).load! - - @global = Node::Global.new(@config) - @global.process! + loader = Loader.new(config) + @config = loader.load! end def to_hash diff --git a/lib/gitlab/ci/config/node/configurable.rb b/lib/gitlab/ci/config/node/configurable.rb deleted file mode 100644 index d60f87f3f94..00000000000 --- a/lib/gitlab/ci/config/node/configurable.rb +++ /dev/null @@ -1,61 +0,0 @@ -module Gitlab - module Ci - class Config - module Node - ## - # This mixin is responsible for adding DSL, which purpose is to - # simplifly process of adding child nodes. - # - # This can be used only if parent node is a configuration entry that - # holds a hash as a configuration value, for example: - # - # job: - # script: ... - # artifacts: ... - # - module Configurable - extend ActiveSupport::Concern - - def allowed_nodes - self.class.allowed_nodes || {} - end - - private - - def prevalidate! - unless @value.is_a?(Hash) - @errors << 'should be a configuration entry with hash value' - end - end - - def create_node(key, factory) - factory.with(value: @value[key]) - factory.nullify! unless @value.has_key?(key) - factory.create! - end - - class_methods do - def allowed_nodes - Hash[@allowed_nodes.map { |key, factory| [key, factory.dup] }] - end - - private - - def allow_node(symbol, entry_class, metadata) - factory = Node::Factory.new(entry_class) - .with(description: metadata[:description]) - - define_method(symbol) do - raise Entry::InvalidError unless valid? - - @nodes[symbol].try(:value) - end - - (@allowed_nodes ||= {}).merge!(symbol => factory) - end - end - end - end - end - end -end diff --git a/lib/gitlab/ci/config/node/entry.rb b/lib/gitlab/ci/config/node/entry.rb deleted file mode 100644 index 52758a962f3..00000000000 --- a/lib/gitlab/ci/config/node/entry.rb +++ /dev/null @@ -1,77 +0,0 @@ -module Gitlab - module Ci - class Config - module Node - ## - # Base abstract class for each configuration entry node. - # - class Entry - class InvalidError < StandardError; end - - attr_accessor :description - - def initialize(value) - @value = value - @nodes = {} - @errors = [] - - prevalidate! - end - - def process! - return if leaf? - return unless valid? - - compose! - - nodes.each(&:process!) - nodes.each(&:validate!) - end - - def nodes - @nodes.values - end - - def valid? - errors.none? - end - - def leaf? - allowed_nodes.none? - end - - def errors - @errors + nodes.map(&:errors).flatten - end - - def allowed_nodes - {} - end - - def validate! - raise NotImplementedError - end - - def value - raise NotImplementedError - end - - private - - def prevalidate! - end - - def compose! - allowed_nodes.each do |key, essence| - @nodes[key] = create_node(key, essence) - end - end - - def create_node(key, essence) - raise NotImplementedError - end - end - end - end - end -end diff --git a/lib/gitlab/ci/config/node/factory.rb b/lib/gitlab/ci/config/node/factory.rb deleted file mode 100644 index 787ca006f5a..00000000000 --- a/lib/gitlab/ci/config/node/factory.rb +++ /dev/null @@ -1,39 +0,0 @@ -module Gitlab - module Ci - class Config - module Node - ## - # Factory class responsible for fabricating node entry objects. - # - # It uses Fluent Interface pattern to set all necessary attributes. - # - class Factory - class InvalidFactory < StandardError; end - - def initialize(entry_class) - @entry_class = entry_class - @attributes = {} - end - - def with(attributes) - @attributes.merge!(attributes) - self - end - - def nullify! - @entry_class = Node::Null - self - end - - def create! - raise InvalidFactory unless @attributes.has_key?(:value) - - @entry_class.new(@attributes[:value]).tap do |entry| - entry.description = @attributes[:description] - end - end - end - end - end - end -end diff --git a/lib/gitlab/ci/config/node/global.rb b/lib/gitlab/ci/config/node/global.rb deleted file mode 100644 index 044603423d5..00000000000 --- a/lib/gitlab/ci/config/node/global.rb +++ /dev/null @@ -1,18 +0,0 @@ -module Gitlab - module Ci - class Config - module Node - ## - # This class represents a global entry - root node for entire - # GitLab CI Configuration file. - # - class Global < Entry - include Configurable - - allow_node :before_script, Script, - description: 'Script that will be executed before each job.' - end - end - end - end -end diff --git a/lib/gitlab/ci/config/node/null.rb b/lib/gitlab/ci/config/node/null.rb deleted file mode 100644 index 4f590f6bec8..00000000000 --- a/lib/gitlab/ci/config/node/null.rb +++ /dev/null @@ -1,27 +0,0 @@ -module Gitlab - module Ci - class Config - module Node - ## - # This class represents a configuration entry that is not being used - # in configuration file. - # - # This implements Null Object pattern. - # - class Null < Entry - def value - nil - end - - def validate! - nil - end - - def method_missing(*) - nil - end - end - end - end - end -end diff --git a/lib/gitlab/ci/config/node/script.rb b/lib/gitlab/ci/config/node/script.rb deleted file mode 100644 index 5072bf0db7d..00000000000 --- a/lib/gitlab/ci/config/node/script.rb +++ /dev/null @@ -1,29 +0,0 @@ -module Gitlab - module Ci - class Config - module Node - ## - # Entry that represents a script. - # - # Each element in the value array is a command that will be executed - # by GitLab Runner. Currently we concatenate these commands with - # new line character as a separator, what is compatible with - # implementation in Runner. - # - class Script < Entry - include ValidationHelpers - - def value - @value.join("\n") - end - - def validate! - unless validate_array_of_strings(@value) - @errors << 'before_script should be an array of strings' - end - end - end - end - end - end -end diff --git a/lib/gitlab/ci/config/node/validation_helpers.rb b/lib/gitlab/ci/config/node/validation_helpers.rb deleted file mode 100644 index 3900fc89391..00000000000 --- a/lib/gitlab/ci/config/node/validation_helpers.rb +++ /dev/null @@ -1,38 +0,0 @@ -module Gitlab - module Ci - class Config - module Node - module ValidationHelpers - private - - def validate_duration(value) - value.is_a?(String) && ChronicDuration.parse(value) - rescue ChronicDuration::DurationParseError - false - end - - def validate_array_of_strings(values) - values.is_a?(Array) && values.all? { |value| validate_string(value) } - end - - def validate_variables(variables) - variables.is_a?(Hash) && - variables.all? { |key, value| validate_string(key) && validate_string(value) } - end - - def validate_string(value) - value.is_a?(String) || value.is_a?(Symbol) - end - - def validate_environment(value) - value.is_a?(String) && value =~ Gitlab::Regex.environment_name_regex - end - - def validate_boolean(value) - value.in?([true, false]) - end - end - end - end - end -end diff --git a/lib/gitlab/database.rb b/lib/gitlab/database.rb index d76ecb54017..04fa6a3a5de 100644 --- a/lib/gitlab/database.rb +++ b/lib/gitlab/database.rb @@ -30,10 +30,6 @@ module Gitlab order end - def self.random - Gitlab::Database.postgresql? ? "RANDOM()" : "RAND()" - end - def true_value if Gitlab::Database.postgresql? "'t'" diff --git a/lib/gitlab/database/migration_helpers.rb b/lib/gitlab/database/migration_helpers.rb index dd3ff0ab18b..978c3f7896d 100644 --- a/lib/gitlab/database/migration_helpers.rb +++ b/lib/gitlab/database/migration_helpers.rb @@ -31,6 +31,8 @@ module Gitlab # Any data inserted while running this method (or after it has finished # running) is _not_ updated automatically. # + # This method _only_ updates rows where the column's value is set to NULL. + # # table - The name of the table. # column - The name of the column to update. # value - The value for the column. @@ -53,10 +55,10 @@ module Gitlab first['count']. to_i - # Update in batches of 5% until we run out of any rows to update. + # Update in batches of 5% batch_size = ((total / 100.0) * 5.0).ceil - loop do + while processed < total start_row = exec_query(%Q{ SELECT id FROM #{quoted_table} @@ -64,9 +66,6 @@ module Gitlab LIMIT 1 OFFSET #{processed} }).to_hash.first - # There are no more rows to process - break unless start_row - stop_row = exec_query(%Q{ SELECT id FROM #{quoted_table} @@ -127,8 +126,6 @@ module Gitlab begin transaction do update_column_in_batches(table, column, default) - - change_column_null(table, column, false) unless allow_null end # We want to rescue _all_ exceptions here, even those that don't inherit # from StandardError. @@ -137,6 +134,8 @@ module Gitlab raise error end + + change_column_null(table, column, false) unless allow_null end end end diff --git a/lib/gitlab/gl_id.rb b/lib/gitlab/gl_id.rb deleted file mode 100644 index 624fd00367e..00000000000 --- a/lib/gitlab/gl_id.rb +++ /dev/null @@ -1,11 +0,0 @@ -module Gitlab - module GlId - def self.gl_id(user) - if user.present? - "user-#{user.id}" - else - "" - end - end - end -end diff --git a/lib/gitlab/metrics/instrumentation.rb b/lib/gitlab/metrics/instrumentation.rb index d81d26754fe..0f115893a15 100644 --- a/lib/gitlab/metrics/instrumentation.rb +++ b/lib/gitlab/metrics/instrumentation.rb @@ -56,7 +56,7 @@ module Gitlab end end - # Instruments all public and private methods of a module. + # Instruments all public methods of a module. # # This method optionally takes a block that can be used to determine if a # method should be instrumented or not. The block is passed the receiving @@ -65,8 +65,7 @@ module Gitlab # # mod - The module to instrument. def self.instrument_methods(mod) - methods = mod.methods(false) + mod.private_methods(false) - methods.each do |name| + mod.public_methods(false).each do |name| method = mod.method(name) if method.owner == mod.singleton_class @@ -77,14 +76,13 @@ module Gitlab end end - # Instruments all public and private instance methods of a module. + # Instruments all public instance methods of a module. # # See `instrument_methods` for more information. # # mod - The module to instrument. def self.instrument_instance_methods(mod) - methods = mod.instance_methods(false) + mod.private_instance_methods(false) - methods.each do |name| + mod.public_instance_methods(false).each do |name| method = mod.instance_method(name) if method.owner == mod @@ -151,16 +149,13 @@ module Gitlab trans = Gitlab::Metrics::Instrumentation.transaction if trans - start = Time.now - cpu_start = Gitlab::Metrics::System.cpu_time - retval = super - duration = (Time.now - start) * 1000.0 + start = Time.now + retval = super + duration = (Time.now - start) * 1000.0 if duration >= Gitlab::Metrics.method_call_threshold - cpu_duration = Gitlab::Metrics::System.cpu_time - cpu_start - trans.add_metric(Gitlab::Metrics::Instrumentation::SERIES, - { duration: duration, cpu_duration: cpu_duration }, + { duration: duration }, method: #{label.inspect}) end diff --git a/lib/gitlab/metrics/rack_middleware.rb b/lib/gitlab/metrics/rack_middleware.rb index 3fe27779d03..6f179789d3e 100644 --- a/lib/gitlab/metrics/rack_middleware.rb +++ b/lib/gitlab/metrics/rack_middleware.rb @@ -1,9 +1,8 @@ module Gitlab module Metrics - # Rack middleware for tracking Rails and Grape requests. + # Rack middleware for tracking Rails requests. class RackMiddleware CONTROLLER_KEY = 'action_controller.instance' - ENDPOINT_KEY = 'api.endpoint' def initialize(app) @app = app @@ -22,8 +21,6 @@ module Gitlab ensure if env[CONTROLLER_KEY] tag_controller(trans, env) - elsif env[ENDPOINT_KEY] - tag_endpoint(trans, env) end trans.finish @@ -45,26 +42,6 @@ module Gitlab controller = env[CONTROLLER_KEY] trans.action = "#{controller.class.name}##{controller.action_name}" end - - def tag_endpoint(trans, env) - endpoint = env[ENDPOINT_KEY] - path = endpoint_paths_cache[endpoint.route.route_method][endpoint.route.route_path] - trans.action = "Grape##{endpoint.route.route_method} #{path}" - end - - private - - def endpoint_paths_cache - @endpoint_paths_cache ||= Hash.new do |hash, http_method| - hash[http_method] = Hash.new do |inner_hash, raw_path| - inner_hash[raw_path] = endpoint_instrumentable_path(raw_path) - end - end - end - - def endpoint_instrumentable_path(raw_path) - raw_path.sub('(.:format)', '').sub('/:version', '') - end end end end diff --git a/lib/gitlab/metrics/sampler.rb b/lib/gitlab/metrics/sampler.rb index 0000450d9bb..fc709222a9b 100644 --- a/lib/gitlab/metrics/sampler.rb +++ b/lib/gitlab/metrics/sampler.rb @@ -66,11 +66,7 @@ module Gitlab def sample_objects sample = Allocations.to_hash counts = sample.each_with_object({}) do |(klass, count), hash| - name = klass.name - - next unless name - - hash[name] = count + hash[klass.name] = count end # Symbols aren't allocated so we'll need to add those manually. diff --git a/lib/gitlab/regex.rb b/lib/gitlab/regex.rb index c84c68f96f6..1cbd6d945a0 100644 --- a/lib/gitlab/regex.rb +++ b/lib/gitlab/regex.rb @@ -100,13 +100,5 @@ module Gitlab def container_registry_reference_regex git_reference_regex end - - def environment_name_regex - @environment_name_regex ||= /\A[a-zA-Z0-9_-]+\z/.freeze - end - - def environment_name_regex_message - "can contain only letters, digits, '-' and '_'." - end end end diff --git a/lib/gitlab/workhorse.rb b/lib/gitlab/workhorse.rb index 40e8299c36b..388f84dbe0e 100644 --- a/lib/gitlab/workhorse.rb +++ b/lib/gitlab/workhorse.rb @@ -8,7 +8,7 @@ module Gitlab class << self def git_http_ok(repository, user) { - 'GL_ID' => Gitlab::GlId.gl_id(user), + 'GL_ID' => Gitlab::ShellEnv.gl_id(user), 'RepoPath' => repository.path_to_repo, } end diff --git a/scripts/prepare_build.sh b/scripts/prepare_build.sh index 7e71a030901..d6fb1a34e8c 100755 --- a/scripts/prepare_build.sh +++ b/scripts/prepare_build.sh @@ -16,10 +16,10 @@ retry() { } if [ -f /.dockerenv ] || [ -f ./dockerinit ]; then - mkdir -p vendor/apt + mkdir -p vendor # Install phantomjs package - pushd vendor/apt + pushd vendor if [ ! -e phantomjs_1.9.8-0jessie_amd64.deb ]; then wget -q https://gitlab.com/axil/phantomjs-debian/raw/master/phantomjs_1.9.8-0jessie_amd64.deb fi diff --git a/spec/controllers/blob_controller_spec.rb b/spec/controllers/blob_controller_spec.rb index 465013231f9..eb91e577b87 100644 --- a/spec/controllers/blob_controller_spec.rb +++ b/spec/controllers/blob_controller_spec.rb @@ -38,11 +38,6 @@ describe Projects::BlobController do let(:id) { 'invalid-branch/README.md' } it { is_expected.to respond_with(:not_found) } end - - context "binary file" do - let(:id) { 'binary-encoding/encoding/binary-1.bin' } - it { is_expected.to respond_with(:success) } - end end describe 'GET show with tree path' do diff --git a/spec/controllers/groups/group_members_controller_spec.rb b/spec/controllers/groups/group_members_controller_spec.rb index 89c2c26a367..a5986598715 100644 --- a/spec/controllers/groups/group_members_controller_spec.rb +++ b/spec/controllers/groups/group_members_controller_spec.rb @@ -4,211 +4,17 @@ describe Groups::GroupMembersController do let(:user) { create(:user) } let(:group) { create(:group) } - describe '#index' do + context "index" do before do group.add_owner(user) stub_application_setting(restricted_visibility_levels: [Gitlab::VisibilityLevel::PUBLIC]) end it 'renders index with group members' do - get :index, group_id: group + get :index, group_id: group.path expect(response.status).to eq(200) expect(response).to render_template(:index) end end - - describe '#destroy' do - let(:group) { create(:group, :public) } - - context 'when member is not found' do - it 'returns 403' do - delete :destroy, group_id: group, - id: 42 - - expect(response.status).to eq(403) - end - end - - context 'when member is found' do - let(:user) { create(:user) } - let(:group_user) { create(:user) } - let(:member) do - group.add_developer(group_user) - group.members.find_by(user_id: group_user) - end - - context 'when user does not have enough rights' do - before do - group.add_developer(user) - sign_in(user) - end - - it 'returns 403' do - delete :destroy, group_id: group, - id: member - - expect(response.status).to eq(403) - expect(group.users).to include group_user - end - end - - context 'when user has enough rights' do - before do - group.add_owner(user) - sign_in(user) - end - - it '[HTML] removes user from members' do - delete :destroy, group_id: group, - id: member - - expect(response).to set_flash.to 'User was successfully removed from group.' - expect(response).to redirect_to(group_group_members_path(group)) - expect(group.users).not_to include group_user - end - - it '[JS] removes user from members' do - xhr :delete, :destroy, group_id: group, - id: member - - expect(response).to be_success - expect(group.users).not_to include group_user - end - end - end - end - - describe '#leave' do - let(:group) { create(:group, :public) } - let(:user) { create(:user) } - - context 'when member is not found' do - before { sign_in(user) } - - it 'returns 403' do - delete :leave, group_id: group - - expect(response.status).to eq(403) - end - end - - context 'when member is found' do - context 'and is not an owner' do - before do - group.add_developer(user) - sign_in(user) - end - - it 'removes user from members' do - delete :leave, group_id: group - - expect(response).to set_flash.to "You left the \"#{group.name}\" group." - expect(response).to redirect_to(dashboard_groups_path) - expect(group.users).not_to include user - end - end - - context 'and is an owner' do - before do - group.add_owner(user) - sign_in(user) - end - - it 'cannot removes himself from the group' do - delete :leave, group_id: group - - expect(response).to redirect_to(group_path(group)) - expect(response).to set_flash[:alert].to "You can not leave the \"#{group.name}\" group. Transfer or delete the group." - expect(group.users).to include user - end - end - - context 'and is a requester' do - before do - group.request_access(user) - sign_in(user) - end - - it 'removes user from members' do - delete :leave, group_id: group - - expect(response).to set_flash.to 'Your access request to the group has been withdrawn.' - expect(response).to redirect_to(dashboard_groups_path) - expect(group.members.request).to be_empty - expect(group.users).not_to include user - end - end - end - end - - describe '#request_access' do - let(:group) { create(:group, :public) } - let(:user) { create(:user) } - - before do - sign_in(user) - end - - it 'creates a new GroupMember that is not a team member' do - post :request_access, group_id: group - - expect(response).to set_flash.to 'Your request for access has been queued for review.' - expect(response).to redirect_to(group_path(group)) - expect(group.members.request.exists?(user_id: user)).to be_truthy - expect(group.users).not_to include user - end - end - - describe '#approve_access_request' do - let(:group) { create(:group, :public) } - - context 'when member is not found' do - it 'returns 403' do - post :approve_access_request, group_id: group, - id: 42 - - expect(response.status).to eq(403) - end - end - - context 'when member is found' do - let(:user) { create(:user) } - let(:group_requester) { create(:user) } - let(:member) do - group.request_access(group_requester) - group.members.request.find_by(user_id: group_requester) - end - - context 'when user does not have enough rights' do - before do - group.add_developer(user) - sign_in(user) - end - - it 'returns 403' do - post :approve_access_request, group_id: group, - id: member - - expect(response.status).to eq(403) - expect(group.users).not_to include group_requester - end - end - - context 'when user has enough rights' do - before do - group.add_owner(user) - sign_in(user) - end - - it 'adds user to members' do - post :approve_access_request, group_id: group, - id: member - - expect(response).to redirect_to(group_group_members_path(group)) - expect(group.users).to include group_requester - end - end - end - end end diff --git a/spec/controllers/projects/commit_controller_spec.rb b/spec/controllers/projects/commit_controller_spec.rb index 6e3db10e451..438e776ec4b 100644 --- a/spec/controllers/projects/commit_controller_spec.rb +++ b/spec/controllers/projects/commit_controller_spec.rb @@ -2,8 +2,6 @@ require 'rails_helper' describe Projects::CommitController do describe 'GET show' do - render_views - let(:project) { create(:project) } before do @@ -29,16 +27,6 @@ describe Projects::CommitController do end end - it 'handles binary files' do - get(:show, - namespace_id: project.namespace.to_param, - project_id: project.to_param, - id: TestEnv::BRANCH_SHA['binary-encoding'], - format: "html") - - expect(response).to be_success - end - def go(id:) get :show, namespace_id: project.namespace.to_param, diff --git a/spec/controllers/projects/issues_controller_spec.rb b/spec/controllers/projects/issues_controller_spec.rb index cbaa3e0b7b2..78be7e3dc35 100644 --- a/spec/controllers/projects/issues_controller_spec.rb +++ b/spec/controllers/projects/issues_controller_spec.rb @@ -105,15 +105,6 @@ describe Projects::IssuesController do expect(assigns(:issues)).to eq [issue] end - it 'should not list confidential issues for project members with guest role' do - sign_in(member) - project.team << [member, :guest] - - get_issues - - expect(assigns(:issues)).to eq [issue] - end - it 'should list confidential issues for author' do sign_in(author) get_issues @@ -157,7 +148,7 @@ describe Projects::IssuesController do shared_examples_for 'restricted action' do |http_status| it 'returns 404 for guests' do - sign_out(:user) + sign_out :user go(id: unescaped_parameter_value.to_param) expect(response).to have_http_status :not_found @@ -170,14 +161,6 @@ describe Projects::IssuesController do expect(response).to have_http_status :not_found end - it 'returns 404 for project members with guest role' do - sign_in(member) - project.team << [member, :guest] - go(id: unescaped_parameter_value.to_param) - - expect(response).to have_http_status :not_found - end - it "returns #{http_status[:success]} for author" do sign_in(author) go(id: unescaped_parameter_value.to_param) diff --git a/spec/controllers/projects/project_members_controller_spec.rb b/spec/controllers/projects/project_members_controller_spec.rb index fc5f458e795..750fbecdd07 100644 --- a/spec/controllers/projects/project_members_controller_spec.rb +++ b/spec/controllers/projects/project_members_controller_spec.rb @@ -1,22 +1,22 @@ require('spec_helper') describe Projects::ProjectMembersController do - describe '#apply_import' do - let(:project) { create(:project) } - let(:another_project) { create(:project, :private) } - let(:user) { create(:user) } - let(:member) { create(:user) } - - before do - project.team << [user, :master] - another_project.team << [member, :guest] - sign_in(user) - end + let(:project) { create(:project) } + let(:another_project) { create(:project, :private) } + let(:user) { create(:user) } + let(:member) { create(:user) } + + before do + project.team << [user, :master] + another_project.team << [member, :guest] + sign_in(user) + end + describe '#apply_import' do shared_context 'import applied' do before do - post(:apply_import, namespace_id: project.namespace, - project_id: project, + post(:apply_import, namespace_id: project.namespace.to_param, + project_id: project.to_param, source_project_id: another_project.id) end end @@ -48,231 +48,18 @@ describe Projects::ProjectMembersController do end describe '#index' do + let(:project) { create(:project, :private) } + context 'when user is member' do + let(:member) { create(:user) } + before do - project = create(:project, :private) - member = create(:user) project.team << [member, :guest] sign_in(member) - - get :index, namespace_id: project.namespace, project_id: project + get :index, namespace_id: project.namespace.to_param, project_id: project.to_param end it { expect(response.status).to eq(200) } end end - - describe '#destroy' do - let(:project) { create(:project, :public) } - - context 'when member is not found' do - it 'returns 404' do - delete :destroy, namespace_id: project.namespace, - project_id: project, - id: 42 - - expect(response.status).to eq(404) - end - end - - context 'when member is found' do - let(:user) { create(:user) } - let(:team_user) { create(:user) } - let(:member) do - project.team << [team_user, :developer] - project.members.find_by(user_id: team_user.id) - end - - context 'when user does not have enough rights' do - before do - project.team << [user, :developer] - sign_in(user) - end - - it 'returns 404' do - delete :destroy, namespace_id: project.namespace, - project_id: project, - id: member - - expect(response.status).to eq(404) - expect(project.users).to include team_user - end - end - - context 'when user has enough rights' do - before do - project.team << [user, :master] - sign_in(user) - end - - it '[HTML] removes user from members' do - delete :destroy, namespace_id: project.namespace, - project_id: project, - id: member - - expect(response).to redirect_to( - namespace_project_project_members_path(project.namespace, project) - ) - expect(project.users).not_to include team_user - end - - it '[JS] removes user from members' do - xhr :delete, :destroy, namespace_id: project.namespace, - project_id: project, - id: member - - expect(response).to be_success - expect(project.users).not_to include team_user - end - end - end - end - - describe '#leave' do - let(:project) { create(:project, :public) } - let(:user) { create(:user) } - - context 'when member is not found' do - before { sign_in(user) } - - it 'returns 403' do - delete :leave, namespace_id: project.namespace, - project_id: project - - expect(response.status).to eq(403) - end - end - - context 'when member is found' do - context 'and is not an owner' do - before do - project.team << [user, :developer] - sign_in(user) - end - - it 'removes user from members' do - delete :leave, namespace_id: project.namespace, - project_id: project - - expect(response).to set_flash.to "You left the \"#{project.human_name}\" project." - expect(response).to redirect_to(dashboard_projects_path) - expect(project.users).not_to include user - end - end - - context 'and is an owner' do - before do - project.update(namespace_id: user.namespace_id) - project.team << [user, :master, user] - sign_in(user) - end - - it 'cannot remove himself from the project' do - delete :leave, namespace_id: project.namespace, - project_id: project - - expect(response).to redirect_to( - namespace_project_path(project.namespace, project) - ) - expect(response).to set_flash[:alert].to "You can not leave the \"#{project.human_name}\" project. Transfer or delete the project." - expect(project.users).to include user - end - end - - context 'and is a requester' do - before do - project.request_access(user) - sign_in(user) - end - - it 'removes user from members' do - delete :leave, namespace_id: project.namespace, - project_id: project - - expect(response).to set_flash.to 'Your access request to the project has been withdrawn.' - expect(response).to redirect_to(dashboard_projects_path) - expect(project.members.request).to be_empty - expect(project.users).not_to include user - end - end - end - end - - describe '#request_access' do - let(:project) { create(:project, :public) } - let(:user) { create(:user) } - - before do - sign_in(user) - end - - it 'creates a new ProjectMember that is not a team member' do - post :request_access, namespace_id: project.namespace, - project_id: project - - expect(response).to set_flash.to 'Your request for access has been queued for review.' - expect(response).to redirect_to( - namespace_project_path(project.namespace, project) - ) - expect(project.members.request.exists?(user_id: user)).to be_truthy - expect(project.users).not_to include user - end - end - - describe '#approve' do - let(:project) { create(:project, :public) } - - context 'when member is not found' do - it 'returns 404' do - post :approve_access_request, namespace_id: project.namespace, - project_id: project, - id: 42 - - expect(response.status).to eq(404) - end - end - - context 'when member is found' do - let(:user) { create(:user) } - let(:team_requester) { create(:user) } - let(:member) do - project.request_access(team_requester) - project.members.request.find_by(user_id: team_requester.id) - end - - context 'when user does not have enough rights' do - before do - project.team << [user, :developer] - sign_in(user) - end - - it 'returns 404' do - post :approve_access_request, namespace_id: project.namespace, - project_id: project, - id: member - - expect(response.status).to eq(404) - expect(project.users).not_to include team_requester - end - end - - context 'when user has enough rights' do - before do - project.team << [user, :master] - sign_in(user) - end - - it 'adds user to members' do - post :approve_access_request, namespace_id: project.namespace, - project_id: project, - id: member - - expect(response).to redirect_to( - namespace_project_project_members_path(project.namespace, project) - ) - expect(project.users).to include team_requester - end - end - end - end end diff --git a/spec/factories/deployments.rb b/spec/factories/deployments.rb deleted file mode 100644 index 82591604fcb..00000000000 --- a/spec/factories/deployments.rb +++ /dev/null @@ -1,13 +0,0 @@ -FactoryGirl.define do - factory :deployment, class: Deployment do - sha '97de212e80737a608d939f648d959671fb0a0142' - ref 'master' - tag false - - environment factory: :environment - - after(:build) do |deployment, evaluator| - deployment.project = deployment.environment.project - end - end -end diff --git a/spec/factories/environments.rb b/spec/factories/environments.rb deleted file mode 100644 index 07265c26ca3..00000000000 --- a/spec/factories/environments.rb +++ /dev/null @@ -1,7 +0,0 @@ -FactoryGirl.define do - factory :environment, class: Environment do - sequence(:name) { |n| "environment#{n}" } - - project factory: :empty_project - end -end diff --git a/spec/factories/projects.rb b/spec/factories/projects.rb index 5c8ddbebf0d..da8d97c9f82 100644 --- a/spec/factories/projects.rb +++ b/spec/factories/projects.rb @@ -67,6 +67,9 @@ FactoryGirl.define do 'new_issue_url' => 'http://redmine/projects/project_name_in_redmine/issues/new' } ) + + project.issues_tracker = 'redmine' + project.issues_tracker_id = 'project_name_in_redmine' end end @@ -81,6 +84,9 @@ FactoryGirl.define do 'new_issue_url' => 'http://jira.example/secure/CreateIssue.jspa' } ) + + project.issues_tracker = 'jira' + project.issues_tracker_id = 'project_name_in_jira' end end end diff --git a/spec/features/admin/admin_hooks_spec.rb b/spec/features/admin/admin_hooks_spec.rb index 31633817d53..7265cdac7a7 100644 --- a/spec/features/admin/admin_hooks_spec.rb +++ b/spec/features/admin/admin_hooks_spec.rb @@ -12,11 +12,9 @@ describe "Admin::Hooks", feature: true do describe "GET /admin/hooks" do it "should be ok" do visit admin_root_path - - page.within ".layout-nav" do + page.within ".sidebar-wrapper" do click_on "Hooks" end - expect(current_path).to eq(admin_hooks_path) end diff --git a/spec/features/atom/dashboard_issues_spec.rb b/spec/features/atom/dashboard_issues_spec.rb index 4dd9548cfc5..b710cb3c72f 100644 --- a/spec/features/atom/dashboard_issues_spec.rb +++ b/spec/features/atom/dashboard_issues_spec.rb @@ -5,6 +5,8 @@ describe "Dashboard Issues Feed", feature: true do let!(:user) { create(:user) } let!(:project1) { create(:project) } let!(:project2) { create(:project) } + let!(:issue1) { create(:issue, author: user, assignee: user, project: project1) } + let!(:issue2) { create(:issue, author: user, assignee: user, project: project2) } before do project1.team << [user, :master] @@ -12,51 +14,16 @@ describe "Dashboard Issues Feed", feature: true do end describe "atom feed" do - it "renders atom feed via private token" do + it "should render atom feed via private token" do visit issues_dashboard_path(:atom, private_token: user.private_token) - expect(response_headers['Content-Type']).to have_content('application/atom+xml') + expect(response_headers['Content-Type']). + to have_content('application/atom+xml') expect(body).to have_selector('title', text: "#{user.name} issues") - end - - context "issue with basic fields" do - let!(:issue2) { create(:issue, author: user, assignee: user, project: project2, description: 'test desc') } - - it "renders issue fields" do - visit issues_dashboard_path(:atom, private_token: user.private_token) - - entry = find(:xpath, "//feed/entry[contains(summary/text(),'#{issue2.title}')]") - - expect(entry).to be_present - expect(entry).to have_selector('author email', text: issue2.author_email) - expect(entry).to have_selector('assignee email', text: issue2.author_email) - expect(entry).not_to have_selector('labels') - expect(entry).not_to have_selector('milestone') - expect(entry).to have_selector('description', text: issue2.description) - end - end - - context "issue with label and milestone" do - let!(:milestone1) { create(:milestone, project: project1, title: 'v1') } - let!(:label1) { create(:label, project: project1, title: 'label1') } - let!(:issue1) { create(:issue, author: user, assignee: user, project: project1, milestone: milestone1) } - - before do - issue1.labels << label1 - end - - it "renders issue label and milestone info" do - visit issues_dashboard_path(:atom, private_token: user.private_token) - - entry = find(:xpath, "//feed/entry[contains(summary/text(),'#{issue1.title}')]") - - expect(entry).to be_present - expect(entry).to have_selector('author email', text: issue1.author_email) - expect(entry).to have_selector('assignee email', text: issue1.author_email) - expect(entry).to have_selector('labels label', text: label1.title) - expect(entry).to have_selector('milestone', text: milestone1.title) - expect(entry).not_to have_selector('description') - end + expect(body).to have_selector('author email', text: issue1.author_email) + expect(body).to have_selector('entry summary', text: issue1.title) + expect(body).to have_selector('author email', text: issue2.author_email) + expect(body).to have_selector('entry summary', text: issue2.title) end end end diff --git a/spec/features/builds_spec.rb b/spec/features/builds_spec.rb index 16832c297ac..b8ecc356b4d 100644 --- a/spec/features/builds_spec.rb +++ b/spec/features/builds_spec.rb @@ -97,42 +97,6 @@ describe "Builds" do end end - context 'Artifacts expire date' do - before do - @build.update_attributes(artifacts_file: artifacts_file, artifacts_expire_at: expire_at) - visit namespace_project_build_path(@project.namespace, @project, @build) - end - - context 'no expire date defined' do - let(:expire_at) { nil } - - it 'does not have the Keep button' do - expect(page).not_to have_content 'Keep' - end - end - - context 'when expire date is defined' do - let(:expire_at) { Time.now + 7.days } - - it 'keeps artifacts when Keep button is clicked' do - expect(page).to have_content 'The artifacts will be removed' - click_link 'Keep' - - expect(page).not_to have_link 'Keep' - expect(page).not_to have_content 'The artifacts will be removed' - end - end - - context 'when artifacts expired' do - let(:expire_at) { Time.now - 7.days } - - it 'does not have the Keep button' do - expect(page).to have_content 'The artifacts were removed' - expect(page).not_to have_link 'Keep' - end - end - end - context 'Build raw trace' do before do @build.run! diff --git a/spec/features/environments_spec.rb b/spec/features/environments_spec.rb deleted file mode 100644 index 40fea5211e9..00000000000 --- a/spec/features/environments_spec.rb +++ /dev/null @@ -1,160 +0,0 @@ -require 'spec_helper' - -feature 'Environments', feature: true do - given(:project) { create(:empty_project) } - given(:user) { create(:user) } - given(:role) { :developer } - - background do - login_as(user) - project.team << [user, role] - end - - describe 'when showing environments' do - given!(:environment) { } - given!(:deployment) { } - - before do - visit namespace_project_environments_path(project.namespace, project) - end - - context 'without environments' do - scenario 'does show no environments' do - expect(page).to have_content('No environments to show') - end - end - - context 'with environments' do - given(:environment) { create(:environment, project: project) } - - scenario 'does show environment name' do - expect(page).to have_link(environment.name) - end - - context 'without deployments' do - scenario 'does show no deployments' do - expect(page).to have_content('No deployments yet') - end - end - - context 'with deployments' do - given(:deployment) { create(:deployment, environment: environment) } - - scenario 'does show deployment SHA' do - expect(page).to have_link(deployment.short_sha) - end - end - end - - scenario 'does have a New environment button' do - expect(page).to have_link('New environment') - end - end - - describe 'when showing the environment' do - given(:environment) { create(:environment, project: project) } - given!(:deployment) { } - - before do - visit namespace_project_environment_path(project.namespace, project, environment) - end - - context 'without deployments' do - scenario 'does show no deployments' do - expect(page).to have_content('No deployments for') - end - end - - context 'with deployments' do - given(:deployment) { create(:deployment, environment: environment) } - - scenario 'does show deployment SHA' do - expect(page).to have_link(deployment.short_sha) - end - - scenario 'does not show a retry button for deployment without build' do - expect(page).not_to have_link('Retry') - end - - context 'with build' do - given(:build) { create(:ci_build, project: project) } - given(:deployment) { create(:deployment, environment: environment, deployable: build) } - - scenario 'does show build name' do - expect(page).to have_link("#{build.name} (##{build.id})") - end - - scenario 'does show retry button' do - expect(page).to have_link('Retry') - end - end - end - end - - describe 'when creating a new environment' do - before do - visit namespace_project_environments_path(project.namespace, project) - end - - context 'when logged as developer' do - before do - click_link 'New environment' - end - - context 'for valid name' do - before do - fill_in('Name', with: 'production') - click_on 'Create environment' - end - - scenario 'does create a new pipeline' do - expect(page).to have_content('production') - end - end - - context 'for invalid name' do - before do - fill_in('Name', with: 'name with spaces') - click_on 'Create environment' - end - - scenario 'does show errors' do - expect(page).to have_content('Name can contain only letters') - end - end - end - - context 'when logged as reporter' do - given(:role) { :reporter } - - scenario 'does not have a New environment link' do - expect(page).not_to have_link('New environment') - end - end - end - - describe 'when deleting existing environment' do - given(:environment) { create(:environment, project: project) } - - before do - visit namespace_project_environment_path(project.namespace, project, environment) - end - - context 'when logged as master' do - given(:role) { :master } - - scenario 'does delete environment' do - click_link 'Destroy' - expect(page).not_to have_link(environment.name) - end - end - - context 'when logged as developer' do - given(:role) { :developer } - - scenario 'does not have a Destroy link' do - expect(page).not_to have_link('Destroy') - end - end - end -end diff --git a/spec/features/groups/members/owner_manages_access_requests_spec.rb b/spec/features/groups/members/owner_manages_access_requests_spec.rb deleted file mode 100644 index 22525ce530b..00000000000 --- a/spec/features/groups/members/owner_manages_access_requests_spec.rb +++ /dev/null @@ -1,48 +0,0 @@ -require 'spec_helper' - -feature 'Groups > Members > Owner manages access requests', feature: true do - let(:user) { create(:user) } - let(:owner) { create(:user) } - let(:group) { create(:group, :public) } - - background do - group.request_access(user) - group.add_owner(owner) - login_as(owner) - end - - scenario 'owner can see access requests' do - visit group_group_members_path(group) - - expect_visible_access_request(group, user) - end - - scenario 'master can grant access' do - visit group_group_members_path(group) - - expect_visible_access_request(group, user) - - perform_enqueued_jobs { click_on 'Grant access' } - - expect(ActionMailer::Base.deliveries.last.to).to eq [user.notification_email] - expect(ActionMailer::Base.deliveries.last.subject).to match "Access to the #{group.name} group was granted" - end - - scenario 'master can deny access' do - visit group_group_members_path(group) - - expect_visible_access_request(group, user) - - perform_enqueued_jobs { click_on 'Deny access' } - - expect(ActionMailer::Base.deliveries.last.to).to eq [user.notification_email] - expect(ActionMailer::Base.deliveries.last.subject).to match "Access to the #{group.name} group was denied" - end - - - def expect_visible_access_request(group, user) - expect(group.members.request.exists?(user_id: user)).to be_truthy - expect(page).to have_content "#{group.name} access requests (1)" - expect(page).to have_content user.name - end -end diff --git a/spec/features/groups/members/user_requests_access_spec.rb b/spec/features/groups/members/user_requests_access_spec.rb deleted file mode 100644 index a878a96b6ee..00000000000 --- a/spec/features/groups/members/user_requests_access_spec.rb +++ /dev/null @@ -1,48 +0,0 @@ -require 'spec_helper' - -feature 'Groups > Members > User requests access', feature: true do - let(:user) { create(:user) } - let(:owner) { create(:user) } - let(:group) { create(:group, :public) } - - background do - group.add_owner(owner) - login_as(user) - visit group_path(group) - end - - scenario 'user can request access to a group' do - perform_enqueued_jobs { click_link 'Request Access' } - - expect(ActionMailer::Base.deliveries.last.to).to eq [owner.notification_email] - expect(ActionMailer::Base.deliveries.last.subject).to match "Request to join the #{group.name} group" - - expect(group.members.request.exists?(user_id: user)).to be_truthy - expect(page).to have_content 'Your request for access has been queued for review.' - - expect(page).to have_content 'Withdraw Access Request' - end - - scenario 'user is not listed in the group members page' do - click_link 'Request Access' - - expect(group.members.request.exists?(user_id: user)).to be_truthy - - click_link 'Members' - - page.within('.content') do - expect(page).not_to have_content(user.name) - end - end - - scenario 'user can withdraw its request for access' do - click_link 'Request Access' - - expect(group.members.request.exists?(user_id: user)).to be_truthy - - click_link 'Withdraw Access Request' - - expect(group.members.request.exists?(user_id: user)).to be_falsey - expect(page).to have_content 'Your access request to the group has been withdrawn.' - end -end diff --git a/spec/features/issues/bulk_assigment_labels_spec.rb b/spec/features/issues/bulk_assigment_labels_spec.rb index 0fbc2062e39..c58b87281a3 100644 --- a/spec/features/issues/bulk_assigment_labels_spec.rb +++ b/spec/features/issues/bulk_assigment_labels_spec.rb @@ -83,23 +83,6 @@ feature 'Issues > Labels bulk assignment', feature: true do end end - context 'can assign a label to all issues when label is present' do - before do - issue2.labels << bug - issue2.labels << feature - visit namespace_project_issues_path(project.namespace, project) - - check 'check_all_issues' - open_labels_dropdown ['bug'] - update_issues - end - - it do - expect(find("#issue_#{issue1.id}")).to have_content 'bug' - expect(find("#issue_#{issue2.id}")).to have_content 'bug' - end - end - context 'can bulk un-assign' do context 'all labels to all issues' do before do diff --git a/spec/features/issues/filter_by_labels_spec.rb b/spec/features/issues/filter_by_labels_spec.rb index 5ea02b8d39c..16c619c9288 100644 --- a/spec/features/issues/filter_by_labels_spec.rb +++ b/spec/features/issues/filter_by_labels_spec.rb @@ -56,9 +56,8 @@ feature 'Issue filtering by Labels', feature: true do end it 'should remove label "bug"' do - find('.js-label-filter-remove').click - wait_for_ajax - expect(find('.filtered-labels', visible: false)).to have_no_content "bug" + first('.js-label-filter-remove').click + expect(find('.filtered-labels')).to have_no_content "bug" end end @@ -143,8 +142,7 @@ feature 'Issue filtering by Labels', feature: true do end it 'should remove label "enhancement"' do - find('.js-label-filter-remove', match: :first).click - wait_for_ajax + first('.js-label-filter-remove').click expect(find('.filtered-labels')).to have_no_content "enhancement" end end @@ -181,7 +179,6 @@ feature 'Issue filtering by Labels', feature: true do before do page.within '.labels-filter' do click_button 'Label' - wait_for_ajax click_link 'bug' find('.dropdown-menu-close').click end @@ -192,11 +189,14 @@ feature 'Issue filtering by Labels', feature: true do end it 'should allow user to remove filtered labels' do - first('.js-label-filter-remove').click - wait_for_ajax + page.within '.filtered-labels' do + first('.js-label-filter-remove').click + expect(page).not_to have_content 'bug' + end - expect(find('.filtered-labels', visible: false)).not_to have_content 'bug' - expect(find('.labels-filter')).not_to have_content 'bug' + page.within '.labels-filter' do + expect(page).not_to have_content 'bug' + end end end diff --git a/spec/features/issues/filter_issues_spec.rb b/spec/features/issues/filter_issues_spec.rb index 4bcb105b17d..1f0594e6b02 100644 --- a/spec/features/issues/filter_issues_spec.rb +++ b/spec/features/issues/filter_issues_spec.rb @@ -1,7 +1,6 @@ require 'rails_helper' describe 'Filter issues', feature: true do - include WaitForAjax let!(:project) { create(:project) } let!(:user) { create(:user)} @@ -22,7 +21,7 @@ describe 'Filter issues', feature: true do find('.dropdown-menu-user-link', text: user.username).click - wait_for_ajax + sleep 2 end context 'assignee', js: true do @@ -54,7 +53,7 @@ describe 'Filter issues', feature: true do find('.milestone-filter .dropdown-content a', text: milestone.title).click - wait_for_ajax + sleep 2 end context 'milestone', js: true do @@ -81,21 +80,23 @@ describe 'Filter issues', feature: true do before do visit namespace_project_issues_path(project.namespace, project) find('.js-label-select').click - wait_for_ajax end it 'should filter by any label' do find('.dropdown-menu-labels a', text: 'Any Label').click page.first('.labels-filter .dropdown-title .dropdown-menu-close-icon').click - wait_for_ajax + sleep 2 - expect(find('.labels-filter')).to have_content 'Label' + page.within '.labels-filter' do + expect(page).to have_content 'Any Label' + end + expect(find('.js-label-select .dropdown-toggle-text')).to have_content('Any Label') end it 'should filter by no label' do find('.dropdown-menu-labels a', text: 'No Label').click page.first('.labels-filter .dropdown-title .dropdown-menu-close-icon').click - wait_for_ajax + sleep 2 page.within '.labels-filter' do expect(page).to have_content 'No Label' @@ -121,14 +122,14 @@ describe 'Filter issues', feature: true do find('.dropdown-menu-user-link', text: user.username).click - wait_for_ajax + sleep 2 find('.js-label-select').click find('.dropdown-menu-labels .dropdown-content a', text: label.title).click page.first('.labels-filter .dropdown-title .dropdown-menu-close-icon').click - wait_for_ajax + sleep 2 end context 'assignee and label', js: true do @@ -275,12 +276,9 @@ describe 'Filter issues', feature: true do it 'should be able to filter and sort issues' do click_button 'Label' - wait_for_ajax page.within '.labels-filter' do click_link 'bug' end - find('.dropdown-menu-close-icon').click - wait_for_ajax page.within '.issues-list' do expect(page).to have_selector('.issue', count: 2) @@ -290,7 +288,6 @@ describe 'Filter issues', feature: true do page.within '.dropdown-menu-sort' do click_link 'Oldest created' end - wait_for_ajax page.within '.issues-list' do expect(first('.issue')).to have_content('Frontend') diff --git a/spec/features/issues/move_spec.rb b/spec/features/issues/move_spec.rb index 7773c486b4e..c7019c5aea1 100644 --- a/spec/features/issues/move_spec.rb +++ b/spec/features/issues/move_spec.rb @@ -26,7 +26,6 @@ feature 'issue move to another project' do context 'user has permission to move issue' do let!(:mr) { create(:merge_request, source_project: old_project) } let(:new_project) { create(:project) } - let(:new_project_search) { create(:project) } let(:text) { 'Text with !1' } let(:cross_reference) { old_project.to_reference } @@ -48,21 +47,6 @@ feature 'issue move to another project' do expect(page).to have_content(issue.title) end - scenario 'searching project dropdown', js: true do - new_project_search.team << [user, :reporter] - - page.within '.js-move-dropdown' do - first('.select2-choice').click - end - - fill_in('s2id_autogen2_search', with: new_project_search.name) - - page.within '.select2-drop' do - expect(page).to have_content(new_project_search.name) - expect(page).not_to have_content(new_project.name) - end - end - context 'user does not have permission to move the issue to a project', js: true do let!(:private_project) { create(:project, :private) } let(:another_project) { create(:project) } diff --git a/spec/features/issues/todo_spec.rb b/spec/features/issues/todo_spec.rb deleted file mode 100644 index b69cce3e7d7..00000000000 --- a/spec/features/issues/todo_spec.rb +++ /dev/null @@ -1,33 +0,0 @@ -require 'rails_helper' - -feature 'Manually create a todo item from issue', feature: true, js: true do - let!(:project) { create(:project) } - let!(:issue) { create(:issue, project: project) } - let!(:user) { create(:user)} - - before do - project.team << [user, :master] - login_as(user) - visit namespace_project_issue_path(project.namespace, project, issue) - end - - it 'should create todo when clicking button' do - page.within '.issuable-sidebar' do - click_button 'Add Todo' - expect(page).to have_content 'Mark Done' - end - - page.within '.header-content .todos-pending-count' do - expect(page).to have_content '1' - end - end - - it 'should mark a todo as done' do - page.within '.issuable-sidebar' do - click_button 'Add Todo' - click_button 'Mark Done' - end - - expect(page).to have_selector('.todos-pending-count', visible: false) - end -end diff --git a/spec/features/issues_spec.rb b/spec/features/issues_spec.rb index c3cb3379440..f6fb6a72d22 100644 --- a/spec/features/issues_spec.rb +++ b/spec/features/issues_spec.rb @@ -396,27 +396,6 @@ describe 'Issues', feature: true do expect(page).to have_content @user.name end end - - it 'allows user to unselect themselves', js: true do - issue2 = create(:issue, project: project, author: @user) - visit namespace_project_issue_path(project.namespace, project, issue2) - - page.within '.assignee' do - click_link 'Edit' - click_link @user.name - - page.within '.value' do - expect(page).to have_content @user.name - end - - click_link 'Edit' - click_link @user.name - - page.within '.value' do - expect(page).to have_content "No assignee" - end - end - end end context 'by unauthorized user' do @@ -461,26 +440,6 @@ describe 'Issues', feature: true do expect(issue.reload.milestone).to be_nil end - - it 'allows user to de-select milestone', js: true do - visit namespace_project_issue_path(project.namespace, project, issue) - - page.within('.milestone') do - click_link 'Edit' - click_link milestone.title - - page.within '.value' do - expect(page).to have_content milestone.title - end - - click_link 'Edit' - click_link milestone.title - - page.within '.value' do - expect(page).to have_content 'None' - end - end - end end context 'by unauthorized user' do @@ -556,10 +515,10 @@ describe 'Issues', feature: true do first('.ui-state-default').click end - expect(page).to have_no_content 'No due date' + expect(page).to have_no_content 'None' click_link 'remove due date' - expect(page).to have_content 'No due date' + expect(page).to have_content 'None' end end end diff --git a/spec/features/projects/commits/cherry_pick_spec.rb b/spec/features/projects/commits/cherry_pick_spec.rb index f88c0616b52..0559b02f321 100644 --- a/spec/features/projects/commits/cherry_pick_spec.rb +++ b/spec/features/projects/commits/cherry_pick_spec.rb @@ -16,7 +16,6 @@ describe 'Cherry-pick Commits' do it do visit namespace_project_commit_path(project.namespace, project, master_pickable_commit.id) find("a[href='#modal-cherry-pick-commit']").click - expect(page).not_to have_content('v1.0.0') # Only branches, not tags page.within('#modal-cherry-pick-commit') do uncheck 'create_merge_request' click_button 'Cherry-pick' diff --git a/spec/features/projects/members/master_manages_access_requests_spec.rb b/spec/features/projects/members/master_manages_access_requests_spec.rb deleted file mode 100644 index 5fe4caa12f0..00000000000 --- a/spec/features/projects/members/master_manages_access_requests_spec.rb +++ /dev/null @@ -1,47 +0,0 @@ -require 'spec_helper' - -feature 'Projects > Members > Master manages access requests', feature: true do - let(:user) { create(:user) } - let(:master) { create(:user) } - let(:project) { create(:project, :public) } - - background do - project.request_access(user) - project.team << [master, :master] - login_as(master) - end - - scenario 'master can see access requests' do - visit namespace_project_project_members_path(project.namespace, project) - - expect_visible_access_request(project, user) - end - - scenario 'master can grant access' do - visit namespace_project_project_members_path(project.namespace, project) - - expect_visible_access_request(project, user) - - perform_enqueued_jobs { click_on 'Grant access' } - - expect(ActionMailer::Base.deliveries.last.to).to eq [user.notification_email] - expect(ActionMailer::Base.deliveries.last.subject).to match "Access to the #{project.name_with_namespace} project was granted" - end - - scenario 'master can deny access' do - visit namespace_project_project_members_path(project.namespace, project) - - expect_visible_access_request(project, user) - - perform_enqueued_jobs { click_on 'Deny access' } - - expect(ActionMailer::Base.deliveries.last.to).to eq [user.notification_email] - expect(ActionMailer::Base.deliveries.last.subject).to match "Access to the #{project.name_with_namespace} project was denied" - end - - def expect_visible_access_request(project, user) - expect(project.members.request.exists?(user_id: user)).to be_truthy - expect(page).to have_content "#{project.name} access requests (1)" - expect(page).to have_content user.name - end -end diff --git a/spec/features/projects/members/user_requests_access_spec.rb b/spec/features/projects/members/user_requests_access_spec.rb deleted file mode 100644 index fd92a3a2f0c..00000000000 --- a/spec/features/projects/members/user_requests_access_spec.rb +++ /dev/null @@ -1,54 +0,0 @@ -require 'spec_helper' - -feature 'Projects > Members > User requests access', feature: true do - let(:user) { create(:user) } - let(:master) { create(:user) } - let(:project) { create(:project, :public) } - - background do - project.team << [master, :master] - login_as(user) - visit namespace_project_path(project.namespace, project) - end - - scenario 'user can request access to a project' do - perform_enqueued_jobs { click_link 'Request Access' } - - expect(ActionMailer::Base.deliveries.last.to).to eq [master.notification_email] - expect(ActionMailer::Base.deliveries.last.subject).to eq "Request to join the #{project.name_with_namespace} project" - - expect(project.members.request.exists?(user_id: user)).to be_truthy - expect(page).to have_content 'Your request for access has been queued for review.' - - expect(page).to have_content 'Withdraw Access Request' - end - - scenario 'user is not listed in the project members page' do - click_link 'Request Access' - - expect(project.members.request.exists?(user_id: user)).to be_truthy - - open_project_settings_menu - click_link 'Members' - - visit namespace_project_project_members_path(project.namespace, project) - page.within('.content') do - expect(page).not_to have_content(user.name) - end - end - - scenario 'user can withdraw its request for access' do - click_link 'Request Access' - - expect(project.members.request.exists?(user_id: user)).to be_truthy - - click_link 'Withdraw Access Request' - - expect(project.members.request.exists?(user_id: user)).to be_falsey - expect(page).to have_content 'Your access request to the project has been withdrawn.' - end - - def open_project_settings_menu - find('#project-settings-button').click - end -end diff --git a/spec/features/search_spec.rb b/spec/features/search_spec.rb index b9e63a7152c..029a11ea43c 100644 --- a/spec/features/search_spec.rb +++ b/spec/features/search_spec.rb @@ -47,83 +47,4 @@ describe "Search", feature: true do expect(page).to have_link(snippet.title) end end - - - describe 'Right header search field', feature: true do - - describe 'Search in project page' do - before do - visit namespace_project_path(project.namespace, project) - end - - it 'top right search form is present' do - expect(page).to have_selector('#search') - end - - it 'top right search form contains location badge' do - expect(page).to have_selector('.has-location-badge') - end - - context 'clicking the search field', js: true do - it 'should show category search dropdown' do - page.find('#search').click - - expect(page).to have_selector('.dropdown-header', text: /#{project.name}/i) - end - end - - context 'click the links in the category search dropdown', js: true do - - before do - page.find('#search').click - end - - it 'should take user to her issues page when issues assigned is clicked' do - find('.dropdown-menu').click_link 'Issues assigned to me' - sleep 2 - - expect(page).to have_selector('.issues-holder') - expect(find('.js-assignee-search .dropdown-toggle-text')).to have_content(user.name) - end - - it 'should take user to her issues page when issues authored is clicked' do - find('.dropdown-menu').click_link "Issues I've created" - sleep 2 - - expect(page).to have_selector('.issues-holder') - expect(find('.js-author-search .dropdown-toggle-text')).to have_content(user.name) - end - - it 'should take user to her MR page when MR assigned is clicked' do - find('.dropdown-menu').click_link 'Merge requests assigned to me' - sleep 2 - - expect(page).to have_selector('.merge-requests-holder') - expect(find('.js-assignee-search .dropdown-toggle-text')).to have_content(user.name) - end - - it 'should take user to her MR page when MR authored is clicked' do - find('.dropdown-menu').click_link "Merge requests I've created" - sleep 2 - - expect(page).to have_selector('.merge-requests-holder') - expect(find('.js-author-search .dropdown-toggle-text')).to have_content(user.name) - end - end - - context 'entering text into the search field', js: true do - before do - page.within '.search-input-wrap' do - fill_in "search", with: project.name[0..3] - end - end - - it 'should not display the category search dropdown' do - expect(page).not_to have_selector('.dropdown-header', text: /#{project.name}/i) - end - end - end - end - - end diff --git a/spec/features/security/project/public_access_spec.rb b/spec/features/security/project/public_access_spec.rb index f6c6687e162..c5f741709ad 100644 --- a/spec/features/security/project/public_access_spec.rb +++ b/spec/features/security/project/public_access_spec.rb @@ -175,49 +175,6 @@ describe "Public Project Access", feature: true do end end - describe "GET /:project_path/environments" do - subject { namespace_project_environments_path(project.namespace, project) } - - it { is_expected.to be_allowed_for :admin } - it { is_expected.to be_allowed_for owner } - it { is_expected.to be_allowed_for master } - it { is_expected.to be_allowed_for developer } - it { is_expected.to be_allowed_for reporter } - it { is_expected.to be_denied_for guest } - it { is_expected.to be_denied_for :user } - it { is_expected.to be_denied_for :external } - it { is_expected.to be_denied_for :visitor } - end - - describe "GET /:project_path/environments/:id" do - let(:environment) { create(:environment, project: project) } - subject { namespace_project_environments_path(project.namespace, project, environment) } - - it { is_expected.to be_allowed_for :admin } - it { is_expected.to be_allowed_for owner } - it { is_expected.to be_allowed_for master } - it { is_expected.to be_allowed_for developer } - it { is_expected.to be_allowed_for reporter } - it { is_expected.to be_denied_for guest } - it { is_expected.to be_denied_for :user } - it { is_expected.to be_denied_for :external } - it { is_expected.to be_denied_for :visitor } - end - - describe "GET /:project_path/environments/new" do - subject { new_namespace_project_environment_path(project.namespace, project) } - - it { is_expected.to be_allowed_for :admin } - it { is_expected.to be_allowed_for owner } - it { is_expected.to be_allowed_for master } - it { is_expected.to be_allowed_for developer } - it { is_expected.to be_denied_for reporter } - it { is_expected.to be_denied_for guest } - it { is_expected.to be_denied_for :user } - it { is_expected.to be_denied_for :external } - it { is_expected.to be_denied_for :visitor } - end - describe "GET /:project_path/blob" do let(:commit) { project.repository.commit } diff --git a/spec/features/u2f_spec.rb b/spec/features/u2f_spec.rb index 14613754f74..366a90228b1 100644 --- a/spec/features/u2f_spec.rb +++ b/spec/features/u2f_spec.rb @@ -12,24 +12,39 @@ feature 'Using U2F (Universal 2nd Factor) Devices for Authentication', feature: describe "registration" do let(:user) { create(:user) } - - before do - login_as(user) - user.update_attribute(:otp_required_for_login, true) - end + before { login_as(user) } describe 'when 2FA via OTP is disabled' do - before { user.update_attribute(:otp_required_for_login, false) } + it 'allows registering a new device' do + visit profile_account_path + click_on 'Enable Two-Factor Authentication' - it 'does not allow registering a new device' do + register_u2f_device + + expect(page.body).to match('Your U2F device was registered') + end + + it 'allows registering more than one device' do visit profile_account_path + + # First device click_on 'Enable Two-Factor Authentication' + register_u2f_device + expect(page.body).to match('Your U2F device was registered') + + # Second device + click_on 'Manage Two-Factor Authentication' + register_u2f_device + expect(page.body).to match('Your U2F device was registered') + click_on 'Manage Two-Factor Authentication' - expect(page).to have_button('Setup New U2F Device', disabled: true) + expect(page.body).to match('You have 2 U2F devices registered') end end describe 'when 2FA via OTP is enabled' do + before { user.update_attributes(otp_required_for_login: true) } + it 'allows registering a new device' do visit profile_account_path click_on 'Manage Two-Factor Authentication' @@ -52,6 +67,7 @@ feature 'Using U2F (Universal 2nd Factor) Devices for Authentication', feature: click_on 'Manage Two-Factor Authentication' register_u2f_device expect(page.body).to match('Your U2F device was registered') + click_on 'Manage Two-Factor Authentication' expect(page.body).to match('You have 2 U2F devices registered') end @@ -60,16 +76,15 @@ feature 'Using U2F (Universal 2nd Factor) Devices for Authentication', feature: it 'allows the same device to be registered for multiple users' do # First user visit profile_account_path - click_on 'Manage Two-Factor Authentication' + click_on 'Enable Two-Factor Authentication' u2f_device = register_u2f_device expect(page.body).to match('Your U2F device was registered') logout # Second user - user = login_as(:user) - user.update_attribute(:otp_required_for_login, true) + login_as(:user) visit profile_account_path - click_on 'Manage Two-Factor Authentication' + click_on 'Enable Two-Factor Authentication' register_u2f_device(u2f_device) expect(page.body).to match('Your U2F device was registered') @@ -79,7 +94,7 @@ feature 'Using U2F (Universal 2nd Factor) Devices for Authentication', feature: context "when there are form errors" do it "doesn't register the device if there are errors" do visit profile_account_path - click_on 'Manage Two-Factor Authentication' + click_on 'Enable Two-Factor Authentication' # Have the "u2f device" respond with bad data page.execute_script("u2f.register = function(_,_,_,callback) { callback('bad response'); };") @@ -94,7 +109,7 @@ feature 'Using U2F (Universal 2nd Factor) Devices for Authentication', feature: it "allows retrying registration" do visit profile_account_path - click_on 'Manage Two-Factor Authentication' + click_on 'Enable Two-Factor Authentication' # Failed registration page.execute_script("u2f.register = function(_,_,_,callback) { callback('bad response'); };") @@ -118,9 +133,8 @@ feature 'Using U2F (Universal 2nd Factor) Devices for Authentication', feature: before do # Register and logout login_as(user) - user.update_attribute(:otp_required_for_login, true) visit profile_account_path - click_on 'Manage Two-Factor Authentication' + click_on 'Enable Two-Factor Authentication' @u2f_device = register_u2f_device logout end @@ -140,7 +154,7 @@ feature 'Using U2F (Universal 2nd Factor) Devices for Authentication', feature: describe "when 2FA via OTP is enabled" do it "allows logging in with the U2F device" do - user.update_attribute(:otp_required_for_login, true) + user.update_attributes(otp_required_for_login: true) login_with(user) @u2f_device.respond_to_u2f_authentication @@ -157,9 +171,8 @@ feature 'Using U2F (Universal 2nd Factor) Devices for Authentication', feature: it "does not allow logging in with that particular device" do # Register current user with the different U2F device current_user = login_as(:user) - current_user.update_attribute(:otp_required_for_login, true) visit profile_account_path - click_on 'Manage Two-Factor Authentication' + click_on 'Enable Two-Factor Authentication' register_u2f_device logout @@ -178,9 +191,8 @@ feature 'Using U2F (Universal 2nd Factor) Devices for Authentication', feature: it "allows logging in with that particular device" do # Register current user with the same U2F device current_user = login_as(:user) - current_user.update_attribute(:otp_required_for_login, true) visit profile_account_path - click_on 'Manage Two-Factor Authentication' + click_on 'Enable Two-Factor Authentication' register_u2f_device(@u2f_device) logout @@ -215,9 +227,8 @@ feature 'Using U2F (Universal 2nd Factor) Devices for Authentication', feature: before do login_as(user) - user.update_attribute(:otp_required_for_login, true) visit profile_account_path - click_on 'Manage Two-Factor Authentication' + click_on 'Enable Two-Factor Authentication' register_u2f_device end diff --git a/spec/finders/notes_finder_spec.rb b/spec/finders/notes_finder_spec.rb index 639b28d49ee..c83824b900d 100644 --- a/spec/finders/notes_finder_spec.rb +++ b/spec/finders/notes_finder_spec.rb @@ -34,21 +34,5 @@ describe NotesFinder do notes = NotesFinder.new.execute(project, user, params) expect(notes).to eq([note1]) end - - context 'confidential issue notes' do - let(:confidential_issue) { create(:issue, :confidential, project: project, author: user) } - let!(:confidential_note) { create(:note, noteable: confidential_issue, project: confidential_issue.project) } - - let(:params) { { target_id: confidential_issue.id, target_type: 'issue', last_fetched_at: 1.hour.ago.to_i } } - - it 'returns notes if user can see the issue' do - expect(NotesFinder.new.execute(project, user, params)).to eq([confidential_note]) - end - - it 'raises an error if user can not see the issue' do - user = create(:user) - expect { NotesFinder.new.execute(project, user, params) }.to raise_error(ActiveRecord::RecordNotFound) - end - end end end diff --git a/spec/fixtures/container_registry/tag_manifest_1.json b/spec/fixtures/container_registry/tag_manifest_1.json deleted file mode 100644 index d09ede5bea7..00000000000 --- a/spec/fixtures/container_registry/tag_manifest_1.json +++ /dev/null @@ -1,32 +0,0 @@ -{ - "schemaVersion": 1, - "name": "library/alpine", - "tag": "2.6", - "architecture": "amd64", - "fsLayers": [ - { - "blobSum": "sha256:2a3ebcb7fbcc29bf40c4f62863008bb573acdea963454834d9483b3e5300c45d" - } - ], - "history": [ - { - "v1Compatibility": "{\"id\":\"dd807873c9a21bcc82e30317c283e6601d7e19f5cf7867eec34cdd1aeb3f099e\",\"created\":\"2016-01-18T18:32:39.162138276Z\",\"container\":\"556a728876db7b0e621adc029c87c649d32520804f8f15defd67bb070dc1a88d\",\"container_config\":{\"Hostname\":\"556a728876db\",\"Domainname\":\"\",\"User\":\"\",\"AttachStdin\":false,\"AttachStdout\":false,\"AttachStderr\":false,\"Tty\":false,\"OpenStdin\":false,\"StdinOnce\":false,\"Env\":null,\"Cmd\":[\"/bin/sh\",\"-c\",\"#(nop) ADD file:7dee8a455bcc39013aa168d27ece9227aad155adbaacbd153d94ca60113f59fc in /\"],\"Image\":\"\",\"Volumes\":null,\"WorkingDir\":\"\",\"Entrypoint\":null,\"OnBuild\":null,\"Labels\":null},\"docker_version\":\"1.8.3\",\"config\":{\"Hostname\":\"556a728876db\",\"Domainname\":\"\",\"User\":\"\",\"AttachStdin\":false,\"AttachStdout\":false,\"AttachStderr\":false,\"Tty\":false,\"OpenStdin\":false,\"StdinOnce\":false,\"Env\":null,\"Cmd\":null,\"Image\":\"\",\"Volumes\":null,\"WorkingDir\":\"\",\"Entrypoint\":null,\"OnBuild\":null,\"Labels\":null},\"architecture\":\"amd64\",\"os\":\"linux\",\"Size\":4501436}" - } - ], - "signatures": [ - { - "header": { - "jwk": { - "crv": "P-256", - "kid": "4MZL:Z5ZP:2RPA:Q3TD:QOHA:743L:EM2G:QY6Q:ZJCX:BSD7:CRYC:LQ6T", - "kty": "EC", - "x": "qmWOaxPUk7QsE5iTPdeG1e9yNE-wranvQEnWzz9FhWM", - "y": "WeeBpjTOYnTNrfCIxtFY5qMrJNNk9C1vc5ryxbbMD_M" - }, - "alg": "ES256" - }, - "signature": "0zmjTJ4m21yVwAeteLc3SsQ0miScViCDktFPR67W-ozGjjI3iBjlDjwOl6o2sds5ZI9U6bSIKOeLDinGOhHoOQ", - "protected": "eyJmb3JtYXRMZW5ndGgiOjEzNzIsImZvcm1hdFRhaWwiOiJDbjAiLCJ0aW1lIjoiMjAxNi0wNi0xNVQxMDo0NDoxNFoifQ" - } - ] -} diff --git a/spec/helpers/gitlab_routing_helper_spec.rb b/spec/helpers/gitlab_routing_helper_spec.rb deleted file mode 100644 index 14847d0a49e..00000000000 --- a/spec/helpers/gitlab_routing_helper_spec.rb +++ /dev/null @@ -1,79 +0,0 @@ -require 'spec_helper' - -describe GitlabRoutingHelper do - describe 'Project URL helpers' do - describe '#project_members_url' do - let(:project) { build_stubbed(:empty_project) } - - it { expect(project_members_url(project)).to eq namespace_project_project_members_url(project.namespace, project) } - end - - describe '#project_member_path' do - let(:project_member) { create(:project_member) } - - it { expect(project_member_path(project_member)).to eq namespace_project_project_member_path(project_member.source.namespace, project_member.source, project_member) } - end - - describe '#request_access_project_members_path' do - let(:project) { build_stubbed(:empty_project) } - - it { expect(request_access_project_members_path(project)).to eq request_access_namespace_project_project_members_path(project.namespace, project) } - end - - describe '#leave_project_members_path' do - let(:project) { build_stubbed(:empty_project) } - - it { expect(leave_project_members_path(project)).to eq leave_namespace_project_project_members_path(project.namespace, project) } - end - - describe '#approve_access_request_project_member_path' do - let(:project_member) { create(:project_member) } - - it { expect(approve_access_request_project_member_path(project_member)).to eq approve_access_request_namespace_project_project_member_path(project_member.source.namespace, project_member.source, project_member) } - end - - describe '#resend_invite_project_member_path' do - let(:project_member) { create(:project_member) } - - it { expect(resend_invite_project_member_path(project_member)).to eq resend_invite_namespace_project_project_member_path(project_member.source.namespace, project_member.source, project_member) } - end - end - - describe 'Group URL helpers' do - describe '#group_members_url' do - let(:group) { build_stubbed(:group) } - - it { expect(group_members_url(group)).to eq group_group_members_url(group) } - end - - describe '#group_member_path' do - let(:group_member) { create(:group_member) } - - it { expect(group_member_path(group_member)).to eq group_group_member_path(group_member.source, group_member) } - end - - describe '#request_access_group_members_path' do - let(:group) { build_stubbed(:group) } - - it { expect(request_access_group_members_path(group)).to eq request_access_group_group_members_path(group) } - end - - describe '#leave_group_members_path' do - let(:group) { build_stubbed(:group) } - - it { expect(leave_group_members_path(group)).to eq leave_group_group_members_path(group) } - end - - describe '#approve_access_request_group_member_path' do - let(:group_member) { create(:group_member) } - - it { expect(approve_access_request_group_member_path(group_member)).to eq approve_access_request_group_group_member_path(group_member.source, group_member) } - end - - describe '#resend_invite_group_member_path' do - let(:group_member) { create(:group_member) } - - it { expect(resend_invite_group_member_path(group_member)).to eq resend_invite_group_group_member_path(group_member.source, group_member) } - end - end -end diff --git a/spec/helpers/issues_helper_spec.rb b/spec/helpers/issues_helper_spec.rb index 831ae7fb69c..eae61a54dfc 100644 --- a/spec/helpers/issues_helper_spec.rb +++ b/spec/helpers/issues_helper_spec.rb @@ -7,7 +7,10 @@ describe IssuesHelper do describe "url_for_project_issues" do let(:project_url) { ext_project.external_issue_tracker.project_url } - let(:ext_expected) { project_url.gsub(':project_id', ext_project.id.to_s) } + let(:ext_expected) do + project_url.gsub(':project_id', ext_project.id.to_s) + .gsub(':issues_tracker_id', ext_project.issues_tracker_id.to_s) + end let(:int_expected) { polymorphic_path([@project.namespace, project]) } it "should return internal path if used internal tracker" do @@ -53,7 +56,11 @@ describe IssuesHelper do describe "url_for_issue" do let(:issues_url) { ext_project.external_issue_tracker.issues_url} - let(:ext_expected) { issues_url.gsub(':id', issue.iid.to_s).gsub(':project_id', ext_project.id.to_s) } + let(:ext_expected) do + issues_url.gsub(':id', issue.iid.to_s) + .gsub(':project_id', ext_project.id.to_s) + .gsub(':issues_tracker_id', ext_project.issues_tracker_id.to_s) + end let(:int_expected) { polymorphic_path([@project.namespace, project, issue]) } it "should return internal path if used internal tracker" do @@ -99,7 +106,10 @@ describe IssuesHelper do describe 'url_for_new_issue' do let(:issues_url) { ext_project.external_issue_tracker.new_issue_url } - let(:ext_expected) { issues_url.gsub(':project_id', ext_project.id.to_s) } + let(:ext_expected) do + issues_url.gsub(':project_id', ext_project.id.to_s) + .gsub(':issues_tracker_id', ext_project.issues_tracker_id.to_s) + end let(:int_expected) { new_namespace_project_issue_path(project.namespace, project) } it "should return internal path if used internal tracker" do diff --git a/spec/helpers/members_helper_spec.rb b/spec/helpers/members_helper_spec.rb deleted file mode 100644 index 7998209b7b0..00000000000 --- a/spec/helpers/members_helper_spec.rb +++ /dev/null @@ -1,56 +0,0 @@ -require 'spec_helper' - -describe MembersHelper do - describe '#action_member_permission' do - let(:project_member) { build(:project_member) } - let(:group_member) { build(:group_member) } - - it { expect(action_member_permission(:admin, project_member)).to eq :admin_project_member } - it { expect(action_member_permission(:admin, group_member)).to eq :admin_group_member } - end - - describe '#remove_member_message' do - let(:requester) { build(:user) } - let(:project) { create(:project) } - let(:project_member) { build(:project_member, project: project) } - let(:project_member_invite) { build(:project_member, project: project).tap { |m| m.generate_invite_token! } } - let(:project_member_request) { project.request_access(requester) } - let(:group) { create(:group) } - let(:group_member) { build(:group_member, group: group) } - let(:group_member_invite) { build(:group_member, group: group).tap { |m| m.generate_invite_token! } } - let(:group_member_request) { group.request_access(requester) } - - it { expect(remove_member_message(project_member)).to eq "Are you sure you want to remove #{project_member.user.name} from the #{project.name_with_namespace} project?" } - it { expect(remove_member_message(project_member_invite)).to eq "Are you sure you want to revoke the invitation for #{project_member_invite.invite_email} to join the #{project.name_with_namespace} project?" } - it { expect(remove_member_message(project_member_request)).to eq "Are you sure you want to deny #{requester.name}'s request to join the #{project.name_with_namespace} project?" } - it { expect(remove_member_message(project_member_request, user: requester)).to eq "Are you sure you want to withdraw your access request for the #{project.name_with_namespace} project?" } - it { expect(remove_member_message(group_member)).to eq "Are you sure you want to remove #{group_member.user.name} from the #{group.name} group?" } - it { expect(remove_member_message(group_member_invite)).to eq "Are you sure you want to revoke the invitation for #{group_member_invite.invite_email} to join the #{group.name} group?" } - it { expect(remove_member_message(group_member_request)).to eq "Are you sure you want to deny #{requester.name}'s request to join the #{group.name} group?" } - it { expect(remove_member_message(group_member_request, user: requester)).to eq "Are you sure you want to withdraw your access request for the #{group.name} group?" } - end - - describe '#remove_member_title' do - let(:requester) { build(:user) } - let(:project) { create(:project) } - let(:project_member) { build(:project_member, project: project) } - let(:project_member_request) { project.request_access(requester) } - let(:group) { create(:group) } - let(:group_member) { build(:group_member, group: group) } - let(:group_member_request) { group.request_access(requester) } - - it { expect(remove_member_title(project_member)).to eq 'Remove user from project' } - it { expect(remove_member_title(project_member_request)).to eq 'Deny access request from project' } - it { expect(remove_member_title(group_member)).to eq 'Remove user from group' } - it { expect(remove_member_title(group_member_request)).to eq 'Deny access request from group' } - end - - describe '#leave_confirmation_message' do - let(:project) { build_stubbed(:project) } - let(:group) { build_stubbed(:group) } - let(:user) { build_stubbed(:user) } - - it { expect(leave_confirmation_message(project)).to eq "Are you sure you want to leave the \"#{project.name_with_namespace}\" project?" } - it { expect(leave_confirmation_message(group)).to eq "Are you sure you want to leave the \"#{group.name}\" group?" } - end -end diff --git a/spec/helpers/projects_helper_spec.rb b/spec/helpers/projects_helper_spec.rb index 09e0bbfd00b..ac5af8740dc 100644 --- a/spec/helpers/projects_helper_spec.rb +++ b/spec/helpers/projects_helper_spec.rb @@ -45,6 +45,16 @@ describe ProjectsHelper do end end + describe 'user_max_access_in_project' do + let(:project) { create(:project) } + let(:user) { create(:user) } + before do + project.team.add_user(user, Gitlab::Access::MASTER) + end + + it { expect(helper.user_max_access_in_project(user.id, project)).to eq('Master') } + end + describe "readme_cache_key" do let(:project) { create(:project) } diff --git a/spec/javascripts/application_spec.js.coffee b/spec/javascripts/application_spec.js.coffee deleted file mode 100644 index 8af39c41f2f..00000000000 --- a/spec/javascripts/application_spec.js.coffee +++ /dev/null @@ -1,30 +0,0 @@ -#= require lib/common_utils - -describe 'Application', -> - describe 'disable buttons', -> - fixture.preload('application.html') - - beforeEach -> - fixture.load('application.html') - - it 'should prevent default action for disabled buttons', -> - - gl.utils.preventDisabledButtons() - - isClicked = false - $button = $ '#test-button' - - $button.click -> isClicked = true - $button.trigger 'click' - - expect(isClicked).toBe false - - - it 'should be on the same page if a disabled link clicked', -> - - locationBeforeLinkClick = window.location.href - gl.utils.preventDisabledButtons() - - $('#test-link').click() - - expect(window.location.href).toBe locationBeforeLinkClick diff --git a/spec/javascripts/fixtures/application.html.haml b/spec/javascripts/fixtures/application.html.haml deleted file mode 100644 index 3fc6114407d..00000000000 --- a/spec/javascripts/fixtures/application.html.haml +++ /dev/null @@ -1,2 +0,0 @@ -%a#test-link.btn.disabled{:href => "/foo"} Test link -%button#test-button.btn.disabled Test Button diff --git a/spec/javascripts/fixtures/search_autocomplete.html.haml b/spec/javascripts/fixtures/search_autocomplete.html.haml deleted file mode 100644 index 7785120da5b..00000000000 --- a/spec/javascripts/fixtures/search_autocomplete.html.haml +++ /dev/null @@ -1,10 +0,0 @@ -.search.search-form.has-location-badge - %form.navbar-form - .search-input-container - %div.location-badge - This project - .search-input-wrap - .dropdown - %input#search.search-input.dropdown-menu-toggle - .dropdown-menu.dropdown-select - .dropdown-content diff --git a/spec/javascripts/fixtures/u2f/register.html.haml b/spec/javascripts/fixtures/u2f/register.html.haml index 5ed51be689c..393c0613fd3 100644 --- a/spec/javascripts/fixtures/u2f/register.html.haml +++ b/spec/javascripts/fixtures/u2f/register.html.haml @@ -1,2 +1 @@ -- user = FactoryGirl.build(:user, :two_factor_via_otp) -= render partial: "u2f/register", locals: { create_u2f_profile_two_factor_auth_path: '/profile/two_factor_auth/create_u2f', current_user: user } += render partial: "u2f/register", locals: { create_u2f_profile_two_factor_auth_path: '/profile/two_factor_auth/create_u2f' } diff --git a/spec/javascripts/notes_spec.js.coffee b/spec/javascripts/notes_spec.js.coffee index 3a3c8d63e82..dd160e821b3 100644 --- a/spec/javascripts/notes_spec.js.coffee +++ b/spec/javascripts/notes_spec.js.coffee @@ -1,7 +1,7 @@ #= require notes #= require gl_form -window.gon or= {} +window.gon = {} window.disableButtonIfEmptyField = -> null describe 'Notes', -> diff --git a/spec/javascripts/project_title_spec.js.coffee b/spec/javascripts/project_title_spec.js.coffee index 9be29097f4c..1cf34d4d2d3 100644 --- a/spec/javascripts/project_title_spec.js.coffee +++ b/spec/javascripts/project_title_spec.js.coffee @@ -6,7 +6,7 @@ #= require project_select #= require project -window.gon or= {} +window.gon = {} window.gon.api_version = 'v3' describe 'Project Title', -> diff --git a/spec/javascripts/search_autocomplete_spec.js.coffee b/spec/javascripts/search_autocomplete_spec.js.coffee deleted file mode 100644 index e77177783a7..00000000000 --- a/spec/javascripts/search_autocomplete_spec.js.coffee +++ /dev/null @@ -1,149 +0,0 @@ -#= require gl_dropdown -#= require search_autocomplete -#= require jquery -#= require lib/common_utils -#= require lib/type_utility -#= require fuzzaldrin-plus - - -widget = null -userId = 1 -window.gon or= {} -window.gon.current_user_id = userId - -dashboardIssuesPath = '/dashboard/issues' -dashboardMRsPath = '/dashboard/merge_requests' -projectIssuesPath = '/gitlab-org/gitlab-ce/issues' -projectMRsPath = '/gitlab-org/gitlab-ce/merge_requests' -groupIssuesPath = '/groups/gitlab-org/issues' -groupMRsPath = '/groups/gitlab-org/merge_requests' -projectName = 'GitLab Community Edition' -groupName = 'Gitlab Org' - - -# Add required attributes to body before starting the test. -# section would be dashboard|group|project -addBodyAttributes = (section = 'dashboard') -> - - $body = $ 'body' - - $body.removeAttr 'data-page' - $body.removeAttr 'data-project' - $body.removeAttr 'data-group' - - switch section - when 'dashboard' - $body.data 'page', 'root:index' - when 'group' - $body.data 'page', 'groups:show' - $body.data 'group', 'gitlab-org' - when 'project' - $body.data 'page', 'projects:show' - $body.data 'project', 'gitlab-ce' - - -# Mock `gl` object in window for dashboard specific page. App code will need it. -mockDashboardOptions = -> - - window.gl or= {} - window.gl.dashboardOptions = - issuesPath: dashboardIssuesPath - mrPath : dashboardMRsPath - - -# Mock `gl` object in window for project specific page. App code will need it. -mockProjectOptions = -> - - window.gl or= {} - window.gl.projectOptions = - 'gitlab-ce' : - issuesPath : projectIssuesPath - mrPath : projectMRsPath - projectName : projectName - - -mockGroupOptions = -> - - window.gl or= {} - window.gl.groupOptions = - 'gitlab-org' : - issuesPath : groupIssuesPath - mrPath : groupMRsPath - projectName : groupName - - -assertLinks = (list, issuesPath, mrsPath) -> - - issuesAssignedToMeLink = "#{issuesPath}/?assignee_id=#{userId}" - issuesIHaveCreatedLink = "#{issuesPath}/?author_id=#{userId}" - mrsAssignedToMeLink = "#{mrsPath}/?assignee_id=#{userId}" - mrsIHaveCreatedLink = "#{mrsPath}/?author_id=#{userId}" - - a1 = "a[href='#{issuesAssignedToMeLink}']" - a2 = "a[href='#{issuesIHaveCreatedLink}']" - a3 = "a[href='#{mrsAssignedToMeLink}']" - a4 = "a[href='#{mrsIHaveCreatedLink}']" - - expect(list.find(a1).length).toBe 1 - expect(list.find(a1).text()).toBe ' Issues assigned to me ' - - expect(list.find(a2).length).toBe 1 - expect(list.find(a2).text()).toBe " Issues I've created " - - expect(list.find(a3).length).toBe 1 - expect(list.find(a3).text()).toBe ' Merge requests assigned to me ' - - expect(list.find(a4).length).toBe 1 - expect(list.find(a4).text()).toBe " Merge requests I've created " - - -describe 'Search autocomplete dropdown', -> - - fixture.preload 'search_autocomplete.html' - - beforeEach -> - - fixture.load 'search_autocomplete.html' - widget = new SearchAutocomplete - - - it 'should show Dashboard specific dropdown menu', -> - - addBodyAttributes() - mockDashboardOptions() - widget.searchInput.focus() - - list = widget.wrap.find('.dropdown-menu').find 'ul' - assertLinks list, dashboardIssuesPath, dashboardMRsPath - - - it 'should show Group specific dropdown menu', -> - - addBodyAttributes 'group' - mockGroupOptions() - widget.searchInput.focus() - - list = widget.wrap.find('.dropdown-menu').find 'ul' - assertLinks list, groupIssuesPath, groupMRsPath - - - it 'should show Project specific dropdown menu', -> - - addBodyAttributes 'project' - mockProjectOptions() - widget.searchInput.focus() - - list = widget.wrap.find('.dropdown-menu').find 'ul' - assertLinks list, projectIssuesPath, projectMRsPath - - - it 'should not show category related menu if there is text in the input', -> - - addBodyAttributes 'project' - mockProjectOptions() - widget.searchInput.val 'help' - widget.searchInput.focus() - - list = widget.wrap.find('.dropdown-menu').find 'ul' - link = "a[href='#{projectIssuesPath}/?assignee_id=#{userId}']" - expect(list.find(link).length).toBe 0 diff --git a/spec/lib/banzai/filter/redactor_filter_spec.rb b/spec/lib/banzai/filter/redactor_filter_spec.rb index f181125156b..697d10bbf70 100644 --- a/spec/lib/banzai/filter/redactor_filter_spec.rb +++ b/spec/lib/banzai/filter/redactor_filter_spec.rb @@ -69,18 +69,6 @@ describe Banzai::Filter::RedactorFilter, lib: true do expect(doc.css('a').length).to eq 0 end - it 'removes references for project members with guest role' do - member = create(:user) - project = create(:empty_project, :public) - project.team << [member, :guest] - issue = create(:issue, :confidential, project: project) - - link = reference_link(project: project.id, issue: issue.id, reference_type: 'issue') - doc = filter(link, current_user: member) - - expect(doc.css('a').length).to eq 0 - end - it 'allows references for author' do author = create(:user) project = create(:empty_project, :public) diff --git a/spec/lib/ci/gitlab_ci_yaml_processor_spec.rb b/spec/lib/ci/gitlab_ci_yaml_processor_spec.rb index 143e2e6d238..7375539cf17 100644 --- a/spec/lib/ci/gitlab_ci_yaml_processor_spec.rb +++ b/spec/lib/ci/gitlab_ci_yaml_processor_spec.rb @@ -26,8 +26,7 @@ module Ci tag_list: [], options: {}, allow_failure: false, - when: "on_success", - environment: nil, + when: "on_success" }) end @@ -388,8 +387,7 @@ module Ci services: ["mysql"] }, allow_failure: false, - when: "on_success", - environment: nil, + when: "on_success" }) end @@ -417,8 +415,7 @@ module Ci services: ["postgresql"] }, allow_failure: false, - when: "on_success", - environment: nil, + when: "on_success" }) end end @@ -504,7 +501,6 @@ module Ci }) config_processor = GitlabCiYamlProcessor.new(config, path) - builds = config_processor.builds_for_stage_and_ref("test", "master") expect(builds.size).to eq(1) expect(builds.first[:when]).to eq(when_state) @@ -576,12 +572,7 @@ module Ci services: ["mysql"], before_script: ["pwd"], rspec: { - artifacts: { - paths: ["logs/", "binaries/"], - untracked: true, - name: "custom_name", - expire_in: "7d" - }, + artifacts: { paths: ["logs/", "binaries/"], untracked: true, name: "custom_name" }, script: "rspec" } }) @@ -603,77 +594,13 @@ module Ci artifacts: { name: "custom_name", paths: ["logs/", "binaries/"], - untracked: true, - expire_in: "7d" + untracked: true } }, when: "on_success", - allow_failure: false, - environment: nil, + allow_failure: false }) end - - %w[on_success on_failure always].each do |when_state| - it "returns artifacts for when #{when_state} defined" do - config = YAML.dump({ - rspec: { - script: "rspec", - artifacts: { paths: ["logs/", "binaries/"], when: when_state } - } - }) - - config_processor = GitlabCiYamlProcessor.new(config, path) - - builds = config_processor.builds_for_stage_and_ref("test", "master") - expect(builds.size).to eq(1) - expect(builds.first[:options][:artifacts][:when]).to eq(when_state) - end - end - end - - describe '#environment' do - let(:config) do - { - deploy_to_production: { stage: 'deploy', script: 'test', environment: environment } - } - end - - let(:processor) { GitlabCiYamlProcessor.new(YAML.dump(config)) } - let(:builds) { processor.builds_for_stage_and_ref('deploy', 'master') } - - context 'when a production environment is specified' do - let(:environment) { 'production' } - - it 'does return production' do - expect(builds.size).to eq(1) - expect(builds.first[:environment]).to eq(environment) - end - end - - context 'when no environment is specified' do - let(:environment) { nil } - - it 'does return nil environment' do - expect(builds.size).to eq(1) - expect(builds.first[:environment]).to be_nil - end - end - - context 'is not a string' do - let(:environment) { 1 } - - it 'raises error' do - expect { builds }.to raise_error("deploy_to_production job: environment parameter #{Gitlab::Regex.environment_name_regex_message}") - end - end - - context 'is not a valid string' do - let(:environment) { 'production staging' } - - it 'raises error' do - expect { builds }.to raise_error("deploy_to_production job: environment parameter #{Gitlab::Regex.environment_name_regex_message}") - end - end end describe "Dependencies" do @@ -737,8 +664,7 @@ module Ci tag_list: [], options: {}, when: "on_success", - allow_failure: false, - environment: nil, + allow_failure: false }) end end @@ -783,8 +709,7 @@ module Ci tag_list: [], options: {}, when: "on_success", - allow_failure: false, - environment: nil, + allow_failure: false }) expect(subject.second).to eq({ except: nil, @@ -796,8 +721,7 @@ module Ci tag_list: [], options: {}, when: "on_success", - allow_failure: false, - environment: nil, + allow_failure: false }) end end @@ -1043,27 +967,6 @@ EOT end.to raise_error(GitlabCiYamlProcessor::ValidationError, "rspec job: artifacts:name parameter should be a string") end - it "returns errors if job artifacts:when is not an a predefined value" do - config = YAML.dump({ types: ["build", "test"], rspec: { script: "test", artifacts: { when: 1 } } }) - expect do - GitlabCiYamlProcessor.new(config) - end.to raise_error(GitlabCiYamlProcessor::ValidationError, "rspec job: artifacts:when parameter should be on_success, on_failure or always") - end - - it "returns errors if job artifacts:expire_in is not an a string" do - config = YAML.dump({ types: ["build", "test"], rspec: { script: "test", artifacts: { expire_in: 1 } } }) - expect do - GitlabCiYamlProcessor.new(config) - end.to raise_error(GitlabCiYamlProcessor::ValidationError, "rspec job: artifacts:expire_in parameter should be a duration") - end - - it "returns errors if job artifacts:expire_in is not an a valid duration" do - config = YAML.dump({ types: ["build", "test"], rspec: { script: "test", artifacts: { expire_in: "7 elephants" } } }) - expect do - GitlabCiYamlProcessor.new(config) - end.to raise_error(GitlabCiYamlProcessor::ValidationError, "rspec job: artifacts:expire_in parameter should be a duration") - end - it "returns errors if job artifacts:untracked is not an array of strings" do config = YAML.dump({ types: ["build", "test"], rspec: { script: "test", artifacts: { untracked: "string" } } }) expect do diff --git a/spec/lib/container_registry/tag_spec.rb b/spec/lib/container_registry/tag_spec.rb index c7324c2bf77..858cb0bb134 100644 --- a/spec/lib/container_registry/tag_spec.rb +++ b/spec/lib/container_registry/tag_spec.rb @@ -17,85 +17,46 @@ describe ContainerRegistry::Tag do end context 'manifest processing' do - context 'schema v1' do - before do - stub_request(:get, 'http://example.com/v2/group/test/manifests/tag'). - with(headers: headers). - to_return( - status: 200, - body: File.read(Rails.root + 'spec/fixtures/container_registry/tag_manifest_1.json'), - headers: { 'Content-Type' => 'application/vnd.docker.distribution.manifest.v1+prettyjws' }) - end - - context '#layers' do - subject { tag.layers } - - it { expect(subject.length).to eq(1) } - end - - context '#total_size' do - subject { tag.total_size } - - it { is_expected.to be_nil } - end + before do + stub_request(:get, 'http://example.com/v2/group/test/manifests/tag'). + with(headers: headers). + to_return( + status: 200, + body: File.read(Rails.root + 'spec/fixtures/container_registry/tag_manifest.json'), + headers: { 'Content-Type' => 'application/vnd.docker.distribution.manifest.v2+json' }) + end - context 'config processing' do - context '#config' do - subject { tag.config } + context '#layers' do + subject { tag.layers } - it { is_expected.to be_nil } - end + it { expect(subject.length).to eq(1) } + end - context '#created_at' do - subject { tag.created_at } + context '#total_size' do + subject { tag.total_size } - it { is_expected.to be_nil } - end - end + it { is_expected.to eq(2319870) } end - context 'schema v2' do + context 'config processing' do before do - stub_request(:get, 'http://example.com/v2/group/test/manifests/tag'). - with(headers: headers). + stub_request(:get, 'http://example.com/v2/group/test/blobs/sha256:d7a513a663c1a6dcdba9ed832ca53c02ac2af0c333322cd6ca92936d1d9917ac'). + with(headers: { 'Accept' => 'application/octet-stream' }). to_return( status: 200, - body: File.read(Rails.root + 'spec/fixtures/container_registry/tag_manifest.json'), - headers: { 'Content-Type' => 'application/vnd.docker.distribution.manifest.v2+json' }) + body: File.read(Rails.root + 'spec/fixtures/container_registry/config_blob.json')) end - context '#layers' do - subject { tag.layers } + context '#config' do + subject { tag.config } - it { expect(subject.length).to eq(1) } + it { is_expected.not_to be_nil } end - context '#total_size' do - subject { tag.total_size } - - it { is_expected.to eq(2319870) } - end - - context 'config processing' do - before do - stub_request(:get, 'http://example.com/v2/group/test/blobs/sha256:d7a513a663c1a6dcdba9ed832ca53c02ac2af0c333322cd6ca92936d1d9917ac'). - with(headers: { 'Accept' => 'application/octet-stream' }). - to_return( - status: 200, - body: File.read(Rails.root + 'spec/fixtures/container_registry/config_blob.json')) - end - - context '#config' do - subject { tag.config } - - it { is_expected.not_to be_nil } - end - - context '#created_at' do - subject { tag.created_at } + context '#created_at' do + subject { tag.created_at } - it { is_expected.not_to be_nil } - end + it { is_expected.not_to be_nil } end end end diff --git a/spec/lib/gitlab/auth_spec.rb b/spec/lib/gitlab/auth_spec.rb index 7bec1367156..a814ad2a4e7 100644 --- a/spec/lib/gitlab/auth_spec.rb +++ b/spec/lib/gitlab/auth_spec.rb @@ -3,7 +3,7 @@ require 'spec_helper' describe Gitlab::Auth, lib: true do let(:gl_auth) { described_class } - describe 'find_for_git_client' do + describe 'find' do it 'recognizes CI' do token = '123' project = create(:empty_project) @@ -11,7 +11,7 @@ describe Gitlab::Auth, lib: true do ip = 'ip' expect(gl_auth).to receive(:rate_limit!).with(ip, success: true, login: 'gitlab-ci-token') - expect(gl_auth.find_for_git_client('gitlab-ci-token', token, project: project, ip: ip)).to eq(Gitlab::Auth::Result.new(nil, :ci)) + expect(gl_auth.find('gitlab-ci-token', token, project: project, ip: ip)).to eq(Gitlab::Auth::Result.new(nil, :ci)) end it 'recognizes master passwords' do @@ -19,7 +19,7 @@ describe Gitlab::Auth, lib: true do ip = 'ip' expect(gl_auth).to receive(:rate_limit!).with(ip, success: true, login: user.username) - expect(gl_auth.find_for_git_client(user.username, 'password', project: nil, ip: ip)).to eq(Gitlab::Auth::Result.new(user, :gitlab_or_ldap)) + expect(gl_auth.find(user.username, 'password', project: nil, ip: ip)).to eq(Gitlab::Auth::Result.new(user, :gitlab_or_ldap)) end it 'recognizes OAuth tokens' do @@ -29,7 +29,7 @@ describe Gitlab::Auth, lib: true do ip = 'ip' expect(gl_auth).to receive(:rate_limit!).with(ip, success: true, login: 'oauth2') - expect(gl_auth.find_for_git_client("oauth2", token.token, project: nil, ip: ip)).to eq(Gitlab::Auth::Result.new(user, :oauth)) + expect(gl_auth.find("oauth2", token.token, project: nil, ip: ip)).to eq(Gitlab::Auth::Result.new(user, :oauth)) end it 'returns double nil for invalid credentials' do @@ -37,11 +37,11 @@ describe Gitlab::Auth, lib: true do ip = 'ip' expect(gl_auth).to receive(:rate_limit!).with(ip, success: false, login: login) - expect(gl_auth.find_for_git_client(login, 'bar', project: nil, ip: ip)).to eq(Gitlab::Auth::Result.new) + expect(gl_auth.find(login, 'bar', project: nil, ip: ip)).to eq(Gitlab::Auth::Result.new) end end - describe 'find_with_user_password' do + describe 'find_in_gitlab_or_ldap' do let!(:user) do create(:user, username: username, @@ -52,25 +52,25 @@ describe Gitlab::Auth, lib: true do let(:password) { 'my-secret' } it "should find user by valid login/password" do - expect( gl_auth.find_with_user_password(username, password) ).to eql user + expect( gl_auth.find_in_gitlab_or_ldap(username, password) ).to eql user end it 'should find user by valid email/password with case-insensitive email' do - expect(gl_auth.find_with_user_password(user.email.upcase, password)).to eql user + expect(gl_auth.find_in_gitlab_or_ldap(user.email.upcase, password)).to eql user end it 'should find user by valid username/password with case-insensitive username' do - expect(gl_auth.find_with_user_password(username.upcase, password)).to eql user + expect(gl_auth.find_in_gitlab_or_ldap(username.upcase, password)).to eql user end it "should not find user with invalid password" do password = 'wrong' - expect( gl_auth.find_with_user_password(username, password) ).not_to eql user + expect( gl_auth.find_in_gitlab_or_ldap(username, password) ).not_to eql user end it "should not find user with invalid login" do user = 'wrong' - expect( gl_auth.find_with_user_password(username, password) ).not_to eql user + expect( gl_auth.find_in_gitlab_or_ldap(username, password) ).not_to eql user end context "with ldap enabled" do @@ -81,13 +81,13 @@ describe Gitlab::Auth, lib: true do it "tries to autheticate with db before ldap" do expect(Gitlab::LDAP::Authentication).not_to receive(:login) - gl_auth.find_with_user_password(username, password) + gl_auth.find_in_gitlab_or_ldap(username, password) end it "uses ldap as fallback to for authentication" do expect(Gitlab::LDAP::Authentication).to receive(:login) - gl_auth.find_with_user_password('ldap_user', 'password') + gl_auth.find_in_gitlab_or_ldap('ldap_user', 'password') end end end diff --git a/spec/lib/gitlab/ci/config/node/configurable_spec.rb b/spec/lib/gitlab/ci/config/node/configurable_spec.rb deleted file mode 100644 index 47c68f96dc8..00000000000 --- a/spec/lib/gitlab/ci/config/node/configurable_spec.rb +++ /dev/null @@ -1,35 +0,0 @@ -require 'spec_helper' - -describe Gitlab::Ci::Config::Node::Configurable do - let(:node) { Class.new } - - before do - node.include(described_class) - end - - describe 'allowed nodes' do - before do - node.class_eval do - allow_node :object, Object, description: 'test object' - end - end - - describe '#allowed_nodes' do - it 'has valid allowed nodes' do - expect(node.allowed_nodes).to include :object - end - - it 'creates a node factory' do - expect(node.allowed_nodes[:object]) - .to be_an_instance_of Gitlab::Ci::Config::Node::Factory - end - - it 'returns a duplicated factory object' do - first_factory = node.allowed_nodes[:object] - second_factory = node.allowed_nodes[:object] - - expect(first_factory).not_to be_equal(second_factory) - end - end - end -end diff --git a/spec/lib/gitlab/ci/config/node/factory_spec.rb b/spec/lib/gitlab/ci/config/node/factory_spec.rb deleted file mode 100644 index d681aa32456..00000000000 --- a/spec/lib/gitlab/ci/config/node/factory_spec.rb +++ /dev/null @@ -1,49 +0,0 @@ -require 'spec_helper' - -describe Gitlab::Ci::Config::Node::Factory do - describe '#create!' do - let(:factory) { described_class.new(entry_class) } - let(:entry_class) { Gitlab::Ci::Config::Node::Script } - - context 'when value setting value' do - it 'creates entry with valid value' do - entry = factory - .with(value: ['ls', 'pwd']) - .create! - - expect(entry.value).to eq "ls\npwd" - end - - context 'when setting description' do - it 'creates entry with description' do - entry = factory - .with(value: ['ls', 'pwd']) - .with(description: 'test description') - .create! - - expect(entry.value).to eq "ls\npwd" - expect(entry.description).to eq 'test description' - end - end - end - - context 'when not setting value' do - it 'raises error' do - expect { factory.create! }.to raise_error( - Gitlab::Ci::Config::Node::Factory::InvalidFactory - ) - end - end - - context 'when creating a null entry' do - it 'creates a null entry' do - entry = factory - .with(value: nil) - .nullify! - .create! - - expect(entry).to be_an_instance_of Gitlab::Ci::Config::Node::Null - end - end - end -end diff --git a/spec/lib/gitlab/ci/config/node/global_spec.rb b/spec/lib/gitlab/ci/config/node/global_spec.rb deleted file mode 100644 index b1972172435..00000000000 --- a/spec/lib/gitlab/ci/config/node/global_spec.rb +++ /dev/null @@ -1,104 +0,0 @@ -require 'spec_helper' - -describe Gitlab::Ci::Config::Node::Global do - let(:global) { described_class.new(hash) } - - describe '#allowed_nodes' do - it 'can contain global config keys' do - expect(global.allowed_nodes).to include :before_script - end - - it 'returns a hash' do - expect(global.allowed_nodes).to be_a Hash - end - end - - context 'when hash is valid' do - let(:hash) do - { before_script: ['ls', 'pwd'] } - end - - describe '#process!' do - before { global.process! } - - it 'creates nodes hash' do - expect(global.nodes).to be_an Array - end - - it 'creates node object for each entry' do - expect(global.nodes.count).to eq 1 - end - - it 'creates node object using valid class' do - expect(global.nodes.first) - .to be_an_instance_of Gitlab::Ci::Config::Node::Script - end - - it 'sets correct description for nodes' do - expect(global.nodes.first.description) - .to eq 'Script that will be executed before each job.' - end - end - - describe '#leaf?' do - it 'is not leaf' do - expect(global).not_to be_leaf - end - end - - describe '#before_script' do - context 'when processed' do - before { global.process! } - - it 'returns correct script' do - expect(global.before_script).to eq "ls\npwd" - end - end - - context 'when not processed' do - it 'returns nil' do - expect(global.before_script).to be nil - end - end - end - end - - context 'when hash is not valid' do - before { global.process! } - - let(:hash) do - { before_script: 'ls' } - end - - describe '#valid?' do - it 'is not valid' do - expect(global).not_to be_valid - end - end - - describe '#errors' do - it 'reports errors from child nodes' do - expect(global.errors) - .to include 'before_script should be an array of strings' - end - end - - describe '#before_script' do - it 'raises error' do - expect { global.before_script }.to raise_error( - Gitlab::Ci::Config::Node::Entry::InvalidError - ) - end - end - end - - context 'when value is not a hash' do - let(:hash) { [] } - - describe '#valid?' do - it 'is not valid' do - expect(global).not_to be_valid - end - end - end -end diff --git a/spec/lib/gitlab/ci/config/node/null_spec.rb b/spec/lib/gitlab/ci/config/node/null_spec.rb deleted file mode 100644 index 36101c62462..00000000000 --- a/spec/lib/gitlab/ci/config/node/null_spec.rb +++ /dev/null @@ -1,23 +0,0 @@ -require 'spec_helper' - -describe Gitlab::Ci::Config::Node::Null do - let(:entry) { described_class.new(nil) } - - describe '#leaf?' do - it 'is leaf node' do - expect(entry).to be_leaf - end - end - - describe '#any_method' do - it 'responds with nil' do - expect(entry.any_method).to be nil - end - end - - describe '#value' do - it 'returns nil' do - expect(entry.value).to be nil - end - end -end diff --git a/spec/lib/gitlab/ci/config/node/script_spec.rb b/spec/lib/gitlab/ci/config/node/script_spec.rb deleted file mode 100644 index e4d6481f8a5..00000000000 --- a/spec/lib/gitlab/ci/config/node/script_spec.rb +++ /dev/null @@ -1,48 +0,0 @@ -require 'spec_helper' - -describe Gitlab::Ci::Config::Node::Script do - let(:entry) { described_class.new(value) } - - describe '#validate!' do - before { entry.validate! } - - context 'when entry value is correct' do - let(:value) { ['ls', 'pwd'] } - - describe '#value' do - it 'returns concatenated command' do - expect(entry.value).to eq "ls\npwd" - end - end - - describe '#errors' do - it 'does not append errors' do - expect(entry.errors).to be_empty - end - end - - describe '#valid?' do - it 'is valid' do - expect(entry).to be_valid - end - end - end - - context 'when entry value is not correct' do - let(:value) { 'ls' } - - describe '#errors' do - it 'saves errors' do - expect(entry.errors) - .to include /should be an array of strings/ - end - end - - describe '#valid?' do - it 'is not valid' do - expect(entry).not_to be_valid - end - end - end - end -end diff --git a/spec/lib/gitlab/ci/config_spec.rb b/spec/lib/gitlab/ci/config_spec.rb index 3871d939feb..4d46abe520f 100644 --- a/spec/lib/gitlab/ci/config_spec.rb +++ b/spec/lib/gitlab/ci/config_spec.rb @@ -29,43 +29,17 @@ describe Gitlab::Ci::Config do expect(config.to_hash).to eq hash end - - describe '#valid?' do - it 'is valid' do - expect(config).to be_valid - end - - it 'has no errors' do - expect(config.errors).to be_empty - end - end end context 'when config is invalid' do - context 'when yml is incorrect' do - let(:yml) { '// invalid' } - - describe '.new' do - it 'raises error' do - expect { config }.to raise_error( - Gitlab::Ci::Config::Loader::FormatError, - /Invalid configuration format/ - ) - end - end - end - - context 'when config logic is incorrect' do - let(:yml) { 'before_script: "ls"' } - - describe '#valid?' do - it 'is not valid' do - expect(config).not_to be_valid - end - - it 'has errors' do - expect(config.errors).not_to be_empty - end + let(:yml) { '// invalid' } + + describe '.new' do + it 'raises error' do + expect { config }.to raise_error( + Gitlab::Ci::Config::Loader::FormatError, + /Invalid configuration format/ + ) end end end diff --git a/spec/lib/gitlab/database/migration_helpers_spec.rb b/spec/lib/gitlab/database/migration_helpers_spec.rb index 1ec539066a7..83ddabe6b0b 100644 --- a/spec/lib/gitlab/database/migration_helpers_spec.rb +++ b/spec/lib/gitlab/database/migration_helpers_spec.rb @@ -120,19 +120,6 @@ describe Gitlab::Database::MigrationHelpers, lib: true do model.add_column_with_default(:projects, :foo, :integer, default: 10) end.to raise_error(RuntimeError) end - - it 'removes the added column whenever changing a column NULL constraint fails' do - expect(model).to receive(:change_column_null). - with(:projects, :foo, false). - and_raise(RuntimeError) - - expect(model).to receive(:remove_column). - with(:projects, :foo) - - expect do - model.add_column_with_default(:projects, :foo, :integer, default: 10) - end.to raise_error(RuntimeError) - end end context 'inside a transaction' do diff --git a/spec/lib/gitlab/metrics/instrumentation_spec.rb b/spec/lib/gitlab/metrics/instrumentation_spec.rb index cdf641341cb..220e86924a2 100644 --- a/spec/lib/gitlab/metrics/instrumentation_spec.rb +++ b/spec/lib/gitlab/metrics/instrumentation_spec.rb @@ -9,31 +9,9 @@ describe Gitlab::Metrics::Instrumentation do text end - class << self - def buzz(text = 'buzz') - text - end - private :buzz - - def flaky(text = 'flaky') - text - end - protected :flaky - end - def bar(text = 'bar') text end - - def wadus(text = 'wadus') - text - end - private :wadus - - def chaf(text = 'chaf') - text - end - protected :chaf end allow(@dummy).to receive(:name).and_return('Dummy') @@ -79,7 +57,7 @@ describe Gitlab::Metrics::Instrumentation do and_return(transaction) expect(transaction).to receive(:add_metric). - with(described_class::SERIES, hash_including(:duration, :cpu_duration), + with(described_class::SERIES, an_instance_of(Hash), method: 'Dummy.foo') @dummy.foo @@ -159,7 +137,7 @@ describe Gitlab::Metrics::Instrumentation do and_return(transaction) expect(transaction).to receive(:add_metric). - with(described_class::SERIES, hash_including(:duration, :cpu_duration), + with(described_class::SERIES, an_instance_of(Hash), method: 'Dummy#bar') @dummy.new.bar @@ -230,21 +208,6 @@ describe Gitlab::Metrics::Instrumentation do described_class.instrument_methods(@dummy) expect(described_class.instrumented?(@dummy.singleton_class)).to eq(true) - expect(@dummy.method(:foo).source_location.first).to match(/instrumentation\.rb/) - end - - it 'instruments all protected class methods' do - described_class.instrument_methods(@dummy) - - expect(described_class.instrumented?(@dummy.singleton_class)).to eq(true) - expect(@dummy.method(:flaky).source_location.first).to match(/instrumentation\.rb/) - end - - it 'instruments all private instance methods' do - described_class.instrument_methods(@dummy) - - expect(described_class.instrumented?(@dummy.singleton_class)).to eq(true) - expect(@dummy.method(:buzz).source_location.first).to match(/instrumentation\.rb/) end it 'only instruments methods directly defined in the module' do @@ -278,21 +241,6 @@ describe Gitlab::Metrics::Instrumentation do described_class.instrument_instance_methods(@dummy) expect(described_class.instrumented?(@dummy)).to eq(true) - expect(@dummy.new.method(:bar).source_location.first).to match(/instrumentation\.rb/) - end - - it 'instruments all protected instance methods' do - described_class.instrument_instance_methods(@dummy) - - expect(described_class.instrumented?(@dummy)).to eq(true) - expect(@dummy.new.method(:chaf).source_location.first).to match(/instrumentation\.rb/) - end - - it 'instruments all private instance methods' do - described_class.instrument_instance_methods(@dummy) - - expect(described_class.instrumented?(@dummy)).to eq(true) - expect(@dummy.new.method(:wadus).source_location.first).to match(/instrumentation\.rb/) end it 'only instruments methods directly defined in the module' do @@ -305,7 +253,7 @@ describe Gitlab::Metrics::Instrumentation do described_class.instrument_instance_methods(@dummy) - expect(@dummy.new.method(:kittens).source_location.first).not_to match(/instrumentation\.rb/) + expect(@dummy.method_defined?(:_original_kittens)).to eq(false) end it 'can take a block to determine if a method should be instrumented' do @@ -313,7 +261,7 @@ describe Gitlab::Metrics::Instrumentation do false end - expect(@dummy.new.method(:bar).source_location.first).not_to match(/instrumentation\.rb/) + expect(@dummy.method_defined?(:_original_bar)).to eq(false) end end end diff --git a/spec/lib/gitlab/metrics/rack_middleware_spec.rb b/spec/lib/gitlab/metrics/rack_middleware_spec.rb index 40289f8b972..b99be4e1060 100644 --- a/spec/lib/gitlab/metrics/rack_middleware_spec.rb +++ b/spec/lib/gitlab/metrics/rack_middleware_spec.rb @@ -31,20 +31,6 @@ describe Gitlab::Metrics::RackMiddleware do middleware.call(env) end - - it 'tags a transaction with the method andpath of the route in the grape endpoint' do - route = double(:route, route_method: "GET", route_path: "/:version/projects/:id/archive(.:format)") - endpoint = double(:endpoint, route: route) - - env['api.endpoint'] = endpoint - - allow(app).to receive(:call).with(env) - - expect(middleware).to receive(:tag_endpoint). - with(an_instance_of(Gitlab::Metrics::Transaction), env) - - middleware.call(env) - end end describe '#transaction_from_env' do @@ -74,19 +60,4 @@ describe Gitlab::Metrics::RackMiddleware do expect(transaction.action).to eq('TestController#show') end end - - describe '#tag_endpoint' do - let(:transaction) { middleware.transaction_from_env(env) } - - it 'tags a transaction with the method and path of the route in the grape endpount' do - route = double(:route, route_method: "GET", route_path: "/:version/projects/:id/archive(.:format)") - endpoint = double(:endpoint, route: route) - - env['api.endpoint'] = endpoint - - middleware.tag_endpoint(transaction, env) - - expect(transaction.action).to eq('Grape#GET /projects/:id/archive') - end - end end diff --git a/spec/lib/gitlab/metrics/sampler_spec.rb b/spec/lib/gitlab/metrics/sampler_spec.rb index 1ab923b58cf..59db127674a 100644 --- a/spec/lib/gitlab/metrics/sampler_spec.rb +++ b/spec/lib/gitlab/metrics/sampler_spec.rb @@ -72,25 +72,14 @@ describe Gitlab::Metrics::Sampler do end end - if Gitlab::Metrics.mri? - describe '#sample_objects' do - it 'adds a metric containing the amount of allocated objects' do - expect(sampler).to receive(:add_metric). - with(/object_counts/, an_instance_of(Hash), an_instance_of(Hash)). - at_least(:once). - and_call_original - - sampler.sample_objects - end - - it 'ignores classes without a name' do - expect(Allocations).to receive(:to_hash).and_return({ Class.new => 4 }) - - expect(sampler).not_to receive(:add_metric). - with('object_counts', an_instance_of(Hash), type: nil) + describe '#sample_objects' do + it 'adds a metric containing the amount of allocated objects' do + expect(sampler).to receive(:add_metric). + with(/object_counts/, an_instance_of(Hash), an_instance_of(Hash)). + at_least(:once). + and_call_original - sampler.sample_objects - end + sampler.sample_objects end end diff --git a/spec/lib/gitlab/project_search_results_spec.rb b/spec/lib/gitlab/project_search_results_spec.rb index 270b89972d7..db0ff95b4f5 100644 --- a/spec/lib/gitlab/project_search_results_spec.rb +++ b/spec/lib/gitlab/project_search_results_spec.rb @@ -43,18 +43,6 @@ describe Gitlab::ProjectSearchResults, lib: true do expect(results.issues_count).to eq 1 end - it 'should not list project confidential issues for project members with guest role' do - project.team << [member, :guest] - - results = described_class.new(member, project, query) - issues = results.objects('issues') - - expect(issues).to include issue - expect(issues).not_to include security_issue_1 - expect(issues).not_to include security_issue_2 - expect(results.issues_count).to eq 1 - end - it 'should list project confidential issues for author' do results = described_class.new(author, project, query) issues = results.objects('issues') diff --git a/spec/lib/gitlab/search_results_spec.rb b/spec/lib/gitlab/search_results_spec.rb index 1bb444bf34f..f4afe597e8d 100644 --- a/spec/lib/gitlab/search_results_spec.rb +++ b/spec/lib/gitlab/search_results_spec.rb @@ -86,22 +86,6 @@ describe Gitlab::SearchResults do expect(results.issues_count).to eq 1 end - it 'should not list confidential issues for project members with guest role' do - project_1.team << [member, :guest] - project_2.team << [member, :guest] - - results = described_class.new(member, limit_projects, query) - issues = results.objects('issues') - - expect(issues).to include issue - expect(issues).not_to include security_issue_1 - expect(issues).not_to include security_issue_2 - expect(issues).not_to include security_issue_3 - expect(issues).not_to include security_issue_4 - expect(issues).not_to include security_issue_5 - expect(results.issues_count).to eq 1 - end - it 'should list confidential issues for author' do results = described_class.new(author, limit_projects, query) issues = results.objects('issues') diff --git a/spec/mailers/notify_spec.rb b/spec/mailers/notify_spec.rb index 1e6eb20ab39..818825b1477 100644 --- a/spec/mailers/notify_spec.rb +++ b/spec/mailers/notify_spec.rb @@ -400,136 +400,26 @@ describe Notify do end end - describe 'project access requested' do - let(:project) { create(:project) } - let(:user) { create(:user) } - let(:project_member) do - project.request_access(user) - project.members.request.find_by(user_id: user.id) - end - subject { Notify.member_access_requested_email('project', project_member.id) } - - it_behaves_like 'an email sent from GitLab' - it_behaves_like 'it should not have Gmail Actions links' - it_behaves_like "a user cannot unsubscribe through footer link" - - it 'contains all the useful information' do - is_expected.to have_subject "Request to join the #{project.name_with_namespace} project" - is_expected.to have_body_text /#{project.name_with_namespace}/ - is_expected.to have_body_text /#{namespace_project_project_members_url(project.namespace, project)}/ - is_expected.to have_body_text /#{project_member.human_access}/ - end - end - - describe 'project access denied' do - let(:project) { create(:project) } - let(:user) { create(:user) } - let(:project_member) do - project.request_access(user) - project.members.request.find_by(user_id: user.id) - end - subject { Notify.member_access_denied_email('project', project.id, user.id) } - - it_behaves_like 'an email sent from GitLab' - it_behaves_like 'it should not have Gmail Actions links' - it_behaves_like "a user cannot unsubscribe through footer link" - - it 'contains all the useful information' do - is_expected.to have_subject "Access to the #{project.name_with_namespace} project was denied" - is_expected.to have_body_text /#{project.name_with_namespace}/ - is_expected.to have_body_text /#{project.web_url}/ - end - end - describe 'project access changed' do let(:project) { create(:project) } let(:user) { create(:user) } let(:project_member) { create(:project_member, project: project, user: user) } - subject { Notify.member_access_granted_email('project', project_member.id) } - - it_behaves_like 'an email sent from GitLab' - it_behaves_like 'it should not have Gmail Actions links' - it_behaves_like "a user cannot unsubscribe through footer link" - - it 'contains all the useful information' do - is_expected.to have_subject "Access to the #{project.name_with_namespace} project was granted" - is_expected.to have_body_text /#{project.name_with_namespace}/ - is_expected.to have_body_text /#{project.web_url}/ - is_expected.to have_body_text /#{project_member.human_access}/ - end - end - - def invite_to_project(project:, email:, inviter:) - ProjectMember.add_user(project.project_members, 'toto@example.com', Gitlab::Access::DEVELOPER, inviter) - - project.project_members.invite.last - end - - describe 'project invitation' do - let(:project) { create(:project) } - let(:master) { create(:user).tap { |u| project.team << [u, :master] } } - let(:project_member) { invite_to_project(project: project, email: 'toto@example.com', inviter: master) } - - subject { Notify.member_invited_email('project', project_member.id, project_member.invite_token) } + subject { Notify.project_access_granted_email(project_member.id) } it_behaves_like 'an email sent from GitLab' it_behaves_like 'it should not have Gmail Actions links' it_behaves_like "a user cannot unsubscribe through footer link" - it 'contains all the useful information' do - is_expected.to have_subject "Invitation to join the #{project.name_with_namespace} project" - is_expected.to have_body_text /#{project.name_with_namespace}/ - is_expected.to have_body_text /#{project.web_url}/ - is_expected.to have_body_text /#{project_member.human_access}/ - is_expected.to have_body_text /#{project_member.invite_token}/ - end - end - - describe 'project invitation accepted' do - let(:project) { create(:project) } - let(:invited_user) { create(:user) } - let(:master) { create(:user).tap { |u| project.team << [u, :master] } } - let(:project_member) do - invitee = invite_to_project(project: project, email: 'toto@example.com', inviter: master) - invitee.accept_invite!(invited_user) - invitee - end - - subject { Notify.member_invite_accepted_email('project', project_member.id) } - - it_behaves_like 'an email sent from GitLab' - it_behaves_like 'it should not have Gmail Actions links' - it_behaves_like "a user cannot unsubscribe through footer link" - - it 'contains all the useful information' do - is_expected.to have_subject 'Invitation accepted' - is_expected.to have_body_text /#{project.name_with_namespace}/ - is_expected.to have_body_text /#{project.web_url}/ - is_expected.to have_body_text /#{project_member.invite_email}/ - is_expected.to have_body_text /#{invited_user.name}/ + it 'has the correct subject' do + is_expected.to have_subject /Access to project was granted/ end - end - describe 'project invitation declined' do - let(:project) { create(:project) } - let(:master) { create(:user).tap { |u| project.team << [u, :master] } } - let(:project_member) do - invitee = invite_to_project(project: project, email: 'toto@example.com', inviter: master) - invitee.decline_invite! - invitee + it 'contains name of project' do + is_expected.to have_body_text /#{project.name}/ end - subject { Notify.member_invite_declined_email('project', project.id, project_member.invite_email, master.id) } - - it_behaves_like 'an email sent from GitLab' - it_behaves_like 'it should not have Gmail Actions links' - it_behaves_like "a user cannot unsubscribe through footer link" - - it 'contains all the useful information' do - is_expected.to have_subject 'Invitation declined' - is_expected.to have_body_text /#{project.name_with_namespace}/ - is_expected.to have_body_text /#{project.web_url}/ - is_expected.to have_body_text /#{project_member.invite_email}/ + it 'contains new user role' do + is_expected.to have_body_text /#{project_member.human_access}/ end end @@ -645,139 +535,27 @@ describe Notify do end end - context 'for a group' do - describe 'group access requested' do - let(:group) { create(:group) } - let(:user) { create(:user) } - let(:group_member) do - group.request_access(user) - group.members.request.find_by(user_id: user.id) - end - subject { Notify.member_access_requested_email('group', group_member.id) } - - it_behaves_like 'an email sent from GitLab' - it_behaves_like 'it should not have Gmail Actions links' - it_behaves_like "a user cannot unsubscribe through footer link" - - it 'contains all the useful information' do - is_expected.to have_subject "Request to join the #{group.name} group" - is_expected.to have_body_text /#{group.name}/ - is_expected.to have_body_text /#{group_group_members_url(group)}/ - is_expected.to have_body_text /#{group_member.human_access}/ - end - end - - describe 'group access denied' do - let(:group) { create(:group) } - let(:user) { create(:user) } - let(:group_member) do - group.request_access(user) - group.members.request.find_by(user_id: user.id) - end - subject { Notify.member_access_denied_email('group', group.id, user.id) } - - it_behaves_like 'an email sent from GitLab' - it_behaves_like 'it should not have Gmail Actions links' - it_behaves_like "a user cannot unsubscribe through footer link" - - it 'contains all the useful information' do - is_expected.to have_subject "Access to the #{group.name} group was denied" - is_expected.to have_body_text /#{group.name}/ - is_expected.to have_body_text /#{group.web_url}/ - end - end - - describe 'group access changed' do - let(:group) { create(:group) } - let(:user) { create(:user) } - let(:group_member) { create(:group_member, group: group, user: user) } - - subject { Notify.member_access_granted_email('group', group_member.id) } - - it_behaves_like 'an email sent from GitLab' - it_behaves_like 'it should not have Gmail Actions links' - it_behaves_like "a user cannot unsubscribe through footer link" - - it 'contains all the useful information' do - is_expected.to have_subject "Access to the #{group.name} group was granted" - is_expected.to have_body_text /#{group.name}/ - is_expected.to have_body_text /#{group.web_url}/ - is_expected.to have_body_text /#{group_member.human_access}/ - end - end - - def invite_to_group(group:, email:, inviter:) - GroupMember.add_user(group.group_members, 'toto@example.com', Gitlab::Access::DEVELOPER, inviter) - - group.group_members.invite.last - end - - describe 'group invitation' do - let(:group) { create(:group) } - let(:owner) { create(:user).tap { |u| group.add_user(u, Gitlab::Access::OWNER) } } - let(:group_member) { invite_to_group(group: group, email: 'toto@example.com', inviter: owner) } + describe 'group access changed' do + let(:group) { create(:group) } + let(:user) { create(:user) } + let(:membership) { create(:group_member, group: group, user: user) } - subject { Notify.member_invited_email('group', group_member.id, group_member.invite_token) } + subject { Notify.group_access_granted_email(membership.id) } - it_behaves_like 'an email sent from GitLab' - it_behaves_like 'it should not have Gmail Actions links' - it_behaves_like "a user cannot unsubscribe through footer link" + it_behaves_like 'an email sent from GitLab' + it_behaves_like 'it should not have Gmail Actions links' + it_behaves_like "a user cannot unsubscribe through footer link" - it 'contains all the useful information' do - is_expected.to have_subject "Invitation to join the #{group.name} group" - is_expected.to have_body_text /#{group.name}/ - is_expected.to have_body_text /#{group.web_url}/ - is_expected.to have_body_text /#{group_member.human_access}/ - is_expected.to have_body_text /#{group_member.invite_token}/ - end + it 'has the correct subject' do + is_expected.to have_subject /Access to group was granted/ end - describe 'group invitation accepted' do - let(:group) { create(:group) } - let(:invited_user) { create(:user) } - let(:owner) { create(:user).tap { |u| group.add_user(u, Gitlab::Access::OWNER) } } - let(:group_member) do - invitee = invite_to_group(group: group, email: 'toto@example.com', inviter: owner) - invitee.accept_invite!(invited_user) - invitee - end - - subject { Notify.member_invite_accepted_email('group', group_member.id) } - - it_behaves_like 'an email sent from GitLab' - it_behaves_like 'it should not have Gmail Actions links' - it_behaves_like "a user cannot unsubscribe through footer link" - - it 'contains all the useful information' do - is_expected.to have_subject 'Invitation accepted' - is_expected.to have_body_text /#{group.name}/ - is_expected.to have_body_text /#{group.web_url}/ - is_expected.to have_body_text /#{group_member.invite_email}/ - is_expected.to have_body_text /#{invited_user.name}/ - end + it 'contains name of project' do + is_expected.to have_body_text /#{group.name}/ end - describe 'group invitation declined' do - let(:group) { create(:group) } - let(:owner) { create(:user).tap { |u| group.add_user(u, Gitlab::Access::OWNER) } } - let(:group_member) do - invitee = invite_to_group(group: group, email: 'toto@example.com', inviter: owner) - invitee.decline_invite! - invitee - end - - subject { Notify.member_invite_declined_email('group', group.id, group_member.invite_email, owner.id) } - - it_behaves_like 'an email sent from GitLab' - it_behaves_like 'it should not have Gmail Actions links' - it_behaves_like "a user cannot unsubscribe through footer link" - - it 'contains all the useful information' do - is_expected.to have_subject 'Invitation declined' - is_expected.to have_body_text /#{group.name}/ - is_expected.to have_body_text /#{group.web_url}/ - is_expected.to have_body_text /#{group_member.invite_email}/ - end + it 'contains new user role' do + is_expected.to have_body_text /#{membership.human_access}/ end end diff --git a/spec/models/build_spec.rb b/spec/models/build_spec.rb index 5d1fa8226e5..2beb6cc598d 100644 --- a/spec/models/build_spec.rb +++ b/spec/models/build_spec.rb @@ -397,34 +397,9 @@ describe Ci::Build, models: true do context 'artifacts archive exists' do let(:build) { create(:ci_build, :artifacts) } it { is_expected.to be_truthy } - - context 'is expired' do - before { build.update(artifacts_expire_at: Time.now - 7.days) } - it { is_expected.to be_falsy } - end - - context 'is not expired' do - before { build.update(artifacts_expire_at: Time.now + 7.days) } - it { is_expected.to be_truthy } - end end end - describe '#artifacts_expired?' do - subject { build.artifacts_expired? } - - context 'is expired' do - before { build.update(artifacts_expire_at: Time.now - 7.days) } - - it { is_expected.to be_truthy } - end - - context 'is not expired' do - before { build.update(artifacts_expire_at: Time.now + 7.days) } - - it { is_expected.to be_falsey } - end - end describe '#artifacts_metadata?' do subject { build.artifacts_metadata? } @@ -437,6 +412,7 @@ describe Ci::Build, models: true do it { is_expected.to be_truthy } end end + describe '#repo_url' do let(:build) { create(:ci_build) } let(:project) { build.project } @@ -451,50 +427,6 @@ describe Ci::Build, models: true do it { is_expected.to include(project.web_url[7..-1]) } end - describe '#artifacts_expire_in' do - subject { build.artifacts_expire_in } - it { is_expected.to be_nil } - - context 'when artifacts_expire_at is specified' do - let(:expire_at) { Time.now + 7.days } - - before { build.artifacts_expire_at = expire_at } - - it { is_expected.to be_within(5).of(expire_at - Time.now) } - end - end - - describe '#artifacts_expire_in=' do - subject { build.artifacts_expire_in } - - it 'when assigning valid duration' do - build.artifacts_expire_in = '7 days' - - is_expected.to be_within(10).of(7.days.to_i) - end - - it 'when assigning invalid duration' do - expect { build.artifacts_expire_in = '7 elephants' }.to raise_error(ChronicDuration::DurationParseError) - is_expected.to be_nil - end - - it 'when resseting value' do - build.artifacts_expire_in = nil - - is_expected.to be_nil - end - end - - describe '#keep_artifacts!' do - let(:build) { create(:ci_build, artifacts_expire_at: Time.now + 7.days) } - - it 'to reset expire_at' do - build.keep_artifacts! - - expect(build.artifacts_expire_at).to be_nil - end - end - describe '#depends_on_builds' do let!(:build) { create(:ci_build, pipeline: pipeline, name: 'build', stage_idx: 0, stage: 'build') } let!(:rspec_test) { create(:ci_build, pipeline: pipeline, name: 'rspec', stage_idx: 1, stage: 'test') } diff --git a/spec/models/concerns/access_requestable_spec.rb b/spec/models/concerns/access_requestable_spec.rb deleted file mode 100644 index 98307876962..00000000000 --- a/spec/models/concerns/access_requestable_spec.rb +++ /dev/null @@ -1,40 +0,0 @@ -require 'spec_helper' - -describe AccessRequestable do - describe 'Group' do - describe '#request_access' do - let(:group) { create(:group, :public) } - let(:user) { create(:user) } - - it { expect(group.request_access(user)).to be_a(GroupMember) } - it { expect(group.request_access(user).user).to eq(user) } - end - - describe '#access_requested?' do - let(:group) { create(:group, :public) } - let(:user) { create(:user) } - - before { group.request_access(user) } - - it { expect(group.members.request.exists?(user_id: user)).to be_truthy } - end - end - - describe 'Project' do - describe '#request_access' do - let(:project) { create(:empty_project, :public) } - let(:user) { create(:user) } - - it { expect(project.request_access(user)).to be_a(ProjectMember) } - end - - describe '#access_requested?' do - let(:project) { create(:empty_project, :public) } - let(:user) { create(:user) } - - before { project.request_access(user) } - - it { expect(project.members.request.exists?(user_id: user)).to be_truthy } - end - end -end diff --git a/spec/models/concerns/milestoneish_spec.rb b/spec/models/concerns/milestoneish_spec.rb index 7e9ab8940cf..47c3be673c5 100644 --- a/spec/models/concerns/milestoneish_spec.rb +++ b/spec/models/concerns/milestoneish_spec.rb @@ -5,7 +5,6 @@ describe Milestone, 'Milestoneish' do let(:assignee) { create(:user) } let(:non_member) { create(:user) } let(:member) { create(:user) } - let(:guest) { create(:user) } let(:admin) { create(:admin) } let(:project) { create(:project, :public) } let(:milestone) { create(:milestone, project: project) } @@ -22,7 +21,6 @@ describe Milestone, 'Milestoneish' do before do project.team << [member, :developer] - project.team << [guest, :guest] end describe '#closed_items_count' do @@ -30,10 +28,6 @@ describe Milestone, 'Milestoneish' do expect(milestone.closed_items_count(non_member)).to eq 2 end - it 'should not count confidential issues for project members with guest role' do - expect(milestone.closed_items_count(guest)).to eq 2 - end - it 'should count confidential issues for author' do expect(milestone.closed_items_count(author)).to eq 4 end @@ -56,10 +50,6 @@ describe Milestone, 'Milestoneish' do expect(milestone.total_items_count(non_member)).to eq 4 end - it 'should not count confidential issues for project members with guest role' do - expect(milestone.total_items_count(guest)).to eq 4 - end - it 'should count confidential issues for author' do expect(milestone.total_items_count(author)).to eq 7 end @@ -95,10 +85,6 @@ describe Milestone, 'Milestoneish' do expect(milestone.percent_complete(non_member)).to eq 50 end - it 'should not count confidential issues for project members with guest role' do - expect(milestone.percent_complete(guest)).to eq 50 - end - it 'should count confidential issues for author' do expect(milestone.percent_complete(author)).to eq 57 end diff --git a/spec/models/deployment_spec.rb b/spec/models/deployment_spec.rb deleted file mode 100644 index b273018707f..00000000000 --- a/spec/models/deployment_spec.rb +++ /dev/null @@ -1,17 +0,0 @@ -require 'spec_helper' - -describe Deployment, models: true do - subject { build(:deployment) } - - it { is_expected.to belong_to(:project) } - it { is_expected.to belong_to(:environment) } - it { is_expected.to belong_to(:user) } - it { is_expected.to belong_to(:deployable) } - - it { is_expected.to delegate_method(:name).to(:environment).with_prefix } - it { is_expected.to delegate_method(:commit).to(:project) } - it { is_expected.to delegate_method(:commit_title).to(:commit).as(:try) } - - it { is_expected.to validate_presence_of(:ref) } - it { is_expected.to validate_presence_of(:sha) } -end diff --git a/spec/models/environment_spec.rb b/spec/models/environment_spec.rb deleted file mode 100644 index 7629af6a570..00000000000 --- a/spec/models/environment_spec.rb +++ /dev/null @@ -1,14 +0,0 @@ -require 'spec_helper' - -describe Environment, models: true do - let(:environment) { create(:environment) } - - it { is_expected.to belong_to(:project) } - it { is_expected.to have_many(:deployments) } - - it { is_expected.to delegate_method(:last_deployment).to(:deployments).as(:last) } - - it { is_expected.to validate_presence_of(:name) } - it { is_expected.to validate_uniqueness_of(:name).scoped_to(:project_id) } - it { is_expected.to validate_length_of(:name).is_within(0..255) } -end diff --git a/spec/models/event_spec.rb b/spec/models/event_spec.rb index 166a1dc4ddb..b0e76fec693 100644 --- a/spec/models/event_spec.rb +++ b/spec/models/event_spec.rb @@ -50,7 +50,6 @@ describe Event, models: true do let(:project) { create(:empty_project, :public) } let(:non_member) { create(:user) } let(:member) { create(:user) } - let(:guest) { create(:user) } let(:author) { create(:author) } let(:assignee) { create(:user) } let(:admin) { create(:admin) } @@ -62,7 +61,6 @@ describe Event, models: true do before do project.team << [member, :developer] - project.team << [guest, :guest] end context 'issue event' do @@ -73,7 +71,6 @@ describe Event, models: true do it { expect(event.visible_to_user?(author)).to eq true } it { expect(event.visible_to_user?(assignee)).to eq true } it { expect(event.visible_to_user?(member)).to eq true } - it { expect(event.visible_to_user?(guest)).to eq true } it { expect(event.visible_to_user?(admin)).to eq true } end @@ -84,7 +81,6 @@ describe Event, models: true do it { expect(event.visible_to_user?(author)).to eq true } it { expect(event.visible_to_user?(assignee)).to eq true } it { expect(event.visible_to_user?(member)).to eq true } - it { expect(event.visible_to_user?(guest)).to eq false } it { expect(event.visible_to_user?(admin)).to eq true } end end @@ -97,7 +93,6 @@ describe Event, models: true do it { expect(event.visible_to_user?(author)).to eq true } it { expect(event.visible_to_user?(assignee)).to eq true } it { expect(event.visible_to_user?(member)).to eq true } - it { expect(event.visible_to_user?(guest)).to eq true } it { expect(event.visible_to_user?(admin)).to eq true } end @@ -108,7 +103,6 @@ describe Event, models: true do it { expect(event.visible_to_user?(author)).to eq true } it { expect(event.visible_to_user?(assignee)).to eq true } it { expect(event.visible_to_user?(member)).to eq true } - it { expect(event.visible_to_user?(guest)).to eq false } it { expect(event.visible_to_user?(admin)).to eq true } end end diff --git a/spec/models/group_spec.rb b/spec/models/group_spec.rb index 2c19aa3f67f..6fa16be7f04 100644 --- a/spec/models/group_spec.rb +++ b/spec/models/group_spec.rb @@ -5,11 +5,7 @@ describe Group, models: true do describe 'associations' do it { is_expected.to have_many :projects } - it { is_expected.to have_many(:group_members).dependent(:destroy) } - it { is_expected.to have_many(:users).through(:group_members) } - it { is_expected.to have_many(:project_group_links).dependent(:destroy) } - it { is_expected.to have_many(:shared_projects).through(:project_group_links) } - it { is_expected.to have_many(:notification_settings).dependent(:destroy) } + it { is_expected.to have_many :group_members } end describe 'modules' do @@ -135,58 +131,4 @@ describe Group, models: true do expect(described_class.search(group.path.upcase)).to eq([group]) end end - - describe '#has_owner?' do - before { @members = setup_group_members(group) } - - it { expect(group.has_owner?(@members[:owner])).to be_truthy } - it { expect(group.has_owner?(@members[:master])).to be_falsey } - it { expect(group.has_owner?(@members[:developer])).to be_falsey } - it { expect(group.has_owner?(@members[:reporter])).to be_falsey } - it { expect(group.has_owner?(@members[:guest])).to be_falsey } - it { expect(group.has_owner?(@members[:requester])).to be_falsey } - end - - describe '#has_master?' do - before { @members = setup_group_members(group) } - - it { expect(group.has_master?(@members[:owner])).to be_falsey } - it { expect(group.has_master?(@members[:master])).to be_truthy } - it { expect(group.has_master?(@members[:developer])).to be_falsey } - it { expect(group.has_master?(@members[:reporter])).to be_falsey } - it { expect(group.has_master?(@members[:guest])).to be_falsey } - it { expect(group.has_master?(@members[:requester])).to be_falsey } - end - - describe '#owners' do - let(:owner) { create(:user) } - let(:developer) { create(:user) } - - it 'returns the owners of a Group' do - group.add_owner(owner) - group.add_developer(developer) - - expect(group.owners).to eq([owner]) - end - end - - def setup_group_members(group) - members = { - owner: create(:user), - master: create(:user), - developer: create(:user), - reporter: create(:user), - guest: create(:user), - requester: create(:user) - } - - group.add_user(members[:owner], GroupMember::OWNER) - group.add_user(members[:master], GroupMember::MASTER) - group.add_user(members[:developer], GroupMember::DEVELOPER) - group.add_user(members[:reporter], GroupMember::REPORTER) - group.add_user(members[:guest], GroupMember::GUEST) - group.request_access(members[:requester]) - - members - end end diff --git a/spec/models/member_spec.rb b/spec/models/member_spec.rb index 3ed3202ac6c..6e51730eecd 100644 --- a/spec/models/member_spec.rb +++ b/spec/models/member_spec.rb @@ -55,97 +55,11 @@ describe Member, models: true do end end - describe 'Scopes & finders' do - before do - project = create(:project) - group = create(:group) - @owner_user = create(:user).tap { |u| group.add_owner(u) } - @owner = group.members.find_by(user_id: @owner_user.id) - - @master_user = create(:user).tap { |u| project.team << [u, :master] } - @master = project.members.find_by(user_id: @master_user.id) - - ProjectMember.add_user(project.members, 'toto1@example.com', Gitlab::Access::DEVELOPER, @master_user) - @invited_member = project.members.invite.find_by_invite_email('toto1@example.com') - - accepted_invite_user = build(:user) - ProjectMember.add_user(project.members, 'toto2@example.com', Gitlab::Access::DEVELOPER, @master_user) - @accepted_invite_member = project.members.invite.find_by_invite_email('toto2@example.com').tap { |u| u.accept_invite!(accepted_invite_user) } - - requested_user = create(:user).tap { |u| project.request_access(u) } - @requested_member = project.members.request.find_by(user_id: requested_user.id) - - accepted_request_user = create(:user).tap { |u| project.request_access(u) } - @accepted_request_member = project.members.request.find_by(user_id: accepted_request_user.id).tap { |m| m.accept_request } - end - - describe '.invite' do - it { expect(described_class.invite).not_to include @master } - it { expect(described_class.invite).to include @invited_member } - it { expect(described_class.invite).not_to include @accepted_invite_member } - it { expect(described_class.invite).not_to include @requested_member } - it { expect(described_class.invite).not_to include @accepted_request_member } - end - - describe '.non_invite' do - it { expect(described_class.non_invite).to include @master } - it { expect(described_class.non_invite).not_to include @invited_member } - it { expect(described_class.non_invite).to include @accepted_invite_member } - it { expect(described_class.non_invite).to include @requested_member } - it { expect(described_class.non_invite).to include @accepted_request_member } - end - - describe '.request' do - it { expect(described_class.request).not_to include @master } - it { expect(described_class.request).not_to include @invited_member } - it { expect(described_class.request).not_to include @accepted_invite_member } - it { expect(described_class.request).to include @requested_member } - it { expect(described_class.request).not_to include @accepted_request_member } - end - - describe '.non_request' do - it { expect(described_class.non_request).to include @master } - it { expect(described_class.non_request).to include @invited_member } - it { expect(described_class.non_request).to include @accepted_invite_member } - it { expect(described_class.non_request).not_to include @requested_member } - it { expect(described_class.non_request).to include @accepted_request_member } - end - - describe '.non_pending' do - it { expect(described_class.non_pending).to include @master } - it { expect(described_class.non_pending).not_to include @invited_member } - it { expect(described_class.non_pending).to include @accepted_invite_member } - it { expect(described_class.non_pending).not_to include @requested_member } - it { expect(described_class.non_pending).to include @accepted_request_member } - end - - describe '.owners_and_masters' do - it { expect(described_class.owners_and_masters).to include @owner } - it { expect(described_class.owners_and_masters).to include @master } - it { expect(described_class.owners_and_masters).not_to include @invited_member } - it { expect(described_class.owners_and_masters).not_to include @accepted_invite_member } - it { expect(described_class.owners_and_masters).not_to include @requested_member } - it { expect(described_class.owners_and_masters).not_to include @accepted_request_member } - end - end - describe "Delegate methods" do it { is_expected.to respond_to(:user_name) } it { is_expected.to respond_to(:user_email) } end - describe 'Callbacks' do - describe 'after_destroy :post_decline_request, if: :request?' do - let(:member) { create(:project_member, requested_at: Time.now.utc) } - - it 'calls #post_decline_request' do - expect(member).to receive(:post_decline_request) - - member.destroy - end - end - end - describe ".add_user" do let!(:user) { create(:user) } let(:project) { create(:project) } @@ -183,44 +97,6 @@ describe Member, models: true do end end - describe '#accept_request' do - let(:member) { create(:project_member, requested_at: Time.now.utc) } - - it { expect(member.accept_request).to be_truthy } - - it 'clears requested_at' do - member.accept_request - - expect(member.requested_at).to be_nil - end - - it 'calls #after_accept_request' do - expect(member).to receive(:after_accept_request) - - member.accept_request - end - end - - describe '#invite?' do - subject { create(:project_member, invite_email: "user@example.com", user: nil) } - - it { is_expected.to be_invite } - end - - describe '#request?' do - subject { create(:project_member, requested_at: Time.now.utc) } - - it { is_expected.to be_request } - end - - describe '#pending?' do - let(:invited_member) { create(:project_member, invite_email: "user@example.com", user: nil) } - let(:requester) { create(:project_member, requested_at: Time.now.utc) } - - it { expect(invited_member).to be_invite } - it { expect(requester).to be_pending } - end - describe "#accept_invite!" do let!(:member) { create(:project_member, invite_email: "user@example.com", user: nil) } let(:user) { create(:user) } diff --git a/spec/models/members/group_member_spec.rb b/spec/models/members/group_member_spec.rb index eeb74a462ac..5424c9b9cba 100644 --- a/spec/models/members/group_member_spec.rb +++ b/spec/models/members/group_member_spec.rb @@ -20,7 +20,7 @@ require 'spec_helper' describe GroupMember, models: true do - describe 'notifications' do + context 'notification' do describe "#after_create" do it "should send email to user" do membership = build(:group_member) @@ -50,31 +50,5 @@ describe GroupMember, models: true do @group_member.update_attribute(:access_level, GroupMember::OWNER) end end - - describe '#after_accept_request' do - it 'calls NotificationService.accept_group_access_request' do - member = create(:group_member, user: build_stubbed(:user), requested_at: Time.now) - - expect_any_instance_of(NotificationService).to receive(:new_group_member) - - member.__send__(:after_accept_request) - end - end - - describe '#post_decline_request' do - it 'calls NotificationService.decline_group_access_request' do - member = create(:group_member, user: build_stubbed(:user), requested_at: Time.now) - - expect_any_instance_of(NotificationService).to receive(:decline_group_access_request) - - member.__send__(:post_decline_request) - end - end - - describe '#real_source_type' do - subject { create(:group_member).real_source_type } - - it { is_expected.to eq 'Group' } - end end end diff --git a/spec/models/members/project_member_spec.rb b/spec/models/members/project_member_spec.rb index 1e466f9c620..9f13874b532 100644 --- a/spec/models/members/project_member_spec.rb +++ b/spec/models/members/project_member_spec.rb @@ -33,12 +33,6 @@ describe ProjectMember, models: true do it { is_expected.to include_module(Gitlab::ShellAdapter) } end - describe '#real_source_type' do - subject { create(:project_member).real_source_type } - - it { is_expected.to eq 'Project' } - end - describe "#destroy" do let(:owner) { create(:project_member, access_level: ProjectMember::OWNER) } let(:project) { owner.project } @@ -141,26 +135,4 @@ describe ProjectMember, models: true do it { expect(@project_1.users).to be_empty } it { expect(@project_2.users).to be_empty } end - - describe 'notifications' do - describe '#after_accept_request' do - it 'calls NotificationService.new_project_member' do - member = create(:project_member, user: build_stubbed(:user), requested_at: Time.now) - - expect_any_instance_of(NotificationService).to receive(:new_project_member) - - member.__send__(:after_accept_request) - end - end - - describe '#post_decline_request' do - it 'calls NotificationService.decline_project_access_request' do - member = create(:project_member, user: build_stubbed(:user), requested_at: Time.now) - - expect_any_instance_of(NotificationService).to receive(:decline_project_access_request) - - member.__send__(:post_decline_request) - end - end - end end diff --git a/spec/models/note_spec.rb b/spec/models/note_spec.rb index 285ab19cfaf..f15e96714b2 100644 --- a/spec/models/note_spec.rb +++ b/spec/models/note_spec.rb @@ -162,23 +162,16 @@ describe Note, models: true do end context "confidential issues" do - let(:user) { create(:user) } - let(:project) { create(:project) } - let(:confidential_issue) { create(:issue, :confidential, project: project, author: user) } - let(:confidential_note) { create(:note, note: "Random", noteable: confidential_issue, project: confidential_issue.project) } + let(:user) { create :user } + let(:confidential_issue) { create(:issue, :confidential, author: user) } + let(:confidential_note) { create :note, note: "Random", noteable: confidential_issue, project: confidential_issue.project } it "returns notes with matching content if user can see the issue" do expect(described_class.search(confidential_note.note, as_user: user)).to eq([confidential_note]) end it "does not return notes with matching content if user can not see the issue" do - user = create(:user) - expect(described_class.search(confidential_note.note, as_user: user)).to be_empty - end - - it "does not return notes with matching content for project members with guest role" do - user = create(:user) - project.team << [user, :guest] + user = create :user expect(described_class.search(confidential_note.note, as_user: user)).to be_empty end diff --git a/spec/models/notification_setting_spec.rb b/spec/models/notification_setting_spec.rb index 4e24e89b008..295081e9da1 100644 --- a/spec/models/notification_setting_spec.rb +++ b/spec/models/notification_setting_spec.rb @@ -10,6 +10,7 @@ RSpec.describe NotificationSetting, type: :model do subject { NotificationSetting.new(source_id: 1, source_type: 'Project') } it { is_expected.to validate_presence_of(:user) } + it { is_expected.to validate_presence_of(:source) } it { is_expected.to validate_presence_of(:level) } it { is_expected.to validate_uniqueness_of(:user_id).scoped_to([:source_id, :source_type]).with_message(/already exists in source/) } end diff --git a/spec/models/project_services/bamboo_service_spec.rb b/spec/models/project_services/bamboo_service_spec.rb index 9ae461f8c2d..ec81f05fc7a 100644 --- a/spec/models/project_services/bamboo_service_spec.rb +++ b/spec/models/project_services/bamboo_service_spec.rb @@ -126,25 +126,25 @@ describe BambooService, models: true do it 'returns a specific URL when status is 500' do stub_request(status: 500) - expect(service.build_page('123', 'unused')).to eq('http://gitlab.com/bamboo/browse/foo') + expect(service.build_page('123', 'unused')).to eq('http://gitlab.com/browse/foo') end it 'returns a specific URL when response has no results' do stub_request(body: %Q({"results":{"results":{"size":"0"}}})) - expect(service.build_page('123', 'unused')).to eq('http://gitlab.com/bamboo/browse/foo') + expect(service.build_page('123', 'unused')).to eq('http://gitlab.com/browse/foo') end it 'returns a build URL when bamboo_url has no trailing slash' do stub_request(body: %Q({"results":{"results":{"result":{"planResultKey":{"key":"42"}}}}})) - expect(service(bamboo_url: 'http://gitlab.com/bamboo').build_page('123', 'unused')).to eq('http://gitlab.com/bamboo/browse/42') + expect(service(bamboo_url: 'http://gitlab.com').build_page('123', 'unused')).to eq('http://gitlab.com/browse/42') end it 'returns a build URL when bamboo_url has a trailing slash' do stub_request(body: %Q({"results":{"results":{"result":{"planResultKey":{"key":"42"}}}}})) - expect(service(bamboo_url: 'http://gitlab.com/bamboo/').build_page('123', 'unused')).to eq('http://gitlab.com/bamboo/browse/42') + expect(service(bamboo_url: 'http://gitlab.com/').build_page('123', 'unused')).to eq('http://gitlab.com/browse/42') end end @@ -192,7 +192,7 @@ describe BambooService, models: true do end end - def service(bamboo_url: 'http://gitlab.com/bamboo') + def service(bamboo_url: 'http://gitlab.com') described_class.create( project: create(:empty_project), properties: { @@ -205,7 +205,7 @@ describe BambooService, models: true do end def stub_request(status: 200, body: nil, build_state: 'success') - bamboo_full_url = 'http://mic:password@gitlab.com/bamboo/rest/api/latest/result?label=123&os_authType=basic' + bamboo_full_url = 'http://mic:password@gitlab.com/rest/api/latest/result?label=123&os_authType=basic' body ||= %Q({"results":{"results":{"result":{"buildState":"#{build_state}"}}}}) WebMock.stub_request(:get, bamboo_full_url).to_return( diff --git a/spec/models/project_services/teamcity_service_spec.rb b/spec/models/project_services/teamcity_service_spec.rb index 474715d24c3..24a708ca849 100644 --- a/spec/models/project_services/teamcity_service_spec.rb +++ b/spec/models/project_services/teamcity_service_spec.rb @@ -126,19 +126,19 @@ describe TeamcityService, models: true do it 'returns a specific URL when status is 500' do stub_request(status: 500) - expect(service.build_page('123', 'unused')).to eq('http://gitlab.com/teamcity/viewLog.html?buildTypeId=foo') + expect(service.build_page('123', 'unused')).to eq('http://gitlab.com/viewLog.html?buildTypeId=foo') end it 'returns a build URL when teamcity_url has no trailing slash' do stub_request(body: %Q({"build":{"id":"666"}})) - expect(service(teamcity_url: 'http://gitlab.com/teamcity').build_page('123', 'unused')).to eq('http://gitlab.com/teamcity/viewLog.html?buildId=666&buildTypeId=foo') + expect(service(teamcity_url: 'http://gitlab.com').build_page('123', 'unused')).to eq('http://gitlab.com/viewLog.html?buildId=666&buildTypeId=foo') end it 'returns a build URL when teamcity_url has a trailing slash' do stub_request(body: %Q({"build":{"id":"666"}})) - expect(service(teamcity_url: 'http://gitlab.com/teamcity/').build_page('123', 'unused')).to eq('http://gitlab.com/teamcity/viewLog.html?buildId=666&buildTypeId=foo') + expect(service(teamcity_url: 'http://gitlab.com/').build_page('123', 'unused')).to eq('http://gitlab.com/viewLog.html?buildId=666&buildTypeId=foo') end end @@ -180,7 +180,7 @@ describe TeamcityService, models: true do end end - def service(teamcity_url: 'http://gitlab.com/teamcity') + def service(teamcity_url: 'http://gitlab.com') described_class.create( project: create(:empty_project), properties: { @@ -193,7 +193,7 @@ describe TeamcityService, models: true do end def stub_request(status: 200, body: nil, build_status: 'success') - teamcity_full_url = 'http://mic:password@gitlab.com/teamcity/httpAuth/app/rest/builds/branch:unspecified:any,number:123' + teamcity_full_url = 'http://mic:password@gitlab.com/httpAuth/app/rest/builds/branch:unspecified:any,number:123' body ||= %Q({"build":{"status":"#{build_status}","id":"666"}}) WebMock.stub_request(:get, teamcity_full_url).to_return( diff --git a/spec/models/project_spec.rb b/spec/models/project_spec.rb index fedab1f913b..f3590f72cfe 100644 --- a/spec/models/project_spec.rb +++ b/spec/models/project_spec.rb @@ -28,8 +28,6 @@ describe Project, models: true do it { is_expected.to have_many(:runners) } it { is_expected.to have_many(:variables) } it { is_expected.to have_many(:triggers) } - it { is_expected.to have_many(:environments).dependent(:destroy) } - it { is_expected.to have_many(:deployments).dependent(:destroy) } it { is_expected.to have_many(:todos).dependent(:destroy) } end @@ -55,6 +53,7 @@ describe Project, models: true do it { is_expected.to validate_length_of(:path).is_within(0..255) } it { is_expected.to validate_length_of(:description).is_within(0..2000) } it { is_expected.to validate_presence_of(:creator) } + it { is_expected.to validate_length_of(:issues_tracker_id).is_within(0..255) } it { is_expected.to validate_presence_of(:namespace) } it 'should not allow new projects beyond user limits' do @@ -91,17 +90,11 @@ describe Project, models: true do it { is_expected.to respond_to(:repo_exists?) } it { is_expected.to respond_to(:update_merge_requests) } it { is_expected.to respond_to(:execute_hooks) } + it { is_expected.to respond_to(:name_with_namespace) } it { is_expected.to respond_to(:owner) } it { is_expected.to respond_to(:path_with_namespace) } end - describe '#name_with_namespace' do - let(:project) { build_stubbed(:empty_project) } - - it { expect(project.name_with_namespace).to eq "#{project.namespace.human_name} / #{project.name}" } - it { expect(project.human_name).to eq project.name_with_namespace } - end - describe '#to_reference' do let(:project) { create(:empty_project) } @@ -328,6 +321,27 @@ describe Project, models: true do end end + describe :can_have_issues_tracker_id? do + let(:project) { create(:project) } + let(:ext_project) { create(:redmine_project) } + + it 'should be true for projects with external issues tracker if issues enabled' do + expect(ext_project.can_have_issues_tracker_id?).to be_truthy + end + + it 'should be false for projects with internal issue tracker if issues enabled' do + expect(project.can_have_issues_tracker_id?).to be_falsey + end + + it 'should be always false if issues disabled' do + project.issues_enabled = false + ext_project.issues_enabled = false + + expect(project.can_have_issues_tracker_id?).to be_falsey + expect(ext_project.can_have_issues_tracker_id?).to be_falsey + end + end + describe :open_branches do let(:project) { create(:project) } diff --git a/spec/models/project_team_spec.rb b/spec/models/project_team_spec.rb index 9262aeb6ed8..bacb17a8883 100644 --- a/spec/models/project_team_spec.rb +++ b/spec/models/project_team_spec.rb @@ -29,9 +29,6 @@ describe ProjectTeam, models: true do it { expect(project.team.master?(nonmember)).to be_falsey } it { expect(project.team.member?(nonmember)).to be_falsey } it { expect(project.team.member?(guest)).to be_truthy } - it { expect(project.team.member?(reporter, Gitlab::Access::REPORTER)).to be_truthy } - it { expect(project.team.member?(guest, Gitlab::Access::REPORTER)).to be_falsey } - it { expect(project.team.member?(nonmember, Gitlab::Access::GUEST)).to be_falsey } end end @@ -67,48 +64,50 @@ describe ProjectTeam, models: true do it { expect(project.team.master?(nonmember)).to be_falsey } it { expect(project.team.member?(nonmember)).to be_falsey } it { expect(project.team.member?(guest)).to be_truthy } - it { expect(project.team.member?(guest, Gitlab::Access::MASTER)).to be_truthy } - it { expect(project.team.member?(reporter, Gitlab::Access::MASTER)).to be_falsey } - it { expect(project.team.member?(nonmember, Gitlab::Access::GUEST)).to be_falsey } end end - describe '#find_member' do - context 'personal project' do - let(:project) { create(:empty_project) } - let(:requester) { create(:user) } - - before do - project.team << [master, :master] - project.team << [reporter, :reporter] - project.team << [guest, :guest] - project.request_access(requester) - end - - it { expect(project.team.find_member(master.id)).to be_a(ProjectMember) } - it { expect(project.team.find_member(reporter.id)).to be_a(ProjectMember) } - it { expect(project.team.find_member(guest.id)).to be_a(ProjectMember) } - it { expect(project.team.find_member(nonmember.id)).to be_nil } - it { expect(project.team.find_member(requester.id)).to be_nil } + describe :max_invited_level do + let(:group) { create(:group) } + let(:project) { create(:empty_project) } + + before do + project.project_group_links.create( + group: group, + group_access: Gitlab::Access::DEVELOPER + ) + + group.add_user(master, Gitlab::Access::MASTER) + group.add_user(reporter, Gitlab::Access::REPORTER) + end + + it { expect(project.team.max_invited_level(master.id)).to eq(Gitlab::Access::DEVELOPER) } + it { expect(project.team.max_invited_level(reporter.id)).to eq(Gitlab::Access::REPORTER) } + it { expect(project.team.max_invited_level(nonmember.id)).to be_nil } + end + + describe :max_member_access do + let(:group) { create(:group) } + let(:project) { create(:empty_project) } + + before do + project.project_group_links.create( + group: group, + group_access: Gitlab::Access::DEVELOPER + ) + + group.add_user(master, Gitlab::Access::MASTER) + group.add_user(reporter, Gitlab::Access::REPORTER) end - context 'group project' do - let(:group) { create(:group) } - let(:project) { create(:empty_project, group: group) } - let(:requester) { create(:user) } - - before do - group.add_master(master) - group.add_reporter(reporter) - group.add_guest(guest) - group.request_access(requester) - end - - it { expect(project.team.find_member(master.id)).to be_a(GroupMember) } - it { expect(project.team.find_member(reporter.id)).to be_a(GroupMember) } - it { expect(project.team.find_member(guest.id)).to be_a(GroupMember) } - it { expect(project.team.find_member(nonmember.id)).to be_nil } - it { expect(project.team.find_member(requester.id)).to be_nil } + it { expect(project.team.max_member_access(master.id)).to eq(Gitlab::Access::DEVELOPER) } + it { expect(project.team.max_member_access(reporter.id)).to eq(Gitlab::Access::REPORTER) } + it { expect(project.team.max_member_access(nonmember.id)).to be_nil } + + it "does not have an access" do + project.namespace.update(share_with_group_lock: true) + expect(project.team.max_member_access(master.id)).to be_nil + expect(project.team.max_member_access(reporter.id)).to be_nil end end @@ -133,69 +132,4 @@ describe ProjectTeam, models: true do expect(project.team.human_max_access(user.id)).to eq 'Owner' end end - - describe '#max_member_access' do - let(:requester) { create(:user) } - - context 'personal project' do - let(:project) { create(:empty_project) } - - context 'when project is not shared with group' do - before do - project.team << [master, :master] - project.team << [reporter, :reporter] - project.team << [guest, :guest] - project.request_access(requester) - end - - it { expect(project.team.max_member_access(master.id)).to eq(Gitlab::Access::MASTER) } - it { expect(project.team.max_member_access(reporter.id)).to eq(Gitlab::Access::REPORTER) } - it { expect(project.team.max_member_access(guest.id)).to eq(Gitlab::Access::GUEST) } - it { expect(project.team.max_member_access(nonmember.id)).to be_nil } - it { expect(project.team.max_member_access(requester.id)).to be_nil } - end - - context 'when project is shared with group' do - before do - group = create(:group) - project.project_group_links.create( - group: group, - group_access: Gitlab::Access::DEVELOPER) - - group.add_master(master) - group.add_reporter(reporter) - end - - it { expect(project.team.max_member_access(master.id)).to eq(Gitlab::Access::DEVELOPER) } - it { expect(project.team.max_member_access(reporter.id)).to eq(Gitlab::Access::REPORTER) } - it { expect(project.team.max_member_access(nonmember.id)).to be_nil } - it { expect(project.team.max_member_access(requester.id)).to be_nil } - - context 'but share_with_group_lock is true' do - before { project.namespace.update(share_with_group_lock: true) } - - it { expect(project.team.max_member_access(master.id)).to be_nil } - it { expect(project.team.max_member_access(reporter.id)).to be_nil } - end - end - end - - context 'group project' do - let(:group) { create(:group) } - let(:project) { create(:empty_project, group: group) } - - before do - group.add_master(master) - group.add_reporter(reporter) - group.add_guest(guest) - group.request_access(requester) - end - - it { expect(project.team.max_member_access(master.id)).to eq(Gitlab::Access::MASTER) } - it { expect(project.team.max_member_access(reporter.id)).to eq(Gitlab::Access::REPORTER) } - it { expect(project.team.max_member_access(guest.id)).to eq(Gitlab::Access::GUEST) } - it { expect(project.team.max_member_access(nonmember.id)).to be_nil } - it { expect(project.team.max_member_access(requester.id)).to be_nil } - end - end end diff --git a/spec/requests/api/builds_spec.rb b/spec/requests/api/builds_spec.rb index ac85f340922..6cb7be188ef 100644 --- a/spec/requests/api/builds_spec.rb +++ b/spec/requests/api/builds_spec.rb @@ -241,30 +241,4 @@ describe API::API, api: true do end end end - - describe 'POST /projects/:id/builds/:build_id/artifacts/keep' do - before do - post api("/projects/#{project.id}/builds/#{build.id}/artifacts/keep", user) - end - - context 'artifacts did not expire' do - let(:build) do - create(:ci_build, :trace, :artifacts, :success, - project: project, pipeline: pipeline, artifacts_expire_at: Time.now + 7.days) - end - - it 'keeps artifacts' do - expect(response.status).to eq 200 - expect(build.reload.artifacts_expire_at).to be_nil - end - end - - context 'no artifacts' do - let(:build) { create(:ci_build, project: project, pipeline: pipeline) } - - it 'responds with not found' do - expect(response.status).to eq 404 - end - end - end end diff --git a/spec/requests/api/issues_spec.rb b/spec/requests/api/issues_spec.rb index 59e557c5b2a..bb926172593 100644 --- a/spec/requests/api/issues_spec.rb +++ b/spec/requests/api/issues_spec.rb @@ -5,7 +5,6 @@ describe API::API, api: true do let(:user) { create(:user) } let(:user2) { create(:user) } let(:non_member) { create(:user) } - let(:guest) { create(:user) } let(:author) { create(:author) } let(:assignee) { create(:assignee) } let(:admin) { create(:user, :admin) } @@ -42,10 +41,7 @@ describe API::API, api: true do end let!(:note) { create(:note_on_issue, author: user, project: project, noteable: issue) } - before do - project.team << [user, :reporter] - project.team << [guest, :guest] - end + before { project.team << [user, :reporter] } describe "GET /issues" do context "when unauthenticated" do @@ -148,14 +144,6 @@ describe API::API, api: true do expect(json_response.first['title']).to eq(issue.title) end - it 'should return project issues without confidential issues for project members with guest role' do - get api("#{base_url}/issues", guest) - expect(response.status).to eq(200) - expect(json_response).to be_an Array - expect(json_response.length).to eq(2) - expect(json_response.first['title']).to eq(issue.title) - end - it 'should return project confidential issues for author' do get api("#{base_url}/issues", author) expect(response.status).to eq(200) @@ -290,11 +278,6 @@ describe API::API, api: true do expect(response.status).to eq(404) end - it "should return 404 for project members with guest role" do - get api("/projects/#{project.id}/issues/#{confidential_issue.id}", guest) - expect(response.status).to eq(404) - end - it "should return confidential issue for project members" do get api("/projects/#{project.id}/issues/#{confidential_issue.id}", user) expect(response.status).to eq(200) @@ -430,12 +413,6 @@ describe API::API, api: true do expect(response.status).to eq(403) end - it "should return 403 for project members with guest role" do - put api("/projects/#{project.id}/issues/#{confidential_issue.id}", guest), - title: 'updated title' - expect(response.status).to eq(403) - end - it "should update a confidential issue for project members" do put api("/projects/#{project.id}/issues/#{confidential_issue.id}", user), title: 'updated title' diff --git a/spec/requests/api/milestones_spec.rb b/spec/requests/api/milestones_spec.rb index 0154d1c62cc..241995041bb 100644 --- a/spec/requests/api/milestones_spec.rb +++ b/spec/requests/api/milestones_spec.rb @@ -146,7 +146,6 @@ describe API::API, api: true do let(:milestone) { create(:milestone, project: public_project) } let(:issue) { create(:issue, project: public_project) } let(:confidential_issue) { create(:issue, confidential: true, project: public_project) } - before do public_project.team << [user, :developer] milestone.issues << issue << confidential_issue @@ -161,18 +160,6 @@ describe API::API, api: true do expect(json_response.map { |issue| issue['id'] }).to include(issue.id, confidential_issue.id) end - it 'does not return confidential issues to team members with guest role' do - member = create(:user) - project.team << [member, :guest] - - get api("/projects/#{public_project.id}/milestones/#{milestone.id}/issues", member) - - expect(response.status).to eq(200) - expect(json_response).to be_an Array - expect(json_response.size).to eq(1) - expect(json_response.map { |issue| issue['id'] }).to include(issue.id) - end - it 'does not return confidential issues to regular users' do get api("/projects/#{public_project.id}/milestones/#{milestone.id}/issues", create(:user)) diff --git a/spec/requests/ci/api/builds_spec.rb b/spec/requests/ci/api/builds_spec.rb index 7e50bea90d1..e8508f8f950 100644 --- a/spec/requests/ci/api/builds_spec.rb +++ b/spec/requests/ci/api/builds_spec.rb @@ -364,42 +364,6 @@ describe Ci::API::API do end end - context 'with an expire date' do - let!(:artifacts) { file_upload } - - let(:post_data) do - { 'file.path' => artifacts.path, - 'file.name' => artifacts.original_filename, - 'expire_in' => expire_in } - end - - before do - post(post_url, post_data, headers_with_token) - end - - context 'with an expire_in given' do - let(:expire_in) { '7 days' } - - it 'updates when specified' do - build.reload - expect(response.status).to eq(201) - expect(json_response['artifacts_expire_at']).not_to be_empty - expect(build.artifacts_expire_at).to be_within(5.minutes).of(Time.now + 7.days) - end - end - - context 'with no expire_in given' do - let(:expire_in) { nil } - - it 'ignores if not specified' do - build.reload - expect(response.status).to eq(201) - expect(json_response['artifacts_expire_at']).to be_nil - expect(build.artifacts_expire_at).to be_nil - end - end - end - context "artifacts file is too large" do it "should fail to post too large artifact" do stub_application_setting(max_artifacts_size: 0) diff --git a/spec/requests/git_http_spec.rb b/spec/requests/git_http_spec.rb index fd26ca97818..c44a4a7a1fc 100644 --- a/spec/requests/git_http_spec.rb +++ b/spec/requests/git_http_spec.rb @@ -340,7 +340,7 @@ describe 'Git HTTP requests', lib: true do end end - context "when the file does not exist" do + context "when the file exists" do before { get "/#{project.path_with_namespace}/blob/master/info/refs" } it "returns not found" do diff --git a/spec/requests/jwt_controller_spec.rb b/spec/requests/jwt_controller_spec.rb index d2d4a9eca18..c995993a853 100644 --- a/spec/requests/jwt_controller_spec.rb +++ b/spec/requests/jwt_controller_spec.rb @@ -44,7 +44,7 @@ describe JwtController do let(:user) { create(:user) } let(:headers) { { authorization: credentials('user', 'password') } } - before { expect(Gitlab::Auth).to receive(:find_with_user_password).with('user', 'password').and_return(user) } + before { expect(Gitlab::Auth).to receive(:find_in_gitlab_or_ldap).with('user', 'password').and_return(user) } subject! { get '/jwt/auth', parameters, headers } diff --git a/spec/services/ci/register_build_service_spec.rb b/spec/services/ci/register_build_service_spec.rb index f28f2f1438d..d91fc574299 100644 --- a/spec/services/ci/register_build_service_spec.rb +++ b/spec/services/ci/register_build_service_spec.rb @@ -45,73 +45,11 @@ module Ci end end - context 'deleted projects' do - before do - project.update(pending_delete: true) - end - - context 'for shared runners' do - before do - project.update(shared_runners_enabled: true) - end - - it 'does not pick a build' do - expect(service.execute(shared_runner)).to be_nil - end - end - - context 'for specific runner' do - it 'does not pick a build' do - expect(service.execute(specific_runner)).to be_nil - end - end - end - context 'allow shared runners' do before do project.update(shared_runners_enabled: true) end - context 'for multiple builds' do - let!(:project2) { create :empty_project, shared_runners_enabled: true } - let!(:pipeline2) { create :ci_pipeline, project: project2 } - let!(:project3) { create :empty_project, shared_runners_enabled: true } - let!(:pipeline3) { create :ci_pipeline, project: project3 } - let!(:build1_project1) { pending_build } - let!(:build2_project1) { FactoryGirl.create :ci_build, pipeline: pipeline } - let!(:build3_project1) { FactoryGirl.create :ci_build, pipeline: pipeline } - let!(:build1_project2) { FactoryGirl.create :ci_build, pipeline: pipeline2 } - let!(:build2_project2) { FactoryGirl.create :ci_build, pipeline: pipeline2 } - let!(:build1_project3) { FactoryGirl.create :ci_build, pipeline: pipeline3 } - - it 'prefers projects without builds first' do - # it gets for one build from each of the projects - expect(service.execute(shared_runner)).to eq(build1_project1) - expect(service.execute(shared_runner)).to eq(build1_project2) - expect(service.execute(shared_runner)).to eq(build1_project3) - - # then it gets a second build from each of the projects - expect(service.execute(shared_runner)).to eq(build2_project1) - expect(service.execute(shared_runner)).to eq(build2_project2) - - # in the end the third build - expect(service.execute(shared_runner)).to eq(build3_project1) - end - - it 'equalises number of running builds' do - # after finishing the first build for project 1, get a second build from the same project - expect(service.execute(shared_runner)).to eq(build1_project1) - build1_project1.success - expect(service.execute(shared_runner)).to eq(build2_project1) - - expect(service.execute(shared_runner)).to eq(build1_project2) - build1_project2.success - expect(service.execute(shared_runner)).to eq(build2_project2) - expect(service.execute(shared_runner)).to eq(build1_project3) - expect(service.execute(shared_runner)).to eq(build3_project1) - end - end - context 'shared runner' do let(:build) { service.execute(shared_runner) } diff --git a/spec/services/create_deployment_service_spec.rb b/spec/services/create_deployment_service_spec.rb deleted file mode 100644 index 654e441f3cd..00000000000 --- a/spec/services/create_deployment_service_spec.rb +++ /dev/null @@ -1,119 +0,0 @@ -require 'spec_helper' - -describe CreateDeploymentService, services: true do - let(:project) { create(:empty_project) } - let(:user) { create(:user) } - - let(:service) { described_class.new(project, user, params) } - - describe '#execute' do - let(:params) do - { environment: 'production', - ref: 'master', - tag: false, - sha: '97de212e80737a608d939f648d959671fb0a0142', - } - end - - subject { service.execute } - - context 'when no environments exist' do - it 'does create a new environment' do - expect { subject }.to change { Environment.count }.by(1) - end - - it 'does create a deployment' do - expect(subject).to be_persisted - end - end - - context 'when environment exist' do - before { create(:environment, project: project, name: 'production') } - - it 'does not create a new environment' do - expect { subject }.not_to change { Environment.count } - end - - it 'does create a deployment' do - expect(subject).to be_persisted - end - end - - context 'for environment with invalid name' do - let(:params) do - { environment: 'name with spaces', - ref: 'master', - tag: false, - sha: '97de212e80737a608d939f648d959671fb0a0142', - } - end - - it 'does not create a new environment' do - expect { subject }.not_to change { Environment.count } - end - - it 'does not create a deployment' do - expect(subject).not_to be_persisted - end - end - end - - describe 'processing of builds' do - let(:environment) { nil } - - shared_examples 'does not create environment and deployment' do - it 'does not create a new environment' do - expect { subject }.not_to change { Environment.count } - end - - it 'does not create a new deployment' do - expect { subject }.not_to change { Deployment.count } - end - - it 'does not call a service' do - expect_any_instance_of(described_class).not_to receive(:execute) - subject - end - end - - shared_examples 'does create environment and deployment' do - it 'does create a new environment' do - expect { subject }.to change { Environment.count }.by(1) - end - - it 'does create a new deployment' do - expect { subject }.to change { Deployment.count }.by(1) - end - - it 'does call a service' do - expect_any_instance_of(described_class).to receive(:execute) - subject - end - end - - context 'without environment specified' do - let(:build) { create(:ci_build, project: project) } - - it_behaves_like 'does not create environment and deployment' do - subject { build.success } - end - end - - context 'when environment is specified' do - let(:pipeline) { create(:ci_pipeline, project: project) } - let(:build) { create(:ci_build, pipeline: pipeline, environment: 'production') } - - context 'when build succeeds' do - it_behaves_like 'does create environment and deployment' do - subject { build.success } - end - end - - context 'when build fails' do - it_behaves_like 'does not create environment and deployment' do - subject { build.drop } - end - end - end - end -end diff --git a/spec/services/notification_service_spec.rb b/spec/services/notification_service_spec.rb index e871a103d42..cef5e0d8659 100644 --- a/spec/services/notification_service_spec.rb +++ b/spec/services/notification_service_spec.rb @@ -72,7 +72,6 @@ describe NotificationService, services: true do should_not_email(@u_disabled) should_not_email(@unsubscriber) should_not_email(@u_outsider_mentioned) - should_not_email(@u_lazy_participant) end it 'filters out "mentioned in" notes' do @@ -81,20 +80,6 @@ describe NotificationService, services: true do expect(Notify).not_to receive(:note_issue_email) notification.new_note(mentioned_note) end - - context 'participating' do - context 'by note' do - before do - ActionMailer::Base.deliveries.clear - note.author = @u_lazy_participant - note.save - notification.new_note(note) - end - - - it { should_not_email(@u_lazy_participant) } - end - end end describe 'new note on issue in project that belongs to a group' do @@ -121,7 +106,6 @@ describe NotificationService, services: true do should_not_email(note.author) should_not_email(@u_participating) should_not_email(@u_disabled) - should_not_email(@u_lazy_participant) end end end @@ -132,14 +116,12 @@ describe NotificationService, services: true do let(:assignee) { create(:user) } let(:non_member) { create(:user) } let(:member) { create(:user) } - let(:guest) { create(:user) } let(:admin) { create(:admin) } let(:confidential_issue) { create(:issue, :confidential, project: project, author: author, assignee: assignee) } let(:note) { create(:note_on_issue, noteable: confidential_issue, project: project, note: "#{author.to_reference} #{assignee.to_reference} #{non_member.to_reference} #{member.to_reference} #{admin.to_reference}") } it 'filters out users that can not read the issue' do project.team << [member, :developer] - project.team << [guest, :guest] expect(SentNotification).to receive(:record).with(confidential_issue, any_args).exactly(4).times @@ -148,7 +130,6 @@ describe NotificationService, services: true do notification.new_note(note) should_not_email(non_member) - should_not_email(guest) should_email(author) should_email(assignee) should_email(member) @@ -254,7 +235,6 @@ describe NotificationService, services: true do should_not_email(note.author) should_not_email(@u_participating) should_not_email(@u_disabled) - should_not_email(@u_lazy_participant) end it do @@ -268,11 +248,10 @@ describe NotificationService, services: true do should_not_email(note.author) should_not_email(@u_participating) should_not_email(@u_disabled) - should_not_email(@u_lazy_participant) end it do - @u_committer = create_global_setting_for(@u_committer, :mention) + @u_committer.update_attributes(notification_level: :mention) notification.new_note(note) should_not_email(@u_committer) end @@ -301,11 +280,10 @@ describe NotificationService, services: true do should_not_email(@u_mentioned) should_not_email(@u_participating) should_not_email(@u_disabled) - should_not_email(@u_lazy_participant) end it do - create_global_setting_for(issue.assignee, :mention) + issue.assignee.update_attributes(notification_level: :mention) notification.new_issue(issue, @u_disabled) should_not_email(issue.assignee) @@ -325,20 +303,17 @@ describe NotificationService, services: true do let(:assignee) { create(:user) } let(:non_member) { create(:user) } let(:member) { create(:user) } - let(:guest) { create(:user) } let(:admin) { create(:admin) } let(:confidential_issue) { create(:issue, :confidential, project: project, title: 'Confidential issue', author: author, assignee: assignee) } it "emails subscribers of the issue's labels that can read the issue" do project.team << [member, :developer] - project.team << [guest, :guest] label = create(:label, issues: [confidential_issue]) label.toggle_subscription(non_member) label.toggle_subscription(author) label.toggle_subscription(assignee) label.toggle_subscription(member) - label.toggle_subscription(guest) label.toggle_subscription(admin) ActionMailer::Base.deliveries.clear @@ -347,7 +322,6 @@ describe NotificationService, services: true do should_not_email(non_member) should_not_email(author) - should_not_email(guest) should_email(assignee) should_email(member) should_email(admin) @@ -367,7 +341,6 @@ describe NotificationService, services: true do should_not_email(@unsubscriber) should_not_email(@u_participating) should_not_email(@u_disabled) - should_not_email(@u_lazy_participant) end it 'emails previous assignee even if he has the "on mention" notif level' do @@ -383,7 +356,6 @@ describe NotificationService, services: true do should_not_email(@unsubscriber) should_not_email(@u_participating) should_not_email(@u_disabled) - should_not_email(@u_lazy_participant) end it 'emails new assignee even if he has the "on mention" notif level' do @@ -399,7 +371,6 @@ describe NotificationService, services: true do should_not_email(@unsubscriber) should_not_email(@u_participating) should_not_email(@u_disabled) - should_not_email(@u_lazy_participant) end it 'emails new assignee' do @@ -415,7 +386,6 @@ describe NotificationService, services: true do should_not_email(@unsubscriber) should_not_email(@u_participating) should_not_email(@u_disabled) - should_not_email(@u_lazy_participant) end it 'does not email new assignee if they are the current user' do @@ -431,35 +401,6 @@ describe NotificationService, services: true do should_not_email(@unsubscriber) should_not_email(@u_participating) should_not_email(@u_disabled) - should_not_email(@u_lazy_participant) - end - - context 'participating' do - context 'by assignee' do - before do - issue.update_attribute(:assignee, @u_lazy_participant) - notification.reassigned_issue(issue, @u_disabled) - end - - it { should_email(@u_lazy_participant) } - end - - context 'by note' do - let!(:note) { create(:note_on_issue, noteable: issue, project_id: issue.project_id, note: 'anything', author: @u_lazy_participant) } - - before { notification.reassigned_issue(issue, @u_disabled) } - - it { should_email(@u_lazy_participant) } - end - - context 'by author' do - before do - issue.author = @u_lazy_participant - notification.reassigned_issue(issue, @u_disabled) - end - - it { should_email(@u_lazy_participant) } - end end end @@ -497,7 +438,6 @@ describe NotificationService, services: true do let(:assignee) { create(:user) } let(:non_member) { create(:user) } let(:member) { create(:user) } - let(:guest) { create(:user) } let(:admin) { create(:admin) } let(:confidential_issue) { create(:issue, :confidential, project: project, title: 'Confidential issue', author: author, assignee: assignee) } let!(:label_1) { create(:label, issues: [confidential_issue]) } @@ -505,13 +445,11 @@ describe NotificationService, services: true do it "emails subscribers of the issue's labels that can read the issue" do project.team << [member, :developer] - project.team << [guest, :guest] label_2.toggle_subscription(non_member) label_2.toggle_subscription(author) label_2.toggle_subscription(assignee) label_2.toggle_subscription(member) - label_2.toggle_subscription(guest) label_2.toggle_subscription(admin) ActionMailer::Base.deliveries.clear @@ -519,7 +457,6 @@ describe NotificationService, services: true do notification.relabeled_issue(confidential_issue, [label_2], @u_disabled) should_not_email(non_member) - should_not_email(guest) should_email(author) should_email(assignee) should_email(member) @@ -542,35 +479,6 @@ describe NotificationService, services: true do should_not_email(@unsubscriber) should_not_email(@u_participating) should_not_email(@u_disabled) - should_not_email(@u_lazy_participant) - end - - context 'participating' do - context 'by assignee' do - before do - issue.update_attribute(:assignee, @u_lazy_participant) - notification.close_issue(issue, @u_disabled) - end - - it { should_email(@u_lazy_participant) } - end - - context 'by note' do - let!(:note) { create(:note_on_issue, noteable: issue, project_id: issue.project_id, note: 'anything', author: @u_lazy_participant) } - - before { notification.close_issue(issue, @u_disabled) } - - it { should_email(@u_lazy_participant) } - end - - context 'by author' do - before do - issue.author = @u_lazy_participant - notification.close_issue(issue, @u_disabled) - end - - it { should_email(@u_lazy_participant) } - end end end @@ -587,35 +495,6 @@ describe NotificationService, services: true do should_email(@watcher_and_subscriber) should_not_email(@unsubscriber) should_not_email(@u_participating) - should_not_email(@u_lazy_participant) - end - - context 'participating' do - context 'by assignee' do - before do - issue.update_attribute(:assignee, @u_lazy_participant) - notification.reopen_issue(issue, @u_disabled) - end - - it { should_email(@u_lazy_participant) } - end - - context 'by note' do - let!(:note) { create(:note_on_issue, noteable: issue, project_id: issue.project_id, note: 'anything', author: @u_lazy_participant) } - - before { notification.reopen_issue(issue, @u_disabled) } - - it { should_email(@u_lazy_participant) } - end - - context 'by author' do - before do - issue.author = @u_lazy_participant - notification.reopen_issue(issue, @u_disabled) - end - - it { should_email(@u_lazy_participant) } - end end end end @@ -641,7 +520,6 @@ describe NotificationService, services: true do should_email(@u_guest_watcher) should_not_email(@u_participating) should_not_email(@u_disabled) - should_not_email(@u_lazy_participant) end it "emails subscribers of the merge request's labels" do @@ -652,36 +530,6 @@ describe NotificationService, services: true do should_email(subscriber) end - - - context 'participating' do - context 'by assignee' do - before do - merge_request.update_attribute(:assignee, @u_lazy_participant) - notification.new_merge_request(merge_request, @u_disabled) - end - - it { should_email(@u_lazy_participant) } - end - - context 'by note' do - let!(:note) { create(:note_on_issue, noteable: merge_request, project_id: project.id, note: 'anything', author: @u_lazy_participant) } - - before { notification.new_merge_request(merge_request, @u_disabled) } - - it { should_email(@u_lazy_participant) } - end - - context 'by author' do - before do - merge_request.author = @u_lazy_participant - merge_request.save - notification.new_merge_request(merge_request, @u_disabled) - end - - it { should_not_email(@u_lazy_participant) } - end - end end describe '#reassigned_merge_request' do @@ -697,36 +545,6 @@ describe NotificationService, services: true do should_not_email(@unsubscriber) should_not_email(@u_participating) should_not_email(@u_disabled) - should_not_email(@u_lazy_participant) - end - - context 'participating' do - context 'by assignee' do - before do - merge_request.update_attribute(:assignee, @u_lazy_participant) - notification.reassigned_merge_request(merge_request, @u_disabled) - end - - it { should_email(@u_lazy_participant) } - end - - context 'by note' do - let!(:note) { create(:note_on_issue, noteable: merge_request, project_id: project.id, note: 'anything', author: @u_lazy_participant) } - - before { notification.reassigned_merge_request(merge_request, @u_disabled) } - - it { should_email(@u_lazy_participant) } - end - - context 'by author' do - before do - merge_request.author = @u_lazy_participant - merge_request.save - notification.reassigned_merge_request(merge_request, @u_disabled) - end - - it { should_email(@u_lazy_participant) } - end end end @@ -754,7 +572,6 @@ describe NotificationService, services: true do should_not_email(@watcher_and_subscriber) should_not_email(@unsubscriber) should_not_email(@u_participating) - should_not_email(@u_lazy_participant) should_not_email(subscriber_to_label) should_email(subscriber_to_label2) end @@ -773,36 +590,6 @@ describe NotificationService, services: true do should_not_email(@unsubscriber) should_not_email(@u_participating) should_not_email(@u_disabled) - should_not_email(@u_lazy_participant) - end - - context 'participating' do - context 'by assignee' do - before do - merge_request.update_attribute(:assignee, @u_lazy_participant) - notification.close_mr(merge_request, @u_disabled) - end - - it { should_email(@u_lazy_participant) } - end - - context 'by note' do - let!(:note) { create(:note_on_issue, noteable: merge_request, project_id: project.id, note: 'anything', author: @u_lazy_participant) } - - before { notification.close_mr(merge_request, @u_disabled) } - - it { should_email(@u_lazy_participant) } - end - - context 'by author' do - before do - merge_request.author = @u_lazy_participant - merge_request.save - notification.close_mr(merge_request, @u_disabled) - end - - it { should_email(@u_lazy_participant) } - end end end @@ -819,36 +606,6 @@ describe NotificationService, services: true do should_not_email(@unsubscriber) should_not_email(@u_participating) should_not_email(@u_disabled) - should_not_email(@u_lazy_participant) - end - - context 'participating' do - context 'by assignee' do - before do - merge_request.update_attribute(:assignee, @u_lazy_participant) - notification.merge_mr(merge_request, @u_disabled) - end - - it { should_email(@u_lazy_participant) } - end - - context 'by note' do - let!(:note) { create(:note_on_issue, noteable: merge_request, project_id: project.id, note: 'anything', author: @u_lazy_participant) } - - before { notification.merge_mr(merge_request, @u_disabled) } - - it { should_email(@u_lazy_participant) } - end - - context 'by author' do - before do - merge_request.author = @u_lazy_participant - merge_request.save - notification.merge_mr(merge_request, @u_disabled) - end - - it { should_email(@u_lazy_participant) } - end end end @@ -865,36 +622,6 @@ describe NotificationService, services: true do should_not_email(@unsubscriber) should_not_email(@u_participating) should_not_email(@u_disabled) - should_not_email(@u_lazy_participant) - end - - context 'participating' do - context 'by assignee' do - before do - merge_request.update_attribute(:assignee, @u_lazy_participant) - notification.reopen_mr(merge_request, @u_disabled) - end - - it { should_email(@u_lazy_participant) } - end - - context 'by note' do - let!(:note) { create(:note_on_issue, noteable: merge_request, project_id: project.id, note: 'anything', author: @u_lazy_participant) } - - before { notification.reopen_mr(merge_request, @u_disabled) } - - it { should_email(@u_lazy_participant) } - end - - context 'by author' do - before do - merge_request.author = @u_lazy_participant - merge_request.save - notification.reopen_mr(merge_request, @u_disabled) - end - - it { should_email(@u_lazy_participant) } - end end end end @@ -913,7 +640,6 @@ describe NotificationService, services: true do should_email(@u_watcher) should_email(@u_participating) - should_email(@u_lazy_participant) should_not_email(@u_guest_watcher) should_not_email(@u_disabled) end @@ -921,19 +647,14 @@ describe NotificationService, services: true do end def build_team(project) - @u_watcher = create_global_setting_for(create(:user), :watch) - @u_participating = create_global_setting_for(create(:user), :participating) - @u_participant_mentioned = create_global_setting_for(create(:user, username: 'participant'), :participating) - @u_disabled = create_global_setting_for(create(:user), :disabled) - @u_mentioned = create_global_setting_for(create(:user, username: 'mention'), :mention) - @u_committer = create(:user, username: 'committer') - @u_not_mentioned = create_global_setting_for(create(:user, username: 'regular'), :participating) - @u_outsider_mentioned = create(:user, username: 'outsider') - - # User to be participant by default - # This user does not contain any record in notification settings table - # It should be treated with a :participating notification_level - @u_lazy_participant = create(:user, username: 'lazy-participant') + @u_watcher = create(:user, notification_level: :watch) + @u_participating = create(:user, notification_level: :participating) + @u_participant_mentioned = create(:user, username: 'participant', notification_level: :participating) + @u_disabled = create(:user, notification_level: :disabled) + @u_mentioned = create(:user, username: 'mention', notification_level: :mention) + @u_committer = create(:user, username: 'committer') + @u_not_mentioned = create(:user, username: 'regular', notification_level: :participating) + @u_outsider_mentioned = create(:user, username: 'outsider') create_guest_watcher @@ -944,15 +665,6 @@ describe NotificationService, services: true do project.team << [@u_mentioned, :master] project.team << [@u_committer, :master] project.team << [@u_not_mentioned, :master] - project.team << [@u_lazy_participant, :master] - end - - def create_global_setting_for(user, level) - setting = user.global_notification_setting - setting.level = level - setting.save - - user end def create_guest_watcher @@ -965,8 +677,8 @@ describe NotificationService, services: true do def add_users_with_subscription(project, issuable) @subscriber = create :user @unsubscriber = create :user - @subscribed_participant = create_global_setting_for(create(:user, username: 'subscribed_participant'), :participating) - @watcher_and_subscriber = create_global_setting_for(create(:user), :watch) + @subscribed_participant = create(:user, username: 'subscribed_participant', notification_level: :participating) + @watcher_and_subscriber = create(:user, notification_level: :watch) project.team << [@subscribed_participant, :master] project.team << [@subscriber, :master] diff --git a/spec/services/projects/autocomplete_service_spec.rb b/spec/services/projects/autocomplete_service_spec.rb index 0971fec2e9f..6108c26a78b 100644 --- a/spec/services/projects/autocomplete_service_spec.rb +++ b/spec/services/projects/autocomplete_service_spec.rb @@ -33,18 +33,6 @@ describe Projects::AutocompleteService, services: true do expect(issues.count).to eq 1 end - it 'should not list project confidential issues for project members with guest role' do - project.team << [member, :guest] - - autocomplete = described_class.new(project, non_member) - issues = autocomplete.issues.map(&:iid) - - expect(issues).to include issue.iid - expect(issues).not_to include security_issue_1.iid - expect(issues).not_to include security_issue_2.iid - expect(issues.count).to eq 1 - end - it 'should list project confidential issues for author' do autocomplete = described_class.new(project, author) issues = autocomplete.issues.map(&:iid) diff --git a/spec/services/todo_service_spec.rb b/spec/services/todo_service_spec.rb index 26f09cdbaf9..489c920f19f 100644 --- a/spec/services/todo_service_spec.rb +++ b/spec/services/todo_service_spec.rb @@ -5,15 +5,13 @@ describe TodoService, services: true do let(:assignee) { create(:user) } let(:non_member) { create(:user) } let(:member) { create(:user) } - let(:guest) { create(:user) } let(:admin) { create(:admin) } let(:john_doe) { create(:user) } let(:project) { create(:project) } - let(:mentions) { [author, assignee, john_doe, member, guest, non_member, admin].map(&:to_reference).join(' ') } + let(:mentions) { [author, assignee, john_doe, member, non_member, admin].map(&:to_reference).join(' ') } let(:service) { described_class.new } before do - project.team << [guest, :guest] project.team << [author, :developer] project.team << [member, :developer] project.team << [john_doe, :developer] @@ -43,20 +41,18 @@ describe TodoService, services: true do service.new_issue(issue, author) should_create_todo(user: member, target: issue, action: Todo::MENTIONED) - should_create_todo(user: guest, target: issue, action: Todo::MENTIONED) should_not_create_todo(user: author, target: issue, action: Todo::MENTIONED) should_not_create_todo(user: john_doe, target: issue, action: Todo::MENTIONED) should_not_create_todo(user: non_member, target: issue, action: Todo::MENTIONED) end - it 'does not create todo if user can not see the issue when issue is confidential' do + it 'does not create todo for non project members when issue is confidential' do service.new_issue(confidential_issue, john_doe) should_create_todo(user: assignee, target: confidential_issue, author: john_doe, action: Todo::ASSIGNED) should_create_todo(user: author, target: confidential_issue, author: john_doe, action: Todo::MENTIONED) should_create_todo(user: member, target: confidential_issue, author: john_doe, action: Todo::MENTIONED) should_create_todo(user: admin, target: confidential_issue, author: john_doe, action: Todo::MENTIONED) - should_not_create_todo(user: guest, target: confidential_issue, author: john_doe, action: Todo::MENTIONED) should_not_create_todo(user: john_doe, target: confidential_issue, author: john_doe, action: Todo::MENTIONED) end @@ -85,7 +81,6 @@ describe TodoService, services: true do service.update_issue(issue, author) should_create_todo(user: member, target: issue, action: Todo::MENTIONED) - should_create_todo(user: guest, target: issue, action: Todo::MENTIONED) should_create_todo(user: john_doe, target: issue, action: Todo::MENTIONED) should_not_create_todo(user: author, target: issue, action: Todo::MENTIONED) should_not_create_todo(user: non_member, target: issue, action: Todo::MENTIONED) @@ -97,14 +92,13 @@ describe TodoService, services: true do expect { service.update_issue(issue, author) }.not_to change(member.todos, :count) end - it 'does not create todo if user can not see the issue when issue is confidential' do + it 'does not create todo for non project members when issue is confidential' do service.update_issue(confidential_issue, john_doe) should_create_todo(user: author, target: confidential_issue, author: john_doe, action: Todo::MENTIONED) should_create_todo(user: assignee, target: confidential_issue, author: john_doe, action: Todo::MENTIONED) should_create_todo(user: member, target: confidential_issue, author: john_doe, action: Todo::MENTIONED) should_create_todo(user: admin, target: confidential_issue, author: john_doe, action: Todo::MENTIONED) - should_not_create_todo(user: guest, target: confidential_issue, author: john_doe, action: Todo::MENTIONED) should_not_create_todo(user: john_doe, target: confidential_issue, author: john_doe, action: Todo::MENTIONED) end @@ -198,20 +192,18 @@ describe TodoService, services: true do service.new_note(note, john_doe) should_create_todo(user: member, target: issue, author: john_doe, action: Todo::MENTIONED, note: note) - should_create_todo(user: guest, target: issue, author: john_doe, action: Todo::MENTIONED, note: note) should_create_todo(user: author, target: issue, author: john_doe, action: Todo::MENTIONED, note: note) should_not_create_todo(user: john_doe, target: issue, author: john_doe, action: Todo::MENTIONED, note: note) should_not_create_todo(user: non_member, target: issue, author: john_doe, action: Todo::MENTIONED, note: note) end - it 'does not create todo if user can not see the issue when leaving a note on a confidential issue' do + it 'does not create todo for non project members when leaving a note on a confidential issue' do service.new_note(note_on_confidential_issue, john_doe) should_create_todo(user: author, target: confidential_issue, author: john_doe, action: Todo::MENTIONED, note: note_on_confidential_issue) should_create_todo(user: assignee, target: confidential_issue, author: john_doe, action: Todo::MENTIONED, note: note_on_confidential_issue) should_create_todo(user: member, target: confidential_issue, author: john_doe, action: Todo::MENTIONED, note: note_on_confidential_issue) should_create_todo(user: admin, target: confidential_issue, author: john_doe, action: Todo::MENTIONED, note: note_on_confidential_issue) - should_not_create_todo(user: guest, target: confidential_issue, author: john_doe, action: Todo::MENTIONED, note: note_on_confidential_issue) should_not_create_todo(user: john_doe, target: confidential_issue, author: john_doe, action: Todo::MENTIONED, note: note_on_confidential_issue) end @@ -228,14 +220,6 @@ describe TodoService, services: true do should_not_create_any_todo { service.new_note(note_on_project_snippet, john_doe) } end end - - describe '#mark_todo' do - it 'creates a todo from a issue' do - service.mark_todo(unassigned_issue, author) - - should_create_todo(user: author, target: unassigned_issue, action: Todo::MARKED) - end - end end describe 'Merge Requests' do @@ -261,7 +245,6 @@ describe TodoService, services: true do service.new_merge_request(mr_assigned, author) should_create_todo(user: member, target: mr_assigned, action: Todo::MENTIONED) - should_create_todo(user: guest, target: mr_assigned, action: Todo::MENTIONED) should_not_create_todo(user: author, target: mr_assigned, action: Todo::MENTIONED) should_not_create_todo(user: john_doe, target: mr_assigned, action: Todo::MENTIONED) should_not_create_todo(user: non_member, target: mr_assigned, action: Todo::MENTIONED) @@ -273,7 +256,6 @@ describe TodoService, services: true do service.update_merge_request(mr_assigned, author) should_create_todo(user: member, target: mr_assigned, action: Todo::MENTIONED) - should_create_todo(user: guest, target: mr_assigned, action: Todo::MENTIONED) should_create_todo(user: john_doe, target: mr_assigned, action: Todo::MENTIONED) should_not_create_todo(user: author, target: mr_assigned, action: Todo::MENTIONED) should_not_create_todo(user: non_member, target: mr_assigned, action: Todo::MENTIONED) @@ -369,14 +351,6 @@ describe TodoService, services: true do expect(second_todo.reload).not_to be_done end end - - describe '#mark_todo' do - it 'creates a todo from a merge request' do - service.mark_todo(mr_unassigned, author) - - should_create_todo(user: author, target: mr_unassigned, action: Todo::MARKED) - end - end end def should_create_todo(attributes = {}) diff --git a/spec/support/test_env.rb b/spec/support/test_env.rb index 498bd4bf800..71664bb192e 100644 --- a/spec/support/test_env.rb +++ b/spec/support/test_env.rb @@ -16,7 +16,6 @@ module TestEnv 'master' => '5937ac0', "'test'" => 'e56497b', 'orphaned-branch' => '45127a9', - 'binary-encoding' => '7b1cf43', } # gitlab-test-fork is a fork of gitlab-fork, but we don't necessarily diff --git a/spec/workers/expire_build_artifacts_worker_spec.rb b/spec/workers/expire_build_artifacts_worker_spec.rb deleted file mode 100644 index e3827cae9a6..00000000000 --- a/spec/workers/expire_build_artifacts_worker_spec.rb +++ /dev/null @@ -1,57 +0,0 @@ -require 'spec_helper' - -describe ExpireBuildArtifactsWorker do - include RepoHelpers - - let(:worker) { described_class.new } - - describe '#perform' do - before { build } - - subject! { worker.perform } - - context 'with expired artifacts' do - let(:build) { create(:ci_build, :artifacts, artifacts_expire_at: Time.now - 7.days) } - - it 'does expire' do - expect(build.reload.artifacts_expired?).to be_truthy - end - - it 'does remove files' do - expect(build.reload.artifacts_file.exists?).to be_falsey - end - end - - context 'with not yet expired artifacts' do - let(:build) { create(:ci_build, :artifacts, artifacts_expire_at: Time.now + 7.days) } - - it 'does not expire' do - expect(build.reload.artifacts_expired?).to be_falsey - end - - it 'does not remove files' do - expect(build.reload.artifacts_file.exists?).to be_truthy - end - end - - context 'without expire date' do - let(:build) { create(:ci_build, :artifacts) } - - it 'does not expire' do - expect(build.reload.artifacts_expired?).to be_falsey - end - - it 'does not remove files' do - expect(build.reload.artifacts_file.exists?).to be_truthy - end - end - - context 'for expired artifacts' do - let(:build) { create(:ci_build, artifacts_expire_at: Time.now - 7.days) } - - it 'is still expired' do - expect(build.reload.artifacts_expired?).to be_truthy - end - end - end -end diff --git a/spec/workers/stuck_ci_builds_worker_spec.rb b/spec/workers/stuck_ci_builds_worker_spec.rb index 801fa31b45d..665ec20f224 100644 --- a/spec/workers/stuck_ci_builds_worker_spec.rb +++ b/spec/workers/stuck_ci_builds_worker_spec.rb @@ -2,7 +2,6 @@ require "spec_helper" describe StuckCiBuildsWorker do let!(:build) { create :ci_build } - let(:worker) { described_class.new } subject do build.reload @@ -17,13 +16,13 @@ describe StuckCiBuildsWorker do it 'gets dropped if it was updated over 2 days ago' do build.update!(updated_at: 2.days.ago) - worker.perform + StuckCiBuildsWorker.new.perform is_expected.to eq('failed') end it "is still #{status}" do build.update!(updated_at: 1.minute.ago) - worker.perform + StuckCiBuildsWorker.new.perform is_expected.to eq(status) end end @@ -37,21 +36,9 @@ describe StuckCiBuildsWorker do it "is still #{status}" do build.update!(updated_at: 2.days.ago) - worker.perform + StuckCiBuildsWorker.new.perform is_expected.to eq(status) end end end - - context "for deleted project" do - before do - build.update!(status: :running, updated_at: 2.days.ago) - build.project.update(pending_delete: true) - end - - it "does not drop build" do - expect_any_instance_of(Ci::Build).not_to receive(:drop) - worker.perform - end - end end diff --git a/vendor/assets/javascripts/raphael.js b/vendor/assets/javascripts/raphael.js deleted file mode 100644 index 3f3f8a0b7f6..00000000000 --- a/vendor/assets/javascripts/raphael.js +++ /dev/null @@ -1,8239 +0,0 @@ -// ┌────────────────────────────────────────────────────────────────────┐ \\ -// │ Raphaël 2.1.4 - JavaScript Vector Library │ \\ -// ├────────────────────────────────────────────────────────────────────┤ \\ -// │ Copyright © 2008-2012 Dmitry Baranovskiy (http://raphaeljs.com) │ \\ -// │ Copyright © 2008-2012 Sencha Labs (http://sencha.com) │ \\ -// ├────────────────────────────────────────────────────────────────────┤ \\ -// │ Licensed under the MIT (http://raphaeljs.com/license.html) license.│ \\ -// └────────────────────────────────────────────────────────────────────┘ \\ -// Copyright (c) 2013 Adobe Systems Incorporated. All rights reserved. -// -// Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at -// -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. -// ┌────────────────────────────────────────────────────────────┐ \\ -// │ Eve 0.4.2 - JavaScript Events Library │ \\ -// ├────────────────────────────────────────────────────────────┤ \\ -// │ Author Dmitry Baranovskiy (http://dmitry.baranovskiy.com/) │ \\ -// └────────────────────────────────────────────────────────────┘ \\ - -(function (glob) { - var version = "0.4.2", - has = "hasOwnProperty", - separator = /[\.\/]/, - wildcard = "*", - fun = function () {}, - numsort = function (a, b) { - return a - b; - }, - current_event, - stop, - events = {n: {}}, - /*\ - * eve - [ method ] - - * Fires event with given `name`, given scope and other parameters. - - > Arguments - - - name (string) name of the *event*, dot (`.`) or slash (`/`) separated - - scope (object) context for the event handlers - - varargs (...) the rest of arguments will be sent to event handlers - - = (object) array of returned values from the listeners - \*/ - eve = function (name, scope) { - name = String(name); - var e = events, - oldstop = stop, - args = Array.prototype.slice.call(arguments, 2), - listeners = eve.listeners(name), - z = 0, - f = false, - l, - indexed = [], - queue = {}, - out = [], - ce = current_event, - errors = []; - current_event = name; - stop = 0; - for (var i = 0, ii = listeners.length; i < ii; i++) if ("zIndex" in listeners[i]) { - indexed.push(listeners[i].zIndex); - if (listeners[i].zIndex < 0) { - queue[listeners[i].zIndex] = listeners[i]; - } - } - indexed.sort(numsort); - while (indexed[z] < 0) { - l = queue[indexed[z++]]; - out.push(l.apply(scope, args)); - if (stop) { - stop = oldstop; - return out; - } - } - for (i = 0; i < ii; i++) { - l = listeners[i]; - if ("zIndex" in l) { - if (l.zIndex == indexed[z]) { - out.push(l.apply(scope, args)); - if (stop) { - break; - } - do { - z++; - l = queue[indexed[z]]; - l && out.push(l.apply(scope, args)); - if (stop) { - break; - } - } while (l) - } else { - queue[l.zIndex] = l; - } - } else { - out.push(l.apply(scope, args)); - if (stop) { - break; - } - } - } - stop = oldstop; - current_event = ce; - return out.length ? out : null; - }; - // Undocumented. Debug only. - eve._events = events; - /*\ - * eve.listeners - [ method ] - - * Internal method which gives you array of all event handlers that will be triggered by the given `name`. - - > Arguments - - - name (string) name of the event, dot (`.`) or slash (`/`) separated - - = (array) array of event handlers - \*/ - eve.listeners = function (name) { - var names = name.split(separator), - e = events, - item, - items, - k, - i, - ii, - j, - jj, - nes, - es = [e], - out = []; - for (i = 0, ii = names.length; i < ii; i++) { - nes = []; - for (j = 0, jj = es.length; j < jj; j++) { - e = es[j].n; - items = [e[names[i]], e[wildcard]]; - k = 2; - while (k--) { - item = items[k]; - if (item) { - nes.push(item); - out = out.concat(item.f || []); - } - } - } - es = nes; - } - return out; - }; - - /*\ - * eve.on - [ method ] - ** - * Binds given event handler with a given name. You can use wildcards “`*`” for the names: - | eve.on("*.under.*", f); - | eve("mouse.under.floor"); // triggers f - * Use @eve to trigger the listener. - ** - > Arguments - ** - - name (string) name of the event, dot (`.`) or slash (`/`) separated, with optional wildcards - - f (function) event handler function - ** - = (function) returned function accepts a single numeric parameter that represents z-index of the handler. It is an optional feature and only used when you need to ensure that some subset of handlers will be invoked in a given order, despite of the order of assignment. - > Example: - | eve.on("mouse", eatIt)(2); - | eve.on("mouse", scream); - | eve.on("mouse", catchIt)(1); - * This will ensure that `catchIt()` function will be called before `eatIt()`. - * - * If you want to put your handler before non-indexed handlers, specify a negative value. - * Note: I assume most of the time you don’t need to worry about z-index, but it’s nice to have this feature “just in case”. - \*/ - eve.on = function (name, f) { - name = String(name); - if (typeof f != "function") { - return function () {}; - } - var names = name.split(separator), - e = events; - for (var i = 0, ii = names.length; i < ii; i++) { - e = e.n; - e = e.hasOwnProperty(names[i]) && e[names[i]] || (e[names[i]] = {n: {}}); - } - e.f = e.f || []; - for (i = 0, ii = e.f.length; i < ii; i++) if (e.f[i] == f) { - return fun; - } - e.f.push(f); - return function (zIndex) { - if (+zIndex == +zIndex) { - f.zIndex = +zIndex; - } - }; - }; - /*\ - * eve.f - [ method ] - ** - * Returns function that will fire given event with optional arguments. - * Arguments that will be passed to the result function will be also - * concated to the list of final arguments. - | el.onclick = eve.f("click", 1, 2); - | eve.on("click", function (a, b, c) { - | console.log(a, b, c); // 1, 2, [event object] - | }); - > Arguments - - event (string) event name - - varargs (…) and any other arguments - = (function) possible event handler function - \*/ - eve.f = function (event) { - var attrs = [].slice.call(arguments, 1); - return function () { - eve.apply(null, [event, null].concat(attrs).concat([].slice.call(arguments, 0))); - }; - }; - /*\ - * eve.stop - [ method ] - ** - * Is used inside an event handler to stop the event, preventing any subsequent listeners from firing. - \*/ - eve.stop = function () { - stop = 1; - }; - /*\ - * eve.nt - [ method ] - ** - * Could be used inside event handler to figure out actual name of the event. - ** - > Arguments - ** - - subname (string) #optional subname of the event - ** - = (string) name of the event, if `subname` is not specified - * or - = (boolean) `true`, if current event’s name contains `subname` - \*/ - eve.nt = function (subname) { - if (subname) { - return new RegExp("(?:\\.|\\/|^)" + subname + "(?:\\.|\\/|$)").test(current_event); - } - return current_event; - }; - /*\ - * eve.nts - [ method ] - ** - * Could be used inside event handler to figure out actual name of the event. - ** - ** - = (array) names of the event - \*/ - eve.nts = function () { - return current_event.split(separator); - }; - /*\ - * eve.off - [ method ] - ** - * Removes given function from the list of event listeners assigned to given name. - * If no arguments specified all the events will be cleared. - ** - > Arguments - ** - - name (string) name of the event, dot (`.`) or slash (`/`) separated, with optional wildcards - - f (function) event handler function - \*/ - /*\ - * eve.unbind - [ method ] - ** - * See @eve.off - \*/ - eve.off = eve.unbind = function (name, f) { - if (!name) { - eve._events = events = {n: {}}; - return; - } - var names = name.split(separator), - e, - key, - splice, - i, ii, j, jj, - cur = [events]; - for (i = 0, ii = names.length; i < ii; i++) { - for (j = 0; j < cur.length; j += splice.length - 2) { - splice = [j, 1]; - e = cur[j].n; - if (names[i] != wildcard) { - if (e[names[i]]) { - splice.push(e[names[i]]); - } - } else { - for (key in e) if (e[has](key)) { - splice.push(e[key]); - } - } - cur.splice.apply(cur, splice); - } - } - for (i = 0, ii = cur.length; i < ii; i++) { - e = cur[i]; - while (e.n) { - if (f) { - if (e.f) { - for (j = 0, jj = e.f.length; j < jj; j++) if (e.f[j] == f) { - e.f.splice(j, 1); - break; - } - !e.f.length && delete e.f; - } - for (key in e.n) if (e.n[has](key) && e.n[key].f) { - var funcs = e.n[key].f; - for (j = 0, jj = funcs.length; j < jj; j++) if (funcs[j] == f) { - funcs.splice(j, 1); - break; - } - !funcs.length && delete e.n[key].f; - } - } else { - delete e.f; - for (key in e.n) if (e.n[has](key) && e.n[key].f) { - delete e.n[key].f; - } - } - e = e.n; - } - } - }; - /*\ - * eve.once - [ method ] - ** - * Binds given event handler with a given name to only run once then unbind itself. - | eve.once("login", f); - | eve("login"); // triggers f - | eve("login"); // no listeners - * Use @eve to trigger the listener. - ** - > Arguments - ** - - name (string) name of the event, dot (`.`) or slash (`/`) separated, with optional wildcards - - f (function) event handler function - ** - = (function) same return function as @eve.on - \*/ - eve.once = function (name, f) { - var f2 = function () { - eve.unbind(name, f2); - return f.apply(this, arguments); - }; - return eve.on(name, f2); - }; - /*\ - * eve.version - [ property (string) ] - ** - * Current version of the library. - \*/ - eve.version = version; - eve.toString = function () { - return "You are running Eve " + version; - }; - (typeof module != "undefined" && module.exports) ? (module.exports = eve) : (typeof define != "undefined" ? (define("eve", [], function() { return eve; })) : (glob.eve = eve)); -})(window || this); -// ┌─────────────────────────────────────────────────────────────────────┐ \\ -// │ "Raphaël 2.1.2" - JavaScript Vector Library │ \\ -// ├─────────────────────────────────────────────────────────────────────┤ \\ -// │ Copyright (c) 2008-2011 Dmitry Baranovskiy (http://raphaeljs.com) │ \\ -// │ Copyright (c) 2008-2011 Sencha Labs (http://sencha.com) │ \\ -// │ Licensed under the MIT (http://raphaeljs.com/license.html) license. │ \\ -// └─────────────────────────────────────────────────────────────────────┘ \\ - -(function (glob, factory) { - // AMD support - if (typeof define === "function" && define.amd) { - // Define as an anonymous module - define(["eve"], function( eve ) { - return factory(glob, eve); - }); - } else { - // Browser globals (glob is window) - // Raphael adds itself to window - factory(glob, glob.eve || (typeof require == "function" && require('eve')) ); - } -}(this, function (window, eve) { - /*\ - * Raphael - [ method ] - ** - * Creates a canvas object on which to draw. - * You must do this first, as all future calls to drawing methods - * from this instance will be bound to this canvas. - > Parameters - ** - - container (HTMLElement|string) DOM element or its ID which is going to be a parent for drawing surface - - width (number) - - height (number) - - callback (function) #optional callback function which is going to be executed in the context of newly created paper - * or - - x (number) - - y (number) - - width (number) - - height (number) - - callback (function) #optional callback function which is going to be executed in the context of newly created paper - * or - - all (array) (first 3 or 4 elements in the array are equal to [containerID, width, height] or [x, y, width, height]. The rest are element descriptions in format {type: type, <attributes>}). See @Paper.add. - - callback (function) #optional callback function which is going to be executed in the context of newly created paper - * or - - onReadyCallback (function) function that is going to be called on DOM ready event. You can also subscribe to this event via Eve’s “DOMLoad” event. In this case method returns `undefined`. - = (object) @Paper - > Usage - | // Each of the following examples create a canvas - | // that is 320px wide by 200px high. - | // Canvas is created at the viewport’s 10,50 coordinate. - | var paper = Raphael(10, 50, 320, 200); - | // Canvas is created at the top left corner of the #notepad element - | // (or its top right corner in dir="rtl" elements) - | var paper = Raphael(document.getElementById("notepad"), 320, 200); - | // Same as above - | var paper = Raphael("notepad", 320, 200); - | // Image dump - | var set = Raphael(["notepad", 320, 200, { - | type: "rect", - | x: 10, - | y: 10, - | width: 25, - | height: 25, - | stroke: "#f00" - | }, { - | type: "text", - | x: 30, - | y: 40, - | text: "Dump" - | }]); - \*/ - function R(first) { - if (R.is(first, "function")) { - return loaded ? first() : eve.on("raphael.DOMload", first); - } else if (R.is(first, array)) { - return R._engine.create[apply](R, first.splice(0, 3 + R.is(first[0], nu))).add(first); - } else { - var args = Array.prototype.slice.call(arguments, 0); - if (R.is(args[args.length - 1], "function")) { - var f = args.pop(); - return loaded ? f.call(R._engine.create[apply](R, args)) : eve.on("raphael.DOMload", function () { - f.call(R._engine.create[apply](R, args)); - }); - } else { - return R._engine.create[apply](R, arguments); - } - } - } - R.version = "2.1.2"; - R.eve = eve; - var loaded, - separator = /[, ]+/, - elements = {circle: 1, rect: 1, path: 1, ellipse: 1, text: 1, image: 1}, - formatrg = /\{(\d+)\}/g, - proto = "prototype", - has = "hasOwnProperty", - g = { - doc: document, - win: window - }, - oldRaphael = { - was: Object.prototype[has].call(g.win, "Raphael"), - is: g.win.Raphael - }, - Paper = function () { - /*\ - * Paper.ca - [ property (object) ] - ** - * Shortcut for @Paper.customAttributes - \*/ - /*\ - * Paper.customAttributes - [ property (object) ] - ** - * If you have a set of attributes that you would like to represent - * as a function of some number you can do it easily with custom attributes: - > Usage - | paper.customAttributes.hue = function (num) { - | num = num % 1; - | return {fill: "hsb(" + num + ", 0.75, 1)"}; - | }; - | // Custom attribute “hue” will change fill - | // to be given hue with fixed saturation and brightness. - | // Now you can use it like this: - | var c = paper.circle(10, 10, 10).attr({hue: .45}); - | // or even like this: - | c.animate({hue: 1}, 1e3); - | - | // You could also create custom attribute - | // with multiple parameters: - | paper.customAttributes.hsb = function (h, s, b) { - | return {fill: "hsb(" + [h, s, b].join(",") + ")"}; - | }; - | c.attr({hsb: "0.5 .8 1"}); - | c.animate({hsb: [1, 0, 0.5]}, 1e3); - \*/ - this.ca = this.customAttributes = {}; - }, - paperproto, - appendChild = "appendChild", - apply = "apply", - concat = "concat", - supportsTouch = ('ontouchstart' in g.win) || g.win.DocumentTouch && g.doc instanceof DocumentTouch, //taken from Modernizr touch test - E = "", - S = " ", - Str = String, - split = "split", - events = "click dblclick mousedown mousemove mouseout mouseover mouseup touchstart touchmove touchend touchcancel"[split](S), - touchMap = { - mousedown: "touchstart", - mousemove: "touchmove", - mouseup: "touchend" - }, - lowerCase = Str.prototype.toLowerCase, - math = Math, - mmax = math.max, - mmin = math.min, - abs = math.abs, - pow = math.pow, - PI = math.PI, - nu = "number", - string = "string", - array = "array", - toString = "toString", - fillString = "fill", - objectToString = Object.prototype.toString, - paper = {}, - push = "push", - ISURL = R._ISURL = /^url\(['"]?(.+?)['"]?\)$/i, - colourRegExp = /^\s*((#[a-f\d]{6})|(#[a-f\d]{3})|rgba?\(\s*([\d\.]+%?\s*,\s*[\d\.]+%?\s*,\s*[\d\.]+%?(?:\s*,\s*[\d\.]+%?)?)\s*\)|hsba?\(\s*([\d\.]+(?:deg|\xb0|%)?\s*,\s*[\d\.]+%?\s*,\s*[\d\.]+(?:%?\s*,\s*[\d\.]+)?)%?\s*\)|hsla?\(\s*([\d\.]+(?:deg|\xb0|%)?\s*,\s*[\d\.]+%?\s*,\s*[\d\.]+(?:%?\s*,\s*[\d\.]+)?)%?\s*\))\s*$/i, - isnan = {"NaN": 1, "Infinity": 1, "-Infinity": 1}, - bezierrg = /^(?:cubic-)?bezier\(([^,]+),([^,]+),([^,]+),([^\)]+)\)/, - round = math.round, - setAttribute = "setAttribute", - toFloat = parseFloat, - toInt = parseInt, - upperCase = Str.prototype.toUpperCase, - availableAttrs = R._availableAttrs = { - "arrow-end": "none", - "arrow-start": "none", - blur: 0, - "clip-rect": "0 0 1e9 1e9", - cursor: "default", - cx: 0, - cy: 0, - fill: "#fff", - "fill-opacity": 1, - font: '10px "Arial"', - "font-family": '"Arial"', - "font-size": "10", - "font-style": "normal", - "font-weight": 400, - gradient: 0, - height: 0, - href: "http://raphaeljs.com/", - "letter-spacing": 0, - opacity: 1, - path: "M0,0", - r: 0, - rx: 0, - ry: 0, - src: "", - stroke: "#000", - "stroke-dasharray": "", - "stroke-linecap": "butt", - "stroke-linejoin": "butt", - "stroke-miterlimit": 0, - "stroke-opacity": 1, - "stroke-width": 1, - target: "_blank", - "text-anchor": "middle", - title: "Raphael", - transform: "", - width: 0, - x: 0, - y: 0 - }, - availableAnimAttrs = R._availableAnimAttrs = { - blur: nu, - "clip-rect": "csv", - cx: nu, - cy: nu, - fill: "colour", - "fill-opacity": nu, - "font-size": nu, - height: nu, - opacity: nu, - path: "path", - r: nu, - rx: nu, - ry: nu, - stroke: "colour", - "stroke-opacity": nu, - "stroke-width": nu, - transform: "transform", - width: nu, - x: nu, - y: nu - }, - whitespace = /[\x09\x0a\x0b\x0c\x0d\x20\xa0\u1680\u180e\u2000\u2001\u2002\u2003\u2004\u2005\u2006\u2007\u2008\u2009\u200a\u202f\u205f\u3000\u2028\u2029]/g, - commaSpaces = /[\x09\x0a\x0b\x0c\x0d\x20\xa0\u1680\u180e\u2000\u2001\u2002\u2003\u2004\u2005\u2006\u2007\u2008\u2009\u200a\u202f\u205f\u3000\u2028\u2029]*,[\x09\x0a\x0b\x0c\x0d\x20\xa0\u1680\u180e\u2000\u2001\u2002\u2003\u2004\u2005\u2006\u2007\u2008\u2009\u200a\u202f\u205f\u3000\u2028\u2029]*/, - hsrg = {hs: 1, rg: 1}, - p2s = /,?([achlmqrstvxz]),?/gi, - pathCommand = /([achlmrqstvz])[\x09\x0a\x0b\x0c\x0d\x20\xa0\u1680\u180e\u2000\u2001\u2002\u2003\u2004\u2005\u2006\u2007\u2008\u2009\u200a\u202f\u205f\u3000\u2028\u2029,]*((-?\d*\.?\d*(?:e[\-+]?\d+)?[\x09\x0a\x0b\x0c\x0d\x20\xa0\u1680\u180e\u2000\u2001\u2002\u2003\u2004\u2005\u2006\u2007\u2008\u2009\u200a\u202f\u205f\u3000\u2028\u2029]*,?[\x09\x0a\x0b\x0c\x0d\x20\xa0\u1680\u180e\u2000\u2001\u2002\u2003\u2004\u2005\u2006\u2007\u2008\u2009\u200a\u202f\u205f\u3000\u2028\u2029]*)+)/ig, - tCommand = /([rstm])[\x09\x0a\x0b\x0c\x0d\x20\xa0\u1680\u180e\u2000\u2001\u2002\u2003\u2004\u2005\u2006\u2007\u2008\u2009\u200a\u202f\u205f\u3000\u2028\u2029,]*((-?\d*\.?\d*(?:e[\-+]?\d+)?[\x09\x0a\x0b\x0c\x0d\x20\xa0\u1680\u180e\u2000\u2001\u2002\u2003\u2004\u2005\u2006\u2007\u2008\u2009\u200a\u202f\u205f\u3000\u2028\u2029]*,?[\x09\x0a\x0b\x0c\x0d\x20\xa0\u1680\u180e\u2000\u2001\u2002\u2003\u2004\u2005\u2006\u2007\u2008\u2009\u200a\u202f\u205f\u3000\u2028\u2029]*)+)/ig, - pathValues = /(-?\d*\.?\d*(?:e[\-+]?\d+)?)[\x09\x0a\x0b\x0c\x0d\x20\xa0\u1680\u180e\u2000\u2001\u2002\u2003\u2004\u2005\u2006\u2007\u2008\u2009\u200a\u202f\u205f\u3000\u2028\u2029]*,?[\x09\x0a\x0b\x0c\x0d\x20\xa0\u1680\u180e\u2000\u2001\u2002\u2003\u2004\u2005\u2006\u2007\u2008\u2009\u200a\u202f\u205f\u3000\u2028\u2029]*/ig, - radial_gradient = R._radial_gradient = /^r(?:\(([^,]+?)[\x09\x0a\x0b\x0c\x0d\x20\xa0\u1680\u180e\u2000\u2001\u2002\u2003\u2004\u2005\u2006\u2007\u2008\u2009\u200a\u202f\u205f\u3000\u2028\u2029]*,[\x09\x0a\x0b\x0c\x0d\x20\xa0\u1680\u180e\u2000\u2001\u2002\u2003\u2004\u2005\u2006\u2007\u2008\u2009\u200a\u202f\u205f\u3000\u2028\u2029]*([^\)]+?)\))?/, - eldata = {}, - sortByKey = function (a, b) { - return a.key - b.key; - }, - sortByNumber = function (a, b) { - return toFloat(a) - toFloat(b); - }, - fun = function () {}, - pipe = function (x) { - return x; - }, - rectPath = R._rectPath = function (x, y, w, h, r) { - if (r) { - return [["M", x + r, y], ["l", w - r * 2, 0], ["a", r, r, 0, 0, 1, r, r], ["l", 0, h - r * 2], ["a", r, r, 0, 0, 1, -r, r], ["l", r * 2 - w, 0], ["a", r, r, 0, 0, 1, -r, -r], ["l", 0, r * 2 - h], ["a", r, r, 0, 0, 1, r, -r], ["z"]]; - } - return [["M", x, y], ["l", w, 0], ["l", 0, h], ["l", -w, 0], ["z"]]; - }, - ellipsePath = function (x, y, rx, ry) { - if (ry == null) { - ry = rx; - } - return [["M", x, y], ["m", 0, -ry], ["a", rx, ry, 0, 1, 1, 0, 2 * ry], ["a", rx, ry, 0, 1, 1, 0, -2 * ry], ["z"]]; - }, - getPath = R._getPath = { - path: function (el) { - return el.attr("path"); - }, - circle: function (el) { - var a = el.attrs; - return ellipsePath(a.cx, a.cy, a.r); - }, - ellipse: function (el) { - var a = el.attrs; - return ellipsePath(a.cx, a.cy, a.rx, a.ry); - }, - rect: function (el) { - var a = el.attrs; - return rectPath(a.x, a.y, a.width, a.height, a.r); - }, - image: function (el) { - var a = el.attrs; - return rectPath(a.x, a.y, a.width, a.height); - }, - text: function (el) { - var bbox = el._getBBox(); - return rectPath(bbox.x, bbox.y, bbox.width, bbox.height); - }, - set : function(el) { - var bbox = el._getBBox(); - return rectPath(bbox.x, bbox.y, bbox.width, bbox.height); - } - }, - /*\ - * Raphael.mapPath - [ method ] - ** - * Transform the path string with given matrix. - > Parameters - - path (string) path string - - matrix (object) see @Matrix - = (string) transformed path string - \*/ - mapPath = R.mapPath = function (path, matrix) { - if (!matrix) { - return path; - } - var x, y, i, j, ii, jj, pathi; - path = path2curve(path); - for (i = 0, ii = path.length; i < ii; i++) { - pathi = path[i]; - for (j = 1, jj = pathi.length; j < jj; j += 2) { - x = matrix.x(pathi[j], pathi[j + 1]); - y = matrix.y(pathi[j], pathi[j + 1]); - pathi[j] = x; - pathi[j + 1] = y; - } - } - return path; - }; - - R._g = g; - /*\ - * Raphael.type - [ property (string) ] - ** - * Can be “SVG”, “VML” or empty, depending on browser support. - \*/ - R.type = (g.win.SVGAngle || g.doc.implementation.hasFeature("http://www.w3.org/TR/SVG11/feature#BasicStructure", "1.1") ? "SVG" : "VML"); - if (R.type == "VML") { - var d = g.doc.createElement("div"), - b; - d.innerHTML = '<v:shape adj="1"/>'; - b = d.firstChild; - b.style.behavior = "url(#default#VML)"; - if (!(b && typeof b.adj == "object")) { - return (R.type = E); - } - d = null; - } - /*\ - * Raphael.svg - [ property (boolean) ] - ** - * `true` if browser supports SVG. - \*/ - /*\ - * Raphael.vml - [ property (boolean) ] - ** - * `true` if browser supports VML. - \*/ - R.svg = !(R.vml = R.type == "VML"); - R._Paper = Paper; - /*\ - * Raphael.fn - [ property (object) ] - ** - * You can add your own method to the canvas. For example if you want to draw a pie chart, - * you can create your own pie chart function and ship it as a Raphaël plugin. To do this - * you need to extend the `Raphael.fn` object. You should modify the `fn` object before a - * Raphaël instance is created, otherwise it will take no effect. Please note that the - * ability for namespaced plugins was removed in Raphael 2.0. It is up to the plugin to - * ensure any namespacing ensures proper context. - > Usage - | Raphael.fn.arrow = function (x1, y1, x2, y2, size) { - | return this.path( ... ); - | }; - | // or create namespace - | Raphael.fn.mystuff = { - | arrow: function () {…}, - | star: function () {…}, - | // etc… - | }; - | var paper = Raphael(10, 10, 630, 480); - | // then use it - | paper.arrow(10, 10, 30, 30, 5).attr({fill: "#f00"}); - | paper.mystuff.arrow(); - | paper.mystuff.star(); - \*/ - R.fn = paperproto = Paper.prototype = R.prototype; - R._id = 0; - R._oid = 0; - /*\ - * Raphael.is - [ method ] - ** - * Handful of replacements for `typeof` operator. - > Parameters - - o (…) any object or primitive - - type (string) name of the type, i.e. “string”, “function”, “number”, etc. - = (boolean) is given value is of given type - \*/ - R.is = function (o, type) { - type = lowerCase.call(type); - if (type == "finite") { - return !isnan[has](+o); - } - if (type == "array") { - return o instanceof Array; - } - return (type == "null" && o === null) || - (type == typeof o && o !== null) || - (type == "object" && o === Object(o)) || - (type == "array" && Array.isArray && Array.isArray(o)) || - objectToString.call(o).slice(8, -1).toLowerCase() == type; - }; - - function clone(obj) { - if (typeof obj == "function" || Object(obj) !== obj) { - return obj; - } - var res = new obj.constructor; - for (var key in obj) if (obj[has](key)) { - res[key] = clone(obj[key]); - } - return res; - } - - /*\ - * Raphael.angle - [ method ] - ** - * Returns angle between two or three points - > Parameters - - x1 (number) x coord of first point - - y1 (number) y coord of first point - - x2 (number) x coord of second point - - y2 (number) y coord of second point - - x3 (number) #optional x coord of third point - - y3 (number) #optional y coord of third point - = (number) angle in degrees. - \*/ - R.angle = function (x1, y1, x2, y2, x3, y3) { - if (x3 == null) { - var x = x1 - x2, - y = y1 - y2; - if (!x && !y) { - return 0; - } - return (180 + math.atan2(-y, -x) * 180 / PI + 360) % 360; - } else { - return R.angle(x1, y1, x3, y3) - R.angle(x2, y2, x3, y3); - } - }; - /*\ - * Raphael.rad - [ method ] - ** - * Transform angle to radians - > Parameters - - deg (number) angle in degrees - = (number) angle in radians. - \*/ - R.rad = function (deg) { - return deg % 360 * PI / 180; - }; - /*\ - * Raphael.deg - [ method ] - ** - * Transform angle to degrees - > Parameters - - rad (number) angle in radians - = (number) angle in degrees. - \*/ - R.deg = function (rad) { - return Math.round ((rad * 180 / PI% 360)* 1000) / 1000; - }; - /*\ - * Raphael.snapTo - [ method ] - ** - * Snaps given value to given grid. - > Parameters - - values (array|number) given array of values or step of the grid - - value (number) value to adjust - - tolerance (number) #optional tolerance for snapping. Default is `10`. - = (number) adjusted value. - \*/ - R.snapTo = function (values, value, tolerance) { - tolerance = R.is(tolerance, "finite") ? tolerance : 10; - if (R.is(values, array)) { - var i = values.length; - while (i--) if (abs(values[i] - value) <= tolerance) { - return values[i]; - } - } else { - values = +values; - var rem = value % values; - if (rem < tolerance) { - return value - rem; - } - if (rem > values - tolerance) { - return value - rem + values; - } - } - return value; - }; - - /*\ - * Raphael.createUUID - [ method ] - ** - * Returns RFC4122, version 4 ID - \*/ - var createUUID = R.createUUID = (function (uuidRegEx, uuidReplacer) { - return function () { - return "xxxxxxxx-xxxx-4xxx-yxxx-xxxxxxxxxxxx".replace(uuidRegEx, uuidReplacer).toUpperCase(); - }; - })(/[xy]/g, function (c) { - var r = math.random() * 16 | 0, - v = c == "x" ? r : (r & 3 | 8); - return v.toString(16); - }); - - /*\ - * Raphael.setWindow - [ method ] - ** - * Used when you need to draw in `<iframe>`. Switched window to the iframe one. - > Parameters - - newwin (window) new window object - \*/ - R.setWindow = function (newwin) { - eve("raphael.setWindow", R, g.win, newwin); - g.win = newwin; - g.doc = g.win.document; - if (R._engine.initWin) { - R._engine.initWin(g.win); - } - }; - var toHex = function (color) { - if (R.vml) { - // http://dean.edwards.name/weblog/2009/10/convert-any-colour-value-to-hex-in-msie/ - var trim = /^\s+|\s+$/g; - var bod; - try { - var docum = new ActiveXObject("htmlfile"); - docum.write("<body>"); - docum.close(); - bod = docum.body; - } catch(e) { - bod = createPopup().document.body; - } - var range = bod.createTextRange(); - toHex = cacher(function (color) { - try { - bod.style.color = Str(color).replace(trim, E); - var value = range.queryCommandValue("ForeColor"); - value = ((value & 255) << 16) | (value & 65280) | ((value & 16711680) >>> 16); - return "#" + ("000000" + value.toString(16)).slice(-6); - } catch(e) { - return "none"; - } - }); - } else { - var i = g.doc.createElement("i"); - i.title = "Rapha\xebl Colour Picker"; - i.style.display = "none"; - g.doc.body.appendChild(i); - toHex = cacher(function (color) { - i.style.color = color; - return g.doc.defaultView.getComputedStyle(i, E).getPropertyValue("color"); - }); - } - return toHex(color); - }, - hsbtoString = function () { - return "hsb(" + [this.h, this.s, this.b] + ")"; - }, - hsltoString = function () { - return "hsl(" + [this.h, this.s, this.l] + ")"; - }, - rgbtoString = function () { - return this.hex; - }, - prepareRGB = function (r, g, b) { - if (g == null && R.is(r, "object") && "r" in r && "g" in r && "b" in r) { - b = r.b; - g = r.g; - r = r.r; - } - if (g == null && R.is(r, string)) { - var clr = R.getRGB(r); - r = clr.r; - g = clr.g; - b = clr.b; - } - if (r > 1 || g > 1 || b > 1) { - r /= 255; - g /= 255; - b /= 255; - } - - return [r, g, b]; - }, - packageRGB = function (r, g, b, o) { - r *= 255; - g *= 255; - b *= 255; - var rgb = { - r: r, - g: g, - b: b, - hex: R.rgb(r, g, b), - toString: rgbtoString - }; - R.is(o, "finite") && (rgb.opacity = o); - return rgb; - }; - - /*\ - * Raphael.color - [ method ] - ** - * Parses the color string and returns object with all values for the given color. - > Parameters - - clr (string) color string in one of the supported formats (see @Raphael.getRGB) - = (object) Combined RGB & HSB object in format: - o { - o r (number) red, - o g (number) green, - o b (number) blue, - o hex (string) color in HTML/CSS format: #••••••, - o error (boolean) `true` if string can’t be parsed, - o h (number) hue, - o s (number) saturation, - o v (number) value (brightness), - o l (number) lightness - o } - \*/ - R.color = function (clr) { - var rgb; - if (R.is(clr, "object") && "h" in clr && "s" in clr && "b" in clr) { - rgb = R.hsb2rgb(clr); - clr.r = rgb.r; - clr.g = rgb.g; - clr.b = rgb.b; - clr.hex = rgb.hex; - } else if (R.is(clr, "object") && "h" in clr && "s" in clr && "l" in clr) { - rgb = R.hsl2rgb(clr); - clr.r = rgb.r; - clr.g = rgb.g; - clr.b = rgb.b; - clr.hex = rgb.hex; - } else { - if (R.is(clr, "string")) { - clr = R.getRGB(clr); - } - if (R.is(clr, "object") && "r" in clr && "g" in clr && "b" in clr) { - rgb = R.rgb2hsl(clr); - clr.h = rgb.h; - clr.s = rgb.s; - clr.l = rgb.l; - rgb = R.rgb2hsb(clr); - clr.v = rgb.b; - } else { - clr = {hex: "none"}; - clr.r = clr.g = clr.b = clr.h = clr.s = clr.v = clr.l = -1; - } - } - clr.toString = rgbtoString; - return clr; - }; - /*\ - * Raphael.hsb2rgb - [ method ] - ** - * Converts HSB values to RGB object. - > Parameters - - h (number) hue - - s (number) saturation - - v (number) value or brightness - = (object) RGB object in format: - o { - o r (number) red, - o g (number) green, - o b (number) blue, - o hex (string) color in HTML/CSS format: #•••••• - o } - \*/ - R.hsb2rgb = function (h, s, v, o) { - if (this.is(h, "object") && "h" in h && "s" in h && "b" in h) { - v = h.b; - s = h.s; - o = h.o; - h = h.h; - } - h *= 360; - var R, G, B, X, C; - h = (h % 360) / 60; - C = v * s; - X = C * (1 - abs(h % 2 - 1)); - R = G = B = v - C; - - h = ~~h; - R += [C, X, 0, 0, X, C][h]; - G += [X, C, C, X, 0, 0][h]; - B += [0, 0, X, C, C, X][h]; - return packageRGB(R, G, B, o); - }; - /*\ - * Raphael.hsl2rgb - [ method ] - ** - * Converts HSL values to RGB object. - > Parameters - - h (number) hue - - s (number) saturation - - l (number) luminosity - = (object) RGB object in format: - o { - o r (number) red, - o g (number) green, - o b (number) blue, - o hex (string) color in HTML/CSS format: #•••••• - o } - \*/ - R.hsl2rgb = function (h, s, l, o) { - if (this.is(h, "object") && "h" in h && "s" in h && "l" in h) { - l = h.l; - s = h.s; - h = h.h; - } - if (h > 1 || s > 1 || l > 1) { - h /= 360; - s /= 100; - l /= 100; - } - h *= 360; - var R, G, B, X, C; - h = (h % 360) / 60; - C = 2 * s * (l < .5 ? l : 1 - l); - X = C * (1 - abs(h % 2 - 1)); - R = G = B = l - C / 2; - - h = ~~h; - R += [C, X, 0, 0, X, C][h]; - G += [X, C, C, X, 0, 0][h]; - B += [0, 0, X, C, C, X][h]; - return packageRGB(R, G, B, o); - }; - /*\ - * Raphael.rgb2hsb - [ method ] - ** - * Converts RGB values to HSB object. - > Parameters - - r (number) red - - g (number) green - - b (number) blue - = (object) HSB object in format: - o { - o h (number) hue - o s (number) saturation - o b (number) brightness - o } - \*/ - R.rgb2hsb = function (r, g, b) { - b = prepareRGB(r, g, b); - r = b[0]; - g = b[1]; - b = b[2]; - - var H, S, V, C; - V = mmax(r, g, b); - C = V - mmin(r, g, b); - H = (C == 0 ? null : - V == r ? (g - b) / C : - V == g ? (b - r) / C + 2 : - (r - g) / C + 4 - ); - H = ((H + 360) % 6) * 60 / 360; - S = C == 0 ? 0 : C / V; - return {h: H, s: S, b: V, toString: hsbtoString}; - }; - /*\ - * Raphael.rgb2hsl - [ method ] - ** - * Converts RGB values to HSL object. - > Parameters - - r (number) red - - g (number) green - - b (number) blue - = (object) HSL object in format: - o { - o h (number) hue - o s (number) saturation - o l (number) luminosity - o } - \*/ - R.rgb2hsl = function (r, g, b) { - b = prepareRGB(r, g, b); - r = b[0]; - g = b[1]; - b = b[2]; - - var H, S, L, M, m, C; - M = mmax(r, g, b); - m = mmin(r, g, b); - C = M - m; - H = (C == 0 ? null : - M == r ? (g - b) / C : - M == g ? (b - r) / C + 2 : - (r - g) / C + 4); - H = ((H + 360) % 6) * 60 / 360; - L = (M + m) / 2; - S = (C == 0 ? 0 : - L < .5 ? C / (2 * L) : - C / (2 - 2 * L)); - return {h: H, s: S, l: L, toString: hsltoString}; - }; - R._path2string = function () { - return this.join(",").replace(p2s, "$1"); - }; - function repush(array, item) { - for (var i = 0, ii = array.length; i < ii; i++) if (array[i] === item) { - return array.push(array.splice(i, 1)[0]); - } - } - function cacher(f, scope, postprocessor) { - function newf() { - var arg = Array.prototype.slice.call(arguments, 0), - args = arg.join("\u2400"), - cache = newf.cache = newf.cache || {}, - count = newf.count = newf.count || []; - if (cache[has](args)) { - repush(count, args); - return postprocessor ? postprocessor(cache[args]) : cache[args]; - } - count.length >= 1e3 && delete cache[count.shift()]; - count.push(args); - cache[args] = f[apply](scope, arg); - return postprocessor ? postprocessor(cache[args]) : cache[args]; - } - return newf; - } - - var preload = R._preload = function (src, f) { - var img = g.doc.createElement("img"); - img.style.cssText = "position:absolute;left:-9999em;top:-9999em"; - img.onload = function () { - f.call(this); - this.onload = null; - g.doc.body.removeChild(this); - }; - img.onerror = function () { - g.doc.body.removeChild(this); - }; - g.doc.body.appendChild(img); - img.src = src; - }; - - function clrToString() { - return this.hex; - } - - /*\ - * Raphael.getRGB - [ method ] - ** - * Parses colour string as RGB object - > Parameters - - colour (string) colour string in one of formats: - # <ul> - # <li>Colour name (“<code>red</code>”, “<code>green</code>”, “<code>cornflowerblue</code>”, etc)</li> - # <li>#••• — shortened HTML colour: (“<code>#000</code>”, “<code>#fc0</code>”, etc)</li> - # <li>#•••••• — full length HTML colour: (“<code>#000000</code>”, “<code>#bd2300</code>”)</li> - # <li>rgb(•••, •••, •••) — red, green and blue channels’ values: (“<code>rgb(200, 100, 0)</code>”)</li> - # <li>rgb(•••%, •••%, •••%) — same as above, but in %: (“<code>rgb(100%, 175%, 0%)</code>”)</li> - # <li>hsb(•••, •••, •••) — hue, saturation and brightness values: (“<code>hsb(0.5, 0.25, 1)</code>”)</li> - # <li>hsb(•••%, •••%, •••%) — same as above, but in %</li> - # <li>hsl(•••, •••, •••) — same as hsb</li> - # <li>hsl(•••%, •••%, •••%) — same as hsb</li> - # </ul> - = (object) RGB object in format: - o { - o r (number) red, - o g (number) green, - o b (number) blue - o hex (string) color in HTML/CSS format: #••••••, - o error (boolean) true if string can’t be parsed - o } - \*/ - R.getRGB = cacher(function (colour) { - if (!colour || !!((colour = Str(colour)).indexOf("-") + 1)) { - return {r: -1, g: -1, b: -1, hex: "none", error: 1, toString: clrToString}; - } - if (colour == "none") { - return {r: -1, g: -1, b: -1, hex: "none", toString: clrToString}; - } - !(hsrg[has](colour.toLowerCase().substring(0, 2)) || colour.charAt() == "#") && (colour = toHex(colour)); - var res, - red, - green, - blue, - opacity, - t, - values, - rgb = colour.match(colourRegExp); - if (rgb) { - if (rgb[2]) { - blue = toInt(rgb[2].substring(5), 16); - green = toInt(rgb[2].substring(3, 5), 16); - red = toInt(rgb[2].substring(1, 3), 16); - } - if (rgb[3]) { - blue = toInt((t = rgb[3].charAt(3)) + t, 16); - green = toInt((t = rgb[3].charAt(2)) + t, 16); - red = toInt((t = rgb[3].charAt(1)) + t, 16); - } - if (rgb[4]) { - values = rgb[4][split](commaSpaces); - red = toFloat(values[0]); - values[0].slice(-1) == "%" && (red *= 2.55); - green = toFloat(values[1]); - values[1].slice(-1) == "%" && (green *= 2.55); - blue = toFloat(values[2]); - values[2].slice(-1) == "%" && (blue *= 2.55); - rgb[1].toLowerCase().slice(0, 4) == "rgba" && (opacity = toFloat(values[3])); - values[3] && values[3].slice(-1) == "%" && (opacity /= 100); - } - if (rgb[5]) { - values = rgb[5][split](commaSpaces); - red = toFloat(values[0]); - values[0].slice(-1) == "%" && (red *= 2.55); - green = toFloat(values[1]); - values[1].slice(-1) == "%" && (green *= 2.55); - blue = toFloat(values[2]); - values[2].slice(-1) == "%" && (blue *= 2.55); - (values[0].slice(-3) == "deg" || values[0].slice(-1) == "\xb0") && (red /= 360); - rgb[1].toLowerCase().slice(0, 4) == "hsba" && (opacity = toFloat(values[3])); - values[3] && values[3].slice(-1) == "%" && (opacity /= 100); - return R.hsb2rgb(red, green, blue, opacity); - } - if (rgb[6]) { - values = rgb[6][split](commaSpaces); - red = toFloat(values[0]); - values[0].slice(-1) == "%" && (red *= 2.55); - green = toFloat(values[1]); - values[1].slice(-1) == "%" && (green *= 2.55); - blue = toFloat(values[2]); - values[2].slice(-1) == "%" && (blue *= 2.55); - (values[0].slice(-3) == "deg" || values[0].slice(-1) == "\xb0") && (red /= 360); - rgb[1].toLowerCase().slice(0, 4) == "hsla" && (opacity = toFloat(values[3])); - values[3] && values[3].slice(-1) == "%" && (opacity /= 100); - return R.hsl2rgb(red, green, blue, opacity); - } - rgb = {r: red, g: green, b: blue, toString: clrToString}; - rgb.hex = "#" + (16777216 | blue | (green << 8) | (red << 16)).toString(16).slice(1); - R.is(opacity, "finite") && (rgb.opacity = opacity); - return rgb; - } - return {r: -1, g: -1, b: -1, hex: "none", error: 1, toString: clrToString}; - }, R); - /*\ - * Raphael.hsb - [ method ] - ** - * Converts HSB values to hex representation of the colour. - > Parameters - - h (number) hue - - s (number) saturation - - b (number) value or brightness - = (string) hex representation of the colour. - \*/ - R.hsb = cacher(function (h, s, b) { - return R.hsb2rgb(h, s, b).hex; - }); - /*\ - * Raphael.hsl - [ method ] - ** - * Converts HSL values to hex representation of the colour. - > Parameters - - h (number) hue - - s (number) saturation - - l (number) luminosity - = (string) hex representation of the colour. - \*/ - R.hsl = cacher(function (h, s, l) { - return R.hsl2rgb(h, s, l).hex; - }); - /*\ - * Raphael.rgb - [ method ] - ** - * Converts RGB values to hex representation of the colour. - > Parameters - - r (number) red - - g (number) green - - b (number) blue - = (string) hex representation of the colour. - \*/ - R.rgb = cacher(function (r, g, b) { - return "#" + (16777216 | b | (g << 8) | (r << 16)).toString(16).slice(1); - }); - /*\ - * Raphael.getColor - [ method ] - ** - * On each call returns next colour in the spectrum. To reset it back to red call @Raphael.getColor.reset - > Parameters - - value (number) #optional brightness, default is `0.75` - = (string) hex representation of the colour. - \*/ - R.getColor = function (value) { - var start = this.getColor.start = this.getColor.start || {h: 0, s: 1, b: value || .75}, - rgb = this.hsb2rgb(start.h, start.s, start.b); - start.h += .075; - if (start.h > 1) { - start.h = 0; - start.s -= .2; - start.s <= 0 && (this.getColor.start = {h: 0, s: 1, b: start.b}); - } - return rgb.hex; - }; - /*\ - * Raphael.getColor.reset - [ method ] - ** - * Resets spectrum position for @Raphael.getColor back to red. - \*/ - R.getColor.reset = function () { - delete this.start; - }; - - // http://schepers.cc/getting-to-the-point - function catmullRom2bezier(crp, z) { - var d = []; - for (var i = 0, iLen = crp.length; iLen - 2 * !z > i; i += 2) { - var p = [ - {x: +crp[i - 2], y: +crp[i - 1]}, - {x: +crp[i], y: +crp[i + 1]}, - {x: +crp[i + 2], y: +crp[i + 3]}, - {x: +crp[i + 4], y: +crp[i + 5]} - ]; - if (z) { - if (!i) { - p[0] = {x: +crp[iLen - 2], y: +crp[iLen - 1]}; - } else if (iLen - 4 == i) { - p[3] = {x: +crp[0], y: +crp[1]}; - } else if (iLen - 2 == i) { - p[2] = {x: +crp[0], y: +crp[1]}; - p[3] = {x: +crp[2], y: +crp[3]}; - } - } else { - if (iLen - 4 == i) { - p[3] = p[2]; - } else if (!i) { - p[0] = {x: +crp[i], y: +crp[i + 1]}; - } - } - d.push(["C", - (-p[0].x + 6 * p[1].x + p[2].x) / 6, - (-p[0].y + 6 * p[1].y + p[2].y) / 6, - (p[1].x + 6 * p[2].x - p[3].x) / 6, - (p[1].y + 6*p[2].y - p[3].y) / 6, - p[2].x, - p[2].y - ]); - } - - return d; - } - /*\ - * Raphael.parsePathString - [ method ] - ** - * Utility method - ** - * Parses given path string into an array of arrays of path segments. - > Parameters - - pathString (string|array) path string or array of segments (in the last case it will be returned straight away) - = (array) array of segments. - \*/ - R.parsePathString = function (pathString) { - if (!pathString) { - return null; - } - var pth = paths(pathString); - if (pth.arr) { - return pathClone(pth.arr); - } - - var paramCounts = {a: 7, c: 6, h: 1, l: 2, m: 2, r: 4, q: 4, s: 4, t: 2, v: 1, z: 0}, - data = []; - if (R.is(pathString, array) && R.is(pathString[0], array)) { // rough assumption - data = pathClone(pathString); - } - if (!data.length) { - Str(pathString).replace(pathCommand, function (a, b, c) { - var params = [], - name = b.toLowerCase(); - c.replace(pathValues, function (a, b) { - b && params.push(+b); - }); - if (name == "m" && params.length > 2) { - data.push([b][concat](params.splice(0, 2))); - name = "l"; - b = b == "m" ? "l" : "L"; - } - if (name == "r") { - data.push([b][concat](params)); - } else while (params.length >= paramCounts[name]) { - data.push([b][concat](params.splice(0, paramCounts[name]))); - if (!paramCounts[name]) { - break; - } - } - }); - } - data.toString = R._path2string; - pth.arr = pathClone(data); - return data; - }; - /*\ - * Raphael.parseTransformString - [ method ] - ** - * Utility method - ** - * Parses given path string into an array of transformations. - > Parameters - - TString (string|array) transform string or array of transformations (in the last case it will be returned straight away) - = (array) array of transformations. - \*/ - R.parseTransformString = cacher(function (TString) { - if (!TString) { - return null; - } - var paramCounts = {r: 3, s: 4, t: 2, m: 6}, - data = []; - if (R.is(TString, array) && R.is(TString[0], array)) { // rough assumption - data = pathClone(TString); - } - if (!data.length) { - Str(TString).replace(tCommand, function (a, b, c) { - var params = [], - name = lowerCase.call(b); - c.replace(pathValues, function (a, b) { - b && params.push(+b); - }); - data.push([b][concat](params)); - }); - } - data.toString = R._path2string; - return data; - }); - // PATHS - var paths = function (ps) { - var p = paths.ps = paths.ps || {}; - if (p[ps]) { - p[ps].sleep = 100; - } else { - p[ps] = { - sleep: 100 - }; - } - setTimeout(function () { - for (var key in p) if (p[has](key) && key != ps) { - p[key].sleep--; - !p[key].sleep && delete p[key]; - } - }); - return p[ps]; - }; - /*\ - * Raphael.findDotsAtSegment - [ method ] - ** - * Utility method - ** - * Find dot coordinates on the given cubic bezier curve at the given t. - > Parameters - - p1x (number) x of the first point of the curve - - p1y (number) y of the first point of the curve - - c1x (number) x of the first anchor of the curve - - c1y (number) y of the first anchor of the curve - - c2x (number) x of the second anchor of the curve - - c2y (number) y of the second anchor of the curve - - p2x (number) x of the second point of the curve - - p2y (number) y of the second point of the curve - - t (number) position on the curve (0..1) - = (object) point information in format: - o { - o x: (number) x coordinate of the point - o y: (number) y coordinate of the point - o m: { - o x: (number) x coordinate of the left anchor - o y: (number) y coordinate of the left anchor - o } - o n: { - o x: (number) x coordinate of the right anchor - o y: (number) y coordinate of the right anchor - o } - o start: { - o x: (number) x coordinate of the start of the curve - o y: (number) y coordinate of the start of the curve - o } - o end: { - o x: (number) x coordinate of the end of the curve - o y: (number) y coordinate of the end of the curve - o } - o alpha: (number) angle of the curve derivative at the point - o } - \*/ - R.findDotsAtSegment = function (p1x, p1y, c1x, c1y, c2x, c2y, p2x, p2y, t) { - var t1 = 1 - t, - t13 = pow(t1, 3), - t12 = pow(t1, 2), - t2 = t * t, - t3 = t2 * t, - x = t13 * p1x + t12 * 3 * t * c1x + t1 * 3 * t * t * c2x + t3 * p2x, - y = t13 * p1y + t12 * 3 * t * c1y + t1 * 3 * t * t * c2y + t3 * p2y, - mx = p1x + 2 * t * (c1x - p1x) + t2 * (c2x - 2 * c1x + p1x), - my = p1y + 2 * t * (c1y - p1y) + t2 * (c2y - 2 * c1y + p1y), - nx = c1x + 2 * t * (c2x - c1x) + t2 * (p2x - 2 * c2x + c1x), - ny = c1y + 2 * t * (c2y - c1y) + t2 * (p2y - 2 * c2y + c1y), - ax = t1 * p1x + t * c1x, - ay = t1 * p1y + t * c1y, - cx = t1 * c2x + t * p2x, - cy = t1 * c2y + t * p2y, - alpha = (90 - math.atan2(mx - nx, my - ny) * 180 / PI); - (mx > nx || my < ny) && (alpha += 180); - return { - x: x, - y: y, - m: {x: mx, y: my}, - n: {x: nx, y: ny}, - start: {x: ax, y: ay}, - end: {x: cx, y: cy}, - alpha: alpha - }; - }; - /*\ - * Raphael.bezierBBox - [ method ] - ** - * Utility method - ** - * Return bounding box of a given cubic bezier curve - > Parameters - - p1x (number) x of the first point of the curve - - p1y (number) y of the first point of the curve - - c1x (number) x of the first anchor of the curve - - c1y (number) y of the first anchor of the curve - - c2x (number) x of the second anchor of the curve - - c2y (number) y of the second anchor of the curve - - p2x (number) x of the second point of the curve - - p2y (number) y of the second point of the curve - * or - - bez (array) array of six points for bezier curve - = (object) point information in format: - o { - o min: { - o x: (number) x coordinate of the left point - o y: (number) y coordinate of the top point - o } - o max: { - o x: (number) x coordinate of the right point - o y: (number) y coordinate of the bottom point - o } - o } - \*/ - R.bezierBBox = function (p1x, p1y, c1x, c1y, c2x, c2y, p2x, p2y) { - if (!R.is(p1x, "array")) { - p1x = [p1x, p1y, c1x, c1y, c2x, c2y, p2x, p2y]; - } - var bbox = curveDim.apply(null, p1x); - return { - x: bbox.min.x, - y: bbox.min.y, - x2: bbox.max.x, - y2: bbox.max.y, - width: bbox.max.x - bbox.min.x, - height: bbox.max.y - bbox.min.y - }; - }; - /*\ - * Raphael.isPointInsideBBox - [ method ] - ** - * Utility method - ** - * Returns `true` if given point is inside bounding boxes. - > Parameters - - bbox (string) bounding box - - x (string) x coordinate of the point - - y (string) y coordinate of the point - = (boolean) `true` if point inside - \*/ - R.isPointInsideBBox = function (bbox, x, y) { - return x >= bbox.x && x <= bbox.x2 && y >= bbox.y && y <= bbox.y2; - }; - /*\ - * Raphael.isBBoxIntersect - [ method ] - ** - * Utility method - ** - * Returns `true` if two bounding boxes intersect - > Parameters - - bbox1 (string) first bounding box - - bbox2 (string) second bounding box - = (boolean) `true` if they intersect - \*/ - R.isBBoxIntersect = function (bbox1, bbox2) { - var i = R.isPointInsideBBox; - return i(bbox2, bbox1.x, bbox1.y) - || i(bbox2, bbox1.x2, bbox1.y) - || i(bbox2, bbox1.x, bbox1.y2) - || i(bbox2, bbox1.x2, bbox1.y2) - || i(bbox1, bbox2.x, bbox2.y) - || i(bbox1, bbox2.x2, bbox2.y) - || i(bbox1, bbox2.x, bbox2.y2) - || i(bbox1, bbox2.x2, bbox2.y2) - || (bbox1.x < bbox2.x2 && bbox1.x > bbox2.x || bbox2.x < bbox1.x2 && bbox2.x > bbox1.x) - && (bbox1.y < bbox2.y2 && bbox1.y > bbox2.y || bbox2.y < bbox1.y2 && bbox2.y > bbox1.y); - }; - function base3(t, p1, p2, p3, p4) { - var t1 = -3 * p1 + 9 * p2 - 9 * p3 + 3 * p4, - t2 = t * t1 + 6 * p1 - 12 * p2 + 6 * p3; - return t * t2 - 3 * p1 + 3 * p2; - } - function bezlen(x1, y1, x2, y2, x3, y3, x4, y4, z) { - if (z == null) { - z = 1; - } - z = z > 1 ? 1 : z < 0 ? 0 : z; - var z2 = z / 2, - n = 12, - Tvalues = [-0.1252,0.1252,-0.3678,0.3678,-0.5873,0.5873,-0.7699,0.7699,-0.9041,0.9041,-0.9816,0.9816], - Cvalues = [0.2491,0.2491,0.2335,0.2335,0.2032,0.2032,0.1601,0.1601,0.1069,0.1069,0.0472,0.0472], - sum = 0; - for (var i = 0; i < n; i++) { - var ct = z2 * Tvalues[i] + z2, - xbase = base3(ct, x1, x2, x3, x4), - ybase = base3(ct, y1, y2, y3, y4), - comb = xbase * xbase + ybase * ybase; - sum += Cvalues[i] * math.sqrt(comb); - } - return z2 * sum; - } - function getTatLen(x1, y1, x2, y2, x3, y3, x4, y4, ll) { - if (ll < 0 || bezlen(x1, y1, x2, y2, x3, y3, x4, y4) < ll) { - return; - } - var t = 1, - step = t / 2, - t2 = t - step, - l, - e = .01; - l = bezlen(x1, y1, x2, y2, x3, y3, x4, y4, t2); - while (abs(l - ll) > e) { - step /= 2; - t2 += (l < ll ? 1 : -1) * step; - l = bezlen(x1, y1, x2, y2, x3, y3, x4, y4, t2); - } - return t2; - } - function intersect(x1, y1, x2, y2, x3, y3, x4, y4) { - if ( - mmax(x1, x2) < mmin(x3, x4) || - mmin(x1, x2) > mmax(x3, x4) || - mmax(y1, y2) < mmin(y3, y4) || - mmin(y1, y2) > mmax(y3, y4) - ) { - return; - } - var nx = (x1 * y2 - y1 * x2) * (x3 - x4) - (x1 - x2) * (x3 * y4 - y3 * x4), - ny = (x1 * y2 - y1 * x2) * (y3 - y4) - (y1 - y2) * (x3 * y4 - y3 * x4), - denominator = (x1 - x2) * (y3 - y4) - (y1 - y2) * (x3 - x4); - - if (!denominator) { - return; - } - var px = nx / denominator, - py = ny / denominator, - px2 = +px.toFixed(2), - py2 = +py.toFixed(2); - if ( - px2 < +mmin(x1, x2).toFixed(2) || - px2 > +mmax(x1, x2).toFixed(2) || - px2 < +mmin(x3, x4).toFixed(2) || - px2 > +mmax(x3, x4).toFixed(2) || - py2 < +mmin(y1, y2).toFixed(2) || - py2 > +mmax(y1, y2).toFixed(2) || - py2 < +mmin(y3, y4).toFixed(2) || - py2 > +mmax(y3, y4).toFixed(2) - ) { - return; - } - return {x: px, y: py}; - } - function inter(bez1, bez2) { - return interHelper(bez1, bez2); - } - function interCount(bez1, bez2) { - return interHelper(bez1, bez2, 1); - } - function interHelper(bez1, bez2, justCount) { - var bbox1 = R.bezierBBox(bez1), - bbox2 = R.bezierBBox(bez2); - if (!R.isBBoxIntersect(bbox1, bbox2)) { - return justCount ? 0 : []; - } - var l1 = bezlen.apply(0, bez1), - l2 = bezlen.apply(0, bez2), - n1 = mmax(~~(l1 / 5), 1), - n2 = mmax(~~(l2 / 5), 1), - dots1 = [], - dots2 = [], - xy = {}, - res = justCount ? 0 : []; - for (var i = 0; i < n1 + 1; i++) { - var p = R.findDotsAtSegment.apply(R, bez1.concat(i / n1)); - dots1.push({x: p.x, y: p.y, t: i / n1}); - } - for (i = 0; i < n2 + 1; i++) { - p = R.findDotsAtSegment.apply(R, bez2.concat(i / n2)); - dots2.push({x: p.x, y: p.y, t: i / n2}); - } - for (i = 0; i < n1; i++) { - for (var j = 0; j < n2; j++) { - var di = dots1[i], - di1 = dots1[i + 1], - dj = dots2[j], - dj1 = dots2[j + 1], - ci = abs(di1.x - di.x) < .001 ? "y" : "x", - cj = abs(dj1.x - dj.x) < .001 ? "y" : "x", - is = intersect(di.x, di.y, di1.x, di1.y, dj.x, dj.y, dj1.x, dj1.y); - if (is) { - if (xy[is.x.toFixed(4)] == is.y.toFixed(4)) { - continue; - } - xy[is.x.toFixed(4)] = is.y.toFixed(4); - var t1 = di.t + abs((is[ci] - di[ci]) / (di1[ci] - di[ci])) * (di1.t - di.t), - t2 = dj.t + abs((is[cj] - dj[cj]) / (dj1[cj] - dj[cj])) * (dj1.t - dj.t); - if (t1 >= 0 && t1 <= 1.001 && t2 >= 0 && t2 <= 1.001) { - if (justCount) { - res++; - } else { - res.push({ - x: is.x, - y: is.y, - t1: mmin(t1, 1), - t2: mmin(t2, 1) - }); - } - } - } - } - } - return res; - } - /*\ - * Raphael.pathIntersection - [ method ] - ** - * Utility method - ** - * Finds intersections of two paths - > Parameters - - path1 (string) path string - - path2 (string) path string - = (array) dots of intersection - o [ - o { - o x: (number) x coordinate of the point - o y: (number) y coordinate of the point - o t1: (number) t value for segment of path1 - o t2: (number) t value for segment of path2 - o segment1: (number) order number for segment of path1 - o segment2: (number) order number for segment of path2 - o bez1: (array) eight coordinates representing beziér curve for the segment of path1 - o bez2: (array) eight coordinates representing beziér curve for the segment of path2 - o } - o ] - \*/ - R.pathIntersection = function (path1, path2) { - return interPathHelper(path1, path2); - }; - R.pathIntersectionNumber = function (path1, path2) { - return interPathHelper(path1, path2, 1); - }; - function interPathHelper(path1, path2, justCount) { - path1 = R._path2curve(path1); - path2 = R._path2curve(path2); - var x1, y1, x2, y2, x1m, y1m, x2m, y2m, bez1, bez2, - res = justCount ? 0 : []; - for (var i = 0, ii = path1.length; i < ii; i++) { - var pi = path1[i]; - if (pi[0] == "M") { - x1 = x1m = pi[1]; - y1 = y1m = pi[2]; - } else { - if (pi[0] == "C") { - bez1 = [x1, y1].concat(pi.slice(1)); - x1 = bez1[6]; - y1 = bez1[7]; - } else { - bez1 = [x1, y1, x1, y1, x1m, y1m, x1m, y1m]; - x1 = x1m; - y1 = y1m; - } - for (var j = 0, jj = path2.length; j < jj; j++) { - var pj = path2[j]; - if (pj[0] == "M") { - x2 = x2m = pj[1]; - y2 = y2m = pj[2]; - } else { - if (pj[0] == "C") { - bez2 = [x2, y2].concat(pj.slice(1)); - x2 = bez2[6]; - y2 = bez2[7]; - } else { - bez2 = [x2, y2, x2, y2, x2m, y2m, x2m, y2m]; - x2 = x2m; - y2 = y2m; - } - var intr = interHelper(bez1, bez2, justCount); - if (justCount) { - res += intr; - } else { - for (var k = 0, kk = intr.length; k < kk; k++) { - intr[k].segment1 = i; - intr[k].segment2 = j; - intr[k].bez1 = bez1; - intr[k].bez2 = bez2; - } - res = res.concat(intr); - } - } - } - } - } - return res; - } - /*\ - * Raphael.isPointInsidePath - [ method ] - ** - * Utility method - ** - * Returns `true` if given point is inside a given closed path. - > Parameters - - path (string) path string - - x (number) x of the point - - y (number) y of the point - = (boolean) true, if point is inside the path - \*/ - R.isPointInsidePath = function (path, x, y) { - var bbox = R.pathBBox(path); - return R.isPointInsideBBox(bbox, x, y) && - interPathHelper(path, [["M", x, y], ["H", bbox.x2 + 10]], 1) % 2 == 1; - }; - R._removedFactory = function (methodname) { - return function () { - eve("raphael.log", null, "Rapha\xebl: you are calling to method \u201c" + methodname + "\u201d of removed object", methodname); - }; - }; - /*\ - * Raphael.pathBBox - [ method ] - ** - * Utility method - ** - * Return bounding box of a given path - > Parameters - - path (string) path string - = (object) bounding box - o { - o x: (number) x coordinate of the left top point of the box - o y: (number) y coordinate of the left top point of the box - o x2: (number) x coordinate of the right bottom point of the box - o y2: (number) y coordinate of the right bottom point of the box - o width: (number) width of the box - o height: (number) height of the box - o cx: (number) x coordinate of the center of the box - o cy: (number) y coordinate of the center of the box - o } - \*/ - var pathDimensions = R.pathBBox = function (path) { - var pth = paths(path); - if (pth.bbox) { - return clone(pth.bbox); - } - if (!path) { - return {x: 0, y: 0, width: 0, height: 0, x2: 0, y2: 0}; - } - path = path2curve(path); - var x = 0, - y = 0, - X = [], - Y = [], - p; - for (var i = 0, ii = path.length; i < ii; i++) { - p = path[i]; - if (p[0] == "M") { - x = p[1]; - y = p[2]; - X.push(x); - Y.push(y); - } else { - var dim = curveDim(x, y, p[1], p[2], p[3], p[4], p[5], p[6]); - X = X[concat](dim.min.x, dim.max.x); - Y = Y[concat](dim.min.y, dim.max.y); - x = p[5]; - y = p[6]; - } - } - var xmin = mmin[apply](0, X), - ymin = mmin[apply](0, Y), - xmax = mmax[apply](0, X), - ymax = mmax[apply](0, Y), - width = xmax - xmin, - height = ymax - ymin, - bb = { - x: xmin, - y: ymin, - x2: xmax, - y2: ymax, - width: width, - height: height, - cx: xmin + width / 2, - cy: ymin + height / 2 - }; - pth.bbox = clone(bb); - return bb; - }, - pathClone = function (pathArray) { - var res = clone(pathArray); - res.toString = R._path2string; - return res; - }, - pathToRelative = R._pathToRelative = function (pathArray) { - var pth = paths(pathArray); - if (pth.rel) { - return pathClone(pth.rel); - } - if (!R.is(pathArray, array) || !R.is(pathArray && pathArray[0], array)) { // rough assumption - pathArray = R.parsePathString(pathArray); - } - var res = [], - x = 0, - y = 0, - mx = 0, - my = 0, - start = 0; - if (pathArray[0][0] == "M") { - x = pathArray[0][1]; - y = pathArray[0][2]; - mx = x; - my = y; - start++; - res.push(["M", x, y]); - } - for (var i = start, ii = pathArray.length; i < ii; i++) { - var r = res[i] = [], - pa = pathArray[i]; - if (pa[0] != lowerCase.call(pa[0])) { - r[0] = lowerCase.call(pa[0]); - switch (r[0]) { - case "a": - r[1] = pa[1]; - r[2] = pa[2]; - r[3] = pa[3]; - r[4] = pa[4]; - r[5] = pa[5]; - r[6] = +(pa[6] - x).toFixed(3); - r[7] = +(pa[7] - y).toFixed(3); - break; - case "v": - r[1] = +(pa[1] - y).toFixed(3); - break; - case "m": - mx = pa[1]; - my = pa[2]; - default: - for (var j = 1, jj = pa.length; j < jj; j++) { - r[j] = +(pa[j] - ((j % 2) ? x : y)).toFixed(3); - } - } - } else { - r = res[i] = []; - if (pa[0] == "m") { - mx = pa[1] + x; - my = pa[2] + y; - } - for (var k = 0, kk = pa.length; k < kk; k++) { - res[i][k] = pa[k]; - } - } - var len = res[i].length; - switch (res[i][0]) { - case "z": - x = mx; - y = my; - break; - case "h": - x += +res[i][len - 1]; - break; - case "v": - y += +res[i][len - 1]; - break; - default: - x += +res[i][len - 2]; - y += +res[i][len - 1]; - } - } - res.toString = R._path2string; - pth.rel = pathClone(res); - return res; - }, - pathToAbsolute = R._pathToAbsolute = function (pathArray) { - var pth = paths(pathArray); - if (pth.abs) { - return pathClone(pth.abs); - } - if (!R.is(pathArray, array) || !R.is(pathArray && pathArray[0], array)) { // rough assumption - pathArray = R.parsePathString(pathArray); - } - if (!pathArray || !pathArray.length) { - return [["M", 0, 0]]; - } - var res = [], - x = 0, - y = 0, - mx = 0, - my = 0, - start = 0; - if (pathArray[0][0] == "M") { - x = +pathArray[0][1]; - y = +pathArray[0][2]; - mx = x; - my = y; - start++; - res[0] = ["M", x, y]; - } - var crz = pathArray.length == 3 && pathArray[0][0] == "M" && pathArray[1][0].toUpperCase() == "R" && pathArray[2][0].toUpperCase() == "Z"; - for (var r, pa, i = start, ii = pathArray.length; i < ii; i++) { - res.push(r = []); - pa = pathArray[i]; - if (pa[0] != upperCase.call(pa[0])) { - r[0] = upperCase.call(pa[0]); - switch (r[0]) { - case "A": - r[1] = pa[1]; - r[2] = pa[2]; - r[3] = pa[3]; - r[4] = pa[4]; - r[5] = pa[5]; - r[6] = +(pa[6] + x); - r[7] = +(pa[7] + y); - break; - case "V": - r[1] = +pa[1] + y; - break; - case "H": - r[1] = +pa[1] + x; - break; - case "R": - var dots = [x, y][concat](pa.slice(1)); - for (var j = 2, jj = dots.length; j < jj; j++) { - dots[j] = +dots[j] + x; - dots[++j] = +dots[j] + y; - } - res.pop(); - res = res[concat](catmullRom2bezier(dots, crz)); - break; - case "M": - mx = +pa[1] + x; - my = +pa[2] + y; - default: - for (j = 1, jj = pa.length; j < jj; j++) { - r[j] = +pa[j] + ((j % 2) ? x : y); - } - } - } else if (pa[0] == "R") { - dots = [x, y][concat](pa.slice(1)); - res.pop(); - res = res[concat](catmullRom2bezier(dots, crz)); - r = ["R"][concat](pa.slice(-2)); - } else { - for (var k = 0, kk = pa.length; k < kk; k++) { - r[k] = pa[k]; - } - } - switch (r[0]) { - case "Z": - x = mx; - y = my; - break; - case "H": - x = r[1]; - break; - case "V": - y = r[1]; - break; - case "M": - mx = r[r.length - 2]; - my = r[r.length - 1]; - default: - x = r[r.length - 2]; - y = r[r.length - 1]; - } - } - res.toString = R._path2string; - pth.abs = pathClone(res); - return res; - }, - l2c = function (x1, y1, x2, y2) { - return [x1, y1, x2, y2, x2, y2]; - }, - q2c = function (x1, y1, ax, ay, x2, y2) { - var _13 = 1 / 3, - _23 = 2 / 3; - return [ - _13 * x1 + _23 * ax, - _13 * y1 + _23 * ay, - _13 * x2 + _23 * ax, - _13 * y2 + _23 * ay, - x2, - y2 - ]; - }, - a2c = function (x1, y1, rx, ry, angle, large_arc_flag, sweep_flag, x2, y2, recursive) { - // for more information of where this math came from visit: - // http://www.w3.org/TR/SVG11/implnote.html#ArcImplementationNotes - var _120 = PI * 120 / 180, - rad = PI / 180 * (+angle || 0), - res = [], - xy, - rotate = cacher(function (x, y, rad) { - var X = x * math.cos(rad) - y * math.sin(rad), - Y = x * math.sin(rad) + y * math.cos(rad); - return {x: X, y: Y}; - }); - if (!recursive) { - xy = rotate(x1, y1, -rad); - x1 = xy.x; - y1 = xy.y; - xy = rotate(x2, y2, -rad); - x2 = xy.x; - y2 = xy.y; - var cos = math.cos(PI / 180 * angle), - sin = math.sin(PI / 180 * angle), - x = (x1 - x2) / 2, - y = (y1 - y2) / 2; - var h = (x * x) / (rx * rx) + (y * y) / (ry * ry); - if (h > 1) { - h = math.sqrt(h); - rx = h * rx; - ry = h * ry; - } - var rx2 = rx * rx, - ry2 = ry * ry, - k = (large_arc_flag == sweep_flag ? -1 : 1) * - math.sqrt(abs((rx2 * ry2 - rx2 * y * y - ry2 * x * x) / (rx2 * y * y + ry2 * x * x))), - cx = k * rx * y / ry + (x1 + x2) / 2, - cy = k * -ry * x / rx + (y1 + y2) / 2, - f1 = math.asin(((y1 - cy) / ry).toFixed(9)), - f2 = math.asin(((y2 - cy) / ry).toFixed(9)); - - f1 = x1 < cx ? PI - f1 : f1; - f2 = x2 < cx ? PI - f2 : f2; - f1 < 0 && (f1 = PI * 2 + f1); - f2 < 0 && (f2 = PI * 2 + f2); - if (sweep_flag && f1 > f2) { - f1 = f1 - PI * 2; - } - if (!sweep_flag && f2 > f1) { - f2 = f2 - PI * 2; - } - } else { - f1 = recursive[0]; - f2 = recursive[1]; - cx = recursive[2]; - cy = recursive[3]; - } - var df = f2 - f1; - if (abs(df) > _120) { - var f2old = f2, - x2old = x2, - y2old = y2; - f2 = f1 + _120 * (sweep_flag && f2 > f1 ? 1 : -1); - x2 = cx + rx * math.cos(f2); - y2 = cy + ry * math.sin(f2); - res = a2c(x2, y2, rx, ry, angle, 0, sweep_flag, x2old, y2old, [f2, f2old, cx, cy]); - } - df = f2 - f1; - var c1 = math.cos(f1), - s1 = math.sin(f1), - c2 = math.cos(f2), - s2 = math.sin(f2), - t = math.tan(df / 4), - hx = 4 / 3 * rx * t, - hy = 4 / 3 * ry * t, - m1 = [x1, y1], - m2 = [x1 + hx * s1, y1 - hy * c1], - m3 = [x2 + hx * s2, y2 - hy * c2], - m4 = [x2, y2]; - m2[0] = 2 * m1[0] - m2[0]; - m2[1] = 2 * m1[1] - m2[1]; - if (recursive) { - return [m2, m3, m4][concat](res); - } else { - res = [m2, m3, m4][concat](res).join()[split](","); - var newres = []; - for (var i = 0, ii = res.length; i < ii; i++) { - newres[i] = i % 2 ? rotate(res[i - 1], res[i], rad).y : rotate(res[i], res[i + 1], rad).x; - } - return newres; - } - }, - findDotAtSegment = function (p1x, p1y, c1x, c1y, c2x, c2y, p2x, p2y, t) { - var t1 = 1 - t; - return { - x: pow(t1, 3) * p1x + pow(t1, 2) * 3 * t * c1x + t1 * 3 * t * t * c2x + pow(t, 3) * p2x, - y: pow(t1, 3) * p1y + pow(t1, 2) * 3 * t * c1y + t1 * 3 * t * t * c2y + pow(t, 3) * p2y - }; - }, - curveDim = cacher(function (p1x, p1y, c1x, c1y, c2x, c2y, p2x, p2y) { - var a = (c2x - 2 * c1x + p1x) - (p2x - 2 * c2x + c1x), - b = 2 * (c1x - p1x) - 2 * (c2x - c1x), - c = p1x - c1x, - t1 = (-b + math.sqrt(b * b - 4 * a * c)) / 2 / a, - t2 = (-b - math.sqrt(b * b - 4 * a * c)) / 2 / a, - y = [p1y, p2y], - x = [p1x, p2x], - dot; - abs(t1) > "1e12" && (t1 = .5); - abs(t2) > "1e12" && (t2 = .5); - if (t1 > 0 && t1 < 1) { - dot = findDotAtSegment(p1x, p1y, c1x, c1y, c2x, c2y, p2x, p2y, t1); - x.push(dot.x); - y.push(dot.y); - } - if (t2 > 0 && t2 < 1) { - dot = findDotAtSegment(p1x, p1y, c1x, c1y, c2x, c2y, p2x, p2y, t2); - x.push(dot.x); - y.push(dot.y); - } - a = (c2y - 2 * c1y + p1y) - (p2y - 2 * c2y + c1y); - b = 2 * (c1y - p1y) - 2 * (c2y - c1y); - c = p1y - c1y; - t1 = (-b + math.sqrt(b * b - 4 * a * c)) / 2 / a; - t2 = (-b - math.sqrt(b * b - 4 * a * c)) / 2 / a; - abs(t1) > "1e12" && (t1 = .5); - abs(t2) > "1e12" && (t2 = .5); - if (t1 > 0 && t1 < 1) { - dot = findDotAtSegment(p1x, p1y, c1x, c1y, c2x, c2y, p2x, p2y, t1); - x.push(dot.x); - y.push(dot.y); - } - if (t2 > 0 && t2 < 1) { - dot = findDotAtSegment(p1x, p1y, c1x, c1y, c2x, c2y, p2x, p2y, t2); - x.push(dot.x); - y.push(dot.y); - } - return { - min: {x: mmin[apply](0, x), y: mmin[apply](0, y)}, - max: {x: mmax[apply](0, x), y: mmax[apply](0, y)} - }; - }), - path2curve = R._path2curve = cacher(function (path, path2) { - var pth = !path2 && paths(path); - if (!path2 && pth.curve) { - return pathClone(pth.curve); - } - var p = pathToAbsolute(path), - p2 = path2 && pathToAbsolute(path2), - attrs = {x: 0, y: 0, bx: 0, by: 0, X: 0, Y: 0, qx: null, qy: null}, - attrs2 = {x: 0, y: 0, bx: 0, by: 0, X: 0, Y: 0, qx: null, qy: null}, - processPath = function (path, d, pcom) { - var nx, ny, tq = {T:1, Q:1}; - if (!path) { - return ["C", d.x, d.y, d.x, d.y, d.x, d.y]; - } - !(path[0] in tq) && (d.qx = d.qy = null); - switch (path[0]) { - case "M": - d.X = path[1]; - d.Y = path[2]; - break; - case "A": - path = ["C"][concat](a2c[apply](0, [d.x, d.y][concat](path.slice(1)))); - break; - case "S": - if (pcom == "C" || pcom == "S") { // In "S" case we have to take into account, if the previous command is C/S. - nx = d.x * 2 - d.bx; // And reflect the previous - ny = d.y * 2 - d.by; // command's control point relative to the current point. - } - else { // or some else or nothing - nx = d.x; - ny = d.y; - } - path = ["C", nx, ny][concat](path.slice(1)); - break; - case "T": - if (pcom == "Q" || pcom == "T") { // In "T" case we have to take into account, if the previous command is Q/T. - d.qx = d.x * 2 - d.qx; // And make a reflection similar - d.qy = d.y * 2 - d.qy; // to case "S". - } - else { // or something else or nothing - d.qx = d.x; - d.qy = d.y; - } - path = ["C"][concat](q2c(d.x, d.y, d.qx, d.qy, path[1], path[2])); - break; - case "Q": - d.qx = path[1]; - d.qy = path[2]; - path = ["C"][concat](q2c(d.x, d.y, path[1], path[2], path[3], path[4])); - break; - case "L": - path = ["C"][concat](l2c(d.x, d.y, path[1], path[2])); - break; - case "H": - path = ["C"][concat](l2c(d.x, d.y, path[1], d.y)); - break; - case "V": - path = ["C"][concat](l2c(d.x, d.y, d.x, path[1])); - break; - case "Z": - path = ["C"][concat](l2c(d.x, d.y, d.X, d.Y)); - break; - } - return path; - }, - fixArc = function (pp, i) { - if (pp[i].length > 7) { - pp[i].shift(); - var pi = pp[i]; - while (pi.length) { - pcoms1[i]="A"; // if created multiple C:s, their original seg is saved - p2 && (pcoms2[i]="A"); // the same as above - pp.splice(i++, 0, ["C"][concat](pi.splice(0, 6))); - } - pp.splice(i, 1); - ii = mmax(p.length, p2 && p2.length || 0); - } - }, - fixM = function (path1, path2, a1, a2, i) { - if (path1 && path2 && path1[i][0] == "M" && path2[i][0] != "M") { - path2.splice(i, 0, ["M", a2.x, a2.y]); - a1.bx = 0; - a1.by = 0; - a1.x = path1[i][1]; - a1.y = path1[i][2]; - ii = mmax(p.length, p2 && p2.length || 0); - } - }, - pcoms1 = [], // path commands of original path p - pcoms2 = [], // path commands of original path p2 - pfirst = "", // temporary holder for original path command - pcom = ""; // holder for previous path command of original path - for (var i = 0, ii = mmax(p.length, p2 && p2.length || 0); i < ii; i++) { - p[i] && (pfirst = p[i][0]); // save current path command - - if (pfirst != "C") // C is not saved yet, because it may be result of conversion - { - pcoms1[i] = pfirst; // Save current path command - i && ( pcom = pcoms1[i-1]); // Get previous path command pcom - } - p[i] = processPath(p[i], attrs, pcom); // Previous path command is inputted to processPath - - if (pcoms1[i] != "A" && pfirst == "C") pcoms1[i] = "C"; // A is the only command - // which may produce multiple C:s - // so we have to make sure that C is also C in original path - - fixArc(p, i); // fixArc adds also the right amount of A:s to pcoms1 - - if (p2) { // the same procedures is done to p2 - p2[i] && (pfirst = p2[i][0]); - if (pfirst != "C") - { - pcoms2[i] = pfirst; - i && (pcom = pcoms2[i-1]); - } - p2[i] = processPath(p2[i], attrs2, pcom); - - if (pcoms2[i]!="A" && pfirst=="C") pcoms2[i]="C"; - - fixArc(p2, i); - } - fixM(p, p2, attrs, attrs2, i); - fixM(p2, p, attrs2, attrs, i); - var seg = p[i], - seg2 = p2 && p2[i], - seglen = seg.length, - seg2len = p2 && seg2.length; - attrs.x = seg[seglen - 2]; - attrs.y = seg[seglen - 1]; - attrs.bx = toFloat(seg[seglen - 4]) || attrs.x; - attrs.by = toFloat(seg[seglen - 3]) || attrs.y; - attrs2.bx = p2 && (toFloat(seg2[seg2len - 4]) || attrs2.x); - attrs2.by = p2 && (toFloat(seg2[seg2len - 3]) || attrs2.y); - attrs2.x = p2 && seg2[seg2len - 2]; - attrs2.y = p2 && seg2[seg2len - 1]; - } - if (!p2) { - pth.curve = pathClone(p); - } - return p2 ? [p, p2] : p; - }, null, pathClone), - parseDots = R._parseDots = cacher(function (gradient) { - var dots = []; - for (var i = 0, ii = gradient.length; i < ii; i++) { - var dot = {}, - par = gradient[i].match(/^([^:]*):?([\d\.]*)/); - dot.color = R.getRGB(par[1]); - if (dot.color.error) { - return null; - } - dot.color = dot.color.hex; - par[2] && (dot.offset = par[2] + "%"); - dots.push(dot); - } - for (i = 1, ii = dots.length - 1; i < ii; i++) { - if (!dots[i].offset) { - var start = toFloat(dots[i - 1].offset || 0), - end = 0; - for (var j = i + 1; j < ii; j++) { - if (dots[j].offset) { - end = dots[j].offset; - break; - } - } - if (!end) { - end = 100; - j = ii; - } - end = toFloat(end); - var d = (end - start) / (j - i + 1); - for (; i < j; i++) { - start += d; - dots[i].offset = start + "%"; - } - } - } - return dots; - }), - tear = R._tear = function (el, paper) { - el == paper.top && (paper.top = el.prev); - el == paper.bottom && (paper.bottom = el.next); - el.next && (el.next.prev = el.prev); - el.prev && (el.prev.next = el.next); - }, - tofront = R._tofront = function (el, paper) { - if (paper.top === el) { - return; - } - tear(el, paper); - el.next = null; - el.prev = paper.top; - paper.top.next = el; - paper.top = el; - }, - toback = R._toback = function (el, paper) { - if (paper.bottom === el) { - return; - } - tear(el, paper); - el.next = paper.bottom; - el.prev = null; - paper.bottom.prev = el; - paper.bottom = el; - }, - insertafter = R._insertafter = function (el, el2, paper) { - tear(el, paper); - el2 == paper.top && (paper.top = el); - el2.next && (el2.next.prev = el); - el.next = el2.next; - el.prev = el2; - el2.next = el; - }, - insertbefore = R._insertbefore = function (el, el2, paper) { - tear(el, paper); - el2 == paper.bottom && (paper.bottom = el); - el2.prev && (el2.prev.next = el); - el.prev = el2.prev; - el2.prev = el; - el.next = el2; - }, - /*\ - * Raphael.toMatrix - [ method ] - ** - * Utility method - ** - * Returns matrix of transformations applied to a given path - > Parameters - - path (string) path string - - transform (string|array) transformation string - = (object) @Matrix - \*/ - toMatrix = R.toMatrix = function (path, transform) { - var bb = pathDimensions(path), - el = { - _: { - transform: E - }, - getBBox: function () { - return bb; - } - }; - extractTransform(el, transform); - return el.matrix; - }, - /*\ - * Raphael.transformPath - [ method ] - ** - * Utility method - ** - * Returns path transformed by a given transformation - > Parameters - - path (string) path string - - transform (string|array) transformation string - = (string) path - \*/ - transformPath = R.transformPath = function (path, transform) { - return mapPath(path, toMatrix(path, transform)); - }, - extractTransform = R._extractTransform = function (el, tstr) { - if (tstr == null) { - return el._.transform; - } - tstr = Str(tstr).replace(/\.{3}|\u2026/g, el._.transform || E); - var tdata = R.parseTransformString(tstr), - deg = 0, - dx = 0, - dy = 0, - sx = 1, - sy = 1, - _ = el._, - m = new Matrix; - _.transform = tdata || []; - if (tdata) { - for (var i = 0, ii = tdata.length; i < ii; i++) { - var t = tdata[i], - tlen = t.length, - command = Str(t[0]).toLowerCase(), - absolute = t[0] != command, - inver = absolute ? m.invert() : 0, - x1, - y1, - x2, - y2, - bb; - if (command == "t" && tlen == 3) { - if (absolute) { - x1 = inver.x(0, 0); - y1 = inver.y(0, 0); - x2 = inver.x(t[1], t[2]); - y2 = inver.y(t[1], t[2]); - m.translate(x2 - x1, y2 - y1); - } else { - m.translate(t[1], t[2]); - } - } else if (command == "r") { - if (tlen == 2) { - bb = bb || el.getBBox(1); - m.rotate(t[1], bb.x + bb.width / 2, bb.y + bb.height / 2); - deg += t[1]; - } else if (tlen == 4) { - if (absolute) { - x2 = inver.x(t[2], t[3]); - y2 = inver.y(t[2], t[3]); - m.rotate(t[1], x2, y2); - } else { - m.rotate(t[1], t[2], t[3]); - } - deg += t[1]; - } - } else if (command == "s") { - if (tlen == 2 || tlen == 3) { - bb = bb || el.getBBox(1); - m.scale(t[1], t[tlen - 1], bb.x + bb.width / 2, bb.y + bb.height / 2); - sx *= t[1]; - sy *= t[tlen - 1]; - } else if (tlen == 5) { - if (absolute) { - x2 = inver.x(t[3], t[4]); - y2 = inver.y(t[3], t[4]); - m.scale(t[1], t[2], x2, y2); - } else { - m.scale(t[1], t[2], t[3], t[4]); - } - sx *= t[1]; - sy *= t[2]; - } - } else if (command == "m" && tlen == 7) { - m.add(t[1], t[2], t[3], t[4], t[5], t[6]); - } - _.dirtyT = 1; - el.matrix = m; - } - } - - /*\ - * Element.matrix - [ property (object) ] - ** - * Keeps @Matrix object, which represents element transformation - \*/ - el.matrix = m; - - _.sx = sx; - _.sy = sy; - _.deg = deg; - _.dx = dx = m.e; - _.dy = dy = m.f; - - if (sx == 1 && sy == 1 && !deg && _.bbox) { - _.bbox.x += +dx; - _.bbox.y += +dy; - } else { - _.dirtyT = 1; - } - }, - getEmpty = function (item) { - var l = item[0]; - switch (l.toLowerCase()) { - case "t": return [l, 0, 0]; - case "m": return [l, 1, 0, 0, 1, 0, 0]; - case "r": if (item.length == 4) { - return [l, 0, item[2], item[3]]; - } else { - return [l, 0]; - } - case "s": if (item.length == 5) { - return [l, 1, 1, item[3], item[4]]; - } else if (item.length == 3) { - return [l, 1, 1]; - } else { - return [l, 1]; - } - } - }, - equaliseTransform = R._equaliseTransform = function (t1, t2) { - t2 = Str(t2).replace(/\.{3}|\u2026/g, t1); - t1 = R.parseTransformString(t1) || []; - t2 = R.parseTransformString(t2) || []; - var maxlength = mmax(t1.length, t2.length), - from = [], - to = [], - i = 0, j, jj, - tt1, tt2; - for (; i < maxlength; i++) { - tt1 = t1[i] || getEmpty(t2[i]); - tt2 = t2[i] || getEmpty(tt1); - if ((tt1[0] != tt2[0]) || - (tt1[0].toLowerCase() == "r" && (tt1[2] != tt2[2] || tt1[3] != tt2[3])) || - (tt1[0].toLowerCase() == "s" && (tt1[3] != tt2[3] || tt1[4] != tt2[4])) - ) { - return; - } - from[i] = []; - to[i] = []; - for (j = 0, jj = mmax(tt1.length, tt2.length); j < jj; j++) { - j in tt1 && (from[i][j] = tt1[j]); - j in tt2 && (to[i][j] = tt2[j]); - } - } - return { - from: from, - to: to - }; - }; - R._getContainer = function (x, y, w, h) { - var container; - container = h == null && !R.is(x, "object") ? g.doc.getElementById(x) : x; - if (container == null) { - return; - } - if (container.tagName) { - if (y == null) { - return { - container: container, - width: container.style.pixelWidth || container.offsetWidth, - height: container.style.pixelHeight || container.offsetHeight - }; - } else { - return { - container: container, - width: y, - height: w - }; - } - } - return { - container: 1, - x: x, - y: y, - width: w, - height: h - }; - }; - /*\ - * Raphael.pathToRelative - [ method ] - ** - * Utility method - ** - * Converts path to relative form - > Parameters - - pathString (string|array) path string or array of segments - = (array) array of segments. - \*/ - R.pathToRelative = pathToRelative; - R._engine = {}; - /*\ - * Raphael.path2curve - [ method ] - ** - * Utility method - ** - * Converts path to a new path where all segments are cubic bezier curves. - > Parameters - - pathString (string|array) path string or array of segments - = (array) array of segments. - \*/ - R.path2curve = path2curve; - /*\ - * Raphael.matrix - [ method ] - ** - * Utility method - ** - * Returns matrix based on given parameters. - > Parameters - - a (number) - - b (number) - - c (number) - - d (number) - - e (number) - - f (number) - = (object) @Matrix - \*/ - R.matrix = function (a, b, c, d, e, f) { - return new Matrix(a, b, c, d, e, f); - }; - function Matrix(a, b, c, d, e, f) { - if (a != null) { - this.a = +a; - this.b = +b; - this.c = +c; - this.d = +d; - this.e = +e; - this.f = +f; - } else { - this.a = 1; - this.b = 0; - this.c = 0; - this.d = 1; - this.e = 0; - this.f = 0; - } - } - (function (matrixproto) { - /*\ - * Matrix.add - [ method ] - ** - * Adds given matrix to existing one. - > Parameters - - a (number) - - b (number) - - c (number) - - d (number) - - e (number) - - f (number) - or - - matrix (object) @Matrix - \*/ - matrixproto.add = function (a, b, c, d, e, f) { - var out = [[], [], []], - m = [[this.a, this.c, this.e], [this.b, this.d, this.f], [0, 0, 1]], - matrix = [[a, c, e], [b, d, f], [0, 0, 1]], - x, y, z, res; - - if (a && a instanceof Matrix) { - matrix = [[a.a, a.c, a.e], [a.b, a.d, a.f], [0, 0, 1]]; - } - - for (x = 0; x < 3; x++) { - for (y = 0; y < 3; y++) { - res = 0; - for (z = 0; z < 3; z++) { - res += m[x][z] * matrix[z][y]; - } - out[x][y] = res; - } - } - this.a = out[0][0]; - this.b = out[1][0]; - this.c = out[0][1]; - this.d = out[1][1]; - this.e = out[0][2]; - this.f = out[1][2]; - }; - /*\ - * Matrix.invert - [ method ] - ** - * Returns inverted version of the matrix - = (object) @Matrix - \*/ - matrixproto.invert = function () { - var me = this, - x = me.a * me.d - me.b * me.c; - return new Matrix(me.d / x, -me.b / x, -me.c / x, me.a / x, (me.c * me.f - me.d * me.e) / x, (me.b * me.e - me.a * me.f) / x); - }; - /*\ - * Matrix.clone - [ method ] - ** - * Returns copy of the matrix - = (object) @Matrix - \*/ - matrixproto.clone = function () { - return new Matrix(this.a, this.b, this.c, this.d, this.e, this.f); - }; - /*\ - * Matrix.translate - [ method ] - ** - * Translate the matrix - > Parameters - - x (number) - - y (number) - \*/ - matrixproto.translate = function (x, y) { - this.add(1, 0, 0, 1, x, y); - }; - /*\ - * Matrix.scale - [ method ] - ** - * Scales the matrix - > Parameters - - x (number) - - y (number) #optional - - cx (number) #optional - - cy (number) #optional - \*/ - matrixproto.scale = function (x, y, cx, cy) { - y == null && (y = x); - (cx || cy) && this.add(1, 0, 0, 1, cx, cy); - this.add(x, 0, 0, y, 0, 0); - (cx || cy) && this.add(1, 0, 0, 1, -cx, -cy); - }; - /*\ - * Matrix.rotate - [ method ] - ** - * Rotates the matrix - > Parameters - - a (number) - - x (number) - - y (number) - \*/ - matrixproto.rotate = function (a, x, y) { - a = R.rad(a); - x = x || 0; - y = y || 0; - var cos = +math.cos(a).toFixed(9), - sin = +math.sin(a).toFixed(9); - this.add(cos, sin, -sin, cos, x, y); - this.add(1, 0, 0, 1, -x, -y); - }; - /*\ - * Matrix.x - [ method ] - ** - * Return x coordinate for given point after transformation described by the matrix. See also @Matrix.y - > Parameters - - x (number) - - y (number) - = (number) x - \*/ - matrixproto.x = function (x, y) { - return x * this.a + y * this.c + this.e; - }; - /*\ - * Matrix.y - [ method ] - ** - * Return y coordinate for given point after transformation described by the matrix. See also @Matrix.x - > Parameters - - x (number) - - y (number) - = (number) y - \*/ - matrixproto.y = function (x, y) { - return x * this.b + y * this.d + this.f; - }; - matrixproto.get = function (i) { - return +this[Str.fromCharCode(97 + i)].toFixed(4); - }; - matrixproto.toString = function () { - return R.svg ? - "matrix(" + [this.get(0), this.get(1), this.get(2), this.get(3), this.get(4), this.get(5)].join() + ")" : - [this.get(0), this.get(2), this.get(1), this.get(3), 0, 0].join(); - }; - matrixproto.toFilter = function () { - return "progid:DXImageTransform.Microsoft.Matrix(M11=" + this.get(0) + - ", M12=" + this.get(2) + ", M21=" + this.get(1) + ", M22=" + this.get(3) + - ", Dx=" + this.get(4) + ", Dy=" + this.get(5) + ", sizingmethod='auto expand')"; - }; - matrixproto.offset = function () { - return [this.e.toFixed(4), this.f.toFixed(4)]; - }; - function norm(a) { - return a[0] * a[0] + a[1] * a[1]; - } - function normalize(a) { - var mag = math.sqrt(norm(a)); - a[0] && (a[0] /= mag); - a[1] && (a[1] /= mag); - } - /*\ - * Matrix.split - [ method ] - ** - * Splits matrix into primitive transformations - = (object) in format: - o dx (number) translation by x - o dy (number) translation by y - o scalex (number) scale by x - o scaley (number) scale by y - o shear (number) shear - o rotate (number) rotation in deg - o isSimple (boolean) could it be represented via simple transformations - \*/ - matrixproto.split = function () { - var out = {}; - // translation - out.dx = this.e; - out.dy = this.f; - - // scale and shear - var row = [[this.a, this.c], [this.b, this.d]]; - out.scalex = math.sqrt(norm(row[0])); - normalize(row[0]); - - out.shear = row[0][0] * row[1][0] + row[0][1] * row[1][1]; - row[1] = [row[1][0] - row[0][0] * out.shear, row[1][1] - row[0][1] * out.shear]; - - out.scaley = math.sqrt(norm(row[1])); - normalize(row[1]); - out.shear /= out.scaley; - - // rotation - var sin = -row[0][1], - cos = row[1][1]; - if (cos < 0) { - out.rotate = R.deg(math.acos(cos)); - if (sin < 0) { - out.rotate = 360 - out.rotate; - } - } else { - out.rotate = R.deg(math.asin(sin)); - } - - out.isSimple = !+out.shear.toFixed(9) && (out.scalex.toFixed(9) == out.scaley.toFixed(9) || !out.rotate); - out.isSuperSimple = !+out.shear.toFixed(9) && out.scalex.toFixed(9) == out.scaley.toFixed(9) && !out.rotate; - out.noRotation = !+out.shear.toFixed(9) && !out.rotate; - return out; - }; - /*\ - * Matrix.toTransformString - [ method ] - ** - * Return transform string that represents given matrix - = (string) transform string - \*/ - matrixproto.toTransformString = function (shorter) { - var s = shorter || this[split](); - if (s.isSimple) { - s.scalex = +s.scalex.toFixed(4); - s.scaley = +s.scaley.toFixed(4); - s.rotate = +s.rotate.toFixed(4); - return (s.dx || s.dy ? "t" + [s.dx, s.dy] : E) + - (s.scalex != 1 || s.scaley != 1 ? "s" + [s.scalex, s.scaley, 0, 0] : E) + - (s.rotate ? "r" + [s.rotate, 0, 0] : E); - } else { - return "m" + [this.get(0), this.get(1), this.get(2), this.get(3), this.get(4), this.get(5)]; - } - }; - })(Matrix.prototype); - - // WebKit rendering bug workaround method - var version = navigator.userAgent.match(/Version\/(.*?)\s/) || navigator.userAgent.match(/Chrome\/(\d+)/); - if ((navigator.vendor == "Apple Computer, Inc.") && (version && version[1] < 4 || navigator.platform.slice(0, 2) == "iP") || - (navigator.vendor == "Google Inc." && version && version[1] < 8)) { - /*\ - * Paper.safari - [ method ] - ** - * There is an inconvenient rendering bug in Safari (WebKit): - * sometimes the rendering should be forced. - * This method should help with dealing with this bug. - \*/ - paperproto.safari = function () { - var rect = this.rect(-99, -99, this.width + 99, this.height + 99).attr({stroke: "none"}); - setTimeout(function () {rect.remove();}); - }; - } else { - paperproto.safari = fun; - } - - var preventDefault = function () { - this.returnValue = false; - }, - preventTouch = function () { - return this.originalEvent.preventDefault(); - }, - stopPropagation = function () { - this.cancelBubble = true; - }, - stopTouch = function () { - return this.originalEvent.stopPropagation(); - }, - getEventPosition = function (e) { - var scrollY = g.doc.documentElement.scrollTop || g.doc.body.scrollTop, - scrollX = g.doc.documentElement.scrollLeft || g.doc.body.scrollLeft; - - return { - x: e.clientX + scrollX, - y: e.clientY + scrollY - }; - }, - addEvent = (function () { - if (g.doc.addEventListener) { - return function (obj, type, fn, element) { - var f = function (e) { - var pos = getEventPosition(e); - return fn.call(element, e, pos.x, pos.y); - }; - obj.addEventListener(type, f, false); - - if (supportsTouch && touchMap[type]) { - var _f = function (e) { - var pos = getEventPosition(e), - olde = e; - - for (var i = 0, ii = e.targetTouches && e.targetTouches.length; i < ii; i++) { - if (e.targetTouches[i].target == obj) { - e = e.targetTouches[i]; - e.originalEvent = olde; - e.preventDefault = preventTouch; - e.stopPropagation = stopTouch; - break; - } - } - - return fn.call(element, e, pos.x, pos.y); - }; - obj.addEventListener(touchMap[type], _f, false); - } - - return function () { - obj.removeEventListener(type, f, false); - - if (supportsTouch && touchMap[type]) - obj.removeEventListener(touchMap[type], _f, false); - - return true; - }; - }; - } else if (g.doc.attachEvent) { - return function (obj, type, fn, element) { - var f = function (e) { - e = e || g.win.event; - var scrollY = g.doc.documentElement.scrollTop || g.doc.body.scrollTop, - scrollX = g.doc.documentElement.scrollLeft || g.doc.body.scrollLeft, - x = e.clientX + scrollX, - y = e.clientY + scrollY; - e.preventDefault = e.preventDefault || preventDefault; - e.stopPropagation = e.stopPropagation || stopPropagation; - return fn.call(element, e, x, y); - }; - obj.attachEvent("on" + type, f); - var detacher = function () { - obj.detachEvent("on" + type, f); - return true; - }; - return detacher; - }; - } - })(), - drag = [], - dragMove = function (e) { - var x = e.clientX, - y = e.clientY, - scrollY = g.doc.documentElement.scrollTop || g.doc.body.scrollTop, - scrollX = g.doc.documentElement.scrollLeft || g.doc.body.scrollLeft, - dragi, - j = drag.length; - while (j--) { - dragi = drag[j]; - if (supportsTouch && e.touches) { - var i = e.touches.length, - touch; - while (i--) { - touch = e.touches[i]; - if (touch.identifier == dragi.el._drag.id) { - x = touch.clientX; - y = touch.clientY; - (e.originalEvent ? e.originalEvent : e).preventDefault(); - break; - } - } - } else { - e.preventDefault(); - } - var node = dragi.el.node, - o, - next = node.nextSibling, - parent = node.parentNode, - display = node.style.display; - g.win.opera && parent.removeChild(node); - node.style.display = "none"; - o = dragi.el.paper.getElementByPoint(x, y); - node.style.display = display; - g.win.opera && (next ? parent.insertBefore(node, next) : parent.appendChild(node)); - o && eve("raphael.drag.over." + dragi.el.id, dragi.el, o); - x += scrollX; - y += scrollY; - eve("raphael.drag.move." + dragi.el.id, dragi.move_scope || dragi.el, x - dragi.el._drag.x, y - dragi.el._drag.y, x, y, e); - } - }, - dragUp = function (e) { - R.unmousemove(dragMove).unmouseup(dragUp); - var i = drag.length, - dragi; - while (i--) { - dragi = drag[i]; - dragi.el._drag = {}; - eve("raphael.drag.end." + dragi.el.id, dragi.end_scope || dragi.start_scope || dragi.move_scope || dragi.el, e); - } - drag = []; - }, - /*\ - * Raphael.el - [ property (object) ] - ** - * You can add your own method to elements. This is usefull when you want to hack default functionality or - * want to wrap some common transformation or attributes in one method. In difference to canvas methods, - * you can redefine element method at any time. Expending element methods wouldn’t affect set. - > Usage - | Raphael.el.red = function () { - | this.attr({fill: "#f00"}); - | }; - | // then use it - | paper.circle(100, 100, 20).red(); - \*/ - elproto = R.el = {}; - /*\ - * Element.click - [ method ] - ** - * Adds event handler for click for the element. - > Parameters - - handler (function) handler for the event - = (object) @Element - \*/ - /*\ - * Element.unclick - [ method ] - ** - * Removes event handler for click for the element. - > Parameters - - handler (function) #optional handler for the event - = (object) @Element - \*/ - - /*\ - * Element.dblclick - [ method ] - ** - * Adds event handler for double click for the element. - > Parameters - - handler (function) handler for the event - = (object) @Element - \*/ - /*\ - * Element.undblclick - [ method ] - ** - * Removes event handler for double click for the element. - > Parameters - - handler (function) #optional handler for the event - = (object) @Element - \*/ - - /*\ - * Element.mousedown - [ method ] - ** - * Adds event handler for mousedown for the element. - > Parameters - - handler (function) handler for the event - = (object) @Element - \*/ - /*\ - * Element.unmousedown - [ method ] - ** - * Removes event handler for mousedown for the element. - > Parameters - - handler (function) #optional handler for the event - = (object) @Element - \*/ - - /*\ - * Element.mousemove - [ method ] - ** - * Adds event handler for mousemove for the element. - > Parameters - - handler (function) handler for the event - = (object) @Element - \*/ - /*\ - * Element.unmousemove - [ method ] - ** - * Removes event handler for mousemove for the element. - > Parameters - - handler (function) #optional handler for the event - = (object) @Element - \*/ - - /*\ - * Element.mouseout - [ method ] - ** - * Adds event handler for mouseout for the element. - > Parameters - - handler (function) handler for the event - = (object) @Element - \*/ - /*\ - * Element.unmouseout - [ method ] - ** - * Removes event handler for mouseout for the element. - > Parameters - - handler (function) #optional handler for the event - = (object) @Element - \*/ - - /*\ - * Element.mouseover - [ method ] - ** - * Adds event handler for mouseover for the element. - > Parameters - - handler (function) handler for the event - = (object) @Element - \*/ - /*\ - * Element.unmouseover - [ method ] - ** - * Removes event handler for mouseover for the element. - > Parameters - - handler (function) #optional handler for the event - = (object) @Element - \*/ - - /*\ - * Element.mouseup - [ method ] - ** - * Adds event handler for mouseup for the element. - > Parameters - - handler (function) handler for the event - = (object) @Element - \*/ - /*\ - * Element.unmouseup - [ method ] - ** - * Removes event handler for mouseup for the element. - > Parameters - - handler (function) #optional handler for the event - = (object) @Element - \*/ - - /*\ - * Element.touchstart - [ method ] - ** - * Adds event handler for touchstart for the element. - > Parameters - - handler (function) handler for the event - = (object) @Element - \*/ - /*\ - * Element.untouchstart - [ method ] - ** - * Removes event handler for touchstart for the element. - > Parameters - - handler (function) #optional handler for the event - = (object) @Element - \*/ - - /*\ - * Element.touchmove - [ method ] - ** - * Adds event handler for touchmove for the element. - > Parameters - - handler (function) handler for the event - = (object) @Element - \*/ - /*\ - * Element.untouchmove - [ method ] - ** - * Removes event handler for touchmove for the element. - > Parameters - - handler (function) #optional handler for the event - = (object) @Element - \*/ - - /*\ - * Element.touchend - [ method ] - ** - * Adds event handler for touchend for the element. - > Parameters - - handler (function) handler for the event - = (object) @Element - \*/ - /*\ - * Element.untouchend - [ method ] - ** - * Removes event handler for touchend for the element. - > Parameters - - handler (function) #optional handler for the event - = (object) @Element - \*/ - - /*\ - * Element.touchcancel - [ method ] - ** - * Adds event handler for touchcancel for the element. - > Parameters - - handler (function) handler for the event - = (object) @Element - \*/ - /*\ - * Element.untouchcancel - [ method ] - ** - * Removes event handler for touchcancel for the element. - > Parameters - - handler (function) #optional handler for the event - = (object) @Element - \*/ - for (var i = events.length; i--;) { - (function (eventName) { - R[eventName] = elproto[eventName] = function (fn, scope) { - if (R.is(fn, "function")) { - this.events = this.events || []; - this.events.push({name: eventName, f: fn, unbind: addEvent(this.shape || this.node || g.doc, eventName, fn, scope || this)}); - } - return this; - }; - R["un" + eventName] = elproto["un" + eventName] = function (fn) { - var events = this.events || [], - l = events.length; - while (l--){ - if (events[l].name == eventName && (R.is(fn, "undefined") || events[l].f == fn)) { - events[l].unbind(); - events.splice(l, 1); - !events.length && delete this.events; - } - } - return this; - }; - })(events[i]); - } - - /*\ - * Element.data - [ method ] - ** - * Adds or retrieves given value asociated with given key. - ** - * See also @Element.removeData - > Parameters - - key (string) key to store data - - value (any) #optional value to store - = (object) @Element - * or, if value is not specified: - = (any) value - * or, if key and value are not specified: - = (object) Key/value pairs for all the data associated with the element. - > Usage - | for (var i = 0, i < 5, i++) { - | paper.circle(10 + 15 * i, 10, 10) - | .attr({fill: "#000"}) - | .data("i", i) - | .click(function () { - | alert(this.data("i")); - | }); - | } - \*/ - elproto.data = function (key, value) { - var data = eldata[this.id] = eldata[this.id] || {}; - if (arguments.length == 0) { - return data; - } - if (arguments.length == 1) { - if (R.is(key, "object")) { - for (var i in key) if (key[has](i)) { - this.data(i, key[i]); - } - return this; - } - eve("raphael.data.get." + this.id, this, data[key], key); - return data[key]; - } - data[key] = value; - eve("raphael.data.set." + this.id, this, value, key); - return this; - }; - /*\ - * Element.removeData - [ method ] - ** - * Removes value associated with an element by given key. - * If key is not provided, removes all the data of the element. - > Parameters - - key (string) #optional key - = (object) @Element - \*/ - elproto.removeData = function (key) { - if (key == null) { - eldata[this.id] = {}; - } else { - eldata[this.id] && delete eldata[this.id][key]; - } - return this; - }; - /*\ - * Element.getData - [ method ] - ** - * Retrieves the element data - = (object) data - \*/ - elproto.getData = function () { - return clone(eldata[this.id] || {}); - }; - /*\ - * Element.hover - [ method ] - ** - * Adds event handlers for hover for the element. - > Parameters - - f_in (function) handler for hover in - - f_out (function) handler for hover out - - icontext (object) #optional context for hover in handler - - ocontext (object) #optional context for hover out handler - = (object) @Element - \*/ - elproto.hover = function (f_in, f_out, scope_in, scope_out) { - return this.mouseover(f_in, scope_in).mouseout(f_out, scope_out || scope_in); - }; - /*\ - * Element.unhover - [ method ] - ** - * Removes event handlers for hover for the element. - > Parameters - - f_in (function) handler for hover in - - f_out (function) handler for hover out - = (object) @Element - \*/ - elproto.unhover = function (f_in, f_out) { - return this.unmouseover(f_in).unmouseout(f_out); - }; - var draggable = []; - /*\ - * Element.drag - [ method ] - ** - * Adds event handlers for drag of the element. - > Parameters - - onmove (function) handler for moving - - onstart (function) handler for drag start - - onend (function) handler for drag end - - mcontext (object) #optional context for moving handler - - scontext (object) #optional context for drag start handler - - econtext (object) #optional context for drag end handler - * Additionaly following `drag` events will be triggered: `drag.start.<id>` on start, - * `drag.end.<id>` on end and `drag.move.<id>` on every move. When element will be dragged over another element - * `drag.over.<id>` will be fired as well. - * - * Start event and start handler will be called in specified context or in context of the element with following parameters: - o x (number) x position of the mouse - o y (number) y position of the mouse - o event (object) DOM event object - * Move event and move handler will be called in specified context or in context of the element with following parameters: - o dx (number) shift by x from the start point - o dy (number) shift by y from the start point - o x (number) x position of the mouse - o y (number) y position of the mouse - o event (object) DOM event object - * End event and end handler will be called in specified context or in context of the element with following parameters: - o event (object) DOM event object - = (object) @Element - \*/ - elproto.drag = function (onmove, onstart, onend, move_scope, start_scope, end_scope) { - function start(e) { - (e.originalEvent || e).preventDefault(); - var x = e.clientX, - y = e.clientY, - scrollY = g.doc.documentElement.scrollTop || g.doc.body.scrollTop, - scrollX = g.doc.documentElement.scrollLeft || g.doc.body.scrollLeft; - this._drag.id = e.identifier; - if (supportsTouch && e.touches) { - var i = e.touches.length, touch; - while (i--) { - touch = e.touches[i]; - this._drag.id = touch.identifier; - if (touch.identifier == this._drag.id) { - x = touch.clientX; - y = touch.clientY; - break; - } - } - } - this._drag.x = x + scrollX; - this._drag.y = y + scrollY; - !drag.length && R.mousemove(dragMove).mouseup(dragUp); - drag.push({el: this, move_scope: move_scope, start_scope: start_scope, end_scope: end_scope}); - onstart && eve.on("raphael.drag.start." + this.id, onstart); - onmove && eve.on("raphael.drag.move." + this.id, onmove); - onend && eve.on("raphael.drag.end." + this.id, onend); - eve("raphael.drag.start." + this.id, start_scope || move_scope || this, e.clientX + scrollX, e.clientY + scrollY, e); - } - this._drag = {}; - draggable.push({el: this, start: start}); - this.mousedown(start); - return this; - }; - /*\ - * Element.onDragOver - [ method ] - ** - * Shortcut for assigning event handler for `drag.over.<id>` event, where id is id of the element (see @Element.id). - > Parameters - - f (function) handler for event, first argument would be the element you are dragging over - \*/ - elproto.onDragOver = function (f) { - f ? eve.on("raphael.drag.over." + this.id, f) : eve.unbind("raphael.drag.over." + this.id); - }; - /*\ - * Element.undrag - [ method ] - ** - * Removes all drag event handlers from given element. - \*/ - elproto.undrag = function () { - var i = draggable.length; - while (i--) if (draggable[i].el == this) { - this.unmousedown(draggable[i].start); - draggable.splice(i, 1); - eve.unbind("raphael.drag.*." + this.id); - } - !draggable.length && R.unmousemove(dragMove).unmouseup(dragUp); - drag = []; - }; - /*\ - * Paper.circle - [ method ] - ** - * Draws a circle. - ** - > Parameters - ** - - x (number) x coordinate of the centre - - y (number) y coordinate of the centre - - r (number) radius - = (object) Raphaël element object with type “circle” - ** - > Usage - | var c = paper.circle(50, 50, 40); - \*/ - paperproto.circle = function (x, y, r) { - var out = R._engine.circle(this, x || 0, y || 0, r || 0); - this.__set__ && this.__set__.push(out); - return out; - }; - /*\ - * Paper.rect - [ method ] - * - * Draws a rectangle. - ** - > Parameters - ** - - x (number) x coordinate of the top left corner - - y (number) y coordinate of the top left corner - - width (number) width - - height (number) height - - r (number) #optional radius for rounded corners, default is 0 - = (object) Raphaël element object with type “rect” - ** - > Usage - | // regular rectangle - | var c = paper.rect(10, 10, 50, 50); - | // rectangle with rounded corners - | var c = paper.rect(40, 40, 50, 50, 10); - \*/ - paperproto.rect = function (x, y, w, h, r) { - var out = R._engine.rect(this, x || 0, y || 0, w || 0, h || 0, r || 0); - this.__set__ && this.__set__.push(out); - return out; - }; - /*\ - * Paper.ellipse - [ method ] - ** - * Draws an ellipse. - ** - > Parameters - ** - - x (number) x coordinate of the centre - - y (number) y coordinate of the centre - - rx (number) horizontal radius - - ry (number) vertical radius - = (object) Raphaël element object with type “ellipse” - ** - > Usage - | var c = paper.ellipse(50, 50, 40, 20); - \*/ - paperproto.ellipse = function (x, y, rx, ry) { - var out = R._engine.ellipse(this, x || 0, y || 0, rx || 0, ry || 0); - this.__set__ && this.__set__.push(out); - return out; - }; - /*\ - * Paper.path - [ method ] - ** - * Creates a path element by given path data string. - > Parameters - - pathString (string) #optional path string in SVG format. - * Path string consists of one-letter commands, followed by comma seprarated arguments in numercal form. Example: - | "M10,20L30,40" - * Here we can see two commands: “M”, with arguments `(10, 20)` and “L” with arguments `(30, 40)`. Upper case letter mean command is absolute, lower case—relative. - * - # <p>Here is short list of commands available, for more details see <a href="http://www.w3.org/TR/SVG/paths.html#PathData" title="Details of a path's data attribute's format are described in the SVG specification.">SVG path string format</a>.</p> - # <table><thead><tr><th>Command</th><th>Name</th><th>Parameters</th></tr></thead><tbody> - # <tr><td>M</td><td>moveto</td><td>(x y)+</td></tr> - # <tr><td>Z</td><td>closepath</td><td>(none)</td></tr> - # <tr><td>L</td><td>lineto</td><td>(x y)+</td></tr> - # <tr><td>H</td><td>horizontal lineto</td><td>x+</td></tr> - # <tr><td>V</td><td>vertical lineto</td><td>y+</td></tr> - # <tr><td>C</td><td>curveto</td><td>(x1 y1 x2 y2 x y)+</td></tr> - # <tr><td>S</td><td>smooth curveto</td><td>(x2 y2 x y)+</td></tr> - # <tr><td>Q</td><td>quadratic Bézier curveto</td><td>(x1 y1 x y)+</td></tr> - # <tr><td>T</td><td>smooth quadratic Bézier curveto</td><td>(x y)+</td></tr> - # <tr><td>A</td><td>elliptical arc</td><td>(rx ry x-axis-rotation large-arc-flag sweep-flag x y)+</td></tr> - # <tr><td>R</td><td><a href="http://en.wikipedia.org/wiki/Catmull–Rom_spline#Catmull.E2.80.93Rom_spline">Catmull-Rom curveto</a>*</td><td>x1 y1 (x y)+</td></tr></tbody></table> - * * “Catmull-Rom curveto” is a not standard SVG command and added in 2.0 to make life easier. - * Note: there is a special case when path consist of just three commands: “M10,10R…z”. In this case path will smoothly connects to its beginning. - > Usage - | var c = paper.path("M10 10L90 90"); - | // draw a diagonal line: - | // move to 10,10, line to 90,90 - * For example of path strings, check out these icons: http://raphaeljs.com/icons/ - \*/ - paperproto.path = function (pathString) { - pathString && !R.is(pathString, string) && !R.is(pathString[0], array) && (pathString += E); - var out = R._engine.path(R.format[apply](R, arguments), this); - this.__set__ && this.__set__.push(out); - return out; - }; - /*\ - * Paper.image - [ method ] - ** - * Embeds an image into the surface. - ** - > Parameters - ** - - src (string) URI of the source image - - x (number) x coordinate position - - y (number) y coordinate position - - width (number) width of the image - - height (number) height of the image - = (object) Raphaël element object with type “image” - ** - > Usage - | var c = paper.image("apple.png", 10, 10, 80, 80); - \*/ - paperproto.image = function (src, x, y, w, h) { - var out = R._engine.image(this, src || "about:blank", x || 0, y || 0, w || 0, h || 0); - this.__set__ && this.__set__.push(out); - return out; - }; - /*\ - * Paper.text - [ method ] - ** - * Draws a text string. If you need line breaks, put “\n” in the string. - ** - > Parameters - ** - - x (number) x coordinate position - - y (number) y coordinate position - - text (string) The text string to draw - = (object) Raphaël element object with type “text” - ** - > Usage - | var t = paper.text(50, 50, "Raphaël\nkicks\nbutt!"); - \*/ - paperproto.text = function (x, y, text) { - var out = R._engine.text(this, x || 0, y || 0, Str(text)); - this.__set__ && this.__set__.push(out); - return out; - }; - /*\ - * Paper.set - [ method ] - ** - * Creates array-like object to keep and operate several elements at once. - * Warning: it doesn’t create any elements for itself in the page, it just groups existing elements. - * Sets act as pseudo elements — all methods available to an element can be used on a set. - = (object) array-like object that represents set of elements - ** - > Usage - | var st = paper.set(); - | st.push( - | paper.circle(10, 10, 5), - | paper.circle(30, 10, 5) - | ); - | st.attr({fill: "red"}); // changes the fill of both circles - \*/ - paperproto.set = function (itemsArray) { - !R.is(itemsArray, "array") && (itemsArray = Array.prototype.splice.call(arguments, 0, arguments.length)); - var out = new Set(itemsArray); - this.__set__ && this.__set__.push(out); - out["paper"] = this; - out["type"] = "set"; - return out; - }; - /*\ - * Paper.setStart - [ method ] - ** - * Creates @Paper.set. All elements that will be created after calling this method and before calling - * @Paper.setFinish will be added to the set. - ** - > Usage - | paper.setStart(); - | paper.circle(10, 10, 5), - | paper.circle(30, 10, 5) - | var st = paper.setFinish(); - | st.attr({fill: "red"}); // changes the fill of both circles - \*/ - paperproto.setStart = function (set) { - this.__set__ = set || this.set(); - }; - /*\ - * Paper.setFinish - [ method ] - ** - * See @Paper.setStart. This method finishes catching and returns resulting set. - ** - = (object) set - \*/ - paperproto.setFinish = function (set) { - var out = this.__set__; - delete this.__set__; - return out; - }; - /*\ - * Paper.getSize - [ method ] - ** - * Obtains current paper actual size. - ** - = (object) - \*/ - paperproto.getSize = function () { - var container = this.canvas.parentNode; - return { - width: container.offsetWidth, - height: container.offsetHeight - }; - }; - /*\ - * Paper.setSize - [ method ] - ** - * If you need to change dimensions of the canvas call this method - ** - > Parameters - ** - - width (number) new width of the canvas - - height (number) new height of the canvas - \*/ - paperproto.setSize = function (width, height) { - return R._engine.setSize.call(this, width, height); - }; - /*\ - * Paper.setViewBox - [ method ] - ** - * Sets the view box of the paper. Practically it gives you ability to zoom and pan whole paper surface by - * specifying new boundaries. - ** - > Parameters - ** - - x (number) new x position, default is `0` - - y (number) new y position, default is `0` - - w (number) new width of the canvas - - h (number) new height of the canvas - - fit (boolean) `true` if you want graphics to fit into new boundary box - \*/ - paperproto.setViewBox = function (x, y, w, h, fit) { - return R._engine.setViewBox.call(this, x, y, w, h, fit); - }; - /*\ - * Paper.top - [ property ] - ** - * Points to the topmost element on the paper - \*/ - /*\ - * Paper.bottom - [ property ] - ** - * Points to the bottom element on the paper - \*/ - paperproto.top = paperproto.bottom = null; - /*\ - * Paper.raphael - [ property ] - ** - * Points to the @Raphael object/function - \*/ - paperproto.raphael = R; - var getOffset = function (elem) { - var box = elem.getBoundingClientRect(), - doc = elem.ownerDocument, - body = doc.body, - docElem = doc.documentElement, - clientTop = docElem.clientTop || body.clientTop || 0, clientLeft = docElem.clientLeft || body.clientLeft || 0, - top = box.top + (g.win.pageYOffset || docElem.scrollTop || body.scrollTop ) - clientTop, - left = box.left + (g.win.pageXOffset || docElem.scrollLeft || body.scrollLeft) - clientLeft; - return { - y: top, - x: left - }; - }; - /*\ - * Paper.getElementByPoint - [ method ] - ** - * Returns you topmost element under given point. - ** - = (object) Raphaël element object - > Parameters - ** - - x (number) x coordinate from the top left corner of the window - - y (number) y coordinate from the top left corner of the window - > Usage - | paper.getElementByPoint(mouseX, mouseY).attr({stroke: "#f00"}); - \*/ - paperproto.getElementByPoint = function (x, y) { - var paper = this, - svg = paper.canvas, - target = g.doc.elementFromPoint(x, y); - if (g.win.opera && target.tagName == "svg") { - var so = getOffset(svg), - sr = svg.createSVGRect(); - sr.x = x - so.x; - sr.y = y - so.y; - sr.width = sr.height = 1; - var hits = svg.getIntersectionList(sr, null); - if (hits.length) { - target = hits[hits.length - 1]; - } - } - if (!target) { - return null; - } - while (target.parentNode && target != svg.parentNode && !target.raphael) { - target = target.parentNode; - } - target == paper.canvas.parentNode && (target = svg); - target = target && target.raphael ? paper.getById(target.raphaelid) : null; - return target; - }; - - /*\ - * Paper.getElementsByBBox - [ method ] - ** - * Returns set of elements that have an intersecting bounding box - ** - > Parameters - ** - - bbox (object) bbox to check with - = (object) @Set - \*/ - paperproto.getElementsByBBox = function (bbox) { - var set = this.set(); - this.forEach(function (el) { - if (R.isBBoxIntersect(el.getBBox(), bbox)) { - set.push(el); - } - }); - return set; - }; - - /*\ - * Paper.getById - [ method ] - ** - * Returns you element by its internal ID. - ** - > Parameters - ** - - id (number) id - = (object) Raphaël element object - \*/ - paperproto.getById = function (id) { - var bot = this.bottom; - while (bot) { - if (bot.id == id) { - return bot; - } - bot = bot.next; - } - return null; - }; - /*\ - * Paper.forEach - [ method ] - ** - * Executes given function for each element on the paper - * - * If callback function returns `false` it will stop loop running. - ** - > Parameters - ** - - callback (function) function to run - - thisArg (object) context object for the callback - = (object) Paper object - > Usage - | paper.forEach(function (el) { - | el.attr({ stroke: "blue" }); - | }); - \*/ - paperproto.forEach = function (callback, thisArg) { - var bot = this.bottom; - while (bot) { - if (callback.call(thisArg, bot) === false) { - return this; - } - bot = bot.next; - } - return this; - }; - /*\ - * Paper.getElementsByPoint - [ method ] - ** - * Returns set of elements that have common point inside - ** - > Parameters - ** - - x (number) x coordinate of the point - - y (number) y coordinate of the point - = (object) @Set - \*/ - paperproto.getElementsByPoint = function (x, y) { - var set = this.set(); - this.forEach(function (el) { - if (el.isPointInside(x, y)) { - set.push(el); - } - }); - return set; - }; - function x_y() { - return this.x + S + this.y; - } - function x_y_w_h() { - return this.x + S + this.y + S + this.width + " \xd7 " + this.height; - } - /*\ - * Element.isPointInside - [ method ] - ** - * Determine if given point is inside this element’s shape - ** - > Parameters - ** - - x (number) x coordinate of the point - - y (number) y coordinate of the point - = (boolean) `true` if point inside the shape - \*/ - elproto.isPointInside = function (x, y) { - var rp = this.realPath = getPath[this.type](this); - if (this.attr('transform') && this.attr('transform').length) { - rp = R.transformPath(rp, this.attr('transform')); - } - return R.isPointInsidePath(rp, x, y); - }; - /*\ - * Element.getBBox - [ method ] - ** - * Return bounding box for a given element - ** - > Parameters - ** - - isWithoutTransform (boolean) flag, `true` if you want to have bounding box before transformations. Default is `false`. - = (object) Bounding box object: - o { - o x: (number) top left corner x - o y: (number) top left corner y - o x2: (number) bottom right corner x - o y2: (number) bottom right corner y - o width: (number) width - o height: (number) height - o } - \*/ - elproto.getBBox = function (isWithoutTransform) { - if (this.removed) { - return {}; - } - var _ = this._; - if (isWithoutTransform) { - if (_.dirty || !_.bboxwt) { - this.realPath = getPath[this.type](this); - _.bboxwt = pathDimensions(this.realPath); - _.bboxwt.toString = x_y_w_h; - _.dirty = 0; - } - return _.bboxwt; - } - if (_.dirty || _.dirtyT || !_.bbox) { - if (_.dirty || !this.realPath) { - _.bboxwt = 0; - this.realPath = getPath[this.type](this); - } - _.bbox = pathDimensions(mapPath(this.realPath, this.matrix)); - _.bbox.toString = x_y_w_h; - _.dirty = _.dirtyT = 0; - } - return _.bbox; - }; - /*\ - * Element.clone - [ method ] - ** - = (object) clone of a given element - ** - \*/ - elproto.clone = function () { - if (this.removed) { - return null; - } - var out = this.paper[this.type]().attr(this.attr()); - this.__set__ && this.__set__.push(out); - return out; - }; - /*\ - * Element.glow - [ method ] - ** - * Return set of elements that create glow-like effect around given element. See @Paper.set. - * - * Note: Glow is not connected to the element. If you change element attributes it won’t adjust itself. - ** - > Parameters - ** - - glow (object) #optional parameters object with all properties optional: - o { - o width (number) size of the glow, default is `10` - o fill (boolean) will it be filled, default is `false` - o opacity (number) opacity, default is `0.5` - o offsetx (number) horizontal offset, default is `0` - o offsety (number) vertical offset, default is `0` - o color (string) glow colour, default is `black` - o } - = (object) @Paper.set of elements that represents glow - \*/ - elproto.glow = function (glow) { - if (this.type == "text") { - return null; - } - glow = glow || {}; - var s = { - width: (glow.width || 10) + (+this.attr("stroke-width") || 1), - fill: glow.fill || false, - opacity: glow.opacity || .5, - offsetx: glow.offsetx || 0, - offsety: glow.offsety || 0, - color: glow.color || "#000" - }, - c = s.width / 2, - r = this.paper, - out = r.set(), - path = this.realPath || getPath[this.type](this); - path = this.matrix ? mapPath(path, this.matrix) : path; - for (var i = 1; i < c + 1; i++) { - out.push(r.path(path).attr({ - stroke: s.color, - fill: s.fill ? s.color : "none", - "stroke-linejoin": "round", - "stroke-linecap": "round", - "stroke-width": +(s.width / c * i).toFixed(3), - opacity: +(s.opacity / c).toFixed(3) - })); - } - return out.insertBefore(this).translate(s.offsetx, s.offsety); - }; - var curveslengths = {}, - getPointAtSegmentLength = function (p1x, p1y, c1x, c1y, c2x, c2y, p2x, p2y, length) { - if (length == null) { - return bezlen(p1x, p1y, c1x, c1y, c2x, c2y, p2x, p2y); - } else { - return R.findDotsAtSegment(p1x, p1y, c1x, c1y, c2x, c2y, p2x, p2y, getTatLen(p1x, p1y, c1x, c1y, c2x, c2y, p2x, p2y, length)); - } - }, - getLengthFactory = function (istotal, subpath) { - return function (path, length, onlystart) { - path = path2curve(path); - var x, y, p, l, sp = "", subpaths = {}, point, - len = 0; - for (var i = 0, ii = path.length; i < ii; i++) { - p = path[i]; - if (p[0] == "M") { - x = +p[1]; - y = +p[2]; - } else { - l = getPointAtSegmentLength(x, y, p[1], p[2], p[3], p[4], p[5], p[6]); - if (len + l > length) { - if (subpath && !subpaths.start) { - point = getPointAtSegmentLength(x, y, p[1], p[2], p[3], p[4], p[5], p[6], length - len); - sp += ["C" + point.start.x, point.start.y, point.m.x, point.m.y, point.x, point.y]; - if (onlystart) {return sp;} - subpaths.start = sp; - sp = ["M" + point.x, point.y + "C" + point.n.x, point.n.y, point.end.x, point.end.y, p[5], p[6]].join(); - len += l; - x = +p[5]; - y = +p[6]; - continue; - } - if (!istotal && !subpath) { - point = getPointAtSegmentLength(x, y, p[1], p[2], p[3], p[4], p[5], p[6], length - len); - return {x: point.x, y: point.y, alpha: point.alpha}; - } - } - len += l; - x = +p[5]; - y = +p[6]; - } - sp += p.shift() + p; - } - subpaths.end = sp; - point = istotal ? len : subpath ? subpaths : R.findDotsAtSegment(x, y, p[0], p[1], p[2], p[3], p[4], p[5], 1); - point.alpha && (point = {x: point.x, y: point.y, alpha: point.alpha}); - return point; - }; - }; - var getTotalLength = getLengthFactory(1), - getPointAtLength = getLengthFactory(), - getSubpathsAtLength = getLengthFactory(0, 1); - /*\ - * Raphael.getTotalLength - [ method ] - ** - * Returns length of the given path in pixels. - ** - > Parameters - ** - - path (string) SVG path string. - ** - = (number) length. - \*/ - R.getTotalLength = getTotalLength; - /*\ - * Raphael.getPointAtLength - [ method ] - ** - * Return coordinates of the point located at the given length on the given path. - ** - > Parameters - ** - - path (string) SVG path string - - length (number) - ** - = (object) representation of the point: - o { - o x: (number) x coordinate - o y: (number) y coordinate - o alpha: (number) angle of derivative - o } - \*/ - R.getPointAtLength = getPointAtLength; - /*\ - * Raphael.getSubpath - [ method ] - ** - * Return subpath of a given path from given length to given length. - ** - > Parameters - ** - - path (string) SVG path string - - from (number) position of the start of the segment - - to (number) position of the end of the segment - ** - = (string) pathstring for the segment - \*/ - R.getSubpath = function (path, from, to) { - if (this.getTotalLength(path) - to < 1e-6) { - return getSubpathsAtLength(path, from).end; - } - var a = getSubpathsAtLength(path, to, 1); - return from ? getSubpathsAtLength(a, from).end : a; - }; - /*\ - * Element.getTotalLength - [ method ] - ** - * Returns length of the path in pixels. Only works for element of “path” type. - = (number) length. - \*/ - elproto.getTotalLength = function () { - var path = this.getPath(); - if (!path) { - return; - } - - if (this.node.getTotalLength) { - return this.node.getTotalLength(); - } - - return getTotalLength(path); - }; - /*\ - * Element.getPointAtLength - [ method ] - ** - * Return coordinates of the point located at the given length on the given path. Only works for element of “path” type. - ** - > Parameters - ** - - length (number) - ** - = (object) representation of the point: - o { - o x: (number) x coordinate - o y: (number) y coordinate - o alpha: (number) angle of derivative - o } - \*/ - elproto.getPointAtLength = function (length) { - var path = this.getPath(); - if (!path) { - return; - } - - return getPointAtLength(path, length); - }; - /*\ - * Element.getPath - [ method ] - ** - * Returns path of the element. Only works for elements of “path” type and simple elements like circle. - = (object) path - ** - \*/ - elproto.getPath = function () { - var path, - getPath = R._getPath[this.type]; - - if (this.type == "text" || this.type == "set") { - return; - } - - if (getPath) { - path = getPath(this); - } - - return path; - }; - /*\ - * Element.getSubpath - [ method ] - ** - * Return subpath of a given element from given length to given length. Only works for element of “path” type. - ** - > Parameters - ** - - from (number) position of the start of the segment - - to (number) position of the end of the segment - ** - = (string) pathstring for the segment - \*/ - elproto.getSubpath = function (from, to) { - var path = this.getPath(); - if (!path) { - return; - } - - return R.getSubpath(path, from, to); - }; - /*\ - * Raphael.easing_formulas - [ property ] - ** - * Object that contains easing formulas for animation. You could extend it with your own. By default it has following list of easing: - # <ul> - # <li>“linear”</li> - # <li>“<” or “easeIn” or “ease-in”</li> - # <li>“>” or “easeOut” or “ease-out”</li> - # <li>“<>” or “easeInOut” or “ease-in-out”</li> - # <li>“backIn” or “back-in”</li> - # <li>“backOut” or “back-out”</li> - # <li>“elastic”</li> - # <li>“bounce”</li> - # </ul> - # <p>See also <a href="http://raphaeljs.com/easing.html">Easing demo</a>.</p> - \*/ - var ef = R.easing_formulas = { - linear: function (n) { - return n; - }, - "<": function (n) { - return pow(n, 1.7); - }, - ">": function (n) { - return pow(n, .48); - }, - "<>": function (n) { - var q = .48 - n / 1.04, - Q = math.sqrt(.1734 + q * q), - x = Q - q, - X = pow(abs(x), 1 / 3) * (x < 0 ? -1 : 1), - y = -Q - q, - Y = pow(abs(y), 1 / 3) * (y < 0 ? -1 : 1), - t = X + Y + .5; - return (1 - t) * 3 * t * t + t * t * t; - }, - backIn: function (n) { - var s = 1.70158; - return n * n * ((s + 1) * n - s); - }, - backOut: function (n) { - n = n - 1; - var s = 1.70158; - return n * n * ((s + 1) * n + s) + 1; - }, - elastic: function (n) { - if (n == !!n) { - return n; - } - return pow(2, -10 * n) * math.sin((n - .075) * (2 * PI) / .3) + 1; - }, - bounce: function (n) { - var s = 7.5625, - p = 2.75, - l; - if (n < (1 / p)) { - l = s * n * n; - } else { - if (n < (2 / p)) { - n -= (1.5 / p); - l = s * n * n + .75; - } else { - if (n < (2.5 / p)) { - n -= (2.25 / p); - l = s * n * n + .9375; - } else { - n -= (2.625 / p); - l = s * n * n + .984375; - } - } - } - return l; - } - }; - ef.easeIn = ef["ease-in"] = ef["<"]; - ef.easeOut = ef["ease-out"] = ef[">"]; - ef.easeInOut = ef["ease-in-out"] = ef["<>"]; - ef["back-in"] = ef.backIn; - ef["back-out"] = ef.backOut; - - var animationElements = [], - requestAnimFrame = window.requestAnimationFrame || - window.webkitRequestAnimationFrame || - window.mozRequestAnimationFrame || - window.oRequestAnimationFrame || - window.msRequestAnimationFrame || - function (callback) { - setTimeout(callback, 16); - }, - animation = function () { - var Now = +new Date, - l = 0; - for (; l < animationElements.length; l++) { - var e = animationElements[l]; - if (e.el.removed || e.paused) { - continue; - } - var time = Now - e.start, - ms = e.ms, - easing = e.easing, - from = e.from, - diff = e.diff, - to = e.to, - t = e.t, - that = e.el, - set = {}, - now, - init = {}, - key; - if (e.initstatus) { - time = (e.initstatus * e.anim.top - e.prev) / (e.percent - e.prev) * ms; - e.status = e.initstatus; - delete e.initstatus; - e.stop && animationElements.splice(l--, 1); - } else { - e.status = (e.prev + (e.percent - e.prev) * (time / ms)) / e.anim.top; - } - if (time < 0) { - continue; - } - if (time < ms) { - var pos = easing(time / ms); - for (var attr in from) if (from[has](attr)) { - switch (availableAnimAttrs[attr]) { - case nu: - now = +from[attr] + pos * ms * diff[attr]; - break; - case "colour": - now = "rgb(" + [ - upto255(round(from[attr].r + pos * ms * diff[attr].r)), - upto255(round(from[attr].g + pos * ms * diff[attr].g)), - upto255(round(from[attr].b + pos * ms * diff[attr].b)) - ].join(",") + ")"; - break; - case "path": - now = []; - for (var i = 0, ii = from[attr].length; i < ii; i++) { - now[i] = [from[attr][i][0]]; - for (var j = 1, jj = from[attr][i].length; j < jj; j++) { - now[i][j] = +from[attr][i][j] + pos * ms * diff[attr][i][j]; - } - now[i] = now[i].join(S); - } - now = now.join(S); - break; - case "transform": - if (diff[attr].real) { - now = []; - for (i = 0, ii = from[attr].length; i < ii; i++) { - now[i] = [from[attr][i][0]]; - for (j = 1, jj = from[attr][i].length; j < jj; j++) { - now[i][j] = from[attr][i][j] + pos * ms * diff[attr][i][j]; - } - } - } else { - var get = function (i) { - return +from[attr][i] + pos * ms * diff[attr][i]; - }; - // now = [["r", get(2), 0, 0], ["t", get(3), get(4)], ["s", get(0), get(1), 0, 0]]; - now = [["m", get(0), get(1), get(2), get(3), get(4), get(5)]]; - } - break; - case "csv": - if (attr == "clip-rect") { - now = []; - i = 4; - while (i--) { - now[i] = +from[attr][i] + pos * ms * diff[attr][i]; - } - } - break; - default: - var from2 = [][concat](from[attr]); - now = []; - i = that.paper.customAttributes[attr].length; - while (i--) { - now[i] = +from2[i] + pos * ms * diff[attr][i]; - } - break; - } - set[attr] = now; - } - that.attr(set); - (function (id, that, anim) { - setTimeout(function () { - eve("raphael.anim.frame." + id, that, anim); - }); - })(that.id, that, e.anim); - } else { - (function(f, el, a) { - setTimeout(function() { - eve("raphael.anim.frame." + el.id, el, a); - eve("raphael.anim.finish." + el.id, el, a); - R.is(f, "function") && f.call(el); - }); - })(e.callback, that, e.anim); - that.attr(to); - animationElements.splice(l--, 1); - if (e.repeat > 1 && !e.next) { - for (key in to) if (to[has](key)) { - init[key] = e.totalOrigin[key]; - } - e.el.attr(init); - runAnimation(e.anim, e.el, e.anim.percents[0], null, e.totalOrigin, e.repeat - 1); - } - if (e.next && !e.stop) { - runAnimation(e.anim, e.el, e.next, null, e.totalOrigin, e.repeat); - } - } - } - R.svg && that && that.paper && that.paper.safari(); - animationElements.length && requestAnimFrame(animation); - }, - upto255 = function (color) { - return color > 255 ? 255 : color < 0 ? 0 : color; - }; - /*\ - * Element.animateWith - [ method ] - ** - * Acts similar to @Element.animate, but ensure that given animation runs in sync with another given element. - ** - > Parameters - ** - - el (object) element to sync with - - anim (object) animation to sync with - - params (object) #optional final attributes for the element, see also @Element.attr - - ms (number) #optional number of milliseconds for animation to run - - easing (string) #optional easing type. Accept on of @Raphael.easing_formulas or CSS format: `cubic‐bezier(XX, XX, XX, XX)` - - callback (function) #optional callback function. Will be called at the end of animation. - * or - - element (object) element to sync with - - anim (object) animation to sync with - - animation (object) #optional animation object, see @Raphael.animation - ** - = (object) original element - \*/ - elproto.animateWith = function (el, anim, params, ms, easing, callback) { - var element = this; - if (element.removed) { - callback && callback.call(element); - return element; - } - var a = params instanceof Animation ? params : R.animation(params, ms, easing, callback), - x, y; - runAnimation(a, element, a.percents[0], null, element.attr()); - for (var i = 0, ii = animationElements.length; i < ii; i++) { - if (animationElements[i].anim == anim && animationElements[i].el == el) { - animationElements[ii - 1].start = animationElements[i].start; - break; - } - } - return element; - // - // - // var a = params ? R.animation(params, ms, easing, callback) : anim, - // status = element.status(anim); - // return this.animate(a).status(a, status * anim.ms / a.ms); - }; - function CubicBezierAtTime(t, p1x, p1y, p2x, p2y, duration) { - var cx = 3 * p1x, - bx = 3 * (p2x - p1x) - cx, - ax = 1 - cx - bx, - cy = 3 * p1y, - by = 3 * (p2y - p1y) - cy, - ay = 1 - cy - by; - function sampleCurveX(t) { - return ((ax * t + bx) * t + cx) * t; - } - function solve(x, epsilon) { - var t = solveCurveX(x, epsilon); - return ((ay * t + by) * t + cy) * t; - } - function solveCurveX(x, epsilon) { - var t0, t1, t2, x2, d2, i; - for(t2 = x, i = 0; i < 8; i++) { - x2 = sampleCurveX(t2) - x; - if (abs(x2) < epsilon) { - return t2; - } - d2 = (3 * ax * t2 + 2 * bx) * t2 + cx; - if (abs(d2) < 1e-6) { - break; - } - t2 = t2 - x2 / d2; - } - t0 = 0; - t1 = 1; - t2 = x; - if (t2 < t0) { - return t0; - } - if (t2 > t1) { - return t1; - } - while (t0 < t1) { - x2 = sampleCurveX(t2); - if (abs(x2 - x) < epsilon) { - return t2; - } - if (x > x2) { - t0 = t2; - } else { - t1 = t2; - } - t2 = (t1 - t0) / 2 + t0; - } - return t2; - } - return solve(t, 1 / (200 * duration)); - } - elproto.onAnimation = function (f) { - f ? eve.on("raphael.anim.frame." + this.id, f) : eve.unbind("raphael.anim.frame." + this.id); - return this; - }; - function Animation(anim, ms) { - var percents = [], - newAnim = {}; - this.ms = ms; - this.times = 1; - if (anim) { - for (var attr in anim) if (anim[has](attr)) { - newAnim[toFloat(attr)] = anim[attr]; - percents.push(toFloat(attr)); - } - percents.sort(sortByNumber); - } - this.anim = newAnim; - this.top = percents[percents.length - 1]; - this.percents = percents; - } - /*\ - * Animation.delay - [ method ] - ** - * Creates a copy of existing animation object with given delay. - ** - > Parameters - ** - - delay (number) number of ms to pass between animation start and actual animation - ** - = (object) new altered Animation object - | var anim = Raphael.animation({cx: 10, cy: 20}, 2e3); - | circle1.animate(anim); // run the given animation immediately - | circle2.animate(anim.delay(500)); // run the given animation after 500 ms - \*/ - Animation.prototype.delay = function (delay) { - var a = new Animation(this.anim, this.ms); - a.times = this.times; - a.del = +delay || 0; - return a; - }; - /*\ - * Animation.repeat - [ method ] - ** - * Creates a copy of existing animation object with given repetition. - ** - > Parameters - ** - - repeat (number) number iterations of animation. For infinite animation pass `Infinity` - ** - = (object) new altered Animation object - \*/ - Animation.prototype.repeat = function (times) { - var a = new Animation(this.anim, this.ms); - a.del = this.del; - a.times = math.floor(mmax(times, 0)) || 1; - return a; - }; - function runAnimation(anim, element, percent, status, totalOrigin, times) { - percent = toFloat(percent); - var params, - isInAnim, - isInAnimSet, - percents = [], - next, - prev, - timestamp, - ms = anim.ms, - from = {}, - to = {}, - diff = {}; - if (status) { - for (i = 0, ii = animationElements.length; i < ii; i++) { - var e = animationElements[i]; - if (e.el.id == element.id && e.anim == anim) { - if (e.percent != percent) { - animationElements.splice(i, 1); - isInAnimSet = 1; - } else { - isInAnim = e; - } - element.attr(e.totalOrigin); - break; - } - } - } else { - status = +to; // NaN - } - for (var i = 0, ii = anim.percents.length; i < ii; i++) { - if (anim.percents[i] == percent || anim.percents[i] > status * anim.top) { - percent = anim.percents[i]; - prev = anim.percents[i - 1] || 0; - ms = ms / anim.top * (percent - prev); - next = anim.percents[i + 1]; - params = anim.anim[percent]; - break; - } else if (status) { - element.attr(anim.anim[anim.percents[i]]); - } - } - if (!params) { - return; - } - if (!isInAnim) { - for (var attr in params) if (params[has](attr)) { - if (availableAnimAttrs[has](attr) || element.paper.customAttributes[has](attr)) { - from[attr] = element.attr(attr); - (from[attr] == null) && (from[attr] = availableAttrs[attr]); - to[attr] = params[attr]; - switch (availableAnimAttrs[attr]) { - case nu: - diff[attr] = (to[attr] - from[attr]) / ms; - break; - case "colour": - from[attr] = R.getRGB(from[attr]); - var toColour = R.getRGB(to[attr]); - diff[attr] = { - r: (toColour.r - from[attr].r) / ms, - g: (toColour.g - from[attr].g) / ms, - b: (toColour.b - from[attr].b) / ms - }; - break; - case "path": - var pathes = path2curve(from[attr], to[attr]), - toPath = pathes[1]; - from[attr] = pathes[0]; - diff[attr] = []; - for (i = 0, ii = from[attr].length; i < ii; i++) { - diff[attr][i] = [0]; - for (var j = 1, jj = from[attr][i].length; j < jj; j++) { - diff[attr][i][j] = (toPath[i][j] - from[attr][i][j]) / ms; - } - } - break; - case "transform": - var _ = element._, - eq = equaliseTransform(_[attr], to[attr]); - if (eq) { - from[attr] = eq.from; - to[attr] = eq.to; - diff[attr] = []; - diff[attr].real = true; - for (i = 0, ii = from[attr].length; i < ii; i++) { - diff[attr][i] = [from[attr][i][0]]; - for (j = 1, jj = from[attr][i].length; j < jj; j++) { - diff[attr][i][j] = (to[attr][i][j] - from[attr][i][j]) / ms; - } - } - } else { - var m = (element.matrix || new Matrix), - to2 = { - _: {transform: _.transform}, - getBBox: function () { - return element.getBBox(1); - } - }; - from[attr] = [ - m.a, - m.b, - m.c, - m.d, - m.e, - m.f - ]; - extractTransform(to2, to[attr]); - to[attr] = to2._.transform; - diff[attr] = [ - (to2.matrix.a - m.a) / ms, - (to2.matrix.b - m.b) / ms, - (to2.matrix.c - m.c) / ms, - (to2.matrix.d - m.d) / ms, - (to2.matrix.e - m.e) / ms, - (to2.matrix.f - m.f) / ms - ]; - // from[attr] = [_.sx, _.sy, _.deg, _.dx, _.dy]; - // var to2 = {_:{}, getBBox: function () { return element.getBBox(); }}; - // extractTransform(to2, to[attr]); - // diff[attr] = [ - // (to2._.sx - _.sx) / ms, - // (to2._.sy - _.sy) / ms, - // (to2._.deg - _.deg) / ms, - // (to2._.dx - _.dx) / ms, - // (to2._.dy - _.dy) / ms - // ]; - } - break; - case "csv": - var values = Str(params[attr])[split](separator), - from2 = Str(from[attr])[split](separator); - if (attr == "clip-rect") { - from[attr] = from2; - diff[attr] = []; - i = from2.length; - while (i--) { - diff[attr][i] = (values[i] - from[attr][i]) / ms; - } - } - to[attr] = values; - break; - default: - values = [][concat](params[attr]); - from2 = [][concat](from[attr]); - diff[attr] = []; - i = element.paper.customAttributes[attr].length; - while (i--) { - diff[attr][i] = ((values[i] || 0) - (from2[i] || 0)) / ms; - } - break; - } - } - } - var easing = params.easing, - easyeasy = R.easing_formulas[easing]; - if (!easyeasy) { - easyeasy = Str(easing).match(bezierrg); - if (easyeasy && easyeasy.length == 5) { - var curve = easyeasy; - easyeasy = function (t) { - return CubicBezierAtTime(t, +curve[1], +curve[2], +curve[3], +curve[4], ms); - }; - } else { - easyeasy = pipe; - } - } - timestamp = params.start || anim.start || +new Date; - e = { - anim: anim, - percent: percent, - timestamp: timestamp, - start: timestamp + (anim.del || 0), - status: 0, - initstatus: status || 0, - stop: false, - ms: ms, - easing: easyeasy, - from: from, - diff: diff, - to: to, - el: element, - callback: params.callback, - prev: prev, - next: next, - repeat: times || anim.times, - origin: element.attr(), - totalOrigin: totalOrigin - }; - animationElements.push(e); - if (status && !isInAnim && !isInAnimSet) { - e.stop = true; - e.start = new Date - ms * status; - if (animationElements.length == 1) { - return animation(); - } - } - if (isInAnimSet) { - e.start = new Date - e.ms * status; - } - animationElements.length == 1 && requestAnimFrame(animation); - } else { - isInAnim.initstatus = status; - isInAnim.start = new Date - isInAnim.ms * status; - } - eve("raphael.anim.start." + element.id, element, anim); - } - /*\ - * Raphael.animation - [ method ] - ** - * Creates an animation object that can be passed to the @Element.animate or @Element.animateWith methods. - * See also @Animation.delay and @Animation.repeat methods. - ** - > Parameters - ** - - params (object) final attributes for the element, see also @Element.attr - - ms (number) number of milliseconds for animation to run - - easing (string) #optional easing type. Accept one of @Raphael.easing_formulas or CSS format: `cubic‐bezier(XX, XX, XX, XX)` - - callback (function) #optional callback function. Will be called at the end of animation. - ** - = (object) @Animation - \*/ - R.animation = function (params, ms, easing, callback) { - if (params instanceof Animation) { - return params; - } - if (R.is(easing, "function") || !easing) { - callback = callback || easing || null; - easing = null; - } - params = Object(params); - ms = +ms || 0; - var p = {}, - json, - attr; - for (attr in params) if (params[has](attr) && toFloat(attr) != attr && toFloat(attr) + "%" != attr) { - json = true; - p[attr] = params[attr]; - } - if (!json) { - // if percent-like syntax is used and end-of-all animation callback used - if(callback){ - // find the last one - var lastKey = 0; - for(var i in params){ - var percent = toInt(i); - if(params[has](i) && percent > lastKey){ - lastKey = percent; - } - } - lastKey += '%'; - // if already defined callback in the last keyframe, skip - !params[lastKey].callback && (params[lastKey].callback = callback); - } - return new Animation(params, ms); - } else { - easing && (p.easing = easing); - callback && (p.callback = callback); - return new Animation({100: p}, ms); - } - }; - /*\ - * Element.animate - [ method ] - ** - * Creates and starts animation for given element. - ** - > Parameters - ** - - params (object) final attributes for the element, see also @Element.attr - - ms (number) number of milliseconds for animation to run - - easing (string) #optional easing type. Accept one of @Raphael.easing_formulas or CSS format: `cubic‐bezier(XX, XX, XX, XX)` - - callback (function) #optional callback function. Will be called at the end of animation. - * or - - animation (object) animation object, see @Raphael.animation - ** - = (object) original element - \*/ - elproto.animate = function (params, ms, easing, callback) { - var element = this; - if (element.removed) { - callback && callback.call(element); - return element; - } - var anim = params instanceof Animation ? params : R.animation(params, ms, easing, callback); - runAnimation(anim, element, anim.percents[0], null, element.attr()); - return element; - }; - /*\ - * Element.setTime - [ method ] - ** - * Sets the status of animation of the element in milliseconds. Similar to @Element.status method. - ** - > Parameters - ** - - anim (object) animation object - - value (number) number of milliseconds from the beginning of the animation - ** - = (object) original element if `value` is specified - * Note, that during animation following events are triggered: - * - * On each animation frame event `anim.frame.<id>`, on start `anim.start.<id>` and on end `anim.finish.<id>`. - \*/ - elproto.setTime = function (anim, value) { - if (anim && value != null) { - this.status(anim, mmin(value, anim.ms) / anim.ms); - } - return this; - }; - /*\ - * Element.status - [ method ] - ** - * Gets or sets the status of animation of the element. - ** - > Parameters - ** - - anim (object) #optional animation object - - value (number) #optional 0 – 1. If specified, method works like a setter and sets the status of a given animation to the value. This will cause animation to jump to the given position. - ** - = (number) status - * or - = (array) status if `anim` is not specified. Array of objects in format: - o { - o anim: (object) animation object - o status: (number) status - o } - * or - = (object) original element if `value` is specified - \*/ - elproto.status = function (anim, value) { - var out = [], - i = 0, - len, - e; - if (value != null) { - runAnimation(anim, this, -1, mmin(value, 1)); - return this; - } else { - len = animationElements.length; - for (; i < len; i++) { - e = animationElements[i]; - if (e.el.id == this.id && (!anim || e.anim == anim)) { - if (anim) { - return e.status; - } - out.push({ - anim: e.anim, - status: e.status - }); - } - } - if (anim) { - return 0; - } - return out; - } - }; - /*\ - * Element.pause - [ method ] - ** - * Stops animation of the element with ability to resume it later on. - ** - > Parameters - ** - - anim (object) #optional animation object - ** - = (object) original element - \*/ - elproto.pause = function (anim) { - for (var i = 0; i < animationElements.length; i++) if (animationElements[i].el.id == this.id && (!anim || animationElements[i].anim == anim)) { - if (eve("raphael.anim.pause." + this.id, this, animationElements[i].anim) !== false) { - animationElements[i].paused = true; - } - } - return this; - }; - /*\ - * Element.resume - [ method ] - ** - * Resumes animation if it was paused with @Element.pause method. - ** - > Parameters - ** - - anim (object) #optional animation object - ** - = (object) original element - \*/ - elproto.resume = function (anim) { - for (var i = 0; i < animationElements.length; i++) if (animationElements[i].el.id == this.id && (!anim || animationElements[i].anim == anim)) { - var e = animationElements[i]; - if (eve("raphael.anim.resume." + this.id, this, e.anim) !== false) { - delete e.paused; - this.status(e.anim, e.status); - } - } - return this; - }; - /*\ - * Element.stop - [ method ] - ** - * Stops animation of the element. - ** - > Parameters - ** - - anim (object) #optional animation object - ** - = (object) original element - \*/ - elproto.stop = function (anim) { - for (var i = 0; i < animationElements.length; i++) if (animationElements[i].el.id == this.id && (!anim || animationElements[i].anim == anim)) { - if (eve("raphael.anim.stop." + this.id, this, animationElements[i].anim) !== false) { - animationElements.splice(i--, 1); - } - } - return this; - }; - function stopAnimation(paper) { - for (var i = 0; i < animationElements.length; i++) if (animationElements[i].el.paper == paper) { - animationElements.splice(i--, 1); - } - } - eve.on("raphael.remove", stopAnimation); - eve.on("raphael.clear", stopAnimation); - elproto.toString = function () { - return "Rapha\xebl\u2019s object"; - }; - - // Set - var Set = function (items) { - this.items = []; - this.length = 0; - this.type = "set"; - if (items) { - for (var i = 0, ii = items.length; i < ii; i++) { - if (items[i] && (items[i].constructor == elproto.constructor || items[i].constructor == Set)) { - this[this.items.length] = this.items[this.items.length] = items[i]; - this.length++; - } - } - } - }, - setproto = Set.prototype; - /*\ - * Set.push - [ method ] - ** - * Adds each argument to the current set. - = (object) original element - \*/ - setproto.push = function () { - var item, - len; - for (var i = 0, ii = arguments.length; i < ii; i++) { - item = arguments[i]; - if (item && (item.constructor == elproto.constructor || item.constructor == Set)) { - len = this.items.length; - this[len] = this.items[len] = item; - this.length++; - } - } - return this; - }; - /*\ - * Set.pop - [ method ] - ** - * Removes last element and returns it. - = (object) element - \*/ - setproto.pop = function () { - this.length && delete this[this.length--]; - return this.items.pop(); - }; - /*\ - * Set.forEach - [ method ] - ** - * Executes given function for each element in the set. - * - * If function returns `false` it will stop loop running. - ** - > Parameters - ** - - callback (function) function to run - - thisArg (object) context object for the callback - = (object) Set object - \*/ - setproto.forEach = function (callback, thisArg) { - for (var i = 0, ii = this.items.length; i < ii; i++) { - if (callback.call(thisArg, this.items[i], i) === false) { - return this; - } - } - return this; - }; - for (var method in elproto) if (elproto[has](method)) { - setproto[method] = (function (methodname) { - return function () { - var arg = arguments; - return this.forEach(function (el) { - el[methodname][apply](el, arg); - }); - }; - })(method); - } - setproto.attr = function (name, value) { - if (name && R.is(name, array) && R.is(name[0], "object")) { - for (var j = 0, jj = name.length; j < jj; j++) { - this.items[j].attr(name[j]); - } - } else { - for (var i = 0, ii = this.items.length; i < ii; i++) { - this.items[i].attr(name, value); - } - } - return this; - }; - /*\ - * Set.clear - [ method ] - ** - * Removes all elements from the set - \*/ - setproto.clear = function () { - while (this.length) { - this.pop(); - } - }; - /*\ - * Set.splice - [ method ] - ** - * Removes given element from the set - ** - > Parameters - ** - - index (number) position of the deletion - - count (number) number of element to remove - - insertion… (object) #optional elements to insert - = (object) set elements that were deleted - \*/ - setproto.splice = function (index, count, insertion) { - index = index < 0 ? mmax(this.length + index, 0) : index; - count = mmax(0, mmin(this.length - index, count)); - var tail = [], - todel = [], - args = [], - i; - for (i = 2; i < arguments.length; i++) { - args.push(arguments[i]); - } - for (i = 0; i < count; i++) { - todel.push(this[index + i]); - } - for (; i < this.length - index; i++) { - tail.push(this[index + i]); - } - var arglen = args.length; - for (i = 0; i < arglen + tail.length; i++) { - this.items[index + i] = this[index + i] = i < arglen ? args[i] : tail[i - arglen]; - } - i = this.items.length = this.length -= count - arglen; - while (this[i]) { - delete this[i++]; - } - return new Set(todel); - }; - /*\ - * Set.exclude - [ method ] - ** - * Removes given element from the set - ** - > Parameters - ** - - element (object) element to remove - = (boolean) `true` if object was found & removed from the set - \*/ - setproto.exclude = function (el) { - for (var i = 0, ii = this.length; i < ii; i++) if (this[i] == el) { - this.splice(i, 1); - return true; - } - }; - setproto.animate = function (params, ms, easing, callback) { - (R.is(easing, "function") || !easing) && (callback = easing || null); - var len = this.items.length, - i = len, - item, - set = this, - collector; - if (!len) { - return this; - } - callback && (collector = function () { - !--len && callback.call(set); - }); - easing = R.is(easing, string) ? easing : collector; - var anim = R.animation(params, ms, easing, collector); - item = this.items[--i].animate(anim); - while (i--) { - this.items[i] && !this.items[i].removed && this.items[i].animateWith(item, anim, anim); - (this.items[i] && !this.items[i].removed) || len--; - } - return this; - }; - setproto.insertAfter = function (el) { - var i = this.items.length; - while (i--) { - this.items[i].insertAfter(el); - } - return this; - }; - setproto.getBBox = function () { - var x = [], - y = [], - x2 = [], - y2 = []; - for (var i = this.items.length; i--;) if (!this.items[i].removed) { - var box = this.items[i].getBBox(); - x.push(box.x); - y.push(box.y); - x2.push(box.x + box.width); - y2.push(box.y + box.height); - } - x = mmin[apply](0, x); - y = mmin[apply](0, y); - x2 = mmax[apply](0, x2); - y2 = mmax[apply](0, y2); - return { - x: x, - y: y, - x2: x2, - y2: y2, - width: x2 - x, - height: y2 - y - }; - }; - setproto.clone = function (s) { - s = this.paper.set(); - for (var i = 0, ii = this.items.length; i < ii; i++) { - s.push(this.items[i].clone()); - } - return s; - }; - setproto.toString = function () { - return "Rapha\xebl\u2018s set"; - }; - - setproto.glow = function(glowConfig) { - var ret = this.paper.set(); - this.forEach(function(shape, index){ - var g = shape.glow(glowConfig); - if(g != null){ - g.forEach(function(shape2, index2){ - ret.push(shape2); - }); - } - }); - return ret; - }; - - - /*\ - * Set.isPointInside - [ method ] - ** - * Determine if given point is inside this set’s elements - ** - > Parameters - ** - - x (number) x coordinate of the point - - y (number) y coordinate of the point - = (boolean) `true` if point is inside any of the set's elements - \*/ - setproto.isPointInside = function (x, y) { - var isPointInside = false; - this.forEach(function (el) { - if (el.isPointInside(x, y)) { - isPointInside = true; - return false; // stop loop - } - }); - return isPointInside; - }; - - /*\ - * Raphael.registerFont - [ method ] - ** - * Adds given font to the registered set of fonts for Raphaël. Should be used as an internal call from within Cufón’s font file. - * Returns original parameter, so it could be used with chaining. - # <a href="http://wiki.github.com/sorccu/cufon/about">More about Cufón and how to convert your font form TTF, OTF, etc to JavaScript file.</a> - ** - > Parameters - ** - - font (object) the font to register - = (object) the font you passed in - > Usage - | Cufon.registerFont(Raphael.registerFont({…})); - \*/ - R.registerFont = function (font) { - if (!font.face) { - return font; - } - this.fonts = this.fonts || {}; - var fontcopy = { - w: font.w, - face: {}, - glyphs: {} - }, - family = font.face["font-family"]; - for (var prop in font.face) if (font.face[has](prop)) { - fontcopy.face[prop] = font.face[prop]; - } - if (this.fonts[family]) { - this.fonts[family].push(fontcopy); - } else { - this.fonts[family] = [fontcopy]; - } - if (!font.svg) { - fontcopy.face["units-per-em"] = toInt(font.face["units-per-em"], 10); - for (var glyph in font.glyphs) if (font.glyphs[has](glyph)) { - var path = font.glyphs[glyph]; - fontcopy.glyphs[glyph] = { - w: path.w, - k: {}, - d: path.d && "M" + path.d.replace(/[mlcxtrv]/g, function (command) { - return {l: "L", c: "C", x: "z", t: "m", r: "l", v: "c"}[command] || "M"; - }) + "z" - }; - if (path.k) { - for (var k in path.k) if (path[has](k)) { - fontcopy.glyphs[glyph].k[k] = path.k[k]; - } - } - } - } - return font; - }; - /*\ - * Paper.getFont - [ method ] - ** - * Finds font object in the registered fonts by given parameters. You could specify only one word from the font name, like “Myriad” for “Myriad Pro”. - ** - > Parameters - ** - - family (string) font family name or any word from it - - weight (string) #optional font weight - - style (string) #optional font style - - stretch (string) #optional font stretch - = (object) the font object - > Usage - | paper.print(100, 100, "Test string", paper.getFont("Times", 800), 30); - \*/ - paperproto.getFont = function (family, weight, style, stretch) { - stretch = stretch || "normal"; - style = style || "normal"; - weight = +weight || {normal: 400, bold: 700, lighter: 300, bolder: 800}[weight] || 400; - if (!R.fonts) { - return; - } - var font = R.fonts[family]; - if (!font) { - var name = new RegExp("(^|\\s)" + family.replace(/[^\w\d\s+!~.:_-]/g, E) + "(\\s|$)", "i"); - for (var fontName in R.fonts) if (R.fonts[has](fontName)) { - if (name.test(fontName)) { - font = R.fonts[fontName]; - break; - } - } - } - var thefont; - if (font) { - for (var i = 0, ii = font.length; i < ii; i++) { - thefont = font[i]; - if (thefont.face["font-weight"] == weight && (thefont.face["font-style"] == style || !thefont.face["font-style"]) && thefont.face["font-stretch"] == stretch) { - break; - } - } - } - return thefont; - }; - /*\ - * Paper.print - [ method ] - ** - * Creates path that represent given text written using given font at given position with given size. - * Result of the method is path element that contains whole text as a separate path. - ** - > Parameters - ** - - x (number) x position of the text - - y (number) y position of the text - - string (string) text to print - - font (object) font object, see @Paper.getFont - - size (number) #optional size of the font, default is `16` - - origin (string) #optional could be `"baseline"` or `"middle"`, default is `"middle"` - - letter_spacing (number) #optional number in range `-1..1`, default is `0` - - line_spacing (number) #optional number in range `1..3`, default is `1` - = (object) resulting path element, which consist of all letters - > Usage - | var txt = r.print(10, 50, "print", r.getFont("Museo"), 30).attr({fill: "#fff"}); - \*/ - paperproto.print = function (x, y, string, font, size, origin, letter_spacing, line_spacing) { - origin = origin || "middle"; // baseline|middle - letter_spacing = mmax(mmin(letter_spacing || 0, 1), -1); - line_spacing = mmax(mmin(line_spacing || 1, 3), 1); - var letters = Str(string)[split](E), - shift = 0, - notfirst = 0, - path = E, - scale; - R.is(font, "string") && (font = this.getFont(font)); - if (font) { - scale = (size || 16) / font.face["units-per-em"]; - var bb = font.face.bbox[split](separator), - top = +bb[0], - lineHeight = bb[3] - bb[1], - shifty = 0, - height = +bb[1] + (origin == "baseline" ? lineHeight + (+font.face.descent) : lineHeight / 2); - for (var i = 0, ii = letters.length; i < ii; i++) { - if (letters[i] == "\n") { - shift = 0; - curr = 0; - notfirst = 0; - shifty += lineHeight * line_spacing; - } else { - var prev = notfirst && font.glyphs[letters[i - 1]] || {}, - curr = font.glyphs[letters[i]]; - shift += notfirst ? (prev.w || font.w) + (prev.k && prev.k[letters[i]] || 0) + (font.w * letter_spacing) : 0; - notfirst = 1; - } - if (curr && curr.d) { - path += R.transformPath(curr.d, ["t", shift * scale, shifty * scale, "s", scale, scale, top, height, "t", (x - top) / scale, (y - height) / scale]); - } - } - } - return this.path(path).attr({ - fill: "#000", - stroke: "none" - }); - }; - - /*\ - * Paper.add - [ method ] - ** - * Imports elements in JSON array in format `{type: type, <attributes>}` - ** - > Parameters - ** - - json (array) - = (object) resulting set of imported elements - > Usage - | paper.add([ - | { - | type: "circle", - | cx: 10, - | cy: 10, - | r: 5 - | }, - | { - | type: "rect", - | x: 10, - | y: 10, - | width: 10, - | height: 10, - | fill: "#fc0" - | } - | ]); - \*/ - paperproto.add = function (json) { - if (R.is(json, "array")) { - var res = this.set(), - i = 0, - ii = json.length, - j; - for (; i < ii; i++) { - j = json[i] || {}; - elements[has](j.type) && res.push(this[j.type]().attr(j)); - } - } - return res; - }; - - /*\ - * Raphael.format - [ method ] - ** - * Simple format function. Replaces construction of type “`{<number>}`” to the corresponding argument. - ** - > Parameters - ** - - token (string) string to format - - … (string) rest of arguments will be treated as parameters for replacement - = (string) formated string - > Usage - | var x = 10, - | y = 20, - | width = 40, - | height = 50; - | // this will draw a rectangular shape equivalent to "M10,20h40v50h-40z" - | paper.path(Raphael.format("M{0},{1}h{2}v{3}h{4}z", x, y, width, height, -width)); - \*/ - R.format = function (token, params) { - var args = R.is(params, array) ? [0][concat](params) : arguments; - token && R.is(token, string) && args.length - 1 && (token = token.replace(formatrg, function (str, i) { - return args[++i] == null ? E : args[i]; - })); - return token || E; - }; - /*\ - * Raphael.fullfill - [ method ] - ** - * A little bit more advanced format function than @Raphael.format. Replaces construction of type “`{<name>}`” to the corresponding argument. - ** - > Parameters - ** - - token (string) string to format - - json (object) object which properties will be used as a replacement - = (string) formated string - > Usage - | // this will draw a rectangular shape equivalent to "M10,20h40v50h-40z" - | paper.path(Raphael.fullfill("M{x},{y}h{dim.width}v{dim.height}h{dim['negative width']}z", { - | x: 10, - | y: 20, - | dim: { - | width: 40, - | height: 50, - | "negative width": -40 - | } - | })); - \*/ - R.fullfill = (function () { - var tokenRegex = /\{([^\}]+)\}/g, - objNotationRegex = /(?:(?:^|\.)(.+?)(?=\[|\.|$|\()|\[('|")(.+?)\2\])(\(\))?/g, // matches .xxxxx or ["xxxxx"] to run over object properties - replacer = function (all, key, obj) { - var res = obj; - key.replace(objNotationRegex, function (all, name, quote, quotedName, isFunc) { - name = name || quotedName; - if (res) { - if (name in res) { - res = res[name]; - } - typeof res == "function" && isFunc && (res = res()); - } - }); - res = (res == null || res == obj ? all : res) + ""; - return res; - }; - return function (str, obj) { - return String(str).replace(tokenRegex, function (all, key) { - return replacer(all, key, obj); - }); - }; - })(); - /*\ - * Raphael.ninja - [ method ] - ** - * If you want to leave no trace of Raphaël (Well, Raphaël creates only one global variable `Raphael`, but anyway.) You can use `ninja` method. - * Beware, that in this case plugins could stop working, because they are depending on global variable existance. - ** - = (object) Raphael object - > Usage - | (function (local_raphael) { - | var paper = local_raphael(10, 10, 320, 200); - | … - | })(Raphael.ninja()); - \*/ - R.ninja = function () { - oldRaphael.was ? (g.win.Raphael = oldRaphael.is) : delete Raphael; - return R; - }; - /*\ - * Raphael.st - [ property (object) ] - ** - * You can add your own method to elements and sets. It is wise to add a set method for each element method - * you added, so you will be able to call the same method on sets too. - ** - * See also @Raphael.el. - > Usage - | Raphael.el.red = function () { - | this.attr({fill: "#f00"}); - | }; - | Raphael.st.red = function () { - | this.forEach(function (el) { - | el.red(); - | }); - | }; - | // then use it - | paper.set(paper.circle(100, 100, 20), paper.circle(110, 100, 20)).red(); - \*/ - R.st = setproto; - - eve.on("raphael.DOMload", function () { - loaded = true; - }); - - // Firefox <3.6 fix: http://webreflection.blogspot.com/2009/11/195-chars-to-help-lazy-loading.html - (function (doc, loaded, f) { - if (doc.readyState == null && doc.addEventListener){ - doc.addEventListener(loaded, f = function () { - doc.removeEventListener(loaded, f, false); - doc.readyState = "complete"; - }, false); - doc.readyState = "loading"; - } - function isLoaded() { - (/in/).test(doc.readyState) ? setTimeout(isLoaded, 9) : R.eve("raphael.DOMload"); - } - isLoaded(); - })(document, "DOMContentLoaded"); - -// ┌─────────────────────────────────────────────────────────────────────┐ \\ -// │ Raphaël - JavaScript Vector Library │ \\ -// ├─────────────────────────────────────────────────────────────────────┤ \\ -// │ SVG Module │ \\ -// ├─────────────────────────────────────────────────────────────────────┤ \\ -// │ Copyright (c) 2008-2011 Dmitry Baranovskiy (http://raphaeljs.com) │ \\ -// │ Copyright (c) 2008-2011 Sencha Labs (http://sencha.com) │ \\ -// │ Licensed under the MIT (http://raphaeljs.com/license.html) license. │ \\ -// └─────────────────────────────────────────────────────────────────────┘ \\ - -(function(){ - if (!R.svg) { - return; - } - var has = "hasOwnProperty", - Str = String, - toFloat = parseFloat, - toInt = parseInt, - math = Math, - mmax = math.max, - abs = math.abs, - pow = math.pow, - separator = /[, ]+/, - eve = R.eve, - E = "", - S = " "; - var xlink = "http://www.w3.org/1999/xlink", - markers = { - block: "M5,0 0,2.5 5,5z", - classic: "M5,0 0,2.5 5,5 3.5,3 3.5,2z", - diamond: "M2.5,0 5,2.5 2.5,5 0,2.5z", - open: "M6,1 1,3.5 6,6", - oval: "M2.5,0A2.5,2.5,0,0,1,2.5,5 2.5,2.5,0,0,1,2.5,0z" - }, - markerCounter = {}; - R.toString = function () { - return "Your browser supports SVG.\nYou are running Rapha\xebl " + this.version; - }; - var $ = function (el, attr) { - if (attr) { - if (typeof el == "string") { - el = $(el); - } - for (var key in attr) if (attr[has](key)) { - if (key.substring(0, 6) == "xlink:") { - el.setAttributeNS(xlink, key.substring(6), Str(attr[key])); - } else { - el.setAttribute(key, Str(attr[key])); - } - } - } else { - el = R._g.doc.createElementNS("http://www.w3.org/2000/svg", el); - el.style && (el.style.webkitTapHighlightColor = "rgba(0,0,0,0)"); - } - return el; - }, - addGradientFill = function (element, gradient) { - var type = "linear", - id = element.id + gradient, - fx = .5, fy = .5, - o = element.node, - SVG = element.paper, - s = o.style, - el = R._g.doc.getElementById(id); - if (!el) { - gradient = Str(gradient).replace(R._radial_gradient, function (all, _fx, _fy) { - type = "radial"; - if (_fx && _fy) { - fx = toFloat(_fx); - fy = toFloat(_fy); - var dir = ((fy > .5) * 2 - 1); - pow(fx - .5, 2) + pow(fy - .5, 2) > .25 && - (fy = math.sqrt(.25 - pow(fx - .5, 2)) * dir + .5) && - fy != .5 && - (fy = fy.toFixed(5) - 1e-5 * dir); - } - return E; - }); - gradient = gradient.split(/\s*\-\s*/); - if (type == "linear") { - var angle = gradient.shift(); - angle = -toFloat(angle); - if (isNaN(angle)) { - return null; - } - var vector = [0, 0, math.cos(R.rad(angle)), math.sin(R.rad(angle))], - max = 1 / (mmax(abs(vector[2]), abs(vector[3])) || 1); - vector[2] *= max; - vector[3] *= max; - if (vector[2] < 0) { - vector[0] = -vector[2]; - vector[2] = 0; - } - if (vector[3] < 0) { - vector[1] = -vector[3]; - vector[3] = 0; - } - } - var dots = R._parseDots(gradient); - if (!dots) { - return null; - } - id = id.replace(/[\(\)\s,\xb0#]/g, "_"); - - if (element.gradient && id != element.gradient.id) { - SVG.defs.removeChild(element.gradient); - delete element.gradient; - } - - if (!element.gradient) { - el = $(type + "Gradient", {id: id}); - element.gradient = el; - $(el, type == "radial" ? { - fx: fx, - fy: fy - } : { - x1: vector[0], - y1: vector[1], - x2: vector[2], - y2: vector[3], - gradientTransform: element.matrix.invert() - }); - SVG.defs.appendChild(el); - for (var i = 0, ii = dots.length; i < ii; i++) { - el.appendChild($("stop", { - offset: dots[i].offset ? dots[i].offset : i ? "100%" : "0%", - "stop-color": dots[i].color || "#fff" - })); - } - } - } - $(o, { - fill: "url('" + document.location + "#" + id + "')", - opacity: 1, - "fill-opacity": 1 - }); - s.fill = E; - s.opacity = 1; - s.fillOpacity = 1; - return 1; - }, - updatePosition = function (o) { - var bbox = o.getBBox(1); - $(o.pattern, {patternTransform: o.matrix.invert() + " translate(" + bbox.x + "," + bbox.y + ")"}); - }, - addArrow = function (o, value, isEnd) { - if (o.type == "path") { - var values = Str(value).toLowerCase().split("-"), - p = o.paper, - se = isEnd ? "end" : "start", - node = o.node, - attrs = o.attrs, - stroke = attrs["stroke-width"], - i = values.length, - type = "classic", - from, - to, - dx, - refX, - attr, - w = 3, - h = 3, - t = 5; - while (i--) { - switch (values[i]) { - case "block": - case "classic": - case "oval": - case "diamond": - case "open": - case "none": - type = values[i]; - break; - case "wide": h = 5; break; - case "narrow": h = 2; break; - case "long": w = 5; break; - case "short": w = 2; break; - } - } - if (type == "open") { - w += 2; - h += 2; - t += 2; - dx = 1; - refX = isEnd ? 4 : 1; - attr = { - fill: "none", - stroke: attrs.stroke - }; - } else { - refX = dx = w / 2; - attr = { - fill: attrs.stroke, - stroke: "none" - }; - } - if (o._.arrows) { - if (isEnd) { - o._.arrows.endPath && markerCounter[o._.arrows.endPath]--; - o._.arrows.endMarker && markerCounter[o._.arrows.endMarker]--; - } else { - o._.arrows.startPath && markerCounter[o._.arrows.startPath]--; - o._.arrows.startMarker && markerCounter[o._.arrows.startMarker]--; - } - } else { - o._.arrows = {}; - } - if (type != "none") { - var pathId = "raphael-marker-" + type, - markerId = "raphael-marker-" + se + type + w + h + "-obj" + o.id; - if (!R._g.doc.getElementById(pathId)) { - p.defs.appendChild($($("path"), { - "stroke-linecap": "round", - d: markers[type], - id: pathId - })); - markerCounter[pathId] = 1; - } else { - markerCounter[pathId]++; - } - var marker = R._g.doc.getElementById(markerId), - use; - if (!marker) { - marker = $($("marker"), { - id: markerId, - markerHeight: h, - markerWidth: w, - orient: "auto", - refX: refX, - refY: h / 2 - }); - use = $($("use"), { - "xlink:href": "#" + pathId, - transform: (isEnd ? "rotate(180 " + w / 2 + " " + h / 2 + ") " : E) + "scale(" + w / t + "," + h / t + ")", - "stroke-width": (1 / ((w / t + h / t) / 2)).toFixed(4) - }); - marker.appendChild(use); - p.defs.appendChild(marker); - markerCounter[markerId] = 1; - } else { - markerCounter[markerId]++; - use = marker.getElementsByTagName("use")[0]; - } - $(use, attr); - var delta = dx * (type != "diamond" && type != "oval"); - if (isEnd) { - from = o._.arrows.startdx * stroke || 0; - to = R.getTotalLength(attrs.path) - delta * stroke; - } else { - from = delta * stroke; - to = R.getTotalLength(attrs.path) - (o._.arrows.enddx * stroke || 0); - } - attr = {}; - attr["marker-" + se] = "url(#" + markerId + ")"; - if (to || from) { - attr.d = R.getSubpath(attrs.path, from, to); - } - $(node, attr); - o._.arrows[se + "Path"] = pathId; - o._.arrows[se + "Marker"] = markerId; - o._.arrows[se + "dx"] = delta; - o._.arrows[se + "Type"] = type; - o._.arrows[se + "String"] = value; - } else { - if (isEnd) { - from = o._.arrows.startdx * stroke || 0; - to = R.getTotalLength(attrs.path) - from; - } else { - from = 0; - to = R.getTotalLength(attrs.path) - (o._.arrows.enddx * stroke || 0); - } - o._.arrows[se + "Path"] && $(node, {d: R.getSubpath(attrs.path, from, to)}); - delete o._.arrows[se + "Path"]; - delete o._.arrows[se + "Marker"]; - delete o._.arrows[se + "dx"]; - delete o._.arrows[se + "Type"]; - delete o._.arrows[se + "String"]; - } - for (attr in markerCounter) if (markerCounter[has](attr) && !markerCounter[attr]) { - var item = R._g.doc.getElementById(attr); - item && item.parentNode.removeChild(item); - } - } - }, - dasharray = { - "": [0], - "none": [0], - "-": [3, 1], - ".": [1, 1], - "-.": [3, 1, 1, 1], - "-..": [3, 1, 1, 1, 1, 1], - ". ": [1, 3], - "- ": [4, 3], - "--": [8, 3], - "- .": [4, 3, 1, 3], - "--.": [8, 3, 1, 3], - "--..": [8, 3, 1, 3, 1, 3] - }, - addDashes = function (o, value, params) { - value = dasharray[Str(value).toLowerCase()]; - if (value) { - var width = o.attrs["stroke-width"] || "1", - butt = {round: width, square: width, butt: 0}[o.attrs["stroke-linecap"] || params["stroke-linecap"]] || 0, - dashes = [], - i = value.length; - while (i--) { - dashes[i] = value[i] * width + ((i % 2) ? 1 : -1) * butt; - } - $(o.node, {"stroke-dasharray": dashes.join(",")}); - } - }, - setFillAndStroke = function (o, params) { - var node = o.node, - attrs = o.attrs, - vis = node.style.visibility; - node.style.visibility = "hidden"; - for (var att in params) { - if (params[has](att)) { - if (!R._availableAttrs[has](att)) { - continue; - } - var value = params[att]; - attrs[att] = value; - switch (att) { - case "blur": - o.blur(value); - break; - case "title": - var title = node.getElementsByTagName("title"); - - // Use the existing <title>. - if (title.length && (title = title[0])) { - title.firstChild.nodeValue = value; - } else { - title = $("title"); - var val = R._g.doc.createTextNode(value); - title.appendChild(val); - node.appendChild(title); - } - break; - case "href": - case "target": - var pn = node.parentNode; - if (pn.tagName.toLowerCase() != "a") { - var hl = $("a"); - pn.insertBefore(hl, node); - hl.appendChild(node); - pn = hl; - } - if (att == "target") { - pn.setAttributeNS(xlink, "show", value == "blank" ? "new" : value); - } else { - pn.setAttributeNS(xlink, att, value); - } - break; - case "cursor": - node.style.cursor = value; - break; - case "transform": - o.transform(value); - break; - case "arrow-start": - addArrow(o, value); - break; - case "arrow-end": - addArrow(o, value, 1); - break; - case "clip-rect": - var rect = Str(value).split(separator); - if (rect.length == 4) { - o.clip && o.clip.parentNode.parentNode.removeChild(o.clip.parentNode); - var el = $("clipPath"), - rc = $("rect"); - el.id = R.createUUID(); - $(rc, { - x: rect[0], - y: rect[1], - width: rect[2], - height: rect[3] - }); - el.appendChild(rc); - o.paper.defs.appendChild(el); - $(node, {"clip-path": "url(#" + el.id + ")"}); - o.clip = rc; - } - if (!value) { - var path = node.getAttribute("clip-path"); - if (path) { - var clip = R._g.doc.getElementById(path.replace(/(^url\(#|\)$)/g, E)); - clip && clip.parentNode.removeChild(clip); - $(node, {"clip-path": E}); - delete o.clip; - } - } - break; - case "path": - if (o.type == "path") { - $(node, {d: value ? attrs.path = R._pathToAbsolute(value) : "M0,0"}); - o._.dirty = 1; - if (o._.arrows) { - "startString" in o._.arrows && addArrow(o, o._.arrows.startString); - "endString" in o._.arrows && addArrow(o, o._.arrows.endString, 1); - } - } - break; - case "width": - node.setAttribute(att, value); - o._.dirty = 1; - if (attrs.fx) { - att = "x"; - value = attrs.x; - } else { - break; - } - case "x": - if (attrs.fx) { - value = -attrs.x - (attrs.width || 0); - } - case "rx": - if (att == "rx" && o.type == "rect") { - break; - } - case "cx": - node.setAttribute(att, value); - o.pattern && updatePosition(o); - o._.dirty = 1; - break; - case "height": - node.setAttribute(att, value); - o._.dirty = 1; - if (attrs.fy) { - att = "y"; - value = attrs.y; - } else { - break; - } - case "y": - if (attrs.fy) { - value = -attrs.y - (attrs.height || 0); - } - case "ry": - if (att == "ry" && o.type == "rect") { - break; - } - case "cy": - node.setAttribute(att, value); - o.pattern && updatePosition(o); - o._.dirty = 1; - break; - case "r": - if (o.type == "rect") { - $(node, {rx: value, ry: value}); - } else { - node.setAttribute(att, value); - } - o._.dirty = 1; - break; - case "src": - if (o.type == "image") { - node.setAttributeNS(xlink, "href", value); - } - break; - case "stroke-width": - if (o._.sx != 1 || o._.sy != 1) { - value /= mmax(abs(o._.sx), abs(o._.sy)) || 1; - } - node.setAttribute(att, value); - if (attrs["stroke-dasharray"]) { - addDashes(o, attrs["stroke-dasharray"], params); - } - if (o._.arrows) { - "startString" in o._.arrows && addArrow(o, o._.arrows.startString); - "endString" in o._.arrows && addArrow(o, o._.arrows.endString, 1); - } - break; - case "stroke-dasharray": - addDashes(o, value, params); - break; - case "fill": - var isURL = Str(value).match(R._ISURL); - if (isURL) { - el = $("pattern"); - var ig = $("image"); - el.id = R.createUUID(); - $(el, {x: 0, y: 0, patternUnits: "userSpaceOnUse", height: 1, width: 1}); - $(ig, {x: 0, y: 0, "xlink:href": isURL[1]}); - el.appendChild(ig); - - (function (el) { - R._preload(isURL[1], function () { - var w = this.offsetWidth, - h = this.offsetHeight; - $(el, {width: w, height: h}); - $(ig, {width: w, height: h}); - o.paper.safari(); - }); - })(el); - o.paper.defs.appendChild(el); - $(node, {fill: "url(#" + el.id + ")"}); - o.pattern = el; - o.pattern && updatePosition(o); - break; - } - var clr = R.getRGB(value); - if (!clr.error) { - delete params.gradient; - delete attrs.gradient; - !R.is(attrs.opacity, "undefined") && - R.is(params.opacity, "undefined") && - $(node, {opacity: attrs.opacity}); - !R.is(attrs["fill-opacity"], "undefined") && - R.is(params["fill-opacity"], "undefined") && - $(node, {"fill-opacity": attrs["fill-opacity"]}); - } else if ((o.type == "circle" || o.type == "ellipse" || Str(value).charAt() != "r") && addGradientFill(o, value)) { - if ("opacity" in attrs || "fill-opacity" in attrs) { - var gradient = R._g.doc.getElementById(node.getAttribute("fill").replace(/^url\(#|\)$/g, E)); - if (gradient) { - var stops = gradient.getElementsByTagName("stop"); - $(stops[stops.length - 1], {"stop-opacity": ("opacity" in attrs ? attrs.opacity : 1) * ("fill-opacity" in attrs ? attrs["fill-opacity"] : 1)}); - } - } - attrs.gradient = value; - attrs.fill = "none"; - break; - } - clr[has]("opacity") && $(node, {"fill-opacity": clr.opacity > 1 ? clr.opacity / 100 : clr.opacity}); - case "stroke": - clr = R.getRGB(value); - node.setAttribute(att, clr.hex); - att == "stroke" && clr[has]("opacity") && $(node, {"stroke-opacity": clr.opacity > 1 ? clr.opacity / 100 : clr.opacity}); - if (att == "stroke" && o._.arrows) { - "startString" in o._.arrows && addArrow(o, o._.arrows.startString); - "endString" in o._.arrows && addArrow(o, o._.arrows.endString, 1); - } - break; - case "gradient": - (o.type == "circle" || o.type == "ellipse" || Str(value).charAt() != "r") && addGradientFill(o, value); - break; - case "opacity": - if (attrs.gradient && !attrs[has]("stroke-opacity")) { - $(node, {"stroke-opacity": value > 1 ? value / 100 : value}); - } - // fall - case "fill-opacity": - if (attrs.gradient) { - gradient = R._g.doc.getElementById(node.getAttribute("fill").replace(/^url\(#|\)$/g, E)); - if (gradient) { - stops = gradient.getElementsByTagName("stop"); - $(stops[stops.length - 1], {"stop-opacity": value}); - } - break; - } - default: - att == "font-size" && (value = toInt(value, 10) + "px"); - var cssrule = att.replace(/(\-.)/g, function (w) { - return w.substring(1).toUpperCase(); - }); - node.style[cssrule] = value; - o._.dirty = 1; - node.setAttribute(att, value); - break; - } - } - } - - tuneText(o, params); - node.style.visibility = vis; - }, - leading = 1.2, - tuneText = function (el, params) { - if (el.type != "text" || !(params[has]("text") || params[has]("font") || params[has]("font-size") || params[has]("x") || params[has]("y"))) { - return; - } - var a = el.attrs, - node = el.node, - fontSize = node.firstChild ? toInt(R._g.doc.defaultView.getComputedStyle(node.firstChild, E).getPropertyValue("font-size"), 10) : 10; - - if (params[has]("text")) { - a.text = params.text; - while (node.firstChild) { - node.removeChild(node.firstChild); - } - var texts = Str(params.text).split("\n"), - tspans = [], - tspan; - for (var i = 0, ii = texts.length; i < ii; i++) { - tspan = $("tspan"); - i && $(tspan, {dy: fontSize * leading, x: a.x}); - tspan.appendChild(R._g.doc.createTextNode(texts[i])); - node.appendChild(tspan); - tspans[i] = tspan; - } - } else { - tspans = node.getElementsByTagName("tspan"); - for (i = 0, ii = tspans.length; i < ii; i++) if (i) { - $(tspans[i], {dy: fontSize * leading, x: a.x}); - } else { - $(tspans[0], {dy: 0}); - } - } - $(node, {x: a.x, y: a.y}); - el._.dirty = 1; - var bb = el._getBBox(), - dif = a.y - (bb.y + bb.height / 2); - dif && R.is(dif, "finite") && $(tspans[0], {dy: dif}); - }, - getRealNode = function (node) { - if (node.parentNode && node.parentNode.tagName.toLowerCase() === "a") { - return node.parentNode; - } else { - return node; - } - }, - Element = function (node, svg) { - var X = 0, - Y = 0; - /*\ - * Element.node - [ property (object) ] - ** - * Gives you a reference to the DOM object, so you can assign event handlers or just mess around. - ** - * Note: Don’t mess with it. - > Usage - | // draw a circle at coordinate 10,10 with radius of 10 - | var c = paper.circle(10, 10, 10); - | c.node.onclick = function () { - | c.attr("fill", "red"); - | }; - \*/ - this[0] = this.node = node; - /*\ - * Element.raphael - [ property (object) ] - ** - * Internal reference to @Raphael object. In case it is not available. - > Usage - | Raphael.el.red = function () { - | var hsb = this.paper.raphael.rgb2hsb(this.attr("fill")); - | hsb.h = 1; - | this.attr({fill: this.paper.raphael.hsb2rgb(hsb).hex}); - | } - \*/ - node.raphael = true; - /*\ - * Element.id - [ property (number) ] - ** - * Unique id of the element. Especially useful when you want to listen to events of the element, - * because all events are fired in format `<module>.<action>.<id>`. Also useful for @Paper.getById method. - \*/ - this.id = R._oid++; - node.raphaelid = this.id; - this.matrix = R.matrix(); - this.realPath = null; - /*\ - * Element.paper - [ property (object) ] - ** - * Internal reference to “paper” where object drawn. Mainly for use in plugins and element extensions. - > Usage - | Raphael.el.cross = function () { - | this.attr({fill: "red"}); - | this.paper.path("M10,10L50,50M50,10L10,50") - | .attr({stroke: "red"}); - | } - \*/ - this.paper = svg; - this.attrs = this.attrs || {}; - this._ = { - transform: [], - sx: 1, - sy: 1, - deg: 0, - dx: 0, - dy: 0, - dirty: 1 - }; - !svg.bottom && (svg.bottom = this); - /*\ - * Element.prev - [ property (object) ] - ** - * Reference to the previous element in the hierarchy. - \*/ - this.prev = svg.top; - svg.top && (svg.top.next = this); - svg.top = this; - /*\ - * Element.next - [ property (object) ] - ** - * Reference to the next element in the hierarchy. - \*/ - this.next = null; - }, - elproto = R.el; - - Element.prototype = elproto; - elproto.constructor = Element; - - R._engine.path = function (pathString, SVG) { - var el = $("path"); - SVG.canvas && SVG.canvas.appendChild(el); - var p = new Element(el, SVG); - p.type = "path"; - setFillAndStroke(p, { - fill: "none", - stroke: "#000", - path: pathString - }); - return p; - }; - /*\ - * Element.rotate - [ method ] - ** - * Deprecated! Use @Element.transform instead. - * Adds rotation by given angle around given point to the list of - * transformations of the element. - > Parameters - - deg (number) angle in degrees - - cx (number) #optional x coordinate of the centre of rotation - - cy (number) #optional y coordinate of the centre of rotation - * If cx & cy aren’t specified centre of the shape is used as a point of rotation. - = (object) @Element - \*/ - elproto.rotate = function (deg, cx, cy) { - if (this.removed) { - return this; - } - deg = Str(deg).split(separator); - if (deg.length - 1) { - cx = toFloat(deg[1]); - cy = toFloat(deg[2]); - } - deg = toFloat(deg[0]); - (cy == null) && (cx = cy); - if (cx == null || cy == null) { - var bbox = this.getBBox(1); - cx = bbox.x + bbox.width / 2; - cy = bbox.y + bbox.height / 2; - } - this.transform(this._.transform.concat([["r", deg, cx, cy]])); - return this; - }; - /*\ - * Element.scale - [ method ] - ** - * Deprecated! Use @Element.transform instead. - * Adds scale by given amount relative to given point to the list of - * transformations of the element. - > Parameters - - sx (number) horisontal scale amount - - sy (number) vertical scale amount - - cx (number) #optional x coordinate of the centre of scale - - cy (number) #optional y coordinate of the centre of scale - * If cx & cy aren’t specified centre of the shape is used instead. - = (object) @Element - \*/ - elproto.scale = function (sx, sy, cx, cy) { - if (this.removed) { - return this; - } - sx = Str(sx).split(separator); - if (sx.length - 1) { - sy = toFloat(sx[1]); - cx = toFloat(sx[2]); - cy = toFloat(sx[3]); - } - sx = toFloat(sx[0]); - (sy == null) && (sy = sx); - (cy == null) && (cx = cy); - if (cx == null || cy == null) { - var bbox = this.getBBox(1); - } - cx = cx == null ? bbox.x + bbox.width / 2 : cx; - cy = cy == null ? bbox.y + bbox.height / 2 : cy; - this.transform(this._.transform.concat([["s", sx, sy, cx, cy]])); - return this; - }; - /*\ - * Element.translate - [ method ] - ** - * Deprecated! Use @Element.transform instead. - * Adds translation by given amount to the list of transformations of the element. - > Parameters - - dx (number) horisontal shift - - dy (number) vertical shift - = (object) @Element - \*/ - elproto.translate = function (dx, dy) { - if (this.removed) { - return this; - } - dx = Str(dx).split(separator); - if (dx.length - 1) { - dy = toFloat(dx[1]); - } - dx = toFloat(dx[0]) || 0; - dy = +dy || 0; - this.transform(this._.transform.concat([["t", dx, dy]])); - return this; - }; - /*\ - * Element.transform - [ method ] - ** - * Adds transformation to the element which is separate to other attributes, - * i.e. translation doesn’t change `x` or `y` of the rectange. The format - * of transformation string is similar to the path string syntax: - | "t100,100r30,100,100s2,2,100,100r45s1.5" - * Each letter is a command. There are four commands: `t` is for translate, `r` is for rotate, `s` is for - * scale and `m` is for matrix. - * - * There are also alternative “absolute” translation, rotation and scale: `T`, `R` and `S`. They will not take previous transformation into account. For example, `...T100,0` will always move element 100 px horisontally, while `...t100,0` could move it vertically if there is `r90` before. Just compare results of `r90t100,0` and `r90T100,0`. - * - * So, the example line above could be read like “translate by 100, 100; rotate 30° around 100, 100; scale twice around 100, 100; - * rotate 45° around centre; scale 1.5 times relative to centre”. As you can see rotate and scale commands have origin - * coordinates as optional parameters, the default is the centre point of the element. - * Matrix accepts six parameters. - > Usage - | var el = paper.rect(10, 20, 300, 200); - | // translate 100, 100, rotate 45°, translate -100, 0 - | el.transform("t100,100r45t-100,0"); - | // if you want you can append or prepend transformations - | el.transform("...t50,50"); - | el.transform("s2..."); - | // or even wrap - | el.transform("t50,50...t-50-50"); - | // to reset transformation call method with empty string - | el.transform(""); - | // to get current value call it without parameters - | console.log(el.transform()); - > Parameters - - tstr (string) #optional transformation string - * If tstr isn’t specified - = (string) current transformation string - * else - = (object) @Element - \*/ - elproto.transform = function (tstr) { - var _ = this._; - if (tstr == null) { - return _.transform; - } - R._extractTransform(this, tstr); - - this.clip && $(this.clip, {transform: this.matrix.invert()}); - this.pattern && updatePosition(this); - this.node && $(this.node, {transform: this.matrix}); - - if (_.sx != 1 || _.sy != 1) { - var sw = this.attrs[has]("stroke-width") ? this.attrs["stroke-width"] : 1; - this.attr({"stroke-width": sw}); - } - - return this; - }; - /*\ - * Element.hide - [ method ] - ** - * Makes element invisible. See @Element.show. - = (object) @Element - \*/ - elproto.hide = function () { - !this.removed && this.paper.safari(this.node.style.display = "none"); - return this; - }; - /*\ - * Element.show - [ method ] - ** - * Makes element visible. See @Element.hide. - = (object) @Element - \*/ - elproto.show = function () { - !this.removed && this.paper.safari(this.node.style.display = ""); - return this; - }; - /*\ - * Element.remove - [ method ] - ** - * Removes element from the paper. - \*/ - elproto.remove = function () { - var node = getRealNode(this.node); - if (this.removed || !node.parentNode) { - return; - } - var paper = this.paper; - paper.__set__ && paper.__set__.exclude(this); - eve.unbind("raphael.*.*." + this.id); - if (this.gradient) { - paper.defs.removeChild(this.gradient); - } - R._tear(this, paper); - - node.parentNode.removeChild(node); - - // Remove custom data for element - this.removeData(); - - for (var i in this) { - this[i] = typeof this[i] == "function" ? R._removedFactory(i) : null; - } - this.removed = true; - }; - elproto._getBBox = function () { - if (this.node.style.display == "none") { - this.show(); - var hide = true; - } - var canvasHidden = false, - containerStyle; - if (this.paper.canvas.parentElement) { - containerStyle = this.paper.canvas.parentElement.style; - } //IE10+ can't find parentElement - else if (this.paper.canvas.parentNode) { - containerStyle = this.paper.canvas.parentNode.style; - } - - if(containerStyle && containerStyle.display == "none") { - canvasHidden = true; - containerStyle.display = ""; - } - var bbox = {}; - try { - bbox = this.node.getBBox(); - } catch(e) { - // Firefox 3.0.x, 25.0.1 (probably more versions affected) play badly here - possible fix - bbox = { - x: this.node.clientLeft, - y: this.node.clientTop, - width: this.node.clientWidth, - height: this.node.clientHeight - } - } finally { - bbox = bbox || {}; - if(canvasHidden){ - containerStyle.display = "none"; - } - } - hide && this.hide(); - return bbox; - }; - /*\ - * Element.attr - [ method ] - ** - * Sets the attributes of the element. - > Parameters - - attrName (string) attribute’s name - - value (string) value - * or - - params (object) object of name/value pairs - * or - - attrName (string) attribute’s name - * or - - attrNames (array) in this case method returns array of current values for given attribute names - = (object) @Element if attrsName & value or params are passed in. - = (...) value of the attribute if only attrsName is passed in. - = (array) array of values of the attribute if attrsNames is passed in. - = (object) object of attributes if nothing is passed in. - > Possible parameters - # <p>Please refer to the <a href="http://www.w3.org/TR/SVG/" title="The W3C Recommendation for the SVG language describes these properties in detail.">SVG specification</a> for an explanation of these parameters.</p> - o arrow-end (string) arrowhead on the end of the path. The format for string is `<type>[-<width>[-<length>]]`. Possible types: `classic`, `block`, `open`, `oval`, `diamond`, `none`, width: `wide`, `narrow`, `medium`, length: `long`, `short`, `midium`. - o clip-rect (string) comma or space separated values: x, y, width and height - o cursor (string) CSS type of the cursor - o cx (number) the x-axis coordinate of the center of the circle, or ellipse - o cy (number) the y-axis coordinate of the center of the circle, or ellipse - o fill (string) colour, gradient or image - o fill-opacity (number) - o font (string) - o font-family (string) - o font-size (number) font size in pixels - o font-weight (string) - o height (number) - o href (string) URL, if specified element behaves as hyperlink - o opacity (number) - o path (string) SVG path string format - o r (number) radius of the circle, ellipse or rounded corner on the rect - o rx (number) horisontal radius of the ellipse - o ry (number) vertical radius of the ellipse - o src (string) image URL, only works for @Element.image element - o stroke (string) stroke colour - o stroke-dasharray (string) [“”, “`-`”, “`.`”, “`-.`”, “`-..`”, “`. `”, “`- `”, “`--`”, “`- .`”, “`--.`”, “`--..`”] - o stroke-linecap (string) [“`butt`”, “`square`”, “`round`”] - o stroke-linejoin (string) [“`bevel`”, “`round`”, “`miter`”] - o stroke-miterlimit (number) - o stroke-opacity (number) - o stroke-width (number) stroke width in pixels, default is '1' - o target (string) used with href - o text (string) contents of the text element. Use `\n` for multiline text - o text-anchor (string) [“`start`”, “`middle`”, “`end`”], default is “`middle`” - o title (string) will create tooltip with a given text - o transform (string) see @Element.transform - o width (number) - o x (number) - o y (number) - > Gradients - * Linear gradient format: “`‹angle›-‹colour›[-‹colour›[:‹offset›]]*-‹colour›`”, example: “`90-#fff-#000`” – 90° - * gradient from white to black or “`0-#fff-#f00:20-#000`” – 0° gradient from white via red (at 20%) to black. - * - * radial gradient: “`r[(‹fx›, ‹fy›)]‹colour›[-‹colour›[:‹offset›]]*-‹colour›`”, example: “`r#fff-#000`” – - * gradient from white to black or “`r(0.25, 0.75)#fff-#000`” – gradient from white to black with focus point - * at 0.25, 0.75. Focus point coordinates are in 0..1 range. Radial gradients can only be applied to circles and ellipses. - > Path String - # <p>Please refer to <a href="http://www.w3.org/TR/SVG/paths.html#PathData" title="Details of a path’s data attribute’s format are described in the SVG specification.">SVG documentation regarding path string</a>. Raphaël fully supports it.</p> - > Colour Parsing - # <ul> - # <li>Colour name (“<code>red</code>”, “<code>green</code>”, “<code>cornflowerblue</code>”, etc)</li> - # <li>#••• — shortened HTML colour: (“<code>#000</code>”, “<code>#fc0</code>”, etc)</li> - # <li>#•••••• — full length HTML colour: (“<code>#000000</code>”, “<code>#bd2300</code>”)</li> - # <li>rgb(•••, •••, •••) — red, green and blue channels’ values: (“<code>rgb(200, 100, 0)</code>”)</li> - # <li>rgb(•••%, •••%, •••%) — same as above, but in %: (“<code>rgb(100%, 175%, 0%)</code>”)</li> - # <li>rgba(•••, •••, •••, •••) — red, green and blue channels’ values: (“<code>rgba(200, 100, 0, .5)</code>”)</li> - # <li>rgba(•••%, •••%, •••%, •••%) — same as above, but in %: (“<code>rgba(100%, 175%, 0%, 50%)</code>”)</li> - # <li>hsb(•••, •••, •••) — hue, saturation and brightness values: (“<code>hsb(0.5, 0.25, 1)</code>”)</li> - # <li>hsb(•••%, •••%, •••%) — same as above, but in %</li> - # <li>hsba(•••, •••, •••, •••) — same as above, but with opacity</li> - # <li>hsl(•••, •••, •••) — almost the same as hsb, see <a href="http://en.wikipedia.org/wiki/HSL_and_HSV" title="HSL and HSV - Wikipedia, the free encyclopedia">Wikipedia page</a></li> - # <li>hsl(•••%, •••%, •••%) — same as above, but in %</li> - # <li>hsla(•••, •••, •••, •••) — same as above, but with opacity</li> - # <li>Optionally for hsb and hsl you could specify hue as a degree: “<code>hsl(240deg, 1, .5)</code>” or, if you want to go fancy, “<code>hsl(240°, 1, .5)</code>”</li> - # </ul> - \*/ - elproto.attr = function (name, value) { - if (this.removed) { - return this; - } - if (name == null) { - var res = {}; - for (var a in this.attrs) if (this.attrs[has](a)) { - res[a] = this.attrs[a]; - } - res.gradient && res.fill == "none" && (res.fill = res.gradient) && delete res.gradient; - res.transform = this._.transform; - return res; - } - if (value == null && R.is(name, "string")) { - if (name == "fill" && this.attrs.fill == "none" && this.attrs.gradient) { - return this.attrs.gradient; - } - if (name == "transform") { - return this._.transform; - } - var names = name.split(separator), - out = {}; - for (var i = 0, ii = names.length; i < ii; i++) { - name = names[i]; - if (name in this.attrs) { - out[name] = this.attrs[name]; - } else if (R.is(this.paper.customAttributes[name], "function")) { - out[name] = this.paper.customAttributes[name].def; - } else { - out[name] = R._availableAttrs[name]; - } - } - return ii - 1 ? out : out[names[0]]; - } - if (value == null && R.is(name, "array")) { - out = {}; - for (i = 0, ii = name.length; i < ii; i++) { - out[name[i]] = this.attr(name[i]); - } - return out; - } - if (value != null) { - var params = {}; - params[name] = value; - } else if (name != null && R.is(name, "object")) { - params = name; - } - for (var key in params) { - eve("raphael.attr." + key + "." + this.id, this, params[key]); - } - for (key in this.paper.customAttributes) if (this.paper.customAttributes[has](key) && params[has](key) && R.is(this.paper.customAttributes[key], "function")) { - var par = this.paper.customAttributes[key].apply(this, [].concat(params[key])); - this.attrs[key] = params[key]; - for (var subkey in par) if (par[has](subkey)) { - params[subkey] = par[subkey]; - } - } - setFillAndStroke(this, params); - return this; - }; - /*\ - * Element.toFront - [ method ] - ** - * Moves the element so it is the closest to the viewer’s eyes, on top of other elements. - = (object) @Element - \*/ - elproto.toFront = function () { - if (this.removed) { - return this; - } - var node = getRealNode(this.node); - node.parentNode.appendChild(node); - var svg = this.paper; - svg.top != this && R._tofront(this, svg); - return this; - }; - /*\ - * Element.toBack - [ method ] - ** - * Moves the element so it is the furthest from the viewer’s eyes, behind other elements. - = (object) @Element - \*/ - elproto.toBack = function () { - if (this.removed) { - return this; - } - var node = getRealNode(this.node); - var parentNode = node.parentNode; - parentNode.insertBefore(node, parentNode.firstChild); - R._toback(this, this.paper); - var svg = this.paper; - return this; - }; - /*\ - * Element.insertAfter - [ method ] - ** - * Inserts current object after the given one. - = (object) @Element - \*/ - elproto.insertAfter = function (element) { - if (this.removed || !element) { - return this; - } - - var node = getRealNode(this.node); - var afterNode = getRealNode(element.node || element[element.length - 1].node); - if (afterNode.nextSibling) { - afterNode.parentNode.insertBefore(node, afterNode.nextSibling); - } else { - afterNode.parentNode.appendChild(node); - } - R._insertafter(this, element, this.paper); - return this; - }; - /*\ - * Element.insertBefore - [ method ] - ** - * Inserts current object before the given one. - = (object) @Element - \*/ - elproto.insertBefore = function (element) { - if (this.removed || !element) { - return this; - } - - var node = getRealNode(this.node); - var beforeNode = getRealNode(element.node || element[0].node); - beforeNode.parentNode.insertBefore(node, beforeNode); - R._insertbefore(this, element, this.paper); - return this; - }; - elproto.blur = function (size) { - // Experimental. No Safari support. Use it on your own risk. - var t = this; - if (+size !== 0) { - var fltr = $("filter"), - blur = $("feGaussianBlur"); - t.attrs.blur = size; - fltr.id = R.createUUID(); - $(blur, {stdDeviation: +size || 1.5}); - fltr.appendChild(blur); - t.paper.defs.appendChild(fltr); - t._blur = fltr; - $(t.node, {filter: "url(#" + fltr.id + ")"}); - } else { - if (t._blur) { - t._blur.parentNode.removeChild(t._blur); - delete t._blur; - delete t.attrs.blur; - } - t.node.removeAttribute("filter"); - } - return t; - }; - R._engine.circle = function (svg, x, y, r) { - var el = $("circle"); - svg.canvas && svg.canvas.appendChild(el); - var res = new Element(el, svg); - res.attrs = {cx: x, cy: y, r: r, fill: "none", stroke: "#000"}; - res.type = "circle"; - $(el, res.attrs); - return res; - }; - R._engine.rect = function (svg, x, y, w, h, r) { - var el = $("rect"); - svg.canvas && svg.canvas.appendChild(el); - var res = new Element(el, svg); - res.attrs = {x: x, y: y, width: w, height: h, rx: r || 0, ry: r || 0, fill: "none", stroke: "#000"}; - res.type = "rect"; - $(el, res.attrs); - return res; - }; - R._engine.ellipse = function (svg, x, y, rx, ry) { - var el = $("ellipse"); - svg.canvas && svg.canvas.appendChild(el); - var res = new Element(el, svg); - res.attrs = {cx: x, cy: y, rx: rx, ry: ry, fill: "none", stroke: "#000"}; - res.type = "ellipse"; - $(el, res.attrs); - return res; - }; - R._engine.image = function (svg, src, x, y, w, h) { - var el = $("image"); - $(el, {x: x, y: y, width: w, height: h, preserveAspectRatio: "none"}); - el.setAttributeNS(xlink, "href", src); - svg.canvas && svg.canvas.appendChild(el); - var res = new Element(el, svg); - res.attrs = {x: x, y: y, width: w, height: h, src: src}; - res.type = "image"; - return res; - }; - R._engine.text = function (svg, x, y, text) { - var el = $("text"); - svg.canvas && svg.canvas.appendChild(el); - var res = new Element(el, svg); - res.attrs = { - x: x, - y: y, - "text-anchor": "middle", - text: text, - "font-family": R._availableAttrs["font-family"], - "font-size": R._availableAttrs["font-size"], - stroke: "none", - fill: "#000" - }; - res.type = "text"; - setFillAndStroke(res, res.attrs); - return res; - }; - R._engine.setSize = function (width, height) { - this.width = width || this.width; - this.height = height || this.height; - this.canvas.setAttribute("width", this.width); - this.canvas.setAttribute("height", this.height); - if (this._viewBox) { - this.setViewBox.apply(this, this._viewBox); - } - return this; - }; - R._engine.create = function () { - var con = R._getContainer.apply(0, arguments), - container = con && con.container, - x = con.x, - y = con.y, - width = con.width, - height = con.height; - if (!container) { - throw new Error("SVG container not found."); - } - var cnvs = $("svg"), - css = "overflow:hidden;", - isFloating; - x = x || 0; - y = y || 0; - width = width || 512; - height = height || 342; - $(cnvs, { - height: height, - version: 1.1, - width: width, - xmlns: "http://www.w3.org/2000/svg", - "xmlns:xlink": "http://www.w3.org/1999/xlink" - }); - if (container == 1) { - cnvs.style.cssText = css + "position:absolute;left:" + x + "px;top:" + y + "px"; - R._g.doc.body.appendChild(cnvs); - isFloating = 1; - } else { - cnvs.style.cssText = css + "position:relative"; - if (container.firstChild) { - container.insertBefore(cnvs, container.firstChild); - } else { - container.appendChild(cnvs); - } - } - container = new R._Paper; - container.width = width; - container.height = height; - container.canvas = cnvs; - container.clear(); - container._left = container._top = 0; - isFloating && (container.renderfix = function () {}); - container.renderfix(); - return container; - }; - R._engine.setViewBox = function (x, y, w, h, fit) { - eve("raphael.setViewBox", this, this._viewBox, [x, y, w, h, fit]); - var paperSize = this.getSize(), - size = mmax(w / paperSize.width, h / paperSize.height), - top = this.top, - aspectRatio = fit ? "xMidYMid meet" : "xMinYMin", - vb, - sw; - if (x == null) { - if (this._vbSize) { - size = 1; - } - delete this._vbSize; - vb = "0 0 " + this.width + S + this.height; - } else { - this._vbSize = size; - vb = x + S + y + S + w + S + h; - } - $(this.canvas, { - viewBox: vb, - preserveAspectRatio: aspectRatio - }); - while (size && top) { - sw = "stroke-width" in top.attrs ? top.attrs["stroke-width"] : 1; - top.attr({"stroke-width": sw}); - top._.dirty = 1; - top._.dirtyT = 1; - top = top.prev; - } - this._viewBox = [x, y, w, h, !!fit]; - return this; - }; - /*\ - * Paper.renderfix - [ method ] - ** - * Fixes the issue of Firefox and IE9 regarding subpixel rendering. If paper is dependant - * on other elements after reflow it could shift half pixel which cause for lines to lost their crispness. - * This method fixes the issue. - ** - Special thanks to Mariusz Nowak (http://www.medikoo.com/) for this method. - \*/ - R.prototype.renderfix = function () { - var cnvs = this.canvas, - s = cnvs.style, - pos; - try { - pos = cnvs.getScreenCTM() || cnvs.createSVGMatrix(); - } catch (e) { - pos = cnvs.createSVGMatrix(); - } - var left = -pos.e % 1, - top = -pos.f % 1; - if (left || top) { - if (left) { - this._left = (this._left + left) % 1; - s.left = this._left + "px"; - } - if (top) { - this._top = (this._top + top) % 1; - s.top = this._top + "px"; - } - } - }; - /*\ - * Paper.clear - [ method ] - ** - * Clears the paper, i.e. removes all the elements. - \*/ - R.prototype.clear = function () { - R.eve("raphael.clear", this); - var c = this.canvas; - while (c.firstChild) { - c.removeChild(c.firstChild); - } - this.bottom = this.top = null; - (this.desc = $("desc")).appendChild(R._g.doc.createTextNode("Created with Rapha\xebl " + R.version)); - c.appendChild(this.desc); - c.appendChild(this.defs = $("defs")); - }; - /*\ - * Paper.remove - [ method ] - ** - * Removes the paper from the DOM. - \*/ - R.prototype.remove = function () { - eve("raphael.remove", this); - this.canvas.parentNode && this.canvas.parentNode.removeChild(this.canvas); - for (var i in this) { - this[i] = typeof this[i] == "function" ? R._removedFactory(i) : null; - } - }; - var setproto = R.st; - for (var method in elproto) if (elproto[has](method) && !setproto[has](method)) { - setproto[method] = (function (methodname) { - return function () { - var arg = arguments; - return this.forEach(function (el) { - el[methodname].apply(el, arg); - }); - }; - })(method); - } -})(); - -// ┌─────────────────────────────────────────────────────────────────────┐ \\ -// │ Raphaël - JavaScript Vector Library │ \\ -// ├─────────────────────────────────────────────────────────────────────┤ \\ -// │ VML Module │ \\ -// ├─────────────────────────────────────────────────────────────────────┤ \\ -// │ Copyright (c) 2008-2011 Dmitry Baranovskiy (http://raphaeljs.com) │ \\ -// │ Copyright (c) 2008-2011 Sencha Labs (http://sencha.com) │ \\ -// │ Licensed under the MIT (http://raphaeljs.com/license.html) license. │ \\ -// └─────────────────────────────────────────────────────────────────────┘ \\ - -(function(){ - if (!R.vml) { - return; - } - var has = "hasOwnProperty", - Str = String, - toFloat = parseFloat, - math = Math, - round = math.round, - mmax = math.max, - mmin = math.min, - abs = math.abs, - fillString = "fill", - separator = /[, ]+/, - eve = R.eve, - ms = " progid:DXImageTransform.Microsoft", - S = " ", - E = "", - map = {M: "m", L: "l", C: "c", Z: "x", m: "t", l: "r", c: "v", z: "x"}, - bites = /([clmz]),?([^clmz]*)/gi, - blurregexp = / progid:\S+Blur\([^\)]+\)/g, - val = /-?[^,\s-]+/g, - cssDot = "position:absolute;left:0;top:0;width:1px;height:1px;behavior:url(#default#VML)", - zoom = 21600, - pathTypes = {path: 1, rect: 1, image: 1}, - ovalTypes = {circle: 1, ellipse: 1}, - path2vml = function (path) { - var total = /[ahqstv]/ig, - command = R._pathToAbsolute; - Str(path).match(total) && (command = R._path2curve); - total = /[clmz]/g; - if (command == R._pathToAbsolute && !Str(path).match(total)) { - var res = Str(path).replace(bites, function (all, command, args) { - var vals = [], - isMove = command.toLowerCase() == "m", - res = map[command]; - args.replace(val, function (value) { - if (isMove && vals.length == 2) { - res += vals + map[command == "m" ? "l" : "L"]; - vals = []; - } - vals.push(round(value * zoom)); - }); - return res + vals; - }); - return res; - } - var pa = command(path), p, r; - res = []; - for (var i = 0, ii = pa.length; i < ii; i++) { - p = pa[i]; - r = pa[i][0].toLowerCase(); - r == "z" && (r = "x"); - for (var j = 1, jj = p.length; j < jj; j++) { - r += round(p[j] * zoom) + (j != jj - 1 ? "," : E); - } - res.push(r); - } - return res.join(S); - }, - compensation = function (deg, dx, dy) { - var m = R.matrix(); - m.rotate(-deg, .5, .5); - return { - dx: m.x(dx, dy), - dy: m.y(dx, dy) - }; - }, - setCoords = function (p, sx, sy, dx, dy, deg) { - var _ = p._, - m = p.matrix, - fillpos = _.fillpos, - o = p.node, - s = o.style, - y = 1, - flip = "", - dxdy, - kx = zoom / sx, - ky = zoom / sy; - s.visibility = "hidden"; - if (!sx || !sy) { - return; - } - o.coordsize = abs(kx) + S + abs(ky); - s.rotation = deg * (sx * sy < 0 ? -1 : 1); - if (deg) { - var c = compensation(deg, dx, dy); - dx = c.dx; - dy = c.dy; - } - sx < 0 && (flip += "x"); - sy < 0 && (flip += " y") && (y = -1); - s.flip = flip; - o.coordorigin = (dx * -kx) + S + (dy * -ky); - if (fillpos || _.fillsize) { - var fill = o.getElementsByTagName(fillString); - fill = fill && fill[0]; - o.removeChild(fill); - if (fillpos) { - c = compensation(deg, m.x(fillpos[0], fillpos[1]), m.y(fillpos[0], fillpos[1])); - fill.position = c.dx * y + S + c.dy * y; - } - if (_.fillsize) { - fill.size = _.fillsize[0] * abs(sx) + S + _.fillsize[1] * abs(sy); - } - o.appendChild(fill); - } - s.visibility = "visible"; - }; - R.toString = function () { - return "Your browser doesn\u2019t support SVG. Falling down to VML.\nYou are running Rapha\xebl " + this.version; - }; - var addArrow = function (o, value, isEnd) { - var values = Str(value).toLowerCase().split("-"), - se = isEnd ? "end" : "start", - i = values.length, - type = "classic", - w = "medium", - h = "medium"; - while (i--) { - switch (values[i]) { - case "block": - case "classic": - case "oval": - case "diamond": - case "open": - case "none": - type = values[i]; - break; - case "wide": - case "narrow": h = values[i]; break; - case "long": - case "short": w = values[i]; break; - } - } - var stroke = o.node.getElementsByTagName("stroke")[0]; - stroke[se + "arrow"] = type; - stroke[se + "arrowlength"] = w; - stroke[se + "arrowwidth"] = h; - }, - setFillAndStroke = function (o, params) { - // o.paper.canvas.style.display = "none"; - o.attrs = o.attrs || {}; - var node = o.node, - a = o.attrs, - s = node.style, - xy, - newpath = pathTypes[o.type] && (params.x != a.x || params.y != a.y || params.width != a.width || params.height != a.height || params.cx != a.cx || params.cy != a.cy || params.rx != a.rx || params.ry != a.ry || params.r != a.r), - isOval = ovalTypes[o.type] && (a.cx != params.cx || a.cy != params.cy || a.r != params.r || a.rx != params.rx || a.ry != params.ry), - res = o; - - - for (var par in params) if (params[has](par)) { - a[par] = params[par]; - } - if (newpath) { - a.path = R._getPath[o.type](o); - o._.dirty = 1; - } - params.href && (node.href = params.href); - params.title && (node.title = params.title); - params.target && (node.target = params.target); - params.cursor && (s.cursor = params.cursor); - "blur" in params && o.blur(params.blur); - if (params.path && o.type == "path" || newpath) { - node.path = path2vml(~Str(a.path).toLowerCase().indexOf("r") ? R._pathToAbsolute(a.path) : a.path); - o._.dirty = 1; - if (o.type == "image") { - o._.fillpos = [a.x, a.y]; - o._.fillsize = [a.width, a.height]; - setCoords(o, 1, 1, 0, 0, 0); - } - } - "transform" in params && o.transform(params.transform); - if (isOval) { - var cx = +a.cx, - cy = +a.cy, - rx = +a.rx || +a.r || 0, - ry = +a.ry || +a.r || 0; - node.path = R.format("ar{0},{1},{2},{3},{4},{1},{4},{1}x", round((cx - rx) * zoom), round((cy - ry) * zoom), round((cx + rx) * zoom), round((cy + ry) * zoom), round(cx * zoom)); - o._.dirty = 1; - } - if ("clip-rect" in params) { - var rect = Str(params["clip-rect"]).split(separator); - if (rect.length == 4) { - rect[2] = +rect[2] + (+rect[0]); - rect[3] = +rect[3] + (+rect[1]); - var div = node.clipRect || R._g.doc.createElement("div"), - dstyle = div.style; - dstyle.clip = R.format("rect({1}px {2}px {3}px {0}px)", rect); - if (!node.clipRect) { - dstyle.position = "absolute"; - dstyle.top = 0; - dstyle.left = 0; - dstyle.width = o.paper.width + "px"; - dstyle.height = o.paper.height + "px"; - node.parentNode.insertBefore(div, node); - div.appendChild(node); - node.clipRect = div; - } - } - if (!params["clip-rect"]) { - node.clipRect && (node.clipRect.style.clip = "auto"); - } - } - if (o.textpath) { - var textpathStyle = o.textpath.style; - params.font && (textpathStyle.font = params.font); - params["font-family"] && (textpathStyle.fontFamily = '"' + params["font-family"].split(",")[0].replace(/^['"]+|['"]+$/g, E) + '"'); - params["font-size"] && (textpathStyle.fontSize = params["font-size"]); - params["font-weight"] && (textpathStyle.fontWeight = params["font-weight"]); - params["font-style"] && (textpathStyle.fontStyle = params["font-style"]); - } - if ("arrow-start" in params) { - addArrow(res, params["arrow-start"]); - } - if ("arrow-end" in params) { - addArrow(res, params["arrow-end"], 1); - } - if (params.opacity != null || - params["stroke-width"] != null || - params.fill != null || - params.src != null || - params.stroke != null || - params["stroke-width"] != null || - params["stroke-opacity"] != null || - params["fill-opacity"] != null || - params["stroke-dasharray"] != null || - params["stroke-miterlimit"] != null || - params["stroke-linejoin"] != null || - params["stroke-linecap"] != null) { - var fill = node.getElementsByTagName(fillString), - newfill = false; - fill = fill && fill[0]; - !fill && (newfill = fill = createNode(fillString)); - if (o.type == "image" && params.src) { - fill.src = params.src; - } - params.fill && (fill.on = true); - if (fill.on == null || params.fill == "none" || params.fill === null) { - fill.on = false; - } - if (fill.on && params.fill) { - var isURL = Str(params.fill).match(R._ISURL); - if (isURL) { - fill.parentNode == node && node.removeChild(fill); - fill.rotate = true; - fill.src = isURL[1]; - fill.type = "tile"; - var bbox = o.getBBox(1); - fill.position = bbox.x + S + bbox.y; - o._.fillpos = [bbox.x, bbox.y]; - - R._preload(isURL[1], function () { - o._.fillsize = [this.offsetWidth, this.offsetHeight]; - }); - } else { - fill.color = R.getRGB(params.fill).hex; - fill.src = E; - fill.type = "solid"; - if (R.getRGB(params.fill).error && (res.type in {circle: 1, ellipse: 1} || Str(params.fill).charAt() != "r") && addGradientFill(res, params.fill, fill)) { - a.fill = "none"; - a.gradient = params.fill; - fill.rotate = false; - } - } - } - if ("fill-opacity" in params || "opacity" in params) { - var opacity = ((+a["fill-opacity"] + 1 || 2) - 1) * ((+a.opacity + 1 || 2) - 1) * ((+R.getRGB(params.fill).o + 1 || 2) - 1); - opacity = mmin(mmax(opacity, 0), 1); - fill.opacity = opacity; - if (fill.src) { - fill.color = "none"; - } - } - node.appendChild(fill); - var stroke = (node.getElementsByTagName("stroke") && node.getElementsByTagName("stroke")[0]), - newstroke = false; - !stroke && (newstroke = stroke = createNode("stroke")); - if ((params.stroke && params.stroke != "none") || - params["stroke-width"] || - params["stroke-opacity"] != null || - params["stroke-dasharray"] || - params["stroke-miterlimit"] || - params["stroke-linejoin"] || - params["stroke-linecap"]) { - stroke.on = true; - } - (params.stroke == "none" || params.stroke === null || stroke.on == null || params.stroke == 0 || params["stroke-width"] == 0) && (stroke.on = false); - var strokeColor = R.getRGB(params.stroke); - stroke.on && params.stroke && (stroke.color = strokeColor.hex); - opacity = ((+a["stroke-opacity"] + 1 || 2) - 1) * ((+a.opacity + 1 || 2) - 1) * ((+strokeColor.o + 1 || 2) - 1); - var width = (toFloat(params["stroke-width"]) || 1) * .75; - opacity = mmin(mmax(opacity, 0), 1); - params["stroke-width"] == null && (width = a["stroke-width"]); - params["stroke-width"] && (stroke.weight = width); - width && width < 1 && (opacity *= width) && (stroke.weight = 1); - stroke.opacity = opacity; - - params["stroke-linejoin"] && (stroke.joinstyle = params["stroke-linejoin"] || "miter"); - stroke.miterlimit = params["stroke-miterlimit"] || 8; - params["stroke-linecap"] && (stroke.endcap = params["stroke-linecap"] == "butt" ? "flat" : params["stroke-linecap"] == "square" ? "square" : "round"); - if ("stroke-dasharray" in params) { - var dasharray = { - "-": "shortdash", - ".": "shortdot", - "-.": "shortdashdot", - "-..": "shortdashdotdot", - ". ": "dot", - "- ": "dash", - "--": "longdash", - "- .": "dashdot", - "--.": "longdashdot", - "--..": "longdashdotdot" - }; - stroke.dashstyle = dasharray[has](params["stroke-dasharray"]) ? dasharray[params["stroke-dasharray"]] : E; - } - newstroke && node.appendChild(stroke); - } - if (res.type == "text") { - res.paper.canvas.style.display = E; - var span = res.paper.span, - m = 100, - fontSize = a.font && a.font.match(/\d+(?:\.\d*)?(?=px)/); - s = span.style; - a.font && (s.font = a.font); - a["font-family"] && (s.fontFamily = a["font-family"]); - a["font-weight"] && (s.fontWeight = a["font-weight"]); - a["font-style"] && (s.fontStyle = a["font-style"]); - fontSize = toFloat(a["font-size"] || fontSize && fontSize[0]) || 10; - s.fontSize = fontSize * m + "px"; - res.textpath.string && (span.innerHTML = Str(res.textpath.string).replace(/</g, "<").replace(/&/g, "&").replace(/\n/g, "<br>")); - var brect = span.getBoundingClientRect(); - res.W = a.w = (brect.right - brect.left) / m; - res.H = a.h = (brect.bottom - brect.top) / m; - // res.paper.canvas.style.display = "none"; - res.X = a.x; - res.Y = a.y + res.H / 2; - - ("x" in params || "y" in params) && (res.path.v = R.format("m{0},{1}l{2},{1}", round(a.x * zoom), round(a.y * zoom), round(a.x * zoom) + 1)); - var dirtyattrs = ["x", "y", "text", "font", "font-family", "font-weight", "font-style", "font-size"]; - for (var d = 0, dd = dirtyattrs.length; d < dd; d++) if (dirtyattrs[d] in params) { - res._.dirty = 1; - break; - } - - // text-anchor emulation - switch (a["text-anchor"]) { - case "start": - res.textpath.style["v-text-align"] = "left"; - res.bbx = res.W / 2; - break; - case "end": - res.textpath.style["v-text-align"] = "right"; - res.bbx = -res.W / 2; - break; - default: - res.textpath.style["v-text-align"] = "center"; - res.bbx = 0; - break; - } - res.textpath.style["v-text-kern"] = true; - } - // res.paper.canvas.style.display = E; - }, - addGradientFill = function (o, gradient, fill) { - o.attrs = o.attrs || {}; - var attrs = o.attrs, - pow = Math.pow, - opacity, - oindex, - type = "linear", - fxfy = ".5 .5"; - o.attrs.gradient = gradient; - gradient = Str(gradient).replace(R._radial_gradient, function (all, fx, fy) { - type = "radial"; - if (fx && fy) { - fx = toFloat(fx); - fy = toFloat(fy); - pow(fx - .5, 2) + pow(fy - .5, 2) > .25 && (fy = math.sqrt(.25 - pow(fx - .5, 2)) * ((fy > .5) * 2 - 1) + .5); - fxfy = fx + S + fy; - } - return E; - }); - gradient = gradient.split(/\s*\-\s*/); - if (type == "linear") { - var angle = gradient.shift(); - angle = -toFloat(angle); - if (isNaN(angle)) { - return null; - } - } - var dots = R._parseDots(gradient); - if (!dots) { - return null; - } - o = o.shape || o.node; - if (dots.length) { - o.removeChild(fill); - fill.on = true; - fill.method = "none"; - fill.color = dots[0].color; - fill.color2 = dots[dots.length - 1].color; - var clrs = []; - for (var i = 0, ii = dots.length; i < ii; i++) { - dots[i].offset && clrs.push(dots[i].offset + S + dots[i].color); - } - fill.colors = clrs.length ? clrs.join() : "0% " + fill.color; - if (type == "radial") { - fill.type = "gradientTitle"; - fill.focus = "100%"; - fill.focussize = "0 0"; - fill.focusposition = fxfy; - fill.angle = 0; - } else { - // fill.rotate= true; - fill.type = "gradient"; - fill.angle = (270 - angle) % 360; - } - o.appendChild(fill); - } - return 1; - }, - Element = function (node, vml) { - this[0] = this.node = node; - node.raphael = true; - this.id = R._oid++; - node.raphaelid = this.id; - this.X = 0; - this.Y = 0; - this.attrs = {}; - this.paper = vml; - this.matrix = R.matrix(); - this._ = { - transform: [], - sx: 1, - sy: 1, - dx: 0, - dy: 0, - deg: 0, - dirty: 1, - dirtyT: 1 - }; - !vml.bottom && (vml.bottom = this); - this.prev = vml.top; - vml.top && (vml.top.next = this); - vml.top = this; - this.next = null; - }; - var elproto = R.el; - - Element.prototype = elproto; - elproto.constructor = Element; - elproto.transform = function (tstr) { - if (tstr == null) { - return this._.transform; - } - var vbs = this.paper._viewBoxShift, - vbt = vbs ? "s" + [vbs.scale, vbs.scale] + "-1-1t" + [vbs.dx, vbs.dy] : E, - oldt; - if (vbs) { - oldt = tstr = Str(tstr).replace(/\.{3}|\u2026/g, this._.transform || E); - } - R._extractTransform(this, vbt + tstr); - var matrix = this.matrix.clone(), - skew = this.skew, - o = this.node, - split, - isGrad = ~Str(this.attrs.fill).indexOf("-"), - isPatt = !Str(this.attrs.fill).indexOf("url("); - matrix.translate(1, 1); - if (isPatt || isGrad || this.type == "image") { - skew.matrix = "1 0 0 1"; - skew.offset = "0 0"; - split = matrix.split(); - if ((isGrad && split.noRotation) || !split.isSimple) { - o.style.filter = matrix.toFilter(); - var bb = this.getBBox(), - bbt = this.getBBox(1), - dx = bb.x - bbt.x, - dy = bb.y - bbt.y; - o.coordorigin = (dx * -zoom) + S + (dy * -zoom); - setCoords(this, 1, 1, dx, dy, 0); - } else { - o.style.filter = E; - setCoords(this, split.scalex, split.scaley, split.dx, split.dy, split.rotate); - } - } else { - o.style.filter = E; - skew.matrix = Str(matrix); - skew.offset = matrix.offset(); - } - if (oldt !== null) { // empty string value is true as well - this._.transform = oldt; - R._extractTransform(this, oldt); - } - return this; - }; - elproto.rotate = function (deg, cx, cy) { - if (this.removed) { - return this; - } - if (deg == null) { - return; - } - deg = Str(deg).split(separator); - if (deg.length - 1) { - cx = toFloat(deg[1]); - cy = toFloat(deg[2]); - } - deg = toFloat(deg[0]); - (cy == null) && (cx = cy); - if (cx == null || cy == null) { - var bbox = this.getBBox(1); - cx = bbox.x + bbox.width / 2; - cy = bbox.y + bbox.height / 2; - } - this._.dirtyT = 1; - this.transform(this._.transform.concat([["r", deg, cx, cy]])); - return this; - }; - elproto.translate = function (dx, dy) { - if (this.removed) { - return this; - } - dx = Str(dx).split(separator); - if (dx.length - 1) { - dy = toFloat(dx[1]); - } - dx = toFloat(dx[0]) || 0; - dy = +dy || 0; - if (this._.bbox) { - this._.bbox.x += dx; - this._.bbox.y += dy; - } - this.transform(this._.transform.concat([["t", dx, dy]])); - return this; - }; - elproto.scale = function (sx, sy, cx, cy) { - if (this.removed) { - return this; - } - sx = Str(sx).split(separator); - if (sx.length - 1) { - sy = toFloat(sx[1]); - cx = toFloat(sx[2]); - cy = toFloat(sx[3]); - isNaN(cx) && (cx = null); - isNaN(cy) && (cy = null); - } - sx = toFloat(sx[0]); - (sy == null) && (sy = sx); - (cy == null) && (cx = cy); - if (cx == null || cy == null) { - var bbox = this.getBBox(1); - } - cx = cx == null ? bbox.x + bbox.width / 2 : cx; - cy = cy == null ? bbox.y + bbox.height / 2 : cy; - - this.transform(this._.transform.concat([["s", sx, sy, cx, cy]])); - this._.dirtyT = 1; - return this; - }; - elproto.hide = function () { - !this.removed && (this.node.style.display = "none"); - return this; - }; - elproto.show = function () { - !this.removed && (this.node.style.display = E); - return this; - }; - // Needed to fix the vml setViewBox issues - elproto.auxGetBBox = R.el.getBBox; - elproto.getBBox = function(){ - var b = this.auxGetBBox(); - if (this.paper && this.paper._viewBoxShift) - { - var c = {}; - var z = 1/this.paper._viewBoxShift.scale; - c.x = b.x - this.paper._viewBoxShift.dx; - c.x *= z; - c.y = b.y - this.paper._viewBoxShift.dy; - c.y *= z; - c.width = b.width * z; - c.height = b.height * z; - c.x2 = c.x + c.width; - c.y2 = c.y + c.height; - return c; - } - return b; - }; - elproto._getBBox = function () { - if (this.removed) { - return {}; - } - return { - x: this.X + (this.bbx || 0) - this.W / 2, - y: this.Y - this.H, - width: this.W, - height: this.H - }; - }; - elproto.remove = function () { - if (this.removed || !this.node.parentNode) { - return; - } - this.paper.__set__ && this.paper.__set__.exclude(this); - R.eve.unbind("raphael.*.*." + this.id); - R._tear(this, this.paper); - this.node.parentNode.removeChild(this.node); - this.shape && this.shape.parentNode.removeChild(this.shape); - for (var i in this) { - this[i] = typeof this[i] == "function" ? R._removedFactory(i) : null; - } - this.removed = true; - }; - elproto.attr = function (name, value) { - if (this.removed) { - return this; - } - if (name == null) { - var res = {}; - for (var a in this.attrs) if (this.attrs[has](a)) { - res[a] = this.attrs[a]; - } - res.gradient && res.fill == "none" && (res.fill = res.gradient) && delete res.gradient; - res.transform = this._.transform; - return res; - } - if (value == null && R.is(name, "string")) { - if (name == fillString && this.attrs.fill == "none" && this.attrs.gradient) { - return this.attrs.gradient; - } - var names = name.split(separator), - out = {}; - for (var i = 0, ii = names.length; i < ii; i++) { - name = names[i]; - if (name in this.attrs) { - out[name] = this.attrs[name]; - } else if (R.is(this.paper.customAttributes[name], "function")) { - out[name] = this.paper.customAttributes[name].def; - } else { - out[name] = R._availableAttrs[name]; - } - } - return ii - 1 ? out : out[names[0]]; - } - if (this.attrs && value == null && R.is(name, "array")) { - out = {}; - for (i = 0, ii = name.length; i < ii; i++) { - out[name[i]] = this.attr(name[i]); - } - return out; - } - var params; - if (value != null) { - params = {}; - params[name] = value; - } - value == null && R.is(name, "object") && (params = name); - for (var key in params) { - eve("raphael.attr." + key + "." + this.id, this, params[key]); - } - if (params) { - for (key in this.paper.customAttributes) if (this.paper.customAttributes[has](key) && params[has](key) && R.is(this.paper.customAttributes[key], "function")) { - var par = this.paper.customAttributes[key].apply(this, [].concat(params[key])); - this.attrs[key] = params[key]; - for (var subkey in par) if (par[has](subkey)) { - params[subkey] = par[subkey]; - } - } - // this.paper.canvas.style.display = "none"; - if (params.text && this.type == "text") { - this.textpath.string = params.text; - } - setFillAndStroke(this, params); - // this.paper.canvas.style.display = E; - } - return this; - }; - elproto.toFront = function () { - !this.removed && this.node.parentNode.appendChild(this.node); - this.paper && this.paper.top != this && R._tofront(this, this.paper); - return this; - }; - elproto.toBack = function () { - if (this.removed) { - return this; - } - if (this.node.parentNode.firstChild != this.node) { - this.node.parentNode.insertBefore(this.node, this.node.parentNode.firstChild); - R._toback(this, this.paper); - } - return this; - }; - elproto.insertAfter = function (element) { - if (this.removed) { - return this; - } - if (element.constructor == R.st.constructor) { - element = element[element.length - 1]; - } - if (element.node.nextSibling) { - element.node.parentNode.insertBefore(this.node, element.node.nextSibling); - } else { - element.node.parentNode.appendChild(this.node); - } - R._insertafter(this, element, this.paper); - return this; - }; - elproto.insertBefore = function (element) { - if (this.removed) { - return this; - } - if (element.constructor == R.st.constructor) { - element = element[0]; - } - element.node.parentNode.insertBefore(this.node, element.node); - R._insertbefore(this, element, this.paper); - return this; - }; - elproto.blur = function (size) { - var s = this.node.runtimeStyle, - f = s.filter; - f = f.replace(blurregexp, E); - if (+size !== 0) { - this.attrs.blur = size; - s.filter = f + S + ms + ".Blur(pixelradius=" + (+size || 1.5) + ")"; - s.margin = R.format("-{0}px 0 0 -{0}px", round(+size || 1.5)); - } else { - s.filter = f; - s.margin = 0; - delete this.attrs.blur; - } - return this; - }; - - R._engine.path = function (pathString, vml) { - var el = createNode("shape"); - el.style.cssText = cssDot; - el.coordsize = zoom + S + zoom; - el.coordorigin = vml.coordorigin; - var p = new Element(el, vml), - attr = {fill: "none", stroke: "#000"}; - pathString && (attr.path = pathString); - p.type = "path"; - p.path = []; - p.Path = E; - setFillAndStroke(p, attr); - vml.canvas.appendChild(el); - var skew = createNode("skew"); - skew.on = true; - el.appendChild(skew); - p.skew = skew; - p.transform(E); - return p; - }; - R._engine.rect = function (vml, x, y, w, h, r) { - var path = R._rectPath(x, y, w, h, r), - res = vml.path(path), - a = res.attrs; - res.X = a.x = x; - res.Y = a.y = y; - res.W = a.width = w; - res.H = a.height = h; - a.r = r; - a.path = path; - res.type = "rect"; - return res; - }; - R._engine.ellipse = function (vml, x, y, rx, ry) { - var res = vml.path(), - a = res.attrs; - res.X = x - rx; - res.Y = y - ry; - res.W = rx * 2; - res.H = ry * 2; - res.type = "ellipse"; - setFillAndStroke(res, { - cx: x, - cy: y, - rx: rx, - ry: ry - }); - return res; - }; - R._engine.circle = function (vml, x, y, r) { - var res = vml.path(), - a = res.attrs; - res.X = x - r; - res.Y = y - r; - res.W = res.H = r * 2; - res.type = "circle"; - setFillAndStroke(res, { - cx: x, - cy: y, - r: r - }); - return res; - }; - R._engine.image = function (vml, src, x, y, w, h) { - var path = R._rectPath(x, y, w, h), - res = vml.path(path).attr({stroke: "none"}), - a = res.attrs, - node = res.node, - fill = node.getElementsByTagName(fillString)[0]; - a.src = src; - res.X = a.x = x; - res.Y = a.y = y; - res.W = a.width = w; - res.H = a.height = h; - a.path = path; - res.type = "image"; - fill.parentNode == node && node.removeChild(fill); - fill.rotate = true; - fill.src = src; - fill.type = "tile"; - res._.fillpos = [x, y]; - res._.fillsize = [w, h]; - node.appendChild(fill); - setCoords(res, 1, 1, 0, 0, 0); - return res; - }; - R._engine.text = function (vml, x, y, text) { - var el = createNode("shape"), - path = createNode("path"), - o = createNode("textpath"); - x = x || 0; - y = y || 0; - text = text || ""; - path.v = R.format("m{0},{1}l{2},{1}", round(x * zoom), round(y * zoom), round(x * zoom) + 1); - path.textpathok = true; - o.string = Str(text); - o.on = true; - el.style.cssText = cssDot; - el.coordsize = zoom + S + zoom; - el.coordorigin = "0 0"; - var p = new Element(el, vml), - attr = { - fill: "#000", - stroke: "none", - font: R._availableAttrs.font, - text: text - }; - p.shape = el; - p.path = path; - p.textpath = o; - p.type = "text"; - p.attrs.text = Str(text); - p.attrs.x = x; - p.attrs.y = y; - p.attrs.w = 1; - p.attrs.h = 1; - setFillAndStroke(p, attr); - el.appendChild(o); - el.appendChild(path); - vml.canvas.appendChild(el); - var skew = createNode("skew"); - skew.on = true; - el.appendChild(skew); - p.skew = skew; - p.transform(E); - return p; - }; - R._engine.setSize = function (width, height) { - var cs = this.canvas.style; - this.width = width; - this.height = height; - width == +width && (width += "px"); - height == +height && (height += "px"); - cs.width = width; - cs.height = height; - cs.clip = "rect(0 " + width + " " + height + " 0)"; - if (this._viewBox) { - R._engine.setViewBox.apply(this, this._viewBox); - } - return this; - }; - R._engine.setViewBox = function (x, y, w, h, fit) { - R.eve("raphael.setViewBox", this, this._viewBox, [x, y, w, h, fit]); - var paperSize = this.getSize(), - width = paperSize.width, - height = paperSize.height, - H, W; - if (fit) { - H = height / h; - W = width / w; - if (w * H < width) { - x -= (width - w * H) / 2 / H; - } - if (h * W < height) { - y -= (height - h * W) / 2 / W; - } - } - this._viewBox = [x, y, w, h, !!fit]; - this._viewBoxShift = { - dx: -x, - dy: -y, - scale: paperSize - }; - this.forEach(function (el) { - el.transform("..."); - }); - return this; - }; - var createNode; - R._engine.initWin = function (win) { - var doc = win.document; - if (doc.styleSheets.length < 31) { - doc.createStyleSheet().addRule(".rvml", "behavior:url(#default#VML)"); - } else { - // no more room, add to the existing one - // http://msdn.microsoft.com/en-us/library/ms531194%28VS.85%29.aspx - doc.styleSheets[0].addRule(".rvml", "behavior:url(#default#VML)"); - } - try { - !doc.namespaces.rvml && doc.namespaces.add("rvml", "urn:schemas-microsoft-com:vml"); - createNode = function (tagName) { - return doc.createElement('<rvml:' + tagName + ' class="rvml">'); - }; - } catch (e) { - createNode = function (tagName) { - return doc.createElement('<' + tagName + ' xmlns="urn:schemas-microsoft.com:vml" class="rvml">'); - }; - } - }; - R._engine.initWin(R._g.win); - R._engine.create = function () { - var con = R._getContainer.apply(0, arguments), - container = con.container, - height = con.height, - s, - width = con.width, - x = con.x, - y = con.y; - if (!container) { - throw new Error("VML container not found."); - } - var res = new R._Paper, - c = res.canvas = R._g.doc.createElement("div"), - cs = c.style; - x = x || 0; - y = y || 0; - width = width || 512; - height = height || 342; - res.width = width; - res.height = height; - width == +width && (width += "px"); - height == +height && (height += "px"); - res.coordsize = zoom * 1e3 + S + zoom * 1e3; - res.coordorigin = "0 0"; - res.span = R._g.doc.createElement("span"); - res.span.style.cssText = "position:absolute;left:-9999em;top:-9999em;padding:0;margin:0;line-height:1;"; - c.appendChild(res.span); - cs.cssText = R.format("top:0;left:0;width:{0};height:{1};display:inline-block;position:relative;clip:rect(0 {0} {1} 0);overflow:hidden", width, height); - if (container == 1) { - R._g.doc.body.appendChild(c); - cs.left = x + "px"; - cs.top = y + "px"; - cs.position = "absolute"; - } else { - if (container.firstChild) { - container.insertBefore(c, container.firstChild); - } else { - container.appendChild(c); - } - } - res.renderfix = function () {}; - return res; - }; - R.prototype.clear = function () { - R.eve("raphael.clear", this); - this.canvas.innerHTML = E; - this.span = R._g.doc.createElement("span"); - this.span.style.cssText = "position:absolute;left:-9999em;top:-9999em;padding:0;margin:0;line-height:1;display:inline;"; - this.canvas.appendChild(this.span); - this.bottom = this.top = null; - }; - R.prototype.remove = function () { - R.eve("raphael.remove", this); - this.canvas.parentNode.removeChild(this.canvas); - for (var i in this) { - this[i] = typeof this[i] == "function" ? R._removedFactory(i) : null; - } - return true; - }; - - var setproto = R.st; - for (var method in elproto) if (elproto[has](method) && !setproto[has](method)) { - setproto[method] = (function (methodname) { - return function () { - var arg = arguments; - return this.forEach(function (el) { - el[methodname].apply(el, arg); - }); - }; - })(method); - } -})(); - - // EXPOSE - // SVG and VML are appended just before the EXPOSE line - // Even with AMD, Raphael should be defined globally - oldRaphael.was ? (g.win.Raphael = R) : (Raphael = R); - - if(typeof exports == "object"){ - module.exports = R; - } - return R; -})); |