diff options
author | Matija Čupić <matteeyah@gmail.com> | 2018-01-05 01:38:13 +0300 |
---|---|---|
committer | Matija Čupić <matteeyah@gmail.com> | 2018-01-05 01:38:13 +0300 |
commit | 9c9f7dc639768a0d6b930ec11c050a1333df934e (patch) | |
tree | 7a54287bb3e8eec9a57223bdace347de72beb936 | |
parent | 6fb4a533b74c861a1e533604da462efb6d309de0 (diff) | |
parent | 6f1b4dc76b4619f538b7216ad3a10ca9336d0c2b (diff) |
Merge branch 'master' into 41249-clearing-the-cache
732 files changed, 7450 insertions, 2629 deletions
diff --git a/.gitlab-ci.yml b/.gitlab-ci.yml index 4b149b13178..4f47d3f0171 100644 --- a/.gitlab-ci.yml +++ b/.gitlab-ci.yml @@ -431,6 +431,7 @@ ee_compat_check: - master - tags - /^[\d-]+-stable(-ee)?/ + - /^security-/ - branches@gitlab-org/gitlab-ee - branches@gitlab/gitlab-ee retry: 0 @@ -508,7 +509,7 @@ db:rollback-mysql: <<: *db-rollback <<: *use-mysql -.db-seed_fu: &db-seed_fu +.gitlab-setup: &gitlab-setup <<: *dedicated-runner <<: *except-docs-and-qa <<: *pull-cache @@ -517,22 +518,24 @@ db:rollback-mysql: SIZE: "1" SETUP_DB: "false" CREATE_DB_USER: "true" + FIXTURE_PATH: db/fixtures/development script: - git clone https://gitlab.com/gitlab-org/gitlab-test.git /home/git/repositories/gitlab-org/gitlab-test.git - - bundle exec rake db:setup db:seed_fu + - scripts/gitaly-test-spawn + - force=yes bundle exec rake gitlab:setup artifacts: when: on_failure expire_in: 1d paths: - log/development.log -db:seed_fu-pg: - <<: *db-seed_fu +gitlab:setup-pg: + <<: *gitlab-setup <<: *use-pg -db:seed_fu-mysql: - <<: *db-seed_fu +gitlab:setup-mysql: + <<: *gitlab-setup <<: *use-mysql # Frontend-related jobs @@ -600,6 +603,14 @@ codequality: artifacts: paths: [codeclimate.json] +sast: + image: registry.gitlab.com/gitlab-org/gl-sast:latest + before_script: [] + script: + - /app/bin/run . + artifacts: + paths: [gl-sast-report.json] + qa:internal: <<: *dedicated-runner <<: *except-docs diff --git a/.rubocop.yml b/.rubocop.yml index 0199bb9683a..9adc2fae7a8 100644 --- a/.rubocop.yml +++ b/.rubocop.yml @@ -3,6 +3,7 @@ inherit_gem: - rubocop-default.yml inherit_from: .rubocop_todo.yml +require: ./rubocop/rubocop AllCops: TargetRailsVersion: 4.2 @@ -24,8 +25,10 @@ Gitlab/ModuleWithInstanceVariables: Exclude: # We ignore Rails helpers right now because it's hard to workaround it - app/helpers/**/*_helper.rb + - ee/app/helpers/**/*_helper.rb # We ignore Rails mailers right now because it's hard to workaround it - app/mailers/emails/**/*.rb + - ee/**/emails/**/*.rb # We ignore spec helpers because it usually doesn't matter - spec/support/**/*.rb - features/steps/**/*.rb diff --git a/CHANGELOG.md b/CHANGELOG.md index a9539bbed87..26580e7183f 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -2,6 +2,35 @@ documentation](doc/development/changelog.md) for instructions on adding your own entry. +## 10.3.3 (2018-01-02) + +### Fixed (3 changes) + +- Fix links to old commits in merge request comments. +- Fix 404 errors after a user edits an issue description and solves the reCAPTCHA. +- Gracefully handle orphaned write deploy keys in /internal/post_receive. + + +## 10.3.2 (2017-12-28) + +### Fixed (1 change) + +- Fix migration for removing orphaned issues.moved_to_id values in MySQL and PostgreSQL. + + +## 10.3.1 (2017-12-27) + +### Fixed (3 changes) + +- Don't link LFS objects to a project when unlinking forks when they were already linked. !16006 +- Execute project hooks and services after commit when moving an issue. +- Fix Error 500s with anonymous clones for a project that has moved. + +### Changed (1 change) + +- Reduce the number of buckets in gitlab_cache_operation_duration_seconds metric. !15881 + + ## 10.3.0 (2017-12-22) ### Security (1 change, 1 of them is from the community) diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index 01d4a546b97..2b79f0825e2 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -553,7 +553,7 @@ the feature you contribute through all of these steps. 1. Description explaining the relevancy (see following item) 1. Working and clean code that is commented where needed -1. [Unit and system tests][testing] that pass on the CI server +1. [Unit, integration, and system tests][testing] that pass on the CI server 1. Performance/scalability implications have been considered, addressed, and tested 1. [Documented][doc-styleguide] in the `/doc` directory 1. [Changelog entry added][changelog], if necessary diff --git a/GITALY_SERVER_VERSION b/GITALY_SERVER_VERSION index 7e750b4ebf3..afed694eede 100644 --- a/GITALY_SERVER_VERSION +++ b/GITALY_SERVER_VERSION @@ -1 +1 @@ -0.60.0 +0.65.0 @@ -12,7 +12,7 @@ gem 'sprockets', '~> 3.7.0' gem 'default_value_for', '~> 3.0.0' # Supported DBs -gem 'mysql2', '~> 0.4.5', group: :mysql +gem 'mysql2', '~> 0.4.10', group: :mysql gem 'pg', '~> 0.18.2', group: :postgres gem 'rugged', '~> 0.26.0' @@ -283,7 +283,7 @@ group :metrics do gem 'influxdb', '~> 0.2', require: false # Prometheus - gem 'prometheus-client-mmap', '~> 0.7.0.beta43' + gem 'prometheus-client-mmap', '~> 0.7.0.beta44' gem 'raindrops', '~> 0.18' end @@ -402,7 +402,7 @@ group :ed25519 do end # Gitaly GRPC client -gem 'gitaly-proto', '~> 0.61.0', require: 'gitaly' +gem 'gitaly-proto', '~> 0.64.0', require: 'gitaly' gem 'toml-rb', '~> 0.3.15', require: false diff --git a/Gemfile.lock b/Gemfile.lock index b11b438a29c..c510a6da2d7 100644 --- a/Gemfile.lock +++ b/Gemfile.lock @@ -284,7 +284,7 @@ GEM po_to_json (>= 1.0.0) rails (>= 3.2.0) gherkin-ruby (0.3.2) - gitaly-proto (0.61.0) + gitaly-proto (0.64.0) google-protobuf (~> 3.1) grpc (~> 1.0) github-linguist (4.7.6) @@ -505,7 +505,7 @@ GEM mustermann (1.0.0) mustermann-grape (1.0.0) mustermann (~> 1.0.0) - mysql2 (0.4.5) + mysql2 (0.4.10) net-ldap (0.16.0) net-ssh (4.1.0) netrc (0.11.0) @@ -634,7 +634,7 @@ GEM parser unparser procto (0.0.3) - prometheus-client-mmap (0.7.0.beta43) + prometheus-client-mmap (0.7.0.beta44) pry (0.10.4) coderay (~> 1.1.0) method_source (~> 0.8.1) @@ -708,7 +708,7 @@ GEM json recursive-open-struct (1.0.0) redcarpet (3.4.0) - redis (3.3.3) + redis (3.3.5) redis-actionpack (5.0.2) actionpack (>= 4.0, < 6) redis-rack (>= 1, < 3) @@ -839,11 +839,11 @@ GEM rack shoulda-matchers (3.1.2) activesupport (>= 4.0.0) - sidekiq (5.0.4) + sidekiq (5.0.5) concurrent-ruby (~> 1.0) connection_pool (~> 2.2, >= 2.2.0) rack-protection (>= 1.5.0) - redis (~> 3.3, >= 3.3.3) + redis (>= 3.3.4, < 5) sidekiq-cron (0.6.0) rufus-scheduler (>= 3.3.0) sidekiq (>= 4.2.1) @@ -1046,7 +1046,7 @@ DEPENDENCIES gettext (~> 3.2.2) gettext_i18n_rails (~> 1.8.0) gettext_i18n_rails_js (~> 1.2.0) - gitaly-proto (~> 0.61.0) + gitaly-proto (~> 0.64.0) github-linguist (~> 4.7.0) gitlab-flowdock-git-hook (~> 1.0.1) gitlab-markup (~> 1.6.2) @@ -1087,7 +1087,7 @@ DEPENDENCIES method_source (~> 0.8) minitest (~> 5.7.0) mousetrap-rails (~> 1.4.6) - mysql2 (~> 0.4.5) + mysql2 (~> 0.4.10) net-ldap net-ssh (~> 4.1.0) nokogiri (~> 1.8.1) @@ -1122,7 +1122,7 @@ DEPENDENCIES peek-sidekiq (~> 1.0.3) pg (~> 0.18.2) premailer-rails (~> 1.9.7) - prometheus-client-mmap (~> 0.7.0.beta43) + prometheus-client-mmap (~> 0.7.0.beta44) pry-byebug (~> 3.4.1) pry-rails (~> 0.3.4) rack-attack (~> 4.4.1) diff --git a/app/assets/images/file_icons.svg b/app/assets/images/file_icons.svg new file mode 100644 index 00000000000..26ec1a6b388 --- /dev/null +++ b/app/assets/images/file_icons.svg @@ -0,0 +1 @@ +<?xml version="1.0" encoding="UTF-8" standalone="no"?><!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd"><svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink"><symbol viewBox="0 0 24 24" id="actionscript" xmlns="http://www.w3.org/2000/svg"><text style="line-height:113.99999857%" x="5.605" y="15.892" transform="scale(.91325 1.095)" font-weight="400" font-size="42.822" font-family="sans-serif" letter-spacing="0" word-spacing="0" fill="#f44336"/><path style="line-height:125%" d="M4.744 2.031c-1.157 0-1.994.31-2.51.93-.515.612-.771 1.678-.771 3.197v2.467c0 1.408-.402 2.111-1.201 2.111v2.035c.8 0 1.2.679 1.2 2.036v2.654c0 1.512.26 2.562.78 3.152.52.59 1.355.885 2.502.885V19.43c-.447 0-.77-.151-.97-.453-.195-.303-.292-.815-.292-1.538v-2.267c0-1.807-.404-2.937-1.214-3.395v-.045c.81-.464 1.214-1.581 1.214-3.351V6.025c0-1.283.42-1.925 1.262-1.925V2.03zm14.66 0V4.1c.842 0 1.262.642 1.262 1.925v2.268c0 1.843.402 2.996 1.207 3.46v.046c-.805.442-1.207 1.544-1.207 3.306v2.356c0 .715-.099 1.22-.299 1.516-.2.302-.52.453-.963.453v2.068c1.152 0 1.984-.295 2.494-.885.516-.59.772-1.663.772-3.218V14.84c0-1.379.404-2.069 1.209-2.069v-2.035c-.805 0-1.21-.696-1.21-2.09V6.113c0-1.49-.255-2.54-.77-3.152-.516-.62-1.348-.93-2.495-.93zm-3.054 4.46c-.455 0-.886.057-1.293.173a3.056 3.056 0 0 0-1.078.527c-.308.241-.551.549-.731.924-.18.37-.27.817-.27 1.336 0 .663.165 1.227.493 1.695.33.468.831.864 1.502 1.188.263.125.509.249.736.37.227.12.422.244.586.374.168.13.299.271.394.424a.963.963 0 0 1 .145.521c0 .144-.03.28-.09.405a.9.9 0 0 1-.275.318c-.12.088-.272.158-.455.21a2.34 2.34 0 0 1-.635.075c-.415 0-.825-.083-1.233-.25a3.644 3.644 0 0 1-1.13-.763v2.222a3.68 3.68 0 0 0 1.101.418c.427.093.875.139 1.346.139.459 0 .894-.05 1.305-.152a3.002 3.002 0 0 0 1.09-.5c.31-.237.556-.543.736-.918.183-.38.275-.849.275-1.405 0-.403-.052-.755-.156-1.056a2.542 2.542 0 0 0-.45-.813 3.295 3.295 0 0 0-.704-.633 6.754 6.754 0 0 0-.922-.535 12.4 12.4 0 0 1-.676-.348c-.2-.115-.37-.231-.51-.347a1.502 1.502 0 0 1-.322-.375.91.91 0 0 1-.115-.453c0-.153.033-.288.101-.408a.948.948 0 0 1 .29-.32c.123-.089.275-.156.454-.202a2.18 2.18 0 0 1 .598-.078c.16 0 .326.015.502.043.18.028.36.07.539.13.18.056.354.13.522.218.171.088.329.188.472.304V6.871a4.039 4.039 0 0 0-.957-.285 6.448 6.448 0 0 0-1.185-.096zm-8.774.165l-3.123 9.967h2.094l.605-2.217h3.053l.61 2.217h2.107L9.869 6.656H7.576zm1.072 1.78h.047c.028.347.077.646.145.896l.922 3.35H7.564l.934-3.377c.08-.288.13-.578.15-.87z" font-weight="400" font-size="51.019" font-family="sans-serif" letter-spacing="0" word-spacing="0" fill="#f44336"/></symbol><symbol viewBox="0 0 24 24" id="android" xmlns="http://www.w3.org/2000/svg"><path d="M15 5h-1V4h1m-5 1H9V4h1m5.53-1.84L16.84.85c.19-.19.19-.51 0-.71a.513.513 0 0 0-.71 0l-1.48 1.48C13.85 1.23 12.95 1 12 1c-.96 0-1.86.23-2.66.63L7.85.14a.501.501 0 0 0-.7 0c-.2.2-.2.52 0 .71l1.31 1.31C6.97 3.26 6 5 6 7h12c0-2-1-3.75-2.47-4.84M20.5 8A1.5 1.5 0 0 0 19 9.5v7a1.5 1.5 0 0 0 1.5 1.5 1.5 1.5 0 0 0 1.5-1.5v-7A1.5 1.5 0 0 0 20.5 8m-17 0A1.5 1.5 0 0 0 2 9.5v7A1.5 1.5 0 0 0 3.5 18 1.5 1.5 0 0 0 5 16.5v-7A1.5 1.5 0 0 0 3.5 8M6 18a1 1 0 0 0 1 1h1v3.5A1.5 1.5 0 0 0 9.5 24a1.5 1.5 0 0 0 1.5-1.5V19h2v3.5a1.5 1.5 0 0 0 1.5 1.5 1.5 1.5 0 0 0 1.5-1.5V19h1a1 1 0 0 0 1-1V8H6v10z" fill="#c0ca33"/></symbol><symbol viewBox="0 0 24 24" id="angular" xmlns="http://www.w3.org/2000/svg"><path d="M12.102 2.625l8.84 3.15-1.34 11.7-7.5 4.15-7.5-4.15-1.34-11.7 8.84-3.15m0 2.1l-5.53 12.4h2.06l1.11-2.78h4.7l1.11 2.78h2.05l-5.5-12.4m1.62 7.9h-3.23l1.61-3.87z" fill="#e53935"/></symbol><symbol viewBox="0 0 24 24" id="angular-component" xmlns="http://www.w3.org/2000/svg"><path d="M12.102 2.625l8.84 3.15-1.34 11.7-7.5 4.15-7.5-4.15-1.34-11.7 8.84-3.15m0 2.1l-5.53 12.4h2.06l1.11-2.78h4.7l1.11 2.78h2.05l-5.5-12.4m1.62 7.9h-3.23l1.61-3.87z" fill="#0288d1"/></symbol><symbol viewBox="0 0 24 24" id="angular-directive" xmlns="http://www.w3.org/2000/svg"><path d="M12.102 2.625l8.84 3.15-1.34 11.7-7.5 4.15-7.5-4.15-1.34-11.7 8.84-3.15m0 2.1l-5.53 12.4h2.06l1.11-2.78h4.7l1.11 2.78h2.05l-5.5-12.4m1.62 7.9h-3.23l1.61-3.87z" fill="#ab47bc"/></symbol><symbol viewBox="0 0 24 24" id="angular-guard" xmlns="http://www.w3.org/2000/svg"><path d="M12.102 2.625l8.84 3.15-1.34 11.7-7.5 4.15-7.5-4.15-1.34-11.7 8.84-3.15m0 2.1l-5.53 12.4h2.06l1.11-2.78h4.7l1.11 2.78h2.05l-5.5-12.4m1.62 7.9h-3.23l1.61-3.87z" fill="#43a047"/></symbol><symbol viewBox="0 0 24 24" id="angular-pipe" xmlns="http://www.w3.org/2000/svg"><path d="M12.102 2.625l8.84 3.15-1.34 11.7-7.5 4.15-7.5-4.15-1.34-11.7 8.84-3.15m0 2.1l-5.53 12.4h2.06l1.11-2.78h4.7l1.11 2.78h2.05l-5.5-12.4m1.62 7.9h-3.23l1.61-3.87z" fill="#00897b"/></symbol><symbol viewBox="0 0 24 24" id="angular-resolver" xmlns="http://www.w3.org/2000/svg"><path d="M12.102 2.625l8.84 3.15-1.34 11.7-7.5 4.15-7.5-4.15-1.34-11.7 8.84-3.15m0 2.1l-5.53 12.4h2.06l1.11-2.78h4.7l1.11 2.78h2.05l-5.5-12.4m1.62 7.9h-3.23l1.61-3.87z" fill="#43a047"/></symbol><symbol viewBox="0 0 24 24" id="angular-routing" xmlns="http://www.w3.org/2000/svg"><path d="M11 10H5L3 8l2-2h6V3l1-1 1 1v1h6l2 2-2 2h-6v2h6l2 2-2 2h-6v6a2 2 0 0 1 2 2H9a2 2 0 0 1 2-2V10z" fill="#43a047"/></symbol><symbol viewBox="0 0 24 24" id="angular-service" xmlns="http://www.w3.org/2000/svg"><path d="M12.102 2.625l8.84 3.15-1.34 11.7-7.5 4.15-7.5-4.15-1.34-11.7 8.84-3.15m0 2.1l-5.53 12.4h2.06l1.11-2.78h4.7l1.11 2.78h2.05l-5.5-12.4m1.62 7.9h-3.23l1.61-3.87z" fill="#ffca28"/></symbol><symbol viewBox="0 0 100 100" id="apiblueprint" xmlns="http://www.w3.org/2000/svg"><title>api-blueprint</title><path d="M50.133 7.521A16.998 16.998 0 0 0 33.135 24.52a16.998 16.998 0 0 0 4.945 11.974L24.861 57.398a16.998 16.998 0 0 0-3.175-.308A16.998 16.998 0 0 0 4.688 74.088a16.998 16.998 0 0 0 16.998 16.998 16.998 16.998 0 0 0 16.998-16.998 16.998 16.998 0 0 0-7.063-13.773l12.576-19.89a16.998 16.998 0 0 0 5.936 1.093 16.998 16.998 0 0 0 6.154-1.155l12.537 19.83a16.998 16.998 0 0 0-7.244 13.895 16.998 16.998 0 0 0 16.998 17 16.998 16.998 0 0 0 16.998-17A16.998 16.998 0 0 0 78.578 57.09a16.998 16.998 0 0 0-2.95.262L62.337 36.327A16.998 16.998 0 0 0 67.13 24.52 16.998 16.998 0 0 0 50.132 7.522z" fill="#42a5f5"/></symbol><symbol viewBox="0 0 24 24" id="applescript" xmlns="http://www.w3.org/2000/svg"><path d="M18.71 19.5c-.83 1.24-1.71 2.45-3.05 2.47-1.34.03-1.77-.79-3.29-.79-1.53 0-2 .77-3.27.82-1.31.05-2.3-1.32-3.14-2.53C4.25 17 2.94 12.45 4.7 9.39c.87-1.52 2.43-2.48 4.12-2.51 1.28-.02 2.5.87 3.29.87.78 0 2.26-1.07 3.81-.91.65.03 2.47.26 3.64 1.98-.09.06-2.17 1.28-2.15 3.81.03 3.02 2.65 4.03 2.68 4.04-.03.07-.42 1.44-1.38 2.83M13 3.5c.73-.83 1.94-1.46 2.94-1.5.13 1.17-.34 2.35-1.04 3.19-.69.85-1.83 1.51-2.95 1.42-.15-1.15.41-2.35 1.05-3.11z" fill="#78909c"/></symbol><symbol viewBox="0 0 24 24" id="appveyor" xmlns="http://www.w3.org/2000/svg"><path d="M12 2c-.084 0-.165.008-.248.01a10 10 0 0 0-.266.01 9.952 9.952 0 0 0-.754.066 10 10 0 0 0-.148.018 9.855 9.855 0 0 0-.93.177 10 10 0 0 0-.07.02c-.196.049-.392.1-.584.16v.012a10 10 0 0 0-2 .875V3.34c-.02.012-.038.027-.059.039a10 10 0 0 0-.953.635c-.09.067-.172.142-.26.213a10 10 0 0 0-.628.546c-.109.104-.211.211-.315.319a10 10 0 0 0-.476.539c-.1.12-.201.237-.295.361a10 10 0 0 0-.52.766c-.088.143-.17.288-.252.435a10 10 0 0 0-.363.723c-.072.161-.136.327-.2.492a10 10 0 0 0-.269.778c-.02.067-.044.131-.062.199a10 10 0 0 0-.008.027c-.098.364-.166.728-.22 1.09-.012.077-.024.153-.034.23a9.85 9.85 0 0 0-.08 1.182c0 .03-.006.057-.006.086a10 10 0 0 0 .008.148c.001.094-.002.188.002.282l.011.004a10 10 0 0 0 .333 2.158l-.012-.004c.012.047.033.091.047.139a10 10 0 0 0 .322.955c.02.052.037.106.059.158a10 10 0 0 0 .503 1.035c.065.116.14.226.21.34a10 10 0 0 0 .423.64c.092.128.187.252.285.375a10 10 0 0 0 .448.52c.112.123.222.248.341.365a10 10 0 0 0 .803.719 10 10 0 0 0 .01.006c.099.078.207.146.309.22a10 10 0 0 0 .648.442c.138.085.28.163.424.242a10 10 0 0 0 .715.358c.114.051.226.106.343.154a10 10 0 0 0 1.133.389c.016.004.031.01.047.015a10 10 0 0 0 .461.098 10 10 0 0 0 .482.103 10 10 0 0 0 .418.051 10 10 0 0 0 .575.065 10 10 0 0 0 .144.005A10 10 0 0 0 12 22a10 10 0 0 0 .197-.01 10 10 0 0 0 .496-.025 10 10 0 0 0 .49-.043 10 10 0 0 0 .489-.074 10 10 0 0 0 .51-.098 10 10 0 0 0 .47-.12 10 10 0 0 0 .477-.14 10 10 0 0 0 .47-.172 10 10 0 0 0 .481-.197 10 10 0 0 0 .414-.201 10 10 0 0 0 .475-.252 10 10 0 0 0 .39-.238 10 10 0 0 0 .452-.301 10 10 0 0 0 .38-.291 10 10 0 0 0 .385-.315 10 10 0 0 0 .375-.347 10 10 0 0 0 .36-.363 10 10 0 0 0 .293-.334 10 10 0 0 0 .353-.434 10 10 0 0 0 .28-.393 10 10 0 0 0 .263-.4 10 10 0 0 0 .264-.461 10 10 0 0 0 .228-.436 10 10 0 0 0 .195-.437 10 10 0 0 0 .196-.48 10 10 0 0 0 .228-.69 10 10 0 0 0 .028-.094 10 10 0 0 0 .021-.066 10 10 0 0 0 .098-.461 10 10 0 0 0 .103-.482 10 10 0 0 0 .051-.418 10 10 0 0 0 .065-.575 10 10 0 0 0 .005-.144A10 10 0 0 0 22 12a10 10 0 0 0-.01-.197 10 10 0 0 0-.025-.496 10 10 0 0 0-.043-.49 10 10 0 0 0-.074-.489 10 10 0 0 0-.098-.51 10 10 0 0 0-.12-.47 10 10 0 0 0-.14-.477 10 10 0 0 0-.172-.47 10 10 0 0 0-.197-.481 10 10 0 0 0-.201-.414 10 10 0 0 0-.252-.475 10 10 0 0 0-.238-.39 10 10 0 0 0-.301-.452 10 10 0 0 0-.291-.38 10 10 0 0 0-.315-.385 10 10 0 0 0-.347-.375 10 10 0 0 0-.363-.36 10 10 0 0 0-.334-.293 10 10 0 0 0-.434-.353 10 10 0 0 0-.393-.28 10 10 0 0 0-.4-.263 10 10 0 0 0-.461-.264 10 10 0 0 0-.436-.228 10 10 0 0 0-.437-.196 10 10 0 0 0-.48-.195 10 10 0 0 0-.69-.228 10 10 0 0 0-.094-.028 10 10 0 0 0-.066-.021 10 10 0 0 0-.461-.098 10 10 0 0 0-.482-.103 10 10 0 0 0-.418-.051 10 10 0 0 0-.575-.065 10 10 0 0 0-.144-.005A10 10 0 0 0 12 2zm-.016 5.002a5 5 0 0 1 .262.01 5 5 0 0 1 .227.011 5 5 0 0 1 .341.05 5 5 0 0 1 .135.019 5 5 0 0 1 .014.004 5 5 0 0 1 .115.025 5 5 0 0 1 .303.076 5 5 0 0 1 .265.086 5 5 0 0 1 .2.074 5 5 0 0 1 .242.106 5 5 0 0 1 .228.11 5 5 0 0 1 .196.109 5 5 0 0 1 .244.15 5 5 0 0 1 .17.12 5 5 0 0 1 .224.171 5 5 0 0 1 .186.16 5 5 0 0 1 .176.164 5 5 0 0 1 .172.18 5 5 0 0 1 .177.203 5 5 0 0 1 .133.172 5 5 0 0 1 .16.223 5 5 0 0 1 .133.214 5 5 0 0 1 .12.21 5 5 0 0 1 .107.216 5 5 0 0 1 .109.24 5 5 0 0 1 .084.223 5 5 0 0 1 .08.242 5 5 0 0 1 .07.264 5 5 0 0 1 .047.207 5 5 0 0 1 .045.277 5 5 0 0 1 .028.227 5 5 0 0 1 .02.351 5 5 0 0 1 .003.079 5 5 0 0 1-.012.271 5 5 0 0 1-.011.227 5 5 0 0 1-.05.341 5 5 0 0 1-.019.135 5 5 0 0 1-.004.014 5 5 0 0 1-.025.115 5 5 0 0 1-.076.303 5 5 0 0 1-.086.265 5 5 0 0 1-.074.2 5 5 0 0 1-.106.242 5 5 0 0 1-.11.228 5 5 0 0 1-.109.196 5 5 0 0 1-.15.244 5 5 0 0 1-.12.17 5 5 0 0 1-.171.224 5 5 0 0 1-.16.186 5 5 0 0 1-.164.176 5 5 0 0 1-.18.172 5 5 0 0 1-.203.177l-.002.002c-.018.019-.028.035-.047.053l-3.959 5.09-3.05-.979a141.684 141.684 0 0 0 3.177-3.084 5 5 0 0 1-.103-.015 5 5 0 0 1-.149-.024 5 5 0 0 1-.115-.025 5 5 0 0 1-3.57-3.04 5.072 5.072 0 0 1-.206-.661 5 5 0 0 1-.033-.147c-.025-.118-.036-.24-.054-.36-.987.993-1.964 1.993-2.954 3.05l-.98-3.053 5.092-3.957c.043-.044.082-.07.125-.11a5 5 0 0 1 .71-.634c.18-.13.367-.25.561-.356a5 5 0 0 1 .16-.08 4.94 4.94 0 0 1 .516-.222 5 5 0 0 1 .147-.057c.211-.07.43-.123.654-.164a5 5 0 0 1 .172-.027c.236-.035.476-.058.722-.059zM12 9a3 3 0 0 0-.053.002 3 3 0 0 0-.166.01 3 3 0 0 0-.133.011 3 3 0 0 0-.17.026 3 3 0 0 0-.113.021 3 3 0 0 0-.19.05 3 3 0 0 0-.103.03 3 3 0 0 0-.16.057 3 3 0 0 0-.129.053 3 3 0 0 0-.146.072 3 3 0 0 0-.12.063 3 3 0 0 0-.132.082 3 3 0 0 0-.123.08 3 3 0 0 0-.116.088 3 3 0 0 0-.126.105 3 3 0 0 0-.1.094 3 3 0 0 0-.111.111 3 3 0 0 0-.096.107 3 3 0 0 0-.094.116 3 3 0 0 0-.098.136 3 3 0 0 0-.072.11 3 3 0 0 0-.076.133 3 3 0 0 0-.07.132 3 3 0 0 0-.063.14 3 3 0 0 0-.054.14 3 3 0 0 0-.077.228 3 3 0 0 0-.007.026 3 3 0 0 0-.03.138 3 3 0 0 0-.031.149 3 3 0 0 0-.014.11 3 3 0 0 0-.02.183 3 3 0 0 0-.001.052A3 3 0 0 0 9 12a3 3 0 0 0 .002.053 3 3 0 0 0 .01.166 3 3 0 0 0 .011.133 3 3 0 0 0 .026.17 3 3 0 0 0 .021.113 3 3 0 0 0 .05.19 3 3 0 0 0 .03.103 3 3 0 0 0 .057.16 3 3 0 0 0 .053.129 3 3 0 0 0 .072.146 3 3 0 0 0 .063.12 3 3 0 0 0 .082.132 3 3 0 0 0 .08.123 3 3 0 0 0 .088.116 3 3 0 0 0 .105.126 3 3 0 0 0 .094.1 3 3 0 0 0 .111.111 3 3 0 0 0 .107.096 3 3 0 0 0 .116.094 3 3 0 0 0 .136.098 3 3 0 0 0 .11.072 3 3 0 0 0 .133.076 3 3 0 0 0 .132.07 3 3 0 0 0 .135.06 3 3 0 0 0 .153.061 3 3 0 0 0 .216.07 3 3 0 0 0 .004.003 3 3 0 0 0 .026.007 3 3 0 0 0 .138.03 3 3 0 0 0 .149.031 3 3 0 0 0 .11.014 3 3 0 0 0 .183.02 3 3 0 0 0 .011.001 3 3 0 0 0 .041 0A3 3 0 0 0 12 15a3 3 0 0 0 .053-.002 3 3 0 0 0 .166-.01 3 3 0 0 0 .133-.011 3 3 0 0 0 .17-.026 3 3 0 0 0 .113-.021 3 3 0 0 0 .19-.05 3 3 0 0 0 .103-.03 3 3 0 0 0 .16-.057 3 3 0 0 0 .129-.053 3 3 0 0 0 .146-.072 3 3 0 0 0 .12-.063 3 3 0 0 0 .132-.082 3 3 0 0 0 .123-.08 3 3 0 0 0 .116-.088 3 3 0 0 0 .126-.105 3 3 0 0 0 .1-.094 3 3 0 0 0 .111-.111 3 3 0 0 0 .096-.107 3 3 0 0 0 .094-.116 3 3 0 0 0 .098-.136 3 3 0 0 0 .072-.11 3 3 0 0 0 .076-.133 3 3 0 0 0 .07-.132 3 3 0 0 0 .06-.135 3 3 0 0 0 .061-.153 3 3 0 0 0 .07-.216 3 3 0 0 0 .003-.004 3 3 0 0 0 .007-.026 3 3 0 0 0 .03-.138 3 3 0 0 0 .031-.149 3 3 0 0 0 .002-.008 3 3 0 0 0 .012-.101 3 3 0 0 0 .02-.184 3 3 0 0 0 .001-.011 3 3 0 0 0 0-.041A3 3 0 0 0 15 12a3 3 0 0 0-.002-.053 3 3 0 0 0-.01-.166 3 3 0 0 0-.011-.133 3 3 0 0 0-.026-.17 3 3 0 0 0-.021-.113 3 3 0 0 0-.05-.19 3 3 0 0 0-.03-.103 3 3 0 0 0-.057-.16 3 3 0 0 0-.053-.129 3 3 0 0 0-.072-.146 3 3 0 0 0-.063-.12 3 3 0 0 0-.082-.132 3 3 0 0 0-.08-.123 3 3 0 0 0-.088-.116 3 3 0 0 0-.105-.126 3 3 0 0 0-.094-.1 3 3 0 0 0-.111-.111 3 3 0 0 0-.107-.096 3 3 0 0 0-.116-.094 3 3 0 0 0-.136-.098 3 3 0 0 0-.11-.072 3 3 0 0 0-.133-.076 3 3 0 0 0-.132-.07 3 3 0 0 0-.14-.063 3 3 0 0 0-.14-.054 3 3 0 0 0-.228-.077 3 3 0 0 0-.026-.007 3 3 0 0 0-.138-.03 3 3 0 0 0-.149-.031 3 3 0 0 0-.008-.002 3 3 0 0 0-.101-.012 3 3 0 0 0-.184-.02 3 3 0 0 0-.011-.001 3 3 0 0 0-.041 0A3 3 0 0 0 12 9z" fill="#42a5f5"/></symbol><symbol viewBox="0 0 720 720" id="arduino" xmlns="http://www.w3.org/2000/svg"><defs><symbol id="ana" preserveAspectRatio="xMinYMin meet" xmlns="http://www.w3.org/2000/svg"><path fill="none" stroke-opacity="100%" stroke-width="60" stroke="#00979c" d="M174 30a10.5 10.1 0 0 0 0 280C364 320 344 30 544 30a10.5 10.1 0 0 1 0 280C354 320 374 30 174 30"/><path d="M528 205v-32.8h-32.5v-13.7H528V126h13.9v32.5h32.5v13.7h-32.5V205H528z" text-anchor="middle" fill="#00979c" stroke-width="20" stroke="#00979c" font-family="sans-serif" font-size="167"/><path fill="#00979c" stroke="#00979c" stroke-width="23.6" transform="matrix(1.56 0 0 .64 -366 .528)" d="M321 266v-17.4h53.3V266H321z"/></symbol></defs><title>Layer 1</title><use x="20.063" y="360.85" transform="matrix(.997 0 0 .997 -18.596 -159.19)" xlink:href="#ana"/></symbol><symbol viewBox="0 0 24 24" id="assembly" xmlns="http://www.w3.org/2000/svg"><path d="M1.746 1.566v20.905H5.13v-2.088H3.438V3.656h1.69v-2.09H1.747zm17.219 0v2.09h1.693v16.727h-1.693v2.09h3.383V1.566h-3.383zM15.196 3.988c-.5 0-.93.076-1.29.225-.359.15-.652.372-.877.671-.226.302-.39.673-.494 1.108a6.715 6.715 0 0 0-.155 1.54c0 .573.049 1.083.15 1.528.1.442.264.811.49 1.11.222.298.512.524.872.676.36.153.795.23 1.304.23.518 0 .954-.075 1.308-.224.353-.153.643-.376.869-.671.219-.29.38-.661.484-1.112.104-.454.156-.967.156-1.54 0-.573-.052-1.079-.152-1.515a2.92 2.92 0 0 0-.485-1.106 2.09 2.09 0 0 0-.868-.686c-.354-.155-.79-.234-1.312-.234zm-6.814.12a.941.941 0 0 1-.138.458.849.849 0 0 1-.356.296A1.71 1.71 0 0 1 7.385 5a5.244 5.244 0 0 1-.631.037v1.11H8.19v3.6H6.754v1.188h4.545V9.745H9.894V4.11H8.382zm6.814 1.138c.375 0 .643.176.805.527.161.348.241.933.241 1.756 0 .814-.082 1.399-.247 1.756-.164.356-.43.534-.799.534-.369 0-.636-.178-.8-.534-.165-.357-.248-.941-.248-1.749 0-.829.082-1.415.243-1.763.162-.35.43-.527.805-.527zm-6.33 7.64c-.5 0-.93.073-1.29.223-.359.15-.651.374-.877.673-.225.302-.39.67-.494 1.106a6.715 6.715 0 0 0-.155 1.54c0 .573.05 1.082.15 1.527.1.442.264.814.49 1.112.222.3.514.525.874.677.36.152.793.229 1.302.229.519 0 .954-.076 1.308-.225.354-.153.643-.376.869-.672.22-.29.38-.66.484-1.111.104-.455.156-.967.156-1.54 0-.573-.05-1.079-.15-1.515a2.923 2.923 0 0 0-.487-1.106 2.084 2.084 0 0 0-.867-.686c-.353-.156-.791-.232-1.313-.232zm5.846.119a.941.941 0 0 1-.138.457.85.85 0 0 1-.356.296 1.71 1.71 0 0 1-.503.137 5.245 5.245 0 0 1-.631.037v1.112h1.435v3.597h-1.435v1.189h4.545v-1.189h-1.405v-5.636h-1.512zm-5.846 1.137c.375 0 .643.176.805.527.162.347.241.933.241 1.756 0 .813-.08 1.399-.245 1.755-.164.357-.432.534-.8.534-.37 0-.637-.177-.802-.534-.164-.356-.245-.939-.245-1.746 0-.83.08-1.418.242-1.765.161-.35.43-.527.804-.527z" fill="#ff6e40"/></symbol><symbol viewBox="0 0 24 24" id="aurelia" xmlns="http://www.w3.org/2000/svg"><defs><linearGradient id="api" x1="-31.824" x2="19.682" y1="-11.741" y2="35.548" gradientTransform="scale(.95818 1.0436)" gradientUnits="userSpaceOnUse" xlink:href="#apa"/><linearGradient id="apa" x1="-3.881" x2="2.377" y1="-1.442" y2="4.304"><stop stop-color="#C06FBB" offset="0"/><stop stop-color="#6E4D9B" offset="1"/></linearGradient><linearGradient id="apj" x1="12.022" x2="-15.716" y1="13.922" y2="-23.952" gradientTransform="scale(.96226 1.0392)" gradientUnits="userSpaceOnUse" xlink:href="#apb"/><linearGradient id="apb" x1=".729" x2="-.971" y1=".844" y2="-1.477"><stop stop-color="#6E4D9B" offset="0"/><stop stop-color="#77327A" offset=".14"/><stop stop-color="#B31777" offset=".29"/><stop stop-color="#CD0F7E" offset=".84"/><stop stop-color="#ED2C89" offset="1"/></linearGradient><linearGradient id="apk" x1="-23.39" x2="23.931" y1="-57.289" y2="8.573" gradientTransform="scale(1.0429 .95884)" gradientUnits="userSpaceOnUse" xlink:href="#apc"/><linearGradient id="apc" x1="-2.839" x2="2.875" y1="-6.936" y2="1.017"><stop stop-color="#C06FBB" offset="0"/><stop stop-color="#6E4D9B" offset="1"/></linearGradient><linearGradient id="apl" x1="-53.331" x2="6.771" y1="-30.517" y2="18.785" gradientTransform="scale(.99898 1.001)" gradientUnits="userSpaceOnUse" xlink:href="#apd"/><linearGradient id="apd" x1="-8.212" x2="1.02" y1="-4.691" y2="2.882"><stop stop-color="#C06FBB" offset="0"/><stop stop-color="#6E4D9B" offset="1"/></linearGradient><linearGradient id="apm" x1="-14.029" x2="41.998" y1="-23.111" y2="26.259" gradientTransform="scale(1.0003 .99965)" gradientUnits="userSpaceOnUse" xlink:href="#ape"/><linearGradient id="ape" x1="-1.404" x2="4.19" y1="-2.309" y2="2.62"><stop stop-color="#C06FBB" offset="0"/><stop stop-color="#6E4D9B" offset="1"/></linearGradient><linearGradient id="apn" x1="31.177" x2="3.37" y1="41.442" y2="3.402" gradientTransform="scale(.96254 1.0389)" gradientUnits="userSpaceOnUse" xlink:href="#apf"/><linearGradient id="apf" x1="1.911" x2=".204" y1="2.539" y2=".204"><stop stop-color="#6E4D9B" offset="0"/><stop stop-color="#77327A" offset=".14"/><stop stop-color="#B31777" offset=".29"/><stop stop-color="#CD0F7E" offset=".84"/><stop stop-color="#ED2C89" offset="1"/></linearGradient><linearGradient id="apo" x1="-31.905" x2="19.599" y1="-14.258" y2="42.767" gradientTransform="scale(.95823 1.0436)" gradientUnits="userSpaceOnUse" xlink:href="#apg"/><linearGradient id="apg" x1="-3.881" x2="2.377" y1="-1.738" y2="5.19"><stop stop-color="#C06FBB" offset="0"/><stop stop-color="#6E4D9B" offset="1"/></linearGradient><linearGradient id="app" x1="4.301" x2="34.534" y1="34.41" y2="4.514" gradientTransform="scale(1.002 .99796)" gradientUnits="userSpaceOnUse" xlink:href="#aph"/><linearGradient id="aph" x1=".112" x2=".901" y1=".897" y2=".116"><stop stop-color="#6E4D9B" offset="0"/><stop stop-color="#77327A" offset=".14"/><stop stop-color="#B31777" offset=".53"/><stop stop-color="#CD0F7E" offset=".79"/><stop stop-color="#ED2C89" offset="1"/></linearGradient></defs><g transform="rotate(11.282 -1.694 21.569) scale(.47102)" clip-rule="evenodd" fill="none" fill-rule="evenodd" stroke-linejoin="round" stroke-miterlimit="1.414"><path d="M8.002 6.127L4.117 8.719.116 2.723 4 .13z" transform="rotate(-11.284 17.839 -78.732)" fill="url(#api)"/><path d="M9.179 1.887l6.637 9.946-7.906 5.276-6.637-9.946L.115 5.43 8.02.153z" transform="rotate(-11.284 129.49 -99.884)" fill="url(#apj)"/><path d="M7.3 1.88l1.462 2.189-6.018 4.015L.124 4.16l1.315-.877L6.143.144z" transform="rotate(-11.284 167.2 -62.32)" fill="url(#apk)"/><path d="M2.328 1.146L4.016.02l2.619 3.925L2.75 6.537 1.29 4.347l2.197-1.466zm-1.04 3.201L.132 2.612l2.197-1.466 1.158 1.735z" transform="rotate(-11.284 104.37 -149.22)" fill="url(#apl)"/><path d="M5.346 9.155l-1.315.877L.03 4.035 6.047.019l2.805 4.204L4.15 7.36l4.703-3.138 1.197 1.793z" transform="rotate(-11.284 81.819 7.645)" fill="url(#apm)"/><path d="M14.533 9.934l1.197 1.793-7.907 5.276-1.196-1.793L.052 5.358 7.958.082z" transform="rotate(-11.284 17.141 -7.825)" fill="url(#apn)"/><path d="M6.235 7.177L4.038 8.643 2.84 6.849.036 2.646 3.92.053 7.923 6.05z" transform="rotate(-11.284 18.188 -79.174)" fill="url(#apo)"/><path d="M18.955 35.925L17.48 34.45l3.998-3.998 1.475 1.475z" fill="#714896"/><path d="M33.33 21.55l-1.475-1.474 1.867-1.868 1.475 1.475z" fill="#6f4795"/><path d="M7.12 24.09l-1.525-1.525 3.998-3.998 1.525 1.525z" fill="#88519f"/><path d="M21.495 9.714L19.97 8.19l1.868-1.868 1.524 1.525z" fill="#85509e"/><path d="M31.418 23.462l-6.72 6.72-1.475-1.474 6.72-6.721z" fill="#8d166a"/><path d="M18.058 10.101l1.525 1.525-6.721 6.72-1.525-1.524z" fill="#a70d6f"/><path d="M2.375 11.769l1.9 1.9-1.9 1.901-1.901-1.9z" fill="#9e61ad"/><path d="M15.523 36.482l1.9 1.9-1.9 1.901-1.9-1.9z" fill="#8053a3"/><path d="M8.372 38.294L.017 29.876 29.749.08l8.636 8.201z" transform="translate(1.823 1.548)" fill="url(#app)"/></g></symbol><symbol viewBox="0 0 24 24" id="autohotkey" xmlns="http://www.w3.org/2000/svg"><path d="M5 3c-1.11 0-2 .89-2 2v14a2 2 0 0 0 2 2h14a2 2 0 0 0 2-2V5a2 2 0 0 0-2-2H5zm3.668 3.447a.9.9 0 0 1 .652.256.84.84 0 0 1 .262.625c0 .34-.014.852-.041 1.537-.022.68-.033 1.19-.033 1.53 0 .111-.016.326-.047.644a6.149 6.149 0 0 0-.033.68l2.578-.485c1.007-.179 1.874-.281 2.603-.308.018-.3.048-1.105.088-2.416.01-.345.115-.742.317-1.19.25-.55.533-.826.851-.826.237 0 .448.08.631.236.197.17.295.382.295.637a.775.775 0 0 1-.025.201c-.09.327-.135.612-.135.854 0 .125-.014.32-.041.584-.023.26-.033.453-.033.578 0 .425-.022 1.056-.067 1.893a38.963 38.963 0 0 0-.068 1.892c0 .327.025.816.074 1.465.05.649.074 1.136.074 1.463a.84.84 0 0 1-.261.625.893.893 0 0 1-.65.254 1 1 0 0 1-.686-.254.777.777 0 0 1-.29-.611c0-.327-.015-.818-.046-1.471a39.552 39.552 0 0 1-.041-1.47c0-.256.004-.482.013-.679-.702.032-1.57.142-2.603.33-.86.157-1.719.316-2.578.477-.01.304-.042.812-.096 1.523a22.354 22.354 0 0 0-.066 1.538.84.84 0 0 1-.262.625.893.893 0 0 1-.65.253.898.898 0 0 1-.653-.253.84.84 0 0 1-.262-.625c0-.452.038-1.128.114-2.028.08-.9.12-1.575.12-2.027 0-.573.015-1.436.042-2.586.027-1.155.04-2.017.04-2.59a.84.84 0 0 1 .263-.625.895.895 0 0 1 .65-.256z" fill="#4caf50"/></symbol><symbol viewBox="0 0 24 24" id="autoit" xmlns="http://www.w3.org/2000/svg"><defs id="ardefs8"><style id="arstyle4482">.cls-1{fill:#5d83ac}.cls-2{fill:#f0f0f0;fill-rule:evenodd}</style><style id="arstyle4510">.cls-1{fill:#5d83ac}.cls-2{fill:#f0f0f0;fill-rule:evenodd}</style></defs><g id="arg4522" transform="translate(-59.538 -26.404) scale(.0555)"><path d="M12.8 2.133A10.666 10.666 0 0 0 2.136 12.799 10.666 10.666 0 0 0 12.8 23.465a10.666 10.666 0 0 0 10.668-10.666A10.666 10.666 0 0 0 12.8 2.133zm.15 4.713c.456 0 .836.105 1.142.314.306.21.565.469.78.78l6.089 8.812H9.627l1.82-2.506h3.36c.315 0 .589.01.822.03a11.93 11.93 0 0 1-.473-.663 39.13 39.13 0 0 0-.517-.75l-1.748-2.578-4.577 6.467H4.746l6.25-8.813c.204-.281.46-.534.772-.757.31-.224.705-.336 1.181-.336z" transform="matrix(16.89188 0 0 16.89188 1072.761 475.745)" id="arcircle4514" fill="#1976d2" stroke-width=".026"/></g></symbol><symbol viewBox="0 0 213.33333 213.33333" id="babel" xmlns="http://www.w3.org/2000/svg"><path d="M50.22 199.659c-.875-.406-1.261-1.6-.857-2.652.404-1.053.12-1.914-.63-1.914s-1.615.748-1.92 1.663c-.328.983-1.27.302-2.304-1.667-.962-1.831-3.718-5.533-6.126-8.226-9.418-10.535-7.71-27.444 5.432-53.77 12.459-24.96 23.117-39.033 45.966-60.696 30.229-28.66 52.679-46.223 70.587-55.22 10.98-5.518 13.025-5.059 2.778.624-11.004 6.102-11.378 6.359-10.512 7.226.33.33 7.306-2.67 15.504-6.667 15.87-7.737 16.34-7.912 16.34-6.082 0 .652-4.95 3.738-11 6.858-13.062 6.736-12.722 6.48-10.472 7.872 1.117.69 5.428-.582 11.54-3.406 5.367-2.48 10.397-4.508 11.179-4.508 2.755 0-3.928 5.302-11.541 9.157-20.437 10.35-68.937 46.043-68.07 50.097.166.777-5.792 7.639-13.241 15.248-15.257 15.587-26.14 30.002-33.748 44.706-6.379 12.326-7.457 17.734-5.385 26.996 3.482 15.56 11.592 18.366 31.482 10.895 28.228-10.603 45.758-28.704 47.022-48.556.602-9.442-1.317-13.479-8.52-17.93-4.01-2.48-5.268-2.621-12.065-1.365-4.173.771-10.153 2.906-13.289 4.744s-6.455 3.34-7.377 3.34c-.922 0-3.216 1.336-5.096 2.968-1.88 1.633.48-1.13 5.247-6.14 6.82-7.167 7.956-8.9 5.333-8.132-5.208 1.525-10.194 4.33-15.649 8.803-2.76 2.264-.923.175 4.08-4.641 11.565-11.131 21.183-15.97 33.088-16.641 17.097-.966 27.254 5.805 31.964 21.31 2.435 8.017 2.609 10.24 1.353 17.37-1.65 9.361-7.034 21.553-15.593 35.307-4.398 7.067-8.434 11.427-15.588 16.844-9.166 6.94-15.654 11.02-15.654 9.845 0-.295 2.455-2.161 5.455-4.147 8.818-5.835 5.075-5.377-8.326 1.02-6.854 3.27-15.199 6.593-18.542 7.38-7.106 1.675-30.527 3.164-32.846 2.089zm-8.408-19.899c0-1.1-.6-2-1.333-2-.734 0-1.334.9-1.334 2s.6 2 1.333 2c.734 0 1.334-.9 1.334-2zm89.255-8.204c1.53-1.945 2.473-3.845 2.097-4.222-.377-.377-.836-.435-1.02-.13-.182.306-1.787 2.206-3.565 4.223-1.778 2.016-2.571 3.666-1.763 3.666s2.72-1.591 4.25-3.536zm-77.644-1.745c-.82-2.172-1.74-3.7-2.045-3.396-.951.952 1.088 7.345 2.343 7.345.656 0 .522-1.777-.298-3.95zm82.303-27.915c-.837-.837-3.217 2.55-3.184 4.53.012.734.896.178 1.965-1.235 1.07-1.413 1.618-2.896 1.219-3.295zm-66.238-36.904c-1.312-1.312-3.676.702-3.676 3.133 0 2.035.175 2.031 2.254-.047 1.24-1.24 1.88-2.628 1.422-3.086zm39.657.768c4.403-2.196 6.8-3.986 5.333-3.982-2.838.01-16.667 6.028-16.667 7.254 0 1.6 3.717.527 11.333-3.272zm16.667-5.333c0-.733-.9-1.333-2-1.333s-2 .6-2 1.333.9 1.333 2 1.333 2-.6 2-1.333zm-3.334-3.923l5.334-1.104-7.334-.133c-4.033-.073-8.233.45-9.333 1.16-2.539 1.64 3.572 1.682 11.333.077zm35.738-63.976c2.788-1.69 4.765-3.376 4.393-3.748-.947-.947-11.942 5.654-14.237 8.548-1.792 2.258-1.714 2.276 1.44.329a1452.76 1452.76 0 0 1 8.403-5.13z" fill="#ffca28" stroke-width="1.333"/></symbol><symbol viewBox="0 0 400 400" fill-opacity=".05" id="bithound" xmlns="http://www.w3.org/2000/svg"><g transform="matrix(.88 0 0 .88 24.121 2.895)" fill="#e53935" fill-opacity="1"><path d="M370.5 207c-1.5-14.8-4.8-29.9-9.5-44-13.5-40.3-38.6-81.6-70.3-110.1-1.4-1.2-6.7-4.4-8.7-3.3-5.2 2.9 4.6 22.8 5.8 26.4 7.4 22 12.1 45.3 6.8 68.3-7.1 30.4-30.4 51.7-61.5 54.3-17.1 1.4-34.3-.5-51.4 1.5-25.6 3-51.7 11.8-68 32.8-1.9 2.4-3.6 5.1-5.2 7.9h-.4c-6.3.7-12.6-2-15.7-3.7-.8-.5-1.6-.9-2.2-1.2-19-10.5-33-34-41.6-53.4-3.9-9-7.2-18.4-9.3-27.9-1-4.3-1.1-8.8-1.3-13.2-.1-2.7.3-6.5-1.2-8.9-3.3-5.2-7.5-.2-8.2 4-1.1 6.9-2.1 13.7-1.8 20.7.5 11.8 3.8 23.5 8 34.5 6.2 16.2 14.9 31.1 26.2 44.4 4.7 5.5 9.7 10.6 15.1 15.3 4.8 4.3 10.9 7.7 14.5 13.2 4.2 6.3 4.9 14.1 4.5 21.4-1 19.3-1.6 37.4 3.9 56.2 4.8 16.7 10.8 33.8 20.8 48.1 5 7.1 11.2 14.6 18 19.9 4.6 3.6 13.3 4 8.3-9.2-11.1-29.3-12.1-59.7 5.2-87.1 14.5-22.8 40.1-43.1 69-39.5 42.5 5.3 72.1 44.3 70 86-.6 11.7-1 21.7-4.7 32.7-1.5 4.4-2.6 10-1.5 14.6 1.8 7.8 10.5 4.9 14.3-.2 10.3-14 21.1-27.6 30.8-42 31.6-47.2 47-101.8 41.3-158.5z"/><path d="M132.4 92.1c.7 2.3 1.4 4.8 1.9 7.5.1 1.1.4 2.3 1 3.4 2.6 6.8 8.9 10.5 14.8 14 3.6 2.2 10.1 4.3 14.1 5.9 5.2 2.1 16.4-.6 21.7-1 12.2-1 23.5-5.3 34.7 1.2-57.4 67.3-3.2 82.3 38.8 49.9 48-37 2.8-124.3 2.8-124.3s-1-6.8-19.2-10.8c-1.7-.9-3.4-1.7-5.1-2.4-18-8.3-34.2 5.3-47.2 16.4-3.8 3.2-7.5 6.4-11.5 9.4-5.4 4-11.2 7.3-17.3 10.2-6.4 3-14 6.4-21.1 6.7-1 0-2.9.2-4.9.6-3.1.3-4.7 1.1-5.4 2.5-1.2 1-2 2.4-1.8 4.2.2 2.5 1.4 4.6 2.7 6.2.4.1.7.3 1 .4z"/></g></symbol><symbol viewBox="0 0 400.00001 399.99999" id="bower" xmlns="http://www.w3.org/2000/svg"><g transform="translate(12.061 33.203) scale(.81733)"><path d="M447.61 200.08c-23.139-22.234-138.85-36.114-175.36-40.154a107.137 107.137 0 0 0 4.517-12.944 146.107 146.107 0 0 1 15.905-5.901c.677 1.997 3.865 9.648 5.682 13.279 73.415 2.025 77.184-54.557 80.17-70.058 2.92-15.157 2.771-29.802 27.953-56.575-37.516-10.933-91.467 16.945-109.54 58.437-6.79-2.545-13.597-4.424-20.328-5.586-4.824-19.46-29.944-73.672-95.863-73.672-83.46 0-174.43 68.853-174.43 185.41 0 97.976 66.891 183.84 104.68 183.84 16.505 0 30.703-12.36 34.036-23.44 2.795 7.597 11.368 31.213 14.184 37.225 4.162 8.89 23.41 16.583 31.833 7.357 10.83 6.017 30.703 9.641 41.534-6.405 20.86 4.412 39.3-8.026 39.702-22.868 10.235-.546 15.256-14.918 13.021-26.363-1.647-8.426-19.248-38.66-26.113-49.098 13.59 11.054 48.013 14.183 52.194.007 21.911 17.198 56.057 8.171 58.765-5.815 26.624 6.917 57.16-8.276 52.146-26.676 42.771-2.958 37.296-48.464 25.296-59.996z" fill="#543729" stroke-width=".973"/><path d="M328.514 103.025c9.212-18.277 20.788-38.234 35.409-50.58-16.093 6.485-31.981 25.873-41.375 46.595a144.914 144.914 0 0 0-14.552-8.132c13.105-27.972 43.555-51.332 77.112-53.157-22.477 20.385-14.498 62.754-32.979 85.183-5.288-5.311-17.43-15.562-23.615-19.909zm-14.53 29.762c.01-.7.272-6.094.763-8.557-1.288-.304-9.3-1.87-13.476-1.772-.304 5.245 2.204 14.17 4.684 19.541 17.075-.358 29.408-5.471 36.667-10.172-6.18-2.88-16.726-5.442-24.745-6.974-.894 1.851-3.097 6.568-3.892 7.934z" fill="#00acee"/><g stroke-width=".973"><path d="M250.54 277.39c.004.024.015.057.018.082-2.165-4.657-4.463-10.314-7.208-17.708 10.688 15.557 44.184 7.533 42.427-6.407 16.395 12.336 50.143-2.055 42.471-19.353 16.423 7.653 35.168-7.745 30.964-14.455 28 5.4 54.832 10.783 63.256 12.938-5.595 9.123-18.339 15.566-37.549 11.089 10.38 14.14-9.773 31.105-37.844 21.76 6.18 13.883-18.814 26.38-47.22 11.91.361 13.889-35.24 15.488-49.315.143zm55.543-70.194c32.497 2.495 86.238 7.34 119.51 11.997-2.102-10.828-7.844-13.921-25.905-18.772-19.425 2.072-68.706 6.913-93.604 6.776z" fill="#2baf2b"/><path d="M285.78 253.36c16.395 12.336 50.143-2.055 42.471-19.353 16.423 7.653 35.168-7.745 30.964-14.455-33.103-6.383-67.84-12.788-75.719-13.908 4.78.254 12.702.797 22.59 1.556 24.899.137 74.18-4.704 93.604-6.775-31.452-7.975-95.666-19.613-140.01-22.48-2.055 3.003-5.833 8.097-12.413 13.51-19.403 41.053-54.557 68.34-93.454 68.34-11.335 0-24.018-1.912-38.233-6.456-8.865 9.497-46.661 16.694-77.329 1.641 24.326 56.961 80.74 94.984 143.19 94.984 52.591 0 75.912-53.704 70.808-67.914-1.238-3.45-6.145-14.889-8.891-22.283 10.689 15.556 44.185 7.532 42.429-6.408z" fill="#ffcc2f"/><path d="M253.91 145.27c4.644-2.526 20.69-12.253 35.981-15.908a67.843 67.843 0 0 1-.536-5.12c-10.032 2.403-28.945 10.51-39.784-.661 22.866 6.9 34.283-6.149 51.09-6.149 10.014 0 24.305 2.798 35.57 7.22-9.061-8.37-38.772-33.63-75.558-33.717-8.213 9.957-17.09 31.526-6.764 54.334z" fill="#cecece"/><path d="M115.58 253.33c14.215 4.544 26.898 6.457 38.233 6.457 38.896 0 74.05-27.29 93.454-68.341-14.351 11.978-39.291 22.228-78.241 22.228 34.694-7.866 64.56-25.156 79.753-50.427-10.68-16.998-22.263-54.603 7.07-84.33-4.512-14.497-26.475-52.766-75.095-52.766-84.85 0-155.17 71.001-155.17 166.15 0 22.525 4.547 43.65 12.67 62.664 30.666 15.054 68.462 7.858 77.327-1.64z" fill="#ef5734"/><path d="M141.03 108.45c0 21.644 17.546 39.191 39.19 39.191s39.192-17.548 39.192-39.191c0-21.644-17.548-39.191-39.192-39.191-21.644 0-39.19 17.547-39.19 39.191z" fill="#ffcc2f"/><path d="M156.76 108.45c0 12.958 10.507 23.463 23.463 23.463 12.96 0 23.464-10.506 23.464-23.463 0-12.959-10.504-23.464-23.464-23.464-12.957 0-23.463 10.506-23.463 23.464z" fill="#543729"/><ellipse cx="180.22" cy="98.044" rx="13.673" ry="8.501" fill="#fff"/></g></g></symbol><symbol viewBox="0 0 140 140" id="browserlist" xmlns="http://www.w3.org/2000/svg"><title>Browserslist logo</title><path d="M70.314 10.066a59.828 59.828 0 0 0-59.828 59.828 59.828 59.828 0 0 0 59.828 59.828 59.828 59.828 0 0 0 59.828-59.828 59.828 59.828 0 0 0-59.828-59.828zm-4.836 8.785c.496 4.043 1.352 7.322 2.572 10.223 4.779-4.287 10.265-7.546 16.041-9.02-.981 3.938-1.357 7.295-1.261 10.43 6.026-2.314 12.349-3.404 18.3-2.706-3.182 2.413-5.482 4.717-7.128 7.015-2.201 12.074 6.858 20.43 14.779 24.551a5.128 5.128 0 0 1 5.183-3.888 5.128 5.128 0 0 1 3.7 8.435v.002c-.487 1.055-2.002 2.343-3.497 3.219-4.075 2.39-11.172 5.736-20.914 7.39.045 1.214.077 2.453.077 3.747 0 4.817-.485 8.291-1.385 10.699-3.3 13.313-12.648 26.76-24.695 31.95.357-4.083.197-7.485-.402-10.591-5.582 3.218-11.646 5.278-17.623 5.52h-.002c1.785-3.662 2.855-6.878 3.412-9.976-6.347.996-12.727.742-18.377-1.17 2.93-2.732 5.054-5.314 6.673-7.96-6.292-1.344-12.169-3.87-16.766-7.686 3.822-1.544 6.795-3.239 9.3-5.197-5.426-3.517-10.034-7.998-12.972-13.23 4.012-.07 7.321-.568 10.3-1.453-3.786-5.215-6.468-11.032-7.333-16.951 3.861 1.405 7.196 2.133 10.36 2.355-1.662-6.22-2.081-12.605-.768-18.436 3.03 2.634 5.824 4.48 8.63 5.815.678-6.406 2.576-12.52 5.893-17.496 1.926 3.622 3.914 6.391 6.111 8.672 2.93-5.754 6.9-10.798 11.791-14.262zm26.465 19.557c-2.395 5.514-1.665 11.297-.555 18.732a2.138 2.138 0 0 0 .28-4.178 3.419 3.419 0 1 1 .092 6.704c.574 3.882 1.157 8.18 1.421 13.125a67.143 67.143 0 0 0 3.25-.649c6.616-1.487 12.258-3.801 16.871-6.506.45-.264.884-.563 1.276-.867.366-.557.333-.957.035-1.285-4.831-1.245-10.891-4.53-15.258-8.795-4.764-4.653-7.428-10.164-7.412-16.281z" fill="#ffca28" stroke-width=".855"/></symbol><symbol viewBox="0 0 140 140" id="browserlist_light" xmlns="http://www.w3.org/2000/svg"><title>Browserslist logo</title><g transform="translate(10.823 10.1)" stroke-width=".855"><circle cx="59.492" cy="59.795" r="59.828" fill="#ffca28"/><path d="M54.656 8.752c-4.89 3.464-8.862 8.508-11.791 14.262-2.198-2.28-4.185-5.05-6.111-8.672-3.318 4.976-5.216 11.09-5.893 17.496-2.807-1.335-5.6-3.18-8.63-5.814-1.314 5.83-.895 12.216.767 18.436-3.164-.223-6.498-.95-10.36-2.356.865 5.92 3.548 11.737 7.333 16.951-2.978.885-6.287 1.383-10.3 1.453 2.939 5.233 7.547 9.714 12.972 13.23-2.505 1.959-5.478 3.654-9.299 5.198 4.596 3.815 10.474 6.341 16.766 7.685-1.62 2.647-3.743 5.228-6.674 7.96 5.65 1.912 12.03 2.166 18.377 1.17-.556 3.098-1.626 6.314-3.412 9.975h.002c5.977-.24 12.042-2.3 17.623-5.52.6 3.108.76 6.51.402 10.593 12.047-5.19 21.395-18.638 24.695-31.951.9-2.408 1.385-5.881 1.385-10.7 0-1.293-.031-2.531-.076-3.745 9.742-1.655 16.839-5.001 20.914-7.39 1.494-.877 3.01-2.165 3.496-3.22v-.002a5.128 5.128 0 0 0-3.7-8.435 5.128 5.128 0 0 0-5.183 3.889c-7.92-4.122-16.98-12.477-14.779-24.551 1.646-2.299 3.947-4.603 7.13-7.016-5.952-.698-12.276.392-18.302 2.707-.095-3.135.28-6.492 1.262-10.43-5.776 1.473-11.262 4.733-16.041 9.02-1.22-2.902-2.076-6.18-2.572-10.223zm26.465 19.557c-.015 6.117 2.648 11.628 7.412 16.281 4.366 4.265 10.426 7.55 15.258 8.795.298.328.331.728-.035 1.285-.392.304-.825.603-1.275.867-4.613 2.704-10.256 5.019-16.871 6.506-1.071.24-2.154.458-3.25.649-.265-4.945-.848-9.243-1.422-13.125a3.419 3.419 0 1 0-.092-6.703 2.138 2.138 0 0 1-.28 4.177c-1.11-7.435-1.84-13.218.555-18.732z" fill="#37474f"/></g></symbol><symbol viewBox="0 0 24 24" id="bucklescript" xmlns="http://www.w3.org/2000/svg"><path d="M3 3v18h18V3H3zm14.1 8.858a5.5 5.5 0 0 1 1.26.145c.417.093.778.213 1.082.357v1.723h-.18a3.281 3.281 0 0 0-.959-.603 2.867 2.867 0 0 0-1.155-.247c-.14 0-.277.011-.416.035a1.4 1.4 0 0 0-.395.12.756.756 0 0 0-.291.231.54.54 0 0 0-.123.348c0 .198.065.35.196.456.13.104.376.2.738.288.237.057.466.11.683.164.22.054.455.128.706.222.496.188.86.444 1.095.77.238.32.357.738.357 1.253 0 .737-.271 1.336-.813 1.798-.538.46-1.27.689-2.197.689a5.447 5.447 0 0 1-1.402-.161 6.725 6.725 0 0 1-1.117-.416v-1.794h.183c.344.318.73.563 1.155.734.429.17.839.256 1.233.256.1 0 .235-.01.4-.03.166-.02.3-.055.403-.102a.97.97 0 0 0 .313-.225c.084-.09.127-.223.127-.4a.568.568 0 0 0-.183-.424c-.119-.12-.294-.213-.526-.276-.243-.067-.5-.128-.773-.185a5.523 5.523 0 0 1-.76-.227c-.544-.204-.936-.48-1.177-.828-.237-.351-.357-.786-.357-1.305 0-.697.27-1.265.81-1.703.54-.442 1.235-.663 2.083-.663zm-8.981.135h2.51c.521 0 .903.02 1.143.06.243.041.484.13.721.266.246.144.43.338.548.583.121.24.181.518.181.83 0 .36-.082.68-.247.959a1.697 1.697 0 0 1-.7.642v.04c.423.098.758.298 1.004.603.249.305.373.706.373 1.205 0 .361-.063.686-.19.97-.125.285-.296.52-.516.707a2.31 2.31 0 0 1-.845.472c-.304.094-.69.141-1.159.141H8.12v-7.478zm1.659 1.372v1.582h.262c.263 0 .486-.007.672-.017.185-.01.332-.043.44-.1.15-.077.248-.175.294-.295.046-.124.07-.266.07-.427a.91.91 0 0 0-.083-.371.518.518 0 0 0-.282-.277 1.187 1.187 0 0 0-.456-.086c-.18-.007-.433-.01-.76-.01h-.157zm0 2.873V18.1H9.9c.469 0 .804-.002 1.007-.006.202-.003.39-.046.56-.13a.712.712 0 0 0 .357-.33c.067-.142.099-.302.099-.483 0-.237-.04-.42-.121-.547-.078-.13-.214-.228-.405-.291a1.842 1.842 0 0 0-.538-.072 49.47 49.47 0 0 0-.716-.003h-.366z" fill="#26a69a" stroke-width="1.067"/></symbol><symbol viewBox="0 0 24 24" id="c" xmlns="http://www.w3.org/2000/svg"><path d="M15.45 15.97l.42 2.44c-.26.14-.68.27-1.24.39-.57.13-1.24.2-2.01.2-2.21-.04-3.87-.7-4.98-1.96-1.14-1.27-1.68-2.88-1.68-4.83C6 9.9 6.68 8.13 8 6.89 9.28 5.64 10.92 5 12.9 5c.75 0 1.4.07 1.94.19s.94.25 1.2.4l-.6 2.49-1.04-.34c-.4-.1-.87-.15-1.4-.15-1.15-.01-2.11.36-2.86 1.1-.76.73-1.14 1.85-1.18 3.34.01 1.36.37 2.42 1.08 3.2.71.77 1.7 1.17 2.99 1.18l1.33-.12c.43-.08.79-.19 1.09-.32z" fill="#0277bd"/></symbol><symbol viewBox="0 0 300 300" id="cabal" xmlns="http://www.w3.org/2000/svg"><g transform="translate(0 -822.52)" fill-rule="evenodd" color="#000"><rect transform="matrix(-.98339 .18149 .60192 .79856 0 0)" x="405.55" y="967.22" width="107.25" height="156.59" rx="12.306" ry="12.31" fill="#2d9bbd"/><rect transform="matrix(-.98528 .17093 -.59175 .80612 0 0)" x="-1156.5" y="1461.9" width="108.34" height="123.15" rx="10.69" ry="12.31" fill="#4a4bcd"/><path d="M52.112 965.158c-1.343 3.515-26.292 23.248-25.744 27.277.548 4.03 29.812 16.023 32.04 19.027s10.545 41.668 13.603 42.5 18.828-31.274 21.548-32.932c2.72-1.658 32.808 2.503 34.15-1.01 1.343-3.515-18.174-35.352-18.721-39.381-.548-4.03 9.732-40.12 7.502-43.125-2.229-3.005-30.06 9.427-33.118 8.594-3.059-.833-26.793-27.3-29.514-25.643-2.72 1.657-.405 41.177-1.747 44.693z" fill="#2e5bc1"/></g></symbol><symbol viewBox="0 0 24 24" id="cake" xmlns="http://www.w3.org/2000/svg"><path d="M12.254 6.621a1.807 1.807 0 0 0 1.808-1.807c0-.344-.09-.66-.262-.932l-1.546-2.684-1.546 2.684a1.72 1.72 0 0 0-.262.932 1.808 1.808 0 0 0 1.808 1.807m4.158 9.04l-.967-.976-.976.976c-1.175 1.166-3.236 1.175-4.42 0l-.959-.976-.994.976a3.134 3.134 0 0 1-3.977.353v4.167a.904.904 0 0 0 .904.904h14.463a.904.904 0 0 0 .904-.904v-4.167a3.134 3.134 0 0 1-3.977-.353m1.265-6.328h-4.52V7.525H11.35v1.808H6.83a2.712 2.712 0 0 0-2.711 2.712v1.392c0 .977.795 1.772 1.771 1.772.489 0 .94-.18 1.248-.515l1.952-1.926 1.908 1.926c.669.669 1.835.669 2.504 0l1.916-1.926 1.944 1.926c.316.334.768.515 1.247.515.976 0 1.78-.795 1.78-1.772v-1.392a2.712 2.712 0 0 0-2.711-2.712z" fill="#ff7043" stroke-width=".904"/></symbol><symbol viewBox="0 0 24 24" id="certificate" xmlns="http://www.w3.org/2000/svg"><path d="M4 3c-1.11 0-2 .89-2 2v10a2 2 0 0 0 2 2h8v5l3-3 3 3v-5h2a2 2 0 0 0 2-2V5a2 2 0 0 0-2-2H4m8 2l3 2 3-2v3.5l3 1.5-3 1.5V15l-3-2-3 2v-3.5L9 10l3-1.5V5M4 5h5v2H4V5m0 4h3v2H4V9m0 4h5v2H4v-2z" fill="#ff5722"/></symbol><symbol viewBox="0 0 24 24" id="changelog" xmlns="http://www.w3.org/2000/svg"><path d="M11 7v5.11l4.71 2.79.79-1.28-4-2.37V7m0-5C8.97 2 5.91 3.92 4.27 6.77L2 4.5V11h6.5L5.75 8.25C6.96 5.73 9.5 4 12.5 4a7.5 7.5 0 0 1 7.5 7.5 7.5 7.5 0 0 1-7.5 7.5c-3.27 0-6.03-2.09-7.06-5h-2.1c1.1 4.03 4.77 7 9.16 7 5.24 0 9.5-4.25 9.5-9.5A9.5 9.5 0 0 0 12.5 2z" fill="#8bc34a"/></symbol><symbol viewBox="0 0 24 24" id="clojure" xmlns="http://www.w3.org/2000/svg"><path d="M3.355 1.78c-.845 0-1.525.68-1.525 1.525v17.441c0 .845.68 1.525 1.525 1.525h17.442c.845 0 1.525-.68 1.525-1.525V3.305c0-.845-.68-1.526-1.525-1.526H3.355zm6.168 2.572h1.963l6.368 14.931H15.93l-3.38-8.086-3.349 8.086H7.21l4.346-10.38-2.032-4.551z" fill="#42a5f5"/></symbol><symbol viewBox="0 0 24 24" id="cmake" xmlns="http://www.w3.org/2000/svg"><path d="M11.99 2.965L2.977 20.999l9.874-8.47-.863-9.564z" fill="#1e88e5"/><path d="M12.007 2.963l.002.29 1.312 14.498-.001.006.023.26 7.362 2.979h.416l-.158-.311-.114-.228h-.002l-8.84-17.494z" fill="#e53935"/><path d="M8.607 16.11L2.98 20.995h17.743v-.016L8.607 16.11z" fill="#7cb342"/></symbol><symbol class="bfmain_logo__svg" viewBox="0 0 300 300.00001" id="code-climate" xmlns="http://www.w3.org/2000/svg"><path class="bfsymbol" d="M196.19 75.562l-51.846 51.561 30.766 30.766 21.08-21.08 59.252 59.537 30.481-30.766zm-61.246 60.961l-30.481-30.481-78.053 78.053-11.964 11.964 30.766 30.766 11.964-12.249 39.596-39.312 7.691-7.691 30.481 30.48 28.772 28.773 30.766-30.766-28.772-28.772z" fill="#eee" stroke-width="2.849"/></symbol><symbol class="bgmain_logo__svg" viewBox="0 0 300 300.00001" id="code-climate_light" xmlns="http://www.w3.org/2000/svg"><path class="bgsymbol" d="M196.19 75.562l-51.846 51.561 30.766 30.766 21.08-21.08 59.252 59.537 30.481-30.766zm-61.246 60.961l-30.481-30.481-78.053 78.053-11.964 11.964 30.766 30.766 11.964-12.249 39.596-39.312 7.691-7.691 30.481 30.48 28.772 28.773 30.766-30.766-28.772-28.772z" fill="#455a64" stroke-width="2.849"/></symbol><symbol viewBox="0 0 24 24" id="coffee" xmlns="http://www.w3.org/2000/svg"><path d="M2 21h18v-2H2M20 8h-2V5h2m0-2H4v10a4 4 0 0 0 4 4h6a4 4 0 0 0 4-4v-3h2a2 2 0 0 0 2-2V5a2 2 0 0 0-2-2z" fill="#42a5f5"/></symbol><symbol viewBox="0 0 24 24" id="coldfusion" xmlns="http://www.w3.org/2000/svg"><rect transform="rotate(90)" x="2.283" y="-21.86" width="19.487" height="19.487" ry="0" fill="#0d3858" stroke="#4dd0e1" stroke-width=".7"/><text x="6.653" y="16.426" fill="#4dd0e1" font-family="Calibri" font-size="29.001" font-weight="bold" letter-spacing="0" stroke-width=".725" word-spacing="0" style="line-height:1.25"><tspan x="6.653" y="16.426" font-family="'Segoe UI'" font-size="10.634" font-weight="normal">C<tspan font-size="11.844">f</tspan></tspan></text></symbol><symbol viewBox="0 0 24 24" id="conduct" xmlns="http://www.w3.org/2000/svg"><path d="M10 17l-4-4 1.41-1.41L10 14.17l6.59-6.59L18 9m-6-6a1 1 0 0 1 1 1 1 1 0 0 1-1 1 1 1 0 0 1-1-1 1 1 0 0 1 1-1m7 0h-4.18C14.4 1.84 13.3 1 12 1c-1.3 0-2.4.84-2.82 2H5a2 2 0 0 0-2 2v14a2 2 0 0 0 2 2h14a2 2 0 0 0 2-2V5a2 2 0 0 0-2-2z" fill="#cddc39"/></symbol><symbol viewBox="0 0 24 24" id="console" xmlns="http://www.w3.org/2000/svg"><path d="M20 19V7H4v12h16m0-16a2 2 0 0 1 2 2v14a2 2 0 0 1-2 2H4a2 2 0 0 1-2-2V5a2 2 0 0 1 2-2h16m-7 14v-2h5v2h-5m-3.42-4L5.57 9H8.4l3.3 3.3c.39.39.39 1.03 0 1.42L8.42 17H5.59z" fill="#ff7043"/></symbol><symbol viewBox="0 0 24 24" id="contributing" xmlns="http://www.w3.org/2000/svg"><path d="M17 9H7V7h10m0 6H7v-2h10m-3 6H7v-2h7M12 3a1 1 0 0 1 1 1 1 1 0 0 1-1 1 1 1 0 0 1-1-1 1 1 0 0 1 1-1m7 0h-4.18C14.4 1.84 13.3 1 12 1c-1.3 0-2.4.84-2.82 2H5a2 2 0 0 0-2 2v14a2 2 0 0 0 2 2h14a2 2 0 0 0 2-2V5a2 2 0 0 0-2-2z" fill="#ffca28"/></symbol><symbol viewBox="0 0 24 24" id="cpp" xmlns="http://www.w3.org/2000/svg"><path d="M10.5 15.97l.41 2.44c-.26.14-.68.27-1.24.39-.57.13-1.24.2-2.01.2-2.21-.04-3.87-.7-4.98-1.96C1.56 15.77 1 14.16 1 12.21c.05-2.31.72-4.08 2-5.32C4.32 5.64 5.96 5 7.94 5c.75 0 1.4.07 1.94.19s.94.25 1.2.4l-.58 2.49-1.06-.34c-.4-.1-.86-.15-1.39-.15-1.16-.01-2.12.36-2.87 1.1-.76.73-1.15 1.85-1.18 3.34 0 1.36.37 2.42 1.08 3.2.71.77 1.71 1.17 2.99 1.18l1.33-.12c.43-.08.79-.19 1.1-.32M11 11h2V9h2v2h2v2h-2v2h-2v-2h-2v-2m7 0h2V9h2v2h2v2h-2v2h-2v-2h-2v-2z" fill="#0277bd"/></symbol><symbol viewBox="0 0 24 24" id="credits" xmlns="http://www.w3.org/2000/svg"><path d="M3 3h18v2H3V3m4 4h10v2H7V7m-4 4h18v2H3v-2m4 4h10v2H7v-2m-4 4h18v2H3v-2z" fill="#9ccc65"/></symbol><symbol viewBox="0 0 200 200" id="crystal" xmlns="http://www.w3.org/2000/svg"><style>.st0{fill:none}</style><path d="M179.363 121.67l-57.623 57.507c-.23.23-.576.346-.806.23l-78.713-21.09c-.346-.115-.577-.345-.577-.576L20.44 79.144c-.115-.345 0-.576.23-.806L78.294 20.83c.23-.23.576-.346.807-.23l78.713 21.09c.345.114.576.345.576.575l21.09 78.597c.23.346.115.577-.115.807zM102.148 59.09l-77.33 20.63c-.115 0-.23.23-.115.345l56.586 56.47c.115.115.346.115.346-.115l20.744-77.215c.115 0-.115-.23-.23-.116z" stroke-width="1.153" fill="#cfd8dc"/></symbol><symbol viewBox="0 0 200 200" id="crystal_light" xmlns="http://www.w3.org/2000/svg"><style>.st0{fill:none}</style><path d="M179.363 121.67l-57.623 57.507c-.23.23-.576.346-.806.23l-78.713-21.09c-.346-.115-.577-.345-.577-.576L20.44 79.144c-.115-.345 0-.576.23-.806L78.294 20.83c.23-.23.576-.346.807-.23l78.713 21.09c.345.114.576.345.576.575l21.09 78.597c.23.346.115.577-.115.807zM102.148 59.09l-77.33 20.63c-.115 0-.23.23-.115.345l56.586 56.47c.115.115.346.115.346-.115l20.744-77.215c.115 0-.115-.23-.23-.116z" fill="#37474f" stroke-width="1.153"/></symbol><symbol viewBox="0 0 24 24" id="csharp" xmlns="http://www.w3.org/2000/svg"><path d="M11.5 15.97l.41 2.44c-.26.14-.68.27-1.24.39-.57.13-1.24.2-2.01.2-2.21-.04-3.87-.7-4.98-1.96C2.56 15.77 2 14.16 2 12.21c.05-2.31.72-4.08 2-5.32C5.32 5.64 6.96 5 8.94 5c.75 0 1.4.07 1.94.19s.94.25 1.2.4l-.58 2.49-1.06-.34c-.4-.1-.86-.15-1.39-.15-1.16-.01-2.12.36-2.87 1.1-.76.73-1.15 1.85-1.18 3.34 0 1.36.37 2.42 1.08 3.2.71.77 1.71 1.17 2.99 1.18l1.33-.12c.43-.08.79-.19 1.1-.32M13.89 19l.61-4H13l.34-2h1.5l.32-2h-1.5L14 9h1.5l.61-4h2l-.61 4h1l.61-4h2l-.61 4H22l-.34 2h-1.5l-.32 2h1.5L21 15h-1.5l-.61 4h-2l.61-4h-1l-.61 4h-2m2.95-6h1l.32-2h-1l-.32 2z" fill="#0277bd"/></symbol><symbol viewBox="0 0 24 24" id="css" xmlns="http://www.w3.org/2000/svg"><path d="M5 3l-.65 3.34h13.59L17.5 8.5H3.92l-.66 3.33h13.59l-.76 3.81-5.48 1.81-4.75-1.81.33-1.64H2.85l-.79 4 7.85 3 9.05-3 1.2-6.03.24-1.21L21.94 3H5z" fill="#42a5f5"/></symbol><symbol viewBox="0 0 24 24" id="css-map" xmlns="http://www.w3.org/2000/svg"><path d="M18 8v2h2v10H10v-2H8v4h14V8h-4z" fill="#42a5f5"/><path d="M4.676 3l-.488 2.51h10.211l-.33 1.623H3.864l-.496 2.502H13.58l-.57 2.863-4.119 1.36-3.569-1.36.248-1.232H3.06l-.593 3.005 5.898 2.254 6.8-2.254.902-4.53.18-.91L17.406 3H4.675z" fill="#42a5f5"/></symbol><symbol viewBox="0 0 33 33" id="cucumber" xmlns="http://www.w3.org/2000/svg"><title>cucumber-mark-transparent-pips</title><g transform="translate(0 -5)" fill="none" fill-rule="evenodd"><path d="M-4-1h40v40H-4z"/><path d="M16.641 7.092c-7.028 0-12.714 5.686-12.714 12.714 0 6.187 4.435 11.327 10.288 12.471v3.64C21.824 34.77 28.561 28.73 29.063 20.8c.303-4.772-2.076-9.644-6.09-12.01a10.575 10.575 0 0 0-1.455-.728l-.243-.097c-.223-.082-.448-.175-.68-.242a12.614 12.614 0 0 0-3.954-.632zm2.62 4.707a1.387 1.387 0 0 0-1.213.485c-.233.31-.379.611-.534.923-.466 1.087-.31 2.251.388 3.105 1.087-.233 2.01-.927 2.475-2.014a2.45 2.45 0 0 0 .243-1.02c.048-.824-.634-1.404-1.359-1.479zm-5.654.073c-.708.068-1.382.63-1.382 1.407 0 .31.087.709.243 1.02.466 1.086 1.46 1.78 2.546 2.013.621-.854.782-2.018.316-3.105-.155-.311-.3-.617-.534-.85a1.364 1.364 0 0 0-1.188-.485zm-3.809 3.735c-1.224.063-1.77 1.602-.752 2.402.31.233.612.403.922.559 1.087.466 2.344.306 3.275-.316-.233-1.009-1.023-1.936-2.11-2.402-.388-.155-.703-.243-1.092-.243-.087-.009-.161-.004-.243 0zm11.961 4.708a3.551 3.551 0 0 0-2.013.582c.233 1.01 1.023 1.936 2.11 2.401.389.156.705.244 1.093.244 1.397.077 2.08-1.65.994-2.427-.31-.233-.611-.379-.922-.534a3.354 3.354 0 0 0-1.262-.266zm-10.603.072a3.376 3.376 0 0 0-1.261.267c-.389.155-.69.325-.923.558-1.009.854-.33 2.48 1.068 2.402.388 0 .782-.087 1.092-.243 1.087-.465 1.859-1.392 2.014-2.401a3.474 3.474 0 0 0-1.99-.582zm3.931 2.378c-1.087.233-2.009.927-2.475 2.014-.155.31-.243.684-.243.995-.077 1.32 1.724 2.028 2.5 1.02.233-.312.378-.613.534-.923.466-1.01.306-2.174-.316-3.106zm2.887.073c-.621.854-.781 2.019-.315 3.106.155.31.3.615.534.848.854.932 2.65.243 2.572-.921 0-.31-.088-.71-.243-1.02-.466-1.087-1.46-1.78-2.547-2.013z" fill="#4caf50" stroke-width=".776"/></g></symbol><symbol id="cuda" viewBox="0 0 200 200" xmlns="http://www.w3.org/2000/svg"><style>.bust0{fill:#76b900}</style><title>NVIDIA-Logo</title><path id="buEye_Mark" class="bust0" d="M76.362 75.199V64.116c1.095-.068 2.19-.137 3.284-.137 30.377-.958 50.286 26.135 50.286 26.135s-21.483 29.83-44.539 29.83c-3.079 0-6.089-.48-8.962-1.438v-33.66c11.836 1.436 14.23 6.636 21.277 18.471l15.804-13.273s-11.562-15.12-30.992-15.12c-2.053-.068-4.105.069-6.158.274m0-36.67v16.556l3.284-.205c42.213-1.437 69.784 34.618 69.784 34.618s-31.608 38.45-64.516 38.45c-2.873 0-5.678-.274-8.483-.753v10.262c2.326.274 4.72.48 7.046.48 30.65 0 52.817-15.668 74.3-34.14 3.558 2.874 18.13 9.784 21.14 12.794-20.388 17.104-67.937 30.856-94.893 30.856-2.6 0-5.062-.137-7.525-.41v14.436h116.44V38.532zm0 79.977v8.757C48.038 122.2 40.17 92.712 40.17 92.712s13.615-15.05 36.192-17.514v9.579h-.068c-11.836-1.437-21.14 9.646-21.14 9.646s5.268 18.678 21.209 24.082M26.077 91.481S42.839 66.714 76.43 64.115v-9.03C39.213 58.094 7.057 89.565 7.057 89.565s18.199 52.68 69.305 57.47v-9.579c-37.492-4.652-50.286-45.975-50.286-45.975z" fill="#8bc34a" stroke-width=".684"/></symbol><symbol viewBox="0 0 24 24" id="dart" xmlns="http://www.w3.org/2000/svg"><title>Dart</title><path d="M12.486 1.385a.978.978 0 0 0-.682.281l-.01.007-6.387 3.692 6.371 6.372v.004l7.659 7.659 1.46-2.63-5.265-12.64-2.456-2.457a.972.972 0 0 0-.69-.288z" fill="#00ca94"/><path d="M5.422 5.35L1.73 11.733l-.007.01a.967.967 0 0 0 .006 1.371l3.059 3.061 11.963 4.706 2.704-1.502-.073-.073-.018.002-7.5-7.512h-.01L5.423 5.35z" fill="#1565c0"/><path d="M5.405 5.353l6.518 6.525h.01l7.502 7.51 2.855-.544.005-8.449-3.016-2.955c-.66-.647-1.675-1.064-2.695-1.202l.002-.032-11.181-.853z" fill="#1565c0"/><path d="M5.414 5.361l6.521 6.522v.009l7.506 7.506-.546 2.855h-8.448l-2.954-3.017c-.647-.66-1.064-1.676-1.2-2.696l-.033.003L5.414 5.36z" fill="#00ee94"/></symbol><symbol viewBox="0 0 24 24" id="database" xmlns="http://www.w3.org/2000/svg"><path d="M12 3C7.58 3 4 4.79 4 7s3.58 4 8 4 8-1.79 8-4-3.58-4-8-4M4 9v3c0 2.21 3.58 4 8 4s8-1.79 8-4V9c0 2.21-3.58 4-8 4s-8-1.79-8-4m0 5v3c0 2.21 3.58 4 8 4s8-1.79 8-4v-3c0 2.21-3.58 4-8 4s-8-1.79-8-4z" fill="#ffca28"/></symbol><symbol viewBox="0 0 24 24" id="diff" xmlns="http://www.w3.org/2000/svg"><path d="M3 1c-1.11 0-2 .89-2 2v11c0 1.11.89 2 2 2h2v-2H3V3h11v2h2V3c0-1.11-.89-2-2-2H3m6 6c-1.11 0-2 .89-2 2v2h2V9h2V7H9m4 0v2h1v1h2V7h-3m5 0v2h2v11H9v-2H7v2c0 1.11.89 2 2 2h11c1.11 0 2-.89 2-2V9c0-1.11-.89-2-2-2h-2m-4 5v2h-2v2h2c1.11 0 2-.89 2-2v-2h-2m-7 1v3h3v-2H9v-1H7z" fill="#42a5f5"/></symbol><symbol id="docker" viewBox="0 0 41 34.5" xmlns="http://www.w3.org/2000/svg"><style id="bystyle2">.byst0{fill:#fff}.byst1{clip-path:url(#bySVGID_4_)}</style><g id="byg34" transform="translate(.292 1.9)" fill="#0087c9"><g id="byg32"><g id="byg30"><title id="bytitle4">Group 3</title><g id="byg28"><g id="byg26"><g id="byg9"><path id="bySVGID_1_" class="byst0" d="M8.7 24c-1.1 0-2.1-.9-2.1-2s.9-2 2.1-2c1.2 0 2.1.9 2.1 2s-1 2-2.1 2zm25.8-10.9c-.2-1.6-1.2-2.9-2.5-3.9l-.5-.4-.4.5c-.8.9-1.1 2.5-1 3.7.1.9.4 1.8.9 2.5-.4.2-.9.4-1.3.6-.9.3-1.8.4-2.7.4H1.1l-.1.6c-.2 1.9.1 3.9.9 5.7l.4.7v.1c2.4 4 6.7 5.8 11.4 5.8 9 0 16.4-3.9 19.9-12.3 2.3.1 4.6-.5 5.7-2.7l.3-.5-.5-.3c-1.3-.8-3.1-.9-4.6-.5zm-12.9-1.6h-3.9v3.9h3.9zm0-4.9h-3.9v3.9h3.9zm0-5h-3.9v3.9h3.9zm4.8 9.9h-3.9v3.9h3.9zm-14.5 0H8v3.9h3.9zm4.9 0h-3.9v3.9h3.9zm-9.7 0H3.2v3.9h3.9zm9.7-4.9h-3.9v3.9h3.9zm-4.9 0H8v3.9h3.9z"/></g><g id="byg24"><defs id="bydefs12"><path id="bySVGID_2_" d="M8.7 24c-1.1 0-2.1-.9-2.1-2s.9-2 2.1-2c1.2 0 2.1.9 2.1 2s-1 2-2.1 2zm25.8-10.9c-.2-1.6-1.2-2.9-2.5-3.9l-.5-.4-.4.5c-.8.9-1.1 2.5-1 3.7.1.9.4 1.8.9 2.5-.4.2-.9.4-1.3.6-.9.3-1.8.4-2.7.4H1.1l-.1.6c-.2 1.9.1 3.9.9 5.7l.4.7v.1c2.4 4 6.7 5.8 11.4 5.8 9 0 16.4-3.9 19.9-12.3 2.3.1 4.6-.5 5.7-2.7l.3-.5-.5-.3c-1.3-.8-3.1-.9-4.6-.5zm-12.9-1.6h-3.9v3.9h3.9zm0-4.9h-3.9v3.9h3.9zm0-5h-3.9v3.9h3.9zm4.8 9.9h-3.9v3.9h3.9zm-14.5 0H8v3.9h3.9zm4.9 0h-3.9v3.9h3.9zm-9.7 0H3.2v3.9h3.9zm9.7-4.9h-3.9v3.9h3.9zm-4.9 0H8v3.9h3.9z"/></defs><clipPath id="bySVGID_4_"><use xlink:href="#bySVGID_2_" id="byuse14" width="100%" height="100%" overflow="visible"/></clipPath><g class="byst1" clip-path="url(#bySVGID_4_)" id="byg22"><g id="byg20"><g id="byg18"><path id="bySVGID_3_" class="byst0" d="M-48.8-21H1226v151.4H-48.8z"/></g></g></g></g></g></g></g></g></g></symbol><symbol viewBox="0 0 24 24" id="document" xmlns="http://www.w3.org/2000/svg"><path d="M13 9h5.5L13 3.5V9M6 2h8l6 6v12a2 2 0 0 1-2 2H6a2 2 0 0 1-2-2V4c0-1.11.89-2 2-2m9 16v-2H6v2h9m3-4v-2H6v2h12z" fill="#42a5f5"/></symbol><symbol preserveAspectRatio="xMidYMid" viewBox="0 0 200 200" id="drone" xmlns="http://www.w3.org/2000/svg"><g fill="#e0e0e0" transform="translate(9.063 22.346) scale(.71044)"><path d="M128.22.723C32.095.723.39 84.566.39 115.222h77.928S89.36 75.275 128.22 75.275s49.906 39.947 49.906 39.947h77.476c0-30.66-31.257-114.5-127.38-114.5m98.82 134.45h-48.914s-8.55 39.946-49.906 39.946c-41.355 0-49.902-39.948-49.902-39.948H30.255c0 10.25 37.727 82.708 98.443 82.708 60.714 0 98.344-59.604 98.344-82.708"/><circle cx="128" cy="126.08" r="32.768"/></g></symbol><symbol preserveAspectRatio="xMidYMid" viewBox="0 0 200 200" id="drone_light" xmlns="http://www.w3.org/2000/svg"><g fill="#424242" transform="translate(9.063 22.346) scale(.71044)"><path d="M128.22.723C32.095.723.39 84.566.39 115.222h77.928S89.36 75.275 128.22 75.275s49.906 39.947 49.906 39.947h77.476c0-30.66-31.257-114.5-127.38-114.5m98.82 134.45h-48.914s-8.55 39.946-49.906 39.946c-41.355 0-49.902-39.948-49.902-39.948H30.255c0 10.25 37.727 82.708 98.443 82.708 60.714 0 98.344-59.604 98.344-82.708"/><circle cx="128" cy="126.08" r="32.768"/></g></symbol><symbol viewBox="0 0 3473 3473" id="editorconfig" shape-rendering="geometricPrecision" text-rendering="geometricPrecision" image-rendering="optimizeQuality" fill-rule="evenodd" clip-rule="evenodd" xmlns="http://www.w3.org/2000/svg"><defs id="ccdefs4"><style id="ccstyle2">.ccfil2{fill:#020202}.ccfil0{fill:#e3e3f8}.ccfil5{fill:#efefef}.ccfil6{fill:#faf1f1}.ccfil3{fill:#fdf2f2}.ccfil1{fill:#fdfdfd}.ccfil4{fill:#fef3f3}</style></defs><g id="ccLayer_x0020_1" transform="matrix(.8945 0 0 .8945 138.649 275.985)"><g id="cc_631799120"><g id="ccg11"><path class="ccfil0" d="M967 1895c46-30 84-105 61-158-63 27-60 89-61 158z" id="ccpath7" fill="#e3e3f8"/><path class="ccfil0" d="M1679 2067c50-16 98-72 71-130-39 27-64 64-71 130z" id="ccpath9" fill="#e3e3f8"/></g><g id="ccg21"><path class="ccfil1" d="M280 2895c0 63 16 131 60 155 162 91 730 20 923-23 101-23 183-98 278-139 214-93 369-168 540-293 124-91 321-347 342-500l-169-38c-4 172-43 211-196 251-103 28-304 34-409 16-139-23-202-96-265-179-122-162 27-275-166-286-203 249-561 70-718 45-67 97-224 727-222 871 97-33 158 3 245 37 308 119 39 224-84 193-84-20-110-75-159-110z" id="ccpath13" fill="#fdfdfd"/><path class="ccfil1" d="M683 1458c125 24 236 76 342 129 173 86 204 74 220 194 2 22-2 34 61 54 106 33-61-26 223-25 169 1 556 69 681 148 52 33 42 75 218 70-2-207-57-516-138-706-99-230-230-265-497-351-156-50-614-105-756-17-133 83-158 182-282 356-36 51-49 90-72 148z" id="ccpath15" fill="#fdfdfd"/><path class="ccfil1" d="M1784 1883c100 41-5 306-144 242-45-127 62-199 91-256-60-9-231-36-282-17-66 25-81 166-47 232 160 314 867 247 792 3-30-99-58-115-159-149-81-27-162-55-251-55z" id="ccpath17" fill="#fdfdfd"/><path class="ccfil1" d="M527 1848c80 77 261 89 378 95 15-155 28-271 152-262 61 83 29 181-35 244 109-1 172-83 156-202-92-66-371-198-511-217-39 42-135 272-140 342z" id="ccpath19" fill="#fdfdfd"/></g><path class="ccfil2" d="M339 2838c66-6 238 44 252 100-107 13-243 3-252-100zm-59 57c49 35 75 90 159 110 123 31 392-74 84-193-87-34-148-70-245-37-2-144 155-774 222-871 157 25 515 204 718-45 193 11 44 124 166 286 63 83 126 156 265 179 105 18 306 12 409-16 153-40 192-79 196-251l169 38c-21 153-218 409-342 500-171 125-326 200-540 293-95 41-177 116-278 139-193 43-761 114-923 23-44-24-60-92-60-155zm1399-828c7-66 32-103 71-130 27 58-21 114-71 130zm105-184c89 0 170 28 251 55 101 34 129 50 159 149 75 244-632 311-792-3-34-66-19-207 47-232 51-19 222 8 282 17-29 57-136 129-91 256 139 64 244-201 144-242zm-817 12c1-69-2-131 61-158 23 53-15 128-61 158zm-440-47c5-70 101-300 140-342 140 19 419 151 511 217 16 119-47 201-156 202 64-63 96-161 35-244-124-9-137 107-152 262-117-6-298-18-378-95zm-100-80c-37-102-37-261 120-274l-80 223c-21 48-21 37-40 51zm256-310c23-58 36-97 72-148 124-174 149-273 282-356 142-88 600-33 756 17 267 86 398 121 497 351 81 190 136 499 138 706-176 5-166-37-218-70-125-79-512-147-681-148-284-1-117 58-223 25-63-20-59-32-61-54-16-120-47-108-220-194-106-53-217-105-342-129zm1770-49c-19-63 16-59 77-102 35-25 63-51 106-75 161-90 461-105 589 2 52 43 137 127 124 237-27 219-177 339-300 439-125 102-333 207-548 137-18-44-4-323-25-426-19-92-9-102 44-157 156-162 494-280 686-141 81 60 58 83 100 129 52-56-45-244-403-232-243 8-348 198-450 189zM997 840c5-139 133-427 261-527 155-120 317-233 555-98 59 33 56 50 62 132 5 79-2 108-22 172-158 510-290 217-796 338 19-166 163-314 243-391 137-133 236-219 442-191 57 95 63 155-6 266-92 148-115 139-101 240 72-18 94-88 127-158 201-420-91-471-270-394-120 51-334 287-404 429-14 28-29 64-42 95zm792 21c21-125 145-156 145-541 0-166-204-315-471-204-229 94-264 166-386 350-115 174-111 365-210 526-29 46-55 62-87 108-23 34-40 77-63 117-47 77-95 133-133 225-120 3-221 5-233 129-16 170 64 212 64 276-1 69-281 765-203 1180 22 114 97 115 217 129 289 35 664 23 923-81l470-225c119-67 319-194 408-287 63-65 96-120 150-197 74-108 76-106 92-253 98 18 281 61 342 114-7 69-41 36-41 98 39 1 104-48 120-102-41-60-84-50-143-98 47-37 132-54 197-81 140-58 379-234 438-394 47-129 12-344-64-428-80-88-266-133-418-133-181 0-368 130-514 186-56-49-60-105-101-159-47-64-353-224-499-255z" id="ccpath23" fill="#020202"/><path class="ccfil3" d="M2453 1409c102 9 207-181 450-189 358-12 455 176 403 232-42-46-19-69-100-129-192-139-530-21-686 141-53 55-63 65-44 157 21 103 7 382 25 426 215 70 423-35 548-137 123-100 273-220 300-439 13-110-72-194-124-237-128-107-428-92-589-2-43 24-71 50-106 75-61 43-96 39-77 102z" id="ccpath25" fill="#fdf2f2"/><path class="ccfil4" d="M997 840l49-87c13-31 28-67 42-95 70-142 284-378 404-429 179-77 471-26 270 394-33 70-55 140-127 158-14-101 9-92 101-240 69-111 63-171 6-266-206-28-305 58-442 191-80 77-224 225-243 391 506-121 638 172 796-338 20-64 27-93 22-172-6-82-3-99-62-132-238-135-400-22-555 98-128 100-256 388-261 527z" id="ccpath27" fill="#fef3f3"/><path class="ccfil5" d="M427 1768c19-14 19-3 40-51l80-223c-157 13-157 172-120 274z" id="ccpath29" fill="#efefef"/><path class="ccfil6" d="M591 2938c-14-56-186-106-252-100 9 103 145 113 252 100z" id="ccpath31" fill="#faf1f1"/></g></g></symbol><symbol viewBox="0 0 24 24" id="elixir" xmlns="http://www.w3.org/2000/svg"><path d="M12.431 22.383c-3.86 0-6.99-3.64-6.99-8.13 0-3.678 2.774-8.172 4.916-10.91 1.014-1.295 2.931-2.321 2.931-2.321s-.982 5.238 1.683 7.318c2.365 1.847 4.105 4.25 4.105 6.363 0 4.232-2.784 7.68-6.645 7.68z" fill="#9575cd" stroke-width="1.256"/></symbol><symbol viewBox="0 0 323.00001 322.99999" id="elm" xmlns="http://www.w3.org/2000/svg"><g transform="matrix(.8053 0 0 .8053 30.106 31.524)"><path fill="#f0ad00" d="M160.8 153.865l68.028-68.03H92.77z"/><path fill="#7fd13b" d="M160.983 5.098H12.033l68.524 68.525H229.51z"/><path fill="#7fd13b" stroke-width=".974" d="M243.906 88.021l74.136 74.137-74.474 74.475-74.137-74.137z"/><path fill="#60b5cc" d="M318.2 145.045V5.098H178.252z"/><path fill="#5a6378" d="M152.164 162.499L3.4 13.733v297.533z"/><path fill="#f0ad00" d="M252.205 245.27l65.995 65.996v-131.99z"/><path fill="#60b5cc" d="M160.8 171.134L12.034 319.899h297.53z"/></g></symbol><symbol viewBox="0 0 24 24" id="email" xmlns="http://www.w3.org/2000/svg"><path d="M20 8l-8 5-8-5V6l8 5 8-5m0-2H4c-1.11 0-2 .89-2 2v12a2 2 0 0 0 2 2h16a2 2 0 0 0 2-2V6a2 2 0 0 0-2-2z" fill="#42a5f5"/></symbol><symbol viewBox="0 0 30 30" id="erlang" xmlns="http://www.w3.org/2000/svg"><path style="line-height:1.25;-inkscape-font-specification:'Wide Latin'" d="M5.217 4.367c-.048.052-.097.1-.144.153C2.697 7.182 1.51 10.798 1.51 15.366c0 4.418 1.156 7.862 3.46 10.34h19.414c2.553-1.152 4.127-3.43 4.127-3.43l-3.147-2.52-1.454 1.381c-.866.773-.845.931-2.314 1.78-1.496.674-3.04.966-4.634.966-2.516 0-4.423-.909-5.723-2.059-1.286-1.15-1.985-4.511-2.097-6.68l17.458.067-.182-1.472s-.847-7.129-2.542-9.372zm8.76.846c1.565 0 3.22.535 3.96 1.471.742.937.932 1.667.974 3.524H9.12c.111-1.955.436-2.81 1.372-3.697.937-.888 2.03-1.298 3.484-1.298z" font-weight="400" font-size="48" font-family="Wide Latin" letter-spacing="0" word-spacing="0" fill="#f44336" stroke-width=".97"/></symbol><symbol viewBox="0 0 299.99999 300.00001" id="eslint" xmlns="http://www.w3.org/2000/svg"><g transform="translate(-2.88 18.438) scale(1.0344)"><path d="M97.021 99.016l48.432-27.962c1.212-.7 2.706-.7 3.918 0l48.433 27.962a3.92 3.92 0 0 1 1.959 3.393v55.924a3.924 3.924 0 0 1-1.959 3.394l-48.433 27.962c-1.212.7-2.706.7-3.918 0l-48.432-27.962a3.92 3.92 0 0 1-1.959-3.394v-55.924a3.922 3.922 0 0 1 1.959-3.393" fill="#7986cb"/><path d="M273.34 124.49L215.473 23.82c-2.102-3.64-5.985-6.325-10.188-6.325H89.545c-4.204 0-8.088 2.685-10.19 6.325L21.488 124.27c-2.102 3.641-2.102 8.236 0 11.877l57.867 99.847c2.102 3.64 5.986 5.501 10.19 5.501h115.74c4.203 0 8.087-1.805 10.188-5.446l57.867-100.01c2.104-3.639 2.104-7.907.001-11.547m-47.917 48.41c0 1.48-.891 2.849-2.174 3.59l-73.71 42.527a4.194 4.194 0 0 1-4.17 0l-73.767-42.527c-1.282-.741-2.179-2.109-2.179-3.59V87.847c0-1.481.884-2.849 2.167-3.59l73.707-42.527a4.185 4.185 0 0 1 4.168 0l73.772 42.527c1.283.741 2.186 2.109 2.186 3.59z" fill="#3f51b5"/></g></symbol><symbol viewBox="0 0 24 24" id="exe" xmlns="http://www.w3.org/2000/svg"><path d="M19 4a2 2 0 0 1 2 2v12a2 2 0 0 1-2 2H5a2 2 0 0 1-2-2V6a2 2 0 0 1 2-2h14m0 14V8H5v10h14z" fill="#e64a19"/></symbol><symbol viewBox="0 0 24 24" id="favicon" xmlns="http://www.w3.org/2000/svg"><path d="M12 17.27L18.18 21l-1.64-7.03L22 9.24l-7.19-.62L12 2 9.19 8.62 2 9.24l5.45 4.73L5.82 21 12 17.27z" fill="#ffd54f"/></symbol><symbol viewBox="0 0 24 24" id="file" xmlns="http://www.w3.org/2000/svg"><path d="M13 9h5.5L13 3.5V9M6 2h8l6 6v12a2 2 0 0 1-2 2H6a2 2 0 0 1-2-2V4c0-1.11.89-2 2-2m5 2H6v16h12v-9h-7V4z" fill="#42a5f5"/></symbol><symbol viewBox="0 0 400 400" id="firebase" xmlns="http://www.w3.org/2000/svg"><g transform="translate(0 103)"><path d="M72.55 208.77l44.456-292.29 56.209 90.445L195.49-37.57 330.6 209.28z" fill="#ffa712"/><path d="M195.7 276.73l134.9-67.45-46.5-224.83L72.55 208.77z" fill="#fcca3f"/><path d="M173.22 6.932L72.56 208.772l136.35-144.58z" fill="#f6820c"/></g></symbol><symbol viewBox="0 0 24 24" id="flash" xmlns="http://www.w3.org/2000/svg"><defs><linearGradient id="cma"><stop offset="0" stop-color="#d92f3c"/><stop offset="1" stop-color="#791223"/></linearGradient><linearGradient xlink:href="#cma" id="cmb" x1="2.373" y1="12.027" x2="21.86" y2="12.027" gradientUnits="userSpaceOnUse" gradientTransform="translate(-.09 -24.144)"/></defs><rect width="19.487" height="19.487" x="2.283" y="-21.86" transform="rotate(90)" ry="0" fill="url(#cmb)"/><path style="line-height:125%" d="M16.802 5.768l-.013.002a6.43 6.43 0 0 0-1.182.192 5.062 5.062 0 0 0-1.494.718c-.428.323-.817.72-1.17 1.191-.34.48-.682 1.032-1.022 1.66-.12.228-.233.424-.35.636v.002h-.004l-1.34 2.394-.005-.002c-.238.443-.461.847-.665 1.198a4.358 4.358 0 0 1-.716.94 2.79 2.79 0 0 1-.907.594c-.072.027-.161.042-.242.063h-.989v2.414h.989v-.002a6.427 6.427 0 0 0 1.185-.192 5.062 5.062 0 0 0 1.494-.718 5.94 5.94 0 0 0 1.171-1.191c.34-.48.681-1.033 1.021-1.66.12-.228.235-.425.353-.637l.006.002.003-.005.037-.066h2.53v.002h1.124v-2.408h-.33v-.001h-1.98c.22-.407.432-.789.621-1.115.214-.37.452-.682.717-.94a2.79 2.79 0 0 1 .906-.594c.07-.027.16-.041.239-.061h.992V8.18h-.002V5.77h-.977v-.002z" font-weight="400" font-size="40" font-family="sans-serif" letter-spacing="0" word-spacing="0" fill="#fff"/></symbol><symbol class="cnflow-logo" viewBox="0 0 299.99999 300" id="flow" xmlns="http://www.w3.org/2000/svg"><title>Flow logo</title><path d="M38.75 33.427l77.461 77.47H54.436l61.145 61.16H38.437l93.462 93.478v-77.158l.01-.01v-77.47h-.01V66.982h46.691l20.394 20.393H153.57v76.531h22.05l24.474 24.473h-15.806l-.01-.01v.01h-31.665l-.01-.01v.01h-.313l.313.313v77.148h109.149l-39.2-39.2v-15.806l8.465 8.466v-77.37h-15.682l.017-38.191 30.09 30.086V56.362h-64.874l-22.94-22.934H113.71z" fill="#fbc02d" fill-opacity=".976" stroke-width=".955" class="cnflow-logo-mark"/></symbol><symbol clip-rule="evenodd" fill-rule="evenodd" stroke-linejoin="round" stroke-miterlimit="1.414" viewBox="0 0 24 24" id="folder-aurelia" xmlns="http://www.w3.org/2000/svg"><defs><linearGradient id="coa" x1="-388.15%" x2="237.68%" y1="-144.18%" y2="430.41%"><stop stop-color="#C06FBB" offset="0"/><stop stop-color="#6E4D9B" offset="1"/></linearGradient><linearGradient id="cob" x1="72.945%" x2="-97.052%" y1="84.424%" y2="-147.7%"><stop stop-color="#6E4D9B" offset="0"/><stop stop-color="#77327A" offset=".14"/><stop stop-color="#B31777" offset=".29"/><stop stop-color="#CD0F7E" offset=".84"/><stop stop-color="#ED2C89" offset="1"/></linearGradient><linearGradient id="coc" x1="-283.88%" x2="287.54%" y1="-693.6%" y2="101.71%"><stop stop-color="#C06FBB" offset="0"/><stop stop-color="#6E4D9B" offset="1"/></linearGradient><linearGradient id="cod" x1="-821.19%" x2="101.99%" y1="-469.05%" y2="288.24%"><stop stop-color="#C06FBB" offset="0"/><stop stop-color="#6E4D9B" offset="1"/></linearGradient><linearGradient id="coe" x1="-140.36%" x2="419.01%" y1="-230.93%" y2="261.98%"><stop stop-color="#C06FBB" offset="0"/><stop stop-color="#6E4D9B" offset="1"/></linearGradient><linearGradient id="cof" x1="191.08%" x2="20.358%" y1="253.95%" y2="20.403%"><stop stop-color="#6E4D9B" offset="0"/><stop stop-color="#77327A" offset=".14"/><stop stop-color="#B31777" offset=".29"/><stop stop-color="#CD0F7E" offset=".84"/><stop stop-color="#ED2C89" offset="1"/></linearGradient><linearGradient id="cog" x1="-388.09%" x2="237.67%" y1="-173.85%" y2="518.99%"><stop stop-color="#C06FBB" offset="0"/><stop stop-color="#6E4D9B" offset="1"/></linearGradient><linearGradient id="coi" x1="-31.824" x2="19.682" y1="-11.741" y2="35.548" gradientTransform="scale(.95818 1.0436)" gradientUnits="userSpaceOnUse" xlink:href="#coa"/><linearGradient id="coj" x1="12.022" x2="-15.716" y1="13.922" y2="-23.952" gradientTransform="scale(.96226 1.0392)" gradientUnits="userSpaceOnUse" xlink:href="#cob"/><linearGradient id="cok" x1="-23.39" x2="23.931" y1="-57.289" y2="8.573" gradientTransform="scale(1.0429 .95884)" gradientUnits="userSpaceOnUse" xlink:href="#coc"/><linearGradient id="col" x1="-53.331" x2="6.771" y1="-30.517" y2="18.785" gradientTransform="scale(.99898 1.001)" gradientUnits="userSpaceOnUse" xlink:href="#cod"/><linearGradient id="com" x1="-14.029" x2="41.998" y1="-23.111" y2="26.259" gradientTransform="scale(1.0003 .99965)" gradientUnits="userSpaceOnUse" xlink:href="#coe"/><linearGradient id="con" x1="31.177" x2="3.37" y1="41.442" y2="3.402" gradientTransform="scale(.96254 1.0389)" gradientUnits="userSpaceOnUse" xlink:href="#cof"/><linearGradient id="coo" x1="-31.905" x2="19.599" y1="-14.258" y2="42.767" gradientTransform="scale(.95823 1.0436)" gradientUnits="userSpaceOnUse" xlink:href="#cog"/><linearGradient id="cop" x1="4.301" x2="34.534" y1="34.41" y2="4.514" gradientTransform="scale(1.002 .99796)" gradientUnits="userSpaceOnUse" xlink:href="#coh"/><linearGradient id="coh" x1=".112" x2=".901" y1=".897" y2=".116"><stop stop-color="#6E4D9B" offset="0"/><stop stop-color="#77327A" offset=".14"/><stop stop-color="#B31777" offset=".53"/><stop stop-color="#CD0F7E" offset=".79"/><stop stop-color="#ED2C89" offset="1"/></linearGradient></defs><path d="M10 4H4c-1.11 0-2 .89-2 2v12c0 1.097.903 2 2 2h16c1.097 0 2-.903 2-2V8a2 2 0 0 0-2-2h-8l-2-2z" fill="#f06292" fill-rule="nonzero"/><g transform="matrix(.31022 .0619 -.0619 .31022 11.807 7.546)" fill="none"><path d="M8.002 6.127L4.117 8.719.116 2.723 4 .13z" transform="rotate(-11.284 17.839 -78.732)" fill="url(#coi)"/><path d="M9.179 1.887l6.637 9.946-7.906 5.276-6.637-9.946L.115 5.43 8.02.153z" transform="rotate(-11.284 129.49 -99.884)" fill="url(#coj)"/><path d="M7.3 1.88l1.462 2.189-6.018 4.015L.124 4.16l1.315-.877L6.143.144z" transform="rotate(-11.284 167.2 -62.32)" fill="url(#cok)"/><path d="M2.328 1.146L4.016.02l2.619 3.925L2.75 6.537 1.29 4.347l2.197-1.466zm-1.04 3.201L.132 2.612l2.197-1.466 1.158 1.735z" transform="rotate(-11.284 104.37 -149.22)" fill="url(#col)"/><path d="M5.346 9.155l-1.315.877L.03 4.035 6.047.019l2.805 4.204L4.15 7.36l4.703-3.138 1.197 1.793z" transform="rotate(-11.284 81.819 7.645)" fill="url(#com)"/><path d="M14.533 9.934l1.197 1.793-7.907 5.276-1.196-1.793L.052 5.358 7.958.082z" transform="rotate(-11.284 17.141 -7.825)" fill="url(#con)"/><path d="M6.235 7.177L4.038 8.643 2.84 6.849.036 2.646 3.92.053 7.923 6.05z" transform="rotate(-11.284 18.188 -79.174)" fill="url(#coo)"/><path d="M18.955 35.925L17.48 34.45l3.998-3.998 1.475 1.475z" fill="#714896"/><path d="M33.33 21.55l-1.475-1.474 1.867-1.868 1.475 1.475z" fill="#6f4795"/><path d="M7.12 24.09l-1.525-1.525 3.998-3.998 1.525 1.525z" fill="#88519f"/><path d="M21.495 9.714L19.97 8.19l1.868-1.868 1.524 1.525z" fill="#85509e"/><path d="M31.418 23.462l-6.72 6.72-1.475-1.474 6.72-6.721z" fill="#8d166a"/><path d="M18.058 10.101l1.525 1.525-6.721 6.72-1.525-1.524z" fill="#a70d6f"/><path d="M2.375 11.769l1.9 1.9-1.9 1.901-1.901-1.9z" fill="#9e61ad"/><path d="M15.523 36.482l1.9 1.9-1.9 1.901-1.9-1.9z" fill="#8053a3"/><path d="M8.372 38.294L.017 29.876 29.749.08l8.636 8.201z" transform="translate(1.823 1.548)" fill="url(#cop)"/></g></symbol><symbol clip-rule="evenodd" fill-rule="evenodd" stroke-linejoin="round" stroke-miterlimit="1.414" viewBox="0 0 24 24" id="folder-aurelia-open" xmlns="http://www.w3.org/2000/svg"><defs><linearGradient id="cpi" x1="-31.824" x2="19.682" y1="-11.741" y2="35.548" gradientTransform="scale(.95818 1.0436)" gradientUnits="userSpaceOnUse" xlink:href="#cpa"/><linearGradient id="cpa" x1="-3.881" x2="2.377" y1="-1.442" y2="4.304"><stop stop-color="#C06FBB" offset="0"/><stop stop-color="#6E4D9B" offset="1"/></linearGradient><linearGradient id="cpj" x1="12.022" x2="-15.716" y1="13.922" y2="-23.952" gradientTransform="scale(.96226 1.0392)" gradientUnits="userSpaceOnUse" xlink:href="#cpb"/><linearGradient id="cpb" x1=".729" x2="-.971" y1=".844" y2="-1.477"><stop stop-color="#6E4D9B" offset="0"/><stop stop-color="#77327A" offset=".14"/><stop stop-color="#B31777" offset=".29"/><stop stop-color="#CD0F7E" offset=".84"/><stop stop-color="#ED2C89" offset="1"/></linearGradient><linearGradient id="cpk" x1="-23.39" x2="23.931" y1="-57.289" y2="8.573" gradientTransform="scale(1.0429 .95884)" gradientUnits="userSpaceOnUse" xlink:href="#cpc"/><linearGradient id="cpc" x1="-2.839" x2="2.875" y1="-6.936" y2="1.017"><stop stop-color="#C06FBB" offset="0"/><stop stop-color="#6E4D9B" offset="1"/></linearGradient><linearGradient id="cpl" x1="-53.331" x2="6.771" y1="-30.517" y2="18.785" gradientTransform="scale(.99898 1.001)" gradientUnits="userSpaceOnUse" xlink:href="#cpd"/><linearGradient id="cpd" x1="-8.212" x2="1.02" y1="-4.691" y2="2.882"><stop stop-color="#C06FBB" offset="0"/><stop stop-color="#6E4D9B" offset="1"/></linearGradient><linearGradient id="cpm" x1="-14.029" x2="41.998" y1="-23.111" y2="26.259" gradientTransform="scale(1.0003 .99965)" gradientUnits="userSpaceOnUse" xlink:href="#cpe"/><linearGradient id="cpe" x1="-1.404" x2="4.19" y1="-2.309" y2="2.62"><stop stop-color="#C06FBB" offset="0"/><stop stop-color="#6E4D9B" offset="1"/></linearGradient><linearGradient id="cpn" x1="31.177" x2="3.37" y1="41.442" y2="3.402" gradientTransform="scale(.96254 1.0389)" gradientUnits="userSpaceOnUse" xlink:href="#cpf"/><linearGradient id="cpf" x1="1.911" x2=".204" y1="2.539" y2=".204"><stop stop-color="#6E4D9B" offset="0"/><stop stop-color="#77327A" offset=".14"/><stop stop-color="#B31777" offset=".29"/><stop stop-color="#CD0F7E" offset=".84"/><stop stop-color="#ED2C89" offset="1"/></linearGradient><linearGradient id="cpo" x1="-31.905" x2="19.599" y1="-14.258" y2="42.767" gradientTransform="scale(.95823 1.0436)" gradientUnits="userSpaceOnUse" xlink:href="#cpg"/><linearGradient id="cpg" x1="-3.881" x2="2.377" y1="-1.738" y2="5.19"><stop stop-color="#C06FBB" offset="0"/><stop stop-color="#6E4D9B" offset="1"/></linearGradient><linearGradient id="cpp" x1="4.301" x2="34.534" y1="34.41" y2="4.514" gradientTransform="scale(1.002 .99796)" gradientUnits="userSpaceOnUse" xlink:href="#cph"/><linearGradient id="cph" x1=".112" x2=".901" y1=".897" y2=".116"><stop stop-color="#6E4D9B" offset="0"/><stop stop-color="#77327A" offset=".14"/><stop stop-color="#B31777" offset=".53"/><stop stop-color="#CD0F7E" offset=".79"/><stop stop-color="#ED2C89" offset="1"/></linearGradient></defs><path d="M19 20H4a2 2 0 0 1-2-2V6c0-1.11.89-2 2-2h6l2 2h7c1.097 0 2 .903 2 2H4v10l2.14-8h17.07l-2.28 8.5c-.23.87-1.01 1.5-1.93 1.5z" fill="#f06292" fill-rule="nonzero"/><g transform="matrix(.31022 .0619 -.0619 .31022 11.807 7.546)" fill="none"><path d="M8.002 6.127L4.117 8.719.116 2.723 4 .13z" transform="rotate(-11.284 17.839 -78.732)" fill="url(#cpi)"/><path d="M9.179 1.887l6.637 9.946-7.906 5.276-6.637-9.946L.115 5.43 8.02.153z" transform="rotate(-11.284 129.49 -99.884)" fill="url(#cpj)"/><path d="M7.3 1.88l1.462 2.189-6.018 4.015L.124 4.16l1.315-.877L6.143.144z" transform="rotate(-11.284 167.2 -62.32)" fill="url(#cpk)"/><path d="M2.328 1.146L4.016.02l2.619 3.925L2.75 6.537 1.29 4.347l2.197-1.466zm-1.04 3.201L.132 2.612l2.197-1.466 1.158 1.735z" transform="rotate(-11.284 104.37 -149.22)" fill="url(#cpl)"/><path d="M5.346 9.155l-1.315.877L.03 4.035 6.047.019l2.805 4.204L4.15 7.36l4.703-3.138 1.197 1.793z" transform="rotate(-11.284 81.819 7.645)" fill="url(#cpm)"/><path d="M14.533 9.934l1.197 1.793-7.907 5.276-1.196-1.793L.052 5.358 7.958.082z" transform="rotate(-11.284 17.141 -7.825)" fill="url(#cpn)"/><path d="M6.235 7.177L4.038 8.643 2.84 6.849.036 2.646 3.92.053 7.923 6.05z" transform="rotate(-11.284 18.188 -79.174)" fill="url(#cpo)"/><path d="M18.955 35.925L17.48 34.45l3.998-3.998 1.475 1.475z" fill="#714896"/><path d="M33.33 21.55l-1.475-1.474 1.867-1.868 1.475 1.475z" fill="#6f4795"/><path d="M7.12 24.09l-1.525-1.525 3.998-3.998 1.525 1.525z" fill="#88519f"/><path d="M21.495 9.714L19.97 8.19l1.868-1.868 1.524 1.525z" fill="#85509e"/><path d="M31.418 23.462l-6.72 6.72-1.475-1.474 6.72-6.721z" fill="#8d166a"/><path d="M18.058 10.101l1.525 1.525-6.721 6.72-1.525-1.524z" fill="#a70d6f"/><path d="M2.375 11.769l1.9 1.9-1.9 1.901-1.901-1.9z" fill="#9e61ad"/><path d="M15.523 36.482l1.9 1.9-1.9 1.901-1.9-1.9z" fill="#8053a3"/><path d="M8.372 38.294L.017 29.876 29.749.08l8.636 8.201z" transform="translate(1.823 1.548)" fill="url(#cpp)"/></g></symbol><symbol clip-rule="evenodd" fill-rule="evenodd" stroke-linejoin="round" stroke-miterlimit="1.414" viewBox="0 0 24 24" id="folder-components" xmlns="http://www.w3.org/2000/svg"><path d="M10 4H4c-1.11 0-2 .89-2 2v12c0 1.097.903 2 2 2h16c1.097 0 2-.903 2-2V8a2 2 0 0 0-2-2h-8l-2-2z" fill="#cddc39" fill-rule="nonzero"/><path d="M11.185 9.613h5.346v2.9l3.782-3.775 3.775 3.775-3.775 3.782h2.9v5.346h-5.346v-5.346h2.446l-3.782-3.782v2.446h-5.346V9.613m0 6.682h5.346v5.346h-5.346z" fill="#f0f4c3" stroke-width=".668"/></symbol><symbol clip-rule="evenodd" stroke-linejoin="round" stroke-miterlimit="1.414" viewBox="0 0 24 24" id="folder-components-open" xmlns="http://www.w3.org/2000/svg"><path d="M19 20H4a2 2 0 0 1-2-2V6c0-1.11.89-2 2-2h6l2 2h7c1.097 0 2 .903 2 2H4v10l2.14-8h17.07l-2.28 8.5c-.23.87-1.01 1.5-1.93 1.5z" fill="#cddc39"/><path d="M11.185 9.613h5.346v2.9l3.782-3.775 3.775 3.775-3.775 3.782h2.9v5.346h-5.346v-5.346h2.446l-3.782-3.782v2.446h-5.346V9.613m0 6.682h5.346v5.346h-5.346z" fill="#f0f4c3" stroke-width=".668"/></symbol><symbol clip-rule="evenodd" fill-rule="evenodd" stroke-linejoin="round" stroke-miterlimit="1.414" viewBox="0 0 24 24" id="folder-config" xmlns="http://www.w3.org/2000/svg"><path d="M10 4H4c-1.11 0-2 .89-2 2v12c0 1.097.903 2 2 2h16c1.097 0 2-.903 2-2V8a2 2 0 0 0-2-2h-8l-2-2z" fill="#00acc1" fill-rule="nonzero"/><path d="M17.293 17.786a2.308 2.308 0 0 1-2.308-2.308 2.308 2.308 0 0 1 2.308-2.307 2.308 2.308 0 0 1 2.308 2.307 2.308 2.308 0 0 1-2.308 2.308m4.899-1.668c.026-.211.046-.422.046-.64 0-.217-.02-.435-.046-.659l1.391-1.075a.333.333 0 0 0 .08-.422l-1.32-2.28c-.079-.146-.257-.205-.402-.146l-1.641.66a4.779 4.779 0 0 0-1.115-.647l-.244-1.747a.333.333 0 0 0-.33-.277h-2.637a.333.333 0 0 0-.33.277l-.243 1.747a4.78 4.78 0 0 0-1.114.646l-1.642-.659a.324.324 0 0 0-.402.145l-1.319 2.281a.325.325 0 0 0 .08.422l1.39 1.075c-.026.224-.046.442-.046.66s.02.428.046.639l-1.39 1.094a.325.325 0 0 0-.08.422l1.319 2.282c.079.145.257.197.402.145l1.642-.666c.342.264.698.488 1.114.653l.244 1.747a.333.333 0 0 0 .33.277h2.637a.333.333 0 0 0 .33-.277l.243-1.747a4.802 4.802 0 0 0 1.115-.653l1.641.666c.145.052.323 0 .403-.145l1.318-2.282a.333.333 0 0 0-.079-.422z" fill="#80deea" stroke-width=".659"/></symbol><symbol clip-rule="evenodd" stroke-linejoin="round" stroke-miterlimit="1.414" viewBox="0 0 24 24" id="folder-config-open" xmlns="http://www.w3.org/2000/svg"><path d="M19 20H4a2 2 0 0 1-2-2V6c0-1.11.89-2 2-2h6l2 2h7c1.097 0 2 .903 2 2H4v10l2.14-8h17.07l-2.28 8.5c-.23.87-1.01 1.5-1.93 1.5z" fill="#00acc1"/><path d="M17.293 17.786a2.308 2.308 0 0 1-2.308-2.308 2.308 2.308 0 0 1 2.308-2.307 2.308 2.308 0 0 1 2.308 2.307 2.308 2.308 0 0 1-2.308 2.308m4.899-1.668c.026-.211.046-.422.046-.64 0-.217-.02-.435-.046-.659l1.391-1.075a.333.333 0 0 0 .08-.422l-1.32-2.28c-.079-.146-.257-.205-.402-.146l-1.641.66a4.779 4.779 0 0 0-1.115-.647l-.244-1.747a.333.333 0 0 0-.33-.277h-2.637a.333.333 0 0 0-.33.277l-.243 1.747a4.78 4.78 0 0 0-1.114.646l-1.642-.659a.324.324 0 0 0-.402.145l-1.319 2.281a.325.325 0 0 0 .08.422l1.39 1.075c-.026.224-.046.442-.046.66s.02.428.046.639l-1.39 1.094a.325.325 0 0 0-.08.422l1.319 2.282c.079.145.257.197.402.145l1.642-.666c.342.264.698.488 1.114.653l.244 1.747a.333.333 0 0 0 .33.277h2.637a.333.333 0 0 0 .33-.277l.243-1.747a4.802 4.802 0 0 0 1.115-.653l1.641.666c.145.052.323 0 .403-.145l1.318-2.282a.333.333 0 0 0-.079-.422z" fill="#80deea" stroke-width=".659"/></symbol><symbol clip-rule="evenodd" fill-rule="evenodd" stroke-linejoin="round" stroke-miterlimit="1.414" viewBox="0 0 24 24" id="folder-css" xmlns="http://www.w3.org/2000/svg"><path d="M10 4H4c-1.11 0-2 .89-2 2v12c0 1.097.903 2 2 2h16c1.097 0 2-.903 2-2V8a2 2 0 0 0-2-2h-8l-2-2z" fill="#42a5f5" fill-rule="nonzero"/><path d="M12.488 9.415l-.44 2.259h9.188l-.298 1.46h-9.18l-.447 2.251H20.5l-.514 2.576-3.705 1.224-3.211-1.224.223-1.109h-2.258l-.534 2.704 5.307 2.029 6.118-2.029.812-4.076.162-.818 1.041-5.247H12.488z" fill-rule="nonzero" fill="#bbdefb"/></symbol><symbol clip-rule="evenodd" fill-rule="evenodd" stroke-linejoin="round" stroke-miterlimit="1.414" viewBox="0 0 24 24" id="folder-css-open" xmlns="http://www.w3.org/2000/svg"><path d="M19 20H4a2 2 0 0 1-2-2V6c0-1.11.89-2 2-2h6l2 2h7c1.097 0 2 .903 2 2H4v10l2.14-8h17.07l-2.28 8.5c-.23.87-1.01 1.5-1.93 1.5z" fill="#42a5f5" fill-rule="nonzero"/><path d="M12.488 9.415l-.44 2.259h9.188l-.298 1.46h-9.18l-.447 2.251H20.5l-.514 2.576-3.705 1.224-3.211-1.224.223-1.109h-2.258l-.534 2.704 5.307 2.029 6.118-2.029.812-4.076.162-.818 1.041-5.247H12.488z" fill-rule="nonzero" fill="#bbdefb"/></symbol><symbol clip-rule="evenodd" fill-rule="evenodd" stroke-linejoin="round" stroke-miterlimit="1.414" viewBox="0 0 24 24" id="folder-dist" xmlns="http://www.w3.org/2000/svg"><path d="M10 4H4c-1.11 0-2 .89-2 2v12c0 1.097.903 2 2 2h16c1.097 0 2-.903 2-2V8a2 2 0 0 0-2-2h-8l-2-2z" fill="#e57373" fill-rule="nonzero"/><path d="M18.575 11.113h-2.576V9.825h2.576m3.864 1.288h-2.576V9.825l-1.288-1.288h-2.576L14.71 9.825v1.288h-2.577c-.715 0-1.288.573-1.288 1.288v7.085a1.288 1.288 0 0 0 1.288 1.288H22.44a1.288 1.288 0 0 0 1.288-1.288V12.4c0-.715-.58-1.288-1.288-1.288z" fill="#ffcdd2" stroke-width=".644"/></symbol><symbol clip-rule="evenodd" stroke-linejoin="round" stroke-miterlimit="1.414" viewBox="0 0 24 24" id="folder-dist-open" xmlns="http://www.w3.org/2000/svg"><path d="M19 20H4a2 2 0 0 1-2-2V6c0-1.11.89-2 2-2h6l2 2h7c1.097 0 2 .903 2 2H4v10l2.14-8h17.07l-2.28 8.5c-.23.87-1.01 1.5-1.93 1.5z" fill="#e57373"/><path d="M18.575 11.113h-2.576V9.825h2.576m3.864 1.288h-2.576V9.825l-1.288-1.288h-2.576L14.71 9.825v1.288h-2.577c-.715 0-1.288.573-1.288 1.288v7.085a1.288 1.288 0 0 0 1.288 1.288H22.44a1.288 1.288 0 0 0 1.288-1.288V12.4c0-.715-.58-1.288-1.288-1.288z" fill="#ffcdd2" fill-rule="evenodd" stroke-width=".644"/></symbol><symbol id="folder-docker" clip-rule="evenodd" fill-rule="evenodd" stroke-linejoin="round" stroke-miterlimit="1.414" viewBox="0 0 24 24" xmlns="http://www.w3.org/2000/svg"><defs id="cydefs10"><path id="cySVGID_2_" d="M8.7 24c-1.1 0-2.1-.9-2.1-2s.9-2 2.1-2 2.1.9 2.1 2-1 2-2.1 2zm25.8-10.9c-.2-1.6-1.2-2.9-2.5-3.9l-.5-.4-.4.5c-.8.9-1.1 2.5-1 3.7.1.9.4 1.8.9 2.5-.4.2-.9.4-1.3.6-.9.3-1.8.4-2.7.4H1.1l-.1.6c-.2 1.9.1 3.9.9 5.7l.4.7v.1c2.4 4 6.7 5.8 11.4 5.8 9 0 16.4-3.9 19.9-12.3 2.3.1 4.6-.5 5.7-2.7l.3-.5-.5-.3c-1.3-.8-3.1-.9-4.6-.5zm-12.9-1.6h-3.9v3.9h3.9zm0-4.9h-3.9v3.9h3.9zm0-5h-3.9v3.9h3.9zm4.8 9.9h-3.9v3.9h3.9zm-14.5 0H8v3.9h3.9zm4.9 0h-3.9v3.9h3.9zm-9.7 0H3.2v3.9h3.9zm9.7-4.9h-3.9v3.9h3.9zm-4.9 0H8v3.9h3.9z"/></defs><path id="cypath2" d="M10 4H4c-1.11 0-2 .89-2 2v12c0 1.097.903 2 2 2h16c1.097 0 2-.903 2-2V8a2 2 0 0 0-2-2h-8l-2-2z" fill="#039be5" fill-rule="nonzero"/><style id="cystyle2">.cyst0{fill:#fff}.cyst1{clip-path:url(#cySVGID_4_)}</style><g id="cyg34" transform="translate(8.319 9.626) scale(.39491)" fill="#b3e5fc"><g id="cyg32"><g id="cyg30"><title id="cytitle4">Group 3</title><g id="cyg28"><g id="cyg26"><g id="cyg9"><path id="cySVGID_1_" class="cyst0" d="M8.7 24c-1.1 0-2.1-.9-2.1-2s.9-2 2.1-2 2.1.9 2.1 2-1 2-2.1 2zm25.8-10.9c-.2-1.6-1.2-2.9-2.5-3.9l-.5-.4-.4.5c-.8.9-1.1 2.5-1 3.7.1.9.4 1.8.9 2.5-.4.2-.9.4-1.3.6-.9.3-1.8.4-2.7.4H1.1l-.1.6c-.2 1.9.1 3.9.9 5.7l.4.7v.1c2.4 4 6.7 5.8 11.4 5.8 9 0 16.4-3.9 19.9-12.3 2.3.1 4.6-.5 5.7-2.7l.3-.5-.5-.3c-1.3-.8-3.1-.9-4.6-.5zm-12.9-1.6h-3.9v3.9h3.9zm0-4.9h-3.9v3.9h3.9zm0-5h-3.9v3.9h3.9zm4.8 9.9h-3.9v3.9h3.9zm-14.5 0H8v3.9h3.9zm4.9 0h-3.9v3.9h3.9zm-9.7 0H3.2v3.9h3.9zm9.7-4.9h-3.9v3.9h3.9zm-4.9 0H8v3.9h3.9z"/></g><g id="cyg24"><clipPath id="cySVGID_4_"><use id="cyuse14" width="100%" height="100%" xlink:href="#cySVGID_2_"/></clipPath><g id="cyg22" class="cyst1" clip-path="url(#cySVGID_4_)"><g id="cyg20"><g id="cyg18"><path id="cySVGID_3_" class="cyst0" d="M-48.8-21H1226v151.4H-48.8z"/></g></g></g></g></g></g></g></g></g></symbol><symbol clip-rule="evenodd" stroke-linejoin="round" stroke-miterlimit="1.414" viewBox="0 0 24 24" id="folder-docker-open" xmlns="http://www.w3.org/2000/svg"><defs><clipPath id="cza"><use width="100%" height="100%" xmlns:xlink="http://www.w3.org/1999/xlink" xlink:href="#SVGID_2_"/></clipPath></defs><path d="M19 20H4a2 2 0 0 1-2-2V6c0-1.11.89-2 2-2h6l2 2h7c1.097 0 2 .903 2 2H4v10l2.14-8h17.07l-2.28 8.5c-.23.87-1.01 1.5-1.93 1.5z" fill="#039be5"/><g transform="matrix(.3949 0 0 .39489 8.319 9.626)" fill="#b3e5fc"><title>Group 3</title><path class="czst0" d="M8.7 24c-1.1 0-2.1-.9-2.1-2s.9-2 2.1-2 2.1.9 2.1 2-1 2-2.1 2zm25.8-10.9c-.2-1.6-1.2-2.9-2.5-3.9l-.5-.4-.4.5c-.8.9-1.1 2.5-1 3.7.1.9.4 1.8.9 2.5-.4.2-.9.4-1.3.6-.9.3-1.8.4-2.7.4H1.1l-.1.6c-.2 1.9.1 3.9.9 5.7l.4.7v.1c2.4 4 6.7 5.8 11.4 5.8 9 0 16.4-3.9 19.9-12.3 2.3.1 4.6-.5 5.7-2.7l.3-.5-.5-.3c-1.3-.8-3.1-.9-4.6-.5zm-12.9-1.6h-3.9v3.9h3.9zm0-4.9h-3.9v3.9h3.9zm0-5h-3.9v3.9h3.9zm4.8 9.9h-3.9v3.9h3.9zm-14.5 0H8v3.9h3.9zm4.9 0h-3.9v3.9h3.9zm-9.7 0H3.2v3.9h3.9zm9.7-4.9h-3.9v3.9h3.9zm-4.9 0H8v3.9h3.9z"/><g class="czst1" clip-path="url(#cza)"><path class="czst0" d="M-48.8-21H1226v151.4H-48.8z"/></g></g></symbol><symbol clip-rule="evenodd" fill-rule="evenodd" stroke-linejoin="round" stroke-miterlimit="1.414" viewBox="0 0 24 24" id="folder-docs" xmlns="http://www.w3.org/2000/svg"><path d="M10 4H4c-1.11 0-2 .89-2 2v12c0 1.097.903 2 2 2h16c1.097 0 2-.903 2-2V8a2 2 0 0 0-2-2h-8l-2-2z" fill="#0277bd" fill-rule="nonzero"/><path d="M18.575 12.859h3.713l-3.713-3.713v3.713M13.85 8.134h5.4l4.05 4.05v8.1c0 .74-.61 1.35-1.35 1.35h-8.1a1.35 1.35 0 0 1-1.35-1.35v-10.8c0-.75.6-1.35 1.35-1.35m6.075 10.8v-1.35H13.85v1.35h6.075m2.025-2.7v-1.35h-8.1v1.35h8.1z" fill-rule="nonzero" fill="#b3e5fc"/></symbol><symbol clip-rule="evenodd" fill-rule="evenodd" stroke-linejoin="round" stroke-miterlimit="1.414" viewBox="0 0 24 24" id="folder-docs-open" xmlns="http://www.w3.org/2000/svg"><path d="M19 20H4a2 2 0 0 1-2-2V6c0-1.11.89-2 2-2h6l2 2h7c1.097 0 2 .903 2 2H4v10l2.14-8h17.07l-2.28 8.5c-.23.87-1.01 1.5-1.93 1.5z" fill="#0277bd" fill-rule="nonzero"/><path d="M18.575 12.859h3.713l-3.713-3.713v3.713M13.85 8.134h5.4l4.05 4.05v8.1c0 .74-.61 1.35-1.35 1.35h-8.1a1.35 1.35 0 0 1-1.35-1.35v-10.8c0-.75.6-1.35 1.35-1.35m6.075 10.8v-1.35H13.85v1.35h6.075m2.025-2.7v-1.35h-8.1v1.35h8.1z" fill-rule="nonzero" fill="#b3e5fc"/></symbol><symbol clip-rule="evenodd" fill-rule="evenodd" stroke-linejoin="round" stroke-miterlimit="1.414" viewBox="0 0 24 24" id="folder-expo" xmlns="http://www.w3.org/2000/svg"><path d="M10 4H4c-1.11 0-2 .89-2 2v12c0 1.097.903 2 2 2h16c1.097 0 2-.903 2-2V8a2 2 0 0 0-2-2h-8l-2-2z" fill="#01579b" fill-rule="nonzero"/><style>.dcst0{fill:#1173b6}.st1{fill:#585d67}</style><path class="dcst0" d="M18.575 9.82c-.489-.745-.605-.844-1.6-.844h-.024c-.996 0-1.106.099-1.601.844-.46.699-5.024 9.058-5.024 9.291 0 .338.087.658.402 1.112.32.46.873.716 1.275.309.273-.274 3.201-5.321 4.616-7.23a.425.425 0 0 1 .693 0c1.414 1.909 4.343 6.956 4.616 7.23.402.407.955.15 1.275-.309.314-.454.402-.774.402-1.112-.006-.233-4.57-8.598-5.03-9.291z" fill="#1173b6" stroke-width=".058"/></symbol><symbol clip-rule="evenodd" stroke-linejoin="round" stroke-miterlimit="1.414" viewBox="0 0 24 24" id="folder-expo-open" xmlns="http://www.w3.org/2000/svg"><path d="M19 20H4a2 2 0 0 1-2-2V6c0-1.11.89-2 2-2h6l2 2h7c1.097 0 2 .903 2 2H4v10l2.14-8h17.07l-2.28 8.5c-.23.87-1.01 1.5-1.93 1.5z" fill="#01579b"/><path class="ddst0" d="M18.575 9.82c-.489-.745-.605-.844-1.6-.844h-.024c-.996 0-1.106.099-1.601.844-.46.699-5.024 9.058-5.024 9.291 0 .338.087.658.402 1.112.32.46.873.716 1.275.309.273-.274 3.201-5.321 4.616-7.23a.425.425 0 0 1 .693 0c1.414 1.909 4.343 6.956 4.616 7.23.402.407.955.15 1.275-.309.314-.454.402-.774.402-1.112-.006-.233-4.57-8.598-5.03-9.291z" fill="#1173b6" stroke-width=".058" fill-rule="evenodd"/></symbol><symbol viewBox="0 0 24 24" fill-rule="evenodd" clip-rule="evenodd" stroke-linejoin="round" stroke-miterlimit="1.414" id="folder-font" xmlns="http://www.w3.org/2000/svg"><path d="M10 4H4c-1.11 0-2 .89-2 2v12c0 1.097.903 2 2 2h16c1.097 0 2-.903 2-2V8a2 2 0 0 0-2-2h-8l-2-2z" fill="#ef9a9a" fill-rule="nonzero"/><path d="M14.62 17.403l2.38-6.33 2.37 6.33m-3.37-9l-5.5 14h2.25l1.12-3h6.25l1.13 3h2.25l-5.5-14h-2z" fill="#f44336" fill-rule="nonzero"/></symbol><symbol viewBox="0 0 24 24" fill-rule="evenodd" clip-rule="evenodd" stroke-linejoin="round" stroke-miterlimit="1.414" id="folder-font-open" xmlns="http://www.w3.org/2000/svg"><path d="M19 20H4a2 2 0 0 1-2-2V6c0-1.11.89-2 2-2h6l2 2h7c1.097 0 2 .903 2 2H4v10l2.14-8h17.07l-2.28 8.5c-.23.87-1.01 1.5-1.93 1.5z" fill="#ef9a9a" fill-rule="nonzero"/><path d="M14.62 17.403l2.38-6.33 2.37 6.33m-3.37-9l-5.5 14h2.25l1.12-3h6.25l1.13 3h2.25l-5.5-14h-2z" fill="#f44336" fill-rule="nonzero"/></symbol><symbol viewBox="0 0 24 24" fill-rule="evenodd" clip-rule="evenodd" stroke-linejoin="round" stroke-miterlimit="1.414" id="folder-git" xmlns="http://www.w3.org/2000/svg"><path d="M10 4H4c-1.11 0-2 .89-2 2v12c0 1.097.903 2 2 2h16c1.097 0 2-.903 2-2V8a2 2 0 0 0-2-2h-8l-2-2z" fill="#ff8a65" fill-rule="nonzero"/><path d="M10.43 14.14l4.044-4.052 1.183 1.19a1.387 1.387 0 0 0 .65 1.56v3.877c-.42.238-.699.693-.699 1.21 0 .768.632 1.4 1.4 1.4.767 0 1.4-.632 1.4-1.4 0-.517-.28-.972-.7-1.21v-3.4l1.448 1.462c-.05.105-.05.224-.05.35 0 .767.633 1.399 1.4 1.399.768 0 1.4-.632 1.4-1.4 0-.767-.632-1.4-1.4-1.4-.126 0-.245 0-.35.05l-1.798-1.799a1.385 1.385 0 0 0-.805-1.637c-.3-.112-.615-.14-.895-.063l-1.19-1.183.553-.545a1.381 1.381 0 0 1 1.973 0l5.591 5.59a1.381 1.381 0 0 1 0 1.974l-5.59 5.591a1.381 1.381 0 0 1-1.974 0l-5.591-5.59a1.381 1.381 0 0 1 0-1.974z" fill="#e64a19" fill-rule="nonzero"/></symbol><symbol viewBox="0 0 24 24" fill-rule="evenodd" clip-rule="evenodd" stroke-linejoin="round" stroke-miterlimit="1.414" id="folder-git-open" xmlns="http://www.w3.org/2000/svg"><path d="M19 20H4a2 2 0 0 1-2-2V6c0-1.11.89-2 2-2h6l2 2h7c1.097 0 2 .903 2 2H4v10l2.14-8h17.07l-2.28 8.5c-.23.87-1.01 1.5-1.93 1.5z" fill="#ff8a65" fill-rule="nonzero"/><path d="M10.43 14.14l4.044-4.052 1.183 1.19a1.387 1.387 0 0 0 .65 1.56v3.877c-.42.238-.699.693-.699 1.21 0 .768.632 1.4 1.4 1.4.767 0 1.4-.632 1.4-1.4 0-.517-.28-.972-.7-1.21v-3.4l1.448 1.462c-.05.105-.05.224-.05.35 0 .767.633 1.399 1.4 1.399.768 0 1.4-.632 1.4-1.4 0-.767-.632-1.4-1.4-1.4-.126 0-.245 0-.35.05l-1.798-1.799a1.385 1.385 0 0 0-.805-1.637c-.3-.112-.615-.14-.895-.063l-1.19-1.183.553-.545a1.381 1.381 0 0 1 1.973 0l5.591 5.59a1.381 1.381 0 0 1 0 1.974l-5.59 5.591a1.381 1.381 0 0 1-1.974 0l-5.591-5.59a1.381 1.381 0 0 1 0-1.974z" fill="#e64a19" fill-rule="nonzero"/></symbol><symbol clip-rule="evenodd" fill-rule="evenodd" stroke-linejoin="round" stroke-miterlimit="1.414" viewBox="0 0 24 24" id="folder-global" xmlns="http://www.w3.org/2000/svg"><path d="M10 4H4c-1.11 0-2 .89-2 2v12c0 1.097.903 2 2 2h16c1.097 0 2-.903 2-2V8a2 2 0 0 0-2-2h-8l-2-2z" fill="#5c6bc0" fill-rule="nonzero"/><path d="M21.132 18.585a1.22 1.22 0 0 0-1.156-.846h-.609v-1.825a.608.608 0 0 0-.608-.609h-3.65v-1.217h1.216a.608.608 0 0 0 .609-.608v-1.217h1.217a1.217 1.217 0 0 0 1.216-1.217v-.25a4.858 4.858 0 0 1 1.765 7.79m-4.198 1.545a4.86 4.86 0 0 1-4.26-4.826c0-.377.049-.742.128-1.089l2.915 2.915v.608a1.217 1.217 0 0 0 1.217 1.217m.608-9.735a6.085 6.085 0 0 0-6.085 6.084 6.085 6.085 0 0 0 6.085 6.085 6.085 6.085 0 0 0 6.085-6.085 6.085 6.085 0 0 0-6.085-6.084z" fill="#c5cae9" stroke-width=".608"/></symbol><symbol clip-rule="evenodd" stroke-linejoin="round" stroke-miterlimit="1.414" viewBox="0 0 24 24" id="folder-global-open" xmlns="http://www.w3.org/2000/svg"><path d="M19 20H4a2 2 0 0 1-2-2V6c0-1.11.89-2 2-2h6l2 2h7c1.097 0 2 .903 2 2H4v10l2.14-8h17.07l-2.28 8.5c-.23.87-1.01 1.5-1.93 1.5z" fill="#5c6bc0"/><path d="M21.133 18.585a1.22 1.22 0 0 0-1.156-.846h-.609v-1.825a.608.608 0 0 0-.608-.609h-3.65v-1.217h1.216a.608.608 0 0 0 .609-.608v-1.217h1.217a1.217 1.217 0 0 0 1.216-1.217v-.25a4.858 4.858 0 0 1 1.765 7.79m-4.198 1.545a4.86 4.86 0 0 1-4.26-4.826c0-.377.049-.742.128-1.089l2.915 2.915v.608a1.217 1.217 0 0 0 1.216 1.217m.609-9.735a6.085 6.085 0 0 0-6.085 6.084 6.085 6.085 0 0 0 6.085 6.085 6.085 6.085 0 0 0 6.085-6.085 6.085 6.085 0 0 0-6.085-6.084z" fill="#c5cae9" stroke-width=".608"/></symbol><symbol clip-rule="evenodd" fill-rule="evenodd" stroke-linejoin="round" stroke-miterlimit="1.414" viewBox="0 0 24 24" id="folder-i18n" xmlns="http://www.w3.org/2000/svg"><path d="M10 4H4c-1.11 0-2 .89-2 2v12c0 1.097.903 2 2 2h16c1.097 0 2-.903 2-2V8a2 2 0 0 0-2-2h-8l-2-2z" fill="#5c6bc0" fill-rule="nonzero"/><path d="M17.293 17.786l-1.53-1.512.018-.018a10.555 10.555 0 0 0 2.235-3.934h1.765v-1.205h-4.217V9.912h-1.205v1.205h-4.217v1.205h6.73a9.5 9.5 0 0 1-1.91 3.223 9.424 9.424 0 0 1-1.392-2.018h-1.205c.44.982 1.042 1.91 1.795 2.747l-3.067 3.024.856.856 3.012-3.013 1.874 1.874.458-1.229m3.392-3.054H19.48l-2.711 7.23h1.205l.674-1.808h2.862l.68 1.807h1.206l-2.711-7.23m-1.579 4.218l.976-2.609.976 2.609z" fill="#c5cae9" stroke-width=".602"/></symbol><symbol clip-rule="evenodd" stroke-linejoin="round" stroke-miterlimit="1.414" viewBox="0 0 24 24" id="folder-i18n-open" xmlns="http://www.w3.org/2000/svg"><path d="M19 20H4a2 2 0 0 1-2-2V6c0-1.11.89-2 2-2h6l2 2h7c1.097 0 2 .903 2 2H4v10l2.14-8h17.07l-2.28 8.5c-.23.87-1.01 1.5-1.93 1.5z" fill="#5c6bc0"/><path d="M17.293 17.786l-1.53-1.512.018-.018a10.555 10.555 0 0 0 2.235-3.934h1.765v-1.205h-4.217V9.912h-1.205v1.205h-4.217v1.205h6.73a9.5 9.5 0 0 1-1.91 3.223 9.424 9.424 0 0 1-1.392-2.018h-1.205c.44.982 1.042 1.91 1.795 2.747l-3.067 3.024.856.856 3.012-3.013 1.874 1.874.458-1.229m3.392-3.054H19.48l-2.711 7.23h1.205l.674-1.808h2.862l.68 1.807h1.206l-2.711-7.23m-1.579 4.218l.976-2.609.976 2.609z" fill="#c5cae9" stroke-width=".602"/></symbol><symbol clip-rule="evenodd" fill-rule="evenodd" stroke-linejoin="round" stroke-miterlimit="1.414" viewBox="0 0 24 24" id="folder-images" xmlns="http://www.w3.org/2000/svg"><path d="M10 4H4c-1.11 0-2 .89-2 2v12c0 1.097.903 2 2 2h16c1.097 0 2-.903 2-2V8a2 2 0 0 0-2-2h-8l-2-2z" fill="#009688" fill-rule="nonzero"/><path d="M18.575 12.859h3.713l-3.713-3.713v3.713M13.85 8.134h5.4l4.05 4.05v8.1c0 .74-.61 1.35-1.35 1.35h-8.1a1.35 1.35 0 0 1-1.35-1.35v-10.8c0-.75.6-1.35 1.35-1.35m0 12.15h8.1v-5.4l-2.7 2.7-1.35-1.35-4.05 4.05m1.35-7.425c-.74 0-1.35.61-1.35 1.35s.61 1.35 1.35 1.35 1.35-.61 1.35-1.35-.61-1.35-1.35-1.35z" fill-rule="nonzero" fill="#b2dfdb"/></symbol><symbol clip-rule="evenodd" fill-rule="evenodd" stroke-linejoin="round" stroke-miterlimit="1.414" viewBox="0 0 24 24" id="folder-images-open" xmlns="http://www.w3.org/2000/svg"><path d="M19 20H4a2 2 0 0 1-2-2V6c0-1.11.89-2 2-2h6l2 2h7c1.097 0 2 .903 2 2H4v10l2.14-8h17.07l-2.28 8.5c-.23.87-1.01 1.5-1.93 1.5z" fill="#009688" fill-rule="nonzero"/><path d="M18.575 12.859h3.713l-3.713-3.713v3.713M13.85 8.134h5.4l4.05 4.05v8.1c0 .74-.61 1.35-1.35 1.35h-8.1a1.35 1.35 0 0 1-1.35-1.35v-10.8c0-.75.6-1.35 1.35-1.35m0 12.15h8.1v-5.4l-2.7 2.7-1.35-1.35-4.05 4.05m1.35-7.425c-.74 0-1.35.61-1.35 1.35s.61 1.35 1.35 1.35 1.35-.61 1.35-1.35-.61-1.35-1.35-1.35z" fill-rule="nonzero" fill="#b2dfdb"/></symbol><symbol clip-rule="evenodd" fill-rule="evenodd" stroke-linejoin="round" stroke-miterlimit="1.414" viewBox="0 0 24 24" id="folder-include" xmlns="http://www.w3.org/2000/svg"><path d="M10 4H4c-1.11 0-2 .89-2 2v12c0 1.097.903 2 2 2h16c1.097 0 2-.903 2-2V8a2 2 0 0 0-2-2h-8l-2-2z" fill="#039be5" fill-rule="nonzero"/><path d="M20.788 15.981h-2.434v2.434h-1.217V15.98h-2.434v-1.217h2.434V12.33h1.217v2.434h2.434m-3.042-5.476a6.085 6.085 0 0 0-6.085 6.084 6.085 6.085 0 0 0 6.085 6.085 6.085 6.085 0 0 0 6.084-6.085 6.085 6.085 0 0 0-6.084-6.084z" fill="#b3e5fc" stroke-width=".608"/></symbol><symbol clip-rule="evenodd" stroke-linejoin="round" stroke-miterlimit="1.414" viewBox="0 0 24 24" id="folder-include-open" xmlns="http://www.w3.org/2000/svg"><path d="M19 20H4a2 2 0 0 1-2-2V6c0-1.11.89-2 2-2h6l2 2h7c1.097 0 2 .903 2 2H4v10l2.14-8h17.07l-2.28 8.5c-.23.87-1.01 1.5-1.93 1.5z" fill="#039be5"/><path d="M20.788 15.981h-2.434v2.434h-1.217V15.98h-2.434v-1.217h2.434V12.33h1.217v2.434h2.434m-3.042-5.476a6.085 6.085 0 0 0-6.085 6.084 6.085 6.085 0 0 0 6.085 6.085 6.085 6.085 0 0 0 6.084-6.085 6.085 6.085 0 0 0-6.084-6.084z" fill="#b3e5fc" stroke-width=".608"/></symbol><symbol clip-rule="evenodd" fill-rule="evenodd" stroke-linejoin="round" stroke-miterlimit="1.414" viewBox="0 0 24 24" id="folder-javascript" xmlns="http://www.w3.org/2000/svg"><path d="M10 4H4c-1.11 0-2 .89-2 2v12c0 1.097.903 2 2 2h16c1.097 0 2-.903 2-2V8a2 2 0 0 0-2-2h-8l-2-2z" fill="#ffca28" fill-rule="nonzero"/><path d="M17.935 18.374a2.18 2.18 0 0 0 1.972 1.213c.829 0 1.354-.415 1.354-.987 0-.682-.542-.927-1.452-1.324l-.502-.216c-1.435-.613-2.404-1.378-2.404-3.005 0-1.5 1.167-2.638 2.917-2.638a2.957 2.957 0 0 1 2.842 1.599l-1.552.999a1.362 1.362 0 0 0-1.29-.858.873.873 0 0 0-.957.858c0 .583.374.84 1.226 1.213l.502.216c1.697.733 2.654 1.47 2.654 3.139 0 1.798-1.411 2.783-3.308 2.783a3.839 3.839 0 0 1-3.618-2.046zm-7.048.175c.315.583.583 1.027 1.283 1.027s1.066-.256 1.066-1.255v-6.774h1.998v6.804c0 2.064-1.214 3.01-2.982 3.01a3.104 3.104 0 0 1-2.993-1.826z" fill-rule="nonzero" fill="#ffecb3"/></symbol><symbol clip-rule="evenodd" stroke-linejoin="round" stroke-miterlimit="1.414" viewBox="0 0 24 24" id="folder-javascript-open" xmlns="http://www.w3.org/2000/svg"><path d="M19 20H4a2 2 0 0 1-2-2V6c0-1.11.89-2 2-2h6l2 2h7c1.097 0 2 .903 2 2H4v10l2.14-8h17.07l-2.28 8.5c-.23.87-1.01 1.5-1.93 1.5z" fill="#ffca28"/><path d="M17.935 18.374a2.18 2.18 0 0 0 1.972 1.213c.829 0 1.354-.415 1.354-.987 0-.682-.542-.927-1.452-1.324l-.502-.216c-1.435-.613-2.404-1.378-2.404-3.005 0-1.5 1.167-2.638 2.917-2.638a2.957 2.957 0 0 1 2.842 1.599l-1.552.999a1.362 1.362 0 0 0-1.29-.858.873.873 0 0 0-.957.858c0 .583.374.84 1.226 1.213l.502.216c1.697.733 2.654 1.47 2.654 3.139 0 1.798-1.412 2.783-3.308 2.783a3.839 3.839 0 0 1-3.618-2.046zm-7.048.175c.315.583.583 1.027 1.283 1.027s1.066-.256 1.066-1.255v-6.774h1.998v6.804c0 2.064-1.214 3.01-2.982 3.01a3.104 3.104 0 0 1-2.993-1.826z" fill="#ffecb3"/></symbol><symbol clip-rule="evenodd" fill-rule="evenodd" stroke-linejoin="round" stroke-miterlimit="1.414" viewBox="0 0 24 24" id="folder-lib" xmlns="http://www.w3.org/2000/svg"><path d="M10 4H4c-1.11 0-2 .89-2 2v12c0 1.097.903 2 2 2h16c1.097 0 2-.903 2-2V8a2 2 0 0 0-2-2h-8l-2-2z" fill="#c0ca33" fill-rule="nonzero"/><path d="M17.39 12.544a2.05 2.05 0 0 0 2.05-2.05 2.05 2.05 0 0 0-2.05-2.052 2.05 2.05 0 0 0-2.05 2.051 2.05 2.05 0 0 0 2.05 2.051m0 2.42a8.992 8.992 0 0 0-6.152-2.42v7.52c2.392 0 4.539.923 6.152 2.42a8.992 8.992 0 0 1 6.152-2.42v-7.52c-2.392 0-4.539.923-6.152 2.42z" fill="#f0f4c3" stroke-width=".684"/></symbol><symbol clip-rule="evenodd" stroke-linejoin="round" stroke-miterlimit="1.414" viewBox="0 0 24 24" id="folder-lib-open" xmlns="http://www.w3.org/2000/svg"><path d="M19 20H4a2 2 0 0 1-2-2V6c0-1.11.89-2 2-2h6l2 2h7c1.097 0 2 .903 2 2H4v10l2.14-8h17.07l-2.28 8.5c-.23.87-1.01 1.5-1.93 1.5z" fill="#c0ca33"/><path d="M17.391 12.543a2.05 2.05 0 0 0 2.05-2.05 2.05 2.05 0 0 0-2.05-2.052 2.05 2.05 0 0 0-2.05 2.051 2.05 2.05 0 0 0 2.05 2.051m0 2.42a8.992 8.992 0 0 0-6.152-2.42v7.52c2.392 0 4.539.923 6.152 2.42a8.992 8.992 0 0 1 6.152-2.42v-7.52c-2.392 0-4.539.923-6.152 2.42z" fill="#f0f4c3" stroke-width=".684"/></symbol><symbol clip-rule="evenodd" fill-rule="evenodd" stroke-linejoin="round" stroke-miterlimit="1.414" viewBox="0 0 24 24" id="folder-ngrx-actions" xmlns="http://www.w3.org/2000/svg"><path d="M10 4H4c-1.11 0-2 .89-2 2v12c0 1.097.903 2 2 2h16c1.097 0 2-.903 2-2V8a2 2 0 0 0-2-2h-8l-2-2z" fill="#ab47bc" fill-rule="nonzero"/><path d="M17.655 8.39l-6.152 2.193.933 8.142 5.219 2.888 5.219-2.888.932-8.142zm-1.278 2.067c.234-.004.487.07.768.223.124.066.498.16.83.21 1.183.17 2.586 1.073 3.03 1.95.306.602.243.927-.225 1.169-.404.209-1.23.108-2.43-.297l-1.012-.342-.36.137c-.522.2-1.044.694-1.258 1.19-.154.359-.177.527-.149 1.116.028.59.071.761.28 1.132.239.422.786.96.88.866.026-.026-.03-.197-.124-.38-.093-.183-.148-.368-.122-.41.026-.042.273.114.548.347.611.518 1.326.848 1.981.917.538.056.661-.044.258-.211a1.238 1.238 0 0 1-.374-.25c-.157-.173-.166-.168.504-.318.417-.094 1.24-.531 1.29-.685.016-.05-.118-.07-.338-.053-.2.016-.363-.004-.363-.046 0-.04.164-.243.363-.451.748-.781 1.004-1.365 1.083-2.474l.055-.767.176.365c.194.401.23.98.091 1.478-.115.416-.038.462.173.104.261-.443.345-.373.299.251-.05.678-.283 1.187-.808 1.762-.429.468-.377.552.141.233.5-.308.567-.26.31.224-.487.914-1.516 1.69-2.585 1.948-.647.158-1.106.187-1.7.11-1.55-.204-3.018-1.249-3.718-2.648a8.736 8.736 0 0 0-.572-.989c-.275-.373-.298-.54-.113-.823.093-.141.114-.286.076-.502-.176-.999-.17-1.03.23-1.437.35-.353.371-.4.371-.813 0-.358.036-.475.198-.637.109-.108.282-.199.384-.2.296-.003.807-.277 1.11-.595.252-.265.52-.4.822-.404z" fill="#e1bee7" stroke-width=".696"/></symbol><symbol clip-rule="evenodd" stroke-linejoin="round" stroke-miterlimit="1.414" viewBox="0 0 24 24" id="folder-ngrx-actions-open" xmlns="http://www.w3.org/2000/svg"><path d="M19 20H4a2 2 0 0 1-2-2V6c0-1.11.89-2 2-2h6l2 2h7c1.097 0 2 .903 2 2H4v10l2.14-8h17.07l-2.28 8.5c-.23.87-1.01 1.5-1.93 1.5z" fill="#ab47bc"/><path d="M17.655 8.39l-6.152 2.192.933 8.143 5.219 2.888 5.219-2.888.932-8.143zm-1.278 2.067c.234-.004.487.07.768.222.124.067.498.162.83.21 1.183.171 2.586 1.074 3.03 1.95.306.603.243.928-.225 1.17-.404.208-1.23.107-2.43-.298l-1.012-.341-.36.137c-.522.2-1.044.694-1.258 1.19-.154.359-.177.527-.149 1.116.028.59.071.761.28 1.132.239.422.786.96.88.866.026-.026-.03-.197-.124-.38-.093-.183-.148-.368-.122-.41.026-.042.273.114.548.347.611.517 1.326.848 1.981.917.538.056.661-.044.258-.211a1.238 1.238 0 0 1-.374-.25c-.157-.173-.166-.168.504-.318.417-.094 1.24-.531 1.29-.685.016-.05-.118-.07-.338-.053-.2.016-.363-.005-.363-.046 0-.04.164-.243.363-.452.748-.78 1.004-1.364 1.083-2.474l.055-.766.176.364c.194.402.23.981.091 1.479-.115.416-.038.462.173.103.261-.442.345-.372.299.252-.05.678-.283 1.186-.808 1.761-.429.47-.377.553.141.234.5-.308.567-.26.31.224-.487.914-1.516 1.689-2.585 1.948-.647.158-1.106.187-1.7.109-1.55-.203-3.018-1.248-3.718-2.647a8.736 8.736 0 0 0-.572-.989c-.275-.373-.298-.54-.113-.823.093-.142.114-.286.076-.502-.176-.999-.17-1.03.23-1.437.35-.353.371-.4.371-.813 0-.358.036-.475.198-.637.109-.109.282-.2.384-.2.296-.003.807-.277 1.11-.595.252-.265.52-.4.822-.404z" fill="#e1bee7" stroke-width=".696"/></symbol><symbol clip-rule="evenodd" fill-rule="evenodd" stroke-linejoin="round" stroke-miterlimit="1.414" viewBox="0 0 24 24" id="folder-ngrx-effects" xmlns="http://www.w3.org/2000/svg"><path d="M10 4H4c-1.11 0-2 .89-2 2v12c0 1.097.903 2 2 2h16c1.097 0 2-.903 2-2V8a2 2 0 0 0-2-2h-8l-2-2z" fill="#00bcd4" fill-rule="nonzero"/><path d="M17.655 8.39l-6.152 2.193.933 8.142 5.219 2.888 5.219-2.888.932-8.142zm-1.278 2.067c.234-.004.487.07.768.223.124.066.498.16.83.21 1.183.17 2.586 1.073 3.03 1.95.306.602.243.927-.225 1.169-.404.209-1.23.108-2.43-.297l-1.012-.342-.36.137c-.522.2-1.044.694-1.258 1.19-.154.359-.177.527-.149 1.116.028.59.071.761.28 1.132.239.422.786.96.88.866.026-.026-.03-.197-.124-.38-.093-.183-.148-.368-.122-.41.026-.042.273.114.548.347.611.518 1.326.848 1.981.917.538.056.661-.044.258-.211a1.238 1.238 0 0 1-.374-.25c-.157-.173-.166-.168.504-.318.417-.094 1.24-.531 1.29-.685.016-.05-.118-.07-.338-.053-.2.016-.363-.004-.363-.046 0-.04.164-.243.363-.451.748-.781 1.004-1.365 1.083-2.474l.055-.767.176.365c.194.401.23.98.091 1.478-.115.416-.038.462.173.104.261-.443.345-.373.299.251-.05.678-.283 1.187-.808 1.762-.429.468-.377.552.141.233.5-.308.567-.26.31.224-.487.914-1.516 1.69-2.585 1.948-.647.158-1.106.187-1.7.11-1.55-.204-3.018-1.249-3.718-2.648a8.736 8.736 0 0 0-.572-.989c-.275-.373-.298-.54-.113-.823.093-.141.114-.286.076-.502-.176-.999-.17-1.03.23-1.437.35-.353.371-.4.371-.813 0-.358.036-.475.198-.637.109-.108.282-.199.384-.2.296-.003.807-.277 1.11-.595.252-.265.52-.4.822-.404z" fill="#b2ebf2" stroke-width=".696"/></symbol><symbol clip-rule="evenodd" stroke-linejoin="round" stroke-miterlimit="1.414" viewBox="0 0 24 24" id="folder-ngrx-effects-open" xmlns="http://www.w3.org/2000/svg"><path d="M19 20H4a2 2 0 0 1-2-2V6c0-1.11.89-2 2-2h6l2 2h7c1.097 0 2 .903 2 2H4v10l2.14-8h17.07l-2.28 8.5c-.23.87-1.01 1.5-1.93 1.5z" fill="#00bcd4"/><path d="M17.655 8.39l-6.152 2.192.933 8.143 5.219 2.888 5.219-2.888.932-8.143zm-1.278 2.067c.234-.004.487.07.768.222.124.067.498.162.83.21 1.183.171 2.586 1.074 3.03 1.95.306.603.243.928-.225 1.17-.404.208-1.23.107-2.43-.298l-1.012-.341-.36.137c-.522.2-1.044.694-1.258 1.19-.154.359-.177.527-.149 1.116.028.59.071.761.28 1.132.239.422.786.96.88.866.026-.026-.03-.197-.124-.38-.093-.183-.148-.368-.122-.41.026-.042.273.114.548.347.611.517 1.326.848 1.981.917.538.056.661-.044.258-.211a1.238 1.238 0 0 1-.374-.25c-.157-.173-.166-.168.504-.318.417-.094 1.24-.531 1.29-.685.016-.05-.118-.07-.338-.053-.2.016-.363-.005-.363-.046 0-.04.164-.243.363-.452.748-.78 1.004-1.364 1.083-2.474l.055-.766.176.364c.194.402.23.981.091 1.479-.115.416-.038.462.173.103.261-.442.345-.372.299.252-.05.678-.283 1.186-.808 1.761-.429.47-.377.553.141.234.5-.308.567-.26.31.224-.487.914-1.516 1.689-2.585 1.948-.647.158-1.106.187-1.7.109-1.55-.203-3.018-1.248-3.718-2.647a8.736 8.736 0 0 0-.572-.989c-.275-.373-.298-.54-.113-.823.093-.142.114-.286.076-.502-.176-.999-.17-1.03.23-1.437.35-.353.371-.4.371-.813 0-.358.036-.475.198-.637.109-.109.282-.2.384-.2.296-.003.807-.277 1.11-.595.252-.265.52-.4.822-.404z" fill="#b2ebf2" stroke-width=".696"/></symbol><symbol clip-rule="evenodd" fill-rule="evenodd" stroke-linejoin="round" stroke-miterlimit="1.414" viewBox="0 0 24 24" id="folder-ngrx-reducer" xmlns="http://www.w3.org/2000/svg"><path d="M10 4H4c-1.11 0-2 .89-2 2v12c0 1.097.903 2 2 2h16c1.097 0 2-.903 2-2V8a2 2 0 0 0-2-2h-8l-2-2z" fill="#ef5350" fill-rule="nonzero"/><path d="M17.655 8.39l-6.152 2.193.933 8.142 5.219 2.888 5.219-2.888.932-8.142zm-1.278 2.067c.234-.004.487.07.768.223.124.066.498.16.83.21 1.183.17 2.586 1.073 3.03 1.95.306.602.243.927-.225 1.169-.404.209-1.23.108-2.43-.297l-1.012-.342-.36.137c-.522.2-1.044.694-1.258 1.19-.154.359-.177.527-.149 1.116.028.59.071.761.28 1.132.239.422.786.96.88.866.026-.026-.03-.197-.124-.38-.093-.183-.148-.368-.122-.41.026-.042.273.114.548.347.611.518 1.326.848 1.981.917.538.056.661-.044.258-.211a1.238 1.238 0 0 1-.374-.25c-.157-.173-.166-.168.504-.318.417-.094 1.24-.531 1.29-.685.016-.05-.118-.07-.338-.053-.2.016-.363-.004-.363-.046 0-.04.164-.243.363-.451.748-.781 1.004-1.365 1.083-2.474l.055-.767.176.365c.194.401.23.98.091 1.478-.115.416-.038.462.173.104.261-.443.345-.373.299.251-.05.678-.283 1.187-.808 1.762-.429.468-.377.552.141.233.5-.308.567-.26.31.224-.487.914-1.516 1.69-2.585 1.948-.647.158-1.106.187-1.7.11-1.55-.204-3.018-1.249-3.718-2.648a8.736 8.736 0 0 0-.572-.989c-.275-.373-.298-.54-.113-.823.093-.141.114-.286.076-.502-.176-.999-.17-1.03.23-1.437.35-.353.371-.4.371-.813 0-.358.036-.475.198-.637.109-.108.282-.199.384-.2.296-.003.807-.277 1.11-.595.252-.265.52-.4.822-.404z" fill="#ffcdd2" stroke-width=".696"/></symbol><symbol clip-rule="evenodd" stroke-linejoin="round" stroke-miterlimit="1.414" viewBox="0 0 24 24" id="folder-ngrx-reducer-open" xmlns="http://www.w3.org/2000/svg"><path d="M19 20H4a2 2 0 0 1-2-2V6c0-1.11.89-2 2-2h6l2 2h7c1.097 0 2 .903 2 2H4v10l2.14-8h17.07l-2.28 8.5c-.23.87-1.01 1.5-1.93 1.5z" fill="#ef5350"/><path d="M17.655 8.39l-6.152 2.192.933 8.143 5.219 2.888 5.219-2.888.932-8.143zm-1.278 2.067c.234-.004.487.07.768.222.124.067.498.162.83.21 1.183.171 2.586 1.074 3.03 1.95.306.603.243.928-.225 1.17-.404.208-1.23.107-2.43-.298l-1.012-.341-.36.137c-.522.2-1.044.694-1.258 1.19-.154.359-.177.527-.149 1.116.028.59.071.761.28 1.132.239.422.786.96.88.866.026-.026-.03-.197-.124-.38-.093-.183-.148-.368-.122-.41.026-.042.273.114.548.347.611.517 1.326.848 1.981.917.538.056.661-.044.258-.211a1.238 1.238 0 0 1-.374-.25c-.157-.173-.166-.168.504-.318.417-.094 1.24-.531 1.29-.685.016-.05-.118-.07-.338-.053-.2.016-.363-.005-.363-.046 0-.04.164-.243.363-.452.748-.78 1.004-1.364 1.083-2.474l.055-.766.176.364c.194.402.23.981.091 1.479-.115.416-.038.462.173.103.261-.442.345-.372.299.252-.05.678-.283 1.186-.808 1.761-.429.47-.377.553.141.234.5-.308.567-.26.31.224-.487.914-1.516 1.689-2.585 1.948-.647.158-1.106.187-1.7.109-1.55-.203-3.018-1.248-3.718-2.647a8.736 8.736 0 0 0-.572-.989c-.275-.373-.298-.54-.113-.823.093-.142.114-.286.076-.502-.176-.999-.17-1.03.23-1.437.35-.353.371-.4.371-.813 0-.358.036-.475.198-.637.109-.109.282-.2.384-.2.296-.003.807-.277 1.11-.595.252-.265.52-.4.822-.404z" fill="#ffcdd2" stroke-width=".696"/></symbol><symbol clip-rule="evenodd" fill-rule="evenodd" stroke-linejoin="round" stroke-miterlimit="1.414" viewBox="0 0 24 24" id="folder-ngrx-state" xmlns="http://www.w3.org/2000/svg"><path d="M10 4H4c-1.11 0-2 .89-2 2v12c0 1.097.903 2 2 2h16c1.097 0 2-.903 2-2V8a2 2 0 0 0-2-2h-8l-2-2z" fill="#8bc34a" fill-rule="nonzero"/><path d="M17.655 8.39l-6.152 2.193.933 8.142 5.219 2.888 5.219-2.888.932-8.142zm-1.278 2.067c.234-.004.487.07.768.223.124.066.498.16.83.21 1.183.17 2.586 1.073 3.03 1.95.306.602.243.927-.225 1.169-.404.209-1.23.108-2.43-.297l-1.012-.342-.36.137c-.522.2-1.044.694-1.258 1.19-.154.359-.177.527-.149 1.116.028.59.071.761.28 1.132.239.422.786.96.88.866.026-.026-.03-.197-.124-.38-.093-.183-.148-.368-.122-.41.026-.042.273.114.548.347.611.518 1.326.848 1.981.917.538.056.661-.044.258-.211a1.238 1.238 0 0 1-.374-.25c-.157-.173-.166-.168.504-.318.417-.094 1.24-.531 1.29-.685.016-.05-.118-.07-.338-.053-.2.016-.363-.004-.363-.046 0-.04.164-.243.363-.451.748-.781 1.004-1.365 1.083-2.474l.055-.767.176.365c.194.401.23.98.091 1.478-.115.416-.038.462.173.104.261-.443.345-.373.299.251-.05.678-.283 1.187-.808 1.762-.429.468-.377.552.141.233.5-.308.567-.26.31.224-.487.914-1.516 1.69-2.585 1.948-.647.158-1.106.187-1.7.11-1.55-.204-3.018-1.249-3.718-2.648a8.736 8.736 0 0 0-.572-.989c-.275-.373-.298-.54-.113-.823.093-.141.114-.286.076-.502-.176-.999-.17-1.03.23-1.437.35-.353.371-.4.371-.813 0-.358.036-.475.198-.637.109-.108.282-.199.384-.2.296-.003.807-.277 1.11-.595.252-.265.52-.4.822-.404z" fill="#dcedc8" stroke-width=".696"/></symbol><symbol clip-rule="evenodd" stroke-linejoin="round" stroke-miterlimit="1.414" viewBox="0 0 24 24" id="folder-ngrx-state-open" xmlns="http://www.w3.org/2000/svg"><path d="M19 20H4a2 2 0 0 1-2-2V6c0-1.11.89-2 2-2h6l2 2h7c1.097 0 2 .903 2 2H4v10l2.14-8h17.07l-2.28 8.5c-.23.87-1.01 1.5-1.93 1.5z" fill="#8bc34a"/><path d="M17.655 8.39l-6.152 2.192.933 8.143 5.219 2.888 5.219-2.888.932-8.143zm-1.278 2.067c.234-.004.487.07.768.222.124.067.498.162.83.21 1.183.171 2.586 1.074 3.03 1.95.306.603.243.928-.225 1.17-.404.208-1.23.107-2.43-.298l-1.012-.341-.36.137c-.522.2-1.044.694-1.258 1.19-.154.359-.177.527-.149 1.116.028.59.071.761.28 1.132.239.422.786.96.88.866.026-.026-.03-.197-.124-.38-.093-.183-.148-.368-.122-.41.026-.042.273.114.548.347.611.517 1.326.848 1.981.917.538.056.661-.044.258-.211a1.238 1.238 0 0 1-.374-.25c-.157-.173-.166-.168.504-.318.417-.094 1.24-.531 1.29-.685.016-.05-.118-.07-.338-.053-.2.016-.363-.005-.363-.046 0-.04.164-.243.363-.452.748-.78 1.004-1.364 1.083-2.474l.055-.766.176.364c.194.402.23.981.091 1.479-.115.416-.038.462.173.103.261-.442.345-.372.299.252-.05.678-.283 1.186-.808 1.761-.429.47-.377.553.141.234.5-.308.567-.26.31.224-.487.914-1.516 1.689-2.585 1.948-.647.158-1.106.187-1.7.109-1.55-.203-3.018-1.248-3.718-2.647a8.736 8.736 0 0 0-.572-.989c-.275-.373-.298-.54-.113-.823.093-.142.114-.286.076-.502-.176-.999-.17-1.03.23-1.437.35-.353.371-.4.371-.813 0-.358.036-.475.198-.637.109-.109.282-.2.384-.2.296-.003.807-.277 1.11-.595.252-.265.52-.4.822-.404z" fill="#dcedc8" stroke-width=".696"/></symbol><symbol clip-rule="evenodd" fill-rule="evenodd" stroke-linejoin="round" stroke-miterlimit="1.414" viewBox="0 0 24 24" id="folder-node" xmlns="http://www.w3.org/2000/svg"><path d="M10 4H4c-1.11 0-2 .89-2 2v12c0 1.097.903 2 2 2h16c1.097 0 2-.903 2-2V8a2 2 0 0 0-2-2h-8l-2-2z" fill="#8bc34a" fill-rule="nonzero"/><path d="M17.25 8.403c-.188 0-.382.048-.542.139l-5.166 2.986a1.096 1.096 0 0 0-.542.944v5.959c0 .388.208.75.542.944l1.354.778c.66.32.882.326 1.187.326.973 0 1.535-.59 1.535-1.618V12.98a.154.154 0 0 0-.153-.153h-.646c-.09 0-.16.07-.16.153v5.882c0 .458-.472.91-1.228.528l-1.424-.813a.181.181 0 0 1-.076-.145v-5.959c0-.062.027-.118.076-.146l5.167-2.979a.15.15 0 0 1 .152 0l5.167 2.98a.164.164 0 0 1 .076.145v5.959a.181.181 0 0 1-.076.145l-5.167 2.98c-.041.027-.11.027-.16 0l-1.305-.792c-.055-.021-.111-.028-.146-.007-.368.208-.437.25-.778.354-.083.028-.215.076.05.222l1.721 1.021c.167.097.348.146.542.146s.375-.049.542-.146l5.166-2.979c.334-.194.542-.556.542-.944v-5.959c0-.389-.208-.75-.542-.944l-5.166-2.986a1.103 1.103 0 0 0-.542-.14m1.389 4.272c-1.472 0-2.354.618-2.354 1.66 0 1.117.875 1.444 2.291 1.583 1.688.166 1.82.416 1.82.75 0 .576-.465.82-1.549.82-1.375 0-1.666-.341-1.77-1.022a.157.157 0 0 0-.153-.125h-.667c-.083 0-.146.063-.146.153 0 .861.472 1.903 2.736 1.903 1.632 0 2.57-.646 2.57-1.771 0-1.118-.75-1.41-2.34-1.625-1.605-.208-1.765-.32-1.765-.694 0-.313.14-.73 1.327-.73 1.042 0 1.451.23 1.611.945.014.07.076.118.146.118h.673a.134.134 0 0 0 .105-.049c.027-.028.048-.07.034-.11-.097-1.237-.916-1.806-2.57-1.806z" fill-rule="nonzero" fill="#f1f8e9"/></symbol><symbol clip-rule="evenodd" fill-rule="evenodd" stroke-linejoin="round" stroke-miterlimit="1.414" viewBox="0 0 24 24" id="folder-node-open" xmlns="http://www.w3.org/2000/svg"><path d="M19 20H4a2 2 0 0 1-2-2V6c0-1.11.89-2 2-2h6l2 2h7c1.097 0 2 .903 2 2H4v10l2.14-8h17.07l-2.28 8.5c-.23.87-1.01 1.5-1.93 1.5z" fill="#8bc34a" fill-rule="nonzero"/><path d="M17.25 8.403c-.188 0-.382.048-.542.139l-5.166 2.986a1.096 1.096 0 0 0-.542.944v5.959c0 .388.208.75.542.944l1.354.778c.66.32.882.326 1.187.326.973 0 1.535-.59 1.535-1.618V12.98a.154.154 0 0 0-.153-.153h-.646c-.09 0-.16.07-.16.153v5.882c0 .458-.472.91-1.228.528l-1.424-.813a.181.181 0 0 1-.076-.145v-5.959c0-.062.027-.118.076-.146l5.167-2.979a.15.15 0 0 1 .152 0l5.167 2.98a.164.164 0 0 1 .076.145v5.959a.181.181 0 0 1-.076.145l-5.167 2.98c-.041.027-.11.027-.16 0l-1.305-.792c-.055-.021-.111-.028-.146-.007-.368.208-.437.25-.778.354-.083.028-.215.076.05.222l1.721 1.021c.167.097.348.146.542.146s.375-.049.542-.146l5.166-2.979c.334-.194.542-.556.542-.944v-5.959c0-.389-.208-.75-.542-.944l-5.166-2.986a1.103 1.103 0 0 0-.542-.14m1.389 4.272c-1.472 0-2.354.618-2.354 1.66 0 1.117.875 1.444 2.291 1.583 1.688.166 1.82.416 1.82.75 0 .576-.465.82-1.549.82-1.375 0-1.666-.341-1.77-1.022a.157.157 0 0 0-.153-.125h-.667c-.083 0-.146.063-.146.153 0 .861.472 1.903 2.736 1.903 1.632 0 2.57-.646 2.57-1.771 0-1.118-.75-1.41-2.34-1.625-1.605-.208-1.765-.32-1.765-.694 0-.313.14-.73 1.327-.73 1.042 0 1.451.23 1.611.945.014.07.076.118.146.118h.673a.134.134 0 0 0 .105-.049c.027-.028.048-.07.034-.11-.097-1.237-.916-1.806-2.57-1.806z" fill-rule="nonzero" fill="#f1f8e9"/></symbol><symbol clip-rule="evenodd" fill-rule="evenodd" stroke-linejoin="round" stroke-miterlimit="1.414" viewBox="0 0 24 24" id="folder-public" xmlns="http://www.w3.org/2000/svg"><path d="M10 4H4c-1.11 0-2 .89-2 2v12c0 1.097.903 2 2 2h16c1.097 0 2-.903 2-2V8a2 2 0 0 0-2-2h-8l-2-2z" fill="#039be5" fill-rule="nonzero"/><path d="M20.036 16.746c.05-.408.087-.817.087-1.237s-.037-.83-.087-1.238h2.091c.099.396.16.81.16 1.238a5.1 5.1 0 0 1-.16 1.237m-3.186 3.44c.371-.687.656-1.43.854-2.203h1.825a4.968 4.968 0 0 1-2.679 2.203m-.155-3.44h-2.895c-.062-.408-.099-.817-.099-1.237s.037-.835.1-1.238h2.894c.056.403.1.817.1 1.238s-.044.829-.1 1.237m-1.447 3.687a8.39 8.39 0 0 1-1.182-2.45h2.363a8.39 8.39 0 0 1-1.181 2.45m-2.475-7.399h-1.806a4.902 4.902 0 0 1 2.672-2.202c-.37.686-.65 1.429-.866 2.202m-1.806 4.95h1.806c.217.773.495 1.515.866 2.202a4.954 4.954 0 0 1-2.672-2.203m-.508-1.237a5.099 5.099 0 0 1-.16-1.237 5.1 5.1 0 0 1 .16-1.238h2.091c-.049.409-.086.817-.086 1.238s.037.829.086 1.237m2.698-6.168a8.425 8.425 0 0 1 1.181 2.456h-2.363a8.426 8.426 0 0 1 1.182-2.456m4.28 2.456h-1.824a9.682 9.682 0 0 0-.854-2.202 4.94 4.94 0 0 1 2.679 2.202m-4.281-3.712a6.193 6.193 0 0 0-6.187 6.187 6.186 6.186 0 0 0 6.187 6.186 6.186 6.186 0 0 0 6.186-6.186 6.186 6.186 0 0 0-6.186-6.187z" fill="#b3e5fc" stroke-width=".619"/></symbol><symbol clip-rule="evenodd" stroke-linejoin="round" stroke-miterlimit="1.414" viewBox="0 0 24 24" id="folder-public-open" xmlns="http://www.w3.org/2000/svg"><path d="M19 20H4a2 2 0 0 1-2-2V6c0-1.11.89-2 2-2h6l2 2h7c1.097 0 2 .903 2 2H4v10l2.14-8h17.07l-2.28 8.5c-.23.87-1.01 1.5-1.93 1.5z" fill="#039be5"/><path d="M20.037 16.746c.05-.408.087-.817.087-1.237s-.037-.83-.087-1.238h2.091c.099.396.16.81.16 1.238a5.1 5.1 0 0 1-.16 1.237m-3.186 3.44c.371-.687.656-1.43.854-2.203h1.825a4.967 4.967 0 0 1-2.68 2.203m-.154-3.44h-2.895c-.062-.408-.099-.817-.099-1.237s.037-.835.099-1.238h2.895c.056.403.1.817.1 1.238s-.044.829-.1 1.237m-1.447 3.687a8.39 8.39 0 0 1-1.182-2.45h2.363a8.39 8.39 0 0 1-1.181 2.45m-2.475-7.399H13.06a4.902 4.902 0 0 1 2.672-2.202c-.371.686-.65 1.429-.866 2.202m-1.806 4.95h1.806c.217.773.495 1.515.866 2.202a4.954 4.954 0 0 1-2.672-2.203m-.508-1.237a5.099 5.099 0 0 1-.16-1.237 5.1 5.1 0 0 1 .16-1.238h2.091c-.05.409-.086.817-.086 1.238s.037.829.086 1.237m2.698-6.168a8.425 8.425 0 0 1 1.181 2.456h-2.363a8.426 8.426 0 0 1 1.182-2.456m4.28 2.456h-1.824a9.682 9.682 0 0 0-.854-2.202 4.941 4.941 0 0 1 2.679 2.202M17.34 9.322a6.193 6.193 0 0 0-6.187 6.187 6.186 6.186 0 0 0 6.187 6.186 6.186 6.186 0 0 0 6.186-6.186 6.186 6.186 0 0 0-6.186-6.187z" fill="#b3e5fc" stroke-width=".619"/></symbol><symbol clip-rule="evenodd" fill-rule="evenodd" stroke-linejoin="round" stroke-miterlimit="1.414" viewBox="0 0 24 24" id="folder-react-components" xmlns="http://www.w3.org/2000/svg"><path d="M10 4H4c-1.11 0-2 .89-2 2v12c0 1.097.903 2 2 2h16c1.097 0 2-.903 2-2V8a2 2 0 0 0-2-2h-8l-2-2z" fill="#00bcd4" fill-rule="nonzero"/><path d="M16.473 13.927c.723 0 1.313.59 1.313 1.327 0 .703-.59 1.3-1.313 1.3a1.318 1.318 0 0 1-1.313-1.3c0-.737.59-1.327 1.313-1.327m-3.252 6.946c.443.267 1.412-.14 2.529-1.194a17.015 17.015 0 0 1-1.06-1.335 15.945 15.945 0 0 1-1.686-.252c-.358 1.502-.225 2.535.217 2.78m.499-4.03l-.204-.359a5.558 5.558 0 0 0-.203.604c.19.042.4.078.618.113l-.211-.359m4.593-.533l.569-1.054-.569-1.053c-.21-.372-.435-.702-.639-1.032-.38-.022-.78-.022-1.2-.022-.422 0-.823 0-1.202.022-.204.33-.428.66-.639 1.032l-.569 1.053.57 1.054c.21.372.434.702.638 1.032.38.021.78.021 1.201.021.421 0 .822 0 1.201-.02.204-.331.428-.661.639-1.033m-1.84-4.72c-.133.155-.274.316-.414.506h.828c-.14-.19-.28-.351-.414-.506m0 7.332c.133-.154.274-.316.414-.505h-.828c.14.19.28.35.414.505m3.245-9.284c-.436-.267-1.405.14-2.522 1.194.366.414.724.864 1.06 1.334.577.057 1.146.14 1.686.253.359-1.503.225-2.535-.224-2.78m-.492 4.03l.204.358c.077-.203.154-.407.203-.604-.19-.042-.4-.077-.618-.112l.211.358m1.018-4.95c1.033.589 1.145 2.141.71 3.953 1.784.527 3.069 1.398 3.069 2.584 0 1.187-1.285 2.058-3.07 2.585.436 1.812.324 3.364-.709 3.954-1.025.59-2.423-.085-3.77-1.37-1.35 1.285-2.747 1.96-3.78 1.37-1.025-.59-1.137-2.142-.702-3.954-1.783-.527-3.069-1.398-3.069-2.585s1.286-2.057 3.07-2.584c-.436-1.812-.324-3.364.702-3.954 1.032-.59 2.43.084 3.778 1.37 1.348-1.286 2.746-1.96 3.771-1.37m-.203 6.538c.239.527.45 1.054.625 1.588 1.475-.443 2.303-1.075 2.303-1.588 0-.512-.828-1.144-2.303-1.587a15.81 15.81 0 0 1-.625 1.587m-7.136 0a15.806 15.806 0 0 1-.625-1.587c-1.474.443-2.303 1.075-2.303 1.587 0 .513.829 1.145 2.303 1.588.176-.534.387-1.06.625-1.588m6.321 1.588l-.21.358c.217-.035.428-.07.617-.113-.049-.196-.126-.4-.203-.604l-.204.359m-2.03 2.837c1.117 1.053 2.086 1.46 2.522 1.194.45-.246.583-1.278.224-2.781-.54.112-1.11.196-1.685.253-.337.47-.695.92-1.06 1.334m-3.477-6.012l.21-.358c-.217.035-.428.07-.617.113.049.196.126.4.203.604l.204-.359m2.03-2.837c-1.117-1.053-2.086-1.46-2.529-1.194-.442.246-.576 1.278-.217 2.781.54-.112 1.11-.196 1.685-.253.337-.47.695-.92 1.06-1.334z" fill="#b2ebf2" stroke-width=".702"/></symbol><symbol clip-rule="evenodd" stroke-linejoin="round" stroke-miterlimit="1.414" viewBox="0 0 24 24" id="folder-react-components-open" xmlns="http://www.w3.org/2000/svg"><path d="M19 20H4a2 2 0 0 1-2-2V6c0-1.11.89-2 2-2h6l2 2h7c1.097 0 2 .903 2 2H4v10l2.14-8h17.07l-2.28 8.5c-.23.87-1.01 1.5-1.93 1.5z" fill="#00bcd4"/><path d="M16.473 13.928c.723 0 1.313.59 1.313 1.327 0 .703-.59 1.3-1.313 1.3a1.318 1.318 0 0 1-1.313-1.3c0-.737.59-1.327 1.313-1.327m-3.252 6.946c.443.267 1.412-.14 2.529-1.194a16.997 16.997 0 0 1-1.06-1.335 15.945 15.945 0 0 1-1.686-.252c-.358 1.502-.225 2.535.217 2.78m.499-4.03l-.204-.359c-.077.204-.154.408-.203.604.19.042.4.078.618.113l-.211-.359m4.593-.533l.569-1.054-.57-1.053c-.21-.372-.434-.702-.638-1.032-.38-.022-.78-.022-1.201-.022-.421 0-.822 0-1.2.022-.205.33-.43.66-.64 1.032l-.569 1.053.569 1.054c.21.372.435.702.64 1.032.378.021.779.021 1.2.021.421 0 .822 0 1.2-.02.205-.33.43-.661.64-1.033m-1.84-4.72c-.133.155-.274.316-.414.506h.828c-.14-.19-.28-.351-.414-.506m0 7.332c.133-.154.274-.316.414-.505h-.828c.14.19.28.35.414.505m3.244-9.284c-.435-.267-1.404.14-2.52 1.194.364.414.723.864 1.06 1.334.575.057 1.144.14 1.685.253.358-1.503.225-2.535-.225-2.78m-.491 4.03l.203.358c.078-.203.155-.407.204-.604-.19-.042-.4-.077-.618-.112l.21.358m1.02-4.95c1.032.589 1.144 2.141.708 3.953 1.784.527 3.07 1.398 3.07 2.584 0 1.187-1.286 2.058-3.07 2.585.436 1.812.323 3.364-.709 3.954-1.025.59-2.423-.085-3.771-1.37-1.348 1.285-2.746 1.96-3.778 1.37-1.026-.59-1.138-2.142-.703-3.954-1.783-.527-3.069-1.398-3.069-2.585s1.286-2.057 3.07-2.584c-.436-1.812-.324-3.364.702-3.954 1.032-.59 2.43.084 3.778 1.37 1.348-1.286 2.746-1.96 3.771-1.37m-.204 6.538c.24.527.45 1.054.625 1.588 1.475-.443 2.304-1.075 2.304-1.588 0-.512-.829-1.144-2.304-1.587a15.81 15.81 0 0 1-.625 1.587m-7.135 0a15.808 15.808 0 0 1-.625-1.587c-1.475.443-2.303 1.075-2.303 1.587 0 .513.828 1.145 2.303 1.588.176-.534.386-1.06.625-1.588m6.32 1.588l-.21.358c.218-.035.428-.07.618-.113a5.56 5.56 0 0 0-.204-.604l-.203.359m-2.03 2.837c1.117 1.053 2.086 1.46 2.521 1.194.45-.246.583-1.278.225-2.781-.54.112-1.11.196-1.685.253-.338.47-.696.92-1.06 1.334m-3.477-6.012l.21-.358c-.217.035-.428.07-.617.112.049.197.126.4.203.604l.204-.358m2.03-2.837c-1.117-1.053-2.086-1.46-2.529-1.194-.442.246-.576 1.278-.217 2.781.54-.112 1.11-.196 1.685-.253.337-.47.695-.92 1.06-1.334z" fill="#b2ebf2" stroke-width=".702"/></symbol><symbol clip-rule="evenodd" fill-rule="evenodd" stroke-linejoin="round" stroke-miterlimit="1.414" viewBox="0 0 24 24" id="folder-redux-actions" xmlns="http://www.w3.org/2000/svg"><path d="M10 4H4c-1.11 0-2 .89-2 2v12c0 1.097.903 2 2 2h16c1.097 0 2-.903 2-2V8a2 2 0 0 0-2-2h-8l-2-2z" fill="#ab47bc" fill-rule="nonzero"/><g transform="translate(8.378 6.436) scale(.17228)" fill="#e1bee7" stroke="#e1bee7" stroke-miterlimit="4" stroke-width="1.702"><path d="M65.6 65.4c2.9-.3 5.1-2.8 5-5.8S68 54.2 65 54.2h-.2c-3.1.1-5.5 2.7-5.4 5.8.1 1.5.7 2.8 1.6 3.7-3.4 6.7-8.6 11.6-16.4 15.7-5.3 2.8-10.8 3.8-16.3 3.1-4.5-.6-8-2.6-10.2-5.9-3.2-4.9-3.5-10.2-.8-15.5 1.9-3.8 4.9-6.6 6.8-8-.4-1.3-1-3.5-1.3-5.1-14.5 10.5-13 24.7-8.6 31.4 3.3 5 10 8.1 17.4 8.1 2 0 4-.2 6-.7 12.8-2.5 22.5-10.1 28-21.4z"/><path d="M83.2 53c-7.6-8.9-18.8-13.8-31.6-13.8H50c-.9-1.8-2.8-3-4.9-3h-.2c-3.1.1-5.5 2.7-5.4 5.8.1 3 2.6 5.4 5.6 5.4h.2c2.2-.1 4.1-1.5 4.9-3.4H52c7.6 0 14.8 2.2 21.3 6.5 5 3.3 8.6 7.6 10.6 12.8 1.7 4.2 1.6 8.3-.2 11.8-2.8 5.3-7.5 8.2-13.7 8.2-4 0-7.8-1.2-9.8-2.1-1.1 1-3.1 2.6-4.5 3.6 4.3 2 8.7 3.1 12.9 3.1 9.6 0 16.7-5.3 19.4-10.6 2.9-5.8 2.7-15.8-4.8-24.3z"/><path d="M32.4 67.1c.1 3 2.6 5.4 5.6 5.4h.2c3.1-.1 5.5-2.7 5.4-5.8-.1-3-2.6-5.4-5.6-5.4h-.2c-.2 0-.5 0-.7.1-4.1-6.8-5.8-14.2-5.2-22.2.4-6 2.4-11.2 5.9-15.5 2.9-3.7 8.5-5.5 12.3-5.6 10.6-.2 15.1 13 15.4 18.3 1.3.3 3.5 1 5 1.5-1.2-16.2-11.2-24.6-20.8-24.6-9 0-17.3 6.5-20.6 16.1-4.6 12.8-1.6 25.1 4 34.8-.5.7-.8 1.8-.7 2.9z"/></g></symbol><symbol clip-rule="evenodd" stroke-linejoin="round" stroke-miterlimit="1.414" viewBox="0 0 24 24" id="folder-redux-actions-open" xmlns="http://www.w3.org/2000/svg"><path d="M19 20H4a2 2 0 0 1-2-2V6c0-1.11.89-2 2-2h6l2 2h7c1.097 0 2 .903 2 2H4v10l2.14-8h17.07l-2.28 8.5c-.23.87-1.01 1.5-1.93 1.5z" fill="#ab47bc"/><g transform="translate(8.378 6.436) scale(.17228)" fill="#e1bee7" stroke="#e1bee7" stroke-miterlimit="4" stroke-width="1.702"><path d="M65.6 65.4c2.9-.3 5.1-2.8 5-5.8S68 54.2 65 54.2h-.2c-3.1.1-5.5 2.7-5.4 5.8.1 1.5.7 2.8 1.6 3.7-3.4 6.7-8.6 11.6-16.4 15.7-5.3 2.8-10.8 3.8-16.3 3.1-4.5-.6-8-2.6-10.2-5.9-3.2-4.9-3.5-10.2-.8-15.5 1.9-3.8 4.9-6.6 6.8-8-.4-1.3-1-3.5-1.3-5.1-14.5 10.5-13 24.7-8.6 31.4 3.3 5 10 8.1 17.4 8.1 2 0 4-.2 6-.7 12.8-2.5 22.5-10.1 28-21.4z"/><path d="M83.2 53c-7.6-8.9-18.8-13.8-31.6-13.8H50c-.9-1.8-2.8-3-4.9-3h-.2c-3.1.1-5.5 2.7-5.4 5.8.1 3 2.6 5.4 5.6 5.4h.2c2.2-.1 4.1-1.5 4.9-3.4H52c7.6 0 14.8 2.2 21.3 6.5 5 3.3 8.6 7.6 10.6 12.8 1.7 4.2 1.6 8.3-.2 11.8-2.8 5.3-7.5 8.2-13.7 8.2-4 0-7.8-1.2-9.8-2.1-1.1 1-3.1 2.6-4.5 3.6 4.3 2 8.7 3.1 12.9 3.1 9.6 0 16.7-5.3 19.4-10.6 2.9-5.8 2.7-15.8-4.8-24.3z"/><path d="M32.4 67.1c.1 3 2.6 5.4 5.6 5.4h.2c3.1-.1 5.5-2.7 5.4-5.8-.1-3-2.6-5.4-5.6-5.4h-.2c-.2 0-.5 0-.7.1-4.1-6.8-5.8-14.2-5.2-22.2.4-6 2.4-11.2 5.9-15.5 2.9-3.7 8.5-5.5 12.3-5.6 10.6-.2 15.1 13 15.4 18.3 1.3.3 3.5 1 5 1.5-1.2-16.2-11.2-24.6-20.8-24.6-9 0-17.3 6.5-20.6 16.1-4.6 12.8-1.6 25.1 4 34.8-.5.7-.8 1.8-.7 2.9z"/></g></symbol><symbol clip-rule="evenodd" fill-rule="evenodd" stroke-linejoin="round" stroke-miterlimit="1.414" viewBox="0 0 24 24" id="folder-redux-reducer" xmlns="http://www.w3.org/2000/svg"><path d="M10 4H4c-1.11 0-2 .89-2 2v12c0 1.097.903 2 2 2h16c1.097 0 2-.903 2-2V8a2 2 0 0 0-2-2h-8l-2-2z" fill="#ef5350" fill-rule="nonzero"/><g transform="translate(8.378 6.436) scale(.17228)" fill="#ffcdd2" stroke="#ffcdd2" stroke-miterlimit="4" stroke-width="1.702"><path d="M65.6 65.4c2.9-.3 5.1-2.8 5-5.8S68 54.2 65 54.2h-.2c-3.1.1-5.5 2.7-5.4 5.8.1 1.5.7 2.8 1.6 3.7-3.4 6.7-8.6 11.6-16.4 15.7-5.3 2.8-10.8 3.8-16.3 3.1-4.5-.6-8-2.6-10.2-5.9-3.2-4.9-3.5-10.2-.8-15.5 1.9-3.8 4.9-6.6 6.8-8-.4-1.3-1-3.5-1.3-5.1-14.5 10.5-13 24.7-8.6 31.4 3.3 5 10 8.1 17.4 8.1 2 0 4-.2 6-.7 12.8-2.5 22.5-10.1 28-21.4z"/><path d="M83.2 53c-7.6-8.9-18.8-13.8-31.6-13.8H50c-.9-1.8-2.8-3-4.9-3h-.2c-3.1.1-5.5 2.7-5.4 5.8.1 3 2.6 5.4 5.6 5.4h.2c2.2-.1 4.1-1.5 4.9-3.4H52c7.6 0 14.8 2.2 21.3 6.5 5 3.3 8.6 7.6 10.6 12.8 1.7 4.2 1.6 8.3-.2 11.8-2.8 5.3-7.5 8.2-13.7 8.2-4 0-7.8-1.2-9.8-2.1-1.1 1-3.1 2.6-4.5 3.6 4.3 2 8.7 3.1 12.9 3.1 9.6 0 16.7-5.3 19.4-10.6 2.9-5.8 2.7-15.8-4.8-24.3z"/><path d="M32.4 67.1c.1 3 2.6 5.4 5.6 5.4h.2c3.1-.1 5.5-2.7 5.4-5.8-.1-3-2.6-5.4-5.6-5.4h-.2c-.2 0-.5 0-.7.1-4.1-6.8-5.8-14.2-5.2-22.2.4-6 2.4-11.2 5.9-15.5 2.9-3.7 8.5-5.5 12.3-5.6 10.6-.2 15.1 13 15.4 18.3 1.3.3 3.5 1 5 1.5-1.2-16.2-11.2-24.6-20.8-24.6-9 0-17.3 6.5-20.6 16.1-4.6 12.8-1.6 25.1 4 34.8-.5.7-.8 1.8-.7 2.9z"/></g></symbol><symbol clip-rule="evenodd" stroke-linejoin="round" stroke-miterlimit="1.414" viewBox="0 0 24 24" id="folder-redux-reducer-open" xmlns="http://www.w3.org/2000/svg"><path d="M19 20H4a2 2 0 0 1-2-2V6c0-1.11.89-2 2-2h6l2 2h7c1.097 0 2 .903 2 2H4v10l2.14-8h17.07l-2.28 8.5c-.23.87-1.01 1.5-1.93 1.5z" fill="#ef5350"/><g transform="translate(8.378 6.436) scale(.17228)" fill="#ffcdd2" stroke="#ffcdd2" stroke-miterlimit="4" stroke-width="1.702"><path d="M65.6 65.4c2.9-.3 5.1-2.8 5-5.8S68 54.2 65 54.2h-.2c-3.1.1-5.5 2.7-5.4 5.8.1 1.5.7 2.8 1.6 3.7-3.4 6.7-8.6 11.6-16.4 15.7-5.3 2.8-10.8 3.8-16.3 3.1-4.5-.6-8-2.6-10.2-5.9-3.2-4.9-3.5-10.2-.8-15.5 1.9-3.8 4.9-6.6 6.8-8-.4-1.3-1-3.5-1.3-5.1-14.5 10.5-13 24.7-8.6 31.4 3.3 5 10 8.1 17.4 8.1 2 0 4-.2 6-.7 12.8-2.5 22.5-10.1 28-21.4z"/><path d="M83.2 53c-7.6-8.9-18.8-13.8-31.6-13.8H50c-.9-1.8-2.8-3-4.9-3h-.2c-3.1.1-5.5 2.7-5.4 5.8.1 3 2.6 5.4 5.6 5.4h.2c2.2-.1 4.1-1.5 4.9-3.4H52c7.6 0 14.8 2.2 21.3 6.5 5 3.3 8.6 7.6 10.6 12.8 1.7 4.2 1.6 8.3-.2 11.8-2.8 5.3-7.5 8.2-13.7 8.2-4 0-7.8-1.2-9.8-2.1-1.1 1-3.1 2.6-4.5 3.6 4.3 2 8.7 3.1 12.9 3.1 9.6 0 16.7-5.3 19.4-10.6 2.9-5.8 2.7-15.8-4.8-24.3z"/><path d="M32.4 67.1c.1 3 2.6 5.4 5.6 5.4h.2c3.1-.1 5.5-2.7 5.4-5.8-.1-3-2.6-5.4-5.6-5.4h-.2c-.2 0-.5 0-.7.1-4.1-6.8-5.8-14.2-5.2-22.2.4-6 2.4-11.2 5.9-15.5 2.9-3.7 8.5-5.5 12.3-5.6 10.6-.2 15.1 13 15.4 18.3 1.3.3 3.5 1 5 1.5-1.2-16.2-11.2-24.6-20.8-24.6-9 0-17.3 6.5-20.6 16.1-4.6 12.8-1.6 25.1 4 34.8-.5.7-.8 1.8-.7 2.9z"/></g></symbol><symbol clip-rule="evenodd" fill-rule="evenodd" stroke-linejoin="round" stroke-miterlimit="1.414" viewBox="0 0 24 24" id="folder-redux-store" xmlns="http://www.w3.org/2000/svg"><path d="M10 4H4c-1.11 0-2 .89-2 2v12c0 1.097.903 2 2 2h16c1.097 0 2-.903 2-2V8a2 2 0 0 0-2-2h-8l-2-2z" fill="#8bc34a" fill-rule="nonzero"/><g transform="translate(8.378 6.436) scale(.17228)" fill="#dcedc8" stroke="#dcedc8" stroke-miterlimit="4" stroke-width="1.702"><path d="M65.6 65.4c2.9-.3 5.1-2.8 5-5.8S68 54.2 65 54.2h-.2c-3.1.1-5.5 2.7-5.4 5.8.1 1.5.7 2.8 1.6 3.7-3.4 6.7-8.6 11.6-16.4 15.7-5.3 2.8-10.8 3.8-16.3 3.1-4.5-.6-8-2.6-10.2-5.9-3.2-4.9-3.5-10.2-.8-15.5 1.9-3.8 4.9-6.6 6.8-8-.4-1.3-1-3.5-1.3-5.1-14.5 10.5-13 24.7-8.6 31.4 3.3 5 10 8.1 17.4 8.1 2 0 4-.2 6-.7 12.8-2.5 22.5-10.1 28-21.4z"/><path d="M83.2 53c-7.6-8.9-18.8-13.8-31.6-13.8H50c-.9-1.8-2.8-3-4.9-3h-.2c-3.1.1-5.5 2.7-5.4 5.8.1 3 2.6 5.4 5.6 5.4h.2c2.2-.1 4.1-1.5 4.9-3.4H52c7.6 0 14.8 2.2 21.3 6.5 5 3.3 8.6 7.6 10.6 12.8 1.7 4.2 1.6 8.3-.2 11.8-2.8 5.3-7.5 8.2-13.7 8.2-4 0-7.8-1.2-9.8-2.1-1.1 1-3.1 2.6-4.5 3.6 4.3 2 8.7 3.1 12.9 3.1 9.6 0 16.7-5.3 19.4-10.6 2.9-5.8 2.7-15.8-4.8-24.3z"/><path d="M32.4 67.1c.1 3 2.6 5.4 5.6 5.4h.2c3.1-.1 5.5-2.7 5.4-5.8-.1-3-2.6-5.4-5.6-5.4h-.2c-.2 0-.5 0-.7.1-4.1-6.8-5.8-14.2-5.2-22.2.4-6 2.4-11.2 5.9-15.5 2.9-3.7 8.5-5.5 12.3-5.6 10.6-.2 15.1 13 15.4 18.3 1.3.3 3.5 1 5 1.5-1.2-16.2-11.2-24.6-20.8-24.6-9 0-17.3 6.5-20.6 16.1-4.6 12.8-1.6 25.1 4 34.8-.5.7-.8 1.8-.7 2.9z"/></g></symbol><symbol clip-rule="evenodd" stroke-linejoin="round" stroke-miterlimit="1.414" viewBox="0 0 24 24" id="folder-redux-store-open" xmlns="http://www.w3.org/2000/svg"><path d="M19 20H4a2 2 0 0 1-2-2V6c0-1.11.89-2 2-2h6l2 2h7c1.097 0 2 .903 2 2H4v10l2.14-8h17.07l-2.28 8.5c-.23.87-1.01 1.5-1.93 1.5z" fill="#8bc34a"/><g transform="translate(8.378 6.436) scale(.17228)" fill="#dcedc8" stroke="#dcedc8" stroke-miterlimit="4" stroke-width="1.702"><path d="M65.6 65.4c2.9-.3 5.1-2.8 5-5.8S68 54.2 65 54.2h-.2c-3.1.1-5.5 2.7-5.4 5.8.1 1.5.7 2.8 1.6 3.7-3.4 6.7-8.6 11.6-16.4 15.7-5.3 2.8-10.8 3.8-16.3 3.1-4.5-.6-8-2.6-10.2-5.9-3.2-4.9-3.5-10.2-.8-15.5 1.9-3.8 4.9-6.6 6.8-8-.4-1.3-1-3.5-1.3-5.1-14.5 10.5-13 24.7-8.6 31.4 3.3 5 10 8.1 17.4 8.1 2 0 4-.2 6-.7 12.8-2.5 22.5-10.1 28-21.4z"/><path d="M83.2 53c-7.6-8.9-18.8-13.8-31.6-13.8H50c-.9-1.8-2.8-3-4.9-3h-.2c-3.1.1-5.5 2.7-5.4 5.8.1 3 2.6 5.4 5.6 5.4h.2c2.2-.1 4.1-1.5 4.9-3.4H52c7.6 0 14.8 2.2 21.3 6.5 5 3.3 8.6 7.6 10.6 12.8 1.7 4.2 1.6 8.3-.2 11.8-2.8 5.3-7.5 8.2-13.7 8.2-4 0-7.8-1.2-9.8-2.1-1.1 1-3.1 2.6-4.5 3.6 4.3 2 8.7 3.1 12.9 3.1 9.6 0 16.7-5.3 19.4-10.6 2.9-5.8 2.7-15.8-4.8-24.3z"/><path d="M32.4 67.1c.1 3 2.6 5.4 5.6 5.4h.2c3.1-.1 5.5-2.7 5.4-5.8-.1-3-2.6-5.4-5.6-5.4h-.2c-.2 0-.5 0-.7.1-4.1-6.8-5.8-14.2-5.2-22.2.4-6 2.4-11.2 5.9-15.5 2.9-3.7 8.5-5.5 12.3-5.6 10.6-.2 15.1 13 15.4 18.3 1.3.3 3.5 1 5 1.5-1.2-16.2-11.2-24.6-20.8-24.6-9 0-17.3 6.5-20.6 16.1-4.6 12.8-1.6 25.1 4 34.8-.5.7-.8 1.8-.7 2.9z"/></g></symbol><symbol clip-rule="evenodd" fill-rule="evenodd" stroke-linejoin="round" stroke-miterlimit="1.414" viewBox="0 0 24 24" id="folder-resource" xmlns="http://www.w3.org/2000/svg"><path d="M10 4H4c-1.11 0-2 .89-2 2v12c0 1.097.903 2 2 2h16c1.097 0 2-.903 2-2V8a2 2 0 0 0-2-2h-8l-2-2z" fill="#fbc02d" fill-rule="nonzero"/><path d="M21.598 12.059h-6.085v-1.217h6.085m-2.434 6.085h-3.65V15.71h3.65m2.434-1.217h-6.085v-1.217h6.085m.608-4.26h-7.301a1.217 1.217 0 0 0-1.217 1.218v7.301a1.217 1.217 0 0 0 1.217 1.217h7.301a1.217 1.217 0 0 0 1.217-1.217v-7.301a1.217 1.217 0 0 0-1.217-1.217m-9.735 2.434h-1.217v8.518a1.217 1.217 0 0 0 1.217 1.217h8.519v-1.217H12.47z" fill="#fff9c4" stroke-width=".608"/></symbol><symbol clip-rule="evenodd" stroke-linejoin="round" stroke-miterlimit="1.414" viewBox="0 0 24 24" id="folder-resource-open" xmlns="http://www.w3.org/2000/svg"><path d="M19 20H4a2 2 0 0 1-2-2V6c0-1.11.89-2 2-2h6l2 2h7c1.097 0 2 .903 2 2H4v10l2.14-8h17.07l-2.28 8.5c-.23.87-1.01 1.5-1.93 1.5z" fill="#fbc02d"/><path d="M21.598 12.059h-6.085v-1.217h6.085m-2.434 6.085h-3.65V15.71h3.65m2.434-1.217h-6.085v-1.217h6.085m.608-4.26h-7.301a1.217 1.217 0 0 0-1.217 1.218v7.301a1.217 1.217 0 0 0 1.217 1.217h7.301a1.217 1.217 0 0 0 1.217-1.217v-7.301a1.217 1.217 0 0 0-1.217-1.217m-9.735 2.433h-1.217v8.519a1.217 1.217 0 0 0 1.217 1.217h8.519v-1.217H12.47z" fill="#fff9c4" stroke-width=".608"/></symbol><symbol viewBox="0 0 24 24" fill-rule="evenodd" clip-rule="evenodd" id="folder-sass" xmlns="http://www.w3.org/2000/svg"><path d="M10 4H4c-1.11 0-2 .89-2 2v12c0 1.097.903 2 2 2h16c1.097 0 2-.903 2-2V8a2 2 0 0 0-2-2h-8l-2-2z" fill="#f8bbd0" fill-rule="nonzero"/><path d="M23.36 10.506c-.39-1.527-2.922-2.03-5.319-1.178-1.426.507-2.97 1.302-4.08 2.34-1.32 1.235-1.53 2.31-1.444 2.759.306 1.584 2.477 2.62 3.37 3.388v.005c-.264.13-2.19 1.104-2.64 2.1-.476 1.052.075 1.806.44 1.908 1.131.315 2.292-.251 2.916-1.182.602-.897.551-2.056.29-2.633.36-.095.781-.138 1.316-.076 1.508.177 1.804 1.118 1.748 1.513-.057.394-.373.61-.48.676-.105.065-.137.088-.129.137.013.07.062.068.152.053.125-.021.792-.321.821-1.048.037-.924-.849-1.958-2.416-1.93-.646.01-1.052.072-1.345.181-.022-.024-.044-.05-.067-.073-.969-1.034-2.76-1.765-2.684-3.156.027-.505.203-1.835 3.442-3.45 2.653-1.322 4.777-.957 5.145-.151.524 1.152-1.136 3.293-3.891 3.601-1.05.118-1.603-.289-1.74-.44-.145-.16-.166-.167-.22-.137-.088.049-.033.19 0 .274.082.214.42.594.995.782.506.166 1.739.258 3.23-.319 1.669-.646 2.972-2.443 2.59-3.944zm-7.103 7.783a2.2 2.2 0 0 1-.065 1.413 2.405 2.405 0 0 1-.453.704c-.5.546-1.198.752-1.497.579-.323-.188-.161-.956.418-1.568.623-.66 1.52-1.083 1.52-1.083l-.002-.002.079-.043z" fill="#ec407a" fill-rule="nonzero" stroke="#ec407a" stroke-width=".5199012000000001"/></symbol><symbol viewBox="0 0 24 24" fill-rule="evenodd" clip-rule="evenodd" id="folder-sass-open" xmlns="http://www.w3.org/2000/svg"><path d="M19 20H4a2 2 0 0 1-2-2V6c0-1.11.89-2 2-2h6l2 2h7c1.097 0 2 .903 2 2H4v10l2.14-8h17.07l-2.28 8.5c-.23.87-1.01 1.5-1.93 1.5z" fill="#f8bbd0" fill-rule="nonzero"/><path d="M23.36 10.506c-.39-1.527-2.922-2.03-5.319-1.178-1.426.507-2.97 1.302-4.08 2.34-1.32 1.235-1.53 2.31-1.444 2.759.306 1.584 2.477 2.62 3.37 3.388v.005c-.264.13-2.19 1.104-2.64 2.1-.476 1.052.075 1.806.44 1.908 1.131.315 2.292-.251 2.916-1.182.602-.897.551-2.056.29-2.633.36-.095.781-.138 1.316-.076 1.508.177 1.804 1.118 1.748 1.513-.057.394-.373.61-.48.676-.105.065-.137.088-.129.137.013.07.062.068.152.053.125-.021.792-.321.821-1.048.037-.924-.849-1.958-2.416-1.93-.646.01-1.052.072-1.345.181-.022-.024-.044-.05-.067-.073-.969-1.034-2.76-1.765-2.684-3.156.027-.505.203-1.835 3.442-3.45 2.653-1.322 4.777-.957 5.145-.151.524 1.152-1.136 3.293-3.891 3.601-1.05.118-1.603-.289-1.74-.44-.145-.16-.166-.167-.22-.137-.088.049-.033.19 0 .274.082.214.42.594.995.782.506.166 1.739.258 3.23-.319 1.669-.646 2.972-2.443 2.59-3.944zm-7.103 7.783a2.2 2.2 0 0 1-.065 1.413 2.405 2.405 0 0 1-.453.704c-.5.546-1.198.752-1.497.579-.323-.188-.161-.956.418-1.568.623-.66 1.52-1.083 1.52-1.083l-.002-.002.079-.043z" fill="#ec407a" fill-rule="nonzero" stroke="#ec407a" stroke-width=".5199012000000001"/></symbol><symbol clip-rule="evenodd" fill-rule="evenodd" stroke-linejoin="round" stroke-miterlimit="1.414" viewBox="0 0 24 24" id="folder-scripts" xmlns="http://www.w3.org/2000/svg"><path d="M10 4H4c-1.11 0-2 .89-2 2v12c0 1.097.903 2 2 2h16c1.097 0 2-.903 2-2V8a2 2 0 0 0-2-2h-8l-2-2z" fill="#546e7a" fill-rule="nonzero"/><path d="M18.466 20.241c.69 0 1.259-.568 1.259-1.258v-8.18H15.32a.632.632 0 0 0-.63.63v6.292h-1.887v-6.922c0-1.036.852-1.888 1.888-1.888h6.921c1.036 0 1.888.852 1.888 1.888v.63h-2.517v8.18a1.896 1.896 0 0 1-1.888 1.887h-6.292a1.896 1.896 0 0 1-1.888-1.888v-.629h6.293c0 .69.568 1.258 1.258 1.258z" fill-rule="nonzero" fill="#cfd8dc"/></symbol><symbol clip-rule="evenodd" fill-rule="evenodd" stroke-linejoin="round" stroke-miterlimit="1.414" viewBox="0 0 24 24" id="folder-scripts-open" xmlns="http://www.w3.org/2000/svg"><path d="M19 20H4a2 2 0 0 1-2-2V6c0-1.11.89-2 2-2h6l2 2h7c1.097 0 2 .903 2 2H4v10l2.14-8h17.07l-2.28 8.5c-.23.87-1.01 1.5-1.93 1.5z" fill="#546e7a" fill-rule="nonzero"/><path d="M18.466 20.241c.69 0 1.259-.568 1.259-1.258v-8.18H15.32a.632.632 0 0 0-.63.63v6.292h-1.887v-6.922c0-1.036.852-1.888 1.888-1.888h6.921c1.036 0 1.888.852 1.888 1.888v.63h-2.517v8.18a1.896 1.896 0 0 1-1.888 1.887h-6.292a1.896 1.896 0 0 1-1.888-1.888v-.629h6.293c0 .69.568 1.258 1.258 1.258z" fill-rule="nonzero" fill="#cfd8dc"/></symbol><symbol clip-rule="evenodd" fill-rule="evenodd" stroke-linejoin="round" stroke-miterlimit="1.414" viewBox="0 0 24 24" id="folder-src" xmlns="http://www.w3.org/2000/svg"><path d="M10 4H4c-1.11 0-2 .89-2 2v12c0 1.097.903 2 2 2h16c1.097 0 2-.903 2-2V8a2 2 0 0 0-2-2h-8l-2-2z" fill="#4caf50" fill-rule="nonzero"/><g fill="#c8e6c9" transform="translate(2.065 -.225) scale(.70678)"><path d="M19.146 30.989a.902.902 0 0 1-.207-.025 1.045 1.045 0 0 1-.726-1.213l2.709-14.431c.049-.279.209-.525.444-.683a.891.891 0 0 1 .7-.122c.519.152.837.684.727 1.213L20.077 30.16a1.032 1.032 0 0 1-.442.681.895.895 0 0 1-.489.148zM24.578 28.944h-.068a.932.932 0 0 1-.668-.377 1.104 1.104 0 0 1 .1-1.419l4.658-4.553-4.638-4.239a1.105 1.105 0 0 1-.141-1.416.938.938 0 0 1 .661-.4.9.9 0 0 1 .709.237l5.47 5c.386.372.448.974.144 1.416a1.05 1.05 0 0 1-.142.163l-5.447 5.324a.913.913 0 0 1-.638.264zM16.423 28.947a.917.917 0 0 1-.639-.267l-5.452-5.327a.874.874 0 0 1-.132-.153 1.097 1.097 0 0 1 .141-1.414l5.471-5a.882.882 0 0 1 .7-.238.939.939 0 0 1 .665.4 1.104 1.104 0 0 1-.14 1.417L12.4 22.6l4.659 4.551c.377.382.42.988.1 1.419a.928.928 0 0 1-.669.377z" fill-rule="nonzero"/></g></symbol><symbol clip-rule="evenodd" stroke-linejoin="round" stroke-miterlimit="1.414" viewBox="0 0 24 24" id="folder-src-open" xmlns="http://www.w3.org/2000/svg"><path d="M19 20H4a2 2 0 0 1-2-2V6c0-1.11.89-2 2-2h6l2 2h7c1.097 0 2 .903 2 2H4v10l2.14-8h17.07l-2.28 8.5c-.23.87-1.01 1.5-1.93 1.5z" fill="#4caf50"/><g fill="#c8e6c9" fill-rule="evenodd" transform="translate(2.064 -.224) scale(.70678)"><path d="M19.146 30.989a.902.902 0 0 1-.207-.025 1.045 1.045 0 0 1-.726-1.213l2.709-14.431c.049-.279.209-.525.444-.683a.891.891 0 0 1 .7-.122c.519.152.837.684.727 1.213L20.077 30.16a1.032 1.032 0 0 1-.442.681.895.895 0 0 1-.489.148zM24.578 28.944h-.068a.932.932 0 0 1-.668-.377 1.104 1.104 0 0 1 .1-1.419l4.658-4.553-4.638-4.239a1.105 1.105 0 0 1-.141-1.416.938.938 0 0 1 .661-.4.9.9 0 0 1 .709.237l5.47 5c.386.372.448.974.144 1.416a1.05 1.05 0 0 1-.142.163l-5.447 5.324a.913.913 0 0 1-.638.264zM16.423 28.947a.917.917 0 0 1-.639-.267l-5.452-5.327a.874.874 0 0 1-.132-.153 1.097 1.097 0 0 1 .141-1.414l5.471-5a.882.882 0 0 1 .7-.238.939.939 0 0 1 .665.4 1.104 1.104 0 0 1-.14 1.417L12.4 22.6l4.659 4.551c.377.382.42.988.1 1.419a.928.928 0 0 1-.669.377z" fill-rule="nonzero"/></g></symbol><symbol viewBox="0 0 24 24" fill-rule="evenodd" clip-rule="evenodd" stroke-linejoin="round" stroke-miterlimit="1.414" id="folder-test" xmlns="http://www.w3.org/2000/svg"><path d="M10 4H4c-1.11 0-2 .89-2 2v12c0 1.097.903 2 2 2h16c1.097 0 2-.903 2-2V8a2 2 0 0 0-2-2h-8l-2-2z" fill="#1de9b6" fill-rule="nonzero"/><path d="M14 8.097v1.39h.695v9.732A2.794 2.794 0 0 0 17.475 22a2.794 2.794 0 0 0 2.781-2.78V9.486h.695v-1.39H14m2.78 9.732c-.417 0-.695-.278-.695-.695 0-.417.278-.695.696-.695.417 0 .695.278.695.695 0 .417-.278.695-.695.695m1.39-2.78c-.417 0-.695-.278-.695-.696 0-.417.278-.695.695-.695.417 0 .695.278.695.695 0 .418-.278.696-.695.696m.695-3.476h-2.78V9.487h2.78v2.086z" fill="#00897b" fill-rule="nonzero"/></symbol><symbol viewBox="0 0 24 24" fill-rule="evenodd" clip-rule="evenodd" stroke-linejoin="round" stroke-miterlimit="1.414" id="folder-test-open" xmlns="http://www.w3.org/2000/svg"><path d="M19 20H4a2 2 0 0 1-2-2V6c0-1.11.89-2 2-2h6l2 2h7c1.097 0 2 .903 2 2H4v10l2.14-8h17.07l-2.28 8.5c-.23.87-1.01 1.5-1.93 1.5z" fill="#1de9b6" fill-rule="nonzero"/><path d="M14 8.097v1.39h.695v9.732A2.794 2.794 0 0 0 17.475 22a2.794 2.794 0 0 0 2.781-2.78V9.486h.695v-1.39H14m2.78 9.732c-.417 0-.695-.278-.695-.695 0-.417.278-.695.696-.695.417 0 .695.278.695.695 0 .417-.278.695-.695.695m1.39-2.78c-.417 0-.695-.278-.695-.696 0-.417.278-.695.695-.695.417 0 .695.278.695.695 0 .418-.278.696-.695.696m.695-3.476h-2.78V9.487h2.78v2.086z" fill="#00897b" fill-rule="nonzero"/></symbol><symbol clip-rule="evenodd" fill-rule="evenodd" stroke-linejoin="round" stroke-miterlimit="1.414" viewBox="0 0 24 24" id="folder-tools" xmlns="http://www.w3.org/2000/svg"><path d="M10 4H4c-1.11 0-2 .89-2 2v12c0 1.097.903 2 2 2h16c1.097 0 2-.903 2-2V8a2 2 0 0 0-2-2h-8l-2-2z" fill="#1e88e5" fill-rule="nonzero"/><path d="M21.043 15.266a1.07 1.07 0 0 1-1.07-1.07 1.07 1.07 0 0 1 1.07-1.071 1.07 1.07 0 0 1 1.07 1.07 1.07 1.07 0 0 1-1.07 1.071m-2.141-2.855a1.07 1.07 0 0 1-1.07-1.07 1.07 1.07 0 0 1 1.07-1.071 1.07 1.07 0 0 1 1.07 1.07 1.07 1.07 0 0 1-1.07 1.071m-3.569 0a1.07 1.07 0 0 1-1.07-1.07 1.07 1.07 0 0 1 1.07-1.071 1.07 1.07 0 0 1 1.07 1.07 1.07 1.07 0 0 1-1.07 1.071m-2.141 2.855a1.07 1.07 0 0 1-1.07-1.07 1.07 1.07 0 0 1 1.07-1.071 1.07 1.07 0 0 1 1.07 1.07 1.07 1.07 0 0 1-1.07 1.071m3.925-6.424a6.424 6.424 0 0 0-6.423 6.424 6.424 6.424 0 0 0 6.423 6.424 1.07 1.07 0 0 0 1.071-1.07c0-.28-.107-.53-.278-.715a1.105 1.105 0 0 1-.271-.713 1.07 1.07 0 0 1 1.07-1.071h1.263a3.569 3.569 0 0 0 3.57-3.569c0-3.154-2.877-5.71-6.425-5.71z" fill="#bbdefb" stroke-width=".714"/></symbol><symbol clip-rule="evenodd" stroke-linejoin="round" stroke-miterlimit="1.414" viewBox="0 0 24 24" id="folder-tools-open" xmlns="http://www.w3.org/2000/svg"><path d="M19 20H4a2 2 0 0 1-2-2V6c0-1.11.89-2 2-2h6l2 2h7c1.097 0 2 .903 2 2H4v10l2.14-8h17.07l-2.28 8.5c-.23.87-1.01 1.5-1.93 1.5z" fill="#1e88e5"/><path d="M21.043 15.266a1.07 1.07 0 0 1-1.07-1.07 1.07 1.07 0 0 1 1.07-1.071 1.07 1.07 0 0 1 1.07 1.07 1.07 1.07 0 0 1-1.07 1.071m-2.141-2.855a1.07 1.07 0 0 1-1.07-1.07 1.07 1.07 0 0 1 1.07-1.071 1.07 1.07 0 0 1 1.07 1.07 1.07 1.07 0 0 1-1.07 1.071m-3.569 0a1.07 1.07 0 0 1-1.07-1.07 1.07 1.07 0 0 1 1.07-1.071 1.07 1.07 0 0 1 1.07 1.07 1.07 1.07 0 0 1-1.07 1.071m-2.141 2.855a1.07 1.07 0 0 1-1.07-1.07 1.07 1.07 0 0 1 1.07-1.071 1.07 1.07 0 0 1 1.07 1.07 1.07 1.07 0 0 1-1.07 1.071m3.925-6.424a6.424 6.424 0 0 0-6.423 6.424 6.424 6.424 0 0 0 6.423 6.424 1.07 1.07 0 0 0 1.071-1.07c0-.28-.107-.53-.278-.715a1.105 1.105 0 0 1-.271-.713 1.07 1.07 0 0 1 1.07-1.071h1.263a3.569 3.569 0 0 0 3.57-3.569c0-3.154-2.877-5.71-6.425-5.71z" fill="#bbdefb" stroke-width=".714"/></symbol><symbol viewBox="0 0 24 24" fill-rule="evenodd" clip-rule="evenodd" stroke-linejoin="round" stroke-miterlimit="1.414" id="folder-views" xmlns="http://www.w3.org/2000/svg"><path d="M10 4H4c-1.11 0-2 .89-2 2v12c0 1.097.903 2 2 2h16c1.097 0 2-.903 2-2V8a2 2 0 0 0-2-2h-8l-2-2z" fill="#ff8a65" fill-rule="nonzero"/><path d="M12.487 21.868L11.384 9.5H23.5l-1.104 12.366-4.961 1.375-4.948-1.373zm4.464-3.2l-3.926-2.36v-.855l3.926-2.361v1.323l-2.504 1.465 2.504 1.465v1.323zm.982-.001v-1.323l2.522-1.464-2.522-1.464v-1.323l3.926 2.35v.874l-3.926 2.35z" fill="#e44d26"/></symbol><symbol viewBox="0 0 24 24" fill-rule="evenodd" clip-rule="evenodd" stroke-linejoin="round" stroke-miterlimit="1.414" id="folder-views-open" xmlns="http://www.w3.org/2000/svg"><path d="M19 20H4a2 2 0 0 1-2-2V6c0-1.11.89-2 2-2h6l2 2h7c1.097 0 2 .903 2 2H4v10l2.14-8h17.07l-2.28 8.5c-.23.87-1.01 1.5-1.93 1.5z" fill="#ff8a65" fill-rule="nonzero"/><path d="M12.487 21.868L11.384 9.5H23.5l-1.104 12.366-4.961 1.375-4.948-1.373zm4.464-3.2l-3.926-2.36v-.855l3.926-2.361v1.323l-2.504 1.465 2.504 1.465v1.323zm.982-.001v-1.323l2.522-1.464-2.522-1.464v-1.323l3.926 2.35v.874l-3.926 2.35z" fill="#e44d26"/></symbol><symbol clip-rule="evenodd" fill-rule="evenodd" stroke-linejoin="round" stroke-miterlimit="1.414" viewBox="0 0 24 24" id="folder-vscode" xmlns="http://www.w3.org/2000/svg"><path d="M10 4H4c-1.11 0-2 .89-2 2v12c0 1.097.903 2 2 2h16c1.097 0 2-.903 2-2V8a2 2 0 0 0-2-2h-8l-2-2z" fill="#42a5f5" fill-rule="nonzero"/><path d="M20.811 8.52l-5.988 5.506-3.346-2.522-1.383.805 3.298 3.03-3.298 3.032 1.383.807 3.346-2.522 5.988 5.503 2.921-1.419V9.94zm0 3.622v6.396l-4.245-3.198z" fill="#bbdefb" stroke-width=".974"/></symbol><symbol clip-rule="evenodd" fill-rule="evenodd" stroke-linejoin="round" stroke-miterlimit="1.414" viewBox="0 0 24 24" id="folder-vscode-open" xmlns="http://www.w3.org/2000/svg"><path d="M19 20H4a2 2 0 0 1-2-2V6c0-1.11.89-2 2-2h6l2 2h7c1.097 0 2 .903 2 2H4v10l2.14-8h17.07l-2.28 8.5c-.23.87-1.01 1.5-1.93 1.5z" fill="#42a5f5" fill-rule="nonzero"/><path d="M20.81 8.52l-5.988 5.506-3.346-2.522-1.384.805 3.3 3.03-3.3 3.032 1.384.807 3.346-2.522 5.988 5.503 2.921-1.419V9.94zm0 3.621v6.397l-4.245-3.198z" fill="#bbdefb" stroke-width=".974"/></symbol><symbol clip-rule="evenodd" fill-rule="evenodd" stroke-linejoin="round" stroke-miterlimit="1.414" viewBox="0 0 24 24" id="folder-vue" xmlns="http://www.w3.org/2000/svg"><path d="M10 4H4c-1.11 0-2 .89-2 2v12c0 1.097.903 2 2 2h16c1.097 0 2-.903 2-2V8a2 2 0 0 0-2-2h-8l-2-2z" fill="#009688" fill-rule="nonzero"/><g transform="translate(8.459 6.362) scale(.69572)"><path d="M1.821 4.15l10.21 17.618L22.239 4.235v-.084h-7.692l-2.434 4.178-2.422-4.178z" fill="#41b883"/><path d="M5.937 4.15l6.152 10.616 6.18-10.617h-3.722l-2.434 4.179-2.422-4.179z" fill="#35495e"/></g></symbol><symbol clip-rule="evenodd" stroke-linejoin="round" stroke-miterlimit="1.414" viewBox="0 0 24 24" id="folder-vue-open" xmlns="http://www.w3.org/2000/svg"><path d="M19 20H4a2 2 0 0 1-2-2V6c0-1.11.89-2 2-2h6l2 2h7c1.097 0 2 .903 2 2H4v10l2.14-8h17.07l-2.28 8.5c-.23.87-1.01 1.5-1.93 1.5z" fill="#009688"/><g transform="translate(8.458 6.362) scale(.69572)"><path d="M1.821 4.15l10.21 17.618L22.239 4.235v-.084h-7.692l-2.434 4.178-2.422-4.178z" fill="#41b883"/><path d="M5.937 4.15l6.152 10.616 6.18-10.617h-3.722l-2.434 4.179-2.422-4.179z" fill="#35495e"/></g></symbol><symbol clip-rule="evenodd" fill-rule="evenodd" stroke-linejoin="round" stroke-miterlimit="1.414" viewBox="0 0 24 24" id="folder-webpack" xmlns="http://www.w3.org/2000/svg"><path d="M10 4H4c-1.11 0-2 .89-2 2v12c0 1.097.903 2 2 2h16c1.097 0 2-.903 2-2V8a2 2 0 0 0-2-2h-8l-2-2z" fill="#03a9f4" fill-rule="nonzero"/><g transform="translate(9.192 7.48) scale(.66328)"><path d="M19.376 15.988l-7.708 4.45-7.709-4.45v-8.9l7.709-4.451 7.708 4.45z" fill="#fff" fill-opacity=".785"/><path d="M12.286 1.98c-.21 0-.41.059-.57.179l-7.9 4.44c-.32.17-.53.5-.53.88v9c0 .38.21.711.53.881l7.9 4.44c.16.12.36.18.57.18s.41-.06.57-.18l7.9-4.44c.32-.17.53-.5.53-.88v-9c0-.38-.21-.712-.53-.882l-7.9-4.44a.945.945 0 0 0-.57-.179zm0 2.15l7 3.94v2.103h-.016v5.177h.016v.54l-7 3.939-7-3.94V8.07zm0 2.08l-4.9 2.83 4.9 2.83 4.9-2.83zm-5 5.08v3.58l4 2.309v-3.58zm10 0l-4 2.308v3.58l4-2.308z" fill="#8ed6fb"/><path d="M12.286 6.21l-4.9 2.83 4.9 2.83 4.9-2.83zm-5 5.08v3.58l4 2.309v-3.58zm10 0l-4 2.308v3.58l4-2.308z" fill="#1c78c0"/></g></symbol><symbol clip-rule="evenodd" stroke-linejoin="round" stroke-miterlimit="1.414" viewBox="0 0 24 24" id="folder-webpack-open" xmlns="http://www.w3.org/2000/svg"><path d="M19 20H4a2 2 0 0 1-2-2V6c0-1.11.89-2 2-2h6l2 2h7c1.097 0 2 .903 2 2H4v10l2.14-8h17.07l-2.28 8.5c-.23.87-1.01 1.5-1.93 1.5z" fill="#03a9f4"/><g transform="translate(9.193 7.48) scale(.66328)"><path d="M19.376 15.988l-7.708 4.45-7.709-4.45v-8.9l7.709-4.451 7.708 4.45z" fill="#fff" fill-opacity=".785"/><path d="M12.286 1.98c-.21 0-.41.059-.57.179l-7.9 4.44c-.32.17-.53.5-.53.88v9c0 .38.21.711.53.881l7.9 4.44c.16.12.36.18.57.18s.41-.06.57-.18l7.9-4.44c.32-.17.53-.5.53-.88v-9c0-.38-.21-.712-.53-.882l-7.9-4.44a.945.945 0 0 0-.57-.179zm0 2.15l7 3.94v2.103h-.016v5.177h.016v.54l-7 3.939-7-3.94V8.07zm0 2.08l-4.9 2.83 4.9 2.83 4.9-2.83zm-5 5.08v3.58l4 2.309v-3.58zm10 0l-4 2.308v3.58l4-2.308z" fill="#8ed6fb"/><path d="M12.286 6.21l-4.9 2.83 4.9 2.83 4.9-2.83zm-5 5.08v3.58l4 2.309v-3.58zm10 0l-4 2.308v3.58l4-2.308z" fill="#1c78c0"/></g></symbol><symbol viewBox="0 0 24 24" id="font" xmlns="http://www.w3.org/2000/svg"><path d="M9.62 12L12 5.67 14.37 12M11 3L5.5 17h2.25l1.12-3h6.25l1.13 3h2.25L13 3h-2z" fill="#f44336"/></symbol><symbol viewBox="0 0 500 500" id="fsharp" xmlns="http://www.w3.org/2000/svg"><path d="M235.906 36.66L21.963 250.601l213.943 213.943v-84.36L106.209 250.487l129.697-129.696z" fill="#378bba" stroke-width="14.706"/><path d="M235.906 156.614l-93.622 93.62 93.622 93.622z" fill="#378bba" stroke-width="15.006"/><path d="M263.417 36.64L477.36 250.583 263.417 464.526v-84.36l129.696-129.697-129.696-129.696z" fill="#30b9db" stroke-width="14.706"/></symbol><symbol viewBox="0 0 152.99 160.01" id="fusebox" xmlns="http://www.w3.org/2000/svg"><defs id="fkdefs4"><style id="fkstyle2">.fkcls-1{fill:#fff}.fkcls-2{fill:#515151}.fkcls-3{fill:#1d79bf}.fkcls-4{fill:#383838}</style></defs><title id="fktitle6">Asset 3</title><g id="fkLayer_2" data-name="Layer 2" transform="matrix(.87285 0 0 .87285 10.17 10.175)"><g id="fkFuse_Box" data-name="Fuse Box"><g id="fkLOGO"><path class="fkcls-1" id="fkpolygon8" fill="#fff" d="M76.56 2.19l74.22 24.93-7.7 87.77-65.41 42.66-69.79-43.93-5.7-86.13z"/><path class="fkcls-2" d="M77.69 160L5.87 114.81 0 26 76.55 0 153 25.67l-7.94 90.4zM9.88 112.43l67.77 42.66 63.45-41.39 7.47-85.13-72-24.18L4.36 28.95z" id="fkpath10" fill="#515151"/><path class="fkcls-3" id="fkpolygon12" fill="#1d79bf" d="M76.4 148.8V61.68l66.93-29.82-5.99 78.77z"/><path id="fkF" class="fkcls-4" fill="#383838" d="M76.4 148.8l-60.35-37.39L9.63 31.8 76.4 61.68z"/><path class="fkcls-1" d="M25.58 52.73l.54 15.93 37.35 18.18.12 14.69-37-18.21 1.64 37.1-14.56-9-5.05-80.55 67.79 30.82v15.46z" id="fkpath15" fill="#fff"/><path class="fkcls-1" d="M135.91 90.77c-.08 13.12-6.33 26.59-16.77 33.12l-42.8 27.93V61.71l42.27-18.84c5.16-2.41 9.51-1.43 12.4 3.11 1.9 3 2.89 7.23 2.86 12.21A35.69 35.69 0 0 1 129.34 76c4.29 2 6.66 6.55 6.57 14.77zM123 63.76c0-4.64-2-6.93-4.92-5.45l-29 14.48L89 90l29.44-15.59c2.5-1.32 4.56-5.91 4.56-10.65zM125.15 96c0-5.71-2.42-8.24-6.55-5.93L89 106.64v19.58l29.34-17.46c4.43-2.64 6.79-7.27 6.81-12.76z" id="fkpath17" fill="#fff"/><path id="fkTOP" class="fkcls-4" fill="#383838" d="M76.4 8.82L9.71 31.77l109.77 2.38-84.02 9.21L76.4 61.68l20.76-9.25-27.73-1.37 49.78-8.46 24.12-10.74z"/></g></g></g></symbol><symbol viewBox="0 0 24 24" id="git" xmlns="http://www.w3.org/2000/svg"><path d="M2.6 10.59L8.38 4.8l1.69 1.7c-.24.85.15 1.78.93 2.23v5.54c-.6.34-1 .99-1 1.73a2 2 0 0 0 2 2 2 2 0 0 0 2-2c0-.74-.4-1.39-1-1.73V9.41l2.07 2.09c-.07.15-.07.32-.07.5a2 2 0 0 0 2 2 2 2 0 0 0 2-2 2 2 0 0 0-2-2c-.18 0-.35 0-.5.07L13.93 7.5a1.98 1.98 0 0 0-1.15-2.34c-.43-.16-.88-.2-1.28-.09L9.8 3.38l.79-.78c.78-.79 2.04-.79 2.82 0l7.99 7.99c.79.78.79 2.04 0 2.82l-7.99 7.99c-.78.79-2.04.79-2.82 0L2.6 13.41c-.79-.78-.79-2.04 0-2.82z" fill="#e64a19"/></symbol><symbol viewBox="0 0 494 455" id="gitlab" xmlns="http://www.w3.org/2000/svg"><title>logo</title><defs><path id="fma" d="M0 1173.3h2000V0H0v1173.3z"/></defs><g transform="matrix(.88256 0 0 -.88256 -286.767 742.766)" fill="none" fill-rule="evenodd"><mask id="fmb" fill="#fff"><use width="100%" height="100%" xlink:href="#fma"/></mask><g mask="url(#fmb)"><g transform="translate(358.67 358.67)"><path d="M492.532 195.445l-27.559 84.815-54.617 168.1c-2.81 8.648-15.045 8.648-17.856 0l-54.619-168.1h-181.37l-54.62 168.1c-2.81 8.648-15.045 8.648-17.856 0l-54.617-168.1-27.557-84.815a18.775 18.775 0 0 1 6.82-20.992l238.51-173.29 238.51 173.29a18.777 18.777 0 0 1 6.82 20.992" fill="#fc6d26"/><path d="M247.2 1.16l90.684 279.1h-181.37z" fill="#e24329"/><path d="M247.201 1.16l-90.684 279.09H29.427z" fill="#fc6d26"/><path d="M29.422 280.256L1.862 195.44a18.774 18.774 0 0 1 6.822-20.991L247.194 1.16z" fill="#fca326"/><path d="M29.422 280.26h127.09l-54.619 168.1c-2.81 8.65-15.047 8.65-17.856 0z" fill="#e24329"/><path d="M247.2 1.16l90.684 279.09h127.09z" fill="#fc6d26"/><path d="M464.98 280.256l27.559-84.815a18.774 18.774 0 0 0-6.821-20.991L247.208 1.16z" fill="#fca326"/><path d="M464.97 280.26H337.88l54.619 168.1c2.81 8.65 15.047 8.65 17.856 0z" fill="#e24329"/></g></g></g></symbol><symbol viewBox="0 0 24 24" id="go" xmlns="http://www.w3.org/2000/svg"><path d="M10.575 1.695c-2.634 0-4.756 2.453-4.756 5.502v4.6l-.027-.003v4.71c0 3.05 2.123 5.502 4.757 5.502h2.286c2.634 0 4.757-2.453 4.757-5.502v-4.6a5.1 5.1 0 0 0 .026.003v-4.71c0-3.049-2.122-5.502-4.756-5.502h-2.287z" fill="#73cddc"/><rect width="2.289" height="3.335" x="-1.178" y="6.092" ry="1.125" transform="matrix(.4849 -.87457 .85979 .51065 0 0)" fill="#73cddc"/><rect width="2.297" height="3.39" x="10.261" y="-15.076" ry="1.143" transform="matrix(.44646 .8948 -.89204 .45195 0 0)" fill="#73cddc"/><circle cx="9.267" cy="5.13" r="2.054" fill="#fff" stroke="#5e5d5b" stroke-width=".1"/><circle cx="14.214" cy="5.116" r="2.054" fill="#fff" stroke="#5e5d5b" stroke-width=".1"/><ellipse cx="8.039" cy="5.051" rx=".792" ry=".901" fill="#030d18"/><path d="M11.792 9.556l.763.138a.403.689 0 0 1 .008.138.403.689 0 0 1-.402.69.403.689 0 0 1-.404-.69.403.689 0 0 1 .035-.276z" fill="#fff" stroke="#fff" stroke-width=".155"/><ellipse cx="8.51" cy="5.365" rx=".138" ry=".166" fill="#fff"/><ellipse cx="12.945" cy="5.189" rx=".792" ry=".901" fill="#030d18"/><ellipse cx="13.414" cy="5.446" rx=".138" ry=".166" fill="#fff"/><ellipse cx="-12.982" cy="-3.409" rx=".708" ry="1.026" transform="rotate(-129.403)" fill="#f6d2a1" stroke-width=".4"/><path d="M11.772 9.553l-.757.135a.4.672 0 0 0-.008.135.4.672 0 0 0 .4.672.4.672 0 0 0 .4-.672.4.672 0 0 0-.035-.27z" fill="#fff" stroke="#fff" stroke-width=".153"/><ellipse cx="1.841" cy="-21.563" rx=".707" ry="1.026" transform="scale(1 -1) rotate(50.597)" fill="#f6d2a1" stroke-width=".4"/><ellipse cx="-17.281" cy="-21.784" rx=".864" ry="1.27" transform="matrix(.3054 -.95222 -.97065 -.2405 0 0)" fill="#f6d2a1" stroke-width=".4"/><ellipse cx="22.885" cy="2.587" rx=".864" ry="1.27" transform="matrix(.22652 .974 .95652 -.29167 0 0)" fill="#f6d2a1" stroke-width=".4"/><path d="M10.708 8.392a.594.594 0 0 0-.594.597v.115c0 .331.264.598.594.598h.386a.973.772 0 0 1 .697-.235.973.772 0 0 1 .698.235h.334c.33 0 .594-.267.594-.598V8.99a.595.595 0 0 0-.594-.597h-2.115z" fill="#f6d2a1" stroke="#657075" stroke-width=".1"/><ellipse cx="11.734" cy="8.203" rx="1.208" ry=".68" fill="#030d18" stroke="#fff" stroke-width=".162"/></symbol><symbol viewBox="0 0 24 24" id="gradle" xmlns="http://www.w3.org/2000/svg"><path d="M21.718 5.503c-.731-1.315-2.04-1.708-2.963-1.727-1.133-.023-2.065.605-1.888 1.017.037.088.25.55.38.741.19.275.527.064.646 0 .353-.187.73-.248 1.16-.198.409.048.954.3 1.319 1.001.859 1.652-1.794 5.05-5.114 2.697-3.32-2.353-6.548-1.574-8.01-1.1-1.462.475-2.135.952-1.556 2.055.785 1.498.524 1.038 1.285 2.28 1.21 1.97 3.856-.908 3.856-.908-1.972 2.906-3.662 2.204-4.31 1.188a15.864 15.864 0 0 1-1.038-1.97c-4.993 1.76-3.642 9.534-3.642 9.534h2.48c.632-2.862 2.892-2.757 3.28 0h1.892c1.673-5.59 5.914 0 5.914 0h2.466c-.69-3.812 1.388-5.01 2.697-7.246 1.31-2.235 2.551-4.969 1.146-7.364zm-6.362 7.362c-1.304-.426-.837-1.723-.837-1.723s1.139.368 2.68.87c-.09.403-.856 1.175-1.843.853z" fill="#0097a7" stroke-width=".47"/></symbol><symbol preserveAspectRatio="xMidYMid" viewBox="0 0 300 300" id="graphcool" xmlns="http://www.w3.org/2000/svg"><path d="M246.886 107.727c-12.237-6.892-27.616 2.1-30.081 3.646l-52.834 29.965c-7.8-6.196-18.914-5.933-26.412.625-7.499 6.558-9.24 17.537-4.14 26.094 5.102 8.556 15.588 12.246 24.923 8.768 9.335-3.478 14.852-13.129 13.111-22.937l52.688-29.9.321-.196c3.464-2.188 11.5-5.462 15.256-3.34 2.706 1.524 4.252 6.629 4.376 14.148h-.066v66.092a17.313 17.313 0 0 1-8.635 14.95l-75.739 43.755a17.312 17.312 0 0 1-17.261 0l-75.74-43.756a17.312 17.312 0 0 1-8.634-14.95V113.22c.01-6.165 3.3-11.86 8.634-14.95l68.549-39.562c6.522 7.482 17.451 9.25 26 4.206s12.283-15.468 8.886-24.794c-3.397-9.327-12.962-14.904-22.751-13.27-9.79 1.636-17.022 10.02-17.204 19.944L59.397 85.632a31.932 31.932 0 0 0-15.978 27.588v87.454a31.933 31.933 0 0 0 15.927 27.602l75.74 43.755a31.934 31.934 0 0 0 31.846 0l75.74-43.755a31.933 31.933 0 0 0 15.927-27.58V137.12h.05c.373-14.913-3.616-24.794-11.762-29.389z" fill="#27ae60" stroke="#27ae60" stroke-width="7.883622079999999"/></symbol><symbol viewBox="0 0 400 400" id="graphql" xmlns="http://www.w3.org/2000/svg"><path d="M67.008 293.022l-13.143-7.588L200.282 31.839l13.143 7.588z" fill="#ec407a" stroke-width="6.803" stroke="#ec407a"/><path d="M50.855 265.174H343.69v15.177H50.855z" fill="#ec407a" stroke-width="6.803" stroke="#ec407a"/><path d="M203.122 358.269L56.649 273.7l7.589-13.143 146.472 84.568zm127.24-220.407L183.889 53.293l7.589-13.143 146.472 84.568z" fill="#ec407a" stroke-width="6.803" stroke="#ec407a"/><path d="M64.278 137.803l-7.588-13.142 146.472-84.568 7.588 13.143z" fill="#ec407a" stroke-width="6.803" stroke="#ec407a"/><path d="M327.661 293.025L181.244 39.43l13.143-7.589 146.417 253.596zM62.466 114.597h15.176v169.136H62.466zm254.528 0h15.176v169.136h-15.176z" fill="#ec407a" stroke-width="6.803" stroke="#ec407a"/><path d="M200.538 351.845l-6.628-11.481L321.3 266.812l6.629 11.48z" fill="#ec407a" stroke-width="6.803" stroke="#ec407a"/><path d="M352.284 288.67c-8.777 15.268-28.342 20.48-43.61 11.703-15.268-8.777-20.48-28.342-11.703-43.61 8.777-15.268 28.342-20.48 43.61-11.703 15.36 8.869 20.57 28.342 11.703 43.61M97.574 141.567c-8.778 15.268-28.343 20.48-43.61 11.703-15.269-8.777-20.48-28.342-11.703-43.61 8.777-15.268 28.342-20.48 43.61-11.703 15.268 8.869 20.479 28.342 11.702 43.61M42.353 288.67c-8.777-15.268-3.566-34.741 11.702-43.61 15.268-8.776 34.741-3.565 43.61 11.703 8.776 15.268 3.565 34.741-11.703 43.61-15.36 8.776-34.833 3.565-43.61-11.703m254.71-147.103c-8.776-15.268-3.565-34.741 11.703-43.61 15.268-8.776 34.742-3.565 43.61 11.703 8.777 15.268 3.566 34.741-11.702 43.61-15.268 8.776-34.833 3.565-43.61-11.703m-99.745 236.608c-17.645 0-31.907-14.262-31.907-31.907s14.262-31.907 31.907-31.907 31.907 14.262 31.907 31.907c0 17.554-14.262 31.907-31.907 31.907m0-294.206c-17.645 0-31.907-14.262-31.907-31.907s14.262-31.907 31.907-31.907 31.907 14.262 31.907 31.907-14.262 31.907-31.907 31.907" fill="#ec407a" stroke-width="6.803" stroke="#ec407a"/></symbol><symbol viewBox="0 0 24 24" id="groovy" xmlns="http://www.w3.org/2000/svg"><path d="M12 1.982a10.119 10.119 0 0 0-10.12 10.12A10.119 10.119 0 0 0 12 22.22 10.119 10.119 0 0 0 22.12 12.1 10.119 10.119 0 0 0 12 1.983zm1.254 2.422c.91 0 1.647.261 2.213.78.571.518.857 1.188.857 2.013 0 .889-.319 1.673-.959 2.35-.64.677-1.376 1.015-2.207 1.015-.486 0-.89-.119-1.213-.357-.317-.238-.476-.532-.476-.88 0-.212.06-.4.181-.563.127-.164.274-.246.438-.246.159 0 .238.092.238.277 0 .164.06.29.182.38.121.09.261.136.42.136.423 0 .828-.29 1.215-.866.391-.582.587-1.202.587-1.863 0-.465-.151-.844-.453-1.135-.301-.296-.69-.445-1.166-.445-.714 0-1.406.318-2.078.953-.666.635-1.211 1.47-1.635 2.506-.417 1.031-.627 2.014-.627 2.945 0 .857.185 1.54.555 2.047.37.503.863.754 1.477.754 1.037 0 2.027-.734 2.974-2.2l1.493-.212c.185-.026.277.018.277.135 0 .053-.072.28-.215.681-.143.402-.337 1.074-.586 2.016.82-.476 1.455-1.003 1.904-1.58v.914c-.36.418-1.046.888-2.062 1.412-.212 1.407-.682 2.493-1.406 3.26-.725.772-1.54 1.16-2.444 1.16-.433 0-.775-.102-1.023-.303-.243-.2-.365-.477-.365-.832 0-.984.955-1.94 2.865-2.865.2-.714.395-1.356.586-1.928-.333.482-.817.907-1.451 1.278-.635.37-1.225.554-1.77.554-.889 0-1.628-.383-2.22-1.15-.588-.772-.881-1.748-.881-2.928 0-1.243.333-2.42 1-3.531a7.747 7.747 0 0 1 2.625-2.674c1.084-.672 2.134-1.008 3.15-1.008zM12.03 16.592c-1.375.687-2.062 1.365-2.062 2.031 0 .354.169.533.508.533.666 0 1.184-.856 1.554-2.564z" fill="#26c6da"/></symbol><symbol viewBox="0 0 24 24" id="gulp" xmlns="http://www.w3.org/2000/svg"><path d="M8.37 15.94a596.238 596.238 0 0 1-.482-4.982c.002-.042-.225-.077-.505-.077h-.508V8.95h3.966V5.198l1.871-1.124c1.14-.685 1.978-1.125 2.144-1.125.4 0 .866.506.866.939 0 .19-.057.422-.127.517-.07.095-.722.53-1.45.966l-1.321.792-.029 1.393-.028 1.393h3.972v1.932h-.98l-.495 4.983-.495 4.983H8.854l-.485-4.906z" fill="#e53935"/></symbol><symbol viewBox="0 0 24 24" id="h" xmlns="http://www.w3.org/2000/svg"><path d="M16.745 19.818h-3.007v-5.882q0-2.381-1.736-2.381-.869 0-1.438.663-.56.662-.56 1.718v5.882H6.988V4.533h3.016v6.508h.037q1.186-1.802 3.193-1.802 3.511 0 3.511 4.239z" stroke-width=".478" fill="#0277bd"/></symbol><symbol viewBox="0 0 253.6 253.6" id="hack" xmlns="http://www.w3.org/2000/svg"><g transform="translate(-29.243 -29.515) scale(1.2301)"><path fill="#607d8b" d="M69.496 159.551v52.576l51.77-52.576zM123.507 41.523l-54.01 52.755v55.084l54.01-54.009z"/><path fill="#eceff1" d="M130.023 95.663v51.501l52.128-51.5z"/><path fill="#607d8b" d="M185.465 101.867l-55.442 55.174v55.083l55.442-55.262z"/><path fill="#ffa000" d="M73.068 154.283l50.427.09v-50.248z"/></g></symbol><symbol viewBox="0 0 300 300.00001" id="haml" xmlns="http://www.w3.org/2000/svg"><g transform="translate(0 165.6)"><path d="M78.42-132.307c-12.047-.302-26.924 5.998-26.924 5.998l49.195 99.791L74.605 85.005c23.81 20.134 50.07 10.504 50.07 10.504L136.76 9.212c1.526 1.446 3.146 2.77 4.777 3.995 5.244 3.714 10.925 6.553 16.606 8.738 5.68 2.185 11.583 3.933 17.482 5.244 3.933.874 7.645 1.53 11.578 1.967-1.748 3.933-2.84 8.083-2.621 12.672 0 .437.22.873.656 1.092h.217c4.152 2.185 8.521 3.934 13.328 5.027 4.589.874 9.615 1.312 14.422.656 5.026-.655 10.051-2.623 13.984-5.9 3.933-3.278 6.774-7.648 8.522-12.237l.219-.218v-.217l.656-5.899v-.22c2.185-1.311 4.37-2.621 6.555-4.37 2.622-2.184 5.025-4.589 6.773-7.648 1.748-3.059 2.84-6.774 2.621-10.488-.218-3.496-1.53-6.99-3.06-10.049-1.53-3.059-3.495-5.901-5.68-8.523-4.37-5.026-9.614-9.176-15.295-12.454-5.462-3.496-11.581-6.338-17.7-8.304l-2.404-.656-1.962-.655c-1.311-.437-2.406-1.092-3.498-1.53-2.185-1.31-3.717-2.622-4.809-4.37-2.185-3.278-2.403-8.301-1.31-13.545.218-1.311.656-2.623 1.093-3.934a96.064 96.064 0 0 0 1.31-4.152c.314-1.412.51-2.829.598-4.402l29.203-25.553c-2.275-8.404-27.488-17.158-27.488-17.158l-74.931 63.726-43.243-81.584c-1.553-.35-3.218-.527-4.94-.57zm107.682 73.14c-.449 2.336-.647 4.795-.647 7.258.219 3.715 1.311 7.87 3.715 11.366 2.403 3.496 5.68 6.117 8.957 7.646a29.663 29.663 0 0 0 5.027 1.967l2.623.654 2.184.438c5.68 1.53 11.142 3.714 16.168 6.554 5.025 2.84 9.833 6.337 13.766 10.27s6.992 8.959 7.43 13.984c.218 3.496-.22 6.118-1.313 8.303-1.093 2.404-2.84 4.588-4.807 6.555-.874.874-1.966 1.747-2.84 2.402a27.11 27.11 0 0 0-.654-5.898c-.219-1.093-.438-1.966-.875-3.059-.437-.874-.872-1.966-1.965-2.621-.218 0-.44-.001-.44.217-1.31 3.277-3.494 6.12-5.898 8.086-2.403 1.966-5.462 2.84-8.521 3.058-3.06.219-6.338-.436-9.616-1.31-3.277-.874-6.552-1.968-9.83-3.06l-.439-.22c-.656-.218-1.526.002-1.963.44-1.748 2.185-3.06 4.149-4.59 6.334a58.435 58.435 0 0 0-2.84 5.027c-3.933-1.53-7.649-2.841-11.582-4.37-5.462-2.186-10.925-4.37-15.95-6.991-5.245-2.404-10.268-5.246-14.638-8.524-3.15-2.363-6.062-4.845-8.185-7.681l2.404-17.172z" fill="#f4511e" stroke-width="0" stroke-linejoin="round"/></g></symbol><symbol viewBox="0 0 24 24" id="handlebars" xmlns="http://www.w3.org/2000/svg"><path d="M8.55 10.32c-2.753 0-4.202 3.48-5.793 3.48-.98 0-1.126-.677-1.126-.915 0-.332.236-.706.564-.706.59 0 .414.77.414.77s.798-.555.272-1.298c-.42-.595-1.31-.623-1.92-.17-.617.458-1.057 1.146-.853 2.287.1.551.468 1.35 1.233 1.805.764.455 1.925.566 2.335.566 2.194 0 4.342-1.633 6.639-2.322a5.513 5.513 0 0 1 1.497-.222 6.19 6.19 0 0 1 1.92.226c2.296.689 4.444 2.323 6.638 2.323.41 0 1.57-.11 2.335-.566.765-.455 1.132-1.256 1.231-1.807.204-1.14-.235-1.829-.853-2.287-.61-.453-1.497-.423-1.918.172-.526.743.27 1.297.27 1.297s-.176-.77.414-.77c.329 0 .565.373.565.705 0 .238-.147.914-1.126.914-1.592 0-3.04-3.478-5.794-3.478-2.565 0-3.076 1.177-3.462 1.718-.004.005-.005.011-.008.016-.005-.006-.007-.013-.012-.02-.386-.54-.896-1.717-3.461-1.717z" fill="#ff7043" fill-rule="evenodd" stroke-width=".3"/></symbol><symbol viewBox="0 0 300.00001 300" id="haskell" xmlns="http://www.w3.org/2000/svg"><g stroke-width="2.422"><path d="M23.928 240.5l59.94-89.852-59.94-89.855h44.955l59.94 89.855-59.94 89.852z" fill="#ef5350"/><path d="M83.869 240.5l59.94-89.852-59.94-89.855h44.955l119.88 179.71h-44.95l-37.46-56.156-37.468 56.156z" fill="#ffa726"/><path d="M228.72 188.08l-19.98-29.953h69.93v29.956h-49.95zm-29.97-44.924l-19.98-29.953h99.901v29.953z" fill="#ffee58"/></g></symbol><symbol viewBox="0 0 210 210" id="haxe" xmlns="http://www.w3.org/2000/svg"><g transform="translate(0 -87)"><path fill="#f68712" stroke-width=".221" d="M42.78 191.545l63.431-63.43 63.431 63.43-63.431 63.431z"/><path d="M42.8 191.592L31.193 148.28 19.59 104.97 62.9 116.575l43.311 11.605-31.706 31.706z" fill="#fab20b" stroke-width=".266"/><path d="M105.956 128.111l-43.19-11.544-43.177-11.597 22.927.185 23.228.294 20.264 11.36z" fill="#fbc707" stroke-width=".265"/><path d="M19.59 104.97l11.596 43.176 11.545 43.19-11.303-19.948-11.36-20.263-.294-23.228z" fill="#fff200" stroke-width=".265"/><path d="M106.23 128.133l43.312-11.605 43.311-11.605-11.605 43.31-11.605 43.312-31.706-31.706z" fill="#f47216" stroke-width=".266"/><path d="M169.711 191.289l11.545-43.19 11.597-43.176-.185 22.927-.294 23.228-11.36 20.263z" fill="#f1471d" stroke-width=".265"/><path d="M192.853 104.923l-43.176 11.597-43.19 11.544 19.947-11.303 20.264-11.36 23.228-.293z" fill="#fbc707" stroke-width=".265"/><path d="M169.643 191.545l11.605 43.31 11.605 43.312-43.311-11.605-43.311-11.606 31.706-31.705z" fill="#f25c19" stroke-width=".266"/><path d="M106.487 255.025l43.19 11.544 43.176 11.598-22.927-.185-23.228-.294-20.264-11.36z" fill="#f68712" stroke-width=".265"/><path d="M192.853 278.167l-11.597-43.176-11.545-43.19 11.303 19.947 11.36 20.264.294 23.228z" fill="#f1471d" stroke-width=".265"/><path d="M106.211 254.976l-43.31 11.605-43.312 11.605 11.605-43.31L42.8 191.563l31.706 31.706z" fill="#f89c0e" stroke-width=".266"/><path d="M42.731 191.82l-11.545 43.19-11.597 43.176.185-22.927.294-23.228 11.36-20.263z" fill="#fff200" stroke-width=".265"/><path d="M19.59 278.186l43.175-11.597 43.19-11.544-19.947 11.303-20.264 11.36-23.228.293z" fill="#f25c19" stroke-width=".265"/></g></symbol><symbol viewBox="0 0 144 152" id="heroku" xmlns="http://www.w3.org/2000/svg"><path d="M118.68 13.279H26.865c-6.337 0-11.476 5.139-11.476 11.476V129.32c0 6.338 5.139 11.477 11.476 11.477h91.813c6.338 0 11.477-5.14 11.477-11.477V24.755c0-6.337-5.139-11.476-11.477-11.476zM44.08 121.669V96.165l14.346 12.752zm44.632 0v-38.08c-.063-2.976-1.496-6.551-7.97-6.551-12.966 0-27.51 6.52-27.654 6.586l-9.008 4.08V32.407h12.752v36.201c6.366-2.072 15.266-4.321 23.91-4.321 7.882 0 12.6 3.099 15.17 5.698 5.484 5.547 5.56 12.613 5.551 13.43v38.255zm3.188-68.54H79.149c5.011-6.576 8.158-13.496 9.564-20.723h12.751c-.86 7.243-3.796 14.187-9.563 20.722z" fill="#6963b9"/></symbol><symbol viewBox="0 0 24 24" id="hpp" xmlns="http://www.w3.org/2000/svg"><path d="M9.757 19.818H6.751v-5.882q0-2.381-1.737-2.381-.868 0-1.438.663-.56.662-.56 1.718v5.882H0V4.533h3.016v6.508h.037Q4.24 9.239 6.247 9.239q3.51 0 3.51 4.239z" stroke-width=".478" fill="#0277bd"/><path d="M13.073 11.448v2h-2v2h2v2h2v-2h2v-2h-2v-2zm7 0v2h-2v2h2v2h2v-2h2v-2h-2v-2z" fill="#0277bd"/></symbol><symbol viewBox="0 0 24 24" id="html" xmlns="http://www.w3.org/2000/svg"><path d="M12 17.56l4.07-1.13.55-6.1H9.38L9.2 8.3h7.6l.2-1.99H7l.56 6.01h6.89l-.23 2.58-2.22.6-2.22-.6-.14-1.66h-2l.29 3.19L12 17.56M4.07 3h15.86L18.5 19.2 12 21l-6.5-1.8L4.07 3z" fill="#e44d26"/></symbol><symbol viewBox="0 0 24 24" id="http" xmlns="http://www.w3.org/2000/svg"><path d="M16.046 13.784c.074-.613.13-1.225.13-1.856s-.056-1.244-.13-1.856h3.137c.148.594.241 1.215.241 1.856a7.65 7.65 0 0 1-.241 1.856m-4.78 5.16c.557-1.03.984-2.144 1.281-3.304h2.738a7.452 7.452 0 0 1-4.019 3.304m-.232-5.16H9.828a12.314 12.314 0 0 1-.149-1.856c0-.631.056-1.253.149-1.856h4.343c.084.603.149 1.225.149 1.856 0 .63-.065 1.243-.149 1.856M12 19.315c-.77-1.113-1.393-2.348-1.773-3.675h3.545c-.38 1.327-1.002 2.562-1.773 3.675m-3.712-11.1h-2.71a7.353 7.353 0 0 1 4.01-3.304c-.557 1.03-.975 2.144-1.3 3.304m-2.71 7.425h2.71c.325 1.16.743 2.274 1.3 3.304a7.433 7.433 0 0 1-4.01-3.304m-.761-1.856a7.65 7.65 0 0 1-.241-1.856c0-.64.093-1.262.241-1.856h3.137c-.074.612-.13 1.225-.13 1.856 0 .63.056 1.243.13 1.856m4.046-9.253c.77 1.114 1.393 2.357 1.773 3.684h-3.545c.38-1.327 1.002-2.57 1.773-3.684m6.422 3.684h-2.738a14.523 14.523 0 0 0-1.28-3.304 7.412 7.412 0 0 1 4.018 3.304m-6.423-5.568c-5.132 0-9.28 4.176-9.28 9.28a9.28 9.28 0 0 0 9.28 9.282 9.28 9.28 0 0 0 9.281-9.281A9.28 9.28 0 0 0 12 2.647z" fill="#e53935" stroke-width=".928"/></symbol><symbol viewBox="0 0 24 24" id="image" xmlns="http://www.w3.org/2000/svg"><path d="M13.009 9.202h5.368l-5.368-5.368v5.368M6.177 2.37h7.808l5.856 5.856v11.711a1.952 1.952 0 0 1-1.952 1.952H6.178a1.951 1.951 0 0 1-1.952-1.952V4.322c0-1.083.868-1.952 1.952-1.952m0 17.567h11.71V12.13l-3.903 3.903-1.952-1.951-5.856 5.855M8.13 9.202a1.952 1.952 0 0 0-1.952 1.952 1.952 1.952 0 0 0 1.952 1.952 1.952 1.952 0 0 0 1.952-1.952A1.952 1.952 0 0 0 8.13 9.202z" fill="#26a69a" stroke-width=".976"/></symbol><symbol viewBox="0 0 512 512" id="ionic" xmlns="http://www.w3.org/2000/svg"><g fill="#4f8ff7"><path d="M423.592 132.804A31.855 31.855 0 0 0 429 115c0-17.675-14.33-32-32-32a31.853 31.853 0 0 0-17.805 5.409C344.709 63.015 302.11 48 256 48 141.125 48 48 141.125 48 256c0 114.877 93.125 208 208 208 114.873 0 208-93.123 208-208 0-46.111-15.016-88.71-40.408-123.196zM391.83 391.832c-17.646 17.646-38.191 31.499-61.064 41.174-23.672 10.012-48.826 15.089-74.766 15.089-25.94 0-51.095-5.077-74.767-15.089-22.873-9.675-43.417-23.527-61.064-41.174s-31.5-38.191-41.174-61.064C68.982 307.096 63.905 281.94 63.905 256c0-25.94 5.077-51.095 15.089-74.767 9.674-22.873 23.527-43.417 41.174-61.064s38.191-31.5 61.064-41.174c23.673-10.013 48.828-15.09 74.768-15.09 25.939 0 51.094 5.077 74.766 15.089a191.221 191.221 0 0 1 37.802 21.327A31.853 31.853 0 0 0 365 115c0 17.675 14.327 32 32 32 5.293 0 10.28-1.293 14.678-3.568a191.085 191.085 0 0 1 21.327 37.801c10.013 23.672 15.09 48.827 15.09 74.767 0 25.939-5.077 51.096-15.09 74.768-9.675 22.873-23.527 43.418-41.175 61.064z"/><circle cx="256.003" cy="256" r="96"/></g></symbol><symbol viewBox="0 0 24 24" id="java" xmlns="http://www.w3.org/2000/svg"><path d="M2 21h18v-2H2M20 8h-2V5h2m0-2H4v10a4 4 0 0 0 4 4h6a4 4 0 0 0 4-4v-3h2a2 2 0 0 0 2-2V5a2 2 0 0 0-2-2z" fill="#f44336"/></symbol><symbol viewBox="0 0 24 24" id="javascript" xmlns="http://www.w3.org/2000/svg"><path d="M3 3h18v18H3V3m4.73 15.04c.4.85 1.19 1.55 2.54 1.55 1.5 0 2.53-.8 2.53-2.55v-5.78h-1.7V17c0 .86-.35 1.08-.9 1.08-.58 0-.82-.4-1.09-.87l-1.38.83m5.98-.18c.5.98 1.51 1.73 3.09 1.73 1.6 0 2.8-.83 2.8-2.36 0-1.41-.81-2.04-2.25-2.66l-.42-.18c-.73-.31-1.04-.52-1.04-1.02 0-.41.31-.73.81-.73.48 0 .8.21 1.09.73l1.31-.87c-.55-.96-1.33-1.33-2.4-1.33-1.51 0-2.48.96-2.48 2.23 0 1.38.81 2.03 2.03 2.55l.42.18c.78.34 1.24.55 1.24 1.13 0 .48-.45.83-1.15.83-.83 0-1.31-.43-1.67-1.03l-1.38.8z" fill="#ffca28"/></symbol><symbol viewBox="0 0 24 24" id="javascript-map" xmlns="http://www.w3.org/2000/svg"><path d="M18 8v2h2v10H10v-2H8v4h14V8h-4z" fill="#ffca28"/><path d="M2.444 2.506h14.135v14.136H2.444V2.506m3.714 11.811c.315.668.935 1.218 1.995 1.218 1.178 0 1.987-.629 1.987-2.003V8.993H8.805v4.508c0 .675-.275.848-.707.848-.455 0-.644-.314-.856-.683l-1.084.651m4.697-.14c.392.769 1.185 1.358 2.426 1.358 1.257 0 2.199-.652 2.199-1.854 0-1.107-.636-1.602-1.767-2.089l-.33-.141c-.573-.243-.816-.408-.816-.801 0-.322.243-.573.636-.573.377 0 .628.165.856.573l1.028-.683c-.432-.754-1.044-1.045-1.884-1.045-1.186 0-1.948.754-1.948 1.752 0 1.083.636 1.594 1.594 2.002l.33.141c.613.267.974.432.974.888 0 .377-.354.652-.903.652-.652 0-1.029-.338-1.312-.81l-1.083.63z" fill="#ffca28"/></symbol><symbol viewBox="0 0 180 180" id="jenkins" xmlns="http://www.w3.org/2000/svg"><defs><clipPath id="gia"><path transform="scale(1 -1)" fill="#37474f" d="M.899-144.42h144.42V0H.899z"/></clipPath></defs><g transform="matrix(1.0691 0 0 -1.0691 9.4 166.143)" clip-path="url(#gia)"><g fill-rule="evenodd"><path d="M107.96 30.661l-12.506-1.876-16.883-1.876-10.943-.312-10.629.312-8.13 2.502-7.19 7.815-5.628 15.945-1.25 3.44-7.504 2.5-4.377 7.191-3.126 10.317 3.44 9.067 8.128 2.814 6.565-3.127 3.127-6.878 3.752.626 1.25 1.563-1.25 7.19-.313 9.068 1.876 12.505-.074 7.143 5.701 9.114 10.005 7.19 17.508 7.504 19.383-2.814 16.883-12.193 7.817-12.505 5.002-9.067 1.25-22.51-3.752-19.384-6.877-17.195-6.566-9.066" fill="#f0d6b7"/><path d="M97.334-23.425l-44.709-1.876v-7.503l3.752-26.262-1.876-2.19-31.264 10.63-2.19 3.752-3.126 35.328-7.19 21.26-1.563 5.002 25.01 17.195 7.818 3.127 6.877-8.441 5.94-5.315 6.88-2.188 3.125-.938L68.57 1.899l2.814-3.44 7.19 2.502-5.002-9.693 27.2-12.818-3.439-1.876" fill="#335061"/><path d="M23.238 85.687l8.128 2.814 6.566-3.127 3.127-6.878 3.751.626.938 3.751-1.876 7.19 1.876 17.197-1.563 9.379 5.627 6.565 12.193 9.692-3.44 4.69-17.194-8.442-7.191-5.627-4.064-8.754-6.253-8.442-1.876-10.005 1.251-10.63" fill="#6d6b6d"/><path d="M36.055 115.07s4.69 11.567 23.448 17.195c18.759 5.628.938 4.065.938 4.065l-20.321-7.817-7.817-7.816-3.438-6.253 7.19.626M26.676 87.875s-6.566 21.886 18.446 25.012l-.938 3.752-17.195-4.065-5.003-16.257 1.251-10.63 3.439 2.188" fill="#dcd9d8"/></g><g fill="#f7e4cd"><path d="M36.681 58.799l4.094 3.966s1.847-.214 2.16-2.402c.312-2.19 1.25-21.886 14.693-32.516 1.227-.97-10.004 1.564-10.004 1.564L37.62 45.042M94.209 64.739s.729 9.477 3.28 8.748c2.553-.729 2.553-3.28 2.553-3.28s-6.198-4.01-5.833-5.468" fill-rule="evenodd"/><path d="M120.16 99.442s-5.153-1.088-5.628-5.628c-.474-4.54 5.628-.938 6.566-.625M82.327 99.129s-6.879-.938-6.879-5.314c0-4.378 7.817-4.065 10.005-2.19"/><g fill-rule="evenodd"><path d="M39.807 78.808s-11.881 7.191-13.131.312c-1.25-6.877-4.065-11.88 1.876-19.07l-4.064 1.25-3.752 9.691-1.25 9.38 7.19 7.504 8.129-.626 4.69-3.751.312-4.69M45.435 98.504s5.315 27.512 32.203 32.827c22.136 4.375 33.765-.938 38.142-5.94 0 0-19.696 23.447-38.455 16.257-18.759-7.191-32.514-20.322-32.202-28.762.532-14.377.313-14.382.313-14.382M117.97 122.27s-9.066.312-9.38-7.817c0 0 0-1.25.625-2.5 0 0 7.192 8.129 11.568 3.751"/><path d="M78.268 111.1s-1.56 12.477-12.199 5.223c-6.878-4.69-6.252-11.255-5.002-12.505s.91-3.77 1.862-2.04c.952 1.728.638 7.356 4.078 8.918 3.439 1.564 9.077 3.31 11.26.404"/></g></g><g fill="#49728b" fill-rule="evenodd"><path d="M48.874 26.597L19.486 13.466s12.193-48.46 5.94-63.467l-4.377 1.563-.313 18.446-8.128 35.015-3.44 9.692 30.639 20.633 9.067-8.753M51.896-.206l4.17-5.087v-18.76h-5.003s-.625 13.132-.625 14.696c0 1.563.624 7.19.624 7.19M52-26.866l-14.069-.625 4.065-2.813L52-31.868"/></g><g fill-rule="evenodd"><path d="M100.15-23.739l11.567.313 2.814-28.764-11.881-1.563-2.5 30.014" fill="#335061"/><path d="M103.27-23.739l17.508.938s7.19 18.133 7.19 19.07c0 .939 6.253 26.263 6.253 26.263l-14.069 14.694-2.813 2.501-7.504-7.503V3.148l-6.565-26.887" fill="#335061"/><path d="M111.09-21.55l-10.942-2.188 1.563-8.755c4.064-1.876 10.943 3.127 10.943 3.127M111.4 33.162l21.885-16.257.626 7.503-16.57 15.32-5.94-6.566" fill="#49728b"/><path d="M62.85-85.332l-6.473 26.266-3.22 19.38-.531 14.385 29.296 1.56 18.226.003-1.658-32.83 2.814-25.324-.312-4.69-23.76-1.876-14.382 3.126" fill="#fff"/><path d="M96.083-23.426s-1.563-32.515 3.127-55.65c0 0-9.38-5.94-23.136-7.503l26.262.938 3.126 1.875-3.752 51.273-.938 10.944" fill="#dcd9d8"/><path d="M115.06-49.691l12.193 3.44 23.135 1.25 3.44 10.629-6.254 18.446-7.19.938-10.005-3.127-9.599-4.686-5.095.935-3.972-1.56" fill="#fff"/><path d="M114.84-43.435s8.128 3.751 9.38 3.438L120.78-22.8l4.065 1.563s2.814-16.257 2.814-18.133c0 0 17.507-.938 19.07-.938 0 0 3.752 7.191 2.814 14.694l3.44-10.005.312-5.628-5.002-7.503-5.627-1.25-9.38.312-3.126 4.064-10.943-1.563-3.44-1.25" fill="#dcd9d8"/></g><path d="M102.56-21.241L95.682-3.733l-7.19 10.317s1.562 4.377 3.75 4.377h7.192l6.878-2.501-.625-11.568-3.127-18.134" fill="#fff"/><path d="M103.9-15.297S95.145 1.585 95.145 4.086c0 0 1.563 3.752 3.752 2.814 2.19-.938 6.879-3.439 6.879-3.439v5.94l-10.63 2.19-7.19-.939 12.193-28.763 2.5-.313" fill="#dcd9d8" fill-rule="evenodd"/><path d="M65.664 25.968l-8.661.942-8.13 2.501v-2.814l3.972-4.38 12.506-5.627" fill="#fff"/><path d="M51.689 25.031s9.693-4.065 12.819-3.127l.311-3.748-8.752 1.872-5.316 3.752.938 1.251" fill="#dcd9d8" fill-rule="evenodd"/><path d="M115.03 9.897c-5.305.156-10.098.786-14.294 1.97.285 1.72-.249 3.408.18 4.647 1.17.843 3.13.83 4.898 1.027-1.529.752-3.677 1.049-5.44.615-.042 1.194-.578 1.934-.902 2.868 2.982 1.064 10.024 8.044 13.984 5.732 1.887-1.099 2.689-7.377 2.835-10.43.122-2.533-.23-5.088-1.261-6.43" fill="#d33833" fill-rule="evenodd"/><path d="M115.03 9.897c-5.305.156-10.098.786-14.294 1.97.285 1.72-.249 3.408.18 4.647 1.17.843 3.13.83 4.898 1.027-1.529.752-3.677 1.049-5.44.615-.042 1.194-.578 1.934-.902 2.868 2.982 1.064 10.024 8.044 13.984 5.732 1.887-1.099 2.689-7.377 2.835-10.43.122-2.533-.23-5.088-1.261-6.43z" fill="none" stroke="#d33833" stroke-width="2"/><path d="M89.66 18.569c-.014-.401-.03-.806-.047-1.21-1.656-1.089-4.33-1.076-6.148-1.99 2.68-.117 4.79-.763 6.614-1.672l-.118-3.033c-3.036-2.078-5.81-5.173-9.384-7.122-1.69-.922-7.622-3.294-9.42-2.875-1.017.236-1.109 1.499-1.516 2.689-.866 2.548-2.861 6.605-3.035 10.44-.222 4.846-.71 12.967 4.51 11.969 4.213-.804 9.113-2.745 12.375-4.527 1.993-1.09 3.146-2.436 6.17-2.669" fill="#d33833" fill-rule="evenodd"/><path d="M89.66 18.569c-.014-.401-.03-.806-.047-1.21-1.656-1.089-4.33-1.076-6.148-1.99 2.68-.117 4.79-.763 6.614-1.672l-.118-3.033c-3.036-2.078-5.81-5.173-9.384-7.122-1.69-.922-7.622-3.294-9.42-2.875-1.017.236-1.109 1.499-1.516 2.689-.866 2.548-2.861 6.605-3.035 10.44-.222 4.846-.71 12.967 4.51 11.969 4.213-.804 9.113-2.745 12.375-4.527 1.993-1.09 3.146-2.436 6.17-2.669z" fill="none" stroke="#d33833" stroke-width="2"/><path d="M92.675 12.788c-.463 2.64-.999 3.393-.792 5.695 7.04 4.693 8.361-8.061.792-5.695" fill="#d33833" fill-rule="evenodd"/><path d="M92.675 12.788c-.463 2.64-.999 3.393-.792 5.695 7.04 4.693 8.361-8.061.792-5.695z" fill="none" stroke="#d33833" stroke-width="2"/><path d="M102.87 10.649s-2.19 3.127-.626 4.065c1.564.938 3.127 0 4.065 1.563s0 2.501.313 4.377 1.877 2.189 3.44 2.501c1.562.313 5.94.938 6.565-.625l-1.876 5.627-3.752 1.25-11.88-6.877-.626-3.44v-6.877M70.041.331c-.376 4.88-.773 9.752-1.215 14.626-.662 7.279 1.748 6.009 8.057 6.009.964 0 5.933-1.15 6.289-1.876 1.705-3.483-2.851-2.709 1.964-5.335 4.065-2.216 11.246 1.346 9.603 6.273-.919 1.095-4.789.341-6.176 1.06l-7.327 3.8c-3.108 1.612-10.29 3.962-13.603 1.709-8.395-5.71.53-19.974 3.524-25.93" fill="#ef3d3a" fill-rule="evenodd"/><g fill="#231f20" fill-rule="evenodd"><path d="M78.268 111.1c-8.521 1.985-12.755-3.566-15.338-9.323-2.306.559-1.389 3.695-.806 5.294 1.525 4.194 7.672 9.778 12.694 9.02 2.161-.325 5.086-2.301 3.45-4.99M119.79 101.4l.404-.016c1.926-4 3.593-8.238 6.022-11.769-1.628-3.79-12.322-7.144-12.157-.338 2.313 1.01 6.305.206 8.356 1.497-1.186 3.254-2.897 6.024-2.625 10.626M82.63 101.29c1.827-3.35 2.422-6.868 5.019-9.4 1.17-1.14 3.444-2.529 2.316-5.698-.263-.747-2.189-2.414-3.3-2.741-4.06-1.2-13.521-.248-10.317 4.814 3.358-.157 7.871-2.18 10.38.257-1.927 3.081-5.363 9.177-4.098 12.768M118.26 67.253c-6.113-3.927-12.93-8.197-22.947-7.207-2.14 1.86-2.956 6.002-.877 8.737 1.082-1.861.402-5.284 3.419-5.799 5.684-.972 12.299 3.477 16.387 5.032 2.535 4.275-.219 5.847-2.503 8.597-4.675 5.636-10.947 12.622-10.72 21.06 1.89 1.37 2.053-2.092 2.325-2.722 2.44-5.714 8.585-13.021 13.07-17.912 1.1-1.205 2.914-2.36 3.115-3.157.582-2.315-1.513-5.09-1.27-6.63M37.668 71.387c-1.916 1.094-2.372 5.91-4.622 6.048-3.215.195-2.629-6.25-2.616-10.018-2.213 2.009-2.602 8.194-.976 11.37-1.853.91-2.68-1.003-3.708-1.677 1.32 9.595 14.036 4.45 11.922-5.723M122.15 63.257c-2.846-5.417-6.871-11.382-15.222-11.555-.17 1.75-.3 4.411.009 5.464 6.384.614 10.325 3.863 15.212 6.091M82.149 59.745c5.326-2.8 15.114-3.102 22.353-2.89.388-1.586.379-3.545.394-5.48-9.305-.463-20.307 1.84-22.747 8.37M81.136 54.523c3.683-9.247 16.341-8.182 27.016-7.927-.47-1.2-1.489-2.62-2.755-3.132-3.42-1.392-12.855-2.448-17.604.074-3.011 1.601-4.946 5.219-6.596 7.34-.797 1.024-4.765 3.64-.06 3.645"/></g><path d="M117.82 3.516c-4.322-7.402-8.457-15.005-13.585-21.534 2.15 6.32 3.07 16.9 3.394 24.965 4.498 2.105 8.349-.474 10.191-3.43" fill="#81b0c4" fill-rule="evenodd"/><g fill="#231f20" fill-rule="evenodd"><path d="M141.07-23.089c-4.839-.969-8.239-5.671-12.959-5.37 2.594 3.658 7.14 5.2 12.959 5.37M143.21-30.661c-3.944-.417-8.576-1.055-12.577-.726 1.894 2.892 9.19 1.894 12.577.726M144.58-37.19c-4.433-.096-9.942-.008-14.155.346 2.492 2.677 11.28.993 14.155-.346"/></g><g fill-rule="evenodd"><path d="M109.48-55.057c.636-5.567 2.843-11.207 2.566-17.304-2.45-.827-3.858-1.55-7.142-1.545-.232 5.181-.925 13.102-.718 18.041 1.615-.107 3.997 1.154 5.294.808" fill="#dcd9d8"/><path d="M102.33 26.985c-2.226-1.453-4.121-3.267-6.259-4.818-4.74-.235-7.327.328-10.81 3.05.057.219.407.121.42.39 5.075-2.262 11.524.92 16.648 1.378" fill="#f0d6b7"/><path d="M75.694-7.603c1.394 6.04 6.857 9.17 11.817 12.497 5.12-6.498 8.234-14.855 11.663-22.92-8.102 2.443-16.38 6.406-23.481 10.423" fill="#81b0c4"/><path d="M104.18-55.865c-.207-4.94.486-12.86.718-18.041 3.283-.004 4.691.718 7.142 1.545.276 6.096-1.93 11.737-2.566 17.304-1.298.346-3.679-.914-5.294-.808zm-51.13 28.09c2.165-19.906 5.301-36.639 11.054-54.266 12.766-3.876 28.157-4.214 39.441-.716-2.072 9.948-1.167 22.06-2.378 32.677-.912 7.98-.447 16.009-1.698 24.15-13.673 2.844-33 .665-46.418-1.845zm49.651 1.72c-.115-8.549.383-16.982 1.036-25.542 3.282.493 5.51.822 8.56 1.49-.99 8.241-.869 17.514-2.886 24.804-2.332-.023-4.385.027-6.71-.752zm16.653 1.378c-1.558.357-3.372.014-4.86-.015.7-6.969 2.397-14.659 2.995-21.974 2.342-.073 3.593 1.032 5.52 1.403.102 6.421-.562 15.268-3.655 20.586zm25.215-23.038c4.882 1.186 7.952 7.165 6.586 13.305-.916 4.127-2.548 11.898-4.295 14.538-1.29 1.953-4.79 4.51-7.584 2.72-4.545-2.91-12.552-3.755-15.867-7.278 1.662-5.534 2.178-13.135 2.864-20.146 5.678-.354 12.665 1.562 17.387-.471-3.297-1.068-7.575-1.077-10.423-2.633 2.328-1.125 7.778-.897 11.332-.035zM99.17-18.025c-3.43 8.063-6.543 16.42-11.663 22.918-4.96-3.327-10.423-6.456-11.817-12.497 7.1-4.017 15.379-7.98 23.481-10.422zm8.453 24.971c-.325-8.065-1.245-18.644-3.395-24.965 5.128 6.53 9.263 14.132 13.585 21.534-1.842 2.957-5.693 5.536-10.19 3.431zm-9.582 3.405c-1.943.21-3.592-2.233-6.117-1.177-.58-.64-1.105-1.333-1.695-1.958 5.579-6.723 8.114-16.262 12.423-24.163 2.312 7.59 2.045 15.904 2.555 24.188-3.177-.201-4.94 2.873-7.166 3.11zm-6.161 8.132c-.208-2.303.328-3.056.791-5.695 7.57-2.367 6.248 10.388-.791 5.695zm-8.394 2.755c-3.261 1.782-8.161 3.723-12.374 4.527-5.222.999-4.732-7.123-4.51-11.968.173-3.836 2.168-7.893 3.035-10.441.406-1.19.498-2.453 1.515-2.69 1.798-.418 7.73 1.954 9.42 2.875 3.575 1.95 6.348 5.045 9.384 7.123.04 1.011.078 2.021.119 3.032-1.826.91-3.935 1.555-6.615 1.673 1.818.914 4.492.901 6.148 1.989.016.405.033.81.047 1.21-3.024.234-4.176 1.58-6.17 2.67zm-31.152 5.659c-2.707-2.748 7.592-6.494 10.871-6.696-.018 1.739.991 3.378.788 4.626-3.895.684-9.013.232-11.66 2.07zm33.345-1.29c-.013-.27-.363-.172-.42-.39 3.482-2.722 6.07-3.285 10.81-3.05 2.137 1.551 4.033 3.365 6.259 4.818-5.124-.458-11.574-3.64-16.648-1.379zm30.606-9.282c-.146 3.053-.948 9.332-2.835 10.431-3.961 2.312-11.002-4.668-13.984-5.732.324-.934.86-1.674.901-2.868 1.764.434 3.912.137 5.44-.615-1.767-.198-3.727-.185-4.897-1.027-.429-1.239.105-2.927-.18-4.647 4.196-1.184 8.989-1.814 14.294-1.97 1.032 1.341 1.383 3.896 1.261 6.429zM47.777 24.24c-.85.606-6.6 8.087-7.388 7.777-10.405-4.103-20.134-11.199-28.828-17.91 8.29-17.787 11.635-39.579 12.227-60.582 9.496-4.441 17.836-10.844 30.722-11.512-1.491 10.55-2.852 19.962-3.699 29.895-3.237 1.365-7.882-.062-10.913.423-.025 3.651 4.628 1.6 5.015 4.054.292 1.858-2.56 1.998-1.631 4.923 2.368-.861 3.612-2.763 6.138-3.477 2.309 5.05-.032 13.985.3 18.205.064.792.397 4.39 2.172 3.759 1.57-.559-.09-9.569.082-13.563.157-3.68-.444-7.242 1.046-9.552a355.817 355.817 0 0 0 38.576 3.16c-2.964 1.272-6.485 2.475-10.345 4.651-2.093 1.18-8.69 3.635-9.293 5.622-.964 3.167 2.528 4.855 3.125 7.57-6.285-3.428-7.511 3.286-8.998 8.042-1.347 4.308-2.114 7.526-2.445 10.01-5.414 2.581-11.203 5.195-15.863 8.505zm63.009 6.872c8.67 4.204 10.232-15.711 6.834-22.127.525-1.914 2.331-2.646 3.069-4.366-4.838-8.667-10.211-16.756-15.148-25.32 3.672 2.286 8.917.409 13.238 2.12 1.58.624 2.722 4.24 3.918 7.133 3.29 7.958 6.743 17.99 8.28 25.586.346 1.73 1.292 5.5 1.08 7.04-.378 2.758-4.12 4.803-6.022 6.508-3.506 3.15-5.714 5.921-9.371 8.866-1.483-2.189-4.666-3.66-5.878-5.44zM27.95 107.99c-4.13-4.545-3.266-13.062-2.766-19.121 7.467 4.697 17.377-.372 17.284-8.36 3.565.094 1.332 4.452.687 7.259-2.107 9.169 3.55 19.13.256 27.516-6.395-.485-11.649-3.097-15.46-7.294zm29.558 26.38c-9.352-2.65-21.337-9.446-25.18-17.847 2.976.432 5.041 1.933 7.977 2.119 1.11.072 2.563-.466 3.838-.148 2.54.63 4.685 6.327 6.602 8.447 1.868 2.07 4.114 2.954 5.651 4.841.988.477 2.448.444 2.504 1.927-.428.457-.879.806-1.392.66zm48.681-2.493c-9.707 5.477-26.136 9.596-36.462 4.449-8.331-4.155-19.593-11.027-23.433-19.737 3.587-8.405-1.062-16.106-1.36-24.64-.157-4.54 2.139-8.504 2.315-13.446-1.228-2.025-4.978-2.275-7.574-2.136-.873 4.372-2.403 9.287-6.906 9.78-6.371.697-11.03-4.576-11.319-10.085-.342-6.48 4.978-17.22 12.517-16.475 2.913.287 3.629 3.207 6.802 3.177 1.72-3.432-2.653-4.51-3.103-6.964-.117-.634.363-3.112.642-4.274 1.37-5.658 4.422-12.982 7.427-17.29 3.814-5.464 11.307-6.288 19.37-6.823 1.44 3.101 6.743 2.846 10.2 2.035-4.143 1.64-7.993 5.617-11.185 9.137-3.665 4.039-7.378 8.371-7.566 13.65 6.927-9.61 12.65-18.003 25.246-22.23 9.53-3.196 20.662 1.465 27.986 6.608 3.039 2.137 4.853 5.529 7.013 8.634 8.082 11.626 11.854 28.219 11.024 44.303-.342 6.633-.327 13.244-2.552 17.706-2.326 4.666-10.193 8.84-14.8 4.62-.853 4.537 3.83 7.344 9.331 5.71-3.922 5.063-8.039 11.145-13.614 14.29zm18.084-149.66c7.585 3.77 21.757 10.149 26.512-.014 1.755-3.746 3.814-10.079 4.723-13.946 1.284-5.456-1.392-16.923-7-18.754-4.953-1.617-10.733-1.518-16.7-.32-.702.585-1.484 1.603-2.03 2.665-4.261.165-8.25-.229-11.615-1.98.319-3.15-1.812-3.656-3.81-4.305-1.48-5.872 2.963-13.541 1.9-18.896-.76-3.815-5.453-4.405-8.902-5.118-.113-2.12.15-3.89.386-5.683-.789-2.907-4.327-4.561-7.679-4.967-11.029-1.326-27.775-1.922-38.384 1.893-2.96 7.261-5.292 16.093-7.758 24.384-10.346-1.105-18.715 4.464-26.603 8.113-2.731 1.266-6.51 1.964-7.53 4.138-.99 2.105-.584 6.14-.83 9.95-.625 9.733-1.16 19.12-3.73 29.086-1.154 4.472-3.165 8.418-4.568 12.727C9.358 5.184 7.092 10.12 6.5 14.1c-.877 5.903 4.681 6.232 8.235 8.79 5.494 3.954 9.806 6.142 15.756 9.711 1.762 1.057 7.077 3.733 7.681 4.966 1.202 2.443-2.062 5.888-2.935 7.803-1.38 3.03-2.1 5.602-2.298 8.59-4.992.789-8.775 3.76-11.06 7.109-3.781 5.543-6.403 15.798-3.132 23.599.257.614 1.536 1.822 1.725 2.765.372 1.858-.7 4.329-.768 6.305-.343 10.14 1.716 18.875 8.541 21.932 2.771 11.038 12.688 14.71 22.032 20.195 3.493 2.05 7.343 3.36 11.32 4.824 14.263 5.25 36.15 4.261 47.987-4.692 5.02-3.797 13.044-11.813 15.914-17.617 7.58-15.323 7.042-40.931 1.74-59.571-.712-2.503-1.746-6.181-3.19-9.187-1.006-2.1-4.134-6.3-3.754-8.153.391-1.916 7.132-7.034 8.577-8.428 2.603-2.51 7.548-5.843 7.948-9.012.43-3.372-1.485-7.984-2.456-11.238-3.245-10.858-6.412-20.895-10.091-30.576" fill="#231f20"/><path d="M73.674 57.38c.411.548 2.674 1.38 5.84-.144 0 0-3.752-.626-3.44-6.881l-1.564.313s-1.615 5.672-.836 6.712" fill="#f7e4cd"/><path d="M101.09 3.617a1.72 1.72 0 1 0-3.44.001 1.72 1.72 0 0 0 3.44-.001M102.81-4.355a1.72 1.72 0 1 0-3.44 0 1.72 1.72 0 0 0 3.44 0" fill="#1d1919"/></g><g><rect transform="matrix(.8 0 0 -.8 0 144)" x="16.854" y="177.38" width="70.412" height="4.12" rx=".983" ry=".983"/><rect transform="scale(1 -1)" x="78.502" y="-2.097" width="50.037" height="3.296" rx=".786" ry=".786"/><rect transform="scale(1 -1)" x="13.483" y="-3.697" width="54.831" height="3.296" rx=".786" ry=".786"/><rect transform="scale(1 -1)" x="83.296" y="-3.697" width="45.243" height="3.296" rx=".786" ry=".786"/></g></g></symbol><symbol viewBox="0 0 24 24" id="json" xmlns="http://www.w3.org/2000/svg"><path d="M5 3h2v2H5v5a2 2 0 0 1-2 2 2 2 0 0 1 2 2v5h2v2H5c-1.07-.27-2-.9-2-2v-4a2 2 0 0 0-2-2H0v-2h1a2 2 0 0 0 2-2V5a2 2 0 0 1 2-2m14 0a2 2 0 0 1 2 2v4a2 2 0 0 0 2 2h1v2h-1a2 2 0 0 0-2 2v4a2 2 0 0 1-2 2h-2v-2h2v-5a2 2 0 0 1 2-2 2 2 0 0 1-2-2V5h-2V3h2m-7 12a1 1 0 0 1 1 1 1 1 0 0 1-1 1 1 1 0 0 1-1-1 1 1 0 0 1 1-1m-4 0a1 1 0 0 1 1 1 1 1 0 0 1-1 1 1 1 0 0 1-1-1 1 1 0 0 1 1-1m8 0a1 1 0 0 1 1 1 1 1 0 0 1-1 1 1 1 0 0 1-1-1 1 1 0 0 1 1-1z" fill="#fbc02d"/></symbol><symbol viewBox="0 0 50 50" id="julia" xmlns="http://www.w3.org/2000/svg"><g transform="translate(0 -247)" stroke-width="5.673"><circle cx="13.497" cy="281.632" r="9.555" fill="#bc342d"/><circle cx="36.081" cy="281.632" r="9.555" fill="#864e9f"/><circle cx="24.722" cy="262.389" r="9.555" fill="#328a22"/></g></symbol><symbol viewBox="0 0 64 64" id="karma" xmlns="http://www.w3.org/2000/svg"><g transform="translate(0 -233)"><path d="M38.556 288.413l-20.29-26.687 9.532-7.246 20.29 26.686h-.001.002l5.527 7.247z" fill="#359b8b" stroke-width=".173"/><path d="M35.681 241.172L24.92 255.327v-14.13H12.947v13.817l7.84 33.235h4.132v-13.147l.003.003 20.29-26.686-.008-.006 5.504-7.24H35.84v.12z" fill="#3cbeae" stroke-width=".206"/></g></symbol><symbol viewBox="0 0 24 24" id="key" xmlns="http://www.w3.org/2000/svg"><path d="M7 14a2 2 0 0 1-2-2 2 2 0 0 1 2-2 2 2 0 0 1 2 2 2 2 0 0 1-2 2m5.65-4A5.99 5.99 0 0 0 7 6a6 6 0 0 0-6 6 6 6 0 0 0 6 6 5.99 5.99 0 0 0 5.65-4H17v4h4v-4h2v-4H12.65z" fill="#26a69a"/></symbol><symbol viewBox="0 0 24 24" id="kivy" xmlns="http://www.w3.org/2000/svg"><g transform="matrix(1.89 0 0 1.89 -12.157 -11.429)" fill="#90a4ae"><path d="M7.026 8.63v4.474l1.928-1.928a.437.437 0 0 0 0-.619zM9.38 16.072v-4.474l-1.927 1.927a.437.437 0 0 0 0 .62zM18.576 10.412l-5.346.564-.017.018 2.39 2.39zM9.922 8.502s.023 3.304-.003 4.452c-.02.856.371 1.114.746 1.507.538.564 1.599 1.57 1.599 1.57a.53.53 0 0 0 .75 0l1.843-1.844a.53.53 0 0 0 0-.75z"/></g></symbol><symbol viewBox="0 0 24 24" id="kl" xmlns="http://www.w3.org/2000/svg"><defs><style>.a{fill:#3aaae1}.b{fill:#fdfeff}</style></defs><title>kl</title><path d="M12.033 1.737c-.25-.003-.5.11-.729.337C8.225 5.15 5.15 8.227 2.078 11.31c-.144.144-.229.346-.341.521v.41c.16.223.294.474.485.666a3259.51 3259.51 0 0 0 8.936 8.937c.193.192.443.325.666.486h.41c.205-.142.436-.256.609-.428 3.046-3.041 6.09-6.083 9.133-9.127.47-.47.472-1.005.006-1.472l-9.218-9.217c-.23-.23-.48-.347-.731-.35zm-1.062 4.545l1.386.832c.702.422 1.403.846 2.109 1.262a.544.544 0 0 1 .04.026l.016.013.017.013c.061.056.089.123.088.224a510.281 510.281 0 0 0 0 3.794.463.463 0 0 1-.007.094c-.015.069-.054.103-.142.109a.464.464 0 0 1-.044.002c-.045-.002-.09-.002-.136-.003-.323-.006-.648-.001-.998-.001v-.527-1.34-.671-.003l.004-.668c0-.147-.039-.231-.17-.308-.893-.528-1.78-1.066-2.67-1.6-.051-.03-.101-.065-.173-.111l.001-.003h-.001zm.362 3.39c.068-.003.119.043.173.138.085.148.174.293.264.44l.015.025c.096.154.194.31.292.47l-1.915 1.176c-.337.207-.673.417-1.014.617-.113.067-.154.143-.154.277.01.977.01 1.955.014 2.932V16H7.7V16h-.002c-.004-.053-.014-.112-.014-.17-.005-1.25-.006-2.501-.015-3.751 0-.142.045-.222.164-.294a467.13 467.13 0 0 0 3.353-2.054l.016-.01a.606.606 0 0 1 .032-.017l.016-.008a.308.308 0 0 1 .033-.013l.012-.004a.157.157 0 0 1 .028-.005l.01-.001zm5.677 3.126l.314.54.346.594v.001c-.158.094-.298.178-.438.259l-3.097 1.798c-.106.062-.189.071-.3.01l-.893-.496-1.524-.843-.895-.493c-.035-.02-.068-.044-.129-.085h.001l.137-.25.495-.902 1.446.795c.442.243.886.483 1.323.734.121.07.212.072.334 0 .894-.525 1.792-1.043 2.689-1.563.057-.034.118-.061.191-.1z" fill="#29b6f6" stroke-width=".041"/></symbol><symbol viewBox="0 0 24 24" id="kotlin" xmlns="http://www.w3.org/2000/svg"><defs><linearGradient id="gpb"><stop offset="0" stop-color="#cb55c0"/><stop offset="1" stop-color="#f28e0e"/></linearGradient><linearGradient id="gpa"><stop offset="0" stop-color="#0296d8"/><stop offset="1" stop-color="#8371d9"/></linearGradient><linearGradient xlink:href="#gpa" id="gpc" x1="1.725" y1="22.67" x2="22.185" y2="1.982" gradientUnits="userSpaceOnUse" gradientTransform="translate(1.638 1.155) scale(.89324)"/><linearGradient xlink:href="#gpb" id="gpd" x1="1.869" y1="22.382" x2="22.798" y2="3.377" gradientUnits="userSpaceOnUse" gradientTransform="translate(1.638 1.155) scale(.89324)"/></defs><path d="M3.307 3.003v18.048h18.05v-.03L16.88 16.51l-4.48-4.515 4.48-4.515 4.443-4.477H3.307z" fill="url(#gpc)"/><path d="M12.538 3.003l-9.23 9.23v8.818h.083l9.032-9.032-.025-.024 4.48-4.515 4.444-4.477h-8.784z" fill="url(#gpd)"/></symbol><symbol viewBox="0 0 240 240" id="laravel" xmlns="http://www.w3.org/2000/svg"><path d="M216.05 119.036c-1.433.343-24.945 6.673-24.945 6.673l-19.227-28.622c-.537-.828-.99-1.656.359-1.849 1.345-.196 23.195-4.477 24.182-4.723.99-.245 1.837-.536 3.053 1.267 1.21 1.8 17.836 24.626 18.464 25.506.627.877-.447 1.41-1.883 1.748m-4.101 49.326c.588 1.003 1.176 1.64-.67 2.367-1.843.73-62.243 22.847-63.418 23.39-1.173.546-2.092.73-3.607-1.637-1.51-2.362-21.16-39.264-21.16-39.264l64.03-18.075c1.876-.644 2.317-.405 3.103.822 1.074 1.68 21.143 31.403 21.726 32.4m-103.7-21.087c-.78.202-37.566 9.733-39.525 10.22-1.965.485-1.965.246-2.188-.49-.226-.727-43.728-98.053-44.333-99.271-.605-1.214-.574-2.177 0-2.177.571 0 34.734-3.313 35.944-3.383 1.207-.07 1.08.205 1.526 1.033l49.025 91.818c.84 1.58 1.239 1.81-.452 2.248m94.588-59.77c-3.5-4.58-5.2-3.751-7.357-3.41-2.154.336-27.277 4.915-30.194 5.449-2.918.536-4.758 1.803-2.963 4.53 1.597 2.422 18.113 27.824 21.751 33.42l-65.663 17.066L66.18 49.832c-2.075-3.342-2.507-4.514-7.236-4.28-4.735.23-40.969 3.495-43.55 3.731-2.58.233-5.416 1.479-2.835 8.09 2.583 6.612 43.734 102.82 44.88 105.62 1.149 2.803 4.128 7.345 11.11 5.527 7.157-1.871 31.969-8.894 45.52-12.742 7.163 14.07 21.77 42.619 24.473 46.707 3.607 5.459 6.089 4.56 11.626 2.738 4.325-1.42 67.65-26.129 70.502-27.4 2.855-1.273 4.613-2.184 2.685-5.275-1.419-2.28-18.124-26.558-26.876-39.26 5.993-1.733 27.305-7.888 29.575-8.557 2.646-.779 3.008-2.19 1.572-3.94-1.436-1.755-21.293-28.72-24.79-33.296z" fill="#ff5722" stroke="#ff5722" stroke-width="8.852" fill-rule="evenodd"/></symbol><symbol viewBox="0 0 24 24" id="less" xmlns="http://www.w3.org/2000/svg"><path d="M13.696 2.999V5h2.002v5a2 2 0 0 0 1.999 2 2 2 0 0 0-2 2v5h-2v2h2a2 2 0 0 0 2-2v-4a2 2 0 0 1 2-2h1V11h-1a2 2 0 0 1-2-2V5a2 2 0 0 0-2-2.001zm-.03 12.766v.47a1 1 0 0 0 .03-.236 1 1 0 0 0-.03-.234zM10.566 21v-2.001H8.565v-5a2 2 0 0 0-2-2 2 2 0 0 0 2-2V5h2.001v-2H8.565a2 2 0 0 0-2 2v4a2 2 0 0 1-2 2h-.999V13h1a2 2 0 0 1 2 2v3.999A2 2 0 0 0 8.564 21zm.03-12.766v-.47a1 1 0 0 0-.03.236 1 1 0 0 0 .03.234z" fill="#42a5f5"/></symbol><symbol viewBox="0 0 24 24" id="lib" xmlns="http://www.w3.org/2000/svg"><path d="M19 7H9V5h10m-4 10H9v-2h6m4-2H9V9h10m1-7H8a2 2 0 0 0-2 2v12a2 2 0 0 0 2 2h12a2 2 0 0 0 2-2V4a2 2 0 0 0-2-2M4 6H2v14a2 2 0 0 0 2 2h14v-2H4V6z" fill="#8bc34a"/></symbol><symbol viewBox="0 0 40 40" id="livescript" xmlns="http://www.w3.org/2000/svg"><g transform="translate(0 -257)" fill="#317eac"><path stroke-width="3.299" d="M5.419 260.18h3.685v34.207H5.419z"/><path stroke-width="3.299" d="M37.074 288.197v3.685H2.867v-3.685z"/><path stroke-width="2.894" d="M29.612 265.658l2.004 2.005L7.428 291.85l-2.004-2.005z"/><path stroke-width="2.325" d="M10.73 262.471h2.835v22.08H10.73z"/><path stroke-width="2.063" d="M15.36 262.519h2.835v17.382H15.36z"/><path stroke-width="1.77" d="M19.99 262.471h2.835v12.802H19.99z"/><path stroke-width="1.422" d="M24.526 262.491h2.835v8.254h-2.835z"/><path stroke-width="1.128" d="M28.783 262.463h2.835v5.197h-2.835z"/><path stroke-width="2.325" d="M34.801 286.545v-2.835h-22.08v2.835z"/><path stroke-width="2.063" d="M34.753 281.914v-2.835H17.371v2.835z"/><path stroke-width="1.77" d="M34.801 277.284v-2.835H21.999v2.835z"/><path stroke-width="1.422" d="M34.781 272.749v-2.835h-8.254v2.835z"/><path stroke-width="1.128" d="M34.809 268.492v-2.835h-5.197v2.835z"/></g></symbol><symbol viewBox="0 0 24 24" id="lock" xmlns="http://www.w3.org/2000/svg"><path d="M12 17a2 2 0 0 0 2-2 2 2 0 0 0-2-2 2 2 0 0 0-2 2 2 2 0 0 0 2 2m6-9a2 2 0 0 1 2 2v10a2 2 0 0 1-2 2H6a2 2 0 0 1-2-2V10a2 2 0 0 1 2-2h1V6a5 5 0 0 1 5-5 5 5 0 0 1 5 5v2h1m-6-5a3 3 0 0 0-3 3v2h6V6a3 3 0 0 0-3-3z" fill="#ffd54f"/></symbol><symbol viewBox="0 0 24 24" id="lua" xmlns="http://www.w3.org/2000/svg"><circle cx="12.203" cy="12.102" r="10.322" fill="none" stroke="#42a5f5"/><path d="M12.33 5.746a6.483 6.381 0 0 0-6.482 6.381 6.483 6.381 0 0 0 6.482 6.38 6.483 6.381 0 0 0 6.484-6.38 6.483 6.381 0 0 0-6.484-6.38zm1.86 1.916a2.329 2.292 0 0 1 2.33 2.293 2.329 2.292 0 0 1-2.33 2.291 2.329 2.292 0 0 1-2.329-2.29 2.329 2.292 0 0 1 2.328-2.294z" fill="#42a5f5" fill-rule="evenodd"/><ellipse cy="4.615" cx="19.631" rx="2.329" ry="2.292" fill="#42a5f5" fill-rule="evenodd"/></symbol><symbol viewBox="0 0 24 24" id="markdown" xmlns="http://www.w3.org/2000/svg"><path d="M2 16V8h2l3 3 3-3h2v8h-2v-5.17l-3 3-3-3V16H2m14-8h3v4h2.5l-4 4.5-4-4.5H16V8z" fill="#42a5f5"/></symbol><symbol viewBox="0 0 24 24" preserveAspectRatio="xMidYMid" id="markojs" xmlns="http://www.w3.org/2000/svg"><g transform="translate(0 -120.96)" stroke-width=".984"><path d="M4.002 126.482c-.655 1.07-1.32 2.14-1.976 3.21-.655 1.06-1.308 2.142-1.963 3.212l.002.002-.002.002c.655 1.07 1.308 2.15 1.963 3.211.655 1.07 1.32 2.141 1.976 3.211h3.33c-.664-1.07-1.318-2.14-1.974-3.21-.653-1.069-1.307-2.145-1.961-3.214.654-1.068 1.308-2.146 1.961-3.215a601.93 601.93 0 0 1 1.974-3.209z" fill="#2196f3"/><path d="M3.999 126.482l-.002.002c.655 1.07 1.31 2.15 1.964 3.212.655 1.07 1.32 2.14 1.974 3.21h3.331c-.664-1.07-1.319-2.14-1.974-3.21-.653-1.068-1.306-2.146-1.96-3.214z" fill="#26a69a"/><path d="M15.203 126.482l.002.002c-.655 1.07-1.31 2.15-1.965 3.212-.655 1.07-1.319 2.14-1.974 3.21h-3.33c.664-1.07 1.318-2.14 1.973-3.21.654-1.069 1.307-2.146 1.961-3.214z" fill="#8bc34a"/><path d="M11.874 126.484c.664 1.07 1.318 2.14 1.974 3.21.653 1.068 1.307 2.146 1.961 3.214-.654 1.069-1.308 2.145-1.961 3.213-.656 1.07-1.31 2.14-1.974 3.21h3.33c.655-1.07 1.319-2.14 1.974-3.21.655-1.06 1.31-2.14 1.966-3.21l-.002-.003.002-.002c-.656-1.07-1.311-2.152-1.966-3.213-.655-1.07-1.319-2.138-1.974-3.209z" fill="#ffc107"/><path d="M16.74 126.482c.665 1.07 1.319 2.14 1.974 3.21.654 1.068 1.306 2.146 1.96 3.214-.654 1.069-1.306 2.145-1.96 3.213-.655 1.07-1.31 2.141-1.974 3.211h3.33c.656-1.07 1.32-2.14 1.974-3.21.655-1.062 1.31-2.141 1.966-3.212l-.002-.002.002-.002c-.655-1.07-1.31-2.152-1.966-3.213-.655-1.07-1.318-2.138-1.973-3.209z" fill="#f44336"/></g></symbol><symbol viewBox="0 0 23 24" id="mathematica" xmlns="http://www.w3.org/2000/svg"><path d="M11.512 1.523l-.073.025-.46.794-.454.763-1.217 2.09H9.29L5.435 3.5l-.1-.047h-.018v.092l.025.163v.086l.132 1.226v.082l.032.252v.082l.22 2.137v.075l.018.082v.06l-2.348.507-.04.015-.457.1-.025.01h-.042l-1.096.244-.04.007-.17.036v.082l.018.01 1.859 2.086.053.052.114.132.804.909v.005l-.053.05-.22.257-2.564 2.875-.01.007v.082l.071.006.295.075 1.697.366v.006l2.139.472h.015v.047l-.036.252v.08l-.046.412v.082l-.036.244v.082l-.045.412v.08l-.05.41v.08l-.036.244v.082l-.046.412v.082l-.05.407v.082l-.032.248V20l-.05.407v.104h.037l3.642-1.6.294-.134h.018l.177.312.539.911.015.032.854 1.465.16.262.404.695.007.022h.092l.005-.022.017-.025.56-.947.014-.042.6-1.033.316-.539.644-1.091.05.013 3.906 1.721h.035v-.085l-.138-1.32v-.082l-.032-.244v-.082l-.035-.245v-.085l-.033-.244v-.081l-.032-.245v-.082l-.032-.244v-.085l-.035-.245v-.082l-.032-.245v-.082l-.033-.244v-.085l-.025-.17v-.053l1.632-.354.043-.008.458-.107h.028v-.01l.23-.05.03-.01h.042l.382-.09.025-.01h.043l.194-.05h.033l1.015-.23.07-.007v-.064l-.015-.013-1.19-1.342-.028-.028-.197-.22-1.428-1.604v-.006l.295-.323.4-.457 2.148-2.408.015-.01v-.065l-.035-.008-1.288-.28-.372-.084-.047-.01-2.481-.544v-.045l.432-4.265v-.02h-.042l-.302.135-.01.014h-.025l-3.307 1.45-.297.135h-.015l-2.028-3.483-.099-.145-.014-.045zm-.001 1.114l1.365 2.323.34.592-.008.025-1.18 1.511-.517.66-.012-.01-.258-.335-.04-.05-1.397-1.787.03-.063 1.378-2.365.287-.491zm4.908 2.039l-.007.025-.168.225-.538.066zm-9.817.004l.053.02.677.3h-.499l-.224-.3zM16.947 5l-.123 1.248-.113-.928.226-.307zm-9.26.156l.053.024.705.309-.757-.175zm7.388.116l.02.168-1.318.403.003-.003.16-.071 1.015-.444zM9.669 6.388l.944 1.204v.01L9.483 7.2zm3.55.172l.21.682-.234.084-.089.022-.702.255.008-.022.776-.982zm-5 .836l.986.356.898.312.048.02 1.054.373.011 3.086-.362-.117-.67-.224-.081-.038-.735-.245-.77-.256-.29-.1-.011-.255-.032-1.195-.01-.287-.015-.894-.013-.297zm6.583 0l-.011.227-.028.9-.008.303-.032 1.475-.01.262-.337.117-.734.245-.77.256-.712.245-.355.117.01-3.086 1.632-.578zm.585.437l.09.735.79-.097-.915 1.302-.018.006.01-.183.018-.877zm-9.451.536l.152.22 1.447 2.049-2.607.968-.05.015-1.972-2.214-.28-.312.003-.01.115-.018.424-.1.14-.021.337-.078.042-.01zm11.146.003l3.284.713.029.01-.022.025-1.954 2.192-.277.312-.092-.036-2.564-.95.475-.681.152-.216zM6.787 8.52h.86l.036 1.258-.013-.006-.763-1.078zm1.358 2.625l.152.06.77.252.712.245.746.247.49.167-.065.092-1.723 2.334-1.015-.302-.082-.017-.035-.015-1.902-.56.938-1.22.981-1.277zm6.73 0l.033.006 1.787 2.327.132.17-.128.036-.032.014-2.196.642-.105.032-.564.17-.018-.003-1.053-1.44-.174-.239-.547-.726-.007-.018.469-.16.769-.254.713-.245.77-.252zm-7.766.305l-.007.02-.405.523-.291-.291.657-.245zm8.802 0l.043.007.578.212.714.27-.661.394-.375-.479-.03-.042-.262-.342zm-10.843.75l-.67.668.355-.397.207-.23zm12.911.016l.068.025.045.042.554.627.042.043.204.228-.255.135zm-6.473.265l.022.015 1.38 1.872.032.05.343.465.008.031-.088.117-.422.629-.047.074-.245.343-.97 1.43-.013.007-1.18-1.72-.096-.16-.493-.708-.008-.037 1.618-2.191.007-.01zm7.827 1.194l.565.633.063.082-.272-.093-.037-.013zm-15.785.148l.297.299-.637.218-.152.05.038-.058zm13.224.47l-.855.448.346.66-.185-.058-.27-.088-1.092-.348.012-.01zm-9.687.255l1.222.356-.006.007-.458.145-.443.135-.032.01-.49.157zm-2.765.048l.318.32 2.007.517-.567.18-.055.004-2.103-.469-.744-.156.007-.006zm14.966.205l.548.188v.003l-.457.1-.043.014-1.069.23zm-10.23.507l.007.227.01.347.025 1.363.025.691-.007.255-.24.107-2.863 1.255.032-.372.033-.255.017-.227.031-.256.037-.407.045-.42.018-.23.032-.251.032-.412.05-.414.013-.14 1.455-.457.003-.014.301-.098zm4.908 0l1.245.39v.014l.312.1 1.146.362.022.23.03.255.043.408.04.42.017.23.033.251.032.412.042.325.078.848-.078-.04-3.025-1.322-.004-.305.06-2.368zm-4.295.617l.015.007.067.107.6.875-.64.531-.034-1.438zm3.671 0h.008l-.005.06-.02.678-.005.214-.479-.223zm-2.888 3.605l.763.915.001.37-.017-.006-.025-.05-.464-.791-.012-.018zm1.53.61l.184.083-.343.586-.018.007.002-.532z" fill="#f44336" fill-rule="evenodd" stroke="#f44336" stroke-width=".7747499999999999" stroke-linejoin="round"/></symbol><symbol viewBox="0 0 720 720" id="matlab" xmlns="http://www.w3.org/2000/svg"><title>Layer 1</title><path d="M209.247 329.98L52.368 387.638l121.325 85.822 96.752-95.804-61.198-47.674z" fill="#4db6ac" fill-rule="evenodd" stroke-width=".3"/><path d="M480.193 71.446c-13.123 1.784-9.565 1.013-28.4 16.09-18.008 14.418-69.925 100.347-97.673 129.256-24.688 25.722-34.46 12.199-60.102 33.661-25.68 21.494-65.273 64.464-65.273 64.464l63.978 47.32L394.15 222.754c23.948-32.932 23.694-37.266 36.744-71.82 6.384-16.907 17.76-29.9 27.756-45.809 12.488-19.874 30.186-34.855 21.543-33.68z" fill="#00897b" fill-rule="evenodd" stroke-width=".3"/><path d="M478.206 69.796c-31.268-.189-62.068 137.245-115.56 242.691-54.543 107.519-162.235 176.82-162.235 176.82 18.156 8.243 34.681 4.91 54.236 23.394 13.375 16.164 52.09 95.976 75.174 146.117 0 0 18.964-10.297 42.994-27.695 24.03-17.397 53.124-41.896 73.384-70.3 26.883-37.692 47.897-61.043 65.703-75.271 17.806-14.23 32.404-19.336 46.458-20.54 50.238-4.305 124.582 85.792 124.582 85.792S527.267 70.09 478.206 69.796z" fill="#ffb74d" fill-rule="evenodd" stroke-width=".3"/></symbol><symbol viewBox="0 0 24 24" id="merlin" xmlns="http://www.w3.org/2000/svg"><text style="line-height:1.25;-inkscape-font-specification:'Century Gothic Bold'" x="1.953" y="21.178" transform="scale(.99582 1.0042)" font-weight="700" font-size="30.255" font-family="Century Gothic" letter-spacing="0" word-spacing="0" fill="#42a5f5" stroke-width=".756"><tspan x="1.953" y="21.178" style="-inkscape-font-specification:'Century Gothic Bold'" font-size="22.745">M</tspan></text></symbol><symbol viewBox="0 0 192 191.99999" id="mocha" xmlns="http://www.w3.org/2000/svg"><title>Mocha Logo</title><g transform="translate(-354.75 -262.42) scale(4.835)" fill="#a1887f"><path d="M103.6 69.6c0-.5-.4-1-1-1H83.8c-.5 0-1 .4-1 1 0 3.4.5 15.1 5.5 20.8.2.2.4.3.7.3h8.4c.3 0 .5-.1.7-.3 5-5.6 5.5-17.3 5.5-20.8zm-7.4 18.2h-5.9c-.3 0-.5-.1-.7-.3-3.4-4-3.8-12-3.9-14.8 0-.5.4-1 1-1h13.2c.5 0 1 .4 1 1 0 2.8-.5 10.7-3.9 14.8-.3.2-.5.3-.8.3zM95.1 66.6s3.6-2.1 1.4-5.9c-1.3-2-1.9-3.7-1.4-4.4-1.3 1.6-3.5 3.3-1.1 6.9.8.9 1.2 2.8 1.1 3.4zM91.1 66.9s2.4-1.4.9-4c-.9-1.3-1.3-2.5-.9-2.9-.9 1.1-2.3 2.2-.7 4.7.5.5.7 1.8.7 2.2z"/><path d="M99.3 78.5c-.4 2.7-1.2 5.8-2.9 7.8-.2.2-.4.3-.6.3h-5c-.2 0-.5-.1-.6-.3-1.2-1.5-2-3.5-2.5-5.6 0 0 5.8.8 9.1-.4 2.4-.9 2.5-1.8 2.5-1.8z"/></g></symbol><symbol viewBox="0 0 24 24" id="movie" xmlns="http://www.w3.org/2000/svg"><path d="M18 4l2 4h-3l-2-4h-2l2 4h-3l-2-4H8l2 4H7L5 4H4a2 2 0 0 0-2 2v12a2 2 0 0 0 2 2h16a2 2 0 0 0 2-2V4h-4z" fill="#ff9800"/></symbol><symbol viewBox="0 0 24 24" id="music" xmlns="http://www.w3.org/2000/svg"><path d="M16 9V7h-4v5.5c-.42-.31-.93-.5-1.5-.5A2.5 2.5 0 0 0 8 14.5a2.5 2.5 0 0 0 2.5 2.5 2.5 2.5 0 0 0 2.5-2.5V9h3m-4-7a10 10 0 0 1 10 10 10 10 0 0 1-10 10A10 10 0 0 1 2 12 10 10 0 0 1 12 2z" fill="#ef5350"/></symbol><symbol viewBox="0 0 24 24" id="mxml" xmlns="http://www.w3.org/2000/svg"><path d="M13 9h5.5L13 3.5V9M6 2h8l6 6v12a2 2 0 0 1-2 2H6a2 2 0 0 1-2-2V4c0-1.11.89-2 2-2m.12 13.5l3.74 3.74 1.42-1.41-2.33-2.33 2.33-2.33-1.42-1.41-3.74 3.74m11.16 0l-3.74-3.74-1.42 1.41 2.33 2.33-2.33 2.33 1.42 1.41 3.74-3.74z" fill="#ffa726"/></symbol><symbol viewBox="0 0 300 300" id="ngrx-actions" xmlns="http://www.w3.org/2000/svg"><path d="M150 27.324L35.85 68.006l17.303 151.09 96.843 53.586 96.843-53.586 17.303-151.09zm-23.719 38.349c4.346-.075 9.04 1.316 14.265 4.131 2.3 1.24 9.235 2.994 15.407 3.889 21.936 3.18 47.975 19.934 56.21 36.186 5.667 11.183 4.508 17.209-4.18 21.702-7.492 3.874-22.822 2-45.08-5.517l-18.785-6.343-6.683 2.552c-9.683 3.698-19.366 12.877-23.33 22.09-2.858 6.645-3.293 9.768-2.77 20.705.523 10.955 1.315 14.12 5.2 20.997 4.423 7.829 14.576 17.818 16.331 16.064.473-.473-.574-3.648-2.308-7.048-1.735-3.4-2.744-6.825-2.26-7.606.482-.781 5.054 2.123 10.157 6.44 11.35 9.6 24.608 15.74 36.77 17.01 9.985 1.045 12.266-.814 4.787-3.912-2.41-.998-5.544-3.088-6.95-4.641-2.907-3.212-3.072-3.12 9.356-5.906 7.736-1.733 23.026-9.849 23.937-12.71.29-.91-2.195-1.296-6.27-.972-3.706.295-6.732-.087-6.732-.85 0-.76 3.032-4.523 6.732-8.385 13.883-14.489 18.62-25.32 20.098-45.906l1.02-14.217 3.257 6.756c3.601 7.452 4.265 18.202 1.701 27.437-2.141 7.711-.712 8.564 3.208 1.92 4.845-8.212 6.39-6.905 5.54 4.666-.924 12.587-5.243 22.017-14.993 32.686-7.95 8.699-7.001 10.254 2.624 4.326 9.273-5.711 10.511-4.815 5.736 4.155-9.031 16.964-28.122 31.35-47.948 36.161-12.016 2.917-20.537 3.461-31.544 2.018-28.78-3.775-56.001-23.157-68.993-49.114-3.378-6.748-8.154-14.994-10.62-18.348-5.092-6.924-5.529-10.038-2.09-15.286 1.715-2.618 2.116-5.307 1.41-9.308-3.273-18.531-3.167-19.11 4.276-26.659 6.468-6.56 6.878-7.44 6.878-15.092 0-6.637.671-8.813 3.67-11.811 2.02-2.02 5.23-3.7 7.12-3.718 5.49-.05 14.97-5.135 20.584-11.033 4.687-4.927 9.674-7.417 15.262-7.51z" fill="#ab47bc" stroke-width="12.914"/></symbol><symbol viewBox="0 0 300 300" id="ngrx-effects" xmlns="http://www.w3.org/2000/svg"><path d="M150 27.324L35.85 68.006l17.303 151.09 96.843 53.586 96.843-53.586 17.303-151.09zm-23.719 38.349c4.346-.075 9.04 1.316 14.265 4.131 2.3 1.24 9.235 2.994 15.407 3.889 21.936 3.18 47.975 19.934 56.21 36.186 5.667 11.183 4.508 17.209-4.18 21.702-7.492 3.874-22.822 2-45.08-5.517l-18.785-6.343-6.683 2.552c-9.683 3.698-19.366 12.877-23.33 22.09-2.858 6.645-3.293 9.768-2.77 20.705.523 10.955 1.315 14.12 5.2 20.997 4.423 7.829 14.576 17.818 16.331 16.064.473-.473-.574-3.648-2.308-7.048-1.735-3.4-2.744-6.825-2.26-7.606.482-.781 5.054 2.123 10.157 6.44 11.35 9.6 24.608 15.74 36.77 17.01 9.985 1.045 12.266-.814 4.787-3.912-2.41-.998-5.544-3.088-6.95-4.641-2.907-3.212-3.072-3.12 9.356-5.906 7.736-1.733 23.026-9.849 23.937-12.71.29-.91-2.195-1.296-6.27-.972-3.706.295-6.732-.087-6.732-.85 0-.76 3.032-4.523 6.732-8.385 13.883-14.489 18.62-25.32 20.098-45.906l1.02-14.217 3.257 6.756c3.601 7.452 4.265 18.202 1.701 27.437-2.141 7.711-.712 8.564 3.208 1.92 4.845-8.212 6.39-6.905 5.54 4.666-.924 12.587-5.243 22.017-14.993 32.686-7.95 8.699-7.001 10.254 2.624 4.326 9.273-5.711 10.511-4.815 5.736 4.155-9.031 16.964-28.122 31.35-47.948 36.161-12.016 2.917-20.537 3.461-31.544 2.018-28.78-3.775-56.001-23.157-68.993-49.114-3.378-6.748-8.154-14.994-10.62-18.348-5.092-6.924-5.529-10.038-2.09-15.286 1.715-2.618 2.116-5.307 1.41-9.308-3.273-18.531-3.167-19.11 4.276-26.659 6.468-6.56 6.878-7.44 6.878-15.092 0-6.637.671-8.813 3.67-11.811 2.02-2.02 5.23-3.7 7.12-3.718 5.49-.05 14.97-5.135 20.584-11.033 4.687-4.927 9.674-7.417 15.262-7.51z" fill="#26c6da" stroke-width="12.914"/></symbol><symbol viewBox="0 0 300 300" id="ngrx-reducer" xmlns="http://www.w3.org/2000/svg"><path d="M150 27.324L35.85 68.006l17.303 151.09 96.843 53.586 96.843-53.586 17.303-151.09zm-23.719 38.349c4.346-.075 9.04 1.316 14.265 4.131 2.3 1.24 9.235 2.994 15.407 3.889 21.936 3.18 47.975 19.934 56.21 36.186 5.667 11.183 4.508 17.209-4.18 21.702-7.492 3.874-22.822 2-45.08-5.517l-18.785-6.343-6.683 2.552c-9.683 3.698-19.366 12.877-23.33 22.09-2.858 6.645-3.293 9.768-2.77 20.705.523 10.955 1.315 14.12 5.2 20.997 4.423 7.829 14.576 17.818 16.331 16.064.473-.473-.574-3.648-2.308-7.048-1.735-3.4-2.744-6.825-2.26-7.606.482-.781 5.054 2.123 10.157 6.44 11.35 9.6 24.608 15.74 36.77 17.01 9.985 1.045 12.266-.814 4.787-3.912-2.41-.998-5.544-3.088-6.95-4.641-2.907-3.212-3.072-3.12 9.356-5.906 7.736-1.733 23.026-9.849 23.937-12.71.29-.91-2.195-1.296-6.27-.972-3.706.295-6.732-.087-6.732-.85 0-.76 3.032-4.523 6.732-8.385 13.883-14.489 18.62-25.32 20.098-45.906l1.02-14.217 3.257 6.756c3.601 7.452 4.265 18.202 1.701 27.437-2.141 7.711-.712 8.564 3.208 1.92 4.845-8.212 6.39-6.905 5.54 4.666-.924 12.587-5.243 22.017-14.993 32.686-7.95 8.699-7.001 10.254 2.624 4.326 9.273-5.711 10.511-4.815 5.736 4.155-9.031 16.964-28.122 31.35-47.948 36.161-12.016 2.917-20.537 3.461-31.544 2.018-28.78-3.775-56.001-23.157-68.993-49.114-3.378-6.748-8.154-14.994-10.62-18.348-5.092-6.924-5.529-10.038-2.09-15.286 1.715-2.618 2.116-5.307 1.41-9.308-3.273-18.531-3.167-19.11 4.276-26.659 6.468-6.56 6.878-7.44 6.878-15.092 0-6.637.671-8.813 3.67-11.811 2.02-2.02 5.23-3.7 7.12-3.718 5.49-.05 14.97-5.135 20.584-11.033 4.687-4.927 9.674-7.417 15.262-7.51z" fill="#e53935" stroke-width="12.914"/></symbol><symbol viewBox="0 0 300 300" id="ngrx-state" xmlns="http://www.w3.org/2000/svg"><path d="M150 27.324L35.85 68.006l17.303 151.09 96.843 53.586 96.843-53.586 17.303-151.09zm-23.719 38.349c4.346-.075 9.04 1.316 14.265 4.131 2.3 1.24 9.235 2.994 15.407 3.889 21.936 3.18 47.975 19.934 56.21 36.186 5.667 11.183 4.508 17.209-4.18 21.702-7.492 3.874-22.822 2-45.08-5.517l-18.785-6.343-6.683 2.552c-9.683 3.698-19.366 12.877-23.33 22.09-2.858 6.645-3.293 9.768-2.77 20.705.523 10.955 1.315 14.12 5.2 20.997 4.423 7.829 14.576 17.818 16.331 16.064.473-.473-.574-3.648-2.308-7.048-1.735-3.4-2.744-6.825-2.26-7.606.482-.781 5.054 2.123 10.157 6.44 11.35 9.6 24.608 15.74 36.77 17.01 9.985 1.045 12.266-.814 4.787-3.912-2.41-.998-5.544-3.088-6.95-4.641-2.907-3.212-3.072-3.12 9.356-5.906 7.736-1.733 23.026-9.849 23.937-12.71.29-.91-2.195-1.296-6.27-.972-3.706.295-6.732-.087-6.732-.85 0-.76 3.032-4.523 6.732-8.385 13.883-14.489 18.62-25.32 20.098-45.906l1.02-14.217 3.257 6.756c3.601 7.452 4.265 18.202 1.701 27.437-2.141 7.711-.712 8.564 3.208 1.92 4.845-8.212 6.39-6.905 5.54 4.666-.924 12.587-5.243 22.017-14.993 32.686-7.95 8.699-7.001 10.254 2.624 4.326 9.273-5.711 10.511-4.815 5.736 4.155-9.031 16.964-28.122 31.35-47.948 36.161-12.016 2.917-20.537 3.461-31.544 2.018-28.78-3.775-56.001-23.157-68.993-49.114-3.378-6.748-8.154-14.994-10.62-18.348-5.092-6.924-5.529-10.038-2.09-15.286 1.715-2.618 2.116-5.307 1.41-9.308-3.273-18.531-3.167-19.11 4.276-26.659 6.468-6.56 6.878-7.44 6.878-15.092 0-6.637.671-8.813 3.67-11.811 2.02-2.02 5.23-3.7 7.12-3.718 5.49-.05 14.97-5.135 20.584-11.033 4.687-4.927 9.674-7.417 15.262-7.51z" fill="#9ccc65" stroke-width="12.914"/></symbol><symbol viewBox="0 0 24 24" id="nim" xmlns="http://www.w3.org/2000/svg"><path d="M4.464 15.75L2.288 3.78l5.985 7.617L12.08 3.78l3.809 7.617 5.985-7.617-2.177 11.97H4.464m15.234 3.264a1.088 1.088 0 0 1-1.088 1.088H5.553a1.088 1.088 0 0 1-1.089-1.088v-1.089h15.234z" stroke-width="1.088" fill="#ffca28"/></symbol><symbol viewBox="0 0 500 500" id="nix" xmlns="http://www.w3.org/2000/svg"><g transform="translate(-1.965 36.302)" stroke-width=".395"><path d="M135.59 415.7c0-.295-2.752-5.283-6.116-11.084-3.364-5.801-6.116-10.776-6.116-11.055s9.514-16.889 21.143-36.912c11.629-20.022 21.323-36.798 21.542-37.279.346-.76-1.608-4.363-14.896-27.466-8.412-14.625-15.294-26.785-15.294-27.023 0-.5 24.46-43.501 25.206-44.31.414-.45.592-.384 1.078.395.32.513 16.876 29.256 36.791 63.87 62.62 108.85 74.852 130.01 75.41 130.46.3.242.544.554.544.694 0 .14-11.836.21-26.302.154-23.023-.09-26.313-.175-26.393-.694-.11-.714-27.662-48.825-28.86-50.392-.746-.978-.906-1.035-1.426-.51-.688.696-28.954 49.323-29.49 50.733l-.365.96h-13.229c-10.896 0-13.229-.095-13.229-.538zm167.58-125.61c-.134-.216 1.188-2.863 2.938-5.882 6.924-11.944 84.291-145.75 96.491-166.88 7.143-12.371 13.142-22.465 13.333-22.433.363.062 25.861 43.105 25.861 43.655 0 .174-6.761 11.952-15.026 26.173-8.46 14.557-14.932 26.104-14.81 26.421.185.483 4.564.564 30.213.564h29.996l.958 1.48c.526.814 3.296 5.547 6.155 10.518 2.859 4.971 5.45 9.29 5.756 9.597.706.705.704.724-.16 1.572-.395.388-3.36 5.323-6.587 10.965-3.228 5.643-6.056 10.387-6.285 10.543-.23.156-19.695.171-43.256.034l-42.84-.249-.804 1.15c-.441.632-7.504 12.736-15.696 26.897l-14.892 25.747H339.03c-8.517 0-20.015.116-25.55.259-6.55.168-10.15.121-10.309-.135zM169.42 132.23c-56.373-.055-102.5-.182-102.5-.282 0-.1 5.617-10.132 12.481-22.294l12.481-22.112h30.332c27.113 0 30.332-.065 30.332-.611 0-.336-6.659-12.228-14.797-26.427-8.139-14.199-14.797-25.917-14.797-26.04 0-.123 2.682-4.853 5.96-10.51s6.003-10.578 6.055-10.934c.086-.586 1.376-.648 13.572-.648 7.413 0 13.463.143 13.446.317-.017.174.222.707.531 1.184.31.476 9.763 16.937 21.007 36.578 11.244 19.64 20.71 36.022 21.036 36.4.554.647 2.549.691 31.428.691h30.837l12.896 22.145c7.093 12.18 12.8 22.301 12.682 22.492-.118.19-4.776.303-10.352.249-5.575-.054-56.26-.143-112.63-.198z" fill="#5075c1"/><path d="M25.289 203.14c-6.098 10.563-6.69 11.711-6.225 12.078.283.224 3.18 5.044 6.44 10.712 3.261 5.668 6.017 10.355 6.124 10.417.106.061 13.585.153 29.95.204 16.367.052 29.994.23 30.285.399.472.273-1.08 3.094-14.637 26.574L62.06 289.793l12.907 21.865c7.1 12.026 12.982 21.906 13.068 21.956.086.05 23.257-39.831 51.492-88.624 11.352-19.617 21.214-36.64 30.37-52.442 23.308-40.452 30.68-53.468 30.73-54.132-1.097-.11-6.141-.187-13.006-.216-3.945-.01-7.82-.02-12.75-.002l-25.341.092-15.42 26.706c-14.256 24.693-15.445 26.663-16.278 26.86l-.024.037c-.011.003-1.62-.001-1.825 0-4.29.062-20.453.063-40.226-.01-22.632-.082-41.615-.125-42.183-.096-.568.03-1.147-.03-1.29-.132-.142-.102-3.29 5.066-6.996 11.485zm205.16-190.3c-.123.149 5.62 10.392 12.761 22.763 12.199 21.131 89.393 155.03 96.276 167 1.502 2.613 2.92 4.803 3.443 5.348.9-1.249 3.531-5.63 7.954-13.219a1342.88 1342.88 0 0 1 10.049-17.76l6.606-11.443c.692-1.403.754-1.818.653-2.117-.162-.48-6.904-12.332-14.982-26.337-8.078-14.005-14.824-25.849-14.991-26.32a.73.73 0 0 1-.009-.366l-.426-.913L359.42 72.5c3.69-6.307 6.425-11.042 9.47-16.29 9.159-15.948 12.037-21.189 11.896-21.55-.126-.324-2.7-4.83-5.72-10.017-3.021-5.185-5.845-10.148-6.275-11.026-.483-.987-.734-1.364-1.1-1.456-.054.014-.083.018-.145.035-.42.112-5.454.195-11.189.185-5.734-.01-11.22.024-12.188.073l-1.76.089-14.997 25.978c-12.824 22.212-15.084 25.964-15.595 25.883-.024-.004-.15-.189-.235-.301-.109.066-.2.09-.272.05-.255-.148-7.143-11.902-15.306-26.119l-14.36-25.016c-.115-.186-.444-.744-.457-.752-.477-.275-50.502.287-50.737.57zm-18.646 283.09c-.047.109-.026.262.042.48.329 1.05 25.338 43.735 25.772 43.985.207.119 14.178.239 31.05.266 26.651.044 30.75.152 31.234.832.308.43 9.988 17.214 21.513 37.296s21.152 36.627 21.394 36.767c.242.14 5.927.243 12.633.23 6.706-.013 12.401.099 12.657.246.132.076.382-.141.852-.795l6.008-10.406c5.234-9.065 6.62-11.684 6.294-11.888-.575-.36-15.597-26.643-23.859-41.482-3.09-5.45-5.37-9.516-5.441-9.774-.195-.712-.065-.822 1.156-.98 1.956-.252 57.397-.057 58.07.205.238.092.79-.569 2.594-3.497 1.866-3.067 5.03-8.524 11-18.866 7.22-12.505 13.044-22.784 12.942-22.843-.102-.059-.771-.051-1.489.016l-.046.001c-4.452.204-33.918.203-149.74.025-38.96-.06-69.786-.09-71.912-.072-1.121.01-2.095.076-2.66.172a.25.25 0 0 0-.062.083z" fill="#7db7e1"/></g></symbol><symbol viewBox="0 0 24 24" id="nodejs" xmlns="http://www.w3.org/2000/svg"><path d="M12 1.85c-.27 0-.55.07-.78.2l-7.44 4.3c-.48.28-.78.8-.78 1.36v8.58c0 .56.3 1.08.78 1.36l1.95 1.12c.95.46 1.27.47 1.71.47 1.4 0 2.21-.85 2.21-2.33V8.44c0-.12-.1-.22-.22-.22H8.5c-.13 0-.23.1-.23.22v8.47c0 .66-.68 1.31-1.77.76L4.45 16.5a.26.26 0 0 1-.11-.21V7.71c0-.09.04-.17.11-.21l7.44-4.29c.06-.04.16-.04.22 0l7.44 4.29c.07.04.11.12.11.21v8.58c0 .08-.04.16-.11.21l-7.44 4.29c-.06.04-.16.04-.23 0L10 19.65c-.08-.03-.16-.04-.21-.01-.53.3-.63.36-1.12.51-.12.04-.31.11.07.32l2.48 1.47c.24.14.5.21.78.21s.54-.07.78-.21l7.44-4.29c.48-.28.78-.8.78-1.36V7.71c0-.56-.3-1.08-.78-1.36l-7.44-4.3c-.23-.13-.5-.2-.78-.2M14 8c-2.12 0-3.39.89-3.39 2.39 0 1.61 1.26 2.08 3.3 2.28 2.43.24 2.62.6 2.62 1.08 0 .83-.67 1.18-2.23 1.18-1.98 0-2.4-.49-2.55-1.47a.226.226 0 0 0-.22-.18h-.96c-.12 0-.21.09-.21.22 0 1.24.68 2.74 3.94 2.74 2.35 0 3.7-.93 3.7-2.55 0-1.61-1.08-2.03-3.37-2.34-2.31-.3-2.54-.46-2.54-1 0-.45.2-1.05 1.91-1.05 1.5 0 2.09.33 2.32 1.36.02.1.11.17.21.17h.97c.05 0 .11-.02.15-.07.04-.04.07-.1.05-.16C17.56 8.82 16.38 8 14 8z" fill="#8bc34a"/></symbol><symbol viewBox="0 0 300 300" id="nodemon" xmlns="http://www.w3.org/2000/svg"><title>nodemon</title><path d="M149.868 20.62c-2.124 0-4.25.55-6.154 1.648L41.899 81.083a12.306 12.306 0 0 0-6.15 10.652v117.633a12.29 12.29 0 0 0 6.152 10.646l101.815 58.766h.001a12.282 12.282 0 0 0 12.291 0l101.84-58.766a12.29 12.29 0 0 0 6.153-10.652V91.738a12.31 12.31 0 0 0-6.146-10.652L156.015 22.27a12.302 12.302 0 0 0-6.153-1.648zM83.303 70.93s11.789 33.031 35.477 31.934l27.74-15.961a7.348 7.348 0 0 1 3.414-.99h.641a7.233 7.233 0 0 1 3.404.99l27.738 15.961c23.69 1.094 35.475-31.934 35.475-31.934 5.233 23.154 1.06 38.641-5.924 48.942l4.541 2.614h.002c2.321 1.327 3.734 3.795 3.737 6.49l-.12 95.811a3.724 3.724 0 0 1-1.855 3.227 3.624 3.624 0 0 1-3.735 0L177.1 206.971c-2.311-1.363-3.742-3.818-3.742-6.48v-44.763a7.44 7.44 0 0 0-3.737-6.465l-15.642-9.01a7.28 7.28 0 0 0-3.715-1.01 7.378 7.378 0 0 0-3.742 1.01l-15.648 9.01c-2.316 1.323-3.729 3.798-3.729 6.467v44.762c0 2.663-1.413 5.1-3.738 6.48l-36.748 21.041a3.571 3.571 0 0 1-3.71 0c-1.173-.65-1.864-1.887-1.864-3.224l-.137-95.812a7.483 7.483 0 0 1 3.74-6.49l4.541-2.615c-6.982-10.302-11.16-25.79-5.925-48.942z" fill="#8bc34a"/></symbol><symbol viewBox="0 0 990 990" id="npm" xmlns="http://www.w3.org/2000/svg"><defs><style>.hncls-1{fill:#cb3837}.cls-2{fill:#fff}</style></defs><title>n</title><path class="hncls-1" d="M113.26 876.74V113.27h763.47v763.47zm143.59-620.4v476.18h240.61V355.63h140.21v376.96h95.457V256.34z" fill="#e53935" stroke-width=".771"/></symbol><symbol id="nunjucks" viewBox="0 0 24 24" xmlns="http://www.w3.org/2000/svg"><style>.host0{fill:#388e3c}</style><path class="host0" d="M11.2 21.1H8.1l-2.3-7.9v7.9H2.7V2.9h3.1l2.3 7.4V2.9h3.1zM21.3 19.2c0 1-.8 1.9-1.9 1.9h-4.8c-1 0-1.9-.8-1.9-1.9v-3.8l3.2-.7V18h2.3V7.2h3.1v12z"/></symbol><symbol viewBox="0 0 150 150.00001" id="ocaml" xmlns="http://www.w3.org/2000/svg"><g transform="matrix(.76136 0 0 .76136 11.616 19.98)"><path d="M83.02 101.645l.023-.062c-.035-.159-.047-.195-.024.062z" fill="none" stroke-width="1.028"/><linearGradient id="hpa" gradientUnits="userSpaceOnUse" x1="-696.735" y1="97.7" x2="-696.735" y2="142.997" gradientTransform="matrix(1.02783 0 0 1.02783 776.895 2.337)"><stop offset="0" stop-color="#f29100"/><stop offset="1" stop-color="#ec670f"/></linearGradient><path d="M82.313 138.79c-.471-1.004-1.904-3.621-2.624-4.46-1.562-1.828-1.927-1.966-2.386-4.275-.799-4.02-2.913-11.31-5.405-16.341-1.286-2.596-3.426-4.777-5.385-6.66-1.71-1.652-5.565-4.431-6.237-4.294-6.296 1.257-8.249 7.432-11.21 12.323-1.638 2.705-3.374 5.007-4.665 7.885-1.192 2.646-1.087 5.577-3.128 7.849-2.093 2.333-3.454 4.814-4.48 7.829-.194.574-.747 6.596-1.348 8.015l9.357-.659c8.719.594 6.2 3.936 19.81 3.208l21.487-.665c-.666-1.97-1.584-4.25-1.938-4.991-.599-1.248-1.352-3.69-1.848-4.763z" fill="url(#hpa)" stroke-width="1.028"/><linearGradient id="hpb" gradientUnits="userSpaceOnUse" x1="-666.972" y1="142.12" x2="-666.972" y2="142.12" gradientTransform="matrix(1.02783 0 0 1.02783 776.895 2.337)"><stop offset="0" stop-color="#f29100"/><stop offset="1" stop-color="#ec670f"/></linearGradient><linearGradient id="hpc" gradientUnits="userSpaceOnUse" x1="-675.228" y1="-1.28" x2="-675.228" y2="142.967" gradientTransform="matrix(1.02783 0 0 1.02783 776.895 2.337)"><stop offset="0" stop-color="#f29100"/><stop offset="1" stop-color="#ec670f"/></linearGradient><path d="M109.553 94.296c-1.652 1.193-4.88 4.06-11.902 5.145-3.152.487-6.1.527-9.335.365-1.584-.076-3.077-.157-4.665-.177-.936-.008-4.074-.107-3.919.193l-.349.871c.054.287.169 1.004.2 1.177.129.704.165 1.265.192 1.912.048 1.331-.11 2.719-.043 4.062.141 2.787 1.175 5.326 1.306 8.137.143 3.13 1.69 6.442 3.188 8.998.569.973 1.434 1.084 1.811 2.283.442 1.373.024 2.83.239 4.293.842 5.675 2.477 11.606 5.032 16.728.018.043.038.09.06.128 3.156-.53 6.318-1.665 10.418-2.271 7.517-1.115 17.972-.54 24.688-1.17 16.993-1.597 26.216 6.97 41.478 3.459V22.459c0-11.84-9.594-21.438-21.435-21.438H19.239C7.4 1.021-2.197 10.62-2.197 22.458v46.774c3.067-1.11 7.479-7.635 8.861-9.222 2.419-2.775 2.858-6.315 4.062-8.544 2.743-5.078 3.215-8.57 9.451-8.57 2.907 0 4.061.67 6.027 3.31 1.368 1.834 3.731 5.224 4.837 7.49 1.277 2.615 3.357 6.153 4.272 6.867.677.53 1.35.928 1.976 1.163 1.012.38 1.848-.316 2.525-.855.863-.687 1.235-2.088 2.035-3.957 1.152-2.696 2.408-5.926 3.122-7.054 1.237-1.949 1.658-4.261 2.993-5.381 1.97-1.652 4.54-1.768 5.246-1.908 3.957-.781 5.755 1.906 7.704 3.645 1.276 1.138 3.019 3.432 4.256 6.507.967 2.4 2.199 4.622 2.714 6.008.497 1.339 1.725 3.484 2.453 6.055.661 2.336 2.43 4.125 3.102 5.235 0 0 1.029 2.882 7.285 5.516 1.357.572 4.1 1.501 5.736 2.096 2.718.988 5.351.86 8.704.458 2.391 0 3.686-3.462 4.772-6.234.643-1.639 1.259-6.334 1.678-7.667.406-1.297-.544-2.3.265-3.437.946-1.327 1.508-1.399 2.054-3.129 1.172-3.704 7.95-3.89 11.761-3.89 3.176 0 2.772 3.083 8.16 2.028 3.086-.605 6.059.398 9.335 1.265 2.758.732 5.352 1.566 6.906 3.385 1.005 1.178 3.5 7.08.958 7.331.244.3.423.84.88 1.135-.566 2.226-3.03.64-4.4.355-1.845-.383-3.147.057-4.952.856-3.085 1.374-7.598 1.214-10.286 3.452-2.281 1.898-2.277 6.133-3.34 8.507-.002-.001-2.955 7.6-9.402 12.248z" fill="url(#hpc)" stroke-width="1.028"/><linearGradient id="hpd" gradientUnits="userSpaceOnUse" x1="-735.137" y1="90.833" x2="-735.137" y2="141.967" gradientTransform="matrix(1.02783 0 0 1.02783 776.895 2.337)"><stop offset="0" stop-color="#f29100"/><stop offset="1" stop-color="#ec670f"/></linearGradient><path d="M38.247 105.09c-1.467-.15-2.83-.317-4.256-.605-2.662-.536-5.57-1.06-8.193-1.688-1.592-.385-6.895-2.263-8.048-2.792-2.702-1.246-4.496-4.63-6.609-4.282-1.348.22-2.662.682-3.5 2.042-.685 1.11-.917 3.016-1.391 4.294-.55 1.485-1.5 2.87-2.331 4.284-1.53 2.595-4.282 4.941-5.468 7.469-.239.52-.45 1.101-.649 1.708V144.415a48.57 48.57 0 0 1 4.45.96c11.955 3.19 14.872 3.46 26.598 2.119l1.1-.146c.897-1.867 1.59-8.227 2.171-10.195.454-1.51 1.077-2.712 1.313-4.253.223-1.463-.02-2.858-.146-4.188-.329-3.332 2.427-4.522 3.742-7.384 1.186-2.589 1.871-5.535 2.853-8.181.941-2.54 2.41-6.13 4.918-7.408-.305-.355-5.237-.518-6.554-.65z" fill="url(#hpd)" stroke-width="1.028"/></g></symbol><symbol viewBox="0 0 24 24" id="pdf" xmlns="http://www.w3.org/2000/svg"><path d="M14 9h5.5L14 3.5V9M7 2h8l6 6v12a2 2 0 0 1-2 2H7a2 2 0 0 1-2-2V4a2 2 0 0 1 2-2m4.93 10.44c.41.9.93 1.64 1.53 2.15l.41.32c-.87.16-2.07.44-3.34.93l-.11.04.5-1.04c.45-.87.78-1.66 1.01-2.4m6.48 3.81c.18-.18.27-.41.28-.66.03-.2-.02-.39-.12-.55-.29-.47-1.04-.69-2.28-.69l-1.29.07-.87-.58c-.63-.52-1.2-1.43-1.6-2.56l.04-.14c.33-1.33.64-2.94-.02-3.6a.853.853 0 0 0-.61-.24h-.24c-.37 0-.7.39-.79.77-.37 1.33-.15 2.06.22 3.27v.01c-.25.88-.57 1.9-1.08 2.93l-.96 1.8-.89.49c-1.2.75-1.77 1.59-1.88 2.12-.04.19-.02.36.05.54l.03.05.48.31.44.11c.81 0 1.73-.95 2.97-3.07l.18-.07c1.03-.33 2.31-.56 4.03-.75 1.03.51 2.24.74 3 .74.44 0 .74-.11.91-.3m-.41-.71l.09.11c-.01.1-.04.11-.09.13h-.04l-.19.02c-.46 0-1.17-.19-1.9-.51.09-.1.13-.1.23-.1 1.4 0 1.8.25 1.9.35M8.83 17c-.65 1.19-1.24 1.85-1.69 2 .05-.38.5-1.04 1.21-1.69l.48-.31m3.02-6.91c-.23-.9-.24-1.63-.07-2.05l.07-.12.15.05c.17.24.19.56.09 1.1l-.03.16-.16.82-.05.04z" fill="#f44336"/></symbol><symbol viewBox="0 0 24 24" id="perl" xmlns="http://www.w3.org/2000/svg"><path d="M12 14c-1 0-3 1-3 2 0 2 3 2 3 2v-1a1 1 0 0 1-1-1 1 1 0 0 1 1-1v-1m0 5s-4-.5-4-2.5c0-3 3-3.75 4-3.75V11.5c-1 0-5 1.5-5 4.5 0 4 5 4 5 4v-1M10.07 7.03l1.19.53c.43-2.44 1.58-4.06 1.58-4.06-.43 1.03-.71 1.88-.89 2.55C13.16 3.55 15.61 2 15.61 2a15.916 15.916 0 0 0-2.64 3.53c1.58-1.68 3.77-2.78 3.77-2.78-2.69 1.72-3.9 4.45-4.2 5.21l.55.08c0 .52 0 1 .25 1.38C14.1 11.31 18 11.47 18 16s-4.03 6-6.17 6C9.69 22 5 21.03 5 16s4.95-5.07 5.83-7.08c.12-.38-.76-1.89-.76-1.89z" fill="#9575cd"/></symbol><symbol viewBox="0 0 24 24" id="php" xmlns="http://www.w3.org/2000/svg"><path d="M12 18.08c-6.63 0-12-2.72-12-6.08s5.37-6.08 12-6.08S24 8.64 24 12s-5.37 6.08-12 6.08m-5.19-7.95c.54 0 .91.1 1.09.31.18.2.22.56.13 1.03-.1.53-.29.87-.58 1.09-.28.22-.71.33-1.29.33h-.87l.53-2.76h.99m-3.5 5.55h1.44l.34-1.75h1.23c.54 0 .98-.06 1.33-.17.35-.12.67-.31.96-.58.24-.22.43-.46.58-.73.15-.26.26-.56.31-.88.16-.78.05-1.39-.33-1.82-.39-.44-.99-.65-1.82-.65H4.59l-1.28 6.58m7.25-8.33l-1.28 6.58h1.42l.74-3.77h1.14c.36 0 .6.06.71.18.11.12.13.34.07.66l-.57 2.93h1.45l.59-3.07c.13-.62.03-1.07-.27-1.36-.3-.27-.85-.4-1.65-.4h-1.27L12 7.35h-1.44M18 10.13c.55 0 .91.1 1.09.31.18.2.22.56.13 1.03-.1.53-.29.87-.57 1.09-.29.22-.72.33-1.3.33h-.85l.5-2.76h1m-3.5 5.55h1.44l.34-1.75h1.22c.55 0 1-.06 1.35-.17.35-.12.65-.31.95-.58.24-.22.44-.46.58-.73.15-.26.26-.56.32-.88.15-.78.04-1.39-.34-1.82-.36-.44-.99-.65-1.82-.65h-2.75l-1.29 6.58z" fill="#1E88E5"/></symbol><symbol viewBox="0 0 79 78" id="postcss" xmlns="http://www.w3.org/2000/svg"><title>postcss-logo-symbol</title><g transform="translate(5.48 5.52) scale(.85425)" fill="#e53935" fill-rule="evenodd" stroke="#e53935" stroke-width="1.519"><path d="M15.447 32.623c.106.08.29.132.106.29-.132.184-.29.342-.395.553-.105.185-.184.237-.342.106.21-.343.42-.66.63-.95zM68.342 60.24c0 .078.026.13.026.21.053-.105.053-.158.08-.21zm0 .236v-.026zm-5.368 10.277l-4.58-25.402c-.078-.025-.183-.077-.368-.13.053.105.08.184.106.263.13-.026.184-.026.236-.052 0-.026 0-.052.027-.08l4.58 25.404zm-4.737-31.12c-.026.078-.026.158-.026.237 0-.08 0-.16.028-.238zm.026.526c-.026 0-.026 0-.052-.028v.026c.028.026.028.026.054 0zm-.052.21v-.185c-.077.026-.156.026-.262.053.132.05.264.078.264.13z"/><path d="M78.71 33.967c-.052-1.028-.078-2.056-.184-3.083-.184-1.397-.368-2.82-.684-4.19-.237-1.133-.63-2.214-1.026-3.294-.5-1.265-1-2.556-1.632-3.768-1.026-1.95-2.368-3.69-3.605-5.508-.818-1.16-1.87-2.108-2.66-3.294-.447-.685-1.105-1.264-1.763-1.79-1.053-.845-2.158-1.61-3.263-2.347a32.525 32.525 0 0 0-2.58-1.634c-.71-.397-1.473-.713-2.21-1.056-.842-.395-1.658-.87-2.605-1.054-.238-.05-.448-.13-.685-.21-.605-.21-1.184-.447-1.79-.632-.92-.29-1.815-.632-2.763-.87C50.342 1 49.394.843 48.446.71 47.394.555 46.316.5 45.262.397a26.83 26.83 0 0 0-2.026-.184C42.236.16 41.21.16 40.21.134c-.5-.027-1.026-.08-1.526-.053-.763.026-1.526.105-2.29.21-.736.08-1.473.21-2.183.317-.867.105-1.735.158-2.604.264-.816.106-1.658.264-2.473.396-.29.053-.58.158-.87.21-.63.132-1.288.185-1.92.396-1.13.344-2.263.74-3.368 1.16-1.027.422-2.027.87-3 1.397-1 .552-1.948 1.21-2.895 1.844a45.325 45.325 0 0 0-2.66 1.923c-.84.66-1.63 1.397-2.394 2.135-.42.42-.763.922-1.158 1.396-.657.765-1.315 1.502-1.947 2.293-.524.66-1 1.344-1.5 2.03-.893 1.21-1.656 2.502-2.366 3.794-.29.527-.553 1.054-.816 1.58-.395.79-.816 1.555-1.184 2.372-.264.554-.474 1.16-.632 1.766-.367 1.292-.736 2.61-1.078 3.9-.316 1.16-.395 2.372-.42 3.558-.027 1.054.078 2.082.183 3.136.027.264-.13.58.184.79-.105.29-.026.45.13.5-.182.29.08.476-.024.74-.027.052.08.157.13.236 0 .08-.025.185 0 .264.028.237.133.474.133.738 0 .184.157.395.21.58.026.078 0 .21-.053.263-.158.184-.132.342.105.448.133.342.08.5.054.66.052.236-.027.315 0 .368.21.422.29.896.315 1.37 0 .106.053.212.106.343.026 0 0 .5 0 .5.13-.078.237-.104.368-.157.08.342.158.66.263.95.132.21.132.314.08.34.105.474.157.922.34 1.37 0-.5-.05-1-.13-1.475.368.132.684.263.895.263.027-.08.053-.184.08-.237-.158-.157-.29-.394-.448-.552.053.21 0 .29 0 .37-.105-.054-.237-.107-.368-.16.105-.13.21-.263.368-.42 0-.238-.13-.45-.5-.423.158-.052.316-.13.5-.184.29-.157-.026-.447-.026-.816.026-.447-.237-.895-.316-1.37-.132-.737-.105-1.844-.184-2.582-.158-.132-.29.21-.316.237.08.632.158 1.264.21 1.897-.157-.527-.263-1.107-.394-1.74-.027.185-.053.264-.053.37-.13.13-.026.29.053.474-.184-.08-.395-.052-.395-.052v.738c-.262-.264-.34-.474-.473-.66-.052-.21-.08-.42-.13-.63.05-.133 0-.212 0-.29a15.968 15.968 0 0 1-.08-.634c.026-.026-.026-.42-.026-.42.21.025.343.05.474.05-.263-.34-.08-.552.027-.763.053-.106.237-.13.29-.238.21-.395.553-.71.553-1.212 0-.237.08-.5.105-.738.053-.448.105-.896.13-1.344.054-.58 0-1.16.133-1.713.212-.92.475-1.843.764-2.766.21-.66.448-1.29.71-1.95.395-1.028.764-2.056 1.264-3.03.71-1.424 1.526-2.794 2.316-4.19.5-.87 1.026-1.687 1.58-2.53.525-.817 1.05-1.66 1.657-2.425a21.452 21.452 0 0 1 2.79-2.978c1.053-.948 2.053-1.923 3.184-2.793a32.218 32.218 0 0 1 4.685-3.005c1.343-.71 2.737-1.266 4.132-1.793.895-.342 1.868-.5 2.79-.79 1.052-.343 2.105-.5 3.21-.527.71-.027 1.395-.106 2.105-.185.632-.05 1.263-.104 1.948-.183-.08.105-.106.158-.132.21-.288.422-.604.844-.894 1.265-.237.343-.5.712-.737 1.054-.422.555-.87 1.108-1.264 1.688-.605.87-1.158 1.766-1.79 2.635-.63.843-1.315 1.634-1.973 2.45-.868 1.134-1.684 2.293-2.552 3.426-.79 1.08-1.63 2.11-2.394 3.19-.684.947-1.29 1.95-1.948 2.923-.973 1.45-1.947 2.872-2.92 4.322a271.93 271.93 0 0 1-2.316 3.294c-.053.08-.132.104-.21.157-.21.342-.21.527-.29.685-.21.395-.42.79-.658 1.16-.132.21-.316.394-.474.605-.026-.316.42-.474.21-.87-.13.212-.263.396-.394.607l-.316.63c.105.08.29.133.105.29-.08.133-.158.29-.237.423a.954.954 0 0 0 .29-.264c0 .29-.158.526-.29.763-.105.21-.368.37-.552.527.026.027.21.106.237.132.237-.08.316-.21.343-.132.08-.105.158-.184.184-.263.104-.264.262-.474.525-.58.106-.053.184-.132.263-.21.79-.818 1.606-1.608 2.316-2.478 1.106-1.345 2.106-2.74 3.16-4.11.446-.58.973-1.16 1.446-1.714.078.606.026 1.185 0 1.74-.08.974-.132 1.95-.21 2.95-.027.395 0 .79-.027 1.186 0 .105-.08.184-.08.29 0 .263.08.553.08.817-.08.975-.186 1.923-.265 2.898-.027.21.078.422.13.607-.13 1.422.16 2.925-.078 4.427.184-.29.237-.474.237-.658.025-.158 0-.316 0-.5v-.264c.025-.475.13-.975.078-1.45-.053-.527-.053-1.027.053-1.528.053-.21-.026-.474.106-.738v.395c-.026 1.5.027 3.003-.183 4.505-.027.132.08.37-.21.343-.238.474.052.817-.21 1.08-.054.053.05.29.077.448-.106.317-.106.317.052.343.026.58.08 1.106.105 1.66.42-1 .21-2.03.396-3.058.026.422.053.844.026 1.29 0 .687-.026 1.345-.052 2.03 0 .132-.027.264-.053.396-.08.37-.105.738-.237 1.08-.105.264-.052.66-.052.975v1.003c.105.448-.027.685.052.948-.08.265-.105.344-.08.423l.08.395c.527-.053.29.343.5.553-.158.212-.105.29-.105.397 0 .237-.025.448-.052.685 0 .606-.026 1.212-.026 1.792 0 .08.026.157.026.236 0 .054-.026.74-.026.74.053.078 0 .157-.08.236-.025 0-.104-3.347-.104-3.347h-.395c-.052 1.58.08 3.003-.21 4.48-.316.025-.42.078-.764.078-.816 0-1.632 0-2.448.026-.974 0-1.92.026-2.895.026-.472 0-.972.054-1.446.054-.632 0-1.29-.08-1.92-.08-.975 0-1.922.08-2.896.106-.71.026-1.42.026-2.13.053-.475.025-.95.05-1.422.104-.21.026-.395.105-.658.184-.08 0-.263-.026-.42 0-.265.053-.5.21-.765.264-.395.08-.5.184-.448.58v.263c-.026.052.58-.08.58-.08-.054 0-.08.158-.16.29.212-.08.343-.132.475-.184.395.185.737.08 1.052.16 1.026.262 2.078.37 3.13.473.685.053 1.343.08 2.027.105.973.053 1.947.106 2.92.106.816 0 1.606-.08 2.42-.08 1.13 0 2.264.052 3.395.08.237 0 .5-.028.763-.028h1.92c1.712-.052 3.422-.08 5.133-.13.975-.028 1.975-.08 2.948-.107l3-.08c1.158-.026 2.316-.026 3.448-.05.868 0 1.71-.03 2.58-.055.972-.026 1.972-.105 2.946-.157.527-.027 1.054-.08 1.58-.132.632-.052 1.29-.13 1.92-.157.948-.054 1.922-.08 2.87-.133 1.184-.078 2.368-.183 3.578-.21 1.106-.052 2.237-.026 3.343-.052.974-.027 1.948-.08 2.948-.106l1.66-.08s1.104-.026 1.657-.08c.947-.052 1.894-.157 2.842-.183.604-.027 1.21 0 1.815-.027.973-.026 1.973-.08 2.947-.08.367 0 .762.054 1.236.08-.21.185-.342.29-.5.422.105.026.21.08.316.132a.71.71 0 0 1-.42.13c-.054.133-.107.186-.16.45h.474c-.184 0-.342.237-.526.395-.21-.054-.395 0-.5.29.184.104.158.183.132.29-.316.104-.553.21-.42.552-.107.052-.238.105-.37.184-.13.21-.368.263-.316.553.106.025.21.08.29.104-.132.053-.263.132-.395.184-.473.29-.262.422-.157.554-.08.053-.158.105-.237.132.052.237.13.29.157.29a9.3 9.3 0 0 0-.395.316c-.08.237-.185.342-.29.5s-.158.37-.29.527c-.552.607-.947 1.32-1.657 1.793-.264.185-.5.422-.737.66-.474.447-.895.948-1.395 1.37a29.595 29.595 0 0 1-2.052 1.554 151.56 151.56 0 0 1-2.604 1.792c-.474.315-1 .552-1.5.842s-.974.554-1.474.843c-.316.21-.606.5-.948.66-.868.37-1.79.685-2.684 1.028-.87.37-1.5.685-2.158.922-.605.21-1.237.37-1.868.5-.21.054-.448 0-.685.027-.448.08-.895.186-1.343.238-1.158.158-2.316.264-3.473.422-.685.08-1.343.21-2.027.29-.473.026-.973-.026-1.447-.026-.342 0-.71.08-1.053.027-.552-.08-1.105-.21-1.658-.316-.13-.026-.316-.08-.42-.026-.21.106-.396-.052-.607 0-.13.027-.262-.08-.394-.08-.106-.025-.238.028-.37 0-.29-.078-.552-.183-.87-.157-.313.026-.63-.132-.97-.21-.475-.106-.92-.21-1.396-.317a2.38 2.38 0 0 1-.525-.237c-.685 0-1.133-.026-1.554-.185-.368-.13-.71-.315-1.105-.262-.104.026-.183-.026-.29-.026-.08-.106-.157-.317-.235-.317-.526.027-.842-.42-1.29-.553-.236-.08-.42-.343-.657-.422-.58-.237-1.052-.737-1.71-.816-.21-.027-.42-.132-.658-.21.08.104.13.183.21.262-.763-.37-1.473-.79-2.184-1.186-.104-.026-.183-.13-.262-.184l-.71-.474c-.395.08-.553-.08-.66-.132-.71-.5-1.525-.817-2.21-1.37-.29-.238-.63-.396-.84-.686-.37-.448-.817-.764-1.317-1.027-.394-.21-.762-.448-1.13-.685-.185-.132-.37-.29-.37-.58 0-.185-.078-.37-.315-.264-.105-.158-.21-.342-.342-.395-.316-.13-.526-.37-.763-.58s-.42-.5-.71-.605c-.527-.21-.843-.658-1.158-1.027-.738-.87-1.396-1.82-2.08-2.74-.053-.08-.158-.133-.237-.212.105.29.237.527.368.79-.262-.105-.446-.29-.604-.474-.027.027 1.815 3.057 1.815 3.057.16.237.29.475.448.712a.813.813 0 0 1-.79-.422c-.236-.42-.5-.684-1.026-.63a4.588 4.588 0 0 1-.13-.58c-.107 0-.185 0-.37-.027.37.58.685 1.08 1.027 1.66-.133-.08-.21-.132-.265-.158.473.5.815 1.133 1.42 1.45.132.605.816.895.974 1.475-.13-.027-.238-.053-.37-.08-.21-.263-.447-.526-.683-.816.052.184.13.342.236.474.316.395.606.79.974 1.133.132.134.316.187.316.424.21.105.29.13.368.13.054.16-.025.397.29.344.21.395.42.395.71.264.343.343.528.37.764.16 0 .13.026.262.026.368.105-.053.08-.132.08-.264.13.105.21.158.262.21.263.37.5.712.868 1.002.5.422.948.87 1.42 1.265.922.765 1.95 1.398 2.975 1.977 1.264.712 2.475 1.476 3.764 2.16 1.552.818 3.21 1.372 4.92 1.767.632.132 1.237.263 1.87.42.55.16 1.104.397 1.657.528.842.185 1.71.343 2.552.5.183.027.37.054.58.08.235.053.524-.053.577.027.132.21.237.104.395.078.184-.053.395-.053.605-.053.737.026 1.447.184 2.184.132.16 0 .396-.133.528.13.236-.105.368-.105.473-.13.028.236 0 .236-.05.262-.054.026-.133.053-.238.132.947.184 1.842.21 2.63 0 1.37.105 2.554-.053 3.686-.448.105.132.184.316.342.053.052-.08.184-.107.29-.133.236-.053.526-.158.736-.08.238.08.317-.13.5-.13.317 0 .606-.027.896-.08.158-.026.316-.105.5-.158a1.285 1.285 0 0 0-.58-.133c.317-.158.606-.29.896-.42-.053.078-.106.183-.21.183h.367c-.08 0-.185.237-.316.395.946-.237 1.814-.448 2.657-.66-.29-.552.315-.367.526-.684-.263.08-.526.158-.79.21.895-.447 1.816-.842 2.71-1.237-.13.158-.29.237-.525.37.158.025.263.025.342.05.42.133.316-.262.447-.5.5 0 .71-.078.947-.158.263-.08.526-.158.79-.263.42-.184.815-.42 1.236-.63.08-.028.21 0 .316 0 .29-.186.394-.344.473-.318.37.053.63-.08.736-.42.184-.133.316-.238.447-.318.578-.316 1.13-.632 1.71-.948.21 0 .316 0 .368-.027.344-.16.66-.342.975-.527a2.258 2.258 0 0 1-.263-.13c.262-.054.34-.08.5-.133.63-.74 1.5-1.24 2.157-1.82.29-.026.29-.105.29-.157.104-.132.21-.29.34-.396.58-.527 1.21-.975 1.737-1.528a37.16 37.16 0 0 0 2.184-2.374c.63-.738 1.264-1.475 1.79-2.292.737-1.133 1.368-2.293 2.026-3.48.474-.842.895-1.685 1.37-2.528.05-.08.157-.185.236-.185.71-.08 1.422-.13 2.106-.21.158-.026.342-.13.5-.21-.08-.132-.132-.29-.21-.422-.106-.16-.264-.29-.37-.45-.104-.13-.183-.29-.262-.447-.08-.13-.158-.236-.237-.37a9.7 9.7 0 0 1-.45-.894c-.026-.08-.08-.21-.052-.29.474-1.027.658-2.134 1.105-3.162.447-1.054.58-2.24.79-3.373.184-1.08.29-2.16.42-3.24.08-.764.185-1.502.21-2.266.16-1.212.106-2.346.08-3.48-.026-1-.08-2.028-.13-3.03zM12.685 66.405c-.184-.21-.342-.448-.526-.658l.08-.08c.287.317.577.633.866.976-.158-.08-.342-.132-.42-.238zm.42.238c.08-.027.16-.027.238-.053.08.132.132.29.21.448-.368-.027-.552-.185-.447-.395zm27.37 10.883v-.08c.5-.052.973-.105 1.473-.157v.077c-.5.08-.973.13-1.473.158zm6.63-.685c-.367.08-.762.133-1.13.186-.132.026-.29.158-.342-.08-.053.027-.106.027-.158.054.13.394.447.078.71.236-.58.08-1.13.132-1.684.21v-.052c.16-.026.343-.053.5-.08v-.078a7.743 7.743 0 0 0-.79-.053c-.077 0-.183.106-.262.132-.105.026-.21.053-.342.053-.447.026-.894.026-1.316.052-.027 0-.08-.026-.106-.026v-.08c1.763-.236 3.5-.473 5.263-.71.027.052.027.105.053.157-.158 0-.263.055-.395.08zm.396-.262c.606-.08 1.16-.132 1.738-.21-1.21.342-1.605.394-1.737.21zM24.58 23.374c.84-1.16 1.71-2.32 2.552-3.505.263-.345.473-.714.736-1.056.08-.106.185-.158.316-.264l-.026-.05c.105-.133.21-.24.263-.344.134-.21.213-.448.318-.685a.385.385 0 0 1 .105-.103c.37.184.37-.21.5-.343.237-.264.474-.553.684-.817.158-.21.316-.395.448-.632.026-.08-.053-.21-.08-.317h-.078c.08-.052.158-.13.237-.184.026 0 .026 0 .052-.026.158-.238.316-.475.474-.686.315-.42.657-.842 1.025-1.21-.052.13-.105.263-.158.368.027 0 .027.027.053.027.316-.422.658-.817.974-1.24-.027-.025-.053-.052-.08-.052-.13.132-.236.264-.368.396-.026-.027-.052-.053-.08-.053.265-.343.528-.685.79-1.08.053.08.106.184.21.395.107-.263.212-.447.29-.632-.078.08-.183.158-.262.238l-.08-.08.474-.71c.5-.712 1-1.45 1.5-2.162.185-.263.42-.474.58-.738.5-1 1.29-1.792 1.894-2.714.132-.184.316-.342.474-.5.13-.16.237-.106.342.026.71.896 1.42 1.818 2.13 2.714.528.66 1.054 1.29 1.554 1.976.605.844 1.184 1.687 1.79 2.53.684.975 1.368 1.95 2.026 2.95 1 1.477 1.947 2.953 2.947 4.428.737 1.08 1.474 2.135 2.184 3.215h-1.344c-1.236-.025-2.5-.13-3.736-.078-1.684.08-3.394.264-5.078.396-2.132.185-4.29.21-6.42.21-.765 0-1.528.107-2.29.16-.922.052-1.817.105-2.738.13-1.08.054-2.13.08-3.21.107-.606.026-1.237 0-1.895 0zm30.183 12.12v.238c-.026 0-.052.027-.105.027-.105-.37-.21-.766-.342-1.135-.263-.765-.553-1.53-1.027-2.214-.528-.737-1-1.5-1.528-2.265-.13-.185-.316-.343-.474-.5-.553-.607-1.106-1.24-1.816-1.687a21.485 21.485 0 0 0-3.29-1.688 7.374 7.374 0 0 1-.92-.474h.63l4.5-.08c.974-.025 1.922-.025 2.895-.078.236 0 .368.08.5.29.236.395.473.79.736 1.186.027.052.08.13.08.21 0 .58 0 1.186.026 1.766.025.606.08 1.186.104 1.792 0 .606-.053 1.238-.026 1.87.027.897.053 1.82.053 2.74zM26.447 26.67c1.237-.053 2.42-.132 3.632-.185.945-.053 1.92-.08 2.866-.132.395-.025.764-.05 1.158 0-.42.212-.842.423-1.21.686-.474.316-.92.737-1.395 1.08-.475.342-.896.764-1.29 1.212-.5.605-1.053 1.132-1.58 1.712-.37.422-.79.817-1.105 1.265-.447.58-.842 1.21-1.263 1.87.132-2.504.29-4.98.184-7.51zm17.185 25.35c-.843.21-1.71.448-2.58.553-.736.106-1.5.08-2.263.08a25.42 25.42 0 0 1-2.028-.08c-.763-.078-1.526-.157-2.263-.5-.633-.29-1.29-.553-1.92-.87-.634-.316-1.265-.684-1.74-1.264-.34-.423-.815-.765-1.236-1.134.08.316.263.58.553.764-.132.158-.316.08-.58-.343-.078.053-.157.08-.21.106.08-.185.158-.37.237-.527-.105-.21-.237-.448-.342-.66-.21-.342-.42-.71-.605-1.053-.053-.08-.053-.158-.105-.237a5.893 5.893 0 0 1-.37-.475c-.21-.315-.394-.657-.657-.974 0 .08.027.158.027.264-.027 0-.053.026-.053.026l-.554-1.344c-.026 0-.026 0-.052.026l.473 1.74c-.026 0-.052.025-.08.025-.077-.104-.156-.21-.21-.34-.052-.212-.21-.212-.34-.133-.08.053-.133.237-.106.316.185.448.395.896.606 1.344.052.158.105.29.184.448.027.053.106.105.106.184.106.21.185.42.316.606.237.316.5.632.737.948.235.316.445.66.656.975.026.053.105.053.13.08.133.395.58.684.896.526.08.606.737.817 1 1.397a11.957 11.957 0 0 1-.763-.343c-.027.026-.027.052-.054.105.316.158.632.316.92.5.265.16.528.317.765.5.316.29.685.45 1.13.554a.282.282 0 0 0-.05-.107c.736.343 1.5.712 2.078 1-2.737.054-5.658.107-8.685.16 0-.5-.026-.975-.026-1.476 0-.21.052-.395.025-.606-.08-1.21-.08-2.424-.237-3.61-.157-1.264-.157-2.503-.13-3.77.025-.683-.027-1.394-.054-2.08 0-.922 0-1.82.028-2.74 0-.132.053-.237.106-.37h.08c.025.054 0 .133.05.16.08.08.212.21.265.184.157-.106.394-.21.447-.37.13-.315.184-.658.184-.974 0-.236.106-.394.21-.553.054-.08.08-.158.133-.263-.105-.08-.21-.132-.342-.237.106-.29.08-.633.475-.79.052-.027.052-.16.08-.238.025-.213.05-.45.078-.66.052.08.08.105.13.157a.42.42 0 0 1 .054-.08c0-.104-.026-.315 0-.315.316-.053.184-.395.342-.553.025-.028-.027-.107-.027-.16 0-.052 0-.13.026-.13.367-.08.315-.475.552-.66.08-.053.105-.13.21-.263.21.368-.158.553-.184.816.446-.263.578-.895.315-1.08.105-.08.21-.184.29-.29.29-.316.604-.606.868-.922.185-.236.29-.526.474-.763.106-.132.316-.237.474-.317.474-.262.92-.552 1.21-1 .053-.053.132-.105.21-.158.08-.053.238-.053.264-.132.027-.052-.052-.184-.105-.263.104-.053.21-.158.42-.264-.08.158-.105.264-.158.37l.13.13c.238-.184.606-.394.843-.552 0-.025-.132-.13-.132-.13-.157.08-.394.21-.63.316.05-.08.05-.132.08-.158.367-.237.735-.474 1.13-.66.92-.42 1.842-.842 2.763-1.237.158-.08.37-.026.553-.026.078 0 .13 0 .21-.026.42-.132.842-.264 1.263-.37.183-.052.393-.078.58-.078.787.025 1.577.025 2.366.078.342.026.658.105.974.21a9.88 9.88 0 0 1 1.184.5c.447.24.868.502 1.29.792.763.5 1.473 1.054 2.236 1.502.737.448 1.316 1.054 1.79 1.74.58.816 1.237 1.554 1.5 2.555l.394 1.74c.08.316.264.632.185 1-.133.66-.238 1.345-.343 2.004-.052.265-.105.53-.078.79.05.82-.265 1.53-.58 2.268-.106.237-.264.475-.395.738a.798.798 0 0 0 .21.106l.237-.474c.027 0 .027 0 .053.027-.132.368-.237.764-.37 1.133-.314.817-.63 1.66-1.025 2.45-.21.448-.58.817-.842 1.24-.262.368-.473.763-.736 1.106-.237.29-.473.58-.79.79-.71.527-1.447 1.054-2.21 1.476-.473.29-1.026.448-1.552.58zm-14.027-1.4l-.026.027c-.055-.026-.134-.052-.186-.105l-.632-.95c-.052-.078-.08-.157-.052-.262.29.448.58.87.895 1.29zm16.37 3.61c1.183-.5 2.157-1.21 3.05-2.028.133-.132.264-.263.422-.37 1.106-.684 1.92-1.633 2.658-2.687.842-1.212 1.395-2.582 2.08-3.873a2.73 2.73 0 0 1 .157-.29c-.053 3.004.29 5.955.684 8.933-2.973.105-6 .21-9.052.316zm26.683-.79c-.026.053-.08.106-.105.16-.027-.054-.027-.133-.053-.24-.158.423-.5.212-.737.212-1.42.027-2.868.027-4.29.027-1.368 0-2.762 0-4.13.024-.448 0-.922.105-1.37.132-1.078.052-2.157.08-3.236.105-.08 0-.158-.13-.29-.236a1.81 1.81 0 0 1-.158.237c-.028-.052-.08-.104-.133-.183-.026.08-.053.158-.08.21H58c-.053-.368-.158-.71-.158-1.08 0-.79.08-1.58.105-2.372.027-.368 0-.71 0-1.054.106.08.185.133.29.21.052-.103.105-.182.158-.26 0 0-.053-.028-.106-.08.05-.027.104-.08.104-.106.026-.08.08-.158.08-.21 0-.185-.054-.343-.08-.5.026 0 .052 0 .08-.028l.157.79h.08c-.106-.183.236-.342-.053-.552-.026-.027.026-.185.026-.264-.08-.157-.13-.315-.21-.526.026-.026.105-.053.184-.08-.105-.052-.184-.104-.263-.13.263-.238.263-.37.026-.633.054-.025.106-.025.106-.05 0-.238 0-.475-.052-.71-.053-.266.08-.58-.316-.74a.79.79 0 0 0 .105.21s-.08.027-.158.08c-.342-.317-.13-.74-.21-1.213.184.053.316.106.447.16-.053-.186-.184-.397-.263-.634h-.107v-1.74c0 .027.184.027.29.054 0-.027.025-.053.025-.08-.08-.105-.185-.21-.29-.342l.053-.053c-.21-.262-.105-.63-.105-.71V39.4c.264.264-.13.606.264.764v-.263h-.027c-.026-.395-.026-.79-.052-1.186h-.052c-.027.054-.027.08-.054.133h-.052l.158-6.298c.263.342.552.66.736 1 .606 1.108 1.395 2.057 2.132 3.058.632.87 1.21 1.818 1.79 2.714.71 1.08 1.394 2.16 2.105 3.24a81.41 81.41 0 0 0 1.63 2.426c.5.71 1.028 1.396 1.554 2.082.446.606.92 1.212 1.367 1.818.527.738 1.053 1.475 1.58 2.187.262.368.552.737.84 1.106.16.21.396.37.554.5-.025 0-.052 0-.104-.026.08.105.13.184.184.237.29.158.316.316.158.554zM74 46.854v-.185c0 .052.026.13 0 .184zm.895-11.62c-.027 0-.184-.16-.21-.186-.027.08 0 .158-.053.264-.027-.078-.21-.052-.21-.13-.027.368.157.737.13 1.106.08-.053.395-.08.474-.158.027.026.08.052.106.052-.527.396-.395.79-.158 1.24.052.104.21.315.052.526-.052.053.027.21.053.343h.077v.05l-.237.08c-.052-.08-.367-.236-.367-.37v1.346c.263.08.263.448.368.633a.768.768 0 0 0 .107-.21l.027.024c-.027.158-.053.316-.106.475-.052.236-.105.447-.13.684 0 .026.05.08.05.105-.288.66-.13 1.396-.235 2.08-.08.5 0 1.03-.053 1.556-.054.448-.16.922-.264 1.37-.027.08-.08.105-.21.158.052-.316.026-.527-.027-.817-.028 0-.37-.184-.397-.184 0 .37.21.87.29 1.29-.08-.026-.395-.21-.42-.21-.054.316-.054.738-.08 1.08-.027.264-.263.5-.29.79 0 .16.184.264.158.528h.21c0-.526.238-1 .238-1.554h.078c.027.053.106.106.08.132-.053.29-.16.606-.132.896 0 .158.13.316.08.5-.054.16-.08.317-.107.554-.027-.132-.053-.184-.053-.263-.026 0-.263-.027-.29-.027-.026.158.185.316.158.448-.026.026-.052.026-.105.053l-.868-1.266c-.686-1-1.37-2.003-2.054-3.03a6.312 6.312 0 0 1-.475-.79 37.09 37.09 0 0 0-2.71-4.033c-.762-.974-1.37-2.03-2.08-3.055-.656-.975-1.314-1.924-1.972-2.9-.237-.315-.526-.605-.737-.948-.683-1.08-1.29-2.187-1.972-3.267-.58-.897-1.21-1.767-1.816-2.636-.21-.29-.42-.607-.632-.923a.37.37 0 0 1-.052-.182c-.053-.58-.106-1.16-.132-1.713 0-.527.053-1.054.053-1.608v-.474c0-.132.025-.237.025-.37.025-.025.052-.078.078-.104-.763 0-1.553-.028-2.316 0-.5.025-.763-.186-1.105-.555-1-1.133-1.737-2.424-2.605-3.636a162.42 162.42 0 0 0-2.5-3.427c-.685-.922-1.37-1.818-2.053-2.74-.764-1.054-1.5-2.108-2.29-3.162a381.983 381.983 0 0 0-2.895-3.794c-.45-.58-.95-1.133-1.45-1.74.343.054.66.106.975.133l1.264.08c.947.077 1.894.13 2.84.26.79.107 1.58.265 2.396.396 1.738.29 3.448.765 5.106 1.318.974.316 1.92.738 2.87 1.133 2.13.87 4.157 1.924 6.157 3.03.63.343 1 .896 1.472 1.397.685.712 1.37 1.423 2.027 2.16.762.87 1.472 1.766 2.21 2.662.657.79 1.34 1.58 2 2.372.21.237.37.527.552.79.42.633.895 1.24 1.263 1.924.262.502.42 1.082.604 1.635.262.817.526 1.607.79 2.424.183.606.34 1.24.472 1.87.106.423.08.87.21 1.29.16.556 0 1.16.16 1.715.025.053.05.132.078.185.105.104.184.21.026.368-.025.026-.025.13 0 .21.054-.052.08-.105.133-.184 0 .053.025.08.025.105 0 .104-.027.21 0 .315 0 .052.052.13.078.184.053-.054.105-.08.21-.16.237.897.264 1.793.264 2.715 0 .87.157 1.74-.21 2.583.078-.29-.106-.555-.027-.818z"/><path d="M58.08 45.482c.025 0 .052.027.052.027l-.027-.03c0-.025 0-.025-.026 0zm4.157 26.036c-.29.21-.58.395-.948.474-.028-.026-.028-.053-.054-.08.29-.184.605-.368.895-.553.027.05.08.104.106.157zM12.895 35.81c.29-.367.58-.736.894-1.105.025.026.235.08.262.105-.29.37-.685.87-.974 1.265-.054-.053-.133-.237-.185-.264zM5.42 48.725c-.21-.448-.42-.923-.63-1.37a.91.91 0 0 1 .236-.106c.29.42.42.92.632 1.37 0 0-.21.105-.237.105zm6.712-12.65c-.158.238-.316.502-.474.74-.026-.028-.316.104-.342.078.158-.237.552-.66.71-.896.027.026.053.053.106.08zM59.422 72.6c.025 0 .025-.026.052-.026.184.026.394.052.605.052-.344.237-.555.21-.66-.026zm-47.24-35.418c.028-.08.08-.158.133-.237.052 0 .13-.027.13-.027.107-.184.107-.316.212-.474-.026-.026-.053-.026-.08-.053-.157.108-.315.24-.473.345.053.052.053.08.053.132-.21-.027-.29.08-.395.368-.026.08-.158.106-.29.21-.026.054-.052.186-.105.317l.027.028c-.053.053-.132.08-.132.08-.158.157-.342.29-.5.447-.026.08-.052.158-.052.237.185-.184.5-.527.737-.738l.027.027c.105-.158.184-.316.29-.474.025.026.025.052.052.08-.08.21-.158.446-.237.657-.055.026-.134.08-.134.053-.105.08-.184.184-.29.263l-.473.316c-.263.237-.526.447-.816.685-.184.29-.368.553-.58.896.317-.08.396.053.37.317.368.052.395-.237.5-.448.026-.054.053-.16.105-.186.237-.21.5-.394.763-.605.053-.053.053-.16.053-.238 0-.026-.133-.026-.212-.053.237-.264.58-.71.816-1 .132-.08.263-.186.263-.265-.026-.29.158-.368.37-.474-.106-.08-.133-.157-.133-.183z"/><path d="M12.71 36.892c-.105.184-.21.342-.315.527l-.158-.08c-.105.605-.474 1.132-.842 1.237.105.053.21.106.29.08.078-.027.13-.16.183-.238l.71-1.028.238-.396-.105-.105zM3.948 48.46c.132 0 .264.026.42.026 0-.105.133-.08.133-.184h.08c0 .132.026.237.026.37h-.552c-.027-.027-.132-.186-.106-.212zm-.21-1.212c-.08-.08-.21-.158-.21-.237-.027-.104.052-.235.13-.367.054.184.08.342.132.527-.027.025-.053.052-.053.078zm.658-1.687c.105.266.21.556.316.82a.798.798 0 0 0-.21.105c-.105-.264-.237-.554-.342-.817a.652.652 0 0 1 .237-.106zm58.58 25.194c.13-.052.288-.08.5-.13-.238.183-.422.315-.58.473-.027-.026-.053-.053-.08-.053.053-.105.106-.184.16-.29zM30.63 15.074c.157-.106.29-.185.447-.29l.052.052c-.16.21-.29.42-.475.685-.026-.183-.026-.29-.053-.42-.026 0 0 0 .027-.026zm7.71 13.333c.237-.106.474-.21.763-.343-.026.158-.026.264-.026.37a.927.927 0 0 0-.264-.054c-.158.027-.448.238-.58.264-.025 0 .106-.21.106-.237zm19.74 22.346c.052.263.552.395.052.658.08.055.157.08.236.134a.2.2 0 0 1-.052.106c-.053.025-.158.078-.21.05-.027 0-.08-.104-.08-.157 0-.237.027-.474.053-.79z"/></g></symbol><symbol viewBox="0 0 24 24" id="powerpoint" xmlns="http://www.w3.org/2000/svg"><path d="M6 2h8l6 6v12a2 2 0 0 1-2 2H6a2 2 0 0 1-2-2V4a2 2 0 0 1 2-2m7 1.5V9h5.5L13 3.5M8 11v2h1v6H8v1h4v-1h-1v-2h2a3 3 0 0 0 3-3 3 3 0 0 0-3-3H8m5 2a1 1 0 0 1 1 1 1 1 0 0 1-1 1h-2v-2h2z" fill="#d14524"/></symbol><symbol viewBox="0 0 67.47 70" id="powershell" xmlns="http://www.w3.org/2000/svg"><path d="M18.545 12.4c-3.014 0-6.08 2.34-6.873 5.248L1.91 53.438c-.793 2.908.996 5.248 4.01 5.248h42.887c3.014 0 6.08-2.34 6.873-5.248l9.761-35.79c.794-2.908-.993-5.248-4.007-5.248h-42.89zm4.848 6.243c.652.04 1.29.33 1.76.86l7.96 9.013-3.957 3.246 3.957-3.244 4.832 5.47c.037.042.06.088.094.131.026.034.057.06.082.096.02.028.032.057.05.086.057.087.105.176.15.267.028.06.055.117.08.178a2.546 2.546 0 0 1 .171.764c.005.073.01.146.008.219-.002.09-.01.178-.021.267a2.53 2.53 0 0 1-.036.217 2.56 2.56 0 0 1-.07.252c-.024.076-.048.15-.08.224a2.547 2.547 0 0 1-.111.22 2.503 2.503 0 0 1-.133.218 2.546 2.546 0 0 1-.147.187c-.058.07-.118.137-.185.202-.027.026-.048.057-.076.082-.037.032-.077.054-.116.084-.038.03-.07.065-.11.093L16.8 52.271a2.552 2.552 0 0 1-3.563-.626 2.553 2.553 0 0 1 .63-3.563l18.349-12.853-3.06-3.467-7.839-8.873a2.549 2.549 0 0 1 .225-3.608 2.546 2.546 0 0 1 1.85-.638zm22.441 28.214c1.377 0 2.255 1.083 1.969 2.43-.287 1.347-1.627 2.433-3.004 2.434l-9.957.006c-1.378 0-2.256-1.083-1.969-2.43.287-1.347 1.626-2.433 3.004-2.434l9.957-.006z" fill="#03a9f4" stroke-width="5.342" stroke-linejoin="round"/></symbol><symbol viewBox="0 0 210 210" id="prettier" xmlns="http://www.w3.org/2000/svg"><title>prettier-icon-dark</title><g transform="matrix(.9 0 0 .9 10.5 10.5)" fill="none" fill-rule="evenodd"><rect fill="#56B3B4" x="165" y="40" width="20" height="10" rx="5"/><rect fill="#EA5E5E" x="15" y="200" width="60" height="10" rx="5"/><rect fill="#BF85BF" x="135" y="120" width="40" height="10" rx="5"/><rect fill="#EA5E5E" x="75" y="120" width="50" height="10" rx="5"/><rect fill="#56B3B4" x="15" y="120" width="50" height="10" rx="5"/><rect fill="#BF85BF" x="15" y="160" width="60" height="10" rx="5"/><rect fill="#BF85BF" x="15" y="80" width="60" height="10" rx="5"/><rect fill="#F7BA3E" x="65" y="20" width="110" height="10" rx="5"/><rect fill="#EA5E5E" x="15" y="20" width="40" height="10" rx="5"/><rect fill="#F7BA3E" x="55" y="180" width="20" height="10" rx="5"/><rect fill="#56B3B4" x="55" y="60" width="20" height="10" rx="5"/><rect fill="#56B3B4" x="15" y="180" width="30" height="10" rx="5"/><rect fill="#F7BA3E" x="15" y="60" width="30" height="10" rx="5"/><rect fill="#56B3B4" x="95" y="100" width="90" height="10" rx="5"/><rect fill="#F7BA3E" x="45" y="100" width="40" height="10" rx="5"/><rect fill="#EA5E5E" x="15" y="100" width="20" height="10" rx="5"/><rect fill="#BF85BF" x="105" y="40" width="50" height="10" rx="5"/><rect fill="#56B3B4" x="15" y="40" width="80" height="10" rx="5"/><rect fill="#F7BA3E" x="45" y="140" width="100" height="10" rx="5"/><rect fill="#BF85BF" x="15" y="140" width="20" height="10" rx="5"/><rect fill="#EA5E5E" x="135" y="60" width="60" height="10" rx="5"/><rect fill="#F7BA3E" x="135" y="80" width="60" height="10" rx="5"/><rect fill="#56B3B4" x="15" width="130" height="10" rx="5"/></g></symbol><symbol viewBox="0 0 80 80" id="protractor" xmlns="http://www.w3.org/2000/svg"><defs><clipPath id="hxa"><path transform="scale(1 -1)" fill="#564b55" stroke-width="27.224" d="M-2.983-69.251h69.412v67.108H-2.983z"/></clipPath></defs><g transform="matrix(1.13039 0 0 -1.13039 5.714 82.137)" clip-path="url(#hxa)"><g transform="scale(.1)"><path d="M1180.54 92.324c-5.53 0-9.93-1.797-13.23-5.39-3.29-3.614-5.22-8.594-5.81-14.97h36.02c0 6.583-1.47 11.622-4.4 15.126-2.93 3.496-7.12 5.234-12.58 5.234zm2.84-62.656c-10.19 0-18.22 3.086-24.11 9.297-5.88 6.21-8.83 14.824-8.83 25.84 0 11.101 2.73 19.922 8.21 26.464 5.45 6.524 12.81 9.805 22.02 9.805 8.63 0 15.46-2.851 20.48-8.523 5.03-5.676 7.55-13.157 7.55-22.461v-6.613h-47.45c.21-8.086 2.26-14.22 6.12-18.418 3.89-4.18 9.34-6.29 16.38-6.29 7.42 0 14.76 1.563 22 4.669V34.14c-3.68-1.602-7.18-2.746-10.48-3.438-3.28-.684-7.24-1.035-11.89-1.035M1272.34 30.918v44.57c0 5.606-1.28 9.805-3.82 12.559-2.56 2.773-6.56 4.16-12.02 4.16-7.2 0-12.49-1.953-15.84-5.851-3.34-3.895-5.03-10.32-5.03-19.286V30.918h-10.42v68.887h8.47l1.71-9.422h.5c2.14 3.387 5.14 6.023 8.99 7.887 3.85 1.867 8.15 2.804 12.88 2.804 8.29 0 14.54-2.011 18.73-6.015 4.19-3.985 6.28-10.391 6.28-19.192V30.918h-10.43M1328.96 38.406c7.1 0 12.27 1.938 15.48 5.813 3.22 3.879 4.81 10.129 4.81 18.758v2.199c0 9.765-1.62 16.726-4.87 20.898-3.25 4.18-8.44 6.25-15.56 6.25-6.11 0-10.79-2.383-14.04-7.129-3.26-4.746-4.88-11.472-4.88-20.136 0-8.797 1.61-15.45 4.84-19.93 3.23-4.484 7.97-6.723 14.22-6.723zm20.85 1.762h-.56c-4.83-7.004-12.02-10.5-21.62-10.5-9.01 0-16.03 3.066-21.04 9.238-5 6.153-7.5 14.922-7.5 26.27 0 11.355 2.51 20.176 7.54 26.465 5.03 6.289 12.03 9.433 21 9.433 9.34 0 16.5-3.398 21.49-10.195h.81l-.43 4.96-.25 4.845v28.039h10.43V30.918h-8.49l-1.38 9.25M1434.91 38.27c1.85 0 3.63.136 5.34.421 1.72.274 3.09.547 4.1.84v-7.976c-1.15-.559-2.81-.996-5.01-1.36-2.18-.351-4.17-.527-5.94-.527-13.32 0-19.97 7.012-19.97 21.055V91.71h-9.88v5.027l9.88 4.336 4.38 14.707h6.04V99.805h20V91.71h-20V51.16c0-4.15.98-7.333 2.96-9.56 1.97-2.206 4.67-3.331 8.1-3.331M1463.81 65.43c0-8.809 1.76-15.508 5.27-20.118 3.53-4.609 8.69-6.906 15.53-6.906s12.01 2.297 15.56 6.875c3.53 4.602 5.3 11.301 5.3 20.149 0 8.75-1.77 15.41-5.3 19.953-3.55 4.539-8.77 6.824-15.69 6.824-6.82 0-11.99-2.246-15.47-6.73-3.46-4.48-5.2-11.16-5.2-20.047zm52.47 0c0-11.23-2.83-20-8.48-26.309-5.66-6.309-13.47-9.453-23.44-9.453-6.17 0-11.64 1.445-16.42 4.336-4.78 2.89-8.46 7.031-11.06 12.45-2.59 5.401-3.88 11.73-3.88 18.976 0 11.23 2.8 19.968 8.41 26.242 5.61 6.258 13.4 9.402 23.38 9.402 9.64 0 17.3-3.222 22.97-9.62 5.69-6.415 8.52-15.087 8.52-26.024M1591.71 92.324c-5.54 0-9.94-1.797-13.23-5.39-3.3-3.614-5.24-8.594-5.81-14.97h36c0 6.583-1.46 11.622-4.39 15.126-2.93 3.496-7.13 5.234-12.57 5.234zm2.83-62.656c-10.19 0-18.22 3.086-24.11 9.297-5.89 6.21-8.83 14.824-8.83 25.84 0 11.101 2.74 19.922 8.2 26.464 5.46 6.524 12.81 9.805 22.04 9.805 8.62 0 15.45-2.851 20.48-8.523 5.03-5.676 7.54-13.157 7.54-22.461v-6.613h-47.45c.21-8.086 2.25-14.22 6.13-18.418 3.87-4.18 9.33-6.29 16.36-6.29 7.43 0 14.77 1.563 22.01 4.669V34.14c-3.69-1.602-7.17-2.746-10.46-3.438-3.3-.684-7.27-1.035-11.91-1.035M1683.5 30.918v44.57c0 5.606-1.27 9.805-3.83 12.559-2.55 2.773-6.55 4.16-12.01 4.16-7.2 0-12.48-1.953-15.83-5.851-3.35-3.895-5.03-10.32-5.03-19.286V30.918h-10.43v68.887h8.48l1.69-9.422h.51c2.14 3.387 5.14 6.023 8.99 7.887 3.84 1.867 8.15 2.804 12.88 2.804 8.3 0 14.54-2.011 18.74-6.015 4.19-3.985 6.29-10.391 6.29-19.192V30.918h-10.45M1740.11 38.406c7.12 0 12.28 1.938 15.49 5.813 3.21 3.879 4.81 10.129 4.81 18.758v2.199c0 9.765-1.62 16.726-4.87 20.898-3.25 4.18-8.43 6.25-15.56 6.25-6.12 0-10.8-2.383-14.05-7.129-3.24-4.746-4.88-11.472-4.88-20.136 0-8.797 1.64-15.45 4.85-19.93 3.22-4.484 7.96-6.723 14.21-6.723zm20.87 1.762h-.57c-4.82-7.004-12.03-10.5-21.62-10.5-9.01 0-16.02 3.066-21.03 9.238-5 6.153-7.52 14.922-7.52 26.27 0 11.355 2.52 20.176 7.55 26.465 5.02 6.289 12.02 9.433 21 9.433 9.34 0 16.5-3.398 21.48-10.195h.83l-.44 4.96-.25 4.845v28.039h10.43V30.918h-8.49l-1.37 9.25M1846.07 38.27c1.85 0 3.64.136 5.36.421 1.7.274 3.07.547 4.08.84v-7.976c-1.13-.559-2.8-.996-5-1.36-2.2-.351-4.18-.527-5.94-.527-13.33 0-19.99 7.012-19.99 21.055V91.71h-9.86v5.027l9.86 4.336 4.4 14.707h6.04V99.805H1855V91.71h-19.98V51.16c0-4.15.98-7.333 2.95-9.56 1.97-2.206 4.68-3.331 8.1-3.331M1894.26 92.324c-5.53 0-9.94-1.797-13.22-5.39-3.31-3.614-5.25-8.594-5.83-14.97h36.01c0 6.583-1.45 11.622-4.38 15.126-2.95 3.496-7.13 5.234-12.58 5.234zm2.83-62.656c-10.19 0-18.22 3.086-24.1 9.297-5.9 6.21-8.84 14.824-8.84 25.84 0 11.101 2.73 19.922 8.2 26.464 5.47 6.524 12.81 9.805 22.03 9.805 8.63 0 15.46-2.851 20.49-8.523 5.03-5.676 7.55-13.157 7.55-22.461v-6.613h-47.46c.22-8.086 2.26-14.22 6.13-18.418 3.87-4.18 9.33-6.29 16.37-6.29 7.42 0 14.75 1.563 22 4.669V34.14c-3.7-1.602-7.17-2.746-10.47-3.438-3.28-.684-7.25-1.035-11.9-1.035M1983.36 49.727c0-6.426-2.4-11.368-7.18-14.844-4.77-3.477-11.47-5.215-20.11-5.215-9.13 0-16.26 1.445-21.37 4.336v9.687a51.32 51.32 0 0 1 10.65-3.964c3.79-.977 7.45-1.457 10.97-1.457 5.46 0 9.64.87 12.57 2.609 2.95 1.738 4.41 4.394 4.41 7.95 0 2.694-1.17 4.98-3.5 6.894-2.32 1.914-6.85 4.152-13.6 6.757-6.41 2.383-10.97 4.473-13.67 6.25-2.71 1.778-4.72 3.81-6.04 6.067-1.31 2.254-1.98 4.96-1.98 8.113 0 5.606 2.29 10.04 6.86 13.281 4.57 3.25 10.84 4.883 18.79 4.883 7.42 0 14.66-1.515 21.74-4.531l-3.71-8.496c-6.9 2.851-13.17 4.277-18.79 4.277-4.94 0-8.67-.77-11.18-2.324-2.52-1.543-3.78-3.691-3.78-6.406 0-1.844.48-3.418 1.42-4.707.95-1.309 2.46-2.54 4.56-3.711 2.09-1.184 6.11-2.871 12.07-5.086 8.16-2.98 13.69-5.98 16.55-8.996 2.87-3.02 4.32-6.809 4.32-11.367M2021.28 38.27c1.85 0 3.64.136 5.35.421 1.71.274 3.09.547 4.09.84v-7.976c-1.14-.559-2.81-.996-5.01-1.36-2.18-.351-4.18-.527-5.93-.527-13.33 0-19.99 7.012-19.99 21.055V91.71h-9.87v5.027l9.87 4.336 4.4 14.707h6.02V99.805h20V91.71h-20V51.16c0-4.15 1-7.333 2.97-9.56 1.98-2.206 4.67-3.331 8.1-3.331M2053.61 30.918h-10.42v68.887h10.42zm-11.31 87.559c0 2.39.59 4.14 1.76 5.253 1.18 1.106 2.65 1.661 4.42 1.661 1.67 0 3.1-.567 4.32-1.7 1.22-1.132 1.82-2.871 1.82-5.214 0-2.344-.6-4.09-1.82-5.247-1.22-1.16-2.65-1.726-4.32-1.726-1.77 0-3.24.566-4.42 1.726-1.17 1.157-1.76 2.903-1.76 5.247M2121.59 30.918v44.57c0 5.606-1.27 9.805-3.83 12.559-2.55 2.773-6.55 4.16-12 4.16-7.21 0-12.49-1.953-15.84-5.851-3.35-3.895-5.03-10.32-5.03-19.286V30.918h-10.43v68.887h8.49l1.69-9.422h.5c2.15 3.387 5.14 6.023 8.99 7.887 3.85 1.867 8.16 2.804 12.88 2.804 8.3 0 14.54-2.011 18.74-6.015 4.19-3.985 6.29-10.391 6.29-19.192V30.918h-10.45M2159.29 77.742c0-4.812 1.35-8.465 4.08-10.926 2.72-2.48 6.51-3.71 11.37-3.71 10.19 0 15.28 4.953 15.28 14.831 0 10.344-5.16 15.532-15.47 15.532-4.9 0-8.67-1.32-11.31-3.965-2.63-2.649-3.95-6.555-3.95-11.762zm-5.67-58.387c0-3.73 1.58-6.55 4.72-8.488 3.14-1.922 7.65-2.879 13.52-2.879 8.75 0 15.24 1.309 19.45 3.926 4.21 2.617 6.31 6.172 6.31 10.652 0 3.723-1.15 6.32-3.45 7.754-2.31 1.457-6.65 2.168-13.01 2.168h-12.51c-4.74 0-8.43-1.12-11.06-3.386-2.65-2.266-3.97-5.508-3.97-9.747zm54.94 80.45v-6.582l-12.76-1.512c1.18-1.477 2.23-3.39 3.15-5.754.91-2.371 1.37-5.039 1.37-8.02 0-6.746-2.29-12.128-6.91-16.152-4.61-4.012-10.93-6.023-18.98-6.023-2.05 0-3.98.156-5.78.5-4.45-2.356-6.67-5.305-6.67-8.871 0-1.883.77-3.282 2.34-4.176 1.54-.902 4.21-1.36 7.97-1.36h12.2c7.46 0 13.19-1.574 17.19-4.707 4-3.144 6-7.714 6-13.71 0-7.618-3.06-13.426-9.17-17.43C2192.38 2.004 2183.46 0 2171.72 0c-9 0-15.95 1.68-20.82 5.027-4.88 3.352-7.34 8.079-7.34 14.211 0 4.18 1.35 7.813 4.03 10.88 2.68 3.046 6.45 5.116 11.32 6.21-1.77.8-3.24 2.031-4.44 3.711-1.19 1.68-1.78 3.633-1.78 5.84 0 2.52.66 4.707 2.01 6.602 1.34 1.882 3.44 3.71 6.34 5.468-3.56 1.465-6.46 3.953-8.71 7.48-2.23 3.516-3.35 7.54-3.35 12.06 0 7.55 2.26 13.37 6.79 17.452 4.52 4.082 10.93 6.133 19.22 6.133 3.6 0 6.86-.429 9.75-1.27h23.82M2284.61 91.71h-17.54V30.919h-10.43v60.793h-12.31v4.707l12.31 3.766v3.839c0 16.922 7.4 25.391 22.19 25.391 3.65 0 7.93-.73 12.82-2.195l-2.7-8.364c-4.03 1.301-7.46 1.946-10.31 1.946-3.93 0-6.85-1.309-8.73-3.926-1.89-2.617-2.84-6.816-2.84-12.598v-4.472h17.54V91.71M2302.87 65.43c0-8.809 1.76-15.508 5.28-20.118 3.52-4.609 8.7-6.906 15.52-6.906 6.84 0 12.02 2.297 15.57 6.875 3.54 4.602 5.3 11.301 5.3 20.149 0 8.75-1.76 15.41-5.3 19.953-3.55 4.539-8.78 6.824-15.69 6.824-6.83 0-11.99-2.246-15.46-6.73-3.48-4.48-5.22-11.16-5.22-20.047zm52.48 0c0-11.23-2.82-20-8.47-26.309-5.67-6.309-13.48-9.453-23.46-9.453-6.15 0-11.62 1.445-16.4 4.336-4.77 2.89-8.47 7.031-11.06 12.45-2.59 5.401-3.9 11.73-3.9 18.976 0 11.23 2.81 19.968 8.43 26.242 5.6 6.258 13.4 9.402 23.38 9.402 9.63 0 17.28-3.222 22.97-9.62 5.68-6.415 8.51-15.087 8.51-26.024M2403.79 101.074c3.07 0 5.8-.254 8.22-.761l-1.43-9.676c-2.86.633-5.37.933-7.55.933-5.58 0-10.33-2.261-14.3-6.785-3.95-4.531-5.94-10.156-5.94-16.902V30.918h-10.43v68.887h8.62l1.19-12.754h.5c2.56 4.48 5.63 7.949 9.23 10.37 3.61 2.423 7.56 3.653 11.89 3.653M2500.33 69.766l-10.68 28.476c-1.39 3.594-2.81 8.028-4.28 13.262-.93-4.024-2.24-8.438-3.96-13.262l-10.81-28.476zm14.77-38.848l-11.44 29.227h-36.83l-11.32-29.227h-10.81l36.34 92.273h8.98l36.13-92.273h-11.05M2583.07 30.918v44.57c0 5.606-1.27 9.805-3.83 12.559-2.55 2.773-6.55 4.16-12 4.16-7.21 0-12.49-1.953-15.84-5.851-3.35-3.895-5.03-10.32-5.03-19.286V30.918h-10.43v68.887h8.48l1.69-9.422h.51c2.14 3.387 5.14 6.023 8.99 7.887 3.84 1.867 8.15 2.804 12.88 2.804 8.3 0 14.54-2.011 18.74-6.015 4.19-3.985 6.29-10.391 6.29-19.192V30.918h-10.45M2620.76 77.742c0-4.812 1.36-8.465 4.08-10.926 2.73-2.48 6.53-3.71 11.37-3.71 10.2 0 15.28 4.953 15.28 14.831 0 10.344-5.15 15.532-15.45 15.532-4.91 0-8.68-1.32-11.32-3.965-2.64-2.649-3.96-6.555-3.96-11.762zm-5.66-58.387c0-3.73 1.57-6.55 4.71-8.488 3.15-1.922 7.65-2.879 13.53-2.879 8.75 0 15.23 1.309 19.44 3.926 4.21 2.617 6.31 6.172 6.31 10.652 0 3.723-1.14 6.32-3.45 7.754-2.31 1.457-6.64 2.168-13 2.168h-12.51c-4.74 0-8.43-1.12-11.07-3.386-2.63-2.266-3.96-5.508-3.96-9.747zm54.94 80.45v-6.582l-12.76-1.512c1.18-1.477 2.22-3.39 3.14-5.754.92-2.371 1.38-5.039 1.38-8.02 0-6.746-2.3-12.128-6.92-16.152-4.61-4.012-10.92-6.023-18.97-6.023-2.05 0-3.99.156-5.78.5-4.46-2.356-6.67-5.305-6.67-8.871 0-1.883.78-3.282 2.33-4.176 1.55-.902 4.21-1.36 7.98-1.36h12.2c7.46 0 13.18-1.574 17.18-4.707 4.01-3.144 6-7.714 6-13.71 0-7.618-3.06-13.426-9.17-17.43C2653.87 2.004 2644.94 0 2633.2 0c-9 0-15.95 1.68-20.83 5.027-4.88 3.352-7.33 8.079-7.33 14.211 0 4.18 1.35 7.813 4.02 10.88 2.69 3.046 6.47 5.116 11.32 6.21-1.77.8-3.23 2.031-4.43 3.711-1.19 1.68-1.79 3.633-1.79 5.84 0 2.52.66 4.707 2.01 6.602 1.35 1.882 3.45 3.71 6.35 5.468-3.56 1.465-6.47 3.953-8.71 7.48-2.23 3.516-3.35 7.54-3.35 12.06 0 7.55 2.25 13.37 6.79 17.452 4.52 4.082 10.92 6.133 19.21 6.133 3.62 0 6.86-.429 9.75-1.27h23.83M2692.7 99.805V55.117c0-5.605 1.27-9.805 3.83-12.566 2.56-2.766 6.57-4.145 12.01-4.145 7.2 0 12.47 1.965 15.81 5.903 3.33 3.945 4.99 10.379 4.99 19.304v36.192h10.44V30.918h-8.62l-1.5 9.25h-.58c-2.13-3.41-5.1-5.988-8.88-7.793-3.8-1.809-8.13-2.707-12.99-2.707-8.37 0-14.65 1.992-18.81 5.977-4.18 3.964-6.26 10.351-6.26 19.101v45.059h10.56M2760.61 30.918h10.43v97.805h-10.43zM2810.67 38.27c6.5 0 11.6 1.789 15.31 5.343 3.71 3.575 5.56 8.555 5.56 14.961v6.23l-10.44-.448c-8.3-.286-14.27-1.583-17.94-3.868-3.66-2.273-5.5-5.82-5.5-10.644 0-3.781 1.14-6.64 3.42-8.613 2.29-1.973 5.48-2.961 9.59-2.961zm23.57-7.352l-2.07 9.805h-.51c-3.44-4.305-6.86-7.227-10.27-8.77-3.42-1.523-7.68-2.285-12.8-2.285-6.83 0-12.17 1.758-16.05 5.273-3.87 3.528-5.81 8.536-5.81 15.032 0 13.906 11.12 21.199 33.37 21.875l11.7.359v4.277c0 5.418-1.17 9.395-3.5 11.985-2.32 2.566-6.03 3.855-11.15 3.855-5.74 0-12.24-1.758-19.49-5.273l-3.21 7.988c3.4 1.836 7.11 3.281 11.16 4.324a47.81 47.81 0 0 0 12.16 1.575c8.23 0 14.3-1.817 18.27-5.461 3.96-3.66 5.93-9.5 5.93-17.54V30.919h-7.73M2893.6 101.074c3.07 0 5.8-.254 8.25-.761l-1.46-9.676c-2.84.633-5.35.933-7.54.933-5.56 0-10.33-2.261-14.3-6.785-3.96-4.531-5.93-10.156-5.93-16.902V30.918h-10.44v68.887h8.61l1.19-12.754h.5c2.57 4.48 5.65 7.949 9.25 10.37 3.6 2.423 7.56 3.653 11.87 3.653M2901.63 6.727c-3.94 0-7.04.558-9.31 1.691v9.121c2.97-.84 6.08-1.25 9.31-1.25 4.14 0 7.3 1.25 9.45 3.77 2.16 2.507 3.24 6.132 3.24 10.859v91.895h10.69V31.797c0-7.95-2.01-14.121-6.04-18.496-4.02-4.383-9.8-6.574-17.34-6.574M2999.96 55.371c0-8.086-2.93-14.394-8.8-18.918-5.87-4.52-13.83-6.785-23.88-6.785-10.9 0-19.27 1.406-25.14 4.219v10.3c3.77-1.59 7.88-2.847 12.31-3.765 4.45-.93 8.85-1.399 13.21-1.399 7.12 0 12.49 1.36 16.09 4.063 3.59 2.695 5.4 6.465 5.4 11.277 0 3.196-.63 5.805-1.91 7.832-1.29 2.024-3.42 3.907-6.42 5.625-2.99 1.711-7.56 3.664-13.67 5.84-8.55 3.059-14.66 6.692-18.32 10.871-3.66 4.2-5.51 9.668-5.51 16.407 0 7.089 2.68 12.714 7.99 16.914 5.32 4.191 12.36 6.289 21.12 6.289 9.13 0 17.54-1.68 25.2-5.032l-3.32-9.304c-7.59 3.183-14.96 4.785-22.13 4.785-5.66 0-10.07-1.223-13.26-3.652-3.19-2.43-4.78-5.809-4.78-10.118 0-3.191.59-5.8 1.76-7.832 1.17-2.031 3.14-3.886 5.95-5.597 2.78-1.688 7.04-3.563 12.79-5.625 9.63-3.426 16.26-7.118 19.89-11.063 3.62-3.937 5.43-9.043 5.43-15.332M741.648 375.406h30c28.965 0 50.227 5.039 63.774 15.117 13.531 10.079 20.32 25.821 20.32 47.247 0 19.832-6.074 34.628-18.191 44.402-12.141 9.758-31.028 14.641-56.692 14.641h-39.211zm172.192 64.246c0-36.062-11.809-63.691-35.434-82.898-23.621-19.219-57.234-28.82-100.847-28.82h-35.911V198.73h-56.445v345.329h99.438c43.14 0 75.457-8.829 96.961-26.465 21.496-17.637 32.238-43.614 32.238-77.942M1099.26 464.691c11.17 0 20.39-.789 27.63-2.371l-5.43-51.718c-7.88 1.894-16.07 2.832-24.57 2.832-22.2 0-40.19-7.246-53.97-21.731-13.78-14.48-20.66-33.301-20.66-56.453V198.73h-55.514v261.227h43.464l7.32-46.055h2.83c8.66 15.594 19.96 27.95 33.9 37.09 13.93 9.141 28.93 13.699 45 13.699M1206.88 329.82c0-60.308 22.28-90.465 66.85-90.465 44.08 0 66.13 30.157 66.13 90.465 0 59.688-22.21 89.512-66.61 89.512-23.31 0-40.2-7.707-50.67-23.144-10.47-15.43-15.7-37.54-15.7-66.368zm190.13 0c0-42.672-10.95-75.972-32.83-99.898-21.89-23.945-52.35-35.918-91.41-35.918-24.41 0-45.97 5.508-64.7 16.543-18.75 11.016-33.16 26.836-43.23 47.48-10.08 20.625-15.11 44.551-15.11 71.793 0 42.364 10.86 75.43 32.58 99.2 21.73 23.777 52.36 35.671 91.89 35.671 37.79 0 67.7-12.156 89.75-36.492 22.05-24.328 33.06-57.121 33.06-98.379M1558.11 238.887c13.54 0 27.07 2.129 40.62 6.386v-41.816c-6.13-2.676-14.05-4.922-23.73-6.738-9.69-1.797-19.73-2.715-30.12-2.715-52.59 0-78.88 27.715-78.88 83.144v140.778h-35.68v24.558l38.26 20.325 18.9 55.261h34.26v-58.113h74.39v-42.031h-74.39v-139.84c0-13.379 3.34-23.242 10.03-29.629 6.69-6.387 15.48-9.57 26.34-9.57M1783.44 464.691c11.17 0 20.38-.789 27.62-2.371l-5.43-51.718c-7.88 1.894-16.06 2.832-24.56 2.832-22.2 0-40.2-7.246-53.97-21.731-13.78-14.48-20.66-33.301-20.66-56.453V198.73h-55.52v261.227h43.46l7.34-46.055h2.82c8.66 15.594 19.95 27.95 33.9 37.09 13.92 9.141 28.93 13.699 45 13.699M1925.05 236.523c20.15 0 36.32 5.625 48.52 16.895 12.21 11.25 18.31 27.051 18.31 47.344v22.676l-33.54-1.407c-26.13-.937-45.16-5.312-57.04-13.105-11.89-7.793-17.82-19.727-17.82-35.781 0-11.661 3.45-20.665 10.39-27.051 6.91-6.387 17.32-9.571 31.18-9.571zm82.66-37.793l-11.11 36.387h-1.87c-12.62-15.918-25.29-26.738-38.04-32.48-12.74-5.742-29.13-8.633-49.13-8.633-25.67 0-45.7 6.934-60.1 20.801-14.41 13.847-21.62 33.457-21.62 58.808 0 26.934 10 47.246 30 60.934 19.99 13.691 50.45 21.172 91.41 22.441l45.09 1.414v13.938c0 16.699-3.88 29.16-11.68 37.441-7.79 8.262-19.88 12.383-36.25 12.383-13.39 0-26.23-1.953-38.5-5.891a294.638 294.638 0 0 1-35.44-13.933l-17.94 39.668c14.17 7.41 29.68 13.035 46.52 16.894 16.85 3.868 32.77 5.789 47.72 5.789 33.22 0 58.31-7.246 75.22-21.726 16.94-14.492 25.4-37.246 25.4-68.262V198.73h-39.68M2220.04 194.004c-39.52 0-69.55 11.543-90.1 34.609-20.55 23.067-30.82 56.172-30.82 99.321 0 43.925 10.74 77.707 32.23 101.339 21.5 23.614 52.56 35.418 93.18 35.418 27.56 0 52.35-5.117 74.41-15.359l-16.78-44.641c-23.46 9.133-42.82 13.704-58.1 13.704-45.19 0-67.79-29.993-67.79-89.981 0-29.293 5.63-51.305 16.89-66.031 11.26-14.707 27.76-22.09 49.48-22.09 24.72 0 48.11 6.152 70.15 18.437v-48.417c-9.92-5.84-20.5-10-31.76-12.52-11.26-2.52-24.93-3.789-40.99-3.789M2451.52 238.887c13.54 0 27.08 2.129 40.63 6.386v-41.816c-6.15-2.676-14.05-4.922-23.73-6.738-9.69-1.797-19.73-2.715-30.12-2.715-52.6 0-78.9 27.715-78.9 83.144v140.778h-35.66v24.558l38.26 20.325 18.9 55.261h34.26v-58.113h74.39v-42.031h-74.39v-139.84c0-13.379 3.34-23.242 10.03-29.629 6.69-6.387 15.47-9.57 26.33-9.57M2585.92 329.82c0-60.308 22.28-90.465 66.84-90.465 44.09 0 66.15 30.157 66.15 90.465 0 59.688-22.22 89.512-66.62 89.512-23.31 0-40.2-7.707-50.67-23.144-10.47-15.43-15.7-37.54-15.7-66.368zm190.13 0c0-42.672-10.94-75.972-32.83-99.898-21.89-23.945-52.36-35.918-91.4-35.918-24.42 0-45.98 5.508-64.72 16.543-18.74 11.016-33.14 26.836-43.22 47.48-10.07 20.625-15.12 44.551-15.12 71.793 0 42.364 10.87 75.43 32.59 99.2 21.74 23.777 52.36 35.671 91.89 35.671 37.79 0 67.7-12.156 89.75-36.492 22.04-24.328 33.06-57.121 33.06-98.379M2972.33 464.691c11.18 0 20.38-.789 27.63-2.371l-5.43-51.718c-7.87 1.894-16.05 2.832-24.57 2.832-22.2 0-40.19-7.246-53.96-21.731-13.78-14.48-20.67-33.301-20.67-56.453V198.73h-55.51v261.227h43.46l7.33-46.055h2.83c8.66 15.594 19.96 27.95 33.89 37.09 13.94 9.141 28.94 13.699 45 13.699" fill="#100f0d"/><path d="M610.11 372.83c0-170.584-138.257-308.862-308.846-308.862-170.602 0-308.846 138.278-308.846 308.863 0 170.576 138.244 308.846 308.846 308.846 170.59 0 308.846-138.27 308.846-308.846" fill="#e53935" stroke-width="1.029"/><path d="M460.694 521.792l-105.04.958-61.415 61.415-72.096-47.883 12.445-12.438-29.207.26-99.129-166.817H67.357l24.39-24.402-24.57-41.363L294.66 64.049c2.192-.04 4.399-.08 6.603-.08 170.416 0 308.585 138.055 308.846 308.408L460.694 521.792" fill="#d51c2f" stroke-width="1.029"/><path d="M149.093 350.258c0 84.048 68.13 152.151 152.171 152.151 84.028 0 152.139-68.103 152.139-152.151zm342.063-7.017v14.046h44.015c-1.75 59.337-25.556 113.104-63.54 153.419L438.75 477.81l-9.925 9.94 32.875 32.887c-40.314 37.983-94.081 61.79-153.41 63.527l-.015-44.003h-14.035v44.003c-59.34-1.737-113.096-25.556-153.41-63.527l32.887-32.887-9.945-9.92-32.883 32.875c-37.975-40.315-61.781-94.082-63.53-153.419h44.002l-.008-14.034H67.176v-51.511h468.176v51.5h-44.196" fill="#f5f5f5" stroke-width="1.029"/></g></g></symbol><symbol id="pug" viewBox="0 0 128 128" xmlns="http://www.w3.org/2000/svg"><style>.st0{fill:#c1272d}.hyst1{fill:#efcca3}.st2{fill:#ed1c24}.hyst3{fill:#ccac8d}.hyst4{fill:#fff}.st5{fill:#ff931e}.st6{fill:#ffb81e}.hyst7{fill:#56332b}.hyst8{fill:#442823}.hyst9{fill:#7f4a41}.hyst10{fill:#331712}.st11{fill:#fc6}.st12{fill:#ccc}.st13{fill:#b3b3b3}.st14{fill:#989898}.st15{fill:#323232}.st16{fill:#1e1e1e}.st17{fill:#4c4c4c}.st18{fill:#e6e6e6}.st19{fill:#606060}</style><path class="hyst1" d="M107.4 50.9c-.2-4.4.4-8.3-1.6-11.6-4.8-8.2-16.8-13-40.8-13v.7h-.5.5v-.7c-24 0-36.6 4.8-41.4 13.1-1.9 3.4-1.7 7.2-2 11.6-.2 3.5-1.8 7.2-1.1 11.2.8 5.2 1.1 10.4 1.9 15.2.6 3.9 6 7.2 6.5 10.9 1.4 10.2 12 14.9 36 14.9v.8h-.6.7v-.8c24 0 34.2-4.7 35.5-14.9.5-3.8 5.5-7 6.1-10.9.8-4.8 1.1-10 1.9-15.2.7-4-.9-7.8-1.1-11.3z"/><path class="hyst3" d="M64.6 54.5c4.3.1 7.3 2.8 10.1 5.3 3.3 2.9 8.9 4.9 11.2 7.4 2.3 2.5 5.3 5 6.4 8.9 1.1 3.9 1.4 8.9 1.4 10.2 0 1.3.7 1 2.7 0 4.7-2.3 9.9-8.5 9.9-8.5-.6 3.9-5.7 7.4-6.2 11.1C98.9 99.1 89 104 64.5 104h-.1.6"/><path class="hyst3" d="M80.4 46.7c.9 3.1 4.1 13.6-2.1 10.1 0 0 2.6 1.5 4.2 7.2 1.7 5.7 5.8 6.4 5.8 6.4s6.7 1.3 11.7-3c4.2-3.6 4.9-10 3.1-14.9-1.8-4.8-5-6.3-9.7-7.3-4.7-1.1-14.1-2-13 1.5z"/><circle cx="92.3" cy="58.1" r="8.8"/><circle class="hyst4" cx="90" cy="54.2" r="2.3"/><path class="hyst1" d="M78.9 57.7s7.9 5.4 12.2 10.7c4.3 5.3 4.2 6.3 4.2 6.3l-3.1 1.4s-4.4-8.3-9.8-11.4c-5.5-3.1-6.1-5.7-6.1-5.7l2.6-1.3z"/><path class="hyst3" d="M64.9 54.5c-4.3.1-7.5 2.8-10.4 5.3-3.3 2.9-9.1 4.9-11.4 7.4-2.3 2.5-5.4 5-6.5 8.9-1.1 3.9-1.5 8.9-1.5 10.2 0 1.3.2 1.4-2.7 0-4.7-2.2-9.9-8.5-9.9-8.5.6 3.9 5.7 7.4 6.2 11.1C30.1 99.1 40 104 64.5 104h.5"/><path class="hyst7" d="M88.1 71.4C83.3 65.5 75.6 60 64.9 60h-.1c-10.7 0-18.4 5.5-23.2 11.4-5 6.1-4.6 8.5-4.6 14.3 0 21 7.4 15 12.3 17.6 5 2.5 10.2 1.7 15.5 1.7h.1c5.4 0 10.5.7 15.5-1.8 4.9-2.5 12.3 3.7 12.3-17.3.1-5.8.4-8.4-4.6-14.5z"/><path class="hyst8" d="M64.4 65.2s-.7 9.7-2.1 11.6l2.6-.6-.5-11z"/><path class="hyst8" d="M65.1 65.2s.7 9.7 2.1 11.6l-2.6-.6.5-11z"/><path class="hyst7" d="M56.7 62.9c-1-2.3 2.6-6 8.3-6.1 5.7 0 9.3 3.7 8.3 6.1-1 2.4-4.6 3.1-8.3 3.2-3.6-.1-7.3-.8-8.3-3.2z"/><path d="M65 65.2c0-.4 3.4-.5 5.2-1.7 0 0-3.7 1.2-4.5.7-.8-.4-1-1.6-1-1.6s-.3 1.2-.9 1.6c-.7.4-4.9-.7-4.9-.7s5.6 1.4 5.6 1.7c0 .3-.1 1.3-.1 2 0 2.5 0 8.7.4 9.2.6.9.4-6.7.4-9.2-.1-.8-.1-1.6-.2-2z"/><path class="hyst9" d="M65.2 78.6c1.7 0 4.7 1.2 7.4 3.1-2.6-2.9-5.7-4.9-7.4-4.9-1.8 0-5.6 2.2-8.3 5.4 2.8-2.2 6.4-3.6 8.3-3.6z"/><path class="hyst8" d="M64.5 96.3c-3.8 0-7.5-1.2-10.9-2.1-.7-.2-1.4.3-2.1.1-6.3-2-11.4-5.4-14.5-9.7v1c0 21 7.4 15.1 12.3 17.6 5 2.5 10.2 1.7 15.5 1.7h.1c5.4 0 10.5.7 15.5-1.8 4.9-2.5 12.3 3.6 12.3-17.4 0-.8 0-1.6.1-2.3-2.9 4.7-8.2 8.4-14.8 10.6-.6.2-2-.3-2.6-.2-3.6 1.2-6.8 2.5-10.9 2.5z"/><path class="hyst8" d="M55 85s-2.5 7.5-.8 10.8l-2.3-1s1.7-7.6 3.1-9.8zM74.8 85s2.5 7.5.8 10.8l2.3-1s-1.8-7.6-3.1-9.8z"/><path class="hyst3" d="M48.6 46.7c-.9 3.1-4.1 13.6 2.1 10.1 0 0-2.6 1.5-4.2 7.2s-5.8 6.4-5.8 6.4-6.7 1.3-11.7-3c-4.2-3.6-4.9-10-3.1-14.9s5-6.3 9.7-7.3c4.7-1.1 14-2 13 1.5z"/><path d="M64.9 76.8c2.7 0 11.1 5.8 11.2 12.9v-.4c0-7.4-6.8-13.3-11.2-13.3-4.4 0-11.2 6-11.2 13.3v.4c.1-7.1 8.5-12.9 11.2-12.9z"/><ellipse transform="rotate(-14.465 66.712 61.468)" class="hyst10" cx="66.7" cy="61.5" rx=".8" ry="1.5"/><ellipse transform="rotate(17.235 62.371 61.462)" class="hyst10" cx="62.4" cy="61.5" rx=".8" ry="1.5"/><circle cx="37.2" cy="58.1" r="8.8"/><circle class="hyst4" cx="39.5" cy="54.2" r="2.3"/><path class="hyst9" d="M67.5 58.2c0-.1-2.3 1-2.9 1.1-.6-.1-2.9-1.2-2.9-1.1h5.8z"/><path class="hyst1" d="M50 57.7s-7.9 5.4-12.2 10.7c-4.3 5.3-4.2 6.3-4.2 6.3l3.1 1.4s4.4-8.3 9.8-11.4 6.1-5.7 6.1-5.7L50 57.7z"/><path class="hyst3" d="M32.7 41.7S30 49.1 24 52.2c0 0 9.4-1.1 8.7-10.5zM95.8 41.7s2.7 7.4 8.7 10.5c0 0-9.4-1.1-8.7-10.5zM78.7 55.5s-5.9-6.2-13.8-6.4h.1.1c-8 .2-13.8 6.4-13.8 6.4 6.9-4.8 12.8-4.7 13.8-4.7-.1 0 6.7-.1 13.6 4.7zM71.8 42.5s-3-4.2-7-4.3h.2c-3 .1-6.9 4.3-6.9 4.3 3.4-3.3 6.9-3.2 6.9-3.2s3.3-.1 6.8 3.2zM37.2 73.2s-4.7 2.3-8.1.9H29c-3-1.7-4.5-6.8-4.5-6.8s3 9 12.7 5.9zM92 73.2s4.7 2.3 8.1.9c4-1.7 4.6-6.8 4.6-6.8s-3 9-12.7 5.9z"/><path class="hyst3" d="M42.6 41.2c2.6-.5 6.9-.6 10.3.5 4.3 1.5.8 7 1.7 7.3.9.3 2.1-3.8 10.1-3.4 8.1.4 9 4 10.1 3.4s-1.1-10 11-7.8c0 0-12.7-3.4-12.1 5.8 0 0-7.3-5.6-17.5-.6.1 0 2.7-8.6-13.6-5.2zM86.9 41.2c.2 0 .3.1.4.1.1 0-.1-.1-.4-.1zM86.9 41.2zM39.1 28.9S28.3 42.5 26.7 47.7c-1.6 5.3-2.8 27-4.2 30.1l-5-21.4 9.2-22.3 12.4-5.2zM89.9 28.9s10.8 13.6 12.4 18.8c1.6 5.3 2.8 27 4.2 30.1l5-21.4-9.2-22.3-12.4-5.2z"/><path class="hyst7" d="M89.4 28.9s11.6 9.7 15 20.9c3.4 11.2 2 24.8 4.6 26.5 3.7 2.4 7.9-11.9 9.3-13.4 2.2-2.4 9.5-8.5 10-9.6.5-1.1-14.8-17.8-21.5-21.1-8.1-3.8-18.1-4.1-17.4-3.3z"/><path class="hyst8" d="M99.3 34.9s13.7 17.5 13.5 39.3l5.5-11.2c-.1 0-4.9-14.3-19-28.1z"/><path class="hyst7" d="M39.1 28.9s-11.6 9.7-15 20.9-2 24.8-4.6 26.5c-3.7 2.4-7.9-11.9-9.3-13.4C8 60.5.7 54.4.2 53.3-.3 52.2 15 35.5 21.7 32.2c8.1-3.8 18.1-4.1 17.4-3.3z"/><path class="hyst8" d="M29.2 34.9S15.5 52.4 15.7 74.2L10.3 63s4.8-14.3 18.9-28.1z"/><path class="hyst3" d="M21.8 74.6s1 5.4 2.6 7.1.5-1.3.5-1.3-1.7-.9-1.4-7.8-1.7 2-1.7 2zM107.1 74.6s-1 5.4-2.6 7.1-.5-1.3-.5-1.3 1.7-.9 1.4-7.8 1.7 2 1.7 2z"/><g><circle class="hyst8" cx="54.5" cy="70.5" r=".8"/><circle class="hyst8" cx="49.9" cy="75.3" r=".8"/><circle class="hyst8" cx="48.4" cy="70.5" r=".8"/></g><g><circle class="hyst8" cx="74" cy="70.5" r=".8"/><circle class="hyst8" cx="78.6" cy="75.3" r=".8"/><circle class="hyst8" cx="80.1" cy="70.5" r=".8"/></g></symbol><symbol viewBox="0 0 50 50" id="puppet" xmlns="http://www.w3.org/2000/svg"><g transform="translate(0 -247)" fill="#fbc02d"><path stroke-width=".283" d="M11.559 249.467h13.587v13.587H11.559zM27.435 265.056h13.587v13.587H27.435zM11.559 281.074h13.587v13.587H11.559z"/><path stroke-width=".256" d="M16.62 251.615l18.305 18.305-3.236 3.236-18.305-18.305z"/><path stroke-width=".256" d="M37.834 271.331L19.53 289.636l-3.237-3.237 18.305-18.304z"/></g></symbol><symbol viewBox="0 0 100 99.999997" id="purescript" xmlns="http://www.w3.org/2000/svg"><path clip-path="url(#SVGID_2_)" d="M98.079 38.548L79.22 19.68l-5.087 5.088L90.447 41.09 74.134 57.41l5.087 5.087 18.858-18.86a3.59 3.59 0 0 0 1.055-2.55 3.578 3.578 0 0 0-1.055-2.54M25.483 42.794l-5.09-5.089L1.53 56.568a3.566 3.566 0 0 0-1.05 2.545c0 .961.373 1.863 1.05 2.542L20.394 80.52l5.089-5.086L9.162 59.113z" fill="#42a5f5" stroke-width="1.192"/><path clip-path="url(#SVGID_2_)" transform="matrix(1.19175 0 0 1.19175 -306.84 -629.047)" fill="#42a5f5" d="M281.841 551.736l6.461 6.037h28.379l-6.461-6.037zM288.302 566.861l-6.463 6.035h28.381l6.463-6.035zM281.838 581.982l6.464 6.035h28.381l-6.463-6.035z"/></symbol><symbol viewBox="0 0 24 24" id="python" xmlns="http://www.w3.org/2000/svg"><path d="M19.14 7.5A2.86 2.86 0 0 1 22 10.36v3.78A2.86 2.86 0 0 1 19.14 17H12c0 .39.32.96.71.96H17v1.68a2.86 2.86 0 0 1-2.86 2.86H9.86A2.86 2.86 0 0 1 7 19.64v-3.75a2.85 2.85 0 0 1 2.86-2.85h5.25a2.85 2.85 0 0 0 2.85-2.86V7.5h1.18m-4.28 11.79c-.4 0-.72.3-.72.89 0 .59.32.71.72.71a.71.71 0 0 0 .71-.71c0-.59-.32-.89-.71-.89m-10-1.79A2.86 2.86 0 0 1 2 14.64v-3.78A2.86 2.86 0 0 1 4.86 8H12c0-.39-.32-.96-.71-.96H7V5.36A2.86 2.86 0 0 1 9.86 2.5h4.28A2.86 2.86 0 0 1 17 5.36v3.75a2.85 2.85 0 0 1-2.86 2.85H8.89a2.85 2.85 0 0 0-2.85 2.86v2.68H4.86M9.14 5.71c.4 0 .72-.3.72-.89 0-.59-.32-.71-.72-.71-.39 0-.71.12-.71.71s.32.89.71.89z"/><path d="M9.264 22.379c-.895-.24-1.581-.799-1.947-1.582-.228-.489-.237-.606-.238-2.957-.001-2.745.057-3.074.666-3.785.193-.226.568-.517.833-.648.47-.23.579-.239 3.839-.288 3.131-.048 3.386-.065 3.814-.264.626-.291 1.07-.687 1.4-1.247.27-.46.278-.522.311-2.29l.034-1.82.932.051c1.075.058 1.504.211 2.098.748.853.77.869.841.869 3.957 0 2.434-.02 2.783-.18 3.075a3.365 3.365 0 0 1-1.337 1.33l-.517.273-3.95.031-3.951.031.068.274c.037.151.164.377.282.503.209.224.262.229 2.433.229h2.22v1.05c0 1.653-.394 2.437-1.54 3.072l-.545.302-2.644.018c-1.455.01-2.782-.018-2.95-.063zm6.12-1.692c.22-.222.253-.325.206-.675-.07-.523-.278-.73-.732-.73-.467 0-.672.217-.735.78-.042.372-.012.496.163.672.3.3.77.28 1.097-.047z" fill="#fc0" stroke="#fc0" stroke-width=".102"/><path d="M9.349 22.38c-.911-.15-1.936-1.074-2.176-1.963-.073-.273-.101-1.279-.079-2.868.033-2.317.047-2.473.27-2.926.13-.263.401-.623.603-.8.674-.592.87-.63 3.484-.675 4.399-.076 4.927-.166 5.705-.967.642-.662.706-.9.774-2.883l.061-1.784.951.055c.523.031 1.11.122 1.304.204.54.225 1.358 1.042 1.472 1.47.153.572.243 3.18.16 4.617-.071 1.23-.093 1.327-.395 1.78-.193.288-.577.647-.966.903l-.647.425-3.922.008c-2.157.004-3.942.028-3.966.052-.115.115.354.82.587.883.14.038 1.181.073 2.314.079l2.06.01v.91c0 1.739-.326 2.446-1.454 3.162l-.631.4-2.543-.011c-1.398-.007-2.733-.043-2.966-.081zm5.98-1.718c.285-.256.313-.328.251-.658-.09-.483-.301-.682-.722-.682-.436 0-.625.193-.715.73-.065.384-.044.453.2.663.358.308.595.295.985-.053z" fill="#fdd835" stroke-width=".102"/><path d="M4.281 17.396c-.88-.215-1.714-.935-2.024-1.747-.149-.389-.168-.804-.142-3.041.027-2.26.054-2.638.215-2.962.259-.519.851-1.092 1.392-1.346.437-.206.632-.217 4.408-.245l3.95-.03-.067-.275a1.367 1.367 0 0 0-.282-.504c-.21-.224-.263-.23-2.433-.23h-2.22l.002-1.143c.003-1.338.157-1.795.84-2.493.746-.763 1.103-.838 4.025-.838 2.961 0 3.28.06 4.067.768.37.333.572.621.728 1.037.201.539.213.735.183 3.072-.035 2.777-.045 2.824-.78 3.598-.787.829-.76.824-4.59.883-3.812.06-3.797.057-4.61.806-.765.706-.917 1.2-.964 3.133l-.04 1.653-.677-.01c-.371-.007-.813-.045-.98-.086zM9.59 5.551c.237-.204.286-.326.286-.72 0-.547-.201-.763-.71-.763-.502 0-.765.248-.765.724 0 .492.141.782.439.902.345.14.444.12.75-.143z" fill="#3c78aa"/></symbol><symbol viewBox="0 0 24 24" id="r" xmlns="http://www.w3.org/2000/svg"><path d="M11.956 4.05c-5.694 0-10.354 3.106-10.354 6.947 0 3.396 3.686 6.212 8.531 6.813v2.205h3.53V17.82c.88-.093 1.699-.259 2.475-.497l1.43 2.692h3.996l-2.402-4.048c1.936-1.263 3.147-3.034 3.147-4.97 0-3.841-4.659-6.947-10.354-6.947m1.584 2.712c4.349 0 7.558 1.45 7.558 4.753 0 1.77-.952 3.013-2.505 3.779a1.081 1.081 0 0 1-.228-.156c-.373-.165-.994-.352-.994-.352s3.085-.227 3.085-3.302-3.23-3.127-3.23-3.127h-7.092v7.413c-2.64-.766-4.462-2.392-4.462-4.255 0-2.63 3.52-4.753 7.868-4.753m.156 4.12h2.143s.983-.05.983.974c0 1.004-.983 1.004-.983 1.004h-2.143v-1.977m-.031 4.566h.952c.186 0 .28.052.445.207.135.103.28.3.404.476-.57.073-1.17.104-1.801.104z" fill="#1976d2" stroke-width="1.035"/></symbol><symbol viewBox="0 0 24 24" id="raml" xmlns="http://www.w3.org/2000/svg"><path d="M5 3h2v2H5v5a2 2 0 0 1-2 2 2 2 0 0 1 2 2v5h2v2H5c-1.07-.27-2-.9-2-2v-4a2 2 0 0 0-2-2H0v-2h1a2 2 0 0 0 2-2V5a2 2 0 0 1 2-2m14 0a2 2 0 0 1 2 2v4a2 2 0 0 0 2 2h1v2h-1a2 2 0 0 0-2 2v4a2 2 0 0 1-2 2h-2v-2h2v-5a2 2 0 0 1 2-2 2 2 0 0 1-2-2V5h-2V3h2m-7 12a1 1 0 0 1 1 1 1 1 0 0 1-1 1 1 1 0 0 1-1-1 1 1 0 0 1 1-1m-4 0a1 1 0 0 1 1 1 1 1 0 0 1-1 1 1 1 0 0 1-1-1 1 1 0 0 1 1-1m8 0a1 1 0 0 1 1 1 1 1 0 0 1-1 1 1 1 0 0 1-1-1 1 1 0 0 1 1-1z" fill="#42a5f5"/></symbol><symbol viewBox="0 0 24 24" id="razor" xmlns="http://www.w3.org/2000/svg"><path d="M15.45 11.91c-.11-2.21-1.75-3.54-3.73-3.54h-.08c-2.29 0-3.55 1.8-3.55 3.84 0 2.29 1.53 3.74 3.54 3.74 2.25 0 3.72-1.65 3.83-3.59m-3.81-5.97c1.53 0 2.97.68 4.02 1.74 0-.51.33-.89.83-.89h.11c.74 0 .89.7.89.92v7.9c-.04.52.54.78.87.44 1.27-1.29 2.78-6.69-.79-9.81-3.33-2.92-7.8-2.44-10.18-.8-2.52 1.74-4.14 5.61-2.57 9.22 1.71 3.95 6.61 5.13 9.52 3.95 1.48-.59 2.15 1.4.65 2.05-2.34.99-8.77.89-11.78-4.32-2.03-3.52-1.93-9.71 3.46-12.92C10.81 1.42 16.24 2.1 19.5 5.5c3.45 3.6 3.25 10.3-.1 12.91-1.51 1.18-3.76.03-3.74-1.7l-.02-.56a5.611 5.611 0 0 1-3.99 1.66C8.63 17.81 6 15.15 6 12.13c0-3.05 2.63-5.74 5.65-5.74z" fill="#42a5f5"/></symbol><symbol viewBox="0 0 24 24" id="react" xmlns="http://www.w3.org/2000/svg"><path d="M12 10.11c1.03 0 1.87.84 1.87 1.89 0 1-.84 1.85-1.87 1.85-1.03 0-1.87-.85-1.87-1.85 0-1.05.84-1.89 1.87-1.89M7.37 20c.63.38 2.01-.2 3.6-1.7-.52-.59-1.03-1.23-1.51-1.9a22.7 22.7 0 0 1-2.4-.36c-.51 2.14-.32 3.61.31 3.96m.71-5.74l-.29-.51c-.11.29-.22.58-.29.86.27.06.57.11.88.16l-.3-.51m6.54-.76l.81-1.5-.81-1.5c-.3-.53-.62-1-.91-1.47C13.17 9 12.6 9 12 9c-.6 0-1.17 0-1.71.03-.29.47-.61.94-.91 1.47L8.57 12l.81 1.5c.3.53.62 1 .91 1.47.54.03 1.11.03 1.71.03.6 0 1.17 0 1.71-.03.29-.47.61-.94.91-1.47M12 6.78c-.19.22-.39.45-.59.72h1.18c-.2-.27-.4-.5-.59-.72m0 10.44c.19-.22.39-.45.59-.72h-1.18c.2.27.4.5.59.72M16.62 4c-.62-.38-2 .2-3.59 1.7.52.59 1.03 1.23 1.51 1.9.82.08 1.63.2 2.4.36.51-2.14.32-3.61-.32-3.96m-.7 5.74l.29.51c.11-.29.22-.58.29-.86-.27-.06-.57-.11-.88-.16l.3.51m1.45-7.05c1.47.84 1.63 3.05 1.01 5.63 2.54.75 4.37 1.99 4.37 3.68 0 1.69-1.83 2.93-4.37 3.68.62 2.58.46 4.79-1.01 5.63-1.46.84-3.45-.12-5.37-1.95-1.92 1.83-3.91 2.79-5.38 1.95-1.46-.84-1.62-3.05-1-5.63-2.54-.75-4.37-1.99-4.37-3.68 0-1.69 1.83-2.93 4.37-3.68-.62-2.58-.46-4.79 1-5.63 1.47-.84 3.46.12 5.38 1.95 1.92-1.83 3.91-2.79 5.37-1.95M17.08 12c.34.75.64 1.5.89 2.26 2.1-.63 3.28-1.53 3.28-2.26 0-.73-1.18-1.63-3.28-2.26-.25.76-.55 1.51-.89 2.26M6.92 12c-.34-.75-.64-1.5-.89-2.26-2.1.63-3.28 1.53-3.28 2.26 0 .73 1.18 1.63 3.28 2.26.25-.76.55-1.51.89-2.26m9 2.26l-.3.51c.31-.05.61-.1.88-.16-.07-.28-.18-.57-.29-.86l-.29.51m-2.89 4.04c1.59 1.5 2.97 2.08 3.59 1.7.64-.35.83-1.82.32-3.96-.77.16-1.58.28-2.4.36-.48.67-.99 1.31-1.51 1.9M8.08 9.74l.3-.51c-.31.05-.61.1-.88.16.07.28.18.57.29.86l.29-.51m2.89-4.04C9.38 4.2 8 3.62 7.37 4c-.63.35-.82 1.82-.31 3.96a22.7 22.7 0 0 1 2.4-.36c.48-.67.99-1.31 1.51-1.9z" fill="#00bcd4"/></symbol><symbol viewBox="0 0 24 24" id="readme" xmlns="http://www.w3.org/2000/svg"><path d="M13 9h-2V7h2m0 10h-2v-6h2m-1-9A10 10 0 0 0 2 12a10 10 0 0 0 10 10 10 10 0 0 0 10-10A10 10 0 0 0 12 2z" fill="#42a5f5"/></symbol><symbol viewBox="0 0 24 24" id="reason" xmlns="http://www.w3.org/2000/svg"><path d="M3 3v18h18V3H3zm5.119 8.993h2.798c.382 0 .71.025.985.075.275.05.534.159.774.326.244.168.435.386.577.654.145.265.218.598.218 1 0 .552-.112 1.001-.335 1.35-.22.348-.536.638-.947.87l2.16 3.203H12.31l-1.763-2.742h-.77v2.742H8.12v-7.478zm6.594 0h4.676v1.447h-3.018v1.29h2.802v1.447h-2.802v1.848h3.018v1.446h-4.676v-7.478zM9.778 13.37v2.014h.513c.266 0 .49-.014.67-.044.18-.03.329-.1.45-.207a.96.96 0 0 0 .253-.34c.055-.128.082-.297.082-.508 0-.187-.034-.35-.1-.483a.698.698 0 0 0-.343-.317 1.086 1.086 0 0 0-.395-.095 6.012 6.012 0 0 0-.526-.02h-.604z" fill="#f44336" stroke-width="1.067"/></symbol><symbol viewBox="0 0 172 193" id="restql" xmlns="http://www.w3.org/2000/svg"><title>Group</title><g transform="translate(14.767 16.713) scale(.82795)" fill="none"><path d="M171.39 55.799c-.975-6.147-4.673-11.642-10.15-14.805L96.381 3.546C93.217 1.72 89.615.756 85.964.756s-7.253.964-10.415 2.788L10.69 40.992A20.896 20.896 0 0 0 .272 59.035v74.89a20.894 20.894 0 0 0 10.416 18.042l64.859 37.446c3.165 1.827 6.767 2.791 10.417 2.791s7.252-.964 10.415-2.79l64.859-37.445c5.479-3.166 9.178-8.66 10.152-14.808zm-16.516 85.147L90.017 178.39a8.104 8.104 0 0 1-8.108 0l-64.857-37.444a8.109 8.109 0 0 1-4.053-7.021v-74.89a8.109 8.109 0 0 1 4.053-7.021l64.857-37.446c1.254-.725 2.654-1.086 4.054-1.086s2.8.361 4.054 1.086l64.857 37.446a8.106 8.106 0 0 1 4.053 7.021v74.89a8.109 8.109 0 0 1-4.053 7.021z" fill="#83e8c2"/><path d="M158.93 59.035a8.109 8.109 0 0 0-4.053-7.021L90.02 14.568c-1.254-.725-2.654-1.086-4.054-1.086s-2.8.361-4.054 1.086L17.055 52.014a8.106 8.106 0 0 0-4.053 7.021v74.89a8.109 8.109 0 0 0 4.053 7.021l64.857 37.444a8.104 8.104 0 0 0 8.108 0l64.857-37.444a8.109 8.109 0 0 0 4.053-7.021zm-46.766 31.681c.119-.069.242-.118.365-.149.044-.012.088-.01.131-.018.076-.012.152-.029.228-.029l.015.001c.02.001.038.005.059.006.093.005.184.019.273.04l.1.03c.077.025.15.057.223.095.028.014.057.027.084.043.094.057.184.122.263.199.007.008.013.017.021.024.07.071.133.15.188.235.018.029.033.059.05.09.04.072.072.148.099.229a1.512 1.512 0 0 1 .081.46v16.209l-3.278 1.893a1.548 1.548 0 0 0-.678.83 1.533 1.533 0 0 0-.098.514v3.785l-14.038 8.104-.01.004a1.55 1.55 0 0 1-.354.146c-.045.012-.09.011-.135.018-.074.012-.15.029-.225.029l-.014-.001c-.02-.001-.039-.005-.059-.006a1.463 1.463 0 0 1-.273-.041c-.034-.008-.066-.019-.1-.03a1.318 1.318 0 0 1-.223-.094c-.029-.015-.057-.027-.084-.044a1.45 1.45 0 0 1-.263-.198c-.009-.008-.015-.019-.023-.027a1.495 1.495 0 0 1-.185-.232c-.019-.029-.034-.06-.051-.09a1.422 1.422 0 0 1-.098-.229 1.702 1.702 0 0 1-.033-.101 1.487 1.487 0 0 1-.048-.358l-.001-.002v-20.053a1.446 1.446 0 0 1 .727-1.255zM85.24 31.369a1.449 1.449 0 0 1 1.452 0l45.741 26.41a1.45 1.45 0 0 1 0 2.512l-17.366 10.027a1.457 1.457 0 0 1-1.452 0l-15.49-8.943 1.727-.996a1.552 1.552 0 0 0 0-2.688l-13.111-7.57c-.239-.139-.508-.207-.775-.207s-.535.068-.775.207l-3.278 1.893-14.038-8.104a1.451 1.451 0 0 1 0-2.513zM57.59 47.558c.251 0 .501.065.726.194l15.489 8.942-1.727.997a1.552 1.552 0 0 0 0 2.688l1.727.996-15.488 8.943a1.457 1.457 0 0 1-1.452 0L39.499 60.291a1.45 1.45 0 0 1 0-2.512l17.366-10.027c.225-.129.475-.194.725-.194zm-9.56 92.328c-.241 0-.489-.062-.724-.196l-17.365-10.026a1.45 1.45 0 0 1-.726-1.256V75.59c0-.847.694-1.453 1.452-1.453.242 0 .49.062.724.197l17.366 10.025c.449.26.726.738.726 1.257v17.886l-1.727-.997a1.552 1.552 0 0 0-2.327 1.344v15.139c0 .555.295 1.067.775 1.344l3.278 1.894v16.209a1.45 1.45 0 0 1-1.452 1.451zm29.828 14.929a1.452 1.452 0 0 1-2.177 1.257l-17.365-10.026a1.452 1.452 0 0 1-.726-1.257v-17.885l1.726.996c.25.145.515.211.773.211.811 0 1.554-.648 1.554-1.555v-1.993l15.489 8.942c.449.26.726.738.726 1.257zm0-32.768c0 .127-.02.246-.049.36-.009.035-.021.067-.032.101-.026.08-.059.157-.099.229-.017.03-.032.061-.05.09a1.48 1.48 0 0 1-.188.235l-.021.025a1.51 1.51 0 0 1-.264.199c-.026.016-.055.028-.082.043a1.597 1.597 0 0 1-.324.124 1.362 1.362 0 0 1-.278.041c-.018.001-.036.006-.055.006l-.015.001c-.077 0-.155-.018-.233-.03-.043-.007-.084-.005-.125-.017a1.484 1.484 0 0 1-.366-.149l-14.035-8.104v-3.784a1.545 1.545 0 0 0-.776-1.343l-3.276-1.892V91.976c0-.127.02-.246.049-.361.009-.034.021-.066.032-.1a1.33 1.33 0 0 1 .099-.229c.017-.03.032-.062.051-.091.054-.084.116-.163.187-.234l.021-.025c.079-.076.168-.142.263-.199.027-.016.056-.029.084-.043a1.476 1.476 0 0 1 .601-.166c.019 0 .036-.005.055-.005l.015-.001c.078 0 .157.018.236.03.04.007.081.005.122.017.124.031.246.08.366.149l17.361 10.023a1.456 1.456 0 0 1 .726 1.259zm-9.984-45.373a1.448 1.448 0 0 1-.544-.55 1.466 1.466 0 0 1 0-1.413c.121-.219.303-.41.544-.55l14.038-8.104 3.277 1.892c.48.276 1.071.276 1.551 0l3.278-1.893 14.038 8.105a1.45 1.45 0 0 1 0 2.513L86.691 86.7a1.447 1.447 0 0 1-1.452 0zm74.842 51.733c0 .518-.276.997-.726 1.256l-45.741 26.409a1.452 1.452 0 0 1-2.177-1.257v-20.053c0-.519.277-.997.727-1.257l15.488-8.941v1.992c0 .906.743 1.555 1.553 1.555.26 0 .523-.066.774-.21l13.11-7.57a1.55 1.55 0 0 0 .776-1.344v-3.784l14.038-8.105a1.452 1.452 0 0 1 2.177 1.257v20.052zm0-32.764c0 .519-.276.997-.726 1.256l-15.489 8.943v-1.993c0-.906-.744-1.554-1.554-1.554a1.519 1.519 0 0 0-.773.21l-1.727.996V85.616c0-.519.277-.997.727-1.257l17.365-10.025c.234-.135.482-.197.724-.197.758 0 1.453.606 1.453 1.453z" fill="#111d5a"/><g fill="#83e8c2"><path d="M59.402 90.568zM94.485 123.06zM94.771 123.29zM77.775 122.51zM77.072 123.33zM77.418 123.09zM77.856 122.05zM76.749 123.45zM94.119 122.41zM77.131 133.51l-15.489-8.942v1.993c0 .906-.743 1.555-1.554 1.555a1.53 1.53 0 0 1-.773-.211l-1.726-.996v17.885c0 .519.276.997.726 1.257l17.365 10.026a1.452 1.452 0 0 0 2.177-1.257v-20.053a1.454 1.454 0 0 0-.726-1.257zM94.25 122.74zM110.28 111.42zM94.494 100.98c.088-.089.189-.168.303-.232l17.365-10.026-17.365 10.026a1.392 1.392 0 0 0-.303.232zM77.627 122.83zM58.027 90.936zM58.374 90.693zM59.044 90.521l-.015.001c.083-.001.167.015.251.029-.079-.012-.158-.03-.236-.03zM57.819 91.195zM58.696 90.568zM57.589 91.977zM76.043 123.46zM57.67 91.516zM75.677 123.31l-14.035-8.11zM76.401 123.5l.015-.001c-.082.001-.166-.016-.248-.029.078.012.156.03.233.03zM112.16 90.716zM77.662 101.27zM113.64 90.734zM96.237 123.31zM113.33 90.597zM112.89 90.52c-.075 0-.151.018-.228.029.081-.014.162-.029.242-.028l-.014-.001zM141.26 74.137c-.241 0-.489.062-.724.197l-17.365 10.025c-.449.26-.727.738-.727 1.257v17.885l1.727-.996c.25-.145.515-.211.773-.21.81 0 1.554.647 1.554 1.554v1.993l15.489-8.943a1.45 1.45 0 0 0 .726-1.256V75.59c0-.847-.695-1.453-1.453-1.453zM112.96 90.526zM95.523 123.5c.074 0 .15-.018.225-.029-.08.013-.159.028-.238.028l.013.001zM95.451 123.5zM85.238 86.7zM95.078 123.43zM141.26 106.9c-.241 0-.489.062-.724.196l-14.038 8.105v3.784c0 .555-.296 1.067-.776 1.344l-13.11 7.57c-.251.144-.515.21-.774.21-.81 0-1.553-.648-1.553-1.555v-1.992l-15.488 8.941c-.449.26-.727.738-.727 1.257v20.053a1.452 1.452 0 0 0 2.177 1.257l45.741-26.409a1.45 1.45 0 0 0 .726-1.256v-20.053a1.454 1.454 0 0 0-1.454-1.452zM67.871 41.396a1.451 1.451 0 0 0 0 2.513l14.038 8.104 3.278-1.893c.24-.139.508-.207.775-.207s.536.068.775.207l13.111 7.57a1.552 1.552 0 0 1 0 2.688l-1.727.996 15.49 8.943a1.457 1.457 0 0 0 1.452 0l17.366-10.027a1.45 1.45 0 0 0 0-2.512l-45.741-26.41a1.449 1.449 0 0 0-1.452 0zM39.497 57.779a1.45 1.45 0 0 0 0 2.512l17.366 10.027a1.457 1.457 0 0 0 1.452 0l15.488-8.943-1.727-.996a1.552 1.552 0 0 1 0-2.688l1.727-.997-15.489-8.942a1.458 1.458 0 0 0-1.451 0zM49.481 138.43v-16.209l-3.278-1.894a1.55 1.55 0 0 1-.775-1.344v-15.139c0-.906.743-1.555 1.554-1.554.259 0 .523.065.773.21l1.727.997V85.611a1.45 1.45 0 0 0-.726-1.257L31.39 74.33a1.436 1.436 0 0 0-.724-.197c-.758 0-1.452.606-1.452 1.453v52.817c0 .518.276.997.726 1.256l17.365 10.026a1.45 1.45 0 0 0 2.176-1.255zM114.34 108.18l-3.278 1.893 3.278-1.893V91.971zM114.11 91.193zM114.16 91.283z"/></g><g fill="#de5941"><path d="M94.494 100.98a1.45 1.45 0 0 0-.424 1.023v20.053l.001.002c0 .126.02.244.048.358.01.034.021.066.033.101.026.08.059.156.098.229.017.03.032.061.051.09.055.084.115.162.185.232.009.009.015.02.023.027.079.077.169.142.263.198.027.017.055.029.084.044a1.46 1.46 0 0 0 .596.165c.02.001.039.005.059.006.079 0 .158-.016.238-.028.045-.007.09-.006.135-.018.119-.031.238-.08.354-.146l.01-.004 14.038-8.104v-3.785c0-.18.04-.35.098-.514.122-.343.353-.643.678-.83l3.278-1.893V91.977c0-.127-.021-.246-.049-.361-.009-.033-.021-.065-.032-.099a1.266 1.266 0 0 0-.099-.229c-.017-.031-.032-.061-.05-.09a1.425 1.425 0 0 0-.188-.235l-.021-.024a1.41 1.41 0 0 0-.263-.199c-.027-.016-.056-.029-.084-.043a1.509 1.509 0 0 0-.323-.125 1.591 1.591 0 0 0-.273-.04c-.021-.001-.039-.005-.059-.006-.08-.001-.161.015-.242.028-.043.008-.087.006-.131.018-.123.031-.246.08-.365.149l-17.365 10.026a1.447 1.447 0 0 0-.302.233zM77.13 100.74L59.769 90.717a1.424 1.424 0 0 0-.366-.149c-.041-.012-.082-.01-.122-.017-.084-.015-.168-.03-.251-.029-.019 0-.036.005-.055.005-.095.005-.188.02-.278.041-.034.009-.065.02-.099.03a1.406 1.406 0 0 0-.224.095c-.028.014-.057.027-.084.043a1.515 1.515 0 0 0-.263.199l-.021.025c-.07.071-.133.15-.187.234-.019.029-.034.061-.051.091-.04.073-.072.149-.099.229a1.463 1.463 0 0 0-.081.461v16.206l3.276 1.892a1.547 1.547 0 0 1 .776 1.343v3.784l14.035 8.104c.119.068.242.117.366.149.041.012.082.01.125.017.082.014.166.03.248.029.019 0 .037-.005.055-.006.095-.004.188-.019.278-.041.034-.008.065-.019.099-.029.077-.025.152-.058.225-.095.027-.015.056-.027.082-.043.095-.058.185-.123.264-.199l.021-.025c.07-.071.133-.15.188-.235.018-.029.033-.06.05-.09.04-.072.072-.149.099-.229a1.448 1.448 0 0 0 .081-.461v-20.047a1.456 1.456 0 0 0-.726-1.259zM86.689 86.7l17.365-10.026a1.45 1.45 0 0 0 0-2.513l-14.038-8.105-3.278 1.893a1.556 1.556 0 0 1-1.551 0l-3.277-1.892-14.038 8.104c-.241.14-.423.331-.544.55a1.466 1.466 0 0 0 0 1.413c.121.218.303.41.544.55L85.238 86.7a1.447 1.447 0 0 0 1.451 0z"/></g></g></symbol><symbol viewBox="0 0 24 24" id="riot" xmlns="http://www.w3.org/2000/svg"><defs><path d="M13.26 3.04l.58.05.54.07.52.09.49.11.46.13.44.14.41.16.39.17.36.19.33.21.32.22.29.23.26.25.22.22.2.22.19.24.17.24.15.25.15.26.12.27.12.28.1.29.08.31.07.31.05.32.04.34.02.35.01.37v.05l-.02.51-.05.49-.09.48-.13.45-.15.43-.19.4-.22.39-.26.37-.28.34-.31.33-.33.3-.37.28-.39.27-.41.24-.44.22L21 21h-7.04l-3.48-5.14H9.17V21H3V3h9.01l.64.01.61.03zm-4.09 8.52h2.66l.99-.11.75-.35.47-.55.16-.74v-.05l-.17-.75-.47-.54-.74-.32-.96-.11H9.17v3.52z" id="ija"/></defs><use xlink:href="#ija" fill="#ff1744"/><use xlink:href="#ija" fill-opacity="0" stroke="#000" stroke-opacity="0"/></symbol><symbol viewBox="0 0 24 24" id="robot" xmlns="http://www.w3.org/2000/svg"><path d="M12.05 2.804a1.787 1.787 0 0 1 1.788 1.788c0 .661-.357 1.242-.893 1.546v1.135h.893a6.256 6.256 0 0 1 6.256 6.256h.894a.894.894 0 0 1 .893.893v2.681a.894.894 0 0 1-.893.894h-.894v.894a1.787 1.787 0 0 1-1.787 1.787H5.795a1.787 1.787 0 0 1-1.787-1.787v-.894h-.894a.894.894 0 0 1-.894-.894v-2.68a.894.894 0 0 1 .894-.894h.894a6.256 6.256 0 0 1 6.255-6.256h.894V6.138a1.773 1.773 0 0 1-.894-1.546 1.787 1.787 0 0 1 1.788-1.788m-4.022 9.83a2.234 2.234 0 0 0-2.234 2.235 2.234 2.234 0 0 0 2.234 2.234 2.234 2.234 0 0 0 2.234-2.234 2.234 2.234 0 0 0-2.234-2.234m8.043 0a2.234 2.234 0 0 0-2.234 2.234 2.234 2.234 0 0 0 2.234 2.234 2.234 2.234 0 0 0 2.235-2.234 2.234 2.234 0 0 0-2.235-2.234z" fill="#ff5722" stroke-width=".894"/></symbol><symbol viewBox="100 100 800 800" id="rollup" xmlns="http://www.w3.org/2000/svg"><style>.ilst0{fill:url(#ilXMLID_4_)}.ilst1{fill:url(#ilXMLID_5_)}.ilst2{fill:url(#ilXMLID_8_)}.ilst3{fill:url(#ilXMLID_9_)}.ilst4{fill:url(#ilXMLID_11_)}.ilst5{opacity:.3;fill:url(#ilXMLID_16_)}</style><g id="ilXMLID_14_" transform="translate(-54.117 -62.353) scale(1.1129)"><linearGradient id="ilXMLID_4_" x1="444.47" x2="598.47" y1="526.05" y2="562.05" gradientUnits="userSpaceOnUse"><stop stop-color="#FF6533" offset="0"/><stop stop-color="#FF5633" offset=".157"/><stop stop-color="#FF4333" offset=".434"/><stop stop-color="#FF3733" offset=".714"/><stop stop-color="#F33" offset="1"/></linearGradient><path id="ilXMLID_15_" class="ilst0" d="M721 410c0-33.6-8.8-65.1-24.3-92.4-41.1-42.3-130.5-52.1-152.7-.2-22.8 53.2 38.3 112.4 65 107.7 34-6-6-84-6-84 52 98 40 68-54 158S359 779 345 787c-.6.4-1.2.7-1.9 1h368.7c6.5 0 10.7-6.9 7.8-12.7l-96.4-190.8c-2.1-4.1-.6-9.2 3.4-11.5C683 540.6 721 479.8 721 410z" fill="url(#ilXMLID_4_)"/></g><g id="ilXMLID_2_" transform="translate(-54.117 -62.353) scale(1.1129)"><linearGradient id="ilXMLID_5_" x1="420.38" x2="696.38" y1="475" y2="689" gradientUnits="userSpaceOnUse"><stop stop-color="#BF3338" offset="0"/><stop stop-color="#F33" offset="1"/></linearGradient><path id="ilXMLID_10_" class="ilst1" d="M721 410c0-33.6-8.8-65.1-24.3-92.4-41.1-42.3-130.5-52.1-152.7-.2-22.8 53.2 38.3 112.4 65 107.7 34-6-6-84-6-84 52 98 40 68-54 158S359 779 345 787c-.6.4-1.2.7-1.9 1h368.7c6.5 0 10.7-6.9 7.8-12.7l-96.4-190.8c-2.1-4.1-.6-9.2 3.4-11.5C683 540.6 721 479.8 721 410z" fill="url(#ilXMLID_5_)"/></g><linearGradient id="ilXMLID_8_" x1="429.39" x2="469.39" y1="517.16" y2="559.16" gradientTransform="translate(-54.117 -62.353) scale(1.1129)" gradientUnits="userSpaceOnUse"><stop stop-color="#FF6533" offset="0"/><stop stop-color="#FF5633" offset=".157"/><stop stop-color="#FF4333" offset=".434"/><stop stop-color="#FF3733" offset=".714"/><stop stop-color="#F33" offset="1"/></linearGradient><path id="ilXMLID_3_" class="ilst2" d="M329.82 813.46c15.58-8.903 122.41-220.34 227.02-320.5s117.96-66.771 60.094-175.83c0 0-221.46 310.49-301.58 464.06" fill="url(#ilXMLID_8_)" stroke-width="1.113"/><g id="ilXMLID_7_" transform="translate(-54.117 -62.353) scale(1.1129)"><linearGradient id="ilXMLID_9_" x1="502.11" x2="490.11" y1="589.46" y2="417.46" gradientUnits="userSpaceOnUse"><stop stop-color="#FF6533" offset="0"/><stop stop-color="#FF5633" offset=".157"/><stop stop-color="#FF4333" offset=".434"/><stop stop-color="#FF3733" offset=".714"/><stop stop-color="#F33" offset="1"/></linearGradient><path id="ilXMLID_12_" class="ilst3" d="M373 537c134.4-247.1 152-272 222-272 36.8 0 73.9 16.6 97.9 46.1-32.7-52.7-90.6-88-156.9-89H307.7c-4.8 0-8.7 3.9-8.7 8.7V691c13.6-35.1 36.7-85.3 74-154z" fill="url(#ilXMLID_9_)"/></g><linearGradient id="ilXMLID_11_" x1="450.12" x2="506.94" y1="514.21" y2="552.85" gradientTransform="translate(-54.117 -62.353) scale(1.1129)" gradientUnits="userSpaceOnUse"><stop stop-color="#FBB040" offset="0"/><stop stop-color="#FB8840" offset="1"/></linearGradient><path id="ilXMLID_6_" class="ilst4" d="M556.84 492.96c-104.61 100.16-211.44 311.6-227.02 320.5s-41.732 10.016-55.643-5.564c-14.801-16.582-37.837-43.401 86.802-272.65 149.57-274.99 169.15-302.7 247.05-302.7 40.953 0 82.24 18.473 108.95 51.302 1.447 2.337 2.893 4.785 4.34 7.233-45.738-47.074-145.23-57.98-169.93-.222-25.373 59.204 42.622 125.08 72.335 119.85 37.837-6.677-6.677-93.48-6.677-93.48 57.757 108.95 44.403 75.563-60.205 175.72z" fill="url(#ilXMLID_11_)" stroke-width="1.113"/><linearGradient id="ilXMLID_16_" x1="508.33" x2="450.33" y1="295.76" y2="933.76" gradientTransform="translate(-54.117 -62.353) scale(1.1129)" gradientUnits="userSpaceOnUse"><stop stop-color="#FFF" offset="0"/><stop stop-color="#FFF" stop-opacity="0" offset="1"/></linearGradient><path id="ilXMLID_13_" class="ilst5" d="M373.22 547.49c149.57-274.99 169.15-302.7 247.05-302.7 33.719 0 67.661 12.575 93.48 35.277-26.708-30.492-66.326-47.519-105.72-47.519-77.9 0-97.486 27.71-247.05 302.7-124.64 229.25-101.6 256.07-86.802 272.65 2.114 2.337 4.563 4.34 7.122 6.01-13.02-18.919-18.807-62.877 91.922-266.42z" fill="url(#ilXMLID_16_)" opacity=".3" stroke-width="1.113"/></symbol><symbol viewBox="0 0 24 24" id="ruby" xmlns="http://www.w3.org/2000/svg"><path d="M16 9h3l-5 7m-4-7h4l-2 8M5 9h3l2 7m5-12h2l2 3h-3m-5-3h2l1 3h-4M7 4h2L8 7H5m1-5L2 8l10 14L22 8l-4-6H6z" fill="#f44336"/></symbol><symbol viewBox="0 0 144 144" id="rust" xmlns="http://www.w3.org/2000/svg"><path d="M68.252 26.206a3.561 3.561 0 0 1 7.123 0 3.561 3.561 0 0 1-7.123 0M25.766 58.451a3.561 3.561 0 0 1 7.123 0 3.561 3.561 0 0 1-7.123 0m84.97.166a3.561 3.561 0 0 1 7.123 0 3.561 3.561 0 0 1-7.123 0m-74.661 4.88a3.252 3.252 0 0 0 1.651-4.29l-1.58-3.574h6.214v28.01H29.823a43.847 43.847 0 0 1-1.42-16.738zm25.994.688v-8.256h14.798c.764 0 5.397.883 5.397 4.347 0 2.877-3.553 3.908-6.475 3.908zm-20.203 44.452a3.561 3.561 0 0 1 7.123 0 3.561 3.561 0 0 1-7.123 0m52.769.166a3.561 3.561 0 0 1 7.123 0 3.561 3.561 0 0 1-7.123 0m1.101-8.076a3.246 3.246 0 0 0-3.856 2.498l-1.787 8.342a43.847 43.847 0 0 1-36.566-.175l-1.787-8.342a3.246 3.246 0 0 0-3.854-2.497l-7.365 1.581a43.847 43.847 0 0 1-3.808-4.488h35.834c.406 0 .676-.074.676-.443V84.527c0-.369-.27-.442-.676-.442h-10.48V76.05h11.335c1.035 0 5.532.296 6.97 6.045.45 1.768 1.44 7.519 2.116 9.36.674 2.065 3.417 6.19 6.34 6.19h18.501a43.847 43.847 0 0 1-4.06 4.7zm19.898-33.468a43.847 43.847 0 0 1 .093 7.612h-4.499c-.45 0-.631.296-.631.737v2.066c0 4.863-2.742 5.92-5.145 6.19-2.288.258-4.825-.958-5.138-2.358-1.35-7.593-3.6-9.214-7.152-12.016 4.409-2.8 8.996-6.93 8.996-12.457 0-5.97-4.092-9.729-6.881-11.572-3.914-2.58-8.246-3.096-9.415-3.096H39.336A43.847 43.847 0 0 1 63.867 28.52l5.484 5.753a3.243 3.243 0 0 0 4.59.105l6.137-5.869a43.847 43.847 0 0 1 30.017 21.38l-4.201 9.487a3.256 3.256 0 0 0 1.652 4.29zm10.477.154l-.143-1.467 4.327-4.036c.88-.82.55-2.472-.574-2.891l-5.532-2.068-.433-1.428 3.45-4.792c.704-.974.058-2.53-1.127-2.724l-5.833-.949-.7-1.31 2.45-5.38c.502-1.095-.43-2.496-1.636-2.45l-5.92.206-.935-1.135 1.36-5.766c.275-1.17-.913-2.36-2.084-2.085l-5.765 1.359-1.136-.935.207-5.92c.046-1.198-1.357-2.135-2.45-1.637l-5.379 2.452-1.31-.703-.95-5.833c-.193-1.183-1.75-1.83-2.723-1.128l-4.796 3.45-1.425-.432-2.068-5.532c-.42-1.127-2.072-1.452-2.89-.576l-4.036 4.33-1.467-.143-3.117-5.036c-.63-1.02-2.318-1.02-2.946 0l-3.117 5.036-1.467.143-4.037-4.33c-.819-.876-2.47-.551-2.89.576l-2.069 5.532-1.426.432-4.795-3.45c-.974-.703-2.53-.055-2.723 1.128l-.951 5.833-1.31.703-5.379-2.452c-1.093-.5-2.496.439-2.45 1.637l.206 5.92-1.136.935-5.765-1.36c-1.171-.272-2.36.915-2.086 2.086l1.358 5.766-.933 1.135-5.92-.206c-1.193-.035-2.134 1.355-1.637 2.45l2.453 5.38-.703 1.31-5.832.949c-1.185.192-1.827 1.75-1.128 2.724l3.45 4.792-.433 1.428-5.532 2.068c-1.123.42-1.452 2.07-.574 2.891l4.328 4.036-.143 1.467-5.035 3.116c-1.02.63-1.02 2.318 0 2.946l5.035 3.117.143 1.467-4.328 4.037c-.878.818-.549 2.468.574 2.89l5.532 2.068.433 1.428-3.45 4.793c-.701.976-.056 2.532 1.129 2.723l5.831.948.703 1.312-2.453 5.378c-.5 1.093.444 2.5 1.638 2.451l5.917-.207.935 1.136-1.358 5.768c-.275 1.168.915 2.355 2.086 2.08l5.765-1.357 1.137.932-.207 5.921c-.046 1.199 1.357 2.136 2.45 1.636l5.379-2.45 1.31.702.95 5.83c.193 1.187 1.75 1.829 2.725 1.13l4.792-3.453 1.427.435 2.069 5.53c.42 1.123 2.072 1.454 2.89.574l4.037-4.328 1.467.146 3.117 5.035c.628 1.016 2.316 1.018 2.946 0l3.117-5.035 1.467-.146 4.036 4.328c.818.88 2.47.549 2.89-.574l2.068-5.53 1.428-.435 4.793 3.453c.974.699 2.53.055 2.722-1.13l.952-5.83 1.31-.703 5.378 2.451c1.093.5 2.493-.435 2.45-1.636l-.206-5.92 1.135-.933 5.765 1.357c1.171.275 2.36-.912 2.085-2.08l-1.358-5.768.932-1.136 5.92.207c1.194.048 2.138-1.358 1.636-2.451l-2.45-5.378.7-1.312 5.833-.948c1.187-.19 1.831-1.747 1.127-2.723l-3.45-4.793.433-1.428 5.532-2.068c1.125-.422 1.454-2.072.574-2.89l-4.327-4.037.143-1.467 5.035-3.117c1.02-.628 1.021-2.315.001-2.946z" fill="#ff7043" stroke-width="1.146"/></symbol><symbol viewBox="0 0 500 500" id="sass" xmlns="http://www.w3.org/2000/svg"><path d="M422.676 96.573c-12.192-47.839-91.508-63.557-166.575-36.892-44.68 15.877-93.029 40.786-127.81 73.311-41.349 38.675-47.943 72.328-45.216 86.395 9.583 49.622 77.585 82.069 105.535 106.126v.144c-8.246 4.05-68.565 34.584-82.684 65.799-14.893 32.932 2.372 56.556 13.804 59.742 35.424 9.859 71.764-7.866 91.311-37.01 18.853-28.12 17.28-64.422 9.086-82.487 11.3-2.976 24.476-4.314 41.218-2.36 47.248 5.52 56.517 35.017 54.747 47.366-1.77 12.35-11.681 19.14-14.998 21.186-3.317 2.045-4.326 2.766-4.05 4.287.405 2.215 1.94 2.137 4.758 1.652 3.894-.656 24.804-10.042 25.709-32.828 1.14-28.933-26.587-61.302-75.684-60.45-20.216.354-32.933 2.268-42.123 5.69-.681-.774-1.363-1.547-2.084-2.307-30.35-32.382-86.46-55.285-84.088-98.824.866-15.823 6.372-57.5 107.817-108.052 83.104-41.415 149.637-30.009 161.135-4.76 16.427 36.08-35.554 103.137-121.858 112.812-32.88 3.684-50.198-9.059-54.498-13.804-4.536-4.995-5.204-5.218-6.909-4.287-2.753 1.533-1.01 5.938 0 8.574 2.583 6.712 13.15 18.603 31.176 24.515 15.863 5.205 54.459 8.063 101.156-9.99 52.283-20.255 93.12-76.523 81.125-123.548zM200.213 340.34c3.92 14.5 3.487 28.016-.564 40.248a65.289 65.289 0 0 1-3.225 7.97c-3.12 6.477-7.316 12.534-12.442 18.132-15.653 17.069-37.507 23.532-46.88 18.092-10.122-5.874-5.048-29.944 13.083-49.11 19.52-20.636 47.602-33.903 47.602-33.903l-.039-.079 2.465-1.35z" fill="#ec407a" stroke="#ec407a" stroke-width="16.286552999999998"/></symbol><symbol viewBox="0 0 300 300" id="sbt" xmlns="http://www.w3.org/2000/svg"><path d="M105.46 209.517c-7.875 0-13.452-7.521-13.452-15.37v-.327c0-7.848 5.578-13.735 13.452-13.735h164.05c1.476-4.905 2.625-11.446 3.281-17.986h-137.81c-7.875 0-14.273-6.05-14.273-13.898s6.398-13.898 14.273-13.898h137.31c-.82-6.54-1.969-13.081-3.773-17.986h-104.01c-7.875 0-14.273-6.05-14.273-13.898s6.398-13.898 14.273-13.898h91.87c-21.327-37.607-60.864-61.315-106.14-61.315-67.918 0-123.04 54.448-123.04 122.3 0 67.856 55.122 123.28 123.04 123.28 46.59 0 87.112-25.507 107.95-63.114h-152.73z" fill="#0277bd" stroke-width="1.638"/></symbol><symbol viewBox="0 0 256 256" id="scala" xmlns="http://www.w3.org/2000/svg"><path fill="#f44336" fill-rule="evenodd" stroke-width=".3" d="M59.607 50.647l149.097-21.982v49.488L59.607 100.135zM59.593 114.08L208.69 92.098v49.488L59.593 163.568zM59.587 177.358l149.097-21.982v49.488L59.587 226.846z"/><path fill="#f44336" fill-rule="evenodd" stroke-width=".3" d="M62.425 91.414l95.605 30.923-2.832 8.757-95.605-30.922zM113.084 61.13l95.604 30.922-2.832 8.757-95.605-30.922zM62.425 154.79l95.605 30.922-2.833 8.758-95.604-30.923zM113.097 124.408l95.604 30.923-2.832 8.757-95.605-30.922z"/></symbol><symbol viewBox="0 0 24 24" id="settings" xmlns="http://www.w3.org/2000/svg"><path d="M12 15.5A3.5 3.5 0 0 1 8.5 12 3.5 3.5 0 0 1 12 8.5a3.5 3.5 0 0 1 3.5 3.5 3.5 3.5 0 0 1-3.5 3.5m7.43-2.53c.04-.32.07-.64.07-.97 0-.33-.03-.66-.07-1l2.11-1.63c.19-.15.24-.42.12-.64l-2-3.46c-.12-.22-.39-.31-.61-.22l-2.49 1c-.52-.39-1.06-.73-1.69-.98l-.37-2.65A.506.506 0 0 0 14 2h-4c-.25 0-.46.18-.5.42l-.37 2.65c-.63.25-1.17.59-1.69.98l-2.49-1c-.22-.09-.49 0-.61.22l-2 3.46c-.13.22-.07.49.12.64L4.57 11c-.04.34-.07.67-.07 1 0 .33.03.65.07.97l-2.11 1.66c-.19.15-.25.42-.12.64l2 3.46c.12.22.39.3.61.22l2.49-1.01c.52.4 1.06.74 1.69.99l.37 2.65c.04.24.25.42.5.42h4c.25 0 .46-.18.5-.42l.37-2.65c.63-.26 1.17-.59 1.69-.99l2.49 1.01c.22.08.49 0 .61-.22l2-3.46c.12-.22.07-.49-.12-.64l-2.11-1.66z" fill="#42a5f5"/></symbol><symbol viewBox="0 0 24 24" id="shaderlab" xmlns="http://www.w3.org/2000/svg"><path d="M9.11 17H6.5l-4.91-5L6.5 7h2.61l1.31-2.26L17.21 3l1.87 6.74L17.77 12l1.31 2.26L17.21 21l-6.79-1.74L9.11 17m.14-.25l5.13 1.38L11.42 13H5.5l3.75 3.75m6.87.38L17.5 12l-1.38-5.13L13.15 12l2.97 5.13M9.25 7.25L5.5 11h5.92l2.96-5.13-5.13 1.38z" fill="#1976d2"/></symbol><symbol viewBox="0 0 24 24" id="slim" xmlns="http://www.w3.org/2000/svg"><path d="M6.959 2.5a4.605 4.605 0 0 0-4.615 4.615v9.957a4.605 4.605 0 0 0 4.615 4.615h9.957a4.605 4.605 0 0 0 4.615-4.615V7.115A4.605 4.605 0 0 0 16.916 2.5zm4.938 2.691a6.811 6.811 0 0 1 6.81 6.813H13.43L9.938 7.287l.699 4.717H5.086a6.811 6.811 0 0 1 6.81-6.813z" fill="#f57f17"/></symbol><symbol id="smarty" viewBox="0 0 24 24" xmlns="http://www.w3.org/2000/svg"><style>.iust0{fill:#ffce00}</style><path class="iust0" d="M9.14 20.606c0 .556.398.953.954.953h3.812c.556 0 .953-.397.953-.953v-.953H9.141zM12 2.5c-3.653 0-6.671 3.018-6.671 6.671 0 2.303 1.112 4.289 2.859 5.48v2.144c0 .556.397.953.953.953h5.718c.556 0 .953-.397.953-.953V14.65c1.747-1.191 2.86-3.177 2.86-5.48 0-3.653-3.019-6.671-6.672-6.671zm2.7 10.563l-.794.555v2.224h-3.812v-2.224l-.794-.555A4.712 4.712 0 0 1 7.235 9.17 4.78 4.78 0 0 1 12 4.405a4.78 4.78 0 0 1 4.765 4.765 4.712 4.712 0 0 1-2.065 3.892z"/></symbol><symbol viewBox="0 0 200 200" id="snyk" xmlns="http://www.w3.org/2000/svg"><title>Group 2</title><g transform="translate(15.255 18.22) scale(1.8477)" fill="none" fill-rule="evenodd"><path d="M65.161 24.997c-1.656 5.974-5.255 23.587-5.255 23.587s-6.618-2.464-14.148-2.476h-.055c-.413.002-.822.012-1.23.026v41.649h6.677v.003h5.815v-.003h20.858c.111-8.177-2.036-27.066-2.036-27.066-1.088-2.279.46-7.668.46-7.668-8.869-9.092-11.086-28.051-11.086-28.051zm-3.357 43.958c5.476 0 1.381 4.64.9 5.168H52.35c.944-1.18 4.504-5.168 9.453-5.168z" fill="#607d8b" stroke-width="1.6"/><path d="M26.366 24.995s-2.217 18.961-11.087 28.053c0 0 1.548 5.391.46 7.669 0 0-2.15 18.895-2.038 27.066h19.273v.003h7.079v-.003h5.744V46.107h-.025c-7.532.013-14.151 2.478-14.151 2.478s-3.6-17.615-5.255-23.59zm3.264 43.96c4.95 0 8.51 3.987 9.452 5.168H28.73c-.479-.528-4.573-5.168.9-5.168z" fill="#90a4ae" stroke-width="1.6"/><g transform="translate(23.76 77.45) scale(1.5998)"><g transform="translate(17.526)"><path d="M7.357.06H.177v.075C.177 2.64 2.345 4.67 4.89 4.67 7.431 4.67 9.6 2.64 9.6.135V.059z" fill="#455a64"/><path d="M1.972.06v.075a2.692 2.692 0 1 0 5.386 0V.059z" fill="#fff"/><path d="M5.496.06H4.234c-.012 0-.023.005-.034.007.157.033.243.388.21.624a.721.721 0 0 1-.71.617c.102.471.487.85.997.922a1.188 1.188 0 0 0 1.35-1.007C6.112.743 5.881.06 5.495.06z" fill="#37474f"/></g><path d="M7.552.06H.372v.075c0 2.505 2.17 4.535 4.712 4.535 2.544 0 4.712-2.03 4.712-4.535V.059z" fill="#455a64"/><path d="M2.168.06v.075a2.692 2.692 0 1 0 5.385 0V.059z" fill="#fff"/><path d="M5.692.06H4.428c-.01 0-.022.005-.032.007.156.033.242.388.21.624a.72.72 0 0 1-.712.617c.104.471.488.85.999.922A1.187 1.187 0 0 0 6.24 1.223C6.308.743 6.078.06 5.69.06z" fill="#37474f"/></g><path d="M25.514-.27l-4.202 7.697C19.838 10.17 6.858 34.465 6.858 43.243v.516L12.8 59.573c-.8 7.258-2.203 21.643-1.78 28.21h5.73c-.354-3.787.648-17.008 1.903-28.25l.076-.677-1.075-2.892c3.694-3.868 6.285-9.193 8.073-14.261l.174 1.235 5.869 9.629 2.291-.983c.058-.024 5.935-2.523 11.643-2.523 5.672 0 11.646 2.5 11.702 2.525l2.29.976 5.86-9.626.23-1.608c1.769 5.117 4.358 10.536 8.07 14.49l-1.127 3.035.076.678c1.259 11.286 2.266 24.564 1.916 28.252h5.677c.406-6.567-1.05-20.952-1.848-28.208l5.838-15.817v-.514c0-8.779-12.876-33.074-14.347-35.816L65.923-.27l-5.897 41.229-2.723 4.478c-2.628-.882-7.1-2.11-11.603-2.11-4.498 0-8.94 1.225-11.557 2.108l-2.722-4.476-2.07-14.452a.832.832 0 0 0 .006-.071l-.016-.004zm-3.166 18.39l1.206 8.407c-.46 3.143-2.561 15.47-8.198 23.24l-2.598-6.99c.325-4.554 5.067-15.462 9.59-24.656zm46.763 0c4.523 9.194 9.267 20.104 9.592 24.657L76.166 49.6c-6.09-8.553-8-22.459-8.166-23.73z" fill="#607d8b" stroke-width="1.6"/></g></symbol><symbol viewBox="0 0 24 24" id="solidity" xmlns="http://www.w3.org/2000/svg"><path d="M5.8 14.05l6.253 8.61 6.252-8.61-6.254 3.807z" fill="#0288d1" stroke-width="4.553" stroke-linejoin="round"/><path d="M12.051 1.347L5.8 11.833l6.252 3.807 6.254-3.807z" fill="#0288d1" stroke-width="5.025" stroke-linejoin="round"/></symbol><symbol viewBox="0 0 120 120" id="sonar" xmlns="http://www.w3.org/2000/svg"><style>.a,.b{fill:#fff}.b{stroke:#fff;stroke-miterlimit:10}</style><path d="M115.45 23.033S97.961 33.27 97.534 33.412c-.427.284-.852.57-1.137.854-1.422 1.421-1.848 3.41-1.422 5.26.285.852.711 1.849 1.422 2.56.711.71 1.564 1.137 2.559 1.422 1.848.426 3.84 0 5.262-1.422.426-.427.709-.853.851-1.28l.143-.427 2.56-4.692zm-39.102 9.242c-27.441 0-31.99 13.08-31.99 29.29 0 3.838.569 7.962-1.99 11.942-3.84 5.972-8.957 5.828-10.236 5.828-1.706 0-7.962-.993-8.246-2.841h.994c6.682 0 11.658-5.404 11.658-12.655v-2.56h-5.686c-4.123 0-7.82 1.849-10.238 5.12-2.417-3.271-6.113-5.12-10.236-5.12h-5.83v2.56c0 7.11 5.688 12.795 12.797 12.795h1.848c0 4.124 5.687 20.332 47.63 20.332 16.352 0 40.665-2.843 40.665-33.697 0-5.829-1.848-11.23-4.691-15.78-.996.284-1.992.568-3.13.568a8.92 8.92 0 0 1-8.956-8.957c0-.995.141-1.991.425-2.986-4.265-2.702-8.53-3.838-14.787-3.838z" fill="#1e88e5" stroke-width="1.422"/></symbol><symbol viewBox="0 0 412 395" id="stylelint" xmlns="http://www.w3.org/2000/svg"><title>stylelint-icon-white</title><g transform="translate(31.478 29.499) scale(.84775)" fill="#cfd8dc" fill-rule="evenodd"><path d="M208.8 393.05c45.057-161.12 43.75-161.85 76.32-276.73l7.832 4.523c4.255 2.458 7.738.448 7.738-4.455V61.602c8.643-30.27 15.416-53.66 17.4-60.693h35.287l58.618 54.304-38.498 33.27 29.11 31.473-191.86 273.09c-.938 1.542-2.244 1.19-1.947 0zm20.96-347.28c1.733 0 3.148.958 3.148 2.147v28.077c0 1.186-1.415 2.15-3.147 2.15h-47.396c-1.742 0-3.153-.96-3.153-2.15V47.917c0-1.185 1.41-2.147 3.153-2.147h47.396z"/><path d="M288.26 14.688l-52.14 30.1c.605.92.973 1.98.973 3.136v28.078c0 1.457-.565 2.77-1.496 3.83l52.663 30.402c3.59 2.073 6.535.377 6.535-3.764V18.456c0-4.145-2.944-5.836-6.535-3.768zM175.02 76V47.923c0-1.15.368-2.21.966-3.13l-52.14-30.105c-3.588-2.068-6.53-.376-6.53 3.768v88.013c0 4.14 2.938 5.84 6.53 3.76l52.66-30.405c-.926-1.06-1.487-2.37-1.487-3.827z"/><path d="M201.25 393.05h1.947c-45.05-161.12-43.753-161.85-76.32-276.73l-7.833 4.523c-4.253 2.458-7.737.448-7.737-4.455V61.602C102.662 31.332 95.892 7.942 93.902.909H58.619L.002 55.213l38.494 33.27-29.11 31.473z"/><circle cx="204.57" cy="122.54" r="14.231"/><circle cx="204.57" cy="207.16" r="14.231"/><circle cx="204.57" cy="291.78" r="14.23"/></g></symbol><symbol viewBox="0 0 412 395" id="stylelint_light" xmlns="http://www.w3.org/2000/svg"><title>stylelint-icon-black</title><g transform="translate(31.478 29.499) scale(.84775)" fill="#546e7a" fill-rule="evenodd"><path d="M208.8 393.05c45.057-161.12 43.75-161.85 76.32-276.73l7.832 4.523c4.255 2.458 7.738.448 7.738-4.455V61.602c8.643-30.27 15.416-53.66 17.4-60.693h35.287l58.618 54.304-38.498 33.27 29.11 31.473-191.86 273.09c-.938 1.542-2.244 1.19-1.947 0zm20.96-347.28c1.733 0 3.148.958 3.148 2.147v28.077c0 1.186-1.415 2.15-3.147 2.15h-47.396c-1.742 0-3.153-.96-3.153-2.15V47.917c0-1.185 1.41-2.147 3.153-2.147h47.396z"/><path d="M288.26 14.688l-52.14 30.1c.605.92.973 1.98.973 3.136v28.078c0 1.457-.565 2.77-1.496 3.83l52.663 30.402c3.59 2.073 6.535.377 6.535-3.764V18.456c0-4.145-2.944-5.836-6.535-3.768zM175.02 76V47.923c0-1.15.368-2.21.966-3.13l-52.14-30.105c-3.588-2.068-6.53-.376-6.53 3.768v88.013c0 4.14 2.938 5.84 6.53 3.76l52.66-30.405c-.926-1.06-1.487-2.37-1.487-3.827z"/><path d="M201.25 393.05h1.947c-45.05-161.12-43.753-161.85-76.32-276.73l-7.833 4.523c-4.253 2.458-7.737.448-7.737-4.455V61.602C102.662 31.332 95.892 7.942 93.902.909H58.619L.002 55.213l38.494 33.27-29.11 31.473z"/><circle cx="204.57" cy="122.54" r="14.231"/><circle cx="204.57" cy="207.16" r="14.231"/><circle cx="204.57" cy="291.78" r="14.23"/></g></symbol><symbol viewBox="0 0 200.00001 200.00001" id="stylus" xmlns="http://www.w3.org/2000/svg"><path d="M126.814 155.9c14.64-17.51 16.362-35.595 5.024-69.18-7.177-21.24-19.09-37.602-10.334-50.807 9.329-14.065 29.135-.43 12.63 18.371l3.301 2.297c19.806 2.296 29.566-24.83 14.783-32.58C113.179 3.621 79.02 42.803 94.09 88.156c6.458 19.232 15.5 39.613 8.18 55.83-6.314 13.923-18.514 22.103-26.695 22.39-17.079.862-5.74-38.32 13.922-48.08 1.722-.861 4.162-2.01 1.866-4.88-24.256-2.727-38.464 8.468-46.645 24.112-23.825 45.497 45.21 62.29 82.095 18.371z" fill="#c0ca33" stroke-width="1.435"/></symbol><symbol viewBox="0 0 24 24" id="swc" xmlns="http://www.w3.org/2000/svg"><defs><linearGradient id="jba"><stop offset="0" stop-color="#791223"/><stop offset="1" stop-color="#d92f3c"/></linearGradient><linearGradient xlink:href="#jba" id="jbb" x1="12.356" y1="21.559" x2="12.356" y2="2.949" gradientUnits="userSpaceOnUse"/></defs><path d="M6 3c-.47 0-.88.21-1.16.55L3.46 5.23C3.17 5.57 3 6 3 6.5V19a2 2 0 0 0 2 2h14a2 2 0 0 0 2-2V6.5c0-.5-.17-.93-.46-1.27l-1.39-1.68C18.88 3.21 18.47 3 18 3H6zm-.07 1h12l.94 1H5.12l.81-1z" fill="url(#jbb)"/><path style="line-height:125%" d="M11.053 11.918h-.008c-.244.022-.475.054-.676.11a2.9 2.9 0 0 0-.856.412 3.399 3.399 0 0 0-.67.683 9.36 9.36 0 0 0-.586.95c-.07.131-.134.244-.201.365v.001h-.002l-.768 1.372-.003-.001c-.136.253-.264.485-.38.686-.123.212-.26.39-.411.539a1.599 1.599 0 0 1-.52.34c-.04.016-.092.024-.138.036h-.567v1.383H5.834v-.001c.245-.02.477-.053.679-.11a2.9 2.9 0 0 0 .856-.411c.245-.185.469-.413.67-.683.195-.275.39-.591.585-.95.07-.131.135-.244.202-.366l.004.001.002-.002.02-.038H10.948v-1.378h-.19v-.001H9.624c.125-.234.246-.452.355-.64.123-.21.259-.39.41-.538.152-.148.325-.26.52-.34.04-.015.091-.024.136-.035h.57V13.3h-.002v-1.381h-.56v-.001z" font-weight="400" font-size="40" font-family="sans-serif" letter-spacing="0" word-spacing="0" fill="#fff"/></symbol><symbol viewBox="0 0 24 24" id="swift" xmlns="http://www.w3.org/2000/svg"><path d="M17.09 19.72c-2.36 1.36-5.59 1.5-8.86.1A13.807 13.807 0 0 1 2 14.5c.67.55 1.46 1 2.3 1.4 3.37 1.57 6.73 1.46 9.1 0-3.37-2.59-6.24-5.96-8.37-8.71-.45-.45-.78-1.01-1.12-1.51 8.28 6.05 7.92 7.59 2.41-1.01 4.89 4.94 9.43 7.74 9.43 7.74.16.09.25.16.36.22.1-.25.19-.51.26-.78.79-2.85-.11-6.12-2.08-8.81 4.55 2.75 7.25 7.91 6.12 12.24-.03.11-.06.22-.05.39 2.24 2.83 1.64 5.78 1.35 5.22-1.21-2.39-3.48-1.65-4.62-1.17z" fill="#fe5e2f"/></symbol><symbol viewBox="0 0 24 24" id="table" xmlns="http://www.w3.org/2000/svg"><path d="M6 2h8l6 6v12a2 2 0 0 1-2 2H6a2 2 0 0 1-2-2V4a2 2 0 0 1 2-2m7 1.5V9h5.5L13 3.5m4 7.5h-4v2h1l-2 1.67L10 13h1v-2H7v2h1l3 2.5L8 18H7v2h4v-2h-1l2-1.67L14 18h-1v2h4v-2h-1l-3-2.5 3-2.5h1v-2z" fill="#8bc34a"/></symbol><symbol viewBox="0 0 200 200" id="terraform" xmlns="http://www.w3.org/2000/svg"><g transform="translate(177.03 -58.705) scale(.92881)" fill="#5c6bc0" stroke="#b0aff5" stroke-linejoin="round"><g stroke-width=".288"><path transform="skewY(26.439) scale(.89541 1)" d="M-203.8 170.95h64.714v51.88H-203.8zM-124.37 171.04h64.714v51.88h-64.714zM-124.37 236.09h64.714v51.88h-64.714z"/></g><path transform="skewY(-22.59) scale(-.92328 1)" stroke-width=".284" d="M-19.172 128.27h62.76v51.88h-62.76z"/></g></symbol><symbol viewBox="0 0 24 24" id="test-js" xmlns="http://www.w3.org/2000/svg"><path d="M5 19a1 1 0 0 0 1 1h12a1 1 0 0 0 1-1c0-.21-.07-.41-.18-.57L13 8.35V4h-2v4.35L5.18 18.43c-.11.16-.18.36-.18.57m1 3a3 3 0 0 1-3-3c0-.6.18-1.16.5-1.63L9 7.81V6a1 1 0 0 1-1-1V4a2 2 0 0 1 2-2h4a2 2 0 0 1 2 2v1a1 1 0 0 1-1 1v1.81l5.5 9.56c.32.47.5 1.03.5 1.63a3 3 0 0 1-3 3H6m7-6l1.34-1.34L16.27 18H7.73l2.66-4.61L13 16m-.5-4a.5.5 0 0 1 .5.5.5.5 0 0 1-.5.5.5.5 0 0 1-.5-.5.5.5 0 0 1 .5-.5z" fill="#ffca28"/></symbol><symbol viewBox="0 0 24 24" id="test-jsx" xmlns="http://www.w3.org/2000/svg"><path d="M5 19a1 1 0 0 0 1 1h12a1 1 0 0 0 1-1c0-.21-.07-.41-.18-.57L13 8.35V4h-2v4.35L5.18 18.43c-.11.16-.18.36-.18.57m1 3a3 3 0 0 1-3-3c0-.6.18-1.16.5-1.63L9 7.81V6a1 1 0 0 1-1-1V4a2 2 0 0 1 2-2h4a2 2 0 0 1 2 2v1a1 1 0 0 1-1 1v1.81l5.5 9.56c.32.47.5 1.03.5 1.63a3 3 0 0 1-3 3H6m7-6l1.34-1.34L16.27 18H7.73l2.66-4.61L13 16m-.5-4a.5.5 0 0 1 .5.5.5.5 0 0 1-.5.5.5.5 0 0 1-.5-.5.5.5 0 0 1 .5-.5z" fill="#00bcd4"/></symbol><symbol viewBox="0 0 24 24" id="test-ts" xmlns="http://www.w3.org/2000/svg"><path d="M5 19a1 1 0 0 0 1 1h12a1 1 0 0 0 1-1c0-.21-.07-.41-.18-.57L13 8.35V4h-2v4.35L5.18 18.43c-.11.16-.18.36-.18.57m1 3a3 3 0 0 1-3-3c0-.6.18-1.16.5-1.63L9 7.81V6a1 1 0 0 1-1-1V4a2 2 0 0 1 2-2h4a2 2 0 0 1 2 2v1a1 1 0 0 1-1 1v1.81l5.5 9.56c.32.47.5 1.03.5 1.63a3 3 0 0 1-3 3H6m7-6l1.34-1.34L16.27 18H7.73l2.66-4.61L13 16m-.5-4a.5.5 0 0 1 .5.5.5.5 0 0 1-.5.5.5.5 0 0 1-.5-.5.5.5 0 0 1 .5-.5z" fill="#0288d1"/></symbol><symbol viewBox="0 0 500 500" fill-rule="evenodd" clip-rule="evenodd" stroke-linejoin="round" stroke-miterlimit="1.414" id="tex" xmlns="http://www.w3.org/2000/svg"><g font-weight="400" font-size="40" font-family="sans-serif" letter-spacing="0" word-spacing="0" fill="#42a5f5" stroke-linejoin="miter"><text style="line-height:125%" x="9.914" y="364.919"><tspan x="9.914" y="364.919" font-size="287.5">T</tspan></text><text style="line-height:125%" x="136.374" y="435.558"><tspan x="136.374" y="435.558" font-size="287.5">E</tspan></text><text style="line-height:125%" x="307.819" y="361.201"><tspan x="307.819" y="361.201" font-size="287.5">X</tspan></text></g></symbol><symbol viewBox="0 0 24 24" id="todo" xmlns="http://www.w3.org/2000/svg"><path d="M3 5h6v6H3V5m2 2v2h2V7H5m6 0h10v2H11V7m0 8h10v2H11v-2m-6 5l-3.5-3.5 1.41-1.41L5 17.17l4.59-4.58L11 14l-6 6z" fill="#42a5f5"/></symbol><symbol id="travis" viewBox="0 0 200 200" xmlns="http://www.w3.org/2000/svg"><style id="jkstyle2">.jkst0{fill:#cb3349}.jkst1{fill:#f4edae}.jkst2{fill:#e6ccad}.jkst3{fill:#656c67}.jkst4{fill:#e5caa3}.jkst5{fill:#c7b39a}.jkst6{fill:#ebd599}.jkst7{fill:#2d3136}.jkst8{fill:#edf6fa}.jkst9{opacity:.8}.jkst10{opacity:.75;fill:#ebd599}</style><g id="jkg99" transform="translate(11.017 12.484) scale(.8858)"><g id="jkg10"><path class="jkst0" d="M47.781 86.572s-31.118 21.903-32.335 30.247l2.335-.48S55.045 91.64 84.584 88.628l.669-3.749z" id="jkpath4" fill="#cb3349"/><path class="jkst0" d="M96.629 83.442l-24.511 17.385 1.325 1.063c.999-.806 43.539-13.798 43.539-13.798l8.969-5.623c-6.018.749-29.322.973-29.322.973z" id="jkpath6" fill="#cb3349"/><path class="jkst0" d="M117.932 104.469c17.405 0 43.495-17.046 43.495-17.046l-8.434-1.605c-.417.417-13.6-.462-13.6-.462l-6.258-1.738-14.951 17.036-1.217 2.956c1.075-.437.965.859.965.859z" id="jkpath8" fill="#cb3349"/></g><path class="jkst0" d="M174.728 158.832l-5.377 1.514-24.843-.537-15.541-12.085-18.784 4.7-21.726-1.88-12.166 13.294-22.828 6.819-11.398-3.534-.574-.494 5.116 12.527s11.588 12.424 18.061 13.885c6.472 1.461 18.165-.105 26.935-1.463 8.769-1.357 15.764-4.489 18.582-9.603 2.818-5.117 3.236-6.578 3.236-6.578s8.353 11.797 15.556 13.155c7.203 1.357 28.605-5.952 28.605-5.952s13.051-3.549 15.346-8.038c2.297-4.489 8.353-19.209 8.353-19.209zM44.456 169.038l-.361-.166-2.013-1.736z" id="jkpath12" fill="#cb3349"/><g id="jkg97"><path class="jkst1" d="M195.832 70.085a48.125 48.125 0 0 0-.21-2.009 26.472 26.472 0 0 0-.215-1.424c-1.793-1.509-3.831-2.851-5.952-4.071-2.299-1.343-4.704-2.546-7.159-3.663-2.438-1.15-4.942-2.191-7.461-3.207a134.313 134.313 0 0 0-3.798-1.477c-1.269-.495-2.55-.956-3.835-1.424 2.697.447 5.366 1.059 8.015 1.741 1.723.446 3.437.945 5.14 1.477-12.112-31.655-41.07-52.27-72.687-52.27-31.622 0-60.577 20.615-72.686 52.27a109.044 109.044 0 0 1 5.137-1.477c2.653-.682 5.323-1.294 8.018-1.741-1.289.468-2.567.929-3.84 1.424-1.267.472-2.536.967-3.798 1.477-2.519 1.016-5.016 2.057-7.46 3.207-2.45 1.117-4.857 2.32-7.156 3.663-2.121 1.219-4.157 2.562-5.957 4.071-.075.457-.151.951-.21 1.424a51.768 51.768 0 0 0-.21 2.009 51.354 51.354 0 0 0-.177 4.061 59.216 59.216 0 0 0 .5 8.11c.37 2.692.864 5.366 1.595 7.951.36 1.295.768 2.572 1.24 3.808.237.617.495 1.225.764 1.816.134.294.274.585.413.864l.172.328c.199.101.408.204.607.3l1.204.575c.671.305 1.6.746 2.368 1.09.043-.037.086-.075.123-.114l-2.235-8.513c.474-.13 4.718-1.225 12.032-2.617a38.816 38.816 0 0 1-1.772-.381c-1.665-.414-3.309-.919-4.899-1.564a22.415 22.415 0 0 1-2.309-1.115c-.742-.426-1.472-.908-2.037-1.548 8.036 2.622 24.64 1.434 39.399-.091 13.499-1.391 27.029-2.293 40.63-2.32 13.602.027 27.137.929 40.63 2.32 14.766 1.525 31.37 2.713 39.405.091-.564.64-1.293 1.123-2.035 1.548a22.5 22.5 0 0 1-2.308 1.115c-1.592.645-3.234 1.15-4.899 1.564-.247.059-.496.113-.743.166 8.02 1.488 12.689 2.697 13.188 2.831l-2.138 8.11c.43-.194.864-.381 1.29-.574l1.202-.575c.2-.097.403-.199.607-.3l.166-.328c.146-.279.286-.57.419-.864.27-.591.528-1.199.764-1.816a42.235 42.235 0 0 0 1.241-3.808c.731-2.585 1.225-5.259 1.595-7.951.345-2.685.526-5.398.501-8.11a50.874 50.874 0 0 0-.179-4.059z" id="jkpath14" fill="#f4edae"/><path class="jkst2" d="M116.787 182.661c-1.064.16-2.128.295-3.186.375-.682.033-1.404.102-2.059.102l-.242.005c.822-1.837 1.446-3.26 1.919-4.339.963 1.08 2.188 2.417 3.568 3.857z" id="jkpath16" fill="#e6ccad"/><path class="jkst2" d="M119.101 185.018c3.304 3.272 7.398 5.146 11.904 5.479-7.569 3.074-14.702 4.26-20.197 4.63-5.478.367-11.032-.279-16.474-1.771.456-.082.79-.14 1.193-.189.447-.054 10.206-1.327 14.605-7.868l.413.009 1.08-.009c.731 0 1.395-.06 2.094-.087a43.69 43.69 0 0 0 4.878-.703c.167.171.333.338.504.509z" id="jkpath18" fill="#e6ccad"/><path class="jkst3" d="M128.464 87.071a98.82 98.82 0 0 1-1.048 1.343c-1.933 2.444-4.614 5.57-7.794 8.627a369.585 369.585 0 0 0-11.404-.177c-6.46 0-12.655.171-18.537.457 8.311-3.449 18.296-6.818 29.109-8.842a113.323 113.323 0 0 1 9.674-1.408z" id="jkpath20" fill="#656c67"/><path class="jkst3" d="M79.821 90.792c-2.966 2.084-6.317 4.744-9.566 7.971a360.155 360.155 0 0 0-21.567 2.81c9.207-4.232 19.713-8.127 31.133-10.781z" id="jkpath22" fill="#656c67"/><path class="jkst3" d="M181.48 107.969l-3.384 23.679-16.212 11.355-42.283-4.807-6.365-20.961a1.383 1.383 0 0 0-1.108-.971c-1.567-.253-2.953-.382-4.108-.382-1.16 0-2.541.129-4.115.382-.522.086-.95.461-1.106.971l-6.209 20.45-42.047 9.357-16.662-11.672-3.283-26.572c.715-.404 1.441-.806 2.176-1.209 1.031-.222 2.191-.457 3.475-.704l3.094 25.073c.048.392.264.741.586.967l11.462 8.032a1.425 1.425 0 0 0 1.101.213l34.57-7.692c.119-.027.237-.069.344-.124a1.39 1.39 0 0 0 .682-.827l6.225-20.498c1.67-.43 5.947-1.429 9.706-1.429 3.749 0 8.03.999 9.701 1.429l6.225 20.498c.161.532.624.912 1.176.977l34.57 3.927c.335.037.677-.05.952-.242l11.469-8.025c.31-.22.52-.566.573-.946l3.062-21.421c2.301.444 4.224.846 5.733 1.172z" id="jkpath24" fill="#656c67"/><path class="jkst3" d="M185.751 93.119l-2.976 11.29c-6.086-1.342-19.456-3.975-37.654-5.747 5.946-2.535 12-5.715 17.531-9.69 10.829 1.53 18.78 3.169 23.099 4.147z" id="jkpath26" fill="#656c67"/><g id="jkg32"><path class="jkst4" d="M63.841 128.441c2.357-1.274 5.021-1.085 9.19-1.079.447.011.908.005 1.39-.005.41-.005.822-.011 1.258-.022 4.296-.042 7.869.366 7.806-6.381-.065-6.746-3.062-12.198-7.354-12.155-4.297.037-8.454 5.564-8.197 12.306.07 1.756.328 3.023.742 3.937-3.745.938-4.777 3.254-4.835 3.399zm51.657-27.749a46.634 46.634 0 0 1-5.249 3.712l-6.097 3.68a52.065 52.065 0 0 0-7.331 1.467 1.216 1.216 0 0 0-.317.14 1.406 1.406 0 0 0-.629.794l-6.209 20.46-33.185 7.38-10.452-7.321-3.041-24.634c5.936-1.09 13.874-2.352 23.41-3.42a56.802 56.802 0 0 0-2.955 3.855l-5.677 8.149 8.266-5.511c.123-.086 5.387-3.549 13.998-7.761a377.407 377.407 0 0 1 35.468-.99z" id="jkpath28" fill="#e5caa3"/><path class="jkst4" d="M151.835 125.675c-.042-.16-.945-2.873-4.942-2.397.461-1.003.666-2.356.521-4.21-.528-6.731-4.443-12.08-8.735-11.931-4.292.152-7.042 5.731-6.805 12.478.236 6.741 3.84 6.694 8.132 6.543 5.77-.107 8.939-1.88 11.829-.483zm21.18-19.385l-2.992 20.944-10.539 7.379-33.141-3.766-6.183-20.363a1.41 1.41 0 0 0-.945-.934c-.205-.06-4.308-1.23-8.659-1.607l.795-.053c.687-.049 12.118-1.451 25.767-6.157 15.115 1.161 27.458 3.02 35.897 4.557z" id="jkpath30" fill="#e5caa3"/></g><g id="jkg38"><path class="jkst5" d="M63.841 128.441c2.357-1.274 5.021-1.085 9.19-1.079.447.011.908.005 1.39-.005.41-.005.822-.011 1.258-.022 4.296-.042 7.869.366 7.806-6.381-.065-6.746-3.062-12.198-7.354-12.155-4.297.037-8.454 5.564-8.197 12.306.07 1.756.328 3.023.742 3.937-3.745.938-4.777 3.254-4.835 3.399zm51.657-27.749a46.634 46.634 0 0 1-5.249 3.712l-6.097 3.68a52.065 52.065 0 0 0-7.331 1.467 1.216 1.216 0 0 0-.317.14 1.406 1.406 0 0 0-.629.794l-6.209 20.46-33.185 7.38-10.452-7.321-3.041-24.634c5.936-1.09 13.874-2.352 23.41-3.42a56.802 56.802 0 0 0-2.955 3.855l-5.677 8.149 8.266-5.511c.123-.086 5.387-3.549 13.998-7.761a377.407 377.407 0 0 1 35.468-.99z" id="jkpath34" fill="#c7b39a"/><path class="jkst5" d="M151.835 125.675c-.042-.16-.945-2.873-4.942-2.397.461-1.003.666-2.356.521-4.21-.528-6.731-4.443-12.08-8.735-11.931-4.292.152-7.042 5.731-6.805 12.478.236 6.741 3.84 6.694 8.132 6.543 5.77-.107 8.939-1.88 11.829-.483zm21.18-19.385l-2.992 20.944-10.539 7.379-33.141-3.766-6.183-20.363a1.41 1.41 0 0 0-.945-.934c-.205-.06-4.308-1.23-8.659-1.607l.795-.053c.687-.049 12.118-1.451 25.767-6.157 15.115 1.161 27.458 3.02 35.897 4.557z" id="jkpath36" fill="#c7b39a"/></g><path class="jkst2" d="M187.481 115.502c.508.419.911 1.504.456 6.558-.559 6.188-3.16 17.049-4.771 18.8-1.778.344-5.505-.064-7.778-.595.393-1.559.505-2.306.822-3.9l3.975-2.781c.317-.22.526-.566.58-.941l2.778-19.466c1.686.912 3.421 1.899 3.938 2.325z" id="jkpath40" fill="#e6ccad"/><path class="jkst2" d="M40.937 140.908c.199.704.408 1.407.624 2.1-2.139.628-6.495 1.23-8.465.886-1.633-1.645-4.679-12.966-5.345-18.978-.543-4.871-.162-5.924.333-6.334.575-.483 2.728-1.708 4.593-2.707l2.519 20.449c.048.393.257.741.586.967z" id="jkpath42" fill="#e6ccad"/><path class="jkst2" d="M121.347 141.194l-.151 1.305s-4.581 4.248-11.956 5.199c-7.375.95-13.171-3.582-13.171-3.582.242.788.586 2.567 2.256 4.086a53.184 53.184 0 0 0-6.313-.393c-.804 0-1.616.023-2.401.061-4.539.237-10.924 7.1-15.414 14.014-2.203.697-9.089 2.883-17.06 5.237-7.44-10.309-11.098-20.842-11.469-21.932l.005-.006c-.15-.419-.301-.839-.441-1.268l1.913 1.338v.005l4.726 3.309 1.58 1.101c.236.167.515.253.794.253.102 0 .204-.011.305-.031l43.435-9.67a1.385 1.385 0 0 0 1.025-.95l6.194-20.39c1.069-.145 2.008-.22 2.814-.22.801 0 1.746.075 2.815.22l6.374 20.997c.162.532.624.919 1.171.977z" id="jkpath44" fill="#e6ccad"/><path class="jkst2" d="M170.926 140.066l1.402-.984c-.232.973-.484 1.94-.747 2.896-1.949 6.248-4.25 11.774-6.805 16.656-.565.039-1.161.061-1.8.061-1.972 0-3.986-.167-6.215-.371-3.868-.355-10.007-1.058-11.946-1.283-1.67-1.332-7.385-5.873-12.14-9.615-.187-.151-.348-.291-.505-.42-.837-.708-1.789-1.513-3.717-1.513-1.751 0-4.308.638-10.489 2.508 3.212-2.401 3.233-5.5 3.233-5.5l.151-1.305 40.748 4.629a1.41 1.41 0 0 0 .955-.241l4.094-2.868z" id="jkpath46" fill="#e6ccad"/><path class="jkst6" d="M140.937 54.337c.124 3.625.033 10.194-1.655 16.345a1.335 1.335 0 0 0 0 .704 259.298 259.298 0 0 0-6.446-.591c2.412-5.054 2.938-10.436 3.052-12.332 1.852-1.317 3.696-2.896 5.049-4.126z" id="jkpath48" fill="#ebd599"/><path class="jkst6" d="M79.456 58.462c.112 1.896.638 7.267 3.046 12.317-2.149.171-4.297.37-6.441.596a1.328 1.328 0 0 0 0-.694c-1.686-6.139-1.772-12.714-1.654-16.345 1.353 1.231 3.19 2.81 5.049 4.126z" id="jkpath50" fill="#ebd599"/><path class="jkst7" d="M151.835 125.675c-2.89-1.396-6.059.377-11.828.484-4.292.151-7.896.198-8.132-6.543-.237-6.747 2.513-12.326 6.805-12.478 4.292-.15 8.207 5.2 8.735 11.931.145 1.854-.06 3.207-.521 4.21 3.996-.477 4.899 2.235 4.941 2.396zm-13.488-9.878a2.203 2.203 0 0 0 2.154-2.235 2.186 2.186 0 0 0-2.235-2.153 2.194 2.194 0 0 0 .081 4.388z" id="jkpath52" fill="#2d3136"/><circle transform="rotate(-1.049 138.093 113.428)" class="jkst8" cx="138.307" cy="113.602" id="jkellipse54" r="2.194" fill="#edf6fa"/><path class="jkst7" d="M83.484 120.953c.063 6.747-3.509 6.339-7.806 6.381-.435.011-.848.016-1.258.022-.482.011-.944.016-1.39.005-4.168-.005-6.833-.194-9.19 1.079.058-.145 1.09-2.461 4.835-3.4-.414-.914-.673-2.181-.742-3.937-.257-6.741 3.9-12.269 8.197-12.306 4.292-.042 7.289 5.411 7.354 12.156zm-6.634-3.529a2.195 2.195 0 1 0-.122-4.388 2.195 2.195 0 0 0 .122 4.388z" id="jkpath56" fill="#2d3136"/><circle transform="rotate(-1.473 76.78 115.216)" class="jkst8" cx="76.79" cy="115.23" id="jkellipse58" r="2.195" fill="#edf6fa"/><g class="jkst9" id="jkg64" opacity=".8"><path class="jkst6" d="M50.691 75.155s.667-8.692 2.03-12.023c.702-1.717 4.996-2.81 8.276-3.591 3.278-.78 8.508-2.342 9.524 2.264 1.015 4.606 2.653 7.963 3.746 9.446l-1.404-18.97-22.562 5.464-1.484 16.786.703 1.327 1.171-.703" id="jkpath60" fill="#ebd599"/><path class="jkst6" d="M164.855 75.155s-.666-8.692-2.029-12.023c-.703-1.717-4.997-2.81-8.275-3.591-3.28-.78-8.51-2.342-9.526 2.264-1.013 4.606-2.654 7.963-3.748 9.446l1.407-18.97 22.562 5.464 1.483 16.786-.703 1.327-1.171-.703" id="jkpath62" fill="#ebd599"/></g><path class="jkst10" d="M132.965 18.378s-.598 45.49-11.224 45.49h-14.875-12.752c-10.626 0-11.484-45.47-11.484-45.47l-5.22 15.438.085 21.183 3.707 2.947 1.685 9.096 2.357 5.307 45.482.084 2.105-3.791 1.769-6.4.254-4.043 5.023-14.341z" id="jkpath66" opacity=".75" fill="#ebd599"/><path class="jkst10" d="M166.429 60.794s2.187 15.692 7.974 18.522c5.788 2.829 0 0 0 0l-8.103-2.444z" id="jkpath68" opacity=".75" fill="#ebd599"/><path class="jkst10" d="M48.908 60.794s-2.187 15.692-7.975 18.522c-5.788 2.829 0 0 0 0l8.104-2.444z" id="jkpath70" opacity=".75" fill="#ebd599"/><path class="jkst7" d="M167.987 76.8c2.755.902 5.526 1.858 8.036 3.325-1.343-.532-2.729-.913-4.126-1.257a70.385 70.385 0 0 0-4.201-.924c-2.82-.531-5.65-.982-8.498-1.327-2.841-.37-5.687-.682-8.546-.924-2.858-.241-5.709-.483-8.573-.65-11.446-.704-22.924-.88-34.41-.892-11.483.006-22.962.221-34.409.897-2.862.166-5.715.409-8.572.651-2.857.241-5.71.548-8.546.923-2.847.345-5.678.796-8.498 1.327-1.407.264-2.81.57-4.206.919-1.391.344-2.783.725-4.126 1.257 2.509-1.466 5.28-2.427 8.041-3.331.232-.075.467-.139.703-.214-.015-.059-.032-.113-.043-.177-.048-.317-1.069-7.859.709-18.645.086-.516.456-.935.962-1.075l2.917-.831c.634-22.625 9.952-33.266 10.243-33.594-8.326 13.397-8.25 29.286-8.106 32.986l18.128-5.152c.016-.005.026-.005.042-.01.076-.016.151-.027.226-.032.021 0 .049-.006.075-.006a1.19 1.19 0 0 1 .297.027c.015 0 .031.011.053.016.075.016.145.042.224.075.033.016.054.033.086.049.058.033.119.07.177.112.016.011.034.016.049.033l.032.032c.016.016.037.027.054.044.012.016.494.493 1.262 1.209-.182-5.973.102-23.108 8.262-37.31-.172.498-6.646 19.428-4.415 40.645.724.58 1.486 1.149 2.229 1.649.359.247.58.655.585 1.09.006.07.161 6.833 3.148 12.586.042.086.074.177.102.268 7.429-.505 14.878-.709 22.312-.714 7.436.005 14.88.22 22.307.731.027-.097.06-.193.109-.285 2.986-5.753 3.142-12.516 3.142-12.586.01-.436.231-.843.591-1.09.741-.5 1.493-1.069 2.224-1.649 2.234-21.217-4.24-40.147-4.411-40.645 8.153 14.201 8.444 31.336 8.262 37.31a62.536 62.536 0 0 0 1.261-1.209c.016-.016.039-.027.053-.044.012-.01.018-.021.033-.032.016-.016.033-.022.049-.033.06-.042.119-.079.177-.118.028-.01.054-.027.081-.043.081-.033.155-.059.236-.08.016 0 .033-.011.049-.011.096-.021.2-.032.296-.027.027 0 .049.006.07.006.075.005.156.016.231.032.012.006.028.006.042.01l18.129 5.152c.146-3.7.221-19.59-8.104-32.986.289.328 9.609 10.969 10.237 33.594l2.922.831c.499.14.875.559.962 1.075 1.777 10.786.752 18.328.708 18.645-.01.065-.026.124-.042.182.239.07.47.139.707.215zm-3.297-.968c.14-1.207.789-7.809-.591-16.801l-20.52-5.833c.184 3.475.265 11.012-1.707 18.199a1.619 1.619 0 0 1-.101.258c.203.021.408.037.606.064 5.769.661 11.511 1.584 17.189 2.83 1.712.398 3.426.823 5.124 1.283zm-25.409-5.151c1.688-6.15 1.779-12.72 1.655-16.345-1.353 1.23-3.197 2.809-5.049 4.125-.114 1.896-.64 7.278-3.052 12.332 2.149.173 4.298.366 6.446.591a1.33 1.33 0 0 1 0-.703zm-56.78.098c-2.408-5.05-2.934-10.422-3.046-12.317-1.858-1.316-3.696-2.895-5.049-4.125-.119 3.631-.032 10.206 1.654 16.345.065.237.058.473 0 .694 2.145-.227 4.292-.425 6.441-.597zm-8.933.864a1.65 1.65 0 0 1-.098-.247c-1.975-7.187-1.889-14.723-1.712-18.199L51.244 59.03c-1.38 8.982-.736 15.583-.597 16.797 1.703-.462 3.411-.887 5.131-1.284 2.835-.628 5.693-1.154 8.556-1.638 2.869-.478 5.747-.843 8.626-1.192.205-.027.404-.042.608-.07z" id="jkpath72" fill="#2d3136"/><g id="jkXMLID_1_"><g id="jkg78"><path class="jkst7" d="M129.293 18.973v17.025h-12.068v-4.974h-2.72v22.981h4.109v12.85H97.505v-12.85h4.092v-22.98h-2.711v4.974h-12.06V18.973zm-3.626 13.408v-9.789H90.443v9.789h4.816v-4.974h9.964v30.225h-4.1v5.606h13.865v-5.606h-4.1V27.407h9.964v4.974z" id="jkpath74" fill="#2d3136"/><path class="jkst0" id="jkpolygon76" fill="#cb3349" d="M101.123 57.632h4.1V27.407h-9.964v4.974h-4.816v-9.79h35.224v9.79h-4.816v-4.974h-9.964v30.225h4.1v5.606h-13.864z"/></g></g><path class="jkst3" d="M30.694 93.119c1.759-.399 4.136-.907 7.051-1.47a104.37 104.37 0 0 0-6.222 4.597z" id="jkpath83" fill="#656c67"/><path class="jkst5" d="M95.111 139.78s.492 3.165-3.938 4.519c-4.428 1.355-32.482 9.716-35.682 9.263-3.199-.451-11.319-5.874-11.319-5.874l-1.969-7.004 12.016 7.492z" id="jkpath85" fill="#c7b39a"/><path class="jkst5" d="M120.242 139.167s-.354 3.182 4.131 4.345c4.484 1.161 32.875 8.295 36.05 7.704 3.176-.591 11.053-6.361 11.053-6.361l1.663-7.084-11.045 6.588z" id="jkpath87" fill="#c7b39a"/><path class="jkst5" d="M28.412 133.956s3.887 7.775 10.166 5.083l4.485 1.645-.448 3.29-9.419 1.195-2.541-1.494z" id="jkpath89" fill="#c7b39a"/><path class="jkst5" d="M187.551 131.822s-6.353 8.115-12.632 5.424l-2.019 1.302.448 3.289 9.419 1.196 2.54-1.495z" id="jkpath91" fill="#c7b39a"/><path class="jkst5" d="M89.279 192.904s23.03 11.611 49.106-4.188l-8.374-.571s-18.272 7.232-32.738 3.235z" id="jkpath93" fill="#c7b39a"/><path class="jkst7" d="M112.626 171.509l1.594 1.899c.036.046 3.577 4.26 7.906 8.552 2.879 2.853 6.357 4.297 10.343 4.297 1.361 0 2.791-.175 4.235-.523 1.34-.326 2.796-.673 4.287-1.03 5.384-1.287 11.482-2.749 14.438-3.577.585-.166 1.238-.315 1.925-.472 3.935-.909 9.329-2.163 12.187-7.889 2.149-4.297 5.047-9.874 7.197-13.961-1.863.859-3.816 1.79-5.203 2.52-2.138 1.123-4.938 1.667-8.558 1.667-2.152 0-4.266-.181-6.605-.389-4.675-.43-12.586-1.361-12.667-1.372l-.606-.067-.478-.383c-.071-.052-7.003-5.575-12.606-9.981-.227-.186-.434-.358-.621-.513-.59-.503-.59-.503-.942-.503-1.797 0-7.02 1.62-18.462 5.167l-.703.223-.689-.26c-.078-.026-7.585-2.81-16.581-2.81-.736 0-1.47.019-2.185.056-.901.046-5.958 2.448-12.425 12.68l-.419.657-.741.238c-.107.037-11.238 3.63-23.042 7.005l-.766.218-.725-.337c-.077-.031-4.696-2.174-9.091-4.194 2.397 3.541 5.462 7.958 8.159 11.422 4.711 6.067 10.649 11.674 22.034 11.674 1.428 0 2.945-.088 4.503-.265 11.581-1.309 14.563-1.837 16.168-2.117.543-.092.973-.171 1.522-.238.088-.011 9.571-1.237 12.232-7.206 2.744-6.134 3.298-7.595 3.319-7.651l.968-2.583s.12-.669.317-.877c0 .005 0 .005.005.005l.019.016c.305.219.757.902.757.902zM40.499 55.71c-2.516 1.014-5.016 2.06-7.46 3.209-2.449 1.119-4.856 2.32-7.155 3.66-2.121 1.222-4.157 2.563-5.954 4.076-.077.455-.149.952-.211 1.423a51.357 51.357 0 0 0-.388 6.068c-.026 2.713.16 5.426.502 8.112.372 2.692.864 5.369 1.594 7.952a41.963 41.963 0 0 0 1.243 3.804c.233.623.492 1.228.762 1.818.134.294.274.585.413.864l.172.326c.201.104.409.207.605.3l1.206.574c.673.311 1.6.751 2.366 1.093.046-.037.088-.078.124-.114l-2.231-8.511c.471-.129 4.717-1.227 12.032-2.619a33.744 33.744 0 0 1-1.775-.379 36.704 36.704 0 0 1-4.898-1.563 22.857 22.857 0 0 1-2.309-1.119c-.741-.425-1.471-.905-2.035-1.547 8.035 2.624 24.637 1.433 39.398-.088 13.501-1.393 27.028-2.293 40.628-2.325 13.6.031 27.138.931 40.63 2.325 14.77 1.522 31.374 2.713 39.406.088-.564.642-1.293 1.122-2.034 1.547-.739.42-1.522.782-2.309 1.119a36.965 36.965 0 0 1-4.903 1.563c-.244.056-.492.114-.741.166 8.02 1.486 12.689 2.697 13.186 2.832l-2.138 8.107c.43-.192.864-.377 1.288-.574l1.207-.574c.196-.094.404-.196.606-.3l.166-.326c.144-.279.284-.57.419-.864.27-.591.528-1.196.767-1.818.471-1.231.879-2.51 1.236-3.804.731-2.583 1.228-5.26 1.595-7.952.346-2.686.528-5.4.502-8.112a52.755 52.755 0 0 0-.176-4.059 51.573 51.573 0 0 0-.213-2.009 29.83 29.83 0 0 0-.213-1.423c-1.797-1.513-3.831-2.853-5.954-4.076-2.299-1.34-4.704-2.541-7.159-3.66-2.438-1.149-4.943-2.195-7.46-3.209a140.105 140.105 0 0 0-3.801-1.476c-1.267-.491-2.552-.956-3.835-1.423 2.696.445 5.369 1.06 8.013 1.739 1.724.446 3.444.948 5.141 1.481-12.11-31.658-41.07-52.272-72.685-52.272-31.622 0-60.576 20.614-72.684 52.272a107.832 107.832 0 0 1 5.135-1.481c2.651-.678 5.322-1.294 8.02-1.739-1.29.466-2.568.931-3.842 1.423-1.268.47-2.535.967-3.799 1.475zm159.43 18.316a53.972 53.972 0 0 1-.258 8.733 55.462 55.462 0 0 1-1.619 8.605c-.4 1.414-.86 2.811-1.404 4.198a38.295 38.295 0 0 1-.89 2.071c-.161.341-.331.678-.523 1.025l-.284.512a8.975 8.975 0 0 1-.348.574l-.294.457-.461.237c-.492.254-.895.445-1.342.653l-1.298.585a88.22 88.22 0 0 1-2.62 1.065c-.611.239-1.15.457-1.662.674l-1.444 5.487c-.036-.009-.471-.12-1.283-.315l-.078.574c1.594.833 4.726 2.522 5.793 3.403 2.148 1.775 2.299 4.587 1.823 9.841-.244 2.697-1.139 7.946-2.381 12.767-2.144 8.298-3.283 9.273-4.753 9.649-.746.192-1.894.383-3.008.383-2.266 0-5.353.063-7.429-.439-.533 1.888-2.055 6.812-5.068 12.962.151-.073.3-.135.435-.207 3.717-1.952 10.861-5.064 11.162-5.199l5.643-2.452-2.89 5.435c-.067.118-6.264 11.773-10.059 19.383-3.769 7.538-10.835 9.179-15.065 10.151-.637.151-1.241.291-1.733.425-3.035.854-9.18 2.319-14.599 3.623-.064.016-.13.033-.197.042a64.057 64.057 0 0 1-10.955 5.411c-14.568 5.518-29.923 5.208-43.844.092a647.05 647.05 0 0 1-9.193 1.097 45.12 45.12 0 0 1-4.985.291c-13.264 0-20.294-6.736-25.425-13.331-5.493-7.062-12.212-17.546-12.497-17.985L31 158.426l6.585 2.961c3.152 1.419 12.524 5.757 15.205 7 .217-.061.43-.124.642-.186-4.457-6.357-8.112-13.605-10.695-21.634-2.195.662-5.576 1.175-8.206 1.175-.961 0-1.822-.072-2.484-.228-1.471-.336-3.148-1.754-5.431-9.795-1.325-4.668-2.314-9.764-2.603-12.387-.57-5.121-.466-7.864 1.662-9.636 1.283-1.071 5.611-3.344 6.507-3.809l-.192-1.58c-13.75 8.08-21.991 15.22-22.157 15.366L0 134.302l7.005-11.047c5.544-8.755 11.948-15.832 17.84-21.284-.244-.098-.471-.196-.71-.294l-1.299-.585a34.907 34.907 0 0 1-1.34-.653l-.461-.237-.295-.457c-.166-.249-.238-.388-.347-.574l-.29-.512c-.181-.347-.358-.684-.518-1.025a30.878 30.878 0 0 1-.89-2.071 44.74 44.74 0 0 1-1.404-4.198 54.745 54.745 0 0 1-1.62-8.605 54.664 54.664 0 0 1-.259-8.733c.078-1.455.218-2.909.419-4.354.104-.725.213-1.45.358-2.17.15-.734.296-1.418.518-2.221l.155-.564.404-.317c2.294-1.802 4.768-3.163 7.284-4.369a78.87 78.87 0 0 1 6.311-2.616c5.943-16.493 16.162-31.118 29.591-41.311C74.337 5.57 90.664 0 107.671 0s33.334 5.57 47.218 16.106c13.43 10.193 23.649 24.819 29.588 41.307a78.282 78.282 0 0 1 6.316 2.62c2.515 1.206 4.99 2.567 7.283 4.369l.404.317.156.564c.227.803.372 1.487.517 2.221.146.72.26 1.445.357 2.17.203 1.443.348 2.897.419 4.352zm-11.995 48.031c.456-5.052.058-6.139-.455-6.554-.513-.43-2.247-1.412-3.935-2.329l-2.779 19.464a1.39 1.39 0 0 1-.58.942l-3.977 2.781c-.315 1.593-.429 2.345-.817 3.903 2.273.528 5.999.938 7.775.595 1.612-1.748 4.214-12.61 4.768-18.802zm-5.161-17.648l2.977-11.29c-4.318-.978-12.27-2.615-23.1-4.148-5.53 3.976-11.582 7.155-17.53 9.691 18.199 1.771 31.57 4.406 37.653 5.747zm-4.68 27.237l3.385-23.676a240.127 240.127 0 0 0-5.731-1.169l-3.059 21.422a1.415 1.415 0 0 1-.575.943l-11.472 8.023c-.27.192-.616.28-.947.243l-34.572-3.929a1.391 1.391 0 0 1-1.176-.973l-6.227-20.5c-1.668-.431-5.949-1.43-9.696-1.43-3.764 0-8.041.999-9.708 1.43l-6.228 20.5a1.388 1.388 0 0 1-1.025.947l-34.572 7.692a1.483 1.483 0 0 1-.306.033 1.36 1.36 0 0 1-.792-.25l-11.467-8.029a1.396 1.396 0 0 1-.585-.968l-3.091-25.072c-1.284.249-2.443.487-3.479.703-.734.405-1.46.809-2.174 1.213l3.281 26.568 16.666 11.675 42.047-9.354 6.207-20.449a1.389 1.389 0 0 1 1.108-.975c1.574-.253 2.95-.382 4.116-.382 1.153 0 2.536.129 4.105.382.528.083.957.461 1.108.975l6.366 20.956 42.282 4.808zm-8.07-4.411l2.992-20.948c-8.439-1.536-20.78-3.394-35.897-4.554-13.647 4.707-25.077 6.108-25.766 6.155l-.797.057c4.353.374 8.454 1.544 8.66 1.605.452.135.804.481.944.933l6.186 20.366 33.138 3.764zm2.303 11.845l-1.404.983-3.779 2.651-4.095 2.868c-.279.192-.621.28-.954.243l-40.746-4.633-2.966-.337a1.39 1.39 0 0 1-1.171-.977l-6.377-20.998c-1.066-.145-2.014-.219-2.81-.219-.809 0-1.751.073-2.817.219l-6.192 20.392a1.383 1.383 0 0 1-1.025.946l-43.435 9.672c-.103.02-.206.03-.305.03-.279 0-.559-.083-.798-.253l-1.578-1.098-4.726-3.307v-.011l-1.91-1.335c.135.43.289.85.441 1.268l-.006.006c.368 1.092 4.028 11.622 11.467 21.929a873.96 873.96 0 0 0 17.057-5.234c4.488-6.917 10.877-13.777 15.418-14.014a51.12 51.12 0 0 1 2.402-.061c2.221 0 4.344.16 6.31.393-1.671-1.517-2.013-3.298-2.256-4.085 0 0 5.793 4.53 13.17 3.584 7.378-.953 11.959-5.204 11.959-5.204s-.021 3.102-3.236 5.503c6.182-1.869 8.739-2.511 10.489-2.511 1.931 0 2.883.808 3.717 1.519.161.129.322.268.507.419a3519.302 3519.302 0 0 1 12.141 9.614c1.936.227 8.075.926 11.943 1.283 2.23.201 4.245.372 6.217.372.637 0 1.233-.026 1.797-.063 2.558-4.88 4.857-10.411 6.808-16.653.261-.96.516-1.928.743-2.901zm-15.034-51.593c-.01-.006-.02-.012-.031-.012a551.624 551.624 0 0 0-9.826-.651 905.6 905.6 0 0 0-13.667-.668 72.95 72.95 0 0 1-1.574 2.225c-2.479 3.355-7.398 9.51-13.704 14.729 8.926-1.6 24.409-5.56 37.803-14.905.336-.238.668-.486.999-.718zm-29.876.926c.377-.471.729-.926 1.044-1.34-3.281.331-6.512.808-9.67 1.408-10.814 2.024-20.801 5.389-29.11 8.837a383.259 383.259 0 0 1 18.54-.455c3.908 0 7.708.067 11.404.176 3.179-3.056 5.861-6.182 7.792-8.626zm3.587 102.085c-4.503-.332-8.598-2.205-11.903-5.477a271.86 271.86 0 0 0-.502-.512 44.25 44.25 0 0 1-4.881.704c-.698.026-1.361.087-2.091.087l-1.083.011-.413-.011c-4.396 6.539-14.159 7.813-14.605 7.87-.403.046-.734.103-1.191.186 5.442 1.491 10.996 2.138 16.474 1.77 5.492-.367 12.627-1.558 20.195-4.628zm-17.4-7.461a45.604 45.604 0 0 0 3.184-.378 138.958 138.958 0 0 1-3.568-3.857 398.441 398.441 0 0 1-1.92 4.339h.243c.658.001 1.378-.071 2.061-.104zm-3.354-78.632c1.827-1.103 3.582-2.366 5.249-3.712a422.33 422.33 0 0 0-7.278-.072c-10.137 0-19.606.415-28.189 1.061-8.61 4.209-13.875 7.672-13.998 7.76l-8.268 5.514 5.679-8.149a52.452 52.452 0 0 1 2.956-3.857c-9.536 1.066-17.477 2.329-23.41 3.422l3.038 24.632 10.453 7.321 33.184-7.378 6.212-20.464c.104-.337.331-.621.627-.793.098-.063.202-.109.315-.14.192-.052 3.51-.999 7.336-1.465zm3.816-18.788c-2.31-.036-4.623-.057-6.933-.062h-.005c-3.39.005-6.787.041-10.189.109l-6.269 2.971c-.005.005-.041.021-.088.048-.942.46-9.174 4.613-16.919 12.021 6.943-3.65 17.146-8.418 29.153-12.115a144.186 144.186 0 0 1 11.25-2.972zM70.251 98.761c3.251-3.225 6.605-5.886 9.567-7.967-11.415 2.651-21.923 6.543-31.128 10.778a360.846 360.846 0 0 1 21.561-2.811zm2.159-9.949a150.122 150.122 0 0 1 11.813-2.796c-5.798.212-11.6.481-17.393.808-3.366.186-6.715.414-10.065.667-1.678.129-3.345.263-5.007.445-.476.046-.942.098-1.418.16-4.369 2.614-21.127 13.134-32.631 26.889 11.179-7.769 30.654-19.443 54.701-26.173zm-30.85 54.197a68.861 68.861 0 0 1-.621-2.102l-5.162-3.612a1.391 1.391 0 0 1-.586-.969l-2.516-20.449c-1.864.999-4.017 2.225-4.592 2.707-.497.409-.875 1.46-.336 6.332.668 6.01 3.712 17.333 5.348 18.979 1.968.347 6.327-.258 8.465-.886zm-3.815-51.36a229.005 229.005 0 0 0-7.051 1.47l.829 3.127a103.93 103.93 0 0 1 6.222-4.597z" id="jkpath95" fill="#2d3136"/></g></g></symbol><symbol viewBox="0 0 24 24" fill-rule="evenodd" clip-rule="evenodd" stroke-linejoin="round" stroke-miterlimit="1.414" id="tune" xmlns="http://www.w3.org/2000/svg"><path d="M6.85 2.852h-2v6h2v-6m12 0h-2v10h2v-10m-16 10h2v8h2v-8h2v-2h-6v2m12-6h-2v-4h-2v4h-2v2h6v-2m-4 14h2v-10h-2v10m4-6v2h2v4h2v-4h2v-2h-6z" fill="#fbc02d" fill-rule="nonzero"/></symbol><symbol viewBox="0 0 50 50" id="twig" xmlns="http://www.w3.org/2000/svg"><path d="M9.727 47.556c-.125-.223-.297-2.168-.183-2.087.034.025.171.267.304.537.132.27.282.487.332.482.123-.011.075-1.196-.1-2.454-.331-2.398-1.176-4.435-2.358-5.69-.2-.212-.344-.4-.319-.419.093-.067 1.327.843 1.842 1.359.293.293.735.825.981 1.181.328.474.465.618.51.534.078-.147-.21-9.903-.376-12.701-.074-1.255.063-1.023.61 1.035 1.064 4.006 1.858 7.922 2.342 11.55.086.637.173 1.172.195 1.19.022.016.092.001.157-.034.888-.483 1.524-.667 2.55-.736.727-.048.945.062.35.178-1.15.222-1.99 1.013-2.344 2.201-.315 1.061-.327 2.707-.024 3.434.152.366.037.426-1.067.56-.716.088-.977.096-1.202.037-.356-.092-1.118-.098-1.195-.008-.031.036-.243.066-.47.066-.38 0-.423-.017-.535-.215zm1.974-3.233c.152-.205.072-.41-.204-.522-.225-.09-.263-.088-.437.025-.21.137-.252.43-.08.554.18.13.607.096.72-.057zm1.248.086a.763.763 0 0 0 .214-.203c.241-.33-.352-.622-.745-.366-.406.265.08.785.531.569zm2.288 3.094c-.033-.039.117-.387.334-.775.216-.387.411-.665.433-.618.07.152-.201 1.28-.33 1.372-.15.108-.354.117-.437.02zM8.2 47.092c-.29-.343-.221-.434.14-.182.176.123.321.263.321.31 0 .165-.279.087-.46-.128zm8.649-.145c0-.053.102-.18.227-.282.25-.204.312-.113.143.207-.095.18-.37.236-.37.075zm8.065-.827c-.243-.025-.48-.088-.527-.141-.11-.125-.114-3.043-.004-3.043.045 0 .132.149.193.331.127.38.228.42.31.124.094-.337.065-3.472-.039-4.297-.449-3.55-1.865-6.124-4.342-7.89-1.086-.774-2.653-1.436-4.047-1.711-.764-.15-.522-.224.598-.182 2.364.089 4.167.706 5.847 2.001a11.046 11.046 0 0 1 2.32 2.502c.453.682.64.854.64.584 0-.07.063-.882.139-1.805.679-8.26 2.396-15.1 4.984-19.86 1.86-3.422 5.108-6.817 7.885-8.244 1.397-.718 2.539-.988 4.02-.952.933.023 1.01.036 1.77.307a6.822 6.822 0 0 1 1.363.662c.612.407 1.309 1.004 1.235 1.058-.026.018-.343-.165-.705-.407-2.657-1.771-5.062-1.52-7.12.742-1.108 1.22-2.651 3.53-3.634 5.443-2.828 5.503-4.541 11.464-5.291 18.413-.163 1.509-.282 3.76-.195 3.703.032-.022.266-.52.518-1.108 1.597-3.723 3.578-6.428 5.79-7.908.672-.449 1.612-.904 1.715-.83.022.016-.172.22-.432.454-1.957 1.754-3.248 3.76-4.232 6.572-.938 2.68-1.366 5.588-1.368 9.3-.002 1.741.188 4.385.366 5.101.125.505.08.546-.585.546-.55 0-2.306.138-3.416.27-.414.05-.817.04-1.609-.036-.58-.056-1.129-.119-1.218-.14-.165-.037-.18-.014-.2.302-.01.186-.098.203-.728.139zm2.507-6.725c.294-.11.375-.22.375-.517 0-.63-1.309-.706-1.524-.088-.074.211.13.51.42.616.297.108.413.106.73-.011zm2.369-.052c.277-.222.318-.364.174-.611-.4-.691-1.755-.307-1.428.404.121.266.299.35.738.354.227 0 .387-.045.516-.147zm3.011 6.681c-.027-.05.088-.268.256-.484.879-1.135 1.22-1.544 1.284-1.544.04 0 .056.037.036.082l-.423.964c-.212.485-.445.924-.519.977-.169.122-.57.125-.634.005zm2.446-.596c0-.121.853-.683.896-.59.018.04-.056.209-.166.376-.168.259-.238.305-.464.305-.164 0-.266-.035-.266-.091zm-13.04-.124c-.177-.159-.493-.656-.462-.725.018-.038.248.1.512.309.264.207.457.405.428.438-.075.088-.371.074-.478-.022z" fill="#9bb92f" stroke-width=".078"/></symbol><symbol viewBox="0 0 500 500" fill-rule="evenodd" clip-rule="evenodd" stroke-linejoin="round" stroke-miterlimit="1.414" id="typescript" xmlns="http://www.w3.org/2000/svg"><path d="M49 51h408v408H49V51zm246.669 314.879l19.463-1.702c.922 7.8 3.067 14.199 6.435 19.198 3.368 4.998 8.597 9.04 15.688 12.124 7.09 3.085 15.067 4.627 23.93 4.627 7.87 0 14.819-1.17 20.845-3.51 6.027-2.34 10.512-5.548 13.455-9.625 2.942-4.077 4.413-8.526 4.413-13.348 0-4.892-1.418-9.164-4.254-12.816-2.836-3.651-7.516-6.718-14.039-9.2-4.183-1.63-13.436-4.165-27.759-7.604s-24.355-6.683-30.099-9.732c-7.445-3.899-12.993-8.739-16.644-14.517-3.652-5.779-5.478-12.249-5.478-19.41 0-7.871 2.234-15.227 6.701-22.069 4.467-6.842 10.99-12.036 19.569-15.581 8.58-3.546 18.116-5.318 28.61-5.318 11.557 0 21.75 1.861 30.577 5.584 8.828 3.722 15.617 9.199 20.368 16.432 4.75 7.232 7.303 15.421 7.657 24.568l-19.782 1.489c-1.064-9.856-4.662-17.301-10.795-22.335-6.133-5.034-15.191-7.551-27.174-7.551-12.479 0-21.573 2.286-27.281 6.86-5.707 4.573-8.561 10.086-8.561 16.538 0 5.602 2.021 10.21 6.062 13.826 3.971 3.617 14.34 7.321 31.109 11.115 16.769 3.793 28.273 7.108 34.513 9.944 9.076 4.183 15.776 9.483 20.101 15.9 4.325 6.417 6.488 13.809 6.488 22.175 0 8.296-2.375 16.113-7.126 23.452-4.751 7.338-11.575 13.046-20.474 17.123-8.898 4.077-18.913 6.116-30.045 6.116-14.11 0-25.933-2.056-35.47-6.169-9.537-4.112-17.017-10.299-22.441-18.559-5.424-8.26-8.278-17.602-8.562-28.025zm-65.728 50.094V278.454h51.583v-18.399H157.938v18.399h51.37v137.519h20.633z" fill="#0288d1"/></symbol><symbol viewBox="0 0 500 500" fill-rule="evenodd" clip-rule="evenodd" stroke-linejoin="round" stroke-miterlimit="1.414" id="typescript-def" xmlns="http://www.w3.org/2000/svg"><path d="M457 459H49V51h408v408zM69 71v368h368V71H69z" fill="#0288d1"/><text x="342.219" y="344.544" font-family="ArialMT" font-size="12" fill="#0288d1" transform="translate(-6058.94 -5838) scale(18.1514)"><tspan style="-inkscape-font-specification:sans-serif" font-family="sans-serif" font-weight="400">TS</tspan></text></symbol><symbol viewBox="0 0 24 24" id="url" xmlns="http://www.w3.org/2000/svg"><path d="M16 6h-3v1.9h3a4.1 4.1 0 0 1 4.1 4.1 4.1 4.1 0 0 1-4.1 4.1h-3V18h3a6 6 0 0 0 6-6c0-3.32-2.69-6-6-6M3.9 12A4.1 4.1 0 0 1 8 7.9h3V6H8a6 6 0 0 0-6 6 6 6 0 0 0 6 6h3v-1.9H8c-2.26 0-4.1-1.84-4.1-4.1M8 13h8v-2H8v2z" fill="#42a5f5"/></symbol><symbol viewBox="0 0 24 24" id="verilog" xmlns="http://www.w3.org/2000/svg"><path d="M17.282 17.08H6.718V6.513h10.564m4.226 4.226V8.627h-2.113V6.514c0-1.173-.95-2.113-2.113-2.113H15.17V2.288h-2.113v2.113h-2.112V2.288H8.83v2.113H6.718c-1.173 0-2.113.94-2.113 2.113v2.113H2.492v2.113h2.113v2.113H2.492v2.113h2.113v2.113a2.113 2.113 0 0 0 2.113 2.113H8.83v2.113h2.113v-2.113h2.112v2.113h2.113v-2.113h2.113a2.113 2.113 0 0 0 2.113-2.113v-2.113h2.113v-2.113h-2.113V10.74m-6.339 2.113h-2.112V10.74h2.112m2.113-2.113H8.831v6.34h6.338z" fill="#ff7043" stroke-width="1.056"/></symbol><symbol viewBox="0 0 24 23.999999" id="vfl" xmlns="http://www.w3.org/2000/svg"><defs><style>.jra{fill:#f05223}.jrb{fill:url(#jra)}</style><radialGradient id="jra" cx="205.45" cy="208.29" r="225.35" gradientTransform="matrix(.04556 0 0 .0456 2.888 2.88)" gradientUnits="userSpaceOnUse"><stop stop-color="#ffd104" offset="0"/><stop stop-color="#faa60e" offset=".35"/><stop stop-color="#f05023" offset="1"/></radialGradient></defs><title>houdinibadge</title><g stroke-width=".046"><path class="jra" d="M19.97 3H4.03A1.03 1.031 0 0 0 3 4.031v4.135C4.548 6.977 6.563 6.21 8.948 6.21c5.107.003 8.35 3.574 8.348 8.081 0 3.13-1.46 5.485-3.746 6.71h6.42A1.03 1.031 0 0 0 21 19.968V4.031a1.03 1.031 0 0 0-1.03-1.03z" fill="#f4511e"/><path class="jrb" d="M3 17.722v2.247A1.03 1.031 0 0 0 4.03 21h1.837C4.474 20.21 3.49 19 3 17.722z" fill="url(#jra)"/><path class="jra" d="M8.948 8.231c-2.586-.09-4.598.86-5.948 2.264v3.163c.918-2.654 3.447-3.87 5.565-3.85 2.647.027 4.689 2.025 4.7 4.284.012 2.159-.892 3.748-3.33 4.14-1.33.213-3.411-.567-3.318-2.578.046-1.037.854-1.622 1.777-1.58-.905 1.213.293 2.102 1.139 1.921 1.048-.224 1.475-1.156 1.475-1.878 0-.762-.718-1.994-2.498-1.951-2.204.052-3.591 1.639-3.638 3.602-.056 2.468 2.253 4.091 4.622 4.121 3.48.046 5.543-2.24 5.539-5.586-.005-3.029-2.434-5.946-6.085-6.072z" fill="#f05223"/></g></symbol><symbol viewBox="0 0 24 24" id="virtual" xmlns="http://www.w3.org/2000/svg"><path d="M21 14H3V4h18m0-2H3c-1.11 0-2 .89-2 2v12a2 2 0 0 0 2 2h7l-2 3v1h8v-1l-2-3h7a2 2 0 0 0 2-2V4a2 2 0 0 0-2-2z" fill="#42a5f5"/></symbol><symbol viewBox="0 0 281.25 281.25" id="visualstudio" xmlns="http://www.w3.org/2000/svg"><path d="M196.18 101.74l-52.778 42.444 52.778 40.889V101.74m-136.67 110l-30-18.889v-100L62.843 81.74l47.778 37 96.666-89.222 44.444 27.778v172.22l-55.555 22.222-85.111-81.555-51.555 41.555m3.333-48.889l20.667-19.111-20.667-19.778z" fill="#ab47bc" stroke-width="11.111"/></symbol><symbol viewBox="0 0 300 300" id="vscode" xmlns="http://www.w3.org/2000/svg"><defs><style>.icon-canvas-transparent{fill:#f6f6f6;opacity:0}.icon-white{fill:#fff}</style></defs><title>BrandVisualStudioCode</title><path d="M218.62 29.953l-105.41 96.92L54.301 82.47 29.955 96.64l58.068 53.359-58.068 53.359 24.346 14.212 58.909-44.402 105.41 96.878 51.424-24.976V54.93zm0 63.744v112.6l-74.719-56.302z" fill="#2196f3" stroke-width="17.15"/></symbol><symbol viewBox="0 0 24 24" id="vue" xmlns="http://www.w3.org/2000/svg"><path d="M1.821 4.15l10.21 17.618L22.24 4.235V4.15h-7.692L12.113 8.33 9.691 4.15H1.82z" fill="#41b883"/><path d="M5.937 4.15l6.152 10.616 6.18-10.617h-3.722l-2.434 4.179-2.422-4.179H5.937z" fill="#35495e"/></symbol><symbol viewBox="0 0 420 419" id="watchman" xmlns="http://www.w3.org/2000/svg"><g stroke="#fff" stroke-linecap="round" stroke-linejoin="bevel"><path d="M166.95 145.32a93.935 123.23 0 0 1 92.934 3.263" fill="none" stroke-width="18.467"/><path d="M162.92 137.96L44.63 256.25a174.07 173.93 0 0 0 5.705 16.486l123.68-123.68-11.096-11.096zM266.54 144.04l-11.096 11.096 117.16 117.16a174.07 173.93 0 0 0 5.691-16.5l-111.76-111.76zm170.65 170.65v22.193l17.1 17.1 11.096-11.098-28.195-28.195z" fill="#fff" stroke-width="1.963"/><path d="M167.52 273.36a93.935 123.23 0 0 1 92.934-3.263" fill="none" stroke-width="18.467"/><path d="M49.516 144.56a174.07 173.93 0 0 0-.809 2.213 174.07 173.93 0 0 0-4.757 14.344 174.07 173.93 0 0 0-.016.055l119.56 119.56 11.098-11.096-125.07-125.07zM454.87 64.703l-17.668 17.668v22.191l28.764-28.764-11.096-11.096zm-80.984 80.984l-117.86 117.86 11.098 11.096 112.18-112.18a174.07 173.93 0 0 0-5.416-16.777z" fill="#fff" stroke-width="1.963"/></g><image x="21.229" y="20.262" width="378" height="377.1" preserveAspectRatio="none" xmlns:xlink="http://www.w3.org/1999/xlink" xlink:href="data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAaQAAAGjCAYAAABjSWGNAAAgAElEQVR4AeydB3hUVdrH/+fOpNMF JAFUmivFXtZuIBRRQUUTil1RV3et6Lr6rSu6rg3B1dXVtXeBCCioCASIimLDhkFsgAIJSAkhPZmZ 8z3vjReGMJlMueeWmfc88MzMLaf8zp3855zznvcV4MQEkoxA7sVr01PrUrv4pdZNBNA1AHQRkJ0g ZAcJrYOA7Aipv8/Q6JgUKdBkG0iZISDSDVwSaAfAY3z+/bVSAD56L+mfwA5INAqgSkrUCKA2IOQO IcQOIUU5pNwhNZQLia2Q2OyH3OJBxqaiwk4VzfLlj0wg4QmIhG8hNzDJCEgxJH9DjgfevgGBAyCx vxSyh5DoAWA/CHQH0MEFUOoBbJbAeiHEBiED6wMSv0DgF02In1J2Vq+ZP78fXcOJCSQMARakhOnK 5GrICaO3tE3P8B0kERgkgP6Qop8E+gqgL4Bdo5gEphKAwHpIrIHAjxL4DpCrAo1yVfHsHhsSuN3c tAQmwIKUwJ2bGE2TYsjY33qLQOBwCHk4JA6HhgE08kmM9ilpRQUgvwPE1xD4MiC0r2R67dfFz/eq U1IaZ8oETCLAgmQSSM7GHAIj8td38sN7HAROkpDHATgMTWs15hSQvLn4JLBaQHwqEPgQQiwvmpHz XfLi4JY7kQALkhN7JYnqlDd2w4GQnuMk5AkCOBHAQQD4ubTgGZDAFg1ieQCBj6Dhg7SKmhW8LmUB eC6iRQL8xW8RDZ8wn4AUQ8eVHib9YjCELj4nAOhqfjmcY4wE6iGxAkIsCwDvSel5v7iwa1WMefFt TCBqAixIUSPjG6IhkDtmQw/N4xkKTQ4TEkNZgKKhZ/u1DZD4CAKLIMSik/p3WzF5sgjYXiuuQMIS YEFK2K61p2GTJ0tt2Xebj5EyMArAab+vAdlTGS7VVAI0xSeAd6QQb2uBtIW8V8pUvJwZz9XzM2AG AdpoKqpTh3uEdlYA8gwBdDEjX87D0QQaALlMCLzha5Rz2NTc0X3lmsrxCMk1XeWsig6/YFOWr0Ge JoBzICWNhNo6q4ZcGwsJkFOKT4TUZiOgzSqate8aC8vmohKIAAtSAnWm6qaMHPljWl2bzBGapk2A lKMBZKguk/N3JYHPpMCrHuGbuWj6fqWubAFX2hYCLEi2YHdToVIMKSg7SQAXABgDoJObas91tZWA H5DvCeCV2rrUwg/ndqm0tTZcuOMJsCA5vovsqeDwszd19af4LwbEZQAOtKcWXGoCEaiCwAwB7emi Gd0+TqB2cVNMJMCCZCJMt2dFFnIflJQNh8BEgKbkRIrb28T1dySBlULIpzyBwCsLCntud2QNuVK2 EGBBsgW7swqlvUIer+cSQE7UPWI7q3pcm8QlUAtgtibFU4sKu70PCJm4TeWWRUKABSkSSgl5DTkt 3XSqJuVfJDAiRFyfhGw1N8qxBH4AxJP+hrpnit/otcOxteSKKSXAgqQUr/My18216wMXCOB6AH9w Xg25RklOoEoI8bzw+R5ZNKvnj0nOIumaz4KUJF0+YsyWbL/Xdx0gr5BAxyRpNjfTvQQCkPIdDdqD iwqz33NvM7jm0RBgQYqGlguvbfKmrd2MJrPtNBc2gauc5ASElJ8EIO4/eWD2m+xLL7EfBhakBO3f wfmlR2oCtwI4G4CWoM3kZiURASmwGpAPVLTPeXnFk6IxiZqeNE1lQUqwrs4bW3Y0AoF/QIjT2Vdh gnUuN8cgsFYK3LNPoPzFwsKBDcZBfnU/ARYk9/eh3oIh+WXHaELeIZs8bCdIq7gZTCAsgXUQuK9T oPw5FqawnFxzkgXJNV0VuqJD8zcdDCHvlvpG1tDX8FEmkOAE1gohJp/Yv9vLvMbk7p5mQXJp/w0Z u7mPkP7JACbwGpFLO5GrbS4BiRII/H3xzOw3eZOtuWityo0FySrSJpWTO760s8cv7wQEeVVINSlb zoYJJBAB+bGQ4uaiwpxlCdSopGgKC5JLuvm4/PUZmcJ7NSD/DqCDS6rN1WQCdhKY45f+vxYX9vzJ zkpw2ZETYEGKnJVNV1L4h9JxAuI+9jNnUxdwsW4mQFZ4//E31N/NLomc340sSA7uo2H5Gw7zC+3f AjjFwdXkqjEBNxDYKgVuPbl/9rNs+ODc7mJBcmDf5J61toMnNfVfgLgCgNeBVeQqMQF3EpD4XGja NRyTyZndx4LkqH6RYkj+pouEkFMAdHZU1bgyTCBxCEgIPFvvabx52av7lydOs9zfEhYkh/Th0HM2 95Ye//8ADHVIlbgaTCDRCWwWENcWzcyemegNdUv7WJBs7qncXOnVum66XoBMuZFpc3W4eCaQdAQE xFyfz//n4tk9NiRd4x3WYBYkGzuEjBak0J6WwJE2VoOLZgJMANgJIW49qX+3J9jowb7HgQXJBva0 pyhLeO+QkJPYaMGGDuAimUDLBJYJgSuKZuR81/IlfEYVARYkVWRbyHfouLJcBPCUhOzbwiV8mAkw AXsJ1EOKe3Z07HYvh7mwtiNYkCzinXvx2nRvTdq9EriOw0JYBJ2LYQLxEfgCUlywuDB7VXzZ8N2R EmBBipRUHNcNGVt6hJB4CcCAOLLhW5kAE7CeQB1tqF0yI/thdtiqHj4LkkLG+fnSUy7KbpWQ/wBE isKiOGsmwASUEpBLPBouXji9+3qlxSR55ixIih6A3DEbemhe7WV2+6MIMGfLBKwnsA3AxMUzc96w vujkKJEFSUE/D87fOFoT4lkA+yjInrNkAkzAVgLyv/7MhknFz/eqs7UaCVg4C5KJnTpy5I9p9W3b TBGQf2HDBRPBclZMwHkEVnr8KFg4K2e186rm3hqxIJnUd0PGlO4vPHgdAkeZlCVnwwSYgLMJVEHI yxfP6D7d2dV0T+1YkEzoq6H5ZadLIV/gKToTYHIWTMBlBIQQj6bsrLpp/vx+9S6ruuOqy4IUR5fo VnRa2Z1S4jaeoosDJN/KBNxP4DMhcW5RYc6v7m+KfS1gQYqRvR6zKCXtVQiMjDELvo0JMIEEIiCB LR4p8hcVZr+XQM2ytCmapaUlSGF5+WUDvKnpn7EYJUiHcjOYgAkEBNAlIAKLho7deI0J2SVlFjxC irLbh+SXni0EXgTQJspb+XImwASShICAeC6lsuoqXleKrsNZkKLglVdQeiuAf/F6URTQ+FImkLwE lnkatXMWzun2W/IiiK7lLEgR8KL9RQ1tsyia60URXM6XMAEmwAQMAmsDHjFq6WvZJcYBfm2ZAAtS y2z0M7njSzt7/JgD4MRWLuXTTIAJMIFQBCqkkAVLZnRfGOokH9tNgI0adrPY611u/vq+Xr9YzmK0 Fxo+wASYQOQE2gsp3h4ytnRi5Lck55UsSC30+5D8smM8wvMhB9JrARAfZgJMIBoCXiHxVN7YjXcC kmemWiDHYEKAyRu76QzIALkDyQpxmg8xASbABGImQBZ4vt+6XVFcLHwxZ5KgN7IgNevYvPyNl0EI MmDwNDvFH5kAE2AC5hCQmJ9Zh3PnzcupMSfDxMiFp+yC+nFIQdlNEOIpFqMgKPyWCTAB8wkIjKxJ xyLy+GJ+5u7NkUdIet9JkVdQRvuLaJ8RJybABJiAVQS+9kvvqcWFXTdZVaCTy0l6QZo8WWrLVpU9 JoE/ObmjuG5MgAkkJgEB8ZNPw7Di6dnrErOFkbcqqQWJvHVvF2UU2fXCyJHxlUyACTAB0wn86pf+ vOLCnj+ZnrOLMkzaNaTcXOndLja9zGLkoqeVq8oEEpfAfh7hfW/4OaUHJW4TW29ZUo6Q8vNLUreh 42tCYEzriPgKJsAEmIBlBDZDiiGLC7NXWVaigwpKOkEiMSoXnQol5GgH9QNXhQkwASagE6C4SprU 8ooKu61MNiRJNWVH03Q0MmIxSrbHnNvLBNxDgOIqSREoGjq2tL97am1OTZNmhEQGDNtE6asCosAc dJwLE2ACTEAlAVkGIXMXz+jxg8pSnJR3UoyQfreme57FyEmPHteFCTCB8ARENqS2mJw8h78ucc4m gSBJUS7KHgVwfuJ0G7eECTCBJCHQwyM8i3LHbOiRDO1NeEEaMrbsHt70mgyPMreRCSQsgQM8Hu3d kfllXRK2hb83LKEFaUjBxluExN8SvRO5fUyACSQ4AYGBjQjMG5q/vX0itzRhBSlvbOmVAuLeRO48 bhsTYALJQ0AK8UcpamfT1pVEbXVCCtLg/I2jIfEYgKSxIkzUB5TbxQSYQDABMWS76Phsogb5SzhB Gjxu0x81IV7jEBLBDzG/ZwJMIIEInJc3tuyhBGrPrqYklCCReaQIBOYByNzVQn7DBJgAE0g0AhLX Dc0vTbj18YSZ0iILlEaBjyRk0tjsJ9p3jNvDBJhAVAQkpBy/uLD7jKjucvDFCTFCokW+Bsg3WIwc /KRx1ZgAEzCbgIAQz9IyhdkZ25VfAgiSFPoin8DxdkHkcpkAE2ACNhHI1AKBuYmycdb1gjS0oOz/ AJxn08PAxTIBJsAE7CbQ1ePV3kmEPUquFqS8/LJzJXCX3U8Dl88EmAATsJnAwVLUv+R2c3CPzRBj Ln5o/qaDIeRbABJ2k1jMcPhGJsAEkpHAH3oPrNTWlkxb6tbGu9LKLu/sDfsgRfsEQB+3gud6MwEm wAQUEJBC4NyiGTmzFeStPEvXTdlRKAmkaLTxlcVI+ePBBTABJuAyAkJKvKDPILms4lRd1wlSudj0 LwDDXMiaq8wEmAATsIJAG6kFZrnRyMFVgkQ+6iTkX63oUS6DCTABJuBaAhL9Aqhznc871wjS0HM2 99aEeIEdprr2K8IVZwJMwEICQmBMXsGmGy0sMu6iXGHUkHvx2nRPddpHEDg87hZzBkyACTCBpCEg G4UUQ4oKc5a5ocmuGCF5a9IeYjFyw+PEdWQCTMBZBESKFGKGbpnsrIqFrI3jBWno2NIxHII8ZN/x QSbABJhABARkDlI8z7lh06yjBWnImNL9IfF0BMT5EibABJgAE2iRgByVl192bYunHXLCsWtItN9o O8reBztNdcijwtVgAkzA5QTqhSaPK5re/UuntsOxI6RyUXo7i5FTHxuuFxNgAi4kkBaQ4tVRo0od G8DUkYI0JL/sGAlBXrw5MQEmwASYgEkEhMRBtZnifpOyMz0bx03ZDb9gU5a/PvAFgANNby1nyASY ABNgAlIKeeqSGd0XOg2F40ZI/nr/AyxGTntMuD5MgAkkEAEhpHh2RP76Tk5rk6MEacjYjcMBcZXT IHF9mAATYAIJRqC7X3gedVqbHDNld8LoLW3T0xu/BbCf0yBxfZgAE2ACiUhASoxZUpgzxyltc8wI KT29cQqLkVMeC64HE2ACyUBACPnYiRN+6eiUtjpCkIbll50C4AqnQOF6MAEmwASSg4DITvN7pzml rbZP2ZFNfE0GvuGAe055JLgeTIAJJB0BiZGLC3Petbvdto+QajPEP1iM7H4MuHwmwASSmoDA407Y MGurIOWNX3+IRMBV8TqS+qHlxjMBJpCoBA6oyRB32N042wRp8mSpwe99AhApdkPg8pkAE2ACTEDe OHjshkPt5GCbIC0r2XQlII+zs/FcNhNgAkyACewi4NWkeEIfLOw6ZO0bWwRpZH5ZFynkPdY2lUtj AkyACTCB8ATEse9/V3Zp+GvUnbVFkBqaxKiDumZxzkyACTABJhATAYl7cs9aa8vfZ8sFadi40qMA 2KbAMXUQ38QEmAATSBICAuiipabfZUdzLRYkKQIB+R8AFpdrB1oukwkwASbgTgIC8uph+RsOs7r2 lgrDkPxNFwHiWKsbyeUxASbABJhAVAQ8AWgPRXWHCRdb5qnhd48MPwDobkK9OQsmwASYABNQTEAI eVbRjO5vKi5mV/aWjZBq0sVNLEa7uPMbJsAEmIDjCUgpHjjyCmnZXlFLBGnEmC3ZEPJmx9PnCjIB JsAEmEAwgQM7lJddHXxA5XtLBMmX0vhPAG1UNoTzZgJMgAkwAQUEBG63KkSFckEafk7pQZC4SAEm zpIJMAEmwATUE9gn1Z/yV/XFWGB+7feARkdeKxrDZTABJsAEmID5BITENXnjN+9rfs575qh0hPT7 Jthz9iySPzEBJsAEmIDLCGSJQODvquusVJACAZC/OstMy1XD4vyZABNgAslKQEp5xfD8Tb1Utl+Z IA0pKD0ZwDCVlee8mQATYAJMwDICqX7NTwFVlSVlgiQgbQ/2pIwaZ8wEmAATSEYCUpw/ZOzmPqqa rkSQ8saVngSIIaoqzfkyASbABJiALQS8Aj5la0lKBAkBOdkWVFwoE2ACTIAJqCUgxflDz9ncW0Uh pgvS0PzSE3l0pKKrOE8mwASYgCMIeOFRY3FnuiBB4FZHIONKMAEmwASYgBICEvK84eM29jQ7c1MF KW/8+kMkMNLsSnJ+TIAJMAEm4CgCqb6AuMHsGpkqSPB7yL0E7zsyu5c4PybABJiAwwgI4PIR+es7 mVkt0wQpd1zZAQDGmlk5zosJMAEmwAQcS6CNT3j/bGbtTBMkLYAb2WedmV3DeTEBJsAEnE5AXntc /voMs2ppitPT3LPWdhCQl5hVKc6HCdhNwOsVaN9WQ1amQFaGQEaGhox0gdQUAaEBaali19x0Q6PU j1OdG30S/gDQ0CDh9wPVtQFUVUlUVgdQXRNATa20u2lcPhMwk0DnLOE5H8BTZmRqiiBpqekTAcnx jszoEc7DUgIkLL3396JvrxT03T8FPXK8yNnXgy77eJTUo7pG4tdSH9Zv9OGXDT78vK4Rq39uRFV1 QEl5nCkTUE1AAtcD8mlAxP1rK24DhNxc6fV0LfsZwH6qG875M4F4CXTqoOHQAWk4dGAqBv0hFQf0 NOU3WbzV0gXqu58asXJ1A75YWY/NW/xx58kZMAGrCAjg1KKZOQviLS/ub6O3a9mZksUo3n7g+xUR SEkROKR/Kv54eJr+v3t23I+8kpr27O4F/R9+StN0/MZNPnzxTQM++7oeK76pR31D3D8+ldSbM2UC REBKXAUgbkGKe4SUl1+6FAK53C1MwCkEaK3n2CPSMfiEdBx/VLpTqhVXPZZ9WocPP6vDxyvq9fWo uDLjm5mA+QQCwu/pVzRr3zXxZB2XIOXllw2AkCXxVIDvZQJmEKCR0PFHpWHw8Rk48ZjEEKGWuJAw LXq/Fp98WY/GRh45tcSJj1tLQEDcXzQz+2/xlBqfIBVsfBQQptqhx9MYvjf5CPQ5IAUjB2fgrFOz kq/xAN4qqsE7i2vww5rGpGw/N9pRBLZ2kuXdCwsHNsRaq5gFadSo0syaDGwE0CHWwvk+JhALAbKM yzsxA6OGZ6Jfr5RYski4e0iQ5i2qwdIPa1FXz6OmhOtglzRISHleUWH3V2OtbsyCNLRg46US4plY C+b7mEC0BPbt4sGoYZkYdybvMAjHbtbb1Zi7qAYby3zhLuNzTMB8AhIfLC7MoWjhMaWYBElKeeCM udULXp1TeQDtq+DEBFQSGHRQKkYPy8SQE03bEK6yuo7J+73ldZi7sBpfr4p5BsUxbeGKuIOApkE+ PLnTjQMOSv93LDWOWpBIjAB8T4XNW1iDp1/bCRalWNDzPa0ROO7IdH1a7pjD0lq7lM+HIfDNqgZ9 xFT8UW2Yq/gUE4iPAE2fP35fZyOTvwsh/mV8iPQ1FkG6F8AuSwoWpUhR83WREsg9PkMfER0yIDXS W/i6CAjQxtt5C6t1Cz3JExsREONLIiXQe78UPDlllxgZt3mFEFHt8I5KkKSU5E9lr4lpFiWDP7/G Q+CUY2lElIXDBrIQxcMxknsfeqoCbxfVRHIpX8MEwhJoQYzontOEEPPD3tzsZLSCdCqAkAWQKP3v 5Z1s4dMMMH9snQBNyZHFHE3RcbKOwKofGnTLPNrTxIkJxEIgjBhRdjOFEFGFJIpWkF4EcEFLFSdR eviZipZO83EmsAeBA3unYPTwTJw6OHOP4/zBWgLkmohMxskbBCcmECkB8gP59INdWru8vRBiZ2sX GecjFiQpJf3VqDZubOmVRaklMnzcINCxvaabbp9zuns3s1IYCWMdJjNDoLau6XNKCnaFojDa65ZX GimRVd53P/ImW7f0mV31pC0Y40a30Wc2WqnDJUKI51u5ZtfpaASJhl7Td90Z5g2LUhg4SXxKCGD8 WW1w6bi2jqZAISHWl/qwcZMf5OR0yzY/tu8IoKIygIqdgV1CFK4RXg/QsYMHnTtq6NTRg25dPdi/ hxf75XhBZuxOTrSPqfCtamzdHtV6tJObxHUzkUAUYkSlLhJCDI+0+GgE6U0AoyPNmEUpUlLJcR15 Vrj1Guc59aA/urRP59vVDbr7nTW/+pT7hyNh7pHtxYADU3BQ31TdiKNnjvO8kD8/sxKvzK6KSICT 4ynmVkYpRgawbCHEJuNDuNeIBElK2RHA9nAZhTrHohSKSnIdoz+8Z52a6Shfcx9/UY9PvqjDFysb 9BGQE3qka2cPjjg4DUcflgayNnRSuu/RHSj6gA0fnNQndtQlRjGiql4jhHg0kjpHKkgUnvzZSDJs fg2LUnMiyfP5vDFtcMlYZ0zP0R/UpR/V4qtvGxwfW4g8lx91SBpOOCYNp+Y6w+CDnLjSd/nnX3h9 KXm+wbtb2r6thosL2kayZrT7pt3vioUQg3d/bPldpIL0NtmUt5xN+DMsSuH5JNpZp3hYIKuxhe/V 6kHu3BqmgcSJggtS4D4nxHZ66fUqvPh6JU/jJdqXNkx7sjIFJo5vF6sYGTnvK4T4zfjQ0murgiSl bA9gR0sZRHqcRSlSUu69zuMBLhvXDgWj7bOeW7fBh/lLavQpJjJASKTUrq2GYSdl4KqL2tnerH8+ VI73PmYzcds7QnEFTBIjquWVQognW6tuJII0HkDM7sSDK8CiFEwjsd4POzkDt/zZPqMFGg3NXViD L1bWJxbYEK0ho4hDB6TqXi3sXG+a/U41XplTpVsehqgmH3I5ARPFiEgsFEKMaA1JJII0E0B+axlF ep5FKVJS7riOwoVfPLYtzjnNnlHRzHnVmLeoGmWbk9NEOWdfjy5M+WfYw5+e0keeqdB/DLjjieVa RkKA9tZdPiHuabrmRXUQQoT1nNCqIAUCcqYQ5gkS1ZBFqXk/ufOznaOiFwsrMXt+DaqqE2taLtYn gf6AnD0yyzYjkjcX1GD2/GqOwRRrBzrsvusuax/vmlGoFh0phPgi1AnjWKuCNOutquvHnJ71kHGD Wa8sSmaRtD4fWiuiX0/n2vCr/IXCSsycW+14Sznre6WpRK9XYMxpmbjiPHvWmZ54aSdef6tVhy52 4eFyIyCgSIyoZE0IEdbPPHnvDpu2+f5y7uqfGo/PO8nc4Gh/6JOCju09+OTLxJ/zDwvYZSfJ0uvZ aV0w4EBrvQ2QEP3ffeX63iF/cs7ORfSkBAJAyfeN+tpOba3EkYdYG0vqqEPT0KG9B6Wb/dhZyaPX iDrNQRepEqM/3bK14sWnMqesWnVnfILUa9Ckh0s3+Tuv/qkRLEoOenJsqArtKbpuIhldWpdmzK3C P6aU47Ov6sFCFDl38rNX8kMjXn2jGo0+icMHWSdM9GPzrFOzUF0r2S9e5F1m+5WqxOiav28lLyjp dahatGbV1F/DNTTsCGnI2M19hJR3UQbk14tFKRzKxD3X94AUfVOclc5Q58yvxgOPV2Dph3U8PRfH o0UjppXfNWDGvGpoAjjYQj96Rx+ahvbtPPh5nU93PhtHM/hWxQRUidH1d2zDqh9+30wtsGltydQl 4ZoSVpB6DbxxrADOMDIgUfplgw+nHMfTdwaTRH+lX7p33dwR/XqnWNLU5Svq8PgLO/HGuzU85WMi cRpdfvltAxYva9o71L+fNVOuB/VNAVkA/rYtgJ/WsZcHE7vUtKyuubQ9Ro8w3yMIiRH5iAxKaWtL pj4d9Hmvt2GNGobkl84SAmOa30V7H26/gdzbmZvY0MFcnvHmRhswrTTnfvQ5EiJnLoiTFdthA9NA 01H7dfcip5sHbTI1tMnSdMwNjVIfBZBn8A2lPtAG3a9K6rH2170CLMfbLabcT/uYRg/PwinHWec3 j4wdyOiBk3MIkBidaY0YUaP9XunvuqCwZ4t+UVsUpPx86dkuyrYCCLnbUZUokfnof54Na6runN5M 0JrQegMFzjvpj9b8saJ1ohdmVoH+qDsppaUKDD4hA8NPzsAhA2IbUWwvD+CDT+vwzmJn+oEbNSzT 0nXBjz6v04MB0pogJ3sJqBKjG+7YhpV7jox2N1SK/MWF2a/vPrDnuxYFKS9/w3EQ2kd7Xr7nJxal PXkkwieaXrnyAmtMht9bTt4VqvXwD05iRzvUzz29DS44t42p1fr863p9Ayn9UXZS0jToa4QTzja3 veHa+NyMptAW4a7hc+oIqBKj2+7djk/D/dgQeHLxjJwrW2pZi2tIvQbedJEQGNLSjXSc1pNUrCnR vDMthn7KJuHh8Jt+jqboLjjXGu/cT76yU18r2rzFOTbcNCKiP8r33baP7prHbMA53bz6iOsPfVJR XRvAxjJntJ0s8mh9icJy0B6zvr3UrxfSKJy+4zSlSdF3OVlH4M8Xt1MSDqZVMWpqYoe1JVMfaam1 LY6QhuaXLpACEUX645FSS3jdcZz2FNHUDXleUJ30Hf3vVDsmDhG1lzaTjh1tvZcDGimR/z0aOTkp 0ZoC/YK2Kt37nx1YvIzjLVnBm8SIPHqYnSIUI71YKf09lhT23BiqDiFHSLm50ivaVP0XQEQT5zxS CoXWHcdGDc/EnZM6os/+6n8VP/x0BV6aVYXKKmdsmKTRwLgz22DaHftYuk/HeDIoSuzQkzJ0I4kd lQE4ZbT4/c+NmPVONVJThCUboGmt0uMR+KpkD4ssAxO/mkTACWJETdEgVqxZNW1lqGaFFKRex151 tJDy6lA3tHSMRaklMs49ftn4tpg4Qf16EVnO0eZWChXuhETesmmt7N93ddajtNpdpwN6pmBEbia6 dfVie7kfW7fbL9iNjdBHbr+W+nGyBRFsDx63vWsAACAASURBVOmfqntuIevE6hqewjP7mVQlRpOn lmP5iihH+EJsXVsy9a1QbQwpSH37T5oAgWGhbgh3jEUpHB3nnOvYXsOV57eDFRtdyWKSgrrV1tn/ R4aEiPZVPfqvziAXN05LfQ5IwWl5mejU0YMt2wIor7BfmNat9+mjWlpfG/SHiCZMYsZKJvW0zWB9 qR9ULidzCKgSI4qJ9cEnMRnoZK0tmfpYqNaFXEPKKyibC8hRoW6I5BivKUVCyZ5rjjksDffc2kl5 4bQ2MnNuFTY5wGiBhIj+0N9wuXXrImYApvW2eQur9T1NZuQXbx4nHJ2OO28yf/9hqHqRN/cXX68K dYqPRUHgTxeocYIcb4DGVCm6zi/M3tK8KSFHSL0GTnpYADGvfPFIqTlmZ3ymxcxbrwm5rczUCj79 WiWeea0SVQ6YeqHQ3/+7vwsorLrbElmbjh6RpW++JWG321np+tKm0VJ6qoaBikdLhw5MA0XI/ea7 BvicYYzotscHThUjAtkoRPG6kqk/Noe6lyANz9/USwp5W/MLo/3MohQtMbXXTxzfFpeOU2vSvWRZ LR5+Zifo1e405MQMkH8uFRZFVreN3PzQVGN6uoYNZfavsaxYWY9fNvpwyrFqrTIP6puqm+GvXN2I Tb+xKkXz3CkTo3+bE7peCPnz2pJpxc3btJcgHXDwDacC4tzmF8bymUUpFmrm3pOWJnD1Re2Vxy4i bwsPP70Tv2219w8HucK59rL2utFC1857Pd7mwrU4NxqV0BoLWb+tXe9DXb1963L03SaHrRlpAqr9 4tEot6IyALL+49Q6AVViNOVxMs+Pac0oRKVFw9qSqS81P7HXN7b3oJsmAji2+YWxfmZRipVc/PfR lM+F57bF6UPNd5wYXLsHn6jQg+YFH7P6PcVposXb8We1Qbcuez3WVldHaXmDDkpFwag2ujB9v6YR ZBFnRyKHrZ99XY/ynQEce4TaKdE/Hp6u7xejDbycWiYwcUJbFIw23+MGidGCYlNnPjpflP/gA8XF e8ZH2suoIa9g43JAmCZIBjo2dDBIWPOad2KG8vUicoY7Y16VrdMpZC1HfvdIkJI1vTK7Cq+9UWXr iClnXw/yz2ijIuz1Ht1Khh5Pv7rTEVabe1TMAR9IjGhfndlJgRjpVfT40X/hrJzVwfXd46ckOVSt FVX/BmD6LkkeKQVjV/ueHkrVgfTojyB5bq6qtmfaiJydXj6haR8VbTBN5kR7eMjlEfXEdz82gmIg WZ0qq6Ue/ZnqQF7RVSUa9Xfr6sGOioDt08Oq2hhLvm4TI72NQi5vvkF2D0HKOfjKgwBcHwuQSO5h UYqEUnzXkPHChflqjRfuf2yHvpM/vprGdnf/vim4dFw7XH1RO9CGUjsTTR9R8Luff/FhQ5kfASn1 zZ121YmE4PwxbdDogx5M0w5h+mZVA35c68OQE9QZPPTaLwWnDs7E9h0BikRqF27HlKtKjMizyjtL TJ2m24OZEFi7pmTaouCDe0zZ5Y0tPQ8SLwdfoOL9iNwM3HyV+ebHyR66QtUGOOMZoCm619+2xw9d v14p+nTQaUPUrocZbW3p1fDYTYEEySlp89Q2S9MNSM4bY/7USfOyWvtMDmxnvV1tS+j3lBShj2DH nBbz7pHWmqeff3lWFZ6fWRnRtYl4kSoxeuSZCt3PokpmUorFSwqzhwaXsccIqffASRcBOD74AhXv KaTx5q1+0EY7M1Oyegnvso8Hl09ohzNPVfflnzm3Gv99YaflfugO6OnVR3yTrmwPEiW7Erk9euqV nfr+KtqP01KimE7kk418wdEIhabT7EpHHpKGC85pq6+30FSelYnaTgYPZAlI9VCVaOqW9iuFDXmg qnCb8724oK0+VWt2NawQI6qzEGi/tmTqA8H133OEVFBKw6c9FCv4YrPf80gpfqL0B2/a5H3izyhM DnZEcqV1IfJArvoXdphm66dKvm/QfynG6o2a3DSRqfa4s+wfMVE/vrmgOuTIrjUO8Zy3IuAjjd4L 36pC6WZ7tx3Ewymae0mMzj/H/GfKKjEy2iok9i8qzPl112fjDb3mFWwsBUR28DHV71WJ0pz51Xjs +cQOl2yFJd3N/9ymx8pR/RwY+dOUF5luF4xWN9ozymrt9YH/7sDC98yZQ9+3iwdnjshCwSj72/XQ UxV4u6imteabep6CAF51oZrQB8EVjSYMQvB9bnqfKGKkM5cYubgw512D/64puxMn/NLRG/DcbZyw 6lXV9B1t1mvbRkOihkomx6g3XKHONxv5orvlX9vx68aWp6fMfkbox8kjd3dW7pamtXrTH2zyTk7P plmJPFiv+KYeRR/U6lN5FIPKrkRulAb8IVWfygs3/Whm/Wi9jb6LZHBxxMHqpvDyTsrAth0B/Jig xg6qxOh/L+3EnPnW/kjRny8hvlpbMnW58aztEqQDD/rrURC41Dhh5SuLUnS06aGk0BGqEnldePyF naD1EKsSmS3/5RJ1AhtJO2hK6//u367UcovMo8kwYulHTUYRqr0ctNTunH29GHx8Bsj4wMrNpt+u brJKpLJVJRJcenLJ4i+REhnKXKTAgpbEqPCtaptQiQ1rS6bONQrfJUi9Dp40EsAZxgmrX1mUIiNO bkFUrkfQw0lB9KxMNNq74jz1cZlaahO1+dZ7t+um0i1dY/ZxcpRKI4YPP6unxV0c2Nseg42DD0rV nbeSAYJViUZlbxXVIC1NA4WcUJHIBD4zQ8Pn31jXLhXtMPIkMbpkrPk/Qu0VI2qdrF9bMu0Zo527 BKn3oEkXmOkyyCggmlcWpfC0/nJJO6WL/HdOK8f8peasmYRvye6zfQ9IsSykwe5Sm96RR3ISom+/ t9YCLbgeFPPo4y/qdXGiUOoUE8nqRKO01FSBL1ZaN6Kg+FiffFkP8rWoKs4STYt2bO9ByQ+Nlo72 ze6/xBUjIiXarS2Zep/BbJcg9Rk46RoA/YwTdr2yKO1Nnhb6aUGYFsVVJLJQuu/RHVi52ro/SEY7 aPqxn8Wjg+dmkBCV61M6ofYSGXWz8pWixH74WR2+WtUACoZn9aZf8o9nbFy3st0kghSm5OjD1Kwr 0QisXRtND3hIG2ndllSJ0bPTKzFjrl3TdHv0QlrvQ/76xNpvH9Qrs0uQeg2c9A8Anfe41KYPLEq7 we/X3YsJZ6nzETb7nWrQ2okdsXbItFvFBund9PZ890JhJf7vvnJ9zcQOLwZ71ib0p81b/Hj/kzpQ yIWMdIH9e1jnFql9O49pVoWhWxf6KO2RIiexZDWqItEPnjOGZeplbCxzj1m4KjGiH2SvzrF2Wj5c v2oy8Maakmnr6RpdkJp82FVOBcQugQqXgRXnWJSAwwel4rF7OiubZ6fQ4k+/at8ud/rCDein3tqM /O7d/sB2fP51gy1eC2L5vlD8n/eW12HVj43IzBCwwl8f+Yhb9UOjLXt5SCjeXlyL9FSh7Hknwftt WwA/rbNvijbSZyH/jCxMnGD+uiqJEX0fHJWE9t6akqlfU510Aeox8KoDpMCNjqokoJvdqvDo4AaT 8NzjM3D3X9WFGievC9PftPfBJFdH7dtqyh676W9UYfK0cny8ot62EA3xNo42epJF3k/rfPo2hpxu akdM6ekCxcvNinkTXeuNdSUKRKgqIi15hSfvEbSu5NREYnTlBUkiRmTWAJSsLZm6lPpDf7r98PcF 9nDa4Ji+MmJwmD21Y0QSdeLmWfJQoNJb9z8p6qNNf3SMB8vrgbJf/TPnVYOmIrdud8/0jMGlpdeP Pq8D/acfKqOHZYJc5qhIJx6Trlv92bm29uTLO1G+w6/kjzIxu+L8droFnhN94CWbGFF/aEAf41nW BUlqYn9h3ZYTo+yIX5NJlFQ9kAbsa2/fhlU/WG+8YJRvvHbsYP7sMDkSJQ8dm7YkjhAZvIzX4o9q Qf8piuqo4Vkg7+dmp306emwXc9oXQ/14x40dzW6enh+53UlPE3oIFSUFxJCpqu8+OaB13DRdEB8p sb/xUf+r0HvQjWcC4hTjoBNfk2FNidZUVMwbU3+SJR25VdlQZp73gXieE/pjkD/KHF9cNBp64L8V IH9zZLGVDIlCXsxfUqOviZgdnHDRB7Uod4BFGnkJ+fDzemiK9mmRWTgZcnz6pf17lc4ckYmrLzZ/ Y7grvKELYG3J1Ifoe6uPkITUekp9b7Ozv8qJPFJS5RKEetSJfv0aTJjCp82VJLQ//2JCZs5+9Fus 3btLa7CguAan5WXihsvN/4PWYsEWnfh5XaPuZd7nB+iPttmJ8iTBe+H1Sj3on9n5R5IfRTy+5lLz +84VYtQEKCc3V3qLi4VPHyEdMOCma4RA70jg2X1NIo6UKKYJuc5RkWio/uQr9lnStdQm2ogZT7hl cm1EFkO0sZQTdN9t5GFjZ6XEMYfHt6eHhN4JIySjX/1+6KMYVcYOtFcpI03Tpwgrdlr7PJEYXXtZ UosRdbOW0rby6Z+/nbZTN3ESQvY0Ot8NrzRSomiGZicydCDLLyuT7groTDVi9NQrlfofbSvbw2XZ R4AMEd54txpTHt9hXyUUlkzGDuRdQ0UaNTwTz0ztAnKlZFVSJUZkPetEg41wXGUA+9F5w+a2R7iL nXhu3qIaUOwOs5OVokTid+4Zarwv/PupCpCTVE5MIJEIvPZGFR58wvzvvcHooTv3gdlrckbewa8q xcjOvYXBbYzmfUBqetgjbdSoUpqYNX9yNpraxHgthUhwqyjRnLFheh5j81u87Z8PlevOK1u8gE8w ARcToHUzitOlKt11c0cMO1mN1wiqM4VZUTFNRyMjN4oRMZEIdKNXrTrTY2lAPrMfIpWiRNNpZidy B3PdZe2VLNBSXW+6axve+9iejY1ms+L8mEBLBChkxgXX/qYbtbR0TTzHb/lzB9AoxuykKiCpm8WI GAsNXejVK4Xs7OQ9SJE8ECRKlMz+1WFMpz3xkjmRZ7MyBSaObwearzY7kbXZ7Hersd7CgHpmt4Hz YwLRECjb7Mdjz1cgINVY4NHfE/JcMdMkJ6QsRmF6V4ocOuvVAgFXj5CMJjpdlLp29mD8mWqcpJIY Pf3aTlBUUk5MIF4CNbXWWprFU18yB//PsxXw+SQorpbZieJ0kfd18vsYT1IlRq+/Ve3aabpmPHXH 3l4BdEmUP2NOFaVuXTwYO1qNGJFVFXnr5sQEzCIg3aNHu5r8+Is7dR91tLnc7ERRWj2aiNlyjdw9 me36jNpIYmTW7I3ZzKLPT+xL93ghRQe4fc4uqPVOE6Xe+6XgySlqonok1gMZ1In8lgnEQID2pdXU Slx+nvmRVcnVkNeLqEcjpxybjr9f1yGG1oS/JfG++1KHpEnR9CZ88911lkRJhfUdrSlFY+jQr5c6 MaJFzMT5deSu54tr61wCtNVh2pNqzMJpI3c0338So9tvMN8XX+KJkf486aA0IPEEiZpntyhRBM7H 71MzMiKXIG4173TunzKuWaIQeGdxDf4xpVxJcyL9UapKjMgNWIL+EG0aIQkI8yVcyaMQfaZ2iRIF 1vv3nftEX+EI7qBpCbftwo6gWXwJEzCVAIXquO4favYqkSiF8+hysqKRkRN9UprYaem5F69NJ08N 5jtSMrGW8WZltSgdc1gaptyuRoz+99JOR7uRj7ev+H4mYCaBku8bcOmNW5TsVaJN7aEcotL3/x8K pukSXIyaur06q4NXAubv/jTzqTIhLxIlSqr3KdHDeM+taqK8PvxMhZIvlgl4OQsm4FgCFMLi+cIm /3dm7/8zPIXTd5OSqu9/UogRgFQEMryQSHdosFhTH3LVovTFynplYnT/Yzuw6P1aU3lwZkwgWQiQ B+9HnlWzgdYQueUr6pR8/5NFjOhZDABtvBCIz1e9i55qlaJkeHUwG8fkqeVY9im7AjKbK+eXXATI EzptoA0EpOk+JEmUDGEyk+qbC2rw2PPJs8fQ70EmBehL6DWk5g+IKlFqXo4ZnynC66df2R/N0oy2 cB5MwAkE6A88xVdS9QPSrDaSGJGAJlMSgUAaCVLSjJCMznWDKN04eRu++a7BqDK/MgEmYBIBMpuu b5BQ4dXBjComoxgRN02KtmRll24GRLflQaLk1OHw9XewGLnteeL6uosAbZ+g/XxOS8kqRkY/0Agp aRMtGHo9wJUKwkzECvWKm7diza+Nsd7O9zEBJhAhAdrP1+iTuGSs+a6GIqzCHpeRk+Rkm6YLBiAR aJPUgkQwCt+q1pnYLUr0ML65sBrr1vuC+4jfMwEmoJDAK7Or0NAgbf9RSt9/w3xcYXMdnbUAPCRI zvh5YCMqu0WJHsbpc6uweYvfRgpcNBNITgL0/acwFuG8L6gkw2K0my4JEq0jJX2yS5ToYXx5dhW2 lbMYJf1DyABsI0DT936/NH3zfGsNYjHak1DST9kF47BalOhh5MB6wT3A75mAfQTI0Ims71TELgrV Khajvanw6KgZExIl8hmnOrEYqSbM+TOB6AksKK7FPx9S4yk8uDb0/f/fy+r/zgSX6Yb3JEgujA+p Fq1qUWIxUtt/nDsTiIfAex/XKRUl4/tfV58osbrjob3nvSRITZ4H9zye1J+yMgVy9lU3m0luRkYO yUxqxtx4JuBkAocMUOcvgL7/+WeYH2rdyTwjrZu6v7qR1sBh15EYTRzfTolvquCmUuRJEWR2Hnwu Gd5npFPrOTEB5xG47rL2yr//FBKdEsc2293/EvCzIO3mAavEyCiS9j4JAcyc17QXyjieDK8eXr1M hm52VRszMwQun6D+x6gBhUXJINH0KqBV0Z8FdiUNWC5GRldccX47x/rUMurIr0wg0Ql0bK9ZKkYG TxKliwuSfiuogQM0Qkp6d9JWj4x20f/9DbkuSfEKHr43BxPmcy0vCIehw6eiIdCtiwdjR7dRPk3X Up14pNREJiBkJQlScvk4b/ZU0DDdijWjZsXu9ZEeSq8XePpVtjHZC06IA7SJkRMTiJdA7/1S8OSU zvFmE/f99P0nv3rkyihZk9S0eg0yuUdIVs4Zt/agjTuzDSZO4OF7a5z4PBMwg0D/fs4QI6MtNFPi 1JAYRh1Vvnr8qNEgkncNyQprmmg7kESJLPA4MQG7CGRkJL4F5CEDUvGfu+0fGTXv42QWJQ2o0gSQ lNuFnShGxsNJ0SztcvRo1IFf3UugPs64jhr9VUjgdPxR6Zh2xz6ObWGyilIDtNqkXENyshgZ35Kz R2aB/jAkc3wUgwW/RkegsZHX11oiNuzkDNzy5w4tnXbMcRIlSkm1ppRVvUOTkOodNzmmmwFVYvT6 73GVzGzqmSMy9fqmpyX2L1YzmXFeTKAlAqOHZyoTI4pAa3YiUco/I8vsbJ2aX13x873qvIDY4dQa ml0vVWJ0273b8elXTdbzNN1mZiI3I5QK36pC6WYOUWEmW84reQgUjMoC7flTkcgZK/m/o2SMbMwq xwgcakQiMCtfB+aj65AmZHIIkhVi9MRLO6FipESi9OIjXXFQ3xQHPkdcJSbgbAIXntvGEjGi6bWX Z5lvtk2ilAQjJX2mToOQCT9CskKMjK8kiZIqV0CP/qszjj5MndNHow38ygQShQBto7gwX81Witsf 2D0yMniRbzoWJYNGNK9NAyNNAluiuc1t11opRgabJ1/eielvmv9LifK/99ZOGHJChlEUvzIBJtAC Ado+QdsoVKS/3bMdy1eE9rqmUpTMXhJQwSa2POVmuk8LaFpZbBk4/y47xMigQh4XVPxSovxvu7YD Rg3j8BUGa35lAs0JXHNpe6j64339Hdvw+dfhPa6pEiUSWTLOSMC0ldrkFVLobxKtgXaKkcGSHkpy B2L2Qiflf93E9qANjDPnJp+ncIMvvzKB5gQ6ddBwwTltlfmlu/KWrfh5XWPzYkN+pu8/JcNXXciL Yjh47WXt9bso5HrCJCFLqS3erBp/WU2CzQA5QYyMB4UWOn0+4PLzzJ/HvuK8dsjK0KDC5NSoP78y AbcQ+EOfFDx2jxrvCxTldfa71Vi/0RcVDhIl8lFp9tRhoomSDDQtHWnz5uWQzCaM1DpJjIwnd8bc KvzvJTUOMcj31VUXqjFnNerPr0zA6QTI+4JKMZo+typqMTKY0fS9ijVlEqVEmb4T0DYRLyNM2gYD nptfad7Y2LdjZjuC9xnFmi/tI3jkGTWO1c85PQvU9pQU3kAba//wfe4lcFpeJu66uaOSBtDI6OnX dmLzlvj2AKoUpTOGun9NSRMB3ZZBjxgrpVgvhDxQSY9alCn9QSbPBmanux4q37XpNd68ac63vkHi 5qvMd11CbScXZLPnV2N9aXTTCvG2i+9nAnYRoDhGKqbDqT1vLqjBo89VQJrkickILWP29N31l7fX 16oXFNfa1Q1xlys0/EqZ6CMkoQXWx52jjRmoEiPagf3+7zuwzWoePTQkcioSjQ6fe6gLDh/Ee5VU 8OU8nUWAjIVUidGc+dW6H0mzxMggp2qkRD9yR+S61hjA37ApRx8hNU3ZSbhWkFSKkeEOxHiYzHol kbv5n9vNym6vfKbc3glDT3Ltw7lXe/gAE2hO4OqL2imLHUTeVh57Xs2aL7WDREmFRxcXi1JpcbHQ p3V0QZJC6MOl5p3u9M9uFCOD6Zff1uNPt2zFO4vV2JP87S8dcM5p5vrVM+ruhFdaxD7qEB4JOqEv rKxD504e3eHwGEXP9qtzqkDeVlQnVW7GXClKQQMifQ0JkL8C7loQd7MYGQ/7T+sa8eLrVfD7ocQY 46qL2qFDew3PvGa+J2KjDXa9nnB0Ouh/8Ue1mLuoBt+sijMIkF0NMbFcjwf6jxBVTkRNrGpMWR06 IBVTFcYxou0TVoZ7MITP7A28JEqNPmDJMpesKQUNiHRB8gY8P/pFIKaHxI6bEkGMDG5bt/vx3xeb fpGpsBAcf1YbZKQLPPqc+l99RpusfM09PgP0f+F7tZi3sBrf/RTZpkUr66i6LCEAGjEksvk/rY/Q H1pVibZl2OFRW5Uo3XZNB/h9cpcXclXczMlXrjHy8dCbIwZOqawVlbcCQv9snHTiayKJkcE3EAA+ +bIebdto6N8v1Ths2utBfVPRsb0Hm7b4UbHTGT88AhIgsTQr9TkgBWT+26mjB1u2BVBe4Yx2mtW+ UPmQEJ11ahZ0p7uHmjd9+eaCauxwyHNC7R53Vhtcc0mTd4JQHOI99u+nKvDGu2qmziOp2+ff1CMz Q8OAA8397p9yXAZ+2eDT/0dSD7uuEZDPrymZ9iWVrwvQqlV3yt4DbzofgHPj+gL6XhsVpt3B8Uzs 6hQq97Ov1DyYlDftYj9zRBbW/OrDr1HuNlfBhKYUzj2jjel7pw7snaL7+WvXVmsS4MrEFKbTh2bi v/d2xjGHmydERj/TNHJdvUm2zkamMb6SN5ILzjXvh0vzakyeWo6iD+yf2iJRUvGD1BWipImpa7+d qtsx7BoR9Rk4aSSAfs07zCmfE3FkFIotPZgej8Ah/c39tWSURdNbldUSqx0wtdW/Xwp65vy+jGlU 0KRXGhWSAGdladj0mx87qxJDmIafkoHrr1DnXLeqOoDnZ6rxVB9N12ZmCPzpwvagTd+q0rW3b8OK b8I7SVVVdqh86QdpMoqSP+C9bd2qKfpDt0uQeg2adAyAY0OBsvtYsoiRwfmrkgb9F+qRiqzIjjks TRc9KsfOJCFw8rHpSqswoF+qPq2Vnq5hQ5kP1TXO+OUfbaPph8S1l7bX14q6dNr1tY02m1avX/5F vel771ottNkFBx+UiosK2mLkYPM3ulNR5H2BRkY0neW0pFKUflzr078DDmvzzqWF3f5u1GnXk917 0KT9AZxhnHDKa7KJkcG95IdG/LY1gOOPVvMHm0ZgNK21YmW9aTvRjbpH+kpThxeca77T2VDlD/xD qm6BlpoisHa9zzFTUqHqGnzsxGPS8eeLm+L67Ntl19c1+BJT39Pifumm+NzkxFMhcoMzeVJH9NpP TXRk8r7wyLMVqKl17g8TVaJEcdRoZmSjjf0b4tn4Zm3J1KeM47ue8D4DJmVA4FLjhBNek1WMDPZk Fk4PUJ6iTa40rUW/trduD2BbufVTWrQLnkYsVkbBHXRQKgpGNa1d/bCmEY0ONcr74xFpuPrC9nro gpx91UxrGs+Z8frdj436pk3js9Wv5HlBpck6bUb97wvusDZVJUr0t8RhorR4bcnUN4xnbZcg9Rt4 fVVAaLcYJ+x+/csl7fSpFrPr4RQDhkjbRb9mPvi0DgJCN0yI9L5Ir+vbKwW0QL55qx8/r7N+CoP+ CLZv58FBfdX8Im6JA00LkZUfrddRHWgvmBPSUYem4U8XtsPFBW3RI9saITLa/ez0Sqz5xfpngJwC k+cF+qGgKlGwzKddth+PREnFd8NJoiQhX1tbMu1Do993CdLPqx6q7T3wxisBYc0cilGDEK80RXH2 SPMXM+9/bAeWfBg67HCIajjm0I6KAFaubkCbTE2JKFFDaZMppa9t2GBK0Tc7tPMoa1u4jqSpSwrh QRM4q35oBJng25EOGZCKyye0xcQJ7ZQZeoRr18x51SicZ32wx4EHpuLisW11k/1w9Yvn3JMv78Qr c+w31IilDZ9+mdiipEnt32tWTf3RYLNLkOhArwE3jxQCvY2TdryqEqMpj+/QN0/a0SYzyiQzaZV7 laiOhw5M09eVSJSsHDHQ1N2n9GuwrfUjJaNvDhuYpk+PEWea0rBKmPr3TcGl49rpI4QDelo7SjTa To5En/h9c7ZxzIrXkUOawkb03l9du+/9zw68VWTfHiMzOKoUpa9WNcQdWiOeNgrg1jWrpu6Ky7OH IPUeNOkQAMfHU0A896oUIze7Zg9mSsN4r1fgYEVm4bSuRCOGb75r0PfxBJet+j198eoaJFRZF0ZS /yMObhKm2jqJ739uVGbw0Wf/FN2SjEIH0KZeuxK5ynnyFetdS106ri2uPF9tYMnrbt+m/4izi62Z 5aoSpRG5mfji2wb8ttWWOettiwtzCjVkfQAAIABJREFU/i+Y056CNHBSVwBjgi+w6j2LUeSkv/y2 AVWKjQGGn5Jpy36lku8b8frb1boQqBLdSEjTWs4F57RFTZ3U15giuSeSa2jf1QXntMHNV3cAbeK1 Ky1fUYfHX9iJtxU5922pXZ06aLj8vHYw239bcHlk1v23e7Y70cQ5uJpRv1clSqcOtkeUpBTL166a +mIwiD0Eqc+gSWRz9OfgC6x4z2IUPWVaiCevC7Q/RVWi/UoZGZrlmwdp2oxEd96iGgT8AFnG2ZVI mC48ty12VjWNmGKtR86+Hpx/Tlvcek0HJe6hIq0X+fp7bnqlbk1ntfkvmbBTmHHyGqIqGaEjGhqc a9YdT9tJlMgNmNkM7RAlTcjZa0qmLQrmsYcgHTngwfJaUXUTAHVPTHDppH6KDBhozShRpumaIdv1 kfbxqLTAo4Jo0blrZ3tMw8l9DU0nLHivVl/Tor1EdiVy0UPCtG1HAD+uidxWvMs+TUL09+s7mu6r LFoW0/5XgUeeqcDPNljSnX9OG9D0pMr0YmElnplu/fSjyjaFypvWkhNBlITEY2tWTVsZ3Ma9Yk7k FWxcDghLPDawGAV3RezvyckmsSRHmyoT/TGjMOx2pe7ZXowelqnUnUwkbaNRBnkWX/R+bYtrTB3b a/pGXHIManci56E0NWd29NNI2kWbry/KbwsVPiiDy3/46Qp9RB18LNHfX3dZeyVha668ZSt+Xhf5 j65YOQuBAUUzcr4Lvn9vQRpb+m9IXBd8kYr3LEbmU504vq3uGdn8nHfnSFMihsv83Uetfdeze5Mw qdgaEG1LSJRopEpGEB4N6NhBQ98DUkBTfXYnCjlCnrvtECJqO7mF+scNHZVj+L/7tieM8UK0sFSJ 0hU3b8WaX5WKUsVJA7I7TZ68Z9yjEIK0cRykeC1aMNFcz2IUDa3orqXF4j9doNZ6iUK70wjBbl94 ZKlGMaTI3Qyn3QTI/Y9hGLL7qLXvaGMvTdOpTGS88PLsKmwrt8VCTGXTosrbpaK0aPHMnOHNG7rH GhKd7HPQLTXQpLIREotR8y4w9zNt7ly3wQdyO68qHdDDC7LCa2gEvv3ePgetFPPo4y/q9bAdZApv p/m0KtbR5EuRgW+9dzu+/V7pL9uwVdqvu1f3MpE/Su30MW3kJTdANDJN9kTfQRWb5unH3rJP69XE FhPylbUl04qb991eIyS6YEhB6W8C6NL84ng/sxjFSzDy+7t38+Lc07OUzDEH14KcVU5/swpbttn/ K5W8HdAak0rLw+C2O+X9C4WVeO2Navh89v5xpj9g9GtddbIruqvqdsWTf1amwMTx7ZR835VM3wlt 1OIZ3d5q3ua9Rkh0Qe+BN51EMd2aXxzPZxajeOhFf29lVUCfV8/KND8SZXBtyAcdCd9v2wIgZ7B2 ps1b/Hj/kzqQp3QK206/1hM5kX82GhF9sbLBMs8SoXimpQndKSo5R1Wd/jGlPOGtZ2NhSE6CVY6U Fr5Xq+99jKVuoe5Jlbj+p1VT97KQakGQbuwJiGGhMorlGDlOHHOa+UP4ZDDtjoV38D3kJ67BB5AH ApXp+KPS9TDMX5XUg8KT25nKNvtRvLwOZA1Hgd5UBQG0q43kXeH2B7brU5VWungK1V4ajf7v/s6g uFMqE60X3fVQue7WSWU5bs5bpSileIW+FGBSPLHvFhbmTAnFOqQg9Rn0Vz8gJ4a6IdpjtMCuIuoj mbLOX2J/6OFoedhx/berG/S9J4MVbqKldg04MFXf/PnLRp8jgp9RXJ+lH9Xhp3U+PRJnTjd3j5im v1GFO6eVY/mKekeEzZg4oa3ug0/1M134VjUee36na4MrquYTnD+JEq0jZ2WY64iZNuKmmidKs9eW TN1ruo7aEVKQ9usyZZOWVTUJQFw/e0iMVLgIof0wtIufU+QE1pf68M6SWqSlqgljEVyTU47NAE3j kLcFJyRqe9EHtfi11I8O7TRYEejOzHbTAv49/9mBDz6pc0RgwWOPSMNVF7XHqbnqrRtpi8FLr7vT U7eZz0A0eTU0Sn00QwJipkcHs0RJSPlQ8w2xRvtCCtK6dXcG+gyYdAoE+hgXRvuqUozs3JwZLQcn XU9RMmmXN8WfoXhAKtOgP6Tqng0oTLRTQkWvW+/TvT5s2uJHp44ePTihSgbx5j3r7Wrc/98KFH9U 65jRAe11u3Zie0tiNd3yr+1Y/AHPgsTyHNHUGlnbOlGU/H55/brvpoWMlBhSkAjAAQMn7S8EhsQC g8UoFmrW3UMjF4oSe9xRasKjB7eEzM8zMzR8/Z29C+/BdSLXOfOX1FjGILjsSN6/8W41pjxeoY/q yDjFCYnWCGktmLxDq07U/kl3bQeNbDnFTkC1KJERRdQRlwXWLH29+90ttapFQeo98AYfhLispRtb Os5i1BIZZx3/cW2jbpGmKhJtcGv1taUxbWyLShtcl+D3xOClWbSxMoDjjlQvzsFlh3pPcXumPlGh W5FV7HSGENH+LgoaSBGcaSuB6vTcjErQfiq7jTVUt9Oq/FWKEu19ilqUBApbWj8iJi0K0lEDp5bV iqprAUT8TWUxsuoxM6ecHTubTMPT0zRY4biUotKSb7MNZT49tIU5rYg/F3KWSsK0s1KCnKhancik lox0SJBos69T0ojcDDx+b2dLng1qM5l0v2NxOAynsFZZDxKlX0t9GGNyFG5aU4pWlATEA2tKppa0 1N6QG2ONi/MKSmcDONv4HO6VxSgcHeefOy0vEzdeoX5To0Hi6dcqQVZjTkvkqJb2VV2p2P0StZvW huYuqsE3NoSND8edfAWeOTxTubNeow40RUcjI/rDyUkdgd77peDJKZ1NL4BM8p9+LSIryIBX+rss KOy5vaVKtDhCohv6DLipEwROb+lm4ziLkUHCva80fbXkwzp4NHMtc1oiQvuiaCqPDC2ctlZAZrOv zKlCQ4Oa/VsffV6nmzFPf7Pa1vDRofrmgnPb4I4bO4IiB1uR6IfJs9Mro1+LsKJyCVYGjb7JFRB5 1DAzGSOllasbQLHMwqTPFxX2eCTMeYQdIeXmr+/rEZ4fw2XAYhSOjjvP0Y57CmNuVZozv1p3Bkqe FpyWPB6A9m+dPjQzbstEmpKjX5M//2KvR4tQjIeckKH/oVJtfWmU7RQHvUZ9kulV5Ujp4WcqwqG8 Z/HMPUOWN784rCDRxXkFpd8DOLD5jfSZxSgUlcQ4RtE9J09SHzogmBYFWCPvzQHnLKMEVw8d2ms4 8uA0HH5wKg7okYIe2R60ydL2uMb4QKbltFb2/U+NesTdVT80wOc8vdVDZYwengmasrUq0b6qJ18O afVrVRWSvhw7RElInFRUmLMsHPxIBOlBALRJdo/EYrQHjoT8QPuVaDf+OQrcPoUDZncgwHB1a36O XBNlpGtITxfw+yRq6qTugbqx0dnrISSk487MwrgzrRsJE7up/yMPK7ypvflzZMfnfr1S8Ph9ataU QoyUtneS2V0LC0XYn2Vh15AIUu+BN9H8wkXBwFiMgmkk7nsaqZAvvPKdARx7RMTGlnED+eMR6Ti4 f6rulYCC3zk50Zw5hUCg/UJVNRL1DdKxIzziqGnAhLPb4P7/64RBijdHB/cbGS7c8eAOfPOdM7x3 BNctWd9v3xHAF9824NTB5o6OaU2JQqzTJvygNOutwnazgj6HfNuqIHU64cGN6XW7zb9ZjEJyTOiD P/zciIXv18LjESDv3lak7K5e5B6Xgb69UnQT8dLNYX9YWVElV5dB1oMU4v4/d3fG4YOsNW0n9z/P z6zi2EUOfIJ+2+q3RJSkEHevLZm6qjUErU7ZUQZDC0pnSiCfpm9UDPEp0Nbsd6pbqyufdwABKyLS hmqmU02kQ9XVScdIiGh96IbLrTPpN9pPBhxvLqwGuWzi5GwCNFr+9537mF5Jipf2n2d3NAqZ0aWo sFNYiwcqPCJByhu7cdzE8e1eUyFGHGzL9GdAeYb79/DirBHqg/+FasiyT+t0x7orvtljOiDUpUl9 jIRoxCmZuOkq64WIwD/1SiVmzHXePrOkfihaabwqUVr6Ue36ISdk7tdK8frpiASp9LfGU7O7eOdH kmE017AYRUPLeddSWJGrLmxnS8U+/6ZeN6H+8LM6W8p3aqFkpj5qWJbu6seOOvKoyA7q5pV5+KBU TLnd/JESgH5CiJ9aq2lEgiSlfA3AuNYyi+Y8i1E0tJx7bbcuHj3ECK1P2JHW/NqoC9O7xbVwumWb Sj5tszR9H9Gl49RHbW2pHbRW9PpbPPXeEh+3HD/msDTcc2sns6vbTQixubVMIxWkCwG80FpmkZ5n MYqUlHuus9r1UCgy09+swluLakB7gJIl0X4S2nk/api5llLR8CMLOgqi58SNzdG0g6/dTcBkUSoW QgzenXvL7yIVpA4AylvOJvIzLEaRs3LblakpAhcXtEXBaHtGSwavT76ox4L3an6PrOrs/UBGnaN5 peCHucel47QhmZY5Pm2pfryvqCUy7j9uoihdI4R4NBIiEQkSZSSlfBPA6EgybekaFqOWyCTW8aMO SdN/tZN3b7sTWfkUfVCD1T81QrpYm8hI4ZD+qcg7McNSrwot9R+NRskHnVO9arRUbz4eHQGTRClb CLEpkpKjEaSxAKZHkmmoa1iMQlFJ7GMFo7Jwxfn2GD2EIkvrGxQGfNWPDa4QJxKhAf1SccIx6SCW TkhLljV5KP92NW9wdUJ/WFEH+mF5500xuxFbJIQYHmk9oxEkmqSOacWSxSjS7ki869q20XDe2W10 wwcnta7og1p8+mU9Vqysh1OC4RGfrEyBwwam4Y9HpOlTck5i9uhzO0HrRZySj8Apx6bj9htiEqVL hBDPR0osYkGiDKWULwK4INLM6ToWo2hoJe61hw5IxejhWTjlOPun8ZpTXrfBh5XfNej/v1/TiNJN PstGUB3bayBXKxQgkTwoWOUJozmDcJ9fnVOFFwo5ims4RslwLkZRai+EiNiTbrSCdCqAiPcjsRgl w2MaXRuHnZyBW/5MNjLOTuRzjTwMbNzkQ+kmP0o3+0ARdmMZTZGT2k4dNHTu6EGPHA96ZnuxXw+v 7mm7a+dWvXfZBmreoqZwGWRaz4kJEIEoRalQCFEQDbloBYm+PRH5AWExiqYbkutaWhspGNUGl59n 356ZWIn7/UBFZUDf80SOVBt9TZYSXo8Atcv4nJmu6Y5MMzMFaI+QmxJZKc5dVA165cQEmhOIQpRO F0K80/z+cJ+jEiTKSEp5L4C/hcuUxSgcHT5nEKCRw4Sz2oCilHJyBoEHn6jAu0s5PIQzesO5tYhw psMrRPhwE81bGIsg/QHA6uYZGZ/Zh5VBgl8jJdC+rYb8M7Iw7iwWpkiZmX0dGSy8uaDasrUzs+vP +VlPYERuBm6+qsXp9/uEELdGW6uoBYkKkFJ+AODE5oU9N6MSr8xmh4rNufDnyAjQesroYZksTJHh MuUqms14/W0WIlNgJmEmYUTpICEERRuPKsUqSJcCeCa4JBajYBr8Ph4CZHlGUWp5xBQPxfD3khDN nl8NWhPjxATiIRBClJYJIU6KJc9YBYl26e0aCrEYxYKe72mNAIXZPuvUTN0dUWvX8vnICDz8dAXe XlzDHhYiw8VXRUigmSidJ4R4NcJb97gsJkGiHKSU1wJ4+LHnd2LOfN4stwdV/mAqAa9X4PS8DFxz qT2xfUxtjA2Zfb2qAXMXVuP9j+t4jcgG/slSZJssbdurj+57aVaWmBtrm2MWJCrw7oe3n7j0wzpa T+LEBJQTILPqIw9Jw+jhmTj+KOdtsFUOIMoCFhTXYt6iat2PX5S38uVMIGoCAuL+opnZYS2wW8s0 LkGizPPyS5dCILe1gvg8EzCTABlAnDE0ExPOZsu85lzJ6ek7S2qwoyLQ/BR/ZgKqCASE39OvaNa+ a+IpwARBKjsXQhbGUwm+lwnESsDrAY45PB3DT8nAicck76jpvY/r9FhQX5XU87RcrA8T3xczAQEx t2hm9pkxZ/D7jXELUm6u9Hq6lv0MIKKY6fFWmO9nAi0RoP1Mg0/I0GMFDTootaXLEub4N6sasPjD Wn1tqLKKR0MJ07EubIgATi2ambMg3qrHLUhUgSEFZTcJyCnxVobvZwJmEdinowcn/TEdJx+brscR Mitfu/MhH3sffV6H95bXYcs2ttm2uz+4fJ3AqsUzswcBIu6IY6YIUu5Zazt4UtPWA+AJfX5CHUeA zMePPCQVfzw8HUcflgba5+SmRNNxH6+ow8df1INHQm7queSoqwCuKJqZ85QZrTVFkKgiQwrKHhGQ 15hRKc6DCagk0LO7F4f2T8XB/VP10VOXfZzjcZsct67+sRG0FvTFtw1Y84u7I92q7EfO2xEEttZI /37LC3vWmlEb0wRpeP6mXn4R+AGA14yKcR5MwCoC7dtpOLB3CvockIJ+vVLQM8eD7vt6kZZm2tcj ZFPKNvvx60Yfftnow/c/NYBiMW36jafhQsLig84kIMUdiwuz7zKrcqZ+4/IKSl8GcJ5ZleN8mICd BMhIonMnD9q1FaBpP4p+2yZT6EKV4hWgDbspv//8CgRoszjg+X2wVVsndW8IdQ0S9fUSOysD+nTb zqqAbo69dbsfPtYeO7uXy46fQJVX+vdfUNhze/xZNeVg7mjG438Afs8EAKYKnVmN5XyYQDQEaPqM /nNiAkxgbwISeMpMMaISTF3dXfxaz29EFBFl924iH2ECTIAJMAEXEGjwavIhs+tpqiBR5aSG+8yu JOfHBJgAE2ACziEgIF5ZOL07WVabmkwXpMXTcz4A5BJTa8mZMQEmwASYgFMI+ODX7lZRGdMFiSop Ie5UUVnOkwkwASbABGwmIOTL8fqsa6kFSgRpycyc9wEsbalQPs4EmAATYAKuJODzBwL/UlVzJYJE lZXAZFWV5nyZABNgAkzAFgKvFBf2/ElVycoE6fdR0iJVFed8mQATYAJMwFICDR6pKV2OUSZIhEnT cFvTYMlSaFwYE2ACTIAJmE5APrWwsNta07MNylCpIC2anvM5gFlB5fFbJsAEmAATcB+BGq8vVdna kYFDqSBRIR4/bgfgMwrkVybABJgAE3AXASnwyILZXcpU11q5IC2clbMaAi+obgjnzwSYABNgAkoI bGvwND6gJOdmmSoXJCrP25hCo6TqZmXzRybABJgAE3A4ASlw97JX9y+3opqWCBIN9SQER5S1oke5 DCbABJiAWQQEfqxon/2YWdm1lo8lgkSVyKqlEOeitLUK8XkmwASYABNwBgEZwC0rnhSNVtXGMkGa Ny+nRorA361qGJfDBJgAE2ACsROQwHtLCnPmxJ5D9HdaJkhUtZP757wACTIF58QEmAATYALOJeD3 yMD1VlfP8kB6eWPLjoaUH5sdi8lqcFweE2ACTCBRCUghH18yo/vVVrfP0hESNW7xjOzPADxrdUO5 PCbABJgAE2idgAS2BOobyMuO5clyQaIWpkpBjd1heWu5QCbABJgAEwhPQOC24jd62fL32RZBml+Y vQVC/F94KnyWCTABJsAErCQgpPzk5P7Zts1g2SJIBPik/t2eAPS1JCt5c1lMgAkwASYQmoDPr8kr J08WgdCn1R+13KghuEmDx244VJMaWd15g4/zeybABJgAE7CYgJBTFs/o/leLS92jONtGSFSLpTN6 fA2IaXvUiD8wASbABJiA1QTWeVI9SmMdRdIgWwWJKphZKwmC0hgbkYDga5gAE2ACyUpACnH1wpe6 2e5v1HZBIg8Omha4nAP5JetXgdvNBJiAzQReXjIje77NddCLt12QqBaLpvdYDOBJJwDhOjABJsAE kojAJq/0X+eU9jpCkAhGXV3KzQB+dQoYrgcTYAJMINEJSImrFxT23O6UdjpGkD6c26VSCslTd055 MrgeTIAJJDgBMd1q56mtAXWMIFFFl8zovhCQj7dWaT7PBJgAE2AC8RAQpV7p+3M8Oai411GCRA30 pHnIDv4HFY3lPJkAE2ACTABSQF7qpKk6o08cJ0hkehjQtAsB+IxK8isTYAJMgAmYRUD+t2hmzgKz cjMzH8cJEjVu6fRun0DIe8xsKOfFBJgAE0h2AlJgdWatsNUbQ7g+cKQgUYX9m3P+CYmPwlWezzEB JsAEmEDEBBoAnEd7PyO+w+ILHStIxcXCJ/2YIIByi5lwcUyACTCBxCMg8dclM3K+cHLDHCtIBG3J 7JxfpBRXOBkg140JMAEm4AICby8uzH7E6fV0tCARvMWF2a8L4Amng+T6MQEmwAScSUCU+j24GBDS mfXbXSvHCxJVtVr6b4TEl7urze+YABNgAkwgAgI+aHJc8Ws5WyO41vZLXCFIywt71krNkw+gwnZi XAEmwASYgEsISIhbF0/P+cAl1YUrBIlgLpmx789CyIvYK7hbHi2uJxNgAjYTmLNkZrepNtchquJd I0jUqqIZ3d+EkA9G1UK+mAkwASaQbAQEfhQy/RI3rBsFd42rBIkq3imQc6uUgsJVcGICTIAJMIG9 CVSLgHZOUWEn1y1xuE6QCguFPw0YD4Ff9u4HPsIEmAATSGoCUgAXFRV2W+lGCq4TJII8vzB7SwCB MwE4dsexGx8GrjMTYALuJiCBfxXNzJnl1la4UpAI9tIZPb6WQlzMRg5uffS43kyACZhKQMq3Th6Q fYepeVqcmbC4PNOLyysoux2Qd5meMWfIBJgAE3APgZVCpp/kxnWjYMSuFyRAiryCspfIaWBww/g9 E2ACTCAZCEhgS8AXOKJ4do8Nbm+va6fsdoMXspMsvxSQH+8+xu+YABNgAklBoEZq2qhEECPqrQQY ITU9dCPzy7o0CnwkIfsmxWPIjWQCTCDZCUgJed6Smd1fSxQQCTBCauoKsrzzSd9IGr4mSudwO5gA E2ACLREQErclkhhROxNmhGR02uBxm/6oBQJLAGQax/iVCTABJpBIBKSQjy+Z0f3qRGoTtSVhRkhG x1D484CU4ynorHGMX5kAE2ACCUTglX0COdckUHt2NSXhBIlatrSw+1wJ/IX3KO3qZ37DBJhAQhCQ S8iIizzWJERzmjUi4absgts3NL/0b1Lg3uBj/J4JMAEm4FICnwqZPtzte43CsU/IEZLR4KLCnPsE xP3GZ35lAkyACbiUwKpUKc5IZDGifknoEVLTg6dvnH0cwJUufRC52kyACSQzAYFf/I2BExNlr1G4 rkzoEVJTw2njbPafAbwSDgSfYwJMgAk4kMAGf8A/NBnEiNgnwQip6RHLz5eebaL0VQFR4MCHjqvE BJgAE2hGQJZByNzFM3r80OxEwn5MGkGiHjzyCpnSvrxsuhAYk7A9yg1jAkzA9QRog78mcErRjJzv XN+YKBqQBFN2u2mseFI07oPy8QJi7u6j/I4JMAEm4BwCuhhJLS/ZxIh6IKkEiRpcWDiwoaPcni8l ZjvnEeSaMAEmwAR0ApulRwx2a8TXePswqabsgmHl5kqvZ99NL0PKscHH+T0TYAJMwB4CotTjl3kL Z+Wstqd8+0tNuhGSgby4WPg6BbpRDKWXjWP8ygSYABOwicB6v/SdksxiRNyTVpCo8eR+o5PMpjDo /7PpIeRimQATSHICAuInvyZOLi7s+VOSo0ges+/wHS3FkLFl9wiJv4W/js8yASbABEwkIMU3fnhG FBd23WRirq7NKqlHSLt7TcglM3JuFVL8lR2y7qbC75gAE1BIQOKj+pSGXBaj3YyT1qhhN4I93w0Z WzpRSDwBwLPnGf7EBJgAEzCNwLuZtThn3rycGtNyTICMWJBCdOKQgo2jBASFBc4KcZoPMQEmwARi JiAgnivv0O1K2hcZcyYJeiMLUgsdS5FnRSAwTwBdWriEDzMBJsAEoiQg/7l4Zs4dgJBR3pgUl7Mg henm3Pz1fb3CO19C9g1zGZ9iAkyACbRGwC+Aq4pm5jzV2oXJfJ4FqZXezx1f2tnjxxwAJ7ZyKZ9m AkyACYQiUCGAsUUzcxaEOsnHdhNgK7vdLEK+K34tZ2tqZfVQ3kAbEg8fZAJMIDyBtQGPOIHFKDwk 4yyPkAwSEbzmFZTeCuBfyRS2IwIsfAkTYAIhCYjlnkZx1sI53X4LeZoP7kWABWkvJOEPDMkvPVsI vAigTfgr+SwTYAJJS0DI51N31vxp/vx+9UnLIIaGsyDFAG342E2DAlLOYWOHGODxLUwgsQn4hJA3 Fs3o/p/Ebqaa1rEgxcj1xAm/dEzzpbwK4NQYs+DbmAATSCACehwjTRQUTc8uTqBmWdoUNmqIEfey V/cv7ySzzwBwL7sbihEi38YEEoWAxOfw4WgWo/g6lEdI8fHT7x6aX3a6FPIFAPuYkB1nwQSYgIsI CCEeTdlZdROvF8XfaSxI8TPUcxiev6mXH4GZEDjKpCw5GybABJxNoAoCf1o8I+cVZ1fTPbXjKTuT +mphYbe1qVXVJ9KvJZ7CMwkqZ8MEnEtgpcePo1mMzO0gHiGZy1PPbejYjWdKKZ7hKTwFcDlLJmAz ASnk44GMhhuLn+9VZ3NVEq54FiRFXZo7ZkMPzau9LIBTFBXB2TIBJmAtge1SYuKSwhxyJcZJAQGe slMAlbIsnt1jwz4yOw8Q/wAku5lXxJmzZQIWEVgqJA5nMVJLm0dIavnquQ8bV3pUIACywhtgQXFc BBNgAuYRqBPAbScOyH548mQRMC9bzikUARakUFQUHDsuf31Gpua5FxLXsi88BYA5SyZgNgGJLwFx /uLC7FVmZ835hSbAghSai7KjeQVlgwXw5P+3d26xVVRRGP7/PbW2oqYgSIGC8a6QoPLgJYKppYL1 Fi+p0AcUrw/6oDHxHhU1XuOLiYmJqVGjRvAIGjERQ9GCGNEgmGgUlYhSORRRqQhKy5lZZho1EUs9 9zMz5386ObPXWnutb03yZ86Zvbe2HSoZYgUWgUIJDBj48K8NjQ/rVNdCUebmL0HKjVdRrAeflliz ALBbANQUJaiCiIAIFEzAgA/DPS8CAAAGbElEQVRovF5PRQWjzCuABCkvbMVxap275RQL2AlgWnEi KooIiECeBHYCvGvG5Man9V9RngSL4CZBKgLEQkI0N1uNO7z3ZsIeAFBfSCz5ioAI5EOAS/2Mf0P4 Zmw+3vIpHgEJUvFYFhSp9bJtRwUueIa0mQUFkrMIiEC2BLbB7KYVqQmLsnWQXWkJSJBKyzfH6MaW Oen5ND4OYHSOzjIXARHIjoABfK7GMre+k5r4S3YusioHAQlSOSjnOMfs9p5RGboHAV6vlx5yhCdz ERiewDrS3di1qHHN8GYarQQBCVIlqGc55+BLDz6fBDEjSxeZiYAIDE3gJxrunj5lXKdeWhgaUBSu SpCi0IVhczC2XJ6eS/BRAJOGNdWgCIjAvgQGQDzl9/c/2P3GkX37Dup7tAhIkKLVj/1mE65dGgHv JiNuB9CwX0MNiIAI/E3gdd/827pTEzf+fUGf0SYgQYp2f/6TXXNHerTn2/0ArgN4wH8MdEEEqpwA zT6C5+7QceLxuxEkSPHr2WDGze09x3j0FgDoAKBd22PaR6VdVAJfkLina9G41wFaUSMrWFkISJDK grl0k8zs6JmKjHsI5AWlm0WRRSDCBIjvYbxvlDW+lErRj3CmSu1/CEiQ/gdQXIZb5/SebkFwL4i2 uOSsPEWgQAKbaXx0JH55NpWaMlBgLLlHgIAEKQJNKGYKLe1bTyXtXgDnFzOuYolAhAh8R8MjI7Hj eQlRhLpShFQkSEWAGMUQ4aGAvo87SVys/5ii2CHllAeBrwh7fEfD+Bd1LEQe9GLgIkGKQZMKSfHs 9i3HO8dbYZgHoLaQWPIVgQoR+JjEY9NPHPeGFrVWqANlmlaCVCbQlZ7mnLmbxwfm3QzjdVrHVOlu aP4sCAQElhn4xIpXx72Xhb1MEkBAgpSAJuZSwqx5vSP8Absy3OUYwHG5+MpWBMpAYDdgL4D25IpF TV+XYT5NESECEqQINaOcqSxYYG7lF73nerAbDZgNwCvn/JpLBP5FgPjGjM8EA3s6tcXPv8hU1RcJ UlW1e+hiW9vTkwLyKsKu1n55QzPS1ZIQ6AfwGh07uxY2rtRi1pIwjlVQCVKs2lXaZMOnplVf9s5G YNeSdqG2Jiot7yqO/hkMz9bAf1HnEVXxXTBE6RKkIaDoEjCzY9tY8zPzSV4Dw7FiIgIFEtgF8FWY 37ki1fRhgbHknlACEqSENrZ4ZYXHX2ydQeIKGi41YGTxYitSwgkEAFbC7OXa2rrU2y8ftjPh9aq8 AglIkAoEWE3ubW3fHDhwyIg2g3UQvBBAfTXVr1qzJGBYC+AV52UWLl84KZ2ll8xEABIk3QR5ETjz ou2H1B04cB7Jyww4D8CIvALJKQkEDLCPaG4JAre4a/HYb5NQlGooPwEJUvmZJ27G8PDAg+iFr45f YkAbgTGJK1IF7UsgA9gqI5cGe4PXupc0/bCvgb6LQK4EJEi5EpP9sATa28372UufxoAXwHg+aFOH ddBgnAj8BGAZzN4i6pd1pUb9GqfklWv0CUiQot+jWGfYfOkPTTU1nGVw5xhspp6eYtXO8EiHNQSW +84tH+2PXavzhmLVv9glK0GKXcvim3C4zmn1hvRJ8F2rITgL5HTtqxepfvowrIfDKgZ8NwNvZXfq 8F2RylDJJJqABCnR7Y12ceHPezu89FTzeRaIGQBOBzAh2lknKrvdMKwDsdrI9/v/qFn9wZtjfktU hSomVgQkSLFqV/KTbWnvmeCcdxrMzjDwVAAnAzg0+ZWXvMKMARsIfGLAGs+CNXu3T/i8u5uZks+s CUQgSwISpCxByaxSBIwtc348CgimATaNNihQk7Xn3rD92EmzL438FMT6gO7Tg3cHny1dOv73Yb00 KAIVJiBBqnADNH1+BMJ1ULUH+ZO9wJ9ixAk0d7yZHQ3iaAB1+UWNlZcBCF+13mjARho2mLPPXcAN Xanxm2NViZIVgb8ISJB0KySMgHHW3HSTH7hjBp+iDEcYrYmGpsHvxMSY/AQY7oS9zYAewLYQDD83 G7DJkRsz9f2bup8/ck/CmqdyqpyABKnKb4BqLL95/qa62j21Y3xzjQDHGvwxNDfKYA2ObDCzBpg1 GFy9ozWY8QA4Oxhm9QT/efraz75+vxMIxQTh9gUg+mDYS2CXGcKxPwJaH8k+GnfArC8g+hyw3Rx+ 9PZie+DqtmqNTzXemar5T7boKrYfCqI6AAAAAElFTkSuQmCC"/></symbol><symbol viewBox="0 0 24 24" id="webpack" xmlns="http://www.w3.org/2000/svg"><path d="M19.376 15.988l-7.709 4.45-7.708-4.45V7.087l7.708-4.45 7.709 4.45z" fill="#fff" fill-opacity=".785" stroke-width="0"/><path d="M12.286 1.98c-.21 0-.41.059-.57.179l-7.9 4.44c-.32.17-.53.5-.53.88v9c0 .38.21.711.53.881l7.9 4.44c.16.12.36.18.57.18.21 0 .41-.06.57-.18l7.9-4.44c.32-.17.53-.5.53-.88v-9c0-.38-.21-.712-.53-.882l-7.9-4.44a.945.945 0 0 0-.57-.179zm0 2.15l7 3.939v2.104h-.016v5.177h.016v.54l-7 3.939-7-3.94V8.07l7-3.94zm0 2.08l-4.9 2.83 4.9 2.83 4.9-2.83-4.9-2.83zm-5 5.08v3.58l4 2.308v-3.58l-4-2.308zm10 0l-4 2.308v3.58l4-2.308v-3.58z" fill="#8ed6fb"/><path d="M12.286 6.21l-4.9 2.83 4.9 2.83 4.9-2.83-4.9-2.83zm-5 5.08v3.58l4 2.308v-3.58l-4-2.308zm10 0l-4 2.308v3.58l4-2.308v-3.58z" fill="#1c78c0"/></symbol><symbol viewBox="0 0 24 24" id="wolframlanguage" xmlns="http://www.w3.org/2000/svg"><title>wolframLanguage</title><g transform="scale(.12121)" fill="none" fill-rule="evenodd"><circle cx="99.197" cy="98.946" r="83.28" fill="#212121" stroke-width=".841"/><path d="M182.529 98.828a83.406 83.406 0 0 1-39.14 70.721.064.064 0 0 1-.038.019l-28.62-35.665 23.71 2.612s11.385 1.177 13.978 0c2.373-.938 15.175-18.963 15.175-18.963s-36.75-23.23-49.312-36.032c1.434-21.575-1.656-50.269-1.656-50.03-9.251 9.234-10.429 10.669-19.68 19.203-4.028-13.04-5.923-17.547-9.95-30.588-12.104 9.95-21.337 26.799-27.977 46.48a78.68 78.68 0 0 0-4.23 5.094 109.774 109.774 0 0 0-2.667 3.66 114.558 114.558 0 0 0-5.132 8.002 172.555 172.555 0 0 0-3.403 6.051c-7.706 14.475-14.034 31.066-19.515 46.001a.858.858 0 0 1-.092-.184c-14.988-30.912-9.502-67.85 13.822-93.072 23.325-25.223 59.723-33.575 91.71-21.045 31.988 12.53 53.029 43.382 53.017 77.736z" fill="#e53935"/><path d="M101.452 69.178s-1.416-8.295-2.373-11.367c6.401-6.18 7.357-7.118 13.52-13.04.477 11.845.238 18.006-.479 32.481-3.55-3.568-10.668-8.074-10.668-8.074zm-27.737 40.778s-6.64-4.029-11.624-4.728c1.435-3.329 5.223-7.596 6.18-8.773-1.913.699-15.653 6.86-17.087 12.084a74.804 74.804 0 0 1 11.385 3.79 35.993 35.993 0 0 0-8.774 20.158s21.815-3.33 38.185-1.196c.283.168.609.251.938.24l8.534.239 27.111 45.136.221.35c-.037.018-.055.037-.073.037-51.133 18.485-88.085-15.543-95.976-27.443.034-.102.058-.206.074-.313 7.1-30.017 15.855-65.939 30-76.552 7.356-12.82 9.49-31.783 22.751-41.734 3.33 9.951 8.553 30.588 12.103 40.539 15.653 15.652 39.361 35.094 55.234 43.15 1.656.956 3.79 7.596 3.79 7.596l-6.401 8.056-68.276-6.879a54.462 54.462 0 0 0-4.58-.183 86.848 86.848 0 0 0-14.144 1.36c3.311-8.295 10.43-14.935 10.43-14.935zm22.054-8.774c3.789-.46 7.817.956 12.323 3.568 4.267-1.195 4.745-1.434 9.013-2.612-5.463-4.028-11.386-8.295-19.442-7.118a47.249 47.249 0 0 0-1.894 6.162z" fill="#fff" stroke-width=".936"/></g></symbol><symbol viewBox="0 0 24 24" id="word" xmlns="http://www.w3.org/2000/svg"><path d="M6 2h8l6 6v12a2 2 0 0 1-2 2H6a2 2 0 0 1-2-2V4a2 2 0 0 1 2-2m7 1.5V9h5.5L13 3.5M7 13l1.5 7h2l1.5-3 1.5 3h2l1.5-7h1v-2h-4v2h1l-.9 4.2L13 15h-2l-1.1 2.2L9 13h1v-2H6v2h1z" fill="#01579b"/></symbol><symbol viewBox="0 0 24 24" id="xaml" xmlns="http://www.w3.org/2000/svg"><path d="M18.93 12l-3.47 6H8.54l-3.47-6 3.47-6h6.92l3.47 6m4.84 0l-4.04 7L18 18l3.46-6L18 6l1.73-1 4.04 7M.23 12l4.04-7L6 6l-3.46 6L6 18l-1.73 1-4.04-7z" fill="#42a5f5"/></symbol><symbol viewBox="0 0 24 24" id="xml" xmlns="http://www.w3.org/2000/svg"><path d="M13 9h5.5L13 3.5V9M6 2h8l6 6v12a2 2 0 0 1-2 2H6a2 2 0 0 1-2-2V4c0-1.11.89-2 2-2m.12 13.5l3.74 3.74 1.42-1.41-2.33-2.33 2.33-2.33-1.42-1.41-3.74 3.74m11.16 0l-3.74-3.74-1.42 1.41 2.33 2.33-2.33 2.33 1.42 1.41 3.74-3.74z" fill="#8bc34a"/></symbol><symbol viewBox="0 0 24 24" id="yaml" xmlns="http://www.w3.org/2000/svg"><path d="M5 3h2v2H5v5a2 2 0 0 1-2 2 2 2 0 0 1 2 2v5h2v2H5c-1.07-.27-2-.9-2-2v-4a2 2 0 0 0-2-2H0v-2h1a2 2 0 0 0 2-2V5a2 2 0 0 1 2-2m14 0a2 2 0 0 1 2 2v4a2 2 0 0 0 2 2h1v2h-1a2 2 0 0 0-2 2v4a2 2 0 0 1-2 2h-2v-2h2v-5a2 2 0 0 1 2-2 2 2 0 0 1-2-2V5h-2V3h2m-7 12a1 1 0 0 1 1 1 1 1 0 0 1-1 1 1 1 0 0 1-1-1 1 1 0 0 1 1-1m-4 0a1 1 0 0 1 1 1 1 1 0 0 1-1 1 1 1 0 0 1-1-1 1 1 0 0 1 1-1m8 0a1 1 0 0 1 1 1 1 1 0 0 1-1 1 1 1 0 0 1-1-1 1 1 0 0 1 1-1z" fill="#f44336"/></symbol><symbol viewBox="0 0 24 24" id="yang" xmlns="http://www.w3.org/2000/svg"><path d="M12 2a10 10 0 0 1 10 10 10 10 0 0 1-10 10A10 10 0 0 1 2 12 10 10 0 0 1 12 2m0 2a8 8 0 0 0-8 8 8 8 0 0 0 8 8 4 4 0 0 1-4-4 4 4 0 0 1 4-4 4 4 0 0 0 4-4 4 4 0 0 0-4-4m0 2.5A1.5 1.5 0 0 1 13.5 8 1.5 1.5 0 0 1 12 9.5 1.5 1.5 0 0 1 10.5 8 1.5 1.5 0 0 1 12 6.5m0 8a1.5 1.5 0 0 0-1.5 1.5 1.5 1.5 0 0 0 1.5 1.5 1.5 1.5 0 0 0 1.5-1.5 1.5 1.5 0 0 0-1.5-1.5z" fill="#42a5f5"/></symbol><symbol viewBox="0 0 289.99999 290.00001" id="yarn" xmlns="http://www.w3.org/2000/svg"><path d="M250.733 218.418c-12.39 2.943-18.661 5.653-33.993 15.641-24.004 15.487-50.176 22.688-50.176 22.688s-2.168 3.252-8.44 4.723c-10.84 2.633-51.647 4.878-55.364 4.956-9.988.077-16.105-2.555-17.809-6.66-5.188-12.388 7.434-17.809 7.434-17.809s-2.788-1.703-4.414-3.252c-1.471-1.47-3.02-4.413-3.484-3.33-1.936 4.724-2.943 16.261-8.13 21.45-7.125 7.2-20.598 4.8-28.573.619-8.75-4.646.62-15.564.62-15.564s-4.724 2.788-8.518-2.942c-3.407-5.266-6.582-14.248-5.73-25.32 1.084-12.777 15.176-25.011 15.176-25.011s-2.477-18.661 5.653-37.787c7.356-17.422 27.179-31.437 27.179-31.437s-16.648-18.352-10.454-35c4.027-10.84 5.653-10.763 6.97-11.227 4.645-1.781 9.136-3.717 12.466-7.356 16.648-17.964 37.864-14.557 37.864-14.557s9.911-30.431 19.203-24.469c2.865 1.859 13.163 24.778 13.163 24.778s10.996-6.426 12.235-4.026c6.659 12.931 7.433 37.632 4.49 52.654-4.955 24.778-17.344 38.096-22.3 46.459-1.161 1.936 13.319 8.053 22.456 33.373 8.44 23.152.929 42.587 2.245 44.756.232.387.31.542.31.542s9.679.774 29.114-11.228c10.376-6.427 22.688-13.628 36.703-13.783 13.55-.232 14.247 15.719 4.104 18.12z" fill="#2c8ebb" stroke-width=".774"/></symbol><symbol viewBox="0 0 24 24" id="zip" xmlns="http://www.w3.org/2000/svg"><path d="M14 17h-2v-2h-2v-2h2v2h2m0-6h-2v2h2v2h-2v-2h-2V9h2V7h-2V5h2v2h2m5-4H5c-1.11 0-2 .89-2 2v14a2 2 0 0 0 2 2h14a2 2 0 0 0 2-2V5a2 2 0 0 0-2-2z" fill="#afb42b"/></symbol></svg>
\ No newline at end of file diff --git a/app/assets/images/multi-editor-off.png b/app/assets/images/multi-editor-off.png Binary files differnew file mode 100644 index 00000000000..82a6127f853 --- /dev/null +++ b/app/assets/images/multi-editor-off.png diff --git a/app/assets/images/multi-editor-on.png b/app/assets/images/multi-editor-on.png Binary files differnew file mode 100644 index 00000000000..2bcd29abf13 --- /dev/null +++ b/app/assets/images/multi-editor-on.png diff --git a/app/assets/javascripts/blob/notebook/index.js b/app/assets/javascripts/blob/notebook/index.js index c858a6bb7b4..57b031956e8 100644 --- a/app/assets/javascripts/blob/notebook/index.js +++ b/app/assets/javascripts/blob/notebook/index.js @@ -1,10 +1,8 @@ /* eslint-disable no-new */ import Vue from 'vue'; -import VueResource from 'vue-resource'; +import axios from '../../lib/utils/axios_utils'; import notebookLab from '../../notebook/index.vue'; -Vue.use(VueResource); - export default () => { const el = document.getElementById('js-notebook-viewer'); @@ -50,14 +48,14 @@ export default () => { `, methods: { loadFile() { - this.$http.get(el.dataset.endpoint) - .then(response => response.json()) - .then((res) => { - this.json = res; + axios.get(el.dataset.endpoint) + .then(res => res.data) + .then((data) => { + this.json = data; this.loading = false; }) .catch((e) => { - if (e.status) { + if (e.status !== 200) { this.loadError = true; } diff --git a/app/assets/javascripts/boards/boards_bundle.js b/app/assets/javascripts/boards/boards_bundle.js index 20d23162940..679c883cdcf 100644 --- a/app/assets/javascripts/boards/boards_bundle.js +++ b/app/assets/javascripts/boards/boards_bundle.js @@ -2,7 +2,6 @@ import _ from 'underscore'; import Vue from 'vue'; -import VueResource from 'vue-resource'; import Flash from '../flash'; import { __ } from '../locale'; import FilteredSearchBoards from './filtered_search_boards'; @@ -25,8 +24,6 @@ import './components/new_list_dropdown'; import './components/modal/index'; import '../vue_shared/vue_resource_interceptor'; -Vue.use(VueResource); - $(() => { const $boardApp = document.getElementById('board-app'); const Store = gl.issueBoards.BoardsStore; @@ -95,14 +92,13 @@ $(() => { Store.disabled = this.disabled; gl.boardService.all() - .then(response => response.json()) - .then((resp) => { - resp.forEach((board) => { + .then(res => res.data) + .then((data) => { + data.forEach((board) => { const list = Store.addList(board, this.defaultAvatar); if (list.type === 'closed') { list.position = Infinity; - list.label = { description: 'Shows all closed issues. Moving an issue to this list closes it' }; } else if (list.type === 'backlog') { list.position = -1; } @@ -113,7 +109,9 @@ $(() => { Store.addBlankState(); this.loading = false; }) - .catch(() => new Flash('An error occurred. Please try again.')); + .catch(() => { + Flash('An error occurred while fetching the board lists. Please try again.'); + }); }, methods: { updateTokens() { @@ -124,7 +122,7 @@ $(() => { if (sidebarInfoEndpoint && newIssue.subscribed === undefined) { newIssue.setFetchingState('subscriptions', true); BoardService.getIssueInfo(sidebarInfoEndpoint) - .then(res => res.json()) + .then(res => res.data) .then((data) => { newIssue.setFetchingState('subscriptions', false); newIssue.updateData({ diff --git a/app/assets/javascripts/boards/components/board_blank_state.js b/app/assets/javascripts/boards/components/board_blank_state.js index edfe7c326db..72db626d3c7 100644 --- a/app/assets/javascripts/boards/components/board_blank_state.js +++ b/app/assets/javascripts/boards/components/board_blank_state.js @@ -65,7 +65,7 @@ export default { // Save the labels gl.boardService.generateDefaultLists() - .then(resp => resp.json()) + .then(res => res.data) .then((data) => { data.forEach((listObj) => { const list = Store.findList('title', listObj.title); diff --git a/app/assets/javascripts/boards/components/board_list.js b/app/assets/javascripts/boards/components/board_list.js index 29aeb8e84aa..84b76a6f1b1 100644 --- a/app/assets/javascripts/boards/components/board_list.js +++ b/app/assets/javascripts/boards/components/board_list.js @@ -115,7 +115,7 @@ export default { }, mounted() { const options = gl.issueBoards.getBoardSortableDefaultOptions({ - scroll: document.querySelectorAll('.boards-list')[0], + scroll: true, group: 'issues', disabled: this.disabled, filter: '.board-list-count, .is-disabled', diff --git a/app/assets/javascripts/boards/components/board_sidebar.js b/app/assets/javascripts/boards/components/board_sidebar.js index 616de2347e1..983429550f0 100644 --- a/app/assets/javascripts/boards/components/board_sidebar.js +++ b/app/assets/javascripts/boards/components/board_sidebar.js @@ -1,5 +1,4 @@ /* eslint-disable comma-dangle, space-before-function-paren, no-new */ -/* global MilestoneSelect */ import Vue from 'vue'; import Flash from '../../flash'; @@ -12,6 +11,7 @@ import './sidebar/remove_issue'; import IssuableContext from '../../issuable_context'; import LabelsSelect from '../../labels_select'; import subscriptions from '../../sidebar/components/subscriptions/subscriptions.vue'; +import MilestoneSelect from '../../milestone_select'; const Store = gl.issueBoards.BoardsStore; diff --git a/app/assets/javascripts/boards/components/modal/index.js b/app/assets/javascripts/boards/components/modal/index.js index d2044f20ebe..d825ff38587 100644 --- a/app/assets/javascripts/boards/components/modal/index.js +++ b/app/assets/javascripts/boards/components/modal/index.js @@ -89,7 +89,7 @@ gl.issueBoards.IssuesModal = Vue.extend({ page: this.page, per: this.perPage, })) - .then(resp => resp.json()) + .then(res => res.data) .then((data) => { if (clearIssues) { this.issues = []; diff --git a/app/assets/javascripts/boards/models/list.js b/app/assets/javascripts/boards/models/list.js index df2809e1805..e210d69895e 100644 --- a/app/assets/javascripts/boards/models/list.js +++ b/app/assets/javascripts/boards/models/list.js @@ -40,7 +40,7 @@ class List { save () { return gl.boardService.createList(this.label.id) - .then(resp => resp.json()) + .then(res => res.data) .then((data) => { this.id = data.id; this.type = data.list_type; @@ -90,7 +90,7 @@ class List { } return gl.boardService.getIssuesForList(this.id, data) - .then(resp => resp.json()) + .then(res => res.data) .then((data) => { this.loading = false; this.issuesSize = data.size; @@ -108,7 +108,7 @@ class List { this.issuesSize += 1; return gl.boardService.newIssue(this.id, issue) - .then(resp => resp.json()) + .then(res => res.data) .then((data) => { issue.id = data.id; issue.iid = data.iid; diff --git a/app/assets/javascripts/boards/services/board_service.js b/app/assets/javascripts/boards/services/board_service.js index fa7ddd25e1f..d78d4701974 100644 --- a/app/assets/javascripts/boards/services/board_service.js +++ b/app/assets/javascripts/boards/services/board_service.js @@ -1,82 +1,79 @@ -/* eslint-disable space-before-function-paren, comma-dangle, no-param-reassign, camelcase, max-len, no-unused-vars */ - -import Vue from 'vue'; +import axios from '../../lib/utils/axios_utils'; +import { mergeUrlParams } from '../../lib/utils/url_utility'; export default class BoardService { - constructor ({ boardsEndpoint, listsEndpoint, bulkUpdatePath, boardId }) { - this.boards = Vue.resource(`${boardsEndpoint}{/id}.json`, {}, { - issues: { - method: 'GET', - url: `${gon.relative_url_root}/-/boards/${boardId}/issues.json`, - } - }); - this.lists = Vue.resource(`${listsEndpoint}{/id}`, {}, { - generate: { - method: 'POST', - url: `${listsEndpoint}/generate.json` - } - }); - this.issue = Vue.resource(`${gon.relative_url_root}/-/boards/${boardId}/issues{/id}`, {}); - this.issues = Vue.resource(`${listsEndpoint}{/id}/issues`, {}, { - bulkUpdate: { - method: 'POST', - url: bulkUpdatePath, - }, - }); + constructor({ boardsEndpoint, listsEndpoint, bulkUpdatePath, boardId }) { + this.boardsEndpoint = boardsEndpoint; + this.boardId = boardId; + this.listsEndpoint = listsEndpoint; + this.listsEndpointGenerate = `${listsEndpoint}/generate.json`; + this.bulkUpdatePath = bulkUpdatePath; + } + + generateBoardsPath(id) { + return `${this.boardsEndpoint}${id ? `/${id}` : ''}.json`; } - all () { - return this.lists.get(); + generateIssuesPath(id) { + return `${this.listsEndpoint}${id ? `/${id}` : ''}/issues`; } - generateDefaultLists () { - return this.lists.generate({}); + static generateIssuePath(boardId, id) { + return `${gon.relative_url_root}/-/boards/${boardId ? `/${boardId}` : ''}/issues${id ? `/${id}` : ''}`; } - createList (label_id) { - return this.lists.save({}, { + all() { + return axios.get(this.listsEndpoint); + } + + generateDefaultLists() { + return axios.post(this.listsEndpointGenerate, {}); + } + + createList(labelId) { + return axios.post(this.listsEndpoint, { list: { - label_id - } + label_id: labelId, + }, }); } - updateList (id, position) { - return this.lists.update({ id }, { + updateList(id, position) { + return axios.put(`${this.listsEndpoint}/${id}`, { list: { - position - } + position, + }, }); } - destroyList (id) { - return this.lists.delete({ id }); + destroyList(id) { + return axios.delete(`${this.listsEndpoint}/${id}`); } - getIssuesForList (id, filter = {}) { + getIssuesForList(id, filter = {}) { const data = { id }; Object.keys(filter).forEach((key) => { data[key] = filter[key]; }); - return this.issues.get(data); + return axios.get(mergeUrlParams(data, this.generateIssuesPath(id))); } - moveIssue (id, from_list_id = null, to_list_id = null, move_before_id = null, move_after_id = null) { - return this.issue.update({ id }, { - from_list_id, - to_list_id, - move_before_id, - move_after_id, + moveIssue(id, fromListId = null, toListId = null, moveBeforeId = null, moveAfterId = null) { + return axios.put(BoardService.generateIssuePath(this.boardId, id), { + from_list_id: fromListId, + to_list_id: toListId, + move_before_id: moveBeforeId, + move_after_id: moveAfterId, }); } - newIssue (id, issue) { - return this.issues.save({ id }, { - issue + newIssue(id, issue) { + return axios.post(this.generateIssuesPath(id), { + issue, }); } getBacklog(data) { - return this.boards.issues(data); + return axios.get(mergeUrlParams(data, `${gon.relative_url_root}/-/boards/${this.boardId}/issues.json`)); } bulkUpdate(issueIds, extraData = {}) { @@ -86,15 +83,15 @@ export default class BoardService { }), }; - return this.issues.bulkUpdate(data); + return axios.post(this.bulkUpdatePath, data); } static getIssueInfo(endpoint) { - return Vue.http.get(endpoint); + return axios.get(endpoint); } static toggleIssueSubscription(endpoint) { - return Vue.http.post(endpoint); + return axios.post(endpoint); } } diff --git a/app/assets/javascripts/dispatcher.js b/app/assets/javascripts/dispatcher.js index 118437b82a3..42f61d33f6e 100644 --- a/app/assets/javascripts/dispatcher.js +++ b/app/assets/javascripts/dispatcher.js @@ -5,7 +5,7 @@ import IssuableIndex from './issuable_index'; import Milestone from './milestone'; import IssuableForm from './issuable_form'; import LabelsSelect from './labels_select'; -/* global MilestoneSelect */ +import MilestoneSelect from './milestone_select'; import NewBranchForm from './new_branch_form'; import NotificationsForm from './notifications_form'; import notificationsDropdown from './notifications_dropdown'; diff --git a/app/assets/javascripts/filtered_search/filtered_search_manager.js b/app/assets/javascripts/filtered_search/filtered_search_manager.js index 2ba85c7da97..c05a83176f2 100644 --- a/app/assets/javascripts/filtered_search/filtered_search_manager.js +++ b/app/assets/javascripts/filtered_search/filtered_search_manager.js @@ -127,7 +127,7 @@ class FilteredSearchManager { this.handleInputVisualTokenWrapper = this.handleInputVisualToken.bind(this); this.checkForEnterWrapper = this.checkForEnter.bind(this); this.onClearSearchWrapper = this.onClearSearch.bind(this); - this.checkForBackspaceWrapper = this.checkForBackspace.bind(this); + this.checkForBackspaceWrapper = this.checkForBackspace.call(this); this.removeSelectedTokenKeydownWrapper = this.removeSelectedTokenKeydown.bind(this); this.unselectEditTokensWrapper = this.unselectEditTokens.bind(this); this.editTokenWrapper = this.editToken.bind(this); @@ -180,22 +180,34 @@ class FilteredSearchManager { this.unbindStateEvents(); } - checkForBackspace(e) { - // 8 = Backspace Key - // 46 = Delete Key - if (e.keyCode === 8 || e.keyCode === 46) { - const { lastVisualToken } = gl.FilteredSearchVisualTokens.getLastVisualTokenBeforeInput(); + checkForBackspace() { + let backspaceCount = 0; + + // closure for keeping track of the number of backspace keystrokes + return (e) => { + // 8 = Backspace Key + // 46 = Delete Key + if (e.keyCode === 8 || e.keyCode === 46) { + const { lastVisualToken } = gl.FilteredSearchVisualTokens.getLastVisualTokenBeforeInput(); + const { tokenName, tokenValue } = gl.DropdownUtils.getVisualTokenValues(lastVisualToken); + const canEdit = tokenName && this.canEdit && this.canEdit(tokenName, tokenValue); + + if (this.filteredSearchInput.value === '' && lastVisualToken && canEdit) { + backspaceCount += 1; + + if (backspaceCount === 2) { + backspaceCount = 0; + this.filteredSearchInput.value = gl.FilteredSearchVisualTokens.getLastTokenPartial(); + gl.FilteredSearchVisualTokens.removeLastTokenPartial(); + } + } - const { tokenName, tokenValue } = gl.DropdownUtils.getVisualTokenValues(lastVisualToken); - const canEdit = tokenName && this.canEdit && this.canEdit(tokenName, tokenValue); - if (this.filteredSearchInput.value === '' && lastVisualToken && canEdit) { - this.filteredSearchInput.value = gl.FilteredSearchVisualTokens.getLastTokenPartial(); - gl.FilteredSearchVisualTokens.removeLastTokenPartial(); + // Reposition dropdown so that it is aligned with cursor + this.dropdownManager.updateCurrentDropdownOffset(); + } else { + backspaceCount = 0; } - - // Reposition dropdown so that it is aligned with cursor - this.dropdownManager.updateCurrentDropdownOffset(); - } + }; } checkForEnter(e) { diff --git a/app/assets/javascripts/groups/components/group_item.vue b/app/assets/javascripts/groups/components/group_item.vue index 6421547bbde..02129d39846 100644 --- a/app/assets/javascripts/groups/components/group_item.vue +++ b/app/assets/javascripts/groups/components/group_item.vue @@ -77,7 +77,8 @@ export default { class="group-row" > <div - class="group-row-contents"> + class="group-row-contents" + :class="{ 'project-row-contents': !isGroup }"> <item-actions v-if="isGroup" :group="group" @@ -97,7 +98,7 @@ export default { /> </div> <div - class="avatar-container s40 hidden-xs" + class="avatar-container prepend-top-8 prepend-left-5 s24 hidden-xs" :class="{ 'content-loading': group.isChildrenLoading }" > <a @@ -106,11 +107,12 @@ export default { > <img v-if="hasAvatar" - class="avatar s40" + class="avatar s24" :src="group.avatarUrl" /> <identicon v-else + size-class="s24" :entity-id=group.id :entity-name="group.name" /> @@ -123,7 +125,7 @@ export default { :href="group.relativePath" :title="group.fullName" class="no-expand" - data-placement="top" + data-placement="bottom" >{{ // ending bracket must be by closing tag to prevent // link hover text-decoration from over-extending diff --git a/app/assets/javascripts/groups/components/item_actions.vue b/app/assets/javascripts/groups/components/item_actions.vue index 58ba5aff7cf..a685960d862 100644 --- a/app/assets/javascripts/groups/components/item_actions.vue +++ b/app/assets/javascripts/groups/components/item_actions.vue @@ -1,14 +1,14 @@ <script> -import { s__ } from '../../locale'; -import tooltip from '../../vue_shared/directives/tooltip'; -import modal from '../../vue_shared/components/modal.vue'; +import { s__ } from '~/locale'; +import tooltip from '~/vue_shared/directives/tooltip'; +import icon from '~/vue_shared/components/icon.vue'; +import modal from '~/vue_shared/components/modal.vue'; import eventHub from '../event_hub'; import { COMMON_STR } from '../constants'; -import Icon from '../../vue_shared/components/icon.vue'; export default { components: { - Icon, + icon, modal, }, directives: { @@ -64,10 +64,9 @@ export default { :title="editBtnTitle" :aria-label="editBtnTitle" data-container="body" + data-placement="bottom" class="edit-group btn no-expand"> - <icon - name="settings"> - </icon> + <icon name="settings"/> </a> <a v-tooltip @@ -77,10 +76,9 @@ export default { :title="leaveBtnTitle" :aria-label="leaveBtnTitle" data-container="body" + data-placement="bottom" class="leave-group btn no-expand"> - <i - class="fa fa-sign-out" - aria-hidden="true"/> + <icon name="leave"/> </a> <modal v-show="modalStatus" diff --git a/app/assets/javascripts/groups/components/item_caret.vue b/app/assets/javascripts/groups/components/item_caret.vue index 959b984816f..9e90fe2b701 100644 --- a/app/assets/javascripts/groups/components/item_caret.vue +++ b/app/assets/javascripts/groups/components/item_caret.vue @@ -1,4 +1,6 @@ <script> +import icon from '~/vue_shared/components/icon.vue'; + export default { props: { isGroupOpen: { @@ -7,9 +9,12 @@ export default { default: false, }, }, + components: { + icon, + }, computed: { iconClass() { - return this.isGroupOpen ? 'fa-caret-down' : 'fa-caret-right'; + return this.isGroupOpen ? 'angle-down' : 'angle-right'; }, }, }; @@ -17,9 +22,9 @@ export default { <template> <span class="folder-caret"> - <i - :class="iconClass" - class="fa" - aria-hidden="true"/> + <icon + :size="12" + :name="iconClass" + /> </span> </template> diff --git a/app/assets/javascripts/groups/components/item_stats.vue b/app/assets/javascripts/groups/components/item_stats.vue index 9f8ac138fc3..803dc63d39c 100644 --- a/app/assets/javascripts/groups/components/item_stats.vue +++ b/app/assets/javascripts/groups/components/item_stats.vue @@ -1,10 +1,14 @@ <script> -import tooltip from '../../vue_shared/directives/tooltip'; +import icon from '~/vue_shared/components/icon.vue'; +import timeAgoTooltip from '~/vue_shared/components/time_ago_tooltip.vue'; import { ITEM_TYPE, VISIBILITY_TYPE_ICON, GROUP_VISIBILITY_TYPE, PROJECT_VISIBILITY_TYPE } from '../constants'; +import itemStatsValue from './item_stats_value.vue'; export default { - directives: { - tooltip, + components: { + icon, + timeAgoTooltip, + itemStatsValue, }, props: { item: { @@ -34,65 +38,47 @@ export default { <template> <div class="stats"> - <span - v-tooltip + <item-stats-value v-if="isGroup" + css-class="number-subgroups" + icon-name="folder" :title="s__('Subgroups')" - class="number-subgroups" - data-placement="top" - data-container="body"> - <i - class="fa fa-folder" - aria-hidden="true" - /> - {{item.subgroupCount}} - </span> - <span - v-tooltip + :value=item.subgroupCount + /> + <item-stats-value v-if="isGroup" + css-class="number-projects" + icon-name="bookmark" :title="s__('Projects')" - class="number-projects" - data-placement="top" - data-container="body"> - <i - class="fa fa-bookmark" - aria-hidden="true" - /> - {{item.projectCount}} - </span> - <span - v-tooltip + :value=item.projectCount + /> + <item-stats-value v-if="isGroup" + css-class="number-users" + icon-name="users" :title="s__('Members')" - class="number-users" - data-placement="top" - data-container="body"> - <i - class="fa fa-users" - aria-hidden="true" - /> - {{item.memberCount}} - </span> - <span + :value=item.memberCount + /> + <item-stats-value v-if="isProject" - class="project-stars"> - <i - class="fa fa-star" - aria-hidden="true" - /> - {{item.starCount}} - </span> - <span - v-tooltip + css-class="project-stars" + icon-name="star" + :value=item.starCount + /> + <item-stats-value + css-class="item-visibility" + tooltip-placement="left" + :icon-name="visibilityIcon" :title="visibilityTooltip" - data-placement="left" - data-container="body" - class="item-visibility"> - <i - :class="visibilityIcon" - class="fa" - aria-hidden="true" + /> + <div + class="last-updated" + v-if="isProject" + > + <time-ago-tooltip + tooltip-placement="bottom" + :time="item.updatedAt" /> - </span> + </div> </div> </template> diff --git a/app/assets/javascripts/groups/components/item_stats_value.vue b/app/assets/javascripts/groups/components/item_stats_value.vue new file mode 100644 index 00000000000..f441cabf6d2 --- /dev/null +++ b/app/assets/javascripts/groups/components/item_stats_value.vue @@ -0,0 +1,68 @@ +<script> +import tooltip from '~/vue_shared/directives/tooltip'; +import icon from '~/vue_shared/components/icon.vue'; + +export default { + props: { + title: { + type: String, + required: false, + default: '', + }, + cssClass: { + type: String, + required: false, + default: '', + }, + iconName: { + type: String, + required: true, + }, + tooltipPlacement: { + type: String, + required: false, + default: 'bottom', + }, + /** + * value could either be number or string + * as `memberCount` is always passed as string + * while `subgroupCount` & `projectCount` + * are always number + */ + value: { + type: [Number, String], + required: false, + default: '', + }, + }, + directives: { + tooltip, + }, + components: { + icon, + }, + computed: { + isValuePresent() { + return this.value !== ''; + }, + }, +}; +</script> + +<template> + <span + v-tooltip + data-container="body" + :data-placement="tooltipPlacement" + :class="cssClass" + :title="title" + > + <icon :name="iconName"/> + <span + v-if="isValuePresent" + class="stat-value" + > + {{value}} + </span> + </span> +</template> diff --git a/app/assets/javascripts/groups/components/item_type_icon.vue b/app/assets/javascripts/groups/components/item_type_icon.vue index c02a8ad6d8c..118d94d4937 100644 --- a/app/assets/javascripts/groups/components/item_type_icon.vue +++ b/app/assets/javascripts/groups/components/item_type_icon.vue @@ -1,7 +1,11 @@ <script> +import icon from '~/vue_shared/components/icon.vue'; import { ITEM_TYPE } from '../constants'; export default { + components: { + icon, + }, props: { itemType: { type: String, @@ -16,9 +20,9 @@ export default { computed: { iconClass() { if (this.itemType === ITEM_TYPE.GROUP) { - return this.isGroupOpen ? 'fa-folder-open' : 'fa-folder'; + return this.isGroupOpen ? 'folder-open' : 'folder'; } - return 'fa-bookmark'; + return 'bookmark'; }, }, }; @@ -26,9 +30,6 @@ export default { <template> <span class="item-type-icon"> - <i - :class="iconClass" - class="fa" - aria-hidden="true"/> + <icon :name="iconClass"/> </span> </template> diff --git a/app/assets/javascripts/groups/constants.js b/app/assets/javascripts/groups/constants.js index 6fde41414b3..b8baed682f5 100644 --- a/app/assets/javascripts/groups/constants.js +++ b/app/assets/javascripts/groups/constants.js @@ -29,7 +29,7 @@ export const PROJECT_VISIBILITY_TYPE = { }; export const VISIBILITY_TYPE_ICON = { - public: 'fa-globe', - internal: 'fa-shield', - private: 'fa-lock', + public: 'earth', + internal: 'shield', + private: 'lock', }; diff --git a/app/assets/javascripts/groups/store/groups_store.js b/app/assets/javascripts/groups/store/groups_store.js index a1689f4c5cc..ffc86175548 100644 --- a/app/assets/javascripts/groups/store/groups_store.js +++ b/app/assets/javascripts/groups/store/groups_store.js @@ -91,6 +91,7 @@ export default class GroupsStore { subgroupCount: rawGroupItem.subgroup_count, memberCount: rawGroupItem.number_users_with_delimiter, starCount: rawGroupItem.star_count, + updatedAt: rawGroupItem.updated_at, }; } diff --git a/app/assets/javascripts/ide/components/ide_context_bar.vue b/app/assets/javascripts/ide/components/ide_context_bar.vue index 5a08718e386..78c01272af6 100644 --- a/app/assets/javascripts/ide/components/ide_context_bar.vue +++ b/app/assets/javascripts/ide/components/ide_context_bar.vue @@ -2,11 +2,18 @@ import { mapGetters, mapState, mapActions } from 'vuex'; import repoCommitSection from './repo_commit_section.vue'; import icon from '../../vue_shared/components/icon.vue'; +import panelResizer from '../../vue_shared/components/panel_resizer.vue'; export default { + data() { + return { + width: 290, + }; + }, components: { repoCommitSection, icon, + panelResizer, }, computed: { ...mapState([ @@ -18,10 +25,20 @@ export default { currentIcon() { return this.rightPanelCollapsed ? 'angle-double-left' : 'angle-double-right'; }, + maxSize() { + return window.innerWidth / 2; + }, + panelStyle() { + if (!this.rightPanelCollapsed) { + return { width: `${this.width}px` }; + } + return {}; + }, }, methods: { ...mapActions([ 'setPanelCollapsedStatus', + 'setResizingStatus', ]), toggleCollapsed() { this.setPanelCollapsedStatus({ @@ -29,6 +46,12 @@ export default { collapsed: !this.rightPanelCollapsed, }); }, + resizingStarted() { + this.setResizingStatus(true); + }, + resizingEnded() { + this.setResizingStatus(false); + }, }, }; </script> @@ -39,6 +62,7 @@ export default { :class="{ 'is-collapsed': rightPanelCollapsed, }" + :style="panelStyle" > <div class="multi-file-commit-panel-section"> @@ -71,5 +95,14 @@ export default { <repo-commit-section class=""/> </div> + <panel-resizer + :size.sync="width" + :enabled="!rightPanelCollapsed" + :start-size="290" + :min-size="200" + :max-size="maxSize" + @resize-start="resizingStarted" + @resize-end="resizingEnded" + side="left"/> </div> </template> diff --git a/app/assets/javascripts/ide/components/ide_side_bar.vue b/app/assets/javascripts/ide/components/ide_side_bar.vue index 535398d98c2..269f300a04d 100644 --- a/app/assets/javascripts/ide/components/ide_side_bar.vue +++ b/app/assets/javascripts/ide/components/ide_side_bar.vue @@ -2,11 +2,18 @@ import { mapState, mapActions } from 'vuex'; import projectTree from './ide_project_tree.vue'; import icon from '../../vue_shared/components/icon.vue'; +import panelResizer from '../../vue_shared/components/panel_resizer.vue'; export default { + data() { + return { + width: 290, + }; + }, components: { projectTree, icon, + panelResizer, }, computed: { ...mapState([ @@ -16,10 +23,20 @@ export default { currentIcon() { return this.leftPanelCollapsed ? 'angle-double-right' : 'angle-double-left'; }, + maxSize() { + return window.innerWidth / 2; + }, + panelStyle() { + if (!this.leftPanelCollapsed) { + return { width: `${this.width}px` }; + } + return {}; + }, }, methods: { ...mapActions([ 'setPanelCollapsedStatus', + 'setResizingStatus', ]), toggleCollapsed() { this.setPanelCollapsedStatus({ @@ -27,6 +44,12 @@ export default { collapsed: !this.leftPanelCollapsed, }); }, + resizingStarted() { + this.setResizingStatus(true); + }, + resizingEnded() { + this.setResizingStatus(false); + }, }, }; </script> @@ -37,6 +60,7 @@ export default { :class="{ 'is-collapsed': leftPanelCollapsed, }" + :style="panelStyle" > <div class="multi-file-commit-panel-inner"> <project-tree @@ -58,5 +82,14 @@ export default { class="collapse-text" >Collapse sidebar</span> </button> + <panel-resizer + :size.sync="width" + :enabled="!leftPanelCollapsed" + :start-size="290" + :min-size="200" + :max-size="maxSize" + @resize-start="resizingStarted" + @resize-end="resizingEnded" + side="right"/> </div> </template> diff --git a/app/assets/javascripts/ide/components/repo_editor.vue b/app/assets/javascripts/ide/components/repo_editor.vue index 221be4b9074..343fd0a5300 100644 --- a/app/assets/javascripts/ide/components/repo_editor.vue +++ b/app/assets/javascripts/ide/components/repo_editor.vue @@ -90,6 +90,11 @@ export default { rightPanelCollapsed() { this.editor.updateDimensions(); }, + panelResizing(isResizing) { + if (isResizing === false) { + this.editor.updateDimensions(); + } + }, }, computed: { ...mapGetters([ @@ -99,6 +104,7 @@ export default { ...mapState([ 'leftPanelCollapsed', 'rightPanelCollapsed', + 'panelResizing', ]), shouldHideEditor() { return this.activeFile.binary && !this.activeFile.raw; diff --git a/app/assets/javascripts/ide/components/repo_file.vue b/app/assets/javascripts/ide/components/repo_file.vue index 09ca11531b1..c8b0441d81c 100644 --- a/app/assets/javascripts/ide/components/repo_file.vue +++ b/app/assets/javascripts/ide/components/repo_file.vue @@ -3,6 +3,7 @@ import timeAgoMixin from '../../vue_shared/mixins/timeago'; import skeletonLoadingContainer from '../../vue_shared/components/skeleton_loading_container.vue'; import newDropdown from './new_dropdown/index.vue'; + import fileIcon from '../../vue_shared/components/file_icon.vue'; export default { mixins: [ @@ -11,6 +12,7 @@ components: { skeletonLoadingContainer, newDropdown, + fileIcon, }, props: { file: { @@ -26,13 +28,6 @@ ...mapState([ 'leftPanelCollapsed', ]), - fileIcon() { - return { - 'fa-spinner fa-spin': this.file.loading, - [this.file.icon]: !this.file.loading, - 'fa-folder-open': !this.file.loading && this.file.opened, - }; - }, isSubmodule() { return this.file.type === 'submodule'; }, @@ -94,16 +89,18 @@ class="multi-file-table-name" :colspan="submoduleColSpan" > - <i - class="fa fa-fw file-icon" - :class="fileIcon" - :style="levelIndentation" - aria-hidden="true" - > - </i> <a class="repo-file-name" > + <file-icon + :file-name="file.name" + :loading="file.loading" + :folder="file.type === 'tree'" + :opened="file.opened" + :style="levelIndentation" + :size="16" + > + </file-icon> {{ file.name }} </a> <new-dropdown diff --git a/app/assets/javascripts/ide/components/repo_tab.vue b/app/assets/javascripts/ide/components/repo_tab.vue index 5bd63ac9ec5..e7684884b2c 100644 --- a/app/assets/javascripts/ide/components/repo_tab.vue +++ b/app/assets/javascripts/ide/components/repo_tab.vue @@ -1,5 +1,6 @@ <script> import { mapActions } from 'vuex'; +import fileIcon from '../../vue_shared/components/file_icon.vue'; export default { props: { @@ -8,7 +9,9 @@ export default { required: true, }, }, - + components: { + fileIcon, + }, computed: { closeLabel() { if (this.tab.changed || this.tab.tempFile) { @@ -63,6 +66,11 @@ export default { :class="{active : tab.active }" :title="tab.url" > + <file-icon + :file-name="tab.name" + :size="16" + > + </file-icon> {{ tab.name }} </div> </li> diff --git a/app/assets/javascripts/ide/stores/actions.js b/app/assets/javascripts/ide/stores/actions.js index c01046c8c76..335882bb6d7 100644 --- a/app/assets/javascripts/ide/stores/actions.js +++ b/app/assets/javascripts/ide/stores/actions.js @@ -63,6 +63,10 @@ export const setPanelCollapsedStatus = ({ commit }, { side, collapsed }) => { } }; +export const setResizingStatus = ({ commit }, resizing) => { + commit(types.SET_RESIZING_STATUS, resizing); +}; + export const checkCommitStatus = ({ state }) => service .getBranchData(state.currentProjectId, state.currentBranchId) diff --git a/app/assets/javascripts/ide/stores/mutation_types.js b/app/assets/javascripts/ide/stores/mutation_types.js index 4e3c10972ba..69b218a5e7d 100644 --- a/app/assets/javascripts/ide/stores/mutation_types.js +++ b/app/assets/javascripts/ide/stores/mutation_types.js @@ -5,6 +5,7 @@ export const SET_ROOT = 'SET_ROOT'; export const SET_LAST_COMMIT_DATA = 'SET_LAST_COMMIT_DATA'; export const SET_LEFT_PANEL_COLLAPSED = 'SET_LEFT_PANEL_COLLAPSED'; export const SET_RIGHT_PANEL_COLLAPSED = 'SET_RIGHT_PANEL_COLLAPSED'; +export const SET_RESIZING_STATUS = 'SET_RESIZING_STATUS'; // Project Mutation Types export const SET_PROJECT = 'SET_PROJECT'; diff --git a/app/assets/javascripts/ide/stores/mutations.js b/app/assets/javascripts/ide/stores/mutations.js index 2fed9019cb6..03d81be10a1 100644 --- a/app/assets/javascripts/ide/stores/mutations.js +++ b/app/assets/javascripts/ide/stores/mutations.js @@ -49,6 +49,11 @@ export default { rightPanelCollapsed: collapsed, }); }, + [types.SET_RESIZING_STATUS](state, resizing) { + Object.assign(state, { + panelResizing: resizing, + }); + }, [types.SET_LAST_COMMIT_DATA](state, { entry, lastCommit }) { Object.assign(entry.lastCommit, { id: lastCommit.commit.id, diff --git a/app/assets/javascripts/ide/stores/state.js b/app/assets/javascripts/ide/stores/state.js index 539e382830f..61d12096946 100644 --- a/app/assets/javascripts/ide/stores/state.js +++ b/app/assets/javascripts/ide/stores/state.js @@ -19,4 +19,5 @@ export default () => ({ projects: {}, leftPanelCollapsed: false, rightPanelCollapsed: true, + panelResizing: false, }); diff --git a/app/assets/javascripts/init_issuable_sidebar.js b/app/assets/javascripts/init_issuable_sidebar.js index 5d4c1851fe5..e61b37a2d1f 100644 --- a/app/assets/javascripts/init_issuable_sidebar.js +++ b/app/assets/javascripts/init_issuable_sidebar.js @@ -1,5 +1,6 @@ /* eslint-disable no-new */ -/* global MilestoneSelect */ + +import MilestoneSelect from './milestone_select'; import LabelsSelect from './labels_select'; import IssuableContext from './issuable_context'; import Sidebar from './right_sidebar'; diff --git a/app/assets/javascripts/init_legacy_filters.js b/app/assets/javascripts/init_legacy_filters.js index 2cbb70220d0..b6ff97d1279 100644 --- a/app/assets/javascripts/init_legacy_filters.js +++ b/app/assets/javascripts/init_legacy_filters.js @@ -1,9 +1,9 @@ /* eslint-disable no-new */ import LabelsSelect from './labels_select'; -/* global MilestoneSelect */ import subscriptionSelect from './subscription_select'; import UsersSelect from './users_select'; import issueStatusSelect from './issue_status_select'; +import MilestoneSelect from './milestone_select'; export default () => { new UsersSelect(); diff --git a/app/assets/javascripts/issuable_bulk_update_sidebar.js b/app/assets/javascripts/issuable_bulk_update_sidebar.js index bf77b93b643..2056efe701b 100644 --- a/app/assets/javascripts/issuable_bulk_update_sidebar.js +++ b/app/assets/javascripts/issuable_bulk_update_sidebar.js @@ -1,8 +1,7 @@ /* eslint-disable class-methods-use-this, no-new */ -/* global MilestoneSelect */ import IssuableBulkUpdateActions from './issuable_bulk_update_actions'; -import './milestone_select'; +import MilestoneSelect from './milestone_select'; import issueStatusSelect from './issue_status_select'; import subscriptionSelect from './subscription_select'; import LabelsSelect from './labels_select'; diff --git a/app/assets/javascripts/issue_show/components/app.vue b/app/assets/javascripts/issue_show/components/app.vue index 952f49d522e..fc10a43d1bf 100644 --- a/app/assets/javascripts/issue_show/components/app.vue +++ b/app/assets/javascripts/issue_show/components/app.vue @@ -172,8 +172,8 @@ export default { }, updateIssuable() { - this.service.updateIssuable(this.store.formState) - .then(res => res.json()) + return this.service.updateIssuable(this.store.formState) + .then(res => res.data) .then(data => this.checkForSpam(data)) .then((data) => { if (location.pathname !== data.web_url) { @@ -182,7 +182,7 @@ export default { return this.service.getData(); }) - .then(res => res.json()) + .then(res => res.data) .then((data) => { this.store.updateState(data); eventHub.$emit('close.form'); @@ -207,7 +207,7 @@ export default { deleteIssuable() { this.service.deleteIssuable() - .then(res => res.json()) + .then(res => res.data) .then((data) => { // Stop the poll so we don't get 404's with the issuable not existing this.poll.stop(); @@ -225,7 +225,7 @@ export default { this.poll = new Poll({ resource: this.service, method: 'getData', - successCallback: res => res.json().then(data => this.store.updateState(data)), + successCallback: res => this.store.updateState(res.data), errorCallback(err) { throw new Error(err); }, diff --git a/app/assets/javascripts/issue_show/services/index.js b/app/assets/javascripts/issue_show/services/index.js index 6f0fd0b1768..9546eb22c27 100644 --- a/app/assets/javascripts/issue_show/services/index.js +++ b/app/assets/javascripts/issue_show/services/index.js @@ -1,29 +1,20 @@ -import Vue from 'vue'; -import VueResource from 'vue-resource'; - -Vue.use(VueResource); +import axios from '../../lib/utils/axios_utils'; export default class Service { constructor(endpoint) { - this.endpoint = endpoint; - - this.resource = Vue.resource(`${this.endpoint}.json`, {}, { - realtimeChanges: { - method: 'GET', - url: `${this.endpoint}/realtime_changes`, - }, - }); + this.endpoint = `${endpoint}.json`; + this.realtimeEndpoint = `${endpoint}/realtime_changes`; } getData() { - return this.resource.realtimeChanges(); + return axios.get(this.realtimeEndpoint); } deleteIssuable() { - return this.resource.delete(); + return axios.delete(this.endpoint); } updateIssuable(data) { - return this.resource.update(data); + return axios.put(this.endpoint, data); } } diff --git a/app/assets/javascripts/job.js b/app/assets/javascripts/job.js index 198a7823381..8f32dcc94e2 100644 --- a/app/assets/javascripts/job.js +++ b/app/assets/javascripts/job.js @@ -1,7 +1,7 @@ import _ from 'underscore'; import { visitUrl } from './lib/utils/url_utility'; import bp from './breakpoints'; -import { bytesToKiB } from './lib/utils/number_utils'; +import { numberToHumanSize } from './lib/utils/number_utils'; import { setCiStatusFavicon } from './lib/utils/common_utils'; import { timeFor } from './lib/utils/datetime_utility'; @@ -96,14 +96,15 @@ export default class Job { // eslint-disable-next-line class-methods-use-this canScroll() { - return this.$document.height() > this.$window.height(); + return $(document).height() > $(window).height(); } toggleScroll() { - const currentPosition = this.$document.scrollTop(); - const scrollHeight = this.$document.height(); + const $document = $(document); + const currentPosition = $document.scrollTop(); + const scrollHeight = $document.height(); - const windowHeight = this.$window.height(); + const windowHeight = $(window).height(); if (this.canScroll()) { if (currentPosition > 0 && (scrollHeight - currentPosition !== windowHeight)) { @@ -127,18 +128,22 @@ export default class Job { this.toggleDisableButton(this.$scrollBottomBtn, true); } } - + // eslint-disable-next-line class-methods-use-this isScrolledToBottom() { - const currentPosition = this.$document.scrollTop(); - const scrollHeight = this.$document.height(); + const $document = $(document); + + const currentPosition = $document.scrollTop(); + const scrollHeight = $document.height(); + + const windowHeight = $(window).height(); - const windowHeight = this.$window.height(); return scrollHeight - currentPosition === windowHeight; } // eslint-disable-next-line class-methods-use-this scrollDown() { - this.$document.scrollTop(this.$document.height()); + const $document = $(document); + $document.scrollTop($document.height()); } scrollToBottom() { @@ -148,7 +153,7 @@ export default class Job { } scrollToTop() { - this.$document.scrollTop(0); + $(document).scrollTop(0); this.hasBeenScrolled = true; this.toggleScroll(); } @@ -193,7 +198,7 @@ export default class Job { // we need to show a message warning the user about that. if (this.logBytes < log.total) { // size is in bytes, we need to calculate KiB - const size = bytesToKiB(this.logBytes); + const size = numberToHumanSize(this.logBytes); $('.js-truncated-info-size').html(`${size}`); this.$truncatedInfo.removeClass('hidden'); } else { diff --git a/app/assets/javascripts/lib/utils/axios_utils.js b/app/assets/javascripts/lib/utils/axios_utils.js index 7aeeca3b283..8aff0556011 100644 --- a/app/assets/javascripts/lib/utils/axios_utils.js +++ b/app/assets/javascripts/lib/utils/axios_utils.js @@ -2,6 +2,8 @@ import axios from 'axios'; import csrf from './csrf'; axios.defaults.headers.common[csrf.headerKey] = csrf.token; +// Used by Rails to check if it is a valid XHR request +axios.defaults.headers.common['X-Requested-With'] = 'XMLHttpRequest'; // Maintain a global counter for active requests // see: spec/support/wait_for_requests.rb diff --git a/app/assets/javascripts/lib/utils/common_utils.js b/app/assets/javascripts/lib/utils/common_utils.js index b5328c77b25..03918762842 100644 --- a/app/assets/javascripts/lib/utils/common_utils.js +++ b/app/assets/javascripts/lib/utils/common_utils.js @@ -232,7 +232,7 @@ export const nodeMatchesSelector = (node, selector) => { export const normalizeHeaders = (headers) => { const upperCaseHeaders = {}; - Object.keys(headers).forEach((e) => { + Object.keys(headers || {}).forEach((e) => { upperCaseHeaders[e.toUpperCase()] = headers[e]; }); diff --git a/app/assets/javascripts/lib/utils/url_utility.js b/app/assets/javascripts/lib/utils/url_utility.js index f1ee9c8f2e5..a266bb6771f 100644 --- a/app/assets/javascripts/lib/utils/url_utility.js +++ b/app/assets/javascripts/lib/utils/url_utility.js @@ -18,7 +18,7 @@ export function getParameterValues(sParam) { // @param {String} url export function mergeUrlParams(params, url) { let newUrl = Object.keys(params).reduce((acc, paramName) => { - const paramValue = params[paramName]; + const paramValue = encodeURIComponent(params[paramName]); const pattern = new RegExp(`\\b(${paramName}=).*?(&|$)`); if (paramValue === null) { diff --git a/app/assets/javascripts/milestone_select.js b/app/assets/javascripts/milestone_select.js index 2e5e818d61d..0e854295fe3 100644 --- a/app/assets/javascripts/milestone_select.js +++ b/app/assets/javascripts/milestone_select.js @@ -1,237 +1,228 @@ -/* eslint-disable func-names, space-before-function-paren, wrap-iife, no-var, no-underscore-dangle, prefer-arrow-callback, max-len, one-var, one-var-declaration-per-line, no-unused-vars, object-shorthand, comma-dangle, no-else-return, no-self-compare, consistent-return, no-param-reassign, no-shadow */ +/* eslint-disable func-names, space-before-function-paren, wrap-iife, no-underscore-dangle, prefer-arrow-callback, max-len, one-var, one-var-declaration-per-line, no-unused-vars, object-shorthand, comma-dangle, no-else-return, no-self-compare, consistent-return, no-param-reassign, no-shadow */ /* global Issuable */ /* global ListMilestone */ import _ from 'underscore'; import { timeFor } from './lib/utils/datetime_utility'; -(function() { - this.MilestoneSelect = (function() { - function MilestoneSelect(currentProject, els, options = {}) { - var _this, $els; - if (currentProject != null) { - _this = this; - this.currentProject = typeof currentProject === 'string' ? JSON.parse(currentProject) : currentProject; - } +export default class MilestoneSelect { + constructor(currentProject, els, options = {}) { + if (currentProject !== null) { + this.currentProject = typeof currentProject === 'string' ? JSON.parse(currentProject) : currentProject; + } - $els = $(els); + this.init(els, options); + } - if (!els) { - $els = $('.js-milestone-select'); - } + init(els, options) { + let $els = $(els); - $els.each(function(i, dropdown) { - var $block, $dropdown, $loading, $selectbox, $sidebarCollapsedValue, $value, abilityName, collapsedSidebarLabelTemplate, defaultLabel, defaultNo, issuableId, issueUpdateURL, milestoneLinkNoneTemplate, milestoneLinkTemplate, milestonesUrl, projectId, selectedMilestone, selectedMilestoneDefault, showAny, showNo, showUpcoming, showStarted, useId, showMenuAbove; - $dropdown = $(dropdown); - projectId = $dropdown.data('project-id'); - milestonesUrl = $dropdown.data('milestones'); - issueUpdateURL = $dropdown.data('issueUpdate'); - showNo = $dropdown.data('show-no'); - showAny = $dropdown.data('show-any'); - showMenuAbove = $dropdown.data('showMenuAbove'); - showUpcoming = $dropdown.data('show-upcoming'); - showStarted = $dropdown.data('show-started'); - useId = $dropdown.data('use-id'); - defaultLabel = $dropdown.data('default-label'); - defaultNo = $dropdown.data('default-no'); - issuableId = $dropdown.data('issuable-id'); - abilityName = $dropdown.data('ability-name'); - $selectbox = $dropdown.closest('.selectbox'); - $block = $selectbox.closest('.block'); - $sidebarCollapsedValue = $block.find('.sidebar-collapsed-icon'); - $value = $block.find('.value'); - $loading = $block.find('.block-loading').fadeOut(); - selectedMilestoneDefault = (showAny ? '' : null); - selectedMilestoneDefault = (showNo && defaultNo ? 'No Milestone' : selectedMilestoneDefault); - selectedMilestone = $dropdown.data('selected') || selectedMilestoneDefault; - if (issueUpdateURL) { - milestoneLinkTemplate = _.template('<a href="/<%- full_path %>/milestones/<%- iid %>" class="bold has-tooltip" data-container="body" title="<%- remaining %>"><%- title %></a>'); - milestoneLinkNoneTemplate = '<span class="no-value">None</span>'; - collapsedSidebarLabelTemplate = _.template('<span class="has-tooltip" data-container="body" title="<%- name %><br /><%- remaining %>" data-placement="left" data-html="true"> <%- title %> </span>'); - } - return $dropdown.glDropdown({ - showMenuAbove: showMenuAbove, - data: function(term, callback) { - return $.ajax({ - url: milestonesUrl - }).done(function(data) { - var extraOptions = []; - if (showAny) { - extraOptions.push({ - id: 0, - name: '', - title: 'Any Milestone' - }); - } - if (showNo) { - extraOptions.push({ - id: -1, - name: 'No Milestone', - title: 'No Milestone' - }); - } - if (showUpcoming) { - extraOptions.push({ - id: -2, - name: '#upcoming', - title: 'Upcoming' - }); - } - if (showStarted) { - extraOptions.push({ - id: -3, - name: '#started', - title: 'Started' - }); - } - if (extraOptions.length) { - extraOptions.push('divider'); - } + if (!els) { + $els = $('.js-milestone-select'); + } - callback(extraOptions.concat(data)); - if (showMenuAbove) { - $dropdown.data('glDropdown').positionMenuAbove(); - } - $(`[data-milestone-id="${selectedMilestone}"] > a`).addClass('is-active'); - }); - }, - renderRow: function(milestone) { - return ` - <li data-milestone-id="${milestone.name}"> - <a href='#' class='dropdown-menu-milestone-link'> - ${_.escape(milestone.title)} - </a> - </li> - `; - }, - filterable: true, - search: { - fields: ['title'] - }, - selectable: true, - toggleLabel: function(selected, el, e) { - if (selected && 'id' in selected && $(el).hasClass('is-active')) { - return selected.title; - } else { - return defaultLabel; - } - }, - defaultLabel: defaultLabel, - fieldName: $dropdown.data('field-name'), - text: function(milestone) { - return _.escape(milestone.title); - }, - id: function(milestone) { - if (!useId && !$dropdown.is('.js-issuable-form-dropdown')) { - return milestone.name; - } else { - return milestone.id; - } - }, - isSelected: function(milestone) { - return milestone.name === selectedMilestone; - }, - hidden: function() { - $selectbox.hide(); - // display:block overrides the hide-collapse rule - return $value.css('display', ''); - }, - opened: function(e) { - const $el = $(e.currentTarget); - if ($dropdown.hasClass('js-issue-board-sidebar') || options.handleClick) { - selectedMilestone = $dropdown[0].dataset.selected || selectedMilestoneDefault; - } - $('a.is-active', $el).removeClass('is-active'); - $(`[data-milestone-id="${selectedMilestone}"] > a`, $el).addClass('is-active'); - }, - vue: $dropdown.hasClass('js-issue-board-sidebar'), - clicked: function(clickEvent) { - const { $el, e } = clickEvent; - let selected = clickEvent.selectedObj; + $els.each((i, dropdown) => { + let collapsedSidebarLabelTemplate, milestoneLinkNoneTemplate, milestoneLinkTemplate, selectedMilestone, selectedMilestoneDefault; + const $dropdown = $(dropdown); + const projectId = $dropdown.data('project-id'); + const milestonesUrl = $dropdown.data('milestones'); + const issueUpdateURL = $dropdown.data('issueUpdate'); + const showNo = $dropdown.data('show-no'); + const showAny = $dropdown.data('show-any'); + const showMenuAbove = $dropdown.data('showMenuAbove'); + const showUpcoming = $dropdown.data('show-upcoming'); + const showStarted = $dropdown.data('show-started'); + const useId = $dropdown.data('use-id'); + const defaultLabel = $dropdown.data('default-label'); + const defaultNo = $dropdown.data('default-no'); + const issuableId = $dropdown.data('issuable-id'); + const abilityName = $dropdown.data('ability-name'); + const $selectBox = $dropdown.closest('.selectbox'); + const $block = $selectBox.closest('.block'); + const $sidebarCollapsedValue = $block.find('.sidebar-collapsed-icon'); + const $value = $block.find('.value'); + const $loading = $block.find('.block-loading').fadeOut(); + selectedMilestoneDefault = (showAny ? '' : null); + selectedMilestoneDefault = (showNo && defaultNo ? 'No Milestone' : selectedMilestoneDefault); + selectedMilestone = $dropdown.data('selected') || selectedMilestoneDefault; - var data, isIssueIndex, isMRIndex, isSelecting, page, boardsStore; - if (!selected) return; + if (issueUpdateURL) { + milestoneLinkTemplate = _.template('<a href="/<%- full_path %>/milestones/<%- iid %>" class="bold has-tooltip" data-container="body" title="<%- remaining %>"><%- title %></a>'); + milestoneLinkNoneTemplate = '<span class="no-value">None</span>'; + collapsedSidebarLabelTemplate = _.template('<span class="has-tooltip" data-container="body" title="<%- name %><br /><%- remaining %>" data-placement="left" data-html="true"> <%- title %> </span>'); + } + return $dropdown.glDropdown({ + showMenuAbove: showMenuAbove, + data: (term, callback) => $.ajax({ + url: milestonesUrl + }).done((data) => { + const extraOptions = []; + if (showAny) { + extraOptions.push({ + id: 0, + name: '', + title: 'Any Milestone' + }); + } + if (showNo) { + extraOptions.push({ + id: -1, + name: 'No Milestone', + title: 'No Milestone' + }); + } + if (showUpcoming) { + extraOptions.push({ + id: -2, + name: '#upcoming', + title: 'Upcoming' + }); + } + if (showStarted) { + extraOptions.push({ + id: -3, + name: '#started', + title: 'Started' + }); + } + if (extraOptions.length) { + extraOptions.push('divider'); + } - if (options.handleClick) { - e.preventDefault(); - options.handleClick(selected); - return; - } + callback(extraOptions.concat(data)); + if (showMenuAbove) { + $dropdown.data('glDropdown').positionMenuAbove(); + } + $(`[data-milestone-id="${selectedMilestone}"] > a`).addClass('is-active'); + }), + renderRow: milestone => ` + <li data-milestone-id="${milestone.name}"> + <a href='#' class='dropdown-menu-milestone-link'> + ${_.escape(milestone.title)} + </a> + </li> + `, + filterable: true, + search: { + fields: ['title'] + }, + selectable: true, + toggleLabel: (selected, el, e) => { + if (selected && 'id' in selected && $(el).hasClass('is-active')) { + return selected.title; + } else { + return defaultLabel; + } + }, + defaultLabel: defaultLabel, + fieldName: $dropdown.data('field-name'), + text: milestone => _.escape(milestone.title), + id: (milestone) => { + if (!useId && !$dropdown.is('.js-issuable-form-dropdown')) { + return milestone.name; + } else { + return milestone.id; + } + }, + isSelected: milestone => milestone.name === selectedMilestone, + hidden: () => { + $selectBox.hide(); + // display:block overrides the hide-collapse rule + return $value.css('display', ''); + }, + opened: (e) => { + const $el = $(e.currentTarget); + if ($dropdown.hasClass('js-issue-board-sidebar') || options.handleClick) { + selectedMilestone = $dropdown[0].dataset.selected || selectedMilestoneDefault; + } + $('a.is-active', $el).removeClass('is-active'); + $(`[data-milestone-id="${selectedMilestone}"] > a`, $el).addClass('is-active'); + }, + vue: $dropdown.hasClass('js-issue-board-sidebar'), + clicked: (clickEvent) => { + const { $el, e } = clickEvent; + let selected = clickEvent.selectedObj; - page = $('body').attr('data-page'); - isIssueIndex = page === 'projects:issues:index'; - isMRIndex = (page === page && page === 'projects:merge_requests:index'); - isSelecting = (selected.name !== selectedMilestone); - selectedMilestone = isSelecting ? selected.name : selectedMilestoneDefault; - if ($dropdown.hasClass('js-filter-bulk-update') || $dropdown.hasClass('js-issuable-form-dropdown')) { - e.preventDefault(); - return; - } + let data, boardsStore; + if (!selected) return; - if ($dropdown.closest('.add-issues-modal').length) { - boardsStore = gl.issueBoards.ModalStore.store.filter; - } + if (options.handleClick) { + e.preventDefault(); + options.handleClick(selected); + return; + } - if (boardsStore) { - boardsStore[$dropdown.data('field-name')] = selected.name; - e.preventDefault(); - } else if ($dropdown.hasClass('js-filter-submit') && (isIssueIndex || isMRIndex)) { - return Issuable.filterResults($dropdown.closest('form')); - } else if ($dropdown.hasClass('js-filter-submit')) { - return $dropdown.closest('form').submit(); - } else if ($dropdown.hasClass('js-issue-board-sidebar')) { - if (selected.id !== -1 && isSelecting) { - gl.issueBoards.boardStoreIssueSet('milestone', new ListMilestone({ - id: selected.id, - title: selected.name - })); - } else { - gl.issueBoards.boardStoreIssueDelete('milestone'); - } + const page = $('body').attr('data-page'); + const isIssueIndex = page === 'projects:issues:index'; + const isMRIndex = (page === page && page === 'projects:merge_requests:index'); + const isSelecting = (selected.name !== selectedMilestone); + selectedMilestone = isSelecting ? selected.name : selectedMilestoneDefault; + if ($dropdown.hasClass('js-filter-bulk-update') || $dropdown.hasClass('js-issuable-form-dropdown')) { + e.preventDefault(); + return; + } - $dropdown.trigger('loading.gl.dropdown'); - $loading.removeClass('hidden').fadeIn(); + if ($dropdown.closest('.add-issues-modal').length) { + boardsStore = gl.issueBoards.ModalStore.store.filter; + } - gl.issueBoards.BoardsStore.detail.issue.update($dropdown.attr('data-issue-update')) - .then(function () { - $dropdown.trigger('loaded.gl.dropdown'); - $loading.fadeOut(); - }) - .catch(() => { - $loading.fadeOut(); - }); + if (boardsStore) { + boardsStore[$dropdown.data('field-name')] = selected.name; + e.preventDefault(); + } else if ($dropdown.hasClass('js-filter-submit') && (isIssueIndex || isMRIndex)) { + return Issuable.filterResults($dropdown.closest('form')); + } else if ($dropdown.hasClass('js-filter-submit')) { + return $dropdown.closest('form').submit(); + } else if ($dropdown.hasClass('js-issue-board-sidebar')) { + if (selected.id !== -1 && isSelecting) { + gl.issueBoards.boardStoreIssueSet('milestone', new ListMilestone({ + id: selected.id, + title: selected.name + })); } else { - selected = $selectbox.find('input[type="hidden"]').val(); - data = {}; - data[abilityName] = {}; - data[abilityName].milestone_id = selected != null ? selected : null; - $loading.removeClass('hidden').fadeIn(); - $dropdown.trigger('loading.gl.dropdown'); - return $.ajax({ - type: 'PUT', - url: issueUpdateURL, - data: data - }).done(function(data) { + gl.issueBoards.boardStoreIssueDelete('milestone'); + } + + $dropdown.trigger('loading.gl.dropdown'); + $loading.removeClass('hidden').fadeIn(); + + gl.issueBoards.BoardsStore.detail.issue.update($dropdown.attr('data-issue-update')) + .then(() => { $dropdown.trigger('loaded.gl.dropdown'); $loading.fadeOut(); - $selectbox.hide(); - $value.css('display', ''); - if (data.milestone != null) { - data.milestone.full_path = _this.currentProject.full_path; - data.milestone.remaining = timeFor(data.milestone.due_date); - data.milestone.name = data.milestone.title; - $value.html(milestoneLinkTemplate(data.milestone)); - return $sidebarCollapsedValue.find('span').html(collapsedSidebarLabelTemplate(data.milestone)); - } else { - $value.html(milestoneLinkNoneTemplate); - return $sidebarCollapsedValue.find('span').text('No'); - } + }) + .catch(() => { + $loading.fadeOut(); }); - } + } else { + selected = $selectBox.find('input[type="hidden"]').val(); + data = {}; + data[abilityName] = {}; + data[abilityName].milestone_id = selected != null ? selected : null; + $loading.removeClass('hidden').fadeIn(); + $dropdown.trigger('loading.gl.dropdown'); + return $.ajax({ + type: 'PUT', + url: issueUpdateURL, + data: data + }).done((data) => { + $dropdown.trigger('loaded.gl.dropdown'); + $loading.fadeOut(); + $selectBox.hide(); + $value.css('display', ''); + if (data.milestone != null) { + data.milestone.full_path = this.currentProject.full_path; + data.milestone.remaining = timeFor(data.milestone.due_date); + data.milestone.name = data.milestone.title; + $value.html(milestoneLinkTemplate(data.milestone)); + return $sidebarCollapsedValue.find('span').html(collapsedSidebarLabelTemplate(data.milestone)); + } else { + $value.html(milestoneLinkNoneTemplate); + return $sidebarCollapsedValue.find('span').text('No'); + } + }); } - }); + } }); - } - - return MilestoneSelect; - })(); -}).call(window); + }); + } +} diff --git a/app/assets/javascripts/monitoring/utils/date_time_formatters.js b/app/assets/javascripts/monitoring/utils/date_time_formatters.js index 48bdec1e030..068813ddee6 100644 --- a/app/assets/javascripts/monitoring/utils/date_time_formatters.js +++ b/app/assets/javascripts/monitoring/utils/date_time_formatters.js @@ -1,8 +1,18 @@ import { timeFormat as time } from 'd3-time-format'; -import { timeSecond, timeMinute, timeHour, timeDay, timeMonth, timeYear } from 'd3-time'; +import { timeSecond, timeMinute, timeHour, timeDay, timeWeek, timeMonth, timeYear } from 'd3-time'; import { bisector } from 'd3-array'; -const d3 = { time, bisector, timeSecond, timeMinute, timeHour, timeDay, timeMonth, timeYear }; +const d3 = { + time, + bisector, + timeSecond, + timeMinute, + timeHour, + timeDay, + timeWeek, + timeMonth, + timeYear, +}; export const dateFormat = d3.time('%b %-d, %Y'); export const timeFormat = d3.time('%-I:%M%p'); diff --git a/app/assets/javascripts/profile/profile.js b/app/assets/javascripts/profile/profile.js index 0dc02f012e4..ba4ac850346 100644 --- a/app/assets/javascripts/profile/profile.js +++ b/app/assets/javascripts/profile/profile.js @@ -1,4 +1,5 @@ /* eslint-disable comma-dangle, no-unused-vars, class-methods-use-this, quotes, consistent-return, func-names, prefer-arrow-callback, space-before-function-paren, max-len */ +import Cookies from 'js-cookie'; import Flash from '../flash'; import { getPagePath } from '../lib/utils/common_utils'; @@ -7,6 +8,8 @@ import { getPagePath } from '../lib/utils/common_utils'; constructor({ form } = {}) { this.onSubmitForm = this.onSubmitForm.bind(this); this.form = form || $('.edit-user'); + this.newRepoActivated = Cookies.get('new_repo'); + this.setRepoRadio(); this.bindEvents(); this.initAvatarGlCrop(); } @@ -25,6 +28,7 @@ import { getPagePath } from '../lib/utils/common_utils'; bindEvents() { $('.js-preferences-form').on('change.preference', 'input[type=radio]', this.submitForm); + $('input[name="user[multi_file]"]').on('change', this.setNewRepoCookie); $('#user_notification_email').on('change', this.submitForm); $('#user_notified_of_own_activity').on('change', this.submitForm); $('.update-username').on('ajax:before', this.beforeUpdateUsername); @@ -82,6 +86,23 @@ import { getPagePath } from '../lib/utils/common_utils'; } }); } + + setNewRepoCookie() { + if (this.value === 'off') { + Cookies.remove('new_repo'); + } else { + Cookies.set('new_repo', true, { expires_in: 365 }); + } + } + + setRepoRadio() { + const multiEditRadios = $('input[name="user[multi_file]"]'); + if (this.newRepoActivated || this.newRepoActivated === 'true') { + multiEditRadios.filter('[value=on]').prop('checked', true); + } else { + multiEditRadios.filter('[value=off]').prop('checked', true); + } + } } $(function() { diff --git a/app/assets/javascripts/vue_merge_request_widget/components/mr_widget_deployment.js b/app/assets/javascripts/vue_merge_request_widget/components/mr_widget_deployment.js index ee1a45cc754..d48f3a01420 100644 --- a/app/assets/javascripts/vue_merge_request_widget/components/mr_widget_deployment.js +++ b/app/assets/javascripts/vue_merge_request_widget/components/mr_widget_deployment.js @@ -34,10 +34,10 @@ export default { if (isConfirmed) { MRWidgetService.stopEnvironment(deployment.stop_url) - .then(res => res.json()) - .then((res) => { - if (res.redirect_url) { - visitUrl(res.redirect_url); + .then(res => res.data) + .then((data) => { + if (data.redirect_url) { + visitUrl(data.redirect_url); } }) .catch(() => { diff --git a/app/assets/javascripts/vue_merge_request_widget/components/mr_widget_memory_usage.js b/app/assets/javascripts/vue_merge_request_widget/components/mr_widget_memory_usage.js index a8c686e5065..69e70ba1dd6 100644 --- a/app/assets/javascripts/vue_merge_request_widget/components/mr_widget_memory_usage.js +++ b/app/assets/javascripts/vue_merge_request_widget/components/mr_widget_memory_usage.js @@ -102,11 +102,11 @@ export default { return res; } - return res.json(); + return res.data; }) - .then((res) => { - this.computeGraphData(res.metrics, res.deployment_time); - return res; + .then((data) => { + this.computeGraphData(data.metrics, data.deployment_time); + return data; }) .catch(() => { this.loadFailed = true; diff --git a/app/assets/javascripts/vue_merge_request_widget/components/states/mr_widget_closed.js b/app/assets/javascripts/vue_merge_request_widget/components/states/mr_widget_closed.js index b25cc3443ef..dd8b2665b1d 100644 --- a/app/assets/javascripts/vue_merge_request_widget/components/states/mr_widget_closed.js +++ b/app/assets/javascripts/vue_merge_request_widget/components/states/mr_widget_closed.js @@ -16,9 +16,9 @@ export default { <div class="media-body"> <mr-widget-author-and-time actionText="Closed by" - :author="mr.closedEvent.author" - :dateTitle="mr.closedEvent.updatedAt" - :dateReadable="mr.closedEvent.formattedUpdatedAt" + :author="mr.metrics.closedBy" + :dateTitle="mr.metrics.closedAt" + :dateReadable="mr.metrics.readableClosedAt" /> <section class="mr-info-list"> <p> diff --git a/app/assets/javascripts/vue_merge_request_widget/components/states/mr_widget_merge_when_pipeline_succeeds.js b/app/assets/javascripts/vue_merge_request_widget/components/states/mr_widget_merge_when_pipeline_succeeds.js index 43b2d238f65..bd349111bbd 100644 --- a/app/assets/javascripts/vue_merge_request_widget/components/states/mr_widget_merge_when_pipeline_succeeds.js +++ b/app/assets/javascripts/vue_merge_request_widget/components/states/mr_widget_merge_when_pipeline_succeeds.js @@ -31,9 +31,9 @@ export default { cancelAutomaticMerge() { this.isCancellingAutoMerge = true; this.service.cancelAutomaticMerge() - .then(res => res.json()) - .then((res) => { - eventHub.$emit('UpdateWidgetData', res); + .then(res => res.data) + .then((data) => { + eventHub.$emit('UpdateWidgetData', data); }) .catch(() => { this.isCancellingAutoMerge = false; @@ -49,9 +49,9 @@ export default { this.isRemovingSourceBranch = true; this.service.mergeResource.save(options) - .then(res => res.json()) - .then((res) => { - if (res.status === 'merge_when_pipeline_succeeds') { + .then(res => res.data) + .then((data) => { + if (data.status === 'merge_when_pipeline_succeeds') { eventHub.$emit('MRWidgetUpdateRequested'); } }) diff --git a/app/assets/javascripts/vue_merge_request_widget/components/states/mr_widget_merged.js b/app/assets/javascripts/vue_merge_request_widget/components/states/mr_widget_merged.js index 2dfd87ed904..ba9681680ef 100644 --- a/app/assets/javascripts/vue_merge_request_widget/components/states/mr_widget_merged.js +++ b/app/assets/javascripts/vue_merge_request_widget/components/states/mr_widget_merged.js @@ -47,9 +47,9 @@ export default { removeSourceBranch() { this.isMakingRequest = true; this.service.removeSourceBranch() - .then(res => res.json()) - .then((res) => { - if (res.message === 'Branch was removed') { + .then(res => res.data) + .then((data) => { + if (data.message === 'Branch was removed') { eventHub.$emit('MRWidgetUpdateRequested', () => { this.isMakingRequest = false; }); @@ -68,9 +68,9 @@ export default { <div class="space-children"> <mr-widget-author-and-time actionText="Merged by" - :author="mr.mergedEvent.author" - :date-title="mr.mergedEvent.updatedAt" - :date-readable="mr.mergedEvent.formattedUpdatedAt" /> + :author="mr.metrics.mergedBy" + :date-title="mr.metrics.mergedAt" + :date-readable="mr.metrics.readableMergedAt" /> <a v-if="mr.canRevertInCurrentMR" v-tooltip diff --git a/app/assets/javascripts/vue_merge_request_widget/components/states/mr_widget_ready_to_merge.js b/app/assets/javascripts/vue_merge_request_widget/components/states/mr_widget_ready_to_merge.js index be37dd87de9..e82fb979162 100644 --- a/app/assets/javascripts/vue_merge_request_widget/components/states/mr_widget_ready_to_merge.js +++ b/app/assets/javascripts/vue_merge_request_widget/components/states/mr_widget_ready_to_merge.js @@ -135,16 +135,16 @@ export default { this.isMakingRequest = true; this.service.merge(options) - .then(res => res.json()) - .then((res) => { - const hasError = res.status === 'failed' || res.status === 'hook_validation_error'; + .then(res => res.data) + .then((data) => { + const hasError = data.status === 'failed' || data.status === 'hook_validation_error'; - if (res.status === 'merge_when_pipeline_succeeds') { + if (data.status === 'merge_when_pipeline_succeeds') { eventHub.$emit('MRWidgetUpdateRequested'); - } else if (res.status === 'success') { + } else if (data.status === 'success') { this.initiateMergePolling(); } else if (hasError) { - eventHub.$emit('FailedToMerge', res.merge_error); + eventHub.$emit('FailedToMerge', data.merge_error); } }) .catch(() => { @@ -159,9 +159,9 @@ export default { }, handleMergePolling(continuePolling, stopPolling) { this.service.poll() - .then(res => res.json()) - .then((res) => { - if (res.state === 'merged') { + .then(res => res.data) + .then((data) => { + if (data.state === 'merged') { // If state is merged we should update the widget and stop the polling eventHub.$emit('MRWidgetUpdateRequested'); eventHub.$emit('FetchActionsContent'); @@ -174,11 +174,11 @@ export default { // If user checked remove source branch and we didn't remove the branch yet // we should start another polling for source branch remove process - if (this.removeSourceBranch && res.source_branch_exists) { + if (this.removeSourceBranch && data.source_branch_exists) { this.initiateRemoveSourceBranchPolling(); } - } else if (res.merge_error) { - eventHub.$emit('FailedToMerge', res.merge_error); + } else if (data.merge_error) { + eventHub.$emit('FailedToMerge', data.merge_error); stopPolling(); } else { // MR is not merged yet, continue polling until the state becomes 'merged' @@ -199,11 +199,11 @@ export default { }, handleRemoveBranchPolling(continuePolling, stopPolling) { this.service.poll() - .then(res => res.json()) - .then((res) => { + .then(res => res.data) + .then((data) => { // If source branch exists then we should continue polling // because removing a source branch is a background task and takes time - if (res.source_branch_exists) { + if (data.source_branch_exists) { continuePolling(); } else { // Branch is removed. Update widget, stop polling and hide the spinner diff --git a/app/assets/javascripts/vue_merge_request_widget/components/states/mr_widget_wip.js b/app/assets/javascripts/vue_merge_request_widget/components/states/mr_widget_wip.js index 4f83350e07c..13461440ef2 100644 --- a/app/assets/javascripts/vue_merge_request_widget/components/states/mr_widget_wip.js +++ b/app/assets/javascripts/vue_merge_request_widget/components/states/mr_widget_wip.js @@ -23,9 +23,9 @@ export default { removeWIP() { this.isMakingRequest = true; this.service.removeWIP() - .then(res => res.json()) - .then((res) => { - eventHub.$emit('UpdateWidgetData', res); + .then(res => res.data) + .then((data) => { + eventHub.$emit('UpdateWidgetData', data); new window.Flash('The merge request can now be merged.', 'notice'); // eslint-disable-line $('.merge-request .detail-page-description .title').text(this.mr.title); }) diff --git a/app/assets/javascripts/vue_merge_request_widget/mr_widget_options.js b/app/assets/javascripts/vue_merge_request_widget/mr_widget_options.js index 8a9129c385b..fdae06200de 100644 --- a/app/assets/javascripts/vue_merge_request_widget/mr_widget_options.js +++ b/app/assets/javascripts/vue_merge_request_widget/mr_widget_options.js @@ -84,14 +84,14 @@ export default { }, checkStatus(cb) { return this.service.checkStatus() - .then(res => res.json()) - .then((res) => { - this.handleNotification(res); - this.mr.setData(res); + .then(res => res.data) + .then((data) => { + this.handleNotification(data); + this.mr.setData(data); this.setFaviconHelper(); if (cb) { - cb.call(null, res); + cb.call(null, data); } }) .catch(() => { @@ -124,10 +124,10 @@ export default { }, fetchDeployments() { return this.service.fetchDeployments() - .then(res => res.json()) - .then((res) => { - if (res.length) { - this.mr.deployments = res; + .then(res => res.data) + .then((data) => { + if (data.length) { + this.mr.deployments = data; } }) .catch(() => { @@ -137,9 +137,9 @@ export default { fetchActionsContent() { this.service.fetchMergeActionsContent() .then((res) => { - if (res.body) { + if (res.data) { const el = document.createElement('div'); - el.innerHTML = res.body; + el.innerHTML = res.data; document.body.appendChild(el); Project.initRefSwitcher(); } diff --git a/app/assets/javascripts/vue_merge_request_widget/services/mr_widget_service.js b/app/assets/javascripts/vue_merge_request_widget/services/mr_widget_service.js index 5fa838baba3..7c0bbdd403f 100644 --- a/app/assets/javascripts/vue_merge_request_widget/services/mr_widget_service.js +++ b/app/assets/javascripts/vue_merge_request_widget/services/mr_widget_service.js @@ -1,57 +1,47 @@ -import Vue from 'vue'; -import VueResource from 'vue-resource'; - -Vue.use(VueResource); +import axios from '../../lib/utils/axios_utils'; export default class MRWidgetService { constructor(endpoints) { - this.mergeResource = Vue.resource(endpoints.mergePath); - this.mergeCheckResource = Vue.resource(`${endpoints.statusPath}?serializer=widget`); - this.cancelAutoMergeResource = Vue.resource(endpoints.cancelAutoMergePath); - this.removeWIPResource = Vue.resource(endpoints.removeWIPPath); - this.removeSourceBranchResource = Vue.resource(endpoints.sourceBranchPath); - this.deploymentsResource = Vue.resource(endpoints.ciEnvironmentsStatusPath); - this.pollResource = Vue.resource(`${endpoints.statusPath}?serializer=basic`); - this.mergeActionsContentResource = Vue.resource(endpoints.mergeActionsContentPath); + this.endpoints = endpoints; } merge(data) { - return this.mergeResource.save(data); + return axios.post(this.endpoints.mergePath, data); } cancelAutomaticMerge() { - return this.cancelAutoMergeResource.save(); + return axios.post(this.endpoints.cancelAutoMergePath); } removeWIP() { - return this.removeWIPResource.save(); + return axios.post(this.endpoints.removeWIPPath); } removeSourceBranch() { - return this.removeSourceBranchResource.delete(); + return axios.delete(this.endpoints.sourceBranchPath); } fetchDeployments() { - return this.deploymentsResource.get(); + return axios.get(this.endpoints.ciEnvironmentsStatusPath); } poll() { - return this.pollResource.get(); + return axios.get(`${this.endpoints.statusPath}?serializer=basic`); } checkStatus() { - return this.mergeCheckResource.get(); + return axios.get(`${this.endpoints.statusPath}?serializer=widget`); } fetchMergeActionsContent() { - return this.mergeActionsContentResource.get(); + return axios.get(this.endpoints.mergeActionsContentPath); } static stopEnvironment(url) { - return Vue.http.post(url); + return axios.post(url); } static fetchMetrics(metricsUrl) { - return Vue.http.get(`${metricsUrl}.json`); + return axios.get(`${metricsUrl}.json`); } } diff --git a/app/assets/javascripts/vue_merge_request_widget/stores/mr_widget_store.js b/app/assets/javascripts/vue_merge_request_widget/stores/mr_widget_store.js index 93d31a2a684..474c17ec133 100644 --- a/app/assets/javascripts/vue_merge_request_widget/stores/mr_widget_store.js +++ b/app/assets/javascripts/vue_merge_request_widget/stores/mr_widget_store.js @@ -39,9 +39,8 @@ export default class MergeRequestStore { } this.updatedAt = data.updated_at; - this.mergedEvent = MergeRequestStore.getEventObject(data.merge_event); - this.closedEvent = MergeRequestStore.getEventObject(data.closed_event); - this.setToMWPSBy = MergeRequestStore.getAuthorObject({ author: data.merge_user || {} }); + this.metrics = MergeRequestStore.buildMetrics(data.metrics); + this.setToMWPSBy = MergeRequestStore.formatUserObject(data.merge_user || {}); this.mergeUserId = data.merge_user_id; this.currentUserId = gon.current_user_id; this.sourceBranchPath = data.source_branch_path; @@ -125,43 +124,42 @@ export default class MergeRequestStore { return this.state === stateKey.nothingToMerge; } - static getEventObject(event) { + static buildMetrics(metrics) { + if (!metrics) { + return {}; + } + return { - author: MergeRequestStore.getAuthorObject(event), - updatedAt: formatDate(MergeRequestStore.getEventUpdatedAtDate(event)), - formattedUpdatedAt: MergeRequestStore.getEventDate(event), + mergedBy: MergeRequestStore.formatUserObject(metrics.merged_by), + closedBy: MergeRequestStore.formatUserObject(metrics.closed_by), + mergedAt: formatDate(metrics.merged_at), + closedAt: formatDate(metrics.closed_at), + readableMergedAt: MergeRequestStore.getReadableDate(metrics.merged_at), + readableClosedAt: MergeRequestStore.getReadableDate(metrics.closed_at), }; } - static getAuthorObject(event) { - if (!event) { + static formatUserObject(user) { + if (!user) { return {}; } return { - name: event.author.name || '', - username: event.author.username || '', - webUrl: event.author.web_url || '', - avatarUrl: event.author.avatar_url || '', + name: user.name || '', + username: user.username || '', + webUrl: user.web_url || '', + avatarUrl: user.avatar_url || '', }; } - static getEventUpdatedAtDate(event) { - if (!event) { + static getReadableDate(date) { + if (!date) { return ''; } - return event.updated_at; - } - - static getEventDate(event) { const timeagoInstance = new Timeago(); - if (!event) { - return ''; - } - - return timeagoInstance.format(MergeRequestStore.getEventUpdatedAtDate(event)); + return timeagoInstance.format(date); } } diff --git a/app/assets/javascripts/vue_shared/components/file_icon.vue b/app/assets/javascripts/vue_shared/components/file_icon.vue new file mode 100644 index 00000000000..65c64967fdc --- /dev/null +++ b/app/assets/javascripts/vue_shared/components/file_icon.vue @@ -0,0 +1,92 @@ +<script> + import getIconForFile from './file_icon/file_icon_map'; + import loadingIcon from '../../vue_shared/components/loading_icon.vue'; + import icon from '../../vue_shared/components/icon.vue'; + + /* This is a re-usable vue component for rendering a svg sprite + icon + + Sample configuration: + + <file-icon + name="retry" + :size="32" + css-classes="top" + /> + + */ + export default { + props: { + fileName: { + type: String, + required: true, + }, + + folder: { + type: Boolean, + required: false, + default: false, + }, + + opened: { + type: Boolean, + required: false, + default: false, + }, + + loading: { + type: Boolean, + required: false, + default: false, + }, + + size: { + type: Number, + required: false, + default: 16, + }, + + cssClasses: { + type: String, + required: false, + default: '', + }, + }, + components: { + loadingIcon, + icon, + }, + computed: { + spriteHref() { + const iconName = getIconForFile(this.fileName) || 'file'; + return `${gon.sprite_file_icons}#${iconName}`; + }, + folderIconName() { + // We don't have a open folder icon yet + return this.opened ? 'folder' : 'folder'; + }, + iconSizeClass() { + return this.size ? `s${this.size}` : ''; + }, + }, + }; +</script> +<template> + <span> + <svg + :class="[iconSizeClass, cssClasses]" + v-if="!loading && !folder"> + <use + v-bind="{'xlink:href':spriteHref}"/> + </svg> + <icon + v-if="!loading && folder" + :name="folderIconName" + :size="size" + /> + <loading-icon + v-if="loading" + :inline="true" + /> + </span> +</template> diff --git a/app/assets/javascripts/vue_shared/components/file_icon/file_icon_map.js b/app/assets/javascripts/vue_shared/components/file_icon/file_icon_map.js new file mode 100644 index 00000000000..9ffbaae3ea5 --- /dev/null +++ b/app/assets/javascripts/vue_shared/components/file_icon/file_icon_map.js @@ -0,0 +1,589 @@ +const fileExtensionIcons = { + html: 'html', + htm: 'html', + html_vm: 'html', + asp: 'html', + jade: 'pug', + pug: 'pug', + md: 'markdown', + 'md.rendered': 'markdown', + markdown: 'markdown', + 'markdown.rendered': 'markdown', + rst: 'markdown', + blink: 'blink', + css: 'css', + scss: 'sass', + sass: 'sass', + less: 'less', + json: 'json', + yaml: 'yaml', + 'YAML-tmLanguage': 'yaml', + yml: 'yaml', + xml: 'xml', + plist: 'xml', + xsd: 'xml', + dtd: 'xml', + xsl: 'xml', + xslt: 'xml', + resx: 'xml', + iml: 'xml', + xquery: 'xml', + tmLanguage: 'xml', + manifest: 'xml', + project: 'xml', + png: 'image', + jpeg: 'image', + jpg: 'image', + gif: 'image', + svg: 'image', + ico: 'image', + tif: 'image', + tiff: 'image', + psd: 'image', + psb: 'image', + ami: 'image', + apx: 'image', + bmp: 'image', + bpg: 'image', + brk: 'image', + cur: 'image', + dds: 'image', + dng: 'image', + exr: 'image', + fpx: 'image', + gbr: 'image', + img: 'image', + jbig2: 'image', + jb2: 'image', + jng: 'image', + jxr: 'image', + pbm: 'image', + pgf: 'image', + pic: 'image', + raw: 'image', + webp: 'image', + js: 'javascript', + ejs: 'javascript', + esx: 'javascript', + jsx: 'react', + tsx: 'react', + ini: 'settings', + dlc: 'settings', + dll: 'settings', + config: 'settings', + conf: 'settings', + properties: 'settings', + prop: 'settings', + settings: 'settings', + option: 'settings', + props: 'settings', + toml: 'settings', + prefs: 'settings', + 'sln.dotsettings': 'settings', + 'sln.dotsettings.user': 'settings', + ts: 'typescript', + 'd.ts': 'typescript-def', + marko: 'markojs', + pdf: 'pdf', + xlsx: 'table', + xls: 'table', + csv: 'table', + tsv: 'table', + vscodeignore: 'vscode', + vsixmanifest: 'vscode', + vsix: 'vscode', + 'code-workplace': 'vscode', + suo: 'visualstudio', + sln: 'visualstudio', + csproj: 'visualstudio', + vb: 'visualstudio', + pdb: 'database', + sql: 'database', + pks: 'database', + pkb: 'database', + accdb: 'database', + mdb: 'database', + sqlite: 'database', + cs: 'csharp', + zip: 'zip', + tar: 'zip', + gz: 'zip', + xz: 'zip', + bzip2: 'zip', + gzip: 'zip', + '7z': 'zip', + rar: 'zip', + tgz: 'zip', + exe: 'exe', + msi: 'exe', + java: 'java', + jar: 'java', + jsp: 'java', + c: 'c', + m: 'c', + h: 'h', + cc: 'cpp', + cpp: 'cpp', + mm: 'cpp', + cxx: 'cpp', + hpp: 'hpp', + go: 'go', + py: 'python', + url: 'url', + sh: 'console', + ksh: 'console', + csh: 'console', + tcsh: 'console', + zsh: 'console', + bash: 'console', + bat: 'console', + cmd: 'console', + ps1: 'powershell', + psm1: 'powershell', + psd1: 'powershell', + ps1xml: 'powershell', + psc1: 'powershell', + pssc: 'powershell', + gradle: 'gradle', + doc: 'word', + docx: 'word', + rtf: 'word', + cer: 'certificate', + cert: 'certificate', + crt: 'certificate', + pub: 'key', + key: 'key', + pem: 'key', + asc: 'key', + gpg: 'key', + woff: 'font', + woff2: 'font', + ttf: 'font', + eot: 'font', + suit: 'font', + otf: 'font', + bmap: 'font', + fnt: 'font', + odttf: 'font', + ttc: 'font', + font: 'font', + fonts: 'font', + sui: 'font', + ntf: 'font', + mrf: 'font', + lib: 'lib', + bib: 'lib', + rb: 'ruby', + erb: 'ruby', + fs: 'fsharp', + fsx: 'fsharp', + fsi: 'fsharp', + fsproj: 'fsharp', + swift: 'swift', + ino: 'arduino', + dockerignore: 'docker', + dockerfile: 'docker', + tex: 'tex', + cls: 'tex', + sty: 'tex', + pptx: 'powerpoint', + ppt: 'powerpoint', + pptm: 'powerpoint', + potx: 'powerpoint', + pot: 'powerpoint', + potm: 'powerpoint', + ppsx: 'powerpoint', + ppsm: 'powerpoint', + pps: 'powerpoint', + ppam: 'powerpoint', + ppa: 'powerpoint', + webm: 'movie', + mkv: 'movie', + flv: 'movie', + vob: 'movie', + ogv: 'movie', + ogg: 'movie', + gifv: 'movie', + avi: 'movie', + mov: 'movie', + qt: 'movie', + wmv: 'movie', + yuv: 'movie', + rm: 'movie', + rmvb: 'movie', + mp4: 'movie', + m4v: 'movie', + mpg: 'movie', + mp2: 'movie', + mpeg: 'movie', + mpe: 'movie', + mpv: 'movie', + m2v: 'movie', + vdi: 'virtual', + vbox: 'virtual', + 'vbox-prev': 'virtual', + ics: 'email', + mp3: 'music', + flac: 'music', + m4a: 'music', + wma: 'music', + aiff: 'music', + coffee: 'coffee', + txt: 'document', + graphql: 'graphql', + rs: 'rust', + raml: 'raml', + xaml: 'xaml', + hs: 'haskell', + kt: 'kotlin', + kts: 'kotlin', + patch: 'git', + lua: 'lua', + clj: 'clojure', + cljs: 'clojure', + groovy: 'groovy', + r: 'r', + rmd: 'r', + dart: 'dart', + as: 'actionscript', + mxml: 'mxml', + ahk: 'autohotkey', + swf: 'flash', + swc: 'swc', + cmake: 'cmake', + asm: 'assembly', + a51: 'assembly', + inc: 'assembly', + nasm: 'assembly', + s: 'assembly', + ms: 'assembly', + agc: 'assembly', + ags: 'assembly', + aea: 'assembly', + argus: 'assembly', + mitigus: 'assembly', + binsource: 'assembly', + vue: 'vue', + ml: 'ocaml', + mli: 'ocaml', + cmx: 'ocaml', + 'js.map': 'javascript-map', + 'css.map': 'css-map', + lock: 'lock', + hbs: 'handlebars', + mustache: 'handlebars', + pl: 'perl', + pm: 'perl', + hx: 'haxe', + 'spec.ts': 'test-ts', + 'test.ts': 'test-ts', + 'ts.snap': 'test-ts', + 'spec.tsx': 'test-jsx', + 'test.tsx': 'test-jsx', + 'tsx.snap': 'test-jsx', + 'spec.jsx': 'test-jsx', + 'test.jsx': 'test-jsx', + 'jsx.snap': 'test-jsx', + 'spec.js': 'test-js', + 'test.js': 'test-js', + 'js.snap': 'test-js', + 'routing.ts': 'angular-routing', + 'routing.js': 'angular-routing', + 'module.ts': 'angular', + 'module.js': 'angular', + 'ng-template': 'angular', + 'component.ts': 'angular-component', + 'component.js': 'angular-component', + 'guard.ts': 'angular-guard', + 'guard.js': 'angular-guard', + 'service.ts': 'angular-service', + 'service.js': 'angular-service', + 'pipe.ts': 'angular-pipe', + 'pipe.js': 'angular-pipe', + 'filter.js': 'angular-pipe', + 'directive.ts': 'angular-directive', + 'directive.js': 'angular-directive', + 'resolver.ts': 'angular-resolver', + 'resolver.js': 'angular-resolver', + pp: 'puppet', + ex: 'elixir', + exs: 'elixir', + ls: 'livescript', + erl: 'erlang', + twig: 'twig', + jl: 'julia', + elm: 'elm', + pure: 'purescript', + tpl: 'smarty', + styl: 'stylus', + re: 'reason', + rei: 'reason', + cmj: 'bucklescript', + merlin: 'merlin', + v: 'verilog', + vhd: 'verilog', + sv: 'verilog', + svh: 'verilog', + nb: 'mathematica', + wl: 'wolframlanguage', + wls: 'wolframlanguage', + njk: 'nunjucks', + nunjucks: 'nunjucks', + robot: 'robot', + sol: 'solidity', + au3: 'autoit', + haml: 'haml', + yang: 'yang', + tf: 'terraform', + 'tf.json': 'terraform', + tfvars: 'terraform', + tfstate: 'terraform', + 'blade.php': 'laravel', + 'inky.php': 'laravel', + applescript: 'applescript', + cake: 'cake', + feature: 'cucumber', + nim: 'nim', + nimble: 'nim', + apib: 'apiblueprint', + apiblueprint: 'apiblueprint', + tag: 'riot', + vfl: 'vfl', + kl: 'kl', + pcss: 'postcss', + sss: 'postcss', + todo: 'todo', + cfml: 'coldfusion', + cfc: 'coldfusion', + lucee: 'coldfusion', + cabal: 'cabal', + nix: 'nix', + slim: 'slim', + http: 'http', + rest: 'http', + rql: 'restql', + restql: 'restql', + kv: 'kivy', + graphcool: 'graphcool', + sbt: 'sbt', + 'reducer.ts': 'ngrx-reducer', + 'rootReducer.ts': 'ngrx-reducer', + 'state.ts': 'ngrx-state', + 'actions.ts': 'ngrx-actions', + 'effects.ts': 'ngrx-effects', + cr: 'crystal', + 'drone.yml': 'drone', + cu: 'cuda', + cuh: 'cuda', + log: 'log', +}; + +const fileNameIcons = { + '.jscsrc': 'json', + '.jshintrc': 'json', + 'tsconfig.json': 'json', + 'tslint.json': 'json', + 'composer.lock': 'json', + '.jsbeautifyrc': 'json', + '.esformatter': 'json', + 'cdp.pid': 'json', + '.htaccess': 'xml', + '.jshintignore': 'settings', + '.buildignore': 'settings', + makefile: 'settings', + '.mrconfig': 'settings', + '.yardopts': 'settings', + 'gradle.properties': 'gradle', + gradlew: 'gradle', + 'gradle-wrapper.properties': 'gradle', + license: 'certificate', + 'license.md': 'certificate', + 'license.md.rendered': 'certificate', + 'license.txt': 'certificate', + licence: 'certificate', + 'licence.md': 'certificate', + 'licence.md.rendered': 'certificate', + 'licence.txt': 'certificate', + dockerfile: 'docker', + 'docker-compose.yml': 'docker', + '.mailmap': 'email', + '.gitignore': 'git', + '.gitconfig': 'git', + '.gitattributes': 'git', + '.gitmodules': 'git', + '.gitkeep': 'git', + 'git-history': 'git', + '.Rhistory': 'r', + 'cmakelists.txt': 'cmake', + 'cmakecache.txt': 'cmake', + 'angular-cli.json': 'angular', + '.angular-cli.json': 'angular', + '.vfl': 'vfl', + '.kl': 'kl', + 'postcss.config.js': 'postcss', + '.postcssrc.js': 'postcss', + 'project.graphcool': 'graphcool', + 'webpack.js': 'webpack', + 'webpack.ts': 'webpack', + 'webpack.base.js': 'webpack', + 'webpack.base.ts': 'webpack', + 'webpack.config.js': 'webpack', + 'webpack.config.ts': 'webpack', + 'webpack.common.js': 'webpack', + 'webpack.common.ts': 'webpack', + 'webpack.config.common.js': 'webpack', + 'webpack.config.common.ts': 'webpack', + 'webpack.config.common.babel.js': 'webpack', + 'webpack.config.common.babel.ts': 'webpack', + 'webpack.dev.js': 'webpack', + 'webpack.dev.ts': 'webpack', + 'webpack.config.dev.js': 'webpack', + 'webpack.config.dev.ts': 'webpack', + 'webpack.config.dev.babel.js': 'webpack', + 'webpack.config.dev.babel.ts': 'webpack', + 'webpack.prod.js': 'webpack', + 'webpack.prod.ts': 'webpack', + 'webpack.server.js': 'webpack', + 'webpack.server.ts': 'webpack', + 'webpack.client.js': 'webpack', + 'webpack.client.ts': 'webpack', + 'webpack.config.server.js': 'webpack', + 'webpack.config.server.ts': 'webpack', + 'webpack.config.client.js': 'webpack', + 'webpack.config.client.ts': 'webpack', + 'webpack.config.production.babel.js': 'webpack', + 'webpack.config.production.babel.ts': 'webpack', + 'webpack.config.prod.babel.js': 'webpack', + 'webpack.config.prod.babel.ts': 'webpack', + 'webpack.config.prod.js': 'webpack', + 'webpack.config.prod.ts': 'webpack', + 'webpack.config.production.js': 'webpack', + 'webpack.config.production.ts': 'webpack', + 'webpack.config.staging.js': 'webpack', + 'webpack.config.staging.ts': 'webpack', + 'webpack.config.babel.js': 'webpack', + 'webpack.config.babel.ts': 'webpack', + 'webpack.config.base.babel.js': 'webpack', + 'webpack.config.base.babel.ts': 'webpack', + 'webpack.config.base.js': 'webpack', + 'webpack.config.base.ts': 'webpack', + 'webpack.config.staging.babel.js': 'webpack', + 'webpack.config.staging.babel.ts': 'webpack', + 'webpack.config.coffee': 'webpack', + 'webpack.config.test.js': 'webpack', + 'webpack.config.test.ts': 'webpack', + 'webpack.config.vendor.js': 'webpack', + 'webpack.config.vendor.ts': 'webpack', + 'webpack.config.vendor.production.js': 'webpack', + 'webpack.config.vendor.production.ts': 'webpack', + 'webpack.test.js': 'webpack', + 'webpack.test.ts': 'webpack', + 'webpack.dist.js': 'webpack', + 'webpack.dist.ts': 'webpack', + 'webpackfile.js': 'webpack', + 'webpackfile.ts': 'webpack', + 'ionic.config.json': 'ionic', + '.io-config.json': 'ionic', + 'gulpfile.js': 'gulp', + 'gulpfile.ts': 'gulp', + 'gulpfile.babel.js': 'gulp', + 'package.json': 'nodejs', + 'package-lock.json': 'nodejs', + '.nvmrc': 'nodejs', + '.npmignore': 'npm', + '.npmrc': 'npm', + '.yarnrc': 'yarn', + 'yarn.lock': 'yarn', + '.yarnclean': 'yarn', + '.yarn-integrity': 'yarn', + 'yarn-error.log': 'yarn', + 'androidmanifest.xml': 'android', + '.env': 'tune', + '.env.example': 'tune', + '.babelrc': 'babel', + 'contributing.md': 'contributing', + 'contributing.md.rendered': 'contributing', + 'readme.md': 'readme', + 'readme.md.rendered': 'readme', + changelog: 'changelog', + 'changelog.md': 'changelog', + 'changelog.md.rendered': 'changelog', + CREDITS: 'credits', + 'credits.txt': 'credits', + 'credits.md': 'credits', + 'credits.md.rendered': 'credits', + '.flowconfig': 'flow', + 'favicon.ico': 'favicon', + 'karma.conf.js': 'karma', + 'karma.conf.ts': 'karma', + 'karma.conf.coffee': 'karma', + 'karma.config.js': 'karma', + 'karma.config.ts': 'karma', + 'karma-main.js': 'karma', + 'karma-main.ts': 'karma', + '.bithoundrc': 'bithound', + 'appveyor.yml': 'appveyor', + '.travis.yml': 'travis', + 'protractor.conf.js': 'protractor', + 'protractor.conf.ts': 'protractor', + 'protractor.conf.coffee': 'protractor', + 'protractor.config.js': 'protractor', + 'protractor.config.ts': 'protractor', + 'fuse.js': 'fusebox', + procfile: 'heroku', + '.editorconfig': 'editorconfig', + '.gitlab-ci.yml': 'gitlab', + '.bowerrc': 'bower', + 'bower.json': 'bower', + '.eslintrc.js': 'eslint', + '.eslintrc.yaml': 'eslint', + '.eslintrc.yml': 'eslint', + '.eslintrc.json': 'eslint', + '.eslintrc': 'eslint', + '.eslintignore': 'eslint', + 'code_of_conduct.md': 'conduct', + 'code_of_conduct.md.rendered': 'conduct', + '.watchmanconfig': 'watchman', + 'aurelia.json': 'aurelia', + 'mocha.opts': 'mocha', + jenkinsfile: 'jenkins', + 'firebase.json': 'firebase', + '.firebaserc': 'firebase', + 'rollup.config.js': 'rollup', + 'rollup.config.ts': 'rollup', + 'rollup-config.js': 'rollup', + 'rollup-config.ts': 'rollup', + 'rollup.config.prod.js': 'rollup', + 'rollup.config.prod.ts': 'rollup', + 'rollup.config.dev.js': 'rollup', + 'rollup.config.dev.ts': 'rollup', + 'rollup.config.prod.vendor.js': 'rollup', + 'rollup.config.prod.vendor.ts': 'rollup', + '.hhconfig': 'hack', + '.stylelintrc': 'stylelint', + 'stylelint.config.js': 'stylelint', + '.stylelintrc.json': 'stylelint', + '.stylelintrc.yaml': 'stylelint', + '.stylelintrc.yml': 'stylelint', + '.stylelintrc.js': 'stylelint', + '.stylelintignore': 'stylelint', + '.codeclimate.yml': 'code-climate', + '.prettierrc': 'prettier', + 'prettier.config.js': 'prettier', + '.prettierrc.js': 'prettier', + '.prettierrc.json': 'prettier', + '.prettierrc.yaml': 'prettier', + '.prettierrc.yml': 'prettier', + 'nodemon.json': 'nodemon', + '.sonarrc': 'sonar', + browserslist: 'browserlist', + '.browserslistrc': 'browserlist', + '.snyk': 'snyk', + '.drone.yml': 'drone', +}; + +export default function getIconForFile(name) { + return fileNameIcons[name] || + fileExtensionIcons[name ? name.split('.').pop() : ''] || + ''; +} diff --git a/app/assets/javascripts/vue_shared/components/panel_resizer.vue b/app/assets/javascripts/vue_shared/components/panel_resizer.vue new file mode 100644 index 00000000000..4371534d345 --- /dev/null +++ b/app/assets/javascripts/vue_shared/components/panel_resizer.vue @@ -0,0 +1,91 @@ +<script> +export default { + props: { + startSize: { + type: Number, + required: true, + }, + side: { + type: String, + required: true, + }, + minSize: { + type: Number, + required: false, + default: 0, + }, + maxSize: { + type: Number, + required: false, + default: Number.MAX_VALUE, + }, + enabled: { + type: Boolean, + required: false, + default: true, + }, + }, + data() { + return { + size: this.startSize, + }; + }, + computed: { + className() { + return `drag${this.side}`; + }, + cursorStyle() { + if (this.enabled) { + return { cursor: 'ew-resize' }; + } + return {}; + }, + }, + methods: { + resetSize(e) { + e.preventDefault(); + this.size = this.startSize; + this.$emit('update:size', this.size); + }, + startDrag(e) { + if (this.enabled) { + e.preventDefault(); + this.startPos = e.clientX; + this.currentStartSize = this.size; + document.addEventListener('mousemove', this.drag); + document.addEventListener('mouseup', this.endDrag, { once: true }); + this.$emit('resize-start', this.size); + } + }, + drag(e) { + e.preventDefault(); + let moved = e.clientX - this.startPos; + if (this.side === 'left') moved = -moved; + let newSize = this.currentStartSize + moved; + if (newSize < this.minSize) { + newSize = this.minSize; + } else if (newSize > this.maxSize) { + newSize = this.maxSize; + } + this.size = newSize; + + this.$emit('update:size', newSize); + }, + endDrag(e) { + e.preventDefault(); + document.removeEventListener('mousemove', this.drag); + this.$emit('resize-end', this.size); + }, + }, +}; +</script> + +<template> + <div + class="dragHandle" + :class="className" + :style="cursorStyle" + @mousedown="startDrag" + @dblclick="resetSize" + ></div> +</template> diff --git a/app/assets/stylesheets/framework/avatar.scss b/app/assets/stylesheets/framework/avatar.scss index 26db2386879..077d0424093 100644 --- a/app/assets/stylesheets/framework/avatar.scss +++ b/app/assets/stylesheets/framework/avatar.scss @@ -71,7 +71,7 @@ vertical-align: top; &.s16 { font-size: 12px; line-height: 1.33; } - &.s24 { font-size: 14px; line-height: 1.8; } + &.s24 { font-size: 13px; line-height: 1.8; } &.s26 { font-size: 20px; line-height: 1.33; } &.s32 { font-size: 20px; line-height: 30px; } &.s40 { font-size: 16px; line-height: 38px; } diff --git a/app/assets/stylesheets/framework/blocks.scss b/app/assets/stylesheets/framework/blocks.scss index 91976ca1f56..c5c7afe25be 100644 --- a/app/assets/stylesheets/framework/blocks.scss +++ b/app/assets/stylesheets/framework/blocks.scss @@ -10,7 +10,6 @@ color: $gl-text-color; font-weight: $gl-font-weight-normal; font-size: 14px; - line-height: 36px; &.diff-collapsed { padding: 5px; diff --git a/app/assets/stylesheets/framework/contextual-sidebar.scss b/app/assets/stylesheets/framework/contextual-sidebar.scss index 5da06b90113..1acde98c3ae 100644 --- a/app/assets/stylesheets/framework/contextual-sidebar.scss +++ b/app/assets/stylesheets/framework/contextual-sidebar.scss @@ -23,6 +23,7 @@ .context-header { position: relative; margin-right: 2px; + width: $contextual-sidebar-width; a { transition: padding $sidebar-transition-duration; @@ -358,10 +359,6 @@ } .sidebar-top-level-items > li { - &.active a { - padding-left: 12px; - } - .sidebar-sub-level-items { &:not(.flyout-list) { display: none; diff --git a/app/assets/stylesheets/framework/header.scss b/app/assets/stylesheets/framework/header.scss index 29714e348a0..ad160f37641 100644 --- a/app/assets/stylesheets/framework/header.scss +++ b/app/assets/stylesheets/framework/header.scss @@ -516,7 +516,7 @@ .header-user { .dropdown-menu-nav { width: auto; - min-width: 140px; + min-width: 160px; margin-top: 4px; color: $gl-text-color; left: auto; diff --git a/app/assets/stylesheets/framework/lists.scss b/app/assets/stylesheets/framework/lists.scss index f79a71221c4..dcd98cb522f 100644 --- a/app/assets/stylesheets/framework/lists.scss +++ b/app/assets/stylesheets/framework/lists.scss @@ -12,6 +12,7 @@ padding: 10px 15px; min-height: 20px; border-bottom: 1px solid $list-border; + word-wrap: break-word; &::after { content: " "; @@ -125,10 +126,8 @@ ul.content-list { } .description { - p { - @include str-truncated; - margin-bottom: 0; - } + @include str-truncated; + color: $gl-text-color-secondary; } .controls { @@ -314,7 +313,7 @@ ul.indent-list { border: 2px solid $white-normal; &.identicon { - line-height: 30px; + line-height: 15px; } } } @@ -348,14 +347,19 @@ ul.indent-list { .folder-caret { width: 15px; + + svg { + margin-bottom: 2px; + } } .item-type-icon { + margin-top: 2px; width: 20px; } > .group-row:not(.has-children) { - .folder-caret .fa { + .folder-caret { opacity: 0; } } @@ -438,12 +442,61 @@ ul.indent-list { .avatar-container > a { width: 100%; + text-decoration: none; } &.has-more-items { display: block; padding: 20px 10px; } + + .stats { + position: relative; + line-height: 46px; + + > span { + display: inline-flex; + align-items: center; + height: 16px; + min-width: 30px; + } + + > span:last-child { + margin-right: 0; + } + + .stat-value { + margin: 2px 0 0 5px; + } + } + + .controls { + margin-left: 5px; + + > .btn { + margin-right: $btn-xs-side-margin; + } + } + } + + .project-row-contents .stats { + line-height: inherit; + + > span:first-child { + margin-left: 25px; + } + + .item-visibility { + margin-right: 0; + } + + .last-updated { + position: absolute; + right: 12px; + min-width: 250px; + text-align: right; + color: $gl-text-color-secondary; + } } } @@ -455,12 +508,12 @@ ul.indent-list { ul.group-list-tree { li.group-row { - &.has-description .title { - line-height: inherit; + > .group-row-contents .title { + line-height: $list-text-height; } - &:not(.has-description) .title { - line-height: $list-text-height; + &.has-description > .group-row-contents .title { + line-height: inherit; } } } diff --git a/app/assets/stylesheets/framework/typography.scss b/app/assets/stylesheets/framework/typography.scss index 11c1aeea871..d0999e60e65 100644 --- a/app/assets/stylesheets/framework/typography.scss +++ b/app/assets/stylesheets/framework/typography.scss @@ -178,6 +178,10 @@ font-weight: inherit; } + dd { + margin-left: $gl-padding; + } + ul, ol { padding: 0; diff --git a/app/assets/stylesheets/framework/variables.scss b/app/assets/stylesheets/framework/variables.scss index 1d6c7a5c472..f7853909f56 100644 --- a/app/assets/stylesheets/framework/variables.scss +++ b/app/assets/stylesheets/framework/variables.scss @@ -727,3 +727,8 @@ Popup $popup-triangle-size: 15px; $popup-triangle-border-size: 1px; $popup-box-shadow-color: rgba(90, 90, 90, 0.05); + +/* +Multi file editor +*/ +$border-color-settings: #e1e1e1; diff --git a/app/assets/stylesheets/pages/events.scss b/app/assets/stylesheets/pages/events.scss index eea8b7dd193..da096354b5a 100644 --- a/app/assets/stylesheets/pages/events.scss +++ b/app/assets/stylesheets/pages/events.scss @@ -159,7 +159,6 @@ } } - /* * Last push widget */ @@ -182,6 +181,12 @@ .event-item { padding-left: 0; + &.event-inline { + .event-title { + line-height: 20px; + } + } + .event-title { white-space: normal; overflow: visible; diff --git a/app/assets/stylesheets/pages/profiles/preferences.scss b/app/assets/stylesheets/pages/profiles/preferences.scss index c197494b152..68d40b56133 100644 --- a/app/assets/stylesheets/pages/profiles/preferences.scss +++ b/app/assets/stylesheets/pages/profiles/preferences.scss @@ -20,6 +20,22 @@ } } +.multi-file-editor-options { + label { + margin-right: 20px; + text-align: center; + } + + .preview { + font-size: 0; + + img { + border: 1px solid $border-color-settings; + border-radius: 4px; + } + } +} + .application-theme { label { margin-right: 20px; diff --git a/app/assets/stylesheets/pages/repo.scss b/app/assets/stylesheets/pages/repo.scss index da3c2d7fa5d..d01cbadebcc 100644 --- a/app/assets/stylesheets/pages/repo.scss +++ b/app/assets/stylesheets/pages/repo.scss @@ -36,10 +36,6 @@ } } -.with-performance-bar .ide-view { - height: calc(100vh - #{$header-height}); -} - .ide-file-list { flex: 1; @@ -96,8 +92,14 @@ padding: 6px 12px; } -.multi-file-table-name { +table.table tr td.multi-file-table-name { width: 350px; + padding: 6px 12px; + + svg { + vertical-align: middle; + margin-right: 2px; + } } .multi-file-table-col-commit-message { @@ -132,6 +134,10 @@ border-bottom: 1px solid $white-dark; cursor: pointer; + svg { + vertical-align: middle; + } + &.active { background-color: $white-light; border-bottom-color: $white-light; @@ -232,12 +238,13 @@ .multi-file-commit-panel { display: flex; + position: relative; flex-direction: column; height: 100%; width: 290px; padding: 0; background-color: $gray-light; - border-left: 1px solid $white-dark; + padding-right: 3px; .projects-sidebar { display: flex; @@ -486,3 +493,30 @@ margin-top: $header-height; margin-bottom: 0; } + +.with-performance-bar { + .ide-flash-container.flash-container { + margin-top: $header-height + $performance-bar-height; + } + + .ide-view { + height: calc(100vh - #{$header-height + $performance-bar-height}); + } +} + + +.dragHandle { + position: absolute; + top: 0; + bottom: 0; + width: 3px; + background-color: $white-dark; + + &.dragright { + right: 0; + } + + &.dragleft { + left: 0; + } +} diff --git a/app/assets/stylesheets/pages/settings.scss b/app/assets/stylesheets/pages/settings.scss index 5d630c7d61e..6353482ede7 100644 --- a/app/assets/stylesheets/pages/settings.scss +++ b/app/assets/stylesheets/pages/settings.scss @@ -268,3 +268,7 @@ margin: 0 0 5px 17px; } } + +.deprecated-service { + cursor: default; +} diff --git a/app/controllers/passwords_controller.rb b/app/controllers/passwords_controller.rb index 68a52f40342..57761bfbe26 100644 --- a/app/controllers/passwords_controller.rb +++ b/app/controllers/passwords_controller.rb @@ -1,6 +1,8 @@ class PasswordsController < Devise::PasswordsController include Gitlab::CurrentSettings + skip_before_action :require_no_authentication, only: [:edit, :update] + before_action :resource_from_email, only: [:create] before_action :check_password_authentication_available, only: [:create] before_action :throttle_reset, only: [:create] diff --git a/app/controllers/projects/branches_controller.rb b/app/controllers/projects/branches_controller.rb index 56df9991fda..cabafe26357 100644 --- a/app/controllers/projects/branches_controller.rb +++ b/app/controllers/projects/branches_controller.rb @@ -46,14 +46,16 @@ class Projects::BranchesController < Projects::ApplicationController result = CreateBranchService.new(project, current_user) .execute(branch_name, ref) - if params[:issue_iid] + success = (result[:status] == :success) + + if params[:issue_iid] && success issue = IssuesFinder.new(current_user, project_id: @project.id).find_by(iid: params[:issue_iid]) SystemNoteService.new_issue_branch(issue, @project, current_user, branch_name) if issue end respond_to do |format| format.html do - if result[:status] == :success + if success if redirect_to_autodeploy redirect_to url_to_autodeploy_setup(project, branch_name), notice: view_context.autodeploy_flash_notice(branch_name) @@ -67,7 +69,7 @@ class Projects::BranchesController < Projects::ApplicationController end format.json do - if result[:status] == :success + if success render json: { name: branch_name, url: project_tree_url(@project, branch_name) } else render json: result[:messsage], status: :unprocessable_entity diff --git a/app/controllers/projects/runners_controller.rb b/app/controllers/projects/runners_controller.rb index 9f9773575a5..c950d0f7001 100644 --- a/app/controllers/projects/runners_controller.rb +++ b/app/controllers/projects/runners_controller.rb @@ -29,17 +29,17 @@ class Projects::RunnersController < Projects::ApplicationController def resume if Ci::UpdateRunnerService.new(@runner).update(active: true) - redirect_to runner_path(@runner), notice: 'Runner was successfully updated.' + redirect_to runners_path(@project), notice: 'Runner was successfully updated.' else - redirect_to runner_path(@runner), alert: 'Runner was not updated.' + redirect_to runners_path(@project), alert: 'Runner was not updated.' end end def pause if Ci::UpdateRunnerService.new(@runner).update(active: false) - redirect_to runner_path(@runner), notice: 'Runner was successfully updated.' + redirect_to runners_path(@project), notice: 'Runner was successfully updated.' else - redirect_to runner_path(@runner), alert: 'Runner was not updated.' + redirect_to runners_path(@project), alert: 'Runner was not updated.' end end diff --git a/app/helpers/icons_helper.rb b/app/helpers/icons_helper.rb index c6a83f21ceb..c5522ff7a69 100644 --- a/app/helpers/icons_helper.rb +++ b/app/helpers/icons_helper.rb @@ -30,6 +30,13 @@ module IconsHelper ActionController::Base.helpers.image_path('icons.svg', host: sprite_base_url) end + def sprite_file_icons_path + # SVG Sprites currently don't work across domains, so in the case of a CDN + # we have to set the current path deliberately to prevent addition of asset_host + sprite_base_url = Gitlab.config.gitlab.url if ActionController::Base.asset_host + ActionController::Base.helpers.image_path('file_icons.svg', host: sprite_base_url) + end + def sprite_icon(icon_name, size: nil, css_class: nil) css_classes = size ? "s#{size}" : "" css_classes << " #{css_class}" unless css_class.blank? diff --git a/app/helpers/projects_helper.rb b/app/helpers/projects_helper.rb index 4a6b22b5ff6..f7bdcc6fd9c 100644 --- a/app/helpers/projects_helper.rb +++ b/app/helpers/projects_helper.rb @@ -389,7 +389,7 @@ module ProjectsHelper end def add_special_file_path(project, file_name:, commit_message: nil, branch_name: nil, context: nil) - commit_message ||= s_("CommitMessage|Add %{file_name}") % { file_name: file_name.downcase } + commit_message ||= s_("CommitMessage|Add %{file_name}") % { file_name: file_name } project_new_blob_path( project, project.default_branch || 'master', diff --git a/app/helpers/services_helper.rb b/app/helpers/services_helper.rb index 3707bb5ba36..240783bc7fd 100644 --- a/app/helpers/services_helper.rb +++ b/app/helpers/services_helper.rb @@ -27,5 +27,16 @@ module ServicesHelper "#{event}_events" end + def service_save_button(service) + button_tag(class: 'btn btn-save', type: 'submit', disabled: service.deprecated?) do + icon('spinner spin', class: 'hidden js-btn-spinner') + + content_tag(:span, 'Save changes', class: 'js-btn-label') + end + end + + def disable_fields_service?(service) + !current_controller?("admin/services") && service.deprecated? + end + extend self end diff --git a/app/models/concerns/issuable.rb b/app/models/concerns/issuable.rb index 5ca4a7086cb..4251561a0a0 100644 --- a/app/models/concerns/issuable.rb +++ b/app/models/concerns/issuable.rb @@ -96,7 +96,7 @@ module Issuable strip_attributes :title - after_save :record_metrics, unless: :imported? + after_save :ensure_metrics, unless: :imported? # We want to use optimistic lock for cases when only title or description are involved # http://api.rubyonrails.org/classes/ActiveRecord/Locking/Optimistic.html @@ -335,11 +335,6 @@ module Issuable false end - def record_metrics - metrics = self.metrics || create_metrics - metrics.record! - end - ## # Override in issuable specialization # @@ -347,6 +342,10 @@ module Issuable false end + def ensure_metrics + self.metrics || create_metrics + end + ## # Overriden in MergeRequest # diff --git a/app/models/concerns/storage/legacy_namespace.rb b/app/models/concerns/storage/legacy_namespace.rb index b3020484738..99dbd4fbacf 100644 --- a/app/models/concerns/storage/legacy_namespace.rb +++ b/app/models/concerns/storage/legacy_namespace.rb @@ -34,6 +34,8 @@ module Storage # So we basically we mute exceptions in next actions begin send_update_instructions + write_projects_repository_config + true rescue # Returning false does not rollback after_* transaction but gives diff --git a/app/models/diff_discussion.rb b/app/models/diff_discussion.rb index d67b16584a4..bd6af622bfb 100644 --- a/app/models/diff_discussion.rb +++ b/app/models/diff_discussion.rb @@ -23,8 +23,13 @@ class DiffDiscussion < Discussion def merge_request_version_params return unless for_merge_request? + version_params = get_params + + return version_params unless on_merge_request_commit? && commit_id + + version_params ||= {} version_params.tap do |params| - params[:commit_id] = commit_id if on_merge_request_commit? + params[:commit_id] = commit_id end end @@ -37,7 +42,7 @@ class DiffDiscussion < Discussion private - def version_params + def get_params return {} if active? noteable.version_params_for(position.diff_refs) diff --git a/app/models/event.rb b/app/models/event.rb index 0997b056c6a..8a79100de5a 100644 --- a/app/models/event.rb +++ b/app/models/event.rb @@ -48,7 +48,18 @@ class Event < ActiveRecord::Base belongs_to :author, class_name: "User" belongs_to :project - belongs_to :target, polymorphic: true # rubocop:disable Cop/PolymorphicAssociations + + belongs_to :target, -> { + # If the association for "target" defines an "author" association we want to + # eager-load this so Banzai & friends don't end up performing N+1 queries to + # get the authors of notes, issues, etc. + if reflections['events'].active_record.reflect_on_association(:author) + includes(:author) + else + self + end + }, polymorphic: true # rubocop:disable Cop/PolymorphicAssociations + has_one :push_event_payload # Callbacks diff --git a/app/models/issue.rb b/app/models/issue.rb index dc64888b6fc..4eafc1316d6 100644 --- a/app/models/issue.rb +++ b/app/models/issue.rb @@ -276,6 +276,11 @@ class Issue < ActiveRecord::Base private + def ensure_metrics + super + metrics.record! + end + # Returns `true` if the given User can read the current Issue. # # This method duplicates the same check of issue_policy.rb diff --git a/app/models/merge_request/metrics.rb b/app/models/merge_request/metrics.rb index cdc408738be..9e660eccd86 100644 --- a/app/models/merge_request/metrics.rb +++ b/app/models/merge_request/metrics.rb @@ -1,12 +1,6 @@ class MergeRequest::Metrics < ActiveRecord::Base belongs_to :merge_request belongs_to :pipeline, class_name: 'Ci::Pipeline', foreign_key: :pipeline_id - - def record! - if merge_request.merged? && self.merged_at.blank? - self.merged_at = Time.now - end - - self.save - end + belongs_to :latest_closed_by, class_name: 'User' + belongs_to :merged_by, class_name: 'User' end diff --git a/app/models/namespace.rb b/app/models/namespace.rb index 0ff169d4531..bdcc9159d26 100644 --- a/app/models/namespace.rb +++ b/app/models/namespace.rb @@ -268,4 +268,11 @@ class Namespace < ActiveRecord::Base def namespace_previously_created_with_same_path? RedirectRoute.permanent.exists?(path: path) end + + def write_projects_repository_config + all_projects.find_each do |project| + project.expires_full_path_cache # we need to clear cache to validate renames correctly + project.write_repository_config + end + end end diff --git a/app/models/project.rb b/app/models/project.rb index f9c640300ff..9c0bbf697e2 100644 --- a/app/models/project.rb +++ b/app/models/project.rb @@ -226,7 +226,7 @@ class Project < ActiveRecord::Base delegate :name, to: :owner, allow_nil: true, prefix: true delegate :members, to: :team, prefix: true delegate :add_user, :add_users, to: :team - delegate :add_guest, :add_reporter, :add_developer, :add_master, to: :team + delegate :add_guest, :add_reporter, :add_developer, :add_master, :add_role, to: :team # Validations validates :creator, presence: true, on: :create @@ -639,7 +639,7 @@ class Project < ActiveRecord::Base end def import? - external_import? || forked? || gitlab_project_import? + external_import? || forked? || gitlab_project_import? || bare_repository_import? end def no_import? @@ -679,6 +679,10 @@ class Project < ActiveRecord::Base Gitlab::UrlSanitizer.new(import_url).masked_url end + def bare_repository_import? + import_type == 'bare_repository' + end + def gitlab_project_import? import_type == 'gitlab_project' end @@ -1416,6 +1420,8 @@ class Project < ActiveRecord::Base end def after_rename_repo + write_repository_config + path_before_change = previous_changes['path'].first # We need to check if project had been rolled out to move resource to hashed storage or not and decide @@ -1428,6 +1434,16 @@ class Project < ActiveRecord::Base Gitlab::PagesTransfer.new.rename_project(path_before_change, self.path, namespace.full_path) end + def write_repository_config(gl_full_path: full_path) + # We'd need to keep track of project full path otherwise directory tree + # created with hashed storage enabled cannot be usefully imported using + # the import rake task. + repo.config['gitlab.fullpath'] = gl_full_path + rescue Gitlab::Git::Repository::NoRepository => e + Rails.logger.error("Error writing to .git/config for project #{full_path} (#{id}): #{e.message}.") + nil + end + def rename_repo_notify! send_move_instructions(full_path_was) expires_full_path_cache diff --git a/app/models/project_services/kubernetes_service.rb b/app/models/project_services/kubernetes_service.rb index b82567ce2b3..c72b01b64af 100644 --- a/app/models/project_services/kubernetes_service.rb +++ b/app/models/project_services/kubernetes_service.rb @@ -31,6 +31,7 @@ class KubernetesService < DeploymentService before_validation :enforce_namespace_to_lower_case + validate :deprecation_validation, unless: :template? validates :namespace, allow_blank: true, length: 1..63, @@ -145,6 +146,17 @@ class KubernetesService < DeploymentService @kubeclient ||= build_kubeclient! end + def deprecated? + !active + end + + def deprecation_message + content = <<-MESSAGE.strip_heredoc + Kubernetes service integration has been deprecated. #{deprecated_message_content} your clusters using the new <a href=\'#{Gitlab::Routing.url_helpers.project_clusters_path(project)}'/>Clusters</a> page + MESSAGE + content.html_safe + end + TEMPLATE_PLACEHOLDER = 'Kubernetes namespace'.freeze private @@ -226,4 +238,20 @@ class KubernetesService < DeploymentService def enforce_namespace_to_lower_case self.namespace = self.namespace&.downcase end + + def deprecation_validation + return if active_changed?(from: true, to: false) + + if deprecated? + errors[:base] << deprecation_message + end + end + + def deprecated_message_content + if active? + "Your cluster information on this page is still editable, but you are advised to disable and reconfigure" + else + "Fields on this page are now uneditable, you can configure" + end + end end diff --git a/app/models/project_team.rb b/app/models/project_team.rb index c679758973a..a9e5cfb8240 100644 --- a/app/models/project_team.rb +++ b/app/models/project_team.rb @@ -7,36 +7,24 @@ class ProjectTeam @project = project end - # Shortcut to add users - # - # Use: - # @team << [@user, :master] - # @team << [@users, :master] - # - def <<(args) - users, access, current_user = *args - - if users.respond_to?(:each) - add_users(users, access, current_user: current_user) - else - add_user(users, access, current_user: current_user) - end - end - def add_guest(user, current_user: nil) - self << [user, :guest, current_user] + add_user(user, :guest, current_user: current_user) end def add_reporter(user, current_user: nil) - self << [user, :reporter, current_user] + add_user(user, :reporter, current_user: current_user) end def add_developer(user, current_user: nil) - self << [user, :developer, current_user] + add_user(user, :developer, current_user: current_user) end def add_master(user, current_user: nil) - self << [user, :master, current_user] + add_user(user, :master, current_user: current_user) + end + + def add_role(user, role, current_user: nil) + send(:"add_#{role}", user, current_user: current_user) # rubocop:disable GitlabSecurity/PublicSend end def find_member(user_id) diff --git a/app/models/repository.rb b/app/models/repository.rb index a34f5e5439b..b1fd981965c 100644 --- a/app/models/repository.rb +++ b/app/models/repository.rb @@ -1010,10 +1010,6 @@ class Repository raw_repository.fetch_source_branch!(source_repository.raw_repository, source_branch, local_ref) end - def remote_exists?(name) - raw_repository.remote_exists?(name) - end - def compare_source_branch(target_branch_name, source_repository, source_branch_name, straight:) raw_repository.compare_source_branch(target_branch_name, source_repository.raw_repository, source_branch_name, straight: straight) end diff --git a/app/models/service.rb b/app/models/service.rb index 3c4f1885dd0..176b472e724 100644 --- a/app/models/service.rb +++ b/app/models/service.rb @@ -263,6 +263,14 @@ class Service < ActiveRecord::Base service end + def deprecated? + false + end + + def deprecation_message + nil + end + private def cache_project_has_external_issue_tracker diff --git a/app/models/user.rb b/app/models/user.rb index b52f17cd6a8..4484ee9ff4c 100644 --- a/app/models/user.rb +++ b/app/models/user.rb @@ -94,8 +94,8 @@ class User < ActiveRecord::Base has_one :user_synced_attributes_metadata, autosave: true # Groups - has_many :members, dependent: :destroy # rubocop:disable Cop/ActiveRecordDependent - has_many :group_members, -> { where(requested_at: nil) }, dependent: :destroy, source: 'GroupMember' # rubocop:disable Cop/ActiveRecordDependent + has_many :members + has_many :group_members, -> { where(requested_at: nil) }, 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 @@ -103,7 +103,7 @@ 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, -> { where(requested_at: nil) }, dependent: :destroy # rubocop:disable Cop/ActiveRecordDependent + has_many :project_members, -> { where(requested_at: nil) } has_many :projects, through: :project_members has_many :created_projects, foreign_key: :creator_id, class_name: 'Project' has_many :users_star_projects, dependent: :destroy # rubocop:disable Cop/ActiveRecordDependent @@ -794,10 +794,7 @@ class User < ActiveRecord::Base # `User.select(:id)` raises # `ActiveModel::MissingAttributeError: missing attribute: projects_limit` # without this safeguard! - return unless has_attribute?(:projects_limit) - - connection_default_value_defined = new_record? && !projects_limit_changed? - return unless projects_limit.nil? || connection_default_value_defined + return unless has_attribute?(:projects_limit) && projects_limit.nil? self.projects_limit = current_application_settings.default_projects_limit end diff --git a/app/serializers/event_entity.rb b/app/serializers/event_entity.rb deleted file mode 100644 index 935d67a4f37..00000000000 --- a/app/serializers/event_entity.rb +++ /dev/null @@ -1,4 +0,0 @@ -class EventEntity < Grape::Entity - expose :author, using: UserEntity - expose :updated_at -end diff --git a/app/serializers/merge_request_metrics_entity.rb b/app/serializers/merge_request_metrics_entity.rb new file mode 100644 index 00000000000..3548107ac16 --- /dev/null +++ b/app/serializers/merge_request_metrics_entity.rb @@ -0,0 +1,6 @@ +class MergeRequestMetricsEntity < Grape::Entity + expose :latest_closed_at, as: :closed_at + expose :merged_at + expose :latest_closed_by, as: :closed_by, using: UserEntity + expose :merged_by, using: UserEntity +end diff --git a/app/serializers/merge_request_widget_entity.rb b/app/serializers/merge_request_widget_entity.rb index f8e59b2ffd7..e905e6876c2 100644 --- a/app/serializers/merge_request_widget_entity.rb +++ b/app/serializers/merge_request_widget_entity.rb @@ -17,9 +17,11 @@ class MergeRequestWidgetEntity < IssuableEntity merge_request.project.merge_requests_ff_only_enabled end - # Events - expose :merge_event, using: EventEntity - expose :closed_event, using: EventEntity + expose :metrics do |merge_request| + metrics = build_metrics(merge_request) + + MergeRequestMetricsEntity.new(metrics).as_json + end # User entities expose :merge_user, using: UserEntity @@ -178,4 +180,27 @@ class MergeRequestWidgetEntity < IssuableEntity @presenters ||= {} @presenters[merge_request] ||= MergeRequestPresenter.new(merge_request, current_user: current_user) end + + # Once SchedulePopulateMergeRequestMetricsWithEventsData fully runs, + # we can remove this method and just serialize MergeRequest#metrics + # instead. See https://gitlab.com/gitlab-org/gitlab-ce/issues/41587 + def build_metrics(merge_request) + # There's no need to query and serialize metrics data for merge requests that are not + # merged or closed. + return unless merge_request.merged? || merge_request.closed? + return merge_request.metrics if merge_request.merged? && merge_request.metrics&.merged_by_id + return merge_request.metrics if merge_request.closed? && merge_request.metrics&.latest_closed_by_id + + build_metrics_from_events(merge_request) + end + + def build_metrics_from_events(merge_request) + closed_event = merge_request.closed_event + merge_event = merge_request.merge_event + + MergeRequest::Metrics.new(latest_closed_at: closed_event&.updated_at, + latest_closed_by: closed_event&.author, + merged_at: merge_event&.updated_at, + merged_by: merge_event&.author) + end end diff --git a/app/services/event_create_service.rb b/app/services/event_create_service.rb index 6328d567a07..44dc90b3462 100644 --- a/app/services/event_create_service.rb +++ b/app/services/event_create_service.rb @@ -103,6 +103,6 @@ class EventCreateService author_id: current_user.id ) - Event.create(attributes) + Event.create!(attributes) end end diff --git a/app/services/merge_request_metrics_service.rb b/app/services/merge_request_metrics_service.rb new file mode 100644 index 00000000000..9248de14a53 --- /dev/null +++ b/app/services/merge_request_metrics_service.rb @@ -0,0 +1,19 @@ +class MergeRequestMetricsService + delegate :update!, to: :@merge_request_metrics + + def initialize(merge_request_metrics) + @merge_request_metrics = merge_request_metrics + end + + def merge(event) + update!(merged_by_id: event.author_id, merged_at: event.created_at) + end + + def close(event) + update!(latest_closed_by_id: event.author_id, latest_closed_at: event.created_at) + end + + def reopen + update!(latest_closed_by_id: nil, latest_closed_at: nil) + end +end diff --git a/app/services/merge_requests/base_service.rb b/app/services/merge_requests/base_service.rb index 6b32d65a74b..20a2b50d3de 100644 --- a/app/services/merge_requests/base_service.rb +++ b/app/services/merge_requests/base_service.rb @@ -24,6 +24,10 @@ module MergeRequests private + def merge_request_metrics_service(merge_request) + MergeRequestMetricsService.new(merge_request.metrics) + end + def create_assignee_note(merge_request) SystemNoteService.change_assignee( merge_request, merge_request.project, current_user, merge_request.assignee) diff --git a/app/services/merge_requests/close_service.rb b/app/services/merge_requests/close_service.rb index 40213c99014..f727ec002e7 100644 --- a/app/services/merge_requests/close_service.rb +++ b/app/services/merge_requests/close_service.rb @@ -8,7 +8,7 @@ module MergeRequests merge_request.allow_broken = true if merge_request.close - event_service.close_mr(merge_request, current_user) + create_event(merge_request) create_note(merge_request) notification_service.close_mr(merge_request, current_user) todo_service.close_merge_request(merge_request, current_user) @@ -19,5 +19,16 @@ module MergeRequests merge_request end + + private + + def create_event(merge_request) + # Making sure MergeRequest::Metrics updates are in sync with + # Event creation. + Event.transaction do + close_event = event_service.close_mr(merge_request, current_user) + merge_request_metrics_service(merge_request).close(close_event) + end + end end end diff --git a/app/services/merge_requests/conflicts/list_service.rb b/app/services/merge_requests/conflicts/list_service.rb index 0f677a996f7..ca9a33678e4 100644 --- a/app/services/merge_requests/conflicts/list_service.rb +++ b/app/services/merge_requests/conflicts/list_service.rb @@ -23,7 +23,7 @@ module MergeRequests # when there are no conflict files. conflicts.files.each(&:lines) @conflicts_can_be_resolved_in_ui = conflicts.files.length > 0 - rescue Rugged::OdbError, Gitlab::Git::Conflict::Parser::UnresolvableError, Gitlab::Git::Conflict::Resolver::ConflictSideMissing + rescue Gitlab::Git::CommandError, Gitlab::Git::Conflict::Parser::UnresolvableError, Gitlab::Git::Conflict::Resolver::ConflictSideMissing @conflicts_can_be_resolved_in_ui = false end end diff --git a/app/services/merge_requests/post_merge_service.rb b/app/services/merge_requests/post_merge_service.rb index b1d6bac4d4a..c78e78afcd1 100644 --- a/app/services/merge_requests/post_merge_service.rb +++ b/app/services/merge_requests/post_merge_service.rb @@ -9,7 +9,7 @@ module MergeRequests close_issues(merge_request) todo_service.merge_merge_request(merge_request, current_user) merge_request.mark_as_merged - create_merge_event(merge_request, current_user) + create_event(merge_request) create_note(merge_request) notification_service.merge_mr(merge_request, current_user) execute_hooks(merge_request, 'merge') @@ -34,5 +34,14 @@ module MergeRequests def create_merge_event(merge_request, current_user) EventCreateService.new.merge_mr(merge_request, current_user) end + + def create_event(merge_request) + # Making sure MergeRequest::Metrics updates are in sync with + # Event creation. + Event.transaction do + merge_event = create_merge_event(merge_request, current_user) + merge_request_metrics_service(merge_request).merge(merge_event) + end + end end end diff --git a/app/services/merge_requests/reopen_service.rb b/app/services/merge_requests/reopen_service.rb index c599a90f9fe..120677a7149 100644 --- a/app/services/merge_requests/reopen_service.rb +++ b/app/services/merge_requests/reopen_service.rb @@ -4,7 +4,7 @@ module MergeRequests return merge_request unless can?(current_user, :update_merge_request, merge_request) if merge_request.reopen - event_service.reopen_mr(merge_request, current_user) + create_event(merge_request) create_note(merge_request, 'reopened') notification_service.reopen_mr(merge_request, current_user) execute_hooks(merge_request, 'reopen') @@ -16,5 +16,16 @@ module MergeRequests merge_request end + + private + + def create_event(merge_request) + # Making sure MergeRequest::Metrics updates are in sync with + # Event creation. + Event.transaction do + event_service.reopen_mr(merge_request, current_user) + merge_request_metrics_service(merge_request).reopen + end + end end end diff --git a/app/services/projects/create_service.rb b/app/services/projects/create_service.rb index 71533da31b1..01838ec6b5d 100644 --- a/app/services/projects/create_service.rb +++ b/app/services/projects/create_service.rb @@ -56,11 +56,7 @@ module Projects after_create_actions if @project.persisted? - if @project.errors.empty? - @project.import_schedule if @project.import? - else - fail(error: @project.errors.full_messages.join(', ')) - end + import_schedule @project rescue ActiveRecord::RecordInvalid => e @@ -92,6 +88,7 @@ module Projects log_info("#{@project.owner.name} created a new project \"#{@project.name_with_namespace}\"") unless @project.gitlab_project_import? + @project.write_repository_config @project.create_wiki unless skip_wiki? create_services_from_active_templates(@project) @@ -164,5 +161,15 @@ module Projects @project.path = @project.name.dup.parameterize end end + + private + + def import_schedule + if @project.errors.empty? + @project.import_schedule if @project.import? && !@project.bare_repository_import? + else + fail(error: @project.errors.full_messages.join(', ')) + end + end end end diff --git a/app/services/projects/fork_service.rb b/app/services/projects/fork_service.rb index 03be7039b2a..348eb0bf8d8 100644 --- a/app/services/projects/fork_service.rb +++ b/app/services/projects/fork_service.rb @@ -26,7 +26,7 @@ module Projects name: @project.name, path: @project.path, shared_runners_enabled: @project.shared_runners_enabled, - namespace_id: @params[:namespace].try(:id) || current_user.namespace.id + namespace_id: target_namespace.id } if @project.avatar.present? && @project.avatar.image? @@ -74,14 +74,14 @@ module Projects Projects::ForksCountService.new(@project).refresh_cache end + def target_namespace + @target_namespace ||= @params[:namespace] || current_user.namespace + end + def allowed_visibility_level - project_level = @project.visibility_level + target_level = [@project.visibility_level, target_namespace.visibility_level].min - if Gitlab::VisibilityLevel.non_restricted_level?(project_level) - project_level - else - Gitlab::VisibilityLevel.highest_allowed_level - end + Gitlab::VisibilityLevel.closest_allowed_level(target_level) end end end diff --git a/app/services/projects/hashed_storage/migrate_repository_service.rb b/app/services/projects/hashed_storage/migrate_repository_service.rb index 7212e7524ab..67178de75de 100644 --- a/app/services/projects/hashed_storage/migrate_repository_service.rb +++ b/app/services/projects/hashed_storage/migrate_repository_service.rb @@ -27,7 +27,9 @@ module Projects result &&= move_repository("#{@old_wiki_disk_path}", "#{@new_disk_path}.wiki") end - unless result + if result + project.write_repository_config + else rollback_folder_move project.storage_version = nil end diff --git a/app/services/projects/transfer_service.rb b/app/services/projects/transfer_service.rb index e5cd6fcdfe3..26765e5c3f3 100644 --- a/app/services/projects/transfer_service.rb +++ b/app/services/projects/transfer_service.rb @@ -75,6 +75,8 @@ module Projects project.old_path_with_namespace = @old_path project.expires_full_path_cache + write_repository_config(@new_path) + execute_system_hooks end rescue Exception # rubocop:disable Lint/RescueException @@ -98,6 +100,10 @@ module Projects project.save! end + def write_repository_config(full_path) + project.write_repository_config(gl_full_path: full_path) + end + def refresh_permissions # This ensures we only schedule 1 job for every user that has access to # the namespaces. @@ -110,6 +116,7 @@ module Projects def rollback_side_effects rollback_folder_move update_namespace_and_visibility(@old_namespace) + write_repository_config(@old_path) end def rollback_folder_move diff --git a/app/services/users/destroy_service.rb b/app/services/users/destroy_service.rb index 8e20de8dfa5..00db8a2c434 100644 --- a/app/services/users/destroy_service.rb +++ b/app/services/users/destroy_service.rb @@ -31,6 +31,11 @@ module Users return user end + # Calling all before/after_destroy hooks for the user because + # there is no dependent: destroy in the relationship. And the removal + # is done by a foreign_key. Otherwise they won't be called + user.members.find_each { |member| member.run_callbacks(:destroy) } + user.solo_owned_groups.each do |group| Groups::DestroyService.new(group, current_user).execute end diff --git a/app/views/doorkeeper/applications/show.html.haml b/app/views/doorkeeper/applications/show.html.haml index 72eab964766..6364f0be4a3 100644 --- a/app/views/doorkeeper/applications/show.html.haml +++ b/app/views/doorkeeper/applications/show.html.haml @@ -1,3 +1,5 @@ +- add_to_breadcrumbs "Applications", oauth_applications_path +- breadcrumb_title @application.name - page_title @application.name, "Applications" - @content_class = "limit-container-width" unless fluid_layout diff --git a/app/views/groups/edit.html.haml b/app/views/groups/edit.html.haml index 16038ef2f79..76a8099d7c0 100644 --- a/app/views/groups/edit.html.haml +++ b/app/views/groups/edit.html.haml @@ -13,13 +13,13 @@ = group_icon(@group, alt: '', class: 'avatar group-avatar s160') %p.light - if @group.avatar? - You can change your group avatar here + You can change the group avatar here - else You can upload a group avatar here = render 'shared/choose_group_avatar_button', f: f - if @group.avatar? %hr - = link_to 'Remove avatar', group_avatar_path(@group.to_param), data: { confirm: "Group avatar will be removed. Are you sure?"}, method: :delete, class: "btn btn-remove btn-sm remove-avatar" + = link_to _('Remove avatar'), group_avatar_path(@group.to_param), data: { confirm: _("Avatar will be removed. Are you sure?")}, method: :delete, class: "btn btn-danger btn-inverted" = render 'shared/visibility_level', f: f, visibility_level: @group.visibility_level, can_change_visibility_level: can_change_group_visibility_level?(@group), form_model: @group diff --git a/app/views/layouts/header/_default.html.haml b/app/views/layouts/header/_default.html.haml index 99e7f3b568d..39eb71c2bac 100644 --- a/app/views/layouts/header/_default.html.haml +++ b/app/views/layouts/header/_default.html.haml @@ -56,6 +56,8 @@ = link_to "Profile", current_user, class: 'profile-link', data: { user: current_user.username } %li = link_to "Settings", profile_path + %li + = link_to "Turn on multi edit", profile_preferences_path - if current_user %li = link_to "Help", help_path diff --git a/app/views/layouts/nav/sidebar/_admin.html.haml b/app/views/layouts/nav/sidebar/_admin.html.haml index cb8db306b56..dd086f70641 100644 --- a/app/views/layouts/nav/sidebar/_admin.html.haml +++ b/app/views/layouts/nav/sidebar/_admin.html.haml @@ -130,7 +130,7 @@ %span.badge.count= number_with_delimiter(AbuseReport.count(:all)) %ul.sidebar-sub-level-items.is-fly-out-only = nav_link(controller: :abuse_reports, html_options: { class: "fly-out-top-item" } ) do - = link_to admin_broadcast_messages_path do + = link_to admin_abuse_reports_path do %strong.fly-out-top-item-name #{ _('Abuse Reports') } %span.badge.count.merge_counter.js-merge-counter.fly-out-badge= number_with_delimiter(AbuseReport.count(:all)) diff --git a/app/views/profiles/accounts/show.html.haml b/app/views/profiles/accounts/show.html.haml index ced58dffcdc..f1313b79589 100644 --- a/app/views/profiles/accounts/show.html.haml +++ b/app/views/profiles/accounts/show.html.haml @@ -17,10 +17,6 @@ Status: #{current_user.two_factor_enabled? ? 'Enabled' : 'Disabled'} - if current_user.two_factor_enabled? = link_to 'Manage two-factor authentication', profile_two_factor_auth_path, class: 'btn btn-info' - = link_to 'Disable', profile_two_factor_auth_path, - method: :delete, - data: { confirm: "Are you sure? This will invalidate your registered applications and U2F devices." }, - class: 'btn btn-danger' - else .append-bottom-10 = link_to 'Enable two-factor authentication', profile_two_factor_auth_path, class: 'btn btn-success' diff --git a/app/views/profiles/audit_log.html.haml b/app/views/profiles/audit_log.html.haml index 1a392e29e2a..cbea5ca605a 100644 --- a/app/views/profiles/audit_log.html.haml +++ b/app/views/profiles/audit_log.html.haml @@ -4,7 +4,7 @@ .row.prepend-top-default .col-lg-4.profile-settings-sidebar - %h3.prepend-top-0 + %h4.prepend-top-0 = page_title %p This is a security log of important events involving your account. diff --git a/app/views/profiles/gpg_keys/index.html.haml b/app/views/profiles/gpg_keys/index.html.haml index 8dbb8aef31b..86ebec0179c 100644 --- a/app/views/profiles/gpg_keys/index.html.haml +++ b/app/views/profiles/gpg_keys/index.html.haml @@ -1,4 +1,5 @@ - page_title "GPG Keys" +- @content_class = "limit-container-width" unless fluid_layout = render 'profiles/head' .row.prepend-top-default diff --git a/app/views/profiles/keys/show.html.haml b/app/views/profiles/keys/show.html.haml index 172c0450381..7b7960708c4 100644 --- a/app/views/profiles/keys/show.html.haml +++ b/app/views/profiles/keys/show.html.haml @@ -1,3 +1,5 @@ +- add_to_breadcrumbs "SSH Keys", profile_keys_path +- breadcrumb_title @key.title - page_title @key.title, "SSH Keys" - @content_class = "limit-container-width" unless fluid_layout = render 'profiles/head' diff --git a/app/views/profiles/notifications/show.html.haml b/app/views/profiles/notifications/show.html.haml index e98fdfc7a3d..202eccb7bb6 100644 --- a/app/views/profiles/notifications/show.html.haml +++ b/app/views/profiles/notifications/show.html.haml @@ -10,16 +10,16 @@ %li= msg = hidden_field_tag :notification_type, 'global' - .row + .row.prepend-top-default .col-lg-4.profile-settings-sidebar - %h4 + %h4.prepend-top-0 = page_title %p You can specify notification level per group or per project. %p By default, all projects and groups will use the global notifications setting. .col-lg-8 - %h5 + %h5.prepend-top-0 Global notification settings = form_for @user, url: profile_notifications_path, method: :put, html: { class: 'update-notifications prepend-top-default' } do |f| diff --git a/app/views/profiles/preferences/show.html.haml b/app/views/profiles/preferences/show.html.haml index 66d1d1e8d44..65328791ce5 100644 --- a/app/views/profiles/preferences/show.html.haml +++ b/app/views/profiles/preferences/show.html.haml @@ -3,6 +3,23 @@ = render 'profiles/head' = form_for @user, url: profile_preferences_path, remote: true, method: :put, html: { class: 'row prepend-top-default js-preferences-form' } do |f| + .col-lg-4 + %h4.prepend-top-0 + GitLab multi file editor + %p Unlock an additional editing experience which makes it possible to edit and commit multiple files + .col-lg-8.multi-file-editor-options + = label_tag do + .preview.append-bottom-10= image_tag "multi-editor-off.png" + = f.radio_button :multi_file, "off", checked: true + Off + = label_tag do + .preview.append-bottom-10= image_tag "multi-editor-on.png" + = f.radio_button :multi_file, "on", checked: false + On + + .col-sm-12 + %hr + .col-lg-4.application-theme %h4.prepend-top-0 GitLab navigation theme diff --git a/app/views/profiles/show.html.haml b/app/views/profiles/show.html.haml index 79f334176a5..0f773933ac2 100644 --- a/app/views/profiles/show.html.haml +++ b/app/views/profiles/show.html.haml @@ -22,18 +22,15 @@ .clearfix.avatar-image.append-bottom-default = link_to avatar_icon(@user, 400), target: '_blank', rel: 'noopener noreferrer' do = image_tag avatar_icon(@user, 160), alt: '', class: 'avatar s160' - %h5.prepend-top-0 - Upload new avatar + %h5.prepend-top-0= _("Upload new avatar") .prepend-top-5.append-bottom-10 - %a.btn.js-choose-user-avatar-button - Browse file... - %span.avatar-file-name.prepend-left-default.js-avatar-filename No file chosen + %button.btn.js-choose-user-avatar-button{ type: 'button' }= _("Choose file...") + %span.avatar-file-name.prepend-left-default.js-avatar-filename= _("No file chosen") = f.file_field_without_bootstrap :avatar, class: 'js-user-avatar-input hidden', accept: 'image/*' - .help-block - The maximum file size allowed is 200KB. + .help-block= _("The maximum file size allowed is 200KB.") - if @user.avatar? %hr - = link_to 'Remove avatar', profile_avatar_path, data: { confirm: 'Avatar will be removed. Are you sure?' }, method: :delete, class: 'btn btn-gray' + = link_to _('Remove avatar'), profile_avatar_path, data: { confirm: _('Avatar will be removed. Are you sure?') }, method: :delete, class: 'btn btn-danger btn-inverted' %hr .row .col-lg-4.profile-settings-sidebar diff --git a/app/views/profiles/two_factor_auths/show.html.haml b/app/views/profiles/two_factor_auths/show.html.haml index 0b03276efcc..5207dac3ac2 100644 --- a/app/views/profiles/two_factor_auths/show.html.haml +++ b/app/views/profiles/two_factor_auths/show.html.haml @@ -1,5 +1,5 @@ - page_title 'Two-Factor Authentication', 'Account' -- add_to_breadcrumbs("Account", profile_account_path) +- add_to_breadcrumbs("Two-Factor Authentication", profile_account_path) - @content_class = "limit-container-width" unless fluid_layout = render 'profiles/head' @@ -18,7 +18,12 @@ Use an app on your mobile device to enable two-factor authentication (2FA). .col-lg-8 - if current_user.two_factor_otp_enabled? - = icon "check inverse", base: "circle", class: "text-success", text: "You've already enabled two-factor authentication using mobile authenticator applications. You can disable it from your account settings page." + %p + You've already enabled two-factor authentication using mobile authenticator applications. In order to register a different device, you must first disable two-factor authentication. + = link_to 'Disable two-factor authentication', profile_two_factor_auth_path, + method: :delete, + data: { confirm: "Are you sure? This will invalidate your registered applications and U2F devices." }, + class: 'btn btn-danger' - else %p Download the Google Authenticator application from App Store or Google Play Store and scan this code. diff --git a/app/views/projects/compare/_form.html.haml b/app/views/projects/compare/_form.html.haml index a518fced2b4..d0c8a699608 100644 --- a/app/views/projects/compare/_form.html.haml +++ b/app/views/projects/compare/_form.html.haml @@ -5,22 +5,24 @@ = link_to icon('exchange'), { from: params[:to], to: params[:from] }, class: 'commits-compare-switch has-tooltip btn btn-white', title: 'Swap revisions' .form-group.dropdown.compare-form-group.to.js-compare-to-dropdown .input-group.inline-input-group - %span.input-group-addon Source + %span.input-group-addon + = s_("CompareBranches|Source") = hidden_field_tag :to, params[:to] = button_tag type: 'button', title: params[:to], class: "form-control compare-dropdown-toggle js-compare-dropdown has-tooltip git-revision-dropdown-toggle", required: true, data: { refs_url: refs_project_path(@project), toggle: "dropdown", target: ".js-compare-to-dropdown", selected: params[:to], field_name: :to } do - .dropdown-toggle-text.str-truncated= params[:to] || 'Select branch/tag' + .dropdown-toggle-text.str-truncated= params[:to] || _("Select branch/tag") = render 'shared/ref_dropdown' .compare-ellipsis.inline ... .form-group.dropdown.compare-form-group.from.js-compare-from-dropdown .input-group.inline-input-group - %span.input-group-addon Target + %span.input-group-addon + = s_("CompareBranches|Target") = hidden_field_tag :from, params[:from] = button_tag type: 'button', title: params[:from], class: "form-control compare-dropdown-toggle js-compare-dropdown has-tooltip git-revision-dropdown-toggle", required: true, data: { refs_url: refs_project_path(@project), toggle: "dropdown", target: ".js-compare-from-dropdown", selected: params[:from], field_name: :from } do - .dropdown-toggle-text.str-truncated= params[:from] || 'Select branch/tag' + .dropdown-toggle-text.str-truncated= params[:from] || _("Select branch/tag") = render 'shared/ref_dropdown' - = button_tag "Compare", class: "btn btn-create commits-compare-btn" + = button_tag s_("CompareBranches|Compare"), class: "btn btn-create commits-compare-btn" - if @merge_request.present? - = link_to "View open merge request", project_merge_request_path(@project, @merge_request), class: 'prepend-left-10 btn' + = link_to _("View open merge request"), project_merge_request_path(@project, @merge_request), class: 'prepend-left-10 btn' - elsif create_mr_button? - = link_to "Create merge request", create_mr_path, class: 'prepend-left-10 btn' + = link_to _("Create merge request"), create_mr_path, class: 'prepend-left-10 btn' diff --git a/app/views/projects/compare/index.html.haml b/app/views/projects/compare/index.html.haml index 3ad0166e9cd..14c64b3534a 100644 --- a/app/views/projects/compare/index.html.haml +++ b/app/views/projects/compare/index.html.haml @@ -3,22 +3,16 @@ - page_title "Compare" %div{ class: container_class } + %h3.page-title + = _("Compare Git revisions") .sub-header-block - Compare Git revisions. - %br - Choose a branch/tag (e.g. - = succeed ')' do + - example_master = capture do %code.ref-name master - or enter a commit SHA (e.g. - = succeed ')' do + - example_sha = capture do %code.ref-name 4eedf23 - to see what's changed or to create a merge request. + = (_("Choose a branch/tag (e.g. %{master}) or enter a commit (e.g. %{sha}) to see what's changed or to create a merge request.") % { master: example_master, sha: example_sha }).html_safe %br - Changes are shown as if the - %b source - revision was being merged into the - %b target - revision. + = (_("Changes are shown as if the <b>source</b> revision was being merged into the <b>target</b> revision.")).html_safe .prepend-top-20 = render "form" diff --git a/app/views/projects/compare/show.html.haml b/app/views/projects/compare/show.html.haml index f87f1d476f5..8da55664878 100644 --- a/app/views/projects/compare/show.html.haml +++ b/app/views/projects/compare/show.html.haml @@ -1,5 +1,5 @@ - @no_container = true -- add_to_breadcrumbs "Compare Revisions", project_compare_index_path(@project) +- add_to_breadcrumbs _("Compare Revisions"), project_compare_index_path(@project) - page_title "#{params[:from]}...#{params[:to]}" %div{ class: container_class } @@ -13,12 +13,13 @@ .light-well .center %h4 - There isn't anything to compare. + = s_("CompareBranches|There isn't anything to compare.") %p.slead - if params[:to] == params[:from] - %span.ref-name= params[:from] - and - %span.ref-name= params[:to] - are the same. + - source_branch = capture do + %span.ref-name= params[:from] + - target_branch = capture do + %span.ref-name= params[:to] + = (s_("CompareBranches|%{source_branch} and %{target_branch} are the same.") % { source_branch: source_branch, target_branch: target_branch }).html_safe - else - You'll need to use different branch names to get a valid comparison. + = _("You'll need to use different branch names to get a valid comparison.") diff --git a/app/views/projects/deploy_keys/_index.html.haml b/app/views/projects/deploy_keys/_index.html.haml index e75ae87e771..75dd4c9ae15 100644 --- a/app/views/projects/deploy_keys/_index.html.haml +++ b/app/views/projects/deploy_keys/_index.html.haml @@ -3,7 +3,7 @@ .settings-header %h4 Deploy Keys - %button.btn.js-settings-toggle + %button.btn.js-settings-toggle.qa-expand-deploy-keys = expanded ? 'Collapse' : 'Expand' %p Deploy keys allow read-only or read-write (if enabled) access to your repository. Deploy keys can be used for CI, staging or production servers. You can create a deploy key or add an existing one. diff --git a/app/views/projects/edit.html.haml b/app/views/projects/edit.html.haml index 71206f3a386..e16d132f869 100644 --- a/app/views/projects/edit.html.haml +++ b/app/views/projects/edit.html.haml @@ -42,24 +42,23 @@ = f.text_field :tag_list, value: @project.tag_list.sort.join(', '), maxlength: 2000, class: "form-control" %p.help-block Separate tags with commas. %fieldset.features - %h5.prepend-top-0 - Project avatar + %h5.prepend-top-0= _("Project avatar") .form-group - if @project.avatar? - .avatar-container.s160 + .avatar-container.s160.append-bottom-15 = project_icon(@project.full_path, alt: '', class: 'avatar project-avatar s160') - %p.light - - if @project.avatar_in_git - Project avatar in repository: #{ @project.avatar_in_git } - %a.choose-btn.btn.js-choose-project-avatar-button - Browse file... - %span.file_name.prepend-left-default.js-avatar-filename No file chosen - = f.file_field :avatar, class: "js-project-avatar-input hidden" - .help-block The maximum file size allowed is 200KB. + - if @project.avatar_in_git + %p.light + = _("Project avatar in repository: %{link}").html_safe % { link: @project.avatar_in_git } + .prepend-top-5.append-bottom-10 + %button.btn.js-choose-project-avatar-button{ type: 'button' }= _("Choose file...") + %span.file_name.prepend-left-default.js-avatar-filename= _("No file chosen") + = f.file_field :avatar, class: "js-project-avatar-input hidden" + .help-block= _("The maximum file size allowed is 200KB.") - if @project.avatar? %hr - = link_to 'Remove avatar', project_avatar_path(@project), data: { confirm: "Project avatar will be removed. Are you sure?"}, method: :delete, class: "btn btn-remove btn-sm remove-avatar" - = f.submit 'Save changes', class: "btn btn-save" + = link_to _('Remove avatar'), project_avatar_path(@project), data: { confirm: _("Avatar will be removed. Are you sure?") }, method: :delete, class: "btn btn-danger btn-inverted" + = f.submit 'Save changes', class: "btn btn-success" %section.settings.sharing-permissions.no-animate{ class: ('expanded' if expanded) } .settings-header diff --git a/app/views/projects/jobs/show.html.haml b/app/views/projects/jobs/show.html.haml index 1d0aaa47b60..fd24bbbb9ba 100644 --- a/app/views/projects/jobs/show.html.haml +++ b/app/views/projects/jobs/show.html.haml @@ -60,7 +60,7 @@ .js-truncated-info.truncated-info.hidden-xs.pull-left.hidden< Showing last %span.js-truncated-info-size.truncated-info-size>< - KiB of log - + of log - %a.js-raw-link.raw-link{ href: raw_project_job_path(@project, @build) }>< Complete Raw .controllers.pull-right diff --git a/app/views/projects/merge_requests/diffs/_diffs.html.haml b/app/views/projects/merge_requests/diffs/_diffs.html.haml index 60c91024b23..986ba5ae02d 100644 --- a/app/views/projects/merge_requests/diffs/_diffs.html.haml +++ b/app/views/projects/merge_requests/diffs/_diffs.html.haml @@ -4,14 +4,17 @@ = render 'projects/merge_requests/diffs/commit_widget' - if @merge_request_diff&.empty? - .nothing-here-block - = image_tag 'illustrations/merge_request_changes_empty.svg' - = succeed '.' do - No changes between - %span.ref-name= @merge_request.source_branch - and - %span.ref-name= @merge_request.target_branch - %p= link_to 'Create commit', project_new_blob_path(@project, @merge_request.source_branch), class: 'btn btn-save' + .row.empty-state.nothing-here-block + .col-xs-12 + .svg-content= image_tag 'illustrations/merge_request_changes_empty.svg' + .col-xs-12 + .text-content.text-center + %p + No changes between + %span.ref-name= @merge_request.source_branch + and + %span.ref-name= @merge_request.target_branch + .text-center= link_to 'Create commit', project_new_blob_path(@project, @merge_request.source_branch), class: 'btn btn-save' - else - diff_viewable = @merge_request_diff ? @merge_request_diff.collected? || @merge_request_diff.overflow? : true - if diff_viewable diff --git a/app/views/projects/runners/_runner.html.haml b/app/views/projects/runners/_runner.html.haml index 25d862ab4de..6376496ee1a 100644 --- a/app/views/projects/runners/_runner.html.haml +++ b/app/views/projects/runners/_runner.html.haml @@ -17,6 +17,10 @@ .pull-right - if @project_runners.include?(runner) + - if runner.active? + = link_to 'Pause', pause_project_runner_path(@project, runner), method: :post, class: 'btn btn-sm btn-danger', data: { confirm: "Are you sure?" } + - else + = link_to 'Resume', resume_project_runner_path(@project, runner), method: :post, class: 'btn btn-success btn-sm' - if runner.belongs_to_one_project? = link_to 'Remove Runner', runner_path(runner), data: { confirm: "Are you sure?" }, method: :delete, class: 'btn btn-danger btn-sm' - else diff --git a/app/views/projects/services/_deprecated_message.html.haml b/app/views/projects/services/_deprecated_message.html.haml new file mode 100644 index 00000000000..fea9506a4bb --- /dev/null +++ b/app/views/projects/services/_deprecated_message.html.haml @@ -0,0 +1,3 @@ +.flash-container.flash-container-page + .flash-alert.deprecated-service + %span= @service.deprecation_message diff --git a/app/views/projects/services/_form.html.haml b/app/views/projects/services/_form.html.haml index c0b1c62e8ef..21acd857ce7 100644 --- a/app/views/projects/services/_form.html.haml +++ b/app/views/projects/services/_form.html.haml @@ -13,10 +13,7 @@ = render 'shared/service_settings', form: form, subject: @service - if @service.editable? .footer-block.row-content-block - %button.btn.btn-save{ type: 'submit' } - = icon('spinner spin', class: 'hidden js-btn-spinner') - %span.js-btn-label - Save changes + = service_save_button(@service) - if @service.valid? && @service.activated? - unless @service.can_test? diff --git a/app/views/projects/services/edit.html.haml b/app/views/projects/services/edit.html.haml index 25770df1c90..df1fd583670 100644 --- a/app/views/projects/services/edit.html.haml +++ b/app/views/projects/services/edit.html.haml @@ -2,4 +2,6 @@ - page_title @service.title, "Services" - add_to_breadcrumbs("Settings", edit_project_path(@project)) += render 'deprecated_message' if @service.deprecation_message + = render 'form' diff --git a/app/views/shared/_choose_group_avatar_button.html.haml b/app/views/shared/_choose_group_avatar_button.html.haml index 94295970acf..75c65520350 100644 --- a/app/views/shared/_choose_group_avatar_button.html.haml +++ b/app/views/shared/_choose_group_avatar_button.html.haml @@ -1,7 +1,4 @@ -%button.choose-btn.btn.btn-sm.js-choose-group-avatar-button{ type: 'button' } - %i.fa.fa-paperclip - %span Choose File ... - -%span.file_name.js-avatar-filename File name... -= f.file_field :avatar, class: 'js-group-avatar-input hidden' -.light The maximum file size allowed is 200KB. +%button.btn.js-choose-group-avatar-button{ type: 'button' }= _("Choose File ...") +%span.file_name.js-avatar-filename= _("No file chosen") += f.file_field :avatar, class: "js-group-avatar-input hidden" +.help-block= _("The maximum file size allowed is 200KB.") diff --git a/app/views/shared/_field.html.haml b/app/views/shared/_field.html.haml index 795447a9ca6..aea0a8fd8e0 100644 --- a/app/views/shared/_field.html.haml +++ b/app/views/shared/_field.html.haml @@ -7,6 +7,7 @@ - choices = field[:choices] - default_choice = field[:default_choice] - help = field[:help] +- disabled = disable_fields_service?(@service) .form-group - if type == "password" && value.present? @@ -15,14 +16,14 @@ = form.label name, title, class: "control-label" .col-sm-10 - if type == 'text' - = form.text_field name, class: "form-control", placeholder: placeholder, required: required + = form.text_field name, class: "form-control", placeholder: placeholder, required: required, disabled: disabled - elsif type == 'textarea' - = form.text_area name, rows: 5, class: "form-control", placeholder: placeholder, required: required + = form.text_area name, rows: 5, class: "form-control", placeholder: placeholder, required: required, disabled: disabled - elsif type == 'checkbox' - = form.check_box name + = form.check_box name, disabled: disabled - elsif type == 'select' - = form.select name, options_for_select(choices, value ? value : default_choice), {}, { class: "form-control" } + = form.select name, options_for_select(choices, value ? value : default_choice), {}, { class: "form-control", disabled: disabled} - elsif type == 'password' - = form.password_field name, autocomplete: "new-password", class: "form-control", required: value.blank? && :required + = form.password_field name, autocomplete: "new-password", class: "form-control", required: value.blank? && required, disabled: disabled - if help %span.help-block= help diff --git a/app/views/shared/_recaptcha_form.html.haml b/app/views/shared/_recaptcha_form.html.haml index 0e816870f15..93a4301f366 100644 --- a/app/views/shared/_recaptcha_form.html.haml +++ b/app/views/shared/_recaptcha_form.html.haml @@ -1,9 +1,10 @@ - resource_name = spammable.class.model_name.singular - humanized_resource_name = spammable.class.model_name.human.downcase - script = local_assigns.fetch(:script, true) +- method = params[:action] == 'create' ? :post : :put - has_submit = local_assigns.fetch(:has_submit, true) -= form_for resource_name, method: :post, html: { class: 'recaptcha-form js-recaptcha-form' } do |f| += form_for resource_name, method: method, html: { class: 'recaptcha-form js-recaptcha-form' } do |f| .recaptcha - params[resource_name].each do |field, value| = hidden_field(resource_name, field, value: value) diff --git a/app/views/shared/_service_settings.html.haml b/app/views/shared/_service_settings.html.haml index 7ca14ac93cc..61b39afb5d4 100644 --- a/app/views/shared/_service_settings.html.haml +++ b/app/views/shared/_service_settings.html.haml @@ -11,7 +11,7 @@ .form-group = form.label :active, "Active", class: "control-label" .col-sm-10 - = form.check_box :active + = form.check_box :active, disabled: disable_fields_service?(@service) - if @service.supported_events.present? .form-group diff --git a/changelogs/unreleased/16036-ignore-lost-found-folder-during-backup-on-a-volume.yml b/changelogs/unreleased/16036-ignore-lost-found-folder-during-backup-on-a-volume.yml new file mode 100644 index 00000000000..833650559a3 --- /dev/null +++ b/changelogs/unreleased/16036-ignore-lost-found-folder-during-backup-on-a-volume.yml @@ -0,0 +1,5 @@ +--- +title: "Ignore lost+found folder during backup on a volume" +merge_request: 16036 +author: Julien Millau +type: fixed
\ No newline at end of file diff --git a/changelogs/unreleased/16117-improve-search-for-issues.yml b/changelogs/unreleased/16117-improve-search-for-issues.yml new file mode 100644 index 00000000000..92d5820ddd2 --- /dev/null +++ b/changelogs/unreleased/16117-improve-search-for-issues.yml @@ -0,0 +1,5 @@ +--- +title: Improve search query for issues. +merge_request: +author: +type: performance diff --git a/changelogs/unreleased/20035-pause-resume-runners.yml b/changelogs/unreleased/20035-pause-resume-runners.yml new file mode 100644 index 00000000000..98757e60683 --- /dev/null +++ b/changelogs/unreleased/20035-pause-resume-runners.yml @@ -0,0 +1,5 @@ +--- +title: Add pause/resume button to project runners +merge_request: 16032 +author: Mario de la Ossa +type: added diff --git a/changelogs/unreleased/24347-dont-post-system-note-when-branch-creation-fails.yml b/changelogs/unreleased/24347-dont-post-system-note-when-branch-creation-fails.yml new file mode 100644 index 00000000000..61153ad4f1a --- /dev/null +++ b/changelogs/unreleased/24347-dont-post-system-note-when-branch-creation-fails.yml @@ -0,0 +1,5 @@ +--- +title: Fix when branch creation fails don't post system note +merge_request: +author: Mateusz Bajorski +type: fixed diff --git a/changelogs/unreleased/31995-project-limit-default-fix.yml b/changelogs/unreleased/31995-project-limit-default-fix.yml new file mode 100644 index 00000000000..4f25eb34b45 --- /dev/null +++ b/changelogs/unreleased/31995-project-limit-default-fix.yml @@ -0,0 +1,5 @@ +--- +title: User#projects_limit remove DB default and added NOT NULL constraint +merge_request: 16165 +author: Mario de la Ossa +type: fixed diff --git a/changelogs/unreleased/32364-updating-slack-notification-not-working-by-api.yml b/changelogs/unreleased/32364-updating-slack-notification-not-working-by-api.yml new file mode 100644 index 00000000000..e3fae55c6f0 --- /dev/null +++ b/changelogs/unreleased/32364-updating-slack-notification-not-working-by-api.yml @@ -0,0 +1,5 @@ +--- +title: Support new chat notifications parameters in Services API +merge_request: 11435 +author: +type: added diff --git a/changelogs/unreleased/34534-switch-to-axios.yml b/changelogs/unreleased/34534-switch-to-axios.yml new file mode 100644 index 00000000000..1200272c9eb --- /dev/null +++ b/changelogs/unreleased/34534-switch-to-axios.yml @@ -0,0 +1,5 @@ +--- +title: Fix some POST/DELETE requests in IE by switching some bundles to Axios for Ajax requests +merge_request: 15951 +author: +type: fixed diff --git a/changelogs/unreleased/36782-replace-team-user-role-with-add_role-user-in-specs.yml b/changelogs/unreleased/36782-replace-team-user-role-with-add_role-user-in-specs.yml new file mode 100644 index 00000000000..8773ac73a75 --- /dev/null +++ b/changelogs/unreleased/36782-replace-team-user-role-with-add_role-user-in-specs.yml @@ -0,0 +1,5 @@ +--- +title: Replace '.team << [user, role]' with 'add_role(user)' in specs +merge_request: 16069 +author: "@blackst0ne" +type: other diff --git a/changelogs/unreleased/37843-ci-trace-ansi-colours-256-bold-have-no-css-due-wrongly-ansi2html-light-color-variant-conversion-feature.yml b/changelogs/unreleased/37843-ci-trace-ansi-colours-256-bold-have-no-css-due-wrongly-ansi2html-light-color-variant-conversion-feature.yml new file mode 100644 index 00000000000..abf98cd2af4 --- /dev/null +++ b/changelogs/unreleased/37843-ci-trace-ansi-colours-256-bold-have-no-css-due-wrongly-ansi2html-light-color-variant-conversion-feature.yml @@ -0,0 +1,5 @@ +--- +title: Fix ANSI 256 bold colors in pipelines job output +merge_request: +author: +type: fixed diff --git a/changelogs/unreleased/38596-fix-backspace-visual-token-clearing.yml b/changelogs/unreleased/38596-fix-backspace-visual-token-clearing.yml new file mode 100644 index 00000000000..4a9d0b66a8c --- /dev/null +++ b/changelogs/unreleased/38596-fix-backspace-visual-token-clearing.yml @@ -0,0 +1,5 @@ +--- +title: Clears visual token on second backspace +merge_request: +author: Martin Wortschack +type: fixed diff --git a/changelogs/unreleased/40274-user-settings-breadcrumbs.yml b/changelogs/unreleased/40274-user-settings-breadcrumbs.yml new file mode 100644 index 00000000000..1f381668aca --- /dev/null +++ b/changelogs/unreleased/40274-user-settings-breadcrumbs.yml @@ -0,0 +1,5 @@ +--- +title: Fix breadcrumbs in User Settings +merge_request: 16172 +author: rfwatson +type: fixed diff --git a/changelogs/unreleased/40453-fix-api-endpoints-to-edit-wiki-pages-where-project-belongs-to-a-group.yml b/changelogs/unreleased/40453-fix-api-endpoints-to-edit-wiki-pages-where-project-belongs-to-a-group.yml new file mode 100644 index 00000000000..30917098a95 --- /dev/null +++ b/changelogs/unreleased/40453-fix-api-endpoints-to-edit-wiki-pages-where-project-belongs-to-a-group.yml @@ -0,0 +1,5 @@ +--- +title: Fix API endpoints to edit wiki pages where project belongs to a group +merge_request: 16170 +author: +type: fixed diff --git a/changelogs/unreleased/40533-groups-tree-updates.yml b/changelogs/unreleased/40533-groups-tree-updates.yml new file mode 100644 index 00000000000..1bc0aa90f9e --- /dev/null +++ b/changelogs/unreleased/40533-groups-tree-updates.yml @@ -0,0 +1,6 @@ +--- +title: Update groups tree to use GitLab SVG icons, add last updated at information + for projects +merge_request: 15980 +author: +type: changed diff --git a/changelogs/unreleased/40780-choose-file.yml b/changelogs/unreleased/40780-choose-file.yml new file mode 100644 index 00000000000..73e59dfcce8 --- /dev/null +++ b/changelogs/unreleased/40780-choose-file.yml @@ -0,0 +1,5 @@ +--- +title: Update Browse file to Choose file in all occurences +merge_request: +author: +type: other diff --git a/changelogs/unreleased/41054-disable-creation-of-new-kubernetes-integrations.yml b/changelogs/unreleased/41054-disable-creation-of-new-kubernetes-integrations.yml new file mode 100644 index 00000000000..b960b14624c --- /dev/null +++ b/changelogs/unreleased/41054-disable-creation-of-new-kubernetes-integrations.yml @@ -0,0 +1,6 @@ +--- +title: Disable creation of new Kubernetes Integrations unless they're active or created + from template +merge_request: 41054 +author: +type: added diff --git a/changelogs/unreleased/41424-gitlab-rake-gitlab-import-repos-schedules-an-import.yml b/changelogs/unreleased/41424-gitlab-rake-gitlab-import-repos-schedules-an-import.yml new file mode 100644 index 00000000000..b495754a5a8 --- /dev/null +++ b/changelogs/unreleased/41424-gitlab-rake-gitlab-import-repos-schedules-an-import.yml @@ -0,0 +1,5 @@ +--- +title: Fix gitlab-rake gitlab:import:repos import schedule +merge_request: 16115 +author: +type: fixed diff --git a/changelogs/unreleased/41468-error-500-trying-to-view-a-merge-request-json-undefined-method-binary-for-nil-nilclass.yml b/changelogs/unreleased/41468-error-500-trying-to-view-a-merge-request-json-undefined-method-binary-for-nil-nilclass.yml new file mode 100644 index 00000000000..f69116382f0 --- /dev/null +++ b/changelogs/unreleased/41468-error-500-trying-to-view-a-merge-request-json-undefined-method-binary-for-nil-nilclass.yml @@ -0,0 +1,5 @@ +--- +title: Fix viewing merge request diffs where the underlying blobs are unavailable +merge_request: +author: +type: fixed diff --git a/changelogs/unreleased/bump_mysql_gem.yml b/changelogs/unreleased/bump_mysql_gem.yml new file mode 100644 index 00000000000..58166949d72 --- /dev/null +++ b/changelogs/unreleased/bump_mysql_gem.yml @@ -0,0 +1,5 @@ +--- +title: Bump mysql2 gem version from 0.4.5 to 0.4.10 +merge_request: +author: asaparov +type: other diff --git a/changelogs/unreleased/bvl-fix-unlinking-with-lfs-objects.yml b/changelogs/unreleased/bvl-fix-unlinking-with-lfs-objects.yml deleted file mode 100644 index 058d686e74c..00000000000 --- a/changelogs/unreleased/bvl-fix-unlinking-with-lfs-objects.yml +++ /dev/null @@ -1,6 +0,0 @@ ---- -title: Don't link LFS objects to a project when unlinking forks when they were already - linked -merge_request: 16006 -author: -type: fixed diff --git a/changelogs/unreleased/bvl-fork-public-project-to-private-namespace.yml b/changelogs/unreleased/bvl-fork-public-project-to-private-namespace.yml new file mode 100644 index 00000000000..b802625943d --- /dev/null +++ b/changelogs/unreleased/bvl-fork-public-project-to-private-namespace.yml @@ -0,0 +1,5 @@ +--- +title: Allow forking a public project to a private group +merge_request: 16050 +author: +type: changed diff --git a/changelogs/unreleased/change-issues-closed-at-background-migration.yml b/changelogs/unreleased/change-issues-closed-at-background-migration.yml new file mode 100644 index 00000000000..1c81c6a889e --- /dev/null +++ b/changelogs/unreleased/change-issues-closed-at-background-migration.yml @@ -0,0 +1,5 @@ +--- +title: Use a background migration for issues.closed_at +merge_request: +author: +type: other diff --git a/changelogs/unreleased/conditionally-eager-load-event-target-authors.yml b/changelogs/unreleased/conditionally-eager-load-event-target-authors.yml new file mode 100644 index 00000000000..a5f1a958fa8 --- /dev/null +++ b/changelogs/unreleased/conditionally-eager-load-event-target-authors.yml @@ -0,0 +1,5 @@ +--- +title: Eager load event target authors whenever possible +merge_request: +author: +type: performance diff --git a/changelogs/unreleased/da-handle-hashed-storage-repos-using-repo-import-task.yml b/changelogs/unreleased/da-handle-hashed-storage-repos-using-repo-import-task.yml new file mode 100644 index 00000000000..74a00d49ab3 --- /dev/null +++ b/changelogs/unreleased/da-handle-hashed-storage-repos-using-repo-import-task.yml @@ -0,0 +1,5 @@ +--- +title: Handle GitLab hashed storage repositories using the repo import task +merge_request: +author: +type: added diff --git a/changelogs/unreleased/dm-issue-move-transaction-error.yml b/changelogs/unreleased/dm-issue-move-transaction-error.yml deleted file mode 100644 index 0892169894a..00000000000 --- a/changelogs/unreleased/dm-issue-move-transaction-error.yml +++ /dev/null @@ -1,5 +0,0 @@ ---- -title: Execute project hooks and services after commit when moving an issue -merge_request: -author: -type: fixed diff --git a/changelogs/unreleased/fix-abuse-reports-link-url.yml b/changelogs/unreleased/fix-abuse-reports-link-url.yml new file mode 100644 index 00000000000..44c26f35984 --- /dev/null +++ b/changelogs/unreleased/fix-abuse-reports-link-url.yml @@ -0,0 +1,5 @@ +--- +title: Fix abuse reports link url in admin area navbar +merge_request: 16068 +author: megos +type: fixed diff --git a/changelogs/unreleased/fix-activity-inline-event-line-height.yml b/changelogs/unreleased/fix-activity-inline-event-line-height.yml new file mode 100644 index 00000000000..85e69567499 --- /dev/null +++ b/changelogs/unreleased/fix-activity-inline-event-line-height.yml @@ -0,0 +1,5 @@ +--- +title: Fix activity inline event line height on mobile +merge_request: 16121 +author: George Tsiolis +type: fixed diff --git a/changelogs/unreleased/fix-move-2fa-disable-button.yml b/changelogs/unreleased/fix-move-2fa-disable-button.yml new file mode 100644 index 00000000000..bac98ad5148 --- /dev/null +++ b/changelogs/unreleased/fix-move-2fa-disable-button.yml @@ -0,0 +1,5 @@ +--- +title: Move 2FA disable button +merge_request: 16177 +author: George Tsiolis +type: fixed diff --git a/changelogs/unreleased/fix-profile-settings-content-width.yml b/changelogs/unreleased/fix-profile-settings-content-width.yml new file mode 100644 index 00000000000..bf164dc587d --- /dev/null +++ b/changelogs/unreleased/fix-profile-settings-content-width.yml @@ -0,0 +1,5 @@ +--- +title: Adjust content width for User Settings, GPG Keys +merge_request: 16093 +author: George Tsiolis +type: fixed diff --git a/changelogs/unreleased/fix-profile-settings-sidebar-heading.yml b/changelogs/unreleased/fix-profile-settings-sidebar-heading.yml new file mode 100644 index 00000000000..75e0ea5612f --- /dev/null +++ b/changelogs/unreleased/fix-profile-settings-sidebar-heading.yml @@ -0,0 +1,5 @@ +--- +title: Keep typographic hierarchy in User Settings +merge_request: 16090 +author: George Tsiolis +type: fixed diff --git a/changelogs/unreleased/fix-remove-unnecessary-sidebar-element-alignment.yml b/changelogs/unreleased/fix-remove-unnecessary-sidebar-element-alignment.yml new file mode 100644 index 00000000000..24f6f62b934 --- /dev/null +++ b/changelogs/unreleased/fix-remove-unnecessary-sidebar-element-alignment.yml @@ -0,0 +1,5 @@ +--- +title: Remove unnecessary sidebar element realignment +merge_request: 16159 +author: George Tsiolis +type: fixed diff --git a/changelogs/unreleased/fj-40053-error-500-members-list.yml b/changelogs/unreleased/fj-40053-error-500-members-list.yml new file mode 100644 index 00000000000..8c82950bd41 --- /dev/null +++ b/changelogs/unreleased/fj-40053-error-500-members-list.yml @@ -0,0 +1,5 @@ +--- +title: Fixing error 500 when member exist but not the user +merge_request: 15970 +author: +type: fixed diff --git a/changelogs/unreleased/jivl-activate-repo-cookie-preferences.yml b/changelogs/unreleased/jivl-activate-repo-cookie-preferences.yml new file mode 100644 index 00000000000..778eaa84381 --- /dev/null +++ b/changelogs/unreleased/jivl-activate-repo-cookie-preferences.yml @@ -0,0 +1,5 @@ +--- +title: Added option to user preferences to enable the multi file editor +merge_request: 16056 +author: +type: added diff --git a/changelogs/unreleased/jramsay-4012-i18n-compare.yml b/changelogs/unreleased/jramsay-4012-i18n-compare.yml new file mode 100644 index 00000000000..ff15724be39 --- /dev/null +++ b/changelogs/unreleased/jramsay-4012-i18n-compare.yml @@ -0,0 +1,5 @@ +--- +title: Add i18n helpers to branch comparison view +merge_request: 16031 +author: James Ramsay +type: added diff --git a/changelogs/unreleased/jramsay-41590-add-readme-case.yml b/changelogs/unreleased/jramsay-41590-add-readme-case.yml new file mode 100644 index 00000000000..37b2bd44e0e --- /dev/null +++ b/changelogs/unreleased/jramsay-41590-add-readme-case.yml @@ -0,0 +1,5 @@ +--- +title: Fix inconsistent downcase of filenames in prefilled `Add` commit messages +merge_request: 16232 +author: James Ramsay +type: fixed diff --git a/changelogs/unreleased/mk-no-op-delete-conflicting-redirects.yml b/changelogs/unreleased/mk-no-op-delete-conflicting-redirects.yml new file mode 100644 index 00000000000..37fdb1df6df --- /dev/null +++ b/changelogs/unreleased/mk-no-op-delete-conflicting-redirects.yml @@ -0,0 +1,6 @@ +--- +title: Prevent excessive DB load due to faulty DeleteConflictingRedirectRoutes background + migration +merge_request: 16205 +author: +type: fixed diff --git a/changelogs/unreleased/osw-introduce-merge-request-statistics.yml b/changelogs/unreleased/osw-introduce-merge-request-statistics.yml new file mode 100644 index 00000000000..fed7c2141fb --- /dev/null +++ b/changelogs/unreleased/osw-introduce-merge-request-statistics.yml @@ -0,0 +1,5 @@ +--- +title: Cache merged and closed events data in merge_request_metrics table +merge_request: +author: +type: performance diff --git a/changelogs/unreleased/pawel-reduce_cardinality_of_prometheus_metrics.yml b/changelogs/unreleased/pawel-reduce_cardinality_of_prometheus_metrics.yml deleted file mode 100644 index 0cee0b634d6..00000000000 --- a/changelogs/unreleased/pawel-reduce_cardinality_of_prometheus_metrics.yml +++ /dev/null @@ -1,5 +0,0 @@ ---- -title: Reduce the number of buckets in gitlab_cache_operation_duration_seconds metric -merge_request: 15881 -author: -type: changed diff --git a/changelogs/unreleased/sh-catch-invalid-uri-markdown.yml b/changelogs/unreleased/sh-catch-invalid-uri-markdown.yml new file mode 100644 index 00000000000..9b0233fe988 --- /dev/null +++ b/changelogs/unreleased/sh-catch-invalid-uri-markdown.yml @@ -0,0 +1,5 @@ +--- +title: Gracefully handle garbled URIs in Markdown +merge_request: +author: +type: fixed diff --git a/changelogs/unreleased/sh-make-kib-human.yml b/changelogs/unreleased/sh-make-kib-human.yml new file mode 100644 index 00000000000..c40bb34fa4a --- /dev/null +++ b/changelogs/unreleased/sh-make-kib-human.yml @@ -0,0 +1,5 @@ +--- +title: Humanize the units of "Showing last X KiB of log" in job trace +merge_request: +author: +type: fixed diff --git a/changelogs/unreleased/sh-optimize-commit-stats.yml b/changelogs/unreleased/sh-optimize-commit-stats.yml new file mode 100644 index 00000000000..8c1be1252fb --- /dev/null +++ b/changelogs/unreleased/sh-optimize-commit-stats.yml @@ -0,0 +1,5 @@ +--- +title: Speed up generation of commit stats by using Rugged native methods +merge_request: +author: +type: performance diff --git a/changelogs/unreleased/sh-validate-path-project-import.yml b/changelogs/unreleased/sh-validate-path-project-import.yml new file mode 100644 index 00000000000..acad66c0ab2 --- /dev/null +++ b/changelogs/unreleased/sh-validate-path-project-import.yml @@ -0,0 +1,5 @@ +--- +title: Avoid leaving a push event empty if payload cannot be created +merge_request: +author: +type: fixed diff --git a/changelogs/unreleased/show_proper_labels_in_board_issue_sidebar_when_issue_is_closed.yml b/changelogs/unreleased/show_proper_labels_in_board_issue_sidebar_when_issue_is_closed.yml new file mode 100644 index 00000000000..c2ab34b20a5 --- /dev/null +++ b/changelogs/unreleased/show_proper_labels_in_board_issue_sidebar_when_issue_is_closed.yml @@ -0,0 +1,5 @@ +--- +title: show None when issue is in closed list and no labels assigned +merge_request: 15976 +author: Christiaan Van den Poel +type: fixed diff --git a/config.ru b/config.ru index 065ce59932f..de0400f4f67 100644 --- a/config.ru +++ b/config.ru @@ -17,6 +17,11 @@ end require ::File.expand_path('../config/environment', __FILE__) +warmup do |app| + client = Rack::MockRequest.new(app) + client.get('/') +end + map ENV['RAILS_RELATIVE_URL_ROOT'] || "/" do run Gitlab::Application end diff --git a/config/initializers/active_record_data_types.rb b/config/initializers/active_record_data_types.rb index fef591c397d..0359e14b232 100644 --- a/config/initializers/active_record_data_types.rb +++ b/config/initializers/active_record_data_types.rb @@ -79,3 +79,8 @@ elsif Gitlab::Database.mysql? NATIVE_DATABASE_TYPES[:datetime_with_timezone] = { name: 'timestamp' } end end + +# Ensure `datetime_with_timezone` columns are correctly written to schema.rb +if (ActiveRecord::Base.connection.active? rescue false) + ActiveRecord::Base.connection.send :reload_type_map +end diff --git a/config/initializers/asset_sync.rb b/config/initializers/asset_sync.rb index db8500f6231..7f3934853fa 100644 --- a/config/initializers/asset_sync.rb +++ b/config/initializers/asset_sync.rb @@ -14,8 +14,8 @@ AssetSync.configure do |config| config.fog_directory = ENV['FOG_DIRECTORY'] if ENV.has_key?('FOG_DIRECTORY') config.fog_region = ENV['FOG_REGION'] if ENV.has_key?('FOG_REGION') - config.aws_access_key_id = ENV['AWS_ACCESS_KEY_ID'] if ENV.has_key?('AWS_ACCESS_KEY_ID') - config.aws_secret_access_key = ENV['AWS_SECRET_ACCESS_KEY'] if ENV.has_key?('AWS_SECRET_ACCESS_KEY') + config.aws_access_key_id = ENV['ASSETS_AWS_ACCESS_KEY_ID'] if ENV.has_key?('ASSETS_AWS_ACCESS_KEY_ID') + config.aws_secret_access_key = ENV['ASSETS_AWS_SECRET_ACCESS_KEY'] if ENV.has_key?('ASSETS_AWS_SECRET_ACCESS_KEY') config.aws_reduced_redundancy = ENV['AWS_REDUCED_REDUNDANCY'] == true if ENV.has_key?('AWS_REDUCED_REDUNDANCY') config.rackspace_username = ENV['RACKSPACE_USERNAME'] if ENV.has_key?('RACKSPACE_USERNAME') diff --git a/config/routes/project.rb b/config/routes/project.rb index 905c906b194..d780a7f4b9a 100644 --- a/config/routes/project.rb +++ b/config/routes/project.rb @@ -383,8 +383,8 @@ constraints(ProjectUrlConstrainer.new) do resources :runners, only: [:index, :edit, :update, :destroy, :show] do member do - get :resume - get :pause + post :resume + post :pause end collection do diff --git a/db/fixtures/development/06_teams.rb b/db/fixtures/development/06_teams.rb index 86e0a38aae1..b218f4e71fd 100644 --- a/db/fixtures/development/06_teams.rb +++ b/db/fixtures/development/06_teams.rb @@ -14,7 +14,7 @@ Sidekiq::Testing.inline! do Project.all.each do |project| User.all.sample(4).each do |user| - if project.team << [user, Gitlab::Access.values.sample] + if project.add_role(user, Gitlab::Access.sym_options.keys.sample) print '.' else print 'F' diff --git a/db/migrate/20171019141859_fix_dev_timezone_schema.rb b/db/migrate/20171019141859_fix_dev_timezone_schema.rb new file mode 100644 index 00000000000..fb7c17dd747 --- /dev/null +++ b/db/migrate/20171019141859_fix_dev_timezone_schema.rb @@ -0,0 +1,25 @@ +class FixDevTimezoneSchema < ActiveRecord::Migration + include Gitlab::Database::MigrationHelpers + + # The this migrations tries to help solve unwanted changes to `schema.rb` + # while developing GitLab. Installations created before we started using + # `datetime_with_timezone` are likely to face this problem. Updating those + # columns to the new type should help fix this. + + # Set this constant to true if this migration requires downtime. + DOWNTIME = false + + TIMEZONE_TABLES = %i(appearances ci_group_variables ci_pipeline_schedule_variables events gpg_keys gpg_signatures project_auto_devops) + + def up + return unless Rails.env.development? || Rails.env.test? + + TIMEZONE_TABLES.each do |table| + change_column table, :created_at, :datetime_with_timezone + change_column table, :updated_at, :datetime_with_timezone + end + end + + def down + end +end diff --git a/db/migrate/20171106151218_issues_moved_to_id_foreign_key.rb b/db/migrate/20171106151218_issues_moved_to_id_foreign_key.rb index 8d2ceb8cc18..6395462384b 100644 --- a/db/migrate/20171106151218_issues_moved_to_id_foreign_key.rb +++ b/db/migrate/20171106151218_issues_moved_to_id_foreign_key.rb @@ -15,8 +15,20 @@ class IssuesMovedToIdForeignKey < ActiveRecord::Migration self.table_name = 'issues' def self.with_orphaned_moved_to_issues - where('NOT EXISTS (SELECT true FROM issues WHERE issues.id = issues.moved_to_id)') - .where('moved_to_id IS NOT NULL') + if Gitlab::Database.postgresql? + # Be careful to use a second table here for comparison otherwise we'll null + # out all rows that don't have id == moved.to_id! + where('NOT EXISTS (SELECT true FROM issues B WHERE issues.moved_to_id = B.id)') + .where('moved_to_id IS NOT NULL') + else + # MySQL doesn't allow modification of the same table in a subquery, + # and using a temporary table isn't automatically guaranteed to work + # due to the MySQL query optimizer. See + # https://dev.mysql.com/doc/refman/5.7/en/update.html for more + # details. + joins('LEFT JOIN issues AS b ON issues.moved_to_id = b.id') + .where('issues.moved_to_id IS NOT NULL AND b.id IS NULL') + end end end diff --git a/db/migrate/20171127151038_add_events_related_columns_to_merge_request_metrics.rb b/db/migrate/20171127151038_add_events_related_columns_to_merge_request_metrics.rb new file mode 100644 index 00000000000..18af697cf88 --- /dev/null +++ b/db/migrate/20171127151038_add_events_related_columns_to_merge_request_metrics.rb @@ -0,0 +1,37 @@ +class AddEventsRelatedColumnsToMergeRequestMetrics < ActiveRecord::Migration + include Gitlab::Database::MigrationHelpers + + DOWNTIME = false + + disable_ddl_transaction! + + def up + change_table :merge_request_metrics do |t| + t.references :merged_by, references: :users + t.references :latest_closed_by, references: :users + end + + add_column :merge_request_metrics, :latest_closed_at, :datetime_with_timezone + + add_concurrent_foreign_key :merge_request_metrics, :users, + column: :merged_by_id, + on_delete: :nullify + + add_concurrent_foreign_key :merge_request_metrics, :users, + column: :latest_closed_by_id, + on_delete: :nullify + end + + def down + if foreign_keys_for(:merge_request_metrics, :merged_by_id).any? + remove_foreign_key :merge_request_metrics, column: :merged_by_id + end + + if foreign_keys_for(:merge_request_metrics, :latest_closed_by_id).any? + remove_foreign_key :merge_request_metrics, column: :latest_closed_by_id + end + + remove_columns :merge_request_metrics, + :merged_by_id, :latest_closed_by_id, :latest_closed_at + end +end diff --git a/db/migrate/20171216111734_clean_up_for_members.rb b/db/migrate/20171216111734_clean_up_for_members.rb new file mode 100644 index 00000000000..22e0997dce6 --- /dev/null +++ b/db/migrate/20171216111734_clean_up_for_members.rb @@ -0,0 +1,31 @@ +# See http://doc.gitlab.com/ce/development/migration_style_guide.html +# for more information on how to write migrations for GitLab. + +class CleanUpForMembers < ActiveRecord::Migration + include Gitlab::Database::MigrationHelpers + + # Set this constant to true if this migration requires downtime. + DOWNTIME = false + + disable_ddl_transaction! + + class Member < ActiveRecord::Base + include EachBatch + + self.table_name = 'members' + end + + def up + condition = <<~EOF.squish + invite_token IS NULL AND + NOT EXISTS (SELECT 1 FROM users WHERE users.id = members.user_id) + EOF + + Member.each_batch(of: 10_000) do |batch| + batch.where(condition).delete_all + end + end + + def down + end +end diff --git a/db/migrate/20171216112339_add_foreign_key_for_members.rb b/db/migrate/20171216112339_add_foreign_key_for_members.rb new file mode 100644 index 00000000000..be17769be6a --- /dev/null +++ b/db/migrate/20171216112339_add_foreign_key_for_members.rb @@ -0,0 +1,21 @@ +# See http://doc.gitlab.com/ce/development/migration_style_guide.html +# for more information on how to write migrations for GitLab. + +class AddForeignKeyForMembers < ActiveRecord::Migration + include Gitlab::Database::MigrationHelpers + + # Set this constant to true if this migration requires downtime. + DOWNTIME = false + + disable_ddl_transaction! + + def up + add_concurrent_foreign_key(:members, + :users, + column: :user_id) + end + + def down + remove_foreign_key(:members, column: :user_id) + end +end diff --git a/db/migrate/20171229225929_change_user_project_limit_not_null_and_remove_default.rb b/db/migrate/20171229225929_change_user_project_limit_not_null_and_remove_default.rb new file mode 100644 index 00000000000..54fbbcf1a0d --- /dev/null +++ b/db/migrate/20171229225929_change_user_project_limit_not_null_and_remove_default.rb @@ -0,0 +1,38 @@ +# See http://doc.gitlab.com/ce/development/migration_style_guide.html +# for more information on how to write migrations for GitLab. + +class ChangeUserProjectLimitNotNullAndRemoveDefault < ActiveRecord::Migration + include Gitlab::Database::MigrationHelpers + + # Set this constant to true if this migration requires downtime. + DOWNTIME = false + + # When a migration requires downtime you **must** uncomment the following + # constant and define a short and easy to understand explanation as to why the + # migration requires downtime. + # DOWNTIME_REASON = '' + + # When using the methods "add_concurrent_index", "remove_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" or "remove_concurrent_index" methods make sure + # that either of them 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 or removing fails + # and can be retried or reverted easily. + # + # To disable transactions uncomment the following line and remove these + # comments: + # disable_ddl_transaction! + + def up + # Set Users#projects_limit to NOT NULL and remove the default value + change_column_null :users, :projects_limit, false + change_column_default :users, :projects_limit, nil + end + + def down + change_column_null :users, :projects_limit, true + change_column_default :users, :projects_limit, 10 + end +end diff --git a/db/post_migrate/20170907170235_delete_conflicting_redirect_routes.rb b/db/post_migrate/20170907170235_delete_conflicting_redirect_routes.rb index 3e84b295be4..033019c398e 100644 --- a/db/post_migrate/20170907170235_delete_conflicting_redirect_routes.rb +++ b/db/post_migrate/20170907170235_delete_conflicting_redirect_routes.rb @@ -2,36 +2,12 @@ # for more information on how to write migrations for GitLab. class DeleteConflictingRedirectRoutes < ActiveRecord::Migration - include Gitlab::Database::MigrationHelpers - - DOWNTIME = false - MIGRATION = 'DeleteConflictingRedirectRoutesRange'.freeze - BATCH_SIZE = 200 # At 200, I expect under 20s per batch, which is under our query timeout of 60s. - DELAY_INTERVAL = 12.seconds - - disable_ddl_transaction! - - class Route < ActiveRecord::Base - include EachBatch - - self.table_name = 'routes' - end - def up - say opening_message - - queue_background_migration_jobs_by_range_at_intervals(Route, MIGRATION, DELAY_INTERVAL, batch_size: BATCH_SIZE) + # No-op. + # See https://gitlab.com/gitlab-com/infrastructure/issues/3460#note_53223252 end def down # nothing end - - def opening_message - <<~MSG - Clean up redirect routes that conflict with regular routes. - See initial bug fix: - https://gitlab.com/gitlab-org/gitlab-ce/merge_requests/13357 - MSG - end end diff --git a/db/post_migrate/20171128214150_schedule_populate_merge_request_metrics_with_events_data.rb b/db/post_migrate/20171128214150_schedule_populate_merge_request_metrics_with_events_data.rb new file mode 100644 index 00000000000..547cc68e10e --- /dev/null +++ b/db/post_migrate/20171128214150_schedule_populate_merge_request_metrics_with_events_data.rb @@ -0,0 +1,68 @@ +# frozen_string_literal: true +# rubocop:disable GitlabSecurity/SqlInjection + +class SchedulePopulateMergeRequestMetricsWithEventsData < ActiveRecord::Migration + DOWNTIME = false + BATCH_SIZE = 10_000 + MIGRATION = 'PopulateMergeRequestMetricsWithEventsData' + + disable_ddl_transaction! + + class MergeRequest < ActiveRecord::Base + self.table_name = 'merge_requests' + + include ::EachBatch + end + + def up + merge_requests = MergeRequest.where("id IN (#{updatable_merge_requests_union_sql})").reorder(:id) + + say 'Scheduling `PopulateMergeRequestMetricsWithEventsData` jobs' + # It will update around 4_000_000 records in batches of 10_000 merge + # requests (running between 10 minutes) and should take around 66 hours to complete. + # Apparently, production PostgreSQL is able to vacuum 10k-20k dead_tuples by + # minute, and at maximum, each of these jobs should UPDATE 20k records. + # + # More information about the updates in `PopulateMergeRequestMetricsWithEventsData` class. + # + merge_requests.each_batch(of: BATCH_SIZE) do |relation, index| + range = relation.pluck('MIN(id)', 'MAX(id)').first + + BackgroundMigrationWorker.perform_in(index * 10.minutes, MIGRATION, range) + end + end + + def down + execute "update merge_request_metrics set latest_closed_at = null" + execute "update merge_request_metrics set latest_closed_by_id = null" + execute "update merge_request_metrics set merged_by_id = null" + end + + private + + # On staging: + # Planning time: 0.682 ms + # Execution time: 22033.158 ms + # + def updatable_merge_requests_union_sql + metrics_not_exists_clause = + 'NOT EXISTS (SELECT 1 FROM merge_request_metrics WHERE merge_request_metrics.merge_request_id = merge_requests.id)' + + without_metrics_data = <<-SQL.strip_heredoc + merge_request_metrics.merged_by_id IS NULL OR + merge_request_metrics.latest_closed_by_id IS NULL OR + merge_request_metrics.latest_closed_at IS NULL + SQL + + mrs_without_metrics_record = MergeRequest + .where(metrics_not_exists_clause) + .select(:id) + + mrs_without_events_data = MergeRequest + .joins('INNER JOIN merge_request_metrics ON merge_requests.id = merge_request_metrics.merge_request_id') + .where(without_metrics_data) + .select(:id) + + Gitlab::SQL::Union.new([mrs_without_metrics_record, mrs_without_events_data]).to_sql + end +end diff --git a/db/post_migrate/20171221140220_schedule_issues_closed_at_type_change.rb b/db/post_migrate/20171221140220_schedule_issues_closed_at_type_change.rb new file mode 100644 index 00000000000..be18c5866ae --- /dev/null +++ b/db/post_migrate/20171221140220_schedule_issues_closed_at_type_change.rb @@ -0,0 +1,45 @@ +# See http://doc.gitlab.com/ce/development/migration_style_guide.html +# for more information on how to write migrations for GitLab. + +class ScheduleIssuesClosedAtTypeChange < ActiveRecord::Migration + include Gitlab::Database::MigrationHelpers + + DOWNTIME = false + + disable_ddl_transaction! + + class Issue < ActiveRecord::Base + self.table_name = 'issues' + include EachBatch + + def self.to_migrate + where('closed_at IS NOT NULL') + end + end + + def up + return unless migrate_column_type? + + change_column_type_using_background_migration( + Issue.to_migrate, + :closed_at, + :datetime_with_timezone + ) + end + + def down + return if migrate_column_type? + + change_column_type_using_background_migration( + Issue.to_migrate, + :closed_at, + :datetime + ) + end + + def migrate_column_type? + # Some environments may have already executed the previous version of this + # migration, thus we don't need to migrate those environments again. + column_for('issues', 'closed_at').type == :datetime + end +end diff --git a/db/schema.rb b/db/schema.rb index cd3f87062ab..ccaf35b4d92 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: 20171222183504) do +ActiveRecord::Schema.define(version: 20171229225929) do # These are extensions that must be enabled in order to support this database enable_extension "plpgsql" @@ -657,8 +657,8 @@ ActiveRecord::Schema.define(version: 20171222183504) do t.datetime "created_at" t.datetime "updated_at" t.string "confirmation_token" - t.datetime "confirmed_at" - t.datetime "confirmation_sent_at" + t.datetime_with_timezone "confirmed_at" + t.datetime_with_timezone "confirmation_sent_at" end add_index "emails", ["confirmation_token"], name: "index_emails_on_confirmation_token", unique: true, using: :btree @@ -1056,6 +1056,9 @@ ActiveRecord::Schema.define(version: 20171222183504) do t.datetime "created_at", null: false t.datetime "updated_at", null: false t.integer "pipeline_id" + t.integer "merged_by_id" + t.integer "latest_closed_by_id" + t.datetime_with_timezone "latest_closed_at" end add_index "merge_request_metrics", ["first_deployed_to_production_at"], name: "index_merge_request_metrics_on_first_deployed_to_production_at", using: :btree @@ -1771,8 +1774,8 @@ ActiveRecord::Schema.define(version: 20171222183504) do add_index "user_agent_details", ["subject_id", "subject_type"], name: "index_user_agent_details_on_subject_id_and_subject_type", using: :btree create_table "user_custom_attributes", force: :cascade do |t| - t.datetime "created_at", null: false - t.datetime "updated_at", null: false + t.datetime_with_timezone "created_at", null: false + t.datetime_with_timezone "updated_at", null: false t.integer "user_id", null: false t.string "key", null: false t.string "value", null: false @@ -1806,7 +1809,7 @@ ActiveRecord::Schema.define(version: 20171222183504) do t.datetime "updated_at" t.string "name" t.boolean "admin", default: false, null: false - t.integer "projects_limit", default: 10 + t.integer "projects_limit", null: false t.string "skype", default: "", null: false t.string "linkedin", default: "", null: false t.string "twitter", default: "", null: false @@ -1990,11 +1993,14 @@ ActiveRecord::Schema.define(version: 20171222183504) do add_foreign_key "labels", "projects", name: "fk_7de4989a69", on_delete: :cascade add_foreign_key "lists", "boards", name: "fk_0d3f677137", on_delete: :cascade add_foreign_key "lists", "labels", name: "fk_7a5553d60f", on_delete: :cascade + add_foreign_key "members", "users", name: "fk_2e88fb7ce9", on_delete: :cascade add_foreign_key "merge_request_diff_commits", "merge_request_diffs", on_delete: :cascade add_foreign_key "merge_request_diff_files", "merge_request_diffs", on_delete: :cascade add_foreign_key "merge_request_diffs", "merge_requests", name: "fk_8483f3258f", on_delete: :cascade add_foreign_key "merge_request_metrics", "ci_pipelines", column: "pipeline_id", on_delete: :cascade add_foreign_key "merge_request_metrics", "merge_requests", on_delete: :cascade + add_foreign_key "merge_request_metrics", "users", column: "latest_closed_by_id", name: "fk_ae440388cc", on_delete: :nullify + add_foreign_key "merge_request_metrics", "users", column: "merged_by_id", name: "fk_7f28d925f3", on_delete: :nullify add_foreign_key "merge_requests", "ci_pipelines", column: "head_pipeline_id", name: "fk_fd82eae0b9", on_delete: :nullify add_foreign_key "merge_requests", "merge_request_diffs", column: "latest_merge_request_diff_id", name: "fk_06067f5644", on_delete: :nullify add_foreign_key "merge_requests", "milestones", name: "fk_6a5165a692", on_delete: :nullify diff --git a/doc/administration/integration/plantuml.md b/doc/administration/integration/plantuml.md index 93c3642a1f1..65f59b72690 100644 --- a/doc/administration/integration/plantuml.md +++ b/doc/administration/integration/plantuml.md @@ -58,30 +58,32 @@ our AsciiDoc snippets, wikis and repos using delimited blocks: - **Markdown** - ```plantuml - Bob -> Alice : hello - Alice -> Bob : Go Away - ``` + <pre> + ```plantuml + Bob -> Alice : hello + Alice -> Bob : Go Away + ``` + </pre> - **AsciiDoc** - ``` + <pre> [plantuml, format="png", id="myDiagram", width="200px"] -- Bob->Alice : hello Alice -> Bob : Go Away -- - ``` + </pre> - **reStructuredText** - ``` + <pre> .. plantuml:: :caption: Caption with **bold** and *italic* Bob -> Alice: hello Alice -> Bob: Go Away - ``` + </pre> You can also use the `uml::` directive for compatibility with [sphinxcontrib-plantuml](https://pypi.python.org/pypi/sphinxcontrib-plantuml), but please note that we currently only support the `caption` option. diff --git a/doc/api/services.md b/doc/api/services.md index 08df26db3ec..2928ab6cc75 100644 --- a/doc/api/services.md +++ b/doc/api/services.md @@ -1,5 +1,7 @@ # Services API +>**Note:** This API requires an access token with Master or Owner permissions + ## Asana Asana - Teamwork without email @@ -16,8 +18,10 @@ PUT /projects/:id/services/asana Parameters: -- `api_key` (**required**) - User API token. User must have access to task, all comments will be attributed to this user. -- `restrict_to_branch` (optional) - Comma-separated list of branches which will be automatically inspected. Leave blank to include all branches. +| Parameter | Type | Required | Description | +| --------- | ---- | -------- | ----------- | +| `api_key` | string | true | User API token. User must have access to task, all comments will be attributed to this user. | +| `restrict_to_branch` | string | false | Comma-separated list of branches which will be automatically inspected. Leave blank to include all branches. | ### Delete Asana service @@ -49,8 +53,10 @@ PUT /projects/:id/services/assembla Parameters: -- `token` (**required**) -- `subdomain` (optional) +| Parameter | Type | Required | Description | +| --------- | ---- | -------- | ----------- | +| `token` | string | true | The authentication token +| `subdomain` | string | false | The subdomain setting | ### Delete Assembla service @@ -84,10 +90,12 @@ PUT /projects/:id/services/bamboo Parameters: -- `bamboo_url` (**required**) - Bamboo root URL like https://bamboo.example.com -- `build_key` (**required**) - Bamboo build plan key like KEY -- `username` (**required**) - A user with API access, if applicable -- `password` (**required**) +| Parameter | Type | Required | Description | +| --------- | ---- | -------- | ----------- | +| `bamboo_url` | string | true | Bamboo root URL like https://bamboo.example.com | +| `build_key` | string | true | Bamboo build plan key like KEY | +| `username` | string | true | A user with API access, if applicable | +| `password` | string | true | Password of the user | ### Delete Atlassian Bamboo CI service @@ -105,6 +113,44 @@ Get Atlassian Bamboo CI service settings for a project. GET /projects/:id/services/bamboo ``` +## Bugzilla + +Bugzilla Issue Tracker + +### Create/Edit Buildkite service + +Set Bugzilla service for a project. + +``` +PUT /projects/:id/services/bugzilla +``` + +Parameters: + +| Parameter | Type | Required | Description | +| --------- | ---- | -------- | ----------- | +| `new_issue_url` | string | true | New Issue url | +| `issues_url` | string | true | Issue url | +| `project_url` | string | true | Project url | +| `description` | string | false | Description | +| `title` | string | false | Title | + +### Delete Bugzilla Service + +Delete Bugzilla service for a project. + +``` +DELETE /projects/:id/services/bugzilla +``` + +### Get Bugzilla Service Settings + +Get Bugzilla service settings for a project. + +``` +GET /projects/:id/services/bugzilla +``` + ## Buildkite Continuous integration and deployments @@ -119,9 +165,11 @@ PUT /projects/:id/services/buildkite Parameters: -- `token` (**required**) - Buildkite project GitLab token -- `project_url` (**required**) - https://buildkite.com/example/project -- `enable_ssl_verification` (optional) - Enable SSL verification +| Parameter | Type | Required | Description | +| --------- | ---- | -------- | ----------- | +| `token` | string | true | Buildkite project GitLab token | +| `project_url` | string | true | https://buildkite.com/example/project | +| `enable_ssl_verification` | boolean | false | Enable SSL verification | ### Delete Buildkite service @@ -153,9 +201,11 @@ PUT /projects/:id/services/campfire Parameters: -- `token` (**required**) -- `subdomain` (optional) -- `room` (optional) +| Parameter | Type | Required | Description | +| --------- | ---- | -------- | ----------- | +| `token` | string | true | Campfire token | +| `subdomain` | string | false | Campfire subdomain | +| `room` | string | false | Campfire room | ### Delete Campfire service @@ -187,11 +237,13 @@ PUT /projects/:id/services/custom-issue-tracker Parameters: -- `new_issue_url` (**required**) - New Issue url -- `issues_url` (**required**) - Issue url -- `project_url` (**required**) - Project url -- `description` (optional) - Custom issue tracker -- `title` (optional) - Custom Issue Tracker +| Parameter | Type | Required | Description | +| --------- | ---- | -------- | ----------- | +| `new_issue_url` | string | true | New Issue url +| `issues_url` | string | true | Issue url +| `project_url` | string | true | Project url +| `description` | string | false | Description +| `title` | string | false | Title ### Delete Custom Issue Tracker service @@ -223,9 +275,11 @@ PUT /projects/:id/services/drone-ci Parameters: -- `token` (**required**) - Drone CI project specific token -- `drone_url` (**required**) - http://drone.example.com -- `enable_ssl_verification` (optional) - Enable SSL verification +| Parameter | Type | Required | Description | +| --------- | ---- | -------- | ----------- | +| `token` | string | true | Drone CI project specific token | +| `drone_url` | string | true | http://drone.example.com | +| `enable_ssl_verification` | boolean | false | Enable SSL verification | ### Delete Drone CI service @@ -257,9 +311,11 @@ PUT /projects/:id/services/emails-on-push Parameters: -- `recipients` (**required**) - Emails separated by whitespace -- `disable_diffs` (optional) - Disable code diffs -- `send_from_committer_email` (optional) - Send from committer +| Parameter | Type | Required | Description | +| --------- | ---- | -------- | ----------- | +| `recipients` | string | true | Emails separated by whitespace | +| `disable_diffs` | boolean | false | Disable code diffs | +| `send_from_committer_email` | boolean | false | Send from committer | ### Delete Emails on push service @@ -291,7 +347,9 @@ PUT /projects/:id/services/external-wiki Parameters: -- `external_wiki_url` (**required**) - The URL of the external Wiki +| Parameter | Type | Required | Description | +| --------- | ---- | -------- | ----------- | +| `external_wiki_url` | string | true | The URL of the external Wiki | ### Delete External Wiki service @@ -323,7 +381,9 @@ PUT /projects/:id/services/flowdock Parameters: -- `token` (**required**) - Flowdock Git source token +| Parameter | Type | Required | Description | +| --------- | ---- | -------- | ----------- | +| `token` | string | true | Flowdock Git source token | ### Delete Flowdock service @@ -355,8 +415,10 @@ PUT /projects/:id/services/gemnasium Parameters: -- `api_key` (**required**) - Your personal API KEY on gemnasium.com -- `token` (**required**) - The project's slug on gemnasium.com +| Parameter | Type | Required | Description | +| --------- | ---- | -------- | ----------- | +| `api_key` | string | true | Your personal API KEY on gemnasium.com | +| `token` | string | true | The project's slug on gemnasium.com | ### Delete Gemnasium service @@ -388,12 +450,14 @@ PUT /projects/:id/services/hipchat Parameters: -- `token` (**required**) - Room token -- `color` (optional) -- `notify` (optional) -- `room` (optional) - Room name or ID -- `api_version` (optional) - Leave blank for default (v2) -- `server` (optional) - Leave blank for default. https://hipchat.example.com +| Parameter | Type | Required | Description | +| --------- | ---- | -------- | ----------- | +| `token` | string | true | Room token | +| `color` | string | false | The room color | +| `notify` | boolean | false | Enable notifications | +| `room` | string | false |Room name or ID | +| `api_version` | string | false | Leave blank for default (v2) | +| `server` | string | false | Leave blank for default. https://hipchat.example.com | ### Delete HipChat service @@ -427,11 +491,13 @@ PUT /projects/:id/services/irker Parameters: -- `recipients` (**required**) - Recipients/channels separated by whitespaces -- `default_irc_uri` (optional) - irc://irc.network.net:6697/ -- `server_port` (optional) - 6659 -- `server_host` (optional) - localhost -- `colorize_messages` (optional) +| Parameter | Type | Required | Description | +| --------- | ---- | -------- | ----------- | +| `recipients` | string | true | Recipients/channels separated by whitespaces | +| `default_irc_uri` | string | false | irc://irc.network.net:6697/ | +| `server_host` | string | false | localhost | +| `server_port` | integer | false | 6659 | +| `colorize_messages` | boolean | false | Colorize messages | ### Delete Irker (IRC gateway) service @@ -474,7 +540,9 @@ Set JIRA service for a project. PUT /projects/:id/services/jira ``` -| Attribute | Type | Required | Description | +Parameters: + +| Parameter | Type | Required | Description | | --------- | ---- | -------- | ----------- | | `url` | string | yes | The URL to the JIRA project which is being linked to this GitLab project, e.g., `https://jira.example.com`. | | `project_key` | string | yes | The short identifier for your JIRA project, all uppercase, e.g., `PROJ`. | @@ -494,6 +562,9 @@ DELETE /projects/:id/services/jira Kubernetes / Openshift integration +CAUTION: **Warning:** +Kubernetes service integration has been deprecated in GitLab 10.3. API service endpoints will continue to work as long as the Kubernetes service is active, however if the service is inactive API endpoints will automatically return a `400 Bad Request`. Read [GitLab 10.3 release post](https://about.gitlab.com/2017/12/22/gitlab-10-3-released/#kubernetes-integration-service) for more information. + ### Create/Edit Kubernetes service Set Kubernetes service for a project. @@ -569,7 +640,7 @@ PUT /projects/:id/services/slack-slash-commands Parameters: -| Attribute | Type | Required | Description | +| Parameter | Type | Required | Description | | --------- | ---- | -------- | ----------- | | `token` | string | yes | The Slack token | @@ -604,7 +675,7 @@ PUT /projects/:id/services/mattermost-slash-commands Parameters: -| Attribute | Type | Required | Description | +| Parameter | Type | Required | Description | | --------- | ---- | -------- | ----------- | | `token` | string | yes | The Mattermost token | | `username` | string | no | The username to use to post the message | @@ -665,7 +736,7 @@ PUT /projects/:id/services/pipelines-email Parameters: -| Attribute | Type | Required | Description | +| Parameter | Type | Required | Description | | --------- | ---- | -------- | ----------- | | `recipients` | string | yes | Comma-separated list of recipient email addresses | | `add_pusher` | boolean | no | Add pusher to recipients list | @@ -701,8 +772,10 @@ PUT /projects/:id/services/pivotaltracker Parameters: -- `token` (**required**) -- `restrict_to_branch` (optional) - Comma-separated list of branches which will be automatically inspected. Leave blank to include all branches. +| Parameter | Type | Required | Description | +| --------- | ---- | -------- | ----------- | +| `token` | string | true | The PivotalTracker token | +| `restrict_to_branch` | boolean | false | Comma-separated list of branches which will be automatically inspected. Leave blank to include all branches. | ### Delete PivotalTracker service @@ -720,6 +793,40 @@ Get PivotalTracker service settings for a project. GET /projects/:id/services/pivotaltracker ``` +## Prometheus + +Prometheus is a powerful time-series monitoring service. + +### Create/Edit Prometheus service + +Set Prometheus service for a project. + +``` +PUT /projects/:id/services/prometheus +``` + +Parameters: + +| Parameter | Type | Required | Description | +| --------- | ---- | -------- | ----------- | +| `api_url` | string | true | Prometheus API Base URL, like http://prometheus.example.com/ | + +### Delete Prometheus service + +Delete Prometheus service for a project. + +``` +DELETE /projects/:id/services/prometheus +``` + +### Get Prometheus service settings + +Get Prometheus service settings for a project. + +``` +GET /projects/:id/services/prometheus +``` + ## Pushover Pushover makes it easy to get real-time notifications on your Android device, iPhone, iPad, and Desktop. @@ -734,11 +841,13 @@ PUT /projects/:id/services/pushover Parameters: -- `api_key` (**required**) - Your application key -- `user_key` (**required**) - Your user key -- `priority` (**required**) -- `device` (optional) - Leave blank for all active devices -- `sound` (optional) +| Parameter | Type | Required | Description | +| --------- | ---- | -------- | ----------- | +| `api_key` | string | true | Your application key | +| `user_key` | string | true | Your user key | +| `priority` | string | true | The priority | +| `device` | string | false | Leave blank for all active devices | +| `sound` | string | false | The sound of the notification | ### Delete Pushover service @@ -770,10 +879,12 @@ PUT /projects/:id/services/redmine Parameters: -- `new_issue_url` (**required**) - New Issue url -- `project_url` (**required**) - Project url -- `issues_url` (**required**) - Issue url -- `description` (optional) - Redmine issue tracker +| Parameter | Type | Required | Description | +| --------- | ---- | -------- | ----------- | +| `new_issue_url` | string | true | New Issue url | +| `project_url` | string | true | Project url | +| `issues_url` | string | true | Issue url | +| `description` | string | false | Description | ### Delete Redmine service @@ -803,11 +914,33 @@ Set Slack service for a project. PUT /projects/:id/services/slack ``` +>**Note:** Specific event parameters (e.g. `push_events` flag and `push_channel`) were [introduced in v10.4][11435] + Parameters: -- `webhook` (**required**) - https://hooks.slack.com/services/... -- `username` (optional) - username -- `channel` (optional) - #channel +| Parameter | Type | Required | Description | +| --------- | ---- | -------- | ----------- | +| `webhook` | string | true | https://hooks.slack.com/services/... | +| `username` | string | false | username | +| `channel` | string | false | Default channel to use if others are not configured | +| `notify_only_broken_pipelines` | boolean | false | Send notifications for broken pipelines | +| `notify_only_default_branch` | boolean | false | Send notifications only for the default branch | +| `push_events` | boolean | false | Enable notifications for push events | +| `issues_events` | boolean | false | Enable notifications for issue events | +| `confidential_issues_events` | boolean | false | Enable notifications for confidential issue events | +| `merge_requests_events` | boolean | false | Enable notifications for merge request events | +| `tag_push_events` | boolean | false | Enable notifications for tag push events | +| `note_events` | boolean | false | Enable notifications for note events | +| `pipeline_events` | boolean | false | Enable notifications for pipeline events | +| `wiki_page_events` | boolean | false | Enable notifications for wiki page events | +| `push_channel` | string | false | The name of the channel to receive push events notifications | +| `issue_channel` | string | false | The name of the channel to receive issues events notifications | +| `confidential_issue_channel` | string | false | The name of the channel to receive confidential issues events notifications | +| `merge_request_channel` | string | false | The name of the channel to receive merge request events notifications | +| `note_channel` | string | false | The name of the channel to receive note events notifications | +| `tag_push_channel` | string | false | The name of the channel to receive tag push events notifications | +| `pipeline_channel` | string | false | The name of the channel to receive pipeline events notifications | +| `wiki_page_channel` | string | false | The name of the channel to receive wiki page events notifications | ### Delete Slack service @@ -825,6 +958,40 @@ Get Slack service settings for a project. GET /projects/:id/services/slack ``` +## Microsoft Teams + +Group Chat Software + +### Create/Edit Microsoft Teams service + +Set Microsoft Teams service for a project. + +``` +PUT /projects/:id/services/microsoft_teams +``` + +Parameters: + +| Parameter | Type | Required | Description | +| --------- | ---- | -------- | ----------- | +| `webhook` | string | true | The Microsoft Teams webhook. e.g. https://outlook.office.com/webhook/... | + +### Delete Microsoft Teams service + +Delete Microsoft Teams service for a project. + +``` +DELETE /projects/:id/services/microsoft_teams +``` + +### Get Microsoft Teams service settings + +Get Microsoft Teams service settings for a project. + +``` +GET /projects/:id/services/microsoft_teams +``` + ## Mattermost notifications Receive event notifications in Mattermost @@ -837,11 +1004,33 @@ Set Mattermost service for a project. PUT /projects/:id/services/mattermost ``` +>**Note:** Specific event parameters (e.g. `push_events` flag and `push_channel`) were [introduced in v10.4][11435] + Parameters: -- `webhook` (**required**) - https://mattermost.example/hooks/1298aff... -- `username` (optional) - username -- `channel` (optional) - #channel +| Parameter | Type | Required | Description | +| --------- | ---- | -------- | ----------- | +| `webhook` | string | true | The Mattermost webhook. e.g. http://mattermost_host/hooks/... | +| `username` | string | false | username | +| `channel` | string | false | Default channel to use if others are not configured | +| `notify_only_broken_pipelines` | boolean | false | Send notifications for broken pipelines | +| `notify_only_default_branch` | boolean | false | Send notifications only for the default branch | +| `push_events` | boolean | false | Enable notifications for push events | +| `issues_events` | boolean | false | Enable notifications for issue events | +| `confidential_issues_events` | boolean | false | Enable notifications for confidential issue events | +| `merge_requests_events` | boolean | false | Enable notifications for merge request events | +| `tag_push_events` | boolean | false | Enable notifications for tag push events | +| `note_events` | boolean | false | Enable notifications for note events | +| `pipeline_events` | boolean | false | Enable notifications for pipeline events | +| `wiki_page_events` | boolean | false | Enable notifications for wiki page events | +| `push_channel` | string | false | The name of the channel to receive push events notifications | +| `issue_channel` | string | false | The name of the channel to receive issues events notifications | +| `confidential_issue_channel` | string | false | The name of the channel to receive confidential issues events notifications | +| `merge_request_channel` | string | false | The name of the channel to receive merge request events notifications | +| `note_channel` | string | false | The name of the channel to receive note events notifications | +| `tag_push_channel` | string | false | The name of the channel to receive tag push events notifications | +| `pipeline_channel` | string | false | The name of the channel to receive pipeline events notifications | +| `wiki_page_channel` | string | false | The name of the channel to receive wiki page events notifications | ### Delete Mattermost notifications service @@ -875,10 +1064,12 @@ PUT /projects/:id/services/teamcity Parameters: -- `teamcity_url` (**required**) - TeamCity root URL like https://teamcity.example.com -- `build_type` (**required**) - Build configuration ID -- `username` (**required**) - A user with permissions to trigger a manual build -- `password` (**required**) +| Parameter | Type | Required | Description | +| --------- | ---- | -------- | ----------- | +| `teamcity_url` | string | true | TeamCity root URL like https://teamcity.example.com | +| `build_type` | string | true | Build configuration ID | +| `username` | string | true | A user with permissions to trigger a manual build | +| `password` | string | true | The password of the user | ### Delete JetBrains TeamCity CI service @@ -916,7 +1107,9 @@ PUT /projects/:id/services/mock-ci Parameters: -- `mock_service_url` (**required**) - http://localhost:4004 +| Parameter | Type | Required | Description | +| --------- | ---- | -------- | ----------- | +| `mock_service_url` | string | true | http://localhost:4004 | ### Delete MockCI service @@ -933,3 +1126,5 @@ Get MockCI service settings for a project. ``` GET /projects/:id/services/mock-ci ``` + +[11435]: https://gitlab.com/gitlab-org/gitlab-ce/merge_requests/11435 diff --git a/doc/ci/examples/code_climate.md b/doc/ci/examples/code_climate.md index 4d0ba8bfef3..6a5821762cc 100644 --- a/doc/ci/examples/code_climate.md +++ b/doc/ci/examples/code_climate.md @@ -16,8 +16,7 @@ codequality: - docker:dind script: - docker pull codeclimate/codeclimate - - docker run --env CODECLIMATE_CODE="$PWD" --volume "$PWD":/code --volume /var/run/docker.sock:/var/run/docker.sock --volume /tmp/cc:/tmp/cc codeclimate/codeclimate init - - docker run --env CODECLIMATE_CODE="$PWD" --volume "$PWD":/code --volume /var/run/docker.sock:/var/run/docker.sock --volume /tmp/cc:/tmp/cc codeclimate/codeclimate analyze -f json > codeclimate.json + - docker run --env CODECLIMATE_CODE="$PWD" --volume "$PWD":/code --volume /var/run/docker.sock:/var/run/docker.sock --volume /tmp/cc:/tmp/cc codeclimate/codeclimate analyze -f json > codeclimate.json || true artifacts: paths: [codeclimate.json] ``` diff --git a/doc/development/README.md b/doc/development/README.md index b624aa37c70..12cca9f84b7 100644 --- a/doc/development/README.md +++ b/doc/development/README.md @@ -27,6 +27,7 @@ comments: false ## Backend guides +- [GitLab utilities](utilities.md) - [API styleguide](api_styleguide.md) Use this styleguide if you are contributing to the API. - [Sidekiq guidelines](sidekiq_style_guide.md) for working with Sidekiq workers diff --git a/doc/development/utilities.md b/doc/development/utilities.md new file mode 100644 index 00000000000..951c3ef85ce --- /dev/null +++ b/doc/development/utilities.md @@ -0,0 +1,92 @@ +# GitLab utilities + +We developed a number of utilities to ease development. + +## [`MergeHash`](https://gitlab.com/gitlab-org/gitlab-ce/blob/master/lib/gitlab/utils/merge_hash.rb) + +* Deep merges an array of hashes: + + ``` ruby + Gitlab::Utils::MergeHash.merge( + [{ hello: ["world"] }, + { hello: "Everyone" }, + { hello: { greetings: ['Bonjour', 'Hello', 'Hallo', 'Dzien dobry'] } }, + "Goodbye", "Hallo"] + ) + ``` + + Gives: + + ``` ruby + [ + { + hello: + [ + "world", + "Everyone", + { greetings: ['Bonjour', 'Hello', 'Hallo', 'Dzien dobry'] } + ] + }, + "Goodbye" + ] + ``` + +* Extracts all keys and values from a hash into an array: + + ``` ruby + Gitlab::Utils::MergeHash.crush( + { hello: "world", this: { crushes: ["an entire", "hash"] } } + ) + ``` + + Gives: + + ``` ruby + [:hello, "world", :this, :crushes, "an entire", "hash"] + ``` + +## [`StrongMemoize`](https://gitlab.com/gitlab-org/gitlab-ce/blob/master/lib/gitlab/utils/strong_memoize.rb) + +* Memoize the value even if it is `nil` or `false`. + + We often do `@value ||= compute`, however this doesn't work well if + `compute` might eventually give `nil` and we don't want to compute again. + Instead we could use `defined?` to check if the value is set or not. + However it's tedious to write such pattern, and `StrongMemoize` would + help us use such pattern. + + Instead of writing patterns like this: + + ``` ruby + class Find + def result + return @result if defined?(@result) + + @result = search + end + end + ``` + + We could write it like: + + ``` ruby + class Find + include Gitlab::Utils::StrongMemoize + + def result + strong_memoize(:result) do + search + end + end + end + ``` + +* Clear memoization + + ``` ruby + class Find + include Gitlab::Utils::StrongMemoize + end + + Find.new.clear_memoization(:result) + ``` diff --git a/doc/development/what_requires_downtime.md b/doc/development/what_requires_downtime.md index 05e0a64af18..9d0c62ecc35 100644 --- a/doc/development/what_requires_downtime.md +++ b/doc/development/what_requires_downtime.md @@ -195,6 +195,63 @@ end And that's it, we're done! +## Changing Column Types For Large Tables + +While `change_column_type_concurrently` can be used for changing the type of a +column without downtime it doesn't work very well for large tables. Because all +of the work happens in sequence the migration can take a very long time to +complete, preventing a deployment from proceeding. +`change_column_type_concurrently` can also produce a lot of pressure on the +database due to it rapidly updating many rows in sequence. + +To reduce database pressure you should instead use +`change_column_type_using_background_migration` when migrating a column in a +large table (e.g. `issues`). This method works similar to +`change_column_type_concurrently` but uses background migration to spread the +work / load over a longer time period, without slowing down deployments. + +Usage of this method is fairly simple: + +```ruby +class ExampleMigration < ActiveRecord::Migration + include Gitlab::Database::MigrationHelpers + + disable_ddl_transaction! + + class Issue < ActiveRecord::Base + self.table_name = 'issues' + + include EachBatch + + def self.to_migrate + where('closed_at IS NOT NULL') + end + end + + def up + change_column_type_using_background_migration( + Issue.to_migrate, + :closed_at, + :datetime_with_timezone + ) + end + + def down + change_column_type_using_background_migration( + Issue.to_migrate, + :closed_at, + :datetime + ) + end +end +``` + +This would change the type of `issues.closed_at` to `timestamp with time zone`. + +Keep in mind that the relation passed to +`change_column_type_using_background_migration` _must_ include `EachBatch`, +otherwise it will raise a `TypeError`. + ## Adding Indexes Adding indexes is an expensive process that blocks INSERT and UPDATE queries for diff --git a/doc/install/kubernetes/index.md b/doc/install/kubernetes/index.md index 0932e1eee3a..cd889e74487 100644 --- a/doc/install/kubernetes/index.md +++ b/doc/install/kubernetes/index.md @@ -9,11 +9,11 @@ should be deployed, upgraded, and configured. ## Chart Overview -* **[GitLab-Omnibus](gitlab_omnibus.md)**: The best way to run GitLab on Kubernetes today, suited for small to medium deployments. The chart is in beta and will be deprecated by the [cloud native GitLab chart](#cloud-native-gitlab-chart). +* **[GitLab-Omnibus](gitlab_omnibus.md)**: The best way to run GitLab on Kubernetes today, suited for small deployments. The chart is in beta and will be deprecated by the [cloud native GitLab chart](#cloud-native-gitlab-chart). * **[Cloud Native GitLab Chart](https://gitlab.com/charts/helm.gitlab.io/blob/master/README.md)**: The next generation GitLab chart, currently in development. Will support large deployments with horizontal scaling of individual GitLab components. * Other Charts * [GitLab Runner Chart](gitlab_runner_chart.md): For deploying just the GitLab Runner. - * [Advanced GitLab Installation](gitlab_chart.md): Deprecated, being replaced by the [cloud native GitLab chart](#cloud-native-gitlab-chart). Provides additional deployment options, but provides less functionality out-of-the-box. + * [Advanced GitLab Installation](gitlab_chart.md): Deprecated, being replaced by the [cloud native GitLab chart](#cloud-native-gitlab-chart). Provides additional deployment options, but provides less functionality out-of-the-box. * [Community Contributed Charts](#community-contributed-charts): Community contributed charts, deprecated by the official GitLab chart. ## GitLab-Omnibus Chart (Recommended) diff --git a/doc/topics/autodevops/index.md b/doc/topics/autodevops/index.md index 339bc2bd4fe..e23c73f46fb 100644 --- a/doc/topics/autodevops/index.md +++ b/doc/topics/autodevops/index.md @@ -20,6 +20,7 @@ project in an easy and automatic way: 1. [Auto Test](#auto-test) 1. [Auto Code Quality](#auto-code-quality) 1. [Auto SAST (Static Application Security Testing)](#auto-sast) +1. [Auto Browser Performance Testing](#auto-browser-performance-testing) 1. [Auto Review Apps](#auto-review-apps) 1. [Auto Deploy](#auto-deploy) 1. [Auto Monitoring](#auto-monitoring) @@ -208,6 +209,20 @@ check out. Any security warnings are also [shown in the merge request widget](https://docs.gitlab.com/ee/user/project/merge_requests/sast.html). +### Auto Browser Performance Testing + +> Introduced in [GitLab Enterprise Edition Premium][ee] 10.4. + +Auto Browser Performance Testing utilizes the [Sitespeed.io container](https://hub.docker.com/r/sitespeedio/sitespeed.io/) to measure the performance of a web page. A JSON report is created and uploaded as an artifact, which includes the overall performance score for each page. By default, the root page of Review and Production environments will be tested. If you would like to add additional URL's to test, simply add the paths to a file named `.gitlab-urls.txt` in the root directory, one per line. For example: + +``` +/ +/features +/direction +``` + +In GitLab Enterprise Edition Premium, performance differences between the source and target branches are [shown in the merge request widget](https://docs.gitlab.com/ee/user/project/merge_requests/browser_performance_testing.html). + ### Auto Review Apps NOTE: **Note:** diff --git a/doc/user/project/clusters/index.md b/doc/user/project/clusters/index.md index e2924c66e70..d5619c7b563 100644 --- a/doc/user/project/clusters/index.md +++ b/doc/user/project/clusters/index.md @@ -83,6 +83,7 @@ added directly to your configured cluster. Those applications are needed for | ----------- | :------------: | ----------- | | [Helm Tiller](https://docs.helm.sh/) | 10.2+ | Helm is a package manager for Kubernetes and is required to install all the other applications. It will be automatically installed as a dependency when you try to install a different app. It is installed in its own pod inside the cluster which can run the `helm` CLI in a safe environment. | | [Ingress](https://kubernetes.io/docs/concepts/services-networking/ingress/) | 10.2+ | Ingress can provide load balancing, SSL termination, and name-based virtual hosting. It acts as a web proxy for your applications and is useful if you want to use [Auto DevOps](../../../topics/autodevops/index.md) or deploy your own web apps. | +| [Prometheus](https://prometheus.io/docs/introduction/overview/) | 10.4+ | Prometheus is an open-source monitoring and alerting system useful to supervise your deployed applications | ## Enabling or disabling the Cluster integration diff --git a/doc/user/project/integrations/jira.md b/doc/user/project/integrations/jira.md index 7dc234a9759..f77569e4886 100644 --- a/doc/user/project/integrations/jira.md +++ b/doc/user/project/integrations/jira.md @@ -173,6 +173,7 @@ where `PROJECT-1` is the issue ID of the JIRA project. - Only commits and merges into the project's default branch (usually **master**) will close an issue in Jira. You can change your projects default branch under [project settings](img/jira_project_settings.png). +- The JIRA issue will not be transitioned if it has a resolution. ### JIRA issue closing example @@ -222,6 +223,10 @@ JIRA issue references and update comments will not work if the GitLab issue trac Make sure the `Transition ID` you set within the JIRA settings matches the one your project needs to close a ticket. +Make sure that the JIRA issue is not already marked as resolved, in other words that +the JIRA issue resolution field is not set. (It should not be struck through in +JIRA lists.) + [services-templates]: services_templates.md [jira-repo-old-docs]: https://gitlab.com/gitlab-org/gitlab-ce/blob/8-13-stable/doc/project_services/jira.md [jira]: https://www.atlassian.com/software/jira diff --git a/doc/user/project/integrations/kubernetes.md b/doc/user/project/integrations/kubernetes.md index e9738b683f9..710cf78e84f 100644 --- a/doc/user/project/integrations/kubernetes.md +++ b/doc/user/project/integrations/kubernetes.md @@ -1,7 +1,10 @@ --- -last_updated: 2017-09-25 +last_updated: 2017-12-28 --- +CAUTION: **Warning:** +Kubernetes service integration has been deprecated in GitLab 10.3. If the service is active the cluster information still be editable, however we advised to disable and reconfigure the clusters using the new [Clusters](../clusters/index.md) page. If the service is inactive the fields will be uneditable. Read [GitLab 10.3 release post](https://about.gitlab.com/2017/12/22/gitlab-10-3-released/#kubernetes-integration-service) for more information. + # GitLab Kubernetes / OpenShift integration GitLab can be configured to interact with Kubernetes, or other systems using the diff --git a/doc/user/project/integrations/project_services.md b/doc/user/project/integrations/project_services.md index a0405161495..9496d6f2ce0 100644 --- a/doc/user/project/integrations/project_services.md +++ b/doc/user/project/integrations/project_services.md @@ -39,7 +39,7 @@ Click on the service links to see further configuration instructions and details | [Irker (IRC gateway)](irker.md) | Send IRC messages, on update, to a list of recipients through an Irker gateway | | [JIRA](jira.md) | JIRA issue tracker | | JetBrains TeamCity CI | A continuous integration and build server | -| [Kubernetes](kubernetes.md) | A containerized deployment service | +| [Kubernetes](kubernetes.md) _(Has been deprecated in GitLab 10.3)_ | A containerized deployment service | | [Mattermost slash commands](mattermost_slash_commands.md) | Mattermost chat and ChatOps slash commands | | [Mattermost Notifications](mattermost.md) | Receive event notifications in Mattermost | | [Microsoft teams](microsoft_teams.md) | Receive notifications for actions that happen on GitLab into a room on Microsoft Teams using Office 365 Connectors | diff --git a/doc/user/project/quick_actions.md b/doc/user/project/quick_actions.md index e81e935e37d..442fc978284 100644 --- a/doc/user/project/quick_actions.md +++ b/doc/user/project/quick_actions.md @@ -39,3 +39,5 @@ do. | `/board_move ~column` | Move issue to column on the board | | `/duplicate #issue` | Closes this issue and marks it as a duplicate of another issue | | `/move path/to/project` | Moves issue to another project | +| `/tableflip` | Append the comment with `(╯°□°)╯︵ ┻━┻` | +| `/shrug` | Append the comment with `¯\_(ツ)_/¯` |
\ No newline at end of file diff --git a/features/steps/profile/profile.rb b/features/steps/profile/profile.rb index 4b88cb5e27f..d3b88ae8d2a 100644 --- a/features/steps/profile/profile.rb +++ b/features/steps/profile/profile.rb @@ -165,7 +165,7 @@ class Spinach::Features::Profile < Spinach::FeatureSteps @project = create(:project, :repository, namespace: @group) @event = create(:closed_issue_event, project: @project) - @project.team << [current_user, :master] + @project.add_master(current_user) end step 'I should see groups I belong to' do diff --git a/features/steps/project/deploy_keys.rb b/features/steps/project/deploy_keys.rb index b4403becb0d..9db31522c5c 100644 --- a/features/steps/project/deploy_keys.rb +++ b/features/steps/project/deploy_keys.rb @@ -48,11 +48,11 @@ class Spinach::Features::ProjectDeployKeys < Spinach::FeatureSteps step 'other projects have deploy keys' do @second_project = create(:project, namespace: create(:group)) - @second_project.team << [current_user, :master] + @second_project.add_master(current_user) create(:deploy_keys_project, project: @second_project) @third_project = create(:project, namespace: create(:group)) - @third_project.team << [current_user, :master] + @third_project.add_master(current_user) create(:deploy_keys_project, project: @third_project, deploy_key: @second_project.deploy_keys.first) end diff --git a/features/steps/project/fork.rb b/features/steps/project/fork.rb index 60707f26aee..0350e1c2aef 100644 --- a/features/steps/project/fork.rb +++ b/features/steps/project/fork.rb @@ -10,7 +10,7 @@ class Spinach::Features::ProjectFork < Spinach::FeatureSteps step 'I am a member of project "Shop"' do @project = create(:project, :repository, name: "Shop") - @project.team << [@user, :reporter] + @project.add_reporter(@user) end step 'I should see the forked project page' do @@ -71,7 +71,7 @@ class Spinach::Features::ProjectFork < Spinach::FeatureSteps step 'There is an existent fork of the "Shop" project' do user = create(:user, name: 'Mike') - @project.team << [user, :reporter] + @project.add_reporter(user) @forked_project = Projects::ForkService.new(@project, user).execute end diff --git a/features/steps/project/forked_merge_requests.rb b/features/steps/project/forked_merge_requests.rb index 6781a906a94..fd51ee1a316 100644 --- a/features/steps/project/forked_merge_requests.rb +++ b/features/steps/project/forked_merge_requests.rb @@ -10,7 +10,7 @@ class Spinach::Features::ProjectForkedMergeRequests < Spinach::FeatureSteps step 'I am a member of project "Shop"' do @project = ::Project.find_by(name: "Shop") @project ||= create(:project, :repository, name: "Shop") - @project.team << [@user, :reporter] + @project.add_reporter(@user) end step 'I have a project forked off of "Shop" called "Forked Shop"' do diff --git a/features/steps/project/source/browse_files.rb b/features/steps/project/source/browse_files.rb index 93b057430d3..afaad4b255e 100644 --- a/features/steps/project/source/browse_files.rb +++ b/features/steps/project/source/browse_files.rb @@ -8,7 +8,7 @@ class Spinach::Features::ProjectSourceBrowseFiles < Spinach::FeatureSteps step "I don't have write access" do @project = create(:project, :repository, name: "Other Project", path: "other-project") - @project.team << [@user, :reporter] + @project.add_reporter(@user) visit project_tree_path(@project, root_ref) end diff --git a/features/steps/project/source/markdown_render.rb b/features/steps/project/source/markdown_render.rb index f6445b57ec0..fc4ef26f6b6 100644 --- a/features/steps/project/source/markdown_render.rb +++ b/features/steps/project/source/markdown_render.rb @@ -10,7 +10,7 @@ class Spinach::Features::ProjectSourceMarkdownRender < Spinach::FeatureSteps step 'I own project "Delta"' do @project = ::Project.find_by(name: "Delta") @project ||= create(:project, :repository, name: "Delta", namespace: @user.namespace) - @project.team << [@user, :master] + @project.add_master(@user) end step 'I should see files from repository in markdown' do diff --git a/features/steps/shared/group.rb b/features/steps/shared/group.rb index 03bc7e798e0..0a0588346b1 100644 --- a/features/steps/shared/group.rb +++ b/features/steps/shared/group.rb @@ -41,7 +41,7 @@ module SharedGroup group.add_user(user, role) project ||= create(:project, :repository, namespace: group) create(:closed_issue_event, project: project) - project.team << [user, :master] + project.add_master(user) end def owned_group diff --git a/features/steps/shared/project.rb b/features/steps/shared/project.rb index 5e4edaf99a6..affbccccdf9 100644 --- a/features/steps/shared/project.rb +++ b/features/steps/shared/project.rb @@ -4,13 +4,13 @@ module SharedProject # Create a project without caring about what it's called step "I own a project" do @project = create(:project, :repository, namespace: @user.namespace) - @project.team << [@user, :master] + @project.add_master(@user) end step "I own a project in some group namespace" do @group = create(:group, name: 'some group') @project = create(:project, namespace: @group) - @project.team << [@user, :master] + @project.add_master(@user) end step "project exists in some group namespace" do @@ -22,7 +22,7 @@ module SharedProject step 'I own project "Shop"' do @project = Project.find_by(name: "Shop") @project ||= create(:project, :repository, name: "Shop", namespace: @user.namespace) - @project.team << [@user, :master] + @project.add_master(@user) end step 'I disable snippets in project' do @@ -40,7 +40,7 @@ module SharedProject step 'I add a user to project "Shop"' do @project = Project.find_by(name: "Shop") other_user = create(:user, name: 'Alpha') - @project.team << [other_user, :master] + @project.add_master(other_user) end # Create another specific project called "Forum" @@ -49,14 +49,13 @@ module SharedProject @project ||= create(:project, :repository, name: "Forum", namespace: @user.namespace, path: 'forum_project') @project.build_project_feature @project.project_feature.save - @project.team << [@user, :master] + @project.add_master(@user) end # Create an empty project without caring about the name step 'I own an empty project' do - @project = create(:project, - name: 'Empty Project', namespace: @user.namespace) - @project.team << [@user, :master] + @project = create(:project, name: 'Empty Project', namespace: @user.namespace) + @project.add_master(@user) end step 'I visit my empty project page' do @@ -101,11 +100,11 @@ module SharedProject # ---------------------------------------- step 'I am member of a project with a guest role' do - @project.team << [@user, Gitlab::Access::GUEST] + @project.add_guest(@user) end step 'I am member of a project with a reporter role' do - @project.team << [@user, Gitlab::Access::REPORTER] + @project.add_reporter(@user) end # ---------------------------------------- @@ -245,6 +244,6 @@ module SharedProject user = user_exists(user_name, username: user_name.gsub(/\s/, '').underscore) project = Project.find_by(name: project_name) project ||= create(:project, visibility, name: project_name, namespace: user.namespace) - project.team << [user, :master] + project.add_master(user) end end diff --git a/features/support/capybara.rb b/features/support/capybara.rb index 5a77b859113..4e2b3c67af5 100644 --- a/features/support/capybara.rb +++ b/features/support/capybara.rb @@ -29,6 +29,9 @@ Capybara.register_driver :chrome do |app| options.add_argument("disable-gpu") end + # Disable /dev/shm use in CI. See https://gitlab.com/gitlab-org/gitlab-ee/issues/4252 + options.add_argument("disable-dev-shm-usage") if ENV['CI'] || ENV['CI_SERVER'] + Capybara::Selenium::Driver.new( app, browser: :chrome, diff --git a/lib/api/entities.rb b/lib/api/entities.rb index 928706dfda7..4ad4a1f7867 100644 --- a/lib/api/entities.rb +++ b/lib/api/entities.rb @@ -708,8 +708,9 @@ module API class ProjectService < Grape::Entity expose :id, :title, :created_at, :updated_at, :active - expose :push_events, :issues_events, :merge_requests_events - expose :tag_push_events, :note_events, :pipeline_events + expose :push_events, :issues_events, :confidential_issues_events + expose :merge_requests_events, :tag_push_events, :note_events + expose :pipeline_events, :wiki_page_events expose :job_events # Expose serialized properties expose :properties do |service, options| diff --git a/lib/api/helpers.rb b/lib/api/helpers.rb index 9ba15893f55..8ad4b2ecbf3 100644 --- a/lib/api/helpers.rb +++ b/lib/api/helpers.rb @@ -69,7 +69,7 @@ module API end def wiki_page - page = user_project.wiki.find_page(params[:slug]) + page = ProjectWiki.new(user_project, current_user).find_page(params[:slug]) page || not_found!('Wiki Page') end diff --git a/lib/api/internal.rb b/lib/api/internal.rb index ccaaeca10d4..79b302aae70 100644 --- a/lib/api/internal.rb +++ b/lib/api/internal.rb @@ -190,9 +190,12 @@ module API project = Gitlab::GlRepository.parse(params[:gl_repository]).first user = identify(params[:identifier]) - redirect_message = Gitlab::Checks::ProjectMoved.fetch_redirect_message(user.id, project.id) - if redirect_message - output[:redirected_message] = redirect_message + + # A user is not guaranteed to be returned; an orphaned write deploy + # key could be used + if user + redirect_message = Gitlab::Checks::ProjectMoved.fetch_redirect_message(user.id, project.id) + output[:redirected_message] = redirect_message if redirect_message end output diff --git a/lib/api/members.rb b/lib/api/members.rb index 22e4bdead41..5446f6b54b1 100644 --- a/lib/api/members.rb +++ b/lib/api/members.rb @@ -59,7 +59,9 @@ module API member = source.add_user(params[:user_id], params[:access_level], current_user: current_user, expires_at: params[:expires_at]) - if member.persisted? && member.valid? + if !member + not_allowed! # This currently can only be reached in EE + elsif member.persisted? && member.valid? present member.user, with: Entities::Member, member: member else render_validation_error!(member) diff --git a/lib/api/services.rb b/lib/api/services.rb index bbcc851d07a..a7f44e2869c 100644 --- a/lib/api/services.rb +++ b/lib/api/services.rb @@ -1,5 +1,143 @@ module API class Services < Grape::API + chat_notification_settings = [ + { + required: true, + name: :webhook, + type: String, + desc: 'The chat webhook' + }, + { + required: false, + name: :username, + type: String, + desc: 'The chat username' + }, + { + required: false, + name: :channel, + type: String, + desc: 'The default chat channel' + } + ] + + chat_notification_flags = [ + { + required: false, + name: :notify_only_broken_pipelines, + type: Boolean, + desc: 'Send notifications for broken pipelines' + }, + { + required: false, + name: :notify_only_default_branch, + type: Boolean, + desc: 'Send notifications only for the default branch' + } + ] + + chat_notification_channels = [ + { + required: false, + name: :push_channel, + type: String, + desc: 'The name of the channel to receive push_events notifications' + }, + { + required: false, + name: :issue_channel, + type: String, + desc: 'The name of the channel to receive issues_events notifications' + }, + { + required: false, + name: :confidential_issue_channel, + type: String, + desc: 'The name of the channel to receive confidential_issues_events notifications' + }, + { + required: false, + name: :merge_request_channel, + type: String, + desc: 'The name of the channel to receive merge_requests_events notifications' + }, + { + required: false, + name: :note_channel, + type: String, + desc: 'The name of the channel to receive note_events notifications' + }, + { + required: false, + name: :tag_push_channel, + type: String, + desc: 'The name of the channel to receive tag_push_events notifications' + }, + { + required: false, + name: :pipeline_channel, + type: String, + desc: 'The name of the channel to receive pipeline_events notifications' + }, + { + required: false, + name: :wiki_page_channel, + type: String, + desc: 'The name of the channel to receive wiki_page_events notifications' + } + ] + + chat_notification_events = [ + { + required: false, + name: :push_events, + type: Boolean, + desc: 'Enable notifications for push_events' + }, + { + required: false, + name: :issues_events, + type: Boolean, + desc: 'Enable notifications for issues_events' + }, + { + required: false, + name: :confidential_issues_events, + type: Boolean, + desc: 'Enable notifications for confidential_issues_events' + }, + { + required: false, + name: :merge_requests_events, + type: Boolean, + desc: 'Enable notifications for merge_requests_events' + }, + { + required: false, + name: :note_events, + type: Boolean, + desc: 'Enable notifications for note_events' + }, + { + required: false, + name: :tag_push_events, + type: Boolean, + desc: 'Enable notifications for tag_push_events' + }, + { + required: false, + name: :pipeline_events, + type: Boolean, + desc: 'Enable notifications for pipeline_events' + }, + { + required: false, + name: :wiki_page_events, + type: Boolean, + desc: 'Enable notifications for wiki_page_events' + } + ] + services = { 'asana' => [ { @@ -489,25 +627,11 @@ module API } ], 'slack' => [ - { - required: true, - name: :webhook, - type: String, - desc: 'The Slack webhook. e.g. https://hooks.slack.com/services/...' - }, - { - required: false, - name: :new_issue_url, - type: String, - desc: 'The user name' - }, - { - required: false, - name: :channel, - type: String, - desc: 'The channel name' - } - ], + chat_notification_settings, + chat_notification_flags, + chat_notification_channels, + chat_notification_events + ].flatten, 'microsoft-teams' => [ { required: true, @@ -517,19 +641,11 @@ module API } ], 'mattermost' => [ - { - required: true, - name: :webhook, - type: String, - desc: 'The Mattermost webhook. e.g. http://mattermost_host/hooks/...' - }, - { - required: false, - name: :username, - type: String, - desc: 'The username to use to post the message' - } - ], + chat_notification_settings, + chat_notification_flags, + chat_notification_channels, + chat_notification_events + ].flatten, 'teamcity' => [ { required: true, diff --git a/lib/backup/files.rb b/lib/backup/files.rb index 30a91647b77..287d591e88d 100644 --- a/lib/backup/files.rb +++ b/lib/backup/files.rb @@ -18,7 +18,7 @@ module Backup FileUtils.rm_f(backup_tarball) if ENV['STRATEGY'] == 'copy' - cmd = %W(cp -a #{app_files_dir} #{Gitlab.config.backup.path}) + cmd = %W(rsync -a --exclude=lost+found #{app_files_dir} #{Gitlab.config.backup.path}) output, status = Gitlab::Popen.popen(cmd) unless status.zero? @@ -26,10 +26,10 @@ module Backup abort 'Backup failed' end - run_pipeline!([%W(tar -C #{@backup_files_dir} -cf - .), %w(gzip -c -1)], out: [backup_tarball, 'w', 0600]) + run_pipeline!([%W(tar --exclude=lost+found -C #{@backup_files_dir} -cf - .), %w(gzip -c -1)], out: [backup_tarball, 'w', 0600]) FileUtils.rm_rf(@backup_files_dir) else - run_pipeline!([%W(tar -C #{app_files_dir} -cf - .), %w(gzip -c -1)], out: [backup_tarball, 'w', 0600]) + run_pipeline!([%W(tar --exclude=lost+found -C #{app_files_dir} -cf - .), %w(gzip -c -1)], out: [backup_tarball, 'w', 0600]) end end diff --git a/lib/banzai/filter/relative_link_filter.rb b/lib/banzai/filter/relative_link_filter.rb index c1f933ec54b..5c197afd782 100644 --- a/lib/banzai/filter/relative_link_filter.rb +++ b/lib/banzai/filter/relative_link_filter.rb @@ -66,7 +66,7 @@ module Banzai if uri.relative? && uri.path.present? html_attr.value = rebuild_relative_uri(uri).to_s end - rescue URI::Error + rescue URI::Error, Addressable::URI::InvalidURIError # noop end diff --git a/lib/gitlab/background_migration/cleanup_concurrent_type_change.rb b/lib/gitlab/background_migration/cleanup_concurrent_type_change.rb new file mode 100644 index 00000000000..de622f657b2 --- /dev/null +++ b/lib/gitlab/background_migration/cleanup_concurrent_type_change.rb @@ -0,0 +1,54 @@ +# frozen_string_literal: true + +module Gitlab + module BackgroundMigration + # Background migration for cleaning up a concurrent column rename. + class CleanupConcurrentTypeChange + include Database::MigrationHelpers + + RESCHEDULE_DELAY = 10.minutes + + # table - The name of the table the migration is performed for. + # old_column - The name of the old (to drop) column. + # new_column - The name of the new column. + def perform(table, old_column, new_column) + return unless column_exists?(:issues, new_column) + + rows_to_migrate = define_model_for(table) + .where(new_column => nil) + .where + .not(old_column => nil) + + if rows_to_migrate.any? + BackgroundMigrationWorker.perform_in( + RESCHEDULE_DELAY, + 'CleanupConcurrentTypeChange', + [table, old_column, new_column] + ) + else + cleanup_concurrent_column_type_change(table, old_column) + end + end + + # These methods are necessary so we can re-use the migration helpers in + # this class. + def connection + ActiveRecord::Base.connection + end + + def method_missing(name, *args, &block) + connection.__send__(name, *args, &block) # rubocop: disable GitlabSecurity/PublicSend + end + + def respond_to_missing?(*args) + connection.respond_to?(*args) || super + end + + def define_model_for(table) + Class.new(ActiveRecord::Base) do + self.table_name = table + end + end + end + end +end diff --git a/lib/gitlab/background_migration/copy_column.rb b/lib/gitlab/background_migration/copy_column.rb new file mode 100644 index 00000000000..a2cb215c230 --- /dev/null +++ b/lib/gitlab/background_migration/copy_column.rb @@ -0,0 +1,39 @@ +# frozen_string_literal: true + +module Gitlab + module BackgroundMigration + # CopyColumn is a simple (reusable) background migration that can be used to + # update the value of a column based on the value of another column in the + # same table. + # + # For this background migration to work the table that is migrated _has_ to + # have an `id` column as the primary key. + class CopyColumn + # table - The name of the table that contains the columns. + # copy_from - The column containing the data to copy. + # copy_to - The column to copy the data to. + # start_id - The start ID of the range of rows to update. + # end_id - The end ID of the range of rows to update. + def perform(table, copy_from, copy_to, start_id, end_id) + return unless connection.column_exists?(table, copy_to) + + quoted_table = connection.quote_table_name(table) + quoted_copy_from = connection.quote_column_name(copy_from) + quoted_copy_to = connection.quote_column_name(copy_to) + + # We're using raw SQL here since this job may be frequently executed. As + # a result dynamically defining models would lead to many unnecessary + # schema information queries. + connection.execute <<-SQL.strip_heredoc + UPDATE #{quoted_table} + SET #{quoted_copy_to} = #{quoted_copy_from} + WHERE id BETWEEN #{start_id} AND #{end_id} + SQL + end + + def connection + ActiveRecord::Base.connection + end + end + end +end diff --git a/lib/gitlab/background_migration/delete_conflicting_redirect_routes_range.rb b/lib/gitlab/background_migration/delete_conflicting_redirect_routes_range.rb index a1af045a71f..21b626dde56 100644 --- a/lib/gitlab/background_migration/delete_conflicting_redirect_routes_range.rb +++ b/lib/gitlab/background_migration/delete_conflicting_redirect_routes_range.rb @@ -1,44 +1,12 @@ # frozen_string_literal: true -# rubocop:disable Metrics/LineLength # rubocop:disable Style/Documentation module Gitlab module BackgroundMigration class DeleteConflictingRedirectRoutesRange - class Route < ActiveRecord::Base - self.table_name = 'routes' - end - - class RedirectRoute < ActiveRecord::Base - self.table_name = 'redirect_routes' - end - - # start_id - The start ID of the range of events to process - # end_id - The end ID of the range to process. def perform(start_id, end_id) - return unless migrate? - - conflicts = RedirectRoute.where(routes_match_redirects_clause(start_id, end_id)) - num_rows = conflicts.delete_all - - Rails.logger.info("Gitlab::BackgroundMigration::DeleteConflictingRedirectRoutesRange [#{start_id}, #{end_id}] - Deleted #{num_rows} redirect routes that were conflicting with routes.") - end - - def migrate? - Route.table_exists? && RedirectRoute.table_exists? - end - - def routes_match_redirects_clause(start_id, end_id) - <<~ROUTES_MATCH_REDIRECTS - EXISTS ( - SELECT 1 FROM routes - WHERE ( - LOWER(redirect_routes.path) = LOWER(routes.path) - OR LOWER(redirect_routes.path) LIKE LOWER(CONCAT(routes.path, '/%')) - ) - AND routes.id BETWEEN #{start_id} AND #{end_id} - ) - ROUTES_MATCH_REDIRECTS + # No-op. + # See https://gitlab.com/gitlab-com/infrastructure/issues/3460#note_53223252 end end end diff --git a/lib/gitlab/background_migration/migrate_events_to_push_event_payloads.rb b/lib/gitlab/background_migration/migrate_events_to_push_event_payloads.rb index 84ac00f1a5c..7088aa0860a 100644 --- a/lib/gitlab/background_migration/migrate_events_to_push_event_payloads.rb +++ b/lib/gitlab/background_migration/migrate_events_to_push_event_payloads.rb @@ -128,8 +128,14 @@ module Gitlab end def process_event(event) - replicate_event(event) - create_push_event_payload(event) if event.push_event? + ActiveRecord::Base.transaction do + replicate_event(event) + create_push_event_payload(event) if event.push_event? + end + rescue ActiveRecord::InvalidForeignKey => e + # A foreign key error means the associated event was removed. In this + # case we'll just skip migrating the event. + Rails.logger.error("Unable to migrate event #{event.id}: #{e}") end def replicate_event(event) @@ -137,9 +143,6 @@ module Gitlab .with_indifferent_access.except(:title, :data) EventForMigration.create!(new_attributes) - rescue ActiveRecord::InvalidForeignKey - # A foreign key error means the associated event was removed. In this - # case we'll just skip migrating the event. end def create_push_event_payload(event) @@ -156,9 +159,6 @@ module Gitlab ref: event.trimmed_ref_name, commit_title: event.commit_title ) - rescue ActiveRecord::InvalidForeignKey - # A foreign key error means the associated event was removed. In this - # case we'll just skip migrating the event. end def find_events(start_id, end_id) diff --git a/lib/gitlab/background_migration/populate_merge_request_metrics_with_events_data.rb b/lib/gitlab/background_migration/populate_merge_request_metrics_with_events_data.rb new file mode 100644 index 00000000000..8a901a9bf39 --- /dev/null +++ b/lib/gitlab/background_migration/populate_merge_request_metrics_with_events_data.rb @@ -0,0 +1,135 @@ +# frozen_string_literal: true +# rubocop:disable Metrics/LineLength +# rubocop:disable Metrics/MethodLength +# rubocop:disable Metrics/ClassLength +# rubocop:disable Style/Documentation + +module Gitlab + module BackgroundMigration + class PopulateMergeRequestMetricsWithEventsData + def perform(min_merge_request_id, max_merge_request_id) + insert_metrics_for_range(min_merge_request_id, max_merge_request_id) + update_metrics_with_events_data(min_merge_request_id, max_merge_request_id) + end + + # Inserts merge_request_metrics records for merge_requests without it for + # a given merge request batch. + def insert_metrics_for_range(min, max) + metrics_not_exists_clause = + <<-SQL.strip_heredoc + NOT EXISTS (SELECT 1 FROM merge_request_metrics + WHERE merge_request_metrics.merge_request_id = merge_requests.id) + SQL + + MergeRequest.where(metrics_not_exists_clause).where(id: min..max).each_batch do |batch| + select_sql = batch.select(:id, :created_at, :updated_at).to_sql + + execute("INSERT INTO merge_request_metrics (merge_request_id, created_at, updated_at) #{select_sql}") + end + end + + def update_metrics_with_events_data(min, max) + if Gitlab::Database.postgresql? + # Uses WITH syntax in order to update merged and closed events with a single UPDATE. + # WITH is not supported by MySQL. + update_events_for_range(min, max) + else + update_merged_events_for_range(min, max) + update_closed_events_for_range(min, max) + end + end + + private + + # Updates merge_request_metrics latest_closed_at, latest_closed_by_id and merged_by_id + # based on the latest event records on events table for a given merge request batch. + def update_events_for_range(min, max) + sql = <<-SQL.strip_heredoc + WITH events_for_update AS ( + SELECT DISTINCT ON (target_id, action) target_id, action, author_id, updated_at + FROM events + WHERE target_id BETWEEN #{min} AND #{max} + AND target_type = 'MergeRequest' + AND action IN (#{Event::CLOSED},#{Event::MERGED}) + ORDER BY target_id, action, id DESC + ) + UPDATE merge_request_metrics met + SET latest_closed_at = latest_closed.updated_at, + latest_closed_by_id = latest_closed.author_id, + merged_by_id = latest_merged.author_id + FROM (SELECT * FROM events_for_update WHERE action = #{Event::CLOSED}) AS latest_closed + FULL OUTER JOIN + (SELECT * FROM events_for_update WHERE action = #{Event::MERGED}) AS latest_merged + USING (target_id) + WHERE target_id = merge_request_id; + SQL + + execute(sql) + end + + # Updates merge_request_metrics latest_closed_at, latest_closed_by_id based on the latest closed + # records on events table for a given merge request batch. + def update_closed_events_for_range(min, max) + sql = + <<-SQL.strip_heredoc + UPDATE merge_request_metrics metrics, + (#{select_events(min, max, Event::CLOSED)}) closed_events + SET metrics.latest_closed_by_id = closed_events.author_id, + metrics.latest_closed_at = closed_events.updated_at #{where_matches_closed_events}; + SQL + + execute(sql) + end + + # Updates merge_request_metrics merged_by_id based on the latest merged + # records on events table for a given merge request batch. + def update_merged_events_for_range(min, max) + sql = + <<-SQL.strip_heredoc + UPDATE merge_request_metrics metrics, + (#{select_events(min, max, Event::MERGED)}) merged_events + SET metrics.merged_by_id = merged_events.author_id #{where_matches_merged_events}; + SQL + + execute(sql) + end + + def execute(sql) + @connection ||= ActiveRecord::Base.connection + @connection.execute(sql) + end + + def select_events(min, max, action) + select_max_event_id = <<-SQL.strip_heredoc + SELECT max(id) + FROM events + WHERE action = #{action} + AND target_type = 'MergeRequest' + AND target_id BETWEEN #{min} AND #{max} + GROUP BY target_id + SQL + + <<-SQL.strip_heredoc + SELECT author_id, updated_at, target_id + FROM events + WHERE id IN(#{select_max_event_id}) + SQL + end + + def where_matches_closed_events + <<-SQL.strip_heredoc + WHERE metrics.merge_request_id = closed_events.target_id + AND metrics.latest_closed_at IS NULL + AND metrics.latest_closed_by_id IS NULL + SQL + end + + def where_matches_merged_events + <<-SQL.strip_heredoc + WHERE metrics.merge_request_id = merged_events.target_id + AND metrics.merged_by_id IS NULL + SQL + end + end + end +end diff --git a/lib/gitlab/bare_repository_import/importer.rb b/lib/gitlab/bare_repository_import/importer.rb index 298409d8b5a..709a901aa77 100644 --- a/lib/gitlab/bare_repository_import/importer.rb +++ b/lib/gitlab/bare_repository_import/importer.rb @@ -14,7 +14,7 @@ module Gitlab repos_to_import.each do |repo_path| bare_repo = Gitlab::BareRepositoryImport::Repository.new(import_path, repo_path) - if bare_repo.hashed? || bare_repo.wiki? + unless bare_repo.processable? log " * Skipping repo #{bare_repo.repo_path}".color(:yellow) next @@ -55,12 +55,15 @@ module Gitlab name: project_name, path: project_name, skip_disk_validation: true, - import_type: 'gitlab_project', + skip_wiki: bare_repo.wiki_exists?, + import_type: 'bare_repository', namespace_id: group&.id).execute if project.persisted? && mv_repo(project) log " * Created #{project.name} (#{project_full_path})".color(:green) + project.write_repository_config + ProjectCacheWorker.perform_async(project.id) else log " * Failed trying to create #{project.name} (#{project_full_path})".color(:red) diff --git a/lib/gitlab/bare_repository_import/repository.rb b/lib/gitlab/bare_repository_import/repository.rb index fa7891c8dcc..85b79362196 100644 --- a/lib/gitlab/bare_repository_import/repository.rb +++ b/lib/gitlab/bare_repository_import/repository.rb @@ -6,39 +6,56 @@ module Gitlab def initialize(root_path, repo_path) @root_path = root_path @repo_path = repo_path - @root_path << '/' unless root_path.ends_with?('/') + full_path = + if hashed? && !wiki? + repository.config.get('gitlab.fullpath') + else + repo_relative_path + end + # Split path into 'all/the/namespaces' and 'project_name' - @group_path, _, @project_name = repo_relative_path.rpartition('/') + @group_path, _, @project_name = full_path.to_s.rpartition('/') end def wiki_exists? File.exist?(wiki_path) end - def wiki? - @wiki ||= repo_path.end_with?('.wiki.git') - end - def wiki_path @wiki_path ||= repo_path.sub(/\.git$/, '.wiki.git') end - def hashed? - @hashed ||= group_path.start_with?('@hashed') - end - def project_full_path @project_full_path ||= "#{group_path}/#{project_name}" end + def processable? + return false if wiki? + return false if hashed? && (group_path.blank? || project_name.blank?) + + true + end + private + def wiki? + @wiki ||= repo_path.end_with?('.wiki.git') + end + + def hashed? + @hashed ||= repo_relative_path.include?('@hashed') + end + def repo_relative_path # Remove root path and `.git` at the end repo_path[@root_path.size...-4] end + + def repository + @repository ||= Rugged::Repository.new(repo_path) + end end end end diff --git a/lib/gitlab/checks/project_moved.rb b/lib/gitlab/checks/project_moved.rb index 3a1c0a3455e..dfb2f4d4054 100644 --- a/lib/gitlab/checks/project_moved.rb +++ b/lib/gitlab/checks/project_moved.rb @@ -21,6 +21,10 @@ module Gitlab end def add_redirect_message + # Don't bother with sending a redirect message for anonymous clones + # because they never see it via the `/internal/post_receive` endpoint + return unless user.present? && project.present? + Gitlab::Redis::SharedState.with do |redis| key = self.class.redirect_message_key(user.id, project.id) redis.setex(key, 5.minutes, redirect_message) diff --git a/lib/gitlab/ci/ansi2html.rb b/lib/gitlab/ci/ansi2html.rb index 72b75791bbb..e25916528f4 100644 --- a/lib/gitlab/ci/ansi2html.rb +++ b/lib/gitlab/ci/ansi2html.rb @@ -234,7 +234,7 @@ module Gitlab # Most terminals show bold colored text in the light color variant # Let's mimic that here if @style_mask & STYLE_SWITCHES[:bold] != 0 - fg_color.sub!(/fg-(\w{2,}+)/, 'fg-l-\1') + fg_color.sub!(/fg-([a-z]{2,}+)/, 'fg-l-\1') end css_classes << fg_color end diff --git a/lib/gitlab/conflict/file_collection.rb b/lib/gitlab/conflict/file_collection.rb index 76aee5a3deb..0a3ae2c3760 100644 --- a/lib/gitlab/conflict/file_collection.rb +++ b/lib/gitlab/conflict/file_collection.rb @@ -13,12 +13,13 @@ module Gitlab end def resolve(user, commit_message, files) + msg = commit_message || default_commit_message + resolution = Gitlab::Git::Conflict::Resolution.new(user, files, msg) args = { source_branch: merge_request.source_branch, - target_branch: merge_request.target_branch, - commit_message: commit_message || default_commit_message + target_branch: merge_request.target_branch } - resolver.resolve_conflicts(@source_repo, user, files, args) + resolver.resolve_conflicts(@source_repo, resolution, args) ensure @merge_request.clear_memoized_shas end diff --git a/lib/gitlab/database/migration_helpers.rb b/lib/gitlab/database/migration_helpers.rb index 3f65bc912de..33171f83692 100644 --- a/lib/gitlab/database/migration_helpers.rb +++ b/lib/gitlab/database/migration_helpers.rb @@ -385,10 +385,27 @@ module Gitlab # necessary since we copy over old values further down. change_column_default(table, new, old_col.default) if old_col.default - trigger_name = rename_trigger_name(table, old, new) + install_rename_triggers(table, old, new) + + update_column_in_batches(table, new, Arel::Table.new(table)[old]) + + change_column_null(table, new, false) unless old_col.null + + copy_indexes(table, old, new) + copy_foreign_keys(table, old, new) + end + + # Installs triggers in a table that keep a new column in sync with an old + # one. + # + # table - The name of the table to install the trigger in. + # old_column - The name of the old column. + # new_column - The name of the new column. + def install_rename_triggers(table, old_column, new_column) + trigger_name = rename_trigger_name(table, old_column, new_column) quoted_table = quote_table_name(table) - quoted_old = quote_column_name(old) - quoted_new = quote_column_name(new) + quoted_old = quote_column_name(old_column) + quoted_new = quote_column_name(new_column) if Database.postgresql? install_rename_triggers_for_postgresql(trigger_name, quoted_table, @@ -397,13 +414,6 @@ module Gitlab install_rename_triggers_for_mysql(trigger_name, quoted_table, quoted_old, quoted_new) end - - update_column_in_batches(table, new, Arel::Table.new(table)[old]) - - change_column_null(table, new, false) unless old_col.null - - copy_indexes(table, old, new) - copy_foreign_keys(table, old, new) end # Changes the type of a column concurrently. @@ -455,6 +465,97 @@ module Gitlab remove_column(table, old) end + # Changes the column type of a table using a background migration. + # + # Because this method uses a background migration it's more suitable for + # large tables. For small tables it's better to use + # `change_column_type_concurrently` since it can complete its work in a + # much shorter amount of time and doesn't rely on Sidekiq. + # + # Example usage: + # + # class Issue < ActiveRecord::Base + # self.table_name = 'issues' + # + # include EachBatch + # + # def self.to_migrate + # where('closed_at IS NOT NULL') + # end + # end + # + # change_column_type_using_background_migration( + # Issue.to_migrate, + # :closed_at, + # :datetime_with_timezone + # ) + # + # Reverting a migration like this is done exactly the same way, just with + # a different type to migrate to (e.g. `:datetime` in the above example). + # + # relation - An ActiveRecord relation to use for scheduling jobs and + # figuring out what table we're modifying. This relation _must_ + # have the EachBatch module included. + # + # column - The name of the column for which the type will be changed. + # + # new_type - The new type of the column. + # + # batch_size - The number of rows to schedule in a single background + # migration. + # + # interval - The time interval between every background migration. + def change_column_type_using_background_migration( + relation, + column, + new_type, + batch_size: 10_000, + interval: 10.minutes + ) + unless relation.model < EachBatch + raise TypeError, 'The relation must include the EachBatch module' + end + + temp_column = "#{column}_for_type_change" + table = relation.table_name + max_index = 0 + + add_column(table, temp_column, new_type) + install_rename_triggers(table, column, temp_column) + + # Schedule the jobs that will copy the data from the old column to the + # new one. + relation.each_batch(of: batch_size) do |batch, index| + start_id, end_id = batch.pluck('MIN(id), MAX(id)').first + max_index = index + + BackgroundMigrationWorker.perform_in( + index * interval, + 'CopyColumn', + [table, column, temp_column, start_id, end_id] + ) + end + + # Schedule the renaming of the column to happen (initially) 1 hour after + # the last batch finished. + BackgroundMigrationWorker.perform_in( + (max_index * interval) + 1.hour, + 'CleanupConcurrentTypeChange', + [table, column, temp_column] + ) + + if perform_background_migration_inline? + # To ensure the schema is up to date immediately we perform the + # migration inline in dev / test environments. + Gitlab::BackgroundMigration.steal('CopyColumn') + Gitlab::BackgroundMigration.steal('CleanupConcurrentTypeChange') + end + end + + def perform_background_migration_inline? + Rails.env.test? || Rails.env.development? + end + # Performs a concurrent column rename when using PostgreSQL. def install_rename_triggers_for_postgresql(trigger, table, old, new) execute <<-EOF.strip_heredoc diff --git a/lib/gitlab/diff/file.rb b/lib/gitlab/diff/file.rb index cd490aaa291..34b070dd375 100644 --- a/lib/gitlab/diff/file.rb +++ b/lib/gitlab/diff/file.rb @@ -116,8 +116,10 @@ module Gitlab new_content_sha || old_content_sha end + # Use #itself to check the value wrapped by a BatchLoader instance, rather + # than if the BatchLoader instance itself is falsey. def blob - new_blob || old_blob + new_blob&.itself || old_blob&.itself end attr_writer :highlighted_diff_lines @@ -173,7 +175,7 @@ module Gitlab end def binary? - has_binary_notice? || old_blob&.binary? || new_blob&.binary? + has_binary_notice? || try_blobs(:binary?) end def text? @@ -181,15 +183,15 @@ module Gitlab end def external_storage_error? - old_blob&.external_storage_error? || new_blob&.external_storage_error? + try_blobs(:external_storage_error?) end def stored_externally? - old_blob&.stored_externally? || new_blob&.stored_externally? + try_blobs(:stored_externally?) end def external_storage - old_blob&.external_storage || new_blob&.external_storage + try_blobs(:external_storage) end def content_changed? @@ -204,15 +206,15 @@ module Gitlab end def size - [old_blob&.size, new_blob&.size].compact.sum + valid_blobs.map(&:size).sum end def raw_size - [old_blob&.raw_size, new_blob&.raw_size].compact.sum + valid_blobs.map(&:raw_size).sum end def raw_binary? - old_blob&.raw_binary? || new_blob&.raw_binary? + try_blobs(:raw_binary?) end def raw_text? @@ -235,6 +237,19 @@ module Gitlab private + # The blob instances are instances of BatchLoader, which means calling + # &. directly on them won't work. Object#try also won't work, because Blob + # doesn't inherit from Object, but from BasicObject (via SimpleDelegator). + def try_blobs(meth) + old_blob&.itself&.public_send(meth) || new_blob&.itself&.public_send(meth) + end + + # We can't use #compact for the same reason we can't use &., but calling + # #nil? explicitly does work because it is proxied to the blob itself. + def valid_blobs + [old_blob, new_blob].reject(&:nil?) + end + def text_position_properties(line) { old_line: line.old_line, new_line: line.new_line } end diff --git a/lib/gitlab/encoding_helper.rb b/lib/gitlab/encoding_helper.rb index 582028493e9..6b53eb4533d 100644 --- a/lib/gitlab/encoding_helper.rb +++ b/lib/gitlab/encoding_helper.rb @@ -71,6 +71,16 @@ module Gitlab end end + def encode_binary(s) + return "" if s.nil? + + s.dup.force_encoding(Encoding::ASCII_8BIT) + end + + def binary_stringio(s) + StringIO.new(s || '').tap { |io| io.set_encoding(Encoding::ASCII_8BIT) } + end + private def clean(message) diff --git a/lib/gitlab/git/commit_stats.rb b/lib/gitlab/git/commit_stats.rb index 6bf49a0af18..8463b1eb794 100644 --- a/lib/gitlab/git/commit_stats.rb +++ b/lib/gitlab/git/commit_stats.rb @@ -34,13 +34,8 @@ module Gitlab def rugged_stats(commit) diff = commit.rugged_diff_from_parent - - diff.each_patch do |p| - # TODO: Use the new Rugged convenience methods when they're released - @additions += p.stat[0] - @deletions += p.stat[1] - @total += p.changes - end + _files_changed, @additions, @deletions = diff.stat + @total = @additions + @deletions end end end diff --git a/lib/gitlab/git/conflict/file.rb b/lib/gitlab/git/conflict/file.rb index b2a625e08fa..2a9cf10a068 100644 --- a/lib/gitlab/git/conflict/file.rb +++ b/lib/gitlab/git/conflict/file.rb @@ -2,7 +2,9 @@ module Gitlab module Git module Conflict class File - attr_reader :content, :their_path, :our_path, :our_mode, :repository, :commit_oid + attr_reader :their_path, :our_path, :our_mode, :repository, :commit_oid + + attr_accessor :content def initialize(repository, commit_oid, conflict, content) @repository = repository diff --git a/lib/gitlab/git/conflict/resolution.rb b/lib/gitlab/git/conflict/resolution.rb new file mode 100644 index 00000000000..ab9be683e15 --- /dev/null +++ b/lib/gitlab/git/conflict/resolution.rb @@ -0,0 +1,15 @@ +module Gitlab + module Git + module Conflict + class Resolution + attr_reader :user, :files, :commit_message + + def initialize(user, files, commit_message) + @user = user + @files = files + @commit_message = commit_message + end + end + end + end +end diff --git a/lib/gitlab/git/conflict/resolver.rb b/lib/gitlab/git/conflict/resolver.rb index 03e5c0fcd6f..74c9874d590 100644 --- a/lib/gitlab/git/conflict/resolver.rb +++ b/lib/gitlab/git/conflict/resolver.rb @@ -13,37 +13,27 @@ module Gitlab def conflicts @conflicts ||= begin - target_index = @target_repository.rugged.merge_commits(@our_commit_oid, @their_commit_oid) - - # We don't need to do `with_repo_branch_commit` here, because the target - # project always fetches source refs when creating merge request diffs. - conflict_files(@target_repository, target_index) + @target_repository.gitaly_migrate(:conflicts_list_conflict_files) do |is_enabled| + if is_enabled + gitaly_conflicts_client(@target_repository).list_conflict_files + else + rugged_list_conflict_files + end + end end + rescue GRPC::FailedPrecondition => e + raise Gitlab::Git::Conflict::Resolver::ConflictSideMissing.new(e.message) + rescue Rugged::OdbError, GRPC::BadStatus => e + raise Gitlab::Git::CommandError.new(e) end - def resolve_conflicts(source_repository, user, files, source_branch:, target_branch:, commit_message:) - source_repository.with_repo_branch_commit(@target_repository, target_branch) do - index = source_repository.rugged.merge_commits(@our_commit_oid, @their_commit_oid) - conflicts = conflict_files(source_repository, index) - - files.each do |file_params| - conflict_file = conflict_for_path(conflicts, file_params[:old_path], file_params[:new_path]) - - write_resolved_file_to_index(source_repository, index, conflict_file, file_params) - end - - unless index.conflicts.empty? - missing_files = index.conflicts.map { |file| file[:ours][:path] } - - raise ResolutionError, "Missing resolutions for the following files: #{missing_files.join(', ')}" + def resolve_conflicts(source_repository, resolution, source_branch:, target_branch:) + source_repository.gitaly_migrate(:conflicts_resolve_conflicts) do |is_enabled| + if is_enabled + gitaly_conflicts_client(source_repository).resolve_conflicts(@target_repository, resolution, source_branch, target_branch) + else + rugged_resolve_conflicts(source_repository, resolution, source_branch, target_branch) end - - commit_params = { - message: commit_message, - parents: [@our_commit_oid, @their_commit_oid] - } - - source_repository.commit_index(user, source_branch, index, commit_params) end end @@ -68,6 +58,10 @@ module Gitlab end end + def gitaly_conflicts_client(repository) + repository.gitaly_conflicts_client(@our_commit_oid, @their_commit_oid) + end + def write_resolved_file_to_index(repository, index, file, params) if params[:sections] resolved_lines = file.resolve_lines(params[:sections]) @@ -84,6 +78,40 @@ module Gitlab index.add(path: our_path, oid: oid, mode: file.our_mode) index.conflict_remove(our_path) end + + def rugged_list_conflict_files + target_index = @target_repository.rugged.merge_commits(@our_commit_oid, @their_commit_oid) + + # We don't need to do `with_repo_branch_commit` here, because the target + # project always fetches source refs when creating merge request diffs. + conflict_files(@target_repository, target_index) + end + + def rugged_resolve_conflicts(source_repository, resolution, source_branch, target_branch) + source_repository.with_repo_branch_commit(@target_repository, target_branch) do + index = source_repository.rugged.merge_commits(@our_commit_oid, @their_commit_oid) + conflicts = conflict_files(source_repository, index) + + resolution.files.each do |file_params| + conflict_file = conflict_for_path(conflicts, file_params[:old_path], file_params[:new_path]) + + write_resolved_file_to_index(source_repository, index, conflict_file, file_params) + end + + unless index.conflicts.empty? + missing_files = index.conflicts.map { |file| file[:ours][:path] } + + raise ResolutionError, "Missing resolutions for the following files: #{missing_files.join(', ')}" + end + + commit_params = { + message: resolution.commit_message, + parents: [@our_commit_oid, @their_commit_oid] + } + + source_repository.commit_index(resolution.user, source_branch, index, commit_params) + end + end end end end diff --git a/lib/gitlab/git/gitlab_projects.rb b/lib/gitlab/git/gitlab_projects.rb index d948d7895ed..cba638c06db 100644 --- a/lib/gitlab/git/gitlab_projects.rb +++ b/lib/gitlab/git/gitlab_projects.rb @@ -2,6 +2,9 @@ module Gitlab module Git class GitlabProjects include Gitlab::Git::Popen + include Gitlab::Utils::StrongMemoize + + ShardNameNotFoundError = Class.new(StandardError) # Absolute path to directory where repositories are stored. # Example: /home/git/repositories @@ -97,22 +100,13 @@ module Gitlab end def fork_repository(new_shard_path, new_repository_relative_path) - from_path = repository_absolute_path - to_path = File.join(new_shard_path, new_repository_relative_path) - - # The repository cannot already exist - if File.exist?(to_path) - logger.error "fork-repository failed: destination repository <#{to_path}> already exists." - return false + Gitlab::GitalyClient.migrate(:fork_repository) do |is_enabled| + if is_enabled + gitaly_fork_repository(new_shard_path, new_repository_relative_path) + else + git_fork_repository(new_shard_path, new_repository_relative_path) + end end - - # Ensure the namepsace / hashed storage directory exists - FileUtils.mkdir_p(File.dirname(to_path), mode: 0770) - - logger.info "Forking repository from <#{from_path}> to <#{to_path}>." - cmd = %W(git clone --bare --no-local -- #{from_path} #{to_path}) - - run(cmd, nil) && Gitlab::Git::Repository.create_hooks(to_path, global_hooks_path) end def fetch_remote(name, timeout, force:, tags:, ssh_key: nil, known_hosts: nil) @@ -253,6 +247,48 @@ module Gitlab known_hosts_file&.close! script&.close! end + + private + + def shard_name + strong_memoize(:shard_name) do + shard_name_from_shard_path(shard_path) + end + end + + def shard_name_from_shard_path(shard_path) + Gitlab.config.repositories.storages.find { |_, info| info['path'] == shard_path }&.first || + raise(ShardNameNotFoundError, "no shard found for path '#{shard_path}'") + end + + def git_fork_repository(new_shard_path, new_repository_relative_path) + from_path = repository_absolute_path + to_path = File.join(new_shard_path, new_repository_relative_path) + + # The repository cannot already exist + if File.exist?(to_path) + logger.error "fork-repository failed: destination repository <#{to_path}> already exists." + return false + end + + # Ensure the namepsace / hashed storage directory exists + FileUtils.mkdir_p(File.dirname(to_path), mode: 0770) + + logger.info "Forking repository from <#{from_path}> to <#{to_path}>." + cmd = %W(git clone --bare --no-local -- #{from_path} #{to_path}) + + run(cmd, nil) && Gitlab::Git::Repository.create_hooks(to_path, global_hooks_path) + end + + def gitaly_fork_repository(new_shard_path, new_repository_relative_path) + target_repository = Gitlab::Git::Repository.new(shard_name_from_shard_path(new_shard_path), new_repository_relative_path, nil) + raw_repository = Gitlab::Git::Repository.new(shard_name, repository_relative_path, nil) + + Gitlab::GitalyClient::RepositoryService.new(target_repository).fork_repository(raw_repository) + rescue GRPC::BadStatus => e + logger.error "fork-repository failed: #{e.message}" + false + end end end end diff --git a/lib/gitlab/git/remote_mirror.rb b/lib/gitlab/git/remote_mirror.rb new file mode 100644 index 00000000000..38e9d2a8554 --- /dev/null +++ b/lib/gitlab/git/remote_mirror.rb @@ -0,0 +1,75 @@ +module Gitlab + module Git + class RemoteMirror + def initialize(repository, ref_name) + @repository = repository + @ref_name = ref_name + end + + def update(only_branches_matching: [], only_tags_matching: []) + local_branches = refs_obj(@repository.local_branches, only_refs_matching: only_branches_matching) + remote_branches = refs_obj(@repository.remote_branches(@ref_name), only_refs_matching: only_branches_matching) + + updated_branches = changed_refs(local_branches, remote_branches) + push_branches(updated_branches.keys) if updated_branches.present? + + delete_refs(local_branches, remote_branches) + + local_tags = refs_obj(@repository.tags, only_refs_matching: only_tags_matching) + remote_tags = refs_obj(@repository.remote_tags(@ref_name), only_refs_matching: only_tags_matching) + + updated_tags = changed_refs(local_tags, remote_tags) + @repository.push_remote_branches(@ref_name, updated_tags.keys) if updated_tags.present? + + delete_refs(local_tags, remote_tags) + end + + private + + def refs_obj(refs, only_refs_matching: []) + refs.each_with_object({}) do |ref, refs| + next if only_refs_matching.present? && !only_refs_matching.include?(ref.name) + + refs[ref.name] = ref + end + end + + def changed_refs(local_refs, remote_refs) + local_refs.select do |ref_name, ref| + remote_ref = remote_refs[ref_name] + + remote_ref.nil? || ref.dereferenced_target != remote_ref.dereferenced_target + end + end + + def push_branches(branches) + default_branch, branches = branches.partition do |branch| + @repository.root_ref == branch + end + + # Push the default branch first so it works fine when remote mirror is empty. + branches.unshift(*default_branch) + + @repository.push_remote_branches(@ref_name, branches) + end + + def delete_refs(local_refs, remote_refs) + refs = refs_to_delete(local_refs, remote_refs) + + @repository.delete_remote_branches(@ref_name, refs.keys) if refs.present? + end + + def refs_to_delete(local_refs, remote_refs) + default_branch_id = @repository.commit.id + + remote_refs.select do |remote_ref_name, remote_ref| + next false if local_refs[remote_ref_name] # skip if branch or tag exist in local repo + + remote_ref_id = remote_ref.dereferenced_target.try(:id) + + remote_ref_id && @repository.rugged_is_ancestor?(remote_ref_id, default_branch_id) + end + end + end + end +end diff --git a/lib/gitlab/git/repository.rb b/lib/gitlab/git/repository.rb index 603323d0452..aec85f971ca 100644 --- a/lib/gitlab/git/repository.rb +++ b/lib/gitlab/git/repository.rb @@ -21,6 +21,7 @@ module Gitlab REBASE_WORKTREE_PREFIX = 'rebase'.freeze SQUASH_WORKTREE_PREFIX = 'squash'.freeze GITALY_INTERNAL_URL = 'ssh://gitaly/internal.git'.freeze + GITLAB_PROJECTS_TIMEOUT = Gitlab.config.gitlab_shell.git_timeout NoRepository = Class.new(StandardError) InvalidBlobName = Class.new(StandardError) @@ -83,7 +84,7 @@ module Gitlab # Rugged repo object attr_reader :rugged - attr_reader :storage, :gl_repository, :relative_path + attr_reader :gitlab_projects, :storage, :gl_repository, :relative_path # This initializer method is only used on the client side (gitlab-ce). # Gitaly-ruby uses a different initializer. @@ -93,6 +94,12 @@ module Gitlab @gl_repository = gl_repository storage_path = Gitlab.config.repositories.storages[@storage]['path'] + @gitlab_projects = Gitlab::Git::GitlabProjects.new( + storage_path, + relative_path, + global_hooks_path: Gitlab.config.gitlab_shell.hooks_path, + logger: Rails.logger + ) @path = File.join(storage_path, @relative_path) @name = @relative_path.split("/").last @attributes = Gitlab::Git::Attributes.new(path) @@ -126,7 +133,7 @@ module Gitlab end def exists? - Gitlab::GitalyClient.migrate(:repository_exists, status: Gitlab::GitalyClient::MigrationStatus::OPT_OUT) do |enabled| + Gitlab::GitalyClient.migrate(:repository_exists) do |enabled| if enabled gitaly_repository_client.exists? else @@ -188,7 +195,7 @@ module Gitlab end def local_branches(sort_by: nil) - gitaly_migrate(:local_branches, status: Gitlab::GitalyClient::MigrationStatus::OPT_OUT) do |is_enabled| + gitaly_migrate(:local_branches) do |is_enabled| if is_enabled gitaly_ref_client.local_branches(sort_by: sort_by) else @@ -919,31 +926,23 @@ module Gitlab # If `mirror_refmap` is present the remote is set as mirror with that mapping def add_remote(remote_name, url, mirror_refmap: nil) - rugged.remotes.create(remote_name, url) - - set_remote_as_mirror(remote_name, refmap: mirror_refmap) if mirror_refmap - rescue Rugged::ConfigError - remote_update(remote_name, url: url) + gitaly_migrate(:remote_add_remote) do |is_enabled| + if is_enabled + gitaly_remote_client.add_remote(remote_name, url, mirror_refmap) + else + rugged_add_remote(remote_name, url, mirror_refmap) + end + end end def remove_remote(remote_name) - # When a remote is deleted all its remote refs are deleted too, but in - # the case of mirrors we map its refs (that would usualy go under - # [remote_name]/) to the top level namespace. We clean the mapping so - # those don't get deleted. - if rugged.config["remote.#{remote_name}.mirror"] - rugged.config.delete("remote.#{remote_name}.fetch") + gitaly_migrate(:remote_remove_remote) do |is_enabled| + if is_enabled + gitaly_remote_client.remove_remote(remote_name) + else + rugged_remove_remote(remote_name) + end end - - rugged.remotes.delete(remote_name) - true - rescue Rugged::ConfigError - false - end - - # Returns true if a remote exists. - def remote_exists?(name) - rugged.remotes[name].present? end # Update the specified remote using the values in the +options+ hash @@ -1274,6 +1273,24 @@ module Gitlab fresh_worktree?(worktree_path(SQUASH_WORKTREE_PREFIX, squash_id)) end + def push_remote_branches(remote_name, branch_names, forced: true) + success = @gitlab_projects.push_branches(remote_name, GITLAB_PROJECTS_TIMEOUT, forced, branch_names) + + success || gitlab_projects_error + end + + def delete_remote_branches(remote_name, branch_names) + success = @gitlab_projects.delete_remote_branches(remote_name, branch_names) + + success || gitlab_projects_error + end + + def delete_remote_branches(remote_name, branch_names) + success = @gitlab_projects.delete_remote_branches(remote_name, branch_names) + + success || gitlab_projects_error + end + def gitaly_repository Gitlab::GitalyClient::Util.repository(@storage, @relative_path, @gl_repository) end @@ -1298,6 +1315,14 @@ module Gitlab @gitaly_operation_client ||= Gitlab::GitalyClient::OperationService.new(self) end + def gitaly_remote_client + @gitaly_remote_client ||= Gitlab::GitalyClient::RemoteService.new(self) + end + + def gitaly_conflicts_client(our_commit_oid, their_commit_oid) + Gitlab::GitalyClient::ConflictsService.new(self, our_commit_oid, their_commit_oid) + end + def gitaly_migrate(method, status: Gitlab::GitalyClient::MigrationStatus::OPT_IN, &block) Gitlab::GitalyClient.migrate(method, status: status, &block) rescue GRPC::NotFound => e @@ -1665,6 +1690,7 @@ module Gitlab cmd = %W[#{Gitlab.config.git.bin_path} --git-dir=#{path} rev-list] cmd << "--after=#{options[:after].iso8601}" if options[:after] cmd << "--before=#{options[:before].iso8601}" if options[:before] + cmd << "--max-count=#{options[:max_count]}" if options[:max_count] cmd += %W[--count #{options[:ref]}] cmd += %W[-- #{options[:path]}] if options[:path].present? @@ -1917,9 +1943,36 @@ module Gitlab raise ArgumentError, 'Invalid merge source' end + def rugged_add_remote(remote_name, url, mirror_refmap) + rugged.remotes.create(remote_name, url) + + set_remote_as_mirror(remote_name, refmap: mirror_refmap) if mirror_refmap + rescue Rugged::ConfigError + remote_update(remote_name, url: url) + end + + def rugged_remove_remote(remote_name) + # When a remote is deleted all its remote refs are deleted too, but in + # the case of mirrors we map its refs (that would usualy go under + # [remote_name]/) to the top level namespace. We clean the mapping so + # those don't get deleted. + if rugged.config["remote.#{remote_name}.mirror"] + rugged.config.delete("remote.#{remote_name}.fetch") + end + + rugged.remotes.delete(remote_name) + true + rescue Rugged::ConfigError + false + end + def fetch_remote(remote_name = 'origin', env: nil) run_git(['fetch', remote_name], env: env).last.zero? end + + def gitlab_projects_error + raise CommandError, @gitlab_projects.output + end end end end diff --git a/lib/gitlab/gitaly_client.rb b/lib/gitlab/gitaly_client.rb index b753ac46291..4507ea923b4 100644 --- a/lib/gitlab/gitaly_client.rb +++ b/lib/gitlab/gitaly_client.rb @@ -330,22 +330,6 @@ module Gitlab Google::Protobuf::Timestamp.new(seconds: t.to_i) end - def self.encode(s) - return "" if s.nil? - - s.dup.force_encoding(Encoding::ASCII_8BIT) - end - - def self.binary_stringio(s) - io = StringIO.new(s || '') - io.set_encoding(Encoding::ASCII_8BIT) - io - end - - def self.encode_repeated(a) - Google::Protobuf::RepeatedField.new(:bytes, a.map { |s| self.encode(s) } ) - end - # The default timeout on all Gitaly calls def self.default_timeout return 0 if Sidekiq.server? diff --git a/lib/gitlab/gitaly_client/commit_service.rb b/lib/gitlab/gitaly_client/commit_service.rb index fb3e27770b4..fed05bb6c64 100644 --- a/lib/gitlab/gitaly_client/commit_service.rb +++ b/lib/gitlab/gitaly_client/commit_service.rb @@ -1,6 +1,8 @@ module Gitlab module GitalyClient class CommitService + include Gitlab::EncodingHelper + # The ID of empty tree. # See http://stackoverflow.com/a/40884093/1856239 and https://github.com/git/git/blob/3ad8b5bf26362ac67c9020bf8c30eee54a84f56d/cache.h#L1011-L1012 EMPTY_TREE_ID = '4b825dc642cb6eb9a060e54bf8d69288fbee4904'.freeze @@ -13,7 +15,7 @@ module Gitlab def ls_files(revision) request = Gitaly::ListFilesRequest.new( repository: @gitaly_repo, - revision: GitalyClient.encode(revision) + revision: encode_binary(revision) ) response = GitalyClient.call(@repository.storage, :commit_service, :list_files, request, timeout: GitalyClient.medium_timeout) @@ -73,7 +75,7 @@ module Gitlab request = Gitaly::TreeEntryRequest.new( repository: @gitaly_repo, revision: ref, - path: GitalyClient.encode(path), + path: encode_binary(path), limit: limit.to_i ) @@ -98,8 +100,8 @@ module Gitlab def tree_entries(repository, revision, path) request = Gitaly::GetTreeEntriesRequest.new( repository: @gitaly_repo, - revision: GitalyClient.encode(revision), - path: path.present? ? GitalyClient.encode(path) : '.' + revision: encode_binary(revision), + path: path.present? ? encode_binary(path) : '.' ) response = GitalyClient.call(@repository.storage, :commit_service, :get_tree_entries, request, timeout: GitalyClient.medium_timeout) @@ -112,8 +114,8 @@ module Gitlab type: gitaly_tree_entry.type.downcase, mode: gitaly_tree_entry.mode.to_s(8), name: File.basename(gitaly_tree_entry.path), - path: GitalyClient.encode(gitaly_tree_entry.path), - flat_path: GitalyClient.encode(gitaly_tree_entry.flat_path), + path: encode_binary(gitaly_tree_entry.path), + flat_path: encode_binary(gitaly_tree_entry.flat_path), commit_id: gitaly_tree_entry.commit_oid ) end @@ -128,6 +130,7 @@ module Gitlab request.after = Google::Protobuf::Timestamp.new(seconds: options[:after].to_i) if options[:after].present? request.before = Google::Protobuf::Timestamp.new(seconds: options[:before].to_i) if options[:before].present? request.path = options[:path] if options[:path].present? + request.max_count = options[:max_count] if options[:max_count].present? GitalyClient.call(@repository.storage, :commit_service, :count_commits, request, timeout: GitalyClient.medium_timeout).count end @@ -135,8 +138,8 @@ module Gitlab def last_commit_for_path(revision, path) request = Gitaly::LastCommitForPathRequest.new( repository: @gitaly_repo, - revision: GitalyClient.encode(revision), - path: GitalyClient.encode(path.to_s) + revision: encode_binary(revision), + path: encode_binary(path.to_s) ) gitaly_commit = GitalyClient.call(@repository.storage, :commit_service, :last_commit_for_path, request, timeout: GitalyClient.fast_timeout).commit @@ -202,8 +205,8 @@ module Gitlab def raw_blame(revision, path) request = Gitaly::RawBlameRequest.new( repository: @gitaly_repo, - revision: GitalyClient.encode(revision), - path: GitalyClient.encode(path) + revision: encode_binary(revision), + path: encode_binary(path) ) response = GitalyClient.call(@repository.storage, :commit_service, :raw_blame, request, timeout: GitalyClient.medium_timeout) @@ -213,7 +216,7 @@ module Gitlab def find_commit(revision) request = Gitaly::FindCommitRequest.new( repository: @gitaly_repo, - revision: GitalyClient.encode(revision) + revision: encode_binary(revision) ) response = GitalyClient.call(@repository.storage, :commit_service, :find_commit, request, timeout: GitalyClient.medium_timeout) @@ -224,7 +227,7 @@ module Gitlab def patch(revision) request = Gitaly::CommitPatchRequest.new( repository: @gitaly_repo, - revision: GitalyClient.encode(revision) + revision: encode_binary(revision) ) response = GitalyClient.call(@repository.storage, :diff_service, :commit_patch, request, timeout: GitalyClient.medium_timeout) @@ -234,7 +237,7 @@ module Gitlab def commit_stats(revision) request = Gitaly::CommitStatsRequest.new( repository: @gitaly_repo, - revision: GitalyClient.encode(revision) + revision: encode_binary(revision) ) GitalyClient.call(@repository.storage, :commit_service, :commit_stats, request, timeout: GitalyClient.medium_timeout) end @@ -250,9 +253,9 @@ module Gitlab ) request.after = GitalyClient.timestamp(options[:after]) if options[:after] request.before = GitalyClient.timestamp(options[:before]) if options[:before] - request.revision = GitalyClient.encode(options[:ref]) if options[:ref] + request.revision = encode_binary(options[:ref]) if options[:ref] - request.paths = GitalyClient.encode_repeated(Array(options[:path])) if options[:path].present? + request.paths = encode_repeated(Array(options[:path])) if options[:path].present? response = GitalyClient.call(@repository.storage, :commit_service, :find_commits, request, timeout: GitalyClient.medium_timeout) @@ -264,7 +267,7 @@ module Gitlab enum = Enumerator.new do |y| shas.each_slice(20) do |revs| - request.shas = GitalyClient.encode_repeated(revs) + request.shas = encode_repeated(revs) y.yield request @@ -303,7 +306,7 @@ module Gitlab repository: @gitaly_repo, left_commit_id: from_id, right_commit_id: to_id, - paths: options.fetch(:paths, []).compact.map { |path| GitalyClient.encode(path) } + paths: options.fetch(:paths, []).compact.map { |path| encode_binary(path) } } end @@ -314,6 +317,10 @@ module Gitlab end end end + + def encode_repeated(a) + Google::Protobuf::RepeatedField.new(:bytes, a.map { |s| encode_binary(s) } ) + end end end end diff --git a/lib/gitlab/gitaly_client/conflicts_service.rb b/lib/gitlab/gitaly_client/conflicts_service.rb new file mode 100644 index 00000000000..40f032cf873 --- /dev/null +++ b/lib/gitlab/gitaly_client/conflicts_service.rb @@ -0,0 +1,95 @@ +module Gitlab + module GitalyClient + class ConflictsService + include Gitlab::EncodingHelper + + MAX_MSG_SIZE = 128.kilobytes.freeze + + def initialize(repository, our_commit_oid, their_commit_oid) + @gitaly_repo = repository.gitaly_repository + @repository = repository + @our_commit_oid = our_commit_oid + @their_commit_oid = their_commit_oid + end + + def list_conflict_files + request = Gitaly::ListConflictFilesRequest.new( + repository: @gitaly_repo, + our_commit_oid: @our_commit_oid, + their_commit_oid: @their_commit_oid + ) + response = GitalyClient.call(@repository.storage, :conflicts_service, :list_conflict_files, request) + + files_from_response(response).to_a + end + + def resolve_conflicts(target_repository, resolution, source_branch, target_branch) + reader = binary_stringio(resolution.files.to_json) + + req_enum = Enumerator.new do |y| + header = resolve_conflicts_request_header(target_repository, resolution, source_branch, target_branch) + y.yield Gitaly::ResolveConflictsRequest.new(header: header) + + until reader.eof? + chunk = reader.read(MAX_MSG_SIZE) + + y.yield Gitaly::ResolveConflictsRequest.new(files_json: chunk) + end + end + + response = GitalyClient.call(@repository.storage, :conflicts_service, :resolve_conflicts, req_enum, remote_storage: target_repository.storage) + + if response.resolution_error.present? + raise Gitlab::Git::Conflict::Resolver::ResolutionError, response.resolution_error + end + end + + private + + def resolve_conflicts_request_header(target_repository, resolution, source_branch, target_branch) + Gitaly::ResolveConflictsRequestHeader.new( + repository: @gitaly_repo, + our_commit_oid: @our_commit_oid, + target_repository: target_repository.gitaly_repository, + their_commit_oid: @their_commit_oid, + source_branch: source_branch, + target_branch: target_branch, + commit_message: resolution.commit_message, + user: Gitlab::Git::User.from_gitlab(resolution.user).to_gitaly + ) + end + + def files_from_response(response) + files = [] + + response.each do |msg| + msg.files.each do |gitaly_file| + if gitaly_file.header + files << file_from_gitaly_header(gitaly_file.header) + else + files.last.content << gitaly_file.content + end + end + end + + files + end + + def file_from_gitaly_header(header) + Gitlab::Git::Conflict::File.new( + Gitlab::GitalyClient::Util.git_repository(header.repository), + header.commit_oid, + conflict_from_gitaly_file_header(header), + '' + ) + end + + def conflict_from_gitaly_file_header(header) + { + ours: { path: header.our_path, mode: header.our_mode }, + theirs: { path: header.their_path } + } + end + end + end +end diff --git a/lib/gitlab/gitaly_client/operation_service.rb b/lib/gitlab/gitaly_client/operation_service.rb index 400a4af363b..ae1753ff0ae 100644 --- a/lib/gitlab/gitaly_client/operation_service.rb +++ b/lib/gitlab/gitaly_client/operation_service.rb @@ -1,6 +1,8 @@ module Gitlab module GitalyClient class OperationService + include Gitlab::EncodingHelper + def initialize(repository) @gitaly_repo = repository.gitaly_repository @repository = repository @@ -9,7 +11,7 @@ module Gitlab def rm_tag(tag_name, user) request = Gitaly::UserDeleteTagRequest.new( repository: @gitaly_repo, - tag_name: GitalyClient.encode(tag_name), + tag_name: encode_binary(tag_name), user: Gitlab::Git::User.from_gitlab(user).to_gitaly ) @@ -24,9 +26,9 @@ module Gitlab request = Gitaly::UserCreateTagRequest.new( repository: @gitaly_repo, user: Gitlab::Git::User.from_gitlab(user).to_gitaly, - tag_name: GitalyClient.encode(tag_name), - target_revision: GitalyClient.encode(target), - message: GitalyClient.encode(message.to_s) + tag_name: encode_binary(tag_name), + target_revision: encode_binary(target), + message: encode_binary(message.to_s) ) response = GitalyClient.call(@repository.storage, :operation_service, :user_create_tag, request) @@ -44,9 +46,9 @@ module Gitlab def user_create_branch(branch_name, user, start_point) request = Gitaly::UserCreateBranchRequest.new( repository: @gitaly_repo, - branch_name: GitalyClient.encode(branch_name), + branch_name: encode_binary(branch_name), user: Gitlab::Git::User.from_gitlab(user).to_gitaly, - start_point: GitalyClient.encode(start_point) + start_point: encode_binary(start_point) ) response = GitalyClient.call(@repository.storage, :operation_service, :user_create_branch, request) @@ -64,7 +66,7 @@ module Gitlab def user_delete_branch(branch_name, user) request = Gitaly::UserDeleteBranchRequest.new( repository: @gitaly_repo, - branch_name: GitalyClient.encode(branch_name), + branch_name: encode_binary(branch_name), user: Gitlab::Git::User.from_gitlab(user).to_gitaly ) @@ -89,8 +91,8 @@ module Gitlab repository: @gitaly_repo, user: Gitlab::Git::User.from_gitlab(user).to_gitaly, commit_id: source_sha, - branch: GitalyClient.encode(target_branch), - message: GitalyClient.encode(message) + branch: encode_binary(target_branch), + message: encode_binary(message) ) ) @@ -99,6 +101,7 @@ module Gitlab request_enum.push(Gitaly::UserMergeBranchRequest.new(apply: true)) branch_update = response_enum.next.branch_update + return if branch_update.nil? raise Gitlab::Git::CommitError.new('failed to apply merge to branch') unless branch_update.commit_id.present? Gitlab::Git::OperationService::BranchUpdate.from_gitaly(branch_update) @@ -111,7 +114,7 @@ module Gitlab repository: @gitaly_repo, user: Gitlab::Git::User.from_gitlab(user).to_gitaly, commit_id: source_sha, - branch: GitalyClient.encode(target_branch) + branch: encode_binary(target_branch) ) branch_update = GitalyClient.call( @@ -152,9 +155,9 @@ module Gitlab repository: @gitaly_repo, user: Gitlab::Git::User.from_gitlab(user).to_gitaly, commit: commit.to_gitaly_commit, - branch_name: GitalyClient.encode(branch_name), - message: GitalyClient.encode(message), - start_branch_name: GitalyClient.encode(start_branch_name.to_s), + branch_name: encode_binary(branch_name), + message: encode_binary(message), + start_branch_name: encode_binary(start_branch_name.to_s), start_repository: start_repository.gitaly_repository ) diff --git a/lib/gitlab/gitaly_client/ref_service.rb b/lib/gitlab/gitaly_client/ref_service.rb index 066e4e183c0..5bce1009878 100644 --- a/lib/gitlab/gitaly_client/ref_service.rb +++ b/lib/gitlab/gitaly_client/ref_service.rb @@ -72,7 +72,7 @@ module Gitlab end def ref_exists?(ref_name) - request = Gitaly::RefExistsRequest.new(repository: @gitaly_repo, ref: GitalyClient.encode(ref_name)) + request = Gitaly::RefExistsRequest.new(repository: @gitaly_repo, ref: encode_binary(ref_name)) response = GitalyClient.call(@storage, :ref_service, :ref_exists, request) response.value rescue GRPC::InvalidArgument => e @@ -82,7 +82,7 @@ module Gitlab def find_branch(branch_name) request = Gitaly::FindBranchRequest.new( repository: @gitaly_repo, - name: GitalyClient.encode(branch_name) + name: encode_binary(branch_name) ) response = GitalyClient.call(@repository.storage, :ref_service, :find_branch, request) @@ -96,8 +96,8 @@ module Gitlab def create_branch(ref, start_point) request = Gitaly::CreateBranchRequest.new( repository: @gitaly_repo, - name: GitalyClient.encode(ref), - start_point: GitalyClient.encode(start_point) + name: encode_binary(ref), + start_point: encode_binary(start_point) ) response = GitalyClient.call(@repository.storage, :ref_service, :create_branch, request) @@ -121,7 +121,7 @@ module Gitlab def delete_branch(branch_name) request = Gitaly::DeleteBranchRequest.new( repository: @gitaly_repo, - name: GitalyClient.encode(branch_name) + name: encode_binary(branch_name) ) GitalyClient.call(@repository.storage, :ref_service, :delete_branch, request) diff --git a/lib/gitlab/gitaly_client/remote_service.rb b/lib/gitlab/gitaly_client/remote_service.rb new file mode 100644 index 00000000000..9218f6cfd68 --- /dev/null +++ b/lib/gitlab/gitaly_client/remote_service.rb @@ -0,0 +1,28 @@ +module Gitlab + module GitalyClient + class RemoteService + def initialize(repository) + @repository = repository + @gitaly_repo = repository.gitaly_repository + @storage = repository.storage + end + + def add_remote(name, url, mirror_refmap) + request = Gitaly::AddRemoteRequest.new( + repository: @gitaly_repo, name: name, url: url, + mirror_refmap: mirror_refmap.to_s + ) + + GitalyClient.call(@storage, :remote_service, :add_remote, request) + end + + def remove_remote(name) + request = Gitaly::RemoveRemoteRequest.new(repository: @gitaly_repo, name: name) + + response = GitalyClient.call(@storage, :remote_service, :remove_remote, request) + + response.result + end + end + end +end diff --git a/lib/gitlab/gitaly_client/repository_service.rb b/lib/gitlab/gitaly_client/repository_service.rb index c1f95396878..d43d80da960 100644 --- a/lib/gitlab/gitaly_client/repository_service.rb +++ b/lib/gitlab/gitaly_client/repository_service.rb @@ -1,6 +1,8 @@ module Gitlab module GitalyClient class RepositoryService + include Gitlab::EncodingHelper + def initialize(repository) @repository = repository @gitaly_repo = repository.gitaly_repository @@ -72,13 +74,29 @@ module Gitlab def find_merge_base(*revisions) request = Gitaly::FindMergeBaseRequest.new( repository: @gitaly_repo, - revisions: revisions.map { |r| GitalyClient.encode(r) } + revisions: revisions.map { |r| encode_binary(r) } ) response = GitalyClient.call(@storage, :repository_service, :find_merge_base, request) response.base.presence end + def fork_repository(source_repository) + request = Gitaly::CreateForkRequest.new( + repository: @gitaly_repo, + source_repository: source_repository.gitaly_repository + ) + + GitalyClient.call( + @storage, + :repository_service, + :create_fork, + request, + remote_storage: source_repository.storage, + timeout: GitalyClient.default_timeout + ) + end + def fetch_source_branch(source_repository, source_branch, local_ref) request = Gitaly::FetchSourceBranchRequest.new( repository: @gitaly_repo, diff --git a/lib/gitlab/gitaly_client/util.rb b/lib/gitlab/gitaly_client/util.rb index b1a033280b4..a8c6d478de8 100644 --- a/lib/gitlab/gitaly_client/util.rb +++ b/lib/gitlab/gitaly_client/util.rb @@ -12,12 +12,18 @@ module Gitlab Gitaly::Repository.new( storage_name: repository_storage, relative_path: relative_path, - gl_repository: gl_repository, + gl_repository: gl_repository.to_s, git_object_directory: git_object_directory.to_s, git_alternate_object_directories: git_alternate_object_directories ) end + def git_repository(gitaly_repository) + Gitlab::Git::Repository.new(gitaly_repository.storage_name, + gitaly_repository.relative_path, + gitaly_repository.gl_repository) + end + def gitlab_tag_from_gitaly_tag(repository, gitaly_tag) if gitaly_tag.target_commit.present? commit = Gitlab::Git::Commit.decorate(repository, gitaly_tag.target_commit) diff --git a/lib/gitlab/gitaly_client/wiki_service.rb b/lib/gitlab/gitaly_client/wiki_service.rb index 337d225d081..5c5b170a3e0 100644 --- a/lib/gitlab/gitaly_client/wiki_service.rb +++ b/lib/gitlab/gitaly_client/wiki_service.rb @@ -3,6 +3,8 @@ require 'stringio' module Gitlab module GitalyClient class WikiService + include Gitlab::EncodingHelper + MAX_MSG_SIZE = 128.kilobytes.freeze def initialize(repository) @@ -13,12 +15,12 @@ module Gitlab def write_page(name, format, content, commit_details) request = Gitaly::WikiWritePageRequest.new( repository: @gitaly_repo, - name: GitalyClient.encode(name), + name: encode_binary(name), format: format.to_s, commit_details: gitaly_commit_details(commit_details) ) - strio = GitalyClient.binary_stringio(content) + strio = binary_stringio(content) enum = Enumerator.new do |y| until strio.eof? @@ -39,13 +41,13 @@ module Gitlab def update_page(page_path, title, format, content, commit_details) request = Gitaly::WikiUpdatePageRequest.new( repository: @gitaly_repo, - page_path: GitalyClient.encode(page_path), - title: GitalyClient.encode(title), + page_path: encode_binary(page_path), + title: encode_binary(title), format: format.to_s, commit_details: gitaly_commit_details(commit_details) ) - strio = GitalyClient.binary_stringio(content) + strio = binary_stringio(content) enum = Enumerator.new do |y| until strio.eof? @@ -63,7 +65,7 @@ module Gitlab def delete_page(page_path, commit_details) request = Gitaly::WikiDeletePageRequest.new( repository: @gitaly_repo, - page_path: GitalyClient.encode(page_path), + page_path: encode_binary(page_path), commit_details: gitaly_commit_details(commit_details) ) @@ -73,9 +75,9 @@ module Gitlab def find_page(title:, version: nil, dir: nil) request = Gitaly::WikiFindPageRequest.new( repository: @gitaly_repo, - title: GitalyClient.encode(title), - revision: GitalyClient.encode(version), - directory: GitalyClient.encode(dir) + title: encode_binary(title), + revision: encode_binary(version), + directory: encode_binary(dir) ) response = GitalyClient.call(@repository.storage, :wiki_service, :wiki_find_page, request) @@ -102,8 +104,8 @@ module Gitlab def find_file(name, revision) request = Gitaly::WikiFindFileRequest.new( repository: @gitaly_repo, - name: GitalyClient.encode(name), - revision: GitalyClient.encode(revision) + name: encode_binary(name), + revision: encode_binary(revision) ) response = GitalyClient.call(@repository.storage, :wiki_service, :wiki_find_file, request) @@ -158,9 +160,9 @@ module Gitlab def gitaly_commit_details(commit_details) Gitaly::WikiCommitDetails.new( - name: GitalyClient.encode(commit_details.name), - email: GitalyClient.encode(commit_details.email), - message: GitalyClient.encode(commit_details.message) + name: encode_binary(commit_details.name), + email: encode_binary(commit_details.email), + message: encode_binary(commit_details.message) ) end end diff --git a/lib/gitlab/gon_helper.rb b/lib/gitlab/gon_helper.rb index dfcdfc307b6..9148d7571f2 100644 --- a/lib/gitlab/gon_helper.rb +++ b/lib/gitlab/gon_helper.rb @@ -21,6 +21,7 @@ module Gitlab gon.revision = Gitlab::REVISION gon.gitlab_logo = ActionController::Base.helpers.asset_path('gitlab_logo.png') gon.sprite_icons = IconsHelper.sprite_icon_path + gon.sprite_file_icons = IconsHelper.sprite_file_icons_path if current_user gon.current_user_id = current_user.id diff --git a/lib/gitlab/import_sources.rb b/lib/gitlab/import_sources.rb index eeb03625479..60d5fa4d29a 100644 --- a/lib/gitlab/import_sources.rb +++ b/lib/gitlab/import_sources.rb @@ -7,6 +7,7 @@ module Gitlab module ImportSources ImportSource = Struct.new(:name, :title, :importer) + # We exclude `bare_repository` here as it has no import class associated ImportTable = [ ImportSource.new('github', 'GitHub', Gitlab::GithubImport::ParallelImporter), ImportSource.new('bitbucket', 'Bitbucket', Gitlab::BitbucketImport::Importer), diff --git a/lib/gitlab/kubernetes/helm/api.rb b/lib/gitlab/kubernetes/helm/api.rb index ebd7dc1b100..737081ddc5b 100644 --- a/lib/gitlab/kubernetes/helm/api.rb +++ b/lib/gitlab/kubernetes/helm/api.rb @@ -34,7 +34,7 @@ module Gitlab private def pod_resource(command) - Pod.new(command, @namespace.name, @kubeclient).generate + Gitlab::Kubernetes::Helm::Pod.new(command, @namespace.name, @kubeclient).generate end end end diff --git a/lib/gitlab/metrics/method_call.rb b/lib/gitlab/metrics/method_call.rb index 329b07af5db..c2f9db56824 100644 --- a/lib/gitlab/metrics/method_call.rb +++ b/lib/gitlab/metrics/method_call.rb @@ -5,7 +5,7 @@ module Gitlab # Class for tracking timing information about method calls class MethodCall @@measurement_enabled_cache = Concurrent::AtomicBoolean.new(false) - @@measurement_enabled_cache_expires_at = Concurrent::AtomicFixnum.new(Time.now.to_i) + @@measurement_enabled_cache_expires_at = Concurrent::AtomicReference.new(Time.now.to_i) MUTEX = Mutex.new BASE_LABELS = { module: nil, method: nil }.freeze attr_reader :real_time, :cpu_time, :call_count, :labels diff --git a/lib/gitlab/search_results.rb b/lib/gitlab/search_results.rb index 7037e2e61cc..ca48c6df602 100644 --- a/lib/gitlab/search_results.rb +++ b/lib/gitlab/search_results.rb @@ -82,7 +82,10 @@ module Gitlab end def issues - issues = IssuesFinder.new(current_user).execute.where(project_id: project_ids_relation) + issues = IssuesFinder.new(current_user).execute + unless default_project_filter + issues = issues.where(project_id: project_ids_relation) + end issues = if query =~ /#(\d+)\z/ diff --git a/lib/gitlab/seeder.rb b/lib/gitlab/seeder.rb index 30df7e4a831..94a481a0f2e 100644 --- a/lib/gitlab/seeder.rb +++ b/lib/gitlab/seeder.rb @@ -8,7 +8,7 @@ end module Gitlab class Seeder def self.quiet - mute_mailer unless Rails.env.test? + mute_mailer SeedFu.quiet = true diff --git a/lib/gitlab/shell.rb b/lib/gitlab/shell.rb index 9cdd3d22f18..40650fc5ee7 100644 --- a/lib/gitlab/shell.rb +++ b/lib/gitlab/shell.rb @@ -306,47 +306,6 @@ module Gitlab end end - # Push branch to remote repository - # - # storage - project's storage path - # project_name - project's disk path - # remote_name - remote name - # branch_names - remote branch names to push - # forced - should we use --force flag - # - # Ex. - # push_remote_branches('/path/to/storage', 'gitlab-org/gitlab-test' 'upstream', ['feature']) - # - def push_remote_branches(storage, project_name, remote_name, branch_names, forced: true) - cmd = gitlab_projects(storage, "#{project_name}.git") - - success = cmd.push_branches(remote_name, git_timeout, forced, branch_names) - - raise Error, cmd.output unless success - - success - end - - # Delete branch from remote repository - # - # storage - project's storage path - # project_name - project's disk path - # remote_name - remote name - # branch_names - remote branch names - # - # Ex. - # delete_remote_branches('/path/to/storage', 'gitlab-org/gitlab-test', 'upstream', ['feature']) - # - def delete_remote_branches(storage, project_name, remote_name, branch_names) - cmd = gitlab_projects(storage, "#{project_name}.git") - - success = cmd.delete_remote_branches(remote_name, branch_names) - - raise Error, cmd.output unless success - - success - end - protected def gitlab_shell_path diff --git a/lib/gitlab/visibility_level.rb b/lib/gitlab/visibility_level.rb index 11472ce6cce..6ced06a863d 100644 --- a/lib/gitlab/visibility_level.rb +++ b/lib/gitlab/visibility_level.rb @@ -57,11 +57,17 @@ module Gitlab } end - def highest_allowed_level + def allowed_levels restricted_levels = current_application_settings.restricted_visibility_levels - allowed_levels = self.values - restricted_levels - allowed_levels.max || PRIVATE + self.values - restricted_levels + end + + def closest_allowed_level(target_level) + highest_allowed_level = allowed_levels.select { |level| level <= target_level }.max + + # If all levels are restricted, fall back to PRIVATE + highest_allowed_level || PRIVATE end def allowed_for?(user, level) diff --git a/package.json b/package.json index d80e25e1ac6..3587b015fb3 100644 --- a/package.json +++ b/package.json @@ -108,6 +108,7 @@ "karma-sourcemap-loader": "^0.3.7", "karma-webpack": "^2.0.4", "nodemon": "^1.11.0", + "prettier": "1.9.2", "webpack-dev-server": "^2.6.1" } } @@ -24,6 +24,7 @@ module QA autoload :Sandbox, 'qa/factory/resource/sandbox' autoload :Group, 'qa/factory/resource/group' autoload :Project, 'qa/factory/resource/project' + autoload :DeployKey, 'qa/factory/resource/deploy_key' end module Repository @@ -69,10 +70,15 @@ module QA module Main autoload :Login, 'qa/page/main/login' - autoload :Menu, 'qa/page/main/menu' autoload :OAuth, 'qa/page/main/oauth' end + module Menu + autoload :Main, 'qa/page/menu/main' + autoload :Side, 'qa/page/menu/side' + autoload :Admin, 'qa/page/menu/admin' + end + module Dashboard autoload :Projects, 'qa/page/dashboard/projects' autoload :Groups, 'qa/page/dashboard/groups' @@ -86,10 +92,15 @@ module QA module Project autoload :New, 'qa/page/project/new' autoload :Show, 'qa/page/project/show' + + module Settings + autoload :Common, 'qa/page/project/settings/common' + autoload :Repository, 'qa/page/project/settings/repository' + autoload :DeployKeys, 'qa/page/project/settings/deploy_keys' + end end module Admin - autoload :Menu, 'qa/page/admin/menu' autoload :Settings, 'qa/page/admin/settings' end diff --git a/qa/qa/factory/resource/deploy_key.rb b/qa/qa/factory/resource/deploy_key.rb new file mode 100644 index 00000000000..7c58e70bcc4 --- /dev/null +++ b/qa/qa/factory/resource/deploy_key.rb @@ -0,0 +1,31 @@ +module QA + module Factory + module Resource + class DeployKey < Factory::Base + attr_accessor :title, :key + + dependency Factory::Resource::Project, as: :project do |project| + project.name = 'project-to-deploy' + project.description = 'project for adding deploy key test' + end + + def fabricate! + project.visit! + + Page::Menu::Side.act do + click_repository_setting + end + + Page::Project::Settings::Repository.perform do |setting| + setting.expand_deploy_keys do |page| + page.fill_key_title(title) + page.fill_key_value(key) + + page.add_key + end + end + end + end + end + end +end diff --git a/qa/qa/factory/resource/sandbox.rb b/qa/qa/factory/resource/sandbox.rb index 558da1c973b..ad376988e82 100644 --- a/qa/qa/factory/resource/sandbox.rb +++ b/qa/qa/factory/resource/sandbox.rb @@ -11,7 +11,7 @@ module QA end def fabricate! - Page::Main::Menu.act { go_to_groups } + Page::Menu::Main.act { go_to_groups } Page::Dashboard::Groups.perform do |page| if page.has_group?(@name) diff --git a/qa/qa/factory/settings/hashed_storage.rb b/qa/qa/factory/settings/hashed_storage.rb index eb3b28f2613..13ce2435fe4 100644 --- a/qa/qa/factory/settings/hashed_storage.rb +++ b/qa/qa/factory/settings/hashed_storage.rb @@ -6,15 +6,15 @@ module QA raise ArgumentError unless traits.include?(:enabled) Page::Main::Login.act { sign_in_using_credentials } - Page::Main::Menu.act { go_to_admin_area } - Page::Admin::Menu.act { go_to_settings } + Page::Menu::Main.act { go_to_admin_area } + Page::Menu::Admin.act { go_to_settings } Page::Admin::Settings.act do enable_hashed_storage save_settings end - QA::Page::Main::Menu.act { sign_out } + QA::Page::Menu::Main.act { sign_out } end end end diff --git a/qa/qa/page/admin/menu.rb b/qa/qa/page/menu/admin.rb index dd289ffe269..07fe40fda3a 100644 --- a/qa/qa/page/admin/menu.rb +++ b/qa/qa/page/menu/admin.rb @@ -1,7 +1,7 @@ module QA module Page - module Admin - class Menu < Page::Base + module Menu + class Admin < Page::Base def go_to_license click_link 'License' end diff --git a/qa/qa/page/main/menu.rb b/qa/qa/page/menu/main.rb index bc9c4ec1215..b94c2c6c23d 100644 --- a/qa/qa/page/main/menu.rb +++ b/qa/qa/page/menu/main.rb @@ -1,7 +1,7 @@ module QA module Page - module Main - class Menu < Page::Base + module Menu + class Main < Page::Base def go_to_groups within_top_menu { click_link 'Groups' } end diff --git a/qa/qa/page/menu/side.rb b/qa/qa/page/menu/side.rb new file mode 100644 index 00000000000..6c25aba4bac --- /dev/null +++ b/qa/qa/page/menu/side.rb @@ -0,0 +1,29 @@ +module QA + module Page + module Menu + class Side < Page::Base + def click_repository_setting + hover_setting do + click_link('Repository') + end + end + + private + + def hover_setting + within_sidebar do + find('.nav-item-name', text: 'Settings').hover + + yield + end + end + + def within_sidebar + page.within('.sidebar-top-level-items') do + yield + end + end + end + end + end +end diff --git a/qa/qa/page/project/settings/common.rb b/qa/qa/page/project/settings/common.rb new file mode 100644 index 00000000000..5d1d5120929 --- /dev/null +++ b/qa/qa/page/project/settings/common.rb @@ -0,0 +1,17 @@ +module QA + module Page + module Project + module Settings + module Common + def expand(selector) + page.within('#content-body') do + find(selector).click + + yield + end + end + end + end + end + end +end diff --git a/qa/qa/page/project/settings/deploy_keys.rb b/qa/qa/page/project/settings/deploy_keys.rb new file mode 100644 index 00000000000..4028b8cccc5 --- /dev/null +++ b/qa/qa/page/project/settings/deploy_keys.rb @@ -0,0 +1,27 @@ +module QA + module Page + module Project + module Settings + class DeployKeys < Page::Base + def fill_key_title(title) + fill_in 'deploy_key_title', with: title + end + + def fill_key_value(key) + fill_in 'deploy_key_key', with: key + end + + def add_key + click_on 'Add key' + end + + def has_key_title?(title) + page.within('.deploy-keys') do + page.find('.title', text: title) + end + end + end + end + end + end +end diff --git a/qa/qa/page/project/settings/repository.rb b/qa/qa/page/project/settings/repository.rb new file mode 100644 index 00000000000..034b0d09c1c --- /dev/null +++ b/qa/qa/page/project/settings/repository.rb @@ -0,0 +1,17 @@ +module QA + module Page + module Project + module Settings + class Repository < Page::Base + include Common + + def expand_deploy_keys(&block) + expand('.qa-expand-deploy-keys') do + DeployKeys.perform(&block) + end + end + end + end + end + end +end diff --git a/qa/qa/runtime/user.rb b/qa/qa/runtime/user.rb index 60027c89ab1..2832439d9e0 100644 --- a/qa/qa/runtime/user.rb +++ b/qa/qa/runtime/user.rb @@ -10,6 +10,17 @@ module QA def password ENV['GITLAB_PASSWORD'] || '5iveL!fe' end + + def ssh_key + <<~KEY.delete("\n") + ssh-rsa AAAAB3NzaC1yc2EAAAADAQABAAABAQDFf6RYK3qu/RKF/3ndJmL5xgMLp3O9 + 6x8lTay+QGZ0+9FnnAXMdUqBq/ZU6d/gyMB4IaW3nHzM1w049++yAB6UPCzMB8Uo27K5 + /jyZCtj7Vm9PFNjF/8am1kp46c/SeYicQgQaSBdzIW3UDEa1Ef68qroOlvpi9PYZ/tA7 + M0YP0K5PXX+E36zaIRnJVMPT3f2k+GnrxtjafZrwFdpOP/Fol5BQLBgcsyiU+LM1SuaC + rzd8c9vyaTA1CxrkxaZh+buAi0PmdDtaDrHd42gqZkXCKavyvgM5o2CkQ5LJHCgzpXy0 + 5qNFzmThBSkb+XtoxbyagBiGbVZtSVow6Xa7qewz= dummy@gitlab.com + KEY + end end end end diff --git a/qa/qa/specs/features/login/standard_spec.rb b/qa/qa/specs/features/login/standard_spec.rb index 9eaa2b772e6..141ffa3cfb7 100644 --- a/qa/qa/specs/features/login/standard_spec.rb +++ b/qa/qa/specs/features/login/standard_spec.rb @@ -7,7 +7,7 @@ module QA # TODO, since `Signed in successfully` message was removed # this is the only way to tell if user is signed in correctly. # - Page::Main::Menu.perform do |menu| + Page::Menu::Main.perform do |menu| expect(menu).to have_personal_area end end diff --git a/qa/qa/specs/features/mattermost/group_create_spec.rb b/qa/qa/specs/features/mattermost/group_create_spec.rb index b3dbe44bf6e..2e27a285223 100644 --- a/qa/qa/specs/features/mattermost/group_create_spec.rb +++ b/qa/qa/specs/features/mattermost/group_create_spec.rb @@ -3,7 +3,7 @@ module QA scenario 'creating a group with a mattermost team' do Runtime::Browser.visit(:gitlab, Page::Main::Login) Page::Main::Login.act { sign_in_using_credentials } - Page::Main::Menu.act { go_to_groups } + Page::Menu::Main.act { go_to_groups } Page::Dashboard::Groups.perform do |page| page.go_to_new_group diff --git a/qa/qa/specs/features/project/add_deploy_key_spec.rb b/qa/qa/specs/features/project/add_deploy_key_spec.rb new file mode 100644 index 00000000000..43a85213501 --- /dev/null +++ b/qa/qa/specs/features/project/add_deploy_key_spec.rb @@ -0,0 +1,22 @@ +module QA + feature 'deploy keys support', :core do + given(:deploy_key_title) { 'deploy key title' } + given(:deploy_key_value) { Runtime::User.ssh_key } + + scenario 'user adds a deploy key' do + Runtime::Browser.visit(:gitlab, Page::Main::Login) + Page::Main::Login.act { sign_in_using_credentials } + + Factory::Resource::DeployKey.fabricate! do |deploy_key| + deploy_key.title = deploy_key_title + deploy_key.key = deploy_key_value + end + + Page::Project::Settings::Repository.perform do |setting| + setting.expand_deploy_keys do |page| + expect(page).to have_key_title(deploy_key_title) + end + end + end + end +end diff --git a/rubocop/rubocop.rb b/rubocop/rubocop.rb index 26087f15984..2f63babc425 100644 --- a/rubocop/rubocop.rb +++ b/rubocop/rubocop.rb @@ -1,4 +1,3 @@ -require_relative 'cop/active_record_dependent' require_relative 'cop/gitlab/module_with_instance_variables' require_relative 'cop/include_sidekiq_worker' require_relative 'cop/migration/add_column' @@ -18,6 +17,4 @@ require_relative 'cop/migration/update_column_in_batches' require_relative 'cop/migration/update_large_table' require_relative 'cop/project_path_helper' require_relative 'cop/rspec/env_assignment' -require_relative 'cop/rspec/single_line_hook' -require_relative 'cop/rspec/verbose_include_metadata' require_relative 'cop/sidekiq_options_queue' diff --git a/scripts/add-code-formatters b/scripts/add-code-formatters new file mode 100755 index 00000000000..56bb8754d80 --- /dev/null +++ b/scripts/add-code-formatters @@ -0,0 +1,18 @@ +#!/bin/sh + +# Check if file exists with -f. Check if in in the gdk rook directory. +if [ ! -f ../GDK_ROOT ]; then + echo "Please run script from gitlab (e.g. gitlab-development-kit/gitlab) root directory." + exit 1 +fi + +PRECOMMIT=$(git rev-parse --git-dir)/hooks/pre-commit + +# Check if symlink exists with -L. Check if script was already installed. +if [ -L $PRECOMMIT ]; then + echo "Pre-commit script already installed." + exit 1 +fi + +ln -s ./pre-commit $PRECOMMIT +echo "Pre-commit script installed successfully" diff --git a/scripts/create_mysql_user.sh b/scripts/create_mysql_user.sh index 28f6cfb50ae..286b1325f1d 100644 --- a/scripts/create_mysql_user.sh +++ b/scripts/create_mysql_user.sh @@ -1,7 +1,7 @@ #!/bin/bash mysql --user=root --host=mysql <<EOF -CREATE DATABASE IF NOT EXISTS gitlabhq_test; +CREATE DATABASE IF NOT EXISTS gitlabhq_test DEFAULT CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci; CREATE USER IF NOT EXISTS 'gitlab'@'%'; GRANT ALL PRIVILEGES ON gitlabhq_test.* TO 'gitlab'@'%'; FLUSH PRIVILEGES; diff --git a/scripts/lint-changelog-yaml b/scripts/lint-changelog-yaml index cce5f1c7667..6553e02ffca 100755 --- a/scripts/lint-changelog-yaml +++ b/scripts/lint-changelog-yaml @@ -8,11 +8,13 @@ invalid_changelogs = Dir['changelogs/**/*'].reject do |changelog| begin YAML.load_file(changelog) - rescue + rescue => exception + puts exception end end if invalid_changelogs.any? + puts puts "Invalid changelogs found!\n" puts invalid_changelogs.sort exit 1 diff --git a/scripts/pre-commit b/scripts/pre-commit new file mode 100644 index 00000000000..48935e90a87 --- /dev/null +++ b/scripts/pre-commit @@ -0,0 +1,18 @@ +#!/bin/sh + +# Check if file exists with -f. Check if in in the gdk rook directory. +if [ ! -f ../GDK_ROOT ]; then + echo "Please run pre-commit from gitlab (e.g. gitlab-development-kit/gitlab) root directory." + exit 1 +fi + +jsfiles=$(git diff --cached --name-only --diff-filter=ACM "*.js" | tr '\n' ' ') +[ -z "$jsfiles" ] && exit 0 + +# Prettify all staged .js files +echo "$jsfiles" | xargs ./node_modules/.bin/prettier --write + +# Add back the modified/prettified files to staging +echo "$jsfiles" | xargs git add + +exit 0 diff --git a/scripts/static-analysis b/scripts/static-analysis index 51a2fd81a79..2a2bc67800d 100755 --- a/scripts/static-analysis +++ b/scripts/static-analysis @@ -3,12 +3,10 @@ require ::File.expand_path('../lib/gitlab/popen', __dir__) tasks = [ - %w[bundle exec bundle-audit check --update], %w[bundle exec rake config_lint], %w[bundle exec rake flay], %w[bundle exec rake haml_lint], %w[bundle exec rake scss_lint], - %w[bundle exec rake brakeman], %w[bundle exec license_finder], %w[yarn run eslint], %w[bundle exec rubocop --parallel], diff --git a/spec/controllers/admin/users_controller_spec.rb b/spec/controllers/admin/users_controller_spec.rb index f044a068938..f350641a643 100644 --- a/spec/controllers/admin/users_controller_spec.rb +++ b/spec/controllers/admin/users_controller_spec.rb @@ -13,7 +13,7 @@ describe Admin::UsersController do let!(:issue) { create(:issue, author: user) } before do - project.team << [user, :developer] + project.add_developer(user) end it 'deletes user and ghosts their contributions' do diff --git a/spec/controllers/boards/issues_controller_spec.rb b/spec/controllers/boards/issues_controller_spec.rb index 44d504d5852..79bbc29e80d 100644 --- a/spec/controllers/boards/issues_controller_spec.rb +++ b/spec/controllers/boards/issues_controller_spec.rb @@ -13,8 +13,8 @@ describe Boards::IssuesController do let!(:list2) { create(:list, board: board, label: development, position: 1) } before do - project.team << [user, :master] - project.team << [guest, :guest] + project.add_master(user) + project.add_guest(guest) end describe 'GET index' do @@ -221,7 +221,7 @@ describe Boards::IssuesController do let(:guest) { create(:user) } before do - project.team << [guest, :guest] + project.add_guest(guest) end it 'returns a forbidden 403 response' do diff --git a/spec/controllers/boards/lists_controller_spec.rb b/spec/controllers/boards/lists_controller_spec.rb index a2b432af23a..71d45a22d91 100644 --- a/spec/controllers/boards/lists_controller_spec.rb +++ b/spec/controllers/boards/lists_controller_spec.rb @@ -7,8 +7,8 @@ describe Boards::ListsController do let(:guest) { create(:user) } before do - project.team << [user, :master] - project.team << [guest, :guest] + project.add_master(user) + project.add_guest(guest) end describe 'GET index' do diff --git a/spec/controllers/dashboard/milestones_controller_spec.rb b/spec/controllers/dashboard/milestones_controller_spec.rb index 2f3d7be9abe..60547db82b6 100644 --- a/spec/controllers/dashboard/milestones_controller_spec.rb +++ b/spec/controllers/dashboard/milestones_controller_spec.rb @@ -17,7 +17,7 @@ describe Dashboard::MilestonesController do before do sign_in(user) - project.team << [user, :master] + project.add_master(user) end it_behaves_like 'milestone tabs' diff --git a/spec/controllers/dashboard/todos_controller_spec.rb b/spec/controllers/dashboard/todos_controller_spec.rb index f9faa4fa59a..b4a731fd3a3 100644 --- a/spec/controllers/dashboard/todos_controller_spec.rb +++ b/spec/controllers/dashboard/todos_controller_spec.rb @@ -8,7 +8,7 @@ describe Dashboard::TodosController do before do sign_in(user) - project.team << [user, :developer] + project.add_developer(user) end describe 'GET #index' do diff --git a/spec/controllers/dashboard_controller_spec.rb b/spec/controllers/dashboard_controller_spec.rb index 566d8515198..97c2c3fb940 100644 --- a/spec/controllers/dashboard_controller_spec.rb +++ b/spec/controllers/dashboard_controller_spec.rb @@ -5,7 +5,7 @@ describe DashboardController do let(:project) { create(:project) } before do - project.team << [user, :master] + project.add_master(user) sign_in(user) end diff --git a/spec/controllers/groups/milestones_controller_spec.rb b/spec/controllers/groups/milestones_controller_spec.rb index c1aba46be04..733386500ca 100644 --- a/spec/controllers/groups/milestones_controller_spec.rb +++ b/spec/controllers/groups/milestones_controller_spec.rb @@ -28,7 +28,7 @@ describe Groups::MilestonesController do before do sign_in(user) group.add_owner(user) - project.team << [user, :master] + project.add_master(user) end describe '#index' do diff --git a/spec/controllers/notification_settings_controller_spec.rb b/spec/controllers/notification_settings_controller_spec.rb index 9014b8b5084..e133950e684 100644 --- a/spec/controllers/notification_settings_controller_spec.rb +++ b/spec/controllers/notification_settings_controller_spec.rb @@ -6,7 +6,7 @@ describe NotificationSettingsController do let(:user) { create(:user) } before do - project.team << [user, :developer] + project.add_developer(user) end describe '#create' do diff --git a/spec/controllers/projects/artifacts_controller_spec.rb b/spec/controllers/projects/artifacts_controller_spec.rb index d1051741430..12cb7b2647f 100644 --- a/spec/controllers/projects/artifacts_controller_spec.rb +++ b/spec/controllers/projects/artifacts_controller_spec.rb @@ -1,7 +1,7 @@ require 'spec_helper' describe Projects::ArtifactsController do - set(:user) { create(:user) } + let(:user) { project.owner } set(:project) { create(:project, :repository, :public) } let(:pipeline) do @@ -15,14 +15,12 @@ describe Projects::ArtifactsController do let(:job) { create(:ci_build, :success, :artifacts, pipeline: pipeline) } before do - project.add_developer(user) - sign_in(user) end describe 'GET download' do it 'sends the artifacts file' do - expect(controller).to receive(:send_file).with(job.artifacts_file.path, disposition: 'attachment').and_call_original + expect(controller).to receive(:send_file).with(job.artifacts_file.path, hash_including(disposition: 'attachment')).and_call_original get :download, namespace_id: project.namespace, project_id: project, job_id: job end @@ -113,20 +111,43 @@ describe Projects::ArtifactsController do end describe 'GET raw' do + subject { get(:raw, namespace_id: project.namespace, project_id: project, job_id: job, path: path) } + context 'when the file exists' do - it 'serves the file using workhorse' do - get :raw, namespace_id: project.namespace, project_id: project, job_id: job, path: 'ci_artifacts.txt' + let(:path) { 'ci_artifacts.txt' } - send_data = response.headers[Gitlab::Workhorse::SEND_DATA_HEADER] + shared_examples 'a valid file' do + it 'serves the file using workhorse' do + subject - expect(send_data).to start_with('artifacts-entry:') + expect(response).to have_gitlab_http_status(200) + expect(send_data).to start_with('artifacts-entry:') - base64_params = send_data.sub(/\Aartifacts\-entry:/, '') - params = JSON.parse(Base64.urlsafe_decode64(base64_params)) + expect(params.keys).to eq(%w(Archive Entry)) + expect(params['Archive']).to start_with(archive_path) + # On object storage, the URL can end with a query string + expect(params['Archive']).to match(/build_artifacts.zip(\?[^?]+)?$/) + expect(params['Entry']).to eq(Base64.encode64('ci_artifacts.txt')) + end + + def send_data + response.headers[Gitlab::Workhorse::SEND_DATA_HEADER] + end - expect(params.keys).to eq(%w(Archive Entry)) - expect(params['Archive']).to end_with('build_artifacts.zip') - expect(params['Entry']).to eq(Base64.encode64('ci_artifacts.txt')) + def params + @params ||= begin + base64_params = send_data.sub(/\Aartifacts\-entry:/, '') + JSON.parse(Base64.urlsafe_decode64(base64_params)) + end + end + end + + context 'when using local file storage' do + it_behaves_like 'a valid file' do + let(:job) { create(:ci_build, :success, :artifacts, pipeline: pipeline) } + let(:store) { ObjectStoreUploader::LOCAL_STORE } + let(:archive_path) { JobArtifactUploader.local_store_path } + end end end end diff --git a/spec/controllers/projects/avatars_controller_spec.rb b/spec/controllers/projects/avatars_controller_spec.rb index f5ea097af8b..3bbe168f6d5 100644 --- a/spec/controllers/projects/avatars_controller_spec.rb +++ b/spec/controllers/projects/avatars_controller_spec.rb @@ -6,7 +6,7 @@ describe Projects::AvatarsController do before do sign_in(user) - project.team << [user, :master] + project.add_master(user) controller.instance_variable_set(:@project, project) end diff --git a/spec/controllers/projects/blame_controller_spec.rb b/spec/controllers/projects/blame_controller_spec.rb index 54282aa4001..88d4f4e9cd0 100644 --- a/spec/controllers/projects/blame_controller_spec.rb +++ b/spec/controllers/projects/blame_controller_spec.rb @@ -7,7 +7,7 @@ describe Projects::BlameController do before do sign_in(user) - project.team << [user, :master] + project.add_master(user) controller.instance_variable_set(:@project, project) end diff --git a/spec/controllers/projects/blob_controller_spec.rb b/spec/controllers/projects/blob_controller_spec.rb index 6a1c07b4a0b..00a7df6ccc8 100644 --- a/spec/controllers/projects/blob_controller_spec.rb +++ b/spec/controllers/projects/blob_controller_spec.rb @@ -89,7 +89,7 @@ describe Projects::BlobController do end before do - project.team << [user, :master] + project.add_master(user) sign_in(user) end @@ -147,7 +147,7 @@ describe Projects::BlobController do let(:developer) { create(:user) } before do - project.team << [developer, :developer] + project.add_developer(developer) sign_in(developer) get :edit, default_params end @@ -161,7 +161,7 @@ describe Projects::BlobController do let(:master) { create(:user) } before do - project.team << [master, :master] + project.add_master(master) sign_in(master) get :edit, default_params end @@ -190,7 +190,7 @@ describe Projects::BlobController do end before do - project.team << [user, :master] + project.add_master(user) sign_in(user) end diff --git a/spec/controllers/projects/boards_controller_spec.rb b/spec/controllers/projects/boards_controller_spec.rb index d6ccb92c54b..305af289531 100644 --- a/spec/controllers/projects/boards_controller_spec.rb +++ b/spec/controllers/projects/boards_controller_spec.rb @@ -5,7 +5,7 @@ describe Projects::BoardsController do let(:user) { create(:user) } before do - project.team << [user, :master] + project.add_master(user) sign_in(user) end diff --git a/spec/controllers/projects/branches_controller_spec.rb b/spec/controllers/projects/branches_controller_spec.rb index d731200f70f..734396ddf7b 100644 --- a/spec/controllers/projects/branches_controller_spec.rb +++ b/spec/controllers/projects/branches_controller_spec.rb @@ -6,8 +6,8 @@ describe Projects::BranchesController do let(:developer) { create(:user) } before do - project.team << [user, :master] - project.team << [user, :developer] + project.add_master(user) + project.add_developer(user) allow(project).to receive(:branches).and_return(['master', 'foo/bar/baz']) allow(project).to receive(:tags).and_return(['v1.0.0', 'v2.0.0']) @@ -148,6 +148,20 @@ describe Projects::BranchesController do end end + context 'when create branch service fails' do + let(:branch) { "./invalid-branch-name" } + + it "doesn't post a system note" do + expect(SystemNoteService).not_to receive(:new_issue_branch) + + post :create, + namespace_id: project.namespace, + project_id: project, + branch_name: branch, + issue_iid: issue.iid + end + end + context 'without issue feature access' do before do project.update!(visibility_level: Gitlab::VisibilityLevel::PUBLIC) diff --git a/spec/controllers/projects/commits_controller_spec.rb b/spec/controllers/projects/commits_controller_spec.rb index c459d732507..73fb90d73ec 100644 --- a/spec/controllers/projects/commits_controller_spec.rb +++ b/spec/controllers/projects/commits_controller_spec.rb @@ -6,7 +6,7 @@ describe Projects::CommitsController do before do sign_in(user) - project.team << [user, :master] + project.add_master(user) end describe "GET show" do diff --git a/spec/controllers/projects/compare_controller_spec.rb b/spec/controllers/projects/compare_controller_spec.rb index fe5818da0bc..046ce027965 100644 --- a/spec/controllers/projects/compare_controller_spec.rb +++ b/spec/controllers/projects/compare_controller_spec.rb @@ -8,7 +8,7 @@ describe Projects::CompareController do before do sign_in(user) - project.team << [user, :master] + project.add_master(user) end it 'compare shows some diffs' do diff --git a/spec/controllers/projects/cycle_analytics_controller_spec.rb b/spec/controllers/projects/cycle_analytics_controller_spec.rb index 6fae52edbad..7c708a418a7 100644 --- a/spec/controllers/projects/cycle_analytics_controller_spec.rb +++ b/spec/controllers/projects/cycle_analytics_controller_spec.rb @@ -6,7 +6,7 @@ describe Projects::CycleAnalyticsController do before do sign_in(user) - project.team << [user, :master] + project.add_master(user) end describe 'cycle analytics not set up flag' do diff --git a/spec/controllers/projects/deploy_keys_controller_spec.rb b/spec/controllers/projects/deploy_keys_controller_spec.rb index c3208357694..97db69427e9 100644 --- a/spec/controllers/projects/deploy_keys_controller_spec.rb +++ b/spec/controllers/projects/deploy_keys_controller_spec.rb @@ -5,7 +5,7 @@ describe Projects::DeployKeysController do let(:user) { create(:user) } before do - project.team << [user, :master] + project.add_master(user) sign_in(user) end @@ -48,7 +48,7 @@ describe Projects::DeployKeysController do end before do - project2.team << [user, :developer] + project2.add_developer(user) end it 'returns json in a correct format' do diff --git a/spec/controllers/projects/deployments_controller_spec.rb b/spec/controllers/projects/deployments_controller_spec.rb index 3164fd5c143..73e7921fab7 100644 --- a/spec/controllers/projects/deployments_controller_spec.rb +++ b/spec/controllers/projects/deployments_controller_spec.rb @@ -8,7 +8,7 @@ describe Projects::DeploymentsController do let(:environment) { create(:environment, name: 'production', project: project) } before do - project.team << [user, :master] + project.add_master(user) sign_in(user) end diff --git a/spec/controllers/projects/discussions_controller_spec.rb b/spec/controllers/projects/discussions_controller_spec.rb index 3bf676637a2..00328d3ea51 100644 --- a/spec/controllers/projects/discussions_controller_spec.rb +++ b/spec/controllers/projects/discussions_controller_spec.rb @@ -31,7 +31,7 @@ describe Projects::DiscussionsController do context "when the user is authorized to resolve the discussion" do before do - project.team << [user, :developer] + project.add_developer(user) end context "when the discussion is not resolvable" do @@ -92,7 +92,7 @@ describe Projects::DiscussionsController do context "when the user is authorized to resolve the discussion" do before do - project.team << [user, :developer] + project.add_developer(user) end context "when the discussion is not resolvable" do diff --git a/spec/controllers/projects/find_file_controller_spec.rb b/spec/controllers/projects/find_file_controller_spec.rb index 6a5433bcc9c..505fe82851a 100644 --- a/spec/controllers/projects/find_file_controller_spec.rb +++ b/spec/controllers/projects/find_file_controller_spec.rb @@ -7,7 +7,7 @@ describe Projects::FindFileController do before do sign_in(user) - project.team << [user, :master] + project.add_master(user) controller.instance_variable_set(:@project, project) end diff --git a/spec/controllers/projects/forks_controller_spec.rb b/spec/controllers/projects/forks_controller_spec.rb index 1bedb8ebdff..c4b32dc3a09 100644 --- a/spec/controllers/projects/forks_controller_spec.rb +++ b/spec/controllers/projects/forks_controller_spec.rb @@ -51,7 +51,7 @@ describe Projects::ForksController do context 'when user is a member of the Project' do before do - forked_project.team << [project.creator, :developer] + forked_project.add_developer(project.creator) end it 'sees the project listed' do diff --git a/spec/controllers/projects/graphs_controller_spec.rb b/spec/controllers/projects/graphs_controller_spec.rb index 5af03ae118c..c3605555fe7 100644 --- a/spec/controllers/projects/graphs_controller_spec.rb +++ b/spec/controllers/projects/graphs_controller_spec.rb @@ -6,7 +6,7 @@ describe Projects::GraphsController do before do sign_in(user) - project.team << [user, :master] + project.add_master(user) end describe 'GET languages' do diff --git a/spec/controllers/projects/group_links_controller_spec.rb b/spec/controllers/projects/group_links_controller_spec.rb index f8c792cd0f0..5bfc3d31401 100644 --- a/spec/controllers/projects/group_links_controller_spec.rb +++ b/spec/controllers/projects/group_links_controller_spec.rb @@ -7,7 +7,7 @@ describe Projects::GroupLinksController do let(:user) { create(:user) } before do - project.team << [user, :master] + project.add_master(user) sign_in(user) end diff --git a/spec/controllers/projects/hooks_controller_spec.rb b/spec/controllers/projects/hooks_controller_spec.rb index 07174660f46..aba70c6d4c1 100644 --- a/spec/controllers/projects/hooks_controller_spec.rb +++ b/spec/controllers/projects/hooks_controller_spec.rb @@ -5,7 +5,7 @@ describe Projects::HooksController do let(:user) { create(:user) } before do - project.team << [user, :master] + project.add_master(user) sign_in(user) end diff --git a/spec/controllers/projects/imports_controller_spec.rb b/spec/controllers/projects/imports_controller_spec.rb index 2a5ec6d584b..7fb4c1b7425 100644 --- a/spec/controllers/projects/imports_controller_spec.rb +++ b/spec/controllers/projects/imports_controller_spec.rb @@ -9,7 +9,7 @@ describe Projects::ImportsController do before do sign_in(user) - project.team << [user, :master] + project.add_master(user) end it 'renders template' do @@ -30,7 +30,7 @@ describe Projects::ImportsController do before do sign_in(user) - project.team << [user, :master] + project.add_master(user) end context 'when import is in progress' do diff --git a/spec/controllers/projects/issues_controller_spec.rb b/spec/controllers/projects/issues_controller_spec.rb index a2ef937609b..6b7db947216 100644 --- a/spec/controllers/projects/issues_controller_spec.rb +++ b/spec/controllers/projects/issues_controller_spec.rb @@ -37,7 +37,7 @@ describe Projects::IssuesController do context 'internal issue tracker' do before do sign_in(user) - project.team << [user, :developer] + project.add_developer(user) end it_behaves_like "issuables list meta-data", :issue @@ -69,7 +69,7 @@ describe Projects::IssuesController do before do sign_in(user) - project.team << [user, :developer] + project.add_developer(user) allow(Kaminari.config).to receive(:default_per_page).and_return(1) end @@ -116,7 +116,7 @@ describe Projects::IssuesController do context 'internal issue tracker' do before do sign_in(user) - project.team << [user, :developer] + project.add_developer(user) end it 'builds a new issue' do @@ -127,7 +127,7 @@ describe Projects::IssuesController do it 'fills in an issue for a merge request' do project_with_repository = create(:project, :repository) - project_with_repository.team << [user, :developer] + project_with_repository.add_developer(user) mr = create(:merge_request_with_diff_notes, source_project: project_with_repository) get :new, namespace_id: project_with_repository.namespace, project_id: project_with_repository, merge_request_to_resolve_discussions_of: mr.iid @@ -153,7 +153,7 @@ describe Projects::IssuesController do before do sign_in(user) - project.team << [user, :developer] + project.add_developer(user) external = double allow(project).to receive(:external_issue_tracker).and_return(external) @@ -329,7 +329,7 @@ describe Projects::IssuesController do it 'does not list confidential issues for project members with guest role' do sign_in(member) - project.team << [member, :guest] + project.add_guest(member) get_issues @@ -354,7 +354,7 @@ describe Projects::IssuesController do it 'lists confidential issues for project members' do sign_in(member) - project.team << [member, :developer] + project.add_developer(member) get_issues @@ -394,7 +394,7 @@ describe Projects::IssuesController do it 'returns 404 for project members with guest role' do sign_in(member) - project.team << [member, :guest] + project.add_guest(member) go(id: unescaped_parameter_value.to_param) expect(response).to have_gitlab_http_status :not_found @@ -416,7 +416,7 @@ describe Projects::IssuesController do it "returns #{http_status[:success]} for project members" do sign_in(member) - project.team << [member, :developer] + project.add_developer(member) go(id: unescaped_parameter_value.to_param) expect(response).to have_gitlab_http_status http_status[:success] @@ -450,7 +450,7 @@ describe Projects::IssuesController do before do sign_in(user) - project.team << [user, :developer] + project.add_developer(user) end it_behaves_like 'restricted action', success: 200 @@ -594,7 +594,7 @@ describe Projects::IssuesController do let(:deleted_user) { create(:user) } before do - project.team << [user, :developer] + project.add_developer(user) issue.update!(last_edited_by: deleted_user, last_edited_at: Time.now) @@ -638,7 +638,7 @@ describe Projects::IssuesController do def post_new_issue(issue_attrs = {}, additional_params = {}) sign_in(user) project = create(:project, :public) - project.team << [user, :developer] + project.add_developer(user) post :create, { namespace_id: project.namespace.to_param, @@ -655,7 +655,7 @@ describe Projects::IssuesController do let(:project) { merge_request.source_project } before do - project.team << [user, :master] + project.add_master(user) sign_in user end @@ -829,7 +829,7 @@ describe Projects::IssuesController do def post_spam admin = create(:admin) create(:user_agent_detail, subject: issue) - project.team << [admin, :master] + project.add_master(admin) sign_in(admin) post :mark_as_spam, { namespace_id: project.namespace, diff --git a/spec/controllers/projects/jobs_controller_spec.rb b/spec/controllers/projects/jobs_controller_spec.rb index 7490f8fefce..e6a4e7c8257 100644 --- a/spec/controllers/projects/jobs_controller_spec.rb +++ b/spec/controllers/projects/jobs_controller_spec.rb @@ -374,7 +374,7 @@ describe Projects::JobsController do let(:role) { :master } before do - project.team << [user, role] + project.add_role(user, role) sign_in(user) post_erase diff --git a/spec/controllers/projects/labels_controller_spec.rb b/spec/controllers/projects/labels_controller_spec.rb index cf83f2f3265..452d7e23983 100644 --- a/spec/controllers/projects/labels_controller_spec.rb +++ b/spec/controllers/projects/labels_controller_spec.rb @@ -6,7 +6,7 @@ describe Projects::LabelsController do let(:user) { create(:user) } before do - project.team << [user, :master] + project.add_master(user) sign_in(user) end diff --git a/spec/controllers/projects/mattermosts_controller_spec.rb b/spec/controllers/projects/mattermosts_controller_spec.rb index 33d48ff94d1..c5ac0be27bb 100644 --- a/spec/controllers/projects/mattermosts_controller_spec.rb +++ b/spec/controllers/projects/mattermosts_controller_spec.rb @@ -5,7 +5,7 @@ describe Projects::MattermostsController do let!(:user) { create(:user) } before do - project.team << [user, :master] + project.add_master(user) sign_in(user) end diff --git a/spec/controllers/projects/merge_requests/creations_controller_spec.rb b/spec/controllers/projects/merge_requests/creations_controller_spec.rb index 7fdddc02fd3..7e2366847f4 100644 --- a/spec/controllers/projects/merge_requests/creations_controller_spec.rb +++ b/spec/controllers/projects/merge_requests/creations_controller_spec.rb @@ -6,7 +6,7 @@ describe Projects::MergeRequests::CreationsController do let(:fork_project) { create(:forked_project_with_submodules) } before do - fork_project.team << [user, :master] + fork_project.add_master(user) sign_in(user) end @@ -86,7 +86,7 @@ describe Projects::MergeRequests::CreationsController do let(:other_project) { create(:project, :repository) } before do - other_project.team << [user, :master] + other_project.add_master(user) end context 'when the path exists in the diff' do diff --git a/spec/controllers/projects/merge_requests/diffs_controller_spec.rb b/spec/controllers/projects/merge_requests/diffs_controller_spec.rb index ba97ccfbbd4..5d297c654bf 100644 --- a/spec/controllers/projects/merge_requests/diffs_controller_spec.rb +++ b/spec/controllers/projects/merge_requests/diffs_controller_spec.rb @@ -151,7 +151,7 @@ describe Projects::MergeRequests::DiffsController do let(:other_project) { create(:project) } before do - other_project.team << [user, :master] + other_project.add_master(user) diff_for_path(old_path: existing_path, new_path: existing_path, project_id: other_project) end diff --git a/spec/controllers/projects/milestones_controller_spec.rb b/spec/controllers/projects/milestones_controller_spec.rb index 209979e642d..00cf464ec5b 100644 --- a/spec/controllers/projects/milestones_controller_spec.rb +++ b/spec/controllers/projects/milestones_controller_spec.rb @@ -11,7 +11,7 @@ describe Projects::MilestonesController do before do sign_in(user) - project.team << [user, :master] + project.add_master(user) controller.instance_variable_set(:@project, project) end diff --git a/spec/controllers/projects/notes_controller_spec.rb b/spec/controllers/projects/notes_controller_spec.rb index 37e9f863fc4..de132dfaa21 100644 --- a/spec/controllers/projects/notes_controller_spec.rb +++ b/spec/controllers/projects/notes_controller_spec.rb @@ -32,7 +32,7 @@ describe Projects::NotesController do before do sign_in(user) - project.team << [user, :developer] + project.add_developer(user) end it 'passes last_fetched_at from headers to NotesFinder' do @@ -351,7 +351,7 @@ describe Projects::NotesController do before do sign_in(note.author) - project.team << [note.author, :developer] + project.add_developer(note.author) end it "updates the note" do @@ -372,7 +372,7 @@ describe Projects::NotesController do context 'user is the author of a note' do before do sign_in(note.author) - project.team << [note.author, :developer] + project.add_developer(note.author) end it "returns status 200 for html" do @@ -389,7 +389,7 @@ describe Projects::NotesController do context 'user is not the author of a note' do before do sign_in(user) - project.team << [user, :developer] + project.add_developer(user) end it "returns status 404" do @@ -403,7 +403,7 @@ describe Projects::NotesController do describe 'POST toggle_award_emoji' do before do sign_in(user) - project.team << [user, :developer] + project.add_developer(user) end it "toggles the award emoji" do @@ -445,7 +445,7 @@ describe Projects::NotesController do context "when the user is authorized to resolve the note" do before do - project.team << [user, :developer] + project.add_developer(user) end context "when the note is not resolvable" do @@ -506,7 +506,7 @@ describe Projects::NotesController do context "when the user is authorized to resolve the note" do before do - project.team << [user, :developer] + project.add_developer(user) end context "when the note is not resolvable" do diff --git a/spec/controllers/projects/project_members_controller_spec.rb b/spec/controllers/projects/project_members_controller_spec.rb index 290dba0610a..46b08a03b19 100644 --- a/spec/controllers/projects/project_members_controller_spec.rb +++ b/spec/controllers/projects/project_members_controller_spec.rb @@ -21,7 +21,7 @@ describe Projects::ProjectMembersController do context 'when user does not have enough rights' do before do - project.team << [user, :developer] + project.add_developer(user) end it 'returns 404' do @@ -37,7 +37,7 @@ describe Projects::ProjectMembersController do context 'when user has enough rights' do before do - project.team << [user, :master] + project.add_master(user) end it 'adds user to members' do @@ -106,7 +106,7 @@ describe Projects::ProjectMembersController do context 'when member is found' do context 'when user does not have enough rights' do before do - project.team << [user, :developer] + project.add_developer(user) end it 'returns 404' do @@ -121,7 +121,7 @@ describe Projects::ProjectMembersController do context 'when user has enough rights' do before do - project.team << [user, :master] + project.add_master(user) end it '[HTML] removes user from members' do @@ -164,7 +164,7 @@ describe Projects::ProjectMembersController do context 'when member is found' do context 'and is not an owner' do before do - project.team << [user, :developer] + project.add_developer(user) end it 'removes user from members' do @@ -181,7 +181,7 @@ describe Projects::ProjectMembersController do let(:project) { create(:project, namespace: user.namespace) } before do - project.team << [user, :master] + project.add_master(user) end it 'cannot remove himself from the project' do @@ -248,7 +248,7 @@ describe Projects::ProjectMembersController do context 'when member is found' do context 'when user does not have enough rights' do before do - project.team << [user, :developer] + project.add_developer(user) end it 'returns 404' do @@ -263,7 +263,7 @@ describe Projects::ProjectMembersController do context 'when user has enough rights' do before do - project.team << [user, :master] + project.add_master(user) end it 'adds user to members' do @@ -285,8 +285,8 @@ describe Projects::ProjectMembersController do let(:member) { create(:user) } before do - project.team << [user, :master] - another_project.team << [member, :guest] + project.add_master(user) + another_project.add_guest(member) sign_in(user) end @@ -300,7 +300,7 @@ describe Projects::ProjectMembersController do context 'when user can access source project members' do before do - another_project.team << [user, :guest] + another_project.add_guest(user) end include_context 'import applied' @@ -332,7 +332,7 @@ describe Projects::ProjectMembersController do context 'when creating owner' do before do - project.team << [user, :master] + project.add_master(user) sign_in(user) end @@ -348,7 +348,7 @@ describe Projects::ProjectMembersController do context 'when create master' do before do - project.team << [user, :master] + project.add_master(user) sign_in(user) end diff --git a/spec/controllers/projects/refs_controller_spec.rb b/spec/controllers/projects/refs_controller_spec.rb index 748ae040928..ceaffd92623 100644 --- a/spec/controllers/projects/refs_controller_spec.rb +++ b/spec/controllers/projects/refs_controller_spec.rb @@ -6,7 +6,7 @@ describe Projects::RefsController do before do sign_in(user) - project.team << [user, :developer] + project.add_developer(user) end describe 'GET #logs_tree' do diff --git a/spec/controllers/projects/releases_controller_spec.rb b/spec/controllers/projects/releases_controller_spec.rb index 358f26dfb02..fc1619acec6 100644 --- a/spec/controllers/projects/releases_controller_spec.rb +++ b/spec/controllers/projects/releases_controller_spec.rb @@ -7,7 +7,7 @@ describe Projects::ReleasesController do let!(:tag) { release.tag } before do - project.team << [user, :developer] + project.add_developer(user) sign_in(user) end diff --git a/spec/controllers/projects/repositories_controller_spec.rb b/spec/controllers/projects/repositories_controller_spec.rb index 8b777eb68ca..04d16e98913 100644 --- a/spec/controllers/projects/repositories_controller_spec.rb +++ b/spec/controllers/projects/repositories_controller_spec.rb @@ -17,7 +17,7 @@ describe Projects::RepositoriesController do let(:user) { create(:user) } before do - project.team << [user, :developer] + project.add_developer(user) sign_in(user) end diff --git a/spec/controllers/projects/services_controller_spec.rb b/spec/controllers/projects/services_controller_spec.rb index a907da2b60f..847ac6f2be0 100644 --- a/spec/controllers/projects/services_controller_spec.rb +++ b/spec/controllers/projects/services_controller_spec.rb @@ -9,7 +9,7 @@ describe Projects::ServicesController do before do sign_in(user) - project.team << [user, :master] + project.add_master(user) end describe '#test' do @@ -114,5 +114,41 @@ describe Projects::ServicesController do expect(flash[:notice]).to eq 'HipChat settings saved, but not activated.' end end + + context 'with a deprecated service' do + let(:service) { create(:kubernetes_service, project: project) } + + before do + put :update, + namespace_id: project.namespace, project_id: project, id: service.to_param, service: { namespace: 'updated_namespace' } + end + + it 'should not update the service' do + service.reload + expect(service.namespace).not_to eq('updated_namespace') + end + end + end + + describe "GET #edit" do + before do + get :edit, namespace_id: project.namespace, project_id: project, id: service_id + end + + context 'with approved services' do + let(:service_id) { 'jira' } + + it 'should render edit page' do + expect(response).to be_success + end + end + + context 'with a deprecated service' do + let(:service_id) { 'kubernetes' } + + it 'should render edit page' do + expect(response).to be_success + end + end end end diff --git a/spec/controllers/projects/settings/ci_cd_controller_spec.rb b/spec/controllers/projects/settings/ci_cd_controller_spec.rb index acd40f4a305..e8ba04c7f24 100644 --- a/spec/controllers/projects/settings/ci_cd_controller_spec.rb +++ b/spec/controllers/projects/settings/ci_cd_controller_spec.rb @@ -5,7 +5,7 @@ describe Projects::Settings::CiCdController do let(:user) { create(:user) } before do - project.team << [user, :master] + project.add_master(user) sign_in(user) end diff --git a/spec/controllers/projects/settings/integrations_controller_spec.rb b/spec/controllers/projects/settings/integrations_controller_spec.rb index 3068837f394..77df9a6f567 100644 --- a/spec/controllers/projects/settings/integrations_controller_spec.rb +++ b/spec/controllers/projects/settings/integrations_controller_spec.rb @@ -5,7 +5,7 @@ describe Projects::Settings::IntegrationsController do let(:user) { create(:user) } before do - project.team << [user, :master] + project.add_master(user) sign_in(user) end diff --git a/spec/controllers/projects/templates_controller_spec.rb b/spec/controllers/projects/templates_controller_spec.rb index 70e7f9ca96e..8fcfa3c9ecd 100644 --- a/spec/controllers/projects/templates_controller_spec.rb +++ b/spec/controllers/projects/templates_controller_spec.rb @@ -8,7 +8,7 @@ describe Projects::TemplatesController do let(:body) { JSON.parse(response.body) } before do - project.team << [user, :developer] + project.add_developer(user) sign_in(user) end diff --git a/spec/controllers/projects/todos_controller_spec.rb b/spec/controllers/projects/todos_controller_spec.rb index 4622e27e60f..e2524be7724 100644 --- a/spec/controllers/projects/todos_controller_spec.rb +++ b/spec/controllers/projects/todos_controller_spec.rb @@ -20,7 +20,7 @@ describe Projects::TodosController do context 'when authorized' do before do sign_in(user) - project.team << [user, :developer] + project.add_developer(user) end it 'creates todo for issue' do @@ -88,7 +88,7 @@ describe Projects::TodosController do context 'when authorized' do before do sign_in(user) - project.team << [user, :developer] + project.add_developer(user) end it 'creates todo for merge request' do diff --git a/spec/controllers/projects/tree_controller_spec.rb b/spec/controllers/projects/tree_controller_spec.rb index 65b821c9486..d3188f054cf 100644 --- a/spec/controllers/projects/tree_controller_spec.rb +++ b/spec/controllers/projects/tree_controller_spec.rb @@ -7,7 +7,7 @@ describe Projects::TreeController do before do sign_in(user) - project.team << [user, :master] + project.add_master(user) controller.instance_variable_set(:@project, project) end diff --git a/spec/controllers/projects/variables_controller_spec.rb b/spec/controllers/projects/variables_controller_spec.rb index d065cd00d00..9fde6544215 100644 --- a/spec/controllers/projects/variables_controller_spec.rb +++ b/spec/controllers/projects/variables_controller_spec.rb @@ -6,7 +6,7 @@ describe Projects::VariablesController do before do sign_in(user) - project.team << [user, :master] + project.add_master(user) end describe 'POST #create' do diff --git a/spec/controllers/projects_controller_spec.rb b/spec/controllers/projects_controller_spec.rb index e61187fb518..5202ffdd8bb 100644 --- a/spec/controllers/projects_controller_spec.rb +++ b/spec/controllers/projects_controller_spec.rb @@ -102,7 +102,7 @@ describe ProjectsController do render_views before do - project.team << [user, :developer] + project.add_developer(user) project.project_feature.update_attribute(:repository_access_level, ProjectFeature::DISABLED) end @@ -437,7 +437,7 @@ describe ProjectsController do before do sign_in(user) - project.team << [user, :developer] + project.add_developer(user) allow(Gitlab.config.incoming_email).to receive(:enabled).and_return(true) end @@ -465,7 +465,7 @@ describe ProjectsController do before do sign_in(user) - project.team << [user, :developer] + project.add_developer(user) allow(Gitlab.config.incoming_email).to receive(:enabled).and_return(true) end diff --git a/spec/controllers/uploads_controller_spec.rb b/spec/controllers/uploads_controller_spec.rb index 7e42e43345c..b1f601a19e5 100644 --- a/spec/controllers/uploads_controller_spec.rb +++ b/spec/controllers/uploads_controller_spec.rb @@ -265,13 +265,13 @@ describe UploadsController do context "when the user has access to the project" do before do - project.team << [user, :master] + project.add_master(user) end context "when the user is blocked" do before do user.block - project.team << [user, :master] + project.add_master(user) end it "redirects to the sign in page" do @@ -465,13 +465,13 @@ describe UploadsController do context "when the user has access to the project" do before do - project.team << [user, :master] + project.add_master(user) end context "when the user is blocked" do before do user.block - project.team << [user, :master] + project.add_master(user) end it "redirects to the sign in page" do diff --git a/spec/controllers/users_controller_spec.rb b/spec/controllers/users_controller_spec.rb index 01ab59aa363..2898c4b119e 100644 --- a/spec/controllers/users_controller_spec.rb +++ b/spec/controllers/users_controller_spec.rb @@ -91,7 +91,7 @@ describe UsersController do before do sign_in(user) - project.team << [user, :developer] + project.add_developer(user) push_data = Gitlab::DataBuilder::Push.build_sample(project, user) @@ -117,7 +117,7 @@ describe UsersController do allow_any_instance_of(User).to receive(:contributed_projects_ids).and_return([project.id]) sign_in(user) - project.team << [user, :developer] + project.add_developer(user) end it 'assigns @calendar_date' do diff --git a/spec/factories/keys.rb b/spec/factories/keys.rb index e6eb76f71d3..552b4b7e06e 100644 --- a/spec/factories/keys.rb +++ b/spec/factories/keys.rb @@ -21,12 +21,14 @@ FactoryBot.define do factory :rsa_key_2048 do key do - 'ssh-rsa AAAAB3NzaC1yc2EAAAADAQABAAABAQDFf6RYK3qu/RKF/3ndJmL5xgMLp3O9' \ - '6x8lTay+QGZ0+9FnnAXMdUqBq/ZU6d/gyMB4IaW3nHzM1w049++yAB6UPCzMB8Uo27K5' \ - '/jyZCtj7Vm9PFNjF/8am1kp46c/SeYicQgQaSBdzIW3UDEa1Ef68qroOlvpi9PYZ/tA7' \ - 'M0YP0K5PXX+E36zaIRnJVMPT3f2k+GnrxtjafZrwFdpOP/Fol5BQLBgcsyiU+LM1SuaC' \ - 'rzd8c9vyaTA1CxrkxaZh+buAi0PmdDtaDrHd42gqZkXCKavyvgM5o2CkQ5LJHCgzpXy0' \ - '5qNFzmThBSkb+XtoxbyagBiGbVZtSVow6Xa7qewz= dummy@gitlab.com' + <<~KEY.delete("\n") + ssh-rsa AAAAB3NzaC1yc2EAAAADAQABAAABAQDFf6RYK3qu/RKF/3ndJmL5xgMLp3O9 + 6x8lTay+QGZ0+9FnnAXMdUqBq/ZU6d/gyMB4IaW3nHzM1w049++yAB6UPCzMB8Uo27K5 + /jyZCtj7Vm9PFNjF/8am1kp46c/SeYicQgQaSBdzIW3UDEa1Ef68qroOlvpi9PYZ/tA7 + M0YP0K5PXX+E36zaIRnJVMPT3f2k+GnrxtjafZrwFdpOP/Fol5BQLBgcsyiU+LM1SuaC + rzd8c9vyaTA1CxrkxaZh+buAi0PmdDtaDrHd42gqZkXCKavyvgM5o2CkQ5LJHCgzpXy0 + 5qNFzmThBSkb+XtoxbyagBiGbVZtSVow6Xa7qewz= dummy@gitlab.com + KEY end factory :rsa_deploy_key_2048, class: 'DeployKey' @@ -34,37 +36,44 @@ FactoryBot.define do factory :dsa_key_2048 do key do - 'ssh-dss AAAAB3NzaC1kc3MAAAEBAO/3/NPLA/zSFkMOCaTtGo+uos1flfQ5f038Uk+G' \ - 'Y9AeLGzX+Srhw59GdVXmOQLYBrOt5HdGwqYcmLnE2VurUGmhtfeO5H+3p5pGJbkS0Gxp' \ - 'YH1HRO9lWsncF3Hh1w4lYsDjkclDiSTdfTuN8F4Kb3DXNnVSCieeonp+B25F/CXagyTQ' \ - '/pvNmHFeYgGCVdnBtFdi+xfxaZ8NKdPrGggzokbKHElDZQ4Xo5EpdcyLajgM7nB2r2Rz' \ - 'OrmeaevKi5lV68ehRa9Yyrb7vxvwiwBwOgqR/mnN7Gnaq1jUdmJY+ct04Qwx37f5jvhv' \ - '5gA4U40SGMoiHM8RFIN7Ksz0jsyX73MAAAAVALRWOfjfzHpK7KLz4iqDvvTUAevJAAAB' \ - 'AEa9NZ+6y9iQ5erGsdfLTXFrhSefTG0NhghoO/5IFkSGfd8V7kzTvCHaFrcfpEA5kP8t' \ - 'poeOG0TASB6tgGOxm1Bq4Wncry5RORBPJlAVpDGRcvZ931ddH7IgltEInS6za2uH6F/1' \ - 'M1QfKePSLr6xJ1ZLYfP0Og5KTp1x6yMQvfwV0a+XdA+EPgaJWLWp/pWwKWa0oLUgjsIH' \ - 'MYzuOGh5c708uZrmkzqvgtW2NgXhcIroRgynT3IfI2lP2rqqb3uuuE/qH5UCUFO+Dc3H' \ - 'nAFNeQDT/M25AERdPYBAY5a+iPjIgO+jT7BfmfByT+AZTqZySrCyc7nNZL3YgGLK0l6A' \ - '1GgAAAEBAN9FpFOdIXE+YEZhKl1vPmbcn+b1y5zOl6N4x1B7Q8pD/pLMziWROIS8uLzb' \ - 'aZ0sMIWezHIkxuo1iROMeT+jtCubn7ragaN6AX7nMpxYUH9+mYZZs/fyElt6wCviVhTI' \ - 'zM+u7VdQsnZttOOlQfogHdL+SpeAft0DsfJjlcgQnsLlHQKv6aPqCPYUST2nE7RyW/Ex' \ - 'PrMxLtOWt0/j8RYHbwwqvyeZqBz3ESBgrS9c5tBdBfauwYUV/E7gPLOU3OZFw9ue7o+z' \ - 'wzoTZqW6Xouy5wtWvSLQSLT5XwOslmQz8QMBxD0AQyDfEFGsBCWzmbTgKv9uqrBjubsS' \ - 'Taja+Cf9kMo== dummy@gitlab.com' + <<~KEY.delete("\n") + ssh-dss AAAAB3NzaC1kc3MAAAEBAO/3/NPLA/zSFkMOCaTtGo+uos1flfQ5f038Uk+G + Y9AeLGzX+Srhw59GdVXmOQLYBrOt5HdGwqYcmLnE2VurUGmhtfeO5H+3p5pGJbkS0Gxp + YH1HRO9lWsncF3Hh1w4lYsDjkclDiSTdfTuN8F4Kb3DXNnVSCieeonp+B25F/CXagyTQ + /pvNmHFeYgGCVdnBtFdi+xfxaZ8NKdPrGggzokbKHElDZQ4Xo5EpdcyLajgM7nB2r2Rz + OrmeaevKi5lV68ehRa9Yyrb7vxvwiwBwOgqR/mnN7Gnaq1jUdmJY+ct04Qwx37f5jvhv + 5gA4U40SGMoiHM8RFIN7Ksz0jsyX73MAAAAVALRWOfjfzHpK7KLz4iqDvvTUAevJAAAB + AEa9NZ+6y9iQ5erGsdfLTXFrhSefTG0NhghoO/5IFkSGfd8V7kzTvCHaFrcfpEA5kP8t + poeOG0TASB6tgGOxm1Bq4Wncry5RORBPJlAVpDGRcvZ931ddH7IgltEInS6za2uH6F/1 + M1QfKePSLr6xJ1ZLYfP0Og5KTp1x6yMQvfwV0a+XdA+EPgaJWLWp/pWwKWa0oLUgjsIH + MYzuOGh5c708uZrmkzqvgtW2NgXhcIroRgynT3IfI2lP2rqqb3uuuE/qH5UCUFO+Dc3H + nAFNeQDT/M25AERdPYBAY5a+iPjIgO+jT7BfmfByT+AZTqZySrCyc7nNZL3YgGLK0l6A + 1GgAAAEBAN9FpFOdIXE+YEZhKl1vPmbcn+b1y5zOl6N4x1B7Q8pD/pLMziWROIS8uLzb + aZ0sMIWezHIkxuo1iROMeT+jtCubn7ragaN6AX7nMpxYUH9+mYZZs/fyElt6wCviVhTI + zM+u7VdQsnZttOOlQfogHdL+SpeAft0DsfJjlcgQnsLlHQKv6aPqCPYUST2nE7RyW/Ex + PrMxLtOWt0/j8RYHbwwqvyeZqBz3ESBgrS9c5tBdBfauwYUV/E7gPLOU3OZFw9ue7o+z + wzoTZqW6Xouy5wtWvSLQSLT5XwOslmQz8QMBxD0AQyDfEFGsBCWzmbTgKv9uqrBjubsS + Taja+Cf9kMo== dummy@gitlab.com + KEY end end factory :ecdsa_key_256 do key do - 'ecdsa-sha2-nistp256 AAAAE2VjZHNhLXNoYTItbmlzdHAyNTYAAAAIbmlzdHAyNTYA' \ - 'AABBBJZmkzTgY0fiCQ+DVReyH/fFwTFz0XoR3RUO0u+199H19KFw7mNPxRSMOVS7tEtO' \ - 'Nj3Q7FcZXfqthHvgAzDiHsc= dummy@gitlab.com' + <<~KEY.delete("\n") + ecdsa-sha2-nistp256 AAAAE2VjZHNhLXNoYTItbmlzdHAyNTYAAAAIbmlzdHAyNTYA + AABBBJZmkzTgY0fiCQ+DVReyH/fFwTFz0XoR3RUO0u+199H19KFw7mNPxRSMOVS7tEtO + Nj3Q7FcZXfqthHvgAzDiHsc= dummy@gitlab.com + KEY end end factory :ed25519_key_256 do key do - 'ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIETnVTgzqC1gatgSlC4zH6aYt2CAQzgJOhDRvf59ohL6 dummy@gitlab.com' + <<~KEY.delete("\n") + ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIETnVTgzqC1gatgSlC4zH6aYt2CAQzgJ + OhDRvf59ohL6 dummy@gitlab.com + KEY end end end diff --git a/spec/factories/services.rb b/spec/factories/services.rb index 4b0377967c7..110ef33c6f7 100644 --- a/spec/factories/services.rb +++ b/spec/factories/services.rb @@ -18,6 +18,7 @@ FactoryBot.define do factory :kubernetes_service do project + type 'KubernetesService' active true properties({ api_url: 'https://kubernetes.example.com', diff --git a/spec/features/admin/admin_projects_spec.rb b/spec/features/admin/admin_projects_spec.rb index 94b12105088..d02ac6c2e2a 100644 --- a/spec/features/admin/admin_projects_spec.rb +++ b/spec/features/admin/admin_projects_spec.rb @@ -88,7 +88,7 @@ describe "Admin::Projects" do describe 'add admin himself to a project' do before do - project.team << [user, :master] + project.add_master(user) end it 'adds admin a to a project as developer', :js do @@ -110,8 +110,8 @@ describe "Admin::Projects" do describe 'admin remove himself from a project' do before do - project.team << [user, :master] - project.team << [current_user, :developer] + project.add_master(user) + project.add_developer(current_user) end it 'removes admin from the project' do diff --git a/spec/features/atom/dashboard_issues_spec.rb b/spec/features/atom/dashboard_issues_spec.rb index 89c9d377003..d673bac4995 100644 --- a/spec/features/atom/dashboard_issues_spec.rb +++ b/spec/features/atom/dashboard_issues_spec.rb @@ -8,8 +8,8 @@ describe "Dashboard Issues Feed" do let!(:project2) { create(:project) } before do - project1.team << [user, :master] - project2.team << [user, :master] + project1.add_master(user) + project2.add_master(user) end describe "atom feed" do diff --git a/spec/features/atom/dashboard_spec.rb b/spec/features/atom/dashboard_spec.rb index 2c0c331b6db..c6683bb3bc9 100644 --- a/spec/features/atom/dashboard_spec.rb +++ b/spec/features/atom/dashboard_spec.rb @@ -26,7 +26,7 @@ describe "Dashboard Feed" do let(:note) { create(:note, noteable: issue, author: user, note: 'Bug confirmed', project: project) } before do - project.team << [user, :master] + project.add_master(user) issue_event(issue, user) note_event(note, user) visit dashboard_projects_path(:atom, rss_token: user.rss_token) diff --git a/spec/features/atom/issues_spec.rb b/spec/features/atom/issues_spec.rb index 4102ac0588a..525ce23aa56 100644 --- a/spec/features/atom/issues_spec.rb +++ b/spec/features/atom/issues_spec.rb @@ -9,7 +9,7 @@ describe 'Issues Feed' do let!(:issue) { create(:issue, author: user, assignees: [assignee], project: project) } before do - project.team << [user, :developer] + project.add_developer(user) group.add_developer(user) end diff --git a/spec/features/atom/users_spec.rb b/spec/features/atom/users_spec.rb index 2b934d81674..782f42aab04 100644 --- a/spec/features/atom/users_spec.rb +++ b/spec/features/atom/users_spec.rb @@ -47,7 +47,7 @@ describe "User Feed" do let!(:push_event_payload) { create(:push_event_payload, event: push_event) } before do - project.team << [user, :master] + project.add_master(user) issue_event(issue, user) note_event(note, user) merge_request_event(merge_request, user) diff --git a/spec/features/auto_deploy_spec.rb b/spec/features/auto_deploy_spec.rb index 7a395f62511..9aef68b7156 100644 --- a/spec/features/auto_deploy_spec.rb +++ b/spec/features/auto_deploy_spec.rb @@ -52,7 +52,7 @@ describe 'Auto deploy' do context 'when user configured kubernetes from Integration > Kubernetes' do before do create :kubernetes_service, project: project - project.team << [user, :master] + project.add_master(user) sign_in user end @@ -65,7 +65,7 @@ describe 'Auto deploy' do context 'when user configured kubernetes from CI/CD > Clusters' do before do create(:cluster, :provided_by_gcp, projects: [project]) - project.team << [user, :master] + project.add_master(user) sign_in user end diff --git a/spec/features/boards/add_issues_modal_spec.rb b/spec/features/boards/add_issues_modal_spec.rb index e4cfcea45a5..18901a954cc 100644 --- a/spec/features/boards/add_issues_modal_spec.rb +++ b/spec/features/boards/add_issues_modal_spec.rb @@ -12,7 +12,7 @@ describe 'Issue Boards add issue modal', :js do let!(:issue2) { create(:issue, project: project, title: 'hij', description: 'klm') } before do - project.team << [user, :master] + project.add_master(user) sign_in(user) diff --git a/spec/features/boards/boards_spec.rb b/spec/features/boards/boards_spec.rb index e8d779f5772..3876d1c76d7 100644 --- a/spec/features/boards/boards_spec.rb +++ b/spec/features/boards/boards_spec.rb @@ -11,8 +11,8 @@ describe 'Issue Boards', :js do let!(:user2) { create(:user) } before do - project.team << [user, :master] - project.team << [user2, :master] + project.add_master(user) + project.add_master(user2) set_cookie('sidebar_collapsed', 'true') @@ -551,7 +551,7 @@ describe 'Issue Boards', :js do let(:user_guest) { create(:user) } before do - project.team << [user_guest, :guest] + project.add_guest(user_guest) sign_out(:user) sign_in(user_guest) visit project_board_path(project, board) diff --git a/spec/features/boards/issue_ordering_spec.rb b/spec/features/boards/issue_ordering_spec.rb index 4cbb48e2e6e..5abd02dbb48 100644 --- a/spec/features/boards/issue_ordering_spec.rb +++ b/spec/features/boards/issue_ordering_spec.rb @@ -13,7 +13,7 @@ describe 'Issue Boards', :js do let!(:issue3) { create(:labeled_issue, project: project, title: 'testing 3', labels: [label], relative_position: 1) } before do - project.team << [user, :master] + project.add_master(user) sign_in(user) end diff --git a/spec/features/boards/modal_filter_spec.rb b/spec/features/boards/modal_filter_spec.rb index 422d96175f7..5907bb0840f 100644 --- a/spec/features/boards/modal_filter_spec.rb +++ b/spec/features/boards/modal_filter_spec.rb @@ -10,7 +10,7 @@ describe 'Issue Boards add issue modal filtering', :js do let!(:issue1) { create(:issue, project: project) } before do - project.team << [user, :master] + project.add_master(user) sign_in(user) end @@ -76,7 +76,7 @@ describe 'Issue Boards add issue modal filtering', :js do let!(:issue) { create(:issue, project: project, author: user2) } before do - project.team << [user2, :developer] + project.add_developer(user2) visit_board end @@ -99,7 +99,7 @@ describe 'Issue Boards add issue modal filtering', :js do let!(:issue) { create(:issue, project: project, assignees: [user2]) } before do - project.team << [user2, :developer] + project.add_developer(user2) visit_board end diff --git a/spec/features/boards/new_issue_spec.rb b/spec/features/boards/new_issue_spec.rb index 5ac4d87e90b..6769acb7c9c 100644 --- a/spec/features/boards/new_issue_spec.rb +++ b/spec/features/boards/new_issue_spec.rb @@ -8,7 +8,7 @@ describe 'Issue Boards new issue', :js do context 'authorized user' do before do - project.team << [user, :master] + project.add_master(user) sign_in(user) diff --git a/spec/features/commits_spec.rb b/spec/features/commits_spec.rb index 77dcdf89f37..a28b8905b65 100644 --- a/spec/features/commits_spec.rb +++ b/spec/features/commits_spec.rb @@ -26,7 +26,7 @@ describe 'Commits' do let!(:status) { create(:generic_commit_status, pipeline: pipeline) } before do - project.team << [user, :reporter] + project.add_reporter(user) end describe 'Commit builds' do @@ -51,7 +51,7 @@ describe 'Commits' do context 'when logged as developer' do before do - project.team << [user, :developer] + project.add_developer(user) end describe 'Project commits' do @@ -145,7 +145,7 @@ describe 'Commits' do context "when logged as reporter" do before do - project.team << [user, :reporter] + project.add_reporter(user) build.update_attributes(legacy_artifacts_file: artifacts_file) visit pipeline_path(pipeline) end @@ -188,7 +188,7 @@ describe 'Commits' do let(:branch_name) { 'master' } before do - project.team << [user, :master] + project.add_master(user) sign_in(user) visit project_commits_path(project, branch_name) end diff --git a/spec/features/cycle_analytics_spec.rb b/spec/features/cycle_analytics_spec.rb index 177cd50dd72..d36954954b6 100644 --- a/spec/features/cycle_analytics_spec.rb +++ b/spec/features/cycle_analytics_spec.rb @@ -95,7 +95,7 @@ feature 'Cycle Analytics', :js do before do user.update_attribute(:preferred_language, 'es') - project.team << [user, :master] + project.add_master(user) sign_in(user) visit project_cycle_analytics_path(project) wait_for_requests diff --git a/spec/features/dashboard/activity_spec.rb b/spec/features/dashboard/activity_spec.rb index bd115785646..a74a8aac2b2 100644 --- a/spec/features/dashboard/activity_spec.rb +++ b/spec/features/dashboard/activity_spec.rb @@ -24,6 +24,7 @@ feature 'Dashboard > Activity' do end let(:note) { create(:note, project: project, noteable: merge_request) } + let(:milestone) { create(:milestone, :active, project: project, title: '1.0') } let!(:push_event) do event = create(:push_event, project: project, author: user) @@ -54,6 +55,10 @@ feature 'Dashboard > Activity' do create(:event, :commented, project: project, target: note, author: user) end + let!(:milestone_event) do + create(:event, :closed, project: project, target: milestone, author: user) + end + before do project.add_master(user) @@ -68,6 +73,7 @@ feature 'Dashboard > Activity' do expect(page).to have_content('accepted') expect(page).to have_content('closed') expect(page).to have_content('commented on') + expect(page).to have_content('closed milestone') end end @@ -107,6 +113,7 @@ feature 'Dashboard > Activity' do expect(page).not_to have_content('accepted') expect(page).to have_content('closed') expect(page).not_to have_content('commented on') + expect(page).to have_content('closed milestone') end end diff --git a/spec/features/dashboard/archived_projects_spec.rb b/spec/features/dashboard/archived_projects_spec.rb index e8d699ff5e0..b36231fd78b 100644 --- a/spec/features/dashboard/archived_projects_spec.rb +++ b/spec/features/dashboard/archived_projects_spec.rb @@ -6,8 +6,8 @@ RSpec.describe 'Dashboard Archived Project' do let(:archived_project) { create(:project, :archived) } before do - project.team << [user, :master] - archived_project.team << [user, :master] + project.add_master(user) + archived_project.add_master(user) sign_in(user) diff --git a/spec/features/dashboard/datetime_on_tooltips_spec.rb b/spec/features/dashboard/datetime_on_tooltips_spec.rb index 349f9a47112..089c388636d 100644 --- a/spec/features/dashboard/datetime_on_tooltips_spec.rb +++ b/spec/features/dashboard/datetime_on_tooltips_spec.rb @@ -8,7 +8,7 @@ feature 'Tooltips on .timeago dates', :js do context 'on the activity tab' do before do - project.team << [user, :master] + project.add_master(user) Event.create( project: project, author_id: user.id, action: Event::JOINED, updated_at: created_date, created_at: created_date) @@ -27,7 +27,7 @@ feature 'Tooltips on .timeago dates', :js do context 'on the snippets tab' do before do - project.team << [user, :master] + project.add_master(user) create(:snippet, author: user, updated_at: created_date, created_at: created_date) sign_in user diff --git a/spec/features/dashboard/groups_list_spec.rb b/spec/features/dashboard/groups_list_spec.rb index d92c002b4e7..a71020002dc 100644 --- a/spec/features/dashboard/groups_list_spec.rb +++ b/spec/features/dashboard/groups_list_spec.rb @@ -94,22 +94,14 @@ feature 'Dashboard Groups page', :js do end it 'can toggle parent group' do - # Collapsed by default - expect(page).not_to have_selector("#group-#{group.id} .fa-caret-down", count: 1) - expect(page).to have_selector("#group-#{group.id} .fa-caret-right") - # expand click_group_caret(group) - expect(page).to have_selector("#group-#{group.id} .fa-caret-down") - expect(page).not_to have_selector("#group-#{group.id} .fa-caret-right", count: 1) expect(page).to have_selector("#group-#{group.id} #group-#{subgroup.id}") # collapse click_group_caret(group) - expect(page).not_to have_selector("#group-#{group.id} .fa-caret-down", count: 1) - expect(page).to have_selector("#group-#{group.id} .fa-caret-right") expect(page).not_to have_selector("#group-#{group.id} #group-#{subgroup.id}") end end diff --git a/spec/features/dashboard/issues_spec.rb b/spec/features/dashboard/issues_spec.rb index 5b4c00b3c7e..54652e2d849 100644 --- a/spec/features/dashboard/issues_spec.rb +++ b/spec/features/dashboard/issues_spec.rb @@ -12,7 +12,7 @@ RSpec.describe 'Dashboard Issues' do let!(:other_issue) { create :issue, project: project } before do - [project, project_with_issues_disabled].each { |project| project.team << [current_user, :master] } + [project, project_with_issues_disabled].each { |project| project.add_master(current_user) } sign_in(current_user) visit issues_dashboard_path(assignee_id: current_user.id) end diff --git a/spec/features/dashboard/milestones_spec.rb b/spec/features/dashboard/milestones_spec.rb index 41d37376cfb..7787772a958 100644 --- a/spec/features/dashboard/milestones_spec.rb +++ b/spec/features/dashboard/milestones_spec.rb @@ -16,7 +16,7 @@ feature 'Dashboard > Milestones' do let(:project) { create(:project, namespace: user.namespace) } let!(:milestone) { create(:milestone, project: project) } before do - project.team << [user, :master] + project.add_master(user) sign_in(user) visit dashboard_milestones_path end diff --git a/spec/features/dashboard/project_member_activity_index_spec.rb b/spec/features/dashboard/project_member_activity_index_spec.rb index 8f96899fb4f..6c3093607b0 100644 --- a/spec/features/dashboard/project_member_activity_index_spec.rb +++ b/spec/features/dashboard/project_member_activity_index_spec.rb @@ -5,7 +5,7 @@ feature 'Project member activity', :js do let(:project) { create(:project, :public, name: 'x', namespace: user.namespace) } before do - project.team << [user, :master] + project.add_master(user) end def visit_activities_and_wait_with_event(event_type) diff --git a/spec/features/dashboard/projects_spec.rb b/spec/features/dashboard/projects_spec.rb index fbf8b5c0db6..586c7b48d0b 100644 --- a/spec/features/dashboard/projects_spec.rb +++ b/spec/features/dashboard/projects_spec.rb @@ -6,7 +6,7 @@ feature 'Dashboard Projects' do let(:project2) { create(:project, :public, name: 'Community project') } before do - project.team << [user, :developer] + project.add_developer(user) sign_in(user) end diff --git a/spec/features/dashboard/todos/todos_filtering_spec.rb b/spec/features/dashboard/todos/todos_filtering_spec.rb index ad0f132da8c..2fc34301d51 100644 --- a/spec/features/dashboard/todos/todos_filtering_spec.rb +++ b/spec/features/dashboard/todos/todos_filtering_spec.rb @@ -15,8 +15,8 @@ feature 'Dashboard > User filters todos', :js do create(:todo, user: user_1, author: user_2, project: project_1, target: issue, action: 1) create(:todo, user: user_1, author: user_1, project: project_2, target: merge_request, action: 2) - project_1.team << [user_1, :developer] - project_2.team << [user_1, :developer] + project_1.add_developer(user_1) + project_2.add_developer(user_1) sign_in(user_1) visit dashboard_todos_path end @@ -66,8 +66,8 @@ feature 'Dashboard > User filters todos', :js do create(:todo, user: user_1, author: user_3, project: project_1, target: issue, action: 1, state: :done) create(:todo, user: user_1, author: user_4, project: project_2, target: merge_request, action: 2, state: :done) - project_1.team << [user_3, :developer] - project_2.team << [user_4, :developer] + project_1.add_developer(user_3) + project_2.add_developer(user_4) visit dashboard_todos_path(state: 'done') diff --git a/spec/features/dashboard/todos/todos_sorting_spec.rb b/spec/features/dashboard/todos/todos_sorting_spec.rb index b7d39a872b0..10e3ad843fd 100644 --- a/spec/features/dashboard/todos/todos_sorting_spec.rb +++ b/spec/features/dashboard/todos/todos_sorting_spec.rb @@ -9,7 +9,7 @@ feature 'Dashboard > User sorts todos' do let(:label_3) { create(:label, title: 'label_3', project: project, priority: 3) } before do - project.team << [user, :developer] + project.add_developer(user) end context 'sort options' do diff --git a/spec/features/dashboard/user_filters_projects_spec.rb b/spec/features/dashboard/user_filters_projects_spec.rb index c352b6ded14..92f4d4b854c 100644 --- a/spec/features/dashboard/user_filters_projects_spec.rb +++ b/spec/features/dashboard/user_filters_projects_spec.rb @@ -7,14 +7,14 @@ describe 'Dashboard > User filters projects' do let(:project2) { create(:project, name: 'Treasure', namespace: user2.namespace) } before do - project.team << [user, :master] + project.add_master(user) sign_in(user) end describe 'filtering personal projects' do before do - project2.team << [user, :developer] + project2.add_developer(user) visit dashboard_projects_path end diff --git a/spec/features/global_search_spec.rb b/spec/features/global_search_spec.rb index f04e13adba7..4f575613848 100644 --- a/spec/features/global_search_spec.rb +++ b/spec/features/global_search_spec.rb @@ -5,7 +5,7 @@ feature 'Global search' do let(:project) { create(:project, namespace: user.namespace) } before do - project.team << [user, :master] + project.add_master(user) sign_in(user) end diff --git a/spec/features/issues/award_emoji_spec.rb b/spec/features/issues/award_emoji_spec.rb index 850b35c4467..1131e1711bf 100644 --- a/spec/features/issues/award_emoji_spec.rb +++ b/spec/features/issues/award_emoji_spec.rb @@ -11,7 +11,7 @@ describe 'Awards Emoji' do context 'authorized user' do before do - project.team << [user, :master] + project.add_master(user) sign_in(user) end diff --git a/spec/features/issues/bulk_assignment_labels_spec.rb b/spec/features/issues/bulk_assignment_labels_spec.rb index fa4d3a55c62..587ece22ec7 100644 --- a/spec/features/issues/bulk_assignment_labels_spec.rb +++ b/spec/features/issues/bulk_assignment_labels_spec.rb @@ -11,7 +11,7 @@ feature 'Issues > Labels bulk assignment' do context 'as an allowed user', :js do before do - project.team << [user, :master] + project.add_master(user) sign_in user end diff --git a/spec/features/issues/create_issue_for_discussions_in_merge_request_spec.rb b/spec/features/issues/create_issue_for_discussions_in_merge_request_spec.rb index 822ba48e005..e0466aaf422 100644 --- a/spec/features/issues/create_issue_for_discussions_in_merge_request_spec.rb +++ b/spec/features/issues/create_issue_for_discussions_in_merge_request_spec.rb @@ -8,7 +8,7 @@ feature 'Resolving all open discussions in a merge request from an issue', :js d describe 'as a user with access to the project' do before do - project.team << [user, :master] + project.add_master(user) sign_in user visit project_merge_request_path(project, merge_request) end @@ -81,7 +81,7 @@ feature 'Resolving all open discussions in a merge request from an issue', :js d describe 'as a reporter' do before do - project.team << [user, :reporter] + project.add_reporter(user) sign_in user visit new_project_issue_path(project, merge_request_to_resolve_discussions_of: merge_request.iid) end diff --git a/spec/features/issues/create_issue_for_single_discussion_in_merge_request_spec.rb b/spec/features/issues/create_issue_for_single_discussion_in_merge_request_spec.rb index f0bed85595c..34beb282bad 100644 --- a/spec/features/issues/create_issue_for_single_discussion_in_merge_request_spec.rb +++ b/spec/features/issues/create_issue_for_single_discussion_in_merge_request_spec.rb @@ -8,7 +8,7 @@ feature 'Resolve an open discussion in a merge request by creating an issue' do describe 'As a user with access to the project' do before do - project.team << [user, :master] + project.add_master(user) sign_in user visit project_merge_request_path(project, merge_request) end @@ -65,7 +65,7 @@ feature 'Resolve an open discussion in a merge request by creating an issue' do describe 'as a reporter' do before do - project.team << [user, :reporter] + project.add_reporter(user) sign_in user visit new_project_issue_path(project, merge_request_to_resolve_discussions_of: merge_request.iid, discussion_to_resolve: discussion.id) diff --git a/spec/features/issues/filtered_search/dropdown_assignee_spec.rb b/spec/features/issues/filtered_search/dropdown_assignee_spec.rb index 2e4a25ee15d..cbd0949c192 100644 --- a/spec/features/issues/filtered_search/dropdown_assignee_spec.rb +++ b/spec/features/issues/filtered_search/dropdown_assignee_spec.rb @@ -20,9 +20,9 @@ describe 'Dropdown assignee', :js do end before do - project.team << [user, :master] - project.team << [user_john, :master] - project.team << [user_jacob, :master] + project.add_master(user) + project.add_master(user_john) + project.add_master(user_jacob) sign_in(user) create(:issue, project: project) @@ -222,7 +222,7 @@ describe 'Dropdown assignee', :js do expect(initial_size).to be > 0 new_user = create(:user) - project.team << [new_user, :master] + project.add_master(new_user) find('.filtered-search-box .clear-search').click filtered_search.set('assignee') filtered_search.send_keys(':') diff --git a/spec/features/issues/filtered_search/dropdown_author_spec.rb b/spec/features/issues/filtered_search/dropdown_author_spec.rb index 2fb5e7cdba4..70b4f11410d 100644 --- a/spec/features/issues/filtered_search/dropdown_author_spec.rb +++ b/spec/features/issues/filtered_search/dropdown_author_spec.rb @@ -28,9 +28,9 @@ describe 'Dropdown author', :js do end before do - project.team << [user, :master] - project.team << [user_john, :master] - project.team << [user_jacob, :master] + project.add_master(user) + project.add_master(user_john) + project.add_master(user_jacob) sign_in(user) create(:issue, project: project) @@ -195,7 +195,7 @@ describe 'Dropdown author', :js do expect(initial_size).to be > 0 new_user = create(:user) - project.team << [new_user, :master] + project.add_master(new_user) find('.filtered-search-box .clear-search').click filtered_search.set('author') send_keys_to_filtered_search(':') diff --git a/spec/features/issues/filtered_search/dropdown_emoji_spec.rb b/spec/features/issues/filtered_search/dropdown_emoji_spec.rb index 8db435634fd..436625a6f7b 100644 --- a/spec/features/issues/filtered_search/dropdown_emoji_spec.rb +++ b/spec/features/issues/filtered_search/dropdown_emoji_spec.rb @@ -28,7 +28,7 @@ describe 'Dropdown emoji', :js do end before do - project.team << [user, :master] + project.add_master(user) create_list(:award_emoji, 2, user: user, name: 'thumbsup') create_list(:award_emoji, 1, user: user, name: 'thumbsdown') create_list(:award_emoji, 3, user: user, name: 'star') diff --git a/spec/features/issues/filtered_search/dropdown_hint_spec.rb b/spec/features/issues/filtered_search/dropdown_hint_spec.rb index 0183495a1db..ef40dddfd3a 100644 --- a/spec/features/issues/filtered_search/dropdown_hint_spec.rb +++ b/spec/features/issues/filtered_search/dropdown_hint_spec.rb @@ -13,7 +13,7 @@ describe 'Dropdown hint', :js do end before do - project.team << [user, :master] + project.add_master(user) create(:issue, project: project) end @@ -176,6 +176,7 @@ describe 'Dropdown hint', :js do it 'reuses existing author text' do filtered_search.send_keys('author:') filtered_search.send_keys(:backspace) + filtered_search.send_keys(:backspace) click_hint('author') expect_tokens([{ name: 'author' }]) @@ -185,6 +186,7 @@ describe 'Dropdown hint', :js do it 'reuses existing assignee text' do filtered_search.send_keys('assignee:') filtered_search.send_keys(:backspace) + filtered_search.send_keys(:backspace) click_hint('assignee') expect_tokens([{ name: 'assignee' }]) @@ -194,6 +196,7 @@ describe 'Dropdown hint', :js do it 'reuses existing milestone text' do filtered_search.send_keys('milestone:') filtered_search.send_keys(:backspace) + filtered_search.send_keys(:backspace) click_hint('milestone') expect_tokens([{ name: 'milestone' }]) @@ -203,6 +206,7 @@ describe 'Dropdown hint', :js do it 'reuses existing label text' do filtered_search.send_keys('label:') filtered_search.send_keys(:backspace) + filtered_search.send_keys(:backspace) click_hint('label') expect_tokens([{ name: 'label' }]) @@ -212,6 +216,7 @@ describe 'Dropdown hint', :js do it 'reuses existing emoji text' do filtered_search.send_keys('my-reaction:') filtered_search.send_keys(:backspace) + filtered_search.send_keys(:backspace) click_hint('my-reaction') expect_tokens([{ name: 'my-reaction' }]) diff --git a/spec/features/issues/filtered_search/dropdown_milestone_spec.rb b/spec/features/issues/filtered_search/dropdown_milestone_spec.rb index 031eb06723a..94710c2f71f 100644 --- a/spec/features/issues/filtered_search/dropdown_milestone_spec.rb +++ b/spec/features/issues/filtered_search/dropdown_milestone_spec.rb @@ -29,7 +29,7 @@ describe 'Dropdown milestone', :js do end before do - project.team << [user, :master] + project.add_master(user) sign_in(user) create(:issue, project: project) diff --git a/spec/features/issues/filtered_search/search_bar_spec.rb b/spec/features/issues/filtered_search/search_bar_spec.rb index 88688422dc7..268590da599 100644 --- a/spec/features/issues/filtered_search/search_bar_spec.rb +++ b/spec/features/issues/filtered_search/search_bar_spec.rb @@ -8,7 +8,7 @@ describe 'Search bar', :js do let(:filtered_search) { find('.filtered-search') } before do - project.team << [user, :master] + project.add_master(user) sign_in(user) create(:issue, project: project) diff --git a/spec/features/issues/form_spec.rb b/spec/features/issues/form_spec.rb index 2db6f9a2982..faf14be4818 100644 --- a/spec/features/issues/form_spec.rb +++ b/spec/features/issues/form_spec.rb @@ -13,8 +13,8 @@ describe 'New/edit issue', :js do let!(:issue) { create(:issue, project: project, assignees: [user], milestone: milestone) } before do - project.team << [user, :master] - project.team << [user2, :master] + project.add_master(user) + project.add_master(user2) sign_in(user) end diff --git a/spec/features/issues/gfm_autocomplete_spec.rb b/spec/features/issues/gfm_autocomplete_spec.rb index 6a9a80235c1..f2624f55c86 100644 --- a/spec/features/issues/gfm_autocomplete_spec.rb +++ b/spec/features/issues/gfm_autocomplete_spec.rb @@ -7,7 +7,7 @@ feature 'GFM autocomplete', :js do let(:issue) { create(:issue, project: project) } before do - project.team << [user, :master] + project.add_master(user) sign_in(user) visit project_issue_path(project, issue) diff --git a/spec/features/issues/issue_sidebar_spec.rb b/spec/features/issues/issue_sidebar_spec.rb index a9de52bd8d5..a5c9d0bde5d 100644 --- a/spec/features/issues/issue_sidebar_spec.rb +++ b/spec/features/issues/issue_sidebar_spec.rb @@ -18,7 +18,7 @@ feature 'Issue Sidebar' do let(:issue2) { create(:issue, project: project, author: user2) } before do - project.team << [user, :developer] + project.add_developer(user) visit_issue(project, issue2) find('.block.assignee .edit-link').click @@ -78,7 +78,7 @@ feature 'Issue Sidebar' do context 'as a allowed user' do before do - project.team << [user, :developer] + project.add_developer(user) visit_issue(project, issue) end @@ -156,7 +156,7 @@ feature 'Issue Sidebar' do context 'as a guest' do before do - project.team << [user, :guest] + project.add_guest(user) visit_issue(project, issue) end diff --git a/spec/features/issues/move_spec.rb b/spec/features/issues/move_spec.rb index 17035b5501c..076a02150a4 100644 --- a/spec/features/issues/move_spec.rb +++ b/spec/features/issues/move_spec.rb @@ -13,7 +13,7 @@ feature 'issue move to another project' do context 'user does not have permission to move issue' do background do - old_project.team << [user, :guest] + old_project.add_guest(user) visit issue_path(issue) end @@ -31,8 +31,8 @@ feature 'issue move to another project' do let(:cross_reference) { old_project.to_reference(new_project) } background do - old_project.team << [user, :reporter] - new_project.team << [user, :reporter] + old_project.add_reporter(user) + new_project.add_reporter(user) visit issue_path(issue) end @@ -50,7 +50,7 @@ feature 'issue move to another project' do end scenario 'searching project dropdown', :js do - new_project_search.team << [user, :reporter] + new_project_search.add_reporter(user) find('.js-move-issue').click wait_for_requests @@ -66,7 +66,7 @@ feature 'issue move to another project' do context 'user does not have permission to move the issue to a project', :js do let!(:private_project) { create(:project, :private) } let(:another_project) { create(:project) } - background { another_project.team << [user, :guest] } + background { another_project.add_guest(user) } scenario 'browsing projects in projects select' do find('.js-move-issue').click diff --git a/spec/features/issues/notes_on_issues_spec.rb b/spec/features/issues/notes_on_issues_spec.rb index 05c93a19253..f08c73f947c 100644 --- a/spec/features/issues/notes_on_issues_spec.rb +++ b/spec/features/issues/notes_on_issues_spec.rb @@ -8,7 +8,7 @@ describe 'Create notes on issues', :js do let(:note_text) { "Check #{mention.to_reference}" } before do - project.team << [user, :developer] + project.add_developer(user) sign_in(user) visit project_issue_path(project, issue) diff --git a/spec/features/issues/spam_issues_spec.rb b/spec/features/issues/spam_issues_spec.rb index d25231d624c..53706ef84bc 100644 --- a/spec/features/issues/spam_issues_spec.rb +++ b/spec/features/issues/spam_issues_spec.rb @@ -17,7 +17,7 @@ describe 'New issue', :js do recaptcha_private_key: 'test private key' ) - project.team << [user, :master] + project.add_master(user) sign_in(user) end diff --git a/spec/features/issues/todo_spec.rb b/spec/features/issues/todo_spec.rb index 29a2d38ae18..8e6493bbd93 100644 --- a/spec/features/issues/todo_spec.rb +++ b/spec/features/issues/todo_spec.rb @@ -6,7 +6,7 @@ feature 'Manually create a todo item from issue', :js do let!(:user) { create(:user)} before do - project.team << [user, :master] + project.add_master(user) sign_in(user) visit project_issue_path(project, issue) end diff --git a/spec/features/issues/update_issues_spec.rb b/spec/features/issues/update_issues_spec.rb index bcc6e9bab0f..7d6edc171f8 100644 --- a/spec/features/issues/update_issues_spec.rb +++ b/spec/features/issues/update_issues_spec.rb @@ -6,7 +6,7 @@ feature 'Multiple issue updating from issues#index', :js do let!(:user) { create(:user)} before do - project.team << [user, :master] + project.add_master(user) sign_in(user) end diff --git a/spec/features/issues/user_uses_slash_commands_spec.rb b/spec/features/issues/user_uses_slash_commands_spec.rb index c4c06ed514b..e711a191db2 100644 --- a/spec/features/issues/user_uses_slash_commands_spec.rb +++ b/spec/features/issues/user_uses_slash_commands_spec.rb @@ -12,7 +12,7 @@ feature 'Issues > User uses quick actions', :js do let(:project) { create(:project, :public) } before do - project.team << [user, :master] + project.add_master(user) sign_in(user) visit project_issue_path(project, issue) end @@ -50,7 +50,7 @@ feature 'Issues > User uses quick actions', :js do context 'when the current user cannot update the due date' do let(:guest) { create(:user) } before do - project.team << [guest, :guest] + project.add_guest(guest) gitlab_sign_out sign_in(guest) visit project_issue_path(project, issue) @@ -90,7 +90,7 @@ feature 'Issues > User uses quick actions', :js do context 'when the current user cannot update the due date' do let(:guest) { create(:user) } before do - project.team << [guest, :guest] + project.add_guest(guest) gitlab_sign_out sign_in(guest) visit project_issue_path(project, issue) @@ -138,7 +138,7 @@ feature 'Issues > User uses quick actions', :js do context 'when the current user cannot update the issue' do let(:guest) { create(:user) } before do - project.team << [guest, :guest] + project.add_guest(guest) gitlab_sign_out sign_in(guest) visit project_issue_path(project, issue) @@ -163,7 +163,7 @@ feature 'Issues > User uses quick actions', :js do let(:target_project) { create(:project, :public) } before do - target_project.team << [user, :master] + target_project.add_master(user) sign_in(user) visit project_issue_path(project, issue) end @@ -220,7 +220,7 @@ feature 'Issues > User uses quick actions', :js do let(:wontfix_target) { create(:label, project: target_project, title: 'wontfix') } before do - target_project.team << [user, :master] + target_project.add_master(user) sign_in(user) visit project_issue_path(project, issue) end diff --git a/spec/features/issues_spec.rb b/spec/features/issues_spec.rb index d1ff057a0c6..314bd19f586 100644 --- a/spec/features/issues_spec.rb +++ b/spec/features/issues_spec.rb @@ -25,7 +25,8 @@ describe 'Issues' do sign_in(user) user2 = create(:user) - project.team << [[user, user2], :developer] + project.add_developer(user) + project.add_developer(user2) end describe 'empty state' do @@ -383,7 +384,7 @@ describe 'Issues' do before do stub_incoming_email_setting(enabled: true, address: "p+%{key}@gl.ab") - project1.team << [user, :master] + project1.add_master(user) visit namespace_project_issues_path(user.namespace, project1) end @@ -491,7 +492,7 @@ describe 'Issues' do let(:guest) { create(:user) } before do - project.team << [[guest], :guest] + project.add_guest(guest) end it 'shows assignee text', :js do @@ -552,7 +553,7 @@ describe 'Issues' do let(:guest) { create(:user) } before do - project.team << [guest, :guest] + project.add_guest(guest) issue.milestone = milestone issue.save end diff --git a/spec/features/merge_requests/assign_issues_spec.rb b/spec/features/merge_requests/assign_issues_spec.rb index d49d145f254..b2d64a62b4f 100644 --- a/spec/features/merge_requests/assign_issues_spec.rb +++ b/spec/features/merge_requests/assign_issues_spec.rb @@ -9,7 +9,7 @@ feature 'Merge request issue assignment', :js do let(:service) { MergeRequests::AssignIssuesService.new(merge_request, user, user, project) } before do - project.team << [user, :developer] + project.add_developer(user) end def visit_merge_request(current_user = nil) diff --git a/spec/features/merge_requests/check_if_mergeable_with_unresolved_discussions_spec.rb b/spec/features/merge_requests/check_if_mergeable_with_unresolved_discussions_spec.rb index fbbfe7942be..892c32c8806 100644 --- a/spec/features/merge_requests/check_if_mergeable_with_unresolved_discussions_spec.rb +++ b/spec/features/merge_requests/check_if_mergeable_with_unresolved_discussions_spec.rb @@ -7,7 +7,7 @@ feature 'Check if mergeable with unresolved discussions', :js do before do sign_in user - project.team << [user, :master] + project.add_master(user) end context 'when project.only_allow_merge_if_all_discussions_are_resolved == true' do diff --git a/spec/features/merge_requests/cherry_pick_spec.rb b/spec/features/merge_requests/cherry_pick_spec.rb index 48f370c3ad4..205e38337d1 100644 --- a/spec/features/merge_requests/cherry_pick_spec.rb +++ b/spec/features/merge_requests/cherry_pick_spec.rb @@ -8,7 +8,7 @@ describe 'Cherry-pick Merge Requests', :js do before do sign_in user - project.team << [user, :master] + project.add_master(user) end context "Viewing a merged merge request" do diff --git a/spec/features/merge_requests/closes_issues_spec.rb b/spec/features/merge_requests/closes_issues_spec.rb index 4dd4e40f52c..55de9a01ed5 100644 --- a/spec/features/merge_requests/closes_issues_spec.rb +++ b/spec/features/merge_requests/closes_issues_spec.rb @@ -18,7 +18,7 @@ feature 'Merge Request closing issues message', :js do let(:merge_request_title) { 'Merge Request Title' } before do - project.team << [user, :master] + project.add_master(user) sign_in user diff --git a/spec/features/merge_requests/conflicts_spec.rb b/spec/features/merge_requests/conflicts_spec.rb index 4e2963c116d..05d99a2dff2 100644 --- a/spec/features/merge_requests/conflicts_spec.rb +++ b/spec/features/merge_requests/conflicts_spec.rb @@ -88,7 +88,7 @@ feature 'Merge request conflict resolution', :js do context 'can be resolved in the UI' do before do - project.team << [user, :developer] + project.add_developer(user) sign_in(user) end @@ -175,7 +175,7 @@ feature 'Merge request conflict resolution', :js do let(:merge_request) { create_merge_request(source_branch) } before do - project.team << [user, :developer] + project.add_developer(user) sign_in(user) visit project_merge_request_path(project, merge_request) diff --git a/spec/features/merge_requests/create_new_mr_spec.rb b/spec/features/merge_requests/create_new_mr_spec.rb index db5ce2d11a8..486555ed5cd 100644 --- a/spec/features/merge_requests/create_new_mr_spec.rb +++ b/spec/features/merge_requests/create_new_mr_spec.rb @@ -5,7 +5,7 @@ feature 'Create New Merge Request', :js do let(:project) { create(:project, :public, :repository) } before do - project.team << [user, :master] + project.add_master(user) sign_in user end diff --git a/spec/features/merge_requests/created_from_fork_spec.rb b/spec/features/merge_requests/created_from_fork_spec.rb index ca2225318cd..53b62caf743 100644 --- a/spec/features/merge_requests/created_from_fork_spec.rb +++ b/spec/features/merge_requests/created_from_fork_spec.rb @@ -14,7 +14,7 @@ feature 'Merge request created from fork' do end background do - forked_project.team << [user, :master] + forked_project.add_master(user) sign_in user end diff --git a/spec/features/merge_requests/deleted_source_branch_spec.rb b/spec/features/merge_requests/deleted_source_branch_spec.rb index 7f69e82af4c..56aa0b2ede2 100644 --- a/spec/features/merge_requests/deleted_source_branch_spec.rb +++ b/spec/features/merge_requests/deleted_source_branch_spec.rb @@ -9,7 +9,7 @@ describe 'Deleted source branch', :js do before do sign_in user - merge_request.project.team << [user, :master] + merge_request.project.add_master(user) merge_request.update!(source_branch: 'this-branch-does-not-exist') visit project_merge_request_path(merge_request.project, merge_request) end diff --git a/spec/features/merge_requests/diff_notes_avatars_spec.rb b/spec/features/merge_requests/diff_notes_avatars_spec.rb index 9e816cf041b..ef8f314cc03 100644 --- a/spec/features/merge_requests/diff_notes_avatars_spec.rb +++ b/spec/features/merge_requests/diff_notes_avatars_spec.rb @@ -19,7 +19,7 @@ feature 'Diff note avatars', :js do let!(:note) { create(:diff_note_on_merge_request, project: project, noteable: merge_request, position: position) } before do - project.team << [user, :master] + project.add_master(user) sign_in user set_cookie('sidebar_collapsed', 'true') diff --git a/spec/features/merge_requests/diff_notes_resolve_spec.rb b/spec/features/merge_requests/diff_notes_resolve_spec.rb index 15d380b1bf4..9d4194d8ca0 100644 --- a/spec/features/merge_requests/diff_notes_resolve_spec.rb +++ b/spec/features/merge_requests/diff_notes_resolve_spec.rb @@ -18,7 +18,7 @@ feature 'Diff notes resolve', :js do context 'no discussions' do before do - project.team << [user, :master] + project.add_master(user) sign_in user note.destroy visit_merge_request @@ -32,7 +32,7 @@ feature 'Diff notes resolve', :js do context 'as authorized user' do before do - project.team << [user, :master] + project.add_master(user) sign_in user visit_merge_request end @@ -429,7 +429,7 @@ feature 'Diff notes resolve', :js do let(:guest) { create(:user) } before do - project.team << [guest, :guest] + project.add_guest(guest) sign_in guest end diff --git a/spec/features/merge_requests/edit_mr_spec.rb b/spec/features/merge_requests/edit_mr_spec.rb index 4362f8b3fcc..79be2fbf945 100644 --- a/spec/features/merge_requests/edit_mr_spec.rb +++ b/spec/features/merge_requests/edit_mr_spec.rb @@ -6,7 +6,7 @@ feature 'Edit Merge Request' do let(:merge_request) { create(:merge_request, :simple, source_project: project) } before do - project.team << [user, :master] + project.add_master(user) sign_in user diff --git a/spec/features/merge_requests/filter_by_milestone_spec.rb b/spec/features/merge_requests/filter_by_milestone_spec.rb index 8b9ff9be943..8db94352f73 100644 --- a/spec/features/merge_requests/filter_by_milestone_spec.rb +++ b/spec/features/merge_requests/filter_by_milestone_spec.rb @@ -14,7 +14,7 @@ feature 'Merge Request filtering by Milestone' do end before do - project.team << [user, :master] + project.add_master(user) sign_in(user) end diff --git a/spec/features/merge_requests/form_spec.rb b/spec/features/merge_requests/form_spec.rb index 1dcc1e139a0..1ebf762a006 100644 --- a/spec/features/merge_requests/form_spec.rb +++ b/spec/features/merge_requests/form_spec.rb @@ -12,8 +12,8 @@ describe 'New/edit merge request', :js do let!(:label2) { create(:label, project: project) } before do - project.team << [user, :master] - project.team << [user2, :master] + project.add_master(user) + project.add_master(user2) end context 'owned projects' do @@ -172,7 +172,7 @@ describe 'New/edit merge request', :js do context 'forked project' do before do - forked_project.team << [user, :master] + forked_project.add_master(user) sign_in(user) end diff --git a/spec/features/merge_requests/image_diff_notes_spec.rb b/spec/features/merge_requests/image_diff_notes_spec.rb index b53570835cb..d0f8da4e6cd 100644 --- a/spec/features/merge_requests/image_diff_notes_spec.rb +++ b/spec/features/merge_requests/image_diff_notes_spec.rb @@ -7,7 +7,7 @@ feature 'image diff notes', :js do let(:project) { create(:project, :public, :repository) } before do - project.team << [user, :master] + project.add_master(user) sign_in user # Stub helper to return any blob file as image from public app folder. diff --git a/spec/features/merge_requests/merge_commit_message_toggle_spec.rb b/spec/features/merge_requests/merge_commit_message_toggle_spec.rb index 82b2b56ef80..ddd034e1376 100644 --- a/spec/features/merge_requests/merge_commit_message_toggle_spec.rb +++ b/spec/features/merge_requests/merge_commit_message_toggle_spec.rb @@ -32,7 +32,7 @@ feature 'Clicking toggle commit message link', :js do end before do - project.team << [user, :master] + project.add_master(user) sign_in user diff --git a/spec/features/merge_requests/merge_immediately_with_pipeline_spec.rb b/spec/features/merge_requests/merge_immediately_with_pipeline_spec.rb index 0b5a595acce..e1317b33ad1 100644 --- a/spec/features/merge_requests/merge_immediately_with_pipeline_spec.rb +++ b/spec/features/merge_requests/merge_immediately_with_pipeline_spec.rb @@ -19,7 +19,7 @@ feature 'Merge immediately', :js do end before do - project.team << [user, :master] + project.add_master(user) end context 'when there is active pipeline for merge request' do diff --git a/spec/features/merge_requests/only_allow_merge_if_build_succeeds_spec.rb b/spec/features/merge_requests/only_allow_merge_if_build_succeeds_spec.rb index 91f207bd339..7d9282b932b 100644 --- a/spec/features/merge_requests/only_allow_merge_if_build_succeeds_spec.rb +++ b/spec/features/merge_requests/only_allow_merge_if_build_succeeds_spec.rb @@ -7,7 +7,7 @@ feature 'Only allow merge requests to be merged if the pipeline succeeds', :js d before do sign_in merge_request.author - project.team << [merge_request.author, :master] + project.add_master(merge_request.author) end context 'project does not have CI enabled', :js do diff --git a/spec/features/merge_requests/pipelines_spec.rb b/spec/features/merge_requests/pipelines_spec.rb index 307c860eac4..04e3f4bdcf1 100644 --- a/spec/features/merge_requests/pipelines_spec.rb +++ b/spec/features/merge_requests/pipelines_spec.rb @@ -7,7 +7,7 @@ feature 'Pipelines for Merge Requests', :js do given(:project) { merge_request.target_project } before do - project.team << [user, :master] + project.add_master(user) sign_in user end diff --git a/spec/features/merge_requests/reset_filters_spec.rb b/spec/features/merge_requests/reset_filters_spec.rb index eed95816bdf..daca4422bf1 100644 --- a/spec/features/merge_requests/reset_filters_spec.rb +++ b/spec/features/merge_requests/reset_filters_spec.rb @@ -17,7 +17,7 @@ feature 'Merge requests filter clear button', :js do before do mr2.labels << bug - project.team << [user, :developer] + project.add_developer(user) end context 'when a milestone filter has been applied' do diff --git a/spec/features/merge_requests/target_branch_spec.rb b/spec/features/merge_requests/target_branch_spec.rb index bce36e05e57..d9f7a056dea 100644 --- a/spec/features/merge_requests/target_branch_spec.rb +++ b/spec/features/merge_requests/target_branch_spec.rb @@ -11,7 +11,7 @@ describe 'Target branch', :js do before do sign_in user - project.team << [user, :master] + project.add_master(user) end context 'when branch was deleted' do diff --git a/spec/features/merge_requests/update_merge_requests_spec.rb b/spec/features/merge_requests/update_merge_requests_spec.rb index c5498563b39..a96404b86ed 100644 --- a/spec/features/merge_requests/update_merge_requests_spec.rb +++ b/spec/features/merge_requests/update_merge_requests_spec.rb @@ -6,7 +6,7 @@ feature 'Multiple merge requests updating from merge_requests#index' do let!(:merge_request) { create(:merge_request, source_project: project, target_project: project) } before do - project.team << [user, :master] + project.add_master(user) sign_in(user) end diff --git a/spec/features/merge_requests/user_uses_slash_commands_spec.rb b/spec/features/merge_requests/user_uses_slash_commands_spec.rb index ee0766f1192..5874bf5e187 100644 --- a/spec/features/merge_requests/user_uses_slash_commands_spec.rb +++ b/spec/features/merge_requests/user_uses_slash_commands_spec.rb @@ -15,7 +15,7 @@ feature 'Merge Requests > User uses quick actions', :js do let!(:milestone) { create(:milestone, project: project, title: 'ASAP') } before do - project.team << [user, :master] + project.add_master(user) sign_in(user) visit project_merge_request_path(project, merge_request) end @@ -58,7 +58,7 @@ feature 'Merge Requests > User uses quick actions', :js do context 'when the current user cannot toggle the WIP prefix' do let(:guest) { create(:user) } before do - project.team << [guest, :guest] + project.add_guest(guest) sign_out(:user) sign_in(guest) visit project_merge_request_path(project, merge_request) @@ -104,7 +104,7 @@ feature 'Merge Requests > User uses quick actions', :js do context 'when the current user cannot merge the MR' do let(:guest) { create(:user) } before do - project.team << [guest, :guest] + project.add_guest(guest) sign_out(:user) sign_in(guest) visit project_merge_request_path(project, merge_request) @@ -134,7 +134,7 @@ feature 'Merge Requests > User uses quick actions', :js do before do sign_out(:user) - another_project.team << [user, :master] + another_project.add_master(user) sign_in(user) end @@ -188,7 +188,7 @@ feature 'Merge Requests > User uses quick actions', :js do context 'when current user can not change target branch' do let(:guest) { create(:user) } before do - project.team << [guest, :guest] + project.add_guest(guest) sign_out(:user) sign_in(guest) visit project_merge_request_path(project, merge_request) diff --git a/spec/features/merge_requests/widget_deployments_spec.rb b/spec/features/merge_requests/widget_deployments_spec.rb index 72a52c979b3..ec2da72ddff 100644 --- a/spec/features/merge_requests/widget_deployments_spec.rb +++ b/spec/features/merge_requests/widget_deployments_spec.rb @@ -13,7 +13,7 @@ feature 'Widget Deployments Header', :js do background do sign_in(user) - project.team << [user, role] + project.add_role(user, role) visit project_merge_request_path(project, merge_request) end diff --git a/spec/features/merge_requests/widget_spec.rb b/spec/features/merge_requests/widget_spec.rb index 3ee094c216e..8970586a160 100644 --- a/spec/features/merge_requests/widget_spec.rb +++ b/spec/features/merge_requests/widget_spec.rb @@ -273,7 +273,7 @@ describe 'Merge request', :js do let(:user2) { create(:user) } before do - project.team << [user2, :master] + project.add_master(user2) sign_out(:user) sign_in(user2) merge_request.update(target_project: fork_project) diff --git a/spec/features/merge_requests/wip_message_spec.rb b/spec/features/merge_requests/wip_message_spec.rb index b422c76249d..2617e735c25 100644 --- a/spec/features/merge_requests/wip_message_spec.rb +++ b/spec/features/merge_requests/wip_message_spec.rb @@ -5,7 +5,7 @@ feature 'Work In Progress help message' do let!(:user) { create(:user) } before do - project.team << [user, :master] + project.add_master(user) sign_in(user) end diff --git a/spec/features/milestone_spec.rb b/spec/features/milestone_spec.rb index 9f24193a2ac..b02d2d4261c 100644 --- a/spec/features/milestone_spec.rb +++ b/spec/features/milestone_spec.rb @@ -7,7 +7,7 @@ feature 'Milestone' do before do create(:group_member, group: group, user: user) - project.team << [user, :master] + project.add_master(user) sign_in(user) end diff --git a/spec/features/password_reset_spec.rb b/spec/features/password_reset_spec.rb index b45972b7f6b..73a526c3d8a 100644 --- a/spec/features/password_reset_spec.rb +++ b/spec/features/password_reset_spec.rb @@ -33,6 +33,25 @@ feature 'Password reset' do end end + describe 'Changing password while logged in' do + it 'updates the password' do + user = create(:user) + token = user.send_reset_password_instructions + + sign_in(user) + + visit(edit_user_password_path(reset_password_token: token)) + + fill_in 'New password', with: 'hello1234' + fill_in 'Confirm new password', with: 'hello1234' + + click_button 'Change your password' + + expect(page).to have_content(I18n.t('devise.passwords.updated_not_active')) + expect(current_path).to eq new_user_session_path + end + end + def forgot_password(user) visit root_path click_on 'Forgot your password?' diff --git a/spec/features/profiles/keys_spec.rb b/spec/features/profiles/keys_spec.rb index 7d5ba3a7328..b04a5422fed 100644 --- a/spec/features/profiles/keys_spec.rb +++ b/spec/features/profiles/keys_spec.rb @@ -27,6 +27,7 @@ feature 'Profile > SSH Keys' do expect(page).to have_content("Title: #{attrs[:title]}") expect(page).to have_content(attrs[:key]) + expect(find('.breadcrumbs-sub-title')).to have_link(attrs[:title]) end context 'when only DSA and ECDSA keys are allowed' do diff --git a/spec/features/profiles/oauth_applications_spec.rb b/spec/features/profiles/oauth_applications_spec.rb index d1edeef8da4..7d204f89fba 100644 --- a/spec/features/profiles/oauth_applications_spec.rb +++ b/spec/features/profiles/oauth_applications_spec.rb @@ -2,12 +2,20 @@ require 'spec_helper' describe 'Profile > Applications' do let(:user) { create(:user) } + let(:application) { create(:oauth_application, owner: user) } before do sign_in(user) end describe 'User manages applications', :js do + it 'views an application' do + visit oauth_application_path(application) + + expect(page).to have_content("Application: #{application.name}") + expect(find('.breadcrumbs-sub-title')).to have_link(application.name) + end + it 'deletes an application' do create(:oauth_application, owner: user) visit oauth_applications_path diff --git a/spec/features/profiles/user_visits_notifications_tab_spec.rb b/spec/features/profiles/user_visits_notifications_tab_spec.rb index df89918f17a..1952fdae798 100644 --- a/spec/features/profiles/user_visits_notifications_tab_spec.rb +++ b/spec/features/profiles/user_visits_notifications_tab_spec.rb @@ -5,7 +5,7 @@ feature 'User visits the notifications tab', :js do let(:user) { create(:user) } before do - project.team << [user, :master] + project.add_master(user) sign_in(user) visit(profile_notifications_path) end diff --git a/spec/features/profiles/user_visits_profile_preferences_page_spec.rb b/spec/features/profiles/user_visits_profile_preferences_page_spec.rb index 90d6841af0e..266af8f4e3d 100644 --- a/spec/features/profiles/user_visits_profile_preferences_page_spec.rb +++ b/spec/features/profiles/user_visits_profile_preferences_page_spec.rb @@ -32,6 +32,18 @@ describe 'User visits the profile preferences page' do end end + describe 'User changes their multi file editor preferences', :js do + it 'set the new_repo cookie when the option is ON' do + choose 'user_multi_file_on' + expect(get_cookie('new_repo')).not_to be_nil + end + + it 'deletes the new_repo cookie when the option is OFF' do + choose 'user_multi_file_off' + expect(get_cookie('new_repo')).to be_nil + end + end + describe 'User changes their default dashboard', :js do it 'creates a flash message' do select 'Starred Projects', from: 'user_dashboard' diff --git a/spec/features/projects/activity/rss_spec.rb b/spec/features/projects/activity/rss_spec.rb index 84c2faa2015..2693e539268 100644 --- a/spec/features/projects/activity/rss_spec.rb +++ b/spec/features/projects/activity/rss_spec.rb @@ -11,7 +11,7 @@ feature 'Project Activity RSS' do context 'when signed in' do before do - project.team << [user, :developer] + project.add_developer(user) sign_in(user) visit path end diff --git a/spec/features/projects/badges/coverage_spec.rb b/spec/features/projects/badges/coverage_spec.rb index c68e10a2563..821ce88a402 100644 --- a/spec/features/projects/badges/coverage_spec.rb +++ b/spec/features/projects/badges/coverage_spec.rb @@ -6,7 +6,7 @@ feature 'test coverage badge' do context 'when user has access to view badge' do background do - project.team << [user, :developer] + project.add_developer(user) sign_in(user) end diff --git a/spec/features/projects/badges/list_spec.rb b/spec/features/projects/badges/list_spec.rb index 68c4a647958..c705e479690 100644 --- a/spec/features/projects/badges/list_spec.rb +++ b/spec/features/projects/badges/list_spec.rb @@ -4,7 +4,7 @@ feature 'list of badges' do background do user = create(:user) project = create(:project, :repository) - project.team << [user, :master] + project.add_master(user) sign_in(user) visit project_pipelines_settings_path(project) end diff --git a/spec/features/projects/blobs/edit_spec.rb b/spec/features/projects/blobs/edit_spec.rb index 965028a6f90..69e4c9f04a1 100644 --- a/spec/features/projects/blobs/edit_spec.rb +++ b/spec/features/projects/blobs/edit_spec.rb @@ -13,7 +13,7 @@ feature 'Editing file blob', :js do let(:role) { :developer } before do - project.team << [user, role] + project.add_role(user, role) sign_in(user) end @@ -55,7 +55,7 @@ feature 'Editing file blob', :js do let(:user) { create(:user) } before do - project.team << [user, :developer] + project.add_developer(user) visit project_edit_blob_path(project, tree_join(branch, file_path)) end @@ -90,7 +90,7 @@ feature 'Editing file blob', :js do let(:protected_branch) { 'protected-branch' } before do - project.team << [user, :developer] + project.add_developer(user) project.repository.add_branch(user, protected_branch, 'master') create(:protected_branch, project: project, name: protected_branch) sign_in(user) @@ -122,7 +122,7 @@ feature 'Editing file blob', :js do let(:user) { create(:user) } before do - project.team << [user, :master] + project.add_master(user) sign_in(user) visit project_edit_blob_path(project, tree_join(branch, file_path)) end diff --git a/spec/features/projects/branches/download_buttons_spec.rb b/spec/features/projects/branches/download_buttons_spec.rb index 2f407b13c2f..39bcea013e7 100644 --- a/spec/features/projects/branches/download_buttons_spec.rb +++ b/spec/features/projects/branches/download_buttons_spec.rb @@ -23,7 +23,7 @@ feature 'Download buttons in branches page' do background do sign_in(user) - project.team << [user, role] + project.add_role(user, role) end describe 'when checking branches' do diff --git a/spec/features/projects/branches_spec.rb b/spec/features/projects/branches_spec.rb index 7a77df83034..2fddd274078 100644 --- a/spec/features/projects/branches_spec.rb +++ b/spec/features/projects/branches_spec.rb @@ -8,7 +8,7 @@ describe 'Branches' do context 'logged in as developer' do before do sign_in(user) - project.team << [user, :developer] + project.add_developer(user) end describe 'Initial branches page' do @@ -78,7 +78,7 @@ describe 'Branches' do context 'logged in as master' do before do sign_in(user) - project.team << [user, :master] + project.add_master(user) end describe 'Initial branches page' do diff --git a/spec/features/projects/clusters/interchangeability_spec.rb b/spec/features/projects/clusters/interchangeability_spec.rb index 01f9526608f..3ddb35c755c 100644 --- a/spec/features/projects/clusters/interchangeability_spec.rb +++ b/spec/features/projects/clusters/interchangeability_spec.rb @@ -1,7 +1,7 @@ require 'spec_helper' feature 'Interchangeability between KubernetesService and Platform::Kubernetes' do - EXCEPT_METHODS = %i[test title description help fields initialize_properties namespace namespace= api_url api_url=].freeze + EXCEPT_METHODS = %i[test title description help fields initialize_properties namespace namespace= api_url api_url= deprecated? deprecation_message].freeze EXCEPT_METHODS_GREP_V = %w[_touched? _changed? _was].freeze it 'Clusters::Platform::Kubernetes covers core interfaces in KubernetesService' do diff --git a/spec/features/projects/commit/builds_spec.rb b/spec/features/projects/commit/builds_spec.rb index 79e84a4f0a6..36a746ac83d 100644 --- a/spec/features/projects/commit/builds_spec.rb +++ b/spec/features/projects/commit/builds_spec.rb @@ -5,7 +5,7 @@ feature 'project commit pipelines', :js do background do user = create(:user) - project.team << [user, :master] + project.add_master(user) sign_in(user) end diff --git a/spec/features/projects/commit/cherry_pick_spec.rb b/spec/features/projects/commit/cherry_pick_spec.rb index c11a95732b2..c4c399e3058 100644 --- a/spec/features/projects/commit/cherry_pick_spec.rb +++ b/spec/features/projects/commit/cherry_pick_spec.rb @@ -9,7 +9,7 @@ describe 'Cherry-pick Commits' do before do sign_in(user) - project.team << [user, :master] + project.add_master(user) visit project_commit_path(project, master_pickable_commit.id) end diff --git a/spec/features/projects/commits/rss_spec.rb b/spec/features/projects/commits/rss_spec.rb index db958346f06..0d9c7355ddd 100644 --- a/spec/features/projects/commits/rss_spec.rb +++ b/spec/features/projects/commits/rss_spec.rb @@ -7,7 +7,7 @@ feature 'Project Commits RSS' do context 'when signed in' do before do - project.team << [user, :developer] + project.add_developer(user) sign_in(user) visit path end diff --git a/spec/features/projects/compare_spec.rb b/spec/features/projects/compare_spec.rb index 87ffc2a0b90..1fb22fd0e4c 100644 --- a/spec/features/projects/compare_spec.rb +++ b/spec/features/projects/compare_spec.rb @@ -5,7 +5,7 @@ describe "Compare", :js do let(:project) { create(:project, :repository) } before do - project.team << [user, :master] + project.add_master(user) sign_in user visit project_compare_index_path(project, from: "master", to: "master") end diff --git a/spec/features/projects/deploy_keys_spec.rb b/spec/features/projects/deploy_keys_spec.rb index e445758cb5e..886c56e7163 100644 --- a/spec/features/projects/deploy_keys_spec.rb +++ b/spec/features/projects/deploy_keys_spec.rb @@ -5,7 +5,7 @@ describe 'Project deploy keys', :js do let(:project) { create(:project_empty_repo) } before do - project.team << [user, :master] + project.add_master(user) sign_in(user) end diff --git a/spec/features/projects/developer_views_empty_project_instructions_spec.rb b/spec/features/projects/developer_views_empty_project_instructions_spec.rb index 36809240f76..bf55917bf4c 100644 --- a/spec/features/projects/developer_views_empty_project_instructions_spec.rb +++ b/spec/features/projects/developer_views_empty_project_instructions_spec.rb @@ -5,7 +5,7 @@ feature 'Developer views empty project instructions' do let(:developer) { create(:user) } background do - project.team << [developer, :developer] + project.add_developer(developer) sign_in(developer) end diff --git a/spec/features/projects/edit_spec.rb b/spec/features/projects/edit_spec.rb index 7a372757523..1d4b4d0fdca 100644 --- a/spec/features/projects/edit_spec.rb +++ b/spec/features/projects/edit_spec.rb @@ -7,7 +7,7 @@ feature 'Project edit', :js do context 'feature visibility' do before do - project.team << [user, :master] + project.add_master(user) sign_in(user) visit edit_project_path(project) diff --git a/spec/features/projects/environments/environment_spec.rb b/spec/features/projects/environments/environment_spec.rb index dfcf97ad495..64e600144e0 100644 --- a/spec/features/projects/environments/environment_spec.rb +++ b/spec/features/projects/environments/environment_spec.rb @@ -7,7 +7,7 @@ feature 'Environment' do background do sign_in(user) - project.team << [user, role] + project.add_role(user, role) end feature 'environment details page' do diff --git a/spec/features/projects/environments/environments_spec.rb b/spec/features/projects/environments/environments_spec.rb index 4a05313c14a..5248a783db4 100644 --- a/spec/features/projects/environments/environments_spec.rb +++ b/spec/features/projects/environments/environments_spec.rb @@ -6,7 +6,7 @@ feature 'Environments page', :js do given(:role) { :developer } background do - project.team << [user, role] + project.add_role(user, role) sign_in(user) end diff --git a/spec/features/projects/features_visibility_spec.rb b/spec/features/projects/features_visibility_spec.rb index 033c45a60bf..b0eb7c5b42a 100644 --- a/spec/features/projects/features_visibility_spec.rb +++ b/spec/features/projects/features_visibility_spec.rb @@ -8,7 +8,7 @@ describe 'Edit Project Settings' do describe 'project features visibility selectors', :js do before do - project.team << [member, :master] + project.add_master(member) sign_in(member) end @@ -165,7 +165,7 @@ describe 'Edit Project Settings' do describe 'repository visibility', :js do before do - project.team << [member, :master] + project.add_master(member) sign_in(member) visit edit_project_path(project) end @@ -261,7 +261,7 @@ describe 'Edit Project Settings' do let!(:project) { create(:project, :private) } before do - project.team << [member, :guest] + project.add_guest(member) sign_in(member) visit project_path(project) end diff --git a/spec/features/projects/files/browse_files_spec.rb b/spec/features/projects/files/browse_files_spec.rb index 84197e45dcb..2c38c380d9d 100644 --- a/spec/features/projects/files/browse_files_spec.rb +++ b/spec/features/projects/files/browse_files_spec.rb @@ -5,7 +5,7 @@ feature 'user browses project', :js do let(:user) { create(:user) } before do - project.team << [user, :master] + project.add_master(user) sign_in(user) visit project_tree_path(project, project.default_branch) end diff --git a/spec/features/projects/files/creating_a_file_spec.rb b/spec/features/projects/files/creating_a_file_spec.rb index e1852a6e544..8d982636525 100644 --- a/spec/features/projects/files/creating_a_file_spec.rb +++ b/spec/features/projects/files/creating_a_file_spec.rb @@ -5,7 +5,7 @@ feature 'User wants to create a file' do let(:user) { create(:user) } background do - project.team << [user, :master] + project.add_master(user) sign_in user visit project_new_blob_path(project, project.default_branch) end diff --git a/spec/features/projects/files/dockerfile_dropdown_spec.rb b/spec/features/projects/files/dockerfile_dropdown_spec.rb index 3c3a5326538..f4a39e331fd 100644 --- a/spec/features/projects/files/dockerfile_dropdown_spec.rb +++ b/spec/features/projects/files/dockerfile_dropdown_spec.rb @@ -5,7 +5,7 @@ feature 'User wants to add a Dockerfile file' do before do user = create(:user) project = create(:project, :repository) - project.team << [user, :master] + project.add_master(user) sign_in user diff --git a/spec/features/projects/files/download_buttons_spec.rb b/spec/features/projects/files/download_buttons_spec.rb index d2382d55c0b..2101627f324 100644 --- a/spec/features/projects/files/download_buttons_spec.rb +++ b/spec/features/projects/files/download_buttons_spec.rb @@ -23,7 +23,7 @@ feature 'Download buttons in files tree' do background do sign_in(user) - project.team << [user, role] + project.add_role(user, role) end describe 'when files tree' do diff --git a/spec/features/projects/files/edit_file_soft_wrap_spec.rb b/spec/features/projects/files/edit_file_soft_wrap_spec.rb index 3ab43b3c656..8d32ada5795 100644 --- a/spec/features/projects/files/edit_file_soft_wrap_spec.rb +++ b/spec/features/projects/files/edit_file_soft_wrap_spec.rb @@ -4,7 +4,7 @@ feature 'User uses soft wrap whilst editing file', :js do before do user = create(:user) project = create(:project, :repository) - project.team << [user, :master] + project.add_master(user) sign_in user visit project_new_blob_path(project, 'master', file_name: 'test_file-name') page.within('.file-editor.code') do diff --git a/spec/features/projects/files/editing_a_file_spec.rb b/spec/features/projects/files/editing_a_file_spec.rb index 20be968e89f..d874cdbff8d 100644 --- a/spec/features/projects/files/editing_a_file_spec.rb +++ b/spec/features/projects/files/editing_a_file_spec.rb @@ -16,7 +16,7 @@ feature 'User wants to edit a file' do end background do - project.team << [user, :master] + project.add_master(user) sign_in user visit project_edit_blob_path(project, File.join(project.default_branch, '.gitignore')) diff --git a/spec/features/projects/files/files_sort_submodules_with_folders_spec.rb b/spec/features/projects/files/files_sort_submodules_with_folders_spec.rb index 702b99de733..ead9f7e9168 100644 --- a/spec/features/projects/files/files_sort_submodules_with_folders_spec.rb +++ b/spec/features/projects/files/files_sort_submodules_with_folders_spec.rb @@ -5,7 +5,7 @@ feature 'User views files page' do let(:project) { create(:forked_project_with_submodules) } before do - project.team << [user, :master] + project.add_master(user) sign_in user visit project_tree_path(project, project.repository.root_ref) end diff --git a/spec/features/projects/files/find_file_keyboard_spec.rb b/spec/features/projects/files/find_file_keyboard_spec.rb index 618725ee781..e9ff06c72d8 100644 --- a/spec/features/projects/files/find_file_keyboard_spec.rb +++ b/spec/features/projects/files/find_file_keyboard_spec.rb @@ -5,7 +5,7 @@ feature 'Find file keyboard shortcuts', :js do let(:project) { create(:project, :repository) } before do - project.team << [user, :master] + project.add_master(user) sign_in user visit project_find_file_path(project, project.repository.root_ref) diff --git a/spec/features/projects/files/gitignore_dropdown_spec.rb b/spec/features/projects/files/gitignore_dropdown_spec.rb index 81d68c3d67c..79f3fd09b48 100644 --- a/spec/features/projects/files/gitignore_dropdown_spec.rb +++ b/spec/features/projects/files/gitignore_dropdown_spec.rb @@ -4,7 +4,7 @@ feature 'User wants to add a .gitignore file' do before do user = create(:user) project = create(:project, :repository) - project.team << [user, :master] + project.add_master(user) sign_in user visit project_new_blob_path(project, 'master', file_name: '.gitignore') end diff --git a/spec/features/projects/files/gitlab_ci_yml_dropdown_spec.rb b/spec/features/projects/files/gitlab_ci_yml_dropdown_spec.rb index 8e58fa7bd56..db6c67b802e 100644 --- a/spec/features/projects/files/gitlab_ci_yml_dropdown_spec.rb +++ b/spec/features/projects/files/gitlab_ci_yml_dropdown_spec.rb @@ -4,7 +4,7 @@ feature 'User wants to add a .gitlab-ci.yml file' do before do user = create(:user) project = create(:project, :repository) - project.team << [user, :master] + project.add_master(user) sign_in user visit project_new_blob_path(project, 'master', file_name: '.gitlab-ci.yml') end diff --git a/spec/features/projects/files/project_owner_creates_license_file_spec.rb b/spec/features/projects/files/project_owner_creates_license_file_spec.rb index 6c5b1086ec1..07599600876 100644 --- a/spec/features/projects/files/project_owner_creates_license_file_spec.rb +++ b/spec/features/projects/files/project_owner_creates_license_file_spec.rb @@ -6,7 +6,7 @@ feature 'project owner creates a license file', :js do background do project.repository.delete_file(project_master, 'LICENSE', message: 'Remove LICENSE', branch_name: 'master') - project.team << [project_master, :master] + project.add_master(project_master) sign_in(project_master) visit project_path(project) end diff --git a/spec/features/projects/files/template_type_dropdown_spec.rb b/spec/features/projects/files/template_type_dropdown_spec.rb index f95a60e5194..97408a9c41e 100644 --- a/spec/features/projects/files/template_type_dropdown_spec.rb +++ b/spec/features/projects/files/template_type_dropdown_spec.rb @@ -5,7 +5,7 @@ feature 'Template type dropdown selector', :js do let(:user) { create(:user) } before do - project.team << [user, :master] + project.add_master(user) sign_in user end diff --git a/spec/features/projects/files/undo_template_spec.rb b/spec/features/projects/files/undo_template_spec.rb index 64fe350f3dc..fbf35fb4e1c 100644 --- a/spec/features/projects/files/undo_template_spec.rb +++ b/spec/features/projects/files/undo_template_spec.rb @@ -5,7 +5,7 @@ feature 'Template Undo Button', :js do let(:user) { create(:user) } before do - project.team << [user, :master] + project.add_master(user) sign_in user end diff --git a/spec/features/projects/guest_navigation_menu_spec.rb b/spec/features/projects/guest_navigation_menu_spec.rb index 98c7ef57a51..199682b943c 100644 --- a/spec/features/projects/guest_navigation_menu_spec.rb +++ b/spec/features/projects/guest_navigation_menu_spec.rb @@ -5,7 +5,7 @@ describe 'Guest navigation menu' do let(:guest) { create(:user) } before do - project.team << [guest, :guest] + project.add_guest(guest) sign_in(guest) end diff --git a/spec/features/projects/issuable_templates_spec.rb b/spec/features/projects/issuable_templates_spec.rb index 4319fc2746c..e26caf1f456 100644 --- a/spec/features/projects/issuable_templates_spec.rb +++ b/spec/features/projects/issuable_templates_spec.rb @@ -8,7 +8,7 @@ feature 'issuable templates', :js do let(:issue_form_location) { '#content-body .issuable-details .detail-page-description' } before do - project.team << [user, :master] + project.add_master(user) sign_in user end @@ -120,7 +120,7 @@ feature 'issuable templates', :js do background do sign_out(:user) - project.team << [fork_user, :developer] + project.add_developer(fork_user) sign_in(fork_user) diff --git a/spec/features/projects/issues/rss_spec.rb b/spec/features/projects/issues/rss_spec.rb index 58eeef8c258..ff91aabc311 100644 --- a/spec/features/projects/issues/rss_spec.rb +++ b/spec/features/projects/issues/rss_spec.rb @@ -12,7 +12,7 @@ feature 'Project Issues RSS' do let(:user) { create(:user) } before do - project.team << [user, :developer] + project.add_developer(user) sign_in(user) visit path end diff --git a/spec/features/projects/jobs_spec.rb b/spec/features/projects/jobs_spec.rb index 0b0d5a2dce8..f8ea1a52656 100644 --- a/spec/features/projects/jobs_spec.rb +++ b/spec/features/projects/jobs_spec.rb @@ -15,7 +15,7 @@ feature 'Jobs' do end before do - project.team << [user, user_access_level] + project.add_role(user, user_access_level) sign_in(user) end diff --git a/spec/features/projects/labels/subscription_spec.rb b/spec/features/projects/labels/subscription_spec.rb index e8c70dec854..70e8d436dcb 100644 --- a/spec/features/projects/labels/subscription_spec.rb +++ b/spec/features/projects/labels/subscription_spec.rb @@ -9,7 +9,7 @@ feature 'Labels subscription' do context 'when signed in' do before do - project.team << [user, :developer] + project.add_developer(user) sign_in user end diff --git a/spec/features/projects/labels/update_prioritization_spec.rb b/spec/features/projects/labels/update_prioritization_spec.rb index d063f5c27b5..85bd776932b 100644 --- a/spec/features/projects/labels/update_prioritization_spec.rb +++ b/spec/features/projects/labels/update_prioritization_spec.rb @@ -12,7 +12,7 @@ feature 'Prioritize labels' do context 'when user belongs to project team' do before do - project.team << [user, :developer] + project.add_developer(user) sign_in user end diff --git a/spec/features/projects/main/download_buttons_spec.rb b/spec/features/projects/main/download_buttons_spec.rb index 3f2579bb01a..81f08e44cf3 100644 --- a/spec/features/projects/main/download_buttons_spec.rb +++ b/spec/features/projects/main/download_buttons_spec.rb @@ -23,7 +23,7 @@ feature 'Download buttons in project main page' do background do sign_in(user) - project.team << [user, role] + project.add_role(user, role) end describe 'when checking project main page' do diff --git a/spec/features/projects/main/rss_spec.rb b/spec/features/projects/main/rss_spec.rb index 7914180b951..3c98c11b490 100644 --- a/spec/features/projects/main/rss_spec.rb +++ b/spec/features/projects/main/rss_spec.rb @@ -7,7 +7,7 @@ feature 'Project RSS' do context 'when signed in' do before do - project.team << [user, :developer] + project.add_developer(user) sign_in(user) visit path end diff --git a/spec/features/projects/members/anonymous_user_sees_members_spec.rb b/spec/features/projects/members/anonymous_user_sees_members_spec.rb index bf0990d675d..e2a48bfd1d4 100644 --- a/spec/features/projects/members/anonymous_user_sees_members_spec.rb +++ b/spec/features/projects/members/anonymous_user_sees_members_spec.rb @@ -6,7 +6,7 @@ feature 'Projects > Members > Anonymous user sees members' do let(:project) { create(:project, :public) } background do - project.team << [user, :master] + project.add_master(user) create(:project_group_link, project: project, group: group) end diff --git a/spec/features/projects/members/group_members_spec.rb b/spec/features/projects/members/group_members_spec.rb index c140fece41d..e22b6fa6c43 100644 --- a/spec/features/projects/members/group_members_spec.rb +++ b/spec/features/projects/members/group_members_spec.rb @@ -11,7 +11,7 @@ feature 'Projects members' do let(:group_requester) { create(:user) } background do - project.team << [developer, :developer] + project.add_developer(developer) group.add_owner(user) sign_in(user) end diff --git a/spec/features/projects/members/groups_with_access_list_spec.rb b/spec/features/projects/members/groups_with_access_list_spec.rb index 7f067aadec6..e6d0c6e00f8 100644 --- a/spec/features/projects/members/groups_with_access_list_spec.rb +++ b/spec/features/projects/members/groups_with_access_list_spec.rb @@ -6,7 +6,7 @@ feature 'Projects > Members > Groups with access list', :js do let(:project) { create(:project, :public) } background do - project.team << [user, :master] + project.add_master(user) @group_link = create(:project_group_link, project: project, group: group) sign_in(user) diff --git a/spec/features/projects/members/master_adds_member_with_expiration_date_spec.rb b/spec/features/projects/members/master_adds_member_with_expiration_date_spec.rb index 0f88f4cb1e8..8fe340d3bae 100644 --- a/spec/features/projects/members/master_adds_member_with_expiration_date_spec.rb +++ b/spec/features/projects/members/master_adds_member_with_expiration_date_spec.rb @@ -9,7 +9,7 @@ feature 'Projects > Members > Master adds member with expiration date', :js do let!(:new_member) { create(:user) } background do - project.team << [master, :master] + project.add_master(master) sign_in(master) end diff --git a/spec/features/projects/members/master_manages_access_requests_spec.rb b/spec/features/projects/members/master_manages_access_requests_spec.rb index eb3c8034873..d575596937d 100644 --- a/spec/features/projects/members/master_manages_access_requests_spec.rb +++ b/spec/features/projects/members/master_manages_access_requests_spec.rb @@ -7,7 +7,7 @@ feature 'Projects > Members > Master manages access requests' do background do project.request_access(user) - project.team << [master, :master] + project.add_master(master) sign_in(master) end diff --git a/spec/features/projects/members/member_cannot_request_access_to_his_project_spec.rb b/spec/features/projects/members/member_cannot_request_access_to_his_project_spec.rb index 04806f8fd9e..47911c32a72 100644 --- a/spec/features/projects/members/member_cannot_request_access_to_his_project_spec.rb +++ b/spec/features/projects/members/member_cannot_request_access_to_his_project_spec.rb @@ -5,7 +5,7 @@ feature 'Projects > Members > Member cannot request access to his project' do let(:project) { create(:project) } background do - project.team << [member, :developer] + project.add_developer(member) sign_in(member) visit project_path(project) end diff --git a/spec/features/projects/members/member_leaves_project_spec.rb b/spec/features/projects/members/member_leaves_project_spec.rb index 1bcf827d33c..e54c2c76975 100644 --- a/spec/features/projects/members/member_leaves_project_spec.rb +++ b/spec/features/projects/members/member_leaves_project_spec.rb @@ -5,7 +5,7 @@ feature 'Projects > Members > Member leaves project' do let(:project) { create(:project, :repository) } background do - project.team << [user, :developer] + project.add_developer(user) sign_in(user) visit project_path(project) end diff --git a/spec/features/projects/merge_requests/list_spec.rb b/spec/features/projects/merge_requests/list_spec.rb index a879efef4b5..b34b13db381 100644 --- a/spec/features/projects/merge_requests/list_spec.rb +++ b/spec/features/projects/merge_requests/list_spec.rb @@ -5,7 +5,7 @@ feature 'Merge Requests List' do let(:project) { create(:project, :repository) } background do - project.team << [user, :developer] + project.add_developer(user) sign_in(user) end diff --git a/spec/features/projects/pages_spec.rb b/spec/features/projects/pages_spec.rb index 013ed6f2e58..2e334caa98f 100644 --- a/spec/features/projects/pages_spec.rb +++ b/spec/features/projects/pages_spec.rb @@ -8,7 +8,7 @@ feature 'Pages' do background do allow(Gitlab.config.pages).to receive(:enabled).and_return(true) - project.team << [user, role] + project.add_role(user, role) sign_in(user) end diff --git a/spec/features/projects/pipelines/pipeline_spec.rb b/spec/features/projects/pipelines/pipeline_spec.rb index 3987cea0b4f..266ef693d0b 100644 --- a/spec/features/projects/pipelines/pipeline_spec.rb +++ b/spec/features/projects/pipelines/pipeline_spec.rb @@ -6,7 +6,7 @@ describe 'Pipeline', :js do before do sign_in(user) - project.team << [user, :developer] + project.add_developer(user) end shared_context 'pipeline builds' do diff --git a/spec/features/projects/pipelines/pipelines_spec.rb b/spec/features/projects/pipelines/pipelines_spec.rb index 69a836292fc..e8fc56ab11e 100644 --- a/spec/features/projects/pipelines/pipelines_spec.rb +++ b/spec/features/projects/pipelines/pipelines_spec.rb @@ -8,7 +8,7 @@ describe 'Pipelines', :js do before do sign_in(user) - project.team << [user, :developer] + project.add_developer(user) end describe 'GET /:project/pipelines' do diff --git a/spec/features/projects/services/user_activates_jira_spec.rb b/spec/features/projects/services/user_activates_jira_spec.rb index ac78b1dfb1c..028669eeaf2 100644 --- a/spec/features/projects/services/user_activates_jira_spec.rb +++ b/spec/features/projects/services/user_activates_jira_spec.rb @@ -18,7 +18,7 @@ describe 'User activates Jira', :js do end before do - project.team << [user, :master] + project.add_master(user) sign_in(user) visit project_settings_integrations_path(project) diff --git a/spec/features/projects/services/user_activates_mattermost_slash_command_spec.rb b/spec/features/projects/services/user_activates_mattermost_slash_command_spec.rb index 6f057137867..b2906e315f7 100644 --- a/spec/features/projects/services/user_activates_mattermost_slash_command_spec.rb +++ b/spec/features/projects/services/user_activates_mattermost_slash_command_spec.rb @@ -8,7 +8,7 @@ feature 'Setup Mattermost slash commands', :js do before do stub_mattermost_setting(enabled: mattermost_enabled) - project.team << [user, :master] + project.add_master(user) sign_in(user) visit edit_project_service_path(project, service) end diff --git a/spec/features/projects/services/user_activates_slack_slash_command_spec.rb b/spec/features/projects/services/user_activates_slack_slash_command_spec.rb index a8baf126269..4a88654210c 100644 --- a/spec/features/projects/services/user_activates_slack_slash_command_spec.rb +++ b/spec/features/projects/services/user_activates_slack_slash_command_spec.rb @@ -6,7 +6,7 @@ feature 'Slack slash commands' do given(:service) { project.create_slack_slash_commands_service } background do - project.team << [user, :master] + project.add_master(user) sign_in(user) visit edit_project_service_path(project, service) end diff --git a/spec/features/projects/settings/integration_settings_spec.rb b/spec/features/projects/settings/integration_settings_spec.rb index cbdb7973ac8..f6a1a46df11 100644 --- a/spec/features/projects/settings/integration_settings_spec.rb +++ b/spec/features/projects/settings/integration_settings_spec.rb @@ -8,7 +8,7 @@ feature 'Integration settings' do background do sign_in(user) - project.team << [user, role] + project.add_role(user, role) end context 'for developer' do diff --git a/spec/features/projects/settings/merge_requests_settings_spec.rb b/spec/features/projects/settings/merge_requests_settings_spec.rb index ac76c30cc7c..015db603d33 100644 --- a/spec/features/projects/settings/merge_requests_settings_spec.rb +++ b/spec/features/projects/settings/merge_requests_settings_spec.rb @@ -5,7 +5,7 @@ feature 'Project settings > Merge Requests', :js do let(:user) { create(:user) } background do - project.team << [user, :master] + project.add_master(user) sign_in(user) end diff --git a/spec/features/projects/settings/pipelines_settings_spec.rb b/spec/features/projects/settings/pipelines_settings_spec.rb index 561f08cba00..d0720855564 100644 --- a/spec/features/projects/settings/pipelines_settings_spec.rb +++ b/spec/features/projects/settings/pipelines_settings_spec.rb @@ -7,7 +7,7 @@ feature "Pipelines settings" do background do sign_in(user) - project.team << [user, role] + project.add_role(user, role) end context 'for developer' do diff --git a/spec/features/projects/settings/repository_settings_spec.rb b/spec/features/projects/settings/repository_settings_spec.rb index e2a5619c22b..81b282502fc 100644 --- a/spec/features/projects/settings/repository_settings_spec.rb +++ b/spec/features/projects/settings/repository_settings_spec.rb @@ -6,7 +6,7 @@ feature 'Repository settings' do let(:role) { :developer } background do - project.team << [user, role] + project.add_role(user, role) sign_in(user) end @@ -66,7 +66,7 @@ feature 'Repository settings' do scenario 'edit a deploy key from projects user has access to' do project2 = create(:project_empty_repo) - project2.team << [user, role] + project2.add_role(user, role) project2.deploy_keys << private_deploy_key visit project_settings_repository_path(project) diff --git a/spec/features/projects/settings/visibility_settings_spec.rb b/spec/features/projects/settings/visibility_settings_spec.rb index 1c3b84d0114..06f6702670b 100644 --- a/spec/features/projects/settings/visibility_settings_spec.rb +++ b/spec/features/projects/settings/visibility_settings_spec.rb @@ -31,7 +31,7 @@ feature 'Visibility settings', :js do let(:master_user) { create(:user) } before do - project.team << [master_user, :master] + project.add_master(master_user) sign_in(master_user) visit edit_project_path(project) end diff --git a/spec/features/projects/snippets/create_snippet_spec.rb b/spec/features/projects/snippets/create_snippet_spec.rb index e4215291f99..3466a3dfb77 100644 --- a/spec/features/projects/snippets/create_snippet_spec.rb +++ b/spec/features/projects/snippets/create_snippet_spec.rb @@ -16,7 +16,7 @@ feature 'Create Snippet', :js do context 'when a user is authenticated' do before do - project.team << [user, :master] + project.add_master(user) sign_in(user) visit project_snippets_path(project) diff --git a/spec/features/projects/snippets/show_spec.rb b/spec/features/projects/snippets/show_spec.rb index 08dc7cf6c5b..216f2af7c88 100644 --- a/spec/features/projects/snippets/show_spec.rb +++ b/spec/features/projects/snippets/show_spec.rb @@ -6,7 +6,7 @@ feature 'Project snippet', :js do let(:snippet) { create(:project_snippet, project: project, file_name: file_name, content: content) } before do - project.team << [user, :master] + project.add_master(user) sign_in(user) end diff --git a/spec/features/projects/tags/download_buttons_spec.rb b/spec/features/projects/tags/download_buttons_spec.rb index d38a5b1324b..b62498194c4 100644 --- a/spec/features/projects/tags/download_buttons_spec.rb +++ b/spec/features/projects/tags/download_buttons_spec.rb @@ -24,7 +24,7 @@ feature 'Download buttons in tags page' do background do sign_in(user) - project.team << [user, role] + project.add_role(user, role) end describe 'when checking tags' do diff --git a/spec/features/projects/tree/rss_spec.rb b/spec/features/projects/tree/rss_spec.rb index 4f2e0a76a65..6407370ac0d 100644 --- a/spec/features/projects/tree/rss_spec.rb +++ b/spec/features/projects/tree/rss_spec.rb @@ -7,7 +7,7 @@ feature 'Project Tree RSS' do context 'when signed in' do before do - project.team << [user, :developer] + project.add_developer(user) sign_in(user) visit path end diff --git a/spec/features/projects/user_browses_files_spec.rb b/spec/features/projects/user_browses_files_spec.rb index f5e4d7f5130..62e6419cc42 100644 --- a/spec/features/projects/user_browses_files_spec.rb +++ b/spec/features/projects/user_browses_files_spec.rb @@ -15,7 +15,7 @@ describe 'User browses files' do let(:user) { create(:user) } before do - project.team << [user, :master] + project.add_master(user) sign_in(user) end diff --git a/spec/features/projects/user_creates_directory_spec.rb b/spec/features/projects/user_creates_directory_spec.rb index 052cb3188c5..00e48f6fabd 100644 --- a/spec/features/projects/user_creates_directory_spec.rb +++ b/spec/features/projects/user_creates_directory_spec.rb @@ -11,7 +11,7 @@ feature 'User creates a directory', :js do let(:user) { create(:user) } before do - project.team << [user, :developer] + project.add_developer(user) sign_in(user) visit project_tree_path(project, 'master') end @@ -63,7 +63,7 @@ feature 'User creates a directory', :js do context 'when an user does not have write access' do before do - project2.team << [user, :reporter] + project2.add_reporter(user) visit(project2_tree_path_root_ref) end diff --git a/spec/features/projects/user_creates_files_spec.rb b/spec/features/projects/user_creates_files_spec.rb index d84b91ddc32..7a935dd2477 100644 --- a/spec/features/projects/user_creates_files_spec.rb +++ b/spec/features/projects/user_creates_files_spec.rb @@ -12,7 +12,7 @@ describe 'User creates files' do let(:user) { create(:user) } before do - project.team << [user, :master] + project.add_master(user) sign_in(user) end @@ -33,7 +33,7 @@ describe 'User creates files' do context 'when an user does not have write access' do before do - project2.team << [user, :reporter] + project2.add_reporter(user) visit(project2_tree_path_root_ref) end @@ -131,7 +131,7 @@ describe 'User creates files' do context 'when an user does not have write access' do before do - project2.team << [user, :reporter] + project2.add_reporter(user) visit(project2_tree_path_root_ref) end diff --git a/spec/features/projects/user_deletes_files_spec.rb b/spec/features/projects/user_deletes_files_spec.rb index 9e4e92ec076..9d55197e719 100644 --- a/spec/features/projects/user_deletes_files_spec.rb +++ b/spec/features/projects/user_deletes_files_spec.rb @@ -17,7 +17,7 @@ describe 'User deletes files' do context 'when an user has write access' do before do - project.team << [user, :master] + project.add_master(user) visit(project_tree_path_root_ref) end @@ -37,7 +37,7 @@ describe 'User deletes files' do context 'when an user does not have write access' do before do - project2.team << [user, :reporter] + project2.add_reporter(user) visit(project2_tree_path_root_ref) end diff --git a/spec/features/projects/user_edits_files_spec.rb b/spec/features/projects/user_edits_files_spec.rb index d26ee653415..05c2be473da 100644 --- a/spec/features/projects/user_edits_files_spec.rb +++ b/spec/features/projects/user_edits_files_spec.rb @@ -14,7 +14,7 @@ describe 'User edits files' do context 'when an user has write access' do before do - project.team << [user, :master] + project.add_master(user) visit(project_tree_path_root_ref) end @@ -33,7 +33,9 @@ describe 'User edits files' do binary_file = File.join(project.repository.root_ref, 'files/images/logo-black.png') visit(project_blob_path(project, binary_file)) - expect(page).not_to have_link('edit') + page.within '.content' do + expect(page).not_to have_link('edit') + end end it 'commits an edited file', :js do @@ -87,7 +89,7 @@ describe 'User edits files' do context 'when an user does not have write access' do before do - project2.team << [user, :reporter] + project2.add_reporter(user) visit(project2_tree_path_root_ref) end diff --git a/spec/features/projects/user_replaces_files_spec.rb b/spec/features/projects/user_replaces_files_spec.rb index 245b6aa285b..74872403b35 100644 --- a/spec/features/projects/user_replaces_files_spec.rb +++ b/spec/features/projects/user_replaces_files_spec.rb @@ -19,7 +19,7 @@ describe 'User replaces files' do context 'when an user has write access' do before do - project.team << [user, :master] + project.add_master(user) visit(project_tree_path_root_ref) end @@ -45,7 +45,7 @@ describe 'User replaces files' do context 'when an user does not have write access' do before do - project2.team << [user, :reporter] + project2.add_reporter(user) visit(project2_tree_path_root_ref) end diff --git a/spec/features/projects/user_uploads_files_spec.rb b/spec/features/projects/user_uploads_files_spec.rb index ae51901adc6..75898afcda9 100644 --- a/spec/features/projects/user_uploads_files_spec.rb +++ b/spec/features/projects/user_uploads_files_spec.rb @@ -14,7 +14,7 @@ describe 'User uploads files' do let(:user) { create(:user) } before do - project.team << [user, :master] + project.add_master(user) sign_in(user) end @@ -50,7 +50,7 @@ describe 'User uploads files' do context 'when an user does not have write access' do before do - project2.team << [user, :reporter] + project2.add_reporter(user) visit(project2_tree_path_root_ref) end diff --git a/spec/features/projects/wiki/markdown_preview_spec.rb b/spec/features/projects/wiki/markdown_preview_spec.rb index 337baaf4dcd..006c15d60c5 100644 --- a/spec/features/projects/wiki/markdown_preview_spec.rb +++ b/spec/features/projects/wiki/markdown_preview_spec.rb @@ -13,7 +13,7 @@ feature 'Projects > Wiki > User previews markdown changes', :js do end background do - project.team << [user, :master] + project.add_master(user) sign_in(user) diff --git a/spec/features/projects/wiki/user_views_wiki_in_project_page_spec.rb b/spec/features/projects/wiki/user_views_wiki_in_project_page_spec.rb index ebb3bd044c1..2682b62fa04 100644 --- a/spec/features/projects/wiki/user_views_wiki_in_project_page_spec.rb +++ b/spec/features/projects/wiki/user_views_wiki_in_project_page_spec.rb @@ -4,7 +4,7 @@ describe 'Projects > Wiki > User views wiki in project page' do let(:user) { create(:user) } before do - project.team << [user, :master] + project.add_master(user) sign_in(user) end diff --git a/spec/features/projects_spec.rb b/spec/features/projects_spec.rb index 63e6051b571..b66a7dea598 100644 --- a/spec/features/projects_spec.rb +++ b/spec/features/projects_spec.rb @@ -133,7 +133,7 @@ feature 'Project' do before do sign_in(user) - project.team << [user, :master] + project.add_master(user) visit edit_project_path(project) end @@ -151,7 +151,7 @@ feature 'Project' do let(:project) { create(:forked_project_with_submodules) } before do - project.team << [user, :master] + project.add_master(user) sign_in user visit project_path(project) end @@ -180,7 +180,7 @@ feature 'Project' do let(:project) { create(:project, :repository) } before do - project.team << [user, :master] + project.add_master(user) sign_in user visit project_path(project) end diff --git a/spec/features/runners_spec.rb b/spec/features/runners_spec.rb index c7f0e342809..aec9de6c7ca 100644 --- a/spec/features/runners_spec.rb +++ b/spec/features/runners_spec.rb @@ -33,6 +33,26 @@ feature 'Runners' do expect(page).to have_content(specific_runner.platform) end + scenario 'user can pause and resume the specific runner' do + visit runners_path(project) + + within '.activated-specific-runners' do + expect(page).to have_content('Pause') + end + + click_on 'Pause' + + within '.activated-specific-runners' do + expect(page).to have_content('Resume') + end + + click_on 'Resume' + + within '.activated-specific-runners' do + expect(page).to have_content('Pause') + end + end + scenario 'user removes an activated specific runner if this is last project for that runners' do visit runners_path(project) diff --git a/spec/features/signed_commits_spec.rb b/spec/features/signed_commits_spec.rb index 8efa5b58141..6088b831c14 100644 --- a/spec/features/signed_commits_spec.rb +++ b/spec/features/signed_commits_spec.rb @@ -5,7 +5,7 @@ describe 'GPG signed commits', :js do it 'changes from unverified to verified when the user changes his email to match the gpg key' do user = create :user, email: 'unrelated.user@example.org' - project.team << [user, :master] + project.add_master(user) Sidekiq::Testing.inline! do create :gpg_key, key: GpgHelpers::User1.public_key, user: user @@ -36,7 +36,7 @@ describe 'GPG signed commits', :js do it 'changes from unverified to verified when the user adds the missing gpg key' do user = create :user, email: GpgHelpers::User1.emails.first - project.team << [user, :master] + project.add_master(user) sign_in(user) @@ -86,7 +86,7 @@ describe 'GPG signed commits', :js do before do user = create :user - project.team << [user, :master] + project.add_master(user) sign_in(user) end diff --git a/spec/features/tags/master_creates_tag_spec.rb b/spec/features/tags/master_creates_tag_spec.rb index 1f8bd8d681e..8a8f6933fa5 100644 --- a/spec/features/tags/master_creates_tag_spec.rb +++ b/spec/features/tags/master_creates_tag_spec.rb @@ -5,7 +5,7 @@ feature 'Master creates tag' do let(:project) { create(:project, :repository, namespace: user.namespace) } before do - project.team << [user, :master] + project.add_master(user) sign_in(user) end diff --git a/spec/features/tags/master_deletes_tag_spec.rb b/spec/features/tags/master_deletes_tag_spec.rb index dfda664d673..c0b4fa52526 100644 --- a/spec/features/tags/master_deletes_tag_spec.rb +++ b/spec/features/tags/master_deletes_tag_spec.rb @@ -5,7 +5,7 @@ feature 'Master deletes tag' do let(:project) { create(:project, :repository, namespace: user.namespace) } before do - project.team << [user, :master] + project.add_master(user) sign_in(user) visit project_tags_path(project) end diff --git a/spec/features/tags/master_updates_tag_spec.rb b/spec/features/tags/master_updates_tag_spec.rb index b93ad44dfd3..1c370a99b13 100644 --- a/spec/features/tags/master_updates_tag_spec.rb +++ b/spec/features/tags/master_updates_tag_spec.rb @@ -5,7 +5,7 @@ feature 'Master updates tag' do let(:project) { create(:project, :repository, namespace: user.namespace) } before do - project.team << [user, :master] + project.add_master(user) sign_in(user) visit project_tags_path(project) end diff --git a/spec/features/triggers_spec.rb b/spec/features/triggers_spec.rb index bc472e74997..19784120108 100644 --- a/spec/features/triggers_spec.rb +++ b/spec/features/triggers_spec.rb @@ -10,9 +10,9 @@ feature 'Triggers', :js do sign_in(user) @project = create(:project) - @project.team << [user, :master] - @project.team << [user2, :master] - @project.team << [guest_user, :guest] + @project.add_master(user) + @project.add_master(user2) + @project.add_guest(guest_user) visit project_settings_ci_cd_path(@project) end diff --git a/spec/features/u2f_spec.rb b/spec/features/u2f_spec.rb index c9afef2a8de..50ee1656e10 100644 --- a/spec/features/u2f_spec.rb +++ b/spec/features/u2f_spec.rb @@ -264,7 +264,7 @@ feature 'Using U2F (Universal 2nd Factor) Devices for Authentication', :js do end it "deletes u2f registrations" do - visit profile_account_path + visit profile_two_factor_auth_path expect do accept_confirm { click_on "Disable" } end.to change { U2fRegistration.count }.by(-1) diff --git a/spec/features/variables_spec.rb b/spec/features/variables_spec.rb index dde60c83536..79ca2b4bb4a 100644 --- a/spec/features/variables_spec.rb +++ b/spec/features/variables_spec.rb @@ -7,7 +7,7 @@ describe 'Project variables', :js do before do sign_in(user) - project.team << [user, :master] + project.add_master(user) project.variables << variable visit project_settings_ci_cd_path(project) diff --git a/spec/finders/access_requests_finder_spec.rb b/spec/finders/access_requests_finder_spec.rb index 0789d3a9b44..650f7229647 100644 --- a/spec/finders/access_requests_finder_spec.rb +++ b/spec/finders/access_requests_finder_spec.rb @@ -51,7 +51,7 @@ describe AccessRequestsFinder do context 'when current user can see access requests' do before do - project.team << [user, :master] + project.add_master(user) group.add_owner(user) end @@ -78,7 +78,7 @@ describe AccessRequestsFinder do context 'when current user can see access requests' do before do - project.team << [user, :master] + project.add_master(user) group.add_owner(user) end diff --git a/spec/finders/group_projects_finder_spec.rb b/spec/finders/group_projects_finder_spec.rb index c6d257bc479..27a09d7c6f5 100644 --- a/spec/finders/group_projects_finder_spec.rb +++ b/spec/finders/group_projects_finder_spec.rb @@ -45,7 +45,7 @@ describe GroupProjectsFinder do describe 'without group member current_user' do before do - shared_project_2.team << [current_user, Gitlab::Access::MASTER] + shared_project_2.add_master(current_user) current_user.reload end @@ -70,7 +70,7 @@ describe GroupProjectsFinder do context "without external user" do before do - private_project.team << [current_user, Gitlab::Access::MASTER] + private_project.add_master(current_user) end it { is_expected.to match_array([private_project, public_project]) } diff --git a/spec/finders/issues_finder_spec.rb b/spec/finders/issues_finder_spec.rb index 47b173dea0a..47fd98234f9 100644 --- a/spec/finders/issues_finder_spec.rb +++ b/spec/finders/issues_finder_spec.rb @@ -22,9 +22,9 @@ describe IssuesFinder do let(:issues) { described_class.new(search_user, params.reverse_merge(scope: scope, state: 'opened')).execute } before(:context) do - project1.team << [user, :master] - project2.team << [user, :developer] - project2.team << [user2, :developer] + project1.add_master(user) + project2.add_developer(user) + project2.add_developer(user2) issue1 issue2 diff --git a/spec/finders/labels_finder_spec.rb b/spec/finders/labels_finder_spec.rb index afa2a40ed2a..d507af3fd3d 100644 --- a/spec/finders/labels_finder_spec.rb +++ b/spec/finders/labels_finder_spec.rb @@ -27,7 +27,7 @@ describe LabelsFinder do create(:label, project: project_3, title: 'Label 3') create(:group_label, group: group_3, title: 'Group Label 4') - project_1.team << [user, :developer] + project_1.add_developer(user) end context 'with no filter' do @@ -73,7 +73,7 @@ describe LabelsFinder do # project_3 has a label associated to it, which we don't want coming # back when we ask for the isolated project's labels - project_3.team << [admin, :reporter] + project_3.add_reporter(admin) finder = described_class.new(admin, project_id: isolated_project.id) expect(finder.execute).to be_empty diff --git a/spec/finders/merge_requests_finder_spec.rb b/spec/finders/merge_requests_finder_spec.rb index 883bdf3746a..687ffaec7cc 100644 --- a/spec/finders/merge_requests_finder_spec.rb +++ b/spec/finders/merge_requests_finder_spec.rb @@ -20,10 +20,10 @@ describe MergeRequestsFinder do let!(:merge_request4) { create(:merge_request, :simple, author: user, source_project: project3, target_project: project3) } before do - project1.team << [user, :master] - project2.team << [user, :developer] - project3.team << [user, :developer] - project2.team << [user2, :developer] + project1.add_master(user) + project2.add_developer(user) + project3.add_developer(user) + project2.add_developer(user2) end describe "#execute" do diff --git a/spec/finders/move_to_project_finder_spec.rb b/spec/finders/move_to_project_finder_spec.rb index e577083a2d0..74639d4147f 100644 --- a/spec/finders/move_to_project_finder_spec.rb +++ b/spec/finders/move_to_project_finder_spec.rb @@ -15,39 +15,39 @@ describe MoveToProjectFinder do describe '#execute' do context 'filter' do it 'does not return projects under Gitlab::Access::REPORTER' do - guest_project.team << [user, :guest] + guest_project.add_guest(user) expect(subject.execute(project)).to be_empty end it 'returns projects equal or above Gitlab::Access::REPORTER ordered by id in descending order' do - reporter_project.team << [user, :reporter] - developer_project.team << [user, :developer] - master_project.team << [user, :master] + reporter_project.add_reporter(user) + developer_project.add_developer(user) + master_project.add_master(user) expect(subject.execute(project).to_a).to eq([master_project, developer_project, reporter_project]) end it 'does not include the source project' do - project.team << [user, :reporter] + project.add_reporter(user) expect(subject.execute(project).to_a).to be_empty end it 'does not return archived projects' do - reporter_project.team << [user, :reporter] + reporter_project.add_reporter(user) reporter_project.archive! other_reporter_project = create(:project) - other_reporter_project.team << [user, :reporter] + other_reporter_project.add_reporter(user) expect(subject.execute(project).to_a).to eq([other_reporter_project]) end it 'does not return projects for which issues are disabled' do - reporter_project.team << [user, :reporter] + reporter_project.add_reporter(user) reporter_project.update_attributes(issues_enabled: false) other_reporter_project = create(:project) - other_reporter_project.team << [user, :reporter] + other_reporter_project.add_reporter(user) expect(subject.execute(project).to_a).to eq([other_reporter_project]) end @@ -55,9 +55,9 @@ describe MoveToProjectFinder do it 'returns a page of projects ordered by id in descending order' do stub_const 'MoveToProjectFinder::PAGE_SIZE', 2 - reporter_project.team << [user, :reporter] - developer_project.team << [user, :developer] - master_project.team << [user, :master] + reporter_project.add_reporter(user) + developer_project.add_developer(user) + master_project.add_master(user) expect(subject.execute(project).to_a).to eq([master_project, developer_project]) end @@ -65,9 +65,9 @@ describe MoveToProjectFinder do it 'returns projects after the given offset id' do stub_const 'MoveToProjectFinder::PAGE_SIZE', 2 - reporter_project.team << [user, :reporter] - developer_project.team << [user, :developer] - master_project.team << [user, :master] + reporter_project.add_reporter(user) + developer_project.add_developer(user) + master_project.add_master(user) expect(subject.execute(project, search: nil, offset_id: master_project.id).to_a).to eq([developer_project, reporter_project]) expect(subject.execute(project, search: nil, offset_id: developer_project.id).to_a).to eq([reporter_project]) @@ -84,10 +84,10 @@ describe MoveToProjectFinder do it 'returns projects matching a search query' do foo_project = create(:project) - foo_project.team << [user, :master] + foo_project.add_master(user) wadus_project = create(:project, name: 'wadus') - wadus_project.team << [user, :master] + wadus_project.add_master(user) expect(subject.execute(project).to_a).to eq([wadus_project, foo_project]) expect(subject.execute(project, search: 'wadus').to_a).to eq([wadus_project]) diff --git a/spec/finders/notes_finder_spec.rb b/spec/finders/notes_finder_spec.rb index 900fa2b12d1..7b43494eea2 100644 --- a/spec/finders/notes_finder_spec.rb +++ b/spec/finders/notes_finder_spec.rb @@ -5,7 +5,7 @@ describe NotesFinder do let(:project) { create(:project) } before do - project.team << [user, :master] + project.add_master(user) end describe '#execute' do @@ -147,7 +147,7 @@ describe NotesFinder do it 'raises an error for project members with guest role' do user = create(:user) - project.team << [user, :guest] + project.add_guest(user) expect { described_class.new(project, user, params).execute }.to raise_error(ActiveRecord::RecordNotFound) end @@ -189,7 +189,7 @@ describe NotesFinder do it "does not return notes with matching content for project members with guest role" do user = create(:user) - project.team << [user, :guest] + project.add_guest(user) expect(described_class.new(confidential_note.project, user, search: confidential_note.note).execute).to be_empty end diff --git a/spec/finders/personal_projects_finder_spec.rb b/spec/finders/personal_projects_finder_spec.rb index d0113ba87df..5e52898e9c0 100644 --- a/spec/finders/personal_projects_finder_spec.rb +++ b/spec/finders/personal_projects_finder_spec.rb @@ -15,7 +15,7 @@ describe PersonalProjectsFinder do end before do - private_project.team << [current_user, Gitlab::Access::DEVELOPER] + private_project.add_developer(current_user) end describe 'without a current user' do diff --git a/spec/finders/snippets_finder_spec.rb b/spec/finders/snippets_finder_spec.rb index 7ae7b7d2140..0a018d2b417 100644 --- a/spec/finders/snippets_finder_spec.rb +++ b/spec/finders/snippets_finder_spec.rb @@ -188,7 +188,7 @@ describe SnippetsFinder do end it "returns all snippets for project members" do - project1.team << [user, :developer] + project1.add_developer(user) snippets = described_class.new(user, project: project1).execute @@ -196,7 +196,7 @@ describe SnippetsFinder do end it "returns private snippets for project members" do - project1.team << [user, :developer] + project1.add_developer(user) snippets = described_class.new(user, project: project1, visibility: Snippet::PRIVATE).execute diff --git a/spec/finders/todos_finder_spec.rb b/spec/finders/todos_finder_spec.rb index 884ce22091e..90eb0fe21e4 100644 --- a/spec/finders/todos_finder_spec.rb +++ b/spec/finders/todos_finder_spec.rb @@ -7,7 +7,7 @@ describe TodosFinder do let(:finder) { described_class } before do - project.team << [user, :developer] + project.add_developer(user) end describe '#sort' do diff --git a/spec/fixtures/api/schemas/entities/merge_request_metrics.json b/spec/fixtures/api/schemas/entities/merge_request_metrics.json new file mode 100644 index 00000000000..3fa767f85df --- /dev/null +++ b/spec/fixtures/api/schemas/entities/merge_request_metrics.json @@ -0,0 +1,21 @@ +{ + "type": "object", + "required": ["closed_at", "merged_at", "closed_by", "merged_by"], + "properties" : { + "closed_at": { "type": ["datetime", "null"] }, + "merged_at": { "type": ["datetime", "null"] }, + "closed_by": { + "oneOf": [ + { "type": "null" }, + { "$ref": "user.json" } + ] + }, + "merged_by": { + "oneOf": [ + { "type": "null" }, + { "$ref": "user.json" } + ] + } + }, + "additionalProperties": false +} diff --git a/spec/fixtures/api/schemas/entities/merge_request_widget.json b/spec/fixtures/api/schemas/entities/merge_request_widget.json index 342890c3dee..9de27bee751 100644 --- a/spec/fixtures/api/schemas/entities/merge_request_widget.json +++ b/spec/fixtures/api/schemas/entities/merge_request_widget.json @@ -31,8 +31,12 @@ "source_project_id": { "type": "integer" }, "target_branch": { "type": "string" }, "target_project_id": { "type": "integer" }, - "merge_event": { "type": ["object", "null"] }, - "closed_event": { "type": ["object", "null"] }, + "metrics": { + "oneOf": [ + { "type": "null" }, + { "$ref": "merge_request_metrics.json" } + ] + }, "author": { "type": ["object", "null"] }, "merge_user": { "type": ["object", "null"] }, "diff_head_sha": { "type": ["string", "null"] }, diff --git a/spec/fixtures/api/schemas/entities/user.json b/spec/fixtures/api/schemas/entities/user.json new file mode 100644 index 00000000000..6482e0eedd2 --- /dev/null +++ b/spec/fixtures/api/schemas/entities/user.json @@ -0,0 +1,17 @@ +{ + "type": "object", + "required": [ + "id", + "state", + "avatar_url", + "web_url", + "path" + ], + "properties": { + "id": { "type": "integer" }, + "state": { "type": "string" }, + "avatar_url": { "type": "string" }, + "web_url": { "type": "string" }, + "path": { "type": "string" } + } +} diff --git a/spec/helpers/markup_helper_spec.rb b/spec/helpers/markup_helper_spec.rb index ba0039f3a11..c0dc9293397 100644 --- a/spec/helpers/markup_helper_spec.rb +++ b/spec/helpers/markup_helper_spec.rb @@ -11,7 +11,7 @@ describe MarkupHelper do before do # Ensure the generated reference links aren't redacted - project.team << [user, :master] + project.add_master(user) # Helper expects a @project instance variable helper.instance_variable_set(:@project, project) diff --git a/spec/helpers/notes_helper_spec.rb b/spec/helpers/notes_helper_spec.rb index 36a44f8567a..b992bdb4a5e 100644 --- a/spec/helpers/notes_helper_spec.rb +++ b/spec/helpers/notes_helper_spec.rb @@ -17,9 +17,9 @@ describe NotesHelper do before do group.add_owner(owner) - project.team << [master, :master] - project.team << [reporter, :reporter] - project.team << [guest, :guest] + project.add_master(master) + project.add_reporter(reporter) + project.add_guest(guest) end describe "#notes_max_access_for_users" do @@ -31,7 +31,7 @@ describe NotesHelper do it 'handles access in different projects' do second_project = create(:project) - second_project.team << [master, :reporter] + second_project.add_reporter(master) other_note = create(:note, author: master, project: second_project) expect(helper.note_max_access_for_user(master_note)).to eq(Gitlab::Access::MASTER) diff --git a/spec/javascripts/blob/notebook/index_spec.js b/spec/javascripts/blob/notebook/index_spec.js index c3e67550f05..df1b2c9960b 100644 --- a/spec/javascripts/blob/notebook/index_spec.js +++ b/spec/javascripts/blob/notebook/index_spec.js @@ -1,4 +1,5 @@ -import Vue from 'vue'; +import MockAdapter from 'axios-mock-adapter'; +import axios from '~/lib/utils/axios_utils'; import renderNotebook from '~/blob/notebook'; describe('iPython notebook renderer', () => { @@ -17,8 +18,11 @@ describe('iPython notebook renderer', () => { }); describe('successful response', () => { - const response = (request, next) => { - next(request.respondWith(JSON.stringify({ + let mock; + + beforeEach((done) => { + mock = new MockAdapter(axios); + mock.onGet('/test').reply(200, { cells: [{ cell_type: 'markdown', source: ['# test'], @@ -31,13 +35,7 @@ describe('iPython notebook renderer', () => { ], outputs: [], }], - }), { - status: 200, - })); - }; - - beforeEach((done) => { - Vue.http.interceptors.push(response); + }); renderNotebook(); @@ -47,9 +45,7 @@ describe('iPython notebook renderer', () => { }); afterEach(() => { - Vue.http.interceptors = _.without( - Vue.http.interceptors, response, - ); + mock.reset(); }); it('does not show loading icon', () => { @@ -86,14 +82,11 @@ describe('iPython notebook renderer', () => { }); describe('error in JSON response', () => { - const response = (request, next) => { - next(request.respondWith('{ "cells": [{"cell_type": "markdown"} }', { - status: 200, - })); - }; + let mock; beforeEach((done) => { - Vue.http.interceptors.push(response); + mock = new MockAdapter(axios); + mock.onGet('/test').reply(() => Promise.reject({ status: 200, data: '{ "cells": [{"cell_type": "markdown"} }' })); renderNotebook(); @@ -103,9 +96,7 @@ describe('iPython notebook renderer', () => { }); afterEach(() => { - Vue.http.interceptors = _.without( - Vue.http.interceptors, response, - ); + mock.reset(); }); it('does not show loading icon', () => { @@ -122,14 +113,11 @@ describe('iPython notebook renderer', () => { }); describe('error getting file', () => { - const response = (request, next) => { - next(request.respondWith('', { - status: 500, - })); - }; + let mock; beforeEach((done) => { - Vue.http.interceptors.push(response); + mock = new MockAdapter(axios); + mock.onGet('/test').reply(500, ''); renderNotebook(); @@ -139,9 +127,7 @@ describe('iPython notebook renderer', () => { }); afterEach(() => { - Vue.http.interceptors = _.without( - Vue.http.interceptors, response, - ); + mock.reset(); }); it('does not show loading icon', () => { diff --git a/spec/javascripts/boards/board_blank_state_spec.js b/spec/javascripts/boards/board_blank_state_spec.js index 2ee3792dd65..f757dadfada 100644 --- a/spec/javascripts/boards/board_blank_state_spec.js +++ b/spec/javascripts/boards/board_blank_state_spec.js @@ -1,9 +1,8 @@ /* global BoardService */ -/* global mockBoardService */ import Vue from 'vue'; import '~/boards/stores/boards_store'; import boardBlankState from '~/boards/components/board_blank_state'; -import './mock_data'; +import { mockBoardService } from './mock_data'; describe('Boards blank state', () => { let vm; @@ -20,17 +19,15 @@ describe('Boards blank state', () => { reject(); } else { resolve({ - json() { - return [{ - id: 1, - title: 'To Do', - label: { id: 1 }, - }, { - id: 2, - title: 'Doing', - label: { id: 2 }, - }]; - }, + data: [{ + id: 1, + title: 'To Do', + label: { id: 1 }, + }, { + id: 2, + title: 'Doing', + label: { id: 2 }, + }], }); } })); diff --git a/spec/javascripts/boards/board_card_spec.js b/spec/javascripts/boards/board_card_spec.js index 8f607899b20..4e73fa1fe87 100644 --- a/spec/javascripts/boards/board_card_spec.js +++ b/spec/javascripts/boards/board_card_spec.js @@ -1,12 +1,11 @@ /* global List */ /* global ListAssignee */ /* global ListLabel */ -/* global listObj */ -/* global boardsMockInterceptor */ /* global BoardService */ -/* global mockBoardService */ import Vue from 'vue'; +import MockAdapter from 'axios-mock-adapter'; +import axios from '~/lib/utils/axios_utils'; import '~/boards/models/assignee'; import eventHub from '~/boards/eventhub'; @@ -14,13 +13,15 @@ import '~/boards/models/list'; import '~/boards/models/label'; import '~/boards/stores/boards_store'; import boardCard from '~/boards/components/board_card.vue'; -import './mock_data'; +import { listObj, boardsMockInterceptor, mockBoardService } from './mock_data'; describe('Board card', () => { let vm; + let mock; beforeEach((done) => { - Vue.http.interceptors.push(boardsMockInterceptor); + mock = new MockAdapter(axios); + mock.onAny().reply(boardsMockInterceptor); gl.boardService = mockBoardService(); gl.issueBoards.BoardsStore.create(); @@ -54,7 +55,7 @@ describe('Board card', () => { }); afterEach(() => { - Vue.http.interceptors = _.without(Vue.http.interceptors, boardsMockInterceptor); + mock.reset(); }); it('returns false when detailIssue is empty', () => { diff --git a/spec/javascripts/boards/board_list_spec.js b/spec/javascripts/boards/board_list_spec.js index 6bd00943a8f..7c5888b6d82 100644 --- a/spec/javascripts/boards/board_list_spec.js +++ b/spec/javascripts/boards/board_list_spec.js @@ -1,11 +1,9 @@ /* global BoardService */ -/* global boardsMockInterceptor */ /* global List */ -/* global listObj */ /* global ListIssue */ -/* global mockBoardService */ import Vue from 'vue'; -import _ from 'underscore'; +import MockAdapter from 'axios-mock-adapter'; +import axios from '~/lib/utils/axios_utils'; import Sortable from 'vendor/Sortable'; import BoardList from '~/boards/components/board_list'; import eventHub from '~/boards/eventhub'; @@ -13,18 +11,20 @@ import '~/boards/mixins/sortable_default_options'; import '~/boards/models/issue'; import '~/boards/models/list'; import '~/boards/stores/boards_store'; -import './mock_data'; +import { listObj, boardsMockInterceptor, mockBoardService } from './mock_data'; window.Sortable = Sortable; describe('Board list component', () => { + let mock; let component; beforeEach((done) => { const el = document.createElement('div'); document.body.appendChild(el); - Vue.http.interceptors.push(boardsMockInterceptor); + mock = new MockAdapter(axios); + mock.onAny().reply(boardsMockInterceptor); gl.boardService = mockBoardService(); gl.issueBoards.BoardsStore.create(); gl.IssueBoardsApp = new Vue(); @@ -60,7 +60,7 @@ describe('Board list component', () => { }); afterEach(() => { - Vue.http.interceptors = _.without(Vue.http.interceptors, boardsMockInterceptor); + mock.reset(); }); it('renders component', () => { diff --git a/spec/javascripts/boards/board_new_issue_spec.js b/spec/javascripts/boards/board_new_issue_spec.js index 02e6692dda8..c62c537841c 100644 --- a/spec/javascripts/boards/board_new_issue_spec.js +++ b/spec/javascripts/boards/board_new_issue_spec.js @@ -1,24 +1,22 @@ -/* global boardsMockInterceptor */ /* global BoardService */ /* global List */ -/* global listObj */ -/* global mockBoardService */ import Vue from 'vue'; +import MockAdapter from 'axios-mock-adapter'; +import axios from '~/lib/utils/axios_utils'; import boardNewIssue from '~/boards/components/board_new_issue'; import '~/boards/models/list'; -import './mock_data'; +import { listObj, boardsMockInterceptor, mockBoardService } from './mock_data'; describe('Issue boards new issue form', () => { let vm; let list; + let mock; let newIssueMock; const promiseReturn = { - json() { - return { - iid: 100, - }; + data: { + iid: 100, }, }; @@ -35,7 +33,9 @@ describe('Issue boards new issue form', () => { const BoardNewIssueComp = Vue.extend(boardNewIssue); - Vue.http.interceptors.push(boardsMockInterceptor); + mock = new MockAdapter(axios); + mock.onAny().reply(boardsMockInterceptor); + gl.boardService = mockBoardService(); gl.issueBoards.BoardsStore.create(); gl.IssueBoardsApp = new Vue(); @@ -56,7 +56,10 @@ describe('Issue boards new issue form', () => { .catch(done.fail); }); - afterEach(() => vm.$destroy()); + afterEach(() => { + vm.$destroy(); + mock.reset(); + }); it('calls submit if submit button is clicked', (done) => { spyOn(vm, 'submit').and.callFake(e => e.preventDefault()); diff --git a/spec/javascripts/boards/boards_store_spec.js b/spec/javascripts/boards/boards_store_spec.js index 0e656858182..49fb20f4c84 100644 --- a/spec/javascripts/boards/boards_store_spec.js +++ b/spec/javascripts/boards/boards_store_spec.js @@ -1,12 +1,10 @@ /* eslint-disable comma-dangle, one-var, no-unused-vars */ /* global BoardService */ -/* global boardsMockInterceptor */ -/* global listObj */ -/* global listObjDuplicate */ /* global ListIssue */ -/* global mockBoardService */ import Vue from 'vue'; +import MockAdapter from 'axios-mock-adapter'; +import axios from '~/lib/utils/axios_utils'; import Cookies from 'js-cookie'; import '~/boards/models/issue'; @@ -15,11 +13,14 @@ import '~/boards/models/list'; import '~/boards/models/assignee'; import '~/boards/services/board_service'; import '~/boards/stores/boards_store'; -import './mock_data'; +import { listObj, listObjDuplicate, boardsMockInterceptor, mockBoardService } from './mock_data'; describe('Store', () => { + let mock; + beforeEach(() => { - Vue.http.interceptors.push(boardsMockInterceptor); + mock = new MockAdapter(axios); + mock.onAny().reply(boardsMockInterceptor); gl.boardService = mockBoardService(); gl.issueBoards.BoardsStore.create(); @@ -34,7 +35,7 @@ describe('Store', () => { }); afterEach(() => { - Vue.http.interceptors = _.without(Vue.http.interceptors, boardsMockInterceptor); + mock.reset(); }); it('starts with a blank state', () => { diff --git a/spec/javascripts/boards/components/board_spec.js b/spec/javascripts/boards/components/board_spec.js index 8dacac20cad..19346e305cf 100644 --- a/spec/javascripts/boards/components/board_spec.js +++ b/spec/javascripts/boards/components/board_spec.js @@ -1,9 +1,8 @@ -/* global mockBoardService */ import Vue from 'vue'; import '~/boards/services/board_service'; import '~/boards/components/board'; import '~/boards/models/list'; -import '../mock_data'; +import { mockBoardService } from '../mock_data'; describe('Board component', () => { let vm; diff --git a/spec/javascripts/boards/issue_card_spec.js b/spec/javascripts/boards/issue_card_spec.js index 7d430ec35e2..8ef221257be 100644 --- a/spec/javascripts/boards/issue_card_spec.js +++ b/spec/javascripts/boards/issue_card_spec.js @@ -1,6 +1,5 @@ /* global ListAssignee */ /* global ListLabel */ -/* global listObj */ /* global ListIssue */ import Vue from 'vue'; @@ -11,7 +10,7 @@ import '~/boards/models/list'; import '~/boards/models/assignee'; import '~/boards/stores/boards_store'; import '~/boards/components/issue_card_inner'; -import './mock_data'; +import { listObj } from './mock_data'; describe('Issue card component', () => { const user = new ListAssignee({ diff --git a/spec/javascripts/boards/issue_spec.js b/spec/javascripts/boards/issue_spec.js index 41dcb19df3c..dbbe14fe3e0 100644 --- a/spec/javascripts/boards/issue_spec.js +++ b/spec/javascripts/boards/issue_spec.js @@ -1,7 +1,6 @@ /* eslint-disable comma-dangle */ /* global BoardService */ /* global ListIssue */ -/* global mockBoardService */ import Vue from 'vue'; import '~/boards/models/issue'; @@ -10,7 +9,7 @@ import '~/boards/models/list'; import '~/boards/models/assignee'; import '~/boards/services/board_service'; import '~/boards/stores/boards_store'; -import './mock_data'; +import { mockBoardService } from './mock_data'; describe('Issue model', () => { let issue; diff --git a/spec/javascripts/boards/list_spec.js b/spec/javascripts/boards/list_spec.js index eead396ca7e..645ce831b53 100644 --- a/spec/javascripts/boards/list_spec.js +++ b/spec/javascripts/boards/list_spec.js @@ -1,13 +1,10 @@ /* eslint-disable comma-dangle */ -/* global boardsMockInterceptor */ /* global BoardService */ -/* global mockBoardService */ /* global List */ /* global ListIssue */ -/* global listObj */ -/* global listObjDuplicate */ -import Vue from 'vue'; +import MockAdapter from 'axios-mock-adapter'; +import axios from '~/lib/utils/axios_utils'; import '~/boards/models/issue'; import '~/boards/models/label'; @@ -15,13 +12,15 @@ import '~/boards/models/list'; import '~/boards/models/assignee'; import '~/boards/services/board_service'; import '~/boards/stores/boards_store'; -import './mock_data'; +import { listObj, listObjDuplicate, boardsMockInterceptor, mockBoardService } from './mock_data'; describe('List model', () => { let list; + let mock; beforeEach(() => { - Vue.http.interceptors.push(boardsMockInterceptor); + mock = new MockAdapter(axios); + mock.onAny().reply(boardsMockInterceptor); gl.boardService = mockBoardService({ bulkUpdatePath: '/test/issue-boards/board/1/lists', }); @@ -31,7 +30,7 @@ describe('List model', () => { }); afterEach(() => { - Vue.http.interceptors = _.without(Vue.http.interceptors, boardsMockInterceptor); + mock.reset(); }); it('gets issues when created', (done) => { @@ -158,10 +157,8 @@ describe('List model', () => { describe('newIssue', () => { beforeEach(() => { spyOn(gl.boardService, 'newIssue').and.returnValue(Promise.resolve({ - json() { - return { - id: 42, - }; + data: { + id: 42, }, })); }); diff --git a/spec/javascripts/boards/mock_data.js b/spec/javascripts/boards/mock_data.js index 0a93086985e..9ae2d535398 100644 --- a/spec/javascripts/boards/mock_data.js +++ b/spec/javascripts/boards/mock_data.js @@ -1,20 +1,20 @@ /* global BoardService */ /* eslint-disable comma-dangle, no-unused-vars, quote-props */ -const listObj = { - id: _.random(10000), +export const listObj = { + id: 300, position: 0, title: 'Test', list_type: 'label', label: { - id: _.random(10000), + id: 5000, title: 'Testing', color: 'red', description: 'testing;' } }; -const listObjDuplicate = { +export const listObjDuplicate = { id: listObj.id, position: 1, title: 'Test', @@ -27,9 +27,9 @@ const listObjDuplicate = { } }; -const BoardsMockData = { +export const BoardsMockData = { 'GET': { - '/test/boards/1{/id}/issues': { + '/test/-/boards/1/lists/300/issues?id=300&page=1&=': { issues: [{ title: 'Testing', id: 1, @@ -41,7 +41,7 @@ const BoardsMockData = { } }, 'POST': { - '/test/boards/1{/id}': listObj + '/test/-/boards/1/lists': listObj }, 'PUT': { '/test/issue-boards/board/1/lists{/id}': {} @@ -51,17 +51,14 @@ const BoardsMockData = { } }; -const boardsMockInterceptor = (request, next) => { - const body = BoardsMockData[request.method][request.url]; - - next(request.respondWith(JSON.stringify(body), { - status: 200 - })); +export const boardsMockInterceptor = (config) => { + const body = BoardsMockData[config.method.toUpperCase()][config.url]; + return [200, body]; }; -const mockBoardService = (opts = {}) => { - const boardsEndpoint = opts.boardsEndpoint || '/test/issue-boards/board'; - const listsEndpoint = opts.listsEndpoint || '/test/boards/1'; +export const mockBoardService = (opts = {}) => { + const boardsEndpoint = opts.boardsEndpoint || '/test/issue-boards/boards.json'; + const listsEndpoint = opts.listsEndpoint || '/test/-/boards/1/lists'; const bulkUpdatePath = opts.bulkUpdatePath || ''; const boardId = opts.boardId || '1'; @@ -72,9 +69,3 @@ const mockBoardService = (opts = {}) => { boardId, }); }; - -window.listObj = listObj; -window.listObjDuplicate = listObjDuplicate; -window.BoardsMockData = BoardsMockData; -window.boardsMockInterceptor = boardsMockInterceptor; -window.mockBoardService = mockBoardService; diff --git a/spec/javascripts/filtered_search/filtered_search_manager_spec.js b/spec/javascripts/filtered_search/filtered_search_manager_spec.js index 5111632d681..b8890e4cda1 100644 --- a/spec/javascripts/filtered_search/filtered_search_manager_spec.js +++ b/spec/javascripts/filtered_search/filtered_search_manager_spec.js @@ -252,6 +252,7 @@ describe('Filtered Search Manager', () => { it('removes last token', () => { spyOn(gl.FilteredSearchVisualTokens, 'removeLastTokenPartial').and.callThrough(); dispatchBackspaceEvent(input, 'keyup'); + dispatchBackspaceEvent(input, 'keyup'); expect(gl.FilteredSearchVisualTokens.removeLastTokenPartial).toHaveBeenCalled(); }); @@ -259,6 +260,7 @@ describe('Filtered Search Manager', () => { it('sets the input', () => { spyOn(gl.FilteredSearchVisualTokens, 'getLastTokenPartial').and.callThrough(); dispatchDeleteEvent(input, 'keyup'); + dispatchDeleteEvent(input, 'keyup'); expect(gl.FilteredSearchVisualTokens.getLastTokenPartial).toHaveBeenCalled(); expect(input.value).toEqual('~bug'); @@ -276,6 +278,18 @@ describe('Filtered Search Manager', () => { expect(gl.FilteredSearchVisualTokens.getLastTokenPartial).not.toHaveBeenCalled(); expect(input.value).toEqual('text'); }); + + it('does not remove previous token on single backspace press', () => { + spyOn(gl.FilteredSearchVisualTokens, 'removeLastTokenPartial').and.callThrough(); + spyOn(gl.FilteredSearchVisualTokens, 'getLastTokenPartial').and.callThrough(); + + input.value = 't'; + dispatchDeleteEvent(input, 'keyup'); + + expect(gl.FilteredSearchVisualTokens.removeLastTokenPartial).not.toHaveBeenCalled(); + expect(gl.FilteredSearchVisualTokens.getLastTokenPartial).not.toHaveBeenCalled(); + expect(input.value).toEqual('t'); + }); }); describe('removeToken', () => { diff --git a/spec/javascripts/groups/components/item_caret_spec.js b/spec/javascripts/groups/components/item_caret_spec.js index 4310a07e6e6..8faad455825 100644 --- a/spec/javascripts/groups/components/item_caret_spec.js +++ b/spec/javascripts/groups/components/item_caret_spec.js @@ -16,24 +16,20 @@ describe('ItemCaretComponent', () => { describe('template', () => { it('should render component template correctly', () => { const vm = createComponent(); - vm.$mount(); expect(vm.$el.classList.contains('folder-caret')).toBeTruthy(); + expect(vm.$el.querySelectorAll('svg').length).toBe(1); vm.$destroy(); }); it('should render caret down icon if `isGroupOpen` prop is `true`', () => { const vm = createComponent(true); - vm.$mount(); - expect(vm.$el.querySelectorAll('i.fa.fa-caret-down').length).toBe(1); - expect(vm.$el.querySelectorAll('i.fa.fa-caret-right').length).toBe(0); + expect(vm.$el.querySelector('svg use').getAttribute('xlink:href')).toContain('angle-down'); vm.$destroy(); }); it('should render caret right icon if `isGroupOpen` prop is `false`', () => { const vm = createComponent(); - vm.$mount(); - expect(vm.$el.querySelectorAll('i.fa.fa-caret-down').length).toBe(0); - expect(vm.$el.querySelectorAll('i.fa.fa-caret-right').length).toBe(1); + expect(vm.$el.querySelector('svg use').getAttribute('xlink:href')).toContain('angle-right'); vm.$destroy(); }); }); diff --git a/spec/javascripts/groups/components/item_stats_spec.js b/spec/javascripts/groups/components/item_stats_spec.js index e200f9f08bd..55a7a713ca6 100644 --- a/spec/javascripts/groups/components/item_stats_spec.js +++ b/spec/javascripts/groups/components/item_stats_spec.js @@ -26,7 +26,6 @@ describe('ItemStatsComponent', () => { Object.keys(VISIBILITY_TYPE_ICON).forEach((visibility) => { const item = Object.assign({}, mockParentGroupItem, { visibility }); const vm = createComponent(item); - vm.$mount(); expect(vm.visibilityIcon).toBe(VISIBILITY_TYPE_ICON[visibility]); vm.$destroy(); }); @@ -41,7 +40,6 @@ describe('ItemStatsComponent', () => { type: ITEM_TYPE.GROUP, }); const vm = createComponent(item); - vm.$mount(); expect(vm.visibilityTooltip).toBe(GROUP_VISIBILITY_TYPE[visibility]); vm.$destroy(); }); @@ -54,7 +52,6 @@ describe('ItemStatsComponent', () => { type: ITEM_TYPE.PROJECT, }); const vm = createComponent(item); - vm.$mount(); expect(vm.visibilityTooltip).toBe(PROJECT_VISIBILITY_TYPE[visibility]); vm.$destroy(); }); @@ -68,13 +65,11 @@ describe('ItemStatsComponent', () => { item = Object.assign({}, mockParentGroupItem, { type: ITEM_TYPE.PROJECT }); vm = createComponent(item); - vm.$mount(); expect(vm.isProject).toBeTruthy(); vm.$destroy(); item = Object.assign({}, mockParentGroupItem, { type: ITEM_TYPE.GROUP }); vm = createComponent(item); - vm.$mount(); expect(vm.isProject).toBeFalsy(); vm.$destroy(); }); @@ -87,13 +82,11 @@ describe('ItemStatsComponent', () => { item = Object.assign({}, mockParentGroupItem, { type: ITEM_TYPE.GROUP }); vm = createComponent(item); - vm.$mount(); expect(vm.isGroup).toBeTruthy(); vm.$destroy(); item = Object.assign({}, mockParentGroupItem, { type: ITEM_TYPE.PROJECT }); vm = createComponent(item); - vm.$mount(); expect(vm.isGroup).toBeFalsy(); vm.$destroy(); }); @@ -101,57 +94,37 @@ describe('ItemStatsComponent', () => { }); describe('template', () => { - it('should render component template correctly', () => { + it('renders component container element correctly', () => { const vm = createComponent(); - vm.$mount(); - const visibilityIconEl = vm.$el.querySelector('.item-visibility'); - expect(vm.$el.classList.contains('.stats')).toBeDefined(); - expect(visibilityIconEl).toBeDefined(); - expect(visibilityIconEl.dataset.originalTitle).toBe(vm.visibilityTooltip); - expect(visibilityIconEl.querySelector('i.fa')).toBeDefined(); + expect(vm.$el.classList.contains('stats')).toBeTruthy(); vm.$destroy(); }); - it('should render stat icons if `item.type` is Group', () => { - const item = Object.assign({}, mockParentGroupItem, { type: ITEM_TYPE.GROUP }); - const vm = createComponent(item); - vm.$mount(); - - const subgroupIconEl = vm.$el.querySelector('span.number-subgroups'); - expect(subgroupIconEl).toBeDefined(); - expect(subgroupIconEl.dataset.originalTitle).toBe('Subgroups'); - expect(subgroupIconEl.querySelector('i.fa.fa-folder')).toBeDefined(); - expect(subgroupIconEl.innerText.trim()).toBe(`${vm.item.subgroupCount}`); - - const projectsIconEl = vm.$el.querySelector('span.number-projects'); - expect(projectsIconEl).toBeDefined(); - expect(projectsIconEl.dataset.originalTitle).toBe('Projects'); - expect(projectsIconEl.querySelector('i.fa.fa-bookmark')).toBeDefined(); - expect(projectsIconEl.innerText.trim()).toBe(`${vm.item.projectCount}`); - - const membersIconEl = vm.$el.querySelector('span.number-users'); - expect(membersIconEl).toBeDefined(); - expect(membersIconEl.dataset.originalTitle).toBe('Members'); - expect(membersIconEl.querySelector('i.fa.fa-users')).toBeDefined(); - expect(membersIconEl.innerText.trim()).toBe(`${vm.item.memberCount}`); + it('renders item visibility icon and tooltip correctly', () => { + const vm = createComponent(); + + const visibilityIconEl = vm.$el.querySelector('.item-visibility'); + expect(visibilityIconEl).not.toBe(null); + expect(visibilityIconEl.dataset.originalTitle).toBe(vm.visibilityTooltip); + expect(visibilityIconEl.querySelectorAll('svg').length > 0).toBeTruthy(); vm.$destroy(); }); - it('should render stat icons if `item.type` is Project', () => { + it('renders start count and last updated information for project item correctly', () => { const item = Object.assign({}, mockParentGroupItem, { type: ITEM_TYPE.PROJECT, starCount: 4, }); const vm = createComponent(item); - vm.$mount(); const projectStarIconEl = vm.$el.querySelector('.project-stars'); - expect(projectStarIconEl).toBeDefined(); - expect(projectStarIconEl.querySelector('i.fa.fa-star')).toBeDefined(); - expect(projectStarIconEl.innerText.trim()).toBe(`${vm.item.starCount}`); + expect(projectStarIconEl).not.toBe(null); + expect(projectStarIconEl.querySelectorAll('svg').length > 0).toBeTruthy(); + expect(projectStarIconEl.querySelectorAll('.stat-value').length > 0).toBeTruthy(); + expect(vm.$el.querySelectorAll('.last-updated').length > 0).toBeTruthy(); vm.$destroy(); }); diff --git a/spec/javascripts/groups/components/item_stats_value_spec.js b/spec/javascripts/groups/components/item_stats_value_spec.js new file mode 100644 index 00000000000..e990870aaa6 --- /dev/null +++ b/spec/javascripts/groups/components/item_stats_value_spec.js @@ -0,0 +1,81 @@ +import Vue from 'vue'; + +import itemStatsValueComponent from '~/groups/components/item_stats_value.vue'; + +import mountComponent from '../../helpers/vue_mount_component_helper'; + +const createComponent = ({ title, cssClass, iconName, tooltipPlacement, value }) => { + const Component = Vue.extend(itemStatsValueComponent); + + return mountComponent(Component, { + title, + cssClass, + iconName, + tooltipPlacement, + value, + }); +}; + +describe('ItemStatsValueComponent', () => { + describe('computed', () => { + let vm; + const itemConfig = { + title: 'Subgroups', + cssClass: 'number-subgroups', + iconName: 'folder', + tooltipPlacement: 'left', + }; + + describe('isValuePresent', () => { + it('returns true if non-empty `value` is present', () => { + vm = createComponent(Object.assign({}, itemConfig, { value: 10 })); + expect(vm.isValuePresent).toBeTruthy(); + }); + + it('returns false if empty `value` is present', () => { + vm = createComponent(itemConfig); + expect(vm.isValuePresent).toBeFalsy(); + }); + + afterEach(() => { + vm.$destroy(); + }); + }); + }); + + describe('template', () => { + let vm; + beforeEach(() => { + vm = createComponent({ + title: 'Subgroups', + cssClass: 'number-subgroups', + iconName: 'folder', + tooltipPlacement: 'left', + value: 10, + }); + }); + + it('renders component element correctly', () => { + expect(vm.$el.classList.contains('number-subgroups')).toBeTruthy(); + expect(vm.$el.querySelectorAll('svg').length > 0).toBeTruthy(); + expect(vm.$el.querySelectorAll('.stat-value').length > 0).toBeTruthy(); + }); + + it('renders element tooltip correctly', () => { + expect(vm.$el.dataset.originalTitle).toBe('Subgroups'); + expect(vm.$el.dataset.placement).toBe('left'); + }); + + it('renders element icon correctly', () => { + expect(vm.$el.querySelector('svg use').getAttribute('xlink:href')).toContain('folder'); + }); + + it('renders value count correctly', () => { + expect(vm.$el.querySelector('.stat-value').innerText.trim()).toContain('10'); + }); + + afterEach(() => { + vm.$destroy(); + }); + }); +}); diff --git a/spec/javascripts/groups/components/item_type_icon_spec.js b/spec/javascripts/groups/components/item_type_icon_spec.js index 528e6ed1b4c..495cc97b475 100644 --- a/spec/javascripts/groups/components/item_type_icon_spec.js +++ b/spec/javascripts/groups/components/item_type_icon_spec.js @@ -28,12 +28,12 @@ describe('ItemTypeIconComponent', () => { vm = createComponent(ITEM_TYPE.GROUP, true); vm.$mount(); - expect(vm.$el.querySelector('i.fa.fa-folder-open')).toBeDefined(); + expect(vm.$el.querySelector('use').getAttribute('xlink:href')).toContain('folder-open'); vm.$destroy(); vm = createComponent(ITEM_TYPE.GROUP); vm.$mount(); - expect(vm.$el.querySelector('i.fa.fa-folder')).toBeDefined(); + expect(vm.$el.querySelector('use').getAttribute('xlink:href')).toContain('folder'); vm.$destroy(); }); @@ -42,12 +42,12 @@ describe('ItemTypeIconComponent', () => { vm = createComponent(ITEM_TYPE.PROJECT); vm.$mount(); - expect(vm.$el.querySelectorAll('i.fa.fa-bookmark').length).toBe(1); + expect(vm.$el.querySelector('use').getAttribute('xlink:href')).toContain('bookmark'); vm.$destroy(); vm = createComponent(ITEM_TYPE.GROUP); vm.$mount(); - expect(vm.$el.querySelectorAll('i.fa.fa-bookmark').length).toBe(0); + expect(vm.$el.querySelector('use').getAttribute('xlink:href')).not.toContain('bookmark'); vm.$destroy(); }); }); diff --git a/spec/javascripts/groups/mock_data.js b/spec/javascripts/groups/mock_data.js index 6184d671790..8bf6417487d 100644 --- a/spec/javascripts/groups/mock_data.js +++ b/spec/javascripts/groups/mock_data.js @@ -18,9 +18,9 @@ export const PROJECT_VISIBILITY_TYPE = { }; export const VISIBILITY_TYPE_ICON = { - public: 'fa-globe', - internal: 'fa-shield', - private: 'fa-lock', + public: 'earth', + internal: 'shield', + private: 'lock', }; export const mockParentGroupItem = { @@ -46,6 +46,7 @@ export const mockParentGroupItem = { isOpen: true, isChildrenLoading: false, isBeingRemoved: false, + updatedAt: '2017-04-09T18:40:39.101Z', }; export const mockRawChildren = [ @@ -69,6 +70,7 @@ export const mockRawChildren = [ subgroup_count: 2, can_leave: false, children: [], + updated_at: '2017-04-09T18:40:39.101Z', }, ]; @@ -96,6 +98,7 @@ export const mockChildren = [ isOpen: true, isChildrenLoading: false, isBeingRemoved: false, + updatedAt: '2017-04-09T18:40:39.101Z', }, ]; @@ -119,6 +122,7 @@ export const mockGroups = [ project_count: 2, subgroup_count: 0, can_leave: false, + updated_at: '2017-04-09T18:40:39.101Z', }, { id: 67, @@ -139,6 +143,7 @@ export const mockGroups = [ project_count: 0, subgroup_count: 0, can_leave: false, + updated_at: '2017-04-09T18:40:39.101Z', }, { id: 54, @@ -159,6 +164,7 @@ export const mockGroups = [ project_count: 0, subgroup_count: 1, can_leave: false, + updated_at: '2017-04-09T18:40:39.101Z', }, { id: 5, @@ -179,6 +185,7 @@ export const mockGroups = [ project_count: 1, subgroup_count: 0, can_leave: false, + updated_at: '2017-04-09T18:40:39.101Z', }, { id: 4, @@ -199,6 +206,7 @@ export const mockGroups = [ project_count: 2, subgroup_count: 0, can_leave: false, + updated_at: '2017-04-09T18:40:39.101Z', }, { id: 3, @@ -219,6 +227,7 @@ export const mockGroups = [ project_count: 1, subgroup_count: 0, can_leave: false, + updated_at: '2017-04-09T18:40:39.101Z', }, { id: 2, @@ -239,6 +248,7 @@ export const mockGroups = [ project_count: 4, subgroup_count: 0, can_leave: false, + updated_at: '2017-04-09T18:40:39.101Z', }, ]; @@ -262,6 +272,7 @@ export const mockSearchedGroups = [ project_count: 1, subgroup_count: 2, can_leave: false, + updated_at: '2017-04-09T18:40:39.101Z', children: [ { id: 57, @@ -282,6 +293,7 @@ export const mockSearchedGroups = [ project_count: 4, subgroup_count: 2, can_leave: false, + updated_at: '2017-04-09T18:40:39.101Z', children: [ { id: 60, @@ -302,6 +314,7 @@ export const mockSearchedGroups = [ project_count: 0, subgroup_count: 1, can_leave: false, + updated_at: '2017-04-09T18:40:39.101Z', children: [ { id: 61, @@ -322,6 +335,7 @@ export const mockSearchedGroups = [ project_count: 2, subgroup_count: 0, can_leave: false, + updated_at: '2017-04-09T18:40:39.101Z', children: [ { id: 17, @@ -336,6 +350,7 @@ export const mockSearchedGroups = [ permission: null, edit_path: '/platform/hardware/bsp/kernel/common/v4.4/edit', star_count: 0, + updated_at: '2017-09-12T06:37:04.925Z', }, { id: 16, @@ -350,6 +365,7 @@ export const mockSearchedGroups = [ permission: null, edit_path: '/platform/hardware/bsp/kernel/common/v4.1/edit', star_count: 0, + updated_at: '2017-04-09T18:41:03.112Z', }, ], }, diff --git a/spec/javascripts/issue_show/components/app_spec.js b/spec/javascripts/issue_show/components/app_spec.js index 7159148f8fa..1454ca52018 100644 --- a/spec/javascripts/issue_show/components/app_spec.js +++ b/spec/javascripts/issue_show/components/app_spec.js @@ -1,4 +1,6 @@ import Vue from 'vue'; +import MockAdapter from 'axios-mock-adapter'; +import axios from '~/lib/utils/axios_utils'; import '~/render_math'; import '~/render_gfm'; import * as urlUtils from '~/lib/utils/url_utility'; @@ -11,26 +13,29 @@ function formatText(text) { return text.trim().replace(/\s\s+/g, ' '); } +const REALTIME_REQUEST_STACK = [ + issueShowData.initialRequest, + issueShowData.secondRequest, +]; + describe('Issuable output', () => { - let requestData = issueShowData.initialRequest; + let mock; + let realtimeRequestCount = 0; + let vm; document.body.innerHTML = '<span id="task_status"></span>'; - const interceptor = (request, next) => { - next(request.respondWith(JSON.stringify(requestData), { - status: 200, - })); - }; - - let vm; - beforeEach((done) => { spyOn(eventHub, '$emit'); const IssuableDescriptionComponent = Vue.extend(issuableApp); - requestData = issueShowData.initialRequest; - Vue.http.interceptors.push(interceptor); + mock = new MockAdapter(axios); + mock.onGet('/gitlab-org/gitlab-shell/issues/9/realtime_changes/realtime_changes').reply(() => { + const res = Promise.resolve([200, REALTIME_REQUEST_STACK[realtimeRequestCount]]); + realtimeRequestCount += 1; + return res; + }); vm = new IssuableDescriptionComponent({ propsData: { @@ -54,10 +59,10 @@ describe('Issuable output', () => { }); afterEach(() => { - Vue.http.interceptors = _.without(Vue.http.interceptors, interceptor); + mock.reset(); + realtimeRequestCount = 0; vm.poll.stop(); - vm.$destroy(); }); @@ -77,7 +82,6 @@ describe('Issuable output', () => { expect(editedText.querySelector('time')).toBeTruthy(); }) .then(() => { - requestData = issueShowData.secondRequest; vm.poll.makeRequest(); }) .then(() => new Promise(resolve => setTimeout(resolve))) @@ -141,24 +145,19 @@ describe('Issuable output', () => { spyOn(vm.service, 'getData').and.callThrough(); spyOn(vm.service, 'updateIssuable').and.callFake(() => new Promise((resolve) => { resolve({ - json() { - return { - confidential: false, - web_url: location.pathname, - }; + data: { + confidential: false, + web_url: location.pathname, }, }); })); - vm.updateIssuable(); - - setTimeout(() => { - expect( - vm.service.getData, - ).toHaveBeenCalled(); - - done(); - }); + vm.updateIssuable() + .then(() => { + expect(vm.service.getData).toHaveBeenCalled(); + }) + .then(done) + .catch(done.fail); }); it('correctly updates issuable data', (done) => { @@ -166,29 +165,22 @@ describe('Issuable output', () => { resolve(); })); - vm.updateIssuable(); - - setTimeout(() => { - expect( - vm.service.updateIssuable, - ).toHaveBeenCalledWith(vm.formState); - expect( - eventHub.$emit, - ).toHaveBeenCalledWith('close.form'); - - done(); - }); + vm.updateIssuable() + .then(() => { + expect(vm.service.updateIssuable).toHaveBeenCalledWith(vm.formState); + expect(eventHub.$emit).toHaveBeenCalledWith('close.form'); + }) + .then(done) + .catch(done.fail); }); it('does not redirect if issue has not moved', (done) => { spyOn(urlUtils, 'visitUrl'); spyOn(vm.service, 'updateIssuable').and.callFake(() => new Promise((resolve) => { resolve({ - json() { - return { - web_url: location.pathname, - confidential: vm.isConfidential, - }; + data: { + web_url: location.pathname, + confidential: vm.isConfidential, }, }); })); @@ -208,11 +200,9 @@ describe('Issuable output', () => { spyOn(urlUtils, 'visitUrl'); spyOn(vm.service, 'updateIssuable').and.callFake(() => new Promise((resolve) => { resolve({ - json() { - return { - web_url: '/testing-issue-move', - confidential: vm.isConfidential, - }; + data: { + web_url: '/testing-issue-move', + confidential: vm.isConfidential, }, }); })); @@ -283,10 +273,8 @@ describe('Issuable output', () => { let modal; const promise = new Promise((resolve) => { resolve({ - json() { - return { - recaptcha_html: '<div class="g-recaptcha">recaptcha_html</div>', - }; + data: { + recaptcha_html: '<div class="g-recaptcha">recaptcha_html</div>', }, }); }); @@ -323,8 +311,8 @@ describe('Issuable output', () => { spyOn(urlUtils, 'visitUrl'); spyOn(vm.service, 'deleteIssuable').and.callFake(() => new Promise((resolve) => { resolve({ - json() { - return { web_url: '/test' }; + data: { + web_url: '/test', }, }); })); @@ -345,8 +333,8 @@ describe('Issuable output', () => { spyOn(vm.poll, 'stop').and.callThrough(); spyOn(vm.service, 'deleteIssuable').and.callFake(() => new Promise((resolve) => { resolve({ - json() { - return { web_url: '/test' }; + data: { + web_url: '/test', }, }); })); @@ -385,22 +373,21 @@ describe('Issuable output', () => { describe('open form', () => { it('shows locked warning if form is open & data is different', (done) => { - Vue.nextTick() + vm.$nextTick() .then(() => { vm.openForm(); - requestData = issueShowData.secondRequest; vm.poll.makeRequest(); }) - .then(() => new Promise(resolve => setTimeout(resolve))) + // Wait for the request + .then(vm.$nextTick) + // Wait for the successCallback to update the store state + .then(vm.$nextTick) + // Wait for the new state to flow to the Vue components + .then(vm.$nextTick) .then(() => { - expect( - vm.formState.lockedWarningVisible, - ).toBeTruthy(); - - expect( - vm.$el.querySelector('.alert'), - ).not.toBeNull(); + expect(vm.formState.lockedWarningVisible).toEqual(true); + expect(vm.$el.querySelector('.alert')).not.toBeNull(); }) .then(done) .catch(done.fail); diff --git a/spec/javascripts/issue_show/mock_data.js b/spec/javascripts/issue_show/mock_data.js index eb3111412a7..74b3efb014b 100644 --- a/spec/javascripts/issue_show/mock_data.js +++ b/spec/javascripts/issue_show/mock_data.js @@ -19,14 +19,4 @@ export default { updated_by_name: 'Other User', updated_by_path: '/other_user', }, - issueSpecRequest: { - title: '<p>this is a title</p>', - title_text: 'this is a title', - description: '<li class="task-list-item enabled"><input type="checkbox" class="task-list-item-checkbox">Task List Item</li>', - description_text: '- [ ] Task List Item', - task_status: '0 of 1 completed', - updated_at: '2017-05-15T12:31:04.428Z', - updated_by_name: 'Last User', - updated_by_path: '/last_user', - }, }; diff --git a/spec/javascripts/job_spec.js b/spec/javascripts/job_spec.js index 4f06237deb5..b740c9ed893 100644 --- a/spec/javascripts/job_spec.js +++ b/spec/javascripts/job_spec.js @@ -1,4 +1,4 @@ -import { bytesToKiB } from '~/lib/utils/number_utils'; +import { numberToHumanSize } from '~/lib/utils/number_utils'; import * as urlUtils from '~/lib/utils/url_utility'; import '~/lib/utils/datetime_utility'; import Job from '~/job'; @@ -169,7 +169,7 @@ describe('Job', () => { expect( document.querySelector('.js-truncated-info-size').textContent.trim(), - ).toEqual(`${bytesToKiB(size)}`); + ).toEqual(`${numberToHumanSize(size)}`); }); it('shows incremented size', () => { @@ -195,7 +195,7 @@ describe('Job', () => { expect( document.querySelector('.js-truncated-info-size').textContent.trim(), - ).toEqual(`${bytesToKiB(50)}`); + ).toEqual(`${numberToHumanSize(50)}`); jasmine.clock().tick(4001); @@ -209,7 +209,7 @@ describe('Job', () => { expect( document.querySelector('.js-truncated-info-size').textContent.trim(), - ).toEqual(`${bytesToKiB(60)}`); + ).toEqual(`${numberToHumanSize(60)}`); }); it('renders the raw link', () => { diff --git a/spec/javascripts/repo/components/repo_file_spec.js b/spec/javascripts/repo/components/repo_file_spec.js index e8b370f97b4..0810da87e80 100644 --- a/spec/javascripts/repo/components/repo_file_spec.js +++ b/spec/javascripts/repo/components/repo_file_spec.js @@ -32,13 +32,9 @@ describe('RepoFile', () => { vm.$mount(); const name = vm.$el.querySelector('.repo-file-name'); - const fileIcon = vm.$el.querySelector('.file-icon'); - expect(vm.$el.querySelector(`.${vm.file.icon}`).style.marginLeft).toEqual('0px'); expect(name.href).toMatch(''); expect(name.textContent.trim()).toEqual(vm.file.name); - expect(fileIcon.classList.contains(vm.file.icon)).toBeTruthy(); - expect(fileIcon.style.marginLeft).toEqual(`${vm.file.level * 10}px`); }); it('does render if hasFiles is true and is loading tree', () => { @@ -49,17 +45,6 @@ describe('RepoFile', () => { expect(vm.$el.querySelector('.fa-spin.fa-spinner')).toBeFalsy(); }); - it('renders a spinner if the file is loading', () => { - const f = file(); - f.loading = true; - vm = createComponent({ - file: f, - }); - - expect(vm.$el.querySelector('.fa-spin.fa-spinner')).not.toBeNull(); - expect(vm.$el.querySelector('.fa-spin.fa-spinner').style.marginLeft).toEqual(`${vm.file.level * 16}px`); - }); - it('does not render commit message and datetime if mini', (done) => { vm = createComponent({ file: file(), diff --git a/spec/javascripts/vue_mr_widget/components/mr_widget_deployment_spec.js b/spec/javascripts/vue_mr_widget/components/mr_widget_deployment_spec.js index db7d083065b..6a59dc3c87e 100644 --- a/spec/javascripts/vue_mr_widget/components/mr_widget_deployment_spec.js +++ b/spec/javascripts/vue_mr_widget/components/mr_widget_deployment_spec.js @@ -95,10 +95,8 @@ describe('MRWidgetDeployment', () => { const url = '/foo/bar'; const returnPromise = () => new Promise((resolve) => { resolve({ - json() { - return { - redirect_url: url, - }; + data: { + redirect_url: url, }, }); }); diff --git a/spec/javascripts/vue_mr_widget/components/mr_widget_memory_usage_spec.js b/spec/javascripts/vue_mr_widget/components/mr_widget_memory_usage_spec.js index 2ae3adc1f93..07ed7f7f532 100644 --- a/spec/javascripts/vue_mr_widget/components/mr_widget_memory_usage_spec.js +++ b/spec/javascripts/vue_mr_widget/components/mr_widget_memory_usage_spec.js @@ -155,9 +155,7 @@ describe('MemoryUsage', () => { describe('loadMetrics', () => { const returnServicePromise = () => new Promise((resolve) => { resolve({ - json() { - return metricsMockData; - }, + data: metricsMockData, }); }); diff --git a/spec/javascripts/vue_mr_widget/components/states/mr_widget_closed_spec.js b/spec/javascripts/vue_mr_widget/components/states/mr_widget_closed_spec.js index d23b558f4ea..1bf97bbf093 100644 --- a/spec/javascripts/vue_mr_widget/components/states/mr_widget_closed_spec.js +++ b/spec/javascripts/vue_mr_widget/components/states/mr_widget_closed_spec.js @@ -4,13 +4,16 @@ import closedComponent from '~/vue_merge_request_widget/components/states/mr_wid const mr = { targetBranch: 'good-branch', targetBranchPath: '/good-branch', - closedEvent: { - author: { + metrics: { + mergedBy: {}, + mergedAt: 'mergedUpdatedAt', + closedBy: { name: 'Fatih Acet', username: 'fatihacet', }, - updatedAt: 'closedEventUpdatedAt', - formattedUpdatedAt: '', + closedAt: 'closedEventUpdatedAt', + readableMergedAt: '', + readableClosedAt: '', }, updatedAt: 'mrUpdatedAt', closedAt: '1 day ago', @@ -56,7 +59,7 @@ describe('MRWidgetClosed', () => { it('should have correct elements', () => { expect(el.querySelector('h4').textContent).toContain('Closed by'); - expect(el.querySelector('h4').textContent).toContain(mr.closedEvent.author.name); + expect(el.querySelector('h4').textContent).toContain(mr.metrics.closedBy.name); expect(el.textContent).toContain('The changes were not merged into'); expect(el.querySelector('.label-branch').getAttribute('href')).toEqual(mr.targetBranchPath); expect(el.querySelector('.label-branch').textContent).toContain(mr.targetBranch); diff --git a/spec/javascripts/vue_mr_widget/components/states/mr_widget_merge_when_pipeline_succeeds_spec.js b/spec/javascripts/vue_mr_widget/components/states/mr_widget_merge_when_pipeline_succeeds_spec.js index 9a71d0b47d7..5f4df15bcd6 100644 --- a/spec/javascripts/vue_mr_widget/components/states/mr_widget_merge_when_pipeline_succeeds_spec.js +++ b/spec/javascripts/vue_mr_widget/components/states/mr_widget_merge_when_pipeline_succeeds_spec.js @@ -108,9 +108,7 @@ describe('MRWidgetMergeWhenPipelineSucceeds', () => { spyOn(eventHub, '$emit'); spyOn(vm.service, 'cancelAutomaticMerge').and.returnValue(new Promise((resolve) => { resolve({ - json() { - return mrObj; - }, + data: mrObj, }); })); @@ -129,10 +127,8 @@ describe('MRWidgetMergeWhenPipelineSucceeds', () => { spyOn(eventHub, '$emit'); spyOn(vm.service.mergeResource, 'save').and.returnValue(new Promise((resolve) => { resolve({ - json() { - return { - status: 'merge_when_pipeline_succeeds', - }; + data: { + status: 'merge_when_pipeline_succeeds', }, }); })); diff --git a/spec/javascripts/vue_mr_widget/components/states/mr_widget_merged_spec.js b/spec/javascripts/vue_mr_widget/components/states/mr_widget_merged_spec.js index 2714e8294fa..2dc3b72ea40 100644 --- a/spec/javascripts/vue_mr_widget/components/states/mr_widget_merged_spec.js +++ b/spec/javascripts/vue_mr_widget/components/states/mr_widget_merged_spec.js @@ -14,10 +14,13 @@ const createComponent = () => { canRevertInCurrentMR: true, canRemoveSourceBranch: true, sourceBranchRemoved: true, - mergedEvent: { - author: {}, - updatedAt: 'mergedUpdatedAt', - formattedUpdatedAt: '', + metrics: { + mergedBy: {}, + mergedAt: 'mergedUpdatedAt', + readableMergedAt: '', + closedBy: {}, + closedAt: 'mergedUpdatedAt', + readableClosedAt: '', }, updatedAt: 'mrUpdatedAt', targetBranch, @@ -111,10 +114,8 @@ describe('MRWidgetMerged', () => { spyOn(eventHub, '$emit'); spyOn(vm.service, 'removeSourceBranch').and.returnValue(new Promise((resolve) => { resolve({ - json() { - return { - message: 'Branch was removed', - }; + data: { + message: 'Branch was removed', }, }); })); diff --git a/spec/javascripts/vue_mr_widget/components/states/mr_widget_ready_to_merge_spec.js b/spec/javascripts/vue_mr_widget/components/states/mr_widget_ready_to_merge_spec.js index df3d29ee1f9..1127576617b 100644 --- a/spec/javascripts/vue_mr_widget/components/states/mr_widget_ready_to_merge_spec.js +++ b/spec/javascripts/vue_mr_widget/components/states/mr_widget_ready_to_merge_spec.js @@ -292,8 +292,8 @@ describe('MRWidgetReadyToMerge', () => { describe('handleMergeButtonClick', () => { const returnPromise = status => new Promise((resolve) => { resolve({ - json() { - return { status }; + data: { + status, }, }); }); @@ -364,8 +364,9 @@ describe('MRWidgetReadyToMerge', () => { describe('handleMergePolling', () => { const returnPromise = state => new Promise((resolve) => { resolve({ - json() { - return { state, source_branch_exists: true }; + data: { + state, + source_branch_exists: true, }, }); }); @@ -422,8 +423,8 @@ describe('MRWidgetReadyToMerge', () => { describe('handleRemoveBranchPolling', () => { const returnPromise = state => new Promise((resolve) => { resolve({ - json() { - return { source_branch_exists: state }; + data: { + source_branch_exists: state, }, }); }); diff --git a/spec/javascripts/vue_mr_widget/components/states/mr_widget_wip_spec.js b/spec/javascripts/vue_mr_widget/components/states/mr_widget_wip_spec.js index 2cb3aaa6951..98ab61a0367 100644 --- a/spec/javascripts/vue_mr_widget/components/states/mr_widget_wip_spec.js +++ b/spec/javascripts/vue_mr_widget/components/states/mr_widget_wip_spec.js @@ -50,9 +50,7 @@ describe('MRWidgetWIP', () => { spyOn(eventHub, '$emit'); spyOn(vm.service, 'removeWIP').and.returnValue(new Promise((resolve) => { resolve({ - json() { - return mrObj; - }, + data: mrObj, }); })); diff --git a/spec/javascripts/vue_mr_widget/mock_data.js b/spec/javascripts/vue_mr_widget/mock_data.js index 1ad7c2d8efa..ca29c9fee32 100644 --- a/spec/javascripts/vue_mr_widget/mock_data.js +++ b/spec/javascripts/vue_mr_widget/mock_data.js @@ -33,8 +33,8 @@ export default { "source_project_id": 19, "target_branch": "master", "target_project_id": 19, - "merge_event": { - "author": { + "metrics": { + "merged_by": { "name": "Administrator", "username": "root", "id": 1, @@ -42,9 +42,10 @@ export default { "avatar_url": "http://www.gravatar.com/avatar/e64c7d89f26bd1972efa854d13d7dd61?s=80&d=identicon", "web_url": "http://localhost:3000/root" }, - "updated_at": "2017-04-07T15:39:25.696Z" + "merged_at": "2017-04-07T15:39:25.696Z", + "closed_by": null, + "closed_at": null }, - "closed_event": null, "author": { "name": "Administrator", "username": "root", diff --git a/spec/javascripts/vue_mr_widget/mr_widget_options_spec.js b/spec/javascripts/vue_mr_widget/mr_widget_options_spec.js index 74b343c573e..cd00d0a39a3 100644 --- a/spec/javascripts/vue_mr_widget/mr_widget_options_spec.js +++ b/spec/javascripts/vue_mr_widget/mr_widget_options_spec.js @@ -8,10 +8,7 @@ import mountComponent from '../helpers/vue_mount_component_helper'; const returnPromise = data => new Promise((resolve) => { resolve({ - json() { - return data; - }, - body: data, + data, }); }); diff --git a/spec/javascripts/vue_shared/components/file_icon_spec.js b/spec/javascripts/vue_shared/components/file_icon_spec.js new file mode 100644 index 00000000000..d99b17bdc79 --- /dev/null +++ b/spec/javascripts/vue_shared/components/file_icon_spec.js @@ -0,0 +1,83 @@ +import Vue from 'vue'; +import fileIcon from '~/vue_shared/components/file_icon.vue'; +import mountComponent from '../../helpers/vue_mount_component_helper'; + +describe('File Icon component', () => { + let vm; + let FileIcon; + + beforeEach(() => { + FileIcon = Vue.extend(fileIcon); + }); + + afterEach(() => { + vm.$destroy(); + }); + + it('should render a span element with an svg', () => { + vm = mountComponent(FileIcon, { + fileName: 'test.js', + }); + + expect(vm.$el.tagName).toEqual('SPAN'); + expect(vm.$el.querySelector('span > svg')).toBeDefined(); + }); + + it('should render a javascript icon based on file ending', () => { + vm = mountComponent(FileIcon, { + fileName: 'test.js', + }); + + expect(vm.$el.firstChild.firstChild.getAttribute('xlink:href')).toBe(`${gon.sprite_file_icons}#javascript`); + }); + + it('should render a image icon based on file ending', () => { + vm = mountComponent(FileIcon, { + fileName: 'test.png', + }); + + expect(vm.$el.firstChild.firstChild.getAttribute('xlink:href')).toBe(`${gon.sprite_file_icons}#image`); + }); + + it('should render a webpack icon based on file namer', () => { + vm = mountComponent(FileIcon, { + fileName: 'webpack.js', + }); + + expect(vm.$el.firstChild.firstChild.getAttribute('xlink:href')).toBe(`${gon.sprite_file_icons}#webpack`); + }); + + it('should render a standard folder icon', () => { + vm = mountComponent(FileIcon, { + fileName: 'js', + folder: true, + }); + + expect(vm.$el.querySelector('span > svg > use').getAttribute('xlink:href')).toBe(`${gon.sprite_file_icons}#folder`); + }); + + it('should render a loading icon', () => { + vm = mountComponent(FileIcon, { + fileName: 'test.js', + loading: true, + }); + + expect( + vm.$el.querySelector('i').getAttribute('class'), + ).toEqual('fa fa-spin fa-spinner fa-1x'); + }); + + it('should add a special class and a size class', () => { + vm = mountComponent(FileIcon, { + fileName: 'test.js', + cssClasses: 'extraclasses', + size: 120, + }); + + const classList = vm.$el.firstChild.classList; + const containsSizeClass = classList.contains('s120'); + const containsCustomClass = classList.contains('extraclasses'); + expect(containsSizeClass).toBe(true); + expect(containsCustomClass).toBe(true); + }); +}); diff --git a/spec/javascripts/vue_shared/components/panel_resizer_spec.js b/spec/javascripts/vue_shared/components/panel_resizer_spec.js new file mode 100644 index 00000000000..70ce3dffaba --- /dev/null +++ b/spec/javascripts/vue_shared/components/panel_resizer_spec.js @@ -0,0 +1,59 @@ +import Vue from 'vue'; +import panelResizer from '~/vue_shared/components/panel_resizer.vue'; +import mountComponent from '../../helpers/vue_mount_component_helper'; + +describe('Panel Resizer component', () => { + let vm; + let PanelResizer; + + const triggerEvent = (eventName, el = vm.$el, clientX = 0) => { + const event = document.createEvent('MouseEvents'); + event.initMouseEvent(eventName, true, true, window, 1, clientX, 0, clientX, 0, false, false, + false, false, 0, null); + + el.dispatchEvent(event); + }; + + beforeEach(() => { + PanelResizer = Vue.extend(panelResizer); + }); + + afterEach(() => { + vm.$destroy(); + }); + + it('should render a div element with the correct classes and styles', () => { + vm = mountComponent(PanelResizer, { + startSize: 100, + side: 'left', + }); + + expect(vm.$el.tagName).toEqual('DIV'); + expect(vm.$el.getAttribute('class')).toBe('dragHandle dragleft'); + expect(vm.$el.getAttribute('style')).toBe('cursor: ew-resize;'); + }); + + it('should render a div element with the correct classes for a right side panel', () => { + vm = mountComponent(PanelResizer, { + startSize: 100, + side: 'right', + }); + + expect(vm.$el.tagName).toEqual('DIV'); + expect(vm.$el.getAttribute('class')).toBe('dragHandle dragright'); + }); + + it('drag the resizer', () => { + vm = mountComponent(PanelResizer, { + startSize: 100, + side: 'left', + }); + + spyOn(vm, '$emit'); + triggerEvent('mousedown', vm.$el); + triggerEvent('mousemove', document); + triggerEvent('mouseup', document); + expect(vm.$emit.calls.allArgs()).toEqual([['resize-start', 100], ['update:size', 100], ['resize-end', 100]]); + expect(vm.size).toBe(100); + }); +}); diff --git a/spec/lib/banzai/filter/redactor_filter_spec.rb b/spec/lib/banzai/filter/redactor_filter_spec.rb index 68643effb66..5a7858e77f3 100644 --- a/spec/lib/banzai/filter/redactor_filter_spec.rb +++ b/spec/lib/banzai/filter/redactor_filter_spec.rb @@ -46,7 +46,7 @@ describe Banzai::Filter::RedactorFilter do it 'allows permitted Project references' do user = create(:user) project = create(:project) - project.team << [user, :master] + project.add_master(user) link = reference_link(project: project.id, reference_type: 'test') doc = filter(link, current_user: user) @@ -94,7 +94,7 @@ describe Banzai::Filter::RedactorFilter do it 'removes references for project members with guest role' do member = create(:user) project = create(:project, :public) - project.team << [member, :guest] + project.add_guest(member) issue = create(:issue, :confidential, project: project) link = reference_link(project: project.id, issue: issue.id, reference_type: 'issue') @@ -128,7 +128,7 @@ describe Banzai::Filter::RedactorFilter do it 'allows references for project members' do member = create(:user) project = create(:project, :public) - project.team << [member, :developer] + project.add_developer(member) issue = create(:issue, :confidential, project: project) link = reference_link(project: project.id, issue: issue.id, reference_type: 'issue') diff --git a/spec/lib/banzai/filter/relative_link_filter_spec.rb b/spec/lib/banzai/filter/relative_link_filter_spec.rb index ef306f1cd4a..f38f0776303 100644 --- a/spec/lib/banzai/filter/relative_link_filter_spec.rb +++ b/spec/lib/banzai/filter/relative_link_filter_spec.rb @@ -76,6 +76,11 @@ describe Banzai::Filter::RelativeLinkFilter do expect { filter(act) }.not_to raise_error end + it 'does not raise an exception with a garbled path' do + act = link("open(/var/tmp/):%20/location%0Afrom:%20/test") + expect { filter(act) }.not_to raise_error + end + it 'ignores ref if commit is passed' do doc = filter(link('non/existent.file'), commit: project.commit('empty-branch') ) expect(doc.at_css('a')['href']) diff --git a/spec/lib/banzai/filter/user_reference_filter_spec.rb b/spec/lib/banzai/filter/user_reference_filter_spec.rb index fc03741976e..c76adc262fc 100644 --- a/spec/lib/banzai/filter/user_reference_filter_spec.rb +++ b/spec/lib/banzai/filter/user_reference_filter_spec.rb @@ -34,11 +34,11 @@ describe Banzai::Filter::UserReferenceFilter do let(:reference) { User.reference_prefix + 'all' } before do - project.team << [project.creator, :developer] + project.add_developer(project.creator) end it 'supports a special @all mention' do - project.team << [user, :developer] + project.add_developer(user) doc = reference_filter("Hey #{reference}", author: user) expect(doc.css('a').length).to eq 1 @@ -47,7 +47,7 @@ describe Banzai::Filter::UserReferenceFilter do end it 'includes a data-author attribute when there is an author' do - project.team << [user, :developer] + project.add_developer(user) doc = reference_filter(reference, author: user) expect(doc.css('a').first.attr('data-author')).to eq(user.id.to_s) diff --git a/spec/lib/banzai/reference_parser/user_parser_spec.rb b/spec/lib/banzai/reference_parser/user_parser_spec.rb index e49726aca6c..b079a3be029 100644 --- a/spec/lib/banzai/reference_parser/user_parser_spec.rb +++ b/spec/lib/banzai/reference_parser/user_parser_spec.rb @@ -63,8 +63,8 @@ describe Banzai::ReferenceParser::UserParser do let(:contributor) { create(:user) } before do - project.team << [user, :developer] - project.team << [contributor, :developer] + project.add_developer(user) + project.add_developer(contributor) end it 'returns the members of a project' do @@ -162,7 +162,7 @@ describe Banzai::ReferenceParser::UserParser do context 'when the link has a data-author attribute' do it 'returns the nodes when the user is a member of the project' do other_project = create(:project) - other_project.team << [user, :developer] + other_project.add_developer(user) link['data-project'] = other_project.id.to_s link['data-author'] = user.id.to_s diff --git a/spec/lib/gitlab/background_migration/delete_conflicting_redirect_routes_range_spec.rb b/spec/lib/gitlab/background_migration/delete_conflicting_redirect_routes_range_spec.rb index 5c471cbdeda..9bae7e53b71 100644 --- a/spec/lib/gitlab/background_migration/delete_conflicting_redirect_routes_range_spec.rb +++ b/spec/lib/gitlab/background_migration/delete_conflicting_redirect_routes_range_spec.rb @@ -24,17 +24,12 @@ describe Gitlab::BackgroundMigration::DeleteConflictingRedirectRoutesRange, :mig redirect_routes.create!(source_id: 1, source_type: 'Namespace', path: 'foo5') end - it 'deletes the conflicting redirect_routes in the range' do + # No-op. See https://gitlab.com/gitlab-com/infrastructure/issues/3460#note_53223252 + it 'NO-OP: does not delete any redirect_routes' do expect(redirect_routes.count).to eq(8) - expect do - described_class.new.perform(1, 3) - end.to change { redirect_routes.where("path like 'foo%'").count }.from(5).to(2) + described_class.new.perform(1, 5) - expect do - described_class.new.perform(4, 5) - end.to change { redirect_routes.where("path like 'foo%'").count }.from(2).to(0) - - expect(redirect_routes.count).to eq(3) + expect(redirect_routes.count).to eq(8) end end diff --git a/spec/lib/gitlab/background_migration/migrate_events_to_push_event_payloads_spec.rb b/spec/lib/gitlab/background_migration/migrate_events_to_push_event_payloads_spec.rb index 7351d45336a..5432d270555 100644 --- a/spec/lib/gitlab/background_migration/migrate_events_to_push_event_payloads_spec.rb +++ b/spec/lib/gitlab/background_migration/migrate_events_to_push_event_payloads_spec.rb @@ -281,6 +281,17 @@ describe Gitlab::BackgroundMigration::MigrateEventsToPushEventPayloads, :migrati migration.process_event(event) end + + it 'handles an error gracefully' do + event1 = create_push_event(project, author, { commits: [] }) + + expect(migration).to receive(:replicate_event).and_call_original + expect(migration).to receive(:create_push_event_payload).and_raise(ActiveRecord::InvalidForeignKey, 'invalid foreign key') + + migration.process_event(event1) + + expect(described_class::EventForMigration.all.count).to eq(0) + end end describe '#replicate_event' do @@ -335,9 +346,8 @@ describe Gitlab::BackgroundMigration::MigrateEventsToPushEventPayloads, :migrati it 'does not create push event payloads for removed events' do allow(event).to receive(:id).and_return(-1) - payload = migration.create_push_event_payload(event) + expect { migration.create_push_event_payload(event) }.to raise_error(ActiveRecord::InvalidForeignKey) - expect(payload).to be_nil expect(PushEventPayload.count).to eq(0) end diff --git a/spec/lib/gitlab/background_migration/populate_merge_request_metrics_with_events_data_spec.rb b/spec/lib/gitlab/background_migration/populate_merge_request_metrics_with_events_data_spec.rb new file mode 100644 index 00000000000..dfe3b31f1c0 --- /dev/null +++ b/spec/lib/gitlab/background_migration/populate_merge_request_metrics_with_events_data_spec.rb @@ -0,0 +1,124 @@ +require 'rails_helper' + +describe Gitlab::BackgroundMigration::PopulateMergeRequestMetricsWithEventsData, :migration, schema: 20171128214150 do + describe '#perform' do + let(:mr_with_event) { create(:merge_request) } + let!(:merged_event) { create(:event, :merged, target: mr_with_event) } + let!(:closed_event) { create(:event, :closed, target: mr_with_event) } + + before do + # Make sure no metrics are created and kept through after_* callbacks. + mr_with_event.metrics.destroy! + end + + it 'inserts metrics and updates closed and merged events' do + subject.perform(mr_with_event.id, mr_with_event.id) + + mr_with_event.reload + + expect(mr_with_event.metrics).to have_attributes(latest_closed_by_id: closed_event.author_id, + merged_by_id: merged_event.author_id) + expect(mr_with_event.metrics.latest_closed_at.to_s).to eq(closed_event.updated_at.to_s) + end + end + + describe '#insert_metrics_for_range' do + let!(:mrs_without_metrics) { create_list(:merge_request, 3) } + let!(:mrs_with_metrics) { create_list(:merge_request, 2) } + + before do + # Make sure no metrics are created and kept through after_* callbacks. + mrs_without_metrics.each { |m| m.metrics.destroy! } + end + + it 'inserts merge_request_metrics for merge_requests without one' do + expect { subject.insert_metrics_for_range(MergeRequest.first.id, MergeRequest.last.id) } + .to change(MergeRequest::Metrics, :count).from(2).to(5) + + mrs_without_metrics.each do |mr_without_metrics| + expect(mr_without_metrics.reload.metrics).to be_present + end + end + + it 'does not inserts merge_request_metrics for MRs out of given range' do + expect { subject.insert_metrics_for_range(mrs_with_metrics.first.id, mrs_with_metrics.last.id) } + .not_to change(MergeRequest::Metrics, :count).from(2) + end + end + + describe '#update_metrics_with_events_data' do + context 'closed events data update' do + let(:users) { create_list(:user, 3) } + let(:mrs_with_event) { create_list(:merge_request, 3) } + + before do + create_list(:event, 2, :closed, author: users.first, target: mrs_with_event.first) + create_list(:event, 3, :closed, author: users.second, target: mrs_with_event.second) + create(:event, :closed, author: users.third, target: mrs_with_event.third) + end + + it 'migrates multiple MR metrics with closed event data' do + mr_without_event = create(:merge_request) + create(:event, :merged) + + subject.update_metrics_with_events_data(mrs_with_event.first.id, mrs_with_event.last.id) + + mrs_with_event.each do |mr_with_event| + latest_event = Event.where(action: 3, target: mr_with_event).last + + mr_with_event.metrics.reload + + expect(mr_with_event.metrics.latest_closed_by).to eq(latest_event.author) + expect(mr_with_event.metrics.latest_closed_at.to_s).to eq(latest_event.updated_at.to_s) + end + + expect(mr_without_event.metrics.reload).to have_attributes(latest_closed_by_id: nil, + latest_closed_at: nil) + end + + it 'does not updates metrics out of given range' do + out_of_range_mr = create(:merge_request) + create(:event, :closed, author: users.last, target: out_of_range_mr) + + expect { subject.perform(mrs_with_event.first.id, mrs_with_event.second.id) } + .not_to change { out_of_range_mr.metrics.reload.merged_by } + .from(nil) + end + end + + context 'merged events data update' do + let(:users) { create_list(:user, 3) } + let(:mrs_with_event) { create_list(:merge_request, 3) } + + before do + create_list(:event, 2, :merged, author: users.first, target: mrs_with_event.first) + create_list(:event, 3, :merged, author: users.second, target: mrs_with_event.second) + create(:event, :merged, author: users.third, target: mrs_with_event.third) + end + + it 'migrates multiple MR metrics with merged event data' do + mr_without_event = create(:merge_request) + create(:event, :merged) + + subject.update_metrics_with_events_data(mrs_with_event.first.id, mrs_with_event.last.id) + + mrs_with_event.each do |mr_with_event| + latest_event = Event.where(action: Event::MERGED, target: mr_with_event).last + + expect(mr_with_event.metrics.reload.merged_by).to eq(latest_event.author) + end + + expect(mr_without_event.metrics.reload).to have_attributes(merged_by_id: nil) + end + + it 'does not updates metrics out of given range' do + out_of_range_mr = create(:merge_request) + create(:event, :merged, author: users.last, target: out_of_range_mr) + + expect { subject.perform(mrs_with_event.first.id, mrs_with_event.second.id) } + .not_to change { out_of_range_mr.metrics.reload.merged_by } + .from(nil) + end + end + end +end diff --git a/spec/lib/gitlab/bare_repository_import/importer_spec.rb b/spec/lib/gitlab/bare_repository_import/importer_spec.rb index 8a83e446935..b5d86df09d2 100644 --- a/spec/lib/gitlab/bare_repository_import/importer_spec.rb +++ b/spec/lib/gitlab/bare_repository_import/importer_spec.rb @@ -68,8 +68,14 @@ describe Gitlab::BareRepositoryImport::Importer, repository: true do expect(Project.find_by_full_path(project_path)).not_to be_nil end + it 'does not schedule an import' do + expect_any_instance_of(Project).not_to receive(:import_schedule) + + importer.create_project_if_needed + end + it 'creates the Git repo in disk' do - FileUtils.mkdir_p(File.join(base_dir, "#{project_path}.git")) + create_bare_repository("#{project_path}.git") importer.create_project_if_needed @@ -124,13 +130,14 @@ describe Gitlab::BareRepositoryImport::Importer, repository: true do end it 'creates the Git repo in disk' do - FileUtils.mkdir_p(File.join(base_dir, "#{project_path}.git")) + create_bare_repository("#{project_path}.git") importer.create_project_if_needed project = Project.find_by_full_path("#{admin.full_path}/#{project_path}") expect(File).to exist(File.join(project.repository_storage_path, project.disk_path + '.git')) + expect(File).to exist(File.join(project.repository_storage_path, project.disk_path + '.wiki.git')) end it 'moves an existing project to the correct path' do @@ -158,8 +165,11 @@ describe Gitlab::BareRepositoryImport::Importer, repository: true do it_behaves_like 'importing a repository' it 'creates the Wiki git repo in disk' do - FileUtils.mkdir_p(File.join(base_dir, "#{project_path}.git")) - FileUtils.mkdir_p(File.join(base_dir, "#{project_path}.wiki.git")) + create_bare_repository("#{project_path}.git") + create_bare_repository("#{project_path}.wiki.git") + + expect(Projects::CreateService).to receive(:new).with(admin, hash_including(skip_wiki: true, + import_type: 'bare_repository')).and_call_original importer.create_project_if_needed @@ -182,4 +192,9 @@ describe Gitlab::BareRepositoryImport::Importer, repository: true do end end end + + def create_bare_repository(project_path) + repo_path = File.join(base_dir, project_path) + Gitlab::Git::Repository.create(repo_path, bare: true) + end end diff --git a/spec/lib/gitlab/bare_repository_import/repository_spec.rb b/spec/lib/gitlab/bare_repository_import/repository_spec.rb index 61b73abcba4..9f42cf1dfca 100644 --- a/spec/lib/gitlab/bare_repository_import/repository_spec.rb +++ b/spec/lib/gitlab/bare_repository_import/repository_spec.rb @@ -1,58 +1,122 @@ require 'spec_helper' describe ::Gitlab::BareRepositoryImport::Repository do - let(:project_repo_path) { described_class.new('/full/path/', '/full/path/to/repo.git') } + context 'legacy storage' do + subject { described_class.new('/full/path/', '/full/path/to/repo.git') } - it 'stores the repo path' do - expect(project_repo_path.repo_path).to eq('/full/path/to/repo.git') - end + it 'stores the repo path' do + expect(subject.repo_path).to eq('/full/path/to/repo.git') + end - it 'stores the group path' do - expect(project_repo_path.group_path).to eq('to') - end + it 'stores the group path' do + expect(subject.group_path).to eq('to') + end - it 'stores the project name' do - expect(project_repo_path.project_name).to eq('repo') - end + it 'stores the project name' do + expect(subject.project_name).to eq('repo') + end - it 'stores the wiki path' do - expect(project_repo_path.wiki_path).to eq('/full/path/to/repo.wiki.git') - end + it 'stores the wiki path' do + expect(subject.wiki_path).to eq('/full/path/to/repo.wiki.git') + end + + describe '#processable?' do + it 'returns false if it is a wiki' do + subject = described_class.new('/full/path/', '/full/path/to/a/b/my.wiki.git') + + expect(subject).not_to be_processable + end + + it 'returns true if group path is missing' do + subject = described_class.new('/full/path/', '/full/path/repo.git') - describe '#wiki?' do - it 'returns true if it is a wiki' do - wiki_path = described_class.new('/full/path/', '/full/path/to/a/b/my.wiki.git') + expect(subject).to be_processable + end - expect(wiki_path.wiki?).to eq(true) + it 'returns true when group path and project name are present' do + expect(subject).to be_processable + end end - it 'returns false if it is not a wiki' do - expect(project_repo_path.wiki?).to eq(false) + describe '#project_full_path' do + it 'returns the project full path with trailing slash in the root path' do + expect(subject.project_full_path).to eq('to/repo') + end + + it 'returns the project full path with no trailing slash in the root path' do + subject = described_class.new('/full/path', '/full/path/to/repo.git') + + expect(subject.project_full_path).to eq('to/repo') + end end end - describe '#hashed?' do - it 'returns true if it is a hashed folder' do - path = described_class.new('/full/path/', '/full/path/@hashed/my.repo.git') + context 'hashed storage' do + let(:gitlab_shell) { Gitlab::Shell.new } + let(:repository_storage) { 'default' } + let(:root_path) { Gitlab.config.repositories.storages[repository_storage]['path'] } + let(:hash) { '6b86b273ff34fce19d6b804eff5a3f5747ada4eaa22f1d49c01e52ddb7875b4b' } + let(:hashed_path) { "@hashed/6b/86/6b86b273ff34fce19d6b804eff5a3f5747ada4eaa22f1d49c01e52ddb7875b4b" } + let(:repo_path) { File.join(root_path, "#{hashed_path}.git") } + let(:wiki_path) { File.join(root_path, "#{hashed_path}.wiki.git") } - expect(path.hashed?).to eq(true) + before do + gitlab_shell.add_repository(repository_storage, hashed_path) + repository = Rugged::Repository.new(repo_path) + repository.config['gitlab.fullpath'] = 'to/repo' end - it 'returns false if it is not a hashed folder' do - expect(project_repo_path.hashed?).to eq(false) + after do + gitlab_shell.remove_repository(root_path, hashed_path) end - end - describe '#project_full_path' do - it 'returns the project full path' do - expect(project_repo_path.repo_path).to eq('/full/path/to/repo.git') - expect(project_repo_path.project_full_path).to eq('to/repo') + subject { described_class.new(root_path, repo_path) } + + it 'stores the repo path' do + expect(subject.repo_path).to eq(repo_path) + end + + it 'stores the wiki path' do + expect(subject.wiki_path).to eq(wiki_path) + end + + it 'reads the group path from .git/config' do + expect(subject.group_path).to eq('to') + end + + it 'reads the project name from .git/config' do + expect(subject.project_name).to eq('repo') end - it 'with no trailing slash in the root path' do - repo_path = described_class.new('/full/path', '/full/path/to/repo.git') + describe '#processable?' do + it 'returns false if it is a wiki' do + subject = described_class.new(root_path, wiki_path) + + expect(subject).not_to be_processable + end + + it 'returns false when group and project name are missing' do + repository = Rugged::Repository.new(repo_path) + repository.config.delete('gitlab.fullpath') + + expect(subject).not_to be_processable + end + + it 'returns true when group path and project name are present' do + expect(subject).to be_processable + end + end + + describe '#project_full_path' do + it 'returns the project full path with trailing slash in the root path' do + expect(subject.project_full_path).to eq('to/repo') + end + + it 'returns the project full path with no trailing slash in the root path' do + subject = described_class.new(root_path[0...-1], repo_path) - expect(repo_path.project_full_path).to eq('to/repo') + expect(subject.project_full_path).to eq('to/repo') + end end end end diff --git a/spec/lib/gitlab/checks/project_moved_spec.rb b/spec/lib/gitlab/checks/project_moved_spec.rb index fa1575e2177..f90c2d6aded 100644 --- a/spec/lib/gitlab/checks/project_moved_spec.rb +++ b/spec/lib/gitlab/checks/project_moved_spec.rb @@ -35,6 +35,12 @@ describe Gitlab::Checks::ProjectMoved, :clean_gitlab_redis_shared_state do project_moved = described_class.new(project, user, 'foo/bar', 'http') expect(project_moved.add_redirect_message).to eq("OK") end + + it 'should handle anonymous clones' do + project_moved = described_class.new(project, nil, 'foo/bar', 'http') + + expect(project_moved.add_redirect_message).to eq(nil) + end end describe '#redirect_message' do diff --git a/spec/lib/gitlab/ci/ansi2html_spec.rb b/spec/lib/gitlab/ci/ansi2html_spec.rb index 33540eab5d6..05e2d94cbd6 100644 --- a/spec/lib/gitlab/ci/ansi2html_spec.rb +++ b/spec/lib/gitlab/ci/ansi2html_spec.rb @@ -120,6 +120,10 @@ describe Gitlab::Ci::Ansi2html do expect(convert_html("\e[48;5;240mHello")).to eq('<span class="xterm-bg-240">Hello</span>') end + it "can print 256 xterm fg bold colors" do + expect(convert_html("\e[38;5;16;1mHello")).to eq('<span class="xterm-fg-16 term-bold">Hello</span>') + end + it "can print 256 xterm bg colors on normal magenta foreground" do expect(convert_html("\e[48;5;16;35mHello")).to eq('<span class="term-fg-magenta xterm-bg-16">Hello</span>') end diff --git a/spec/lib/gitlab/ci/status/build/common_spec.rb b/spec/lib/gitlab/ci/status/build/common_spec.rb index 03d1f46b517..2cce7a23ea7 100644 --- a/spec/lib/gitlab/ci/status/build/common_spec.rb +++ b/spec/lib/gitlab/ci/status/build/common_spec.rb @@ -18,7 +18,7 @@ describe Gitlab::Ci::Status::Build::Common do describe '#has_details?' do context 'when user has access to read build' do before do - project.team << [user, :developer] + project.add_developer(user) end it { is_expected.to have_details } diff --git a/spec/lib/gitlab/ci/status/external/common_spec.rb b/spec/lib/gitlab/ci/status/external/common_spec.rb index b38fbee2486..40871f86568 100644 --- a/spec/lib/gitlab/ci/status/external/common_spec.rb +++ b/spec/lib/gitlab/ci/status/external/common_spec.rb @@ -29,7 +29,7 @@ describe Gitlab::Ci::Status::External::Common do describe '#has_details?' do context 'when user has access to read commit status' do before do - project.team << [user, :developer] + project.add_developer(user) end it { is_expected.to have_details } diff --git a/spec/lib/gitlab/ci/status/external/factory_spec.rb b/spec/lib/gitlab/ci/status/external/factory_spec.rb index c96fd53e730..529d02a3e39 100644 --- a/spec/lib/gitlab/ci/status/external/factory_spec.rb +++ b/spec/lib/gitlab/ci/status/external/factory_spec.rb @@ -8,7 +8,7 @@ describe Gitlab::Ci::Status::External::Factory do let(:external_url) { 'http://gitlab.com/status' } before do - project.team << [user, :developer] + project.add_developer(user) end context 'when external status has a simple core status' do diff --git a/spec/lib/gitlab/ci/status/pipeline/common_spec.rb b/spec/lib/gitlab/ci/status/pipeline/common_spec.rb index 4a5b45e7cae..57df8325635 100644 --- a/spec/lib/gitlab/ci/status/pipeline/common_spec.rb +++ b/spec/lib/gitlab/ci/status/pipeline/common_spec.rb @@ -18,7 +18,7 @@ describe Gitlab::Ci::Status::Pipeline::Common do describe '#has_details?' do context 'when user has access to read pipeline' do before do - project.team << [user, :developer] + project.add_developer(user) end it { is_expected.to have_details } diff --git a/spec/lib/gitlab/ci/status/pipeline/factory_spec.rb b/spec/lib/gitlab/ci/status/pipeline/factory_spec.rb index dd754b849b2..defb3fdc0df 100644 --- a/spec/lib/gitlab/ci/status/pipeline/factory_spec.rb +++ b/spec/lib/gitlab/ci/status/pipeline/factory_spec.rb @@ -7,7 +7,7 @@ describe Gitlab::Ci::Status::Pipeline::Factory do let(:factory) { described_class.new(pipeline, user) } before do - project.team << [user, :developer] + project.add_developer(user) end context 'when pipeline has a core status' do diff --git a/spec/lib/gitlab/ci/status/stage/common_spec.rb b/spec/lib/gitlab/ci/status/stage/common_spec.rb index f5f03ac0395..6ec35f8da7e 100644 --- a/spec/lib/gitlab/ci/status/stage/common_spec.rb +++ b/spec/lib/gitlab/ci/status/stage/common_spec.rb @@ -27,7 +27,7 @@ describe Gitlab::Ci::Status::Stage::Common do context 'when user has permission to read pipeline' do before do - project.team << [user, :master] + project.add_master(user) end it 'has details' do diff --git a/spec/lib/gitlab/ci/status/stage/factory_spec.rb b/spec/lib/gitlab/ci/status/stage/factory_spec.rb index 432b07e4902..dee4f4efd1b 100644 --- a/spec/lib/gitlab/ci/status/stage/factory_spec.rb +++ b/spec/lib/gitlab/ci/status/stage/factory_spec.rb @@ -18,7 +18,7 @@ describe Gitlab::Ci::Status::Stage::Factory do end before do - project.team << [user, :developer] + project.add_developer(user) end context 'when stage has a core status' do diff --git a/spec/lib/gitlab/closing_issue_extractor_spec.rb b/spec/lib/gitlab/closing_issue_extractor_spec.rb index ef7d766a13d..8c79ef54c6c 100644 --- a/spec/lib/gitlab/closing_issue_extractor_spec.rb +++ b/spec/lib/gitlab/closing_issue_extractor_spec.rb @@ -13,8 +13,8 @@ describe Gitlab::ClosingIssueExtractor do subject { described_class.new(project, project.creator) } before do - project.team << [project.creator, :developer] - project2.team << [project.creator, :master] + project.add_developer(project.creator) + project2.add_master(project.creator) end describe "#closed_by_message" do @@ -297,7 +297,7 @@ describe Gitlab::ClosingIssueExtractor do context 'with an external issue tracker reference' do it 'extracts the referenced issue' do jira_project = create(:jira_project, name: 'JIRA_EXT1') - jira_project.team << [jira_project.creator, :master] + jira_project.add_master(jira_project.creator) jira_issue = ExternalIssue.new("#{jira_project.name}-1", project: jira_project) closing_issue_extractor = described_class.new(jira_project, jira_project.creator) message = "Resolve #{jira_issue.to_reference}" diff --git a/spec/lib/gitlab/cycle_analytics/permissions_spec.rb b/spec/lib/gitlab/cycle_analytics/permissions_spec.rb index 2a0dd7be439..6de4bd3dc7c 100644 --- a/spec/lib/gitlab/cycle_analytics/permissions_spec.rb +++ b/spec/lib/gitlab/cycle_analytics/permissions_spec.rb @@ -38,7 +38,7 @@ describe Gitlab::CycleAnalytics::Permissions do context 'user is master' do before do - project.team << [user, :master] + project.add_master(user) end it 'has permissions to issue stage' do @@ -72,7 +72,7 @@ describe Gitlab::CycleAnalytics::Permissions do context 'user has no build permissions' do before do - project.team << [user, :guest] + project.add_guest(user) end it 'has permissions to issue stage' do @@ -90,7 +90,7 @@ describe Gitlab::CycleAnalytics::Permissions do context 'user has no merge request permissions' do before do - project.team << [user, :guest] + project.add_guest(user) end it 'has permissions to issue stage' do @@ -108,7 +108,7 @@ describe Gitlab::CycleAnalytics::Permissions do context 'user has no issue permissions' do before do - project.team << [user, :developer] + project.add_developer(user) project.project_feature.update_attribute(:issues_access_level, ProjectFeature::DISABLED) end diff --git a/spec/lib/gitlab/database/migration_helpers_spec.rb b/spec/lib/gitlab/database/migration_helpers_spec.rb index 664ba0f7234..7727a1d81b1 100644 --- a/spec/lib/gitlab/database/migration_helpers_spec.rb +++ b/spec/lib/gitlab/database/migration_helpers_spec.rb @@ -902,7 +902,7 @@ describe Gitlab::Database::MigrationHelpers do describe '#check_trigger_permissions!' do it 'does nothing when the user has the correct permissions' do expect { model.check_trigger_permissions!('users') } - .not_to raise_error(RuntimeError) + .not_to raise_error end it 'raises RuntimeError when the user does not have the correct permissions' do @@ -1036,4 +1036,93 @@ describe Gitlab::Database::MigrationHelpers do end end end + + describe '#change_column_type_using_background_migration' do + let!(:issue) { create(:issue) } + + let(:issue_model) do + Class.new(ActiveRecord::Base) do + self.table_name = 'issues' + include EachBatch + end + end + + it 'changes the type of a column using a background migration' do + expect(model) + .to receive(:add_column) + .with('issues', 'closed_at_for_type_change', :datetime_with_timezone) + + expect(model) + .to receive(:install_rename_triggers) + .with('issues', :closed_at, 'closed_at_for_type_change') + + expect(BackgroundMigrationWorker) + .to receive(:perform_in) + .ordered + .with( + 10.minutes, + 'CopyColumn', + ['issues', :closed_at, 'closed_at_for_type_change', issue.id, issue.id] + ) + + expect(BackgroundMigrationWorker) + .to receive(:perform_in) + .ordered + .with( + 1.hour + 10.minutes, + 'CleanupConcurrentTypeChange', + ['issues', :closed_at, 'closed_at_for_type_change'] + ) + + expect(Gitlab::BackgroundMigration) + .to receive(:steal) + .ordered + .with('CopyColumn') + + expect(Gitlab::BackgroundMigration) + .to receive(:steal) + .ordered + .with('CleanupConcurrentTypeChange') + + model.change_column_type_using_background_migration( + issue_model.all, + :closed_at, + :datetime_with_timezone + ) + end + end + + describe '#perform_background_migration_inline?' do + it 'returns true in a test environment' do + allow(Rails.env) + .to receive(:test?) + .and_return(true) + + expect(model.perform_background_migration_inline?).to eq(true) + end + + it 'returns true in a development environment' do + allow(Rails.env) + .to receive(:test?) + .and_return(false) + + allow(Rails.env) + .to receive(:development?) + .and_return(true) + + expect(model.perform_background_migration_inline?).to eq(true) + end + + it 'returns false in a production environment' do + allow(Rails.env) + .to receive(:test?) + .and_return(false) + + allow(Rails.env) + .to receive(:development?) + .and_return(false) + + expect(model.perform_background_migration_inline?).to eq(false) + end + end end diff --git a/spec/lib/gitlab/diff/file_spec.rb b/spec/lib/gitlab/diff/file_spec.rb index ff9acfd08b9..9204ea37963 100644 --- a/spec/lib/gitlab/diff/file_spec.rb +++ b/spec/lib/gitlab/diff/file_spec.rb @@ -431,4 +431,29 @@ describe Gitlab::Diff::File do end end end + + context 'when neither blob exists' do + let(:blank_diff_refs) { Gitlab::Diff::DiffRefs.new(base_sha: Gitlab::Git::BLANK_SHA, head_sha: Gitlab::Git::BLANK_SHA) } + let(:diff_file) { described_class.new(diff, diff_refs: blank_diff_refs, repository: project.repository) } + + describe '#blob' do + it 'returns a concrete nil so it can be used in boolean expressions' do + actual = diff_file.blob && true + + expect(actual).to be_nil + end + end + + describe '#binary?' do + it 'returns false' do + expect(diff_file).not_to be_binary + end + end + + describe '#size' do + it 'returns zero' do + expect(diff_file.size).to be_zero + end + end + end end diff --git a/spec/lib/gitlab/email/handler/create_note_handler_spec.rb b/spec/lib/gitlab/email/handler/create_note_handler_spec.rb index d0fa16ce4d1..031efcf1291 100644 --- a/spec/lib/gitlab/email/handler/create_note_handler_spec.rb +++ b/spec/lib/gitlab/email/handler/create_note_handler_spec.rb @@ -66,7 +66,7 @@ describe Gitlab::Email::Handler::CreateNoteHandler do context 'and current user can update noteable' do before do - project.team << [user, :developer] + project.add_developer(user) end it 'does not raise an error' do @@ -99,7 +99,7 @@ describe Gitlab::Email::Handler::CreateNoteHandler do context 'and current user can update noteable' do before do - project.team << [user, :developer] + project.add_developer(user) end it 'post a note and updates the noteable' do diff --git a/spec/lib/gitlab/encoding_helper_spec.rb b/spec/lib/gitlab/encoding_helper_spec.rb index f6e5c55240f..87ec2698fc1 100644 --- a/spec/lib/gitlab/encoding_helper_spec.rb +++ b/spec/lib/gitlab/encoding_helper_spec.rb @@ -145,4 +145,18 @@ describe Gitlab::EncodingHelper do end end end + + describe 'encode_binary' do + [ + [nil, ""], + ["", ""], + [" ", " "], + %w(a1 a1), + ["编码", "\xE7\xBC\x96\xE7\xA0\x81".b] + ].each do |input, result| + it "encodes #{input.inspect} to #{result.inspect}" do + expect(ext_class.encode_binary(input)).to eq(result) + end + end + end end diff --git a/spec/lib/gitlab/gfm/reference_rewriter_spec.rb b/spec/lib/gitlab/gfm/reference_rewriter_spec.rb index 7dc06c90078..4d2f08f95fc 100644 --- a/spec/lib/gitlab/gfm/reference_rewriter_spec.rb +++ b/spec/lib/gitlab/gfm/reference_rewriter_spec.rb @@ -10,7 +10,7 @@ describe Gitlab::Gfm::ReferenceRewriter do let(:text) { 'some text' } before do - old_project.team << [user, :reporter] + old_project.add_reporter(user) end describe '#rewrite' do diff --git a/spec/lib/gitlab/git/commit_spec.rb b/spec/lib/gitlab/git/commit_spec.rb index 5ed639543e0..6d35734d306 100644 --- a/spec/lib/gitlab/git/commit_spec.rb +++ b/spec/lib/gitlab/git/commit_spec.rb @@ -428,6 +428,11 @@ describe Gitlab::Git::Commit, seed_helper: true do subject { super().deletions } it { is_expected.to eq(6) } end + + describe '#total' do + subject { super().total } + it { is_expected.to eq(17) } + end end describe '#stats with gitaly on' do diff --git a/spec/lib/gitlab/git/gitlab_projects_spec.rb b/spec/lib/gitlab/git/gitlab_projects_spec.rb index 24da9589458..a798b188a0d 100644 --- a/spec/lib/gitlab/git/gitlab_projects_spec.rb +++ b/spec/lib/gitlab/git/gitlab_projects_spec.rb @@ -243,7 +243,6 @@ describe Gitlab::Git::GitlabProjects do let(:dest_repos_path) { tmp_repos_path } let(:dest_repo_name) { File.join('@hashed', 'aa', 'bb', 'xyz.git') } let(:dest_repo) { File.join(dest_repos_path, dest_repo_name) } - let(:dest_namespace) { File.dirname(dest_repo) } subject { gl_projects.fork_repository(dest_repos_path, dest_repo_name) } @@ -255,37 +254,64 @@ describe Gitlab::Git::GitlabProjects do FileUtils.rm_rf(dest_repos_path) end - it 'forks the repository' do - message = "Forking repository from <#{tmp_repo_path}> to <#{dest_repo}>." - expect(logger).to receive(:info).with(message) + shared_examples 'forking a repository' do + it 'forks the repository' do + is_expected.to be_truthy - is_expected.to be_truthy + expect(File.exist?(dest_repo)).to be_truthy + expect(File.exist?(File.join(dest_repo, 'hooks', 'pre-receive'))).to be_truthy + expect(File.exist?(File.join(dest_repo, 'hooks', 'post-receive'))).to be_truthy + end + + it 'does not fork if a project of the same name already exists' do + # create a fake project at the intended destination + FileUtils.mkdir_p(dest_repo) - expect(File.exist?(dest_repo)).to be_truthy - expect(File.exist?(File.join(dest_repo, 'hooks', 'pre-receive'))).to be_truthy - expect(File.exist?(File.join(dest_repo, 'hooks', 'post-receive'))).to be_truthy + is_expected.to be_falsy + end end - it 'does not fork if a project of the same name already exists' do - # create a fake project at the intended destination - FileUtils.mkdir_p(dest_repo) + context 'when Gitaly fork_repository feature is enabled' do + it_behaves_like 'forking a repository' + end - # trying to fork again should fail as the repo already exists - message = "fork-repository failed: destination repository <#{dest_repo}> already exists." - expect(logger).to receive(:error).with(message) + context 'when Gitaly fork_repository feature is disabled', :disable_gitaly do + it_behaves_like 'forking a repository' - is_expected.to be_falsy - end + # We seem to be stuck to having only one working Gitaly storage in tests, changing + # that is not very straight-forward so I'm leaving this test here for now till + # https://gitlab.com/gitlab-org/gitlab-ce/issues/41393 is fixed. + context 'different storages' do + let(:dest_repos_path) { File.join(File.dirname(tmp_repos_path), 'alternative') } - context 'different storages' do - let(:dest_repos_path) { File.join(File.dirname(tmp_repos_path), 'alternative') } + it 'forks the repo' do + is_expected.to be_truthy - it 'forks the repo' do - is_expected.to be_truthy + expect(File.exist?(dest_repo)).to be_truthy + expect(File.exist?(File.join(dest_repo, 'hooks', 'pre-receive'))).to be_truthy + expect(File.exist?(File.join(dest_repo, 'hooks', 'post-receive'))).to be_truthy + end + end - expect(File.exist?(dest_repo)).to be_truthy - expect(File.exist?(File.join(dest_repo, 'hooks', 'pre-receive'))).to be_truthy - expect(File.exist?(File.join(dest_repo, 'hooks', 'post-receive'))).to be_truthy + describe 'log messages' do + describe 'successful fork' do + it do + message = "Forking repository from <#{tmp_repo_path}> to <#{dest_repo}>." + expect(logger).to receive(:info).with(message) + + subject + end + end + + describe 'failed fork due existing destination' do + it do + FileUtils.mkdir_p(dest_repo) + message = "fork-repository failed: destination repository <#{dest_repo}> already exists." + expect(logger).to receive(:error).with(message) + + subject + end + end end end end diff --git a/spec/lib/gitlab/git/repository_spec.rb b/spec/lib/gitlab/git/repository_spec.rb index 03a9cc488ca..faccc2c8e00 100644 --- a/spec/lib/gitlab/git/repository_spec.rb +++ b/spec/lib/gitlab/git/repository_spec.rb @@ -18,9 +18,10 @@ describe Gitlab::Git::Repository, seed_helper: true do end let(:repository) { Gitlab::Git::Repository.new('default', TEST_REPO_PATH, '') } + let(:storage_path) { TestEnv.repos_path } describe '.create_hooks' do - let(:repo_path) { File.join(TestEnv.repos_path, 'hook-test.git') } + let(:repo_path) { File.join(storage_path, 'hook-test.git') } let(:hooks_dir) { File.join(repo_path, 'hooks') } let(:target_hooks_dir) { Gitlab.config.gitlab_shell.hooks_path } let(:existing_target) { File.join(repo_path, 'foobar') } @@ -645,7 +646,7 @@ describe Gitlab::Git::Repository, seed_helper: true do end after do - Gitlab::Shell.new.remove_repository(TestEnv.repos_path, 'my_project') + Gitlab::Shell.new.remove_repository(storage_path, 'my_project') end it 'fetches a repository as a mirror remote' do @@ -701,21 +702,6 @@ describe Gitlab::Git::Repository, seed_helper: true do end end - describe '#remote_exists?' do - before(:all) do - @repo = Gitlab::Git::Repository.new('default', TEST_MUTABLE_REPO_PATH, '') - @repo.add_remote("new_remote", SeedHelper::GITLAB_GIT_TEST_REPO_URL) - end - - it 'returns true for an existing remote' do - expect(@repo.remote_exists?('new_remote')).to eq(true) - end - - it 'returns false for a non-existing remote' do - expect(@repo.remote_exists?('foo')).to eq(false) - end - end - describe "#log" do let(:commit_with_old_name) do Gitlab::Git::Commit.decorate(repository, @commit_with_old_name_id) @@ -1030,7 +1016,7 @@ describe Gitlab::Git::Repository, seed_helper: true do shared_examples 'extended commit counting' do context 'with after timestamp' do it 'returns the number of commits after timestamp' do - options = { ref: 'master', limit: nil, after: Time.iso8601('2013-03-03T20:15:01+00:00') } + options = { ref: 'master', after: Time.iso8601('2013-03-03T20:15:01+00:00') } expect(repository.count_commits(options)).to eq(25) end @@ -1038,7 +1024,7 @@ describe Gitlab::Git::Repository, seed_helper: true do context 'with before timestamp' do it 'returns the number of commits before timestamp' do - options = { ref: 'feature', limit: nil, before: Time.iso8601('2015-03-03T20:15:01+00:00') } + options = { ref: 'feature', before: Time.iso8601('2015-03-03T20:15:01+00:00') } expect(repository.count_commits(options)).to eq(9) end @@ -1046,11 +1032,19 @@ describe Gitlab::Git::Repository, seed_helper: true do context 'with path' do it 'returns the number of commits with path ' do - options = { ref: 'master', limit: nil, path: "encoding" } + options = { ref: 'master', path: "encoding" } expect(repository.count_commits(options)).to eq(2) end end + + context 'with max_count' do + it 'returns the number of commits up to the passed limit' do + options = { ref: 'master', max_count: 10, after: Time.iso8601('2013-03-03T20:15:01+00:00') } + + expect(repository.count_commits(options)).to eq(10) + end + end end context 'when Gitaly count_commits feature is enabled' do @@ -1670,7 +1664,7 @@ describe Gitlab::Git::Repository, seed_helper: true do let(:branch_name) { "to-be-deleted-soon" } before do - project.team << [user, :developer] + project.add_developer(user) repository.create_branch(branch_name) end @@ -1734,6 +1728,20 @@ describe Gitlab::Git::Repository, seed_helper: true do expect(result.repo_created).to eq(false) expect(result.branch_created).to eq(false) end + + it 'returns nil if there was a concurrent branch update' do + concurrent_update_id = '33f3729a45c02fc67d00adb1b8bca394b0e761d9' + result = repository.merge(user, source_sha, target_branch, 'Test merge') do + # This ref update should make the merge fail + repository.write_ref(Gitlab::Git::BRANCH_REF_PREFIX + target_branch, concurrent_update_id) + end + + # This 'nil' signals that the merge was not applied + expect(result).to be_nil + + # Our concurrent ref update should not have been undone + expect(repository.find_branch(target_branch).target).to eq(concurrent_update_id) + end end context 'with gitaly' do @@ -1843,6 +1851,166 @@ describe Gitlab::Git::Repository, seed_helper: true do end end + describe 'remotes' do + let(:repository) do + Gitlab::Git::Repository.new('default', TEST_MUTABLE_REPO_PATH, '') + end + let(:remote_name) { 'my-remote' } + + after do + ensure_seeds + end + + describe '#add_remote' do + let(:url) { 'http://my-repo.git' } + let(:mirror_refmap) { '+refs/*:refs/*' } + + it 'creates a new remote via Gitaly' do + expect_any_instance_of(Gitlab::GitalyClient::RemoteService) + .to receive(:add_remote).with(remote_name, url, mirror_refmap) + + repository.add_remote(remote_name, url, mirror_refmap: mirror_refmap) + end + + context 'with Gitaly disabled', :skip_gitaly_mock do + it 'creates a new remote via Rugged' do + expect_any_instance_of(Rugged::RemoteCollection).to receive(:create) + .with(remote_name, url) + expect_any_instance_of(Rugged::Config).to receive(:[]=) + .with("remote.#{remote_name}.mirror", true) + expect_any_instance_of(Rugged::Config).to receive(:[]=) + .with("remote.#{remote_name}.prune", true) + expect_any_instance_of(Rugged::Config).to receive(:[]=) + .with("remote.#{remote_name}.fetch", mirror_refmap) + + repository.add_remote(remote_name, url, mirror_refmap: mirror_refmap) + end + end + end + + describe '#remove_remote' do + it 'removes the remote via Gitaly' do + expect_any_instance_of(Gitlab::GitalyClient::RemoteService) + .to receive(:remove_remote).with(remote_name) + + repository.remove_remote(remote_name) + end + + context 'with Gitaly disabled', :skip_gitaly_mock do + it 'removes the remote via Rugged' do + expect_any_instance_of(Rugged::RemoteCollection).to receive(:delete) + .with(remote_name) + + repository.remove_remote(remote_name) + end + end + end + end + + describe '#gitlab_projects' do + subject { repository.gitlab_projects } + + it { expect(subject.shard_path).to eq(storage_path) } + it { expect(subject.repository_relative_path).to eq(repository.relative_path) } + end + + context 'gitlab_projects commands' do + let(:gitlab_projects) { repository.gitlab_projects } + let(:timeout) { Gitlab.config.gitlab_shell.git_timeout } + + describe '#push_remote_branches' do + subject do + repository.push_remote_branches('downstream-remote', ['master']) + end + + it 'executes the command' do + expect(gitlab_projects).to receive(:push_branches) + .with('downstream-remote', timeout, true, ['master']) + .and_return(true) + + is_expected.to be_truthy + end + + it 'raises an error if the command fails' do + allow(gitlab_projects).to receive(:output) { 'error' } + expect(gitlab_projects).to receive(:push_branches) + .with('downstream-remote', timeout, true, ['master']) + .and_return(false) + + expect { subject }.to raise_error(Gitlab::Git::CommandError, 'error') + end + end + + describe '#delete_remote_branches' do + subject do + repository.delete_remote_branches('downstream-remote', ['master']) + end + + it 'executes the command' do + expect(gitlab_projects).to receive(:delete_remote_branches) + .with('downstream-remote', ['master']) + .and_return(true) + + is_expected.to be_truthy + end + + it 'raises an error if the command fails' do + allow(gitlab_projects).to receive(:output) { 'error' } + expect(gitlab_projects).to receive(:delete_remote_branches) + .with('downstream-remote', ['master']) + .and_return(false) + + expect { subject }.to raise_error(Gitlab::Git::CommandError, 'error') + end + end + + describe '#delete_remote_branches' do + subject do + repository.delete_remote_branches('downstream-remote', ['master']) + end + + it 'executes the command' do + expect(gitlab_projects).to receive(:delete_remote_branches) + .with('downstream-remote', ['master']) + .and_return(true) + + is_expected.to be_truthy + end + + it 'raises an error if the command fails' do + allow(gitlab_projects).to receive(:output) { 'error' } + expect(gitlab_projects).to receive(:delete_remote_branches) + .with('downstream-remote', ['master']) + .and_return(false) + + expect { subject }.to raise_error(Gitlab::Git::CommandError, 'error') + end + end + + describe '#delete_remote_branches' do + subject do + repository.delete_remote_branches('downstream-remote', ['master']) + end + + it 'executes the command' do + expect(gitlab_projects).to receive(:delete_remote_branches) + .with('downstream-remote', ['master']) + .and_return(true) + + is_expected.to be_truthy + end + + it 'raises an error if the command fails' do + allow(gitlab_projects).to receive(:output) { 'error' } + expect(gitlab_projects).to receive(:delete_remote_branches) + .with('downstream-remote', ['master']) + .and_return(false) + + expect { subject }.to raise_error(Gitlab::Git::CommandError, 'error') + end + end + end + def create_remote_branch(repository, remote_name, branch_name, source_branch_name) source_branch = repository.branches.find { |branch| branch.name == source_branch_name } rugged = repository.rugged diff --git a/spec/lib/gitlab/git_access_spec.rb b/spec/lib/gitlab/git_access_spec.rb index 2db560c2cec..4290fbb0087 100644 --- a/spec/lib/gitlab/git_access_spec.rb +++ b/spec/lib/gitlab/git_access_spec.rb @@ -275,7 +275,7 @@ describe Gitlab::GitAccess do describe '#check_command_disabled!' do before do - project.team << [user, :master] + project.add_master(user) end context 'over http' do @@ -404,7 +404,7 @@ describe Gitlab::GitAccess do describe 'reporter user' do before do - project.team << [user, :reporter] + project.add_reporter(user) end context 'pull code' do @@ -417,7 +417,7 @@ describe Gitlab::GitAccess do context 'when member of the project' do before do - project.team << [user, :reporter] + project.add_reporter(user) end context 'pull code' do @@ -497,7 +497,7 @@ describe Gitlab::GitAccess do if role == :admin user.update_attribute(:admin, true) else - project.team << [user, role] + project.add_role(user, role) end aggregate_failures do @@ -658,7 +658,7 @@ describe Gitlab::GitAccess do context 'when project is authorized' do before do - project.team << [user, :reporter] + project.add_reporter(user) end it { expect { push_access_check }.to raise_unauthorized(described_class::ERROR_MESSAGES[:upload]) } diff --git a/spec/lib/gitlab/git_access_wiki_spec.rb b/spec/lib/gitlab/git_access_wiki_spec.rb index 1056074264a..186b2d9279d 100644 --- a/spec/lib/gitlab/git_access_wiki_spec.rb +++ b/spec/lib/gitlab/git_access_wiki_spec.rb @@ -18,7 +18,7 @@ describe Gitlab::GitAccessWiki do context 'when user can :create_wiki' do before do create(:protected_branch, name: 'master', project: project) - project.team << [user, :developer] + project.add_developer(user) end subject { access.check('git-receive-pack', changes) } @@ -41,7 +41,7 @@ describe Gitlab::GitAccessWiki do subject { access.check('git-upload-pack', '_any') } before do - project.team << [user, :developer] + project.add_developer(user) end context 'when wiki feature is enabled' do diff --git a/spec/lib/gitlab/gitaly_client/conflicts_service_spec.rb b/spec/lib/gitlab/gitaly_client/conflicts_service_spec.rb new file mode 100644 index 00000000000..b9641de7eda --- /dev/null +++ b/spec/lib/gitlab/gitaly_client/conflicts_service_spec.rb @@ -0,0 +1,90 @@ +require 'spec_helper' + +describe Gitlab::GitalyClient::ConflictsService do + let(:project) { create(:project, :repository) } + let(:target_project) { create(:project, :repository) } + let(:source_repository) { project.repository.raw } + let(:target_repository) { target_project.repository.raw } + let(:target_gitaly_repository) { target_repository.gitaly_repository } + let(:our_commit_oid) { 'f00' } + let(:their_commit_oid) { 'f44' } + let(:client) do + described_class.new(target_repository, our_commit_oid, their_commit_oid) + end + + describe '#list_conflict_files' do + let(:request) do + Gitaly::ListConflictFilesRequest.new( + repository: target_gitaly_repository, our_commit_oid: our_commit_oid, + their_commit_oid: their_commit_oid + ) + end + let(:our_path) { 'our/path' } + let(:their_path) { 'their/path' } + let(:our_mode) { 0744 } + let(:header) do + double(repository: target_gitaly_repository, commit_oid: our_commit_oid, + our_path: our_path, our_mode: 0744, their_path: their_path) + end + let(:response) do + [ + double(files: [double(header: header), double(content: 'foo', header: nil)]), + double(files: [double(content: 'bar', header: nil)]) + ] + end + let(:file) { subject[0] } + + subject { client.list_conflict_files } + + it 'sends an RPC request' do + expect_any_instance_of(Gitaly::ConflictsService::Stub).to receive(:list_conflict_files) + .with(request, kind_of(Hash)).and_return([]) + + subject + end + + it 'forms a Gitlab::Git::ConflictFile collection from the response' do + allow_any_instance_of(Gitaly::ConflictsService::Stub).to receive(:list_conflict_files) + .with(request, kind_of(Hash)).and_return(response) + + expect(subject.size).to be(1) + expect(file.content).to eq('foobar') + expect(file.their_path).to eq(their_path) + expect(file.our_path).to eq(our_path) + expect(file.our_mode).to be(our_mode) + expect(file.repository).to eq(target_repository) + expect(file.commit_oid).to eq(our_commit_oid) + end + end + + describe '#resolve_conflicts' do + let(:user) { create(:user) } + let(:files) do + [{ old_path: 'some/path', new_path: 'some/path', content: '' }] + end + let(:source_branch) { 'master' } + let(:target_branch) { 'feature' } + let(:commit_message) { 'Solving conflicts' } + let(:resolution) do + Gitlab::Git::Conflict::Resolution.new(user, files, commit_message) + end + + subject do + client.resolve_conflicts(source_repository, resolution, source_branch, target_branch) + end + + it 'sends an RPC request' do + expect_any_instance_of(Gitaly::ConflictsService::Stub).to receive(:resolve_conflicts) + .with(kind_of(Enumerator), kind_of(Hash)).and_return(double(resolution_error: "")) + + subject + end + + it 'raises a relevant exception if resolution_error is present' do + expect_any_instance_of(Gitaly::ConflictsService::Stub).to receive(:resolve_conflicts) + .with(kind_of(Enumerator), kind_of(Hash)).and_return(double(resolution_error: "something happened")) + + expect { subject }.to raise_error(Gitlab::Git::Conflict::Resolver::ResolutionError) + end + end +end diff --git a/spec/lib/gitlab/gitaly_client/remote_service_spec.rb b/spec/lib/gitlab/gitaly_client/remote_service_spec.rb new file mode 100644 index 00000000000..69c6f054016 --- /dev/null +++ b/spec/lib/gitlab/gitaly_client/remote_service_spec.rb @@ -0,0 +1,34 @@ +require 'spec_helper' + +describe Gitlab::GitalyClient::RemoteService do + let(:project) { create(:project) } + let(:storage_name) { project.repository_storage } + let(:relative_path) { project.disk_path + '.git' } + let(:remote_name) { 'my-remote' } + let(:client) { described_class.new(project.repository) } + + describe '#add_remote' do + let(:url) { 'http://my-repo.git' } + let(:mirror_refmap) { :all_refs } + + it 'sends an add_remote message' do + expect_any_instance_of(Gitaly::RemoteService::Stub) + .to receive(:add_remote) + .with(gitaly_request_with_path(storage_name, relative_path), kind_of(Hash)) + .and_return(double(:add_remote_response)) + + client.add_remote(remote_name, url, mirror_refmap) + end + end + + describe '#remove_remote' do + it 'sends an remove_remote message and returns the result value' do + expect_any_instance_of(Gitaly::RemoteService::Stub) + .to receive(:remove_remote) + .with(gitaly_request_with_path(storage_name, relative_path), kind_of(Hash)) + .and_return(double(result: true)) + + expect(client.remove_remote(remote_name)).to be(true) + end + end +end diff --git a/spec/lib/gitlab/gitaly_client_spec.rb b/spec/lib/gitlab/gitaly_client_spec.rb index a871ed0df0e..309b7338ef0 100644 --- a/spec/lib/gitlab/gitaly_client_spec.rb +++ b/spec/lib/gitlab/gitaly_client_spec.rb @@ -38,20 +38,6 @@ describe Gitlab::GitalyClient, skip_gitaly_mock: true do end end - describe 'encode' do - [ - [nil, ""], - ["", ""], - [" ", " "], - %w(a1 a1), - ["编码", "\xE7\xBC\x96\xE7\xA0\x81".b] - ].each do |input, result| - it "encodes #{input.inspect} to #{result.inspect}" do - expect(described_class.encode(input)).to eq result - end - end - end - describe 'allow_n_plus_1_calls' do context 'when RequestStore is enabled', :request_store do it 'returns the result of the allow_n_plus_1_calls block' do diff --git a/spec/lib/gitlab/google_code_import/importer_spec.rb b/spec/lib/gitlab/google_code_import/importer_spec.rb index 798ea0bac58..017facd0f5e 100644 --- a/spec/lib/gitlab/google_code_import/importer_spec.rb +++ b/spec/lib/gitlab/google_code_import/importer_spec.rb @@ -15,7 +15,7 @@ describe Gitlab::GoogleCodeImport::Importer do subject { described_class.new(project) } before do - project.team << [project.creator, :master] + project.add_master(project.creator) project.create_import_data(data: import_data) end diff --git a/spec/lib/gitlab/import_export/project_tree_saver_spec.rb b/spec/lib/gitlab/import_export/project_tree_saver_spec.rb index 6243b6ac9f0..6faf3d82981 100644 --- a/spec/lib/gitlab/import_export/project_tree_saver_spec.rb +++ b/spec/lib/gitlab/import_export/project_tree_saver_spec.rb @@ -9,7 +9,7 @@ describe Gitlab::ImportExport::ProjectTreeSaver do let!(:project) { setup_project } before do - project.team << [user, :master] + project.add_master(user) allow_any_instance_of(Gitlab::ImportExport).to receive(:storage_path).and_return(export_path) allow_any_instance_of(MergeRequest).to receive(:source_branch_sha).and_return('ABCD') allow_any_instance_of(MergeRequest).to receive(:target_branch_sha).and_return('DCBA') diff --git a/spec/lib/gitlab/import_export/repo_saver_spec.rb b/spec/lib/gitlab/import_export/repo_saver_spec.rb index e6ad516deef..44f972fe530 100644 --- a/spec/lib/gitlab/import_export/repo_saver_spec.rb +++ b/spec/lib/gitlab/import_export/repo_saver_spec.rb @@ -9,7 +9,7 @@ describe Gitlab::ImportExport::RepoSaver do let(:bundler) { described_class.new(project: project, shared: shared) } before do - project.team << [user, :master] + project.add_master(user) allow_any_instance_of(Gitlab::ImportExport).to receive(:storage_path).and_return(export_path) end diff --git a/spec/lib/gitlab/import_export/wiki_repo_saver_spec.rb b/spec/lib/gitlab/import_export/wiki_repo_saver_spec.rb index 0e55993c8ef..1d1e7e7f89a 100644 --- a/spec/lib/gitlab/import_export/wiki_repo_saver_spec.rb +++ b/spec/lib/gitlab/import_export/wiki_repo_saver_spec.rb @@ -10,7 +10,7 @@ describe Gitlab::ImportExport::WikiRepoSaver do let!(:project_wiki) { ProjectWiki.new(project, user) } before do - project.team << [user, :master] + project.add_master(user) allow_any_instance_of(Gitlab::ImportExport).to receive(:storage_path).and_return(export_path) project_wiki.wiki project_wiki.create_page("index", "test content") diff --git a/spec/lib/gitlab/project_authorizations_spec.rb b/spec/lib/gitlab/project_authorizations_spec.rb index 953cfbb8b88..f3cd6961e94 100644 --- a/spec/lib/gitlab/project_authorizations_spec.rb +++ b/spec/lib/gitlab/project_authorizations_spec.rb @@ -15,7 +15,7 @@ describe Gitlab::ProjectAuthorizations do end before do - other_project.team << [user, :reporter] + other_project.add_reporter(user) group.add_developer(user) end diff --git a/spec/lib/gitlab/project_search_results_spec.rb b/spec/lib/gitlab/project_search_results_spec.rb index a424f0f5cfe..17937726f2c 100644 --- a/spec/lib/gitlab/project_search_results_spec.rb +++ b/spec/lib/gitlab/project_search_results_spec.rb @@ -179,7 +179,7 @@ describe Gitlab::ProjectSearchResults do end it 'does not list project confidential issues for project members with guest role' do - project.team << [member, :guest] + project.add_guest(member) results = described_class.new(member, project, query) issues = results.objects('issues') @@ -211,7 +211,7 @@ describe Gitlab::ProjectSearchResults do end it 'lists project confidential issues for project members' do - project.team << [member, :developer] + project.add_developer(member) results = described_class.new(member, project, query) issues = results.objects('issues') @@ -290,12 +290,12 @@ describe Gitlab::ProjectSearchResults do let!(:private_project) { create(:project, :private, :repository, creator: creator, namespace: creator.namespace) } let(:team_master) do user = create(:user, username: 'private-project-master') - private_project.team << [user, :master] + private_project.add_master(user) user end let(:team_reporter) do user = create(:user, username: 'private-project-reporter') - private_project.team << [user, :reporter] + private_project.add_reporter(user) user end diff --git a/spec/lib/gitlab/reference_extractor_spec.rb b/spec/lib/gitlab/reference_extractor_spec.rb index 8ec3f55e6de..4139d1c650c 100644 --- a/spec/lib/gitlab/reference_extractor_spec.rb +++ b/spec/lib/gitlab/reference_extractor_spec.rb @@ -4,7 +4,7 @@ describe Gitlab::ReferenceExtractor do let(:project) { create(:project) } before do - project.team << [project.creator, :developer] + project.add_developer(project.creator) end subject { described_class.new(project, project.creator) } @@ -14,8 +14,8 @@ describe Gitlab::ReferenceExtractor do @u_bar = create(:user, username: 'bar') @u_offteam = create(:user, username: 'offteam') - project.team << [@u_foo, :reporter] - project.team << [@u_bar, :guest] + project.add_guest(@u_foo) + project.add_guest(@u_bar) subject.analyze('@foo, @baduser, @bar, and @offteam') expect(subject.users).to match_array([@u_foo, @u_bar, @u_offteam]) @@ -26,8 +26,8 @@ describe Gitlab::ReferenceExtractor do @u_bar = create(:user, username: 'bar') @u_offteam = create(:user, username: 'offteam') - project.team << [@u_foo, :reporter] - project.team << [@u_bar, :guest] + project.add_reporter(@u_foo) + project.add_reporter(@u_bar) subject.analyze(%Q{ Inline code: `@foo` @@ -228,7 +228,7 @@ describe Gitlab::ReferenceExtractor do let(:issue) { create(:issue, project: other_project) } before do - other_project.team << [project.creator, :developer] + other_project.add_developer(project.creator) end it 'handles project issue references' do @@ -246,7 +246,7 @@ describe Gitlab::ReferenceExtractor do let(:text) { "Ref. #{issue.to_reference} and #{label.to_reference}" } before do - project.team << [project.creator, :developer] + project.add_developer(project.creator) subject.analyze(text) end diff --git a/spec/lib/gitlab/search_results_spec.rb b/spec/lib/gitlab/search_results_spec.rb index a958baab44f..b5a9ac570e6 100644 --- a/spec/lib/gitlab/search_results_spec.rb +++ b/spec/lib/gitlab/search_results_spec.rb @@ -16,7 +16,7 @@ describe Gitlab::SearchResults do context 'as a user with access' do before do - project.team << [user, :developer] + project.add_developer(user) end describe '#projects_count' do @@ -52,15 +52,36 @@ describe Gitlab::SearchResults do expect(results.objects('merge_requests')).to include merge_request_2 end - it 'includes project filter by default' do - expect(results).to receive(:project_ids_relation).and_call_original - results.objects('merge_requests') + describe '#merge_requests' do + it 'includes project filter by default' do + expect(results).to receive(:project_ids_relation).and_call_original + + results.objects('merge_requests') + end + + it 'it skips project filter if default project context is used' do + allow(results).to receive(:default_project_filter).and_return(true) + + expect(results).not_to receive(:project_ids_relation) + + results.objects('merge_requests') + end end - it 'it skips project filter if default is used' do - allow(results).to receive(:default_project_filter).and_return(true) - expect(results).not_to receive(:project_ids_relation) - results.objects('merge_requests') + describe '#issues' do + it 'includes project filter by default' do + expect(results).to receive(:project_ids_relation).and_call_original + + results.objects('issues') + end + + it 'it skips project filter if default project context is used' do + allow(results).to receive(:default_project_filter).and_return(true) + + expect(results).not_to receive(:project_ids_relation) + + results.objects('issues') + end end end @@ -104,8 +125,8 @@ describe Gitlab::SearchResults do end it 'does not list confidential issues for project members with guest role' do - project_1.team << [member, :guest] - project_2.team << [member, :guest] + project_1.add_guest(member) + project_2.add_guest(member) results = described_class.new(member, limit_projects, query) issues = results.objects('issues') @@ -146,8 +167,8 @@ describe Gitlab::SearchResults do end it 'lists confidential issues for project members' do - project_1.team << [member, :developer] - project_2.team << [member, :developer] + project_1.add_developer(member) + project_2.add_developer(member) results = described_class.new(member, limit_projects, query) issues = results.objects('issues') diff --git a/spec/lib/gitlab/shell_spec.rb b/spec/lib/gitlab/shell_spec.rb index dd779b04741..81d9e6a8f82 100644 --- a/spec/lib/gitlab/shell_spec.rb +++ b/spec/lib/gitlab/shell_spec.rb @@ -347,62 +347,6 @@ describe Gitlab::Shell do end.to raise_error(Gitlab::Shell::Error, "error") end end - - describe '#push_remote_branches' do - subject(:result) do - gitlab_shell.push_remote_branches( - project.repository_storage_path, - project.disk_path, - 'downstream-remote', - ['master'] - ) - end - - it 'executes the command' do - expect(gitlab_projects).to receive(:push_branches) - .with('downstream-remote', timeout, true, ['master']) - .and_return(true) - - is_expected.to be_truthy - end - - it 'fails to execute the command' do - allow(gitlab_projects).to receive(:output) { 'error' } - expect(gitlab_projects).to receive(:push_branches) - .with('downstream-remote', timeout, true, ['master']) - .and_return(false) - - expect { result }.to raise_error(Gitlab::Shell::Error, 'error') - end - end - - describe '#delete_remote_branches' do - subject(:result) do - gitlab_shell.delete_remote_branches( - project.repository_storage_path, - project.disk_path, - 'downstream-remote', - ['master'] - ) - end - - it 'executes the command' do - expect(gitlab_projects).to receive(:delete_remote_branches) - .with('downstream-remote', ['master']) - .and_return(true) - - is_expected.to be_truthy - end - - it 'fails to execute the command' do - allow(gitlab_projects).to receive(:output) { 'error' } - expect(gitlab_projects).to receive(:delete_remote_branches) - .with('downstream-remote', ['master']) - .and_return(false) - - expect { result }.to raise_error(Gitlab::Shell::Error, 'error') - end - end end describe 'namespace actions' do diff --git a/spec/lib/gitlab/slash_commands/issue_new_spec.rb b/spec/lib/gitlab/slash_commands/issue_new_spec.rb index 75ae58d0582..3b077c58c50 100644 --- a/spec/lib/gitlab/slash_commands/issue_new_spec.rb +++ b/spec/lib/gitlab/slash_commands/issue_new_spec.rb @@ -7,7 +7,7 @@ describe Gitlab::SlashCommands::IssueNew do let(:regex_match) { described_class.match("issue create bird is the word") } before do - project.team << [user, :master] + project.add_master(user) end subject do diff --git a/spec/lib/gitlab/slash_commands/issue_search_spec.rb b/spec/lib/gitlab/slash_commands/issue_search_spec.rb index 51f59216413..e41e5254dde 100644 --- a/spec/lib/gitlab/slash_commands/issue_search_spec.rb +++ b/spec/lib/gitlab/slash_commands/issue_search_spec.rb @@ -21,7 +21,7 @@ describe Gitlab::SlashCommands::IssueSearch do context 'the user has access' do before do - project.team << [user, :master] + project.add_master(user) end it 'returns all results' do diff --git a/spec/lib/gitlab/slash_commands/issue_show_spec.rb b/spec/lib/gitlab/slash_commands/issue_show_spec.rb index 08c380ca8f1..e5834d5a2ee 100644 --- a/spec/lib/gitlab/slash_commands/issue_show_spec.rb +++ b/spec/lib/gitlab/slash_commands/issue_show_spec.rb @@ -8,7 +8,7 @@ describe Gitlab::SlashCommands::IssueShow do let(:regex_match) { described_class.match("issue show #{issue.iid}") } before do - project.team << [user, :master] + project.add_master(user) end subject do diff --git a/spec/lib/gitlab/user_access_spec.rb b/spec/lib/gitlab/user_access_spec.rb index cd97416bcc9..7280acb6c82 100644 --- a/spec/lib/gitlab/user_access_spec.rb +++ b/spec/lib/gitlab/user_access_spec.rb @@ -8,19 +8,19 @@ describe Gitlab::UserAccess do describe '#can_push_to_branch?' do describe 'push to none protected branch' do it 'returns true if user is a master' do - project.team << [user, :master] + project.add_master(user) expect(access.can_push_to_branch?('random_branch')).to be_truthy end it 'returns true if user is a developer' do - project.team << [user, :developer] + project.add_developer(user) expect(access.can_push_to_branch?('random_branch')).to be_truthy end it 'returns false if user is a reporter' do - project.team << [user, :reporter] + project.add_reporter(user) expect(access.can_push_to_branch?('random_branch')).to be_falsey end @@ -31,34 +31,34 @@ describe Gitlab::UserAccess do let(:project_access) { described_class.new(user, project: empty_project) } it 'returns true if user is master' do - empty_project.team << [user, :master] + empty_project.add_master(user) expect(project_access.can_push_to_branch?('master')).to be_truthy end it 'returns false if user is developer and project is fully protected' do - empty_project.team << [user, :developer] + empty_project.add_developer(user) stub_application_setting(default_branch_protection: Gitlab::Access::PROTECTION_FULL) expect(project_access.can_push_to_branch?('master')).to be_falsey end it 'returns false if user is developer and it is not allowed to push new commits but can merge into branch' do - empty_project.team << [user, :developer] + empty_project.add_developer(user) stub_application_setting(default_branch_protection: Gitlab::Access::PROTECTION_DEV_CAN_MERGE) expect(project_access.can_push_to_branch?('master')).to be_falsey end it 'returns true if user is developer and project is unprotected' do - empty_project.team << [user, :developer] + empty_project.add_developer(user) stub_application_setting(default_branch_protection: Gitlab::Access::PROTECTION_NONE) expect(project_access.can_push_to_branch?('master')).to be_truthy end it 'returns true if user is developer and project grants developers permission' do - empty_project.team << [user, :developer] + empty_project.add_developer(user) stub_application_setting(default_branch_protection: Gitlab::Access::PROTECTION_DEV_CAN_PUSH) expect(project_access.can_push_to_branch?('master')).to be_truthy @@ -70,25 +70,25 @@ describe Gitlab::UserAccess do let(:not_existing_branch) { create :protected_branch, :developers_can_merge, project: project } it 'returns true if user is a master' do - project.team << [user, :master] + project.add_master(user) expect(access.can_push_to_branch?(branch.name)).to be_truthy end it 'returns false if user is a developer' do - project.team << [user, :developer] + project.add_developer(user) expect(access.can_push_to_branch?(branch.name)).to be_falsey end it 'returns false if user is a reporter' do - project.team << [user, :reporter] + project.add_reporter(user) expect(access.can_push_to_branch?(branch.name)).to be_falsey end it 'returns false if branch does not exist' do - project.team << [user, :developer] + project.add_developer(user) expect(access.can_push_to_branch?(not_existing_branch.name)).to be_falsey end @@ -100,19 +100,19 @@ describe Gitlab::UserAccess do end it 'returns true if user is a master' do - project.team << [user, :master] + project.add_master(user) expect(access.can_push_to_branch?(@branch.name)).to be_truthy end it 'returns true if user is a developer' do - project.team << [user, :developer] + project.add_developer(user) expect(access.can_push_to_branch?(@branch.name)).to be_truthy end it 'returns false if user is a reporter' do - project.team << [user, :reporter] + project.add_reporter(user) expect(access.can_push_to_branch?(@branch.name)).to be_falsey end @@ -124,19 +124,19 @@ describe Gitlab::UserAccess do end it 'returns true if user is a master' do - project.team << [user, :master] + project.add_master(user) expect(access.can_merge_to_branch?(@branch.name)).to be_truthy end it 'returns true if user is a developer' do - project.team << [user, :developer] + project.add_developer(user) expect(access.can_merge_to_branch?(@branch.name)).to be_truthy end it 'returns false if user is a reporter' do - project.team << [user, :reporter] + project.add_reporter(user) expect(access.can_merge_to_branch?(@branch.name)).to be_falsey end diff --git a/spec/lib/gitlab/visibility_level_spec.rb b/spec/lib/gitlab/visibility_level_spec.rb index 48a67773de9..d85dac630b4 100644 --- a/spec/lib/gitlab/visibility_level_spec.rb +++ b/spec/lib/gitlab/visibility_level_spec.rb @@ -49,4 +49,31 @@ describe Gitlab::VisibilityLevel do .to eq([Gitlab::VisibilityLevel::PUBLIC]) end end + + describe '.allowed_levels' do + it 'only includes the levels that arent restricted' do + stub_application_setting(restricted_visibility_levels: [Gitlab::VisibilityLevel::INTERNAL]) + + expect(described_class.allowed_levels) + .to contain_exactly(described_class::PRIVATE, described_class::PUBLIC) + end + end + + describe '.closest_allowed_level' do + it 'picks INTERNAL instead of PUBLIC if public is restricted' do + stub_application_setting(restricted_visibility_levels: [Gitlab::VisibilityLevel::PUBLIC]) + + expect(described_class.closest_allowed_level(described_class::PUBLIC)) + .to eq(described_class::INTERNAL) + end + + it 'picks PRIVATE if nothing is available' do + stub_application_setting(restricted_visibility_levels: [Gitlab::VisibilityLevel::PUBLIC, + Gitlab::VisibilityLevel::INTERNAL, + Gitlab::VisibilityLevel::PRIVATE]) + + expect(described_class.closest_allowed_level(described_class::PUBLIC)) + .to eq(described_class::PRIVATE) + end + end end diff --git a/spec/mailers/notify_spec.rb b/spec/mailers/notify_spec.rb index 4d0a3942996..cbc8c67da61 100644 --- a/spec/mailers/notify_spec.rb +++ b/spec/mailers/notify_spec.rb @@ -417,7 +417,7 @@ describe Notify do context 'for a project in a user namespace' do let(:project) do create(:project, :public, :access_requestable) do |project| - project.team << [project.owner, :master, project.owner] + project.add_master(project.owner, current_user: project.owner) end end @@ -520,7 +520,7 @@ describe Notify do end describe 'project invitation' do - let(:master) { create(:user).tap { |u| project.team << [u, :master] } } + let(:master) { create(:user).tap { |u| project.add_master(u) } } let(:project_member) { invite_to_project(project, inviter: master) } subject { described_class.member_invited_email('project', project_member.id, project_member.invite_token) } @@ -540,7 +540,7 @@ describe Notify do describe 'project invitation accepted' do let(:invited_user) { create(:user, name: 'invited user') } - let(:master) { create(:user).tap { |u| project.team << [u, :master] } } + let(:master) { create(:user).tap { |u| project.add_master(u) } } let(:project_member) do invitee = invite_to_project(project, inviter: master) invitee.accept_invite!(invited_user) @@ -563,7 +563,7 @@ describe Notify do end describe 'project invitation declined' do - let(:master) { create(:user).tap { |u| project.team << [u, :master] } } + let(:master) { create(:user).tap { |u| project.add_master(u) } } let(:project_member) do invitee = invite_to_project(project, inviter: master) invitee.decline_invite! diff --git a/spec/migrations/clean_up_for_members_spec.rb b/spec/migrations/clean_up_for_members_spec.rb new file mode 100644 index 00000000000..0258860d169 --- /dev/null +++ b/spec/migrations/clean_up_for_members_spec.rb @@ -0,0 +1,78 @@ +require 'spec_helper' +require Rails.root.join('db', 'migrate', '20171216111734_clean_up_for_members.rb') + +describe CleanUpForMembers, :migration do + let(:migration) { described_class.new } + let!(:group_member) { create_group_member } + let!(:unbinded_group_member) { create_group_member } + let!(:invited_group_member) { create_group_member(true) } + let!(:not_valid_group_member) { create_group_member } + let!(:project_member) { create_project_member } + let!(:invited_project_member) { create_project_member(true) } + let!(:unbinded_project_member) { create_project_member } + let!(:not_valid_project_member) { create_project_member } + + it 'removes members without proper user_id' do + unbinded_group_member.update_column(:user_id, nil) + not_valid_group_member.update_column(:user_id, 9999) + unbinded_project_member.update_column(:user_id, nil) + not_valid_project_member.update_column(:user_id, 9999) + + migrate! + + expect(Member.all).not_to include(unbinded_group_member, not_valid_group_member, unbinded_project_member, not_valid_project_member) + expect(Member.all).to include(group_member, invited_group_member, project_member, invited_project_member) + end + + def create_group_member(invited = false) + fill_member(GroupMember.new(group: create_group), invited) + end + + def create_project_member(invited = false) + fill_member(ProjectMember.new(project: create_project), invited) + end + + def fill_member(member_object, invited) + member_object.tap do |m| + m.access_level = 40 + m.notification_level = 3 + + if invited + m.user_id = nil + m.invite_token = 'xxx' + m.invite_email = 'email@email.com' + else + m.user_id = create_user.id + end + + m.save + end + + member_object + end + + def create_group + name = FFaker::Lorem.characters(10) + + Group.create(name: name, path: name.downcase.gsub(/\s/, '_')) + end + + def create_project + name = FFaker::Lorem.characters(10) + creator = create_user + + Project.create(name: name, + path: name.downcase.gsub(/\s/, '_'), + namespace: creator.namespace, + creator: creator) + end + + def create_user + User.create(email: FFaker::Internet.email, + password: '12345678', + name: FFaker::Name.name, + username: FFaker::Internet.user_name, + confirmed_at: Time.now, + confirmation_token: nil) + end +end diff --git a/spec/migrations/delete_conflicting_redirect_routes_spec.rb b/spec/migrations/delete_conflicting_redirect_routes_spec.rb index 1df2477da51..8a191bd7139 100644 --- a/spec/migrations/delete_conflicting_redirect_routes_spec.rb +++ b/spec/migrations/delete_conflicting_redirect_routes_spec.rb @@ -10,9 +10,6 @@ describe DeleteConflictingRedirectRoutes, :migration, :sidekiq do end before do - stub_const("DeleteConflictingRedirectRoutes::BATCH_SIZE", 2) - stub_const("Gitlab::Database::MigrationHelpers::BACKGROUND_MIGRATION_JOB_BUFFER_SIZE", 2) - routes.create!(id: 1, source_id: 1, source_type: 'Namespace', path: 'foo1') routes.create!(id: 2, source_id: 2, source_type: 'Namespace', path: 'foo2') routes.create!(id: 3, source_id: 3, source_type: 'Namespace', path: 'foo3') @@ -32,27 +29,14 @@ describe DeleteConflictingRedirectRoutes, :migration, :sidekiq do redirect_routes.create!(source_id: 1, source_type: 'Namespace', path: 'foo5') end - it 'correctly schedules background migrations' do + # No-op. See https://gitlab.com/gitlab-com/infrastructure/issues/3460#note_53223252 + it 'NO-OP: does not schedule any background migrations' do Sidekiq::Testing.fake! do Timecop.freeze do migrate! - expect(BackgroundMigrationWorker.jobs[0]['args']).to eq([described_class::MIGRATION, [1, 2]]) - expect(BackgroundMigrationWorker.jobs[0]['at']).to eq(12.seconds.from_now.to_f) - expect(BackgroundMigrationWorker.jobs[1]['args']).to eq([described_class::MIGRATION, [3, 4]]) - expect(BackgroundMigrationWorker.jobs[1]['at']).to eq(24.seconds.from_now.to_f) - expect(BackgroundMigrationWorker.jobs[2]['args']).to eq([described_class::MIGRATION, [5, 5]]) - expect(BackgroundMigrationWorker.jobs[2]['at']).to eq(36.seconds.from_now.to_f) - expect(BackgroundMigrationWorker.jobs.size).to eq 3 + expect(BackgroundMigrationWorker.jobs.size).to eq 0 end end end - - it 'schedules background migrations' do - Sidekiq::Testing.inline! do - expect do - migrate! - end.to change { redirect_routes.count }.from(8).to(3) - end - end end diff --git a/spec/migrations/issues_moved_to_id_foreign_key_spec.rb b/spec/migrations/issues_moved_to_id_foreign_key_spec.rb new file mode 100644 index 00000000000..d2eef81f396 --- /dev/null +++ b/spec/migrations/issues_moved_to_id_foreign_key_spec.rb @@ -0,0 +1,25 @@ +require 'spec_helper' +require Rails.root.join('db', 'migrate', '20171106151218_issues_moved_to_id_foreign_key.rb') + +# The schema version has to be far enough in advance to have the +# only_mirror_protected_branches column in the projects table to create a +# project via FactoryBot. +describe IssuesMovedToIdForeignKey, :migration, schema: 20171114150259 do + let!(:issue_first) { create(:issue, moved_to_id: issue_second.id) } + let!(:issue_second) { create(:issue, moved_to_id: issue_third.id) } + let!(:issue_third) { create(:issue) } + + subject { described_class.new } + + it 'removes the orphaned moved_to_id' do + subject.down + + issue_third.update_attributes(moved_to_id: 100000) + + subject.up + + expect(issue_first.reload.moved_to_id).to eq(issue_second.id) + expect(issue_second.reload.moved_to_id).to eq(issue_third.id) + expect(issue_third.reload.moved_to_id).to be_nil + end +end diff --git a/spec/migrations/schedule_populate_merge_request_metrics_with_events_data_spec.rb b/spec/migrations/schedule_populate_merge_request_metrics_with_events_data_spec.rb new file mode 100644 index 00000000000..97e089c5cb8 --- /dev/null +++ b/spec/migrations/schedule_populate_merge_request_metrics_with_events_data_spec.rb @@ -0,0 +1,24 @@ +require 'spec_helper' +require Rails.root.join('db', 'post_migrate', '20171128214150_schedule_populate_merge_request_metrics_with_events_data.rb') + +describe SchedulePopulateMergeRequestMetricsWithEventsData, :migration, :sidekiq do + let!(:mrs) { create_list(:merge_request, 3) } + + it 'correctly schedules background migrations' do + stub_const("#{described_class.name}::BATCH_SIZE", 2) + + Sidekiq::Testing.fake! do + Timecop.freeze do + migrate! + + expect(described_class::MIGRATION) + .to be_scheduled_migration(10.minutes, mrs.first.id, mrs.second.id) + + expect(described_class::MIGRATION) + .to be_scheduled_migration(20.minutes, mrs.third.id, mrs.third.id) + + expect(BackgroundMigrationWorker.jobs.size).to eq(2) + end + end + end +end diff --git a/spec/models/ability_spec.rb b/spec/models/ability_spec.rb index 71aa51e1857..38fb98d4f50 100644 --- a/spec/models/ability_spec.rb +++ b/spec/models/ability_spec.rb @@ -34,19 +34,19 @@ describe Ability do end it 'returns false for a guest user' do - project.team << [user, :guest] + project.add_guest(user) expect(described_class.can_edit_note?(user, note)).to be_falsy end it 'returns false for a developer' do - project.team << [user, :developer] + project.add_developer(user) expect(described_class.can_edit_note?(user, note)).to be_falsy end it 'returns true for a master' do - project.team << [user, :master] + project.add_master(user) expect(described_class.can_edit_note?(user, note)).to be_truthy end diff --git a/spec/models/ci/pipeline_spec.rb b/spec/models/ci/pipeline_spec.rb index a1f63a2534b..7bef798a782 100644 --- a/spec/models/ci/pipeline_spec.rb +++ b/spec/models/ci/pipeline_spec.rb @@ -1440,7 +1440,7 @@ describe Ci::Pipeline, :mailer do end before do - project.team << [pipeline.user, Gitlab::Access::DEVELOPER] + project.add_developer(pipeline.user) pipeline.user.global_notification_setting .update(level: 'custom', failed_pipeline: true, success_pipeline: true) diff --git a/spec/models/ci/trigger_spec.rb b/spec/models/ci/trigger_spec.rb index bd9c837402f..d4b72205203 100644 --- a/spec/models/ci/trigger_spec.rb +++ b/spec/models/ci/trigger_spec.rb @@ -69,7 +69,7 @@ describe Ci::Trigger do context 'and is member of the project' do before do - project.team << [owner, :developer] + project.add_developer(owner) end it { is_expected.to eq(true) } diff --git a/spec/models/commit_spec.rb b/spec/models/commit_spec.rb index cd955a5eb69..4f02dc33cd8 100644 --- a/spec/models/commit_spec.rb +++ b/spec/models/commit_spec.rb @@ -193,8 +193,8 @@ eos let(:commiter) { create :user } before do - project.team << [commiter, :developer] - other_project.team << [commiter, :developer] + project.add_developer(commiter) + other_project.add_developer(commiter) end it 'detects issues that this commit is marked as closing' do diff --git a/spec/models/concerns/mentionable_spec.rb b/spec/models/concerns/mentionable_spec.rb index 8b545aec7f5..c73ea6aa94c 100644 --- a/spec/models/concerns/mentionable_spec.rb +++ b/spec/models/concerns/mentionable_spec.rb @@ -62,7 +62,7 @@ describe Issue, "Mentionable" do context 'when the current user can see the issue' do before do - private_project.team << [user, Gitlab::Access::DEVELOPER] + private_project.add_developer(user) end it 'includes the reference' do @@ -107,7 +107,7 @@ describe Issue, "Mentionable" do let(:issues) { create_list(:issue, 2, project: project, author: author) } before do - project.team << [author, Gitlab::Access::DEVELOPER] + project.add_developer(author) end context 'before changes are persisted' do diff --git a/spec/models/concerns/milestoneish_spec.rb b/spec/models/concerns/milestoneish_spec.rb index 673c609f534..87bf731340f 100644 --- a/spec/models/concerns/milestoneish_spec.rb +++ b/spec/models/concerns/milestoneish_spec.rb @@ -24,8 +24,8 @@ describe Milestone, 'Milestoneish' do let(:label_3) { create(:label, title: 'label_3', project: project) } before do - project.team << [member, :developer] - project.team << [guest, :guest] + project.add_developer(member) + project.add_guest(guest) end describe '#sorted_issues' do diff --git a/spec/models/concerns/resolvable_discussion_spec.rb b/spec/models/concerns/resolvable_discussion_spec.rb index 1616c2ea985..2a2ef5a304d 100644 --- a/spec/models/concerns/resolvable_discussion_spec.rb +++ b/spec/models/concerns/resolvable_discussion_spec.rb @@ -190,7 +190,7 @@ describe Discussion, ResolvableDiscussion do context "when the signed in user can push to the project" do before do - subject.project.team << [current_user, :master] + subject.project.add_master(current_user) end it "returns true" do diff --git a/spec/models/diff_discussion_spec.rb b/spec/models/diff_discussion_spec.rb index fa02434b0fd..50b19000799 100644 --- a/spec/models/diff_discussion_spec.rb +++ b/spec/models/diff_discussion_spec.rb @@ -47,8 +47,20 @@ describe DiffDiscussion do diff_note.save! end - it 'returns the diff ID for the version to show' do - expect(subject.merge_request_version_params).to eq(diff_id: merge_request_diff1.id) + context 'when commit_id is not present' do + it 'returns the diff ID for the version to show' do + expect(subject.merge_request_version_params).to eq(diff_id: merge_request_diff1.id) + end + end + + context 'when commit_id is present' do + before do + diff_note.update_attribute(:commit_id, 'commit_123') + end + + it 'includes the commit_id in the result' do + expect(subject.merge_request_version_params).to eq(diff_id: merge_request_diff1.id, commit_id: 'commit_123') + end end end @@ -70,8 +82,20 @@ describe DiffDiscussion do diff_note.save! end - it 'returns the diff ID and start sha of the versions to compare' do - expect(subject.merge_request_version_params).to eq(diff_id: merge_request_diff3.id, start_sha: merge_request_diff1.head_commit_sha) + context 'when commit_id is not present' do + it 'returns the diff ID and start sha of the versions to compare' do + expect(subject.merge_request_version_params).to eq(diff_id: merge_request_diff3.id, start_sha: merge_request_diff1.head_commit_sha) + end + end + + context 'when commit_id is present' do + before do + diff_note.update_attribute(:commit_id, 'commit_123') + end + + it 'includes the commit_id in the result' do + expect(subject.merge_request_version_params).to eq(diff_id: merge_request_diff3.id, start_sha: merge_request_diff1.head_commit_sha, commit_id: 'commit_123') + end end end @@ -83,8 +107,20 @@ describe DiffDiscussion do diff_note.save! end - it 'returns nil' do - expect(subject.merge_request_version_params).to be_nil + context 'when commit_id is not present' do + it 'returns empty hash' do + expect(subject.merge_request_version_params).to eq(nil) + end + end + + context 'when commit_id is present' do + before do + diff_note.update_attribute(:commit_id, 'commit_123') + end + + it 'returns the commit_id' do + expect(subject.merge_request_version_params).to eq(commit_id: 'commit_123') + end end end end diff --git a/spec/models/event_spec.rb b/spec/models/event_spec.rb index aa7a8342a4c..67f49348acb 100644 --- a/spec/models/event_spec.rb +++ b/spec/models/event_spec.rb @@ -125,8 +125,8 @@ describe Event do let(:event) { described_class.new(project: project, target: target, author_id: author.id) } before do - project.team << [member, :developer] - project.team << [guest, :guest] + project.add_developer(member) + project.add_guest(guest) end context 'commit note event' do @@ -347,6 +347,22 @@ describe Event do end end + describe '#target' do + it 'eager loads the author of an event target' do + create(:closed_issue_event) + + events = described_class.preload(:target).all.to_a + count = ActiveRecord::QueryRecorder + .new { events.first.target.author }.count + + # This expectation exists to make sure the test doesn't pass when the + # author is for some reason not loaded at all. + expect(events.first.target.author).to be_an_instance_of(User) + + expect(count).to be_zero + end + end + def create_push_event(project, user) event = create(:push_event, project: project, author: user) diff --git a/spec/models/generic_commit_status_spec.rb b/spec/models/generic_commit_status_spec.rb index 7f1909710d8..673049d1cc4 100644 --- a/spec/models/generic_commit_status_spec.rb +++ b/spec/models/generic_commit_status_spec.rb @@ -43,7 +43,7 @@ describe GenericCommitStatus do context 'when user has ability to see datails' do before do - project.team << [user, :developer] + project.add_developer(user) end it 'details path points to an external URL' do diff --git a/spec/models/hooks/system_hook_spec.rb b/spec/models/hooks/system_hook_spec.rb index 431e3db9f00..0e965f541d8 100644 --- a/spec/models/hooks/system_hook_spec.rb +++ b/spec/models/hooks/system_hook_spec.rb @@ -62,7 +62,7 @@ describe SystemHook do end it "project_create hook" do - project.team << [user, :master] + project.add_master(user) expect(WebMock).to have_requested(:post, system_hook.url).with( body: /user_add_to_team/, @@ -71,7 +71,7 @@ describe SystemHook do end it "project_destroy hook" do - project.team << [user, :master] + project.add_master(user) project.project_members.destroy_all expect(WebMock).to have_requested(:post, system_hook.url).with( diff --git a/spec/models/issue_collection_spec.rb b/spec/models/issue_collection_spec.rb index 34d98a3c975..580a98193af 100644 --- a/spec/models/issue_collection_spec.rb +++ b/spec/models/issue_collection_spec.rb @@ -42,7 +42,7 @@ describe IssueCollection do context 'using a user that has reporter access to the project' do it 'returns the issues of the project' do - project.team << [user, :reporter] + project.add_reporter(user) expect(collection.updatable_by_user(user)).to eq([issue1, issue2]) end diff --git a/spec/models/issue_spec.rb b/spec/models/issue_spec.rb index 0ea287d007a..5ced000cdb6 100644 --- a/spec/models/issue_spec.rb +++ b/spec/models/issue_spec.rb @@ -23,6 +23,32 @@ describe Issue do it { is_expected.to have_db_index(:deleted_at) } end + describe 'callbacks' do + describe '#ensure_metrics' do + it 'creates metrics after saving' do + issue = create(:issue) + + expect(issue.metrics).to be_persisted + expect(Issue::Metrics.count).to eq(1) + end + + it 'does not create duplicate metrics for an issue' do + issue = create(:issue) + + issue.close! + + expect(issue.metrics).to be_persisted + expect(Issue::Metrics.count).to eq(1) + end + + it 'records current metrics' do + expect_any_instance_of(Issue::Metrics).to receive(:record!) + + create(:issue) + end + end + end + describe '#order_by_position_and_priority' do let(:project) { create :project } let(:p1) { create(:label, title: 'P1', project: project, priority: 1) } @@ -238,7 +264,7 @@ describe Issue do let(:issue) { create(:issue, project: project) } before do - project.team << [user, :reporter] + project.add_reporter(user) end it { is_expected.to eq true } @@ -254,7 +280,7 @@ describe Issue do context 'destination project allowed' do before do - to_project.team << [user, :reporter] + to_project.add_reporter(user) end it { is_expected.to eq true } @@ -262,7 +288,7 @@ describe Issue do context 'destination project not allowed' do before do - to_project.team << [user, :guest] + to_project.add_guest(user) end it { is_expected.to eq false } @@ -550,7 +576,7 @@ describe Issue do context 'when the user is the project owner' do before do - project.team << [user, :master] + project.add_master(user) end it 'returns true for a regular issue' do @@ -574,7 +600,7 @@ describe Issue do context 'using a public project' do before do - project.team << [user, Gitlab::Access::DEVELOPER] + project.add_developer(user) end it 'returns true for a regular issue' do @@ -594,7 +620,7 @@ describe Issue do let(:project) { create(:project, :internal) } before do - project.team << [user, Gitlab::Access::DEVELOPER] + project.add_developer(user) end it 'returns true for a regular issue' do @@ -614,7 +640,7 @@ describe Issue do let(:project) { create(:project, :private) } before do - project.team << [user, Gitlab::Access::DEVELOPER] + project.add_developer(user) end it 'returns true for a regular issue' do diff --git a/spec/models/member_spec.rb b/spec/models/member_spec.rb index 0a017c068ad..6aa0e7f49c3 100644 --- a/spec/models/member_spec.rb +++ b/spec/models/member_spec.rb @@ -62,12 +62,12 @@ describe Member do @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_user = create(:user).tap { |u| project.add_master(u) } @master = project.members.find_by(user_id: @master_user.id) @blocked_user = create(:user).tap do |u| - project.team << [u, :master] - project.team << [u, :developer] + project.add_master(u) + project.add_developer(u) u.block! end @@ -527,7 +527,7 @@ describe Member do it "refreshes user's authorized projects" do project = create(:project, :private) user = create(:user) - member = project.team << [user, :reporter] + member = project.add_reporter(user) member.destroy diff --git a/spec/models/members/project_member_spec.rb b/spec/models/members/project_member_spec.rb index fa3e80ba062..3e46fa36375 100644 --- a/spec/models/members/project_member_spec.rb +++ b/spec/models/members/project_member_spec.rb @@ -88,8 +88,8 @@ describe ProjectMember do @user_1 = create :user @user_2 = create :user - @project_1.team << [@user_1, :developer] - @project_2.team << [@user_2, :reporter] + @project_1.add_developer(@user_1) + @project_2.add_reporter(@user_2) @status = @project_2.team.import(@project_1) end @@ -136,8 +136,8 @@ describe ProjectMember do @user_1 = create :user @user_2 = create :user - @project_1.team << [@user_1, :developer] - @project_2.team << [@user_2, :reporter] + @project_1.add_developer(@user_1) + @project_2.add_reporter(@user_2) described_class.truncate_teams([@project_1.id, @project_2.id]) end diff --git a/spec/models/merge_request/metrics_spec.rb b/spec/models/merge_request/metrics_spec.rb index 9353d5c3c8a..02ff7839739 100644 --- a/spec/models/merge_request/metrics_spec.rb +++ b/spec/models/merge_request/metrics_spec.rb @@ -1,16 +1,11 @@ require 'spec_helper' describe MergeRequest::Metrics do - subject { create(:merge_request) } + subject { described_class.new } - describe "when recording the default set of metrics on merge request save" do - it "records the merge time" do - time = Time.now - Timecop.freeze(time) { subject.mark_as_merged } - metrics = subject.metrics - - expect(metrics).to be_present - expect(metrics.merged_at).to be_like_time(time) - end + describe 'associations' do + it { is_expected.to belong_to(:merge_request) } + it { is_expected.to belong_to(:latest_closed_by).class_name('User') } + it { is_expected.to belong_to(:merged_by).class_name('User') } end end diff --git a/spec/models/merge_request_spec.rb b/spec/models/merge_request_spec.rb index bb63abd167b..d8ebd46faab 100644 --- a/spec/models/merge_request_spec.rb +++ b/spec/models/merge_request_spec.rb @@ -65,6 +65,25 @@ describe MergeRequest do end end + describe 'callbacks' do + describe '#ensure_merge_request_metrics' do + it 'creates metrics after saving' do + merge_request = create(:merge_request) + + expect(merge_request.metrics).to be_persisted + expect(MergeRequest::Metrics.count).to eq(1) + end + + it 'does not duplicate metrics for a merge request' do + merge_request = create(:merge_request) + + merge_request.mark_as_merged! + + expect(MergeRequest::Metrics.count).to eq(1) + end + end + end + describe 'respond to' do it { is_expected.to respond_to(:unchecked?) } it { is_expected.to respond_to(:can_be_merged?) } @@ -195,7 +214,7 @@ describe MergeRequest do describe '#cache_merge_request_closes_issues!' do before do - subject.project.team << [subject.author, :developer] + subject.project.add_developer(subject.author) subject.target_branch = subject.project.default_branch end @@ -481,7 +500,7 @@ describe MergeRequest do let(:commit2) { double('commit2', safe_message: "Fixes #{issue1.to_reference}") } before do - subject.project.team << [subject.author, :developer] + subject.project.add_developer(subject.author) allow(subject).to receive(:commits).and_return([commit0, commit1, commit2]) end @@ -509,7 +528,7 @@ describe MergeRequest do let(:commit) { double('commit', safe_message: "Fixes #{closing_issue.to_reference}") } it 'detects issues mentioned in description but not closed' do - subject.project.team << [subject.author, :developer] + subject.project.add_developer(subject.author) subject.description = "Is related to #{mentioned_issue.to_reference} and #{closing_issue.to_reference}" allow(subject).to receive(:commits).and_return([commit]) @@ -521,7 +540,7 @@ describe MergeRequest do context 'when the project has an external issue tracker' do before do - subject.project.team << [subject.author, :developer] + subject.project.add_developer(subject.author) commit = double(:commit, safe_message: 'Fixes TEST-3') create(:jira_service, project: subject.project) @@ -660,7 +679,7 @@ describe MergeRequest do it 'includes its closed issues in the body' do issue = create(:issue, project: subject.project) - subject.project.team << [subject.author, :developer] + subject.project.add_developer(subject.author) subject.description = "This issue Closes #{issue.to_reference}" allow(subject.project).to receive(:default_branch) @@ -1688,7 +1707,7 @@ describe MergeRequest do let(:mr_sha) { merge_request.diff_head_sha } before do - project.team << [developer, :developer] + project.add_developer(developer) end context 'when autocomplete_precheck is set to true' do diff --git a/spec/models/namespace_spec.rb b/spec/models/namespace_spec.rb index b7c6286fd83..0678cae9b93 100644 --- a/spec/models/namespace_spec.rb +++ b/spec/models/namespace_spec.rb @@ -203,7 +203,7 @@ describe Namespace do context 'with subgroups' do let(:parent) { create(:group, name: 'parent', path: 'parent') } let(:child) { create(:group, name: 'child', path: 'child', parent: parent) } - let!(:project) { create(:project_empty_repo, path: 'the-project', namespace: child) } + let!(:project) { create(:project_empty_repo, path: 'the-project', namespace: child, skip_disk_validation: true) } let(:uploads_dir) { File.join(CarrierWave.root, FileUploader.base_dir) } let(:pages_dir) { File.join(TestEnv.pages_path) } @@ -240,6 +240,20 @@ describe Namespace do end end end + + it 'updates project full path in .git/config for each project inside namespace' do + parent = create(:group, name: 'mygroup', path: 'mygroup') + subgroup = create(:group, name: 'mysubgroup', path: 'mysubgroup', parent: parent) + project_in_parent_group = create(:project, :repository, namespace: parent, name: 'foo1') + hashed_project_in_subgroup = create(:project, :repository, :hashed, namespace: subgroup, name: 'foo2') + legacy_project_in_subgroup = create(:project, :repository, namespace: subgroup, name: 'foo3') + + parent.update(path: 'mygroup_new') + + expect(project_in_parent_group.repo.config['gitlab.fullpath']).to eq "mygroup_new/#{project_in_parent_group.path}" + expect(hashed_project_in_subgroup.repo.config['gitlab.fullpath']).to eq "mygroup_new/mysubgroup/#{hashed_project_in_subgroup.path}" + expect(legacy_project_in_subgroup.repo.config['gitlab.fullpath']).to eq "mygroup_new/mysubgroup/#{legacy_project_in_subgroup.path}" + end end describe '#rm_dir', 'callback' do diff --git a/spec/models/note_spec.rb b/spec/models/note_spec.rb index cefbf60b28c..3d030927036 100644 --- a/spec/models/note_spec.rb +++ b/spec/models/note_spec.rb @@ -195,7 +195,7 @@ describe Note do describe "cross_reference_not_visible_for?" do let(:private_user) { create(:user) } - let(:private_project) { create(:project, namespace: private_user.namespace) { |p| p.team << [private_user, :master] } } + let(:private_project) { create(:project, namespace: private_user.namespace) { |p| p.add_master(private_user) } } let(:private_issue) { create(:issue, project: private_project) } let(:ext_proj) { create(:project, :public) } diff --git a/spec/models/project_feature_spec.rb b/spec/models/project_feature_spec.rb index de3e86b627f..63c6fbda3f2 100644 --- a/spec/models/project_feature_spec.rb +++ b/spec/models/project_feature_spec.rb @@ -37,7 +37,7 @@ describe ProjectFeature do end it "returns true when user is a team member" do - project.team << [user, :developer] + project.add_developer(user) features.each do |feature| project.project_feature.update_attribute("#{feature}_access_level".to_sym, ProjectFeature::PRIVATE) diff --git a/spec/models/project_services/kubernetes_service_spec.rb b/spec/models/project_services/kubernetes_service_spec.rb index f037ee77a94..6980ba335b8 100644 --- a/spec/models/project_services/kubernetes_service_spec.rb +++ b/spec/models/project_services/kubernetes_service_spec.rb @@ -52,12 +52,75 @@ describe KubernetesService, :use_clean_rails_memory_store_caching do context 'when service is inactive' do before do + subject.project = project subject.active = false end it { is_expected.not_to validate_presence_of(:api_url) } it { is_expected.not_to validate_presence_of(:token) } end + + context 'with a deprecated service' do + let(:kubernetes_service) { create(:kubernetes_service) } + + before do + kubernetes_service.update_attribute(:active, false) + kubernetes_service.properties[:namespace] = "foo" + end + + it 'should not update attributes' do + expect(kubernetes_service.save).to be_falsy + end + + it 'should include an error with a deprecation message' do + kubernetes_service.valid? + expect(kubernetes_service.errors[:base].first).to match(/Kubernetes service integration has been deprecated/) + end + end + + context 'with a non-deprecated service' do + let(:kubernetes_service) { create(:kubernetes_service) } + + it 'should update attributes' do + kubernetes_service.properties[:namespace] = 'foo' + expect(kubernetes_service.save).to be_truthy + end + end + + context 'with an active and deprecated service' do + let(:kubernetes_service) { create(:kubernetes_service) } + + before do + kubernetes_service.active = false + kubernetes_service.properties[:namespace] = 'foo' + kubernetes_service.save + end + + it 'should deactive the service' do + expect(kubernetes_service.active?).to be_falsy + end + + it 'should not include a deprecation message as error' do + expect(kubernetes_service.errors.messages.count).to eq(0) + end + + it 'should update attributes' do + expect(kubernetes_service.properties[:namespace]).to eq("foo") + end + end + + context 'with a template service' do + let(:kubernetes_service) { create(:kubernetes_service, template: true, active: false) } + + before do + kubernetes_service.properties[:namespace] = 'foo' + end + + it 'should update attributes' do + expect(kubernetes_service.save).to be_truthy + expect(kubernetes_service.properties[:namespace]).to eq('foo') + end + end end describe '#initialize_properties' do @@ -318,4 +381,42 @@ describe KubernetesService, :use_clean_rails_memory_store_caching do it { is_expected.to eq(pods: []) } end end + + describe "#deprecated?" do + let(:kubernetes_service) { create(:kubernetes_service) } + + context 'with an active kubernetes service' do + it 'should return false' do + expect(kubernetes_service.deprecated?).to be_falsy + end + end + + context 'with a inactive kubernetes service' do + it 'should return true' do + kubernetes_service.update_attribute(:active, false) + expect(kubernetes_service.deprecated?).to be_truthy + end + end + end + + describe "#deprecation_message" do + let(:kubernetes_service) { create(:kubernetes_service) } + + it 'should indicate the service is deprecated' do + expect(kubernetes_service.deprecation_message).to match(/Kubernetes service integration has been deprecated/) + end + + context 'if the services is active' do + it 'should return a message' do + expect(kubernetes_service.deprecation_message).to match(/Your cluster information on this page is still editable/) + end + end + + context 'if the service is not active' do + it 'should return a message' do + kubernetes_service.update_attribute(:active, false) + expect(kubernetes_service.deprecation_message).to match(/Fields on this page are now uneditable/) + end + end + end end diff --git a/spec/models/project_services/pipelines_email_service_spec.rb b/spec/models/project_services/pipelines_email_service_spec.rb index be07ca2d945..75ae2207910 100644 --- a/spec/models/project_services/pipelines_email_service_spec.rb +++ b/spec/models/project_services/pipelines_email_service_spec.rb @@ -37,7 +37,7 @@ describe PipelinesEmailService, :mailer do let(:user) { create(:user) } before do - project.team << [user, :developer] + project.add_developer(user) end it 'builds test data' do diff --git a/spec/models/project_spec.rb b/spec/models/project_spec.rb index cbeac2f05d3..cea22bbd184 100644 --- a/spec/models/project_spec.rb +++ b/spec/models/project_spec.rb @@ -93,7 +93,7 @@ describe Project do let(:developer) { create(:user) } before do project.request_access(requester) - project.team << [developer, :developer] + project.add_developer(developer) end it_behaves_like 'members and requesters associations' do @@ -520,7 +520,7 @@ describe Project do let(:user) { create(:user) } before do - project.team << [user, :developer] + project.add_developer(user) end context 'with default issues tracker' do @@ -1435,35 +1435,35 @@ describe Project do let(:user) { create(:user) } it 'returns false when default_branch_protection is in full protection and user is developer' do - project.team << [user, :developer] + project.add_developer(user) stub_application_setting(default_branch_protection: Gitlab::Access::PROTECTION_FULL) expect(project.user_can_push_to_empty_repo?(user)).to be_falsey end it 'returns false when default_branch_protection only lets devs merge and user is dev' do - project.team << [user, :developer] + project.add_developer(user) stub_application_setting(default_branch_protection: Gitlab::Access::PROTECTION_DEV_CAN_MERGE) expect(project.user_can_push_to_empty_repo?(user)).to be_falsey end it 'returns true when default_branch_protection lets devs push and user is developer' do - project.team << [user, :developer] + project.add_developer(user) stub_application_setting(default_branch_protection: Gitlab::Access::PROTECTION_DEV_CAN_PUSH) expect(project.user_can_push_to_empty_repo?(user)).to be_truthy end it 'returns true when default_branch_protection is unprotected and user is developer' do - project.team << [user, :developer] + project.add_developer(user) stub_application_setting(default_branch_protection: Gitlab::Access::PROTECTION_NONE) expect(project.user_can_push_to_empty_repo?(user)).to be_truthy end it 'returns true when user is master' do - project.team << [user, :master] + project.add_master(user) expect(project.user_can_push_to_empty_repo?(user)).to be_truthy end @@ -2626,6 +2626,14 @@ describe Project do project.rename_repo end end + + it 'updates project full path in .git/config' do + allow(project_storage).to receive(:rename_repo).and_return(true) + + project.rename_repo + + expect(project.repo.config['gitlab.fullpath']).to eq(project.full_path) + end end describe '#pages_path' do @@ -2668,14 +2676,12 @@ describe Project do end context 'hashed storage' do - let(:project) { create(:project, :repository) } + let(:project) { create(:project, :repository, skip_disk_validation: true) } let(:gitlab_shell) { Gitlab::Shell.new } - let(:hash) { '6b86b273ff34fce19d6b804eff5a3f5747ada4eaa22f1d49c01e52ddb7875b4b' } + let(:hash) { Digest::SHA2.hexdigest(project.id.to_s) } before do stub_application_setting(hashed_storage_enabled: true) - allow(Digest::SHA2).to receive(:hexdigest) { hash } - allow(project).to receive(:gitlab_shell).and_return(gitlab_shell) end describe '#legacy_storage?' do @@ -2698,13 +2704,13 @@ describe Project do describe '#base_dir' do it 'returns base_dir based on hash of project id' do - expect(project.base_dir).to eq('@hashed/6b/86') + expect(project.base_dir).to eq("@hashed/#{hash[0..1]}/#{hash[2..3]}") end end describe '#disk_path' do it 'returns disk_path based on hash of project id' do - hashed_path = '@hashed/6b/86/6b86b273ff34fce19d6b804eff5a3f5747ada4eaa22f1d49c01e52ddb7875b4b' + hashed_path = "@hashed/#{hash[0..1]}/#{hash[2..3]}/#{hash}" expect(project.disk_path).to eq(hashed_path) end @@ -2712,7 +2718,9 @@ describe Project do describe '#ensure_storage_path_exists' do it 'delegates to gitlab_shell to ensure namespace is created' do - expect(gitlab_shell).to receive(:add_namespace).with(project.repository_storage_path, '@hashed/6b/86') + allow(project).to receive(:gitlab_shell).and_return(gitlab_shell) + + expect(gitlab_shell).to receive(:add_namespace).with(project.repository_storage_path, "@hashed/#{hash[0..1]}/#{hash[2..3]}") project.ensure_storage_path_exists end @@ -2772,7 +2780,7 @@ describe Project do end context 'when not rolled out' do - let(:project) { create(:project, :repository, storage_version: 1) } + let(:project) { create(:project, :repository, storage_version: 1, skip_disk_validation: true) } it 'moves pages folder to new location' do expect_any_instance_of(Gitlab::UploadsTransfer).to receive(:rename_project) @@ -2781,6 +2789,12 @@ describe Project do end end end + + it 'updates project full path in .git/config' do + project.rename_repo + + expect(project.repo.config['gitlab.fullpath']).to eq(project.full_path) + end end describe '#pages_path' do @@ -3141,4 +3155,26 @@ describe Project do it { is_expected.to eq(platform_kubernetes) } end end + + describe '#write_repository_config' do + set(:project) { create(:project, :repository) } + + it 'writes full path in .git/config when key is missing' do + project.write_repository_config + + expect(project.repo.config['gitlab.fullpath']).to eq project.full_path + end + + it 'updates full path in .git/config when key is present' do + project.write_repository_config(gl_full_path: 'old/path') + + expect { project.write_repository_config }.to change { project.repo.config['gitlab.fullpath'] }.from('old/path').to(project.full_path) + end + + it 'does not raise an error with an empty repository' do + project = create(:project_empty_repo) + + expect { project.write_repository_config }.not_to raise_error + end + end end diff --git a/spec/models/project_team_spec.rb b/spec/models/project_team_spec.rb index 314824b32da..e07c522800a 100644 --- a/spec/models/project_team_spec.rb +++ b/spec/models/project_team_spec.rb @@ -291,8 +291,8 @@ describe ProjectTeam do group.add_master(master) group.add_developer(developer) - members_project.team << [developer, :developer] - members_project.team << [master, :master] + members_project.add_developer(developer) + members_project.add_master(master) create(:project_group_link, project: shared_project, group: group) end diff --git a/spec/models/service_spec.rb b/spec/models/service_spec.rb index 0f2f906c667..540615de117 100644 --- a/spec/models/service_spec.rb +++ b/spec/models/service_spec.rb @@ -254,4 +254,22 @@ describe Service do end end end + + describe "#deprecated?" do + let(:project) { create(:project, :repository) } + + it 'should return false by default' do + service = create(:service, project: project) + expect(service.deprecated?).to be_falsy + end + end + + describe "#deprecation_message" do + let(:project) { create(:project, :repository) } + + it 'should be empty by default' do + service = create(:service, project: project) + expect(service.deprecation_message).to be_nil + end + end end diff --git a/spec/models/user_spec.rb b/spec/models/user_spec.rb index e58e7588df0..8d0eaf565a7 100644 --- a/spec/models/user_spec.rb +++ b/spec/models/user_spec.rb @@ -22,7 +22,9 @@ describe User do describe 'associations' do it { is_expected.to have_one(:namespace) } it { is_expected.to have_many(:snippets).dependent(:destroy) } - it { is_expected.to have_many(:project_members).dependent(:destroy) } + it { is_expected.to have_many(:members) } + it { is_expected.to have_many(:project_members) } + it { is_expected.to have_many(:group_members) } it { is_expected.to have_many(:groups) } it { is_expected.to have_many(:keys).dependent(:destroy) } it { is_expected.to have_many(:deploy_keys).dependent(:destroy) } @@ -134,6 +136,16 @@ describe User do end end + it 'has a DB-level NOT NULL constraint on projects_limit' do + user = create(:user) + + expect(user.persisted?).to eq(true) + + expect do + user.update_columns(projects_limit: nil) + end.to raise_error(ActiveRecord::StatementInvalid) + end + it { is_expected.to validate_presence_of(:projects_limit) } it { is_expected.to validate_numericality_of(:projects_limit) } it { is_expected.to allow_value(0).for(:projects_limit) } @@ -760,7 +772,7 @@ describe User do before do # add user to project - project.team << [user, :master] + project.add_master(user) # create invite to projet create(:project_member, :developer, project: project, invite_token: '1234', invite_email: 'inviteduser1@example.com') @@ -805,6 +817,13 @@ describe User do expect(user.can_create_group).to be_falsey expect(user.theme_id).to eq(1) end + + it 'does not undo projects_limit setting if it matches old DB default of 10' do + # If the real default project limit is 10 then this test is worthless + expect(Gitlab.config.gitlab.default_projects_limit).not_to eq(10) + user = described_class.new(projects_limit: 10) + expect(user.projects_limit).to eq(10) + end end context 'when current_application_settings.user_default_external is true' do @@ -1448,8 +1467,8 @@ describe User do let!(:merge_event) { create(:event, :created, project: project3, target: merge_request, author: subject) } before do - project1.team << [subject, :master] - project2.team << [subject, :master] + project1.add_master(subject) + project2.add_master(subject) end it "includes IDs for projects the user has pushed to" do @@ -1548,7 +1567,7 @@ describe User do user = create(:user) project = create(:project, :private) - project.team << [user, Gitlab::Access::MASTER] + project.add_master(user) expect(user.authorized_projects(Gitlab::Access::REPORTER)) .to contain_exactly(project) @@ -1567,7 +1586,7 @@ describe User do user2 = create(:user) project = create(:project, :private, namespace: user1.namespace) - project.team << [user2, Gitlab::Access::DEVELOPER] + project.add_developer(user2) expect(user2.authorized_projects).to include(project) end @@ -1612,7 +1631,7 @@ describe User do user2 = create(:user) project = create(:project, :private, namespace: user1.namespace) - project.team << [user2, Gitlab::Access::DEVELOPER] + project.add_developer(user2) expect(user2.authorized_projects).to include(project) @@ -1702,7 +1721,7 @@ describe User do shared_examples :member do context 'when the user is a master' do before do - add_user(Gitlab::Access::MASTER) + add_user(:master) end it 'loads' do @@ -1712,7 +1731,7 @@ describe User do context 'when the user is a developer' do before do - add_user(Gitlab::Access::DEVELOPER) + add_user(:developer) end it 'does not load' do @@ -1736,7 +1755,7 @@ describe User do let(:project) { create(:project) } def add_user(access) - project.team << [user, access] + project.add_role(user, access) end it_behaves_like :member @@ -1749,8 +1768,8 @@ describe User do let(:user) { create(:user) } before do - project1.team << [user, :reporter] - project2.team << [user, :guest] + project1.add_reporter(user) + project2.add_guest(user) end it 'returns the projects when using a single project ID' do @@ -1892,8 +1911,8 @@ describe User do let(:user) { create(:user) } before do - project1.team << [user, :reporter] - project2.team << [user, :guest] + project1.add_reporter(user) + project2.add_guest(user) user.project_authorizations.delete_all user.refresh_authorized_projects diff --git a/spec/policies/ci/build_policy_spec.rb b/spec/policies/ci/build_policy_spec.rb index 298a9d16425..41cf2ef7225 100644 --- a/spec/policies/ci/build_policy_spec.rb +++ b/spec/policies/ci/build_policy_spec.rb @@ -57,7 +57,7 @@ describe Ci::BuildPolicy do context 'team member is a guest' do before do - project.team << [user, :guest] + project.add_guest(user) end context 'when public builds are enabled' do @@ -77,7 +77,7 @@ describe Ci::BuildPolicy do context 'team member is a reporter' do before do - project.team << [user, :reporter] + project.add_reporter(user) end context 'when public builds are enabled' do diff --git a/spec/policies/ci/trigger_policy_spec.rb b/spec/policies/ci/trigger_policy_spec.rb index be40dbb2aa9..14630748c90 100644 --- a/spec/policies/ci/trigger_policy_spec.rb +++ b/spec/policies/ci/trigger_policy_spec.rb @@ -45,7 +45,7 @@ describe Ci::TriggerPolicy do context 'when user is master of the project' do before do - project.team << [user, :master] + project.add_master(user) end it_behaves_like 'allows to admin and manage trigger' @@ -53,7 +53,7 @@ describe Ci::TriggerPolicy do context 'when user is developer of the project' do before do - project.team << [user, :developer] + project.add_developer(user) end it_behaves_like 'disallows to admin and manage trigger' @@ -69,7 +69,7 @@ describe Ci::TriggerPolicy do context 'when user is master of the project' do before do - project.team << [user, :master] + project.add_master(user) end it_behaves_like 'allows to admin and manage trigger' @@ -81,7 +81,7 @@ describe Ci::TriggerPolicy do context 'when user is master of the project' do before do - project.team << [user, :master] + project.add_master(user) end it_behaves_like 'allows to manage trigger' @@ -89,7 +89,7 @@ describe Ci::TriggerPolicy do context 'when user is developer of the project' do before do - project.team << [user, :developer] + project.add_developer(user) end it_behaves_like 'disallows to admin and manage trigger' diff --git a/spec/policies/issue_policy_spec.rb b/spec/policies/issue_policy_spec.rb index be4c24c727c..a4af9361ea6 100644 --- a/spec/policies/issue_policy_spec.rb +++ b/spec/policies/issue_policy_spec.rb @@ -19,10 +19,10 @@ describe IssuePolicy do let(:issue_no_assignee) { create(:issue, project: project) } before do - project.team << [guest, :guest] - project.team << [author, :guest] - project.team << [assignee, :guest] - project.team << [reporter, :reporter] + project.add_guest(guest) + project.add_guest(author) + project.add_guest(assignee) + project.add_reporter(reporter) group.add_reporter(reporter_from_group_link) @@ -114,8 +114,8 @@ describe IssuePolicy do let(:issue_no_assignee) { create(:issue, project: project) } before do - project.team << [guest, :guest] - project.team << [reporter, :reporter] + project.add_guest(guest) + project.add_reporter(reporter) group.add_reporter(reporter_from_group_link) diff --git a/spec/policies/project_snippet_policy_spec.rb b/spec/policies/project_snippet_policy_spec.rb index f0bf46c480a..cdba1b09fc1 100644 --- a/spec/policies/project_snippet_policy_spec.rb +++ b/spec/policies/project_snippet_policy_spec.rb @@ -87,7 +87,7 @@ describe ProjectSnippetPolicy do subject { abilities(external_user, :internal) } before do - project.team << [external_user, :developer] + project.add_developer(external_user) end it do @@ -131,7 +131,7 @@ describe ProjectSnippetPolicy do subject { abilities(regular_user, :private) } before do - project.team << [regular_user, :developer] + project.add_developer(regular_user) end it do @@ -144,7 +144,7 @@ describe ProjectSnippetPolicy do subject { abilities(external_user, :private) } before do - project.team << [external_user, :developer] + project.add_developer(external_user) end it do diff --git a/spec/presenters/merge_request_presenter_spec.rb b/spec/presenters/merge_request_presenter_spec.rb index f325d1776e4..969c4753f33 100644 --- a/spec/presenters/merge_request_presenter_spec.rb +++ b/spec/presenters/merge_request_presenter_spec.rb @@ -116,7 +116,7 @@ describe MergeRequestPresenter do end before do - project.team << [user, :developer] + project.add_developer(user) allow(resource.project).to receive(:default_branch) .and_return(resource.target_branch) @@ -270,7 +270,7 @@ describe MergeRequestPresenter do context 'when can create issue and issues enabled' do it 'returns path' do allow(project).to receive(:issues_enabled?) { true } - project.team << [user, :master] + project.add_master(user) is_expected .to eq("/#{resource.project.full_path}/issues/new?merge_request_to_resolve_discussions_of=#{resource.iid}") @@ -288,7 +288,7 @@ describe MergeRequestPresenter do context 'when issues disabled' do it 'returns nil' do allow(project).to receive(:issues_enabled?) { false } - project.team << [user, :master] + project.add_master(user) is_expected.to be_nil end @@ -307,7 +307,7 @@ describe MergeRequestPresenter do context 'when merge request enabled and has permission' do it 'has remove_wip_path' do allow(project).to receive(:merge_requests_enabled?) { true } - project.team << [user, :master] + project.add_master(user) is_expected .to eq("/#{resource.project.full_path}/merge_requests/#{resource.iid}/remove_wip") diff --git a/spec/requests/api/access_requests_spec.rb b/spec/requests/api/access_requests_spec.rb index 35ca3635a9d..24389f28b21 100644 --- a/spec/requests/api/access_requests_spec.rb +++ b/spec/requests/api/access_requests_spec.rb @@ -8,8 +8,8 @@ describe API::AccessRequests do set(:project) do create(:project, :public, :access_requestable, creator_id: master.id, namespace: master.namespace) do |project| - project.team << [developer, :developer] - project.team << [master, :master] + project.add_developer(developer) + project.add_master(master) project.request_access(access_requester) end end diff --git a/spec/requests/api/award_emoji_spec.rb b/spec/requests/api/award_emoji_spec.rb index eaf12f71421..5adfb33677f 100644 --- a/spec/requests/api/award_emoji_spec.rb +++ b/spec/requests/api/award_emoji_spec.rb @@ -10,7 +10,7 @@ describe API::AwardEmoji do set(:note) { create(:note, project: project, noteable: issue) } before do - project.team << [user, :master] + project.add_master(user) end describe "GET /projects/:id/awardable/:awardable_id/award_emoji" do diff --git a/spec/requests/api/boards_spec.rb b/spec/requests/api/boards_spec.rb index 546a1697e56..f65af69dc7f 100644 --- a/spec/requests/api/boards_spec.rb +++ b/spec/requests/api/boards_spec.rb @@ -33,8 +33,8 @@ describe API::Boards do end before do - project.team << [user, :reporter] - project.team << [guest, :guest] + project.add_reporter(user) + project.add_guest(guest) end describe "GET /projects/:id/boards" do diff --git a/spec/requests/api/deployments_spec.rb b/spec/requests/api/deployments_spec.rb index c7977e624ff..6732c99e329 100644 --- a/spec/requests/api/deployments_spec.rb +++ b/spec/requests/api/deployments_spec.rb @@ -7,7 +7,7 @@ describe API::Deployments do let!(:deployment) { create(:deployment) } before do - project.team << [user, :master] + project.add_master(user) end describe 'GET /projects/:id/deployments' do diff --git a/spec/requests/api/environments_spec.rb b/spec/requests/api/environments_spec.rb index 3665cfd7241..53d48a91007 100644 --- a/spec/requests/api/environments_spec.rb +++ b/spec/requests/api/environments_spec.rb @@ -7,7 +7,7 @@ describe API::Environments do let!(:environment) { create(:environment, project: project) } before do - project.team << [user, :master] + project.add_master(user) end describe 'GET /projects/:id/environments' do diff --git a/spec/requests/api/files_spec.rb b/spec/requests/api/files_spec.rb index 5d8338a3fb7..d8fdfd6dee1 100644 --- a/spec/requests/api/files_spec.rb +++ b/spec/requests/api/files_spec.rb @@ -14,7 +14,7 @@ describe API::Files do let(:author_name) { 'John Doe' } before do - project.team << [user, :developer] + project.add_developer(user) end def route(file_path = nil) diff --git a/spec/requests/api/groups_spec.rb b/spec/requests/api/groups_spec.rb index 6330c140246..3c0b4728dc2 100644 --- a/spec/requests/api/groups_spec.rb +++ b/spec/requests/api/groups_spec.rb @@ -417,7 +417,7 @@ describe API::Groups do end it "only returns projects to which user has access" do - project3.team << [user3, :developer] + project3.add_developer(user3) get api("/groups/#{group1.id}/projects", user3) diff --git a/spec/requests/api/internal_spec.rb b/spec/requests/api/internal_spec.rb index 3c31980b273..7b25047ea8f 100644 --- a/spec/requests/api/internal_spec.rb +++ b/spec/requests/api/internal_spec.rb @@ -147,7 +147,7 @@ describe API::Internal do describe "POST /internal/lfs_authenticate" do before do - project.team << [user, :developer] + project.add_developer(user) end context 'user key' do @@ -199,7 +199,7 @@ describe API::Internal do end before do - project.team << [user, :developer] + project.add_developer(user) end context 'with env passed as a JSON' do @@ -359,7 +359,7 @@ describe API::Internal do context "access denied" do before do - project.team << [user, :guest] + project.add_guest(user) end context "git pull" do @@ -413,7 +413,7 @@ describe API::Internal do context "archived project" do before do - project.team << [user, :developer] + project.add_developer(user) project.archive! end @@ -527,7 +527,7 @@ describe API::Internal do context 'web actions are always allowed' do it 'allows WEB push' do stub_application_setting(enabled_git_access_protocol: 'ssh') - project.team << [user, :developer] + project.add_developer(user) push(key, project, 'web') expect(response.status).to eq(200) @@ -540,7 +540,7 @@ describe API::Internal do let!(:repository) { project.repository } before do - project.team << [user, :developer] + project.add_developer(user) project.path = 'new_path' project.save! end @@ -566,7 +566,7 @@ describe API::Internal do let(:changes) { URI.escape("#{Gitlab::Git::BLANK_SHA} 570e7b2abdd848b95f2f578043fc23bd6f6fd24d refs/heads/new_branch") } before do - project.team << [user, :developer] + project.add_developer(user) end it 'returns link to create new merge request' do @@ -701,7 +701,7 @@ describe API::Internal do end before do - project.team << [user, :developer] + project.add_developer(user) allow(described_class).to receive(:identify).and_return(user) allow_any_instance_of(Gitlab::Identifier).to receive(:identify).and_return(user) end @@ -784,6 +784,16 @@ describe API::Internal do expect(json_response["redirected_message"]).to eq(project_moved.redirect_message) end end + + context 'with an orphaned write deploy key' do + it 'does not try to notify that project moved' do + allow_any_instance_of(Gitlab::Identifier).to receive(:identify).and_return(nil) + + post api("/internal/post_receive"), valid_params + + expect(response).to have_gitlab_http_status(200) + end + end end describe 'POST /internal/pre_receive' do diff --git a/spec/requests/api/issues_spec.rb b/spec/requests/api/issues_spec.rb index 3f5070a1fd2..00d9c795619 100644 --- a/spec/requests/api/issues_spec.rb +++ b/spec/requests/api/issues_spec.rb @@ -58,8 +58,8 @@ describe API::Issues, :mailer do let(:no_milestone_title) { URI.escape(Milestone::None.title) } before(:all) do - project.team << [user, :reporter] - project.team << [guest, :guest] + project.add_reporter(user) + project.add_guest(guest) end describe "GET /issues" do @@ -344,7 +344,7 @@ describe API::Issues, :mailer do let!(:group_note) { create(:note_on_issue, author: user, project: group_project, noteable: group_issue) } before do - group_project.team << [user, :reporter] + group_project.add_reporter(user) end let(:base_url) { "/groups/#{group.id}/issues" } @@ -967,7 +967,7 @@ describe API::Issues, :mailer do let(:project) { merge_request.source_project } before do - project.team << [user, :master] + project.add_master(user) end context 'resolving all discussions in a merge request' do diff --git a/spec/requests/api/jobs_spec.rb b/spec/requests/api/jobs_spec.rb index 2a83213e87a..e77745acbb7 100644 --- a/spec/requests/api/jobs_spec.rb +++ b/spec/requests/api/jobs_spec.rb @@ -503,7 +503,7 @@ describe API::Jobs do let(:role) { :master } before do - project.team << [user, role] + project.add_role(user, role) post api("/projects/#{project.id}/jobs/#{job.id}/erase", user) end diff --git a/spec/requests/api/labels_spec.rb b/spec/requests/api/labels_spec.rb index 3498e5bc8d9..34cbf75f4c1 100644 --- a/spec/requests/api/labels_spec.rb +++ b/spec/requests/api/labels_spec.rb @@ -7,7 +7,7 @@ describe API::Labels do let!(:priority_label) { create(:label, title: 'bug', project: project, priority: 3) } before do - project.team << [user, :master] + project.add_master(user) end describe 'GET /projects/:id/labels' do diff --git a/spec/requests/api/members_spec.rb b/spec/requests/api/members_spec.rb index 3349e396ab8..5d4f81e07a6 100644 --- a/spec/requests/api/members_spec.rb +++ b/spec/requests/api/members_spec.rb @@ -8,8 +8,8 @@ describe API::Members do let(:project) do create(:project, :public, :access_requestable, creator_id: master.id, namespace: master.namespace) do |project| - project.team << [developer, :developer] - project.team << [master, :master] + project.add_developer(developer) + project.add_master(master) project.request_access(access_requester) end end diff --git a/spec/requests/api/merge_request_diffs_spec.rb b/spec/requests/api/merge_request_diffs_spec.rb index bf4c8443b23..cb647aee70f 100644 --- a/spec/requests/api/merge_request_diffs_spec.rb +++ b/spec/requests/api/merge_request_diffs_spec.rb @@ -8,7 +8,7 @@ describe API::MergeRequestDiffs, 'MergeRequestDiffs' do before do merge_request.merge_request_diffs.create(head_commit_sha: '6f6d7e7ed97bb5f0054f2b1df789b39ca89b6ff9') merge_request.merge_request_diffs.create(head_commit_sha: '5937ac0a7beb003549fc5fd26fc247adbce4a52e') - project.team << [user, :master] + project.add_master(user) end describe 'GET /projects/:id/merge_requests/:merge_request_iid/versions' do diff --git a/spec/requests/api/merge_requests_spec.rb b/spec/requests/api/merge_requests_spec.rb index 60dbd74d59d..ef3f610740d 100644 --- a/spec/requests/api/merge_requests_spec.rb +++ b/spec/requests/api/merge_requests_spec.rb @@ -25,7 +25,7 @@ describe API::MergeRequests do let!(:upvote) { create(:award_emoji, :upvote, awardable: merge_request) } before do - project.team << [user, :reporter] + project.add_reporter(user) end describe 'GET /merge_requests' do @@ -730,7 +730,7 @@ describe API::MergeRequests do let(:developer) { create(:user) } before do - project.team << [developer, :developer] + project.add_developer(developer) end it "denies the deletion of the merge request" do @@ -808,7 +808,7 @@ describe API::MergeRequests do it "returns 401 if user has no permissions to merge" do user2 = create(:user) - project.team << [user2, :reporter] + project.add_reporter(user2) put api("/projects/#{project.id}/merge_requests/#{merge_request.iid}/merge", user2) expect(response).to have_gitlab_http_status(401) expect(json_response['message']).to eq('401 Unauthorized') @@ -997,7 +997,7 @@ describe API::MergeRequests do project = create(:project, :private) merge_request = create(:merge_request, :simple, source_project: project) guest = create(:user) - project.team << [guest, :guest] + project.add_guest(guest) get api("/projects/#{project.id}/merge_requests/#{merge_request.iid}/closes_issues", guest) @@ -1045,7 +1045,7 @@ describe API::MergeRequests do it 'returns 403 if user has no access to read code' do guest = create(:user) - project.team << [guest, :guest] + project.add_guest(guest) post api("/projects/#{project.id}/merge_requests/#{merge_request.iid}/subscribe", guest) @@ -1081,7 +1081,7 @@ describe API::MergeRequests do it 'returns 403 if user has no access to read code' do guest = create(:user) - project.team << [guest, :guest] + project.add_guest(guest) post api("/projects/#{project.id}/merge_requests/#{merge_request.iid}/unsubscribe", guest) diff --git a/spec/requests/api/notes_spec.rb b/spec/requests/api/notes_spec.rb index 3bfb4c5506f..981c9c27325 100644 --- a/spec/requests/api/notes_spec.rb +++ b/spec/requests/api/notes_spec.rb @@ -14,7 +14,7 @@ describe API::Notes do let(:private_user) { create(:user) } let(:private_project) do create(:project, namespace: private_user.namespace) - .tap { |p| p.team << [private_user, :master] } + .tap { |p| p.add_master(private_user) } end let(:private_issue) { create(:issue, project: private_project) } @@ -29,7 +29,7 @@ describe API::Notes do end before do - project.team << [user, :reporter] + project.add_reporter(user) end describe "GET /projects/:id/noteable/:noteable_id/notes" do @@ -464,7 +464,7 @@ describe API::Notes do describe "POST /projects/:id/noteable/:noteable_id/notes to test observer on create" do it "creates an activity event when an issue note is created" do - expect(Event).to receive(:create) + expect(Event).to receive(:create!) post api("/projects/#{project.id}/issues/#{issue.iid}/notes", user), body: 'hi!' end diff --git a/spec/requests/api/pipelines_spec.rb b/spec/requests/api/pipelines_spec.rb index e4dcc9252fa..0736329f9fd 100644 --- a/spec/requests/api/pipelines_spec.rb +++ b/spec/requests/api/pipelines_spec.rb @@ -11,7 +11,7 @@ describe API::Pipelines do end before do - project.team << [user, :master] + project.add_master(user) end describe 'GET /projects/:id/pipelines ' do @@ -424,7 +424,7 @@ describe API::Pipelines do let!(:reporter) { create(:user) } before do - project.team << [reporter, :reporter] + project.add_reporter(reporter) end it 'rejects the action' do diff --git a/spec/requests/api/project_hooks_spec.rb b/spec/requests/api/project_hooks_spec.rb index f31344a6238..1fd082ecc38 100644 --- a/spec/requests/api/project_hooks_spec.rb +++ b/spec/requests/api/project_hooks_spec.rb @@ -13,8 +13,8 @@ describe API::ProjectHooks, 'ProjectHooks' do end before do - project.team << [user, :master] - project.team << [user3, :developer] + project.add_master(user) + project.add_developer(user3) end describe "GET /projects/:id/hooks" do @@ -206,7 +206,7 @@ describe API::ProjectHooks, 'ProjectHooks' do it "returns a 404 if a user attempts to delete project hooks he/she does not own" do test_user = create(:user) other_project = create(:project) - other_project.team << [test_user, :master] + other_project.add_master(test_user) delete api("/projects/#{other_project.id}/hooks/#{hook.id}", test_user) expect(response).to have_gitlab_http_status(404) diff --git a/spec/requests/api/project_milestones_spec.rb b/spec/requests/api/project_milestones_spec.rb index 72e1574b55f..08ea7314bb3 100644 --- a/spec/requests/api/project_milestones_spec.rb +++ b/spec/requests/api/project_milestones_spec.rb @@ -7,7 +7,7 @@ describe API::ProjectMilestones do let!(:milestone) { create(:milestone, project: project, title: 'version2', description: 'open milestone') } before do - project.team << [user, :developer] + project.add_developer(user) end it_behaves_like 'group and project milestones', "/projects/:id/milestones" do @@ -16,7 +16,7 @@ describe API::ProjectMilestones do describe 'PUT /projects/:id/milestones/:milestone_id to test observer on close' do it 'creates an activity event when an milestone is closed' do - expect(Event).to receive(:create) + expect(Event).to receive(:create!) put api("/projects/#{project.id}/milestones/#{milestone.id}", user), state_event: 'close' diff --git a/spec/requests/api/projects_spec.rb b/spec/requests/api/projects_spec.rb index a41345da05b..de1763015fa 100644 --- a/spec/requests/api/projects_spec.rb +++ b/spec/requests/api/projects_spec.rb @@ -908,7 +908,7 @@ describe API::Projects do describe 'permissions' do context 'all projects' do before do - project.team << [user, :master] + project.add_master(user) end it 'contains permission information' do @@ -923,7 +923,7 @@ describe API::Projects do context 'personal project' do it 'sets project access and returns 200' do - project.team << [user, :master] + project.add_master(user) get api("/projects/#{project.id}", user) expect(response).to have_gitlab_http_status(200) @@ -1539,7 +1539,7 @@ describe API::Projects do context 'user without archiving rights to the project' do before do - project.team << [user3, :developer] + project.add_developer(user3) end it 'rejects the action' do @@ -1575,7 +1575,7 @@ describe API::Projects do context 'user without archiving rights to the project' do before do - project.team << [user3, :developer] + project.add_developer(user3) end it 'rejects the action' do @@ -1650,7 +1650,7 @@ describe API::Projects do it 'does not remove a project if not an owner' do user3 = create(:user) - project.team << [user3, :developer] + project.add_developer(user3) delete api("/projects/#{project.id}", user3) expect(response).to have_gitlab_http_status(403) end diff --git a/spec/requests/api/services_spec.rb b/spec/requests/api/services_spec.rb index ba697e2b305..26d56c04862 100644 --- a/spec/requests/api/services_spec.rb +++ b/spec/requests/api/services_spec.rb @@ -53,6 +53,10 @@ describe API::Services do describe "DELETE /projects/:id/services/#{service.dasherize}" do include_context service + before do + initialize_service(service) + end + it "deletes #{service}" do delete api("/projects/#{project.id}/services/#{dashed_service}", user) @@ -67,9 +71,7 @@ describe API::Services do # inject some properties into the service before do - service_object = project.find_or_initialize_service(service) - service_object.properties = service_attrs - service_object.save + initialize_service(service) end it 'returns authentication error when unauthenticated' do @@ -92,7 +94,7 @@ describe API::Services do end it "returns error when authenticated but not a project owner" do - project.team << [user2, :developer] + project.add_developer(user2) get api("/projects/#{project.id}/services/#{dashed_service}", user2) expect(response).to have_gitlab_http_status(403) diff --git a/spec/requests/api/todos_spec.rb b/spec/requests/api/todos_spec.rb index c6063a2e089..fb3a33cadff 100644 --- a/spec/requests/api/todos_spec.rb +++ b/spec/requests/api/todos_spec.rb @@ -13,8 +13,8 @@ describe API::Todos do let!(:done) { create(:todo, :done, project: project_1, author: author_1, user: john_doe) } before do - project_1.team << [john_doe, :developer] - project_2.team << [john_doe, :developer] + project_1.add_developer(john_doe) + project_2.add_developer(john_doe) end describe 'GET /todos' do @@ -191,7 +191,7 @@ describe API::Todos do it 'returns an error if the issuable is not accessible' do guest = create(:user) - project_1.team << [guest, :guest] + project_1.add_guest(guest) post api("/projects/#{project_1.id}/#{issuable_type}/#{issuable.iid}/todo", guest) diff --git a/spec/requests/api/v3/award_emoji_spec.rb b/spec/requests/api/v3/award_emoji_spec.rb index 0cd8b70007f..6dc430676b0 100644 --- a/spec/requests/api/v3/award_emoji_spec.rb +++ b/spec/requests/api/v3/award_emoji_spec.rb @@ -9,7 +9,7 @@ describe API::V3::AwardEmoji do let!(:downvote) { create(:award_emoji, :downvote, awardable: merge_request, user: user) } set(:note) { create(:note, project: project, noteable: issue) } - before { project.team << [user, :master] } + before { project.add_master(user) } describe "GET /projects/:id/awardable/:awardable_id/award_emoji" do context 'on an issue' do diff --git a/spec/requests/api/v3/boards_spec.rb b/spec/requests/api/v3/boards_spec.rb index 14409d25544..dde4f096193 100644 --- a/spec/requests/api/v3/boards_spec.rb +++ b/spec/requests/api/v3/boards_spec.rb @@ -27,8 +27,8 @@ describe API::V3::Boards do end before do - project.team << [user, :reporter] - project.team << [guest, :guest] + project.add_reporter(user) + project.add_guest(guest) end describe "GET /projects/:id/boards" do diff --git a/spec/requests/api/v3/commits_spec.rb b/spec/requests/api/v3/commits_spec.rb index d31c94ddd2c..8b115e01f47 100644 --- a/spec/requests/api/v3/commits_spec.rb +++ b/spec/requests/api/v3/commits_spec.rb @@ -9,11 +9,11 @@ describe API::V3::Commits do let!(:note) { create(:note_on_commit, author: user, project: project, commit_id: project.repository.commit.id, note: 'a comment on a commit') } let!(:another_note) { create(:note_on_commit, author: user, project: project, commit_id: project.repository.commit.id, note: 'another comment on a commit') } - before { project.team << [user, :reporter] } + before { project.add_reporter(user) } describe "List repository commits" do context "authorized user" do - before { project.team << [user2, :reporter] } + before { project.add_reporter(user2) } it "returns project commits" do commit = project.repository.commit @@ -415,7 +415,7 @@ describe API::V3::Commits do describe "Get the diff of a commit" do context "authorized user" do - before { project.team << [user2, :reporter] } + before { project.add_reporter(user2) } it "returns the diff of the selected commit" do get v3_api("/projects/#{project.id}/repository/commits/#{project.repository.commit.id}/diff", user) @@ -487,7 +487,7 @@ describe API::V3::Commits do end it 'returns 400 if you are not allowed to push to the target branch' do - project.team << [user2, :developer] + project.add_developer(user2) protected_branch = create(:protected_branch, project: project, name: 'feature') post v3_api("/projects/#{project.id}/repository/commits/#{master_pickable_commit.id}/cherry_pick", user2), branch: protected_branch.name diff --git a/spec/requests/api/v3/deployments_spec.rb b/spec/requests/api/v3/deployments_spec.rb index 90eabda4dac..ac86fbea498 100644 --- a/spec/requests/api/v3/deployments_spec.rb +++ b/spec/requests/api/v3/deployments_spec.rb @@ -7,7 +7,7 @@ describe API::V3::Deployments do let!(:deployment) { create(:deployment) } before do - project.team << [user, :master] + project.add_master(user) end shared_examples 'a paginated resources' do diff --git a/spec/requests/api/v3/environments_spec.rb b/spec/requests/api/v3/environments_spec.rb index 937250b5219..68be5256b64 100644 --- a/spec/requests/api/v3/environments_spec.rb +++ b/spec/requests/api/v3/environments_spec.rb @@ -7,7 +7,7 @@ describe API::V3::Environments do let!(:environment) { create(:environment, project: project) } before do - project.team << [user, :master] + project.add_master(user) end shared_examples 'a paginated resources' do diff --git a/spec/requests/api/v3/files_spec.rb b/spec/requests/api/v3/files_spec.rb index 5500c1cf770..26a3d8870a0 100644 --- a/spec/requests/api/v3/files_spec.rb +++ b/spec/requests/api/v3/files_spec.rb @@ -27,7 +27,7 @@ describe API::V3::Files do let(:author_email) { 'user@example.org' } let(:author_name) { 'John Doe' } - before { project.team << [user, :developer] } + before { project.add_developer(user) } describe "GET /projects/:id/repository/files" do let(:route) { "/projects/#{project.id}/repository/files" } diff --git a/spec/requests/api/v3/groups_spec.rb b/spec/requests/api/v3/groups_spec.rb index 498cb42fad1..a1cdf583de3 100644 --- a/spec/requests/api/v3/groups_spec.rb +++ b/spec/requests/api/v3/groups_spec.rb @@ -330,7 +330,7 @@ describe API::V3::Groups do end it "only returns projects to which user has access" do - project3.team << [user3, :developer] + project3.add_developer(user3) get v3_api("/groups/#{group1.id}/projects", user3) diff --git a/spec/requests/api/v3/issues_spec.rb b/spec/requests/api/v3/issues_spec.rb index 39a47a62f16..0dd6d673625 100644 --- a/spec/requests/api/v3/issues_spec.rb +++ b/spec/requests/api/v3/issues_spec.rb @@ -50,8 +50,8 @@ describe API::V3::Issues, :mailer do let(:no_milestone_title) { URI.escape(Milestone::None.title) } before do - project.team << [user, :reporter] - project.team << [guest, :guest] + project.add_reporter(user) + project.add_guest(guest) end describe "GET /issues" do @@ -278,7 +278,7 @@ describe API::V3::Issues, :mailer do let!(:group_note) { create(:note_on_issue, author: user, project: group_project, noteable: group_issue) } before do - group_project.team << [user, :reporter] + group_project.add_reporter(user) end let(:base_url) { "/groups/#{group.id}/issues" } @@ -827,7 +827,7 @@ describe API::V3::Issues, :mailer do let(:merge_request) { discussion.noteable } let(:project) { merge_request.source_project } before do - project.team << [user, :master] + project.add_master(user) post v3_api("/projects/#{project.id}/issues", user), title: 'New Issue', merge_request_for_resolving_discussions: merge_request.iid diff --git a/spec/requests/api/v3/labels_spec.rb b/spec/requests/api/v3/labels_spec.rb index 1d31213d5ca..cdab4d2bd73 100644 --- a/spec/requests/api/v3/labels_spec.rb +++ b/spec/requests/api/v3/labels_spec.rb @@ -7,7 +7,7 @@ describe API::V3::Labels do let!(:priority_label) { create(:label, title: 'bug', project: project, priority: 3) } before do - project.team << [user, :master] + project.add_master(user) end describe 'GET /projects/:id/labels' do diff --git a/spec/requests/api/v3/members_spec.rb b/spec/requests/api/v3/members_spec.rb index 68be3d24c26..b91782ae511 100644 --- a/spec/requests/api/v3/members_spec.rb +++ b/spec/requests/api/v3/members_spec.rb @@ -8,8 +8,8 @@ describe API::V3::Members do let(:project) do create(:project, :public, :access_requestable, creator_id: master.id, namespace: master.namespace) do |project| - project.team << [developer, :developer] - project.team << [master, :master] + project.add_developer(developer) + project.add_master(master) project.request_access(access_requester) end end diff --git a/spec/requests/api/v3/merge_request_diffs_spec.rb b/spec/requests/api/v3/merge_request_diffs_spec.rb index e613036a88d..547c066fadc 100644 --- a/spec/requests/api/v3/merge_request_diffs_spec.rb +++ b/spec/requests/api/v3/merge_request_diffs_spec.rb @@ -8,7 +8,7 @@ describe API::V3::MergeRequestDiffs, 'MergeRequestDiffs' do before do merge_request.merge_request_diffs.create(head_commit_sha: '6f6d7e7ed97bb5f0054f2b1df789b39ca89b6ff9') merge_request.merge_request_diffs.create(head_commit_sha: '5937ac0a7beb003549fc5fd26fc247adbce4a52e') - project.team << [user, :master] + project.add_master(user) end describe 'GET /projects/:id/merge_requests/:merge_request_id/versions' do diff --git a/spec/requests/api/v3/merge_requests_spec.rb b/spec/requests/api/v3/merge_requests_spec.rb index 2e2b9449429..b8b7d9d1c40 100644 --- a/spec/requests/api/v3/merge_requests_spec.rb +++ b/spec/requests/api/v3/merge_requests_spec.rb @@ -14,7 +14,7 @@ describe API::MergeRequests do let(:milestone) { create(:milestone, title: '1.0.0', project: project) } before do - project.team << [user, :reporter] + project.add_reporter(user) end describe "GET /projects/:id/merge_requests" do @@ -396,7 +396,7 @@ describe API::MergeRequests do let(:developer) { create(:user) } before do - project.team << [developer, :developer] + project.add_developer(developer) end it "denies the deletion of the merge request" do @@ -458,7 +458,7 @@ describe API::MergeRequests do it "returns 401 if user has no permissions to merge" do user2 = create(:user) - project.team << [user2, :reporter] + project.add_reporter(user2) put v3_api("/projects/#{project.id}/merge_requests/#{merge_request.id}/merge", user2) expect(response).to have_gitlab_http_status(401) expect(json_response['message']).to eq('401 Unauthorized') @@ -645,7 +645,7 @@ describe API::MergeRequests do project = create(:project, :private, :repository) merge_request = create(:merge_request, :simple, source_project: project) guest = create(:user) - project.team << [guest, :guest] + project.add_guest(guest) get v3_api("/projects/#{project.id}/merge_requests/#{merge_request.id}/closes_issues", guest) @@ -675,7 +675,7 @@ describe API::MergeRequests do it 'returns 403 if user has no access to read code' do guest = create(:user) - project.team << [guest, :guest] + project.add_guest(guest) post v3_api("/projects/#{project.id}/merge_requests/#{merge_request.id}/subscription", guest) @@ -705,7 +705,7 @@ describe API::MergeRequests do it 'returns 403 if user has no access to read code' do guest = create(:user) - project.team << [guest, :guest] + project.add_guest(guest) delete v3_api("/projects/#{project.id}/merge_requests/#{merge_request.id}/subscription", guest) diff --git a/spec/requests/api/v3/milestones_spec.rb b/spec/requests/api/v3/milestones_spec.rb index e82f35598a6..6021600e09c 100644 --- a/spec/requests/api/v3/milestones_spec.rb +++ b/spec/requests/api/v3/milestones_spec.rb @@ -6,7 +6,7 @@ describe API::V3::Milestones do let!(:closed_milestone) { create(:closed_milestone, project: project) } let!(:milestone) { create(:milestone, project: project) } - before { project.team << [user, :developer] } + before { project.add_developer(user) } describe 'GET /projects/:id/milestones' do it 'returns project milestones' do @@ -161,7 +161,7 @@ describe API::V3::Milestones do describe 'PUT /projects/:id/milestones/:milestone_id to test observer on close' do it 'creates an activity event when an milestone is closed' do - expect(Event).to receive(:create) + expect(Event).to receive(:create!) put v3_api("/projects/#{project.id}/milestones/#{milestone.id}", user), state_event: 'close' @@ -200,7 +200,7 @@ describe API::V3::Milestones do let(:confidential_issue) { create(:issue, confidential: true, project: public_project) } before do - public_project.team << [user, :developer] + public_project.add_developer(user) milestone.issues << issue << confidential_issue end @@ -215,7 +215,7 @@ describe API::V3::Milestones do it 'does not return confidential issues to team members with guest role' do member = create(:user) - project.team << [member, :guest] + project.add_guest(member) get v3_api("/projects/#{public_project.id}/milestones/#{milestone.id}/issues", member) diff --git a/spec/requests/api/v3/notes_spec.rb b/spec/requests/api/v3/notes_spec.rb index d3455a4bba4..5532795ab02 100644 --- a/spec/requests/api/v3/notes_spec.rb +++ b/spec/requests/api/v3/notes_spec.rb @@ -14,7 +14,7 @@ describe API::V3::Notes do let(:private_user) { create(:user) } let(:private_project) do create(:project, namespace: private_user.namespace) - .tap { |p| p.team << [private_user, :master] } + .tap { |p| p.add_master(private_user) } end let(:private_issue) { create(:issue, project: private_project) } @@ -28,7 +28,7 @@ describe API::V3::Notes do system: true end - before { project.team << [user, :reporter] } + before { project.add_reporter(user) } describe "GET /projects/:id/noteable/:noteable_id/notes" do context "when noteable is an Issue" do @@ -302,7 +302,7 @@ describe API::V3::Notes do describe "POST /projects/:id/noteable/:noteable_id/notes to test observer on create" do it "creates an activity event when an issue note is created" do - expect(Event).to receive(:create) + expect(Event).to receive(:create!) post v3_api("/projects/#{project.id}/issues/#{issue.id}/notes", user), body: 'hi!' end diff --git a/spec/requests/api/v3/pipelines_spec.rb b/spec/requests/api/v3/pipelines_spec.rb index 1c7d9fe32bb..ea943f22c41 100644 --- a/spec/requests/api/v3/pipelines_spec.rb +++ b/spec/requests/api/v3/pipelines_spec.rb @@ -10,7 +10,7 @@ describe API::V3::Pipelines do ref: project.default_branch) end - before { project.team << [user, :master] } + before { project.add_master(user) } shared_examples 'a paginated resources' do before do @@ -188,7 +188,7 @@ describe API::V3::Pipelines do context 'user without proper access rights' do let!(:reporter) { create(:user) } - before { project.team << [reporter, :reporter] } + before { project.add_reporter(reporter) } it 'rejects the action' do post v3_api("/projects/#{project.id}/pipelines/#{pipeline.id}/cancel", reporter) diff --git a/spec/requests/api/v3/project_hooks_spec.rb b/spec/requests/api/v3/project_hooks_spec.rb index 00f59744a31..248ae97f875 100644 --- a/spec/requests/api/v3/project_hooks_spec.rb +++ b/spec/requests/api/v3/project_hooks_spec.rb @@ -13,8 +13,8 @@ describe API::ProjectHooks, 'ProjectHooks' do end before do - project.team << [user, :master] - project.team << [user3, :developer] + project.add_master(user) + project.add_developer(user3) end describe "GET /projects/:id/hooks" do @@ -205,7 +205,7 @@ describe API::ProjectHooks, 'ProjectHooks' do it "returns a 404 if a user attempts to delete project hooks he/she does not own" do test_user = create(:user) other_project = create(:project) - other_project.team << [test_user, :master] + other_project.add_master(test_user) delete v3_api("/projects/#{other_project.id}/hooks/#{hook.id}", test_user) expect(response).to have_gitlab_http_status(404) diff --git a/spec/requests/api/v3/projects_spec.rb b/spec/requests/api/v3/projects_spec.rb index 27288b98d1c..13e465e0b2d 100644 --- a/spec/requests/api/v3/projects_spec.rb +++ b/spec/requests/api/v3/projects_spec.rb @@ -753,7 +753,7 @@ describe API::V3::Projects do describe 'permissions' do context 'all projects' do - before { project.team << [user, :master] } + before { project.add_master(user) } it 'contains permission information' do get v3_api("/projects", user) @@ -767,7 +767,7 @@ describe API::V3::Projects do context 'personal project' do it 'sets project access and returns 200' do - project.team << [user, :master] + project.add_master(user) get v3_api("/projects/#{project.id}", user) expect(response).to have_gitlab_http_status(200) @@ -1362,7 +1362,7 @@ describe API::V3::Projects do context 'user without archiving rights to the project' do before do - project.team << [user3, :developer] + project.add_developer(user3) end it 'rejects the action' do @@ -1398,7 +1398,7 @@ describe API::V3::Projects do context 'user without archiving rights to the project' do before do - project.team << [user3, :developer] + project.add_developer(user3) end it 'rejects the action' do @@ -1466,7 +1466,7 @@ describe API::V3::Projects do it 'does not remove a project if not an owner' do user3 = create(:user) - project.team << [user3, :developer] + project.add_developer(user3) delete v3_api("/projects/#{project.id}", user3) expect(response).to have_gitlab_http_status(403) end diff --git a/spec/requests/api/v3/services_spec.rb b/spec/requests/api/v3/services_spec.rb index 8f212ab6be6..c69a7d58ca6 100644 --- a/spec/requests/api/v3/services_spec.rb +++ b/spec/requests/api/v3/services_spec.rb @@ -10,6 +10,10 @@ describe API::V3::Services do describe "DELETE /projects/:id/services/#{service.dasherize}" do include_context service + before do + initialize_service(service) + end + it "deletes #{service}" do delete v3_api("/projects/#{project.id}/services/#{dashed_service}", user) diff --git a/spec/requests/api/v3/todos_spec.rb b/spec/requests/api/v3/todos_spec.rb index 8f5c3fbf8dd..53fd962272a 100644 --- a/spec/requests/api/v3/todos_spec.rb +++ b/spec/requests/api/v3/todos_spec.rb @@ -12,8 +12,8 @@ describe API::V3::Todos do let!(:done) { create(:todo, :done, project: project_1, author: author_1, user: john_doe) } before do - project_1.team << [john_doe, :developer] - project_2.team << [john_doe, :developer] + project_1.add_developer(john_doe) + project_2.add_developer(john_doe) end describe 'DELETE /todos/:id' do diff --git a/spec/requests/api/wikis_spec.rb b/spec/requests/api/wikis_spec.rb index 65bd001e491..fb0806ff9f1 100644 --- a/spec/requests/api/wikis_spec.rb +++ b/spec/requests/api/wikis_spec.rb @@ -12,6 +12,8 @@ require 'spec_helper' describe API::Wikis do let(:user) { create(:user) } + let(:group) { create(:group).tap { |g| g.add_owner(user) } } + let(:project_wiki) { create(:project_wiki, project: project, user: user) } let(:payload) { { content: 'content', format: 'rdoc', title: 'title' } } let(:expected_keys_with_content) { %w(content format slug title) } let(:expected_keys_without_content) { %w(format slug title) } @@ -19,8 +21,8 @@ describe API::Wikis do shared_examples_for 'returns list of wiki pages' do context 'when wiki has pages' do let!(:pages) do - [create(:wiki_page, wiki: project.wiki, attrs: { title: 'page1', content: 'content of page1' }), - create(:wiki_page, wiki: project.wiki, attrs: { title: 'page2', content: 'content of page2' })] + [create(:wiki_page, wiki: project_wiki, attrs: { title: 'page1', content: 'content of page1' }), + create(:wiki_page, wiki: project_wiki, attrs: { title: 'page2', content: 'content of page2' })] end it 'returns the list of wiki pages without content' do @@ -445,7 +447,7 @@ describe API::Wikis do end describe 'PUT /projects/:id/wikis/:slug' do - let(:page) { create(:wiki_page, wiki: project.wiki) } + let(:page) { create(:wiki_page, wiki: project_wiki) } let(:payload) { { title: 'new title', content: 'new content' } } let(:url) { "/projects/#{project.id}/wikis/#{page.slug}" } @@ -568,10 +570,20 @@ describe API::Wikis do end end end + + context 'when wiki belongs to a group project' do + let(:project) { create(:project, namespace: group) } + + before do + put(api(url, user), payload) + end + + include_examples 'updates wiki page' + end end describe 'DELETE /projects/:id/wikis/:slug' do - let(:page) { create(:wiki_page, wiki: project.wiki) } + let(:page) { create(:wiki_page, wiki: project_wiki) } let(:url) { "/projects/#{project.id}/wikis/#{page.slug}" } context 'when wiki is disabled' do @@ -675,5 +687,15 @@ describe API::Wikis do end end end + + context 'when wiki belongs to a group project' do + let(:project) { create(:project, namespace: group) } + + before do + delete(api(url, user)) + end + + include_examples '204 No Content' + end end end diff --git a/spec/requests/git_http_spec.rb b/spec/requests/git_http_spec.rb index fa02fffc82a..27bd22d6bca 100644 --- a/spec/requests/git_http_spec.rb +++ b/spec/requests/git_http_spec.rb @@ -149,7 +149,7 @@ describe 'Git HTTP requests' do context 'and as a developer on the team' do before do - project.team << [user, :developer] + project.add_developer(user) end context 'but the repo is disabled' do @@ -182,7 +182,7 @@ describe 'Git HTTP requests' do context 'when authenticated' do context 'and as a developer on the team' do before do - project.team << [user, :developer] + project.add_developer(user) end context 'but the repo is disabled' do @@ -240,7 +240,7 @@ describe 'Git HTTP requests' do context 'as a developer on the team' do before do - project.team << [user, :developer] + project.add_developer(user) end it_behaves_like 'pulls are allowed' @@ -365,13 +365,13 @@ describe 'Git HTTP requests' do context "when the user has access to the project" do before do - project.team << [user, :master] + project.add_master(user) end context "when the user is blocked" do it "rejects pulls with 401 Unauthorized" do user.block - project.team << [user, :master] + project.add_master(user) download(path, env) do |response| expect(response).to have_gitlab_http_status(:unauthorized) @@ -434,7 +434,7 @@ describe 'Git HTTP requests' do let(:path) { "#{project.full_path}.git" } before do - project.team << [user, :master] + project.add_master(user) end context 'when username and password are provided' do @@ -612,7 +612,7 @@ describe 'Git HTTP requests' do context 'and build created by' do before do build.update(user: user) - project.team << [user, :reporter] + project.add_reporter(user) end shared_examples 'can download code only' do @@ -795,7 +795,7 @@ describe 'Git HTTP requests' do context 'and the user is on the team' do before do - project.team << [user, :master] + project.add_master(user) end it "responds with status 200" do diff --git a/spec/requests/lfs_http_spec.rb b/spec/requests/lfs_http_spec.rb index c597623bc4d..5e59bb0d585 100644 --- a/spec/requests/lfs_http_spec.rb +++ b/spec/requests/lfs_http_spec.rb @@ -63,7 +63,7 @@ describe 'Git LFS API and storage' do context 'with LFS disabled globally' do before do - project.team << [user, :master] + project.add_master(user) allow(Gitlab.config.lfs).to receive(:enabled).and_return(false) end @@ -106,7 +106,7 @@ describe 'Git LFS API and storage' do context 'with LFS enabled globally' do before do - project.team << [user, :master] + project.add_master(user) enable_lfs end @@ -234,7 +234,7 @@ describe 'Git LFS API and storage' do context 'and does have project access' do let(:update_permissions) do - project.team << [user, :master] + project.add_master(user) project.lfs_objects << lfs_object end @@ -259,7 +259,7 @@ describe 'Git LFS API and storage' do context 'when user allowed' do let(:update_permissions) do - project.team << [user, :master] + project.add_master(user) project.lfs_objects << lfs_object end @@ -295,7 +295,7 @@ describe 'Git LFS API and storage' do let(:pipeline) { create(:ci_empty_pipeline, project: project) } let(:update_permissions) do - project.team << [user, :reporter] + project.add_reporter(user) project.lfs_objects << lfs_object end @@ -517,7 +517,7 @@ describe 'Git LFS API and storage' do let(:authorization) { authorize_user } let(:update_user_permissions) do - project.team << [user, role] + project.add_role(user, role) end it_behaves_like 'an authorized requests' do @@ -553,7 +553,7 @@ describe 'Git LFS API and storage' do let(:pipeline) { create(:ci_empty_pipeline, project: project) } let(:update_user_permissions) do - project.team << [user, :reporter] + project.add_reporter(user) end it_behaves_like 'an authorized requests' @@ -673,7 +673,7 @@ describe 'Git LFS API and storage' do let(:authorization) { authorize_user } let(:update_user_permissions) do - project.team << [user, :developer] + project.add_developer(user) end context 'when pushing an lfs object that already exists' do @@ -795,7 +795,7 @@ describe 'Git LFS API and storage' do context 'when user is not authenticated' do context 'when user has push access' do let(:update_user_permissions) do - project.team << [user, :master] + project.add_master(user) end it 'responds with status 401' do @@ -840,7 +840,7 @@ describe 'Git LFS API and storage' do before do allow(Gitlab::Database).to receive(:read_only?) { true } - project.team << [user, :master] + project.add_master(user) enable_lfs end @@ -935,7 +935,7 @@ describe 'Git LFS API and storage' do describe 'when user has push access to the project' do before do - project.team << [user, :developer] + project.add_developer(user) end context 'and the request bypassed workhorse' do @@ -993,7 +993,7 @@ describe 'Git LFS API and storage' do describe 'and user does not have push access' do before do - project.team << [user, :reporter] + project.add_reporter(user) end it_behaves_like 'forbidden' @@ -1010,7 +1010,7 @@ describe 'Git LFS API and storage' do let(:build) { create(:ci_build, :running, pipeline: pipeline, user: user) } before do - project.team << [user, :developer] + project.add_developer(user) put_authorize end @@ -1062,7 +1062,7 @@ describe 'Git LFS API and storage' do describe 'when user has push access to the project' do before do - project.team << [user, :developer] + project.add_developer(user) end context 'and request is sent by gitlab-workhorse to authorize the request' do @@ -1149,7 +1149,7 @@ describe 'Git LFS API and storage' do let(:authorization) { authorize_user } before do - second_project.team << [user, :master] + second_project.add_master(user) upstream_project.lfs_objects << lfs_object end diff --git a/spec/requests/projects/cycle_analytics_events_spec.rb b/spec/requests/projects/cycle_analytics_events_spec.rb index 286d8a884a4..98f70e2101b 100644 --- a/spec/requests/projects/cycle_analytics_events_spec.rb +++ b/spec/requests/projects/cycle_analytics_events_spec.rb @@ -7,7 +7,7 @@ describe 'cycle analytics events' do describe 'GET /:namespace/:project/cycle_analytics/events/issues' do before do - project.team << [user, :developer] + project.add_developer(user) 3.times do |count| Timecop.freeze(Time.now + count.days) do diff --git a/spec/serializers/event_entity_spec.rb b/spec/serializers/event_entity_spec.rb deleted file mode 100644 index bb54597c967..00000000000 --- a/spec/serializers/event_entity_spec.rb +++ /dev/null @@ -1,13 +0,0 @@ -require 'spec_helper' - -describe EventEntity do - subject { described_class.represent(create(:event)).as_json } - - it 'exposes author' do - expect(subject).to include(:author) - end - - it 'exposes core elements of event' do - expect(subject).to include(:updated_at) - end -end diff --git a/spec/serializers/merge_request_widget_entity_spec.rb b/spec/serializers/merge_request_widget_entity_spec.rb index a5924a8589c..e25552eb0d8 100644 --- a/spec/serializers/merge_request_widget_entity_spec.rb +++ b/spec/serializers/merge_request_widget_entity_spec.rb @@ -35,6 +35,81 @@ describe MergeRequestWidgetEntity do end end + describe 'metrics' do + context 'when metrics record exists with merged data' do + before do + resource.mark_as_merged! + resource.metrics.update!(merged_by: user) + end + + it 'matches merge request metrics schema' do + expect(subject[:metrics].with_indifferent_access) + .to match_schema('entities/merge_request_metrics') + end + + it 'returns values from metrics record' do + expect(subject.dig(:metrics, :merged_by, :id)) + .to eq(resource.metrics.merged_by_id) + end + end + + context 'when metrics record exists with closed data' do + before do + resource.close! + resource.metrics.update!(latest_closed_by: user) + end + + it 'matches merge request metrics schema' do + expect(subject[:metrics].with_indifferent_access) + .to match_schema('entities/merge_request_metrics') + end + + it 'returns values from metrics record' do + expect(subject.dig(:metrics, :closed_by, :id)) + .to eq(resource.metrics.latest_closed_by_id) + end + end + + context 'when metrics does not exists' do + before do + resource.mark_as_merged! + resource.metrics.destroy! + resource.reload + end + + context 'when events exists' do + let!(:closed_event) { create(:event, :closed, project: project, target: resource) } + let!(:merge_event) { create(:event, :merged, project: project, target: resource) } + + it 'matches merge request metrics schema' do + expect(subject[:metrics].with_indifferent_access) + .to match_schema('entities/merge_request_metrics') + end + + it 'returns values from events record' do + expect(subject.dig(:metrics, :merged_by, :id)) + .to eq(merge_event.author_id) + + expect(subject.dig(:metrics, :closed_by, :id)) + .to eq(closed_event.author_id) + + expect(subject.dig(:metrics, :merged_at).to_s) + .to eq(merge_event.updated_at.to_s) + + expect(subject.dig(:metrics, :closed_at).to_s) + .to eq(closed_event.updated_at.to_s) + end + end + + context 'when events does not exists' do + it 'matches merge request metrics schema' do + expect(subject[:metrics].with_indifferent_access) + .to match_schema('entities/merge_request_metrics') + end + end + end + end + it 'has email_patches_path' do expect(subject[:email_patches_path]) .to eq("/#{resource.project.full_path}/merge_requests/#{resource.iid}.patch") diff --git a/spec/services/boards/issues/create_service_spec.rb b/spec/services/boards/issues/create_service_spec.rb index 1a56164dba4..f0179e35652 100644 --- a/spec/services/boards/issues/create_service_spec.rb +++ b/spec/services/boards/issues/create_service_spec.rb @@ -11,7 +11,7 @@ describe Boards::Issues::CreateService do subject(:service) { described_class.new(board.parent, project, user, board_id: board.id, list_id: list.id, title: 'New issue') } before do - project.team << [user, :developer] + project.add_developer(user) end it 'delegates the create proceedings to Issues::CreateService' do diff --git a/spec/services/boards/issues/list_service_spec.rb b/spec/services/boards/issues/list_service_spec.rb index 01ee3856c99..ff5733b7064 100644 --- a/spec/services/boards/issues/list_service_spec.rb +++ b/spec/services/boards/issues/list_service_spec.rb @@ -34,7 +34,7 @@ describe Boards::Issues::ListService do let!(:closed_issue5) { create(:labeled_issue, :closed, project: project, labels: [development]) } before do - project.team << [user, :developer] + project.add_developer(user) end it 'delegates search to IssuesFinder' do diff --git a/spec/services/boards/issues/move_service_spec.rb b/spec/services/boards/issues/move_service_spec.rb index 464ff9f94b3..280e411683e 100644 --- a/spec/services/boards/issues/move_service_spec.rb +++ b/spec/services/boards/issues/move_service_spec.rb @@ -15,7 +15,7 @@ describe Boards::Issues::MoveService do let!(:closed) { create(:closed_list, board: board1) } before do - project.team << [user, :developer] + project.add_developer(user) end context 'when moving an issue between lists' do diff --git a/spec/services/boards/lists/create_service_spec.rb b/spec/services/boards/lists/create_service_spec.rb index 7d0b396cd06..d5322e1bb21 100644 --- a/spec/services/boards/lists/create_service_spec.rb +++ b/spec/services/boards/lists/create_service_spec.rb @@ -10,7 +10,7 @@ describe Boards::Lists::CreateService do subject(:service) { described_class.new(project, user, label_id: label.id) } before do - project.team << [user, :developer] + project.add_developer(user) end context 'when board lists is empty' do diff --git a/spec/services/boards/lists/generate_service_spec.rb b/spec/services/boards/lists/generate_service_spec.rb index 592f25059ac..82dbd1ee744 100644 --- a/spec/services/boards/lists/generate_service_spec.rb +++ b/spec/services/boards/lists/generate_service_spec.rb @@ -9,7 +9,7 @@ describe Boards::Lists::GenerateService do subject(:service) { described_class.new(project, user) } before do - project.team << [user, :developer] + project.add_developer(user) end context 'when board lists is empty' do diff --git a/spec/services/ci/stop_environments_service_spec.rb b/spec/services/ci/stop_environments_service_spec.rb index e2a9ed27e87..3fc4e499b0c 100644 --- a/spec/services/ci/stop_environments_service_spec.rb +++ b/spec/services/ci/stop_environments_service_spec.rb @@ -15,7 +15,7 @@ describe Ci::StopEnvironmentsService do context 'when user has permission to stop environment' do before do - project.team << [user, :developer] + project.add_developer(user) end context 'when environment is associated with removed branch' do @@ -57,7 +57,7 @@ describe Ci::StopEnvironmentsService do context 'when user does not have permission to stop environment' do context 'when user has no access to manage deployments' do before do - project.team << [user, :guest] + project.add_guest(user) end it 'does not stop environment' do @@ -86,7 +86,7 @@ describe Ci::StopEnvironmentsService do context 'when user has permission to stop environments' do before do - project.team << [user, :master] + project.add_master(user) end it 'does not stop environment' do diff --git a/spec/services/create_deployment_service_spec.rb b/spec/services/create_deployment_service_spec.rb index 08267d6e6a0..b9bfbb11511 100644 --- a/spec/services/create_deployment_service_spec.rb +++ b/spec/services/create_deployment_service_spec.rb @@ -266,7 +266,7 @@ describe CreateDeploymentService do context "while updating the 'first_deployed_to_production_at' time" do before do - merge_request.mark_as_merged + merge_request.metrics.update!(merged_at: Time.now) end context "for merge requests merged before the current deploy" do diff --git a/spec/services/delete_branch_service_spec.rb b/spec/services/delete_branch_service_spec.rb index 19855c9bee2..9c9fba030e7 100644 --- a/spec/services/delete_branch_service_spec.rb +++ b/spec/services/delete_branch_service_spec.rb @@ -9,7 +9,7 @@ describe DeleteBranchService do describe '#execute' do context 'when user has access to push to repository' do before do - project.team << [user, :developer] + project.add_developer(user) end it 'removes the branch' do diff --git a/spec/services/discussions/resolve_service_spec.rb b/spec/services/discussions/resolve_service_spec.rb index ab8df7b74cd..3895a0b3aea 100644 --- a/spec/services/discussions/resolve_service_spec.rb +++ b/spec/services/discussions/resolve_service_spec.rb @@ -9,7 +9,7 @@ describe Discussions::ResolveService do let(:service) { described_class.new(discussion.noteable.project, user, merge_request: merge_request) } before do - project.team << [user, :master] + project.add_master(user) end it "doesn't resolve discussions the user can't resolve" do diff --git a/spec/services/files/delete_service_spec.rb b/spec/services/files/delete_service_spec.rb index e9f8f0efe6b..ace5f293097 100644 --- a/spec/services/files/delete_service_spec.rb +++ b/spec/services/files/delete_service_spec.rb @@ -37,7 +37,7 @@ describe Files::DeleteService do end before do - project.team << [user, :master] + project.add_master(user) end describe "#execute" do diff --git a/spec/services/files/multi_service_spec.rb b/spec/services/files/multi_service_spec.rb index 085a28d267f..2b79609930c 100644 --- a/spec/services/files/multi_service_spec.rb +++ b/spec/services/files/multi_service_spec.rb @@ -36,7 +36,7 @@ describe Files::MultiService do end before do - project.team << [user, :master] + project.add_master(user) end describe '#execute' do diff --git a/spec/services/files/update_service_spec.rb b/spec/services/files/update_service_spec.rb index 2b4f8cd42ee..43b0c9a63a9 100644 --- a/spec/services/files/update_service_spec.rb +++ b/spec/services/files/update_service_spec.rb @@ -24,7 +24,7 @@ describe Files::UpdateService do end before do - project.team << [user, :master] + project.add_master(user) end describe "#execute" do diff --git a/spec/services/git_push_service_spec.rb b/spec/services/git_push_service_spec.rb index cc3d4e7da49..26fdf8d4b24 100644 --- a/spec/services/git_push_service_spec.rb +++ b/spec/services/git_push_service_spec.rb @@ -11,7 +11,7 @@ describe GitPushService, services: true do let(:ref) { 'refs/heads/master' } before do - project.team << [user, :master] + project.add_master(user) end describe 'Push branches' do @@ -266,8 +266,8 @@ describe GitPushService, services: true do let(:commit) { project.commit } before do - project.team << [commit_author, :developer] - project.team << [user, :developer] + project.add_developer(commit_author) + project.add_developer(user) allow(commit).to receive_messages( safe_message: "this commit \n mentions #{issue.to_reference}", @@ -323,8 +323,8 @@ describe GitPushService, services: true do let(:commit_time) { Time.now } before do - project.team << [commit_author, :developer] - project.team << [user, :developer] + project.add_developer(commit_author) + project.add_developer(user) allow(commit).to receive_messages( safe_message: "this commit \n mentions #{issue.to_reference}", @@ -376,7 +376,7 @@ describe GitPushService, services: true do allow_any_instance_of(ProcessCommitWorker).to receive(:build_commit) .and_return(closing_commit) - project.team << [commit_author, :master] + project.add_master(commit_author) end context "to default branches" do diff --git a/spec/services/git_tag_push_service_spec.rb b/spec/services/git_tag_push_service_spec.rb index 05695aa8188..33405d7a7ec 100644 --- a/spec/services/git_tag_push_service_spec.rb +++ b/spec/services/git_tag_push_service_spec.rb @@ -35,7 +35,7 @@ describe GitTagPushService do before do stub_ci_pipeline_to_return_yaml_file - project.team << [user, :developer] + project.add_developer(user) end it "creates a new pipeline" do diff --git a/spec/services/issuable/bulk_update_service_spec.rb b/spec/services/issuable/bulk_update_service_spec.rb index bdaab88d673..53c85f73cde 100644 --- a/spec/services/issuable/bulk_update_service_spec.rb +++ b/spec/services/issuable/bulk_update_service_spec.rb @@ -54,7 +54,7 @@ describe Issuable::BulkUpdateService do context 'when the new assignee ID is a valid user' do it 'succeeds' do new_assignee = create(:user) - project.team << [new_assignee, :developer] + project.add_developer(new_assignee) result = bulk_update(merge_request, assignee_id: new_assignee.id) @@ -64,7 +64,7 @@ describe Issuable::BulkUpdateService do it 'updates the assignee to the user ID passed' do assignee = create(:user) - project.team << [assignee, :developer] + project.add_developer(assignee) expect { bulk_update(merge_request, assignee_id: assignee.id) } .to change { merge_request.reload.assignee }.from(user).to(assignee) @@ -92,7 +92,7 @@ describe Issuable::BulkUpdateService do context 'when the new assignee ID is a valid user' do it 'succeeds' do new_assignee = create(:user) - project.team << [new_assignee, :developer] + project.add_developer(new_assignee) result = bulk_update(issue, assignee_ids: [new_assignee.id]) @@ -102,7 +102,7 @@ describe Issuable::BulkUpdateService do it 'updates the assignee to the user ID passed' do assignee = create(:user) - project.team << [assignee, :developer] + project.add_developer(assignee) expect { bulk_update(issue, assignee_ids: [assignee.id]) } .to change { issue.reload.assignees.first }.from(user).to(assignee) end diff --git a/spec/services/issues/build_service_spec.rb b/spec/services/issues/build_service_spec.rb index 03f76bd428d..248e7d5a389 100644 --- a/spec/services/issues/build_service_spec.rb +++ b/spec/services/issues/build_service_spec.rb @@ -5,7 +5,7 @@ describe Issues::BuildService do let(:user) { create(:user) } before do - project.team << [user, :developer] + project.add_developer(user) end context 'for a single discussion' do diff --git a/spec/services/issues/close_service_spec.rb b/spec/services/issues/close_service_spec.rb index 5c27e8fd561..8897a64a138 100644 --- a/spec/services/issues/close_service_spec.rb +++ b/spec/services/issues/close_service_spec.rb @@ -9,9 +9,9 @@ describe Issues::CloseService do let!(:todo) { create(:todo, :assigned, user: user, project: project, target: issue, author: user2) } before do - project.team << [user, :master] - project.team << [user2, :developer] - project.team << [guest, :guest] + project.add_master(user) + project.add_developer(user2) + project.add_guest(guest) end describe '#execute' do diff --git a/spec/services/issues/create_service_spec.rb b/spec/services/issues/create_service_spec.rb index d86da244520..79bcdc41fb0 100644 --- a/spec/services/issues/create_service_spec.rb +++ b/spec/services/issues/create_service_spec.rb @@ -13,8 +13,8 @@ describe Issues::CreateService do let(:labels) { create_pair(:label, project: project) } before do - project.team << [user, :master] - project.team << [assignee, :master] + project.add_master(user) + project.add_master(assignee) end let(:opts) do @@ -43,7 +43,7 @@ describe Issues::CreateService do let(:guest) { create(:user) } before do - project.team << [guest, :guest] + project.add_guest(guest) end it 'filters out params that cannot be set without the :admin_issue permission' do @@ -130,7 +130,7 @@ describe Issues::CreateService do end it 'invalidates open issues counter for assignees when issue is assigned' do - project.team << [assignee, :master] + project.add_master(assignee) described_class.new(project, user, opts).execute @@ -160,7 +160,7 @@ describe Issues::CreateService do context 'issue create service' do context 'assignees' do before do - project.team << [user, :master] + project.add_master(user) end it 'removes assignee when user id is invalid' do @@ -180,7 +180,7 @@ describe Issues::CreateService do end it 'saves assignee when user id is valid' do - project.team << [assignee, :master] + project.add_master(assignee) opts = { title: 'Title', description: 'Description', assignee_ids: [assignee.id] } issue = described_class.new(project, user, opts).execute @@ -224,8 +224,8 @@ describe Issues::CreateService do end before do - project.team << [user, :master] - project.team << [assignee, :master] + project.add_master(user) + project.add_master(assignee) end it 'assigns and sets milestone to issuable from command' do @@ -242,7 +242,7 @@ describe Issues::CreateService do let(:project) { merge_request.source_project } before do - project.team << [user, :master] + project.add_master(user) end describe 'for a single discussion' do diff --git a/spec/services/issues/move_service_spec.rb b/spec/services/issues/move_service_spec.rb index 2cad695d7c4..53ea88332fb 100644 --- a/spec/services/issues/move_service_spec.rb +++ b/spec/services/issues/move_service_spec.rb @@ -20,8 +20,8 @@ describe Issues::MoveService do shared_context 'user can move issue' do before do - old_project.team << [user, :reporter] - new_project.team << [user, :reporter] + old_project.add_reporter(user) + new_project.add_reporter(user) labels = Array.new(2) { |x| "label%d" % (x + 1) } @@ -313,7 +313,7 @@ describe Issues::MoveService do context 'user is reporter only in new project' do before do - new_project.team << [user, :reporter] + new_project.add_reporter(user) end it { expect { move }.to raise_error(StandardError, /permissions/) } @@ -321,7 +321,7 @@ describe Issues::MoveService do context 'user is reporter only in old project' do before do - old_project.team << [user, :reporter] + old_project.add_reporter(user) end it { expect { move }.to raise_error(StandardError, /permissions/) } @@ -329,8 +329,8 @@ describe Issues::MoveService do context 'user is reporter in one project and guest in another' do before do - new_project.team << [user, :guest] - old_project.team << [user, :reporter] + new_project.add_guest(user) + old_project.add_reporter(user) end it { expect { move }.to raise_error(StandardError, /permissions/) } @@ -358,8 +358,8 @@ describe Issues::MoveService do context 'movable issue with no assigned labels' do before do - old_project.team << [user, :reporter] - new_project.team << [user, :reporter] + old_project.add_reporter(user) + new_project.add_reporter(user) labels = Array.new(2) { |x| "label%d" % (x + 1) } diff --git a/spec/services/issues/reopen_service_spec.rb b/spec/services/issues/reopen_service_spec.rb index 48fc98b3b2f..42e5d544f4c 100644 --- a/spec/services/issues/reopen_service_spec.rb +++ b/spec/services/issues/reopen_service_spec.rb @@ -8,7 +8,7 @@ describe Issues::ReopenService do context 'when user is not authorized to reopen issue' do before do guest = create(:user) - project.team << [guest, :guest] + project.add_guest(guest) perform_enqueued_jobs do described_class.new(project, guest).execute(issue) @@ -24,7 +24,7 @@ describe Issues::ReopenService do let(:user) { create(:user) } before do - project.team << [user, :master] + project.add_master(user) end it 'invalidates counter cache for assignees' do diff --git a/spec/services/issues/resolve_discussions_spec.rb b/spec/services/issues/resolve_discussions_spec.rb index 67a86c50fc1..13accc6ae1b 100644 --- a/spec/services/issues/resolve_discussions_spec.rb +++ b/spec/services/issues/resolve_discussions_spec.rb @@ -14,7 +14,7 @@ describe Issues::ResolveDiscussions do let(:user) { create(:user) } before do - project.team << [user, :developer] + project.add_developer(user) end describe "for resolving discussions" do diff --git a/spec/services/issues/update_service_spec.rb b/spec/services/issues/update_service_spec.rb index f07b81e842a..1cb6f2e097f 100644 --- a/spec/services/issues/update_service_spec.rb +++ b/spec/services/issues/update_service_spec.rb @@ -17,9 +17,9 @@ describe Issues::UpdateService, :mailer do end before do - project.team << [user, :master] - project.team << [user2, :developer] - project.team << [user3, :developer] + project.add_master(user) + project.add_developer(user2) + project.add_developer(user3) end describe 'execute' do @@ -99,7 +99,7 @@ describe Issues::UpdateService, :mailer do context 'when current user cannot admin issues in the project' do let(:guest) { create(:user) } before do - project.team << [guest, :guest] + project.add_guest(guest) end it 'filters out params that cannot be set without the :admin_issue permission' do @@ -318,7 +318,7 @@ describe Issues::UpdateService, :mailer do let!(:subscriber) do create(:user).tap do |u| label.toggle_subscription(u, project) - project.team << [u, :developer] + project.add_developer(u) end end @@ -556,7 +556,7 @@ describe Issues::UpdateService, :mailer do context 'valid project' do before do - target_project.team << [user, :master] + target_project.add_master(user) end it 'calls the move service with the proper issue and project' do diff --git a/spec/services/labels/find_or_create_service_spec.rb b/spec/services/labels/find_or_create_service_spec.rb index a781fbc7f7d..78aa5d442e7 100644 --- a/spec/services/labels/find_or_create_service_spec.rb +++ b/spec/services/labels/find_or_create_service_spec.rb @@ -17,7 +17,7 @@ describe Labels::FindOrCreateService do let(:user) { create(:user) } subject(:service) { described_class.new(user, project, params) } before do - project.team << [user, :developer] + project.add_developer(user) end context 'when label does not exist at group level' do diff --git a/spec/services/members/approve_access_request_service_spec.rb b/spec/services/members/approve_access_request_service_spec.rb index 302c488d6c6..b3018169a1c 100644 --- a/spec/services/members/approve_access_request_service_spec.rb +++ b/spec/services/members/approve_access_request_service_spec.rb @@ -123,7 +123,7 @@ describe Members::ApproveAccessRequestService do context 'when current user can approve access request to the project' do before do - project.team << [user, :master] + project.add_master(user) group.add_owner(user) end diff --git a/spec/services/members/authorized_destroy_service_spec.rb b/spec/services/members/authorized_destroy_service_spec.rb index d4ef31c0c74..757c45708b9 100644 --- a/spec/services/members/authorized_destroy_service_spec.rb +++ b/spec/services/members/authorized_destroy_service_spec.rb @@ -13,7 +13,7 @@ describe Members::AuthorizedDestroyService do context 'Invited users' do # Regression spec for issue: https://gitlab.com/gitlab-org/gitlab-ce/issues/32504 it 'destroys invited project member' do - project.team << [member_user, :developer] + project.add_developer(member_user) member = create :project_member, :invited, project: project @@ -52,7 +52,7 @@ describe Members::AuthorizedDestroyService do context 'Project member' do it "unassigns issues and merge requests" do - project.team << [member_user, :developer] + project.add_developer(member_user) create :issue, project: project, assignees: [member_user] create :merge_request, target_project: project, source_project: project, assignee: member_user diff --git a/spec/services/members/create_service_spec.rb b/spec/services/members/create_service_spec.rb index 2a793e0eb4d..6bd4718e780 100644 --- a/spec/services/members/create_service_spec.rb +++ b/spec/services/members/create_service_spec.rb @@ -6,7 +6,7 @@ describe Members::CreateService do let(:project_user) { create(:user) } before do - project.team << [user, :master] + project.add_master(user) end it 'adds user to members' do diff --git a/spec/services/members/destroy_service_spec.rb b/spec/services/members/destroy_service_spec.rb index 72f5e27180d..91152df3ad9 100644 --- a/spec/services/members/destroy_service_spec.rb +++ b/spec/services/members/destroy_service_spec.rb @@ -71,7 +71,7 @@ describe Members::DestroyService do context 'when a member is found' do before do - project.team << [member_user, :developer] + project.add_developer(member_user) group.add_developer(member_user) end let(:params) { { user_id: member_user.id } } @@ -88,7 +88,7 @@ describe Members::DestroyService do context 'when current user can destroy the given member' do before do - project.team << [user, :master] + project.add_master(user) group.add_owner(user) end diff --git a/spec/services/merge_requests/assign_issues_service_spec.rb b/spec/services/merge_requests/assign_issues_service_spec.rb index fcbe0e5985f..bda6383a346 100644 --- a/spec/services/merge_requests/assign_issues_service_spec.rb +++ b/spec/services/merge_requests/assign_issues_service_spec.rb @@ -8,7 +8,7 @@ describe MergeRequests::AssignIssuesService do let(:service) { described_class.new(project, user, merge_request: merge_request) } before do - project.team << [user, :developer] + project.add_developer(user) end it 'finds unassigned issues fixed in merge request' do diff --git a/spec/services/merge_requests/build_service_spec.rb b/spec/services/merge_requests/build_service_spec.rb index b5c92e681fb..a9605c6e4c6 100644 --- a/spec/services/merge_requests/build_service_spec.rb +++ b/spec/services/merge_requests/build_service_spec.rb @@ -28,7 +28,7 @@ describe MergeRequests::BuildService do end before do - project.team << [user, :guest] + project.add_guest(user) end def stub_compare diff --git a/spec/services/merge_requests/close_service_spec.rb b/spec/services/merge_requests/close_service_spec.rb index b3886987316..4d12de3ecce 100644 --- a/spec/services/merge_requests/close_service_spec.rb +++ b/spec/services/merge_requests/close_service_spec.rb @@ -9,9 +9,9 @@ describe MergeRequests::CloseService do let!(:todo) { create(:todo, :assigned, user: user, project: project, target: merge_request, author: user2) } before do - project.team << [user, :master] - project.team << [user2, :developer] - project.team << [guest, :guest] + project.add_master(user) + project.add_developer(user2) + project.add_guest(guest) end describe '#execute' do @@ -52,6 +52,19 @@ describe MergeRequests::CloseService do end end + it 'updates metrics' do + metrics = merge_request.metrics + metrics_service = double(MergeRequestMetricsService) + allow(MergeRequestMetricsService) + .to receive(:new) + .with(metrics) + .and_return(metrics_service) + + expect(metrics_service).to receive(:close) + + described_class.new(project, user, {}).execute(merge_request) + end + it 'refreshes the number of open merge requests for a valid MR', :use_clean_rails_memory_store_caching do service = described_class.new(project, user, {}) diff --git a/spec/services/merge_requests/conflicts/list_service_spec.rb b/spec/services/merge_requests/conflicts/list_service_spec.rb index 0b32c51a16f..6cadcd438c3 100644 --- a/spec/services/merge_requests/conflicts/list_service_spec.rb +++ b/spec/services/merge_requests/conflicts/list_service_spec.rb @@ -32,14 +32,6 @@ describe MergeRequests::Conflicts::ListService do expect(conflicts_service(merge_request).can_be_resolved_in_ui?).to be_falsey end - it 'returns a falsey value when the MR has a missing ref after a force push' do - merge_request = create_merge_request('conflict-resolvable') - service = conflicts_service(merge_request) - allow_any_instance_of(Rugged::Repository).to receive(:merge_commits).and_raise(Rugged::OdbError) - - expect(service.can_be_resolved_in_ui?).to be_falsey - end - it 'returns a falsey value when the MR does not support new diff notes' do merge_request = create_merge_request('conflict-resolvable') merge_request.merge_request_diff.update_attributes(start_commit_sha: nil) @@ -76,5 +68,23 @@ describe MergeRequests::Conflicts::ListService do expect(conflicts_service(merge_request).can_be_resolved_in_ui?).to be_truthy end + + it 'returns a falsey value when the MR has a missing ref after a force push' do + merge_request = create_merge_request('conflict-resolvable') + service = conflicts_service(merge_request) + allow_any_instance_of(Gitlab::GitalyClient::ConflictsService).to receive(:list_conflict_files).and_raise(GRPC::Unknown) + + expect(service.can_be_resolved_in_ui?).to be_falsey + end + + context 'with gitaly disabled', :skip_gitaly_mock do + it 'returns a falsey value when the MR has a missing ref after a force push' do + merge_request = create_merge_request('conflict-resolvable') + service = conflicts_service(merge_request) + allow_any_instance_of(Rugged::Repository).to receive(:merge_commits).and_raise(Rugged::OdbError) + + expect(service.can_be_resolved_in_ui?).to be_falsey + end + end end end diff --git a/spec/services/merge_requests/conflicts/resolve_service_spec.rb b/spec/services/merge_requests/conflicts/resolve_service_spec.rb index e28d8d7ae5c..cff09237005 100644 --- a/spec/services/merge_requests/conflicts/resolve_service_spec.rb +++ b/spec/services/merge_requests/conflicts/resolve_service_spec.rb @@ -111,15 +111,6 @@ describe MergeRequests::Conflicts::ResolveService do described_class.new(merge_request_from_fork).execute(user, params) end - it 'gets conflicts from the source project' do - # REFACTOR NOTE: We used to test that `project.repository.rugged` wasn't - # used in this case, but since the refactor, for simplification, - # we always use that repository for read only operations. - expect(forked_project.repository.rugged).to receive(:merge_commits).and_call_original - - subject - end - it 'creates a commit with the message' do subject @@ -132,6 +123,17 @@ describe MergeRequests::Conflicts::ResolveService do expect(merge_request_from_fork.source_branch_head.parents.map(&:id)) .to eq(['404fa3fc7c2c9b5dacff102f353bdf55b1be2813', target_head]) end + + context 'when gitaly is disabled', :skip_gitaly_mock do + it 'gets conflicts from the source project' do + # REFACTOR NOTE: We used to test that `project.repository.rugged` wasn't + # used in this case, but since the refactor, for simplification, + # we always use that repository for read only operations. + expect(forked_project.repository.rugged).to receive(:merge_commits).and_call_original + + subject + end + end end end diff --git a/spec/services/merge_requests/create_service_spec.rb b/spec/services/merge_requests/create_service_spec.rb index a047f891ab2..dd8c803a2f7 100644 --- a/spec/services/merge_requests/create_service_spec.rb +++ b/spec/services/merge_requests/create_service_spec.rb @@ -21,8 +21,8 @@ describe MergeRequests::CreateService do let(:merge_request) { service.execute } before do - project.team << [user, :master] - project.team << [assignee, :developer] + project.add_master(user) + project.add_developer(assignee) allow(service).to receive(:execute_hooks) end @@ -148,8 +148,8 @@ describe MergeRequests::CreateService do end before do - project.team << [user, :master] - project.team << [assignee, :master] + project.add_master(user) + project.add_master(assignee) end it 'assigns and sets milestone to issuable from command' do @@ -165,7 +165,7 @@ describe MergeRequests::CreateService do let(:assignee) { create(:user) } before do - project.team << [user, :master] + project.add_master(user) end it 'removes assignee_id when user id is invalid' do @@ -185,7 +185,7 @@ describe MergeRequests::CreateService do end it 'saves assignee when user id is valid' do - project.team << [assignee, :master] + project.add_master(assignee) opts = { title: 'Title', description: 'Description', assignee_id: assignee.id } merge_request = described_class.new(project, user, opts).execute @@ -205,7 +205,7 @@ describe MergeRequests::CreateService do end it 'invalidates open merge request counter for assignees when merge request is assigned' do - project.team << [assignee, :master] + project.add_master(assignee) described_class.new(project, user, opts).execute @@ -249,8 +249,8 @@ describe MergeRequests::CreateService do end before do - project.team << [user, :master] - project.team << [assignee, :developer] + project.add_master(user) + project.add_developer(assignee) end it 'creates a `MergeRequestsClosingIssues` record for each issue' do diff --git a/spec/services/merge_requests/ff_merge_service_spec.rb b/spec/services/merge_requests/ff_merge_service_spec.rb index aaabf3ed2b0..aa90feeef89 100644 --- a/spec/services/merge_requests/ff_merge_service_spec.rb +++ b/spec/services/merge_requests/ff_merge_service_spec.rb @@ -12,8 +12,8 @@ describe MergeRequests::FfMergeService do let(:project) { merge_request.project } before do - project.team << [user, :master] - project.team << [user2, :developer] + project.add_master(user) + project.add_developer(user2) end describe '#execute' do diff --git a/spec/services/merge_requests/post_merge_service_spec.rb b/spec/services/merge_requests/post_merge_service_spec.rb index d2bd05d921f..70957431942 100644 --- a/spec/services/merge_requests/post_merge_service_spec.rb +++ b/spec/services/merge_requests/post_merge_service_spec.rb @@ -6,7 +6,7 @@ describe MergeRequests::PostMergeService do let(:project) { merge_request.project } before do - project.team << [user, :master] + project.add_master(user) end describe '#execute' do @@ -22,5 +22,18 @@ describe MergeRequests::PostMergeService do expect { service.execute(merge_request) } .to change { project.open_merge_requests_count }.from(1).to(0) end + + it 'updates metrics' do + metrics = merge_request.metrics + metrics_service = double(MergeRequestMetricsService) + allow(MergeRequestMetricsService) + .to receive(:new) + .with(metrics) + .and_return(metrics_service) + + expect(metrics_service).to receive(:merge) + + described_class.new(project, user, {}).execute(merge_request) + end end end diff --git a/spec/services/merge_requests/refresh_service_spec.rb b/spec/services/merge_requests/refresh_service_spec.rb index 61ec4709c59..7a01d3dd698 100644 --- a/spec/services/merge_requests/refresh_service_spec.rb +++ b/spec/services/merge_requests/refresh_service_spec.rb @@ -300,8 +300,8 @@ describe MergeRequests::RefreshService do let(:commit) { project.commit } before do - project.team << [commit_author, :developer] - project.team << [user, :developer] + project.add_developer(commit_author) + project.add_developer(user) allow(commit).to receive_messages( safe_message: "Closes #{issue.to_reference}", diff --git a/spec/services/merge_requests/reopen_service_spec.rb b/spec/services/merge_requests/reopen_service_spec.rb index fa652611c6b..a44d63e5f9f 100644 --- a/spec/services/merge_requests/reopen_service_spec.rb +++ b/spec/services/merge_requests/reopen_service_spec.rb @@ -8,9 +8,9 @@ describe MergeRequests::ReopenService do let(:project) { merge_request.project } before do - project.team << [user, :master] - project.team << [user2, :developer] - project.team << [guest, :guest] + project.add_master(user) + project.add_developer(user2) + project.add_guest(guest) end describe '#execute' do @@ -47,6 +47,19 @@ describe MergeRequests::ReopenService do end end + it 'updates metrics' do + metrics = merge_request.metrics + service = double(MergeRequestMetricsService) + allow(MergeRequestMetricsService) + .to receive(:new) + .with(metrics) + .and_return(service) + + expect(service).to receive(:reopen) + + described_class.new(project, user, {}).execute(merge_request) + end + it 'refreshes the number of open merge requests for a valid MR' do service = described_class.new(project, user, {}) diff --git a/spec/services/merge_requests/update_service_spec.rb b/spec/services/merge_requests/update_service_spec.rb index 7a66b809550..2238da2d14d 100644 --- a/spec/services/merge_requests/update_service_spec.rb +++ b/spec/services/merge_requests/update_service_spec.rb @@ -16,9 +16,9 @@ describe MergeRequests::UpdateService, :mailer do end before do - project.team << [user, :master] - project.team << [user2, :developer] - project.team << [user3, :developer] + project.add_master(user) + project.add_developer(user2) + project.add_developer(user3) end describe 'execute' do @@ -356,8 +356,8 @@ describe MergeRequests::UpdateService, :mailer do let!(:subscriber) { create(:user) { |u| label.toggle_subscription(u, project) } } before do - project.team << [non_subscriber, :developer] - project.team << [subscriber, :developer] + project.add_developer(non_subscriber) + project.add_developer(subscriber) end it 'sends notifications for subscribers of newly added labels' do diff --git a/spec/services/milestones/close_service_spec.rb b/spec/services/milestones/close_service_spec.rb index 2bdf224804d..adad73f7e11 100644 --- a/spec/services/milestones/close_service_spec.rb +++ b/spec/services/milestones/close_service_spec.rb @@ -6,7 +6,7 @@ describe Milestones::CloseService do let(:milestone) { create(:milestone, title: "Milestone v1.2", project: project) } before do - project.team << [user, :master] + project.add_master(user) end describe '#execute' do diff --git a/spec/services/milestones/create_service_spec.rb b/spec/services/milestones/create_service_spec.rb index 8837b91051d..f2a18c7295a 100644 --- a/spec/services/milestones/create_service_spec.rb +++ b/spec/services/milestones/create_service_spec.rb @@ -7,7 +7,7 @@ describe Milestones::CreateService do describe '#execute' do context "valid params" do before do - project.team << [user, :master] + project.add_master(user) opts = { title: 'v2.1.9', diff --git a/spec/services/milestones/destroy_service_spec.rb b/spec/services/milestones/destroy_service_spec.rb index af35e17bfa7..9703780b0e9 100644 --- a/spec/services/milestones/destroy_service_spec.rb +++ b/spec/services/milestones/destroy_service_spec.rb @@ -8,7 +8,7 @@ describe Milestones::DestroyService do let!(:merge_request) { create(:merge_request, source_project: project, milestone: milestone) } before do - project.team << [user, :master] + project.add_master(user) end def service diff --git a/spec/services/notes/create_service_spec.rb b/spec/services/notes/create_service_spec.rb index 661d26946e7..0ae26e87154 100644 --- a/spec/services/notes/create_service_spec.rb +++ b/spec/services/notes/create_service_spec.rb @@ -10,7 +10,7 @@ describe Notes::CreateService do describe '#execute' do before do - project.team << [user, :master] + project.add_master(user) end context "valid params" do diff --git a/spec/services/notes/post_process_service_spec.rb b/spec/services/notes/post_process_service_spec.rb index a2b3638059f..6ef5e93cb20 100644 --- a/spec/services/notes/post_process_service_spec.rb +++ b/spec/services/notes/post_process_service_spec.rb @@ -7,7 +7,7 @@ describe Notes::PostProcessService do describe '#execute' do before do - project.team << [user, :master] + project.add_master(user) note_opts = { note: 'Awesome comment', noteable_type: 'Issue', diff --git a/spec/services/notes/quick_actions_service_spec.rb b/spec/services/notes/quick_actions_service_spec.rb index 0280a19098b..5eafe56c99d 100644 --- a/spec/services/notes/quick_actions_service_spec.rb +++ b/spec/services/notes/quick_actions_service_spec.rb @@ -3,11 +3,11 @@ require 'spec_helper' describe Notes::QuickActionsService do shared_context 'note on noteable' do let(:project) { create(:project) } - let(:master) { create(:user).tap { |u| project.team << [u, :master] } } + let(:master) { create(:user).tap { |u| project.add_master(u) } } let(:assignee) { create(:user) } before do - project.team << [assignee, :master] + project.add_master(assignee) end end @@ -226,7 +226,7 @@ describe Notes::QuickActionsService do context 'CE restriction for issue assignees' do describe '/assign' do let(:project) { create(:project) } - let(:master) { create(:user).tap { |u| project.team << [u, :master] } } + let(:master) { create(:user).tap { |u| project.add_master(u) } } let(:assignee) { create(:user) } let(:master) { create(:user) } let(:service) { described_class.new(project, master) } @@ -237,8 +237,8 @@ describe Notes::QuickActionsService do end before do - project.team << [master, :master] - project.team << [assignee, :master] + project.add_master(master) + project.add_master(assignee) end it 'adds only one assignee from the list' do diff --git a/spec/services/notes/update_service_spec.rb b/spec/services/notes/update_service_spec.rb index 3210539f3ee..65b1d613998 100644 --- a/spec/services/notes/update_service_spec.rb +++ b/spec/services/notes/update_service_spec.rb @@ -9,9 +9,9 @@ describe Notes::UpdateService do let(:note) { create(:note, project: project, noteable: issue, author: user, note: "Old note #{user2.to_reference}") } before do - project.team << [user, :master] - project.team << [user2, :developer] - project.team << [user3, :developer] + project.add_master(user) + project.add_developer(user2) + project.add_developer(user3) end describe '#execute' do diff --git a/spec/services/projects/autocomplete_service_spec.rb b/spec/services/projects/autocomplete_service_spec.rb index 426593be428..7a8c54673f7 100644 --- a/spec/services/projects/autocomplete_service_spec.rb +++ b/spec/services/projects/autocomplete_service_spec.rb @@ -34,7 +34,7 @@ describe Projects::AutocompleteService do end it 'does not list project confidential issues for project members with guest role' do - project.team << [member, :guest] + project.add_guest(member) autocomplete = described_class.new(project, non_member) issues = autocomplete.issues.map(&:iid) @@ -66,7 +66,7 @@ describe Projects::AutocompleteService do end it 'lists project confidential issues for project members' do - project.team << [member, :developer] + project.add_developer(member) autocomplete = described_class.new(project, member) issues = autocomplete.issues.map(&:iid) diff --git a/spec/services/projects/create_service_spec.rb b/spec/services/projects/create_service_spec.rb index dc89fdebce7..1833078f37c 100644 --- a/spec/services/projects/create_service_spec.rb +++ b/spec/services/projects/create_service_spec.rb @@ -252,6 +252,12 @@ describe Projects::CreateService, '#execute' do end end + it 'writes project full path to .git/config' do + project = create_project(user, opts) + + expect(project.repo.config['gitlab.fullpath']).to eq project.full_path + end + def create_project(user, opts) Projects::CreateService.new(user, opts).execute end diff --git a/spec/services/projects/fork_service_spec.rb b/spec/services/projects/fork_service_spec.rb index 4057caca2ac..409d5de8d43 100644 --- a/spec/services/projects/fork_service_spec.rb +++ b/spec/services/projects/fork_service_spec.rb @@ -139,10 +139,10 @@ describe Projects::ForkService do stub_application_setting(restricted_visibility_levels: [Gitlab::VisibilityLevel::INTERNAL]) end - it "creates fork with highest allowed level" do + it "creates fork with lowest level" do forked_project = fork_project(@from_project, @to_user) - expect(forked_project.visibility_level).to eq(Gitlab::VisibilityLevel::PUBLIC) + expect(forked_project.visibility_level).to eq(Gitlab::VisibilityLevel::PRIVATE) end end @@ -209,6 +209,19 @@ describe Projects::ForkService do expect(to_project.errors[:path]).to eq(['has already been taken']) end end + + context 'when the namespace has a lower visibility level than the project' do + it 'creates the project with the lower visibility level' do + public_project = create(:project, :public) + private_group = create(:group, :private) + group_owner = create(:user) + private_group.add_owner(group_owner) + + forked_project = fork_project(public_project, group_owner, namespace: private_group) + + expect(forked_project.visibility_level).to eq(Gitlab::VisibilityLevel::PRIVATE) + end + end end end diff --git a/spec/services/projects/hashed_storage/migrate_repository_service_spec.rb b/spec/services/projects/hashed_storage/migrate_repository_service_spec.rb index 3a3e47fd9c0..ded864beb1d 100644 --- a/spec/services/projects/hashed_storage/migrate_repository_service_spec.rb +++ b/spec/services/projects/hashed_storage/migrate_repository_service_spec.rb @@ -2,7 +2,7 @@ require 'spec_helper' describe Projects::HashedStorage::MigrateRepositoryService do let(:gitlab_shell) { Gitlab::Shell.new } - let(:project) { create(:project, :empty_repo, :wiki_repo) } + let(:project) { create(:project, :repository, :wiki_repo) } let(:service) { described_class.new(project) } let(:legacy_storage) { Storage::LegacyProject.new(project) } let(:hashed_storage) { Storage::HashedProject.new(project) } @@ -33,6 +33,12 @@ describe Projects::HashedStorage::MigrateRepositoryService do service.execute end + + it 'writes project full path to .git/config' do + service.execute + + expect(project.repo.config['gitlab.fullpath']).to eq project.full_path + end end context 'when one move fails' do diff --git a/spec/services/projects/transfer_service_spec.rb b/spec/services/projects/transfer_service_spec.rb index 2b1337bee7e..7377c748698 100644 --- a/spec/services/projects/transfer_service_spec.rb +++ b/spec/services/projects/transfer_service_spec.rb @@ -54,6 +54,12 @@ describe Projects::TransferService do expect(project.disk_path).not_to eq(old_path) expect(project.disk_path).to start_with(group.path) end + + it 'updates project full path in .git/config' do + transfer_project(project, user, group) + + expect(project.repo.config['gitlab.fullpath']).to eq "#{group.full_path}/#{project.path}" + end end context 'when transfer fails' do @@ -86,6 +92,12 @@ describe Projects::TransferService do expect(original_path).to eq current_path end + it 'rolls back project full path in .git/config' do + attempt_project_transfer + + expect(project.repo.config['gitlab.fullpath']).to eq project.full_path + end + it "doesn't send move notifications" do expect_any_instance_of(NotificationService).not_to receive(:project_was_moved) diff --git a/spec/services/projects/update_service_spec.rb b/spec/services/projects/update_service_spec.rb index d887f70efae..fc6aa713d6f 100644 --- a/spec/services/projects/update_service_spec.rb +++ b/spec/services/projects/update_service_spec.rb @@ -61,7 +61,7 @@ describe Projects::UpdateService do end end - context 'When project visibility is higher than parent group' do + context 'when project visibility is higher than parent group' do let(:group) { create(:group, visibility_level: Gitlab::VisibilityLevel::INTERNAL) } before do diff --git a/spec/services/quick_actions/interpret_service_spec.rb b/spec/services/quick_actions/interpret_service_spec.rb index eb46480fa54..ae160d104f1 100644 --- a/spec/services/quick_actions/interpret_service_spec.rb +++ b/spec/services/quick_actions/interpret_service_spec.rb @@ -12,7 +12,7 @@ describe QuickActions::InterpretService do let(:service) { described_class.new(project, developer) } before do - project.team << [developer, :developer] + project.add_developer(developer) end describe '#execute' do @@ -440,7 +440,7 @@ describe QuickActions::InterpretService do let(:content) { "/assign @#{developer.username} @#{developer2.username}" } before do - project.team << [developer2, :developer] + project.add_developer(developer2) end context 'Issue' do diff --git a/spec/services/search/snippet_service_spec.rb b/spec/services/search/snippet_service_spec.rb index eae9bd4f5cf..bc7885b03d9 100644 --- a/spec/services/search/snippet_service_spec.rb +++ b/spec/services/search/snippet_service_spec.rb @@ -33,7 +33,7 @@ describe Search::SnippetService do it 'returns public, internal snippets and project private snippets for project members' do member = create(:user) - project.team << [member, :developer] + project.add_developer(member) search = described_class.new(member, search: 'password') results = search.execute diff --git a/spec/services/system_note_service_spec.rb b/spec/services/system_note_service_spec.rb index 9025589ae0b..4e640a82dfc 100644 --- a/spec/services/system_note_service_spec.rb +++ b/spec/services/system_note_service_spec.rb @@ -2,6 +2,7 @@ require 'spec_helper' describe SystemNoteService do include Gitlab::Routing + include RepoHelpers set(:group) { create(:group) } set(:project) { create(:project, :repository, group: group) } @@ -1070,17 +1071,32 @@ describe SystemNoteService do let(:action) { 'outdated' } end - it 'creates a new note in the discussion' do - # we need to completely rebuild the merge request object, or the `@discussions` on the merge request are not reloaded. - expect { subject }.to change { reloaded_merge_request.discussions.first.notes.size }.by(1) + context 'when the change_position is valid for the discussion' do + it 'creates a new note in the discussion' do + # we need to completely rebuild the merge request object, or the `@discussions` on the merge request are not reloaded. + expect { subject }.to change { reloaded_merge_request.discussions.first.notes.size }.by(1) + end + + it 'links to the diff in the system note' do + expect(subject.note).to include('version 1') + + diff_id = merge_request.merge_request_diff.id + line_code = change_position.line_code(project.repository) + expect(subject.note).to include(diffs_project_merge_request_url(project, merge_request, diff_id: diff_id, anchor: line_code)) + end end - it 'links to the diff in the system note' do - expect(subject.note).to include('version 1') + context 'when the change_position is invalid for the discussion' do + let(:change_position) { project.commit(sample_commit.id) } - diff_id = merge_request.merge_request_diff.id - line_code = change_position.line_code(project.repository) - expect(subject.note).to include(diffs_project_merge_request_url(project, merge_request, diff_id: diff_id, anchor: line_code)) + it 'creates a new note in the discussion' do + # we need to completely rebuild the merge request object, or the `@discussions` on the merge request are not reloaded. + expect { subject }.to change { reloaded_merge_request.discussions.first.notes.size }.by(1) + end + + it 'does not create a link' do + expect(subject.note).to eq('changed this line in version 1 of the diff') + end end end diff --git a/spec/services/todo_service_spec.rb b/spec/services/todo_service_spec.rb index 88013acae0a..5e6c24f5730 100644 --- a/spec/services/todo_service_spec.rb +++ b/spec/services/todo_service_spec.rb @@ -17,11 +17,11 @@ describe TodoService do let(:service) { described_class.new } before do - project.team << [guest, :guest] - project.team << [author, :developer] - project.team << [member, :developer] - project.team << [john_doe, :developer] - project.team << [skipped, :developer] + project.add_guest(guest) + project.add_developer(author) + project.add_developer(member) + project.add_developer(john_doe) + project.add_developer(skipped) end describe 'Issues' do diff --git a/spec/services/update_merge_request_metrics_service_spec.rb b/spec/services/update_merge_request_metrics_service_spec.rb new file mode 100644 index 00000000000..b5fb999381d --- /dev/null +++ b/spec/services/update_merge_request_metrics_service_spec.rb @@ -0,0 +1,42 @@ +require 'rails_helper' + +describe MergeRequestMetricsService do + let(:metrics) { create(:merge_request).metrics } + + describe '#merge' do + it 'updates metrics' do + user = create(:user) + service = described_class.new(metrics) + event = double(Event, author_id: user.id, created_at: Time.now) + + service.merge(event) + + expect(metrics.merged_by).to eq(user) + expect(metrics.merged_at).to eq(event.created_at) + end + end + + describe '#close' do + it 'updates metrics' do + user = create(:user) + service = described_class.new(metrics) + event = double(Event, author_id: user.id, created_at: Time.now) + + service.close(event) + + expect(metrics.latest_closed_by).to eq(user) + expect(metrics.latest_closed_at).to eq(event.created_at) + end + end + + describe '#reopen' do + it 'updates metrics' do + service = described_class.new(metrics) + + service.reopen + + expect(metrics.latest_closed_by).to be_nil + expect(metrics.latest_closed_at).to be_nil + end + end +end diff --git a/spec/services/users/destroy_service_spec.rb b/spec/services/users/destroy_service_spec.rb index 58a5bede3de..aeba9cd60bc 100644 --- a/spec/services/users/destroy_service_spec.rb +++ b/spec/services/users/destroy_service_spec.rb @@ -188,5 +188,22 @@ describe Users::DestroyService do end end end + + describe "calls the before/after callbacks" do + it 'of project_members' do + expect_any_instance_of(ProjectMember).to receive(:run_callbacks).with(:destroy).once + + service.execute(user) + end + + it 'of group_members' do + group_member = create(:group_member) + group_member.group.group_members.create(user: user, access_level: 40) + + expect_any_instance_of(GroupMember).to receive(:run_callbacks).with(:destroy).once + + service.execute(user) + end + end end end diff --git a/spec/support/api/milestones_shared_examples.rb b/spec/support/api/milestones_shared_examples.rb index d9080b02541..0388f110d71 100644 --- a/spec/support/api/milestones_shared_examples.rb +++ b/spec/support/api/milestones_shared_examples.rb @@ -258,7 +258,7 @@ shared_examples_for 'group and project milestones' do |route_definition| # Add public project to the group in context setup_for_group if context_group - public_project.team << [user, :developer] + public_project.add_developer(user) milestone.issues << issue << confidential_issue end @@ -275,7 +275,7 @@ shared_examples_for 'group and project milestones' do |route_definition| it 'does not return confidential issues to team members with guest role' do member = create(:user) - public_project.team << [member, :guest] + public_project.add_guest(member) get api(issues_route, member) diff --git a/spec/support/capybara.rb b/spec/support/capybara.rb index 935b170a0f6..5189c57b7db 100644 --- a/spec/support/capybara.rb +++ b/spec/support/capybara.rb @@ -33,6 +33,9 @@ Capybara.register_driver :chrome do |app| options.add_argument("disable-gpu") end + # Disable /dev/shm use in CI. See https://gitlab.com/gitlab-org/gitlab-ee/issues/4252 + options.add_argument("disable-dev-shm-usage") if ENV['CI'] || ENV['CI_SERVER'] + Capybara::Selenium::Driver.new( app, browser: :chrome, diff --git a/spec/support/cookie_helper.rb b/spec/support/cookie_helper.rb index 224619c899c..d72925e1838 100644 --- a/spec/support/cookie_helper.rb +++ b/spec/support/cookie_helper.rb @@ -8,6 +8,10 @@ module CookieHelper page.driver.browser.manage.add_cookie(name: name, value: value, **options) end + def get_cookie(name) + page.driver.browser.manage.cookie_named(name) + end + private def on_a_page? diff --git a/spec/support/features/issuable_slash_commands_shared_examples.rb b/spec/support/features/issuable_slash_commands_shared_examples.rb index 08e21ee2537..2c20821ac3f 100644 --- a/spec/support/features/issuable_slash_commands_shared_examples.rb +++ b/spec/support/features/issuable_slash_commands_shared_examples.rb @@ -19,7 +19,7 @@ shared_examples 'issuable record that supports quick actions in its description let(:new_url_opts) { {} } before do - project.team << [master, :master] + project.add_master(master) gitlab_sign_in(master) end diff --git a/spec/support/markdown_feature.rb b/spec/support/markdown_feature.rb index a0d854d3641..39e94ad53de 100644 --- a/spec/support/markdown_feature.rb +++ b/spec/support/markdown_feature.rb @@ -24,7 +24,7 @@ class MarkdownFeature def project @project ||= create(:project, :repository, group: group).tap do |project| - project.team << [user, :master] + project.add_master(user) end end @@ -85,7 +85,7 @@ class MarkdownFeature @xproject ||= begin group = create(:group, :nested) create(:project, :repository, namespace: group) do |project| - project.team << [user, :developer] + project.add_developer(user) end end end diff --git a/spec/support/mentionable_shared_examples.rb b/spec/support/mentionable_shared_examples.rb index 3ac201f1fb1..1685decbe94 100644 --- a/spec/support/mentionable_shared_examples.rb +++ b/spec/support/mentionable_shared_examples.rb @@ -53,7 +53,7 @@ shared_context 'mentionable context' do set_mentionable_text.call(ref_string) - project.team << [author, :developer] + project.add_developer(author) end end diff --git a/spec/support/reference_parser_shared_examples.rb b/spec/support/reference_parser_shared_examples.rb index bd83cb88058..baf8bcc04b8 100644 --- a/spec/support/reference_parser_shared_examples.rb +++ b/spec/support/reference_parser_shared_examples.rb @@ -26,7 +26,7 @@ RSpec.shared_examples "referenced feature visibility" do |*related_features| end it "creates reference for member" do - project.team << [user, :developer] + project.add_developer(user) expect(subject.nodes_visible_to_user(user, [link])).to eq([link]) end diff --git a/spec/support/services/issuable_create_service_slash_commands_shared_examples.rb b/spec/support/services/issuable_create_service_slash_commands_shared_examples.rb index 9399745f900..7b064162726 100644 --- a/spec/support/services/issuable_create_service_slash_commands_shared_examples.rb +++ b/spec/support/services/issuable_create_service_slash_commands_shared_examples.rb @@ -3,7 +3,7 @@ shared_examples 'new issuable record that supports quick actions' do let!(:project) { create(:project, :repository) } - let(:user) { create(:user).tap { |u| project.team << [u, :master] } } + let(:user) { create(:user).tap { |u| project.add_master(u) } } let(:assignee) { create(:user) } let!(:milestone) { create(:milestone, project: project) } let!(:labels) { create_list(:label, 3, project: project) } @@ -12,7 +12,7 @@ shared_examples 'new issuable record that supports quick actions' do let(:issuable) { described_class.new(project, user, params).execute } before do - project.team << [assignee, :master] + project.add_master(assignee) end context 'with labels in command only' do diff --git a/spec/support/services_shared_context.rb b/spec/support/services_shared_context.rb index 7457484a932..3f1fd169b72 100644 --- a/spec/support/services_shared_context.rb +++ b/spec/support/services_shared_context.rb @@ -29,5 +29,13 @@ Service.available_services_names.each do |service| end end end + + def initialize_service(service) + service_item = project.find_or_initialize_service(service) + service_item.properties = service_attrs + service_item.active = true if service == "kubernetes" + service_item.save + service_item + end end end diff --git a/spec/support/test_env.rb b/spec/support/test_env.rb index ffc051a3fff..1d99746b09f 100644 --- a/spec/support/test_env.rb +++ b/spec/support/test_env.rb @@ -215,7 +215,7 @@ module TestEnv end def copy_repo(project, bare_repo:, refs:) - target_repo_path = File.expand_path(project.repository_storage_path + "/#{project.full_path}.git") + target_repo_path = File.expand_path(project.repository_storage_path + "/#{project.disk_path}.git") FileUtils.mkdir_p(target_repo_path) FileUtils.cp_r("#{File.expand_path(bare_repo)}/.", target_repo_path) FileUtils.chmod_R 0755, target_repo_path diff --git a/spec/support/updating_mentions_shared_examples.rb b/spec/support/updating_mentions_shared_examples.rb index 565d3203e4f..5e3f19ba19e 100644 --- a/spec/support/updating_mentions_shared_examples.rb +++ b/spec/support/updating_mentions_shared_examples.rb @@ -3,7 +3,7 @@ RSpec.shared_examples 'updating mentions' do |service_class| let(:service_class) { service_class } before do - project.team << [mentioned_user, :developer] + project.add_developer(mentioned_user) end def update_mentionable(opts) diff --git a/spec/views/projects/imports/new.html.haml_spec.rb b/spec/views/projects/imports/new.html.haml_spec.rb index 9b293065797..ec435ec3b32 100644 --- a/spec/views/projects/imports/new.html.haml_spec.rb +++ b/spec/views/projects/imports/new.html.haml_spec.rb @@ -8,7 +8,7 @@ describe "projects/imports/new.html.haml" do before do sign_in(user) - project.team << [user, :master] + project.add_master(user) end it "escapes HTML in import errors" do diff --git a/spec/views/shared/notes/_form.html.haml_spec.rb b/spec/views/shared/notes/_form.html.haml_spec.rb index cae6bee2776..50980718e66 100644 --- a/spec/views/shared/notes/_form.html.haml_spec.rb +++ b/spec/views/shared/notes/_form.html.haml_spec.rb @@ -7,7 +7,7 @@ describe 'shared/notes/_form' do let(:project) { create(:project, :repository) } before do - project.team << [user, :master] + project.add_master(user) assign(:project, project) assign(:note, note) diff --git a/spec/workers/merge_worker_spec.rb b/spec/workers/merge_worker_spec.rb index 303193bab9b..c861a56497e 100644 --- a/spec/workers/merge_worker_spec.rb +++ b/spec/workers/merge_worker_spec.rb @@ -8,7 +8,7 @@ describe MergeWorker do let!(:author) { merge_request.author } before do - source_project.team << [author, :master] + source_project.add_master(author) source_project.repository.expire_branches_cache end diff --git a/vendor/gitlab-ci-yml/Auto-DevOps.gitlab-ci.yml b/vendor/gitlab-ci-yml/Auto-DevOps.gitlab-ci.yml index 275487071f3..18910a46d11 100644 --- a/vendor/gitlab-ci-yml/Auto-DevOps.gitlab-ci.yml +++ b/vendor/gitlab-ci-yml/Auto-DevOps.gitlab-ci.yml @@ -41,6 +41,7 @@ stages: - staging - canary - production + - performance - cleanup build: @@ -83,6 +84,21 @@ codequality: artifacts: paths: [codeclimate.json] +performance: + stage: performance + image: + name: sitespeedio/sitespeed.io:6.0.3 + entrypoint: [""] + script: + - performance + artifacts: + paths: + - performance.json + only: + refs: + - branches + kubernetes: active + sast: image: registry.gitlab.com/gitlab-org/gl-sast:latest variables: @@ -103,10 +119,13 @@ review: - install_tiller - create_secret - deploy + - persist_environment_url environment: name: review/$CI_COMMIT_REF_NAME url: http://$CI_PROJECT_PATH_SLUG-$CI_ENVIRONMENT_SLUG.$AUTO_DEVOPS_DOMAIN on_stop: stop_review + artifacts: + paths: [environment_url.txt] only: refs: - branches @@ -201,9 +220,12 @@ production: - create_secret - deploy - delete canary + - persist_environment_url environment: name: production url: http://$CI_PROJECT_PATH_SLUG.$AUTO_DEVOPS_DOMAIN + artifacts: + paths: [environment_url.txt] # when: manual only: refs: @@ -415,6 +437,29 @@ production: --docker-email="$GITLAB_USER_EMAIL" \ -o yaml --dry-run | kubectl replace -n "$KUBE_NAMESPACE" --force -f - } + + function performance() { + export CI_ENVIRONMENT_URL=$(cat environment_url.txt) + + mkdir gitlab-exporter + wget -O gitlab-exporter/index.js https://gitlab.com/gitlab-org/gl-performance/raw/10-3/index.js + + mkdir sitespeed-results + + if [ -f .gitlab-urls.txt ] + then + sed -i -e 's@^@'"$CI_ENVIRONMENT_URL"'@' .gitlab-urls.txt + /start.sh --plugins.add gitlab-exporter --outputFolder sitespeed-results .gitlab-urls.txt + else + /start.sh --plugins.add gitlab-exporter --outputFolder sitespeed-results $CI_ENVIRONMENT_URL + fi + + mv sitespeed-results/data/performance.json performance.json + } + + function persist_environment_url() { + echo $CI_ENVIRONMENT_URL > environment_url.txt + } function delete() { track="${1-stable}" diff --git a/yarn.lock b/yarn.lock index 358a1baec42..b29fc022bde 100644 --- a/yarn.lock +++ b/yarn.lock @@ -5146,6 +5146,10 @@ preserve@^0.2.0: version "0.2.0" resolved "https://registry.yarnpkg.com/preserve/-/preserve-0.2.0.tgz#815ed1f6ebc65926f865b310c0713bcb3315ce4b" +prettier@1.9.2: + version "1.9.2" + resolved "https://registry.yarnpkg.com/prettier/-/prettier-1.9.2.tgz#96bc2132f7a32338e6078aeb29727178c6335827" + prettier@^1.7.0: version "1.8.2" resolved "https://registry.yarnpkg.com/prettier/-/prettier-1.8.2.tgz#bff83e7fd573933c607875e5ba3abbdffb96aeb8" |