diff options
1591 files changed, 20655 insertions, 11383 deletions
diff --git a/.eslintrc.yml b/.eslintrc.yml index ecd9f57b075..b0794bb7434 100644 --- a/.eslintrc.yml +++ b/.eslintrc.yml @@ -33,3 +33,4 @@ rules: vue/no-unused-components: off vue/no-use-v-if-with-v-for: off vue/no-v-html: off + vue/use-v-on-exact: off diff --git a/.gitlab-ci.yml b/.gitlab-ci.yml index b26c2d16d77..3d12f4142ba 100644 --- a/.gitlab-ci.yml +++ b/.gitlab-ci.yml @@ -77,18 +77,6 @@ stages: - mysql:5.7 - redis:alpine -.rails4: &rails4 - allow_failure: false - except: - variables: - - $CI_COMMIT_REF_NAME =~ /(^docs[\/-].*|.*-docs$)/ - - $CI_COMMIT_REF_NAME =~ /(^qa[\/-].*|.*-qa$)/ - - $CI_COMMIT_REF_NAME =~ /norails4/ - - $RAILS5_DISABLED - variables: - BUNDLE_GEMFILE: "Gemfile.rails4" - RAILS5: "false" - # Skip all jobs except the ones that begin with 'docs/'. # Used for commits including ONLY documentation changes. # https://docs.gitlab.com/ce/development/documentation/#testing @@ -180,18 +168,10 @@ stages: <<: *rspec-metadata <<: *use-pg -.rspec-metadata-pg-rails4: &rspec-metadata-pg-rails4 - <<: *rspec-metadata-pg - <<: *rails4 - .rspec-metadata-mysql: &rspec-metadata-mysql <<: *rspec-metadata <<: *use-mysql -.rspec-metadata-mysql-rails4: &rspec-metadata-mysql-rails4 - <<: *rspec-metadata-mysql - <<: *rails4 - .only-canonical-masters: &only-canonical-masters only: - master@gitlab-org/gitlab-ce @@ -251,10 +231,16 @@ package-and-qa: <<: *single-script-job variables: <<: *single-script-job-variables + API_TOKEN: "${GITLAB_BOT_MULTI_PROJECT_PIPELINE_POLLING_TOKEN}" SCRIPT_NAME: trigger-build retry: 0 script: - gem install gitlab --no-document + - apk add --update openssl curl jq + - wget $CI_PROJECT_URL/raw/$CI_COMMIT_SHA/scripts/review_apps/review-apps.sh + - chmod 755 review-apps.sh + - source ./review-apps.sh + - wait_for_job_to_be_done "gitlab:assets:compile" - ./$SCRIPT_NAME omnibus when: manual only: @@ -432,7 +418,6 @@ setup-test-env: script: - bundle exec ruby -Ispec -e 'require "spec_helper" ; TestEnv.init' - scripts/gitaly-test-build # Do not use 'bundle exec' here - - BUNDLE_GEMFILE=Gemfile.rails4 bundle install $BUNDLE_INSTALL_FLAGS artifacts: expire_in: 7d paths: @@ -513,14 +498,6 @@ rspec-mysql: <<: *rspec-metadata-mysql parallel: 50 -rspec-pg-rails4: - <<: *rspec-metadata-pg-rails4 - parallel: 50 - -rspec-mysql-rails4: - <<: *rspec-metadata-mysql-rails4 - parallel: 50 - static-analysis: <<: *dedicated-no-docs-no-db-pull-cache-job dependencies: @@ -565,12 +542,6 @@ downtime_check: - /(^docs[\/-].*|.*-docs$)/ - /(^qa[\/-].*|.*-qa$)/ -rails4_gemfile_lock_check: - <<: *dedicated-no-docs-no-db-pull-cache-job - <<: *except-docs-and-qa - script: - - scripts/rails4-gemfile-lock-check - ee_compat_check: <<: *rake-exec dependencies: [] @@ -635,7 +606,7 @@ gitlab:setup-mysql: # Frontend-related jobs gitlab:assets:compile: - <<: *dedicated-no-docs-and-no-qa-pull-cache-job + <<: *dedicated-no-docs-pull-cache-job image: dev.gitlab.org:5005/gitlab/gitlab-build-images:ruby-2.5.3-git-2.18-chrome-69.0-node-8.x-yarn-1.12-graphicsmagick-1.3.29-docker-18.06.1 dependencies: [] services: diff --git a/.gitlab/CODEOWNERS.disabled b/.gitlab/CODEOWNERS.disabled index 82e914a502f..b9f886c1d47 100644 --- a/.gitlab/CODEOWNERS.disabled +++ b/.gitlab/CODEOWNERS.disabled @@ -1,6 +1,6 @@ # Backend Maintainers are the default for all ruby files -*.rb @ayufan @DouweM @dzaporozhets @grzesiek @nick.thomas @rspeicher @rymai @smcgivern -*.rake @ayufan @DouweM @dzaporozhets @grzesiek @nick.thomas @rspeicher @rymai @smcgivern +*.rb @ayufan @dbalexandre @DouweM @dzaporozhets @godfat @grzesiek @nick.thomas @rspeicher @rymai @smcgivern +*.rake @ayufan @dbalexandre @DouweM @dzaporozhets @godfat @grzesiek @nick.thomas @rspeicher @rymai @smcgivern # Technical writing team are the default reviewers for everything in `doc/` /doc/ @axil @marcia diff --git a/.gitlab/issue_templates/Feature proposal.md b/.gitlab/issue_templates/Feature proposal.md index ad517f0457d..639a236631d 100644 --- a/.gitlab/issue_templates/Feature proposal.md +++ b/.gitlab/issue_templates/Feature proposal.md @@ -4,7 +4,7 @@ ### Target audience -<!--- For whom are we doing this? Include either a persona from https://design.gitlab.com/#/getting-started/personas or define a specific company role. e.a. "Release Manager" or "Security Analyst" --> +<!--- For whom are we doing this? Include either a persona from https://design.gitlab.com/getting-started/personas or define a specific company role. e.a. "Release Manager" or "Security Analyst" --> ### Further details diff --git a/.rubocop.yml b/.rubocop.yml index 741403af009..e8e550fdbde 100644 --- a/.rubocop.yml +++ b/.rubocop.yml @@ -8,7 +8,7 @@ require: - rubocop-rspec AllCops: - TargetRailsVersion: 4.2 + TargetRailsVersion: 5.0 Exclude: - 'vendor/**/*' - 'node_modules/**/*' @@ -174,3 +174,9 @@ GitlabSecurity/PublicSend: - 'ee/db/**/*' - 'ee/lib/**/*.rake' - 'ee/spec/**/*' + +Cop/InjectEnterpriseEditionModule: + Enabled: true + Exclude: + - 'spec/**/*' + - 'ee/spec/**/*' diff --git a/.rubocop_todo.yml b/.rubocop_todo.yml index 3ab76965287..847a0f74aa2 100644 --- a/.rubocop_todo.yml +++ b/.rubocop_todo.yml @@ -149,17 +149,6 @@ RSpec/ExpectChange: RSpec/ExpectInHook: Enabled: false -# Offense count: 7 -# Configuration parameters: EnforcedStyle. -# SupportedStyles: implicit, each, example -RSpec/HookArgument: - Exclude: - - 'spec/spec_helper.rb' - - 'spec/support/carrierwave.rb' - - 'spec/support/db_cleaner.rb' - - 'spec/support/gitaly.rb' - - 'spec/support/setup_builds_storage.rb' - # Offense count: 19 # Configuration parameters: EnforcedStyle. # SupportedStyles: it_behaves_like, it_should_behave_like diff --git a/CHANGELOG.md b/CHANGELOG.md index d1e324c5518..56ac809b548 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -2,6 +2,328 @@ documentation](doc/development/changelog.md) for instructions on adding your own entry. +## 11.6.2 (2019-01-02) + +### Fixed (7 changes) + +- Hide cluster features that don't work yet with Group Clusters. !23935 +- Fix a 500 error that could occur until all migrations are done. !23939 +- Fix missing Git clone button when protocol restriction setting enabled. !24015 +- Fix clone dropdown parent inheritance issues in HAML. !24029 +- Fix content-disposition in blobs and files API endpoint. !24078 +- Fixed markdown toolbar buttons. +- Adjust line-height of blame view line numbers. + + +## 11.6.1 (2018-12-28) + +### Security (15 changes) + +- Escape label and milestone titles to prevent XSS in GFM autocomplete. !2740 +- Prevent private snippets from being embeddable. +- Add subresources removal to member destroy service. +- Escape html entities in LabelReferenceFilter when no label found. +- Allow changing group CI/CD settings only for owners. +- Authorize before reading job information via API. +- Prevent leaking protected variables for ambiguous refs. +- Ensure that build token is only used when running. +- Issuable no longer is visible to users when project can't be viewed. +- Don't expose cross project repositories through diffs when creating merge reqeusts. +- Fix SSRF with import_url and remote mirror url. +- Fix persistent symlink in project import. +- Set URL rel attribute for broken URLs. +- Project guests no longer are able to see refs page. +- Delete confidential todos for user when downgraded to Guest. + +### Other (1 change) + +- Fix due date test. !23845 + + +## 11.6.0 (2018-12-22) + +### Security (24 changes, 1 of them is from the community) + +- Fix possible XSS attack in Markdown urls with spaces. !2599 +- Update rack to 2.0.6 (for QA environments). !23171 (Takuya Noguchi) +- Bump nokogiri, loofah, and rack gems for security updates. !23204 +- Encrypt runners tokens. !23412 +- Encrypt CI/CD builds authentication tokens. !23436 +- Configure mermaid to not render HTML content in diagrams. +- Fix a possible symlink time of check to time of use race condition in GitLab Pages. +- Removed ability to see private group names when the group id is entered in the url. +- Fix stored XSS for Environments. +- Fix persistent symlink in project import. +- Fixed ability of guest users to edit/delete comments on locked or confidential issues. +- Fixed ability to comment on locked/confidential issues. +- Fix CRLF vulnerability in Project hooks. +- Fix SSRF in project integrations. +- Resolve reflected XSS in Ouath authorize window. +- Restrict Personal Access Tokens to API scope on web requests. +- Provide email notification when a user changes their email address. +- Don't expose confidential information in commit message list. +- Validate LFS hrefs before downloading them. +- Do not follow redirects in Prometheus service when making http requests to the configured api url. +- Escape user fullname while rendering autocomplete template to prevent XSS. +- Redact sensitive information on gitlab-workhorse log. +- Fix milestone promotion authorization check. +- Prevent a path traversal attack on global file templates. + +### Removed (1 change) + +- Remove obsolete gitlab_shell rake tasks. !22417 + +### Fixed (86 changes, 13 of them are from the community) + +- Remove limit of 100 when searching repository code. !8671 +- Show error message when attempting to reopen an MR and there is an open MR for the same branch. !16447 (Akos Gyimesi) +- Fix a bug where internal email pattern wasn't respected. !22516 +- Fix project selector consistency in groups issues / MRs / boards pages. !22612 (Heinrich Lee Yu) +- Add empty state for graphs with no values. !22630 +- Fix navigating by unresolved discussions on Merge Request page. !22789 +- Fix "merged with [commit]" info for merge requests being merged automatically by other actions. !22794 +- Fixing regression issues on pages settings and details. !22821 +- Remove duplicate primary button in dashboard snippets on small viewports. !22902 (George Tsiolis) +- Fix API::Namespaces routing to accept namepaces with dots. !22912 +- Switch kubernetes:active with checking in Auto-DevOps.gitlab-ci.yml. !22929 +- Avoid Gitaly RPC errors when fetching diff stats. !22995 +- Removes promote to group label for anonymous user. !23042 (Jacopo Beschi @jacopo-beschi) +- Fix enabling project deploy key for admins. !23043 +- Align issue status label and confidential icon. !23046 (George Tsiolis) +- Fix default sorting for subgroups and projects list. !23058 (Jacopo Beschi @jacopo-beschi) +- Hashed Storage: allow migration to be retried in partially migrated projects. !23087 +- Fix line height of numbers in file blame view. !23090 (Johann Hubert Sonntagbauer) +- Fixes an issue where default values from models would override values set in the interface (e.g. users would be set to external even though their emails matches the internal email address pattern). !23114 +- Remove display of local Sidekiq process in /admin/sidekiq. !23118 +- Fix unrelated deployment status in MR widget. !23175 +- Respect confirmed flag on secondary emails. !23181 +- Restrict member access level to be higher than that of any parent group. !23226 +- Return real deployment status to frontend. !23270 +- Handle force_remove_source_branch when creating merge request. !23281 +- Avoid creating invalid refs using rugged, shelling out for writing refs. !23286 +- Remove needless auto-capitalization on Wiki page titles. !23288 +- Modify the wording for the knative cluster application to match upstream. !23289 (Chris Baumbauer) +- Change container width for project import. !23318 (George Tsiolis) +- Validate chunk size when persist. !23341 +- Resolve Main navbar is broken in certain viewport widths. !23348 +- Gracefully handle references with null bytes. !23365 +- Display commit ID for commit diff discussion on merge request. !23370 +- Pass commit when posting diff discussions. !23371 +- Fix flash notice styling for fluid layout. !23382 +- Add monkey patch to unicorn to fix eof? problem. !23385 +- Commits API: Preserve file content in move operations if unspecified. !23387 +- Disable password autocomplete in mirror form fill. !23402 +- Fix "protected branches only" checkbox not set properly at init. !23409 +- Support RSA and ECDSA algorithms in Omniauth JWT provider. !23411 (Michael Tsyganov) +- Make KUBECONFIG nil if KUBE_TOKEN is nil. !23414 +- Allow search and sort users at same time on admin users page. !23439 +- Fix: Unstar icon button is misaligned. !23444 +- Fix error when searching for group issues with priority or popularity sort. !23445 +- Fix Order By dropdown menu styling in tablet and mobile screens. !23446 +- Fix collapsing discussion replies. !23462 +- Gracefully handle unknown/invalid GPG keys. !23492 +- Fix multiple commits shade overlapping vertical discussion line. !23515 +- Use read_repository scope on read-only files API. !23534 +- Avoid 500's when serializing legacy diff notes. !23544 +- Fix web hook functionality when the database encryption key is too short. !23573 +- Hide Knative from group cluster applications until supported. !23577 +- Add top padding for nested environment items loading icon. !23580 (George Tsiolis) +- Improve help and validation sections of maximum build timeout inputs. !23586 +- Fix milestone select in issue sidebar of issue boards. !23625 +- Fix gitlab:web_hook tasks. !23635 +- Avoid caching BroadcastMessage as an ActiveRecord object. !23662 +- Only allow strings in URL::Sanitizer.valid?. !23675 +- Fix a frozen string error in app/mailers/notify.rb. !23683 +- Fix a frozen string error in lib/gitlab/utils.rb. !23690 +- Fix MR resolved discussion counts being too low. !23710 +- Fix a potential frozen string error in app/mailers/notify.rb. !23728 +- Remove unnecessary div from MarkdownField to apply list styles correctly. !23733 +- Display reply field if resolved discussion has no replies. !23801 +- Restore kubernetes:active in Auto-DevOps.gitlab-ci.yml (reverts 22929). !23826 +- Fix mergeUrlParams with fragment URL. !54218 (Thomas Holder) +- Fixed multiple diff line discussions not expanding. +- Fixed diff files expanding not loading commit content. +- Fixed styling of image comment badges on commits. +- Resolve possible cherry pick API race condition. +- When user clicks linenumber in MR changes, highlight that line. +- Remove old webhook logs after 90 days, as documented, instead of after 2. +- Add an external IP address to the knative cluster application page. (Chris Baumbauer) +- Fixed duplicate discussions getting added to diff lines. +- Fix deadlock on ChunkedIO. +- Show tree collapse button for merge request commit diffs. +- Use approximate count for big tables for usage statistics. +- Lock writes to trace stream. +- Ensure that SVG sprite icons are properly rendered in IE11. +- Make new branch form fields' fonts consistent. +- Open first 10 merge request files in IDE. +- Prevent user from navigating away from file edit without commit. +- Prevent empty button being rendered in empty state. +- Adds margins between tags when a job is stuck. +- Fix Image Lazy Loader for some older browsers. +- Correctly styles tags in sidebar for job page. + +### Changed (34 changes, 9 of them are from the community) + +- Include new link in breadcrumb for issues, merge requests, milestones, and labels. !18515 (George Tsiolis) +- Allow sorting issues and MRs in reverse order. !21438 +- Design improvements to project overview page. !22196 +- Remove auto deactivation when failed to create a pipeline via pipeline schedules. !22243 +- Use group clusters when deploying (DeploymentPlatform). !22308 +- Improve initial discussion rendering performance. !22607 +- removes partially matching of No Label filter and makes it case-insensitive. !22622 (Jacopo Beschi @jacopo-beschi) +- Use search bar for filtering in dashboard issues / MRs. !22641 (Heinrich Lee Yu) +- Show different empty state for filtered issues and MRs. !22775 (Heinrich Lee Yu) +- Relocate JSONWebToken::HMACToken from EE. !22906 +- Resolve Add border around the repository file tree. !23018 +- Change breadcrumb title for contribution charts. !23071 (George Tsiolis) +- Update environments metrics empty state. !23074 (George Tsiolis) +- Refine cursor positioning in Markdown Editor for wrap tags. !23085 (Johann Hubert Sonntagbauer) +- Use reports syntax for SAST in Auto DevOps. !23163 +- SystemCheck: Use a more reliable way to detect current Ruby version. !23291 +- Changed frontmatter filtering to support YAML, JSON, TOML, and arbitrary languages. !23331 (Travis Miller) +- Don't remove failed install pods after installing GitLab managed applications. !23350 +- Expose merge request pipeline variables. !23398 +- Scope default MR search in WebIDE dropdown to current project. !23400 +- Show user contributions in correct timezone within user profile. !23419 +- Redesign of MR header sections (CE). !23465 +- Auto DevOps: Add echo for each branch of the deploy() function where we run helm upgrade. !23499 +- Updates service to update Kubernetes project namespaces and restricted service account if present. !23525 +- Adjust divider margin to comply with design specs. !23548 +- Adjust dropdown item and header padding to comply with design specs. !23552 +- Truncate merge request titles with periods instead of ellipsis. !23558 +- Remove close icon from projects dropdown in issue boards. !23567 +- Change dropdown divider color to gray-200 (#dfdfdf). !23592 +- Define the default value for only/except policies. !23765 +- Don't show Memory Usage for unmerged MRs. +- reorder notification settings by noisy-ness. (C.J. Jameson) +- Changed merge request filtering to be by path instead of name. +- Make diff file headers sticky. + +### Performance (22 changes, 6 of them are from the community) + +- Upgrade to Ruby 2.5.3. !2806 +- Removes all the irrelevant code and columns that were migrated from the Project table over to the ProjectImportState table. !21497 +- Approximate counting strategy with TABLESAMPLE. !22650 +- Replace tooltip directive with gl-tooltip diretive in badges, cycle analytics, and diffs. !22770 (George Tsiolis) +- Validate foreign keys being created and indexed for column with _id. !22808 +- Remove monospace extend. !23089 (George Tsiolis) +- Use Nokogiri as the ActiveSupport XML backend. !23136 +- Improve memory performance by reducing dirty pages after fork(). !23169 +- Add partial index for ci_builds on project_id and status. !23268 +- Reduce Gitaly calls in projects dashboard. !23307 +- Batch load only data from same repository when lazy object is accessed. !23309 +- Add index for events on project_id and created_at. !23354 +- Remove index for notes on updated_at. !23356 +- Improves performance of Project#readme_url by caching the README path. !23357 +- Populate MR metrics with events table information (migration). !23564 +- Remove unused data from discussions endpoint. !23570 +- Speed up issue board lists in groups with many projects. +- Use cached size when passing artifacts to Runner. +- Enable even more frozen string for lib/gitlab. (gfyoung) +- Enable even more frozen string in lib/gitlab/**/*.rb. (gfyoung) +- Enable even more frozen string in lib/gitlab/**/*.rb. (gfyoung) +- Enable even more frozen string for lib/gitlab. (gfyoung) + +### Added (32 changes, 13 of them are from the community) + +- Add ability to create group level clusters and install gitlab managed applications. !22450 +- Creates /create_merge_request quickaction. !22485 (Jacopo Beschi @jacopo-beschi) +- Filter by None/Any for labels in issues/mrs API. !22622 (Jacopo Beschi @jacopo-beschi) +- Chat message push notifications now include links back to GitLab branches. !22651 (Tony Castrogiovanni) +- Added feature flag to signal content headers detection by Workhorse. !22667 +- Add Discord integration. !22684 (@blackst0ne) +- Upgrade helm to 2.11.0 and upgrade on every install. !22693 +- Add knative client to kubeclient library. !22968 (cab105) +- Allow SSH public-key authentication for push mirroring. !22982 +- Allow deleting a Pipeline via the API. !22988 +- #40635: Adds support for cert-manager. !23036 (Amit Rathi) +- WebIDE: Pressing Ctrl-Enter while typing on the commit message now performs the commit action. !23049 (Thomas Pathier) +- Adds Any option to label filters. !23111 (Jacopo Beschi @jacopo-beschi) +- Added glob for CI changes detection. !23128 (Kirill Zaitsev) +- Add model and relation to store repo full path in database. !23143 +- Add ability to render suggestions. !23147 +- Introduce Knative and Serverless Components. !23174 (Chris Baumbauer) +- Use BFG object maps to clean projects. !23189 +- Merge request pipelines. !23217 +- Extended user centric tooltips on issue and MR page. !23231 +- Add a rebase API endpoint for merge requests. !23296 +- Add config to prohibit impersonation. !23338 +- Merge request pipeline tag, and adds tags to pipeline view. !23364 +- #52753: HTTPS for JupyterHub installation. !23479 (Amit Rathi) +- Fill project_repositories for hashed storage projects. !23482 +- Ability to override email for cert-manager. !23503 (Amit Rathi) +- Allow public forks to be deduplicated. !23508 +- Pipeline trigger variable values are hidden in the UI by default. Maintainers have the option to reveal them. !23518 (jhampton) +- Add new endpoint to download single artifact file for a ref. !23538 +- Log and pass correlation-id between Unicorn, Sidekiq and Gitaly. +- Allow user to scroll to top of tab on MR page. +- Adds states to the deployment widget. + +### Other (54 changes, 30 of them are from the community) + +- Switch to Rails 5. !21492 +- Migration to write fullpath in all repository configs. !22322 +- Rails5: env is deprecated and will be removed from Rails 5.1. !22626 (Jasper Maes) +- Update haml_lint to 0.28.0. !22660 (Takuya Noguchi) +- Update ffaker to 2.10.0. !22661 (Takuya Noguchi) +- Drop gcp_clusters table. !22713 +- Upgrade minimum required Git version to 2.18.0. !22803 +- Adds new icon size to Vue icon component. !22899 +- Make sure there's only one slash as path separator. !22954 +- Show HTTP response code for Kubernetes errors. !22964 +- Update config map for gitlab managed application if already present on install. !22969 +- Drop default value on status column in deployments table. !22971 +- UI improvements to user's profile. !22977 +- Update asana to 0.8.1. !23039 (Takuya Noguchi) +- Update asciidoctor to 1.5.8. !23047 (Takuya Noguchi) +- Make auto-generated icons for subgroups in the breadcrumb dropdown display as a circle. !23062 (Thomas Pathier) +- Make reply shortcut only quote selected discussion text. !23096 (Thomas Pathier) +- Fix typo in notebook props. !23103 (George Tsiolis) +- Fix typos in lib. !23106 (George Tsiolis) +- Rename diffs store variable. !23123 (George Tsiolis) +- Fix overlapping navbar separator and overflowing navbar dropdown on small displays. !23126 (Thomas Pathier) +- Show what RPC is called in the performance bar. !23140 +- Updated Gitaly to v0.133.0. !23148 +- Rails5: Passing a class as a value in an Active Record query is deprecated. !23164 (Jasper Maes) +- Fix project identicon aligning Harry Kiselev. !23166 (Harry Kiselev) +- Fix horizontal scrollbar overlapping on horizontal scrolling-tabs. !23167 (Harry Kiselev) +- Fix bottom paddings of profile header and some markup updates of profile. !23168 (Harry Kiselev) +- Fixes to AWS documentation spelling and grammar. !23198 (Brendan O'Leary) +- Adds a PHILOSOPHY.md which references GitLab Product Handbook. !23200 +- Externalize strings from `/app/views/invites`. !23205 (Tao Wang) +- Externalize strings from `/app/views/project/runners`. !23208 (Tao Wang) +- Fix typo for scheduled pipeline. !23218 (Davy Defaud) +- Force content disposition attachment to several endpoints. !23223 +- Upgrade kubeclient to 4.0.0. !23261 (Praveen Arimbrathodiyil @pravi) +- Update used version of Runner Helm Chart to 0.1.38. !23304 +- render :nothing option is deprecated, Use head method to respond with empty response body. !23311 (Jasper Maes) +- Passing an argument to force an association to reload is now deprecated. !23334 (Jasper Maes) +- Externalize strings from `/app/views/snippets`. !23351 (Tao Wang) +- Fix deprecation: You are passing an instance of ActiveRecord::Base to. !23369 (Jasper Maes) +- Resolve status emoji being replaced by avatar on mobile. !23408 +- Fix deprecation: render :text is deprecated because it does not actually render a text/plain response. !23425 (Jasper Maes) +- Fix lack of documentation on how to fetch a snippet's content using API. !23448 (Colin Leroy) +- Upgrade GitLab Workhorse to v7.3.0. !23489 +- Fallback to admin KUBE_TOKEN for project clusters only. !23527 +- Update used version of Runner Helm Chart to 0.1.39. !23633 +- Show primary button when all labels are prioritized. !23648 (George Tsiolis) +- Upgrade workhorse to 7.6.0. !23694 +- Upgrade Gitaly to v1.7.1 for correlation-id logging. !23732 +- Fix due date test. !23845 +- Remove unused project method. !54103 (George Tsiolis) +- Uses new gitlab-ui components in Jobs and Pipelines components. +- Replaces tooltip directive with the new gl-tooltip directive for consistency in some ci/cd code. +- Bump gpgme gem version from 2.0.13 to 2.0.18. (asaparov) +- Enable Rubocop on lib/gitlab. (gfyoung) + + +## 11.5.5 (2018-12-20) + +### Security (1 change) + +- Fix persistent symlink in project import. + + ## 11.5.3 (2018-12-06) ### Security (1 change) @@ -628,6 +950,20 @@ entry. - Check frozen string in style builds. (gfyoung) +## 11.3.14 (2018-12-20) + +### Security (1 change) + +- Fix persistent symlink in project import. + + +## 11.3.13 (2018-12-13) + +### Security (1 change) + +- Validate LFS hrefs before downloading them. + + ## 11.3.12 (2018-12-06) ### Security (1 change) diff --git a/Dangerfile b/Dangerfile index 469e77b2514..6a2c5cf2773 100644 --- a/Dangerfile +++ b/Dangerfile @@ -8,5 +8,6 @@ danger.import_dangerfile(path: 'danger/database') danger.import_dangerfile(path: 'danger/documentation') danger.import_dangerfile(path: 'danger/frozen_string') danger.import_dangerfile(path: 'danger/commit_messages') +danger.import_dangerfile(path: 'danger/duplicate_yarn_dependencies') danger.import_dangerfile(path: 'danger/prettier') danger.import_dangerfile(path: 'danger/eslint') diff --git a/GITALY_SERVER_VERSION b/GITALY_SERVER_VERSION index bd8bf882d06..0eed1a29efd 100644 --- a/GITALY_SERVER_VERSION +++ b/GITALY_SERVER_VERSION @@ -1 +1 @@ -1.7.0 +1.12.0 diff --git a/GITLAB_SHELL_VERSION b/GITLAB_SHELL_VERSION index 016dac34bf9..917d38ec9f9 100644 --- a/GITLAB_SHELL_VERSION +++ b/GITLAB_SHELL_VERSION @@ -1 +1 @@ -8.4.3 +8.4.4 @@ -1,22 +1,6 @@ -# --- Special code for migrating to Rails 5.0 --- -def rails5? - !%w[0 false].include?(ENV["RAILS5"]) -end - -gem_versions = {} -gem_versions['activerecord_sane_schema_dumper'] = rails5? ? '1.0' : '0.2' -gem_versions['rails'] = rails5? ? '5.0.7' : '4.2.11' -gem_versions['rails-i18n'] = rails5? ? '~> 5.1' : '~> 4.0.9' - -# The 2.0.6 version of rack requires monkeypatch to be present in -# `config.ru`. This can be removed once a new update for Rack -# is available that contains https://github.com/rack/rack/pull/1201. -gem_versions['rack'] = rails5? ? '2.0.6' : '1.6.11' -# --- The end of special code for migrating to Rails 5.0 --- - source 'https://rubygems.org' -gem 'rails', gem_versions['rails'] +gem 'rails', '5.0.7.1' gem 'rails-deprecated_sanitizer', '~> 1.0.3' # Improves copy-on-write performance for MRI @@ -28,11 +12,7 @@ gem 'responders', '~> 2.0' gem 'sprockets', '~> 3.7.0' # Default values for AR models -if rails5? - gem 'gitlab-default_value_for', '~> 3.1.1', require: 'default_value_for' -else - gem 'default_value_for', '~> 3.0.0' -end +gem 'gitlab-default_value_for', '~> 3.1.1', require: 'default_value_for' # Supported DBs gem 'mysql2', '~> 0.4.10', group: :mysql @@ -54,7 +34,7 @@ gem 'omniauth-cas3', '~> 1.1.4' gem 'omniauth-facebook', '~> 4.0.0' gem 'omniauth-github', '~> 1.3' gem 'omniauth-gitlab', '~> 1.0.2' -gem 'omniauth-google-oauth2', '~> 0.5.3' +gem 'omniauth-google-oauth2', '~> 0.6.0' gem 'omniauth-kerberos', '~> 0.3.0', group: :kerberos gem 'omniauth-oauth2-generic', '~> 0.2.2' gem 'omniauth-saml', '~> 1.10' @@ -63,7 +43,7 @@ gem 'omniauth-twitter', '~> 1.4' gem 'omniauth_crowd', '~> 2.2.0' gem 'omniauth-authentiq', '~> 0.3.3' gem 'rack-oauth2', '~> 1.2.1' -gem 'jwt', '~> 1.5.6' +gem 'jwt', '~> 2.1.0' # Spam and anti-bot protection gem 'recaptcha', '~> 3.0', require: 'recaptcha/rails' @@ -109,9 +89,7 @@ gem 'kaminari', '~> 1.0' gem 'hamlit', '~> 2.8.8' # Files attachments -# Locked until https://github.com/carrierwaveuploader/carrierwave/pull/2332/files is merged. -# config/initializers/carrierwave_patch.rb can be removed once that change is released. -gem 'carrierwave', '= 1.2.3' +gem 'carrierwave', '~> 1.3' gem 'mini_magick' # for backups @@ -149,7 +127,7 @@ gem 'asciidoctor-plantuml', '0.0.8' gem 'rouge', '~> 3.1' gem 'truncato', '~> 0.7.9' gem 'bootstrap_form', '~> 2.7.0' -gem 'nokogiri', '~> 1.8.2' +gem 'nokogiri', '~> 1.8.5' gem 'escape_utils', '~> 1.1' # Calendar rendering @@ -159,7 +137,10 @@ gem 'icalendar' gem 'diffy', '~> 3.1.0' # Application server -gem 'rack', gem_versions['rack'] +# The 2.0.6 version of rack requires monkeypatch to be present in +# `config.ru`. This can be removed once a new update for Rack +# is available that contains https://github.com/rack/rack/pull/1201. +gem 'rack', '2.0.6' group :unicorn do gem 'unicorn', '~> 5.1.0' @@ -181,6 +162,7 @@ gem 'acts-as-taggable-on', '~> 5.0' gem 'sidekiq', '~> 5.2.1' gem 'sidekiq-cron', '~> 0.6.0' gem 'redis-namespace', '~> 1.6.0' +gem 'gitlab-sidekiq-fetcher', '~> 0.4.0', require: 'sidekiq-reliable-fetch' # Cron Parser gem 'rufus-scheduler', '~> 3.4' @@ -297,7 +279,7 @@ gem 'premailer-rails', '~> 1.9.7' # I18n gem 'ruby_parser', '~> 3.8', require: false -gem 'rails-i18n', gem_versions['rails-i18n'] +gem 'rails-i18n', '~> 5.1' gem 'gettext_i18n_rails', '~> 1.8.0' gem 'gettext_i18n_rails_js', '~> 1.3' gem 'gettext', '~> 3.2.2', require: false, group: :development @@ -307,11 +289,10 @@ gem 'batch-loader', '~> 1.2.2' # Perf bar gem 'peek', '~> 1.0.1' gem 'peek-gc', '~> 0.0.2' -gem 'peek-mysql2', '~> 1.1.0', group: :mysql +gem 'peek-mysql2', '~> 1.2.0', group: :mysql gem 'peek-pg', '~> 1.3.0', group: :postgres gem 'peek-rblineprof', '~> 0.2.0' gem 'peek-redis', '~> 1.2.0' -gem 'gitlab-sidekiq-fetcher', require: 'sidekiq-reliable-fetch' # Metrics group :metrics do @@ -341,13 +322,13 @@ end group :development, :test do gem 'bootsnap', '~> 1.3' gem 'bullet', '~> 5.5.0', require: !!ENV['ENABLE_BULLET'] - gem 'pry-byebug', '~> 3.4.1', platform: :mri + gem 'pry-byebug', '~> 3.5.1', platform: :mri gem 'pry-rails', '~> 0.3.4' gem 'awesome_print', require: false gem 'fuubar', '~> 2.2.0' - gem 'database_cleaner', '~> 1.5.0' + gem 'database_cleaner', '~> 1.7.0' gem 'factory_bot_rails', '~> 4.8.2' gem 'rspec-rails', '~> 3.7.0' gem 'rspec-retry', '~> 0.4.5' @@ -356,13 +337,13 @@ group :development, :test do gem 'rspec-parameterized', require: false # Prevent occasions where minitest is not bundled in packaged versions of ruby (see #3826) - gem 'minitest', '~> 5.7.0' + gem 'minitest', '~> 5.11.0' # Generate Fake data gem 'ffaker', '~> 2.10' - gem 'capybara', '~> 2.15' - gem 'capybara-screenshot', '~> 1.0.0' + gem 'capybara', '~> 2.16.1' + gem 'capybara-screenshot', '~> 1.0.18' gem 'selenium-webdriver', '~> 3.12' gem 'spring', '~> 2.0.0' @@ -383,7 +364,7 @@ group :development, :test do gem 'license_finder', '~> 5.4', require: false gem 'knapsack', '~> 1.17' - gem 'activerecord_sane_schema_dumper', gem_versions['activerecord_sane_schema_dumper'] + gem 'activerecord_sane_schema_dumper', '1.0' gem 'stackprof', '~> 0.2.10', require: false @@ -397,8 +378,7 @@ group :test do gem 'email_spec', '~> 2.2.0' gem 'json-schema', '~> 2.8.0' gem 'webmock', '~> 2.3.2' - gem 'rails-controller-testing' if rails5? # Rails5 only gem. - gem 'test_after_commit', '~> 1.1' unless rails5? # Remove this gem when migrated to rails 5.0. It's been integrated to rails 5.0. + gem 'rails-controller-testing' gem 'sham_rack', '~> 1.3.6' gem 'concurrent-ruby', '~> 1.1' gem 'test-prof', '~> 0.2.5' @@ -436,7 +416,7 @@ group :ed25519 do end # Gitaly GRPC client -gem 'gitaly-proto', '~> 1.3.0', require: 'gitaly' +gem 'gitaly-proto', '~> 1.5.0', require: 'gitaly' gem 'grpc', '~> 1.15.0' gem 'google-protobuf', '~> 3.6' diff --git a/Gemfile.lock b/Gemfile.lock index b9780d4c23f..4dda0bcad18 100644 --- a/Gemfile.lock +++ b/Gemfile.lock @@ -4,41 +4,41 @@ GEM RedCloth (4.3.2) abstract_type (0.0.7) ace-rails-ap (4.1.2) - actioncable (5.0.7) - actionpack (= 5.0.7) + actioncable (5.0.7.1) + actionpack (= 5.0.7.1) nio4r (>= 1.2, < 3.0) websocket-driver (~> 0.6.1) - actionmailer (5.0.7) - actionpack (= 5.0.7) - actionview (= 5.0.7) - activejob (= 5.0.7) + actionmailer (5.0.7.1) + actionpack (= 5.0.7.1) + actionview (= 5.0.7.1) + activejob (= 5.0.7.1) mail (~> 2.5, >= 2.5.4) rails-dom-testing (~> 2.0) - actionpack (5.0.7) - actionview (= 5.0.7) - activesupport (= 5.0.7) + actionpack (5.0.7.1) + actionview (= 5.0.7.1) + activesupport (= 5.0.7.1) rack (~> 2.0) rack-test (~> 0.6.3) rails-dom-testing (~> 2.0) rails-html-sanitizer (~> 1.0, >= 1.0.2) - actionview (5.0.7) - activesupport (= 5.0.7) + actionview (5.0.7.1) + activesupport (= 5.0.7.1) builder (~> 3.1) erubis (~> 2.7.0) rails-dom-testing (~> 2.0) rails-html-sanitizer (~> 1.0, >= 1.0.3) - activejob (5.0.7) - activesupport (= 5.0.7) + activejob (5.0.7.1) + activesupport (= 5.0.7.1) globalid (>= 0.3.6) - activemodel (5.0.7) - activesupport (= 5.0.7) - activerecord (5.0.7) - activemodel (= 5.0.7) - activesupport (= 5.0.7) + activemodel (5.0.7.1) + activesupport (= 5.0.7.1) + activerecord (5.0.7.1) + activemodel (= 5.0.7.1) + activesupport (= 5.0.7.1) arel (~> 7.0) activerecord_sane_schema_dumper (1.0) rails (>= 5, < 6) - activesupport (5.0.7) + activesupport (5.0.7.1) concurrent-ruby (~> 1.0, >= 1.0.2) i18n (>= 0.7, < 2) minitest (~> 5.1) @@ -97,18 +97,18 @@ GEM bundler-audit (0.5.0) bundler (~> 1.2) thor (~> 0.18) - byebug (9.0.6) - capybara (2.15.1) + byebug (9.1.0) + capybara (2.16.1) addressable mini_mime (>= 0.1.3) nokogiri (>= 1.3.3) rack (>= 1.0.0) rack-test (>= 0.5.4) xpath (~> 2.0) - capybara-screenshot (1.0.14) - capybara (>= 1.0, < 3) + capybara-screenshot (1.0.22) + capybara (>= 1.0, < 4) launchy - carrierwave (1.2.3) + carrierwave (1.3.0) activemodel (>= 4.0.0) activesupport (>= 4.0.0) mime-types (>= 1.16) @@ -140,7 +140,7 @@ GEM css_parser (1.5.0) addressable daemons (1.2.6) - database_cleaner (1.5.3) + database_cleaner (1.7.0) debug_inspector (0.0.3) debugger-ruby_core_source (1.3.8) deckar01-task_list (2.0.0) @@ -274,13 +274,13 @@ GEM gettext_i18n_rails (>= 0.7.1) po_to_json (>= 1.0.0) rails (>= 3.2.0) - gitaly-proto (1.3.0) + gitaly-proto (1.5.0) grpc (~> 1.0) github-markup (1.7.0) gitlab-default_value_for (3.1.1) activerecord (>= 3.2.0, < 6.0) gitlab-markup (1.6.5) - gitlab-sidekiq-fetcher (0.3.0) + gitlab-sidekiq-fetcher (0.4.0) sidekiq (~> 5) gitlab-styles (2.4.1) rubocop (~> 0.54.0) @@ -381,7 +381,7 @@ GEM json (~> 1.8) multi_xml (>= 0.5.2) httpclient (2.8.3) - i18n (1.1.1) + i18n (1.2.0) concurrent-ruby (~> 1.0) icalendar (2.4.1) ice_nine (0.11.2) @@ -403,7 +403,7 @@ GEM bindata json-schema (2.8.0) addressable (>= 2.4) - jwt (1.5.6) + jwt (2.1.0) kaminari (1.0.1) activesupport (>= 4.1.0) kaminari-actionview (= 1.0.1) @@ -449,7 +449,7 @@ GEM loofah (2.2.3) crass (~> 1.0.2) nokogiri (>= 1.5.9) - mail (2.7.0) + mail (2.7.1) mini_mime (>= 0.1.1) mail_room (0.9.1) memoist (0.16.0) @@ -463,7 +463,7 @@ GEM mini_magick (4.8.0) mini_mime (1.0.1) mini_portile2 (2.3.0) - minitest (5.7.0) + minitest (5.11.3) msgpack (1.2.4) multi_json (1.13.1) multi_xml (0.6.0) @@ -483,24 +483,24 @@ GEM nokogiri numerizer (0.1.1) oauth (0.5.4) - oauth2 (1.4.0) - faraday (>= 0.8, < 0.13) - jwt (~> 1.0) + oauth2 (1.4.1) + faraday (>= 0.8, < 0.16.0) + jwt (>= 1.0, < 3.0) multi_json (~> 1.3) multi_xml (~> 0.5) rack (>= 1.2, < 3) octokit (4.9.0) sawyer (~> 0.8.0, >= 0.5.3) - omniauth (1.8.1) - hashie (>= 3.4.6, < 3.6.0) + omniauth (1.9.0) + hashie (>= 3.4.6, < 3.7.0) rack (>= 1.6.2, < 3) omniauth-auth0 (2.0.0) omniauth-oauth2 (~> 1.4) omniauth-authentiq (0.3.3) jwt (>= 1.5) omniauth-oauth2 (>= 1.5) - omniauth-azure-oauth2 (0.0.9) - jwt (~> 1.0) + omniauth-azure-oauth2 (0.0.10) + jwt (>= 1.0, < 3.0) omniauth (~> 1.0) omniauth-oauth2 (~> 1.4) omniauth-cas3 (1.1.4) @@ -515,8 +515,8 @@ GEM omniauth-gitlab (1.0.3) omniauth (~> 1.0) omniauth-oauth2 (~> 1.0) - omniauth-google-oauth2 (0.5.3) - jwt (>= 1.5) + omniauth-google-oauth2 (0.6.0) + jwt (>= 2.0) omniauth (>= 1.1.1) omniauth-oauth2 (>= 1.5) omniauth-kerberos (0.3.0) @@ -527,9 +527,9 @@ GEM omniauth-oauth (1.1.0) oauth omniauth (~> 1.0) - omniauth-oauth2 (1.5.0) + omniauth-oauth2 (1.6.0) oauth2 (~> 1.1) - omniauth (~> 1.2) + omniauth (~> 1.9) omniauth-oauth2-generic (0.2.2) omniauth-oauth2 (~> 1.0) omniauth-saml (1.10.0) @@ -558,8 +558,9 @@ GEM railties (>= 4.0.0) peek-gc (0.0.2) peek - peek-mysql2 (1.1.0) - atomic (>= 1.0.0) + peek-mysql2 (1.2.0) + concurrent-ruby + concurrent-ruby-ext mysql2 peek peek-pg (1.3.0) @@ -594,8 +595,8 @@ GEM pry (0.11.3) coderay (~> 1.1.0) method_source (~> 0.9.0) - pry-byebug (3.4.3) - byebug (>= 9.0, < 9.1) + pry-byebug (3.5.1) + byebug (~> 9.1) pry (~> 0.10) pry-rails (0.3.6) pry (>= 0.10.4) @@ -623,17 +624,17 @@ GEM rack rack-test (0.6.3) rack (>= 1.0) - rails (5.0.7) - actioncable (= 5.0.7) - actionmailer (= 5.0.7) - actionpack (= 5.0.7) - actionview (= 5.0.7) - activejob (= 5.0.7) - activemodel (= 5.0.7) - activerecord (= 5.0.7) - activesupport (= 5.0.7) + rails (5.0.7.1) + actioncable (= 5.0.7.1) + actionmailer (= 5.0.7.1) + actionpack (= 5.0.7.1) + actionview (= 5.0.7.1) + activejob (= 5.0.7.1) + activemodel (= 5.0.7.1) + activerecord (= 5.0.7.1) + activesupport (= 5.0.7.1) bundler (>= 1.3.0) - railties (= 5.0.7) + railties (= 5.0.7.1) sprockets-rails (>= 2.0.0) rails-controller-testing (1.0.2) actionpack (~> 5.x, >= 5.0.1) @@ -649,15 +650,15 @@ GEM rails-i18n (5.1.1) i18n (>= 0.7, < 2) railties (>= 5.0, < 6) - railties (5.0.7) - actionpack (= 5.0.7) - activesupport (= 5.0.7) + railties (5.0.7.1) + actionpack (= 5.0.7.1) + activesupport (= 5.0.7.1) method_source rake (>= 0.8.7) thor (>= 0.18.1, < 2.0) rainbow (3.0.0) raindrops (0.18.0) - rake (12.3.1) + rake (12.3.2) rb-fsevent (0.10.2) rb-inotify (0.9.10) ffi (>= 0.5.0, < 2) @@ -962,9 +963,9 @@ DEPENDENCIES browser (~> 2.5) bullet (~> 5.5.0) bundler-audit (~> 0.5.0) - capybara (~> 2.15) - capybara-screenshot (~> 1.0.0) - carrierwave (= 1.2.3) + capybara (~> 2.16.1) + capybara-screenshot (~> 1.0.18) + carrierwave (~> 1.3) charlock_holmes (~> 0.7.5) chronic (~> 0.10.2) chronic_duration (~> 0.10.6) @@ -972,7 +973,7 @@ DEPENDENCIES concurrent-ruby (~> 1.1) connection_pool (~> 2.0) creole (~> 0.5.0) - database_cleaner (~> 1.5.0) + database_cleaner (~> 1.7.0) deckar01-task_list (= 2.0.0) device_detector devise (~> 4.4) @@ -1007,11 +1008,11 @@ DEPENDENCIES gettext (~> 3.2.2) gettext_i18n_rails (~> 1.8.0) gettext_i18n_rails_js (~> 1.3) - gitaly-proto (~> 1.3.0) + gitaly-proto (~> 1.5.0) github-markup (~> 1.7.0) gitlab-default_value_for (~> 3.1.1) gitlab-markup (~> 1.6.5) - gitlab-sidekiq-fetcher + gitlab-sidekiq-fetcher (~> 0.4.0) gitlab-styles (~> 2.4) gitlab_omniauth-ldap (~> 2.0.4) gon (~> 6.2) @@ -1040,7 +1041,7 @@ DEPENDENCIES jquery-atwho-rails (~> 1.3.2) js_regex (~> 2.2.1) json-schema (~> 2.8.0) - jwt (~> 1.5.6) + jwt (~> 2.1.0) kaminari (~> 1.0) knapsack (~> 1.17) kubeclient (~> 4.0.0) @@ -1053,12 +1054,12 @@ DEPENDENCIES method_source (~> 0.8) mimemagic (~> 0.3.2) mini_magick - minitest (~> 5.7.0) + minitest (~> 5.11.0) mysql2 (~> 0.4.10) nakayoshi_fork (~> 0.0.4) net-ldap net-ssh (~> 5.0) - nokogiri (~> 1.8.2) + nokogiri (~> 1.8.5) oauth2 (~> 1.4) octokit (~> 4.9) omniauth (~> 1.8) @@ -1069,7 +1070,7 @@ DEPENDENCIES omniauth-facebook (~> 4.0.0) omniauth-github (~> 1.3) omniauth-gitlab (~> 1.0.2) - omniauth-google-oauth2 (~> 0.5.3) + omniauth-google-oauth2 (~> 0.6.0) omniauth-kerberos (~> 0.3.0) omniauth-oauth2-generic (~> 0.2.2) omniauth-saml (~> 1.10) @@ -1079,14 +1080,14 @@ DEPENDENCIES org-ruby (~> 0.9.12) peek (~> 1.0.1) peek-gc (~> 0.0.2) - peek-mysql2 (~> 1.1.0) + peek-mysql2 (~> 1.2.0) peek-pg (~> 1.3.0) peek-rblineprof (~> 0.2.0) peek-redis (~> 1.2.0) pg (~> 0.18.2) premailer-rails (~> 1.9.7) prometheus-client-mmap (~> 0.9.4) - pry-byebug (~> 3.4.1) + pry-byebug (~> 3.5.1) pry-rails (~> 0.3.4) puma (~> 3.12) puma_worker_killer @@ -1095,7 +1096,7 @@ DEPENDENCIES rack-cors (~> 1.0.0) rack-oauth2 (~> 1.2.1) rack-proxy (~> 0.6.0) - rails (= 5.0.7) + rails (= 5.0.7.1) rails-controller-testing rails-deprecated_sanitizer (~> 1.0.3) rails-i18n (~> 5.1) @@ -1170,4 +1171,4 @@ DEPENDENCIES wikicloth (= 0.8.1) BUNDLED WITH - 1.17.1 + 1.17.3 diff --git a/Gemfile.rails4 b/Gemfile.rails4 deleted file mode 100644 index 0ec00e702aa..00000000000 --- a/Gemfile.rails4 +++ /dev/null @@ -1,7 +0,0 @@ -# BUNDLE_GEMFILE=Gemfile.rails4 bundle install - -ENV["RAILS5"] = "false" - -gemfile = File.expand_path("../Gemfile", __FILE__) - -eval(File.read(gemfile), nil, gemfile) diff --git a/Gemfile.rails4.lock b/Gemfile.rails4.lock deleted file mode 100644 index 3d81c570b89..00000000000 --- a/Gemfile.rails4.lock +++ /dev/null @@ -1,1164 +0,0 @@ -GEM - remote: https://rubygems.org/ - specs: - RedCloth (4.3.2) - abstract_type (0.0.7) - ace-rails-ap (4.1.2) - actionmailer (4.2.10) - actionpack (= 4.2.10) - actionview (= 4.2.10) - activejob (= 4.2.10) - mail (~> 2.5, >= 2.5.4) - rails-dom-testing (~> 1.0, >= 1.0.5) - actionpack (4.2.10) - actionview (= 4.2.10) - activesupport (= 4.2.10) - rack (~> 1.6) - rack-test (~> 0.6.2) - rails-dom-testing (~> 1.0, >= 1.0.5) - rails-html-sanitizer (~> 1.0, >= 1.0.2) - actionview (4.2.10) - activesupport (= 4.2.10) - builder (~> 3.1) - erubis (~> 2.7.0) - rails-dom-testing (~> 1.0, >= 1.0.5) - rails-html-sanitizer (~> 1.0, >= 1.0.3) - activejob (4.2.10) - activesupport (= 4.2.10) - globalid (>= 0.3.0) - activemodel (4.2.10) - activesupport (= 4.2.10) - builder (~> 3.1) - activerecord (4.2.10) - activemodel (= 4.2.10) - activesupport (= 4.2.10) - arel (~> 6.0) - activerecord_sane_schema_dumper (0.2) - rails (>= 4, < 5) - activesupport (4.2.10) - i18n (~> 0.7) - minitest (~> 5.1) - thread_safe (~> 0.3, >= 0.3.4) - tzinfo (~> 1.1) - acts-as-taggable-on (5.0.0) - activerecord (>= 4.2.8) - adamantium (0.2.0) - ice_nine (~> 0.11.0) - memoizable (~> 0.4.0) - addressable (2.5.2) - public_suffix (>= 2.0.2, < 4.0) - aes_key_wrap (1.0.1) - akismet (2.0.0) - arel (6.0.4) - asana (0.8.1) - faraday (~> 0.9) - faraday_middleware (~> 0.9) - faraday_middleware-multi_json (~> 0.0) - oauth2 (~> 1.0) - asciidoctor (1.5.8) - asciidoctor-plantuml (0.0.8) - asciidoctor (~> 1.5) - ast (2.4.0) - atomic (1.1.99) - attr_encrypted (3.1.0) - encryptor (~> 3.0.0) - attr_required (1.0.0) - awesome_print (1.8.0) - axiom-types (0.1.1) - descendants_tracker (~> 0.0.4) - ice_nine (~> 0.11.0) - thread_safe (~> 0.3, >= 0.3.1) - babosa (1.0.2) - base32 (0.3.2) - batch-loader (1.2.2) - bcrypt (3.1.12) - bcrypt_pbkdf (1.0.0) - benchmark-ips (2.3.0) - better_errors (2.5.0) - coderay (>= 1.0.0) - erubi (>= 1.0.0) - rack (>= 0.9.0) - bindata (2.4.3) - binding_ninja (0.2.2) - binding_of_caller (0.8.0) - debug_inspector (>= 0.0.1) - bootsnap (1.3.2) - msgpack (~> 1.0) - bootstrap_form (2.7.0) - brakeman (4.2.1) - browser (2.5.3) - builder (3.2.3) - bullet (5.5.1) - activesupport (>= 3.0.0) - uniform_notifier (~> 1.10.0) - bundler-audit (0.5.0) - bundler (~> 1.2) - thor (~> 0.18) - byebug (9.0.6) - capybara (2.15.1) - addressable - mini_mime (>= 0.1.3) - nokogiri (>= 1.3.3) - rack (>= 1.0.0) - rack-test (>= 0.5.4) - xpath (~> 2.0) - capybara-screenshot (1.0.14) - capybara (>= 1.0, < 3) - launchy - carrierwave (1.2.3) - activemodel (>= 4.0.0) - activesupport (>= 4.0.0) - mime-types (>= 1.16) - cause (0.1) - charlock_holmes (0.7.6) - childprocess (0.9.0) - ffi (~> 1.0, >= 1.0.11) - chronic (0.10.2) - chronic_duration (0.10.6) - numerizer (~> 0.1.1) - chunky_png (1.3.5) - citrus (3.0.2) - coderay (1.1.2) - coercible (1.0.0) - descendants_tracker (~> 0.0.1) - commonmarker (0.17.13) - ruby-enum (~> 0.5) - concord (0.1.5) - adamantium (~> 0.2.0) - equalizer (~> 0.0.9) - concurrent-ruby (1.1.3) - concurrent-ruby-ext (1.1.3) - concurrent-ruby (= 1.1.3) - connection_pool (2.2.2) - crack (0.4.3) - safe_yaml (~> 1.0.0) - crass (1.0.4) - creole (0.5.0) - css_parser (1.5.0) - addressable - daemons (1.2.6) - database_cleaner (1.5.3) - debug_inspector (0.0.3) - debugger-ruby_core_source (1.3.8) - deckar01-task_list (2.0.0) - html-pipeline - declarative (0.0.10) - declarative-option (0.1.0) - default_value_for (3.0.2) - activerecord (>= 3.2.0, < 5.1) - descendants_tracker (0.0.4) - thread_safe (~> 0.3, >= 0.3.1) - device_detector (1.0.0) - devise (4.4.3) - bcrypt (~> 3.0) - orm_adapter (~> 0.1) - railties (>= 4.1.0, < 6.0) - responders - warden (~> 1.2.3) - devise-two-factor (3.0.0) - activesupport - attr_encrypted (>= 1.3, < 4, != 2) - devise (~> 4.0) - railties - rotp (~> 2.0) - diff-lcs (1.3) - diffy (3.1.0) - discordrb-webhooks-blackst0ne (3.3.0) - rest-client (~> 2.0) - docile (1.1.5) - domain_name (0.5.20180417) - unf (>= 0.0.5, < 1.0.0) - doorkeeper (4.3.2) - railties (>= 4.2) - doorkeeper-openid_connect (1.5.0) - doorkeeper (~> 4.3) - json-jwt (~> 1.6) - ed25519 (1.2.4) - email_reply_trimmer (0.1.6) - email_spec (2.2.0) - htmlentities (~> 4.3.3) - launchy (~> 2.1) - mail (~> 2.7) - encryptor (3.0.0) - equalizer (0.0.11) - erubi (1.7.1) - erubis (2.7.0) - escape_utils (1.2.1) - et-orbi (1.0.3) - tzinfo - eventmachine (1.2.7) - excon (0.62.0) - execjs (2.6.0) - expression_parser (0.9.0) - factory_bot (4.8.2) - activesupport (>= 3.0.0) - factory_bot_rails (4.8.2) - factory_bot (~> 4.8.2) - railties (>= 3.0.0) - faraday (0.12.2) - multipart-post (>= 1.2, < 3) - faraday_middleware (0.12.2) - faraday (>= 0.7.4, < 1.0) - faraday_middleware-multi_json (0.0.6) - faraday_middleware - multi_json - fast_blank (1.0.0) - fast_gettext (1.6.0) - ffaker (2.10.0) - ffi (1.9.25) - flipper (0.13.0) - flipper-active_record (0.13.0) - activerecord (>= 3.2, < 6) - flipper (~> 0.13.0) - flipper-active_support_cache_store (0.13.0) - activesupport (>= 3.2, < 6) - flipper (~> 0.13.0) - flowdock (0.7.1) - httparty (~> 0.7) - multi_json - fog-aliyun (0.2.0) - fog-core (~> 1.27) - fog-json (~> 1.0) - ipaddress (~> 0.8) - xml-simple (~> 1.1) - fog-aws (2.0.1) - fog-core (~> 1.38) - fog-json (~> 1.0) - fog-xml (~> 0.1) - ipaddress (~> 0.8) - fog-core (1.45.0) - builder - excon (~> 0.58) - formatador (~> 0.2) - fog-google (1.7.1) - fog-core - fog-json - fog-xml - google-api-client (~> 0.23.0) - fog-json (1.0.2) - fog-core (~> 1.0) - multi_json (~> 1.10) - fog-local (0.3.1) - fog-core (~> 1.27) - fog-openstack (0.1.21) - fog-core (>= 1.40) - fog-json (>= 1.0) - ipaddress (>= 0.8) - fog-rackspace (0.1.1) - fog-core (>= 1.35) - fog-json (>= 1.0) - fog-xml (>= 0.1) - ipaddress (>= 0.8) - fog-xml (0.1.3) - fog-core - nokogiri (>= 1.5.11, < 2.0.0) - font-awesome-rails (4.7.0.1) - railties (>= 3.2, < 5.1) - foreman (0.84.0) - thor (~> 0.19.1) - formatador (0.2.5) - fuubar (2.2.0) - rspec-core (~> 3.0) - ruby-progressbar (~> 1.4) - gemojione (3.3.0) - json - get_process_mem (0.2.0) - gettext (3.2.9) - locale (>= 2.0.5) - text (>= 1.3.0) - gettext_i18n_rails (1.8.0) - fast_gettext (>= 0.9.0) - gettext_i18n_rails_js (1.3.0) - gettext (>= 3.0.2) - gettext_i18n_rails (>= 0.7.1) - po_to_json (>= 1.0.0) - rails (>= 3.2.0) - gitaly-proto (1.3.0) - grpc (~> 1.0) - github-markup (1.7.0) - gitlab-markup (1.6.5) - gitlab-sidekiq-fetcher (0.3.0) - sidekiq (~> 5) - gitlab-styles (2.4.1) - rubocop (~> 0.54.0) - rubocop-gitlab-security (~> 0.1.0) - rubocop-rspec (~> 1.19) - gitlab_omniauth-ldap (2.0.4) - net-ldap (~> 0.16) - omniauth (~> 1.3) - pyu-ruby-sasl (>= 0.0.3.3, < 0.1) - rubyntlm (~> 0.5) - globalid (0.4.1) - activesupport (>= 4.2.0) - gon (6.2.0) - actionpack (>= 3.0) - multi_json - request_store (>= 1.0) - google-api-client (0.23.4) - addressable (~> 2.5, >= 2.5.1) - googleauth (>= 0.5, < 0.7.0) - httpclient (>= 2.8.1, < 3.0) - mime-types (~> 3.0) - representable (~> 3.0) - retriable (>= 2.0, < 4.0) - google-protobuf (3.6.1) - googleapis-common-protos-types (1.0.2) - google-protobuf (~> 3.0) - googleauth (0.6.6) - faraday (~> 0.12) - jwt (>= 1.4, < 3.0) - memoist (~> 0.12) - multi_json (~> 1.11) - os (>= 0.9, < 2.0) - signet (~> 0.7) - gpgme (2.0.18) - mini_portile2 (~> 2.3) - grape (1.1.0) - activesupport - builder - mustermann-grape (~> 1.0.0) - rack (>= 1.3.0) - rack-accept - virtus (>= 1.0.0) - grape-entity (0.7.1) - activesupport (>= 4.0) - multi_json (>= 1.3.2) - grape-path-helpers (1.0.6) - activesupport (>= 4, < 5.1) - grape (~> 1.0) - rake (~> 12) - grape_logging (1.7.0) - grape - graphiql-rails (1.4.10) - railties - sprockets-rails - graphql (1.8.1) - grpc (1.15.0) - google-protobuf (~> 3.1) - googleapis-common-protos-types (~> 1.0.0) - haml (5.0.4) - temple (>= 0.8.0) - tilt - haml_lint (0.28.0) - haml (>= 4.0, < 5.1) - rainbow - rake (>= 10, < 13) - rubocop (>= 0.50.0) - sysexits (~> 1.1) - hamlit (2.8.8) - temple (>= 0.8.0) - thor - tilt - hangouts-chat (0.0.5) - hashdiff (0.3.4) - hashie (3.5.7) - hashie-forbidden_attributes (0.1.1) - hashie (>= 3.0) - health_check (2.6.0) - rails (>= 4.0) - hipchat (1.5.2) - httparty - mimemagic - html-pipeline (2.8.4) - activesupport (>= 2) - nokogiri (>= 1.4) - html2text (0.2.0) - nokogiri (~> 1.6) - htmlentities (4.3.4) - http (3.3.0) - addressable (~> 2.3) - http-cookie (~> 1.0) - http-form_data (~> 2.0) - http_parser.rb (~> 0.6.0) - http-cookie (1.0.3) - domain_name (~> 0.5) - http-form_data (2.1.1) - http_parser.rb (0.6.0) - httparty (0.13.7) - json (~> 1.8) - multi_xml (>= 0.5.2) - httpclient (2.8.3) - i18n (0.9.5) - concurrent-ruby (~> 1.0) - icalendar (2.4.1) - ice_nine (0.11.2) - influxdb (0.2.3) - cause - json - ipaddress (0.8.3) - jira-ruby (1.4.1) - activesupport - multipart-post - oauth (~> 0.5, >= 0.5.0) - jquery-atwho-rails (1.3.2) - js_regex (2.2.1) - regexp_parser (>= 0.4.11, <= 0.5.0) - json (1.8.6) - json-jwt (1.9.4) - activesupport - aes_key_wrap - bindata - json-schema (2.8.0) - addressable (>= 2.4) - jwt (1.5.6) - kaminari (1.0.1) - activesupport (>= 4.1.0) - kaminari-actionview (= 1.0.1) - kaminari-activerecord (= 1.0.1) - kaminari-core (= 1.0.1) - kaminari-actionview (1.0.1) - actionview - kaminari-core (= 1.0.1) - kaminari-activerecord (1.0.1) - activerecord - kaminari-core (= 1.0.1) - kaminari-core (1.0.1) - kgio (2.10.0) - knapsack (1.17.0) - rake - kubeclient (4.0.0) - http (~> 3.0) - recursive-open-struct (~> 1.0, >= 1.0.4) - rest-client (~> 2.0) - launchy (2.4.3) - addressable (~> 2.3) - letter_opener (1.4.1) - launchy (~> 2.2) - letter_opener_web (1.3.0) - actionmailer (>= 3.2) - letter_opener (~> 1.0) - railties (>= 3.2) - license_finder (5.4.0) - bundler - rubyzip - thor - toml (= 0.2.0) - with_env (= 1.1.0) - xml-simple - licensee (8.9.2) - rugged (~> 0.24) - locale (2.1.2) - lograge (0.10.0) - actionpack (>= 4) - activesupport (>= 4) - railties (>= 4) - request_store (~> 1.0) - loofah (2.2.3) - crass (~> 1.0.2) - nokogiri (>= 1.5.9) - mail (2.7.0) - mini_mime (>= 0.1.1) - mail_room (0.9.1) - memoist (0.16.0) - memoizable (0.4.2) - thread_safe (~> 0.3, >= 0.3.1) - method_source (0.9.0) - mime-types (3.2.2) - mime-types-data (~> 3.2015) - mime-types-data (3.2018.0812) - mimemagic (0.3.2) - mini_magick (4.8.0) - mini_mime (1.0.1) - mini_portile2 (2.3.0) - minitest (5.7.0) - msgpack (1.2.4) - multi_json (1.13.1) - multi_xml (0.6.0) - multipart-post (2.0.0) - mustermann (1.0.3) - mustermann-grape (1.0.0) - mustermann (~> 1.0.0) - mysql2 (0.4.10) - nakayoshi_fork (0.0.4) - net-ldap (0.16.0) - net-ssh (5.0.1) - netrc (0.11.0) - nokogiri (1.8.5) - mini_portile2 (~> 2.3.0) - nokogumbo (1.5.0) - nokogiri - numerizer (0.1.1) - oauth (0.5.4) - oauth2 (1.4.0) - faraday (>= 0.8, < 0.13) - jwt (~> 1.0) - multi_json (~> 1.3) - multi_xml (~> 0.5) - rack (>= 1.2, < 3) - octokit (4.9.0) - sawyer (~> 0.8.0, >= 0.5.3) - omniauth (1.8.1) - hashie (>= 3.4.6, < 3.6.0) - rack (>= 1.6.2, < 3) - omniauth-auth0 (2.0.0) - omniauth-oauth2 (~> 1.4) - omniauth-authentiq (0.3.3) - jwt (>= 1.5) - omniauth-oauth2 (>= 1.5) - omniauth-azure-oauth2 (0.0.9) - jwt (~> 1.0) - omniauth (~> 1.0) - omniauth-oauth2 (~> 1.4) - omniauth-cas3 (1.1.4) - addressable (~> 2.3) - nokogiri (~> 1.7, >= 1.7.1) - omniauth (~> 1.2) - omniauth-facebook (4.0.0) - omniauth-oauth2 (~> 1.2) - omniauth-github (1.3.0) - omniauth (~> 1.5) - omniauth-oauth2 (>= 1.4.0, < 2.0) - omniauth-gitlab (1.0.3) - omniauth (~> 1.0) - omniauth-oauth2 (~> 1.0) - omniauth-google-oauth2 (0.5.3) - jwt (>= 1.5) - omniauth (>= 1.1.1) - omniauth-oauth2 (>= 1.5) - omniauth-kerberos (0.3.0) - omniauth-multipassword - timfel-krb5-auth (~> 0.8) - omniauth-multipassword (0.4.2) - omniauth (~> 1.0) - omniauth-oauth (1.1.0) - oauth - omniauth (~> 1.0) - omniauth-oauth2 (1.5.0) - oauth2 (~> 1.1) - omniauth (~> 1.2) - omniauth-oauth2-generic (0.2.2) - omniauth-oauth2 (~> 1.0) - omniauth-saml (1.10.0) - omniauth (~> 1.3, >= 1.3.2) - ruby-saml (~> 1.7) - omniauth-shibboleth (1.3.0) - omniauth (>= 1.0.0) - omniauth-twitter (1.4.0) - omniauth-oauth (~> 1.1) - rack - omniauth_crowd (2.2.3) - activesupport - nokogiri (>= 1.4.4) - omniauth (~> 1.0) - org-ruby (0.9.12) - rubypants (~> 0.2) - orm_adapter (0.5.0) - os (1.0.0) - parallel (1.12.1) - parser (2.5.3.0) - ast (~> 2.4.0) - parslet (1.8.2) - peek (1.0.1) - concurrent-ruby (>= 0.9.0) - concurrent-ruby-ext (>= 0.9.0) - railties (>= 4.0.0) - peek-gc (0.0.2) - peek - peek-mysql2 (1.1.0) - atomic (>= 1.0.0) - mysql2 - peek - peek-pg (1.3.0) - concurrent-ruby - concurrent-ruby-ext - peek - pg - peek-rblineprof (0.2.0) - peek - rblineprof - peek-redis (1.2.0) - atomic (>= 1.0.0) - peek - redis - pg (0.18.4) - po_to_json (1.0.1) - json (>= 1.6.0) - powerpack (0.1.1) - premailer (1.10.4) - addressable - css_parser (>= 1.4.10) - htmlentities (>= 4.0.0) - premailer-rails (1.9.7) - actionmailer (>= 3, < 6) - premailer (~> 1.7, >= 1.7.9) - proc_to_ast (0.1.0) - coderay - parser - unparser - procto (0.0.3) - prometheus-client-mmap (0.9.4) - pry (0.11.3) - coderay (~> 1.1.0) - method_source (~> 0.9.0) - pry-byebug (3.4.3) - byebug (>= 9.0, < 9.1) - pry (~> 0.10) - pry-rails (0.3.6) - pry (>= 0.10.4) - public_suffix (3.0.3) - puma (3.12.0) - puma_worker_killer (0.1.0) - get_process_mem (~> 0.2) - puma (>= 2.7, < 4) - pyu-ruby-sasl (0.0.3.3) - rack (1.6.11) - rack-accept (0.4.5) - rack (>= 0.4) - rack-attack (4.4.1) - rack - rack-cors (1.0.2) - rack-oauth2 (1.2.3) - activesupport (>= 2.3) - attr_required (>= 0.0.5) - httpclient (>= 2.4) - multi_json (>= 1.3.6) - rack (>= 1.1) - rack-protection (2.0.4) - rack - rack-proxy (0.6.0) - rack - rack-test (0.6.3) - rack (>= 1.0) - rails (4.2.11) - actionmailer (= 4.2.11) - actionpack (= 4.2.11) - actionview (= 4.2.11) - activejob (= 4.2.11) - activemodel (= 4.2.11) - activerecord (= 4.2.11) - activesupport (= 4.2.11) - bundler (>= 1.3.0, < 2.0) - railties (= 4.2.11) - sprockets-rails - rails-deprecated_sanitizer (1.0.3) - activesupport (>= 4.2.0.alpha) - rails-dom-testing (1.0.9) - activesupport (>= 4.2.0, < 5.0) - nokogiri (~> 1.6) - rails-deprecated_sanitizer (>= 1.0.1) - rails-html-sanitizer (1.0.4) - loofah (~> 2.2, >= 2.2.2) - rails-i18n (4.0.9) - i18n (~> 0.7) - railties (~> 4.0) - railties (4.2.10) - actionpack (= 4.2.10) - activesupport (= 4.2.10) - rake (>= 0.8.7) - thor (>= 0.18.1, < 2.0) - rainbow (3.0.0) - raindrops (0.18.0) - rake (12.3.1) - rb-fsevent (0.10.2) - rb-inotify (0.9.10) - ffi (>= 0.5.0, < 2) - rblineprof (0.3.6) - debugger-ruby_core_source (~> 1.3) - rbtrace (0.4.10) - ffi (>= 1.0.6) - msgpack (>= 0.4.3) - trollop (>= 1.16.2) - rdoc (6.0.4) - re2 (1.1.1) - recaptcha (3.0.0) - json - recursive-open-struct (1.1.0) - redcarpet (3.4.0) - redis (3.3.5) - redis-actionpack (5.0.2) - actionpack (>= 4.0, < 6) - redis-rack (>= 1, < 3) - redis-store (>= 1.1.0, < 2) - redis-activesupport (5.0.4) - activesupport (>= 3, < 6) - redis-store (>= 1.3, < 2) - redis-namespace (1.6.0) - redis (>= 3.0.4) - redis-rack (2.0.4) - rack (>= 1.5, < 3) - redis-store (>= 1.2, < 2) - redis-rails (5.0.2) - redis-actionpack (>= 5.0, < 6) - redis-activesupport (>= 5.0, < 6) - redis-store (>= 1.2, < 2) - redis-store (1.6.0) - redis (>= 2.2, < 5) - regexp_parser (0.5.0) - representable (3.0.4) - declarative (< 0.1.0) - declarative-option (< 0.2.0) - uber (< 0.2.0) - request_store (1.3.1) - responders (2.4.0) - actionpack (>= 4.2.0, < 5.3) - railties (>= 4.2.0, < 5.3) - rest-client (2.0.2) - http-cookie (>= 1.0.2, < 2.0) - mime-types (>= 1.16, < 4.0) - netrc (~> 0.8) - retriable (3.1.2) - rinku (2.0.0) - rotp (2.1.2) - rouge (3.3.0) - rqrcode (0.7.0) - chunky_png - rqrcode-rails3 (0.1.7) - rqrcode (>= 0.4.2) - rspec (3.7.0) - rspec-core (~> 3.7.0) - rspec-expectations (~> 3.7.0) - rspec-mocks (~> 3.7.0) - rspec-core (3.7.1) - rspec-support (~> 3.7.0) - rspec-expectations (3.7.0) - diff-lcs (>= 1.2.0, < 2.0) - rspec-support (~> 3.7.0) - rspec-mocks (3.7.0) - diff-lcs (>= 1.2.0, < 2.0) - rspec-support (~> 3.7.0) - rspec-parameterized (0.4.1) - binding_ninja (>= 0.2.1) - parser - proc_to_ast - rspec (>= 2.13, < 4) - unparser - rspec-rails (3.7.2) - actionpack (>= 3.0) - activesupport (>= 3.0) - railties (>= 3.0) - rspec-core (~> 3.7.0) - rspec-expectations (~> 3.7.0) - rspec-mocks (~> 3.7.0) - rspec-support (~> 3.7.0) - rspec-retry (0.4.5) - rspec-core - rspec-set (0.1.3) - rspec-support (3.7.1) - rspec_junit_formatter (0.2.3) - builder (< 4) - rspec-core (>= 2, < 4, != 2.12.0) - rspec_profiling (0.0.5) - activerecord - pg - rails - sqlite3 - rubocop (0.54.0) - parallel (~> 1.10) - parser (>= 2.5) - powerpack (~> 0.1) - rainbow (>= 2.2.2, < 4.0) - ruby-progressbar (~> 1.7) - unicode-display_width (~> 1.0, >= 1.0.1) - rubocop-gitlab-security (0.1.1) - rubocop (>= 0.51) - rubocop-rspec (1.22.2) - rubocop (>= 0.52.1) - ruby-enum (0.7.2) - i18n - ruby-fogbugz (0.2.1) - crack (~> 0.4) - ruby-prof (0.17.0) - ruby-progressbar (1.9.0) - ruby-saml (1.7.2) - nokogiri (>= 1.5.10) - ruby_parser (3.11.0) - sexp_processor (~> 4.9) - rubyntlm (0.6.2) - rubypants (0.2.0) - rubyzip (1.2.2) - rufus-scheduler (3.4.0) - et-orbi (~> 1.0) - rugged (0.27.5) - safe_yaml (1.0.4) - sanitize (4.6.6) - crass (~> 1.0.2) - nokogiri (>= 1.4.4) - nokogumbo (~> 1.4) - sass (3.5.5) - sass-listen (~> 4.0.0) - sass-listen (4.0.0) - rb-fsevent (~> 0.9, >= 0.9.4) - rb-inotify (~> 0.9, >= 0.9.7) - sass-rails (5.0.6) - railties (>= 4.0.0, < 6) - sass (~> 3.1) - sprockets (>= 2.8, < 4.0) - sprockets-rails (>= 2.0, < 4.0) - tilt (>= 1.1, < 3) - sawyer (0.8.1) - addressable (>= 2.3.5, < 2.6) - faraday (~> 0.8, < 1.0) - scss_lint (0.56.0) - rake (>= 0.9, < 13) - sass (~> 3.5.3) - seed-fu (2.3.7) - activerecord (>= 3.1) - activesupport (>= 3.1) - select2-rails (3.5.9.3) - thor (~> 0.14) - selenium-webdriver (3.12.0) - childprocess (~> 0.5) - rubyzip (~> 1.2) - sentry-raven (2.7.2) - faraday (>= 0.7.6, < 1.0) - settingslogic (2.0.9) - sexp_processor (4.11.0) - sham_rack (1.3.6) - rack - shoulda-matchers (3.1.2) - activesupport (>= 4.0.0) - sidekiq (5.2.3) - connection_pool (~> 2.2, >= 2.2.2) - rack-protection (>= 1.5.0) - redis (>= 3.3.5, < 5) - sidekiq-cron (0.6.0) - rufus-scheduler (>= 3.3.0) - sidekiq (>= 4.2.1) - signet (0.11.0) - addressable (~> 2.3) - faraday (~> 0.9) - jwt (>= 1.5, < 3.0) - multi_json (~> 1.10) - simple_po_parser (1.1.2) - simplecov (0.14.1) - docile (~> 1.1.0) - json (>= 1.8, < 3) - simplecov-html (~> 0.10.0) - simplecov-html (0.10.0) - slack-notifier (1.5.1) - spring (2.0.2) - activesupport (>= 4.2) - spring-commands-rspec (1.0.4) - spring (>= 0.9.1) - sprockets (3.7.2) - concurrent-ruby (~> 1.0) - rack (> 1, < 3) - sprockets-rails (3.2.1) - actionpack (>= 4.0) - activesupport (>= 4.0) - sprockets (>= 3.0.0) - sqlite3 (1.3.13) - sshkey (1.9.0) - stackprof (0.2.10) - state_machines (0.5.0) - state_machines-activemodel (0.5.1) - activemodel (>= 4.1, < 6.0) - state_machines (>= 0.5.0) - state_machines-activerecord (0.5.1) - activerecord (>= 4.1, < 6.0) - state_machines-activemodel (>= 0.5.0) - sys-filesystem (1.1.6) - ffi - sysexits (1.2.0) - temple (0.8.0) - test-prof (0.2.5) - test_after_commit (1.1.0) - activerecord (>= 3.2) - text (1.3.1) - thin (1.7.2) - daemons (~> 1.0, >= 1.0.9) - eventmachine (~> 1.0, >= 1.0.4) - rack (>= 1, < 3) - thor (0.19.4) - thread_safe (0.3.6) - tilt (2.0.8) - timecop (0.8.1) - timfel-krb5-auth (0.8.3) - toml (0.2.0) - parslet (~> 1.8.0) - toml-rb (1.0.0) - citrus (~> 3.0, > 3.0) - trollop (2.1.3) - truncato (0.7.10) - htmlentities (~> 4.3.1) - nokogiri (~> 1.8.0, >= 1.7.0) - tzinfo (1.2.5) - thread_safe (~> 0.1) - u2f (0.2.1) - uber (0.1.0) - uglifier (2.7.2) - execjs (>= 0.3.0) - json (>= 1.8.0) - unf (0.1.4) - unf_ext - unf_ext (0.0.7.5) - unicode-display_width (1.3.2) - unicorn (5.1.0) - kgio (~> 2.6) - raindrops (~> 0.7) - unicorn-worker-killer (0.4.4) - get_process_mem (~> 0) - unicorn (>= 4, < 6) - uniform_notifier (1.10.0) - unparser (0.4.2) - abstract_type (~> 0.0.7) - adamantium (~> 0.2.0) - concord (~> 0.1.5) - diff-lcs (~> 1.3) - equalizer (~> 0.0.9) - parser (>= 2.3.1.2, < 2.6) - procto (~> 0.0.2) - validates_hostname (1.0.6) - activerecord (>= 3.0) - activesupport (>= 3.0) - version_sorter (2.1.0) - virtus (1.0.5) - axiom-types (~> 0.1) - coercible (~> 1.0) - descendants_tracker (~> 0.0, >= 0.0.3) - equalizer (~> 0.0, >= 0.0.9) - vmstat (2.3.0) - warden (1.2.7) - rack (>= 1.0) - webmock (2.3.2) - addressable (>= 2.3.6) - crack (>= 0.3.2) - hashdiff - webpack-rails (0.9.11) - railties (>= 3.2.0) - wikicloth (0.8.1) - builder - expression_parser - rinku - with_env (1.1.0) - xml-simple (1.1.5) - xpath (2.1.0) - nokogiri (~> 1.3) - -PLATFORMS - ruby - -DEPENDENCIES - RedCloth (~> 4.3.2) - ace-rails-ap (~> 4.1.0) - activerecord_sane_schema_dumper (= 0.2) - acts-as-taggable-on (~> 5.0) - addressable (~> 2.5.2) - akismet (~> 2.0) - asana (~> 0.8.1) - asciidoctor (~> 1.5.8) - asciidoctor-plantuml (= 0.0.8) - attr_encrypted (~> 3.1.0) - awesome_print - babosa (~> 1.0.2) - base32 (~> 0.3.0) - batch-loader (~> 1.2.2) - bcrypt_pbkdf (~> 1.0) - benchmark-ips (~> 2.3.0) - better_errors (~> 2.5.0) - binding_of_caller (~> 0.8.0) - bootsnap (~> 1.3) - bootstrap_form (~> 2.7.0) - brakeman (~> 4.2) - browser (~> 2.5) - bullet (~> 5.5.0) - bundler-audit (~> 0.5.0) - capybara (~> 2.15) - capybara-screenshot (~> 1.0.0) - carrierwave (= 1.2.3) - charlock_holmes (~> 0.7.5) - chronic (~> 0.10.2) - chronic_duration (~> 0.10.6) - commonmarker (~> 0.17) - concurrent-ruby (~> 1.1) - connection_pool (~> 2.0) - creole (~> 0.5.0) - database_cleaner (~> 1.5.0) - deckar01-task_list (= 2.0.0) - default_value_for (~> 3.0.0) - device_detector - devise (~> 4.4) - devise-two-factor (~> 3.0.0) - diffy (~> 3.1.0) - discordrb-webhooks-blackst0ne (~> 3.3) - doorkeeper (~> 4.3) - doorkeeper-openid_connect (~> 1.5) - ed25519 (~> 1.2) - email_reply_trimmer (~> 0.1) - email_spec (~> 2.2.0) - escape_utils (~> 1.1) - factory_bot_rails (~> 4.8.2) - faraday (~> 0.12) - fast_blank - ffaker (~> 2.10) - flipper (~> 0.13.0) - flipper-active_record (~> 0.13.0) - flipper-active_support_cache_store (~> 0.13.0) - flowdock (~> 0.7) - fog-aliyun (~> 0.2.0) - fog-aws (~> 2.0.1) - fog-core (~> 1.44) - fog-google (~> 1.7.1) - fog-local (~> 0.3) - fog-openstack (~> 0.1) - fog-rackspace (~> 0.1.1) - font-awesome-rails (~> 4.7) - foreman (~> 0.84.0) - fuubar (~> 2.2.0) - gemojione (~> 3.3) - gettext (~> 3.2.2) - gettext_i18n_rails (~> 1.8.0) - gettext_i18n_rails_js (~> 1.3) - gitaly-proto (~> 1.3.0) - github-markup (~> 1.7.0) - gitlab-markup (~> 1.6.5) - gitlab-sidekiq-fetcher - gitlab-styles (~> 2.4) - gitlab_omniauth-ldap (~> 2.0.4) - gon (~> 6.2) - google-api-client (~> 0.23) - google-protobuf (~> 3.6) - gpgme (~> 2.0.18) - grape (~> 1.1.0) - grape-entity (~> 0.7.1) - grape-path-helpers (~> 1.0) - grape_logging (~> 1.7) - graphiql-rails (~> 1.4.10) - graphql (~> 1.8.0) - grpc (~> 1.15.0) - haml_lint (~> 0.28.0) - hamlit (~> 2.8.8) - hangouts-chat (~> 0.0.5) - hashie-forbidden_attributes - health_check (~> 2.6.0) - hipchat (~> 1.5.0) - html-pipeline (~> 2.8) - html2text - httparty (~> 0.13.3) - icalendar - influxdb (~> 0.2) - jira-ruby (~> 1.4) - jquery-atwho-rails (~> 1.3.2) - js_regex (~> 2.2.1) - json-schema (~> 2.8.0) - jwt (~> 1.5.6) - kaminari (~> 1.0) - knapsack (~> 1.17) - kubeclient (~> 4.0.0) - letter_opener_web (~> 1.3.0) - license_finder (~> 5.4) - licensee (~> 8.9) - lograge (~> 0.5) - loofah (~> 2.2) - mail_room (~> 0.9.1) - method_source (~> 0.8) - mimemagic (~> 0.3.2) - mini_magick - minitest (~> 5.7.0) - mysql2 (~> 0.4.10) - nakayoshi_fork (~> 0.0.4) - net-ldap - net-ssh (~> 5.0) - nokogiri (~> 1.8.2) - oauth2 (~> 1.4) - octokit (~> 4.9) - omniauth (~> 1.8) - omniauth-auth0 (~> 2.0.0) - omniauth-authentiq (~> 0.3.3) - omniauth-azure-oauth2 (~> 0.0.9) - omniauth-cas3 (~> 1.1.4) - omniauth-facebook (~> 4.0.0) - omniauth-github (~> 1.3) - omniauth-gitlab (~> 1.0.2) - omniauth-google-oauth2 (~> 0.5.3) - omniauth-kerberos (~> 0.3.0) - omniauth-oauth2-generic (~> 0.2.2) - omniauth-saml (~> 1.10) - omniauth-shibboleth (~> 1.3.0) - omniauth-twitter (~> 1.4) - omniauth_crowd (~> 2.2.0) - org-ruby (~> 0.9.12) - peek (~> 1.0.1) - peek-gc (~> 0.0.2) - peek-mysql2 (~> 1.1.0) - peek-pg (~> 1.3.0) - peek-rblineprof (~> 0.2.0) - peek-redis (~> 1.2.0) - pg (~> 0.18.2) - premailer-rails (~> 1.9.7) - prometheus-client-mmap (~> 0.9.4) - pry-byebug (~> 3.4.1) - pry-rails (~> 0.3.4) - puma (~> 3.12) - puma_worker_killer - rack (= 1.6.11) - rack-attack (~> 4.4.1) - rack-cors (~> 1.0.0) - rack-oauth2 (~> 1.2.1) - rack-proxy (~> 0.6.0) - rails (= 4.2.11) - rails-deprecated_sanitizer (~> 1.0.3) - rails-i18n (~> 4.0.9) - rainbow (~> 3.0) - raindrops (~> 0.18) - rblineprof (~> 0.3.6) - rbtrace (~> 0.4) - rdoc (~> 6.0) - re2 (~> 1.1.1) - recaptcha (~> 3.0) - redcarpet (~> 3.4) - redis (~> 3.2) - redis-namespace (~> 1.6.0) - redis-rails (~> 5.0.2) - request_store (~> 1.3) - responders (~> 2.0) - rouge (~> 3.1) - rqrcode-rails3 (~> 0.1.7) - rspec-parameterized - rspec-rails (~> 3.7.0) - rspec-retry (~> 0.4.5) - rspec-set (~> 0.1.3) - rspec_junit_formatter - rspec_profiling (~> 0.0.5) - rubocop (~> 0.54.0) - rubocop-rspec (~> 1.22.1) - ruby-fogbugz (~> 0.2.1) - ruby-prof (~> 0.17.0) - ruby-progressbar - ruby_parser (~> 3.8) - rufus-scheduler (~> 3.4) - rugged (~> 0.27) - sanitize (~> 4.6) - sass (~> 3.5) - sass-rails (~> 5.0.6) - scss_lint (~> 0.56.0) - seed-fu (~> 2.3.7) - select2-rails (~> 3.5.9) - selenium-webdriver (~> 3.12) - sentry-raven (~> 2.7) - settingslogic (~> 2.0.9) - sham_rack (~> 1.3.6) - shoulda-matchers (~> 3.1.2) - sidekiq (~> 5.2.1) - sidekiq-cron (~> 0.6.0) - simple_po_parser (~> 1.1.2) - simplecov (~> 0.14.0) - slack-notifier (~> 1.5.1) - spring (~> 2.0.0) - spring-commands-rspec (~> 1.0.4) - sprockets (~> 3.7.0) - sshkey (~> 1.9.0) - stackprof (~> 0.2.10) - state_machines-activerecord (~> 0.5.1) - sys-filesystem (~> 1.1.6) - test-prof (~> 0.2.5) - test_after_commit (~> 1.1) - thin (~> 1.7.0) - timecop (~> 0.8.0) - toml-rb (~> 1.0.0) - truncato (~> 0.7.9) - u2f (~> 0.2.1) - uglifier (~> 2.7.2) - unf (~> 0.1.4) - unicorn (~> 5.1.0) - unicorn-worker-killer (~> 0.4.4) - validates_hostname (~> 1.0.6) - version_sorter (~> 2.1.0) - virtus (~> 1.0.1) - vmstat (~> 2.3.0) - webmock (~> 2.3.2) - webpack-rails (~> 0.9.10) - wikicloth (= 0.8.1) - -BUNDLED WITH - 1.17.1 diff --git a/PROCESS.md b/PROCESS.md index aadd6a321f0..5aafbd33daf 100644 --- a/PROCESS.md +++ b/PROCESS.md @@ -12,15 +12,18 @@ - [Assigning issues](#assigning-issues) - [Be kind](#be-kind) - [Feature freeze on the 7th for the release on the 22nd](#feature-freeze-on-the-7th-for-the-release-on-the-22nd) + - [Feature flags](#feature-flags) - [Between the 1st and the 7th](#between-the-1st-and-the-7th) + - [What happens if these deadlines are missed?](#what-happens-if-these-deadlines-are-missed) - [On the 7th](#on-the-7th) + - [Feature merge requests](#feature-merge-requests) + - [Documentation merge requests](#documentation-merge-requests) - [After the 7th](#after-the-7th) + - [Asking for an exception](#asking-for-an-exception) - [Bugs](#bugs) - [Regressions](#regressions) - [Managing bugs](#managing-bugs) - [Release retrospective and kickoff](#release-retrospective-and-kickoff) - - [Retrospective](#retrospective) - - [Kickoff](#kickoff) - [Copy & paste responses](#copy--paste-responses) - [Improperly formatted issue](#improperly-formatted-issue) - [Issue report for old version](#issue-report-for-old-version) @@ -28,11 +31,8 @@ - [Code format](#code-format) - [Issue fixed in newer version](#issue-fixed-in-newer-version) - [Improperly formatted merge request](#improperly-formatted-merge-request) - - [Inactivity close of an issue](#inactivity-close-of-an-issue) - - [Inactivity close of a merge request](#inactivity-close-of-a-merge-request) - [Accepting merge requests](#accepting-merge-requests) - [Only accepting merge requests with green tests](#only-accepting-merge-requests-with-green-tests) - - [Closing down the issue tracker on GitHub](#closing-down-the-issue-tracker-on-github) <!-- END doctoc generated TOC please keep comment here to allow auto update --> @@ -58,6 +58,18 @@ their contributions accepted by meeting our [Definition of done][done]. What you can expect from them is described at https://about.gitlab.com/roles/merge-request-coach/. +### Milestones on community contribution issues + +The milestone of an issue that is currently being worked on by a community contributor +should not be set to a named GitLab milestone (e.g. 11.7, 11.8), until the associated +merge request is very close to being merged, and we will likely know in which named +GitLab milestone the issue will land. There are many factors that influence when +a community contributor finishes an issue, or even at all. So we should set this +milestone only when we have more certainty. + +Note this only applies to issues currently assigned to community contributors. For +issues assigned to GitLabbers, we are [ambitious in assigning milestones to issues](https://about.gitlab.com/direction/#how-we-plan-releases). + ## Assigning issues If an issue is complex and needs the attention of a specific person, assignment is a good option but assigning issues might discourage other people from contributing to that issue. We need all the contributions we can get so this should never be discouraged. Also, an assigned person might not have time for a few weeks, so others should feel free to takeover. @@ -249,7 +261,7 @@ A ~bug is a defect, error, failure which causes the system to behave incorrectly The level of impact of a ~bug can vary from blocking a whole functionality or a feature usability bug. A bug should always be linked to a severity level. -Refer to our [severity levels](../CONTRIBUTING.md#severity-labels) +Refer to our [severity levels](https://docs.gitlab.com/ee/development/contributing/issue_workflow.html#severity-labels) Whether the bug is also a regression or not, the triage process should start as soon as possible. Ensure that the Engineering Manager and/or the Product Manager for the relative area is involved to prioritize the work as needed. @@ -281,10 +293,10 @@ The two scenarios below can [bypass the exception request in the release process When a bug is found: 1. Create an issue describing the problem in the most detailed way possible. 1. If possible, provide links to real examples and how to reproduce the problem. -1. Label the issue properly, using the [team label](../CONTRIBUTING.md#team-labels), - the [subject label](../CONTRIBUTING.md#subject-labels) +1. Label the issue properly, using the [team label](https://docs.gitlab.com/ee/development/contributing/issue_workflow.html#team-labels), + the [subject label](https://docs.gitlab.com/ee/development/contributing/issue_workflow.html#subject-labels) and any other label that may apply in the specific case -1. Notify the respective Engineering Manager to evaluate and apply the [Severity label](../CONTRIBUTING.md#bug-severity-labels) and [Priority label](../CONTRIBUTING.md#bug-priority-labels). +1. Notify the respective Engineering Manager to evaluate and apply the [Severity label](https://docs.gitlab.com/ee/development/contributing/issue_workflow.html#severity-labels) and [Priority label](https://docs.gitlab.com/ee/development/contributing/issue_workflow.html#priority-labels). The counterpart Product Manager is included to weigh-in on prioritization as needed. 1. If the ~bug is **NOT** a regression: 1. The Engineering Manager decides which milestone the bug will be fixed. The appropriate milestone is applied. @@ -306,67 +318,77 @@ The counterpart Product Manager is included to weigh-in on prioritization as nee ### Improperly formatted issue -Thanks for the issue report. Please reformat your issue to conform to the [contributing guidelines](https://gitlab.com/gitlab-org/gitlab-ce/blob/master/CONTRIBUTING.md#issue-tracker-guidelines). +``` +Thanks for the issue report. Please reformat your issue to conform to the +[contributing guidelines](https://docs.gitlab.com/ee/development/contributing/issue_workflow.html#issue-tracker-guidelines). +``` ### Issue report for old version -Thanks for the issue report but we only support issues for the latest stable version of GitLab. I'm closing this issue but if you still experience this problem in the latest stable version, please open a new issue (but also reference the old issue(s)). Make sure to also include the necessary debugging information conforming to the issue tracker guidelines found in our [contributing guidelines](https://gitlab.com/gitlab-org/gitlab-ce/blob/master/CONTRIBUTING.md#issue-tracker-guidelines). +``` +Thanks for the issue report but we only support issues for the latest stable version of GitLab. +I'm closing this issue but if you still experience this problem in the latest stable version, +please open a new issue (but also reference the old issue(s)). +Make sure to also include the necessary debugging information conforming to the issue tracker +guidelines found in our [contributing guidelines](https://docs.gitlab.com/ee/development/contributing/issue_workflow.html#issue-tracker-guidelines). +``` ### Support requests and configuration questions +``` Thanks for your interest in GitLab. We don't use the issue tracker for support requests and configuration questions. Please check our [getting help](https://about.gitlab.com/getting-help/) page to see all of the available -support options. Also, have a look at the [contribution guidelines](https://gitlab.com/gitlab-org/gitlab-ce/blob/master/CONTRIBUTING.md) +support options. Also, have a look at the [contribution guidelines](https://docs.gitlab.com/ee/development/contributing/index.html) for more information. +``` ### Code format +``` Please use \`\`\` to format console output, logs, and code as it's very hard to read otherwise. +``` ### Issue fixed in newer version -Thanks for the issue report. This issue has already been fixed in newer versions of GitLab. Due to the size of this project and our limited resources we are only able to support the latest stable release as outlined in our [contributing guidelines](https://gitlab.com/gitlab-org/gitlab-ce/blob/master/CONTRIBUTING.md#issue-tracker). In order to get this bug fix and enjoy many new features please [upgrade](https://gitlab.com/gitlab-org/gitlab-ce/tree/master/doc/update). If you still experience issues at that time please open a new issue following our issue tracker guidelines found in the [contributing guidelines](https://gitlab.com/gitlab-org/gitlab-ce/blob/master/CONTRIBUTING.md#issue-tracker-guidelines). +``` +Thanks for the issue report. This issue has already been fixed in newer versions of GitLab. +Due to the size of this project and our limited resources we are only able to support the +latest stable release as outlined in our [contributing guidelines](https://docs.gitlab.com/ee/development/contributing/issue_workflow.html). +In order to get this bug fix and enjoy many new features please +[upgrade](https://gitlab.com/gitlab-org/gitlab-ce/tree/master/doc/update). +If you still experience issues at that time please open a new issue following our issue +tracker guidelines found in the [contributing guidelines](https://docs.gitlab.com/ee/development/contributing/issue_workflow.html#issue-tracker-guidelines). +``` ### Improperly formatted merge request -Thanks for your interest in improving the GitLab codebase! Please update your merge request according to the [contributing guidelines](https://gitlab.com/gitlab-org/gitlab-ce/blob/master/CONTRIBUTING.md#pull-request-guidelines). - -### Inactivity close of an issue - -It's been at least 2 weeks (and a new release) since we heard from you. I'm closing this issue but if you still experience this problem, please open a new issue (but also reference the old issue(s)). Make sure to also include the necessary debugging information conforming to the issue tracker guidelines found in our [contributing guidelines](https://gitlab.com/gitlab-org/gitlab-ce/blob/master/CONTRIBUTING.md#issue-tracker-guidelines). - -### Inactivity close of a merge request - -This merge request has been closed because a request for more information has not been reacted to for more than 2 weeks. If you respond and conform to the merge request guidelines in our [contributing guidelines](https://gitlab.com/gitlab-org/gitlab-ce/blob/master/CONTRIBUTING.md#pull-requests) we will reopen this merge request. +``` +Thanks for your interest in improving the GitLab codebase! +Please update your merge request according to the [contributing guidelines](https://gitlab.com/gitlab-org/gitlab-ce/blob/master/doc/development/contributing/merge_request_workflow.md#merge-request-guidelines). +``` ### Accepting merge requests +``` Is there an issue on the [issue tracker](https://gitlab.com/gitlab-org/gitlab-ce/issues) that is similar to this? Could you please link it here? Please be aware that new functionality that is not marked -[accepting merge requests](https://gitlab.com/gitlab-org/gitlab-ce/issues?milestone_id=&scope=all&sort=created_desc&state=opened&utf8=%E2%9C%93&assignee_id=&author_id=&milestone_title=&label_name=Accepting+Merge+Requests) +[`Accepting merge requests`](https://docs.gitlab.com/ee/development/contributing/issue_workflow.html#label-for-community-contributors) might not make it into GitLab. +``` ### Only accepting merge requests with green tests +``` We can only accept a merge request if all the tests are green. I've just restarted the build. When the tests are still not passing after this restart and you're sure that is does not have anything to do with your code changes, please rebase with master to see if that solves the issue. - -### Closing down the issue tracker on GitHub - -We are currently in the process of closing down the issue tracker on GitHub, to -prevent duplication with the GitLab.com issue tracker. -Since this is an older issue I'll be closing this for now. If you think this is -still an issue I encourage you to open it on the [GitLab.com issue tracker](https://gitlab.com/gitlab-org/gitlab-ce/issues). +``` [team]: https://about.gitlab.com/team/ -[contribution acceptance criteria]: https://gitlab.com/gitlab-org/gitlab-ce/blob/master/CONTRIBUTING.md#contribution-acceptance-criteria -["Implement design & UI elements" guidelines]: https://gitlab.com/gitlab-org/gitlab-ce/blob/master/CONTRIBUTING.md#implement-design-ui-elements -[Thoughtbot code review guide]: https://github.com/thoughtbot/guides/tree/master/code-review -[done]: https://gitlab.com/gitlab-org/gitlab-ce/blob/master/CONTRIBUTING.md#definition-of-done +[done]: https://docs.gitlab.com/ee/development/contributing/merge_request_workflow.html#definition-of-done [automatic_ce_ee_merge]: https://docs.gitlab.com/ce/development/automatic_ce_ee_merge.html [ee_features]: https://docs.gitlab.com/ce/development/ee_features.html @@ -1 +1 @@ -11.6.0-pre +11.7.0-pre diff --git a/app/assets/images/none-scheme-preview.png b/app/assets/images/none-scheme-preview.png Binary files differnew file mode 100644 index 00000000000..2eb6bf96671 --- /dev/null +++ b/app/assets/images/none-scheme-preview.png diff --git a/app/assets/javascripts/api.js b/app/assets/javascripts/api.js index e2740981a4b..d1396b6c4bc 100644 --- a/app/assets/javascripts/api.js +++ b/app/assets/javascripts/api.js @@ -25,9 +25,11 @@ const Api = { userStatusPath: '/api/:version/users/:id/status', userPostStatusPath: '/api/:version/user/status', commitPath: '/api/:version/projects/:id/repository/commits', + applySuggestionPath: '/api/:version/suggestions/:id/apply', commitPipelinesPath: '/:project_id/commit/:sha/pipelines', branchSinglePath: '/api/:version/projects/:id/repository/branches/:branch', createBranchPath: '/api/:version/projects/:id/repository/branches', + releasesPath: '/api/:version/projects/:id/releases', group(groupId, callback) { const url = Api.buildUrl(Api.groupPath).replace(':id', groupId); @@ -185,6 +187,12 @@ const Api = { }); }, + applySuggestion(id) { + const url = Api.buildUrl(Api.applySuggestionPath).replace(':id', encodeURIComponent(id)); + + return axios.put(url); + }, + commitPipelines(projectId, sha) { const encodedProjectId = projectId .split('/') @@ -300,6 +308,12 @@ const Api = { }); }, + releases(id) { + const url = Api.buildUrl(this.releasesPath).replace(':id', encodeURIComponent(id)); + + return axios.get(url); + }, + buildUrl(url) { let urlRoot = ''; if (gon.relative_url_root != null) { diff --git a/app/assets/javascripts/behaviors/shortcuts/shortcuts_navigation.js b/app/assets/javascripts/behaviors/shortcuts/shortcuts_navigation.js index fa9b2c9f755..bef1553703b 100644 --- a/app/assets/javascripts/behaviors/shortcuts/shortcuts_navigation.js +++ b/app/assets/javascripts/behaviors/shortcuts/shortcuts_navigation.js @@ -8,6 +8,7 @@ export default class ShortcutsNavigation extends Shortcuts { Mousetrap.bind('g p', () => findAndFollowLink('.shortcuts-project')); Mousetrap.bind('g v', () => findAndFollowLink('.shortcuts-project-activity')); + Mousetrap.bind('g r', () => findAndFollowLink('.shortcuts-project-releases')); Mousetrap.bind('g f', () => findAndFollowLink('.shortcuts-tree')); Mousetrap.bind('g c', () => findAndFollowLink('.shortcuts-commits')); Mousetrap.bind('g j', () => findAndFollowLink('.shortcuts-builds')); diff --git a/app/assets/javascripts/blob_edit/blob_bundle.js b/app/assets/javascripts/blob_edit/blob_bundle.js index 9f547471170..b07f951346e 100644 --- a/app/assets/javascripts/blob_edit/blob_bundle.js +++ b/app/assets/javascripts/blob_edit/blob_bundle.js @@ -17,6 +17,11 @@ export default () => { const currentAction = $('.js-file-title').data('currentAction'); const projectId = editBlobForm.data('project-id'); const commitButton = $('.js-commit-button'); + const cancelLink = $('.btn.btn-cancel'); + + cancelLink.on('click', () => { + window.onbeforeunload = null; + }); commitButton.on('click', () => { window.onbeforeunload = null; diff --git a/app/assets/javascripts/boards/components/issue_due_date.vue b/app/assets/javascripts/boards/components/issue_due_date.vue index e038198e6f0..9c4c6632976 100644 --- a/app/assets/javascripts/boards/components/issue_due_date.vue +++ b/app/assets/javascripts/boards/components/issue_due_date.vue @@ -3,7 +3,12 @@ import dateFormat from 'dateformat'; import { GlTooltip } from '@gitlab/ui'; import Icon from '~/vue_shared/components/icon.vue'; import { __ } from '~/locale'; -import { getDayDifference, getTimeago, dateInWords } from '~/lib/utils/datetime_utility'; +import { + getDayDifference, + getTimeago, + dateInWords, + parsePikadayDate, +} from '~/lib/utils/datetime_utility'; export default { components: { @@ -54,7 +59,7 @@ export default { return standardDateFormat; }, issueDueDate() { - return new Date(this.date); + return parsePikadayDate(this.date); }, timeDifference() { const today = new Date(); diff --git a/app/assets/javascripts/boards/components/new_list_dropdown.js b/app/assets/javascripts/boards/components/new_list_dropdown.js index f7016561f93..10577da9305 100644 --- a/app/assets/javascripts/boards/components/new_list_dropdown.js +++ b/app/assets/javascripts/boards/components/new_list_dropdown.js @@ -37,7 +37,7 @@ export default function initNewListDropdown() { }); }, renderRow(label) { - const active = boardsStore.findList('title', label.title); + const active = boardsStore.findListByLabelId(label.id); const $li = $('<li />'); const $a = $('<a />', { class: active ? `is-active js-board-list-${active.id}` : '', @@ -63,7 +63,7 @@ export default function initNewListDropdown() { const label = options.selectedObj; e.preventDefault(); - if (!boardsStore.findList('title', label.title)) { + if (!boardsStore.findListByLabelId(label.id)) { boardsStore.new({ title: label.title, position: boardsStore.state.lists.length - 2, diff --git a/app/assets/javascripts/boards/models/issue.js b/app/assets/javascripts/boards/models/issue.js index 5e0f0b07247..dd92d3c8552 100644 --- a/app/assets/javascripts/boards/models/issue.js +++ b/app/assets/javascripts/boards/models/issue.js @@ -55,12 +55,12 @@ class ListIssue { } findLabel(findLabel) { - return this.labels.filter(label => label.title === findLabel.title)[0]; + return this.labels.find(label => label.id === findLabel.id); } removeLabel(removeLabel) { if (removeLabel) { - this.labels = this.labels.filter(label => removeLabel.title !== label.title); + this.labels = this.labels.filter(label => removeLabel.id !== label.id); } } @@ -75,7 +75,7 @@ class ListIssue { } findAssignee(findAssignee) { - return this.assignees.filter(assignee => assignee.id === findAssignee.id)[0]; + return this.assignees.find(assignee => assignee.id === findAssignee.id); } removeAssignee(removeAssignee) { diff --git a/app/assets/javascripts/boards/stores/boards_store.js b/app/assets/javascripts/boards/stores/boards_store.js index cf88a973d33..802796208c2 100644 --- a/app/assets/javascripts/boards/stores/boards_store.js +++ b/app/assets/javascripts/boards/stores/boards_store.js @@ -166,6 +166,9 @@ const boardsStore = { }); return filteredList[0]; }, + findListByLabelId(id) { + return this.state.lists.find(list => list.type === 'label' && list.label.id === id); + }, updateFiltersUrl() { window.history.pushState(null, null, `?${this.filter.path}`); }, diff --git a/app/assets/javascripts/ci_variable_list/ci_variable_list.js b/app/assets/javascripts/ci_variable_list/ci_variable_list.js index ee0f7cda189..5b20fa141cd 100644 --- a/app/assets/javascripts/ci_variable_list/ci_variable_list.js +++ b/app/assets/javascripts/ci_variable_list/ci_variable_list.js @@ -36,7 +36,9 @@ export default class VariableList { }, protected: { selector: '.js-ci-variable-input-protected', - default: 'false', + // use `attr` instead of `data` as we don't want the value to be + // converted. we need the value as a string. + default: $('.js-ci-variable-input-protected').attr('data-default'), }, environment_scope: { // We can't use a `.js-` class here because diff --git a/app/assets/javascripts/diffs/components/app.vue b/app/assets/javascripts/diffs/components/app.vue index bf9244df7f7..d4c1b07093d 100644 --- a/app/assets/javascripts/diffs/components/app.vue +++ b/app/assets/javascripts/diffs/components/app.vue @@ -42,6 +42,16 @@ export default { type: Object, required: true, }, + helpPagePath: { + type: String, + required: false, + default: '', + }, + changesEmptyStateIllustration: { + type: String, + required: false, + default: '', + }, }, data() { return { @@ -63,7 +73,7 @@ export default { plainDiffPath: state => state.diffs.plainDiffPath, emailPatchPath: state => state.diffs.emailPatchPath, }), - ...mapState('diffs', ['showTreeList', 'isLoading']), + ...mapState('diffs', ['showTreeList', 'isLoading', 'startVersion']), ...mapGetters('diffs', ['isParallelView']), ...mapGetters(['isNotesFetched', 'getNoteableData']), targetBranch() { @@ -79,6 +89,13 @@ export default { showCompareVersions() { return this.mergeRequestDiffs && this.mergeRequestDiff; }, + renderDiffFiles() { + return ( + this.diffFiles.length > 0 || + (this.startVersion && + this.startVersion.version_index === this.mergeRequestDiff.version_index) + ); + }, }, watch: { diffViewType() { @@ -191,15 +208,16 @@ export default { <div v-show="showTreeList" class="diff-tree-list"><tree-list /></div> <div class="diff-files-holder"> <commit-widget v-if="commit" :commit="commit" /> - <template v-if="diffFiles.length > 0"> + <template v-if="renderDiffFiles"> <diff-file v-for="file in diffFiles" :key="file.newPath" :file="file" + :help-page-path="helpPagePath" :can-current-user-fork="canCurrentUserFork" /> </template> - <no-changes v-else /> + <no-changes v-else :changes-empty-state-illustration="changesEmptyStateIllustration" /> </div> </div> </div> diff --git a/app/assets/javascripts/diffs/components/compare_versions_dropdown.vue b/app/assets/javascripts/diffs/components/compare_versions_dropdown.vue index 8da02ed0b7c..b9b1ee02697 100644 --- a/app/assets/javascripts/diffs/components/compare_versions_dropdown.vue +++ b/app/assets/javascripts/diffs/components/compare_versions_dropdown.vue @@ -129,7 +129,7 @@ export default { </strong> </div> <div> - <small class="commit-sha"> {{ version.truncated_commit_sha }} </small> + <small class="commit-sha"> {{ version.short_commit_sha }} </small> </div> <div> <small> diff --git a/app/assets/javascripts/diffs/components/diff_content.vue b/app/assets/javascripts/diffs/components/diff_content.vue index 11cc4c09fed..ba6dcd63880 100644 --- a/app/assets/javascripts/diffs/components/diff_content.vue +++ b/app/assets/javascripts/diffs/components/diff_content.vue @@ -1,6 +1,7 @@ <script> import { mapActions, mapGetters, mapState } from 'vuex'; import DiffViewer from '~/vue_shared/components/diff_viewer/diff_viewer.vue'; +import EmptyFileViewer from '~/vue_shared/components/diff_viewer/viewers/empty_file.vue'; import InlineDiffView from './inline_diff_view.vue'; import ParallelDiffView from './parallel_diff_view.vue'; import NoteForm from '../../notes/components/note_form.vue'; @@ -17,12 +18,18 @@ export default { NoteForm, DiffDiscussions, ImageDiffOverlay, + EmptyFileViewer, }, props: { diffFile: { type: Object, required: true, }, + helpPagePath: { + type: String, + required: false, + default: '', + }, }, computed: { ...mapState({ @@ -38,6 +45,9 @@ export default { isTextFile() { return this.diffFile.viewer.name === 'text'; }, + errorMessage() { + return this.diffFile.viewer.error; + }, diffFileCommentForm() { return this.getCommentFormForDiffFile(this.diffFile.file_hash); }, @@ -68,17 +78,20 @@ export default { <template> <div class="diff-content"> - <div class="diff-viewer"> + <div v-if="!errorMessage" class="diff-viewer"> <template v-if="isTextFile"> + <empty-file-viewer v-if="diffFile.empty" /> <inline-diff-view - v-if="isInlineView" + v-else-if="isInlineView" :diff-file="diffFile" :diff-lines="diffFile.highlighted_diff_lines || []" + :help-page-path="helpPagePath" /> <parallel-diff-view - v-if="isParallelView" + v-else-if="isParallelView" :diff-file="diffFile" :diff-lines="diffFile.parallel_diff_lines || []" + :help-page-path="helpPagePath" /> </template> <diff-viewer @@ -119,5 +132,8 @@ export default { </div> </diff-viewer> </div> + <div v-else class="diff-viewer"> + <div class="nothing-here-block" v-html="errorMessage"></div> + </div> </div> </template> diff --git a/app/assets/javascripts/diffs/components/diff_discussions.vue b/app/assets/javascripts/diffs/components/diff_discussions.vue index bee29b04e92..b2021cd6061 100644 --- a/app/assets/javascripts/diffs/components/diff_discussions.vue +++ b/app/assets/javascripts/diffs/components/diff_discussions.vue @@ -13,6 +13,11 @@ export default { type: Array, required: true, }, + line: { + type: Object, + required: false, + default: null, + }, shouldCollapseDiscussions: { type: Boolean, required: false, @@ -23,6 +28,11 @@ export default { required: false, default: false, }, + helpPagePath: { + type: String, + required: false, + default: '', + }, }, methods: { ...mapActions(['toggleDiscussion']), @@ -72,6 +82,8 @@ export default { :render-diff-file="false" :always-expanded="true" :discussions-by-diff-order="true" + :line="line" + :help-page-path="helpPagePath" @noteDeleted="deleteNoteHandler" > <span v-if="renderAvatarBadge" slot="avatar-badge" class="badge badge-pill"> diff --git a/app/assets/javascripts/diffs/components/diff_file.vue b/app/assets/javascripts/diffs/components/diff_file.vue index bed29efb253..449f7007077 100644 --- a/app/assets/javascripts/diffs/components/diff_file.vue +++ b/app/assets/javascripts/diffs/components/diff_file.vue @@ -23,6 +23,11 @@ export default { type: Boolean, required: true, }, + helpPagePath: { + type: String, + required: false, + default: '', + }, }, data() { return { @@ -164,6 +169,7 @@ export default { v-if="!isCollapsed && file.renderIt" :class="{ hidden: isCollapsed || file.too_large }" :diff-file="file" + :help-page-path="helpPagePath" /> <gl-loading-icon v-if="showLoadingIcon" class="diff-content loading" /> <div v-else-if="showExpandMessage" class="nothing-here-block diff-collapsed"> diff --git a/app/assets/javascripts/diffs/components/diff_line_note_form.vue b/app/assets/javascripts/diffs/components/diff_line_note_form.vue index 9fd02acbd6e..e7569ba7b84 100644 --- a/app/assets/javascripts/diffs/components/diff_line_note_form.vue +++ b/app/assets/javascripts/diffs/components/diff_line_note_form.vue @@ -94,6 +94,7 @@ export default { ref="noteForm" :is-editing="true" :line-code="line.line_code" + :line="line" save-button-title="Comment" class="diff-comment-form" @cancelForm="handleCancelCommentForm" diff --git a/app/assets/javascripts/diffs/components/inline_diff_comment_row.vue b/app/assets/javascripts/diffs/components/inline_diff_comment_row.vue index aa40b24950a..814ee0b7c02 100644 --- a/app/assets/javascripts/diffs/components/inline_diff_comment_row.vue +++ b/app/assets/javascripts/diffs/components/inline_diff_comment_row.vue @@ -16,6 +16,11 @@ export default { type: String, required: true, }, + helpPagePath: { + type: String, + required: false, + default: '', + }, }, computed: { className() { @@ -38,7 +43,12 @@ export default { <tr v-if="shouldRender" :class="className" class="notes_holder"> <td class="notes_content" colspan="3"> <div class="content"> - <diff-discussions v-if="line.discussions.length" :discussions="line.discussions" /> + <diff-discussions + v-if="line.discussions.length" + :line="line" + :discussions="line.discussions" + :help-page-path="helpPagePath" + /> <diff-line-note-form v-if="line.hasForm" :diff-file-hash="diffFileHash" diff --git a/app/assets/javascripts/diffs/components/inline_diff_view.vue b/app/assets/javascripts/diffs/components/inline_diff_view.vue index 6a0ce760e6d..e781397214d 100644 --- a/app/assets/javascripts/diffs/components/inline_diff_view.vue +++ b/app/assets/javascripts/diffs/components/inline_diff_view.vue @@ -17,6 +17,11 @@ export default { type: Array, required: true, }, + helpPagePath: { + type: String, + required: false, + default: '', + }, }, computed: { ...mapGetters('diffs', ['commitId']), @@ -44,9 +49,10 @@ export default { :is-bottom="index + 1 === diffLinesLength" /> <inline-diff-comment-row - :key="`icr-${index}`" + :key="`icr-${line.line_code || index}`" :diff-file-hash="diffFile.file_hash" :line="line" + :help-page-path="helpPagePath" /> </template> </tbody> diff --git a/app/assets/javascripts/diffs/components/no_changes.vue b/app/assets/javascripts/diffs/components/no_changes.vue index 25ec157ed25..47e9627a957 100644 --- a/app/assets/javascripts/diffs/components/no_changes.vue +++ b/app/assets/javascripts/diffs/components/no_changes.vue @@ -1,34 +1,51 @@ <script> -import { mapState } from 'vuex'; -import emptyImage from '~/../../views/shared/icons/_mr_widget_empty_state.svg'; +import { mapGetters } from 'vuex'; +import _ from 'underscore'; +import { GlButton } from '@gitlab/ui'; +import { __, sprintf } from '~/locale'; export default { - data() { - return { - emptyImage, - }; + components: { + GlButton, + }, + props: { + changesEmptyStateIllustration: { + type: String, + required: true, + }, }, computed: { - ...mapState({ - sourceBranch: state => state.notes.noteableData.source_branch, - targetBranch: state => state.notes.noteableData.target_branch, - newBlobPath: state => state.notes.noteableData.new_blob_path, - }), + ...mapGetters(['getNoteableData']), + emptyStateText() { + return sprintf( + __( + 'No changes between %{ref_start}%{source_branch}%{ref_end} and %{ref_start}%{target_branch}%{ref_end}', + ), + { + ref_start: '<span class="ref-name">', + ref_end: '</span>', + source_branch: _.escape(this.getNoteableData.source_branch), + target_branch: _.escape(this.getNoteableData.target_branch), + }, + false, + ); + }, }, }; </script> <template> - <div class="row empty-state nothing-here-block"> - <div class="col-xs-12"> - <div class="svg-content"><span v-html="emptyImage"></span></div> + <div class="row empty-state"> + <div class="col-12"> + <div class="svg-content svg-250"><img :src="changesEmptyStateIllustration" /></div> </div> - <div class="col-xs-12"> + <div class="col-12"> <div class="text-content text-center"> - No changes between <span class="ref-name">{{ sourceBranch }}</span> and - <span class="ref-name">{{ targetBranch }}</span> + <span v-html="emptyStateText"></span> <div class="text-center"> - <a :href="newBlobPath" class="btn btn-success"> {{ __('Create commit') }} </a> + <gl-button :href="getNoteableData.new_blob_path" variant="success">{{ + __('Create commit') + }}</gl-button> </div> </div> </div> diff --git a/app/assets/javascripts/diffs/components/parallel_diff_comment_row.vue b/app/assets/javascripts/diffs/components/parallel_diff_comment_row.vue index b98463d3dd3..a65cf025cde 100644 --- a/app/assets/javascripts/diffs/components/parallel_diff_comment_row.vue +++ b/app/assets/javascripts/diffs/components/parallel_diff_comment_row.vue @@ -20,6 +20,11 @@ export default { type: Number, required: true, }, + helpPagePath: { + type: String, + required: false, + default: '', + }, }, computed: { hasExpandedDiscussionOnLeft() { @@ -87,6 +92,8 @@ export default { <diff-discussions v-if="line.left.discussions.length" :discussions="line.left.discussions" + :line="line.left" + :help-page-path="helpPagePath" /> </div> <diff-line-note-form @@ -102,6 +109,8 @@ export default { <diff-discussions v-if="line.right.discussions.length" :discussions="line.right.discussions" + :line="line.right" + :help-page-path="helpPagePath" /> </div> <diff-line-note-form diff --git a/app/assets/javascripts/diffs/components/parallel_diff_view.vue b/app/assets/javascripts/diffs/components/parallel_diff_view.vue index 9a6e0e82529..1bf693380db 100644 --- a/app/assets/javascripts/diffs/components/parallel_diff_view.vue +++ b/app/assets/javascripts/diffs/components/parallel_diff_view.vue @@ -17,6 +17,11 @@ export default { type: Array, required: true, }, + helpPagePath: { + type: String, + required: false, + default: '', + }, }, computed: { ...mapGetters('diffs', ['commitId']), @@ -38,17 +43,18 @@ export default { <tbody> <template v-for="(line, index) in diffLines"> <parallel-diff-table-row - :key="index" + :key="line.line_code" :file-hash="diffFile.file_hash" :context-lines-path="diffFile.context_lines_path" :line="line" :is-bottom="index + 1 === diffLinesLength" /> <parallel-diff-comment-row - :key="`dcr-${index}`" + :key="`dcr-${line.line_code || index}`" :line="line" :diff-file-hash="diffFile.file_hash" :line-index="index" + :help-page-path="helpPagePath" /> </template> </tbody> diff --git a/app/assets/javascripts/diffs/index.js b/app/assets/javascripts/diffs/index.js index 06ef4207d85..b130cedc24c 100644 --- a/app/assets/javascripts/diffs/index.js +++ b/app/assets/javascripts/diffs/index.js @@ -16,7 +16,9 @@ export default function initDiffsApp(store) { return { endpoint: dataset.endpoint, projectPath: dataset.projectPath, + helpPagePath: dataset.helpPagePath, currentUser: JSON.parse(dataset.currentUserData) || {}, + changesEmptyStateIllustration: dataset.changesEmptyStateIllustration, }; }, computed: { @@ -30,7 +32,9 @@ export default function initDiffsApp(store) { endpoint: this.endpoint, currentUser: this.currentUser, projectPath: this.projectPath, + helpPagePath: this.helpPagePath, shouldShow: this.activeTab === 'diffs', + changesEmptyStateIllustration: this.changesEmptyStateIllustration, }, }); }, diff --git a/app/assets/javascripts/diffs/store/mutations.js b/app/assets/javascripts/diffs/store/mutations.js index 61314db1dbd..ed4203cf5e0 100644 --- a/app/assets/javascripts/diffs/store/mutations.js +++ b/app/assets/javascripts/diffs/store/mutations.js @@ -123,22 +123,23 @@ export default { diffPosition: diffPositionByLineCode[line.line_code], latestDiff, }); + const mapDiscussions = (line, extraCheck = () => true) => ({ + ...line, + discussions: extraCheck() + ? line.discussions + .filter(() => !line.discussions.some(({ id }) => discussion.id === id)) + .concat(lineCheck(line) ? discussion : line.discussions) + : [], + }); state.diffFiles = state.diffFiles.map(diffFile => { if (diffFile.file_hash === fileHash) { const file = { ...diffFile }; if (file.highlighted_diff_lines) { - file.highlighted_diff_lines = file.highlighted_diff_lines.map(line => { - if (!line.discussions.some(({ id }) => discussion.id === id) && lineCheck(line)) { - return { - ...line, - discussions: line.discussions.concat(discussion), - }; - } - - return line; - }); + file.highlighted_diff_lines = file.highlighted_diff_lines.map(line => + lineCheck(line) ? mapDiscussions(line) : line, + ); } if (file.parallel_diff_lines) { @@ -148,20 +149,8 @@ export default { if (left || right) { return { - left: { - ...line.left, - discussions: - left && !line.left.discussions.some(({ id }) => id === discussion.id) - ? line.left.discussions.concat(discussion) - : (line.left && line.left.discussions) || [], - }, - right: { - ...line.right, - discussions: - right && !left && !line.right.discussions.some(({ id }) => id === discussion.id) - ? line.right.discussions.concat(discussion) - : (line.right && line.right.discussions) || [], - }, + left: line.left ? mapDiscussions(line.left) : null, + right: line.right ? mapDiscussions(line.right, () => !left) : null, }; } @@ -180,7 +169,7 @@ export default { }); }, - [types.REMOVE_LINE_DISCUSSIONS_FOR_FILE](state, { fileHash, lineCode, id }) { + [types.REMOVE_LINE_DISCUSSIONS_FOR_FILE](state, { fileHash, lineCode }) { const selectedFile = state.diffFiles.find(f => f.file_hash === fileHash); if (selectedFile) { if (selectedFile.parallel_diff_lines) { @@ -193,7 +182,7 @@ export default { const side = targetLine.left && targetLine.left.line_code === lineCode ? 'left' : 'right'; Object.assign(targetLine[side], { - discussions: [], + discussions: targetLine[side].discussions.filter(discussion => discussion.notes.length), }); } } @@ -205,14 +194,14 @@ export default { if (targetInlineLine) { Object.assign(targetInlineLine, { - discussions: [], + discussions: targetInlineLine.discussions.filter(discussion => discussion.notes.length), }); } } if (selectedFile.discussions && selectedFile.discussions.length) { selectedFile.discussions = selectedFile.discussions.filter( - discussion => discussion.id !== id, + discussion => discussion.notes.length, ); } } diff --git a/app/assets/javascripts/diffs/store/utils.js b/app/assets/javascripts/diffs/store/utils.js index cbaa0e26395..2fe20551642 100644 --- a/app/assets/javascripts/diffs/store/utils.js +++ b/app/assets/javascripts/diffs/store/utils.js @@ -196,6 +196,15 @@ export function trimFirstCharOfLineContent(line = {}) { return parsedLine; } +function getLineCode({ left, right }, index) { + if (left && left.line_code) { + return left.line_code; + } else if (right && right.line_code) { + return right.line_code; + } + return index; +} + // This prepares and optimizes the incoming diff data from the server // by setting up incremental rendering and removing unneeded data export function prepareDiffData(diffData) { @@ -208,6 +217,8 @@ export function prepareDiffData(diffData) { const linesLength = file.parallel_diff_lines.length; for (let u = 0; u < linesLength; u += 1) { const line = file.parallel_diff_lines[u]; + + line.line_code = getLineCode(line, u); if (line.left) { line.left = trimFirstCharOfLineContent(line.left); line.left.hasForm = false; diff --git a/app/assets/javascripts/environments/components/environment_item.vue b/app/assets/javascripts/environments/components/environment_item.vue index cd2f46fd07a..f44806d82a6 100644 --- a/app/assets/javascripts/environments/components/environment_item.vue +++ b/app/assets/javascripts/environments/components/environment_item.vue @@ -14,6 +14,7 @@ import MonitoringButtonComponent from './environment_monitoring.vue'; import CommitComponent from '../../vue_shared/components/commit.vue'; import eventHub from '../event_hub'; import { convertObjectPropsToCamelCase } from '~/lib/utils/common_utils'; +import { CLUSTER_TYPE } from '~/clusters/constants'; /** * Environment Item Component @@ -85,6 +86,15 @@ export default { }, /** + * Hide group cluster features which are not currently implemented. + * + * @returns {Boolean} + */ + disableGroupClusterFeatures() { + return this.model && this.model.cluster_type === CLUSTER_TYPE.GROUP; + }, + + /** * Returns whether the environment can be stopped. * * @returns {Boolean} @@ -547,6 +557,7 @@ export default { <terminal-button-component v-if="model && model.terminal_path" :terminal-path="model.terminal_path" + :disabled="disableGroupClusterFeatures" /> <rollback-component diff --git a/app/assets/javascripts/environments/components/environment_terminal_button.vue b/app/assets/javascripts/environments/components/environment_terminal_button.vue index 83727caad16..6d74d136a94 100644 --- a/app/assets/javascripts/environments/components/environment_terminal_button.vue +++ b/app/assets/javascripts/environments/components/environment_terminal_button.vue @@ -19,6 +19,11 @@ export default { required: false, default: '', }, + disabled: { + type: Boolean, + required: false, + default: false, + }, }, computed: { title() { @@ -33,6 +38,7 @@ export default { :title="title" :aria-label="title" :href="terminalPath" + :class="{ disabled: disabled }" class="btn terminal-button d-none d-sm-none d-md-block" > <icon name="terminal" /> diff --git a/app/assets/javascripts/filtered_search/dropdown_utils.js b/app/assets/javascripts/filtered_search/dropdown_utils.js index 1b79a3320c6..8d92af2cf7e 100644 --- a/app/assets/javascripts/filtered_search/dropdown_utils.js +++ b/app/assets/javascripts/filtered_search/dropdown_utils.js @@ -54,67 +54,6 @@ export default class DropdownUtils { return updatedItem; } - static mergeDuplicateLabels(dataMap, newLabel) { - const updatedMap = dataMap; - const key = newLabel.title; - - const hasKeyProperty = Object.prototype.hasOwnProperty.call(updatedMap, key); - - if (!hasKeyProperty) { - updatedMap[key] = newLabel; - } else { - const existing = updatedMap[key]; - - if (!existing.multipleColors) { - existing.multipleColors = [existing.color]; - } - - existing.multipleColors.push(newLabel.color); - } - - return updatedMap; - } - - static duplicateLabelColor(labelColors) { - const colors = labelColors; - const spacing = 100 / colors.length; - - // Reduce the colors to 4 - colors.length = Math.min(colors.length, 4); - - const color = colors - .map((c, i) => { - const percentFirst = Math.floor(spacing * i); - const percentSecond = Math.floor(spacing * (i + 1)); - return `${c} ${percentFirst}%, ${c} ${percentSecond}%`; - }) - .join(', '); - - return `linear-gradient(${color})`; - } - - static duplicateLabelPreprocessing(data) { - const results = []; - const dataMap = {}; - - data.forEach(DropdownUtils.mergeDuplicateLabels.bind(null, dataMap)); - - Object.keys(dataMap).forEach(key => { - const label = dataMap[key]; - - if (label.multipleColors) { - label.color = DropdownUtils.duplicateLabelColor(label.multipleColors); - label.text_color = '#000000'; - } - - results.push(label); - }); - - results.preprocessed = true; - - return results; - } - static filterHint(config, item) { const { input, allowedKeys } = config; const updatedItem = item; diff --git a/app/assets/javascripts/filtered_search/filtered_search_visual_tokens.js b/app/assets/javascripts/filtered_search/filtered_search_visual_tokens.js index 89dcff74d0e..fba31f16d65 100644 --- a/app/assets/javascripts/filtered_search/filtered_search_visual_tokens.js +++ b/app/assets/javascripts/filtered_search/filtered_search_visual_tokens.js @@ -79,11 +79,7 @@ export default class FilteredSearchVisualTokens { static setTokenStyle(tokenContainer, backgroundColor, textColor) { const token = tokenContainer; - // Labels with linear gradient should not override default background color - if (backgroundColor.indexOf('linear-gradient') === -1) { - token.style.backgroundColor = backgroundColor; - } - + token.style.backgroundColor = backgroundColor; token.style.color = textColor; if (textColor === '#FFFFFF') { @@ -94,18 +90,6 @@ export default class FilteredSearchVisualTokens { return token; } - static preprocessLabel(labelsEndpoint, labels) { - let processed = labels; - - if (!labels.preprocessed) { - processed = DropdownUtils.duplicateLabelPreprocessing(labels); - AjaxCache.override(labelsEndpoint, processed); - processed.preprocessed = true; - } - - return processed; - } - static updateLabelTokenColor(tokenValueContainer, tokenValue) { const filteredSearchInput = FilteredSearchContainer.container.querySelector('.filtered-search'); const { baseEndpoint } = filteredSearchInput.dataset; @@ -115,7 +99,6 @@ export default class FilteredSearchVisualTokens { ); return AjaxCache.retrieve(labelsEndpoint) - .then(FilteredSearchVisualTokens.preprocessLabel.bind(null, labelsEndpoint)) .then(labels => { const matchingLabel = (labels || []).find( label => `~${DropdownUtils.getEscapedText(label.title)}` === tokenValue, diff --git a/app/assets/javascripts/gfm_auto_complete.js b/app/assets/javascripts/gfm_auto_complete.js index c14eb936930..8178821be3d 100644 --- a/app/assets/javascripts/gfm_auto_complete.js +++ b/app/assets/javascripts/gfm_auto_complete.js @@ -256,7 +256,7 @@ class GfmAutoComplete { displayTpl(value) { let tmpl = GfmAutoComplete.Loading.template; if (value.title != null) { - tmpl = GfmAutoComplete.Milestones.template; + tmpl = GfmAutoComplete.Milestones.templateFunction(value.title); } return tmpl; }, @@ -323,7 +323,7 @@ class GfmAutoComplete { searchKey: 'search', data: GfmAutoComplete.defaultLoadingData, displayTpl(value) { - let tmpl = GfmAutoComplete.Labels.template; + let tmpl = GfmAutoComplete.Labels.templateFunction(value.color, value.title); if (GfmAutoComplete.isLoading(value)) { tmpl = GfmAutoComplete.Loading.template; } @@ -588,9 +588,11 @@ GfmAutoComplete.Members = { }, }; GfmAutoComplete.Labels = { - template: - // eslint-disable-next-line no-template-curly-in-string - '<li><span class="dropdown-label-box" style="background: ${color}"></span> ${title}</li>', + templateFunction(color, title) { + return `<li><span class="dropdown-label-box" style="background: ${_.escape( + color, + )}"></span> ${_.escape(title)}</li>`; + }, }; // Issues, MergeRequests and Snippets GfmAutoComplete.Issues = { @@ -600,8 +602,9 @@ GfmAutoComplete.Issues = { }; // Milestones GfmAutoComplete.Milestones = { - // eslint-disable-next-line no-template-curly-in-string - template: '<li>${title}</li>', + templateFunction(title) { + return `<li>${_.escape(title)}</li>`; + }, }; GfmAutoComplete.Loading = { template: diff --git a/app/assets/javascripts/jobs/components/artifacts_block.vue b/app/assets/javascripts/jobs/components/artifacts_block.vue index 309b7427b9e..0bce860df91 100644 --- a/app/assets/javascripts/jobs/components/artifacts_block.vue +++ b/app/assets/javascripts/jobs/components/artifacts_block.vue @@ -28,27 +28,29 @@ export default { </script> <template> <div class="block"> - <div class="title">{{ s__('Job|Job artifacts') }}</div> + <div class="title font-weight-bold">{{ s__('Job|Job artifacts') }}</div> - <p v-if="isExpired" class="js-artifacts-removed build-detail-row"> - {{ s__('Job|The artifacts were removed') }} + <p + v-if="isExpired || willExpire" + :class="{ + 'js-artifacts-removed': isExpired, + 'js-artifacts-will-be-removed': willExpire, + }" + class="build-detail-row" + > + <span v-if="isExpired">{{ s__('Job|The artifacts were removed') }}</span> + <span v-if="willExpire">{{ s__('Job|The artifacts will be removed') }}</span> + <timeago-tooltip v-if="artifact.expire_at" :time="artifact.expire_at" /> </p> - <p v-else-if="willExpire" class="js-artifacts-will-be-removed build-detail-row"> - {{ s__('Job|The artifacts will be removed in') }} - </p> - - <timeago-tooltip v-if="artifact.expire_at" :time="artifact.expire_at" /> - - <div class="btn-group d-flex" role="group"> + <div class="btn-group d-flex prepend-top-10" role="group"> <gl-link v-if="artifact.keep_path" :href="artifact.keep_path" class="js-keep-artifacts btn btn-sm btn-default" data-method="post" + >{{ s__('Job|Keep') }}</gl-link > - {{ s__('Job|Keep') }} - </gl-link> <gl-link v-if="artifact.download_path" @@ -56,17 +58,15 @@ export default { class="js-download-artifacts btn btn-sm btn-default" download rel="nofollow" + >{{ s__('Job|Download') }}</gl-link > - {{ s__('Job|Download') }} - </gl-link> <gl-link v-if="artifact.browse_path" :href="artifact.browse_path" class="js-browse-artifacts btn btn-sm btn-default" + >{{ s__('Job|Browse') }}</gl-link > - {{ s__('Job|Browse') }} - </gl-link> </div> </div> </template> diff --git a/app/assets/javascripts/jobs/components/commit_block.vue b/app/assets/javascripts/jobs/components/commit_block.vue index 3b9c61bd48c..e0f55518eef 100644 --- a/app/assets/javascripts/jobs/components/commit_block.vue +++ b/app/assets/javascripts/jobs/components/commit_block.vue @@ -31,12 +31,12 @@ export default { block: !isLastBlock, }" > - <p> - {{ __('Commit') }} + <p class="append-bottom-5"> + <span class="font-weight-bold">{{ __('Commit') }}</span> - <gl-link :href="commit.commit_path" class="js-commit-sha commit-sha link-commit">{{ - commit.short_id - }}</gl-link> + <gl-link :href="commit.commit_path" class="js-commit-sha commit-sha link-commit"> + {{ commit.short_id }} + </gl-link> <clipboard-button :text="commit.short_id" @@ -44,11 +44,14 @@ export default { css-class="btn btn-clipboard btn-transparent" /> - <gl-link v-if="mergeRequest" :href="mergeRequest.path" class="js-link-commit link-commit" - >!{{ mergeRequest.iid }}</gl-link - > + <span v-if="mergeRequest"> + {{ __('in') }} + <gl-link :href="mergeRequest.path" class="js-link-commit link-commit" + >!{{ mergeRequest.iid }}</gl-link + > + </span> </p> - <p class="build-light-text append-bottom-0">{{ commit.title }}</p> + <p class="append-bottom-0">{{ commit.title }}</p> </div> </template> diff --git a/app/assets/javascripts/jobs/components/sidebar.vue b/app/assets/javascripts/jobs/components/sidebar.vue index 934ecd0e3ec..ad3e7dabc79 100644 --- a/app/assets/javascripts/jobs/components/sidebar.vue +++ b/app/assets/javascripts/jobs/components/sidebar.vue @@ -110,22 +110,20 @@ export default { <aside class="right-sidebar build-sidebar" data-offset-top="101" data-spy="affix"> <div class="sidebar-container"> <div class="blocks-container"> - <div class="block"> - <strong class="inline prepend-top-8"> {{ job.name }} </strong> + <div class="block d-flex align-items-center"> + <h4 class="flex-grow-1 prepend-top-8 m-0">{{ job.name }}</h4> <gl-link v-if="job.retry_path" :class="retryButtonClass" :href="job.retry_path" data-method="post" rel="nofollow" + >{{ __('Retry') }}</gl-link > - {{ __('Retry') }} - </gl-link> <gl-link v-if="job.terminal_path" :href="job.terminal_path" - class="js-terminal-link pull-right btn btn-primary - btn-inverted visible-md-block visible-lg-block" + class="js-terminal-link pull-right btn btn-primary btn-inverted visible-md-block visible-lg-block" target="_blank" > {{ __('Debug') }} <icon name="external-link" /> @@ -133,8 +131,7 @@ export default { <gl-button :aria-label="__('Toggle Sidebar')" type="button" - class="btn btn-blank gutter-toggle - float-right d-block d-md-none js-sidebar-build-toggle" + class="btn btn-blank gutter-toggle float-right d-block d-md-none js-sidebar-build-toggle" @click="toggleSidebar" > <i aria-hidden="true" data-hidden="true" class="fa fa-angle-double-right"></i> @@ -145,25 +142,18 @@ export default { v-if="job.new_issue_path" :href="job.new_issue_path" class="js-new-issue btn btn-success btn-inverted" + >{{ __('New issue') }}</gl-link > - {{ __('New issue') }} - </gl-link> <gl-link v-if="job.retry_path" :href="job.retry_path" class="js-retry-job btn btn-inverted-secondary" data-method="post" rel="nofollow" + >{{ __('Retry') }}</gl-link > - {{ __('Retry') }} - </gl-link> </div> <div :class="{ block: renderBlock }"> - <p v-if="job.merge_request" class="build-detail-row js-job-mr"> - <span class="build-light-text"> {{ __('Merge Request:') }} </span> - <gl-link :href="job.merge_request.path"> !{{ job.merge_request.iid }} </gl-link> - </p> - <detail-row v-if="job.duration" :value="duration" @@ -198,10 +188,10 @@ export default { title="Coverage" /> <p v-if="job.tags.length" class="build-detail-row js-job-tags"> - <span class="build-light-text"> {{ __('Tags:') }} </span> - <span v-for="(tag, i) in job.tags" :key="i" class="badge badge-primary"> - {{ tag }} - </span> + <span class="font-weight-bold">{{ __('Tags:') }}</span> + <span v-for="(tag, i) in job.tags" :key="i" class="badge badge-primary mr-1">{{ + tag + }}</span> </p> <div v-if="job.cancel_path" class="btn-group prepend-top-5" role="group"> @@ -210,9 +200,8 @@ export default { class="js-cancel-job btn btn-sm btn-default" data-method="post" rel="nofollow" + >{{ __('Cancel') }}</gl-link > - {{ __('Cancel') }} - </gl-link> </div> </div> diff --git a/app/assets/javascripts/jobs/components/sidebar_detail_row.vue b/app/assets/javascripts/jobs/components/sidebar_detail_row.vue index 77be295e802..b826007ec2c 100644 --- a/app/assets/javascripts/jobs/components/sidebar_detail_row.vue +++ b/app/assets/javascripts/jobs/components/sidebar_detail_row.vue @@ -34,8 +34,7 @@ export default { </script> <template> <p class="build-detail-row"> - <span v-if="hasTitle" class="build-light-text"> {{ title }}: </span> {{ value }} - + <span v-if="hasTitle" class="font-weight-bold">{{ title }}:</span> {{ value }} <span v-if="hasHelpURL" class="help-button float-right"> <gl-link :href="helpUrl" target="_blank" rel="noopener noreferrer nofollow"> <i class="fa fa-question-circle" aria-hidden="true"></i> diff --git a/app/assets/javascripts/jobs/components/stages_dropdown.vue b/app/assets/javascripts/jobs/components/stages_dropdown.vue index 90482500bbf..7f79e92067f 100644 --- a/app/assets/javascripts/jobs/components/stages_dropdown.vue +++ b/app/assets/javascripts/jobs/components/stages_dropdown.vue @@ -38,11 +38,11 @@ export default { <div class="block-last dropdown"> <ci-icon :status="pipeline.details.status" class="vertical-align-middle" /> - {{ __('Pipeline') }} - <a :href="pipeline.path" class="js-pipeline-path link-commit"> #{{ pipeline.id }} </a> + <span class="font-weight-bold">{{ __('Pipeline') }}</span> + <a :href="pipeline.path" class="js-pipeline-path link-commit">#{{ pipeline.id }}</a> <template v-if="hasRef"> {{ __('from') }} - <a :href="pipeline.ref.path" class="link-commit ref-name"> {{ pipeline.ref.name }} </a> + <a :href="pipeline.ref.path" class="link-commit ref-name">{{ pipeline.ref.name }}</a> </template> <button diff --git a/app/assets/javascripts/jobs/components/trigger_block.vue b/app/assets/javascripts/jobs/components/trigger_block.vue index 3cd3b743108..997737b3e23 100644 --- a/app/assets/javascripts/jobs/components/trigger_block.vue +++ b/app/assets/javascripts/jobs/components/trigger_block.vue @@ -43,23 +43,24 @@ export default { <template> <div class="build-widget block"> - <h4 class="title">{{ __('Trigger') }}</h4> - <p v-if="trigger.short_token" class="js-short-token" - :class="{ 'append-bottom-0': !hasVariables }" + :class="{ 'append-bottom-5': hasVariables, 'append-bottom-0': !hasVariables }" > - <span class="build-light-text"> {{ __('Token') }} </span> {{ trigger.short_token }} + <span class="font-weight-bold">{{ __('Trigger token:') }}</span> {{ trigger.short_token }} </p> <template v-if="hasVariables"> <p class="trigger-variables-btn-container"> - <span class="build-light-text"> {{ __('Variables:') }} </span> + <span class="font-weight-bold">{{ __('Trigger variables:') }}</span> - <gl-button v-if="hasValues" class="group js-reveal-variables" @click="toggleValues"> - {{ getToggleButtonText }} - </gl-button> + <gl-button + v-if="hasValues" + class="btn-sm group js-reveal-variables trigger-variables-btn" + @click="toggleValues" + >{{ getToggleButtonText }}</gl-button + > </p> <table class="js-build-variables trigger-build-variables"> diff --git a/app/assets/javascripts/labels_select.js b/app/assets/javascripts/labels_select.js index c0a76814102..f7a611fbca0 100644 --- a/app/assets/javascripts/labels_select.js +++ b/app/assets/javascripts/labels_select.js @@ -7,7 +7,6 @@ import _ from 'underscore'; import { sprintf, __ } from './locale'; import axios from './lib/utils/axios_utils'; import IssuableBulkUpdateActions from './issuable_bulk_update_actions'; -import DropdownUtils from './filtered_search/dropdown_utils'; import CreateLabelDropdown from './create_label'; import flash from './flash'; import ModalStore from './boards/stores/modal_store'; @@ -171,23 +170,7 @@ export default class LabelsSelect { axios .get(labelUrl) .then(res => { - let data = _.chain(res.data) - .groupBy(function(label) { - return label.title; - }) - .map(function(label) { - var color; - color = _.map(label, function(dup) { - return dup.color; - }); - return { - id: label[0].id, - title: label[0].title, - color: color, - duplicate: color.length > 1, - }; - }) - .value(); + let { data } = res; if ($dropdown.hasClass('js-extra-options')) { var extraData = []; if (showNo) { @@ -272,15 +255,9 @@ export default class LabelsSelect { selectedClass.push('dropdown-clear-active'); } } - if (label.duplicate) { - color = DropdownUtils.duplicateLabelColor(label.color); - } else { - if (label.color != null) { - [color] = label.color; - } - } - if (color) { - colorEl = "<span class='dropdown-label-box' style='background: " + color + "'></span>"; + if (label.color) { + colorEl = + "<span class='dropdown-label-box' style='background: " + label.color + "'></span>"; } else { colorEl = ''; } @@ -435,7 +412,7 @@ export default class LabelsSelect { new ListLabel({ id: label.id, title: label.title, - color: label.color[0], + color: label.color, textColor: '#fff', }), ); diff --git a/app/assets/javascripts/lib/utils/text_markdown.js b/app/assets/javascripts/lib/utils/text_markdown.js index 3618c6af7e2..1254ec798a6 100644 --- a/app/assets/javascripts/lib/utils/text_markdown.js +++ b/app/assets/javascripts/lib/utils/text_markdown.js @@ -39,7 +39,14 @@ function blockTagText(text, textArea, blockTag, selected) { } } -function moveCursor({ textArea, tag, positionBetweenTags, removedLastNewLine, select }) { +function moveCursor({ + textArea, + tag, + cursorOffset, + positionBetweenTags, + removedLastNewLine, + select, +}) { var pos; if (!textArea.setSelectionRange) { return; @@ -61,11 +68,24 @@ function moveCursor({ textArea, tag, positionBetweenTags, removedLastNewLine, se pos -= 1; } + if (cursorOffset) { + pos -= cursorOffset; + } + return textArea.setSelectionRange(pos, pos); } } -export function insertMarkdownText({ textArea, text, tag, blockTag, selected, wrap, select }) { +export function insertMarkdownText({ + textArea, + text, + tag, + cursorOffset, + blockTag, + selected = '', + wrap, + select, +}) { var textToInsert, selectedSplit, startChar, @@ -154,20 +174,30 @@ export function insertMarkdownText({ textArea, text, tag, blockTag, selected, wr return moveCursor({ textArea, tag: tag.replace(textPlaceholder, selected), + cursorOffset, positionBetweenTags: wrap && selected.length === 0, removedLastNewLine, select, }); } -function updateText({ textArea, tag, blockTag, wrap, select }) { +function updateText({ textArea, tag, cursorOffset, blockTag, wrap, select, tagContent }) { var $textArea, selected, text; $textArea = $(textArea); textArea = $textArea.get(0); text = $textArea.val(); - selected = selectedText(text, textArea); + selected = selectedText(text, textArea) || tagContent; $textArea.focus(); - return insertMarkdownText({ textArea, text, tag, blockTag, selected, wrap, select }); + return insertMarkdownText({ + textArea, + text, + tag, + cursorOffset, + blockTag, + selected, + wrap, + select, + }); } export function addMarkdownListeners(form) { @@ -178,9 +208,11 @@ export function addMarkdownListeners(form) { return updateText({ textArea: $this.closest('.md-area').find('textarea'), tag: $this.data('mdTag'), + cursorOffset: $this.data('mdCursorOffset'), blockTag: $this.data('mdBlock'), wrap: !$this.data('mdPrepend'), select: $this.data('mdSelect'), + tagContent: $this.data('mdTagContent'), }); }); } diff --git a/app/assets/javascripts/monitoring/utils/multiple_time_series.js b/app/assets/javascripts/monitoring/utils/multiple_time_series.js index bb24a1acdb3..50ba14dfb2e 100644 --- a/app/assets/javascripts/monitoring/utils/multiple_time_series.js +++ b/app/assets/javascripts/monitoring/utils/multiple_time_series.js @@ -92,7 +92,11 @@ function queryTimeSeries(query, graphDrawData, lineStyle) { if (seriesCustomizationData) { metricTag = seriesCustomizationData.value || timeSeriesMetricLabel; [lineColor, areaColor] = pickColor(seriesCustomizationData.color); - shouldRenderLegend = false; + if (timeSeriesParsed.length > 0) { + shouldRenderLegend = false; + } else { + shouldRenderLegend = true; + } } else { metricTag = timeSeriesMetricLabel || query.label || `series ${timeSeriesNumber + 1}`; [lineColor, areaColor] = pickColor(); @@ -101,19 +105,6 @@ function queryTimeSeries(query, graphDrawData, lineStyle) { } } - if (!shouldRenderLegend) { - if (!timeSeriesParsed[0].tracksLegend) { - timeSeriesParsed[0].tracksLegend = []; - } - timeSeriesParsed[0].tracksLegend.push({ - max: maximumValue, - average: accum / timeSeries.values.length, - lineStyle, - lineColor, - metricTag, - }); - } - const values = datesWithoutGaps.map(time => ({ time, value: findByDate(timeSeries.values, time), @@ -135,6 +126,19 @@ function queryTimeSeries(query, graphDrawData, lineStyle) { shouldRenderLegend, renderCanary, }); + + if (!shouldRenderLegend) { + if (!timeSeriesParsed[0].tracksLegend) { + timeSeriesParsed[0].tracksLegend = []; + } + timeSeriesParsed[0].tracksLegend.push({ + max: maximumValue, + average: accum / timeSeries.values.length, + lineStyle, + lineColor, + metricTag, + }); + } }); return timeSeriesParsed; diff --git a/app/assets/javascripts/mr_notes/index.js b/app/assets/javascripts/mr_notes/index.js index 1c98683c597..e4d72eb8318 100644 --- a/app/assets/javascripts/mr_notes/index.js +++ b/app/assets/javascripts/mr_notes/index.js @@ -33,6 +33,7 @@ export default function initMrNotes() { noteableData, currentUserData: JSON.parse(notesDataset.currentUserData), notesData: JSON.parse(notesDataset.notesData), + helpPagePath: notesDataset.helpPagePath, }; }, computed: { @@ -71,6 +72,7 @@ export default function initMrNotes() { notesData: this.notesData, userData: this.currentUserData, shouldShow: this.activeTab === 'show', + helpPagePath: this.helpPagePath, }, }); }, diff --git a/app/assets/javascripts/notes/components/discussion_filter.vue b/app/assets/javascripts/notes/components/discussion_filter.vue index 86c114a761a..f5c410211b6 100644 --- a/app/assets/javascripts/notes/components/discussion_filter.vue +++ b/app/assets/javascripts/notes/components/discussion_filter.vue @@ -2,7 +2,11 @@ import $ from 'jquery'; import { mapGetters, mapActions } from 'vuex'; import Icon from '~/vue_shared/components/icon.vue'; -import { DISCUSSION_FILTERS_DEFAULT_VALUE, HISTORY_ONLY_FILTER_VALUE } from '../constants'; +import { + DISCUSSION_FILTERS_DEFAULT_VALUE, + HISTORY_ONLY_FILTER_VALUE, + DISCUSSION_TAB_LABEL, +} from '../constants'; export default { components: { @@ -23,6 +27,7 @@ export default { return { currentValue: this.selectedValue, defaultValue: DISCUSSION_FILTERS_DEFAULT_VALUE, + displayFilters: true, }; }, computed: { @@ -32,6 +37,14 @@ export default { return this.filters.find(filter => filter.value === this.currentValue); }, }, + created() { + if (window.mrTabs) { + const { eventHub, currentTab } = window.mrTabs; + + eventHub.$on('MergeRequestTabChange', this.toggleFilters); + this.toggleFilters(currentTab); + } + }, mounted() { this.toggleCommentsForm(); }, @@ -51,12 +64,15 @@ export default { toggleCommentsForm() { this.setCommentsDisabled(this.currentValue === HISTORY_ONLY_FILTER_VALUE); }, + toggleFilters(tab) { + this.displayFilters = tab === DISCUSSION_TAB_LABEL; + }, }, }; </script> <template> - <div class="discussion-filter-container d-inline-block align-bottom"> + <div v-if="displayFilters" class="discussion-filter-container d-inline-block align-bottom"> <button id="discussion-filter-dropdown" ref="dropdownToggle" diff --git a/app/assets/javascripts/notes/components/note_body.vue b/app/assets/javascripts/notes/components/note_body.vue index c0bee600181..bcf5d334da4 100644 --- a/app/assets/javascripts/notes/components/note_body.vue +++ b/app/assets/javascripts/notes/components/note_body.vue @@ -1,10 +1,12 @@ <script> +import { mapActions } from 'vuex'; import $ from 'jquery'; import noteEditedText from './note_edited_text.vue'; import noteAwardsList from './note_awards_list.vue'; import noteAttachment from './note_attachment.vue'; import noteForm from './note_form.vue'; import autosave from '../mixins/autosave'; +import Suggestions from '~/vue_shared/components/markdown/suggestions.vue'; export default { components: { @@ -12,6 +14,7 @@ export default { noteAwardsList, noteAttachment, noteForm, + Suggestions, }, mixins: [autosave], props: { @@ -19,6 +22,11 @@ export default { type: Object, required: true, }, + line: { + type: Object, + required: false, + default: null, + }, canEdit: { type: Boolean, required: true, @@ -28,11 +36,22 @@ export default { required: false, default: false, }, + helpPagePath: { + type: String, + required: false, + default: '', + }, }, computed: { noteBody() { return this.note.note; }, + hasSuggestion() { + return this.note.suggestions && this.note.suggestions.length; + }, + lineType() { + return this.line ? this.line.type : null; + }, }, mounted() { this.renderGFM(); @@ -53,6 +72,7 @@ export default { } }, methods: { + ...mapActions(['submitSuggestion']), renderGFM() { $(this.$refs['note-body']).renderGFM(); }, @@ -62,19 +82,35 @@ export default { formCancelHandler(shouldConfirm, isDirty) { this.$emit('cancelForm', shouldConfirm, isDirty); }, + applySuggestion({ suggestionId, flashContainer, callback }) { + const { discussion_id: discussionId, id: noteId } = this.note; + + this.submitSuggestion({ discussionId, noteId, suggestionId, flashContainer, callback }); + }, }, }; </script> <template> <div ref="note-body" :class="{ 'js-task-list-container': canEdit }" class="note-body"> - <div class="note-text md" v-html="note.note_html"></div> + <suggestions + v-if="hasSuggestion && !isEditing" + :suggestions="note.suggestions" + :note-html="note.note_html" + :line-type="lineType" + :help-page-path="helpPagePath" + @apply="applySuggestion" + /> + <div v-else class="note-text md" v-html="note.note_html"></div> <note-form v-if="isEditing" ref="noteForm" :is-editing="isEditing" :note-body="noteBody" :note-id="note.id" + :line="line" + :note="note" + :help-page-path="helpPagePath" :markdown-version="note.cached_markdown_version" @handleFormUpdate="handleFormUpdate" @cancelForm="formCancelHandler" diff --git a/app/assets/javascripts/notes/components/note_form.vue b/app/assets/javascripts/notes/components/note_form.vue index 95164183ccb..e78596f8b52 100644 --- a/app/assets/javascripts/notes/components/note_form.vue +++ b/app/assets/javascripts/notes/components/note_form.vue @@ -1,4 +1,5 @@ <script> +import { mergeUrlParams } from '~/lib/utils/url_utility'; import { mapGetters, mapActions } from 'vuex'; import eventHub from '../event_hub'; import issueWarning from '../../vue_shared/components/issue/issue_warning.vue'; @@ -53,6 +54,21 @@ export default { required: false, default: false, }, + line: { + type: Object, + required: false, + default: null, + }, + note: { + type: Object, + required: false, + default: null, + }, + helpPagePath: { + type: String, + required: false, + default: '', + }, }, data() { return { @@ -79,7 +95,8 @@ export default { return '#'; }, markdownPreviewPath() { - return this.getNoteableDataByProp('preview_note_path'); + const notable = this.getNoteableDataByProp('preview_note_path'); + return mergeUrlParams({ preview_suggestions: true }, notable); }, markdownDocsPath() { return this.getNotesDataByProp('markdownDocsPath'); @@ -93,6 +110,18 @@ export default { isDisabled() { return !this.updatedNoteBody.length || this.isSubmitting; }, + discussionNote() { + const discussionNote = this.discussion.id + ? this.getDiscussionLastNote(this.discussion) + : this.note; + return discussionNote || {}; + }, + canSuggest() { + return ( + this.getNoteableData.can_receive_suggestion && + (this.line && this.line.can_receive_suggestion) + ); + }, }, watch: { noteBody() { @@ -171,7 +200,11 @@ export default { :markdown-docs-path="markdownDocsPath" :markdown-version="markdownVersion" :quick-actions-docs-path="quickActionsDocsPath" + :line="line" + :note="discussionNote" + :can-suggest="canSuggest" :add-spacing-classes="false" + :help-page-path="helpPagePath" > <textarea id="note_note" @@ -193,7 +226,7 @@ export default { <button :disabled="isDisabled" type="button" - class="js-vue-issue-save btn btn-success js-comment-button" + class="js-vue-issue-save btn btn-success js-comment-button qa-reply-comment-button" @click="handleUpdate();" > {{ saveButtonTitle }} diff --git a/app/assets/javascripts/notes/components/noteable_discussion.vue b/app/assets/javascripts/notes/components/noteable_discussion.vue index 5c9a28b8512..7c3f5d00308 100644 --- a/app/assets/javascripts/notes/components/noteable_discussion.vue +++ b/app/assets/javascripts/notes/components/noteable_discussion.vue @@ -49,6 +49,11 @@ export default { type: Object, required: true, }, + line: { + type: Object, + required: false, + default: null, + }, renderDiffFile: { type: Boolean, required: false, @@ -64,6 +69,11 @@ export default { required: false, default: false, }, + helpPagePath: { + type: String, + required: false, + default: '', + }, }, data() { const { diff_discussion: isDiffDiscussion, resolved } = this.discussion; @@ -168,31 +178,39 @@ export default { commitId = `<span class="commit-sha">${truncateSha(commitId)}</span>`; } - let text = s__('MergeRequests|started a discussion'); + const { + for_commit: isForCommit, + diff_discussion: isDiffDiscussion, + active: isActive, + } = this.discussion; - if (this.discussion.for_commit) { + let text = s__('MergeRequests|started a discussion'); + if (isForCommit) { text = s__( 'MergeRequests|started a discussion on commit %{linkStart}%{commitId}%{linkEnd}', ); - } else if (this.discussion.diff_discussion) { - if (this.discussion.active) { - text = s__('MergeRequests|started a discussion on %{linkStart}the diff%{linkEnd}'); - } else { - text = s__( - 'MergeRequests|started a discussion on %{linkStart}an old version of the diff%{linkEnd}', - ); - } + } else if (isDiffDiscussion && commitId) { + text = isActive + ? s__('MergeRequests|started a discussion on commit %{linkStart}%{commitId}%{linkEnd}') + : s__( + 'MergeRequests|started a discussion on an outdated change in commit %{linkStart}%{commitId}%{linkEnd}', + ); + } else if (isDiffDiscussion) { + text = isActive + ? s__('MergeRequests|started a discussion on %{linkStart}the diff%{linkEnd}') + : s__( + 'MergeRequests|started a discussion on %{linkStart}an old version of the diff%{linkEnd}', + ); } - return sprintf( - text, - { - commitId, - linkStart, - linkEnd, - }, - false, - ); + return sprintf(text, { commitId, linkStart, linkEnd }, false); + }, + diffLine() { + if (this.discussion.diff_discussion && this.discussion.truncated_diff_lines) { + return this.discussion.truncated_diff_lines.slice(-1)[0]; + } + + return this.line; }, }, watch: { @@ -357,8 +375,18 @@ Please check your network connection and try again.`; <component :is="componentName(initialDiscussion)" :note="componentData(initialDiscussion)" + :line="line" + :help-page-path="helpPagePath" @handleDeleteNote="deleteNoteHandler" > + <note-edited-text + v-if="discussion.resolved" + slot="discussion-resolved-text" + :edited-at="discussion.resolved_at" + :edited-by="discussion.resolved_by" + :action-text="resolvedText" + class-name="discussion-headline-light js-discussion-headline discussion-resolved-text" + /> <slot slot="avatar-badge" name="avatar-badge"></slot> </component> <toggle-replies-widget @@ -373,6 +401,8 @@ Please check your network connection and try again.`; v-for="note in replies" :key="note.id" :note="componentData(note)" + :help-page-path="helpPagePath" + :line="line" @handleDeleteNote="deleteNoteHandler" /> </template> @@ -383,6 +413,8 @@ Please check your network connection and try again.`; v-for="(note, index) in discussion.notes" :key="note.id" :note="componentData(note)" + :help-page-path="helpPagePath" + :line="diffLine" @handleDeleteNote="deleteNoteHandler" > <slot v-if="index === 0" slot="avatar-badge" name="avatar-badge"></slot> @@ -390,7 +422,7 @@ Please check your network connection and try again.`; </template> </ul> <div - v-if="!isRepliesCollapsed" + v-if="!isRepliesCollapsed || !hasReplies" :class="{ 'is-replying': isReplying }" class="discussion-reply-holder" > @@ -447,6 +479,7 @@ Please check your network connection and try again.`; ref="noteForm" :discussion="discussion" :is-editing="false" + :line="diffLine" save-button-title="Comment" @handleFormUpdate="saveReply" @cancelForm="cancelReplyForm" diff --git a/app/assets/javascripts/notes/components/noteable_note.vue b/app/assets/javascripts/notes/components/noteable_note.vue index a17be51353e..4c02588127e 100644 --- a/app/assets/javascripts/notes/components/noteable_note.vue +++ b/app/assets/javascripts/notes/components/noteable_note.vue @@ -27,6 +27,16 @@ export default { type: Object, required: true, }, + line: { + type: Object, + required: false, + default: null, + }, + helpPagePath: { + type: String, + required: false, + default: '', + }, }, data() { return { @@ -185,7 +195,7 @@ export default { :img-alt="author.name" :img-size="40" > - <slot slot="avatar-badge" name="avatar-badge"> </slot> + <slot slot="avatar-badge" name="avatar-badge"></slot> </user-avatar-link> </div> <div class="timeline-content"> @@ -217,14 +227,19 @@ export default { @handleResolve="resolveHandler" /> </div> - <note-body - ref="noteBody" - :note="note" - :can-edit="note.current_user.can_edit" - :is-editing="isEditing" - @handleFormUpdate="formUpdateHandler" - @cancelForm="formCancelHandler" - /> + <div class="timeline-discussion-body"> + <slot name="discussion-resolved-text"></slot> + <note-body + ref="noteBody" + :note="note" + :line="line" + :can-edit="note.current_user.can_edit" + :is-editing="isEditing" + :help-page-path="helpPagePath" + @handleFormUpdate="formUpdateHandler" + @cancelForm="formCancelHandler" + /> + </div> </div> </timeline-entry-item> </template> diff --git a/app/assets/javascripts/notes/components/notes_app.vue b/app/assets/javascripts/notes/components/notes_app.vue index 27f896cee35..f3fcfdfda05 100644 --- a/app/assets/javascripts/notes/components/notes_app.vue +++ b/app/assets/javascripts/notes/components/notes_app.vue @@ -49,6 +49,11 @@ export default { required: false, default: 0, }, + helpPagePath: { + type: String, + required: false, + default: '', + }, }, data() { return { @@ -206,6 +211,7 @@ export default { :key="discussion.id" :discussion="discussion" :render-diff-file="true" + :help-page-path="helpPagePath" /> </template> </ul> diff --git a/app/assets/javascripts/notes/components/toggle_replies_widget.vue b/app/assets/javascripts/notes/components/toggle_replies_widget.vue index 72a8ff28466..f1b0b12bdce 100644 --- a/app/assets/javascripts/notes/components/toggle_replies_widget.vue +++ b/app/assets/javascripts/notes/components/toggle_replies_widget.vue @@ -57,7 +57,7 @@ export default { tooltip-placement="bottom" /> </div> - <button class="btn btn-link js-replies-text" type="button" @click="toggle"> + <button class="btn btn-link js-replies-text qa-expand-replies" type="button" @click="toggle"> {{ replies.length }} {{ n__('reply', 'replies', replies.length) }} </button> {{ __('Last reply by') }} @@ -66,7 +66,11 @@ export default { </a> <time-ago-tooltip :time="lastReply.created_at" tooltip-placement="bottom" /> </template> - <span v-else class="collapse-replies-btn js-collapse-replies" @click="toggle"> + <span + v-else + class="collapse-replies-btn js-collapse-replies qa-collapse-replies" + @click="toggle" + > <icon name="chevron-down" /> {{ s__('Notes|Collapse replies') }} </span> </li> diff --git a/app/assets/javascripts/notes/constants.js b/app/assets/javascripts/notes/constants.js index 3147dc64c27..78d365fe94b 100644 --- a/app/assets/javascripts/notes/constants.js +++ b/app/assets/javascripts/notes/constants.js @@ -17,6 +17,7 @@ export const RESOLVE_NOTE_METHOD_NAME = 'post'; export const DESCRIPTION_TYPE = 'changed the description'; export const HISTORY_ONLY_FILTER_VALUE = 2; export const DISCUSSION_FILTERS_DEFAULT_VALUE = 0; +export const DISCUSSION_TAB_LABEL = 'show'; export const NOTEABLE_TYPE_MAPPING = { Issue: ISSUE_NOTEABLE_TYPE, diff --git a/app/assets/javascripts/notes/services/notes_service.js b/app/assets/javascripts/notes/services/notes_service.js index 47a6f07cce2..237e70c0a4c 100644 --- a/app/assets/javascripts/notes/services/notes_service.js +++ b/app/assets/javascripts/notes/services/notes_service.js @@ -1,4 +1,5 @@ import Vue from 'vue'; +import Api from '~/api'; import VueResource from 'vue-resource'; import * as constants from '../constants'; @@ -44,4 +45,7 @@ export default { toggleIssueState(endpoint, data) { return Vue.http.put(endpoint, data); }, + applySuggestion(id) { + return Api.applySuggestion(id); + }, }; diff --git a/app/assets/javascripts/notes/stores/actions.js b/app/assets/javascripts/notes/stores/actions.js index 4716ab52333..65f85314fa0 100644 --- a/app/assets/javascripts/notes/stores/actions.js +++ b/app/assets/javascripts/notes/stores/actions.js @@ -405,5 +405,25 @@ export const startTaskList = ({ dispatch }) => export const updateResolvableDiscussonsCounts = ({ commit }) => commit(types.UPDATE_RESOLVABLE_DISCUSSIONS_COUNTS); +export const submitSuggestion = ( + { commit }, + { discussionId, noteId, suggestionId, flashContainer, callback }, +) => { + service + .applySuggestion(suggestionId) + .then(() => { + commit(types.APPLY_SUGGESTION, { discussionId, noteId, suggestionId }); + callback(); + }) + .catch(() => { + Flash( + __('Something went wrong while applying the suggestion. Please try again.'), + 'alert', + flashContainer, + ); + callback(); + }); +}; + // prevent babel-plugin-rewire from generating an invalid default during karma tests export default () => {}; diff --git a/app/assets/javascripts/notes/stores/modules/index.js b/app/assets/javascripts/notes/stores/modules/index.js index b5fe8bdb1d3..887e6d22b06 100644 --- a/app/assets/javascripts/notes/stores/modules/index.js +++ b/app/assets/javascripts/notes/stores/modules/index.js @@ -20,6 +20,7 @@ export default () => ({ userData: {}, noteableData: { current_user: {}, + preview_note_path: 'path/to/preview', }, commentsDisabled: false, resolvableDiscussionsCount: 0, diff --git a/app/assets/javascripts/notes/stores/mutation_types.js b/app/assets/javascripts/notes/stores/mutation_types.js index 9c68ab67a8c..df943c155f4 100644 --- a/app/assets/javascripts/notes/stores/mutation_types.js +++ b/app/assets/javascripts/notes/stores/mutation_types.js @@ -16,6 +16,7 @@ export const SET_DISCUSSION_DIFF_LINES = 'SET_DISCUSSION_DIFF_LINES'; export const SET_NOTES_FETCHED_STATE = 'SET_NOTES_FETCHED_STATE'; export const SET_NOTES_LOADING_STATE = 'SET_NOTES_LOADING_STATE'; export const DISABLE_COMMENTS = 'DISABLE_COMMENTS'; +export const APPLY_SUGGESTION = 'APPLY_SUGGESTION'; // DISCUSSION export const COLLAPSE_DISCUSSION = 'COLLAPSE_DISCUSSION'; diff --git a/app/assets/javascripts/notes/stores/mutations.js b/app/assets/javascripts/notes/stores/mutations.js index 39ff0ff73d7..8992454be2e 100644 --- a/app/assets/javascripts/notes/stores/mutations.js +++ b/app/assets/javascripts/notes/stores/mutations.js @@ -197,6 +197,17 @@ export default { } }, + [types.APPLY_SUGGESTION](state, { noteId, discussionId, suggestionId }) { + const noteObj = utils.findNoteObjectById(state.discussions, discussionId); + const comment = utils.findNoteObjectById(noteObj.notes, noteId); + + comment.suggestions = comment.suggestions.map(suggestion => ({ + ...suggestion, + applied: suggestion.applied || suggestion.id === suggestionId, + appliable: false, + })); + }, + [types.UPDATE_DISCUSSION](state, noteData) { const note = noteData; const selectedDiscussion = state.discussions.find(disc => disc.id === note.id); diff --git a/app/assets/javascripts/pages/dashboard/projects/index.js b/app/assets/javascripts/pages/dashboard/projects/index.js index 0c585e162cb..8f98be79640 100644 --- a/app/assets/javascripts/pages/dashboard/projects/index.js +++ b/app/assets/javascripts/pages/dashboard/projects/index.js @@ -1,3 +1,7 @@ import ProjectsList from '~/projects_list'; +import Star from '../../../star'; -document.addEventListener('DOMContentLoaded', () => new ProjectsList()); +document.addEventListener('DOMContentLoaded', () => { + new ProjectsList(); // eslint-disable-line no-new + new Star('.project-row'); // eslint-disable-line no-new +}); diff --git a/app/assets/javascripts/pages/projects/issues/form.js b/app/assets/javascripts/pages/projects/issues/form.js index 02a56685a35..f99023ad8e7 100644 --- a/app/assets/javascripts/pages/projects/issues/form.js +++ b/app/assets/javascripts/pages/projects/issues/form.js @@ -17,7 +17,7 @@ export default () => { new MilestoneSelect(); new IssuableTemplateSelectors(); - if (gon.features.issueSuggestions && gon.features.graphql) { + if (gon.features.graphql) { initSuggestions(); } }; diff --git a/app/assets/javascripts/pages/projects/releases/index/index.js b/app/assets/javascripts/pages/projects/releases/index/index.js new file mode 100644 index 00000000000..c183fbb9610 --- /dev/null +++ b/app/assets/javascripts/pages/projects/releases/index/index.js @@ -0,0 +1,3 @@ +import initReleases from '~/releases'; + +document.addEventListener('DOMContentLoaded', initReleases); diff --git a/app/assets/javascripts/pages/root/index.js b/app/assets/javascripts/pages/root/index.js new file mode 100644 index 00000000000..09f8185d3b5 --- /dev/null +++ b/app/assets/javascripts/pages/root/index.js @@ -0,0 +1,5 @@ +// if the "projects dashboard" is a user's default dashboard, when they visit the +// instance root index, the dashboard will be served by the root controller instead +// of a dashboard controller. The root index redirects for all other default dashboards. + +import '../dashboard/projects/index'; diff --git a/app/assets/javascripts/pages/users/user_overview_block.js b/app/assets/javascripts/pages/users/user_overview_block.js index eec2b5ca8e5..e9ecec717d6 100644 --- a/app/assets/javascripts/pages/users/user_overview_block.js +++ b/app/assets/javascripts/pages/users/user_overview_block.js @@ -29,18 +29,21 @@ export default class UserOverviewBlock { render(data) { const { html, count } = data; - const contentList = document.querySelector(`${this.container} .overview-content-list`); + const containerEl = document.querySelector(this.container); + const contentList = containerEl.querySelector('.overview-content-list'); contentList.innerHTML += html; - const loadingEl = document.querySelector(`${this.container} .loading`); + const loadingEl = containerEl.querySelector('.loading'); if (count && count > 0) { - document.querySelector(`${this.container} .js-view-all`).classList.remove('hide'); + containerEl.querySelector('.js-view-all').classList.remove('hide'); } else { - document - .querySelector(`${this.container} .nothing-here-block`) - .classList.add('text-left', 'p-0'); + const nothingHereBlock = containerEl.querySelector('.nothing-here-block'); + + if (nothingHereBlock) { + nothingHereBlock.classList.add('text-left', 'p-0'); + } } loadingEl.classList.add('hide'); diff --git a/app/assets/javascripts/pages/users/user_tabs.js b/app/assets/javascripts/pages/users/user_tabs.js index aa537d4a43e..1c3fd58ca74 100644 --- a/app/assets/javascripts/pages/users/user_tabs.js +++ b/app/assets/javascripts/pages/users/user_tabs.js @@ -151,8 +151,10 @@ export default class UserTabs { loadTab(action, endpoint) { this.toggleLoading(true); + const params = action === 'projects' ? { skip_namespace: true } : {}; + return axios - .get(endpoint) + .get(endpoint, { params }) .then(({ data }) => { const tabSelector = `div#${action}`; this.$parentEl.find(tabSelector).html(data.html); @@ -188,7 +190,7 @@ export default class UserTabs { requestParams: { limit: 10 }, }); UserTabs.renderMostRecentBlocks('#js-overview .projects-block', { - requestParams: { limit: 10, skip_pagination: true }, + requestParams: { limit: 10, skip_pagination: true, skip_namespace: true, compact_mode: true }, }); this.loaded.overview = true; @@ -206,6 +208,8 @@ export default class UserTabs { loadActivityCalendar() { const $calendarWrap = this.$parentEl.find('.tab-pane.active .user-calendar'); + if (!$calendarWrap.length) return; + const calendarPath = $calendarWrap.data('calendarPath'); AjaxCache.retrieve(calendarPath) diff --git a/app/assets/javascripts/pipelines/components/pipeline_url.vue b/app/assets/javascripts/pipelines/components/pipeline_url.vue index 30a5bbf92ce..7d8863dff29 100644 --- a/app/assets/javascripts/pipelines/components/pipeline_url.vue +++ b/app/assets/javascripts/pipelines/components/pipeline_url.vue @@ -65,7 +65,7 @@ export default { v-if="pipeline.flags.latest" v-gl-tooltip class="js-pipeline-url-latest badge badge-success" - title="__('Latest pipeline for this branch')" + :title="__('Latest pipeline for this branch')" > latest </span> @@ -100,7 +100,7 @@ export default { <span v-if="pipeline.flags.merge_request" v-gl-tooltip - title="__('This pipeline is run in a merge request context')" + :title="__('This pipeline is run in a merge request context')" class="js-pipeline-url-mergerequest badge badge-info" > merge request diff --git a/app/assets/javascripts/releases/components/app.vue b/app/assets/javascripts/releases/components/app.vue new file mode 100644 index 00000000000..0ad5ee2915c --- /dev/null +++ b/app/assets/javascripts/releases/components/app.vue @@ -0,0 +1,82 @@ +<script> +import { mapState, mapActions } from 'vuex'; +import { GlLoadingIcon, GlEmptyState } from '@gitlab/ui'; +import ReleaseBlock from './release_block.vue'; + +export default { + name: 'ReleasesApp', + components: { + GlLoadingIcon, + GlEmptyState, + ReleaseBlock, + }, + props: { + projectId: { + type: String, + required: true, + }, + documentationLink: { + type: String, + required: true, + }, + illustrationPath: { + type: String, + required: true, + }, + }, + computed: { + ...mapState(['isLoading', 'releases', 'hasError']), + shouldRenderEmptyState() { + return !this.releases.length && !this.hasError && !this.isLoading; + }, + shouldRenderSuccessState() { + return this.releases.length && !this.isLoading && !this.hasError; + }, + }, + created() { + this.fetchReleases(this.projectId); + }, + methods: { + ...mapActions(['fetchReleases']), + }, +}; +</script> +<template> + <div class="prepend-top-default"> + <gl-loading-icon v-if="isLoading" :size="2" class="js-loading prepend-top-20" /> + + <gl-empty-state + v-else-if="shouldRenderEmptyState" + class="js-empty-state" + :title="__('Getting started with releases')" + :svg-path="illustrationPath" + :description=" + __( + 'Releases mark specific points in a project\'s development history, communicate information about the type of change, and deliver on prepared, often compiled, versions of the software to be reused elsewhere. Currently, releases can only be created through the API.', + ) + " + :primary-button-link="documentationLink" + :primary-button-text="__('Open Documentation')" + /> + + <div v-else-if="shouldRenderSuccessState" class="js-success-state"> + <release-block + v-for="(release, index) in releases" + :key="release.tag_name" + :release="release" + :class="{ 'linked-card': releases.length > 1 && index !== releases.length - 1 }" + /> + </div> + </div> +</template> +<style> +.linked-card::after { + width: 1px; + content: ' '; + border: 1px solid #e5e5e5; + height: 17px; + top: 100%; + position: absolute; + left: 32px; +} +</style> diff --git a/app/assets/javascripts/releases/components/release_block.vue b/app/assets/javascripts/releases/components/release_block.vue new file mode 100644 index 00000000000..34b97826cdb --- /dev/null +++ b/app/assets/javascripts/releases/components/release_block.vue @@ -0,0 +1,129 @@ +<script> +import _ from 'underscore'; +import { GlTooltipDirective, GlLink } from '@gitlab/ui'; +import Icon from '~/vue_shared/components/icon.vue'; +import UserAvatarLink from '~/vue_shared/components/user_avatar/user_avatar_link.vue'; +import timeagoMixin from '~/vue_shared/mixins/timeago'; +import { sprintf } from '../../locale'; + +export default { + name: 'ReleaseBlock', + components: { + GlLink, + Icon, + UserAvatarLink, + }, + directives: { + GlTooltip: GlTooltipDirective, + }, + mixins: [timeagoMixin], + props: { + release: { + type: Object, + required: true, + default: () => ({}), + }, + }, + computed: { + releasedTimeAgo() { + return sprintf('released %{time}', { + time: this.timeFormated(this.release.created_at), + }); + }, + userImageAltDescription() { + return this.author && this.author.username + ? sprintf("%{username}'s avatar", { username: this.author.username }) + : null; + }, + commit() { + return this.release.commit || {}; + }, + assets() { + return this.release.assets || {}; + }, + author() { + return this.release.author || {}; + }, + hasAuthor() { + return _.isEmpty(this.author); + }, + }, +}; +</script> +<template> + <div class="card"> + <div class="card-body"> + <h2 class="card-title mt-0">{{ release.name }}</h2> + + <div class="card-subtitle d-flex flex-wrap text-secondary"> + <div class="append-right-8"> + <icon name="commit" class="align-middle" /> + <span v-gl-tooltip.bottom :title="commit.title">{{ commit.short_id }}</span> + </div> + + <div class="append-right-8"> + <icon name="tag" class="align-middle" /> + <span v-gl-tooltip.bottom :title="__('Tag')">{{ release.tag_name }}</span> + </div> + + <div class="append-right-4"> + • + <span v-gl-tooltip.bottom :title="tooltipTitle(release.created_at)">{{ + releasedTimeAgo + }}</span> + </div> + + <div v-if="hasAuthor" class="d-flex"> + by + <user-avatar-link + class="prepend-left-4" + :link-href="author.path" + :img-src="author.avatar_url" + :img-alt="userImageAltDescription" + :tooltip-text="author.username" + /> + </div> + </div> + + <div + v-if="assets.links.length || assets.sources.length" + Sclass="card-text prepend-top-default" + > + <b> + {{ __('Assets') }} + <span class="js-assets-count badge badge-pill">{{ assets.count }}</span> + </b> + + <ul v-if="assets.links.length" class="pl-0 mb-0 prepend-top-8 list-unstyled js-assets-list"> + <li v-for="link in assets.links" :key="link.name" class="append-bottom-8"> + <gl-link v-gl-tooltip.bottom :title="__('Download asset')" :href="link.url"> + <icon name="package" class="align-middle append-right-4 align-text-bottom" /> + {{ link.name }} + </gl-link> + </li> + </ul> + + <div v-if="assets.sources.length" class="dropdown"> + <button + type="button" + class="btn btn-link" + data-toggle="dropdown" + aria-haspopup="true" + aria-expanded="false" + > + <icon name="doc-code" class="align-top append-right-4" /> {{ __('Source code') }} + <icon name="arrow-down" /> + </button> + + <div class="js-sources-dropdown dropdown-menu"> + <li v-for="asset in assets.sources" :key="asset.url"> + <gl-link :href="asset.url">{{ __('Download') }} {{ asset.format }}</gl-link> + </li> + </div> + </div> + </div> + + <div class="card-text prepend-top-default"><div v-html="release.description_html"></div></div> + </div> + </div> +</template> diff --git a/app/assets/javascripts/releases/index.js b/app/assets/javascripts/releases/index.js new file mode 100644 index 00000000000..adbed3cb8e2 --- /dev/null +++ b/app/assets/javascripts/releases/index.js @@ -0,0 +1,24 @@ +import Vue from 'vue'; +import App from './components/app.vue'; +import createStore from './store'; + +export default () => { + const element = document.getElementById('js-releases-page'); + + return new Vue({ + el: element, + store: createStore(), + components: { + App, + }, + render(createElement) { + return createElement('app', { + props: { + projectId: element.dataset.projectId, + documentationLink: element.dataset.documentationPath, + illustrationPath: element.dataset.illustrationPath, + }, + }); + }, + }); +}; diff --git a/app/assets/javascripts/releases/store/actions.js b/app/assets/javascripts/releases/store/actions.js new file mode 100644 index 00000000000..baa2251403e --- /dev/null +++ b/app/assets/javascripts/releases/store/actions.js @@ -0,0 +1,37 @@ +import * as types from './mutation_types'; +import createFlash from '~/flash'; +import { __ } from '~/locale'; +import api from '~/api'; + +/** + * Commits a mutation to update the state while the main endpoint is being requested. + */ +export const requestReleases = ({ commit }) => commit(types.REQUEST_RELEASES); + +/** + * Fetches the main endpoint. + * Will dispatch requestNamespace action before starting the request. + * Will dispatch receiveNamespaceSuccess if the request is successfull + * Will dispatch receiveNamesapceError if the request returns an error + * + * @param {String} projectId + */ +export const fetchReleases = ({ dispatch }, projectId) => { + dispatch('requestReleases'); + + api + .releases(projectId) + .then(({ data }) => dispatch('receiveReleasesSuccess', data)) + .catch(() => dispatch('receiveReleasesError')); +}; + +export const receiveReleasesSuccess = ({ commit }, data) => + commit(types.RECEIVE_RELEASES_SUCCESS, data); + +export const receiveReleasesError = ({ commit }) => { + commit(types.RECEIVE_RELEASES_ERROR); + createFlash(__('An error occured while fetching the releases. Please try again.')); +}; + +// prevent babel-plugin-rewire from generating an invalid default during karma tests +export default () => {}; diff --git a/app/assets/javascripts/releases/store/index.js b/app/assets/javascripts/releases/store/index.js new file mode 100644 index 00000000000..968b94f0e0d --- /dev/null +++ b/app/assets/javascripts/releases/store/index.js @@ -0,0 +1,14 @@ +import Vue from 'vue'; +import Vuex from 'vuex'; +import state from './state'; +import * as actions from './actions'; +import mutations from './mutations'; + +Vue.use(Vuex); + +export default () => + new Vuex.Store({ + actions, + mutations, + state: state(), + }); diff --git a/app/assets/javascripts/releases/store/mutation_types.js b/app/assets/javascripts/releases/store/mutation_types.js new file mode 100644 index 00000000000..a74bf15c515 --- /dev/null +++ b/app/assets/javascripts/releases/store/mutation_types.js @@ -0,0 +1,3 @@ +export const REQUEST_RELEASES = 'REQUEST_RELEASES'; +export const RECEIVE_RELEASES_SUCCESS = 'RECEIVE_RELEASES_SUCCESS'; +export const RECEIVE_RELEASES_ERROR = 'RECEIVE_RELEASES_ERROR'; diff --git a/app/assets/javascripts/releases/store/mutations.js b/app/assets/javascripts/releases/store/mutations.js new file mode 100644 index 00000000000..b97dc6cb0ab --- /dev/null +++ b/app/assets/javascripts/releases/store/mutations.js @@ -0,0 +1,37 @@ +import * as types from './mutation_types'; + +export default { + /** + * Sets isLoading to true while the request is being made. + * @param {Object} state + */ + [types.REQUEST_RELEASES](state) { + state.isLoading = true; + }, + + /** + * Sets isLoading to false. + * Sets hasError to false. + * Sets the received data + * @param {Object} state + * @param {Object} data + */ + [types.RECEIVE_RELEASES_SUCCESS](state, data) { + state.hasError = false; + state.isLoading = false; + state.releases = data; + }, + + /** + * Sets isLoading to false. + * Sets hasError to true. + * Resets the data + * @param {Object} state + * @param {Object} data + */ + [types.RECEIVE_RELEASES_ERROR](state) { + state.isLoading = false; + state.releases = []; + state.hasError = true; + }, +}; diff --git a/app/assets/javascripts/releases/store/state.js b/app/assets/javascripts/releases/store/state.js new file mode 100644 index 00000000000..bf25e651c99 --- /dev/null +++ b/app/assets/javascripts/releases/store/state.js @@ -0,0 +1,5 @@ +export default () => ({ + isLoading: false, + hasError: false, + releases: [], +}); diff --git a/app/assets/javascripts/right_sidebar.js b/app/assets/javascripts/right_sidebar.js index 225e21ad322..9a0cdc02952 100644 --- a/app/assets/javascripts/right_sidebar.js +++ b/app/assets/javascripts/right_sidebar.js @@ -79,11 +79,12 @@ Sidebar.prototype.sidebarToggleClicked = function(e, triggered) { Sidebar.prototype.toggleTodo = function(e) { var $btnText, $this, $todoLoading, ajaxType, url; $this = $(e.currentTarget); - ajaxType = $this.attr('data-delete-path') ? 'delete' : 'post'; - if ($this.attr('data-delete-path')) { - url = '' + $this.attr('data-delete-path'); + ajaxType = $this.data('deletePath') ? 'delete' : 'post'; + + if ($this.data('deletePath')) { + url = '' + $this.data('deletePath'); } else { - url = '' + $this.data('url'); + url = '' + $this.data('createPath'); } $this.tooltip('hide'); @@ -119,14 +120,14 @@ Sidebar.prototype.todoUpdateDone = function(data) { .removeClass('is-loading') .enable() .attr('aria-label', $el.data(`${attrPrefix}Text`)) - .attr('data-delete-path', deletePath) - .attr('title', $el.data(`${attrPrefix}Text`)); + .attr('title', $el.data(`${attrPrefix}Text`)) + .data('deletePath', deletePath); if ($el.hasClass('has-tooltip')) { $el.tooltip('_fixTitle'); } - if ($el.data(`${attrPrefix}Icon`)) { + if (typeof $el.data('isCollapsed') !== 'undefined') { $elText.html($el.data(`${attrPrefix}Icon`)); } else { $elText.text($el.data(`${attrPrefix}Text`)); diff --git a/app/assets/javascripts/serverless/components/functions.vue b/app/assets/javascripts/serverless/components/functions.vue index 7874a7b6b6a..349e14670b1 100644 --- a/app/assets/javascripts/serverless/components/functions.vue +++ b/app/assets/javascripts/serverless/components/functions.vue @@ -81,7 +81,7 @@ export default { </p> <ul> <li>Your repository does not have a corresponding <code>serverless.yml</code> file.</li> - <li>Your <code>gitlab-ci.yml</code> file is not properly configured.</li> + <li>Your <code>.gitlab-ci.yml</code> file is not properly configured.</li> <li> The functions listed in the <code>serverless.yml</code> file don't match the namespace of your cluster. diff --git a/app/assets/javascripts/set_status_modal/emoji_menu_in_modal.js b/app/assets/javascripts/set_status_modal/emoji_menu_in_modal.js index 14a89ef9293..3a8631a196f 100644 --- a/app/assets/javascripts/set_status_modal/emoji_menu_in_modal.js +++ b/app/assets/javascripts/set_status_modal/emoji_menu_in_modal.js @@ -12,9 +12,8 @@ class EmojiMenuInModal extends AwardsHandler { this.bindEvents(); } - postEmoji($emojiButton, awardUrl, selectedEmoji, callback) { + postEmoji($emojiButton, awardUrl, selectedEmoji) { this.selectEmojiCallback(selectedEmoji, this.emoji.glEmojiTag(selectedEmoji)); - callback(); } } diff --git a/app/assets/javascripts/sidebar/stores/sidebar_store.js b/app/assets/javascripts/sidebar/stores/sidebar_store.js index f20cc6d8cca..7b8b4c5d856 100644 --- a/app/assets/javascripts/sidebar/stores/sidebar_store.js +++ b/app/assets/javascripts/sidebar/stores/sidebar_store.js @@ -71,7 +71,7 @@ export default class SidebarStore { } findAssignee(findAssignee) { - return this.assignees.filter(assignee => assignee.id === findAssignee.id)[0]; + return this.assignees.find(assignee => assignee.id === findAssignee.id); } removeAssignee(removeAssignee) { diff --git a/app/assets/javascripts/star.js b/app/assets/javascripts/star.js index 9af5d5b23cb..7404dfbf22a 100644 --- a/app/assets/javascripts/star.js +++ b/app/assets/javascripts/star.js @@ -5,11 +5,12 @@ import { spriteIcon } from './lib/utils/common_utils'; import axios from './lib/utils/axios_utils'; export default class Star { - constructor() { - $('.project-home-panel .toggle-star').on('click', function toggleStarClickCallback() { + constructor(container = '.project-home-panel') { + $(`${container} .toggle-star`).on('click', function toggleStarClickCallback() { const $this = $(this); const $starSpan = $this.find('span'); - const $startIcon = $this.find('svg'); + const $starIcon = $this.find('svg'); + const iconClasses = $starIcon.attr('class').split(' '); axios .post($this.data('endpoint')) @@ -22,12 +23,12 @@ export default class Star { if (isStarred) { $starSpan.removeClass('starred').text(s__('StarProject|Star')); - $startIcon.remove(); - $this.prepend(spriteIcon('star-o', 'icon')); + $starIcon.remove(); + $this.prepend(spriteIcon('star-o', iconClasses)); } else { $starSpan.addClass('starred').text(__('Unstar')); - $startIcon.remove(); - $this.prepend(spriteIcon('star', 'icon')); + $starIcon.remove(); + $this.prepend(spriteIcon('star', iconClasses)); } }) .catch(() => Flash('Star toggle failed. Try again later.')); diff --git a/app/assets/javascripts/vue_merge_request_widget/components/mr_widget_icon.vue b/app/assets/javascripts/vue_merge_request_widget/components/mr_widget_icon.vue index e3adc7f7af5..4b57693e8f1 100644 --- a/app/assets/javascripts/vue_merge_request_widget/components/mr_widget_icon.vue +++ b/app/assets/javascripts/vue_merge_request_widget/components/mr_widget_icon.vue @@ -13,5 +13,7 @@ export default { </script> <template> - <div class="circle-icon-container append-right-default"><icon :name="name" /></div> + <div class="circle-icon-container append-right-default align-self-start align-self-lg-center"> + <icon :name="name" /> + </div> </template> diff --git a/app/assets/javascripts/vue_merge_request_widget/components/states/mr_widget_rebase.vue b/app/assets/javascripts/vue_merge_request_widget/components/states/mr_widget_rebase.vue index adfbcd18588..0bcccc50eb2 100644 --- a/app/assets/javascripts/vue_merge_request_widget/components/states/mr_widget_rebase.vue +++ b/app/assets/javascripts/vue_merge_request_widget/components/states/mr_widget_rebase.vue @@ -72,7 +72,7 @@ export default { Flash('Something went wrong. Please try again.'); } - eventHub.$emit('MRWidgetUpdateRequested'); + eventHub.$emit('MRWidgetRebaseSuccess'); stopPolling(); } }) diff --git a/app/assets/javascripts/vue_merge_request_widget/mr_widget_options.vue b/app/assets/javascripts/vue_merge_request_widget/mr_widget_options.vue index 3c3e3efcc36..b7f12076958 100644 --- a/app/assets/javascripts/vue_merge_request_widget/mr_widget_options.vue +++ b/app/assets/javascripts/vue_merge_request_widget/mr_widget_options.vue @@ -106,6 +106,9 @@ export default { (!this.mr.isNothingToMergeState && !this.mr.isMergedState) ); }, + shouldRenderCollaborationStatus() { + return this.mr.allowCollaboration && this.mr.isOpen; + }, shouldRenderMergedPipeline() { return this.mr.state === 'merged' && !_.isEmpty(this.mr.mergePipeline); }, @@ -155,13 +158,13 @@ export default { }; return new MRWidgetService(endpoints); }, - checkStatus(cb) { + checkStatus(cb, isRebased) { return this.service .checkStatus() .then(res => res.data) .then(data => { this.handleNotification(data); - this.mr.setData(data); + this.mr.setData(data, isRebased); this.setFaviconHelper(); if (cb) { @@ -263,6 +266,10 @@ export default { this.checkStatus(cb); }); + eventHub.$on('MRWidgetRebaseSuccess', cb => { + this.checkStatus(cb, true); + }); + // `params` should be an Array contains a Boolean, like `[true]` // Passing parameter as Boolean didn't work. eventHub.$on('SetBranchRemoveFlag', params => { @@ -311,7 +318,7 @@ export default { <div class="mr-widget-section"> <component :is="componentName" :mr="mr" :service="service" /> - <section v-if="mr.allowCollaboration" class="mr-info-list mr-links"> + <section v-if="shouldRenderCollaborationStatus" class="mr-info-list mr-links"> {{ s__('mrWidget|Allows commits from members who can merge to the target branch') }} </section> diff --git a/app/assets/javascripts/vue_merge_request_widget/stores/get_state_key.js b/app/assets/javascripts/vue_merge_request_widget/stores/get_state_key.js index f7f0c1b6cb7..066a3b833d7 100644 --- a/app/assets/javascripts/vue_merge_request_widget/stores/get_state_key.js +++ b/app/assets/javascripts/vue_merge_request_widget/stores/get_state_key.js @@ -19,7 +19,7 @@ export default function deviseState(data) { return stateKey.unresolvedDiscussions; } else if (this.isPipelineBlocked) { return stateKey.pipelineBlocked; - } else if (this.hasSHAChanged) { + } else if (this.isSHAMismatch) { return stateKey.shaMismatch; } else if (this.mergeWhenPipelineSucceeds) { return this.mergeError ? stateKey.autoMergeFailed : stateKey.mergeWhenPipelineSucceeds; 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 5c9a7133a6e..c777bcca0fa 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 @@ -11,7 +11,11 @@ export default class MergeRequestStore { this.setData(data); } - setData(data) { + setData(data, isRebased) { + if (isRebased) { + this.sha = data.diff_head_sha; + } + const currentUser = data.current_user; const pipelineStatus = data.pipeline ? data.pipeline.details.status : null; @@ -84,7 +88,7 @@ export default class MergeRequestStore { this.canMerge = !!data.merge_path; this.canCreateIssue = currentUser.can_create_issue || false; this.canCancelAutomaticMerge = !!data.cancel_merge_when_pipeline_succeeds_path; - this.hasSHAChanged = this.sha !== data.diff_head_sha; + this.isSHAMismatch = this.sha !== data.diff_head_sha; this.canBeMerged = data.can_be_merged || false; this.isMergeAllowed = data.mergeable || false; this.mergeOngoing = data.merge_ongoing; diff --git a/app/assets/javascripts/vue_shared/components/callout.vue b/app/assets/javascripts/vue_shared/components/callout.vue index ddbb14ae812..56bafebf4ce 100644 --- a/app/assets/javascripts/vue_shared/components/callout.vue +++ b/app/assets/javascripts/vue_shared/components/callout.vue @@ -11,13 +11,14 @@ export default { }, message: { type: String, - required: true, + required: false, + default: '', }, }, }; </script> <template> <div :class="`bs-callout bs-callout-${category}`" role="alert" aria-live="assertive"> - {{ message }} + {{ message }} <slot></slot> </div> </template> diff --git a/app/assets/javascripts/vue_shared/components/diff_viewer/viewers/empty_file.vue b/app/assets/javascripts/vue_shared/components/diff_viewer/viewers/empty_file.vue new file mode 100644 index 00000000000..53210cbcc93 --- /dev/null +++ b/app/assets/javascripts/vue_shared/components/diff_viewer/viewers/empty_file.vue @@ -0,0 +1,3 @@ +<template> + <div class="nothing-here-block">{{ __('Empty file') }}</div> +</template> diff --git a/app/assets/javascripts/vue_shared/components/markdown/field.vue b/app/assets/javascripts/vue_shared/components/markdown/field.vue index 43def2673eb..2f7ed4a982c 100644 --- a/app/assets/javascripts/vue_shared/components/markdown/field.vue +++ b/app/assets/javascripts/vue_shared/components/markdown/field.vue @@ -1,17 +1,21 @@ <script> import $ from 'jquery'; +import _ from 'underscore'; import { __ } from '~/locale'; +import { stripHtml } from '~/lib/utils/text_utility'; import Flash from '../../../flash'; import GLForm from '../../../gl_form'; import markdownHeader from './header.vue'; import markdownToolbar from './toolbar.vue'; import icon from '../icon.vue'; +import Suggestions from '~/vue_shared/components/markdown/suggestions.vue'; export default { components: { markdownHeader, markdownToolbar, icon, + Suggestions, }, props: { markdownPreviewPath: { @@ -48,12 +52,33 @@ export default { required: false, default: true, }, + line: { + type: Object, + required: false, + default: null, + }, + note: { + type: Object, + required: false, + default: () => ({}), + }, + canSuggest: { + type: Boolean, + required: false, + default: false, + }, + helpPagePath: { + type: String, + required: false, + default: '', + }, }, data() { return { markdownPreview: '', referencedCommands: '', referencedUsers: '', + hasSuggestion: false, markdownPreviewLoading: false, previewMarkdown: false, }; @@ -63,6 +88,39 @@ export default { const referencedUsersThreshold = 10; return this.referencedUsers.length >= referencedUsersThreshold; }, + lineContent() { + const FIRST_CHAR_REGEX = /^(\+|-)/; + const [firstSuggestion] = this.suggestions; + if (firstSuggestion) { + return firstSuggestion.from_content; + } + + if (this.line) { + const { rich_text: richText, text } = this.line; + + if (text) { + return text.replace(FIRST_CHAR_REGEX, ''); + } + + return _.unescape(stripHtml(richText).replace(/\n/g, '')); + } + + return ''; + }, + lineNumber() { + let lineNumber; + if (this.line) { + const { new_line: newLine, old_line: oldLine } = this.line; + lineNumber = newLine || oldLine; + } + return lineNumber; + }, + suggestions() { + return this.note.suggestions || []; + }, + lineType() { + return this.line ? this.line.type : ''; + }, }, mounted() { /* @@ -122,6 +180,7 @@ export default { if (data.references) { this.referencedCommands = data.references.commands; this.referencedUsers = data.references.users; + this.hasSuggestion = data.references.suggestions && data.references.suggestions.length; } this.$nextTick(() => { @@ -147,6 +206,8 @@ export default { > <markdown-header :preview-markdown="previewMarkdown" + :line-content="lineContent" + :can-suggest="canSuggest" @preview-markdown="showPreviewTab" @write-markdown="showWriteTab" /> @@ -163,19 +224,39 @@ export default { /> </div> </div> - <div - v-show="previewMarkdown" - ref="markdown-preview" - class="md-preview js-vue-md-preview md md-preview-holder" - v-html="markdownPreview" - ></div> + <template v-if="hasSuggestion"> + <div + v-show="previewMarkdown" + ref="markdown-preview" + class="md-preview js-vue-md-preview md md-preview-holder" + > + <suggestions + v-if="hasSuggestion" + :note-html="markdownPreview" + :from-line="lineNumber" + :from-content="lineContent" + :line-type="lineType" + :disabled="true" + :suggestions="suggestions" + :help-page-path="helpPagePath" + /> + </div> + </template> + <template v-else> + <div + v-show="previewMarkdown" + ref="markdown-preview" + class="md-preview js-vue-md-preview md md-preview-holder" + v-html="markdownPreview" + ></div> + </template> <template v-if="previewMarkdown && !markdownPreviewLoading"> <div v-if="referencedCommands" class="referenced-commands" v-html="referencedCommands"></div> <div v-if="shouldShowReferencedUsers" class="referenced-users"> <span> - <i class="fa fa-exclamation-triangle" aria-hidden="true"> </i> You are about to add + <i class="fa fa-exclamation-triangle" aria-hidden="true"></i> You are about to add <strong> - <span class="js-referenced-users-count"> {{ referencedUsers.length }} </span> + <span class="js-referenced-users-count">{{ referencedUsers.length }}</span> </strong> people to the discussion. Proceed with caution. </span> diff --git a/app/assets/javascripts/vue_shared/components/markdown/header.vue b/app/assets/javascripts/vue_shared/components/markdown/header.vue index 4c4ba537065..bf4d42670ee 100644 --- a/app/assets/javascripts/vue_shared/components/markdown/header.vue +++ b/app/assets/javascripts/vue_shared/components/markdown/header.vue @@ -17,6 +17,16 @@ export default { type: Boolean, required: true, }, + lineContent: { + type: String, + required: false, + default: '', + }, + canSuggest: { + type: Boolean, + required: false, + default: true, + }, }, computed: { mdTable() { @@ -27,6 +37,9 @@ export default { '| cell | cell |', ].join('\n'); }, + mdSuggestion() { + return ['```suggestion', `{text}`, '```'].join('\n'); + }, }, mounted() { $(document).on('markdown-preview:show.vue', this.previewMarkdownTab); @@ -119,6 +132,16 @@ export default { :button-title="__('Add a table')" icon="table" /> + <toolbar-button + v-if="canSuggest" + :tag="mdSuggestion" + :prepend="true" + :button-title="__('Insert suggestion')" + :cursor-offset="4" + :tag-content="lineContent" + icon="doc-code" + class="qa-suggestion-btn" + /> <button v-gl-tooltip aria-label="Go full screen" diff --git a/app/assets/javascripts/vue_shared/components/markdown/suggestion_diff.vue b/app/assets/javascripts/vue_shared/components/markdown/suggestion_diff.vue new file mode 100644 index 00000000000..f98560f7336 --- /dev/null +++ b/app/assets/javascripts/vue_shared/components/markdown/suggestion_diff.vue @@ -0,0 +1,74 @@ +<script> +import SuggestionDiffHeader from './suggestion_diff_header.vue'; + +export default { + components: { + SuggestionDiffHeader, + }, + props: { + newLines: { + type: Array, + required: true, + }, + fromContent: { + type: String, + required: false, + default: '', + }, + fromLine: { + type: Number, + required: true, + }, + suggestion: { + type: Object, + required: true, + }, + disabled: { + type: Boolean, + required: false, + default: false, + }, + helpPagePath: { + type: String, + required: true, + }, + }, + methods: { + applySuggestion(callback) { + this.$emit('apply', { suggestionId: this.suggestion.id, callback }); + }, + }, +}; +</script> + +<template> + <div> + <suggestion-diff-header + class="qa-suggestion-diff-header" + :can-apply="suggestion.appliable && suggestion.current_user.can_apply && !disabled" + :is-applied="suggestion.applied" + :help-page-path="helpPagePath" + @apply="applySuggestion" + /> + <table class="mb-3 md-suggestion-diff"> + <tbody> + <!-- Old Line --> + <tr class="line_holder old"> + <td class="diff-line-num old_line qa-old-diff-line-number old">{{ fromLine }}</td> + <td class="diff-line-num new_line old"></td> + <td class="line_content old"> + <span>{{ fromContent }}</span> + </td> + </tr> + <!-- New Line(s) --> + <tr v-for="(line, key) of newLines" :key="key" class="line_holder new"> + <td class="diff-line-num old_line new"></td> + <td class="diff-line-num new_line qa-new-diff-line-number new">{{ line.lineNumber }}</td> + <td class="line_content new"> + <span>{{ line.content }}</span> + </td> + </tr> + </tbody> + </table> + </div> +</template> diff --git a/app/assets/javascripts/vue_shared/components/markdown/suggestion_diff_header.vue b/app/assets/javascripts/vue_shared/components/markdown/suggestion_diff_header.vue new file mode 100644 index 00000000000..563e2f94fcc --- /dev/null +++ b/app/assets/javascripts/vue_shared/components/markdown/suggestion_diff_header.vue @@ -0,0 +1,60 @@ +<script> +import Icon from '~/vue_shared/components/icon.vue'; + +export default { + components: { Icon }, + props: { + canApply: { + type: Boolean, + required: false, + default: false, + }, + isApplied: { + type: Boolean, + required: true, + default: false, + }, + helpPagePath: { + type: String, + required: true, + }, + }, + data() { + return { + isAppliedSuccessfully: false, + isApplying: false, + }; + }, + methods: { + applySuggestion() { + if (!this.canApply) return; + this.isApplying = true; + this.$emit('apply', this.applySuggestionCallback); + }, + applySuggestionCallback() { + this.isApplying = false; + }, + }, +}; +</script> + +<template> + <div class="md-suggestion-header border-bottom-0 mt-2"> + <div class="qa-suggestion-diff-header font-weight-bold"> + {{ __('Suggested change') }} + <a v-if="helpPagePath" :href="helpPagePath" :aria-label="__('Help')"> + <icon name="question-o" css-classes="link-highlight" /> + </a> + </div> + <span v-if="isApplied" class="badge badge-success">{{ __('Applied') }}</span> + <button + v-if="canApply" + type="button" + class="btn qa-apply-btn" + :disabled="isApplying" + @click="applySuggestion" + > + {{ __('Apply suggestion') }} + </button> + </div> +</template> diff --git a/app/assets/javascripts/vue_shared/components/markdown/suggestions.vue b/app/assets/javascripts/vue_shared/components/markdown/suggestions.vue new file mode 100644 index 00000000000..7c6dbee3e19 --- /dev/null +++ b/app/assets/javascripts/vue_shared/components/markdown/suggestions.vue @@ -0,0 +1,136 @@ +<script> +import Vue from 'vue'; +import SuggestionDiff from './suggestion_diff.vue'; +import Flash from '~/flash'; + +export default { + components: { SuggestionDiff }, + props: { + fromLine: { + type: Number, + required: false, + default: 0, + }, + fromContent: { + type: String, + required: false, + default: '', + }, + lineType: { + type: String, + required: false, + default: '', + }, + suggestions: { + type: Array, + required: false, + default: () => [], + }, + noteHtml: { + type: String, + required: true, + }, + disabled: { + type: Boolean, + required: false, + default: false, + }, + helpPagePath: { + type: String, + required: true, + }, + }, + data() { + return { + isRendered: false, + }; + }, + watch: { + suggestions() { + this.reset(); + }, + noteHtml() { + this.reset(); + }, + }, + mounted() { + this.renderSuggestions(); + }, + methods: { + renderSuggestions() { + // swaps out suggestion(s) markdown with rich diff components + // (while still keeping non-suggestion markdown in place) + + if (!this.noteHtml) return; + const { container } = this.$refs; + const suggestionElements = container.querySelectorAll('.js-render-suggestion'); + + if (this.lineType === 'old') { + Flash('Unable to apply suggestions to a deleted line.', 'alert', this.$el); + } + + suggestionElements.forEach((suggestionEl, i) => { + const suggestionParentEl = suggestionEl.parentElement; + const newLines = this.extractNewLines(suggestionParentEl); + const diffComponent = this.generateDiff(newLines, i); + diffComponent.$mount(suggestionParentEl); + }); + + this.isRendered = true; + }, + extractNewLines(suggestionEl) { + // extracts the suggested lines from the markdown + // calculates a line number for each line + + const FIRST_CHAR_REGEX = /^(\+|-)/; + const newLines = suggestionEl.querySelectorAll('.line'); + const fromLine = this.suggestions.length ? this.suggestions[0].from_line : this.fromLine; + const lines = []; + + newLines.forEach((line, i) => { + const content = `${line.innerText.replace(FIRST_CHAR_REGEX, '')}\n`; + const lineNumber = fromLine + i; + lines.push({ content, lineNumber }); + }); + + return lines; + }, + generateDiff(newLines, suggestionIndex) { + // generates the diff <suggestion-diff /> component + // all `suggestion` markdown will be swapped out by this component + + const { suggestions, disabled, helpPagePath } = this; + const suggestion = + suggestions && suggestions[suggestionIndex] ? suggestions[suggestionIndex] : {}; + const fromContent = suggestion.from_content || this.fromContent; + const fromLine = suggestion.from_line || this.fromLine; + const SuggestionDiffComponent = Vue.extend(SuggestionDiff); + const suggestionDiff = new SuggestionDiffComponent({ + propsData: { newLines, fromLine, fromContent, disabled, suggestion, helpPagePath }, + }); + + suggestionDiff.$on('apply', ({ suggestionId, callback }) => { + this.$emit('apply', { suggestionId, callback, flashContainer: this.$el }); + }); + + return suggestionDiff; + }, + reset() { + // resets the container HTML (replaces it with the updated noteHTML) + // calls `renderSuggestions` once the updated noteHTML is added to the DOM + + this.$refs.container.innerHTML = this.noteHtml; + this.isRendered = false; + this.renderSuggestions(); + this.$nextTick(() => this.renderSuggestions()); + }, + }, +}; +</script> + +<template> + <div> + <div class="flash-container mt-3"></div> + <div v-show="isRendered" ref="container" v-html="noteHtml"></div> + </div> +</template> diff --git a/app/assets/javascripts/vue_shared/components/markdown/toolbar_button.vue b/app/assets/javascripts/vue_shared/components/markdown/toolbar_button.vue index a6d2cecdf7e..4572caa907b 100644 --- a/app/assets/javascripts/vue_shared/components/markdown/toolbar_button.vue +++ b/app/assets/javascripts/vue_shared/components/markdown/toolbar_button.vue @@ -37,6 +37,16 @@ export default { required: false, default: false, }, + tagContent: { + type: String, + required: false, + default: '', + }, + cursorOffset: { + type: Number, + required: false, + default: 0, + }, }, }; </script> @@ -45,8 +55,10 @@ export default { <button v-gl-tooltip :data-md-tag="tag" + :data-md-cursor-offset="cursorOffset" :data-md-select="tagSelect" :data-md-block="tagBlock" + :data-md-tag-content="tagContent" :data-md-prepend="prepend" :title="buttonTitle" :aria-label="buttonTitle" diff --git a/app/assets/javascripts/vue_shared/components/user_popover/user_popover.vue b/app/assets/javascripts/vue_shared/components/user_popover/user_popover.vue index 7fbadcc0111..d24fe1b547e 100644 --- a/app/assets/javascripts/vue_shared/components/user_popover/user_popover.vue +++ b/app/assets/javascripts/vue_shared/components/user_popover/user_popover.vue @@ -1,6 +1,5 @@ <script> import { GlPopover, GlSkeletonLoading } from '@gitlab/ui'; -import { __, sprintf } from '~/locale'; import UserAvatarImage from '../user_avatar/user_avatar_image.vue'; import { glEmojiTag } from '../../../emoji'; @@ -28,19 +27,6 @@ export default { }, }, computed: { - jobLine() { - if (this.user.bio && this.user.organization) { - return sprintf(__('%{bio} at %{organization}'), { - bio: this.user.bio, - organization: this.user.organization, - }); - } else if (this.user.bio) { - return this.user.bio; - } else if (this.user.organization) { - return this.user.organization; - } - return null; - }, statusHtml() { if (this.user.status.emoji && this.user.status.message) { return `${glEmojiTag(this.user.status.emoji)} ${this.user.status.message}`; @@ -82,7 +68,8 @@ export default { <gl-skeleton-loading v-else :lines="1" class="animation-container-small mb-1" /> </div> <div class="text-secondary"> - {{ jobLine }} + <div v-if="user.bio" class="js-bio">{{ user.bio }}</div> + <div v-if="user.organization" class="js-organization">{{ user.organization }}</div> <gl-skeleton-loading v-if="jobInfoIsLoading" :lines="1" diff --git a/app/assets/stylesheets/application.scss b/app/assets/stylesheets/application.scss index 985fac11c87..bdf20866197 100644 --- a/app/assets/stylesheets/application.scss +++ b/app/assets/stylesheets/application.scss @@ -47,6 +47,7 @@ @import "highlight/solarized_dark"; @import "highlight/solarized_light"; @import "highlight/white"; +@import "highlight/none"; /* * Styles for JS behaviors. diff --git a/app/assets/stylesheets/bootstrap_migration.scss b/app/assets/stylesheets/bootstrap_migration.scss index f0671e36130..587127bb059 100644 --- a/app/assets/stylesheets/bootstrap_migration.scss +++ b/app/assets/stylesheets/bootstrap_migration.scss @@ -70,6 +70,17 @@ h6, margin-bottom: 10px; } +/* Our adjustments to hx & .hx above add unnecessary margins to modal-title + and page-title in modals, so we set them to 0 in order to have properly + formatted modal headers. */ +.modal-header { + .modal-title, + .page-title { + margin-top: 0; + margin-bottom: 0; + } +} + h5, .h5 { font-size: $gl-font-size; @@ -134,7 +145,8 @@ table { pointer-events: none; } -.popover { +.popover, +.popover-header { font-size: 14px; } @@ -142,7 +154,9 @@ table { @include media-breakpoint-up($breakpoint) { $infix: breakpoint-infix($breakpoint, $grid-breakpoints); - .d#{$infix}-table-header-group { display: table-header-group !important; } + .d#{$infix}-table-header-group { + display: table-header-group !important; + } } } diff --git a/app/assets/stylesheets/framework/animations.scss b/app/assets/stylesheets/framework/animations.scss index 549a8730301..43d4044033f 100644 --- a/app/assets/stylesheets/framework/animations.scss +++ b/app/assets/stylesheets/framework/animations.scss @@ -260,3 +260,25 @@ $skeleton-line-widths: ( .slide-down-leave-to { transform: translateY(-30%); } + +@keyframes spin { + 0% { transform: rotate(0deg);} + 100% { transform: rotate(360deg);} +} + +/** COMMON ANIMATION CLASSES **/ +.transform-origin-center { @include webkit-prefix(transform-origin, 50% 50%); } +.animate-n-spin { @include webkit-prefix(animation-name, spin); } +.animate-c-infinite { @include webkit-prefix(animation-iteration-count, infinite); } +.animate-t-linear { @include webkit-prefix(animation-timing-function, linear); } +.animate-d-1 { @include webkit-prefix(animation-duration, 1s); } +.animate-d-2 { @include webkit-prefix(animation-duration, 2s); } + +/** COMPOSITE ANIMATION CLASSES **/ +.gl-spinner { + @include webkit-prefix(animation-name, spin); + @include webkit-prefix(animation-iteration-count, infinite); + @include webkit-prefix(animation-timing-function, linear); + @include webkit-prefix(animation-duration, 1s); + transform-origin: 50% 50%; +} diff --git a/app/assets/stylesheets/framework/avatar.scss b/app/assets/stylesheets/framework/avatar.scss index 054c75912ea..e132aa4c216 100644 --- a/app/assets/stylesheets/framework/avatar.scss +++ b/app/assets/stylesheets/framework/avatar.scss @@ -108,6 +108,7 @@ width: 100%; height: 100%; display: flex; + text-decoration: none; } .avatar { @@ -120,6 +121,7 @@ } &.s40 { min-width: 40px; min-height: 40px; } + &.s64 { min-width: 64px; min-height: 64px; } } .avatar-counter { diff --git a/app/assets/stylesheets/framework/buttons.scss b/app/assets/stylesheets/framework/buttons.scss index e36f99ac577..a4a9276c580 100644 --- a/app/assets/stylesheets/framework/buttons.scss +++ b/app/assets/stylesheets/framework/buttons.scss @@ -148,8 +148,8 @@ &.btn-xs { padding: 2px $gl-btn-padding; - font-size: $gl-btn-small-font-size; - line-height: $gl-btn-small-line-height; + font-size: $gl-btn-xs-font-size; + line-height: $gl-btn-xs-line-height; } &.btn-success, diff --git a/app/assets/stylesheets/framework/dropdowns.scss b/app/assets/stylesheets/framework/dropdowns.scss index b47b1cb76dc..afcb230797a 100644 --- a/app/assets/stylesheets/framework/dropdowns.scss +++ b/app/assets/stylesheets/framework/dropdowns.scss @@ -534,8 +534,9 @@ .dropdown-title { position: relative; - padding: 2px 25px 10px; - margin: 0 10px 10px; + padding: $dropdown-item-padding-y $dropdown-item-padding-x; + padding-bottom: #{2 * $dropdown-item-padding-y}; + margin-bottom: $dropdown-item-padding-y; font-weight: $gl-font-weight-bold; line-height: 1; text-align: center; diff --git a/app/assets/stylesheets/framework/files.scss b/app/assets/stylesheets/framework/files.scss index 3ac7b6b704b..037a5adfb7e 100644 --- a/app/assets/stylesheets/framework/files.scss +++ b/app/assets/stylesheets/framework/files.scss @@ -24,7 +24,7 @@ } } - &:not(.use-csslab) table { + table { @extend .table; } diff --git a/app/assets/stylesheets/framework/forms.scss b/app/assets/stylesheets/framework/forms.scss index afd888af672..4da2243981e 100644 --- a/app/assets/stylesheets/framework/forms.scss +++ b/app/assets/stylesheets/framework/forms.scss @@ -256,7 +256,12 @@ label { } } +.input-md { + max-width: $input-md-width; + width: 100%; +} + .input-lg { - max-width: 320px; + max-width: $input-lg-width; width: 100%; } diff --git a/app/assets/stylesheets/framework/gitlab_theme.scss b/app/assets/stylesheets/framework/gitlab_theme.scss index b8bb9e1e07b..0ef50e139f2 100644 --- a/app/assets/stylesheets/framework/gitlab_theme.scss +++ b/app/assets/stylesheets/framework/gitlab_theme.scss @@ -22,6 +22,10 @@ .container-fluid { .navbar-toggler { border-left: 1px solid lighten($border-and-box-shadow, 10%); + + svg { + fill: $search-and-nav-links; + } } } @@ -309,12 +313,14 @@ body { .navbar-nav { > li { > a:hover, - > a:focus { + > a:focus, + > button:hover { color: $theme-gray-900; } &.active > a, - &.active > a:hover { + &.active > a:hover, + &.active > button { color: $white-light; } } diff --git a/app/assets/stylesheets/framework/header.scss b/app/assets/stylesheets/framework/header.scss index 82906e5d427..5574873fa22 100644 --- a/app/assets/stylesheets/framework/header.scss +++ b/app/assets/stylesheets/framework/header.scss @@ -33,6 +33,7 @@ .close-icon { display: block; + margin: auto; } } @@ -168,12 +169,6 @@ color: currentColor; background-color: transparent; } - - .more-icon, - .close-icon { - fill: $white-light; - margin: auto; - } } .navbar-nav { diff --git a/app/assets/stylesheets/framework/highlight.scss b/app/assets/stylesheets/framework/highlight.scss index 73533571a2f..946f575ac13 100644 --- a/app/assets/stylesheets/framework/highlight.scss +++ b/app/assets/stylesheets/framework/highlight.scss @@ -42,7 +42,6 @@ padding: 10px; text-align: right; float: left; - line-height: 1; a { font-family: $monospace-font; @@ -69,3 +68,9 @@ } } } + +// Vertically aligns <table> line numbers (eg. blame view) +// see https://gitlab.com/gitlab-org/gitlab-ce/issues/54048 +td.line-numbers { + line-height: 1; +} diff --git a/app/assets/stylesheets/framework/issue_box.scss b/app/assets/stylesheets/framework/issue_box.scss index a66604e56ff..e51f230a680 100644 --- a/app/assets/stylesheets/framework/issue_box.scss +++ b/app/assets/stylesheets/framework/issue_box.scss @@ -45,9 +45,4 @@ &.status-box-upcoming { background: $gl-text-color-secondary; } - - &.status-box-milestone { - color: $gl-text-color; - background: $gray-darker; - } } diff --git a/app/assets/stylesheets/framework/markdown_area.scss b/app/assets/stylesheets/framework/markdown_area.scss index c2b429ac29b..c674d4c23ea 100644 --- a/app/assets/stylesheets/framework/markdown_area.scss +++ b/app/assets/stylesheets/framework/markdown_area.scss @@ -277,6 +277,27 @@ } } +.md-suggestion-diff { + display: table !important; + border: 1px solid $border-color !important; +} + +.md-suggestion-header { + height: $suggestion-header-height; + display: flex; + align-items: center; + justify-content: space-between; + background-color: $gray-light; + border: 1px solid $border-color; + padding: $gl-padding; + border-radius: $border-radius-default $border-radius-default 0 0; + + svg { + vertical-align: middle; + margin-bottom: 3px; + } +} + @include media-breakpoint-down(xs) { .atwho-view-ul { width: 350px; diff --git a/app/assets/stylesheets/framework/modal.scss b/app/assets/stylesheets/framework/modal.scss index 7e30747963a..46d40ea7aa5 100644 --- a/app/assets/stylesheets/framework/modal.scss +++ b/app/assets/stylesheets/framework/modal.scss @@ -25,14 +25,10 @@ &.w-100 { // after upgrading to Bootstrap 4.2 we can use $modal-header-padding-x here // https://github.com/twbs/bootstrap/pull/26976 - margin-right: -2rem; - padding-right: 2rem; + margin-right: -28px; + padding-right: 28px; } } - - .page-title { - margin-top: 0; - } } .modal-body { diff --git a/app/assets/stylesheets/framework/selects.scss b/app/assets/stylesheets/framework/selects.scss index 7f0edd88dfb..a68f1e4e570 100644 --- a/app/assets/stylesheets/framework/selects.scss +++ b/app/assets/stylesheets/framework/selects.scss @@ -1,6 +1,11 @@ /** Select2 selectbox style override **/ .select2-container { width: 100% !important; + + &.input-md, + &.input-lg { + display: block; + } } .select2-container, diff --git a/app/assets/stylesheets/framework/variables.scss b/app/assets/stylesheets/framework/variables.scss index 5310195d9c5..7e53f1ec48d 100644 --- a/app/assets/stylesheets/framework/variables.scss +++ b/app/assets/stylesheets/framework/variables.scss @@ -251,6 +251,7 @@ $browserScrollbarSize: 10px; * Misc */ $header-height: 40px; +$suggestion-header-height: 46px; $ide-statusbar-height: 25px; $fixed-layout-width: 1280px; $limited-layout-width: 990px; @@ -276,6 +277,7 @@ $project-title-row-height: 64px; $project-avatar-mobile-size: 24px; $gl-line-height: 16px; $gl-line-height-24: 24px; +$gl-line-height-14: 14px; /* * Common component specific colors @@ -369,7 +371,9 @@ $gl-btn-line-height: 16px; $gl-btn-vert-padding: 8px; $gl-btn-horz-padding: 12px; $gl-btn-small-font-size: 13px; -$gl-btn-small-line-height: 13px; +$gl-btn-small-line-height: 18px; +$gl-btn-xs-font-size: 13px; +$gl-btn-xs-line-height: 13px; /* * Badges @@ -504,6 +508,8 @@ $gl-field-focus-shadow: rgba(0, 0, 0, 0.075); $gl-field-focus-shadow-error: rgba($red-500, 0.6); $input-short-width: 200px; $input-short-md-width: 280px; +$input-md-width: 240px; +$input-lg-width: 320px; /* * Help diff --git a/app/assets/stylesheets/framework/variables_overrides.scss b/app/assets/stylesheets/framework/variables_overrides.scss index 5ca76bb6c5a..069f45bff49 100644 --- a/app/assets/stylesheets/framework/variables_overrides.scss +++ b/app/assets/stylesheets/framework/variables_overrides.scss @@ -28,3 +28,9 @@ $popover-border-width: 1px; $popover-border-color: $border-color; $popover-box-shadow: 0 $border-radius-small $border-radius-default 0 $shadow-color; $popover-arrow-outer-color: $shadow-color; +$h1-font-size: 14px * 2.5; +$h2-font-size: 14px * 2; +$h3-font-size: 14px * 1.75; +$h4-font-size: 14px * 1.5; +$h5-font-size: 14px * 1.25; +$h6-font-size: 14px; diff --git a/app/assets/stylesheets/highlight/none.scss b/app/assets/stylesheets/highlight/none.scss new file mode 100644 index 00000000000..7d692a87e33 --- /dev/null +++ b/app/assets/stylesheets/highlight/none.scss @@ -0,0 +1,242 @@ +/* +* None Syntax Colors +*/ + + + +@mixin matchLine { + color: $black-transparent; + background-color: $white-normal; +} + +.code.none { + // Line numbers + .line-numbers, + .diff-line-num { + background-color: $gray-light; + } + + .diff-line-num, + .diff-line-num a { + color: $black-transparent; + } + + // Code itself + pre.code, + .diff-line-num { + border-color: $white-normal; + } + + &, + pre.code, + .line_holder .line_content { + background-color: $white-light; + color: $gl-text-color; + } + +// Diff line + + $none-over-bg: #ded7fc; + $none-expanded-border: #e0e0e0; + $none-expanded-bg: #f7f7f7; + + .line_holder { + + &.match .line_content, + .new-nonewline.line_content, + .old-nonewline.line_content { + @include matchLine; + } + + .diff-line-num { + &.old { + background-color: $line-number-old; + border-color: $line-removed-dark; + + a { + color: scale-color($line-number-old, $red: -30%, $green: -30%, $blue: -30%); + } + } + + &.new { + background-color: $line-number-new; + border-color: $line-added-dark; + + a { + color: scale-color($line-number-new, $red: -30%, $green: -30%, $blue: -30%); + } + } + + &.is-over, + &.hll:not(.empty-cell).is-over { + background-color: $none-over-bg; + border-color: darken($none-over-bg, 5%); + + a { + color: darken($none-over-bg, 15%); + } + } + + &.hll:not(.empty-cell) { + background-color: $line-number-select; + border-color: $line-select-yellow-dark; + } + } + + &:not(.diff-expanded) + .diff-expanded, + &.diff-expanded + .line_holder:not(.diff-expanded) { + > .diff-line-num, + > .line_content { + border-top: 1px solid $none-expanded-border; + } + } + + &.diff-expanded { + > .diff-line-num, + > .line_content { + background: $none-expanded-bg; + border-color: $none-expanded-bg; + } + } + + .line_content { + &.old { + background-color: $line-removed; + + &::before { + color: scale-color($line-number-old, $red: -30%, $green: -30%, $blue: -30%); + } + + span.idiff { + background-color: $line-removed-dark; + } + } + + &.new { + background-color: $line-added; + + &::before { + color: scale-color($line-number-new, $red: -30%, $green: -30%, $blue: -30%); + } + + span.idiff { + background-color: $line-added-dark; + } + } + + &.match { + @include matchLine; + } + + &.hll:not(.empty-cell) { + background-color: $line-select-yellow; + } + } + } + + // highlight line via anchor + pre .hll { + background-color: $white-normal; + } + + // Search result highlight + span.highlight_word { + background-color: $white-normal; + } + + // Links to URLs, emails, or dependencies + .line a { + color: $gl-text-color; + text-decoration: underline; + } + + .hll { background-color: $white-light; } + + .gd { + color: $gl-text-color; + background-color: $white-light; + + .x { + color: $gl-text-color; + background-color: $white-light; + } + } + + .gi { + color: $gl-text-color; + background-color: $white-light; + + .x { + color: $gl-text-color; + background-color: $white-light; + } + } + + .c { color: $gl-text-color; } /* Comment */ + .err { color: $gl-text-color; } /* Error */ + .g { color: $gl-text-color; } /* Generic */ + .k { color: $gl-text-color; } /* Keyword */ + .l { color: $gl-text-color; } /* Literal */ + .n { color: $gl-text-color; } /* Name */ + .o { color: $gl-text-color; } /* Operator */ + .x { color: $gl-text-color; } /* Other */ + .p { color: $gl-text-color; } /* Punctuation */ + .cm { color: $gl-text-color; } /* Comment.Multiline */ + .cp { color: $gl-text-color; } /* Comment.Preproc */ + .c1 { color: $gl-text-color; } /* Comment.Single */ + .cs { color: $gl-text-color; } /* Comment.Special */ + .ge { color: $gl-text-color; } /* Generic.Emph */ + .gr { color: $gl-text-color; } /* Generic.Error */ + .gh { color: $gl-text-color; } /* Generic.Heading */ + .go { color: $gl-text-color; } /* Generic.Output */ + .gp { color: $gl-text-color; } /* Generic.Prompt */ + .gs { color: $gl-text-color; } /* Generic.Strong */ + .gu { color: $gl-text-color; } /* Generic.Subheading */ + .gt { color: $gl-text-color; } /* Generic.Traceback */ + .kc { color: $gl-text-color; } /* Keyword.Constant */ + .kd { color: $gl-text-color; } /* Keyword.Declaration */ + .kn { color: $gl-text-color; } /* Keyword.Namespace */ + .kp { color: $gl-text-color; } /* Keyword.Pseudo */ + .kr { color: $gl-text-color; } /* Keyword.Reserved */ + .kt { color: $gl-text-color; } /* Keyword.Type */ + .ld { color: $gl-text-color; } /* Literal.Date */ + .m { color: $gl-text-color; } /* Literal.Number */ + .s { color: $gl-text-color; } /* Literal.String */ + .na { color: $gl-text-color; } /* Name.Attribute */ + .nb { color: $gl-text-color; } /* Name.Builtin */ + .nc { color: $gl-text-color; } /* Name.Class */ + .no { color: $gl-text-color; } /* Name.Constant */ + .nd { color: $gl-text-color; } /* Name.Decorator */ + .ni { color: $gl-text-color; } /* Name.Entity */ + .ne { color: $gl-text-color; } /* Name.Exception */ + .nf { color: $gl-text-color; } /* Name.Function */ + .nl { color: $gl-text-color; } /* Name.Label */ + .nn { color: $gl-text-color; } /* Name.Namespace */ + .nx { color: $gl-text-color; } /* Name.Other */ + .py { color: $gl-text-color; } /* Name.Property */ + .nt { color: $gl-text-color; } /* Name.Tag */ + .nv { color: $gl-text-color; } /* Name.Variable */ + .ow { color: $gl-text-color; } /* Operator.Word */ + .w { color: $gl-text-color; } /* Text.Whitespace */ + .mf { color: $gl-text-color; } /* Literal.Number.Float */ + .mh { color: $gl-text-color; } /* Literal.Number.Hex */ + .mi { color: $gl-text-color; } /* Literal.Number.Integer */ + .mo { color: $gl-text-color; } /* Literal.Number.Oct */ + .sb { color: $gl-text-color; } /* Literal.String.Backtick */ + .sc { color: $gl-text-color; } /* Literal.String.Char */ + .sd { color: $gl-text-color; } /* Literal.String.Doc */ + .s2 { color: $gl-text-color; } /* Literal.String.Double */ + .se { color: $gl-text-color; } /* Literal.String.Escape */ + .sh { color: $gl-text-color; } /* Literal.String.Heredoc */ + .si { color: $gl-text-color; } /* Literal.String.Interpol */ + .sx { color: $gl-text-color; } /* Literal.String.Other */ + .sr { color: $gl-text-color; } /* Literal.String.Regex */ + .s1 { color: $gl-text-color; } /* Literal.String.Single */ + .ss { color: $gl-text-color; } /* Literal.String.Symbol */ + .bp { color: $gl-text-color; } /* Name.Builtin.Pseudo */ + .vc { color: $gl-text-color; } /* Name.Variable.Class */ + .vg { color: $gl-text-color; } /* Name.Variable.Global */ + .vi { color: $gl-text-color; } /* Name.Variable.Instance */ + .il { color: $gl-text-color; } /* Literal.Number.Integer.Long */ + +} diff --git a/app/assets/stylesheets/pages/builds.scss b/app/assets/stylesheets/pages/builds.scss index 57918eafd6f..09235661cea 100644 --- a/app/assets/stylesheets/pages/builds.scss +++ b/app/assets/stylesheets/pages/builds.scss @@ -135,6 +135,7 @@ .build-loader-animation { @include build-loader-animation; float: left; + padding-left: $gl-padding-8; } } @@ -232,6 +233,11 @@ @extend .d-flex; justify-content: space-between; align-items: center; + + .trigger-variables-btn { + margin-top: -5px; + margin-bottom: -5px; + } } .trigger-build-variables { diff --git a/app/assets/stylesheets/pages/issuable.scss b/app/assets/stylesheets/pages/issuable.scss index 5b5f486ea63..a1069aa9783 100644 --- a/app/assets/stylesheets/pages/issuable.scss +++ b/app/assets/stylesheets/pages/issuable.scss @@ -60,6 +60,10 @@ padding: 0; margin-bottom: $gl-padding; border-bottom: 0; + word-wrap: break-word; + overflow-wrap: break-word; + min-width: 0; + width: 100%; } .btn-edit { diff --git a/app/assets/stylesheets/pages/login.scss b/app/assets/stylesheets/pages/login.scss index fa0ab1a3bae..67d7a8175ac 100644 --- a/app/assets/stylesheets/pages/login.scss +++ b/app/assets/stylesheets/pages/login.scss @@ -49,8 +49,8 @@ .login-box, .omniauth-container { box-shadow: 0 0 0 1px $border-color; - border-bottom-right-radius: 2px; - border-bottom-left-radius: 2px; + border-bottom-right-radius: $border-radius-small; + border-bottom-left-radius: $border-radius-small; padding: 15px; .login-heading h3 { @@ -95,6 +95,7 @@ } .omniauth-container { + border-radius: $border-radius-small; font-size: 13px; p { diff --git a/app/assets/stylesheets/pages/merge_conflicts.scss b/app/assets/stylesheets/pages/merge_conflicts.scss index d26659701e1..e0f7d075fc7 100644 --- a/app/assets/stylesheets/pages/merge_conflicts.scss +++ b/app/assets/stylesheets/pages/merge_conflicts.scss @@ -93,8 +93,28 @@ $colors: ( solarized-dark-line-origin-chosen : rgba(#2878c9, .35), solarized-dark-button-origin-chosen : #0082cc, - solarized-dark-header-not-chosen : rgba(#839496, .25), - solarized-dark-line-not-chosen : rgba(#839496, .15) + solarized_dark_header_not_chosen : rgba(#839496, .25), + solarized_dark_line_not_chosen : rgba(#839496, .15), + + none_header_head_neutral : $gray-normal, + none_line_head_neutral : $gray-normal, + none_button_head_neutral : $gray-normal, + + none_header_head_chosen : $gray-darker, + none_line_head_chosen : $gray-darker, + none_button_head_chosen : $gray-darker, + + none_header_origin_neutral : $gray-normal, + none_line_origin_neutral : $gray-normal, + none_button_origin_neutral : $gray-normal, + + none_header_origin_chosen : $gray-darker, + none_line_origin_chosen : $gray-darker, + none_button_origin_chosen : $gray-darker, + + none_header_not_chosen : $gray-light, + none_line_not_chosen : $gray-light + ); // scss-lint:enable ColorVariable diff --git a/app/assets/stylesheets/pages/milestone.scss b/app/assets/stylesheets/pages/milestone.scss index 1e92582d6d9..94bf32945fc 100644 --- a/app/assets/stylesheets/pages/milestone.scss +++ b/app/assets/stylesheets/pages/milestone.scss @@ -1,3 +1,5 @@ +$status-box-line-height: 26px; + .issues-sortable-list .str-truncated { max-width: 90%; } @@ -38,6 +40,7 @@ font-size: $tooltip-font-size; margin-top: 0; margin-right: $gl-padding-4; + line-height: $status-box-line-height; @include media-breakpoint-down(xs) { line-height: unset; diff --git a/app/assets/stylesheets/pages/notes.scss b/app/assets/stylesheets/pages/notes.scss index 2adfa0d312e..a5b1eff3e1d 100644 --- a/app/assets/stylesheets/pages/notes.scss +++ b/app/assets/stylesheets/pages/notes.scss @@ -152,6 +152,16 @@ $note-form-margin-left: 72px; display: block; position: relative; + .timeline-discussion-body { + margin-top: -8px; + overflow-x: auto; + overflow-y: hidden; + + .discussion-resolved-text { + margin-bottom: 8px; + } + } + .diff-content { overflow: visible; padding: 0; diff --git a/app/assets/stylesheets/pages/pipelines.scss b/app/assets/stylesheets/pages/pipelines.scss index fdd17af35fb..7a47e0a2836 100644 --- a/app/assets/stylesheets/pages/pipelines.scss +++ b/app/assets/stylesheets/pages/pipelines.scss @@ -978,7 +978,6 @@ button.mini-pipeline-graph-dropdown-toggle { * Top arrow in the dropdown in the mini pipeline graph */ .mini-pipeline-graph-dropdown-menu { - z-index: 200; &::before, &::after { diff --git a/app/assets/stylesheets/pages/profile.scss b/app/assets/stylesheets/pages/profile.scss index a4831b64344..b813eb16dad 100644 --- a/app/assets/stylesheets/pages/profile.scss +++ b/app/assets/stylesheets/pages/profile.scss @@ -456,4 +456,15 @@ table.u2f-registrations { } } } + + @include media-breakpoint-down(sm) { + .input-md, + .input-lg { + max-width: 100%; + } + } +} + +.help-block { + color: $gl-text-color-secondary; } diff --git a/app/assets/stylesheets/pages/projects.scss b/app/assets/stylesheets/pages/projects.scss index 278800aba95..004c49dd226 100644 --- a/app/assets/stylesheets/pages/projects.scss +++ b/app/assets/stylesheets/pages/projects.scss @@ -969,34 +969,73 @@ pre.light-well { @include basic-list-stats; display: flex; align-items: center; - } + color: $gl-text-color-secondary; + padding: $gl-padding 0; - h3 { - font-size: $gl-font-size; + @include media-breakpoint-up(lg) { + padding: $gl-padding 0; + } + + &.no-description { + @include media-breakpoint-up(sm) { + .avatar-container { + align-self: center; + } + + .metadata-info { + margin-bottom: 0; + } + } + } } - .avatar-container, - .controls { - flex: 0 0 auto; + h2 { + font-size: $gl-font-size-large; + font-weight: $gl-font-weight-bold; + margin-bottom: 0; + + @include media-breakpoint-up(sm) { + .namespace-name { + font-weight: $gl-font-weight-normal; + } + } } .avatar-container { + flex: 0 0 auto; align-self: flex-start; } .project-details { min-width: 0; + line-height: $gl-line-height; + + .flex-wrapper { + min-width: 0; + margin-top: -$gl-padding-8; // negative margin required for flex-wrap + } p, .commit-row-message { @include str-truncated(100%); margin-bottom: 0; } - } - .controls { - margin-left: auto; - text-align: right; + .user-access-role { + margin: 0; + } + + @include media-breakpoint-up(md) { + .description { + color: $gl-text-color; + } + } + + @include media-breakpoint-down(md) { + .user-access-role { + line-height: $gl-line-height-14; + } + } } .ci-status-link { @@ -1008,6 +1047,149 @@ pre.light-well { text-decoration: none; } } + + .controls { + margin-top: $gl-padding-8; + + @include media-breakpoint-down(md) { + margin-top: 0; + } + + @include media-breakpoint-down(xs) { + margin-top: $gl-padding-8; + } + + .icon-wrapper { + color: inherit; + margin-right: $gl-padding; + + @include media-breakpoint-down(md) { + margin-right: 0; + margin-left: $gl-padding-8; + } + + @include media-breakpoint-down(xs) { + &:first-child { + margin-left: 0; + } + } + } + + .ci-status-link { + display: inline-flex; + } + } + + .star-button { + .icon { + top: 0; + } + } + + .icon-container { + @include media-breakpoint-down(xs) { + margin-right: $gl-padding-8; + } + } + + &.compact { + .project-row { + padding: $gl-padding 0; + } + + h2 { + font-size: $gl-font-size; + } + + .avatar-container { + @include avatar-size(40px, 10px); + min-height: 40px; + min-width: 40px; + + .identicon.s64 { + font-size: 16px; + } + } + + .controls { + @include media-breakpoint-up(sm) { + margin-top: 0; + } + } + + .updated-note { + @include media-breakpoint-up(sm) { + margin-top: $gl-padding-8; + } + } + + .icon-wrapper { + margin-left: $gl-padding-8; + margin-right: 0; + + @include media-breakpoint-down(xs) { + &:first-child { + margin-left: 0; + } + } + } + + .user-access-role { + line-height: $gl-line-height-14; + } + } + + @include media-breakpoint-down(md) { + h2 { + font-size: $gl-font-size; + } + + .avatar-container { + @include avatar-size(40px, 10px); + min-height: 40px; + min-width: 40px; + + .identicon.s64 { + font-size: 16px; + } + } + } + + @include media-breakpoint-down(md) { + .updated-note { + margin-top: $gl-padding-8; + text-align: right; + } + } + + .forks, + .pipeline-status, + .updated-note { + display: flex; + } + + @include media-breakpoint-down(md) { + &:not(.explore) { + .forks { + display: none; + + } + } + + &.explore { + .pipeline-status, + .updated-note { + display: none !important; + } + } + } + + @include media-breakpoint-down(xs) { + .updated-note { + margin-top: 0; + text-align: left; + } + } } .card .projects-list li { diff --git a/app/controllers/admin/application_settings_controller.rb b/app/controllers/admin/application_settings_controller.rb index 8f683ca06ad..8f267eccc8a 100644 --- a/app/controllers/admin/application_settings_controller.rb +++ b/app/controllers/admin/application_settings_controller.rb @@ -77,7 +77,7 @@ class Admin::ApplicationSettingsController < Admin::ApplicationController def reset_health_check_token @application_setting.reset_health_check_access_token! flash[:notice] = 'New health check access token has been generated!' - redirect_to :back + redirect_back_or_default end def clear_repository_check_states diff --git a/app/controllers/admin/health_check_controller.rb b/app/controllers/admin/health_check_controller.rb index 25cc241e5b0..7cd80e8b5e1 100644 --- a/app/controllers/admin/health_check_controller.rb +++ b/app/controllers/admin/health_check_controller.rb @@ -2,6 +2,12 @@ class Admin::HealthCheckController < Admin::ApplicationController def show - @errors = HealthCheck::Utils.process_checks(['standard']) + @errors = HealthCheck::Utils.process_checks(checks) + end + + private + + def checks + ['standard'] end end diff --git a/app/controllers/application_controller.rb b/app/controllers/application_controller.rb index 7c8c1392c1c..a8fc848c879 100644 --- a/app/controllers/application_controller.rb +++ b/app/controllers/application_controller.rb @@ -12,9 +12,6 @@ class ApplicationController < ActionController::Base include EnforcesTwoFactorAuthentication include WithPerformanceBar include SessionlessAuthentication - # this can be removed after switching to rails 5 - # https://gitlab.com/gitlab-org/gitlab-ce/issues/51908 - include InvalidUTF8ErrorHandler unless Gitlab.rails5? before_action :authenticate_user! before_action :enforce_terms!, if: :should_enforce_terms? @@ -79,7 +76,7 @@ class ApplicationController < ActionController::Base end def redirect_back_or_default(default: root_path, options: {}) - redirect_to request.referer.present? ? :back : default, options + redirect_back(fallback_location: default, **options) end def not_found @@ -157,7 +154,7 @@ class ApplicationController < ActionController::Base def log_exception(exception) Gitlab::Sentry.track_acceptable_exception(exception) - backtrace_cleaner = Gitlab.rails5? ? request.env["action_dispatch.backtrace_cleaner"] : env + backtrace_cleaner = request.env["action_dispatch.backtrace_cleaner"] application_trace = ActionDispatch::ExceptionWrapper.new(backtrace_cleaner, exception).application_trace application_trace.map! { |t| " #{t}\n" } logger.error "\n#{exception.class.name} (#{exception.message}):\n#{application_trace.join}" @@ -406,7 +403,7 @@ class ApplicationController < ActionController::Base end def manifest_import_enabled? - Group.supports_nested_groups? && Gitlab::CurrentSettings.import_sources.include?('manifest') + Group.supports_nested_objects? && Gitlab::CurrentSettings.import_sources.include?('manifest') end # U2F (universal 2nd factor) devices need a unique identifier for the application diff --git a/app/controllers/clusters/clusters_controller.rb b/app/controllers/clusters/clusters_controller.rb index 9aa8b758539..b9717b97640 100644 --- a/app/controllers/clusters/clusters_controller.rb +++ b/app/controllers/clusters/clusters_controller.rb @@ -18,8 +18,20 @@ class Clusters::ClustersController < Clusters::BaseController STATUS_POLLING_INTERVAL = 10_000 def index - clusters = ClustersFinder.new(clusterable, current_user, :all).execute - @clusters = clusters.page(params[:page]).per(20) + finder = ClusterAncestorsFinder.new(clusterable.subject, current_user) + clusters = finder.execute + + # Note: We are paginating through an array here but this should OK as: + # + # In CE, we can have a maximum group nesting depth of 21, so including + # project cluster, we can have max 22 clusters for a group hierachy. + # In EE (Premium) we can have any number, as multiple clusters are + # supported, but the number of clusters are fairly low currently. + # + # See https://gitlab.com/gitlab-org/gitlab-ce/issues/55260 also. + @clusters = Kaminari.paginate_array(clusters).page(params[:page]).per(20) + + @has_ancestor_clusters = finder.has_ancestor_clusters? end def new diff --git a/app/controllers/concerns/group_tree.rb b/app/controllers/concerns/group_tree.rb index 4f56346832c..e9a7d6a3152 100644 --- a/app/controllers/concerns/group_tree.rb +++ b/app/controllers/concerns/group_tree.rb @@ -32,14 +32,14 @@ module GroupTree def filtered_groups_with_ancestors(groups) filtered_groups = groups.search(params[:filter]).page(params[:page]) - if Group.supports_nested_groups? + if Group.supports_nested_objects? # We find the ancestors by ID of the search results here. # Otherwise the ancestors would also have filters applied, # which would cause them not to be preloaded. # # Pagination needs to be applied before loading the ancestors to # make sure ancestors are not cut off by pagination. - Gitlab::GroupHierarchy.new(Group.where(id: filtered_groups.select(:id))) + Gitlab::ObjectHierarchy.new(Group.where(id: filtered_groups.select(:id))) .base_and_ancestors else filtered_groups diff --git a/app/controllers/concerns/invalid_utf8_error_handler.rb b/app/controllers/concerns/invalid_utf8_error_handler.rb deleted file mode 100644 index 44c6d6b0da0..00000000000 --- a/app/controllers/concerns/invalid_utf8_error_handler.rb +++ /dev/null @@ -1,27 +0,0 @@ -# frozen_string_literal: true - -module InvalidUTF8ErrorHandler - extend ActiveSupport::Concern - - included do - rescue_from ArgumentError, with: :handle_invalid_utf8 - end - - private - - def handle_invalid_utf8(error) - if error.message == "invalid byte sequence in UTF-8" - render_412 - else - raise(error) - end - end - - def render_412 - respond_to do |format| - format.html { render "errors/precondition_failed", layout: "errors", status: 412 } - format.js { render json: { error: 'Invalid UTF-8' }, status: :precondition_failed, content_type: 'application/json' } - format.any { head :precondition_failed } - end - end -end diff --git a/app/controllers/concerns/issuable_actions.rb b/app/controllers/concerns/issuable_actions.rb index ad9cc0925b7..3d64ae8b775 100644 --- a/app/controllers/concerns/issuable_actions.rb +++ b/app/controllers/concerns/issuable_actions.rb @@ -5,7 +5,6 @@ module IssuableActions include Gitlab::Utils::StrongMemoize included do - before_action :labels, only: [:show, :new, :edit] before_action :authorize_destroy_issuable!, only: :destroy before_action :authorize_admin_issuable!, only: :bulk_update end @@ -25,7 +24,10 @@ module IssuableActions def show respond_to do |format| - format.html + format.html do + @issuable_sidebar = serializer.represent(issuable, serializer: 'sidebar') # rubocop:disable Gitlab/ModuleWithInstanceVariables + end + format.json do render json: serializer.represent(issuable, serializer: params[:serializer]) end @@ -168,10 +170,6 @@ module IssuableActions end end - def labels - @labels ||= LabelsFinder.new(current_user, project_id: @project.id).execute # rubocop:disable Gitlab/ModuleWithInstanceVariables - end - def authorize_destroy_issuable! unless can?(current_user, :"destroy_#{issuable.to_ability_name}", issuable) return access_denied! diff --git a/app/controllers/concerns/lfs_request.rb b/app/controllers/concerns/lfs_request.rb index 9576eb14fdd..5572c3cee2d 100644 --- a/app/controllers/concerns/lfs_request.rb +++ b/app/controllers/concerns/lfs_request.rb @@ -94,6 +94,7 @@ module LfsRequest def lfs_upload_access? return false unless project.lfs_enabled? return false unless has_authentication_ability?(:push_code) + return false if limit_exceeded? lfs_deploy_token? || can?(user, :push_code, project) end @@ -121,4 +122,9 @@ module LfsRequest def has_authentication_ability?(capability) (authentication_abilities || []).include?(capability) end + + # Overriden in EE + def limit_exceeded? + false + end end diff --git a/app/controllers/concerns/preview_markdown.rb b/app/controllers/concerns/preview_markdown.rb index c61b9fabe9e..4b0f0b8255c 100644 --- a/app/controllers/concerns/preview_markdown.rb +++ b/app/controllers/concerns/preview_markdown.rb @@ -12,7 +12,7 @@ module PreviewMarkdown when 'wikis' then { pipeline: :wiki, project_wiki: @project_wiki, page_slug: params[:id] } when 'snippets' then { skip_project_check: true } when 'groups' then { group: group } - when 'projects' then { issuable_state_filter_enabled: true } + when 'projects' then projects_filter_params else {} end @@ -22,9 +22,17 @@ module PreviewMarkdown body: view_context.markdown(result[:text], markdown_params), references: { users: result[:users], + suggestions: result[:suggestions], commands: view_context.markdown(result[:commands]) } } end + + def projects_filter_params + { + issuable_state_filter_enabled: true, + suggestions_filter_enabled: params[:preview_suggestions].present? + } + end # rubocop:enable Gitlab/ModuleWithInstanceVariables end diff --git a/app/controllers/concerns/service_params.rb b/app/controllers/concerns/service_params.rb index 8bd93a349ef..c6ae4fe15bf 100644 --- a/app/controllers/concerns/service_params.rb +++ b/app/controllers/concerns/service_params.rb @@ -70,7 +70,7 @@ module ServiceParams def service_params dynamic_params = @service.event_channel_names + @service.event_names # rubocop:disable Gitlab/ModuleWithInstanceVariables - service_params = params.permit(:id, service: ALLOWED_PARAMS_CE + dynamic_params) + service_params = params.permit(:id, service: allowed_service_params + dynamic_params) if service_params[:service].is_a?(Hash) FILTER_BLANK_PARAMS.each do |param| @@ -80,4 +80,8 @@ module ServiceParams service_params end + + def allowed_service_params + ALLOWED_PARAMS_CE + end end diff --git a/app/controllers/graphql_controller.rb b/app/controllers/graphql_controller.rb index 6ea4758ec32..3ef03bc9622 100644 --- a/app/controllers/graphql_controller.rb +++ b/app/controllers/graphql_controller.rb @@ -43,6 +43,6 @@ class GraphqlController < ApplicationController end def check_graphql_feature_flag! - render_404 unless Feature.enabled?(:graphql) + render_404 unless Gitlab::Graphql.enabled? end end diff --git a/app/controllers/groups/milestones_controller.rb b/app/controllers/groups/milestones_controller.rb index b42116b0f36..868deea3f01 100644 --- a/app/controllers/groups/milestones_controller.rb +++ b/app/controllers/groups/milestones_controller.rb @@ -43,14 +43,7 @@ class Groups::MilestonesController < Groups::ApplicationController def update # Keep this compatible with legacy group milestones where we have to update # all projects milestones states at once. - if @milestone.legacy_group_milestone? - update_params = milestone_params.select { |key| key == "state_event" } - milestones = @milestone.milestones - else - update_params = milestone_params - milestones = [@milestone] - end - + milestones, update_params = get_milestones_for_update milestones.each do |milestone| Milestones::UpdateService.new(milestone.parent, current_user, update_params).execute(milestone) end @@ -71,6 +64,14 @@ class Groups::MilestonesController < Groups::ApplicationController private + def get_milestones_for_update + if @milestone.legacy_group_milestone? + [@milestone.milestones, legacy_milestone_params] + else + [[@milestone], milestone_params] + end + end + def authorize_admin_milestones! return render_404 unless can?(current_user, :admin_milestone, group) end @@ -79,6 +80,10 @@ class Groups::MilestonesController < Groups::ApplicationController params.require(:milestone).permit(:title, :description, :start_date, :due_date, :state_event) end + def legacy_milestone_params + params.require(:milestone).permit(:state_event) + end + def milestone_path if @milestone.legacy_group_milestone? group_milestone_path(group, @milestone.safe_title, title: @milestone.title) diff --git a/app/controllers/groups/settings/ci_cd_controller.rb b/app/controllers/groups/settings/ci_cd_controller.rb index c1dcc463de7..f476f428fdb 100644 --- a/app/controllers/groups/settings/ci_cd_controller.rb +++ b/app/controllers/groups/settings/ci_cd_controller.rb @@ -4,7 +4,7 @@ module Groups module Settings class CiCdController < Groups::ApplicationController skip_cross_project_access_check :show - before_action :authorize_admin_pipeline! + before_action :authorize_admin_group! def show define_ci_variables @@ -26,8 +26,8 @@ module Groups .map { |variable| variable.present(current_user: current_user) } end - def authorize_admin_pipeline! - return render_404 unless can?(current_user, :admin_pipeline, group) + def authorize_admin_group! + return render_404 unless can?(current_user, :admin_group, group) end end end diff --git a/app/controllers/profiles/keys_controller.rb b/app/controllers/profiles/keys_controller.rb index dcee8eb7e6e..055d900eece 100644 --- a/app/controllers/profiles/keys_controller.rb +++ b/app/controllers/profiles/keys_controller.rb @@ -40,7 +40,6 @@ class Profiles::KeysController < Profiles::ApplicationController begin user = UserFinder.new(params[:username]).find_by_username if user.present? - headers['Content-Disposition'] = 'attachment' render plain: user.all_ssh_keys.join("\n") else return render_404 diff --git a/app/controllers/projects/blob_controller.rb b/app/controllers/projects/blob_controller.rb index 60fabd15333..ff286c0ccf0 100644 --- a/app/controllers/projects/blob_controller.rb +++ b/app/controllers/projects/blob_controller.rb @@ -260,7 +260,7 @@ class Projects::BlobController < Projects::ApplicationController extension: blob.extension, size: blob.raw_size, mime_type: blob.mime_type, - binary: blob.raw_binary?, + binary: blob.binary?, simple_viewer: blob.simple_viewer&.class&.partial_name, rich_viewer: blob.rich_viewer&.class&.partial_name, show_viewer_switcher: !!blob.show_viewer_switcher?, diff --git a/app/controllers/projects/deploy_keys_controller.rb b/app/controllers/projects/deploy_keys_controller.rb index 0a593bd35b6..6824a07dc76 100644 --- a/app/controllers/projects/deploy_keys_controller.rb +++ b/app/controllers/projects/deploy_keys_controller.rb @@ -24,7 +24,7 @@ class Projects::DeployKeysController < Projects::ApplicationController end def create - @key = DeployKeys::CreateService.new(current_user, create_params).execute + @key = DeployKeys::CreateService.new(current_user, create_params).execute(project: @project) unless @key.valid? flash[:alert] = @key.errors.full_messages.join(', ').html_safe diff --git a/app/controllers/projects/imports_controller.rb b/app/controllers/projects/imports_controller.rb index a10e159ea1e..8b33fa85c1e 100644 --- a/app/controllers/projects/imports_controller.rb +++ b/app/controllers/projects/imports_controller.rb @@ -13,7 +13,7 @@ class Projects::ImportsController < Projects::ApplicationController end def create - if @project.update(safe_import_params) + if @project.update(import_params) @project.import_state.reload.schedule end @@ -66,11 +66,11 @@ class Projects::ImportsController < Projects::ApplicationController end end - def import_params - params.require(:project).permit(:import_url) + def import_params_attributes + [:import_url] end - def safe_import_params - import_params + def import_params + params.require(:project).permit(import_params_attributes) end end diff --git a/app/controllers/projects/issues_controller.rb b/app/controllers/projects/issues_controller.rb index c6ab6b4642e..5ed46fc0545 100644 --- a/app/controllers/projects/issues_controller.rb +++ b/app/controllers/projects/issues_controller.rb @@ -268,7 +268,6 @@ class Projects::IssuesController < Projects::ApplicationController end def set_suggested_issues_feature_flags - push_frontend_feature_flag(:graphql) - push_frontend_feature_flag(:issue_suggestions) + push_frontend_feature_flag(:graphql, default_enabled: true) end end diff --git a/app/controllers/projects/jobs_controller.rb b/app/controllers/projects/jobs_controller.rb index c58b30eace7..bfbbcba883f 100644 --- a/app/controllers/projects/jobs_controller.rb +++ b/app/controllers/projects/jobs_controller.rb @@ -9,7 +9,7 @@ class Projects::JobsController < Projects::ApplicationController before_action :authorize_update_build!, except: [:index, :show, :status, :raw, :trace, :cancel_all, :erase] before_action :authorize_erase_build!, only: [:erase] - before_action :authorize_use_build_terminal!, only: [:terminal, :terminal_workhorse_authorize] + before_action :authorize_use_build_terminal!, only: [:terminal, :terminal_websocket_authorize] before_action :verify_api_request!, only: :terminal_websocket_authorize layout 'project' diff --git a/app/controllers/projects/merge_requests/conflicts_controller.rb b/app/controllers/projects/merge_requests/conflicts_controller.rb index ac1969adc6e..045a4e974fe 100644 --- a/app/controllers/projects/merge_requests/conflicts_controller.rb +++ b/app/controllers/projects/merge_requests/conflicts_controller.rb @@ -8,7 +8,7 @@ class Projects::MergeRequests::ConflictsController < Projects::MergeRequests::Ap def show respond_to do |format| format.html do - labels + @issuable_sidebar = serializer.represent(@merge_request, serializer: 'sidebar') end format.json do @@ -60,9 +60,15 @@ class Projects::MergeRequests::ConflictsController < Projects::MergeRequests::Ap end end + private + def authorize_can_resolve_conflicts! @conflicts_list = ::MergeRequests::Conflicts::ListService.new(@merge_request) return render_404 unless @conflicts_list.can_be_resolved_by?(current_user) end + + def serializer + MergeRequestSerializer.new(current_user: current_user, project: project) + end end diff --git a/app/controllers/projects/merge_requests_controller.rb b/app/controllers/projects/merge_requests_controller.rb index da9316d5f22..162c2636641 100644 --- a/app/controllers/projects/merge_requests_controller.rb +++ b/app/controllers/projects/merge_requests_controller.rb @@ -22,8 +22,7 @@ class Projects::MergeRequestsController < Projects::MergeRequests::ApplicationCo format.html format.json do render json: { - html: view_to_html_string("projects/merge_requests/_merge_requests"), - labels: @labels.as_json(methods: :text_color) + html: view_to_html_string("projects/merge_requests/_merge_requests") } end end @@ -43,8 +42,7 @@ class Projects::MergeRequestsController < Projects::MergeRequests::ApplicationCo @noteable = @merge_request @commits_count = @merge_request.commits_count - - labels + @issuable_sidebar = serializer.represent(@merge_request, serializer: 'sidebar') set_pipeline_variables @@ -220,6 +218,12 @@ class Projects::MergeRequestsController < Projects::MergeRequests::ApplicationCo head :ok end + def discussions + merge_request.preload_discussions_diff_highlight + + super + end + protected alias_method :subscribable_resource, :merge_request diff --git a/app/controllers/projects/protected_branches_controller.rb b/app/controllers/projects/protected_branches_controller.rb index a860be83e95..c5454883060 100644 --- a/app/controllers/projects/protected_branches_controller.rb +++ b/app/controllers/projects/protected_branches_controller.rb @@ -15,6 +15,10 @@ class Projects::ProtectedBranchesController < Projects::ProtectedRefsController @protected_ref = @project.protected_branches.find(params[:id]) end + def access_levels + [:merge_access_levels, :push_access_levels] + end + def protected_ref_params params.require(:protected_branch).permit(:name, merge_access_levels_attributes: access_level_attributes, diff --git a/app/controllers/projects/protected_refs_controller.rb b/app/controllers/projects/protected_refs_controller.rb index 3a3a29ddd0d..4e2a9df5576 100644 --- a/app/controllers/projects/protected_refs_controller.rb +++ b/app/controllers/projects/protected_refs_controller.rb @@ -32,7 +32,7 @@ class Projects::ProtectedRefsController < Projects::ApplicationController @protected_ref = update_service_class.new(@project, current_user, protected_ref_params).execute(@protected_ref) if @protected_ref.valid? - render json: @protected_ref, status: :ok + render json: @protected_ref, status: :ok, include: access_levels else render json: @protected_ref.errors, status: :unprocessable_entity end @@ -62,6 +62,6 @@ class Projects::ProtectedRefsController < Projects::ApplicationController end def access_level_attributes - %i(access_level id) + %i[access_level id] end end diff --git a/app/controllers/projects/protected_tags_controller.rb b/app/controllers/projects/protected_tags_controller.rb index 01cedba95ac..41191639c2b 100644 --- a/app/controllers/projects/protected_tags_controller.rb +++ b/app/controllers/projects/protected_tags_controller.rb @@ -15,6 +15,10 @@ class Projects::ProtectedTagsController < Projects::ProtectedRefsController @protected_ref = @project.protected_tags.find(params[:id]) end + def access_levels + [:create_access_levels] + end + def protected_ref_params params.require(:protected_tag).permit(:name, create_access_levels_attributes: access_level_attributes) end diff --git a/app/controllers/projects/releases_controller.rb b/app/controllers/projects/releases_controller.rb index 55827075896..62bdc84b41a 100644 --- a/app/controllers/projects/releases_controller.rb +++ b/app/controllers/projects/releases_controller.rb @@ -3,40 +3,17 @@ class Projects::ReleasesController < Projects::ApplicationController # Authorize before_action :require_non_empty_project - before_action :authorize_download_code! - before_action :authorize_push_code! - before_action :tag - before_action :release + before_action :authorize_read_release! + before_action :check_releases_page_feature_flag - def edit - end - - def update - # Release belongs to Tag which is not active record object, - # it exists only to save a description to each Tag. - # If description is empty we should destroy the existing record. - if release_params[:description].present? - release.update(release_params) - else - release.destroy - end - - redirect_to project_tag_path(@project, @tag.name) + def index end private - def tag - @tag ||= @repository.find_tag(params[:tag_id]) - end - - # rubocop: disable CodeReuse/ActiveRecord - def release - @release ||= @project.releases.find_or_initialize_by(tag: @tag.name) - end - # rubocop: enable CodeReuse/ActiveRecord + def check_releases_page_feature_flag + return render_404 unless Feature.enabled?(:releases_page, @project) - def release_params - params.require(:release).permit(:description) + push_frontend_feature_flag(:releases_page, @project) end end diff --git a/app/controllers/projects/settings/repository_controller.rb b/app/controllers/projects/settings/repository_controller.rb index 30724de7f6a..ac3004d069f 100644 --- a/app/controllers/projects/settings/repository_controller.rb +++ b/app/controllers/projects/settings/repository_controller.rb @@ -5,7 +5,6 @@ module Projects class RepositoryController < Projects::ApplicationController before_action :authorize_admin_project! before_action :remote_mirror, only: [:show] - before_action :check_cleanup_feature_flag!, only: :cleanup def show render_show @@ -37,10 +36,6 @@ module Projects private - def check_cleanup_feature_flag! - render_404 unless ::Feature.enabled?(:project_cleanup, project) - end - def render_show @deploy_keys = DeployKeysPresenter.new(@project, current_user: current_user) @deploy_tokens = @project.deploy_tokens.active diff --git a/app/controllers/projects/snippets_controller.rb b/app/controllers/projects/snippets_controller.rb index a44acb12bdf..255f1f3569a 100644 --- a/app/controllers/projects/snippets_controller.rb +++ b/app/controllers/projects/snippets_controller.rb @@ -75,7 +75,14 @@ class Projects::SnippetsController < Projects::ApplicationController format.json do render_blob_json(blob) end - format.js { render 'shared/snippets/show'} + + format.js do + if @snippet.embeddable? + render 'shared/snippets/show' + else + head :not_found + end + end end end diff --git a/app/controllers/projects/tags/releases_controller.rb b/app/controllers/projects/tags/releases_controller.rb new file mode 100644 index 00000000000..334e1847cc8 --- /dev/null +++ b/app/controllers/projects/tags/releases_controller.rb @@ -0,0 +1,42 @@ +# frozen_string_literal: true + +class Projects::Tags::ReleasesController < Projects::ApplicationController + # Authorize + before_action :require_non_empty_project + before_action :authorize_download_code! + before_action :authorize_push_code! + before_action :tag + before_action :release + + def edit + end + + def update + # Release belongs to Tag which is not active record object, + # it exists only to save a description to each Tag. + # If description is empty we should destroy the existing record. + if release_params[:description].present? + release.update(release_params) + else + release.destroy + end + + redirect_to project_tag_path(@project, @tag.name) + end + + private + + def tag + @tag ||= @repository.find_tag(params[:tag_id]) + end + + # rubocop: disable CodeReuse/ActiveRecord + def release + @release ||= @project.releases.find_or_initialize_by(tag: @tag.name) + end + # rubocop: enable CodeReuse/ActiveRecord + + def release_params + params.require(:release).permit(:description) + end +end diff --git a/app/controllers/projects/tags_controller.rb b/app/controllers/projects/tags_controller.rb index 686d66b10a3..a17c050b696 100644 --- a/app/controllers/projects/tags_controller.rb +++ b/app/controllers/projects/tags_controller.rb @@ -42,10 +42,23 @@ class Projects::TagsController < Projects::ApplicationController # rubocop: enable CodeReuse/ActiveRecord def create - result = Tags::CreateService.new(@project, current_user) - .execute(params[:tag_name], params[:ref], params[:message], params[:release_description]) + result = ::Tags::CreateService.new(@project, current_user) + .execute(params[:tag_name], params[:ref], params[:message]) if result[:status] == :success + # Release creation with Tags was deprecated in GitLab 11.7 + if params[:release_description].present? + release_params = { + tag: params[:tag_name], + name: params[:tag_name], + description: params[:release_description] + } + + Releases::CreateService + .new(@project, current_user, release_params) + .execute + end + @tag = result[:tag] redirect_to project_tag_path(@project, @tag.name) @@ -58,7 +71,7 @@ class Projects::TagsController < Projects::ApplicationController end def destroy - result = Tags::DestroyService.new(project, current_user).execute(params[:id]) + result = ::Tags::DestroyService.new(project, current_user).execute(params[:id]) respond_to do |format| if result[:status] == :success diff --git a/app/controllers/projects_controller.rb b/app/controllers/projects_controller.rb index 8bf93bfd68d..878816475b2 100644 --- a/app/controllers/projects_controller.rb +++ b/app/controllers/projects_controller.rb @@ -19,6 +19,7 @@ class ProjectsController < Projects::ApplicationController before_action :lfs_blob_ids, only: [:show], if: [:repo_exists?, :project_view_files?] before_action :project_export_enabled, only: [:export, :download_export, :remove_export, :generate_new_export] before_action :present_project, only: [:edit] + before_action :authorize_download_code!, only: [:refs] # Authorize before_action :authorize_admin_project!, only: [:edit, :update, :housekeeping, :download_export, :export, :remove_export, :generate_new_export] diff --git a/app/controllers/sherlock/transactions_controller.rb b/app/controllers/sherlock/transactions_controller.rb index 46e382e594e..8d1847507cc 100644 --- a/app/controllers/sherlock/transactions_controller.rb +++ b/app/controllers/sherlock/transactions_controller.rb @@ -15,7 +15,7 @@ module Sherlock def destroy_all Gitlab::Sherlock.collection.clear - redirect_to :back, status: :found + redirect_back_or_default(options: { status: :found }) end end end diff --git a/app/controllers/snippets_controller.rb b/app/controllers/snippets_controller.rb index dd9bf17cf0c..8ea5450b4e8 100644 --- a/app/controllers/snippets_controller.rb +++ b/app/controllers/snippets_controller.rb @@ -80,7 +80,13 @@ class SnippetsController < ApplicationController render_blob_json(blob) end - format.js { render 'shared/snippets/show' } + format.js do + if @snippet.embeddable? + render 'shared/snippets/show' + else + head :not_found + end + end end end diff --git a/app/controllers/users_controller.rb b/app/controllers/users_controller.rb index 8b040dc080e..072d62ddf38 100644 --- a/app/controllers/users_controller.rb +++ b/app/controllers/users_controller.rb @@ -58,11 +58,13 @@ class UsersController < ApplicationController load_projects skip_pagination = Gitlab::Utils.to_boolean(params[:skip_pagination]) + skip_namespace = Gitlab::Utils.to_boolean(params[:skip_namespace]) + compact_mode = Gitlab::Utils.to_boolean(params[:compact_mode]) respond_to do |format| format.html { render 'show' } format.json do - pager_json("shared/projects/_list", @projects.count, projects: @projects, skip_pagination: skip_pagination) + pager_json("shared/projects/_list", @projects.count, projects: @projects, skip_pagination: skip_pagination, skip_namespace: skip_namespace, compact_mode: compact_mode) end end end diff --git a/app/finders/cluster_ancestors_finder.rb b/app/finders/cluster_ancestors_finder.rb new file mode 100644 index 00000000000..2f9709ee057 --- /dev/null +++ b/app/finders/cluster_ancestors_finder.rb @@ -0,0 +1,35 @@ +# frozen_string_literal: true + +class ClusterAncestorsFinder + include Gitlab::Utils::StrongMemoize + + def initialize(clusterable, current_user) + @clusterable = clusterable + @current_user = current_user + end + + def execute + return [] unless can_read_clusters? + + clusterable.clusters + ancestor_clusters + end + + def has_ancestor_clusters? + ancestor_clusters.any? + end + + private + + attr_reader :clusterable, :current_user + + def can_read_clusters? + Ability.allowed?(current_user, :read_cluster, clusterable) + end + + # This unfortunately returns an Array, not a Relation! + def ancestor_clusters + strong_memoize(:ancestor_clusters) do + Clusters::Cluster.ancestor_clusters_for_clusterable(clusterable) + end + end +end diff --git a/app/finders/concerns/finder_with_cross_project_access.rb b/app/finders/concerns/finder_with_cross_project_access.rb index 220f62bcc7f..06ebb286086 100644 --- a/app/finders/concerns/finder_with_cross_project_access.rb +++ b/app/finders/concerns/finder_with_cross_project_access.rb @@ -5,7 +5,8 @@ # # This module depends on the finder implementing the following methods: # -# - `#execute` should return an `ActiveRecord::Relation` +# - `#execute` should return an `ActiveRecord::Relation` or the `model` needs to +# be defined in the call to `requires_cross_project_access`. # - `#current_user` the user that requires access (or nil) module FinderWithCrossProjectAccess extend ActiveSupport::Concern @@ -13,20 +14,35 @@ module FinderWithCrossProjectAccess prepended do extend Gitlab::CrossProjectAccess::ClassMethods + + cattr_accessor :finder_model + + def self.requires_cross_project_access(*args) + super + + self.finder_model = extract_model_from_arguments(args) + end + + private + + def self.extract_model_from_arguments(args) + args.detect { |argument| argument.is_a?(Hash) && argument[:model] } + &.fetch(:model) + end end override :execute def execute(*args) check = Gitlab::CrossProjectAccess.find_check(self) - original = super + original = -> { super } - return original unless check - return original if should_skip_cross_project_check || can_read_cross_project? + return original.call unless check + return original.call if should_skip_cross_project_check || can_read_cross_project? if check.should_run?(self) - original.model.none + finder_model&.none || original.call.model.none else - original + original.call end end @@ -48,8 +64,6 @@ module FinderWithCrossProjectAccess skip_cross_project_check { super } end - private - attr_accessor :should_skip_cross_project_check def skip_cross_project_check diff --git a/app/finders/events_finder.rb b/app/finders/events_finder.rb index 8df01f1dad9..234b7090fd9 100644 --- a/app/finders/events_finder.rb +++ b/app/finders/events_finder.rb @@ -3,22 +3,27 @@ class EventsFinder prepend FinderMethods prepend FinderWithCrossProjectAccess + + MAX_PER_PAGE = 100 + attr_reader :source, :params, :current_user - requires_cross_project_access unless: -> { source.is_a?(Project) } + requires_cross_project_access unless: -> { source.is_a?(Project) }, model: Event # Used to filter Events # # Arguments: # source - which user or project to looks for events on # current_user - only return events for projects visible to this user - # WARNING: does not consider project feature visibility! # params: # action: string # target_type: string # before: datetime # after: datetime - # + # per_page: integer (max. 100) + # page: integer + # with_associations: boolean + # sort: 'asc' or 'desc' def initialize(params = {}) @source = params.delete(:source) @current_user = params.delete(:current_user) @@ -33,15 +38,18 @@ class EventsFinder events = by_target_type(events) events = by_created_at_before(events) events = by_created_at_after(events) + events = sort(events) + + events = events.with_associations if params[:with_associations] - events + paginated_filtered_by_user_visibility(events) end private # rubocop: disable CodeReuse/ActiveRecord def by_current_user_access(events) - events.merge(ProjectsFinder.new(current_user: current_user).execute) # rubocop: disable CodeReuse/Finder + events.merge(Project.public_or_visible_to_user(current_user)) .joins(:project) end # rubocop: enable CodeReuse/ActiveRecord @@ -77,4 +85,31 @@ class EventsFinder events.where('events.created_at > ?', params[:after].end_of_day) end # rubocop: enable CodeReuse/ActiveRecord + + def sort(events) + return events unless params[:sort] + + if params[:sort] == 'asc' + events.order_id_asc + else + events.order_id_desc + end + end + + def paginated_filtered_by_user_visibility(events) + limited_events = events.page(page).per(per_page) + visible_events = limited_events.select { |event| event.visible_to_user?(current_user) } + + Kaminari.paginate_array(visible_events, total_count: events.count) + end + + def per_page + return MAX_PER_PAGE unless params[:per_page] + + [params[:per_page], MAX_PER_PAGE].min + end + + def page + params[:page] || 1 + end end diff --git a/app/finders/group_descendants_finder.rb b/app/finders/group_descendants_finder.rb index a9ce5be13f3..96a36db7ec8 100644 --- a/app/finders/group_descendants_finder.rb +++ b/app/finders/group_descendants_finder.rb @@ -112,7 +112,7 @@ class GroupDescendantsFinder # rubocop: disable CodeReuse/ActiveRecord def ancestors_of_groups(base_for_ancestors) group_ids = base_for_ancestors.except(:select, :sort).select(:id) - Gitlab::GroupHierarchy.new(Group.where(id: group_ids)) + Gitlab::ObjectHierarchy.new(Group.where(id: group_ids)) .base_and_ancestors(upto: parent_group.id) end # rubocop: enable CodeReuse/ActiveRecord @@ -132,7 +132,7 @@ class GroupDescendantsFinder end def subgroups - return Group.none unless Group.supports_nested_groups? + return Group.none unless Group.supports_nested_objects? # When filtering subgroups, we want to find all matches withing the tree of # descendants to show to the user @@ -183,7 +183,7 @@ class GroupDescendantsFinder # rubocop: disable CodeReuse/ActiveRecord def hierarchy_for_parent - @hierarchy ||= Gitlab::GroupHierarchy.new(Group.where(id: parent_group.id)) + @hierarchy ||= Gitlab::ObjectHierarchy.new(Group.where(id: parent_group.id)) end # rubocop: enable CodeReuse/ActiveRecord end diff --git a/app/finders/groups_finder.rb b/app/finders/groups_finder.rb index ea954f98220..0080123407d 100644 --- a/app/finders/groups_finder.rb +++ b/app/finders/groups_finder.rb @@ -46,7 +46,7 @@ class GroupsFinder < UnionFinder return [Group.all] if current_user&.full_private_access? && all_available? groups = [] - groups << Gitlab::GroupHierarchy.new(groups_for_ancestors, groups_for_descendants).all_groups if current_user + groups << Gitlab::ObjectHierarchy.new(groups_for_ancestors, groups_for_descendants).all_objects if current_user groups << Group.unscoped.public_to_user(current_user) if include_public_groups? groups << Group.none if groups.empty? groups @@ -66,7 +66,7 @@ class GroupsFinder < UnionFinder .groups .where('members.access_level >= ?', params[:min_access_level]) - Gitlab::GroupHierarchy + Gitlab::ObjectHierarchy .new(groups) .base_and_descendants end diff --git a/app/finders/releases_finder.rb b/app/finders/releases_finder.rb new file mode 100644 index 00000000000..59e84198fde --- /dev/null +++ b/app/finders/releases_finder.rb @@ -0,0 +1,14 @@ +# frozen_string_literal: true + +class ReleasesFinder + def initialize(project, current_user = nil) + @project = project + @current_user = current_user + end + + def execute + return Release.none unless Ability.allowed?(@current_user, :read_release, @project) + + @project.releases.sorted + end +end diff --git a/app/helpers/appearances_helper.rb b/app/helpers/appearances_helper.rb index 3f69af50f25..473c90c882c 100644 --- a/app/helpers/appearances_helper.rb +++ b/app/helpers/appearances_helper.rb @@ -11,7 +11,7 @@ module AppearancesHelper end def brand_image - image_tag(current_appearance.logo) if current_appearance&.logo? + image_tag(current_appearance.logo_path) if current_appearance&.logo? end def brand_text @@ -28,7 +28,7 @@ module AppearancesHelper def brand_header_logo if current_appearance&.header_logo? - image_tag current_appearance.header_logo + image_tag current_appearance.header_logo_path else render 'shared/logo.svg' end diff --git a/app/helpers/application_helper.rb b/app/helpers/application_helper.rb index 74042f0bae8..82bb2d1a805 100644 --- a/app/helpers/application_helper.rb +++ b/app/helpers/application_helper.rb @@ -171,7 +171,6 @@ module ApplicationHelper def page_filter_path(options = {}) without = options.delete(:without) - add_label = options.delete(:label) options = request.query_parameters.merge(options) @@ -181,11 +180,7 @@ module ApplicationHelper end end - params = options.compact - - params.delete(:label_name) unless add_label - - "#{request.path}?#{params.to_param}" + "#{request.path}?#{options.compact.to_param}" end def outdated_browser? diff --git a/app/helpers/application_settings_helper.rb b/app/helpers/application_settings_helper.rb index 086bb38ce9a..5a7c005fd06 100644 --- a/app/helpers/application_settings_helper.rb +++ b/app/helpers/application_settings_helper.rb @@ -26,6 +26,18 @@ module ApplicationSettingsHelper end end + def all_protocols_enabled? + Gitlab::CurrentSettings.enabled_git_access_protocol.blank? + end + + def ssh_enabled? + all_protocols_enabled? || enabled_protocol == 'ssh' + end + + def http_enabled? + all_protocols_enabled? || enabled_protocol == 'http' + end + def enabled_project_button(project, protocol) case protocol when 'ssh' @@ -218,7 +230,8 @@ module ApplicationSettingsHelper :version_check_enabled, :web_ide_clientside_preview_enabled, :diff_max_patch_bytes, - :commit_email_hostname + :commit_email_hostname, + :protected_ci_variables ] end diff --git a/app/helpers/blob_helper.rb b/app/helpers/blob_helper.rb index bd42f00944f..3dea0975beb 100644 --- a/app/helpers/blob_helper.rb +++ b/app/helpers/blob_helper.rb @@ -140,36 +140,6 @@ module BlobHelper Gitlab::Sanitizers::SVG.clean(data) end - # Remove once https://gitlab.com/gitlab-org/gitlab-ce/issues/36103 is closed - # and :workhorse_set_content_type flag is removed - # If we blindly set the 'real' content type when serving a Git blob we - # are enabling XSS attacks. An attacker could upload e.g. a Javascript - # file to a Git repository, trick the browser of a victim into - # downloading the blob, and then the 'application/javascript' content - # type would tell the browser to execute the attacker's Javascript. By - # overriding the content type and setting it to 'text/plain' (in the - # example of Javascript) we tell the browser of the victim not to - # execute untrusted data. - def safe_content_type(blob) - if blob.extension == 'svg' - blob.mime_type - elsif blob.text? - 'text/plain; charset=utf-8' - elsif blob.image? - blob.content_type - else - 'application/octet-stream' - end - end - - def content_disposition(blob, inline) - # Remove the following line when https://gitlab.com/gitlab-org/gitlab-ce/issues/36103 - # is closed and :workhorse_set_content_type flag is removed - return 'attachment' if blob.extension == 'svg' - - inline ? 'inline' : 'attachment' - end - def ref_project @ref_project ||= @target_project || @project end @@ -223,7 +193,7 @@ module BlobHelper def open_raw_blob_button(blob) return if blob.empty? - return if blob.raw_binary? || blob.stored_externally? + return if blob.binary? || blob.stored_externally? title = 'Open raw' link_to icon('file-code-o'), blob_raw_path, class: 'btn btn-sm has-tooltip', target: '_blank', rel: 'noopener noreferrer', title: title, data: { container: 'body' } diff --git a/app/helpers/ci_variables_helper.rb b/app/helpers/ci_variables_helper.rb new file mode 100644 index 00000000000..e3728804c2a --- /dev/null +++ b/app/helpers/ci_variables_helper.rb @@ -0,0 +1,15 @@ +# frozen_string_literal: true + +module CiVariablesHelper + def ci_variable_protected_by_default? + Gitlab::CurrentSettings.current_application_settings.protected_ci_variables + end + + def ci_variable_protected?(variable, only_key_value) + if variable && !only_key_value + variable.protected + else + ci_variable_protected_by_default? + end + end +end diff --git a/app/helpers/diff_helper.rb b/app/helpers/diff_helper.rb index b6844d36052..32431959851 100644 --- a/app/helpers/diff_helper.rb +++ b/app/helpers/diff_helper.rb @@ -138,30 +138,6 @@ module DiffHelper !diff_file.deleted_file? && @merge_request && @merge_request.source_project end - def diff_render_error_reason(viewer) - case viewer.render_error - when :too_large - "it is too large" - when :server_side_but_stored_externally - case viewer.diff_file.external_storage - when :lfs - 'it is stored in LFS' - else - 'it is stored externally' - end - end - end - - def diff_render_error_options(viewer) - diff_file = viewer.diff_file - options = [] - - blob_url = project_blob_path(@project, tree_join(diff_file.content_sha, diff_file.file_path)) - options << link_to('view the blob', blob_url) - - options - end - def diff_file_changed_icon(diff_file) if diff_file.deleted_file? "file-deletion" diff --git a/app/helpers/emails_helper.rb b/app/helpers/emails_helper.rb index 2d2e89a2a50..fa5d3ae474a 100644 --- a/app/helpers/emails_helper.rb +++ b/app/helpers/emails_helper.rb @@ -58,7 +58,7 @@ module EmailsHelper def header_logo if current_appearance&.header_logo? image_tag( - current_appearance.header_logo, + current_appearance.header_logo_path, style: 'height: 50px' ) else @@ -98,4 +98,29 @@ module EmailsHelper "#{string} on #{Gitlab.config.gitlab.host}" end + + def create_list_id_string(project, list_id_max_length = 255) + project_path_as_domain = project.full_path.downcase + .split('/').reverse.join('/') + .gsub(%r{[^a-z0-9\/]}, '-') + .gsub(%r{\/+}, '.') + .gsub(/(\A\.+|\.+\z)/, '') + + max_domain_length = list_id_max_length - Gitlab.config.gitlab.host.length - project.id.to_s.length - 2 + + if max_domain_length < 3 + return project.id.to_s + "..." + Gitlab.config.gitlab.host + end + + if project_path_as_domain.length > max_domain_length + project_path_as_domain = project_path_as_domain.slice(0, max_domain_length) + + last_dot_index = project_path_as_domain[0..-2].rindex(".") + last_dot_index ||= max_domain_length - 2 + + project_path_as_domain = project_path_as_domain.slice(0, last_dot_index).concat("..") + end + + project.id.to_s + "." + project_path_as_domain + "." + Gitlab.config.gitlab.host + end end diff --git a/app/helpers/groups_helper.rb b/app/helpers/groups_helper.rb index 866fc555856..4a9ed123161 100644 --- a/app/helpers/groups_helper.rb +++ b/app/helpers/groups_helper.rb @@ -126,7 +126,7 @@ module GroupsHelper end def supports_nested_groups? - Group.supports_nested_groups? + Group.supports_nested_objects? end private diff --git a/app/helpers/issuables_helper.rb b/app/helpers/issuables_helper.rb index da991458ea7..5f7147508c7 100644 --- a/app/helpers/issuables_helper.rb +++ b/app/helpers/issuables_helper.rb @@ -23,30 +23,41 @@ module IssuablesHelper end end - def sidebar_due_date_tooltip_label(issuable) - if issuable.due_date - "#{_('Due date')}<br />#{due_date_remaining_days(issuable)}" - else - _('Due date') - end + def sidebar_milestone_tooltip_label(milestone) + return _('Milestone') unless milestone.present? + + [milestone[:title], sidebar_milestone_remaining_days(milestone) || _('Milestone')].join('<br/>') + end + + def sidebar_milestone_remaining_days(milestone) + due_date_with_remaining_days(milestone[:due_date], milestone[:start_date]) + end + + def sidebar_due_date_tooltip_label(due_date) + [_('Due date'), due_date_with_remaining_days(due_date)].compact.join('<br/>') end - def due_date_remaining_days(issuable) - remaining_days_in_words = remaining_days_in_words(issuable) + def due_date_with_remaining_days(due_date, start_date = nil) + return unless due_date - "#{issuable.due_date.to_s(:medium)} (#{remaining_days_in_words})" + "#{due_date.to_s(:medium)} (#{remaining_days_in_words(due_date, start_date)})" + end + + def sidebar_label_filter_path(base_path, label_name) + query_params = { label_name: [label_name] }.to_query + + "#{base_path}?#{query_params}" end def multi_label_name(current_labels, default_label) - if current_labels && current_labels.any? - title = current_labels.first.try(:title) - if current_labels.size > 1 - "#{title} +#{current_labels.size - 1} more" - else - title - end + return default_label if current_labels.blank? + + title = current_labels.first.try(:title) || current_labels.first[:title] + + if current_labels.size > 1 + "#{title} +#{current_labels.size - 1} more" else - default_label + title end end @@ -197,19 +208,11 @@ module IssuablesHelper output.join.html_safe end - # rubocop: disable CodeReuse/ActiveRecord - def issuable_todo(issuable) - if current_user - current_user.todos.find_by(target: issuable, state: :pending) - end - end - # rubocop: enable CodeReuse/ActiveRecord - def issuable_labels_tooltip(labels, limit: 5) first, last = labels.partition.with_index { |_, i| i < limit } if labels && labels.any? - label_names = first.collect(&:name) + label_names = first.collect { |label| label.fetch(:title) } label_names << "and #{last.size} more" unless last.empty? label_names.join(', ') @@ -356,12 +359,6 @@ module IssuablesHelper issuable.model_name.human.downcase end - def selected_labels - Array(params[:label_name]).map do |label_name| - Label.new(title: label_name) - end - end - def has_filter_bar_param? finder.class.scalar_params.any? { |p| params[p].present? } end @@ -386,19 +383,20 @@ module IssuablesHelper params[:issuable_template] if issuable_templates(issuable).any? { |template| template[:name] == params[:issuable_template] } end - def issuable_todo_button_data(issuable, todo, is_collapsed) + def issuable_todo_button_data(issuable, is_collapsed) { - todo_text: "Add todo", - mark_text: "Mark todo as done", - todo_icon: (is_collapsed ? sprite_icon('todo-add') : nil), - mark_icon: (is_collapsed ? sprite_icon('todo-done', css_class: 'todo-undone') : nil), - issuable_id: issuable.id, - issuable_type: issuable.class.name.underscore, - url: project_todos_path(@project), - delete_path: (dashboard_todo_path(todo) if todo), - placement: (is_collapsed ? 'left' : nil), - container: (is_collapsed ? 'body' : nil), - boundary: 'viewport' + todo_text: _('Add todo'), + mark_text: _('Mark todo as done'), + todo_icon: sprite_icon('todo-add'), + mark_icon: sprite_icon('todo-done', css_class: 'todo-undone'), + issuable_id: issuable[:id], + issuable_type: issuable[:type], + create_path: issuable[:create_todo_path], + delete_path: issuable.dig(:current_user, :todo, :delete_path), + placement: is_collapsed ? 'left' : nil, + container: is_collapsed ? 'body' : nil, + boundary: 'viewport', + is_collapsed: is_collapsed } end @@ -418,27 +416,20 @@ module IssuablesHelper end end - def issuable_sidebar_options(issuable, can_edit_issuable) + def issuable_sidebar_options(issuable) { - endpoint: "#{issuable_json_path(issuable)}?serializer=sidebar", - toggleSubscriptionEndpoint: toggle_subscription_path(issuable), - moveIssueEndpoint: move_namespace_project_issue_path(namespace_id: issuable.project.namespace.to_param, project_id: issuable.project, id: issuable), - projectsAutocompleteEndpoint: autocomplete_projects_path(project_id: @project.id), - editable: can_edit_issuable, - currentUser: UserSerializer.new.represent(current_user), + endpoint: "#{issuable[:issuable_json_path]}?serializer=sidebar_extras", + toggleSubscriptionEndpoint: issuable[:toggle_subscription_path], + moveIssueEndpoint: issuable[:move_issue_path], + projectsAutocompleteEndpoint: issuable[:projects_autocomplete_path], + editable: issuable.dig(:current_user, :can_edit), + currentUser: issuable[:current_user], rootPath: root_path, - fullPath: @project.full_path + fullPath: issuable[:project_full_path] } end def parent @project || @group end - - def issuable_milestone_tooltip_title(issuable) - if issuable.milestone - milestone_tooltip = milestone_tooltip_title(issuable.milestone) - _('Milestone') + (milestone_tooltip ? ': ' + milestone_tooltip : '') - end - end end diff --git a/app/helpers/milestones_helper.rb b/app/helpers/milestones_helper.rb index 9666080092b..50aec83b867 100644 --- a/app/helpers/milestones_helper.rb +++ b/app/helpers/milestones_helper.rb @@ -114,12 +114,6 @@ module MilestonesHelper end end - def milestone_tooltip_title(milestone) - if milestone - "#{milestone.title}<br />#{milestone_tooltip_due_date(milestone)}" - end - end - def milestone_time_for(date, date_type) title = date_type == :start ? "Start date" : "End date" @@ -173,7 +167,7 @@ module MilestonesHelper def milestone_tooltip_due_date(milestone) if milestone.due_date - "#{milestone.due_date.to_s(:medium)} (#{remaining_days_in_words(milestone)})" + "#{milestone.due_date.to_s(:medium)} (#{remaining_days_in_words(milestone.due_date, milestone.start_date)})" else _('Milestone') end @@ -237,12 +231,15 @@ module MilestonesHelper end end - def group_or_dashboard_milestone_path(milestone) - if milestone.group_milestone? - group_milestone_path(milestone.group, milestone.iid, milestone: { title: milestone.title }) - else - dashboard_milestone_path(milestone.safe_title, title: milestone.title) - end + def group_or_project_milestone_path(milestone) + params = + if milestone.group_milestone? + { milestone: { title: milestone.title } } + else + { title: milestone.title } + end + + milestone_path(milestone.milestone, params) end def can_admin_project_milestones? diff --git a/app/helpers/projects_helper.rb b/app/helpers/projects_helper.rb index 7c8557a1a8a..0cfc2db3285 100644 --- a/app/helpers/projects_helper.rb +++ b/app/helpers/projects_helper.rb @@ -271,13 +271,27 @@ module ProjectsHelper params[:legacy_render] ? { markdown_engine: :redcarpet } : {} end + def explore_projects_tab? + current_page?(explore_projects_path) || + current_page?(trending_explore_projects_path) || + current_page?(starred_explore_projects_path) + end + + def show_merge_request_count?(disabled: false, compact_mode: false) + !disabled && !compact_mode && Feature.enabled?(:project_list_show_mr_count, default_enabled: true) + end + + def show_issue_count?(disabled: false, compact_mode: false) + !disabled && !compact_mode && Feature.enabled?(:project_list_show_issue_count, default_enabled: true) + end + private def get_project_nav_tabs(project, current_user) nav_tabs = [:home] if !project.empty_repo? && can?(current_user, :download_code, project) - nav_tabs << [:files, :commits, :network, :graphs, :forks] + nav_tabs << [:files, :commits, :network, :graphs, :forks, :releases] end if project.repo_exists? && can?(current_user, :read_merge_request, project) @@ -519,6 +533,7 @@ module ProjectsHelper %w[ projects#show projects#activity + releases#index cycle_analytics#show ] end @@ -550,7 +565,6 @@ module ProjectsHelper projects/repositories tags branches - releases graphs network ] diff --git a/app/helpers/snippets_helper.rb b/app/helpers/snippets_helper.rb index c7d31f3469d..ecb2b2d707b 100644 --- a/app/helpers/snippets_helper.rb +++ b/app/helpers/snippets_helper.rb @@ -110,7 +110,7 @@ module SnippetsHelper def embedded_snippet_raw_button blob = @snippet.blob - return if blob.empty? || blob.raw_binary? || blob.stored_externally? + return if blob.empty? || blob.binary? || blob.stored_externally? snippet_raw_url = if @snippet.is_a?(PersonalSnippet) raw_snippet_url(@snippet) @@ -130,12 +130,4 @@ module SnippetsHelper link_to external_snippet_icon('download'), download_url, class: 'btn', target: '_blank', title: 'Download', rel: 'noopener noreferrer' end - - def public_snippet? - if @snippet.project_id? - can?(nil, :read_project_snippet, @snippet) - else - can?(nil, :read_personal_snippet, @snippet) - end - end end diff --git a/app/helpers/sorting_helper.rb b/app/helpers/sorting_helper.rb index 67c808b167a..02762897c89 100644 --- a/app/helpers/sorting_helper.rb +++ b/app/helpers/sorting_helper.rb @@ -159,27 +159,28 @@ module SortingHelper sort_options_hash[sort_value] end + def issuable_sort_icon_suffix(sort_value) + case sort_value + when sort_value_milestone, sort_value_due_date, /_asc\z/ + 'lowest' + else + 'highest' + end + end + def issuable_sort_direction_button(sort_value) link_class = 'btn btn-default has-tooltip reverse-sort-btn qa-reverse-sort' reverse_sort = issuable_reverse_sort_order_hash[sort_value] if reverse_sort - reverse_url = page_filter_path(sort: reverse_sort, label: true) + reverse_url = page_filter_path(sort: reverse_sort) else reverse_url = '#' link_class += ' disabled' end link_to(reverse_url, type: 'button', class: link_class, title: 'Sort direction') do - icon_suffix = - case sort_value - when sort_value_milestone, sort_value_due_date, /_asc\z/ - 'lowest' - else - 'highest' - end - - sprite_icon("sort-#{icon_suffix}", size: 16) + sprite_icon("sort-#{issuable_sort_icon_suffix(sort_value)}", size: 16) end end @@ -233,7 +234,7 @@ module SortingHelper end def sort_title_milestone - s_('SortOptions|Milestone') + s_('SortOptions|Milestone due date') end def sort_title_milestone_later diff --git a/app/helpers/users_helper.rb b/app/helpers/users_helper.rb index bde9ca0cbf2..73c1402eae5 100644 --- a/app/helpers/users_helper.rb +++ b/app/helpers/users_helper.rb @@ -8,7 +8,7 @@ module UsersHelper end def user_email_help_text(user) - return 'We also use email for avatar detection if no avatar is uploaded.' unless user.unconfirmed_email.present? + return 'We also use email for avatar detection if no avatar is uploaded' unless user.unconfirmed_email.present? confirmation_link = link_to 'Resend confirmation e-mail', user_confirmation_path(user: { email: @user.unconfirmed_email }), method: :post diff --git a/app/helpers/version_check_helper.rb b/app/helpers/version_check_helper.rb index ab77b149072..5e519cf5c19 100644 --- a/app/helpers/version_check_helper.rb +++ b/app/helpers/version_check_helper.rb @@ -6,8 +6,7 @@ module VersionCheckHelper return unless Gitlab::CurrentSettings.version_check_enabled return if User.single_user&.requires_usage_stats_consent? - image_url = VersionCheck.new.url - image_tag image_url, class: 'js-version-status-badge' + image_tag VersionCheck.url, class: 'js-version-status-badge' end def link_to_version diff --git a/app/helpers/workhorse_helper.rb b/app/helpers/workhorse_helper.rb index e9fc39e451b..bb5b1555dc4 100644 --- a/app/helpers/workhorse_helper.rb +++ b/app/helpers/workhorse_helper.rb @@ -7,8 +7,7 @@ module WorkhorseHelper def send_git_blob(repository, blob, inline: true) headers.store(*Gitlab::Workhorse.send_git_blob(repository, blob)) - headers['Content-Disposition'] = content_disposition(blob, inline) - headers['Content-Type'] = safe_content_type(blob) + headers['Content-Disposition'] = inline ? 'inline' : 'attachment' # If enabled, this will override the values set above workhorse_set_content_type! @@ -47,6 +46,6 @@ module WorkhorseHelper end def workhorse_set_content_type! - headers[Gitlab::Workhorse::DETECT_HEADER] = "true" if Feature.enabled?(:workhorse_set_content_type) + headers[Gitlab::Workhorse::DETECT_HEADER] = "true" end end diff --git a/app/mailers/emails/issues.rb b/app/mailers/emails/issues.rb index 93b51fb1774..370e6d2f90b 100644 --- a/app/mailers/emails/issues.rb +++ b/app/mailers/emails/issues.rb @@ -56,7 +56,9 @@ module Emails @milestone = milestone @milestone_url = milestone_url(@milestone) - mail_answer_thread(@issue, issue_thread_options(updated_by_user_id, recipient_id, reason)) + mail_answer_thread(@issue, issue_thread_options(updated_by_user_id, recipient_id, reason).merge({ + template_name: 'changed_milestone_email' + })) end def issue_status_changed_email(recipient_id, issue_id, status, updated_by_user_id, reason = nil) diff --git a/app/mailers/emails/merge_requests.rb b/app/mailers/emails/merge_requests.rb index 6524d0c2087..9ba8f92fcbf 100644 --- a/app/mailers/emails/merge_requests.rb +++ b/app/mailers/emails/merge_requests.rb @@ -51,7 +51,9 @@ module Emails @milestone = milestone @milestone_url = milestone_url(@milestone) - mail_answer_thread(@merge_request, merge_request_thread_options(updated_by_user_id, recipient_id, reason)) + mail_answer_thread(@merge_request, merge_request_thread_options(updated_by_user_id, recipient_id, reason).merge({ + template_name: 'changed_milestone_email' + })) end def closed_merge_request_email(recipient_id, merge_request_id, updated_by_user_id, reason = nil) diff --git a/app/mailers/notify.rb b/app/mailers/notify.rb index 49a3920e43f..efa1233b434 100644 --- a/app/mailers/notify.rb +++ b/app/mailers/notify.rb @@ -3,6 +3,7 @@ class Notify < BaseMailer include ActionDispatch::Routing::PolymorphicRoutes include GitlabRoutingHelper + include EmailsHelper include Emails::Issues include Emails::MergeRequests @@ -15,6 +16,7 @@ class Notify < BaseMailer include Emails::AutoDevops include Emails::RemoteMirrors + helper MilestonesHelper helper MergeRequestsHelper helper DiffHelper helper BlobHelper @@ -194,6 +196,7 @@ class Notify < BaseMailer headers['X-GitLab-Project'] = @project.name headers['X-GitLab-Project-Id'] = @project.id headers['X-GitLab-Project-Path'] = @project.full_path + headers['List-Id'] = "#{@project.full_path} <#{create_list_id_string(@project)}>" end def add_unsubscription_headers_and_links diff --git a/app/models/appearance.rb b/app/models/appearance.rb index bffba3e13fa..e114c435b67 100644 --- a/app/models/appearance.rb +++ b/app/models/appearance.rb @@ -28,4 +28,32 @@ class Appearance < ActiveRecord::Base errors.add(:single_appearance_row, 'Only 1 appearances row can exist') end end + + def logo_path + logo_system_path(logo, 'logo') + end + + def header_logo_path + logo_system_path(header_logo, 'header_logo') + end + + def favicon_path + logo_system_path(favicon, 'favicon') + end + + private + + def logo_system_path(logo, mount_type) + return unless logo&.upload + + # If we're using a CDN, we need to use the full URL + asset_host = ActionController::Base.asset_host + local_path = Gitlab::Routing.url_helpers.appearance_upload_path( + filename: logo.filename, + id: logo.upload.model_id, + model: 'appearance', + mounted_as: mount_type) + + Gitlab::Utils.append_path(asset_host, local_path) + end end diff --git a/app/models/application_setting.rb b/app/models/application_setting.rb index 4319db42019..88746375c67 100644 --- a/app/models/application_setting.rb +++ b/app/models/application_setting.rb @@ -302,7 +302,8 @@ class ApplicationSetting < ActiveRecord::Base user_show_add_ssh_key_message: true, usage_stats_set_by_user_id: nil, diff_max_patch_bytes: Gitlab::Git::Diff::DEFAULT_MAX_PATCH_BYTES, - commit_email_hostname: default_commit_email_hostname + commit_email_hostname: default_commit_email_hostname, + protected_ci_variables: false } end @@ -311,7 +312,7 @@ class ApplicationSetting < ActiveRecord::Base end def self.create_from_defaults - create(defaults) + build_from_defaults.tap(&:save) end def self.human_attribute_name(attr, _options = {}) @@ -382,7 +383,7 @@ class ApplicationSetting < ActiveRecord::Base end def restricted_visibility_levels=(levels) - super(levels.map { |level| Gitlab::VisibilityLevel.level_value(level) }) + super(levels&.map { |level| Gitlab::VisibilityLevel.level_value(level) }) end def strip_sentry_values diff --git a/app/models/blob.rb b/app/models/blob.rb index 66a0925c495..c5766eb0327 100644 --- a/app/models/blob.rb +++ b/app/models/blob.rb @@ -102,7 +102,7 @@ class Blob < SimpleDelegator # If the blob is a text based blob the content is converted to UTF-8 and any # invalid byte sequences are replaced. def data - if binary? + if binary_in_repo? super else @data ||= super.encode(Encoding::UTF_8, invalid: :replace, undef: :replace) @@ -149,7 +149,7 @@ class Blob < SimpleDelegator # an LFS pointer, we assume the file stored in LFS is binary, unless a # text-based rich blob viewer matched on the file's extension. Otherwise, this # depends on the type of the blob itself. - def raw_binary? + def binary? if stored_externally? if rich_viewer rich_viewer.binary? @@ -161,7 +161,7 @@ class Blob < SimpleDelegator true end else - binary? + binary_in_repo? end end @@ -180,7 +180,7 @@ class Blob < SimpleDelegator end def readable_text? - text? && !stored_externally? && !truncated? + text_in_repo? && !stored_externally? && !truncated? end def simple_viewer @@ -220,7 +220,7 @@ class Blob < SimpleDelegator def simple_viewer_class if empty? BlobViewer::Empty - elsif raw_binary? + elsif binary? BlobViewer::Download else # text BlobViewer::Text diff --git a/app/models/blob_viewer/base.rb b/app/models/blob_viewer/base.rb index eaaf9af1330..df6b9bb2f0b 100644 --- a/app/models/blob_viewer/base.rb +++ b/app/models/blob_viewer/base.rb @@ -16,7 +16,7 @@ module BlobViewer def initialize(blob) @blob = blob - @initially_binary = blob.binary? + @initially_binary = blob.binary_in_repo? end def self.partial_path @@ -52,7 +52,7 @@ module BlobViewer end def self.can_render?(blob, verify_binary: true) - return false if verify_binary && binary? != blob.binary? + return false if verify_binary && binary? != blob.binary_in_repo? return true if extensions&.include?(blob.extension) return true if file_types&.include?(blob.file_type) @@ -72,7 +72,7 @@ module BlobViewer end def binary_detected_after_load? - !@initially_binary && blob.binary? + !@initially_binary && blob.binary_in_repo? end # This method is used on the server side to check whether we can attempt to diff --git a/app/models/broadcast_message.rb b/app/models/broadcast_message.rb index 277f7c2717c..2d237383e60 100644 --- a/app/models/broadcast_message.rb +++ b/app/models/broadcast_message.rb @@ -22,49 +22,30 @@ class BroadcastMessage < ActiveRecord::Base after_commit :flush_redis_cache def self.current - raw_messages = Rails.cache.fetch(CACHE_KEY, expires_in: cache_expires_in) do + messages = cache.fetch(CACHE_KEY, as: BroadcastMessage, expires_in: cache_expires_in) do remove_legacy_cache_key - current_and_future_messages.to_json + current_and_future_messages end - messages = decode_messages(raw_messages) - return [] unless messages&.present? now_or_future = messages.select(&:now_or_future?) # If there are cached entries but none are to be displayed we'll purge the # cache so we don't keep running this code all the time. - Rails.cache.delete(CACHE_KEY) if now_or_future.empty? + cache.expire(CACHE_KEY) if now_or_future.empty? now_or_future.select(&:now?) end - def self.decode_messages(raw_messages) - return unless raw_messages&.present? - - message_list = ActiveSupport::JSON.decode(raw_messages) - - return unless message_list.is_a?(Array) - - valid_attr = BroadcastMessage.attribute_names - - message_list.map do |raw| - BroadcastMessage.new(raw) if valid_cache_entry?(raw, valid_attr) - end.compact - rescue ActiveSupport::JSON.parse_error - end - - def self.valid_cache_entry?(raw, valid_attr) - return false unless raw.is_a?(Hash) - - (raw.keys - valid_attr).empty? - end - def self.current_and_future_messages where('ends_at > :now', now: Time.zone.now).order_id_asc end + def self.cache + Gitlab::JsonCache.new(cache_key_with_version: false) + end + def self.cache_expires_in nil end @@ -74,7 +55,7 @@ class BroadcastMessage < ActiveRecord::Base # environment a one-shot migration would not work because the cache # would be repopulated by a node that has not been upgraded. def self.remove_legacy_cache_key - Rails.cache.delete(LEGACY_CACHE_KEY) + cache.expire(LEGACY_CACHE_KEY) end def active? @@ -102,7 +83,7 @@ class BroadcastMessage < ActiveRecord::Base end def flush_redis_cache - Rails.cache.delete(CACHE_KEY) + self.class.cache.expire(CACHE_KEY) self.class.remove_legacy_cache_key end end diff --git a/app/models/ci/build.rb b/app/models/ci/build.rb index e2917049902..aeb35538d67 100644 --- a/app/models/ci/build.rb +++ b/app/models/ci/build.rb @@ -10,6 +10,7 @@ module Ci include Importable include Gitlab::Utils::StrongMemoize include Deployable + include HasRef belongs_to :project, inverse_of: :builds belongs_to :runner @@ -220,6 +221,10 @@ module Ci next unless build.project build.deployment&.drop + end + + after_transition any => [:failed] do |build| + next unless build.project if build.retry_failure? begin @@ -640,11 +645,11 @@ module Ci def secret_group_variables return [] unless project.group - project.group.ci_variables_for(ref, project) + project.group.ci_variables_for(git_ref, project) end def secret_project_variables(environment: persisted_environment) - project.ci_variables_for(ref: ref, environment: environment) + project.ci_variables_for(ref: git_ref, environment: environment) end def steps @@ -840,6 +845,7 @@ module Ci variables.append(key: 'CI_JOB_NAME', value: name) variables.append(key: 'CI_JOB_STAGE', value: stage) variables.append(key: 'CI_COMMIT_SHA', value: sha) + variables.append(key: 'CI_COMMIT_SHORT_SHA', value: short_sha) variables.append(key: 'CI_COMMIT_BEFORE_SHA', value: before_sha) variables.append(key: 'CI_COMMIT_REF_NAME', value: ref) variables.append(key: 'CI_COMMIT_REF_SLUG', value: ref_slug) diff --git a/app/models/ci/pipeline.rb b/app/models/ci/pipeline.rb index d06022a0fb7..1f5017cc3c3 100644 --- a/app/models/ci/pipeline.rb +++ b/app/models/ci/pipeline.rb @@ -11,6 +11,7 @@ module Ci include Gitlab::Utils::StrongMemoize include AtomicInternalId include EnumWithNil + include HasRef belongs_to :project, inverse_of: :all_pipelines belongs_to :user @@ -56,11 +57,7 @@ module Ci validates :tag, inclusion: { in: [false], if: :merge_request? } validates :status, presence: { unless: :importing? } validate :valid_commit_sha, unless: :importing? - - # Replace validator below with - # `validates :source, presence: { unless: :importing? }, on: :create` - # when removing Gitlab.rails5? code. - validate :valid_source, unless: :importing?, on: :create + validates :source, exclusion: { in: %w(unknown), unless: :importing? }, on: :create after_create :keep_around_commits, unless: :importing? @@ -68,11 +65,7 @@ module Ci # this `Hash` with new values. enum_with_nil source: ::Ci::PipelineEnums.sources - enum_with_nil config_source: { - unknown_source: nil, - repository_source: 1, - auto_devops_source: 2 - } + enum_with_nil config_source: ::Ci::PipelineEnums.config_sources # We use `Ci::PipelineEnums.failure_reasons` here so that EE can more easily # extend this `Hash` with new values. @@ -388,7 +381,7 @@ module Ci end def branch? - !tag? && !merge_request? + super && !merge_request? end def stuck? @@ -588,7 +581,7 @@ module Ci end def protected_ref? - strong_memoize(:protected_ref) { project.protected_for?(ref) } + strong_memoize(:protected_ref) { project.protected_for?(git_ref) } end def legacy_trigger @@ -720,14 +713,10 @@ module Ci end def git_ref - if branch? - Gitlab::Git::BRANCH_REF_PREFIX + ref.to_s - elsif merge_request? + if merge_request? Gitlab::Git::BRANCH_REF_PREFIX + ref.to_s - elsif tag? - Gitlab::Git::TAG_REF_PREFIX + ref.to_s else - raise ArgumentError, 'Invalid pipeline type!' + super end end @@ -742,11 +731,5 @@ module Ci project.repository.keep_around(self.sha, self.before_sha) end - - def valid_source - if source.nil? || source == "unknown" - errors.add(:source, "invalid source") - end - end end end diff --git a/app/models/ci/pipeline_enums.rb b/app/models/ci/pipeline_enums.rb index c0f16066e0b..2994aaae4aa 100644 --- a/app/models/ci/pipeline_enums.rb +++ b/app/models/ci/pipeline_enums.rb @@ -25,5 +25,15 @@ module Ci merge_request: 10 } end + + # Returns the `Hash` to use for creating the `config_sources` enum for + # `Ci::Pipeline`. + def self.config_sources + { + unknown_source: nil, + repository_source: 1, + auto_devops_source: 2 + } + end end end diff --git a/app/models/ci/runner.rb b/app/models/ci/runner.rb index 2693386443a..8249199e76f 100644 --- a/app/models/ci/runner.rb +++ b/app/models/ci/runner.rb @@ -58,8 +58,7 @@ module Ci # BACKWARD COMPATIBILITY: There are needed to maintain compatibility with `AVAILABLE_SCOPES` used by `lib/api/runners.rb` scope :deprecated_shared, -> { instance_type } - # this should get replaced with `project_type.or(group_type)` once using Rails5 - scope :deprecated_specific, -> { where(runner_type: [runner_types[:project_type], runner_types[:group_type]]) } + scope :deprecated_specific, -> { project_type.or(group_type) } scope :belonging_to_project, -> (project_id) { joins(:runner_projects).where(ci_runner_projects: { project_id: project_id }) @@ -67,7 +66,7 @@ module Ci scope :belonging_to_parent_group_of_project, -> (project_id) { project_groups = ::Group.joins(:projects).where(projects: { id: project_id }) - hierarchy_groups = Gitlab::GroupHierarchy.new(project_groups).base_and_ancestors + hierarchy_groups = Gitlab::ObjectHierarchy.new(project_groups).base_and_ancestors joins(:groups).where(namespaces: { id: hierarchy_groups }) } diff --git a/app/models/clusters/applications/cert_manager.rb b/app/models/clusters/applications/cert_manager.rb index 74ef7c7e145..c758577815a 100644 --- a/app/models/clusters/applications/cert_manager.rb +++ b/app/models/clusters/applications/cert_manager.rb @@ -3,7 +3,7 @@ module Clusters module Applications class CertManager < ActiveRecord::Base - VERSION = 'v0.5.0'.freeze + VERSION = 'v0.5.2'.freeze self.table_name = 'clusters_applications_cert_managers' diff --git a/app/models/clusters/applications/ingress.rb b/app/models/clusters/applications/ingress.rb index 8f8790585a3..7799f069742 100644 --- a/app/models/clusters/applications/ingress.rb +++ b/app/models/clusters/applications/ingress.rb @@ -23,7 +23,7 @@ module Clusters FETCH_IP_ADDRESS_DELAY = 30.seconds state_machine :status do - before_transition any => [:installed] do |application| + after_transition any => [:installed] do |application| application.run_after_commit do ClusterWaitForIngressIpAddressWorker.perform_in( FETCH_IP_ADDRESS_DELAY, application.name, application.id) diff --git a/app/models/clusters/applications/knative.rb b/app/models/clusters/applications/knative.rb index 168a24da738..5ac152278da 100644 --- a/app/models/clusters/applications/knative.rb +++ b/app/models/clusters/applications/knative.rb @@ -3,7 +3,7 @@ module Clusters module Applications class Knative < ActiveRecord::Base - VERSION = '0.1.3'.freeze + VERSION = '0.2.2'.freeze REPOSITORY = 'https://storage.googleapis.com/triggermesh-charts'.freeze FETCH_IP_ADDRESS_DELAY = 30.seconds @@ -20,7 +20,7 @@ module Clusters self.reactive_cache_key = ->(knative) { [knative.class.model_name.singular, knative.id] } state_machine :status do - before_transition any => [:installed] do |application| + after_transition any => [:installed] do |application| application.run_after_commit do ClusterWaitForIngressIpAddressWorker.perform_in( FETCH_IP_ADDRESS_DELAY, application.name, application.id) diff --git a/app/models/clusters/applications/runner.rb b/app/models/clusters/applications/runner.rb index c931b340b24..0c0247da1fb 100644 --- a/app/models/clusters/applications/runner.rb +++ b/app/models/clusters/applications/runner.rb @@ -3,7 +3,7 @@ module Clusters module Applications class Runner < ActiveRecord::Base - VERSION = '0.1.39'.freeze + VERSION = '0.1.43'.freeze self.table_name = 'clusters_applications_runners' diff --git a/app/models/clusters/platforms/kubernetes.rb b/app/models/clusters/platforms/kubernetes.rb index 867f0edcb07..0dc0c4f80d6 100644 --- a/app/models/clusters/platforms/kubernetes.rb +++ b/app/models/clusters/platforms/kubernetes.rb @@ -106,7 +106,7 @@ module Clusters def terminals(environment) with_reactive_cache do |data| pods = filter_by_label(data[:pods], app: environment.slug) - terminals = pods.flat_map { |pod| terminals_for_pod(api_url, actual_namespace, pod) } + terminals = pods.flat_map { |pod| terminals_for_pod(api_url, actual_namespace, pod) }.compact terminals.each { |terminal| add_terminal_auth(terminal, terminal_auth) } end end @@ -228,7 +228,7 @@ module Clusters return unless namespace_changed? run_after_commit do - ClusterPlatformConfigureWorker.perform_async(cluster_id) + ClusterConfigureWorker.perform_async(cluster_id) end end end diff --git a/app/models/commit.rb b/app/models/commit.rb index a422a0995ff..01f4c58daa1 100644 --- a/app/models/commit.rb +++ b/app/models/commit.rb @@ -469,6 +469,10 @@ class Commit !!merged_merge_request(user) end + def cache_key + "commit:#{sha}" + end + private def commit_reference(from, referable_commit_id, full: false) diff --git a/app/models/concerns/avatarable.rb b/app/models/concerns/avatarable.rb index b42236c1fa2..4687ec7d166 100644 --- a/app/models/concerns/avatarable.rb +++ b/app/models/concerns/avatarable.rb @@ -43,7 +43,18 @@ module Avatarable end def avatar_path(only_path: true, size: nil) - return unless self[:avatar].present? + unless self.try(:id) + return uncached_avatar_path(only_path: only_path, size: size) + end + + # Cache this avatar path only within the request because avatars in + # object storage may be generated with time-limited, signed URLs. + key = "#{self.class.name}:#{self.id}:#{only_path}:#{size}" + Gitlab::SafeRequestStore[key] ||= uncached_avatar_path(only_path: only_path, size: size) + end + + def uncached_avatar_path(only_path: true, size: nil) + return unless self.try(:avatar).present? asset_host = ActionController::Base.asset_host use_asset_host = asset_host.present? diff --git a/app/models/concerns/blob_like.rb b/app/models/concerns/blob_like.rb index f20f01486a5..dc80f8d62f4 100644 --- a/app/models/concerns/blob_like.rb +++ b/app/models/concerns/blob_like.rb @@ -28,7 +28,7 @@ module BlobLike nil end - def binary? + def binary_in_repo? false end diff --git a/app/models/concerns/cacheable_attributes.rb b/app/models/concerns/cacheable_attributes.rb index 75592bb63e2..3d60f6924c1 100644 --- a/app/models/concerns/cacheable_attributes.rb +++ b/app/models/concerns/cacheable_attributes.rb @@ -23,7 +23,12 @@ module CacheableAttributes end def build_from_defaults(attributes = {}) - new(defaults.merge(attributes)) + final_attributes = defaults + .merge(attributes) + .stringify_keys + .slice(*column_names) + + new(final_attributes) end def cached diff --git a/app/models/concerns/descendant.rb b/app/models/concerns/descendant.rb new file mode 100644 index 00000000000..4c436522122 --- /dev/null +++ b/app/models/concerns/descendant.rb @@ -0,0 +1,11 @@ +# frozen_string_literal: true + +module Descendant + extend ActiveSupport::Concern + + class_methods do + def supports_nested_objects? + Gitlab::Database.postgresql? + end + end +end diff --git a/app/models/concerns/discussion_on_diff.rb b/app/models/concerns/discussion_on_diff.rb index 266c37fa3a1..e4e5928f5cf 100644 --- a/app/models/concerns/discussion_on_diff.rb +++ b/app/models/concerns/discussion_on_diff.rb @@ -9,7 +9,7 @@ module DiscussionOnDiff included do delegate :line_code, :original_line_code, - :diff_file, + :note_diff_file, :diff_line, :active?, :created_at_diff?, @@ -39,6 +39,7 @@ module DiscussionOnDiff # Returns an array of at most 16 highlighted lines above a diff note def truncated_diff_lines(highlight: true, diff_limit: nil) + return [] unless on_text? return [] if diff_line.nil? && first_note.is_a?(LegacyDiffNote) diff_limit = [diff_limit, NUMBER_OF_TRUNCATED_DIFF_LINES].compact.min @@ -59,6 +60,13 @@ module DiscussionOnDiff prev_lines end + def diff_file + strong_memoize(:diff_file) do + # Falling back here is important as `note_diff_files` are created async. + fetch_preloaded_diff_file || first_note.diff_file + end + end + def line_code_in_diffs(diff_refs) if active?(diff_refs) line_code @@ -66,4 +74,15 @@ module DiscussionOnDiff original_line_code end end + + private + + def fetch_preloaded_diff_file + fetch_preloaded_diff = + context_noteable && + context_noteable.preloads_discussion_diff_highlighting? && + note_diff_file + + context_noteable.discussions_diffs.find_by_id(note_diff_file.id) if fetch_preloaded_diff + end end diff --git a/app/models/concerns/enum_with_nil.rb b/app/models/concerns/enum_with_nil.rb index 23acfe9a55f..6d0a21cf070 100644 --- a/app/models/concerns/enum_with_nil.rb +++ b/app/models/concerns/enum_with_nil.rb @@ -16,7 +16,7 @@ module EnumWithNil # E.g. for enum_with_nil failure_reason: { unknown_failure: nil } # this overrides auto-generated method `unknown_failure?` define_method("#{key_with_nil}?") do - Gitlab.rails5? ? self[name].nil? : super() + self[name].nil? end # E.g. for enum_with_nil failure_reason: { unknown_failure: nil } @@ -24,7 +24,6 @@ module EnumWithNil define_method(name) do orig = super() - return orig unless Gitlab.rails5? return orig unless orig.nil? self.class.public_send(name.to_s.pluralize).key(nil) # rubocop:disable GitlabSecurity/PublicSend diff --git a/app/models/concerns/has_ref.rb b/app/models/concerns/has_ref.rb new file mode 100644 index 00000000000..d7089294efc --- /dev/null +++ b/app/models/concerns/has_ref.rb @@ -0,0 +1,17 @@ +# frozen_string_literal: true + +module HasRef + extend ActiveSupport::Concern + + def branch? + !tag? + end + + def git_ref + if branch? + Gitlab::Git::BRANCH_REF_PREFIX + ref.to_s + elsif tag? + Gitlab::Git::TAG_REF_PREFIX + ref.to_s + end + end +end diff --git a/app/models/concerns/milestoneish.rb b/app/models/concerns/milestoneish.rb index e44a069b730..055ffe04646 100644 --- a/app/models/concerns/milestoneish.rb +++ b/app/models/concerns/milestoneish.rb @@ -42,7 +42,7 @@ module Milestoneish def issues_visible_to_user(user) memoize_per_user(user, :issues_visible_to_user) do IssuesFinder.new(user, issues_finder_params) - .execute.preload(:assignees).where(milestone_id: milestoneish_ids) + .execute.preload(:assignees).where(milestone_id: milestoneish_id) end end diff --git a/app/models/concerns/noteable.rb b/app/models/concerns/noteable.rb index eb315058c3a..29476654bf7 100644 --- a/app/models/concerns/noteable.rb +++ b/app/models/concerns/noteable.rb @@ -26,10 +26,18 @@ module Noteable DiscussionNote.noteable_types.include?(base_class_name) end + def supports_suggestion? + false + end + def discussions_rendered_on_frontend? false end + def preloads_discussion_diff_highlighting? + false + end + def discussion_notes notes end diff --git a/app/models/concerns/redis_cacheable.rb b/app/models/concerns/redis_cacheable.rb index 69554f18ea2..4bb4ffe2a8e 100644 --- a/app/models/concerns/redis_cacheable.rb +++ b/app/models/concerns/redis_cacheable.rb @@ -49,10 +49,6 @@ module RedisCacheable end def cast_value_from_cache(attribute, value) - if Gitlab.rails5? - self.class.type_for_attribute(attribute.to_s).cast(value) - else - self.class.column_for_attribute(attribute).type_cast_from_database(value) - end + self.class.type_for_attribute(attribute.to_s).cast(value) end end diff --git a/app/models/dashboard_group_milestone.rb b/app/models/dashboard_group_milestone.rb index 32e8104125c..9bcc95e35a5 100644 --- a/app/models/dashboard_group_milestone.rb +++ b/app/models/dashboard_group_milestone.rb @@ -5,9 +5,8 @@ class DashboardGroupMilestone < GlobalMilestone attr_reader :group_name - override :initialize def initialize(milestone) - super(milestone.title, Array(milestone)) + super @group_name = milestone.group.full_name end @@ -19,22 +18,4 @@ class DashboardGroupMilestone < GlobalMilestone .active .map { |m| new(m) } end - - override :group_milestone? - def group_milestone? - @first_milestone.group_milestone? - end - - override :milestoneish_ids - def milestoneish_ids - milestones.map(&:id) - end - - def group - @first_milestone.group - end - - def iid - @first_milestone.iid - end end diff --git a/app/models/dashboard_milestone.rb b/app/models/dashboard_milestone.rb index 96bc8090b81..9b377b70e5b 100644 --- a/app/models/dashboard_milestone.rb +++ b/app/models/dashboard_milestone.rb @@ -1,11 +1,15 @@ # frozen_string_literal: true class DashboardMilestone < GlobalMilestone - def issues_finder_params - { authorized_only: true } + attr_reader :project_name + + def initialize(milestone) + super + + @project_name = milestone.project.full_name end - def dashboard_milestone? + def project_milestone? true end end diff --git a/app/models/diff_note.rb b/app/models/diff_note.rb index c32008aa9c7..279603496b0 100644 --- a/app/models/diff_note.rb +++ b/app/models/diff_note.rb @@ -66,10 +66,23 @@ class DiffNote < Note self.original_position.diff_refs == diff_refs end + def supports_suggestion? + return false unless noteable.supports_suggestion? && on_text? + # We don't want to trigger side-effects of `diff_file` call. + return false unless file = fetch_diff_file + return false unless line = file.line_for_position(self.original_position) + + line&.suggestible? + end + def discussion_first_note? self == discussion.first_note end + def banzai_render_context(field) + super.merge(suggestions_filter_enabled: supports_suggestion?) + end + private def enqueue_diff_file_creation_job diff --git a/app/models/diff_viewer/base.rb b/app/models/diff_viewer/base.rb index 1176861a827..527ee33b83b 100644 --- a/app/models/diff_viewer/base.rb +++ b/app/models/diff_viewer/base.rb @@ -18,7 +18,7 @@ module DiffViewer def initialize(diff_file) @diff_file = diff_file - @initially_binary = diff_file.binary? + @initially_binary = diff_file.binary_in_repo? end def self.partial_path @@ -48,7 +48,7 @@ module DiffViewer def self.can_render_blob?(blob, verify_binary: true) return true if blob.nil? - return false if verify_binary && binary? != blob.binary? + return false if verify_binary && binary? != blob.binary_in_repo? return true if extensions&.include?(blob.extension) return true if file_types&.include?(blob.file_type) @@ -70,20 +70,49 @@ module DiffViewer end def binary_detected_after_load? - !@initially_binary && diff_file.binary? + !@initially_binary && diff_file.binary_in_repo? end # This method is used on the server side to check whether we can attempt to - # render the diff_file at all. Human-readable error messages are found in the - # `BlobHelper#diff_render_error_reason` helper. + # render the diff_file at all. The human-readable error message can be + # retrieved by #render_error_message. def render_error if too_large? :too_large end end + def render_error_message + return unless render_error + + _("This %{viewer} could not be displayed because %{reason}. You can %{options} instead.") % + { + viewer: switcher_title, + reason: render_error_reason, + options: render_error_options.to_sentence(two_words_connector: _(' or '), last_word_connector: _(', or ')) + } + end + def prepare! # To be overridden by subclasses end + + private + + def render_error_options + options = [] + + blob_url = Gitlab::Routing.url_helpers.project_blob_path(diff_file.repository.project, + File.join(diff_file.content_sha, diff_file.file_path)) + options << ActionController::Base.helpers.link_to(_('view the blob'), blob_url) + + options + end + + def render_error_reason + if render_error == :too_large + _("it is too large") + end + end end end diff --git a/app/models/diff_viewer/image.rb b/app/models/diff_viewer/image.rb index c356c2ca50e..350bef1d42a 100644 --- a/app/models/diff_viewer/image.rb +++ b/app/models/diff_viewer/image.rb @@ -9,6 +9,6 @@ module DiffViewer self.extensions = UploaderHelper::IMAGE_EXT self.binary = true self.switcher_icon = 'picture-o' - self.switcher_title = 'image diff' + self.switcher_title = _('image diff') end end diff --git a/app/models/diff_viewer/rich.rb b/app/models/diff_viewer/rich.rb index 2faa1be6567..5caefa2031c 100644 --- a/app/models/diff_viewer/rich.rb +++ b/app/models/diff_viewer/rich.rb @@ -7,7 +7,7 @@ module DiffViewer included do self.type = :rich self.switcher_icon = 'file-text-o' - self.switcher_title = 'rendered diff' + self.switcher_title = _('rendered diff') end end end diff --git a/app/models/diff_viewer/server_side.rb b/app/models/diff_viewer/server_side.rb index 977204e6c97..0877c9dddec 100644 --- a/app/models/diff_viewer/server_side.rb +++ b/app/models/diff_viewer/server_side.rb @@ -24,5 +24,17 @@ module DiffViewer super end + + private + + def render_error_reason + return super unless render_error == :server_side_but_stored_externally + + if diff_file.external_storage == :lfs + _('it is stored in LFS') + else + _('it is stored externally') + end + end end end diff --git a/app/models/diff_viewer/simple.rb b/app/models/diff_viewer/simple.rb index 8d28ca5239a..929d8ad5a7e 100644 --- a/app/models/diff_viewer/simple.rb +++ b/app/models/diff_viewer/simple.rb @@ -7,7 +7,7 @@ module DiffViewer included do self.type = :simple self.switcher_icon = 'code' - self.switcher_title = 'source diff' + self.switcher_title = _('source diff') end end end diff --git a/app/models/environment.rb b/app/models/environment.rb index 934828946b9..cdfe3b7c023 100644 --- a/app/models/environment.rb +++ b/app/models/environment.rb @@ -1,6 +1,7 @@ # frozen_string_literal: true class Environment < ActiveRecord::Base + include Gitlab::Utils::StrongMemoize # Used to generate random suffixes for the slug LETTERS = 'a'..'z' NUMBERS = '0'..'9' @@ -231,7 +232,9 @@ class Environment < ActiveRecord::Base end def deployment_platform - project.deployment_platform(environment: self.name) + strong_memoize(:deployment_platform) do + project.deployment_platform(environment: self.name) + end end private diff --git a/app/models/event.rb b/app/models/event.rb index 2ceef412af5..6a35bca72c5 100644 --- a/app/models/event.rb +++ b/app/models/event.rb @@ -114,19 +114,6 @@ class Event < ActiveRecord::Base end end - # Remove this method when removing Gitlab.rails5? code. - def subclass_from_attributes(attrs) - return super if Gitlab.rails5? - - # Without this Rails will keep calling this method on the returned class, - # resulting in an infinite loop. - return unless self == Event - - action = attrs.with_indifferent_access[inheritance_column].to_i - - PushEvent if action == PUSHED - end - # Update Gitlab::ContributionsCalendar#activity_dates if this changes def contributions where("action = ? OR (target_type IN (?) AND action IN (?)) OR (target_type = ? AND action = ?)", diff --git a/app/models/global_milestone.rb b/app/models/global_milestone.rb index 085ffd16c6a..4e82f3fed27 100644 --- a/app/models/global_milestone.rb +++ b/app/models/global_milestone.rb @@ -3,69 +3,78 @@ class GlobalMilestone include Milestoneish - EPOCH = DateTime.parse('1970-01-01') STATE_COUNT_HASH = { opened: 0, closed: 0, all: 0 }.freeze - attr_accessor :title, :milestones + attr_reader :milestone alias_attribute :name, :title + delegate :title, :state, :due_date, :start_date, :participants, :project, :group, :expires_at, :closed?, :iid, :group_milestone?, :safe_title, :milestoneish_id, to: :milestone + + def to_hash + { + name: title, + title: title, + group_name: group&.full_name, + project_name: project&.full_name + } + end + def for_display - @first_milestone + @milestone end def self.build_collection(projects, params) - params = - { project_ids: projects.map(&:id), state: params[:state] } - - child_milestones = MilestonesFinder.new(params).execute # rubocop: disable CodeReuse/Finder - - milestones = child_milestones.select(:id, :title).group_by(&:title).map do |title, grouped| - milestones_relation = Milestone.where(id: grouped.map(&:id)) - new(title, milestones_relation) - end + items = Milestone.of_projects(projects) + .reorder_by_due_date_asc + .order_by_name_asc - milestones.sort_by { |milestone| milestone.due_date || EPOCH } + Milestone.filter_by_state(items, params[:state]).map { |m| new(m) } end + # necessary for legacy milestones def self.build(projects, title) - child_milestones = Milestone.of_projects(projects).where(title: title) - return if child_milestones.blank? + milestones = Milestone.of_projects(projects).where(title: title) + return if milestones.blank? - new(title, child_milestones) + new(milestones.first) end - def self.count_by_state(milestones_by_state_and_title, state) - milestones_by_state_and_title.count do |(milestone_state, _), _| - milestone_state == state + def self.states_count(projects, group = nil) + legacy_group_milestones_count = legacy_group_milestone_states_count(projects) + group_milestones_count = group_milestones_states_count(group) + + legacy_group_milestones_count.merge(group_milestones_count) do |k, legacy_group_milestones_count, group_milestones_count| + legacy_group_milestones_count + group_milestones_count end end - private_class_method :count_by_state - def initialize(title, milestones) - @title = title - @name = title - @milestones = milestones - @first_milestone = milestones.find {|m| m.description.present? } || milestones.first - end + def self.group_milestones_states_count(group) + return STATE_COUNT_HASH unless group - def milestoneish_ids - milestones.select(:id) - end + counts_by_state = Milestone.of_groups(group).count_by_state - def safe_title - @title.to_slug.normalize.to_s + { + opened: counts_by_state['active'] || 0, + closed: counts_by_state['closed'] || 0, + all: counts_by_state.values.sum + } end - def projects - @projects ||= Project.for_milestones(milestoneish_ids) - end + def self.legacy_group_milestone_states_count(projects) + return STATE_COUNT_HASH unless projects - def state - milestones.each do |milestone| - return 'active' if milestone.state != 'closed' - end + # We need to reorder(nil) on the projects, because the controller passes them in sorted. + relation = Milestone.of_projects(projects.reorder(nil)).count_by_state - 'closed' + { + opened: relation['active'] || 0, + closed: relation['closed'] || 0, + all: relation.values.sum + } + end + + def initialize(milestone) + @milestone = milestone end def active? @@ -77,37 +86,14 @@ class GlobalMilestone end def issues - @issues ||= Issue.of_milestones(milestoneish_ids).includes(:project, :assignees, :labels) + @issues ||= Issue.of_milestones(milestone).includes(:project, :assignees, :labels) end def merge_requests - @merge_requests ||= MergeRequest.of_milestones(milestoneish_ids).includes(:target_project, :assignee, :labels) - end - - def participants - @participants ||= milestones.map(&:participants).flatten.uniq + @merge_requests ||= MergeRequest.of_milestones(milestone).includes(:target_project, :assignee, :labels) end def labels - @labels ||= GlobalLabel.build_collection(milestones.includes(:labels).map(&:labels).flatten) - .sort_by!(&:title) - end - - def due_date - return @due_date if defined?(@due_date) - - @due_date = - if @milestones.all? { |x| x.due_date == @milestones.first.due_date } - @milestones.first.due_date - end - end - - def start_date - return @start_date if defined?(@start_date) - - @start_date = - if @milestones.all? { |x| x.start_date == @milestones.first.start_date } - @milestones.first.start_date - end + @labels ||= GlobalLabel.build_collection(milestone.labels).sort_by!(&:title) end end diff --git a/app/models/group.rb b/app/models/group.rb index 233747cc2c2..edac2444c4d 100644 --- a/app/models/group.rb +++ b/app/models/group.rb @@ -10,6 +10,7 @@ class Group < Namespace include Referable include SelectForProjectAuthorization include LoadedInGroupList + include Descendant include GroupDescendant include TokenAuthenticatable include WithUploads @@ -63,10 +64,6 @@ class Group < Namespace after_update :path_changed_hook, if: :path_changed? class << self - def supports_nested_groups? - Gitlab::Database.postgresql? - end - def sort_by_attribute(method) if method == 'storage_size_desc' # storage_size is a virtual column so we need to diff --git a/app/models/group_milestone.rb b/app/models/group_milestone.rb index 9dfaebacc83..a58537de319 100644 --- a/app/models/group_milestone.rb +++ b/app/models/group_milestone.rb @@ -1,18 +1,35 @@ # frozen_string_literal: true # Group Milestones are milestones that can be shared among many projects within the same group class GroupMilestone < GlobalMilestone - attr_accessor :group + attr_reader :group, :milestones def self.build_collection(group, projects, params) - super(projects, params).each do |milestone| - milestone.group = group + params = + { state: params[:state] } + + project_milestones = Milestone.of_projects(projects) + child_milestones = Milestone.filter_by_state(project_milestones, params[:state]) + grouped_milestones = child_milestones.group_by(&:title) + + grouped_milestones.map do |title, grouped| + new(title, grouped, group) end end def self.build(group, projects, title) - super(projects, title).tap do |milestone| - milestone&.group = group - end + child_milestones = Milestone.of_projects(projects).where(title: title) + return if child_milestones.blank? + + new(title, child_milestones, group) + end + + def initialize(title, milestones, group) + @milestones = milestones + @group = group + end + + def milestone + @milestone ||= milestones.find { |m| m.description.present? } || milestones.first end def issues_finder_params diff --git a/app/models/merge_request.rb b/app/models/merge_request.rb index a13cac73d04..6092c56b925 100644 --- a/app/models/merge_request.rb +++ b/app/models/merge_request.rb @@ -48,8 +48,8 @@ class MergeRequest < ActiveRecord::Base # is the inverse of MergeRequest#merge_request_diff, which means it may not be # the latest diff, because we could have loaded any diff from this particular # MR. If we haven't already loaded a diff, then it's fine to load the latest. - def merge_request_diff(*args) - fallback = latest_merge_request_diff if args.empty? && !association(:merge_request_diff).loaded? + def merge_request_diff + fallback = latest_merge_request_diff unless association(:merge_request_diff).loaded? fallback || super end @@ -105,7 +105,9 @@ class MergeRequest < ActiveRecord::Base before_transition any => :opened do |merge_request| merge_request.merge_jid = nil + end + after_transition any => :opened do |merge_request| merge_request.run_after_commit do UpdateHeadPipelineForMergeRequestWorker.perform_async(merge_request.id) end @@ -363,6 +365,10 @@ class MergeRequest < ActiveRecord::Base end end + def supports_suggestion? + true + end + # Calls `MergeWorker` to proceed with the merge process and # updates `merge_jid` with the MergeWorker#jid. # This helps tracking enqueued and ongoing merge jobs. @@ -404,6 +410,28 @@ class MergeRequest < ActiveRecord::Base merge_request_diffs.where.not(id: merge_request_diff.id) end + def preloads_discussion_diff_highlighting? + true + end + + def preload_discussions_diff_highlight + preloadable_files = note_diff_files.for_commit_or_unresolved + + discussions_diffs.load_highlight(preloadable_files.pluck(:id)) + end + + def discussions_diffs + strong_memoize(:discussions_diffs) do + Gitlab::DiscussionsDiff::FileCollection.new(note_diff_files.to_a) + end + end + + def note_diff_files + NoteDiffFile + .where(diff_note: discussion_notes) + .includes(diff_note: :project) + end + def diff_size # Calling `merge_request_diff.diffs.real_size` will also perform # highlighting, which we don't need here. @@ -615,10 +643,6 @@ class MergeRequest < ActiveRecord::Base end end - def reload_merge_request_diff - merge_request_diff(true) - end - def viewable_diffs @viewable_diffs ||= merge_request_diffs.viewable.to_a end diff --git a/app/models/milestone.rb b/app/models/milestone.rb index 3cc8e2c44bb..f55c39d9912 100644 --- a/app/models/milestone.rb +++ b/app/models/milestone.rb @@ -94,6 +94,10 @@ class Milestone < ActiveRecord::Base end end + def count_by_state + reorder(nil).group(:state).count + end + def predefined?(milestone) milestone == Any || milestone == None || @@ -212,10 +216,10 @@ class Milestone < ActiveRecord::Base end def reference_link_text(from = nil) - self.title + self.class.reference_prefix + self.title end - def milestoneish_ids + def milestoneish_id id end diff --git a/app/models/namespace.rb b/app/models/namespace.rb index 3c9b1d32a53..a0bebc5e9a2 100644 --- a/app/models/namespace.rb +++ b/app/models/namespace.rb @@ -175,16 +175,16 @@ class Namespace < ActiveRecord::Base # Returns all ancestors, self, and descendants of the current namespace. def self_and_hierarchy - Gitlab::GroupHierarchy + Gitlab::ObjectHierarchy .new(self.class.where(id: id)) - .all_groups + .all_objects end # Returns all the ancestors of the current namespaces. def ancestors return self.class.none unless parent_id - Gitlab::GroupHierarchy + Gitlab::ObjectHierarchy .new(self.class.where(id: parent_id)) .base_and_ancestors end @@ -192,27 +192,27 @@ class Namespace < ActiveRecord::Base # returns all ancestors upto but excluding the given namespace # when no namespace is given, all ancestors upto the top are returned def ancestors_upto(top = nil, hierarchy_order: nil) - Gitlab::GroupHierarchy.new(self.class.where(id: id)) + Gitlab::ObjectHierarchy.new(self.class.where(id: id)) .ancestors(upto: top, hierarchy_order: hierarchy_order) end def self_and_ancestors return self.class.where(id: id) unless parent_id - Gitlab::GroupHierarchy + Gitlab::ObjectHierarchy .new(self.class.where(id: id)) .base_and_ancestors end # Returns all the descendants of the current namespace. def descendants - Gitlab::GroupHierarchy + Gitlab::ObjectHierarchy .new(self.class.where(parent_id: id)) .base_and_descendants end def self_and_descendants - Gitlab::GroupHierarchy + Gitlab::ObjectHierarchy .new(self.class.where(id: id)) .base_and_descendants end @@ -293,7 +293,7 @@ class Namespace < ActiveRecord::Base end def force_share_with_group_lock_on_descendants - return unless Group.supports_nested_groups? + return unless Group.supports_nested_objects? # We can't use `descendants.update_all` since Rails will throw away the WITH # RECURSIVE statement. We also can't use WHERE EXISTS since we can't use @@ -306,6 +306,7 @@ class Namespace < ActiveRecord::Base def write_projects_repository_config all_projects.find_each do |project| project.write_repository_config + project.track_project_repository end end end diff --git a/app/models/note.rb b/app/models/note.rb index 17c7d97fa0a..becf14e9785 100644 --- a/app/models/note.rb +++ b/app/models/note.rb @@ -69,6 +69,12 @@ class Note < ActiveRecord::Base belongs_to :last_edited_by, class_name: 'User' has_many :todos + + # The delete_all definition is required here in order + # to generate the correct DELETE sql for + # suggestions.delete_all calls + has_many :suggestions, -> { order(:relative_order) }, + inverse_of: :note, dependent: :delete_all # rubocop:disable Cop/ActiveRecordDependent has_many :events, as: :target, dependent: :destroy # rubocop:disable Cop/ActiveRecordDependent has_one :system_note_metadata has_one :note_diff_file, inverse_of: :diff_note, foreign_key: :diff_note_id @@ -110,7 +116,7 @@ class Note < ActiveRecord::Base scope :inc_author, -> { includes(:author) } scope :inc_relations_for_view, -> do includes(:project, { author: :status }, :updated_by, :resolved_by, :award_emoji, - :system_note_metadata, :note_diff_file) + :system_note_metadata, :note_diff_file, :suggestions) end scope :with_notes_filter, -> (notes_filter) do @@ -226,6 +232,10 @@ class Note < ActiveRecord::Base Gitlab::HookData::NoteBuilder.new(self).build end + def supports_suggestion? + false + end + def for_commit? noteable_type == "Commit" end diff --git a/app/models/note_diff_file.rb b/app/models/note_diff_file.rb index 27aef7adc48..e369122003e 100644 --- a/app/models/note_diff_file.rb +++ b/app/models/note_diff_file.rb @@ -3,7 +3,22 @@ class NoteDiffFile < ActiveRecord::Base include DiffFile + scope :for_commit_or_unresolved, -> do + joins(:diff_note).where("resolved_at IS NULL OR noteable_type = 'Commit'") + end + + delegate :original_position, :project, to: :diff_note + belongs_to :diff_note, inverse_of: :note_diff_file validates :diff_note, presence: true + + def raw_diff_file + raw_diff = Gitlab::Git::Diff.new(to_hash) + + Gitlab::Diff::File.new(raw_diff, + repository: project.repository, + diff_refs: original_position.diff_refs, + unique_identifier: id) + end end diff --git a/app/models/pool_repository.rb b/app/models/pool_repository.rb index dbde00b5584..ad6a008dee8 100644 --- a/app/models/pool_repository.rb +++ b/app/models/pool_repository.rb @@ -18,6 +18,7 @@ class PoolRepository < ActiveRecord::Base state :scheduled state :ready state :failed + state :obsolete event :schedule do transition none: :scheduled @@ -31,6 +32,10 @@ class PoolRepository < ActiveRecord::Base transition all => :failed end + event :mark_obsolete do + transition all => :obsolete + end + state all - [:ready] do def joinable? false @@ -54,6 +59,12 @@ class PoolRepository < ActiveRecord::Base ::ObjectPool::ScheduleJoinWorker.perform_async(pool.id) end end + + after_transition any => :obsolete do |pool, _| + pool.run_after_commit do + ::ObjectPool::DestroyWorker.perform_async(pool.id) + end + end end def create_object_pool @@ -71,10 +82,10 @@ class PoolRepository < ActiveRecord::Base end # This RPC can cause data loss, as not all objects are present the local repository - # No execution path yet, will be added through: - # https://gitlab.com/gitlab-org/gitaly/issues/1415 - def delete_repository_alternate(repository) + def unlink_repository(repository) object_pool.unlink_repository(repository.raw) + + mark_obsolete unless member_projects.where.not(id: repository.project.id).exists? end def object_pool @@ -84,6 +95,10 @@ class PoolRepository < ActiveRecord::Base source_project.repository.raw) end + def inspect + "#<#{self.class.name} id:#{id} state:#{state} disk_path:#{disk_path} source_project: #{source_project.full_path}>" + end + private def correct_disk_path diff --git a/app/models/project.rb b/app/models/project.rb index 67262ecce85..cd558752080 100644 --- a/app/models/project.rb +++ b/app/models/project.rb @@ -256,7 +256,7 @@ class Project < ActiveRecord::Base # other pipelines, like webide ones, that we won't retrieve # if we use this relation. has_many :ci_pipelines, - -> { Feature.enabled?(:pipeline_ci_sources_only, default_enabled: true) ? ci_sources : all }, + -> { ci_sources }, class_name: 'Ci::Pipeline', inverse_of: :project has_many :stages, class_name: 'Ci::Stage', inverse_of: :project @@ -324,10 +324,9 @@ class Project < ActiveRecord::Base validates :namespace, presence: true validates :name, uniqueness: { scope: :namespace_id } - validates :import_url, url: { protocols: ->(project) { project.persisted? ? VALID_MIRROR_PROTOCOLS : VALID_IMPORT_PROTOCOLS }, - ports: ->(project) { project.persisted? ? VALID_MIRROR_PORTS : VALID_IMPORT_PORTS }, - allow_localhost: false, - enforce_user: true }, if: [:external_import?, :import_url_changed?] + validates :import_url, public_url: { protocols: ->(project) { project.persisted? ? VALID_MIRROR_PROTOCOLS : VALID_IMPORT_PROTOCOLS }, + ports: ->(project) { project.persisted? ? VALID_MIRROR_PORTS : VALID_IMPORT_PORTS }, + enforce_user: true }, if: [:external_import?, :import_url_changed?] validates :star_count, numericality: { greater_than_or_equal_to: 0 } validate :check_limit, on: :create validate :check_repository_path_availability, on: :update, if: ->(project) { project.renamed? } @@ -570,7 +569,7 @@ class Project < ActiveRecord::Base # returns all ancestor-groups upto but excluding the given namespace # when no namespace is given, all ancestors upto the top are returned def ancestors_upto(top = nil, hierarchy_order: nil) - Gitlab::GroupHierarchy.new(Group.where(id: namespace_id)) + Gitlab::ObjectHierarchy.new(Group.where(id: namespace_id)) .base_and_ancestors(upto: top, hierarchy_order: hierarchy_order) end @@ -1244,10 +1243,8 @@ class Project < ActiveRecord::Base end def track_project_repository - return unless hashed_storage?(:repository) - - project_repo = project_repository || build_project_repository - project_repo.update!(shard_name: repository_storage, disk_path: disk_path) + repository = project_repository || build_project_repository + repository.update!(shard_name: repository_storage, disk_path: disk_path) end def create_repository(force: false) @@ -1736,10 +1733,21 @@ class Project < ActiveRecord::Base end def protected_for?(ref) - if repository.branch_exists?(ref) - ProtectedBranch.protected?(self, ref) - elsif repository.tag_exists?(ref) - ProtectedTag.protected?(self, ref) + raise Repository::AmbiguousRefError if repository.ambiguous_ref?(ref) + + resolved_ref = repository.expand_ref(ref) || ref + return false unless Gitlab::Git.tag_ref?(resolved_ref) || Gitlab::Git.branch_ref?(resolved_ref) + + ref_name = if resolved_ref == ref + Gitlab::Git.ref_name(resolved_ref) + else + ref + end + + if Gitlab::Git.branch_ref?(resolved_ref) + ProtectedBranch.protected?(self, ref_name) + elsif Gitlab::Git.tag_ref?(resolved_ref) + ProtectedTag.protected?(self, ref_name) end end @@ -2004,6 +2012,10 @@ class Project < ActiveRecord::Base Feature.enabled?(:object_pools, self) end + def leave_pool_repository + pool_repository&.unlink_repository(repository) + end + private def create_new_pool_repository diff --git a/app/models/project_services/kubernetes_service.rb b/app/models/project_services/kubernetes_service.rb index b801fd84a07..f69edd60003 100644 --- a/app/models/project_services/kubernetes_service.rb +++ b/app/models/project_services/kubernetes_service.rb @@ -53,7 +53,7 @@ class KubernetesService < DeploymentService end def description - 'Kubernetes / Openshift integration' + 'Kubernetes / OpenShift integration' end def help diff --git a/app/models/prometheus_metric.rb b/app/models/prometheus_metric.rb index ce2db9cb44c..5594594a48d 100644 --- a/app/models/prometheus_metric.rb +++ b/app/models/prometheus_metric.rb @@ -5,11 +5,12 @@ class PrometheusMetric < ActiveRecord::Base enum group: { # built-in groups - nginx_ingress: -1, + nginx_ingress_vts: -1, ha_proxy: -2, aws_elb: -3, nginx: -4, kubernetes: -5, + nginx_ingress: -6, # custom/user groups business: 0, @@ -17,6 +18,54 @@ class PrometheusMetric < ActiveRecord::Base system: 2 } + GROUP_DETAILS = { + # built-in groups + nginx_ingress_vts: { + group_title: _('Response metrics (NGINX Ingress VTS)'), + required_metrics: %w(nginx_upstream_responses_total nginx_upstream_response_msecs_avg), + priority: 10 + }.freeze, + nginx_ingress: { + group_title: _('Response metrics (NGINX Ingress)'), + required_metrics: %w(nginx_ingress_controller_requests nginx_ingress_controller_ingress_upstream_latency_seconds_sum), + priority: 10 + }.freeze, + ha_proxy: { + group_title: _('Response metrics (HA Proxy)'), + required_metrics: %w(haproxy_frontend_http_requests_total haproxy_frontend_http_responses_total), + priority: 10 + }.freeze, + aws_elb: { + group_title: _('Response metrics (AWS ELB)'), + required_metrics: %w(aws_elb_request_count_sum aws_elb_latency_average aws_elb_httpcode_backend_5_xx_sum), + priority: 10 + }.freeze, + nginx: { + group_title: _('Response metrics (NGINX)'), + required_metrics: %w(nginx_server_requests nginx_server_requestMsec), + priority: 10 + }.freeze, + kubernetes: { + group_title: _('System metrics (Kubernetes)'), + required_metrics: %w(container_memory_usage_bytes container_cpu_usage_seconds_total), + priority: 5 + }.freeze, + + # custom/user groups + business: { + group_title: _('Business metrics (Custom)'), + priority: 0 + }.freeze, + response: { + group_title: _('Response metrics (Custom)'), + priority: -5 + }.freeze, + system: { + group_title: _('System metrics (Custom)'), + priority: -10 + }.freeze + }.freeze + validates :title, presence: true validates :query, presence: true validates :group, presence: true @@ -28,34 +77,16 @@ class PrometheusMetric < ActiveRecord::Base scope :common, -> { where(common: true) } - GROUP_TITLES = { - # built-in groups - nginx_ingress: _('Response metrics (NGINX Ingress)'), - ha_proxy: _('Response metrics (HA Proxy)'), - aws_elb: _('Response metrics (AWS ELB)'), - nginx: _('Response metrics (NGINX)'), - kubernetes: _('System metrics (Kubernetes)'), - - # custom/user groups - business: _('Business metrics (Custom)'), - response: _('Response metrics (Custom)'), - system: _('System metrics (Custom)') - }.freeze - - REQUIRED_METRICS = { - nginx_ingress: %w(nginx_upstream_responses_total nginx_upstream_response_msecs_avg), - ha_proxy: %w(haproxy_frontend_http_requests_total haproxy_frontend_http_responses_total), - aws_elb: %w(aws_elb_request_count_sum aws_elb_latency_average aws_elb_httpcode_backend_5_xx_sum), - nginx: %w(nginx_server_requests nginx_server_requestMsec), - kubernetes: %w(container_memory_usage_bytes container_cpu_usage_seconds_total) - }.freeze + def priority + group_details(group).fetch(:priority) + end def group_title - GROUP_TITLES[group.to_sym] + group_details(group).fetch(:group_title) end def required_metrics - REQUIRED_METRICS[group.to_sym].to_a.map(&:to_s) + group_details(group).fetch(:required_metrics, []).map(&:to_s) end def to_query_metric @@ -86,4 +117,10 @@ class PrometheusMetric < ActiveRecord::Base }] end end + + private + + def group_details(group) + GROUP_DETAILS.fetch(group.to_sym) + end end diff --git a/app/models/release.rb b/app/models/release.rb index cba80ad30ca..df3dfe1cf2f 100644 --- a/app/models/release.rb +++ b/app/models/release.rb @@ -2,10 +2,39 @@ class Release < ActiveRecord::Base include CacheMarkdownField + include Gitlab::Utils::StrongMemoize cache_markdown_field :description belongs_to :project + # releases prior to 11.7 have no author + belongs_to :author, class_name: 'User' validates :description, :project, :tag, presence: true + + scope :sorted, -> { order(created_at: :desc) } + + delegate :repository, to: :project + + def commit + strong_memoize(:commit) do + repository.commit(actual_sha) + end + end + + def tag_missing? + actual_tag.nil? + end + + private + + def actual_sha + sha || actual_tag&.dereferenced_target + end + + def actual_tag + strong_memoize(:actual_tag) do + repository.find_tag(tag) + end + end end diff --git a/app/models/remote_mirror.rb b/app/models/remote_mirror.rb index 5a6895aefab..a3fa67c72bf 100644 --- a/app/models/remote_mirror.rb +++ b/app/models/remote_mirror.rb @@ -17,7 +17,7 @@ class RemoteMirror < ActiveRecord::Base belongs_to :project, inverse_of: :remote_mirrors - validates :url, presence: true, url: { protocols: %w(ssh git http https), allow_blank: true, enforce_user: true } + validates :url, presence: true, public_url: { protocols: %w(ssh git http https), allow_blank: true, enforce_user: true } before_save :set_new_remote_name, if: :mirror_url_changed? diff --git a/app/models/repository.rb b/app/models/repository.rb index 015a179f374..b19ae2e0e6a 100644 --- a/app/models/repository.rb +++ b/app/models/repository.rb @@ -25,6 +25,7 @@ class Repository delegate :bundle_to_disk, to: :raw_repository CreateTreeError = Class.new(StandardError) + AmbiguousRefError = Class.new(StandardError) # Methods that cache data from the Git repository. # @@ -181,6 +182,18 @@ class Repository tags.find { |tag| tag.name == name } end + def ambiguous_ref?(ref) + tag_exists?(ref) && branch_exists?(ref) + end + + def expand_ref(ref) + if tag_exists?(ref) + Gitlab::Git::TAG_REF_PREFIX + ref + elsif branch_exists?(ref) + Gitlab::Git::BRANCH_REF_PREFIX + ref + end + end + def add_branch(user, branch_name, ref) branch = raw_repository.add_branch(branch_name, user: user, target: ref) diff --git a/app/models/service.rb b/app/models/service.rb index 5b8bf6e7cf0..9dcb0aab0a3 100644 --- a/app/models/service.rb +++ b/app/models/service.rb @@ -210,11 +210,7 @@ class Service < ActiveRecord::Base class_eval %{ def #{arg}? # '!!' is used because nil or empty string is converted to nil - if Gitlab.rails5? - !!ActiveRecord::Type::Boolean.new.cast(#{arg}) - else - !!ActiveRecord::Type::Boolean.new.type_cast_from_database(#{arg}) - end + !!ActiveRecord::Type::Boolean.new.cast(#{arg}) end } end diff --git a/app/models/snippet.rb b/app/models/snippet.rb index 11856b55902..f9b23bbbf6c 100644 --- a/app/models/snippet.rb +++ b/app/models/snippet.rb @@ -175,6 +175,12 @@ class Snippet < ActiveRecord::Base :visibility_level end + def embeddable? + ability = project_id? ? :read_project_snippet : :read_personal_snippet + + Ability.allowed?(nil, ability, self) + end + def notes_with_associations notes.includes(:author) end diff --git a/app/models/suggestion.rb b/app/models/suggestion.rb new file mode 100644 index 00000000000..c76b8e71507 --- /dev/null +++ b/app/models/suggestion.rb @@ -0,0 +1,55 @@ +# frozen_string_literal: true + +class Suggestion < ApplicationRecord + belongs_to :note, inverse_of: :suggestions + validates :note, presence: true + validates :commit_id, presence: true, if: :applied? + + delegate :original_position, :position, :diff_file, + :noteable, to: :note + + def project + noteable.source_project + end + + def branch + noteable.source_branch + end + + # For now, suggestions only serve as a way to send patches that + # will change a single line (being able to apply multiple in the same place), + # which explains `from_line` and `to_line` being the same line. + # We'll iterate on that in https://gitlab.com/gitlab-org/gitlab-ce/issues/53310 + # when allowing multi-line suggestions. + def from_line + position.new_line + end + alias_method :to_line, :from_line + + def from_original_line + original_position.new_line + end + alias_method :to_original_line, :from_original_line + + # `from_line_index` and `to_line_index` represents diff/blob line numbers in + # index-like way (N-1). + def from_line_index + from_line - 1 + end + alias_method :to_line_index, :from_line_index + + def appliable? + return false unless note.supports_suggestion? + + !applied? && + noteable.opened? && + different_content? && + note.active? + end + + private + + def different_content? + from_content != to_content + end +end diff --git a/app/models/todo.rb b/app/models/todo.rb index 7b64615f699..d9b86d941b6 100644 --- a/app/models/todo.rb +++ b/app/models/todo.rb @@ -4,6 +4,11 @@ class Todo < ActiveRecord::Base include Sortable include FromUnion + # Time to wait for todos being removed when not visible for user anymore. + # Prevents TODOs being removed by mistake, for example, removing access from a user + # and giving it back again. + WAIT_FOR_DELETE = 1.hour + ASSIGNED = 1 MENTIONED = 2 BUILD_FAILED = 3 diff --git a/app/models/user.rb b/app/models/user.rb index dbd754dd25a..26fd2d903a1 100644 --- a/app/models/user.rb +++ b/app/models/user.rb @@ -130,6 +130,7 @@ class User < ActiveRecord::Base has_many :issues, dependent: :destroy, foreign_key: :author_id # rubocop:disable Cop/ActiveRecordDependent has_many :merge_requests, dependent: :destroy, foreign_key: :author_id # rubocop:disable Cop/ActiveRecordDependent has_many :events, dependent: :destroy, foreign_key: :author_id # rubocop:disable Cop/ActiveRecordDependent + has_many :releases, dependent: :nullify, foreign_key: :author_id # rubocop:disable Cop/ActiveRecordDependent has_many :subscriptions, dependent: :destroy # rubocop:disable Cop/ActiveRecordDependent has_many :oauth_applications, class_name: 'Doorkeeper::Application', as: :owner, dependent: :destroy # rubocop:disable Cop/ActiveRecordDependent has_one :abuse_report, dependent: :destroy, foreign_key: :user_id # rubocop:disable Cop/ActiveRecordDependent @@ -708,13 +709,13 @@ class User < ActiveRecord::Base # Returns the groups a user is a member of, either directly or through a parent group def membership_groups - Gitlab::GroupHierarchy.new(groups).base_and_descendants + Gitlab::ObjectHierarchy.new(groups).base_and_descendants end # Returns a relation of groups the user has access to, including their parent # and child groups (recursively). def all_expanded_groups - Gitlab::GroupHierarchy.new(groups).all_groups + Gitlab::ObjectHierarchy.new(groups).all_objects end def expanded_groups_requiring_two_factor_authentication @@ -1152,7 +1153,7 @@ class User < ActiveRecord::Base end def manageable_groups - Gitlab::GroupHierarchy.new(owned_or_maintainers_groups).base_and_descendants + Gitlab::ObjectHierarchy.new(owned_or_maintainers_groups).base_and_descendants end def namespaces @@ -1421,6 +1422,10 @@ class User < ActiveRecord::Base todos.where(id: ids) end + def pending_todo_for(target) + todos.find_by(target: target, state: :pending) + end + # @deprecated alias_method :owned_or_masters_groups, :owned_or_maintainers_groups diff --git a/app/policies/concerns/clusterable_actions.rb b/app/policies/concerns/clusterable_actions.rb new file mode 100644 index 00000000000..08ddd742ea9 --- /dev/null +++ b/app/policies/concerns/clusterable_actions.rb @@ -0,0 +1,14 @@ +# frozen_string_literal: true + +module ClusterableActions + private + + # Overridden on EE module + def multiple_clusters_available? + false + end + + def clusterable_has_clusters? + !subject.clusters.empty? + end +end diff --git a/app/policies/group_policy.rb b/app/policies/group_policy.rb index 6b4e56ef5e4..c25766a5af8 100644 --- a/app/policies/group_policy.rb +++ b/app/policies/group_policy.rb @@ -1,6 +1,8 @@ # frozen_string_literal: true class GroupPolicy < BasePolicy + include ClusterableActions + desc "Group is public" with_options scope: :subject, score: 0 condition(:public_group) { @subject.public? } @@ -16,7 +18,7 @@ class GroupPolicy < BasePolicy condition(:maintainer) { access_level >= GroupMember::MAINTAINER } condition(:reporter) { access_level >= GroupMember::REPORTER } - condition(:nested_groups_supported, scope: :global) { Group.supports_nested_groups? } + condition(:nested_groups_supported, scope: :global) { Group.supports_nested_objects? } condition(:has_parent, scope: :subject) { @subject.has_parent? } condition(:share_with_group_locked, scope: :subject) { @subject.share_with_group_lock? } @@ -27,6 +29,9 @@ class GroupPolicy < BasePolicy GroupProjectsFinder.new(group: @subject, current_user: @user, options: { include_subgroups: true }).execute.any? end + condition(:has_clusters, scope: :subject) { clusterable_has_clusters? } + condition(:can_have_multiple_clusters) { multiple_clusters_available? } + with_options scope: :subject, score: 0 condition(:request_access_enabled) { @subject.request_access_enabled } @@ -40,11 +45,12 @@ class GroupPolicy < BasePolicy rule { guest }.policy do enable :read_group + enable :read_list enable :upload_file enable :read_label end - rule { admin } .enable :read_group + rule { admin }.enable :read_group rule { has_projects }.policy do enable :read_group @@ -66,6 +72,7 @@ class GroupPolicy < BasePolicy enable :admin_pipeline enable :admin_build enable :read_cluster + enable :add_cluster enable :create_cluster enable :update_cluster enable :admin_cluster @@ -105,6 +112,8 @@ class GroupPolicy < BasePolicy rule { owner & (~share_with_group_locked | ~has_parent | ~parent_share_with_group_locked | can_change_parent_share_with_group_lock) }.enable :change_share_with_group_lock + rule { ~can_have_multiple_clusters & has_clusters }.prevent :add_cluster + def access_level return GroupMember::NO_ACCESS if @user.nil? diff --git a/app/policies/issuable_policy.rb b/app/policies/issuable_policy.rb index 6d8b575102e..ecb2797d1d9 100644 --- a/app/policies/issuable_policy.rb +++ b/app/policies/issuable_policy.rb @@ -11,7 +11,7 @@ class IssuablePolicy < BasePolicy @user && @subject.assignee_or_author?(@user) end - rule { assignee_or_author }.policy do + rule { can?(:guest_access) & assignee_or_author }.policy do enable :read_issue enable :update_issue enable :reopen_issue diff --git a/app/policies/project_policy.rb b/app/policies/project_policy.rb index 1c082945299..3146f26bed5 100644 --- a/app/policies/project_policy.rb +++ b/app/policies/project_policy.rb @@ -2,6 +2,7 @@ class ProjectPolicy < BasePolicy extend ClassMethods + include ClusterableActions READONLY_FEATURES_WHEN_ARCHIVED = %i[ issue @@ -22,6 +23,7 @@ class ProjectPolicy < BasePolicy container_image pages cluster + release ].freeze desc "User is a project owner" @@ -103,6 +105,9 @@ class ProjectPolicy < BasePolicy @subject.feature_available?(:merge_requests, @user) end + condition(:has_clusters, scope: :subject) { clusterable_has_clusters? } + condition(:can_have_multiple_clusters) { multiple_clusters_available? } + features = %w[ merge_requests issues @@ -169,6 +174,7 @@ class ProjectPolicy < BasePolicy enable :read_cycle_analytics enable :award_emoji enable :read_pages_content + enable :read_release end # These abilities are not allowed to admins that are not members of the project, @@ -235,6 +241,8 @@ class ProjectPolicy < BasePolicy enable :update_container_image enable :create_environment enable :create_deployment + enable :create_release + enable :update_release end rule { can?(:maintainer_access) }.policy do @@ -257,10 +265,12 @@ class ProjectPolicy < BasePolicy enable :read_pages enable :update_pages enable :read_cluster + enable :add_cluster enable :create_cluster enable :update_cluster enable :admin_cluster enable :create_environment_terminal + enable :destroy_release end rule { (mirror_available & can?(:admin_project)) | admin }.enable :admin_remote_mirror @@ -320,6 +330,7 @@ class ProjectPolicy < BasePolicy prevent :download_code prevent :fork_project prevent :read_commit_status + prevent(*create_read_update_admin_destroy(:release)) end rule { container_registry_disabled }.policy do @@ -349,6 +360,7 @@ class ProjectPolicy < BasePolicy enable :read_commit_status enable :read_container_image enable :download_code + enable :read_release enable :download_wiki_code enable :read_cycle_analytics enable :read_pages_content @@ -381,6 +393,8 @@ class ProjectPolicy < BasePolicy (can?(:read_project_for_iids) & merge_requests_visible_to_user) | can?(:read_merge_request) end.enable :read_merge_request_iid + rule { ~can_have_multiple_clusters & has_clusters }.prevent :add_cluster + private def team_member? diff --git a/app/policies/release_policy.rb b/app/policies/release_policy.rb new file mode 100644 index 00000000000..d7f9e5d7445 --- /dev/null +++ b/app/policies/release_policy.rb @@ -0,0 +1,5 @@ +# frozen_string_literal: true + +class ReleasePolicy < BasePolicy + delegate { @subject.project } +end diff --git a/app/policies/suggestion_policy.rb b/app/policies/suggestion_policy.rb new file mode 100644 index 00000000000..301b7d965f5 --- /dev/null +++ b/app/policies/suggestion_policy.rb @@ -0,0 +1,11 @@ +# frozen_string_literal: true + +class SuggestionPolicy < BasePolicy + delegate { @subject.project } + + condition(:can_push_to_branch) do + Gitlab::UserAccess.new(@user, project: @subject.project).can_push_to_branch?(@subject.branch) + end + + rule { can_push_to_branch }.enable :apply_suggestion +end diff --git a/app/presenters/clusterable_presenter.rb b/app/presenters/clusterable_presenter.rb index 9cc137aa3bd..d94d9118eee 100644 --- a/app/presenters/clusterable_presenter.rb +++ b/app/presenters/clusterable_presenter.rb @@ -12,6 +12,10 @@ class ClusterablePresenter < Gitlab::View::Presenter::Delegated .fabricate! end + def can_add_cluster? + can?(current_user, :add_cluster, clusterable) + end + def can_create_cluster? can?(current_user, :create_cluster, clusterable) end diff --git a/app/presenters/clusters/cluster_presenter.rb b/app/presenters/clusters/cluster_presenter.rb index 7e6eccb648c..7a5b68f9a4b 100644 --- a/app/presenters/clusters/cluster_presenter.rb +++ b/app/presenters/clusters/cluster_presenter.rb @@ -2,8 +2,22 @@ module Clusters class ClusterPresenter < Gitlab::View::Presenter::Delegated + include ActionView::Helpers::SanitizeHelper + include ActionView::Helpers::UrlHelper + include IconsHelper + presents :cluster + # We do not want to show the group path for clusters belonging to the + # clusterable, only for the ancestor clusters. + def item_link(clusterable_presenter) + if cluster.group_type? && clusterable != clusterable_presenter.subject + contracted_group_name(cluster.group) + ' / ' + link_to_cluster + else + link_to_cluster + end + end + def gke_cluster_url "https://console.cloud.google.com/kubernetes/clusters/details/#{provider.zone}/#{name}" if gcp? end @@ -12,6 +26,18 @@ module Clusters can?(current_user, :update_cluster, cluster) && created? end + def can_read_cluster? + can?(current_user, :read_cluster, cluster) + end + + def cluster_type_description + if cluster.project_type? + s_("ClusterIntegration|Project cluster") + elsif cluster.group_type? + s_("ClusterIntegration|Group cluster") + end + end + def show_path if cluster.project_type? project_cluster_path(project, cluster) @@ -21,5 +47,29 @@ module Clusters raise NotImplementedError end end + + private + + def clusterable + if cluster.group_type? + cluster.group + elsif cluster.project_type? + cluster.project + end + end + + def contracted_group_name(group) + sanitize(group.full_name) + .sub(%r{\/.*\/}, "/ #{contracted_icon} /") + .html_safe + end + + def contracted_icon + sprite_icon('ellipsis_h', size: 12, css_class: 'vertical-align-middle') + end + + def link_to_cluster + link_to_if(can_read_cluster?, cluster.name, show_path) + end end end diff --git a/app/serializers/diff_file_entity.rb b/app/serializers/diff_file_entity.rb index f0881829efd..b0aaec3326d 100644 --- a/app/serializers/diff_file_entity.rb +++ b/app/serializers/diff_file_entity.rb @@ -5,6 +5,7 @@ class DiffFileEntity < DiffFileBaseEntity include IconsHelper expose :too_large?, as: :too_large + expose :empty?, as: :empty expose :added_lines expose :removed_lines diff --git a/app/serializers/diff_line_entity.rb b/app/serializers/diff_line_entity.rb index 942714b7787..bfef6d3bde8 100644 --- a/app/serializers/diff_line_entity.rb +++ b/app/serializers/diff_line_entity.rb @@ -11,4 +11,6 @@ class DiffLineEntity < Grape::Entity expose :rich_text do |line| ERB::Util.html_escape(line.rich_text || line.text) end + + expose :suggestible?, as: :can_receive_suggestion end diff --git a/app/serializers/diff_viewer_entity.rb b/app/serializers/diff_viewer_entity.rb index 27fba03cb3f..587fa2347fd 100644 --- a/app/serializers/diff_viewer_entity.rb +++ b/app/serializers/diff_viewer_entity.rb @@ -4,4 +4,7 @@ class DiffViewerEntity < Grape::Entity # Partial name refers directly to a Rails feature, let's avoid # using this on the frontend. expose :partial_name, as: :name + expose :error do |diff_viewer| + diff_viewer.render_error_message + end end diff --git a/app/serializers/entity_date_helper.rb b/app/serializers/entity_date_helper.rb index cc0c2abf863..f515abe5917 100644 --- a/app/serializers/entity_date_helper.rb +++ b/app/serializers/entity_date_helper.rb @@ -44,14 +44,14 @@ module EntityDateHelper # It returns "Upcoming" for upcoming entities # If due date is provided, it returns "# days|weeks|months remaining|ago" # If start date is provided and elapsed, with no due date, it returns "# days elapsed" - def remaining_days_in_words(entity) - if entity.try(:expired?) + def remaining_days_in_words(due_date, start_date = nil) + if due_date&.past? content_tag(:strong, 'Past due') - elsif entity.try(:upcoming?) + elsif start_date&.future? content_tag(:strong, 'Upcoming') - elsif entity.due_date - is_upcoming = (entity.due_date - Date.today).to_i > 0 - time_ago = time_ago_in_words(entity.due_date) + elsif due_date + is_upcoming = (due_date - Date.today).to_i > 0 + time_ago = time_ago_in_words(due_date) # https://gitlab.com/gitlab-org/gitlab-ce/issues/49440 # @@ -63,8 +63,8 @@ module EntityDateHelper remaining_or_ago = is_upcoming ? _("remaining") : _("ago") "#{content} #{remaining_or_ago}".html_safe - elsif entity.start_date && entity.start_date.past? - days = entity.elapsed_days + elsif start_date&.past? + days = (Date.today - start_date).to_i "#{content_tag(:strong, days)} #{'day'.pluralize(days)} elapsed".html_safe end end diff --git a/app/serializers/environment_entity.rb b/app/serializers/environment_entity.rb index 07a13c33b89..4a7d13915dd 100644 --- a/app/serializers/environment_entity.rb +++ b/app/serializers/environment_entity.rb @@ -23,6 +23,10 @@ class EnvironmentEntity < Grape::Entity stop_project_environment_path(environment.project, environment) end + expose :cluster_type, if: ->(environment, _) { cluster_platform_kubernetes? } do |environment| + cluster.cluster_type + end + expose :terminal_path, if: ->(*) { environment.has_terminals? && can_access_terminal? } do |environment| terminal_project_environment_path(environment.project, environment) end @@ -48,4 +52,16 @@ class EnvironmentEntity < Grape::Entity def can_access_terminal? can?(request.current_user, :create_environment_terminal, environment) end + + def cluster_platform_kubernetes? + deployment_platform && deployment_platform.is_a?(Clusters::Platforms::Kubernetes) + end + + def deployment_platform + environment.deployment_platform + end + + def cluster + deployment_platform.cluster + end end diff --git a/app/serializers/issuable_sidebar_basic_entity.rb b/app/serializers/issuable_sidebar_basic_entity.rb new file mode 100644 index 00000000000..61de3c93337 --- /dev/null +++ b/app/serializers/issuable_sidebar_basic_entity.rb @@ -0,0 +1,106 @@ +# frozen_string_literal: true + +class IssuableSidebarBasicEntity < Grape::Entity + include RequestAwareEntity + + expose :id + expose :type do |issuable| + issuable.to_ability_name + end + expose :author_id + expose :project_id do |issuable| + issuable.project.id + end + expose :discussion_locked + expose :reference do |issuable| + issuable.to_reference(issuable.project, full: true) + end + + expose :milestone, using: ::API::Entities::Milestone + expose :labels, using: LabelEntity + + expose :current_user, if: lambda { |_issuable| current_user } do + expose :current_user, merge: true, using: API::Entities::UserBasic + + expose :todo, using: IssuableSidebarTodoEntity do |issuable| + current_user.pending_todo_for(issuable) + end + + expose :can_edit do |issuable| + can?(current_user, :"admin_#{issuable.to_ability_name}", issuable.project) + end + + expose :can_move do |issuable| + issuable.can_move?(current_user) + end + + expose :can_admin_label do |issuable| + can?(current_user, :admin_label, issuable.project) + end + end + + expose :issuable_json_path do |issuable| + if issuable.is_a?(MergeRequest) + project_merge_request_path(issuable.project, issuable.iid, :json) + else + project_issue_path(issuable.project, issuable.iid, :json) + end + end + + expose :namespace_path do |issuable| + issuable.project.namespace.full_path + end + + expose :project_path do |issuable| + issuable.project.path + end + + expose :project_full_path do |issuable| + issuable.project.full_path + end + + expose :project_issuables_path do |issuable| + project = issuable.project + namespace = project.namespace + + if issuable.is_a?(MergeRequest) + namespace_project_merge_requests_path(namespace, project) + else + namespace_project_issues_path(namespace, project) + end + end + + expose :create_todo_path do |issuable| + project_todos_path(issuable.project) + end + + expose :project_milestones_path do |issuable| + project_milestones_path(issuable.project, :json) + end + + expose :project_labels_path do |issuable| + project_labels_path(issuable.project, :json, include_ancestor_groups: true) + end + + expose :toggle_subscription_path do |issuable| + toggle_subscription_path(issuable) + end + + expose :move_issue_path do |issuable| + move_namespace_project_issue_path( + namespace_id: issuable.project.namespace.to_param, + project_id: issuable.project, + id: issuable + ) + end + + expose :projects_autocomplete_path do |issuable| + autocomplete_projects_path(project_id: issuable.project.id) + end + + private + + def current_user + request.current_user + end +end diff --git a/app/serializers/issuable_sidebar_entity.rb b/app/serializers/issuable_sidebar_extras_entity.rb index 773d78d324c..d60253564e1 100644 --- a/app/serializers/issuable_sidebar_entity.rb +++ b/app/serializers/issuable_sidebar_extras_entity.rb @@ -1,8 +1,8 @@ # frozen_string_literal: true -class IssuableSidebarEntity < Grape::Entity - include TimeTrackableEntity +class IssuableSidebarExtrasEntity < Grape::Entity include RequestAwareEntity + include TimeTrackableEntity expose :participants, using: ::API::Entities::UserBasic do |issuable| issuable.participants(request.current_user) diff --git a/app/serializers/issuable_sidebar_todo_entity.rb b/app/serializers/issuable_sidebar_todo_entity.rb new file mode 100644 index 00000000000..b2c98433f05 --- /dev/null +++ b/app/serializers/issuable_sidebar_todo_entity.rb @@ -0,0 +1,11 @@ +# frozen_string_literal: true + +class IssuableSidebarTodoEntity < Grape::Entity + include Gitlab::Routing + + expose :id + + expose :delete_path do |todo| + dashboard_todo_path(todo) if todo + end +end diff --git a/app/serializers/issue_board_entity.rb b/app/serializers/issue_board_entity.rb index 58ab804a3c8..f7719447b92 100644 --- a/app/serializers/issue_board_entity.rb +++ b/app/serializers/issue_board_entity.rb @@ -17,7 +17,7 @@ class IssueBoardEntity < Grape::Entity end expose :milestone, expose_nil: false do |issue| - API::Entities::Project.represent issue.milestone, only: [:id, :title] + API::Entities::Milestone.represent issue.milestone, only: [:id, :title] end expose :assignees do |issue| @@ -37,7 +37,7 @@ class IssueBoardEntity < Grape::Entity end expose :issue_sidebar_endpoint, if: -> (issue) { issue.project } do |issue| - project_issue_path(issue.project, issue, format: :json, serializer: 'sidebar') + project_issue_path(issue.project, issue, format: :json, serializer: 'sidebar_extras') end expose :toggle_subscription_endpoint, if: -> (issue) { issue.project } do |issue| diff --git a/app/serializers/issue_serializer.rb b/app/serializers/issue_serializer.rb index d66f0a5acb7..0fa76f098cd 100644 --- a/app/serializers/issue_serializer.rb +++ b/app/serializers/issue_serializer.rb @@ -2,13 +2,15 @@ class IssueSerializer < BaseSerializer # This overrided method takes care of which entity should be used - # to serialize the `issue` based on `basic` key in `opts` param. + # to serialize the `issue` based on `serializer` key in `opts` param. # Hence, `entity` doesn't need to be declared on the class scope. def represent(issue, opts = {}) entity = case opts[:serializer] when 'sidebar' - IssueSidebarEntity + IssueSidebarBasicEntity + when 'sidebar_extras' + IssueSidebarExtrasEntity when 'board' IssueBoardEntity else diff --git a/app/serializers/issue_sidebar_basic_entity.rb b/app/serializers/issue_sidebar_basic_entity.rb new file mode 100644 index 00000000000..723875809ec --- /dev/null +++ b/app/serializers/issue_sidebar_basic_entity.rb @@ -0,0 +1,6 @@ +# frozen_string_literal: true + +class IssueSidebarBasicEntity < IssuableSidebarBasicEntity + expose :due_date + expose :confidential +end diff --git a/app/serializers/issue_sidebar_entity.rb b/app/serializers/issue_sidebar_extras_entity.rb index 349ad9d1fef..7b6e860140b 100644 --- a/app/serializers/issue_sidebar_entity.rb +++ b/app/serializers/issue_sidebar_extras_entity.rb @@ -1,5 +1,5 @@ # frozen_string_literal: true -class IssueSidebarEntity < IssuableSidebarEntity +class IssueSidebarExtrasEntity < IssuableSidebarExtrasEntity expose :assignees, using: API::Entities::UserBasic end diff --git a/app/serializers/merge_request_basic_entity.rb b/app/serializers/merge_request_basic_entity.rb index f7eb74cf392..084627f9dbe 100644 --- a/app/serializers/merge_request_basic_entity.rb +++ b/app/serializers/merge_request_basic_entity.rb @@ -1,6 +1,6 @@ # frozen_string_literal: true -class MergeRequestBasicEntity < IssuableSidebarEntity +class MergeRequestBasicEntity < Grape::Entity expose :assignee_id expose :merge_status expose :merge_error diff --git a/app/serializers/merge_request_basic_serializer.rb b/app/serializers/merge_request_basic_serializer.rb deleted file mode 100644 index a68b48b00db..00000000000 --- a/app/serializers/merge_request_basic_serializer.rb +++ /dev/null @@ -1,5 +0,0 @@ -# frozen_string_literal: true - -class MergeRequestBasicSerializer < BaseSerializer - entity MergeRequestBasicEntity -end diff --git a/app/serializers/merge_request_serializer.rb b/app/serializers/merge_request_serializer.rb index 1f8c830e1aa..4cf84336aa4 100644 --- a/app/serializers/merge_request_serializer.rb +++ b/app/serializers/merge_request_serializer.rb @@ -7,9 +7,14 @@ class MergeRequestSerializer < BaseSerializer def represent(merge_request, opts = {}) entity = case opts[:serializer] - when 'basic', 'sidebar' + when 'sidebar' + MergeRequestSidebarBasicEntity + when 'sidebar_extras' + IssuableSidebarExtrasEntity + when 'basic' MergeRequestBasicEntity - else # It's 'widget' + else + # fallback to widget for old poll requests without `serializer` set MergeRequestWidgetEntity end diff --git a/app/serializers/merge_request_sidebar_basic_entity.rb b/app/serializers/merge_request_sidebar_basic_entity.rb new file mode 100644 index 00000000000..0ae7298a7c1 --- /dev/null +++ b/app/serializers/merge_request_sidebar_basic_entity.rb @@ -0,0 +1,11 @@ +# frozen_string_literal: true + +class MergeRequestSidebarBasicEntity < IssuableSidebarBasicEntity + expose :assignee, if: lambda { |issuable| issuable.assignee } do + expose :assignee, merge: true, using: API::Entities::UserBasic + + expose :can_merge do |issuable| + issuable.can_be_merged_by?(issuable.assignee) + end + end +end diff --git a/app/serializers/merge_request_widget_entity.rb b/app/serializers/merge_request_widget_entity.rb index f33a1654d5e..9731b52f1ad 100644 --- a/app/serializers/merge_request_widget_entity.rb +++ b/app/serializers/merge_request_widget_entity.rb @@ -238,6 +238,8 @@ class MergeRequestWidgetEntity < IssuableEntity end end + expose :supports_suggestion?, as: :can_receive_suggestion + private delegate :current_user, to: :request diff --git a/app/serializers/note_entity.rb b/app/serializers/note_entity.rb index c6d27817411..1d3b59eb1b7 100644 --- a/app/serializers/note_entity.rb +++ b/app/serializers/note_entity.rb @@ -36,6 +36,7 @@ class NoteEntity < API::Entities::Note end end + expose :suggestions, using: SuggestionEntity expose :resolved?, as: :resolved expose :resolvable?, as: :resolvable diff --git a/app/serializers/suggestion_entity.rb b/app/serializers/suggestion_entity.rb new file mode 100644 index 00000000000..4d0d4da10be --- /dev/null +++ b/app/serializers/suggestion_entity.rb @@ -0,0 +1,17 @@ +# frozen_string_literal: true + +class SuggestionEntity < API::Entities::Suggestion + include RequestAwareEntity + + expose :current_user do + expose :can_apply do |suggestion| + Ability.allowed?(current_user, :apply_suggestion, suggestion) + end + end + + private + + def current_user + request.current_user + end +end diff --git a/app/services/ci/create_pipeline_service.rb b/app/services/ci/create_pipeline_service.rb index 19b5552887f..f8d8ef04001 100644 --- a/app/services/ci/create_pipeline_service.rb +++ b/app/services/ci/create_pipeline_service.rb @@ -31,7 +31,8 @@ module Ci seeds_block: block, variables_attributes: params[:variables_attributes], project: project, - current_user: current_user) + current_user: current_user, + push_options: params[:push_options]) sequence = Gitlab::Ci::Pipeline::Chain::Sequence .new(pipeline, command, SEQUENCE) diff --git a/app/services/ci/register_job_service.rb b/app/services/ci/register_job_service.rb index 13321b2682e..6707a1363d0 100644 --- a/app/services/ci/register_job_service.rb +++ b/app/services/ci/register_job_service.rb @@ -118,7 +118,7 @@ module Ci # Workaround for weird Rails bug, that makes `runner.groups.to_sql` to return `runner_id = NULL` groups = ::Group.joins(:runner_namespaces).merge(runner.runner_namespaces) - hierarchy_groups = Gitlab::GroupHierarchy.new(groups).base_and_descendants + hierarchy_groups = Gitlab::ObjectHierarchy.new(groups).base_and_descendants projects = Project.where(namespace_id: hierarchy_groups) .with_group_runners_enabled .with_builds_enabled diff --git a/app/services/clusters/gcp/finalize_creation_service.rb b/app/services/clusters/gcp/finalize_creation_service.rb index 301059f0326..5525c1b9b7f 100644 --- a/app/services/clusters/gcp/finalize_creation_service.rb +++ b/app/services/clusters/gcp/finalize_creation_service.rb @@ -13,17 +13,17 @@ module Clusters configure_kubernetes cluster.save! - ClusterPlatformConfigureWorker.perform_async(cluster.id) + ClusterConfigureWorker.perform_async(cluster.id) rescue Google::Apis::ServerError, Google::Apis::ClientError, Google::Apis::AuthorizationError => e log_service_error(e.class.name, provider.id, e.message) - provider.make_errored!("Failed to request to CloudPlatform; #{e.message}") + provider.make_errored!(s_('ClusterIntegration|Failed to request to Google Cloud Platform: %{message}') % { message: e.message }) rescue Kubeclient::HttpError => e log_service_error(e.class.name, provider.id, e.message) - provider.make_errored!("Failed to run Kubeclient: #{e.message}") + provider.make_errored!(s_('ClusterIntegration|Failed to run Kubeclient: %{message}') % { message: e.message }) rescue ActiveRecord::RecordInvalid => e log_service_error(e.class.name, provider.id, e.message) - provider.make_errored!("Failed to configure Google Kubernetes Engine Cluster: #{e.message}") + provider.make_errored!(s_('ClusterIntegration|Failed to configure Google Kubernetes Engine Cluster: %{message}') % { message: e.message }) end private diff --git a/app/services/commits/tag_service.rb b/app/services/commits/tag_service.rb index 7961ba4d3c4..bb8cfb63f98 100644 --- a/app/services/commits/tag_service.rb +++ b/app/services/commits/tag_service.rb @@ -9,11 +9,10 @@ module Commits tag_name = params[:tag_name] message = params[:tag_message] - release_description = nil result = Tags::CreateService .new(commit.project, current_user) - .execute(tag_name, commit.sha, message, release_description) + .execute(tag_name, commit.sha, message) if result[:status] == :success tag = result[:tag] diff --git a/app/services/create_release_service.rb b/app/services/create_release_service.rb deleted file mode 100644 index 8d1fdbe11c3..00000000000 --- a/app/services/create_release_service.rb +++ /dev/null @@ -1,30 +0,0 @@ -# frozen_string_literal: true - -class CreateReleaseService < BaseService - # rubocop: disable CodeReuse/ActiveRecord - def execute(tag_name, release_description) - repository = project.repository - existing_tag = repository.find_tag(tag_name) - - # Only create a release if the tag exists - if existing_tag - release = project.releases.find_by(tag: tag_name) - - if release - error('Release already exists', 409) - else - release = project.releases.new({ tag: tag_name, description: release_description }) - release.save - - success(release) - end - else - error('Tag does not exist', 404) - end - end - # rubocop: enable CodeReuse/ActiveRecord - - def success(release) - super().merge(release: release) - end -end diff --git a/app/services/deploy_keys/create_service.rb b/app/services/deploy_keys/create_service.rb index a25e73666f8..0c935285657 100644 --- a/app/services/deploy_keys/create_service.rb +++ b/app/services/deploy_keys/create_service.rb @@ -2,7 +2,7 @@ module DeployKeys class CreateService < Keys::BaseService - def execute + def execute(project: nil) DeployKey.create(params.merge(user: user)) end end diff --git a/app/services/git_push_service.rb b/app/services/git_push_service.rb index f1883877d56..9ecee7c6156 100644 --- a/app/services/git_push_service.rb +++ b/app/services/git_push_service.rb @@ -174,7 +174,8 @@ class GitPushService < BaseService params[:newrev], params[:ref], @push_commits, - commits_count: commits_count) + commits_count: commits_count, + push_options: params[:push_options] || []) end def push_to_existing_branch? diff --git a/app/services/git_tag_push_service.rb b/app/services/git_tag_push_service.rb index dbadafc0f52..03fcf614c64 100644 --- a/app/services/git_tag_push_service.rb +++ b/app/services/git_tag_push_service.rb @@ -45,7 +45,8 @@ class GitTagPushService < BaseService params[:newrev], params[:ref], commits, - message) + message, + push_options: params[:push_options] || []) end def build_system_push_data diff --git a/app/services/groups/nested_create_service.rb b/app/services/groups/nested_create_service.rb index 50d34d8cb91..f01f5656296 100644 --- a/app/services/groups/nested_create_service.rb +++ b/app/services/groups/nested_create_service.rb @@ -18,7 +18,7 @@ module Groups return namespace end - if group_path.include?('/') && !Group.supports_nested_groups? + if group_path.include?('/') && !Group.supports_nested_objects? raise 'Nested groups are not supported on MySQL' end diff --git a/app/services/groups/transfer_service.rb b/app/services/groups/transfer_service.rb index 5efa746dfb9..f64e327416a 100644 --- a/app/services/groups/transfer_service.rb +++ b/app/services/groups/transfer_service.rb @@ -40,7 +40,7 @@ module Groups def ensure_allowed_transfer raise_transfer_error(:group_is_already_root) if group_is_already_root? - raise_transfer_error(:database_not_supported) unless Group.supports_nested_groups? + raise_transfer_error(:database_not_supported) unless Group.supports_nested_objects? raise_transfer_error(:same_parent_as_current) if same_parent? raise_transfer_error(:invalid_policies) unless valid_policies? raise_transfer_error(:namespace_with_same_path) if namespace_with_same_path? diff --git a/app/services/groups/update_service.rb b/app/services/groups/update_service.rb index 0bf0e967dcc..de78a3f7b27 100644 --- a/app/services/groups/update_service.rb +++ b/app/services/groups/update_service.rb @@ -31,12 +31,12 @@ module Groups def after_update if group.previous_changes.include?(:visibility_level) && group.private? # don't enqueue immediately to prevent todos removal in case of a mistake - TodosDestroyer::GroupPrivateWorker.perform_in(1.hour, group.id) + TodosDestroyer::GroupPrivateWorker.perform_in(Todo::WAIT_FOR_DELETE, group.id) end end def reject_parent_id! - params.except!(:parent_id) + params.delete(:parent_id) end def valid_share_with_group_lock_change? diff --git a/app/services/issuable/common_system_notes_service.rb b/app/services/issuable/common_system_notes_service.rb index 765de9c66b0..885e14bba8f 100644 --- a/app/services/issuable/common_system_notes_service.rb +++ b/app/services/issuable/common_system_notes_service.rb @@ -4,20 +4,23 @@ module Issuable class CommonSystemNotesService < ::BaseService attr_reader :issuable - def execute(issuable, old_labels) + def execute(issuable, old_labels: [], is_update: true) @issuable = issuable - if issuable.previous_changes.include?('title') - create_title_change_note(issuable.previous_changes['title'].first) - end + if is_update + if issuable.previous_changes.include?('title') + create_title_change_note(issuable.previous_changes['title'].first) + end - handle_description_change_note + handle_description_change_note + + handle_time_tracking_note if issuable.is_a?(TimeTrackable) + create_discussion_lock_note if issuable.previous_changes.include?('discussion_locked') + end - handle_time_tracking_note if issuable.is_a?(TimeTrackable) - create_labels_note(old_labels) if issuable.labels != old_labels - create_discussion_lock_note if issuable.previous_changes.include?('discussion_locked') - create_milestone_note if issuable.previous_changes.include?('milestone_id') create_due_date_note if issuable.previous_changes.include?('due_date') + create_milestone_note if issuable.previous_changes.include?('milestone_id') + create_labels_note(old_labels) if issuable.labels != old_labels end private diff --git a/app/services/issuable_base_service.rb b/app/services/issuable_base_service.rb index e32e262ac31..c7e7bb55e4b 100644 --- a/app/services/issuable_base_service.rb +++ b/app/services/issuable_base_service.rb @@ -152,6 +152,10 @@ class IssuableBaseService < BaseService before_create(issuable) if issuable.save + ActiveRecord::Base.no_touching do + Issuable::CommonSystemNotesService.new(project, current_user).execute(issuable, is_update: false) + end + after_create(issuable) execute_hooks(issuable) invalidate_cache_counts(issuable, users: issuable.assignees) @@ -207,7 +211,7 @@ class IssuableBaseService < BaseService if issuable.with_transaction_returning_status { issuable.save } # We do not touch as it will affect a update on updated_at field ActiveRecord::Base.no_touching do - Issuable::CommonSystemNotesService.new(project, current_user).execute(issuable, old_associations[:labels]) + Issuable::CommonSystemNotesService.new(project, current_user).execute(issuable, old_labels: old_associations[:labels]) end handle_changes(issuable, old_associations: old_associations) diff --git a/app/services/issues/update_service.rb b/app/services/issues/update_service.rb index a1d0cc0e568..e992d682c79 100644 --- a/app/services/issues/update_service.rb +++ b/app/services/issues/update_service.rb @@ -44,7 +44,7 @@ module Issues if issue.previous_changes.include?('confidential') # don't enqueue immediately to prevent todos removal in case of a mistake - TodosDestroyer::ConfidentialIssueWorker.perform_in(1.hour, issue.id) if issue.confidential? + TodosDestroyer::ConfidentialIssueWorker.perform_in(Todo::WAIT_FOR_DELETE, issue.id) if issue.confidential? create_confidentiality_note(issue) end diff --git a/app/services/labels/promote_service.rb b/app/services/labels/promote_service.rb index f30ad706c63..3c0e6196d4f 100644 --- a/app/services/labels/promote_service.rb +++ b/app/services/labels/promote_service.rb @@ -57,7 +57,7 @@ module Labels def update_issuables(new_label, label_ids) LabelLink .where(label: label_ids) - .update_all(label_id: new_label) + .update_all(label_id: new_label.id) end # rubocop: enable CodeReuse/ActiveRecord @@ -65,7 +65,7 @@ module Labels def update_resource_label_events(new_label, label_ids) ResourceLabelEvent .where(label: label_ids) - .update_all(label_id: new_label) + .update_all(label_id: new_label.id) end # rubocop: enable CodeReuse/ActiveRecord @@ -73,7 +73,7 @@ module Labels def update_issue_board_lists(new_label, label_ids) List .where(label: label_ids) - .update_all(label_id: new_label) + .update_all(label_id: new_label.id) end # rubocop: enable CodeReuse/ActiveRecord @@ -81,7 +81,7 @@ module Labels def update_priorities(new_label, label_ids) LabelPriority .where(label: label_ids) - .update_all(label_id: new_label) + .update_all(label_id: new_label.id) end # rubocop: enable CodeReuse/ActiveRecord diff --git a/app/services/members/base_service.rb b/app/services/members/base_service.rb index d734571f835..e78affff797 100644 --- a/app/services/members/base_service.rb +++ b/app/services/members/base_service.rb @@ -47,5 +47,11 @@ module Members raise "Unknown action '#{action}' on #{member}!" end end + + def enqueue_delete_todos(member) + type = member.is_a?(GroupMember) ? 'Group' : 'Project' + # don't enqueue immediately to prevent todos removal in case of a mistake + TodosDestroyer::EntityLeaveWorker.perform_in(Todo::WAIT_FOR_DELETE, member.user_id, member.source_id, type) + end end end diff --git a/app/services/members/destroy_service.rb b/app/services/members/destroy_service.rb index c186a5971dc..ae0c644e6c0 100644 --- a/app/services/members/destroy_service.rb +++ b/app/services/members/destroy_service.rb @@ -15,7 +15,7 @@ module Members notification_service.decline_access_request(member) end - enqeue_delete_todos(member) + enqueue_delete_todos(member) after_execute(member: member) @@ -24,12 +24,6 @@ module Members private - def enqeue_delete_todos(member) - type = member.is_a?(GroupMember) ? 'Group' : 'Project' - # don't enqueue immediately to prevent todos removal in case of a mistake - TodosDestroyer::EntityLeaveWorker.perform_in(1.hour, member.user_id, member.source_id, type) - end - def can_destroy_member?(member) can?(current_user, destroy_member_permission(member), member) end diff --git a/app/services/members/update_service.rb b/app/services/members/update_service.rb index 1f5618dae53..ff8d5c1d8c9 100644 --- a/app/services/members/update_service.rb +++ b/app/services/members/update_service.rb @@ -10,9 +10,18 @@ module Members if member.update(params) after_execute(action: permission, old_access_level: old_access_level, member: member) + + # Deletes only confidential issues todos for guests + enqueue_delete_todos(member) if downgrading_to_guest? end member end + + private + + def downgrading_to_guest? + params[:access_level] == Gitlab::Access::GUEST + end end end diff --git a/app/services/merge_requests/build_service.rb b/app/services/merge_requests/build_service.rb index 36767621d74..48419da98ad 100644 --- a/app/services/merge_requests/build_service.rb +++ b/app/services/merge_requests/build_service.rb @@ -18,7 +18,7 @@ module MergeRequests merge_request.source_project = find_source_project merge_request.target_project = find_target_project merge_request.target_branch = find_target_branch - merge_request.can_be_created = branches_valid? + merge_request.can_be_created = projects_and_branches_valid? # compare branches only if branches are valid, otherwise # compare_branches may raise an error @@ -49,15 +49,19 @@ module MergeRequests to: :merge_request def find_source_project - return source_project if source_project.present? && can?(current_user, :read_project, source_project) + return source_project if source_project.present? && can?(current_user, :create_merge_request_from, source_project) project end def find_target_project - return target_project if target_project.present? && can?(current_user, :read_project, target_project) + return target_project if target_project.present? && can?(current_user, :create_merge_request_in, target_project) - project.default_merge_request_target + target_project = project.default_merge_request_target + + return target_project if target_project.present? && can?(current_user, :create_merge_request_in, target_project) + + project end def find_target_branch @@ -72,10 +76,11 @@ module MergeRequests params[:target_branch].present? end - def branches_valid? + def projects_and_branches_valid? + return false if source_project.nil? || target_project.nil? return false unless source_branch_specified? || target_branch_specified? - validate_branches + validate_projects_and_branches errors.blank? end @@ -94,7 +99,12 @@ module MergeRequests end end - def validate_branches + def validate_projects_and_branches + merge_request.validate_target_project + merge_request.validate_fork + + return if errors.any? + add_error('You must select source and target branch') unless branches_present? add_error('You must select different branches') if same_source_and_target? add_error("Source branch \"#{source_branch}\" does not exist") unless source_branch_exists? diff --git a/app/services/merge_requests/update_service.rb b/app/services/merge_requests/update_service.rb index aacaf10d09c..86a04587f79 100644 --- a/app/services/merge_requests/update_service.rb +++ b/app/services/merge_requests/update_service.rb @@ -5,14 +5,15 @@ module MergeRequests def execute(merge_request) # We don't allow change of source/target projects and source branch # after merge request was created - params.except!(:source_project_id) - params.except!(:target_project_id) - params.except!(:source_branch) + params.delete(:source_project_id) + params.delete(:target_project_id) + params.delete(:source_branch) merge_from_quick_action(merge_request) if params[:merge] if merge_request.closed_without_fork? - params.except!(:target_branch, :force_remove_source_branch) + params.delete(:target_branch) + params.delete(:force_remove_source_branch) end if params[:force_remove_source_branch].present? @@ -45,11 +46,13 @@ module MergeRequests end if merge_request.previous_changes.include?('assignee_id') + reassigned_merge_request_args = [merge_request, current_user] + old_assignee_id = merge_request.previous_changes['assignee_id'].first - old_assignee = User.find(old_assignee_id) if old_assignee_id + reassigned_merge_request_args << User.find(old_assignee_id) if old_assignee_id create_assignee_note(merge_request) - notification_service.async.reassigned_merge_request(merge_request, current_user, old_assignee) + notification_service.async.reassigned_merge_request(*reassigned_merge_request_args) todo_service.reassigned_merge_request(merge_request, current_user) end diff --git a/app/services/notes/create_service.rb b/app/services/notes/create_service.rb index e03789e3ca9..c4546f30235 100644 --- a/app/services/notes/create_service.rb +++ b/app/services/notes/create_service.rb @@ -36,6 +36,7 @@ module Notes if !only_commands && note.save todo_service.new_note(note, current_user) clear_noteable_diffs_cache(note) + Suggestions::CreateService.new(note).execute end if command_params.present? diff --git a/app/services/notes/update_service.rb b/app/services/notes/update_service.rb index 35db409eb27..d2052bed646 100644 --- a/app/services/notes/update_service.rb +++ b/app/services/notes/update_service.rb @@ -14,6 +14,17 @@ module Notes TodoService.new.update_note(note, current_user, old_mentioned_users) end + if note.supports_suggestion? + Suggestion.transaction do + note.suggestions.delete_all + Suggestions::CreateService.new(note).execute + end + + # We need to refresh the previous suggestions call cache + # in order to get the new records. + note.reload + end + note end end diff --git a/app/services/notification_service.rb b/app/services/notification_service.rb index ff035fea216..e1cf327209b 100644 --- a/app/services/notification_service.rb +++ b/app/services/notification_service.rb @@ -188,7 +188,7 @@ class NotificationService # * merge_request assignee if their notification level is not Disabled # * users with custom level checked with "reassign merge request" # - def reassigned_merge_request(merge_request, current_user, previous_assignee) + def reassigned_merge_request(merge_request, current_user, previous_assignee = nil) recipients = NotificationRecipientService.build_recipients( merge_request, current_user, diff --git a/app/services/preview_markdown_service.rb b/app/services/preview_markdown_service.rb index de8757006f1..a449a5dc3e9 100644 --- a/app/services/preview_markdown_service.rb +++ b/app/services/preview_markdown_service.rb @@ -4,10 +4,12 @@ class PreviewMarkdownService < BaseService def execute text, commands = explain_quick_actions(params[:text]) users = find_user_references(text) + suggestions = find_suggestions(text) success( text: text, users: users, + suggestions: suggestions, commands: commands.join(' '), markdown_engine: markdown_engine ) @@ -28,6 +30,12 @@ class PreviewMarkdownService < BaseService extractor.users.map(&:username) end + def find_suggestions(text) + return [] unless params[:preview_suggestions] + + Banzai::SuggestionsParser.parse(text) + end + def find_commands_target QuickActions::TargetService .new(project, current_user) diff --git a/app/services/projects/after_rename_service.rb b/app/services/projects/after_rename_service.rb index 4131da44f5a..aa9b253eb20 100644 --- a/app/services/projects/after_rename_service.rb +++ b/app/services/projects/after_rename_service.rb @@ -81,6 +81,7 @@ module Projects def update_repository_configuration project.reload_repository! project.write_repository_config + project.track_project_repository end def rename_transferred_documents diff --git a/app/services/projects/destroy_service.rb b/app/services/projects/destroy_service.rb index 210571b6b4e..336d029d330 100644 --- a/app/services/projects/destroy_service.rb +++ b/app/services/projects/destroy_service.rb @@ -137,6 +137,8 @@ module Projects raise_error('Failed to remove some tags in project container registry. Please try again or contact administrator.') end + project.leave_pool_repository + Project.transaction do log_destroy_event trash_repositories! diff --git a/app/services/projects/lfs_pointers/lfs_download_service.rb b/app/services/projects/lfs_pointers/lfs_download_service.rb index 1c4a8d05be6..b5128443435 100644 --- a/app/services/projects/lfs_pointers/lfs_download_service.rb +++ b/app/services/projects/lfs_pointers/lfs_download_service.rb @@ -4,33 +4,51 @@ module Projects module LfsPointers class LfsDownloadService < BaseService + VALID_PROTOCOLS = %w[http https].freeze + # rubocop: disable CodeReuse/ActiveRecord def execute(oid, url) return unless project&.lfs_enabled? && oid.present? && url.present? return if LfsObject.exists?(oid: oid) - sanitized_uri = Gitlab::UrlSanitizer.new(url) + sanitized_uri = sanitize_url!(url) with_tmp_file(oid) do |file| - size = download_and_save_file(file, sanitized_uri) - lfs_object = LfsObject.new(oid: oid, size: size, file: file) + download_and_save_file(file, sanitized_uri) + lfs_object = LfsObject.new(oid: oid, size: file.size, file: file) project.all_lfs_objects << lfs_object end + rescue Gitlab::UrlBlocker::BlockedUrlError => e + Rails.logger.error("LFS file with oid #{oid} couldn't be downloaded: #{e.message}") rescue StandardError => e - Rails.logger.error("LFS file with oid #{oid} could't be downloaded from #{sanitized_uri.sanitized_url}: #{e.message}") + Rails.logger.error("LFS file with oid #{oid} couldn't be downloaded from #{sanitized_uri.sanitized_url}: #{e.message}") end # rubocop: enable CodeReuse/ActiveRecord private + def sanitize_url!(url) + Gitlab::UrlSanitizer.new(url).tap do |sanitized_uri| + # Just validate that HTTP/HTTPS protocols are used. The + # subsequent Gitlab::HTTP.get call will do network checks + # based on the settings. + Gitlab::UrlBlocker.validate!(sanitized_uri.sanitized_url, + protocols: VALID_PROTOCOLS) + end + end + def download_and_save_file(file, sanitized_uri) - IO.copy_stream(open(sanitized_uri.sanitized_url, headers(sanitized_uri)), file) # rubocop:disable Security/Open + response = Gitlab::HTTP.get(sanitized_uri.sanitized_url, headers(sanitized_uri)) do |fragment| + file.write(fragment) + end + + raise StandardError, "Received error code #{response.code}" unless response.success? end def headers(sanitized_uri) - {}.tap do |headers| + query_options.tap do |headers| credentials = sanitized_uri.credentials if credentials[:user].present? || credentials[:password].present? @@ -40,10 +58,14 @@ module Projects end end + def query_options + { stream_body: true } + end + def with_tmp_file(oid) create_tmp_storage_dir - File.open(File.join(tmp_storage_dir, oid), 'w') { |file| yield file } + File.open(File.join(tmp_storage_dir, oid), 'wb') { |file| yield file } end def create_tmp_storage_dir diff --git a/app/services/projects/transfer_service.rb b/app/services/projects/transfer_service.rb index 9db3fd9cf17..5da1e39a1fb 100644 --- a/app/services/projects/transfer_service.rb +++ b/app/services/projects/transfer_service.rb @@ -81,7 +81,7 @@ module Projects project.old_path_with_namespace = @old_path - write_repository_config(@new_path) + update_repository_configuration(@new_path) execute_system_hooks end @@ -106,8 +106,9 @@ module Projects project.save! end - def write_repository_config(full_path) + def update_repository_configuration(full_path) project.write_repository_config(gl_full_path: full_path) + project.track_project_repository end def refresh_permissions @@ -123,7 +124,7 @@ module Projects rollback_folder_move project.reload update_namespace_and_visibility(@old_namespace) - write_repository_config(@old_path) + update_repository_configuration(@old_path) end def rollback_folder_move diff --git a/app/services/projects/update_service.rb b/app/services/projects/update_service.rb index 93e48fc0199..dd1b9680ece 100644 --- a/app/services/projects/update_service.rb +++ b/app/services/projects/update_service.rb @@ -61,9 +61,9 @@ module Projects if project.previous_changes.include?(:visibility_level) && project.private? # don't enqueue immediately to prevent todos removal in case of a mistake - TodosDestroyer::ProjectPrivateWorker.perform_in(1.hour, project.id) + TodosDestroyer::ProjectPrivateWorker.perform_in(Todo::WAIT_FOR_DELETE, project.id) elsif (project_changed_feature_keys & todos_features_changes).present? - TodosDestroyer::PrivateFeaturesWorker.perform_in(1.hour, project.id) + TodosDestroyer::PrivateFeaturesWorker.perform_in(Todo::WAIT_FOR_DELETE, project.id) end if project.previous_changes.include?('path') diff --git a/app/services/releases/concerns.rb b/app/services/releases/concerns.rb new file mode 100644 index 00000000000..a04bb8f9e14 --- /dev/null +++ b/app/services/releases/concerns.rb @@ -0,0 +1,48 @@ +# frozen_string_literal: true + +module Releases + module Concerns + extend ActiveSupport::Concern + include Gitlab::Utils::StrongMemoize + + included do + def tag_name + params[:tag] + end + + def ref + params[:ref] + end + + def name + params[:name] + end + + def description + params[:description] + end + + def release + strong_memoize(:release) do + project.releases.find_by_tag(tag_name) + end + end + + def existing_tag + strong_memoize(:existing_tag) do + repository.find_tag(tag_name) + end + end + + def tag_exist? + existing_tag.present? + end + + def repository + strong_memoize(:repository) do + project.repository + end + end + end + end +end diff --git a/app/services/releases/create_service.rb b/app/services/releases/create_service.rb new file mode 100644 index 00000000000..73fcebf79af --- /dev/null +++ b/app/services/releases/create_service.rb @@ -0,0 +1,54 @@ +# frozen_string_literal: true + +module Releases + class CreateService < BaseService + include Releases::Concerns + + def execute + return error('Access Denied', 403) unless allowed? + return error('Release already exists', 409) if release + + tag = ensure_tag + + return tag unless tag.is_a?(Gitlab::Git::Tag) + + create_release(tag) + end + + private + + def ensure_tag + existing_tag || create_tag + end + + def create_tag + return error('Ref is not specified', 422) unless ref + + result = Tags::CreateService + .new(project, current_user) + .execute(tag_name, ref, nil) + + return result unless result[:status] == :success + + result[:tag] + end + + def allowed? + Ability.allowed?(current_user, :create_release, project) + end + + def create_release(tag) + release = project.releases.create!( + name: name, + description: description, + author: current_user, + tag: tag.name, + sha: tag.dereferenced_target.sha + ) + + success(tag: tag, release: release) + rescue ActiveRecord::RecordInvalid => e + error(e.message, 400) + end + end +end diff --git a/app/services/releases/destroy_service.rb b/app/services/releases/destroy_service.rb new file mode 100644 index 00000000000..8c2bc3b4e6e --- /dev/null +++ b/app/services/releases/destroy_service.rb @@ -0,0 +1,25 @@ +# frozen_string_literal: true + +module Releases + class DestroyService < BaseService + include Releases::Concerns + + def execute + return error('Tag does not exist', 404) unless existing_tag + return error('Release does not exist', 404) unless release + return error('Access Denied', 403) unless allowed? + + if release.destroy + success(tag: existing_tag, release: release) + else + error(release.errors.messages || '400 Bad request', 400) + end + end + + private + + def allowed? + Ability.allowed?(current_user, :destroy_release, release) + end + end +end diff --git a/app/services/releases/update_service.rb b/app/services/releases/update_service.rb new file mode 100644 index 00000000000..fabfa398c59 --- /dev/null +++ b/app/services/releases/update_service.rb @@ -0,0 +1,32 @@ +# frozen_string_literal: true + +module Releases + class UpdateService < BaseService + include Releases::Concerns + + def execute + return error('Tag does not exist', 404) unless existing_tag + return error('Release does not exist', 404) unless release + return error('Access Denied', 403) unless allowed? + return error('params is empty', 400) if empty_params? + + if release.update(params) + success(tag: existing_tag, release: release) + else + error(release.errors.messages || '400 Bad request', 400) + end + end + + private + + def allowed? + Ability.allowed?(current_user, :update_release, release) + end + + # rubocop: disable CodeReuse/ActiveRecord + def empty_params? + params.except(:tag).empty? + end + # rubocop: enable CodeReuse/ActiveRecord + end +end diff --git a/app/services/suggestions/apply_service.rb b/app/services/suggestions/apply_service.rb new file mode 100644 index 00000000000..d931d528c86 --- /dev/null +++ b/app/services/suggestions/apply_service.rb @@ -0,0 +1,54 @@ +# frozen_string_literal: true + +module Suggestions + class ApplyService < ::BaseService + def initialize(current_user) + @current_user = current_user + end + + def execute(suggestion) + unless suggestion.appliable? + return error('Suggestion is not appliable') + end + + params = file_update_params(suggestion) + result = ::Files::UpdateService.new(suggestion.project, @current_user, params).execute + + if result[:status] == :success + suggestion.update(commit_id: result[:result], applied: true) + end + + result + end + + private + + def file_update_params(suggestion) + diff_file = suggestion.diff_file + + file_path = diff_file.file_path + branch_name = suggestion.noteable.source_branch + file_content = new_file_content(suggestion) + commit_message = "Apply suggestion to #{file_path}" + + { + file_path: file_path, + branch_name: branch_name, + start_branch: branch_name, + commit_message: commit_message, + file_content: file_content + } + end + + def new_file_content(suggestion) + range = suggestion.from_line_index..suggestion.to_line_index + blob = suggestion.diff_file.new_blob + + blob.load_all_data! + content = blob.data.lines + content[range] = suggestion.to_content + + content.join + end + end +end diff --git a/app/services/suggestions/create_service.rb b/app/services/suggestions/create_service.rb new file mode 100644 index 00000000000..77e958cbe0c --- /dev/null +++ b/app/services/suggestions/create_service.rb @@ -0,0 +1,56 @@ +# frozen_string_literal: true + +module Suggestions + class CreateService + def initialize(note) + @note = note + end + + def execute + return unless @note.supports_suggestion? + + suggestions = Banzai::SuggestionsParser.parse(@note.note) + + # For single line suggestion we're only looking forward to + # change the line receiving the comment. Though, in + # https://gitlab.com/gitlab-org/gitlab-ce/issues/53310 + # we'll introduce a ```suggestion:L<x>-<y>, so this will + # slightly change. + comment_line = @note.position.new_line + + rows = + suggestions.map.with_index do |suggestion, index| + from_content = changing_lines(comment_line, comment_line) + + # The parsed suggestion doesn't have information about the correct + # ending characters (we may have a line break, or not), so we take + # this information from the last line being changed (last + # characters). + endline_chars = line_break_chars(from_content.lines.last) + to_content = "#{suggestion}#{endline_chars}" + + { + note_id: @note.id, + from_content: from_content, + to_content: to_content, + relative_order: index + } + end + + rows.in_groups_of(100, false) do |rows| + Gitlab::Database.bulk_insert('suggestions', rows) + end + end + + private + + def changing_lines(from_line, to_line) + @note.diff_file.new_blob_lines_between(from_line, to_line).join + end + + def line_break_chars(line) + match = /\r\n|\r|\n/.match(line) + match[0] if match + end + end +end diff --git a/app/services/tags/create_service.rb b/app/services/tags/create_service.rb index 35390f5082c..4de6b2d2774 100644 --- a/app/services/tags/create_service.rb +++ b/app/services/tags/create_service.rb @@ -2,7 +2,7 @@ module Tags class CreateService < BaseService - def execute(tag_name, target, message, release_description = nil) + def execute(tag_name, target, message) valid_tag = Gitlab::GitRefValidator.validate(tag_name) return error('Tag name invalid') unless valid_tag @@ -20,10 +20,7 @@ module Tags end if new_tag - if release_description - CreateReleaseService.new(@project, @current_user) - .execute(tag_name, release_description) - end + repository.expire_tags_cache success.merge(tag: new_tag) else diff --git a/app/services/tags/destroy_service.rb b/app/services/tags/destroy_service.rb index 6bfef09ac54..cab507946b4 100644 --- a/app/services/tags/destroy_service.rb +++ b/app/services/tags/destroy_service.rb @@ -2,7 +2,6 @@ module Tags class DestroyService < BaseService - # rubocop: disable CodeReuse/ActiveRecord def execute(tag_name) repository = project.repository tag = repository.find_tag(tag_name) @@ -12,8 +11,12 @@ module Tags end if repository.rm_tag(current_user, tag_name) - release = project.releases.find_by(tag: tag_name) - release&.destroy + ## + # When a tag in a repository is destroyed, + # release assets will be destroyed too. + Releases::DestroyService + .new(project, current_user, tag: tag_name) + .execute push_data = build_push_data(tag) EventCreateService.new.push(project, current_user, push_data) @@ -27,7 +30,6 @@ module Tags rescue Gitlab::Git::PreReceiveError => ex error(ex.message) end - # rubocop: enable CodeReuse/ActiveRecord def error(message, return_code = 400) super(message).merge(return_code: return_code) diff --git a/app/services/update_release_service.rb b/app/services/update_release_service.rb deleted file mode 100644 index e2228ca026c..00000000000 --- a/app/services/update_release_service.rb +++ /dev/null @@ -1,28 +0,0 @@ -# frozen_string_literal: true - -class UpdateReleaseService < BaseService - # rubocop: disable CodeReuse/ActiveRecord - def execute(tag_name, release_description) - repository = project.repository - existing_tag = repository.find_tag(tag_name) - - if existing_tag - release = project.releases.find_by(tag: tag_name) - - if release - release.update(description: release_description) - - success(release) - else - error('Release does not exist', 404) - end - else - error('Tag does not exist', 404) - end - end - # rubocop: enable CodeReuse/ActiveRecord - - def success(release) - super().merge(release: release) - end -end diff --git a/app/services/users/refresh_authorized_projects_service.rb b/app/services/users/refresh_authorized_projects_service.rb index 23b63aaabdf..fe5a82e23fa 100644 --- a/app/services/users/refresh_authorized_projects_service.rb +++ b/app/services/users/refresh_authorized_projects_service.rb @@ -102,7 +102,7 @@ module Users end def fresh_authorizations - klass = if Group.supports_nested_groups? + klass = if Group.supports_nested_objects? Gitlab::ProjectAuthorizations::WithNestedGroups else Gitlab::ProjectAuthorizations::WithoutNestedGroups diff --git a/app/services/users/update_service.rb b/app/services/users/update_service.rb index a897e4bd56a..af4fe1aebb9 100644 --- a/app/services/users/update_service.rb +++ b/app/services/users/update_service.rb @@ -16,7 +16,7 @@ module Users user_exists = @user.persisted? - assign_attributes(&block) + assign_attributes if @user.save(validate: validate) && update_status notify_success(user_exists) @@ -48,9 +48,11 @@ module Users success end - def assign_attributes(&block) - if @user.user_synced_attributes_metadata - params.except!(*@user.user_synced_attributes_metadata.read_only_attributes) + def assign_attributes + if (metadata = @user.user_synced_attributes_metadata) + read_only = metadata.read_only_attributes + + params.reject! { |key, _| read_only.include?(key.to_sym) } end @user.assign_attributes(params) if params.any? diff --git a/app/views/admin/appearances/_form.html.haml b/app/views/admin/appearances/_form.html.haml index cb67079853e..544f09048f5 100644 --- a/app/views/admin/appearances/_form.html.haml +++ b/app/views/admin/appearances/_form.html.haml @@ -8,7 +8,7 @@ = f.label :header_logo, 'Header logo', class: 'col-sm-2 col-form-label pt-0' .col-sm-10 - if @appearance.header_logo? - = image_tag @appearance.header_logo_url, class: 'appearance-light-logo-preview' + = image_tag @appearance.header_logo_path, class: 'appearance-light-logo-preview' - if @appearance.persisted? %br = link_to 'Remove header logo', header_logos_admin_appearances_path, data: { confirm: "Header logo will be removed. Are you sure?"}, method: :delete, class: "btn btn-inverted btn-remove btn-sm remove-logo" @@ -25,7 +25,7 @@ = f.label :favicon, 'Favicon', class: 'col-sm-2 col-form-label pt-0' .col-sm-10 - if @appearance.favicon? - = image_tag @appearance.favicon_url, class: 'appearance-light-logo-preview' + = image_tag @appearance.favicon_path, class: 'appearance-light-logo-preview' - if @appearance.persisted? %br = link_to 'Remove favicon', favicon_admin_appearances_path, data: { confirm: "Favicon will be removed. Are you sure?"}, method: :delete, class: "btn btn-inverted btn-remove btn-sm remove-logo" @@ -54,7 +54,7 @@ = f.label :logo, class: 'col-sm-2 col-form-label pt-0' .col-sm-10 - if @appearance.logo? - = image_tag @appearance.logo_url, class: 'appearance-logo-preview' + = image_tag @appearance.logo_path, class: 'appearance-logo-preview' - if @appearance.persisted? %br = link_to 'Remove logo', logo_admin_appearances_path, data: { confirm: "Logo will be removed. Are you sure?"}, method: :delete, class: "btn btn-inverted btn-remove btn-sm remove-logo" diff --git a/app/views/admin/application_settings/_ci_cd.html.haml b/app/views/admin/application_settings/_ci_cd.html.haml index 0d42094fc89..fdaad1cf181 100644 --- a/app/views/admin/application_settings/_ci_cd.html.haml +++ b/app/views/admin/application_settings/_ci_cd.html.haml @@ -49,5 +49,12 @@ Once that time passes, the jobs will be archived and no longer able to be retried. Make it empty to never expire jobs. It has to be no less than 1 day, for example: <code>15 days</code>, <code>1 month</code>, <code>2 years</code>. + .form-group + .form-check + = f.check_box :protected_ci_variables, class: 'form-check-input' + = f.label :protected_ci_variables, class: 'form-check-label' do + = s_('AdminSettings|Environment variables are protected by default') + .form-text.text-muted + = s_('AdminSettings|When creating a new environment variable it will be protected by default.') = f.submit 'Save changes', class: "btn btn-success" diff --git a/app/views/admin/runners/_sort_dropdown.html.haml b/app/views/admin/runners/_sort_dropdown.html.haml index 19c2a50ebd9..4f4f0a543e0 100644 --- a/app/views/admin/runners/_sort_dropdown.html.haml +++ b/app/views/admin/runners/_sort_dropdown.html.haml @@ -6,6 +6,6 @@ = icon('chevron-down') %ul.dropdown-menu.dropdown-menu-right.dropdown-menu-selectable.dropdown-menu-sort %li - = sortable_item(sort_title_created_date, page_filter_path(sort: sort_value_created_date, label: true), sorted_by) - = sortable_item(sort_title_contacted_date, page_filter_path(sort: sort_value_contacted_date, label: true), sorted_by) + = sortable_item(sort_title_created_date, page_filter_path(sort: sort_value_created_date), sorted_by) + = sortable_item(sort_title_contacted_date, page_filter_path(sort: sort_value_contacted_date), sorted_by) diff --git a/app/views/ci/variables/_content.html.haml b/app/views/ci/variables/_content.html.haml index d355e7799df..fa82611d9c1 100644 --- a/app/views/ci/variables/_content.html.haml +++ b/app/views/ci/variables/_content.html.haml @@ -1 +1 @@ -= _('Variables are applied to environments via the runner. They can be protected by only exposing them to protected branches or tags. You can use variables for passwords, secret keys, or whatever you want.') += _('Environment variables are applied to environments via the runner. They can be protected by only exposing them to protected branches or tags. You can use environment variables for passwords, secret keys, or whatever you want.') diff --git a/app/views/ci/variables/_header.html.haml b/app/views/ci/variables/_header.html.haml new file mode 100644 index 00000000000..cb7779e2175 --- /dev/null +++ b/app/views/ci/variables/_header.html.haml @@ -0,0 +1,11 @@ +- expanded = local_assigns.fetch(:expanded) + +%h4 + = _('Environment variables') + = link_to icon('question-circle'), help_page_path('ci/variables/README', anchor: 'variables'), target: '_blank', rel: 'noopener noreferrer' + +%button.btn.btn-default.js-settings-toggle{ type: 'button' } + = expanded ? _('Collapse') : _('Expand') + +%p.append-bottom-0 + = render "ci/variables/content" diff --git a/app/views/ci/variables/_index.html.haml b/app/views/ci/variables/_index.html.haml index f34305e94fa..dc9ccb6cc39 100644 --- a/app/views/ci/variables/_index.html.haml +++ b/app/views/ci/variables/_index.html.haml @@ -1,5 +1,10 @@ - save_endpoint = local_assigns.fetch(:save_endpoint, nil) +- if ci_variable_protected_by_default? + %p.settings-message.text-center + - link_start = '<a href="%{url}">'.html_safe % { url: help_page_path('ci/variables/README', anchor: 'protected-variables') } + = s_('Environment variables are configured by your administrator to be %{link_start}protected%{link_end} by default').html_safe % { link_start: link_start, link_end: '</a>'.html_safe } + .row .col-lg-12.js-ci-variable-list-section{ data: { save_endpoint: save_endpoint } } .hide.alert.alert-danger.js-ci-variable-error-box diff --git a/app/views/ci/variables/_variable_row.html.haml b/app/views/ci/variables/_variable_row.html.haml index 6ee55836dd2..16a7527c8ce 100644 --- a/app/views/ci/variables/_variable_row.html.haml +++ b/app/views/ci/variables/_variable_row.html.haml @@ -5,7 +5,8 @@ - id = variable&.id - key = variable&.key - value = variable&.value -- is_protected = variable && !only_key_value ? variable.protected : false +- is_protected_default = ci_variable_protected_by_default? +- is_protected = ci_variable_protected?(variable, only_key_value) - id_input_name = "#{form_field}[variables_attributes][][id]" - destroy_input_name = "#{form_field}[variables_attributes][][_destroy]" @@ -39,7 +40,8 @@ %input{ type: "hidden", class: 'js-ci-variable-input-protected js-project-feature-toggle-input', name: protected_input_name, - value: is_protected } + value: is_protected, + data: { default: is_protected_default.to_s } } %span.toggle-icon = sprite_icon('status_success_borderless', size: 16, css_class: 'toggle-icon-svg toggle-status-checked') = sprite_icon('status_failed_borderless', size: 16, css_class: 'toggle-icon-svg toggle-status-unchecked') diff --git a/app/views/clusters/clusters/_buttons.html.haml b/app/views/clusters/clusters/_buttons.html.haml index db2e247e341..c81d1d5b05a 100644 --- a/app/views/clusters/clusters/_buttons.html.haml +++ b/app/views/clusters/clusters/_buttons.html.haml @@ -1,4 +1,6 @@ --# This partial is overridden in EE .nav-controls - %span.btn.btn-add-cluster.disabled.js-add-cluster - = s_("ClusterIntegration|Add Kubernetes cluster") + - if clusterable.can_add_cluster? + = link_to s_('ClusterIntegration|Add Kubernetes cluster'), clusterable.new_path, class: 'btn btn-success js-add-cluster' + - else + %span.btn.btn-add-cluster.disabled.js-add-cluster + = s_("ClusterIntegration|Add Kubernetes cluster") diff --git a/app/views/clusters/clusters/_cluster.html.haml b/app/views/clusters/clusters/_cluster.html.haml index adeca013749..b89789e9915 100644 --- a/app/views/clusters/clusters/_cluster.html.haml +++ b/app/views/clusters/clusters/_cluster.html.haml @@ -3,7 +3,7 @@ .table-section.section-60 .table-mobile-header{ role: "rowheader" }= s_("ClusterIntegration|Kubernetes cluster") .table-mobile-content - = link_to cluster.name, cluster.show_path + = cluster.item_link(clusterable) - unless cluster.enabled? %span.badge.badge-danger Connection disabled .table-section.section-25 @@ -13,4 +13,4 @@ .table-mobile-header{ role: "rowheader" } .table-mobile-content %span.badge.badge-light - = cluster.project_type? ? s_("ClusterIntegration|Project cluster") : s_("ClusterIntegration|Group cluster") + = cluster.cluster_type_description diff --git a/app/views/clusters/clusters/_empty_state.html.haml b/app/views/clusters/clusters/_empty_state.html.haml index c926ec258f0..cfdbfe2dea1 100644 --- a/app/views/clusters/clusters/_empty_state.html.haml +++ b/app/views/clusters/clusters/_empty_state.html.haml @@ -9,6 +9,6 @@ = clusterable.empty_state_help_text = clusterable.learn_more_link - - if clusterable.can_create_cluster? + - if clusterable.can_add_cluster? .text-center = link_to s_('ClusterIntegration|Add Kubernetes cluster'), clusterable.new_path, class: 'btn btn-success' diff --git a/app/views/clusters/clusters/index.html.haml b/app/views/clusters/clusters/index.html.haml index ad6d1d856d6..58d0a304363 100644 --- a/app/views/clusters/clusters/index.html.haml +++ b/app/views/clusters/clusters/index.html.haml @@ -11,6 +11,13 @@ .nav-text = s_("ClusterIntegration|Kubernetes clusters can be used to deploy applications and to provide Review Apps for this project") = render 'clusters/clusters/buttons' + + - if @has_ancestor_clusters + .bs-callout.bs-callout-info + = s_("ClusterIntegration|Clusters are utilized by selecting the nearest ancestor with a matching environment scope. For example, project clusters will override group clusters.") + %strong + = link_to _('More information'), help_page_path('user/group/clusters/', anchor: 'cluster-precedence') + .clusters-table.js-clusters-list .gl-responsive-table-row.table-row-header{ role: "row" } .table-section.section-60{ role: "rowheader" } diff --git a/app/views/dashboard/milestones/_milestone.html.haml b/app/views/dashboard/milestones/_milestone.html.haml index b876d6fd1f3..89212eb6bf9 100644 --- a/app/views/dashboard/milestones/_milestone.html.haml +++ b/app/views/dashboard/milestones/_milestone.html.haml @@ -1,5 +1,5 @@ = render 'shared/milestones/milestone', - milestone_path: group_or_dashboard_milestone_path(milestone), + milestone_path: group_or_project_milestone_path(milestone), issues_path: issues_dashboard_path(milestone_title: milestone.title), merge_requests_path: merge_requests_dashboard_path(milestone_title: milestone.title), milestone: milestone, diff --git a/app/views/events/_events.html.haml b/app/views/events/_events.html.haml index 68c19df092d..6ae4c334f7f 100644 --- a/app/views/events/_events.html.haml +++ b/app/views/events/_events.html.haml @@ -1 +1,4 @@ -= render partial: 'events/event', collection: @events +- if @events.present? + = render partial: 'events/event', collection: @events +- else + .nothing-here-block= _("No activities found") diff --git a/app/views/groups/settings/ci_cd/show.html.haml b/app/views/groups/settings/ci_cd/show.html.haml index a5e6abdba52..d9332e36ef5 100644 --- a/app/views/groups/settings/ci_cd/show.html.haml +++ b/app/views/groups/settings/ci_cd/show.html.haml @@ -5,13 +5,7 @@ %section.settings#ci-variables.no-animate{ class: ('expanded' if expanded) } .settings-header - %h4 - = _('Variables') - = link_to icon('question-circle'), help_page_path('ci/variables/README', anchor: 'variables'), target: '_blank', rel: 'noopener noreferrer' - %button.btn.btn-default.js-settings-toggle{ type: "button" } - = expanded ? _('Collapse') : _('Expand') - %p.append-bottom-0 - = render "ci/variables/content" + = render 'ci/variables/header', expanded: expanded .settings-content = render 'ci/variables/index', save_endpoint: group_variables_path diff --git a/app/views/issues/_issues_calendar.ics.ruby b/app/views/issues/_issues_calendar.ics.ruby index 73ab8489e0c..94c3099ace2 100644 --- a/app/views/issues/_issues_calendar.ics.ruby +++ b/app/views/issues/_issues_calendar.ics.ruby @@ -3,7 +3,7 @@ cal.prodid = '-//GitLab//NONSGML GitLab//EN' cal.x_wr_calname = 'GitLab Issues' # rubocop: disable CodeReuse/ActiveRecord -@issues.includes(project: :namespace).each do |issue| +@issues.preload(project: :namespace).each do |issue| cal.event do |event| event.dtstart = Icalendar::Values::Date.new(issue.due_date) event.summary = "#{issue.title} (in #{issue.project.full_path})" diff --git a/app/views/layouts/_search.html.haml b/app/views/layouts/_search.html.haml index a86972d8cf3..a6023a1cbb9 100644 --- a/app/views/layouts/_search.html.haml +++ b/app/views/layouts/_search.html.haml @@ -2,7 +2,7 @@ - group_data_attrs = { group_path: j(@group.path), name: @group.name, issues_path: issues_group_path(j(@group.path)), mr_path: merge_requests_group_path(j(@group.path)) } - if @project && @project.persisted? - project_data_attrs = { project_path: j(@project.path), name: j(@project.name), issues_path: project_issues_path(@project), mr_path: project_merge_requests_path(@project), issues_disabled: !@project.issues_enabled? } -.search.search-form +.search.search-form{ data: { track_label: "navbar_search", track_event: "activate_form_input" } } = form_tag search_path, method: :get, class: 'form-inline' do |f| .search-input-container .search-input-wrap diff --git a/app/views/layouts/devise.html.haml b/app/views/layouts/devise.html.haml index 43bd07679ba..6003d973c88 100644 --- a/app/views/layouts/devise.html.haml +++ b/app/views/layouts/devise.html.haml @@ -1,7 +1,7 @@ !!! 5 %html.devise-layout-html{ class: system_message_class } = render "layouts/head" - %body.ui-indigo.login-page.application.navless{ data: { page: body_data_page } } + %body.ui-indigo.login-page.application.navless.qa-login-page{ data: { page: body_data_page } } .page-wrap = render "layouts/header/empty" .login-page-broadcast @@ -9,7 +9,7 @@ .container.navless-container .content = render "layouts/flash" - .row + .row.append-bottom-15 .col-sm-7.brand-holder %h1 = brand_title diff --git a/app/views/layouts/header/_default.html.haml b/app/views/layouts/header/_default.html.haml index e8d0d809181..a9b85889846 100644 --- a/app/views/layouts/header/_default.html.haml +++ b/app/views/layouts/header/_default.html.haml @@ -60,7 +60,7 @@ .dropdown-menu.dropdown-menu-right = render 'layouts/header/help_dropdown' - if header_link?(:user_dropdown) - %li.nav-item.header-user.dropdown + %li.nav-item.header-user.dropdown{ data: { track_label: "profile_dropdown", track_event: "click_dropdown" } } = link_to current_user, class: user_dropdown_class, data: { toggle: "dropdown" } do = image_tag avatar_icon_for_user(current_user, 23), width: 23, height: 23, class: "header-user-avatar qa-user-avatar" = sprite_icon('angle-down', css_class: 'caret-down') diff --git a/app/views/layouts/header/_new_dropdown.haml b/app/views/layouts/header/_new_dropdown.haml index 5cb8aebadb3..e42251f9ec8 100644 --- a/app/views/layouts/header/_new_dropdown.haml +++ b/app/views/layouts/header/_new_dropdown.haml @@ -1,4 +1,4 @@ -%li.header-new.dropdown +%li.header-new.dropdown{ data: { track_label: "new_dropdown", track_event: "click_dropdown" } } = link_to new_project_path, class: "header-new-dropdown-toggle has-tooltip qa-new-menu-toggle", title: _("New..."), ref: 'tooltip', aria: { label: _("New...") }, data: { toggle: 'dropdown', placement: 'bottom', container: 'body', display: 'static' } do = sprite_icon('plus-square', size: 16) = sprite_icon('angle-down', css_class: 'caret-down') diff --git a/app/views/layouts/nav/_dashboard.html.haml b/app/views/layouts/nav/_dashboard.html.haml index 7057a5a142f..ddd30efe062 100644 --- a/app/views/layouts/nav/_dashboard.html.haml +++ b/app/views/layouts/nav/_dashboard.html.haml @@ -2,7 +2,7 @@ -# https://gitlab.com/gitlab-org/gitlab-ce/issues/49713 for more information. %ul.list-unstyled.navbar-sub-nav - if dashboard_nav_link?(:projects) - = nav_link(path: ['root#index', 'projects#trending', 'projects#starred', 'dashboard/projects#index'], html_options: { id: 'nav-projects-dropdown', class: "home dropdown header-projects qa-projects-dropdown" }) do + = nav_link(path: ['root#index', 'projects#trending', 'projects#starred', 'dashboard/projects#index'], html_options: { id: 'nav-projects-dropdown', class: "home dropdown header-projects qa-projects-dropdown", data: { track_label: "projects_dropdown", track_event: "click_dropdown" } }) do %button{ type: 'button', data: { toggle: "dropdown" } } = _('Projects') = sprite_icon('angle-down', css_class: 'caret-down') @@ -10,7 +10,7 @@ = render "layouts/nav/projects_dropdown/show" - if dashboard_nav_link?(:groups) - = nav_link(controller: ['dashboard/groups', 'explore/groups'], html_options: { id: 'nav-groups-dropdown', class: "home dropdown header-groups qa-groups-dropdown" }) do + = nav_link(controller: ['dashboard/groups', 'explore/groups'], html_options: { id: 'nav-groups-dropdown', class: "home dropdown header-groups qa-groups-dropdown", data: { track_label: "groups_dropdown", track_event: "click_dropdown" } }) do %button{ type: 'button', data: { toggle: "dropdown" } } = _('Groups') = sprite_icon('angle-down', css_class: 'caret-down') diff --git a/app/views/layouts/nav/sidebar/_group.html.haml b/app/views/layouts/nav/sidebar/_group.html.haml index 477030a20c1..bf475c07711 100644 --- a/app/views/layouts/nav/sidebar/_group.html.haml +++ b/app/views/layouts/nav/sidebar/_group.html.haml @@ -103,19 +103,6 @@ = _('Merge Requests') %span.badge.badge-pill.count.merge_counter.js-merge-counter.fly-out-badge= number_with_delimiter(merge_requests_count) - - if group_sidebar_link?(:group_members) - = nav_link(path: 'group_members#index') do - = link_to group_group_members_path(@group) do - .nav-icon-container - = sprite_icon('users') - %span.nav-item-name.qa-group-members-item - = _('Members') - %ul.sidebar-sub-level-items.is-fly-out-only - = nav_link(path: 'group_members#index', html_options: { class: "fly-out-top-item" } ) do - = link_to group_group_members_path(@group) do - %strong.fly-out-top-item-name - = _('Members') - - if group_sidebar_link?(:kubernetes) = nav_link(controller: [:clusters]) do = link_to group_clusters_path(@group) do @@ -129,6 +116,19 @@ %strong.fly-out-top-item-name = _('Kubernetes') + - if group_sidebar_link?(:group_members) + = nav_link(path: 'group_members#index') do + = link_to group_group_members_path(@group) do + .nav-icon-container + = sprite_icon('users') + %span.nav-item-name.qa-group-members-item + = _('Members') + %ul.sidebar-sub-level-items.is-fly-out-only + = nav_link(path: 'group_members#index', html_options: { class: "fly-out-top-item" } ) do + = link_to group_group_members_path(@group) do + %strong.fly-out-top-item-name + = _('Members') + - if group_sidebar_link?(:settings) = nav_link(path: group_nav_link_paths) do = link_to edit_group_path(@group) do diff --git a/app/views/layouts/nav/sidebar/_project.html.haml b/app/views/layouts/nav/sidebar/_project.html.haml index bdd0108db0d..d8017742c90 100644 --- a/app/views/layouts/nav/sidebar/_project.html.haml +++ b/app/views/layouts/nav/sidebar/_project.html.haml @@ -29,6 +29,11 @@ = link_to activity_project_path(@project), title: _('Activity'), class: 'shortcuts-project-activity' do %span= _('Activity') + - if project_nav_tab?(:releases) && Feature.enabled?(:releases_page, @project) + = nav_link(controller: :releases) do + = link_to project_releases_path(@project), title: _('Releases'), class: 'shortcuts-project-releases' do + %span= _('Releases') + = render_if_exists 'projects/sidebar/security_dashboard' - if can?(current_user, :read_cycle_analytics, @project) @@ -62,7 +67,7 @@ = link_to project_branches_path(@project) do = _('Branches') - = nav_link(controller: [:tags, :releases]) do + = nav_link(controller: [:tags]) do = link_to project_tags_path(@project) do = _('Tags') diff --git a/app/views/notify/_note_email.html.haml b/app/views/notify/_note_email.html.haml index 1fbae2f64ed..83c7f548975 100644 --- a/app/views/notify/_note_email.html.haml +++ b/app/views/notify/_note_email.html.haml @@ -4,17 +4,13 @@ - note_style = local_assigns.fetch(:note_style, "") - discussion = note.discussion if note.part_of_discussion? -- diff_discussion = discussion&.diff_discussion? -- on_image = discussion.on_image? if diff_discussion - if discussion - - phrase_end_char = on_image ? "." : ":" - %p{ style: "color: #777777;" } - = succeed phrase_end_char do + = succeed ':' do = link_to note.author_name, user_url(note.author) - - if diff_discussion + - if discussion&.diff_discussion? - if discussion.new_discussion? started a new discussion - else @@ -31,7 +27,7 @@ %p.details #{link_to note.author_name, user_url(note.author)} commented: -- if diff_discussion && !on_image +- if discussion&.diff_discussion? && discussion.on_text? = content_for :head do = stylesheet_link_tag 'mailers/highlighted_diff_email' diff --git a/app/views/notify/_note_email.text.erb b/app/views/notify/_note_email.text.erb index 4bf252b6ce1..50209c46ed1 100644 --- a/app/views/notify/_note_email.text.erb +++ b/app/views/notify/_note_email.text.erb @@ -20,7 +20,7 @@ <% end -%> -<% if discussion&.diff_discussion? -%> +<% if discussion&.diff_discussion? && discussion.on_text? -%> <% discussion.truncated_diff_lines(highlight: false, diff_limit: diff_limit).each do |line| -%> <%= "> #{line.text}\n" -%> <% end -%> diff --git a/app/views/notify/changed_milestone_issue_email.html.haml b/app/views/notify/changed_milestone_email.html.haml index 7d5425fc72d..01d27cac36b 100644 --- a/app/views/notify/changed_milestone_issue_email.html.haml +++ b/app/views/notify/changed_milestone_email.html.haml @@ -1,3 +1,5 @@ %p Milestone changed to %strong= link_to(@milestone.name, @milestone_url) + - if date_range = milestone_date_range(@milestone) + = "(#{date_range})" diff --git a/app/views/notify/changed_milestone_email.text.erb b/app/views/notify/changed_milestone_email.text.erb new file mode 100644 index 00000000000..a466da4eb19 --- /dev/null +++ b/app/views/notify/changed_milestone_email.text.erb @@ -0,0 +1 @@ +Milestone changed to <%= @milestone.name %><% if date_range = milestone_date_range(@milestone) %> (<%= date_range %>)<% end %> ( <%= @milestone_url %> ) diff --git a/app/views/notify/changed_milestone_issue_email.text.erb b/app/views/notify/changed_milestone_issue_email.text.erb deleted file mode 100644 index c5fc0b61518..00000000000 --- a/app/views/notify/changed_milestone_issue_email.text.erb +++ /dev/null @@ -1 +0,0 @@ -Milestone changed to <%= @milestone.name %> ( <%= @milestone_url %> ) diff --git a/app/views/notify/changed_milestone_merge_request_email.html.haml b/app/views/notify/changed_milestone_merge_request_email.html.haml deleted file mode 100644 index 7d5425fc72d..00000000000 --- a/app/views/notify/changed_milestone_merge_request_email.html.haml +++ /dev/null @@ -1,3 +0,0 @@ -%p - Milestone changed to - %strong= link_to(@milestone.name, @milestone_url) diff --git a/app/views/notify/changed_milestone_merge_request_email.text.erb b/app/views/notify/changed_milestone_merge_request_email.text.erb deleted file mode 100644 index c5fc0b61518..00000000000 --- a/app/views/notify/changed_milestone_merge_request_email.text.erb +++ /dev/null @@ -1 +0,0 @@ -Milestone changed to <%= @milestone.name %> ( <%= @milestone_url %> ) diff --git a/app/views/profiles/show.html.haml b/app/views/profiles/show.html.haml index 2603c558c0f..2629b374e7c 100644 --- a/app/views/profiles/show.html.haml +++ b/app/views/profiles/show.html.haml @@ -71,43 +71,43 @@ %h4.prepend-top-0 = s_("Profiles|Main settings") %p - = s_("Profiles|This information will appear on your profile.") + = s_("Profiles|This information will appear on your profile") - if current_user.ldap_user? = s_("Profiles|Some options are unavailable for LDAP accounts") .col-lg-8 .row - if @user.read_only_attribute?(:name) = f.text_field :name, required: true, readonly: true, wrapper: { class: 'col-md-9' }, - help: s_("Profiles|Your name was automatically set based on your %{provider_label} account, so people you know can recognize you.") % { provider_label: attribute_provider_label(:name) } + help: s_("Profiles|Your name was automatically set based on your %{provider_label} account, so people you know can recognize you") % { provider_label: attribute_provider_label(:name) } - else - = f.text_field :name, label: 'Full name', required: true, wrapper: { class: 'col-md-9' }, help: "Enter your name, so people you know can recognize you." + = f.text_field :name, label: 'Full name', required: true, wrapper: { class: 'col-md-9' }, help: s_("Profiles|Enter your name, so people you know can recognize you") = f.text_field :id, readonly: true, label: 'User ID', wrapper: { class: 'col-md-3' } - if @user.read_only_attribute?(:email) - = f.text_field :email, required: true, readonly: true, help: s_("Profiles|Your email address was automatically set based on your %{provider_label} account.") % { provider_label: attribute_provider_label(:email) } + = f.text_field :email, required: true, class: 'input-lg', readonly: true, help: s_("Profiles|Your email address was automatically set based on your %{provider_label} account") % { provider_label: attribute_provider_label(:email) } - else - = f.text_field :email, required: true, value: (@user.email unless @user.temp_oauth_email?), + = f.text_field :email, required: true, class: 'input-lg', value: (@user.email unless @user.temp_oauth_email?), help: user_email_help_text(@user) = f.select :public_email, options_for_select(@user.all_emails, selected: @user.public_email), - { help: s_("Profiles|This email will be displayed on your public profile."), include_blank: s_("Profiles|Do not show on profile") }, - control_class: 'select2' + { help: s_("Profiles|This email will be displayed on your public profile"), include_blank: s_("Profiles|Do not show on profile") }, + control_class: 'select2 input-lg' - commit_email_docs_link = link_to s_('Profiles|Learn more'), help_page_path('user/profile/index', anchor: 'commit-email', target: '_blank') = f.select :commit_email, options_for_select(commit_email_select_options(@user), selected: selected_commit_email(@user)), { help: s_("Profiles|This email will be used for web based operations, such as edits and merges. %{learn_more}").html_safe % { learn_more: commit_email_docs_link } }, - control_class: 'select2' + control_class: 'select2 input-lg' = f.select :preferred_language, Gitlab::I18n::AVAILABLE_LANGUAGES.map { |value, label| [label, value] }, - { help: s_("Profiles|This feature is experimental and translations are not complete yet.") }, - control_class: 'select2' - = f.text_field :skype - = f.text_field :linkedin - = f.text_field :twitter - = f.text_field :website_url, label: s_("Profiles|Website") + { help: s_("Profiles|This feature is experimental and translations are not complete yet") }, + control_class: 'select2 input-lg' + = f.text_field :skype, class: 'input-md', placeholder: s_("Profiles|username") + = f.text_field :linkedin, class: 'input-md', help: s_("Profiles|Your LinkedIn profile name from linkedin.com/in/profilename") + = f.text_field :twitter, class: 'input-md', placeholder: s_("Profiles|@username") + = f.text_field :website_url, class: 'input-lg', placeholder: s_("Profiles|website.com") - if @user.read_only_attribute?(:location) - = f.text_field :location, readonly: true, help: s_("Profiles|Your location was automatically set based on your %{provider_label} account.") % { provider_label: attribute_provider_label(:location) } + = f.text_field :location, readonly: true, help: s_("Profiles|Your location was automatically set based on your %{provider_label} account") % { provider_label: attribute_provider_label(:location) } - else - = f.text_field :location - = f.text_field :organization - = f.text_area :bio, rows: 4, maxlength: 250, help: s_("Profiles|Tell us about yourself in fewer than 250 characters.") + = f.text_field :location, class: 'input-lg', placeholder: s_("Profiles|City, country") + = f.text_field :organization, class: 'input-md', help: s_("Profiles|Who you represent or work for") + = f.text_area :bio, rows: 4, maxlength: 250, help: s_("Profiles|Tell us about yourself in fewer than 250 characters") %hr %h5= ("Private profile") .checkbox-icon-inline-wrapper @@ -118,7 +118,7 @@ %h5= s_("Profiles|Private contributions") = f.check_box :include_private_contributions, label: 'Include private contributions on my profile' .help-block - = s_("Profiles|Choose to show contributions of private projects on your public profile without any project, repository or organization information.") + = s_("Profiles|Choose to show contributions of private projects on your public profile without any project, repository or organization information") .prepend-top-default.append-bottom-default = f.submit s_("Profiles|Update profile settings"), class: 'btn btn-success' = link_to _("Cancel"), user_path(current_user), class: 'btn btn-cancel' diff --git a/app/views/profiles/two_factor_auths/show.html.haml b/app/views/profiles/two_factor_auths/show.html.haml index 94ec0cc5db8..d986c566928 100644 --- a/app/views/profiles/two_factor_auths/show.html.haml +++ b/app/views/profiles/two_factor_auths/show.html.haml @@ -25,7 +25,8 @@ - else %p - Download the Google Authenticator application from App Store or Google Play Store and scan this code. + Install a soft token authenticator like <a href="https://freeotp.github.io/">FreeOTP</a> + or Google Authenticator from your application repository and scan this QR code. More information is available in the #{link_to('documentation', help_page_path('user/profile/account/two_factor_authentication'))}. .row.append-bottom-10 .col-md-4 diff --git a/app/views/projects/_home_panel.html.haml b/app/views/projects/_home_panel.html.haml index e191b009db2..82b2ab64a5d 100644 --- a/app/views/projects/_home_panel.html.haml +++ b/app/views/projects/_home_panel.html.haml @@ -2,7 +2,7 @@ - show_auto_devops_callout = show_auto_devops_callout?(@project) .project-home-panel{ class: ("empty-project" if empty_repo) } .project-header.row.append-bottom-8 - .project-title-row.col-md-12.col-lg-7.d-flex + .project-title-row.col-md-12.col-lg-6.d-flex .avatar-container.project-avatar.float-none = project_icon(@project, alt: @project.name, class: 'avatar avatar-tile s64', width: 64, height: 64) .d-flex.flex-column.flex-wrap.align-items-baseline @@ -25,7 +25,7 @@ - if @project.has_extra_tags? = _("+ %{count} more") % { count: @project.count_of_extra_tags_not_shown } - .project-repo-buttons.col-md-12.col-lg-5.d-inline-flex.flex-wrap.justify-content-lg-end + .project-repo-buttons.col-md-12.col-lg-6.d-inline-flex.flex-wrap.justify-content-lg-end - if current_user .d-inline-flex = render 'projects/buttons/notifications', notification_setting: @notification_setting, btn_class: 'btn-xs' diff --git a/app/views/projects/buttons/_clone.html.haml b/app/views/projects/buttons/_clone.html.haml index d453a3a9dac..159d9e44e17 100644 --- a/app/views/projects/buttons/_clone.html.haml +++ b/app/views/projects/buttons/_clone.html.haml @@ -1,16 +1,12 @@ - project = project || @project .git-clone-holder.js-git-clone-holder.input-group - - if allowed_protocols_present? - .input-group-text.clone-dropdown-btn.btn - %span.js-clone-dropdown-label - = enabled_project_button(project, enabled_protocol) - - else - %a#clone-dropdown.input-group-text.btn.btn-primary.btn-xs.clone-dropdown-btn.qa-clone-dropdown{ href: '#', data: { toggle: 'dropdown' } } - %span.append-right-4.js-clone-dropdown-label - = _('Clone') - = sprite_icon("arrow-down", css_class: "icon") - %form.p-3.dropdown-menu.dropdown-menu-right.dropdown-menu-large.dropdown-menu-selectable.clone-options-dropdown.qa-clone-options + %a#clone-dropdown.input-group-text.btn.btn-primary.btn-xs.clone-dropdown-btn.qa-clone-dropdown{ href: '#', data: { toggle: 'dropdown' } } + %span.append-right-4.js-clone-dropdown-label + = _('Clone') + = sprite_icon("arrow-down", css_class: "icon") + %ul.p-3.dropdown-menu.dropdown-menu-right.dropdown-menu-large.dropdown-menu-selectable.clone-options-dropdown.qa-clone-options + - if ssh_enabled? %li.pb-2 %label.label-bold = _('Clone with SSH') @@ -19,6 +15,7 @@ .input-group-append = clipboard_button(target: '#ssh_project_clone', title: _("Copy URL to clipboard"), class: "input-group-text btn-default btn-clipboard") = render_if_exists 'projects/buttons/geo' + - if http_enabled? %li %label.label-bold = _('Clone with %{http_label}') % { http_label: gitlab_config.protocol.upcase } diff --git a/app/views/projects/cleanup/_show.html.haml b/app/views/projects/cleanup/_show.html.haml index 778d27fc61d..cecc139b183 100644 --- a/app/views/projects/cleanup/_show.html.haml +++ b/app/views/projects/cleanup/_show.html.haml @@ -1,5 +1,3 @@ -- return unless Feature.enabled?(:project_cleanup, @project) - - expanded = Rails.env.test? %section.settings.no-animate#cleanup{ class: ('expanded' if expanded) } diff --git a/app/views/projects/diffs/_render_error.html.haml b/app/views/projects/diffs/_render_error.html.haml index c3dc47a56a7..7dbd9897e83 100644 --- a/app/views/projects/diffs/_render_error.html.haml +++ b/app/views/projects/diffs/_render_error.html.haml @@ -1,6 +1,2 @@ .nothing-here-block - = _("This %{viewer} could not be displayed because %{reason}.") % { viewer: viewer.switcher_title, reason: diff_render_error_reason(viewer) } - - You can - = diff_render_error_options(viewer).to_sentence(two_words_connector: ' or ', last_word_connector: ', or ').html_safe - instead. + = viewer.render_error_message.html_safe diff --git a/app/views/projects/environments/index.html.haml b/app/views/projects/environments/index.html.haml index 6c0ad34c486..d66de7ab698 100644 --- a/app/views/projects/environments/index.html.haml +++ b/app/views/projects/environments/index.html.haml @@ -1,6 +1,5 @@ - @no_container = true - page_title _("Environments") -- add_to_breadcrumbs(_("Pipelines"), project_pipelines_path(@project)) #environments-list-view{ data: { environments_data: environments_list_data, "can-create-deployment" => can?(current_user, :create_deployment, @project).to_s, diff --git a/app/views/projects/issues/show.html.haml b/app/views/projects/issues/show.html.haml index 5a960e764b0..f048fb91304 100644 --- a/app/views/projects/issues/show.html.haml +++ b/app/views/projects/issues/show.html.haml @@ -88,4 +88,4 @@ %section.issuable-discussion = render 'projects/issues/discussion' -= render 'shared/issuable/sidebar', issuable: @issue += render 'shared/issuable/sidebar', issuable_sidebar: @issuable_sidebar, assignees: @issue.assignees diff --git a/app/views/projects/merge_requests/conflicts.html.haml b/app/views/projects/merge_requests/conflicts.html.haml deleted file mode 100644 index a6e2565a485..00000000000 --- a/app/views/projects/merge_requests/conflicts.html.haml +++ /dev/null @@ -1,36 +0,0 @@ -- page_title "Merge Conflicts", "#{@merge_request.title} (#{@merge_request.to_reference}", "Merge Requests" -- content_for :page_specific_javascripts do - = page_specific_javascript_tag('lib/ace.js') -= render "projects/merge_requests/mr_title" - -.merge-request-details.issuable-details - = render "projects/merge_requests/mr_box" - -= render 'shared/issuable/sidebar', issuable: @merge_request - -#conflicts{ "v-cloak" => "true", data: { conflicts_path: conflicts_project_merge_request_path(@merge_request.project, @merge_request, format: :json), - resolve_conflicts_path: resolve_conflicts_project_merge_request_path(@merge_request.project, @merge_request) } } - .loading{ "v-if" => "isLoading" } - %i.fa.fa-spinner.fa-spin - - .nothing-here-block{ "v-if" => "hasError" } - {{conflictsData.errorMessage}} - - = render partial: "projects/merge_requests/conflicts/commit_stats" - - .files-wrapper{ "v-if" => "!isLoading && !hasError" } - .files - .diff-file.file-holder.conflict{ "v-for" => "file in conflictsData.files" } - .js-file-title.file-title - %i.fa.fa-fw{ ":class" => "file.iconClass" } - %strong {{file.filePath}} - = render partial: 'projects/merge_requests/conflicts/file_actions' - .diff-content.diff-wrap-lines - .diff-wrap-lines.code.file-content.js-syntax-highlight{ "v-show" => "!isParallel && file.resolveMode === 'interactive' && file.type === 'text'" } - = render partial: "projects/merge_requests/conflicts/components/inline_conflict_lines" - .diff-wrap-lines.code.file-content.js-syntax-highlight{ "v-show" => "isParallel && file.resolveMode === 'interactive' && file.type === 'text'" } - %parallel-conflict-lines{ ":file" => "file" } - %div{ "v-show" => "file.resolveMode === 'edit' || file.type === 'text-editor'" } - = render partial: "projects/merge_requests/conflicts/components/diff_file_editor" - - = render partial: "projects/merge_requests/conflicts/submit_form" diff --git a/app/views/projects/merge_requests/conflicts/show.html.haml b/app/views/projects/merge_requests/conflicts/show.html.haml index a6e2565a485..09aeb81671a 100644 --- a/app/views/projects/merge_requests/conflicts/show.html.haml +++ b/app/views/projects/merge_requests/conflicts/show.html.haml @@ -6,7 +6,7 @@ .merge-request-details.issuable-details = render "projects/merge_requests/mr_box" -= render 'shared/issuable/sidebar', issuable: @merge_request += render 'shared/issuable/sidebar', issuable_sidebar: @issuable_sidebar, assignees: @merge_request.assignees #conflicts{ "v-cloak" => "true", data: { conflicts_path: conflicts_project_merge_request_path(@merge_request.project, @merge_request, format: :json), resolve_conflicts_path: resolve_conflicts_project_merge_request_path(@merge_request.project, @merge_request) } } diff --git a/app/views/projects/merge_requests/show.html.haml b/app/views/projects/merge_requests/show.html.haml index 4ebb029e48b..d6f340d0ee2 100644 --- a/app/views/projects/merge_requests/show.html.haml +++ b/app/views/projects/merge_requests/show.html.haml @@ -5,6 +5,7 @@ - page_title "#{@merge_request.title} (#{@merge_request.to_reference})", "Merge Requests" - page_description @merge_request.description - page_card_attributes @merge_request.card_attributes +- suggest_changes_help_path = help_page_path('user/discussions/index.md', anchor: 'suggest-changes') .merge-request{ data: { mr_action: j(params[:tab].presence || 'show'), url: merge_request_path(@merge_request, format: :json), project_path: project_path(@merge_request.project) } } = render "projects/merge_requests/mr_title" @@ -67,6 +68,7 @@ noteable_data: serialize_issuable(@merge_request), noteable_type: 'MergeRequest', target_type: 'merge_request', + help_page_path: suggest_changes_help_path, current_user_data: UserSerializer.new(project: @project).represent(current_user, {}, MergeRequestUserEntity).to_json} } #commits.commits.tab-pane @@ -76,13 +78,16 @@ = render 'projects/commit/pipelines_list', disable_initialization: true, endpoint: pipelines_project_merge_request_path(@project, @merge_request) #js-diffs-app.diffs.tab-pane{ data: { "is-locked" => @merge_request.discussion_locked?, endpoint: diffs_project_merge_request_path(@project, @merge_request, 'json', request.query_parameters), + help_page_path: suggest_changes_help_path, current_user_data: UserSerializer.new(project: @project).represent(current_user, {}, MergeRequestUserEntity).to_json, - project_path: project_path(@merge_request.project)} } + project_path: project_path(@merge_request.project), + changes_empty_state_illustration: image_path('illustrations/merge_request_changes_empty.svg') } } .mr-loading-status = spinner -= render 'shared/issuable/sidebar', issuable: @merge_request += render 'shared/issuable/sidebar', issuable_sidebar: @issuable_sidebar, assignees: @merge_request.assignees + - if @merge_request.can_be_reverted?(current_user) = render "projects/commit/change", type: 'revert', commit: @merge_request.merge_commit, title: @merge_request.title - if @merge_request.can_be_cherry_picked? diff --git a/app/views/projects/releases/index.html.haml b/app/views/projects/releases/index.html.haml new file mode 100644 index 00000000000..f01d4e826b9 --- /dev/null +++ b/app/views/projects/releases/index.html.haml @@ -0,0 +1,5 @@ +- @no_container = true +- page_title _('Releases') + +%div{ class: container_class } + #js-releases-page{ data: { project_id: @project.id, illustration_path: image_path('illustrations/releases.svg'), documentation_path: help_page_path('user/project/releases') } } diff --git a/app/views/projects/services/prometheus/_metrics.html.haml b/app/views/projects/services/prometheus/_metrics.html.haml index 597c029f755..a1d74b91002 100644 --- a/app/views/projects/services/prometheus/_metrics.html.haml +++ b/app/views/projects/services/prometheus/_metrics.html.haml @@ -1,6 +1,6 @@ - project = local_assigns.fetch(:project) -.card.js-panel-monitored-metrics{ data: { active_metrics: active_common_project_prometheus_metrics_path(project, :json), metrics_help_path: help_page_path('user/project/integrations/prometheus_library/metrics') } } +.card.js-panel-monitored-metrics{ data: { active_metrics: active_common_project_prometheus_metrics_path(project, :json), metrics_help_path: help_page_path('user/project/integrations/prometheus_library/index') } } .card-header = s_('PrometheusService|Common metrics') %span.badge.badge-pill.js-monitored-count 0 diff --git a/app/views/projects/services/prometheus/_show.html.haml b/app/views/projects/services/prometheus/_show.html.haml index 1d0b0265bb7..9d4574c4590 100644 --- a/app/views/projects/services/prometheus/_show.html.haml +++ b/app/views/projects/services/prometheus/_show.html.haml @@ -4,7 +4,7 @@ = s_('PrometheusService|Metrics') %p = s_('PrometheusService|Common metrics are automatically monitored based on a library of metrics from popular exporters.') - = link_to s_('PrometheusService|More information'), help_page_path('user/project/integrations/prometheus_library/metrics'), target: '_blank', rel: "noopener noreferrer" + = link_to s_('PrometheusService|More information'), help_page_path('user/project/integrations/prometheus_library/index'), target: '_blank', rel: "noopener noreferrer" .col-lg-9 = render 'projects/services/prometheus/metrics', project: @project diff --git a/app/views/projects/settings/ci_cd/show.html.haml b/app/views/projects/settings/ci_cd/show.html.haml index 98e2829ba43..6966bf96724 100644 --- a/app/views/projects/settings/ci_cd/show.html.haml +++ b/app/views/projects/settings/ci_cd/show.html.haml @@ -43,13 +43,7 @@ %section.qa-variables-settings.settings.no-animate{ class: ('expanded' if expanded) } .settings-header - %h4 - = _('Variables') - = link_to icon('question-circle'), help_page_path('ci/variables/README', anchor: 'variables'), target: '_blank', rel: 'noopener noreferrer' - %button.btn.js-settings-toggle{ type: 'button' } - = expanded ? _('Collapse') : _('Expand') - %p.append-bottom-0 - = render "ci/variables/content" + = render 'ci/variables/header', expanded: expanded .settings-content = render 'ci/variables/index', save_endpoint: project_variables_path(@project) diff --git a/app/views/projects/releases/edit.html.haml b/app/views/projects/tags/releases/edit.html.haml index 52c6c7ec424..52c6c7ec424 100644 --- a/app/views/projects/releases/edit.html.haml +++ b/app/views/projects/tags/releases/edit.html.haml diff --git a/app/views/shared/_milestones_sort_dropdown.html.haml b/app/views/shared/_milestones_sort_dropdown.html.haml index bd68a3e4c84..9a1db831ad3 100644 --- a/app/views/shared/_milestones_sort_dropdown.html.haml +++ b/app/views/shared/_milestones_sort_dropdown.html.haml @@ -8,15 +8,15 @@ = icon('chevron-down') %ul.dropdown-menu.dropdown-menu-right.dropdown-menu-sort %li - = link_to page_filter_path(sort: sort_value_due_date_soon, label: true) do + = link_to page_filter_path(sort: sort_value_due_date_soon) do = sort_title_due_date_soon - = link_to page_filter_path(sort: sort_value_due_date_later, label: true) do + = link_to page_filter_path(sort: sort_value_due_date_later) do = sort_title_due_date_later - = link_to page_filter_path(sort: sort_value_start_date_soon, label: true) do + = link_to page_filter_path(sort: sort_value_start_date_soon) do = sort_title_start_date_soon - = link_to page_filter_path(sort: sort_value_start_date_later, label: true) do + = link_to page_filter_path(sort: sort_value_start_date_later) do = sort_title_start_date_later - = link_to page_filter_path(sort: sort_value_name, label: true) do + = link_to page_filter_path(sort: sort_value_name) do = sort_title_name_asc - = link_to page_filter_path(sort: sort_value_name_desc, label: true) do + = link_to page_filter_path(sort: sort_value_name_desc) do = sort_title_name_desc diff --git a/app/views/shared/_mobile_clone_panel.html.haml b/app/views/shared/_mobile_clone_panel.html.haml index b43662947a8..6e2527bd1a1 100644 --- a/app/views/shared/_mobile_clone_panel.html.haml +++ b/app/views/shared/_mobile_clone_panel.html.haml @@ -7,7 +7,9 @@ %button.btn.btn-primary.dropdown-toggle.js-dropdown-toggle{ type: "button", data: { toggle: "dropdown" } } = sprite_icon("arrow-down", css_class: "dropdown-btn-icon icon") %ul.dropdown-menu.dropdown-menu-selectable.dropdown-menu-right.clone-options-dropdown{ data: { dropdown: true } } - %li - = dropdown_item_with_description(ssh_copy_label, project.ssh_url_to_repo, href: project.ssh_url_to_repo, data: { clone_type: 'ssh' }, default: true) - %li - = dropdown_item_with_description(http_copy_label, project.http_url_to_repo, href: project.http_url_to_repo, data: { clone_type: 'http' }) + - if ssh_enabled? + %li + = dropdown_item_with_description(ssh_copy_label, project.ssh_url_to_repo, href: project.ssh_url_to_repo, data: { clone_type: 'ssh' }, default: true) + - if http_enabled? + %li + = dropdown_item_with_description(http_copy_label, project.http_url_to_repo, href: project.http_url_to_repo, data: { clone_type: 'http' }) diff --git a/app/views/shared/issuable/_form.html.haml b/app/views/shared/issuable/_form.html.haml index 1618655182c..c6a391ae563 100644 --- a/app/views/shared/issuable/_form.html.haml +++ b/app/views/shared/issuable/_form.html.haml @@ -17,7 +17,7 @@ = render 'shared/issuable/form/template_selector', issuable: issuable = render 'shared/issuable/form/title', issuable: issuable, form: form, has_wip_commits: commits && commits.detect(&:work_in_progress?) -- if Feature.enabled?(:issue_suggestions) && Feature.enabled?(:graphql) +- if Gitlab::Graphql.enabled? #js-suggestions{ data: { project_path: @project.full_path } } = render 'shared/form_elements/description', model: issuable, form: form, project: project diff --git a/app/views/shared/issuable/_nav.html.haml b/app/views/shared/issuable/_nav.html.haml index 157637dbd11..71123740ee4 100644 --- a/app/views/shared/issuable/_nav.html.haml +++ b/app/views/shared/issuable/_nav.html.haml @@ -4,20 +4,20 @@ %ul.nav-links.issues-state-filters.mobile-separator.nav.nav-tabs %li{ class: active_when(params[:state] == 'opened') }> - = link_to page_filter_path(state: 'opened', label: true), id: 'state-opened', title: "Filter by #{page_context_word} that are currently opened.", data: { state: 'opened' } do + = link_to page_filter_path(state: 'opened'), id: 'state-opened', title: "Filter by #{page_context_word} that are currently opened.", data: { state: 'opened' } do #{issuables_state_counter_text(type, :opened, display_count)} - if type == :merge_requests %li{ class: active_when(params[:state] == 'merged') }> - = link_to page_filter_path(state: 'merged', label: true), id: 'state-merged', title: 'Filter by merge requests that are currently merged.', data: { state: 'merged' } do + = link_to page_filter_path(state: 'merged'), id: 'state-merged', title: 'Filter by merge requests that are currently merged.', data: { state: 'merged' } do #{issuables_state_counter_text(type, :merged, display_count)} %li{ class: active_when(params[:state] == 'closed') }> - = link_to page_filter_path(state: 'closed', label: true), id: 'state-closed', title: 'Filter by merge requests that are currently closed and unmerged.', data: { state: 'closed' } do + = link_to page_filter_path(state: 'closed'), id: 'state-closed', title: 'Filter by merge requests that are currently closed and unmerged.', data: { state: 'closed' } do #{issuables_state_counter_text(type, :closed, display_count)} - else %li{ class: active_when(params[:state] == 'closed') }> - = link_to page_filter_path(state: 'closed', label: true), id: 'state-closed', title: 'Filter by issues that are currently closed.', data: { state: 'closed' } do + = link_to page_filter_path(state: 'closed'), id: 'state-closed', title: 'Filter by issues that are currently closed.', data: { state: 'closed' } do #{issuables_state_counter_text(type, :closed, display_count)} = render 'shared/issuable/nav_links/all', page_context_word: page_context_word, counter: issuables_state_counter_text(type, :all, display_count) diff --git a/app/views/shared/issuable/_sidebar.html.haml b/app/views/shared/issuable/_sidebar.html.haml index 9eecfa39390..0520eda37a4 100644 --- a/app/views/shared/issuable/_sidebar.html.haml +++ b/app/views/shared/issuable/_sidebar.html.haml @@ -1,32 +1,37 @@ -- todo = issuable_todo(issuable) +-# `assignees` is being passed in for populating selected assignee values in the select box and rendering the assignee link + This should be removed when this sidebar is converted to Vue since assignee data is also available in the `issuable_sidebar` hash -%aside.right-sidebar.js-right-sidebar.js-issuable-sidebar{ data: { signed: { in: current_user.present? } }, class: sidebar_gutter_collapsed_class, 'aria-live' => 'polite' } - .issuable-sidebar{ data: { endpoint: "#{issuable_json_path(issuable)}" } } - - can_edit_issuable = can?(current_user, :"admin_#{issuable.to_ability_name}", @project) +- issuable_type = issuable_sidebar[:type] +- signed_in = !!issuable_sidebar.dig(:current_user, :id) +- can_edit_issuable = issuable_sidebar.dig(:current_user, :can_edit) + +%aside.right-sidebar.js-right-sidebar.js-issuable-sidebar{ data: { signed: { in: signed_in } }, class: sidebar_gutter_collapsed_class, 'aria-live' => 'polite' } + .issuable-sidebar .block.issuable-sidebar-header - - if current_user + - if signed_in %span.issuable-header-text.hide-collapsed.float-left = _('Todo') %a.gutter-toggle.float-right.js-sidebar-toggle.has-tooltip{ role: "button", href: "#", "aria-label" => "Toggle sidebar", title: sidebar_gutter_tooltip_text, data: { container: 'body', placement: 'left', boundary: 'viewport' } } = sidebar_gutter_toggle_icon - - if current_user - = render "shared/issuable/sidebar_todo", todo: todo, issuable: issuable + - if signed_in + = render "shared/issuable/sidebar_todo", issuable_sidebar: issuable_sidebar - = form_for [@project.namespace.becomes(Namespace), @project, issuable], remote: true, format: :json, html: { class: 'issuable-context-form inline-update js-issuable-update' } do |f| - - if current_user + = form_for issuable_type, url: issuable_sidebar[:issuable_json_path], remote: true, html: { class: 'issuable-context-form inline-update js-issuable-update' } do |f| + - if signed_in .block.todo.hide-expanded - = render "shared/issuable/sidebar_todo", todo: todo, issuable: issuable, is_collapsed: true + = render "shared/issuable/sidebar_todo", issuable_sidebar: issuable_sidebar, is_collapsed: true .block.assignee.qa-assignee-block - = render "shared/issuable/sidebar_assignees", issuable: issuable, can_edit_issuable: can_edit_issuable, signed_in: current_user.present? + = render "shared/issuable/sidebar_assignees", issuable_sidebar: issuable_sidebar, assignees: assignees - = render_if_exists 'shared/issuable/sidebar_item_epic', issuable: issuable + = render_if_exists 'shared/issuable/sidebar_item_epic', issuable_sidebar: issuable_sidebar + - milestone = issuable_sidebar[:milestone] || {} .block.milestone - .sidebar-collapsed-icon.has-tooltip{ title: milestone_tooltip_title(issuable.milestone), data: { container: 'body', html: 'true', placement: 'left', boundary: 'viewport' } } + .sidebar-collapsed-icon.has-tooltip{ title: sidebar_milestone_tooltip_label(milestone), data: { container: 'body', html: 'true', placement: 'left', boundary: 'viewport' } } = icon('clock-o', 'aria-hidden': 'true') %span.milestone-title.collapse-truncated-title - - if issuable.milestone - = issuable.milestone.title + - if milestone.present? + = milestone[:title] - else = _('None') .title.hide-collapsed @@ -35,49 +40,50 @@ - if can_edit_issuable = link_to _('Edit'), '#', class: 'js-sidebar-dropdown-toggle edit-link float-right' .value.hide-collapsed - - if issuable.milestone - = link_to issuable.milestone.title, milestone_path(issuable.milestone), class: "bold has-tooltip", title: milestone_tooltip_due_date(issuable.milestone), data: { container: "body", html: 'true', boundary: 'viewport' } + - if milestone.present? + = link_to milestone[:title], milestone[:web_url], class: "bold has-tooltip", title: sidebar_milestone_remaining_days(milestone), data: { container: "body", html: 'true', boundary: 'viewport' } - else %span.no-value = _('None') .selectbox.hide-collapsed - = f.hidden_field 'milestone_id', value: issuable.milestone_id, id: nil - = dropdown_tag('Milestone', options: { title: _('Assign milestone'), toggle_class: 'js-milestone-select js-extra-options', filter: true, dropdown_class: 'dropdown-menu-selectable', placeholder: _('Search milestones'), data: { show_no: true, field_name: "#{issuable.to_ability_name}[milestone_id]", project_id: @project.id, issuable_id: issuable.id, milestones: project_milestones_path(@project, :json), ability_name: issuable.to_ability_name, issue_update: issuable_json_path(issuable), use_id: true, default_no: true, selected: (issuable.milestone.name if issuable.milestone), null_default: true, display: 'static' }}) - - if issuable.has_attribute?(:time_estimate) - #issuable-time-tracker.block - // Fallback while content is loading - .title.hide-collapsed - = _('Time tracking') - = icon('spinner spin', 'aria-hidden': 'true') - - if issuable.has_attribute?(:due_date) + = f.hidden_field 'milestone_id', value: milestone[:id], id: nil + = dropdown_tag('Milestone', options: { title: _('Assign milestone'), toggle_class: 'js-milestone-select js-extra-options', filter: true, dropdown_class: 'dropdown-menu-selectable', placeholder: _('Search milestones'), data: { show_no: true, field_name: "#{issuable_type}[milestone_id]", project_id: issuable_sidebar[:project_id], issuable_id: issuable_sidebar[:id], milestones: issuable_sidebar[:project_milestones_path], ability_name: issuable_type, issue_update: issuable_sidebar[:issuable_json_path], use_id: true, default_no: true, selected: milestone[:title], null_default: true, display: 'static' }}) + + #issuable-time-tracker.block + // Fallback while content is loading + .title.hide-collapsed + = _('Time tracking') + = icon('spinner spin', 'aria-hidden': 'true') + + - if issuable_sidebar.has_key?(:due_date) .block.due_date - .sidebar-collapsed-icon.has-tooltip{ data: { placement: 'left', container: 'body', html: 'true', boundary: 'viewport' }, title: sidebar_due_date_tooltip_label(issuable) } + .sidebar-collapsed-icon.has-tooltip{ data: { placement: 'left', container: 'body', html: 'true', boundary: 'viewport' }, title: sidebar_due_date_tooltip_label(issuable_sidebar[:due_date]) } = icon('calendar', 'aria-hidden': 'true') %span.js-due-date-sidebar-value - = issuable.due_date.try(:to_s, :medium) || 'None' + = issuable_sidebar[:due_date].try(:to_s, :medium) || 'None' .title.hide-collapsed = _('Due date') = icon('spinner spin', class: 'hidden block-loading', 'aria-hidden': 'true') - - if can?(current_user, :"admin_#{issuable.to_ability_name}", @project) + - if can_edit_issuable = link_to _('Edit'), '#', class: 'js-sidebar-dropdown-toggle edit-link float-right' .value.hide-collapsed %span.value-content - - if issuable.due_date - %span.bold= issuable.due_date.to_s(:medium) + - if issuable_sidebar[:due_date] + %span.bold= issuable_sidebar[:due_date].to_s(:medium) - else %span.no-value = _('No due date') - - if can?(current_user, :"admin_#{issuable.to_ability_name}", @project) - %span.no-value.js-remove-due-date-holder{ class: ("hidden" if issuable.due_date.nil?) } + - if can_edit_issuable + %span.no-value.js-remove-due-date-holder{ class: ("hidden" if issuable_sidebar[:due_date].nil?) } \- %a.js-remove-due-date{ href: "#", role: "button" } = _('remove due date') - - if can?(current_user, :"admin_#{issuable.to_ability_name}", @project) + - if can_edit_issuable .selectbox.hide-collapsed - = f.hidden_field :due_date, value: issuable.due_date.try(:strftime, 'yy-mm-dd') + = f.hidden_field :due_date, value: issuable_sidebar[:due_date].try(:strftime, 'yy-mm-dd') .dropdown - %button.dropdown-menu-toggle.js-due-date-select{ type: 'button', data: { toggle: 'dropdown', field_name: "#{issuable.to_ability_name}[due_date]", ability_name: issuable.to_ability_name, issue_update: issuable_json_path(issuable), display: 'static' } } + %button.dropdown-menu-toggle.js-due-date-select{ type: 'button', data: { toggle: 'dropdown', field_name: "#{issuable_type}[due_date]", ability_name: issuable_type, issue_update: issuable_sidebar[:issuable_json_path], display: 'static' } } %span.dropdown-toggle-text = _('Due date') = icon('chevron-down', 'aria-hidden': 'true') @@ -86,56 +92,56 @@ = dropdown_content do .js-due-date-calendar - - if @labels - - selected_labels = issuable.labels - .block.labels - .sidebar-collapsed-icon.js-sidebar-labels-tooltip{ title: issuable_labels_tooltip(issuable.labels_array), data: { placement: "left", container: "body", boundary: 'viewport' } } - = icon('tags', 'aria-hidden': 'true') - %span - = selected_labels.size - .title.hide-collapsed - = _('Labels') - = icon('spinner spin', class: 'hidden block-loading', 'aria-hidden': 'true') - - if can_edit_issuable - = link_to _('Edit'), '#', class: 'js-sidebar-dropdown-toggle edit-link float-right' - .value.issuable-show-labels.dont-hide.hide-collapsed.qa-labels-block{ class: ("has-labels" if selected_labels.any?) } - - if selected_labels.any? - - selected_labels.each do |label| - = link_to_label(label, subject: issuable.project, type: issuable.to_ability_name) - - else - %span.no-value - = _('None') - .selectbox.hide-collapsed + - selected_labels = issuable_sidebar[:labels] + .block.labels + .sidebar-collapsed-icon.js-sidebar-labels-tooltip{ title: issuable_labels_tooltip(selected_labels), data: { placement: "left", container: "body", boundary: 'viewport' } } + = icon('tags', 'aria-hidden': 'true') + %span + = selected_labels.size + .title.hide-collapsed + = _('Labels') + = icon('spinner spin', class: 'hidden block-loading', 'aria-hidden': 'true') + - if can_edit_issuable + = link_to _('Edit'), '#', class: 'js-sidebar-dropdown-toggle edit-link float-right' + .value.issuable-show-labels.dont-hide.hide-collapsed.qa-labels-block{ class: ("has-labels" if selected_labels.any?) } + - if selected_labels.any? - selected_labels.each do |label| - = hidden_field_tag "#{issuable.to_ability_name}[label_names][]", label.id, id: nil - .dropdown - %button.dropdown-menu-toggle.js-label-select.js-multiselect.js-label-sidebar-dropdown{ type: "button", data: {toggle: "dropdown", default_label: "Labels", field_name: "#{issuable.to_ability_name}[label_names][]", ability_name: issuable.to_ability_name, show_no: "true", show_any: "true", namespace_path: @project.try(:namespace).try(:full_path), project_path: @project.try(:path), issue_update: issuable_json_path(issuable), labels: (labels_filter_path_with_defaults if @project), display: 'static' } } - %span.dropdown-toggle-text{ class: ("is-default" if selected_labels.empty?) } - = multi_label_name(selected_labels, "Labels") - = icon('chevron-down', 'aria-hidden': 'true') - .dropdown-menu.dropdown-select.dropdown-menu-paging.dropdown-menu-labels.dropdown-menu-selectable - = render partial: "shared/issuable/label_page_default" - - if can? current_user, :admin_label, @project and @project - = render partial: "shared/issuable/label_page_create" - - = render_if_exists 'shared/issuable/sidebar_weight', issuable: issuable - - - if issuable.has_attribute?(:confidential) + = link_to sidebar_label_filter_path(issuable_sidebar[:project_issuables_path], label[:title]) do + %span.badge.color-label.has-tooltip{ style: "background-color: #{label[:color]}; color: #{label[:text_color]}", title: label[:description], data: { container: "body" } } + = label[:title] + - else + %span.no-value + = _('None') + .selectbox.hide-collapsed + - selected_labels.each do |label| + = hidden_field_tag "#{issuable_type}[label_names][]", label[:id], id: nil + .dropdown + %button.dropdown-menu-toggle.js-label-select.js-multiselect.js-label-sidebar-dropdown{ type: "button", data: {toggle: "dropdown", default_label: "Labels", field_name: "#{issuable_type}[label_names][]", ability_name: issuable_type, show_no: "true", show_any: "true", namespace_path: issuable_sidebar[:namespace_path], project_path: issuable_sidebar[:project_path], issue_update: issuable_sidebar[:issuable_json_path], labels: issuable_sidebar[:project_labels_path], display: 'static' } } + %span.dropdown-toggle-text{ class: ("is-default" if selected_labels.empty?) } + = multi_label_name(selected_labels, "Labels") + = icon('chevron-down', 'aria-hidden': 'true') + .dropdown-menu.dropdown-select.dropdown-menu-paging.dropdown-menu-labels.dropdown-menu-selectable + = render partial: "shared/issuable/label_page_default" + - if issuable_sidebar.dig(:current_user, :can_admin_label) + = render partial: "shared/issuable/label_page_create" + + = render_if_exists 'shared/issuable/sidebar_weight', issuable_sidebar: issuable_sidebar + + - if issuable_sidebar.has_key?(:confidential) -# haml-lint:disable InlineJavaScript - %script#js-confidential-issue-data{ type: "application/json" }= { is_confidential: @issue.confidential, is_editable: can_edit_issuable }.to_json.html_safe + %script#js-confidential-issue-data{ type: "application/json" }= { is_confidential: issuable_sidebar[:confidential], is_editable: can_edit_issuable }.to_json.html_safe #js-confidential-entry-point - - if issuable.has_attribute?(:discussion_locked) - -# haml-lint:disable InlineJavaScript - %script#js-lock-issue-data{ type: "application/json" }= { is_locked: issuable.discussion_locked?, is_editable: can_edit_issuable }.to_json.html_safe - #js-lock-entry-point + -# haml-lint:disable InlineJavaScript + %script#js-lock-issue-data{ type: "application/json" }= { is_locked: issuable_sidebar[:discussion_locked], is_editable: can_edit_issuable }.to_json.html_safe + #js-lock-entry-point .js-sidebar-participants-entry-point - - if current_user + - if signed_in .js-sidebar-subscriptions-entry-point - - project_ref = cross_project_reference(@project, issuable) + - project_ref = issuable_sidebar[:reference] .block.project-reference .sidebar-collapsed-icon.dont-change-state = clipboard_button(text: project_ref, title: _('Copy reference to clipboard'), placement: "left", boundary: 'viewport') @@ -145,7 +151,8 @@ %cite{ title: project_ref } = project_ref = clipboard_button(text: project_ref, title: _('Copy reference to clipboard'), placement: "left", boundary: 'viewport') - - if current_user && issuable.can_move?(current_user) + + - if issuable_sidebar.dig(:current_user, :can_move) .block.js-sidebar-move-issue-block .sidebar-collapsed-icon{ data: { toggle: 'tooltip', placement: 'left', container: 'body', boundary: 'viewport' }, title: _('Move issue') } = custom_icon('icon_arrow_right') @@ -164,4 +171,4 @@ = icon('spinner spin', class: 'sidebar-move-issue-confirmation-loading-icon') -# haml-lint:disable InlineJavaScript - %script.js-sidebar-options{ type: "application/json" }= issuable_sidebar_options(issuable, can_edit_issuable).to_json.html_safe + %script.js-sidebar-options{ type: "application/json" }= issuable_sidebar_options(issuable_sidebar).to_json.html_safe diff --git a/app/views/shared/issuable/_sidebar_assignees.html.haml b/app/views/shared/issuable/_sidebar_assignees.html.haml index 8a13c7a3b83..c5cce1823f0 100644 --- a/app/views/shared/issuable/_sidebar_assignees.html.haml +++ b/app/views/shared/issuable/_sidebar_assignees.html.haml @@ -1,12 +1,17 @@ -- if issuable.is_a?(Issue) - #js-vue-sidebar-assignees{ data: { field: "#{issuable.to_ability_name}[assignee_ids]", signed_in: signed_in } } +- issuable_type = issuable_sidebar[:type] +- signed_in = !!issuable_sidebar.dig(:current_user, :id) +- can_edit_issuable = issuable_sidebar.dig(:current_user, :can_edit) + +- if issuable_type == "issue" + #js-vue-sidebar-assignees{ data: { field: "#{issuable_type}[assignee_ids]", signed_in: signed_in } } .title.hide-collapsed = _('Assignee') = icon('spinner spin') - else - .sidebar-collapsed-icon.sidebar-collapsed-user{ data: { toggle: "tooltip", placement: "left", container: "body", boundary: 'viewport' }, title: sidebar_assignee_tooltip_label(issuable) } - - if issuable.assignee - = link_to_member(@project, issuable.assignee, size: 24) + - assignee = assignees.first + .sidebar-collapsed-icon.sidebar-collapsed-user{ data: { toggle: "tooltip", placement: "left", container: "body", boundary: 'viewport' }, title: (issuable_sidebar.dig(:assignee, :name) || _('Assignee')) } + - if issuable_sidebar[:assignee] + = link_to_member(@project, assignee, size: 24) - else = icon('user', 'aria-hidden': 'true') .title.hide-collapsed @@ -18,13 +23,13 @@ %a.gutter-toggle.float-right.js-sidebar-toggle{ role: "button", href: "#", "aria-label" => _('Toggle sidebar') } = sidebar_gutter_toggle_icon .value.hide-collapsed - - if issuable.assignee - = link_to_member(@project, issuable.assignee, size: 32, extra_class: 'bold') do - - if !issuable.can_be_merged_by?(issuable.assignee) + - if issuable_sidebar[:assignee] + = link_to_member(@project, assignee, size: 32, extra_class: 'bold') do + - if issuable_sidebar[:assignee][:can_merge] %span.float-right.cannot-be-merged{ data: { toggle: 'tooltip', placement: 'left' }, title: _('Not allowed to merge') } = icon('exclamation-triangle', 'aria-hidden': 'true') %span.username - = issuable.assignee.to_reference + @#{issuable_sidebar[:assignee][:username]} - else %span.assign-yourself.no-value = _('No assignee') @@ -34,19 +39,33 @@ = _('assign yourself') .selectbox.hide-collapsed - - issuable.assignees.each do |assignee| - = hidden_field_tag "#{issuable.to_ability_name}[assignee_ids][]", assignee.id, id: nil, data: { avatar_url: assignee.avatar_url, name: assignee.name, username: assignee.username } + - if assignees.none? + = hidden_field_tag "#{issuable_type}[assignee_ids][]", 0, id: nil + - else + - assignees.each do |assignee| + = hidden_field_tag "#{issuable_type}[assignee_ids][]", assignee.id, id: nil, data: { avatar_url: assignee.avatar_url, name: assignee.name, username: assignee.username } - - options = { toggle_class: 'js-user-search js-author-search', title: _('Assign to'), filter: true, dropdown_class: 'dropdown-menu-user dropdown-menu-selectable dropdown-menu-author', placeholder: _('Search users'), data: { first_user: current_user&.username, current_user: true, project_id: @project&.id, author_id: issuable.author_id, field_name: "#{issuable.to_ability_name}[assignee_ids][]", issue_update: issuable_json_path(issuable), ability_name: issuable.to_ability_name, null_user: true, display: 'static' } } + - options = { toggle_class: 'js-user-search js-author-search', + title: _('Assign to'), + filter: true, + dropdown_class: 'dropdown-menu-user dropdown-menu-selectable dropdown-menu-author', + placeholder: _('Search users'), + data: { first_user: issuable_sidebar.dig(:current_user, :username), + current_user: true, + project_id: issuable_sidebar[:project_id], + author_id: issuable_sidebar[:author_id], + field_name: "#{issuable_type}[assignee_ids][]", + issue_update: issuable_sidebar[:issuable_json_path], + ability_name: issuable_type, + null_user: true, + display: 'static' } } - title = _('Select assignee') - - if issuable.is_a?(Issue) - - unless issuable.assignees.any? - = hidden_field_tag "#{issuable.to_ability_name}[assignee_ids][]", 0, id: nil + - if issuable_type == "issue" - dropdown_options = issue_assignees_dropdown_options - title = dropdown_options[:title] - options[:toggle_class] += ' js-multiselect js-save-user-data' - - data = { field_name: "#{issuable.to_ability_name}[assignee_ids][]" } + - data = { field_name: "#{issuable_type}[assignee_ids][]" } - data[:multi_select] = true - data['dropdown-title'] = title - data['dropdown-header'] = dropdown_options[:data][:'dropdown-header'] diff --git a/app/views/shared/issuable/_sidebar_todo.html.haml b/app/views/shared/issuable/_sidebar_todo.html.haml index 660ee6d5777..de4df016cfb 100644 --- a/app/views/shared/issuable/_sidebar_todo.html.haml +++ b/app/views/shared/issuable/_sidebar_todo.html.haml @@ -1,15 +1,15 @@ - is_collapsed = local_assigns.fetch(:is_collapsed, false) -- mark_content = is_collapsed ? sprite_icon('todo-done', css_class: 'todo-undone') : _('Mark todo as done') -- todo_content = is_collapsed ? sprite_icon('todo-add') : _('Add todo') +- has_todo = !!issuable_sidebar.dig(:current_user, :todo, :id) + +- todo_button_data = issuable_todo_button_data(issuable_sidebar, is_collapsed) +- button_title = has_todo ? todo_button_data[:mark_text] : todo_button_data[:todo_text] +- button_icon = has_todo ? todo_button_data[:mark_icon] : todo_button_data[:todo_icon] %button.issuable-todo-btn.js-issuable-todo{ type: 'button', class: (is_collapsed ? 'btn-blank sidebar-collapsed-icon dont-change-state has-tooltip' : 'btn btn-default issuable-header-btn float-right'), - title: (todo.nil? ? _('Add todo') : _('Mark todo as done')), - 'aria-label' => (todo.nil? ? _('Add todo') : _('Mark todo as done')), - data: issuable_todo_button_data(issuable, todo, is_collapsed) } + title: button_title, + 'aria-label' => button_title, + data: todo_button_data } %span.issuable-todo-inner.js-issuable-todo-inner< - - if todo - = mark_content - - else - = todo_content + = is_collapsed ? button_icon : button_title = icon('spin spinner', 'aria-hidden': 'true') diff --git a/app/views/shared/issuable/_sort_dropdown.html.haml b/app/views/shared/issuable/_sort_dropdown.html.haml index c211b9fcaa2..b6ea9185b10 100644 --- a/app/views/shared/issuable/_sort_dropdown.html.haml +++ b/app/views/shared/issuable/_sort_dropdown.html.haml @@ -10,11 +10,12 @@ = icon('chevron-down') %ul.dropdown-menu.dropdown-menu-right.dropdown-menu-selectable.dropdown-menu-sort %li - = sortable_item(sort_title_priority, page_filter_path(sort: sort_value_priority, label: true), sort_title) - = sortable_item(sort_title_created_date, page_filter_path(sort: sort_value_created_date, label: true), sort_title) - = sortable_item(sort_title_recently_updated, page_filter_path(sort: sort_value_recently_updated, label: true), sort_title) - = sortable_item(sort_title_milestone, page_filter_path(sort: sort_value_milestone, label: true), sort_title) - = sortable_item(sort_title_due_date, page_filter_path(sort: sort_value_due_date, label: true), sort_title) if viewing_issues - = sortable_item(sort_title_popularity, page_filter_path(sort: sort_value_popularity, label: true), sort_title) - = sortable_item(sort_title_label_priority, page_filter_path(sort: sort_value_label_priority, label: true), sort_title) + = sortable_item(sort_title_priority, page_filter_path(sort: sort_value_priority), sort_title) + = sortable_item(sort_title_created_date, page_filter_path(sort: sort_value_created_date), sort_title) + = sortable_item(sort_title_recently_updated, page_filter_path(sort: sort_value_recently_updated), sort_title) + = sortable_item(sort_title_milestone, page_filter_path(sort: sort_value_milestone), sort_title) + = sortable_item(sort_title_due_date, page_filter_path(sort: sort_value_due_date), sort_title) if viewing_issues + = sortable_item(sort_title_popularity, page_filter_path(sort: sort_value_popularity), sort_title) + = sortable_item(sort_title_label_priority, page_filter_path(sort: sort_value_label_priority), sort_title) + = render_if_exists('shared/ee/issuable/sort_dropdown', viewing_issues: viewing_issues, sort_title: sort_title) = issuable_sort_direction_button(sort_value) diff --git a/app/views/shared/issuable/form/_metadata.html.haml b/app/views/shared/issuable/form/_metadata.html.haml index ac8d58c0bfe..e370dff9526 100644 --- a/app/views/shared/issuable/form/_metadata.html.haml +++ b/app/views/shared/issuable/form/_metadata.html.haml @@ -19,10 +19,9 @@ .issuable-form-select-holder = render "shared/issuable/milestone_dropdown", selected: issuable.milestone, name: "#{issuable.class.model_name.param_key}[milestone_id]", show_any: false, show_upcoming: false, show_started: false, extra_class: "qa-issuable-milestone-dropdown js-issuable-form-dropdown js-dropdown-keep-input", dropdown_title: "Select milestone" .form-group.row - - has_labels = @labels && @labels.any? = form.label :label_ids, "Labels", class: "col-form-label #{has_due_date ? "col-md-2 col-lg-4" : "col-sm-2"}" = form.hidden_field :label_ids, multiple: true, value: '' - .col-sm-10{ class: "#{"col-md-8" if has_due_date} #{'issuable-form-padding-top' if !has_labels}" } + .col-sm-10{ class: "#{"col-md-8" if has_due_date}" } .issuable-form-select-holder = render "shared/issuable/label_dropdown", classes: ["js-issuable-form-dropdown"], selected: issuable.labels, data_options: { field_name: "#{issuable.class.model_name.param_key}[label_ids][]", show_any: false }, dropdown_title: "Select label" diff --git a/app/views/shared/issuable/nav_links/_all.html.haml b/app/views/shared/issuable/nav_links/_all.html.haml index d7ad7090a45..c92a50bcb70 100644 --- a/app/views/shared/issuable/nav_links/_all.html.haml +++ b/app/views/shared/issuable/nav_links/_all.html.haml @@ -2,5 +2,5 @@ - counter = local_assigns.fetch(:counter) %li{ class: active_when(params[:state] == 'all') }> - = link_to page_filter_path(state: 'all', label: true), id: 'state-all', title: "Show all #{page_context_word}.", data: { state: 'all' } do + = link_to page_filter_path(state: 'all'), id: 'state-all', title: "Show all #{page_context_word}.", data: { state: 'all' } do #{counter} diff --git a/app/views/shared/labels/_sort_dropdown.html.haml b/app/views/shared/labels/_sort_dropdown.html.haml index d664ef1cc2f..07e96eea062 100644 --- a/app/views/shared/labels/_sort_dropdown.html.haml +++ b/app/views/shared/labels/_sort_dropdown.html.haml @@ -6,4 +6,4 @@ %ul.dropdown-menu.dropdown-menu-right.dropdown-menu-sort %li - label_sort_options_hash.each do |value, title| - = sortable_item(title, page_filter_path(sort: value, label: true, subscribed: params[:subscribed]), sort_title) + = sortable_item(title, page_filter_path(sort: value), sort_title) diff --git a/app/views/shared/milestones/_milestone.html.haml b/app/views/shared/milestones/_milestone.html.haml index ed7fefba56d..40b8374848e 100644 --- a/app/views/shared/milestones/_milestone.html.haml +++ b/app/views/shared/milestones/_milestone.html.haml @@ -1,5 +1,5 @@ - dashboard = local_assigns[:dashboard] -- custom_dom_id = dom_id(milestone.try(:milestones) ? milestone.milestones.first : milestone) +- custom_dom_id = dom_id(milestone.try(:milestone) ? milestone.milestone : milestone) - milestone_type = milestone.group_milestone? ? 'Group Milestone' : 'Project Milestone' %li{ class: "milestone milestone-#{milestone.closed? ? 'closed' : 'open'}", id: custom_dom_id } @@ -21,10 +21,12 @@ = milestone.group.full_name - if milestone.legacy_group_milestone? .projects - - milestone.milestones.each do |milestone| - = link_to milestone_path(milestone) do - %span.label-badge.label-badge-blue.d-inline-block.append-bottom-5 - = dashboard ? milestone.project.full_name : milestone.project.name + - link_to milestone_path(milestone.milestone) do + %span.label-badge.label-badge-blue.d-inline-block.append-bottom-5 + = dashboard ? milestone.project.full_name : milestone.project.name + - if milestone.project + .label-badge.label-badge-gray.d-inline-block + = milestone.project.full_name .col-sm-4.milestone-progress = milestone_progress_bar(milestone) @@ -58,5 +60,5 @@ - else = link_to 'Close Milestone', group_milestone_route(milestone, {state_event: :close }), method: :put, class: "btn btn-sm btn-grouped btn-close" - if dashboard - .status-box.status-box-milestone + .label-badge.label-badge-gray = milestone_type diff --git a/app/views/shared/milestones/_sidebar.html.haml b/app/views/shared/milestones/_sidebar.html.haml index becd1c4884e..b24075c7849 100644 --- a/app/views/shared/milestones/_sidebar.html.haml +++ b/app/views/shared/milestones/_sidebar.html.haml @@ -65,7 +65,7 @@ %span.bold= milestone.due_date.to_s(:medium) - else %span.no-value No due date - - remaining_days = remaining_days_in_words(milestone) + - remaining_days = remaining_days_in_words(milestone.due_date, milestone.start_date) - if remaining_days.present? = surround '(', ')' do %span.remaining-days= remaining_days diff --git a/app/views/shared/milestones/_top.html.haml b/app/views/shared/milestones/_top.html.haml index 0499b04a482..55b1c14022f 100644 --- a/app/views/shared/milestones/_top.html.haml +++ b/app/views/shared/milestones/_top.html.haml @@ -62,20 +62,19 @@ %th Open issues %th State %th Due date - - milestone.milestones.each do |ms| %tr %td - - project_name = group ? ms.project.name : ms.project.full_name - = link_to project_name, project_milestone_path(ms.project, ms) + - project_name = group ? milestone.project.name : milestone.project.full_name + = link_to project_name, milestone_path(milestone.milestone) %td - = ms.issues_visible_to_user(current_user).opened.count + = milestone.milestone.issues_visible_to_user(current_user).opened.count %td - - if ms.closed? + - if milestone.closed? Closed - else Open %td - = ms.expires_at + = milestone.expires_at - elsif milestone.group_milestone? %br View diff --git a/app/views/shared/projects/_list.html.haml b/app/views/shared/projects/_list.html.haml index 06eb3d03e31..15c29e14cc0 100644 --- a/app/views/shared/projects/_list.html.haml +++ b/app/views/shared/projects/_list.html.haml @@ -2,24 +2,29 @@ - avatar = true unless local_assigns[:avatar] == false - use_creator_avatar = false unless local_assigns[:use_creator_avatar] == true - stars = true unless local_assigns[:stars] == false -- forks = false unless local_assigns[:forks] == true +- forks = true unless local_assigns[:forks] == false +- merge_requests = true unless local_assigns[:merge_requests] == false +- issues = true unless local_assigns[:issues] == false +- pipeline_status = true unless local_assigns[:pipeline_status] == false - ci = false unless local_assigns[:ci] == true - skip_namespace = false unless local_assigns[:skip_namespace] == true - user = local_assigns[:user] - show_last_commit_as_description = false unless local_assigns[:show_last_commit_as_description] == true - remote = false unless local_assigns[:remote] == true - skip_pagination = false unless local_assigns[:skip_pagination] == true +- compact_mode = false unless local_assigns[:compact_mode] == true +- css_classes = "#{'compact' if compact_mode} #{'explore' if explore_projects_tab?}" .js-projects-list-holder - if any_projects?(projects) - load_pipeline_status(projects) - - %ul.projects-list + %ul.projects-list{ class: css_classes } - projects.each_with_index do |project, i| - css_class = (i >= projects_limit) || project.pending_delete? ? 'hide' : nil = render "shared/projects/project", project: project, skip_namespace: skip_namespace, avatar: avatar, stars: stars, css_class: css_class, ci: ci, use_creator_avatar: use_creator_avatar, - forks: forks, show_last_commit_as_description: show_last_commit_as_description, user: user + forks: forks, show_last_commit_as_description: show_last_commit_as_description, user: user, merge_requests: merge_requests, + issues: issues, pipeline_status: pipeline_status, compact_mode: compact_mode - if @private_forks_count && @private_forks_count > 0 %li.project-row.private-forks-notice diff --git a/app/views/shared/projects/_project.html.haml b/app/views/shared/projects/_project.html.haml index aba790e1217..fea7e17be3d 100644 --- a/app/views/shared/projects/_project.html.haml +++ b/app/views/shared/projects/_project.html.haml @@ -1,62 +1,107 @@ - avatar = true unless local_assigns[:avatar] == false - stars = true unless local_assigns[:stars] == false -- forks = false unless local_assigns[:forks] == true +- forks = true unless local_assigns[:forks] == false +- merge_requests = true unless local_assigns[:merge_requests] == false +- issues = true unless local_assigns[:issues] == false +- pipeline_status = true unless local_assigns[:pipeline_status] == false - skip_namespace = false unless local_assigns[:skip_namespace] == true - access = max_project_member_access(project) -- css_class = '' unless local_assigns[:css_class] +- compact_mode = false unless local_assigns[:compact_mode] == true - show_last_commit_as_description = false unless local_assigns[:show_last_commit_as_description] == true && can_show_last_commit_in_list?(project) +- css_class = '' unless local_assigns[:css_class] - css_class += " no-description" if project.description.blank? && !show_last_commit_as_description - cache_key = project_list_cache_key(project) - updated_tooltip = time_ago_with_tooltip(project.last_activity_date) +- css_details_class = compact_mode ? "d-flex flex-column flex-sm-row flex-md-row align-items-sm-center" : "align-items-center flex-md-fill flex-lg-column d-sm-flex d-lg-block" +- css_controls_class = compact_mode ? "" : "align-items-md-end align-items-lg-center flex-lg-row" -%li.project-row{ class: css_class } +%li.project-row.d-flex{ class: css_class } = cache(cache_key) do - if avatar - .avatar-container.s40 + .avatar-container.s64.flex-grow-0.flex-shrink-0 = link_to project_path(project), class: dom_class(project) do - if project.creator && use_creator_avatar - = image_tag avatar_icon_for_user(project.creator, 40), class: "avatar s40", alt:'' + = image_tag avatar_icon_for_user(project.creator, 64), class: "avatar s65", alt:'' - else - = project_icon(project, alt: '', class: 'avatar project-avatar s40', width: 40, height: 40) - .project-details - %h3.prepend-top-0.append-bottom-0 - = link_to project_path(project), class: 'text-plain' do - %span.project-full-name>< - %span.namespace-name - - if project.namespace && !skip_namespace - = project.namespace.human_name - \/ - %span.project-name< - = project.name - - - if access&.nonzero? - -# haml-lint:disable UnnecessaryStringOutput - = ' ' # prevent haml from eating the space between elements - %span.user-access-role= Gitlab::Access.human_access(access) - - - if show_last_commit_as_description - .description.prepend-top-5 - = link_to_markdown(project.commit.title, project_commit_path(project, project.commit), class: "commit-row-message") - - elsif project.description.present? - .description.prepend-top-5 - = markdown_field(project, :description) - - .controls - .prepend-top-0 - - if project.archived - %span.prepend-left-10.badge.badge-warning archived - - if can?(current_user, :read_cross_project) && project.pipeline_status.has_status? - %span.prepend-left-10 - = render_project_pipeline_status(project.pipeline_status) - - if forks - %span.prepend-left-10 - = sprite_icon('fork', size: 12) - = number_with_delimiter(project.forks_count) - - if stars - %span.prepend-left-10 - = icon('star') - = number_with_delimiter(project.star_count) - %span.prepend-left-10.visibility-icon.has-tooltip{ data: { container: 'body', placement: 'left' }, title: visibility_icon_description(project) } - = visibility_level_icon(project.visibility_level, fw: true) - .prepend-top-0 - updated #{updated_tooltip} + = project_icon(project, alt: '', class: 'avatar project-avatar s64', width: 64, height: 64) + .project-details.flex-sm-fill{ class: css_details_class } + .flex-wrapper.flex-fill + .d-flex.align-items-center.flex-wrap + %h2.d-flex.prepend-top-8 + = link_to project_path(project), class: 'text-plain' do + %span.project-full-name.append-right-8>< + %span.namespace-name + - if project.namespace && !skip_namespace + = project.namespace.human_name + \/ + %span.project-name< + = project.name + + %span.metadata-info.visibility-icon.append-right-10.prepend-top-8.has-tooltip{ data: { container: 'body', placement: 'top' }, title: visibility_icon_description(project) } + = visibility_level_icon(project.visibility_level, fw: true) + + - if explore_projects_tab? && project.repository.license + %span.metadata-info.d-inline-flex.align-items-center.append-right-10.prepend-top-8 + = sprite_icon('scale', size: 14, css_class: 'append-right-4') + = project.repository.license.name + + - if !explore_projects_tab? && access&.nonzero? + -# haml-lint:disable UnnecessaryStringOutput + = ' ' # prevent haml from eating the space between elements + .metadata-info.prepend-top-8 + %span.user-access-role.d-block= Gitlab::Access.human_access(access) + + - if show_last_commit_as_description + .description.d-none.d-sm-block.prepend-top-8.append-right-default + = link_to_markdown(project.commit.title, project_commit_path(project, project.commit), class: "commit-row-message") + - elsif project.description.present? + .description.d-none.d-sm-block.prepend-top-8.append-right-default + = markdown_field(project, :description) + + .controls.d-flex.flex-row.flex-sm-column.flex-md-column.align-items-center.align-items-sm-end.flex-wrap.flex-shrink-0{ class: css_controls_class } + .icon-container.d-flex.align-items-center + - if project.archived + %span.d-flex.icon-wrapper.badge.badge-warning archived + - if stars + %span.d-flex.align-items-center.icon-wrapper.stars.has-tooltip{ data: { container: 'body', placement: 'top' }, title: _('Stars') } + = sprite_icon('star', size: 14, css_class: 'append-right-4') + = number_with_delimiter(project.star_count) + - if forks + = link_to project_forks_path(project), + class: "align-items-center icon-wrapper forks has-tooltip", + title: _('Forks'), data: { container: 'body', placement: 'top' } do + = sprite_icon('fork', size: 14, css_class: 'append-right-4') + = number_with_delimiter(project.forks_count) + - if show_merge_request_count?(disabled: !merge_requests, compact_mode: compact_mode) + = link_to project_merge_requests_path(project), + class: "d-none d-lg-flex align-items-center icon-wrapper merge-requests has-tooltip", + title: _('Merge Requests'), data: { container: 'body', placement: 'top' } do + = sprite_icon('git-merge', size: 14, css_class: 'append-right-4') + = number_with_delimiter(project.open_merge_requests_count) + - if show_issue_count?(disabled: !issues, compact_mode: compact_mode) + = link_to project_issues_path(project), + class: "d-none d-lg-flex align-items-center icon-wrapper issues has-tooltip", + title: _('Issues'), data: { container: 'body', placement: 'top' } do + = sprite_icon('issues', size: 14, css_class: 'append-right-4') + = number_with_delimiter(project.open_issues_count) + - if pipeline_status && can?(current_user, :read_cross_project) && project.pipeline_status.has_status? + %span.icon-wrapper.pipeline-status + = render_project_pipeline_status(project.pipeline_status, tooltip_placement: 'top') + .updated-note + %span Updated #{updated_tooltip} + + .d-none.d-lg-flex.align-item-stretch + - unless compact_mode + - if current_user + %button.star-button.btn.btn-default.d-flex.align-items-center.star-btn.toggle-star{ type: "button", data: { endpoint: toggle_star_project_path(project, :json) } } + - if current_user.starred?(project) + = sprite_icon('star', { css_class: 'icon' }) + %span.starred= s_('ProjectOverview|Unstar') + - else + = sprite_icon('star-o', { css_class: 'icon' }) + %span= s_('ProjectOverview|Star') + + - else + = link_to new_user_session_path, class: 'btn btn-default has-tooltip count-badge-button d-flex align-items-center star-btn', title: s_('ProjectOverview|You must sign in to star a project') do + = sprite_icon('star-o', { css_class: 'icon' }) + %span= s_('ProjectOverview|Star') diff --git a/app/views/shared/snippets/_header.html.haml b/app/views/shared/snippets/_header.html.haml index 10bfc30492a..a43296aa806 100644 --- a/app/views/shared/snippets/_header.html.haml +++ b/app/views/shared/snippets/_header.html.haml @@ -30,7 +30,7 @@ - if @snippet.updated_at != @snippet.created_at = edited_time_ago_with_tooltip(@snippet, placement: 'bottom', html_class: 'snippet-edited-ago', exclude_author: true) - - if public_snippet? + - if @snippet.embeddable? .embed-snippet .input-group .input-group-prepend diff --git a/app/views/users/_overview.html.haml b/app/views/users/_overview.html.haml index b5bc1180290..d22905ecc93 100644 --- a/app/views/users/_overview.html.haml +++ b/app/views/users/_overview.html.haml @@ -9,22 +9,24 @@ .col-md-12.col-lg-6 - if can?(current_user, :read_cross_project) .activities-block + .append-right-5 + .prepend-top-16 + .d-flex.align-items-center.border-bottom + %h4.flex-grow + = s_('UserProfile|Activity') + = link_to s_('UserProfile|View all'), user_activity_path, class: "hide js-view-all" + .overview-content-list{ data: { href: user_path } } + .center.light.loading + = spinner nil, true + + .col-md-12.col-lg-6 + .projects-block + .prepend-left-5 .prepend-top-16 .d-flex.align-items-center.border-bottom %h4.flex-grow - = s_('UserProfile|Activity') - = link_to s_('UserProfile|View all'), user_activity_path, class: "hide js-view-all" - .overview-content-list{ data: { href: user_path } } + = s_('UserProfile|Personal projects') + = link_to s_('UserProfile|View all'), user_projects_path, class: "hide js-view-all" + .overview-content-list{ data: { href: user_projects_path } } .center.light.loading = spinner nil, true - - .col-md-12.col-lg-6 - .projects-block - .prepend-top-16 - .d-flex.align-items-center.border-bottom - %h4.flex-grow - = s_('UserProfile|Personal projects') - = link_to s_('UserProfile|View all'), user_projects_path, class: "hide js-view-all" - .overview-content-list{ data: { href: user_projects_path } } - .center.light.loading - = spinner nil, true diff --git a/app/views/users/show.html.haml b/app/views/users/show.html.haml index dd2cd36eac2..8da63a29ca6 100644 --- a/app/views/users/show.html.haml +++ b/app/views/users/show.html.haml @@ -71,7 +71,7 @@ = icon('twitter-square') - unless @user.website_url.blank? .profile-link-holder.middle-dot-divider - = link_to @user.short_website_url, @user.full_website_url, class: 'text-link', target: '_blank', rel: 'noopener noreferrer nofollow' + = link_to @user.short_website_url, @user.full_website_url, class: 'text-link', target: '_blank', rel: 'me noopener noreferrer nofollow' - unless @user.location.blank? .profile-link-holder.middle-dot-divider = icon('map-marker') diff --git a/app/workers/all_queues.yml b/app/workers/all_queues.yml index bc26b3f8ef2..d3cf21db335 100644 --- a/app/workers/all_queues.yml +++ b/app/workers/all_queues.yml @@ -27,7 +27,7 @@ - gcp_cluster:cluster_wait_for_app_installation - gcp_cluster:wait_for_cluster_creation - gcp_cluster:cluster_wait_for_ingress_ip_address -- gcp_cluster:cluster_platform_configure +- gcp_cluster:cluster_configure - gcp_cluster:cluster_project_configure - github_import_advance_stage @@ -88,6 +88,7 @@ - object_pool:object_pool_create - object_pool:object_pool_schedule_join - object_pool:object_pool_join +- object_pool:object_pool_destroy - default - mailers # ActionMailer::DeliveryJob.queue_name diff --git a/app/workers/cluster_platform_configure_worker.rb b/app/workers/cluster_configure_worker.rb index aa7570caa79..63e6cc147be 100644 --- a/app/workers/cluster_platform_configure_worker.rb +++ b/app/workers/cluster_configure_worker.rb @@ -1,6 +1,6 @@ # frozen_string_literal: true -class ClusterPlatformConfigureWorker +class ClusterConfigureWorker include ApplicationWorker include ClusterQueue diff --git a/app/workers/cluster_provision_worker.rb b/app/workers/cluster_provision_worker.rb index 3d5894b73ec..926ae2b7286 100644 --- a/app/workers/cluster_provision_worker.rb +++ b/app/workers/cluster_provision_worker.rb @@ -10,7 +10,7 @@ class ClusterProvisionWorker Clusters::Gcp::ProvisionService.new.execute(provider) if cluster.gcp? end - ClusterPlatformConfigureWorker.perform_async(cluster.id) if cluster.user? + ClusterConfigureWorker.perform_async(cluster.id) if cluster.user? end end end diff --git a/app/workers/mail_scheduler/notification_service_worker.rb b/app/workers/mail_scheduler/notification_service_worker.rb index 4726e416182..c8ccaf0c487 100644 --- a/app/workers/mail_scheduler/notification_service_worker.rb +++ b/app/workers/mail_scheduler/notification_service_worker.rb @@ -8,14 +8,35 @@ module MailScheduler include MailSchedulerQueue def perform(meth, *args) - deserialized_args = ActiveJob::Arguments.deserialize(args) + check_arguments!(args) + deserialized_args = ActiveJob::Arguments.deserialize(args) notification_service.public_send(meth, *deserialized_args) # rubocop:disable GitlabSecurity/PublicSend rescue ActiveJob::DeserializationError + # No-op. + # This exception gets raised when an argument + # is correct (deserializeable), but it still cannot be deserialized. + # This can happen when an object has been deleted after + # rails passes this job to sidekiq, but before + # sidekiq gets it for execution. + # In this case just do nothing. end def self.perform_async(*args) super(*ActiveJob::Arguments.serialize(args)) end + + private + + # If an argument is in the ActiveJob::Arguments::TYPE_WHITELIST list, + # it means the argument cannot be deserialized. + # Which means there's something wrong with our code. + def check_arguments!(args) + args.each do |arg| + if arg.class.in?(ActiveJob::Arguments::TYPE_WHITELIST) + raise(ArgumentError, "Argument `#{arg}` cannot be deserialized because of its type") + end + end + end end end diff --git a/app/workers/object_pool/destroy_worker.rb b/app/workers/object_pool/destroy_worker.rb new file mode 100644 index 00000000000..ca00d467d9b --- /dev/null +++ b/app/workers/object_pool/destroy_worker.rb @@ -0,0 +1,16 @@ +# frozen_string_literal: true + +module ObjectPool + class DestroyWorker + include ApplicationWorker + include ObjectPoolQueue + + def perform(pool_repository_id) + pool = PoolRepository.find_by_id(pool_repository_id) + return unless pool&.obsolete? + + pool.delete_object_pool + pool.destroy + end + end +end diff --git a/app/workers/post_receive.rb b/app/workers/post_receive.rb index 72a1733a2a8..bbd4ab159e4 100644 --- a/app/workers/post_receive.rb +++ b/app/workers/post_receive.rb @@ -3,7 +3,7 @@ class PostReceive include ApplicationWorker - def perform(gl_repository, identifier, changes) + def perform(gl_repository, identifier, changes, push_options = []) project, is_wiki = Gitlab::GlRepository.parse(gl_repository) if project.nil? @@ -15,7 +15,7 @@ class PostReceive # Use Sidekiq.logger so arguments can be correlated with execution # time and thread ID's. Sidekiq.logger.info "changes: #{changes.inspect}" if ENV['SIDEKIQ_LOG_ARGUMENTS'] - post_received = Gitlab::GitPostReceive.new(project, identifier, changes) + post_received = Gitlab::GitPostReceive.new(project, identifier, changes, push_options) if is_wiki process_wiki_changes(post_received) @@ -38,9 +38,21 @@ class PostReceive post_received.changes_refs do |oldrev, newrev, ref| if Gitlab::Git.tag_ref?(ref) - GitTagPushService.new(post_received.project, @user, oldrev: oldrev, newrev: newrev, ref: ref).execute + GitTagPushService.new( + post_received.project, + @user, + oldrev: oldrev, + newrev: newrev, + ref: ref, + push_options: post_received.push_options).execute elsif Gitlab::Git.branch_ref?(ref) - GitPushService.new(post_received.project, @user, oldrev: oldrev, newrev: newrev, ref: ref).execute + GitPushService.new( + post_received.project, + @user, + oldrev: oldrev, + newrev: newrev, + ref: ref, + push_options: post_received.push_options).execute end changes << Gitlab::DataBuilder::Repository.single_change(oldrev, newrev, ref) diff --git a/app/workers/stuck_merge_jobs_worker.rb b/app/workers/stuck_merge_jobs_worker.rb index 98c81956cba..f34ed6c4844 100644 --- a/app/workers/stuck_merge_jobs_worker.rb +++ b/app/workers/stuck_merge_jobs_worker.rb @@ -4,6 +4,10 @@ class StuckMergeJobsWorker include ApplicationWorker include CronjobQueue + def self.logger + Rails.logger + end + # rubocop: disable CodeReuse/ActiveRecord def perform stuck_merge_requests.find_in_batches(batch_size: 100) do |group| @@ -35,7 +39,7 @@ class StuckMergeJobsWorker # We rely on state machine callbacks to update head_pipeline_id merge_requests_to_reopen.each(&:unlock_mr) - Rails.logger.info("Updated state of locked merge jobs. JIDs: #{completed_jids.join(', ')}") + self.class.logger.info("Updated state of locked merge jobs. JIDs: #{completed_jids.join(', ')}") end # rubocop: enable CodeReuse/ActiveRecord diff --git a/bin/rails b/bin/rails index d21b64b3007..07396602377 100755 --- a/bin/rails +++ b/bin/rails @@ -1,14 +1,4 @@ #!/usr/bin/env ruby - -# Remove this block when upgraded to rails 5.0. -if %w[0 false].include?(ENV["RAILS5"]) - begin - load File.expand_path('../spring', __FILE__) - rescue LoadError => e - raise unless e.message.include?('spring') - end -end - APP_PATH = File.expand_path('../config/application', __dir__) require_relative '../config/boot' require 'rails/commands' @@ -1,14 +1,4 @@ #!/usr/bin/env ruby - -# Remove this block when upgraded to rails 5.0. -if %w[0 false].include?(ENV["RAILS5"]) - begin - load File.expand_path('../spring', __FILE__) - rescue LoadError => e - raise unless e.message.include?('spring') - end -end - require_relative '../config/boot' require 'rake' Rake.application.run diff --git a/bin/rspec b/bin/rspec index b0770e30a70..4236753c9c1 100755 --- a/bin/rspec +++ b/bin/rspec @@ -1,10 +1,5 @@ #!/usr/bin/env ruby -# Remove these two lines below when upgraded to rails 5.0. -# Allow run `rspec` command as `RAILS5=1 rspec ...` instead of `BUNDLE_GEMFILE=Gemfile.rails5 rspec ...` -gemfile = %w[0 false].include?(ENV["RAILS5"]) ? "Gemfile.rails4" : "Gemfile" -ENV['BUNDLE_GEMFILE'] ||= File.expand_path("../#{gemfile}", __dir__) - begin load File.expand_path('../spring', __FILE__) rescue LoadError => e diff --git a/bin/secpick b/bin/secpick index 2b263d452c9..11acdd82226 100755 --- a/bin/secpick +++ b/bin/secpick @@ -1,4 +1,7 @@ #!/usr/bin/env ruby +# frozen_string_literal: false + +require 'active_support/core_ext/object/to_query' require 'optparse' require 'open3' require 'rainbow/refinement' @@ -6,6 +9,7 @@ using Rainbow BRANCH_PREFIX = 'security'.freeze REMOTE = 'dev'.freeze +NEW_MR_URL = 'https://dev.gitlab.org/gitlab/gitlabhq/merge_requests/new'.freeze options = { version: nil, branch: nil, sha: nil } @@ -48,7 +52,24 @@ end.freeze command = "git fetch #{REMOTE} #{stable_branch} && git checkout #{stable_branch} && git pull #{REMOTE} #{stable_branch} && git checkout -B #{branch} && git cherry-pick #{options[:sha]} && git push #{REMOTE} #{branch} && git checkout #{original_branch}" -_stdin, stdout, stderr = Open3.popen3(command) +stdin, stdout, stderr, wait_thr = Open3.popen3(command) puts stdout.read&.green puts stderr.read&.red + +if wait_thr.value.success? + params = { + merge_request: { + source_branch: branch, + target_branch: stable_branch, + title: "WIP: [#{options[:version].tr('-', '.')}] ", + description: '/label ~security' + } + } + + puts "#{NEW_MR_URL}?#{params.to_query}".blue +end + +stdin.close +stdout.close +stderr.close diff --git a/bin/setup b/bin/setup index 34bb667087a..883825bc0a2 100755 --- a/bin/setup +++ b/bin/setup @@ -1,18 +1,12 @@ #!/usr/bin/env ruby -def rails5? - !%w[0 false].include?(ENV["RAILS5"]) -end - require "pathname" # path to your application root. APP_ROOT = Pathname.new File.expand_path("../../", __FILE__) -if rails5? - def system!(*args) - system(*args) || abort("\n== Command #{args} failed ==") - end +def system!(*args) + system(*args) || abort("\n== Command #{args} failed ==") end Dir.chdir APP_ROOT do @@ -20,14 +14,8 @@ Dir.chdir APP_ROOT do # Add necessary setup steps to this file: puts "== Installing dependencies ==" - - if rails5? - system! "gem install bundler --conservative" - system("bundle check") || system!("bundle install") - else - system "gem install bundler --conservative" - system "bundle check || bundle install" - end + system! "gem install bundler --conservative" + system("bundle check") || system!("bundle install") # puts "\n== Copying sample files ==" # unless File.exist?("config/database.yml") @@ -35,27 +23,11 @@ Dir.chdir APP_ROOT do # end puts "\n== Preparing database ==" - - if rails5? - system! "bin/rails db:setup" - else - system "bin/rake db:reset" - end + system! "bin/rails db:setup" puts "\n== Removing old logs and tempfiles ==" - - if rails5? - system! "bin/rails log:clear tmp:clear" - else - system "rm -f log/*" - system "rm -rf tmp/cache" - end + system! "bin/rails log:clear tmp:clear" puts "\n== Restarting application server ==" - - if rails5? - system! "bin/rails restart" - else - system "touch tmp/restart.txt" - end + system! "bin/rails restart" end diff --git a/changelogs/unreleased/18667-handle-push-opts.yml b/changelogs/unreleased/18667-handle-push-opts.yml new file mode 100644 index 00000000000..204293476f6 --- /dev/null +++ b/changelogs/unreleased/18667-handle-push-opts.yml @@ -0,0 +1,5 @@ +--- +title: Handle ci.skip push option +merge_request: 15643 +author: Jonathon Reinhart +type: added diff --git a/changelogs/unreleased/19376-post-bfg-cleanup.yml b/changelogs/unreleased/19376-post-bfg-cleanup.yml deleted file mode 100644 index fc1bcc30db9..00000000000 --- a/changelogs/unreleased/19376-post-bfg-cleanup.yml +++ /dev/null @@ -1,5 +0,0 @@ ---- -title: Use BFG object maps to clean projects -merge_request: 23189 -author: -type: added diff --git a/changelogs/unreleased/1979-redesign-mr-widget-approvals-ce.yml b/changelogs/unreleased/1979-redesign-mr-widget-approvals-ce.yml deleted file mode 100644 index d05b6054b22..00000000000 --- a/changelogs/unreleased/1979-redesign-mr-widget-approvals-ce.yml +++ /dev/null @@ -1,5 +0,0 @@ ---- -title: Redesign of MR header sections (CE) -merge_request: 23465 -author: -type: changed diff --git a/changelogs/unreleased/20422-hide-ui-variables-by-default.yml b/changelogs/unreleased/20422-hide-ui-variables-by-default.yml deleted file mode 100644 index 60285d49718..00000000000 --- a/changelogs/unreleased/20422-hide-ui-variables-by-default.yml +++ /dev/null @@ -1,6 +0,0 @@ ---- -title: Pipeline trigger variable values are hidden in the UI by default. Maintainers - have the option to reveal them. -merge_request: 23518 -author: jhampton -type: added diff --git a/changelogs/unreleased/22548-reopen-error-message.yml b/changelogs/unreleased/22548-reopen-error-message.yml deleted file mode 100644 index 79c20eccb12..00000000000 --- a/changelogs/unreleased/22548-reopen-error-message.yml +++ /dev/null @@ -1,6 +0,0 @@ ---- -title: Show error message when attempting to reopen an MR and there is an open MR - for the same branch -merge_request: 16447 -author: Akos Gyimesi -type: fixed diff --git a/changelogs/unreleased/23367-clarify-docs-allow-failure.yml b/changelogs/unreleased/23367-clarify-docs-allow-failure.yml new file mode 100644 index 00000000000..221d9e83ffb --- /dev/null +++ b/changelogs/unreleased/23367-clarify-docs-allow-failure.yml @@ -0,0 +1,5 @@ +--- +title: Clarifies docs about CI `allow_failure` +merge_request: 23367 +author: C.J. Jameson +type: other diff --git a/changelogs/unreleased/33705-merge-request-rebase-api.yml b/changelogs/unreleased/33705-merge-request-rebase-api.yml deleted file mode 100644 index 322fe31ce87..00000000000 --- a/changelogs/unreleased/33705-merge-request-rebase-api.yml +++ /dev/null @@ -1,5 +0,0 @@ ---- -title: Add a rebase API endpoint for merge requests -merge_request: 23296 -author: -type: added diff --git a/changelogs/unreleased/34758-deployment-cluster.yml b/changelogs/unreleased/34758-deployment-cluster.yml deleted file mode 100644 index 06374098343..00000000000 --- a/changelogs/unreleased/34758-deployment-cluster.yml +++ /dev/null @@ -1,5 +0,0 @@ ---- -title: Use group clusters when deploying (DeploymentPlatform) -merge_request: 22308 -author: -type: changed diff --git a/changelogs/unreleased/34758-extend-can-create-cluster-logic.yml b/changelogs/unreleased/34758-extend-can-create-cluster-logic.yml new file mode 100644 index 00000000000..65f5253a271 --- /dev/null +++ b/changelogs/unreleased/34758-extend-can-create-cluster-logic.yml @@ -0,0 +1,5 @@ +--- +title: Allow user to add Kubernetes cluster for clusterable when there are ancestor clusters +merge_request: 23569 +author: +type: other diff --git a/changelogs/unreleased/34758-group-cluster-controller.yml b/changelogs/unreleased/34758-group-cluster-controller.yml deleted file mode 100644 index 88c4c872714..00000000000 --- a/changelogs/unreleased/34758-group-cluster-controller.yml +++ /dev/null @@ -1,5 +0,0 @@ ---- -title: Add ability to create group level clusters and install gitlab managed applications -merge_request: 22450 -author: -type: added diff --git a/changelogs/unreleased/34758-list-ancestor-clusters.yml b/changelogs/unreleased/34758-list-ancestor-clusters.yml new file mode 100644 index 00000000000..8fdba7ba90a --- /dev/null +++ b/changelogs/unreleased/34758-list-ancestor-clusters.yml @@ -0,0 +1,5 @@ +--- +title: Show clusters of ancestors in cluster list page +merge_request: 22996 +author: +type: changed diff --git a/changelogs/unreleased/38495-calendar-activities-in-timezone.yml b/changelogs/unreleased/38495-calendar-activities-in-timezone.yml deleted file mode 100644 index 778d637609c..00000000000 --- a/changelogs/unreleased/38495-calendar-activities-in-timezone.yml +++ /dev/null @@ -1,5 +0,0 @@ ---- -title: Show user contributions in correct timezone within user profile -merge_request: 23419 -author: -type: changed diff --git a/changelogs/unreleased/39849_controller_sorts.yml b/changelogs/unreleased/39849_controller_sorts.yml deleted file mode 100644 index 5fad0cb4ede..00000000000 --- a/changelogs/unreleased/39849_controller_sorts.yml +++ /dev/null @@ -1,5 +0,0 @@ ---- -title: Allow sorting issues and MRs in reverse order -merge_request: 21438 -author: -type: changed diff --git a/changelogs/unreleased/40085-add-a-create_merge_request-quick-action.yml b/changelogs/unreleased/40085-add-a-create_merge_request-quick-action.yml deleted file mode 100644 index e1614ac7669..00000000000 --- a/changelogs/unreleased/40085-add-a-create_merge_request-quick-action.yml +++ /dev/null @@ -1,5 +0,0 @@ ---- -title: Creates /create_merge_request quickaction -merge_request: 22485 -author: Jacopo Beschi @jacopo-beschi -type: added diff --git a/changelogs/unreleased/40260-reduce-gitaly-calls-project-pipeline-status.yml b/changelogs/unreleased/40260-reduce-gitaly-calls-project-pipeline-status.yml deleted file mode 100644 index 8ab104e95f5..00000000000 --- a/changelogs/unreleased/40260-reduce-gitaly-calls-project-pipeline-status.yml +++ /dev/null @@ -1,5 +0,0 @@ ---- -title: Reduce Gitaly calls in projects dashboard -merge_request: 23307 -author: -type: performance diff --git a/changelogs/unreleased/40270-remove-gitlab-upgrader-completely.yml b/changelogs/unreleased/40270-remove-gitlab-upgrader-completely.yml new file mode 100644 index 00000000000..9ea2157bfb7 --- /dev/null +++ b/changelogs/unreleased/40270-remove-gitlab-upgrader-completely.yml @@ -0,0 +1,5 @@ +--- +title: Removes all instances of deprecated Gitlab Upgrader calls +merge_request: 23603 +author: '@jwolen' +type: removed diff --git a/changelogs/unreleased/40385-prohibit_impersonation.yml b/changelogs/unreleased/40385-prohibit_impersonation.yml deleted file mode 100644 index dd061b17939..00000000000 --- a/changelogs/unreleased/40385-prohibit_impersonation.yml +++ /dev/null @@ -1,5 +0,0 @@ ---- -title: Add config to prohibit impersonation -merge_request: 23338 -author: -type: added diff --git a/changelogs/unreleased/41766-vue-component.yml b/changelogs/unreleased/41766-vue-component.yml new file mode 100644 index 00000000000..12343c8ce84 --- /dev/null +++ b/changelogs/unreleased/41766-vue-component.yml @@ -0,0 +1,5 @@ +--- +title: Creates component for release block +merge_request: 23697 +author: +type: added diff --git a/changelogs/unreleased/41766-vuex-store.yml b/changelogs/unreleased/41766-vuex-store.yml new file mode 100644 index 00000000000..f20fc736a6f --- /dev/null +++ b/changelogs/unreleased/41766-vuex-store.yml @@ -0,0 +1,5 @@ +--- +title: Creates frontend app for releases +merge_request: 23796 +author: +type: added diff --git a/changelogs/unreleased/41875-allow-pipelines-to-be-deleted-by-project-owners.yml b/changelogs/unreleased/41875-allow-pipelines-to-be-deleted-by-project-owners.yml deleted file mode 100644 index 0662ff6f523..00000000000 --- a/changelogs/unreleased/41875-allow-pipelines-to-be-deleted-by-project-owners.yml +++ /dev/null @@ -1,5 +0,0 @@ ---- -title: Allow deleting a Pipeline via the API. -merge_request: 22988 -author: -type: added diff --git a/changelogs/unreleased/42125-extend-override-check-to-also-check-arity.yml b/changelogs/unreleased/42125-extend-override-check-to-also-check-arity.yml new file mode 100644 index 00000000000..9892466ca50 --- /dev/null +++ b/changelogs/unreleased/42125-extend-override-check-to-also-check-arity.yml @@ -0,0 +1,5 @@ +--- +title: Extend override check to also check arity +merge_request: 23498 +author: Jacopo Beschi @jacopo-beschi +type: added diff --git a/changelogs/unreleased/44984-use-serializer-for-issuable-sidebar.yml b/changelogs/unreleased/44984-use-serializer-for-issuable-sidebar.yml new file mode 100644 index 00000000000..ba9edc8740d --- /dev/null +++ b/changelogs/unreleased/44984-use-serializer-for-issuable-sidebar.yml @@ -0,0 +1,5 @@ +--- +title: Refactor issuable sidebar to use serializer +merge_request: 23379 +author: +type: other diff --git a/changelogs/unreleased/46544-webide-ctrl-enter-commit-shortcut.yml b/changelogs/unreleased/46544-webide-ctrl-enter-commit-shortcut.yml deleted file mode 100644 index 334c9b3ec9e..00000000000 --- a/changelogs/unreleased/46544-webide-ctrl-enter-commit-shortcut.yml +++ /dev/null @@ -1,5 +0,0 @@ ---- -title: "WebIDE: Pressing Ctrl-Enter while typing on the commit message now performs the commit action" -merge_request: 23049 -author: Thomas Pathier -type: added diff --git a/changelogs/unreleased/46950-systemcheck-ruby-version.yml b/changelogs/unreleased/46950-systemcheck-ruby-version.yml deleted file mode 100644 index e556e14223b..00000000000 --- a/changelogs/unreleased/46950-systemcheck-ruby-version.yml +++ /dev/null @@ -1,5 +0,0 @@ ---- -title: 'SystemCheck: Use a more reliable way to detect current Ruby version' -merge_request: 23291 -author: -type: changed diff --git a/changelogs/unreleased/47052-merge-button-does-not-appear-after-rebase-ing.yml b/changelogs/unreleased/47052-merge-button-does-not-appear-after-rebase-ing.yml new file mode 100644 index 00000000000..fd1e4605f2d --- /dev/null +++ b/changelogs/unreleased/47052-merge-button-does-not-appear-after-rebase-ing.yml @@ -0,0 +1,5 @@ +--- +title: Allow merge after rebase without page refresh on FF repositories +merge_request: 23572 +author: +type: fixed diff --git a/changelogs/unreleased/48475-gitlab-pages-settings-regressions.yml b/changelogs/unreleased/48475-gitlab-pages-settings-regressions.yml deleted file mode 100644 index f543730a57d..00000000000 --- a/changelogs/unreleased/48475-gitlab-pages-settings-regressions.yml +++ /dev/null @@ -1,5 +0,0 @@ ---- -title: Fixing regression issues on pages settings and details -merge_request: 22821 -author: -type: fixed diff --git a/changelogs/unreleased/48496-merge-request-refactor-does-not-highlight-selected-line.yml b/changelogs/unreleased/48496-merge-request-refactor-does-not-highlight-selected-line.yml deleted file mode 100644 index cfc74bef638..00000000000 --- a/changelogs/unreleased/48496-merge-request-refactor-does-not-highlight-selected-line.yml +++ /dev/null @@ -1,5 +0,0 @@ ---- -title: When user clicks linenumber in MR changes, highlight that line -merge_request: -author: -type: fixed diff --git a/changelogs/unreleased/48889-populate-merge_commit_sha.yml b/changelogs/unreleased/48889-populate-merge_commit_sha.yml deleted file mode 100644 index 0e25d8ecfb0..00000000000 --- a/changelogs/unreleased/48889-populate-merge_commit_sha.yml +++ /dev/null @@ -1,6 +0,0 @@ ---- -title: Fix "merged with [commit]" info for merge requests being merged automatically - by other actions -merge_request: 22794 -author: -type: fixed diff --git a/changelogs/unreleased/49479-hide-unmerged-env-perf-stats.yml b/changelogs/unreleased/49479-hide-unmerged-env-perf-stats.yml deleted file mode 100644 index 5118949f8a3..00000000000 --- a/changelogs/unreleased/49479-hide-unmerged-env-perf-stats.yml +++ /dev/null @@ -1,5 +0,0 @@ ---- -title: Don't show Memory Usage for unmerged MRs -merge_request: -author: -type: changed diff --git a/changelogs/unreleased/49565-ssh-push-mirroring.yml b/changelogs/unreleased/49565-ssh-push-mirroring.yml deleted file mode 100644 index 2dfeffa4088..00000000000 --- a/changelogs/unreleased/49565-ssh-push-mirroring.yml +++ /dev/null @@ -1,5 +0,0 @@ ---- -title: Allow SSH public-key authentication for push mirroring -merge_request: 22982 -author: -type: added diff --git a/changelogs/unreleased/49713-main-navbar-is-broken-in-certain-viewport-widths.yml b/changelogs/unreleased/49713-main-navbar-is-broken-in-certain-viewport-widths.yml deleted file mode 100644 index 0b5d1a6b05a..00000000000 --- a/changelogs/unreleased/49713-main-navbar-is-broken-in-certain-viewport-widths.yml +++ /dev/null @@ -1,5 +0,0 @@ ---- -title: Resolve Main navbar is broken in certain viewport widths -merge_request: 23348 -author: -type: fixed diff --git a/changelogs/unreleased/49726-upgrade-helm-to-2-11.yml b/changelogs/unreleased/49726-upgrade-helm-to-2-11.yml deleted file mode 100644 index dd26af875f5..00000000000 --- a/changelogs/unreleased/49726-upgrade-helm-to-2-11.yml +++ /dev/null @@ -1,5 +0,0 @@ ---- -title: Upgrade helm to 2.11.0 and upgrade on every install -merge_request: 22693 -author: -type: added diff --git a/changelogs/unreleased/50157-extended-user-centric-tooltips.yml b/changelogs/unreleased/50157-extended-user-centric-tooltips.yml deleted file mode 100644 index 3b55a867b87..00000000000 --- a/changelogs/unreleased/50157-extended-user-centric-tooltips.yml +++ /dev/null @@ -1,5 +0,0 @@ ---- -title: Extended user centric tooltips on issue and MR page -merge_request: 23231 -author: -type: added diff --git a/changelogs/unreleased/50264-add-border-around-the-repository-file-tree.yml b/changelogs/unreleased/50264-add-border-around-the-repository-file-tree.yml deleted file mode 100644 index 6315c3e7f36..00000000000 --- a/changelogs/unreleased/50264-add-border-around-the-repository-file-tree.yml +++ /dev/null @@ -1,5 +0,0 @@ ---- -title: Resolve Add border around the repository file tree -merge_request: 23018 -author: -type: changed diff --git a/changelogs/unreleased/50341-cleanup-useless-project-import-attributes.yml b/changelogs/unreleased/50341-cleanup-useless-project-import-attributes.yml deleted file mode 100644 index 3893f14e15c..00000000000 --- a/changelogs/unreleased/50341-cleanup-useless-project-import-attributes.yml +++ /dev/null @@ -1,6 +0,0 @@ ---- -title: Removes all the irrelevant code and columns that were migrated from the Project - table over to the ProjectImportState table -merge_request: 21497 -author: -type: performance diff --git a/changelogs/unreleased/50626-searching-users-by-the-admin-panel-wipes-query-when-using-sort.yml b/changelogs/unreleased/50626-searching-users-by-the-admin-panel-wipes-query-when-using-sort.yml deleted file mode 100644 index c3251fea54d..00000000000 --- a/changelogs/unreleased/50626-searching-users-by-the-admin-panel-wipes-query-when-using-sort.yml +++ /dev/null @@ -1,5 +0,0 @@ ---- -title: Allow search and sort users at same time on admin users page -merge_request: 23439 -author: -type: fixed diff --git a/changelogs/unreleased/50839-webide-mr-dropdown-filter.yml b/changelogs/unreleased/50839-webide-mr-dropdown-filter.yml deleted file mode 100644 index 1c6c8747197..00000000000 --- a/changelogs/unreleased/50839-webide-mr-dropdown-filter.yml +++ /dev/null @@ -1,5 +0,0 @@ ---- -title: Scope default MR search in WebIDE dropdown to current project -merge_request: 23400 -author: -type: changed diff --git a/changelogs/unreleased/51029-status-emoji-currently-replaces-avatar-on-mobile.yml b/changelogs/unreleased/51029-status-emoji-currently-replaces-avatar-on-mobile.yml deleted file mode 100644 index dc11ede5c8d..00000000000 --- a/changelogs/unreleased/51029-status-emoji-currently-replaces-avatar-on-mobile.yml +++ /dev/null @@ -1,5 +0,0 @@ ---- -title: Resolve status emoji being replaced by avatar on mobile -merge_request: 23408 -author: -type: other diff --git a/changelogs/unreleased/51061-readme-url-n-1-rpc-call-resolved.yml b/changelogs/unreleased/51061-readme-url-n-1-rpc-call-resolved.yml deleted file mode 100644 index 86f91fcb427..00000000000 --- a/changelogs/unreleased/51061-readme-url-n-1-rpc-call-resolved.yml +++ /dev/null @@ -1,5 +0,0 @@ ---- -title: Improves performance of Project#readme_url by caching the README path -merge_request: 23357 -author: -type: performance diff --git a/changelogs/unreleased/51083-fix-move-api.yml b/changelogs/unreleased/51083-fix-move-api.yml deleted file mode 100644 index 8838f6f267e..00000000000 --- a/changelogs/unreleased/51083-fix-move-api.yml +++ /dev/null @@ -1,5 +0,0 @@ ---- -title: 'Commits API: Preserve file content in move operations if unspecified' -merge_request: 23387 -author: -type: fixed diff --git a/changelogs/unreleased/51101-can-add-an-existing-group-member-into-a-group-project-with-new-permissions-but-permissions-are-not-overridde.yml b/changelogs/unreleased/51101-can-add-an-existing-group-member-into-a-group-project-with-new-permissions-but-permissions-are-not-overridde.yml deleted file mode 100644 index 96f33a72cc5..00000000000 --- a/changelogs/unreleased/51101-can-add-an-existing-group-member-into-a-group-project-with-new-permissions-but-permissions-are-not-overridde.yml +++ /dev/null @@ -1,5 +0,0 @@ ---- -title: Restrict member access level to be higher than that of any parent group -merge_request: 23226 -author: -type: fixed diff --git a/changelogs/unreleased/51122-fix-navigating-discussions.yml b/changelogs/unreleased/51122-fix-navigating-discussions.yml deleted file mode 100644 index 94d76654589..00000000000 --- a/changelogs/unreleased/51122-fix-navigating-discussions.yml +++ /dev/null @@ -1,5 +0,0 @@ ---- -title: Fix navigating by unresolved discussions on Merge Request page -merge_request: 22789 -author: -type: fixed diff --git a/changelogs/unreleased/51138-54026-breadcrumb-subgroups-ellipsis.yml b/changelogs/unreleased/51138-54026-breadcrumb-subgroups-ellipsis.yml deleted file mode 100644 index f695d5aeff8..00000000000 --- a/changelogs/unreleased/51138-54026-breadcrumb-subgroups-ellipsis.yml +++ /dev/null @@ -1,5 +0,0 @@ ---- -title: "Make auto-generated icons for subgroups in the breadcrumb dropdown display as a circle" -merge_request: 23062 -author: Thomas Pathier -type: fix
\ No newline at end of file diff --git a/changelogs/unreleased/51243-further-improvements-to-project-overview-ui.yml b/changelogs/unreleased/51243-further-improvements-to-project-overview-ui.yml deleted file mode 100644 index ddb5eaa89d0..00000000000 --- a/changelogs/unreleased/51243-further-improvements-to-project-overview-ui.yml +++ /dev/null @@ -1,5 +0,0 @@ ---- -title: Design improvements to project overview page -merge_request: 22196 -author: -type: changed diff --git a/changelogs/unreleased/51259-ci-cd-gitlab-ui-1.yml b/changelogs/unreleased/51259-ci-cd-gitlab-ui-1.yml deleted file mode 100644 index 1d761d6299c..00000000000 --- a/changelogs/unreleased/51259-ci-cd-gitlab-ui-1.yml +++ /dev/null @@ -1,5 +0,0 @@ ---- -title: Uses new gitlab-ui components in Jobs and Pipelines components -merge_request: -author: -type: other diff --git a/changelogs/unreleased/51259-ci-cd-tooltips.yml b/changelogs/unreleased/51259-ci-cd-tooltips.yml deleted file mode 100644 index fc0010dbeba..00000000000 --- a/changelogs/unreleased/51259-ci-cd-tooltips.yml +++ /dev/null @@ -1,6 +0,0 @@ ---- -title: Replaces tooltip directive with the new gl-tooltip directive for consistency - in some ci/cd code -merge_request: -author: -type: other diff --git a/changelogs/unreleased/51485-new-issue-labels-note.yml b/changelogs/unreleased/51485-new-issue-labels-note.yml new file mode 100644 index 00000000000..a312d379ce2 --- /dev/null +++ b/changelogs/unreleased/51485-new-issue-labels-note.yml @@ -0,0 +1,5 @@ +--- +title: Create system notes on issue / MR creation when labels, milestone, or due date is set +merge_request: 23859 +author: +type: added diff --git a/changelogs/unreleased/51606-expanding-a-diff-while-having-an-open-comment-form-will-always-scroll-down-to-the-comment.yml b/changelogs/unreleased/51606-expanding-a-diff-while-having-an-open-comment-form-will-always-scroll-down-to-the-comment.yml new file mode 100644 index 00000000000..a845234b42f --- /dev/null +++ b/changelogs/unreleased/51606-expanding-a-diff-while-having-an-open-comment-form-will-always-scroll-down-to-the-comment.yml @@ -0,0 +1,5 @@ +--- +title: Stop autofocusing on diff comment after initial mount +merge_request: 23849 +author: +type: fixed diff --git a/changelogs/unreleased/51792-dont-delete-failed-install-pods.yml b/changelogs/unreleased/51792-dont-delete-failed-install-pods.yml deleted file mode 100644 index 7a900cbb86e..00000000000 --- a/changelogs/unreleased/51792-dont-delete-failed-install-pods.yml +++ /dev/null @@ -1,5 +0,0 @@ ---- -title: Don't remove failed install pods after installing GitLab managed applications -merge_request: 23350 -author: -type: changed diff --git a/changelogs/unreleased/51944-redesign-project-lists-ui.yml b/changelogs/unreleased/51944-redesign-project-lists-ui.yml new file mode 100644 index 00000000000..56f9a86a686 --- /dev/null +++ b/changelogs/unreleased/51944-redesign-project-lists-ui.yml @@ -0,0 +1,5 @@ +--- +title: Redesign project lists UI +merge_request: 22682 +author: +type: other diff --git a/changelogs/unreleased/51959-branch-and-tag-name-links.yml b/changelogs/unreleased/51959-branch-and-tag-name-links.yml deleted file mode 100644 index 64f1522c70d..00000000000 --- a/changelogs/unreleased/51959-branch-and-tag-name-links.yml +++ /dev/null @@ -1,5 +0,0 @@ ---- -title: Chat message push notifications now include links back to GitLab branches -merge_request: 22651 -author: Tony Castrogiovanni -type: added diff --git a/changelogs/unreleased/51970-correct-ordering-of-metrics.yml b/changelogs/unreleased/51970-correct-ordering-of-metrics.yml new file mode 100644 index 00000000000..fbc7b58d901 --- /dev/null +++ b/changelogs/unreleased/51970-correct-ordering-of-metrics.yml @@ -0,0 +1,5 @@ +--- +title: Correct the ordering of metrics on the performance dashboard +merge_request: 23630 +author: +type: fixed diff --git a/changelogs/unreleased/51994-disable-merging-labels-in-dropdowns.yml b/changelogs/unreleased/51994-disable-merging-labels-in-dropdowns.yml new file mode 100644 index 00000000000..2d54cf814b7 --- /dev/null +++ b/changelogs/unreleased/51994-disable-merging-labels-in-dropdowns.yml @@ -0,0 +1,5 @@ +--- +title: Disable merging of labels with same names +merge_request: 23265 +author: +type: changed diff --git a/changelogs/unreleased/52007-frontmatter-toml-json.yml b/changelogs/unreleased/52007-frontmatter-toml-json.yml deleted file mode 100644 index bdada19f3a7..00000000000 --- a/changelogs/unreleased/52007-frontmatter-toml-json.yml +++ /dev/null @@ -1,5 +0,0 @@ ---- -title: Changed frontmatter filtering to support YAML, JSON, TOML, and arbitrary languages -merge_request: 23331 -author: Travis Miller -type: changed diff --git a/changelogs/unreleased/52276-jump-to-top-in-merge-request.yml b/changelogs/unreleased/52276-jump-to-top-in-merge-request.yml deleted file mode 100644 index 3dc95441eec..00000000000 --- a/changelogs/unreleased/52276-jump-to-top-in-merge-request.yml +++ /dev/null @@ -1,5 +0,0 @@ ---- -title: Allow user to scroll to top of tab on MR page -merge_request: -author: -type: added diff --git a/changelogs/unreleased/52285-omniauth-jwt-ppk-support.yml b/changelogs/unreleased/52285-omniauth-jwt-ppk-support.yml deleted file mode 100644 index 3ef564238c5..00000000000 --- a/changelogs/unreleased/52285-omniauth-jwt-ppk-support.yml +++ /dev/null @@ -1,5 +0,0 @@ ---- -title: Support RSA and ECDSA algorithms in Omniauth JWT provider -merge_request: 23411 -author: Michael Tsyganov -type: fixed diff --git a/changelogs/unreleased/52370-filter-by-none-any-for-labels-in-issues-mrs-boards.yml b/changelogs/unreleased/52370-filter-by-none-any-for-labels-in-issues-mrs-boards.yml deleted file mode 100644 index 9e1ee3ede5e..00000000000 --- a/changelogs/unreleased/52370-filter-by-none-any-for-labels-in-issues-mrs-boards.yml +++ /dev/null @@ -1,5 +0,0 @@ ---- -title: Adds Any option to label filters -merge_request: 23111 -author: Jacopo Beschi @jacopo-beschi -type: added diff --git a/changelogs/unreleased/52371-filter-by-none-any-for-labels-in-issues-mrs-api.yml b/changelogs/unreleased/52371-filter-by-none-any-for-labels-in-issues-mrs-api.yml deleted file mode 100644 index bb196af3e90..00000000000 --- a/changelogs/unreleased/52371-filter-by-none-any-for-labels-in-issues-mrs-api.yml +++ /dev/null @@ -1,5 +0,0 @@ ---- -title: Filter by None/Any for labels in issues/mrs API -merge_request: 22622 -author: Jacopo Beschi @jacopo-beschi -type: added diff --git a/changelogs/unreleased/52371-removes-patially-matching-no-label-and-makes-it-case-insensitive.yml b/changelogs/unreleased/52371-removes-patially-matching-no-label-and-makes-it-case-insensitive.yml deleted file mode 100644 index c1fc21c641a..00000000000 --- a/changelogs/unreleased/52371-removes-patially-matching-no-label-and-makes-it-case-insensitive.yml +++ /dev/null @@ -1,5 +0,0 @@ ---- -title: removes partially matching of No Label filter and makes it case-insensitive -merge_request: 22622 -author: Jacopo Beschi @jacopo-beschi -type: changed diff --git a/changelogs/unreleased/52385-search-bar-for-dashboard-list.yml b/changelogs/unreleased/52385-search-bar-for-dashboard-list.yml deleted file mode 100644 index a437ae560cb..00000000000 --- a/changelogs/unreleased/52385-search-bar-for-dashboard-list.yml +++ /dev/null @@ -1,5 +0,0 @@ ---- -title: Use search bar for filtering in dashboard issues / MRs -merge_request: 22641 -author: Heinrich Lee Yu -type: changed diff --git a/changelogs/unreleased/52453-show-subgroups-in-group-create-issue.yml b/changelogs/unreleased/52453-show-subgroups-in-group-create-issue.yml deleted file mode 100644 index d5877e96d07..00000000000 --- a/changelogs/unreleased/52453-show-subgroups-in-group-create-issue.yml +++ /dev/null @@ -1,5 +0,0 @@ ---- -title: Fix project selector consistency in groups issues / MRs / boards pages -merge_request: 22612 -author: Heinrich Lee Yu -type: fixed diff --git a/changelogs/unreleased/52620-fix-loader-animation-alignment.yml b/changelogs/unreleased/52620-fix-loader-animation-alignment.yml new file mode 100644 index 00000000000..5cfb7fc019f --- /dev/null +++ b/changelogs/unreleased/52620-fix-loader-animation-alignment.yml @@ -0,0 +1,5 @@ +--- +title: Aligns build loader animation with the job log +merge_request: 23959 +author: +type: fixed diff --git a/changelogs/unreleased/52712-further-ui-improvements-to-profile-overview-tab.yml b/changelogs/unreleased/52712-further-ui-improvements-to-profile-overview-tab.yml deleted file mode 100644 index 65aa9323d2e..00000000000 --- a/changelogs/unreleased/52712-further-ui-improvements-to-profile-overview-tab.yml +++ /dev/null @@ -1,5 +0,0 @@ ---- -title: UI improvements to user's profile -merge_request: 22977 -author: -type: other diff --git a/changelogs/unreleased/52828-inconsistency-in-fonts-used-for-branch-name-and-create-from-fields-when-creating-new-branch-from-ui.yml b/changelogs/unreleased/52828-inconsistency-in-fonts-used-for-branch-name-and-create-from-fields-when-creating-new-branch-from-ui.yml deleted file mode 100644 index 8132dde8636..00000000000 --- a/changelogs/unreleased/52828-inconsistency-in-fonts-used-for-branch-name-and-create-from-fields-when-creating-new-branch-from-ui.yml +++ /dev/null @@ -1,5 +0,0 @@ ---- -title: Make new branch form fields' fonts consistent -merge_request: -author: -type: fixed diff --git a/changelogs/unreleased/52888-status-emoji-should-not-be-added-to-awards-section-on-issue-page-2.yml b/changelogs/unreleased/52888-status-emoji-should-not-be-added-to-awards-section-on-issue-page-2.yml new file mode 100644 index 00000000000..501940d6da3 --- /dev/null +++ b/changelogs/unreleased/52888-status-emoji-should-not-be-added-to-awards-section-on-issue-page-2.yml @@ -0,0 +1,5 @@ +--- +title: Prevent awards emoji being updated when updating status +merge_request: 23470 +author: +type: fixed diff --git a/changelogs/unreleased/52940-fix-internal-email-pattern-not-respected.yml b/changelogs/unreleased/52940-fix-internal-email-pattern-not-respected.yml deleted file mode 100644 index 98e15a5cc0a..00000000000 --- a/changelogs/unreleased/52940-fix-internal-email-pattern-not-respected.yml +++ /dev/null @@ -1,5 +0,0 @@ ---- -title: Fix a bug where internal email pattern wasn't respected -merge_request: 22516 -author: -type: fixed diff --git a/changelogs/unreleased/53020-user-specific-profile-page-settings-fields-don-t-have-help-text-placeholders.yml b/changelogs/unreleased/53020-user-specific-profile-page-settings-fields-don-t-have-help-text-placeholders.yml new file mode 100644 index 00000000000..99da02dd31a --- /dev/null +++ b/changelogs/unreleased/53020-user-specific-profile-page-settings-fields-don-t-have-help-text-placeholders.yml @@ -0,0 +1,5 @@ +--- +title: Adds explanatory text to input fields on user profile settings page +merge_request: 23673 +author: +type: other diff --git a/changelogs/unreleased/53289-update-haml_lint-to-0-28-0.yml b/changelogs/unreleased/53289-update-haml_lint-to-0-28-0.yml deleted file mode 100644 index 9a16666c416..00000000000 --- a/changelogs/unreleased/53289-update-haml_lint-to-0-28-0.yml +++ /dev/null @@ -1,5 +0,0 @@ ---- -title: Update haml_lint to 0.28.0 -merge_request: 22660 -author: Takuya Noguchi -type: other diff --git a/changelogs/unreleased/53290-incorrect-project-list-order-select-default-label.yml b/changelogs/unreleased/53290-incorrect-project-list-order-select-default-label.yml deleted file mode 100644 index d076352a27b..00000000000 --- a/changelogs/unreleased/53290-incorrect-project-list-order-select-default-label.yml +++ /dev/null @@ -1,5 +0,0 @@ ---- -title: Fix default sorting for subgroups and projects list -merge_request: 23058 -author: Jacopo Beschi @jacopo-beschi -type: fixed diff --git a/changelogs/unreleased/53291-update-ffaker-to-2-10-0.yml b/changelogs/unreleased/53291-update-ffaker-to-2-10-0.yml deleted file mode 100644 index a1b95df5e32..00000000000 --- a/changelogs/unreleased/53291-update-ffaker-to-2-10-0.yml +++ /dev/null @@ -1,5 +0,0 @@ ---- -title: Update ffaker to 2.10.0 -merge_request: 22661 -author: Takuya Noguchi -type: other diff --git a/changelogs/unreleased/53326-improve-issues-empty-state.yml b/changelogs/unreleased/53326-improve-issues-empty-state.yml deleted file mode 100644 index 7632db808b5..00000000000 --- a/changelogs/unreleased/53326-improve-issues-empty-state.yml +++ /dev/null @@ -1,5 +0,0 @@ ---- -title: Show different empty state for filtered issues and MRs -merge_request: 22775 -author: Heinrich Lee Yu -type: changed diff --git a/changelogs/unreleased/53400-unstar-icon-button-is-misaligned.yml b/changelogs/unreleased/53400-unstar-icon-button-is-misaligned.yml deleted file mode 100644 index b393795f491..00000000000 --- a/changelogs/unreleased/53400-unstar-icon-button-is-misaligned.yml +++ /dev/null @@ -1,5 +0,0 @@ ---- -title: 'Fix: Unstar icon button is misaligned' -merge_request: 23444 -author: -type: fixed diff --git a/changelogs/unreleased/53493-list-id-email-header.yml b/changelogs/unreleased/53493-list-id-email-header.yml new file mode 100644 index 00000000000..09a0639f6f5 --- /dev/null +++ b/changelogs/unreleased/53493-list-id-email-header.yml @@ -0,0 +1,5 @@ +--- +title: Add project identifier as List-Id email Header to ease filtering +merge_request: 22817 +author: Olivier Crête +type: added diff --git a/changelogs/unreleased/53578-fe-deployment-status.yml b/changelogs/unreleased/53578-fe-deployment-status.yml deleted file mode 100644 index b88bd70ee2e..00000000000 --- a/changelogs/unreleased/53578-fe-deployment-status.yml +++ /dev/null @@ -1,5 +0,0 @@ ---- -title: Adds states to the deployment widget -merge_request: -author: -type: added diff --git a/changelogs/unreleased/53626-update-config-map-on-install-retry.yml b/changelogs/unreleased/53626-update-config-map-on-install-retry.yml deleted file mode 100644 index 38e79c06c89..00000000000 --- a/changelogs/unreleased/53626-update-config-map-on-install-retry.yml +++ /dev/null @@ -1,5 +0,0 @@ ---- -title: Update config map for gitlab managed application if already present on install -merge_request: 22969 -author: -type: other diff --git a/changelogs/unreleased/53640-follow-up-from-resolve-redesign-activity-feed.yml b/changelogs/unreleased/53640-follow-up-from-resolve-redesign-activity-feed.yml deleted file mode 100644 index 66301329c52..00000000000 --- a/changelogs/unreleased/53640-follow-up-from-resolve-redesign-activity-feed.yml +++ /dev/null @@ -1,4 +0,0 @@ -title: Adds new icon size to Vue icon component -merge_request: 22899 -author: -type: other diff --git a/changelogs/unreleased/53659-use-padded-key-for-gcm-ciphers.yml b/changelogs/unreleased/53659-use-padded-key-for-gcm-ciphers.yml deleted file mode 100644 index fe9ac7b3dc7..00000000000 --- a/changelogs/unreleased/53659-use-padded-key-for-gcm-ciphers.yml +++ /dev/null @@ -1,5 +0,0 @@ ---- -title: Fix web hook functionality when the database encryption key is too short -merge_request: 23573 -author: -type: fixed diff --git a/changelogs/unreleased/53700-hashed-storagemigration.yml b/changelogs/unreleased/53700-hashed-storagemigration.yml deleted file mode 100644 index 899012ffd22..00000000000 --- a/changelogs/unreleased/53700-hashed-storagemigration.yml +++ /dev/null @@ -1,5 +0,0 @@ ---- -title: 'Hashed Storage: allow migration to be retried in partially migrated projects' -merge_request: 23087 -author: -type: fixed diff --git a/changelogs/unreleased/53728-warn-in-web-editor-when-user-navigates-away.yml b/changelogs/unreleased/53728-warn-in-web-editor-when-user-navigates-away.yml deleted file mode 100644 index 8377fdc6133..00000000000 --- a/changelogs/unreleased/53728-warn-in-web-editor-when-user-navigates-away.yml +++ /dev/null @@ -1,5 +0,0 @@ ---- -title: Prevent user from navigating away from file edit without commit -merge_request: -author: -type: fixed diff --git a/changelogs/unreleased/53816-empty-label-menu-if-not-logged-in.yml b/changelogs/unreleased/53816-empty-label-menu-if-not-logged-in.yml deleted file mode 100644 index a9ca56303eb..00000000000 --- a/changelogs/unreleased/53816-empty-label-menu-if-not-logged-in.yml +++ /dev/null @@ -1,5 +0,0 @@ ---- -title: Removes promote to group label for anonymous user -merge_request: 23042 -author: Jacopo Beschi @jacopo-beschi -type: fixed diff --git a/changelogs/unreleased/53874-navbar-lowres.yml b/changelogs/unreleased/53874-navbar-lowres.yml deleted file mode 100644 index 3b31b8f93fe..00000000000 --- a/changelogs/unreleased/53874-navbar-lowres.yml +++ /dev/null @@ -1,5 +0,0 @@ ---- -title: "Fix overlapping navbar separator and overflowing navbar dropdown on small displays" -merge_request: 23126 -author: Thomas Pathier -type: fix diff --git a/changelogs/unreleased/53907-improve-milestone-links.yml b/changelogs/unreleased/53907-improve-milestone-links.yml new file mode 100644 index 00000000000..8e867e783cc --- /dev/null +++ b/changelogs/unreleased/53907-improve-milestone-links.yml @@ -0,0 +1,5 @@ +--- +title: Add % prefix to milestone reference links +merge_request: 23928 +author: +type: changed diff --git a/changelogs/unreleased/53933-include-dates-in-milestone-change-email.yml b/changelogs/unreleased/53933-include-dates-in-milestone-change-email.yml new file mode 100644 index 00000000000..5c40a1e900c --- /dev/null +++ b/changelogs/unreleased/53933-include-dates-in-milestone-change-email.yml @@ -0,0 +1,5 @@ +--- +title: Add date range in milestone change email notifications +merge_request: 23762 +author: +type: changed diff --git a/changelogs/unreleased/53954-resolved-non-diff-discussions-on-merge-requests-no-longer-show-who-resolved-them-and-when-at-a-glance.yml b/changelogs/unreleased/53954-resolved-non-diff-discussions-on-merge-requests-no-longer-show-who-resolved-them-and-when-at-a-glance.yml new file mode 100644 index 00000000000..0632c1992c7 --- /dev/null +++ b/changelogs/unreleased/53954-resolved-non-diff-discussions-on-merge-requests-no-longer-show-who-resolved-them-and-when-at-a-glance.yml @@ -0,0 +1,5 @@ +--- +title: Show message on non-diff discussions +merge_request: +author: +type: changed diff --git a/changelogs/unreleased/53988-remove-notes-index-on-updated-at.yml b/changelogs/unreleased/53988-remove-notes-index-on-updated-at.yml deleted file mode 100644 index f0bbf69736d..00000000000 --- a/changelogs/unreleased/53988-remove-notes-index-on-updated-at.yml +++ /dev/null @@ -1,5 +0,0 @@ ---- -title: Remove index for notes on updated_at -merge_request: 23356 -author: -type: performance diff --git a/changelogs/unreleased/53992-add-events-index-on-project-id-and-created-at.yml b/changelogs/unreleased/53992-add-events-index-on-project-id-and-created-at.yml deleted file mode 100644 index a2a3fa00f01..00000000000 --- a/changelogs/unreleased/53992-add-events-index-on-project-id-and-created-at.yml +++ /dev/null @@ -1,5 +0,0 @@ ---- -title: Add index for events on project_id and created_at -merge_request: 23354 -author: -type: performance diff --git a/changelogs/unreleased/53994-add-missing-ci_builds-partial-indices.yml b/changelogs/unreleased/53994-add-missing-ci_builds-partial-indices.yml deleted file mode 100644 index 4673ba38bae..00000000000 --- a/changelogs/unreleased/53994-add-missing-ci_builds-partial-indices.yml +++ /dev/null @@ -1,5 +0,0 @@ ---- -title: Add partial index for ci_builds on project_id and status -merge_request: 23268 -author: -type: performance diff --git a/changelogs/unreleased/54004-update-asana-to-0-8-1.yml b/changelogs/unreleased/54004-update-asana-to-0-8-1.yml deleted file mode 100644 index a47b4f3c4d9..00000000000 --- a/changelogs/unreleased/54004-update-asana-to-0-8-1.yml +++ /dev/null @@ -1,5 +0,0 @@ ---- -title: Update asana to 0.8.1 -merge_request: 23039 -author: Takuya Noguchi -type: other diff --git a/changelogs/unreleased/54010-update-asciidoctor-to-1-5-8.yml b/changelogs/unreleased/54010-update-asciidoctor-to-1-5-8.yml deleted file mode 100644 index f0b0aa0ee1c..00000000000 --- a/changelogs/unreleased/54010-update-asciidoctor-to-1-5-8.yml +++ /dev/null @@ -1,5 +0,0 @@ ---- -title: Update asciidoctor to 1.5.8 -merge_request: 23047 -author: Takuya Noguchi -type: other diff --git a/changelogs/unreleased/54015-Markdown-Editor-improve-Cursor-placement.yml b/changelogs/unreleased/54015-Markdown-Editor-improve-Cursor-placement.yml deleted file mode 100644 index 28e3fae01a9..00000000000 --- a/changelogs/unreleased/54015-Markdown-Editor-improve-Cursor-placement.yml +++ /dev/null @@ -1,5 +0,0 @@ ---- -title: Refine cursor positioning in Markdown Editor for wrap tags -merge_request: 23085 -author: Johann Hubert Sonntagbauer -type: changed diff --git a/changelogs/unreleased/54021-empty-button.yml b/changelogs/unreleased/54021-empty-button.yml deleted file mode 100644 index 3b03665cf95..00000000000 --- a/changelogs/unreleased/54021-empty-button.yml +++ /dev/null @@ -1,5 +0,0 @@ ---- -title: Prevent empty button being rendered in empty state -merge_request: -author: -type: fixed diff --git a/changelogs/unreleased/54032-reply-shortcut-only-discussion-text.yml b/changelogs/unreleased/54032-reply-shortcut-only-discussion-text.yml deleted file mode 100644 index 5c1f6e74b39..00000000000 --- a/changelogs/unreleased/54032-reply-shortcut-only-discussion-text.yml +++ /dev/null @@ -1,5 +0,0 @@ ---- -title: Make reply shortcut only quote selected discussion text -merge_request: 23096 -author: Thomas Pathier -type: fix diff --git a/changelogs/unreleased/54048-Line-numbers-are-misaligned-in-file-blame-view.yml b/changelogs/unreleased/54048-Line-numbers-are-misaligned-in-file-blame-view.yml deleted file mode 100644 index 8ceac4ec869..00000000000 --- a/changelogs/unreleased/54048-Line-numbers-are-misaligned-in-file-blame-view.yml +++ /dev/null @@ -1,5 +0,0 @@ ---- -title: Fix line height of numbers in file blame view -merge_request: 23090 -author: Johann Hubert Sonntagbauer -type: fixed diff --git a/changelogs/unreleased/54093-the-default_value_for-gem-doesn-t-handle-actioncontroller-parameters-correctly.yml b/changelogs/unreleased/54093-the-default_value_for-gem-doesn-t-handle-actioncontroller-parameters-correctly.yml deleted file mode 100644 index 3d6fd2d065a..00000000000 --- a/changelogs/unreleased/54093-the-default_value_for-gem-doesn-t-handle-actioncontroller-parameters-correctly.yml +++ /dev/null @@ -1,7 +0,0 @@ ---- -title: Fixes an issue where default values from models would override values set in - the interface (e.g. users would be set to external even though their emails matches - the internal email address pattern) -merge_request: 23114 -author: -type: fixed diff --git a/changelogs/unreleased/54146-fix-calendar-query.yml b/changelogs/unreleased/54146-fix-calendar-query.yml new file mode 100644 index 00000000000..dcac343108a --- /dev/null +++ b/changelogs/unreleased/54146-fix-calendar-query.yml @@ -0,0 +1,5 @@ +--- +title: Fix project calendar feed when sorted by priority +merge_request: 23870 +author: +type: fixed diff --git a/changelogs/unreleased/54160-use-reports-syntax-for-sast-in-auto-devops.yml b/changelogs/unreleased/54160-use-reports-syntax-for-sast-in-auto-devops.yml deleted file mode 100644 index 86c5a0c5a95..00000000000 --- a/changelogs/unreleased/54160-use-reports-syntax-for-sast-in-auto-devops.yml +++ /dev/null @@ -1,5 +0,0 @@ ---- -title: Use reports syntax for SAST in Auto DevOps -merge_request: 23163 -author: -type: changed diff --git a/changelogs/unreleased/54201-update-rack-to-2-0-6.yml b/changelogs/unreleased/54201-update-rack-to-2-0-6.yml deleted file mode 100644 index 020b2bc0957..00000000000 --- a/changelogs/unreleased/54201-update-rack-to-2-0-6.yml +++ /dev/null @@ -1,5 +0,0 @@ ---- -title: Update rack to 2.0.6 (for QA environments) -merge_request: 23171 -author: Takuya Noguchi -type: security diff --git a/changelogs/unreleased/54206-show-the-activity-filter-dropdown-in-discussion-tab-only.yml b/changelogs/unreleased/54206-show-the-activity-filter-dropdown-in-discussion-tab-only.yml new file mode 100644 index 00000000000..e29987b0935 --- /dev/null +++ b/changelogs/unreleased/54206-show-the-activity-filter-dropdown-in-discussion-tab-only.yml @@ -0,0 +1,5 @@ +--- +title: Discussion filter only displayed in discussions tab for merge requests +merge_request: 24082 +author: +type: changed diff --git a/changelogs/unreleased/54218-fix-mergeUrlParams.yml b/changelogs/unreleased/54218-fix-mergeUrlParams.yml deleted file mode 100644 index dae06b66e8e..00000000000 --- a/changelogs/unreleased/54218-fix-mergeUrlParams.yml +++ /dev/null @@ -1,5 +0,0 @@ ---- -title: "Fix mergeUrlParams with fragment URL" -merge_request: 54218 -author: Thomas Holder -type: fixed diff --git a/changelogs/unreleased/54336-include-tags-into-pipeline-detail-view.yml b/changelogs/unreleased/54336-include-tags-into-pipeline-detail-view.yml deleted file mode 100644 index 11f941ab9bb..00000000000 --- a/changelogs/unreleased/54336-include-tags-into-pipeline-detail-view.yml +++ /dev/null @@ -1,5 +0,0 @@ ---- -title: Merge request pipeline tag, and adds tags to pipeline view -merge_request: 23364 -author: -type: added diff --git a/changelogs/unreleased/54391-tag.yml b/changelogs/unreleased/54391-tag.yml deleted file mode 100644 index be571c6b0c3..00000000000 --- a/changelogs/unreleased/54391-tag.yml +++ /dev/null @@ -1,5 +0,0 @@ ---- -title: Correctly styles tags in sidebar for job page -merge_request: -author: -type: fixed diff --git a/changelogs/unreleased/54407-fix-limited-intersection-observers.yml b/changelogs/unreleased/54407-fix-limited-intersection-observers.yml deleted file mode 100644 index 2c2bedb170b..00000000000 --- a/changelogs/unreleased/54407-fix-limited-intersection-observers.yml +++ /dev/null @@ -1,5 +0,0 @@ ---- -title: Fix Image Lazy Loader for some older browsers -merge_request: -author: -type: fixed diff --git a/changelogs/unreleased/54427-label-xss.yml b/changelogs/unreleased/54427-label-xss.yml new file mode 100644 index 00000000000..090d1832af2 --- /dev/null +++ b/changelogs/unreleased/54427-label-xss.yml @@ -0,0 +1,5 @@ +--- +title: Escape html entities in LabelReferenceFilter when no label found +merge_request: +author: +type: security diff --git a/changelogs/unreleased/54571-runner-tags.yml b/changelogs/unreleased/54571-runner-tags.yml deleted file mode 100644 index 1bb19d22e9c..00000000000 --- a/changelogs/unreleased/54571-runner-tags.yml +++ /dev/null @@ -1,5 +0,0 @@ ---- -title: Adds margins between tags when a job is stuck -merge_request: -author: -type: fixed diff --git a/changelogs/unreleased/54626-able-to-download-a-single-archive-file-with-api-by-ref-name.yml b/changelogs/unreleased/54626-able-to-download-a-single-archive-file-with-api-by-ref-name.yml deleted file mode 100644 index fa905b47ca2..00000000000 --- a/changelogs/unreleased/54626-able-to-download-a-single-archive-file-with-api-by-ref-name.yml +++ /dev/null @@ -1,5 +0,0 @@ ---- -title: Add new endpoint to download single artifact file for a ref -merge_request: 23538 -author: -type: added diff --git a/changelogs/unreleased/54648-fix-order-by-dropdown-tablet-screens.yml b/changelogs/unreleased/54648-fix-order-by-dropdown-tablet-screens.yml deleted file mode 100644 index 671d1590991..00000000000 --- a/changelogs/unreleased/54648-fix-order-by-dropdown-tablet-screens.yml +++ /dev/null @@ -1,5 +0,0 @@ ---- -title: Fix Order By dropdown menu styling in tablet and mobile screens -merge_request: 23446 -author: -type: fixed diff --git a/changelogs/unreleased/54736-sign-in-bottom-margin.yml b/changelogs/unreleased/54736-sign-in-bottom-margin.yml new file mode 100644 index 00000000000..32b5b44fe35 --- /dev/null +++ b/changelogs/unreleased/54736-sign-in-bottom-margin.yml @@ -0,0 +1,5 @@ +--- +title: Fix login box bottom margins on signin page +merge_request: 23739 +author: '@gear54' +type: fixed diff --git a/changelogs/unreleased/54786-mr-empty-file-display.yml b/changelogs/unreleased/54786-mr-empty-file-display.yml new file mode 100644 index 00000000000..5adf5744755 --- /dev/null +++ b/changelogs/unreleased/54786-mr-empty-file-display.yml @@ -0,0 +1,5 @@ +--- +title: Display empty files properly on MR diffs +merge_request: 23671 +author: Sean Nichols +type: fixed diff --git a/changelogs/unreleased/54814-sidebar-styling-updates.yml b/changelogs/unreleased/54814-sidebar-styling-updates.yml new file mode 100644 index 00000000000..98e3836ee14 --- /dev/null +++ b/changelogs/unreleased/54814-sidebar-styling-updates.yml @@ -0,0 +1,5 @@ +--- +title: Fix label and header styles in the job details sidebar. +merge_request: 23816 +author: Nathan Friend +type: changed diff --git a/changelogs/unreleased/54826-use-read_repository-scope-on-read-only-files-endpoints.yml b/changelogs/unreleased/54826-use-read_repository-scope-on-read-only-files-endpoints.yml deleted file mode 100644 index ef8e93fca43..00000000000 --- a/changelogs/unreleased/54826-use-read_repository-scope-on-read-only-files-endpoints.yml +++ /dev/null @@ -1,5 +0,0 @@ ---- -title: Use read_repository scope on read-only files API -merge_request: 23534 -author: -type: fixed diff --git a/changelogs/unreleased/54844-report-syntax-dep-scan-ado.yml b/changelogs/unreleased/54844-report-syntax-dep-scan-ado.yml new file mode 100644 index 00000000000..95fc5cb804d --- /dev/null +++ b/changelogs/unreleased/54844-report-syntax-dep-scan-ado.yml @@ -0,0 +1,5 @@ +--- +title: Use reports syntax for Dependency scanning in Auto DevOps +merge_request: 24081 +author: +type: added diff --git a/changelogs/unreleased/54857-fix-templates-path-traversal.yml b/changelogs/unreleased/54857-fix-templates-path-traversal.yml deleted file mode 100644 index 0da02432c60..00000000000 --- a/changelogs/unreleased/54857-fix-templates-path-traversal.yml +++ /dev/null @@ -1,5 +0,0 @@ ---- -title: Prevent a path traversal attack on global file templates -merge_request: -author: -type: security diff --git a/changelogs/unreleased/54953-error-500-viewing-merge-request-due-to-nil-commit_email_hostname.yml b/changelogs/unreleased/54953-error-500-viewing-merge-request-due-to-nil-commit_email_hostname.yml new file mode 100644 index 00000000000..8fd127acf2b --- /dev/null +++ b/changelogs/unreleased/54953-error-500-viewing-merge-request-due-to-nil-commit_email_hostname.yml @@ -0,0 +1,5 @@ +--- +title: Return an ApplicationSetting in CurrentSettings +merge_request: 23766 +author: +type: fixed diff --git a/changelogs/unreleased/54975-fix-web-hooks-rake-task.yml b/changelogs/unreleased/54975-fix-web-hooks-rake-task.yml deleted file mode 100644 index 107a93e5b12..00000000000 --- a/changelogs/unreleased/54975-fix-web-hooks-rake-task.yml +++ /dev/null @@ -1,5 +0,0 @@ ---- -title: Fix gitlab:web_hook tasks -merge_request: 23635 -author: -type: fixed diff --git a/changelogs/unreleased/55104-frozenerror-can-t-modify-frozen-string.yml b/changelogs/unreleased/55104-frozenerror-can-t-modify-frozen-string.yml deleted file mode 100644 index 994859b1d1d..00000000000 --- a/changelogs/unreleased/55104-frozenerror-can-t-modify-frozen-string.yml +++ /dev/null @@ -1,5 +0,0 @@ ---- -title: Fix a frozen string error in app/mailers/notify.rb -merge_request: 23683 -author: -type: fixed diff --git a/changelogs/unreleased/55116-runtimeerror-can-t-modify-frozen-string.yml b/changelogs/unreleased/55116-runtimeerror-can-t-modify-frozen-string.yml deleted file mode 100644 index a98e70465b2..00000000000 --- a/changelogs/unreleased/55116-runtimeerror-can-t-modify-frozen-string.yml +++ /dev/null @@ -1,5 +0,0 @@ ---- -title: Fix a frozen string error in lib/gitlab/utils.rb -merge_request: 23690 -author: -type: fixed diff --git a/changelogs/unreleased/55138-fix-mr-discussions-count.yml b/changelogs/unreleased/55138-fix-mr-discussions-count.yml deleted file mode 100644 index 667e9b971d8..00000000000 --- a/changelogs/unreleased/55138-fix-mr-discussions-count.yml +++ /dev/null @@ -1,5 +0,0 @@ ---- -title: Fix MR resolved discussion counts being too low -merge_request: 23710 -author: -type: fixed diff --git a/changelogs/unreleased/55183-frozenerror-can-t-modify-frozen-string-in-app-mailers-notify-rb.yml b/changelogs/unreleased/55183-frozenerror-can-t-modify-frozen-string-in-app-mailers-notify-rb.yml deleted file mode 100644 index 685a8309c72..00000000000 --- a/changelogs/unreleased/55183-frozenerror-can-t-modify-frozen-string-in-app-mailers-notify-rb.yml +++ /dev/null @@ -1,5 +0,0 @@ ---- -title: Fix a potential frozen string error in app/mailers/notify.rb -merge_request: 23728 -author: -type: fixed diff --git a/changelogs/unreleased/55266-fix-incorrect-due-date-parsing.yml b/changelogs/unreleased/55266-fix-incorrect-due-date-parsing.yml new file mode 100644 index 00000000000..62a57085192 --- /dev/null +++ b/changelogs/unreleased/55266-fix-incorrect-due-date-parsing.yml @@ -0,0 +1,5 @@ +--- +title: Use 'parsePikadayDate' to parse due date string +merge_request: 24045 +author: +type: fixed diff --git a/changelogs/unreleased/55293-split-bio-into-individual-line-in-extended-user-tooltips.yml b/changelogs/unreleased/55293-split-bio-into-individual-line-in-extended-user-tooltips.yml new file mode 100644 index 00000000000..c6ff52b0fa1 --- /dev/null +++ b/changelogs/unreleased/55293-split-bio-into-individual-line-in-extended-user-tooltips.yml @@ -0,0 +1,5 @@ +--- +title: Split bio into individual line in extended user tooltips +merge_request: 23940 +author: +type: other diff --git a/changelogs/unreleased/55344-only-prompt-user-once-when-navigating-away-from-file-editor.yml b/changelogs/unreleased/55344-only-prompt-user-once-when-navigating-away-from-file-editor.yml new file mode 100644 index 00000000000..9c4d73c5323 --- /dev/null +++ b/changelogs/unreleased/55344-only-prompt-user-once-when-navigating-away-from-file-editor.yml @@ -0,0 +1,5 @@ +--- +title: Only prompt user once when navigating away from file editor +merge_request: 23820 +author: Sam Bigelow +type: fixed diff --git a/changelogs/unreleased/55369-update-milestone-sort-to-say-say-milestone-due-date.yml b/changelogs/unreleased/55369-update-milestone-sort-to-say-say-milestone-due-date.yml new file mode 100644 index 00000000000..7476b9caa93 --- /dev/null +++ b/changelogs/unreleased/55369-update-milestone-sort-to-say-say-milestone-due-date.yml @@ -0,0 +1,5 @@ +--- +title: Renames Milestone sort into Milestone due date +merge_request: 24080 +author: Jacopo Beschi @jacopo-beschi +type: changed diff --git a/changelogs/unreleased/55484-fix-edit-button.yml b/changelogs/unreleased/55484-fix-edit-button.yml new file mode 100644 index 00000000000..c8998cba248 --- /dev/null +++ b/changelogs/unreleased/55484-fix-edit-button.yml @@ -0,0 +1,4 @@ +title: Fix edit button disappearing in issue title +merge_request: 23948 +author: Ruben Moya +type: fixed diff --git a/changelogs/unreleased/55669-redesign-project-lists-ui-further-improvements.yml b/changelogs/unreleased/55669-redesign-project-lists-ui-further-improvements.yml new file mode 100644 index 00000000000..a51a08c892a --- /dev/null +++ b/changelogs/unreleased/55669-redesign-project-lists-ui-further-improvements.yml @@ -0,0 +1,5 @@ +--- +title: UI improvements for redesigned project lists +merge_request: 24011 +author: +type: other diff --git a/changelogs/unreleased/55716-update-cert-manager-chart-from-v0-5-0-to-v0-5-2.yml b/changelogs/unreleased/55716-update-cert-manager-chart-from-v0-5-0-to-v0-5-2.yml new file mode 100644 index 00000000000..a25ace9d76d --- /dev/null +++ b/changelogs/unreleased/55716-update-cert-manager-chart-from-v0-5-0-to-v0-5-2.yml @@ -0,0 +1,5 @@ +--- +title: Update cert-manager chart from v0.5.0 to v0.5.2 +merge_request: 24025 +author: Takuya Noguchi +type: other diff --git a/changelogs/unreleased/55755-user-activity-is-stuck-loading-when-there-is-none.yml b/changelogs/unreleased/55755-user-activity-is-stuck-loading-when-there-is-none.yml new file mode 100644 index 00000000000..5362a781281 --- /dev/null +++ b/changelogs/unreleased/55755-user-activity-is-stuck-loading-when-there-is-none.yml @@ -0,0 +1,5 @@ +--- +title: Hide spinner on empty activites list on user profile overview +merge_request: 24063 +author: +type: other diff --git a/changelogs/unreleased/55836-docs-fix-navigation-style-in-docs.yml b/changelogs/unreleased/55836-docs-fix-navigation-style-in-docs.yml new file mode 100644 index 00000000000..2ac3599175b --- /dev/null +++ b/changelogs/unreleased/55836-docs-fix-navigation-style-in-docs.yml @@ -0,0 +1,5 @@ +--- +title: Fix navigation style in docs +merge_request: 24090 +author: Takuya Noguchi +type: other diff --git a/changelogs/unreleased/55838-remove-gem-install-bundler-from-docker-based-ruby-environments.yml b/changelogs/unreleased/55838-remove-gem-install-bundler-from-docker-based-ruby-environments.yml new file mode 100644 index 00000000000..08f60d205df --- /dev/null +++ b/changelogs/unreleased/55838-remove-gem-install-bundler-from-docker-based-ruby-environments.yml @@ -0,0 +1,5 @@ +--- +title: Remove gem install bundler from Docker-based Ruby environments +merge_request: 24093 +author: Takuya Noguchi +type: other diff --git a/changelogs/unreleased/55883-modal-header-titles-have-an-unnecessary-top-margin.yml b/changelogs/unreleased/55883-modal-header-titles-have-an-unnecessary-top-margin.yml new file mode 100644 index 00000000000..7dc783cc2b8 --- /dev/null +++ b/changelogs/unreleased/55883-modal-header-titles-have-an-unnecessary-top-margin.yml @@ -0,0 +1,5 @@ +--- +title: Remove top margin in modal header titles +merge_request: 24108 +author: +type: fixed diff --git a/changelogs/unreleased/_acet-fix-flash-styling.yml b/changelogs/unreleased/_acet-fix-flash-styling.yml deleted file mode 100644 index 57354c04899..00000000000 --- a/changelogs/unreleased/_acet-fix-flash-styling.yml +++ /dev/null @@ -1,5 +0,0 @@ ---- -title: Fix flash notice styling for fluid layout -merge_request: 23382 -author: -type: fixed diff --git a/changelogs/unreleased/ab-approximate-counts.yml b/changelogs/unreleased/ab-approximate-counts.yml deleted file mode 100644 index 8a67239d031..00000000000 --- a/changelogs/unreleased/ab-approximate-counts.yml +++ /dev/null @@ -1,5 +0,0 @@ ---- -title: Approximate counting strategy with TABLESAMPLE. -merge_request: 22650 -author: -type: performance diff --git a/changelogs/unreleased/ac-releases-api.yml b/changelogs/unreleased/ac-releases-api.yml new file mode 100644 index 00000000000..87217cce371 --- /dev/null +++ b/changelogs/unreleased/ac-releases-api.yml @@ -0,0 +1,5 @@ +--- +title: Releases API +merge_request: 23795 +author: +type: added diff --git a/changelogs/unreleased/ac-releases-name-sha-author.yml b/changelogs/unreleased/ac-releases-name-sha-author.yml new file mode 100644 index 00000000000..e84b82847eb --- /dev/null +++ b/changelogs/unreleased/ac-releases-name-sha-author.yml @@ -0,0 +1,5 @@ +--- +title: Add name, author_id, and sha to releases table +merge_request: 23763 +author: +type: added diff --git a/changelogs/unreleased/add-new-nginx-metrics.yml b/changelogs/unreleased/add-new-nginx-metrics.yml new file mode 100644 index 00000000000..57221056d6e --- /dev/null +++ b/changelogs/unreleased/add-new-nginx-metrics.yml @@ -0,0 +1,5 @@ +--- +title: Add NGINX 0.16.0 and above metrics +merge_request: 22133 +author: +type: added diff --git a/changelogs/unreleased/added-glob-for-ci-changes-detection.yml b/changelogs/unreleased/added-glob-for-ci-changes-detection.yml deleted file mode 100644 index 887c6ef0346..00000000000 --- a/changelogs/unreleased/added-glob-for-ci-changes-detection.yml +++ /dev/null @@ -1,5 +0,0 @@ ---- -title: Added glob for CI changes detection -merge_request: 23128 -author: Kirill Zaitsev -type: added diff --git a/changelogs/unreleased/allow-basic-auth-on-go-get-middleware.yml b/changelogs/unreleased/allow-basic-auth-on-go-get-middleware.yml new file mode 100644 index 00000000000..fda3fdc28cf --- /dev/null +++ b/changelogs/unreleased/allow-basic-auth-on-go-get-middleware.yml @@ -0,0 +1,5 @@ +--- +title: Allow basic authentication on go get middleware +merge_request: 23497 +author: Morty Choi @mortyccp +type: changed diff --git a/changelogs/unreleased/allow_collaboration_status_work.yml b/changelogs/unreleased/allow_collaboration_status_work.yml new file mode 100644 index 00000000000..3cf8f13ffea --- /dev/null +++ b/changelogs/unreleased/allow_collaboration_status_work.yml @@ -0,0 +1,5 @@ +--- +title: Update a condition to visibility a merge request collaboration message +merge_request: 23104 +author: Harry Kiselev +type: other diff --git a/changelogs/unreleased/an-gitaly-version-0-133-0.yml b/changelogs/unreleased/an-gitaly-version-0-133-0.yml deleted file mode 100644 index 4f3943ceacb..00000000000 --- a/changelogs/unreleased/an-gitaly-version-0-133-0.yml +++ /dev/null @@ -1,5 +0,0 @@ ---- -title: Updated Gitaly to v0.133.0 -merge_request: 23148 -author: -type: other diff --git a/changelogs/unreleased/ashmckenzie-hmac-token-decode-and-tests.yml b/changelogs/unreleased/ashmckenzie-hmac-token-decode-and-tests.yml deleted file mode 100644 index d15c5654d99..00000000000 --- a/changelogs/unreleased/ashmckenzie-hmac-token-decode-and-tests.yml +++ /dev/null @@ -1,5 +0,0 @@ ---- -title: Relocate JSONWebToken::HMACToken from EE -merge_request: 22906 -author: -type: changed diff --git a/changelogs/unreleased/auto_devops_kubernetes_active.yml b/changelogs/unreleased/auto_devops_kubernetes_active.yml deleted file mode 100644 index 310d37128c9..00000000000 --- a/changelogs/unreleased/auto_devops_kubernetes_active.yml +++ /dev/null @@ -1,5 +0,0 @@ ---- -title: Switch kubernetes:active with checking in Auto-DevOps.gitlab-ci.yml -merge_request: 22929 -author: -type: fixed diff --git a/changelogs/unreleased/blackst0ne-add-discord-service.yml b/changelogs/unreleased/blackst0ne-add-discord-service.yml deleted file mode 100644 index 85dedf6d81f..00000000000 --- a/changelogs/unreleased/blackst0ne-add-discord-service.yml +++ /dev/null @@ -1,5 +0,0 @@ ---- -title: Add Discord integration -merge_request: 22684 -author: "@blackst0ne" -type: added diff --git a/changelogs/unreleased/blackst0ne-bump-rails-cve-2018-16476.yml b/changelogs/unreleased/blackst0ne-bump-rails-cve-2018-16476.yml new file mode 100644 index 00000000000..dfa94c69ce0 --- /dev/null +++ b/changelogs/unreleased/blackst0ne-bump-rails-cve-2018-16476.yml @@ -0,0 +1,5 @@ +--- +title: Bump Ruby on Rails to 5.0.7.1 +merge_request: 23396 +author: "@blackst0ne" +type: security diff --git a/changelogs/unreleased/blackst0ne-convert-specs-rails5-style.yml b/changelogs/unreleased/blackst0ne-convert-specs-rails5-style.yml new file mode 100644 index 00000000000..c29cfec075c --- /dev/null +++ b/changelogs/unreleased/blackst0ne-convert-specs-rails5-style.yml @@ -0,0 +1,5 @@ +--- +title: "[Rails5.1] Update functional specs to use new keyword format" +merge_request: 23095 +author: "@blackst0ne" +type: other diff --git a/changelogs/unreleased/blackst0ne-improve-encoding-helper-spec.yml b/changelogs/unreleased/blackst0ne-improve-encoding-helper-spec.yml new file mode 100644 index 00000000000..09480499b87 --- /dev/null +++ b/changelogs/unreleased/blackst0ne-improve-encoding-helper-spec.yml @@ -0,0 +1,5 @@ +--- +title: Update specs to exclude possible false positive pass +merge_request: 23893 +author: "@blackst0ne" +type: other diff --git a/changelogs/unreleased/bump_gpgme_gem.yml b/changelogs/unreleased/bump_gpgme_gem.yml deleted file mode 100644 index 4c0067cb824..00000000000 --- a/changelogs/unreleased/bump_gpgme_gem.yml +++ /dev/null @@ -1,5 +0,0 @@ ---- -title: Bump gpgme gem version from 2.0.13 to 2.0.18 -merge_request: -author: asaparov -type: other diff --git a/changelogs/unreleased/bvl-hide-confidential-events-take2.yml b/changelogs/unreleased/bvl-hide-confidential-events-take2.yml new file mode 100644 index 00000000000..a5abd496a9d --- /dev/null +++ b/changelogs/unreleased/bvl-hide-confidential-events-take2.yml @@ -0,0 +1,5 @@ +--- +title: Hide confidential events in the API +merge_request: 23746 +author: +type: other diff --git a/changelogs/unreleased/bvl-use-shell-writeref.yml b/changelogs/unreleased/bvl-use-shell-writeref.yml deleted file mode 100644 index 682d428e8c5..00000000000 --- a/changelogs/unreleased/bvl-use-shell-writeref.yml +++ /dev/null @@ -1,5 +0,0 @@ ---- -title: Avoid creating invalid refs using rugged, shelling out for writing refs -merge_request: 23286 -author: -type: fixed diff --git a/changelogs/unreleased/ccr-49289_milestone_link.yml b/changelogs/unreleased/ccr-49289_milestone_link.yml new file mode 100644 index 00000000000..14c09752a24 --- /dev/null +++ b/changelogs/unreleased/ccr-49289_milestone_link.yml @@ -0,0 +1,5 @@ +--- +title: Add project milestone link +merge_request: 22552 +author: +type: added diff --git a/changelogs/unreleased/ce-52811-fix_namespaces_api_routing.yml b/changelogs/unreleased/ce-52811-fix_namespaces_api_routing.yml deleted file mode 100644 index b5fd99c304f..00000000000 --- a/changelogs/unreleased/ce-52811-fix_namespaces_api_routing.yml +++ /dev/null @@ -1,5 +0,0 @@ ---- -title: Fix API::Namespaces routing to accept namepaces with dots -merge_request: 22912 -author: -type: fixed diff --git a/changelogs/unreleased/ce-54109-fix_user_by_any_email.yml b/changelogs/unreleased/ce-54109-fix_user_by_any_email.yml deleted file mode 100644 index eb5d2e3244c..00000000000 --- a/changelogs/unreleased/ce-54109-fix_user_by_any_email.yml +++ /dev/null @@ -1,5 +0,0 @@ ---- -title: Respect confirmed flag on secondary emails -merge_request: 23181 -author: -type: fixed diff --git a/changelogs/unreleased/cert-manager-email.yml b/changelogs/unreleased/cert-manager-email.yml deleted file mode 100644 index 530608d9660..00000000000 --- a/changelogs/unreleased/cert-manager-email.yml +++ /dev/null @@ -1,5 +0,0 @@ ---- -title: Ability to override email for cert-manager -merge_request: 23503 -author: Amit Rathi -type: added diff --git a/changelogs/unreleased/certmanager-temp.yml b/changelogs/unreleased/certmanager-temp.yml deleted file mode 100644 index 3f908d01c9f..00000000000 --- a/changelogs/unreleased/certmanager-temp.yml +++ /dev/null @@ -1,5 +0,0 @@ ---- -title: "#40635: Adds support for cert-manager" -merge_request: 23036 -author: Amit Rathi -type: added diff --git a/changelogs/unreleased/check-if-fetched-data-does-is-complete.yml b/changelogs/unreleased/check-if-fetched-data-does-is-complete.yml deleted file mode 100644 index 31c131045b9..00000000000 --- a/changelogs/unreleased/check-if-fetched-data-does-is-complete.yml +++ /dev/null @@ -1,5 +0,0 @@ ---- -title: Validate chunk size when persist -merge_request: 23341 -author: -type: fixed diff --git a/changelogs/unreleased/ci-dropdown-hidden-bug.yml b/changelogs/unreleased/ci-dropdown-hidden-bug.yml new file mode 100644 index 00000000000..6910f04a6d5 --- /dev/null +++ b/changelogs/unreleased/ci-dropdown-hidden-bug.yml @@ -0,0 +1,5 @@ +--- +title: Don't hide CI dropdown behind diff summary +merge_request: +author: gfyoung +type: fixed diff --git a/changelogs/unreleased/commit-badge-style-fix.yml b/changelogs/unreleased/commit-badge-style-fix.yml deleted file mode 100644 index d7b37717853..00000000000 --- a/changelogs/unreleased/commit-badge-style-fix.yml +++ /dev/null @@ -1,5 +0,0 @@ ---- -title: Fixed styling of image comment badges on commits -merge_request: -author: -type: fixed diff --git a/changelogs/unreleased/define-default-value-for-only-except-keys.yml b/changelogs/unreleased/define-default-value-for-only-except-keys.yml deleted file mode 100644 index 3e5ecdcf51e..00000000000 --- a/changelogs/unreleased/define-default-value-for-only-except-keys.yml +++ /dev/null @@ -1,5 +0,0 @@ ---- -title: Define the default value for only/except policies -merge_request: 23531 -author: -type: changed diff --git a/changelogs/unreleased/depracated-migration-inheritance.yml b/changelogs/unreleased/depracated-migration-inheritance.yml new file mode 100644 index 00000000000..1ea9b2df59c --- /dev/null +++ b/changelogs/unreleased/depracated-migration-inheritance.yml @@ -0,0 +1,5 @@ +--- +title: ActiveRecord::Migration -> ActiveRecord::Migration[5.0] +merge_request: 23910 +author: Jasper Maes +type: other diff --git a/changelogs/unreleased/deprecated-actiondispatch-paramsparser.yml b/changelogs/unreleased/deprecated-actiondispatch-paramsparser.yml new file mode 100644 index 00000000000..9cfb00a9544 --- /dev/null +++ b/changelogs/unreleased/deprecated-actiondispatch-paramsparser.yml @@ -0,0 +1,5 @@ +--- +title: Remove deprecated ActionDispatch::ParamsParser +merge_request: 23848 +author: Jasper Maes +type: other diff --git a/changelogs/unreleased/deprecated-alias-method-chain.yml b/changelogs/unreleased/deprecated-alias-method-chain.yml new file mode 100644 index 00000000000..76dd016e4cc --- /dev/null +++ b/changelogs/unreleased/deprecated-alias-method-chain.yml @@ -0,0 +1,6 @@ +--- +title: 'Fix deprecation: alias_method_chain is deprecated. Please, use Module#prepend + instead' +merge_request: 23887 +author: Jasper Maes +type: other diff --git a/changelogs/unreleased/deprecated-comparing-actioncontroller-params-hash.yml b/changelogs/unreleased/deprecated-comparing-actioncontroller-params-hash.yml new file mode 100644 index 00000000000..a7b9d054a4c --- /dev/null +++ b/changelogs/unreleased/deprecated-comparing-actioncontroller-params-hash.yml @@ -0,0 +1,6 @@ +--- +title: 'Fix deprecation: Comparing equality between ActionController::Parameters and + a Hash is deprecated' +merge_request: 23855 +author: Jasper Maes +type: other diff --git a/changelogs/unreleased/deprecated-delete-all-params.yml b/changelogs/unreleased/deprecated-delete-all-params.yml new file mode 100644 index 00000000000..e23fe92a738 --- /dev/null +++ b/changelogs/unreleased/deprecated-delete-all-params.yml @@ -0,0 +1,5 @@ +--- +title: 'Fix deprecation: Passing conditions to delete_all is deprecated' +merge_request: 23817 +author: Jasper Maes +type: other diff --git a/changelogs/unreleased/deprecated-directly-inheriting-migration.yml b/changelogs/unreleased/deprecated-directly-inheriting-migration.yml new file mode 100644 index 00000000000..2793cc0d44f --- /dev/null +++ b/changelogs/unreleased/deprecated-directly-inheriting-migration.yml @@ -0,0 +1,5 @@ +--- +title: 'Fix deprecation: Directly inheriting from ActiveRecord::Migration is deprecated.' +merge_request: 23884 +author: Jasper Maes +type: other diff --git a/changelogs/unreleased/deprecated-insert-sql.yml b/changelogs/unreleased/deprecated-insert-sql.yml new file mode 100644 index 00000000000..ad21fbd9dde --- /dev/null +++ b/changelogs/unreleased/deprecated-insert-sql.yml @@ -0,0 +1,5 @@ +--- +title: 'Fix deprecation: insert_sql is deprecated and will be removed' +merge_request: 23944 +author: Jasper Maes +type: other diff --git a/changelogs/unreleased/deprecated-instance-find.yml b/changelogs/unreleased/deprecated-instance-find.yml deleted file mode 100644 index d2ba821e124..00000000000 --- a/changelogs/unreleased/deprecated-instance-find.yml +++ /dev/null @@ -1,5 +0,0 @@ ---- -title: 'Fix deprecation: You are passing an instance of ActiveRecord::Base to' -merge_request: 23369 -author: Jasper Maes -type: other diff --git a/changelogs/unreleased/deprecated-passing-activerecord-objects.yml b/changelogs/unreleased/deprecated-passing-activerecord-objects.yml new file mode 100644 index 00000000000..e58647186b8 --- /dev/null +++ b/changelogs/unreleased/deprecated-passing-activerecord-objects.yml @@ -0,0 +1,5 @@ +--- +title: 'Fix deprecation: Passing ActiveRecord::Base objects to sanitize_sql_hash_for_assignment' +merge_request: 23818 +author: Jasper Maes +type: other diff --git a/changelogs/unreleased/deprecated-positional-seperator-parameter.yml b/changelogs/unreleased/deprecated-positional-seperator-parameter.yml new file mode 100644 index 00000000000..0d952e0d5eb --- /dev/null +++ b/changelogs/unreleased/deprecated-positional-seperator-parameter.yml @@ -0,0 +1,5 @@ +--- +title: Passing the separator argument as a positional parameter is deprecated +merge_request: 23334 +author: Jasper Maes +type: other diff --git a/changelogs/unreleased/deprecated-positional-spec-arguments.yml b/changelogs/unreleased/deprecated-positional-spec-arguments.yml new file mode 100644 index 00000000000..8e541df1ad4 --- /dev/null +++ b/changelogs/unreleased/deprecated-positional-spec-arguments.yml @@ -0,0 +1,5 @@ +--- +title: 'Fix deprecation: Using positional arguments in integration tests' +merge_request: 24110 +author: Jasper Maes +type: other diff --git a/changelogs/unreleased/deprecated-redirect-back.yml b/changelogs/unreleased/deprecated-redirect-back.yml new file mode 100644 index 00000000000..7fc567fbdb5 --- /dev/null +++ b/changelogs/unreleased/deprecated-redirect-back.yml @@ -0,0 +1,5 @@ +--- +title: 'Fix deprecation: redirect_to :back is deprecated' +merge_request: 23943 +author: Jasper Maes +type: other diff --git a/changelogs/unreleased/diff-empty-state-fixes.yml b/changelogs/unreleased/diff-empty-state-fixes.yml new file mode 100644 index 00000000000..0d347dd17e4 --- /dev/null +++ b/changelogs/unreleased/diff-empty-state-fixes.yml @@ -0,0 +1,5 @@ +--- +title: Fixed merge request diffs empty states +merge_request: +author: +type: fixed diff --git a/changelogs/unreleased/diff-expand-commit-file.yml b/changelogs/unreleased/diff-expand-commit-file.yml deleted file mode 100644 index 8ca784d75c1..00000000000 --- a/changelogs/unreleased/diff-expand-commit-file.yml +++ /dev/null @@ -1,5 +0,0 @@ ---- -title: Fixed diff files expanding not loading commit content -merge_request: -author: -type: fixed diff --git a/changelogs/unreleased/diff-fix-expanding.yml b/changelogs/unreleased/diff-fix-expanding.yml deleted file mode 100644 index 8ba7f87addc..00000000000 --- a/changelogs/unreleased/diff-fix-expanding.yml +++ /dev/null @@ -1,5 +0,0 @@ ---- -title: Fixed multiple diff line discussions not expanding -merge_request: -author: -type: fixed diff --git a/changelogs/unreleased/discussion-perf-improvement.yml b/changelogs/unreleased/discussion-perf-improvement.yml deleted file mode 100644 index defff8a55f5..00000000000 --- a/changelogs/unreleased/discussion-perf-improvement.yml +++ /dev/null @@ -1,5 +0,0 @@ ---- -title: Improve initial discussion rendering performance -merge_request: 22607 -author: -type: changed diff --git a/changelogs/unreleased/dm-batch-loader-key.yml b/changelogs/unreleased/dm-batch-loader-key.yml deleted file mode 100644 index 047fdbc4b3f..00000000000 --- a/changelogs/unreleased/dm-batch-loader-key.yml +++ /dev/null @@ -1,5 +0,0 @@ ---- -title: Batch load only data from same repository when lazy object is accessed -merge_request: 23309 -author: -type: performance diff --git a/changelogs/unreleased/dm-note-email-image-diff-discussion.yml b/changelogs/unreleased/dm-note-email-image-diff-discussion.yml new file mode 100644 index 00000000000..6532052e132 --- /dev/null +++ b/changelogs/unreleased/dm-note-email-image-diff-discussion.yml @@ -0,0 +1,5 @@ +--- +title: Fix notification email for image diff notes +merge_request: +author: +type: fixed diff --git a/changelogs/unreleased/dm-remove-prune-web-hook-logs-worker.yml b/changelogs/unreleased/dm-remove-prune-web-hook-logs-worker.yml deleted file mode 100644 index fb0c508400c..00000000000 --- a/changelogs/unreleased/dm-remove-prune-web-hook-logs-worker.yml +++ /dev/null @@ -1,5 +0,0 @@ ---- -title: Remove old webhook logs after 90 days, as documented, instead of after 2 -merge_request: -author: -type: fixed diff --git a/changelogs/unreleased/docs-minor-aws-fixes.yml b/changelogs/unreleased/docs-minor-aws-fixes.yml deleted file mode 100644 index 64fa6b12afe..00000000000 --- a/changelogs/unreleased/docs-minor-aws-fixes.yml +++ /dev/null @@ -1,5 +0,0 @@ ---- -title: Fixes to AWS documentation spelling and grammar -merge_request: 23198 -author: Brendan O'Leary -type: other diff --git a/changelogs/unreleased/document-raw-snippet-api.yml b/changelogs/unreleased/document-raw-snippet-api.yml deleted file mode 100644 index 3b8818cea5c..00000000000 --- a/changelogs/unreleased/document-raw-snippet-api.yml +++ /dev/null @@ -1,5 +0,0 @@ ---- -title: Fix lack of documentation on how to fetch a snippet's content using API -merge_request: 23448 -author: Colin Leroy -type: other diff --git a/changelogs/unreleased/drop-default-value-status-deployments.yml b/changelogs/unreleased/drop-default-value-status-deployments.yml deleted file mode 100644 index fdb826a0507..00000000000 --- a/changelogs/unreleased/drop-default-value-status-deployments.yml +++ /dev/null @@ -1,5 +0,0 @@ ---- -title: Drop default value on status column in deployments table -merge_request: 22971 -author: -type: other diff --git a/changelogs/unreleased/drop-gcp-cluster-table.yml b/changelogs/unreleased/drop-gcp-cluster-table.yml deleted file mode 100644 index 15964ec2eaf..00000000000 --- a/changelogs/unreleased/drop-gcp-cluster-table.yml +++ /dev/null @@ -1,5 +0,0 @@ ---- -title: Drop gcp_clusters table -merge_request: 22713 -author: -type: other diff --git a/changelogs/unreleased/expose-mr-pipeline-variables.yml b/changelogs/unreleased/expose-mr-pipeline-variables.yml deleted file mode 100644 index b77b9a69d5c..00000000000 --- a/changelogs/unreleased/expose-mr-pipeline-variables.yml +++ /dev/null @@ -1,5 +0,0 @@ ---- -title: Expose merge request pipeline variables -merge_request: 23398 -author: -type: changed diff --git a/changelogs/unreleased/feature-option-to-make-variables-protected.yml b/changelogs/unreleased/feature-option-to-make-variables-protected.yml new file mode 100644 index 00000000000..c99c0481c35 --- /dev/null +++ b/changelogs/unreleased/feature-option-to-make-variables-protected.yml @@ -0,0 +1,5 @@ +--- +title: Add option to make ci variables protected by default +merge_request: 22744 +author: Alexis Reigel +type: added diff --git a/changelogs/unreleased/fix-55448.yml b/changelogs/unreleased/fix-55448.yml new file mode 100644 index 00000000000..e0bdbb6eda4 --- /dev/null +++ b/changelogs/unreleased/fix-55448.yml @@ -0,0 +1,5 @@ +--- +title: Remove deprecated xhr from specs +merge_request: 23949 +author: Jasper Maes +type: other diff --git a/changelogs/unreleased/fix-calendar-events-fetching-error.yml b/changelogs/unreleased/fix-calendar-events-fetching-error.yml new file mode 100644 index 00000000000..ad4a40cd9a0 --- /dev/null +++ b/changelogs/unreleased/fix-calendar-events-fetching-error.yml @@ -0,0 +1,5 @@ +--- +title: Fix calendar events fetching error on private profile page +merge_request: 23718 +author: Harry Kiselev +type: other diff --git a/changelogs/unreleased/fix-deadlock-chunked-io.yml b/changelogs/unreleased/fix-deadlock-chunked-io.yml deleted file mode 100644 index def7a59e86e..00000000000 --- a/changelogs/unreleased/fix-deadlock-chunked-io.yml +++ /dev/null @@ -1,5 +0,0 @@ ---- -title: Fix deadlock on ChunkedIO -merge_request: -author: -type: fixed diff --git a/changelogs/unreleased/fix-gb-encrypt-ci-build-token.yml b/changelogs/unreleased/fix-gb-encrypt-ci-build-token.yml deleted file mode 100644 index 04fc88bc3d3..00000000000 --- a/changelogs/unreleased/fix-gb-encrypt-ci-build-token.yml +++ /dev/null @@ -1,5 +0,0 @@ ---- -title: Encrypt CI/CD builds authentication tokens -merge_request: 23436 -author: -type: security diff --git a/changelogs/unreleased/fix-gb-encrypt-runners-tokens.yml b/changelogs/unreleased/fix-gb-encrypt-runners-tokens.yml deleted file mode 100644 index 4ce4f96c1dd..00000000000 --- a/changelogs/unreleased/fix-gb-encrypt-runners-tokens.yml +++ /dev/null @@ -1,5 +0,0 @@ ---- -title: Encrypt runners tokens -merge_request: 23412 -author: -type: security diff --git a/changelogs/unreleased/fix-gb-improve-timeout-inputs-help-sections.yml b/changelogs/unreleased/fix-gb-improve-timeout-inputs-help-sections.yml deleted file mode 100644 index 52b431edf2c..00000000000 --- a/changelogs/unreleased/fix-gb-improve-timeout-inputs-help-sections.yml +++ /dev/null @@ -1,5 +0,0 @@ ---- -title: Improve help and validation sections of maximum build timeout inputs -merge_request: 23586 -author: -type: fixed diff --git a/changelogs/unreleased/fix-mr-widget-unrelated-deployment-status.yml b/changelogs/unreleased/fix-mr-widget-unrelated-deployment-status.yml deleted file mode 100644 index ab926fbd43b..00000000000 --- a/changelogs/unreleased/fix-mr-widget-unrelated-deployment-status.yml +++ /dev/null @@ -1,5 +0,0 @@ ---- -title: Fix unrelated deployment status in MR widget -merge_request: 23175 -author: -type: fixed diff --git a/changelogs/unreleased/fix-multiple-comments-shade-overlap.yml b/changelogs/unreleased/fix-multiple-comments-shade-overlap.yml deleted file mode 100644 index 20005ba355e..00000000000 --- a/changelogs/unreleased/fix-multiple-comments-shade-overlap.yml +++ /dev/null @@ -1,5 +0,0 @@ ---- -title: Fix multiple commits shade overlapping vertical discussion line -merge_request: 23515 -author: -type: fixed diff --git a/changelogs/unreleased/fj-44679-skip-per-commit-validations.yml b/changelogs/unreleased/fj-44679-skip-per-commit-validations.yml new file mode 100644 index 00000000000..3f9754409df --- /dev/null +++ b/changelogs/unreleased/fj-44679-skip-per-commit-validations.yml @@ -0,0 +1,5 @@ +--- +title: Skip per-commit validations already evaluated +merge_request: 23984 +author: +type: performance diff --git a/changelogs/unreleased/fj-47494-upgrade-git-to-2-18-0.yml b/changelogs/unreleased/fj-47494-upgrade-git-to-2-18-0.yml deleted file mode 100644 index 0f01552ff7e..00000000000 --- a/changelogs/unreleased/fj-47494-upgrade-git-to-2-18-0.yml +++ /dev/null @@ -1,5 +0,0 @@ ---- -title: Upgrade minimum required Git version to 2.18.0 -merge_request: 22803 -author: -type: other diff --git a/changelogs/unreleased/fj-clean-content-headers.yml b/changelogs/unreleased/fj-clean-content-headers.yml deleted file mode 100644 index 59e25ca6578..00000000000 --- a/changelogs/unreleased/fj-clean-content-headers.yml +++ /dev/null @@ -1,5 +0,0 @@ ---- -title: Added feature flag to signal content headers detection by Workhorse -merge_request: 22667 -author: -type: added diff --git a/changelogs/unreleased/fj-fix-lfs-image-comments-diffs.yml b/changelogs/unreleased/fj-fix-lfs-image-comments-diffs.yml new file mode 100644 index 00000000000..dc1fe5d7417 --- /dev/null +++ b/changelogs/unreleased/fj-fix-lfs-image-comments-diffs.yml @@ -0,0 +1,5 @@ +--- +title: Fix bug commenting on LFS images +merge_request: 23812 +author: +type: fixed diff --git a/changelogs/unreleased/fj-force-content-disposition.yml b/changelogs/unreleased/fj-force-content-disposition.yml deleted file mode 100644 index d84555a489f..00000000000 --- a/changelogs/unreleased/fj-force-content-disposition.yml +++ /dev/null @@ -1,5 +0,0 @@ ---- -title: Force content disposition attachment to several endpoints -merge_request: 23223 -author: -type: other diff --git a/changelogs/unreleased/force-reload-arguments-1.yml b/changelogs/unreleased/force-reload-arguments-2.yml index 29f34b8bdbe..23ab9433b3d 100644 --- a/changelogs/unreleased/force-reload-arguments-1.yml +++ b/changelogs/unreleased/force-reload-arguments-2.yml @@ -1,5 +1,5 @@ --- title: Passing an argument to force an association to reload is now deprecated -merge_request: 23334 +merge_request: 23894 author: Jasper Maes type: other diff --git a/changelogs/unreleased/frozen-string-lib-gitlab-even-even-even-more.yml b/changelogs/unreleased/frozen-string-lib-gitlab-even-even-even-more.yml deleted file mode 100644 index e718d716647..00000000000 --- a/changelogs/unreleased/frozen-string-lib-gitlab-even-even-even-more.yml +++ /dev/null @@ -1,5 +0,0 @@ ---- -title: Enable even more frozen string for lib/gitlab -merge_request: -author: gfyoung -type: performance diff --git a/changelogs/unreleased/frozen-string-lib-gitlab-even-even-more.yml b/changelogs/unreleased/frozen-string-lib-gitlab-even-even-more.yml deleted file mode 100644 index e718d716647..00000000000 --- a/changelogs/unreleased/frozen-string-lib-gitlab-even-even-more.yml +++ /dev/null @@ -1,5 +0,0 @@ ---- -title: Enable even more frozen string for lib/gitlab -merge_request: -author: gfyoung -type: performance diff --git a/changelogs/unreleased/frozen-string-lib-gitlab-even-more.yml b/changelogs/unreleased/frozen-string-lib-gitlab-even-more.yml deleted file mode 100644 index cfbc4ced635..00000000000 --- a/changelogs/unreleased/frozen-string-lib-gitlab-even-more.yml +++ /dev/null @@ -1,5 +0,0 @@ ---- -title: Enable even more frozen string in lib/gitlab/**/*.rb -merge_request: -author: gfyoung -type: performance diff --git a/changelogs/unreleased/frozen-string-lib-gitlab-more.yml b/changelogs/unreleased/frozen-string-lib-gitlab-more.yml deleted file mode 100644 index cfbc4ced635..00000000000 --- a/changelogs/unreleased/frozen-string-lib-gitlab-more.yml +++ /dev/null @@ -1,5 +0,0 @@ ---- -title: Enable even more frozen string in lib/gitlab/**/*.rb -merge_request: -author: gfyoung -type: performance diff --git a/changelogs/unreleased/frozen-string-lib-rubocop.yml b/changelogs/unreleased/frozen-string-lib-rubocop.yml deleted file mode 100644 index 9fe342e251b..00000000000 --- a/changelogs/unreleased/frozen-string-lib-rubocop.yml +++ /dev/null @@ -1,5 +0,0 @@ ---- -title: Enable Rubocop on lib/gitlab -merge_request: -author: gfyoung -type: other diff --git a/changelogs/unreleased/gt-add-top-padding-for-nested-environment-items-loading-icon.yml b/changelogs/unreleased/gt-add-top-padding-for-nested-environment-items-loading-icon.yml deleted file mode 100644 index 606314b5780..00000000000 --- a/changelogs/unreleased/gt-add-top-padding-for-nested-environment-items-loading-icon.yml +++ /dev/null @@ -1,5 +0,0 @@ ---- -title: Add top padding for nested environment items loading icon -merge_request: 23580 -author: George Tsiolis -type: fixed diff --git a/changelogs/unreleased/gt-align-issue-status-and-confidential-icon.yml b/changelogs/unreleased/gt-align-issue-status-and-confidential-icon.yml deleted file mode 100644 index 481ce656dc7..00000000000 --- a/changelogs/unreleased/gt-align-issue-status-and-confidential-icon.yml +++ /dev/null @@ -1,5 +0,0 @@ ---- -title: Align issue status label and confidential icon. -merge_request: 23046 -author: George Tsiolis -type: fixed diff --git a/changelogs/unreleased/gt-change-breadcrumb-title-for-contribution-charts.yml b/changelogs/unreleased/gt-change-breadcrumb-title-for-contribution-charts.yml deleted file mode 100644 index 233cc43117d..00000000000 --- a/changelogs/unreleased/gt-change-breadcrumb-title-for-contribution-charts.yml +++ /dev/null @@ -1,5 +0,0 @@ ---- -title: Change breadcrumb title for contribution charts -merge_request: 23071 -author: George Tsiolis -type: changed diff --git a/changelogs/unreleased/gt-change-container-width-for-project-import.yml b/changelogs/unreleased/gt-change-container-width-for-project-import.yml deleted file mode 100644 index ec2beb15912..00000000000 --- a/changelogs/unreleased/gt-change-container-width-for-project-import.yml +++ /dev/null @@ -1,5 +0,0 @@ ---- -title: Change container width for project import -merge_request: 23318 -author: George Tsiolis -type: fixed diff --git a/changelogs/unreleased/gt-externalize-app-views-invites.yml b/changelogs/unreleased/gt-externalize-app-views-invites.yml deleted file mode 100644 index b5a22177f9b..00000000000 --- a/changelogs/unreleased/gt-externalize-app-views-invites.yml +++ /dev/null @@ -1,5 +0,0 @@ ---- -title: Externalize strings from `/app/views/invites` -merge_request: 23205 -author: Tao Wang -type: other diff --git a/changelogs/unreleased/gt-externalize-app-views-project-runners.yml b/changelogs/unreleased/gt-externalize-app-views-project-runners.yml deleted file mode 100644 index d7d591e2175..00000000000 --- a/changelogs/unreleased/gt-externalize-app-views-project-runners.yml +++ /dev/null @@ -1,5 +0,0 @@ ---- -title: Externalize strings from `/app/views/project/runners` -merge_request: 23208 -author: Tao Wang -type: other diff --git a/changelogs/unreleased/gt-externalize-app-views-snippets.yml b/changelogs/unreleased/gt-externalize-app-views-snippets.yml deleted file mode 100644 index 633aa9f2534..00000000000 --- a/changelogs/unreleased/gt-externalize-app-views-snippets.yml +++ /dev/null @@ -1,5 +0,0 @@ ---- -title: Externalize strings from `/app/views/snippets` -merge_request: 23351 -author: Tao Wang -type: other diff --git a/changelogs/unreleased/gt-fix-typo-in-notebook-props.yml b/changelogs/unreleased/gt-fix-typo-in-notebook-props.yml deleted file mode 100644 index 60603905a2d..00000000000 --- a/changelogs/unreleased/gt-fix-typo-in-notebook-props.yml +++ /dev/null @@ -1,5 +0,0 @@ ---- -title: Fix typo in notebook props -merge_request: 23103 -author: George Tsiolis -type: other diff --git a/changelogs/unreleased/gt-fix-typos-in-lib.yml b/changelogs/unreleased/gt-fix-typos-in-lib.yml deleted file mode 100644 index 32ccd03b063..00000000000 --- a/changelogs/unreleased/gt-fix-typos-in-lib.yml +++ /dev/null @@ -1,5 +0,0 @@ ---- -title: Fix typos in lib -merge_request: 23106 -author: George Tsiolis -type: other diff --git a/changelogs/unreleased/gt-remove-instances-of-extend-monospace.yml b/changelogs/unreleased/gt-remove-instances-of-extend-monospace.yml deleted file mode 100644 index dc41de61046..00000000000 --- a/changelogs/unreleased/gt-remove-instances-of-extend-monospace.yml +++ /dev/null @@ -1,5 +0,0 @@ ---- -title: Remove monospace extend -merge_request: 23089 -author: George Tsiolis -type: performance diff --git a/changelogs/unreleased/gt-remove-unused-project-method.yml b/changelogs/unreleased/gt-remove-unused-project-method.yml deleted file mode 100644 index 2d60c2fe423..00000000000 --- a/changelogs/unreleased/gt-remove-unused-project-method.yml +++ /dev/null @@ -1,5 +0,0 @@ ---- -title: Remove unused project method -merge_request: 54103 -author: George Tsiolis -type: other diff --git a/changelogs/unreleased/gt-rename-diffs-store-variable.yml b/changelogs/unreleased/gt-rename-diffs-store-variable.yml deleted file mode 100644 index 0aed49f3d60..00000000000 --- a/changelogs/unreleased/gt-rename-diffs-store-variable.yml +++ /dev/null @@ -1,5 +0,0 @@ ---- -title: Rename diffs store variable -merge_request: 23123 -author: George Tsiolis -type: other diff --git a/changelogs/unreleased/gt-reorder-group-sidebar-menu-items.yml b/changelogs/unreleased/gt-reorder-group-sidebar-menu-items.yml new file mode 100644 index 00000000000..b1ecf2bb1ed --- /dev/null +++ b/changelogs/unreleased/gt-reorder-group-sidebar-menu-items.yml @@ -0,0 +1,5 @@ +--- +title: Reorder sidebar menu item for group clusters +merge_request: 24001 +author: George Tsiolis +type: changed diff --git a/changelogs/unreleased/gt-show-primary-button-when-all-labels-are-prioritized.yml b/changelogs/unreleased/gt-show-primary-button-when-all-labels-are-prioritized.yml deleted file mode 100644 index eed31950a76..00000000000 --- a/changelogs/unreleased/gt-show-primary-button-when-all-labels-are-prioritized.yml +++ /dev/null @@ -1,5 +0,0 @@ ---- -title: Show primary button when all labels are prioritized -merge_request: 23648 -author: George Tsiolis -type: other diff --git a/changelogs/unreleased/gt-update-env-metrics-empty-state.yml b/changelogs/unreleased/gt-update-env-metrics-empty-state.yml deleted file mode 100644 index a05dc07e65c..00000000000 --- a/changelogs/unreleased/gt-update-env-metrics-empty-state.yml +++ /dev/null @@ -1,5 +0,0 @@ ---- -title: Update environments metrics empty state -merge_request: 23074 -author: George Tsiolis -type: changed diff --git a/changelogs/unreleased/gt-update-environment-breadcrumb.yml b/changelogs/unreleased/gt-update-environment-breadcrumb.yml new file mode 100644 index 00000000000..53b9673a96c --- /dev/null +++ b/changelogs/unreleased/gt-update-environment-breadcrumb.yml @@ -0,0 +1,5 @@ +--- +title: Update environments breadcrumb +merge_request: 23751 +author: George Tsiolis +type: changed diff --git a/changelogs/unreleased/gt-update-navigation-theme-colors.yml b/changelogs/unreleased/gt-update-navigation-theme-colors.yml new file mode 100644 index 00000000000..749587a6343 --- /dev/null +++ b/changelogs/unreleased/gt-update-navigation-theme-colors.yml @@ -0,0 +1,5 @@ +--- +title: Update header navigation theme colors +merge_request: 23734 +author: George Tsiolis +type: fixed diff --git a/changelogs/unreleased/gt-use-gl-tooltip-directive.yml b/changelogs/unreleased/gt-use-gl-tooltip-directive.yml deleted file mode 100644 index 91fdb73e3c6..00000000000 --- a/changelogs/unreleased/gt-use-gl-tooltip-directive.yml +++ /dev/null @@ -1,5 +0,0 @@ ---- -title: Replace tooltip directive with gl-tooltip diretive in badges, cycle analytics, and diffs -merge_request: 22770 -author: George Tsiolis -type: performance diff --git a/changelogs/unreleased/ide-open-all-mr-files.yml b/changelogs/unreleased/ide-open-all-mr-files.yml deleted file mode 100644 index 6a5ea8908fc..00000000000 --- a/changelogs/unreleased/ide-open-all-mr-files.yml +++ /dev/null @@ -1,5 +0,0 @@ ---- -title: Open first 10 merge request files in IDE -merge_request: -author: -type: fixed diff --git a/changelogs/unreleased/ignore-failed-pipeline-creation-on-pipeline-schedule.yml b/changelogs/unreleased/ignore-failed-pipeline-creation-on-pipeline-schedule.yml deleted file mode 100644 index 90f47aa12db..00000000000 --- a/changelogs/unreleased/ignore-failed-pipeline-creation-on-pipeline-schedule.yml +++ /dev/null @@ -1,5 +0,0 @@ ---- -title: Remove auto deactivation when failed to create a pipeline via pipeline schedules -merge_request: 22243 -author: -type: changed diff --git a/changelogs/unreleased/improve_auto_devops_migration_debug.yml b/changelogs/unreleased/improve_auto_devops_migration_debug.yml deleted file mode 100644 index 96a78808361..00000000000 --- a/changelogs/unreleased/improve_auto_devops_migration_debug.yml +++ /dev/null @@ -1,6 +0,0 @@ ---- -title: 'Auto DevOps: Add echo for each branch of the deploy() function where we run - helm upgrade' -merge_request: 23499 -author: -type: changed diff --git a/changelogs/unreleased/include-new-link-in-breadcrumb.yml b/changelogs/unreleased/include-new-link-in-breadcrumb.yml deleted file mode 100644 index 68c808d66d7..00000000000 --- a/changelogs/unreleased/include-new-link-in-breadcrumb.yml +++ /dev/null @@ -1,5 +0,0 @@ ---- -title: Include new link in breadcrumb for issues, merge requests, milestones, and labels -merge_request: 18515 -author: George Tsiolis -type: changed diff --git a/changelogs/unreleased/jivl-add-empty-state-graphs-null-values.yml b/changelogs/unreleased/jivl-add-empty-state-graphs-null-values.yml deleted file mode 100644 index d21254b16d0..00000000000 --- a/changelogs/unreleased/jivl-add-empty-state-graphs-null-values.yml +++ /dev/null @@ -1,5 +0,0 @@ ---- -title: Add empty state for graphs with no values -merge_request: 22630 -author: -type: fixed diff --git a/changelogs/unreleased/jlenny-CI_COMMIT_SHORT_SHA.yml b/changelogs/unreleased/jlenny-CI_COMMIT_SHORT_SHA.yml new file mode 100644 index 00000000000..abece81a20d --- /dev/null +++ b/changelogs/unreleased/jlenny-CI_COMMIT_SHORT_SHA.yml @@ -0,0 +1,5 @@ +--- +title: Add new pipeline variable CI_COMMIT_SHORT_SHA +merge_request: 23822 +author: +type: added diff --git a/changelogs/unreleased/jupyter-tls.yml b/changelogs/unreleased/jupyter-tls.yml deleted file mode 100644 index 4111edd34ff..00000000000 --- a/changelogs/unreleased/jupyter-tls.yml +++ /dev/null @@ -1,5 +0,0 @@ ---- -title: "#52753: HTTPS for JupyterHub installation" -merge_request: 23479 -author: Amit Rathi -type: added diff --git a/changelogs/unreleased/kcj-add-philosophy.yml b/changelogs/unreleased/kcj-add-philosophy.yml deleted file mode 100644 index d164ce165ea..00000000000 --- a/changelogs/unreleased/kcj-add-philosophy.yml +++ /dev/null @@ -1,5 +0,0 @@ ---- -title: Adds a PHILOSOPHY.md which references GitLab Product Handbook -merge_request: 23200 -author: -type: other diff --git a/changelogs/unreleased/kubernetes-http-response-code.yml b/changelogs/unreleased/kubernetes-http-response-code.yml deleted file mode 100644 index 551fe2edc3c..00000000000 --- a/changelogs/unreleased/kubernetes-http-response-code.yml +++ /dev/null @@ -1,5 +0,0 @@ ---- -title: Show HTTP response code for Kubernetes errors -merge_request: 22964 -author: -type: other diff --git a/changelogs/unreleased/legacy_fallback_for_project_clusters_only.yml b/changelogs/unreleased/legacy_fallback_for_project_clusters_only.yml deleted file mode 100644 index c8e959176d0..00000000000 --- a/changelogs/unreleased/legacy_fallback_for_project_clusters_only.yml +++ /dev/null @@ -1,5 +0,0 @@ ---- -title: Fallback to admin KUBE_TOKEN for project clusters only -merge_request: 23527 -author: -type: other diff --git a/changelogs/unreleased/lock-trace-writes.yml b/changelogs/unreleased/lock-trace-writes.yml deleted file mode 100644 index 9c5239081b9..00000000000 --- a/changelogs/unreleased/lock-trace-writes.yml +++ /dev/null @@ -1,5 +0,0 @@ ---- -title: Lock writes to trace stream -merge_request: -author: -type: fixed diff --git a/changelogs/unreleased/mg-fix-knative-application-row.yml b/changelogs/unreleased/mg-fix-knative-application-row.yml deleted file mode 100644 index 95142d380a4..00000000000 --- a/changelogs/unreleased/mg-fix-knative-application-row.yml +++ /dev/null @@ -1,5 +0,0 @@ ---- -title: Hide Knative from group cluster applications until supported -merge_request: 23577 -author: -type: fixed diff --git a/changelogs/unreleased/mk-avoid-read-only-error.yml b/changelogs/unreleased/mk-avoid-read-only-error.yml new file mode 100644 index 00000000000..8641f5db9f0 --- /dev/null +++ b/changelogs/unreleased/mk-avoid-read-only-error.yml @@ -0,0 +1,5 @@ +--- +title: Prevent admins from attempting hashed storage migration on read only DB +merge_request: 23597 +author: +type: fixed diff --git a/changelogs/unreleased/move-group-issues-search-cte-up-the-chain.yml b/changelogs/unreleased/move-group-issues-search-cte-up-the-chain.yml deleted file mode 100644 index 0269e7b6196..00000000000 --- a/changelogs/unreleased/move-group-issues-search-cte-up-the-chain.yml +++ /dev/null @@ -1,5 +0,0 @@ ---- -title: Fix error when searching for group issues with priority or popularity sort -merge_request: 23445 -author: -type: fixed diff --git a/changelogs/unreleased/mr-file-tree-commit.yml b/changelogs/unreleased/mr-file-tree-commit.yml deleted file mode 100644 index e0d47e6e61f..00000000000 --- a/changelogs/unreleased/mr-file-tree-commit.yml +++ /dev/null @@ -1,5 +0,0 @@ ---- -title: Show tree collapse button for merge request commit diffs -merge_request: -author: -type: fixed diff --git a/changelogs/unreleased/mr-origin-23218.yml b/changelogs/unreleased/mr-origin-23218.yml deleted file mode 100644 index 49867f04343..00000000000 --- a/changelogs/unreleased/mr-origin-23218.yml +++ /dev/null @@ -1,5 +0,0 @@ ---- -title: Fix typo for scheduled pipeline -merge_request: 23218 -author: Davy Defaud -type: other diff --git a/changelogs/unreleased/mr-pipelines-2.yml b/changelogs/unreleased/mr-pipelines-2.yml deleted file mode 100644 index 683c626c3ce..00000000000 --- a/changelogs/unreleased/mr-pipelines-2.yml +++ /dev/null @@ -1,5 +0,0 @@ ---- -title: Merge request pipelines -merge_request: 23217 -author: -type: added diff --git a/changelogs/unreleased/mr-sticky-headers.yml b/changelogs/unreleased/mr-sticky-headers.yml deleted file mode 100644 index c20829bc2d7..00000000000 --- a/changelogs/unreleased/mr-sticky-headers.yml +++ /dev/null @@ -1,5 +0,0 @@ ---- -title: Make diff file headers sticky -merge_request: -author: -type: changed diff --git a/changelogs/unreleased/mr-tree-filter-path-name.yml b/changelogs/unreleased/mr-tree-filter-path-name.yml deleted file mode 100644 index 152f8a67337..00000000000 --- a/changelogs/unreleased/mr-tree-filter-path-name.yml +++ /dev/null @@ -1,5 +0,0 @@ ---- -title: Changed merge request filtering to be by path instead of name -merge_request: -author: -type: changed diff --git a/changelogs/unreleased/multiple-diff-line-discussions-fix.yml b/changelogs/unreleased/multiple-diff-line-discussions-fix.yml deleted file mode 100644 index 870a8ab3815..00000000000 --- a/changelogs/unreleased/multiple-diff-line-discussions-fix.yml +++ /dev/null @@ -1,5 +0,0 @@ ---- -title: Fixed duplicate discussions getting added to diff lines -merge_request: -author: -type: fixed diff --git a/changelogs/unreleased/non-webkit-scrollbar-fixing.yml b/changelogs/unreleased/non-webkit-scrollbar-fixing.yml deleted file mode 100644 index 526a9f25486..00000000000 --- a/changelogs/unreleased/non-webkit-scrollbar-fixing.yml +++ /dev/null @@ -1,5 +0,0 @@ ---- -title: Fix horizontal scrollbar overlapping on horizontal scrolling-tabs -merge_request: 23167 -author: Harry Kiselev -type: other diff --git a/changelogs/unreleased/none-syntax-highlighting.yml b/changelogs/unreleased/none-syntax-highlighting.yml new file mode 100644 index 00000000000..b373aac7c02 --- /dev/null +++ b/changelogs/unreleased/none-syntax-highlighting.yml @@ -0,0 +1,5 @@ +--- +title: Add no-color theme for syntax highlighting. +merge_request: !20170 +author: khm +type: added diff --git a/changelogs/unreleased/optimise-job-request.yml b/changelogs/unreleased/optimise-job-request.yml deleted file mode 100644 index e1265841b48..00000000000 --- a/changelogs/unreleased/optimise-job-request.yml +++ /dev/null @@ -1,5 +0,0 @@ ---- -title: Use cached size when passing artifacts to Runner -merge_request: -author: -type: performance diff --git a/changelogs/unreleased/order-of-notification-settings.yml b/changelogs/unreleased/order-of-notification-settings.yml deleted file mode 100644 index 0f0243bcb40..00000000000 --- a/changelogs/unreleased/order-of-notification-settings.yml +++ /dev/null @@ -1,5 +0,0 @@ ---- -title: reorder notification settings by noisy-ness -merge_request: -author: C.J. Jameson -type: changed diff --git a/changelogs/unreleased/osw-cache-discussions-diff-highlighting.yml b/changelogs/unreleased/osw-cache-discussions-diff-highlighting.yml new file mode 100644 index 00000000000..7abc7d85794 --- /dev/null +++ b/changelogs/unreleased/osw-cache-discussions-diff-highlighting.yml @@ -0,0 +1,6 @@ +--- +title: Improve the loading time on merge request's discussion page by caching diff + highlight +merge_request: 23857 +author: +type: performance diff --git a/changelogs/unreleased/osw-fallback-on-blank-refs.yml b/changelogs/unreleased/osw-fallback-on-blank-refs.yml deleted file mode 100644 index 039179f5829..00000000000 --- a/changelogs/unreleased/osw-fallback-on-blank-refs.yml +++ /dev/null @@ -1,5 +0,0 @@ ---- -title: Avoid Gitaly RPC errors when fetching diff stats -merge_request: 22995 -author: -type: fixed diff --git a/changelogs/unreleased/osw-fix-grouping-by-file-path.yml b/changelogs/unreleased/osw-fix-grouping-by-file-path.yml deleted file mode 100644 index dff3116e7c6..00000000000 --- a/changelogs/unreleased/osw-fix-grouping-by-file-path.yml +++ /dev/null @@ -1,5 +0,0 @@ ---- -title: Avoid 500's when serializing legacy diff notes -merge_request: 23544 -author: -type: fixed diff --git a/changelogs/unreleased/osw-remove-unnused-data-from-diff-discussions.yml b/changelogs/unreleased/osw-remove-unnused-data-from-diff-discussions.yml deleted file mode 100644 index 58d9a19d038..00000000000 --- a/changelogs/unreleased/osw-remove-unnused-data-from-diff-discussions.yml +++ /dev/null @@ -1,5 +0,0 @@ ---- -title: Remove unused data from discussions endpoint -merge_request: 23570 -author: -type: performance diff --git a/changelogs/unreleased/osw-update-mr-metrics-with-events-data.yml b/changelogs/unreleased/osw-update-mr-metrics-with-events-data.yml deleted file mode 100644 index 09a10a86adc..00000000000 --- a/changelogs/unreleased/osw-update-mr-metrics-with-events-data.yml +++ /dev/null @@ -1,5 +0,0 @@ ---- -title: Populate MR metrics with events table information (migration) -merge_request: 23564 -author: -type: performance diff --git a/changelogs/unreleased/profile-fixing.yml b/changelogs/unreleased/profile-fixing.yml deleted file mode 100644 index 7e255d997d8..00000000000 --- a/changelogs/unreleased/profile-fixing.yml +++ /dev/null @@ -1,5 +0,0 @@ ---- -title: Fix bottom paddings of profile header and some markup updates of profile -merge_request: 23168 -author: Harry Kiselev -type: other diff --git a/changelogs/unreleased/project_identicon_fix.yml b/changelogs/unreleased/project_identicon_fix.yml deleted file mode 100644 index de4876fc4a5..00000000000 --- a/changelogs/unreleased/project_identicon_fix.yml +++ /dev/null @@ -1,5 +0,0 @@ ---- -title: Fix project identicon aligning Harry Kiselev -merge_request: 23166 -author: Harry Kiselev -type: other diff --git a/changelogs/unreleased/rails5-active-record-class-value.yml b/changelogs/unreleased/rails5-active-record-class-value.yml deleted file mode 100644 index 9f9fdf10cd1..00000000000 --- a/changelogs/unreleased/rails5-active-record-class-value.yml +++ /dev/null @@ -1,5 +0,0 @@ ---- -title: 'Rails5: Passing a class as a value in an Active Record query is deprecated' -merge_request: 23164 -author: Jasper Maes -type: other diff --git a/changelogs/unreleased/rails5-deprecation-render-nothing.yml b/changelogs/unreleased/rails5-deprecation-render-nothing.yml deleted file mode 100644 index 32e2d5800c7..00000000000 --- a/changelogs/unreleased/rails5-deprecation-render-nothing.yml +++ /dev/null @@ -1,6 +0,0 @@ ---- -title: render :nothing option is deprecated, Use head method to respond with empty - response body. -merge_request: 23311 -author: Jasper Maes -type: other diff --git a/changelogs/unreleased/rails5-env-deprecated.yml b/changelogs/unreleased/rails5-env-deprecated.yml deleted file mode 100644 index 2f8573e2ff6..00000000000 --- a/changelogs/unreleased/rails5-env-deprecated.yml +++ /dev/null @@ -1,5 +0,0 @@ ---- -title: 'Rails5: env is deprecated and will be removed from Rails 5.1' -merge_request: 22626 -author: Jasper Maes -type: other diff --git a/changelogs/unreleased/remove-blob-search-limit.yml b/changelogs/unreleased/remove-blob-search-limit.yml deleted file mode 100644 index 5bad3a83dbb..00000000000 --- a/changelogs/unreleased/remove-blob-search-limit.yml +++ /dev/null @@ -1,5 +0,0 @@ ---- -title: Remove limit of 100 when searching repository code. -merge_request: 8671 -author: -type: fixed diff --git a/changelogs/unreleased/remove-deployment-status-hack-from-backend.yml b/changelogs/unreleased/remove-deployment-status-hack-from-backend.yml deleted file mode 100644 index 2348bfab7d9..00000000000 --- a/changelogs/unreleased/remove-deployment-status-hack-from-backend.yml +++ /dev/null @@ -1,5 +0,0 @@ ---- -title: Return real deployment status to frontend -merge_request: 23270 -author: -type: fixed diff --git a/changelogs/unreleased/remove-duplicate-primary-button-in-dashboard-snippets.yml b/changelogs/unreleased/remove-duplicate-primary-button-in-dashboard-snippets.yml deleted file mode 100644 index 3a8b3a0df5d..00000000000 --- a/changelogs/unreleased/remove-duplicate-primary-button-in-dashboard-snippets.yml +++ /dev/null @@ -1,5 +0,0 @@ ---- -title: Remove duplicate primary button in dashboard snippets on small viewports -merge_request: 22902 -author: George Tsiolis -type: fixed diff --git a/changelogs/unreleased/remove-rails4-specific-code.yml b/changelogs/unreleased/remove-rails4-specific-code.yml new file mode 100644 index 00000000000..c6c4c0a5d5b --- /dev/null +++ b/changelogs/unreleased/remove-rails4-specific-code.yml @@ -0,0 +1,5 @@ +--- +title: Remove rails4 specific code +merge_request: 23847 +author: Jasper Maes +type: other diff --git a/changelogs/unreleased/remove-rails4-support.yml b/changelogs/unreleased/remove-rails4-support.yml new file mode 100644 index 00000000000..a05c913a70c --- /dev/null +++ b/changelogs/unreleased/remove-rails4-support.yml @@ -0,0 +1,5 @@ +--- +title: Remove rails 4 support in CI, Gemfiles, bin/ and config/ +merge_request: 23717 +author: Jasper Maes +type: other diff --git a/changelogs/unreleased/render-text-deprecated.yml b/changelogs/unreleased/render-text-deprecated.yml deleted file mode 100644 index 7dbbd13bcef..00000000000 --- a/changelogs/unreleased/render-text-deprecated.yml +++ /dev/null @@ -1,6 +0,0 @@ ---- -title: 'Fix deprecation: render :text is deprecated because it does not actually render - a text/plain response' -merge_request: 23425 -author: Jasper Maes -type: other diff --git a/changelogs/unreleased/retryable_create_or_update_kubernetes_namespace.yml b/changelogs/unreleased/retryable_create_or_update_kubernetes_namespace.yml deleted file mode 100644 index 607f2709f90..00000000000 --- a/changelogs/unreleased/retryable_create_or_update_kubernetes_namespace.yml +++ /dev/null @@ -1,6 +0,0 @@ ---- -title: Updates service to update Kubernetes project namespaces and restricted service - account if present -merge_request: 23525 -author: -type: changed diff --git a/changelogs/unreleased/rs-cherry-pick-api.yml b/changelogs/unreleased/rs-cherry-pick-api.yml deleted file mode 100644 index ce844dfc939..00000000000 --- a/changelogs/unreleased/rs-cherry-pick-api.yml +++ /dev/null @@ -1,5 +0,0 @@ ---- -title: Resolve possible cherry pick API race condition -merge_request: -author: -type: fixed diff --git a/changelogs/unreleased/s3-directories-get.yml b/changelogs/unreleased/s3-directories-get.yml new file mode 100644 index 00000000000..9f76af2bb09 --- /dev/null +++ b/changelogs/unreleased/s3-directories-get.yml @@ -0,0 +1,6 @@ +--- +title: Allow 'rake gitlab:cleanup:remote_upload_files' to read bucket files without + having permissions to see all buckets. +merge_request: 23981 +author: +type: fixed diff --git a/changelogs/unreleased/security-182-update-workhorse.yml b/changelogs/unreleased/security-182-update-workhorse.yml deleted file mode 100644 index 76850901b68..00000000000 --- a/changelogs/unreleased/security-182-update-workhorse.yml +++ /dev/null @@ -1,5 +0,0 @@ ---- -title: Redact sensitive information on gitlab-workhorse log -merge_request: -author: -type: security diff --git a/changelogs/unreleased/security-2717-xss-username-autocomplete.yml b/changelogs/unreleased/security-2717-xss-username-autocomplete.yml deleted file mode 100644 index d9b1015eeb4..00000000000 --- a/changelogs/unreleased/security-2717-xss-username-autocomplete.yml +++ /dev/null @@ -1,5 +0,0 @@ ---- -title: Escape user fullname while rendering autocomplete template to prevent XSS -merge_request: -author: -type: security diff --git a/changelogs/unreleased/security-2736-prometheus-ssrf.yml b/changelogs/unreleased/security-2736-prometheus-ssrf.yml deleted file mode 100644 index 9d0dda8a75f..00000000000 --- a/changelogs/unreleased/security-2736-prometheus-ssrf.yml +++ /dev/null @@ -1,5 +0,0 @@ ---- -title: Do not follow redirects in Prometheus service when making http requests to the configured api url -merge_request: -author: -type: security diff --git a/changelogs/unreleased/security-48259-private-snippet.yml b/changelogs/unreleased/security-48259-private-snippet.yml new file mode 100644 index 00000000000..6cf1e5dc694 --- /dev/null +++ b/changelogs/unreleased/security-48259-private-snippet.yml @@ -0,0 +1,5 @@ +--- +title: Prevent private snippets from being embeddable +merge_request: +author: +type: security diff --git a/changelogs/unreleased/security-53543-user-keeps-access-to-mr-issue-when-removed-from-team.yml b/changelogs/unreleased/security-53543-user-keeps-access-to-mr-issue-when-removed-from-team.yml new file mode 100644 index 00000000000..ab12ba539c1 --- /dev/null +++ b/changelogs/unreleased/security-53543-user-keeps-access-to-mr-issue-when-removed-from-team.yml @@ -0,0 +1,5 @@ +--- +title: Issuable no longer is visible to users when project can't be viewed +merge_request: +author: +type: security diff --git a/changelogs/unreleased/security-54377-label-milestone-name-xss.yml b/changelogs/unreleased/security-54377-label-milestone-name-xss.yml new file mode 100644 index 00000000000..76589b2eb4f --- /dev/null +++ b/changelogs/unreleased/security-54377-label-milestone-name-xss.yml @@ -0,0 +1,5 @@ +--- +title: Escape label and milestone titles to prevent XSS in GFM autocomplete +merge_request: 2693 +author: +type: security diff --git a/changelogs/unreleased/security-bvl-exposure-in-commits-list.yml b/changelogs/unreleased/security-bvl-exposure-in-commits-list.yml deleted file mode 100644 index 0361fb0c041..00000000000 --- a/changelogs/unreleased/security-bvl-exposure-in-commits-list.yml +++ /dev/null @@ -1,5 +0,0 @@ ---- -title: Don't expose confidential information in commit message list -merge_request: -author: -type: security diff --git a/changelogs/unreleased/security-bvl-fix-cross-project-mr-exposure.yml b/changelogs/unreleased/security-bvl-fix-cross-project-mr-exposure.yml new file mode 100644 index 00000000000..11aae4428fb --- /dev/null +++ b/changelogs/unreleased/security-bvl-fix-cross-project-mr-exposure.yml @@ -0,0 +1,5 @@ +--- +title: Don't expose cross project repositories through diffs when creating merge reqeusts +merge_request: +author: +type: security diff --git a/changelogs/unreleased/security-email-change-notification.yml b/changelogs/unreleased/security-email-change-notification.yml deleted file mode 100644 index 45075ff20bb..00000000000 --- a/changelogs/unreleased/security-email-change-notification.yml +++ /dev/null @@ -1,5 +0,0 @@ ---- -title: Provide email notification when a user changes their email address -merge_request: -author: -type: security diff --git a/changelogs/unreleased/security-fix-pat-web-access.yml b/changelogs/unreleased/security-fix-pat-web-access.yml deleted file mode 100644 index 62ffb908fe5..00000000000 --- a/changelogs/unreleased/security-fix-pat-web-access.yml +++ /dev/null @@ -1,5 +0,0 @@ ---- -title: Restrict Personal Access Tokens to API scope on web requests -merge_request: -author: -type: security diff --git a/changelogs/unreleased/security-fix-ssrf-import-url-remote-mirror.yml b/changelogs/unreleased/security-fix-ssrf-import-url-remote-mirror.yml new file mode 100644 index 00000000000..7ba7aa21090 --- /dev/null +++ b/changelogs/unreleased/security-fix-ssrf-import-url-remote-mirror.yml @@ -0,0 +1,5 @@ +--- +title: Fix SSRF with import_url and remote mirror url +merge_request: +author: +type: security diff --git a/changelogs/unreleased/security-fix-uri-xss-applications.yml b/changelogs/unreleased/security-fix-uri-xss-applications.yml deleted file mode 100644 index 0eaa1b1c4a3..00000000000 --- a/changelogs/unreleased/security-fix-uri-xss-applications.yml +++ /dev/null @@ -1,5 +0,0 @@ ---- -title: Resolve reflected XSS in Ouath authorize window -merge_request: -author: -type: security diff --git a/changelogs/unreleased/security-fix-webhook-ssrf-ipv6.yml b/changelogs/unreleased/security-fix-webhook-ssrf-ipv6.yml deleted file mode 100644 index 32c85a2a7da..00000000000 --- a/changelogs/unreleased/security-fix-webhook-ssrf-ipv6.yml +++ /dev/null @@ -1,5 +0,0 @@ ---- -title: Fix SSRF in project integrations -merge_request: -author: -type: security diff --git a/changelogs/unreleased/security-fj-crlf-injection.yml b/changelogs/unreleased/security-fj-crlf-injection.yml deleted file mode 100644 index 861167b8a6e..00000000000 --- a/changelogs/unreleased/security-fj-crlf-injection.yml +++ /dev/null @@ -1,5 +0,0 @@ ---- -title: Fix CRLF vulnerability in Project hooks -merge_request: -author: -type: security diff --git a/changelogs/unreleased/security-guest-comments.yml b/changelogs/unreleased/security-guest-comments.yml deleted file mode 100644 index 2c99512433b..00000000000 --- a/changelogs/unreleased/security-guest-comments.yml +++ /dev/null @@ -1,5 +0,0 @@ ---- -title: Fixed ability to comment on locked/confidential issues. -merge_request: -author: -type: security diff --git a/changelogs/unreleased/security-guest-comments_2.yml b/changelogs/unreleased/security-guest-comments_2.yml deleted file mode 100644 index be6f2d6a490..00000000000 --- a/changelogs/unreleased/security-guest-comments_2.yml +++ /dev/null @@ -1,5 +0,0 @@ ---- -title: Fixed ability of guest users to edit/delete comments on locked or confidential issues. -merge_request: -author: -type: security diff --git a/changelogs/unreleased/security-issue_51301.yml b/changelogs/unreleased/security-issue_51301.yml deleted file mode 100644 index cf8ebb54b1c..00000000000 --- a/changelogs/unreleased/security-issue_51301.yml +++ /dev/null @@ -1,5 +0,0 @@ ---- -title: Fix milestone promotion authorization check -merge_request: -author: -type: security diff --git a/changelogs/unreleased/security-master-group-cicd-settings-accessible-to-maintainer.yml b/changelogs/unreleased/security-master-group-cicd-settings-accessible-to-maintainer.yml new file mode 100644 index 00000000000..5586fa6cd8e --- /dev/null +++ b/changelogs/unreleased/security-master-group-cicd-settings-accessible-to-maintainer.yml @@ -0,0 +1,5 @@ +--- +title: Allow changing group CI/CD settings only for owners. +merge_request: +author: +type: security diff --git a/changelogs/unreleased/security-master-guests-jobs-api.yml b/changelogs/unreleased/security-master-guests-jobs-api.yml new file mode 100644 index 00000000000..83022e91aca --- /dev/null +++ b/changelogs/unreleased/security-master-guests-jobs-api.yml @@ -0,0 +1,5 @@ +--- +title: Authorize before reading job information via API. +merge_request: +author: +type: security diff --git a/changelogs/unreleased/security-master-secret-ci-variables-exposed.yml b/changelogs/unreleased/security-master-secret-ci-variables-exposed.yml new file mode 100644 index 00000000000..702181065f5 --- /dev/null +++ b/changelogs/unreleased/security-master-secret-ci-variables-exposed.yml @@ -0,0 +1,5 @@ +--- +title: Prevent leaking protected variables for ambiguous refs. +merge_request: +author: +type: security diff --git a/changelogs/unreleased/security-master-url-rel.yml b/changelogs/unreleased/security-master-url-rel.yml new file mode 100644 index 00000000000..75f599f6bcd --- /dev/null +++ b/changelogs/unreleased/security-master-url-rel.yml @@ -0,0 +1,5 @@ +--- +title: Set URL rel attribute for broken URLs. +merge_request: +author: +type: security diff --git a/changelogs/unreleased/security-mermaid-xss.yml b/changelogs/unreleased/security-mermaid-xss.yml deleted file mode 100644 index bcf93ef37ff..00000000000 --- a/changelogs/unreleased/security-mermaid-xss.yml +++ /dev/null @@ -1,5 +0,0 @@ ---- -title: Configure mermaid to not render HTML content in diagrams -merge_request: -author: -type: security diff --git a/changelogs/unreleased/security-pages-toctou-race.yml b/changelogs/unreleased/security-pages-toctou-race.yml deleted file mode 100644 index 1c055f6087f..00000000000 --- a/changelogs/unreleased/security-pages-toctou-race.yml +++ /dev/null @@ -1,6 +0,0 @@ ---- -title: Fix a possible symlink time of check to time of use race condition in GitLab - Pages -merge_request: -author: -type: security diff --git a/changelogs/unreleased/security-private-group.yml b/changelogs/unreleased/security-private-group.yml deleted file mode 100644 index dbb7794dfed..00000000000 --- a/changelogs/unreleased/security-private-group.yml +++ /dev/null @@ -1,6 +0,0 @@ ---- -title: Removed ability to see private group names when the group id is entered in - the url. -merge_request: -author: -type: security diff --git a/changelogs/unreleased/security-refs-available-to-project-guest.yml b/changelogs/unreleased/security-refs-available-to-project-guest.yml new file mode 100644 index 00000000000..eb6804c52d3 --- /dev/null +++ b/changelogs/unreleased/security-refs-available-to-project-guest.yml @@ -0,0 +1,5 @@ +--- +title: Project guests no longer are able to see refs page +merge_request: +author: +type: security diff --git a/changelogs/unreleased/security-stored-xss-for-environments.yml b/changelogs/unreleased/security-stored-xss-for-environments.yml deleted file mode 100644 index 5d78ca00942..00000000000 --- a/changelogs/unreleased/security-stored-xss-for-environments.yml +++ /dev/null @@ -1,5 +0,0 @@ ---- -title: Fix stored XSS for Environments -merge_request: -author: -type: security diff --git a/changelogs/unreleased/security-todos_not_redacted_for_guests.yml b/changelogs/unreleased/security-todos_not_redacted_for_guests.yml new file mode 100644 index 00000000000..be0ae9a7193 --- /dev/null +++ b/changelogs/unreleased/security-todos_not_redacted_for_guests.yml @@ -0,0 +1,5 @@ +--- +title: Delete confidential todos for user when downgraded to Guest +merge_request: +author: +type: security diff --git a/changelogs/unreleased/security-xss-in-markdown-following-unrecognized-html-element.yml b/changelogs/unreleased/security-xss-in-markdown-following-unrecognized-html-element.yml deleted file mode 100644 index 3bd8123a346..00000000000 --- a/changelogs/unreleased/security-xss-in-markdown-following-unrecognized-html-element.yml +++ /dev/null @@ -1,5 +0,0 @@ ---- -title: Fix possible XSS attack in Markdown urls with spaces -merge_request: 2599 -author: -type: security diff --git a/changelogs/unreleased/set-kubeconfig-nil-when-token-nil.yml b/changelogs/unreleased/set-kubeconfig-nil-when-token-nil.yml deleted file mode 100644 index 6eac2a0146c..00000000000 --- a/changelogs/unreleased/set-kubeconfig-nil-when-token-nil.yml +++ /dev/null @@ -1,5 +0,0 @@ ---- -title: Make KUBECONFIG nil if KUBE_TOKEN is nil -merge_request: 23414 -author: -type: fixed diff --git a/changelogs/unreleased/sh-53180-append-path.yml b/changelogs/unreleased/sh-53180-append-path.yml deleted file mode 100644 index 64fae5522d8..00000000000 --- a/changelogs/unreleased/sh-53180-append-path.yml +++ /dev/null @@ -1,5 +0,0 @@ ---- -title: Make sure there's only one slash as path separator -merge_request: 22954 -author: -type: other diff --git a/changelogs/unreleased/sh-bump-gems-security.yml b/changelogs/unreleased/sh-bump-gems-security.yml deleted file mode 100644 index 06489f6f979..00000000000 --- a/changelogs/unreleased/sh-bump-gems-security.yml +++ /dev/null @@ -1,5 +0,0 @@ ---- -title: Bump nokogiri, loofah, and rack gems for security updates -merge_request: 23204 -author: -type: security diff --git a/changelogs/unreleased/sh-bump-omniauth-google-gem.yml b/changelogs/unreleased/sh-bump-omniauth-google-gem.yml new file mode 100644 index 00000000000..2b31a55f8b2 --- /dev/null +++ b/changelogs/unreleased/sh-bump-omniauth-google-gem.yml @@ -0,0 +1,5 @@ +--- +title: Upgrade Omniauth and JWT gems to switch away from Google+ API +merge_request: 24068 +author: +type: changed diff --git a/changelogs/unreleased/sh-bump-ruby-2-5-3.yml b/changelogs/unreleased/sh-bump-ruby-2-5-3.yml deleted file mode 100644 index 13cadc73e9c..00000000000 --- a/changelogs/unreleased/sh-bump-ruby-2-5-3.yml +++ /dev/null @@ -1,5 +0,0 @@ ---- -title: Upgrade to Ruby 2.5.3 -merge_request: 2806 -author: -type: performance diff --git a/changelogs/unreleased/sh-cache-avatar-paths.yml b/changelogs/unreleased/sh-cache-avatar-paths.yml new file mode 100644 index 00000000000..b59a4db413d --- /dev/null +++ b/changelogs/unreleased/sh-cache-avatar-paths.yml @@ -0,0 +1,5 @@ +--- +title: Cache avatar URLs and paths within a request +merge_request: 23950 +author: +type: performance diff --git a/changelogs/unreleased/sh-carrierwave-patch-google-acl.yml b/changelogs/unreleased/sh-carrierwave-patch-google-acl.yml new file mode 100644 index 00000000000..206253a100c --- /dev/null +++ b/changelogs/unreleased/sh-carrierwave-patch-google-acl.yml @@ -0,0 +1,5 @@ +--- +title: Fix object storage not working properly with Google S3 compatibility +merge_request: 23858 +author: +type: fixed diff --git a/changelogs/unreleased/sh-disable-autocomplete-mirror-settings.yml b/changelogs/unreleased/sh-disable-autocomplete-mirror-settings.yml deleted file mode 100644 index e42906e88f2..00000000000 --- a/changelogs/unreleased/sh-disable-autocomplete-mirror-settings.yml +++ /dev/null @@ -1,5 +0,0 @@ ---- -title: Disable password autocomplete in mirror form fill -merge_request: 23402 -author: -type: fixed diff --git a/changelogs/unreleased/sh-drop-webhooks-project-export.yml b/changelogs/unreleased/sh-drop-webhooks-project-export.yml new file mode 100644 index 00000000000..217373bce66 --- /dev/null +++ b/changelogs/unreleased/sh-drop-webhooks-project-export.yml @@ -0,0 +1,5 @@ +--- +title: Drop Webhooks from project import/export config +merge_request: 24121 +author: +type: fixed diff --git a/changelogs/unreleased/sh-fix-branches-api-timeout.yml b/changelogs/unreleased/sh-fix-branches-api-timeout.yml new file mode 100644 index 00000000000..8cd29a7269d --- /dev/null +++ b/changelogs/unreleased/sh-fix-branches-api-timeout.yml @@ -0,0 +1,5 @@ +--- +title: Fix timeout issues retrieving branches via API +merge_request: 24034 +author: +type: performance diff --git a/changelogs/unreleased/sh-fix-issue-38317.yml b/changelogs/unreleased/sh-fix-issue-38317.yml deleted file mode 100644 index 13fcb5b8f96..00000000000 --- a/changelogs/unreleased/sh-fix-issue-38317.yml +++ /dev/null @@ -1,5 +0,0 @@ ---- -title: Remove needless auto-capitalization on Wiki page titles -merge_request: 23288 -author: -type: fixed diff --git a/changelogs/unreleased/sh-fix-issue-51220.yml b/changelogs/unreleased/sh-fix-issue-51220.yml deleted file mode 100644 index 048f58611cb..00000000000 --- a/changelogs/unreleased/sh-fix-issue-51220.yml +++ /dev/null @@ -1,5 +0,0 @@ ---- -title: Handle force_remove_source_branch when creating merge request -merge_request: 23281 -author: -type: fixed diff --git a/changelogs/unreleased/sh-fix-issue-53783-ce.yml b/changelogs/unreleased/sh-fix-issue-53783-ce.yml deleted file mode 100644 index 10be1d81768..00000000000 --- a/changelogs/unreleased/sh-fix-issue-53783-ce.yml +++ /dev/null @@ -1,5 +0,0 @@ ---- -title: Fix enabling project deploy key for admins -merge_request: 23043 -author: -type: fixed diff --git a/changelogs/unreleased/sh-fix-issue-55822.yml b/changelogs/unreleased/sh-fix-issue-55822.yml new file mode 100644 index 00000000000..1267b2ace2f --- /dev/null +++ b/changelogs/unreleased/sh-fix-issue-55822.yml @@ -0,0 +1,5 @@ +--- +title: Fix commit SHA not showing in merge request compare dropdown +merge_request: 24084 +author: +type: fixed diff --git a/changelogs/unreleased/sh-fix-mirrors-protected-branches.yml b/changelogs/unreleased/sh-fix-mirrors-protected-branches.yml deleted file mode 100644 index 627de25650d..00000000000 --- a/changelogs/unreleased/sh-fix-mirrors-protected-branches.yml +++ /dev/null @@ -1,5 +0,0 @@ ---- -title: Fix "protected branches only" checkbox not set properly at init -merge_request: 23409 -author: -type: fixed diff --git a/changelogs/unreleased/sh-handle-invalid-gpg-sig.yml b/changelogs/unreleased/sh-handle-invalid-gpg-sig.yml deleted file mode 100644 index 185e2547e16..00000000000 --- a/changelogs/unreleased/sh-handle-invalid-gpg-sig.yml +++ /dev/null @@ -1,5 +0,0 @@ ---- -title: Gracefully handle unknown/invalid GPG keys -merge_request: 23492 -author: -type: fixed diff --git a/changelogs/unreleased/sh-handle-string-null-bytes.yml b/changelogs/unreleased/sh-handle-string-null-bytes.yml deleted file mode 100644 index edc045274e3..00000000000 --- a/changelogs/unreleased/sh-handle-string-null-bytes.yml +++ /dev/null @@ -1,5 +0,0 @@ ---- -title: Gracefully handle references with null bytes -merge_request: 23365 -author: -type: fixed diff --git a/changelogs/unreleased/sh-ignore-arrays-url-sanitizer.yml b/changelogs/unreleased/sh-ignore-arrays-url-sanitizer.yml deleted file mode 100644 index c010bd1f540..00000000000 --- a/changelogs/unreleased/sh-ignore-arrays-url-sanitizer.yml +++ /dev/null @@ -1,5 +0,0 @@ ---- -title: Only allow strings in URL::Sanitizer.valid? -merge_request: 23675 -author: -type: fixed diff --git a/changelogs/unreleased/sh-json-serialize-broadcast-messages.yml b/changelogs/unreleased/sh-json-serialize-broadcast-messages.yml deleted file mode 100644 index e8bee64f780..00000000000 --- a/changelogs/unreleased/sh-json-serialize-broadcast-messages.yml +++ /dev/null @@ -1,5 +0,0 @@ ---- -title: Avoid caching BroadcastMessage as an ActiveRecord object -merge_request: 23662 -author: -type: fixed diff --git a/changelogs/unreleased/sh-remove-local-sidekiq-admin-check.yml b/changelogs/unreleased/sh-remove-local-sidekiq-admin-check.yml deleted file mode 100644 index 3ec15908fc7..00000000000 --- a/changelogs/unreleased/sh-remove-local-sidekiq-admin-check.yml +++ /dev/null @@ -1,5 +0,0 @@ ---- -title: Remove display of local Sidekiq process in /admin/sidekiq -merge_request: 23118 -author: -type: fixed diff --git a/changelogs/unreleased/sh-truncate-with-periods.yml b/changelogs/unreleased/sh-truncate-with-periods.yml deleted file mode 100644 index b1c6b4f9cbd..00000000000 --- a/changelogs/unreleased/sh-truncate-with-periods.yml +++ /dev/null @@ -1,5 +0,0 @@ ---- -title: Truncate merge request titles with periods instead of ellipsis -merge_request: 23558 -author: -type: changed diff --git a/changelogs/unreleased/sh-use-nakayoshi-fork.yml b/changelogs/unreleased/sh-use-nakayoshi-fork.yml deleted file mode 100644 index 5977d9b0974..00000000000 --- a/changelogs/unreleased/sh-use-nakayoshi-fork.yml +++ /dev/null @@ -1,5 +0,0 @@ ---- -title: Improve memory performance by reducing dirty pages after fork() -merge_request: 23169 -author: -type: performance diff --git a/changelogs/unreleased/sh-use-nokogiri-xml-backend.yml b/changelogs/unreleased/sh-use-nokogiri-xml-backend.yml deleted file mode 100644 index 6a82e32c416..00000000000 --- a/changelogs/unreleased/sh-use-nokogiri-xml-backend.yml +++ /dev/null @@ -1,5 +0,0 @@ ---- -title: Use Nokogiri as the ActiveSupport XML backend -merge_request: 23136 -author: -type: performance diff --git a/changelogs/unreleased/spec-positional-arguments.yml b/changelogs/unreleased/spec-positional-arguments.yml new file mode 100644 index 00000000000..9dc114e5595 --- /dev/null +++ b/changelogs/unreleased/spec-positional-arguments.yml @@ -0,0 +1,5 @@ +--- +title: 'Fix deprecation: Using positional arguments in integration tests' +merge_request: 24009 +author: Jasper Maes +type: other diff --git a/changelogs/unreleased/specs-positional-arguments.yml b/changelogs/unreleased/specs-positional-arguments.yml new file mode 100644 index 00000000000..38b831bd72c --- /dev/null +++ b/changelogs/unreleased/specs-positional-arguments.yml @@ -0,0 +1,5 @@ +--- +title: convert specs in javascripts/ and support/ to new syntax +merge_request: 23947 +author: Jasper Maes +type: other diff --git a/changelogs/unreleased/speed-up-relative-positioning.yml b/changelogs/unreleased/speed-up-relative-positioning.yml deleted file mode 100644 index 3bd865fb5de..00000000000 --- a/changelogs/unreleased/speed-up-relative-positioning.yml +++ /dev/null @@ -1,5 +0,0 @@ ---- -title: Speed up issue board lists in groups with many projects -merge_request: -author: -type: performance diff --git a/changelogs/unreleased/store-correlation-logs.yml b/changelogs/unreleased/store-correlation-logs.yml deleted file mode 100644 index d5f6c789a17..00000000000 --- a/changelogs/unreleased/store-correlation-logs.yml +++ /dev/null @@ -1,5 +0,0 @@ ---- -title: Log and pass correlation-id between Unicorn, Sidekiq and Gitaly -merge_request: -author: -type: added diff --git a/changelogs/unreleased/support-gitaly-tls.yml b/changelogs/unreleased/support-gitaly-tls.yml new file mode 100644 index 00000000000..2a15500d6da --- /dev/null +++ b/changelogs/unreleased/support-gitaly-tls.yml @@ -0,0 +1,5 @@ +--- +title: Support tls communication in gitaly +merge_request: 22602 +author: +type: added diff --git a/changelogs/unreleased/switch-rails.yml b/changelogs/unreleased/switch-rails.yml deleted file mode 100644 index 4edf709dbd4..00000000000 --- a/changelogs/unreleased/switch-rails.yml +++ /dev/null @@ -1,5 +0,0 @@ ---- -title: Switch to Rails 5 -merge_request: 21492 -author: -type: other diff --git a/changelogs/unreleased/tc-backfill-full-path-config.yml b/changelogs/unreleased/tc-backfill-full-path-config.yml deleted file mode 100644 index 4f06284d0e3..00000000000 --- a/changelogs/unreleased/tc-backfill-full-path-config.yml +++ /dev/null @@ -1,5 +0,0 @@ ---- -title: Migration to write fullpath in all repository configs -merge_request: 22322 -author: -type: other diff --git a/changelogs/unreleased/tc-backfill-hashed-project_repositories.yml b/changelogs/unreleased/tc-backfill-hashed-project_repositories.yml deleted file mode 100644 index 90a5c8c4e2c..00000000000 --- a/changelogs/unreleased/tc-backfill-hashed-project_repositories.yml +++ /dev/null @@ -1,5 +0,0 @@ ---- -title: Fill project_repositories for hashed storage projects -merge_request: 23482 -author: -type: added diff --git a/changelogs/unreleased/tc-repo-full-path-in-db.yml b/changelogs/unreleased/tc-repo-full-path-in-db.yml deleted file mode 100644 index ead8feabeb9..00000000000 --- a/changelogs/unreleased/tc-repo-full-path-in-db.yml +++ /dev/null @@ -1,5 +0,0 @@ ---- -title: Add model and relation to store repo full path in database -merge_request: 23143 -author: -type: added diff --git a/changelogs/unreleased/triggermesh-knative-version.yml b/changelogs/unreleased/triggermesh-knative-version.yml new file mode 100644 index 00000000000..27f400962da --- /dev/null +++ b/changelogs/unreleased/triggermesh-knative-version.yml @@ -0,0 +1,5 @@ +--- +title: Knative version bump 0.1.3 -> 0.2.2 +merge_request: +author: Chris Baumbauer +type: changed diff --git a/changelogs/unreleased/triggermesh-phase2-external-ip.yml b/changelogs/unreleased/triggermesh-phase2-external-ip.yml deleted file mode 100644 index 582c8f6df2e..00000000000 --- a/changelogs/unreleased/triggermesh-phase2-external-ip.yml +++ /dev/null @@ -1,5 +0,0 @@ ---- -title: Add an external IP address to the knative cluster application page -merge_request: -author: Chris Baumbauer -type: fixed diff --git a/changelogs/unreleased/triggermesh-phase2-knative-description.yml b/changelogs/unreleased/triggermesh-phase2-knative-description.yml deleted file mode 100644 index c6cee1984d5..00000000000 --- a/changelogs/unreleased/triggermesh-phase2-knative-description.yml +++ /dev/null @@ -1,5 +0,0 @@ ---- -title: Modify the wording for the knative cluster application to match upstream -merge_request: 23289 -author: Chris Baumbauer -type: fixed diff --git a/changelogs/unreleased/triggermesh-phase2-serverless-list.yml b/changelogs/unreleased/triggermesh-phase2-serverless-list.yml deleted file mode 100644 index 22e1a35dd90..00000000000 --- a/changelogs/unreleased/triggermesh-phase2-serverless-list.yml +++ /dev/null @@ -1,5 +0,0 @@ ---- -title: Introduce Knative and Serverless Components -merge_request: 23174 -author: Chris Baumbauer -type: added diff --git a/changelogs/unreleased/triggermesh-phase2-serverless.yml b/changelogs/unreleased/triggermesh-phase2-serverless.yml deleted file mode 100644 index bee2b5e1e2c..00000000000 --- a/changelogs/unreleased/triggermesh-phase2-serverless.yml +++ /dev/null @@ -1,5 +0,0 @@ ---- -title: Add knative client to kubeclient library -merge_request: 22968 -author: cab105 -type: added diff --git a/changelogs/unreleased/unicorn-monkey-patch.yml b/changelogs/unreleased/unicorn-monkey-patch.yml deleted file mode 100644 index 6b0e00ca291..00000000000 --- a/changelogs/unreleased/unicorn-monkey-patch.yml +++ /dev/null @@ -1,5 +0,0 @@ ---- -title: Add monkey patch to unicorn to fix eof? problem -merge_request: 23385 -author: -type: fixed diff --git a/changelogs/unreleased/update-gitlab-runner-helm-chart-to-0-1-39.yml b/changelogs/unreleased/update-gitlab-runner-helm-chart-to-0-1-39.yml deleted file mode 100644 index dffcdb0bb5a..00000000000 --- a/changelogs/unreleased/update-gitlab-runner-helm-chart-to-0-1-39.yml +++ /dev/null @@ -1,5 +0,0 @@ ---- -title: Update used version of Runner Helm Chart to 0.1.39 -merge_request: 23633 -author: -type: other diff --git a/changelogs/unreleased/update-gitlab-runner-helm-chart-to-0-1-43.yml b/changelogs/unreleased/update-gitlab-runner-helm-chart-to-0-1-43.yml new file mode 100644 index 00000000000..24471b028b1 --- /dev/null +++ b/changelogs/unreleased/update-gitlab-runner-helm-chart-to-0-1-43.yml @@ -0,0 +1,5 @@ +--- +title: Update GitLab Runner Helm Chart to 0.1.43 +merge_request: 24083 +author: +type: other diff --git a/changelogs/unreleased/update-gitlab-runner-helm-chart-version.yml b/changelogs/unreleased/update-gitlab-runner-helm-chart-version.yml deleted file mode 100644 index 9051e4f79c8..00000000000 --- a/changelogs/unreleased/update-gitlab-runner-helm-chart-version.yml +++ /dev/null @@ -1,5 +0,0 @@ ---- -title: Update used version of Runner Helm Chart to 0.1.38 -merge_request: 23304 -author: -type: other diff --git a/changelogs/unreleased/upgrade-to-workhorse-7-6-0.yml b/changelogs/unreleased/upgrade-to-workhorse-7-6-0.yml deleted file mode 100644 index 1389693b9a9..00000000000 --- a/changelogs/unreleased/upgrade-to-workhorse-7-6-0.yml +++ /dev/null @@ -1,5 +0,0 @@ ---- -title: Upgrade workhorse to 7.6.0 -merge_request: 23694 -author: -type: other diff --git a/changelogs/unreleased/upgrade_kubeclient_400.yml b/changelogs/unreleased/upgrade_kubeclient_400.yml deleted file mode 100644 index edb38710e6a..00000000000 --- a/changelogs/unreleased/upgrade_kubeclient_400.yml +++ /dev/null @@ -1,5 +0,0 @@ ---- -title: Upgrade kubeclient to 4.0.0 -merge_request: 23261 -author: Praveen Arimbrathodiyil @pravi -type: other diff --git a/changelogs/unreleased/usage-count.yml b/changelogs/unreleased/usage-count.yml deleted file mode 100644 index efff2615ce4..00000000000 --- a/changelogs/unreleased/usage-count.yml +++ /dev/null @@ -1,5 +0,0 @@ ---- -title: Use approximate count for big tables for usage statistics. -merge_request: -author: -type: fixed diff --git a/changelogs/unreleased/validate-foreign-keys-being-indexed.yml b/changelogs/unreleased/validate-foreign-keys-being-indexed.yml deleted file mode 100644 index 6608a93c08f..00000000000 --- a/changelogs/unreleased/validate-foreign-keys-being-indexed.yml +++ /dev/null @@ -1,5 +0,0 @@ ---- -title: Validate foreign keys being created and indexed for column with _id -merge_request: 22808 -author: -type: performance diff --git a/changelogs/unreleased/winh-collapse-discussions.yml b/changelogs/unreleased/winh-collapse-discussions.yml deleted file mode 100644 index 19d04506318..00000000000 --- a/changelogs/unreleased/winh-collapse-discussions.yml +++ /dev/null @@ -1,5 +0,0 @@ ---- -title: Fix collapsing discussion replies -merge_request: 23462 -author: -type: fixed diff --git a/changelogs/unreleased/winh-divider-margin.yml b/changelogs/unreleased/winh-divider-margin.yml deleted file mode 100644 index db84090c15c..00000000000 --- a/changelogs/unreleased/winh-divider-margin.yml +++ /dev/null @@ -1,5 +0,0 @@ ---- -title: Adjust divider margin to comply with design specs -merge_request: 23548 -author: -type: changed diff --git a/changelogs/unreleased/winh-dropdown-divider-color.yml b/changelogs/unreleased/winh-dropdown-divider-color.yml deleted file mode 100644 index 6b6ecd831b8..00000000000 --- a/changelogs/unreleased/winh-dropdown-divider-color.yml +++ /dev/null @@ -1,5 +0,0 @@ ---- -title: Change dropdown divider color to gray-200 (#dfdfdf) -merge_request: 23592 -author: -type: changed diff --git a/changelogs/unreleased/winh-dropdown-item-padding.yml b/changelogs/unreleased/winh-dropdown-item-padding.yml deleted file mode 100644 index 9f18abba9d1..00000000000 --- a/changelogs/unreleased/winh-dropdown-item-padding.yml +++ /dev/null @@ -1,5 +0,0 @@ ---- -title: Adjust dropdown item and header padding to comply with design specs -merge_request: 23552 -author: -type: changed diff --git a/changelogs/unreleased/winh-dropdown-title-padding.yml b/changelogs/unreleased/winh-dropdown-title-padding.yml new file mode 100644 index 00000000000..9d65175b536 --- /dev/null +++ b/changelogs/unreleased/winh-dropdown-title-padding.yml @@ -0,0 +1,5 @@ +--- +title: Adjust padding of .dropdown-title to comply with design specs +merge_request: 23546 +author: +type: changed diff --git a/changelogs/unreleased/winh-issue-boards-project-dropdown-close.yml b/changelogs/unreleased/winh-issue-boards-project-dropdown-close.yml deleted file mode 100644 index 18f7da56edb..00000000000 --- a/changelogs/unreleased/winh-issue-boards-project-dropdown-close.yml +++ /dev/null @@ -1,5 +0,0 @@ ---- -title: Remove close icon from projects dropdown in issue boards -merge_request: 23567 -author: -type: changed diff --git a/changelogs/unreleased/winh-markdown-preview-lists.yml b/changelogs/unreleased/winh-markdown-preview-lists.yml deleted file mode 100644 index 6e47726283d..00000000000 --- a/changelogs/unreleased/winh-markdown-preview-lists.yml +++ /dev/null @@ -1,5 +0,0 @@ ---- -title: Remove unnecessary div from MarkdownField to apply list styles correctly -merge_request: 23733 -author: -type: fixed diff --git a/changelogs/unreleased/winh-merge-request-commit-context.yml b/changelogs/unreleased/winh-merge-request-commit-context.yml new file mode 100644 index 00000000000..9e12a926af4 --- /dev/null +++ b/changelogs/unreleased/winh-merge-request-commit-context.yml @@ -0,0 +1,5 @@ +--- +title: Display commit ID for discussions made on merge request commits +merge_request: 23837 +author: +type: fixed diff --git a/changelogs/unreleased/winh-merge-request-commit-discussion.yml b/changelogs/unreleased/winh-merge-request-commit-discussion.yml deleted file mode 100644 index b0c6264369b..00000000000 --- a/changelogs/unreleased/winh-merge-request-commit-discussion.yml +++ /dev/null @@ -1,5 +0,0 @@ ---- -title: Display commit ID for commit diff discussion on merge request -merge_request: 23370 -author: -type: fixed diff --git a/changelogs/unreleased/winh-merge-request-diff-discussion-commit-id.yml b/changelogs/unreleased/winh-merge-request-diff-discussion-commit-id.yml deleted file mode 100644 index 2ce16a2b6b7..00000000000 --- a/changelogs/unreleased/winh-merge-request-diff-discussion-commit-id.yml +++ /dev/null @@ -1,5 +0,0 @@ ---- -title: Pass commit when posting diff discussions -merge_request: 23371 -author: -type: fixed diff --git a/changelogs/unreleased/winh-milestone-select.yml b/changelogs/unreleased/winh-milestone-select.yml deleted file mode 100644 index 8464fc6c541..00000000000 --- a/changelogs/unreleased/winh-milestone-select.yml +++ /dev/null @@ -1,5 +0,0 @@ ---- -title: Fix milestone select in issue sidebar of issue boards -merge_request: 23625 -author: -type: fixed diff --git a/changelogs/unreleased/winh-upgrade-gitlab-ui.yml b/changelogs/unreleased/winh-upgrade-gitlab-ui.yml new file mode 100644 index 00000000000..b312a329f5d --- /dev/null +++ b/changelogs/unreleased/winh-upgrade-gitlab-ui.yml @@ -0,0 +1,5 @@ +--- +title: Upgrade @gitlab/ui to 1.16.2 +merge_request: 23946 +author: +type: other diff --git a/changelogs/unreleased/workhorse-7-3-0.yml b/changelogs/unreleased/workhorse-7-3-0.yml deleted file mode 100644 index 6708b8a3cbb..00000000000 --- a/changelogs/unreleased/workhorse-7-3-0.yml +++ /dev/null @@ -1,5 +0,0 @@ ---- -title: Upgrade GitLab Workhorse to v7.3.0 -merge_request: 23489 -author: -type: other diff --git a/changelogs/unreleased/zj-backup-restore-object-pools.yml b/changelogs/unreleased/zj-backup-restore-object-pools.yml new file mode 100644 index 00000000000..26e1d49aa04 --- /dev/null +++ b/changelogs/unreleased/zj-backup-restore-object-pools.yml @@ -0,0 +1,5 @@ +--- +title: Restore Object Pools when restoring an object pool +merge_request: 23682 +author: +type: added diff --git a/changelogs/unreleased/zj-improve-gitaly-pb.yml b/changelogs/unreleased/zj-improve-gitaly-pb.yml deleted file mode 100644 index 506a0303d8a..00000000000 --- a/changelogs/unreleased/zj-improve-gitaly-pb.yml +++ /dev/null @@ -1,5 +0,0 @@ ---- -title: Show what RPC is called in the performance bar -merge_request: 23140 -author: -type: other diff --git a/changelogs/unreleased/zj-pool-repository-creation.yml b/changelogs/unreleased/zj-pool-repository-creation.yml deleted file mode 100644 index a24b96e4924..00000000000 --- a/changelogs/unreleased/zj-pool-repository-creation.yml +++ /dev/null @@ -1,5 +0,0 @@ ---- -title: Allow public forks to be deduplicated -merge_request: 23508 -author: -type: added diff --git a/changelogs/unreleased/zj-remove-broken-storage.yml b/changelogs/unreleased/zj-remove-broken-storage.yml deleted file mode 100644 index 9df87b40e09..00000000000 --- a/changelogs/unreleased/zj-remove-broken-storage.yml +++ /dev/null @@ -1,5 +0,0 @@ ---- -title: Remove obsolete gitlab_shell rake tasks -merge_request: 22417 -author: -type: removed diff --git a/config/application.rb b/config/application.rb index f10b8ed5bd2..349c7258852 100644 --- a/config/application.rb +++ b/config/application.rb @@ -5,12 +5,6 @@ require 'rails/all' Bundler.require(:default, Rails.env) module Gitlab - # This method is used for smooth upgrading from the current Rails 4.x to Rails 5.0. - # https://gitlab.com/gitlab-org/gitlab-ce/issues/14286 - def self.rails5? - !%w[0 false].include?(ENV["RAILS5"]) - end - class Application < Rails::Application require_dependency Rails.root.join('lib/gitlab/redis/wrapper') require_dependency Rails.root.join('lib/gitlab/redis/cache') @@ -26,9 +20,6 @@ module Gitlab # setting disabled require_dependency Rails.root.join('lib/mysql_zero_date') - # This can be removed when we drop support for rails 4 - require_dependency Rails.root.join('lib/rails4_migration_version') - # Settings in config/environments/* take precedence over those specified here. # Application configuration should go into files in config/initializers # -- all .rb files in that directory are automatically loaded. @@ -86,7 +77,7 @@ module Gitlab # namespaces/users. # https://github.com/rails/rails/blob/5-0-stable/actioncable/lib/action_cable.rb#L38 # Please change this value when configuring ActionCable for real usage. - config.action_cable.mount_path = "/-/cable" if rails5? + config.action_cable.mount_path = "/-/cable" # Configure sensitive parameters which will be filtered from the log file. # @@ -213,8 +204,6 @@ module Gitlab config.cache_store = :redis_store, caching_config_hash - config.active_record.raise_in_transactional_callbacks = true unless rails5? - config.active_job.queue_adapter = :sidekiq # This is needed for gitlab-shell diff --git a/config/boot.rb b/config/boot.rb index 725473ac7f6..2811f0e6188 100644 --- a/config/boot.rb +++ b/config/boot.rb @@ -1,11 +1,4 @@ -def rails5? - !%w[0 false].include?(ENV["RAILS5"]) -end - -require 'rubygems' unless rails5? - -gemfile = rails5? ? "Gemfile" : "Gemfile.rails4" -ENV['BUNDLE_GEMFILE'] ||= File.expand_path("../#{gemfile}", __dir__) +ENV['BUNDLE_GEMFILE'] ||= File.expand_path('../Gemfile', __dir__) # Set up gems listed in the Gemfile. require 'bundler/setup' if File.exist?(ENV['BUNDLE_GEMFILE']) diff --git a/config/environment.rb b/config/environment.rb index 3a52656a2c1..7e55c7803d3 100644 --- a/config/environment.rb +++ b/config/environment.rb @@ -1,11 +1,6 @@ # Load the rails application -# Remove this condition when upgraded to rails 5.0. -if %w[0 false].include?(ENV["RAILS5"]) - require File.expand_path('application', __dir__) -else - require_relative 'application' -end +require_relative 'application' # Initialize the rails application Rails.application.initialize! diff --git a/config/environments/production.rb b/config/environments/production.rb index 49a4e873093..09bcf49a9a5 100644 --- a/config/environments/production.rb +++ b/config/environments/production.rb @@ -9,11 +9,7 @@ Rails.application.configure do config.action_controller.perform_caching = true # Disable Rails's static asset server (Apache or nginx will already do this) - if Gitlab.rails5? - config.public_file_server.enabled = false - else - config.serve_static_files = false - end + config.public_file_server.enabled = false # Compress JavaScripts and CSS. config.assets.js_compressor = :uglifier diff --git a/config/environments/test.rb b/config/environments/test.rb index 072f93150a3..3461099253a 100644 --- a/config/environments/test.rb +++ b/config/environments/test.rb @@ -19,13 +19,8 @@ Rails.application.configure do # Configure static asset server for tests with Cache-Control for performance config.assets.compile = false if ENV['CI'] - if Gitlab.rails5? - config.public_file_server.enabled = true - config.public_file_server.headers = { 'Cache-Control' => 'public, max-age=3600' } - else - config.serve_static_files = true - config.static_cache_control = "public, max-age=3600" - end + config.public_file_server.enabled = true + config.public_file_server.headers = { 'Cache-Control' => 'public, max-age=3600' } # Show full error reports and disable caching config.consider_all_requests_local = true diff --git a/config/gitlab.yml.example b/config/gitlab.yml.example index 1c16b999e55..7fe85f0e0d7 100644 --- a/config/gitlab.yml.example +++ b/config/gitlab.yml.example @@ -612,7 +612,7 @@ production: &base storages: # You must have at least a `default` storage path. default: path: /home/git/repositories/ - gitaly_address: unix:/home/git/gitlab/tmp/sockets/private/gitaly.socket # TCP connections are supported too (e.g. tcp://host:port) + gitaly_address: unix:/home/git/gitlab/tmp/sockets/private/gitaly.socket # TCP connections are supported too (e.g. tcp://host:port). TLS connections are also supported using the system certificate pool (eg: tls://host:port). # gitaly_token: 'special token' # Optional: override global gitaly.token for this storage. ## Backup settings diff --git a/config/initializers/devise.rb b/config/initializers/8_devise.rb index 67eabb0b4fc..67eabb0b4fc 100644 --- a/config/initializers/devise.rb +++ b/config/initializers/8_devise.rb diff --git a/config/initializers/active_record_array_type_casting.rb b/config/initializers/active_record_array_type_casting.rb deleted file mode 100644 index a149e048ee2..00000000000 --- a/config/initializers/active_record_array_type_casting.rb +++ /dev/null @@ -1,23 +0,0 @@ -# Remove this initializer when upgraded to Rails 5.0 -unless Gitlab.rails5? - module ActiveRecord - class PredicateBuilder - class ArrayHandler - module TypeCasting - def call(attribute, value) - # This is necessary because by default ActiveRecord does not respect - # custom type definitions (like our `ShaAttribute`) when providing an - # array in `where`, like in `where(commit_sha: [sha1, sha2, sha3])`. - model = attribute.relation&.engine - type = model.user_provided_columns[attribute.name] if model - value = value.map { |value| type.type_cast_for_database(value) } if type - - super(attribute, value) - end - end - - prepend TypeCasting - end - end - end -end diff --git a/config/initializers/active_record_avoid_type_casting_in_uniqueness_validator.rb b/config/initializers/active_record_avoid_type_casting_in_uniqueness_validator.rb index ef4abb77bd7..228ced32188 100644 --- a/config/initializers/active_record_avoid_type_casting_in_uniqueness_validator.rb +++ b/config/initializers/active_record_avoid_type_casting_in_uniqueness_validator.rb @@ -20,75 +20,73 @@ # # This bug was fixed in Rails 5.1 by https://github.com/rails/rails/pull/24745/commits/aa062318c451512035c10898a1af95943b1a3803 -if Gitlab.rails5? - if Rails.version.start_with?("5.1") - raise "Remove this monkey patch: #{__FILE__}" - end +if Rails.gem_version >= Gem::Version.new("5.1") + raise "Remove this monkey patch: #{__FILE__}" +end - # Copy-paste from https://github.com/kamipo/rails/blob/aa062318c451512035c10898a1af95943b1a3803/activerecord/lib/active_record/validations/uniqueness.rb - # including local fixes to make Rubocop happy again. - module ActiveRecord - module Validations - class UniquenessValidator < ActiveModel::EachValidator # :nodoc: - def validate_each(record, attribute, value) - finder_class = find_finder_class_for(record) - table = finder_class.arel_table - value = map_enum_attribute(finder_class, attribute, value) +# Copy-paste from https://github.com/kamipo/rails/blob/aa062318c451512035c10898a1af95943b1a3803/activerecord/lib/active_record/validations/uniqueness.rb +# including local fixes to make Rubocop happy again. +module ActiveRecord + module Validations + class UniquenessValidator < ActiveModel::EachValidator # :nodoc: + def validate_each(record, attribute, value) + finder_class = find_finder_class_for(record) + table = finder_class.arel_table + value = map_enum_attribute(finder_class, attribute, value) - relation = build_relation(finder_class, table, attribute, value) + relation = build_relation(finder_class, table, attribute, value) - if record.persisted? - if finder_class.primary_key - relation = relation.where.not(finder_class.primary_key => record.id_was || record.id) - else - raise UnknownPrimaryKey.new(finder_class, "Can not validate uniqueness for persisted record without primary key.") - end + if record.persisted? + if finder_class.primary_key + relation = relation.where.not(finder_class.primary_key => record.id_was || record.id) + else + raise UnknownPrimaryKey.new(finder_class, "Can not validate uniqueness for persisted record without primary key.") end + end - relation = scope_relation(record, table, relation) - relation = relation.merge(options[:conditions]) if options[:conditions] + relation = scope_relation(record, table, relation) + relation = relation.merge(options[:conditions]) if options[:conditions] - if relation.exists? - error_options = options.except(:case_sensitive, :scope, :conditions) - error_options[:value] = value + if relation.exists? + error_options = options.except(:case_sensitive, :scope, :conditions) + error_options[:value] = value - record.errors.add(attribute, :taken, error_options) - end - rescue RangeError + record.errors.add(attribute, :taken, error_options) end + rescue RangeError + end - protected - - def build_relation(klass, table, attribute, value) #:nodoc: - if reflection = klass._reflect_on_association(attribute) - attribute = reflection.foreign_key - value = value.attributes[reflection.klass.primary_key] unless value.nil? - end + protected - # the attribute may be an aliased attribute - if klass.attribute_alias?(attribute) - attribute = klass.attribute_alias(attribute) - end + def build_relation(klass, table, attribute, value) #:nodoc: + if reflection = klass._reflect_on_association(attribute) + attribute = reflection.foreign_key + value = value.attributes[reflection.klass.primary_key] unless value.nil? + end - attribute_name = attribute.to_s + # the attribute may be an aliased attribute + if klass.attribute_alias?(attribute) + attribute = klass.attribute_alias(attribute) + end - column = klass.columns_hash[attribute_name] - cast_type = klass.type_for_attribute(attribute_name) + attribute_name = attribute.to_s - comparison = - if !options[:case_sensitive] && !value.nil? - # will use SQL LOWER function before comparison, unless it detects a case insensitive collation - klass.connection.case_insensitive_comparison(table, attribute, column, value) - else - klass.connection.case_sensitive_comparison(table, attribute, column, value) - end + column = klass.columns_hash[attribute_name] + cast_type = klass.type_for_attribute(attribute_name) - if value.nil? - klass.unscoped.where(comparison) + comparison = + if !options[:case_sensitive] && !value.nil? + # will use SQL LOWER function before comparison, unless it detects a case insensitive collation + klass.connection.case_insensitive_comparison(table, attribute, column, value) else - bind = Relation::QueryAttribute.new(attribute_name, value, cast_type) - klass.unscoped.where(comparison, bind) + klass.connection.case_sensitive_comparison(table, attribute, column, value) end + + if value.nil? + klass.unscoped.where(comparison) + else + bind = Relation::QueryAttribute.new(attribute_name, value, cast_type) + klass.unscoped.where(comparison, bind) end end end diff --git a/config/initializers/active_record_data_types.rb b/config/initializers/active_record_data_types.rb index 717e30b5b7e..e95157bfde5 100644 --- a/config/initializers/active_record_data_types.rb +++ b/config/initializers/active_record_data_types.rb @@ -65,7 +65,7 @@ elsif Gitlab::Database.mysql? prepend RegisterDateTimeWithTimeZone # Add the class `DateTimeWithTimeZone` so we can map `timestamp` to it. - class MysqlDateTimeWithTimeZone < (Gitlab.rails5? ? ActiveRecord::Type::DateTime : MysqlDateTime) + class MysqlDateTimeWithTimeZone < ActiveRecord::Type::DateTime def type :datetime_with_timezone end diff --git a/config/initializers/active_record_locking.rb b/config/initializers/active_record_locking.rb index 21ff323927b..1bd1a12e4b7 100644 --- a/config/initializers/active_record_locking.rb +++ b/config/initializers/active_record_locking.rb @@ -1,7 +1,10 @@ # rubocop:disable Lint/RescueException -# Remove this monkey-patch when all lock_version values are converted from NULLs to zeros. -# See https://gitlab.com/gitlab-org/gitlab-ce/issues/25228 +# Remove this monkey patch when we move to Rails 5.1, because the bug has been fixed in https://github.com/rails/rails/pull/26050. +if Rails.gem_version >= Gem::Version.new("5.1") + raise "Remove this monkey patch: #{__FILE__}" +end + module ActiveRecord module Locking module Optimistic @@ -16,12 +19,7 @@ module ActiveRecord return 0 if attribute_names.empty? lock_col = self.class.locking_column - previous_lock_value = send(lock_col).to_i - - # This line is added as a patch - previous_lock_value = nil if previous_lock_value == '0' || previous_lock_value == 0 - increment_lock attribute_names += [lock_col] @@ -32,7 +30,8 @@ module ActiveRecord affected_rows = relation.where( self.class.primary_key => id, - lock_col => previous_lock_value + # Patched because when `lock_version` is read as `0`, it may actually be `NULL` in the DB. + lock_col => previous_lock_value == 0 ? [nil, 0] : previous_lock_value ).update_all( attributes_for_update(attribute_names).map do |name| [name, _read_attribute(name)] @@ -64,21 +63,13 @@ module ActiveRecord # This is patched because we want `lock_version` default to `NULL` # rather than `0` - if Gitlab.rails5? - class LockingType - def deserialize(value) - super - end - - def serialize(value) - super - end + class LockingType + def deserialize(value) + super end - else - class LockingType < SimpleDelegator - def type_cast_from_database(value) - super - end + + def serialize(value) + super end end end diff --git a/config/initializers/application_controller_renderer.rb b/config/initializers/application_controller_renderer.rb deleted file mode 100644 index a65f8aecf9e..00000000000 --- a/config/initializers/application_controller_renderer.rb +++ /dev/null @@ -1,12 +0,0 @@ -# Remove this `if` condition when upgraded to rails 5.0. -# The body must be kept. -if Gitlab.rails5? - # Be sure to restart your server when you modify this file. - - # ActiveSupport::Reloader.to_prepare do - # ApplicationController.renderer.defaults.merge!( - # http_host: 'example.org', - # https: false - # ) - # end -end diff --git a/config/initializers/ar5_batching.rb b/config/initializers/ar5_batching.rb deleted file mode 100644 index 874455ce5af..00000000000 --- a/config/initializers/ar5_batching.rb +++ /dev/null @@ -1,40 +0,0 @@ -# Remove this file when upgraded to rails 5.0. -unless Gitlab.rails5? - module ActiveRecord - module Batches - # Differences from upstream: enumerator support was removed, and custom - # order/limit clauses are ignored without a warning. - def in_batches(of: 1000, start: nil, finish: nil, load: false) - raise "Must provide a block" unless block_given? - - relation = self.reorder(batch_order).limit(of) - relation = relation.where(arel_table[primary_key].gteq(start)) if start - relation = relation.where(arel_table[primary_key].lteq(finish)) if finish - batch_relation = relation - - loop do - if load - records = batch_relation.records - ids = records.map(&:id) - yielded_relation = self.where(primary_key => ids) - yielded_relation.load_records(records) - else - ids = batch_relation.pluck(primary_key) - yielded_relation = self.where(primary_key => ids) - end - - break if ids.empty? - - primary_key_offset = ids.last - raise ArgumentError.new("Primary key not included in the custom select clause") unless primary_key_offset - - yield yielded_relation - - break if ids.length < of - - batch_relation = relation.where(arel_table[primary_key].gt(primary_key_offset)) - end - end - end - end -end diff --git a/config/initializers/ar5_pg_10_support.rb b/config/initializers/ar5_pg_10_support.rb deleted file mode 100644 index 40548290ce8..00000000000 --- a/config/initializers/ar5_pg_10_support.rb +++ /dev/null @@ -1,58 +0,0 @@ -# Remove this file when upgraded to rails 5.0. -if !Gitlab.rails5? && Gitlab::Database.postgresql? - require 'active_record/connection_adapters/postgresql_adapter' - require 'active_record/connection_adapters/postgresql/schema_statements' - - # - # Monkey-patch the refused Rails 4.2 patch at https://github.com/rails/rails/pull/31330 - # - # Updates sequence logic to support PostgreSQL 10. - # - # rubocop:disable all - module ActiveRecord - module ConnectionAdapters - - # We need #postgresql_version to be public as in ActiveRecord 5 for seed_fu - # to work. In ActiveRecord 4, it is protected. - # https://github.com/mbleigh/seed-fu/issues/123 - class PostgreSQLAdapter - public :postgresql_version - end - - module PostgreSQL - module SchemaStatements - # Resets the sequence of a table's primary key to the maximum value. - def reset_pk_sequence!(table, pk = nil, sequence = nil) #:nodoc: - unless pk and sequence - default_pk, default_sequence = pk_and_sequence_for(table) - - pk ||= default_pk - sequence ||= default_sequence - end - - if @logger && pk && !sequence - @logger.warn "#{table} has primary key #{pk} with no default sequence" - end - - if pk && sequence - quoted_sequence = quote_table_name(sequence) - max_pk = select_value("SELECT MAX(#{quote_column_name pk}) FROM #{quote_table_name(table)}") - if max_pk.nil? - if postgresql_version >= 100000 - minvalue = select_value("SELECT seqmin FROM pg_sequence WHERE seqrelid = #{quote(quoted_sequence)}::regclass") - else - minvalue = select_value("SELECT min_value FROM #{quoted_sequence}") - end - end - - select_value <<-end_sql, 'SCHEMA' - SELECT setval(#{quote(quoted_sequence)}, #{max_pk ? max_pk : minvalue}, #{max_pk ? true : false}) - end_sql - end - end - end - end - end - end - # rubocop:enable all -end diff --git a/config/initializers/carrierwave_patch.rb b/config/initializers/carrierwave_patch.rb deleted file mode 100644 index 35ffff03abe..00000000000 --- a/config/initializers/carrierwave_patch.rb +++ /dev/null @@ -1,29 +0,0 @@ -# This monkey patches CarrierWave 1.2.3 to make Google Cloud Storage work with -# extra query parameters: -# https://github.com/carrierwaveuploader/carrierwave/pull/2332/files -module CarrierWave - module Storage - class Fog < Abstract - class File - def authenticated_url(options = {}) - if %w(AWS Google Rackspace OpenStack).include?(@uploader.fog_credentials[:provider]) - # avoid a get by using local references - local_directory = connection.directories.new(key: @uploader.fog_directory) - local_file = local_directory.files.new(key: path) - expire_at = ::Fog::Time.now + @uploader.fog_authenticated_url_expiration - case @uploader.fog_credentials[:provider] - when 'AWS', 'Google' - local_file.url(expire_at, options) - when 'Rackspace' - connection.get_object_https_url(@uploader.fog_directory, path, expire_at, options) - when 'OpenStack' - connection.get_object_https_url(@uploader.fog_directory, path, expire_at) - else - local_file.url(expire_at) - end - end - end - end - end - end -end diff --git a/config/initializers/mysql_set_length_for_binary_indexes.rb b/config/initializers/mysql_set_length_for_binary_indexes.rb index 0445d8fcae2..552f3a20a95 100644 --- a/config/initializers/mysql_set_length_for_binary_indexes.rb +++ b/config/initializers/mysql_set_length_for_binary_indexes.rb @@ -2,28 +2,6 @@ # MySQL adapter apply a length of 20. Otherwise MySQL can't create an index on # binary columns. -# This module can be removed once a Rails 5 schema is used. -# It can't be wrapped in a check that checks Gitlab.rails5? because -# the old Rails 4 schema layout is still used -module MysqlSetLengthForBinaryIndex - def add_index(table_name, column_names, options = {}) - options[:length] ||= {} - Array(column_names).each do |column_name| - column = ActiveRecord::Base.connection.columns(table_name).find { |c| c.name == column_name } - - if column&.type == :binary - options[:length][column_name] = 20 - end - end - - super(table_name, column_names, options) - end -end - -if defined?(ActiveRecord::ConnectionAdapters::Mysql2Adapter) - ActiveRecord::ConnectionAdapters::Mysql2Adapter.send(:prepend, MysqlSetLengthForBinaryIndex) -end - module MysqlSetLengthForBinaryIndexAndIgnorePostgresOptionsForSchema # This method is used in Rails 5 schema loading as t.index def index(column_names, options = {}) @@ -34,15 +12,6 @@ module MysqlSetLengthForBinaryIndexAndIgnorePostgresOptionsForSchema return end - # when running rails 4 with rails 5 schema, rails 4 doesn't support multiple - # indexes on the same set of columns. Mysql doesn't support partial indexes, so if - # an index already exists and we add another index, skip it if it's partial: - # see https://gitlab.com/gitlab-org/gitlab-ce/merge_requests/21492#note_102821326 - if !Gitlab.rails5? && indexes[column_names] && options[:where] - warn "WARNING: index on columns #{column_names} already exists and partial index is not supported, skipping." - return - end - options[:length] ||= {} Array(column_names).each do |column_name| column = columns.find { |c| c.name == column_name } @@ -56,14 +25,6 @@ module MysqlSetLengthForBinaryIndexAndIgnorePostgresOptionsForSchema end end -def mysql_adapter? - defined?(ActiveRecord::ConnectionAdapters::Mysql2Adapter) && ActiveRecord::Base.connection.is_a?(ActiveRecord::ConnectionAdapters::Mysql2Adapter) -end - -if Gitlab.rails5? - if defined?(ActiveRecord::ConnectionAdapters::MySQL::TableDefinition) - ActiveRecord::ConnectionAdapters::MySQL::TableDefinition.send(:prepend, MysqlSetLengthForBinaryIndexAndIgnorePostgresOptionsForSchema) - end -elsif mysql_adapter? && defined?(ActiveRecord::ConnectionAdapters::TableDefinition) - ActiveRecord::ConnectionAdapters::TableDefinition.send(:prepend, MysqlSetLengthForBinaryIndexAndIgnorePostgresOptionsForSchema) +if defined?(ActiveRecord::ConnectionAdapters::MySQL::TableDefinition) + ActiveRecord::ConnectionAdapters::MySQL::TableDefinition.send(:prepend, MysqlSetLengthForBinaryIndexAndIgnorePostgresOptionsForSchema) end diff --git a/config/initializers/new_framework_defaults.rb b/config/initializers/new_framework_defaults.rb index 2d130bc0bf8..5adb9f7a4b4 100644 --- a/config/initializers/new_framework_defaults.rb +++ b/config/initializers/new_framework_defaults.rb @@ -1,29 +1,27 @@ # Remove this `if` condition when upgraded to rails 5.0. # The body must be kept. -if Gitlab.rails5? - # Be sure to restart your server when you modify this file. - # - # This file contains migration options to ease your Rails 5.0 upgrade. - # - # Once upgraded flip defaults one by one to migrate to the new default. - # - # Read the Guide for Upgrading Ruby on Rails for more info on each option. +# Be sure to restart your server when you modify this file. +# +# This file contains migration options to ease your Rails 5.0 upgrade. +# +# Once upgraded flip defaults one by one to migrate to the new default. +# +# Read the Guide for Upgrading Ruby on Rails for more info on each option. - Rails.application.config.action_controller.raise_on_unfiltered_parameters = true +Rails.application.config.action_controller.raise_on_unfiltered_parameters = true - # Enable per-form CSRF tokens. Previous versions had false. - Rails.application.config.action_controller.per_form_csrf_tokens = false +# Enable per-form CSRF tokens. Previous versions had false. +Rails.application.config.action_controller.per_form_csrf_tokens = false - # Enable origin-checking CSRF mitigation. Previous versions had false. - Rails.application.config.action_controller.forgery_protection_origin_check = false +# Enable origin-checking CSRF mitigation. Previous versions had false. +Rails.application.config.action_controller.forgery_protection_origin_check = false - # Make Ruby 2.4 preserve the timezone of the receiver when calling `to_time`. - # Previous versions had false. - ActiveSupport.to_time_preserves_timezone = false +# Make Ruby 2.4 preserve the timezone of the receiver when calling `to_time`. +# Previous versions had false. +ActiveSupport.to_time_preserves_timezone = false - # Require `belongs_to` associations by default. Previous versions had false. - Rails.application.config.active_record.belongs_to_required_by_default = false +# Require `belongs_to` associations by default. Previous versions had false. +Rails.application.config.active_record.belongs_to_required_by_default = false - # Do not halt callback chains when a callback returns false. Previous versions had true. - ActiveSupport.halt_callback_chains_on_return_false = true -end +# Do not halt callback chains when a callback returns false. Previous versions had true. +ActiveSupport.halt_callback_chains_on_return_false = true diff --git a/config/initializers/postgresql_opclasses_support.rb b/config/initializers/postgresql_opclasses_support.rb index 5d643b75dd8..70b530415f5 100644 --- a/config/initializers/postgresql_opclasses_support.rb +++ b/config/initializers/postgresql_opclasses_support.rb @@ -41,10 +41,7 @@ module ActiveRecord # Abstract representation of an index definition on a table. Instances of # this type are typically created and returned by methods in database # adapters. e.g. ActiveRecord::ConnectionAdapters::AbstractMysqlAdapter#indexes - attrs = [:table, :name, :unique, :columns, :lengths, :orders, :where, :type, :using, :opclasses] - - # In Rails 5 the second last attribute is newly `:comment` - attrs.insert(-2, :comment) if Gitlab.rails5? + attrs = [:table, :name, :unique, :columns, :lengths, :orders, :where, :type, :using, :comment, :opclasses] class IndexDefinition < Struct.new(*attrs) #:nodoc: end @@ -112,15 +109,8 @@ module ActiveRecord result.map do |row| index_name = row[0] - unique = if Gitlab.rails5? - row[1] - else - row[1] == 't' - end - indkey = row[2].split(" ") - if Gitlab.rails5? - indkey = indkey.map(&:to_i) - end + unique = row[1] + indkey = row[2].split(" ").map(&:to_i) inddef = row[3] oid = row[4] @@ -144,8 +134,7 @@ module ActiveRecord [column, opclass] if opclass end.compact] - index_attrs = [table_name, index_name, unique, column_names, [], orders, where, nil, using, opclasses] - index_attrs.insert(-2, nil) if Gitlab.rails5? # include index comment for Rails 5 + index_attrs = [table_name, index_name, unique, column_names, [], orders, where, nil, using, nil, opclasses] IndexDefinition.new(*index_attrs) end @@ -205,7 +194,7 @@ module ActiveRecord index_parts << "using: #{index.using.inspect}" if index.using index_parts << "type: #{index.type.inspect}" if index.type index_parts << "opclasses: #{index.opclasses.inspect}" if index.opclasses.present? - index_parts << "comment: #{index.comment.inspect}" if Gitlab.rails5? && index.comment + index_parts << "comment: #{index.comment.inspect}" if index.comment index_parts end diff --git a/config/initializers/sidekiq.rb b/config/initializers/sidekiq.rb index 6aba6c7c21d..be4183f39be 100644 --- a/config/initializers/sidekiq.rb +++ b/config/initializers/sidekiq.rb @@ -42,7 +42,9 @@ Sidekiq.configure_server do |config| end if Feature::FlipperFeature.table_exists? && Feature.enabled?(:gitlab_sidekiq_reliable_fetcher) - Sidekiq::ReliableFetcher.setup_reliable_fetch!(config) + # By default we're going to use Semi Reliable Fetch + config.options[:semi_reliable_fetch] = Feature.enabled?(:gitlab_sidekiq_enable_semi_reliable_fetcher, default_enabled: true) + Sidekiq::ReliableFetch.setup_reliable_fetch!(config) end # Sidekiq-cron: load recurring jobs from gitlab.yml diff --git a/config/initializers/static_files.rb b/config/initializers/static_files.rb index a0b8b68f3ef..e02f0868e9f 100644 --- a/config/initializers/static_files.rb +++ b/config/initializers/static_files.rb @@ -1,26 +1,17 @@ app = Rails.application -if (Gitlab.rails5? && app.config.public_file_server.enabled) || app.config.serve_static_files +if app.config.public_file_server.enabled # The `ActionDispatch::Static` middleware intercepts requests for static files # by checking if they exist in the `/public` directory. # We're replacing it with our `Gitlab::Middleware::Static` that does the same, # except ignoring `/uploads`, letting those go through to the GitLab Rails app. - if Gitlab.rails5? - app.config.middleware.swap( - ActionDispatch::Static, - Gitlab::Middleware::Static, - app.paths["public"].first, - headers: app.config.public_file_server.headers - ) - else - app.config.middleware.swap( - ActionDispatch::Static, - Gitlab::Middleware::Static, - app.paths["public"].first, - app.config.static_cache_control - ) - end + app.config.middleware.swap( + ActionDispatch::Static, + Gitlab::Middleware::Static, + app.paths["public"].first, + headers: app.config.public_file_server.headers + ) # If webpack-dev-server is configured, proxy webpack's public directory # instead of looking for static assets diff --git a/config/initializers/trusted_proxies.rb b/config/initializers/trusted_proxies.rb index ca2eed664ed..7af465d8443 100644 --- a/config/initializers/trusted_proxies.rb +++ b/config/initializers/trusted_proxies.rb @@ -26,12 +26,10 @@ Rails.application.config.action_dispatch.trusted_proxies = ( # A monkey patch to make trusted proxies work with Rails 5.0. # Inspired by https://github.com/rails/rails/issues/5223#issuecomment-263778719 # Remove this monkey patch when upstream is fixed. -if Gitlab.rails5? - module TrustedProxyMonkeyPatch - def ip - @ip ||= (get_header("action_dispatch.remote_ip") || super).to_s - end +module TrustedProxyMonkeyPatch + def ip + @ip ||= (get_header("action_dispatch.remote_ip") || super).to_s end - - ActionDispatch::Request.send(:include, TrustedProxyMonkeyPatch) end + +ActionDispatch::Request.send(:include, TrustedProxyMonkeyPatch) diff --git a/config/initializers/8_metrics.rb b/config/initializers/zz_metrics.rb index 468f80939d7..462e8c811a6 100644 --- a/config/initializers/8_metrics.rb +++ b/config/initializers/zz_metrics.rb @@ -1,3 +1,6 @@ +# This file was prefixed with zz_ because we want to load it the last! +# See: https://gitlab.com/gitlab-org/gitlab-ce/issues/55611 + # Autoload all classes that we want to instrument, and instrument the methods we # need. This takes the Gitlab::Metrics::Instrumentation module as an argument so # that we can stub it for testing, as it is only called when metrics are diff --git a/config/prometheus/common_metrics.yml b/config/prometheus/common_metrics.yml index 52023a2e3cb..9bdaf1575e9 100644 --- a/config/prometheus/common_metrics.yml +++ b/config/prometheus/common_metrics.yml @@ -1,4 +1,5 @@ -- group: Response metrics (NGINX Ingress) + # NGINX Ingress metrics for pre-0.16.0 versions +- group: Response metrics (NGINX Ingress VTS) priority: 10 metrics: - title: "Throughput" @@ -40,6 +41,51 @@ query_range: 'sum(rate(nginx_upstream_responses_total{status_code="5xx", upstream=~"%{kube_namespace}-%{ci_environment_slug}-.*"}[2m])) / sum(rate(nginx_upstream_responses_total{upstream=~"%{kube_namespace}-%{ci_environment_slug}-.*"}[2m])) * 100' label: 5xx Errors unit: "%" +# NGINX Ingress metrics for post-0.16.0 versions +- group: Response metrics (NGINX Ingress) + priority: 10 + metrics: + - title: "Throughput" + y_label: "Requests / Sec" + required_metrics: + - nginx_ingress_controller_requests + weight: 1 + queries: + - id: response_metrics_nginx_ingress_16_throughput_status_code + query_range: 'sum(label_replace(rate(nginx_ingress_controller_requests{namespace="%{kube_namespace}",ingress=~".*%{ci_environment_slug}.*"}[2m]), "status_code", "${1}xx", "status", "(.)..")) by (status_code)' + unit: req / sec + label: Status Code + series: + - label: status_code + when: + - value: 2xx + color: green + - value: 3xx + color: blue + - value: 4xx + color: orange + - value: 5xx + color: red + - title: "Latency" + y_label: "Latency (ms)" + required_metrics: + - nginx_ingress_controller_ingress_upstream_latency_seconds_sum + weight: 1 + queries: + - id: response_metrics_nginx_ingress_16_latency_pod_average + query_range: 'sum(rate(nginx_ingress_controller_ingress_upstream_latency_seconds_sum{namespace="%{kube_namespace}",ingress=~".*%{ci_environment_slug}.*"}[2m])) / sum(rate(nginx_ingress_controller_ingress_upstream_latency_seconds_count{namespace="%{kube_namespace}",ingress=~".*%{ci_environment_slug}.*"}[2m])) * 1000' + label: Pod average + unit: ms + - title: "HTTP Error Rate" + y_label: "HTTP Errors" + required_metrics: + - nginx_ingress_controller_requests + weight: 1 + queries: + - id: response_metrics_nginx_ingress_16_http_error_rate + query_range: 'sum(rate(nginx_ingress_controller_requests{status=~"5.*",namespace="%{kube_namespace}",ingress=~".*%{ci_environment_slug}.*"}[2m])) / sum(rate(nginx_ingress_controller_requests{namespace="%{kube_namespace}",ingress=~".*%{ci_environment_slug}.*"}[2m])) * 100' + label: 5xx Errors + unit: "%" - group: Response metrics (HA Proxy) priority: 10 metrics: diff --git a/config/routes/api.rb b/config/routes/api.rb index b1aebf4d606..3719b7d3a1e 100644 --- a/config/routes/api.rb +++ b/config/routes/api.rb @@ -1,4 +1,4 @@ -constraints(::Constraints::FeatureConstrainer.new(:graphql)) do +constraints(::Constraints::FeatureConstrainer.new(:graphql, default_enabled: true)) do post '/api/graphql', to: 'graphql#execute' mount GraphiQL::Rails::Engine, at: '/-/graphql-explorer', graphql_path: '/api/graphql' end diff --git a/config/routes/project.rb b/config/routes/project.rb index 7d0623cb904..03c95b61e51 100644 --- a/config/routes/project.rb +++ b/config/routes/project.rb @@ -95,6 +95,7 @@ constraints(::Constraints::ProjectUrlConstrainer.new) do end end + resources :releases, only: [:index] resources :forks, only: [:index, :new, :create] resource :import, only: [:new, :create, :show] diff --git a/config/routes/repository.rb b/config/routes/repository.rb index d439cb9acbd..96975759709 100644 --- a/config/routes/repository.rb +++ b/config/routes/repository.rb @@ -55,7 +55,7 @@ scope format: false do resources :branches, only: [:index, :new, :create, :destroy] delete :merged_branches, controller: 'branches', action: :destroy_all_merged resources :tags, only: [:index, :show, :new, :create, :destroy] do - resource :release, only: [:edit, :update] + resource :release, controller: 'tags/releases', only: [:edit, :update] end resources :protected_branches, only: [:index, :show, :create, :update, :destroy] diff --git a/config/routes/uploads.rb b/config/routes/uploads.rb index 6becadd57ae..b594f55f8a0 100644 --- a/config/routes/uploads.rb +++ b/config/routes/uploads.rb @@ -17,7 +17,8 @@ scope path: :uploads do # Appearance get "-/system/:model/:mounted_as/:id/:filename", to: "uploads#show", - constraints: { model: /appearance/, mounted_as: /logo|header_logo|favicon/, filename: /.+/ } + constraints: { model: /appearance/, mounted_as: /logo|header_logo|favicon/, filename: /.+/ }, + as: 'appearance_upload' # Project markdown uploads get ":namespace_id/:project_id/:secret/:filename", diff --git a/danger/commit_messages/Dangerfile b/danger/commit_messages/Dangerfile index c5ebb9b457e..abb36098629 100644 --- a/danger/commit_messages/Dangerfile +++ b/danger/commit_messages/Dangerfile @@ -149,7 +149,7 @@ def lint_commits(commits) if !details && too_many_changed_lines?(commit) fail_commit( commit, - 'Commits that change 30 or more lines in more than three files ' \ + 'Commits that change 30 or more lines across at least three files ' \ 'must describe these changes in the commit body' ) diff --git a/danger/documentation/Dangerfile b/danger/documentation/Dangerfile index be7b301866d..52af837c261 100644 --- a/danger/documentation/Dangerfile +++ b/danger/documentation/Dangerfile @@ -32,7 +32,7 @@ to be reviewed. | Tech writer | Stage(s) | | ------------ | ------------------------------------------------------------ | | `@marcia` | ~Create ~Release + ~"development guidelines" | -| `@axil` | ~Distribution ~Gitaly ~Gitter ~Monitoring ~Packaging ~Secure | +| `@axil` | ~Distribution ~Gitaly ~Gitter ~Monitoring ~Package ~Secure | | `@eread` | ~Manage ~Configure ~Geo ~Verify | | `@mikelewis` | ~Plan | diff --git a/danger/duplicate_yarn_dependencies/Dangerfile b/danger/duplicate_yarn_dependencies/Dangerfile new file mode 100644 index 00000000000..25f81ec86a4 --- /dev/null +++ b/danger/duplicate_yarn_dependencies/Dangerfile @@ -0,0 +1,27 @@ +# frozen_string_literal: true + +return unless helper.all_changed_files.include? 'yarn.lock' + +duplicate = `node_modules/.bin/yarn-deduplicate --list --strategy fewer yarn.lock` + .split(/$/) + .map(&:strip) + .reject(&:empty?) + +return if duplicate.empty? + +warn 'This merge request has introduced duplicated yarn dependencies.' + +markdown(<<~MARKDOWN) + ## Duplicate yarn dependencies + + The following dependencies should be de-duplicated: + + * #{duplicate.map { |path| "`#{path}`" }.join("\n* ")} + + Please run the following command and commit the changes to `yarn.lock`: + + ``` + node_modules/.bin/yarn-deduplicate --strategy fewer yarn.lock \\ + && yarn install + ``` +MARKDOWN diff --git a/danger/gemfile/Dangerfile b/danger/gemfile/Dangerfile index 8ef4a464fe4..4e91abc371a 100644 --- a/danger/gemfile/Dangerfile +++ b/danger/gemfile/Dangerfile @@ -4,7 +4,7 @@ GEMFILE_LOCK_NOT_UPDATED_MESSAGE = <<~MSG.freeze Usually, when %<gemfile>s is updated, you should run ``` bundle install && \ - BUNDLE_GEMFILE=Gemfile.rails5 bundle install + bundle install ``` or diff --git a/db/fixtures/development/03_settings.rb b/db/fixtures/development/03_settings.rb new file mode 100644 index 00000000000..3a4a5d436bf --- /dev/null +++ b/db/fixtures/development/03_settings.rb @@ -0,0 +1,8 @@ +# frozen_string_literal: true + +# Enable hashed storage, in development mode, for all projects by default. +Gitlab::Seeder.quiet do + ApplicationSetting.create_from_defaults unless ApplicationSetting.current_without_cache + ApplicationSetting.current_without_cache.update!(hashed_storage_enabled: true) + print '.' +end diff --git a/db/fixtures/development/24_forks.rb b/db/fixtures/development/24_forks.rb index 61e39c871e6..5eb5956ec74 100644 --- a/db/fixtures/development/24_forks.rb +++ b/db/fixtures/development/24_forks.rb @@ -4,6 +4,12 @@ Sidekiq::Testing.inline! do Gitlab::Seeder.quiet do User.all.sample(10).each do |user| source_project = Project.public_only.sample + + ## + # 04_project.rb might not have created a public project because + # we use randomized approach (e.g. `Array#sample`). + return unless source_project + fork_project = Projects::ForkService.new(source_project, user, namespace: user.namespace).execute if fork_project.valid? diff --git a/db/importers/common_metrics_importer.rb b/db/importers/common_metrics_importer.rb index 6302394d7a6..deadd653ae9 100644 --- a/db/importers/common_metrics_importer.rb +++ b/db/importers/common_metrics_importer.rb @@ -4,11 +4,12 @@ module Importers class PrometheusMetric < ActiveRecord::Base enum group: { # built-in groups - nginx_ingress: -1, + nginx_ingress_vts: -1, ha_proxy: -2, aws_elb: -3, nginx: -4, kubernetes: -5, + nginx_ingress: -6, # custom groups business: 0, @@ -22,6 +23,7 @@ module Importers business: _('Business metrics (Custom)'), response: _('Response metrics (Custom)'), system: _('System metrics (Custom)'), + nginx_ingress_vts: _('Response metrics (NGINX Ingress VTS)'), nginx_ingress: _('Response metrics (NGINX Ingress)'), ha_proxy: _('Response metrics (HA Proxy)'), aws_elb: _('Response metrics (AWS ELB)'), diff --git a/db/migrate/20160226114608_add_trigram_indexes_for_searching.rb b/db/migrate/20160226114608_add_trigram_indexes_for_searching.rb index 82b54c552e0..af8b08c095a 100644 --- a/db/migrate/20160226114608_add_trigram_indexes_for_searching.rb +++ b/db/migrate/20160226114608_add_trigram_indexes_for_searching.rb @@ -37,12 +37,7 @@ class AddTrigramIndexesForSearching < ActiveRecord::Migration[4.2] res = execute("SELECT true AS enabled FROM pg_available_extensions WHERE name = 'pg_trgm' AND installed_version IS NOT NULL;") row = res.first - check = if Gitlab.rails5? - true - else - 't' - end - row && row['enabled'] == check ? true : false + row && row['enabled'] == true end def create_trigrams_extension diff --git a/db/migrate/20161207231620_fixup_environment_name_uniqueness.rb b/db/migrate/20161207231620_fixup_environment_name_uniqueness.rb index 7cae09021cd..420f0ccb45c 100644 --- a/db/migrate/20161207231620_fixup_environment_name_uniqueness.rb +++ b/db/migrate/20161207231620_fixup_environment_name_uniqueness.rb @@ -1,5 +1,4 @@ class FixupEnvironmentNameUniqueness < ActiveRecord::Migration[4.2] - include Gitlab::Database::ArelMethods include Gitlab::Database::MigrationHelpers DOWNTIME = true @@ -42,7 +41,7 @@ class FixupEnvironmentNameUniqueness < ActiveRecord::Migration[4.2] conflicts.each do |id, name| update_sql = - arel_update_manager + Arel::UpdateManager.new .table(environments) .set(environments[:name] => name + "-" + id.to_s) .where(environments[:id].eq(id)) diff --git a/db/migrate/20161207231626_add_environment_slug.rb b/db/migrate/20161207231626_add_environment_slug.rb index 4657b023dfa..993b9bd3330 100644 --- a/db/migrate/20161207231626_add_environment_slug.rb +++ b/db/migrate/20161207231626_add_environment_slug.rb @@ -2,7 +2,6 @@ # for more information on how to write migrations for GitLab. class AddEnvironmentSlug < ActiveRecord::Migration[4.2] - include Gitlab::Database::ArelMethods include Gitlab::Database::MigrationHelpers DOWNTIME = true @@ -20,7 +19,7 @@ class AddEnvironmentSlug < ActiveRecord::Migration[4.2] finder = environments.project(:id, :name) connection.exec_query(finder.to_sql).rows.each do |id, name| - updater = arel_update_manager + updater = Arel::UpdateManager.new .table(environments) .set(environments[:slug] => generate_slug(name)) .where(environments[:id].eq(id)) diff --git a/db/migrate/20180413022611_create_missing_namespace_for_internal_users.rb b/db/migrate/20180413022611_create_missing_namespace_for_internal_users.rb index 90d869a8c10..8de8b3bcc2e 100644 --- a/db/migrate/20180413022611_create_missing_namespace_for_internal_users.rb +++ b/db/migrate/20180413022611_create_missing_namespace_for_internal_users.rb @@ -46,7 +46,7 @@ class CreateMissingNamespaceForInternalUsers < ActiveRecord::Migration[4.2] end insert_query = "INSERT INTO namespaces(owner_id, path, name, created_at, updated_at) VALUES(#{user_id}, '#{path}', '#{path}', NOW(), NOW())" - namespace_id = connection.insert_sql(insert_query) + namespace_id = connection.insert(insert_query) create_route(namespace_id) end diff --git a/db/migrate/20181006004100_import_common_metrics_nginx_vts.rb b/db/migrate/20181006004100_import_common_metrics_nginx_vts.rb new file mode 100644 index 00000000000..5cd312837df --- /dev/null +++ b/db/migrate/20181006004100_import_common_metrics_nginx_vts.rb @@ -0,0 +1,15 @@ +class ImportCommonMetricsNginxVts < ActiveRecord::Migration[5.0] + include Gitlab::Database::MigrationHelpers + + require Rails.root.join('db/importers/common_metrics_importer.rb') + + DOWNTIME = false + + def up + Importers::CommonMetricsImporter.new.execute + end + + def down + # no-op + end +end diff --git a/db/migrate/20181031145139_add_protected_ci_variables_to_application_settings.rb b/db/migrate/20181031145139_add_protected_ci_variables_to_application_settings.rb new file mode 100644 index 00000000000..85ee34afe1e --- /dev/null +++ b/db/migrate/20181031145139_add_protected_ci_variables_to_application_settings.rb @@ -0,0 +1,17 @@ +# frozen_string_literal: true + +class AddProtectedCiVariablesToApplicationSettings < ActiveRecord::Migration[5.0] + include Gitlab::Database::MigrationHelpers + + DOWNTIME = false + + disable_ddl_transaction! + + def up + add_column_with_default(:application_settings, :protected_ci_variables, :boolean, default: false, allow_null: false) + end + + def down + remove_column(:application_settings, :protected_ci_variables) + end +end diff --git a/db/migrate/20181123144235_create_suggestions.rb b/db/migrate/20181123144235_create_suggestions.rb new file mode 100644 index 00000000000..1723f6de7eb --- /dev/null +++ b/db/migrate/20181123144235_create_suggestions.rb @@ -0,0 +1,20 @@ +# frozen_string_literal: true + +class CreateSuggestions < ActiveRecord::Migration[5.0] + DOWNTIME = false + + def change + create_table :suggestions, id: :bigserial do |t| + t.references :note, foreign_key: { on_delete: :cascade }, null: false + t.integer :relative_order, null: false, limit: 2 + t.boolean :applied, null: false, default: false + t.string :commit_id + t.text :from_content, null: false + t.text :to_content, null: false + + t.index [:note_id, :relative_order], + name: 'index_suggestions_on_note_id_and_relative_order', + unique: true + end + end +end diff --git a/db/migrate/20181211092510_add_name_author_id_and_sha_to_releases.rb b/db/migrate/20181211092510_add_name_author_id_and_sha_to_releases.rb new file mode 100644 index 00000000000..60815e0c31a --- /dev/null +++ b/db/migrate/20181211092510_add_name_author_id_and_sha_to_releases.rb @@ -0,0 +1,13 @@ +# frozen_string_literal: true + +class AddNameAuthorIdAndShaToReleases < ActiveRecord::Migration[5.0] + include Gitlab::Database::MigrationHelpers + + DOWNTIME = false + + def change + add_column :releases, :author_id, :integer + add_column :releases, :name, :string + add_column :releases, :sha, :string + end +end diff --git a/db/migrate/20181211092514_add_author_id_index_and_fk_to_releases.rb b/db/migrate/20181211092514_add_author_id_index_and_fk_to_releases.rb new file mode 100644 index 00000000000..f6350a49944 --- /dev/null +++ b/db/migrate/20181211092514_add_author_id_index_and_fk_to_releases.rb @@ -0,0 +1,21 @@ +# frozen_string_literal: true + +class AddAuthorIdIndexAndFkToReleases < ActiveRecord::Migration[5.0] + include Gitlab::Database::MigrationHelpers + + DOWNTIME = false + + disable_ddl_transaction! + + def up + add_concurrent_index :releases, :author_id + + add_concurrent_foreign_key :releases, :users, column: :author_id, on_delete: :nullify + end + + def down + remove_foreign_key :releases, column: :author_id + + remove_concurrent_index :releases, column: :author_id + end +end diff --git a/db/migrate/20181212104941_backfill_releases_name_with_tag_name.rb b/db/migrate/20181212104941_backfill_releases_name_with_tag_name.rb new file mode 100644 index 00000000000..e152dc87bc1 --- /dev/null +++ b/db/migrate/20181212104941_backfill_releases_name_with_tag_name.rb @@ -0,0 +1,17 @@ +# frozen_string_literal: true + +class BackfillReleasesNameWithTagName < ActiveRecord::Migration[5.0] + include Gitlab::Database::MigrationHelpers + + DOWNTIME = false + + disable_ddl_transaction! + + def up + update_column_in_batches(:releases, :name, Release.arel_table[:tag]) + end + + def down + # no-op + end +end diff --git a/db/post_migrate/20161109150329_fix_project_records_with_invalid_visibility.rb b/db/post_migrate/20161109150329_fix_project_records_with_invalid_visibility.rb index d77a22bfb69..6d9c7e4ed72 100644 --- a/db/post_migrate/20161109150329_fix_project_records_with_invalid_visibility.rb +++ b/db/post_migrate/20161109150329_fix_project_records_with_invalid_visibility.rb @@ -1,5 +1,4 @@ class FixProjectRecordsWithInvalidVisibility < ActiveRecord::Migration[4.2] - include Gitlab::Database::ArelMethods include Gitlab::Database::MigrationHelpers BATCH_SIZE = 500 @@ -34,7 +33,7 @@ class FixProjectRecordsWithInvalidVisibility < ActiveRecord::Migration[4.2] end updates.each do |visibility_level, project_ids| - updater = arel_update_manager + updater = Arel::UpdateManager.new .table(projects) .set(projects[:visibility_level] => visibility_level) .where(projects[:id].in(project_ids)) diff --git a/db/post_migrate/20170324160416_migrate_user_activities_to_users_last_activity_on.rb b/db/post_migrate/20170324160416_migrate_user_activities_to_users_last_activity_on.rb index 73989339ad9..61a5c364b2f 100644 --- a/db/post_migrate/20170324160416_migrate_user_activities_to_users_last_activity_on.rb +++ b/db/post_migrate/20170324160416_migrate_user_activities_to_users_last_activity_on.rb @@ -1,6 +1,5 @@ # rubocop:disable Migration/UpdateLargeTable class MigrateUserActivitiesToUsersLastActivityOn < ActiveRecord::Migration[4.2] - include Gitlab::Database::ArelMethods include Gitlab::Database::MigrationHelpers disable_ddl_transaction! @@ -40,7 +39,7 @@ class MigrateUserActivitiesToUsersLastActivityOn < ActiveRecord::Migration[4.2] activities = activities(day.at_beginning_of_day, day.at_end_of_day, page: page) update_sql = - arel_update_manager + Arel::UpdateManager.new .table(users_table) .set(users_table[:last_activity_on] => day.to_date) .where(users_table[:username].in(activities.map(&:first))) diff --git a/db/post_migrate/20181218192239_backfill_project_repositories_for_legacy_storage_projects.rb b/db/post_migrate/20181218192239_backfill_project_repositories_for_legacy_storage_projects.rb new file mode 100644 index 00000000000..42f96750789 --- /dev/null +++ b/db/post_migrate/20181218192239_backfill_project_repositories_for_legacy_storage_projects.rb @@ -0,0 +1,26 @@ +# frozen_string_literal: true + +class BackfillProjectRepositoriesForLegacyStorageProjects < ActiveRecord::Migration[5.0] + include Gitlab::Database::MigrationHelpers + + DOWNTIME = false + BATCH_SIZE = 1_000 + DELAY_INTERVAL = 5.minutes + MIGRATION = 'BackfillLegacyProjectRepositories' + + disable_ddl_transaction! + + class Project < ActiveRecord::Base + include EachBatch + + self.table_name = 'projects' + end + + def up + queue_background_migration_jobs_by_range_at_intervals(Project, MIGRATION, DELAY_INTERVAL) + end + + def down + # no-op: since there could have been existing rows before the migration do not remove anything + end +end diff --git a/db/post_migrate/20181219145520_migrate_cluster_configure_worker_sidekiq_queue.rb b/db/post_migrate/20181219145520_migrate_cluster_configure_worker_sidekiq_queue.rb new file mode 100644 index 00000000000..c37f8c039c0 --- /dev/null +++ b/db/post_migrate/20181219145520_migrate_cluster_configure_worker_sidekiq_queue.rb @@ -0,0 +1,15 @@ +# frozen_string_literal: true + +class MigrateClusterConfigureWorkerSidekiqQueue < ActiveRecord::Migration[5.0] + include Gitlab::Database::MigrationHelpers + + DOWNTIME = false + + def up + sidekiq_queue_migrate 'gcp_cluster:cluster_platform_configure', to: 'gcp_cluster:cluster_configure' + end + + def down + sidekiq_queue_migrate 'gcp_cluster:cluster_configure', to: 'gcp_cluster:cluster_platform_configure' + end +end diff --git a/db/schema.rb b/db/schema.rb index e5e19eb7745..0af185409a9 100644 --- a/db/schema.rb +++ b/db/schema.rb @@ -10,7 +10,7 @@ # # It's strongly recommended that you check this file into your version control system. -ActiveRecord::Schema.define(version: 20181204154019) do +ActiveRecord::Schema.define(version: 20181219145520) do # These are extensions that must be enabled in order to support this database enable_extension "plpgsql" @@ -166,6 +166,7 @@ ActiveRecord::Schema.define(version: 20181204154019) do t.integer "diff_max_patch_bytes", default: 102400, null: false t.integer "archive_builds_in_seconds" t.string "commit_email_hostname" + t.boolean "protected_ci_variables", default: false, null: false t.string "runners_registration_token_encrypted" t.index ["usage_stats_set_by_user_id"], name: "index_application_settings_on_usage_stats_set_by_user_id", using: :btree end @@ -1803,6 +1804,10 @@ ActiveRecord::Schema.define(version: 20181204154019) do t.datetime "updated_at" t.text "description_html" t.integer "cached_markdown_version" + t.integer "author_id" + t.string "name" + t.string "sha" + t.index ["author_id"], name: "index_releases_on_author_id", using: :btree t.index ["project_id", "tag"], name: "index_releases_on_project_id_and_tag", using: :btree t.index ["project_id"], name: "index_releases_on_project_id", using: :btree end @@ -1956,6 +1961,16 @@ ActiveRecord::Schema.define(version: 20181204154019) do t.index ["subscribable_id", "subscribable_type", "user_id", "project_id"], name: "index_subscriptions_on_subscribable_and_user_id_and_project_id", unique: true, using: :btree end + create_table "suggestions", id: :bigserial, force: :cascade do |t| + t.integer "note_id", null: false + t.integer "relative_order", limit: 2, null: false + t.boolean "applied", default: false, null: false + t.string "commit_id" + t.text "from_content", null: false + t.text "to_content", null: false + t.index ["note_id", "relative_order"], name: "index_suggestions_on_note_id_and_relative_order", unique: true, using: :btree + end + create_table "system_note_metadata", force: :cascade do |t| t.integer "note_id", null: false t.integer "commit_count" @@ -2423,6 +2438,7 @@ ActiveRecord::Schema.define(version: 20181204154019) do add_foreign_key "protected_tags", "projects", name: "fk_8e4af87648", on_delete: :cascade add_foreign_key "push_event_payloads", "events", name: "fk_36c74129da", on_delete: :cascade add_foreign_key "releases", "projects", name: "fk_47fe2a0596", on_delete: :cascade + add_foreign_key "releases", "users", column: "author_id", name: "fk_8e4456f90f", on_delete: :nullify add_foreign_key "remote_mirrors", "projects", on_delete: :cascade add_foreign_key "repository_languages", "projects", on_delete: :cascade add_foreign_key "resource_label_events", "issues", on_delete: :cascade @@ -2432,6 +2448,7 @@ ActiveRecord::Schema.define(version: 20181204154019) do add_foreign_key "services", "projects", name: "fk_71cce407f9", on_delete: :cascade add_foreign_key "snippets", "projects", name: "fk_be41fd4bb7", on_delete: :cascade add_foreign_key "subscriptions", "projects", on_delete: :cascade + add_foreign_key "suggestions", "notes", on_delete: :cascade add_foreign_key "system_note_metadata", "notes", name: "fk_d83a918cb1", on_delete: :cascade add_foreign_key "term_agreements", "application_setting_terms", column: "term_id" add_foreign_key "term_agreements", "users", on_delete: :cascade diff --git a/doc/administration/container_registry.md b/doc/administration/container_registry.md index cfe7b0e05e3..5b7a61ef8ff 100644 --- a/doc/administration/container_registry.md +++ b/doc/administration/container_registry.md @@ -604,6 +604,52 @@ Registry out of the box, it is possible to make it work if you follow [Docker's documentation][docker-insecure-self-signed]. You may find some additional information in [issue 18239][ce-18239]. +## Troubleshooting + +When using AWS S3 with the GitLab registry, an error may occur when pushing +large images. Look in the Registry log for the following error: + +``` +level=error msg="response completed with error" err.code=unknown err.detail="unexpected EOF" err.message="unknown error" +``` + +To resolve the error specify a `chunksize` value in the Registry configuration. +Start with a value between `25000000` (25MB) and `50000000` (50MB). + +**For Omnibus installations** + +1. Edit `/etc/gitlab/gitlab.rb`: + + ```ruby + registry['storage'] = { + 's3' => { + 'accesskey' => 'AKIAKIAKI', + 'secretkey' => 'secret123', + 'bucket' => 'gitlab-registry-bucket-AKIAKIAKI', + 'chunksize' => 25000000 + } + } + ``` + +1. Save the file and [reconfigure GitLab][] for the changes to take effect. + +--- + +**For installations from source** + +1. Edit `config/gitlab.yml`: + + ```yaml + storage: + s3: + accesskey: 'AKIAKIAKI' + secretkey: 'secret123' + bucket: 'gitlab-registry-bucket-AKIAKIAKI' + chunksize: 25000000 + ``` + +1. Save the file and [restart GitLab][] for the changes to take effect. + [ce-18239]: https://gitlab.com/gitlab-org/gitlab-ce/issues/18239 [docker-insecure-self-signed]: https://docs.docker.com/registry/insecure/#use-self-signed-certificates [reconfigure gitlab]: restart_gitlab.md#omnibus-gitlab-reconfigure diff --git a/doc/administration/gitaly/index.md b/doc/administration/gitaly/index.md index d7c45e7d91d..cf37eaa0b61 100644 --- a/doc/administration/gitaly/index.md +++ b/doc/administration/gitaly/index.md @@ -1,6 +1,6 @@ # Gitaly -[Gitaly](https://gitlab.com/gitlab-org/gitaly) is the service that +[Gitaly](https://gitlab.com/gitlab-org/gitaly) is the service that provides high-level RPC access to Git repositories. Without it, no other components can read or write Git data. @@ -23,7 +23,7 @@ gitaly['prometheus_listen_addr'] = 'localhost:9236' ``` To change a Gitaly setting in installations from source you can edit -`/home/git/gitaly/config.toml`. Changes will be applied when you run +`/home/git/gitaly/config.toml`. Changes will be applied when you run `service gitlab restart`. ```toml @@ -49,25 +49,6 @@ Starting with GitLab 11.4, Gitaly is a replacement for NFS except when the [Elastic Search indexer](https://gitlab.com/gitlab-org/gitlab-elasticsearch-indexer) is used. -### Network architecture - -- gitlab-rails shards repositories into "repository storages" -- gitlab-rails/config/gitlab.yml contains a map from storage names to - (Gitaly address, Gitaly token) pairs -- the `storage name` -\> `(Gitaly address, Gitaly token)` map in - gitlab.yml is the single source of truth for the Gitaly network - topology -- a (Gitaly address, Gitaly token) corresponds to a Gitaly server -- a Gitaly server hosts one or more storages -- Gitaly addresses must be specified in such a way that they resolve - correctly for ALL Gitaly clients -- Gitaly clients are: unicorn, sidekiq, gitlab-workhorse, - gitlab-shell, and Gitaly itself -- special case: a Gitaly server must be able to make RPC calls **to - itself** via its own (Gitaly address, Gitaly token) pair as - specified in gitlab-rails/config/gitlab.yml -- Gitaly servers must not be exposed to the public internet - Gitaly network traffic is unencrypted so you should use a firewall to restrict access to your Gitaly server. @@ -110,13 +91,13 @@ documentation on configuring Gitaly authentication](https://gitlab.com/gitlab-org/gitaly/blob/master/doc/configuration/README.md#authentication) . -Gitaly must trigger some callbacks to GitLab via GitLab Shell. As a result, +Gitaly must trigger some callbacks to GitLab via GitLab Shell. As a result, the GitLab Shell secret must be the same between the other GitLab servers and the Gitaly server. The easiest way to accomplish this is to copy `/etc/gitlab/gitlab-secrets.json` from an existing GitLab server to the Gitaly server. Without this shared secret, -Git operations in GitLab will result in an API error. +Git operations in GitLab will result in an API error. -> **NOTE:** In most or all cases the storage paths below end in `/repositories` which is +> **NOTE:** In most or all cases the storage paths below end in `/repositories` which is different than `path` in `git_data_dirs` of Omnibus installations. Check the directory layout on your Gitaly server to be sure. @@ -152,6 +133,11 @@ gitaly['storage'] = [ { 'name' => 'default', 'path' => '/mnt/gitlab/default/repositories' }, { 'name' => 'storage1', 'path' => '/mnt/gitlab/storage1/repositories' }, ] + +# To use tls for gitaly you need to add +gitaly['tls_listen_addr'] = "0.0.0.0:9999" +gitaly['certificate_path'] = "path/to/cert.pem" +gitaly['key_path'] = "path/to/key.pem" ``` Source installations: @@ -159,6 +145,11 @@ Source installations: ```toml # /home/git/gitaly/config.toml listen_addr = '0.0.0.0:8075' +tls_listen_addr = '0.0.0.0:9999' + +[tls] +certificate_path = /path/to/cert.pem +key_path = /path/to/key.pem [auth] token = 'abc123secret' @@ -224,6 +215,70 @@ Gitaly logs on your Gitaly server (`sudo gitlab-ctl tail gitaly` or coming in. One sure way to trigger a Gitaly request is to clone a repository from your GitLab server over HTTP. +## TLS support + +Gitaly supports TLS credentials for GRPC authentication. To be able to communicate +with a gitaly instance that listens for secure connections you will need to use `tls://` url +scheme in the `gitaly_address` of the corresponding storage entry in the gitlab configuration. + +The admin needs to bring their own certificate as we do not provide that automatically. +The certificate to be used needs to be installed on all gitaly nodes and on all client nodes that communicate with it following procedures described in [GitLab custom certificate configuration](https://docs.gitlab.com/omnibus/settings/ssl.html#install-custom-public-certificates) + +### Example TLS configuration + +### Omnibus installations: + +#### On client nodes: + +```ruby +# /etc/gitlab/gitlab.rb +git_data_dirs({ + 'default' => { 'path' => '/mnt/gitlab/default', 'gitaly_address' => 'tls://gitaly.internal:9999' }, + 'storage1' => { 'path' => '/mnt/gitlab/storage1', 'gitaly_address' => 'tls://gitaly.internal:9999' }, +}) + +gitlab_rails['gitaly_token'] = 'abc123secret' +``` + +#### On gitaly server nodes: + +```ruby +gitaly['tls_listen_addr'] = "0.0.0.0:9999" +gitaly['certificate_path'] = "path/to/cert.pem" +gitaly['key_path'] = "path/to/key.pem" +``` + +### Source installations: + +#### On client nodes: + +```yaml +# /home/git/gitlab/config/gitlab.yml +gitlab: + repositories: + storages: + default: + path: /mnt/gitlab/default/repositories + gitaly_address: tls://gitaly.internal:9999 + storage1: + path: /mnt/gitlab/storage1/repositories + gitaly_address: tls://gitaly.internal:9999 + + gitaly: + token: 'abc123secret' +``` + +#### On gitaly server nodes: + +```toml +# /home/git/gitaly/config.toml +tls_listen_addr = '0.0.0.0:9999' + +[tls] +certificate_path = '/path/to/cert.pem' +key_path = '/path/to/key.pem' +``` + ## Disabling or enabling the Gitaly service in a cluster environment If you are running Gitaly [as a remote diff --git a/doc/administration/index.md b/doc/administration/index.md index 6083806af6c..89132cd95f0 100644 --- a/doc/administration/index.md +++ b/doc/administration/index.md @@ -95,6 +95,7 @@ created in snippets, wikis, and repos. - [Postfix for incoming email](reply_by_email_postfix_setup.md): Set up a basic Postfix mail server with IMAP authentication on Ubuntu for incoming emails. +- [Abuse reports](../user/admin_area/abuse_reports.md): View and resolve abuse reports from your users. [reply by email]: reply_by_email.md [issues by email]: ../user/project/issues/create_new_issue.md#new-issue-via-email diff --git a/doc/administration/job_artifacts.md b/doc/administration/job_artifacts.md index a1ea78b64bd..25ae535d1ec 100644 --- a/doc/administration/job_artifacts.md +++ b/doc/administration/job_artifacts.md @@ -165,12 +165,6 @@ _The artifacts are stored by default in gitlab-rake gitlab:artifacts:migrate ``` - Currently this has to be executed manually and it will allow you to - migrate the existing artifacts to the object storage, but all new - artifacts will still be stored on the local disk. In the future - you will be given an option to define a default storage artifacts for all - new files. - --- **In installations from source:** @@ -201,12 +195,6 @@ _The artifacts are stored by default in sudo -u git -H bundle exec rake gitlab:artifacts:migrate RAILS_ENV=production ``` - Currently this has to be executed manually and it will allow you to - migrate the existing artifacts to the object storage, but all new - artifacts will still be stored on the local disk. In the future - you will be given an option to define a default storage artifacts for all - new files. - ## Expiring artifacts If an expiry date is used for the artifacts, they are marked for deletion diff --git a/doc/administration/monitoring/performance/img/request_profiling_token.png b/doc/administration/monitoring/performance/img/request_profiling_token.png Binary files differindex a9160b62acb..9f3dd7f08ca 100644 --- a/doc/administration/monitoring/performance/img/request_profiling_token.png +++ b/doc/administration/monitoring/performance/img/request_profiling_token.png diff --git a/doc/administration/monitoring/performance/request_profiling.md b/doc/administration/monitoring/performance/request_profiling.md index dfd9be3d04c..726882fbb87 100644 --- a/doc/administration/monitoring/performance/request_profiling.md +++ b/doc/administration/monitoring/performance/request_profiling.md @@ -2,14 +2,14 @@ ## Procedure -1. Grab the profiling token from `Monitoring > Requests Profiles` admin page +1. Grab the profiling token from **Monitoring > Requests Profiles** admin page (highlighted in a blue in the image below). ![Profile token](img/request_profiling_token.png) 1. Pass the header `X-Profile-Token: <token>` to the request you want to profile. You can use: - Browser extensions. For example, [ModHeader](https://chrome.google.com/webstore/detail/modheader/idgpnmonknjnojddfkpgkljpfnnfcklj) Chrome extension. - `curl`. For example, `curl --header 'X-Profile-Token: <token>' https://gitlab.example.com/group/project`. 1. Once request is finished (which will take a little longer than usual), you can - view the profiling output from `Monitoring > Requests Profiles` admin page. + view the profiling output from **Monitoring > Requests Profiles** admin page. ![Profiling output](img/request_profile_result.png) ## Cleaning up diff --git a/doc/administration/operations/filesystem_benchmarking.md b/doc/administration/operations/filesystem_benchmarking.md index 44018e966e0..0397452e650 100644 --- a/doc/administration/operations/filesystem_benchmarking.md +++ b/doc/administration/operations/filesystem_benchmarking.md @@ -44,12 +44,10 @@ user 0m0.025s sys 0m0.091s ``` -From experience with multiple customers, the following are ranges that indicate -whether your filesystem performance is satisfactory or less than ideal: - -| Rating | Benchmark result | -|:----------|:------------------------| -| Best | Less than 10 seconds | -| OK | 10-18 seconds | -| Poor | 18-25 seconds | -| Very poor | Greater than 25 seconds | +From experience with multiple customers, this task should take under 10 +seconds to indicate good filesystem performance. + +NOTE: **Note:** +This test is naive and only evaluates write performance. It's possible to +receive good results on this test but still have poor performance due to read +speed and various other factors.
\ No newline at end of file diff --git a/doc/administration/uploads.md b/doc/administration/uploads.md index f85a1f791f9..476ae8e8a76 100644 --- a/doc/administration/uploads.md +++ b/doc/administration/uploads.md @@ -120,30 +120,7 @@ _The uploads are stored by default in ``` 1. Save the file and [reconfigure GitLab][] for the changes to take effect. -1. Migrate any existing local uploads to the object storage: - - > **Notes:** - > These task complies with the `BATCH` environment variable to process uploads in batch (200 by default). All of the processing will be done in a background worker and requires **no downtime**. - - ```bash - # gitlab-rake gitlab:uploads:migrate[uploader_class, model_class, mount_point] - - # Avatars - gitlab-rake "gitlab:uploads:migrate[AvatarUploader, Project, :avatar]" - gitlab-rake "gitlab:uploads:migrate[AvatarUploader, Group, :avatar]" - gitlab-rake "gitlab:uploads:migrate[AvatarUploader, User, :avatar]" - - # Attachments - gitlab-rake "gitlab:uploads:migrate[AttachmentUploader, Note, :attachment]" - gitlab-rake "gitlab:uploads:migrate[AttachmentUploader, Appearance, :logo]" - gitlab-rake "gitlab:uploads:migrate[AttachmentUploader, Appearance, :header_logo]" - - # Markdown - gitlab-rake "gitlab:uploads:migrate[FileUploader, Project]" - gitlab-rake "gitlab:uploads:migrate[PersonalFileUploader, Snippet]" - gitlab-rake "gitlab:uploads:migrate[NamespaceFileUploader, Snippet]" - gitlab-rake "gitlab:uploads:migrate[FileUploader, MergeRequest]" - ``` +1. Migrate any existing local uploads to the object storage using [`gitlab:uploads:migrate` rake task](raketasks/uploads/migrate.md). --- @@ -168,32 +145,7 @@ _The uploads are stored by default in ``` 1. Save the file and [restart GitLab][] for the changes to take effect. -1. Migrate any existing local uploads to the object storage: - - > **Notes:** - > - These task comply with the `BATCH` environment variable to process uploads in batch (200 by default). All of the processing will be done in a background worker and requires **no downtime**. - > - To migrate in production use `RAILS_ENV=production` environment variable. - - ```bash - # sudo -u git -H bundle exec rake gitlab:uploads:migrate - - # Avatars - sudo -u git -H bundle exec rake "gitlab:uploads:migrate[AvatarUploader, Project, :avatar]" - sudo -u git -H bundle exec rake "gitlab:uploads:migrate[AvatarUploader, Group, :avatar]" - sudo -u git -H bundle exec rake "gitlab:uploads:migrate[AvatarUploader, User, :avatar]" - - # Attachments - sudo -u git -H bundle exec rake "gitlab:uploads:migrate[AttachmentUploader, Note, :attachment]" - sudo -u git -H bundle exec rake "gitlab:uploads:migrate[AttachmentUploader, Appearance, :logo]" - sudo -u git -H bundle exec rake "gitlab:uploads:migrate[AttachmentUploader, Appearance, :header_logo]" - - # Markdown - sudo -u git -H bundle exec rake "gitlab:uploads:migrate[FileUploader, Project]" - sudo -u git -H bundle exec rake "gitlab:uploads:migrate[PersonalFileUploader, Snippet]" - sudo -u git -H bundle exec rake "gitlab:uploads:migrate[NamespaceFileUploader, Snippet]" - sudo -u git -H bundle exec rake "gitlab:uploads:migrate[FileUploader, MergeRequest]" - - ``` +1. Migrate any existing local uploads to the object storage using [`gitlab:uploads:migrate` rake task](raketasks/uploads/migrate.md). [reconfigure gitlab]: restart_gitlab.md#omnibus-gitlab-reconfigure "How to reconfigure Omnibus GitLab" [restart gitlab]: restart_gitlab.md#installations-from-source "How to restart GitLab" diff --git a/doc/api/jobs.md b/doc/api/jobs.md index 589c48ee08d..d2dd9c676e3 100644 --- a/doc/api/jobs.md +++ b/doc/api/jobs.md @@ -8,12 +8,12 @@ Get a list of jobs in a project. GET /projects/:id/jobs ``` -| Attribute | Type | Required | Description | -|-----------|---------|----------|---------------------| -| `id` | integer/string | yes | The ID or [URL-encoded path of the project](README.md#namespaced-path-encoding) owned by the authenticated user | -| `scope` | string **or** array of strings | no | The scope of jobs to show, one or array of: `created`, `pending`, `running`, `failed`, `success`, `canceled`, `skipped`, `manual`; showing all jobs if none provided | +| Attribute | Type | Required | Description | +|-----------|--------------------------------|----------|----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------| +| `id` | integer/string | yes | The ID or [URL-encoded path of the project](README.md#namespaced-path-encoding) owned by the authenticated user. | +| `scope` | string **or** array of strings | no | Scope of jobs to show. Either one of or an array of the following: `created`, `pending`, `running`, `failed`, `success`, `canceled`, `skipped`, or `manual`. All jobs are returned if `scope` is not provided. | -``` +```sh curl --header "PRIVATE-TOKEN: 9koXpg98eAheJpvBs5tK" 'https://gitlab.example.com/api/v4/projects/1/jobs?scope[]=pending&scope[]=running' ``` @@ -140,13 +140,13 @@ Get a list of jobs for a pipeline. GET /projects/:id/pipelines/:pipeline_id/jobs ``` -| Attribute | Type | Required | Description | -|---------------|--------------------------------|----------|----------------------| -| `id` | integer/string | yes | The ID or [URL-encoded path of the project](README.md#namespaced-path-encoding) owned by the authenticated user | -| `pipeline_id` | integer | yes | The ID of a pipeline | -| `scope` | string **or** array of strings | no | The scope of jobs to show, one or array of: `created`, `pending`, `running`, `failed`, `success`, `canceled`, `skipped`, `manual`; showing all jobs if none provided | +| Attribute | Type | Required | Description | +|---------------|--------------------------------|----------|----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------| +| `id` | integer/string | yes | The ID or [URL-encoded path of the project](README.md#namespaced-path-encoding) owned by the authenticated user. | +| `pipeline_id` | integer | yes | The ID of a pipeline. | +| `scope` | string **or** array of strings | no | Scope of jobs to show. Either one of or an array of the following: `created`, `pending`, `running`, `failed`, `success`, `canceled`, `skipped`, or `manual`. All jobs are returned if `scope` is not provided. | -``` +```sh curl --header "PRIVATE-TOKEN: 9koXpg98eAheJpvBs5tK" 'https://gitlab.example.com/api/v4/projects/1/pipelines/6/jobs?scope[]=pending&scope[]=running' ``` @@ -273,12 +273,12 @@ Get a single job of a project GET /projects/:id/jobs/:job_id ``` -| Attribute | Type | Required | Description | -|------------|---------|----------|---------------------| -| `id` | integer/string | yes | The ID or [URL-encoded path of the project](README.md#namespaced-path-encoding) owned by the authenticated user | -| `job_id` | integer | yes | The ID of a job | +| Attribute | Type | Required | Description | +|-----------|----------------|----------|------------------------------------------------------------------------------------------------------------------| +| `id` | integer/string | yes | The ID or [URL-encoded path of the project](README.md#namespaced-path-encoding) owned by the authenticated user. | +| `job_id` | integer | yes | The ID of a job. | -``` +```sh curl --header "PRIVATE-TOKEN: 9koXpg98eAheJpvBs5tK" "https://gitlab.example.com/api/v4/projects/1/jobs/8" ``` @@ -348,18 +348,18 @@ Get job artifacts of a project. GET /projects/:id/jobs/:job_id/artifacts ``` -| Attribute | Type | Required | Description | -|------------|---------|----------|---------------------| -| `id` | integer/string | yes | The ID or [URL-encoded path of the project](README.md#namespaced-path-encoding) owned by the authenticated user | -| `job_id` | integer | yes | The ID of a job | +| Attribute | Type | Required | Description | +|-----------|----------------|----------|------------------------------------------------------------------------------------------------------------------| +| `id` | integer/string | yes | The ID or [URL-encoded path of the project](README.md#namespaced-path-encoding) owned by the authenticated user. | +| `job_id` | integer | yes | The ID of a job. | Example requests: -``` +```sh curl --location --header "PRIVATE-TOKEN: 9koXpg98eAheJpvBs5tK" "https://gitlab.example.com/api/v4/projects/1/jobs/8/artifacts" ``` -Response: +Possible response status codes: | Status | Description | |-----------|---------------------------------| @@ -383,19 +383,19 @@ GET /projects/:id/jobs/artifacts/:ref_name/download?job=name Parameters -| Attribute | Type | Required | Description | -|-------------|---------|----------|-------------------------- | -| `id` | integer/string | yes | The ID or [URL-encoded path of the project](README.md#namespaced-path-encoding) owned by the authenticated user | -| `ref_name` | string | yes | The ref from a repository (can only be branch or tag name, not HEAD or SHA) | -| `job` | string | yes | The name of the job | +| Attribute | Type | Required | Description | +|------------|----------------|----------|------------------------------------------------------------------------------------------------------------------| +| `id` | integer/string | yes | The ID or [URL-encoded path of the project](README.md#namespaced-path-encoding) owned by the authenticated user. | +| `ref_name` | string | yes | Branch or tag name in repository. HEAD or SHA references are not supported. | +| `job` | string | yes | The name of the job. | Example requests: -``` +```sh curl --header "PRIVATE-TOKEN: 9koXpg98eAheJpvBs5tK" "https://gitlab.example.com/api/v4/projects/1/jobs/artifacts/master/download?job=test" ``` -Example response: +Possible response status codes: | Status | Description | |-----------|---------------------------------| @@ -408,9 +408,9 @@ Example response: > Introduced in GitLab 10.0 -Download a single artifact file from within the job's artifacts archive. - -Only a single file is going to be extracted from the archive and streamed to a client. +Download a single artifact file from a job with a specified ID from within +the job's artifacts archive. The file is extracted from the archive and +streamed to the client. ``` GET /projects/:id/jobs/:job_id/artifacts/*artifact_path @@ -418,19 +418,19 @@ GET /projects/:id/jobs/:job_id/artifacts/*artifact_path Parameters -| Attribute | Type | Required | Description | -|-----------------|---------|----------|-------------------------- | -| `id` | integer/string | yes | The ID or [URL-encoded path of the project](README.md#namespaced-path-encoding) owned by the authenticated user | -| `job_id ` | integer | yes | The unique job identifier | -| `artifact_path` | string | yes | Path to a file inside the artifacts archive | +| Attribute | Type | Required | Description | +|-----------------|----------------|----------|------------------------------------------------------------------------------------------------------------------| +| `id` | integer/string | yes | The ID or [URL-encoded path of the project](README.md#namespaced-path-encoding) owned by the authenticated user. | +| `job_id ` | integer | yes | The unique job identifier. | +| `artifact_path` | string | yes | Path to a file inside the artifacts archive. | Example request: -``` +```sh curl --header "PRIVATE-TOKEN: 9koXpg98eAheJpvBs5tK" "https://gitlab.example.com/api/v4/projects/1/jobs/5/artifacts/some/release/file.pdf" ``` -Example response: +Possible response status codes: | Status | Description | |-----------|--------------------------------------| @@ -481,16 +481,16 @@ Get a trace of a specific job of a project GET /projects/:id/jobs/:job_id/trace ``` -| Attribute | Type | Required | Description | -|------------|---------|----------|---------------------| -| id | integer/string | yes | The ID or [URL-encoded path of the project](README.md#namespaced-path-encoding) owned by the authenticated user | -| job_id | integer | yes | The ID of a job | +| Attribute | Type | Required | Description | +|-----------|----------------|----------|------------------------------------------------------------------------------------------------------------------| +| id | integer/string | yes | The ID or [URL-encoded path of the project](README.md#namespaced-path-encoding) owned by the authenticated user. | +| job_id | integer | yes | The ID of a job. | -``` +```sh curl --header "PRIVATE-TOKEN: 9koXpg98eAheJpvBs5tK" "https://gitlab.example.com/api/v4/projects/1/jobs/8/trace" ``` -Response: +Possible response status codes: | Status | Description | |-----------|-----------------------------------| @@ -505,12 +505,12 @@ Cancel a single job of a project POST /projects/:id/jobs/:job_id/cancel ``` -| Attribute | Type | Required | Description | -|------------|---------|----------|---------------------| -| `id` | integer/string | yes | The ID or [URL-encoded path of the project](README.md#namespaced-path-encoding) owned by the authenticated user | -| `job_id` | integer | yes | The ID of a job | +| Attribute | Type | Required | Description | +|-----------|----------------|----------|------------------------------------------------------------------------------------------------------------------| +| `id` | integer/string | yes | The ID or [URL-encoded path of the project](README.md#namespaced-path-encoding) owned by the authenticated user. | +| `job_id` | integer | yes | The ID of a job. | -``` +```sh curl --request POST --header "PRIVATE-TOKEN: 9koXpg98eAheJpvBs5tK" "https://gitlab.example.com/api/v4/projects/1/jobs/1/cancel" ``` @@ -553,12 +553,12 @@ Retry a single job of a project POST /projects/:id/jobs/:job_id/retry ``` -| Attribute | Type | Required | Description | -|------------|---------|----------|---------------------| -| `id` | integer/string | yes | The ID or [URL-encoded path of the project](README.md#namespaced-path-encoding) owned by the authenticated user | -| `job_id` | integer | yes | The ID of a job | +| Attribute | Type | Required | Description | +|-----------|----------------|----------|------------------------------------------------------------------------------------------------------------------| +| `id` | integer/string | yes | The ID or [URL-encoded path of the project](README.md#namespaced-path-encoding) owned by the authenticated user. | +| `job_id` | integer | yes | The ID of a job. | -``` +```sh curl --request POST --header "PRIVATE-TOKEN: 9koXpg98eAheJpvBs5tK" "https://gitlab.example.com/api/v4/projects/1/jobs/1/retry" ``` @@ -603,14 +603,14 @@ POST /projects/:id/jobs/:job_id/erase Parameters -| Attribute | Type | Required | Description | -|-------------|---------|----------|---------------------| -| `id` | integer/string | yes | The ID or [URL-encoded path of the project](README.md#namespaced-path-encoding) owned by the authenticated user | -| `job_id` | integer | yes | The ID of a job | +| Attribute | Type | Required | Description | +|-----------|----------------|----------|------------------------------------------------------------------------------------------------------------------| +| `id` | integer/string | yes | The ID or [URL-encoded path of the project](README.md#namespaced-path-encoding) owned by the authenticated user. | +| `job_id` | integer | yes | The ID of a job. | Example of request -``` +```sh curl --request POST --header "PRIVATE-TOKEN: 9koXpg98eAheJpvBs5tK" "https://gitlab.example.com/api/v4/projects/1/jobs/1/erase" ``` @@ -656,14 +656,14 @@ POST /projects/:id/jobs/:job_id/artifacts/keep Parameters -| Attribute | Type | Required | Description | -|-------------|---------|----------|---------------------| -| `id` | integer/string | yes | The ID or [URL-encoded path of the project](README.md#namespaced-path-encoding) owned by the authenticated user | -| `job_id` | integer | yes | The ID of a job | +| Attribute | Type | Required | Description | +|-----------|----------------|----------|------------------------------------------------------------------------------------------------------------------| +| `id` | integer/string | yes | The ID or [URL-encoded path of the project](README.md#namespaced-path-encoding) owned by the authenticated user. | +| `job_id` | integer | yes | The ID of a job. | Example request: -``` +```sh curl --request POST --header "PRIVATE-TOKEN: 9koXpg98eAheJpvBs5tK" "https://gitlab.example.com/api/v4/projects/1/jobs/1/artifacts/keep" ``` @@ -707,12 +707,12 @@ Triggers a manual action to start a job. POST /projects/:id/jobs/:job_id/play ``` -| Attribute | Type | Required | Description | -|-----------|---------|----------|---------------------| -| `id` | integer/string | yes | The ID or [URL-encoded path of the project](README.md#namespaced-path-encoding) owned by the authenticated user | -| `job_id` | integer | yes | The ID of a job | +| Attribute | Type | Required | Description | +|-----------|----------------|----------|------------------------------------------------------------------------------------------------------------------| +| `id` | integer/string | yes | The ID or [URL-encoded path of the project](README.md#namespaced-path-encoding) owned by the authenticated user. | +| `job_id` | integer | yes | The ID of a job. | -``` +```sh curl --request POST --header "PRIVATE-TOKEN: 9koXpg98eAheJpvBs5tK" "https://gitlab.example.com/api/v4/projects/1/jobs/1/play" ``` diff --git a/doc/api/lint.md b/doc/api/lint.md index bd5a216a99d..c37a8bff749 100644 --- a/doc/api/lint.md +++ b/doc/api/lint.md @@ -13,7 +13,7 @@ POST /lint | `content` | string | yes | the .gitlab-ci.yaml content| ```bash -curl --header "Content-Type: application/json" https://gitlab.example.com/api/v4/ci/lint --data '{"content": "{ \"image\": \"ruby:2.1\", \"services\": [\"postgres\"], \"before_script\": [\"gem install bundler\", \"bundle install\", \"bundle exec rake db:create\"], \"variables\": {\"DB_NAME\": \"postgres\"}, \"types\": [\"test\", \"deploy\", \"notify\"], \"rspec\": { \"script\": \"rake spec\", \"tags\": [\"ruby\", \"postgres\"], \"only\": [\"branches\"]}}"}' +curl --header "Content-Type: application/json" https://gitlab.example.com/api/v4/ci/lint --data '{"content": "{ \"image\": \"ruby:2.6\", \"services\": [\"postgres\"], \"before_script\": [\"bundle install\", \"bundle exec rake db:create\"], \"variables\": {\"DB_NAME\": \"postgres\"}, \"types\": [\"test\", \"deploy\", \"notify\"], \"rspec\": { \"script\": \"rake spec\", \"tags\": [\"ruby\", \"postgres\"], \"only\": [\"branches\"]}}"}' ``` Be sure to copy paste the exact contents of `.gitlab-ci.yml` as YAML is very picky about indentation and spaces. diff --git a/doc/api/services.md b/doc/api/services.md index f122bac6f1f..c4edaa17815 100644 --- a/doc/api/services.md +++ b/doc/api/services.md @@ -573,7 +573,7 @@ DELETE /projects/:id/services/jira ## Kubernetes -Kubernetes / Openshift integration +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. diff --git a/doc/api/suggestions.md b/doc/api/suggestions.md new file mode 100644 index 00000000000..9d76ef0c4bf --- /dev/null +++ b/doc/api/suggestions.md @@ -0,0 +1,36 @@ +# Suggest Changes API + +Every API call to suggestions must be authenticated. + +## Applying suggestions + +Applies a suggested patch in a merge request. Users must be +at least [Developer](../user/permissions.md) to perform such action. + +``` +PUT /suggestions/:id/apply +``` + +| Attribute | Type | Required | Description | +| --------- | ---- | -------- | ----------- | +| `id` | integer/string | yes | The ID of a suggestion | + +```bash +curl --request PUT --header "PRIVATE-TOKEN: 9koXpg98eAheJpvBs5tK" https://gitlab.example.com/api/v4/suggestions/5/apply +``` + +Example response: + +```json + { + "id": 36, + "from_original_line": 10, + "to_original_line": 10, + "from_line": 10, + "to_line": 10, + "appliable": false, + "applied": true, + "from_content": " \"--talk-name=org.freedesktop.\",\n", + "to_content": " \"--talk-name=org.free.\",\n \"--talk-name=org.desktop.\",\n" + } +``` diff --git a/doc/api/templates/gitlab_ci_ymls.md b/doc/api/templates/gitlab_ci_ymls.md index cecfc8cd9b9..11ec7360e06 100644 --- a/doc/api/templates/gitlab_ci_ymls.md +++ b/doc/api/templates/gitlab_ci_ymls.md @@ -120,6 +120,6 @@ Example response: ```json { "name": "Ruby", - "content": "# This file is a template, and might need editing before it works on your project.\n# Official language image. Look for the different tagged releases at:\n# https://hub.docker.com/r/library/ruby/tags/\nimage: \"ruby:2.3\"\n\n# Pick zero or more services to be used on all builds.\n# Only needed when using a docker container to run your tests in.\n# Check out: http://docs.gitlab.com/ce/ci/docker/using_docker_images.html#what-is-service\nservices:\n - mysql:latest\n - redis:latest\n - postgres:latest\n\nvariables:\n POSTGRES_DB: database_name\n\n# Cache gems in between builds\ncache:\n paths:\n - vendor/ruby\n\n# This is a basic example for a gem or script which doesn't use\n# services such as redis or postgres\nbefore_script:\n - ruby -v # Print out ruby version for debugging\n # Uncomment next line if your rails app needs a JS runtime:\n # - apt-get update -q && apt-get install nodejs -yqq\n - gem install bundler --no-ri --no-rdoc # Bundler is not installed with the image\n - bundle install -j $(nproc) --path vendor # Install dependencies into ./vendor/ruby\n\n# Optional - Delete if not using `rubocop`\nrubocop:\n script:\n - rubocop\n\nrspec:\n script:\n - rspec spec\n\nrails:\n variables:\n DATABASE_URL: \"postgresql://postgres:postgres@postgres:5432/$POSTGRES_DB\"\n script:\n - bundle exec rake db:migrate\n - bundle exec rake db:seed\n - bundle exec rake test\n" + "content": "# This file is a template, and might need editing before it works on your project.\n# Official language image. Look for the different tagged releases at:\n# https://hub.docker.com/r/library/ruby/tags/\nimage: \"ruby:2.5\"\n\n# Pick zero or more services to be used on all builds.\n# Only needed when using a docker container to run your tests in.\n# Check out: http://docs.gitlab.com/ce/ci/docker/using_docker_images.html#what-is-a-service\nservices:\n - mysql:latest\n - redis:latest\n - postgres:latest\n\nvariables:\n POSTGRES_DB: database_name\n\n# Cache gems in between builds\ncache:\n paths:\n - vendor/ruby\n\n# This is a basic example for a gem or script which doesn't use\n# services such as redis or postgres\nbefore_script:\n - ruby -v # Print out ruby version for debugging\n # Uncomment next line if your rails app needs a JS runtime:\n # - apt-get update -q && apt-get install nodejs -yqq\n - bundle install -j $(nproc) --path vendor # Install dependencies into ./vendor/ruby\n\n# Optional - Delete if not using `rubocop`\nrubocop:\n script:\n - rubocop\n\nrspec:\n script:\n - rspec spec\n\nrails:\n variables:\n DATABASE_URL: \"postgresql://postgres:postgres@postgres:5432/$POSTGRES_DB\"\n script:\n - rails db:migrate\n - rails db:seed\n - rails test\n\n# This deploy job uses a simple deploy flow to Heroku, other providers, e.g. AWS Elastic Beanstalk\n# are supported too: https://github.com/travis-ci/dpl\ndeploy:\n type: deploy\n environment: production\n script:\n - gem install dpl\n - dpl --provider=heroku --app=$HEROKU_APP_NAME --api-key=$HEROKU_PRODUCTION_KEY\n" } ``` diff --git a/doc/ci/caching/index.md b/doc/ci/caching/index.md index f93ccc4e3c1..495ec099111 100644 --- a/doc/ci/caching/index.md +++ b/doc/ci/caching/index.md @@ -300,7 +300,6 @@ cache: before_script: - ruby -v # Print out ruby version for debugging - - gem install bundler --no-ri --no-rdoc # Bundler is not installed with the image - bundle install -j $(nproc) --path vendor # Install dependencies into ./vendor/ruby rspec: diff --git a/doc/ci/docker/using_kaniko.md b/doc/ci/docker/using_kaniko.md index 66f0d429165..aa6b387bc58 100644 --- a/doc/ci/docker/using_kaniko.md +++ b/doc/ci/docker/using_kaniko.md @@ -57,3 +57,26 @@ build: only: - tags ``` + +## Using a registry with a custom certificate + +When trying to push to a Docker registry that uses a certificate that is signed +by a custom CA, you might get the following error: + +```sh +$ /kaniko/executor --context $CI_PROJECT_DIR --dockerfile $CI_PROJECT_DIR/Dockerfile --no-push +INFO[0000] Downloading base image registry.gitlab.example.com/group/docker-image +error building image: getting stage builder for stage 0: Get https://registry.gitlab.example.com/v2/: x509: certificate signed by unknown authority +``` + +This can be solved by adding your CA's certificate to the kaniko certificate +store: + +```yaml + before_script: + - echo "{\"auths\":{\"$CI_REGISTRY\":{\"username\":\"$CI_REGISTRY_USER\",\"password\":\"$CI_REGISTRY_PASSWORD\"}}}" > /kaniko/.docker/config.json + - | + echo "-----BEGIN CERTIFICATE----- + ... + -----END CERTIFICATE-----" >> /kaniko/ssl/certs/ca-certificates.crt +``` diff --git a/doc/ci/examples/deploy_spring_boot_to_cloud_foundry/img/cloud_foundry_variables.png b/doc/ci/examples/deploy_spring_boot_to_cloud_foundry/img/cloud_foundry_variables.png Binary files differindex 28323e2d8de..e76767741ce 100644 --- a/doc/ci/examples/deploy_spring_boot_to_cloud_foundry/img/cloud_foundry_variables.png +++ b/doc/ci/examples/deploy_spring_boot_to_cloud_foundry/img/cloud_foundry_variables.png diff --git a/doc/ci/examples/devops_and_game_dev_with_gitlab_ci_cd/index.md b/doc/ci/examples/devops_and_game_dev_with_gitlab_ci_cd/index.md index cae051daa56..1e2be2e8475 100644 --- a/doc/ci/examples/devops_and_game_dev_with_gitlab_ci_cd/index.md +++ b/doc/ci/examples/devops_and_game_dev_with_gitlab_ci_cd/index.md @@ -254,7 +254,7 @@ To ensure our changes don't break the build and all tests still pass, we utilize Continuous Integration (CI) to run these checks automatically for every push. Read through this article to understand [Continuous Integration, Continuous Delivery, and Continuous Deployment](https://about.gitlab.com/2016/08/05/continuous-integration-delivery-and-deployment-with-gitlab/), and how these methods are leveraged by GitLab. -From the [last tutorial](https://ryanhallcs.wordpress.com/2017/03/15/devops-and-game-dev/) we already have a `gitlab-ci.yml` file set up for building our app from +From the [last tutorial](https://ryanhallcs.wordpress.com/2017/03/15/devops-and-game-dev/) we already have a `.gitlab-ci.yml` file set up for building our app from every push. We need to set up a new CI job for testing, which GitLab CI/CD will run after the build job using our generated artifacts from gulp. Please read through the [documentation on CI/CD configuration](../../../ci/yaml/README.md) file to explore its contents and adjust it to your needs. diff --git a/doc/ci/examples/laravel_with_gitlab_and_envoy/img/variables_page.png b/doc/ci/examples/laravel_with_gitlab_and_envoy/img/variables_page.png Binary files differindex 80d8eb0f4fc..4675e20ef79 100644 --- a/doc/ci/examples/laravel_with_gitlab_and_envoy/img/variables_page.png +++ b/doc/ci/examples/laravel_with_gitlab_and_envoy/img/variables_page.png diff --git a/doc/ci/examples/test-and-deploy-python-application-to-heroku.md b/doc/ci/examples/test-and-deploy-python-application-to-heroku.md index ec0b5aaed09..b59271e400f 100644 --- a/doc/ci/examples/test-and-deploy-python-application-to-heroku.md +++ b/doc/ci/examples/test-and-deploy-python-application-to-heroku.md @@ -47,7 +47,7 @@ This project has three jobs: ## Store API keys -You'll need to create two variables in `Settings > CI/CD > Variables` on your GitLab project settings: +You'll need to create two variables in **Settings > CI/CD > Variables** in your GitLab project: - `HEROKU_STAGING_API_KEY` - Heroku API key used to deploy staging app. - `HEROKU_PRODUCTION_API_KEY` - Heroku API key used to deploy production app. diff --git a/doc/ci/img/pipelines-goal.png b/doc/ci/img/pipelines-goal.png Binary files differindex a96368e562b..f15716d0b8f 100644 --- a/doc/ci/img/pipelines-goal.png +++ b/doc/ci/img/pipelines-goal.png diff --git a/doc/ci/img/types-of-pipelines.png b/doc/ci/img/types-of-pipelines.png Binary files differindex bd809de5e68..829a53d5d52 100644 --- a/doc/ci/img/types-of-pipelines.png +++ b/doc/ci/img/types-of-pipelines.png diff --git a/doc/ci/img/view_on_mr_widget.png b/doc/ci/img/view_on_mr_widget.png Binary files differindex 04f4b58df62..efe023b07b5 100644 --- a/doc/ci/img/view_on_mr_widget.png +++ b/doc/ci/img/view_on_mr_widget.png diff --git a/doc/ci/interactive_web_terminal/index.md b/doc/ci/interactive_web_terminal/index.md index d8136b80c11..d299e28d2e6 100644 --- a/doc/ci/interactive_web_terminal/index.md +++ b/doc/ci/interactive_web_terminal/index.md @@ -6,8 +6,9 @@ Interactive web terminals give the user access to a terminal in GitLab for running one-off commands for their CI pipeline. NOTE: **Note:** -GitLab.com does not support interactive web terminal at the moment. Please -follow [this issue](https://gitlab.com/gitlab-org/gitlab-ce/issues/52611) for +GitLab.com does not support interactive web terminal at the moment – neither +using shared GitLab.com runners nor your own runners. Please follow +[this issue](https://gitlab.com/gitlab-org/gitlab-ce/issues/52611) for progress. ## Configuration diff --git a/doc/ci/merge_request_pipelines/img/merge_request.png b/doc/ci/merge_request_pipelines/img/merge_request.png Binary files differindex 1fe2eec2008..cf9c628e9a0 100644 --- a/doc/ci/merge_request_pipelines/img/merge_request.png +++ b/doc/ci/merge_request_pipelines/img/merge_request.png diff --git a/doc/ci/merge_request_pipelines/img/pipeline_detail.png b/doc/ci/merge_request_pipelines/img/pipeline_detail.png Binary files differindex def1781dd75..6094a0975fb 100644 --- a/doc/ci/merge_request_pipelines/img/pipeline_detail.png +++ b/doc/ci/merge_request_pipelines/img/pipeline_detail.png diff --git a/doc/ci/merge_request_pipelines/index.md b/doc/ci/merge_request_pipelines/index.md index 706e83abf44..bf1e61442d4 100644 --- a/doc/ci/merge_request_pipelines/index.md +++ b/doc/ci/merge_request_pipelines/index.md @@ -1,11 +1,12 @@ # Pipelines for merge requests -> [Introduced](https://gitlab.com/gitlab-org/gitlab-ce/issues/15310) in GitLab 11.6 +> [Introduced](https://gitlab.com/gitlab-org/gitlab-ce/issues/15310) in GitLab 11.6. -Usually, when a developer creates a new merge request, a pipeline runs on the +Usually, when you create a new merge request, a pipeline runs on the new change and checks if it's qualified to be merged into a target branch. This pipeline should contain only necessary jobs for checking the new changes. -For example, unit tests, lint checks, and Review Apps are often used in this cycle. +For example, unit tests, lint checks, and [Review Apps](../review_apps/index.md) +are often used in this cycle. With pipelines for merge requests, you can design a specific pipeline structure for merge requests. All you need to do is just adding `only: [merge_requests]` to @@ -14,10 +15,10 @@ Every time, when developers create or update merge requests, a pipeline runs on their new commits at every push to GitLab. NOTE: **Note**: -If you use both this feature and the [Merge When Pipeline Succeeds](../../user/project/merge_requests/merge_when_pipeline_succeeds.md) -feature, pipelines for merge requests take precendence over the other regular pipelines. +If you use both this feature and [Merge When Pipeline Succeeds](../../user/project/merge_requests/merge_when_pipeline_succeeds.md), +pipelines for merge requests take precedence over the other regular pipelines. -For example, consider a GitLab CI/CD configuration in .gitlab-ci.yml as follows: +For example, consider the following [`.gitlab-ci.yml`](../yaml/README.md): ```yaml build: @@ -39,20 +40,18 @@ deploy: script: ./deploy ``` -After a developer updated code in a merge request with whatever methods (e.g. `git push`), -GitLab detects that the code is updated and create a new pipeline for the merge request. +After the merge request is updated with new commits, GitLab detects that changes +have occurred and creates a new pipeline for the merge request. The pipeline fetches the latest code from the source branch and run tests against it. -In this example, the pipeline contains only `build` and `test` jobs. -Since `deploy` job does not have the `only: [merge_requests]` rule, +In the above example, the pipeline contains only `build` and `test` jobs. +Since the `deploy` job doesn't have the `only: [merge_requests]` rule, deployment jobs will not happen in the merge request. -Consider this pipeline list viewed from the **Pipelines** tab in a merge request: +Pipelines tagged as **merge request** indicate that they were triggered +when a merge request was created or updated. ![Merge request page](img/merge_request.png) -Note that pipelines tagged as **merge request** indicate that they were triggered -when a merge request was created or updated. - The same tag is shown on the pipeline's details: ![Pipeline's details](img/pipeline_detail.png) @@ -66,7 +65,7 @@ flow, external contributors follow the following steps: 1. Create a merge request from the forked project that targets the `master` branch in the parent project. 1. A pipeline runs on the merge request. -1. A mainatiner from the parent project checks the pipeline result, and merge +1. A maintainer from the parent project checks the pipeline result, and merge into a target branch if the latest pipeline has passed. Currently, those pipelines are created in a **forked** project, not in the @@ -77,7 +76,7 @@ by tweaking their GitLab Runner in the forked project. There are multiple reasons about why GitLab doesn't allow those pipelines to be created in the parent project, but one of the biggest reasons is security concern. External users could steal secret variables from the parent project by modifying -.gitlab-ci.yml, which could be some sort of credentials. This should not happen. +`.gitlab-ci.yml`, which could be some sort of credentials. This should not happen. We're discussing a secure solution of running pipelines for merge requests that submitted from forked projects, diff --git a/doc/ci/quick_start/README.md b/doc/ci/quick_start/README.md index bdc593493ea..1ec8a8c89c9 100644 --- a/doc/ci/quick_start/README.md +++ b/doc/ci/quick_start/README.md @@ -77,7 +77,7 @@ before_script: - apt-get update -qq && apt-get install -y -qq sqlite3 libsqlite3-dev nodejs - ruby -v - which ruby - - gem install bundler --no-ri --no-rdoc + - gem install bundler --no-document - bundle install --jobs $(nproc) "${FLAGS[@]}" rspec: diff --git a/doc/ci/services/mysql.md b/doc/ci/services/mysql.md index b76f9618fc9..2902c30c7c0 100644 --- a/doc/ci/services/mysql.md +++ b/doc/ci/services/mysql.md @@ -16,7 +16,7 @@ services: - mysql:latest variables: - # Configure mysql environment variables (https://hub.docker.com/r/_/mysql/) + # Configure mysql environment variables (https://hub.docker.com/_/mysql/) MYSQL_DATABASE: el_duderino MYSQL_ROOT_PASSWORD: mysql_strong_password ``` @@ -114,5 +114,5 @@ available [shared runners](../runners/README.md). Want to hack on it? Simply fork it, commit and push your changes. Within a few moments the changes will be picked by a public runner and the job will begin. -[hub-mysql]: https://hub.docker.com/r/_/mysql/ +[hub-mysql]: https://hub.docker.com/_/mysql/ [mysql-example-repo]: https://gitlab.com/gitlab-examples/mysql diff --git a/doc/ci/triggers/img/trigger_variables.png b/doc/ci/triggers/img/trigger_variables.png Binary files differindex f862155b47f..d273b1fe3a2 100644 --- a/doc/ci/triggers/img/trigger_variables.png +++ b/doc/ci/triggers/img/trigger_variables.png diff --git a/doc/ci/variables/README.md b/doc/ci/variables/README.md index 87799be8ab4..209a2c15d90 100644 --- a/doc/ci/variables/README.md +++ b/doc/ci/variables/README.md @@ -44,18 +44,19 @@ future GitLab releases.** |-------------------------------------------|--------|--------|-------------| | **ARTIFACT_DOWNLOAD_ATTEMPTS** | 8.15 | 1.9 | Number of attempts to download artifacts running a job | | **CI** | all | 0.4 | Mark that job is executed in CI environment | +| **CI_COMMIT_BEFORE_SHA** | 11.2 | all | The previous latest commit present on a branch before a push request. | +| **CI_COMMIT_DESCRIPTION** | 10.8 | all | The description of the commit: the message without first line, if the title is shorter than 100 characters; full message in other case. | +| **CI_COMMIT_MESSAGE** | 10.8 | all | The full commit message. | | **CI_COMMIT_REF_NAME** | 9.0 | all | The branch or tag name for which project is built | | **CI_COMMIT_REF_SLUG** | 9.0 | all | `$CI_COMMIT_REF_NAME` lowercased, shortened to 63 bytes, and with everything except `0-9` and `a-z` replaced with `-`. No leading / trailing `-`. Use in URLs, host names and domain names. | | **CI_COMMIT_SHA** | 9.0 | all | The commit revision for which project is built | -| **CI_COMMIT_BEFORE_SHA** | 11.2 | all | The previous latest commit present on a branch before a push request. | +| **CI_COMMIT_SHORT_SHA** | 11.7 | all | The first eight characters of `CI_COMMIT_SHA` | | **CI_COMMIT_TAG** | 9.0 | 0.5 | The commit tag name. Present only when building tags. | -| **CI_COMMIT_MESSAGE** | 10.8 | all | The full commit message. | | **CI_COMMIT_TITLE** | 10.8 | all | The title of the commit - the full first line of the message | -| **CI_COMMIT_DESCRIPTION** | 10.8 | all | The description of the commit: the message without first line, if the title is shorter than 100 characters; full message in other case. | | **CI_CONFIG_PATH** | 9.4 | 0.5 | The path to CI config file. Defaults to `.gitlab-ci.yml` | | **CI_DEBUG_TRACE** | all | 1.7 | Whether [debug tracing](#debug-tracing) is enabled | -| **CI_DEPLOY_USER** | 10.8 | all | Authentication username of the [GitLab Deploy Token][gitlab-deploy-token], only present if the Project has one related.| | **CI_DEPLOY_PASSWORD** | 10.8 | all | Authentication password of the [GitLab Deploy Token][gitlab-deploy-token], only present if the Project has one related.| +| **CI_DEPLOY_USER** | 10.8 | all | Authentication username of the [GitLab Deploy Token][gitlab-deploy-token], only present if the Project has one related.| | **CI_DISPOSABLE_ENVIRONMENT** | all | 10.1 | Marks that the job is executed in a disposable environment (something that is created only for this job and disposed of/destroyed after the execution - all executors except `shell` and `ssh`). If the environment is disposable, it is set to true, otherwise it is not defined at all. | | **CI_ENVIRONMENT_NAME** | 8.15 | all | The name of the environment for this job | | **CI_ENVIRONMENT_SLUG** | 8.15 | all | A simplified version of the environment name, suitable for inclusion in DNS, URLs, Kubernetes labels, etc. | @@ -65,44 +66,44 @@ future GitLab releases.** | **CI_JOB_NAME** | 9.0 | 0.5 | The name of the job as defined in `.gitlab-ci.yml` | | **CI_JOB_STAGE** | 9.0 | 0.5 | The name of the stage as defined in `.gitlab-ci.yml` | | **CI_JOB_TOKEN** | 9.0 | 1.2 | Token used for authenticating with the [GitLab Container Registry][registry] and downloading [dependent repositories][dependent-repositories] | +| **CI_JOB_URL** | 11.1 | 0.5 | Job details URL | | **CI_MERGE_REQUEST_ID** | 11.6 | all | The ID of the merge request if it's [pipelines for merge requests](../merge_request_pipelines/index.md) | | **CI_MERGE_REQUEST_IID** | 11.6 | all | The IID of the merge request if it's [pipelines for merge requests](../merge_request_pipelines/index.md) | -| **CI_MERGE_REQUEST_REF_PATH** | 11.6 | all | The ref path of the merge request if it's [pipelines for merge requests](../merge_request_pipelines/index.md). (e.g. `refs/merge-requests/1/head`) | | **CI_MERGE_REQUEST_PROJECT_ID** | 11.6 | all | The ID of the project of the merge request if it's [pipelines for merge requests](../merge_request_pipelines/index.md) | | **CI_MERGE_REQUEST_PROJECT_PATH** | 11.6 | all | The path of the project of the merge request if it's [pipelines for merge requests](../merge_request_pipelines/index.md) (e.g. `namespace/awesome-project`) | | **CI_MERGE_REQUEST_PROJECT_URL** | 11.6 | all | The URL of the project of the merge request if it's [pipelines for merge requests](../merge_request_pipelines/index.md) (e.g. `http://192.168.10.15:3000/namespace/awesome-project`) | -| **CI_MERGE_REQUEST_TARGET_BRANCH_NAME** | 11.6 | all | The target branch name of the merge request if it's [pipelines for merge requests](../merge_request_pipelines/index.md) | +| **CI_MERGE_REQUEST_REF_PATH** | 11.6 | all | The ref path of the merge request if it's [pipelines for merge requests](../merge_request_pipelines/index.md). (e.g. `refs/merge-requests/1/head`) | +| **CI_MERGE_REQUEST_SOURCE_BRANCH_NAME** | 11.6 | all | The source branch name of the merge request if it's [pipelines for merge requests](../merge_request_pipelines/index.md) | | **CI_MERGE_REQUEST_SOURCE_PROJECT_ID** | 11.6 | all | The ID of the source project of the merge request if it's [pipelines for merge requests](../merge_request_pipelines/index.md) | | **CI_MERGE_REQUEST_SOURCE_PROJECT_PATH** | 11.6 | all | The path of the source project of the merge request if it's [pipelines for merge requests](../merge_request_pipelines/index.md) | | **CI_MERGE_REQUEST_SOURCE_PROJECT_URL** | 11.6 | all | The URL of the source project of the merge request if it's [pipelines for merge requests](../merge_request_pipelines/index.md) | -| **CI_MERGE_REQUEST_SOURCE_BRANCH_NAME** | 11.6 | all | The source branch name of the merge request if it's [pipelines for merge requests](../merge_request_pipelines/index.md) | +| **CI_MERGE_REQUEST_TARGET_BRANCH_NAME** | 11.6 | all | The target branch name of the merge request if it's [pipelines for merge requests](../merge_request_pipelines/index.md) | | **CI_NODE_INDEX** | 11.5 | all | Index of the job in the job set. If the job is not parallelized, this variable is not set. | | **CI_NODE_TOTAL** | 11.5 | all | Total number of instances of this job running in parallel. If the job is not parallelized, this variable is set to `1`. | -| **CI_JOB_URL** | 11.1 | 0.5 | Job details URL | -| **CI_REPOSITORY_URL** | 9.0 | all | The URL to clone the Git repository | -| **CI_RUNNER_DESCRIPTION** | 8.10 | 0.5 | The description of the runner as saved in GitLab | -| **CI_RUNNER_ID** | 8.10 | 0.5 | The unique id of runner being used | -| **CI_RUNNER_TAGS** | 8.10 | 0.5 | The defined runner tags | -| **CI_RUNNER_VERSION** | all | 10.6 | GitLab Runner version that is executing the current job | -| **CI_RUNNER_REVISION** | all | 10.6 | GitLab Runner revision that is executing the current job | -| **CI_RUNNER_EXECUTABLE_ARCH** | all | 10.6 | The OS/architecture of the GitLab Runner executable (note that this is not necessarily the same as the environment of the executor) | | **CI_PIPELINE_ID** | 8.10 | 0.5 | The unique id of the current pipeline that GitLab CI uses internally | | **CI_PIPELINE_IID** | 11.0 | all | The unique id of the current pipeline scoped to project | -| **CI_PIPELINE_TRIGGERED** | all | all | The flag to indicate that job was [triggered] | | **CI_PIPELINE_SOURCE** | 10.0 | all | Indicates how the pipeline was triggered. Possible options are: `push`, `web`, `trigger`, `schedule`, `api`, and `pipeline`. For pipelines created before GitLab 9.5, this will show as `unknown` | +| **CI_PIPELINE_TRIGGERED** | all | all | The flag to indicate that job was [triggered] | +| **CI_PIPELINE_URL** | 11.1 | 0.5 | Pipeline details URL | | **CI_PROJECT_DIR** | all | all | The full path where the repository is cloned and where the job is run | | **CI_PROJECT_ID** | all | all | The unique id of the current project that GitLab CI uses internally | | **CI_PROJECT_NAME** | 8.10 | 0.5 | The project name that is currently being built (actually it is project folder name) | | **CI_PROJECT_NAMESPACE** | 8.10 | 0.5 | The project namespace (username or groupname) that is currently being built | | **CI_PROJECT_PATH** | 8.10 | 0.5 | The namespace with project name | | **CI_PROJECT_PATH_SLUG** | 9.3 | all | `$CI_PROJECT_PATH` lowercased and with everything except `0-9` and `a-z` replaced with `-`. Use in URLs and domain names. | -| **CI_PIPELINE_URL** | 11.1 | 0.5 | Pipeline details URL | | **CI_PROJECT_URL** | 8.10 | 0.5 | The HTTP address to access project | | **CI_PROJECT_VISIBILITY** | 10.3 | all | The project visibility (internal, private, public) | | **CI_REGISTRY** | 8.10 | 0.5 | If the Container Registry is enabled it returns the address of GitLab's Container Registry | | **CI_REGISTRY_IMAGE** | 8.10 | 0.5 | If the Container Registry is enabled for the project it returns the address of the registry tied to the specific project | | **CI_REGISTRY_PASSWORD** | 9.0 | all | The password to use to push containers to the GitLab Container Registry | | **CI_REGISTRY_USER** | 9.0 | all | The username to use to push containers to the GitLab Container Registry | +| **CI_REPOSITORY_URL** | 9.0 | all | The URL to clone the Git repository | +| **CI_RUNNER_DESCRIPTION** | 8.10 | 0.5 | The description of the runner as saved in GitLab | +| **CI_RUNNER_EXECUTABLE_ARCH** | all | 10.6 | The OS/architecture of the GitLab Runner executable (note that this is not necessarily the same as the environment of the executor) | +| **CI_RUNNER_ID** | 8.10 | 0.5 | The unique id of runner being used | +| **CI_RUNNER_REVISION** | all | 10.6 | GitLab Runner revision that is executing the current job | +| **CI_RUNNER_TAGS** | 8.10 | 0.5 | The defined runner tags | +| **CI_RUNNER_VERSION** | all | 10.6 | GitLab Runner version that is executing the current job | | **CI_SERVER** | all | all | Mark that job is executed in CI environment | | **CI_SERVER_NAME** | all | all | The name of CI server that is used to coordinate jobs | | **CI_SERVER_REVISION** | all | all | GitLab revision that is used to schedule jobs | @@ -316,6 +317,8 @@ Running on runner-8a2f473d-project-1796893-concurrent-0 via runner-8a2f473d-mach ++ CI_DEBUG_TRACE=false ++ export CI_COMMIT_SHA=dd648b2e48ce6518303b0bb580b2ee32fadaf045 ++ CI_COMMIT_SHA=dd648b2e48ce6518303b0bb580b2ee32fadaf045 +++ export CI_COMMIT_SHORT_SHA=dd648b2e +++ CI_COMMIT_SHORT_SHA=dd648b2e ++ export CI_COMMIT_BEFORE_SHA=dd648b2e48ce6518303b0bb580b2ee32fadaf045 ++ CI_COMMIT_BEFORE_SHA=dd648b2e48ce6518303b0bb580b2ee32fadaf045 ++ export CI_COMMIT_REF_NAME=master @@ -462,6 +465,7 @@ Example values: ```bash export CI_JOB_ID="50" export CI_COMMIT_SHA="1ecfd275763eff1d6b4844ea3168962458c9f27a" +export CI_COMMIT_SHORT_SHA="1ecfd275" export CI_COMMIT_REF_NAME="master" export CI_REPOSITORY_URL="https://gitlab-ci-token:abcde-1234ABCD5678ef@example.com/gitlab-org/gitlab-ce.git" export CI_COMMIT_TAG="1.0.0" diff --git a/doc/ci/variables/img/variables.png b/doc/ci/variables/img/variables.png Binary files differindex d2dc99bbac0..0795f7c888f 100644 --- a/doc/ci/variables/img/variables.png +++ b/doc/ci/variables/img/variables.png diff --git a/doc/ci/yaml/README.md b/doc/ci/yaml/README.md index 4dafde0462a..5d87a5c0a1f 100644 --- a/doc/ci/yaml/README.md +++ b/doc/ci/yaml/README.md @@ -203,7 +203,7 @@ used for time of the job. The configuration of this feature is covered in jobs, including deploy jobs, but after the restoration of [artifacts](#artifacts). This can be an array or a multi-line string. -`after_script` is used to define the command that will be run after for all +`after_script` is used to define the command that will be run after all jobs, including failed ones. This has to be an array or a multi-line string. The `before_script` and the main `script` are concatenated and run in a single context/container. @@ -402,7 +402,7 @@ job: script: echo 'test' ``` -is translated to +is translated to: ```yaml job: @@ -412,49 +412,64 @@ job: ## `only` and `except` (complex) -> `refs` and `kubernetes` policies introduced in GitLab 10.0 -> -> `variables` policy introduced in 10.7 -> -> `changes` policy [introduced](https://gitlab.com/gitlab-org/gitlab-ce/issues/19232) in 11.4 +> - `refs` and `kubernetes` policies introduced in GitLab 10.0. +> - `variables` policy introduced in GitLab 10.7. +> - `changes` policy [introduced](https://gitlab.com/gitlab-org/gitlab-ce/issues/19232) in GitLab 11.4. CAUTION: **Warning:** This an _alpha_ feature, and it is subject to change at any time without prior notice! -Since GitLab 10.0 it is possible to define a more elaborate only/except job -policy configuration. +GitLab supports both simple and complex strategies, so it's possible to use an +array and a hash configuration scheme. -GitLab now supports both, simple and complex strategies, so it is possible to -use an array and a hash configuration scheme. +Four keys are available: -Four keys are now available: `refs`, `kubernetes` and `variables` and `changes`. +- `refs` +- `variables` +- `changes` +- `kubernetes` -### `refs` and `kubernetes` +If you use multiple keys under `only` or `except`, they act as an AND. The logic is: -Refs strategy equals to simplified only/except configuration, whereas -kubernetes strategy accepts only `active` keyword. +> (any of refs) AND (any of variables) AND (any of changes) AND (if kubernetes is active) -### `only:variables` +### `only:refs` and `except:refs` -`variables` keyword is used to define variables expressions. In other words -you can use predefined variables / project / group or -environment-scoped variables to define an expression GitLab is going to -evaluate in order to decide whether a job should be created or not. +The `refs` strategy can take the same values as the +[simplified only/except configuration](#only-and-except-simplified). -See the example below. Job is going to be created only when pipeline has been -scheduled or runs for a `master` branch, and only if kubernetes service is -active in the project. +In the example below, the `deploy` job is going to be created only when the +pipeline has been [scheduled][schedules] or runs for the `master` branch: ```yaml -job: +deploy: only: refs: - master - schedules +``` + +### `only:kubernetes` and `except:kubernetes` + +The `kubernetes` strategy accepts only the `active` keyword. + +In the example below, the `deploy` job is going to be created only when the +Kubernetes service is active in the project: + +```yaml +deploy: + only: kubernetes: active ``` +### `only:variables` and `except:variables` + +The `variables` keyword is used to define variables expressions. In other words, +you can use predefined variables / project / group or +environment-scoped variables to define an expression GitLab is going to +evaluate in order to decide whether a job should be created or not. + Examples of using variables expressions: ```yaml @@ -468,7 +483,7 @@ deploy: - $STAGING ``` -Another use case is exluding jobs depending on a commit message _(added in 11.0)_: +Another use case is excluding jobs depending on a commit message: ```yaml end-to-end: @@ -478,11 +493,11 @@ end-to-end: - $CI_COMMIT_MESSAGE =~ /skip-end-to-end-tests/ ``` -Learn more about variables expressions on [a separate page][variables-expressions]. +Learn more about [variables expressions](../variables/README.md#variables-expressions). -### `only:changes` +### `only:changes` and `except:changes` -Using `changes` keyword with `only` or `except` makes it possible to define if +Using the `changes` keyword with `only` or `except`, makes it possible to define if a job should be created based on files modified by a git push event. For example: @@ -499,13 +514,13 @@ docker build: ``` In the scenario above, if you are pushing multiple commits to GitLab to an -existing branch, GitLab creates and triggers `docker build` job, provided that +existing branch, GitLab creates and triggers the `docker build` job, provided that one of the commits contains changes to either: - The `Dockerfile` file. - Any of the files inside `docker/scripts/` directory. -- Any of the files and subfolders inside `dockerfiles` directory. -- Any of the files with `rb`, `py`, `sh` extensions inside `more_scripts` directory. +- Any of the files and subdirectories inside the `dockerfiles` directory. +- Any of the files with `rb`, `py`, `sh` extensions inside the `more_scripts` directory. CAUTION: **Warning:** There are some caveats when using this feature with new branches and tags. See @@ -569,15 +584,17 @@ osx job: ## `allow_failure` -`allow_failure` is used when you want to allow a job to fail without impacting -the rest of the CI suite. Failed jobs don't contribute to the commit status. +`allow_failure` allows a job to fail without impacting the rest of the CI +suite. The default value is `false`, except for [manual](#whenmanual) jobs. -When enabled and the job fails, the pipeline will be successful/green for all -intents and purposes, but a "CI build passed with warnings" message will be -displayed on the merge request or commit or job page. This is to be used by -jobs that are allowed to fail, but where failure indicates some other (manual) -steps should be taken elsewhere. +When enabled and the job fails, the job will show an orange warning in the UI. +However, the logical flow of the pipeline will consider the job a +success/passed, and is not blocked. + +Assuming all other jobs are successful, the job's stage and its pipeline will +show the same orange warning. However, the associated commit will be marked +"passed", without warnings. In the example below, `job1` and `job2` will run in parallel, but if `job1` fails, it will not stop the next stage from running, since it's marked with @@ -609,12 +626,13 @@ failure. `when` can be set to one of the following values: 1. `on_success` - execute job only when all jobs from prior stages - succeed. This is the default. + succeed (or are considered succeeding because they are marked + `allow_failure`). This is the default. 1. `on_failure` - execute job only when at least one job from prior stages fails. 1. `always` - execute job regardless of the status of jobs from prior stages. 1. `manual` - execute job manually (added in GitLab 8.10). Read about - [manual actions](#when-manual) below. + [manual actions](#whenmanual) below. For example: @@ -674,7 +692,7 @@ Manual actions are a special type of job that are not executed automatically, they need to be explicitly started by a user. An example usage of manual actions would be a deployment to a production environment. Manual actions can be started from the pipeline, job, environment, and deployment views. Read more at the -[environments documentation][env-manual]. +[environments documentation](../environments.md#manually-deploying-to-environments). Manual actions can be either optional or blocking. Blocking manual actions will block the execution of the pipeline at the stage this action is defined in. It's @@ -1323,7 +1341,7 @@ The test reports are collected regardless of the job results (success or failure You can use [`artifacts:expire_in`](#artifacts-expire_in) to set up an expiration date for their artifacts. -NOTE: **Note:** +NOTE: **Note:** If you also want the ability to browse the report output files, include the [`artifacts:paths`](#artifactspaths) keyword. @@ -1642,7 +1660,7 @@ automatically fetched and evaluated along with the content of `.gitlab-ci.yml`: before_script: - apt-get update -qq && apt-get install -y -qq sqlite3 libsqlite3-dev nodejs - - gem install bundler --no-ri --no-rdoc + - gem install bundler --no-document - bundle install --jobs $(nproc) "${FLAGS[@]}" ``` @@ -1657,7 +1675,7 @@ rspec: ``` NOTE: **Note:** -`include` requires the external YAML files to have the extensions `.yml` or `.yaml`. +`include` requires the external YAML files to have the extensions `.yml` or `.yaml`. The external file will not be included if the extension is missing. You can define it either as a single string, or, in case you want to include @@ -1709,20 +1727,24 @@ include: The remote file must be publicly accessible through a simple GET request, as we don't support authentication schemas in the remote URL. NOTE: **Note:** - In order to include files from another repository inside your local network, + In order to include files from another repository inside your local network, you may need to enable the **Allow requests to the local network from hooks and services** checkbox located in the **Settings > Network > Outbound requests** section within the **Admin area**. --- -Since GitLab 10.8 we are now recursively merging the files defined in `include` +Since GitLab 10.8 we are now deep merging the files defined in `include` with those in `.gitlab-ci.yml`. Files defined by `include` are always -evaluated first and recursively merged with the content of `.gitlab-ci.yml`, no +evaluated first and merged with the content of `.gitlab-ci.yml`, no matter the position of the `include` keyword. You can take advantage of -recursive merging to customize and override details in included CI +merging to customize and override details in included CI configurations with local definitions. +NOTE: **Note:** +The recursive includes are not supported, meaning your external files +should not use the `include` keyword, as it will be ignored. + The following example shows specific YAML-defined variables and details of the `production` job from an include file being customized in `.gitlab-ci.yml`. @@ -1772,11 +1794,7 @@ with the environment url of the `production` job defined in `autodevops-template.yml` have been overridden by new values defined in `.gitlab-ci.yml`. -NOTE: **Note:** -Recursive includes are not supported meaning your external files -should not use the `include` keyword, as it will be ignored. - -Recursive merging lets you extend and override dictionary mappings, but +The merging lets you extend and override dictionary mappings, but you cannot add or modify items to an included array. For example, to add an additional item to the production job script, you must repeat the existing script items. @@ -2182,6 +2200,12 @@ with an API call. If your commit message contains `[ci skip]` or `[skip ci]`, using any capitalization, the commit will be created but the pipeline will be skipped. +Alternatively, one can pass the `ci.skip` [Git push option][push-option] if +using Git 2.10 or newer: +``` +$ git push -o ci.skip +``` + ## Validate the .gitlab-ci.yml Each instance of GitLab CI has an embedded debug tool called Lint, which validates the @@ -2195,19 +2219,15 @@ try to quote them, or change them to a different form (e.g., `/bin/true`). ## Examples -Visit the [examples README][examples] to see a list of examples using GitLab -CI with various languages. +See a [list of examples](../examples/README.md "CI/CD examples") for using +GitLab CI/CD with various languages. -[env-manual]: ../environments.md#manually-deploying-to-environments -[examples]: ../examples/README.md [ce-6323]: https://gitlab.com/gitlab-org/gitlab-ce/merge_requests/6323 -[environment]: ../environments.md [ce-6669]: https://gitlab.com/gitlab-org/gitlab-ce/merge_requests/6669 -[variables]: ../variables/README.md [ce-7983]: https://gitlab.com/gitlab-org/gitlab-ce/merge_requests/7983 [ce-7447]: https://gitlab.com/gitlab-org/gitlab-ce/merge_requests/7447 [ce-12909]: https://gitlab.com/gitlab-org/gitlab-ce/merge_requests/12909 -[schedules]: ../../user/project/pipelines/schedules.md -[variables-expressions]: ../variables/README.md#variables-expressions -[ee]: https://about.gitlab.com/gitlab-ee/ -[gitlab-versions]: https://about.gitlab.com/products/ +[environment]: ../environments.md "CI/CD environments" +[schedules]: ../../user/project/pipelines/schedules.md "Pipelines schedules" +[variables]: ../variables/README.md "CI/CD variables" +[push-option]: https://git-scm.com/docs/git-push#git-push--oltoptiongt diff --git a/doc/development/README.md b/doc/development/README.md index bcf57a223f5..f22dde32de9 100644 --- a/doc/development/README.md +++ b/doc/development/README.md @@ -52,7 +52,6 @@ description: 'Learn how to contribute to GitLab.' - [Prometheus metrics](prometheus_metrics.md) - [Guidelines for reusing abstractions](reusing_abstractions.md) - [DeclarativePolicy framework](policies.md) -- [Switching to Rails 5](switching_to_rails5.md) ## Performance guides diff --git a/doc/development/automatic_ce_ee_merge.md b/doc/development/automatic_ce_ee_merge.md index 8208e9007ea..0cc083cefc0 100644 --- a/doc/development/automatic_ce_ee_merge.md +++ b/doc/development/automatic_ce_ee_merge.md @@ -1,12 +1,13 @@ # Automatic CE->EE merge Commits pushed to CE `master` are automatically merged into EE `master` roughly -every 5 minutes. Changes are merged using the `ours` merge strategy in the -context of EE. This means that any merge conflicts are resolved by taking the EE -changes and discarding the CE changes. This removes the need for resolving -conflicts or reverting changes, at the cost of **absolutely requiring** EE merge -requests to be created whenever a CE merge request causes merge conflicts. -Failing to do so can result in changes not making their way into EE. +every 5 minutes. Changes are merged using the `recursive=ours` merge strategy in +the context of EE. This means that any merge conflicts are resolved by taking +the EE changes and discarding the CE changes. This removes the need for +resolving conflicts or reverting changes, at the cost of **absolutely +requiring** EE merge requests to be created whenever a CE merge request causes +merge conflicts. Failing to do so can result in changes not making their way +into EE. ## Always create an EE merge request if there are conflicts @@ -169,10 +170,6 @@ you are not required to submit the EE-equivalent MR, but it's still recommended. job failed, you are required to submit the EE MR so that you can fix the conflicts in EE before merging your changes into CE. ---- - -[Return to Development documentation](README.md) - ## FAQ ### How does automatic merging work? @@ -180,7 +177,8 @@ before merging your changes into CE. The automatic merging is performed using a project called [Merge Train](https://gitlab.com/gitlab-org/merge-train/). This project will clone CE and EE master, and merge CE master into EE master using `git merge ---strategy=ours`. This process runs roughly every 5 minutes. +--strategy=recursive --strategy-option=ours`. This process runs multiple times +per hour. For more information on the exact implementation you can refer to the source code. @@ -225,3 +223,7 @@ so that the EE specific changes are not intertwined with CE code. For Ruby code you can do this by moving the EE code to a separate module, which can then be injected into the appropriate classes or modules. See [Guidelines for implementing Enterprise Edition features](ee_features.md) for more information. + +--- + +[Return to Development documentation](README.md) diff --git a/doc/development/contributing/index.md b/doc/development/contributing/index.md index 9da4c66933c..9dffca5b99c 100644 --- a/doc/development/contributing/index.md +++ b/doc/development/contributing/index.md @@ -3,7 +3,8 @@ Thank you for your interest in contributing to GitLab. This guide details how to contribute to GitLab in a way that is easy for everyone. -We want to create a welcoming environment for everyone who is interested in contributing. Please visit our [Code of Conduct page](https://about.gitlab.com/contributing/code-of-conduct) to learn more about our committment to an open and welcoming environment. +We want to create a welcoming environment for everyone who is interested in contributing. +Please visit our [Code of Conduct page](https://about.gitlab.com/contributing/code-of-conduct) to learn more about our committment to an open and welcoming environment. For a first-time step-by-step guide to the contribution process, please see ["Contributing to GitLab"](https://about.gitlab.com/contributing/). @@ -92,9 +93,9 @@ When submitting code to GitLab, you may feel that your contribution requires the When your code contains more than 500 changes, any major breaking changes, or an external library, `@mention` a maintainer in the merge request. If you are not sure who to mention, the reviewer will add one early in the merge request process. -## Workflow labels +## Issues -This [documentation](issue_workflow.md) outlines the current workflow labels. +This [documentation](issue_workflow.md) outlines the current issue process. * [Type labels](issue_workflow.md#type-labels) * [Subject labels](issue_workflow.md#subject-labels) @@ -128,6 +129,6 @@ This [documentation](style_guides.md) outlines the current style guidelines. [Return to Development documentation](../README.md) [core team]: https://about.gitlab.com/core-team/ -[team]: https://about.gitlab.com/team/ +[team]: https://about.gitlab.com/company/team/ [getting-help]: https://about.gitlab.com/getting-help/ [codetriage]: http://www.codetriage.com/gitlabhq/gitlabhq diff --git a/doc/development/contributing/merge_request_workflow.md b/doc/development/contributing/merge_request_workflow.md index 5b32b5cd46f..6bcee74a3dd 100644 --- a/doc/development/contributing/merge_request_workflow.md +++ b/doc/development/contributing/merge_request_workflow.md @@ -179,7 +179,6 @@ merge request: 1. Note the addition in the release blog post (create one if it doesn't exist yet) https://gitlab.com/gitlab-com/www-gitlab-com/merge_requests/ 1. Upgrade guide, for example https://gitlab.com/gitlab-org/gitlab-ce/blob/master/doc/update/7.5-to-7.6.md -1. Upgrader https://gitlab.com/gitlab-org/gitlab-ce/blob/master/doc/update/upgrader.md#2-run-gitlab-upgrade-tool 1. Installation guide https://gitlab.com/gitlab-org/gitlab-ce/blob/master/doc/install/installation.md#1-packages-dependencies 1. GitLab Development Kit https://gitlab.com/gitlab-org/gitlab-development-kit 1. Test suite https://gitlab.com/gitlab-org/gitlab-ce/blob/master/scripts/prepare_build.sh diff --git a/doc/development/documentation/feature-change-workflow.md b/doc/development/documentation/feature-change-workflow.md new file mode 100644 index 00000000000..b5b325683a3 --- /dev/null +++ b/doc/development/documentation/feature-change-workflow.md @@ -0,0 +1,112 @@ +--- +description: How to add docs for new or enhanced GitLab features. +--- + +# Documentation process at GitLab + +At GitLab, developers contribute new or updated documentation along with their code, but product managers and technical writers also have essential roles in the process. + +- **Developers**: Author/update documentation in the same MR as their code, and +merge it by the feature freeze for the assigned milestone. Request technical writer +assistance if needed. +- **Product Managers** (PMs): In the issue for all new and enhanced features, +confirm the documentation requirements, plus the mentioned feature description +and use cases, which can be reused in docs. They can bring in a technical +writer for discussion or help, and can be called upon themselves as a doc reviewer. +- **Technical Writers**: Review doc requirements in issues, track issues and MRs +that contain docs changes, help with any questions throughout the authoring/editing process, +and review all new and updated docs content after it's merged (unless a pre-merge +review request is made). + +Beyond this process, any member of the GitLab community can also author documentation +improvements that are not associated with a new or changed feature. See the [Documentation improvement workflow](improvement-workflow.md). + +## When documentation is required + +Documentation must be delivered whenever: + +- A new or enhanced feature is shipped that impacts the user/admin experience +- There are changes to the UI or API +- A process, workflow, or previously documented feature is changed +- A feature is deprecated or removed + +Documentation is not required when a feature is changed on the backend +only and does not directly affect the way that any user or +administrator would interact with GitLab. For example, a UI restyling that offers +no difference in functionality may require documentation updates if screenshots +are now needed, or need to be updated. + +NOTE: **Note:** +When revamping documentation, if unrelated to the feature change, this should be submitted +in its own MR (using the [documentation improvement workflow](improvement-workflow.md)) +so that we can ensure the more time-sensitive doc updates are merged with code by the freeze. + +## Documenting a new or changed feature + +To follow a consistent workflow every month, documentation changes +involve the Product Managers, the developer who shipped the feature, +and the Technical Writing team. Each role is described below. + +### 1. Product Manager's role + +The Product Manager (PM) should confirm or add the following items in the issue: + +- New or updated feature name, overview/description, and use cases, all required per the [Documentation structure and template](structure.md). +- The documentation requirements for the developer working on the docs. + - What new page, new subsection of an existing page, or other update to an existing page/subsection is needed. + - Just one page/section/update or multiple (perhaps there's an end user and admin change needing docs, or we need to update a previously recommended workflow, or we want to link the new feature from various places; consider and mention all ways documentation should be affected. + - Suggested title of any page or subsection, if applicable. +- Label the issue with `Documentation` and `docs:P1` in addition to the `Deliverable` label and correct milestone. + +Anyone is welcome to draft the items above in the issue, but a product manager must review and update them whenever the issue is assigned a specific milestone. + +### 2. Developer's role + +As a developer, you must ship the documentation with the code of the feature that +you are creating or updating. The documentation is an essential part of the product. + +- New and edited docs should be included in the MR introducing the code, and planned +in the issue that proposed the feature. However, if the new or changed doc requires +extensive collaboration or conversation, a separate, linked issue can be used for the planning process. +- Use the [Documentation guidelines](index.md), as well as other resources linked from there, +including the [Structure and template](structure.md) page, [Style Guide](styleguide.md), and [Markdown Guide](https://about.gitlab.com/handbook/product/technical-writing/markdown-guide/). +- If you need any help to choose the correct place for a doc, discuss a documentation +idea or outline, or request any other help, ping the Technical Writer for the relevant +[DevOps stage](https://about.gitlab.com/handbook/product/categories/#devops-stages) +in your issue or MR, or write within `#docs` on the GitLab Slack. +- The docs must be merged with the code **by the feature freeze date**, otherwise +- the feature cannot be included with the release.<!-- TODO: Policy/process for feature-flagged issues --> + +Prior to merge, documentation changes commited by the developer must be reviewed by: +* the person reviewing the code and merging the MR. +* optionally: others involved in the work (such as other devs, the PM, or a technical writer), if requested. + +After merging, documentation changing are reviewed by: +* a technical writer (for clarity, structure, grammar, etc). +* optionally: by the PM (for accuracy and to ensure it's consistent with the vision for how the product will be used). +Any party can raise the item to the PM for review at any point: the dev, the technical writer, or the PM, who can request/plan a review at the outset. + +### 3. Technical Writer's role + +**Planning** +- Once an issue contains a Documentation label and an upcoming milestone, a +technical writer reviews the listed documentation requirements, which should have +already been reviewed by the PM. (These are non-blocking reviews; developers should +not wait to work on docs.) +- Monitor the documentation needs of issues assigned to the current and next milestone, +and participate in any needed discussion on docs planning with the dev, PM, and others. + +**Review** +- Techncial writers provide non-blocking reviews of all documentation changes, +typically after the change is merged. However, if the docs are ready in the MR while +we are awaiting other work in order to merge, the technical writer's review can commence early. +- The technical writer will confirm that the doc is clear, grammatically correct, +and discoverable, while avoiding redundancy, bad file locations, typos, broken links, +etc. The technical writer will review the documentation for the following, which +the developer and code reviewer should have already made a good-faith effort to ensure: + - Clarity. + - Relevance (make sure the content is appropriate given the impact of the feature). + - Location (make sure the doc is in the correct dir and has the correct name). + - Syntax, typos, and broken links. + - Improvements to the content. + - Accordance to the [Documentation Style Guide](styleguide.md) and [structure/template](structure.md). diff --git a/doc/development/documentation/improvement-workflow.md b/doc/development/documentation/improvement-workflow.md new file mode 100644 index 00000000000..ef6392c6f7f --- /dev/null +++ b/doc/development/documentation/improvement-workflow.md @@ -0,0 +1,49 @@ +--- +description: How to improve GitLab's documentation. +--- + +# Documentation improvement workflow + +Anyone can contribute a merge request or create an issue for GitLab's documentation. + +This page covers the process for any contributions to GitLab's docs that are +not part of feature development. If you are looking for information on updating +GitLab's docs as is required with the development and release of a new feature +or feature enhancement, see the [feature-change documentation workflow](feature-change-workflow.md). + +## Who updates the docs + +Anyone can contribute! You can create a merge request with documentation +when you find errors or other room for improvement in an existing doc, or when you +have an idea for all-new documentation that would help a GitLab user or admin +to achieve or improve their DevOps workflows. + +## How to update the docs + +- Follow the described standards and processes listed on the [GitLab Documentation guidelines](index.md) page, +including linked resources: the [Structure and template](structure.md) page, [Style Guide](styleguide.md), and [Markdown Guide](https://about.gitlab.com/handbook/product/technical-writing/markdown-guide/). +- Follow GitLab's [Merge Request Guidelines](../contributing/merge_request_workflow.md#merge-request-guidelines). +- If you need any help to choose the correct place for a doc, discuss a documentation +idea or outline, or request any other help, ping the Technical Writer for the relevant +[DevOps stage](https://about.gitlab.com/handbook/product/categories/#devops-stages) +in your issue or MR, or write within `#docs` if you are a member of GitLab's Slack workspace. + +## Merging + +Anyone with master access to the affected GitLab project can merge documentation changes. +This person must make a good-faith effort to ensure that the content is clear +(sufficiently easy for the intended audience to navigate and understand) and +that it meets the [Documentation Guidelines](index.md) and [Style Guide](styleguide.md). + +If the author or reviewer has any questions, or would like a techncial writer's review +before merging, mention the writer who is assigned to the relevant [DevOps stage](https://about.gitlab.com/handbook/product/categories/#devops-stages). + +## Technical Writer review + +The technical writing team reviews changes after they are merged, unless a prior +review is requested. + +## Other ways to help + +If you have ideas for further documentation resources that would be best +considered/handled by technical writers, devs, and other SMEs, please create an issue. diff --git a/doc/development/documentation/index.md b/doc/development/documentation/index.md index 4e5b4a85a97..256e0476c2f 100644 --- a/doc/development/documentation/index.md +++ b/doc/development/documentation/index.md @@ -4,48 +4,49 @@ description: Learn how to contribute to GitLab Documentation. # GitLab Documentation guidelines -- **General Documentation**: written by the [developers responsible by creating features](#contributing-to-docs). Should be submitted in the same merge request containing code. Feature proposals (by GitLab contributors) should also be accompanied by its respective documentation. They can be later improved by PMs and Technical Writers. -- **[Technical Articles](#technical-articles)**: written by any [GitLab Team](https://about.gitlab.com/team/) member, GitLab contributors, or [Community Writers](https://about.gitlab.com/handbook/product/technical-writing/community-writers/). -- **Indexes per topic**: initially prepared by the Technical Writing Team, and kept up-to-date by developers and PMs in the same merge request containing code. They gather all resources for that topic in a single page (user and admin documentation, articles, and third-party docs). +GitLab's documentation is [intended as the single source of truth (SSOT)](https://about.gitab.com/handbook/documentation/) for information about how to configure, use, and troubleshoot GitLab. The documentation contains use cases and usage instructions covering every GitLab feature, organized by product area and subject. This includes topics and workflows that span multiple GitLab features, as well as the use of GitLab with other applications. -## Contributing to docs +In addition to this page, the following resources to help craft and contribute documentation are available: + +- [Style Guide](styleguide.md) - What belongs in the docs, language guidelines, and more. +- [Structure and template](structure.md) - Learn the typical parts of a doc page and how to write each one. +- [Workflow](workflow.md) - A landing page for our key workflows: + - [Feature-change documentation workflow](feature-change-workflow.md) - Adding required documentation when developing a GitLab feature. + - [Documentation improvement worflow](improvement-workflow.md) - New content not associated with a new feature. +- [Markdown Guide](https://about.gitlab.com/handbook/product/technical-writing/markdown-guide/) - A reference for the markdown implementation used by GitLab's documentation site and about.gitlab.com. +- [Site architecture](/doc/development/documentation/site_architecture/index.md) - How docs.gitlab.com is built. + +## Source and rendered locations -Whenever a feature is changed, updated, introduced, or deprecated, the merge -request introducing these changes must be accompanied by the documentation -(either updating existing ones or creating new ones). This is also valid when -changes are introduced to the UI. +Documentation for GitLab Community Edition (CE) and Enterprise Edition (EE), along with GitLab Runner and Omnibus, is published to [docs.gitlab.com](https://docs.gitlab.com). The documentation for CE and EE is also published within the application at `/help` on the domain of the GitLab instance. -The one responsible for writing the first piece of documentation is the developer who -wrote the code. It's the job of the Product Manager to ensure all features are -shipped with its docs, whether is a small or big change. At the pace GitLab evolves, -this is the only way to keep the docs up-to-date. If you have any questions about it, -ask a Technical Writer. Otherwise, when your content is ready, assign one of -them to review it for you. +At `/help`, only content for your current edition and version is included, whereas multiple versions' content is available at docs.gitlab.com. -We use the [monthly release blog post](https://about.gitlab.com/handbook/marketing/blog/release-posts/#monthly-releases) as a changelog checklist to ensure everything -is documented. +The source of the documentation is maintained in the following repository locations: -Whenever you submit a merge request for the documentation, use the -"Documentation" MR description template. If you're changing documentation -location, use the MR description template called "Change documentation -location" instead. +| Project | Path | +| --- | --- | +| [GitLab Community Edition](https://gitlab.com/gitlab-org/gitlab-ce/) | [`/doc`](https://gitlab.com/gitlab-org/gitlab-ce/tree/master/doc) | +| [GitLab Enterprise Edition](https://gitlab.com/gitlab-org/gitlab-ce/) | [`/doc`](https://gitlab.com/gitlab-org/gitlab-ee/tree/master/doc) | +| [GitLab Runner](https://gitlab.com/gitlab-org/gitlab-runner/) | [`/docs`](https://gitlab.com/gitlab-org/gitlab-runner/tree/master/docs) | +| [Omnibus GitLab](https://gitlab.com/gitlab-org/omnibus-gitlab/) | [`/doc`](https://gitlab.com/gitlab-org/gitlab-ee/tree/master/doc) | -## Documentation workflow +Documentation issues and merge requests are part of their respective repositories and all have the label `Documentation`. -Please read through the [documentation workflow](workflow.md) before getting started. +## Contributing to docs + +[Contributions to GitLab docs](workflow.md) are welcome from the entire GitLab community. -## Documentation structure +To ensure that GitLab docs keep up with changes to the product, special processes and responsibilities are in place concerning all [feature changes](feature-change-workflow.md)—i.e. development work that impacts the appearance, usage, or administration of a feature. -Follow through the [documentation structure guide](structure.md) for learning -how to structure GitLab docs. +Meanwhile, anyone can contribute [documentation improvements](improvement-workflow.md) large or small that are not associated with a feature change. For example, adding a new doc on how to accomplish a use case that's already possible with GitLab or with third-party tools and GitLab. ## Markdown and styles [GitLab docs](https://gitlab.com/gitlab-com/gitlab-docs) uses [GitLab Kramdown](https://gitlab.com/gitlab-org/gitlab_kramdown) -as markdown engine. Check the [GitLab Markdown Kramdown Guide](https://about.gitlab.com/handbook/product/technical-writing/markdown-guide/) -for a complete Kramdown reference. +as its markdown rendering engine. See the [GitLab Markdown Guide](https://about.gitlab.com/handbook/product/technical-writing/markdown-guide/) for a complete Kramdown reference. -Follow the [documentation style guidelines](styleguide.md) strictly. +Adhere to the [Documentation Style Guide](styleguide.md). If a style standard is missing, you are welcome to suggest one via a merge request. ## Documentation directory structure @@ -58,7 +59,7 @@ all docs should be linked. Every new document should be cross-linked to its rela The directories `/workflow/`, `/gitlab-basics/`, `/university/`, and `/articles/` have been **deprecated** and the majority their docs have been moved to their correct location -in small iterations. Please don't create new docs in these folders. +in small iterations. Please do not create new docs in these folders. Organize docs by product area and subject, not type. ### Documentation files @@ -71,18 +72,23 @@ in small iterations. Please don't create new docs in these folders. ### Location and naming documents -The documentation hierarchy can be vastly improved by providing a better layout -and organization of directories. - -Having a structured document layout, we will be able to have meaningful URLs -like `docs.gitlab.com/user/project/merge_requests/index.html`. With this pattern, -you can immediately tell that you are navigating a user related documentation -and is about the project and its merge requests. - -Do not create summaries of similar types of content (e.g. an index of all articles, videos, etc.), -rather organize content by its subject (e.g. everything related to CI goes together) +Our goal is to have a clear hierarchical structure with meaningful URLs +like `docs.gitlab.com/user/project/merge_requests/`. With this pattern, +you can immediately tell that you are navigating to user-related documentation +about project features; specifically about merge requests. Our site's paths match +those of our repository, so the clear structure also makes documentation easier to update. + +While the documentation is home to a variety of content types, we do not organize by content type. +For example, do not create groupings of similar media types (e.g. indexes of all articles, videos, etc.). +Similarly, we do not use glossaries or FAQs. Such grouping of content by type makes +it difficult to browse for the information you need and difficult to maintain up-to-date content. +Instead, organize content by its subject (e.g. everything related to CI goes together) and cross-link between any related content. +Do not simply link out to GitLab technical blog posts. There should be an up-to-date +single source of truth on the topic within the documentation, and the top of the +blog post should be updated to link to that doc. + The table below shows what kind of documentation goes where. | Directory | What belongs here | @@ -90,14 +96,12 @@ The table below shows what kind of documentation goes where. | `doc/user/` | User related documentation. Anything that can be done within the GitLab UI goes here including `/admin`. | | `doc/administration/` | Documentation that requires the user to have access to the server where GitLab is installed. The admin settings that can be accessed via GitLab's interface go under `doc/user/admin_area/`. | | `doc/api/` | API related documentation. | -| `doc/development/` | Documentation related to the development of GitLab. Any styleguides should go here. | +| `doc/development/` | Documentation related to the development of GitLab. Related process and style guides should go here. | | `doc/legal/` | Legal documents about contributing to GitLab. | | `doc/install/` | Probably the most visited directory, since `installation.md` is there. Ideally this should go under `doc/administration/`, but it's best to leave it as-is in order to avoid confusion (still debated though). | | `doc/update/` | Same with `doc/install/`. Should be under `administration/`, but this is a well known location, better leave as-is, at least for now. | | `doc/topics/` | Indexes per Topic (`doc/topics/topic-name/index.md`): all resources for that topic (user and admin documentation, articles, and third-party docs) | ---- - **General rules & best practices:** 1. When creating a new document and it has more than one word in its name, @@ -126,18 +130,23 @@ The table below shows what kind of documentation goes where. `doc/topics/topic-name/subtopic-name/index.md` when subtopics become necessary. General user- and admin- related documentation, should be placed accordingly. -If you are unsure where a document should live, you can ping `@axil` or `@marcia` in your -merge request. +If you are unsure where a document or a content addition should live, this should +not stop you from authoring and contributing. You can use your best judgment and +then ask the reviewer of your MR to confirm your decision, and/or ask a technical writer +at any stage in the process. The techncial writing team will review all documentation +changes, regardless, and can move content if there is a better place for it. ### Changing document location -Changing a document's location is not to be taken lightly. Remember that the -documentation is available to all installations under `help/` and not only to -GitLab.com or <http://docs.gitlab.com>. Make sure this is discussed with the -Documentation team beforehand. +Changing a document's location requires specific steps to be followed to ensure that +users can seamlessly access the new doc page, whether they are accesing content +on a GitLab instance domain at `/help` or at docs.gitlab.com. Be sure to ping a +GitLab technical writer if you have any questions during the process (such as +whether the move is necessary), and ensure that a technical writer reviews this +change prior to merging. -If you indeed need to change a document's location, do NOT remove the old -document, but rather replace all of its contents with a new line: +If you indeed need to change a document's location, do not remove the old +document, but rather replace all of its content with a new line: ```md This document was moved to [another location](path/to/new_doc.md). @@ -175,6 +184,7 @@ Things to note: - Since we also use inline documentation, except for the documentation itself, the document might also be referenced in the views of GitLab (`app/`) which will render when visiting `/help`, and sometimes in the testing suite (`spec/`). + You must search these paths for references to the doc and update them as well. - The above `git grep` command will search recursively in the directory you run it in for `workflow/lfs/lfs_administration` and `lfs/lfs_administration` and will print the file and the line where this file is mentioned. @@ -202,7 +212,7 @@ This redirection method will not provide a redirect fallback on GitLab `/help`. it, make sure to add a link to the new page on the doc, otherwise it's a dead end for users that land on the doc via `/help`. -### Redirections for pages with Disqus comments +#### Redirections for pages with Disqus comments If the documentation page being relocated already has any Disqus comments, we need to preserve the Disqus thread. @@ -240,20 +250,26 @@ choices: | Ending in `-docs` | `123-update-api-issues-docs` | If your branch name matches any of the above, it will run only the docs -tests. If it doesn't, the whole test suite will run (including docs). +tests. If it does not, the whole application test suite will run (including docs tests). ## Merge requests for GitLab documentation Before getting started, make sure you read the introductory section "[contributing to docs](#contributing-to-docs)" above and the -[tech writing workflow](https://about.gitlab.com/handbook/product/technical-writing/workflow/) -for GitLab Team members. +[documentation workflow](workflow.md). - Use the current [merge request description template](https://gitlab.com/gitlab-org/gitlab-ce/blob/master/.gitlab/merge_request_templates/Documentation.md) - Use the correct [branch name](#branch-naming) - Label the MR `Documentation` - Assign the correct milestone (see note below) +Documentation will be merged if it is an improvement on existing content, +represents a good-faith effort to follow the template and style standards, +and is believed to be accurate. + +Further needs for what would make the doc even better should be immediately addressed +in a follow-up MR or issue. + NOTE: **Note:** If the release version you want to add the documentation to has already been frozen or released, use the label `Pick into X.Y` to get it merged into @@ -380,80 +396,14 @@ to merge changes that will break `master` from a merge request with a successful ## Docs site architecture -Read through [docs architecture](site_architecture/index.md) to learn -how we architecture, build, and deploy the docs site, <https://docs.gitlab.com>, and -to check all the assets and libraries available. +See the [Docs site architecture](site_architecture/index.md) page to learn +how we build and deploy the site at [docs.gitlab.com](https://docs.gitlab.com), and +to review all the assets and libraries in use. ### Global navigation -Read through the [global navigation](site_architecture/global_nav.md) doc. - -## General Documentation vs Technical Articles - -### General documentation - -General documentation is categorized by _User_, _Admin_, and _Contributor_, and describe what that feature is, what it does, and its available settings. - -### Technical Articles - -Technical articles replace technical content that once lived in the [GitLab Blog](https://about.gitlab.com/blog/), where they got out-of-date and weren't easily found. - -They are topic-related documentation, written with an user-friendly approach and language, aiming to provide the community with guidance on specific processes to achieve certain objectives. - -A technical article guides users and/or admins to achieve certain objectives (within guides and tutorials), or provide an overview of that particular topic or feature (within technical overviews). It can also describe the use, implementation, or integration of third-party tools with GitLab. - -They should be placed in a new directory named `/article-title/index.md` under a topic-related folder, and their images should be placed in `/article-title/img/`. For example, a new article on GitLab Pages should be placed in `doc/user/project/pages/article-title/` and a new article on GitLab CI/CD should be placed in `doc/ci/examples/article-title/`. - -#### Types of Technical Articles - -- **User guides**: technical content to guide regular users from point A to point B -- **Admin guides**: technical content to guide administrators of GitLab instances from point A to point B -- **Technical Overviews**: technical content describing features, solutions, and third-party integrations -- **Tutorials**: technical content provided step-by-step on how to do things, or how to reach specific objectives - -#### Understanding guides, tutorials, and technical overviews - -Suppose there's a process to go from point A to point B in 5 steps: `(A) 1 > 2 > 3 > 4 > 5 (B)`. - -A **guide** can be understood as a description of certain processes to achieve a particular objective. A guide brings you from A to B describing the characteristics of that process, but not necessarily going over each step. It can mention, for example, steps 2 and 3, but does not necessarily explain how to accomplish them. - -- Live example: "[Static sites and GitLab Pages domains (Part 1)](../../user/project/pages/getting_started_part_one.md) to [Creating and Tweaking GitLab CI/CD for GitLab Pages (Part 4)](../../user/project/pages/getting_started_part_four.md)" - -A **tutorial** requires a clear **step-by-step** guidance to achieve a singular objective. It brings you from A to B, describing precisely all the necessary steps involved in that process, showing each of the 5 steps to go from A to B. -It does not only describes steps 2 and 3, but also shows you how to accomplish them. - -- Live example (on the blog): [Hosting on GitLab.com with GitLab Pages](https://about.gitlab.com/2016/04/07/gitlab-pages-setup/) - -A **technical overview** is a description of what a certain feature is, and what it does, but does not walk -through the process of how to use it systematically. - -- Live example (on the blog): [GitLab Workflow, an overview](https://about.gitlab.com/2016/10/25/gitlab-workflow-an-overview/) - -#### Special format - -Every **Technical Article** contains a frontmatter at the beginning of the doc -with the following information: - -- **Type of article** (user guide, admin guide, technical overview, tutorial) -- **Knowledge level** expected from the reader to be able to follow through (beginner, intermediate, advanced) -- **Author's name** and **GitLab.com handle** -- **Publication date** (ISO format YYYY-MM-DD) - -For example: - -```yaml ---- -author: John Doe -author_gitlab: johnDoe -level: beginner -article_type: user guide -date: 2017-02-01 ---- -``` - -#### Technical Articles - Writing Method - -Use the [writing method](https://about.gitlab.com/handbook/product/technical-writing/#writing-method) defined by the Technical Writing team. +See the [Global navigation](site_architecture/global_nav.md) doc for information +on how the left-side navigation menu is built and updated. ## Previewing the changes live @@ -468,13 +418,13 @@ The live preview is currently enabled for the following projects: - <https://gitlab.com/gitlab-org/gitlab-runner> If your branch contains only documentation changes, you can use -[special branch names](#branch-naming) to avoid long running pipelines. +[special branch names](#branch-naming) to avoid long-running pipelines. For [docs-only changes](#branch-naming), the review app is run automatically. For all other branches, you can use the manual `review-docs-deploy-manual` job in your merge request. You will need at least Maintainer permissions to be able -to run it. In the mini pipeline graph, you should see an `>>` icon. Clicking on it will -reveal the `review-docs-deploy-manual` job. Hit the play button for the job to start. +to run it. In the mini pipeline graph, you should see a `>>` icon. Clicking it will +reveal the `review-docs-deploy-manual` job. Click the play button to start the job. ![Manual trigger a docs build](img/manual_build_docs.png) @@ -630,9 +580,10 @@ A file with `proselint` configuration must be placed in a #### `markdownlint` `markdownlint` checks that certain rules ([example](https://github.com/DavidAnson/markdownlint/blob/master/README.md#rules--aliases)) - are followed for Markdown syntax. Our [style guidelines](styleguide.md) elaborate on which choices - must be made when selecting Markdown syntax for GitLab documentation and this tool helps - catch deviations from those guidelines. + are followed for Markdown syntax. + Our [Documentation Style Guide](styleguide.md) and [Markdown Guide](https://about.gitlab.com/handbook/product/technical-writing/markdown-guide/) + elaborate on which choices must be made when selecting Markdown syntax for + GitLab documentation. This tool helps catch deviations from those guidelines. `markdownlint` can be used [on the command line](https://github.com/igorshubovych/markdownlint-cli#markdownlint-cli--), either on a single Markdown file or on all Markdown files in a project. For example, to run @@ -655,7 +606,7 @@ markdownlint **/*.md The following sample `markdownlint` configuration modifies the available default rules to: -- Adhere to the [style guidelines](styleguide.md). +- Adhere to the [Documentation Style Guide](styleguide.md). - Apply conventions found in the GitLab documentation. - Allow the flexibility of using some inline HTML. @@ -694,9 +645,10 @@ For [`markdownlint`](https://github.com/DavidAnson/markdownlint/), this configur placed in a [valid location](https://github.com/igorshubovych/markdownlint-cli#configuration). For example, `~/.markdownlintrc`. -## Danger bot +## Danger Bot -GitLab uses [danger bot](https://github.com/danger/danger) for some elements in -code review. For docs changes in merge requests, whenever a change under `/doc` -is made, the bot leaves a comment for the author to mention `@gl-docsteam`, so -that the docs can be properly reviewed. +GitLab uses [Danger](https://github.com/danger/danger) for some elements in +code review. For docs changes in merge requests, whenever a change to files under `/doc` +is made, Danger Bot leaves a comment with further instructions about the documentation +process. This is configured in the Dangerfile in the GitLab CE and EE repo under +[/danger/documentation/](https://gitlab.com/gitlab-org/gitlab-ce/tree/master/danger/documentation). diff --git a/doc/development/documentation/site_architecture/index.md b/doc/development/documentation/site_architecture/index.md index 956bf90a5d9..9d4d2d3a28b 100644 --- a/doc/development/documentation/site_architecture/index.md +++ b/doc/development/documentation/site_architecture/index.md @@ -2,11 +2,18 @@ description: "Learn how GitLab's documentation website is architectured." --- -# Docs site architecture +# Documentation site architecture Learn how we build and architecture [`gitlab-docs`](https://gitlab.com/gitlab-com/gitlab-docs) and deploy it to <https://docs.gitlab.com>. +## Repository + +While the source of the documentation content is stored in GitLab's respective product +repositories, the source that is used to build the documentation site _from that content_ +is located at https://gitlab.com/gitlab-com/gitlab-docs. See the README there for +detailed information. + ## Assets To provide an optimized site structure, design, and a search-engine friendly diff --git a/doc/development/documentation/structure.md b/doc/development/documentation/structure.md index 607ad21d459..ee3bd5606a5 100644 --- a/doc/development/documentation/structure.md +++ b/doc/development/documentation/structure.md @@ -1,147 +1,121 @@ --- -description: Learn the how to correctly structure GitLab documentation. +description: What to include in GitLab documentation pages. --- -# Documentation structure +# Documentation structure and template -For consistency throughout the documentation, it's important to maintain the same -structure among the docs. +This document will help you determine how to structure a page within GitLab's +documentation and what content to include. These standards help ensure consistency +and completeness throughout the documentation, and they make it easier to contribute. -Before getting started, read through the following docs: +Before getting started, familiarize yourself with [GitLab's Documentation guidelines](index.md) +and the section on Content in the [Style Guide](styleguide.md). -- [Contributing to GitLab documentation](index.md#contributing-to-docs) -- [Merge requests for GitLab documentation](index.md#merge-requests-for-gitlab-documentation) -- [Branch naming for docs-only changes](index.md#branch-naming) -- [Documentation directory structure](index.md#documentation-directory-structure) -- [Documentation style guidelines](styleguide.md) -- [Documentation workflow](workflow.md) +## Components of a documentation page -## Documentation blurb +Most pages will be dedicated to a specifig GitLab feature or to a use case that involves +one or more features, potentially in conjunction with third-party tools. -Every document should include the following content in the following sequence: +Every feature or use case document should include the following content in the following sequence, +with exceptions and details noted below and in the template included on this page. -- **Feature name**: defines an intuitive name for the feature that clearly - states what it is and is consistent with any relevant UI text. -- **Feature overview** and description: describe what it is, what it does, and in what context it should be used. -- **Use cases**: describes real use case scenarios for that feature. -- **Requirements**: describes what software and/or configuration is required to be able to - use the feature and, if applicable, prerequisite knowledge for being able to follow/implement the tutorial. - For example, familiarity with GitLab CI/CD, an account on a third-party service, dependencies installed, etc. - Link each one to its most relevant resource; i.e., where the reader can go to begin to fullfil that requirement. - (Another doc page, a third party application's site, etc.) -- **Instructions**: clearly describes the steps to use the feature, leaving no gaps. -- **Troubleshooting** guide (recommended but not required): if you know beforehand what issues - one might have when setting it up, or when something is changed, or on upgrading, it's - important to describe those too. Think of things that may go wrong and include them in the - docs. This is important to minimize requests for support, and to avoid doc comments with - questions that you know someone might ask. Answering them beforehand only makes your - document better and more approachable. +- **Title**: Top-level heading with the feature name, or a use case name, which would start with +a verb, like Configuring, Enabling, etc. +- **Introduction**: A couple sentences about the subject matter and what's to be found on this page. +- **Overview** Describe what it is, what it does, and in what context it should be used. +- **Use cases**: describes real use case scenarios for that feature/configuration. +- **Requirements**: describes what software, configuration, account, or knowledge is required. +- **Instructions**: One or more sets of detailed instructions to follow. +- **Troubleshooting** guide (recommended but not required). -For additional details, see the subsections below, as well as the [Documentation template for new docs](#Documentation-template-for-new-docs). +For additional details on each, see the [template for new docs](#template-for-new-docs), +below. -### Feature overview and use cases +Note that you can include additional subsections, as appropriate, such as 'How it Works', 'Architecture', +and other logicial divisions such as pre- and post-deployment steps. -Every major feature (regardless if present in GitLab Community or Enterprise editions) -should present, at the beginning of the document, two main sections: **overview** and -**use cases**. Every GitLab EE-only feature should also contain these sections. - -**Overview**: as the name suggests, the goal here is to provide an overview of the feature. -Describe what is it, what it does, why it is important/cool/nice-to-have, -what problem it solves, and what you can do with this feature that you couldn't -do before. - -**Use cases**: provide at least two, ideally three, use cases for every major feature. -You should answer this question: what can you do with this feature/change? Use cases -are examples of how this feature or change can be used in real life. - -Examples: - -- CE and EE: [Issues](../../user/project/issues/index.md#use-cases) -- CE and EE: [Merge Requests](../../user/project/merge_requests/index.md) -- EE-only: [Geo](https://docs.gitlab.com/ee/administration/geo/replication/index.html) -- EE-only: [Jenkins integration](https://docs.gitlab.com/ee/integration/jenkins.html) - -Note that if you don't have anything to add between the doc title (`<h1>`) and -the header `## Overview`, you can omit the header, but keep the content of the -overview there. - -> **Overview** and **use cases** are required to **every** Enterprise Edition feature, -and for every **major** feature present in Community Edition. - -### Discoverability - -Your new document will be discoverable by the user only if: - -- Crosslinked from the higher-level index (e.g., Issue Boards docs - should be linked from Issues; Prometheus docs should be linked from - Monitoring; CI/CD tutorials should be linked from CI/CD examples). - - When referencing other GitLab products and features, link to their - respective docs; when referencing third-party products or technologies, - link out to their external sites, documentation, and resources. -- The headings are clear. E.g., "App testing" is a bad heading, "Testing - an application with GitLab CI/CD" is much better. Think of something - someone will search for and use these keywords in the headings. - -## Documentation template for new docs +## Template for new docs To start a new document, respect the file tree and file name guidelines, as well as the style guidelines. Use the following template: ```md +<!--Follow the Style Guide when working on this document. https://docs.gitlab.com/ee/development/documentation/styleguide.html +When done, remove all of this commented-out text, except a commented-out Troubleshooting section, +which, if empty, can be left in place to encourage future use.--> --- -description: "short document description." # Up to ~200 chars long. They will be displayed in Google Search Snippets. +description: "Short document description." # Up to ~200 chars long. They will be displayed in Google Search snippets. It may help to write the page intro first, and then reuse it here. --- -# Feature Name **[TIER]** (1) +# Feature Name or Use Case Name **[TIER]** (1) +<!--If writing about a use case, drop the tier, and start with a verb, e.g. 'Configuring', 'Implementing', + the goal/scenario--> -> [Introduced](link_to_issue_or_mr) in GitLab Tier X.Y (2). +<!--For pages on newly introduced features, add the following line. If only some aspects of the feature have been introduced, specify what parts of the feature.--> +> [Introduced](link_to_issue_or_mr) in GitLab (Tier) X.Y (2). -A short description for the feature (can be the same used in the frontmatter's -`description`). +An introduction -- without its own additional header -- goes here. +Offer a very short description of the feature or use case, and what to expect on this page. +(You can reuse this content, or part of it, for the front matter's `description` at the top of this file). ## Overview -To write the feature overview, you should consider answering the following questions: +The feature overview should answer the following questions: -- What is it? +- What is this feature or use case? - Who is it for? - What is the context in which it is used and are there any prerequisites/requirements? -- What can the user do with it? (Be sure to consider multiple audiences, like GitLab admin and developer-user.) -- What are the benefits to using it over any alternatives? +- What can the audience do with this? (Be sure to consider all applicable audiences, like GitLab admin and developer-user.) +- What are the benefits to using this over any alternatives? ## Use cases -Describe one to three use cases for that feature. Give real-life examples. +Describe some use cases, typically in bulleted form. Include real-life examples for each. + +If the page itself is dedicated to a use case, this section can usually include more specific scenarios +for use (e.g. variations on the main use case), but if that's not applicable, the section can be omitted. + +Examples of use cases on feature pages: +- CE and EE: [Issues](../../user/project/issues/index.md#use-cases) +- CE and EE: [Merge Requests](../../user/project/merge_requests/index.md) +- EE-only: [Geo](https://docs.gitlab.com/ee/administration/geo/replication/index.html) +- EE-only: [Jenkins integration](https://docs.gitlab.com/ee/integration/jenkins.html) ## Requirements -State any requirements, if any, for using the feature and/or following along with the tutorial. +State any requirements for using the feature and/or following along with the instructions. -The only assumption that is redundant and doesn't need to be mentioned is having an account -on GitLab. +These can include both: +- technical requirements (e.g. an account on a third party service, an amount of storage space, prior configuration of another feature) +- prerequisite knowledge (e.g. familiarity with certain GitLab features, cloud technologies) + +Link each one to an appropriate place for more information. ## Instructions -("Instructions" is not necessarily the name of the heading) +"Instructions" is usually not the name of the heading. +This is the part of the document where you can include one or more sets of instructions, each to accomplish a specific task. +Headers should describe the task the reader will achieve by following the instructions within, typically starting with a verb. +Larger instruction sets may have subsections covering specific phases of the process. - Write a step-by-step guide, with no gaps between the steps. - Start with an h2 (`##`), break complex steps into small steps using -subheadings h3 > h4 > h5 > h6. _Never skip the hierarchy level, such +subheadings h3 > h4 > h5 > h6. _Never skip a hierarchy level, such as h2 > h4_, as it will break the TOC and may affect the breadcrumbs. - Use short and descriptive headings (up to ~50 chars). You can use one -single heading `## How it works` for the instructions when the feature +single heading like `## Configuring X` for instructions when the feature is simple and the document is short. -- Be clear, concise, and stick to the goal of the doc: explain how to -use that feature. -- Use inclusive language and avoid jargons, as well as uncommon and -fancy words. The docs should be clear and easy to understand. -- Write in the 3rd person (use "we", "you", "us", "one", instead of "I" or "me"). -- Always provide internal and external reference links. -- Always link the doc from its higher-level index. <!-- ## Troubleshooting -Add a troubleshooting guide when possible/applicable. --> +Include any troubleshooting steps that you can foresee. If you know beforehand what issues +one might have when setting this up, or when something is changed, or on upgrading, it's +important to describe those, too. Think of things that may go wrong and include them here. +This is important to minimize requests for support, and to avoid doc comments with +questions that you know someone might ask. + +Each scenario can be a third-level heading, e.g. `### Getting error message X`. +If you have none to add when creating a doc, leave this section in place +but commented out to help encourage others to add to it in the future. --> --- diff --git a/doc/development/documentation/styleguide.md b/doc/development/documentation/styleguide.md index 8309ba9a72c..8760cd194d4 100644 --- a/doc/development/documentation/styleguide.md +++ b/doc/development/documentation/styleguide.md @@ -1,16 +1,16 @@ --- -description: 'Writing styles, markup, formatting, and reusing regular expressions throughout the GitLab Documentation.' +description: 'Writing styles, markup, formatting, and other standards for GitLab Documentation.' --- -# Documentation style guidelines +# Documentation Style Guide The documentation style guide defines the markup structure used in GitLab documentation. Check the [documentation guidelines](index.md) for general development instructions. -Check the GitLab handbook for the [writing styles guidelines](https://about.gitlab.com/handbook/communication/#writing-style-guidelines). +See the GitLab handbook for the [writing style guidelines](https://about.gitlab.com/handbook/communication/#writing-style-guidelines). -For help adhering to the guidelines, see [linting](index.md#linting). +For programmatic help adhering to the guidelines, see [linting](index.md#linting). ## Files @@ -40,13 +40,36 @@ Use Kramdown markup wisely: do not overuse its specific markup (e.g., `{:.class} ## Content -- Make sure that the documentation is added in the correct - [directory](index.md#documentation-directory-structure), linked from its - higher-level index, and linked from other related pages. +These guidelines help toward the goal of having every user's search of documentation +yield a useful result, and ensuring content is helpful and easy to consume. + +- What to include: + - Any and all helpful information, processes, and tips for implementing, +using, and troubleshooting GitLab features. [The documentation is the single source of truth](https://about.gitlab.com/handbook/documentation/#documentation-as-single-source-of-truth-ssot) +for this information. + - 'Risky' or niche problem-solving steps. There is no reason to withhold these or +store them elsewhere; simply include them along with the rest of the docs including all necessary +detail, such as specific warnings and caveats about potential ramifications. + - Any content types/sources, if relevant to users or admins. You can freely +include presentations, videos, etc.; no matter who it was originally written for, +if it is helpful to any of our audiences, we can include it. If an outside source +that's under copyright, rephrase, or summarize and link out; do not copy and paste. + - All applicable subsections as described on the [structure and template](structure.md) page, +with files organized in the [correct directory](index.md#documentation-directory-structure). +- To ensure discoverability, link to each doc from its higher-level index page and other related pages. +- When referencing other GitLab products and features, link to their + respective docs; when referencing third-party products or technologies, + link out to their external sites, documentation, and resources. - Do not duplicate information. -- Be brief and clear. -- Unless there's a logical reason not to, structure the document in alphabetical order -(headings, tables, and lists). +- Structure content in alphabetical order in tables, lists, etc., unless there is +a logical reason not to (for example, when mirroring the UI or an ordered sequence). + +## Language + +- Use inclusive language and avoid jargon, as well as uncommon +words. The docs should be clear and easy to understand. +- Write in the 3rd person (use "we", "you", "us", "one", instead of "I" or "me"). +- Be clear, concise, and stick to the goal of the doc. - Write in US English. - Capitalize "G" and "L" in GitLab. - Use title case when referring to [features](https://about.gitlab.com/features/) or diff --git a/doc/development/documentation/workflow.md b/doc/development/documentation/workflow.md index 75ce8640e87..7c32c92b147 100644 --- a/doc/development/documentation/workflow.md +++ b/doc/development/documentation/workflow.md @@ -1,185 +1,10 @@ --- -description: Learn the process of shipping documentation for GitLab. +description: Learn the processes for contributing to GitLab's documentation. --- -# Documentation process at GitLab +# Documentation workflows at GitLab -At GitLab, developers contribute new or updated documentation along with their code, but product managers and technical writers also have essential roles in the process. +Documentation workflows at GitLab differ depending on the reason for the change. The two types of documentation changes are: -- Product Managers (PMs): in the issue for all new and updated features, - PMs include specific documentation requirements that the developer who is - writing or updating the docs must meet, along with feature descriptions - and use cases. They call out any specific areas where collaborating with - a technical writer is recommended, and usually act as the first reviewer - of the docs. -- Developers: author documentation and merge it on time (up to a week after - the feature freeze). -- Technical Writers: review each issue to ensure PM's requirements are complete, - help developers with any questions throughout the process, and act as the final - reviewer of all new and updated docs content before it's merged. - -## Requirements - -Documentation must be delivered whenever: - -- A new feature is shipped -- There are changes to the UI -- A process, workflow, or previously documented feature is changed - -Documentation is not required when a feature is changed on the backend -only and does not directly affect the way that any regular user or -administrator would interact with GitLab. - -NOTE: **Note:** -When refactoring documentation, it should be submitted in its own MR. -**Do not** join new features' MRs with refactoring existing docs, as they might have -different priorities. - -NOTE: **Note:** -[Smaller MRs are better](https://gitlab.com/gitlab-com/blog-posts/issues/185#note_4401010)! Do not mix subjects, and ship the smallest MR possible. - -### Documentation review process - -The docs shipped by the developer should be reviewed by the PM (for accuracy) and a Technical Writer (for clarity and structure). - -#### Documentation updates that require Technical Writer review - -Every documentation change that meets the criteria below must be reviewed by a Technical Writer -to ensure clarity and discoverability, and avoid redundancy, bad file locations, typos, broken links, etc. -Within the GitLab issue or MR, ping the relevant technical writer for the subject area. If you're not sure who that is, -ping any of them or all of them (`@gl\-docsteam`). - -A Technical Writer must review documentation updates that involve: - -- Docs introducing new features -- Changing documentation location -- Refactoring existing documentation -- Creating new documentation files - -If you need any help to choose the correct place for a doc, discuss a documentation -idea or outline, or request any other help, ping a Technical Writer on your issue, MR, -or on Slack in `#docs`. - -#### Skip the PM's review - -When there's a non-significant change to the docs, you can skip the review -of the PM. Add the same labels as you would for a regular doc change and -assign the correct milestone. In these cases, assign a Technical Writer -for approval/merge, or mention `@gl\-docsteam` in case you don't know -which Tech Writer to assign for. - -#### Skip the entire review - -When the MR only contains corrections to the content (typos, grammar, -broken links, etc), it can be merged without the PM's and Tech Writer's review. - -## Documentation structure - -Read through the [documentation structure](structure.md) docs for an overview. - -## Documentation workflow - -To follow a consistent workflow every month, documentation changes -involve the Product Managers, the developer who shipped the feature, -and the Technical Writing team. Each role is described below. - -### 1. Product Manager's role in the documentation process - -The Product Manager (PM) should add to the feature issue: - -- Feature name, overview/description, and use cases, for the [documentation blurb](structure.md#documentation-blurb) -- The documentation requirements for the developer working on the docs - - What new page, new subsection of an existing page, or other update to an existing page/subsection is needed. - - Just one page/section/update or multiple (perhaps there's an end user and admin change needing docs, or we need to update a previously recommended workflow, or we want to link the new feature from various places; consider and mention all ways documentation should be affected - - Suggested title of any page or subsection, if applicable -- Label the issue with `Documentation`, `Deliverable`, `docs:P1`, and assign - the correct milestone - -### 2. Developer's role in the documentation process - -As a developer, or as a community contributor, you should ship the documentation -with the feature, as in GitLab the documentation is part of the product. - -The docs can either be shipped along with the MR introducing the code, or, -alternatively, created from a follow-up issue and MR. - -The docs should be shipped **by the feature freeze date**. Justified -exceptions are accepted, as long as the [following process](#documentation-shipped-late) -and the missed-deliverable due date (the 14th of each month) are both respected. - -#### Documentation shipped in the feature MR - -The developer should add to the feature MR the documentation containing: - -- The [documentation blurb](structure.md#documentation-blurb): copy the - feature name, overview/description, and use cases from the feature issue -- Instructions: write how to use the feature, step by step, with no gaps. -- [Crosslink for discoverability](structure.md#discoverability): link with - internal docs and external resources (if applicable) -- Index: link the new doc or the new heading from the higher-level index - for [discoverability](#discoverability) -- [Screenshots](styleguide.md#images): when necessary, add screenshots for: - - Illustrating a step of the process - - Indicating the location of a navigation menu -- Label the MR with `Documentation`, `Deliverable`, `docs-P1`, and assign - the correct milestone -- Assign the PM for review -- When done, mention the `@gl\-docsteam` in the MR asking for review -- **Due date**: feature freeze date and time - -#### Documentation shipped in a follow-up MR - -If the docs aren't being shipped within the feature MR: - -- Create a new issue mentioning "docs" or "documentation" in the title (use the Documentation issue description template) -- Label the issue with: `Documentation`, `Deliverable`, `docs-P1`, `<product-label>` - (product label == CI/CD, Pages, Prometheus, etc) -- Add the correct milestone -- Create a new MR for shipping the docs changes and follow the same - process [described above](#documentation-shipped-in-the-feature-mr) -- Use the MR description template called "Documentation" -- Add the same labels and milestone as you did for the issue -- Assign the PM for review -- When done, mention the `@gl\-docsteam` in the MR asking for review -- **Due date**: feature freeze date and time - -#### Documentation shipped late - -Shipping late means that you are affecting the whole feature workflow -as well as other teams' priorities (PMs, tech writers, release managers, -release post reviewers), so every effort should be made to avoid this. - -If you did not ship the docs within the feature freeze, proceed as -[described above](#documentation-shipped-in-a-follow-up-mr) and, -besides the regular labels, include the labels `Pick into X.Y` and -`missed-deliverable` in the issue and the MR, and assign them the correct -milestone. - -The **due date** for **merging** `missed-deliverable` MRs is on the -**14th** of each month. - -### 3. Technical Writer's role in the documentation process - -- **Planning** - - Once an issue contains a Documentation label and the current milestone, a - technical writer reviews the Product Manager's documentation requirements. - - Once the documentation requirements are approved, the technical writer can - work with the developer to discuss any documentation questions and plans/outlines, as needed. - -- **Review** - A technical writer must review the documentation for: - - Clarity - - Relevance (make sure the content is appropriate given the impact of the feature) - - Location (make sure the doc is in the correct dir and has the correct name) - - Syntax, typos, and broken links - - Improvements to the content - - Accordance to the [docs style guide](styleguide.md) - -<!-- TBA: issue and MR description templates as part of the process --> - -<!-- -## New features vs feature updates - -- TBA: - - Describe the difference between new features and feature updates - - Creating a new doc vs updating an existing doc ---> +- [Feature-change documentation workflow](feature-change-workflow.md) - The documentation is being created or updated as part of the development and release of a new or enhanced feature. This process involves the developer of the feature (who includes new/updated documentation files as part of the same merge request containing the feature's code) and also involves the product manager and technical writer who are listed for the feature's [DevOps stage](https://about.gitlab.com/handbook/product/categories/#devops-stages). +- [Documentation improvement workflow](improvement-workflow.md) - All documentation additions not associated with a feature release. Documentation is being created or updated to improve accuracy, completeness, ease of use, or any reason other than a feature change. Anyone (and everyone) can contribute a merge request for this type of change at any time. diff --git a/doc/development/i18n/proofreader.md b/doc/development/i18n/proofreader.md index 8f1317e235d..ac910e80a89 100644 --- a/doc/development/i18n/proofreader.md +++ b/doc/development/i18n/proofreader.md @@ -12,7 +12,7 @@ are very appreciative of the work done by translators and proofreaders! - Bulgarian - Lyubomir Vasilev - [Crowdin](https://crowdin.com/profile/lyubomirv) - Catalan - - Proofreaders needed. + - David Planella - [GitLab](https://gitlab.com/dplanella), [Crowdin](https://crowdin.com/profile/dplanella) - Chinese Simplified - Huang Tao - [GitLab](https://gitlab.com/htve), [Crowdin](https://crowdin.com/profile/htve) - Chinese Traditional diff --git a/doc/development/instrumentation.md b/doc/development/instrumentation.md index bef166f2aec..a36dc6424a7 100644 --- a/doc/development/instrumentation.md +++ b/doc/development/instrumentation.md @@ -35,7 +35,7 @@ Using this method is in general preferred over directly calling the various instrumentation methods. Method instrumentation should be added in the initializer -`config/initializers/8_metrics.rb`. +`config/initializers/zz_metrics.rb`. ### Examples diff --git a/doc/development/new_fe_guide/style/prettier.md b/doc/development/new_fe_guide/style/prettier.md index baaea67d38b..4495f38f262 100644 --- a/doc/development/new_fe_guide/style/prettier.md +++ b/doc/development/new_fe_guide/style/prettier.md @@ -57,3 +57,38 @@ node ./scripts/frontend/prettier.js save-all ./vendor/ ``` This will go over all files in a specific folder and save it. + +## VSCode Settings + +### Format on Save + +To automatically format your files with Prettier, add the following properties to your User or Workspace Settings: + +```javascript +{ + "[javascript]": { + "editor.formatOnSave": true + }, + "[vue]": { + "editor.formatOnSave": true + }, +} +``` + +### Conflicts with Vetur Extension + +There are some [runtime issues](https://github.com/vuejs/vetur/issues/950) with [Prettier](https://marketplace.visualstudio.com/items?itemName=esbenp.prettier-vscode) and [the Vetur extension](https://marketplace.visualstudio.com/items?itemName=octref.vetur) for VSCode. To fix this, try adding the following properties to your User or Workspace Settings: + +```javascript +{ + "prettier.disableLanguages": [], + "vetur.format.defaultFormatter.html": "none", + "vetur.format.defaultFormatter.js": "none", + "vetur.format.defaultFormatter.css": "none", + "vetur.format.defaultFormatter.less": "none", + "vetur.format.defaultFormatter.postcss": "none", + "vetur.format.defaultFormatter.scss": "none", + "vetur.format.defaultFormatter.stylus": "none", + "vetur.format.defaultFormatter.ts": "none", +} +``` diff --git a/doc/development/switching_to_rails5.md b/doc/development/switching_to_rails5.md deleted file mode 100644 index c9a4ce1a1d1..00000000000 --- a/doc/development/switching_to_rails5.md +++ /dev/null @@ -1,27 +0,0 @@ -# Switching to Rails 5 - -GitLab switched recently to Rails 5. This is a big change (especially for backend development) and it introduces couple of temporary inconveniences. - -## After the switch, I found a broken feature. What do I do? - -Many fixes and tweaks were done to make our codebase compatible with Rails 5, but it's possible that not all issues were found. If you find an bug, please create an issue and assign it the ~rails5 label. - -## It takes much longer to run CI pipelines that build GitLab. Why? - -We are temporarily running CI pipelines with Rails 4 and 5 so that we ensure we remain compatible with Rails 4 in case we must revert back to Rails 4 from Rails 5 (this can double the duration of CI pipelines). - -We might revert back to Rails 4 if we found a major issue we were unable to quickly fix. - -Once we are sure we can stay with Rails 5, we will stop running CI pipelines with Rails 4. - -## Can I skip running Rails 4 tests? - -If you are sure that your merge request doesn't introduce any incompatibility, you can just include `norails4` anywhere in your branch name and Rails 4 tests will be skipped. - -## CI is failing on my test with Rails 4. How can I debug it? - -You can run specs locally with Rails 4 using the following command: - -```sh -BUNDLE_GEMFILE=Gemfile.rails4 RAILS5=0 bundle exec rspec ... -``` diff --git a/doc/img/devops-stages.png b/doc/img/devops-stages.png Binary files differindex a971e81a419..424bce95607 100644 --- a/doc/img/devops-stages.png +++ b/doc/img/devops-stages.png diff --git a/doc/install/aws/img/associate_subnet_gateway_2.png b/doc/install/aws/img/associate_subnet_gateway_2.png Binary files differindex 76e101d32a3..6e10d9647b1 100644 --- a/doc/install/aws/img/associate_subnet_gateway_2.png +++ b/doc/install/aws/img/associate_subnet_gateway_2.png diff --git a/doc/install/aws/img/choose_ami.png b/doc/install/aws/img/choose_ami.png Binary files differindex 034ac92691d..a07d42dd6fb 100644 --- a/doc/install/aws/img/choose_ami.png +++ b/doc/install/aws/img/choose_ami.png diff --git a/doc/install/aws/img/ec_az.png b/doc/install/aws/img/ec_az.png Binary files differindex 22a8291c593..431dbb0251b 100644 --- a/doc/install/aws/img/ec_az.png +++ b/doc/install/aws/img/ec_az.png diff --git a/doc/install/aws/img/ec_subnet.png b/doc/install/aws/img/ec_subnet.png Binary files differindex c44fb4485e3..08a9b169267 100644 --- a/doc/install/aws/img/ec_subnet.png +++ b/doc/install/aws/img/ec_subnet.png diff --git a/doc/install/installation.md b/doc/install/installation.md index d041bfa863a..b7781dea384 100644 --- a/doc/install/installation.md +++ b/doc/install/installation.md @@ -142,7 +142,7 @@ Download Ruby and compile it: Then install the Bundler Gem: - sudo gem install bundler --no-ri --no-rdoc + sudo gem install bundler --no-document ## 3. Go diff --git a/doc/install/kubernetes/gitlab_chart.md b/doc/install/kubernetes/gitlab_chart.md index 74e2598f860..26ced45de7b 100644 --- a/doc/install/kubernetes/gitlab_chart.md +++ b/doc/install/kubernetes/gitlab_chart.md @@ -47,8 +47,9 @@ In order to deploy GitLab on Kubernetes, the following are required: 1. `helm` and `kubectl` [installed on your computer](preparation/tools_installation.md). 1. A Kubernetes cluster, version 1.8 or higher. 6vCPU and 16GB of RAM is recommended. - - [Google GKE](https://cloud.google.com/kubernetes-engine/docs/how-to/creating-a-container-cluster) - [Amazon EKS](https://docs.aws.amazon.com/eks/latest/userguide/getting-started.html) + - [Google GKE](https://cloud.google.com/kubernetes-engine/docs/how-to/creating-a-container-cluster) + - [IBM IKS](https://console.bluemix.net/docs/tutorials/scalable-webapp-kubernetes.html#create_kube_cluster) - [Microsoft AKS](https://docs.microsoft.com/en-us/azure/aks/kubernetes-walkthrough-portal) 1. A [wildcard DNS entry and external IP address](preparation/networking.md) 1. [Authenticate and connect](preparation/connect.md) to the cluster diff --git a/doc/install/openshift_and_gitlab/index.md b/doc/install/openshift_and_gitlab/index.md index 4c88b6f97fc..25a87dbd9de 100644 --- a/doc/install/openshift_and_gitlab/index.md +++ b/doc/install/openshift_and_gitlab/index.md @@ -18,7 +18,7 @@ In this tutorial, we will see how to deploy GitLab in OpenShift using GitLab's official Docker image while getting familiar with the web interface and CLI tools that will help us achieve our goal. -For a video demonstration on installing GitLab on Openshift, check the article [In 13 minutes from Kubernetes to a complete application development tool](https://about.gitlab.com/2016/11/14/idea-to-production/). +For a video demonstration on installing GitLab on OpenShift, check the article [In 13 minutes from Kubernetes to a complete application development tool](https://about.gitlab.com/2016/11/14/idea-to-production/). --- @@ -518,7 +518,7 @@ PaaS and managing your applications with the ease of containers. [templates]: https://docs.openshift.org/latest/architecture/core_concepts/templates.html "Documentation - OpenShift templates" [old-post]: https://blog.openshift.com/deploy-gitlab-openshift/ "Old post - Deploy GitLab on OpenShift" [line]: https://gitlab.com/gitlab-org/omnibus-gitlab/blob/658c065c8d022ce858dd63eaeeadb0b2ddc8deea/docker/openshift-template.json#L239 "GitLab - OpenShift template" -[oc-gh]: https://github.com/openshift/origin/releases/tag/v1.3.0 "Openshift 1.3.0 release on GitHub" +[oc-gh]: https://github.com/openshift/origin/releases/tag/v1.3.0 "OpenShift Origin 1.3.0 release on GitHub" [ha]: ../../administration/high_availability/gitlab.html "Documentation - GitLab High Availability" [replicas]: https://docs.openshift.org/latest/architecture/core_concepts/deployments.html#replication-controllers "Documentation - Replication controller" [autoscaling]: https://docs.openshift.org/latest/dev_guide/pod_autoscaling.html "Documentation - Autoscale" diff --git a/doc/integration/google.md b/doc/integration/google.md index b91d40d4bd4..d2b4e119978 100644 --- a/doc/integration/google.md +++ b/doc/integration/google.md @@ -35,7 +35,6 @@ In Google's side: 1. You should now be able to see a Client ID and Client secret. Note them down or keep this page open as you will need them later. -1. From the **Dashboard** select **ENABLE APIS AND SERVICES > Social > Google+ API > Enable** 1. To enable projects to access [Google Kubernetes Engine](../user/project/clusters/index.md), you must also enable these APIs: - Google Kubernetes Engine API diff --git a/doc/integration/img/github_app.png b/doc/integration/img/github_app.png Binary files differindex 4a1523d41ac..b72cf03dd4d 100644 --- a/doc/integration/img/github_app.png +++ b/doc/integration/img/github_app.png diff --git a/doc/integration/img/github_app_entry.png b/doc/integration/img/github_app_entry.png Binary files differindex 9e151f8cdff..0a1fe0ca65a 100644 --- a/doc/integration/img/github_app_entry.png +++ b/doc/integration/img/github_app_entry.png diff --git a/doc/integration/img/github_register_app.png b/doc/integration/img/github_register_app.png Binary files differindex edd3f660f4e..5786b822f53 100644 --- a/doc/integration/img/github_register_app.png +++ b/doc/integration/img/github_register_app.png diff --git a/doc/raketasks/backup_restore.md b/doc/raketasks/backup_restore.md index a63656fafef..57bc71d2903 100644 --- a/doc/raketasks/backup_restore.md +++ b/doc/raketasks/backup_restore.md @@ -657,6 +657,7 @@ Restoring database tables: - Loading fixture wikis...[SKIPPING] Restoring repositories: - Restoring repository abcd... [DONE] +- Object pool 1 ... Deleting tmp directories...[DONE] ``` diff --git a/doc/ssh/README.md b/doc/ssh/README.md index d9ae3c08172..e570627bfc1 100644 --- a/doc/ssh/README.md +++ b/doc/ssh/README.md @@ -185,7 +185,26 @@ your terminal (replacing `gitlab.com` with your GitLab's instance domain): ssh -T git@gitlab.com ``` -You should receive a _Welcome to GitLab, `@username`!_ message. +The first time you connect to GitLab via SSH, you will be asked to verify the +authenticity of the GitLab host you are connecting to. +For example, when connecting to GitLab.com, answer `yes` to add GitLab.com to +the list of trusted hosts: + +``` +The authenticity of host 'gitlab.com (35.231.145.151)' can't be established. +ECDSA key fingerprint is SHA256:HbW3g8zUjNSksFbqTiUWPWg2Bq1x8xdGUrliXFzSnUw. +Are you sure you want to continue connecting (yes/no)? yes +Warning: Permanently added 'gitlab.com' (ECDSA) to the list of known hosts. +``` + +NOTE: **Note:** +For GitLab.com, consult the +[SSH host keys fingerprints](../user/gitlab_com/index.md#ssh-host-keys-fingerprints), +to make sure you're connecting to the correct server. + +Once added to the list of known hosts, you won't be asked to validate the +authenticity of GitLab's host again. Run the above command once more, and +you should only receive a _Welcome to GitLab, `@username`!_ message. If the welcome message doesn't appear, run SSH's verbose mode by replacing `-T` with `-vvvT` to understand where the error is. diff --git a/doc/topics/authentication/index.md b/doc/topics/authentication/index.md index 766a23c419d..a354d3e7884 100644 --- a/doc/topics/authentication/index.md +++ b/doc/topics/authentication/index.md @@ -45,4 +45,4 @@ This page gathers all the resources for the topic **Authentication** within GitL - [Kanboard Plugin GitLab Authentication](https://github.com/kanboard/plugin-gitlab-auth) - [Jenkins GitLab OAuth Plugin](https://wiki.jenkins.io/display/JENKINS/GitLab+OAuth+Plugin) - [How to customize GitLab to support OpenID authentication](http://eric.van-der-vlist.com/blog/2013/11/23/how-to-customize-gitlab-to-support-openid-authentication/) -- [Openshift - Configuring Authentication and User Agent](https://docs.openshift.org/latest/install_config/configuring_authentication.html#GitLab) +- [OKD - Configuring Authentication and User Agent](https://docs.okd.io/latest/install_config/configuring_authentication.html#GitLab) diff --git a/doc/topics/autodevops/index.md b/doc/topics/autodevops/index.md index 7885cffd107..e937d372384 100644 --- a/doc/topics/autodevops/index.md +++ b/doc/topics/autodevops/index.md @@ -340,6 +340,9 @@ Any security warnings are also NOTE: **Note:** The Auto SAST stage will be skipped on licenses other than Ultimate. +NOTE: **Note:** +The Auto SAST job requires GitLab Runner 11.5 or above. + ### Auto Dependency Scanning **[ULTIMATE]** > Introduced in [GitLab Ultimate][ee] 10.7. @@ -356,6 +359,9 @@ Any security warnings are also NOTE: **Note:** The Auto Dependency Scanning stage will be skipped on licenses other than Ultimate. +NOTE: **Note:** +The Auto Dependency Scanning job requires GitLab Runner 11.5 or above. + ### Auto License Management **[ULTIMATE]** > Introduced in [GitLab Ultimate][ee] 11.0. @@ -567,7 +573,7 @@ To view the metrics, open the While Auto DevOps provides great defaults to get you started, you can customize almost everything to fit your needs; from custom [buildpacks](#custom-buildpacks), to [`Dockerfile`s](#custom-dockerfile), [Helm charts](#custom-helm-chart), or -even copying the complete [CI/CD configuration](#customizing-gitlab-ci-yml) +even copying the complete [CI/CD configuration](#customizing-gitlab-ciyml) into your project to enable staging and canary deployments, and more. ### Custom buildpacks diff --git a/doc/university/high-availability/aws/img/reference-arch2.png b/doc/university/high-availability/aws/img/reference-arch2.png Binary files differindex 9f50b2f5171..a9cb6663103 100644 --- a/doc/university/high-availability/aws/img/reference-arch2.png +++ b/doc/university/high-availability/aws/img/reference-arch2.png diff --git a/doc/update/10.0-to-10.1.md b/doc/update/10.0-to-10.1.md index af815d26a74..10cf02a984f 100644 --- a/doc/update/10.0-to-10.1.md +++ b/doc/update/10.0-to-10.1.md @@ -49,7 +49,7 @@ sudo make install Install Bundler: ```bash -sudo gem install bundler --no-ri --no-rdoc +sudo gem install bundler --no-document ``` ### 4. Update Node diff --git a/doc/update/10.1-to-10.2.md b/doc/update/10.1-to-10.2.md index 632e8befa74..20895a05567 100644 --- a/doc/update/10.1-to-10.2.md +++ b/doc/update/10.1-to-10.2.md @@ -49,7 +49,7 @@ sudo make install Install Bundler: ```bash -sudo gem install bundler --no-ri --no-rdoc +sudo gem install bundler --no-document ``` ### 4. Update Node diff --git a/doc/update/10.2-to-10.3.md b/doc/update/10.2-to-10.3.md index f8fe4a4b6bf..441a241d053 100644 --- a/doc/update/10.2-to-10.3.md +++ b/doc/update/10.2-to-10.3.md @@ -49,7 +49,7 @@ sudo make install Install Bundler: ```bash -sudo gem install bundler --no-ri --no-rdoc +sudo gem install bundler --no-document ``` ### 4. Update Node diff --git a/doc/update/10.3-to-10.4.md b/doc/update/10.3-to-10.4.md index 083f6090a8a..9f3efdd790e 100644 --- a/doc/update/10.3-to-10.4.md +++ b/doc/update/10.3-to-10.4.md @@ -51,7 +51,7 @@ sudo make install Install Bundler: ```bash -sudo gem install bundler --no-ri --no-rdoc +sudo gem install bundler --no-document ``` ### 4. Update Node diff --git a/doc/update/10.4-to-10.5.md b/doc/update/10.4-to-10.5.md index 313419ed13d..3766645a141 100644 --- a/doc/update/10.4-to-10.5.md +++ b/doc/update/10.4-to-10.5.md @@ -51,7 +51,7 @@ sudo make install Install Bundler: ```bash -sudo gem install bundler --no-ri --no-rdoc +sudo gem install bundler --no-document ``` ### 4. Update Node diff --git a/doc/update/10.5-to-10.6.md b/doc/update/10.5-to-10.6.md index 2f90fb62c4a..986ecbf5ef0 100644 --- a/doc/update/10.5-to-10.6.md +++ b/doc/update/10.5-to-10.6.md @@ -51,7 +51,7 @@ sudo make install Install Bundler: ```bash -sudo gem install bundler --no-ri --no-rdoc +sudo gem install bundler --no-document ``` ### 4. Update Node diff --git a/doc/update/10.6-to-10.7.md b/doc/update/10.6-to-10.7.md index b9c14395a3a..10d29837bfb 100644 --- a/doc/update/10.6-to-10.7.md +++ b/doc/update/10.6-to-10.7.md @@ -51,7 +51,7 @@ sudo make install Install Bundler: ```bash -sudo gem install bundler --no-ri --no-rdoc +sudo gem install bundler --no-document ``` ### 4. Update Node diff --git a/doc/update/10.7-to-10.8.md b/doc/update/10.7-to-10.8.md index 7bb628f9740..0cc46fc5aa9 100644 --- a/doc/update/10.7-to-10.8.md +++ b/doc/update/10.7-to-10.8.md @@ -52,7 +52,7 @@ sudo make install Install Bundler: ```bash -sudo gem install bundler --no-ri --no-rdoc +sudo gem install bundler --no-document ``` ### 4. Update Node diff --git a/doc/update/10.8-to-11.0.md b/doc/update/10.8-to-11.0.md index 22a0c9f950c..ad3305d8ebd 100644 --- a/doc/update/10.8-to-11.0.md +++ b/doc/update/10.8-to-11.0.md @@ -51,7 +51,7 @@ sudo make install Install Bundler: ```bash -sudo gem install bundler --no-ri --no-rdoc +sudo gem install bundler --no-document ``` ### 4. Update Node diff --git a/doc/update/11.0-to-11.1.md b/doc/update/11.0-to-11.1.md index 3f10a7edb8a..5b2dd48a744 100644 --- a/doc/update/11.0-to-11.1.md +++ b/doc/update/11.0-to-11.1.md @@ -51,7 +51,7 @@ sudo make install Install Bundler: ```bash -sudo gem install bundler --no-ri --no-rdoc +sudo gem install bundler --no-document ``` ### 4. Update Node diff --git a/doc/update/11.1-to-11.2.md b/doc/update/11.1-to-11.2.md index 3edc7e6923e..cb09d0a2505 100644 --- a/doc/update/11.1-to-11.2.md +++ b/doc/update/11.1-to-11.2.md @@ -51,7 +51,7 @@ sudo make install Install Bundler: ```bash -sudo gem install bundler --no-ri --no-rdoc +sudo gem install bundler --no-document ``` ### 4. Update Node diff --git a/doc/update/11.2-to-11.3.md b/doc/update/11.2-to-11.3.md index f2b8efc3e6e..228ff6cb70e 100644 --- a/doc/update/11.2-to-11.3.md +++ b/doc/update/11.2-to-11.3.md @@ -51,7 +51,7 @@ sudo make install Install Bundler: ```bash -sudo gem install bundler --no-ri --no-rdoc +sudo gem install bundler --no-document ``` ### 4. Update Node diff --git a/doc/update/11.3-to-11.4.md b/doc/update/11.3-to-11.4.md index fddec45e57a..5f64bf81127 100644 --- a/doc/update/11.3-to-11.4.md +++ b/doc/update/11.3-to-11.4.md @@ -51,7 +51,7 @@ sudo make install Install Bundler: ```bash -sudo gem install bundler --no-ri --no-rdoc +sudo gem install bundler --no-document ``` ### 4. Update Node diff --git a/doc/update/11.4-to-11.5.md b/doc/update/11.4-to-11.5.md index 44105348d14..fd7a8e5c2ae 100644 --- a/doc/update/11.4-to-11.5.md +++ b/doc/update/11.4-to-11.5.md @@ -51,7 +51,7 @@ sudo make install Install Bundler: ```bash -sudo gem install bundler --no-ri --no-rdoc +sudo gem install bundler --no-document ``` ### 4. Update Node diff --git a/doc/update/11.5-to-11.6.md b/doc/update/11.5-to-11.6.md index 031abc434ca..2e9ec5d71de 100644 --- a/doc/update/11.5-to-11.6.md +++ b/doc/update/11.5-to-11.6.md @@ -51,7 +51,7 @@ sudo make install Install Bundler: ```bash -sudo gem install bundler --no-ri --no-rdoc +sudo gem install bundler --no-document ``` ### 4. Update Node diff --git a/doc/update/6.9-to-7.0.md b/doc/update/6.9-to-7.0.md index 27063948028..781c90e4198 100644 --- a/doc/update/6.9-to-7.0.md +++ b/doc/update/6.9-to-7.0.md @@ -47,7 +47,7 @@ sudo make install Install Bundler: ```bash -sudo gem install bundler --no-ri --no-rdoc +sudo gem install bundler --no-document ``` ### 3. Get latest code diff --git a/doc/update/6.x-or-7.x-to-7.14.md b/doc/update/6.x-or-7.x-to-7.14.md index 61854b91aa2..6fcec5b7974 100644 --- a/doc/update/6.x-or-7.x-to-7.14.md +++ b/doc/update/6.x-or-7.x-to-7.14.md @@ -67,7 +67,7 @@ sudo make install Install Bundler: ```bash -sudo gem install bundler --no-ri --no-rdoc +sudo gem install bundler --no-document ``` ## 3. Get latest code diff --git a/doc/update/7.0-to-7.1.md b/doc/update/7.0-to-7.1.md index 308e8aeb985..fb4710faad5 100644 --- a/doc/update/7.0-to-7.1.md +++ b/doc/update/7.0-to-7.1.md @@ -47,7 +47,7 @@ sudo make install Install Bundler: ```bash -sudo gem install bundler --no-ri --no-rdoc +sudo gem install bundler --no-document ``` ### 3. Get latest code diff --git a/doc/update/8.10-to-8.11.md b/doc/update/8.10-to-8.11.md index df3e34f5cc6..12a465e1602 100644 --- a/doc/update/8.10-to-8.11.md +++ b/doc/update/8.10-to-8.11.md @@ -47,7 +47,7 @@ sudo make install Install Bundler: ```bash -sudo gem install bundler --no-ri --no-rdoc +sudo gem install bundler --no-document ``` ### 4. Get latest code diff --git a/doc/update/8.11-to-8.12.md b/doc/update/8.11-to-8.12.md index 9d6a1f42375..b9a7986d5ba 100644 --- a/doc/update/8.11-to-8.12.md +++ b/doc/update/8.11-to-8.12.md @@ -47,7 +47,7 @@ sudo make install Install Bundler: ```bash -sudo gem install bundler --no-ri --no-rdoc +sudo gem install bundler --no-document ``` ### 4. Get latest code diff --git a/doc/update/8.12-to-8.13.md b/doc/update/8.12-to-8.13.md index 6225dee9802..37e61794e7e 100644 --- a/doc/update/8.12-to-8.13.md +++ b/doc/update/8.12-to-8.13.md @@ -47,7 +47,7 @@ sudo make install Install Bundler: ```bash -sudo gem install bundler --no-ri --no-rdoc +sudo gem install bundler --no-document ``` ### 4. Get latest code diff --git a/doc/update/8.13-to-8.14.md b/doc/update/8.13-to-8.14.md index d2508e3f980..927f453b9bf 100644 --- a/doc/update/8.13-to-8.14.md +++ b/doc/update/8.13-to-8.14.md @@ -47,7 +47,7 @@ sudo make install Install Bundler: ```bash -sudo gem install bundler --no-ri --no-rdoc +sudo gem install bundler --no-document ``` ### 4. Get latest code diff --git a/doc/update/8.14-to-8.15.md b/doc/update/8.14-to-8.15.md index daf8d0f2ca6..d98a60d31c8 100644 --- a/doc/update/8.14-to-8.15.md +++ b/doc/update/8.14-to-8.15.md @@ -50,7 +50,7 @@ sudo make install Install Bundler: ```bash -sudo gem install bundler --no-ri --no-rdoc +sudo gem install bundler --no-document ``` ### 4. Get latest code diff --git a/doc/update/8.15-to-8.16.md b/doc/update/8.15-to-8.16.md index 3668142edd2..94b0102ed48 100644 --- a/doc/update/8.15-to-8.16.md +++ b/doc/update/8.15-to-8.16.md @@ -50,7 +50,7 @@ sudo make install Install Bundler: ```bash -sudo gem install bundler --no-ri --no-rdoc +sudo gem install bundler --no-document ``` ### 4. Get latest code diff --git a/doc/update/8.16-to-8.17.md b/doc/update/8.16-to-8.17.md index ee2e31c2aec..5a4f620a164 100644 --- a/doc/update/8.16-to-8.17.md +++ b/doc/update/8.16-to-8.17.md @@ -50,7 +50,7 @@ sudo make install Install Bundler: ```bash -sudo gem install bundler --no-ri --no-rdoc +sudo gem install bundler --no-document ``` ### 4. Update Node diff --git a/doc/update/8.17-to-9.0.md b/doc/update/8.17-to-9.0.md index 3c73bc573a6..38f7d22437a 100644 --- a/doc/update/8.17-to-9.0.md +++ b/doc/update/8.17-to-9.0.md @@ -49,7 +49,7 @@ sudo make install Install Bundler: ```bash -sudo gem install bundler --no-ri --no-rdoc +sudo gem install bundler --no-document ``` ### 4. Update Node diff --git a/doc/update/9.0-to-9.1.md b/doc/update/9.0-to-9.1.md index 7c9dacc9b90..a4d2e7be23c 100644 --- a/doc/update/9.0-to-9.1.md +++ b/doc/update/9.0-to-9.1.md @@ -49,7 +49,7 @@ sudo make install Install Bundler: ```bash -sudo gem install bundler --no-ri --no-rdoc +sudo gem install bundler --no-document ``` ### 4. Update Node diff --git a/doc/update/9.1-to-9.2.md b/doc/update/9.1-to-9.2.md index b815242ab4e..dd808c51985 100644 --- a/doc/update/9.1-to-9.2.md +++ b/doc/update/9.1-to-9.2.md @@ -49,7 +49,7 @@ sudo make install Install Bundler: ```bash -sudo gem install bundler --no-ri --no-rdoc +sudo gem install bundler --no-document ``` ### 4. Update Node diff --git a/doc/update/9.2-to-9.3.md b/doc/update/9.2-to-9.3.md index a58b12cb81c..d2bcf45a28e 100644 --- a/doc/update/9.2-to-9.3.md +++ b/doc/update/9.2-to-9.3.md @@ -49,7 +49,7 @@ sudo make install Install Bundler: ```bash -sudo gem install bundler --no-ri --no-rdoc +sudo gem install bundler --no-document ``` ### 4. Update Node diff --git a/doc/update/9.3-to-9.4.md b/doc/update/9.3-to-9.4.md index 0c87468334b..dae2162a964 100644 --- a/doc/update/9.3-to-9.4.md +++ b/doc/update/9.3-to-9.4.md @@ -49,7 +49,7 @@ sudo make install Install Bundler: ```bash -sudo gem install bundler --no-ri --no-rdoc +sudo gem install bundler --no-document ``` ### 4. Update Node diff --git a/doc/update/9.4-to-9.5.md b/doc/update/9.4-to-9.5.md index 6a655f77a55..f2811e9471f 100644 --- a/doc/update/9.4-to-9.5.md +++ b/doc/update/9.4-to-9.5.md @@ -49,7 +49,7 @@ sudo make install Install Bundler: ```bash -sudo gem install bundler --no-ri --no-rdoc +sudo gem install bundler --no-document ``` ### 4. Update Node diff --git a/doc/update/9.5-to-10.0.md b/doc/update/9.5-to-10.0.md index 7790d192a82..333a6e35714 100644 --- a/doc/update/9.5-to-10.0.md +++ b/doc/update/9.5-to-10.0.md @@ -49,7 +49,7 @@ sudo make install Install Bundler: ```bash -sudo gem install bundler --no-ri --no-rdoc +sudo gem install bundler --no-document ``` ### 4. Update Node diff --git a/doc/update/upgrader.md b/doc/update/upgrader.md deleted file mode 100644 index 746d6bf93e7..00000000000 --- a/doc/update/upgrader.md +++ /dev/null @@ -1,91 +0,0 @@ ---- -comments: false ---- - -# GitLab Upgrader (deprecated) - -*DEPRECATED* We recommend to [switch to the Omnibus package and repository server](https://about.gitlab.com/update/) instead of using this script. - -Although deprecated, if someone wants to make this script into a gem or otherwise improve it merge requests are welcome. - -*Make sure you view this [upgrade guide from the 'master' branch](https://gitlab.com/gitlab-org/gitlab-ce/blob/master/doc/update/upgrader.md) for the most up to date instructions.* - -GitLab Upgrader - a ruby script that allows you easily upgrade GitLab to latest minor version. - -For example it can update your application from 6.4 to latest GitLab 6 version (like 6.6.1). - -You still need to create a backup and manually restart GitLab after running the script but all other operations are done by this upgrade script. - -If you have local changes to your GitLab repository the script will stash them and you need to use `git stash pop` after running the script. - -**GitLab Upgrader is available only for GitLab version 6.4.2 or higher.** - -**This script does NOT update gitlab-shell, it needs manual update. See step 5 below.** - -## 0. Backup - - cd /home/git/gitlab - sudo -u git -H bundle exec rake gitlab:backup:create RAILS_ENV=production - -## 1. Stop server - - sudo service gitlab stop - -## 2. Run GitLab upgrade tool - -Please replace X.X.X with the [latest GitLab release](https://packages.gitlab.com/gitlab/gitlab-ce). - -GitLab 7.9 adds `nodejs` as a dependency. GitLab 7.6 adds `libkrb5-dev` as a dependency (installed by default on Ubuntu and OSX). GitLab 7.2 adds `pkg-config` and `cmake` as dependency. Please check the dependencies in the [installation guide.](https://gitlab.com/gitlab-org/gitlab-ce/blob/master/doc/install/installation.md#1-packages-dependencies) - - cd /home/git/gitlab - sudo -u git -H ruby -Ilib -e 'require "gitlab/upgrader"' -e 'class Gitlab::Upgrader' -e 'def latest_version_raw' -e '"vX.X.X"' -e 'end' -e 'end' -e 'Gitlab::Upgrader.new.execute' - - # to perform a non-interactive install (no user input required) you can add -y - # sudo -u git -H ruby -Ilib -e 'require "gitlab/upgrader"' -e 'class Gitlab::Upgrader' -e 'def latest_version_raw' -e '"vX.X.X"' -e 'end' -e 'end' -e 'Gitlab::Upgrader.new.execute' -- -y - -## 3. Start application - - sudo service gitlab start - sudo service nginx restart - -## 4. Check application status - -Check if GitLab and its dependencies are configured correctly: - - sudo -u git -H bundle exec rake gitlab:check RAILS_ENV=production - -If all items are green, then congratulations upgrade is complete! - -## 5. Upgrade GitLab Shell - -GitLab Shell might be outdated, running the commands below ensures you're using a compatible version: - -``` -cd /home/git/gitlab-shell -sudo -u git -H git fetch -sudo -u git -H git checkout v`cat /home/git/gitlab/GITLAB_SHELL_VERSION` -sudo -u git -H sh -c 'if [ -x bin/compile ] ; then bin/compile ; fi' -``` - -## One line upgrade command - -You've read through the entire guide and probably already did all the steps one by one. - -Below is a one line command with step 1 to 5 for the next time you upgrade. - -Please replace X.X.X with the [latest GitLab release](https://packages.gitlab.com/gitlab/gitlab-ce). - -```bash -cd /home/git/gitlab; \ - sudo -u git -H bundle exec rake gitlab:backup:create RAILS_ENV=production; \ - sudo service gitlab stop; \ - sudo -u git -H ruby -Ilib -e 'require "gitlab/upgrader"' -e 'class Gitlab::Upgrader' -e 'def latest_version_raw' -e '"vX.X.X"' -e 'end' -e 'end' -e 'Gitlab::Upgrader.new.execute' -- -y; \ - cd /home/git/gitlab-shell; \ - sudo -u git -H git fetch; \ - sudo -u git -H git checkout v`cat /home/git/gitlab/GITLAB_SHELL_VERSION`; \ - sudo -u git -H sh -c 'if [ -x bin/compile ] ; then bin/compile ; fi'; \ - cd /home/git/gitlab; \ - sudo service gitlab start; \ - sudo service nginx restart; \ - sudo -u git -H bundle exec rake gitlab:check RAILS_ENV=production -```
\ No newline at end of file diff --git a/doc/user/abuse_reports.md b/doc/user/abuse_reports.md new file mode 100644 index 00000000000..1f4f598b121 --- /dev/null +++ b/doc/user/abuse_reports.md @@ -0,0 +1,53 @@ +# Abuse reports + +Report abuse from users to GitLab administrators. + +You can report a user through their: + +- [Profile](#reporting-abuse-through-a-users-profile) +- [Comments](#reporting-abuse-through-a-users-comment) +- [Issues and Merge requests](#reporting-abuse-through-a-users-issue-or-merge-request) + +## Reporting abuse through a user's profile + +To report abuse from a user's profile page: + +1. Click on the exclamation point report abuse button at the top right of the user's profile. +1. Complete an abuse report. +1. Click the **Send report** button. + +## Reporting abuse through a user's comment + +To report abuse from a user's comment: + +1. Click on the vertical ellipsis (⋮) more actions button to open the dropdown. +1. Select **Report as abuse**. +1. Complete an abuse report. +1. Click the **Send report** button. + + +NOTE: **Note:** +A URL to the reported user's comment will be +pre-filled in the abuse report's **Message** field. + +## Reporting abuse through a user's issue or merge request + +The **Report abuse** button is displayed at the top right of the issue or merge request: + +- When **Report abuse** is selected from the menu that appears when the **Close issue** or **Close merge request** button is clicked, for users that have permission to close the issue or merge request. +- When viewing the issue or merge request, for users that don't have permission to close the issue or merge request. + +With the **Report abuse** button displayed, to submit an abuse report: + +1. Click the **Report abuse** button. +1. Submit an abuse report. +1. Click the **Send report** button. + +NOTE: **Note:** +A URL to the reported user's issue or merge request will be pre-filled +in the abuse report's **Message** field. + +## Managing abuse reports + +Admins are able to view and resolve abuse reports. +For more information, see [abuse reports administration documentation](admin_area/abuse_reports.md). diff --git a/doc/user/admin_area/abuse_reports.md b/doc/user/admin_area/abuse_reports.md new file mode 100644 index 00000000000..01c2d9607f5 --- /dev/null +++ b/doc/user/admin_area/abuse_reports.md @@ -0,0 +1,31 @@ +# Abuse reports + +View and resolve abuse reports from GitLab users. + +Admins can view abuse reports in the admin area and are able to +resolve the reports by removing the reported user, blocking the reported user, or removing the report. + +## Reporting abuse + +To find out more about reporting abuse, see [abuse reports user documentation](../abuse_reports.md). + +## Resolving abuse reports + +To access abuse reports, go to **Admin area > Abuse Reports**. + +There are 3 ways to resolve an abuse report, with a button for each method: + +- Remove user & report: [Deletes the reported user](../profile/account/delete_account.md) from the instance and removes the abuse report from the list. +- Block user: Blocks the reported user from the instance and does not remove the abuse report from the list. +- Remove report: Removes the abuse report from the list and does not restrict the access for the reported user. + +![abuse-reports-page-image](img/abuse_reports_page.png) + +## Blocked users + +Blocking a user will not remove the abuse report from the list. + +Instead, the block button will be disabled and explain that the user is "Already blocked". +You are still able to remove the user and/or report if necessary. + +![abuse-report-blocked-user-image](img/abuse_report_blocked_user.png) diff --git a/doc/user/admin_area/img/abuse_report_blocked_user.png b/doc/user/admin_area/img/abuse_report_blocked_user.png Binary files differnew file mode 100644 index 00000000000..0cb4c7bb8ac --- /dev/null +++ b/doc/user/admin_area/img/abuse_report_blocked_user.png diff --git a/doc/user/admin_area/img/abuse_reports_page.png b/doc/user/admin_area/img/abuse_reports_page.png Binary files differnew file mode 100644 index 00000000000..81dbe976cda --- /dev/null +++ b/doc/user/admin_area/img/abuse_reports_page.png diff --git a/doc/user/admin_area/img/admin_area_settings_button.png b/doc/user/admin_area/img/admin_area_settings_button.png Binary files differindex 315ef40a375..5b969ecd668 100644 --- a/doc/user/admin_area/img/admin_area_settings_button.png +++ b/doc/user/admin_area/img/admin_area_settings_button.png diff --git a/doc/user/admin_area/settings/img/import_sources.png b/doc/user/admin_area/settings/img/import_sources.png Binary files differindex 4257f02448f..20829a27dd7 100644 --- a/doc/user/admin_area/settings/img/import_sources.png +++ b/doc/user/admin_area/settings/img/import_sources.png diff --git a/doc/user/discussions/img/insert_suggestion.png b/doc/user/discussions/img/insert_suggestion.png Binary files differnew file mode 100644 index 00000000000..4bf293b8297 --- /dev/null +++ b/doc/user/discussions/img/insert_suggestion.png diff --git a/doc/user/discussions/img/make_suggestion.png b/doc/user/discussions/img/make_suggestion.png Binary files differnew file mode 100644 index 00000000000..20acc1417da --- /dev/null +++ b/doc/user/discussions/img/make_suggestion.png diff --git a/doc/user/discussions/img/suggestion.png b/doc/user/discussions/img/suggestion.png Binary files differnew file mode 100644 index 00000000000..68a67e6ae5e --- /dev/null +++ b/doc/user/discussions/img/suggestion.png diff --git a/doc/user/discussions/index.md b/doc/user/discussions/index.md index 0f89d261ff6..9379d047fca 100644 --- a/doc/user/discussions/index.md +++ b/doc/user/discussions/index.md @@ -293,6 +293,51 @@ Once you select one of the filters in a given issue or MR, GitLab will save your preference, so that it will persist when you visit the same page again from any device you're logged into. +## Suggest Changes + +> [Introduced](https://gitlab.com/gitlab-org/gitlab-ce/issues/18008) in GitLab 11.6. + +As a reviewer, you're able to suggest code changes with a simple +markdown syntax in Merge Request Diff discussions. Then, the +Merge Request author (or other users with appropriate +[permission](../permissions.md)) is able to apply these +suggestions with a click, which will generate a commit in +the Merge Request authored by the user that applied them. + +1. Choose a line of code to be changed, add a new comment, then click +on the **Insert suggestion** icon in the toolbar: + + ![Add a new comment](img/insert_suggestion.png) + + > **Note:** + The suggestion will only affect the commented line. Multi-line + suggestions are currently not supported. Will be introduced by + [#53310](https://gitlab.com/gitlab-org/gitlab-ce/issues/53310). + +1. In the comment, add your suggestion to the pre-populated code block: + + ![Add a suggestion into a code block tagged properly](img/make_suggestion.png) + +1. Click **Comment**. + + The suggestions in the comment can be applied by the merge request author + directly from the merge request: + + ![Apply suggestions](img/suggestion.png) + + > **Note:** + Discussions are _not_ automatically resolved. Will be introduced by + [#54405](https://gitlab.com/gitlab-org/gitlab-ce/issues/54405). + +Once the author applies a suggestion, it will be marked with the **Applied** label, +and GitLab will create a new commit with the message `Apply suggestion to <file-name>` +and push the suggested change directly into the codebase in the merge request's branch. +[Developer permission](../permissions.md) is required to do so. + +> **Note:** +Custom commit messages will be introduced by +[#54404](https://gitlab.com/gitlab-org/gitlab-ce/issues/54404). + [ce-5022]: https://gitlab.com/gitlab-org/gitlab-ce/merge_requests/5022 [ce-7125]: https://gitlab.com/gitlab-org/gitlab-ce/merge_requests/7125 [ce-7527]: https://gitlab.com/gitlab-org/gitlab-ce/merge_requests/7527 diff --git a/doc/user/group/clusters/index.md b/doc/user/group/clusters/index.md index adc43921d47..9f9b2da23e1 100644 --- a/doc/user/group/clusters/index.md +++ b/doc/user/group/clusters/index.md @@ -1,9 +1,7 @@ # Group-level Kubernetes clusters > [Introduced](https://gitlab.com/gitlab-org/gitlab-ce/issues/34758) in GitLab 11.6. - -CAUTION: **Warning:** -Group Cluster integration is currently in **Beta**. +> Group Cluster integration is currently in [Beta](https://about.gitlab.com/handbook/product/#alpha-beta-ga). ## Overview @@ -124,3 +122,11 @@ The result will then be: - The Project cluster will be used for the `test` job. - The Staging cluster will be used for the `deploy to staging` job. - The Production cluster will be used for the `deploy to production` job. + +## Unavailable features + +The following features are not currently available for group-level clusters: + +1. Terminals (see [related issue](https://gitlab.com/gitlab-org/gitlab-ce/issues/55487)). +1. Pod logs (see [related issue](https://gitlab.com/gitlab-org/gitlab-ce/issues/55488)). +1. Deployment boards (see [related issue](https://gitlab.com/gitlab-org/gitlab-ce/issues/55488)). diff --git a/doc/user/group/img/add_new_members.png b/doc/user/group/img/add_new_members.png Binary files differindex 4431c9fbe0b..6d43e309e84 100644 --- a/doc/user/group/img/add_new_members.png +++ b/doc/user/group/img/add_new_members.png diff --git a/doc/user/group/img/create_new_project_from_group.png b/doc/user/group/img/create_new_project_from_group.png Binary files differindex b6286ac7800..df98091334c 100644 --- a/doc/user/group/img/create_new_project_from_group.png +++ b/doc/user/group/img/create_new_project_from_group.png diff --git a/doc/user/img/color_inline_colorchip_render_gfm.png b/doc/user/img/color_inline_colorchip_render_gfm.png Binary files differindex 6a8a674d6e0..fed8ca5c34b 100644 --- a/doc/user/img/color_inline_colorchip_render_gfm.png +++ b/doc/user/img/color_inline_colorchip_render_gfm.png diff --git a/doc/user/img/math_inline_sup_render_gfm.png b/doc/user/img/math_inline_sup_render_gfm.png Binary files differindex bf1464457bc..3ee2abb14df 100644 --- a/doc/user/img/math_inline_sup_render_gfm.png +++ b/doc/user/img/math_inline_sup_render_gfm.png diff --git a/doc/user/img/mermaid_diagram_render_gfm.png b/doc/user/img/mermaid_diagram_render_gfm.png Binary files differindex 3b3eb3a738a..9d192a30a85 100644 --- a/doc/user/img/mermaid_diagram_render_gfm.png +++ b/doc/user/img/mermaid_diagram_render_gfm.png diff --git a/doc/user/img/task_list_ordered_render_gfm.png b/doc/user/img/task_list_ordered_render_gfm.png Binary files differindex fdff8a9886c..0905a8378be 100644 --- a/doc/user/img/task_list_ordered_render_gfm.png +++ b/doc/user/img/task_list_ordered_render_gfm.png diff --git a/doc/user/img/unordered_check_list_render_gfm.png b/doc/user/img/unordered_check_list_render_gfm.png Binary files differindex 2e3fb7cbb79..ccdeab6e62c 100644 --- a/doc/user/img/unordered_check_list_render_gfm.png +++ b/doc/user/img/unordered_check_list_render_gfm.png diff --git a/doc/user/index.md b/doc/user/index.md index 08995032cb1..fc68404d0c2 100644 --- a/doc/user/index.md +++ b/doc/user/index.md @@ -113,6 +113,7 @@ methods available in GitLab. user type (guest, reporter, developer, maintainer, owner). - [Feature highlight](feature_highlight.md): Learn more about the little blue dots around the app that explain certain features +- [Abuse reports](abuse_reports.md): Report abuse from users to GitLab administrators ## Groups diff --git a/doc/user/markdown.md b/doc/user/markdown.md index debebd4c081..893658290e5 100644 --- a/doc/user/markdown.md +++ b/doc/user/markdown.md @@ -5,11 +5,11 @@ It is not valid for the [GitLab documentation website](https://docs.gitlab.com) nor [GitLab's main website](https://about.gitlab.com), as they both use [Kramdown](https://kramdown.gettalong.org) as their markdown engine. The documentation website uses an extended Kramdown gem, [GitLab Kramdown](https://gitlab.com/gitlab-org/gitlab_kramdown). -Consult the [GitLab Kramdown Guide](https://about.gitlab.com/handbook/product/technical-writing/markdown-guide/) for a complete Kramdown reference._ +Consult the [GitLab Kramdown Guide](https://about.gitlab.com/handbook/product/technical-writing/markdown-guide/) for a complete Kramdown reference. ## GitLab Flavored Markdown (GFM) -GitLab uses "GitLab Flavored Markdown" (GFM). It extends the [CommonMark specification][commonmark-spec] (which is based on standard Markdown) in a few significant ways to add some useful functionality. It was inspired by [GitHub Flavored Markdown](https://help.github.com/articles/basic-writing-and-formatting-syntax/). +GitLab uses "GitLab Flavored Markdown" (GFM). It extends the [CommonMark specification][commonmark-spec] (which is based on standard Markdown) in a few significant ways to add additional useful functionality. It was inspired by [GitHub Flavored Markdown](https://help.github.com/articles/basic-writing-and-formatting-syntax/). You can use GFM in the following areas: @@ -26,8 +26,7 @@ dependency to do so. Please see the [`github-markup` gem readme](https://github. > **Notes:** > -> For the best result, we encourage you to check this document out as [rendered -> by GitLab itself](markdown.md). +> We encourage you to view this document as [rendered by GitLab itself](markdown.md). > > As of 11.1, GitLab uses the [CommonMark Ruby Library][commonmarker] for Markdown processing of all new issues, merge requests, comments, and other Markdown content @@ -142,7 +141,7 @@ GFM will autolink almost any URL you copy and paste into your text: * <a href="irc://irc.freenode.net/gitlab">irc://irc.freenode.net/gitlab</a> * http://localhost:3000 -### Multiline Blockquote +### Multiline blockquote > If this is not rendered correctly, see https://gitlab.com/gitlab-org/gitlab-ce/blob/master/doc/user/markdown.md#multiline-blockquote @@ -172,7 +171,7 @@ you can quote that without having to manually prepend `>` to every line! <p>you can quote that without having to manually prepend <code>></code> to every line!</p> </blockquote> -### Code and Syntax Highlighting +### Code and syntax highlighting > If this is not rendered correctly, see https://gitlab.com/gitlab-org/gitlab-ce/blob/master/doc/user/markdown.md#code-and-syntax-highlighting @@ -242,7 +241,7 @@ s = "There is no highlighting for this." But let's throw in a <b>tag</b>. ``` -### Inline Diff +### Inline diff > If this is not rendered correctly, see https://gitlab.com/gitlab-org/gitlab-ce/blob/master/doc/user/markdown.md#inline-diff @@ -309,7 +308,7 @@ On Linux, you can download [Noto Color Emoji](https://www.google.com/get/noto/he Ubuntu 18.04 (like many modern Linux distros) has this font installed by default. -### Special GitLab References +### Special GitLab references GFM recognizes special references. @@ -363,7 +362,7 @@ It also has a shorthand version to reference other projects from the same namesp | `project@9ba12248...b19a04f5` | commit range comparison | | `project~"Some label"` | issues with given label | -### Task Lists +### Task lists > If this is not rendered correctly, see https://gitlab.com/gitlab-org/gitlab-ce/blob/master/doc/user/markdown.md#task-lists @@ -505,6 +504,66 @@ Becomes: For details see the [Mermaid official page][mermaid]. +### Front matter + +> [Introduced](https://gitlab.com/gitlab-org/gitlab-ce/merge_requests/23331) + in GitLab 11.6. + +Front matter is metadata included at the beginning of a markdown document, preceding +its content. This data can be used by static site generators such as [Jekyll](https://jekyllrb.com/docs/front-matter/) and [Hugo](https://gohugo.io/content-management/front-matter/), +and many other applications. + +In GitLab, front matter is only used in Markdown files and wiki pages, not the other places where Markdown formatting is supported. +When you view a Markdown file rendered by GitLab, any front matter is displayed as-is, in a box at the top of the document, before the rendered HTML content. +To view an example, you can toggle between the source and rendered version of a [GitLab documentation file](https://gitlab.com/gitlab-org/gitlab-ce/blob/master/doc/README.md). + +The following delimeters are supported: + +- YAML (`---`): + + ``` + --- + title: About Front Matter + example: + language: yaml + --- + ``` + +- TOML (`+++`): + + ``` + +++ + title = "About Front Matter" + [example] + language = "toml" + +++ + ``` + +- JSON (`;;;`): + + ``` + ;;; + { + "title": "About Front Matter" + "example": { + "language": "json" + } + } + ;;; + ``` + +Other languages are supported by adding a specifier to any of the existing +delimiters. For example: + +``` +---php +$title = "About Front Matter"; +$example = array( + 'language' => "php", +); +--- +``` + ## Standard Markdown ### Headers diff --git a/doc/user/permissions.md b/doc/user/permissions.md index c4a2d5f66e5..ed00f86f9de 100644 --- a/doc/user/permissions.md +++ b/doc/user/permissions.md @@ -79,6 +79,7 @@ The following table depicts the various user permission levels in a project. | View approved/blacklisted licenses **[ULTIMATE]** | | | ✓ | ✓ | ✓ | | Use security dashboard **[ULTIMATE]** | | | ✓ | ✓ | ✓ | | Dismiss vulnerability **[ULTIMATE]** | | | ✓ | ✓ | ✓ | +| Apply code change suggestions | | | ✓ | ✓ | ✓ | | Use environment terminals | | | | ✓ | ✓ | | Add new team members | | | | ✓ | ✓ | | Push to protected branches | | | | ✓ | ✓ | diff --git a/doc/user/profile/account/delete_account.md b/doc/user/profile/account/delete_account.md index 49f0ce2cd79..b497cc414af 100644 --- a/doc/user/profile/account/delete_account.md +++ b/doc/user/profile/account/delete_account.md @@ -25,7 +25,7 @@ Here's a list of things that will not be deleted: Instead of being deleted, these records will be moved to a system-wide "Ghost User", whose sole purpose is to act as a container for such records. -When a user is deleted from an abuse report or spam log, these associated +When a user is deleted from an [abuse report](../../admin_area/abuse_reports.md) or spam log, these associated records are not ghosted and will be removed, along with any groups the user is a sole owner of. Administrators can also request this behaviour when deleting users from the [API](../../../api/users.md#user-deletion) or the @@ -35,4 +35,3 @@ admin area. [ce-10273]: https://gitlab.com/gitlab-org/gitlab-ce/merge_requests/10273 [ce-10467]: https://gitlab.com/gitlab-org/gitlab-ce/merge_requests/10467 [ce-11853]: https://gitlab.com/gitlab-org/gitlab-ce/merge_requests/11853 - diff --git a/doc/user/project/clusters/eks_and_gitlab/index.md b/doc/user/project/clusters/eks_and_gitlab/index.md index fa2ed21f980..0e6d4bce153 100644 --- a/doc/user/project/clusters/eks_and_gitlab/index.md +++ b/doc/user/project/clusters/eks_and_gitlab/index.md @@ -49,7 +49,7 @@ A few details from the EKS cluster will be required to connect it to GitLab: - Get the certificate with: ```sh - kubectl get secret <secret name> -o jsonpath="{['data']['ca\.crt']}" | base64 -D + kubectl get secret <secret name> -o jsonpath="{['data']['ca\.crt']}" | base64 --decode ``` 1. **Create admin token**: A `cluster-admin` token is required to install and diff --git a/doc/user/project/clusters/index.md b/doc/user/project/clusters/index.md index 95bd5d1cf4e..6f334af4fb7 100644 --- a/doc/user/project/clusters/index.md +++ b/doc/user/project/clusters/index.md @@ -60,7 +60,7 @@ new Kubernetes cluster to your project: **Sign in with Google** button. 1. From there on, choose your cluster's settings: - **Kubernetes cluster name** - The name you wish to give the cluster. - - **Environment scope** - The [associated environment](#setting-the-environment-scope) to this cluster. + - **Environment scope** - The [associated environment](#setting-the-environment-scope-premium) to this cluster. - **Google Cloud Platform project** - Choose the project you created in your GCP console that will host the Kubernetes cluster. Learn more about [Google Cloud Platform projects](https://cloud.google.com/resource-manager/docs/creating-managing-projects). @@ -140,13 +140,14 @@ To add an existing Kubernetes cluster to your project: to grant access. - **Project namespace** (optional) - You don't have to fill it in; by leaving it blank, GitLab will create one for you. Also: - - Each project should have a unique namespace. - - The project namespace is not necessarily the namespace of the secret, if - you're using a secret with broader permissions, like the secret from `default`. - - You should **not** use `default` as the project namespace. - - If you or someone created a secret specifically for the project, usually - with limited permissions, the secret's namespace and project namespace may - be the same. + - Each project should have a unique namespace. + - The project namespace is not necessarily the namespace of the secret, if + you're using a secret with broader permissions, like the secret from `default`. + - You should **not** use `default` as the project namespace. + - If you or someone created a secret specifically for the project, usually + with limited permissions, the secret's namespace and project namespace may + be the same. + 1. Finally, click the **Create Kubernetes cluster** button. After a couple of minutes, your cluster will be ready to go. You can now proceed @@ -157,8 +158,8 @@ To determine the: - API URL, run `kubectl cluster-info | grep 'Kubernetes master' | awk '/http/ {print $NF}'`. - Token: 1. List the secrets by running: `kubectl get secrets`. Note the name of the secret you need the token for. - 1. Get the token for the appropriate secret by running: `kubectl get secret <SECRET_NAME> -o jsonpath="{['data']['token']}" | base64 -D`. -- CA certificate, run `kubectl get secret <secret name> -o jsonpath="{['data']['ca\.crt']}" | base64 -D`. + 1. Get the token for the appropriate secret by running: `kubectl get secret <SECRET_NAME> -o jsonpath="{['data']['token']}" | base64 --decode`. +- CA certificate, run `kubectl get secret <secret name> -o jsonpath="{['data']['ca\.crt']}" | base64 --decode`. ## Security implications @@ -168,7 +169,7 @@ are trusted, so **only trusted users should be allowed to control your clusters* The default cluster configuration grants access to a wide set of functionalities needed to successfully build and deploy a containerized -application. Bare in mind that the same credentials are used for all the +application. Bear in mind that the same credentials are used for all the applications running on the cluster. ## Access controls @@ -253,15 +254,19 @@ install it manually. GitLab provides a one-click install for various applications which can be added directly to your configured cluster. Those applications are needed for [Review Apps](../../../ci/review_apps/index.md) and -[deployments](../../../ci/environments.md). +[deployments](../../../ci/environments.md). You can install them after you +[create a cluster](#adding-and-creating-a-new-gke-cluster-via-gitlab). + +To see a list of available applications to install: + +1. Navigate to your project's **Operations > Kubernetes**. +1. Select your cluster. + +Install Helm Tiller first because it's used to install other applications. NOTE: **Note:** -With the exception of Knative, the applications will be installed in a dedicated namespace called -`gitlab-managed-apps`. In case you have added an existing Kubernetes cluster -with Tiller already installed, you should be careful as GitLab cannot -detect it. In this event, installing Tiller via the applications will -result in the cluster having it twice. This can lead to confusion during -deployments. +As of GitLab 11.6, Helm Tiller will be upgraded to the latest version supported +by GitLab before installing any of the applications. | Application | GitLab version | Description | Helm Chart | | ----------- | :------------: | ----------- | --------------- | @@ -273,9 +278,14 @@ deployments. | [JupyterHub](http://jupyter.org/) | 11.0+ | [JupyterHub](https://jupyterhub.readthedocs.io/en/stable/) is a multi-user service for managing notebooks across a team. [Jupyter Notebooks](https://jupyter-notebook.readthedocs.io/en/latest/) provide a web-based interactive programming environment used for data analysis, visualization, and machine learning. We use a [custom Jupyter image](https://gitlab.com/gitlab-org/jupyterhub-user-image/blob/master/Dockerfile) that installs additional useful packages on top of the base Jupyter. You will also see ready-to-use DevOps Runbooks built with Nurtch's [Rubix library](https://github.com/amit1rrr/rubix). More information on creating executable runbooks can be found in [our Nurtch documentation](runbooks/index.md#nurtch-executable-runbooks). **Note**: Authentication will be enabled for any user of the GitLab server via OAuth2. HTTPS will be supported in a future release. | [jupyter/jupyterhub](https://jupyterhub.github.io/helm-chart/) | | [Knative](https://cloud.google.com/knative) | 11.5+ | Knative provides a platform to create, deploy, and manage serverless workloads from a Kubernetes cluster. It is used in conjunction with, and includes [Istio](https://istio.io) to provide an external IP address for all programs hosted by Knative. You will be prompted to enter a wildcard domain where your applications will be exposed. Configure your DNS server to use the external IP address for that domain. For any application created and installed, they will be accessible as `<program_name>.<kubernetes_namespace>.<domain_name>`. This will require your kubernetes cluster to have [RBAC enabled](#role-based-access-control-rbac). | [knative/knative](https://storage.googleapis.com/triggermesh-charts) -NOTE: **Note:** -As of GitLab 11.6 Helm Tiller will be upgraded to the latest version supported -by GitLab before installing any of the above applications. +With the exception of Knative, the applications will be installed in a dedicated +namespace called `gitlab-managed-apps`. + +CAUTION: **Caution:** +If you have an existing Kubernetes cluster with Tiller already installed, +you should be careful as GitLab cannot detect it. In this case, installing +Tiller via the applications will result in the cluster having it twice, which +can lead to confusion during deployments. ## Getting the external IP address @@ -354,6 +364,18 @@ to reach your apps. This heavily depends on your domain provider, but in case you aren't sure, just create an A record with a wildcard host like `*.example.com.`. +## Multiple Kubernetes clusters **[PREMIUM]** + +> Introduced in [GitLab Premium][ee] 10.3. + +With GitLab Premium, you can associate more than one Kubernetes clusters to your +project. That way you can have different clusters for different environments, +like dev, staging, production, etc. + +Simply add another cluster, like you did the first time, and make sure to +[set an environment scope](#setting-the-environment-scope) that will +differentiate the new cluster with the rest. + ## Setting the environment scope **[PREMIUM]** When adding more than one Kubernetes clusters to your project, you need @@ -372,11 +394,11 @@ Also, jobs that don't have an environment keyword set will not be able to access For example, let's say the following Kubernetes clusters exist in a project: -| Cluster | Environment scope | -| ---------- | ------------------- | -| Development| `*` | -| Staging | `staging/*` | -| Production | `production/*` | +| Cluster | Environment scope | +| ----------- | ----------------- | +| Development | `*` | +| Staging | `staging` | +| Production | `production` | And the following environments are set in [`.gitlab-ci.yml`](../../../ci/yaml/README.md): @@ -393,14 +415,14 @@ deploy to staging: stage: deploy script: make deploy environment: - name: staging/$CI_COMMIT_REF_NAME + name: staging url: https://staging.example.com/ deploy to production: stage: deploy script: make deploy environment: - name: production/$CI_COMMIT_REF_NAME + name: production url: https://example.com/ ``` @@ -410,18 +432,6 @@ The result will then be: - The staging cluster will be used for the "deploy to staging" job. - The production cluster will be used for the "deploy to production" job. -## Multiple Kubernetes clusters - -> Introduced in [GitLab Premium][ee] 10.3. - -With GitLab Premium, you can associate more than one Kubernetes clusters to your -project. That way you can have different clusters for different environments, -like dev, staging, production, etc. - -Simply add another cluster, like you did the first time, and make sure to -[set an environment scope](#setting-the-environment-scope) that will -differentiate the new cluster with the rest. - ## Deployment variables The Kubernetes cluster integration exposes the following @@ -463,6 +473,14 @@ builds is that they must have a matching your build has no `environment:name` set, it will not be passed the Kubernetes credentials. +## Monitoring your Kubernetes cluster **[ULTIMATE]** + +> [Introduced](https://gitlab.com/gitlab-org/gitlab-ee/merge_requests/4701) in [GitLab Ultimate][ee] 10.6. + +When [Prometheus is deployed](#installing-applications), GitLab will automatically monitor the cluster's health. At the top of the cluster settings page, CPU and Memory utilization is displayed, along with the total amount available. Keeping an eye on cluster resources can be important, if the cluster runs out of memory pods may be shutdown or fail to start. + +![Cluster Monitoring](https://docs.gitlab.com/ee/user/project/clusters/img/k8s_cluster_monitoring.png) + ## Enabling or disabling the Kubernetes cluster integration After you have successfully added your cluster information, you can enable the @@ -489,13 +507,16 @@ To remove the Kubernetes cluster integration from your project, simply click the **Remove integration** button. You will then be able to follow the procedure and add a Kubernetes cluster again. +## View Kubernetes pod logs from GitLab **[ULTIMATE]** + +Learn how to easily +[view the logs of running pods in connected Kubernetes clusters](https://docs.gitlab.com/ee/user/project/clusters/kubernetes_pod_logs.html). + ## What you can get with the Kubernetes integration Here's what you can do with GitLab if you enable the Kubernetes integration. -### Deploy Boards - -> Available in [GitLab Premium][ee]. +### Deploy Boards **[PREMIUM]** GitLab's Deploy Boards offer a consolidated view of the current health and status of each CI [environment](../../../ci/environments.md) running on Kubernetes, @@ -503,24 +524,22 @@ displaying the status of the pods in the deployment. Developers and other teammates can view the progress and status of a rollout, pod by pod, in the workflow they already use without any need to access Kubernetes. -[> Read more about Deploy Boards](https://docs.gitlab.com/ee/user/project/deploy_boards.html) +[Read more about Deploy Boards](https://docs.gitlab.com/ee/user/project/deploy_boards.html) -### Canary Deployments - -> Available in [GitLab Premium][ee]. +### Canary Deployments **[PREMIUM]** Leverage [Kubernetes' Canary deployments](https://kubernetes.io/docs/concepts/cluster-administration/manage-deployment/#canary-deployments) and visualize your canary deployments right inside the Deploy Board, without the need to leave GitLab. -[> Read more about Canary Deployments](https://docs.gitlab.com/ee/user/project/canary_deployments.html) +[Read more about Canary Deployments](https://docs.gitlab.com/ee/user/project/canary_deployments.html) ### Kubernetes monitoring Automatically detect and monitor Kubernetes metrics. Automatic monitoring of [NGINX ingress](../integrations/prometheus_library/nginx.md) is also supported. -[> Read more about Kubernetes monitoring](../integrations/prometheus_library/kubernetes.md) +[Read more about Kubernetes monitoring](../integrations/prometheus_library/kubernetes.md) ### Auto DevOps @@ -530,7 +549,7 @@ applications. To make full use of Auto DevOps(Auto Deploy, Auto Review Apps, and Auto Monitoring) you will need the Kubernetes project integration enabled. -[> Read more about Auto DevOps](../../../topics/autodevops/index.md) +[Read more about Auto DevOps](../../../topics/autodevops/index.md) ### Web terminals @@ -546,8 +565,6 @@ containers. To use this integration, you should deploy to Kubernetes using the deployment variables above, ensuring any pods you create are labelled with `app=$CI_ENVIRONMENT_SLUG`. GitLab will do the rest! -## Read more - ### Integrating Amazon EKS cluster with GitLab - Learn how to [connect and deploy to an Amazon EKS cluster](eks_and_gitlab/index.md). diff --git a/doc/user/project/clusters/runbooks/img/authorize-jupyter.png b/doc/user/project/clusters/runbooks/img/authorize-jupyter.png Binary files differindex 64f95ed45f0..84cce311483 100644 --- a/doc/user/project/clusters/runbooks/img/authorize-jupyter.png +++ b/doc/user/project/clusters/runbooks/img/authorize-jupyter.png diff --git a/doc/user/project/clusters/runbooks/img/demo-runbook.png b/doc/user/project/clusters/runbooks/img/demo-runbook.png Binary files differindex 25c9df4126d..37c110ed0d8 100644 --- a/doc/user/project/clusters/runbooks/img/demo-runbook.png +++ b/doc/user/project/clusters/runbooks/img/demo-runbook.png diff --git a/doc/user/project/clusters/runbooks/img/gitlab-variables.png b/doc/user/project/clusters/runbooks/img/gitlab-variables.png Binary files differindex f76ed21145f..1d338f063a9 100644 --- a/doc/user/project/clusters/runbooks/img/gitlab-variables.png +++ b/doc/user/project/clusters/runbooks/img/gitlab-variables.png diff --git a/doc/user/project/clusters/runbooks/img/helm-install.png b/doc/user/project/clusters/runbooks/img/helm-install.png Binary files differindex e39094bcbf7..003e482e756 100644 --- a/doc/user/project/clusters/runbooks/img/helm-install.png +++ b/doc/user/project/clusters/runbooks/img/helm-install.png diff --git a/doc/user/project/clusters/runbooks/img/ingress-install.png b/doc/user/project/clusters/runbooks/img/ingress-install.png Binary files differindex 093c61f2d0e..7edc11d5b45 100644 --- a/doc/user/project/clusters/runbooks/img/ingress-install.png +++ b/doc/user/project/clusters/runbooks/img/ingress-install.png diff --git a/doc/user/project/clusters/runbooks/img/jupyterhub-install.png b/doc/user/project/clusters/runbooks/img/jupyterhub-install.png Binary files differindex 2115ec9745b..75c6028a763 100644 --- a/doc/user/project/clusters/runbooks/img/jupyterhub-install.png +++ b/doc/user/project/clusters/runbooks/img/jupyterhub-install.png diff --git a/doc/user/project/clusters/runbooks/img/postgres-query.png b/doc/user/project/clusters/runbooks/img/postgres-query.png Binary files differindex 3880438c97a..04315d54d5e 100644 --- a/doc/user/project/clusters/runbooks/img/postgres-query.png +++ b/doc/user/project/clusters/runbooks/img/postgres-query.png diff --git a/doc/user/project/clusters/runbooks/img/sample-runbook.png b/doc/user/project/clusters/runbooks/img/sample-runbook.png Binary files differindex c12ce8990a4..70011202bf0 100644 --- a/doc/user/project/clusters/runbooks/img/sample-runbook.png +++ b/doc/user/project/clusters/runbooks/img/sample-runbook.png diff --git a/doc/user/project/clusters/serverless/img/deploy-stage.png b/doc/user/project/clusters/serverless/img/deploy-stage.png Binary files differindex dc2f8af9c63..a4a6b363b64 100644 --- a/doc/user/project/clusters/serverless/img/deploy-stage.png +++ b/doc/user/project/clusters/serverless/img/deploy-stage.png diff --git a/doc/user/project/clusters/serverless/img/dns-entry.png b/doc/user/project/clusters/serverless/img/dns-entry.png Binary files differindex 2e7655c6041..678743f256e 100644 --- a/doc/user/project/clusters/serverless/img/dns-entry.png +++ b/doc/user/project/clusters/serverless/img/dns-entry.png diff --git a/doc/user/project/clusters/serverless/img/function-execution.png b/doc/user/project/clusters/serverless/img/function-execution.png Binary files differnew file mode 100644 index 00000000000..93b0b6d802d --- /dev/null +++ b/doc/user/project/clusters/serverless/img/function-execution.png diff --git a/doc/user/project/clusters/serverless/img/install-knative.png b/doc/user/project/clusters/serverless/img/install-knative.png Binary files differindex a9fcc127240..93b1cbe602f 100644 --- a/doc/user/project/clusters/serverless/img/install-knative.png +++ b/doc/user/project/clusters/serverless/img/install-knative.png diff --git a/doc/user/project/clusters/serverless/img/knative-app.png b/doc/user/project/clusters/serverless/img/knative-app.png Binary files differindex 54301e1786f..931830d83ae 100644 --- a/doc/user/project/clusters/serverless/img/knative-app.png +++ b/doc/user/project/clusters/serverless/img/knative-app.png diff --git a/doc/user/project/clusters/serverless/img/serverless-page.png b/doc/user/project/clusters/serverless/img/serverless-page.png Binary files differindex 473ee801f10..960d6e736d6 100644 --- a/doc/user/project/clusters/serverless/img/serverless-page.png +++ b/doc/user/project/clusters/serverless/img/serverless-page.png diff --git a/doc/user/project/clusters/serverless/index.md b/doc/user/project/clusters/serverless/index.md index bdbc4f7f09d..a423212a879 100644 --- a/doc/user/project/clusters/serverless/index.md +++ b/doc/user/project/clusters/serverless/index.md @@ -1,6 +1,7 @@ # Serverless > Introduced in GitLab 11.5. +> Serverless is currently in [alpha](https://about.gitlab.com/handbook/product/#alpha). Run serverless workloads on Kubernetes using [Knative](https://cloud.google.com/knative/). @@ -8,104 +9,229 @@ Run serverless workloads on Kubernetes using [Knative](https://cloud.google.com/ Knative extends Kubernetes to provide a set of middleware components that are useful to build modern, source-centric, container-based applications. Knative brings some significant benefits out of the box through its main components: -- [Build:](https://github.com/knative/build) Source-to-container build orchestration -- [Eventing:](https://github.com/knative/eventing) Management and delivery of events -- [Serving:](https://github.com/knative/serving) Request-driven compute that can scale to zero +- [Build](https://github.com/knative/build): Source-to-container build orchestration. +- [Eventing](https://github.com/knative/eventing): Management and delivery of events. +- [Serving](https://github.com/knative/serving): Request-driven compute that can scale to zero. For more information on Knative, visit the [Knative docs repo](https://github.com/knative/docs). +With GitLab serverless, you can deploy both functions-as-a-service (FaaS) and serverless applications. + ## Requirements To run Knative on Gitlab, you will need: -1. **Kubernetes:** An RBAC-enabled Kubernetes cluster is required to deploy Knative. - The simplest way to get started is to add a cluster using [GitLab's GKE integration](https://docs.gitlab.com/ee/user/project/clusters/#adding-and-creating-a-new-gke-cluster-via-gitlab). - GitLab recommends -1. **Helm Tiller:** Helm is a package manager for Kubernetes and is required to install - all the other applications. -1. **Domain Name:** Knative will provide its own load balancer using Istio. It will provide an - external IP address for all the applications served by Knative. You will be prompted to enter a - wildcard domain where your applications will be served. Configure your DNS server to use the +1. **Kubernetes Cluster:** An RBAC-enabled Kubernetes cluster is required to deploy Knative. + The simplest way to get started is to add a cluster using [GitLab's GKE integration](../index.md#adding-and-creating-a-new-gke-cluster-via-gitlab). +1. **Helm Tiller:** Helm is a package manager for Kubernetes and is required to install + Knative. +1. **Domain Name:** Knative will provide its own load balancer using Istio. It will provide an + external IP address for all the applications served by Knative. You will be prompted to enter a + wildcard domain where your applications will be served. Configure your DNS server to use the external IP address for that domain. -1. **Serverless `gitlab-ci.yml` Template:** GitLab uses [Kaniko](https://github.com/GoogleContainerTools/kaniko) - to build the application and the [TriggerMesh CLI](https://github.com/triggermesh/tm), to simplify the +1. **`.gitlab-ci.yml`:** GitLab uses [Kaniko](https://github.com/GoogleContainerTools/kaniko) + to build the application and the [TriggerMesh CLI](https://github.com/triggermesh/tm) to simplify the deployment of knative services and functions. - - Add the following `.gitlab-ci.yml` to the root of your repository (you may skip this step if using the sample - [Knative Ruby App](https://gitlab.com/knative-examples/knative-ruby-app) mentioned below). - - ```yaml - stages: - - build - - deploy - - build: - stage: build - image: - name: gcr.io/kaniko-project/executor:debug - entrypoint: [""] - only: - - master - script: - - echo "{\"auths\":{\"$CI_REGISTRY\":{\"username\":\"$CI_REGISTRY_USER\",\"password\":\"$CI_REGISTRY_PASSWORD\"}}}" > /kaniko/.docker/config.json - - /kaniko/executor --context $CI_PROJECT_DIR --dockerfile $CI_PROJECT_DIR/Dockerfile --destination $CI_REGISTRY_IMAGE - - deploy: - stage: deploy - image: gcr.io/triggermesh/tm@sha256:e3ee74db94d215bd297738d93577481f3e4db38013326c90d57f873df7ab41d5 - only: - - master - environment: production - script: - - echo "$CI_REGISTRY_IMAGE" - - tm -n "$KUBE_NAMESPACE" --config "$KUBECONFIG" deploy service "$CI_PROJECT_NAME" --from-image "$CI_REGISTRY_IMAGE" --wait - ``` - -1. **Dockerfile:** Knative requires a Dockerfile in order to build your application. It should be included - at the root of your project's repo and expose port 8080. +1. **`serverless.yaml`** (for [functions only](#deploying-functions)): When using serverless to deploy functions, the `serverless.yaml` file + will contain the information for all the functions being hosted in the repository as well as a reference to the + runtime being used. +1. **`Dockerfile`** (for [applications only](#deploying-serverless-applications): Knative requires a `Dockerfile` in order to build your application. It should be included + at the root of your project's repo and expose port `8080`. ## Installing Knative via GitLab's Kubernetes integration NOTE: **Note:** -Minimum recommended cluster size to run Knative is 3-nodes, 6 vCPUs, and 22.50 GB memory. RBAC must be enabled. - -You may download the sample [Knative Ruby App](https://gitlab.com/knative-examples/knative-ruby-app) to get started. +The minimum recommended cluster size to run Knative is 3-nodes, 6 vCPUs, and 22.50 GB memory. **RBAC must be enabled.** -1. [Add a Kubernetes cluster](https://docs.gitlab.com/ce/user/project/clusters/) and install Helm. - -1. Once Helm has been successfully installed, on the Knative app section, enter the domain to be used with +1. [Add a Kubernetes cluster](../index.md) and install Helm. +1. Once Helm has been successfully installed, on the Knative app section, enter the domain to be used with your application and click "Install". ![install-knative](img/install-knative.png) -1. After the Knative installation has finished, retrieve the Istio Ingress IP address by running the following command: +1. After the Knative installation has finished, you can wait for the IP address to be displayed in the + **Knative IP Address** field or retrieve the Istio Ingress IP address by running the following command: - ```bash - kubectl get svc --namespace=istio-system knative-ingressgateway -o jsonpath='{.status.loadBalancer.ingress[0].ip} ' - ``` + ```bash + kubectl get svc --namespace=istio-system knative-ingressgateway -o jsonpath='{.status.loadBalancer.ingress[0].ip} ' + ``` - Output: + Output: - ```bash - 35.161.143.124 my-machine-name:~ my-user$ - ``` + ```bash + 35.161.143.124 my-machine-name:~ my-user$ + ``` -1. The ingress is now available at this address and will route incoming requests to the proper service based on the DNS - name in the request. To support this, a wildcard DNS A record should be created for the desired domain name. For example, - if your Knative base domain is `knative.example.com` then you need to create an A record with domain `*.knative.example.com` - pointing the ip address of the ingress. +1. The ingress is now available at this address and will route incoming requests to the proper service based on the DNS + name in the request. To support this, a wildcard DNS A record should be created for the desired domain name. For example, + if your Knative base domain is `knative.info` then you need to create an A record with domain `*.knative.info` + pointing the ip address of the ingress. ![dns entry](img/dns-entry.png) +## Deploying Functions + +> Introduced in GitLab 11.6. + +Using functions is useful for dealing with independent +events without needing to maintain a complex unified infrastructure. This allows +you to focus on a single task that can be executed/scaled automatically and independently. + +Currently the following [runtimes](https://gitlab.com/triggermesh/runtimes) are offered: + +- node.js +- kaniko + +In order to deploy functions to your Knative instance, the following files must be present: + +1. `.gitlab-ci.yml`: This template allows to define the stage, environment, and + image to be used for your functions. It must be included at the root of your repository: + + ```yaml + stages: + - deploy + + functions: + stage: deploy + environment: test + image: gcr.io/triggermesh/tm:v0.0.7 + script: + - tm -n "$KUBE_NAMESPACE" set registry-auth gitlab-registry --registry "$CI_REGISTRY" --username "$CI_REGISTRY_USER" --password "$CI_JOB_TOKEN" + - tm -n "$KUBE_NAMESPACE" --registry-host "$CI_REGISTRY_IMAGE" deploy --wait + ``` + + The `gitlab-ci.yml` template creates a `Deploy` stage with a `functions` job that invokes the `tm` CLI with the required parameters. + +2. `serverless.yaml`: This file contains the metadata for your functions, + such as name, runtime, and environment. It must be included at the root of your repository. The following is a sample `echo` function which shows the required structure for the file. + + NOTE: **Note:** + The file extension for the `serverless.yaml` file must be specified as `.yaml` in order to the file to be parsed properly. Specifying the extension as `.yml` will not work. + + ```yaml + service: my-functions + description: "Deploying functions from GitLab using Knative" + + provider: + name: triggermesh + registry-secret: gitlab-registry + environment: + FOO: BAR + + functions: + echo: + handler: echo + runtime: https://gitlab.com/triggermesh/runtimes/raw/master/nodejs.yaml + description: "echo function using node.js runtime" + buildargs: + - DIRECTORY=echo + environment: + FUNCTION: echo + ``` + + +The `serverless.yaml` file contains three sections with distinct parameters: + +### `service` + +| Parameter | Description | +|-----------|-------------| +| `service` | Name for the Knative service which will serve the function. | +| `description` | A short description of the `service`. | + + +### `provider` + +| Parameter | Description | +|-----------|-------------| +| `name` | Indicates which provider is used to execute the `serverless.yaml` file. In this case, the TriggerMesh `tm` CLI. | +| `registry-secret` | Indicates which registry will be used to store docker images. The sample function is using the GitLab Registry (`gitlab-registry`). A different registry host may be specified using `registry` key in the `provider` object. If changing the default, update the permission and the secret value on the `gitlab-ci.yml` file | +| `environment` | Includes the environment variables to be passed as part of function execution for **all** functions in the file, where `FOO` is the variable name and `BAR` are he variable contents. You may replace this with you own variables. | + +### `functions` + +In the `serverless.yaml` example above, the function name is `echo` and the subsequent lines contain the function attributes. + + +| Parameter | Description | +|-----------|-------------| +| `handler` | The function's file name. In the example above, both the function name and the handler are the same. | +| `runtime` | The runtime to be used to execute the function. | +| `description` | A short description of the function. | +| `buildargs` | Pointer to the function file in the repo. In the sample the function is located in the `echo` directory. | +| `environment` | Sets an environment variable for the specific function only. | + +After the `gitlab-ci.yml` template has been added and the `serverless.yaml` file has been +created, each function must be defined as a single file in your repository. Committing a +function to your project will result in a +CI pipeline being executed which will deploy each function as a Knative service. +Once the deploy stage has finished, additional details for the function will +appear under **Operations > Serverless**. + +![serverless page](img/serverless-page.png) + +This page contains all functions available for the project, the URL for +accessing the function, and if available, the function's runtime information. +The details are derived from the Knative installation inside each of the project's +Kubernetes cluster. + +The function details can be retrieved directly from Knative on the cluster: + +```bash +kubectl -n "$KUBE_NAMESPACE" get services.serving.knative.dev +``` + +The sample function can now be triggered from any HTTP client using a simple `POST` call. + +![function exection](img/function-execution.png) + +Currently, the Serverless page presents all functions available in all clusters registered for the project with Knative installed. + +## Deploying Serverless applications + +> Introduced in GitLab 11.5. + +NOTE: **Note:** +You can reference the sample [Knative Ruby App](https://gitlab.com/knative-examples/knative-ruby-app) to get started. + +Add the following `.gitlab-ci.yml` to the root of your repository +(you may skip this step if using the sample [Knative Ruby App](https://gitlab.com/knative-examples/knative-ruby-app) mentioned above): + +```yaml +stages: + - build + - deploy + +build: + stage: build + image: + name: gcr.io/kaniko-project/executor:debug + entrypoint: [""] + only: + - master + script: + - echo "{\"auths\":{\"$CI_REGISTRY\":{\"username\":\"$CI_REGISTRY_USER\",\"password\":\"$CI_REGISTRY_PASSWORD\"}}}" > /kaniko/.docker/config.json + - /kaniko/executor --context $CI_PROJECT_DIR --dockerfile $CI_PROJECT_DIR/Dockerfile --destination $CI_REGISTRY_IMAGE + +deploy: + stage: deploy + image: gcr.io/triggermesh/tm@sha256:e3ee74db94d215bd297738d93577481f3e4db38013326c90d57f873df7ab41d5 + only: + - master + environment: production + script: + - echo "$CI_REGISTRY_IMAGE" + - tm -n "$KUBE_NAMESPACE" --config "$KUBECONFIG" deploy service "$CI_PROJECT_NAME" --from-image "$CI_REGISTRY_IMAGE" --wait +``` + ## Deploy the application with Knative -With all the pieces in place, you can simply create a new CI pipeline to deploy the Knative application. Navigate to -**CI/CD >> Pipelines** and click the **Run Pipeline** button at the upper-right part of the screen. Then, on the -Pipelines page, click **Create pipeline**. +With all the pieces in place, the next time a CI pipeline runs, the Knative application will be deployed. Navigate to +**CI/CD > Pipelines** and click the most recent pipeline. ## Obtain the URL for the Knative deployment -Once all the stages of the pipeline finish, click the **deploy** stage. +Use the CI/CD deployment job output to obtain the deployment URL. Once all the stages of the pipeline finish, click the **deploy** stage. ![deploy stage](img/deploy-stage.png) @@ -131,7 +257,7 @@ Service domain: knative.knative-4342902.knative.info Job succeeded ``` -The second to last line, labeled **Service domain** contains the URL for the deployment. Copy and paste the domain into your +The second to last line, labeled **Service domain** contains the URL for the deployment. Copy and paste the domain into your browser to see the app live. -![knative app](img/knative-app.png)
\ No newline at end of file +![knative app](img/knative-app.png) diff --git a/doc/user/project/cycle_analytics.md b/doc/user/project/cycle_analytics.md index ea843054f8e..b98221bea61 100644 --- a/doc/user/project/cycle_analytics.md +++ b/doc/user/project/cycle_analytics.md @@ -146,7 +146,8 @@ A few notes: The current permissions on the Cycle Analytics dashboard are: - Public projects - anyone can access -- Private/internal projects - any member (guest level and above) can access +- Internal projects - any authenticated user can access +- Private projects - any member Guest and above can access You can [read more about permissions][permissions] in general. diff --git a/doc/user/project/img/issue_board.png b/doc/user/project/img/issue_board.png Binary files differindex b46b995d8bb..b753593d212 100644 --- a/doc/user/project/img/issue_board.png +++ b/doc/user/project/img/issue_board.png diff --git a/doc/user/project/img/issue_board_summed_weights.png b/doc/user/project/img/issue_board_summed_weights.png Binary files differindex 2288d767d8c..6035d7ca330 100644 --- a/doc/user/project/img/issue_board_summed_weights.png +++ b/doc/user/project/img/issue_board_summed_weights.png diff --git a/doc/user/project/img/issue_boards_core.png b/doc/user/project/img/issue_boards_core.png Binary files differindex 8bc187482ad..41ddbb24b14 100644 --- a/doc/user/project/img/issue_boards_core.png +++ b/doc/user/project/img/issue_boards_core.png diff --git a/doc/user/project/img/issue_boards_premium.png b/doc/user/project/img/issue_boards_premium.png Binary files differindex 4e238ea6983..ef9f5bbea32 100644 --- a/doc/user/project/img/issue_boards_premium.png +++ b/doc/user/project/img/issue_boards_premium.png diff --git a/doc/user/project/img/releases.png b/doc/user/project/img/releases.png Binary files differnew file mode 100644 index 00000000000..aec1db89a75 --- /dev/null +++ b/doc/user/project/img/releases.png diff --git a/doc/user/project/integrations/img/jira_api_token.png b/doc/user/project/integrations/img/jira_api_token.png Binary files differindex 2c64f7bc44f..4fa7a46854e 100644 --- a/doc/user/project/integrations/img/jira_api_token.png +++ b/doc/user/project/integrations/img/jira_api_token.png diff --git a/doc/user/project/integrations/img/jira_api_token_menu.png b/doc/user/project/integrations/img/jira_api_token_menu.png Binary files differindex 20655ba3c0e..55c8fb1bdb9 100644 --- a/doc/user/project/integrations/img/jira_api_token_menu.png +++ b/doc/user/project/integrations/img/jira_api_token_menu.png diff --git a/doc/user/project/integrations/img/jira_service_page.png b/doc/user/project/integrations/img/jira_service_page.png Binary files differindex 869d562ed5b..3a27b4df841 100644 --- a/doc/user/project/integrations/img/jira_service_page.png +++ b/doc/user/project/integrations/img/jira_service_page.png diff --git a/doc/user/project/integrations/prometheus.md b/doc/user/project/integrations/prometheus.md index 7d0e567cae7..5de8e66e7eb 100644 --- a/doc/user/project/integrations/prometheus.md +++ b/doc/user/project/integrations/prometheus.md @@ -30,7 +30,7 @@ GitLab can seamlessly deploy and manage Prometheus on a [connected Kubernetes cl Once you have a connected Kubernetes cluster with Helm installed, deploying a managed Prometheus is as easy as a single click. -1. Go to the `Operations > Kubernetes` page, to view your connected clusters +1. Go to the **Operations > Kubernetes** page to view your connected clusters 1. Select the cluster you would like to deploy Prometheus to 1. Click the **Install** button to deploy Prometheus to the cluster @@ -41,6 +41,7 @@ Once you have a connected Kubernetes cluster with Helm installed, deploying a ma Prometheus is deployed into the `gitlab-managed-apps` namespace, using the [official Helm chart](https://github.com/kubernetes/charts/tree/master/stable/prometheus). Prometheus is only accessible within the cluster, with GitLab communicating through the [Kubernetes API](https://kubernetes.io/docs/concepts/overview/kubernetes-api/). The Prometheus server will [automatically detect and monitor](https://prometheus.io/docs/prometheus/latest/configuration/configuration/#%3Ckubernetes_sd_config%3E) nodes, pods, and endpoints. To configure a resource to be monitored by Prometheus, simply set the following [Kubernetes annotations](https://kubernetes.io/docs/concepts/overview/working-with-objects/annotations/): + * `prometheus.io/scrape` to `true` to enable monitoring of the resource. * `prometheus.io/port` to define the port of the metrics endpoint. * `prometheus.io/path` to define the path of the metrics endpoint. Defaults to `/metrics`. @@ -87,7 +88,7 @@ to integrate with. Once configured, GitLab will attempt to retrieve performance metrics for any environment which has had a successful deployment. -GitLab will automatically scan the Prometheus server for metrics from known serves like Kubernetes and NGINX, and attempt to identify individual environment. The supported metrics and scan process is detailed in our [Prometheus Metric Library documentation](prometheus_library/metrics.html). +GitLab will automatically scan the Prometheus server for metrics from known serves like Kubernetes and NGINX, and attempt to identify individual environment. The supported metrics and scan process is detailed in our [Prometheus Metric Library documentation](prometheus_library/index.md). You can view the performance dashboard for an environment by [clicking on the monitoring button](../../../ci/environments.md#monitoring-environments). diff --git a/doc/user/project/issues/img/issue_board.png b/doc/user/project/issues/img/issue_board.png Binary files differindex c75c35a382e..dd40740aec5 100644 --- a/doc/user/project/issues/img/issue_board.png +++ b/doc/user/project/issues/img/issue_board.png diff --git a/doc/user/project/issues/img/similar_issues.png b/doc/user/project/issues/img/similar_issues.png Binary files differindex 153430d4be7..0dfb5b00e02 100644 --- a/doc/user/project/issues/img/similar_issues.png +++ b/doc/user/project/issues/img/similar_issues.png diff --git a/doc/user/project/merge_requests/img/comment-on-any-diff-line.png b/doc/user/project/merge_requests/img/comment-on-any-diff-line.png Binary files differindex 856ede41527..c2455c2d1e5 100644 --- a/doc/user/project/merge_requests/img/comment-on-any-diff-line.png +++ b/doc/user/project/merge_requests/img/comment-on-any-diff-line.png diff --git a/doc/user/project/merge_requests/img/filter_wip_merge_requests.png b/doc/user/project/merge_requests/img/filter_wip_merge_requests.png Binary files differindex 40913718385..81878709487 100644 --- a/doc/user/project/merge_requests/img/filter_wip_merge_requests.png +++ b/doc/user/project/merge_requests/img/filter_wip_merge_requests.png diff --git a/doc/user/project/merge_requests/img/merge_request_pipeline.png b/doc/user/project/merge_requests/img/merge_request_pipeline.png Binary files differindex 183d9cb910b..ce1d6bab536 100644 --- a/doc/user/project/merge_requests/img/merge_request_pipeline.png +++ b/doc/user/project/merge_requests/img/merge_request_pipeline.png diff --git a/doc/user/project/merge_requests/index.md b/doc/user/project/merge_requests/index.md index 85d8d804133..d4f8cf929f6 100644 --- a/doc/user/project/merge_requests/index.md +++ b/doc/user/project/merge_requests/index.md @@ -150,6 +150,16 @@ in a Merge Request. To do so, click the **...** button in the gutter of the Merg ![Comment on any diff file line](img/comment-on-any-diff-line.png) +## Suggest changes + +> [Introduced](https://gitlab.com/gitlab-org/gitlab-ce/issues/18008) in GitLab 11.6. + +As a reviewer, you can add suggestions to change the content in +merge request discussions, and users with appropriate [permission](../../permissions.md) +can easily apply them to the codebase directly from the UI. Read +through the documentation on [Suggest changes](../../discussions/index.md#suggest-changes) +to learn more. + ## Resolve conflicts When a merge request has conflicts, GitLab may provide the option to resolve diff --git a/doc/user/project/pages/getting_started_part_three.md b/doc/user/project/pages/getting_started_part_three.md index 247e8d2a6a0..cea9628966d 100644 --- a/doc/user/project/pages/getting_started_part_three.md +++ b/doc/user/project/pages/getting_started_part_three.md @@ -234,26 +234,25 @@ reiterating the importance of HTTPS. ## Issuing Certificates -GitLab Pages accepts [PEM](https://support.quovadisglobal.com/kb/a37/what-is-pem-format.aspx) certificates issued by -[Certificate Authorities (CA)](https://en.wikipedia.org/wiki/Certificate_authority) -and self-signed certificates. Of course, -[you'd rather issue a certificate than generate a self-signed](https://en.wikipedia.org/wiki/Self-signed_certificate), -for security reasons and for having browsers trusting your -site's certificate. - -There are several different kinds of certificates, each one -with certain security level. A static personal website will +GitLab Pages accepts certificates provided in the [PEM](https://support.quovadisglobal.com/kb/a37/what-is-pem-format.aspx) format, issued by +[Certificate Authorities (CAs)](https://en.wikipedia.org/wiki/Certificate_authority) or as +[self-signed certificates](https://en.wikipedia.org/wiki/Self-signed_certificate). Note that [self-signed certificates are typically not used](https://securingtomorrow.mcafee.com/other-blogs/mcafee-labs/self-signed-certificates-secure-so-why-ban/) +for public websites for security reasons and to ensure that browsers trust your site's certificate. + +There are various kinds of certificates, each one +with a certain security level. A static personal website will not require the same security level as an online banking web app, -for instance. There are a couple Certificate Authorities that +for instance. + +There are some certificate authorities that offer free certificates, aiming to make the internet more secure to everyone. The most popular is [Let's Encrypt](https://letsencrypt.org/), which issues certificates trusted by most of browsers, it's open -source, and free to use. Please read through this tutorial to -understand [how to secure your GitLab Pages website with Let's Encrypt](https://about.gitlab.com/2016/04/11/tutorial-securing-your-gitlab-pages-with-tls-and-letsencrypt/). +source, and free to use. See our tutorial on [how to secure your GitLab Pages website with Let's Encrypt](lets_encrypt_for_gitlab_pages.md). -With the same popularity, there are [certificates issued by CloudFlare](https://www.cloudflare.com/ssl/), +Similarly popular are [certificates issued by CloudFlare](https://www.cloudflare.com/ssl/), which also offers a [free CDN service](https://blog.cloudflare.com/cloudflares-free-cdn-and-you/). -Their certs are valid up to 15 years. Read through the tutorial on +Their certs are valid up to 15 years. See the tutorial on [how to add a CloudFlare Certificate to your GitLab Pages website](https://about.gitlab.com/2017/02/07/setting-up-gitlab-pages-with-cloudflare-certificates/). ### Adding certificates to your project diff --git a/doc/user/project/pages/img/icons/click.png b/doc/user/project/pages/img/icons/click.png Binary files differindex daaf760ec08..a534ae29e0f 100644 --- a/doc/user/project/pages/img/icons/click.png +++ b/doc/user/project/pages/img/icons/click.png diff --git a/doc/user/project/pages/img/icons/cogs.png b/doc/user/project/pages/img/icons/cogs.png Binary files differindex a12da1b5e8c..f37f8f361d1 100644 --- a/doc/user/project/pages/img/icons/cogs.png +++ b/doc/user/project/pages/img/icons/cogs.png diff --git a/doc/user/project/pages/img/icons/fork.png b/doc/user/project/pages/img/icons/fork.png Binary files differindex e2c9577e7ce..8a3aa46eb37 100644 --- a/doc/user/project/pages/img/icons/fork.png +++ b/doc/user/project/pages/img/icons/fork.png diff --git a/doc/user/project/pages/img/icons/free.png b/doc/user/project/pages/img/icons/free.png Binary files differindex 3b8f8f6863e..ae455033e94 100644 --- a/doc/user/project/pages/img/icons/free.png +++ b/doc/user/project/pages/img/icons/free.png diff --git a/doc/user/project/pages/img/icons/lock.png b/doc/user/project/pages/img/icons/lock.png Binary files differindex 1c1f0b4457b..f4c35c84112 100644 --- a/doc/user/project/pages/img/icons/lock.png +++ b/doc/user/project/pages/img/icons/lock.png diff --git a/doc/user/project/pages/img/icons/monitor.png b/doc/user/project/pages/img/icons/monitor.png Binary files differindex 7b99d430eef..8bad059a74c 100644 --- a/doc/user/project/pages/img/icons/monitor.png +++ b/doc/user/project/pages/img/icons/monitor.png diff --git a/doc/user/project/pages/img/icons/terminal.png b/doc/user/project/pages/img/icons/terminal.png Binary files differindex ab5ae11310c..377eeb4edc6 100644 --- a/doc/user/project/pages/img/icons/terminal.png +++ b/doc/user/project/pages/img/icons/terminal.png diff --git a/doc/user/project/pages/index.md b/doc/user/project/pages/index.md index 7de9ae0caea..ce4fccdaff3 100644 --- a/doc/user/project/pages/index.md +++ b/doc/user/project/pages/index.md @@ -170,7 +170,7 @@ optionally secure it with SSL/TLS certificates. You can read the following tutorials to learn how to use these third-party certificates with GitLab Pages: - [CloudFlare](https://about.gitlab.com/2017/02/07/setting-up-gitlab-pages-with-cloudflare-certificates/) -- [Let's Encrypt](https://about.gitlab.com/2016/04/11/tutorial-securing-your-gitlab-pages-with-tls-and-letsencrypt/) (mind that although this article is out-of-date, it can still be useful to guide you through the basic steps) +- [Let's Encrypt](lets_encrypt_for_gitlab_pages.md) ## Advanced use diff --git a/doc/user/project/pages/lets_encrypt_for_gitlab_pages.md b/doc/user/project/pages/lets_encrypt_for_gitlab_pages.md new file mode 100644 index 00000000000..f639188684b --- /dev/null +++ b/doc/user/project/pages/lets_encrypt_for_gitlab_pages.md @@ -0,0 +1,158 @@ +--- +description: "How to secure GitLab Pages websites with Let's Encrypt." +--- + +# Let's Encrypt for GitLab Pages + +If you have a GitLab Pages website served under your own domain, +you might want to secure it with a SSL/TSL certificate. + +[Let's Encrypt](https://letsencrypt.org) is a free, automated, and +open source Certificate Authority. + +## Requirements + +To follow along with this tutorial, we assume you already have: + +- Created a [project](getting_started_part_two.md) in GitLab which + contains your website's source code. +- Acquired a domain (`example.com`) and added a [DNS entry](getting_started_part_three.md#dns-records) + pointing it to your Pages website. +- [Added your domain to your Pages project](getting_started_part_three.md#add-your-custom-domain-to-gitlab-pages-settings) + and verified your ownership. +- Cloned your project into your computer. +- Your website up and running, served under HTTP protocol at `http://example.com`. + +## Obtaining a Let's Encrypt certificate + +Once you have the requirements addressed, follow the instructions +below to learn how to obtain the certificate. + +NOTE: **Note:** +The instructions below were tested on macOS Mojave. For other +operating systems the steps might be slightly different. Follow the +[CertBot instructions](https://certbot.eff.org/) according to your OS. + +1. On your computer, open a terminal and navigate to your repository's + root directory: + + ```bash + cd path/to/dir + ``` + +1. Install CertBot (the tool Let's Encrypt uses to issue certificates): + + ```bash + brew install certbot + ``` + +1. Request a certificate for your domain (`example.com`) and + provide an email account (`your@email.com`) to receive notifications: + + ```bash + sudo certbot certonly -a manual -d example.com --email your@email.com + ``` + + Alternatively, you can register without adding an e-mail account, + but you won't be notified about the certificate expiration's date: + + ```bash + sudo certbot certonly -a manual -d example.com --register-unsafely-without-email + ``` + + TIP: **Tip:** + Read through CertBot's documentation on their + [command line options](https://certbot.eff.org/docs/using.html#certbot-command-line-options). + +1. You'll be prompted with a message to agree with their terms. + Press `A` to agree and `Y` to let they log your IP. + + CertBot will then prompt you with the following message: + + ```bash + Create a file containing just this data: + + Rxnv6WKo95hsuLVX3osmT6LgmzsJKSaK9htlPToohOP.HUGNKk82jlsmOOfphlt8Jy69iuglsn095nxOMH9j3Yb + + And make it available on your web server at this URL: + + http://example.com/.well-known/acme-challenge/Rxnv6WKo95hsuLVX3osmT6LgmzsJKSaK9htlPToohOP + + Press Enter to Continue + ``` + +1. **Do not press Enter yet.** Let's Encrypt will need to verify your + domain ownership before issuing the certificate. To do so, create 3 + consecutive directories under your website's root: + `/.well-known/acme-challenge/Rxnv6WKo95hsuLVX3osmT6LgmzsJKSaK9htlPToohOP/` + and add to the last folder an `index.html` file containing the content + referred on the previous prompt message: + + ```bash + Rxnv6WKo95hsuLVX3osmT6LgmzsJKSaK9htlPToohOP.HUGNKk82jlsmOOfphlt8Jy69iuglsn095nxOMH9j3Yb + ``` + + Note that this file needs to be accessed under + `http://example.com/.well-known/acme-challenge/Rxnv6WKo95hsuLVX3osmT6LgmzsJKSaK9htlPToohOP` + to allow Let's Encrypt to verify the ownership of your domain, + therefore, it needs to be part of the website content under the + repo's [`public`](index.md#how-it-works) folder. + +1. Add, commit, and push the file into your repo in GitLab. Once the pipeline + passes, press **Enter** on your terminal to continue issuing your + certificate. CertBot will then prompt you with the following message: + + ```bash + Waiting for verification... + Cleaning up challenges + + IMPORTANT NOTES: + - Congratulations! Your certificate and chain have been saved at: + /etc/letsencrypt/live/example.com/fullchain.pem + Your key file has been saved at: + /etc/letsencrypt/live/example.com/privkey.pem + Your cert will expire on 2019-03-12. To obtain a new or tweaked + version of this certificate in the future, simply run certbot + again. To non-interactively renew *all* of your certificates, run + "certbot renew" + - If you like Certbot, please consider supporting our work by: + + Donating to ISRG / Let's Encrypt: https://letsencrypt.org/donate + Donating to EFF: https://eff.org/donate-le + ``` + +## Add your certificate to GitLab Pages + +Now that your certificate has been issued, let's add it to your Pages site: + +1. Back at GitLab, navigate to your project's **Settings > Pages**, + find your domain and click **Details** and **Edit** to add your certificate. +1. From your terminal, copy and paste the certificate into the first field + **Certificate (PEM)**: + + ```bash + sudo cat /etc/letsencrypt/live/example.com/fullchain.pem | pbcopy + ``` + +1. Copy and paste the public key into the second field **Key (PEM)**: + + ```bash + sudo cat /etc/letsencrypt/live/example.com/privkey.pem | pbcopy + ``` + +1. Click **Save changes** to apply them to your website. +1. Wait a few minutes for DNS propagation. +1. Visit your website at `https://example.com`. + +To force `https` connections on your site, navigate to your +project's **Settings > Pages** and check **Force domains with SSL +certificates to use HTTPS**. + +## Renewal + +Let's Encrypt certificates expire every 90 days and you'll have to +renew them periodically. To renew all your certificates at once, run: + +```bash +sudo certbot renew +``` diff --git a/doc/user/project/releases.md b/doc/user/project/releases.md new file mode 100644 index 00000000000..8dad4240c91 --- /dev/null +++ b/doc/user/project/releases.md @@ -0,0 +1,12 @@ +# Releases + +> [Introduced](https://gitlab.com/gitlab-org/gitlab-ce/issues/41766) in GitLab 11.7. + +Releases mark specific points in a project's development history, communicate +information about the type of change, and deliver on prepared, often compiled, +versions of the software to be reused elsewhere. Currently, releases can only be +created through the API. + +Navigate to **Project > Releases** in order to see the list of releases of a project. + +![Releases list](img/releases.png) diff --git a/doc/user/project/repository/branches/img/branch_filter_search_box.png b/doc/user/project/repository/branches/img/branch_filter_search_box.png Binary files differindex c4364ef39f4..5dc7eccf189 100644 --- a/doc/user/project/repository/branches/img/branch_filter_search_box.png +++ b/doc/user/project/repository/branches/img/branch_filter_search_box.png diff --git a/doc/user/project/repository/img/repository_cleanup.png b/doc/user/project/repository/img/repository_cleanup.png Binary files differindex 2749392ffa4..bda40d3e193 100644 --- a/doc/user/project/repository/img/repository_cleanup.png +++ b/doc/user/project/repository/img/repository_cleanup.png diff --git a/doc/user/project/repository/index.md b/doc/user/project/repository/index.md index 1710bba2fd0..fac5975a0dc 100644 --- a/doc/user/project/repository/index.md +++ b/doc/user/project/repository/index.md @@ -29,7 +29,7 @@ You can either use the user interface (UI), or connect your local computer with GitLab [through the command line](../../../gitlab-basics/command-line-commands.md#start-working-on-your-project). To configure [GitLab CI/CD](../../../ci/README.md) to build, test, and deploy -you code, add a file called [.`gitlab-ci.yml`](../../../ci/quick_start/README.md) +you code, add a file called [`.gitlab-ci.yml`](../../../ci/quick_start/README.md) to your repository's root. **From the user interface:** diff --git a/doc/user/project/settings/import_export.md b/doc/user/project/settings/import_export.md index cb68c9318bc..3bbfa74f4b7 100644 --- a/doc/user/project/settings/import_export.md +++ b/doc/user/project/settings/import_export.md @@ -71,6 +71,7 @@ The following items will NOT be exported: - Build traces and artifacts - Container registry images - CI variables +- Webhooks - Any encrypted tokens ## Exporting a project and its data diff --git a/doc/user/search/img/dashboard_links.png b/doc/user/search/img/dashboard_links.png Binary files differindex 2c472c7e464..d784ba8018e 100644 --- a/doc/user/search/img/dashboard_links.png +++ b/doc/user/search/img/dashboard_links.png diff --git a/doc/user/search/img/issues_assigned_to_you.png b/doc/user/search/img/issues_assigned_to_you.png Binary files differindex d2fff5e9a67..55986eedcba 100644 --- a/doc/user/search/img/issues_assigned_to_you.png +++ b/doc/user/search/img/issues_assigned_to_you.png diff --git a/doc/user/snippets.md b/doc/user/snippets.md index 7efb6bafee7..5c9f6ffb163 100644 --- a/doc/user/snippets.md +++ b/doc/user/snippets.md @@ -15,7 +15,7 @@ and private. See [Public access](../public_access/public_access.md) for more inf ## Project snippets Project snippets are always related to a specific project. -See [Project's features](project/index.md#project-39-s-features) for more information. +See [Project's features](project/index.md#projects-features) for more information. ## Discover snippets diff --git a/doc/workflow/img/repository_mirroring_force_update.png b/doc/workflow/img/repository_mirroring_force_update.png Binary files differindex 8ba715d1ba3..1e6dcb9ea08 100644 --- a/doc/workflow/img/repository_mirroring_force_update.png +++ b/doc/workflow/img/repository_mirroring_force_update.png diff --git a/doc/workflow/notifications.md b/doc/workflow/notifications.md index 020aa73f809..6ce789998a4 100644 --- a/doc/workflow/notifications.md +++ b/doc/workflow/notifications.md @@ -135,6 +135,7 @@ Notification emails include headers that provide extra content about the notific | X-GitLab-Pipeline-Id | Only in pipeline emails, the ID of the pipeline the notification is for | | X-GitLab-Reply-Key | A unique token to support reply by email | | X-GitLab-NotificationReason | The reason for being notified. "mentioned", "assigned", etc | +| List-Id | The path of the project in a RFC 2919 mailing list identifier useful for email organization, for example, with GMail filters | #### X-GitLab-NotificationReason diff --git a/doc/workflow/todos.md b/doc/workflow/todos.md index f94d592d0db..830f17aa7f2 100644 --- a/doc/workflow/todos.md +++ b/doc/workflow/todos.md @@ -35,6 +35,9 @@ A Todo appears in your Todos dashboard when: - the author, or - have set it to automatically merge once pipeline succeeds. +NOTE: **Note:** +When an user no longer has access to a resource related to a Todo like an issue, merge request, project or group the related Todos, for security reasons, gets deleted within the next hour. The delete is delayed to prevent data loss in case user got their access revoked by mistake. + ### Directly addressed Todos > [Introduced][ce-7926] in GitLab 9.0. diff --git a/lib/api/api.rb b/lib/api/api.rb index 8abb24e6f69..19da0b2c434 100644 --- a/lib/api/api.rb +++ b/lib/api/api.rb @@ -139,6 +139,7 @@ module API mount ::API::ProjectTemplates mount ::API::ProtectedBranches mount ::API::ProtectedTags + mount ::API::Releases mount ::API::Repositories mount ::API::Runner mount ::API::Runners @@ -149,6 +150,7 @@ module API mount ::API::Snippets mount ::API::Submodules mount ::API::Subscriptions + mount ::API::Suggestions mount ::API::SystemHooks mount ::API::Tags mount ::API::Templates diff --git a/lib/api/branches.rb b/lib/api/branches.rb index e7e58ad0e66..07f529b01bb 100644 --- a/lib/api/branches.rb +++ b/lib/api/branches.rb @@ -34,11 +34,11 @@ module API repository = user_project.repository branches = BranchesFinder.new(repository, declared_params(include_missing: false)).execute - + branches = ::Kaminari.paginate_array(branches) merged_branch_names = repository.merged_branch_names(branches.map(&:name)) present( - paginate(::Kaminari.paginate_array(branches)), + paginate(branches), with: Entities::Branch, current_user: current_user, project: user_project, diff --git a/lib/api/commit_statuses.rb b/lib/api/commit_statuses.rb index 62c966e06b4..08b4f8db8b0 100644 --- a/lib/api/commit_statuses.rb +++ b/lib/api/commit_statuses.rb @@ -116,7 +116,7 @@ module API end MergeRequest.where(source_project: @project, source_branch: ref) - .update_all(head_pipeline_id: pipeline) if pipeline.latest? + .update_all(head_pipeline_id: pipeline.id) if pipeline.latest? present status, with: Entities::CommitStatus rescue StateMachines::InvalidTransition => e diff --git a/lib/api/entities.rb b/lib/api/entities.rb index 5dbfbb85e9e..7116ab2882b 100644 --- a/lib/api/entities.rb +++ b/lib/api/entities.rb @@ -323,7 +323,7 @@ module API expose :request_access_enabled expose :full_name, :full_path - if ::Group.supports_nested_groups? + if ::Group.supports_nested_objects? expose :parent_id end @@ -1087,11 +1087,20 @@ module API expose :password_authentication_enabled_for_web, as: :signin_enabled end - class Release < Grape::Entity + # deprecated old Release representation + class TagRelease < Grape::Entity expose :tag, as: :tag_name expose :description end + class Release < TagRelease + expose :name + expose :description_html + expose :created_at + expose :author, using: Entities::UserBasic, if: -> (release, _) { release.author.present? } + expose :commit, using: Entities::Commit + end + class Tag < Grape::Entity expose :name, :message, :target @@ -1100,7 +1109,7 @@ module API end # rubocop: disable CodeReuse/ActiveRecord - expose :release, using: Entities::Release do |repo_tag, options| + expose :release, using: Entities::TagRelease do |repo_tag, options| options[:project].releases.find_by(tag: repo_tag.name) end # rubocop: enable CodeReuse/ActiveRecord @@ -1495,5 +1504,17 @@ module API expose :label, using: Entities::LabelBasic expose :action end + + class Suggestion < Grape::Entity + expose :id + expose :from_original_line + expose :to_original_line + expose :from_line + expose :to_line + expose :appliable?, as: :appliable + expose :applied + expose :from_content + expose :to_content + end end end diff --git a/lib/api/events.rb b/lib/api/events.rb index 44dae57770d..b98aa9f31e1 100644 --- a/lib/api/events.rb +++ b/lib/api/events.rb @@ -18,29 +18,15 @@ module API desc: 'Return events sorted in ascending and descending order' end - RedactedEvent = OpenStruct.new(target_title: 'Confidential event').freeze - - def redact_events(events) - events.map do |event| - if event.visible_to_user?(current_user) - event - else - RedactedEvent - end - end - end - - # rubocop: disable CodeReuse/ActiveRecord - def present_events(events, redact: true) - events = events.reorder(created_at: params[:sort]) - .with_associations - + def present_events(events) events = paginate(events) - events = redact_events(events) if redact present events, with: Entities::Event end - # rubocop: enable CodeReuse/ActiveRecord + + def find_events(source) + EventsFinder.new(params.merge(source: source, current_user: current_user, with_associations: true)).execute + end end resource :events do @@ -55,16 +41,14 @@ module API use :event_filter_params use :sort_params end - # rubocop: disable CodeReuse/ActiveRecord + get do authenticate! - events = EventsFinder.new(params.merge(source: current_user, current_user: current_user)).execute.preload(:author, :target) + events = find_events(current_user) - # Since we're viewing our own events, redaction is unnecessary - present_events(events, redact: false) + present_events(events) end - # rubocop: enable CodeReuse/ActiveRecord end params do @@ -82,16 +66,15 @@ module API use :event_filter_params use :sort_params end - # rubocop: disable CodeReuse/ActiveRecord + get ':id/events' do user = find_user(params[:id]) not_found!('User') unless user - events = EventsFinder.new(params.merge(source: user, current_user: current_user)).execute.preload(:author, :target) + events = find_events(user) present_events(events) end - # rubocop: enable CodeReuse/ActiveRecord end params do @@ -106,13 +89,12 @@ module API use :event_filter_params use :sort_params end - # rubocop: disable CodeReuse/ActiveRecord + get ":id/events" do - events = EventsFinder.new(params.merge(source: user_project, current_user: current_user)).execute.preload(:author, :target) + events = find_events(user_project) present_events(events) end - # rubocop: enable CodeReuse/ActiveRecord end end end diff --git a/lib/api/groups.rb b/lib/api/groups.rb index 626a2008dee..64958ff982a 100644 --- a/lib/api/groups.rb +++ b/lib/api/groups.rb @@ -113,7 +113,7 @@ module API requires :name, type: String, desc: 'The name of the group' requires :path, type: String, desc: 'The path of the group' - if ::Group.supports_nested_groups? + if ::Group.supports_nested_objects? optional :parent_id, type: Integer, desc: 'The parent group id for creating nested group' end diff --git a/lib/api/helpers.rb b/lib/api/helpers.rb index 2cceb2ec798..6c1a730935a 100644 --- a/lib/api/helpers.rb +++ b/lib/api/helpers.rb @@ -163,9 +163,11 @@ module API end def find_branch!(branch_name) - user_project.repository.find_branch(branch_name) || not_found!('Branch') - rescue Gitlab::Git::CommandError - render_api_error!('The branch refname is invalid', 400) + if Gitlab::GitRefValidator.validate(branch_name) + user_project.repository.find_branch(branch_name) || not_found!('Branch') + else + render_api_error!('The branch refname is invalid', 400) + end end def find_project_label(id) @@ -291,7 +293,7 @@ module API end end permitted_attrs = ActionController::Parameters.new(attrs).permit! - Gitlab.rails5? ? permitted_attrs.to_h : permitted_attrs + permitted_attrs.to_h end # rubocop: disable CodeReuse/ActiveRecord @@ -494,7 +496,7 @@ module API def send_git_blob(repository, blob) env['api.format'] = :txt content_type 'text/plain' - header['Content-Disposition'] = "attachment; filename=#{blob.name.inspect}" + header['Content-Disposition'] = content_disposition('attachment', blob.name) header(*Gitlab::Workhorse.send_git_blob(repository, blob)) end @@ -527,5 +529,11 @@ module API params[:archived] end + + def content_disposition(disposition, filename) + disposition += %(; filename=#{filename.inspect}) if filename.present? + + disposition + end end end diff --git a/lib/api/internal.rb b/lib/api/internal.rb index ae40b5f7557..9488b3469d9 100644 --- a/lib/api/internal.rb +++ b/lib/api/internal.rb @@ -256,8 +256,9 @@ module API post '/post_receive' do status 200 + PostReceive.perform_async(params[:gl_repository], params[:identifier], - params[:changes]) + params[:changes], params[:push_options].to_a) broadcast_message = BroadcastMessage.current&.last&.message reference_counter_decreased = Gitlab::ReferenceCounter.new(params[:gl_repository]).decrease diff --git a/lib/api/jobs.rb b/lib/api/jobs.rb index 80a5cbd6b19..45c694b6448 100644 --- a/lib/api/jobs.rb +++ b/lib/api/jobs.rb @@ -38,6 +38,8 @@ module API end # rubocop: disable CodeReuse/ActiveRecord get ':id/jobs' do + authorize_read_builds! + builds = user_project.builds.order('id DESC') builds = filter_builds(builds, params[:scope]) @@ -56,7 +58,10 @@ module API end # rubocop: disable CodeReuse/ActiveRecord get ':id/pipelines/:pipeline_id/jobs' do + authorize!(:read_pipeline, user_project) pipeline = user_project.ci_pipelines.find(params[:pipeline_id]) + authorize!(:read_build, pipeline) + builds = pipeline.builds builds = filter_builds(builds, params[:scope]) builds = builds.preload(:job_artifacts_archive, :job_artifacts, project: [:namespace]) diff --git a/lib/api/releases.rb b/lib/api/releases.rb new file mode 100644 index 00000000000..37d06988e64 --- /dev/null +++ b/lib/api/releases.rb @@ -0,0 +1,138 @@ +# frozen_string_literal: true + +module API + class Releases < Grape::API + include PaginationParams + + RELEASE_ENDPOINT_REQUIREMETS = API::NAMESPACE_OR_PROJECT_REQUIREMENTS + .merge(tag_name: API::NO_SLASH_URL_PART_REGEX) + + before { error!('404 Not Found', 404) unless Feature.enabled?(:releases_page, user_project) } + before { authorize_read_releases! } + + params do + requires :id, type: String, desc: 'The ID of a project' + end + resource :projects, requirements: API::NAMESPACE_OR_PROJECT_REQUIREMENTS do + desc 'Get a project releases' do + detail 'This feature was introduced in GitLab 11.7.' + success Entities::Release + end + params do + use :pagination + end + get ':id/releases' do + releases = ::ReleasesFinder.new(user_project, current_user).execute + + present paginate(releases), with: Entities::Release + end + + desc 'Get a single project release' do + detail 'This feature was introduced in GitLab 11.7.' + success Entities::Release + end + params do + requires :tag_name, type: String, desc: 'The name of the tag', as: :tag + end + get ':id/releases/:tag_name', requirements: RELEASE_ENDPOINT_REQUIREMETS do + authorize_read_release! + + present release, with: Entities::Release + end + + desc 'Create a new release' do + detail 'This feature was introduced in GitLab 11.7.' + success Entities::Release + end + params do + requires :tag_name, type: String, desc: 'The name of the tag', as: :tag + requires :name, type: String, desc: 'The name of the release' + requires :description, type: String, desc: 'The release notes' + optional :ref, type: String, desc: 'The commit sha or branch name' + end + post ':id/releases' do + authorize_create_release! + + result = ::Releases::CreateService + .new(user_project, current_user, declared_params(include_missing: false)) + .execute + + if result[:status] == :success + present result[:release], with: Entities::Release + else + render_api_error!(result[:message], result[:http_status]) + end + end + + desc 'Update a release' do + detail 'This feature was introduced in GitLab 11.7.' + success Entities::Release + end + params do + requires :tag_name, type: String, desc: 'The name of the tag', as: :tag + optional :name, type: String, desc: 'The name of the release' + optional :description, type: String, desc: 'Release notes with markdown support' + end + put ':id/releases/:tag_name', requirements: RELEASE_ENDPOINT_REQUIREMETS do + authorize_update_release! + + result = ::Releases::UpdateService + .new(user_project, current_user, declared_params(include_missing: false)) + .execute + + if result[:status] == :success + present result[:release], with: Entities::Release + else + render_api_error!(result[:message], result[:http_status]) + end + end + + desc 'Delete a release' do + detail 'This feature was introduced in GitLab 11.7.' + success Entities::Release + end + params do + requires :tag_name, type: String, desc: 'The name of the tag', as: :tag + end + delete ':id/releases/:tag_name', requirements: RELEASE_ENDPOINT_REQUIREMETS do + authorize_destroy_release! + + result = ::Releases::DestroyService + .new(user_project, current_user, declared_params(include_missing: false)) + .execute + + if result[:status] == :success + present result[:release], with: Entities::Release + else + render_api_error!(result[:message], result[:http_status]) + end + end + end + + helpers do + def authorize_create_release! + authorize! :create_release, user_project + end + + def authorize_read_releases! + authorize! :read_release, user_project + end + + def authorize_read_release! + authorize! :read_release, release + end + + def authorize_update_release! + authorize! :update_release, release + end + + def authorize_destroy_release! + authorize! :destroy_release, release + end + + def release + @release ||= user_project.releases.find_by_tag(params[:tag]) + end + end + end +end diff --git a/lib/api/suggestions.rb b/lib/api/suggestions.rb new file mode 100644 index 00000000000..d008d1b9e97 --- /dev/null +++ b/lib/api/suggestions.rb @@ -0,0 +1,31 @@ +# frozen_string_literal: true + +module API + class Suggestions < Grape::API + before { authenticate! } + + resource :suggestions do + desc 'Apply suggestion patch in the Merge Request it was created' do + success Entities::Suggestion + end + params do + requires :id, type: String, desc: 'The suggestion ID' + end + put ':id/apply' do + suggestion = Suggestion.find_by_id(params[:id]) + + not_found! unless suggestion + authorize! :apply_suggestion, suggestion + + result = ::Suggestions::ApplyService.new(current_user).execute(suggestion) + + if result[:status] == :success + present suggestion, with: Entities::Suggestion, current_user: current_user + else + http_status = result[:http_status] || 400 + render_api_error!(result[:message], http_status) + end + end + end + end +end diff --git a/lib/api/tags.rb b/lib/api/tags.rb index b18eec7d796..aacdca3871a 100644 --- a/lib/api/tags.rb +++ b/lib/api/tags.rb @@ -42,21 +42,35 @@ module API end desc 'Create a new repository tag' do + detail 'This optional release_description parameter was deprecated in GitLab 11.7.' success Entities::Tag end params do requires :tag_name, type: String, desc: 'The name of the tag' requires :ref, type: String, desc: 'The commit sha or branch name' optional :message, type: String, desc: 'Specifying a message creates an annotated tag' - optional :release_description, type: String, desc: 'Specifying release notes stored in the GitLab database' + optional :release_description, type: String, desc: 'Specifying release notes stored in the GitLab database (deprecated in GitLab 11.7)' end post ':id/repository/tags' do authorize_push_project result = ::Tags::CreateService.new(user_project, current_user) - .execute(params[:tag_name], params[:ref], params[:message], params[:release_description]) + .execute(params[:tag_name], params[:ref], params[:message]) if result[:status] == :success + # Release creation with Tags API was deprecated in GitLab 11.7 + if params[:release_description].present? + release_create_params = { + tag: params[:tag_name], + name: params[:tag_name], # Name can be specified in new API + description: params[:release_description] + } + + ::Releases::CreateService + .new(user_project, current_user, release_create_params) + .execute + end + present result[:tag], with: Entities::Tag, project: user_project @@ -88,44 +102,72 @@ module API end desc 'Add a release note to a tag' do - success Entities::Release + detail 'This feature was deprecated in GitLab 11.7.' + success Entities::TagRelease end params do - requires :tag_name, type: String, desc: 'The name of the tag' + requires :tag_name, type: String, desc: 'The name of the tag', as: :tag requires :description, type: String, desc: 'Release notes with markdown support' end post ':id/repository/tags/:tag_name/release', requirements: TAG_ENDPOINT_REQUIREMENTS do - authorize_push_project + authorize_create_release! + + ## + # Legacy API does not support tag auto creation. + not_found!('Tag') unless user_project.repository.find_tag(params[:tag]) - result = CreateReleaseService.new(user_project, current_user) - .execute(params[:tag_name], params[:description]) + release_create_params = { + tag: params[:tag], + name: params[:tag], # Name can be specified in new API + description: params[:description] + } + + result = ::Releases::CreateService + .new(user_project, current_user, release_create_params) + .execute if result[:status] == :success - present result[:release], with: Entities::Release + present result[:release], with: Entities::TagRelease else render_api_error!(result[:message], result[:http_status]) end end desc "Update a tag's release note" do - success Entities::Release + detail 'This feature was deprecated in GitLab 11.7.' + success Entities::TagRelease end params do - requires :tag_name, type: String, desc: 'The name of the tag' + requires :tag_name, type: String, desc: 'The name of the tag', as: :tag requires :description, type: String, desc: 'Release notes with markdown support' end put ':id/repository/tags/:tag_name/release', requirements: TAG_ENDPOINT_REQUIREMENTS do - authorize_push_project + authorize_update_release! - result = UpdateReleaseService.new(user_project, current_user) - .execute(params[:tag_name], params[:description]) + result = ::Releases::UpdateService + .new(user_project, current_user, declared_params(include_missing: false)) + .execute if result[:status] == :success - present result[:release], with: Entities::Release + present result[:release], with: Entities::TagRelease else render_api_error!(result[:message], result[:http_status]) end end end + + helpers do + def authorize_create_release! + authorize! :create_release, user_project + end + + def authorize_update_release! + authorize! :update_release, release + end + + def release + @release ||= user_project.releases.find_by_tag(params[:tag]) + end + end end end diff --git a/lib/backup/manager.rb b/lib/backup/manager.rb index a0434a66ef1..0add2b3f875 100644 --- a/lib/backup/manager.rb +++ b/lib/backup/manager.rb @@ -195,7 +195,7 @@ module Backup if connection.service == ::Fog::Storage::Local connection.directories.create(key: remote_directory) else - connection.directories.get(remote_directory) + connection.directories.new(key: remote_directory) end end diff --git a/lib/backup/repository.rb b/lib/backup/repository.rb index c8a5377bfa0..184c7418e75 100644 --- a/lib/backup/repository.rb +++ b/lib/backup/repository.rb @@ -4,6 +4,7 @@ require 'yaml' module Backup class Repository + include Gitlab::ShellAdapter attr_reader :progress def initialize(progress) @@ -75,7 +76,6 @@ module Backup def restore prepare_directories - gitlab_shell = Gitlab::Shell.new Project.find_each(batch_size: 1000) do |project| progress.print " * #{project.full_path} ... " @@ -118,6 +118,8 @@ module Backup end end end + + restore_object_pools end protected @@ -159,5 +161,17 @@ module Backup def display_repo_path(project) project.hashed_storage?(:repository) ? "#{project.full_path} (#{project.disk_path})" : project.full_path end + + def restore_object_pools + PoolRepository.includes(:source_project).find_each do |pool| + progress.puts " - Object pool #{pool.disk_path}..." + + pool.source_project ||= pool.member_projects.first.root_of_fork_network + pool.state = 'none' + pool.save + + pool.schedule + end + end end end diff --git a/lib/banzai/filter/external_link_filter.rb b/lib/banzai/filter/external_link_filter.rb index 2e6d742de27..4f60b6f84c6 100644 --- a/lib/banzai/filter/external_link_filter.rb +++ b/lib/banzai/filter/external_link_filter.rb @@ -9,11 +9,10 @@ module Banzai def call links.each do |node| uri = uri(node['href'].to_s) - next unless uri - node.set_attribute('href', uri.to_s) + node.set_attribute('href', uri.to_s) if uri - if SCHEMES.include?(uri.scheme) && external_url?(uri) + if SCHEMES.include?(uri&.scheme) && !internal_url?(uri) node.set_attribute('rel', 'nofollow noreferrer noopener') node.set_attribute('target', '_blank') end @@ -35,11 +34,12 @@ module Banzai doc.xpath(query) end - def external_url?(uri) + def internal_url?(uri) + return false if uri.nil? # Relative URLs miss a hostname - return false unless uri.hostname + return true unless uri.hostname - uri.hostname != internal_url.hostname + uri.hostname == internal_url.hostname end def internal_url diff --git a/lib/banzai/filter/label_reference_filter.rb b/lib/banzai/filter/label_reference_filter.rb index 04ec38209c7..f90a35952e5 100644 --- a/lib/banzai/filter/label_reference_filter.rb +++ b/lib/banzai/filter/label_reference_filter.rb @@ -29,7 +29,7 @@ module Banzai if label yield match, label.id, project, namespace, $~ else - match + escape_html_entities(match) end end end @@ -102,6 +102,10 @@ module Banzai CGI.unescapeHTML(text.to_s) end + def escape_html_entities(text) + CGI.escapeHTML(text.to_s) + end + def object_link_title(object, matches) # use title of wrapped element instead nil diff --git a/lib/banzai/filter/suggestion_filter.rb b/lib/banzai/filter/suggestion_filter.rb new file mode 100644 index 00000000000..307ea449140 --- /dev/null +++ b/lib/banzai/filter/suggestion_filter.rb @@ -0,0 +1,24 @@ +# frozen_string_literal: true + +module Banzai + module Filter + class SuggestionFilter < HTML::Pipeline::Filter + # Class used for tagging elements that should be rendered + TAG_CLASS = 'js-render-suggestion'.freeze + + def call + return doc unless suggestions_filter_enabled? + + doc.search('pre.suggestion > code').each do |node| + node.add_class(TAG_CLASS) + end + + doc + end + + def suggestions_filter_enabled? + context[:suggestions_filter_enabled] + end + end + end +end diff --git a/lib/banzai/filter/syntax_highlight_filter.rb b/lib/banzai/filter/syntax_highlight_filter.rb index 8a7f9045c24..18e5e9185de 100644 --- a/lib/banzai/filter/syntax_highlight_filter.rb +++ b/lib/banzai/filter/syntax_highlight_filter.rb @@ -69,7 +69,7 @@ module Banzai end def use_rouge?(language) - %w(math mermaid plantuml).exclude?(language) + %w(math mermaid plantuml suggestion).exclude?(language) end end end diff --git a/lib/banzai/pipeline/gfm_pipeline.rb b/lib/banzai/pipeline/gfm_pipeline.rb index 96bea7ca935..5f13a6d6cde 100644 --- a/lib/banzai/pipeline/gfm_pipeline.rb +++ b/lib/banzai/pipeline/gfm_pipeline.rb @@ -29,6 +29,7 @@ module Banzai Filter::TableOfContentsFilter, Filter::AutolinkFilter, Filter::ExternalLinkFilter, + Filter::SuggestionFilter, *reference_filters, diff --git a/lib/banzai/pipeline/post_process_pipeline.rb b/lib/banzai/pipeline/post_process_pipeline.rb index 63a998a2c1f..7eaad6d7560 100644 --- a/lib/banzai/pipeline/post_process_pipeline.rb +++ b/lib/banzai/pipeline/post_process_pipeline.rb @@ -14,7 +14,8 @@ module Banzai [ Filter::RedactorFilter, Filter::RelativeLinkFilter, - Filter::IssuableStateFilter + Filter::IssuableStateFilter, + Filter::SuggestionFilter ] end diff --git a/lib/banzai/suggestions_parser.rb b/lib/banzai/suggestions_parser.rb new file mode 100644 index 00000000000..09f36635020 --- /dev/null +++ b/lib/banzai/suggestions_parser.rb @@ -0,0 +1,14 @@ +# frozen_string_literal: true + +module Banzai + module SuggestionsParser + # Returns the content of each suggestion code block. + # + def self.parse(text) + html = Banzai.render(text, project: nil, no_original_data: true) + doc = Nokogiri::HTML(html) + + doc.search('pre.suggestion').map { |node| node.text } + end + end +end diff --git a/lib/constraints/feature_constrainer.rb b/lib/constraints/feature_constrainer.rb index ca4376a9d38..cd246cf37a4 100644 --- a/lib/constraints/feature_constrainer.rb +++ b/lib/constraints/feature_constrainer.rb @@ -2,14 +2,14 @@ module Constraints class FeatureConstrainer - attr_reader :feature + attr_reader :args - def initialize(feature) - @feature = feature + def initialize(*args) + @args = args end def matches?(_request) - Feature.enabled?(feature) + Feature.enabled?(*args) end end end diff --git a/lib/declarative_policy.rb b/lib/declarative_policy.rb index 5e22523e45a..7ba48ae9c79 100644 --- a/lib/declarative_policy.rb +++ b/lib/declarative_policy.rb @@ -22,14 +22,10 @@ module DeclarativePolicy key = Cache.policy_key(user, subject) cache[key] ||= - if Gitlab.rails5? - # to avoid deadlocks in multi-threaded environment when - # autoloading is enabled, we allow concurrent loads, - # https://gitlab.com/gitlab-org/gitlab-ce/issues/48263 - ActiveSupport::Dependencies.interlock.permit_concurrent_loads do - class_for(subject).new(user, subject, opts) - end - else + # to avoid deadlocks in multi-threaded environment when + # autoloading is enabled, we allow concurrent loads, + # https://gitlab.com/gitlab-org/gitlab-ce/issues/48263 + ActiveSupport::Dependencies.interlock.permit_concurrent_loads do class_for(subject).new(user, subject, opts) end end diff --git a/lib/gitlab/auth.rb b/lib/gitlab/auth.rb index 6eb5f9e2300..7aa02009aa0 100644 --- a/lib/gitlab/auth.rb +++ b/lib/gitlab/auth.rb @@ -199,7 +199,7 @@ module Gitlab end # rubocop: enable CodeReuse/ActiveRecord - def lfs_token_check(login, password, project) + def lfs_token_check(login, encoded_token, project) deploy_key_matches = login.match(/\Alfs\+deploy-key-(\d+)\z/) actor = @@ -222,7 +222,7 @@ module Gitlab read_authentication_abilities end - if Devise.secure_compare(token_handler.token, password) + if token_handler.token_valid?(encoded_token) Gitlab::Auth::Result.new(actor, nil, token_handler.type, authentication_abilities) end end diff --git a/lib/gitlab/background_migration/backfill_hashed_project_repositories.rb b/lib/gitlab/background_migration/backfill_hashed_project_repositories.rb index 2f76f2f7434..a6194616663 100644 --- a/lib/gitlab/background_migration/backfill_hashed_project_repositories.rb +++ b/lib/gitlab/background_migration/backfill_hashed_project_repositories.rb @@ -2,132 +2,13 @@ module Gitlab module BackgroundMigration - # Class that will create fill the project_repositories table - # for all projects that are on hashed storage and an entry is - # is missing in this table. - class BackfillHashedProjectRepositories - # Shard model - class Shard < ActiveRecord::Base - self.table_name = 'shards' - end - - # Class that will find or create the shard by name. - # There is only a small set of shards, which would - # not change quickly, so look them up from memory - # instead of hitting the DB each time. - class ShardFinder - def find_shard_id(name) - shard_id = shards.fetch(name, nil) - return shard_id if shard_id.present? - - Shard.transaction(requires_new: true) do - create!(name) - end - rescue ActiveRecord::RecordNotUnique - reload! - retry - end - - private - - def create!(name) - Shard.create!(name: name).tap { |shard| @shards[name] = shard.id } - end - - def shards - @shards ||= reload! - end - - def reload! - @shards = Hash[*Shard.all.map { |shard| [shard.name, shard.id] }.flatten] - end - end - - # ProjectRegistry model - class ProjectRepository < ActiveRecord::Base - self.table_name = 'project_repositories' - - belongs_to :project, inverse_of: :project_repository - end - - # Project model - class Project < ActiveRecord::Base - self.table_name = 'projects' - - HASHED_PATH_PREFIX = '@hashed' - - HASHED_STORAGE_FEATURES = { - repository: 1, - attachments: 2 - }.freeze - - has_one :project_repository, inverse_of: :project - - class << self - def on_hashed_storage - where(Project.arel_table[:storage_version] - .gteq(HASHED_STORAGE_FEATURES[:repository])) - end - - def without_project_repository - joins(left_outer_join_project_repository) - .where(ProjectRepository.arel_table[:project_id].eq(nil)) - end - - def left_outer_join_project_repository - projects_table = Project.arel_table - repository_table = ProjectRepository.arel_table - - projects_table - .join(repository_table, Arel::Nodes::OuterJoin) - .on(projects_table[:id].eq(repository_table[:project_id])) - .join_sources - end - end - - def hashed_storage? - self.storage_version && self.storage_version >= 1 - end - - def hashed_disk_path - "#{HASHED_PATH_PREFIX}/#{disk_hash[0..1]}/#{disk_hash[2..3]}/#{disk_hash}" - end - - def disk_hash - @disk_hash ||= Digest::SHA2.hexdigest(id.to_s) - end - end - - def perform(start_id, stop_id) - Gitlab::Database.bulk_insert(:project_repositories, project_repositories(start_id, stop_id)) - end - + # Class that will fill the project_repositories table for projects that + # are on hashed storage and an entry is is missing in this table. + class BackfillHashedProjectRepositories < BackfillProjectRepositories private - def project_repositories(start_id, stop_id) + def projects Project.on_hashed_storage - .without_project_repository - .where(id: start_id..stop_id) - .map { |project| build_attributes_for_project(project) } - .compact - end - - def build_attributes_for_project(project) - return unless project.hashed_storage? - - { - project_id: project.id, - shard_id: find_shard_id(project.repository_storage), - disk_path: project.hashed_disk_path - } - end - - def find_shard_id(repository_storage) - shard_finder.find_shard_id(repository_storage) - end - - def shard_finder - @shard_finder ||= ShardFinder.new end end end diff --git a/lib/gitlab/background_migration/backfill_legacy_project_repositories.rb b/lib/gitlab/background_migration/backfill_legacy_project_repositories.rb new file mode 100644 index 00000000000..6dc92672929 --- /dev/null +++ b/lib/gitlab/background_migration/backfill_legacy_project_repositories.rb @@ -0,0 +1,15 @@ +# frozen_string_literal: true + +module Gitlab + module BackgroundMigration + # Class that will fill the project_repositories table for projects that + # are on legacy storage and an entry is is missing in this table. + class BackfillLegacyProjectRepositories < BackfillProjectRepositories + private + + def projects + Project.with_parent.on_legacy_storage + end + end + end +end diff --git a/lib/gitlab/background_migration/backfill_project_repositories.rb b/lib/gitlab/background_migration/backfill_project_repositories.rb new file mode 100644 index 00000000000..aaf520d70f6 --- /dev/null +++ b/lib/gitlab/background_migration/backfill_project_repositories.rb @@ -0,0 +1,219 @@ +# frozen_string_literal: true + +module Gitlab + module BackgroundMigration + # Class that will create fill the project_repositories table + # for projects an entry is is missing in this table. + class BackfillProjectRepositories + OrphanedNamespaceError = Class.new(StandardError) + + # Shard model + class Shard < ActiveRecord::Base + self.table_name = 'shards' + end + + # Class that will find or create the shard by name. + # There is only a small set of shards, which would + # not change quickly, so look them up from memory + # instead of hitting the DB each time. + class ShardFinder + def find_shard_id(name) + shard_id = shards.fetch(name, nil) + return shard_id if shard_id.present? + + Shard.transaction(requires_new: true) do + create!(name) + end + rescue ActiveRecord::RecordNotUnique + reload! + retry + end + + private + + def create!(name) + Shard.create!(name: name).tap { |shard| @shards[name] = shard.id } + end + + def shards + @shards ||= reload! + end + + def reload! + @shards = Hash[*Shard.all.map { |shard| [shard.name, shard.id] }.flatten] + end + end + + module Storage + # Class that returns the disk path for a project using hashed storage + class HashedProject + attr_accessor :project + + ROOT_PATH_PREFIX = '@hashed' + + def initialize(project) + @project = project + end + + def disk_path + "#{ROOT_PATH_PREFIX}/#{disk_hash[0..1]}/#{disk_hash[2..3]}/#{disk_hash}" + end + + def disk_hash + @disk_hash ||= Digest::SHA2.hexdigest(project.id.to_s) + end + end + + # Class that returns the disk path for a project using legacy storage + class LegacyProject + attr_accessor :project + + def initialize(project) + @project = project + end + + def disk_path + project.full_path + end + end + end + + # Concern used by Project and Namespace to determine the full route to the project + module Routable + extend ActiveSupport::Concern + + def full_path + @full_path ||= build_full_path + end + + def build_full_path + return path unless has_parent? + + raise OrphanedNamespaceError if parent.nil? + + parent.full_path + '/' + path + end + + def has_parent? + read_attribute(association(:parent).reflection.foreign_key) + end + end + + # Namespace model. + class Namespace < ActiveRecord::Base + self.table_name = 'namespaces' + self.inheritance_column = nil + + include Routable + + belongs_to :parent, class_name: 'Namespace', inverse_of: 'namespaces' + + has_many :projects, inverse_of: :parent + has_many :namespaces, inverse_of: :parent + end + + # ProjectRegistry model + class ProjectRepository < ActiveRecord::Base + self.table_name = 'project_repositories' + + belongs_to :project, inverse_of: :project_repository + end + + # Project model + class Project < ActiveRecord::Base + self.table_name = 'projects' + + include Routable + + HASHED_STORAGE_FEATURES = { + repository: 1, + attachments: 2 + }.freeze + + scope :with_parent, -> { includes(:parent) } + + belongs_to :parent, class_name: 'Namespace', foreign_key: :namespace_id, inverse_of: 'projects' + + has_one :project_repository, inverse_of: :project + + delegate :disk_path, to: :storage + + class << self + def on_hashed_storage + where(Project.arel_table[:storage_version] + .gteq(HASHED_STORAGE_FEATURES[:repository])) + end + + def on_legacy_storage + where(Project.arel_table[:storage_version].eq(nil) + .or(Project.arel_table[:storage_version].eq(0))) + end + + def without_project_repository + joins(left_outer_join_project_repository) + .where(ProjectRepository.arel_table[:project_id].eq(nil)) + end + + def left_outer_join_project_repository + projects_table = Project.arel_table + repository_table = ProjectRepository.arel_table + + projects_table + .join(repository_table, Arel::Nodes::OuterJoin) + .on(projects_table[:id].eq(repository_table[:project_id])) + .join_sources + end + end + + def storage + @storage ||= + if hashed_storage? + Storage::HashedProject.new(self) + else + Storage::LegacyProject.new(self) + end + end + + def hashed_storage? + self.storage_version && + self.storage_version >= HASHED_STORAGE_FEATURES[:repository] + end + end + + def perform(start_id, stop_id) + Gitlab::Database.bulk_insert(:project_repositories, project_repositories(start_id, stop_id)) + end + + private + + def projects + raise NotImplementedError, + "#{self.class} does not implement #{__method__}" + end + + def project_repositories(start_id, stop_id) + projects + .without_project_repository + .where(id: start_id..stop_id) + .map { |project| build_attributes_for_project(project) } + .compact + end + + def build_attributes_for_project(project) + { + project_id: project.id, + shard_id: find_shard_id(project.repository_storage), + disk_path: project.disk_path + } + end + + def find_shard_id(repository_storage) + shard_finder.find_shard_id(repository_storage) + end + + def shard_finder + @shard_finder ||= ShardFinder.new + end + end + end +end diff --git a/lib/gitlab/blob_helper.rb b/lib/gitlab/blob_helper.rb index 488c1d85387..d3e15a79a8b 100644 --- a/lib/gitlab/blob_helper.rb +++ b/lib/gitlab/blob_helper.rb @@ -12,7 +12,7 @@ module Gitlab end def viewable? - !large? && text? + !large? && text_in_repo? end MEGABYTE = 1024 * 1024 @@ -21,7 +21,7 @@ module Gitlab size.to_i > MEGABYTE end - def binary? + def binary_in_repo? # Large blobs aren't even loaded into memory if data.nil? true @@ -40,8 +40,8 @@ module Gitlab end end - def text? - !binary? + def text_in_repo? + !binary_in_repo? end def image? @@ -113,7 +113,7 @@ module Gitlab def content_type # rubocop:disable Style/MultilineTernaryOperator # rubocop:disable Style/NestedTernaryOperator - @content_type ||= binary_mime_type? || binary? ? mime_type : + @content_type ||= binary_mime_type? || binary_in_repo? ? mime_type : (encoding ? "text/plain; charset=#{encoding.downcase}" : "text/plain") # rubocop:enable Style/NestedTernaryOperator # rubocop:enable Style/MultilineTernaryOperator diff --git a/lib/gitlab/checks/base_checker.rb b/lib/gitlab/checks/base_checker.rb index f8cda0382fe..7fbcf6a4ff4 100644 --- a/lib/gitlab/checks/base_checker.rb +++ b/lib/gitlab/checks/base_checker.rb @@ -33,6 +33,22 @@ module Gitlab def tag_exists? project.repository.tag_exists?(tag_name) end + + def validate_once(resource) + Gitlab::SafeRequestStore.fetch(cache_key_for_resource(resource)) do + yield(resource) + + true + end + end + + def cache_key_for_resource(resource) + "git_access:#{checker_cache_key}:#{resource.cache_key}" + end + + def checker_cache_key + self.class.name.demodulize.underscore + end end end end diff --git a/lib/gitlab/checks/diff_check.rb b/lib/gitlab/checks/diff_check.rb index 8ee345ab45a..63da9a3d6b5 100644 --- a/lib/gitlab/checks/diff_check.rb +++ b/lib/gitlab/checks/diff_check.rb @@ -14,13 +14,17 @@ module Gitlab return if deletion? || newrev.nil? return unless should_run_diff_validations? return if commits.empty? - return unless uses_raw_delta_validations? file_paths = [] - process_raw_deltas do |diff| - file_paths << (diff.new_path || diff.old_path) - validate_diff(diff) + process_commits do |commit| + validate_once(commit) do + commit.raw_deltas.each do |diff| + file_paths << (diff.new_path || diff.old_path) + + validate_diff(diff) + end + end end validate_file_paths(file_paths) @@ -28,17 +32,13 @@ module Gitlab private - def should_run_diff_validations? - validate_lfs_file_locks? - end - def validate_lfs_file_locks? strong_memoize(:validate_lfs_file_locks) do project.lfs_enabled? && project.any_lfs_file_locks? end end - def uses_raw_delta_validations? + def should_run_diff_validations? validations_for_diff.present? || path_validations.present? end @@ -59,16 +59,14 @@ module Gitlab validate_lfs_file_locks? ? [lfs_file_locks_validation] : [] end - def process_raw_deltas + def process_commits logger.log_timed(LOG_MESSAGES[:diff_content_check]) do # n+1: https://gitlab.com/gitlab-org/gitlab-ee/issues/3593 ::Gitlab::GitalyClient.allow_n_plus_1_calls do commits.each do |commit| logger.check_timeout_reached - commit.raw_deltas.each do |diff| - yield(diff) - end + yield(commit) end end end diff --git a/lib/gitlab/checks/lfs_check.rb b/lib/gitlab/checks/lfs_check.rb index e42684e679a..cc6a14d2d9a 100644 --- a/lib/gitlab/checks/lfs_check.rb +++ b/lib/gitlab/checks/lfs_check.rb @@ -7,6 +7,7 @@ module Gitlab ERROR_MESSAGE = 'LFS objects are missing. Ensure LFS is properly set up or try a manual "git lfs push --all".'.freeze def validate! + return unless project.lfs_enabled? return if skip_lfs_integrity_check logger.log_timed(LOG_MESSAGE) do diff --git a/lib/gitlab/ci/config/entry/except_policy.rb b/lib/gitlab/ci/config/entry/except_policy.rb deleted file mode 100644 index 46ded35325d..00000000000 --- a/lib/gitlab/ci/config/entry/except_policy.rb +++ /dev/null @@ -1,17 +0,0 @@ -# frozen_string_literal: true - -module Gitlab - module Ci - class Config - module Entry - ## - # Entry that represents an only/except trigger policy for the job. - # - class ExceptPolicy < Policy - def self.default - end - end - end - end - end -end diff --git a/lib/gitlab/ci/config/entry/job.rb b/lib/gitlab/ci/config/entry/job.rb index 085be5da08d..50942fbdb40 100644 --- a/lib/gitlab/ci/config/entry/job.rb +++ b/lib/gitlab/ci/config/entry/job.rb @@ -16,6 +16,13 @@ module Gitlab dependencies before_script after_script variables environment coverage retry parallel extends].freeze + DEFAULT_ONLY_POLICY = { + refs: %w(branches tags) + }.freeze + + DEFAULT_EXCEPT_POLICY = { + }.freeze + validations do validates :config, allowed_keys: ALLOWED_KEYS validates :config, presence: true @@ -65,10 +72,10 @@ module Gitlab entry :services, Entry::Services, description: 'Services that will be used to execute this job.' - entry :only, Entry::OnlyPolicy, + entry :only, Entry::Policy, description: 'Refs policy this job will be executed for.' - entry :except, Entry::ExceptPolicy, + entry :except, Entry::Policy, description: 'Refs policy this job will be executed for.' entry :variables, Entry::Variables, @@ -154,8 +161,8 @@ module Gitlab services: services_value, stage: stage_value, cache: cache_value, - only: only_value, - except: except_value, + only: DEFAULT_ONLY_POLICY.deep_merge(only_value.to_h), + except: DEFAULT_EXCEPT_POLICY.deep_merge(except_value.to_h), variables: variables_defined? ? variables_value : nil, environment: environment_defined? ? environment_value : nil, environment_name: environment_defined? ? environment_value[:name] : nil, diff --git a/lib/gitlab/ci/config/entry/only_policy.rb b/lib/gitlab/ci/config/entry/only_policy.rb deleted file mode 100644 index 9a581b8e97e..00000000000 --- a/lib/gitlab/ci/config/entry/only_policy.rb +++ /dev/null @@ -1,18 +0,0 @@ -# frozen_string_literal: true - -module Gitlab - module Ci - class Config - module Entry - ## - # Entry that represents an only/except trigger policy for the job. - # - class OnlyPolicy < Policy - def self.default - { refs: %w[branches tags] } - end - end - end - end - end -end diff --git a/lib/gitlab/ci/config/entry/policy.rb b/lib/gitlab/ci/config/entry/policy.rb index 81e74a639fc..998da1f6837 100644 --- a/lib/gitlab/ci/config/entry/policy.rb +++ b/lib/gitlab/ci/config/entry/policy.rb @@ -5,9 +5,12 @@ module Gitlab class Config module Entry ## - # Base class for OnlyPolicy and ExceptPolicy + # Entry that represents an only/except trigger policy for the job. # class Policy < ::Gitlab::Config::Entry::Simplifiable + strategy :RefsPolicy, if: -> (config) { config.is_a?(Array) } + strategy :ComplexPolicy, if: -> (config) { config.is_a?(Hash) } + class RefsPolicy < ::Gitlab::Config::Entry::Node include ::Gitlab::Config::Entry::Validatable @@ -63,16 +66,6 @@ module Gitlab def self.default end - - ## - # Class-level execution won't be inherited by subclasses by default. - # Therefore, we need to explicitly execute that for OnlyPolicy and ExceptPolicy - def self.inherited(klass) - super - - klass.strategy :RefsPolicy, if: -> (config) { config.is_a?(Array) } - klass.strategy :ComplexPolicy, if: -> (config) { config.is_a?(Hash) } - end end end end diff --git a/lib/gitlab/ci/pipeline/chain/command.rb b/lib/gitlab/ci/pipeline/chain/command.rb index 100b9521412..e62d547d862 100644 --- a/lib/gitlab/ci/pipeline/chain/command.rb +++ b/lib/gitlab/ci/pipeline/chain/command.rb @@ -10,7 +10,7 @@ module Gitlab :origin_ref, :checkout_sha, :after_sha, :before_sha, :trigger_request, :schedule, :merge_request, :ignore_skip_ci, :save_incompleted, - :seeds_block, :variables_attributes + :seeds_block, :variables_attributes, :push_options ) do include Gitlab::Utils::StrongMemoize @@ -54,7 +54,13 @@ module Gitlab def protected_ref? strong_memoize(:protected_ref) do - project.protected_for?(ref) + project.protected_for?(origin_ref) + end + end + + def ambiguous_ref? + strong_memoize(:ambiguous_ref) do + project.repository.ambiguous_ref?(origin_ref) end end end diff --git a/lib/gitlab/ci/pipeline/chain/skip.rb b/lib/gitlab/ci/pipeline/chain/skip.rb index b9707d2f8f5..79bbcc1ed1e 100644 --- a/lib/gitlab/ci/pipeline/chain/skip.rb +++ b/lib/gitlab/ci/pipeline/chain/skip.rb @@ -8,6 +8,7 @@ module Gitlab include ::Gitlab::Utils::StrongMemoize SKIP_PATTERN = /\[(ci[ _-]skip|skip[ _-]ci)\]/i + SKIP_PUSH_OPTION = 'ci.skip' def perform! if skipped? @@ -16,7 +17,7 @@ module Gitlab end def skipped? - !@command.ignore_skip_ci && commit_message_skips_ci? + !@command.ignore_skip_ci && (commit_message_skips_ci? || push_option_skips_ci?) end def break? @@ -32,6 +33,10 @@ module Gitlab !!(@pipeline.git_commit_message =~ SKIP_PATTERN) end end + + def push_option_skips_ci? + !!(@command.push_options&.include?(SKIP_PUSH_OPTION)) + end end end end diff --git a/lib/gitlab/ci/pipeline/chain/validate/repository.rb b/lib/gitlab/ci/pipeline/chain/validate/repository.rb index d88851d8245..9c6c2bc8e25 100644 --- a/lib/gitlab/ci/pipeline/chain/validate/repository.rb +++ b/lib/gitlab/ci/pipeline/chain/validate/repository.rb @@ -16,6 +16,10 @@ module Gitlab unless @command.sha return error('Commit not found') end + + if @command.ambiguous_ref? + return error('Ref is ambiguous') + end end def break? diff --git a/lib/gitlab/ci/templates/Auto-DevOps.gitlab-ci.yml b/lib/gitlab/ci/templates/Auto-DevOps.gitlab-ci.yml index d0613aa59e1..8f6cf8d2d03 100644 --- a/lib/gitlab/ci/templates/Auto-DevOps.gitlab-ci.yml +++ b/lib/gitlab/ci/templates/Auto-DevOps.gitlab-ci.yml @@ -149,10 +149,10 @@ performance: only: refs: - branches + kubernetes: active except: variables: - $PERFORMANCE_DISABLED - - $KUBECONFIG == null sast: stage: test @@ -185,7 +185,8 @@ dependency_scanning: - setup_docker - dependency_scanning artifacts: - paths: [gl-dependency-scanning-report.json] + reports: + dependency_scanning: gl-dependency-scanning-report.json only: refs: - branches @@ -228,6 +229,7 @@ dast: only: refs: - branches + kubernetes: active variables: - $GITLAB_FEATURES =~ /\bdast\b/ except: @@ -235,7 +237,6 @@ dast: - master variables: - $DAST_DISABLED - - $KUBECONFIG == null review: stage: review @@ -257,12 +258,12 @@ review: only: refs: - branches + kubernetes: active except: refs: - master variables: - $REVIEW_DISABLED - - $KUBECONFIG == null stop_review: stage: cleanup @@ -280,12 +281,12 @@ stop_review: only: refs: - branches + kubernetes: active except: refs: - master variables: - $REVIEW_DISABLED - - $KUBECONFIG == null # Staging deploys are disabled by default since # continuous deployment to production is enabled by default @@ -309,11 +310,9 @@ staging: only: refs: - master + kubernetes: active variables: - $STAGING_ENABLED - except: - variables: - - $KUBECONFIG == null # Canaries are also disabled by default, but if you want them, # and know what the downsides are, you can enable this by setting @@ -336,11 +335,9 @@ canary: only: refs: - master + kubernetes: active variables: - $CANARY_ENABLED - except: - variables: - - $KUBECONFIG == null .production: &production_template stage: production @@ -366,13 +363,13 @@ production: only: refs: - master + kubernetes: active except: variables: - $STAGING_ENABLED - $CANARY_ENABLED - $INCREMENTAL_ROLLOUT_ENABLED - $INCREMENTAL_ROLLOUT_MODE - - $KUBECONFIG == null production_manual: <<: *production_template @@ -381,6 +378,7 @@ production_manual: only: refs: - master + kubernetes: active variables: - $STAGING_ENABLED - $CANARY_ENABLED @@ -388,7 +386,6 @@ production_manual: variables: - $INCREMENTAL_ROLLOUT_ENABLED - $INCREMENTAL_ROLLOUT_MODE - - $KUBECONFIG == null # This job implements incremental rollout on for every push to `master`. @@ -418,13 +415,13 @@ production_manual: only: refs: - master + kubernetes: active variables: - $INCREMENTAL_ROLLOUT_MODE == "manual" - $INCREMENTAL_ROLLOUT_ENABLED except: variables: - $INCREMENTAL_ROLLOUT_MODE == "timed" - - $KUBECONFIG == null .timed_rollout_template: &timed_rollout_template <<: *rollout_template @@ -433,11 +430,9 @@ production_manual: only: refs: - master + kubernetes: active variables: - $INCREMENTAL_ROLLOUT_MODE == "timed" - except: - variables: - - $KUBECONFIG == null timed rollout 10%: <<: *timed_rollout_template diff --git a/lib/gitlab/ci/templates/Ruby.gitlab-ci.yml b/lib/gitlab/ci/templates/Ruby.gitlab-ci.yml index 93cb31f48c0..0d12cbc6460 100644 --- a/lib/gitlab/ci/templates/Ruby.gitlab-ci.yml +++ b/lib/gitlab/ci/templates/Ruby.gitlab-ci.yml @@ -24,7 +24,6 @@ before_script: - ruby -v # Print out ruby version for debugging # Uncomment next line if your rails app needs a JS runtime: # - apt-get update -q && apt-get install nodejs -yqq - - gem install bundler --no-ri --no-rdoc # Bundler is not installed with the image - bundle install -j $(nproc) --path vendor # Install dependencies into ./vendor/ruby # Optional - Delete if not using `rubocop` diff --git a/lib/gitlab/cleanup/remote_uploads.rb b/lib/gitlab/cleanup/remote_uploads.rb index eba1faacc3a..03298d960a4 100644 --- a/lib/gitlab/cleanup/remote_uploads.rb +++ b/lib/gitlab/cleanup/remote_uploads.rb @@ -67,7 +67,7 @@ module Gitlab end def remote_directory - connection.directories.get(configuration['remote_directory']) + connection.directories.new(key: configuration['remote_directory']) end def connection diff --git a/lib/gitlab/color_schemes.rb b/lib/gitlab/color_schemes.rb index a5e4065cf09..881e5dbc923 100644 --- a/lib/gitlab/color_schemes.rb +++ b/lib/gitlab/color_schemes.rb @@ -12,7 +12,8 @@ module Gitlab Scheme.new(2, 'Dark', 'dark'), Scheme.new(3, 'Solarized Light', 'solarized-light'), Scheme.new(4, 'Solarized Dark', 'solarized-dark'), - Scheme.new(5, 'Monokai', 'monokai') + Scheme.new(5, 'Monokai', 'monokai'), + Scheme.new(6, 'None', 'none') ].freeze # Convenience method to get a space-separated String of all the color scheme diff --git a/lib/gitlab/current_settings.rb b/lib/gitlab/current_settings.rb index 477f9101e98..552aad83dd4 100644 --- a/lib/gitlab/current_settings.rb +++ b/lib/gitlab/current_settings.rb @@ -7,10 +7,6 @@ module Gitlab Gitlab::SafeRequestStore.fetch(:current_application_settings) { ensure_application_settings! } end - def fake_application_settings(attributes = {}) - Gitlab::FakeApplicationSettings.new(::ApplicationSetting.defaults.merge(attributes || {})) - end - def clear_in_memory_application_settings! @in_memory_application_settings = nil end @@ -50,28 +46,21 @@ module Gitlab # and other callers from failing, use any loaded settings and return # defaults for missing columns. if ActiveRecord::Migrator.needs_migration? - return fake_application_settings(current_settings&.attributes) - end - - return current_settings if current_settings.present? - - with_fallback_to_fake_application_settings do - ::ApplicationSetting.create_from_defaults || in_memory_application_settings + db_attributes = current_settings&.attributes || {} + ::ApplicationSetting.build_from_defaults(db_attributes) + elsif current_settings.present? + current_settings + else + ::ApplicationSetting.create_from_defaults end end - def in_memory_application_settings - with_fallback_to_fake_application_settings do - @in_memory_application_settings ||= ::ApplicationSetting.build_from_defaults - end + def fake_application_settings(attributes = {}) + Gitlab::FakeApplicationSettings.new(::ApplicationSetting.defaults.merge(attributes || {})) end - def with_fallback_to_fake_application_settings(&block) - yield - rescue - # In case the application_settings table is not created yet, or if a new - # ApplicationSetting column is not yet migrated we fallback to a simple OpenStruct - fake_application_settings + def in_memory_application_settings + @in_memory_application_settings ||= ::ApplicationSetting.build_from_defaults end def connect_to_db? diff --git a/lib/gitlab/data_builder/push.rb b/lib/gitlab/data_builder/push.rb index 9bf2f9291a8..862127110b9 100644 --- a/lib/gitlab/data_builder/push.rb +++ b/lib/gitlab/data_builder/push.rb @@ -31,7 +31,11 @@ module Gitlab } } ], - total_commits_count: 1 + total_commits_count: 1, + push_options: [ + "ci.skip", + "custom option" + ] }.freeze # Produce a hash of post-receive data @@ -52,10 +56,12 @@ module Gitlab # homepage: String, # }, # commits: Array, - # total_commits_count: Fixnum + # total_commits_count: Fixnum, + # push_options: Array # } # - def build(project, user, oldrev, newrev, ref, commits = [], message = nil, commits_count: nil) + # rubocop:disable Metrics/ParameterLists + def build(project, user, oldrev, newrev, ref, commits = [], message = nil, commits_count: nil, push_options: []) commits = Array(commits) # Total commits count @@ -93,6 +99,7 @@ module Gitlab project: project.hook_attrs, commits: commit_attrs, total_commits_count: commits_count, + push_options: push_options, # DEPRECATED repository: project.hook_attrs.slice(:name, :url, :description, :homepage, :git_http_url, :git_ssh_url, :visibility_level) diff --git a/lib/gitlab/database.rb b/lib/gitlab/database.rb index 6d40e00c035..b6ca777e029 100644 --- a/lib/gitlab/database.rb +++ b/lib/gitlab/database.rb @@ -232,11 +232,7 @@ module Gitlab end def self.cached_table_exists?(table_name) - if Gitlab.rails5? - connection.schema_cache.data_source_exists?(table_name) - else - connection.schema_cache.table_exists?(table_name) - end + connection.schema_cache.data_source_exists?(table_name) end private_class_method :connection diff --git a/lib/gitlab/database/arel_methods.rb b/lib/gitlab/database/arel_methods.rb deleted file mode 100644 index 991e4152dcb..00000000000 --- a/lib/gitlab/database/arel_methods.rb +++ /dev/null @@ -1,20 +0,0 @@ -# frozen_string_literal: true - -module Gitlab - module Database - module ArelMethods - private - - # In Arel 7.0.0 (Arel 7.1.4 is used in Rails 5.0) the `engine` parameter of `Arel::UpdateManager#initializer` - # was removed. - # Remove this file and inline this method when removing rails5? code. - def arel_update_manager - if Gitlab.rails5? - Arel::UpdateManager.new - else - Arel::UpdateManager.new(ActiveRecord::Base) - end - end - end - end -end diff --git a/lib/gitlab/database/median.rb b/lib/gitlab/database/median.rb index 0da5119a3ed..1455e410d4b 100644 --- a/lib/gitlab/database/median.rb +++ b/lib/gitlab/database/median.rb @@ -35,13 +35,7 @@ module Gitlab end def mysql_median_datetime_sql(arel_table, query_so_far, column_sym) - arel_from = if Gitlab.rails5? - arel_table.from - else - arel_table - end - - query = arel_from + query = arel_table.from .from(arel_table.project(Arel.sql('*')).order(arel_table[column_sym]).as(arel_table.table_name)) .project(average([arel_table[column_sym]], 'median')) .where( @@ -151,13 +145,8 @@ module Gitlab .order(arel_table[column_sym]) ).as('row_id') - arel_from = if Gitlab.rails5? - arel_table.from.from(arel_table.alias) - else - arel_table.from(arel_table.alias) - end - - count = arel_from.project('COUNT(*)') + count = arel_table.from.from(arel_table.alias) + .project('COUNT(*)') .where(arel_table[partition_column].eq(arel_table.alias[partition_column])) .as('ct') diff --git a/lib/gitlab/database/migration_helpers.rb b/lib/gitlab/database/migration_helpers.rb index d9578852db6..3abd0600e9d 100644 --- a/lib/gitlab/database/migration_helpers.rb +++ b/lib/gitlab/database/migration_helpers.rb @@ -3,8 +3,6 @@ module Gitlab module Database module MigrationHelpers - include Gitlab::Database::ArelMethods - BACKGROUND_MIGRATION_BATCH_SIZE = 1000 # Number of rows to process per job BACKGROUND_MIGRATION_JOB_BUFFER_SIZE = 1000 # Number of jobs to bulk queue at a time @@ -361,7 +359,7 @@ module Gitlab stop_arel = yield table, stop_arel if block_given? stop_row = exec_query(stop_arel.to_sql).to_hash.first - update_arel = arel_update_manager + update_arel = Arel::UpdateManager.new .table(table) .set([[table[column], value]]) .where(table[:id].gteq(start_id)) diff --git a/lib/gitlab/database/rename_reserved_paths_migration/v1/rename_base.rb b/lib/gitlab/database/rename_reserved_paths_migration/v1/rename_base.rb index a5b42bbfdd9..60afa4bcd52 100644 --- a/lib/gitlab/database/rename_reserved_paths_migration/v1/rename_base.rb +++ b/lib/gitlab/database/rename_reserved_paths_migration/v1/rename_base.rb @@ -5,8 +5,6 @@ module Gitlab module RenameReservedPathsMigration module V1 class RenameBase - include Gitlab::Database::ArelMethods - attr_reader :paths, :migration delegate :update_column_in_batches, @@ -66,7 +64,7 @@ module Gitlab old_full_path, new_full_path) - update = arel_update_manager + update = Arel::UpdateManager.new .table(routes) .set([[routes[:path], replace_statement]]) .where(Arel::Nodes::SqlLiteral.new(filter)) diff --git a/lib/gitlab/database/sha_attribute.rb b/lib/gitlab/database/sha_attribute.rb index 6516d6e648d..8d97adaff99 100644 --- a/lib/gitlab/database/sha_attribute.rb +++ b/lib/gitlab/database/sha_attribute.rb @@ -8,14 +8,7 @@ module Gitlab # behaviour from the default Binary type. ActiveRecord::ConnectionAdapters::PostgreSQL::OID::Bytea else - # In Rails 5.0 `Type` has been moved from `ActiveRecord` to `ActiveModel` - # https://github.com/rails/rails/commit/9cc8c6f3730df3d94c81a55be9ee1b7b4ffd29f6#diff-f8ba7983a51d687976e115adcd95822b - # Remove this method and leave just `ActiveModel::Type::Binary` when removing Gitlab.rails5? code. - if Gitlab.rails5? - ActiveModel::Type::Binary - else - ActiveRecord::Type::Binary - end + ActiveModel::Type::Binary end # Class for casting binary data to hexadecimal SHA1 hashes (and vice-versa). @@ -26,31 +19,9 @@ module Gitlab class ShaAttribute < BINARY_TYPE PACK_FORMAT = 'H*'.freeze - # It is called from activerecord-4.2.10/lib/active_record internal methods. - # Remove this method when removing Gitlab.rails5? code. - def type_cast_from_database(value) - unpack_sha(super) - end - - # It is called from activerecord-4.2.10/lib/active_record internal methods. - # Remove this method when removing Gitlab.rails5? code. - def type_cast_for_database(value) - serialize(value) - end - - # It is called from activerecord-5.0.6/lib/active_record/attribute.rb - # Remove this method when removing Gitlab.rails5? code.. - def deserialize(value) - value = Gitlab.rails5? ? super : method(:type_cast_from_database).super_method.call(value) - - unpack_sha(value) - end - - # Rename this method to `deserialize(value)` removing Gitlab.rails5? code. # Casts binary data to a SHA1 in hexadecimal. - def unpack_sha(value) - # Uncomment this line when removing Gitlab.rails5? code. - # value = super + def deserialize(value) + value = super(value) value ? value.unpack(PACK_FORMAT)[0] : nil end @@ -58,7 +29,7 @@ module Gitlab def serialize(value) arg = value ? [value].pack(PACK_FORMAT) : nil - Gitlab.rails5? ? super(arg) : method(:type_cast_for_database).super_method.call(arg) + super(arg) end end end diff --git a/lib/gitlab/database/subquery.rb b/lib/gitlab/database/subquery.rb index 36e4559b554..10971d2b274 100644 --- a/lib/gitlab/database/subquery.rb +++ b/lib/gitlab/database/subquery.rb @@ -6,15 +6,11 @@ module Gitlab class << self def self_join(relation) t = relation.arel_table - t2 = if !Gitlab.rails5? - relation.arel.as('t2') - else - # Work around a bug in Rails 5, where LIMIT causes trouble - # See https://gitlab.com/gitlab-org/gitlab-ce/issues/51729 - r = relation.limit(nil).arel - r.take(relation.limit_value) if relation.limit_value - r.as('t2') - end + # Work around a bug in Rails 5, where LIMIT causes trouble + # See https://gitlab.com/gitlab-org/gitlab-ce/issues/51729 + r = relation.limit(nil).arel + r.take(relation.limit_value) if relation.limit_value + t2 = r.as('t2') relation.unscoped.joins(t.join(t2).on(t[:id].eq(t2[:id])).join_sources.first) end diff --git a/lib/gitlab/diff/file.rb b/lib/gitlab/diff/file.rb index 8ba44dff06f..e410d5a8333 100644 --- a/lib/gitlab/diff/file.rb +++ b/lib/gitlab/diff/file.rb @@ -3,7 +3,9 @@ module Gitlab module Diff class File - attr_reader :diff, :repository, :diff_refs, :fallback_diff_refs + include Gitlab::Utils::StrongMemoize + + attr_reader :diff, :repository, :diff_refs, :fallback_diff_refs, :unique_identifier delegate :new_file?, :deleted_file?, :renamed_file?, :old_path, :new_path, :a_mode, :b_mode, :mode_changed?, @@ -22,12 +24,20 @@ module Gitlab DiffViewer::Image ].sort_by { |v| v.binary? ? 0 : 1 }.freeze - def initialize(diff, repository:, diff_refs: nil, fallback_diff_refs: nil, stats: nil) + def initialize( + diff, + repository:, + diff_refs: nil, + fallback_diff_refs: nil, + stats: nil, + unique_identifier: nil) + @diff = diff @stats = stats @repository = repository @diff_refs = diff_refs @fallback_diff_refs = fallback_diff_refs + @unique_identifier = unique_identifier @unfolded = false # Ensure items are collected in the the batch @@ -67,7 +77,15 @@ module Gitlab def line_for_position(pos) return nil unless pos.position_type == 'text' - diff_lines.find { |line| line.old_line == pos.old_line && line.new_line == pos.new_line } + # This method is normally used to find which line the diff was + # commented on, and in this context, it's normally the raw diff persisted + # at `note_diff_files`, which is a fraction of the entire diff + # (it goes from the first line, to the commented line, or + # one line below). Therefore it's more performant to fetch + # from bottom to top instead of the other way around. + diff_lines + .reverse_each + .find { |line| line.old_line == pos.old_line && line.new_line == pos.new_line } end def position_for_line_code(code) @@ -122,6 +140,16 @@ module Gitlab old_blob_lazy&.itself end + def new_blob_lines_between(from_line, to_line) + return [] unless new_blob + + from_index = from_line - 1 + to_index = to_line - 1 + + new_blob.load_all_data! + new_blob.data.lines[from_index..to_index] + end + def content_sha new_content_sha || old_content_sha end @@ -156,6 +184,10 @@ module Gitlab @unfolded end + def highlight_loaded? + @highlighted_diff_lines.present? + end + def highlighted_diff_lines @highlighted_diff_lines ||= Gitlab::Diff::Highlight.new(self, repository: self.repository).highlight @@ -202,12 +234,12 @@ module Gitlab repository.attributes(file_path).fetch('diff') { true } end - def binary? - has_binary_notice? || try_blobs(:binary?) + def binary_in_repo? + has_binary_notice? || try_blobs(:binary_in_repo?) end - def text? - !binary? + def text_in_repo? + !binary_in_repo? end def external_storage_error? @@ -245,12 +277,20 @@ module Gitlab end # rubocop: enable CodeReuse/ActiveRecord - def raw_binary? - try_blobs(:raw_binary?) + def empty? + valid_blobs.map(&:empty?).all? + end + + def binary? + strong_memoize(:is_binary) do + try_blobs(:binary?) + end end - def raw_text? - !raw_binary? && !different_type? + def text? + strong_memoize(:is_text) do + !binary? && !different_type? + end end def simple_viewer @@ -333,19 +373,19 @@ module Gitlab return DiffViewer::NotDiffable unless diffable? if content_changed? - if raw_text? + if text? DiffViewer::Text else DiffViewer::NoPreview end elsif new_file? - if raw_text? + if text? DiffViewer::Text else DiffViewer::Added end elsif deleted_file? - if raw_text? + if text? DiffViewer::Text else DiffViewer::Deleted diff --git a/lib/gitlab/diff/line.rb b/lib/gitlab/diff/line.rb index f0c4977fc50..001748afb41 100644 --- a/lib/gitlab/diff/line.rb +++ b/lib/gitlab/diff/line.rb @@ -73,6 +73,10 @@ module Gitlab !meta? end + def suggestible? + !removed? + end + def rich_text @parent_file.try(:highlight_lines!) if @parent_file && !@rich_text diff --git a/lib/gitlab/discussions_diff/file_collection.rb b/lib/gitlab/discussions_diff/file_collection.rb new file mode 100644 index 00000000000..4ab7314f509 --- /dev/null +++ b/lib/gitlab/discussions_diff/file_collection.rb @@ -0,0 +1,76 @@ +# frozen_string_literal: true + +module Gitlab + module DiscussionsDiff + class FileCollection + include Gitlab::Utils::StrongMemoize + + def initialize(collection) + @collection = collection + end + + # Returns a Gitlab::Diff::File with the given ID (`unique_identifier` in + # Gitlab::Diff::File). + def find_by_id(id) + diff_files_indexed_by_id[id] + end + + # Writes cache and preloads highlighted diff lines for + # object IDs, in @collection. + # + # highlightable_ids - Diff file `Array` responding to ID. The ID will be used + # to generate the cache key. + # + # - Highlight cache is written just for uncached diff files + # - The cache content is not updated (there's no need to do so) + def load_highlight(highlightable_ids) + preload_highlighted_lines(highlightable_ids) + end + + private + + def preload_highlighted_lines(ids) + cached_content = read_cache(ids) + + uncached_ids = ids.select.each_with_index { |_, i| cached_content[i].nil? } + mapping = highlighted_lines_by_ids(uncached_ids) + + HighlightCache.write_multiple(mapping) + + diffs = diff_files_indexed_by_id.values_at(*ids) + + diffs.zip(cached_content).each do |diff, cached_lines| + next unless diff && cached_lines + + diff.highlighted_diff_lines = cached_lines + end + end + + def read_cache(ids) + HighlightCache.read_multiple(ids) + end + + def diff_files_indexed_by_id + strong_memoize(:diff_files_indexed_by_id) do + diff_files.index_by(&:unique_identifier) + end + end + + def diff_files + strong_memoize(:diff_files) do + @collection.map(&:raw_diff_file) + end + end + + # Processes the diff lines highlighting for diff files matching the given + # IDs. + # + # Returns a Hash with { id => [Array of Gitlab::Diff::line], ...] + def highlighted_lines_by_ids(ids) + diff_files_indexed_by_id.slice(*ids).each_with_object({}) do |(id, file), hash| + hash[id] = file.highlighted_diff_lines.map(&:to_hash) + end + end + end + end +end diff --git a/lib/gitlab/discussions_diff/highlight_cache.rb b/lib/gitlab/discussions_diff/highlight_cache.rb new file mode 100644 index 00000000000..270cfb89488 --- /dev/null +++ b/lib/gitlab/discussions_diff/highlight_cache.rb @@ -0,0 +1,67 @@ +# frozen_string_literal: true +# +module Gitlab + module DiscussionsDiff + class HighlightCache + class << self + VERSION = 1 + EXPIRATION = 1.week + + # Sets multiple keys to a given value. The value + # is serialized as JSON. + # + # mapping - Write multiple cache values at once + def write_multiple(mapping) + Redis::Cache.with do |redis| + redis.multi do |multi| + mapping.each do |raw_key, value| + key = cache_key_for(raw_key) + + multi.set(key, value.to_json, ex: EXPIRATION) + end + end + end + end + + # Reads multiple cache keys at once. + # + # raw_keys - An Array of unique cache keys, without namespaces. + # + # It returns a list of deserialized diff lines. Ex.: + # [[Gitlab::Diff::Line, ...], [Gitlab::Diff::Line]] + def read_multiple(raw_keys) + return [] if raw_keys.empty? + + keys = raw_keys.map { |id| cache_key_for(id) } + + content = + Redis::Cache.with do |redis| + redis.mget(keys) + end + + content.map! do |lines| + next unless lines + + JSON.parse(lines).map! do |line| + line = line.with_indifferent_access + rich_text = line[:rich_text] + line[:rich_text] = rich_text&.html_safe + + Gitlab::Diff::Line.init_from_hash(line) + end + end + end + + def cache_key_for(raw_key) + "#{cache_key_prefix}:#{raw_key}" + end + + private + + def cache_key_prefix + "#{Redis::Cache::CACHE_NAMESPACE}:#{VERSION}:discussion-highlight" + end + end + end + end +end diff --git a/lib/gitlab/fake_application_settings.rb b/lib/gitlab/fake_application_settings.rb index db1aeeea8d3..bd806269bf0 100644 --- a/lib/gitlab/fake_application_settings.rb +++ b/lib/gitlab/fake_application_settings.rb @@ -37,5 +37,9 @@ module Gitlab def pick_repository_storage repository_storages.sample end + + def commit_email_hostname + super.presence || ApplicationSetting.default_commit_email_hostname + end end end diff --git a/lib/gitlab/git.rb b/lib/gitlab/git.rb index c4aac228b2f..44a62586a23 100644 --- a/lib/gitlab/git.rb +++ b/lib/gitlab/git.rb @@ -54,11 +54,11 @@ module Gitlab end def tag_ref?(ref) - ref.start_with?(TAG_REF_PREFIX) + ref =~ /^#{TAG_REF_PREFIX}.+/ end def branch_ref?(ref) - ref.start_with?(BRANCH_REF_PREFIX) + ref =~ /^#{BRANCH_REF_PREFIX}.+/ end def blank_ref?(ref) diff --git a/lib/gitlab/git/blob.rb b/lib/gitlab/git/blob.rb index 2d25389594e..259a2b7911a 100644 --- a/lib/gitlab/git/blob.rb +++ b/lib/gitlab/git/blob.rb @@ -100,7 +100,7 @@ module Gitlab @loaded_all_data = @loaded_size == size end - def binary? + def binary_in_repo? @binary.nil? ? super : @binary == true end @@ -174,7 +174,7 @@ module Gitlab private def has_lfs_version_key? - !empty? && text? && data.start_with?("version https://git-lfs.github.com/spec") + !empty? && text_in_repo? && data.start_with?("version https://git-lfs.github.com/spec") end end end diff --git a/lib/gitlab/git/object_pool.rb b/lib/gitlab/git/object_pool.rb index 558699a6318..1c6242b444a 100644 --- a/lib/gitlab/git/object_pool.rb +++ b/lib/gitlab/git/object_pool.rb @@ -8,7 +8,7 @@ module Gitlab GL_REPOSITORY = "" delegate :exists?, :size, to: :repository - delegate :delete, to: :object_pool_service + delegate :unlink_repository, :delete, to: :object_pool_service attr_reader :storage, :relative_path, :source_repository @@ -23,13 +23,6 @@ module Gitlab end def link(to_link_repo) - remote_name = to_link_repo.object_pool_remote_name - repository.set_config( - "remote.#{remote_name}.url" => relative_path_to(to_link_repo.relative_path), - "remote.#{remote_name}.tagOpt" => "--no-tags", - "remote.#{remote_name}.fetch" => "+refs/*:refs/remotes/#{remote_name}/*" - ) - object_pool_service.link_repository(to_link_repo) end diff --git a/lib/gitlab/git_post_receive.rb b/lib/gitlab/git_post_receive.rb index cf2329e489d..426436c2164 100644 --- a/lib/gitlab/git_post_receive.rb +++ b/lib/gitlab/git_post_receive.rb @@ -3,12 +3,13 @@ module Gitlab class GitPostReceive include Gitlab::Identifier - attr_reader :project, :identifier, :changes + attr_reader :project, :identifier, :changes, :push_options - def initialize(project, identifier, changes) + def initialize(project, identifier, changes, push_options) @project = project @identifier = identifier @changes = deserialize_changes(changes) + @push_options = push_options end def identify diff --git a/lib/gitlab/gitaly_client.rb b/lib/gitlab/gitaly_client.rb index 11021ee06b3..8bf8a3b53cd 100644 --- a/lib/gitlab/gitaly_client.rb +++ b/lib/gitlab/gitaly_client.rb @@ -26,6 +26,7 @@ module Gitlab end end + PEM_REGEX = /\-+BEGIN CERTIFICATE\-+.+?\-+END CERTIFICATE\-+/m SERVER_VERSION_FILE = 'GITALY_SERVER_VERSION' MAXIMUM_GITALY_CALLS = 35 CLIENT_NAME = (Sidekiq.server? ? 'gitlab-sidekiq' : 'gitlab-web').freeze @@ -50,11 +51,42 @@ module Gitlab @stubs[storage][name] ||= begin klass = stub_class(name) addr = stub_address(storage) - klass.new(addr, :this_channel_is_insecure) + creds = stub_creds(storage) + klass.new(addr, creds) end end end + def self.stub_cert_paths + cert_paths = Dir["#{OpenSSL::X509::DEFAULT_CERT_DIR}/*"] + cert_paths << OpenSSL::X509::DEFAULT_CERT_FILE if File.exist? OpenSSL::X509::DEFAULT_CERT_FILE + cert_paths + end + + def self.stub_certs + return @certs if @certs + + @certs = stub_cert_paths.flat_map do |cert_file| + File.read(cert_file).scan(PEM_REGEX).map do |cert| + begin + OpenSSL::X509::Certificate.new(cert).to_pem + rescue OpenSSL::OpenSSLError => e + Rails.logger.error "Could not load certificate #{cert_file} #{e}" + Gitlab::Sentry.track_exception(e, extra: { cert_file: cert_file }) + nil + end + end.compact + end.uniq.join("\n") + end + + def self.stub_creds(storage) + if URI(address(storage)).scheme == 'tls' + GRPC::Core::ChannelCredentials.new stub_certs + else + :this_channel_is_insecure + end + end + def self.stub_class(name) if name == :health_check Grpc::Health::V1::Health::Stub @@ -64,9 +96,7 @@ module Gitlab end def self.stub_address(storage) - addr = address(storage) - addr = addr.sub(%r{^tcp://}, '') if URI(addr).scheme == 'tcp' - addr + address(storage).sub(%r{^tcp://|^tls://}, '') end def self.clear_stubs! @@ -88,8 +118,8 @@ module Gitlab raise "storage #{storage.inspect} is missing a gitaly_address" end - unless URI(address).scheme.in?(%w(tcp unix)) - raise "Unsupported Gitaly address: #{address.inspect} does not use URL scheme 'tcp' or 'unix'" + unless URI(address).scheme.in?(%w(tcp unix tls)) + raise "Unsupported Gitaly address: #{address.inspect} does not use URL scheme 'tcp' or 'unix' or 'tls'" end address diff --git a/lib/gitlab/gitaly_client/cleanup_service.rb b/lib/gitlab/gitaly_client/cleanup_service.rb index 8e412a9b3ef..3e8d6a773ca 100644 --- a/lib/gitlab/gitaly_client/cleanup_service.rb +++ b/lib/gitlab/gitaly_client/cleanup_service.rb @@ -20,6 +20,7 @@ module Gitlab while data = io.read(RepositoryService::MAX_MSG_SIZE) y.yield Gitaly::ApplyBfgObjectMapRequest.new(object_map: data) + break if io&.eof? end end diff --git a/lib/gitlab/gitaly_client/object_pool_service.rb b/lib/gitlab/gitaly_client/object_pool_service.rb index 272ce73ad64..6e7ede5fd18 100644 --- a/lib/gitlab/gitaly_client/object_pool_service.rb +++ b/lib/gitlab/gitaly_client/object_pool_service.rb @@ -35,7 +35,10 @@ module Gitlab end def unlink_repository(repository) - request = Gitaly::UnlinkRepositoryFromObjectPoolRequest.new(repository: repository.gitaly_repository) + request = Gitaly::UnlinkRepositoryFromObjectPoolRequest.new( + object_pool: object_pool, + repository: repository.gitaly_repository + ) GitalyClient.call(storage, :object_pool_service, :unlink_repository_from_object_pool, request, timeout: GitalyClient.fast_timeout) diff --git a/lib/gitlab/gpg.rb b/lib/gitlab/gpg.rb index e53c2d00743..32f61b1d65c 100644 --- a/lib/gitlab/gpg.rb +++ b/lib/gitlab/gpg.rb @@ -73,13 +73,7 @@ module Gitlab if MUTEX.locked? && MUTEX.owned? optimistic_using_tmp_keychain(&block) else - if Gitlab.rails5? - ActiveSupport::Dependencies.interlock.permit_concurrent_loads do - MUTEX.synchronize do - optimistic_using_tmp_keychain(&block) - end - end - else + ActiveSupport::Dependencies.interlock.permit_concurrent_loads do MUTEX.synchronize do optimistic_using_tmp_keychain(&block) end diff --git a/lib/gitlab/graphql.rb b/lib/gitlab/graphql.rb index 74c04e5380e..8a59e83974f 100644 --- a/lib/gitlab/graphql.rb +++ b/lib/gitlab/graphql.rb @@ -3,5 +3,9 @@ module Gitlab module Graphql StandardGraphqlError = Class.new(StandardError) + + def self.enabled? + Feature.enabled?(:graphql, default_enabled: true) + end end end diff --git a/lib/gitlab/import_export/command_line_util.rb b/lib/gitlab/import_export/command_line_util.rb index c9e2a6a78d9..bdecff0931c 100644 --- a/lib/gitlab/import_export/command_line_util.rb +++ b/lib/gitlab/import_export/command_line_util.rb @@ -3,7 +3,8 @@ module Gitlab module ImportExport module CommandLineUtil - DEFAULT_MODE = 0700 + UNTAR_MASK = 'u+rwX,go+rX,go-w' + DEFAULT_DIR_MODE = 0700 def tar_czf(archive:, dir:) tar_with_options(archive: archive, dir: dir, options: 'czf') @@ -14,8 +15,8 @@ module Gitlab end def mkdir_p(path) - FileUtils.mkdir_p(path, mode: DEFAULT_MODE) - FileUtils.chmod(DEFAULT_MODE, path) + FileUtils.mkdir_p(path, mode: DEFAULT_DIR_MODE) + FileUtils.chmod(DEFAULT_DIR_MODE, path) end private @@ -41,6 +42,7 @@ module Gitlab def untar_with_options(archive:, dir:, options:) execute(%W(tar -#{options} #{archive} -C #{dir})) + execute(%W(chmod -R #{UNTAR_MASK} #{dir})) end def execute(cmd) diff --git a/lib/gitlab/import_export/import_export.yml b/lib/gitlab/import_export/import_export.yml index d10d4f2f746..25cdd5ab121 100644 --- a/lib/gitlab/import_export/import_export.yml +++ b/lib/gitlab/import_export/import_export.yml @@ -27,7 +27,8 @@ project_tree: - :award_emoji - notes: :author - - :releases + - releases: + :author - project_members: - :user - merge_requests: @@ -62,7 +63,6 @@ project_tree: - :triggers - :pipeline_schedules - :services - - :hooks - protected_branches: - :merge_access_levels - :push_access_levels @@ -155,12 +155,6 @@ excluded_attributes: - :reference - :reference_html - :epic_id - hooks: - - :token - - :encrypted_token - - :encrypted_token_iv - - :encrypted_url - - :encrypted_url_iv runners: - :token - :token_encrypted diff --git a/lib/gitlab/json_cache.rb b/lib/gitlab/json_cache.rb new file mode 100644 index 00000000000..1adf83739ad --- /dev/null +++ b/lib/gitlab/json_cache.rb @@ -0,0 +1,87 @@ +# frozen_string_literal: true + +module Gitlab + class JsonCache + attr_reader :backend, :cache_key_with_version, :namespace + + def initialize(options = {}) + @backend = options.fetch(:backend, Rails.cache) + @namespace = options.fetch(:namespace, nil) + @cache_key_with_version = options.fetch(:cache_key_with_version, true) + end + + def active? + if backend.respond_to?(:active?) + backend.active? + else + true + end + end + + def cache_key(key) + expanded_cache_key = [namespace, key].compact + + if cache_key_with_version + expanded_cache_key << Rails.version + end + + expanded_cache_key.join(':') + end + + def expire(key) + backend.delete(cache_key(key)) + end + + def read(key, klass = nil) + value = backend.read(cache_key(key)) + value = parse_value(value, klass) if value + value + end + + def write(key, value, options = nil) + backend.write(cache_key(key), value.to_json, options) + end + + def fetch(key, options = {}, &block) + klass = options.delete(:as) + value = read(key, klass) + + return value unless value.nil? + + value = yield + + write(key, value, options) + + value + end + + private + + def parse_value(raw, klass) + value = ActiveSupport::JSON.decode(raw) + + case value + when Hash then parse_entry(value, klass) + when Array then parse_entries(value, klass) + else + value + end + rescue ActiveSupport::JSON.parse_error + nil + end + + def parse_entry(raw, klass) + klass.new(raw) if valid_entry?(raw, klass) + end + + def valid_entry?(raw, klass) + return false unless klass && raw.is_a?(Hash) + + (raw.keys - klass.attribute_names).empty? + end + + def parse_entries(values, klass) + values.map { |value| parse_entry(value, klass) }.compact + end + end +end diff --git a/lib/gitlab/lfs_token.rb b/lib/gitlab/lfs_token.rb index 05d3096a208..c09d3ebc7be 100644 --- a/lib/gitlab/lfs_token.rb +++ b/lib/gitlab/lfs_token.rb @@ -2,10 +2,21 @@ module Gitlab class LfsToken - attr_accessor :actor + module LfsTokenHelper + def user? + actor.is_a?(User) + end + + def actor_name + user? ? actor.username : "lfs+deploy-key-#{actor.id}" + end + end + + include LfsTokenHelper - TOKEN_LENGTH = 50 - EXPIRY_TIME = 1800 + DEFAULT_EXPIRE_TIME = 1800 + + attr_accessor :actor def initialize(actor) @actor = @@ -19,36 +30,108 @@ module Gitlab end end - def token - Gitlab::Redis::SharedState.with do |redis| - token = redis.get(redis_shared_state_key) - token ||= Devise.friendly_token(TOKEN_LENGTH) - redis.set(redis_shared_state_key, token, ex: EXPIRY_TIME) + def token(expire_time: DEFAULT_EXPIRE_TIME) + HMACToken.new(actor).token(expire_time) + end - token - end + def token_valid?(token_to_check) + HMACToken.new(actor).token_valid?(token_to_check) || + LegacyRedisDeviseToken.new(actor).token_valid?(token_to_check) end def deploy_key_pushable?(project) actor.is_a?(DeployKey) && actor.can_push_to?(project) end - def user? - actor.is_a?(User) - end - def type user? ? :lfs_token : :lfs_deploy_token end - def actor_name - actor.is_a?(User) ? actor.username : "lfs+deploy-key-#{actor.id}" + private # rubocop:disable Lint/UselessAccessModifier + + class HMACToken + include LfsTokenHelper + + def initialize(actor) + @actor = actor + end + + def token(expire_time) + hmac_token = JSONWebToken::HMACToken.new(secret) + hmac_token.expire_time = Time.now + expire_time + hmac_token[:data] = { actor: actor_name } + hmac_token.encoded + end + + def token_valid?(token_to_check) + decoded_token = JSONWebToken::HMACToken.decode(token_to_check, secret).first + decoded_token.dig('data', 'actor') == actor_name + rescue JWT::DecodeError + false + end + + private + + attr_reader :actor + + def secret + salt + key + end + + def salt + case actor + when DeployKey, Key + actor.fingerprint.delete(':').first(16) + when User + # Take the last 16 characters as they're more unique than the first 16 + actor.id.to_s + actor.encrypted_password.last(16) + end + end + + def key + # Take 16 characters of attr_encrypted_db_key_base, as that's what the + # cipher needs exactly + Settings.attr_encrypted_db_key_base.first(16) + end end - private + # TODO: LegacyRedisDeviseToken and references need to be removed after + # next released milestone + # + class LegacyRedisDeviseToken + TOKEN_LENGTH = 50 + DEFAULT_EXPIRY_TIME = 1800 * 1000 # 30 mins + + def initialize(actor) + @actor = actor + end + + def token_valid?(token_to_check) + Devise.secure_compare(stored_token, token_to_check) + end + + def stored_token + Gitlab::Redis::SharedState.with { |redis| redis.get(state_key) } + end + + # This method exists purely to facilitate legacy testing to ensure the + # same redis key is used. + # + def store_new_token(expiry_time_in_ms = DEFAULT_EXPIRY_TIME) + Gitlab::Redis::SharedState.with do |redis| + new_token = Devise.friendly_token(TOKEN_LENGTH) + redis.set(state_key, new_token, px: expiry_time_in_ms) + new_token + end + end + + private - def redis_shared_state_key - "gitlab:lfs_token:#{actor.class.name.underscore}_#{actor.id}" if actor + attr_reader :actor + + def state_key + "gitlab:lfs_token:#{actor.class.name.underscore}_#{actor.id}" + end end end end diff --git a/lib/gitlab/middleware/correlation_id.rb b/lib/gitlab/middleware/correlation_id.rb index 73542dd422e..80dddc41c12 100644 --- a/lib/gitlab/middleware/correlation_id.rb +++ b/lib/gitlab/middleware/correlation_id.rb @@ -20,11 +20,7 @@ module Gitlab private def correlation_id(env) - if Gitlab.rails5? - request(env).request_id - else - request(env).uuid - end + request(env).request_id end def request(env) diff --git a/lib/gitlab/middleware/go.rb b/lib/gitlab/middleware/go.rb index d1a87c3b3bb..72a788022ef 100644 --- a/lib/gitlab/middleware/go.rb +++ b/lib/gitlab/middleware/go.rb @@ -6,6 +6,7 @@ module Gitlab module Middleware class Go include ActionView::Helpers::TagHelper + include ActionController::HttpAuthentication::Basic PROJECT_PATH_REGEX = %r{\A(#{Gitlab::PathRegex.full_namespace_route_regex}/#{Gitlab::PathRegex.project_route_regex})/}.freeze @@ -14,7 +15,7 @@ module Gitlab end def call(env) - request = Rack::Request.new(env) + request = ActionDispatch::Request.new(env) render_go_doc(request) || @app.call(env) end @@ -110,21 +111,23 @@ module Gitlab def project_for_paths(paths, request) project = Project.where_full_path_in(paths).first - return unless Ability.allowed?(current_user(request), :read_project, project) + return unless Ability.allowed?(current_user(request, project), :read_project, project) project end - def current_user(request) - authenticator = Gitlab::Auth::RequestAuthenticator.new(request) - user = authenticator.find_user_from_access_token || authenticator.find_user_from_warden + def current_user(request, project) + return unless has_basic_credentials?(request) - return unless user&.can?(:access_api) + login, password = user_name_and_password(request) + auth_result = Gitlab::Auth.find_for_git_client(login, password, project: project, ip: request.ip) + return unless auth_result.success? - # Right now, the `api` scope is the only one that should be able to determine private project existence. - return unless authenticator.valid_access_token?(scopes: [:api]) + return unless auth_result.actor&.can?(:access_git) - user + return unless auth_result.authentication_abilities.include?(:read_project) + + auth_result.actor end end end diff --git a/lib/gitlab/group_hierarchy.rb b/lib/gitlab/object_hierarchy.rb index 97cbdc6cb39..f2772c733c7 100644 --- a/lib/gitlab/group_hierarchy.rb +++ b/lib/gitlab/object_hierarchy.rb @@ -1,16 +1,16 @@ # frozen_string_literal: true module Gitlab - # Retrieving of parent or child groups based on a base ActiveRecord relation. + # Retrieving of parent or child objects based on a base ActiveRecord relation. # # This class uses recursive CTEs and as a result will only work on PostgreSQL. - class GroupHierarchy + class ObjectHierarchy attr_reader :ancestors_base, :descendants_base, :model # ancestors_base - An instance of ActiveRecord::Relation for which to - # get parent groups. + # get parent objects. # descendants_base - An instance of ActiveRecord::Relation for which to - # get child groups. If omitted, ancestors_base is used. + # get child objects. If omitted, ancestors_base is used. def initialize(ancestors_base, descendants_base = ancestors_base) raise ArgumentError.new("Model of ancestors_base does not match model of descendants_base") if ancestors_base.model != descendants_base.model @@ -39,7 +39,7 @@ module Gitlab end # rubocop: enable CodeReuse/ActiveRecord - # Returns a relation that includes the ancestors_base set of groups + # Returns a relation that includes the ancestors_base set of objects # and all their ancestors (recursively). # # Passing an `upto` will stop the recursion once the specified parent_id is @@ -47,13 +47,13 @@ module Gitlab # included. # # Passing a `hierarchy_order` with either `:asc` or `:desc` will cause the - # recursive query order from most nested group to root or from the root - # ancestor to most nested group respectively. This uses a `depth` column + # recursive query order from most nested object to root or from the root + # ancestor to most nested object respectively. This uses a `depth` column # where `1` is defined as the depth for the base and increment as we go up # each parent. # rubocop: disable CodeReuse/ActiveRecord def base_and_ancestors(upto: nil, hierarchy_order: nil) - return ancestors_base unless Group.supports_nested_groups? + return ancestors_base unless hierarchy_supported? recursive_query = base_and_ancestors_cte(upto, hierarchy_order).apply_to(model.all) recursive_query = recursive_query.order(depth: hierarchy_order) if hierarchy_order @@ -62,16 +62,16 @@ module Gitlab end # rubocop: enable CodeReuse/ActiveRecord - # Returns a relation that includes the descendants_base set of groups + # Returns a relation that includes the descendants_base set of objects # and all their descendants (recursively). def base_and_descendants - return descendants_base unless Group.supports_nested_groups? + return descendants_base unless hierarchy_supported? read_only(base_and_descendants_cte.apply_to(model.all)) end - # Returns a relation that includes the base groups, their ancestors, - # and the descendants of the base groups. + # Returns a relation that includes the base objects, their ancestors, + # and the descendants of the base objects. # # The resulting query will roughly look like the following: # @@ -91,16 +91,16 @@ module Gitlab # Using this approach allows us to further add criteria to the relation with # Rails thinking it's selecting data the usual way. # - # If nested groups are not supported, ancestors_base is returned. + # If nested objects are not supported, ancestors_base is returned. # rubocop: disable CodeReuse/ActiveRecord - def all_groups - return ancestors_base unless Group.supports_nested_groups? + def all_objects + return ancestors_base unless hierarchy_supported? ancestors = base_and_ancestors_cte descendants = base_and_descendants_cte - ancestors_table = ancestors.alias_to(groups_table) - descendants_table = descendants.alias_to(groups_table) + ancestors_table = ancestors.alias_to(objects_table) + descendants_table = descendants.alias_to(objects_table) relation = model .unscoped @@ -117,23 +117,27 @@ module Gitlab private + def hierarchy_supported? + Gitlab::Database.postgresql? + end + # rubocop: disable CodeReuse/ActiveRecord def base_and_ancestors_cte(stop_id = nil, hierarchy_order = nil) cte = SQL::RecursiveCTE.new(:base_and_ancestors) depth_column = :depth base_query = ancestors_base.except(:order) - base_query = base_query.select("1 as #{depth_column}", groups_table[Arel.star]) if hierarchy_order + base_query = base_query.select("1 as #{depth_column}", objects_table[Arel.star]) if hierarchy_order cte << base_query # Recursively get all the ancestors of the base set. parent_query = model - .from([groups_table, cte.table]) - .where(groups_table[:id].eq(cte.table[:parent_id])) + .from([objects_table, cte.table]) + .where(objects_table[:id].eq(cte.table[:parent_id])) .except(:order) - parent_query = parent_query.select(cte.table[depth_column] + 1, groups_table[Arel.star]) if hierarchy_order + parent_query = parent_query.select(cte.table[depth_column] + 1, objects_table[Arel.star]) if hierarchy_order parent_query = parent_query.where(cte.table[:parent_id].not_eq(stop_id)) if stop_id cte << parent_query @@ -149,15 +153,15 @@ module Gitlab # Recursively get all the descendants of the base set. cte << model - .from([groups_table, cte.table]) - .where(groups_table[:parent_id].eq(cte.table[:id])) + .from([objects_table, cte.table]) + .where(objects_table[:parent_id].eq(cte.table[:id])) .except(:order) cte end # rubocop: enable CodeReuse/ActiveRecord - def groups_table + def objects_table model.arel_table end diff --git a/lib/gitlab/prometheus/metric_group.rb b/lib/gitlab/prometheus/metric_group.rb index 8f30cdee232..394556e8708 100644 --- a/lib/gitlab/prometheus/metric_group.rb +++ b/lib/gitlab/prometheus/metric_group.rb @@ -10,9 +10,15 @@ module Gitlab validates :name, :priority, :metrics, presence: true def self.common_metrics - ::PrometheusMetric.common.group_by(&:group_title).map do |name, metrics| - MetricGroup.new(name: name, priority: 0, metrics: metrics.map(&:to_query_metric)) + all_groups = ::PrometheusMetric.common.group_by(&:group_title).map do |name, metrics| + MetricGroup.new( + name: name, + priority: metrics.map(&:priority).max, + metrics: metrics.map(&:to_query_metric) + ) end + + all_groups.sort_by(&:priority).reverse end # EE only diff --git a/lib/gitlab/safe_request_store.rb b/lib/gitlab/safe_request_store.rb index 4e82353adb6..d146913bdb3 100644 --- a/lib/gitlab/safe_request_store.rb +++ b/lib/gitlab/safe_request_store.rb @@ -19,5 +19,13 @@ module Gitlab NULL_STORE end end + + # This method accept an options hash to be compatible with + # ActiveSupport::Cache::Store#write method. The options are + # not passed to the underlying cache implementation because + # RequestStore#write accepts only a key, and value params. + def self.write(key, value, options = nil) + store.write(key, value) + end end end diff --git a/lib/gitlab/upgrader.rb b/lib/gitlab/upgrader.rb deleted file mode 100644 index ccab0e4dd73..00000000000 --- a/lib/gitlab/upgrader.rb +++ /dev/null @@ -1,111 +0,0 @@ -# frozen_string_literal: true - -module Gitlab - class Upgrader - def execute - puts "GitLab #{current_version.major} upgrade tool" - puts "Your version is #{current_version}" - puts "Latest available version for GitLab #{current_version.major} is #{latest_version}" - - if latest_version? - puts "You are using the latest GitLab version" - else - puts "Newer GitLab version is available" - - answer = if ARGV.first == "-y" - "yes" - else - prompt("Do you want to upgrade (yes/no)? ", %w{yes no}) - end - - if answer == "yes" - upgrade - else - exit 0 - end - end - end - - def latest_version? - current_version >= latest_version - end - - def current_version - @current_version ||= Gitlab::VersionInfo.parse(current_version_raw) - end - - def latest_version - @latest_version ||= Gitlab::VersionInfo.parse(latest_version_raw) - end - - def current_version_raw - File.read(File.join(gitlab_path, "VERSION")).strip - end - - def latest_version_raw - git_tags = fetch_git_tags - git_tags = git_tags.select { |version| version =~ /v\d+\.\d+\.\d+\Z/ } - git_versions = git_tags.map { |tag| Gitlab::VersionInfo.parse(tag.match(/v\d+\.\d+\.\d+/).to_s) } - "v#{git_versions.sort.last}" - end - - def fetch_git_tags - remote_tags, _ = Gitlab::Popen.popen(%W(#{Gitlab.config.git.bin_path} ls-remote --tags https://gitlab.com/gitlab-org/gitlab-ce.git)) - remote_tags.split("\n").grep(%r{tags/v#{current_version.major}}) - end - - def update_commands - { - "Stash changed files" => %W(#{Gitlab.config.git.bin_path} stash), - "Get latest code" => %W(#{Gitlab.config.git.bin_path} fetch), - "Switch to new version" => %W(#{Gitlab.config.git.bin_path} checkout v#{latest_version}), - "Install gems" => %w(bundle), - "Migrate DB" => %w(bundle exec rake db:migrate), - "Recompile assets" => %w(bundle exec rake yarn:install gitlab:assets:clean gitlab:assets:compile), - "Clear cache" => %w(bundle exec rake cache:clear) - } - end - - def env - { - 'RAILS_ENV' => 'production', - 'NODE_ENV' => 'production' - } - end - - def upgrade - update_commands.each do |title, cmd| - puts title - puts " -> #{cmd.join(' ')}" - - if system(env, *cmd) - puts " -> OK" - else - puts " -> FAILED" - puts "Failed to upgrade. Try to repeat task or proceed with upgrade manually " - exit 1 - end - end - - puts "Done" - end - - def gitlab_path - File.expand_path(File.join(File.dirname(__FILE__), '../..')) - end - - # Prompt the user to input something - # - # message - the message to display before input - # choices - array of strings of acceptable answers or nil for any answer - # - # Returns the user's answer - def prompt(message, choices = nil) - begin - print(message) - answer = STDIN.gets.chomp - end while !choices.include?(answer) - answer - end - end -end diff --git a/lib/gitlab/usage_data.rb b/lib/gitlab/usage_data.rb index 008e9cd1d24..083c620267a 100644 --- a/lib/gitlab/usage_data.rb +++ b/lib/gitlab/usage_data.rb @@ -85,6 +85,8 @@ module Gitlab releases: count(Release), remote_mirrors: count(RemoteMirror), snippets: count(Snippet), + suggestions: count(Suggestion), + todos: count(Todo), uploads: count(Upload), web_hooks: count(WebHook) }.merge(services_usage).merge(approximate_counts) diff --git a/lib/gitlab/utils/override.rb b/lib/gitlab/utils/override.rb index c412961ea3f..c87e97d0213 100644 --- a/lib/gitlab/utils/override.rb +++ b/lib/gitlab/utils/override.rb @@ -4,16 +4,11 @@ module Gitlab module Utils module Override class Extension - def self.verify_class!(klass, method_name) - instance_method_defined?(klass, method_name) || - raise( - NotImplementedError.new( - "#{klass}\##{method_name} doesn't exist!")) - end - - def self.instance_method_defined?(klass, name, include_super: true) - klass.instance_methods(include_super).include?(name) || - klass.private_instance_methods(include_super).include?(name) + def self.verify_class!(klass, method_name, arity) + extension = new(klass) + parents = extension.parents_for(klass) + extension.verify_method!( + klass: klass, parents: parents, method_name: method_name, sub_method_arity: arity) end attr_reader :subject @@ -22,35 +17,77 @@ module Gitlab @subject = subject end - def add_method_name(method_name) - method_names << method_name - end - - def add_class(klass) - classes << klass + def parents_for(klass) + index = klass.ancestors.index(subject) + klass.ancestors.drop(index + 1) end def verify! classes.each do |klass| - index = klass.ancestors.index(subject) - parents = klass.ancestors.drop(index + 1) - - method_names.each do |method_name| - parents.any? do |parent| - self.class.instance_method_defined?( - parent, method_name, include_super: false) - end || - raise( - NotImplementedError.new( - "#{klass}\##{method_name} doesn't exist!")) + parents = parents_for(klass) + + method_names.each_pair do |method_name, arity| + verify_method!( + klass: klass, + parents: parents, + method_name: method_name, + sub_method_arity: arity) end end end + def verify_method!(klass:, parents:, method_name:, sub_method_arity:) + overridden_parent = parents.find do |parent| + instance_method_defined?(parent, method_name) + end + + raise NotImplementedError.new("#{klass}\##{method_name} doesn't exist!") unless overridden_parent + + super_method_arity = find_direct_method(overridden_parent, method_name).arity + + unless arity_compatible?(sub_method_arity, super_method_arity) + raise NotImplementedError.new("#{subject}\##{method_name} has arity of #{sub_method_arity}, but #{overridden_parent}\##{method_name} has arity of #{super_method_arity}") + end + end + + def add_method_name(method_name, arity = nil) + method_names[method_name] = arity + end + + def add_class(klass) + classes << klass + end + + def verify_override?(method_name) + method_names.has_key?(method_name) + end + private + def instance_method_defined?(klass, name) + klass.instance_methods(false).include?(name) || + klass.private_instance_methods(false).include?(name) + end + + def find_direct_method(klass, name) + method = klass.instance_method(name) + method = method.super_method until method && klass == method.owner + method + end + + def arity_compatible?(sub_method_arity, super_method_arity) + if sub_method_arity >= 0 && super_method_arity >= 0 + # Regular arguments + sub_method_arity == super_method_arity + else + # It's too complex to check this case, just allow sub-method having negative arity + # But we don't allow sub_method_arity > 0 yet super_method_arity < 0 + sub_method_arity < 0 + end + end + def method_names - @method_names ||= [] + @method_names ||= {} end def classes @@ -80,11 +117,21 @@ module Gitlab def override(method_name) return unless ENV['STATIC_VERIFICATION'] + Override.extensions[self] ||= Extension.new(self) + Override.extensions[self].add_method_name(method_name) + end + + def method_added(method_name) + super + + return unless ENV['STATIC_VERIFICATION'] + return unless Override.extensions[self]&.verify_override?(method_name) + + method_arity = instance_method(method_name).arity if is_a?(Class) - Extension.verify_class!(self, method_name) + Extension.verify_class!(self, method_name, method_arity) else # We delay the check for modules - Override.extensions[self] ||= Extension.new(self) - Override.extensions[self].add_method_name(method_name) + Override.extensions[self].add_method_name(method_name, method_arity) end end diff --git a/lib/json_web_token/hmac_token.rb b/lib/json_web_token/hmac_token.rb index ceb1b9c913f..ec0917ab49d 100644 --- a/lib/json_web_token/hmac_token.rb +++ b/lib/json_web_token/hmac_token.rb @@ -18,7 +18,7 @@ module JSONWebToken end def encoded - JWT.encode(payload, secret, JWT_ALGORITHM) + JWT.encode(payload, secret, JWT_ALGORITHM, { typ: 'JWT' }) end private diff --git a/lib/json_web_token/rsa_token.rb b/lib/json_web_token/rsa_token.rb index 160e1e506f1..bcce811cd28 100644 --- a/lib/json_web_token/rsa_token.rb +++ b/lib/json_web_token/rsa_token.rb @@ -11,7 +11,8 @@ module JSONWebToken def encoded headers = { - kid: kid + kid: kid, + typ: 'JWT' } JWT.encode(payload, key, 'RS256', headers) end diff --git a/lib/mysql_zero_date.rb b/lib/mysql_zero_date.rb index 216560148fa..f36610abf8f 100644 --- a/lib/mysql_zero_date.rb +++ b/lib/mysql_zero_date.rb @@ -17,4 +17,4 @@ module MysqlZeroDate end end -ActiveRecord::ConnectionAdapters::AbstractMysqlAdapter.prepend(MysqlZeroDate) if Gitlab.rails5? +ActiveRecord::ConnectionAdapters::AbstractMysqlAdapter.prepend(MysqlZeroDate) diff --git a/lib/rails4_migration_version.rb b/lib/rails4_migration_version.rb deleted file mode 100644 index ae48734dfad..00000000000 --- a/lib/rails4_migration_version.rb +++ /dev/null @@ -1,16 +0,0 @@ -# rubocop:disable Naming/FileName -# frozen_string_literal: true - -# When switching to rails 5, we added migration version to all migration -# classes. This patch makes it possible to run versioned migrations -# also with rails 4 - -unless Gitlab.rails5? - module ActiveRecord - class Migration - def self.[](version) - Migration - end - end - end -end diff --git a/lib/tasks/gitlab/storage.rake b/lib/tasks/gitlab/storage.rake index f539b1df955..09dc3aa9882 100644 --- a/lib/tasks/gitlab/storage.rake +++ b/lib/tasks/gitlab/storage.rake @@ -2,6 +2,12 @@ namespace :gitlab do namespace :storage do desc 'GitLab | Storage | Migrate existing projects to Hashed Storage' task migrate_to_hashed: :environment do + if Gitlab::Database.read_only? + warn 'This task requires database write access. Exiting.' + + next + end + storage_migrator = Gitlab::HashedStorage::Migrator.new helper = Gitlab::HashedStorage::RakeHelper @@ -9,7 +15,7 @@ namespace :gitlab do project = Project.with_unmigrated_storage.find_by(id: helper.range_from) unless project - puts "There are no projects requiring storage migration with ID=#{helper.range_from}" + warn "There are no projects requiring storage migration with ID=#{helper.range_from}" next end @@ -23,7 +29,7 @@ namespace :gitlab do legacy_projects_count = Project.with_unmigrated_storage.count if legacy_projects_count == 0 - puts 'There are no projects requiring storage migration. Nothing to do!' + warn 'There are no projects requiring storage migration. Nothing to do!' next end diff --git a/lib/version_check.rb b/lib/version_check.rb index ccf7bb493db..c9f102f6b19 100644 --- a/lib/version_check.rb +++ b/lib/version_check.rb @@ -5,16 +5,17 @@ require "base64" # This class is used to build image URL to # check if it is a new version for update class VersionCheck - def data + def self.data { version: Gitlab::VERSION } end - def url + def self.url encoded_data = Base64.urlsafe_encode64(data.to_json) + "#{host}?gitlab_info=#{encoded_data}" end - def host + def self.host 'https://version.gitlab.com/check.svg' end end diff --git a/locale/gitlab.pot b/locale/gitlab.pot index 792b6c8cecc..ddfd5599883 100644 --- a/locale/gitlab.pot +++ b/locale/gitlab.pot @@ -19,6 +19,9 @@ msgstr "" msgid " Status" msgstr "" +msgid " or " +msgstr "" + msgid "%d addition" msgid_plural "%d additions" msgstr[0] "" @@ -97,9 +100,6 @@ msgstr[1] "" msgid "%{actionText} & %{openOrClose} %{noteable}" msgstr "" -msgid "%{bio} at %{organization}" -msgstr "" - msgid "%{commit_author_link} authored %{commit_timeago}" msgstr "" @@ -188,6 +188,9 @@ msgstr "" msgid "+ %{moreCount} more" msgstr "" +msgid ", or " +msgstr "" + msgid "- Runner is active and can process any new jobs" msgstr "" @@ -438,9 +441,15 @@ msgstr "" msgid "AdminProjects|Delete project" msgstr "" +msgid "AdminSettings|Environment variables are protected by default" +msgstr "" + msgid "AdminSettings|Specify a domain to use by default for every project's Auto Review Apps and Auto Deploy stages." msgstr "" +msgid "AdminSettings|When creating a new environment variable it will be protected by default." +msgstr "" + msgid "AdminUsers|Block user" msgstr "" @@ -525,6 +534,9 @@ msgstr "" msgid "An error has occurred" msgstr "" +msgid "An error occured while fetching the releases. Please try again." +msgstr "" + msgid "An error occurred creating the new branch." msgstr "" @@ -657,6 +669,12 @@ msgstr "" msgid "Applications" msgstr "" +msgid "Applied" +msgstr "" + +msgid "Apply suggestion" +msgstr "" + msgid "Apr" msgstr "" @@ -705,6 +723,9 @@ msgstr "" msgid "Ask your group maintainer to set up a group Runner." msgstr "" +msgid "Assets" +msgstr "" + msgid "Assign custom color like #FF0000" msgstr "" @@ -1488,6 +1509,9 @@ msgstr "" msgid "ClusterIntegration|Choose which of your environments will use this cluster." msgstr "" +msgid "ClusterIntegration|Clusters are utilized by selecting the nearest ancestor with a matching environment scope. For example, project clusters will override group clusters." +msgstr "" + msgid "ClusterIntegration|Copy API URL" msgstr "" @@ -1530,6 +1554,15 @@ msgstr "" msgid "ClusterIntegration|Every new Google Cloud Platform (GCP) account receives $300 in credit upon %{sign_up_link}. In partnership with Google, GitLab is able to offer an additional $200 for both new and existing GCP accounts to get started with GitLab's Google Kubernetes Engine Integration." msgstr "" +msgid "ClusterIntegration|Failed to configure Google Kubernetes Engine Cluster: %{message}" +msgstr "" + +msgid "ClusterIntegration|Failed to request to Google Cloud Platform: %{message}" +msgstr "" + +msgid "ClusterIntegration|Failed to run Kubeclient: %{message}" +msgstr "" + msgid "ClusterIntegration|Fetching machine types" msgstr "" @@ -2581,6 +2614,9 @@ msgstr "" msgid "Download" msgstr "" +msgid "Download asset" +msgstr "" + msgid "Download tar" msgstr "" @@ -2656,6 +2692,9 @@ msgstr "" msgid "Embed" msgstr "" +msgid "Empty file" +msgstr "" + msgid "Enable" msgstr "" @@ -2716,6 +2755,15 @@ msgstr "" msgid "Enter the merge request title" msgstr "" +msgid "Environment variables" +msgstr "" + +msgid "Environment variables are applied to environments via the runner. They can be protected by only exposing them to protected branches or tags. You can use environment variables for passwords, secret keys, or whatever you want." +msgstr "" + +msgid "Environment variables are configured by your administrator to be %{link_start}protected%{link_end} by default" +msgstr "" + msgid "Environments" msgstr "" @@ -3094,6 +3142,9 @@ msgstr "" msgid "Forking in progress" msgstr "" +msgid "Forks" +msgstr "" + msgid "Format" msgstr "" @@ -3142,6 +3193,9 @@ msgstr "" msgid "Geo" msgstr "" +msgid "Getting started with releases" +msgstr "" + msgid "Git" msgstr "" @@ -3594,6 +3648,9 @@ msgstr "" msgid "Input your repository URL" msgstr "" +msgid "Insert suggestion" +msgstr "" + msgid "Install GitLab Runner" msgstr "" @@ -3723,7 +3780,7 @@ msgstr "" msgid "Job|The artifacts were removed" msgstr "" -msgid "Job|The artifacts will be removed in" +msgid "Job|The artifacts will be removed" msgstr "" msgid "Job|This job is stuck because the project doesn't have any runners online assigned to it." @@ -4057,9 +4114,6 @@ msgstr "" msgid "Merge Request" msgstr "" -msgid "Merge Request:" -msgstr "" - msgid "Merge Requests" msgstr "" @@ -4099,6 +4153,9 @@ msgstr "" msgid "MergeRequests|started a discussion on %{linkStart}the diff%{linkEnd}" msgstr "" +msgid "MergeRequests|started a discussion on an outdated change in commit %{linkStart}%{commitId}%{linkEnd}" +msgstr "" + msgid "MergeRequests|started a discussion on commit %{linkStart}%{commitId}%{linkEnd}" msgstr "" @@ -4359,6 +4416,9 @@ msgstr "" msgid "No" msgstr "" +msgid "No activities found" +msgstr "" + msgid "No assignee" msgstr "" @@ -4368,6 +4428,9 @@ msgstr "" msgid "No changes" msgstr "" +msgid "No changes between %{ref_start}%{source_branch}%{ref_end} and %{ref_start}%{target_branch}%{ref_end}" +msgstr "" + msgid "No connection could be made to a Gitaly Server, please check your logs!" msgstr "" @@ -4592,6 +4655,9 @@ msgstr "" msgid "Open" msgstr "" +msgid "Open Documentation" +msgstr "" + msgid "Open in Xcode" msgstr "" @@ -4970,6 +5036,9 @@ msgstr "" msgid "Profiles| You are going to change the username %{currentUsernameBold} to %{newUsernameBold}. Profile and projects will be redirected to the %{newUsername} namespace but this redirect will expire once the %{currentUsername} namespace is registered by another user or group. Please update your Git repository remotes as soon as possible." msgstr "" +msgid "Profiles|@username" +msgstr "" + msgid "Profiles|Account scheduled for removal." msgstr "" @@ -4991,7 +5060,10 @@ msgstr "" msgid "Profiles|Choose file..." msgstr "" -msgid "Profiles|Choose to show contributions of private projects on your public profile without any project, repository or organization information." +msgid "Profiles|Choose to show contributions of private projects on your public profile without any project, repository or organization information" +msgstr "" + +msgid "Profiles|City, country" msgstr "" msgid "Profiles|Clear status" @@ -5024,6 +5096,9 @@ msgstr "" msgid "Profiles|Edit Profile" msgstr "" +msgid "Profiles|Enter your name, so people you know can recognize you" +msgstr "" + msgid "Profiles|Invalid password" msgstr "" @@ -5063,7 +5138,7 @@ msgstr "" msgid "Profiles|Some options are unavailable for LDAP accounts" msgstr "" -msgid "Profiles|Tell us about yourself in fewer than 250 characters." +msgid "Profiles|Tell us about yourself in fewer than 250 characters" msgstr "" msgid "Profiles|The maximum file size allowed is 200KB." @@ -5072,7 +5147,7 @@ msgstr "" msgid "Profiles|This doesn't look like a public SSH key, are you sure you want to add it?" msgstr "" -msgid "Profiles|This email will be displayed on your public profile." +msgid "Profiles|This email will be displayed on your public profile" msgstr "" msgid "Profiles|This email will be used for web based operations, such as edits and merges. %{learn_more}" @@ -5081,10 +5156,10 @@ msgstr "" msgid "Profiles|This emoji and message will appear on your profile and throughout the interface." msgstr "" -msgid "Profiles|This feature is experimental and translations are not complete yet." +msgid "Profiles|This feature is experimental and translations are not complete yet" msgstr "" -msgid "Profiles|This information will appear on your profile." +msgid "Profiles|This information will appear on your profile" msgstr "" msgid "Profiles|Type your %{confirmationValue} to confirm:" @@ -5111,10 +5186,10 @@ msgstr "" msgid "Profiles|Username successfully changed" msgstr "" -msgid "Profiles|Website" +msgid "Profiles|What's your status?" msgstr "" -msgid "Profiles|What's your status?" +msgid "Profiles|Who you represent or work for" msgstr "" msgid "Profiles|You can change your avatar here" @@ -5135,16 +5210,19 @@ msgstr "" msgid "Profiles|You must transfer ownership or delete these groups before you can delete your account." msgstr "" +msgid "Profiles|Your LinkedIn profile name from linkedin.com/in/profilename" +msgstr "" + msgid "Profiles|Your account is currently an owner in these groups:" msgstr "" -msgid "Profiles|Your email address was automatically set based on your %{provider_label} account." +msgid "Profiles|Your email address was automatically set based on your %{provider_label} account" msgstr "" -msgid "Profiles|Your location was automatically set based on your %{provider_label} account." +msgid "Profiles|Your location was automatically set based on your %{provider_label} account" msgstr "" -msgid "Profiles|Your name was automatically set based on your %{provider_label} account, so people you know can recognize you." +msgid "Profiles|Your name was automatically set based on your %{provider_label} account, so people you know can recognize you" msgstr "" msgid "Profiles|Your status" @@ -5153,6 +5231,12 @@ msgstr "" msgid "Profiles|e.g. My MacBook key" msgstr "" +msgid "Profiles|username" +msgstr "" + +msgid "Profiles|website.com" +msgstr "" + msgid "Profiles|your account" msgstr "" @@ -5470,6 +5554,12 @@ msgstr "" msgid "Related merge requests" msgstr "" +msgid "Releases" +msgstr "" + +msgid "Releases mark specific points in a project's development history, communicate information about the type of change, and deliver on prepared, often compiled, versions of the software to be reused elsewhere. Currently, releases can only be created through the API." +msgstr "" + msgid "Remind later" msgstr "" @@ -5611,6 +5701,9 @@ msgstr "" msgid "Response metrics (HA Proxy)" msgstr "" +msgid "Response metrics (NGINX Ingress VTS)" +msgstr "" + msgid "Response metrics (NGINX Ingress)" msgstr "" @@ -6074,6 +6167,9 @@ msgstr "" msgid "Something went wrong when toggling the button" msgstr "" +msgid "Something went wrong while applying the suggestion. Please try again." +msgstr "" + msgid "Something went wrong while closing the %{issuable}. Please try again later" msgstr "" @@ -6146,7 +6242,7 @@ msgstr "" msgid "SortOptions|Least popular" msgstr "" -msgid "SortOptions|Milestone" +msgid "SortOptions|Milestone due date" msgstr "" msgid "SortOptions|Milestone due later" @@ -6263,6 +6359,9 @@ msgstr "" msgid "Starred projects" msgstr "" +msgid "Stars" +msgstr "" + msgid "Start a %{new_merge_request} with these changes" msgstr "" @@ -6338,6 +6437,9 @@ msgstr "" msgid "Subscribed" msgstr "" +msgid "Suggested change" +msgstr "" + msgid "Switch branch/tag" msgstr "" @@ -6353,6 +6455,9 @@ msgstr "" msgid "System metrics (Kubernetes)" msgstr "" +msgid "Tag" +msgstr "" + msgid "Tags" msgstr "" @@ -6602,7 +6707,7 @@ msgstr "" msgid "Third party offers" msgstr "" -msgid "This %{viewer} could not be displayed because %{reason}." +msgid "This %{viewer} could not be displayed because %{reason}. You can %{options} instead." msgstr "" msgid "This GitLab instance does not provide any shared Runners yet. Instance administrators can register shared Runners in the admin area." @@ -6725,6 +6830,9 @@ msgstr "" msgid "This page will be removed in a future release." msgstr "" +msgid "This pipeline is run in a merge request context" +msgstr "" + msgid "This pipeline makes use of a predefined CI/CD configuration enabled by <b>Auto DevOps.</b>" msgstr "" @@ -7065,10 +7173,13 @@ msgstr "" msgid "Trending" msgstr "" -msgid "Trigger" +msgid "Trigger this manual action" msgstr "" -msgid "Trigger this manual action" +msgid "Trigger token:" +msgstr "" + +msgid "Trigger variables:" msgstr "" msgid "Triggers can force a specific branch or tag to get rebuilt with an API call. These tokens will impersonate their associated user including their access to projects and their project permissions." @@ -7266,15 +7377,6 @@ msgstr "" msgid "Users requesting access to" msgstr "" -msgid "Variables" -msgstr "" - -msgid "Variables are applied to environments via the runner. They can be protected by only exposing them to protected branches or tags. You can use variables for passwords, secret keys, or whatever you want." -msgstr "" - -msgid "Variables:" -msgstr "" - msgid "Various container registry settings." msgstr "" @@ -7798,15 +7900,30 @@ msgstr "" msgid "https://your-bitbucket-server" msgstr "" +msgid "image diff" +msgstr "" + msgid "import flow" msgstr "" msgid "importing" msgstr "" +msgid "in" +msgstr "" + msgid "issue boards" msgstr "" +msgid "it is stored externally" +msgstr "" + +msgid "it is stored in LFS" +msgstr "" + +msgid "it is too large" +msgstr "" + msgid "latest deployment" msgstr "" @@ -8047,6 +8164,9 @@ msgstr "" msgid "remove due date" msgstr "" +msgid "rendered diff" +msgstr "" + msgid "reply" msgid_plural "replies" msgstr[0] "" @@ -8058,6 +8178,9 @@ msgstr "" msgid "source" msgstr "" +msgid "source diff" +msgstr "" + msgid "spendCommand|%{slash_command} will update the sum of the time spent." msgstr "" @@ -8079,6 +8202,9 @@ msgstr "" msgid "view it on GitLab" msgstr "" +msgid "view the blob" +msgstr "" + msgid "with %{additions} additions, %{deletions} deletions." msgstr "" diff --git a/package.json b/package.json index 8857ed318dd..1dc9797c59b 100644 --- a/package.json +++ b/package.json @@ -26,8 +26,8 @@ "@babel/plugin-syntax-import-meta": "^7.0.0", "@babel/preset-env": "^7.1.0", "@gitlab/csslab": "^1.8.0", - "@gitlab/svgs": "^1.40.0", - "@gitlab/ui": "^1.15.0", + "@gitlab/svgs": "^1.43.0", + "@gitlab/ui": "^1.18.0", "apollo-boost": "^0.1.20", "apollo-client": "^2.4.5", "autosize": "^4.0.0", @@ -118,7 +118,7 @@ "xterm": "^3.5.0" }, "devDependencies": { - "@gitlab/eslint-config": "^1.2.0", + "@gitlab/eslint-config": "^1.4.0", "@vue/test-utils": "^1.0.0-beta.25", "axios-mock-adapter": "^1.15.0", "babel-core": "^7.0.0-bridge", @@ -131,10 +131,10 @@ "babel-types": "^6.26.0", "chalk": "^2.4.1", "commander": "^2.18.0", - "eslint": "~5.6.0", + "eslint": "~5.9.0", "eslint-import-resolver-jest": "^2.1.1", "eslint-import-resolver-webpack": "^0.10.1", - "eslint-plugin-html": "4.0.5", + "eslint-plugin-html": "5.0.0", "eslint-plugin-import": "^2.14.0", "eslint-plugin-jasmine": "^2.10.1", "eslint-plugin-jest": "^22.1.0", @@ -158,7 +158,8 @@ "nodemon": "^1.18.4", "prettier": "1.15.2", "vue-jest": "^3.0.1", - "webpack-dev-server": "^3.1.10" + "webpack-dev-server": "^3.1.10", + "yarn-deduplicate": "^1.0.5" }, "engines": { "yarn": "^1.10.0" diff --git a/qa/Dockerfile b/qa/Dockerfile index 9956ced0ef6..ca7f9accb70 100644 --- a/qa/Dockerfile +++ b/qa/Dockerfile @@ -1,4 +1,4 @@ -FROM ruby:2.4-stretch +FROM ruby:2.5-stretch LABEL maintainer "Grzegorz Bizon <grzegorz@gitlab.com>" ENV DEBIAN_FRONTEND noninteractive diff --git a/qa/Gemfile b/qa/Gemfile index d69c71003ae..75ad7bd07af 100644 --- a/qa/Gemfile +++ b/qa/Gemfile @@ -5,5 +5,6 @@ gem 'capybara', '~> 2.16.1' gem 'capybara-screenshot', '~> 1.0.18' gem 'rake', '~> 12.3.0' gem 'rspec', '~> 3.7' -gem 'selenium-webdriver', '~> 3.8.0' +gem 'selenium-webdriver', '~> 3.12' gem 'airborne', '~> 0.2.13' +gem 'nokogiri', '~> 1.8.5' diff --git a/qa/Gemfile.lock b/qa/Gemfile.lock index d61ecf8fbb5..55f3211482b 100644 --- a/qa/Gemfile.lock +++ b/qa/Gemfile.lock @@ -25,7 +25,7 @@ GEM capybara-screenshot (1.0.18) capybara (>= 1.0, < 3) launchy - childprocess (0.8.0) + childprocess (0.9.0) ffi (~> 1.0, >= 1.0.11) coderay (1.1.2) concurrent-ruby (1.0.5) @@ -47,7 +47,7 @@ GEM mini_portile2 (2.3.0) minitest (5.11.1) netrc (0.11.0) - nokogiri (1.8.2) + nokogiri (1.8.5) mini_portile2 (~> 2.3.0) pry (0.11.3) coderay (~> 1.1.0) @@ -78,9 +78,9 @@ GEM rspec-support (~> 3.7.0) rspec-support (3.7.0) rubyzip (1.2.2) - selenium-webdriver (3.8.0) + selenium-webdriver (3.141.0) childprocess (~> 0.5) - rubyzip (~> 1.0) + rubyzip (~> 1.2, >= 1.2.2) thread_safe (0.3.6) tzinfo (1.2.4) thread_safe (~> 0.1) @@ -97,10 +97,11 @@ DEPENDENCIES airborne (~> 0.2.13) capybara (~> 2.16.1) capybara-screenshot (~> 1.0.18) + nokogiri (~> 1.8.5) pry-byebug (~> 3.5.1) rake (~> 12.3.0) rspec (~> 3.7) - selenium-webdriver (~> 3.8.0) + selenium-webdriver (~> 3.12) BUNDLED WITH - 1.17.1 + 1.17.3 @@ -283,6 +283,7 @@ module QA autoload :Select2, 'qa/page/component/select2' autoload :DropdownFilter, 'qa/page/component/dropdown_filter' autoload :UsersSelect, 'qa/page/component/users_select' + autoload :Note, 'qa/page/component/note' module Issuable autoload :Common, 'qa/page/component/issuable/common' diff --git a/qa/qa/page/base.rb b/qa/qa/page/base.rb index 88ade66f47d..5788dceaaae 100644 --- a/qa/qa/page/base.rb +++ b/qa/qa/page/base.rb @@ -80,8 +80,8 @@ module QA page.evaluate_script('xhr.status') == 200 end - def find_element(name, wait: Capybara.default_max_wait_time) - find(element_selector_css(name), wait: wait) + def find_element(name, text_filter = nil, wait: Capybara.default_max_wait_time) + find(element_selector_css(name), wait: wait, text: text_filter) end def all_elements(name) @@ -108,8 +108,12 @@ module QA element.select value.to_s.capitalize end - def has_element?(name) - has_css?(element_selector_css(name)) + def has_element?(name, wait: Capybara.default_max_wait_time) + has_css?(element_selector_css(name), wait: wait) + end + + def has_no_text?(text) + page.has_no_text? text end def within_element(name) diff --git a/qa/qa/page/component/note.rb b/qa/qa/page/component/note.rb new file mode 100644 index 00000000000..67d7f114786 --- /dev/null +++ b/qa/qa/page/component/note.rb @@ -0,0 +1,51 @@ +# frozen_string_literal: true + +module QA + module Page + module Component + module Note + def self.included(base) + base.view 'app/assets/javascripts/notes/components/comment_form.vue' do + element :note_dropdown + element :discussion_option + end + + base.view 'app/assets/javascripts/notes/components/note_form.vue' do + element :reply_input + element :reply_comment_button + end + + base.view 'app/assets/javascripts/notes/components/noteable_discussion.vue' do + element :discussion_reply + end + + base.view 'app/assets/javascripts/notes/components/toggle_replies_widget.vue' do + element :expand_replies + element :collapse_replies + end + end + + def start_discussion(text) + fill_element :comment_input, text + click_element :note_dropdown + click_element :discussion_option + click_element :comment_button + end + + def reply_to_discussion(reply_text) + all_elements(:discussion_reply).last.click + fill_element :reply_input, reply_text + click_element :reply_comment_button + end + + def collapse_replies + click_element :collapse_replies + end + + def expand_replies + click_element :expand_replies + end + end + end + end +end diff --git a/qa/qa/page/main/login.rb b/qa/qa/page/main/login.rb index 97ffe0e5716..cb83ace20b6 100644 --- a/qa/qa/page/main/login.rb +++ b/qa/qa/page/main/login.rb @@ -35,13 +35,21 @@ module QA element :saml_login_button end + view 'app/views/layouts/devise.html.haml' do + element :login_page + end + def initialize # The login page is usually the entry point for all the scenarios so # we need to wait for the instance to start. That said, in some cases # we are already logged-in so we check both cases here. + # Check if we're already logged in first. If we don't then we have to + # wait 10 seconds for the check for the login page to fail every time + # we use this class when we're already logged in (E.g., whenever we + # create a personal access token to use for API access). wait(max: 500) do - has_css?('.login-page') || - Page::Main::Menu.act { has_personal_area?(wait: 0) } + Page::Main::Menu.act { has_personal_area?(wait: 0) } || + has_element?(:login_page) end end diff --git a/qa/qa/page/main/menu.rb b/qa/qa/page/main/menu.rb index cc2724618e9..6804cc8fb20 100644 --- a/qa/qa/page/main/menu.rb +++ b/qa/qa/page/main/menu.rb @@ -63,15 +63,11 @@ module QA end def has_personal_area?(wait: Capybara.default_max_wait_time) - using_wait_time(wait) do - page.has_selector?(element_selector_css(:user_avatar)) - end + has_element?(:user_avatar, wait: wait) end def has_admin_area_link?(wait: Capybara.default_max_wait_time) - using_wait_time(wait) do - page.has_selector?(element_selector_css(:admin_area_link)) - end + has_element?(:admin_area_link, wait: wait) end private diff --git a/qa/qa/page/merge_request/show.rb b/qa/qa/page/merge_request/show.rb index 869dc0b9d21..4f21ed602d9 100644 --- a/qa/qa/page/merge_request/show.rb +++ b/qa/qa/page/merge_request/show.rb @@ -4,6 +4,8 @@ module QA module Page module MergeRequest class Show < Page::Base + include Page::Component::Note + view 'app/assets/javascripts/vue_merge_request_widget/components/states/ready_to_merge.vue' do element :merge_button element :fast_forward_message, 'Fast-forward merge without a merge commit' # rubocop:disable QA/ElementWithPattern @@ -34,19 +36,6 @@ module QA element :diff_comment end - view 'app/assets/javascripts/notes/components/comment_form.vue' do - element :note_dropdown - element :discussion_option - end - - view 'app/assets/javascripts/notes/components/note_form.vue' do - element :reply_input - end - - view 'app/assets/javascripts/notes/components/noteable_discussion.vue' do - element :discussion_reply - end - view 'app/assets/javascripts/diffs/components/inline_diff_table_row.vue' do element :new_diff_line end @@ -163,18 +152,6 @@ module QA fill_element :reply_input, text end - def start_discussion(text) - fill_element :comment_input, text - click_element :note_dropdown - click_element :discussion_option - click_element :comment_button - end - - def reply_to_discussion(reply_text) - all_elements(:discussion_reply).last.click - fill_element :reply_input, reply_text - end - def edit! click_element :edit_button end diff --git a/qa/qa/page/project/issue/show.rb b/qa/qa/page/project/issue/show.rb index 23def93c7dd..1028cc045a0 100644 --- a/qa/qa/page/project/issue/show.rb +++ b/qa/qa/page/project/issue/show.rb @@ -6,6 +6,7 @@ module QA module Issue class Show < Page::Base include Page::Component::Issuable::Common + include Page::Component::Note view 'app/views/shared/notes/_form.html.haml' do element :new_note_form, 'new-note' # rubocop:disable QA/ElementWithPattern @@ -37,17 +38,17 @@ module QA def select_comments_only_filter click_element :discussion_filter - all_elements(:filter_options)[1].click + find_element(:filter_options, "Show comments only").click end def select_history_only_filter click_element :discussion_filter - all_elements(:filter_options).last.click + find_element(:filter_options, "Show history only").click end def select_all_activities_filter click_element :discussion_filter - all_elements(:filter_options).first.click + find_element(:filter_options, "Show all activity").click end end end diff --git a/qa/qa/resource/repository/project_push.rb b/qa/qa/resource/repository/project_push.rb index 37feab4ad70..f4692c3dd4d 100644 --- a/qa/qa/resource/repository/project_push.rb +++ b/qa/qa/resource/repository/project_push.rb @@ -26,6 +26,11 @@ module QA def repository_ssh_uri @repository_ssh_uri ||= project.repository_ssh_location.uri end + + def fabricate! + super + project.visit! + end end end end diff --git a/qa/qa/resource/repository/wiki_push.rb b/qa/qa/resource/repository/wiki_push.rb index f1c39d507fe..77c4c8a514d 100644 --- a/qa/qa/resource/repository/wiki_push.rb +++ b/qa/qa/resource/repository/wiki_push.rb @@ -30,6 +30,11 @@ module QA end end end + + def fabricate! + super + wiki.visit! + end end end end diff --git a/qa/qa/specs/features/browser_ui/2_plan/issue/collapse_comments_in_discussions_spec.rb b/qa/qa/specs/features/browser_ui/2_plan/issue/collapse_comments_in_discussions_spec.rb new file mode 100644 index 00000000000..fa779bd1f4e --- /dev/null +++ b/qa/qa/specs/features/browser_ui/2_plan/issue/collapse_comments_in_discussions_spec.rb @@ -0,0 +1,37 @@ +# frozen_string_literal: true + +module QA + context 'Plan' do + describe 'collapse comments in issue discussions' do + let(:issue_title) { 'issue title' } + + it 'user collapses reply for comments in an issue' do + Runtime::Browser.visit(:gitlab, Page::Main::Login) + Page::Main::Login.perform(&:sign_in_using_credentials) + + Resource::Issue.fabricate! do |issue| + issue.title = issue_title + end + + expect(page).to have_content(issue_title) + + Page::Project::Issue::Show.perform do |show_page| + show_page.select_all_activities_filter + show_page.start_discussion("My first discussion") + expect(show_page).to have_content("My first discussion") + + show_page.reply_to_discussion("My First Reply") + expect(show_page).to have_content("My First Reply") + + show_page.collapse_replies + expect(show_page).to have_content("1 reply") + expect(show_page).not_to have_content("My First Reply") + + show_page.expand_replies + expect(show_page).to have_content("My First Reply") + expect(show_page).not_to have_content("1 reply") + end + end + end + end +end diff --git a/qa/qa/specs/features/browser_ui/2_plan/issue/issue_suggestions_spec.rb b/qa/qa/specs/features/browser_ui/2_plan/issue/issue_suggestions_spec.rb new file mode 100644 index 00000000000..7e8b42e286f --- /dev/null +++ b/qa/qa/specs/features/browser_ui/2_plan/issue/issue_suggestions_spec.rb @@ -0,0 +1,35 @@ +# frozen_string_literal: true + +module QA + context 'Plan' do + describe 'issue suggestions' do + let(:issue_title) { 'Issue Lists are awesome' } + + it 'user sees issue suggestions when creating a new issue' do + Runtime::Browser.visit(:gitlab, Page::Main::Login) + Page::Main::Login.perform(&:sign_in_using_credentials) + + project = Resource::Project.fabricate! do |resource| + resource.name = 'project-for-issue-suggestions' + resource.description = 'project for issue suggestions' + end + + Resource::Issue.fabricate! do |issue| + issue.title = issue_title + issue.project = project + end + + project.visit! + + Page::Project::Show.perform(&:go_to_new_issue) + Page::Project::Issue::New.perform do |new_issue_page| + new_issue_page.add_title("issue") + expect(new_issue_page).to have_content(issue_title) + + new_issue_page.add_title("Issue Board") + expect(new_issue_page).not_to have_content(issue_title) + end + end + end + end +end diff --git a/qa/qa/specs/features/browser_ui/3_create/repository/user_views_raw_diff_patch_requests_spec.rb b/qa/qa/specs/features/browser_ui/3_create/repository/user_views_raw_diff_patch_requests_spec.rb index 75ad18a4111..203338ddf77 100644 --- a/qa/qa/specs/features/browser_ui/3_create/repository/user_views_raw_diff_patch_requests_spec.rb +++ b/qa/qa/specs/features/browser_ui/3_create/repository/user_views_raw_diff_patch_requests_spec.rb @@ -7,17 +7,17 @@ module QA Runtime::Browser.visit(:gitlab, Page::Main::Login) Page::Main::Login.perform(&:sign_in_using_credentials) - @project = Resource::Repository::ProjectPush.fabricate! do |push| + project_push = Resource::Repository::ProjectPush.fabricate! do |push| push.file_name = 'README.md' push.file_content = '# This is a test project' push.commit_message = 'Add README.md' end + @project = project_push.project # first file added has no parent commit, thus no diff data # add second file to repo to enable diff from initial commit @commit_message = 'Add second file' - @project.visit! Page::Project::Show.perform(&:create_new_file!) Page::File::Form.perform do |f| f.add_name('second') diff --git a/qa/qa/support/page/logging.rb b/qa/qa/support/page/logging.rb index 43bc16d8c9a..cfccbb910b7 100644 --- a/qa/qa/support/page/logging.rb +++ b/qa/qa/support/page/logging.rb @@ -37,8 +37,11 @@ module QA exists end - def find_element(name, wait: Capybara.default_max_wait_time) - log("finding :#{name} (wait: #{wait})") + def find_element(name, text_filter = nil, wait: Capybara.default_max_wait_time) + msg = ["finding :#{name}"] + msg << %Q(with text_filter "#{text_filter}") if text_filter + msg << "(wait: #{wait})" + log(msg.compact.join(' ')) element = super @@ -77,7 +80,7 @@ module QA super end - def has_element?(name) + def has_element?(name, wait: Capybara.default_max_wait_time) found = super log("has_element? :#{name} returned #{found}") @@ -85,6 +88,14 @@ module QA found end + def has_no_text?(text) + found = super + + log(%Q{has_no_text?('#{text}') returned #{found}}) + + found + end + def within_element(name) log("within element :#{name}") diff --git a/qa/spec/page/logging_spec.rb b/qa/spec/page/logging_spec.rb index 9d56353062b..f108a5ca318 100644 --- a/qa/spec/page/logging_spec.rb +++ b/qa/spec/page/logging_spec.rb @@ -47,6 +47,15 @@ describe QA::Support::Page::Logging do it 'logs find_element' do expect { subject.find_element(:element) } + .to output(/finding :element/).to_stdout_from_any_process + expect { subject.find_element(:element) } + .to output(/found :element/).to_stdout_from_any_process + end + + it 'logs find_element with text_filter' do + expect { subject.find_element(:element, 'foo') } + .to output(/finding :element with text_filter "foo"/).to_stdout_from_any_process + expect { subject.find_element(:element, 'foo') } .to output(/found :element/).to_stdout_from_any_process end @@ -65,6 +74,13 @@ describe QA::Support::Page::Logging do .to output(/has_element\? :element returned true/).to_stdout_from_any_process end + it 'logs has_no_text?' do + allow(page).to receive(:has_no_text?).with('foo').and_return(true) + + expect { subject.has_no_text? 'foo' } + .to output(/has_no_text\?\('foo'\) returned true/).to_stdout_from_any_process + end + it 'logs within_element' do expect { subject.within_element(:element) } .to output(/within element :element/).to_stdout_from_any_process diff --git a/rubocop/cop/inject_enterprise_edition_module.rb b/rubocop/cop/inject_enterprise_edition_module.rb new file mode 100644 index 00000000000..c8b8aca51ab --- /dev/null +++ b/rubocop/cop/inject_enterprise_edition_module.rb @@ -0,0 +1,47 @@ +# frozen_string_literal: true + +module RuboCop + module Cop + # Cop that blacklists the injecting of EE specific modules anywhere but on + # the last line of a file. Injecting a module in the middle of a file will + # cause merge conflicts, while placing it on the last line will not. + class InjectEnterpriseEditionModule < RuboCop::Cop::Cop + MSG = 'Injecting EE modules must be done on the last line of this file' \ + ', outside of any class or module definitions' + + METHODS = Set.new(%i[include extend prepend]).freeze + + def_node_matcher :ee_const?, <<~PATTERN + (const (const _ :EE) _) + PATTERN + + def on_send(node) + return unless METHODS.include?(node.children[1]) + return unless ee_const?(node.children[2]) + + line = node.location.line + buffer = node.location.expression.source_buffer + last_line = buffer.last_line + + # Parser treats the final newline (if present) as a separate line, + # meaning that a simple `line < last_line` would yield true even though + # the expression is the last line _of code_. + last_line -= 1 if buffer.source.end_with?("\n") + + add_offense(node) if line < last_line + end + + # Automatically correcting these offenses is not always possible, as + # sometimes code needs to be refactored to make this work. As such, we + # only allow developers to easily blacklist existing offenses. + def autocorrect(node) + lambda do |corrector| + corrector.insert_after( + node.source_range, + " # rubocop: disable #{cop_name}" + ) + end + end + end + end +end diff --git a/rubocop/rubocop.rb b/rubocop/rubocop.rb index 4489159f422..3e33419eb2e 100644 --- a/rubocop/rubocop.rb +++ b/rubocop/rubocop.rb @@ -41,3 +41,4 @@ require_relative 'cop/code_reuse/presenter' require_relative 'cop/code_reuse/serializer' require_relative 'cop/code_reuse/active_record' require_relative 'cop/group_public_or_visible_to_user' +require_relative 'cop/inject_enterprise_edition_module' diff --git a/scripts/prepare_build.sh b/scripts/prepare_build.sh index 75a3cea0448..58b74f2f07d 100644 --- a/scripts/prepare_build.sh +++ b/scripts/prepare_build.sh @@ -1,17 +1,16 @@ . scripts/utils.sh export SETUP_DB=${SETUP_DB:-true} -export CREATE_DB_USER=${CREATE_DB_USER:-$SETUP_DB} export USE_BUNDLE_INSTALL=${USE_BUNDLE_INSTALL:-true} export BUNDLE_INSTALL_FLAGS="--without=production --jobs=$(nproc) --path=vendor --retry=3 --quiet" if [ "$USE_BUNDLE_INSTALL" != "false" ]; then - bundle install --clean $BUNDLE_INSTALL_FLAGS && bundle check + bundle install --clean $BUNDLE_INSTALL_FLAGS && bundle check fi # Only install knapsack after bundle install! Otherwise oddly some native # gems could not be found under some circumstance. No idea why, hours wasted. -retry gem install knapsack --no-ri --no-rdoc +retry gem install knapsack --no-document cp config/gitlab.yml.example config/gitlab.yml sed -i 's/bin_path: \/usr\/bin\/git/bin_path: \/usr\/local\/bin\/git/' config/gitlab.yml @@ -23,18 +22,30 @@ export GITLAB_DATABASE=$(echo $CI_JOB_NAME | cut -f1 -d' ' | cut -f2 -d-) # This would make the default database postgresql, and we could also use # pg to mean postgresql. if [ "$GITLAB_DATABASE" != 'mysql' ]; then - export GITLAB_DATABASE='postgresql' + export GITLAB_DATABASE='postgresql' fi cp config/database.yml.$GITLAB_DATABASE config/database.yml +if [ -f config/database_geo.yml.$GITLAB_DATABASE ]; then + cp config/database_geo.yml.$GITLAB_DATABASE config/database_geo.yml +fi + # Set user to a non-superuser to ensure we test permissions sed -i 's/username: root/username: gitlab/g' config/database.yml if [ "$GITLAB_DATABASE" = 'postgresql' ]; then - sed -i 's/localhost/postgres/g' config/database.yml + sed -i 's/localhost/postgres/g' config/database.yml + + if [ -f config/database_geo.yml ]; then + sed -i 's/localhost/postgres/g' config/database_geo.yml + fi else # Assume it's mysql - sed -i 's/localhost/mysql/g' config/database.yml + sed -i 's/localhost/mysql/g' config/database.yml + + if [ -f config/database_geo.yml ]; then + sed -i 's/localhost/mysql/g' config/database_geo.yml + fi fi cp config/resque.yml.example config/resque.yml @@ -50,7 +61,7 @@ cp config/redis.shared_state.yml.example config/redis.shared_state.yml sed -i 's/localhost/redis/g' config/redis.shared_state.yml if [ "$SETUP_DB" != "false" ]; then - setup_db + setup_db elif getent hosts postgres || getent hosts mysql; then - setup_db_user_only + setup_db_user_only fi diff --git a/scripts/rails4-gemfile-lock-check b/scripts/rails4-gemfile-lock-check deleted file mode 100755 index a74a49874e1..00000000000 --- a/scripts/rails4-gemfile-lock-check +++ /dev/null @@ -1,19 +0,0 @@ -#!/usr/bin/env bash - -echo -e "=> Checking if Gemfile.rails4.lock is up-to-date...\\n" - -cp Gemfile.rails4.lock Gemfile.rails4.lock.orig -BUNDLE_GEMFILE=Gemfile.rails4 bundle install "$BUNDLE_INSTALL_FLAGS" -diff -u Gemfile.rails4.lock.orig Gemfile.rails4.lock >/dev/null 2>&1 - -if [ $? == 1 ] -then - diff -u Gemfile.rails4.lock.orig Gemfile.rails4.lock - - echo -e "\\n✖ ERROR: Gemfile.rails4.lock is not up-to-date! - Please run 'BUNDLE_GEMFILE=Gemfile.rails4 bundle install'\\n" >&2 - exit 1 -fi - -echo "✔ Gemfile.rails4.lock is up-to-date" -exit 0 diff --git a/scripts/review_apps/review-apps.sh b/scripts/review_apps/review-apps.sh index b50bf2161cb..9e52366f800 100755 --- a/scripts/review_apps/review-apps.sh +++ b/scripts/review_apps/review-apps.sh @@ -227,7 +227,8 @@ function install_external_dns() { --set aws.zoneType="public" \ --set domainFilters[0]="${domain}" \ --set txtOwnerId="${KUBE_NAMESPACE}" \ - --set rbac.create="true" + --set rbac.create="true" \ + --set policy="sync" fi } @@ -292,7 +293,7 @@ function get_job_id() { local job_id=$(curl --silent --show-error --header "PRIVATE-TOKEN: ${API_TOKEN}" "${url}" | jq "map(select(.name == \"${job_name}\")) | map(.id) | last") [[ "${job_id}" == "null" && "${page}" -lt "$max_page" ]] || break - ((page++)) + let "page++" done if [[ "${job_id}" == "" ]]; then @@ -334,7 +335,7 @@ function wait_for_job_to_be_done() { [[ "${job_status}" == "pending" || "${job_status}" == "running" ]] || break printf "." - ((elapsed_seconds+=$interval)) + let "elapsed_seconds+=interval" sleep ${interval} done diff --git a/scripts/trigger-build b/scripts/trigger-build index 14af3281106..4032ba853e6 100755 --- a/scripts/trigger-build +++ b/scripts/trigger-build @@ -68,7 +68,7 @@ module Trigger def base_variables { - 'GITLAB_REF_SLUG' => ref_slug, + 'GITLAB_REF_SLUG' => ENV['CI_COMMIT_REF_SLUG'], 'TRIGGERED_USER' => ENV['TRIGGERED_USER'] || ENV['GITLAB_USER_NAME'], 'TRIGGER_SOURCE' => ENV['CI_JOB_URL'], 'TOP_UPSTREAM_SOURCE_PROJECT' => ENV['CI_PROJECT_PATH'], @@ -77,12 +77,6 @@ module Trigger } end - def ref_slug - return 'master' if ENV['CI_COMMIT_REF_SLUG'] =~ %r{(\Aqa[/-]|-qa\z)} - - ENV['CI_COMMIT_REF_SLUG'] - end - # Read version files from all components def version_file_variables Dir.glob("*_VERSION").each_with_object({}) do |version_file, params| diff --git a/spec/controllers/abuse_reports_controller_spec.rb b/spec/controllers/abuse_reports_controller_spec.rb index ada011e7595..7104305e9d2 100644 --- a/spec/controllers/abuse_reports_controller_spec.rb +++ b/spec/controllers/abuse_reports_controller_spec.rb @@ -19,7 +19,7 @@ describe AbuseReportsController do user_id = user.id user.destroy - get :new, { user_id: user_id } + get :new, params: { user_id: user_id } expect(response).to redirect_to root_path expect(flash[:alert]).to eq('Cannot create the abuse report. The user has been deleted.') @@ -30,7 +30,7 @@ describe AbuseReportsController do it 'redirects the reporter to the user\'s profile' do user.block - get :new, { user_id: user.id } + get :new, params: { user_id: user.id } expect(response).to redirect_to user expect(flash[:alert]).to eq('Cannot create the abuse report. This user has been blocked.') @@ -42,18 +42,18 @@ describe AbuseReportsController do context 'with valid attributes' do it 'saves the abuse report' do expect do - post :create, abuse_report: attrs + post :create, params: { abuse_report: attrs } end.to change { AbuseReport.count }.by(1) end it 'calls notify' do expect_any_instance_of(AbuseReport).to receive(:notify) - post :create, abuse_report: attrs + post :create, params: { abuse_report: attrs } end it 'redirects back to the reported user' do - post :create, abuse_report: attrs + post :create, params: { abuse_report: attrs } expect(response).to redirect_to user end @@ -62,7 +62,7 @@ describe AbuseReportsController do context 'with invalid attributes' do it 'renders new' do attrs.delete(:user_id) - post :create, abuse_report: attrs + post :create, params: { abuse_report: attrs } expect(response).to render_template(:new) end diff --git a/spec/controllers/admin/application_settings_controller_spec.rb b/spec/controllers/admin/application_settings_controller_spec.rb index 2e0f79cd313..9af472df74e 100644 --- a/spec/controllers/admin/application_settings_controller_spec.rb +++ b/spec/controllers/admin/application_settings_controller_spec.rb @@ -52,35 +52,35 @@ describe Admin::ApplicationSettingsController do end it 'updates the password_authentication_enabled_for_git setting' do - put :update, application_setting: { password_authentication_enabled_for_git: "0" } + put :update, params: { application_setting: { password_authentication_enabled_for_git: "0" } } expect(response).to redirect_to(admin_application_settings_path) expect(ApplicationSetting.current.password_authentication_enabled_for_git).to eq(false) end it 'updates the default_project_visibility for string value' do - put :update, application_setting: { default_project_visibility: "20" } + put :update, params: { application_setting: { default_project_visibility: "20" } } expect(response).to redirect_to(admin_application_settings_path) expect(ApplicationSetting.current.default_project_visibility).to eq(Gitlab::VisibilityLevel::PUBLIC) end it 'update the restricted levels for string values' do - put :update, application_setting: { restricted_visibility_levels: %w[10 20] } + put :update, params: { application_setting: { restricted_visibility_levels: %w[10 20] } } expect(response).to redirect_to(admin_application_settings_path) expect(ApplicationSetting.current.restricted_visibility_levels).to eq([10, 20]) end it 'updates the restricted_visibility_levels when empty array is passed' do - put :update, application_setting: { restricted_visibility_levels: [""] } + put :update, params: { application_setting: { restricted_visibility_levels: [""] } } expect(response).to redirect_to(admin_application_settings_path) expect(ApplicationSetting.current.restricted_visibility_levels).to be_empty end it 'updates the receive_max_input_size setting' do - put :update, application_setting: { receive_max_input_size: "1024" } + put :update, params: { application_setting: { receive_max_input_size: "1024" } } expect(response).to redirect_to(admin_application_settings_path) expect(ApplicationSetting.current.receive_max_input_size).to eq(1024) diff --git a/spec/controllers/admin/applications_controller_spec.rb b/spec/controllers/admin/applications_controller_spec.rb index 7bd6c0e6117..7e1ce70dc7d 100644 --- a/spec/controllers/admin/applications_controller_spec.rb +++ b/spec/controllers/admin/applications_controller_spec.rb @@ -19,7 +19,7 @@ describe Admin::ApplicationsController do describe 'GET #edit' do it 'renders the application form' do - get :edit, id: application.id + get :edit, params: { id: application.id } expect(response).to render_template :edit expect(assigns[:scopes]).to be_kind_of(Doorkeeper::OAuth::Scopes) @@ -31,7 +31,7 @@ describe Admin::ApplicationsController do create_params = attributes_for(:application, trusted: true) expect do - post :create, doorkeeper_application: create_params + post :create, params: { doorkeeper_application: create_params } end.to change { Doorkeeper::Application.count }.by(1) application = Doorkeeper::Application.last @@ -42,7 +42,7 @@ describe Admin::ApplicationsController do it 'renders the application form on errors' do expect do - post :create, doorkeeper_application: attributes_for(:application).merge(redirect_uri: nil) + post :create, params: { doorkeeper_application: attributes_for(:application).merge(redirect_uri: nil) } end.not_to change { Doorkeeper::Application.count } expect(response).to render_template :new @@ -52,7 +52,7 @@ describe Admin::ApplicationsController do describe 'PATCH #update' do it 'updates the application' do - patch :update, id: application.id, doorkeeper_application: { redirect_uri: 'http://example.com/', trusted: true } + patch :update, params: { id: application.id, doorkeeper_application: { redirect_uri: 'http://example.com/', trusted: true } } application.reload @@ -61,7 +61,7 @@ describe Admin::ApplicationsController do end it 'renders the application form on errors' do - patch :update, id: application.id, doorkeeper_application: { redirect_uri: nil } + patch :update, params: { id: application.id, doorkeeper_application: { redirect_uri: nil } } expect(response).to render_template :edit expect(assigns[:scopes]).to be_kind_of(Doorkeeper::OAuth::Scopes) diff --git a/spec/controllers/admin/groups_controller_spec.rb b/spec/controllers/admin/groups_controller_spec.rb index 0dad95e418f..647fce0ecef 100644 --- a/spec/controllers/admin/groups_controller_spec.rb +++ b/spec/controllers/admin/groups_controller_spec.rb @@ -12,12 +12,12 @@ describe Admin::GroupsController do describe 'DELETE #destroy' do it 'schedules a group destroy' do Sidekiq::Testing.fake! do - expect { delete :destroy, id: project.group.path }.to change(GroupDestroyWorker.jobs, :size).by(1) + expect { delete :destroy, params: { id: project.group.path } }.to change(GroupDestroyWorker.jobs, :size).by(1) end end it 'redirects to the admin group path' do - delete :destroy, id: project.group.path + delete :destroy, params: { id: project.group.path } expect(response).to redirect_to(admin_groups_path) end @@ -27,9 +27,11 @@ describe Admin::GroupsController do let(:group_user) { create(:user) } it 'adds user to members' do - put :members_update, id: group, - user_ids: group_user.id, - access_level: Gitlab::Access::GUEST + put :members_update, params: { + id: group, + user_ids: group_user.id, + access_level: Gitlab::Access::GUEST + } expect(response).to set_flash.to 'Users were successfully added.' expect(response).to redirect_to(admin_group_path(group)) @@ -37,18 +39,22 @@ describe Admin::GroupsController do end it 'can add unlimited members' do - put :members_update, id: group, - user_ids: 1.upto(1000).to_a.join(','), - access_level: Gitlab::Access::GUEST + put :members_update, params: { + id: group, + user_ids: 1.upto(1000).to_a.join(','), + access_level: Gitlab::Access::GUEST + } expect(response).to set_flash.to 'Users were successfully added.' expect(response).to redirect_to(admin_group_path(group)) end it 'adds no user to members' do - put :members_update, id: group, - user_ids: '', - access_level: Gitlab::Access::GUEST + put :members_update, params: { + id: group, + user_ids: '', + access_level: Gitlab::Access::GUEST + } expect(response).to set_flash.to 'No users specified.' expect(response).to redirect_to(admin_group_path(group)) diff --git a/spec/controllers/admin/hooks_controller_spec.rb b/spec/controllers/admin/hooks_controller_spec.rb index d2c1e634930..9bc58344e4e 100644 --- a/spec/controllers/admin/hooks_controller_spec.rb +++ b/spec/controllers/admin/hooks_controller_spec.rb @@ -20,7 +20,7 @@ describe Admin::HooksController do merge_requests_events: true } - post :create, hook: hook_params + post :create, params: { hook: hook_params } expect(response).to have_gitlab_http_status(302) expect(SystemHook.all.size).to eq(1) diff --git a/spec/controllers/admin/identities_controller_spec.rb b/spec/controllers/admin/identities_controller_spec.rb index a29853bf8df..e5428c8ddeb 100644 --- a/spec/controllers/admin/identities_controller_spec.rb +++ b/spec/controllers/admin/identities_controller_spec.rb @@ -13,7 +13,7 @@ describe Admin::IdentitiesController do it 'repairs ldap blocks' do expect_any_instance_of(RepairLdapBlockedUserService).to receive(:execute) - put :update, user_id: user.username, id: user.ldap_identity.id, identity: { provider: 'twitter' } + put :update, params: { user_id: user.username, id: user.ldap_identity.id, identity: { provider: 'twitter' } } end end @@ -23,7 +23,7 @@ describe Admin::IdentitiesController do it 'repairs ldap blocks' do expect_any_instance_of(RepairLdapBlockedUserService).to receive(:execute) - delete :destroy, user_id: user.username, id: user.ldap_identity.id + delete :destroy, params: { user_id: user.username, id: user.ldap_identity.id } end end end diff --git a/spec/controllers/admin/projects_controller_spec.rb b/spec/controllers/admin/projects_controller_spec.rb index ee1aff09bdf..8166657f674 100644 --- a/spec/controllers/admin/projects_controller_spec.rb +++ b/spec/controllers/admin/projects_controller_spec.rb @@ -11,13 +11,13 @@ describe Admin::ProjectsController do render_views it 'retrieves the project for the given visibility level' do - get :index, visibility_level: [Gitlab::VisibilityLevel::PUBLIC] + get :index, params: { visibility_level: [Gitlab::VisibilityLevel::PUBLIC] } expect(response.body).to match(project.name) end it 'does not retrieve the project' do - get :index, visibility_level: [Gitlab::VisibilityLevel::INTERNAL] + get :index, params: { visibility_level: [Gitlab::VisibilityLevel::INTERNAL] } expect(response.body).not_to match(project.name) end @@ -47,7 +47,7 @@ describe Admin::ProjectsController do render_views it 'renders show page' do - get :show, namespace_id: project.namespace.path, id: project.path + get :show, params: { namespace_id: project.namespace.path, id: project.path } expect(response).to have_gitlab_http_status(200) expect(response.body).to match(project.name) diff --git a/spec/controllers/admin/runners_controller_spec.rb b/spec/controllers/admin/runners_controller_spec.rb index 312dbdd0624..4cf14030ca1 100644 --- a/spec/controllers/admin/runners_controller_spec.rb +++ b/spec/controllers/admin/runners_controller_spec.rb @@ -17,13 +17,13 @@ describe Admin::RunnersController do describe '#show' do it 'shows a particular runner' do - get :show, id: runner.id + get :show, params: { id: runner.id } expect(response).to have_gitlab_http_status(200) end it 'shows 404 for unknown runner' do - get :show, id: 0 + get :show, params: { id: 0 } expect(response).to have_gitlab_http_status(404) end @@ -34,7 +34,7 @@ describe Admin::RunnersController do new_desc = runner.description.swapcase expect do - post :update, id: runner.id, runner: { description: new_desc } + post :update, params: { id: runner.id, runner: { description: new_desc } } end.to change { runner.ensure_runner_queue_value } runner.reload @@ -46,7 +46,7 @@ describe Admin::RunnersController do describe '#destroy' do it 'destroys the runner' do - delete :destroy, id: runner.id + delete :destroy, params: { id: runner.id } expect(response).to have_gitlab_http_status(302) expect(Ci::Runner.find_by(id: runner.id)).to be_nil @@ -58,7 +58,7 @@ describe Admin::RunnersController do runner.update(active: false) expect do - post :resume, id: runner.id + post :resume, params: { id: runner.id } end.to change { runner.ensure_runner_queue_value } runner.reload @@ -73,7 +73,7 @@ describe Admin::RunnersController do runner.update(active: true) expect do - post :pause, id: runner.id + post :pause, params: { id: runner.id } end.to change { runner.ensure_runner_queue_value } runner.reload diff --git a/spec/controllers/admin/services_controller_spec.rb b/spec/controllers/admin/services_controller_spec.rb index 4439ea4a533..ec161b92245 100644 --- a/spec/controllers/admin/services_controller_spec.rb +++ b/spec/controllers/admin/services_controller_spec.rb @@ -18,7 +18,7 @@ describe Admin::ServicesController do end it 'successfully displays the template' do - get :edit, id: service.id + get :edit, params: { id: service.id } expect(response).to have_gitlab_http_status(200) end @@ -44,7 +44,7 @@ describe Admin::ServicesController do it 'calls the propagation worker when service is active' do expect(PropagateServiceTemplateWorker).to receive(:perform_async).with(service.id) - put :update, id: service.id, service: { active: true } + put :update, params: { id: service.id, service: { active: true } } expect(response).to have_gitlab_http_status(302) end @@ -52,7 +52,7 @@ describe Admin::ServicesController do it 'does not call the propagation worker when service is not active' do expect(PropagateServiceTemplateWorker).not_to receive(:perform_async) - put :update, id: service.id, service: { properties: {} } + put :update, params: { id: service.id, service: { properties: {} } } expect(response).to have_gitlab_http_status(302) end diff --git a/spec/controllers/admin/spam_logs_controller_spec.rb b/spec/controllers/admin/spam_logs_controller_spec.rb index 7a96ef6a5cc..2b946ec1c68 100644 --- a/spec/controllers/admin/spam_logs_controller_spec.rb +++ b/spec/controllers/admin/spam_logs_controller_spec.rb @@ -20,13 +20,13 @@ describe Admin::SpamLogsController do describe '#destroy' do it 'removes only the spam log when removing log' do - expect { delete :destroy, id: first_spam.id }.to change { SpamLog.count }.by(-1) + expect { delete :destroy, params: { id: first_spam.id } }.to change { SpamLog.count }.by(-1) expect(User.find(user.id)).to be_truthy expect(response).to have_gitlab_http_status(200) end it 'removes user and his spam logs when removing the user' do - delete :destroy, id: first_spam.id, remove_user: true + delete :destroy, params: { id: first_spam.id, remove_user: true } expect(flash[:notice]).to eq "User #{user.username} was successfully removed." expect(response).to have_gitlab_http_status(302) @@ -40,7 +40,7 @@ describe Admin::SpamLogsController do allow_any_instance_of(AkismetService).to receive(:submit_ham).and_return(true) end it 'submits the log as ham' do - post :mark_as_ham, id: first_spam.id + post :mark_as_ham, params: { id: first_spam.id } expect(response).to have_gitlab_http_status(302) expect(SpamLog.find(first_spam.id).submitted_as_ham).to be_truthy diff --git a/spec/controllers/admin/users_controller_spec.rb b/spec/controllers/admin/users_controller_spec.rb index 3dd0b2623ac..6b66cbd2651 100644 --- a/spec/controllers/admin/users_controller_spec.rb +++ b/spec/controllers/admin/users_controller_spec.rb @@ -17,7 +17,7 @@ describe Admin::UsersController do end it 'deletes user and ghosts their contributions' do - delete :destroy, id: user.username, format: :json + delete :destroy, params: { id: user.username }, format: :json expect(response).to have_gitlab_http_status(200) expect(User.exists?(user.id)).to be_falsy @@ -25,7 +25,7 @@ describe Admin::UsersController do end it 'deletes the user and their contributions when hard delete is specified' do - delete :destroy, id: user.username, hard_delete: true, format: :json + delete :destroy, params: { id: user.username, hard_delete: true }, format: :json expect(response).to have_gitlab_http_status(200) expect(User.exists?(user.id)).to be_falsy @@ -35,7 +35,7 @@ describe Admin::UsersController do describe 'PUT block/:id' do it 'blocks user' do - put :block, id: user.username + put :block, params: { id: user.username } user.reload expect(user.blocked?).to be_truthy expect(flash[:notice]).to eq 'Successfully blocked' @@ -51,7 +51,7 @@ describe Admin::UsersController do end it 'does not unblock user' do - put :unblock, id: user.username + put :unblock, params: { id: user.username } user.reload expect(user.blocked?).to be_truthy expect(flash[:alert]).to eq 'This user cannot be unlocked manually from GitLab' @@ -64,7 +64,7 @@ describe Admin::UsersController do end it 'unblocks user' do - put :unblock, id: user.username + put :unblock, params: { id: user.username } user.reload expect(user.blocked?).to be_falsey expect(flash[:notice]).to eq 'Successfully unblocked' @@ -79,7 +79,7 @@ describe Admin::UsersController do end it 'unlocks user' do - put :unlock, id: user.username + put :unlock, params: { id: user.username } user.reload expect(user.access_locked?).to be_falsey end @@ -93,7 +93,7 @@ describe Admin::UsersController do end it 'confirms user' do - put :confirm, id: user.username + put :confirm, params: { id: user.username } user.reload expect(user.confirmed?).to be_truthy end @@ -121,17 +121,17 @@ describe Admin::UsersController do end def go - patch :disable_two_factor, id: user.to_param + patch :disable_two_factor, params: { id: user.to_param } end end describe 'POST create' do it 'creates the user' do - expect { post :create, user: attributes_for(:user) }.to change { User.count }.by(1) + expect { post :create, params: { user: attributes_for(:user) } }.to change { User.count }.by(1) end it 'shows only one error message for an invalid email' do - post :create, user: attributes_for(:user, email: 'bogus') + post :create, params: { user: attributes_for(:user, email: 'bogus') } expect(assigns[:user].errors).to contain_exactly("Email is invalid") end end @@ -147,7 +147,7 @@ describe Admin::UsersController do } } - post :update, params + post :update, params: params end context 'when the admin changes his own password' do @@ -227,13 +227,13 @@ describe Admin::UsersController do end it "shows a notice" do - post :impersonate, id: user.username + post :impersonate, params: { id: user.username } expect(flash[:alert]).to eq("You cannot impersonate a blocked user") end it "doesn't sign us in as the user" do - post :impersonate, id: user.username + post :impersonate, params: { id: user.username } expect(warden.user).to eq(admin) end @@ -241,25 +241,25 @@ describe Admin::UsersController do context "when the user is not blocked" do it "stores the impersonator in the session" do - post :impersonate, id: user.username + post :impersonate, params: { id: user.username } expect(session[:impersonator_id]).to eq(admin.id) end it "signs us in as the user" do - post :impersonate, id: user.username + post :impersonate, params: { id: user.username } expect(warden.user).to eq(user) end it "redirects to root" do - post :impersonate, id: user.username + post :impersonate, params: { id: user.username } expect(response).to redirect_to(root_path) end it "shows a notice" do - post :impersonate, id: user.username + post :impersonate, params: { id: user.username } expect(flash[:alert]).to eq("You are now impersonating #{user.username}") end @@ -271,7 +271,7 @@ describe Admin::UsersController do end it "shows error page" do - post :impersonate, id: user.username + post :impersonate, params: { id: user.username } expect(response).to have_gitlab_http_status(404) end diff --git a/spec/controllers/application_controller_spec.rb b/spec/controllers/application_controller_spec.rb index c2bd7fd9808..43f561f7a25 100644 --- a/spec/controllers/application_controller_spec.rb +++ b/spec/controllers/application_controller_spec.rb @@ -522,13 +522,13 @@ describe ApplicationController do end it 'renders a 403 when a message is passed to access denied' do - get :index, message: 'None shall pass' + get :index, params: { message: 'None shall pass' } expect(response).to have_gitlab_http_status(403) end it 'renders a status passed to access denied' do - get :index, status: 401 + get :index, params: { status: 401 } expect(response).to have_gitlab_http_status(401) end @@ -548,34 +548,18 @@ describe ApplicationController do end context 'html' do - subject { get :index, text: "hi \255" } + subject { get :index, params: { text: "hi \255" } } it 'renders 412' do - if Gitlab.rails5? - expect { subject }.to raise_error(ActionController::BadRequest) - else - subject - - expect(response).to have_gitlab_http_status(412) - expect(response).to render_template :precondition_failed - end + expect { subject }.to raise_error(ActionController::BadRequest) end end context 'js' do - subject { get :index, text: "hi \255", format: :js } + subject { get :index, format: :js, params: { text: "hi \255" } } it 'renders 412' do - if Gitlab.rails5? - expect { subject }.to raise_error(ActionController::BadRequest) - else - subject - - json_response = JSON.parse(response.body) - - expect(response).to have_gitlab_http_status(412) - expect(json_response['error']).to eq('Invalid UTF-8') - end + expect { subject }.to raise_error(ActionController::BadRequest) end end end diff --git a/spec/controllers/autocomplete_controller_spec.rb b/spec/controllers/autocomplete_controller_spec.rb index 883bb35f396..4458a7223bf 100644 --- a/spec/controllers/autocomplete_controller_spec.rb +++ b/spec/controllers/autocomplete_controller_spec.rb @@ -15,7 +15,7 @@ describe AutocompleteController do describe 'GET #users with project ID' do before do - get(:users, project_id: project.id) + get(:users, params: { project_id: project.id }) end it 'returns the project members' do @@ -27,7 +27,7 @@ describe AutocompleteController do describe 'GET #users with unknown project' do before do - get(:users, project_id: 'unknown') + get(:users, params: { project_id: 'unknown' }) end it { expect(response).to have_gitlab_http_status(404) } @@ -44,7 +44,7 @@ describe AutocompleteController do describe 'GET #users with group ID' do before do - get(:users, group_id: group.id) + get(:users, params: { group_id: group.id }) end it 'returns the group members' do @@ -56,7 +56,7 @@ describe AutocompleteController do describe 'GET #users with unknown group ID' do before do - get(:users, group_id: 'unknown') + get(:users, params: { group_id: 'unknown' }) end it { expect(response).to have_gitlab_http_status(404) } @@ -72,7 +72,7 @@ describe AutocompleteController do describe 'GET #users with project ID' do before do - get(:users, project_id: project.id, current_user: true) + get(:users, params: { project_id: project.id, current_user: true }) end it 'returns the project members and non-members' do @@ -100,7 +100,7 @@ describe AutocompleteController do user1 = create(:user, username: 'user1', name: 'Ian') sign_in(user) - get(:users, search: 'user') + get(:users, params: { search: 'user' }) response_usernames = json_response.map { |user| user['username'] } @@ -128,7 +128,7 @@ describe AutocompleteController do describe 'GET #users with public project' do before do public_project.add_guest(user) - get(:users, project_id: public_project.id) + get(:users, params: { project_id: public_project.id }) end it { expect(json_response).to be_kind_of(Array) } @@ -137,7 +137,7 @@ describe AutocompleteController do describe 'GET #users with project' do before do - get(:users, project_id: project.id) + get(:users, params: { project_id: project.id }) end it { expect(response).to have_gitlab_http_status(404) } @@ -145,7 +145,7 @@ describe AutocompleteController do describe 'GET #users with unknown project' do before do - get(:users, project_id: 'unknown') + get(:users, params: { project_id: 'unknown' }) end it { expect(response).to have_gitlab_http_status(404) } @@ -154,7 +154,7 @@ describe AutocompleteController do describe 'GET #users with inaccessible group' do before do project.add_guest(user) - get(:users, group_id: user.namespace.id) + get(:users, params: { group_id: user.namespace.id }) end it { expect(response).to have_gitlab_http_status(404) } @@ -171,7 +171,7 @@ describe AutocompleteController do describe 'GET #users with todo filter' do it 'gives an array of users' do - get :users, todo_filter: true + get :users, params: { todo_filter: true } expect(response.status).to eq 200 expect(json_response).to be_kind_of(Array) @@ -186,13 +186,13 @@ describe AutocompleteController do end it 'includes the author' do - get(:users, author_id: non_member.id) + get(:users, params: { author_id: non_member.id }) expect(json_response.first["username"]).to eq non_member.username end it 'rejects non existent user ids' do - get(:users, author_id: 99999) + get(:users, params: { author_id: 99999 }) expect(json_response.collect { |u| u['id'] }).not_to include(99999) end @@ -200,7 +200,7 @@ describe AutocompleteController do context 'without authenticating' do it 'returns empty result' do - get(:users, author_id: non_member.id) + get(:users, params: { author_id: non_member.id }) expect(json_response).to be_empty end @@ -213,7 +213,7 @@ describe AutocompleteController do end it 'skips the user IDs passed' do - get(:users, skip_users: [user, user2].map(&:id)) + get(:users, params: { skip_users: [user, user2].map(&:id) }) response_user_ids = json_response.map { |user| user['id'] } @@ -238,7 +238,7 @@ describe AutocompleteController do describe 'GET #projects with project ID' do before do - get(:projects, project_id: project.id) + get(:projects, params: { project_id: project.id }) end it 'returns projects' do @@ -259,7 +259,7 @@ describe AutocompleteController do describe 'GET #projects with project ID and search' do before do - get(:projects, project_id: project.id, search: 'rugged') + get(:projects, params: { project_id: project.id, search: 'rugged' }) end it 'returns projects' do @@ -283,7 +283,7 @@ describe AutocompleteController do describe 'GET #projects with project ID' do before do - get(:projects, project_id: project.id) + get(:projects, params: { project_id: project.id }) end it 'returns projects' do @@ -305,7 +305,7 @@ describe AutocompleteController do describe 'GET #projects with project ID and offset_id' do before do - get(:projects, project_id: project.id, offset_id: authorized_project.id) + get(:projects, params: { project_id: project.id, offset_id: authorized_project.id }) end it 'returns projects' do @@ -324,7 +324,7 @@ describe AutocompleteController do describe 'GET #projects with project ID' do before do - get(:projects, project_id: project.id) + get(:projects, params: { project_id: project.id }) end it 'returns no projects' do diff --git a/spec/controllers/boards/issues_controller_spec.rb b/spec/controllers/boards/issues_controller_spec.rb index 6d0483f0032..8657fc2ebc0 100644 --- a/spec/controllers/boards/issues_controller_spec.rb +++ b/spec/controllers/boards/issues_controller_spec.rb @@ -153,7 +153,7 @@ describe Boards::IssuesController do params[:project_id] = project end - get :index, params.compact + get :index, params: params.compact end end @@ -230,9 +230,11 @@ describe Boards::IssuesController do def create_issue(user:, board:, list:, title:) sign_in(user) - post :create, board_id: board.to_param, - list_id: list.to_param, - issue: { title: title, project_id: project.id }, + post :create, params: { + board_id: board.to_param, + list_id: list.to_param, + issue: { title: title, project_id: project.id } + }, format: :json end end @@ -291,12 +293,14 @@ describe Boards::IssuesController do def move(user:, board:, issue:, from_list_id:, to_list_id:) sign_in(user) - patch :update, namespace_id: project.namespace.to_param, - project_id: project.id, - board_id: board.to_param, - id: issue.id, - from_list_id: from_list_id, - to_list_id: to_list_id, + patch :update, params: { + namespace_id: project.namespace.to_param, + project_id: project.id, + board_id: board.to_param, + id: issue.id, + from_list_id: from_list_id, + to_list_id: to_list_id + }, format: :json end end diff --git a/spec/controllers/boards/lists_controller_spec.rb b/spec/controllers/boards/lists_controller_spec.rb index 80631d2efb0..70033857168 100644 --- a/spec/controllers/boards/lists_controller_spec.rb +++ b/spec/controllers/boards/lists_controller_spec.rb @@ -46,9 +46,11 @@ describe Boards::ListsController do def read_board_list(user:, board:) sign_in(user) - get :index, namespace_id: project.namespace.to_param, - project_id: project, - board_id: board.to_param, + get :index, params: { + namespace_id: project.namespace.to_param, + project_id: project, + board_id: board.to_param + }, format: :json end end @@ -103,10 +105,12 @@ describe Boards::ListsController do def create_board_list(user:, board:, label_id:) sign_in(user) - post :create, namespace_id: project.namespace.to_param, - project_id: project, - board_id: board.to_param, - list: { label_id: label_id }, + post :create, params: { + namespace_id: project.namespace.to_param, + project_id: project, + board_id: board.to_param, + list: { label_id: label_id } + }, format: :json end end @@ -163,11 +167,7 @@ describe Boards::ListsController do list: { position: position }, format: :json } - if Gitlab.rails5? - patch :update, params: params, as: :json - else - patch :update, params - end + patch :update, params: params, as: :json end end @@ -205,10 +205,12 @@ describe Boards::ListsController do def remove_board_list(user:, board:, list:) sign_in(user) - delete :destroy, namespace_id: project.namespace.to_param, - project_id: project, - board_id: board.to_param, - id: list.to_param, + delete :destroy, params: { + namespace_id: project.namespace.to_param, + project_id: project, + board_id: board.to_param, + id: list.to_param + }, format: :json end end @@ -249,9 +251,11 @@ describe Boards::ListsController do def generate_default_lists(user:, board:) sign_in(user) - post :generate, namespace_id: project.namespace.to_param, - project_id: project, - board_id: board.to_param, + post :generate, params: { + namespace_id: project.namespace.to_param, + project_id: project, + board_id: board.to_param + }, format: :json end end diff --git a/spec/controllers/concerns/controller_with_cross_project_access_check_spec.rb b/spec/controllers/concerns/controller_with_cross_project_access_check_spec.rb index 3c9452cc42a..9b2d054f4fc 100644 --- a/spec/controllers/concerns/controller_with_cross_project_access_check_spec.rb +++ b/spec/controllers/concerns/controller_with_cross_project_access_check_spec.rb @@ -70,7 +70,7 @@ describe ControllerWithCrossProjectAccessCheck do end it 'correctly renders an action that does not require cross project access' do - get :show, id: 'nothing' + get :show, params: { id: 'nothing' } expect(response).to have_gitlab_http_status(200) end @@ -131,13 +131,13 @@ describe ControllerWithCrossProjectAccessCheck do end it 'does not skip the check on an action that is not skipped' do - get :show, id: 'hello' + get :show, params: { id: 'hello' } expect(response).to have_gitlab_http_status(403) end it 'does not skip the check on an action that was not defined to skip' do - get :edit, id: 'hello' + get :edit, params: { id: 'hello' } expect(response).to have_gitlab_http_status(403) end diff --git a/spec/controllers/concerns/group_tree_spec.rb b/spec/controllers/concerns/group_tree_spec.rb index 503eb416962..9fe17210d50 100644 --- a/spec/controllers/concerns/group_tree_spec.rb +++ b/spec/controllers/concerns/group_tree_spec.rb @@ -23,7 +23,7 @@ describe GroupTree do other_group = create(:group, name: 'filter') other_group.add_owner(user) - get :index, filter: 'filt', format: :json + get :index, params: { filter: 'filt' }, format: :json expect(assigns(:groups)).to contain_exactly(other_group) end @@ -40,7 +40,7 @@ describe GroupTree do it 'contains only the subgroup when a parent was given' do subgroup = create(:group, :public, parent: group) - get :index, parent_id: group.id, format: :json + get :index, params: { parent_id: group.id }, format: :json expect(assigns(:groups)).to contain_exactly(subgroup) end @@ -48,7 +48,7 @@ describe GroupTree do it 'allows filtering for subgroups and includes the parents for rendering' do subgroup = create(:group, :public, parent: group, name: 'filter') - get :index, filter: 'filt', format: :json + get :index, params: { filter: 'filt' }, format: :json expect(assigns(:groups)).to contain_exactly(group, subgroup) end @@ -59,7 +59,7 @@ describe GroupTree do subgroup.add_developer(user) _other_subgroup = create(:group, :private, parent: parent, name: 'filte') - get :index, filter: 'filt', format: :json + get :index, params: { filter: 'filt' }, format: :json expect(assigns(:groups)).to contain_exactly(parent, subgroup) end @@ -70,7 +70,7 @@ describe GroupTree do subgroup = create(:group, :public, parent: group) search_result = create(:group, :public, name: 'result', parent: subgroup) - get :index, filter: 'resu', format: :json + get :index, params: { filter: 'resu' }, format: :json expect(assigns(:groups)).to contain_exactly(group, subgroup, search_result) end @@ -87,7 +87,7 @@ describe GroupTree do it 'expands the tree when filtering' do subgroup = create(:group, :public, parent: group, name: 'filter') - get :index, filter: 'filt', format: :json + get :index, params: { filter: 'filt' }, format: :json children_response = json_response.first['children'] diff --git a/spec/controllers/concerns/issuable_collections_spec.rb b/spec/controllers/concerns/issuable_collections_spec.rb index f87eed6ff9f..5a3a7a15f5a 100644 --- a/spec/controllers/concerns/issuable_collections_spec.rb +++ b/spec/controllers/concerns/issuable_collections_spec.rb @@ -90,7 +90,7 @@ describe IssuableCollections do finder_options = controller.send(:finder_options) - expect(finder_options).to eq({ + expect(finder_options).to eq(ActionController::Parameters.new({ 'assignee_id' => '1', 'assignee_username' => 'user1', 'author_id' => '2', @@ -103,7 +103,7 @@ describe IssuableCollections do 'search' => 'baz', 'sort' => 'priority', 'state' => 'opened' - }) + }).permit!) end end end diff --git a/spec/controllers/concerns/lfs_request_spec.rb b/spec/controllers/concerns/lfs_request_spec.rb index 76c878ec5d7..7b49d4b6a3a 100644 --- a/spec/controllers/concerns/lfs_request_spec.rb +++ b/spec/controllers/concerns/lfs_request_spec.rb @@ -34,7 +34,7 @@ describe LfsRequest do describe '#storage_project' do it 'assigns the project as storage project' do - get :show, id: project.id + get :show, params: { id: project.id } expect(assigns(:storage_project)).to eq(project) end @@ -42,7 +42,7 @@ describe LfsRequest do it 'assigns the source of a forked project' do forked_project = fork_project(project) - get :show, id: forked_project.id + get :show, params: { id: forked_project.id } expect(assigns(:storage_project)).to eq(project) end diff --git a/spec/controllers/dashboard/groups_controller_spec.rb b/spec/controllers/dashboard/groups_controller_spec.rb index 9068c1a792e..c8d99f79277 100644 --- a/spec/controllers/dashboard/groups_controller_spec.rb +++ b/spec/controllers/dashboard/groups_controller_spec.rb @@ -33,7 +33,7 @@ describe Dashboard::GroupsController do end it 'renders only groups the user is a member of when searching hierarchy correctly' do - get :index, filter: 'chef', format: :json + get :index, params: { filter: 'chef' }, format: :json expect(response).to have_gitlab_http_status(200) all_groups = [top_level_result, top_level_a, sub_level_result_a] diff --git a/spec/controllers/dashboard/milestones_controller_spec.rb b/spec/controllers/dashboard/milestones_controller_spec.rb index 278b980b6d8..c9ccd5f7c55 100644 --- a/spec/controllers/dashboard/milestones_controller_spec.rb +++ b/spec/controllers/dashboard/milestones_controller_spec.rb @@ -34,7 +34,7 @@ describe Dashboard::MilestonesController do render_views def view_milestone - get :show, id: milestone.safe_title, title: milestone.title + get :show, params: { id: milestone.safe_title, title: milestone.title } end it 'shows milestone page' do @@ -52,7 +52,7 @@ describe Dashboard::MilestonesController do expect(response).to have_gitlab_http_status(200) expect(json_response.size).to eq(2) - expect(json_response.map { |i| i["first_milestone"]["id"] }).to match_array([group_milestone.id, project_milestone.id]) + expect(json_response.map { |i| i["name"] }).to match_array([group_milestone.name, project_milestone.name]) expect(json_response.map { |i| i["group_name"] }.compact).to match_array(group.name) end diff --git a/spec/controllers/dashboard/todos_controller_spec.rb b/spec/controllers/dashboard/todos_controller_spec.rb index e2c799f5205..d88beaff0e1 100644 --- a/spec/controllers/dashboard/todos_controller_spec.rb +++ b/spec/controllers/dashboard/todos_controller_spec.rb @@ -16,19 +16,19 @@ describe Dashboard::TodosController do it 'renders 404 when user does not have read access on given project' do unauthorized_project = create(:project, :private) - get :index, project_id: unauthorized_project.id + get :index, params: { project_id: unauthorized_project.id } expect(response).to have_gitlab_http_status(404) end it 'renders 404 when given project does not exists' do - get :index, project_id: 999 + get :index, params: { project_id: 999 } expect(response).to have_gitlab_http_status(404) end it 'renders 200 when filtering for "any project" todos' do - get :index, project_id: '' + get :index, params: { project_id: '' } expect(response).to have_gitlab_http_status(200) end @@ -36,7 +36,7 @@ describe Dashboard::TodosController do it 'renders 200 when user has access on given project' do authorized_project = create(:project, :public) - get :index, project_id: authorized_project.id + get :index, params: { project_id: authorized_project.id } expect(response).to have_gitlab_http_status(200) end @@ -46,7 +46,7 @@ describe Dashboard::TodosController do it 'renders 404 when user does not have read access on given group' do unauthorized_group = create(:group, :private) - get :index, group_id: unauthorized_group.id + get :index, params: { group_id: unauthorized_group.id } expect(response).to have_gitlab_http_status(404) end @@ -62,13 +62,13 @@ describe Dashboard::TodosController do end it 'redirects to last_page if page number is larger than number of pages' do - get :index, page: (last_page + 1).to_param + get :index, params: { page: (last_page + 1).to_param } expect(response).to redirect_to(dashboard_todos_path(page: last_page)) end it 'goes to the correct page' do - get :index, page: last_page + get :index, params: { page: last_page } expect(assigns(:todos).current_page).to eq(last_page) expect(response).to have_gitlab_http_status(200) @@ -76,7 +76,7 @@ describe Dashboard::TodosController do it 'does not redirect to external sites when provided a host field' do external_host = "www.example.com" - get :index, page: (last_page + 1).to_param, host: external_host + get :index, params: { page: (last_page + 1).to_param, host: external_host } expect(response).to redirect_to(dashboard_todos_path(page: last_page)) end @@ -87,7 +87,7 @@ describe Dashboard::TodosController do expect(user).to receive(:todos_pending_count).and_call_original - get :index, page: (last_page + 1).to_param, sort: :created_asc + get :index, params: { page: (last_page + 1).to_param, sort: :created_asc } expect(response).to redirect_to(dashboard_todos_path(page: last_page, sort: :created_asc)) end @@ -99,7 +99,7 @@ describe Dashboard::TodosController do expect(user).not_to receive(:todos_pending_count) - get :index, page: (last_page + 1).to_param, project_id: project.id + get :index, params: { page: (last_page + 1).to_param, project_id: project.id } expect(response).to redirect_to(dashboard_todos_path(page: last_page, project_id: project.id)) end @@ -111,7 +111,7 @@ describe Dashboard::TodosController do let(:todo) { create(:todo, :done, user: user, project: project, author: author) } it 'restores the todo to pending state' do - patch :restore, id: todo.id + patch :restore, params: { id: todo.id } expect(todo.reload).to be_pending expect(response).to have_gitlab_http_status(200) @@ -123,7 +123,7 @@ describe Dashboard::TodosController do let(:todos) { create_list(:todo, 2, :done, user: user, project: project, author: author) } it 'restores the todos to pending state' do - patch :bulk_restore, ids: todos.map(&:id) + patch :bulk_restore, params: { ids: todos.map(&:id) } todos.each do |todo| expect(todo.reload).to be_pending diff --git a/spec/controllers/explore/projects_controller_spec.rb b/spec/controllers/explore/projects_controller_spec.rb index 2845f258f6f..d57367e931e 100644 --- a/spec/controllers/explore/projects_controller_spec.rb +++ b/spec/controllers/explore/projects_controller_spec.rb @@ -12,13 +12,13 @@ describe Explore::ProjectsController do end it 'sorts by last updated' do - get :trending, sort: 'updated_desc' + get :trending, params: { sort: 'updated_desc' } expect(assigns(:projects)).to eq [project2, project1] end it 'sorts by oldest updated' do - get :trending, sort: 'updated_asc' + get :trending, params: { sort: 'updated_asc' } expect(assigns(:projects)).to eq [project1, project2] end diff --git a/spec/controllers/google_api/authorizations_controller_spec.rb b/spec/controllers/google_api/authorizations_controller_spec.rb index 80d553f0f34..1e8e82da4f3 100644 --- a/spec/controllers/google_api/authorizations_controller_spec.rb +++ b/spec/controllers/google_api/authorizations_controller_spec.rb @@ -6,7 +6,7 @@ describe GoogleApi::AuthorizationsController do let(:token) { 'token' } let(:expires_at) { 1.hour.since.strftime('%s') } - subject { get :callback, code: 'xxx', state: @state } + subject { get :callback, params: { code: 'xxx', state: @state } } before do sign_in(user) diff --git a/spec/controllers/graphql_controller_spec.rb b/spec/controllers/graphql_controller_spec.rb index 949ad532365..a0f40874db1 100644 --- a/spec/controllers/graphql_controller_spec.rb +++ b/spec/controllers/graphql_controller_spec.rb @@ -103,7 +103,7 @@ describe GraphqlController do } QUERY - post :execute, query: query, operationName: 'Echo', variables: variables, private_token: private_token + post :execute, params: { query: query, operationName: 'Echo', variables: variables, private_token: private_token } end def query_response diff --git a/spec/controllers/groups/avatars_controller_spec.rb b/spec/controllers/groups/avatars_controller_spec.rb index 7feecd0c380..772d1d0c1dd 100644 --- a/spec/controllers/groups/avatars_controller_spec.rb +++ b/spec/controllers/groups/avatars_controller_spec.rb @@ -10,7 +10,7 @@ describe Groups::AvatarsController do end it 'removes avatar from DB calling destroy' do - delete :destroy, group_id: group.path + delete :destroy, params: { group_id: group.path } @group = assigns(:group) expect(@group.avatar.present?).to be_falsey expect(@group).to be_valid diff --git a/spec/controllers/groups/boards_controller_spec.rb b/spec/controllers/groups/boards_controller_spec.rb index 99429c93b82..4228e727b52 100644 --- a/spec/controllers/groups/boards_controller_spec.rb +++ b/spec/controllers/groups/boards_controller_spec.rb @@ -105,7 +105,7 @@ describe Groups::BoardsController do end def list_boards(format: :html) - get :index, group_id: group, format: format + get :index, params: { group_id: group }, format: format end end @@ -183,8 +183,10 @@ describe Groups::BoardsController do end def read_board(board:, format: :html) - get :show, group_id: group, - id: board.to_param, + get :show, params: { + group_id: group, + id: board.to_param + }, format: format end end diff --git a/spec/controllers/groups/children_controller_spec.rb b/spec/controllers/groups/children_controller_spec.rb index 22d3076c269..4d5bb1488ab 100644 --- a/spec/controllers/groups/children_controller_spec.rb +++ b/spec/controllers/groups/children_controller_spec.rb @@ -16,7 +16,7 @@ describe Groups::ChildrenController do end it 'shows all children' do - get :index, group_id: group.to_param, format: :json + get :index, params: { group_id: group.to_param }, format: :json expect(assigns(:children)).to contain_exactly(public_project, private_project) end @@ -26,7 +26,7 @@ describe Groups::ChildrenController do group_member.destroy! private_project.add_guest(user) - get :index, group_id: group.to_param, format: :json + get :index, params: { group_id: group.to_param }, format: :json expect(assigns(:children)).to contain_exactly(public_project, private_project) end @@ -35,7 +35,7 @@ describe Groups::ChildrenController do context 'as a guest' do it 'shows the public children' do - get :index, group_id: group.to_param, format: :json + get :index, params: { group_id: group.to_param }, format: :json expect(assigns(:children)).to contain_exactly(public_project) end @@ -54,7 +54,7 @@ describe Groups::ChildrenController do end it 'shows all children' do - get :index, group_id: group.to_param, format: :json + get :index, params: { group_id: group.to_param }, format: :json expect(assigns(:children)).to contain_exactly(public_subgroup, private_subgroup, public_project, private_project) end @@ -65,7 +65,7 @@ describe Groups::ChildrenController do private_subgroup.add_guest(user) private_project.add_guest(user) - get :index, group_id: group.to_param, format: :json + get :index, params: { group_id: group.to_param }, format: :json expect(assigns(:children)).to contain_exactly(public_subgroup, private_subgroup, public_project, private_project) end @@ -74,7 +74,7 @@ describe Groups::ChildrenController do context 'as a guest' do it 'shows the public children' do - get :index, group_id: group.to_param, format: :json + get :index, params: { group_id: group.to_param }, format: :json expect(assigns(:children)).to contain_exactly(public_subgroup, public_project) end @@ -84,7 +84,7 @@ describe Groups::ChildrenController do it 'expands the tree for matching projects' do project = create(:project, :public, namespace: public_subgroup, name: 'filterme') - get :index, group_id: group.to_param, filter: 'filter', format: :json + get :index, params: { group_id: group.to_param, filter: 'filter' }, format: :json group_json = json_response.first project_json = group_json['children'].first @@ -96,7 +96,7 @@ describe Groups::ChildrenController do it 'expands the tree for matching subgroups' do matched_group = create(:group, :public, parent: public_subgroup, name: 'filterme') - get :index, group_id: group.to_param, filter: 'filter', format: :json + get :index, params: { group_id: group.to_param, filter: 'filter' }, format: :json group_json = json_response.first matched_group_json = group_json['children'].first @@ -113,7 +113,7 @@ describe Groups::ChildrenController do l3_subgroup = create(:group, :public, parent: l2_subgroup, path: 'wifi-group') matched_project_2 = create(:project, :public, namespace: l3_subgroup, name: 'mobile') - get :index, group_id: group.to_param, filter: 'mobile', format: :json + get :index, params: { group_id: group.to_param, filter: 'mobile' }, format: :json shared_group_json = json_response.first expect(shared_group_json['id']).to eq(shared_subgroup.id) @@ -136,7 +136,7 @@ describe Groups::ChildrenController do l2_subgroup = create(:group, :public, parent: subgroup) create(:project, :public, namespace: l2_subgroup, name: 'test') - get :index, group_id: subgroup.to_param, filter: 'test', format: :json + get :index, params: { group_id: subgroup.to_param, filter: 'test' }, format: :json expect(response).to have_http_status(200) end @@ -144,7 +144,7 @@ describe Groups::ChildrenController do it 'returns an array with one element when only one result is matched' do create(:project, :public, namespace: group, name: 'match') - get :index, group_id: group.to_param, filter: 'match', format: :json + get :index, params: { group_id: group.to_param, filter: 'match' }, format: :json expect(json_response).to be_kind_of(Array) expect(json_response.size).to eq(1) @@ -155,7 +155,7 @@ describe Groups::ChildrenController do l2_subgroup = create(:group, :public, parent: subgroup) create(:project, :public, namespace: l2_subgroup, name: 'no-match') - get :index, group_id: subgroup.to_param, filter: 'test', format: :json + get :index, params: { group_id: subgroup.to_param, filter: 'test' }, format: :json expect(json_response).to eq([]) end @@ -179,7 +179,7 @@ describe Groups::ChildrenController do end group_to_nest.update!(parent: subgroup) - get :index, group_id: group.to_param, filter: 'filter', per_page: 3, format: :json + get :index, params: { group_id: group.to_param, filter: 'filter', per_page: 3 }, format: :json expect(response).to have_gitlab_http_status(200) end @@ -187,7 +187,7 @@ describe Groups::ChildrenController do it 'includes pagination headers' do 2.times { |i| create(:group, :public, parent: public_subgroup, name: "filterme#{i}") } - get :index, group_id: group.to_param, filter: 'filter', per_page: 1, format: :json + get :index, params: { group_id: group.to_param, filter: 'filter', per_page: 1 }, format: :json expect(response).to include_pagination_headers end @@ -203,7 +203,7 @@ describe Groups::ChildrenController do let(:expected_queries_per_project) { 0 } def get_list - get :index, group_id: group.to_param, format: :json + get :index, params: { group_id: group.to_param }, format: :json end it 'queries the expected amount for a group row' do @@ -227,7 +227,7 @@ describe Groups::ChildrenController do let(:extra_queries_for_hierarchies) { 1 } def get_filtered_list - get :index, group_id: group.to_param, filter: 'filter', format: :json + get :index, params: { group_id: group.to_param, filter: 'filter' }, format: :json end it 'queries the expected amount when nested rows are increased for a group' do @@ -276,13 +276,13 @@ describe Groups::ChildrenController do let!(:first_page_projects) { create_list(:project, per_page, :public, namespace: group ) } it 'has projects on the first page' do - get :index, group_id: group.to_param, sort: 'id_desc', format: :json + get :index, params: { group_id: group.to_param, sort: 'id_desc' }, format: :json expect(assigns(:children)).to contain_exactly(*first_page_projects) end it 'has projects on the second page' do - get :index, group_id: group.to_param, sort: 'id_desc', page: 2, format: :json + get :index, params: { group_id: group.to_param, sort: 'id_desc', page: 2 }, format: :json expect(assigns(:children)).to contain_exactly(other_project) end @@ -294,13 +294,13 @@ describe Groups::ChildrenController do let!(:next_page_projects) { create_list(:project, per_page, :public, namespace: group) } it 'contains all subgroups' do - get :index, group_id: group.to_param, sort: 'id_asc', format: :json + get :index, params: { group_id: group.to_param, sort: 'id_asc' }, format: :json expect(assigns(:children)).to contain_exactly(*first_page_subgroups) end it 'contains the project and group on the second page' do - get :index, group_id: group.to_param, sort: 'id_asc', page: 2, format: :json + get :index, params: { group_id: group.to_param, sort: 'id_asc', page: 2 }, format: :json expect(assigns(:children)).to contain_exactly(other_subgroup, *next_page_projects.take(per_page - 1)) end @@ -310,7 +310,7 @@ describe Groups::ChildrenController do let!(:first_page_projects) { create_list(:project, per_page, :public, namespace: group) } it 'correctly calculates the counts' do - get :index, group_id: group.to_param, sort: 'id_asc', page: 2, format: :json + get :index, params: { group_id: group.to_param, sort: 'id_asc', page: 2 }, format: :json expect(response).to have_gitlab_http_status(200) end diff --git a/spec/controllers/groups/clusters/applications_controller_spec.rb b/spec/controllers/groups/clusters/applications_controller_spec.rb index 68a798542b6..dd5263b077c 100644 --- a/spec/controllers/groups/clusters/applications_controller_spec.rb +++ b/spec/controllers/groups/clusters/applications_controller_spec.rb @@ -81,7 +81,7 @@ describe Groups::Clusters::ApplicationsController do end def go - post :create, params.merge(group_id: group) + post :create, params: params.merge(group_id: group) end end end diff --git a/spec/controllers/groups/clusters_controller_spec.rb b/spec/controllers/groups/clusters_controller_spec.rb index 6e130f830a2..0f28499194e 100644 --- a/spec/controllers/groups/clusters_controller_spec.rb +++ b/spec/controllers/groups/clusters_controller_spec.rb @@ -17,7 +17,7 @@ describe Groups::ClustersController do describe 'GET index' do def go(params = {}) - get :index, params.reverse_merge(group_id: group) + get :index, params: params.reverse_merge(group_id: group) end context 'when feature flag is not enabled' do @@ -104,7 +104,7 @@ describe Groups::ClustersController do describe 'GET new' do def go - get :new, group_id: group + get :new, params: { group_id: group } end describe 'functionality for new cluster' do @@ -198,7 +198,7 @@ describe Groups::ClustersController do end def go - post :create_gcp, params.merge(group_id: group) + post :create_gcp, params: params.merge(group_id: group) end describe 'functionality' do @@ -287,7 +287,7 @@ describe Groups::ClustersController do end def go - post :create_user, params.merge(group_id: group) + post :create_user, params: params.merge(group_id: group) end describe 'functionality' do @@ -353,8 +353,10 @@ describe Groups::ClustersController do def go get :cluster_status, - group_id: group.to_param, - id: cluster, + params: { + group_id: group.to_param, + id: cluster + }, format: :json end @@ -390,8 +392,10 @@ describe Groups::ClustersController do def go get :show, - group_id: group, - id: cluster + params: { + group_id: group, + id: cluster + } end describe 'functionality' do @@ -417,7 +421,7 @@ describe Groups::ClustersController do describe 'PUT update' do def go(format: :html) - put :update, params.merge( + put :update, params: params.merge( group_id: group.to_param, id: cluster, format: format @@ -505,8 +509,10 @@ describe Groups::ClustersController do def go delete :destroy, - group_id: group, - id: cluster + params: { + group_id: group, + id: cluster + } end describe 'functionality' do diff --git a/spec/controllers/groups/group_members_controller_spec.rb b/spec/controllers/groups/group_members_controller_spec.rb index 362d5cc4514..ed38dadfd6b 100644 --- a/spec/controllers/groups/group_members_controller_spec.rb +++ b/spec/controllers/groups/group_members_controller_spec.rb @@ -6,7 +6,7 @@ describe Groups::GroupMembersController do describe 'GET index' do it 'renders index with 200 status code' do - get :index, group_id: group + get :index, params: { group_id: group } expect(response).to have_gitlab_http_status(200) expect(response).to render_template(:index) @@ -26,9 +26,11 @@ describe Groups::GroupMembersController do end it 'returns 403' do - post :create, group_id: group, - user_ids: group_user.id, - access_level: Gitlab::Access::GUEST + post :create, params: { + group_id: group, + user_ids: group_user.id, + access_level: Gitlab::Access::GUEST + } expect(response).to have_gitlab_http_status(403) expect(group.users).not_to include group_user @@ -41,9 +43,11 @@ describe Groups::GroupMembersController do end it 'adds user to members' do - post :create, group_id: group, - user_ids: group_user.id, - access_level: Gitlab::Access::GUEST + post :create, params: { + group_id: group, + user_ids: group_user.id, + access_level: Gitlab::Access::GUEST + } expect(response).to set_flash.to 'Users were successfully added.' expect(response).to redirect_to(group_group_members_path(group)) @@ -51,9 +55,11 @@ describe Groups::GroupMembersController do end it 'adds no user to members' do - post :create, group_id: group, - user_ids: '', - access_level: Gitlab::Access::GUEST + post :create, params: { + group_id: group, + user_ids: '', + access_level: Gitlab::Access::GUEST + } expect(response).to set_flash.to 'No users specified.' expect(response).to redirect_to(group_group_members_path(group)) @@ -72,9 +78,11 @@ describe Groups::GroupMembersController do Gitlab::Access.options.each do |label, value| it "can change the access level to #{label}" do - xhr :put, :update, group_member: { access_level: value }, - group_id: group, - id: requester + put :update, params: { + group_member: { access_level: value }, + group_id: group, + id: requester + }, xhr: true expect(requester.reload.human_access).to eq(label) end @@ -90,7 +98,7 @@ describe Groups::GroupMembersController do context 'when member is not found' do it 'returns 403' do - delete :destroy, group_id: group, id: 42 + delete :destroy, params: { group_id: group, id: 42 } expect(response).to have_gitlab_http_status(403) end @@ -103,7 +111,7 @@ describe Groups::GroupMembersController do end it 'returns 403' do - delete :destroy, group_id: group, id: member + delete :destroy, params: { group_id: group, id: member } expect(response).to have_gitlab_http_status(403) expect(group.members).to include member @@ -116,7 +124,7 @@ describe Groups::GroupMembersController do end it '[HTML] removes user from members' do - delete :destroy, group_id: group, id: member + delete :destroy, params: { group_id: group, id: member } expect(response).to set_flash.to 'User was successfully removed from group.' expect(response).to redirect_to(group_group_members_path(group)) @@ -124,7 +132,7 @@ describe Groups::GroupMembersController do end it '[JS] removes user from members' do - xhr :delete, :destroy, group_id: group, id: member + delete :destroy, params: { group_id: group, id: member }, xhr: true expect(response).to be_success expect(group.members).not_to include member @@ -140,7 +148,7 @@ describe Groups::GroupMembersController do context 'when member is not found' do it 'returns 404' do - delete :leave, group_id: group + delete :leave, params: { group_id: group } expect(response).to have_gitlab_http_status(404) end @@ -153,7 +161,7 @@ describe Groups::GroupMembersController do end it 'removes user from members' do - delete :leave, group_id: group + delete :leave, params: { group_id: group } expect(response).to set_flash.to "You left the \"#{group.name}\" group." expect(response).to redirect_to(dashboard_groups_path) @@ -161,7 +169,7 @@ describe Groups::GroupMembersController do end it 'supports json request' do - delete :leave, group_id: group, format: :json + delete :leave, params: { group_id: group }, format: :json expect(response).to have_gitlab_http_status(200) expect(json_response['notice']).to eq "You left the \"#{group.name}\" group." @@ -174,7 +182,7 @@ describe Groups::GroupMembersController do end it 'cannot removes himself from the group' do - delete :leave, group_id: group + delete :leave, params: { group_id: group } expect(response).to have_gitlab_http_status(403) end @@ -186,7 +194,7 @@ describe Groups::GroupMembersController do end it 'removes user from members' do - delete :leave, group_id: group + delete :leave, params: { group_id: group } expect(response).to set_flash.to 'Your access request to the group has been withdrawn.' expect(response).to redirect_to(group_path(group)) @@ -203,7 +211,7 @@ describe Groups::GroupMembersController do end it 'creates a new GroupMember that is not a team member' do - post :request_access, group_id: group + post :request_access, params: { group_id: group } expect(response).to set_flash.to 'Your request for access has been queued for review.' expect(response).to redirect_to(group_path(group)) @@ -221,7 +229,7 @@ describe Groups::GroupMembersController do context 'when member is not found' do it 'returns 403' do - post :approve_access_request, group_id: group, id: 42 + post :approve_access_request, params: { group_id: group, id: 42 } expect(response).to have_gitlab_http_status(403) end @@ -234,7 +242,7 @@ describe Groups::GroupMembersController do end it 'returns 403' do - post :approve_access_request, group_id: group, id: member + post :approve_access_request, params: { group_id: group, id: member } expect(response).to have_gitlab_http_status(403) expect(group.members).not_to include member @@ -247,7 +255,7 @@ describe Groups::GroupMembersController do end it 'adds user to members' do - post :approve_access_request, group_id: group, id: member + post :approve_access_request, params: { group_id: group, id: member } expect(response).to redirect_to(group_group_members_path(group)) expect(group.members).to include member diff --git a/spec/controllers/groups/labels_controller_spec.rb b/spec/controllers/groups/labels_controller_spec.rb index 185b6b4ce57..fa664a29066 100644 --- a/spec/controllers/groups/labels_controller_spec.rb +++ b/spec/controllers/groups/labels_controller_spec.rb @@ -16,7 +16,7 @@ describe Groups::LabelsController do set(:group_label_1) { create(:group_label, group: group, title: 'group_label_1') } it 'returns group and project labels by default' do - get :index, group_id: group, format: :json + get :index, params: { group_id: group }, format: :json label_ids = json_response.map {|label| label['title']} expect(label_ids).to match_array([label_1.title, group_label_1.title]) @@ -31,7 +31,7 @@ describe Groups::LabelsController do end it 'returns ancestor group labels', :nested_groups do - get :index, group_id: subgroup, include_ancestor_groups: true, only_group_labels: true, format: :json + get :index, params: { group_id: subgroup, include_ancestor_groups: true, only_group_labels: true }, format: :json label_ids = json_response.map {|label| label['title']} expect(label_ids).to match_array([group_label_1.title, subgroup_label_1.title]) @@ -43,7 +43,7 @@ describe Groups::LabelsController do it 'allows user to toggle subscription on group labels' do label = create(:group_label, group: group) - post :toggle_subscription, group_id: group.to_param, id: label.to_param + post :toggle_subscription, params: { group_id: group.to_param, id: label.to_param } expect(response).to have_gitlab_http_status(200) end diff --git a/spec/controllers/groups/milestones_controller_spec.rb b/spec/controllers/groups/milestones_controller_spec.rb index 42723bb3820..40d991a669c 100644 --- a/spec/controllers/groups/milestones_controller_spec.rb +++ b/spec/controllers/groups/milestones_controller_spec.rb @@ -33,7 +33,7 @@ describe Groups::MilestonesController do describe '#index' do it 'shows group milestones page' do - get :index, group_id: group.to_param + get :index, params: { group_id: group.to_param } expect(response).to have_gitlab_http_status(200) end @@ -44,7 +44,7 @@ describe Groups::MilestonesController do let!(:legacy_milestone2) { create(:milestone, project: project2, title: 'legacy') } it 'lists legacy group milestones and group milestones' do - get :index, group_id: group.to_param, format: :json + get :index, params: { group_id: group.to_param }, format: :json milestones = JSON.parse(response.body) @@ -64,10 +64,10 @@ describe Groups::MilestonesController do context 'when there is a title parameter' do it 'searches for a legacy group milestone' do - expect(GlobalMilestone).to receive(:build) + expect(GroupMilestone).to receive(:build) expect(Milestone).not_to receive(:find_by_iid) - get :show, group_id: group.to_param, id: title, title: milestone1.safe_title + get :show, params: { group_id: group.to_param, id: title, title: milestone1.safe_title } end end @@ -76,7 +76,7 @@ describe Groups::MilestonesController do expect(GlobalMilestone).not_to receive(:build) expect(Milestone).to receive(:find_by_iid) - get :show, group_id: group.to_param, id: group_milestone.id + get :show, params: { group_id: group.to_param, id: group_milestone.id } end end end @@ -86,8 +86,10 @@ describe Groups::MilestonesController do describe "#create" do it "creates group milestone with Chinese title" do post :create, - group_id: group.to_param, - milestone: milestone_params + params: { + group_id: group.to_param, + milestone: milestone_params + } milestone = Milestone.find_by_title(title) @@ -105,9 +107,11 @@ describe Groups::MilestonesController do milestone_params[:title] = "title changed" put :update, - id: milestone.iid, - group_id: group.to_param, - milestone: milestone_params + params: { + id: milestone.iid, + group_id: group.to_param, + milestone: milestone_params + } milestone.reload expect(response).to redirect_to(group_milestone_path(group, milestone.iid)) @@ -124,10 +128,12 @@ describe Groups::MilestonesController do milestone_params[:state_event] = "close" put :update, - id: milestone1.title.to_slug.to_s, - group_id: group.to_param, - milestone: milestone_params, - title: milestone1.title + params: { + id: milestone1.title.to_slug.to_s, + group_id: group.to_param, + milestone: milestone_params, + title: milestone1.title + } expect(response).to redirect_to(group_milestone_path(group, milestone1.safe_title, title: milestone1.title)) @@ -145,7 +151,7 @@ describe Groups::MilestonesController do let(:milestone) { create(:milestone, group: group) } it "removes milestone" do - delete :destroy, group_id: group.to_param, id: milestone.iid, format: :js + delete :destroy, params: { group_id: group.to_param, id: milestone.iid }, format: :js expect(response).to be_success expect { Milestone.find(milestone.id) }.to raise_exception(ActiveRecord::RecordNotFound) @@ -162,7 +168,7 @@ describe Groups::MilestonesController do context 'non-show path' do context 'with exactly matching casing' do it 'does not redirect' do - get :index, group_id: group.to_param + get :index, params: { group_id: group.to_param } expect(response).not_to have_gitlab_http_status(301) end @@ -170,7 +176,7 @@ describe Groups::MilestonesController do context 'with different casing' do it 'redirects to the correct casing' do - get :index, group_id: group.to_param.upcase + get :index, params: { group_id: group.to_param.upcase } expect(response).to redirect_to(group_milestones_path(group.to_param)) expect(controller).not_to set_flash[:notice] @@ -181,7 +187,7 @@ describe Groups::MilestonesController do context 'show path' do context 'with exactly matching casing' do it 'does not redirect' do - get :show, group_id: group.to_param, id: title + get :show, params: { group_id: group.to_param, id: title } expect(response).not_to have_gitlab_http_status(301) end @@ -189,7 +195,7 @@ describe Groups::MilestonesController do context 'with different casing' do it 'redirects to the correct casing' do - get :show, group_id: group.to_param.upcase, id: title + get :show, params: { group_id: group.to_param.upcase, id: title } expect(response).to redirect_to(group_milestone_path(group.to_param, title)) expect(controller).not_to set_flash[:notice] @@ -202,7 +208,7 @@ describe Groups::MilestonesController do let(:redirect_route) { group.redirect_routes.create(path: 'old-path') } it 'redirects to the canonical path' do - get :merge_requests, group_id: redirect_route.path, id: title + get :merge_requests, params: { group_id: redirect_route.path, id: title } expect(response).to redirect_to(merge_requests_group_milestone_path(group.to_param, title)) expect(controller).to set_flash[:notice].to(group_moved_message(redirect_route, group)) @@ -212,7 +218,7 @@ describe Groups::MilestonesController do let(:redirect_route) { group.redirect_routes.create(path: 'http') } it 'does not modify the requested host' do - get :merge_requests, group_id: redirect_route.path, id: title + get :merge_requests, params: { group_id: redirect_route.path, id: title } expect(response).to redirect_to(merge_requests_group_milestone_path(group.to_param, title)) expect(controller).to set_flash[:notice].to(group_moved_message(redirect_route, group)) @@ -224,7 +230,7 @@ describe Groups::MilestonesController do let(:redirect_route) { group.redirect_routes.create(path: 'oups') } it 'does not modify the /groups part of the path' do - get :merge_requests, group_id: redirect_route.path, id: title + get :merge_requests, params: { group_id: redirect_route.path, id: title } expect(response).to redirect_to(merge_requests_group_milestone_path(group.to_param, title)) expect(controller).to set_flash[:notice].to(group_moved_message(redirect_route, group)) @@ -236,7 +242,7 @@ describe Groups::MilestonesController do let(:redirect_route) { group.redirect_routes.create(path: 'oups/oup') } it 'does not modify the /groups part of the path' do - get :merge_requests, group_id: redirect_route.path, id: title + get :merge_requests, params: { group_id: redirect_route.path, id: title } expect(response).to redirect_to(merge_requests_group_milestone_path(group.to_param, title)) expect(controller).to set_flash[:notice].to(group_moved_message(redirect_route, group)) @@ -250,16 +256,20 @@ describe Groups::MilestonesController do context 'when requesting the canonical path with different casing' do it 'does not 404' do post :create, - group_id: group.to_param, - milestone: { title: title } + params: { + group_id: group.to_param, + milestone: { title: title } + } expect(response).not_to have_gitlab_http_status(404) end it 'does not redirect to the correct casing' do post :create, - group_id: group.to_param, - milestone: { title: title } + params: { + group_id: group.to_param, + milestone: { title: title } + } expect(response).not_to have_gitlab_http_status(301) end @@ -270,8 +280,10 @@ describe Groups::MilestonesController do it 'returns not found' do post :create, - group_id: redirect_route.path, - milestone: { title: title } + params: { + group_id: redirect_route.path, + milestone: { title: title } + } expect(response).to have_gitlab_http_status(404) end diff --git a/spec/controllers/groups/runners_controller_spec.rb b/spec/controllers/groups/runners_controller_spec.rb index 598fb84552f..469459bfc02 100644 --- a/spec/controllers/groups/runners_controller_spec.rb +++ b/spec/controllers/groups/runners_controller_spec.rb @@ -22,7 +22,7 @@ describe Groups::RunnersController do new_desc = runner.description.swapcase expect do - post :update, params.merge(runner: { description: new_desc } ) + post :update, params: params.merge(runner: { description: new_desc } ) end.to change { runner.ensure_runner_queue_value } runner.reload @@ -34,7 +34,7 @@ describe Groups::RunnersController do describe '#destroy' do it 'destroys the runner' do - delete :destroy, params + delete :destroy, params: params expect(response).to have_gitlab_http_status(302) expect(Ci::Runner.find_by(id: runner.id)).to be_nil @@ -46,7 +46,7 @@ describe Groups::RunnersController do runner.update(active: false) expect do - post :resume, params + post :resume, params: params end.to change { runner.ensure_runner_queue_value } runner.reload @@ -61,7 +61,7 @@ describe Groups::RunnersController do runner.update(active: true) expect do - post :pause, params + post :pause, params: params end.to change { runner.ensure_runner_queue_value } runner.reload diff --git a/spec/controllers/groups/settings/ci_cd_controller_spec.rb b/spec/controllers/groups/settings/ci_cd_controller_spec.rb index 06ccace8242..40673d10b91 100644 --- a/spec/controllers/groups/settings/ci_cd_controller_spec.rb +++ b/spec/controllers/groups/settings/ci_cd_controller_spec.rb @@ -5,30 +5,65 @@ describe Groups::Settings::CiCdController do let(:user) { create(:user) } before do - group.add_maintainer(user) sign_in(user) end describe 'GET #show' do - it 'renders show with 200 status code' do - get :show, group_id: group + context 'when user is owner' do + before do + group.add_owner(user) + end - expect(response).to have_gitlab_http_status(200) - expect(response).to render_template(:show) + it 'renders show with 200 status code' do + get :show, params: { group_id: group } + + expect(response).to have_gitlab_http_status(200) + expect(response).to render_template(:show) + end + end + + context 'when user is not owner' do + before do + group.add_maintainer(user) + end + + it 'renders a 404' do + get :show, params: { group_id: group } + + expect(response).to have_gitlab_http_status(404) + end end end describe 'PUT #reset_registration_token' do - subject { put :reset_registration_token, group_id: group } + subject { put :reset_registration_token, params: { group_id: group } } - it 'resets runner registration token' do - expect { subject }.to change { group.reload.runners_token } + context 'when user is owner' do + before do + group.add_owner(user) + end + + it 'resets runner registration token' do + expect { subject }.to change { group.reload.runners_token } + end + + it 'redirects the user to admin runners page' do + subject + + expect(response).to redirect_to(group_settings_ci_cd_path) + end end - it 'redirects the user to admin runners page' do - subject + context 'when user is not owner' do + before do + group.add_maintainer(user) + end + + it 'renders a 404' do + subject - expect(response).to redirect_to(group_settings_ci_cd_path) + expect(response).to have_gitlab_http_status(404) + end end end end diff --git a/spec/controllers/groups/shared_projects_controller_spec.rb b/spec/controllers/groups/shared_projects_controller_spec.rb index 003c8c262e7..dab7700cf64 100644 --- a/spec/controllers/groups/shared_projects_controller_spec.rb +++ b/spec/controllers/groups/shared_projects_controller_spec.rb @@ -2,7 +2,7 @@ require 'spec_helper' describe Groups::SharedProjectsController do def get_shared_projects(params = {}) - get :index, params.reverse_merge(format: :json, group_id: group.full_path) + get :index, params: params.reverse_merge(format: :json, group_id: group.full_path) end def share_project(project) diff --git a/spec/controllers/groups/uploads_controller_spec.rb b/spec/controllers/groups/uploads_controller_spec.rb index 5a7281ed704..0104ba827da 100644 --- a/spec/controllers/groups/uploads_controller_spec.rb +++ b/spec/controllers/groups/uploads_controller_spec.rb @@ -15,6 +15,6 @@ describe Groups::UploadsController do def post_authorize(verified: true) request.headers.merge!(workhorse_internal_api_request_header) if verified - post :authorize, group_id: model.full_path, format: :json + post :authorize, params: { group_id: model.full_path }, format: :json end end diff --git a/spec/controllers/groups/variables_controller_spec.rb b/spec/controllers/groups/variables_controller_spec.rb index e5ac5634f95..29ec3588316 100644 --- a/spec/controllers/groups/variables_controller_spec.rb +++ b/spec/controllers/groups/variables_controller_spec.rb @@ -13,7 +13,7 @@ describe Groups::VariablesController do let!(:variable) { create(:ci_group_variable, group: group) } subject do - get :show, group_id: group, format: :json + get :show, params: { group_id: group }, format: :json end include_examples 'GET #show lists all variables' @@ -25,8 +25,10 @@ describe Groups::VariablesController do subject do patch :update, - group_id: group, - variables_attributes: variables_attributes, + params: { + group_id: group, + variables_attributes: variables_attributes + }, format: :json end diff --git a/spec/controllers/groups_controller_spec.rb b/spec/controllers/groups_controller_spec.rb index 4b0dc4c9b69..7d87b33e503 100644 --- a/spec/controllers/groups_controller_spec.rb +++ b/spec/controllers/groups_controller_spec.rb @@ -15,7 +15,7 @@ describe GroupsController do it 'renders the new page' do sign_in(member) - get :new, parent_id: group.id + get :new, params: { parent_id: group.id } expect(response).to render_template(:new) end @@ -25,7 +25,7 @@ describe GroupsController do it 'renders the 404 page' do sign_in(member) - get :new, parent_id: group.id + get :new, params: { parent_id: group.id } expect(response).not_to render_template(:new) expect(response.status).to eq(404) @@ -42,7 +42,7 @@ describe GroupsController do it 'assigns events for all the projects in the group' do create(:event, project: project) - get :show, id: group.to_param, format: :atom + get :show, params: { id: group.to_param }, format: :atom expect(assigns(:events)).not_to be_empty end @@ -53,7 +53,7 @@ describe GroupsController do it 'sets the badge API endpoint' do sign_in(owner) - get :edit, id: group.to_param + get :edit, params: { id: group.to_param } expect(assigns(:badge_api_endpoint)).not_to be_nil end @@ -102,7 +102,7 @@ describe GroupsController do create(:event, project: project) end - get :activity, id: group.to_param, format: :json + get :activity, params: { id: group.to_param }, format: :json expect(response).to have_gitlab_http_status(200) expect(json_response['count']).to eq(3) @@ -120,7 +120,7 @@ describe GroupsController do owner.update_attribute(:can_create_group, can_create_group_status) sign_in(owner) - post :create, group: { parent_id: group.id, path: 'subgroup' } + post :create, params: { group: { parent_id: group.id, path: 'subgroup' } } expect(response).to be_redirect expect(response.body).to match(%r{http://test.host/#{group.path}/subgroup}) @@ -134,7 +134,7 @@ describe GroupsController do previous_group_count = Group.count - post :create, group: { parent_id: group.id, path: 'subgroup' } + post :create, params: { group: { parent_id: group.id, path: 'subgroup' } } expect(response).to render_template(:new) expect(Group.count).to eq(previous_group_count) @@ -157,7 +157,7 @@ describe GroupsController do it 'creates the Group' do original_group_count = Group.count - post :create, group: { path: 'subgroup' } + post :create, params: { group: { path: 'subgroup' } } expect(Group.count).to eq(original_group_count + 1) expect(response).to be_redirect @@ -172,7 +172,7 @@ describe GroupsController do it 'does not create the Group' do original_group_count = Group.count - post :create, group: { path: 'subgroup' } + post :create, params: { group: { path: 'subgroup' } } expect(Group.count).to eq(original_group_count) expect(response).to render_template(:new) @@ -215,12 +215,12 @@ describe GroupsController do context 'sorting by votes' do it 'sorts most popular issues' do - get :issues, id: group.to_param, sort: 'upvotes_desc' + get :issues, params: { id: group.to_param, sort: 'upvotes_desc' } expect(assigns(:issues)).to eq [issue_2, issue_1] end it 'sorts least popular issues' do - get :issues, id: group.to_param, sort: 'downvotes_desc' + get :issues, params: { id: group.to_param, sort: 'downvotes_desc' } expect(assigns(:issues)).to eq [issue_2, issue_1] end end @@ -233,19 +233,19 @@ describe GroupsController do end it 'works with popularity sort' do - get :issues, id: group.to_param, search: 'foo', sort: 'popularity' + get :issues, params: { id: group.to_param, search: 'foo', sort: 'popularity' } expect(assigns(:issues)).to eq([issue_1]) end it 'works with priority sort' do - get :issues, id: group.to_param, search: 'foo', sort: 'priority' + get :issues, params: { id: group.to_param, search: 'foo', sort: 'priority' } expect(assigns(:issues)).to eq([issue_1]) end it 'works with label priority sort' do - get :issues, id: group.to_param, search: 'foo', sort: 'label_priority' + get :issues, params: { id: group.to_param, search: 'foo', sort: 'label_priority' } expect(assigns(:issues)).to eq([issue_1]) end @@ -266,12 +266,12 @@ describe GroupsController do context 'sorting by votes' do it 'sorts most popular merge requests' do - get :merge_requests, id: group.to_param, sort: 'upvotes_desc' + get :merge_requests, params: { id: group.to_param, sort: 'upvotes_desc' } expect(assigns(:merge_requests)).to eq [merge_request_2, merge_request_1] end it 'sorts least popular merge requests' do - get :merge_requests, id: group.to_param, sort: 'downvotes_desc' + get :merge_requests, params: { id: group.to_param, sort: 'downvotes_desc' } expect(assigns(:merge_requests)).to eq [merge_request_2, merge_request_1] end end @@ -282,7 +282,7 @@ describe GroupsController do it 'returns 404' do sign_in(create(:user)) - delete :destroy, id: group.to_param + delete :destroy, params: { id: group.to_param } expect(response.status).to eq(404) end @@ -295,12 +295,12 @@ describe GroupsController do it 'schedules a group destroy' do Sidekiq::Testing.fake! do - expect { delete :destroy, id: group.to_param }.to change(GroupDestroyWorker.jobs, :size).by(1) + expect { delete :destroy, params: { id: group.to_param } }.to change(GroupDestroyWorker.jobs, :size).by(1) end end it 'redirects to the root path' do - delete :destroy, id: group.to_param + delete :destroy, params: { id: group.to_param } expect(response).to redirect_to(root_path) end @@ -313,7 +313,7 @@ describe GroupsController do end it 'updates the path successfully' do - post :update, id: group.to_param, group: { path: 'new_path' } + post :update, params: { id: group.to_param, group: { path: 'new_path' } } expect(response).to have_gitlab_http_status(302) expect(controller).to set_flash[:notice] @@ -321,7 +321,7 @@ describe GroupsController do it 'does not update the path on error' do allow_any_instance_of(Group).to receive(:move_dir).and_raise(Gitlab::UpdatePathError) - post :update, id: group.to_param, group: { path: 'new_path' } + post :update, params: { id: group.to_param, group: { path: 'new_path' } } expect(assigns(:group).errors).not_to be_empty expect(assigns(:group).path).not_to eq('new_path') @@ -337,7 +337,7 @@ describe GroupsController do context 'when requesting groups at the root path' do before do allow(request).to receive(:original_fullpath).and_return("/#{group_full_path}") - get :show, id: group_full_path + get :show, params: { id: group_full_path } end context 'when requesting the canonical path with different casing' do @@ -384,7 +384,7 @@ describe GroupsController do context 'non-show path' do context 'with exactly matching casing' do it 'does not redirect' do - get :issues, id: group.to_param + get :issues, params: { id: group.to_param } expect(response).not_to have_gitlab_http_status(301) end @@ -392,7 +392,7 @@ describe GroupsController do context 'with different casing' do it 'redirects to the correct casing' do - get :issues, id: group.to_param.upcase + get :issues, params: { id: group.to_param.upcase } expect(response).to redirect_to(issues_group_path(group.to_param)) expect(controller).not_to set_flash[:notice] @@ -403,7 +403,7 @@ describe GroupsController do context 'show path' do context 'with exactly matching casing' do it 'does not redirect' do - get :show, id: group.to_param + get :show, params: { id: group.to_param } expect(response).not_to have_gitlab_http_status(301) end @@ -411,7 +411,7 @@ describe GroupsController do context 'with different casing' do it 'redirects to the correct casing at the root path' do - get :show, id: group.to_param.upcase + get :show, params: { id: group.to_param.upcase } expect(response).to redirect_to(group) expect(controller).not_to set_flash[:notice] @@ -424,7 +424,7 @@ describe GroupsController do let(:redirect_route) { group.redirect_routes.create(path: 'old-path') } it 'redirects to the canonical path' do - get :issues, id: redirect_route.path + get :issues, params: { id: redirect_route.path } expect(response).to redirect_to(issues_group_path(group.to_param)) expect(controller).to set_flash[:notice].to(group_moved_message(redirect_route, group)) @@ -434,7 +434,7 @@ describe GroupsController do let(:redirect_route) { group.redirect_routes.create(path: 'http') } it 'does not modify the requested host' do - get :issues, id: redirect_route.path + get :issues, params: { id: redirect_route.path } expect(response).to redirect_to(issues_group_path(group.to_param)) expect(controller).to set_flash[:notice].to(group_moved_message(redirect_route, group)) @@ -446,7 +446,7 @@ describe GroupsController do let(:redirect_route) { group.redirect_routes.create(path: 'oups') } it 'does not modify the /groups part of the path' do - get :issues, id: redirect_route.path + get :issues, params: { id: redirect_route.path } expect(response).to redirect_to(issues_group_path(group.to_param)) expect(controller).to set_flash[:notice].to(group_moved_message(redirect_route, group)) @@ -458,7 +458,7 @@ describe GroupsController do let(:redirect_route) { group.redirect_routes.create(path: 'oups/oup') } it 'does not modify the /groups part of the path' do - get :issues, id: redirect_route.path + get :issues, params: { id: redirect_route.path } expect(response).to redirect_to(issues_group_path(group.to_param)) expect(controller).to set_flash[:notice].to(group_moved_message(redirect_route, group)) @@ -470,13 +470,13 @@ describe GroupsController do context 'for a POST request' do context 'when requesting the canonical path with different casing' do it 'does not 404' do - post :update, id: group.to_param.upcase, group: { path: 'new_path' } + post :update, params: { id: group.to_param.upcase, group: { path: 'new_path' } } expect(response).not_to have_gitlab_http_status(404) end it 'does not redirect to the correct casing' do - post :update, id: group.to_param.upcase, group: { path: 'new_path' } + post :update, params: { id: group.to_param.upcase, group: { path: 'new_path' } } expect(response).not_to have_gitlab_http_status(301) end @@ -486,7 +486,7 @@ describe GroupsController do let(:redirect_route) { group.redirect_routes.create(path: 'old-path') } it 'returns not found' do - post :update, id: redirect_route.path, group: { path: 'new_path' } + post :update, params: { id: redirect_route.path, group: { path: 'new_path' } } expect(response).to have_gitlab_http_status(404) end @@ -496,13 +496,13 @@ describe GroupsController do context 'for a DELETE request' do context 'when requesting the canonical path with different casing' do it 'does not 404' do - delete :destroy, id: group.to_param.upcase + delete :destroy, params: { id: group.to_param.upcase } expect(response).not_to have_gitlab_http_status(404) end it 'does not redirect to the correct casing' do - delete :destroy, id: group.to_param.upcase + delete :destroy, params: { id: group.to_param.upcase } expect(response).not_to have_gitlab_http_status(301) end @@ -512,7 +512,7 @@ describe GroupsController do let(:redirect_route) { group.redirect_routes.create(path: 'old-path') } it 'returns not found' do - delete :destroy, id: redirect_route.path + delete :destroy, params: { id: redirect_route.path } expect(response).to have_gitlab_http_status(404) end @@ -537,8 +537,10 @@ describe GroupsController do before do put :transfer, - id: group.to_param, - new_parent_group_id: new_parent_group.id + params: { + id: group.to_param, + new_parent_group_id: new_parent_group.id + } end it 'should return a notice' do @@ -556,8 +558,10 @@ describe GroupsController do before do put :transfer, - id: group.to_param, - new_parent_group_id: '' + params: { + id: group.to_param, + new_parent_group_id: '' + } end it 'should return a notice' do @@ -578,8 +582,10 @@ describe GroupsController do allow_any_instance_of(::Groups::TransferService).to receive(:proceed_to_transfer).and_raise(Gitlab::UpdatePathError, 'namespace directory cannot be moved') put :transfer, - id: group.to_param, - new_parent_group_id: new_parent_group.id + params: { + id: group.to_param, + new_parent_group_id: new_parent_group.id + } end it 'should return an alert' do @@ -598,8 +604,10 @@ describe GroupsController do before do put :transfer, - id: group.to_param, - new_parent_group_id: new_parent_group.id + params: { + id: group.to_param, + new_parent_group_id: new_parent_group.id + } end it 'should be denied' do diff --git a/spec/controllers/health_check_controller_spec.rb b/spec/controllers/health_check_controller_spec.rb index 387ca46ef6f..29e159ad5d7 100644 --- a/spec/controllers/health_check_controller_spec.rb +++ b/spec/controllers/health_check_controller_spec.rb @@ -37,7 +37,7 @@ describe HealthCheckController do end it 'supports passing the token in query params' do - get :index, token: token + get :index, params: { token: token } expect(response).to be_success expect(response.content_type).to eq 'text/plain' @@ -74,7 +74,7 @@ describe HealthCheckController do end it 'supports successful responses for specific checks' do - get :index, checks: 'email', format: :json + get :index, params: { checks: 'email' }, format: :json expect(response).to be_success expect(response.content_type).to eq 'application/json' @@ -124,7 +124,7 @@ describe HealthCheckController do end it 'supports failure responses for specific checks' do - get :index, checks: 'email', format: :json + get :index, params: { checks: 'email' }, format: :json expect(response).to have_gitlab_http_status(500) expect(response.content_type).to eq 'application/json' diff --git a/spec/controllers/health_controller_spec.rb b/spec/controllers/health_controller_spec.rb index ec73c89cb11..f685f2b41c0 100644 --- a/spec/controllers/health_controller_spec.rb +++ b/spec/controllers/health_controller_spec.rb @@ -18,7 +18,7 @@ describe HealthController do shared_context 'endpoint responding with readiness data' do let(:request_params) { {} } - subject { get :readiness, request_params } + subject { get :readiness, params: request_params } it 'responds with readiness checks data' do subject @@ -112,7 +112,7 @@ describe HealthController do context 'token passed as URL param' do it_behaves_like 'endpoint responding with liveness data' do - subject { get :liveness, token: token } + subject { get :liveness, params: { token: token } } end end end diff --git a/spec/controllers/help_controller_spec.rb b/spec/controllers/help_controller_spec.rb index 21d59c62613..5cb284e7e2d 100644 --- a/spec/controllers/help_controller_spec.rb +++ b/spec/controllers/help_controller_spec.rb @@ -43,7 +43,7 @@ describe HelpController do context 'for Markdown formats' do context 'when requested file exists' do before do - get :show, path: 'ssh/README', format: :md + get :show, params: { path: 'ssh/README' }, format: :md end it 'assigns to @markdown' do @@ -58,7 +58,7 @@ describe HelpController do context 'when requested file is missing' do it 'renders not found' do - get :show, path: 'foo/bar', format: :md + get :show, params: { path: 'foo/bar' }, format: :md expect(response).to be_not_found end end @@ -68,7 +68,9 @@ describe HelpController do context 'when requested file exists' do it 'renders the raw file' do get :show, - path: 'user/project/img/labels_default', + params: { + path: 'user/project/img/labels_default' + }, format: :png expect(response).to be_success expect(response.content_type).to eq 'image/png' @@ -79,7 +81,9 @@ describe HelpController do context 'when requested file is missing' do it 'renders not found' do get :show, - path: 'foo/bar', + params: { + path: 'foo/bar' + }, format: :png expect(response).to be_not_found end @@ -89,7 +93,9 @@ describe HelpController do context 'for other formats' do it 'always renders not found' do get :show, - path: 'ssh/README', + params: { + path: 'ssh/README' + }, format: :foo expect(response).to be_not_found end diff --git a/spec/controllers/import/bitbucket_controller_spec.rb b/spec/controllers/import/bitbucket_controller_spec.rb index be49b92d23f..51793f2c048 100644 --- a/spec/controllers/import/bitbucket_controller_spec.rb +++ b/spec/controllers/import/bitbucket_controller_spec.rb @@ -237,7 +237,7 @@ describe Import::BitbucketController do .to receive(:new).with(bitbucket_repo, test_name, nested_namespace, user, access_params) .and_return(double(execute: project)) - post :create, { target_namespace: nested_namespace.full_path, new_name: test_name, format: :json } + post :create, params: { target_namespace: nested_namespace.full_path, new_name: test_name }, format: :json end end @@ -249,7 +249,7 @@ describe Import::BitbucketController do .to receive(:new).with(bitbucket_repo, test_name, kind_of(Namespace), user, access_params) .and_return(double(execute: project)) - post :create, { target_namespace: 'foo/bar', new_name: test_name, format: :json } + post :create, params: { target_namespace: 'foo/bar', new_name: test_name }, format: :json end it 'creates the namespaces' do @@ -257,7 +257,7 @@ describe Import::BitbucketController do .to receive(:new).with(bitbucket_repo, test_name, kind_of(Namespace), user, access_params) .and_return(double(execute: project)) - expect { post :create, { target_namespace: 'foo/bar', new_name: test_name, format: :json } } + expect { post :create, params: { target_namespace: 'foo/bar', new_name: test_name }, format: :json } .to change { Namespace.count }.by(2) end @@ -266,7 +266,7 @@ describe Import::BitbucketController do .to receive(:new).with(bitbucket_repo, test_name, kind_of(Namespace), user, access_params) .and_return(double(execute: project)) - post :create, { target_namespace: 'foo/bar', new_name: test_name, format: :json } + post :create, params: { target_namespace: 'foo/bar', new_name: test_name }, format: :json expect(Namespace.find_by_path_or_name('bar').parent.path).to eq('foo') end @@ -285,7 +285,7 @@ describe Import::BitbucketController do .to receive(:new).with(bitbucket_repo, test_name, kind_of(Namespace), user, access_params) .and_return(double(execute: project)) - post :create, { target_namespace: 'foo/foobar/bar', new_name: test_name, format: :json } + post :create, params: { target_namespace: 'foo/foobar/bar', new_name: test_name }, format: :json end it 'creates the namespaces' do @@ -293,7 +293,7 @@ describe Import::BitbucketController do .to receive(:new).with(bitbucket_repo, test_name, kind_of(Namespace), user, access_params) .and_return(double(execute: project)) - expect { post :create, { target_namespace: 'foo/foobar/bar', new_name: test_name, format: :json } } + expect { post :create, params: { target_namespace: 'foo/foobar/bar', new_name: test_name }, format: :json } .to change { Namespace.count }.by(2) end end @@ -302,7 +302,7 @@ describe Import::BitbucketController do it 'returns 422 response' do other_namespace = create(:group, name: 'other_namespace') - post :create, { target_namespace: other_namespace.name, format: :json } + post :create, params: { target_namespace: other_namespace.name }, format: :json expect(response).to have_gitlab_http_status(422) end diff --git a/spec/controllers/import/bitbucket_server_controller_spec.rb b/spec/controllers/import/bitbucket_server_controller_spec.rb index db912641894..73195463a50 100644 --- a/spec/controllers/import/bitbucket_server_controller_spec.rb +++ b/spec/controllers/import/bitbucket_server_controller_spec.rb @@ -42,19 +42,19 @@ describe Import::BitbucketServerController do .to receive(:new).with(project_key, repo_slug, anything, 'my-project', user.namespace, user, anything) .and_return(double(execute: project)) - post :create, project: project_key, repository: repo_slug, format: :json + post :create, params: { project: project_key, repository: repo_slug }, format: :json expect(response).to have_gitlab_http_status(200) end it 'returns an error when an invalid project key is used' do - post :create, project: 'some&project' + post :create, params: { project: 'some&project' } expect(response).to have_gitlab_http_status(422) end it 'returns an error when an invalid repository slug is used' do - post :create, project: 'some-project', repository: 'try*this' + post :create, params: { project: 'some-project', repository: 'try*this' } expect(response).to have_gitlab_http_status(422) end @@ -62,7 +62,7 @@ describe Import::BitbucketServerController do it 'returns an error when the project cannot be found' do allow(client).to receive(:repo).with(project_key, repo_slug).and_return(nil) - post :create, project: project_key, repository: repo_slug, format: :json + post :create, params: { project: project_key, repository: repo_slug }, format: :json expect(response).to have_gitlab_http_status(422) end @@ -72,7 +72,7 @@ describe Import::BitbucketServerController do .to receive(:new).with(project_key, repo_slug, anything, 'my-project', user.namespace, user, anything) .and_return(double(execute: build(:project))) - post :create, project: project_key, repository: repo_slug, format: :json + post :create, params: { project: project_key, repository: repo_slug }, format: :json expect(response).to have_gitlab_http_status(422) end @@ -80,7 +80,7 @@ describe Import::BitbucketServerController do it "returns an error when the server can't be contacted" do expect(client).to receive(:repo).with(project_key, repo_slug).and_raise(BitbucketServer::Client::ServerError) - post :create, project: project_key, repository: repo_slug, format: :json + post :create, params: { project: project_key, repository: repo_slug }, format: :json expect(response).to have_gitlab_http_status(422) end @@ -103,7 +103,7 @@ describe Import::BitbucketServerController do end it 'sets the session variables' do - post :configure, personal_access_token: token, bitbucket_username: username, bitbucket_server_url: url + post :configure, params: { personal_access_token: token, bitbucket_username: username, bitbucket_server_url: url } expect(session[:bitbucket_server_url]).to eq(url) expect(session[:bitbucket_server_username]).to eq(username) diff --git a/spec/controllers/import/gitlab_controller_spec.rb b/spec/controllers/import/gitlab_controller_spec.rb index 742f4787126..a874a7d36f6 100644 --- a/spec/controllers/import/gitlab_controller_spec.rb +++ b/spec/controllers/import/gitlab_controller_spec.rb @@ -209,7 +209,7 @@ describe Import::GitlabController do .to receive(:new).with(gitlab_repo, nested_namespace, user, access_params) .and_return(double(execute: project)) - post :create, { target_namespace: nested_namespace.full_path, format: :json } + post :create, params: { target_namespace: nested_namespace.full_path }, format: :json end end @@ -221,7 +221,7 @@ describe Import::GitlabController do .to receive(:new).with(gitlab_repo, kind_of(Namespace), user, access_params) .and_return(double(execute: project)) - post :create, { target_namespace: 'foo/bar', format: :json } + post :create, params: { target_namespace: 'foo/bar' }, format: :json end it 'creates the namespaces' do @@ -229,7 +229,7 @@ describe Import::GitlabController do .to receive(:new).with(gitlab_repo, kind_of(Namespace), user, access_params) .and_return(double(execute: project)) - expect { post :create, { target_namespace: 'foo/bar', format: :json } } + expect { post :create, params: { target_namespace: 'foo/bar' }, format: :json } .to change { Namespace.count }.by(2) end @@ -238,7 +238,7 @@ describe Import::GitlabController do .to receive(:new).with(gitlab_repo, kind_of(Namespace), user, access_params) .and_return(double(execute: project)) - post :create, { target_namespace: 'foo/bar', format: :json } + post :create, params: { target_namespace: 'foo/bar' }, format: :json expect(Namespace.find_by_path_or_name('bar').parent.path).to eq('foo') end @@ -257,7 +257,7 @@ describe Import::GitlabController do .to receive(:new).with(gitlab_repo, kind_of(Namespace), user, access_params) .and_return(double(execute: project)) - post :create, { target_namespace: 'foo/foobar/bar', format: :json } + post :create, params: { target_namespace: 'foo/foobar/bar' }, format: :json end it 'creates the namespaces' do @@ -265,7 +265,7 @@ describe Import::GitlabController do .to receive(:new).with(gitlab_repo, kind_of(Namespace), user, access_params) .and_return(double(execute: project)) - expect { post :create, { target_namespace: 'foo/foobar/bar', format: :json } } + expect { post :create, params: { target_namespace: 'foo/foobar/bar' }, format: :json } .to change { Namespace.count }.by(2) end end @@ -274,7 +274,7 @@ describe Import::GitlabController do it 'returns 422 response' do other_namespace = create(:group, name: 'other_namespace') - post :create, { target_namespace: other_namespace.name, format: :json } + post :create, params: { target_namespace: other_namespace.name }, format: :json expect(response).to have_gitlab_http_status(422) end diff --git a/spec/controllers/import/gitlab_projects_controller_spec.rb b/spec/controllers/import/gitlab_projects_controller_spec.rb index cbd1a112602..55bd8ae7182 100644 --- a/spec/controllers/import/gitlab_projects_controller_spec.rb +++ b/spec/controllers/import/gitlab_projects_controller_spec.rb @@ -12,14 +12,14 @@ describe Import::GitlabProjectsController do describe 'POST create' do context 'with an invalid path' do it 'redirects with an error' do - post :create, namespace_id: namespace.id, path: '/test', file: file + post :create, params: { namespace_id: namespace.id, path: '/test', file: file } expect(flash[:alert]).to start_with('Project could not be imported') expect(response).to have_gitlab_http_status(302) end it 'redirects with an error when a relative path is used' do - post :create, namespace_id: namespace.id, path: '../test', file: file + post :create, params: { namespace_id: namespace.id, path: '../test', file: file } expect(flash[:alert]).to start_with('Project could not be imported') expect(response).to have_gitlab_http_status(302) @@ -28,7 +28,7 @@ describe Import::GitlabProjectsController do context 'with a valid path' do it 'redirects to the new project path' do - post :create, namespace_id: namespace.id, path: 'test', file: file + post :create, params: { namespace_id: namespace.id, path: 'test', file: file } expect(flash[:notice]).to include('is being imported') expect(response).to have_gitlab_http_status(302) diff --git a/spec/controllers/import/google_code_controller_spec.rb b/spec/controllers/import/google_code_controller_spec.rb index 0763492d88a..3e5ed2afd93 100644 --- a/spec/controllers/import/google_code_controller_spec.rb +++ b/spec/controllers/import/google_code_controller_spec.rb @@ -12,7 +12,7 @@ describe Import::GoogleCodeController do describe "POST callback" do it "stores Google Takeout dump list in session" do - post :callback, dump_file: dump_file + post :callback, params: { dump_file: dump_file } expect(session[:google_code_dump]).to be_a(Hash) expect(session[:google_code_dump]["kind"]).to eq("projecthosting#user") diff --git a/spec/controllers/invites_controller_spec.rb b/spec/controllers/invites_controller_spec.rb index 6c09ca7dc66..7bbaf36e4df 100644 --- a/spec/controllers/invites_controller_spec.rb +++ b/spec/controllers/invites_controller_spec.rb @@ -12,7 +12,7 @@ describe InvitesController do describe 'GET #accept' do it 'accepts user' do - get :accept, id: token + get :accept, params: { id: token } member.reload expect(response).to have_gitlab_http_status(302) @@ -23,7 +23,7 @@ describe InvitesController do describe 'GET #decline' do it 'declines user' do - get :decline, id: token + get :decline, params: { id: token } expect {member.reload}.to raise_error ActiveRecord::RecordNotFound expect(response).to have_gitlab_http_status(302) diff --git a/spec/controllers/ldap/omniauth_callbacks_controller_spec.rb b/spec/controllers/ldap/omniauth_callbacks_controller_spec.rb index 87c10a86cdd..c9d36a16008 100644 --- a/spec/controllers/ldap/omniauth_callbacks_controller_spec.rb +++ b/spec/controllers/ldap/omniauth_callbacks_controller_spec.rb @@ -11,7 +11,7 @@ describe Ldap::OmniauthCallbacksController do it 'respects remember me checkbox' do expect do - post provider, remember_me: '1' + post provider, params: { remember_me: '1' } end.to change { user.reload.remember_created_at }.from(nil) end @@ -19,7 +19,7 @@ describe Ldap::OmniauthCallbacksController do let(:user) { create(:omniauth_user, :two_factor_via_otp, extern_uid: uid, provider: provider) } it 'passes remember_me to the Devise view' do - post provider, remember_me: '1' + post provider, params: { remember_me: '1' } expect(assigns[:user].remember_me).to eq '1' end diff --git a/spec/controllers/notification_settings_controller_spec.rb b/spec/controllers/notification_settings_controller_spec.rb index a3356a86d4b..cf52ce834b6 100644 --- a/spec/controllers/notification_settings_controller_spec.rb +++ b/spec/controllers/notification_settings_controller_spec.rb @@ -13,8 +13,10 @@ describe NotificationSettingsController do context 'when not authorized' do it 'redirects to sign in page' do post :create, - project_id: project.id, - notification_setting: { level: :participating } + params: { + project_id: project.id, + notification_setting: { level: :participating } + } expect(response).to redirect_to(new_user_session_path) end @@ -41,8 +43,10 @@ describe NotificationSettingsController do it 'creates notification setting' do post :create, - project_id: project.id, - notification_setting: { level: :participating } + params: { + project_id: project.id, + notification_setting: { level: :participating } + } expect(response.status).to eq 200 expect(notification_setting.level).to eq("participating") @@ -54,8 +58,10 @@ describe NotificationSettingsController do context 'with custom settings' do it 'creates notification setting' do post :create, - project_id: project.id, - notification_setting: { level: :custom }.merge(custom_events) + params: { + project_id: project.id, + notification_setting: { level: :custom }.merge(custom_events) + } expect(response.status).to eq 200 expect(notification_setting.level).to eq("custom") @@ -72,8 +78,10 @@ describe NotificationSettingsController do it 'creates notification setting' do post :create, - namespace_id: group.id, - notification_setting: { level: :watch } + params: { + namespace_id: group.id, + notification_setting: { level: :watch } + } expect(response.status).to eq 200 expect(notification_setting.level).to eq("watch") @@ -85,8 +93,10 @@ describe NotificationSettingsController do context 'with custom settings' do it 'creates notification setting' do post :create, - namespace_id: group.id, - notification_setting: { level: :custom }.merge(custom_events) + params: { + namespace_id: group.id, + notification_setting: { level: :custom }.merge(custom_events) + } expect(response.status).to eq 200 expect(notification_setting.level).to eq("custom") @@ -108,8 +118,10 @@ describe NotificationSettingsController do it 'returns 404' do post :create, - project_id: private_project.id, - notification_setting: { level: :participating } + params: { + project_id: private_project.id, + notification_setting: { level: :participating } + } expect(response).to have_gitlab_http_status(404) end @@ -122,8 +134,10 @@ describe NotificationSettingsController do context 'when not authorized' do it 'redirects to sign in page' do put :update, - id: notification_setting, - notification_setting: { level: :participating } + params: { + id: notification_setting, + notification_setting: { level: :participating } + } expect(response).to redirect_to(new_user_session_path) end @@ -136,8 +150,10 @@ describe NotificationSettingsController do it 'returns success' do put :update, - id: notification_setting, - notification_setting: { level: :participating } + params: { + id: notification_setting, + notification_setting: { level: :participating } + } expect(response.status).to eq 200 end @@ -153,8 +169,10 @@ describe NotificationSettingsController do it 'returns success' do put :update, - id: notification_setting, - notification_setting: { level: :participating, events: custom_events } + params: { + id: notification_setting, + notification_setting: { level: :participating, events: custom_events } + } expect(response.status).to eq 200 end @@ -170,8 +188,10 @@ describe NotificationSettingsController do it 'returns 404' do put :update, - id: notification_setting, - notification_setting: { level: :participating } + params: { + id: notification_setting, + notification_setting: { level: :participating } + } expect(response).to have_gitlab_http_status(404) end diff --git a/spec/controllers/oauth/applications_controller_spec.rb b/spec/controllers/oauth/applications_controller_spec.rb index b4219856fc0..caf2b87428b 100644 --- a/spec/controllers/oauth/applications_controller_spec.rb +++ b/spec/controllers/oauth/applications_controller_spec.rb @@ -26,7 +26,7 @@ describe Oauth::ApplicationsController do describe 'POST #create' do it 'creates an application' do - post :create, oauth_params + post :create, params: oauth_params expect(response).to have_gitlab_http_status(302) expect(response).to redirect_to(oauth_application_path(Doorkeeper::Application.last)) @@ -35,7 +35,7 @@ describe Oauth::ApplicationsController do it 'redirects back to profile page if OAuth applications are disabled' do disable_user_oauth - post :create, oauth_params + post :create, params: oauth_params expect(response).to have_gitlab_http_status(302) expect(response).to redirect_to(profile_path) @@ -52,7 +52,7 @@ describe Oauth::ApplicationsController do } } - post :create, invalid_uri_params + post :create, params: invalid_uri_params expect(response.body).to include 'Redirect URI is forbidden by the server' end diff --git a/spec/controllers/oauth/authorizations_controller_spec.rb b/spec/controllers/oauth/authorizations_controller_spec.rb index 8c10ea53a7a..cc8fa2c01b4 100644 --- a/spec/controllers/oauth/authorizations_controller_spec.rb +++ b/spec/controllers/oauth/authorizations_controller_spec.rb @@ -30,7 +30,7 @@ describe Oauth::AuthorizationsController do render_views it 'returns 200 code and renders view' do - get :new, params + get :new, params: params expect(response).to have_gitlab_http_status(200) expect(response).to render_template('doorkeeper/authorizations/new') @@ -40,7 +40,7 @@ describe Oauth::AuthorizationsController do application.update(trusted: true) request.session['user_return_to'] = 'http://example.com' - get :new, params + get :new, params: params expect(request.session['user_return_to']).to be_nil expect(response).to have_gitlab_http_status(302) @@ -57,7 +57,7 @@ describe Oauth::AuthorizationsController do end it 'authorizes the request and redirects' do - get :new, params + get :new, params: params expect(request.session['user_return_to']).to be_nil expect(response).to have_gitlab_http_status(302) diff --git a/spec/controllers/passwords_controller_spec.rb b/spec/controllers/passwords_controller_spec.rb index 4d31cfedbd2..0af55cf3408 100644 --- a/spec/controllers/passwords_controller_spec.rb +++ b/spec/controllers/passwords_controller_spec.rb @@ -22,7 +22,7 @@ describe PasswordsController do let(:user) { create(:omniauth_user, provider: 'ldapmain', email: 'ldapuser@gitlab.com') } it 'prevents a password reset' do - post :create, user: { email: user.email } + post :create, params: { user: { email: user.email } } expect(flash[:alert]).to eq 'Password authentication is unavailable.' end diff --git a/spec/controllers/profiles/accounts_controller_spec.rb b/spec/controllers/profiles/accounts_controller_spec.rb index f8d9d7e39ee..bb2ab27e2dd 100644 --- a/spec/controllers/profiles/accounts_controller_spec.rb +++ b/spec/controllers/profiles/accounts_controller_spec.rb @@ -9,7 +9,7 @@ describe Profiles::AccountsController do end it 'renders 404 if someone tries to unlink a non existent provider' do - delete :unlink, provider: 'github' + delete :unlink, params: { provider: 'github' } expect(response).to have_gitlab_http_status(404) end @@ -21,7 +21,7 @@ describe Profiles::AccountsController do it "does not allow to unlink connected account" do identity = user.identities.last - delete :unlink, provider: provider.to_s + delete :unlink, params: { provider: provider.to_s } expect(response).to have_gitlab_http_status(302) expect(user.reload.identities).to include(identity) @@ -36,7 +36,7 @@ describe Profiles::AccountsController do it 'allows to unlink connected account' do identity = user.identities.last - delete :unlink, provider: provider.to_s + delete :unlink, params: { provider: provider.to_s } expect(response).to have_gitlab_http_status(302) expect(user.reload.identities).not_to include(identity) diff --git a/spec/controllers/profiles/emails_controller_spec.rb b/spec/controllers/profiles/emails_controller_spec.rb index ecf14aad54f..a8a1f96befe 100644 --- a/spec/controllers/profiles/emails_controller_spec.rb +++ b/spec/controllers/profiles/emails_controller_spec.rb @@ -11,7 +11,7 @@ describe Profiles::EmailsController do let(:email_params) { { email: "add_email@example.com" } } it 'sends an email confirmation' do - expect { post(:create, { email: email_params }) }.to change { ActionMailer::Base.deliveries.size } + expect { post(:create, params: { email: email_params }) }.to change { ActionMailer::Base.deliveries.size } expect(ActionMailer::Base.deliveries.last.to).to eq [email_params[:email]] expect(ActionMailer::Base.deliveries.last.subject).to match "Confirmation instructions" end @@ -23,13 +23,13 @@ describe Profiles::EmailsController do it 'resends an email confirmation' do email = user.emails.create(email: 'add_email@example.com') - expect { put(:resend_confirmation_instructions, { id: email }) }.to change { ActionMailer::Base.deliveries.size } + expect { put(:resend_confirmation_instructions, params: { id: email }) }.to change { ActionMailer::Base.deliveries.size } expect(ActionMailer::Base.deliveries.last.to).to eq [email_params[:email]] expect(ActionMailer::Base.deliveries.last.subject).to match "Confirmation instructions" end it 'unable to resend an email confirmation' do - expect { put(:resend_confirmation_instructions, { id: 1 }) }.not_to change { ActionMailer::Base.deliveries.size } + expect { put(:resend_confirmation_instructions, params: { id: 1 }) }.not_to change { ActionMailer::Base.deliveries.size } end end end diff --git a/spec/controllers/profiles/keys_controller_spec.rb b/spec/controllers/profiles/keys_controller_spec.rb index 685db8488f0..5e2cc82bd8c 100644 --- a/spec/controllers/profiles/keys_controller_spec.rb +++ b/spec/controllers/profiles/keys_controller_spec.rb @@ -6,7 +6,7 @@ describe Profiles::KeysController do describe "#get_keys" do describe "non existent user" do it "does not generally work" do - get :get_keys, username: 'not-existent' + get :get_keys, params: { username: 'not-existent' } expect(response).not_to be_success end @@ -14,19 +14,19 @@ describe Profiles::KeysController do describe "user with no keys" do it "does generally work" do - get :get_keys, username: user.username + get :get_keys, params: { username: user.username } expect(response).to be_success end it "renders all keys separated with a new line" do - get :get_keys, username: user.username + get :get_keys, params: { username: user.username } expect(response.body).to eq("") end it "responds with text/plain content type" do - get :get_keys, username: user.username + get :get_keys, params: { username: user.username } expect(response.content_type).to eq("text/plain") end end @@ -37,13 +37,13 @@ describe Profiles::KeysController do let!(:deploy_key) { create(:deploy_key, user: user) } it "does generally work" do - get :get_keys, username: user.username + get :get_keys, params: { username: user.username } expect(response).to be_success end it "renders all non deploy keys separated with a new line" do - get :get_keys, username: user.username + get :get_keys, params: { username: user.username } expect(response.body).not_to eq('') expect(response.body).to eq(user.all_ssh_keys.join("\n")) @@ -55,22 +55,16 @@ describe Profiles::KeysController do end it "does not render the comment of the key" do - get :get_keys, username: user.username + get :get_keys, params: { username: user.username } expect(response.body).not_to match(/dummy@gitlab.com/) end it "responds with text/plain content type" do - get :get_keys, username: user.username + get :get_keys, params: { username: user.username } expect(response.content_type).to eq("text/plain") end - - it "responds with attachment content disposition" do - get :get_keys, username: user.username - - expect(response.headers['Content-Disposition']).to eq('attachment') - end end end end diff --git a/spec/controllers/profiles/notifications_controller_spec.rb b/spec/controllers/profiles/notifications_controller_spec.rb index b97cdd4d489..1b76446a0cf 100644 --- a/spec/controllers/profiles/notifications_controller_spec.rb +++ b/spec/controllers/profiles/notifications_controller_spec.rb @@ -24,7 +24,7 @@ describe Profiles::NotificationsController do it 'updates only permitted attributes' do sign_in(user) - put :update, user: { notification_email: 'new@example.com', notified_of_own_activity: true, admin: true } + put :update, params: { user: { notification_email: 'new@example.com', notified_of_own_activity: true, admin: true } } user.reload expect(user.notification_email).to eq('new@example.com') @@ -36,7 +36,7 @@ describe Profiles::NotificationsController do it 'shows an error message if the params are invalid' do sign_in(user) - put :update, user: { notification_email: '' } + put :update, params: { user: { notification_email: '' } } expect(user.reload.notification_email).to eq('original@example.com') expect(controller).to set_flash[:alert].to('Failed to save new settings') diff --git a/spec/controllers/profiles/personal_access_tokens_controller_spec.rb b/spec/controllers/profiles/personal_access_tokens_controller_spec.rb index f5860d4296b..021bf2429e3 100644 --- a/spec/controllers/profiles/personal_access_tokens_controller_spec.rb +++ b/spec/controllers/profiles/personal_access_tokens_controller_spec.rb @@ -17,7 +17,7 @@ describe Profiles::PersonalAccessTokensController do name = 'My PAT' scopes = %w[api read_user] - post :create, personal_access_token: token_attributes.merge(scopes: scopes, name: name) + post :create, params: { personal_access_token: token_attributes.merge(scopes: scopes, name: name) } expect(created_token).not_to be_nil expect(created_token.name).to eq(name) @@ -28,7 +28,7 @@ describe Profiles::PersonalAccessTokensController do it "allows creation of a token with an expiry date" do expires_at = 5.days.from_now.to_date - post :create, personal_access_token: token_attributes.merge(expires_at: expires_at) + post :create, params: { personal_access_token: token_attributes.merge(expires_at: expires_at) } expect(created_token).not_to be_nil expect(created_token.expires_at).to eq(expires_at) diff --git a/spec/controllers/profiles/preferences_controller_spec.rb b/spec/controllers/profiles/preferences_controller_spec.rb index a66b4ab0902..012f016b091 100644 --- a/spec/controllers/profiles/preferences_controller_spec.rb +++ b/spec/controllers/profiles/preferences_controller_spec.rb @@ -29,7 +29,7 @@ describe Profiles::PreferencesController do theme_id: '1' ) - patch :update, user: params, format: format + patch :update, params: { user: params }, format: format end context 'on successful update' do @@ -45,7 +45,7 @@ describe Profiles::PreferencesController do theme_id: '2' }.with_indifferent_access - expect(user).to receive(:assign_attributes).with(prefs) + expect(user).to receive(:assign_attributes).with(ActionController::Parameters.new(prefs).permit!) expect(user).to receive(:save) go params: prefs diff --git a/spec/controllers/profiles/two_factor_auths_controller_spec.rb b/spec/controllers/profiles/two_factor_auths_controller_spec.rb index d08d0018b35..0151a434998 100644 --- a/spec/controllers/profiles/two_factor_auths_controller_spec.rb +++ b/spec/controllers/profiles/two_factor_auths_controller_spec.rb @@ -32,7 +32,7 @@ describe Profiles::TwoFactorAuthsController do let(:pin) { 'pin-code' } def go - post :create, pin_code: pin + post :create, params: { pin_code: pin } end context 'with valid pin' do diff --git a/spec/controllers/profiles_controller_spec.rb b/spec/controllers/profiles_controller_spec.rb index 360c536c667..11cb59aa12a 100644 --- a/spec/controllers/profiles_controller_spec.rb +++ b/spec/controllers/profiles_controller_spec.rb @@ -9,7 +9,7 @@ describe ProfilesController, :request_store do expect do post :update, - user: { password: 'hello12345', password_confirmation: 'hello12345' } + params: { user: { password: 'hello12345', password_confirmation: 'hello12345' } } end.not_to change { user.reload.encrypted_password } expect(response.status).to eq(302) @@ -21,7 +21,7 @@ describe ProfilesController, :request_store do sign_in(user) put :update, - user: { email: "john@gmail.com", name: "John" } + params: { user: { email: "john@gmail.com", name: "John" } } user.reload @@ -35,7 +35,7 @@ describe ProfilesController, :request_store do sign_in(user) put :update, - user: { email: "john@gmail.com", name: "John" } + params: { user: { email: "john@gmail.com", name: "John" } } user.reload @@ -52,7 +52,7 @@ describe ProfilesController, :request_store do sign_in(ldap_user) put :update, - user: { email: "john@gmail.com", name: "John" } + params: { user: { email: "john@gmail.com", name: "John" } } ldap_user.reload @@ -69,7 +69,7 @@ describe ProfilesController, :request_store do sign_in(ldap_user) put :update, - user: { email: "john@gmail.com", name: "John", location: "City, Country" } + params: { user: { email: "john@gmail.com", name: "John", location: "City, Country" } } ldap_user.reload @@ -82,7 +82,7 @@ describe ProfilesController, :request_store do it 'allows setting a user status' do sign_in(user) - put :update, user: { status: { message: 'Working hard!' } } + put :update, params: { user: { status: { message: 'Working hard!' } } } expect(user.reload.status.message).to eq('Working hard!') expect(response).to have_gitlab_http_status(302) @@ -98,7 +98,7 @@ describe ProfilesController, :request_store do sign_in(user) put :update_username, - user: { username: new_username } + params: { user: { username: new_username } } user.reload @@ -110,7 +110,9 @@ describe ProfilesController, :request_store do sign_in(user) put :update_username, - user: { username: new_username }, + params: { + user: { username: new_username } + }, format: :json expect(response.status).to eq(200) @@ -121,7 +123,9 @@ describe ProfilesController, :request_store do sign_in(user) put :update_username, - user: { username: 'invalid username.git' }, + params: { + user: { username: 'invalid username.git' } + }, format: :json expect(response.status).to eq(422) @@ -131,7 +135,7 @@ describe ProfilesController, :request_store do it 'raises a correct error when the username is missing' do sign_in(user) - expect { put :update_username, user: { gandalf: 'you shall not pass' } } + expect { put :update_username, params: { user: { gandalf: 'you shall not pass' } } } .to raise_error(ActionController::ParameterMissing) end @@ -142,7 +146,7 @@ describe ProfilesController, :request_store do sign_in(user) put :update_username, - user: { username: new_username } + params: { user: { username: new_username } } user.reload @@ -160,7 +164,7 @@ describe ProfilesController, :request_store do sign_in(user) put :update_username, - user: { username: new_username } + params: { user: { username: new_username } } user.reload diff --git a/spec/controllers/projects/artifacts_controller_spec.rb b/spec/controllers/projects/artifacts_controller_spec.rb index b3c8d6a954e..bd10de45b67 100644 --- a/spec/controllers/projects/artifacts_controller_spec.rb +++ b/spec/controllers/projects/artifacts_controller_spec.rb @@ -22,7 +22,7 @@ describe Projects::ArtifactsController do def download_artifact(extra_params = {}) params = { namespace_id: project.namespace, project_id: project, job_id: job }.merge(extra_params) - get :download, params + get :download, params: params end context 'when no file type is supplied' do @@ -86,7 +86,7 @@ describe Projects::ArtifactsController do describe 'GET browse' do context 'when the directory exists' do it 'renders the browse view' do - get :browse, namespace_id: project.namespace, project_id: project, job_id: job, path: 'other_artifacts_0.1.2' + get :browse, params: { namespace_id: project.namespace, project_id: project, job_id: job, path: 'other_artifacts_0.1.2' } expect(response).to render_template('projects/artifacts/browse') end @@ -94,7 +94,7 @@ describe Projects::ArtifactsController do context 'when the directory does not exist' do it 'responds Not Found' do - get :browse, namespace_id: project.namespace, project_id: project, job_id: job, path: 'unknown' + get :browse, params: { namespace_id: project.namespace, project_id: project, job_id: job, path: 'unknown' } expect(response).to be_not_found end @@ -113,7 +113,7 @@ describe Projects::ArtifactsController do context 'when the file exists' do it 'renders the file view' do - get :file, namespace_id: project.namespace, project_id: project, job_id: job, path: 'ci_artifacts.txt' + get :file, params: { namespace_id: project.namespace, project_id: project, job_id: job, path: 'ci_artifacts.txt' } expect(response).to have_gitlab_http_status(302) end @@ -121,7 +121,7 @@ describe Projects::ArtifactsController do context 'when the file does not exist' do it 'responds Not Found' do - get :file, namespace_id: project.namespace, project_id: project, job_id: job, path: 'unknown' + get :file, params: { namespace_id: project.namespace, project_id: project, job_id: job, path: 'unknown' } expect(response).to be_not_found end @@ -131,7 +131,7 @@ describe Projects::ArtifactsController do context 'when the file is served through Rails' do context 'when the file exists' do it 'renders the file view' do - get :file, namespace_id: project.namespace, project_id: project, job_id: job, path: 'ci_artifacts.txt' + get :file, params: { namespace_id: project.namespace, project_id: project, job_id: job, path: 'ci_artifacts.txt' } expect(response).to have_gitlab_http_status(:ok) expect(response).to render_template('projects/artifacts/file') @@ -140,7 +140,7 @@ describe Projects::ArtifactsController do context 'when the file does not exist' do it 'responds Not Found' do - get :file, namespace_id: project.namespace, project_id: project, job_id: job, path: 'unknown' + get :file, params: { namespace_id: project.namespace, project_id: project, job_id: job, path: 'unknown' } expect(response).to be_not_found end @@ -159,7 +159,7 @@ describe Projects::ArtifactsController do end it 'does not redirect the request' do - get :file, namespace_id: private_project.namespace, project_id: private_project, job_id: job, path: 'ci_artifacts.txt' + get :file, params: { namespace_id: private_project.namespace, project_id: private_project, job_id: job, path: 'ci_artifacts.txt' } expect(response).to have_gitlab_http_status(:ok) expect(response).to render_template('projects/artifacts/file') @@ -168,7 +168,7 @@ describe Projects::ArtifactsController do end describe 'GET raw' do - subject { get(:raw, namespace_id: project.namespace, project_id: project, job_id: job, path: path) } + subject { get(:raw, params: { namespace_id: project.namespace, project_id: project, job_id: job, path: path }) } context 'when the file exists' do let(:path) { 'ci_artifacts.txt' } @@ -239,7 +239,7 @@ describe Projects::ArtifactsController do context 'has no such ref' do before do - get :latest_succeeded, params_from_ref('TAIL', job.name) + get :latest_succeeded, params: params_from_ref('TAIL', job.name) end it_behaves_like 'not found' @@ -247,7 +247,7 @@ describe Projects::ArtifactsController do context 'has no such job' do before do - get :latest_succeeded, params_from_ref(pipeline.ref, 'NOBUILD') + get :latest_succeeded, params: params_from_ref(pipeline.ref, 'NOBUILD') end it_behaves_like 'not found' @@ -255,7 +255,7 @@ describe Projects::ArtifactsController do context 'has no path' do before do - get :latest_succeeded, params_from_ref(pipeline.sha, job.name, '') + get :latest_succeeded, params: params_from_ref(pipeline.sha, job.name, '') end it_behaves_like 'not found' @@ -276,7 +276,7 @@ describe Projects::ArtifactsController do pipeline.update(ref: 'master', sha: project.commit('master').sha) - get :latest_succeeded, params_from_ref('master') + get :latest_succeeded, params: params_from_ref('master') end it_behaves_like 'redirect to the job' @@ -287,7 +287,7 @@ describe Projects::ArtifactsController do pipeline.update(ref: 'improve/awesome', sha: project.commit('improve/awesome').sha) - get :latest_succeeded, params_from_ref('improve/awesome') + get :latest_succeeded, params: params_from_ref('improve/awesome') end it_behaves_like 'redirect to the job' @@ -298,7 +298,7 @@ describe Projects::ArtifactsController do pipeline.update(ref: 'improve/awesome', sha: project.commit('improve/awesome').sha) - get :latest_succeeded, params_from_ref('improve/awesome', job.name, 'file/README.md') + get :latest_succeeded, params: params_from_ref('improve/awesome', job.name, 'file/README.md') end it 'redirects' do diff --git a/spec/controllers/projects/avatars_controller_spec.rb b/spec/controllers/projects/avatars_controller_spec.rb index 5a77a7ac06f..95b7ae5885a 100644 --- a/spec/controllers/projects/avatars_controller_spec.rb +++ b/spec/controllers/projects/avatars_controller_spec.rb @@ -8,7 +8,7 @@ describe Projects::AvatarsController do end describe 'GET #show' do - subject { get :show, namespace_id: project.namespace, project_id: project } + subject { get :show, params: { namespace_id: project.namespace, project_id: project } } context 'when repository has no avatar' do it 'shows 404' do @@ -26,37 +26,13 @@ describe Projects::AvatarsController do context 'when the avatar is stored in the repository' do let(:filepath) { 'files/images/logo-white.png' } - context 'when feature flag workhorse_set_content_type is' do - before do - stub_feature_flags(workhorse_set_content_type: flag_value) - end + it 'sends the avatar' do + subject - context 'enabled' do - let(:flag_value) { true } - - it 'sends the avatar' do - subject - - expect(response).to have_gitlab_http_status(200) - expect(response.header['Content-Disposition']).to eq('inline') - expect(response.header['Content-Type']).to eq 'image/png' - expect(response.header[Gitlab::Workhorse::SEND_DATA_HEADER]).to start_with('git-blob:') - expect(response.header[Gitlab::Workhorse::DETECT_HEADER]).to eq "true" - end - end - - context 'disabled' do - let(:flag_value) { false } - - it 'sends the avatar' do - subject - - expect(response).to have_gitlab_http_status(200) - expect(response.header['Content-Type']).to eq('image/png') - expect(response.header[Gitlab::Workhorse::SEND_DATA_HEADER]).to start_with('git-blob:') - expect(response.header[Gitlab::Workhorse::DETECT_HEADER]).to eq nil - end - end + expect(response).to have_gitlab_http_status(200) + expect(response.header['Content-Disposition']).to eq('inline') + expect(response.header[Gitlab::Workhorse::SEND_DATA_HEADER]).to start_with('git-blob:') + expect(response.header[Gitlab::Workhorse::DETECT_HEADER]).to eq "true" end end @@ -71,7 +47,7 @@ describe Projects::AvatarsController do describe 'DELETE #destroy' do it 'removes avatar from DB by calling destroy' do - delete :destroy, namespace_id: project.namespace.id, project_id: project.id + delete :destroy, params: { namespace_id: project.namespace.id, project_id: project.id } expect(project.avatar.present?).to be_falsey expect(project).to be_valid diff --git a/spec/controllers/projects/badges_controller_spec.rb b/spec/controllers/projects/badges_controller_spec.rb index dfe34171b55..2556bc3ae50 100644 --- a/spec/controllers/projects/badges_controller_spec.rb +++ b/spec/controllers/projects/badges_controller_spec.rb @@ -23,6 +23,6 @@ describe Projects::BadgesController do end def get_badge(badge) - get badge, namespace_id: project.namespace.to_param, project_id: project, ref: pipeline.ref, format: :svg + get badge, params: { namespace_id: project.namespace.to_param, project_id: project, ref: pipeline.ref }, format: :svg end end diff --git a/spec/controllers/projects/blame_controller_spec.rb b/spec/controllers/projects/blame_controller_spec.rb index fe4c4863717..eb110ea0002 100644 --- a/spec/controllers/projects/blame_controller_spec.rb +++ b/spec/controllers/projects/blame_controller_spec.rb @@ -16,9 +16,11 @@ describe Projects::BlameController do before do get(:show, - namespace_id: project.namespace, - project_id: project, - id: id) + params: { + namespace_id: project.namespace, + project_id: project, + id: id + }) end context "valid file" do diff --git a/spec/controllers/projects/blob_controller_spec.rb b/spec/controllers/projects/blob_controller_spec.rb index 9fc6af6a045..38957e96798 100644 --- a/spec/controllers/projects/blob_controller_spec.rb +++ b/spec/controllers/projects/blob_controller_spec.rb @@ -11,9 +11,11 @@ describe Projects::BlobController do context 'with file path' do before do get(:show, - namespace_id: project.namespace, - project_id: project, - id: id) + params: { + namespace_id: project.namespace, + project_id: project, + id: id + }) end context "valid branch, valid file" do @@ -48,9 +50,11 @@ describe Projects::BlobController do before do get(:show, - namespace_id: project.namespace, - project_id: project, - id: id, + params: { + namespace_id: project.namespace, + project_id: project, + id: id + }, format: :json) end @@ -66,11 +70,13 @@ describe Projects::BlobController do before do get(:show, - namespace_id: project.namespace, - project_id: project, - id: id, - format: :json, - viewer: 'none') + params: { + namespace_id: project.namespace, + project_id: project, + id: id, + viewer: 'none' + }, + format: :json) end it do @@ -84,9 +90,11 @@ describe Projects::BlobController do context 'with tree path' do before do get(:show, - namespace_id: project.namespace, - project_id: project, - id: id) + params: { + namespace_id: project.namespace, + project_id: project, + id: id + }) controller.instance_variable_set(:@blob, nil) end @@ -109,7 +117,7 @@ describe Projects::BlobController do params = { namespace_id: project.namespace, project_id: project, id: 'master/CHANGELOG' } - get :diff, params.merge(opts) + get :diff, params: params.merge(opts) end before do @@ -200,7 +208,7 @@ describe Projects::BlobController do context 'anonymous' do before do - get :edit, default_params + get :edit, params: default_params end it 'redirects to sign in and returns' do @@ -213,7 +221,7 @@ describe Projects::BlobController do before do sign_in(guest) - get :edit, default_params + get :edit, params: default_params end it 'redirects to blob show' do @@ -227,7 +235,7 @@ describe Projects::BlobController do before do project.add_developer(developer) sign_in(developer) - get :edit, default_params + get :edit, params: default_params end it 'redirects to blob show' do @@ -241,7 +249,7 @@ describe Projects::BlobController do before do project.add_maintainer(maintainer) sign_in(maintainer) - get :edit, default_params + get :edit, params: default_params end it 'redirects to blob show' do @@ -274,7 +282,7 @@ describe Projects::BlobController do end it 'redirects to blob' do - put :update, default_params + put :update, params: default_params expect(response).to redirect_to(blob_after_edit_path) end @@ -284,7 +292,7 @@ describe Projects::BlobController do let(:mr_params) { default_params.merge(from_merge_request_iid: merge_request.iid) } it 'redirects to MR diff' do - put :update, mr_params + put :update, params: mr_params after_edit_path = diffs_project_merge_request_path(project, merge_request) file_anchor = "##{Digest::SHA1.hexdigest('CHANGELOG')}" @@ -298,7 +306,7 @@ describe Projects::BlobController do end it "it redirect to blob" do - put :update, mr_params + put :update, params: mr_params expect(response).to redirect_to(blob_after_edit_path) end @@ -320,7 +328,7 @@ describe Projects::BlobController do end it 'redirects to blob' do - put :update, default_params + put :update, params: default_params expect(response).to redirect_to(project_blob_path(forked_project, 'master/CHANGELOG')) end @@ -331,7 +339,7 @@ describe Projects::BlobController do default_params[:branch_name] = "fork-test-1" default_params[:create_merge_request] = 1 - put :update, default_params + put :update, params: default_params expect(response).to redirect_to( project_new_merge_request_path( @@ -374,7 +382,7 @@ describe Projects::BlobController do let(:after_delete_path) { project_tree_path(project, 'master/files') } it 'redirects to the sub directory' do - delete :destroy, default_params + delete :destroy, params: default_params expect(response).to redirect_to(after_delete_path) end @@ -393,7 +401,7 @@ describe Projects::BlobController do end it 'redirects to the project root' do - delete :destroy, default_params + delete :destroy, params: default_params expect(response).to redirect_to(project_root_path) end @@ -413,7 +421,7 @@ describe Projects::BlobController do let(:after_delete_path) { project_tree_path(project, 'binary-encoding') } it 'redirects to the project root of the branch' do - delete :destroy, default_params + delete :destroy, params: default_params expect(response).to redirect_to(after_delete_path) end diff --git a/spec/controllers/projects/boards_controller_spec.rb b/spec/controllers/projects/boards_controller_spec.rb index 8d503f6ad32..09199067024 100644 --- a/spec/controllers/projects/boards_controller_spec.rb +++ b/spec/controllers/projects/boards_controller_spec.rb @@ -121,8 +121,10 @@ describe Projects::BoardsController do end def list_boards(format: :html) - get :index, namespace_id: project.namespace, - project_id: project, + get :index, params: { + namespace_id: project.namespace, + project_id: project + }, format: format end end @@ -207,9 +209,11 @@ describe Projects::BoardsController do end def read_board(board:, format: :html) - get :show, namespace_id: project.namespace, - project_id: project, - id: board.to_param, + get :show, params: { + namespace_id: project.namespace, + project_id: project, + id: board.to_param + }, format: format end end diff --git a/spec/controllers/projects/branches_controller_spec.rb b/spec/controllers/projects/branches_controller_spec.rb index 31471cde420..02b3d5269a6 100644 --- a/spec/controllers/projects/branches_controller_spec.rb +++ b/spec/controllers/projects/branches_controller_spec.rb @@ -22,10 +22,12 @@ describe Projects::BranchesController do sign_in(user) post :create, - namespace_id: project.namespace, - project_id: project, - branch_name: branch, - ref: ref + params: { + namespace_id: project.namespace, + project_id: project, + branch_name: branch, + ref: ref + } end context "valid branch name, valid source" do @@ -76,10 +78,12 @@ describe Projects::BranchesController do it 'redirects' do post :create, - namespace_id: project.namespace, - project_id: project, - branch_name: branch, - issue_iid: issue.iid + params: { + namespace_id: project.namespace, + project_id: project, + branch_name: branch, + issue_iid: issue.iid + } expect(subject) .to redirect_to("/#{project.full_path}/tree/1-feature-branch") @@ -89,10 +93,12 @@ describe Projects::BranchesController do expect(SystemNoteService).to receive(:new_issue_branch).with(issue, project, user, "1-feature-branch") post :create, - namespace_id: project.namespace, - project_id: project, - branch_name: branch, - issue_iid: issue.iid + params: { + namespace_id: project.namespace, + project_id: project, + branch_name: branch, + issue_iid: issue.iid + } end context 'repository-less project' do @@ -105,10 +111,12 @@ describe Projects::BranchesController do expect(SystemNoteService).to receive(:new_issue_branch).and_return(true) post :create, - namespace_id: project.namespace.to_param, - project_id: project.to_param, - branch_name: branch, - issue_iid: issue.iid + params: { + namespace_id: project.namespace.to_param, + project_id: project.to_param, + branch_name: branch, + issue_iid: issue.iid + } expect(response).to redirect_to project_tree_path(project, branch) end @@ -121,10 +129,12 @@ describe Projects::BranchesController do expect(SystemNoteService).to receive(:new_issue_branch).and_return(true) post :create, - namespace_id: project.namespace.to_param, - project_id: project.to_param, - branch_name: branch, - issue_iid: issue.iid + params: { + namespace_id: project.namespace.to_param, + project_id: project.to_param, + branch_name: branch, + issue_iid: issue.iid + } expect(response.location).to include(project_new_blob_path(project, branch)) expect(response).to have_gitlab_http_status(302) @@ -156,10 +166,12 @@ describe Projects::BranchesController do expect(SystemNoteService).to receive(:new_issue_branch).and_return(true) post :create, - namespace_id: project.namespace.to_param, - project_id: project.to_param, - branch_name: branch, - issue_iid: issue.iid + params: { + namespace_id: project.namespace.to_param, + project_id: project.to_param, + branch_name: branch, + issue_iid: issue.iid + } expect(response.location).to include(project_new_blob_path(project, branch)) expect(response).to have_gitlab_http_status(302) @@ -173,10 +185,12 @@ describe Projects::BranchesController 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 + params: { + namespace_id: project.namespace, + project_id: project, + branch_name: branch, + issue_iid: issue.iid + } end end @@ -191,10 +205,12 @@ describe Projects::BranchesController 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 + params: { + namespace_id: project.namespace, + project_id: project, + branch_name: branch, + issue_iid: issue.iid + } end end end @@ -228,11 +244,14 @@ describe Projects::BranchesController do end def create_branch(name:, ref:) - post :create, namespace_id: project.namespace.to_param, - project_id: project.to_param, - branch_name: name, - ref: ref, - format: :json + post :create, + format: :json, + params: { + namespace_id: project.namespace.to_param, + project_id: project.to_param, + branch_name: name, + ref: ref + } end end @@ -246,9 +265,11 @@ describe Projects::BranchesController do it 'returns 303' do post :destroy, format: :html, - id: 'foo/bar/baz', - namespace_id: project.namespace, - project_id: project + params: { + id: 'foo/bar/baz', + namespace_id: project.namespace, + project_id: project + } expect(response).to have_gitlab_http_status(303) end @@ -261,10 +282,12 @@ describe Projects::BranchesController do sign_in(user) post :destroy, - format: format, - id: branch, - namespace_id: project.namespace, - project_id: project + format: format, + params: { + id: branch, + namespace_id: project.namespace, + project_id: project + } end context 'as JS' do @@ -359,8 +382,10 @@ describe Projects::BranchesController do describe "DELETE destroy_all_merged" do def destroy_all_merged delete :destroy_all_merged, - namespace_id: project.namespace, - project_id: project + params: { + namespace_id: project.namespace, + project_id: project + } end context 'when user is allowed to push' do @@ -404,10 +429,12 @@ describe Projects::BranchesController do context 'when rendering a JSON format' do it 'filters branches by name' do get :index, - namespace_id: project.namespace, - project_id: project, format: :json, - search: 'master' + params: { + namespace_id: project.namespace, + project_id: project, + search: 'master' + } parsed_response = JSON.parse(response.body) @@ -423,10 +450,12 @@ describe Projects::BranchesController do context 'when cache is enabled yet cold', :request_store do it 'return with a status 200' do get :index, - namespace_id: project.namespace, - project_id: project, - state: 'all', - format: :html + format: :html, + params: { + namespace_id: project.namespace, + project_id: project, + state: 'all' + } expect(response).to have_gitlab_http_status(200) end @@ -439,10 +468,12 @@ describe Projects::BranchesController do it 'return with a status 200' do get :index, - namespace_id: project.namespace, - project_id: project, - state: 'all', - format: :html + format: :html, + params: { + namespace_id: project.namespace, + project_id: project, + state: 'all' + } expect(response).to have_gitlab_http_status(200) end @@ -451,30 +482,36 @@ describe Projects::BranchesController do context 'when deprecated sort/search/page parameters are specified' do it 'returns with a status 301 when sort specified' do get :index, - namespace_id: project.namespace, - project_id: project, - sort: 'updated_asc', - format: :html + format: :html, + params: { + namespace_id: project.namespace, + project_id: project, + sort: 'updated_asc' + } expect(response).to redirect_to project_branches_filtered_path(project, state: 'all') end it 'returns with a status 301 when search specified' do get :index, - namespace_id: project.namespace, - project_id: project, - search: 'feature', - format: :html + format: :html, + params: { + namespace_id: project.namespace, + project_id: project, + search: 'feature' + } expect(response).to redirect_to project_branches_filtered_path(project, state: 'all') end it 'returns with a status 301 when page specified' do get :index, - namespace_id: project.namespace, - project_id: project, - page: 2, - format: :html + format: :html, + params: { + namespace_id: project.namespace, + project_id: project, + page: 2 + } expect(response).to redirect_to project_branches_filtered_path(project, state: 'all') end diff --git a/spec/controllers/projects/ci/lints_controller_spec.rb b/spec/controllers/projects/ci/lints_controller_spec.rb index 1249a5528a9..cfa010c2d1c 100644 --- a/spec/controllers/projects/ci/lints_controller_spec.rb +++ b/spec/controllers/projects/ci/lints_controller_spec.rb @@ -13,7 +13,7 @@ describe Projects::Ci::LintsController do before do project.add_developer(user) - get :show, namespace_id: project.namespace, project_id: project + get :show, params: { namespace_id: project.namespace, project_id: project } end it 'should be success' do @@ -33,7 +33,7 @@ describe Projects::Ci::LintsController do before do project.add_guest(user) - get :show, namespace_id: project.namespace, project_id: project + get :show, params: { namespace_id: project.namespace, project_id: project } end it 'should respond with 404' do @@ -51,7 +51,6 @@ describe Projects::Ci::LintsController do - apt-get update -qq && apt-get install -y -qq sqlite3 libsqlite3-dev nodejs - ruby -v - which ruby - - gem install bundler --no-ri --no-rdoc - bundle install --jobs $(nproc) "${FLAGS[@]}" HEREDOC end @@ -72,7 +71,7 @@ describe Projects::Ci::LintsController do WebMock.stub_request(:get, remote_file_path).to_return(body: remote_file_content) project.add_developer(user) - post :create, namespace_id: project.namespace, project_id: project, content: content + post :create, params: { namespace_id: project.namespace, project_id: project, content: content } end it 'should be success' do @@ -100,7 +99,7 @@ describe Projects::Ci::LintsController do before do project.add_developer(user) - post :create, namespace_id: project.namespace, project_id: project, content: content + post :create, params: { namespace_id: project.namespace, project_id: project, content: content } end it 'should assign errors' do @@ -112,7 +111,7 @@ describe Projects::Ci::LintsController do before do project.add_guest(user) - post :create, namespace_id: project.namespace, project_id: project, content: content + post :create, params: { namespace_id: project.namespace, project_id: project, content: content } end it 'should respond with 404' do diff --git a/spec/controllers/projects/clusters/applications_controller_spec.rb b/spec/controllers/projects/clusters/applications_controller_spec.rb index 8106453a775..cb558259225 100644 --- a/spec/controllers/projects/clusters/applications_controller_spec.rb +++ b/spec/controllers/projects/clusters/applications_controller_spec.rb @@ -81,7 +81,7 @@ describe Projects::Clusters::ApplicationsController do end def go - post :create, params.merge(namespace_id: project.namespace, project_id: project) + post :create, params: params.merge(namespace_id: project.namespace, project_id: project) end end end diff --git a/spec/controllers/projects/clusters_controller_spec.rb b/spec/controllers/projects/clusters_controller_spec.rb index 483222363bb..d94c18ddc02 100644 --- a/spec/controllers/projects/clusters_controller_spec.rb +++ b/spec/controllers/projects/clusters_controller_spec.rb @@ -18,7 +18,7 @@ describe Projects::ClustersController do describe 'GET index' do def go(params = {}) - get :index, params.reverse_merge(namespace_id: project.namespace.to_param, project_id: project) + get :index, params: params.reverse_merge(namespace_id: project.namespace.to_param, project_id: project) end describe 'functionality' do @@ -80,7 +80,7 @@ describe Projects::ClustersController do describe 'GET new' do def go - get :new, namespace_id: project.namespace, project_id: project + get :new, params: { namespace_id: project.namespace, project_id: project } end describe 'functionality for new cluster' do @@ -174,7 +174,7 @@ describe Projects::ClustersController do end def go - post :create_gcp, params.merge(namespace_id: project.namespace, project_id: project) + post :create_gcp, params: params.merge(namespace_id: project.namespace, project_id: project) end describe 'functionality' do @@ -261,7 +261,7 @@ describe Projects::ClustersController do end def go - post :create_user, params.merge(namespace_id: project.namespace, project_id: project) + post :create_user, params: params.merge(namespace_id: project.namespace, project_id: project) end describe 'functionality' do @@ -311,7 +311,7 @@ describe Projects::ClustersController do describe 'security' do before do - allow(ClusterPlatformConfigureWorker).to receive(:perform_async) + allow(ClusterConfigureWorker).to receive(:perform_async) stub_kubeclient_get_namespace('https://kubernetes.example.com', namespace: 'my-namespace') end @@ -331,9 +331,11 @@ describe Projects::ClustersController do def go get :cluster_status, - namespace_id: project.namespace.to_param, - project_id: project.to_param, - id: cluster, + params: { + namespace_id: project.namespace.to_param, + project_id: project.to_param, + id: cluster + }, format: :json end @@ -369,9 +371,11 @@ describe Projects::ClustersController do def go get :show, - namespace_id: project.namespace, - project_id: project, - id: cluster + params: { + namespace_id: project.namespace, + project_id: project, + id: cluster + } end describe 'functionality' do @@ -397,15 +401,15 @@ describe Projects::ClustersController do describe 'PUT update' do def go(format: :html) - put :update, params.merge(namespace_id: project.namespace.to_param, - project_id: project.to_param, - id: cluster, - format: format - ) + put :update, params: params.merge(namespace_id: project.namespace.to_param, + project_id: project.to_param, + id: cluster, + format: format + ) end before do - allow(ClusterPlatformConfigureWorker).to receive(:perform_async) + allow(ClusterConfigureWorker).to receive(:perform_async) stub_kubeclient_get_namespace('https://kubernetes.example.com', namespace: 'my-namespace') end @@ -500,9 +504,11 @@ describe Projects::ClustersController do def go delete :destroy, - namespace_id: project.namespace, - project_id: project, - id: cluster + params: { + namespace_id: project.namespace, + project_id: project, + id: cluster + } end describe 'functionality' do diff --git a/spec/controllers/projects/commit_controller_spec.rb b/spec/controllers/projects/commit_controller_spec.rb index e34fdee62d6..26eec90da06 100644 --- a/spec/controllers/projects/commit_controller_spec.rb +++ b/spec/controllers/projects/commit_controller_spec.rb @@ -21,7 +21,7 @@ describe Projects::CommitController do project_id: project } - get :show, params.merge(extra_params) + get :show, params: params.merge(extra_params) end context 'with valid id' do @@ -102,9 +102,11 @@ describe Projects::CommitController do it 'renders it' do get(:show, - namespace_id: fork_project.namespace, - project_id: fork_project, - id: commit.id) + params: { + namespace_id: fork_project.namespace, + project_id: fork_project, + id: commit.id + }) expect(response).to be_success end @@ -132,9 +134,11 @@ describe Projects::CommitController do commit = project.commit('5937ac0a7beb003549fc5fd26fc247adbce4a52e') get(:branches, - namespace_id: project.namespace, - project_id: project, - id: commit.id) + params: { + namespace_id: project.namespace, + project_id: project, + id: commit.id + }) expect(assigns(:branches)).to include('master', 'feature_conflict') expect(assigns(:branches_limit_exceeded)).to be_falsey @@ -148,9 +152,11 @@ describe Projects::CommitController do allow_any_instance_of(Repository).to receive(:tag_count).and_return(1001) get(:branches, - namespace_id: project.namespace, - project_id: project, - id: commit.id) + params: { + namespace_id: project.namespace, + project_id: project, + id: commit.id + }) expect(assigns(:branches)).to eq([]) expect(assigns(:branches_limit_exceeded)).to be_truthy @@ -163,9 +169,11 @@ describe Projects::CommitController do context 'when target branch is not provided' do it 'renders the 404 page' do post(:revert, - namespace_id: project.namespace, - project_id: project, - id: commit.id) + params: { + namespace_id: project.namespace, + project_id: project, + id: commit.id + }) expect(response).not_to be_success expect(response).to have_gitlab_http_status(404) @@ -175,10 +183,12 @@ describe Projects::CommitController do context 'when the revert was successful' do it 'redirects to the commits page' do post(:revert, - namespace_id: project.namespace, - project_id: project, - start_branch: 'master', - id: commit.id) + params: { + namespace_id: project.namespace, + project_id: project, + start_branch: 'master', + id: commit.id + }) expect(response).to redirect_to project_commits_path(project, 'master') expect(flash[:notice]).to eq('The commit has been successfully reverted.') @@ -188,19 +198,23 @@ describe Projects::CommitController do context 'when the revert failed' do before do post(:revert, - namespace_id: project.namespace, - project_id: project, - start_branch: 'master', - id: commit.id) + params: { + namespace_id: project.namespace, + project_id: project, + start_branch: 'master', + id: commit.id + }) end it 'redirects to the commit page' do # Reverting a commit that has been already reverted. post(:revert, - namespace_id: project.namespace, - project_id: project, - start_branch: 'master', - id: commit.id) + params: { + namespace_id: project.namespace, + project_id: project, + start_branch: 'master', + id: commit.id + }) expect(response).to redirect_to project_commit_path(project, commit.id) expect(flash[:alert]).to match('Sorry, we cannot revert this commit automatically.') @@ -212,9 +226,11 @@ describe Projects::CommitController do context 'when target branch is not provided' do it 'renders the 404 page' do post(:cherry_pick, - namespace_id: project.namespace, - project_id: project, - id: master_pickable_commit.id) + params: { + namespace_id: project.namespace, + project_id: project, + id: master_pickable_commit.id + }) expect(response).not_to be_success expect(response).to have_gitlab_http_status(404) @@ -224,10 +240,12 @@ describe Projects::CommitController do context 'when the cherry-pick was successful' do it 'redirects to the commits page' do post(:cherry_pick, - namespace_id: project.namespace, - project_id: project, - start_branch: 'master', - id: master_pickable_commit.id) + params: { + namespace_id: project.namespace, + project_id: project, + start_branch: 'master', + id: master_pickable_commit.id + }) expect(response).to redirect_to project_commits_path(project, 'master') expect(flash[:notice]).to eq('The commit has been successfully cherry-picked into master.') @@ -237,19 +255,23 @@ describe Projects::CommitController do context 'when the cherry_pick failed' do before do post(:cherry_pick, - namespace_id: project.namespace, - project_id: project, - start_branch: 'master', - id: master_pickable_commit.id) + params: { + namespace_id: project.namespace, + project_id: project, + start_branch: 'master', + id: master_pickable_commit.id + }) end it 'redirects to the commit page' do # Cherry-picking a commit that has been already cherry-picked. post(:cherry_pick, - namespace_id: project.namespace, - project_id: project, - start_branch: 'master', - id: master_pickable_commit.id) + params: { + namespace_id: project.namespace, + project_id: project, + start_branch: 'master', + id: master_pickable_commit.id + }) expect(response).to redirect_to project_commit_path(project, master_pickable_commit.id) expect(flash[:alert]).to match('Sorry, we cannot cherry-pick this commit automatically.') @@ -264,7 +286,7 @@ describe Projects::CommitController do project_id: project } - get :diff_for_path, params.merge(extra_params) + get :diff_for_path, params: params.merge(extra_params) end let(:existing_path) { '.gitmodules' } @@ -332,7 +354,7 @@ describe Projects::CommitController do project_id: project } - get :pipelines, params.merge(extra_params) + get :pipelines, params: params.merge(extra_params) end context 'when the commit exists' do diff --git a/spec/controllers/projects/commits_controller_spec.rb b/spec/controllers/projects/commits_controller_spec.rb index 80513650636..8cb9130b834 100644 --- a/spec/controllers/projects/commits_controller_spec.rb +++ b/spec/controllers/projects/commits_controller_spec.rb @@ -17,8 +17,10 @@ describe Projects::CommitsController do context "no ref is provided" do it 'should redirect to the default branch of the project' do get(:commits_root, - namespace_id: project.namespace, - project_id: project) + params: { + namespace_id: project.namespace, + project_id: project + }) expect(response).to redirect_to project_commits_path(project) end @@ -31,9 +33,11 @@ describe Projects::CommitsController do context 'with file path' do before do get(:show, - namespace_id: project.namespace, - project_id: project, - id: id) + params: { + namespace_id: project.namespace, + project_id: project, + id: id + }) end context "valid branch, valid file" do @@ -65,9 +69,11 @@ describe Projects::CommitsController do context "when the ref does not exist with the suffix" do before do get(:show, - namespace_id: project.namespace, - project_id: project, - id: "master.atom") + params: { + namespace_id: project.namespace, + project_id: project, + id: "master.atom" + }) end it "renders as atom" do @@ -88,9 +94,11 @@ describe Projects::CommitsController do allow_any_instance_of(Repository).to receive(:commit).with('master.atom').and_return(commit) get(:show, - namespace_id: project.namespace, - project_id: project, - id: "master.atom") + params: { + namespace_id: project.namespace, + project_id: project, + id: "master.atom" + }) end it "renders as HTML" do @@ -106,9 +114,11 @@ describe Projects::CommitsController do before do get(:signatures, - namespace_id: project.namespace, - project_id: project, - id: id, + params: { + namespace_id: project.namespace, + project_id: project, + id: id + }, format: :json) end diff --git a/spec/controllers/projects/compare_controller_spec.rb b/spec/controllers/projects/compare_controller_spec.rb index 17883d0fadd..cfd70e93efb 100644 --- a/spec/controllers/projects/compare_controller_spec.rb +++ b/spec/controllers/projects/compare_controller_spec.rb @@ -13,7 +13,7 @@ describe Projects::CompareController do render_views before do - get :index, namespace_id: project.namespace, project_id: project + get :index, params: { namespace_id: project.namespace, project_id: project } end it 'returns successfully' do @@ -24,7 +24,7 @@ describe Projects::CompareController do describe 'GET show' do render_views - subject(:show_request) { get :show, request_params } + subject(:show_request) { get :show, params: request_params } let(:request_params) do { @@ -130,7 +130,7 @@ describe Projects::CompareController do project_id: project } - get :diff_for_path, params.merge(extra_params) + get :diff_for_path, params: params.merge(extra_params) end let(:existing_path) { 'files/ruby/feature.rb' } @@ -201,7 +201,7 @@ describe Projects::CompareController do end describe 'POST create' do - subject(:create_request) { post :create, request_params } + subject(:create_request) { post :create, params: request_params } let(:request_params) do { @@ -260,7 +260,7 @@ describe Projects::CompareController do end describe 'GET signatures' do - subject(:signatures_request) { get :signatures, request_params } + subject(:signatures_request) { get :signatures, params: request_params } let(:request_params) do { diff --git a/spec/controllers/projects/cycle_analytics_controller_spec.rb b/spec/controllers/projects/cycle_analytics_controller_spec.rb index 5c79269e8f1..6a63cbdf8e2 100644 --- a/spec/controllers/projects/cycle_analytics_controller_spec.rb +++ b/spec/controllers/projects/cycle_analytics_controller_spec.rb @@ -13,8 +13,10 @@ describe Projects::CycleAnalyticsController do context 'with no data' do it 'is true' do get(:show, - namespace_id: project.namespace, - project_id: project) + params: { + namespace_id: project.namespace, + project_id: project + }) expect(response).to be_success expect(assigns(:cycle_analytics_no_data)).to eq(true) @@ -32,8 +34,10 @@ describe Projects::CycleAnalyticsController do it 'is false' do get(:show, - namespace_id: project.namespace, - project_id: project) + params: { + namespace_id: project.namespace, + project_id: project + }) expect(response).to be_success expect(assigns(:cycle_analytics_no_data)).to eq(false) diff --git a/spec/controllers/projects/deploy_keys_controller_spec.rb b/spec/controllers/projects/deploy_keys_controller_spec.rb index 4567a51b88e..e54cf3e8181 100644 --- a/spec/controllers/projects/deploy_keys_controller_spec.rb +++ b/spec/controllers/projects/deploy_keys_controller_spec.rb @@ -16,8 +16,8 @@ describe Projects::DeployKeysController do end context 'when html requested' do - it 'redirects to blob' do - get :index, params + it 'redirects to project settings with the correct anchor' do + get :index, params: params expect(response).to redirect_to(project_settings_repository_path(project, anchor: 'js-deploy-keys-settings')) end @@ -48,7 +48,7 @@ describe Projects::DeployKeysController do end it 'returns json in a correct format' do - get :index, params.merge(format: :json) + get :index, params: params.merge(format: :json) json = JSON.parse(response.body) @@ -60,6 +60,40 @@ describe Projects::DeployKeysController do end end + describe 'POST create' do + def create_params(title = 'my-key') + { + namespace_id: project.namespace.path, + project_id: project.path, + deploy_key: { + title: title, + key: attributes_for(:deploy_key)[:key], + deploy_keys_projects_attributes: { '0' => { can_push: '1' } } + } + } + end + + it 'creates a new deploy key for the project' do + expect { post :create, params: create_params }.to change(project.deploy_keys, :count).by(1) + + expect(response).to redirect_to(project_settings_repository_path(project, anchor: 'js-deploy-keys-settings')) + end + + it 'redirects to project settings with the correct anchor' do + post :create, params: create_params + + expect(response).to redirect_to(project_settings_repository_path(project, anchor: 'js-deploy-keys-settings')) + end + + context 'when the deploy key is invalid' do + it 'shows an alert with the validations errors' do + post :create, params: create_params(nil) + + expect(flash[:alert]).to eq("Title can't be blank, Deploy keys projects deploy key title can't be blank") + end + end + end + describe '/enable/:id' do let(:deploy_key) { create(:deploy_key) } let(:project2) { create(:project) } @@ -74,7 +108,7 @@ describe Projects::DeployKeysController do it 'redirects to login' do expect do - put :enable, id: deploy_key.id, namespace_id: project.namespace, project_id: project + put :enable, params: { id: deploy_key.id, namespace_id: project.namespace, project_id: project } end.not_to change { DeployKeysProject.count } expect(response).to have_http_status(302) @@ -89,7 +123,7 @@ describe Projects::DeployKeysController do it 'returns 404' do expect do - put :enable, id: deploy_key.id, namespace_id: project.namespace, project_id: project + put :enable, params: { id: deploy_key.id, namespace_id: project.namespace, project_id: project } end.not_to change { DeployKeysProject.count } expect(response).to have_http_status(404) @@ -103,7 +137,7 @@ describe Projects::DeployKeysController do it 'returns 302' do expect do - put :enable, id: deploy_key.id, namespace_id: project.namespace, project_id: project + put :enable, params: { id: deploy_key.id, namespace_id: project.namespace, project_id: project } end.to change { DeployKeysProject.count }.by(1) expect(DeployKeysProject.where(project_id: project.id, deploy_key_id: deploy_key.id).count).to eq(1) @@ -112,7 +146,7 @@ describe Projects::DeployKeysController do end it 'returns 404' do - put :enable, id: 0, namespace_id: project.namespace, project_id: project + put :enable, params: { id: 0, namespace_id: project.namespace, project_id: project } expect(response).to have_http_status(404) end @@ -125,7 +159,7 @@ describe Projects::DeployKeysController do it 'returns 302' do expect do - put :enable, id: deploy_key.id, namespace_id: project.namespace, project_id: project + put :enable, params: { id: deploy_key.id, namespace_id: project.namespace, project_id: project } end.to change { DeployKeysProject.count }.by(1) expect(DeployKeysProject.where(project_id: project.id, deploy_key_id: deploy_key.id).count).to eq(1) @@ -145,7 +179,7 @@ describe Projects::DeployKeysController do end it 'redirects to login' do - put :disable, id: deploy_key.id, namespace_id: project.namespace, project_id: project + put :disable, params: { id: deploy_key.id, namespace_id: project.namespace, project_id: project } expect(response).to have_http_status(302) expect(response).to redirect_to(new_user_session_path) @@ -159,7 +193,7 @@ describe Projects::DeployKeysController do end it 'returns 404' do - put :disable, id: deploy_key.id, namespace_id: project.namespace, project_id: project + put :disable, params: { id: deploy_key.id, namespace_id: project.namespace, project_id: project } expect(response).to have_http_status(404) expect(DeployKey.find(deploy_key.id)).to eq(deploy_key) @@ -168,7 +202,7 @@ describe Projects::DeployKeysController do context 'with user with permission' do it 'returns 302' do - put :disable, id: deploy_key.id, namespace_id: project.namespace, project_id: project + put :disable, params: { id: deploy_key.id, namespace_id: project.namespace, project_id: project } expect(response).to have_http_status(302) expect(response).to redirect_to(namespace_project_settings_repository_path(anchor: 'js-deploy-keys-settings')) @@ -177,7 +211,7 @@ describe Projects::DeployKeysController do end it 'returns 404' do - put :disable, id: 0, namespace_id: project.namespace, project_id: project + put :disable, params: { id: 0, namespace_id: project.namespace, project_id: project } expect(response).to have_http_status(404) end @@ -190,7 +224,7 @@ describe Projects::DeployKeysController do it 'returns 302' do expect do - put :disable, id: deploy_key.id, namespace_id: project.namespace, project_id: project + put :disable, params: { id: deploy_key.id, namespace_id: project.namespace, project_id: project } end.to change { DeployKey.count }.by(-1) expect(response).to have_http_status(302) diff --git a/spec/controllers/projects/deployments_controller_spec.rb b/spec/controllers/projects/deployments_controller_spec.rb index 5b7da81b6a1..5c33098fd31 100644 --- a/spec/controllers/projects/deployments_controller_spec.rb +++ b/spec/controllers/projects/deployments_controller_spec.rb @@ -19,7 +19,7 @@ describe Projects::DeploymentsController do create(:deployment, :success, environment: environment, created_at: 7.hours.ago) create(:deployment, :success, environment: environment) - get :index, deployment_params(after: 8.hours.ago) + get :index, params: deployment_params(after: 8.hours.ago) expect(response).to be_ok @@ -29,7 +29,7 @@ describe Projects::DeploymentsController do it 'returns a list with deployments information' do create(:deployment, :success, environment: environment) - get :index, deployment_params + get :index, params: deployment_params expect(response).to be_ok expect(response).to match_response_schema('deployments') @@ -49,7 +49,7 @@ describe Projects::DeploymentsController do end it 'responds with not found' do - get :metrics, deployment_params(id: deployment.id) + get :metrics, params: deployment_params(id: deployment.id) expect(response).to be_not_found end @@ -66,7 +66,7 @@ describe Projects::DeploymentsController do end it 'returns a empty response 204 resposne' do - get :metrics, deployment_params(id: deployment.id) + get :metrics, params: deployment_params(id: deployment.id) expect(response).to have_gitlab_http_status(204) expect(response.body).to eq('') end @@ -86,7 +86,7 @@ describe Projects::DeploymentsController do end it 'returns a metrics JSON document' do - get :metrics, deployment_params(id: deployment.id) + get :metrics, params: deployment_params(id: deployment.id) expect(response).to be_ok expect(json_response['success']).to be(true) @@ -101,7 +101,7 @@ describe Projects::DeploymentsController do end it 'responds with not found' do - get :metrics, deployment_params(id: deployment.id) + get :metrics, params: deployment_params(id: deployment.id) expect(response).to be_not_found end @@ -122,7 +122,7 @@ describe Projects::DeploymentsController do end it 'responds with not found' do - get :metrics, deployment_params(id: deployment.id) + get :metrics, params: deployment_params(id: deployment.id) expect(response).to be_not_found end @@ -141,7 +141,7 @@ describe Projects::DeploymentsController do end it 'returns a empty response 204 response' do - get :additional_metrics, deployment_params(id: deployment.id, format: :json) + get :additional_metrics, params: deployment_params(id: deployment.id, format: :json) expect(response).to have_gitlab_http_status(204) expect(response.body).to eq('') end @@ -161,7 +161,7 @@ describe Projects::DeploymentsController do end it 'returns a metrics JSON document' do - get :additional_metrics, deployment_params(id: deployment.id, format: :json) + get :additional_metrics, params: deployment_params(id: deployment.id, format: :json) expect(response).to be_ok expect(json_response['success']).to be(true) diff --git a/spec/controllers/projects/discussions_controller_spec.rb b/spec/controllers/projects/discussions_controller_spec.rb index 4aa33dbbb01..0b9f336cf13 100644 --- a/spec/controllers/projects/discussions_controller_spec.rb +++ b/spec/controllers/projects/discussions_controller_spec.rb @@ -23,7 +23,7 @@ describe Projects::DiscussionsController do context 'when user is not authorized to read the MR' do it 'returns 404' do - get :show, request_params, format: :json + get :show, params: request_params, session: { format: :json } expect(response).to have_gitlab_http_status(404) end @@ -35,7 +35,7 @@ describe Projects::DiscussionsController do end it 'returns status 200' do - get :show, request_params, format: :json + get :show, params: request_params, session: { format: :json } expect(response).to have_gitlab_http_status(200) end @@ -43,7 +43,7 @@ describe Projects::DiscussionsController do it 'returns status 404 if MR does not exists' do merge_request.destroy! - get :show, request_params, format: :json + get :show, params: request_params, session: { format: :json } expect(response).to have_gitlab_http_status(404) end @@ -56,7 +56,7 @@ describe Projects::DiscussionsController do end it 'returns status 200' do - get :show, request_params, format: :json + get :show, params: request_params, session: { format: :json } expect(response).to have_gitlab_http_status(200) end @@ -70,7 +70,7 @@ describe Projects::DiscussionsController do context "when the user is not authorized to resolve the discussion" do it "returns status 404" do - post :resolve, request_params + post :resolve, params: request_params expect(response).to have_gitlab_http_status(404) end @@ -87,7 +87,7 @@ describe Projects::DiscussionsController do end it "returns status 404" do - post :resolve, request_params + post :resolve, params: request_params expect(response).to have_gitlab_http_status(404) end @@ -95,7 +95,7 @@ describe Projects::DiscussionsController do context "when the discussion is resolvable" do it "resolves the discussion" do - post :resolve, request_params + post :resolve, params: request_params expect(note.reload.discussion.resolved?).to be true expect(note.reload.discussion.resolved_by).to eq(user) @@ -104,17 +104,17 @@ describe Projects::DiscussionsController do it "sends notifications if all discussions are resolved" do expect_any_instance_of(MergeRequests::ResolvedDiscussionNotificationService).to receive(:execute).with(merge_request) - post :resolve, request_params + post :resolve, params: request_params end it "returns the name of the resolving user" do - post :resolve, request_params + post :resolve, params: request_params expect(JSON.parse(response.body)['resolved_by']['name']).to eq(user.name) end it "returns status 200" do - post :resolve, request_params + post :resolve, params: request_params expect(response).to have_gitlab_http_status(200) end @@ -123,7 +123,7 @@ describe Projects::DiscussionsController do expect_any_instance_of(DiscussionSerializer).to receive(:represent) .with(instance_of(Discussion), { context: instance_of(described_class), render_truncated_diff_lines: true }) - post :resolve, request_params + post :resolve, params: request_params end context 'diff discussion' do @@ -131,7 +131,7 @@ describe Projects::DiscussionsController do let(:discussion) { note.discussion } it "returns truncated diff lines" do - post :resolve, request_params + post :resolve, params: request_params expect(JSON.parse(response.body)['truncated_diff_lines']).to be_present end @@ -149,7 +149,7 @@ describe Projects::DiscussionsController do context "when the user is not authorized to resolve the discussion" do it "returns status 404" do - delete :unresolve, request_params + delete :unresolve, params: request_params expect(response).to have_gitlab_http_status(404) end @@ -166,7 +166,7 @@ describe Projects::DiscussionsController do end it "returns status 404" do - delete :unresolve, request_params + delete :unresolve, params: request_params expect(response).to have_gitlab_http_status(404) end @@ -174,13 +174,13 @@ describe Projects::DiscussionsController do context "when the discussion is resolvable" do it "unresolves the discussion" do - delete :unresolve, request_params + delete :unresolve, params: request_params expect(note.reload.discussion.resolved?).to be false end it "returns status 200" do - delete :unresolve, request_params + delete :unresolve, params: request_params expect(response).to have_gitlab_http_status(200) end @@ -194,7 +194,7 @@ describe Projects::DiscussionsController do expect_any_instance_of(DiscussionSerializer).to receive(:represent) .with(instance_of(Discussion), { context: instance_of(described_class), render_truncated_diff_lines: true }) - delete :unresolve, request_params + delete :unresolve, params: request_params end end end diff --git a/spec/controllers/projects/environments_controller_spec.rb b/spec/controllers/projects/environments_controller_spec.rb index 5fa0488014f..94fb85f217c 100644 --- a/spec/controllers/projects/environments_controller_spec.rb +++ b/spec/controllers/projects/environments_controller_spec.rb @@ -17,7 +17,7 @@ describe Projects::EnvironmentsController do describe 'GET index' do context 'when a request for the HTML is made' do it 'responds with status code 200' do - get :index, environment_params + get :index, params: environment_params expect(response).to have_gitlab_http_status(:ok) end @@ -26,7 +26,7 @@ describe Projects::EnvironmentsController do expect_any_instance_of(Gitlab::EtagCaching::Store) .to receive(:touch).with(project_environments_path(project, format: :json)) - get :index, environment_params + get :index, params: environment_params end end @@ -49,7 +49,7 @@ describe Projects::EnvironmentsController do context 'when requesting available environments scope' do before do - get :index, environment_params(format: :json, scope: :available) + get :index, params: environment_params(format: :json, scope: :available) end it 'responds with a payload describing available environments' do @@ -73,7 +73,7 @@ describe Projects::EnvironmentsController do context 'when requesting stopped environments scope' do before do - get :index, environment_params(format: :json, scope: :stopped) + get :index, params: environment_params(format: :json, scope: :stopped) end it 'responds with a payload describing stopped environments' do @@ -103,9 +103,11 @@ describe Projects::EnvironmentsController do context 'when using default format' do it 'responds with HTML' do - get :folder, namespace_id: project.namespace, - project_id: project, - id: 'staging-1.0' + get :folder, params: { + namespace_id: project.namespace, + project_id: project, + id: 'staging-1.0' + } expect(response).to be_ok expect(response).to render_template 'folder' @@ -114,9 +116,11 @@ describe Projects::EnvironmentsController do context 'when using JSON format' do it 'sorts the subfolders lexicographically' do - get :folder, namespace_id: project.namespace, - project_id: project, - id: 'staging-1.0', + get :folder, params: { + namespace_id: project.namespace, + project_id: project, + id: 'staging-1.0' + }, format: :json expect(response).to be_ok @@ -132,7 +136,7 @@ describe Projects::EnvironmentsController do describe 'GET show' do context 'with valid id' do it 'responds with a status code 200' do - get :show, environment_params + get :show, params: environment_params expect(response).to be_ok end @@ -142,7 +146,7 @@ describe Projects::EnvironmentsController do it 'responds with a status code 404' do params = environment_params params[:id] = 12345 - get :show, params + get :show, params: params expect(response).to have_gitlab_http_status(404) end @@ -151,7 +155,7 @@ describe Projects::EnvironmentsController do describe 'GET edit' do it 'responds with a status code 200' do - get :edit, environment_params + get :edit, params: environment_params expect(response).to be_ok end @@ -160,7 +164,7 @@ describe Projects::EnvironmentsController do describe 'PATCH #update' do it 'responds with a 302' do patch_params = environment_params.merge(environment: { external_url: 'https://git.gitlab.com' }) - patch :update, patch_params + patch :update, params: patch_params expect(response).to have_gitlab_http_status(302) end @@ -171,7 +175,7 @@ describe Projects::EnvironmentsController do it 'returns 404' do allow_any_instance_of(Environment).to receive(:available?) { false } - patch :stop, environment_params(format: :json) + patch :stop, params: environment_params(format: :json) expect(response).to have_gitlab_http_status(404) end @@ -184,7 +188,7 @@ describe Projects::EnvironmentsController do allow_any_instance_of(Environment) .to receive_messages(available?: true, stop_with_action!: action) - patch :stop, environment_params(format: :json) + patch :stop, params: environment_params(format: :json) expect(response).to have_gitlab_http_status(200) expect(json_response).to eq( @@ -198,7 +202,7 @@ describe Projects::EnvironmentsController do allow_any_instance_of(Environment) .to receive_messages(available?: true, stop_with_action!: nil) - patch :stop, environment_params(format: :json) + patch :stop, params: environment_params(format: :json) expect(response).to have_gitlab_http_status(200) expect(json_response).to eq( @@ -211,7 +215,7 @@ describe Projects::EnvironmentsController do describe 'GET #terminal' do context 'with valid id' do it 'responds with a status code 200' do - get :terminal, environment_params + get :terminal, params: environment_params expect(response).to have_gitlab_http_status(200) end @@ -222,13 +226,13 @@ describe Projects::EnvironmentsController do expect_any_instance_of(defined?(EE) ? EE::Environment : Environment) .to receive(:terminals) - get :terminal, environment_params + get :terminal, params: environment_params end end context 'with invalid id' do it 'responds with a status code 404' do - get :terminal, environment_params(id: 666) + get :terminal, params: environment_params(id: 666) expect(response).to have_gitlab_http_status(404) end @@ -254,7 +258,7 @@ describe Projects::EnvironmentsController do .with(:fake_terminal) .and_return(workhorse: :response) - get :terminal_websocket_authorize, environment_params + get :terminal_websocket_authorize, params: environment_params expect(response).to have_gitlab_http_status(200) expect(response.headers["Content-Type"]).to eq(Gitlab::Workhorse::INTERNAL_API_CONTENT_TYPE) @@ -264,7 +268,7 @@ describe Projects::EnvironmentsController do context 'and invalid id' do it 'returns 404' do - get :terminal_websocket_authorize, environment_params(id: 666) + get :terminal_websocket_authorize, params: environment_params(id: 666) expect(response).to have_gitlab_http_status(404) end @@ -275,7 +279,7 @@ describe Projects::EnvironmentsController do it 'aborts with an exception' do allow(Gitlab::Workhorse).to receive(:verify_api_request!).and_raise(JWT::DecodeError) - expect { get :terminal_websocket_authorize, environment_params }.to raise_error(JWT::DecodeError) + expect { get :terminal_websocket_authorize, params: environment_params }.to raise_error(JWT::DecodeError) # controller tests don't set the response status correctly. It's enough # to check that the action raised an exception end @@ -288,13 +292,13 @@ describe Projects::EnvironmentsController do it 'redirects to environment if it exists' do environment = create(:environment, name: 'production', project: project) - get :metrics_redirect, namespace_id: project.namespace, project_id: project + get :metrics_redirect, params: { namespace_id: project.namespace, project_id: project } expect(response).to redirect_to(environment_metrics_path(environment)) end it 'redirects to empty page if no environment exists' do - get :metrics_redirect, namespace_id: project.namespace, project_id: project + get :metrics_redirect, params: { namespace_id: project.namespace, project_id: project } expect(response).to be_ok expect(response).to render_template 'empty' @@ -312,14 +316,14 @@ describe Projects::EnvironmentsController do end it 'returns a metrics page' do - get :metrics, environment_params + get :metrics, params: environment_params expect(response).to be_ok end context 'when requesting metrics as JSON' do it 'returns a metrics JSON document' do - get :metrics, environment_params(format: :json) + get :metrics, params: environment_params(format: :json) expect(response).to have_gitlab_http_status(204) expect(json_response).to eq({}) @@ -337,7 +341,7 @@ describe Projects::EnvironmentsController do end it 'returns a metrics JSON document' do - get :metrics, environment_params(format: :json) + get :metrics, params: environment_params(format: :json) expect(response).to be_ok expect(json_response['success']).to be(true) @@ -359,7 +363,7 @@ describe Projects::EnvironmentsController do context 'when requesting metrics as JSON' do it 'returns a metrics JSON document' do - get :additional_metrics, environment_params(format: :json) + get :additional_metrics, params: environment_params(format: :json) expect(response).to have_gitlab_http_status(204) expect(json_response).to eq({}) @@ -379,7 +383,7 @@ describe Projects::EnvironmentsController do end it 'returns a metrics JSON document' do - get :additional_metrics, environment_params(format: :json) + get :additional_metrics, params: environment_params(format: :json) expect(response).to be_ok expect(json_response['success']).to be(true) diff --git a/spec/controllers/projects/find_file_controller_spec.rb b/spec/controllers/projects/find_file_controller_spec.rb index 66fe41108e2..9072d67af07 100644 --- a/spec/controllers/projects/find_file_controller_spec.rb +++ b/spec/controllers/projects/find_file_controller_spec.rb @@ -17,9 +17,11 @@ describe Projects::FindFileController do before do get(:show, - namespace_id: project.namespace, - project_id: project, - id: id) + params: { + namespace_id: project.namespace, + project_id: project, + id: id + }) end context "valid branch" do @@ -36,9 +38,11 @@ describe Projects::FindFileController do describe "GET #list" do def go(format: 'json') get :list, - namespace_id: project.namespace, - project_id: project, - id: id, + params: { + namespace_id: project.namespace, + project_id: project, + id: id + }, format: format end diff --git a/spec/controllers/projects/forks_controller_spec.rb b/spec/controllers/projects/forks_controller_spec.rb index 945b6142abf..0e1663c8585 100644 --- a/spec/controllers/projects/forks_controller_spec.rb +++ b/spec/controllers/projects/forks_controller_spec.rb @@ -13,8 +13,10 @@ describe Projects::ForksController do describe 'GET index' do def get_forks get :index, - namespace_id: project.namespace, - project_id: project + params: { + namespace_id: project.namespace, + project_id: project + } end context 'when fork is public' do @@ -83,8 +85,10 @@ describe Projects::ForksController do describe 'GET new' do def get_new get :new, - namespace_id: project.namespace, - project_id: project + params: { + namespace_id: project.namespace, + project_id: project + } end context 'when user is signed in' do @@ -111,9 +115,11 @@ describe Projects::ForksController do describe 'POST create' do def post_create post :create, - namespace_id: project.namespace, - project_id: project, - namespace_key: user.namespace.id + params: { + namespace_id: project.namespace, + project_id: project, + namespace_key: user.namespace.id + } end context 'when user is signed in' do diff --git a/spec/controllers/projects/graphs_controller_spec.rb b/spec/controllers/projects/graphs_controller_spec.rb index da78592a6f6..73fb7307e11 100644 --- a/spec/controllers/projects/graphs_controller_spec.rb +++ b/spec/controllers/projects/graphs_controller_spec.rb @@ -11,7 +11,7 @@ describe Projects::GraphsController do describe 'GET languages' do it "redirects_to action charts" do - get(:commits, namespace_id: project.namespace.path, project_id: project.path, id: 'master') + get(:commits, params: { namespace_id: project.namespace.path, project_id: project.path, id: 'master' }) expect(response).to redirect_to action: :charts end @@ -19,7 +19,7 @@ describe Projects::GraphsController do describe 'GET commits' do it "redirects_to action charts" do - get(:commits, namespace_id: project.namespace.path, project_id: project.path, id: 'master') + get(:commits, params: { namespace_id: project.namespace.path, project_id: project.path, id: 'master' }) expect(response).to redirect_to action: :charts end diff --git a/spec/controllers/projects/group_links_controller_spec.rb b/spec/controllers/projects/group_links_controller_spec.rb index 879aff26deb..675eeff8d12 100644 --- a/spec/controllers/projects/group_links_controller_spec.rb +++ b/spec/controllers/projects/group_links_controller_spec.rb @@ -14,10 +14,12 @@ describe Projects::GroupLinksController do describe '#create' do shared_context 'link project to group' do before do - post(:create, namespace_id: project.namespace, - project_id: project, - link_group_id: group.id, - link_group_access: ProjectGroupLink.default_access) + post(:create, params: { + namespace_id: project.namespace, + project_id: project, + link_group_id: group.id, + link_group_access: ProjectGroupLink.default_access + }) end end @@ -65,10 +67,12 @@ describe Projects::GroupLinksController do context 'when project group id equal link group id' do before do - post(:create, namespace_id: project.namespace, - project_id: project, - link_group_id: group2.id, - link_group_access: ProjectGroupLink.default_access) + post(:create, params: { + namespace_id: project.namespace, + project_id: project, + link_group_id: group2.id, + link_group_access: ProjectGroupLink.default_access + }) end it 'does not share project with selected group' do @@ -84,9 +88,11 @@ describe Projects::GroupLinksController do context 'when link group id is not present' do before do - post(:create, namespace_id: project.namespace, - project_id: project, - link_group_access: ProjectGroupLink.default_access) + post(:create, params: { + namespace_id: project.namespace, + project_id: project, + link_group_access: ProjectGroupLink.default_access + }) end it 'redirects to project group links page' do diff --git a/spec/controllers/projects/hooks_controller_spec.rb b/spec/controllers/projects/hooks_controller_spec.rb index 7d3a8c3d0d3..3037c922b68 100644 --- a/spec/controllers/projects/hooks_controller_spec.rb +++ b/spec/controllers/projects/hooks_controller_spec.rb @@ -11,7 +11,7 @@ describe Projects::HooksController do describe '#index' do it 'redirects to settings/integrations page' do - get(:index, namespace_id: project.namespace, project_id: project) + get(:index, params: { namespace_id: project.namespace, project_id: project }) expect(response).to redirect_to( project_settings_integrations_path(project) @@ -38,7 +38,7 @@ describe Projects::HooksController do wiki_page_events: true } - post :create, namespace_id: project.namespace, project_id: project, hook: hook_params + post :create, params: { namespace_id: project.namespace, project_id: project, hook: hook_params } expect(response).to have_http_status(302) expect(ProjectHook.all.size).to eq(1) diff --git a/spec/controllers/projects/imports_controller_spec.rb b/spec/controllers/projects/imports_controller_spec.rb index cdc63f5aab3..3ebfe4b0918 100644 --- a/spec/controllers/projects/imports_controller_spec.rb +++ b/spec/controllers/projects/imports_controller_spec.rb @@ -12,13 +12,13 @@ describe Projects::ImportsController do describe 'GET #show' do context 'when repository does not exists' do it 'renders template' do - get :show, namespace_id: project.namespace.to_param, project_id: project + get :show, params: { namespace_id: project.namespace.to_param, project_id: project } expect(response).to render_template :show end it 'sets flash.now if params is present' do - get :show, namespace_id: project.namespace.to_param, project_id: project, continue: { to: '/', notice_now: 'Started' } + get :show, params: { namespace_id: project.namespace.to_param, project_id: project, continue: { to: '/', notice_now: 'Started' } } expect(flash.now[:notice]).to eq 'Started' end @@ -34,13 +34,13 @@ describe Projects::ImportsController do end it 'renders template' do - get :show, namespace_id: project.namespace.to_param, project_id: project + get :show, params: { namespace_id: project.namespace.to_param, project_id: project } expect(response).to render_template :show end it 'sets flash.now if params is present' do - get :show, namespace_id: project.namespace.to_param, project_id: project, continue: { to: '/', notice_now: 'In progress' } + get :show, params: { namespace_id: project.namespace.to_param, project_id: project, continue: { to: '/', notice_now: 'In progress' } } expect(flash.now[:notice]).to eq 'In progress' end @@ -52,7 +52,7 @@ describe Projects::ImportsController do end it 'redirects to new_namespace_project_import_path' do - get :show, namespace_id: project.namespace.to_param, project_id: project + get :show, params: { namespace_id: project.namespace.to_param, project_id: project } expect(response).to redirect_to new_project_import_path(project) end @@ -67,7 +67,7 @@ describe Projects::ImportsController do it 'redirects to namespace_project_path' do allow_any_instance_of(Project).to receive(:forked?).and_return(true) - get :show, namespace_id: project.namespace.to_param, project_id: project + get :show, params: { namespace_id: project.namespace.to_param, project_id: project } expect(flash[:notice]).to eq 'The project was successfully forked.' expect(response).to redirect_to project_path(project) @@ -76,7 +76,7 @@ describe Projects::ImportsController do context 'when project is external' do it 'redirects to namespace_project_path' do - get :show, namespace_id: project.namespace.to_param, project_id: project + get :show, params: { namespace_id: project.namespace.to_param, project_id: project } expect(flash[:notice]).to eq 'The project was successfully imported.' expect(response).to redirect_to project_path(project) @@ -92,7 +92,7 @@ describe Projects::ImportsController do end it 'redirects to internal params[:to]' do - get :show, namespace_id: project.namespace.to_param, project_id: project, continue: params + get :show, params: { namespace_id: project.namespace.to_param, project_id: project, continue: params } expect(flash[:notice]).to eq params[:notice] expect(response).to redirect_to params[:to] @@ -101,7 +101,7 @@ describe Projects::ImportsController do it 'does not redirect to external params[:to]' do params[:to] = "//google.com" - get :show, namespace_id: project.namespace.to_param, project_id: project, continue: params + get :show, params: { namespace_id: project.namespace.to_param, project_id: project, continue: params } expect(response).not_to redirect_to params[:to] end end @@ -113,7 +113,7 @@ describe Projects::ImportsController do end it 'redirects to namespace_project_path' do - get :show, namespace_id: project.namespace.to_param, project_id: project + get :show, params: { namespace_id: project.namespace.to_param, project_id: project } expect(response).to redirect_to project_path(project) end diff --git a/spec/controllers/projects/issues_controller_spec.rb b/spec/controllers/projects/issues_controller_spec.rb index 6240ab6d867..a239ac16c0d 100644 --- a/spec/controllers/projects/issues_controller_spec.rb +++ b/spec/controllers/projects/issues_controller_spec.rb @@ -18,7 +18,7 @@ describe Projects::IssuesController do project.issues_enabled = false project.save! - get :index, namespace_id: project.namespace, project_id: project + get :index, params: { namespace_id: project.namespace, project_id: project } expect(response).to have_gitlab_http_status(404) end @@ -26,7 +26,7 @@ describe Projects::IssuesController do context 'when GitLab issues enabled' do it 'renders the "index" template' do - get :index, namespace_id: project.namespace, project_id: project + get :index, params: { namespace_id: project.namespace, project_id: project } expect(response).to have_gitlab_http_status(200) expect(response).to render_template(:index) @@ -45,13 +45,13 @@ describe Projects::IssuesController do it_behaves_like 'set sort order from user preference' it "returns index" do - get :index, namespace_id: project.namespace, project_id: project + get :index, params: { namespace_id: project.namespace, project_id: project } expect(response).to have_gitlab_http_status(200) end it "returns 301 if request path doesn't match project path" do - get :index, namespace_id: project.namespace, project_id: project.path.upcase + get :index, params: { namespace_id: project.namespace, project_id: project.path.upcase } expect(response).to redirect_to(project_issues_path(project)) end @@ -60,7 +60,7 @@ describe Projects::IssuesController do project.issues_enabled = false project.save! - get :index, namespace_id: project.namespace, project_id: project + get :index, params: { namespace_id: project.namespace, project_id: project } expect(response).to have_gitlab_http_status(404) end end @@ -77,18 +77,22 @@ describe Projects::IssuesController do it 'redirects to last_page if page number is larger than number of pages' do get :index, - namespace_id: project.namespace.to_param, - project_id: project, - page: (last_page + 1).to_param + params: { + namespace_id: project.namespace.to_param, + project_id: project, + page: (last_page + 1).to_param + } expect(response).to redirect_to(namespace_project_issues_path(page: last_page, state: controller.params[:state], scope: controller.params[:scope])) end it 'redirects to specified page' do get :index, - namespace_id: project.namespace.to_param, - project_id: project, - page: last_page.to_param + params: { + namespace_id: project.namespace.to_param, + project_id: project, + page: last_page.to_param + } expect(assigns(:issues).current_page).to eq(last_page) expect(response).to have_gitlab_http_status(200) @@ -97,10 +101,12 @@ describe Projects::IssuesController do it 'does not redirect to external sites when provided a host field' do external_host = "www.example.com" get :index, - namespace_id: project.namespace.to_param, - project_id: project, - page: (last_page + 1).to_param, - host: external_host + params: { + namespace_id: project.namespace.to_param, + project_id: project, + page: (last_page + 1).to_param, + host: external_host + } expect(response).to redirect_to(namespace_project_issues_path(page: last_page, state: controller.params[:state], scope: controller.params[:scope])) end @@ -109,9 +115,11 @@ describe Projects::IssuesController do allow(controller).to receive(:pagination_disabled?).and_return(true) get :index, - namespace_id: project.namespace.to_param, - project_id: project, - page: (last_page + 1).to_param + params: { + namespace_id: project.namespace.to_param, + project_id: project, + page: (last_page + 1).to_param + } expect(response).to have_gitlab_http_status(200) expect(assigns(:issues).size).to eq(2) @@ -121,7 +129,7 @@ describe Projects::IssuesController do describe 'GET #new' do it 'redirects to signin if not logged in' do - get :new, namespace_id: project.namespace, project_id: project + get :new, params: { namespace_id: project.namespace, project_id: project } expect(flash[:notice]).to eq 'Please sign in to create the new issue.' expect(response).to redirect_to(new_user_session_path) @@ -134,7 +142,7 @@ describe Projects::IssuesController do end it 'builds a new issue' do - get :new, namespace_id: project.namespace, project_id: project + get :new, params: { namespace_id: project.namespace, project_id: project } expect(assigns(:issue)).to be_a_new(Issue) end @@ -144,7 +152,7 @@ describe Projects::IssuesController do 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 + get :new, params: { namespace_id: project_with_repository.namespace, project_id: project_with_repository, merge_request_to_resolve_discussions_of: mr.iid } expect(assigns(:issue).title).not_to be_empty expect(assigns(:issue).description).not_to be_empty @@ -153,7 +161,7 @@ describe Projects::IssuesController do it 'fills in an issue for a discussion' do note = create(:note_on_merge_request, project: project) - get :new, namespace_id: project.namespace.path, project_id: project, merge_request_to_resolve_discussions_of: note.noteable.iid, discussion_to_resolve: note.discussion_id + get :new, params: { namespace_id: project.namespace.path, project_id: project, merge_request_to_resolve_discussions_of: note.noteable.iid, discussion_to_resolve: note.discussion_id } expect(assigns(:issue).title).not_to be_empty expect(assigns(:issue).description).not_to be_empty @@ -178,7 +186,7 @@ describe Projects::IssuesController do project.issues_enabled = false project.save! - get :new, namespace_id: project.namespace, project_id: project + get :new, params: { namespace_id: project.namespace, project_id: project } expect(response).to have_gitlab_http_status(404) end @@ -186,7 +194,7 @@ describe Projects::IssuesController do context 'when GitLab issues enabled' do it 'renders the "new" template' do - get :new, namespace_id: project.namespace, project_id: project + get :new, params: { namespace_id: project.namespace, project_id: project } expect(response).to have_gitlab_http_status(200) expect(response).to render_template(:new) @@ -198,12 +206,12 @@ describe Projects::IssuesController do describe 'Redirect after sign in' do context 'with an AJAX request' do it 'does not store the visited URL' do - xhr :get, - :show, + get :show, params: { format: :json, namespace_id: project.namespace, project_id: project, id: issue.iid + }, xhr: true expect(session['user_return_to']).to be_blank end @@ -212,9 +220,11 @@ describe Projects::IssuesController do context 'without an AJAX request' do it 'stores the visited URL' do get :show, - namespace_id: project.namespace.to_param, - project_id: project, - id: issue.iid + params: { + namespace_id: project.namespace.to_param, + project_id: project, + id: issue.iid + } expect(session['user_return_to']).to eq("/#{project.namespace.to_param}/#{project.to_param}/issues/#{issue.iid}") end @@ -253,11 +263,13 @@ describe Projects::IssuesController do def move_issue post :move, - format: :json, - namespace_id: project.namespace.to_param, - project_id: project, - id: issue.iid, - move_to_project_id: another_project.id + params: { + namespace_id: project.namespace.to_param, + project_id: project, + id: issue.iid, + move_to_project_id: another_project.id + }, + format: :json end end end @@ -265,10 +277,13 @@ describe Projects::IssuesController do describe 'PUT #update' do subject do put :update, - namespace_id: project.namespace, - project_id: project, - id: issue.to_param, - issue: { title: 'New title' }, format: :json + params: { + namespace_id: project.namespace, + project_id: project, + id: issue.to_param, + issue: { title: 'New title' } + }, + format: :json end before do @@ -318,9 +333,11 @@ describe Projects::IssuesController do describe 'GET #realtime_changes' do def go(id:) get :realtime_changes, - namespace_id: project.namespace.to_param, - project_id: project, - id: id + params: { + namespace_id: project.namespace.to_param, + project_id: project, + id: id + } end context 'when an issue was edited' do @@ -433,8 +450,10 @@ describe Projects::IssuesController do def get_issues get :index, - namespace_id: project.namespace.to_param, - project_id: project + params: { + namespace_id: project.namespace.to_param, + project_id: project + } end end @@ -502,7 +521,7 @@ describe Projects::IssuesController do format: :json }.merge(additional_params) - put :update, params + put :update, params: params end def go(id:) @@ -635,9 +654,11 @@ describe Projects::IssuesController do def go(id:) get :show, - namespace_id: project.namespace.to_param, - project_id: project, - id: id + params: { + namespace_id: project.namespace.to_param, + project_id: project, + id: id + } end it 'avoids (most) N+1s loading labels', :request_store do @@ -658,9 +679,11 @@ describe Projects::IssuesController do def go(id:) get :realtime_changes, - namespace_id: project.namespace.to_param, - project_id: project, - id: id + params: { + namespace_id: project.namespace.to_param, + project_id: project, + id: id + } end end @@ -669,9 +692,11 @@ describe Projects::IssuesController do def go(id:) get :edit, - namespace_id: project.namespace.to_param, - project_id: project, - id: id + params: { + namespace_id: project.namespace.to_param, + project_id: project, + id: id + } end end @@ -680,10 +705,12 @@ describe Projects::IssuesController do def go(id:) put :update, - namespace_id: project.namespace.to_param, - project_id: project, - id: id, - issue: { title: 'New title' } + params: { + namespace_id: project.namespace.to_param, + project_id: project, + id: id, + issue: { title: 'New title' } + } end end end @@ -694,7 +721,7 @@ describe Projects::IssuesController do project = create(:project, :public) project.add_developer(user) - post :create, { + post :create, params: { namespace_id: project.namespace.to_param, project_id: project, issue: { title: 'Title', description: 'Description' }.merge(issue_attrs) @@ -718,7 +745,7 @@ describe Projects::IssuesController do end def post_issue(issue_params, other_params: {}) - post :create, { namespace_id: project.namespace.to_param, project_id: project, issue: issue_params, merge_request_to_resolve_discussions_of: merge_request.iid }.merge(other_params) + post :create, params: { namespace_id: project.namespace.to_param, project_id: project, issue: issue_params, merge_request_to_resolve_discussions_of: merge_request.iid }.merge(other_params) end it 'creates an issue for the project' do @@ -885,7 +912,7 @@ describe Projects::IssuesController do create(:user_agent_detail, subject: issue) project.add_maintainer(admin) sign_in(admin) - post :mark_as_spam, { + post :mark_as_spam, params: { namespace_id: project.namespace, project_id: project, id: issue.iid @@ -906,7 +933,7 @@ describe Projects::IssuesController do end it "rejects a developer to destroy an issue" do - delete :destroy, namespace_id: project.namespace, project_id: project, id: issue.iid + delete :destroy, params: { namespace_id: project.namespace, project_id: project, id: issue.iid } expect(response).to have_gitlab_http_status(404) end end @@ -921,7 +948,7 @@ describe Projects::IssuesController do end it "deletes the issue" do - delete :destroy, namespace_id: project.namespace, project_id: project, id: issue.iid + delete :destroy, params: { namespace_id: project.namespace, project_id: project, id: issue.iid } expect(response).to have_gitlab_http_status(302) expect(controller).to set_flash[:notice].to(/The issue was successfully deleted\./) @@ -930,7 +957,7 @@ describe Projects::IssuesController do it 'delegates the update of the todos count cache to TodoService' do expect_any_instance_of(TodoService).to receive(:destroy_target).with(issue).once - delete :destroy, namespace_id: project.namespace, project_id: project, id: issue.iid + delete :destroy, params: { namespace_id: project.namespace, project_id: project, id: issue.iid } end end end @@ -943,8 +970,12 @@ describe Projects::IssuesController do it "toggles the award emoji" do expect do - post(:toggle_award_emoji, namespace_id: project.namespace, - project_id: project, id: issue.iid, name: "thumbsup") + post(:toggle_award_emoji, params: { + namespace_id: project.namespace, + project_id: project, + id: issue.iid, + name: "thumbsup" + }) end.to change { issue.award_emoji.count }.by(1) expect(response).to have_gitlab_http_status(200) @@ -986,9 +1017,11 @@ describe Projects::IssuesController do end def create_merge_request - post :create_merge_request, namespace_id: project.namespace.to_param, - project_id: project.to_param, - id: issue.to_param, + post :create_merge_request, params: { + namespace_id: project.namespace.to_param, + project_id: project.to_param, + id: issue.to_param + }, format: :json end end @@ -1002,7 +1035,7 @@ describe Projects::IssuesController do end it 'returns discussion json' do - get :discussions, namespace_id: project.namespace, project_id: project, id: issue.iid + get :discussions, params: { namespace_id: project.namespace, project_id: project, id: issue.iid } expect(json_response.first.keys).to match_array(%w[id reply_id expanded notes diff_discussion discussion_path individual_note resolvable resolved resolved_at resolved_by resolved_by_push commit_id for_commit project_id]) end @@ -1010,7 +1043,7 @@ describe Projects::IssuesController do it 'renders the author status html if there is a status' do create(:user_status, user: discussion.author) - get :discussions, namespace_id: project.namespace, project_id: project, id: issue.iid + get :discussions, params: { namespace_id: project.namespace, project_id: project, id: issue.iid } note_json = json_response.first['notes'].first @@ -1019,14 +1052,14 @@ describe Projects::IssuesController do it 'does not cause an extra query for the status' do control = ActiveRecord::QueryRecorder.new do - get :discussions, namespace_id: project.namespace, project_id: project, id: issue.iid + get :discussions, params: { namespace_id: project.namespace, project_id: project, id: issue.iid } end create(:user_status, user: discussion.author) second_discussion = create(:discussion_note_on_issue, noteable: issue, project: issue.project, author: create(:user)) create(:user_status, user: second_discussion.author) - expect { get :discussions, namespace_id: project.namespace, project_id: project, id: issue.iid } + expect { get :discussions, params: { namespace_id: project.namespace, project_id: project, id: issue.iid } } .not_to exceed_query_limit(control) end @@ -1046,26 +1079,26 @@ describe Projects::IssuesController do end it 'filters notes that the user should not see' do - get :discussions, namespace_id: project.namespace, project_id: project, id: issue.iid + get :discussions, params: { namespace_id: project.namespace, project_id: project, id: issue.iid } expect(JSON.parse(response.body).count).to eq(1) end it 'does not result in N+1 queries' do # Instantiate the controller variables to ensure QueryRecorder has an accurate base count - get :discussions, namespace_id: project.namespace, project_id: project, id: issue.iid + get :discussions, params: { namespace_id: project.namespace, project_id: project, id: issue.iid } RequestStore.clear! control_count = ActiveRecord::QueryRecorder.new do - get :discussions, namespace_id: project.namespace, project_id: project, id: issue.iid + get :discussions, params: { namespace_id: project.namespace, project_id: project, id: issue.iid } end.count RequestStore.clear! create_list(:discussion_note_on_issue, 2, :system, noteable: issue, project: issue.project, note: cross_reference) - expect { get :discussions, namespace_id: project.namespace, project_id: project, id: issue.iid }.not_to exceed_query_limit(control_count) + expect { get :discussions, params: { namespace_id: project.namespace, project_id: project, id: issue.iid } }.not_to exceed_query_limit(control_count) end end end diff --git a/spec/controllers/projects/jobs_controller_spec.rb b/spec/controllers/projects/jobs_controller_spec.rb index fca313dafb1..7f65fe551e9 100644 --- a/spec/controllers/projects/jobs_controller_spec.rb +++ b/spec/controllers/projects/jobs_controller_spec.rb @@ -96,7 +96,7 @@ describe Projects::JobsController, :clean_gitlab_redis_shared_state do project_id: project } - get :index, params.merge(extra_params) + get :index, params: params.merge(extra_params) end end @@ -461,7 +461,7 @@ describe Projects::JobsController, :clean_gitlab_redis_shared_state do project_id: project } - get :show, params.merge(extra_params) + get :show, params: params.merge(extra_params) end end @@ -552,9 +552,11 @@ describe Projects::JobsController, :clean_gitlab_redis_shared_state do end def get_trace - get :trace, namespace_id: project.namespace, - project_id: project, - id: job.id, + get :trace, params: { + namespace_id: project.namespace, + project_id: project, + id: job.id + }, format: :json end end @@ -564,9 +566,11 @@ describe Projects::JobsController, :clean_gitlab_redis_shared_state do let(:status) { job.detailed_status(double('user')) } before do - get :status, namespace_id: project.namespace, - project_id: project, - id: job.id, + get :status, params: { + namespace_id: project.namespace, + project_id: project, + id: job.id + }, format: :json end @@ -605,9 +609,11 @@ describe Projects::JobsController, :clean_gitlab_redis_shared_state do end def post_retry - post :retry, namespace_id: project.namespace, - project_id: project, - id: job.id + post :retry, params: { + namespace_id: project.namespace, + project_id: project, + id: job.id + } end end @@ -645,9 +651,11 @@ describe Projects::JobsController, :clean_gitlab_redis_shared_state do end def post_play - post :play, namespace_id: project.namespace, - project_id: project, - id: job.id + post :play, params: { + namespace_id: project.namespace, + project_id: project, + id: job.id + } end end @@ -714,9 +722,9 @@ describe Projects::JobsController, :clean_gitlab_redis_shared_state do end def post_cancel(additional_params = {}) - post :cancel, { namespace_id: project.namespace, - project_id: project, - id: job.id }.merge(additional_params) + post :cancel, params: { namespace_id: project.namespace, + project_id: project, + id: job.id }.merge(additional_params) end end @@ -754,9 +762,11 @@ describe Projects::JobsController, :clean_gitlab_redis_shared_state do end def post_unschedule - post :unschedule, namespace_id: project.namespace, - project_id: project, - id: job.id + post :unschedule, params: { + namespace_id: project.namespace, + project_id: project, + id: job.id + } end end @@ -797,8 +807,10 @@ describe Projects::JobsController, :clean_gitlab_redis_shared_state do end def post_cancel_all - post :cancel_all, namespace_id: project.namespace, - project_id: project + post :cancel_all, params: { + namespace_id: project.namespace, + project_id: project + } end end @@ -860,52 +872,33 @@ describe Projects::JobsController, :clean_gitlab_redis_shared_state do end def post_erase - post :erase, namespace_id: project.namespace, - project_id: project, - id: job.id + post :erase, params: { + namespace_id: project.namespace, + project_id: project, + id: job.id + } end end describe 'GET raw' do subject do - post :raw, namespace_id: project.namespace, - project_id: project, - id: job.id + post :raw, params: { + namespace_id: project.namespace, + project_id: project, + id: job.id + } end context "when job has a trace artifact" do let(:job) { create(:ci_build, :trace_artifact, pipeline: pipeline) } - context 'when feature flag workhorse_set_content_type is' do - before do - stub_feature_flags(workhorse_set_content_type: flag_value) - end - - context 'enabled' do - let(:flag_value) { true } - - it "sets #{Gitlab::Workhorse::DETECT_HEADER} header" do - response = subject - - expect(response).to have_gitlab_http_status(:ok) - expect(response.headers["Content-Type"]).to eq("text/plain; charset=utf-8") - expect(response.body).to eq(job.job_artifacts_trace.open.read) - expect(response.header[Gitlab::Workhorse::DETECT_HEADER]).to eq "true" - end - end - - context 'disabled' do - let(:flag_value) { false } - - it 'returns a trace' do - response = subject + it "sets #{Gitlab::Workhorse::DETECT_HEADER} header" do + response = subject - expect(response).to have_gitlab_http_status(:ok) - expect(response.headers["Content-Type"]).to eq("text/plain; charset=utf-8") - expect(response.body).to eq(job.job_artifacts_trace.open.read) - expect(response.header[Gitlab::Workhorse::DETECT_HEADER]).to be nil - end - end + expect(response).to have_gitlab_http_status(:ok) + expect(response.headers["Content-Type"]).to eq("text/plain; charset=utf-8") + expect(response.body).to eq(job.job_artifacts_trace.open.read) + expect(response.header[Gitlab::Workhorse::DETECT_HEADER]).to eq "true" end end @@ -1020,7 +1013,7 @@ describe Projects::JobsController, :clean_gitlab_redis_shared_state do project_id: project } - get :terminal, params.merge(extra_params) + get :terminal, params: params.merge(extra_params) end end @@ -1074,7 +1067,7 @@ describe Projects::JobsController, :clean_gitlab_redis_shared_state do project_id: project } - get :terminal_websocket_authorize, params.merge(extra_params) + get :terminal_websocket_authorize, params: params.merge(extra_params) end end end diff --git a/spec/controllers/projects/labels_controller_spec.rb b/spec/controllers/projects/labels_controller_spec.rb index e03d23bcdf6..32897a0f1b4 100644 --- a/spec/controllers/projects/labels_controller_spec.rb +++ b/spec/controllers/projects/labels_controller_spec.rb @@ -67,7 +67,7 @@ describe Projects::LabelsController do end def list_labels - get :index, namespace_id: project.namespace.to_param, project_id: project + get :index, params: { namespace_id: project.namespace.to_param, project_id: project } end end @@ -76,7 +76,7 @@ describe Projects::LabelsController do let(:personal_project) { create(:project, namespace: user.namespace) } it 'creates labels' do - post :generate, namespace_id: personal_project.namespace.to_param, project_id: personal_project + post :generate, params: { namespace_id: personal_project.namespace.to_param, project_id: personal_project } expect(response).to have_gitlab_http_status(302) end @@ -84,7 +84,7 @@ describe Projects::LabelsController do context 'project belonging to a group' do it 'creates labels' do - post :generate, namespace_id: project.namespace.to_param, project_id: project + post :generate, params: { namespace_id: project.namespace.to_param, project_id: project } expect(response).to have_gitlab_http_status(302) end @@ -109,7 +109,7 @@ describe Projects::LabelsController do end def toggle_subscription(label) - post :toggle_subscription, namespace_id: project.namespace.to_param, project_id: project, id: label.to_param + post :toggle_subscription, params: { namespace_id: project.namespace.to_param, project_id: project, id: label.to_param } end end @@ -119,7 +119,7 @@ describe Projects::LabelsController do context 'not group reporters' do it 'denies access' do - post :promote, namespace_id: project.namespace.to_param, project_id: project, id: label_1.to_param + post :promote, params: { namespace_id: project.namespace.to_param, project_id: project, id: label_1.to_param } expect(response).to have_gitlab_http_status(404) end @@ -131,13 +131,13 @@ describe Projects::LabelsController do end it 'gives access' do - post :promote, namespace_id: project.namespace.to_param, project_id: project, id: label_1.to_param + post :promote, params: { namespace_id: project.namespace.to_param, project_id: project, id: label_1.to_param } expect(response).to redirect_to(namespace_project_labels_path) end it 'promotes the label' do - post :promote, namespace_id: project.namespace.to_param, project_id: project, id: label_1.to_param + post :promote, params: { namespace_id: project.namespace.to_param, project_id: project, id: label_1.to_param } expect(Label.where(id: label_1.id)).to be_empty expect(GroupLabel.find_by(title: promoted_label_name)).not_to be_nil @@ -146,7 +146,7 @@ describe Projects::LabelsController do it 'renders label name without parsing it as HTML' do label_1.update!(name: 'CCC<img src=x onerror=alert(document.domain)>') - post :promote, namespace_id: project.namespace.to_param, project_id: project, id: label_1.to_param + post :promote, params: { namespace_id: project.namespace.to_param, project_id: project, id: label_1.to_param } expect(flash[:notice]).to eq("CCC<img src=x onerror=alert(document.domain)> promoted to <a href=\"#{group_labels_path(project.group)}\"><u>group label</u></a>.") end @@ -159,7 +159,7 @@ describe Projects::LabelsController do end it 'returns to label list' do - post :promote, namespace_id: project.namespace.to_param, project_id: project, id: label_1.to_param + post :promote, params: { namespace_id: project.namespace.to_param, project_id: project, id: label_1.to_param } expect(response).to redirect_to(namespace_project_labels_path) end end @@ -176,7 +176,7 @@ describe Projects::LabelsController do context 'non-show path' do context 'with exactly matching casing' do it 'does not redirect' do - get :index, namespace_id: project.namespace, project_id: project.to_param + get :index, params: { namespace_id: project.namespace, project_id: project.to_param } expect(response).not_to have_gitlab_http_status(301) end @@ -184,7 +184,7 @@ describe Projects::LabelsController do context 'with different casing' do it 'redirects to the correct casing' do - get :index, namespace_id: project.namespace, project_id: project.to_param.upcase + get :index, params: { namespace_id: project.namespace, project_id: project.to_param.upcase } expect(response).to redirect_to(project_labels_path(project)) expect(controller).not_to set_flash[:notice] @@ -197,7 +197,7 @@ describe Projects::LabelsController do let!(:redirect_route) { project.redirect_routes.create(path: project.full_path + 'old') } it 'redirects to the canonical path' do - get :index, namespace_id: project.namespace, project_id: project.to_param + 'old' + get :index, params: { namespace_id: project.namespace, project_id: project.to_param + 'old' } expect(response).to redirect_to(project_labels_path(project)) expect(controller).to set_flash[:notice].to(project_moved_message(redirect_route, project)) @@ -209,13 +209,13 @@ describe Projects::LabelsController do context 'for a non-GET request' do context 'when requesting the canonical path with different casing' do it 'does not 404' do - post :generate, namespace_id: project.namespace, project_id: project + post :generate, params: { namespace_id: project.namespace, project_id: project } expect(response).not_to have_gitlab_http_status(404) end it 'does not redirect to the correct casing' do - post :generate, namespace_id: project.namespace, project_id: project + post :generate, params: { namespace_id: project.namespace, project_id: project } expect(response).not_to have_gitlab_http_status(301) end @@ -225,7 +225,7 @@ describe Projects::LabelsController do let!(:redirect_route) { project.redirect_routes.create(path: project.full_path + 'old') } it 'returns not found' do - post :generate, namespace_id: project.namespace, project_id: project.to_param + 'old' + post :generate, params: { namespace_id: project.namespace, project_id: project.to_param + 'old' } expect(response).to have_gitlab_http_status(404) end diff --git a/spec/controllers/projects/mattermosts_controller_spec.rb b/spec/controllers/projects/mattermosts_controller_spec.rb index c2a334a849c..6c8c7cd8f2b 100644 --- a/spec/controllers/projects/mattermosts_controller_spec.rb +++ b/spec/controllers/projects/mattermosts_controller_spec.rb @@ -17,8 +17,10 @@ describe Projects::MattermostsController do it 'accepts the request' do get(:new, - namespace_id: project.namespace.to_param, - project_id: project) + params: { + namespace_id: project.namespace.to_param, + project_id: project + }) expect(response).to have_gitlab_http_status(200) end @@ -29,9 +31,11 @@ describe Projects::MattermostsController do subject do post(:create, - namespace_id: project.namespace.to_param, - project_id: project, - mattermost: mattermost_params) + params: { + namespace_id: project.namespace.to_param, + project_id: project, + mattermost: mattermost_params + }) end context 'no request can be made to mattermost' do diff --git a/spec/controllers/projects/merge_requests/conflicts_controller_spec.rb b/spec/controllers/projects/merge_requests/conflicts_controller_spec.rb index 1e1ea9a7144..039f35875d2 100644 --- a/spec/controllers/projects/merge_requests/conflicts_controller_spec.rb +++ b/spec/controllers/projects/merge_requests/conflicts_controller_spec.rb @@ -21,9 +21,11 @@ describe Projects::MergeRequests::ConflictsController do .and_raise(Gitlab::Git::Conflict::Parser::UnmergeableFile) get :show, - namespace_id: merge_request_with_conflicts.project.namespace.to_param, - project_id: merge_request_with_conflicts.project, - id: merge_request_with_conflicts.iid, + params: { + namespace_id: merge_request_with_conflicts.project.namespace.to_param, + project_id: merge_request_with_conflicts.project, + id: merge_request_with_conflicts.iid + }, format: 'json' end @@ -39,9 +41,11 @@ describe Projects::MergeRequests::ConflictsController do context 'with valid conflicts' do before do get :show, - namespace_id: merge_request_with_conflicts.project.namespace.to_param, - project_id: merge_request_with_conflicts.project, - id: merge_request_with_conflicts.iid, + params: { + namespace_id: merge_request_with_conflicts.project.namespace.to_param, + project_id: merge_request_with_conflicts.project, + id: merge_request_with_conflicts.iid + }, format: 'json' end @@ -99,11 +103,13 @@ describe Projects::MergeRequests::ConflictsController do describe 'GET conflict_for_path' do def conflict_for_path(path) get :conflict_for_path, - namespace_id: merge_request_with_conflicts.project.namespace.to_param, - project_id: merge_request_with_conflicts.project, - id: merge_request_with_conflicts.iid, - old_path: path, - new_path: path, + params: { + namespace_id: merge_request_with_conflicts.project.namespace.to_param, + project_id: merge_request_with_conflicts.project, + id: merge_request_with_conflicts.iid, + old_path: path, + new_path: path + }, format: 'json' end @@ -160,12 +166,14 @@ describe Projects::MergeRequests::ConflictsController do def resolve_conflicts(files) post :resolve_conflicts, - namespace_id: merge_request_with_conflicts.project.namespace.to_param, - project_id: merge_request_with_conflicts.project, - id: merge_request_with_conflicts.iid, - format: 'json', - files: files, - commit_message: 'Commit message' + params: { + namespace_id: merge_request_with_conflicts.project.namespace.to_param, + project_id: merge_request_with_conflicts.project, + id: merge_request_with_conflicts.iid, + files: files, + commit_message: 'Commit message' + }, + format: 'json' end context 'with valid params' do diff --git a/spec/controllers/projects/merge_requests/creations_controller_spec.rb b/spec/controllers/projects/merge_requests/creations_controller_spec.rb index f8c37c0a676..ac93393ac3a 100644 --- a/spec/controllers/projects/merge_requests/creations_controller_spec.rb +++ b/spec/controllers/projects/merge_requests/creations_controller_spec.rb @@ -24,7 +24,7 @@ describe Projects::MergeRequests::CreationsController do describe 'GET new' do context 'merge request that removes a submodule' do it 'renders new merge request widget template' do - get :new, get_diff_params + get :new, params: get_diff_params expect(response).to be_success end @@ -52,7 +52,7 @@ describe Projects::MergeRequests::CreationsController do end it 'limits total commits' do - get :new, large_diff_params + get :new, params: large_diff_params expect(response).to be_success @@ -66,7 +66,7 @@ describe Projects::MergeRequests::CreationsController do end it 'shows total commits' do - get :new, large_diff_params + get :new, params: large_diff_params expect(response).to be_success @@ -85,7 +85,7 @@ describe Projects::MergeRequests::CreationsController do it 'does not assign diffs var' do allow_any_instance_of(MergeRequest).to receive(:can_be_created).and_return(false) - get :diffs, get_diff_params.merge(format: 'json') + get :diffs, params: get_diff_params.merge(format: 'json') expect(response).to be_success expect(assigns[:diffs]).to be_nil @@ -101,7 +101,7 @@ describe Projects::MergeRequests::CreationsController do end it 'renders JSON including serialized pipelines' do - get :pipelines, get_diff_params.merge(format: 'json') + get :pipelines, params: get_diff_params.merge(format: 'json') expect(response).to be_ok expect(json_response).to have_key 'pipelines' @@ -117,7 +117,7 @@ describe Projects::MergeRequests::CreationsController do format: 'json' } - get :diff_for_path, params.merge(extra_params) + get :diff_for_path, params: params.merge(extra_params) end let(:existing_path) { 'files/ruby/feature.rb' } @@ -184,10 +184,12 @@ describe Projects::MergeRequests::CreationsController do expect(Ability).to receive(:allowed?).with(user, :read_project, project) { true } get :branch_to, - namespace_id: fork_project.namespace, - project_id: fork_project, - target_project_id: project.id, - ref: 'master' + params: { + namespace_id: fork_project.namespace, + project_id: fork_project, + target_project_id: project.id, + ref: 'master' + } expect(assigns(:commit)).not_to be_nil expect(response).to have_gitlab_http_status(200) @@ -197,10 +199,12 @@ describe Projects::MergeRequests::CreationsController do expect(Ability).to receive(:allowed?).with(user, :read_project, project) { false } get :branch_to, - namespace_id: fork_project.namespace, - project_id: fork_project, - target_project_id: project.id, - ref: 'master' + params: { + namespace_id: fork_project.namespace, + project_id: fork_project, + target_project_id: project.id, + ref: 'master' + } expect(assigns(:commit)).to be_nil expect(response).to have_gitlab_http_status(200) diff --git a/spec/controllers/projects/merge_requests/diffs_controller_spec.rb b/spec/controllers/projects/merge_requests/diffs_controller_spec.rb index 8fc5d302af6..a6017d8e5e6 100644 --- a/spec/controllers/projects/merge_requests/diffs_controller_spec.rb +++ b/spec/controllers/projects/merge_requests/diffs_controller_spec.rb @@ -20,7 +20,7 @@ describe Projects::MergeRequests::DiffsController do format: 'json' } - get :show, params.merge(extra_params) + get :show, params: params.merge(extra_params) end context 'with default params' do @@ -89,7 +89,7 @@ describe Projects::MergeRequests::DiffsController do format: 'json' } - get :diff_for_path, params.merge(extra_params) + get :diff_for_path, params: params.merge(extra_params) end let(:existing_path) { 'files/ruby/popen.rb' } diff --git a/spec/controllers/projects/merge_requests_controller_spec.rb b/spec/controllers/projects/merge_requests_controller_spec.rb index a37a831ddbb..4f4d3ca226f 100644 --- a/spec/controllers/projects/merge_requests_controller_spec.rb +++ b/spec/controllers/projects/merge_requests_controller_spec.rb @@ -19,26 +19,17 @@ describe Projects::MergeRequestsController do describe 'GET commit_change_content' do it 'renders commit_change_content template' do get :commit_change_content, - namespace_id: project.namespace.to_param, - project_id: project, - id: merge_request.iid, + params: { + namespace_id: project.namespace.to_param, + project_id: project, + id: merge_request.iid + }, format: 'html' expect(response).to render_template('_commit_change_content') end end - shared_examples "loads labels" do |action| - it "loads labels into the @labels variable" do - get action, - namespace_id: project.namespace.to_param, - project_id: project, - id: merge_request.iid, - format: 'html' - expect(assigns(:labels)).not_to be_nil - end - end - describe "GET show" do def go(extra_params = {}) params = { @@ -47,11 +38,9 @@ describe Projects::MergeRequestsController do id: merge_request.iid } - get :show, params.merge(extra_params) + get :show, params: params.merge(extra_params) end - it_behaves_like "loads labels", :show - describe 'as html' do context 'when diff files were cleaned' do render_views @@ -153,9 +142,12 @@ describe Projects::MergeRequestsController do def get_merge_requests(page = nil) get :index, - namespace_id: project.namespace.to_param, - project_id: project, - state: 'opened', page: page.to_param + params: { + namespace_id: project.namespace.to_param, + project_id: project, + state: 'opened', + page: page.to_param + } end it_behaves_like "issuables list meta-data", :merge_request @@ -182,11 +174,13 @@ describe Projects::MergeRequestsController do it 'does not redirect to external sites when provided a host field' do external_host = "www.example.com" get :index, - namespace_id: project.namespace.to_param, - project_id: project, - state: 'opened', - page: (last_page + 1).to_param, - host: external_host + params: { + namespace_id: project.namespace.to_param, + project_id: project, + state: 'opened', + page: (last_page + 1).to_param, + host: external_host + } expect(response).to redirect_to(namespace_project_merge_requests_path(page: last_page, state: controller.params[:state], scope: controller.params[:scope])) end @@ -227,7 +221,7 @@ describe Projects::MergeRequestsController do merge_request: mr_params }.merge(additional_params) - put :update, params + put :update, params: params end context 'changing the assignee' do @@ -324,7 +318,7 @@ describe Projects::MergeRequestsController do before do project.add_reporter(user) - xhr :post, :merge, base_params + post :merge, params: base_params, xhr: true end it 'returns 404' do @@ -336,7 +330,7 @@ describe Projects::MergeRequestsController do before do merge_request.update(title: "WIP: #{merge_request.title}") - post :merge, base_params + post :merge, params: base_params end it 'returns :failed' do @@ -346,7 +340,7 @@ describe Projects::MergeRequestsController do context 'when the sha parameter does not match the source SHA' do before do - post :merge, base_params.merge(sha: 'foo') + post :merge, params: base_params.merge(sha: 'foo') end it 'returns :sha_mismatch' do @@ -357,11 +351,7 @@ describe Projects::MergeRequestsController do context 'when the sha parameter matches the source SHA' do def merge_with_sha(params = {}) post_params = base_params.merge(sha: merge_request.diff_head_sha).merge(params) - if Gitlab.rails5? - post :merge, params: post_params, as: :json - else - post :merge, post_params - end + post :merge, params: post_params, as: :json end it 'returns :success' do @@ -400,7 +390,7 @@ describe Projects::MergeRequestsController do end def merge_when_pipeline_succeeds - post :merge, base_params.merge(sha: merge_request.diff_head_sha, merge_when_pipeline_succeeds: '1') + post :merge, params: base_params.merge(sha: merge_request.diff_head_sha, merge_when_pipeline_succeeds: '1') end it 'returns :merge_when_pipeline_succeeds' do @@ -517,7 +507,7 @@ describe Projects::MergeRequestsController do let(:user) { create(:user) } it "denies access to users unless they're admin or project owner" do - delete :destroy, namespace_id: project.namespace, project_id: project, id: merge_request.iid + delete :destroy, params: { namespace_id: project.namespace, project_id: project, id: merge_request.iid } expect(response).to have_gitlab_http_status(404) end @@ -532,7 +522,7 @@ describe Projects::MergeRequestsController do end it "deletes the merge request" do - delete :destroy, namespace_id: project.namespace, project_id: project, id: merge_request.iid + delete :destroy, params: { namespace_id: project.namespace, project_id: project, id: merge_request.iid } expect(response).to have_gitlab_http_status(302) expect(controller).to set_flash[:notice].to(/The merge request was successfully deleted\./) @@ -541,7 +531,7 @@ describe Projects::MergeRequestsController do it 'delegates the update of the todos count cache to TodoService' do expect_any_instance_of(TodoService).to receive(:destroy_target).with(merge_request).once - delete :destroy, namespace_id: project.namespace, project_id: project, id: merge_request.iid + delete :destroy, params: { namespace_id: project.namespace, project_id: project, id: merge_request.iid } end end end @@ -549,9 +539,11 @@ describe Projects::MergeRequestsController do describe 'GET commits' do def go(format: 'html') get :commits, - namespace_id: project.namespace.to_param, - project_id: project, - id: merge_request.iid, + params: { + namespace_id: project.namespace.to_param, + project_id: project, + id: merge_request.iid + }, format: format end @@ -570,9 +562,11 @@ describe Projects::MergeRequestsController do sha: merge_request.diff_head_sha) get :pipelines, - namespace_id: project.namespace.to_param, - project_id: project, - id: merge_request.iid, + params: { + namespace_id: project.namespace.to_param, + project_id: project, + id: merge_request.iid + }, format: :json end @@ -586,9 +580,11 @@ describe Projects::MergeRequestsController do describe 'GET test_reports' do subject do get :test_reports, - namespace_id: project.namespace.to_param, - project_id: project, - id: merge_request.iid, + params: { + namespace_id: project.namespace.to_param, + project_id: project, + id: merge_request.iid + }, format: :json end @@ -670,11 +666,14 @@ describe Projects::MergeRequestsController do merge_request.title = merge_request.wip_title merge_request.save - xhr :post, :remove_wip, - namespace_id: merge_request.project.namespace.to_param, - project_id: merge_request.project, - id: merge_request.iid, - format: :json + post :remove_wip, + params: { + format: :json, + namespace_id: merge_request.project.namespace.to_param, + project_id: merge_request.project, + id: merge_request.iid + }, + xhr: true end it 'removes the wip status' do @@ -688,11 +687,14 @@ describe Projects::MergeRequestsController do describe 'POST cancel_merge_when_pipeline_succeeds' do subject do - xhr :post, :cancel_merge_when_pipeline_succeeds, - namespace_id: merge_request.project.namespace.to_param, - project_id: merge_request.project, - id: merge_request.iid, - format: :json + post :cancel_merge_when_pipeline_succeeds, + params: { + format: :json, + namespace_id: merge_request.project.namespace.to_param, + project_id: merge_request.project, + id: merge_request.iid + }, + xhr: true end it 'calls MergeRequests::MergeWhenPipelineSucceedsService' do @@ -727,9 +729,11 @@ describe Projects::MergeRequestsController do target_branch: 'master') post :assign_related_issues, - namespace_id: project.namespace.to_param, - project_id: project, - id: merge_request.iid + params: { + namespace_id: project.namespace.to_param, + project_id: project, + id: merge_request.iid + } end it 'shows a flash message on success' do @@ -828,7 +832,7 @@ describe Projects::MergeRequestsController do format: 'json' } - get :ci_environments_status, params.merge(extra_params) + get :ci_environments_status, params: params.merge(extra_params) end end end @@ -869,9 +873,11 @@ describe Projects::MergeRequestsController do end def get_pipeline_status - get :pipeline_status, namespace_id: project.namespace, - project_id: project, - id: merge_request.iid, + get :pipeline_status, params: { + namespace_id: project.namespace, + project_id: project, + id: merge_request.iid + }, format: :json end end @@ -880,7 +886,7 @@ describe Projects::MergeRequestsController do let(:viewer) { user } def post_rebase - post :rebase, namespace_id: project.namespace, project_id: project, id: merge_request + post :rebase, params: { namespace_id: project.namespace, project_id: project, id: merge_request } end def expect_rebase_worker_for(user) @@ -936,15 +942,79 @@ describe Projects::MergeRequestsController do end end + describe 'GET discussions' do + context 'when authenticated' do + before do + project.add_developer(user) + sign_in(user) + end + + it 'returns 200' do + get :discussions, params: { namespace_id: project.namespace, project_id: project, id: merge_request.iid } + + expect(response.status).to eq(200) + end + + context 'highlight preloading' do + context 'with commit diff notes' do + let!(:commit_diff_note) do + create(:diff_note_on_commit, project: merge_request.project) + end + + it 'preloads notes diffs highlights' do + expect_next_instance_of(Gitlab::DiscussionsDiff::FileCollection) do |collection| + note_diff_file = commit_diff_note.note_diff_file + + expect(collection).to receive(:load_highlight).with([note_diff_file.id]).and_call_original + expect(collection).to receive(:find_by_id).with(note_diff_file.id).and_call_original + end + + get :discussions, params: { namespace_id: project.namespace, project_id: project, id: merge_request.iid } + end + end + + context 'with diff notes' do + let!(:diff_note) do + create(:diff_note_on_merge_request, noteable: merge_request, project: merge_request.project) + end + + it 'preloads notes diffs highlights' do + expect_next_instance_of(Gitlab::DiscussionsDiff::FileCollection) do |collection| + note_diff_file = diff_note.note_diff_file + + expect(collection).to receive(:load_highlight).with([note_diff_file.id]).and_call_original + expect(collection).to receive(:find_by_id).with(note_diff_file.id).and_call_original + end + + get :discussions, params: { namespace_id: project.namespace, project_id: project, id: merge_request.iid } + end + + it 'does not preload highlights when diff note is resolved' do + Notes::ResolveService.new(diff_note.project, user).execute(diff_note) + + expect_next_instance_of(Gitlab::DiscussionsDiff::FileCollection) do |collection| + note_diff_file = diff_note.note_diff_file + + expect(collection).to receive(:load_highlight).with([]).and_call_original + expect(collection).to receive(:find_by_id).with(note_diff_file.id).and_call_original + end + + get :discussions, params: { namespace_id: project.namespace, project_id: project, id: merge_request.iid } + end + end + end + end + end + describe 'GET edit' do it 'responds successfully' do - get :edit, namespace_id: project.namespace, project_id: project, id: merge_request + get :edit, params: { namespace_id: project.namespace, project_id: project, id: merge_request } expect(response).to have_gitlab_http_status(:success) end it 'assigns the noteable to make sure autocompletes work' do - get :edit, namespace_id: project.namespace, project_id: project, id: merge_request + get :edit, params: { namespace_id: project.namespace, project_id: project, id: merge_request } expect(assigns(:noteable)).not_to be_nil end diff --git a/spec/controllers/projects/milestones_controller_spec.rb b/spec/controllers/projects/milestones_controller_spec.rb index 658aa2a6738..5892024e756 100644 --- a/spec/controllers/projects/milestones_controller_spec.rb +++ b/spec/controllers/projects/milestones_controller_spec.rb @@ -22,7 +22,7 @@ describe Projects::MilestonesController do def view_milestone(options = {}) params = { namespace_id: project.namespace.id, project_id: project.id, id: milestone.iid } - get :show, params.merge(options) + get :show, params: params.merge(options) end it 'shows milestone page' do @@ -43,9 +43,11 @@ describe Projects::MilestonesController do describe "#index" do context "as html" do def render_index(project:, page:) - get :index, namespace_id: project.namespace.id, - project_id: project.id, - page: page + get :index, params: { + namespace_id: project.namespace.id, + project_id: project.id, + page: page + } end it "queries only projects milestones" do @@ -90,7 +92,7 @@ describe Projects::MilestonesController do context 'with a single group ancestor' do before do project.update(namespace: group) - get :index, namespace_id: project.namespace.id, project_id: project.id, format: :json + get :index, params: { namespace_id: project.namespace.id, project_id: project.id }, format: :json end it "queries projects milestones and groups milestones" do @@ -107,7 +109,7 @@ describe Projects::MilestonesController do before do project.update(namespace: subgroup) - get :index, namespace_id: project.namespace.id, project_id: project.id, format: :json + get :index, params: { namespace_id: project.namespace.id, project_id: project.id }, format: :json end it "queries projects milestones and all ancestors milestones" do @@ -124,7 +126,7 @@ describe Projects::MilestonesController do it "removes milestone" do expect(issue.milestone_id).to eq(milestone.id) - delete :destroy, namespace_id: project.namespace.id, project_id: project.id, id: milestone.iid, format: :js + delete :destroy, params: { namespace_id: project.namespace.id, project_id: project.id, id: milestone.iid }, format: :js expect(response).to be_success expect(Event.recent.first.action).to eq(Event::DESTROYED) @@ -155,7 +157,7 @@ describe Projects::MilestonesController do end it 'renders 404' do - post :promote, namespace_id: project.namespace.id, project_id: project.id, id: milestone.iid + post :promote, params: { namespace_id: project.namespace.id, project_id: project.id, id: milestone.iid } expect(response).to have_gitlab_http_status(404) end @@ -167,7 +169,7 @@ describe Projects::MilestonesController do end it 'shows group milestone' do - post :promote, namespace_id: project.namespace.id, project_id: project.id, id: milestone.iid + post :promote, params: { namespace_id: project.namespace.id, project_id: project.id, id: milestone.iid } expect(flash[:notice]).to eq("#{milestone.title} promoted to <a href=\"#{group_milestone_path(project.group, milestone.iid)}\"><u>group milestone</u></a>.") expect(response).to redirect_to(project_milestones_path(project)) @@ -176,7 +178,7 @@ describe Projects::MilestonesController do it 'renders milestone name without parsing it as HTML' do milestone.update!(name: 'CCC<img src=x onerror=alert(document.domain)>') - post :promote, namespace_id: project.namespace.id, project_id: project.id, id: milestone.iid + post :promote, params: { namespace_id: project.namespace.id, project_id: project.id, id: milestone.iid } expect(flash[:notice]).to eq("CCC promoted to <a href=\"#{group_milestone_path(project.group, milestone.iid)}\"><u>group milestone</u></a>.") end @@ -190,7 +192,7 @@ describe Projects::MilestonesController do it 'renders 404' do project.update(namespace: user.namespace) - post :promote, namespace_id: project.namespace.id, project_id: project.id, id: milestone.iid + post :promote, params: { namespace_id: project.namespace.id, project_id: project.id, id: milestone.iid } expect(response).to have_gitlab_http_status(404) end diff --git a/spec/controllers/projects/mirrors_controller_spec.rb b/spec/controllers/projects/mirrors_controller_spec.rb index 976f480930c..86a12a5e903 100644 --- a/spec/controllers/projects/mirrors_controller_spec.rb +++ b/spec/controllers/projects/mirrors_controller_spec.rb @@ -147,7 +147,7 @@ describe Projects::MirrorsController do end def do_get(project, url = 'ssh://example.com') - get :ssh_host_keys, namespace_id: project.namespace, project_id: project, ssh_url: url + get :ssh_host_keys, params: { namespace_id: project.namespace, project_id: project, ssh_url: url } end end @@ -155,6 +155,6 @@ describe Projects::MirrorsController do attrs = extra_attrs.merge(namespace_id: project.namespace.to_param, project_id: project.to_param) attrs[:project] = options - put :update, attrs + put :update, params: attrs end end diff --git a/spec/controllers/projects/notes_controller_spec.rb b/spec/controllers/projects/notes_controller_spec.rb index d2a26068362..81892575889 100644 --- a/spec/controllers/projects/notes_controller_spec.rb +++ b/spec/controllers/projects/notes_controller_spec.rb @@ -44,7 +44,7 @@ describe Projects::NotesController do .with(anything, anything, hash_including(last_fetched_at: last_fetched_at)) .and_call_original - get :index, request_params + get :index, params: request_params end context 'when user notes_filter is present' do @@ -55,7 +55,7 @@ describe Projects::NotesController do it 'filters system notes by comments' do user.set_notes_filter(UserPreference::NOTES_FILTERS[:only_comments], issue) - get :index, request_params + get :index, params: request_params expect(notes_json.count).to eq(1) expect(notes_json.first[:id].to_i).to eq(comment.id) @@ -64,7 +64,7 @@ describe Projects::NotesController do it 'returns all notes' do user.set_notes_filter(UserPreference::NOTES_FILTERS[:all_notes], issue) - get :index, request_params + get :index, params: request_params expect(notes_json.map { |note| note[:id].to_i }).to contain_exactly(comment.id, system_note.id) end @@ -74,7 +74,7 @@ describe Projects::NotesController do expect(ResourceEvents::MergeIntoNotesService).not_to receive(:new) - get :index, request_params + get :index, params: request_params end end @@ -85,7 +85,7 @@ describe Projects::NotesController do let(:params) { request_params.merge(target_type: 'merge_request', target_id: note.noteable_id, html: true) } it 'responds with the expected attributes' do - get :index, params + get :index, params: params expect(note_json[:id]).to eq(note.id) expect(note_json[:discussion_html]).not_to be_nil @@ -101,7 +101,7 @@ describe Projects::NotesController do let(:params) { request_params.merge(target_type: 'merge_request', target_id: note.noteable_id, html: true) } it 'responds with the expected attributes' do - get :index, params + get :index, params: params expect(note_json[:id]).to eq(note.id) expect(note_json[:discussion_html]).not_to be_nil @@ -120,7 +120,7 @@ describe Projects::NotesController do let(:params) { request_params.merge(target_type: 'merge_request', target_id: merge_request.id, html: true) } it 'responds with the expected attributes' do - get :index, params + get :index, params: params expect(note_json[:id]).to eq(note.id) expect(note_json[:discussion_html]).not_to be_nil @@ -133,7 +133,7 @@ describe Projects::NotesController do let(:params) { request_params.merge(target_type: 'commit', target_id: note.commit_id, html: true) } it 'responds with the expected attributes' do - get :index, params + get :index, params: params expect(note_json[:id]).to eq(note.id) expect(note_json[:discussion_html]).to be_nil @@ -148,7 +148,7 @@ describe Projects::NotesController do end it 'renders 404' do - get :index, params + get :index, params: params expect(response).to have_gitlab_http_status(404) end @@ -162,7 +162,7 @@ describe Projects::NotesController do let(:params) { request_params.merge(target_type: 'merge_request', target_id: note.noteable_id, html: true) } it 'responds with the expected attributes' do - get :index, params + get :index, params: params expect(note_json[:id]).to eq(note.id) expect(note_json[:html]).not_to be_nil @@ -182,7 +182,7 @@ describe Projects::NotesController do end it 'filters notes that the user should not see' do - get :index, request_params + get :index, params: request_params expect(parsed_response[:notes].count).to eq(1) expect(note_json[:id]).to eq(note.id.to_s) @@ -190,19 +190,19 @@ describe Projects::NotesController do it 'does not result in N+1 queries' do # Instantiate the controller variables to ensure QueryRecorder has an accurate base count - get :index, request_params + get :index, params: request_params RequestStore.clear! control_count = ActiveRecord::QueryRecorder.new do - get :index, request_params + get :index, params: request_params end.count RequestStore.clear! create_list(:discussion_note_on_issue, 2, :system, noteable: issue, project: issue.project, note: cross_reference) - expect { get :index, request_params }.not_to exceed_query_limit(control_count) + expect { get :index, params: request_params }.not_to exceed_query_limit(control_count) end end end @@ -227,19 +227,19 @@ describe Projects::NotesController do end it "returns status 302 for html" do - post :create, request_params + post :create, params: request_params expect(response).to have_gitlab_http_status(302) end it "returns status 200 for json" do - post :create, request_params.merge(format: :json) + post :create, params: request_params.merge(format: :json) expect(response).to have_gitlab_http_status(200) end it 'returns discussion JSON when the return_discussion param is set' do - post :create, request_params.merge(format: :json, return_discussion: 'true') + post :create, params: request_params.merge(format: :json, return_discussion: 'true') expect(response).to have_gitlab_http_status(200) expect(json_response).to have_key 'discussion' @@ -248,19 +248,19 @@ describe Projects::NotesController do context 'when merge_request_diff_head_sha present' do before do - service_params = { + service_params = ActionController::Parameters.new({ note: 'some note', noteable_id: merge_request.id.to_s, noteable_type: 'MergeRequest', merge_request_diff_head_sha: 'sha', in_reply_to_discussion_id: nil - } + }).permit! expect(Notes::CreateService).to receive(:new).with(project, user, service_params).and_return(double(execute: true)) end it "returns status 302 for html" do - post :create, request_params + post :create, params: request_params expect(response).to have_gitlab_http_status(302) end @@ -282,7 +282,7 @@ describe Projects::NotesController do end def post_create(extra_params = {}) - post :create, { + post :create, params: { note: { note: 'some other note', noteable_id: merge_request.id }, namespace_id: project.namespace, project_id: project, @@ -342,7 +342,7 @@ describe Projects::NotesController do namespace_id: project.namespace } - expect { post :create, request_params }.to change { issue.notes.count }.by(1) + expect { post :create, params: request_params }.to change { issue.notes.count }.by(1) .and change { locked_issue.notes.count }.by(0) expect(response).to have_gitlab_http_status(302) end @@ -357,7 +357,7 @@ describe Projects::NotesController do context 'when a noteable is not found' do it 'returns 404 status' do request_params[:target_id] = 9999 - post :create, request_params.merge(format: :json) + post :create, params: request_params.merge(format: :json) expect(response).to have_gitlab_http_status(404) end @@ -365,19 +365,19 @@ describe Projects::NotesController do context 'when a user is a team member' do it 'returns 302 status for html' do - post :create, request_params + post :create, params: request_params expect(response).to have_gitlab_http_status(302) end it 'returns 200 status for json' do - post :create, request_params.merge(format: :json) + post :create, params: request_params.merge(format: :json) expect(response).to have_gitlab_http_status(200) end it 'creates a new note' do - expect { post :create, request_params }.to change { Note.count }.by(1) + expect { post :create, params: request_params }.to change { Note.count }.by(1) end end @@ -387,13 +387,13 @@ describe Projects::NotesController do end it 'returns 404 status' do - post :create, request_params + post :create, params: request_params expect(response).to have_gitlab_http_status(404) end it 'does not create a new note' do - expect { post :create, request_params }.not_to change { Note.count } + expect { post :create, params: request_params }.not_to change { Note.count } end end end @@ -419,7 +419,7 @@ describe Projects::NotesController do end it "updates the note" do - expect { put :update, request_params }.to change { note.reload.note } + expect { put :update, params: request_params }.to change { note.reload.note } end end context "doesnt update the note" do @@ -441,7 +441,7 @@ describe Projects::NotesController do note: "New comment" } } - expect { put :update, request_params }.not_to change { note.reload.note } + expect { put :update, params: request_params }.not_to change { note.reload.note } expect(response).to have_gitlab_http_status(404) end end @@ -464,13 +464,13 @@ describe Projects::NotesController do end it "returns status 200 for html" do - delete :destroy, request_params + delete :destroy, params: request_params expect(response).to have_gitlab_http_status(200) end it "deletes the note" do - expect { delete :destroy, request_params }.to change { Note.count }.from(1).to(0) + expect { delete :destroy, params: request_params }.to change { Note.count }.from(1).to(0) end end @@ -481,7 +481,7 @@ describe Projects::NotesController do end it "returns status 404" do - delete :destroy, request_params + delete :destroy, params: request_params expect(response).to have_gitlab_http_status(404) end @@ -496,17 +496,17 @@ describe Projects::NotesController do it "toggles the award emoji" do expect do - post(:toggle_award_emoji, request_params.merge(name: "thumbsup")) + post(:toggle_award_emoji, params: request_params.merge(name: "thumbsup")) end.to change { note.award_emoji.count }.by(1) expect(response).to have_gitlab_http_status(200) end it "removes the already awarded emoji" do - post(:toggle_award_emoji, request_params.merge(name: "thumbsup")) + post(:toggle_award_emoji, params: request_params.merge(name: "thumbsup")) expect do - post(:toggle_award_emoji, request_params.merge(name: "thumbsup")) + post(:toggle_award_emoji, params: request_params.merge(name: "thumbsup")) end.to change { AwardEmoji.count }.by(-1) expect(response).to have_gitlab_http_status(200) @@ -525,7 +525,7 @@ describe Projects::NotesController do context "when the user is not authorized to resolve the note" do it "returns status 404" do - post :resolve, request_params + post :resolve, params: request_params expect(response).to have_gitlab_http_status(404) end @@ -542,7 +542,7 @@ describe Projects::NotesController do end it "returns status 404" do - post :resolve, request_params + post :resolve, params: request_params expect(response).to have_gitlab_http_status(404) end @@ -550,7 +550,7 @@ describe Projects::NotesController do context "when the note is resolvable" do it "resolves the note" do - post :resolve, request_params + post :resolve, params: request_params expect(note.reload.resolved?).to be true expect(note.reload.resolved_by).to eq(user) @@ -559,17 +559,17 @@ describe Projects::NotesController do it "sends notifications if all discussions are resolved" do expect_any_instance_of(MergeRequests::ResolvedDiscussionNotificationService).to receive(:execute).with(merge_request) - post :resolve, request_params + post :resolve, params: request_params end it "returns the name of the resolving user" do - post :resolve, request_params.merge(html: true) + post :resolve, params: request_params.merge(html: true) expect(JSON.parse(response.body)["resolved_by"]).to eq(user.name) end it "returns status 200" do - post :resolve, request_params + post :resolve, params: request_params expect(response).to have_gitlab_http_status(200) end @@ -586,7 +586,7 @@ describe Projects::NotesController do context "when the user is not authorized to resolve the note" do it "returns status 404" do - delete :unresolve, request_params + delete :unresolve, params: request_params expect(response).to have_gitlab_http_status(404) end @@ -603,7 +603,7 @@ describe Projects::NotesController do end it "returns status 404" do - delete :unresolve, request_params + delete :unresolve, params: request_params expect(response).to have_gitlab_http_status(404) end @@ -611,13 +611,13 @@ describe Projects::NotesController do context "when the note is resolvable" do it "unresolves the note" do - delete :unresolve, request_params + delete :unresolve, params: request_params expect(note.reload.resolved?).to be false end it "returns status 200" do - delete :unresolve, request_params + delete :unresolve, params: request_params expect(response).to have_gitlab_http_status(200) end diff --git a/spec/controllers/projects/pages_controller_spec.rb b/spec/controllers/projects/pages_controller_spec.rb index 927b6e0c473..382c1b5d124 100644 --- a/spec/controllers/projects/pages_controller_spec.rb +++ b/spec/controllers/projects/pages_controller_spec.rb @@ -19,7 +19,7 @@ describe Projects::PagesController do describe 'GET show' do it 'returns 200 status' do - get :show, request_params + get :show, params: request_params expect(response).to have_gitlab_http_status(200) end @@ -29,7 +29,7 @@ describe Projects::PagesController do let(:project) { create(:project, namespace: group) } it 'returns a 404 status code' do - get :show, request_params + get :show, params: request_params expect(response).to have_gitlab_http_status(404) end @@ -38,7 +38,7 @@ describe Projects::PagesController do describe 'DELETE destroy' do it 'returns 302 status' do - delete :destroy, request_params + delete :destroy, params: request_params expect(response).to have_gitlab_http_status(302) end @@ -51,7 +51,7 @@ describe Projects::PagesController do describe 'GET show' do it 'returns 404 status' do - get :show, request_params + get :show, params: request_params expect(response).to have_gitlab_http_status(404) end @@ -59,7 +59,7 @@ describe Projects::PagesController do describe 'DELETE destroy' do it 'returns 404 status' do - delete :destroy, request_params + delete :destroy, params: request_params expect(response).to have_gitlab_http_status(404) end @@ -82,13 +82,13 @@ describe Projects::PagesController do end it 'returns 302 status' do - patch :update, request_params + patch :update, params: request_params expect(response).to have_gitlab_http_status(:found) end it 'redirects back to the pages settings' do - patch :update, request_params + patch :update, params: request_params expect(response).to redirect_to(project_pages_path(project)) end @@ -99,7 +99,7 @@ describe Projects::PagesController do .with(project, user, ActionController::Parameters.new(request_params[:project]).permit!) .and_return(update_service) - patch :update, request_params + patch :update, params: request_params end end end diff --git a/spec/controllers/projects/pages_domains_controller_spec.rb b/spec/controllers/projects/pages_domains_controller_spec.rb index 75871eab1ab..8b7f7587701 100644 --- a/spec/controllers/projects/pages_domains_controller_spec.rb +++ b/spec/controllers/projects/pages_domains_controller_spec.rb @@ -24,7 +24,7 @@ describe Projects::PagesDomainsController do describe 'GET show' do it "displays the 'show' page" do - get(:show, request_params.merge(id: pages_domain.domain)) + get(:show, params: request_params.merge(id: pages_domain.domain)) expect(response).to have_gitlab_http_status(200) expect(response).to render_template('show') @@ -33,7 +33,7 @@ describe Projects::PagesDomainsController do describe 'GET new' do it "displays the 'new' page" do - get(:new, request_params) + get(:new, params: request_params) expect(response).to have_gitlab_http_status(200) expect(response).to render_template('new') @@ -43,7 +43,7 @@ describe Projects::PagesDomainsController do describe 'POST create' do it "creates a new pages domain" do expect do - post(:create, request_params.merge(pages_domain: pages_domain_params)) + post(:create, params: request_params.merge(pages_domain: pages_domain_params)) end.to change { PagesDomain.count }.by(1) created_domain = PagesDomain.reorder(:id).last @@ -55,7 +55,7 @@ describe Projects::PagesDomainsController do describe 'GET edit' do it "displays the 'edit' page" do - get(:edit, request_params.merge(id: pages_domain.domain)) + get(:edit, params: request_params.merge(id: pages_domain.domain)) expect(response).to have_gitlab_http_status(200) expect(response).to render_template('edit') @@ -78,14 +78,14 @@ describe Projects::PagesDomainsController do it 'updates the domain' do expect(pages_domain) .to receive(:update) - .with(pages_domain_params) + .with(ActionController::Parameters.new(pages_domain_params).permit!) .and_return(true) - patch(:update, params) + patch(:update, params: params) end it 'redirects to the project page' do - patch(:update, params) + patch(:update, params: params) expect(flash[:notice]).to eq 'Domain was updated' expect(response).to redirect_to(project_pages_path(project)) @@ -95,7 +95,7 @@ describe Projects::PagesDomainsController do it 'renders the edit action' do allow(pages_domain).to receive(:update).and_return(false) - patch(:update, params) + patch(:update, params: params) expect(response).to render_template('edit') end @@ -108,7 +108,7 @@ describe Projects::PagesDomainsController do .with(hash_not_including(:domain)) .and_return(true) - patch(:update, params.deep_merge(pages_domain: { domain: 'abc' })) + patch(:update, params: params.deep_merge(pages_domain: { domain: 'abc' })) end end end @@ -127,7 +127,7 @@ describe Projects::PagesDomainsController do it 'handles verification success' do expect(stub_service).to receive(:execute).and_return(status: :success) - post :verify, params + post :verify, params: params expect(response).to redirect_to project_pages_domain_path(project, pages_domain) expect(flash[:notice]).to eq('Successfully verified domain ownership') @@ -136,14 +136,14 @@ describe Projects::PagesDomainsController do it 'handles verification failure' do expect(stub_service).to receive(:execute).and_return(status: :failed) - post :verify, params + post :verify, params: params expect(response).to redirect_to project_pages_domain_path(project, pages_domain) expect(flash[:alert]).to eq('Failed to verify domain ownership') end it 'returns a 404 response for an unknown domain' do - post :verify, request_params.merge(id: 'unknown-domain') + post :verify, params: request_params.merge(id: 'unknown-domain') expect(response).to have_gitlab_http_status(404) end @@ -152,7 +152,7 @@ describe Projects::PagesDomainsController do describe 'DELETE destroy' do it "deletes the pages domain" do expect do - delete(:destroy, request_params.merge(id: pages_domain.domain)) + delete(:destroy, params: request_params.merge(id: pages_domain.domain)) end.to change { PagesDomain.count }.by(-1) expect(response).to redirect_to(project_pages_path(project)) @@ -166,7 +166,7 @@ describe Projects::PagesDomainsController do describe 'GET show' do it 'returns 404 status' do - get(:show, request_params.merge(id: pages_domain.domain)) + get(:show, params: request_params.merge(id: pages_domain.domain)) expect(response).to have_gitlab_http_status(404) end @@ -174,7 +174,7 @@ describe Projects::PagesDomainsController do describe 'GET new' do it 'returns 404 status' do - get :new, request_params + get :new, params: request_params expect(response).to have_gitlab_http_status(404) end @@ -182,7 +182,7 @@ describe Projects::PagesDomainsController do describe 'POST create' do it "returns 404 status" do - post(:create, request_params.merge(pages_domain: pages_domain_params)) + post(:create, params: request_params.merge(pages_domain: pages_domain_params)) expect(response).to have_gitlab_http_status(404) end @@ -190,7 +190,7 @@ describe Projects::PagesDomainsController do describe 'DELETE destroy' do it "deletes the pages domain" do - delete(:destroy, request_params.merge(id: pages_domain.domain)) + delete(:destroy, params: request_params.merge(id: pages_domain.domain)) expect(response).to have_gitlab_http_status(404) end diff --git a/spec/controllers/projects/pipeline_schedules_controller_spec.rb b/spec/controllers/projects/pipeline_schedules_controller_spec.rb index 7179423dde2..80506249ea9 100644 --- a/spec/controllers/projects/pipeline_schedules_controller_spec.rb +++ b/spec/controllers/projects/pipeline_schedules_controller_spec.rb @@ -44,7 +44,7 @@ describe Projects::PipelineSchedulesController do end def visit_pipelines_schedules - get :index, namespace_id: project.namespace.to_param, project_id: project, scope: scope + get :index, params: { namespace_id: project.namespace.to_param, project_id: project, scope: scope } end end @@ -57,7 +57,7 @@ describe Projects::PipelineSchedulesController do end it 'initializes a pipeline schedule model' do - get :new, namespace_id: project.namespace.to_param, project_id: project + get :new, params: { namespace_id: project.namespace.to_param, project_id: project } expect(response).to have_gitlab_http_status(:ok) expect(assigns(:schedule)).to be_a_new(Ci::PipelineSchedule) @@ -131,7 +131,7 @@ describe Projects::PipelineSchedulesController do end def go - post :create, namespace_id: project.namespace.to_param, project_id: project, schedule: schedule + post :create, params: { namespace_id: project.namespace.to_param, project_id: project, schedule: schedule } end end @@ -310,19 +310,11 @@ describe Projects::PipelineSchedulesController do end def go - if Gitlab.rails5? - put :update, params: { namespace_id: project.namespace.to_param, - project_id: project, - id: pipeline_schedule, - schedule: schedule }, - as: :html - - else - put :update, namespace_id: project.namespace.to_param, - project_id: project, - id: pipeline_schedule, - schedule: schedule - end + put :update, params: { namespace_id: project.namespace.to_param, + project_id: project, + id: pipeline_schedule, + schedule: schedule }, + as: :html end end @@ -336,7 +328,7 @@ describe Projects::PipelineSchedulesController do end it 'loads the pipeline schedule' do - get :edit, namespace_id: project.namespace.to_param, project_id: project, id: pipeline_schedule.id + get :edit, params: { namespace_id: project.namespace.to_param, project_id: project, id: pipeline_schedule.id } expect(response).to have_gitlab_http_status(:ok) expect(assigns(:schedule)).to eq(pipeline_schedule) @@ -356,7 +348,7 @@ describe Projects::PipelineSchedulesController do end def go - get :edit, namespace_id: project.namespace.to_param, project_id: project, id: pipeline_schedule.id + get :edit, params: { namespace_id: project.namespace.to_param, project_id: project, id: pipeline_schedule.id } end end @@ -374,7 +366,7 @@ describe Projects::PipelineSchedulesController do end def go - post :take_ownership, namespace_id: project.namespace.to_param, project_id: project, id: pipeline_schedule.id + post :take_ownership, params: { namespace_id: project.namespace.to_param, project_id: project, id: pipeline_schedule.id } end end @@ -396,7 +388,7 @@ describe Projects::PipelineSchedulesController do it 'does not allow pipeline to be executed' do expect(RunPipelineScheduleWorker).not_to receive(:perform_async) - post :play, namespace_id: project.namespace.to_param, project_id: project, id: pipeline_schedule.id + post :play, params: { namespace_id: project.namespace.to_param, project_id: project, id: pipeline_schedule.id } expect(response).to have_gitlab_http_status(404) end @@ -406,7 +398,7 @@ describe Projects::PipelineSchedulesController do it 'executes a new pipeline' do expect(RunPipelineScheduleWorker).to receive(:perform_async).with(pipeline_schedule.id, user.id).and_return('job-123') - post :play, namespace_id: project.namespace.to_param, project_id: project, id: pipeline_schedule.id + post :play, params: { namespace_id: project.namespace.to_param, project_id: project, id: pipeline_schedule.id } expect(flash[:notice]).to start_with 'Successfully scheduled a pipeline to run' expect(response).to have_gitlab_http_status(302) @@ -414,7 +406,7 @@ describe Projects::PipelineSchedulesController do it 'prevents users from scheduling the same pipeline repeatedly' do 2.times do - post :play, namespace_id: project.namespace.to_param, project_id: project, id: pipeline_schedule.id + post :play, params: { namespace_id: project.namespace.to_param, project_id: project, id: pipeline_schedule.id } end expect(flash.to_a.size).to eq(2) @@ -430,7 +422,7 @@ describe Projects::PipelineSchedulesController do expect(RunPipelineScheduleWorker).not_to receive(:perform_async) - post :play, namespace_id: project.namespace.to_param, project_id: project, id: protected_schedule.id + post :play, params: { namespace_id: project.namespace.to_param, project_id: project, id: protected_schedule.id } expect(response).to have_gitlab_http_status(404) end @@ -445,7 +437,7 @@ describe Projects::PipelineSchedulesController do project.add_developer(user) sign_in(user) - delete :destroy, namespace_id: project.namespace.to_param, project_id: project, id: pipeline_schedule.id + delete :destroy, params: { namespace_id: project.namespace.to_param, project_id: project, id: pipeline_schedule.id } end it 'does not delete the pipeline schedule' do @@ -461,7 +453,7 @@ describe Projects::PipelineSchedulesController do it 'destroys the pipeline schedule' do expect do - delete :destroy, namespace_id: project.namespace.to_param, project_id: project, id: pipeline_schedule.id + delete :destroy, params: { namespace_id: project.namespace.to_param, project_id: project, id: pipeline_schedule.id } end.to change { project.pipeline_schedules.count }.by(-1) expect(response).to have_gitlab_http_status(302) diff --git a/spec/controllers/projects/pipelines_controller_spec.rb b/spec/controllers/projects/pipelines_controller_spec.rb index 5c7415a318d..0bb3ef76a3b 100644 --- a/spec/controllers/projects/pipelines_controller_spec.rb +++ b/spec/controllers/projects/pipelines_controller_spec.rb @@ -119,8 +119,10 @@ describe Projects::PipelinesController do end def get_pipelines_index_json - get :index, namespace_id: project.namespace, - project_id: project, + get :index, params: { + namespace_id: project.namespace, + project_id: project + }, format: :json end @@ -185,7 +187,7 @@ describe Projects::PipelinesController do end def get_pipeline_json - get :show, namespace_id: project.namespace, project_id: project, id: pipeline, format: :json + get :show, params: { namespace_id: project.namespace, project_id: project, id: pipeline }, format: :json end def create_build(stage, stage_idx, name) @@ -240,12 +242,14 @@ describe Projects::PipelinesController do end def get_stage(name, params = {}) - get :stage, **params.merge( - namespace_id: project.namespace, - project_id: project, - id: pipeline.id, - stage: name, - format: :json) + get :stage, params: { +**params.merge( + namespace_id: project.namespace, + project_id: project, + id: pipeline.id, + stage: name, + format: :json) +} end end @@ -277,10 +281,12 @@ describe Projects::PipelinesController do end def get_stage_ajax(name) - get :stage_ajax, namespace_id: project.namespace, - project_id: project, - id: pipeline.id, - stage: name, + get :stage_ajax, params: { + namespace_id: project.namespace, + project_id: project, + id: pipeline.id, + stage: name + }, format: :json end end @@ -290,9 +296,11 @@ describe Projects::PipelinesController do let(:status) { pipeline.detailed_status(double('user')) } before do - get :status, namespace_id: project.namespace, - project_id: project, - id: pipeline.id, + get :status, params: { + namespace_id: project.namespace, + project_id: project, + id: pipeline.id + }, format: :json end @@ -310,9 +318,11 @@ describe Projects::PipelinesController do let!(:build) { create(:ci_build, :failed, pipeline: pipeline) } before do - post :retry, namespace_id: project.namespace, - project_id: project, - id: pipeline.id, + post :retry, params: { + namespace_id: project.namespace, + project_id: project, + id: pipeline.id + }, format: :json end @@ -337,9 +347,11 @@ describe Projects::PipelinesController do let!(:build) { create(:ci_build, :running, pipeline: pipeline) } before do - post :cancel, namespace_id: project.namespace, - project_id: project, - id: pipeline.id, + post :cancel, params: { + namespace_id: project.namespace, + project_id: project, + id: pipeline.id + }, format: :json end diff --git a/spec/controllers/projects/pipelines_settings_controller_spec.rb b/spec/controllers/projects/pipelines_settings_controller_spec.rb index b1ba9f74e38..269f105bed2 100644 --- a/spec/controllers/projects/pipelines_settings_controller_spec.rb +++ b/spec/controllers/projects/pipelines_settings_controller_spec.rb @@ -13,7 +13,7 @@ describe Projects::PipelinesSettingsController do describe 'GET show' do it 'redirects with 302 status code' do - get :show, namespace_id: project.namespace, project_id: project + get :show, params: { namespace_id: project.namespace, project_id: project } expect(response).to have_gitlab_http_status(302) end diff --git a/spec/controllers/projects/project_members_controller_spec.rb b/spec/controllers/projects/project_members_controller_spec.rb index 519af10d78c..3cc3fe69fba 100644 --- a/spec/controllers/projects/project_members_controller_spec.rb +++ b/spec/controllers/projects/project_members_controller_spec.rb @@ -6,7 +6,7 @@ describe Projects::ProjectMembersController do describe 'GET index' do it 'should have the project_members address with a 200 status code' do - get :index, namespace_id: project.namespace, project_id: project + get :index, params: { namespace_id: project.namespace, project_id: project } expect(response).to have_gitlab_http_status(200) end @@ -25,10 +25,12 @@ describe Projects::ProjectMembersController do end it 'returns 404' do - post :create, namespace_id: project.namespace, - project_id: project, - user_ids: project_user.id, - access_level: Gitlab::Access::GUEST + post :create, params: { + namespace_id: project.namespace, + project_id: project, + user_ids: project_user.id, + access_level: Gitlab::Access::GUEST + } expect(response).to have_gitlab_http_status(404) expect(project.users).not_to include project_user @@ -43,10 +45,12 @@ describe Projects::ProjectMembersController do it 'adds user to members' do expect_any_instance_of(Members::CreateService).to receive(:execute).and_return(status: :success) - post :create, namespace_id: project.namespace, - project_id: project, - user_ids: project_user.id, - access_level: Gitlab::Access::GUEST + post :create, params: { + namespace_id: project.namespace, + project_id: project, + user_ids: project_user.id, + access_level: Gitlab::Access::GUEST + } expect(response).to set_flash.to 'Users were successfully added.' expect(response).to redirect_to(project_project_members_path(project)) @@ -55,10 +59,12 @@ describe Projects::ProjectMembersController do it 'adds no user to members' do expect_any_instance_of(Members::CreateService).to receive(:execute).and_return(status: :failure, message: 'Message') - post :create, namespace_id: project.namespace, - project_id: project, - user_ids: '', - access_level: Gitlab::Access::GUEST + post :create, params: { + namespace_id: project.namespace, + project_id: project, + user_ids: '', + access_level: Gitlab::Access::GUEST + } expect(response).to set_flash.to 'Message' expect(response).to redirect_to(project_project_members_path(project)) @@ -76,10 +82,12 @@ describe Projects::ProjectMembersController do Gitlab::Access.options.each do |label, value| it "can change the access level to #{label}" do - xhr :put, :update, project_member: { access_level: value }, - namespace_id: project.namespace, - project_id: project, - id: requester + put :update, params: { + project_member: { access_level: value }, + namespace_id: project.namespace, + project_id: project, + id: requester + }, xhr: true expect(requester.reload.human_access).to eq(label) end @@ -95,9 +103,11 @@ describe Projects::ProjectMembersController do context 'when member is not found' do it 'returns 404' do - delete :destroy, namespace_id: project.namespace, - project_id: project, - id: 42 + delete :destroy, params: { + namespace_id: project.namespace, + project_id: project, + id: 42 + } expect(response).to have_gitlab_http_status(404) end @@ -110,9 +120,11 @@ describe Projects::ProjectMembersController do end it 'returns 404' do - delete :destroy, namespace_id: project.namespace, - project_id: project, - id: member + delete :destroy, params: { + namespace_id: project.namespace, + project_id: project, + id: member + } expect(response).to have_gitlab_http_status(404) expect(project.members).to include member @@ -125,9 +137,11 @@ describe Projects::ProjectMembersController do end it '[HTML] removes user from members' do - delete :destroy, namespace_id: project.namespace, - project_id: project, - id: member + delete :destroy, params: { + namespace_id: project.namespace, + project_id: project, + id: member + } expect(response).to redirect_to( project_project_members_path(project) @@ -136,9 +150,11 @@ describe Projects::ProjectMembersController do end it '[JS] removes user from members' do - xhr :delete, :destroy, namespace_id: project.namespace, - project_id: project, - id: member + delete :destroy, params: { + namespace_id: project.namespace, + project_id: project, + id: member + }, xhr: true expect(response).to be_success expect(project.members).not_to include member @@ -154,8 +170,10 @@ describe Projects::ProjectMembersController do context 'when member is not found' do it 'returns 404' do - delete :leave, namespace_id: project.namespace, - project_id: project + delete :leave, params: { + namespace_id: project.namespace, + project_id: project + } expect(response).to have_gitlab_http_status(404) end @@ -168,8 +186,10 @@ describe Projects::ProjectMembersController do end it 'removes user from members' do - delete :leave, namespace_id: project.namespace, - project_id: project + delete :leave, params: { + namespace_id: project.namespace, + project_id: project + } expect(response).to set_flash.to "You left the \"#{project.human_name}\" project." expect(response).to redirect_to(dashboard_projects_path) @@ -185,8 +205,10 @@ describe Projects::ProjectMembersController do end it 'cannot remove themselves from the project' do - delete :leave, namespace_id: project.namespace, - project_id: project + delete :leave, params: { + namespace_id: project.namespace, + project_id: project + } expect(response).to have_gitlab_http_status(403) end @@ -198,8 +220,10 @@ describe Projects::ProjectMembersController do end it 'removes user from members' do - delete :leave, namespace_id: project.namespace, - project_id: project + delete :leave, params: { + namespace_id: project.namespace, + project_id: project + } expect(response).to set_flash.to 'Your access request to the project has been withdrawn.' expect(response).to redirect_to(project_path(project)) @@ -216,8 +240,10 @@ describe Projects::ProjectMembersController do end it 'creates a new ProjectMember that is not a team member' do - post :request_access, namespace_id: project.namespace, - project_id: project + post :request_access, params: { + namespace_id: project.namespace, + project_id: project + } expect(response).to set_flash.to 'Your request for access has been queued for review.' expect(response).to redirect_to( @@ -237,9 +263,11 @@ describe Projects::ProjectMembersController do context 'when member is not found' do it 'returns 404' do - post :approve_access_request, namespace_id: project.namespace, - project_id: project, - id: 42 + post :approve_access_request, params: { + namespace_id: project.namespace, + project_id: project, + id: 42 + } expect(response).to have_gitlab_http_status(404) end @@ -252,9 +280,11 @@ describe Projects::ProjectMembersController do end it 'returns 404' do - post :approve_access_request, namespace_id: project.namespace, - project_id: project, - id: member + post :approve_access_request, params: { + namespace_id: project.namespace, + project_id: project, + id: member + } expect(response).to have_gitlab_http_status(404) expect(project.members).not_to include member @@ -267,9 +297,11 @@ describe Projects::ProjectMembersController do end it 'adds user to members' do - post :approve_access_request, namespace_id: project.namespace, - project_id: project, - id: member + post :approve_access_request, params: { + namespace_id: project.namespace, + project_id: project, + id: member + } expect(response).to redirect_to( project_project_members_path(project) @@ -292,9 +324,11 @@ describe Projects::ProjectMembersController do shared_context 'import applied' do before do - post(:apply_import, namespace_id: project.namespace, - project_id: project, - source_project_id: another_project.id) + post(:apply_import, params: { + namespace_id: project.namespace, + project_id: project, + source_project_id: another_project.id + }) end end @@ -338,10 +372,12 @@ describe Projects::ProjectMembersController do it 'does not create a member' do expect do - post :create, user_ids: stranger.id, - namespace_id: project.namespace, - access_level: Member::OWNER, - project_id: project + post :create, params: { + user_ids: stranger.id, + namespace_id: project.namespace, + access_level: Member::OWNER, + project_id: project + } end.to change { project.members.count }.by(0) end end @@ -354,10 +390,12 @@ describe Projects::ProjectMembersController do it 'creates a member' do expect do - post :create, user_ids: stranger.id, - namespace_id: project.namespace, - access_level: Member::MAINTAINER, - project_id: project + post :create, params: { + user_ids: stranger.id, + namespace_id: project.namespace, + access_level: Member::MAINTAINER, + project_id: project + } end.to change { project.members.count }.by(1) end end diff --git a/spec/controllers/projects/prometheus/metrics_controller_spec.rb b/spec/controllers/projects/prometheus/metrics_controller_spec.rb index 5c56a712245..635763ce1d3 100644 --- a/spec/controllers/projects/prometheus/metrics_controller_spec.rb +++ b/spec/controllers/projects/prometheus/metrics_controller_spec.rb @@ -24,7 +24,7 @@ describe Projects::Prometheus::MetricsController do end it 'returns no content response' do - get :active_common, project_params(format: :json) + get :active_common, params: project_params(format: :json) expect(response).to have_gitlab_http_status(204) end @@ -38,7 +38,7 @@ describe Projects::Prometheus::MetricsController do end it 'returns no content response' do - get :active_common, project_params(format: :json) + get :active_common, params: project_params(format: :json) expect(response).to have_gitlab_http_status(200) expect(json_response).to eq(sample_response.deep_stringify_keys) @@ -47,7 +47,7 @@ describe Projects::Prometheus::MetricsController do context 'when requesting non json response' do it 'returns not found response' do - get :active_common, project_params + get :active_common, params: project_params expect(response).to have_gitlab_http_status(404) end @@ -62,7 +62,7 @@ describe Projects::Prometheus::MetricsController do allow(controller).to receive(:prometheus_adapter).and_return(prometheus_adapter) allow(prometheus_adapter).to receive(:query).with(:matched_metrics).and_return({}) - get :active_common, project_params(format: :json) + get :active_common, params: project_params(format: :json) expect(response).to have_gitlab_http_status(404) end @@ -70,7 +70,7 @@ describe Projects::Prometheus::MetricsController do context 'when prometheus_adapter is disabled' do it 'renders 404' do - get :active_common, project_params(format: :json) + get :active_common, params: project_params(format: :json) expect(response).to have_gitlab_http_status(404) end diff --git a/spec/controllers/projects/protected_branches_controller_spec.rb b/spec/controllers/projects/protected_branches_controller_spec.rb index ac812707e74..483d3bbc37c 100644 --- a/spec/controllers/projects/protected_branches_controller_spec.rb +++ b/spec/controllers/projects/protected_branches_controller_spec.rb @@ -15,7 +15,7 @@ describe Projects::ProtectedBranchesController do let(:project) { create(:project_empty_repo, :public) } it "redirects empty repo to projects page" do - get(:index, namespace_id: project.namespace.to_param, project_id: project) + get(:index, params: { namespace_id: project.namespace.to_param, project_id: project }) end end @@ -33,7 +33,7 @@ describe Projects::ProtectedBranchesController do it 'creates the protected branch rule' do expect do - post(:create, project_params.merge(protected_branch: create_params)) + post(:create, params: project_params.merge(protected_branch: create_params)) end.to change(ProtectedBranch, :count).by(1) end @@ -44,7 +44,7 @@ describe Projects::ProtectedBranchesController do end it "prevents creation of the protected branch rule" do - post(:create, project_params.merge(protected_branch: create_params)) + post(:create, params: project_params.merge(protected_branch: create_params)) expect(ProtectedBranch.count).to eq 0 end @@ -59,7 +59,7 @@ describe Projects::ProtectedBranchesController do end it 'updates the protected branch rule' do - put(:update, base_params.merge(protected_branch: update_params)) + put(:update, params: base_params.merge(protected_branch: update_params)) expect(protected_branch.reload.name).to eq('new_name') expect(json_response["name"]).to eq('new_name') @@ -74,7 +74,7 @@ describe Projects::ProtectedBranchesController do it "prevents update of the protected branch rule" do old_name = protected_branch.name - put(:update, base_params.merge(protected_branch: update_params)) + put(:update, params: base_params.merge(protected_branch: update_params)) expect(protected_branch.reload.name).to eq(old_name) end @@ -87,7 +87,7 @@ describe Projects::ProtectedBranchesController do end it "deletes the protected branch rule" do - delete(:destroy, base_params) + delete(:destroy, params: base_params) expect { ProtectedBranch.find(protected_branch.id) }.to raise_error(ActiveRecord::RecordNotFound) end @@ -99,7 +99,7 @@ describe Projects::ProtectedBranchesController do end it "prevents deletion of the protected branch rule" do - delete(:destroy, base_params) + delete(:destroy, params: base_params) expect(response.status).to eq(403) end diff --git a/spec/controllers/projects/protected_tags_controller_spec.rb b/spec/controllers/projects/protected_tags_controller_spec.rb index 20440c5a5d5..1553e081dee 100644 --- a/spec/controllers/projects/protected_tags_controller_spec.rb +++ b/spec/controllers/projects/protected_tags_controller_spec.rb @@ -5,7 +5,7 @@ describe Projects::ProtectedTagsController do let(:project) { create(:project_empty_repo, :public) } it "redirects empty repo to projects page" do - get(:index, namespace_id: project.namespace.to_param, project_id: project) + get(:index, params: { namespace_id: project.namespace.to_param, project_id: project }) end end @@ -20,7 +20,7 @@ describe Projects::ProtectedTagsController do end it "deletes the protected tag" do - delete(:destroy, namespace_id: project.namespace.to_param, project_id: project, id: protected_tag.id) + delete(:destroy, params: { namespace_id: project.namespace.to_param, project_id: project, id: protected_tag.id }) expect { ProtectedTag.find(protected_tag.id) }.to raise_error(ActiveRecord::RecordNotFound) end diff --git a/spec/controllers/projects/raw_controller_spec.rb b/spec/controllers/projects/raw_controller_spec.rb index d3cd15fbcd7..cffdf30da6b 100644 --- a/spec/controllers/projects/raw_controller_spec.rb +++ b/spec/controllers/projects/raw_controller_spec.rb @@ -6,82 +6,37 @@ describe Projects::RawController do describe 'GET #show' do subject do get(:show, - namespace_id: project.namespace, - project_id: project, - id: filepath) + params: { + namespace_id: project.namespace, + project_id: project, + id: filepath + }) end context 'regular filename' do let(:filepath) { 'master/README.md' } - context 'when feature flag workhorse_set_content_type is' do - before do - stub_feature_flags(workhorse_set_content_type: flag_value) + it 'delivers ASCII file' do + subject - subject - end - - context 'enabled' do - let(:flag_value) { true } - - it 'delivers ASCII file' do - expect(response).to have_gitlab_http_status(200) - expect(response.header['Content-Type']).to eq('text/plain; charset=utf-8') - expect(response.header['Content-Disposition']).to eq('inline') - expect(response.header[Gitlab::Workhorse::DETECT_HEADER]).to eq "true" - expect(response.header[Gitlab::Workhorse::SEND_DATA_HEADER]).to start_with('git-blob:') - end - end - - context 'disabled' do - let(:flag_value) { false } - - it 'delivers ASCII file' do - expect(response).to have_gitlab_http_status(200) - expect(response.header['Content-Type']).to eq('text/plain; charset=utf-8') - expect(response.header['Content-Disposition']).to eq('inline') - expect(response.header[Gitlab::Workhorse::DETECT_HEADER]).to eq nil - expect(response.header[Gitlab::Workhorse::SEND_DATA_HEADER]).to start_with('git-blob:') - end - end + expect(response).to have_gitlab_http_status(200) + expect(response.header['Content-Type']).to eq('text/plain; charset=utf-8') + expect(response.header['Content-Disposition']).to eq('inline') + expect(response.header[Gitlab::Workhorse::DETECT_HEADER]).to eq "true" + expect(response.header[Gitlab::Workhorse::SEND_DATA_HEADER]).to start_with('git-blob:') end end context 'image header' do let(:filepath) { 'master/files/images/6049019_460s.jpg' } - context 'when feature flag workhorse_set_content_type is' do - before do - stub_feature_flags(workhorse_set_content_type: flag_value) - end - - context 'enabled' do - let(:flag_value) { true } - - it 'leaves image content disposition' do - subject - - expect(response).to have_gitlab_http_status(200) - expect(response.header['Content-Type']).to eq('image/jpeg') - expect(response.header['Content-Disposition']).to eq('inline') - expect(response.header[Gitlab::Workhorse::DETECT_HEADER]).to eq "true" - expect(response.header[Gitlab::Workhorse::SEND_DATA_HEADER]).to start_with('git-blob:') - end - end - - context 'disabled' do - let(:flag_value) { false } - - it 'sets image content type header' do - subject + it 'leaves image content disposition' do + subject - expect(response).to have_gitlab_http_status(200) - expect(response.header['Content-Type']).to eq('image/jpeg') - expect(response.header['Content-Disposition']).to eq('inline') - expect(response.header[Gitlab::Workhorse::DETECT_HEADER]).to eq nil - expect(response.header[Gitlab::Workhorse::SEND_DATA_HEADER]).to start_with('git-blob:') - end - end + expect(response).to have_gitlab_http_status(200) + expect(response.header['Content-Disposition']).to eq('inline') + expect(response.header[Gitlab::Workhorse::DETECT_HEADER]).to eq "true" + expect(response.header[Gitlab::Workhorse::SEND_DATA_HEADER]).to start_with('git-blob:') end end diff --git a/spec/controllers/projects/refs_controller_spec.rb b/spec/controllers/projects/refs_controller_spec.rb index ceaffd92623..62f2af947e4 100644 --- a/spec/controllers/projects/refs_controller_spec.rb +++ b/spec/controllers/projects/refs_controller_spec.rb @@ -12,21 +12,23 @@ describe Projects::RefsController do describe 'GET #logs_tree' do def default_get(format = :html) get :logs_tree, - namespace_id: project.namespace.to_param, - project_id: project, - id: 'master', - path: 'foo/bar/baz.html', + params: { + namespace_id: project.namespace.to_param, + project_id: project, + id: 'master', + path: 'foo/bar/baz.html' + }, format: format end def xhr_get(format = :html) - xhr :get, - :logs_tree, - namespace_id: project.namespace.to_param, - project_id: project, - id: 'master', - path: 'foo/bar/baz.html', - format: format + get :logs_tree, params: { + namespace_id: project.namespace.to_param, + project_id: project, + id: 'master', + path: 'foo/bar/baz.html', + format: format + }, xhr: true end it 'never throws MissingTemplate' do diff --git a/spec/controllers/projects/registry/repositories_controller_spec.rb b/spec/controllers/projects/registry/repositories_controller_spec.rb index d11e42b411b..eca187af33d 100644 --- a/spec/controllers/projects/registry/repositories_controller_spec.rb +++ b/spec/controllers/projects/registry/repositories_controller_spec.rb @@ -111,15 +111,19 @@ describe Projects::Registry::RepositoriesController do end def go_to_index(format: :html) - get :index, namespace_id: project.namespace, - project_id: project, + get :index, params: { + namespace_id: project.namespace, + project_id: project + }, format: format end def delete_repository(repository) - delete :destroy, namespace_id: project.namespace, - project_id: project, - id: repository, + delete :destroy, params: { + namespace_id: project.namespace, + project_id: project, + id: repository + }, format: :json end end diff --git a/spec/controllers/projects/registry/tags_controller_spec.rb b/spec/controllers/projects/registry/tags_controller_spec.rb index 7fee8fd44ff..ed0197afcfc 100644 --- a/spec/controllers/projects/registry/tags_controller_spec.rb +++ b/spec/controllers/projects/registry/tags_controller_spec.rb @@ -65,9 +65,11 @@ describe Projects::Registry::TagsController do private def get_tags - get :index, namespace_id: project.namespace, - project_id: project, - repository_id: repository, + get :index, params: { + namespace_id: project.namespace, + project_id: project, + repository_id: repository + }, format: :json end end @@ -100,10 +102,12 @@ describe Projects::Registry::TagsController do private def destroy_tag(name) - post :destroy, namespace_id: project.namespace, - project_id: project, - repository_id: repository, - id: name, + post :destroy, params: { + namespace_id: project.namespace, + project_id: project, + repository_id: repository, + id: name + }, format: :json end end diff --git a/spec/controllers/projects/releases_controller_spec.rb b/spec/controllers/projects/releases_controller_spec.rb index 20a6beb3df8..f170a2ab613 100644 --- a/spec/controllers/projects/releases_controller_spec.rb +++ b/spec/controllers/projects/releases_controller_spec.rb @@ -1,55 +1,65 @@ +# frozen_string_literal: true + require 'spec_helper' describe Projects::ReleasesController do - let!(:project) { create(:project, :repository) } + let!(:project) { create(:project, :repository, :public) } let!(:user) { create(:user) } - let!(:release) { create(:release, project: project) } - let!(:tag) { release.tag } before do - project.add_developer(user) - sign_in(user) + stub_feature_flags(releases_page: true) end - describe 'GET #edit' do - it 'initializes a new release' do - tag_id = release.tag - project.releases.destroy_all # rubocop: disable DestroyAll - - get :edit, namespace_id: project.namespace, project_id: project, tag_id: tag_id + describe 'GET #index' do + it 'renders a 200' do + get_index - release = assigns(:release) - expect(release).not_to be_nil - expect(release).not_to be_persisted + expect(response.status).to eq(200) end - it 'retrieves an existing release' do - get :edit, namespace_id: project.namespace, project_id: project, tag_id: release.tag + context 'when the project is private' do + let!(:project) { create(:project, :repository, :private) } - release = assigns(:release) - expect(release).not_to be_nil - expect(release).to be_persisted - end - end + it 'renders a 302' do + get_index + + expect(response.status).to eq(302) + end + + it 'renders a 200 for a logged in developer' do + project.add_developer(user) + sign_in(user) - describe 'PUT #update' do - it 'updates release note description' do - update_release('description updated') + get_index - release = project.releases.find_by_tag(tag) - expect(release.description).to eq("description updated") + expect(response.status).to eq(200) + end + + it 'renders a 404 when logged in but not in the project' do + sign_in(user) + + get_index + + expect(response.status).to eq(404) + end end - it 'deletes release note when description is null' do - expect { update_release('') }.to change(project.releases, :count).by(-1) + context 'when releases_page feature flag is disabled' do + before do + stub_feature_flags(releases_page: false) + end + + it 'renders a 404' do + get_index + + expect(response.status).to eq(404) + end end end - def update_release(description) - put :update, - namespace_id: project.namespace.to_param, - project_id: project, - tag_id: release.tag, - release: { description: description } + private + + def get_index + get :index, params: { namespace_id: project.namespace, project_id: project } end end diff --git a/spec/controllers/projects/repositories_controller_spec.rb b/spec/controllers/projects/repositories_controller_spec.rb index a102a3a3c8c..5f4f6f8558f 100644 --- a/spec/controllers/projects/repositories_controller_spec.rb +++ b/spec/controllers/projects/repositories_controller_spec.rb @@ -6,7 +6,7 @@ describe Projects::RepositoriesController do describe "GET archive" do context 'as a guest' do it 'responds with redirect in correct format' do - get :archive, namespace_id: project.namespace, project_id: project, id: "master", format: "zip" + get :archive, params: { namespace_id: project.namespace, project_id: project, id: "master" }, format: "zip" expect(response.header["Content-Type"]).to start_with('text/html') expect(response).to be_redirect @@ -22,26 +22,26 @@ describe Projects::RepositoriesController do end it "uses Gitlab::Workhorse" do - get :archive, namespace_id: project.namespace, project_id: project, id: "master", format: "zip" + get :archive, params: { namespace_id: project.namespace, project_id: project, id: "master" }, format: "zip" expect(response.header[Gitlab::Workhorse::SEND_DATA_HEADER]).to start_with("git-archive:") end it 'responds with redirect to the short name archive if fully qualified' do - get :archive, namespace_id: project.namespace, project_id: project, id: "master/#{project.path}-master", format: "zip" + get :archive, params: { namespace_id: project.namespace, project_id: project, id: "master/#{project.path}-master" }, format: "zip" expect(assigns(:ref)).to eq("master") expect(response.header[Gitlab::Workhorse::SEND_DATA_HEADER]).to start_with("git-archive:") end it 'handles legacy queries with no ref' do - get :archive, namespace_id: project.namespace, project_id: project, format: "zip" + get :archive, params: { namespace_id: project.namespace, project_id: project }, format: "zip" expect(response.header[Gitlab::Workhorse::SEND_DATA_HEADER]).to start_with("git-archive:") end it 'handles legacy queries with the ref specified as ref in params' do - get :archive, namespace_id: project.namespace, project_id: project, ref: 'feature', format: 'zip' + get :archive, params: { namespace_id: project.namespace, project_id: project, ref: 'feature' }, format: 'zip' expect(response).to have_gitlab_http_status(200) expect(assigns(:ref)).to eq('feature') @@ -49,7 +49,7 @@ describe Projects::RepositoriesController do end it 'handles legacy queries with the ref specified as id in params' do - get :archive, namespace_id: project.namespace, project_id: project, id: 'feature', format: 'zip' + get :archive, params: { namespace_id: project.namespace, project_id: project, id: 'feature' }, format: 'zip' expect(response).to have_gitlab_http_status(200) expect(assigns(:ref)).to eq('feature') @@ -57,7 +57,7 @@ describe Projects::RepositoriesController do end it 'prioritizes the id param over the ref param when both are specified' do - get :archive, namespace_id: project.namespace, project_id: project, id: 'feature', ref: 'feature_conflict', format: 'zip' + get :archive, params: { namespace_id: project.namespace, project_id: project, id: 'feature', ref: 'feature_conflict' }, format: 'zip' expect(response).to have_gitlab_http_status(200) expect(assigns(:ref)).to eq('feature') @@ -70,7 +70,7 @@ describe Projects::RepositoriesController do end it "renders Not Found" do - get :archive, namespace_id: project.namespace, project_id: project, id: "master", format: "zip" + get :archive, params: { namespace_id: project.namespace, project_id: project, id: "master" }, format: "zip" expect(response).to have_gitlab_http_status(404) end diff --git a/spec/controllers/projects/runners_controller_spec.rb b/spec/controllers/projects/runners_controller_spec.rb index b1e0b496ede..0baaa4e7192 100644 --- a/spec/controllers/projects/runners_controller_spec.rb +++ b/spec/controllers/projects/runners_controller_spec.rb @@ -23,7 +23,7 @@ describe Projects::RunnersController do new_desc = runner.description.swapcase expect do - post :update, params.merge(runner: { description: new_desc } ) + post :update, params: params.merge(runner: { description: new_desc } ) end.to change { runner.ensure_runner_queue_value } runner.reload @@ -35,7 +35,7 @@ describe Projects::RunnersController do describe '#destroy' do it 'destroys the runner' do - delete :destroy, params + delete :destroy, params: params expect(response).to have_gitlab_http_status(302) expect(Ci::Runner.find_by(id: runner.id)).to be_nil @@ -47,7 +47,7 @@ describe Projects::RunnersController do runner.update(active: false) expect do - post :resume, params + post :resume, params: params end.to change { runner.ensure_runner_queue_value } runner.reload @@ -62,7 +62,7 @@ describe Projects::RunnersController do runner.update(active: true) expect do - post :pause, params + post :pause, params: params end.to change { runner.ensure_runner_queue_value } runner.reload diff --git a/spec/controllers/projects/serverless/functions_controller_spec.rb b/spec/controllers/projects/serverless/functions_controller_spec.rb index 284b582b1f5..a9759c4fbd8 100644 --- a/spec/controllers/projects/serverless/functions_controller_spec.rb +++ b/spec/controllers/projects/serverless/functions_controller_spec.rb @@ -32,13 +32,13 @@ describe Projects::Serverless::FunctionsController do describe 'GET #index' do context 'empty cache' do it 'has no data' do - get :index, params({ format: :json }) + get :index, params: params({ format: :json }) expect(response).to have_gitlab_http_status(204) end it 'renders an html page' do - get :index, params + get :index, params: params expect(response).to have_gitlab_http_status(200) end @@ -51,7 +51,7 @@ describe Projects::Serverless::FunctionsController do end it 'has data' do - get :index, params({ format: :json }) + get :index, params: params({ format: :json }) expect(response).to have_gitlab_http_status(200) @@ -64,7 +64,7 @@ describe Projects::Serverless::FunctionsController do end it 'has data in html' do - get :index, params + get :index, params: params expect(response).to have_gitlab_http_status(200) end diff --git a/spec/controllers/projects/services_controller_spec.rb b/spec/controllers/projects/services_controller_spec.rb index 45cea8c1351..4a5d2bdecb7 100644 --- a/spec/controllers/projects/services_controller_spec.rb +++ b/spec/controllers/projects/services_controller_spec.rb @@ -3,9 +3,8 @@ require 'spec_helper' describe Projects::ServicesController do let(:project) { create(:project, :repository) } let(:user) { create(:user) } - let(:service) { create(:hipchat_service, project: project) } - let(:hipchat_client) { { '#room' => double(send: true) } } - let(:service_params) { { token: 'hipchat_token_p', room: '#room' } } + let(:service) { create(:jira_service, project: project) } + let(:service_params) { { username: 'username', password: 'password', url: 'http://example.com' } } before do sign_in(user) @@ -17,20 +16,20 @@ describe Projects::ServicesController do it 'renders 404' do allow_any_instance_of(Service).to receive(:can_test?).and_return(false) - put :test, namespace_id: project.namespace, project_id: project, id: service.to_param + put :test, params: { namespace_id: project.namespace, project_id: project, id: service.to_param } expect(response).to have_gitlab_http_status(404) end end context 'when validations fail' do - let(:service_params) { { active: 'true', token: '' } } + let(:service_params) { { active: 'true', url: '' } } it 'returns error messages in JSON response' do - put :test, namespace_id: project.namespace, project_id: project, id: :hipchat, service: service_params + put :test, params: { namespace_id: project.namespace, project_id: project, id: service.to_param, service: service_params } expect(json_response['message']).to eq "Validations failed." - expect(json_response['service_response']).to eq "Token can't be blank" + expect(json_response['service_response']).to include "Url can't be blank" expect(response).to have_gitlab_http_status(200) end end @@ -45,25 +44,27 @@ describe Projects::ServicesController do it 'returns success' do allow_any_instance_of(MicrosoftTeams::Notifier).to receive(:ping).and_return(true) - put :test, namespace_id: project.namespace, project_id: project, id: service.to_param + put :test, params: { namespace_id: project.namespace, project_id: project, id: service.to_param } expect(response.status).to eq(200) end end it 'returns success' do - expect(HipChat::Client).to receive(:new).with('hipchat_token_p', anything).and_return(hipchat_client) + stub_request(:get, 'http://example.com/rest/api/2/serverInfo') + .to_return(status: 200, body: '{}') - put :test, namespace_id: project.namespace, project_id: project, id: service.to_param, service: service_params + put :test, params: { namespace_id: project.namespace, project_id: project, id: service.to_param, service: service_params } expect(response.status).to eq(200) end end it 'returns success' do - expect(HipChat::Client).to receive(:new).with('hipchat_token_p', anything).and_return(hipchat_client) + stub_request(:get, 'http://example.com/rest/api/2/serverInfo') + .to_return(status: 200, body: '{}') - put :test, namespace_id: project.namespace, project_id: project, id: service.to_param, service: service_params + put :test, params: { namespace_id: project.namespace, project_id: project, id: service.to_param, service: service_params } expect(response.status).to eq(200) end @@ -76,33 +77,44 @@ describe Projects::ServicesController do it 'persist the object' do do_put + expect(response).to have_gitlab_http_status(200) + expect(json_response).to be_empty expect(BuildkiteService.first).to be_present end it 'creates the ServiceHook object' do do_put + expect(response).to have_gitlab_http_status(200) + expect(json_response).to be_empty expect(BuildkiteService.first.service_hook).to be_present end def do_put - put :test, namespace_id: project.namespace, - project_id: project, - id: 'buildkite', - service: { 'active' => '1', 'push_events' => '1', token: 'token', 'project_url' => 'http://test.com' } + put :test, params: { + namespace_id: project.namespace, + project_id: project, + id: 'buildkite', + service: { 'active' => '1', 'push_events' => '1', token: 'token', 'project_url' => 'http://test.com' } + } end end end context 'failure' do it 'returns success status code and the error message' do - expect(HipChat::Client).to receive(:new).with('hipchat_token_p', anything).and_raise('Bad test') + stub_request(:get, 'http://example.com/rest/api/2/serverInfo') + .to_return(status: 404) - put :test, namespace_id: project.namespace, project_id: project, id: service.to_param, service: service_params + put :test, params: { namespace_id: project.namespace, project_id: project, id: service.to_param, service: service_params } - expect(response.status).to eq(200) - expect(JSON.parse(response.body)) - .to eq('error' => true, 'message' => 'Test failed.', 'service_response' => 'Bad test', 'test_failed' => true) + expect(response).to have_gitlab_http_status(200) + expect(json_response).to eq( + 'error' => true, + 'message' => 'Test failed.', + 'service_response' => '', + 'test_failed' => true + ) end end end @@ -111,19 +123,19 @@ describe Projects::ServicesController do context 'when param `active` is set to true' do it 'activates the service and redirects to integrations paths' do put :update, - namespace_id: project.namespace, project_id: project, id: service.to_param, service: { active: true } + params: { namespace_id: project.namespace, project_id: project, id: service.to_param, service: { active: true } } expect(response).to redirect_to(project_settings_integrations_path(project)) - expect(flash[:notice]).to eq 'HipChat activated.' + expect(flash[:notice]).to eq 'JIRA activated.' end end context 'when param `active` is set to false' do it 'does not activate the service but saves the settings' do put :update, - namespace_id: project.namespace, project_id: project, id: service.to_param, service: { active: false } + params: { namespace_id: project.namespace, project_id: project, id: service.to_param, service: { active: false } } - expect(flash[:notice]).to eq 'HipChat settings saved, but not activated.' + expect(flash[:notice]).to eq 'JIRA settings saved, but not activated.' end end @@ -132,7 +144,7 @@ describe Projects::ServicesController do before do put :update, - namespace_id: project.namespace, project_id: project, id: service.to_param, service: { namespace: 'updated_namespace' } + params: { namespace_id: project.namespace, project_id: project, id: service.to_param, service: { namespace: 'updated_namespace' } } end it 'should not update the service' do @@ -144,7 +156,7 @@ describe Projects::ServicesController do describe "GET #edit" do before do - get :edit, namespace_id: project.namespace, project_id: project, id: service_id + get :edit, params: { namespace_id: project.namespace, project_id: project, id: service_id } end context 'with approved services' do diff --git a/spec/controllers/projects/settings/ci_cd_controller_spec.rb b/spec/controllers/projects/settings/ci_cd_controller_spec.rb index 4629929f9af..41cc0607cee 100644 --- a/spec/controllers/projects/settings/ci_cd_controller_spec.rb +++ b/spec/controllers/projects/settings/ci_cd_controller_spec.rb @@ -12,7 +12,7 @@ describe Projects::Settings::CiCdController do describe 'GET show' do it 'renders show with 200 status code' do - get :show, namespace_id: project.namespace, project_id: project + get :show, params: { namespace_id: project.namespace, project_id: project } expect(response).to have_gitlab_http_status(200) expect(response).to render_template(:show) @@ -29,7 +29,7 @@ describe Projects::Settings::CiCdController do it 'sets assignable project runners only' do group.add_maintainer(user) - get :show, namespace_id: project.namespace, project_id: project + get :show, params: { namespace_id: project.namespace, project_id: project } expect(assigns(:assignable_runners)).to contain_exactly(project_runner) end @@ -45,7 +45,7 @@ describe Projects::Settings::CiCdController do allow(ResetProjectCacheService).to receive_message_chain(:new, :execute).and_return(true) end - subject { post :reset_cache, namespace_id: project.namespace, project_id: project, format: :json } + subject { post :reset_cache, params: { namespace_id: project.namespace, project_id: project }, format: :json } it 'calls reset project cache service' do expect(ResetProjectCacheService).to receive_message_chain(:new, :execute) @@ -75,7 +75,7 @@ describe Projects::Settings::CiCdController do end describe 'PUT #reset_registration_token' do - subject { put :reset_registration_token, namespace_id: project.namespace, project_id: project } + subject { put :reset_registration_token, params: { namespace_id: project.namespace, project_id: project } } it 'resets runner registration token' do expect { subject }.to change { project.reload.runners_token } end @@ -92,9 +92,11 @@ describe Projects::Settings::CiCdController do subject do patch :update, - namespace_id: project.namespace.to_param, - project_id: project, - project: params + params: { + namespace_id: project.namespace.to_param, + project_id: project, + project: params + } end it 'redirects to the settings page' do diff --git a/spec/controllers/projects/settings/integrations_controller_spec.rb b/spec/controllers/projects/settings/integrations_controller_spec.rb index a2484c04c7a..8624eb4d1a0 100644 --- a/spec/controllers/projects/settings/integrations_controller_spec.rb +++ b/spec/controllers/projects/settings/integrations_controller_spec.rb @@ -11,7 +11,7 @@ describe Projects::Settings::IntegrationsController do describe 'GET show' do it 'renders show with 200 status code' do - get :show, namespace_id: project.namespace, project_id: project + get :show, params: { namespace_id: project.namespace, project_id: project } expect(response).to have_gitlab_http_status(200) expect(response).to render_template(:show) diff --git a/spec/controllers/projects/settings/repository_controller_spec.rb b/spec/controllers/projects/settings/repository_controller_spec.rb index 70f79a47e63..638cce60a25 100644 --- a/spec/controllers/projects/settings/repository_controller_spec.rb +++ b/spec/controllers/projects/settings/repository_controller_spec.rb @@ -11,7 +11,7 @@ describe Projects::Settings::RepositoryController do describe 'GET show' do it 'renders show with 200 status code' do - get :show, namespace_id: project.namespace, project_id: project + get :show, params: { namespace_id: project.namespace, project_id: project } expect(response).to have_gitlab_http_status(200) expect(response).to render_template(:show) @@ -19,35 +19,15 @@ describe Projects::Settings::RepositoryController do end describe 'PUT cleanup' do - before do - allow(RepositoryCleanupWorker).to receive(:perform_async) - end - - def do_put! - object_map = fixture_file_upload('spec/fixtures/bfg_object_map.txt') - - put :cleanup, namespace_id: project.namespace, project_id: project, project: { object_map: object_map } - end + let(:object_map) { fixture_file_upload('spec/fixtures/bfg_object_map.txt') } - context 'feature enabled' do - it 'enqueues a RepositoryCleanupWorker' do - stub_feature_flags(project_cleanup: true) - - do_put! - - expect(response).to redirect_to project_settings_repository_path(project) - expect(RepositoryCleanupWorker).to have_received(:perform_async).once - end - end - - context 'feature disabled' do - it 'shows a 404 error' do - stub_feature_flags(project_cleanup: false) + it 'enqueues a RepositoryCleanupWorker' do + allow(RepositoryCleanupWorker).to receive(:perform_async) - do_put! + put :cleanup, params: { namespace_id: project.namespace, project_id: project, project: { object_map: object_map } } - expect(response).to have_gitlab_http_status(404) - end + expect(response).to redirect_to project_settings_repository_path(project) + expect(RepositoryCleanupWorker).to have_received(:perform_async).once end end end diff --git a/spec/controllers/projects/snippets_controller_spec.rb b/spec/controllers/projects/snippets_controller_spec.rb index 9c383bd7628..75c9839dd9b 100644 --- a/spec/controllers/projects/snippets_controller_spec.rb +++ b/spec/controllers/projects/snippets_controller_spec.rb @@ -17,16 +17,22 @@ describe Projects::SnippetsController do it 'redirects to last_page if page number is larger than number of pages' do get :index, - namespace_id: project.namespace, - project_id: project, page: (last_page + 1).to_param + params: { + namespace_id: project.namespace, + project_id: project, + page: (last_page + 1).to_param + } expect(response).to redirect_to(namespace_project_snippets_path(page: last_page)) end it 'redirects to specified page' do get :index, - namespace_id: project.namespace, - project_id: project, page: last_page.to_param + params: { + namespace_id: project.namespace, + project_id: project, + page: last_page.to_param + } expect(assigns(:snippets).current_page).to eq(last_page) expect(response).to have_gitlab_http_status(200) @@ -38,7 +44,7 @@ describe Projects::SnippetsController do context 'when anonymous' do it 'does not include the private snippet' do - get :index, namespace_id: project.namespace, project_id: project + get :index, params: { namespace_id: project.namespace, project_id: project } expect(assigns(:snippets)).not_to include(project_snippet) expect(response).to have_gitlab_http_status(200) @@ -51,7 +57,7 @@ describe Projects::SnippetsController do end it 'renders the snippet' do - get :index, namespace_id: project.namespace, project_id: project + get :index, params: { namespace_id: project.namespace, project_id: project } expect(assigns(:snippets)).to include(project_snippet) expect(response).to have_gitlab_http_status(200) @@ -64,7 +70,7 @@ describe Projects::SnippetsController do end it 'renders the snippet' do - get :index, namespace_id: project.namespace, project_id: project + get :index, params: { namespace_id: project.namespace, project_id: project } expect(assigns(:snippets)).to include(project_snippet) expect(response).to have_gitlab_http_status(200) @@ -79,7 +85,7 @@ describe Projects::SnippetsController do project.add_developer(user) - post :create, { + post :create, params: { namespace_id: project.namespace.to_param, project_id: project, project_snippet: { title: 'Title', content: 'Content', description: 'Description' }.merge(snippet_params) @@ -164,7 +170,7 @@ describe Projects::SnippetsController do project.add_developer(user) - put :update, { + put :update, params: { namespace_id: project.namespace.to_param, project_id: project, id: snippet.id, @@ -295,9 +301,11 @@ describe Projects::SnippetsController do sign_in(admin) post :mark_as_spam, - namespace_id: project.namespace, - project_id: project, - id: snippet.id + params: { + namespace_id: project.namespace, + project_id: project, + id: snippet.id + } end it 'updates the snippet' do @@ -314,7 +322,7 @@ describe Projects::SnippetsController do context 'when anonymous' do it 'responds with status 404' do - get action, namespace_id: project.namespace, project_id: project, id: project_snippet.to_param + get action, params: { namespace_id: project.namespace, project_id: project, id: project_snippet.to_param } expect(response).to have_gitlab_http_status(404) end @@ -326,7 +334,7 @@ describe Projects::SnippetsController do end it 'renders the snippet' do - get action, namespace_id: project.namespace, project_id: project, id: project_snippet.to_param + get action, params: { namespace_id: project.namespace, project_id: project, id: project_snippet.to_param } expect(assigns(:snippet)).to eq(project_snippet) expect(response).to have_gitlab_http_status(200) @@ -339,7 +347,7 @@ describe Projects::SnippetsController do end it 'renders the snippet' do - get action, namespace_id: project.namespace, project_id: project, id: project_snippet.to_param + get action, params: { namespace_id: project.namespace, project_id: project, id: project_snippet.to_param } expect(assigns(:snippet)).to eq(project_snippet) expect(response).to have_gitlab_http_status(200) @@ -350,7 +358,7 @@ describe Projects::SnippetsController do context 'when the project snippet does not exist' do context 'when anonymous' do it 'responds with status 404' do - get action, namespace_id: project.namespace, project_id: project, id: 42 + get action, params: { namespace_id: project.namespace, project_id: project, id: 42 } expect(response).to have_gitlab_http_status(404) end @@ -362,7 +370,7 @@ describe Projects::SnippetsController do end it 'responds with status 404' do - get action, namespace_id: project.namespace, project_id: project, id: 42 + get action, params: { namespace_id: project.namespace, project_id: project, id: 42 } expect(response).to have_gitlab_http_status(404) end @@ -371,6 +379,46 @@ describe Projects::SnippetsController do end end + describe "GET #show for embeddable content" do + let(:project_snippet) { create(:project_snippet, snippet_permission, project: project, author: user) } + + before do + sign_in(user) + + get :show, params: { namespace_id: project.namespace, project_id: project, id: project_snippet.to_param }, format: :js + end + + context 'when snippet is private' do + let(:snippet_permission) { :private } + + it 'responds with status 404' do + expect(response).to have_gitlab_http_status(404) + end + end + + context 'when snippet is public' do + let(:snippet_permission) { :public } + + it 'responds with status 200' do + expect(assigns(:snippet)).to eq(project_snippet) + expect(response).to have_gitlab_http_status(200) + end + end + + context 'when the project is private' do + let(:project) { create(:project_empty_repo, :private) } + + context 'when snippet is public' do + let(:project_snippet) { create(:project_snippet, :public, project: project, author: user) } + + it 'responds with status 404' do + expect(assigns(:snippet)).to eq(project_snippet) + expect(response).to have_gitlab_http_status(404) + end + end + end + end + describe 'GET #raw' do let(:project_snippet) do create( @@ -391,13 +439,13 @@ describe Projects::SnippetsController do end it 'returns LF line endings by default' do - get :raw, params + get :raw, params: params expect(response.body).to eq("first line\nsecond line\nthird line") end it 'does not convert line endings when parameter present' do - get :raw, params.merge(line_ending: :raw) + get :raw, params: params.merge(line_ending: :raw) expect(response.body).to eq("first line\r\nsecond line\r\nthird line") end diff --git a/spec/controllers/projects/tags/releases_controller_spec.rb b/spec/controllers/projects/tags/releases_controller_spec.rb new file mode 100644 index 00000000000..29f206c574b --- /dev/null +++ b/spec/controllers/projects/tags/releases_controller_spec.rb @@ -0,0 +1,58 @@ +# frozen_string_literal: true + +require 'spec_helper' + +describe Projects::Tags::ReleasesController do + let!(:project) { create(:project, :repository) } + let!(:user) { create(:user) } + let!(:release) { create(:release, project: project) } + let!(:tag) { release.tag } + + before do + project.add_developer(user) + sign_in(user) + end + + describe 'GET #edit' do + it 'initializes a new release' do + tag_id = release.tag + project.releases.destroy_all # rubocop: disable DestroyAll + + get :edit, params: { namespace_id: project.namespace, project_id: project, tag_id: tag_id } + + release = assigns(:release) + expect(release).not_to be_nil + expect(release).not_to be_persisted + end + + it 'retrieves an existing release' do + get :edit, params: { namespace_id: project.namespace, project_id: project, tag_id: release.tag } + + release = assigns(:release) + expect(release).not_to be_nil + expect(release).to be_persisted + end + end + + describe 'PUT #update' do + it 'updates release note description' do + update_release('description updated') + + release = project.releases.find_by_tag(tag) + expect(release.description).to eq("description updated") + end + + it 'deletes release note when description is null' do + expect { update_release('') }.to change(project.releases, :count).by(-1) + end + end + + def update_release(description) + put :update, params: { + namespace_id: project.namespace.to_param, + project_id: project, + tag_id: release.tag, + release: { description: description } + } + end +end diff --git a/spec/controllers/projects/tags_controller_spec.rb b/spec/controllers/projects/tags_controller_spec.rb index 6fbf75d0259..379430bff3b 100644 --- a/spec/controllers/projects/tags_controller_spec.rb +++ b/spec/controllers/projects/tags_controller_spec.rb @@ -7,7 +7,7 @@ describe Projects::TagsController do describe 'GET index' do before do - get :index, namespace_id: project.namespace.to_param, project_id: project + get :index, params: { namespace_id: project.namespace.to_param, project_id: project } end it 'returns the tags for the page' do @@ -22,7 +22,7 @@ describe Projects::TagsController do describe 'GET show' do before do - get :show, namespace_id: project.namespace.to_param, project_id: project, id: id + get :show, params: { namespace_id: project.namespace.to_param, project_id: project, id: id } end context "valid tag" do diff --git a/spec/controllers/projects/templates_controller_spec.rb b/spec/controllers/projects/templates_controller_spec.rb index d7f07aa2b01..01e53669627 100644 --- a/spec/controllers/projects/templates_controller_spec.rb +++ b/spec/controllers/projects/templates_controller_spec.rb @@ -20,7 +20,7 @@ describe Projects::TemplatesController do describe '#show' do it 'renders template name and content as json' do - get(:show, namespace_id: project.namespace.to_param, template_type: "issue", key: "bug", project_id: project, format: :json) + get(:show, params: { namespace_id: project.namespace.to_param, template_type: "issue", key: "bug", project_id: project }, format: :json) expect(response.status).to eq(200) expect(body["name"]).to eq("bug") @@ -29,21 +29,21 @@ describe Projects::TemplatesController do it 'renders 404 when unauthorized' do sign_in(user2) - get(:show, namespace_id: project.namespace.to_param, template_type: "issue", key: "bug", project_id: project, format: :json) + get(:show, params: { namespace_id: project.namespace.to_param, template_type: "issue", key: "bug", project_id: project }, format: :json) expect(response.status).to eq(404) end it 'renders 404 when template type is not found' do sign_in(user) - get(:show, namespace_id: project.namespace.to_param, template_type: "dont_exist", key: "bug", project_id: project, format: :json) + get(:show, params: { namespace_id: project.namespace.to_param, template_type: "dont_exist", key: "bug", project_id: project }, format: :json) expect(response.status).to eq(404) end it 'renders 404 without errors' do sign_in(user) - expect { get(:show, namespace_id: project.namespace.to_param, template_type: "dont_exist", key: "bug", project_id: project, format: :json) }.not_to raise_error + expect { get(:show, params: { namespace_id: project.namespace.to_param, template_type: "dont_exist", key: "bug", project_id: project }, format: :json) }.not_to raise_error end end end diff --git a/spec/controllers/projects/todos_controller_spec.rb b/spec/controllers/projects/todos_controller_spec.rb index 58f2817c7cc..987772f38aa 100644 --- a/spec/controllers/projects/todos_controller_spec.rb +++ b/spec/controllers/projects/todos_controller_spec.rb @@ -29,10 +29,12 @@ describe Projects::TodosController do describe 'POST create' do def post_create post :create, - namespace_id: project.namespace, - project_id: project, - issuable_id: issue.id, - issuable_type: 'issue', + params: { + namespace_id: project.namespace, + project_id: project, + issuable_id: issue.id, + issuable_type: 'issue' + }, format: 'html' end @@ -44,10 +46,12 @@ describe Projects::TodosController do describe 'POST create' do def post_create post :create, - namespace_id: project.namespace, - project_id: project, - issuable_id: merge_request.id, - issuable_type: 'merge_request', + params: { + namespace_id: project.namespace, + project_id: project, + issuable_id: merge_request.id, + issuable_type: 'merge_request' + }, format: 'html' end diff --git a/spec/controllers/projects/tree_controller_spec.rb b/spec/controllers/projects/tree_controller_spec.rb index 9982b49eebb..b15a2bc84a5 100644 --- a/spec/controllers/projects/tree_controller_spec.rb +++ b/spec/controllers/projects/tree_controller_spec.rb @@ -17,9 +17,11 @@ describe Projects::TreeController do before do get(:show, - namespace_id: project.namespace.to_param, - project_id: project, - id: id) + params: { + namespace_id: project.namespace.to_param, + project_id: project, + id: id + }) end context "valid branch, no path" do @@ -73,9 +75,11 @@ describe Projects::TreeController do before do get(:show, - namespace_id: project.namespace.to_param, - project_id: project, - id: id) + params: { + namespace_id: project.namespace.to_param, + project_id: project, + id: id + }) end context 'redirect to blob' do @@ -93,12 +97,14 @@ describe Projects::TreeController do before do post(:create_dir, - namespace_id: project.namespace.to_param, - project_id: project, - id: 'master', - dir_name: path, - branch_name: branch_name, - commit_message: 'Test commit message') + params: { + namespace_id: project.namespace.to_param, + project_id: project, + id: 'master', + dir_name: path, + branch_name: branch_name, + commit_message: 'Test commit message' + }) end context 'successful creation' do diff --git a/spec/controllers/projects/uploads_controller_spec.rb b/spec/controllers/projects/uploads_controller_spec.rb index 9802e4d5b1e..cfa67683dd3 100644 --- a/spec/controllers/projects/uploads_controller_spec.rb +++ b/spec/controllers/projects/uploads_controller_spec.rb @@ -12,7 +12,7 @@ describe Projects::UploadsController do context 'when the URL the old style, without /-/system' do it 'responds with a redirect to the login page' do - get :show, namespace_id: 'project', project_id: 'avatar', filename: 'foo.png', secret: 'bar' + get :show, params: { namespace_id: 'project', project_id: 'avatar', filename: 'foo.png', secret: 'bar' } expect(response).to redirect_to(new_user_session_path) end @@ -35,6 +35,6 @@ describe Projects::UploadsController do def post_authorize(verified: true) request.headers.merge!(workhorse_internal_api_request_header) if verified - post :authorize, namespace_id: model.namespace, project_id: model.path, format: :json + post :authorize, params: { namespace_id: model.namespace, project_id: model.path }, format: :json end end diff --git a/spec/controllers/projects/variables_controller_spec.rb b/spec/controllers/projects/variables_controller_spec.rb index 9afd1f751c6..8cceda72c28 100644 --- a/spec/controllers/projects/variables_controller_spec.rb +++ b/spec/controllers/projects/variables_controller_spec.rb @@ -13,7 +13,7 @@ describe Projects::VariablesController do let!(:variable) { create(:ci_variable, project: project) } subject do - get :show, namespace_id: project.namespace.to_param, project_id: project, format: :json + get :show, params: { namespace_id: project.namespace.to_param, project_id: project }, format: :json end include_examples 'GET #show lists all variables' @@ -25,9 +25,11 @@ describe Projects::VariablesController do subject do patch :update, - namespace_id: project.namespace.to_param, - project_id: project, - variables_attributes: variables_attributes, + params: { + namespace_id: project.namespace.to_param, + project_id: project, + variables_attributes: variables_attributes + }, format: :json end diff --git a/spec/controllers/projects/wikis_controller_spec.rb b/spec/controllers/projects/wikis_controller_spec.rb index b974d927856..b2f40231796 100644 --- a/spec/controllers/projects/wikis_controller_spec.rb +++ b/spec/controllers/projects/wikis_controller_spec.rb @@ -20,7 +20,7 @@ describe Projects::WikisController do describe 'GET #show' do render_views - subject { get :show, namespace_id: project.namespace, project_id: project, id: wiki_title } + subject { get :show, params: { namespace_id: project.namespace, project_id: project, id: wiki_title } } it 'limits the retrieved pages for the sidebar' do expect(controller).to receive(:load_wiki).and_return(project_wiki) @@ -52,56 +52,24 @@ describe Projects::WikisController do let(:path) { upload_file_to_wiki(project, user, file_name) } - subject { get :show, namespace_id: project.namespace, project_id: project, id: path } + before do + get :show, params: { namespace_id: project.namespace, project_id: project, id: path } + end context 'when file is an image' do let(:file_name) { 'dk.png' } - context 'when feature flag workhorse_set_content_type is' do - before do - stub_feature_flags(workhorse_set_content_type: flag_value) - - subject - end - - context 'enabled' do - let(:flag_value) { true } - - it 'delivers the image' do - expect(response.headers['Content-Type']).to eq('image/png') - expect(response.headers['Content-Disposition']).to match(/^inline/) - expect(response.headers[Gitlab::Workhorse::DETECT_HEADER]).to eq "true" - end - - context 'when file is a svg' do - let(:file_name) { 'unsanitized.svg' } - - it 'delivers the image' do - expect(response.headers['Content-Type']).to eq('image/svg+xml') - expect(response.headers['Content-Disposition']).to match(/^attachment/) - expect(response.headers[Gitlab::Workhorse::DETECT_HEADER]).to eq "true" - end - end - end - - context 'disabled' do - let(:flag_value) { false } - - it 'renders the content inline' do - expect(response.headers['Content-Type']).to eq('image/png') - expect(response.headers['Content-Disposition']).to match(/^inline/) - expect(response.headers[Gitlab::Workhorse::DETECT_HEADER]).to eq nil - end + it 'delivers the image' do + expect(response.headers['Content-Disposition']).to match(/^inline/) + expect(response.headers[Gitlab::Workhorse::DETECT_HEADER]).to eq "true" + end - context 'when file is a svg' do - let(:file_name) { 'unsanitized.svg' } + context 'when file is a svg' do + let(:file_name) { 'unsanitized.svg' } - it 'renders the content as an attachment' do - expect(response.headers['Content-Type']).to eq('image/svg+xml') - expect(response.headers['Content-Disposition']).to match(/^attachment/) - expect(response.headers[Gitlab::Workhorse::DETECT_HEADER]).to eq nil - end - end + it 'delivers the image' do + expect(response.headers['Content-Disposition']).to match(/^inline/) + expect(response.headers[Gitlab::Workhorse::DETECT_HEADER]).to eq "true" end end end @@ -109,32 +77,9 @@ describe Projects::WikisController do context 'when file is a pdf' do let(:file_name) { 'git-cheat-sheet.pdf' } - context 'when feature flag workhorse_set_content_type is' do - before do - stub_feature_flags(workhorse_set_content_type: flag_value) - - subject - end - - context 'enabled' do - let(:flag_value) { true } - - it 'sets the content type to sets the content response headers' do - expect(response.headers['Content-Type']).to eq 'application/octet-stream' - expect(response.headers['Content-Disposition']).to match(/^inline/) - expect(response.headers[Gitlab::Workhorse::DETECT_HEADER]).to eq "true" - end - end - - context 'disabled' do - let(:flag_value) { false } - - it 'sets the content response headers' do - expect(response.headers['Content-Type']).to eq 'application/octet-stream' - expect(response.headers['Content-Disposition']).to match(/^inline/) - expect(response.headers[Gitlab::Workhorse::DETECT_HEADER]).to eq nil - end - end + it 'sets the content type to sets the content response headers' do + expect(response.headers['Content-Disposition']).to match(/^inline/) + expect(response.headers[Gitlab::Workhorse::DETECT_HEADER]).to eq "true" end end end @@ -142,14 +87,14 @@ describe Projects::WikisController do describe 'POST #preview_markdown' do it 'renders json in a correct format' do - post :preview_markdown, namespace_id: project.namespace, project_id: project, id: 'page/path', text: '*Markdown* text' + post :preview_markdown, params: { namespace_id: project.namespace, project_id: project, id: 'page/path', text: '*Markdown* text' } expect(JSON.parse(response.body).keys).to match_array(%w(body references)) end end describe 'GET #edit' do - subject { get(:edit, namespace_id: project.namespace, project_id: project, id: wiki_title) } + subject { get(:edit, params: { namespace_id: project.namespace, project_id: project, id: wiki_title }) } context 'when page content encoding is invalid' do it 'redirects to show' do @@ -178,10 +123,12 @@ describe Projects::WikisController do let(:new_content) { 'New content' } subject do patch(:update, - namespace_id: project.namespace, - project_id: project, - id: wiki_title, - wiki: { title: new_title, content: new_content }) + params: { + namespace_id: project.namespace, + project_id: project, + id: wiki_title, + wiki: { title: new_title, content: new_content } + }) end context 'when page content encoding is invalid' do diff --git a/spec/controllers/projects_controller_spec.rb b/spec/controllers/projects_controller_spec.rb index 576191a5788..f84f069f4db 100644 --- a/spec/controllers/projects_controller_spec.rb +++ b/spec/controllers/projects_controller_spec.rb @@ -22,7 +22,7 @@ describe ProjectsController do it 'renders the template' do group.add_owner(user) - get :new, namespace_id: group.id + get :new, params: { namespace_id: group.id } expect(response).to have_gitlab_http_status(200) expect(response).to render_template('new') @@ -31,7 +31,7 @@ describe ProjectsController do context 'when user does not have access to the namespace' do it 'responds with status 404' do - get :new, namespace_id: group.id + get :new, params: { namespace_id: group.id } expect(response).to have_gitlab_http_status(404) expect(response).not_to render_template('new') @@ -71,7 +71,7 @@ describe ProjectsController do let(:private_project) { create(:project, :private) } it "does not initialize notification setting" do - get :show, namespace_id: private_project.namespace, id: private_project + get :show, params: { namespace_id: private_project.namespace, id: private_project } expect(assigns(:notification_setting)).to be_nil end end @@ -79,7 +79,7 @@ describe ProjectsController do context "user has access to project" do context "and does not have notification setting" do it "initializes notification as disabled" do - get :show, namespace_id: public_project.namespace, id: public_project + get :show, params: { namespace_id: public_project.namespace, id: public_project } expect(assigns(:notification_setting).level).to eq("global") end end @@ -92,7 +92,7 @@ describe ProjectsController do end it "shows current notification setting" do - get :show, namespace_id: public_project.namespace, id: public_project + get :show, params: { namespace_id: public_project.namespace, id: public_project } expect(assigns(:notification_setting).level).to eq("watch") end end @@ -107,7 +107,7 @@ describe ProjectsController do end it 'shows wiki homepage' do - get :show, namespace_id: project.namespace, id: project + get :show, params: { namespace_id: project.namespace, id: project } expect(response).to render_template('projects/_wiki') end @@ -116,7 +116,7 @@ describe ProjectsController do project.project_feature.update_attribute(:wiki_access_level, ProjectFeature::DISABLED) create(:issue, project: project) - get :show, namespace_id: project.namespace, id: project + get :show, params: { namespace_id: project.namespace, id: project } expect(response).to render_template('projects/issues/_issues') expect(assigns(:issuable_meta_data)).not_to be_nil @@ -126,7 +126,7 @@ describe ProjectsController do project.project_feature.update_attribute(:wiki_access_level, ProjectFeature::DISABLED) project.project_feature.update_attribute(:issues_access_level, ProjectFeature::DISABLED) - get :show, namespace_id: project.namespace, id: project + get :show, params: { namespace_id: project.namespace, id: project } expect(response).to render_template("projects/_customize_workflow") end @@ -134,7 +134,7 @@ describe ProjectsController do it 'shows activity if enabled by user' do user.update_attribute(:project_view, 'activity') - get :show, namespace_id: project.namespace, id: project + get :show, params: { namespace_id: project.namespace, id: project } expect(response).to render_template("projects/_activity") end @@ -150,7 +150,7 @@ describe ProjectsController do end it 'renders a 503' do - get :show, namespace_id: project.namespace, id: project + get :show, params: { namespace_id: project.namespace, id: project } expect(response).to have_gitlab_http_status(503) end @@ -168,7 +168,7 @@ describe ProjectsController do before do user.update(project_view: project_view) - get :show, namespace_id: empty_project.namespace, id: empty_project + get :show, params: { namespace_id: empty_project.namespace, id: empty_project } end it "renders the empty project view" do @@ -190,7 +190,7 @@ describe ProjectsController do before do user.update(project_view: project_view) - get :show, namespace_id: empty_project.namespace, id: empty_project + get :show, params: { namespace_id: empty_project.namespace, id: empty_project } end it "renders the empty project view" do @@ -211,7 +211,7 @@ describe ProjectsController do allow(controller).to receive(:current_user).and_return(user) allow(user).to receive(:project_view).and_return('activity') - get :show, namespace_id: public_project.namespace, id: public_project + get :show, params: { namespace_id: public_project.namespace, id: public_project } expect(response).to render_template('_activity') end @@ -219,7 +219,7 @@ describe ProjectsController do allow(controller).to receive(:current_user).and_return(user) allow(user).to receive(:project_view).and_return('files') - get :show, namespace_id: public_project.namespace, id: public_project + get :show, params: { namespace_id: public_project.namespace, id: public_project } expect(response).to render_template('_files') end @@ -227,7 +227,7 @@ describe ProjectsController do allow(controller).to receive(:current_user).and_return(user) allow(user).to receive(:project_view).and_return('readme') - get :show, namespace_id: public_project.namespace, id: public_project + get :show, params: { namespace_id: public_project.namespace, id: public_project } expect(response).to render_template('_readme') end end @@ -245,7 +245,7 @@ describe ProjectsController do project = create(:project, pending_delete: true) sign_in(user) - get :show, namespace_id: project.namespace, id: project + get :show, params: { namespace_id: project.namespace, id: project } expect(response.status).to eq 404 end @@ -255,7 +255,7 @@ describe ProjectsController do it 'redirects to project page (format.html)' do project = create(:project, :public) - get :show, namespace_id: project.namespace, id: project, format: :git + get :show, params: { namespace_id: project.namespace, id: project }, format: :git expect(response).to have_gitlab_http_status(302) expect(response).to redirect_to(namespace_project_path) @@ -278,7 +278,7 @@ describe ProjectsController do it 'does not increase the number of queries when the project is forked' do expected_query = /#{public_project.fork_network.find_forks_in(other_user.namespace).to_sql}/ - expect { get(:show, namespace_id: public_project.namespace, id: public_project) } + expect { get(:show, params: { namespace_id: public_project.namespace, id: public_project }) } .not_to exceed_query_limit(2).for_query(expected_query) end end @@ -290,8 +290,10 @@ describe ProjectsController do project.add_maintainer(user) get :edit, - namespace_id: project.namespace.path, - id: project.path + params: { + namespace_id: project.namespace.path, + id: project.path + } expect(assigns(:badge_api_endpoint)).not_to be_nil end @@ -355,9 +357,11 @@ describe ProjectsController do } put :update, - namespace_id: project.namespace, - id: project.id, - project: params + params: { + namespace_id: project.namespace, + id: project.id, + project: params + } expect(response).to have_gitlab_http_status(302) params.each do |param, value| @@ -367,9 +371,11 @@ describe ProjectsController do def update_project(**parameters) put :update, - namespace_id: project.namespace.path, - id: project.path, - project: parameters + params: { + namespace_id: project.namespace.path, + id: project.path, + project: parameters + } end end @@ -397,9 +403,11 @@ describe ProjectsController do sign_in(admin) put :transfer, - namespace_id: project.namespace.path, - new_namespace_id: new_namespace.id, - id: project.path, + params: { + namespace_id: project.namespace.path, + new_namespace_id: new_namespace.id, + id: project.path + }, format: :js project.reload @@ -416,9 +424,11 @@ describe ProjectsController do old_namespace = project.namespace put :transfer, - namespace_id: old_namespace.path, - new_namespace_id: nil, - id: project.path, + params: { + namespace_id: old_namespace.path, + new_namespace_id: nil, + id: project.path + }, format: :js project.reload @@ -438,7 +448,7 @@ describe ProjectsController do sign_in(admin) orig_id = project.id - delete :destroy, namespace_id: project.namespace, id: project + delete :destroy, params: { namespace_id: project.namespace, id: project } expect { Project.find(orig_id) }.to raise_error(ActiveRecord::RecordNotFound) expect(response).to have_gitlab_http_status(302) @@ -458,7 +468,7 @@ describe ProjectsController do project.merge_requests << merge_request sign_in(admin) - delete :destroy, namespace_id: forked_project.namespace, id: forked_project + delete :destroy, params: { namespace_id: forked_project.namespace, id: forked_project } expect(merge_request.reload.state).to eq('closed') end @@ -468,9 +478,11 @@ describe ProjectsController do describe 'PUT #new_issuable_address for issue' do subject do put :new_issuable_address, - namespace_id: project.namespace, - id: project, - issuable_type: 'issue' + params: { + namespace_id: project.namespace, + id: project, + issuable_type: 'issue' + } user.reload end @@ -496,9 +508,11 @@ describe ProjectsController do describe 'PUT #new_issuable_address for merge request' do subject do put :new_issuable_address, - namespace_id: project.namespace, - id: project, - issuable_type: 'merge_request' + params: { + namespace_id: project.namespace, + id: project, + issuable_type: 'merge_request' + } user.reload end @@ -526,23 +540,31 @@ describe ProjectsController do sign_in(user) expect(user.starred?(public_project)).to be_falsey post(:toggle_star, - namespace_id: public_project.namespace, - id: public_project) + params: { + namespace_id: public_project.namespace, + id: public_project + }) expect(user.starred?(public_project)).to be_truthy post(:toggle_star, - namespace_id: public_project.namespace, - id: public_project) + params: { + namespace_id: public_project.namespace, + id: public_project + }) expect(user.starred?(public_project)).to be_falsey end it "does nothing if user is not signed in" do post(:toggle_star, - namespace_id: project.namespace, - id: public_project) + params: { + namespace_id: project.namespace, + id: public_project + }) expect(user.starred?(public_project)).to be_falsey post(:toggle_star, - namespace_id: project.namespace, - id: public_project) + params: { + namespace_id: project.namespace, + id: public_project + }) expect(user.starred?(public_project)).to be_falsey end end @@ -558,8 +580,11 @@ describe ProjectsController do it 'removes fork from project' do delete(:remove_fork, - namespace_id: forked_project.namespace.to_param, - id: forked_project.to_param, format: :js) + params: { + namespace_id: forked_project.namespace.to_param, + id: forked_project.to_param + }, + format: :js) expect(forked_project.reload.forked?).to be_falsey expect(flash[:notice]).to eq('The fork relationship has been removed.') @@ -572,8 +597,11 @@ describe ProjectsController do it 'does nothing if project was not forked' do delete(:remove_fork, - namespace_id: unforked_project.namespace, - id: unforked_project, format: :js) + params: { + namespace_id: unforked_project.namespace, + id: unforked_project + }, + format: :js) expect(flash[:notice]).to be_nil expect(response).to render_template(:remove_fork) @@ -583,17 +611,20 @@ describe ProjectsController do it "does nothing if user is not signed in" do delete(:remove_fork, - namespace_id: project.namespace, - id: project, format: :js) + params: { + namespace_id: project.namespace, + id: project + }, + format: :js) expect(response).to have_gitlab_http_status(401) end end describe "GET refs" do - let(:public_project) { create(:project, :public, :repository) } + let(:project) { create(:project, :public, :repository) } it 'gets a list of branches and tags' do - get :refs, namespace_id: public_project.namespace, id: public_project, sort: 'updated_desc' + get :refs, params: { namespace_id: project.namespace, id: project, sort: 'updated_desc' } parsed_body = JSON.parse(response.body) expect(parsed_body['Branches']).to include('master') @@ -603,7 +634,7 @@ describe ProjectsController do end it "gets a list of branches, tags and commits" do - get :refs, namespace_id: public_project.namespace, id: public_project, ref: "123456" + get :refs, params: { namespace_id: project.namespace, id: project, ref: "123456" } parsed_body = JSON.parse(response.body) expect(parsed_body["Branches"]).to include("master") @@ -618,7 +649,7 @@ describe ProjectsController do end it "gets a list of branches, tags and commits" do - get :refs, namespace_id: public_project.namespace, id: public_project, ref: "123456" + get :refs, params: { namespace_id: project.namespace, id: project, ref: "123456" } parsed_body = JSON.parse(response.body) expect(parsed_body["Branches"]).to include("master") @@ -626,6 +657,22 @@ describe ProjectsController do expect(parsed_body["Commits"]).to include("123456") end end + + context 'when private project' do + let(:project) { create(:project, :repository) } + + context 'as a guest' do + it 'renders forbidden' do + user = create(:user) + project.add_guest(user) + + sign_in(user) + get :refs, params: { namespace_id: project.namespace, id: project } + + expect(response).to have_gitlab_http_status(404) + end + end + end end describe 'POST #preview_markdown' do @@ -634,7 +681,7 @@ describe ProjectsController do end it 'renders json in a correct format' do - post :preview_markdown, namespace_id: public_project.namespace, id: public_project, text: '*Markdown* text' + post :preview_markdown, params: { namespace_id: public_project.namespace, id: public_project, text: '*Markdown* text' } expect(JSON.parse(response.body).keys).to match_array(%w(body references)) end @@ -644,9 +691,11 @@ describe ProjectsController do let(:merge_request) { create(:merge_request, :closed, target_project: public_project) } it 'renders JSON body with state filter for issues' do - post :preview_markdown, namespace_id: public_project.namespace, - id: public_project, - text: issue.to_reference + post :preview_markdown, params: { + namespace_id: public_project.namespace, + id: public_project, + text: issue.to_reference + } json_response = JSON.parse(response.body) @@ -654,9 +703,11 @@ describe ProjectsController do end it 'renders JSON body with state filter for MRs' do - post :preview_markdown, namespace_id: public_project.namespace, - id: public_project, - text: merge_request.to_reference + post :preview_markdown, params: { + namespace_id: public_project.namespace, + id: public_project, + text: merge_request.to_reference + } json_response = JSON.parse(response.body) @@ -674,7 +725,7 @@ describe ProjectsController do context 'when requesting the canonical path' do context "with exactly matching casing" do it "loads the project" do - get :show, namespace_id: public_project.namespace, id: public_project + get :show, params: { namespace_id: public_project.namespace, id: public_project } expect(assigns(:project)).to eq(public_project) expect(response).to have_gitlab_http_status(200) @@ -683,7 +734,7 @@ describe ProjectsController do context "with different casing" do it "redirects to the normalized path" do - get :show, namespace_id: public_project.namespace, id: public_project.path.upcase + get :show, params: { namespace_id: public_project.namespace, id: public_project.path.upcase } expect(assigns(:project)).to eq(public_project) expect(response).to redirect_to("/#{public_project.full_path}") @@ -696,14 +747,14 @@ describe ProjectsController do let!(:redirect_route) { public_project.redirect_routes.create!(path: "foo/bar") } it 'redirects to the canonical path' do - get :show, namespace_id: 'foo', id: 'bar' + get :show, params: { namespace_id: 'foo', id: 'bar' } expect(response).to redirect_to(public_project) expect(controller).to set_flash[:notice].to(project_moved_message(redirect_route, public_project)) end it 'redirects to the canonical path (testing non-show action)' do - get :refs, namespace_id: 'foo', id: 'bar' + get :refs, params: { namespace_id: 'foo', id: 'bar' } expect(response).to redirect_to(refs_project_path(public_project)) expect(controller).to set_flash[:notice].to(project_moved_message(redirect_route, public_project)) @@ -714,13 +765,13 @@ describe ProjectsController do context 'for a POST request' do context 'when requesting the canonical path with different casing' do it 'does not 404' do - post :toggle_star, namespace_id: public_project.namespace, id: public_project.path.upcase + post :toggle_star, params: { namespace_id: public_project.namespace, id: public_project.path.upcase } expect(response).not_to have_gitlab_http_status(404) end it 'does not redirect to the correct casing' do - post :toggle_star, namespace_id: public_project.namespace, id: public_project.path.upcase + post :toggle_star, params: { namespace_id: public_project.namespace, id: public_project.path.upcase } expect(response).not_to have_gitlab_http_status(301) end @@ -730,7 +781,7 @@ describe ProjectsController do let!(:redirect_route) { public_project.redirect_routes.create!(path: "foo/bar") } it 'returns not found' do - post :toggle_star, namespace_id: 'foo', id: 'bar' + post :toggle_star, params: { namespace_id: 'foo', id: 'bar' } expect(response).to have_gitlab_http_status(404) end @@ -744,13 +795,13 @@ describe ProjectsController do context 'when requesting the canonical path with different casing' do it 'does not 404' do - delete :destroy, namespace_id: project.namespace, id: project.path.upcase + delete :destroy, params: { namespace_id: project.namespace, id: project.path.upcase } expect(response).not_to have_gitlab_http_status(404) end it 'does not redirect to the correct casing' do - delete :destroy, namespace_id: project.namespace, id: project.path.upcase + delete :destroy, params: { namespace_id: project.namespace, id: project.path.upcase } expect(response).not_to have_gitlab_http_status(301) end @@ -760,7 +811,7 @@ describe ProjectsController do let!(:redirect_route) { project.redirect_routes.create!(path: "foo/bar") } it 'returns not found' do - delete :destroy, namespace_id: 'foo', id: 'bar' + delete :destroy, params: { namespace_id: 'foo', id: 'bar' } expect(response).to have_gitlab_http_status(404) end @@ -777,7 +828,7 @@ describe ProjectsController do context 'when project export is enabled' do it 'returns 302' do - get :export, namespace_id: project.namespace, id: project + get :export, params: { namespace_id: project.namespace, id: project } expect(response).to have_gitlab_http_status(302) end @@ -789,7 +840,7 @@ describe ProjectsController do end it 'returns 404' do - get :export, namespace_id: project.namespace, id: project + get :export, params: { namespace_id: project.namespace, id: project } expect(response).to have_gitlab_http_status(404) end @@ -806,7 +857,7 @@ describe ProjectsController do context 'object storage enabled' do context 'when project export is enabled' do it 'returns 302' do - get :download_export, namespace_id: project.namespace, id: project + get :download_export, params: { namespace_id: project.namespace, id: project } expect(response).to have_gitlab_http_status(302) end @@ -818,7 +869,7 @@ describe ProjectsController do end it 'returns 404' do - get :download_export, namespace_id: project.namespace, id: project + get :download_export, params: { namespace_id: project.namespace, id: project } expect(response).to have_gitlab_http_status(404) end @@ -835,7 +886,7 @@ describe ProjectsController do context 'when project export is enabled' do it 'returns 302' do - post :remove_export, namespace_id: project.namespace, id: project + post :remove_export, params: { namespace_id: project.namespace, id: project } expect(response).to have_gitlab_http_status(302) end @@ -847,7 +898,7 @@ describe ProjectsController do end it 'returns 404' do - post :remove_export, namespace_id: project.namespace, id: project + post :remove_export, params: { namespace_id: project.namespace, id: project } expect(response).to have_gitlab_http_status(404) end @@ -863,7 +914,7 @@ describe ProjectsController do context 'when project export is enabled' do it 'returns 302' do - post :generate_new_export, namespace_id: project.namespace, id: project + post :generate_new_export, params: { namespace_id: project.namespace, id: project } expect(response).to have_gitlab_http_status(302) end @@ -875,7 +926,7 @@ describe ProjectsController do end it 'returns 404' do - post :generate_new_export, namespace_id: project.namespace, id: project + post :generate_new_export, params: { namespace_id: project.namespace, id: project } expect(response).to have_gitlab_http_status(404) end diff --git a/spec/controllers/registrations_controller_spec.rb b/spec/controllers/registrations_controller_spec.rb index d334a2ff566..fd151e8a298 100644 --- a/spec/controllers/registrations_controller_spec.rb +++ b/spec/controllers/registrations_controller_spec.rb @@ -17,7 +17,7 @@ describe RegistrationsController do it 'signs the user in' do allow_any_instance_of(ApplicationSetting).to receive(:send_user_confirmation_email).and_return(false) - expect { post(:create, user_params) }.not_to change { ActionMailer::Base.deliveries.size } + expect { post(:create, params: user_params) }.not_to change { ActionMailer::Base.deliveries.size } expect(subject.current_user).not_to be_nil end end @@ -26,7 +26,7 @@ describe RegistrationsController do it 'does not authenticate user and sends confirmation email' do allow_any_instance_of(ApplicationSetting).to receive(:send_user_confirmation_email).and_return(true) - post(:create, user_params) + post(:create, params: user_params) expect(ActionMailer::Base.deliveries.last.to.first).to eq(user_params[:user][:email]) expect(subject.current_user).to be_nil @@ -37,7 +37,7 @@ describe RegistrationsController do it 'redirects to sign_in' do allow_any_instance_of(ApplicationSetting).to receive(:signup_enabled?).and_return(false) - expect { post(:create, user_params) }.not_to change(User, :count) + expect { post(:create, params: user_params) }.not_to change(User, :count) expect(response).to redirect_to(new_user_session_path) end end @@ -52,7 +52,7 @@ describe RegistrationsController do # Without this, `verify_recaptcha` arbitrarily returns true in test env Recaptcha.configuration.skip_verify_env.delete('test') - post(:create, user_params) + post(:create, params: user_params) expect(response).to render_template(:new) expect(flash[:alert]).to include 'There was an error with the reCAPTCHA. Please solve the reCAPTCHA again.' @@ -64,7 +64,7 @@ describe RegistrationsController do Recaptcha.configuration.skip_verify_env << 'test' end - post(:create, user_params) + post(:create, params: user_params) expect(flash[:notice]).to include 'Welcome! You have signed up successfully.' end @@ -76,13 +76,13 @@ describe RegistrationsController do end it 'redirects back with a notice when the checkbox was not checked' do - post :create, user_params + post :create, params: user_params expect(flash[:alert]).to match /you must accept our terms/i end it 'creates the user with agreement when terms are accepted' do - post :create, user_params.merge(terms_opt_in: '1') + post :create, params: user_params.merge(terms_opt_in: '1') expect(subject.current_user).to be_present expect(subject.current_user.terms_accepted?).to be(true) @@ -125,13 +125,13 @@ describe RegistrationsController do end it 'fails if password confirmation is wrong' do - post :destroy, password: 'wrong password' + post :destroy, params: { password: 'wrong password' } expect_password_failure end it 'succeeds if password is confirmed' do - post :destroy, password: '12345678' + post :destroy, params: { password: '12345678' } expect_success end @@ -150,13 +150,13 @@ describe RegistrationsController do end it 'fails if username confirmation is wrong' do - post :destroy, username: 'wrong username' + post :destroy, params: { username: 'wrong username' } expect_username_failure end it 'succeeds if username is confirmed' do - post :destroy, username: user.username + post :destroy, params: { username: user.username } expect_success end diff --git a/spec/controllers/search_controller_spec.rb b/spec/controllers/search_controller_spec.rb index 416a09e1684..c9b53336fd1 100644 --- a/spec/controllers/search_controller_spec.rb +++ b/spec/controllers/search_controller_spec.rb @@ -11,7 +11,7 @@ describe SearchController do project = create(:project, :public) note = create(:note_on_issue, project: project) - get :show, project_id: project.id, scope: 'notes', search: note.note + get :show, params: { project_id: project.id, scope: 'notes', search: note.note } expect(assigns[:search_objects].first).to eq note end @@ -30,13 +30,13 @@ describe SearchController do end it 'still blocks searches without a project_id' do - get :show, search: 'hello' + get :show, params: { search: 'hello' } expect(response).to have_gitlab_http_status(403) end it 'allows searches with a project_id' do - get :show, search: 'hello', project_id: create(:project, :public).id + get :show, params: { search: 'hello', project_id: create(:project, :public).id } expect(response).to have_gitlab_http_status(200) end @@ -52,7 +52,7 @@ describe SearchController do project = create(:project, :public, :issues_private) note = create(:note_on_issue, project: project) - get :show, project_id: project.id, scope: 'notes', search: note.note + get :show, params: { project_id: project.id, scope: 'notes', search: note.note } expect(assigns[:search_objects].count).to eq(0) end @@ -62,7 +62,7 @@ describe SearchController do project = create(:project, :public, :merge_requests_private) note = create(:note_on_merge_request, project: project) - get :show, project_id: project.id, scope: 'notes', search: note.note + get :show, params: { project_id: project.id, scope: 'notes', search: note.note } expect(assigns[:search_objects].count).to eq(0) end @@ -71,7 +71,7 @@ describe SearchController do project = create(:project, :public, :snippets_private) note = create(:note_on_project_snippet, project: project) - get :show, project_id: project.id, scope: 'notes', search: note.note + get :show, params: { project_id: project.id, scope: 'notes', search: note.note } expect(assigns[:search_objects].count).to eq(0) end diff --git a/spec/controllers/sent_notifications_controller_spec.rb b/spec/controllers/sent_notifications_controller_spec.rb index 54a9af92f07..75c91dd8607 100644 --- a/spec/controllers/sent_notifications_controller_spec.rb +++ b/spec/controllers/sent_notifications_controller_spec.rb @@ -15,7 +15,7 @@ describe SentNotificationsController do context 'when the user is not logged in' do context 'when the force param is passed' do before do - get(:unsubscribe, id: sent_notification.reply_key, force: true) + get(:unsubscribe, params: { id: sent_notification.reply_key, force: true }) end it 'unsubscribes the user' do @@ -33,7 +33,7 @@ describe SentNotificationsController do context 'when the force param is not passed' do before do - get(:unsubscribe, id: sent_notification.reply_key) + get(:unsubscribe, params: { id: sent_notification.reply_key }) end it 'does not unsubscribe the user' do @@ -57,7 +57,7 @@ describe SentNotificationsController do context 'when the ID passed does not exist' do before do - get(:unsubscribe, id: sent_notification.reply_key.reverse) + get(:unsubscribe, params: { id: sent_notification.reply_key.reverse }) end it 'does not unsubscribe the user' do @@ -75,7 +75,7 @@ describe SentNotificationsController do context 'when the force param is passed' do before do - get(:unsubscribe, id: sent_notification.reply_key, force: true) + get(:unsubscribe, params: { id: sent_notification.reply_key, force: true }) end it 'unsubscribes the user' do @@ -101,7 +101,7 @@ describe SentNotificationsController do let(:sent_notification) { create(:sent_notification, project: project, noteable: merge_request, recipient: user) } before do - get(:unsubscribe, id: sent_notification.reply_key) + get(:unsubscribe, params: { id: sent_notification.reply_key }) end it 'unsubscribes the user' do diff --git a/spec/controllers/sessions_controller_spec.rb b/spec/controllers/sessions_controller_spec.rb index c691b3f478b..ea7242c1aa8 100644 --- a/spec/controllers/sessions_controller_spec.rb +++ b/spec/controllers/sessions_controller_spec.rb @@ -26,7 +26,7 @@ describe SessionsController do context 'and auto_sign_in=false param is passed' do it 'responds with 200' do - get(:new, auto_sign_in: 'false') + get(:new, params: { auto_sign_in: 'false' }) expect(response).to have_gitlab_http_status(200) end @@ -42,7 +42,7 @@ describe SessionsController do context 'when using standard authentications' do context 'invalid password' do it 'does not authenticate user' do - post(:create, user: { login: 'invalid', password: 'invalid' }) + post(:create, params: { user: { login: 'invalid', password: 'invalid' } }) expect(response) .to set_flash.now[:alert].to /Invalid Login or password/ @@ -54,19 +54,19 @@ describe SessionsController do let(:user_params) { { login: user.username, password: user.password } } it 'authenticates user correctly' do - post(:create, user: user_params) + post(:create, params: { user: user_params }) expect(subject.current_user). to eq user end it 'creates an audit log record' do - expect { post(:create, user: user_params) }.to change { SecurityEvent.count }.by(1) + expect { post(:create, params: { user: user_params }) }.to change { SecurityEvent.count }.by(1) expect(SecurityEvent.last.details[:with]).to eq('standard') end include_examples 'user login request with unique ip limit', 302 do def request - post(:create, user: user_params) + post(:create, params: { user: user_params }) expect(subject.current_user).to eq user subject.sign_out user end @@ -74,7 +74,7 @@ describe SessionsController do it 'updates the user activity' do expect do - post(:create, user: user_params) + post(:create, params: { user: user_params }) end.to change { user.reload.last_activity_on }.to(Date.today) end end @@ -98,7 +98,7 @@ describe SessionsController do .with(:failed_login_captcha_total, anything) .and_return(counter) - post(:create, user: user_params) + post(:create, params: { user: user_params }) expect(response).to render_template(:new) expect(flash[:alert]).to include 'There was an error with the reCAPTCHA. Please solve the reCAPTCHA again.' @@ -116,7 +116,7 @@ describe SessionsController do .and_return(counter) expect(Gitlab::Metrics).to receive(:counter).and_call_original - post(:create, user: user_params) + post(:create, params: { user: user_params }) expect(subject.current_user).to eq user end @@ -127,7 +127,7 @@ describe SessionsController do let(:user) { create(:user, :two_factor) } def authenticate_2fa(user_params) - post(:create, { user: user_params }, { otp_user_id: user.id }) + post(:create, params: { user: user_params }, session: { otp_user_id: user.id }) end context 'remember_me field' do @@ -233,7 +233,7 @@ describe SessionsController do end it 'keeps the user locked on future login attempts' do - post(:create, user: { login: user.username, password: user.password }) + post(:create, params: { user: { login: user.username, password: user.password } }) expect(response) .to set_flash.now[:alert].to /Invalid Login or password/ @@ -265,7 +265,7 @@ describe SessionsController do let(:user) { create(:user, :two_factor) } def authenticate_2fa_u2f(user_params) - post(:create, { user: user_params }, { otp_user_id: user.id }) + post(:create, params: { user: user_params }, session: { otp_user_id: user.id }) end context 'remember_me field' do @@ -309,7 +309,7 @@ describe SessionsController do search_path = "/search?search=seed_project" request.headers[:HTTP_REFERER] = "http://#{host}#{search_path}" - get(:new, redirect_to_referer: :yes) + get(:new, params: { redirect_to_referer: :yes }) expect(controller.stored_location_for(:redirect)).to eq(search_path) end diff --git a/spec/controllers/snippets/notes_controller_spec.rb b/spec/controllers/snippets/notes_controller_spec.rb index e6148ea1734..6efbd1f6c71 100644 --- a/spec/controllers/snippets/notes_controller_spec.rb +++ b/spec/controllers/snippets/notes_controller_spec.rb @@ -16,7 +16,7 @@ describe Snippets::NotesController do before do note_on_public - get :index, { snippet_id: public_snippet } + get :index, params: { snippet_id: public_snippet } end it "returns status 200" do @@ -35,7 +35,7 @@ describe Snippets::NotesController do context 'when user not logged in' do it "returns status 404" do - get :index, { snippet_id: internal_snippet } + get :index, params: { snippet_id: internal_snippet } expect(response).to have_gitlab_http_status(404) end @@ -47,7 +47,7 @@ describe Snippets::NotesController do end it "returns status 200" do - get :index, { snippet_id: internal_snippet } + get :index, params: { snippet_id: internal_snippet } expect(response).to have_gitlab_http_status(200) end @@ -61,7 +61,7 @@ describe Snippets::NotesController do context 'when user not logged in' do it "returns status 404" do - get :index, { snippet_id: private_snippet } + get :index, params: { snippet_id: private_snippet } expect(response).to have_gitlab_http_status(404) end @@ -73,7 +73,7 @@ describe Snippets::NotesController do end it "returns status 404" do - get :index, { snippet_id: private_snippet } + get :index, params: { snippet_id: private_snippet } expect(response).to have_gitlab_http_status(404) end @@ -87,13 +87,13 @@ describe Snippets::NotesController do end it "returns status 200" do - get :index, { snippet_id: private_snippet } + get :index, params: { snippet_id: private_snippet } expect(response).to have_gitlab_http_status(200) end it "returns 1 note" do - get :index, { snippet_id: private_snippet } + get :index, params: { snippet_id: private_snippet } expect(JSON.parse(response.body)['notes'].count).to eq(1) end @@ -110,7 +110,7 @@ describe Snippets::NotesController do end it "does not return any note" do - get :index, { snippet_id: public_snippet } + get :index, params: { snippet_id: public_snippet } expect(JSON.parse(response.body)['notes'].count).to eq(0) end @@ -132,13 +132,13 @@ describe Snippets::NotesController do end it "returns status 200" do - delete :destroy, request_params + delete :destroy, params: request_params expect(response).to have_gitlab_http_status(200) end it "deletes the note" do - expect { delete :destroy, request_params }.to change { Note.count }.from(1).to(0) + expect { delete :destroy, params: request_params }.to change { Note.count }.from(1).to(0) end context 'system note' do @@ -147,7 +147,7 @@ describe Snippets::NotesController do end it "does not delete the note" do - expect { delete :destroy, request_params }.not_to change { Note.count } + expect { delete :destroy, params: request_params }.not_to change { Note.count } end end end @@ -160,13 +160,13 @@ describe Snippets::NotesController do end it "returns status 404" do - delete :destroy, request_params + delete :destroy, params: request_params expect(response).to have_gitlab_http_status(404) end it "does not update the note" do - expect { delete :destroy, request_params }.not_to change { Note.count } + expect { delete :destroy, params: request_params }.not_to change { Note.count } end end end @@ -177,7 +177,7 @@ describe Snippets::NotesController do sign_in(user) end - subject { post(:toggle_award_emoji, snippet_id: public_snippet, id: note.id, name: "thumbsup") } + subject { post(:toggle_award_emoji, params: { snippet_id: public_snippet, id: note.id, name: "thumbsup" }) } it "toggles the award emoji" do expect { subject }.to change { note.award_emoji.count }.by(1) diff --git a/spec/controllers/snippets_controller_spec.rb b/spec/controllers/snippets_controller_spec.rb index 957bab638b1..5c6858dc7b2 100644 --- a/spec/controllers/snippets_controller_spec.rb +++ b/spec/controllers/snippets_controller_spec.rb @@ -8,7 +8,7 @@ describe SnippetsController do context 'when username parameter is present' do it 'renders snippets of a user when username is present' do - get :index, username: user.username + get :index, params: { username: user.username } expect(response).to render_template(:index) end @@ -67,7 +67,7 @@ describe SnippetsController do let(:other_personal_snippet) { create(:personal_snippet, :private, author: other_author) } it 'responds with status 404' do - get :show, id: other_personal_snippet.to_param + get :show, params: { id: other_personal_snippet.to_param } expect(response).to have_gitlab_http_status(404) end @@ -75,17 +75,23 @@ describe SnippetsController do context 'when signed in user is the author' do it 'renders the snippet' do - get :show, id: personal_snippet.to_param + get :show, params: { id: personal_snippet.to_param } expect(assigns(:snippet)).to eq(personal_snippet) expect(response).to have_gitlab_http_status(200) end + + it 'responds with status 404 when embeddable content is requested' do + get :show, params: { id: personal_snippet.to_param }, format: :js + + expect(response).to have_gitlab_http_status(404) + end end end context 'when not signed in' do it 'redirects to the sign in page' do - get :show, id: personal_snippet.to_param + get :show, params: { id: personal_snippet.to_param } expect(response).to redirect_to(new_user_session_path) end @@ -101,16 +107,22 @@ describe SnippetsController do end it 'renders the snippet' do - get :show, id: personal_snippet.to_param + get :show, params: { id: personal_snippet.to_param } expect(assigns(:snippet)).to eq(personal_snippet) expect(response).to have_gitlab_http_status(200) end + + it 'responds with status 404 when embeddable content is requested' do + get :show, params: { id: personal_snippet.to_param }, format: :js + + expect(response).to have_gitlab_http_status(404) + end end context 'when not signed in' do it 'redirects to the sign in page' do - get :show, id: personal_snippet.to_param + get :show, params: { id: personal_snippet.to_param } expect(response).to redirect_to(new_user_session_path) end @@ -126,7 +138,14 @@ describe SnippetsController do end it 'renders the snippet' do - get :show, id: personal_snippet.to_param + get :show, params: { id: personal_snippet.to_param } + + expect(assigns(:snippet)).to eq(personal_snippet) + expect(response).to have_gitlab_http_status(200) + end + + it 'responds with status 200 when embeddable content is requested' do + get :show, params: { id: personal_snippet.to_param }, format: :js expect(assigns(:snippet)).to eq(personal_snippet) expect(response).to have_gitlab_http_status(200) @@ -135,7 +154,7 @@ describe SnippetsController do context 'when not signed in' do it 'renders the snippet' do - get :show, id: personal_snippet.to_param + get :show, params: { id: personal_snippet.to_param } expect(assigns(:snippet)).to eq(personal_snippet) expect(response).to have_gitlab_http_status(200) @@ -150,7 +169,7 @@ describe SnippetsController do end it 'responds with status 404' do - get :show, id: 'doesntexist' + get :show, params: { id: 'doesntexist' } expect(response).to have_gitlab_http_status(404) end @@ -158,7 +177,7 @@ describe SnippetsController do context 'when not signed in' do it 'responds with status 404' do - get :show, id: 'doesntexist' + get :show, params: { id: 'doesntexist' } expect(response).to redirect_to(new_user_session_path) end @@ -170,7 +189,7 @@ describe SnippetsController do def create_snippet(snippet_params = {}, additional_params = {}) sign_in(user) - post :create, { + post :create, params: { personal_snippet: { title: 'Title', content: 'Content', description: 'Description' }.merge(snippet_params) }.merge(additional_params) @@ -279,7 +298,7 @@ describe SnippetsController do def update_snippet(snippet_params = {}, additional_params = {}) sign_in(user) - put :update, { + put :update, params: { id: snippet.id, personal_snippet: { title: 'Title', content: 'Content' }.merge(snippet_params) }.merge(additional_params) @@ -406,7 +425,7 @@ describe SnippetsController do create(:user_agent_detail, subject: snippet) sign_in(admin) - post :mark_as_spam, id: snippet.id + post :mark_as_spam, params: { id: snippet.id } end it 'updates the snippet' do @@ -430,18 +449,15 @@ describe SnippetsController do let(:other_personal_snippet) { create(:personal_snippet, :private, author: other_author) } it 'responds with status 404' do - get :raw, id: other_personal_snippet.to_param + get :raw, params: { id: other_personal_snippet.to_param } expect(response).to have_gitlab_http_status(404) end end context 'when signed in user is the author' do - let(:flag_value) { false } - before do - stub_feature_flags(workhorse_set_content_type: flag_value) - get :raw, id: personal_snippet.to_param + get :raw, params: { id: personal_snippet.to_param } end it 'responds with status 200' do @@ -455,29 +471,16 @@ describe SnippetsController do expect(response.header['Content-Disposition']).to match(/inline/) end - context 'when feature flag workhorse_set_content_type is' do - context 'enabled' do - let(:flag_value) { true } - - it "sets #{Gitlab::Workhorse::DETECT_HEADER} header" do - expect(response).to have_gitlab_http_status(200) - expect(response.header[Gitlab::Workhorse::DETECT_HEADER]).to eq "true" - end - end - - context 'disabled' do - it "does not set #{Gitlab::Workhorse::DETECT_HEADER} header" do - expect(response).to have_gitlab_http_status(200) - expect(response.header[Gitlab::Workhorse::DETECT_HEADER]).to be nil - end - end + it "sets #{Gitlab::Workhorse::DETECT_HEADER} header" do + expect(response).to have_gitlab_http_status(200) + expect(response.header[Gitlab::Workhorse::DETECT_HEADER]).to eq "true" end end end context 'when not signed in' do it 'redirects to the sign in page' do - get :raw, id: personal_snippet.to_param + get :raw, params: { id: personal_snippet.to_param } expect(response).to redirect_to(new_user_session_path) end @@ -493,7 +496,7 @@ describe SnippetsController do end it 'responds with status 200' do - get :raw, id: personal_snippet.to_param + get :raw, params: { id: personal_snippet.to_param } expect(assigns(:snippet)).to eq(personal_snippet) expect(response).to have_gitlab_http_status(200) @@ -502,7 +505,7 @@ describe SnippetsController do context 'when not signed in' do it 'redirects to the sign in page' do - get :raw, id: personal_snippet.to_param + get :raw, params: { id: personal_snippet.to_param } expect(response).to redirect_to(new_user_session_path) end @@ -518,7 +521,7 @@ describe SnippetsController do end it 'responds with status 200' do - get :raw, id: personal_snippet.to_param + get :raw, params: { id: personal_snippet.to_param } expect(assigns(:snippet)).to eq(personal_snippet) expect(response).to have_gitlab_http_status(200) @@ -530,13 +533,13 @@ describe SnippetsController do end it 'returns LF line endings by default' do - get :raw, id: personal_snippet.to_param + get :raw, params: { id: personal_snippet.to_param } expect(response.body).to eq("first line\nsecond line\nthird line") end it 'does not convert line endings when parameter present' do - get :raw, id: personal_snippet.to_param, line_ending: :raw + get :raw, params: { id: personal_snippet.to_param, line_ending: :raw } expect(response.body).to eq("first line\r\nsecond line\r\nthird line") end @@ -545,7 +548,7 @@ describe SnippetsController do context 'when not signed in' do it 'responds with status 200' do - get :raw, id: personal_snippet.to_param + get :raw, params: { id: personal_snippet.to_param } expect(assigns(:snippet)).to eq(personal_snippet) expect(response).to have_gitlab_http_status(200) @@ -560,7 +563,7 @@ describe SnippetsController do end it 'responds with status 404' do - get :raw, id: 'doesntexist' + get :raw, params: { id: 'doesntexist' } expect(response).to have_gitlab_http_status(404) end @@ -568,7 +571,7 @@ describe SnippetsController do context 'when not signed in' do it 'redirects to the sign in path' do - get :raw, id: 'doesntexist' + get :raw, params: { id: 'doesntexist' } expect(response).to redirect_to(new_user_session_path) end @@ -587,17 +590,17 @@ describe SnippetsController do describe 'POST #toggle_award_emoji' do it "toggles the award emoji" do expect do - post(:toggle_award_emoji, id: personal_snippet.to_param, name: "thumbsup") + post(:toggle_award_emoji, params: { id: personal_snippet.to_param, name: "thumbsup" }) end.to change { personal_snippet.award_emoji.count }.from(0).to(1) expect(response.status).to eq(200) end it "removes the already awarded emoji" do - post(:toggle_award_emoji, id: personal_snippet.to_param, name: "thumbsup") + post(:toggle_award_emoji, params: { id: personal_snippet.to_param, name: "thumbsup" }) expect do - post(:toggle_award_emoji, id: personal_snippet.to_param, name: "thumbsup") + post(:toggle_award_emoji, params: { id: personal_snippet.to_param, name: "thumbsup" }) end.to change { personal_snippet.award_emoji.count }.from(1).to(0) expect(response.status).to eq(200) @@ -611,7 +614,7 @@ describe SnippetsController do it 'renders json in a correct format' do sign_in(user) - post :preview_markdown, id: snippet, text: '*Markdown* text' + post :preview_markdown, params: { id: snippet, text: '*Markdown* text' } expect(JSON.parse(response.body).keys).to match_array(%w(body references)) end diff --git a/spec/controllers/uploads_controller_spec.rb b/spec/controllers/uploads_controller_spec.rb index 6420b70a54f..19142aa1272 100644 --- a/spec/controllers/uploads_controller_spec.rb +++ b/spec/controllers/uploads_controller_spec.rb @@ -8,11 +8,7 @@ end shared_examples 'content not cached without revalidation and no-store' do it 'ensures content will not be cached without revalidation' do # Fixed in newer versions of ActivePack, it will only output a single `private`. - if Gitlab.rails5? - expect(subject['Cache-Control']).to eq('max-age=0, private, must-revalidate, no-store') - else - expect(subject['Cache-Control']).to eq('max-age=0, private, must-revalidate, private, no-store') - end + expect(subject['Cache-Control']).to eq('max-age=0, private, must-revalidate, no-store') end end @@ -27,7 +23,7 @@ describe UploadsController do context 'when a user does not have permissions to upload a file' do it "returns 401 when the user is not logged in" do - post :create, model: model, id: snippet.id, format: :json + post :create, params: { model: model, id: snippet.id }, format: :json expect(response).to have_gitlab_http_status(401) end @@ -36,7 +32,7 @@ describe UploadsController do private_snippet = create(:personal_snippet, :private) sign_in(user) - post :create, model: model, id: private_snippet.id, format: :json + post :create, params: { model: model, id: private_snippet.id }, format: :json expect(response).to have_gitlab_http_status(404) end @@ -48,25 +44,25 @@ describe UploadsController do end it "returns an error without file" do - post :create, model: model, id: snippet.id, format: :json + post :create, params: { model: model, id: snippet.id }, format: :json expect(response).to have_gitlab_http_status(422) end it "returns an error with invalid model" do - expect { post :create, model: 'invalid', id: snippet.id, format: :json } + expect { post :create, params: { model: 'invalid', id: snippet.id }, format: :json } .to raise_error(ActionController::UrlGenerationError) end it "returns 404 status when object not found" do - post :create, model: model, id: 9999, format: :json + post :create, params: { model: model, id: 9999 }, format: :json expect(response).to have_gitlab_http_status(404) end context 'with valid image' do before do - post :create, model: 'personal_snippet', id: snippet.id, file: jpg, format: :json + post :create, params: { model: 'personal_snippet', id: snippet.id, file: jpg }, format: :json end it 'returns a content with original filename, new link, and correct type.' do @@ -86,7 +82,7 @@ describe UploadsController do context 'with valid non-image file' do before do - post :create, model: 'personal_snippet', id: snippet.id, file: txt, format: :json + post :create, params: { model: 'personal_snippet', id: snippet.id, file: txt }, format: :json end it 'returns a content with original filename, new link, and correct type.' do @@ -106,7 +102,7 @@ describe UploadsController do context 'temporal with valid image' do subject do - post :create, model: 'personal_snippet', file: jpg, format: :json + post :create, params: { model: 'personal_snippet', file: jpg }, format: :json end it 'returns a content with original filename, new link, and correct type.' do @@ -123,7 +119,7 @@ describe UploadsController do context 'temporal with valid non-image file' do subject do - post :create, model: 'personal_snippet', file: txt, format: :json + post :create, params: { model: 'personal_snippet', file: txt }, format: :json end it 'returns a content with original filename, new link, and correct type.' do @@ -147,7 +143,7 @@ describe UploadsController do context 'for PNG files' do it 'returns Content-Disposition: inline' do note = create(:note, :with_attachment, project: project) - get :show, model: 'note', mounted_as: 'attachment', id: note.id, filename: 'dk.png' + get :show, params: { model: 'note', mounted_as: 'attachment', id: note.id, filename: 'dk.png' } expect(response['Content-Disposition']).to start_with('inline;') end @@ -156,7 +152,7 @@ describe UploadsController do context 'for SVG files' do it 'returns Content-Disposition: attachment' do note = create(:note, :with_svg_attachment, project: project) - get :show, model: 'note', mounted_as: 'attachment', id: note.id, filename: 'unsanitized.svg' + get :show, params: { model: 'note', mounted_as: 'attachment', id: note.id, filename: 'unsanitized.svg' } expect(response['Content-Disposition']).to start_with('attachment;') end @@ -175,7 +171,7 @@ describe UploadsController do end it "redirects to the sign in page" do - get :show, model: "user", mounted_as: "avatar", id: user.id, filename: "dk.png" + get :show, params: { model: "user", mounted_as: "avatar", id: user.id, filename: "dk.png" } expect(response).to redirect_to(new_user_session_path) end @@ -183,14 +179,14 @@ describe UploadsController do context "when the user isn't blocked" do it "responds with status 200" do - get :show, model: "user", mounted_as: "avatar", id: user.id, filename: "dk.png" + get :show, params: { model: "user", mounted_as: "avatar", id: user.id, filename: "dk.png" } expect(response).to have_gitlab_http_status(200) end it_behaves_like 'content not cached without revalidation and no-store' do subject do - get :show, model: 'user', mounted_as: 'avatar', id: user.id, filename: 'dk.png' + get :show, params: { model: 'user', mounted_as: 'avatar', id: user.id, filename: 'dk.png' } response end @@ -200,14 +196,14 @@ describe UploadsController do context "when not signed in" do it "responds with status 200" do - get :show, model: "user", mounted_as: "avatar", id: user.id, filename: "dk.png" + get :show, params: { model: "user", mounted_as: "avatar", id: user.id, filename: "dk.png" } expect(response).to have_gitlab_http_status(200) end it_behaves_like 'content not cached without revalidation' do subject do - get :show, model: 'user', mounted_as: 'avatar', id: user.id, filename: 'dk.png' + get :show, params: { model: 'user', mounted_as: 'avatar', id: user.id, filename: 'dk.png' } response end @@ -225,14 +221,14 @@ describe UploadsController do context "when not signed in" do it "responds with status 200" do - get :show, model: "project", mounted_as: "avatar", id: project.id, filename: "dk.png" + get :show, params: { model: "project", mounted_as: "avatar", id: project.id, filename: "dk.png" } expect(response).to have_gitlab_http_status(200) end it_behaves_like 'content not cached without revalidation' do subject do - get :show, model: 'project', mounted_as: 'avatar', id: project.id, filename: 'dk.png' + get :show, params: { model: 'project', mounted_as: 'avatar', id: project.id, filename: 'dk.png' } response end @@ -245,14 +241,14 @@ describe UploadsController do end it "responds with status 200" do - get :show, model: "project", mounted_as: "avatar", id: project.id, filename: "dk.png" + get :show, params: { model: "project", mounted_as: "avatar", id: project.id, filename: "dk.png" } expect(response).to have_gitlab_http_status(200) end it_behaves_like 'content not cached without revalidation and no-store' do subject do - get :show, model: 'project', mounted_as: 'avatar', id: project.id, filename: 'dk.png' + get :show, params: { model: 'project', mounted_as: 'avatar', id: project.id, filename: 'dk.png' } response end @@ -267,7 +263,7 @@ describe UploadsController do context "when not signed in" do it "redirects to the sign in page" do - get :show, model: "project", mounted_as: "avatar", id: project.id, filename: "dk.png" + get :show, params: { model: "project", mounted_as: "avatar", id: project.id, filename: "dk.png" } expect(response).to redirect_to(new_user_session_path) end @@ -290,7 +286,7 @@ describe UploadsController do end it "redirects to the sign in page" do - get :show, model: "project", mounted_as: "avatar", id: project.id, filename: "dk.png" + get :show, params: { model: "project", mounted_as: "avatar", id: project.id, filename: "dk.png" } expect(response).to redirect_to(new_user_session_path) end @@ -298,14 +294,14 @@ describe UploadsController do context "when the user isn't blocked" do it "responds with status 200" do - get :show, model: "project", mounted_as: "avatar", id: project.id, filename: "dk.png" + get :show, params: { model: "project", mounted_as: "avatar", id: project.id, filename: "dk.png" } expect(response).to have_gitlab_http_status(200) end it_behaves_like 'content not cached without revalidation and no-store' do subject do - get :show, model: 'project', mounted_as: 'avatar', id: project.id, filename: 'dk.png' + get :show, params: { model: 'project', mounted_as: 'avatar', id: project.id, filename: 'dk.png' } response end @@ -315,7 +311,7 @@ describe UploadsController do context "when the user doesn't have access to the project" do it "responds with status 404" do - get :show, model: "project", mounted_as: "avatar", id: project.id, filename: "dk.png" + get :show, params: { model: "project", mounted_as: "avatar", id: project.id, filename: "dk.png" } expect(response).to have_gitlab_http_status(404) end @@ -330,14 +326,14 @@ describe UploadsController do context "when the group is public" do context "when not signed in" do it "responds with status 200" do - get :show, model: "group", mounted_as: "avatar", id: group.id, filename: "dk.png" + get :show, params: { model: "group", mounted_as: "avatar", id: group.id, filename: "dk.png" } expect(response).to have_gitlab_http_status(200) end it_behaves_like 'content not cached without revalidation' do subject do - get :show, model: 'group', mounted_as: 'avatar', id: group.id, filename: 'dk.png' + get :show, params: { model: 'group', mounted_as: 'avatar', id: group.id, filename: 'dk.png' } response end @@ -350,14 +346,14 @@ describe UploadsController do end it "responds with status 200" do - get :show, model: "group", mounted_as: "avatar", id: group.id, filename: "dk.png" + get :show, params: { model: "group", mounted_as: "avatar", id: group.id, filename: "dk.png" } expect(response).to have_gitlab_http_status(200) end it_behaves_like 'content not cached without revalidation and no-store' do subject do - get :show, model: 'group', mounted_as: 'avatar', id: group.id, filename: 'dk.png' + get :show, params: { model: 'group', mounted_as: 'avatar', id: group.id, filename: 'dk.png' } response end @@ -386,7 +382,7 @@ describe UploadsController do end it "redirects to the sign in page" do - get :show, model: "group", mounted_as: "avatar", id: group.id, filename: "dk.png" + get :show, params: { model: "group", mounted_as: "avatar", id: group.id, filename: "dk.png" } expect(response).to redirect_to(new_user_session_path) end @@ -394,14 +390,14 @@ describe UploadsController do context "when the user isn't blocked" do it "responds with status 200" do - get :show, model: "group", mounted_as: "avatar", id: group.id, filename: "dk.png" + get :show, params: { model: "group", mounted_as: "avatar", id: group.id, filename: "dk.png" } expect(response).to have_gitlab_http_status(200) end it_behaves_like 'content not cached without revalidation and no-store' do subject do - get :show, model: 'group', mounted_as: 'avatar', id: group.id, filename: 'dk.png' + get :show, params: { model: 'group', mounted_as: 'avatar', id: group.id, filename: 'dk.png' } response end @@ -411,7 +407,7 @@ describe UploadsController do context "when the user doesn't have access to the project" do it "responds with status 404" do - get :show, model: "group", mounted_as: "avatar", id: group.id, filename: "dk.png" + get :show, params: { model: "group", mounted_as: "avatar", id: group.id, filename: "dk.png" } expect(response).to have_gitlab_http_status(404) end @@ -431,14 +427,14 @@ describe UploadsController do context "when not signed in" do it "responds with status 200" do - get :show, model: "note", mounted_as: "attachment", id: note.id, filename: "dk.png" + get :show, params: { model: "note", mounted_as: "attachment", id: note.id, filename: "dk.png" } expect(response).to have_gitlab_http_status(200) end it_behaves_like 'content not cached without revalidation' do subject do - get :show, model: 'note', mounted_as: 'attachment', id: note.id, filename: 'dk.png' + get :show, params: { model: 'note', mounted_as: 'attachment', id: note.id, filename: 'dk.png' } response end @@ -451,14 +447,14 @@ describe UploadsController do end it "responds with status 200" do - get :show, model: "note", mounted_as: "attachment", id: note.id, filename: "dk.png" + get :show, params: { model: "note", mounted_as: "attachment", id: note.id, filename: "dk.png" } expect(response).to have_gitlab_http_status(200) end it_behaves_like 'content not cached without revalidation and no-store' do subject do - get :show, model: 'note', mounted_as: 'attachment', id: note.id, filename: 'dk.png' + get :show, params: { model: 'note', mounted_as: 'attachment', id: note.id, filename: 'dk.png' } response end @@ -473,7 +469,7 @@ describe UploadsController do context "when not signed in" do it "redirects to the sign in page" do - get :show, model: "note", mounted_as: "attachment", id: note.id, filename: "dk.png" + get :show, params: { model: "note", mounted_as: "attachment", id: note.id, filename: "dk.png" } expect(response).to redirect_to(new_user_session_path) end @@ -496,7 +492,7 @@ describe UploadsController do end it "redirects to the sign in page" do - get :show, model: "note", mounted_as: "attachment", id: note.id, filename: "dk.png" + get :show, params: { model: "note", mounted_as: "attachment", id: note.id, filename: "dk.png" } expect(response).to redirect_to(new_user_session_path) end @@ -504,14 +500,14 @@ describe UploadsController do context "when the user isn't blocked" do it "responds with status 200" do - get :show, model: "note", mounted_as: "attachment", id: note.id, filename: "dk.png" + get :show, params: { model: "note", mounted_as: "attachment", id: note.id, filename: "dk.png" } expect(response).to have_gitlab_http_status(200) end it_behaves_like 'content not cached without revalidation and no-store' do subject do - get :show, model: 'note', mounted_as: 'attachment', id: note.id, filename: 'dk.png' + get :show, params: { model: 'note', mounted_as: 'attachment', id: note.id, filename: 'dk.png' } response end @@ -521,7 +517,7 @@ describe UploadsController do context "when the user doesn't have access to the project" do it "responds with status 404" do - get :show, model: "note", mounted_as: "attachment", id: note.id, filename: "dk.png" + get :show, params: { model: "note", mounted_as: "attachment", id: note.id, filename: "dk.png" } expect(response).to have_gitlab_http_status(404) end @@ -536,14 +532,14 @@ describe UploadsController do context 'when not signed in' do it 'responds with status 200' do - get :show, model: 'appearance', mounted_as: 'header_logo', id: appearance.id, filename: 'dk.png' + get :show, params: { model: 'appearance', mounted_as: 'header_logo', id: appearance.id, filename: 'dk.png' } expect(response).to have_gitlab_http_status(200) end it_behaves_like 'content not cached without revalidation' do subject do - get :show, model: 'appearance', mounted_as: 'header_logo', id: appearance.id, filename: 'dk.png' + get :show, params: { model: 'appearance', mounted_as: 'header_logo', id: appearance.id, filename: 'dk.png' } response end @@ -556,14 +552,14 @@ describe UploadsController do context 'when not signed in' do it 'responds with status 200' do - get :show, model: 'appearance', mounted_as: 'logo', id: appearance.id, filename: 'dk.png' + get :show, params: { model: 'appearance', mounted_as: 'logo', id: appearance.id, filename: 'dk.png' } expect(response).to have_gitlab_http_status(200) end it_behaves_like 'content not cached without revalidation' do subject do - get :show, model: 'appearance', mounted_as: 'logo', id: appearance.id, filename: 'dk.png' + get :show, params: { model: 'appearance', mounted_as: 'logo', id: appearance.id, filename: 'dk.png' } response end @@ -577,7 +573,7 @@ describe UploadsController do context 'has a valid filename on the original file' do it 'successfully returns the file' do - get :show, model: 'appearance', mounted_as: 'favicon', id: appearance.id, filename: 'dk.png' + get :show, params: { model: 'appearance', mounted_as: 'favicon', id: appearance.id, filename: 'dk.png' } expect(response).to have_gitlab_http_status(200) expect(response.header['Content-Disposition']).to end_with 'filename="dk.png"' @@ -586,7 +582,7 @@ describe UploadsController do context 'has an invalid filename on the original file' do it 'returns a 404' do - get :show, model: 'appearance', mounted_as: 'favicon', id: appearance.id, filename: 'bogus.png' + get :show, params: { model: 'appearance', mounted_as: 'favicon', id: appearance.id, filename: 'bogus.png' } expect(response).to have_gitlab_http_status(404) end diff --git a/spec/controllers/user_callouts_controller_spec.rb b/spec/controllers/user_callouts_controller_spec.rb index 48e2ff75cac..c71d75a3e7f 100644 --- a/spec/controllers/user_callouts_controller_spec.rb +++ b/spec/controllers/user_callouts_controller_spec.rb @@ -8,7 +8,7 @@ describe UserCalloutsController do end describe "POST #create" do - subject { post :create, feature_name: feature_name, format: :json } + subject { post :create, params: { feature_name: feature_name }, format: :json } context 'with valid feature name' do let(:feature_name) { UserCallout.feature_names.keys.first } diff --git a/spec/controllers/users/terms_controller_spec.rb b/spec/controllers/users/terms_controller_spec.rb index 0d77e91a67d..cbfd2b17864 100644 --- a/spec/controllers/users/terms_controller_spec.rb +++ b/spec/controllers/users/terms_controller_spec.rb @@ -40,7 +40,7 @@ describe Users::TermsController do describe 'POST #accept' do it 'saves that the user accepted the terms' do - post :accept, id: term.id + post :accept, params: { id: term.id } agreement = user.term_agreements.find_by(term: term) @@ -48,7 +48,7 @@ describe Users::TermsController do end it 'redirects to a path when specified' do - post :accept, id: term.id, redirect: groups_path + post :accept, params: { id: term.id, redirect: groups_path } expect(response).to redirect_to(groups_path) end @@ -56,14 +56,14 @@ describe Users::TermsController do it 'redirects to the referer when no redirect specified' do request.env["HTTP_REFERER"] = groups_url - post :accept, id: term.id + post :accept, params: { id: term.id } expect(response).to redirect_to(groups_path) end context 'redirecting to another domain' do it 'is prevented when passing a redirect param' do - post :accept, id: term.id, redirect: '//example.com/random/path' + post :accept, params: { id: term.id, redirect: '//example.com/random/path' } expect(response).to redirect_to(root_path) end @@ -71,7 +71,7 @@ describe Users::TermsController do it 'is prevented when redirecting to the referer' do request.env["HTTP_REFERER"] = 'http://example.com/and/a/path' - post :accept, id: term.id + post :accept, params: { id: term.id } expect(response).to redirect_to(root_path) end @@ -80,7 +80,7 @@ describe Users::TermsController do describe 'POST #decline' do it 'stores that the user declined the terms' do - post :decline, id: term.id + post :decline, params: { id: term.id } agreement = user.term_agreements.find_by(term: term) @@ -88,7 +88,7 @@ describe Users::TermsController do end it 'signs out the user' do - post :decline, id: term.id + post :decline, params: { id: term.id } expect(response).to redirect_to(root_path) expect(assigns(:current_user)).to be_nil diff --git a/spec/controllers/users_controller_spec.rb b/spec/controllers/users_controller_spec.rb index fe438e71e9e..27edf226ca3 100644 --- a/spec/controllers/users_controller_spec.rb +++ b/spec/controllers/users_controller_spec.rb @@ -15,7 +15,7 @@ describe UsersController do end it 'renders the show template' do - get :show, username: user.username + get :show, params: { username: user.username } expect(response).to be_success expect(response).to render_template('show') @@ -24,7 +24,7 @@ describe UsersController do describe 'when logged out' do it 'renders the show template' do - get :show, username: user.username + get :show, params: { username: user.username } expect(response).to have_gitlab_http_status(200) expect(response).to render_template('show') @@ -39,7 +39,7 @@ describe UsersController do context 'when logged out' do it 'redirects to login page' do - get :show, username: user.username + get :show, params: { username: user.username } expect(response).to redirect_to new_user_session_path end end @@ -50,7 +50,7 @@ describe UsersController do end it 'renders show' do - get :show, username: user.username + get :show, params: { username: user.username } expect(response).to have_gitlab_http_status(200) expect(response).to render_template('show') end @@ -60,7 +60,7 @@ describe UsersController do context 'when a user by that username does not exist' do context 'when logged out' do it 'redirects to login page' do - get :show, username: 'nonexistent' + get :show, params: { username: 'nonexistent' } expect(response).to redirect_to new_user_session_path end end @@ -71,7 +71,7 @@ describe UsersController do end it 'renders 404' do - get :show, username: 'nonexistent' + get :show, params: { username: 'nonexistent' } expect(response).to have_gitlab_http_status(404) end end @@ -87,7 +87,7 @@ describe UsersController do end it 'loads events' do - get :show, username: user, format: :json + get :show, params: { username: user }, format: :json expect(assigns(:events)).not_to be_empty end @@ -96,7 +96,7 @@ describe UsersController do allow(Ability).to receive(:allowed?).and_call_original expect(Ability).to receive(:allowed?).with(user, :read_cross_project) { false } - get :show, username: user, format: :json + get :show, params: { username: user }, format: :json expect(assigns(:events)).to be_empty end @@ -104,7 +104,7 @@ describe UsersController do it 'hides events if the user has a private profile' do Gitlab::DataBuilder::Push.build_sample(project, private_user) - get :show, username: private_user.username, format: :json + get :show, params: { username: private_user.username }, format: :json expect(assigns(:events)).to be_empty end @@ -125,7 +125,7 @@ describe UsersController do push_data = Gitlab::DataBuilder::Push.build_sample(project, public_user) EventCreateService.new.push(project, public_user, push_data) - get :calendar, username: public_user.username, format: :json + get :calendar, params: { username: public_user.username }, format: :json expect(response).to have_gitlab_http_status(200) end @@ -136,7 +136,7 @@ describe UsersController do push_data = Gitlab::DataBuilder::Push.build_sample(project, private_user) EventCreateService.new.push(project, private_user, push_data) - get :calendar, username: private_user.username, format: :json + get :calendar, params: { username: private_user.username }, format: :json expect(response).to have_gitlab_http_status(:not_found) end @@ -161,7 +161,7 @@ describe UsersController do end it 'includes forked projects' do - get :calendar, username: user.username + get :calendar, params: { username: user.username } expect(assigns(:contributions_calendar).projects.count).to eq(2) end end @@ -179,7 +179,7 @@ describe UsersController do end it 'assigns @calendar_date' do - get :calendar_activities, username: user.username, date: '2014-07-31' + get :calendar_activities, params: { username: user.username, date: '2014-07-31' } expect(assigns(:calendar_date)).to eq(Date.parse('2014-07-31')) end @@ -189,7 +189,7 @@ describe UsersController do push_data = Gitlab::DataBuilder::Push.build_sample(project, public_user) EventCreateService.new.push(project, public_user, push_data) - get :calendar_activities, username: public_user.username + get :calendar_activities, params: { username: public_user.username } expect(assigns[:events]).not_to be_empty end end @@ -199,7 +199,7 @@ describe UsersController do push_data = Gitlab::DataBuilder::Push.build_sample(project, private_user) EventCreateService.new.push(project, private_user, push_data) - get :calendar_activities, username: private_user.username + get :calendar_activities, params: { username: private_user.username } expect(response).to have_gitlab_http_status(:not_found) end end @@ -213,7 +213,7 @@ describe UsersController do context 'format html' do it 'renders snippets page' do - get :snippets, username: user.username + get :snippets, params: { username: user.username } expect(response).to have_gitlab_http_status(200) expect(response).to render_template('show') end @@ -221,7 +221,7 @@ describe UsersController do context 'format json' do it 'response with snippets json data' do - get :snippets, username: user.username, format: :json + get :snippets, params: { username: user.username }, format: :json expect(response).to have_gitlab_http_status(200) expect(JSON.parse(response.body)).to have_key('html') end @@ -235,7 +235,7 @@ describe UsersController do context 'when user exists' do it 'returns JSON indicating the user exists' do - get :exists, username: user.username + get :exists, params: { username: user.username } expected_json = { exists: true }.to_json expect(response.body).to eq(expected_json) @@ -245,7 +245,7 @@ describe UsersController do let(:user) { create(:user, username: 'CamelCaseUser') } it 'returns JSON indicating the user exists' do - get :exists, username: user.username.downcase + get :exists, params: { username: user.username.downcase } expected_json = { exists: true }.to_json expect(response.body).to eq(expected_json) @@ -255,7 +255,7 @@ describe UsersController do context 'when the user does not exist' do it 'returns JSON indicating the user does not exist' do - get :exists, username: 'foo' + get :exists, params: { username: 'foo' } expected_json = { exists: false }.to_json expect(response.body).to eq(expected_json) @@ -265,7 +265,7 @@ describe UsersController do let(:redirect_route) { user.namespace.redirect_routes.create(path: 'old-username') } it 'returns JSON indicating a user by that username does not exist' do - get :exists, username: 'old-username' + get :exists, params: { username: 'old-username' } expected_json = { exists: false }.to_json expect(response.body).to eq(expected_json) @@ -286,7 +286,7 @@ describe UsersController do context 'with exactly matching casing' do it 'responds with success' do - get :show, username: user.username + get :show, params: { username: user.username } expect(response).to be_success end @@ -294,7 +294,7 @@ describe UsersController do context 'with different casing' do it 'redirects to the correct casing' do - get :show, username: user.username.downcase + get :show, params: { username: user.username.downcase } expect(response).to redirect_to(user) expect(controller).not_to set_flash[:notice] @@ -306,7 +306,7 @@ describe UsersController do let(:redirect_route) { user.namespace.redirect_routes.create(path: 'old-path') } it 'redirects to the canonical path' do - get :show, username: redirect_route.path + get :show, params: { username: redirect_route.path } expect(response).to redirect_to(user) expect(controller).to set_flash[:notice].to(user_moved_message(redirect_route, user)) @@ -316,7 +316,7 @@ describe UsersController do let(:redirect_route) { user.namespace.redirect_routes.create(path: 'http') } it 'does not modify the requested host' do - get :show, username: redirect_route.path + get :show, params: { username: redirect_route.path } expect(response).to redirect_to(user) expect(controller).to set_flash[:notice].to(user_moved_message(redirect_route, user)) @@ -327,7 +327,7 @@ describe UsersController do let(:redirect_route) { user.namespace.redirect_routes.create(path: 'ser') } it 'redirects to the canonical path' do - get :show, username: redirect_route.path + get :show, params: { username: redirect_route.path } expect(response).to redirect_to(user) expect(controller).to set_flash[:notice].to(user_moved_message(redirect_route, user)) @@ -342,7 +342,7 @@ describe UsersController do context 'with exactly matching casing' do it 'responds with success' do - get :projects, username: user.username + get :projects, params: { username: user.username } expect(response).to be_success end @@ -350,7 +350,7 @@ describe UsersController do context 'with different casing' do it 'redirects to the correct casing' do - get :projects, username: user.username.downcase + get :projects, params: { username: user.username.downcase } expect(response).to redirect_to(user_projects_path(user)) expect(controller).not_to set_flash[:notice] @@ -362,7 +362,7 @@ describe UsersController do let(:redirect_route) { user.namespace.redirect_routes.create(path: 'old-path') } it 'redirects to the canonical path' do - get :projects, username: redirect_route.path + get :projects, params: { username: redirect_route.path } expect(response).to redirect_to(user_projects_path(user)) expect(controller).to set_flash[:notice].to(user_moved_message(redirect_route, user)) @@ -372,7 +372,7 @@ describe UsersController do let(:redirect_route) { user.namespace.redirect_routes.create(path: 'http') } it 'does not modify the requested host' do - get :projects, username: redirect_route.path + get :projects, params: { username: redirect_route.path } expect(response).to redirect_to(user_projects_path(user)) expect(controller).to set_flash[:notice].to(user_moved_message(redirect_route, user)) @@ -384,7 +384,7 @@ describe UsersController do # I.e. /users/ser should not become /ufoos/ser it 'does not modify the /users part of the path' do - get :projects, username: redirect_route.path + get :projects, params: { username: redirect_route.path } expect(response).to redirect_to(user_projects_path(user)) expect(controller).to set_flash[:notice].to(user_moved_message(redirect_route, user)) diff --git a/spec/db/importers/common_metrics_importer_spec.rb b/spec/db/importers/common_metrics_importer_spec.rb index 68260820958..6133b17ac61 100644 --- a/spec/db/importers/common_metrics_importer_spec.rb +++ b/spec/db/importers/common_metrics_importer_spec.rb @@ -4,12 +4,18 @@ require 'rails_helper' require Rails.root.join("db", "importers", "common_metrics_importer.rb") describe Importers::PrometheusMetric do + let(:existing_group_titles) do + ::PrometheusMetric::GROUP_DETAILS.each_with_object({}) do |(key, value), memo| + memo[key] = value[:group_title] + end + end + it 'group enum equals ::PrometheusMetric' do expect(described_class.groups).to eq(::PrometheusMetric.groups) end it 'GROUP_TITLES equals ::PrometheusMetric' do - expect(described_class::GROUP_TITLES).to eq(::PrometheusMetric::GROUP_TITLES) + expect(described_class::GROUP_TITLES).to eq(existing_group_titles) end end diff --git a/spec/db/schema_spec.rb b/spec/db/schema_spec.rb index e8584846b56..7c505ee0d43 100644 --- a/spec/db/schema_spec.rb +++ b/spec/db/schema_spec.rb @@ -54,7 +54,8 @@ describe 'Database schema' do user_agent_details: %w[subject_id], users: %w[color_scheme_id created_by_id theme_id], users_star_projects: %w[user_id], - web_hooks: %w[service_id] + web_hooks: %w[service_id], + suggestions: %w[commit_id] }.with_indifferent_access.freeze context 'for table' do diff --git a/spec/factories/appearances.rb b/spec/factories/appearances.rb index 18c7453bd1b..dd5129229d4 100644 --- a/spec/factories/appearances.rb +++ b/spec/factories/appearances.rb @@ -15,6 +15,10 @@ FactoryBot.define do header_logo { fixture_file_upload('spec/fixtures/dk.png') } end + trait :with_favicon do + favicon { fixture_file_upload('spec/fixtures/dk.png') } + end + trait :with_logos do with_logo with_header_logo diff --git a/spec/factories/notes.rb b/spec/factories/notes.rb index 2d1f48bf249..c51f2f958f9 100644 --- a/spec/factories/notes.rb +++ b/spec/factories/notes.rb @@ -64,6 +64,21 @@ FactoryBot.define do resolved_at { Time.now } resolved_by { create(:user) } end + + factory :image_diff_note_on_merge_request do + position do + Gitlab::Diff::Position.new( + old_path: "files/images/any_image.png", + new_path: "files/images/any_image.png", + width: 10, + height: 10, + x: 1, + y: 1, + diff_refs: diff_refs, + position_type: "image" + ) + end + end end factory :diff_note_on_commit, traits: [:on_commit], class: DiffNote do diff --git a/spec/factories/pool_repositories.rb b/spec/factories/pool_repositories.rb index 265a4643f46..36e54cf44b4 100644 --- a/spec/factories/pool_repositories.rb +++ b/spec/factories/pool_repositories.rb @@ -15,6 +15,10 @@ FactoryBot.define do state :failed end + trait :obsolete do + state :obsolete + end + trait :ready do state :ready diff --git a/spec/factories/project_repositories.rb b/spec/factories/project_repositories.rb new file mode 100644 index 00000000000..39e8ea2e11e --- /dev/null +++ b/spec/factories/project_repositories.rb @@ -0,0 +1,12 @@ +# frozen_string_literal: true + +FactoryBot.define do + factory :project_repository do + project + + after(:build) do |project_repository, _| + project_repository.shard_name = project_repository.project.repository_storage + project_repository.disk_path = project_repository.project.disk_path + end + end +end diff --git a/spec/factories/releases.rb b/spec/factories/releases.rb index d80c65cf8bb..cab6b4a811f 100644 --- a/spec/factories/releases.rb +++ b/spec/factories/releases.rb @@ -1,7 +1,15 @@ FactoryBot.define do factory :release do tag "v1.1.0" + sha 'b83d6e391c22777fca1ed3012fce84f633d7fed0' + name { tag } description "Awesome release" project + author + + trait :legacy do + sha nil + author nil + end end end diff --git a/spec/factories/suggestions.rb b/spec/factories/suggestions.rb new file mode 100644 index 00000000000..307523cc061 --- /dev/null +++ b/spec/factories/suggestions.rb @@ -0,0 +1,20 @@ +# frozen_string_literal: true + +FactoryBot.define do + factory :suggestion do + relative_order 0 + association :note, factory: :diff_note_on_merge_request + from_content " vars = {\n" + to_content " vars = [\n" + + trait :unappliable do + from_content "foo" + to_content "foo" + end + + trait :applied do + applied true + commit_id { RepoHelpers.sample_commit.id } + end + end +end diff --git a/spec/features/admin/admin_disables_git_access_protocol_spec.rb b/spec/features/admin/admin_disables_git_access_protocol_spec.rb index 91c22e7ad82..f066b088800 100644 --- a/spec/features/admin/admin_disables_git_access_protocol_spec.rb +++ b/spec/features/admin/admin_disables_git_access_protocol_spec.rb @@ -1,7 +1,8 @@ require 'rails_helper' -describe 'Admin disables Git access protocol' do +describe 'Admin disables Git access protocol', :js do include StubENV + include MobileHelpers let(:project) { create(:project, :empty_repo) } let(:admin) { create(:admin) } @@ -20,7 +21,24 @@ describe 'Admin disables Git access protocol' do visit_project expect(page).to have_content("git clone #{project.ssh_url_to_repo}") - expect(page).not_to have_selector('#clone-dropdown') + + find('.clone-dropdown-btn').click + + within('.git-clone-holder') do + expect(page).to have_content('Clone with SSH') + expect(page).not_to have_content('Clone with HTTP') + end + end + + context 'mobile component' do + it 'shows only the SSH clone information' do + resize_screen_xs + visit_project + find('.dropdown-toggle').click + + expect(page).to have_content('Copy SSH clone URL') + expect(page).not_to have_content('Copy HTTP clone URL') + end end end @@ -31,9 +49,25 @@ describe 'Admin disables Git access protocol' do it 'shows only HTTP url' do visit_project + find('.clone-dropdown-btn').click expect(page).to have_content("git clone #{project.http_url_to_repo}") - expect(page).not_to have_selector('#clone-dropdown') + + within('.git-clone-holder') do + expect(page).to have_content('Clone with HTTP') + expect(page).not_to have_content('Clone with SSH') + end + end + + context 'mobile component' do + it 'shows only the HTTP clone information' do + resize_screen_xs + visit_project + find('.dropdown-toggle').click + + expect(page).to have_content('Copy HTTP clone URL') + expect(page).not_to have_content('Copy SSH clone URL') + end end end @@ -46,7 +80,24 @@ describe 'Admin disables Git access protocol' do visit_project expect(page).to have_content("git clone #{project.ssh_url_to_repo}") - expect(page).to have_selector('#clone-dropdown') + + find('.clone-dropdown-btn').click + + within('.git-clone-holder') do + expect(page).to have_content('Clone with SSH') + expect(page).to have_content('Clone with HTTP') + end + end + + context 'mobile component' do + it 'shows both SSH and HTTP clone information' do + resize_screen_xs + visit_project + find('.dropdown-toggle').click + + expect(page).to have_content('Copy HTTP clone URL') + expect(page).to have_content('Copy SSH clone URL') + end end end diff --git a/spec/features/group_variables_spec.rb b/spec/features/group_variables_spec.rb index 89e0cdd8ed7..57e3ddfb39c 100644 --- a/spec/features/group_variables_spec.rb +++ b/spec/features/group_variables_spec.rb @@ -7,7 +7,7 @@ describe 'Group variables', :js do let(:page_path) { group_settings_ci_cd_path(group) } before do - group.add_maintainer(user) + group.add_owner(user) gitlab_sign_in(user) visit page_path diff --git a/spec/features/groups/milestone_spec.rb b/spec/features/groups/milestone_spec.rb index 174840794ed..d57eb87ca77 100644 --- a/spec/features/groups/milestone_spec.rb +++ b/spec/features/groups/milestone_spec.rb @@ -81,7 +81,7 @@ describe 'Group milestones' do description: 'Lorem Ipsum is simply dummy text' ) end - let!(:active_project_milestone2) { create(:milestone, project: other_project, state: 'active', title: 'v1.0') } + let!(:active_project_milestone2) { create(:milestone, project: other_project, state: 'active', title: 'v1.1') } let!(:closed_project_milestone1) { create(:milestone, project: project, state: 'closed', title: 'v2.0') } let!(:closed_project_milestone2) { create(:milestone, project: other_project, state: 'closed', title: 'v2.0') } let!(:active_group_milestone) { create(:milestone, group: group, state: 'active', title: 'GL-113') } @@ -104,7 +104,7 @@ describe 'Group milestones' do legacy_milestone = GroupMilestone.build_collection(group, group.projects, { state: 'active' }).first expect(page).to have_selector("#milestone_#{active_group_milestone.id}", count: 1) - expect(page).to have_selector("#milestone_#{legacy_milestone.milestones.first.id}", count: 1) + expect(page).to have_selector("#milestone_#{legacy_milestone.milestone.id}", count: 1) end it 'shows milestone detail and supports its edit' do @@ -121,6 +121,7 @@ describe 'Group milestones' do it 'renders milestones' do expect(page).to have_content('v1.0') + expect(page).to have_content('v1.1') expect(page).to have_content('GL-113') expect(page).to have_link( '1 Issue', diff --git a/spec/features/groups/milestones_sorting_spec.rb b/spec/features/groups/milestones_sorting_spec.rb index bc226ff41c1..7bc015ea28f 100644 --- a/spec/features/groups/milestones_sorting_spec.rb +++ b/spec/features/groups/milestones_sorting_spec.rb @@ -42,6 +42,7 @@ describe 'Milestones sorting', :js do expect(page).to have_button('Due later') + # assert descending sorting within '.milestones' do expect(page.all('ul.content-list > li').first.text).to include('v1.0') expect(page.all('ul.content-list > li')[1].text).to include('v3.0') diff --git a/spec/features/groups/show_spec.rb b/spec/features/groups/show_spec.rb index 4e6f73ef58a..9671a4d8c49 100644 --- a/spec/features/groups/show_spec.rb +++ b/spec/features/groups/show_spec.rb @@ -65,7 +65,7 @@ describe 'Group show page' do context 'when subgroups are supported', :js, :nested_groups do before do - allow(Group).to receive(:supports_nested_groups?) { true } + allow(Group).to receive(:supports_nested_objects?) { true } visit path end @@ -76,7 +76,7 @@ describe 'Group show page' do context 'when subgroups are not supported' do before do - allow(Group).to receive(:supports_nested_groups?) { false } + allow(Group).to receive(:supports_nested_objects?) { false } visit path end diff --git a/spec/features/help_pages_spec.rb b/spec/features/help_pages_spec.rb index c29dfb01381..e24b1f4349d 100644 --- a/spec/features/help_pages_spec.rb +++ b/spec/features/help_pages_spec.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + require 'spec_helper' describe 'Help Pages' do @@ -52,23 +54,21 @@ describe 'Help Pages' do end end - context 'in a production environment with version check enabled', :js do + context 'in a production environment with version check enabled' do before do - allow(Rails.env).to receive(:production?) { true } stub_application_setting(version_check_enabled: true) - allow_any_instance_of(VersionCheck).to receive(:url) { '/version-check-url' } + + allow(Rails.env).to receive(:production?).and_return(true) + allow(VersionCheck).to receive(:url).and_return('/version-check-url') sign_in(create(:user)) visit help_path end it 'has a version check image' do - expect(find('.js-version-status-badge', visible: false)['src']).to end_with('/version-check-url') - end - - it 'hides the version check image if the image request fails' do - # We use '--load-images=yes' with poltergeist so the image fails to load - expect(page).to have_selector('.js-version-status-badge', visible: false) + # Check `data-src` due to lazy image loading + expect(find('.js-version-status-badge', visible: false)['data-src']) + .to end_with('/version-check-url') end end diff --git a/spec/features/ics/project_issues_spec.rb b/spec/features/ics/project_issues_spec.rb index 2ca3d52a5be..54143595e6b 100644 --- a/spec/features/ics/project_issues_spec.rb +++ b/spec/features/ics/project_issues_spec.rb @@ -72,5 +72,14 @@ describe 'Project Issues Calendar Feed' do expect(body).to have_text('TRANSP:TRANSPARENT') end end + + context 'sorted by priority' do + it 'renders calendar feed' do + visit project_issues_path(project, :ics, sort: 'priority', feed_token: user.feed_token) + + expect(response_headers['Content-Type']).to have_content('text/calendar') + expect(body).to have_text('BEGIN:VCALENDAR') + end + end end end diff --git a/spec/features/issues/gfm_autocomplete_spec.rb b/spec/features/issues/gfm_autocomplete_spec.rb index d7531d5fcd9..3b7a17ef355 100644 --- a/spec/features/issues/gfm_autocomplete_spec.rb +++ b/spec/features/issues/gfm_autocomplete_spec.rb @@ -3,6 +3,8 @@ require 'rails_helper' describe 'GFM autocomplete', :js do let(:issue_xss_title) { 'This will execute alert<img src=x onerror=alert(2)<img src=x onerror=alert(1)>' } let(:user_xss_title) { 'eve <img src=x onerror=alert(2)<img src=x onerror=alert(1)>' } + let(:label_xss_title) { 'alert label <img src=x onerror="alert(\'Hello xss\');" a'} + let(:milestone_xss_title) { 'alert milestone <img src=x onerror="alert(\'Hello xss\');" a' } let(:user_xss) { create(:user, name: user_xss_title, username: 'xss.user') } let(:user) { create(:user, name: '💃speciąl someone💃', username: 'someone.special') } @@ -25,10 +27,14 @@ describe 'GFM autocomplete', :js do simulate_input('#issue-description', "@#{user.name[0...3]}") + wait_for_requests + find('.atwho-view .cur').click click_button 'Save changes' + wait_for_requests + expect(find('.description')).to have_content(user.to_reference) end @@ -47,6 +53,8 @@ describe 'GFM autocomplete', :js do find('#note-body').native.send_keys('#') end + wait_for_requests + expect(page).to have_selector('.atwho-container') page.within '.atwho-container #at-view-issues' do @@ -59,6 +67,8 @@ describe 'GFM autocomplete', :js do find('#note-body').native.send_keys('@ev') end + wait_for_requests + expect(page).to have_selector('.atwho-container') page.within '.atwho-container #at-view-users' do @@ -66,6 +76,22 @@ describe 'GFM autocomplete', :js do end end + it 'opens autocomplete menu for Milestone when field starts with text with item escaping HTML characters' do + create(:milestone, project: project, title: milestone_xss_title) + + page.within '.timeline-content-form' do + find('#note-body').native.send_keys('%') + end + + wait_for_requests + + expect(page).to have_selector('.atwho-container') + + page.within '.atwho-container #at-view-milestones' do + expect(find('li').text).to have_content('alert milestone') + end + end + it 'doesnt open autocomplete menu character is prefixed with text' do page.within '.timeline-content-form' do find('#note-body').native.send_keys('testing') @@ -258,12 +284,28 @@ describe 'GFM autocomplete', :js do let!(:bug) { create(:label, project: project, title: 'bug') } let!(:feature_proposal) { create(:label, project: project, title: 'feature proposal') } + it 'opens autocomplete menu for Labels when field starts with text with item escaping HTML characters' do + create(:label, project: project, title: label_xss_title) + + note = find('#note-body') + + # It should show all the labels on "~". + type(note, '~') + + wait_for_requests + + page.within '.atwho-container #at-view-labels' do + expect(find('.atwho-view-ul').text).to have_content('alert label') + end + end + context 'when no labels are assigned' do it 'shows labels' do note = find('#note-body') # It should show all the labels on "~". type(note, '~') + wait_for_requests expect_labels(shown: [backend, bug, feature_proposal]) # It should show all the labels on "/label ~". @@ -290,6 +332,7 @@ describe 'GFM autocomplete', :js do # It should show all the labels on "~". type(note, '~') + wait_for_requests expect_labels(shown: [backend, bug, feature_proposal]) # It should show only unset labels on "/label ~". @@ -316,6 +359,7 @@ describe 'GFM autocomplete', :js do # It should show all the labels on "~". type(note, '~') + wait_for_requests expect_labels(shown: [backend, bug, feature_proposal]) # It should show no labels on "/label ~". diff --git a/spec/features/issues/user_creates_issue_spec.rb b/spec/features/issues/user_creates_issue_spec.rb index 687a6f1eafc..e60486f6dcb 100644 --- a/spec/features/issues/user_creates_issue_spec.rb +++ b/spec/features/issues/user_creates_issue_spec.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + require "spec_helper" describe "User creates issue" do @@ -12,7 +14,7 @@ describe "User creates issue" do visit(new_project_issue_path(project)) end - it "creates issue" do + it "creates issue", :js do page.within(".issue-form") do expect(page).to have_no_content("Assign to") .and have_no_content("Labels") @@ -25,11 +27,15 @@ describe "User creates issue" do issue_title = "500 error on profile" fill_in("Title", with: issue_title) + first('.js-md').click + first('.qa-issuable-form-description').native.send_keys('Description') + click_button("Submit issue") expect(page).to have_content(issue_title) .and have_content(user.name) .and have_content(project.name) + expect(page).to have_selector('strong', text: 'Description') end end @@ -64,10 +70,10 @@ describe "User creates issue" do end context "with labels" do - LABEL_TITLES = %w(bug feature enhancement).freeze + let(:label_titles) { %w(bug feature enhancement) } before do - LABEL_TITLES.each do |title| + label_titles.each do |title| create(:label, project: project, title: title) end end @@ -77,13 +83,13 @@ describe "User creates issue" do fill_in("Title", with: issue_title) click_button("Label") - click_link(LABEL_TITLES.first) + click_link(label_titles.first) click_button("Submit issue") expect(page).to have_content(issue_title) .and have_content(user.name) .and have_content(project.name) - .and have_content(LABEL_TITLES.first) + .and have_content(label_titles.first) end end end diff --git a/spec/features/merge_request/user_creates_image_diff_notes_spec.rb b/spec/features/merge_request/user_creates_image_diff_notes_spec.rb index d790bdc82ce..d19408ee87f 100644 --- a/spec/features/merge_request/user_creates_image_diff_notes_spec.rb +++ b/spec/features/merge_request/user_creates_image_diff_notes_spec.rb @@ -90,9 +90,6 @@ describe 'Merge request > User creates image diff notes', :js do %w(inline parallel).each do |view| context "#{view} view" do - let(:merge_request) { create(:merge_request_with_diffs, :with_image_diffs, source_project: project, author: user) } - let(:path) { "files/images/ee_repo_logo.png" } - let(:position) do Gitlab::Diff::Position.new( old_path: path, @@ -108,9 +105,11 @@ describe 'Merge request > User creates image diff notes', :js do let!(:note) { create(:diff_note_on_merge_request, project: project, noteable: merge_request, position: position) } - describe 'creating a new diff note' do + shared_examples 'creates image diff note' do before do visit diffs_project_merge_request_path(project, merge_request, view: view) + wait_for_requests + create_image_diff_note end @@ -132,6 +131,32 @@ describe 'Merge request > User creates image diff notes', :js do expect(page).to have_content('image diff test comment') end end + + context 'when images are not stored in LFS' do + let(:merge_request) { create(:merge_request_with_diffs, :with_image_diffs, source_project: project, author: user) } + let(:path) { 'files/images/ee_repo_logo.png' } + + it_behaves_like 'creates image diff note' + end + + context 'when images are stored in LFS' do + let(:merge_request) { create(:merge_request, source_project: project, target_project: project, source_branch: 'png-lfs', target_branch: 'master', author: user) } + let(:path) { 'files/images/logo-black.png' } + + before do + allow(Gitlab.config.lfs).to receive(:enabled).and_return(true) + project.update_attribute(:lfs_enabled, true) + end + + it 'shows lfs badges' do + visit diffs_project_merge_request_path(project, merge_request, view: view) + wait_for_requests + + expect(page.all('.diff-file span.label-lfs', visible: :all)).not_to be_empty + end + + it_behaves_like 'creates image diff note' + end end end diff --git a/spec/features/merge_request/user_merges_when_pipeline_succeeds_spec.rb b/spec/features/merge_request/user_merges_when_pipeline_succeeds_spec.rb index 53ed5d78598..29b3d2b629b 100644 --- a/spec/features/merge_request/user_merges_when_pipeline_succeeds_spec.rb +++ b/spec/features/merge_request/user_merges_when_pipeline_succeeds_spec.rb @@ -88,6 +88,8 @@ describe 'Merge request > User merges when pipeline succeeds', :js do describe 'enabling Merge when pipeline succeeds via dropdown' do it 'activates the Merge when pipeline succeeds feature' do + wait_for_requests + find('.js-merge-moment').click click_link 'Merge when pipeline succeeds' diff --git a/spec/features/merge_request/user_sees_diff_spec.rb b/spec/features/merge_request/user_sees_diff_spec.rb index 0df9e4bbc1a..04b07525919 100644 --- a/spec/features/merge_request/user_sees_diff_spec.rb +++ b/spec/features/merge_request/user_sees_diff_spec.rb @@ -87,20 +87,6 @@ describe 'Merge request > User sees diff', :js do let(:current_user) { project.owner } let(:branch_name) {"test_branch"} - def create_file(branch_name, file_name, content) - Files::CreateService.new( - project, - current_user, - start_branch: branch_name, - branch_name: branch_name, - commit_message: "Create file", - file_path: file_name, - file_content: content - ).execute - - project.commit(branch_name) - end - it 'escapes any HTML special characters in the diff chunk header' do file_content = <<~CONTENT @@ -136,5 +122,61 @@ describe 'Merge request > User sees diff', :js do expect(page).to have_css(".line[lang='rust'] .k") end end + + context 'when file is stored in LFS' do + let(:merge_request) { create(:merge_request, source_project: project) } + let(:current_user) { project.owner } + + context 'when LFS is enabled on the project' do + before do + allow(Gitlab.config.lfs).to receive(:enabled).and_return(true) + project.update_attribute(:lfs_enabled, true) + + create_file('master', file_name, project.repository.blob_at('master', 'files/lfs/lfs_object.iso').data) + + visit diffs_project_merge_request_path(project, merge_request) + end + + context 'when file is an image', :js do + let(:file_name) { 'files/lfs/image.png' } + + it 'shows an error message' do + expect(page).not_to have_content('could not be displayed because it is stored in LFS') + end + end + + context 'when file is not an image' do + let(:file_name) { 'files/lfs/ruby.rb' } + + it 'shows an error message' do + expect(page).to have_content('This source diff could not be displayed because it is stored in LFS') + end + end + end + + context 'when LFS is not enabled' do + before do + visit diffs_project_merge_request_path(project, merge_request) + end + + it 'displays the diff' do + expect(page).to have_content('size 1575078') + end + end + end + + def create_file(branch_name, file_name, content) + Files::CreateService.new( + project, + current_user, + start_branch: branch_name, + branch_name: branch_name, + commit_message: "Create file", + file_path: file_name, + file_content: content + ).execute + + project.commit(branch_name) + end end end diff --git a/spec/features/merge_request/user_sees_versions_spec.rb b/spec/features/merge_request/user_sees_versions_spec.rb index f7512294bef..63d8decc2d2 100644 --- a/spec/features/merge_request/user_sees_versions_spec.rb +++ b/spec/features/merge_request/user_sees_versions_spec.rb @@ -64,6 +64,17 @@ describe 'Merge request > User sees versions', :js do end end + it 'shows the commit SHAs for every version in the dropdown' do + page.within '.mr-version-dropdown' do + find('.btn-default').click + + page.within('.dropdown-content') do + shas = merge_request.merge_request_diffs.map { |diff| Commit.truncate_sha(diff.head_commit_sha) } + shas.each { |sha| expect(page).to have_content(sha) } + end + end + end + it 'shows comments that were last relevant at that version' do expect(page).to have_content '5 changed files' diff --git a/spec/features/merge_request/user_suggests_changes_on_diff_spec.rb b/spec/features/merge_request/user_suggests_changes_on_diff_spec.rb new file mode 100644 index 00000000000..c19e299097e --- /dev/null +++ b/spec/features/merge_request/user_suggests_changes_on_diff_spec.rb @@ -0,0 +1,85 @@ +# frozen_string_literal: true + +require 'spec_helper' + +describe 'User comments on a diff', :js do + include MergeRequestDiffHelpers + include RepoHelpers + + let(:project) { create(:project, :repository) } + let(:merge_request) do + create(:merge_request_with_diffs, source_project: project, target_project: project, source_branch: 'merge-test') + end + let(:user) { create(:user) } + + before do + project.add_maintainer(user) + sign_in(user) + + visit(diffs_project_merge_request_path(project, merge_request)) + end + + context 'single suggestion note' do + it 'suggestion is presented' do + click_diff_line(find("[id='#{sample_compare.changes[1][:line_code]}']")) + + page.within('.js-discussion-note-form') do + fill_in('note_note', with: "```suggestion\n# change to a comment\n```") + click_button('Comment') + end + + wait_for_requests + + page.within('.diff-discussions') do + expect(page).to have_button('Apply suggestion') + expect(page).to have_content('Suggested change') + expect(page).to have_content(' url = https://github.com/gitlabhq/gitlab-shell.git') + expect(page).to have_content('# change to a comment') + end + end + + it 'suggestion is appliable' do + click_diff_line(find("[id='#{sample_compare.changes[1][:line_code]}']")) + + page.within('.js-discussion-note-form') do + fill_in('note_note', with: "```suggestion\n# change to a comment\n```") + click_button('Comment') + end + + wait_for_requests + + page.within('.diff-discussions') do + expect(page).not_to have_content('Applied') + + click_button('Apply suggestion') + wait_for_requests + + expect(page).to have_content('Applied') + end + end + end + + context 'multiple suggestions in a single note' do + it 'suggestions are presented' do + click_diff_line(find("[id='#{sample_compare.changes[1][:line_code]}']")) + + page.within('.js-discussion-note-form') do + fill_in('note_note', with: "```suggestion\n# change to a comment\n```\n```suggestion\n# or that\n```") + click_button('Comment') + end + + wait_for_requests + + page.within('.diff-discussions') do + suggestion_1 = page.all(:css, '.md-suggestion-diff')[0] + suggestion_2 = page.all(:css, '.md-suggestion-diff')[1] + + expect(suggestion_1).to have_content(' url = https://github.com/gitlabhq/gitlab-shell.git') + expect(suggestion_1).to have_content('# change to a comment') + + expect(suggestion_2).to have_content(' url = https://github.com/gitlabhq/gitlab-shell.git') + expect(suggestion_2).to have_content('# or that') + end + end + end +end diff --git a/spec/features/merge_request/user_tries_to_access_private_repository_through_new_mr_spec.rb b/spec/features/merge_request/user_tries_to_access_private_repository_through_new_mr_spec.rb new file mode 100644 index 00000000000..9318b5f1ebb --- /dev/null +++ b/spec/features/merge_request/user_tries_to_access_private_repository_through_new_mr_spec.rb @@ -0,0 +1,37 @@ +require 'spec_helper' + +describe 'Merge Request > Tries to access private repo of public project' do + let(:current_user) { create(:user) } + let(:private_project) do + create(:project, :public, :repository, + path: 'nothing-to-see-here', + name: 'nothing to see here', + repository_access_level: ProjectFeature::PRIVATE) + end + let(:owned_project) do + create(:project, :public, :repository, + namespace: current_user.namespace, + creator: current_user) + end + + context 'when the user enters the querystring info for the other project' do + let(:mr_path) do + project_new_merge_request_diffs_path( + owned_project, + merge_request: { + source_project_id: private_project.id, + source_branch: 'feature' + } + ) + end + + before do + sign_in current_user + visit mr_path + end + + it "does not mention the project the user can't see the repo of" do + expect(page).not_to have_content('nothing-to-see-here') + end + end +end diff --git a/spec/features/profiles/user_edit_profile_spec.rb b/spec/features/profiles/user_edit_profile_spec.rb index 5e0434c1c2c..f45bcabd196 100644 --- a/spec/features/profiles/user_edit_profile_spec.rb +++ b/spec/features/profiles/user_edit_profile_spec.rb @@ -147,6 +147,9 @@ describe 'User edit profile' do end context 'user menu' do + let(:issue) { create(:issue, project: project)} + let(:project) { create(:project) } + def open_user_status_modal find('.header-user-dropdown-toggle').click @@ -205,6 +208,17 @@ describe 'User edit profile' do end end + it 'does not update the awards panel emoji' do + project.add_maintainer(user) + visit(project_issue_path(project, issue)) + + emoji = 'biohazard' + open_user_status_modal + select_emoji(emoji, true) + + expect(page.all('.award-control .js-counter')).to all(have_content('0')) + end + it 'adds message to user status' do message = 'I have something to say' open_user_status_modal diff --git a/spec/features/projects/clusters/gcp_spec.rb b/spec/features/projects/clusters/gcp_spec.rb index 3d17eb3a73a..06e30571336 100644 --- a/spec/features/projects/clusters/gcp_spec.rb +++ b/spec/features/projects/clusters/gcp_spec.rb @@ -130,7 +130,7 @@ describe 'Gcp Cluster', :js do context 'when user changes cluster parameters' do before do - allow(ClusterPlatformConfigureWorker).to receive(:perform_async) + allow(ClusterConfigureWorker).to receive(:perform_async) fill_in 'cluster_platform_kubernetes_attributes_namespace', with: 'my-namespace' page.within('#js-cluster-details') { click_button 'Save changes' } end diff --git a/spec/features/projects/commits/user_browses_commits_spec.rb b/spec/features/projects/commits/user_browses_commits_spec.rb index 2159adf49fc..574a8aefd63 100644 --- a/spec/features/projects/commits/user_browses_commits_spec.rb +++ b/spec/features/projects/commits/user_browses_commits_spec.rb @@ -93,7 +93,7 @@ describe 'User browses commits' do it 'shows a blank label' do allow_any_instance_of(Gitlab::Diff::File).to receive(:blob).and_return(nil) - allow_any_instance_of(Gitlab::Diff::File).to receive(:raw_binary?).and_return(true) + allow_any_instance_of(Gitlab::Diff::File).to receive(:binary?).and_return(true) visit(project_commit_path(project, commit)) diff --git a/spec/features/projects/jobs_spec.rb b/spec/features/projects/jobs_spec.rb index 651c02c7ecc..60f37f4b74a 100644 --- a/spec/features/projects/jobs_spec.rb +++ b/spec/features/projects/jobs_spec.rb @@ -351,8 +351,8 @@ describe 'Jobs', :clean_gitlab_redis_shared_state do context 'when user is a maintainer' do shared_examples 'no reveal button variables behavior' do it 'renders a hidden value with no reveal values button', :js do - expect(page).to have_content('Token') - expect(page).to have_content('Variables') + expect(page).to have_content('Trigger token') + expect(page).to have_content('Trigger variables') expect(page).not_to have_css('.js-reveal-variables') @@ -389,8 +389,8 @@ describe 'Jobs', :clean_gitlab_redis_shared_state do shared_examples 'reveal button variables behavior' do it 'renders a hidden value with a reveal values button', :js do - expect(page).to have_content('Token') - expect(page).to have_content('Variables') + expect(page).to have_content('Trigger token') + expect(page).to have_content('Trigger variables') expect(page).to have_css('.js-reveal-variables') diff --git a/spec/features/projects/labels/user_views_labels_spec.rb b/spec/features/projects/labels/user_views_labels_spec.rb index 0cbeca4e392..2c8267764bd 100644 --- a/spec/features/projects/labels/user_views_labels_spec.rb +++ b/spec/features/projects/labels/user_views_labels_spec.rb @@ -1,13 +1,15 @@ +# frozen_string_literal: true + require "spec_helper" describe "User views labels" do set(:project) { create(:project_empty_repo, :public) } set(:user) { create(:user) } - LABEL_TITLES = %w[bug enhancement feature].freeze + let(:label_titles) { %w[bug enhancement feature] } before do - LABEL_TITLES.each { |title| create(:label, project: project, title: title) } + label_titles.each { |title| create(:label, project: project, title: title) } project.add_guest(user) sign_in(user) @@ -17,7 +19,7 @@ describe "User views labels" do it "shows all labels" do page.within('.other-labels .manage-labels-list') do - LABEL_TITLES.each { |title| expect(page).to have_content(title) } + label_titles.each { |title| expect(page).to have_content(title) } end end end diff --git a/spec/features/projects/settings/repository_settings_spec.rb b/spec/features/projects/settings/repository_settings_spec.rb index 418e22f8c35..1982136b89d 100644 --- a/spec/features/projects/settings/repository_settings_spec.rb +++ b/spec/features/projects/settings/repository_settings_spec.rb @@ -200,35 +200,21 @@ describe 'Projects > Settings > Repository settings' do context 'repository cleanup settings' do let(:object_map_file) { Rails.root.join('spec', 'fixtures', 'bfg_object_map.txt') } - context 'feature enabled' do - it 'uploads an object map file', :js do - stub_feature_flags(project_cleanup: true) - - visit project_settings_repository_path(project) + it 'uploads an object map file', :js do + visit project_settings_repository_path(project) - expect(page).to have_content('Repository cleanup') + expect(page).to have_content('Repository cleanup') - page.within('#cleanup') do - attach_file('project[bfg_object_map]', object_map_file, visible: false) + page.within('#cleanup') do + attach_file('project[bfg_object_map]', object_map_file, visible: false) - Sidekiq::Testing.fake! do - click_button 'Start cleanup' - end + Sidekiq::Testing.fake! do + click_button 'Start cleanup' end - - expect(page).to have_content('Repository cleanup has started') - expect(RepositoryCleanupWorker.jobs.count).to eq(1) end - end - context 'feature disabled' do - it 'does not show the settings' do - stub_feature_flags(project_cleanup: false) - - visit project_settings_repository_path(project) - - expect(page).not_to have_content('Repository cleanup') - end + expect(page).to have_content('Repository cleanup has started') + expect(RepositoryCleanupWorker.jobs.count).to eq(1) end end end diff --git a/spec/features/runners_spec.rb b/spec/features/runners_spec.rb index cb7a912946c..09de983f669 100644 --- a/spec/features/runners_spec.rb +++ b/spec/features/runners_spec.rb @@ -259,8 +259,9 @@ describe 'Runners' do context 'group runners in group settings' do let(:group) { create(:group) } + before do - group.add_maintainer(user) + group.add_owner(user) end context 'group with no runners' do diff --git a/spec/features/users/overview_spec.rb b/spec/features/users/overview_spec.rb index 34ed771340f..8748230fa0c 100644 --- a/spec/features/users/overview_spec.rb +++ b/spec/features/users/overview_spec.rb @@ -33,6 +33,8 @@ describe 'Overview tab on a user profile', :js do it 'does not show any entries in the list of activities' do page.within('.activities-block') do + expect(page).to have_selector('.loading', visible: false) + expect(page).to have_content('No activities found') expect(page).not_to have_selector('.event-item') end end @@ -93,6 +95,7 @@ describe 'Overview tab on a user profile', :js do it 'it shows an empty project list with an info message' do page.within('.projects-block') do + expect(page).to have_selector('.loading', visible: false) expect(page).to have_content('No projects found') expect(page).not_to have_selector('.project-row') end @@ -119,6 +122,12 @@ describe 'Overview tab on a user profile', :js do it 'shows a link to the project list' do expect(find('#js-overview .projects-block')).to have_selector('.js-view-all', visible: true) end + + it 'shows projects in "compact mode"' do + page.within('#js-overview .projects-block') do + expect(find('.js-projects-list-holder')).to have_selector('.compact') + end + end end describe 'user has more than ten personal projects' do diff --git a/spec/finders/cluster_ancestors_finder_spec.rb b/spec/finders/cluster_ancestors_finder_spec.rb new file mode 100644 index 00000000000..332086c42e2 --- /dev/null +++ b/spec/finders/cluster_ancestors_finder_spec.rb @@ -0,0 +1,77 @@ +# frozen_string_literal: true + +require 'spec_helper' + +describe ClusterAncestorsFinder, '#execute' do + let(:group) { create(:group) } + let(:project) { create(:project, group: group) } + let(:user) { create(:user) } + + let!(:project_cluster) do + create(:cluster, :provided_by_user, cluster_type: :project_type, projects: [project]) + end + + let!(:group_cluster) do + create(:cluster, :provided_by_user, cluster_type: :group_type, groups: [group]) + end + + subject { described_class.new(clusterable, user).execute } + + context 'for a project' do + let(:clusterable) { project } + + before do + project.add_maintainer(user) + end + + it 'returns the project clusters followed by group clusters' do + is_expected.to eq([project_cluster, group_cluster]) + end + + context 'nested groups', :nested_groups do + let(:group) { create(:group, parent: parent_group) } + let(:parent_group) { create(:group) } + + let!(:parent_group_cluster) do + create(:cluster, :provided_by_user, cluster_type: :group_type, groups: [parent_group]) + end + + it 'returns the project clusters followed by group clusters ordered ascending the hierarchy' do + is_expected.to eq([project_cluster, group_cluster, parent_group_cluster]) + end + end + end + + context 'user cannot read clusters for clusterable' do + let(:clusterable) { project } + + it 'returns nothing' do + is_expected.to be_empty + end + end + + context 'for a group' do + let(:clusterable) { group } + + before do + group.add_maintainer(user) + end + + it 'returns the list of group clusters' do + is_expected.to eq([group_cluster]) + end + + context 'nested groups', :nested_groups do + let(:group) { create(:group, parent: parent_group) } + let(:parent_group) { create(:group) } + + let!(:parent_group_cluster) do + create(:cluster, :provided_by_user, cluster_type: :group_type, groups: [parent_group]) + end + + it 'returns the list of group clusters ordered ascending the hierarchy' do + is_expected.to eq([group_cluster, parent_group_cluster]) + end + end + end +end diff --git a/spec/finders/concerns/finder_with_cross_project_access_spec.rb b/spec/finders/concerns/finder_with_cross_project_access_spec.rb index 1ff65a8101b..f29acb521a8 100644 --- a/spec/finders/concerns/finder_with_cross_project_access_spec.rb +++ b/spec/finders/concerns/finder_with_cross_project_access_spec.rb @@ -115,4 +115,20 @@ describe FinderWithCrossProjectAccess do expect(finder.execute).to include(result) end end + + context 'when specifying a model' do + let(:finder_class) do + Class.new do + prepend FinderWithCrossProjectAccess + + requires_cross_project_access model: Project + end + end + + context '.finder_model' do + it 'is set correctly' do + expect(finder_class.finder_model).to eq(Project) + end + end + end end diff --git a/spec/finders/events_finder_spec.rb b/spec/finders/events_finder_spec.rb index 62968e83292..3bce46cc4d1 100644 --- a/spec/finders/events_finder_spec.rb +++ b/spec/finders/events_finder_spec.rb @@ -14,6 +14,10 @@ describe EventsFinder do let!(:closed_issue_event2) { create(:event, project: project1, author: user, target: closed_issue, action: Event::CLOSED, created_at: Date.new(2016, 2, 2)) } let!(:opened_merge_request_event2) { create(:event, project: project2, author: user, target: opened_merge_request, action: Event::CREATED, created_at: Date.new(2017, 2, 2)) } + let(:public_project) { create(:project, :public, creator_id: user.id, namespace: user.namespace) } + let(:confidential_issue) { create(:closed_issue, confidential: true, project: public_project, author: user) } + let!(:confidential_event) { create(:event, project: public_project, author: user, target: confidential_issue, action: Event::CLOSED) } + context 'when targeting a user' do it 'returns events between specified dates filtered on action and type' do events = described_class.new(source: user, current_user: user, action: 'created', target_type: 'merge_request', after: Date.new(2017, 1, 1), before: Date.new(2017, 2, 1)).execute @@ -27,6 +31,19 @@ describe EventsFinder do expect(events).not_to include(opened_merge_request_event) end + it 'does not include events on confidential issues the user does not have access to' do + events = described_class.new(source: user, current_user: other_user).execute + + expect(events).not_to include(confidential_event) + end + + it 'includes confidential events user has access to' do + public_project.add_developer(other_user) + events = described_class.new(source: user, current_user: other_user).execute + + expect(events).to include(confidential_event) + end + it 'returns nothing when the current user cannot read cross project' do expect(Ability).to receive(:allowed?).with(user, :read_cross_project) { false } diff --git a/spec/finders/releases_finder_spec.rb b/spec/finders/releases_finder_spec.rb new file mode 100644 index 00000000000..32ee15134a2 --- /dev/null +++ b/spec/finders/releases_finder_spec.rb @@ -0,0 +1,42 @@ +# frozen_string_literal: true + +require 'spec_helper' + +describe ReleasesFinder do + let(:user) { create(:user) } + let(:project) { create(:project, :repository) } + let(:repository) { project.repository } + let(:v1_0_0) { create(:release, project: project, tag: 'v1.0.0') } + let(:v1_1_0) { create(:release, project: project, tag: 'v1.1.0') } + + subject { described_class.new(project, user)} + + before do + v1_0_0.update_attribute(:created_at, 2.days.ago) + v1_1_0.update_attribute(:created_at, 1.day.ago) + end + + describe '#execute' do + context 'when the user is not part of the project' do + it 'returns no releases' do + releases = subject.execute + + expect(releases).to be_empty + end + end + + context 'when the user is a project developer' do + before do + project.add_developer(user) + end + + it 'sorts by creation date' do + releases = subject.execute + + expect(releases).to be_present + expect(releases.size).to eq(2) + expect(releases).to eq([v1_1_0, v1_0_0]) + end + end + end +end diff --git a/spec/finders/user_recent_events_finder_spec.rb b/spec/finders/user_recent_events_finder_spec.rb index c5fcd68eb4c..5ebceeb7586 100644 --- a/spec/finders/user_recent_events_finder_spec.rb +++ b/spec/finders/user_recent_events_finder_spec.rb @@ -29,8 +29,9 @@ describe UserRecentEventsFinder do end it 'does not include the events if the user cannot read cross project' do - expect(Ability).to receive(:allowed?).and_call_original + allow(Ability).to receive(:allowed?).and_call_original expect(Ability).to receive(:allowed?).with(current_user, :read_cross_project) { false } + expect(finder.execute).to be_empty end end diff --git a/spec/fixtures/api/schemas/entities/diff_line.json b/spec/fixtures/api/schemas/entities/diff_line.json index 66e8b443e1b..9657004cd2d 100644 --- a/spec/fixtures/api/schemas/entities/diff_line.json +++ b/spec/fixtures/api/schemas/entities/diff_line.json @@ -8,7 +8,8 @@ "new_line": { "type": ["integer", "null"] }, "text": { "type": ["string"] }, "rich_text": { "type": ["string"] }, - "meta_data": { "type": ["object", "null"] } + "meta_data": { "type": ["object", "null"] }, + "can_receive_suggestion": { "type": "boolean" } }, "additionalProperties": false } diff --git a/spec/fixtures/api/schemas/entities/diff_viewer.json b/spec/fixtures/api/schemas/entities/diff_viewer.json index 19780f49a88..81325cd86c6 100644 --- a/spec/fixtures/api/schemas/entities/diff_viewer.json +++ b/spec/fixtures/api/schemas/entities/diff_viewer.json @@ -1,8 +1,20 @@ { "type": "object", - "required": ["name"], + "required": [ + "name" + ], "properties": { - "name": { "type": ["string"] } + "name": { + "type": [ + "string" + ] + }, + "error": { + "type": [ + "string", + "null" + ] + } }, "additionalProperties": false } diff --git a/spec/fixtures/api/schemas/entities/issuable_sidebar_todo.json b/spec/fixtures/api/schemas/entities/issuable_sidebar_todo.json new file mode 100644 index 00000000000..b77e60ece12 --- /dev/null +++ b/spec/fixtures/api/schemas/entities/issuable_sidebar_todo.json @@ -0,0 +1,8 @@ +{ + "type": ["object", "null"], + "properties" : { + "id": { "type": "integer" }, + "delete_path": { "type": "string" } + }, + "additionalProperties": false +} diff --git a/spec/fixtures/api/schemas/entities/issue_sidebar.json b/spec/fixtures/api/schemas/entities/issue_sidebar.json index 682e345d5f5..93adb493d1b 100644 --- a/spec/fixtures/api/schemas/entities/issue_sidebar.json +++ b/spec/fixtures/api/schemas/entities/issue_sidebar.json @@ -2,20 +2,46 @@ "type": "object", "properties" : { "id": { "type": "integer" }, - "iid": { "type": "integer" }, - "subscribed": { "type": "boolean" }, - "time_estimate": { "type": "integer" }, - "total_time_spent": { "type": "integer" }, - "human_time_estimate": { "type": ["integer", "null"] }, - "human_total_time_spent": { "type": ["integer", "null"] }, - "participants": { - "type": "array", - "items": { "$ref": "../public_api/v4/user/basic.json" } + "type": { "type": "string" }, + "author_id": { "type": "integer" }, + "project_id": { "type": "integer" }, + "discussion_locked": { "type": ["boolean", "null"] }, + "due_date": { "type": "date" }, + "confidential": { "type": "boolean" }, + "reference": { "type": "string" }, + "current_user": { + "allOf": [ + { "$ref": "../public_api/v4/user/basic.json" }, + { "type": "object", + "properties" : { + "todo": { "$ref": "issuable_sidebar_todo.json" }, + "can_edit": { "type": "boolean" }, + "can_move": { "type": "boolean" }, + "can_admin_label": { "type": "boolean" } + } + } + ] + }, + "milestone": { + "oneOf": [ + { "type": "null" }, + { "$ref": "../public_api/v4/milestone.json" } + ] }, - "assignees": { + "labels": { "type": "array", - "items": { "$ref": "../public_api/v4/user/basic.json" } - } - }, - "additionalProperties": false + "items": { "$ref": "label.json" } + }, + "issuable_json_path": { "type": "string" }, + "namespace_path": { "type": "string" }, + "project_path": { "type": "string" }, + "project_full_path": { "type": "string" }, + "project_issuables_path": { "type": "string" }, + "create_todo_path": { "type": "string" }, + "project_milestones_path": { "type": "string" }, + "project_labels_path": { "type": "string" }, + "toggle_subscription_path": { "type": "string" }, + "move_issue_path": { "type": "string" }, + "projects_autocomplete_path": { "type": "string" } + } } diff --git a/spec/fixtures/api/schemas/entities/issue_sidebar_extras.json b/spec/fixtures/api/schemas/entities/issue_sidebar_extras.json new file mode 100644 index 00000000000..11be903b083 --- /dev/null +++ b/spec/fixtures/api/schemas/entities/issue_sidebar_extras.json @@ -0,0 +1,18 @@ +{ + "type": "object", + "properties" : { + "subscribed": { "type": "boolean" }, + "time_estimate": { "type": "integer" }, + "total_time_spent": { "type": "integer" }, + "human_time_estimate": { "type": ["integer", "null"] }, + "human_total_time_spent": { "type": ["integer", "null"] }, + "participants": { + "type": "array", + "items": { "$ref": "../public_api/v4/user/basic.json" } + }, + "assignees": { + "type": "array", + "items": { "$ref": "../public_api/v4/user/basic.json" } + } + } +} diff --git a/spec/fixtures/api/schemas/entities/merge_request_basic.json b/spec/fixtures/api/schemas/entities/merge_request_basic.json index cf257ac00de..4c04c838cb8 100644 --- a/spec/fixtures/api/schemas/entities/merge_request_basic.json +++ b/spec/fixtures/api/schemas/entities/merge_request_basic.json @@ -4,15 +4,9 @@ "state": { "type": "string" }, "merge_status": { "type": "string" }, "source_branch_exists": { "type": "boolean" }, - "time_estimate": { "type": "integer" }, - "total_time_spent": { "type": "integer" }, - "human_time_estimate": { "type": ["string", "null"] }, - "human_total_time_spent": { "type": ["string", "null"] }, "merge_error": { "type": ["string", "null"] }, "rebase_in_progress": { "type": "boolean" }, "assignee_id": { "type": ["integer", "null"] }, - "subscribed": { "type": ["boolean", "null"] }, - "participants": { "type": "array" }, "allow_collaboration": { "type": "boolean"}, "allow_maintainer_to_push": { "type": "boolean"}, "assignee": { diff --git a/spec/fixtures/api/schemas/entities/merge_request_sidebar.json b/spec/fixtures/api/schemas/entities/merge_request_sidebar.json new file mode 100644 index 00000000000..7e9e048a9fd --- /dev/null +++ b/spec/fixtures/api/schemas/entities/merge_request_sidebar.json @@ -0,0 +1,56 @@ +{ + "type": "object", + "properties" : { + "id": { "type": "integer" }, + "type": { "type": "string" }, + "author_id": { "type": "integer" }, + "project_id": { "type": "integer" }, + "discussion_locked": { "type": ["boolean", "null"] }, + "reference": { "type": "string" }, + "current_user": { + "allOf": [ + { "$ref": "../public_api/v4/user/basic.json" }, + { "type": "object", + "properties" : { + "todo": { "$ref": "issuable_sidebar_todo.json" }, + "can_edit": { "type": "boolean" }, + "can_move": { "type": "boolean" }, + "can_admin_label": { "type": "boolean" } + } + } + ] + }, + "milestone": { + "oneOf": [ + { "type": "null" }, + { "$ref": "../public_api/v4/milestones.json" } + ] + }, + "labels": { + "type": "array", + "items": { "$ref": "label.json" } + }, + "assignee": { + "allOf": [ + { "$ref": "../public_api/v4/user/basic.json" }, + { "type": "object", + "properties" : { + "can_merge": { "type": "boolean" } + } + } + ] + }, + "issuable_json_path": { "type": "string" }, + "namespace_path": { "type": "string" }, + "project_path": { "type": "string" }, + "project_full_path": { "type": "string" }, + "project_issuables_path": { "type": "string" }, + "create_todo_path": { "type": "string" }, + "project_milestones_path": { "type": "string" }, + "project_labels_path": { "type": "string" }, + "toggle_subscription_path": { "type": "string" }, + "move_issue_path": { "type": "string" }, + "projects_autocomplete_path": { "type": "string" } + }, + "additionalProperties": false +} diff --git a/spec/fixtures/api/schemas/entities/merge_request_sidebar_extras.json b/spec/fixtures/api/schemas/entities/merge_request_sidebar_extras.json new file mode 100644 index 00000000000..682e345d5f5 --- /dev/null +++ b/spec/fixtures/api/schemas/entities/merge_request_sidebar_extras.json @@ -0,0 +1,21 @@ +{ + "type": "object", + "properties" : { + "id": { "type": "integer" }, + "iid": { "type": "integer" }, + "subscribed": { "type": "boolean" }, + "time_estimate": { "type": "integer" }, + "total_time_spent": { "type": "integer" }, + "human_time_estimate": { "type": ["integer", "null"] }, + "human_total_time_spent": { "type": ["integer", "null"] }, + "participants": { + "type": "array", + "items": { "$ref": "../public_api/v4/user/basic.json" } + }, + "assignees": { + "type": "array", + "items": { "$ref": "../public_api/v4/user/basic.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 35971d564d5..193ab6821a5 100644 --- a/spec/fixtures/api/schemas/entities/merge_request_widget.json +++ b/spec/fixtures/api/schemas/entities/merge_request_widget.json @@ -119,7 +119,8 @@ "can_push_to_source_branch": { "type": "boolean" }, "rebase_path": { "type": ["string", "null"] }, "squash": { "type": "boolean" }, - "test_reports_path": { "type": ["string", "null"] } + "test_reports_path": { "type": ["string", "null"] }, + "can_receive_suggestion": { "type": "boolean" } }, "additionalProperties": false } diff --git a/spec/fixtures/api/schemas/public_api/v4/milestone.json b/spec/fixtures/api/schemas/public_api/v4/milestone.json new file mode 100644 index 00000000000..6ca2e88ae91 --- /dev/null +++ b/spec/fixtures/api/schemas/public_api/v4/milestone.json @@ -0,0 +1,22 @@ +{ + "type": "object", + "properties" : { + "id": { "type": "integer" }, + "iid": { "type": "integer" }, + "project_id": { "type": ["integer", "null"] }, + "group_id": { "type": ["integer", "null"] }, + "title": { "type": "string" }, + "description": { "type": ["string", "null"] }, + "state": { "type": "string" }, + "created_at": { "type": "date" }, + "updated_at": { "type": "date" }, + "start_date": { "type": "date" }, + "due_date": { "type": "date" }, + "web_url": { "type": "string" } + }, + "required": [ + "id", "iid", "title", "description", "state", + "state", "created_at", "updated_at", "start_date", "due_date" + ], + "additionalProperties": false +} diff --git a/spec/fixtures/api/schemas/public_api/v4/milestones.json b/spec/fixtures/api/schemas/public_api/v4/milestones.json index 448e97d6c85..dcbc1910bfe 100644 --- a/spec/fixtures/api/schemas/public_api/v4/milestones.json +++ b/spec/fixtures/api/schemas/public_api/v4/milestones.json @@ -1,25 +1,6 @@ { "type": "array", "items": { - "type": "object", - "properties" : { - "id": { "type": "integer" }, - "iid": { "type": "integer" }, - "project_id": { "type": ["integer", "null"] }, - "group_id": { "type": ["integer", "null"] }, - "title": { "type": "string" }, - "description": { "type": ["string", "null"] }, - "state": { "type": "string" }, - "created_at": { "type": "date" }, - "updated_at": { "type": "date" }, - "start_date": { "type": "date" }, - "due_date": { "type": "date" }, - "web_url": { "type": "string" } - }, - "required": [ - "id", "iid", "title", "description", "state", - "state", "created_at", "updated_at", "start_date", "due_date" - ], - "additionalProperties": false + "$ref": "./milestone.json" } } diff --git a/spec/fixtures/api/schemas/release.json b/spec/fixtures/api/schemas/release.json new file mode 100644 index 00000000000..844405c3acd --- /dev/null +++ b/spec/fixtures/api/schemas/release.json @@ -0,0 +1,18 @@ +{ + "type": "object", + "required": ["name", "tag_name"], + "properties": { + "name": { "type": "string" }, + "tag_name": { "type": "string" }, + "description": { "type": "string" }, + "description_html": { "type": "string" }, + "created_at": { "type": "date" }, + "commit": { + "oneOf": [{ "type": "null" }, { "$ref": "public_api/v4/commit/basic.json" }] + }, + "author": { + "oneOf": [{ "type": "null" }, { "$ref": "public_api/v4/user/basic.json" }] + } + }, + "additionalProperties": false +} diff --git a/spec/fixtures/api/schemas/releases.json b/spec/fixtures/api/schemas/releases.json new file mode 100644 index 00000000000..e26215707fe --- /dev/null +++ b/spec/fixtures/api/schemas/releases.json @@ -0,0 +1,4 @@ +{ + "type": "array", + "items": { "$ref": "release.json" } +} diff --git a/spec/fixtures/gitlab/ci/external_files/.gitlab-ci-template-1.yml b/spec/fixtures/gitlab/ci/external_files/.gitlab-ci-template-1.yml index 0bab94a7c2e..1e88cd120aa 100644 --- a/spec/fixtures/gitlab/ci/external_files/.gitlab-ci-template-1.yml +++ b/spec/fixtures/gitlab/ci/external_files/.gitlab-ci-template-1.yml @@ -2,7 +2,6 @@ before_script: - apt-get update -qq && apt-get install -y -qq sqlite3 libsqlite3-dev nodejs - ruby -v - which ruby - - gem install bundler --no-ri --no-rdoc - bundle install --jobs $(nproc) "${FLAGS[@]}" rspec: diff --git a/spec/fixtures/symlink_export.tar.gz b/spec/fixtures/symlink_export.tar.gz Binary files differnew file mode 100644 index 00000000000..f295f69c56c --- /dev/null +++ b/spec/fixtures/symlink_export.tar.gz diff --git a/spec/helpers/diff_helper_spec.rb b/spec/helpers/diff_helper_spec.rb index 53c010fa0db..5396243f44d 100644 --- a/spec/helpers/diff_helper_spec.rb +++ b/spec/helpers/diff_helper_spec.rb @@ -256,43 +256,6 @@ describe DiffHelper do end end - context 'viewer related' do - let(:viewer) { diff_file.simple_viewer } - - before do - assign(:project, project) - end - - describe '#diff_render_error_reason' do - context 'for error :too_large' do - before do - expect(viewer).to receive(:render_error).and_return(:too_large) - end - - it 'returns an error message' do - expect(helper.diff_render_error_reason(viewer)).to eq('it is too large') - end - end - - context 'for error :server_side_but_stored_externally' do - before do - expect(viewer).to receive(:render_error).and_return(:server_side_but_stored_externally) - expect(diff_file).to receive(:external_storage).and_return(:lfs) - end - - it 'returns an error message' do - expect(helper.diff_render_error_reason(viewer)).to eq('it is stored in LFS') - end - end - end - - describe '#diff_render_error_options' do - it 'includes a "view the blob" link' do - expect(helper.diff_render_error_options(viewer)).to include(/view the blob/) - end - end - end - context '#diff_file_path_text' do it 'returns full path by default' do expect(diff_file_path_text(diff_file)).to eq(diff_file.new_path) diff --git a/spec/helpers/emails_helper_spec.rb b/spec/helpers/emails_helper_spec.rb index 139387e0b24..3820cf5cb9d 100644 --- a/spec/helpers/emails_helper_spec.rb +++ b/spec/helpers/emails_helper_spec.rb @@ -73,4 +73,59 @@ describe EmailsHelper do end end end + + describe '#create_list_id_string' do + using RSpec::Parameterized::TableSyntax + + where(:full_path, :list_id_path) do + "01234" | "01234" + "5/0123" | "012.." + "45/012" | "012.." + "012" | "012" + "23/01" | "01.23" + "2/01" | "01.2" + "234/01" | "01.." + "4/2/0" | "0.2.4" + "45/2/0" | "0.2.." + "5/23/0" | "0.." + "0-2/5" | "5.0-2" + "0_2/5" | "5.0-2" + "0.2/5" | "5.0-2" + end + + with_them do + it 'ellipcizes different variants' do + project = double("project") + allow(project).to receive(:full_path).and_return(full_path) + allow(project).to receive(:id).and_return(12345) + # Set a max length that gives only 5 chars for the project full path + max_length = "12345..#{Gitlab.config.gitlab.host}".length + 5 + list_id = create_list_id_string(project, max_length) + + expect(list_id).to eq("12345.#{list_id_path}.#{Gitlab.config.gitlab.host}") + expect(list_id).to satisfy { |s| s.length <= max_length } + end + end + end + + describe 'Create realistic List-Id identifier' do + using RSpec::Parameterized::TableSyntax + + where(:full_path, :list_id_path) do + "gitlab-org/gitlab-ce" | "gitlab-ce.gitlab-org" + "project-name/subproject_name/my.project" | "my-project.subproject-name.project-name" + end + + with_them do + it 'Produces the right List-Id' do + project = double("project") + allow(project).to receive(:full_path).and_return(full_path) + allow(project).to receive(:id).and_return(12345) + list_id = create_list_id_string(project) + + expect(list_id).to eq("12345.#{list_id_path}.#{Gitlab.config.gitlab.host}") + expect(list_id).to satisfy { |s| s.length <= 255 } + end + end + end end diff --git a/spec/helpers/issuables_helper_spec.rb b/spec/helpers/issuables_helper_spec.rb index 4af98bc3678..81231cca085 100644 --- a/spec/helpers/issuables_helper_spec.rb +++ b/spec/helpers/issuables_helper_spec.rb @@ -43,16 +43,19 @@ describe IssuablesHelper do end describe '#issuable_labels_tooltip' do + let(:label_entity) { LabelEntity.represent(label).as_json } + let(:label2_entity) { LabelEntity.represent(label2).as_json } + it 'returns label text with no labels' do expect(issuable_labels_tooltip([])).to eq("Labels") end it 'returns label text with labels within max limit' do - expect(issuable_labels_tooltip([label])).to eq(label.title) + expect(issuable_labels_tooltip([label_entity])).to eq(label[:title]) end it 'returns label text with labels exceeding max limit' do - expect(issuable_labels_tooltip([label, label2], limit: 1)).to eq("#{label.title}, and 1 more") + expect(issuable_labels_tooltip([label_entity, label2_entity], limit: 1)).to eq("#{label[:title]}, and 1 more") end end @@ -197,33 +200,4 @@ describe IssuablesHelper do expect(helper.issuable_initial_data(issue)).to eq(expected_data) end end - - describe '#selected_labels' do - context 'if label_name param is a string' do - it 'returns a new label with title' do - allow(helper).to receive(:params) - .and_return(ActionController::Parameters.new(label_name: 'test label')) - - labels = helper.selected_labels - - expect(labels).to be_an(Array) - expect(labels.size).to eq(1) - expect(labels.first.title).to eq('test label') - end - end - - context 'if label_name param is an array' do - it 'returns a new label with title for each element' do - allow(helper).to receive(:params) - .and_return(ActionController::Parameters.new(label_name: ['test label 1', 'test label 2'])) - - labels = helper.selected_labels - - expect(labels).to be_an(Array) - expect(labels.size).to eq(2) - expect(labels.first.title).to eq('test label 1') - expect(labels.second.title).to eq('test label 2') - end - end - end end diff --git a/spec/helpers/projects_helper_spec.rb b/spec/helpers/projects_helper_spec.rb index 486416c3370..edd680ee1d1 100644 --- a/spec/helpers/projects_helper_spec.rb +++ b/spec/helpers/projects_helper_spec.rb @@ -519,4 +519,114 @@ describe ProjectsHelper do expect(helper.legacy_render_context({})).to be_empty end end + + describe '#explore_projects_tab?' do + subject { helper.explore_projects_tab? } + + it 'returns true when on the "All" tab under "Explore projects"' do + allow(@request).to receive(:path) { explore_projects_path } + + expect(subject).to be_truthy + end + + it 'returns true when on the "Trending" tab under "Explore projects"' do + allow(@request).to receive(:path) { trending_explore_projects_path } + + expect(subject).to be_truthy + end + + it 'returns true when on the "Starred" tab under "Explore projects"' do + allow(@request).to receive(:path) { starred_explore_projects_path } + + expect(subject).to be_truthy + end + + it 'returns false when on the "Your projects" tab' do + allow(@request).to receive(:path) { dashboard_projects_path } + + expect(subject).to be_falsey + end + end + + describe '#show_merge_request_count' do + context 'when the feature flag is enabled' do + before do + stub_feature_flags(project_list_show_mr_count: true) + end + + it 'returns true if compact mode is disabled' do + expect(helper.show_merge_request_count?).to be_truthy + end + + it 'returns false if compact mode is enabled' do + expect(helper.show_merge_request_count?(compact_mode: true)).to be_falsey + end + end + + context 'when the feature flag is disabled' do + before do + stub_feature_flags(project_list_show_mr_count: false) + end + + it 'always returns false' do + expect(helper.show_merge_request_count?(disabled: false)).to be_falsy + expect(helper.show_merge_request_count?(disabled: true)).to be_falsy + end + end + + context 'disabled flag' do + before do + stub_feature_flags(project_list_show_mr_count: true) + end + + it 'returns false if disabled flag is true' do + expect(helper.show_merge_request_count?(disabled: true)).to be_falsey + end + + it 'returns true if disabled flag is false' do + expect(helper.show_merge_request_count?).to be_truthy + end + end + end + + describe '#show_issue_count?' do + context 'when the feature flag is enabled' do + before do + stub_feature_flags(project_list_show_issue_count: true) + end + + it 'returns true if compact mode is disabled' do + expect(helper.show_issue_count?).to be_truthy + end + + it 'returns false if compact mode is enabled' do + expect(helper.show_issue_count?(compact_mode: true)).to be_falsey + end + end + + context 'when the feature flag is disabled' do + before do + stub_feature_flags(project_list_show_issue_count: false) + end + + it 'always returns false' do + expect(helper.show_issue_count?(disabled: false)).to be_falsy + expect(helper.show_issue_count?(disabled: true)).to be_falsy + end + end + + context 'disabled flag' do + before do + stub_feature_flags(project_list_show_issue_count: true) + end + + it 'returns false if disabled flag is true' do + expect(helper.show_issue_count?(disabled: true)).to be_falsey + end + + it 'returns true if disabled flag is false' do + expect(helper.show_issue_count?).to be_truthy + end + end + end end diff --git a/spec/helpers/storage_helper_spec.rb b/spec/helpers/storage_helper_spec.rb index c580b78c908..03df9deafa1 100644 --- a/spec/helpers/storage_helper_spec.rb +++ b/spec/helpers/storage_helper_spec.rb @@ -15,11 +15,7 @@ describe StorageHelper do end it "uses commas as thousands separator" do - if Gitlab.rails5? - expect(helper.storage_counter(100_000_000_000_000_000_000_000)).to eq("86,736.2 EB") - else - expect(helper.storage_counter(100_000_000_000_000_000)).to eq("90,949.5 TB") - end + expect(helper.storage_counter(100_000_000_000_000_000_000_000)).to eq("86,736.2 EB") end end end diff --git a/spec/helpers/version_check_helper_spec.rb b/spec/helpers/version_check_helper_spec.rb index 9d4e34abef5..bfec7ad4bba 100644 --- a/spec/helpers/version_check_helper_spec.rb +++ b/spec/helpers/version_check_helper_spec.rb @@ -13,21 +13,21 @@ describe VersionCheckHelper do before do allow(Rails.env).to receive(:production?) { true } allow(Gitlab::CurrentSettings.current_application_settings).to receive(:version_check_enabled) { true } - allow_any_instance_of(VersionCheck).to receive(:url) { 'https://version.host.com/check.svg?gitlab_info=xxx' } - - @image_tag = helper.version_status_badge + allow(VersionCheck).to receive(:url) { 'https://version.host.com/check.svg?gitlab_info=xxx' } end it 'should return an image tag' do - expect(@image_tag).to match(/^<img/) + expect(helper.version_status_badge).to start_with('<img') end it 'should have a js prefixed css class' do - expect(@image_tag).to match(/class="js-version-status-badge lazy"/) + expect(helper.version_status_badge) + .to match(/class="js-version-status-badge lazy"/) end it 'should have a VersionCheck url as the src' do - expect(@image_tag).to match(%r{src="https://version\.host\.com/check\.svg\?gitlab_info=xxx"}) + expect(helper.version_status_badge) + .to include(%{src="https://version.host.com/check.svg?gitlab_info=xxx"}) end end end diff --git a/spec/initializers/active_record_locking_spec.rb b/spec/initializers/active_record_locking_spec.rb new file mode 100644 index 00000000000..5a16aef78e6 --- /dev/null +++ b/spec/initializers/active_record_locking_spec.rb @@ -0,0 +1,44 @@ +# frozen_string_literal: true + +require 'spec_helper' + +describe 'ActiveRecord locking' do + let(:issue) { create(:issue) } + + shared_examples 'locked model' do + before do + issue.update_column(:lock_version, start_lock_version) + end + + it 'can be updated' do + issue.update(title: "New title") + + expect(issue.reload.lock_version).to eq(new_lock_version) + end + + it 'can be deleted' do + expect { issue.destroy }.to change { Issue.count }.by(-1) + end + end + + context 'when lock_version is NULL' do + let(:start_lock_version) { nil } + let(:new_lock_version) { 1 } + + it_behaves_like 'locked model' + end + + context 'when lock_version is 0' do + let(:start_lock_version) { 0 } + let(:new_lock_version) { 1 } + + it_behaves_like 'locked model' + end + + context 'when lock_version is 1' do + let(:start_lock_version) { 1 } + let(:new_lock_version) { 2 } + + it_behaves_like 'locked model' + end +end diff --git a/spec/initializers/lograge_spec.rb b/spec/initializers/lograge_spec.rb index af54a777373..24d366731a2 100644 --- a/spec/initializers/lograge_spec.rb +++ b/spec/initializers/lograge_spec.rb @@ -6,7 +6,7 @@ describe 'lograge', type: :request do let(:headers) { { 'X-Request-ID' => 'new-correlation-id' } } context 'for API requests' do - subject { get("/api/v4/endpoint", {}, headers) } + subject { get("/api/v4/endpoint", params: {}, headers: headers) } it 'logs to api_json log' do # we assert receiving parameters by grape logger @@ -19,7 +19,7 @@ describe 'lograge', type: :request do end context 'for Controller requests' do - subject { get("/", {}, headers) } + subject { get("/", params: {}, headers: headers) } it 'logs to production_json log' do # formatter receives a hash with correlation id diff --git a/spec/initializers/8_metrics_spec.rb b/spec/initializers/zz_metrics_spec.rb index 80c77057065..3eaccfe8d8b 100644 --- a/spec/initializers/8_metrics_spec.rb +++ b/spec/initializers/zz_metrics_spec.rb @@ -16,7 +16,7 @@ describe 'instrument_classes' do end it 'can autoload and instrument all files' do - require_relative '../../config/initializers/8_metrics' + require_relative '../../config/initializers/zz_metrics' expect { instrument_classes(config) }.not_to raise_error end end diff --git a/spec/javascripts/blob_edit/blob_bundle_spec.js b/spec/javascripts/blob_edit/blob_bundle_spec.js index 759d170af77..57f60a4a3dd 100644 --- a/spec/javascripts/blob_edit/blob_bundle_spec.js +++ b/spec/javascripts/blob_edit/blob_bundle_spec.js @@ -14,6 +14,7 @@ describe('EditBlob', () => { setFixtures(` <div class="js-edit-blob-form"> <button class="js-commit-button"></button> + <a class="btn btn-cancel" href="#"></a> </div>`); blobBundle(); }); @@ -27,4 +28,10 @@ describe('EditBlob', () => { expect(window.onbeforeunload).toBeNull(); }); + + it('removes beforeunload listener when cancel link is clicked', () => { + $('.btn.btn-cancel').click(); + + expect(window.onbeforeunload).toBeNull(); + }); }); diff --git a/spec/javascripts/boards/boards_store_spec.js b/spec/javascripts/boards/boards_store_spec.js index 54f1edfb1f9..22f192bc7f3 100644 --- a/spec/javascripts/boards/boards_store_spec.js +++ b/spec/javascripts/boards/boards_store_spec.js @@ -65,6 +65,13 @@ describe('Store', () => { expect(list).toBeDefined(); }); + it('finds list by label ID', () => { + boardsStore.addList(listObj); + const list = boardsStore.findListByLabelId(listObj.label.id); + + expect(list.id).toBe(listObj.id); + }); + it('gets issue when new list added', done => { boardsStore.addList(listObj); const list = boardsStore.findList('id', listObj.id); diff --git a/spec/javascripts/boards/components/issue_due_date_spec.js b/spec/javascripts/boards/components/issue_due_date_spec.js index 9e49330c052..054cf8c5b7d 100644 --- a/spec/javascripts/boards/components/issue_due_date_spec.js +++ b/spec/javascripts/boards/components/issue_due_date_spec.js @@ -49,10 +49,11 @@ describe('Issue Due Date component', () => { it('should render month and day for other dates', () => { date.setDate(date.getDate() + 17); vm = createComponent(date); + const today = new Date(); + const isDueInCurrentYear = today.getFullYear() === date.getFullYear(); + const format = isDueInCurrentYear ? 'mmm d' : 'mmm d, yyyy'; - expect(vm.$el.querySelector('time').textContent.trim()).toEqual( - dateFormat(date, 'mmm d', true), - ); + expect(vm.$el.querySelector('time').textContent.trim()).toEqual(dateFormat(date, format, true)); }); it('should contain the correct `.text-danger` css class for overdue issue', () => { diff --git a/spec/javascripts/boards/issue_spec.js b/spec/javascripts/boards/issue_spec.js index 437ab4bb3df..54fb0e8228b 100644 --- a/spec/javascripts/boards/issue_spec.js +++ b/spec/javascripts/boards/issue_spec.js @@ -55,15 +55,27 @@ describe('Issue model', () => { expect(issue.labels.length).toBe(2); }); - it('does not add existing label', () => { + it('does not add label if label id exists', () => { + issue.addLabel({ + id: 1, + title: 'test 2', + color: 'blue', + description: 'testing', + }); + + expect(issue.labels.length).toBe(1); + expect(issue.labels[0].color).toBe('red'); + }); + + it('adds other label with same title', () => { issue.addLabel({ id: 2, title: 'test', color: 'blue', - description: 'bugs!', + description: 'other test', }); - expect(issue.labels.length).toBe(1); + expect(issue.labels.length).toBe(2); }); it('finds label', () => { diff --git a/spec/javascripts/ci_variable_list/ci_variable_list_spec.js b/spec/javascripts/ci_variable_list/ci_variable_list_spec.js index 30b15011def..bef59b86d0c 100644 --- a/spec/javascripts/ci_variable_list/ci_variable_list_spec.js +++ b/spec/javascripts/ci_variable_list/ci_variable_list_spec.js @@ -118,6 +118,8 @@ describe('VariableList', () => { loadFixtures('projects/ci_cd_settings.html.raw'); $wrapper = $('.js-ci-variable-list-section'); + $wrapper.find('.js-ci-variable-input-protected').attr('data-default', 'false'); + variableList = new VariableList({ container: $wrapper, formField: 'variables', diff --git a/spec/javascripts/diffs/components/app_spec.js b/spec/javascripts/diffs/components/app_spec.js index 1e2f7ff4fd8..a2cbc0f3c72 100644 --- a/spec/javascripts/diffs/components/app_spec.js +++ b/spec/javascripts/diffs/components/app_spec.js @@ -1,33 +1,44 @@ -import Vue from 'vue'; -import { mountComponentWithStore } from 'spec/helpers/vue_mount_component_helper'; +import Vuex from 'vuex'; +import { shallowMount, createLocalVue } from '@vue/test-utils'; import { TEST_HOST } from 'spec/test_constants'; import App from '~/diffs/components/app.vue'; +import NoChanges from '~/diffs/components/no_changes.vue'; +import DiffFile from '~/diffs/components/diff_file.vue'; import createDiffsStore from '../create_diffs_store'; describe('diffs/components/app', () => { const oldMrTabs = window.mrTabs; - const Component = Vue.extend(App); - + let store; let vm; - beforeEach(() => { - // setup globals (needed for component to mount :/) - window.mrTabs = jasmine.createSpyObj('mrTabs', ['resetViewContainer']); - window.mrTabs.expandViewContainer = jasmine.createSpy(); - window.location.hash = 'ABC_123'; + function createComponent(props = {}, extendStore = () => {}) { + const localVue = createLocalVue(); - // setup component - const store = createDiffsStore(); + localVue.use(Vuex); + + store = createDiffsStore(); store.state.diffs.isLoading = false; - vm = mountComponentWithStore(Component, { - store, - props: { + extendStore(store); + + vm = shallowMount(localVue.extend(App), { + localVue, + propsData: { endpoint: `${TEST_HOST}/diff/endpoint`, projectPath: 'namespace/project', currentUser: {}, + changesEmptyStateIllustration: '', + ...props, }, + store, }); + } + + beforeEach(() => { + // setup globals (needed for component to mount :/) + window.mrTabs = jasmine.createSpyObj('mrTabs', ['resetViewContainer']); + window.mrTabs.expandViewContainer = jasmine.createSpy(); + window.location.hash = 'ABC_123'; }); afterEach(() => { @@ -35,21 +46,53 @@ describe('diffs/components/app', () => { window.mrTabs = oldMrTabs; // reset component - vm.$destroy(); + vm.destroy(); }); it('does not show commit info', () => { - expect(vm.$el).not.toContainElement('.blob-commit-info'); + createComponent(); + + expect(vm.contains('.blob-commit-info')).toBe(false); }); it('sets highlighted row if hash exists in location object', done => { - vm.$props.shouldShow = true; - - vm.$nextTick() - .then(() => { - expect(vm.$store.state.diffs.highlightedRow).toBe('ABC_123'); - }) - .then(done) - .catch(done.fail); + createComponent({ + shouldShow: true, + }); + + // Component uses $nextTick so we wait until that has finished + setTimeout(() => { + expect(store.state.diffs.highlightedRow).toBe('ABC_123'); + + done(); + }); + }); + + describe('empty state', () => { + it('renders empty state when no diff files exist', () => { + createComponent(); + + expect(vm.contains(NoChanges)).toBe(true); + }); + + it('does not render empty state when diff files exist', () => { + createComponent({}, () => { + store.state.diffs.diffFiles.push({ + id: 1, + }); + }); + + expect(vm.contains(NoChanges)).toBe(false); + expect(vm.findAll(DiffFile).length).toBe(1); + }); + + it('does not render empty state when versions match', () => { + createComponent({}, () => { + store.state.diffs.startVersion = { version_index: 1 }; + store.state.diffs.mergeRequestDiff = { version_index: 1 }; + }); + + expect(vm.contains(NoChanges)).toBe(false); + }); }); }); diff --git a/spec/javascripts/diffs/components/diff_content_spec.js b/spec/javascripts/diffs/components/diff_content_spec.js index c25f6167163..9e158327a77 100644 --- a/spec/javascripts/diffs/components/diff_content_spec.js +++ b/spec/javascripts/diffs/components/diff_content_spec.js @@ -17,6 +17,7 @@ describe('DiffContent', () => { current_user: { can_create_note: false, }, + preview_note_path: 'path/to/preview', }; vm = mountComponentWithStore(Component, { @@ -49,6 +50,45 @@ describe('DiffContent', () => { }); }); + describe('empty files', () => { + beforeEach(() => { + vm.diffFile.empty = true; + vm.diffFile.highlighted_diff_lines = []; + vm.diffFile.parallel_diff_lines = []; + }); + + it('should render a message', done => { + vm.$nextTick(() => { + const block = vm.$el.querySelector('.diff-viewer .nothing-here-block'); + + expect(block).not.toBe(null); + expect(block.textContent.trim()).toContain('Empty file'); + + done(); + }); + }); + + it('should not render multiple messages', done => { + vm.diffFile.mode_changed = true; + vm.diffFile.b_mode = '100755'; + vm.diffFile.viewer.name = 'mode_changed'; + + vm.$nextTick(() => { + expect(vm.$el.querySelectorAll('.nothing-here-block').length).toBe(1); + + done(); + }); + }); + + it('should not render diff table', done => { + vm.$nextTick(() => { + expect(vm.$el.querySelector('table')).toBe(null); + + done(); + }); + }); + }); + describe('Non-Text diffs', () => { beforeEach(() => { vm.diffFile.viewer.name = 'image'; diff --git a/spec/javascripts/diffs/components/no_changes_spec.js b/spec/javascripts/diffs/components/no_changes_spec.js index 7237274eb43..e45d34bf9d5 100644 --- a/spec/javascripts/diffs/components/no_changes_spec.js +++ b/spec/javascripts/diffs/components/no_changes_spec.js @@ -1 +1,40 @@ -// TODO: https://gitlab.com/gitlab-org/gitlab-ce/issues/48034 +import { createLocalVue, shallowMount } from '@vue/test-utils'; +import Vuex from 'vuex'; +import { createStore } from '~/mr_notes/stores'; +import NoChanges from '~/diffs/components/no_changes.vue'; + +describe('Diff no changes empty state', () => { + let vm; + + function createComponent(extendStore = () => {}) { + const localVue = createLocalVue(); + localVue.use(Vuex); + + const store = createStore(); + extendStore(store); + + vm = shallowMount(localVue.extend(NoChanges), { + localVue, + store, + propsData: { + changesEmptyStateIllustration: '', + }, + }); + } + + afterEach(() => { + vm.destroy(); + }); + + it('prevents XSS', () => { + createComponent(store => { + // eslint-disable-next-line no-param-reassign + store.state.notes.noteableData = { + source_branch: '<script>alert("test");</script>', + target_branch: '<script>alert("test");</script>', + }; + }); + + expect(vm.contains('script')).toBe(false); + }); +}); diff --git a/spec/javascripts/diffs/mock_data/diff_discussions.js b/spec/javascripts/diffs/mock_data/diff_discussions.js index 44313caba29..c1e9f791925 100644 --- a/spec/javascripts/diffs/mock_data/diff_discussions.js +++ b/spec/javascripts/diffs/mock_data/diff_discussions.js @@ -487,8 +487,19 @@ export default { ], }, diff_discussion: true, - truncated_diff_lines: - '<tr class="line_holder new" id="">\n<td class="diff-line-num new old_line" data-linenumber="1">\n \n</td>\n<td class="diff-line-num new new_line" data-linenumber="1">\n1\n</td>\n<td class="line_content new noteable_line"><span id="LC1" class="line" lang="plaintext"> - Bad dates</span>\n</td>\n</tr>\n<tr class="line_holder new" id="">\n<td class="diff-line-num new old_line" data-linenumber="1">\n \n</td>\n<td class="diff-line-num new new_line" data-linenumber="2">\n2\n</td>\n<td class="line_content new noteable_line"><span id="LC2" class="line" lang="plaintext"></span>\n</td>\n</tr>\n', + truncated_diff_lines: [ + { + text: 'line', + rich_text: + '<tr class="line_holder new" id="">\n<td class="diff-line-num new old_line" data-linenumber="1">\n \n</td>\n<td class="diff-line-num new new_line" data-linenumber="1">\n1\n</td>\n<td class="line_content new noteable_line"><span id="LC1" class="line" lang="plaintext"> - Bad dates</span>\n</td>\n</tr>\n<tr class="line_holder new" id="">\n<td class="diff-line-num new old_line" data-linenumber="1">\n \n</td>\n<td class="diff-line-num new new_line" data-linenumber="2">\n2\n</td>\n<td class="line_content new noteable_line"><span id="LC2" class="line" lang="plaintext"></span>\n</td>\n</tr>\n', + can_receive_suggestion: true, + line_code: '6f209374f7e565f771b95720abf46024c41d1885_1_1', + type: 'new', + old_line: null, + new_line: 1, + meta_data: null, + }, + ], }; export const imageDiffDiscussions = [ diff --git a/spec/javascripts/diffs/store/mutations_spec.js b/spec/javascripts/diffs/store/mutations_spec.js index 23e8761bc55..d8733941181 100644 --- a/spec/javascripts/diffs/store/mutations_spec.js +++ b/spec/javascripts/diffs/store/mutations_spec.js @@ -277,6 +277,152 @@ describe('DiffsStoreMutations', () => { expect(state.diffFiles[0].highlighted_diff_lines[0].discussions[0].id).toEqual(1); }); + it('updates existing discussion', () => { + const diffPosition = { + base_sha: 'ed13df29948c41ba367caa757ab3ec4892509910', + head_sha: 'b921914f9a834ac47e6fd9420f78db0f83559130', + new_line: null, + new_path: '500-lines-4.txt', + old_line: 5, + old_path: '500-lines-4.txt', + start_sha: 'ed13df29948c41ba367caa757ab3ec4892509910', + }; + + const state = { + latestDiff: true, + diffFiles: [ + { + file_hash: 'ABC', + parallel_diff_lines: [ + { + left: { + line_code: 'ABC_1', + discussions: [], + }, + right: { + line_code: 'ABC_1', + discussions: [], + }, + }, + ], + highlighted_diff_lines: [ + { + line_code: 'ABC_1', + discussions: [], + }, + ], + }, + ], + }; + const discussion = { + id: 1, + line_code: 'ABC_1', + diff_discussion: true, + resolvable: true, + original_position: diffPosition, + position: diffPosition, + diff_file: { + file_hash: state.diffFiles[0].file_hash, + }, + }; + + const diffPositionByLineCode = { + ABC_1: diffPosition, + }; + + mutations[types.SET_LINE_DISCUSSIONS_FOR_FILE](state, { + discussion, + diffPositionByLineCode, + }); + + expect(state.diffFiles[0].parallel_diff_lines[0].left.discussions.length).toEqual(1); + expect(state.diffFiles[0].parallel_diff_lines[0].left.discussions[0].id).toEqual(1); + expect(state.diffFiles[0].parallel_diff_lines[0].right.discussions).toEqual([]); + + expect(state.diffFiles[0].highlighted_diff_lines[0].discussions.length).toEqual(1); + expect(state.diffFiles[0].highlighted_diff_lines[0].discussions[0].id).toEqual(1); + + mutations[types.SET_LINE_DISCUSSIONS_FOR_FILE](state, { + discussion: { + ...discussion, + resolved: true, + notes: ['test'], + }, + diffPositionByLineCode, + }); + + expect(state.diffFiles[0].parallel_diff_lines[0].left.discussions[0].notes.length).toBe(1); + expect(state.diffFiles[0].highlighted_diff_lines[0].discussions[0].notes.length).toBe(1); + + expect(state.diffFiles[0].parallel_diff_lines[0].left.discussions[0].resolved).toBe(true); + expect(state.diffFiles[0].highlighted_diff_lines[0].discussions[0].resolved).toBe(true); + }); + + it('should not duplicate inline diff discussions', () => { + const diffPosition = { + base_sha: 'ed13df29948c41ba367caa757ab3ec4892509910', + head_sha: 'b921914f9a834ac47e6fd9420f78db0f83559130', + new_line: null, + new_path: '500-lines-4.txt', + old_line: 5, + old_path: '500-lines-4.txt', + start_sha: 'ed13df29948c41ba367caa757ab3ec4892509910', + }; + + const state = { + latestDiff: true, + diffFiles: [ + { + file_hash: 'ABC', + highlighted_diff_lines: [ + { + line_code: 'ABC_1', + discussions: [ + { + id: 1, + line_code: 'ABC_1', + diff_discussion: true, + resolvable: true, + original_position: diffPosition, + position: diffPosition, + diff_file: { + file_hash: 'ABC', + }, + }, + ], + }, + { + line_code: 'ABC_2', + discussions: [], + }, + ], + }, + ], + }; + const discussion = { + id: 2, + line_code: 'ABC_2', + diff_discussion: true, + resolvable: true, + original_position: diffPosition, + position: diffPosition, + diff_file: { + file_hash: state.diffFiles[0].file_hash, + }, + }; + + const diffPositionByLineCode = { + ABC_2: diffPosition, + }; + + mutations[types.SET_LINE_DISCUSSIONS_FOR_FILE](state, { + discussion, + diffPositionByLineCode, + }); + + expect(state.diffFiles[0].highlighted_diff_lines[0].discussions.length).toBe(1); + }); + it('should add legacy discussions to the given line', () => { const diffPosition = { base_sha: 'ed13df29948c41ba367caa757ab3ec4892509910', @@ -356,10 +502,12 @@ describe('DiffsStoreMutations', () => { { id: 1, line_code: 'ABC_1', + notes: [], }, { id: 2, line_code: 'ABC_1', + notes: [], }, ], }, @@ -376,10 +524,12 @@ describe('DiffsStoreMutations', () => { { id: 1, line_code: 'ABC_1', + notes: [], }, { id: 2, line_code: 'ABC_1', + notes: [], }, ], }, diff --git a/spec/javascripts/diffs/store/utils_spec.js b/spec/javascripts/diffs/store/utils_spec.js index f096638e3d6..4268634d302 100644 --- a/spec/javascripts/diffs/store/utils_spec.js +++ b/spec/javascripts/diffs/store/utils_spec.js @@ -294,10 +294,14 @@ describe('DiffsStoreUtils', () => { }); describe('prepareDiffData', () => { - it('sets the renderIt and collapsed attribute on files', () => { - const preparedDiff = { diff_files: [getDiffFileMock()] }; + let preparedDiff; + + beforeEach(() => { + preparedDiff = { diff_files: [getDiffFileMock()] }; utils.prepareDiffData(preparedDiff); + }); + it('sets the renderIt and collapsed attribute on files', () => { const firstParallelDiffLine = preparedDiff.diff_files[0].parallel_diff_lines[2]; expect(firstParallelDiffLine.left.discussions.length).toBe(0); @@ -323,6 +327,18 @@ describe('DiffsStoreUtils', () => { expect(preparedDiff.diff_files[0].renderIt).toBeTruthy(); expect(preparedDiff.diff_files[0].collapsed).toBeFalsy(); }); + + it('adds line_code to all lines', () => { + expect( + preparedDiff.diff_files[0].parallel_diff_lines.filter(line => !line.line_code), + ).toHaveLength(0); + }); + + it('uses right line code if left has none', () => { + const firstLine = preparedDiff.diff_files[0].parallel_diff_lines[0]; + + expect(firstLine.line_code).toEqual(firstLine.right.line_code); + }); }); describe('isDiscussionApplicableToLine', () => { diff --git a/spec/javascripts/environments/environment_terminal_button_spec.js b/spec/javascripts/environments/environment_terminal_button_spec.js index f1576b19d1b..56e18db59c5 100644 --- a/spec/javascripts/environments/environment_terminal_button_spec.js +++ b/spec/javascripts/environments/environment_terminal_button_spec.js @@ -2,30 +2,46 @@ import Vue from 'vue'; import terminalComp from '~/environments/components/environment_terminal_button.vue'; describe('Stop Component', () => { - let TerminalComponent; let component; const terminalPath = '/path'; - beforeEach(() => { - TerminalComponent = Vue.extend(terminalComp); - + const mountWithProps = props => { + const TerminalComponent = Vue.extend(terminalComp); component = new TerminalComponent({ - propsData: { - terminalPath, - }, + propsData: props, }).$mount(); - }); + }; + + describe('enabled', () => { + beforeEach(() => { + mountWithProps({ terminalPath }); + }); + + describe('computed', () => { + it('title', () => { + expect(component.title).toEqual('Terminal'); + }); + }); - describe('computed', () => { - it('title', () => { - expect(component.title).toEqual('Terminal'); + it('should render a link to open a web terminal with the provided path', () => { + expect(component.$el.tagName).toEqual('A'); + expect(component.$el.getAttribute('data-original-title')).toEqual('Terminal'); + expect(component.$el.getAttribute('aria-label')).toEqual('Terminal'); + expect(component.$el.getAttribute('href')).toEqual(terminalPath); + }); + + it('should render a non-disabled button', () => { + expect(component.$el.classList).not.toContain('disabled'); }); }); - it('should render a link to open a web terminal with the provided path', () => { - expect(component.$el.tagName).toEqual('A'); - expect(component.$el.getAttribute('data-original-title')).toEqual('Terminal'); - expect(component.$el.getAttribute('aria-label')).toEqual('Terminal'); - expect(component.$el.getAttribute('href')).toEqual(terminalPath); + describe('disabled', () => { + beforeEach(() => { + mountWithProps({ terminalPath, disabled: true }); + }); + + it('should render a disabled button', () => { + expect(component.$el.classList).toContain('disabled'); + }); }); }); diff --git a/spec/javascripts/filtered_search/dropdown_utils_spec.js b/spec/javascripts/filtered_search/dropdown_utils_spec.js index 6605b0a30d7..cfd0b96ec43 100644 --- a/spec/javascripts/filtered_search/dropdown_utils_spec.js +++ b/spec/javascripts/filtered_search/dropdown_utils_spec.js @@ -211,132 +211,6 @@ describe('Dropdown Utils', () => { }); }); - describe('mergeDuplicateLabels', () => { - const dataMap = { - label: { - title: 'label', - color: '#FFFFFF', - }, - }; - - it('should add label to dataMap if it is not a duplicate', () => { - const newLabel = { - title: 'new-label', - color: '#000000', - }; - - const updated = DropdownUtils.mergeDuplicateLabels(dataMap, newLabel); - - expect(updated[newLabel.title]).toEqual(newLabel); - }); - - it('should merge colors if label is a duplicate', () => { - const duplicate = { - title: 'label', - color: '#000000', - }; - - const updated = DropdownUtils.mergeDuplicateLabels(dataMap, duplicate); - - expect(updated.label.multipleColors).toEqual([dataMap.label.color, duplicate.color]); - }); - }); - - describe('duplicateLabelColor', () => { - it('should linear-gradient 2 colors', () => { - const gradient = DropdownUtils.duplicateLabelColor(['#FFFFFF', '#000000']); - - expect(gradient).toEqual( - 'linear-gradient(#FFFFFF 0%, #FFFFFF 50%, #000000 50%, #000000 100%)', - ); - }); - - it('should linear-gradient 3 colors', () => { - const gradient = DropdownUtils.duplicateLabelColor(['#FFFFFF', '#000000', '#333333']); - - expect(gradient).toEqual( - 'linear-gradient(#FFFFFF 0%, #FFFFFF 33%, #000000 33%, #000000 66%, #333333 66%, #333333 100%)', - ); - }); - - it('should linear-gradient 4 colors', () => { - const gradient = DropdownUtils.duplicateLabelColor([ - '#FFFFFF', - '#000000', - '#333333', - '#DDDDDD', - ]); - - expect(gradient).toEqual( - 'linear-gradient(#FFFFFF 0%, #FFFFFF 25%, #000000 25%, #000000 50%, #333333 50%, #333333 75%, #DDDDDD 75%, #DDDDDD 100%)', - ); - }); - - it('should not linear-gradient more than 4 colors', () => { - const gradient = DropdownUtils.duplicateLabelColor([ - '#FFFFFF', - '#000000', - '#333333', - '#DDDDDD', - '#EEEEEE', - ]); - - expect(gradient.indexOf('#EEEEEE')).toBe(-1); - }); - }); - - describe('duplicateLabelPreprocessing', () => { - it('should set preprocessed to true', () => { - const results = DropdownUtils.duplicateLabelPreprocessing([]); - - expect(results.preprocessed).toEqual(true); - }); - - it('should not mutate existing data if there are no duplicates', () => { - const data = [ - { - title: 'label1', - color: '#FFFFFF', - }, - { - title: 'label2', - color: '#000000', - }, - ]; - const results = DropdownUtils.duplicateLabelPreprocessing(data); - - expect(results.length).toEqual(2); - expect(results[0]).toEqual(data[0]); - expect(results[1]).toEqual(data[1]); - }); - - describe('duplicate labels', () => { - const data = [ - { - title: 'label', - color: '#FFFFFF', - }, - { - title: 'label', - color: '#000000', - }, - ]; - const results = DropdownUtils.duplicateLabelPreprocessing(data); - - it('should merge duplicate labels', () => { - expect(results.length).toEqual(1); - }); - - it('should convert multiple colored labels into linear-gradient', () => { - expect(results[0].color).toEqual(DropdownUtils.duplicateLabelColor(['#FFFFFF', '#000000'])); - }); - - it('should set multiple colored label text color to black', () => { - expect(results[0].text_color).toEqual('#000000'); - }); - }); - }); - describe('setDataValueIfSelected', () => { beforeEach(() => { spyOn(FilteredSearchDropdownManager, 'addWordToInput').and.callFake(() => {}); diff --git a/spec/javascripts/filtered_search/filtered_search_visual_tokens_spec.js b/spec/javascripts/filtered_search/filtered_search_visual_tokens_spec.js index 4f561df7943..9aa3cbaa231 100644 --- a/spec/javascripts/filtered_search/filtered_search_visual_tokens_spec.js +++ b/spec/javascripts/filtered_search/filtered_search_visual_tokens_spec.js @@ -909,16 +909,6 @@ describe('Filtered Search Visual Tokens', () => { expect(token.style.backgroundColor).not.toEqual(originalBackgroundColor); }); - it('should not set backgroundColor when it is a linear-gradient', () => { - const token = subject.setTokenStyle( - bugLabelToken, - 'linear-gradient(135deg, red, blue)', - 'white', - ); - - expect(token.style.backgroundColor).toEqual(bugLabelToken.style.backgroundColor); - }); - it('should set textColor', () => { const token = subject.setTokenStyle(bugLabelToken, 'white', 'black'); @@ -935,39 +925,6 @@ describe('Filtered Search Visual Tokens', () => { }); }); - describe('preprocessLabel', () => { - const endpoint = 'endpoint'; - - it('does not preprocess more than once', () => { - let labels = []; - - spyOn(DropdownUtils, 'duplicateLabelPreprocessing').and.callFake(() => []); - - labels = FilteredSearchVisualTokens.preprocessLabel(endpoint, labels); - FilteredSearchVisualTokens.preprocessLabel(endpoint, labels); - - expect(DropdownUtils.duplicateLabelPreprocessing.calls.count()).toEqual(1); - }); - - describe('not preprocessed before', () => { - it('returns preprocessed labels', () => { - let labels = []; - - expect(labels.preprocessed).not.toEqual(true); - labels = FilteredSearchVisualTokens.preprocessLabel(endpoint, labels); - - expect(labels.preprocessed).toEqual(true); - }); - - it('overrides AjaxCache with preprocessed results', () => { - spyOn(AjaxCache, 'override').and.callFake(() => {}); - FilteredSearchVisualTokens.preprocessLabel(endpoint, []); - - expect(AjaxCache.override.calls.count()).toEqual(1); - }); - }); - }); - describe('updateLabelTokenColor', () => { const jsonFixtureName = 'labels/project_labels.json'; const dummyEndpoint = '/dummy/endpoint'; diff --git a/spec/javascripts/fixtures/blob.rb b/spec/javascripts/fixtures/blob.rb index 81e8a51a902..1b2a3b484bb 100644 --- a/spec/javascripts/fixtures/blob.rb +++ b/spec/javascripts/fixtures/blob.rb @@ -22,10 +22,11 @@ describe Projects::BlobController, '(JavaScript fixtures)', type: :controller do end it 'blob/show.html.raw' do |example| - get(:show, - namespace_id: project.namespace, - project_id: project, - id: 'add-ipython-files/files/ipython/basic.ipynb') + get(:show, params: { + namespace_id: project.namespace, + project_id: project, + id: 'add-ipython-files/files/ipython/basic.ipynb' + }) expect(response).to be_success store_frontend_fixture(response, example.description) diff --git a/spec/javascripts/fixtures/boards.rb b/spec/javascripts/fixtures/boards.rb index 494c9cabdcc..1d675e008ba 100644 --- a/spec/javascripts/fixtures/boards.rb +++ b/spec/javascripts/fixtures/boards.rb @@ -18,9 +18,10 @@ describe Projects::BoardsController, '(JavaScript fixtures)', type: :controller end it 'boards/show.html.raw' do |example| - get(:index, - namespace_id: project.namespace, - project_id: project) + get(:index, params: { + namespace_id: project.namespace, + project_id: project + }) expect(response).to be_success store_frontend_fixture(response, example.description) diff --git a/spec/javascripts/fixtures/branches.rb b/spec/javascripts/fixtures/branches.rb index 4fc072d2585..3cc713ef90f 100644 --- a/spec/javascripts/fixtures/branches.rb +++ b/spec/javascripts/fixtures/branches.rb @@ -22,9 +22,10 @@ describe Projects::BranchesController, '(JavaScript fixtures)', type: :controlle end it 'branches/new_branch.html.raw' do |example| - get :new, + get :new, params: { namespace_id: project.namespace.to_param, project_id: project + } expect(response).to be_success store_frontend_fixture(response, example.description) diff --git a/spec/javascripts/fixtures/clusters.rb b/spec/javascripts/fixtures/clusters.rb index 8e74c4f859c..69dbe54ffc2 100644 --- a/spec/javascripts/fixtures/clusters.rb +++ b/spec/javascripts/fixtures/clusters.rb @@ -23,10 +23,11 @@ describe Projects::ClustersController, '(JavaScript fixtures)', type: :controlle end it 'clusters/show_cluster.html.raw' do |example| - get :show, + get :show, params: { namespace_id: project.namespace.to_param, project_id: project, id: cluster + } expect(response).to be_success store_frontend_fixture(response, example.description) diff --git a/spec/javascripts/fixtures/commit.rb b/spec/javascripts/fixtures/commit.rb index 24ab8159a18..f0e4bb50c67 100644 --- a/spec/javascripts/fixtures/commit.rb +++ b/spec/javascripts/fixtures/commit.rb @@ -25,7 +25,7 @@ describe Projects::CommitController, '(JavaScript fixtures)', type: :controller id: commit.id } - get :show, params + get :show, params: params expect(response).to be_success store_frontend_fixture(response, example.description) diff --git a/spec/javascripts/fixtures/deploy_keys.rb b/spec/javascripts/fixtures/deploy_keys.rb index 24699c3043a..efbda955972 100644 --- a/spec/javascripts/fixtures/deploy_keys.rb +++ b/spec/javascripts/fixtures/deploy_keys.rb @@ -33,10 +33,10 @@ describe Projects::DeployKeysController, '(JavaScript fixtures)', type: :control create(:deploy_keys_project, project: project3, deploy_key: project_key) create(:deploy_keys_project, project: project4, deploy_key: project_key) - get :index, + get :index, params: { namespace_id: project.namespace.to_param, - project_id: project, - format: :json + project_id: project + }, format: :json expect(response).to be_success store_frontend_fixture(response, example.description) diff --git a/spec/javascripts/fixtures/groups.rb b/spec/javascripts/fixtures/groups.rb index b42f442557c..f8d55fc97c3 100644 --- a/spec/javascripts/fixtures/groups.rb +++ b/spec/javascripts/fixtures/groups.rb @@ -19,8 +19,7 @@ describe 'Groups (JavaScript fixtures)', type: :controller do describe GroupsController, '(JavaScript fixtures)', type: :controller do it 'groups/edit.html.raw' do |example| - get :edit, - id: group + get :edit, params: { id: group } expect(response).to be_success store_frontend_fixture(response, example.description) @@ -29,8 +28,7 @@ describe 'Groups (JavaScript fixtures)', type: :controller do describe Groups::Settings::CiCdController, '(JavaScript fixtures)', type: :controller do it 'groups/ci_cd_settings.html.raw' do |example| - get :show, - group_id: group + get :show, params: { group_id: group } expect(response).to be_success store_frontend_fixture(response, example.description) diff --git a/spec/javascripts/fixtures/issues.rb b/spec/javascripts/fixtures/issues.rb index 0ee2f82dfd6..18fb1bebf8b 100644 --- a/spec/javascripts/fixtures/issues.rb +++ b/spec/javascripts/fixtures/issues.rb @@ -43,9 +43,10 @@ describe Projects::IssuesController, '(JavaScript fixtures)', type: :controller it 'issues/issue_list.html.raw' do |example| create(:issue, project: project) - get :index, + get :index, params: { namespace_id: project.namespace.to_param, project_id: project + } expect(response).to be_success store_frontend_fixture(response, example.description) @@ -54,10 +55,11 @@ describe Projects::IssuesController, '(JavaScript fixtures)', type: :controller private def render_issue(fixture_file_name, issue) - get :show, + get :show, params: { namespace_id: project.namespace.to_param, project_id: project, id: issue.to_param + } expect(response).to be_success store_frontend_fixture(response, fixture_file_name) diff --git a/spec/javascripts/fixtures/jobs.rb b/spec/javascripts/fixtures/jobs.rb index 82d7a5e394e..d6b5349594d 100644 --- a/spec/javascripts/fixtures/jobs.rb +++ b/spec/javascripts/fixtures/jobs.rb @@ -34,21 +34,22 @@ describe Projects::JobsController, '(JavaScript fixtures)', type: :controller do end it 'builds/build-with-artifacts.html.raw' do |example| - get :show, + get :show, params: { namespace_id: project.namespace.to_param, project_id: project, id: build_with_artifacts.to_param + } expect(response).to be_success store_frontend_fixture(response, example.description) end it 'jobs/delayed.json' do |example| - get :show, - namespace_id: project.namespace.to_param, - project_id: project, - id: delayed_job.to_param, - format: :json + get :show, params: { + namespace_id: project.namespace.to_param, + project_id: project, + id: delayed_job.to_param + }, format: :json expect(response).to be_success store_frontend_fixture(response, example.description) diff --git a/spec/javascripts/fixtures/labels.rb b/spec/javascripts/fixtures/labels.rb index b730d557e21..9420194e675 100644 --- a/spec/javascripts/fixtures/labels.rb +++ b/spec/javascripts/fixtures/labels.rb @@ -31,9 +31,9 @@ describe 'Labels (JavaScript fixtures)' do end it 'labels/group_labels.json' do |example| - get :index, - group_id: group, - format: 'json' + get :index, params: { + group_id: group + }, format: 'json' expect(response).to be_success store_frontend_fixture(response, example.description) @@ -48,10 +48,10 @@ describe 'Labels (JavaScript fixtures)' do end it 'labels/project_labels.json' do |example| - get :index, + get :index, params: { namespace_id: group, - project_id: project, - format: 'json' + project_id: project + }, format: 'json' expect(response).to be_success store_frontend_fixture(response, example.description) diff --git a/spec/javascripts/fixtures/merge_requests.rb b/spec/javascripts/fixtures/merge_requests.rb index 7257d0c8556..26e81f06c0b 100644 --- a/spec/javascripts/fixtures/merge_requests.rb +++ b/spec/javascripts/fixtures/merge_requests.rb @@ -112,21 +112,21 @@ describe Projects::MergeRequestsController, '(JavaScript fixtures)', type: :cont private def render_discussions_json(merge_request, fixture_file_name) - get :discussions, + get :discussions, params: { namespace_id: project.namespace.to_param, project_id: project, - id: merge_request.to_param, - format: :json + id: merge_request.to_param + }, format: :json store_frontend_fixture(response, fixture_file_name) end def render_merge_request(fixture_file_name, merge_request) - get :show, + get :show, params: { namespace_id: project.namespace.to_param, project_id: project, - id: merge_request.to_param, - format: :html + id: merge_request.to_param + }, format: :html expect(response).to be_success store_frontend_fixture(response, fixture_file_name) diff --git a/spec/javascripts/fixtures/merge_requests_diffs.rb b/spec/javascripts/fixtures/merge_requests_diffs.rb index afe34b834b0..57462e74bb2 100644 --- a/spec/javascripts/fixtures/merge_requests_diffs.rb +++ b/spec/javascripts/fixtures/merge_requests_diffs.rb @@ -57,13 +57,13 @@ describe Projects::MergeRequests::DiffsController, '(JavaScript fixtures)', type private def render_merge_request(fixture_file_name, merge_request, view: 'inline', **extra_params) - get :show, + get :show, params: { namespace_id: project.namespace.to_param, project_id: project, id: merge_request.to_param, - format: :json, view: view, **extra_params + }, format: :json expect(response).to be_success store_frontend_fixture(response, fixture_file_name) diff --git a/spec/javascripts/fixtures/pipeline_schedules.rb b/spec/javascripts/fixtures/pipeline_schedules.rb index 56f27ea7df1..05d79ec8de9 100644 --- a/spec/javascripts/fixtures/pipeline_schedules.rb +++ b/spec/javascripts/fixtures/pipeline_schedules.rb @@ -22,20 +22,22 @@ describe Projects::PipelineSchedulesController, '(JavaScript fixtures)', type: : end it 'pipeline_schedules/edit.html.raw' do |example| - get :edit, + get :edit, params: { namespace_id: project.namespace.to_param, project_id: project, id: pipeline_schedule.id + } expect(response).to be_success store_frontend_fixture(response, example.description) end it 'pipeline_schedules/edit_with_variables.html.raw' do |example| - get :edit, + get :edit, params: { namespace_id: project.namespace.to_param, project_id: project, id: pipeline_schedule_populated.id + } expect(response).to be_success store_frontend_fixture(response, example.description) diff --git a/spec/javascripts/fixtures/pipelines.rb b/spec/javascripts/fixtures/pipelines.rb index bb85da50f0f..42b552e81c0 100644 --- a/spec/javascripts/fixtures/pipelines.rb +++ b/spec/javascripts/fixtures/pipelines.rb @@ -24,10 +24,10 @@ describe Projects::PipelinesController, '(JavaScript fixtures)', type: :controll end it 'pipelines/pipelines.json' do |example| - get :index, + get :index, params: { namespace_id: namespace, - project_id: project, - format: :json + project_id: project + }, format: :json expect(response).to be_success store_frontend_fixture(response, example.description) diff --git a/spec/javascripts/fixtures/projects.rb b/spec/javascripts/fixtures/projects.rb index d98f7f55b20..9b48646f8f0 100644 --- a/spec/javascripts/fixtures/projects.rb +++ b/spec/javascripts/fixtures/projects.rb @@ -28,27 +28,30 @@ describe 'Projects (JavaScript fixtures)', type: :controller do describe ProjectsController, '(JavaScript fixtures)', type: :controller do it 'projects/dashboard.html.raw' do |example| - get :show, + get :show, params: { namespace_id: project.namespace.to_param, id: project + } expect(response).to be_success store_frontend_fixture(response, example.description) end it 'projects/overview.html.raw' do |example| - get :show, + get :show, params: { namespace_id: project_with_repo.namespace.to_param, id: project_with_repo + } expect(response).to be_success store_frontend_fixture(response, example.description) end it 'projects/edit.html.raw' do |example| - get :edit, + get :edit, params: { namespace_id: project.namespace.to_param, id: project + } expect(response).to be_success store_frontend_fixture(response, example.description) @@ -57,18 +60,20 @@ describe 'Projects (JavaScript fixtures)', type: :controller do describe Projects::Settings::CiCdController, '(JavaScript fixtures)', type: :controller do it 'projects/ci_cd_settings.html.raw' do |example| - get :show, + get :show, params: { namespace_id: project.namespace.to_param, project_id: project + } expect(response).to be_success store_frontend_fixture(response, example.description) end it 'projects/ci_cd_settings_with_variables.html.raw' do |example| - get :show, + get :show, params: { namespace_id: project_variable_populated.namespace.to_param, project_id: project_variable_populated + } expect(response).to be_success store_frontend_fixture(response, example.description) diff --git a/spec/javascripts/fixtures/prometheus_service.rb b/spec/javascripts/fixtures/prometheus_service.rb index f95f8038ffb..746fbfd66dd 100644 --- a/spec/javascripts/fixtures/prometheus_service.rb +++ b/spec/javascripts/fixtures/prometheus_service.rb @@ -23,10 +23,11 @@ describe Projects::ServicesController, '(JavaScript fixtures)', type: :controlle end it 'services/prometheus/prometheus_service.html.raw' do |example| - get :edit, + get :edit, params: { namespace_id: namespace, project_id: project, id: service.to_param + } expect(response).to be_success store_frontend_fixture(response, example.description) diff --git a/spec/javascripts/fixtures/services.rb b/spec/javascripts/fixtures/services.rb index 9280ed5a7f1..6ccd74a07ff 100644 --- a/spec/javascripts/fixtures/services.rb +++ b/spec/javascripts/fixtures/services.rb @@ -23,10 +23,11 @@ describe Projects::ServicesController, '(JavaScript fixtures)', type: :controlle end it 'services/edit_service.html.raw' do |example| - get :edit, + get :edit, params: { namespace_id: namespace, project_id: project, id: service.to_param + } expect(response).to be_success store_frontend_fixture(response, example.description) diff --git a/spec/javascripts/fixtures/snippet.rb b/spec/javascripts/fixtures/snippet.rb index 38fc963caf7..a14837e4d4a 100644 --- a/spec/javascripts/fixtures/snippet.rb +++ b/spec/javascripts/fixtures/snippet.rb @@ -24,7 +24,7 @@ describe SnippetsController, '(JavaScript fixtures)', type: :controller do end it 'snippets/show.html.raw' do |example| - get(:show, id: snippet.to_param) + get(:show, params: { id: snippet.to_param }) expect(response).to be_success store_frontend_fixture(response, example.description) diff --git a/spec/javascripts/fixtures/todos.rb b/spec/javascripts/fixtures/todos.rb index 426b854fe8b..b5f6620873b 100644 --- a/spec/javascripts/fixtures/todos.rb +++ b/spec/javascripts/fixtures/todos.rb @@ -42,12 +42,12 @@ describe 'Todos (JavaScript fixtures)' do end it 'todos/todos.json' do |example| - post :create, + post :create, params: { namespace_id: namespace, project_id: project, issuable_type: 'issue', - issuable_id: issue_2.id, - format: 'json' + issuable_id: issue_2.id + }, format: 'json' expect(response).to be_success store_frontend_fixture(response, example.description) diff --git a/spec/javascripts/fixtures/u2f.rb b/spec/javascripts/fixtures/u2f.rb index e3d7986f2cf..f0aa874bf75 100644 --- a/spec/javascripts/fixtures/u2f.rb +++ b/spec/javascripts/fixtures/u2f.rb @@ -21,7 +21,7 @@ context 'U2F' do it 'u2f/authenticate.html.raw' do |example| allow(controller).to receive(:find_user).and_return(user) - post :create, user: { login: user.username, password: user.password } + post :create, params: { user: { login: user.username, password: user.password } } expect(response).to be_success store_frontend_fixture(response, example.description) diff --git a/spec/javascripts/jobs/components/artifacts_block_spec.js b/spec/javascripts/jobs/components/artifacts_block_spec.js index 2fa7ff653fe..27d480ef2ea 100644 --- a/spec/javascripts/jobs/components/artifacts_block_spec.js +++ b/spec/javascripts/jobs/components/artifacts_block_spec.js @@ -2,6 +2,7 @@ import Vue from 'vue'; import { getTimeago } from '~/lib/utils/datetime_utility'; import component from '~/jobs/components/artifacts_block.vue'; import mountComponent from '../../helpers/vue_mount_component_helper'; +import { trimText } from '../../helpers/vue_component_helper'; describe('Artifacts block', () => { const Component = Vue.extend(component); @@ -9,7 +10,7 @@ describe('Artifacts block', () => { const expireAt = '2018-08-14T09:38:49.157Z'; const timeago = getTimeago(); - const formatedDate = timeago.format(expireAt); + const formattedDate = timeago.format(expireAt); const expiredArtifact = { expire_at: expireAt, @@ -36,9 +37,8 @@ describe('Artifacts block', () => { expect(vm.$el.querySelector('.js-artifacts-removed')).not.toBeNull(); expect(vm.$el.querySelector('.js-artifacts-will-be-removed')).toBeNull(); - expect(vm.$el.textContent).toContain(formatedDate); - expect(vm.$el.querySelector('.js-artifacts-removed').textContent.trim()).toEqual( - 'The artifacts were removed', + expect(trimText(vm.$el.querySelector('.js-artifacts-removed').textContent)).toEqual( + `The artifacts were removed ${formattedDate}`, ); }); }); @@ -51,9 +51,8 @@ describe('Artifacts block', () => { expect(vm.$el.querySelector('.js-artifacts-removed')).toBeNull(); expect(vm.$el.querySelector('.js-artifacts-will-be-removed')).not.toBeNull(); - expect(vm.$el.textContent).toContain(formatedDate); - expect(vm.$el.querySelector('.js-artifacts-will-be-removed').textContent.trim()).toEqual( - 'The artifacts will be removed in', + expect(trimText(vm.$el.querySelector('.js-artifacts-will-be-removed').textContent)).toEqual( + `The artifacts will be removed ${formattedDate}`, ); }); }); diff --git a/spec/javascripts/jobs/components/sidebar_spec.js b/spec/javascripts/jobs/components/sidebar_spec.js index 424092d2d88..b0bc16d7c64 100644 --- a/spec/javascripts/jobs/components/sidebar_spec.js +++ b/spec/javascripts/jobs/components/sidebar_spec.js @@ -79,14 +79,6 @@ describe('Sidebar details block', () => { }); describe('information', () => { - it('should render merge request link', () => { - expect(trimText(vm.$el.querySelector('.js-job-mr').textContent)).toEqual('Merge Request: !2'); - - expect(vm.$el.querySelector('.js-job-mr a').getAttribute('href')).toEqual( - job.merge_request.path, - ); - }); - it('should render job duration', () => { expect(trimText(vm.$el.querySelector('.js-job-duration').textContent)).toEqual( 'Duration: 6 seconds', diff --git a/spec/javascripts/notes/components/discussion_filter_spec.js b/spec/javascripts/notes/components/discussion_filter_spec.js index 9070d968cfd..5efcab436e4 100644 --- a/spec/javascripts/notes/components/discussion_filter_spec.js +++ b/spec/javascripts/notes/components/discussion_filter_spec.js @@ -7,8 +7,9 @@ import { discussionFiltersMock, discussionMock } from '../mock_data'; describe('DiscussionFilter component', () => { let vm; let store; + let eventHub; - beforeEach(() => { + const mountComponent = () => { store = createStore(); const discussions = [ @@ -22,7 +23,7 @@ describe('DiscussionFilter component', () => { const selectedValue = discussionFiltersMock[0].value; store.state.discussions = discussions; - vm = mountComponentWithStore(Component, { + return mountComponentWithStore(Component, { el: null, store, props: { @@ -30,6 +31,11 @@ describe('DiscussionFilter component', () => { selectedValue, }, }); + }; + + beforeEach(() => { + window.mrTabs = undefined; + vm = mountComponent(); }); afterEach(() => { @@ -83,4 +89,30 @@ describe('DiscussionFilter component', () => { expect(defaultFilter.lastChild.classList).toContain('dropdown-divider'); }); + + describe('Merge request tabs', () => { + eventHub = new Vue(); + + beforeEach(() => { + window.mrTabs = { + eventHub, + currentTab: 'show', + }; + + vm = mountComponent(); + }); + + afterEach(() => { + window.mrTabs = undefined; + }); + + it('only renders when discussion tab is active', done => { + eventHub.$emit('MergeRequestTabChange', 'commit'); + + vm.$nextTick(() => { + expect(vm.$el.querySelector).toBeUndefined(); + done(); + }); + }); + }); }); diff --git a/spec/javascripts/notes/components/noteable_discussion_spec.js b/spec/javascripts/notes/components/noteable_discussion_spec.js index 106a4ac2546..3aff2dd0641 100644 --- a/spec/javascripts/notes/components/noteable_discussion_spec.js +++ b/spec/javascripts/notes/components/noteable_discussion_spec.js @@ -133,8 +133,10 @@ describe('noteable_discussion component', () => { }); }); - describe('commit discussion', () => { + describe('action text', () => { const commitId = 'razupaltuff'; + const truncatedCommitId = commitId.substr(0, 8); + let commitElement; beforeEach(() => { vm.$destroy(); @@ -143,7 +145,6 @@ describe('noteable_discussion component', () => { projectPath: 'something', }; - vm.$destroy(); vm = new Component({ propsData: { discussion: { @@ -159,17 +160,73 @@ describe('noteable_discussion component', () => { }, store, }).$mount(); + + commitElement = vm.$el.querySelector('.commit-sha'); + }); + + describe('for commit discussions', () => { + it('should display a monospace started a discussion on commit', () => { + expect(vm.$el).toContainText(`started a discussion on commit ${truncatedCommitId}`); + expect(commitElement).not.toBe(null); + expect(commitElement).toHaveText(truncatedCommitId); + }); }); - it('displays a monospace started a discussion on commit', () => { - const truncatedCommitId = commitId.substr(0, 8); + describe('for diff discussion with a commit id', () => { + it('should display started discussion on commit header', done => { + vm.discussion.for_commit = false; - expect(vm.$el).toContainText(`started a discussion on commit ${truncatedCommitId}`); + vm.$nextTick(() => { + expect(vm.$el).toContainText(`started a discussion on commit ${truncatedCommitId}`); + expect(commitElement).not.toBe(null); - const commitElement = vm.$el.querySelector('.commit-sha'); + done(); + }); + }); - expect(commitElement).not.toBe(null); - expect(commitElement).toHaveText(truncatedCommitId); + it('should display outdated change on commit header', done => { + vm.discussion.for_commit = false; + vm.discussion.active = false; + + vm.$nextTick(() => { + expect(vm.$el).toContainText( + `started a discussion on an outdated change in commit ${truncatedCommitId}`, + ); + + expect(commitElement).not.toBe(null); + + done(); + }); + }); + }); + + describe('for diff discussions without a commit id', () => { + it('should show started a discussion on the diff text', done => { + Object.assign(vm.discussion, { + for_commit: false, + commit_id: null, + }); + + vm.$nextTick(() => { + expect(vm.$el).toContainText('started a discussion on the diff'); + + done(); + }); + }); + + it('should show discussion on older version text', done => { + Object.assign(vm.discussion, { + for_commit: false, + commit_id: null, + active: false, + }); + + vm.$nextTick(() => { + expect(vm.$el).toContainText('started a discussion on an old version of the diff'); + + done(); + }); + }); }); }); }); diff --git a/spec/javascripts/releases/components/app_spec.js b/spec/javascripts/releases/components/app_spec.js new file mode 100644 index 00000000000..f30c7685e34 --- /dev/null +++ b/spec/javascripts/releases/components/app_spec.js @@ -0,0 +1,79 @@ +import Vue from 'vue'; +import app from '~/releases/components/app.vue'; +import createStore from '~/releases/store'; +import api from '~/api'; +import { mountComponentWithStore } from 'spec/helpers/vue_mount_component_helper'; +import { resetStore } from '../store/helpers'; +import { releases } from '../mock_data'; + +describe('Releases App ', () => { + const Component = Vue.extend(app); + let store; + let vm; + + const props = { + projectId: 'gitlab-ce', + documentationLink: 'help/releases', + illustrationPath: 'illustration/path', + }; + + beforeEach(() => { + store = createStore(); + }); + + afterEach(() => { + resetStore(store); + vm.$destroy(); + }); + + describe('while loading', () => { + beforeEach(() => { + spyOn(api, 'releases').and.returnValue(Promise.resolve({ data: [] })); + vm = mountComponentWithStore(Component, { props, store }); + }); + + it('renders loading icon', done => { + expect(vm.$el.querySelector('.js-loading')).not.toBeNull(); + expect(vm.$el.querySelector('.js-empty-state')).toBeNull(); + expect(vm.$el.querySelector('.js-success-state')).toBeNull(); + + setTimeout(() => { + done(); + }, 0); + }); + }); + + describe('with successful request', () => { + beforeEach(() => { + spyOn(api, 'releases').and.returnValue(Promise.resolve({ data: releases })); + vm = mountComponentWithStore(Component, { props, store }); + }); + + it('renders success state', done => { + setTimeout(() => { + expect(vm.$el.querySelector('.js-loading')).toBeNull(); + expect(vm.$el.querySelector('.js-empty-state')).toBeNull(); + expect(vm.$el.querySelector('.js-success-state')).not.toBeNull(); + + done(); + }, 0); + }); + }); + + describe('with empty request', () => { + beforeEach(() => { + spyOn(api, 'releases').and.returnValue(Promise.resolve({ data: [] })); + vm = mountComponentWithStore(Component, { props, store }); + }); + + it('renders empty state', done => { + setTimeout(() => { + expect(vm.$el.querySelector('.js-loading')).toBeNull(); + expect(vm.$el.querySelector('.js-empty-state')).not.toBeNull(); + expect(vm.$el.querySelector('.js-success-state')).toBeNull(); + + done(); + }, 0); + }); + }); +}); diff --git a/spec/javascripts/releases/components/release_block_spec.js b/spec/javascripts/releases/components/release_block_spec.js new file mode 100644 index 00000000000..29420216bc4 --- /dev/null +++ b/spec/javascripts/releases/components/release_block_spec.js @@ -0,0 +1,136 @@ +import Vue from 'vue'; +import component from '~/releases/components/release_block.vue'; +import timeagoMixin from '~/vue_shared/mixins/timeago'; + +import mountComponent from '../../helpers/vue_mount_component_helper'; + +describe('Release block', () => { + const Component = Vue.extend(component); + + const release = { + name: 'Bionic Beaver', + tag_name: '18.04', + description: '## changelog\n\n* line 1\n* line2', + description_html: '<div><h2>changelog</h2><ul><li>line1</li<li>line 2</li></ul></div>', + author_name: 'Release bot', + author_email: 'release-bot@example.com', + created_at: '2012-05-28T05:00:00-07:00', + author: { + avatar_url: 'uploads/-/system/user/avatar/johndoe/avatar.png', + id: 482476, + name: 'John Doe', + path: '/johndoe', + state: 'active', + status_tooltip_html: null, + username: 'johndoe', + web_url: 'https://gitlab.com/johndoe', + }, + commit: { + id: '2695effb5807a22ff3d138d593fd856244e155e7', + short_id: '2695effb', + title: 'Initial commit', + created_at: '2017-07-26T11:08:53.000+02:00', + parent_ids: ['2a4b78934375d7f53875269ffd4f45fd83a84ebe'], + message: 'Initial commit', + author_name: 'John Smith', + author_email: 'john@example.com', + authored_date: '2012-05-28T04:42:42-07:00', + committer_name: 'Jack Smith', + committer_email: 'jack@example.com', + committed_date: '2012-05-28T04:42:42-07:00', + }, + assets: { + count: 6, + sources: [ + { + format: 'zip', + url: 'https://gitlab.com/gitlab-org/gitlab-ce/-/archive/v11.3.12/gitlab-ce-v11.3.12.zip', + }, + { + format: 'tar.gz', + url: + 'https://gitlab.com/gitlab-org/gitlab-ce/-/archive/v11.3.12/gitlab-ce-v11.3.12.tar.gz', + }, + { + format: 'tar.bz2', + url: + 'https://gitlab.com/gitlab-org/gitlab-ce/-/archive/v11.3.12/gitlab-ce-v11.3.12.tar.bz2', + }, + { + format: 'tar', + url: 'https://gitlab.com/gitlab-org/gitlab-ce/-/archive/v11.3.12/gitlab-ce-v11.3.12.tar', + }, + ], + links: [ + { + name: 'release-18.04.dmg', + url: 'https://my-external-hosting.example.com/scrambled-url/', + external: true, + }, + { + name: 'binary-linux-amd64', + url: + 'https://gitlab.com/gitlab-org/gitlab-ce/-/jobs/artifacts/v11.6.0-rc4/download?job=rspec-mysql+41%2F50', + external: false, + }, + ], + }, + }; + let vm; + + beforeEach(() => { + vm = mountComponent(Component, { release }); + }); + + afterEach(() => { + vm.$destroy(); + }); + + it('renders release name', () => { + expect(vm.$el.textContent).toContain(release.name); + }); + + it('renders commit sha', () => { + expect(vm.$el.textContent).toContain(release.commit.short_id); + }); + + it('renders tag name', () => { + expect(vm.$el.textContent).toContain(release.tag_name); + }); + + it('renders release date', () => { + expect(vm.$el.textContent).toContain(timeagoMixin.methods.timeFormated(release.created_at)); + }); + + it('renders number of assets provided', () => { + expect(vm.$el.querySelector('.js-assets-count').textContent).toContain(release.assets.count); + }); + + it('renders dropdown with the sources', () => { + expect(vm.$el.querySelectorAll('.js-sources-dropdown li').length).toEqual( + release.assets.sources.length, + ); + + expect(vm.$el.querySelector('.js-sources-dropdown li a').getAttribute('href')).toEqual( + release.assets.sources[0].url, + ); + + expect(vm.$el.querySelector('.js-sources-dropdown li a').textContent).toContain( + release.assets.sources[0].format, + ); + }); + + it('renders list with the links provided', () => { + expect(vm.$el.querySelectorAll('.js-assets-list li').length).toEqual( + release.assets.links.length, + ); + + expect(vm.$el.querySelector('.js-assets-list li a').getAttribute('href')).toEqual( + release.assets.links[0].url, + ); + + expect(vm.$el.querySelector('.js-assets-list li a').textContent).toContain( + release.assets.links[0].name, + ); + }); +}); diff --git a/spec/javascripts/releases/mock_data.js b/spec/javascripts/releases/mock_data.js new file mode 100644 index 00000000000..2855eca1711 --- /dev/null +++ b/spec/javascripts/releases/mock_data.js @@ -0,0 +1,128 @@ +export const release = { + name: 'Bionic Beaver', + tag_name: '18.04', + description: '## changelog\n\n* line 1\n* line2', + description_html: '<div><h2>changelog</h2><ul><li>line1</li<li>line 2</li></ul></div>', + author_name: 'Release bot', + author_email: 'release-bot@example.com', + created_at: '2012-05-28T05:00:00-07:00', + commit: { + id: '2695effb5807a22ff3d138d593fd856244e155e7', + short_id: '2695effb', + title: 'Initial commit', + created_at: '2017-07-26T11:08:53.000+02:00', + parent_ids: ['2a4b78934375d7f53875269ffd4f45fd83a84ebe'], + message: 'Initial commit', + author: { + avatar_url: 'uploads/-/system/user/avatar/johndoe/avatar.png', + id: 482476, + name: 'John Doe', + path: '/johndoe', + state: 'active', + status_tooltip_html: null, + username: 'johndoe', + web_url: 'https://gitlab.com/johndoe', + }, + authored_date: '2012-05-28T04:42:42-07:00', + committer_name: 'Jack Smith', + committer_email: 'jack@example.com', + committed_date: '2012-05-28T04:42:42-07:00', + }, + assets: { + count: 6, + sources: [ + { + format: 'zip', + url: 'https://gitlab.com/gitlab-org/gitlab-ce/-/archive/v11.3.12/gitlab-ce-v11.3.12.zip', + }, + { + format: 'tar.gz', + url: 'https://gitlab.com/gitlab-org/gitlab-ce/-/archive/v11.3.12/gitlab-ce-v11.3.12.tar.gz', + }, + { + format: 'tar.bz2', + url: + 'https://gitlab.com/gitlab-org/gitlab-ce/-/archive/v11.3.12/gitlab-ce-v11.3.12.tar.bz2', + }, + { + format: 'tar', + url: 'https://gitlab.com/gitlab-org/gitlab-ce/-/archive/v11.3.12/gitlab-ce-v11.3.12.tar', + }, + ], + links: [ + { + name: 'release-18.04.dmg', + url: 'https://my-external-hosting.example.com/scrambled-url/', + external: true, + }, + { + name: 'binary-linux-amd64', + url: + 'https://gitlab.com/gitlab-org/gitlab-ce/-/jobs/artifacts/v11.6.0-rc4/download?job=rspec-mysql+41%2F50', + external: false, + }, + ], + }, +}; + +export const releases = [ + release, + { + name: 'JoJos Bizarre Adventure', + tag_name: '19.00', + description: '## changelog\n\n* line 1\n* line2', + description_html: '<div><h2>changelog</h2><ul><li>line1</li<li>line 2</li></ul></div>', + author_name: 'Release bot', + author_email: 'release-bot@example.com', + created_at: '2012-05-28T05:00:00-07:00', + commit: { + id: '2695effb5807a22ff3d138d593fd856244e155e7', + short_id: '2695effb', + title: 'Initial commit', + created_at: '2017-07-26T11:08:53.000+02:00', + parent_ids: ['2a4b78934375d7f53875269ffd4f45fd83a84ebe'], + message: 'Initial commit', + author: { + avatar_url: 'uploads/-/system/user/avatar/johndoe/avatar.png', + id: 482476, + name: 'John Doe', + path: '/johndoe', + state: 'active', + status_tooltip_html: null, + username: 'johndoe', + web_url: 'https://gitlab.com/johndoe', + }, + authored_date: '2012-05-28T04:42:42-07:00', + committer_name: 'Jack Smith', + committer_email: 'jack@example.com', + committed_date: '2012-05-28T04:42:42-07:00', + }, + assets: { + count: 4, + sources: [ + { + format: 'tar.gz', + url: + 'https://gitlab.com/gitlab-org/gitlab-ce/-/archive/v11.3.12/gitlab-ce-v11.3.12.tar.gz', + }, + { + format: 'tar.bz2', + url: + 'https://gitlab.com/gitlab-org/gitlab-ce/-/archive/v11.3.12/gitlab-ce-v11.3.12.tar.bz2', + }, + { + format: 'tar', + url: 'https://gitlab.com/gitlab-org/gitlab-ce/-/archive/v11.3.12/gitlab-ce-v11.3.12.tar', + }, + ], + links: [ + { + name: 'binary-linux-amd64', + url: + 'https://gitlab.com/gitlab-org/gitlab-ce/-/jobs/artifacts/v11.6.0-rc4/download?job=rspec-mysql+41%2F50', + external: false, + }, + ], + }, + }, +]; diff --git a/spec/javascripts/releases/store/actions_spec.js b/spec/javascripts/releases/store/actions_spec.js new file mode 100644 index 00000000000..6eb8e681be9 --- /dev/null +++ b/spec/javascripts/releases/store/actions_spec.js @@ -0,0 +1,98 @@ +import { + requestReleases, + fetchReleases, + receiveReleasesSuccess, + receiveReleasesError, +} from '~/releases/store/actions'; +import state from '~/releases/store/state'; +import * as types from '~/releases/store/mutation_types'; +import api from '~/api'; +import testAction from 'spec/helpers/vuex_action_helper'; +import { releases } from '../mock_data'; + +describe('Releases State actions', () => { + let mockedState; + + beforeEach(() => { + mockedState = state(); + }); + + describe('requestReleases', () => { + it('should commit REQUEST_RELEASES mutation', done => { + testAction(requestReleases, null, mockedState, [{ type: types.REQUEST_RELEASES }], [], done); + }); + }); + + describe('fetchReleases', () => { + describe('success', () => { + it('dispatches requestReleases and receiveReleasesSuccess ', done => { + spyOn(api, 'releases').and.returnValue(Promise.resolve({ data: releases })); + + testAction( + fetchReleases, + releases, + mockedState, + [], + [ + { + type: 'requestReleases', + }, + { + payload: releases, + type: 'receiveReleasesSuccess', + }, + ], + done, + ); + }); + }); + + describe('error', () => { + it('dispatches requestReleases and receiveReleasesError ', done => { + spyOn(api, 'releases').and.returnValue(Promise.reject()); + + testAction( + fetchReleases, + null, + mockedState, + [], + [ + { + type: 'requestReleases', + }, + { + type: 'receiveReleasesError', + }, + ], + done, + ); + }); + }); + }); + + describe('receiveReleasesSuccess', () => { + it('should commit RECEIVE_RELEASES_SUCCESS mutation', done => { + testAction( + receiveReleasesSuccess, + releases, + mockedState, + [{ type: types.RECEIVE_RELEASES_SUCCESS, payload: releases }], + [], + done, + ); + }); + }); + + describe('receiveReleasesError', () => { + it('should commit RECEIVE_RELEASES_ERROR mutation', done => { + testAction( + receiveReleasesError, + null, + mockedState, + [{ type: types.RECEIVE_RELEASES_ERROR }], + [], + done, + ); + }); + }); +}); diff --git a/spec/javascripts/releases/store/helpers.js b/spec/javascripts/releases/store/helpers.js new file mode 100644 index 00000000000..e962b254377 --- /dev/null +++ b/spec/javascripts/releases/store/helpers.js @@ -0,0 +1,6 @@ +import state from '~/releases/store/state'; + +// eslint-disable-next-line import/prefer-default-export +export const resetStore = store => { + store.replaceState(state()); +}; diff --git a/spec/javascripts/releases/store/mutations_spec.js b/spec/javascripts/releases/store/mutations_spec.js new file mode 100644 index 00000000000..72b98529fe9 --- /dev/null +++ b/spec/javascripts/releases/store/mutations_spec.js @@ -0,0 +1,47 @@ +import state from '~/releases/store/state'; +import mutations from '~/releases/store/mutations'; +import * as types from '~/releases/store/mutation_types'; +import { releases } from '../mock_data'; + +describe('Releases Store Mutations', () => { + let stateCopy; + + beforeEach(() => { + stateCopy = state(); + }); + + describe('REQUEST_RELEASES', () => { + it('sets isLoading to true', () => { + mutations[types.REQUEST_RELEASES](stateCopy); + + expect(stateCopy.isLoading).toEqual(true); + }); + }); + + describe('RECEIVE_RELEASES_SUCCESS', () => { + beforeEach(() => { + mutations[types.RECEIVE_RELEASES_SUCCESS](stateCopy, releases); + }); + + it('sets is loading to false', () => { + expect(stateCopy.isLoading).toEqual(false); + }); + + it('sets hasError to false', () => { + expect(stateCopy.hasError).toEqual(false); + }); + + it('sets data', () => { + expect(stateCopy.releases).toEqual(releases); + }); + }); + + describe('RECEIVE_RELEASES_ERROR', () => { + it('resets data', () => { + mutations[types.RECEIVE_RELEASES_ERROR](stateCopy); + + expect(stateCopy.isLoading).toEqual(false); + expect(stateCopy.releases).toEqual([]); + }); + }); +}); diff --git a/spec/javascripts/sidebar/mock_data.js b/spec/javascripts/sidebar/mock_data.js index fcd7bea3f6d..7f20b0da991 100644 --- a/spec/javascripts/sidebar/mock_data.js +++ b/spec/javascripts/sidebar/mock_data.js @@ -66,7 +66,7 @@ const RESPONSE_MAP = { }, labels: [], }, - '/gitlab-org/gitlab-shell/issues/5.json?serializer=sidebar': { + '/gitlab-org/gitlab-shell/issues/5.json?serializer=sidebar_extras': { assignees: [ { name: 'User 0', @@ -181,7 +181,7 @@ const RESPONSE_MAP = { const mockData = { responseMap: RESPONSE_MAP, mediator: { - endpoint: '/gitlab-org/gitlab-shell/issues/5.json?serializer=sidebar', + endpoint: '/gitlab-org/gitlab-shell/issues/5.json?serializer=sidebar_extras', toggleSubscriptionEndpoint: '/gitlab-org/gitlab-shell/issues/5/toggle_subscription', moveIssueEndpoint: '/gitlab-org/gitlab-shell/issues/5/move', projectsAutocompleteEndpoint: '/autocomplete/projects?project_id=15', diff --git a/spec/javascripts/sidebar/sidebar_mediator_spec.js b/spec/javascripts/sidebar/sidebar_mediator_spec.js index 2d853970fc4..6c69c08e733 100644 --- a/spec/javascripts/sidebar/sidebar_mediator_spec.js +++ b/spec/javascripts/sidebar/sidebar_mediator_spec.js @@ -37,7 +37,7 @@ describe('Sidebar mediator', function() { it('fetches the data', done => { const mockData = - Mock.responseMap.GET['/gitlab-org/gitlab-shell/issues/5.json?serializer=sidebar']; + Mock.responseMap.GET['/gitlab-org/gitlab-shell/issues/5.json?serializer=sidebar_extras']; spyOn(this.mediator, 'processFetchedData').and.callThrough(); this.mediator @@ -51,7 +51,7 @@ describe('Sidebar mediator', function() { it('processes fetched data', () => { const mockData = - Mock.responseMap.GET['/gitlab-org/gitlab-shell/issues/5.json?serializer=sidebar']; + Mock.responseMap.GET['/gitlab-org/gitlab-shell/issues/5.json?serializer=sidebar_extras']; this.mediator.processFetchedData(mockData); expect(this.mediator.store.assignees).toEqual(mockData.assignees); diff --git a/spec/javascripts/vue_mr_widget/components/mr_widget_rebase_spec.js b/spec/javascripts/vue_mr_widget/components/mr_widget_rebase_spec.js index 300133dc602..212519743aa 100644 --- a/spec/javascripts/vue_mr_widget/components/mr_widget_rebase_spec.js +++ b/spec/javascripts/vue_mr_widget/components/mr_widget_rebase_spec.js @@ -114,7 +114,7 @@ describe('Merge request widget rebase component', () => { // Wait for the eventHub to be called .then(vm.$nextTick()) .then(() => { - expect(eventHub.$emit).toHaveBeenCalledWith('MRWidgetUpdateRequested'); + expect(eventHub.$emit).toHaveBeenCalledWith('MRWidgetRebaseSuccess'); }) .then(done) .catch(done.fail); 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 f72bf627c10..99b80df766a 100644 --- a/spec/javascripts/vue_mr_widget/mr_widget_options_spec.js +++ b/spec/javascripts/vue_mr_widget/mr_widget_options_spec.js @@ -18,6 +18,8 @@ describe('mrWidgetOptions', () => { let vm; let MrWidgetOptions; + const COLLABORATION_MESSAGE = 'Allows commits from members who can merge to the target branch'; + beforeEach(() => { // Prevent component mounting delete mrWidgetOptions.el; @@ -132,6 +134,53 @@ describe('mrWidgetOptions', () => { expect(vm.shouldRenderSourceBranchRemovalStatus).toEqual(false); }); }); + + describe('shouldRenderCollaborationStatus', () => { + describe('when collaboration is allowed', () => { + beforeEach(() => { + vm.mr.allowCollaboration = true; + }); + + describe('when merge request is opened', () => { + beforeEach(done => { + vm.mr.isOpen = true; + vm.$nextTick(done); + }); + + it('should render collaboration status', () => { + expect(vm.$el.textContent).toContain(COLLABORATION_MESSAGE); + }); + }); + + describe('when merge request is not opened', () => { + beforeEach(done => { + vm.mr.isOpen = false; + vm.$nextTick(done); + }); + + it('should not render collaboration status', () => { + expect(vm.$el.textContent).not.toContain(COLLABORATION_MESSAGE); + }); + }); + }); + + describe('when collaboration is not allowed', () => { + beforeEach(() => { + vm.mr.allowCollaboration = false; + }); + + describe('when merge request is opened', () => { + beforeEach(done => { + vm.mr.isOpen = true; + vm.$nextTick(done); + }); + + it('should not render collaboration status', () => { + expect(vm.$el.textContent).not.toContain(COLLABORATION_MESSAGE); + }); + }); + }); + }); }); describe('methods', () => { diff --git a/spec/javascripts/vue_mr_widget/stores/get_state_key_spec.js b/spec/javascripts/vue_mr_widget/stores/get_state_key_spec.js index 9d34bdd1084..61ef26cd080 100644 --- a/spec/javascripts/vue_mr_widget/stores/get_state_key_spec.js +++ b/spec/javascripts/vue_mr_widget/stores/get_state_key_spec.js @@ -35,7 +35,7 @@ describe('getStateKey', () => { expect(bound()).toEqual('mergeWhenPipelineSucceeds'); - context.hasSHAChanged = true; + context.isSHAMismatch = true; expect(bound()).toEqual('shaMismatch'); diff --git a/spec/javascripts/vue_mr_widget/stores/mr_widget_store_spec.js b/spec/javascripts/vue_mr_widget/stores/mr_widget_store_spec.js index f5079147f60..c226704694c 100644 --- a/spec/javascripts/vue_mr_widget/stores/mr_widget_store_spec.js +++ b/spec/javascripts/vue_mr_widget/stores/mr_widget_store_spec.js @@ -3,23 +3,30 @@ import { stateKey } from '~/vue_merge_request_widget/stores/state_maps'; import mockData from '../mock_data'; describe('MergeRequestStore', () => { - describe('setData', () => { - let store; + let store; - beforeEach(() => { - store = new MergeRequestStore(mockData); - }); + beforeEach(() => { + store = new MergeRequestStore(mockData); + }); - it('should set hasSHAChanged when the diff SHA changes', () => { + describe('setData', () => { + it('should set isSHAMismatch when the diff SHA changes', () => { store.setData({ ...mockData, diff_head_sha: 'a-different-string' }); - expect(store.hasSHAChanged).toBe(true); + expect(store.isSHAMismatch).toBe(true); }); - it('should not set hasSHAChanged when other data changes', () => { + it('should not set isSHAMismatch when other data changes', () => { store.setData({ ...mockData, work_in_progress: !mockData.work_in_progress }); - expect(store.hasSHAChanged).toBe(false); + expect(store.isSHAMismatch).toBe(false); + }); + + it('should update cached sha after rebasing', () => { + store.setData({ ...mockData, diff_head_sha: 'abc123' }, true); + + expect(store.isSHAMismatch).toBe(false); + expect(store.sha).toBe('abc123'); }); describe('isPipelinePassing', () => { diff --git a/spec/javascripts/vue_shared/components/callout_spec.js b/spec/javascripts/vue_shared/components/callout_spec.js index e62bd86f4ca..91208dfb31a 100644 --- a/spec/javascripts/vue_shared/components/callout_spec.js +++ b/spec/javascripts/vue_shared/components/callout_spec.js @@ -1,45 +1,66 @@ -import Vue from 'vue'; -import callout from '~/vue_shared/components/callout.vue'; -import createComponent from 'spec/helpers/vue_mount_component_helper'; +import { createLocalVue, shallowMount } from '@vue/test-utils'; +import Callout from '~/vue_shared/components/callout.vue'; + +const TEST_MESSAGE = 'This is a callout message!'; +const TEST_SLOT = '<button>This is a callout slot!</button>'; + +const localVue = createLocalVue(); describe('Callout Component', () => { - let CalloutComponent; - let vm; - const exampleMessage = 'This is a callout message!'; + let wrapper; - beforeEach(() => { - CalloutComponent = Vue.extend(callout); - }); + const factory = options => { + wrapper = shallowMount(localVue.extend(Callout), { + localVue, + ...options, + }); + }; afterEach(() => { - vm.$destroy(); + wrapper.destroy(); }); it('should render the appropriate variant of callout', () => { - vm = createComponent(CalloutComponent, { - category: 'info', - message: exampleMessage, + factory({ + propsData: { + category: 'info', + message: TEST_MESSAGE, + }, }); - expect(vm.$el.getAttribute('class')).toEqual('bs-callout bs-callout-info'); + expect(wrapper.classes()).toEqual(['bs-callout', 'bs-callout-info']); - expect(vm.$el.tagName).toEqual('DIV'); + expect(wrapper.element.tagName).toEqual('DIV'); }); it('should render accessibility attributes', () => { - vm = createComponent(CalloutComponent, { - message: exampleMessage, + factory({ + propsData: { + message: TEST_MESSAGE, + }, }); - expect(vm.$el.getAttribute('role')).toEqual('alert'); - expect(vm.$el.getAttribute('aria-live')).toEqual('assertive'); + expect(wrapper.attributes('role')).toEqual('alert'); + expect(wrapper.attributes('aria-live')).toEqual('assertive'); }); it('should render the provided message', () => { - vm = createComponent(CalloutComponent, { - message: exampleMessage, + factory({ + propsData: { + message: TEST_MESSAGE, + }, + }); + + expect(wrapper.element.innerHTML.trim()).toEqual(TEST_MESSAGE); + }); + + it('should render the provided slot', () => { + factory({ + slots: { + default: TEST_SLOT, + }, }); - expect(vm.$el.innerHTML.trim()).toEqual(exampleMessage); + expect(wrapper.element.innerHTML.trim()).toEqual(TEST_SLOT); }); }); diff --git a/spec/javascripts/vue_shared/components/markdown/header_spec.js b/spec/javascripts/vue_shared/components/markdown/header_spec.js index 59613faa49f..e733a95288e 100644 --- a/spec/javascripts/vue_shared/components/markdown/header_spec.js +++ b/spec/javascripts/vue_shared/components/markdown/header_spec.js @@ -28,6 +28,7 @@ describe('Markdown field header component', () => { 'Add a numbered list', 'Add a task list', 'Add a table', + 'Insert suggestion', 'Go full screen', ]; const elements = vm.$el.querySelectorAll('.toolbar-btn'); @@ -93,4 +94,18 @@ describe('Markdown field header component', () => { '| header | header |\n| ------ | ------ |\n| cell | cell |\n| cell | cell |', ); }); + + it('renders suggestion template', () => { + vm.lineContent = 'Some content'; + + expect(vm.mdSuggestion).toEqual('```suggestion\n{text}\n```'); + }); + + it('does not render suggestion button if `canSuggest` is set to false', () => { + vm.canSuggest = false; + + Vue.nextTick(() => { + expect(vm.$el.querySelector('.qa-suggestion-btn')).toBe(null); + }); + }); }); diff --git a/spec/javascripts/vue_shared/components/markdown/suggestion_diff_header_spec.js b/spec/javascripts/vue_shared/components/markdown/suggestion_diff_header_spec.js new file mode 100644 index 00000000000..8187b3204b1 --- /dev/null +++ b/spec/javascripts/vue_shared/components/markdown/suggestion_diff_header_spec.js @@ -0,0 +1,69 @@ +import Vue from 'vue'; +import SuggestionDiffHeaderComponent from '~/vue_shared/components/markdown/suggestion_diff_header.vue'; + +const MOCK_DATA = { + canApply: true, + isApplied: false, + helpPagePath: 'path_to_docs', +}; + +describe('Suggestion Diff component', () => { + let vm; + + function createComponent(propsData) { + const Component = Vue.extend(SuggestionDiffHeaderComponent); + + return new Component({ + propsData, + }).$mount(); + } + + beforeEach(done => { + vm = createComponent(MOCK_DATA); + Vue.nextTick(done); + }); + + describe('init', () => { + it('renders a suggestion header', () => { + const header = vm.$el.querySelector('.qa-suggestion-diff-header'); + + expect(header).not.toBeNull(); + expect(header.innerHTML.includes('Suggested change')).toBe(true); + }); + + it('renders an apply button', () => { + const applyBtn = vm.$el.querySelector('.qa-apply-btn'); + + expect(applyBtn).not.toBeNull(); + expect(applyBtn.innerHTML.includes('Apply suggestion')).toBe(true); + }); + + it('does not render an apply button if `canApply` is set to false', () => { + const props = Object.assign(MOCK_DATA, { canApply: false }); + + vm = createComponent(props); + + expect(vm.$el.querySelector('.qa-apply-btn')).toBeNull(); + }); + }); + + describe('applySuggestion', () => { + it('emits when the apply button is clicked', () => { + const props = Object.assign(MOCK_DATA, { canApply: true }); + + vm = createComponent(props); + spyOn(vm, '$emit'); + vm.applySuggestion(); + + expect(vm.$emit).toHaveBeenCalled(); + }); + + it('does not emit when the canApply is set to false', () => { + spyOn(vm, '$emit'); + vm.canApply = false; + vm.applySuggestion(); + + expect(vm.$emit).not.toHaveBeenCalled(); + }); + }); +}); diff --git a/spec/javascripts/vue_shared/components/markdown/suggestion_diff_spec.js b/spec/javascripts/vue_shared/components/markdown/suggestion_diff_spec.js new file mode 100644 index 00000000000..d4ed8f2f7a4 --- /dev/null +++ b/spec/javascripts/vue_shared/components/markdown/suggestion_diff_spec.js @@ -0,0 +1,79 @@ +import Vue from 'vue'; +import SuggestionDiffComponent from '~/vue_shared/components/markdown/suggestion_diff.vue'; + +const MOCK_DATA = { + canApply: true, + newLines: [ + { content: 'Line 1\n', lineNumber: 1 }, + { content: 'Line 2\n', lineNumber: 2 }, + { content: 'Line 3\n', lineNumber: 3 }, + ], + fromLine: 1, + fromContent: 'Old content', + suggestion: { + id: 1, + }, + helpPagePath: 'path_to_docs', +}; + +describe('Suggestion Diff component', () => { + let vm; + + beforeEach(done => { + const Component = Vue.extend(SuggestionDiffComponent); + + vm = new Component({ + propsData: MOCK_DATA, + }).$mount(); + + Vue.nextTick(done); + }); + + describe('init', () => { + it('renders a suggestion header', () => { + expect(vm.$el.querySelector('.qa-suggestion-diff-header')).not.toBeNull(); + }); + + it('renders a diff table', () => { + expect(vm.$el.querySelector('table.md-suggestion-diff')).not.toBeNull(); + }); + + it('renders the oldLineNumber', () => { + const fromLine = vm.$el.querySelector('.qa-old-diff-line-number').innerHTML; + + expect(parseInt(fromLine, 10)).toBe(vm.fromLine); + }); + + it('renders the oldLineContent', () => { + const fromContent = vm.$el.querySelector('.line_content.old').innerHTML; + + expect(fromContent.includes(vm.fromContent)).toBe(true); + }); + + it('renders the contents of newLines', () => { + const newLines = vm.$el.querySelectorAll('.line_holder.new'); + + newLines.forEach((line, i) => { + expect(newLines[i].innerHTML.includes(vm.newLines[i].content)).toBe(true); + }); + }); + + it('renders a line number for each line', () => { + const newLineNumbers = vm.$el.querySelectorAll('.qa-new-diff-line-number'); + + newLineNumbers.forEach((line, i) => { + expect(newLineNumbers[i].innerHTML.includes(vm.newLines[i].lineNumber)).toBe(true); + }); + }); + }); + + describe('applySuggestion', () => { + it('emits apply event when applySuggestion is called', () => { + const callback = () => {}; + spyOn(vm, '$emit'); + vm.applySuggestion(callback); + + expect(vm.$emit).toHaveBeenCalledWith('apply', { suggestionId: vm.suggestion.id, callback }); + }); + }); +}); diff --git a/spec/javascripts/vue_shared/components/markdown/suggestions_spec.js b/spec/javascripts/vue_shared/components/markdown/suggestions_spec.js new file mode 100644 index 00000000000..ab1b747c360 --- /dev/null +++ b/spec/javascripts/vue_shared/components/markdown/suggestions_spec.js @@ -0,0 +1,125 @@ +import Vue from 'vue'; +import SuggestionsComponent from '~/vue_shared/components/markdown/suggestions.vue'; + +const MOCK_DATA = { + fromLine: 1, + fromContent: 'Old content', + suggestions: [], + noteHtml: ` + <div class="suggestion"> + <div class="line">Suggestion 1</div> + </div> + + <div class="suggestion"> + <div class="line">Suggestion 2</div> + </div> + `, + isApplied: false, + helpPagePath: 'path_to_docs', +}; + +const generateLine = content => { + const line = document.createElement('div'); + line.className = 'line'; + line.innerHTML = content; + + return line; +}; + +const generateMockLines = () => { + const line1 = generateLine('Line 1'); + const line2 = generateLine('Line 2'); + const line3 = generateLine('Line 3'); + const container = document.createElement('div'); + + container.appendChild(line1); + container.appendChild(line2); + container.appendChild(line3); + + return container; +}; + +describe('Suggestion component', () => { + let vm; + let extractedLines; + let diffTable; + + beforeEach(done => { + const Component = Vue.extend(SuggestionsComponent); + + vm = new Component({ + propsData: MOCK_DATA, + }).$mount(); + + extractedLines = vm.extractNewLines(generateMockLines()); + diffTable = vm.generateDiff(extractedLines).$mount().$el; + + spyOn(vm, 'renderSuggestions'); + vm.renderSuggestions(); + Vue.nextTick(done); + }); + + describe('mounted', () => { + it('renders a flash container', () => { + expect(vm.$el.querySelector('.flash-container')).not.toBeNull(); + }); + + it('renders a container for suggestions', () => { + expect(vm.$refs.container).not.toBeNull(); + }); + + it('renders suggestions', () => { + expect(vm.renderSuggestions).toHaveBeenCalled(); + expect(vm.$el.innerHTML.includes('Suggestion 1')).toBe(true); + expect(vm.$el.innerHTML.includes('Suggestion 2')).toBe(true); + }); + }); + + describe('extractNewLines', () => { + it('extracts suggested lines', () => { + const expectedReturn = [ + { content: 'Line 1\n', lineNumber: 1 }, + { content: 'Line 2\n', lineNumber: 2 }, + { content: 'Line 3\n', lineNumber: 3 }, + ]; + + expect(vm.extractNewLines(generateMockLines())).toEqual(expectedReturn); + }); + + it('increments line number for each extracted line', () => { + expect(extractedLines[0].lineNumber).toEqual(1); + expect(extractedLines[1].lineNumber).toEqual(2); + expect(extractedLines[2].lineNumber).toEqual(3); + }); + + it('returns empty array if no lines are found', () => { + const el = document.createElement('div'); + + expect(vm.extractNewLines(el)).toEqual([]); + }); + }); + + describe('generateDiff', () => { + it('generates a diff table', () => { + expect(diffTable.querySelector('.md-suggestion-diff')).not.toBeNull(); + }); + + it('generates a diff table that contains contents of `oldLineContent`', () => { + expect(diffTable.innerHTML.includes(vm.fromContent)).toBe(true); + }); + + it('generates a diff table that contains contents the suggested lines', () => { + extractedLines.forEach((line, i) => { + expect(diffTable.innerHTML.includes(extractedLines[i].content)).toBe(true); + }); + }); + + it('generates a diff table with the correct line number for each suggested line', () => { + const lines = diffTable.getElementsByClassName('qa-new-diff-line-number'); + + expect([...lines][0].innerHTML).toBe('1'); + expect([...lines][1].innerHTML).toBe('2'); + expect([...lines][2].innerHTML).toBe('3'); + }); + }); +}); diff --git a/spec/javascripts/vue_shared/components/user_popover/user_popover_spec.js b/spec/javascripts/vue_shared/components/user_popover/user_popover_spec.js index 1578b0f81f9..25b6e3b6bc8 100644 --- a/spec/javascripts/vue_shared/components/user_popover/user_popover_spec.js +++ b/spec/javascripts/vue_shared/components/user_popover/user_popover_spec.js @@ -89,7 +89,7 @@ describe('User Popover Component', () => { expect(vm.$el.textContent).toContain('GitLab'); }); - it('should have full job line when we have bio and organization', () => { + it('should display bio and organization in separate lines', () => { const testProps = Object.assign({}, DEFAULT_PROPS); testProps.user.bio = 'Engineer'; testProps.user.organization = 'GitLab'; @@ -99,7 +99,24 @@ describe('User Popover Component', () => { target: document.querySelector('.js-user-link'), }); - expect(vm.$el.textContent).toContain('Engineer at GitLab'); + expect(vm.$el.querySelector('.js-bio').textContent).toContain('Engineer'); + expect(vm.$el.querySelector('.js-organization').textContent).toContain('GitLab'); + }); + + it('should not encode special characters in bio and organization', () => { + const testProps = Object.assign({}, DEFAULT_PROPS); + testProps.user.bio = 'Manager & Team Lead'; + testProps.user.organization = 'Me & my <funky> Company'; + + vm = mountComponent(UserPopover, { + ...DEFAULT_PROPS, + target: document.querySelector('.js-user-link'), + }); + + expect(vm.$el.querySelector('.js-bio').textContent).toContain('Manager & Team Lead'); + expect(vm.$el.querySelector('.js-organization').textContent).toContain( + 'Me & my <funky> Company', + ); }); }); diff --git a/spec/lib/api/helpers_spec.rb b/spec/lib/api/helpers_spec.rb index 58a49124ce6..1c73a936e17 100644 --- a/spec/lib/api/helpers_spec.rb +++ b/spec/lib/api/helpers_spec.rb @@ -148,4 +148,36 @@ describe API::Helpers do it_behaves_like 'user namespace finder' end + + describe '#send_git_blob' do + context 'content disposition' do + let(:repository) { double } + let(:blob) { double(name: 'foobar') } + + let(:send_git_blob) do + subject.send(:send_git_blob, repository, blob) + end + + before do + allow(subject).to receive(:env).and_return({}) + allow(subject).to receive(:content_type) + allow(subject).to receive(:header).and_return({}) + allow(Gitlab::Workhorse).to receive(:send_git_blob) + end + + context 'when blob name is null' do + let(:blob) { double(name: nil) } + + it 'returns only the disposition' do + expect(send_git_blob['Content-Disposition']).to eq 'attachment' + end + end + + context 'when blob name is not null' do + it 'returns disposition with the blob name' do + expect(send_git_blob['Content-Disposition']).to eq 'attachment; filename="foobar"' + end + end + end + end end diff --git a/spec/lib/backup/repository_spec.rb b/spec/lib/backup/repository_spec.rb index fdeea814bb2..5ace5c5b1a2 100644 --- a/spec/lib/backup/repository_spec.rb +++ b/spec/lib/backup/repository_spec.rb @@ -67,6 +67,19 @@ describe Backup::Repository do end end end + + context 'restoring object pools' do + it 'schedules restoring of the pool' do + pool_repository = create(:pool_repository, :failed) + pool_repository.delete_object_pool + + subject.restore + + pool_repository.reload + expect(pool_repository).not_to be_failed + expect(pool_repository.object_pool.exists?).to be(true) + end + end end describe '#prepare_directories', :seed_helper do diff --git a/spec/lib/banzai/filter/external_link_filter_spec.rb b/spec/lib/banzai/filter/external_link_filter_spec.rb index 2a3c0cd78b8..e6dae8d5382 100644 --- a/spec/lib/banzai/filter/external_link_filter_spec.rb +++ b/spec/lib/banzai/filter/external_link_filter_spec.rb @@ -49,16 +49,16 @@ describe Banzai::Filter::ExternalLinkFilter do end context 'for invalid urls' do - it 'skips broken hrefs' do + it 'adds rel and target attributes to broken hrefs' do doc = filter %q(<p><a href="don't crash on broken urls">Google</a></p>) - expected = %q(<p><a href="don't%20crash%20on%20broken%20urls">Google</a></p>) + expected = %q(<p><a href="don't%20crash%20on%20broken%20urls" rel="nofollow noreferrer noopener" target="_blank">Google</a></p>) expect(doc.to_html).to eq(expected) end - it 'skips improperly formatted mailtos' do + it 'adds rel and target to improperly formatted mailtos' do doc = filter %q(<p><a href="mailto://jblogs@example.com">Email</a></p>) - expected = %q(<p><a href="mailto://jblogs@example.com">Email</a></p>) + expected = %q(<p><a href="mailto://jblogs@example.com" rel="nofollow noreferrer noopener" target="_blank">Email</a></p>) expect(doc.to_html).to eq(expected) end diff --git a/spec/lib/banzai/filter/label_reference_filter_spec.rb b/spec/lib/banzai/filter/label_reference_filter_spec.rb index 00257ed7904..9cfdb9e53a2 100644 --- a/spec/lib/banzai/filter/label_reference_filter_spec.rb +++ b/spec/lib/banzai/filter/label_reference_filter_spec.rb @@ -236,6 +236,24 @@ describe Banzai::Filter::LabelReferenceFilter do end end + context 'References with html entities' do + let!(:label) { create(:label, name: '<html>', project: project) } + + it 'links to a valid reference' do + doc = reference_filter('See ~"<html>"') + + expect(doc.css('a').first.attr('href')).to eq urls + .project_issues_url(project, label_name: label.name) + expect(doc.text).to eq 'See <html>' + end + + it 'ignores invalid label names and escapes entities' do + act = %(Label #{Label.reference_prefix}"<non valid>") + + expect(reference_filter(act).to_html).to eq act + end + end + describe 'consecutive references' do let(:bug) { create(:label, name: 'bug', project: project) } let(:feature_proposal) { create(:label, name: 'feature proposal', project: project) } diff --git a/spec/lib/banzai/filter/milestone_reference_filter_spec.rb b/spec/lib/banzai/filter/milestone_reference_filter_spec.rb index 1a87cfa5b45..4c94e4fdae0 100644 --- a/spec/lib/banzai/filter/milestone_reference_filter_spec.rb +++ b/spec/lib/banzai/filter/milestone_reference_filter_spec.rb @@ -59,7 +59,7 @@ describe Banzai::Filter::MilestoneReferenceFilter do it 'links with adjacent text' do doc = reference_filter("Milestone (#{reference}.)") - expect(doc.to_html).to match(%r(\(<a.+>#{milestone.name}</a>\.\))) + expect(doc.to_html).to match(%r(\(<a.+>#{milestone.reference_link_text}</a>\.\))) end it 'ignores invalid milestone IIDs' do @@ -80,12 +80,12 @@ describe Banzai::Filter::MilestoneReferenceFilter do doc = reference_filter("See #{reference}") expect(doc.css('a').first.attr('href')).to eq urls.milestone_url(milestone) - expect(doc.text).to eq 'See gfm' + expect(doc.text).to eq "See #{milestone.reference_link_text}" end it 'links with adjacent text' do doc = reference_filter("Milestone (#{reference}.)") - expect(doc.to_html).to match(%r(\(<a.+>#{milestone.name}</a>\.\))) + expect(doc.to_html).to match(%r(\(<a.+>#{milestone.reference_link_text}</a>\.\))) end it 'ignores invalid milestone names' do @@ -106,12 +106,12 @@ describe Banzai::Filter::MilestoneReferenceFilter do doc = reference_filter("See #{reference}") expect(doc.css('a').first.attr('href')).to eq urls.milestone_url(milestone) - expect(doc.text).to eq 'See gfm references' + expect(doc.text).to eq "See #{milestone.reference_link_text}" end it 'links with adjacent text' do doc = reference_filter("Milestone (#{reference}.)") - expect(doc.to_html).to match(%r(\(<a.+>#{milestone.name}</a>\.\))) + expect(doc.to_html).to match(%r(\(<a.+>#{milestone.reference_link_text}</a>\.\))) end it 'ignores invalid milestone names' do @@ -201,14 +201,14 @@ describe Banzai::Filter::MilestoneReferenceFilter do doc = reference_filter("See (#{reference}.)") expect(doc.css('a').first.text) - .to eq("#{milestone.name} in #{another_project.full_path}") + .to eq("#{milestone.reference_link_text} in #{another_project.full_path}") end it 'has valid text' do doc = reference_filter("See (#{reference}.)") expect(doc.text) - .to eq("See (#{milestone.name} in #{another_project.full_path}.)") + .to eq("See (#{milestone.reference_link_text} in #{another_project.full_path}.)") end it 'escapes the name attribute' do @@ -217,7 +217,7 @@ describe Banzai::Filter::MilestoneReferenceFilter do doc = reference_filter("See #{reference}") expect(doc.css('a').first.text) - .to eq "#{milestone.name} in #{another_project.full_path}" + .to eq "#{milestone.reference_link_text} in #{another_project.full_path}" end end @@ -238,14 +238,14 @@ describe Banzai::Filter::MilestoneReferenceFilter do doc = reference_filter("See (#{reference}.)") expect(doc.css('a').first.text) - .to eq("#{milestone.name} in #{another_project.path}") + .to eq("#{milestone.reference_link_text} in #{another_project.path}") end it 'has valid text' do doc = reference_filter("See (#{reference}.)") expect(doc.text) - .to eq("See (#{milestone.name} in #{another_project.path}.)") + .to eq("See (#{milestone.reference_link_text} in #{another_project.path}.)") end it 'escapes the name attribute' do @@ -254,7 +254,7 @@ describe Banzai::Filter::MilestoneReferenceFilter do doc = reference_filter("See #{reference}") expect(doc.css('a').first.text) - .to eq "#{milestone.name} in #{another_project.path}" + .to eq "#{milestone.reference_link_text} in #{another_project.path}" end end @@ -275,14 +275,14 @@ describe Banzai::Filter::MilestoneReferenceFilter do doc = reference_filter("See (#{reference}.)") expect(doc.css('a').first.text) - .to eq("#{milestone.name} in #{another_project.path}") + .to eq("#{milestone.reference_link_text} in #{another_project.path}") end it 'has valid text' do doc = reference_filter("See (#{reference}.)") expect(doc.text) - .to eq("See (#{milestone.name} in #{another_project.path}.)") + .to eq("See (#{milestone.reference_link_text} in #{another_project.path}.)") end it 'escapes the name attribute' do @@ -291,7 +291,7 @@ describe Banzai::Filter::MilestoneReferenceFilter do doc = reference_filter("See #{reference}") expect(doc.css('a').first.text) - .to eq "#{milestone.name} in #{another_project.path}" + .to eq "#{milestone.reference_link_text} in #{another_project.path}" end end @@ -346,7 +346,7 @@ describe Banzai::Filter::MilestoneReferenceFilter do milestone.update!(group: parent_group) doc = reference_filter("See #{reference}") - expect(doc.css('a').first.text).to eq(milestone.name) + expect(doc.css('a').first.text).to eq(milestone.reference_link_text) end end diff --git a/spec/lib/banzai/filter/suggestion_filter_spec.rb b/spec/lib/banzai/filter/suggestion_filter_spec.rb new file mode 100644 index 00000000000..b13c90b54bd --- /dev/null +++ b/spec/lib/banzai/filter/suggestion_filter_spec.rb @@ -0,0 +1,26 @@ +# frozen_string_literal: true + +require 'spec_helper' + +describe Banzai::Filter::SuggestionFilter do + include FilterSpecHelper + + let(:input) { "<pre class='code highlight js-syntax-highlight suggestion'><code>foo\n</code></pre>" } + let(:default_context) do + { suggestions_filter_enabled: true } + end + + it 'includes `js-render-suggestion` class' do + doc = filter(input, default_context) + result = doc.css('code').first + + expect(result[:class]).to include('js-render-suggestion') + end + + it 'includes no `js-render-suggestion` when filter is disabled' do + doc = filter(input) + result = doc.css('code').first + + expect(result[:class]).to be_nil + end +end diff --git a/spec/lib/banzai/suggestions_parser_spec.rb b/spec/lib/banzai/suggestions_parser_spec.rb new file mode 100644 index 00000000000..79658d710ce --- /dev/null +++ b/spec/lib/banzai/suggestions_parser_spec.rb @@ -0,0 +1,32 @@ +# frozen_string_literal: true + +require 'spec_helper' + +describe Banzai::SuggestionsParser do + describe '.parse' do + it 'returns a list of suggestion contents' do + markdown = <<-MARKDOWN.strip_heredoc + ```suggestion + foo + bar + ``` + + ``` + nothing + ``` + + ```suggestion + xpto + baz + ``` + + ```thing + this is not a suggestion, it's a thing + ``` + MARKDOWN + + expect(described_class.parse(markdown)).to eq([" foo\n bar", + " xpto\n baz"]) + end + end +end diff --git a/spec/lib/constraints/feature_constrainer_spec.rb b/spec/lib/constraints/feature_constrainer_spec.rb new file mode 100644 index 00000000000..42efc164f81 --- /dev/null +++ b/spec/lib/constraints/feature_constrainer_spec.rb @@ -0,0 +1,11 @@ +require 'spec_helper' + +describe Constraints::FeatureConstrainer do + describe '#matches' do + it 'calls Feature.enabled? with the correct arguments' do + expect(Feature).to receive(:enabled?).with(:feature_name, "an object", default_enabled: true) + + described_class.new(:feature_name, "an object", default_enabled: true).matches?(double('request')) + end + end +end diff --git a/spec/lib/gitlab/background_migration/backfill_hashed_project_repositories_spec.rb b/spec/lib/gitlab/background_migration/backfill_hashed_project_repositories_spec.rb index b6c1edbbf8b..e802613490b 100644 --- a/spec/lib/gitlab/background_migration/backfill_hashed_project_repositories_spec.rb +++ b/spec/lib/gitlab/background_migration/backfill_hashed_project_repositories_spec.rb @@ -3,88 +3,5 @@ require 'spec_helper' describe Gitlab::BackgroundMigration::BackfillHashedProjectRepositories, :migration, schema: 20181130102132 do - let(:namespaces) { table(:namespaces) } - let(:project_repositories) { table(:project_repositories) } - let(:projects) { table(:projects) } - let(:shards) { table(:shards) } - let(:group) { namespaces.create!(name: 'foo', path: 'foo') } - let(:shard) { shards.create!(name: 'default') } - - describe described_class::ShardFinder do - describe '#find_shard_id' do - it 'creates a new shard when it does not exist yet' do - expect { subject.find_shard_id('other') }.to change(shards, :count).by(1) - end - - it 'returns the shard when it exists' do - shards.create(id: 5, name: 'other') - - shard_id = subject.find_shard_id('other') - - expect(shard_id).to eq(5) - end - - it 'only queries the database once to retrieve shards' do - subject.find_shard_id('default') - - expect { subject.find_shard_id('default') }.not_to exceed_query_limit(0) - end - end - end - - describe described_class::Project do - describe '.on_hashed_storage' do - it 'finds projects with repository on hashed storage' do - projects.create!(id: 1, name: 'foo', path: 'foo', namespace_id: group.id, storage_version: 1) - projects.create!(id: 2, name: 'bar', path: 'bar', namespace_id: group.id, storage_version: 2) - projects.create!(id: 3, name: 'baz', path: 'baz', namespace_id: group.id, storage_version: 0) - projects.create!(id: 4, name: 'zoo', path: 'zoo', namespace_id: group.id, storage_version: nil) - - expect(described_class.on_hashed_storage.pluck(:id)).to match_array([1, 2]) - end - end - - describe '.without_project_repository' do - it 'finds projects which do not have a projects_repositories entry' do - projects.create!(id: 1, name: 'foo', path: 'foo', namespace_id: group.id) - projects.create!(id: 2, name: 'bar', path: 'bar', namespace_id: group.id) - project_repositories.create!(project_id: 2, disk_path: '@phony/foo/bar', shard_id: shard.id) - - expect(described_class.without_project_repository.pluck(:id)).to contain_exactly(1) - end - end - end - - describe '#perform' do - it 'creates a project_repository row for projects on hashed storage that need one' do - projects.create!(id: 1, name: 'foo', path: 'foo', namespace_id: group.id, storage_version: 1) - projects.create!(id: 2, name: 'bar', path: 'bar', namespace_id: group.id, storage_version: 2) - - expect { described_class.new.perform(1, projects.last.id) }.to change(project_repositories, :count).by(2) - end - - it 'does nothing for projects on hashed storage that have already a project_repository row' do - projects.create!(id: 1, name: 'foo', path: 'foo', namespace_id: group.id, storage_version: 1) - project_repositories.create!(project_id: 1, disk_path: '@phony/foo/bar', shard_id: shard.id) - - expect { described_class.new.perform(1, projects.last.id) }.not_to change(project_repositories, :count) - end - - it 'does nothing for projects on legacy storage' do - projects.create!(name: 'foo', path: 'foo', namespace_id: group.id, storage_version: 0) - - expect { described_class.new.perform(1, projects.last.id) }.not_to change(project_repositories, :count) - end - - it 'inserts rows in a single query' do - projects.create!(name: 'foo', path: 'foo', namespace_id: group.id, storage_version: 1, repository_storage: shard.name) - - control_count = ActiveRecord::QueryRecorder.new { described_class.new.perform(1, projects.last.id) } - - projects.create!(name: 'bar', path: 'bar', namespace_id: group.id, storage_version: 1, repository_storage: shard.name) - projects.create!(name: 'zoo', path: 'zoo', namespace_id: group.id, storage_version: 1, repository_storage: shard.name) - - expect { described_class.new.perform(1, projects.last.id) }.not_to exceed_query_limit(control_count) - end - end + it_behaves_like 'backfill migration for project repositories', :hashed end diff --git a/spec/lib/gitlab/background_migration/backfill_legacy_project_repositories_spec.rb b/spec/lib/gitlab/background_migration/backfill_legacy_project_repositories_spec.rb new file mode 100644 index 00000000000..ae4b53d62e6 --- /dev/null +++ b/spec/lib/gitlab/background_migration/backfill_legacy_project_repositories_spec.rb @@ -0,0 +1,7 @@ +# frozen_string_literal: true + +require 'spec_helper' + +describe Gitlab::BackgroundMigration::BackfillLegacyProjectRepositories, :migration, schema: 20181218192239 do + it_behaves_like 'backfill migration for project repositories', :legacy +end diff --git a/spec/lib/gitlab/background_migration/backfill_project_repositories_spec.rb b/spec/lib/gitlab/background_migration/backfill_project_repositories_spec.rb new file mode 100644 index 00000000000..53c071f0268 --- /dev/null +++ b/spec/lib/gitlab/background_migration/backfill_project_repositories_spec.rb @@ -0,0 +1,94 @@ +# frozen_string_literal: true + +require 'spec_helper' + +describe Gitlab::BackgroundMigration::BackfillProjectRepositories do + let(:group) { create(:group, name: 'foo', path: 'foo') } + + describe described_class::ShardFinder do + let(:shard) { create(:shard, name: 'default') } + + describe '#find_shard_id' do + it 'creates a new shard when it does not exist yet' do + expect { subject.find_shard_id('other') }.to change(Shard, :count).by(1) + end + + it 'returns the shard when it exists' do + other_shard = create(:shard, name: 'other') + + shard_id = subject.find_shard_id('other') + + expect(shard_id).to eq(other_shard.id) + end + + it 'only queries the database once to retrieve shards' do + subject.find_shard_id('default') + + expect { subject.find_shard_id('default') }.not_to exceed_query_limit(0) + end + end + end + + describe described_class::Project do + let!(:project_hashed_storage_1) { create(:project, name: 'foo', path: 'foo', namespace: group, storage_version: 1) } + let!(:project_hashed_storage_2) { create(:project, name: 'bar', path: 'bar', namespace: group, storage_version: 2) } + let!(:project_legacy_storage_3) { create(:project, name: 'baz', path: 'baz', namespace: group, storage_version: 0) } + let!(:project_legacy_storage_4) { create(:project, name: 'zoo', path: 'zoo', namespace: group, storage_version: nil) } + + describe '.on_hashed_storage' do + it 'finds projects with repository on hashed storage' do + projects = described_class.on_hashed_storage.pluck(:id) + + expect(projects).to match_array([project_hashed_storage_1.id, project_hashed_storage_2.id]) + end + end + + describe '.on_legacy_storage' do + it 'finds projects with repository on legacy storage' do + projects = described_class.on_legacy_storage.pluck(:id) + + expect(projects).to match_array([project_legacy_storage_3.id, project_legacy_storage_4.id]) + end + end + + describe '.without_project_repository' do + it 'finds projects which do not have a projects_repositories entry' do + create(:project_repository, project: project_hashed_storage_1) + create(:project_repository, project: project_legacy_storage_3) + + projects = described_class.without_project_repository.pluck(:id) + + expect(projects).to contain_exactly(project_hashed_storage_2.id, project_legacy_storage_4.id) + end + end + + describe '#disk_path' do + context 'for projects on hashed storage' do + it 'returns the correct disk_path' do + project = described_class.find(project_hashed_storage_1.id) + + expect(project.disk_path).to eq(project_hashed_storage_1.disk_path) + end + end + + context 'for projects on legacy storage' do + it 'returns the correct disk_path' do + project = described_class.find(project_legacy_storage_3.id) + + expect(project.disk_path).to eq(project_legacy_storage_3.disk_path) + end + + it 'raises OrphanedNamespaceError when any parent namespace does not exist' do + subgroup = create(:group, parent: group) + project_orphaned_namespace = create(:project, name: 'baz', path: 'baz', namespace: subgroup, storage_version: nil) + subgroup.update_column(:parent_id, Namespace.maximum(:id).succ) + + project = described_class.find(project_orphaned_namespace.id) + + expect { project.disk_path } + .to raise_error(Gitlab::BackgroundMigration::BackfillProjectRepositories::OrphanedNamespaceError) + end + end + end + end +end diff --git a/spec/lib/gitlab/background_migration/migrate_build_stage_spec.rb b/spec/lib/gitlab/background_migration/migrate_build_stage_spec.rb index 5ce84c61042..7c7e58d6bb7 100644 --- a/spec/lib/gitlab/background_migration/migrate_build_stage_spec.rb +++ b/spec/lib/gitlab/background_migration/migrate_build_stage_spec.rb @@ -6,8 +6,18 @@ describe Gitlab::BackgroundMigration::MigrateBuildStage, :migration, schema: 201 let(:stages) { table(:ci_stages) } let(:jobs) { table(:ci_builds) } - STATUSES = { created: 0, pending: 1, running: 2, success: 3, - failed: 4, canceled: 5, skipped: 6, manual: 7 }.freeze + let(:statuses) do + { + created: 0, + pending: 1, + running: 2, + success: 3, + failed: 4, + canceled: 5, + skipped: 6, + manual: 7 + } + end before do projects.create!(id: 123, name: 'gitlab', path: 'gitlab-ce') @@ -36,9 +46,9 @@ describe Gitlab::BackgroundMigration::MigrateBuildStage, :migration, schema: 201 expect(stages.all.pluck(:name)).to match_array %w[test build deploy] expect(jobs.where(stage_id: nil)).to be_one expect(jobs.find_by(stage_id: nil).id).to eq 6 - expect(stages.all.pluck(:status)).to match_array [STATUSES[:success], - STATUSES[:failed], - STATUSES[:pending]] + expect(stages.all.pluck(:status)).to match_array [statuses[:success], + statuses[:failed], + statuses[:pending]] end it 'recovers from unique constraint violation only twice' do diff --git a/spec/lib/gitlab/background_migration/migrate_stage_status_spec.rb b/spec/lib/gitlab/background_migration/migrate_stage_status_spec.rb index 878158910be..89b56906ed0 100644 --- a/spec/lib/gitlab/background_migration/migrate_stage_status_spec.rb +++ b/spec/lib/gitlab/background_migration/migrate_stage_status_spec.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + require 'spec_helper' describe Gitlab::BackgroundMigration::MigrateStageStatus, :migration, schema: 20170711145320 do @@ -6,8 +8,18 @@ describe Gitlab::BackgroundMigration::MigrateStageStatus, :migration, schema: 20 let(:stages) { table(:ci_stages) } let(:jobs) { table(:ci_builds) } - STATUSES = { created: 0, pending: 1, running: 2, success: 3, - failed: 4, canceled: 5, skipped: 6, manual: 7 }.freeze + let(:statuses) do + { + created: 0, + pending: 1, + running: 2, + success: 3, + failed: 4, + canceled: 5, + skipped: 6, + manual: 7 + } + end before do projects.create!(id: 1, name: 'gitlab1', path: 'gitlab1') @@ -26,8 +38,8 @@ describe Gitlab::BackgroundMigration::MigrateStageStatus, :migration, schema: 20 it 'sets a correct stage status' do described_class.new.perform(1, 2) - expect(stages.first.status).to eq STATUSES[:running] - expect(stages.second.status).to eq STATUSES[:failed] + expect(stages.first.status).to eq statuses[:running] + expect(stages.second.status).to eq statuses[:failed] end end @@ -35,8 +47,8 @@ describe Gitlab::BackgroundMigration::MigrateStageStatus, :migration, schema: 20 it 'sets a skipped stage status' do described_class.new.perform(1, 2) - expect(stages.first.status).to eq STATUSES[:skipped] - expect(stages.second.status).to eq STATUSES[:skipped] + expect(stages.first.status).to eq statuses[:skipped] + expect(stages.second.status).to eq statuses[:skipped] end end @@ -50,8 +62,8 @@ describe Gitlab::BackgroundMigration::MigrateStageStatus, :migration, schema: 20 it 'sets a correct stage status' do described_class.new.perform(1, 2) - expect(stages.first.status).to eq STATUSES[:canceled] - expect(stages.second.status).to eq STATUSES[:success] + expect(stages.first.status).to eq statuses[:canceled] + expect(stages.second.status).to eq statuses[:success] end end @@ -65,8 +77,8 @@ describe Gitlab::BackgroundMigration::MigrateStageStatus, :migration, schema: 20 it 'sets a correct stage status' do described_class.new.perform(1, 2) - expect(stages.first.status).to eq STATUSES[:manual] - expect(stages.second.status).to eq STATUSES[:success] + expect(stages.first.status).to eq statuses[:manual] + expect(stages.second.status).to eq statuses[:success] 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 3c63e601abc..f4759b69538 100644 --- a/spec/lib/gitlab/bare_repository_import/importer_spec.rb +++ b/spec/lib/gitlab/bare_repository_import/importer_spec.rb @@ -192,7 +192,7 @@ describe Gitlab::BareRepositoryImport::Importer, :seed_helper do let(:project_path) { 'a-group/a-sub-group/a-project' } before do - expect(Group).to receive(:supports_nested_groups?) { false } + expect(Group).to receive(:supports_nested_objects?) { false } end describe '#create_project_if_needed' do diff --git a/spec/lib/gitlab/blob_helper_spec.rb b/spec/lib/gitlab/blob_helper_spec.rb index 0b56f8687c3..e057385b35f 100644 --- a/spec/lib/gitlab/blob_helper_spec.rb +++ b/spec/lib/gitlab/blob_helper_spec.rb @@ -53,11 +53,11 @@ describe Gitlab::BlobHelper do describe '#text?' do it 'returns true' do - expect(blob.text?).to be_truthy + expect(blob.text_in_repo?).to be_truthy end it 'returns false' do - expect(large_blob.text?).to be_falsey + expect(large_blob.text_in_repo?).to be_falsey end end diff --git a/spec/lib/gitlab/checks/diff_check_spec.rb b/spec/lib/gitlab/checks/diff_check_spec.rb index eeec1e83179..a341dfa5636 100644 --- a/spec/lib/gitlab/checks/diff_check_spec.rb +++ b/spec/lib/gitlab/checks/diff_check_spec.rb @@ -47,5 +47,43 @@ describe Gitlab::Checks::DiffCheck do end end end + + context 'commit diff validations' do + before do + allow(subject).to receive(:validations_for_diff).and_return([lambda { |diff| return }]) + + expect_any_instance_of(Commit).to receive(:raw_deltas).and_call_original + + subject.validate! + end + + context 'when request store is inactive' do + it 'are run for every commit' do + expect_any_instance_of(Commit).to receive(:raw_deltas).and_call_original + + subject.validate! + end + end + + context 'when request store is active', :request_store do + it 'are cached for every commit' do + expect_any_instance_of(Commit).not_to receive(:raw_deltas) + + subject.validate! + end + + it 'are run for not cached commits' do + allow(project.repository).to receive(:new_commits).and_return( + project.repository.commits_between('be93687618e4b132087f430a4d8fc3a609c9b77c', 'a5391128b0ef5d21df5dd23d98557f4ef12fae20') + ) + change_access.instance_variable_set(:@commits, project.repository.new_commits) + + expect(project.repository.new_commits.first).not_to receive(:raw_deltas).and_call_original + expect(project.repository.new_commits.last).to receive(:raw_deltas).and_call_original + + subject.validate! + end + end + end end end diff --git a/spec/lib/gitlab/ci/config/entry/except_policy_spec.rb b/spec/lib/gitlab/ci/config/entry/except_policy_spec.rb deleted file mode 100644 index d036bf2f4d1..00000000000 --- a/spec/lib/gitlab/ci/config/entry/except_policy_spec.rb +++ /dev/null @@ -1,15 +0,0 @@ -# frozen_string_literal: true - -require 'spec_helper' - -describe Gitlab::Ci::Config::Entry::ExceptPolicy do - let(:entry) { described_class.new(config) } - - it_behaves_like 'correct only except policy' - - describe '.default' do - it 'does not have a default value' do - expect(described_class.default).to be_nil - end - end -end diff --git a/spec/lib/gitlab/ci/config/entry/global_spec.rb b/spec/lib/gitlab/ci/config/entry/global_spec.rb index 12f4b9dc624..61d78f86b51 100644 --- a/spec/lib/gitlab/ci/config/entry/global_spec.rb +++ b/spec/lib/gitlab/ci/config/entry/global_spec.rb @@ -161,7 +161,8 @@ describe Gitlab::Ci::Config::Entry::Global do variables: { 'VAR' => 'value' }, ignore: false, after_script: ['make clean'], - only: { refs: %w[branches tags] } }, + only: { refs: %w[branches tags] }, + except: {} }, spinach: { name: :spinach, before_script: [], script: %w[spinach], @@ -173,7 +174,8 @@ describe Gitlab::Ci::Config::Entry::Global do variables: {}, ignore: false, after_script: ['make clean'], - only: { refs: %w[branches tags] } } + only: { refs: %w[branches tags] }, + except: {} } ) end end diff --git a/spec/lib/gitlab/ci/config/entry/job_spec.rb b/spec/lib/gitlab/ci/config/entry/job_spec.rb index c1f4a060063..8e32cede3b5 100644 --- a/spec/lib/gitlab/ci/config/entry/job_spec.rb +++ b/spec/lib/gitlab/ci/config/entry/job_spec.rb @@ -259,7 +259,8 @@ describe Gitlab::Ci::Config::Entry::Job do stage: 'test', ignore: false, after_script: %w[cleanup], - only: { refs: %w[branches tags] }) + only: { refs: %w[branches tags] }, + except: {}) end end end diff --git a/spec/lib/gitlab/ci/config/entry/jobs_spec.rb b/spec/lib/gitlab/ci/config/entry/jobs_spec.rb index 2a753408f54..1a2c30d3571 100644 --- a/spec/lib/gitlab/ci/config/entry/jobs_spec.rb +++ b/spec/lib/gitlab/ci/config/entry/jobs_spec.rb @@ -68,13 +68,15 @@ describe Gitlab::Ci::Config::Entry::Jobs do commands: 'rspec', ignore: false, stage: 'test', - only: { refs: %w[branches tags] } }, + only: { refs: %w[branches tags] }, + except: {} }, spinach: { name: :spinach, script: %w[spinach], commands: 'spinach', ignore: false, stage: 'test', - only: { refs: %w[branches tags] } }) + only: { refs: %w[branches tags] }, + except: {} }) end end diff --git a/spec/lib/gitlab/ci/config/entry/only_policy_spec.rb b/spec/lib/gitlab/ci/config/entry/only_policy_spec.rb deleted file mode 100644 index 5518b68e51a..00000000000 --- a/spec/lib/gitlab/ci/config/entry/only_policy_spec.rb +++ /dev/null @@ -1,15 +0,0 @@ -# frozen_string_literal: true - -require 'spec_helper' - -describe Gitlab::Ci::Config::Entry::OnlyPolicy do - let(:entry) { described_class.new(config) } - - it_behaves_like 'correct only except policy' - - describe '.default' do - it 'haa a default value' do - expect(described_class.default).to eq( { refs: %w[branches tags] } ) - end - end -end diff --git a/spec/lib/gitlab/ci/config/entry/policy_spec.rb b/spec/lib/gitlab/ci/config/entry/policy_spec.rb index cf40a22af2e..83001b7fdd8 100644 --- a/spec/lib/gitlab/ci/config/entry/policy_spec.rb +++ b/spec/lib/gitlab/ci/config/entry/policy_spec.rb @@ -1,8 +1,173 @@ -require 'spec_helper' +require 'fast_spec_helper' +require_dependency 'active_model' describe Gitlab::Ci::Config::Entry::Policy do let(:entry) { described_class.new(config) } + context 'when using simplified policy' do + describe 'validations' do + context 'when entry config value is valid' do + context 'when config is a branch or tag name' do + let(:config) { %w[master feature/branch] } + + describe '#valid?' do + it 'is valid' do + expect(entry).to be_valid + end + end + + describe '#value' do + it 'returns refs hash' do + expect(entry.value).to eq(refs: config) + end + end + end + + context 'when config is a regexp' do + let(:config) { ['/^issue-.*$/'] } + + describe '#valid?' do + it 'is valid' do + expect(entry).to be_valid + end + end + end + + context 'when config is a special keyword' do + let(:config) { %w[tags triggers branches] } + + describe '#valid?' do + it 'is valid' do + expect(entry).to be_valid + end + end + end + end + + context 'when entry value is not valid' do + let(:config) { [1] } + + describe '#errors' do + it 'saves errors' do + expect(entry.errors) + .to include /policy config should be an array of strings or regexps/ + end + end + end + end + end + + context 'when using complex policy' do + context 'when specifying refs policy' do + let(:config) { { refs: ['master'] } } + + it 'is a correct configuraton' do + expect(entry).to be_valid + expect(entry.value).to eq(refs: %w[master]) + end + end + + context 'when specifying kubernetes policy' do + let(:config) { { kubernetes: 'active' } } + + it 'is a correct configuraton' do + expect(entry).to be_valid + expect(entry.value).to eq(kubernetes: 'active') + end + end + + context 'when specifying invalid kubernetes policy' do + let(:config) { { kubernetes: 'something' } } + + it 'reports an error about invalid policy' do + expect(entry.errors).to include /unknown value: something/ + end + end + + context 'when specifying valid variables expressions policy' do + let(:config) { { variables: ['$VAR == null'] } } + + it 'is a correct configuraton' do + expect(entry).to be_valid + expect(entry.value).to eq(config) + end + end + + context 'when specifying variables expressions in invalid format' do + let(:config) { { variables: '$MY_VAR' } } + + it 'reports an error about invalid format' do + expect(entry.errors).to include /should be an array of strings/ + end + end + + context 'when specifying invalid variables expressions statement' do + let(:config) { { variables: ['$MY_VAR =='] } } + + it 'reports an error about invalid statement' do + expect(entry.errors).to include /invalid expression syntax/ + end + end + + context 'when specifying invalid variables expressions token' do + let(:config) { { variables: ['$MY_VAR == 123'] } } + + it 'reports an error about invalid expression' do + expect(entry.errors).to include /invalid expression syntax/ + end + end + + context 'when using invalid variables expressions regexp' do + let(:config) { { variables: ['$MY_VAR =~ /some ( thing/'] } } + + it 'reports an error about invalid expression' do + expect(entry.errors).to include /invalid expression syntax/ + end + end + + context 'when specifying a valid changes policy' do + let(:config) { { changes: %w[some/* paths/**/*.rb] } } + + it 'is a correct configuraton' do + expect(entry).to be_valid + expect(entry.value).to eq(config) + end + end + + context 'when changes policy is invalid' do + let(:config) { { changes: [1, 2] } } + + it 'returns errors' do + expect(entry.errors).to include /changes should be an array of strings/ + end + end + + context 'when specifying unknown policy' do + let(:config) { { refs: ['master'], invalid: :something } } + + it 'returns error about invalid key' do + expect(entry.errors).to include /unknown keys: invalid/ + end + end + + context 'when policy is empty' do + let(:config) { {} } + + it 'is not a valid configuration' do + expect(entry.errors).to include /can't be blank/ + end + end + end + + context 'when policy strategy does not match' do + let(:config) { 'string strategy' } + + it 'returns information about errors' do + expect(entry.errors) + .to include /has to be either an array of conditions or a hash/ + end + end + describe '.default' do it 'does not have a default value' do expect(described_class.default).to be_nil diff --git a/spec/lib/gitlab/ci/config/external/file/local_spec.rb b/spec/lib/gitlab/ci/config/external/file/local_spec.rb index 541deb13b97..645db642e29 100644 --- a/spec/lib/gitlab/ci/config/external/file/local_spec.rb +++ b/spec/lib/gitlab/ci/config/external/file/local_spec.rb @@ -44,7 +44,6 @@ describe Gitlab::Ci::Config::External::File::Local do - apt-get update -qq && apt-get install -y -qq sqlite3 libsqlite3-dev nodejs - ruby -v - which ruby - - gem install bundler --no-ri --no-rdoc - bundle install --jobs $(nproc) "${FLAGS[@]}" HEREDOC end diff --git a/spec/lib/gitlab/ci/config/external/file/remote_spec.rb b/spec/lib/gitlab/ci/config/external/file/remote_spec.rb index 7c1a1c38736..eaf621e4140 100644 --- a/spec/lib/gitlab/ci/config/external/file/remote_spec.rb +++ b/spec/lib/gitlab/ci/config/external/file/remote_spec.rb @@ -11,7 +11,6 @@ describe Gitlab::Ci::Config::External::File::Remote do - apt-get update -qq && apt-get install -y -qq sqlite3 libsqlite3-dev nodejs - ruby -v - which ruby - - gem install bundler --no-ri --no-rdoc - bundle install --jobs $(nproc) "${FLAGS[@]}" HEREDOC end diff --git a/spec/lib/gitlab/ci/config/external/processor_spec.rb b/spec/lib/gitlab/ci/config/external/processor_spec.rb index 1a05f716247..dbd28e9745c 100644 --- a/spec/lib/gitlab/ci/config/external/processor_spec.rb +++ b/spec/lib/gitlab/ci/config/external/processor_spec.rb @@ -51,7 +51,6 @@ describe Gitlab::Ci::Config::External::Processor do - apt-get update -qq && apt-get install -y -qq sqlite3 libsqlite3-dev nodejs - ruby -v - which ruby - - gem install bundler --no-ri --no-rdoc - bundle install --jobs $(nproc) "${FLAGS[@]}" rspec: @@ -86,7 +85,6 @@ describe Gitlab::Ci::Config::External::Processor do - apt-get update -qq && apt-get install -y -qq sqlite3 libsqlite3-dev nodejs - ruby -v - which ruby - - gem install bundler --no-ri --no-rdoc - bundle install --jobs $(nproc) "${FLAGS[@]}" HEREDOC end diff --git a/spec/lib/gitlab/ci/config_spec.rb b/spec/lib/gitlab/ci/config_spec.rb index 975e11e8cc1..ea6f1e20014 100644 --- a/spec/lib/gitlab/ci/config_spec.rb +++ b/spec/lib/gitlab/ci/config_spec.rb @@ -170,7 +170,6 @@ describe Gitlab::Ci::Config do before_script_values = [ "apt-get update -qq && apt-get install -y -qq sqlite3 libsqlite3-dev nodejs", "ruby -v", "which ruby", - "gem install bundler --no-ri --no-rdoc", "bundle install --jobs $(nproc) \"${FLAGS[@]}\"" ] variables = { diff --git a/spec/lib/gitlab/ci/pipeline/chain/command_spec.rb b/spec/lib/gitlab/ci/pipeline/chain/command_spec.rb index 75a177d2d1f..6aa802ce6fd 100644 --- a/spec/lib/gitlab/ci/pipeline/chain/command_spec.rb +++ b/spec/lib/gitlab/ci/pipeline/chain/command_spec.rb @@ -182,4 +182,24 @@ describe Gitlab::Ci::Pipeline::Chain::Command do it { is_expected.to eq(false) } end end + + describe '#ambiguous_ref' do + let(:project) { create(:project, :repository) } + let(:command) { described_class.new(project: project, origin_ref: 'ref') } + + subject { command.ambiguous_ref? } + + context 'when ref is not ambiguous' do + it { is_expected. to eq(false) } + end + + context 'when ref is ambiguous' do + before do + project.repository.add_tag(project.creator, 'ref', 'master') + project.repository.add_branch(project.creator, 'ref', 'master') + end + + it { is_expected. to eq(true) } + end + end end diff --git a/spec/lib/gitlab/ci/pipeline/chain/populate_spec.rb b/spec/lib/gitlab/ci/pipeline/chain/populate_spec.rb index 284aed91e29..1b014ecfaa4 100644 --- a/spec/lib/gitlab/ci/pipeline/chain/populate_spec.rb +++ b/spec/lib/gitlab/ci/pipeline/chain/populate_spec.rb @@ -14,6 +14,7 @@ describe Gitlab::Ci::Pipeline::Chain::Populate do Gitlab::Ci::Pipeline::Chain::Command.new( project: project, current_user: user, + origin_ref: 'master', seeds_block: nil) end @@ -106,6 +107,7 @@ describe Gitlab::Ci::Pipeline::Chain::Populate do Gitlab::Ci::Pipeline::Chain::Command.new( project: project, current_user: user, + origin_ref: 'master', seeds_block: seeds_block) end diff --git a/spec/lib/gitlab/ci/pipeline/chain/validate/repository_spec.rb b/spec/lib/gitlab/ci/pipeline/chain/validate/repository_spec.rb index fb1b53fc55c..a7cad423d09 100644 --- a/spec/lib/gitlab/ci/pipeline/chain/validate/repository_spec.rb +++ b/spec/lib/gitlab/ci/pipeline/chain/validate/repository_spec.rb @@ -42,6 +42,27 @@ describe Gitlab::Ci::Pipeline::Chain::Validate::Repository do end end + context 'when ref is ambiguous' do + let(:project) do + create(:project, :repository).tap do |proj| + proj.repository.add_tag(user, 'master', 'master') + end + end + let(:command) do + Gitlab::Ci::Pipeline::Chain::Command.new( + project: project, current_user: user, origin_ref: 'master') + end + + it 'breaks the chain' do + expect(step.break?).to be true + end + + it 'adds an error about missing ref' do + expect(pipeline.errors.to_a) + .to include 'Ref is ambiguous' + end + end + context 'when does not have existing SHA set' do let(:command) do Gitlab::Ci::Pipeline::Chain::Command.new( diff --git a/spec/lib/gitlab/ci/pipeline/seed/build_spec.rb b/spec/lib/gitlab/ci/pipeline/seed/build_spec.rb index fffa727c2ed..2cf812b26dc 100644 --- a/spec/lib/gitlab/ci/pipeline/seed/build_spec.rb +++ b/spec/lib/gitlab/ci/pipeline/seed/build_spec.rb @@ -1,7 +1,8 @@ require 'spec_helper' describe Gitlab::Ci::Pipeline::Seed::Build do - let(:pipeline) { create(:ci_empty_pipeline) } + let(:project) { create(:project, :repository) } + let(:pipeline) { create(:ci_empty_pipeline, project: project) } let(:attributes) do { name: 'rspec', diff --git a/spec/lib/gitlab/ci/pipeline/seed/stage_spec.rb b/spec/lib/gitlab/ci/pipeline/seed/stage_spec.rb index 05ce3412fd8..82f741845db 100644 --- a/spec/lib/gitlab/ci/pipeline/seed/stage_spec.rb +++ b/spec/lib/gitlab/ci/pipeline/seed/stage_spec.rb @@ -1,7 +1,8 @@ require 'spec_helper' describe Gitlab::Ci::Pipeline::Seed::Stage do - let(:pipeline) { create(:ci_empty_pipeline) } + let(:project) { create(:project, :repository) } + let(:pipeline) { create(:ci_empty_pipeline, project: project) } let(:attributes) do { name: 'test', diff --git a/spec/lib/gitlab/cleanup/remote_uploads_spec.rb b/spec/lib/gitlab/cleanup/remote_uploads_spec.rb index 8d03baeb07b..35642cd6e50 100644 --- a/spec/lib/gitlab/cleanup/remote_uploads_spec.rb +++ b/spec/lib/gitlab/cleanup/remote_uploads_spec.rb @@ -25,7 +25,7 @@ describe Gitlab::Cleanup::RemoteUploads do expect(::Fog::Storage).to receive(:new).and_return(connection) - expect(connection).to receive(:directories).and_return(double(get: directory)) + expect(connection).to receive(:directories).and_return(double(new: directory)) expect(directory).to receive(:files).and_return(remote_files) end diff --git a/spec/lib/gitlab/current_settings_spec.rb b/spec/lib/gitlab/current_settings_spec.rb index 55490f37ac7..caf9fc5442c 100644 --- a/spec/lib/gitlab/current_settings_spec.rb +++ b/spec/lib/gitlab/current_settings_spec.rb @@ -54,7 +54,7 @@ describe Gitlab::CurrentSettings do expect(ApplicationSetting).not_to receive(:current) end - it 'returns an in-memory ApplicationSetting object' do + it 'returns a FakeApplicationSettings object' do expect(described_class.current_application_settings).to be_a(Gitlab::FakeApplicationSettings) end @@ -91,6 +91,14 @@ describe Gitlab::CurrentSettings do allow(ActiveRecord::Base.connection).to receive(:cached_table_exists?).with('application_settings').and_return(true) end + context 'with RequestStore enabled', :request_store do + it 'fetches the settings from DB only once' do + described_class.current_application_settings # warm the cache + + expect(ActiveRecord::QueryRecorder.new { described_class.current_application_settings }.count).to eq(0) + end + end + it 'creates default ApplicationSettings if none are present' do settings = described_class.current_application_settings @@ -99,34 +107,45 @@ describe Gitlab::CurrentSettings do expect(settings).to have_attributes(settings_from_defaults) end - context 'with migrations pending' do + context 'with pending migrations' do before do expect(ActiveRecord::Migrator).to receive(:needs_migration?).and_return(true) end - it 'returns an in-memory ApplicationSetting object' do - settings = described_class.current_application_settings + shared_examples 'a non-persisted ApplicationSetting object' do + let(:current_settings) { described_class.current_application_settings } + + it 'returns a non-persisted ApplicationSetting object' do + expect(current_settings).to be_a(ApplicationSetting) + expect(current_settings).not_to be_persisted + end + + it 'uses the default value from ApplicationSetting.defaults' do + expect(current_settings.signup_enabled).to eq(ApplicationSetting.defaults[:signup_enabled]) + end + + it 'uses the default value from custom ApplicationSetting accessors' do + expect(current_settings.commit_email_hostname).to eq(ApplicationSetting.default_commit_email_hostname) + end + + it 'responds to predicate methods' do + expect(current_settings.signup_enabled?).to eq(current_settings.signup_enabled) + end + end - expect(settings).to be_a(Gitlab::FakeApplicationSettings) - expect(settings.sign_in_enabled?).to eq(settings.sign_in_enabled) - expect(settings.sign_up_enabled?).to eq(settings.sign_up_enabled) + context 'with no ApplicationSetting DB record' do + it_behaves_like 'a non-persisted ApplicationSetting object' end - it 'uses the existing database settings and falls back to defaults' do - db_settings = create(:application_setting, - home_page_url: 'http://mydomain.com', - signup_enabled: false) - settings = described_class.current_application_settings - app_defaults = ApplicationSetting.last - - expect(settings).to be_a(Gitlab::FakeApplicationSettings) - expect(settings.home_page_url).to eq(db_settings.home_page_url) - expect(settings.signup_enabled?).to be_falsey - expect(settings.signup_enabled).to be_falsey - - # Check that unspecified values use the defaults - settings.reject! { |key, _| [:home_page_url, :signup_enabled].include? key } - settings.each { |key, _| expect(settings[key]).to eq(app_defaults[key]) } + context 'with an existing ApplicationSetting DB record' do + let!(:db_settings) { ApplicationSetting.build_from_defaults(home_page_url: 'http://mydomain.com').save! && ApplicationSetting.last } + let(:current_settings) { described_class.current_application_settings } + + it_behaves_like 'a non-persisted ApplicationSetting object' + + it 'uses the value from the DB attribute if present and not overriden by an accessor' do + expect(current_settings.home_page_url).to eq(db_settings.home_page_url) + end end end @@ -138,17 +157,12 @@ describe Gitlab::CurrentSettings do end end - context 'when the application_settings table does not exists' do - it 'returns an in-memory ApplicationSetting object' do - expect(ApplicationSetting).to receive(:create_from_defaults).and_raise(ActiveRecord::StatementInvalid) - - expect(described_class.current_application_settings).to be_a(Gitlab::FakeApplicationSettings) - end - end - - context 'when the application_settings table is not fully migrated' do - it 'returns an in-memory ApplicationSetting object' do - expect(ApplicationSetting).to receive(:create_from_defaults).and_raise(ActiveRecord::UnknownAttributeError) + context 'when the application_settings table does not exist' do + it 'returns a FakeApplicationSettings object' do + expect(Gitlab::Database) + .to receive(:cached_table_exists?) + .with('application_settings') + .and_return(false) expect(described_class.current_application_settings).to be_a(Gitlab::FakeApplicationSettings) end diff --git a/spec/lib/gitlab/database/migration_helpers_spec.rb b/spec/lib/gitlab/database/migration_helpers_spec.rb index 23f27939dd2..4e83b27e4a5 100644 --- a/spec/lib/gitlab/database/migration_helpers_spec.rb +++ b/spec/lib/gitlab/database/migration_helpers_spec.rb @@ -1338,12 +1338,7 @@ describe Gitlab::Database::MigrationHelpers do end describe '#index_exists_by_name?' do - # TODO: remove rails5-only after removing rails4 tests - # rails 4 can not handle multiple indexes on the same column set if - # index was added by 't.index' - t.index is used by default in schema.rb in - # rails 5. Let's run this test only in rails 5 env: - # see https://gitlab.com/gitlab-org/gitlab-ce/merge_requests/21492#note_113602758 - it 'returns true if an index exists', :rails5 do + it 'returns true if an index exists' do expect(model.index_exists_by_name?(:projects, 'index_projects_on_path')) .to be_truthy end diff --git a/spec/lib/gitlab/database_spec.rb b/spec/lib/gitlab/database_spec.rb index fc295b2deff..60106ee3c0b 100644 --- a/spec/lib/gitlab/database_spec.rb +++ b/spec/lib/gitlab/database_spec.rb @@ -400,13 +400,8 @@ describe Gitlab::Database do describe '.cached_table_exists?' do it 'only retrieves data once per table' do - if Gitlab.rails5? - expect(ActiveRecord::Base.connection).to receive(:data_source_exists?).with(:projects).once.and_call_original - expect(ActiveRecord::Base.connection).to receive(:data_source_exists?).with(:bogus_table_name).once.and_call_original - else - expect(ActiveRecord::Base.connection).to receive(:table_exists?).with(:projects).once.and_call_original - expect(ActiveRecord::Base.connection).to receive(:table_exists?).with(:bogus_table_name).once.and_call_original - end + expect(ActiveRecord::Base.connection).to receive(:data_source_exists?).with(:projects).once.and_call_original + expect(ActiveRecord::Base.connection).to receive(:data_source_exists?).with(:bogus_table_name).once.and_call_original 2.times do expect(described_class.cached_table_exists?(:projects)).to be_truthy @@ -462,8 +457,7 @@ describe Gitlab::Database do expect(described_class.db_read_only?).to be_truthy end - # TODO: remove rails5-only tag after removing rails4 tests - it 'detects a read only database', :rails5 do + it 'detects a read only database' do allow(ActiveRecord::Base.connection).to receive(:execute).with('SELECT pg_is_in_recovery()').and_return([{ "pg_is_in_recovery" => true }]) expect(described_class.db_read_only?).to be_truthy @@ -475,8 +469,7 @@ describe Gitlab::Database do expect(described_class.db_read_only?).to be_falsey end - # TODO: remove rails5-only tag after removing rails4 tests - it 'detects a read write database', :rails5 do + it 'detects a read write database' do allow(ActiveRecord::Base.connection).to receive(:execute).with('SELECT pg_is_in_recovery()').and_return([{ "pg_is_in_recovery" => false }]) expect(described_class.db_read_only?).to be_falsey diff --git a/spec/lib/gitlab/diff/file_spec.rb b/spec/lib/gitlab/diff/file_spec.rb index 3417896e259..862590268ca 100644 --- a/spec/lib/gitlab/diff/file_spec.rb +++ b/spec/lib/gitlab/diff/file_spec.rb @@ -310,7 +310,7 @@ describe Gitlab::Diff::File do context 'when the content changed' do context 'when the file represented by the diff file is binary' do before do - allow(diff_file).to receive(:raw_binary?).and_return(true) + allow(diff_file).to receive(:binary?).and_return(true) end it 'returns a No Preview viewer' do @@ -345,7 +345,7 @@ describe Gitlab::Diff::File do context 'when the file represented by the diff file is binary' do before do - allow(diff_file).to receive(:raw_binary?).and_return(true) + allow(diff_file).to receive(:binary?).and_return(true) end it 'returns an Added viewer' do @@ -380,7 +380,7 @@ describe Gitlab::Diff::File do context 'when the file represented by the diff file is binary' do before do - allow(diff_file).to receive(:raw_binary?).and_return(true) + allow(diff_file).to receive(:binary?).and_return(true) end it 'returns a Deleted viewer' do @@ -436,7 +436,7 @@ describe Gitlab::Diff::File do allow(diff_file).to receive(:deleted_file?).and_return(false) allow(diff_file).to receive(:renamed_file?).and_return(false) allow(diff_file).to receive(:mode_changed?).and_return(false) - allow(diff_file).to receive(:raw_text?).and_return(false) + allow(diff_file).to receive(:text?).and_return(false) end it 'returns a No Preview viewer' do @@ -583,6 +583,12 @@ describe Gitlab::Diff::File do end end + describe '#empty?' do + it 'returns true' do + expect(diff_file.empty?).to be_truthy + end + end + describe '#different_type?' do it 'returns false' do expect(diff_file).not_to be_different_type @@ -662,4 +668,87 @@ describe Gitlab::Diff::File do end end end + + describe '#empty?' do + let(:project) do + create(:project, :custom_repo, files: {}) + end + let(:branch_name) { 'master' } + + def create_file(file_name, content) + Files::CreateService.new( + project, + project.owner, + commit_message: 'Update', + start_branch: branch_name, + branch_name: branch_name, + file_path: file_name, + file_content: content + ).execute + + project.commit(branch_name).diffs.diff_files.first + end + + def update_file(file_name, content) + Files::UpdateService.new( + project, + project.owner, + commit_message: 'Update', + start_branch: branch_name, + branch_name: branch_name, + file_path: file_name, + file_content: content + ).execute + + project.commit(branch_name).diffs.diff_files.first + end + + def delete_file(file_name) + Files::DeleteService.new( + project, + project.owner, + commit_message: 'Update', + start_branch: branch_name, + branch_name: branch_name, + file_path: file_name + ).execute + + project.commit(branch_name).diffs.diff_files.first + end + + context 'when empty file is created' do + it 'returns true' do + diff_file = create_file('empty.md', '') + + expect(diff_file.empty?).to be_truthy + end + end + + context 'when empty file is deleted' do + it 'returns true' do + create_file('empty.md', '') + diff_file = delete_file('empty.md') + + expect(diff_file.empty?).to be_truthy + end + end + + context 'when file with content is truncated' do + it 'returns false' do + create_file('with-content.md', 'file content') + diff_file = update_file('with-content.md', '') + + expect(diff_file.empty?).to be_falsey + end + end + + context 'when empty file has content added' do + it 'returns false' do + create_file('empty.md', '') + diff_file = update_file('empty.md', 'new content') + + expect(diff_file.empty?).to be_falsey + end + end + end end diff --git a/spec/lib/gitlab/diff/lines_unfolder_spec.rb b/spec/lib/gitlab/diff/lines_unfolder_spec.rb index 8e00c8e0e30..f22c2c90334 100644 --- a/spec/lib/gitlab/diff/lines_unfolder_spec.rb +++ b/spec/lib/gitlab/diff/lines_unfolder_spec.rb @@ -185,7 +185,7 @@ describe Gitlab::Diff::LinesUnfolder do let(:project) { create(:project) } - let(:old_blob) { Gitlab::Git::Blob.new(data: raw_old_blob) } + let(:old_blob) { Blob.decorate(Gitlab::Git::Blob.new(data: raw_old_blob, size: 10)) } let(:diff) do Gitlab::Git::Diff.new(diff: raw_diff, diff --git a/spec/lib/gitlab/discussions_diff/file_collection_spec.rb b/spec/lib/gitlab/discussions_diff/file_collection_spec.rb new file mode 100644 index 00000000000..0489206458b --- /dev/null +++ b/spec/lib/gitlab/discussions_diff/file_collection_spec.rb @@ -0,0 +1,61 @@ +# frozen_string_literal: true + +require 'spec_helper' + +describe Gitlab::DiscussionsDiff::FileCollection do + let(:merge_request) { create(:merge_request) } + let!(:diff_note_a) { create(:diff_note_on_merge_request, project: merge_request.project, noteable: merge_request) } + let!(:diff_note_b) { create(:diff_note_on_merge_request, project: merge_request.project, noteable: merge_request) } + let(:note_diff_file_a) { diff_note_a.note_diff_file } + let(:note_diff_file_b) { diff_note_b.note_diff_file } + + subject { described_class.new([note_diff_file_a, note_diff_file_b]) } + + describe '#load_highlight', :clean_gitlab_redis_shared_state do + it 'writes uncached diffs highlight' do + file_a_caching_content = diff_note_a.diff_file.highlighted_diff_lines.map(&:to_hash) + file_b_caching_content = diff_note_b.diff_file.highlighted_diff_lines.map(&:to_hash) + + expect(Gitlab::DiscussionsDiff::HighlightCache) + .to receive(:write_multiple) + .with({ note_diff_file_a.id => file_a_caching_content, + note_diff_file_b.id => file_b_caching_content }) + .and_call_original + + subject.load_highlight([note_diff_file_a.id, note_diff_file_b.id]) + end + + it 'does not write cache for already cached file' do + subject.load_highlight([note_diff_file_a.id]) + + file_b_caching_content = diff_note_b.diff_file.highlighted_diff_lines.map(&:to_hash) + + expect(Gitlab::DiscussionsDiff::HighlightCache) + .to receive(:write_multiple) + .with({ note_diff_file_b.id => file_b_caching_content }) + .and_call_original + + subject.load_highlight([note_diff_file_a.id, note_diff_file_b.id]) + end + + it 'does not err when given ID does not exist in @collection' do + expect { subject.load_highlight([999]) }.not_to raise_error + end + + it 'loaded diff files have highlighted lines loaded' do + subject.load_highlight([note_diff_file_a.id]) + + diff_file = subject.find_by_id(note_diff_file_a.id) + + expect(diff_file.highlight_loaded?).to be(true) + end + + it 'not loaded diff files does not have highlighted lines loaded' do + subject.load_highlight([note_diff_file_a.id]) + + diff_file = subject.find_by_id(note_diff_file_b.id) + + expect(diff_file.highlight_loaded?).to be(false) + end + end +end diff --git a/spec/lib/gitlab/discussions_diff/highlight_cache_spec.rb b/spec/lib/gitlab/discussions_diff/highlight_cache_spec.rb new file mode 100644 index 00000000000..fe26ebb8796 --- /dev/null +++ b/spec/lib/gitlab/discussions_diff/highlight_cache_spec.rb @@ -0,0 +1,102 @@ +# frozen_string_literal: true + +require 'spec_helper' + +describe Gitlab::DiscussionsDiff::HighlightCache, :clean_gitlab_redis_cache do + describe '#write_multiple' do + it 'sets multiple keys serializing content as JSON' do + mapping = { + 3 => [ + { + text: 'foo', + type: 'new', + index: 2, + old_pos: 10, + new_pos: 11, + line_code: 'xpto', + rich_text: '<blips>blops</blips>' + }, + { + text: 'foo', + type: 'new', + index: 3, + old_pos: 11, + new_pos: 12, + line_code: 'xpto', + rich_text: '<blops>blips</blops>' + } + ] + } + + described_class.write_multiple(mapping) + + mapping.each do |key, value| + full_key = described_class.cache_key_for(key) + found = Gitlab::Redis::Cache.with { |r| r.get(full_key) } + + expect(found).to eq(value.to_json) + end + end + end + + describe '#read_multiple' do + it 'reads multiple keys and serializes content into Gitlab::Diff::Line objects' do + mapping = { + 3 => [ + { + text: 'foo', + type: 'new', + index: 2, + old_pos: 11, + new_pos: 12, + line_code: 'xpto', + rich_text: '<blips>blops</blips>' + }, + { + text: 'foo', + type: 'new', + index: 3, + old_pos: 10, + new_pos: 11, + line_code: 'xpto', + rich_text: '<blips>blops</blips>' + } + ] + } + + described_class.write_multiple(mapping) + + found = described_class.read_multiple(mapping.keys) + + expect(found.size).to eq(1) + expect(found.first.size).to eq(2) + expect(found.first).to all(be_a(Gitlab::Diff::Line)) + end + + it 'returns nil when cached key is not found' do + mapping = { + 3 => [ + { + text: 'foo', + type: 'new', + index: 2, + old_pos: 11, + new_pos: 12, + line_code: 'xpto', + rich_text: '<blips>blops</blips>' + } + ] + } + + described_class.write_multiple(mapping) + + found = described_class.read_multiple([2, 3]) + + expect(found.size).to eq(2) + + expect(found.first).to eq(nil) + expect(found.second.size).to eq(1) + expect(found.second).to all(be_a(Gitlab::Diff::Line)) + end + end +end diff --git a/spec/lib/gitlab/encoding_helper_spec.rb b/spec/lib/gitlab/encoding_helper_spec.rb index a5bf2f2b3df..429816efec3 100644 --- a/spec/lib/gitlab/encoding_helper_spec.rb +++ b/spec/lib/gitlab/encoding_helper_spec.rb @@ -124,7 +124,7 @@ describe Gitlab::EncodingHelper do end it 'returns empty string on conversion errors' do - expect { ext_class.encode_utf8('') }.not_to raise_error(ArgumentError) + expect { ext_class.encode_utf8('') }.not_to raise_error end context 'with strings that can be forcefully encoded into utf8' do diff --git a/spec/lib/gitlab/git/blob_spec.rb b/spec/lib/gitlab/git/blob_spec.rb index 80dd3dcc58e..1bcec04d28f 100644 --- a/spec/lib/gitlab/git/blob_spec.rb +++ b/spec/lib/gitlab/git/blob_spec.rb @@ -59,7 +59,7 @@ describe Gitlab::Git::Blob, :seed_helper do it { expect(blob.data[0..10]).to eq("*.rbc\n*.sas") } it { expect(blob.size).to eq(241) } it { expect(blob.mode).to eq("100644") } - it { expect(blob).not_to be_binary } + it { expect(blob).not_to be_binary_in_repo } end context 'file in root with leading slash' do @@ -92,7 +92,7 @@ describe Gitlab::Git::Blob, :seed_helper do end it 'does not mark the blob as binary' do - expect(blob).not_to be_binary + expect(blob).not_to be_binary_in_repo end end @@ -123,7 +123,7 @@ describe Gitlab::Git::Blob, :seed_helper do .with(hash_including(binary: true)) .and_call_original - expect(blob).to be_binary + expect(blob).to be_binary_in_repo end end end @@ -196,7 +196,7 @@ describe Gitlab::Git::Blob, :seed_helper do it { expect(blob.id).to eq('409f37c4f05865e4fb208c771485f211a22c4c2d') } it { expect(blob.data).to eq('') } it 'does not mark the blob as binary' do - expect(blob).not_to be_binary + expect(blob).not_to be_binary_in_repo end end diff --git a/spec/lib/gitlab/git/object_pool_spec.rb b/spec/lib/gitlab/git/object_pool_spec.rb index 363c2aa67af..0d5069568e1 100644 --- a/spec/lib/gitlab/git/object_pool_spec.rb +++ b/spec/lib/gitlab/git/object_pool_spec.rb @@ -56,16 +56,11 @@ describe Gitlab::Git::ObjectPool do describe '#link' do let!(:pool_repository) { create(:pool_repository, :ready) } - context 'when no remotes are set' do + context 'when linked for the first time' do it 'sets a remote' do - subject.link(source_repository) - - repo = Gitlab::GitalyClient::StorageSettings.allow_disk_access do - Rugged::Repository.new(subject.repository.path) - end - - expect(repo.remotes.count).to be(1) - expect(repo.remotes.first.name).to eq(source_repository.object_pool_remote_name) + expect do + subject.link(source_repository) + end.not_to raise_error end end @@ -75,14 +70,9 @@ describe Gitlab::Git::ObjectPool do end it "doesn't raise an error" do - subject.link(source_repository) - - repo = Gitlab::GitalyClient::StorageSettings.allow_disk_access do - Rugged::Repository.new(subject.repository.path) - end - - expect(repo.remotes.count).to be(1) - expect(repo.remotes.first.name).to eq(source_repository.object_pool_remote_name) + expect do + subject.link(source_repository) + end.not_to raise_error end end end diff --git a/spec/lib/gitlab/git/repository_cleaner_spec.rb b/spec/lib/gitlab/git/repository_cleaner_spec.rb index a9d9e67ef94..7f9cc2bc9ec 100644 --- a/spec/lib/gitlab/git/repository_cleaner_spec.rb +++ b/spec/lib/gitlab/git/repository_cleaner_spec.rb @@ -1,31 +1,61 @@ require 'spec_helper' describe Gitlab::Git::RepositoryCleaner do + include HttpIOHelpers + let(:project) { create(:project, :repository) } let(:repository) { project.repository } let(:head_sha) { repository.head_commit.id } - - let(:object_map) { StringIO.new("#{head_sha} #{'0' * 40}") } + let(:object_map_data) { "#{head_sha} #{'0' * 40}" } subject(:cleaner) { described_class.new(repository.raw) } describe '#apply_bfg_object_map' do - it 'removes internal references pointing at SHAs in the object map' do - # Create some refs we expect to be removed - repository.keep_around(head_sha) - repository.create_ref(head_sha, 'refs/environments/1') - repository.create_ref(head_sha, 'refs/merge-requests/1') - repository.create_ref(head_sha, 'refs/heads/_keep') - repository.create_ref(head_sha, 'refs/tags/_keep') - - cleaner.apply_bfg_object_map(object_map) - - aggregate_failures do - expect(repository.kept_around?(head_sha)).to be_falsy - expect(repository.ref_exists?('refs/environments/1')).to be_falsy - expect(repository.ref_exists?('refs/merge-requests/1')).to be_falsy - expect(repository.ref_exists?('refs/heads/_keep')).to be_truthy - expect(repository.ref_exists?('refs/tags/_keep')).to be_truthy + let(:clean_refs) { %W[refs/environments/1 refs/merge-requests/1 refs/keep-around/#{head_sha}] } + let(:keep_refs) { %w[refs/heads/_keep refs/tags/_keep] } + + before do + (clean_refs + keep_refs).each { |ref| repository.create_ref(head_sha, ref) } + end + + context 'from StringIO' do + let(:object_map) { StringIO.new(object_map_data) } + + it 'removes internal references' do + cleaner.apply_bfg_object_map(object_map) + + aggregate_failures do + clean_refs.each { |ref| expect(repository.ref_exists?(ref)).to be_falsy } + keep_refs.each { |ref| expect(repository.ref_exists?(ref)).to be_truthy } + end + end + end + + context 'from Gitlab::HttpIO' do + let(:url) { 'http://example.com/bfg_object_map.txt' } + let(:tempfile) { Tempfile.new } + let(:object_map) { Gitlab::HttpIO.new(url, object_map_data.size) } + + around do |example| + begin + tempfile.write(object_map_data) + tempfile.close + + example.run + ensure + tempfile.unlink + end + end + + it 'removes internal references' do + stub_remote_url_200(url, tempfile.path) + + cleaner.apply_bfg_object_map(object_map) + + aggregate_failures do + clean_refs.each { |ref| expect(repository.ref_exists?(ref)).to be_falsy } + keep_refs.each { |ref| expect(repository.ref_exists?(ref)).to be_truthy } + end end end end diff --git a/spec/lib/gitlab/git_access_spec.rb b/spec/lib/gitlab/git_access_spec.rb index a417ef77c9e..8d8eb50ad76 100644 --- a/spec/lib/gitlab/git_access_spec.rb +++ b/spec/lib/gitlab/git_access_spec.rb @@ -709,10 +709,22 @@ describe Gitlab::GitAccess do project.add_developer(user) end - it 'checks LFS integrity only for first change' do - expect_any_instance_of(Gitlab::Checks::LfsIntegrity).to receive(:objects_missing?).exactly(1).times + context 'when LFS is not enabled' do + it 'does not run LFSIntegrity check' do + expect(Gitlab::Checks::LfsIntegrity).not_to receive(:new) - push_access_check + push_access_check + end + end + + context 'when LFS is enabled' do + it 'checks LFS integrity only for first change' do + allow(project).to receive(:lfs_enabled?).and_return(true) + + expect_any_instance_of(Gitlab::Checks::LfsIntegrity).to receive(:objects_missing?).exactly(1).times + + push_access_check + end end end diff --git a/spec/lib/gitlab/gitaly_client/blobs_stitcher_spec.rb b/spec/lib/gitlab/gitaly_client/blobs_stitcher_spec.rb index 9db710e759e..742b2872c40 100644 --- a/spec/lib/gitlab/gitaly_client/blobs_stitcher_spec.rb +++ b/spec/lib/gitlab/gitaly_client/blobs_stitcher_spec.rb @@ -21,7 +21,7 @@ describe Gitlab::GitalyClient::BlobsStitcher do expect(blobs[0].size).to eq(1642) expect(blobs[0].commit_id).to eq('f00ba7') expect(blobs[0].data).to eq("first-line\nsecond-line") - expect(blobs[0].binary?).to be false + expect(blobs[0].binary_in_repo?).to be false expect(blobs[1].id).to eq('abcdef2') expect(blobs[1].mode).to eq('100644') @@ -30,7 +30,7 @@ describe Gitlab::GitalyClient::BlobsStitcher do expect(blobs[1].size).to eq(2461) expect(blobs[1].commit_id).to eq('f00ba8') expect(blobs[1].data).to eq("GIF87a\x90\x01".b) - expect(blobs[1].binary?).to be true + expect(blobs[1].binary_in_repo?).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 5eda4d041a8..e41a75c37a7 100644 --- a/spec/lib/gitlab/gitaly_client_spec.rb +++ b/spec/lib/gitlab/gitaly_client_spec.rb @@ -3,6 +3,20 @@ require 'spec_helper' # We stub Gitaly in `spec/support/gitaly.rb` for other tests. We don't want # those stubs while testing the GitalyClient itself. describe Gitlab::GitalyClient do + let(:sample_cert) { Rails.root.join('spec/fixtures/clusters/sample_cert.pem').to_s } + + before do + allow(described_class) + .to receive(:stub_cert_paths) + .and_return([sample_cert]) + end + + def stub_repos_storages(address) + allow(Gitlab.config.repositories).to receive(:storages).and_return({ + 'default' => { 'gitaly_address' => address } + }) + end + describe '.stub_class' do it 'returns the gRPC health check stub' do expect(described_class.stub_class(:health_check)).to eq(::Grpc::Health::V1::Health::Stub) @@ -15,12 +29,8 @@ describe Gitlab::GitalyClient do describe '.stub_address' do it 'returns the same result after being called multiple times' do - address = 'localhost:9876' - prefixed_address = "tcp://#{address}" - - allow(Gitlab.config.repositories).to receive(:storages).and_return({ - 'default' => { 'gitaly_address' => prefixed_address } - }) + address = 'tcp://localhost:9876' + stub_repos_storages address 2.times do expect(described_class.stub_address('default')).to eq('localhost:9876') @@ -28,6 +38,45 @@ describe Gitlab::GitalyClient do end end + describe '.stub_certs' do + it 'skips certificates if OpenSSLError is raised and report it' do + expect(Rails.logger).to receive(:error).at_least(:once) + expect(Gitlab::Sentry) + .to receive(:track_exception) + .with( + a_kind_of(OpenSSL::X509::CertificateError), + extra: { cert_file: a_kind_of(String) }).at_least(:once) + + expect(OpenSSL::X509::Certificate) + .to receive(:new) + .and_raise(OpenSSL::X509::CertificateError).at_least(:once) + + expect(described_class.stub_certs).to be_a(String) + end + end + describe '.stub_creds' do + it 'returns :this_channel_is_insecure if unix' do + address = 'unix:/tmp/gitaly.sock' + stub_repos_storages address + + expect(described_class.stub_creds('default')).to eq(:this_channel_is_insecure) + end + + it 'returns :this_channel_is_insecure if tcp' do + address = 'tcp://localhost:9876' + stub_repos_storages address + + expect(described_class.stub_creds('default')).to eq(:this_channel_is_insecure) + end + + it 'returns Credentials object if tls' do + address = 'tls://localhost:9876' + stub_repos_storages address + + expect(described_class.stub_creds('default')).to be_a(GRPC::Core::ChannelCredentials) + end + end + describe '.stub' do # Notice that this is referring to gRPC "stubs", not rspec stubs before do @@ -37,9 +86,19 @@ describe Gitlab::GitalyClient do context 'when passed a UNIX socket address' do it 'passes the address as-is to GRPC' do address = 'unix:/tmp/gitaly.sock' - allow(Gitlab.config.repositories).to receive(:storages).and_return({ - 'default' => { 'gitaly_address' => address } - }) + stub_repos_storages address + + expect(Gitaly::CommitService::Stub).to receive(:new).with(address, any_args) + + described_class.stub(:commit_service, 'default') + end + end + + context 'when passed a TLS address' do + it 'strips tls:// prefix before passing it to GRPC::Core::Channel initializer' do + address = 'localhost:9876' + prefixed_address = "tls://#{address}" + stub_repos_storages prefixed_address expect(Gitaly::CommitService::Stub).to receive(:new).with(address, any_args) @@ -51,10 +110,7 @@ describe Gitlab::GitalyClient do it 'strips tcp:// prefix before passing it to GRPC::Core::Channel initializer' do address = 'localhost:9876' prefixed_address = "tcp://#{address}" - - allow(Gitlab.config.repositories).to receive(:storages).and_return({ - 'default' => { 'gitaly_address' => prefixed_address } - }) + stub_repos_storages prefixed_address expect(Gitaly::CommitService::Stub).to receive(:new).with(address, any_args) diff --git a/spec/lib/gitlab/github_import/importer/pull_request_importer_spec.rb b/spec/lib/gitlab/github_import/importer/pull_request_importer_spec.rb index efca8564894..25684ea9e2c 100644 --- a/spec/lib/gitlab/github_import/importer/pull_request_importer_spec.rb +++ b/spec/lib/gitlab/github_import/importer/pull_request_importer_spec.rb @@ -240,12 +240,7 @@ describe Gitlab::GithubImport::Importer::PullRequestImporter, :clean_gitlab_redi .and_return(user.id) end - # TODO: remove rails5-only after removing rails4 tests - # rails 4 can not handle multiple indexes on the same column set if - # index was added by 't.index' - t.index is used by default in schema.rb in - # rails 5. Let's run this test only in rails 5 env: - # see https://gitlab.com/gitlab-org/gitlab-ce/merge_requests/21492#note_113602758 - it 'returns the existing merge request', :rails5 do + it 'returns the existing merge request' do mr1, exists1 = importer.create_merge_request mr2, exists2 = importer.create_merge_request diff --git a/spec/lib/gitlab/gpg_spec.rb b/spec/lib/gitlab/gpg_spec.rb index 39d09c49989..48bbd7f854c 100644 --- a/spec/lib/gitlab/gpg_spec.rb +++ b/spec/lib/gitlab/gpg_spec.rb @@ -137,7 +137,7 @@ describe Gitlab::Gpg do described_class.using_tmp_keychain do end end - end.not_to raise_error(ThreadError) + end.not_to raise_error end end end diff --git a/spec/lib/gitlab/import_export/all_models.yml b/spec/lib/gitlab/import_export/all_models.yml index bae5b21c26f..c8c74883640 100644 --- a/spec/lib/gitlab/import_export/all_models.yml +++ b/spec/lib/gitlab/import_export/all_models.yml @@ -37,6 +37,7 @@ notes: - events - system_note_metadata - note_diff_file +- suggestions label_links: - target - label @@ -63,6 +64,7 @@ snippets: - award_emoji - user_agent_detail releases: +- author - project project_members: - created_by diff --git a/spec/lib/gitlab/import_export/command_line_util_spec.rb b/spec/lib/gitlab/import_export/command_line_util_spec.rb new file mode 100644 index 00000000000..8e5e0aefac0 --- /dev/null +++ b/spec/lib/gitlab/import_export/command_line_util_spec.rb @@ -0,0 +1,38 @@ +# frozen_string_literal: true + +require 'spec_helper' + +describe Gitlab::ImportExport::CommandLineUtil do + include ExportFileHelper + + let(:path) { "#{Dir.tmpdir}/symlink_test" } + let(:archive) { 'spec/fixtures/symlink_export.tar.gz' } + let(:shared) { Gitlab::ImportExport::Shared.new(nil) } + + subject do + Class.new do + include Gitlab::ImportExport::CommandLineUtil + + def initialize + @shared = Gitlab::ImportExport::Shared.new(nil) + end + end.new + end + + before do + FileUtils.mkdir_p(path) + subject.untar_zxf(archive: archive, dir: path) + end + + after do + FileUtils.rm_rf(path) + end + + it 'has the right mask for project.json' do + expect(file_permissions("#{path}/project.json")).to eq(0755) # originally 777 + end + + it 'has the right mask for uploads' do + expect(file_permissions("#{path}/uploads")).to eq(0755) # originally 555 + end +end diff --git a/spec/lib/gitlab/import_export/file_importer_spec.rb b/spec/lib/gitlab/import_export/file_importer_spec.rb index bf34cefe18f..fbc9bcd2df5 100644 --- a/spec/lib/gitlab/import_export/file_importer_spec.rb +++ b/spec/lib/gitlab/import_export/file_importer_spec.rb @@ -1,6 +1,8 @@ require 'spec_helper' describe Gitlab::ImportExport::FileImporter do + include ExportFileHelper + let(:shared) { Gitlab::ImportExport::Shared.new(nil) } let(:storage_path) { "#{Dir.tmpdir}/file_importer_spec" } let(:valid_file) { "#{shared.export_path}/valid.json" } @@ -8,6 +10,7 @@ describe Gitlab::ImportExport::FileImporter do let(:hidden_symlink_file) { "#{shared.export_path}/.hidden" } let(:subfolder_symlink_file) { "#{shared.export_path}/subfolder/invalid.json" } let(:evil_symlink_file) { "#{shared.export_path}/.\nevil" } + let(:custom_mode_symlink_file) { "#{shared.export_path}/symlink.mode" } before do stub_const('Gitlab::ImportExport::FileImporter::MAX_RETRIES', 0) @@ -45,10 +48,18 @@ describe Gitlab::ImportExport::FileImporter do expect(File.exist?(subfolder_symlink_file)).to be false end + it 'removes symlinks without any file permissions' do + expect(File.exist?(custom_mode_symlink_file)).to be false + end + it 'does not remove a valid file' do expect(File.exist?(valid_file)).to be true end + it 'does not change a valid file permissions' do + expect(file_permissions(valid_file)).not_to eq(0000) + end + it 'creates the file in the right subfolder' do expect(shared.export_path).to include('test/abcd') end @@ -84,5 +95,7 @@ describe Gitlab::ImportExport::FileImporter do FileUtils.ln_s(valid_file, subfolder_symlink_file) FileUtils.ln_s(valid_file, hidden_symlink_file) FileUtils.ln_s(valid_file, evil_symlink_file) + FileUtils.ln_s(valid_file, custom_mode_symlink_file) + FileUtils.chmod_R(0000, custom_mode_symlink_file) end end diff --git a/spec/lib/gitlab/import_export/safe_model_attributes.yml b/spec/lib/gitlab/import_export/safe_model_attributes.yml index d3bfde181bc..24b1f2d995b 100644 --- a/spec/lib/gitlab/import_export/safe_model_attributes.yml +++ b/spec/lib/gitlab/import_export/safe_model_attributes.yml @@ -112,8 +112,11 @@ ProjectSnippet: - visibility_level Release: - id +- name - tag +- sha - description +- author_id - project_id - created_at - updated_at diff --git a/spec/lib/gitlab/json_cache_spec.rb b/spec/lib/gitlab/json_cache_spec.rb new file mode 100644 index 00000000000..b52078e8556 --- /dev/null +++ b/spec/lib/gitlab/json_cache_spec.rb @@ -0,0 +1,401 @@ +# frozen_string_literal: true + +require 'spec_helper' + +describe Gitlab::JsonCache do + let(:backend) { double('backend').as_null_object } + let(:namespace) { 'geo' } + let(:key) { 'foo' } + let(:expanded_key) { "#{namespace}:#{key}:#{Rails.version}" } + let(:broadcast_message) { create(:broadcast_message) } + + subject(:cache) { described_class.new(namespace: namespace, backend: backend) } + + describe '#active?' do + context 'when backend respond to active? method' do + it 'delegates to the underlying cache implementation' do + backend = double('backend', active?: false) + + cache = described_class.new(namespace: namespace, backend: backend) + + expect(cache.active?).to eq(false) + end + end + + context 'when backend does not respond to active? method' do + it 'returns true' do + backend = double('backend') + + cache = described_class.new(namespace: namespace, backend: backend) + + expect(cache.active?).to eq(true) + end + end + end + + describe '#cache_key' do + context 'when namespace is not defined' do + it 'expands out the key with Rails version' do + cache = described_class.new(cache_key_with_version: true) + + cache_key = cache.cache_key(key) + + expect(cache_key).to eq("#{key}:#{Rails.version}") + end + end + + context 'when cache_key_with_version is true' do + it 'expands out the key with namespace and Rails version' do + cache = described_class.new(namespace: namespace, cache_key_with_version: true) + + cache_key = cache.cache_key(key) + + expect(cache_key).to eq("#{namespace}:#{key}:#{Rails.version}") + end + end + + context 'when cache_key_with_version is false' do + it 'expands out the key with namespace' do + cache = described_class.new(namespace: namespace, cache_key_with_version: false) + + cache_key = cache.cache_key(key) + + expect(cache_key).to eq("#{namespace}:#{key}") + end + end + + context 'when namespace is nil, and cache_key_with_version is false' do + it 'returns the key' do + cache = described_class.new(namespace: nil, cache_key_with_version: false) + + cache_key = cache.cache_key(key) + + expect(cache_key).to eq(key) + end + end + end + + describe '#expire' do + it 'expires the given key from the cache' do + cache.expire(key) + + expect(backend).to have_received(:delete).with(expanded_key) + end + end + + describe '#read' do + it 'reads the given key from the cache' do + cache.read(key) + + expect(backend).to have_received(:read).with(expanded_key) + end + + it 'returns the cached value when there is data in the cache with the given key' do + allow(backend).to receive(:read) + .with(expanded_key) + .and_return("true") + + expect(cache.read(key)).to eq(true) + end + + it 'returns nil when there is no data in the cache with the given key' do + allow(backend).to receive(:read) + .with(expanded_key) + .and_return(nil) + + expect(cache.read(key)).to be_nil + end + + context 'when the cached value is a hash' do + it 'parses the cached value' do + allow(backend).to receive(:read) + .with(expanded_key) + .and_return(broadcast_message.to_json) + + expect(cache.read(key, BroadcastMessage)).to eq(broadcast_message) + end + + it 'returns nil when klass is nil' do + allow(backend).to receive(:read) + .with(expanded_key) + .and_return(broadcast_message.to_json) + + expect(cache.read(key)).to be_nil + end + + it 'gracefully handles bad cached entry' do + allow(backend).to receive(:read) + .with(expanded_key) + .and_return('{') + + expect(cache.read(key, BroadcastMessage)).to be_nil + end + + it 'gracefully handles an empty hash' do + allow(backend).to receive(:read) + .with(expanded_key) + .and_return('{}') + + expect(cache.read(key, BroadcastMessage)).to be_a(BroadcastMessage) + end + + it 'gracefully handles unknown attributes' do + allow(backend).to receive(:read) + .with(expanded_key) + .and_return(broadcast_message.attributes.merge(unknown_attribute: 1).to_json) + + expect(cache.read(key, BroadcastMessage)).to be_nil + end + end + + context 'when the cached value is an array' do + it 'parses the cached value' do + allow(backend).to receive(:read) + .with(expanded_key) + .and_return([broadcast_message].to_json) + + expect(cache.read(key, BroadcastMessage)).to eq([broadcast_message]) + end + + it 'returns an empty array when klass is nil' do + allow(backend).to receive(:read) + .with(expanded_key) + .and_return([broadcast_message].to_json) + + expect(cache.read(key)).to eq([]) + end + + it 'gracefully handles bad cached entry' do + allow(backend).to receive(:read) + .with(expanded_key) + .and_return('[') + + expect(cache.read(key, BroadcastMessage)).to be_nil + end + + it 'gracefully handles an empty array' do + allow(backend).to receive(:read) + .with(expanded_key) + .and_return('[]') + + expect(cache.read(key, BroadcastMessage)).to eq([]) + end + + it 'gracefully handles unknown attributes' do + allow(backend).to receive(:read) + .with(expanded_key) + .and_return([{ unknown_attribute: 1 }, broadcast_message.attributes].to_json) + + expect(cache.read(key, BroadcastMessage)).to eq([broadcast_message]) + end + end + end + + describe '#write' do + it 'writes value to the cache with the given key' do + cache.write(key, true) + + expect(backend).to have_received(:write).with(expanded_key, "true", nil) + end + + it 'writes a string containing a JSON representation of the value to the cache' do + cache.write(key, broadcast_message) + + expect(backend).to have_received(:write) + .with(expanded_key, broadcast_message.to_json, nil) + end + + it 'passes options the underlying cache implementation' do + cache.write(key, true, expires_in: 15.seconds) + + expect(backend).to have_received(:write) + .with(expanded_key, "true", expires_in: 15.seconds) + end + + it 'passes options the underlying cache implementation when options is empty' do + cache.write(key, true, {}) + + expect(backend).to have_received(:write) + .with(expanded_key, "true", {}) + end + + it 'passes options the underlying cache implementation when options is nil' do + cache.write(key, true, nil) + + expect(backend).to have_received(:write) + .with(expanded_key, "true", nil) + end + end + + describe '#fetch', :use_clean_rails_memory_store_caching do + let(:backend) { Rails.cache } + + it 'requires a block' do + expect { cache.fetch(key) }.to raise_error(LocalJumpError) + end + + it 'passes options the underlying cache implementation' do + expect(backend).to receive(:write) + .with(expanded_key, "true", expires_in: 15.seconds) + + cache.fetch(key, expires_in: 15.seconds) { true } + end + + context 'when the given key does not exist in the cache' do + context 'when the result of the block is truthy' do + it 'returns the result of the block' do + result = cache.fetch(key) { true } + + expect(result).to eq(true) + end + + it 'caches the value' do + expect(backend).to receive(:write).with(expanded_key, "true", {}) + + cache.fetch(key) { true } + end + end + + context 'when the result of the block is false' do + it 'returns the result of the block' do + result = cache.fetch(key) { false } + + expect(result).to eq(false) + end + + it 'caches the value' do + expect(backend).to receive(:write).with(expanded_key, "false", {}) + + cache.fetch(key) { false } + end + end + + context 'when the result of the block is nil' do + it 'returns the result of the block' do + result = cache.fetch(key) { nil } + + expect(result).to eq(nil) + end + + it 'caches the value' do + expect(backend).to receive(:write).with(expanded_key, "null", {}) + + cache.fetch(key) { nil } + end + end + end + + context 'when the given key exists in the cache' do + context 'when the cached value is a hash' do + before do + backend.write(expanded_key, broadcast_message.to_json) + end + + it 'parses the cached value' do + result = cache.fetch(key, as: BroadcastMessage) { 'block result' } + + expect(result).to eq(broadcast_message) + end + + it "returns the result of the block when 'as' option is nil" do + result = cache.fetch(key, as: nil) { 'block result' } + + expect(result).to eq('block result') + end + + it "returns the result of the block when 'as' option is not informed" do + result = cache.fetch(key) { 'block result' } + + expect(result).to eq('block result') + end + end + + context 'when the cached value is a array' do + before do + backend.write(expanded_key, [broadcast_message].to_json) + end + + it 'parses the cached value' do + result = cache.fetch(key, as: BroadcastMessage) { 'block result' } + + expect(result).to eq([broadcast_message]) + end + + it "returns an empty array when 'as' option is nil" do + result = cache.fetch(key, as: nil) { 'block result' } + + expect(result).to eq([]) + end + + it "returns an empty array when 'as' option is not informed" do + result = cache.fetch(key) { 'block result' } + + expect(result).to eq([]) + end + end + + context 'when the cached value is true' do + before do + backend.write(expanded_key, "true") + end + + it 'returns the cached value' do + result = cache.fetch(key) { 'block result' } + + expect(result).to eq(true) + end + + it 'does not execute the block' do + expect { |block| cache.fetch(key, &block) }.not_to yield_control + end + + it 'does not write to the cache' do + expect(backend).not_to receive(:write) + + cache.fetch(key) { 'block result' } + end + end + + context 'when the cached value is false' do + before do + backend.write(expanded_key, "false") + end + + it 'returns the cached value' do + result = cache.fetch(key) { 'block result' } + + expect(result).to eq(false) + end + + it 'does not execute the block' do + expect { |block| cache.fetch(key, &block) }.not_to yield_control + end + + it 'does not write to the cache' do + expect(backend).not_to receive(:write) + + cache.fetch(key) { 'block result' } + end + end + + context 'when the cached value is nil' do + before do + backend.write(expanded_key, "null") + end + + it 'returns the result of the block' do + result = cache.fetch(key) { 'block result' } + + expect(result).to eq('block result') + end + + it 'writes the result of the block to the cache' do + expect(backend).to receive(:write) + .with(expanded_key, 'block result'.to_json, {}) + + cache.fetch(key) { 'block result' } + end + end + end + end +end diff --git a/spec/lib/gitlab/legacy_github_import/importer_spec.rb b/spec/lib/gitlab/legacy_github_import/importer_spec.rb index d2df21d7bb5..6bc3792eb22 100644 --- a/spec/lib/gitlab/legacy_github_import/importer_spec.rb +++ b/spec/lib/gitlab/legacy_github_import/importer_spec.rb @@ -138,7 +138,7 @@ describe Gitlab::LegacyGithubImport::Importer do let(:release2) do double( - tag_name: 'v2.0.0', + tag_name: 'v1.1.0', name: 'Second release', body: nil, draft: false, diff --git a/spec/lib/gitlab/lfs_token_spec.rb b/spec/lib/gitlab/lfs_token_spec.rb index 77ee30264bf..1ec1ba19e39 100644 --- a/spec/lib/gitlab/lfs_token_spec.rb +++ b/spec/lib/gitlab/lfs_token_spec.rb @@ -1,50 +1,187 @@ +# frozen_string_literal: true + require 'spec_helper' -describe Gitlab::LfsToken do +describe Gitlab::LfsToken, :clean_gitlab_redis_shared_state do describe '#token' do shared_examples 'an LFS token generator' do - it 'returns a randomly generated token' do - token = handler.token + it 'returns a computed token' do + expect(Settings).to receive(:attr_encrypted_db_key_base).and_return('fbnbv6hdjweo53qka7kza8v8swxc413c05pb51qgtfte0bygh1p2e508468hfsn5ntmjcyiz7h1d92ashpet5pkdyejg7g8or3yryhuso4h8o5c73h429d9c3r6bjnet').twice + + token = lfs_token.token expect(token).not_to be_nil expect(token).to be_a String - expect(token.length).to eq 50 + expect(described_class.new(actor).token_valid?(token)).to be_truthy + end + end + + context 'when the actor is a user' do + let(:actor) { create(:user, username: 'test_user_lfs_1') } + let(:lfs_token) { described_class.new(actor) } + + before do + allow(actor).to receive(:encrypted_password).and_return('$2a$04$ETfzVS5spE9Hexn9wh6NUenCHG1LyZ2YdciOYxieV1WLSa8DHqOFO') end - it 'returns the correct token based on the key' do - token = handler.token + it_behaves_like 'an LFS token generator' + + it 'returns the correct username' do + expect(lfs_token.actor_name).to eq(actor.username) + end - expect(handler.token).to eq(token) + it 'returns the correct token type' do + expect(lfs_token.type).to eq(:lfs_token) end end - context 'when the actor is a user' do - let(:actor) { create(:user) } - let(:handler) { described_class.new(actor) } + context 'when the actor is a key' do + let(:user) { create(:user, username: 'test_user_lfs_2') } + let(:actor) { create(:key, user: user) } + let(:lfs_token) { described_class.new(actor) } + + before do + allow(user).to receive(:encrypted_password).and_return('$2a$04$C1GAMKsOKouEbhKy2JQoe./3LwOfQAZc.hC8zW2u/wt8xgukvnlV.') + end it_behaves_like 'an LFS token generator' it 'returns the correct username' do - expect(handler.actor_name).to eq(actor.username) + expect(lfs_token.actor_name).to eq(user.username) end it 'returns the correct token type' do - expect(handler.type).to eq(:lfs_token) + expect(lfs_token.type).to eq(:lfs_token) end end context 'when the actor is a deploy key' do + let(:actor_id) { 1 } let(:actor) { create(:deploy_key) } - let(:handler) { described_class.new(actor) } + let(:project) { create(:project) } + let(:lfs_token) { described_class.new(actor) } + + before do + allow(actor).to receive(:id).and_return(actor_id) + end it_behaves_like 'an LFS token generator' it 'returns the correct username' do - expect(handler.actor_name).to eq("lfs+deploy-key-#{actor.id}") + expect(lfs_token.actor_name).to eq("lfs+deploy-key-#{actor_id}") end it 'returns the correct token type' do - expect(handler.type).to eq(:lfs_deploy_token) + expect(lfs_token.type).to eq(:lfs_deploy_token) + end + end + + context 'when the actor is invalid' do + it 'raises an exception' do + expect { described_class.new('invalid') }.to raise_error('Bad Actor') + end + end + end + + describe '#token_valid?' do + let(:actor) { create(:user, username: 'test_user_lfs_1') } + let(:lfs_token) { described_class.new(actor) } + + before do + allow(actor).to receive(:encrypted_password).and_return('$2a$04$ETfzVS5spE9Hexn9wh6NUenCHG1LyZ2YdciOYxieV1WLSa8DHqOFO') + end + + context 'for an HMAC token' do + before do + # We're not interested in testing LegacyRedisDeviseToken here + allow(Gitlab::LfsToken::LegacyRedisDeviseToken).to receive_message_chain(:new, :token_valid?).and_return(false) + end + + context 'where the token is invalid' do + context "because it's junk" do + it 'returns false' do + expect(lfs_token.token_valid?('junk')).to be_falsey + end + end + + context "because it's been fiddled with" do + it 'returns false' do + fiddled_token = lfs_token.token.tap { |token| token[0] = 'E' } + expect(lfs_token.token_valid?(fiddled_token)).to be_falsey + end + end + + context "because it was generated with a different secret" do + it 'returns false' do + different_actor = create(:user, username: 'test_user_lfs_2') + different_secret_token = described_class.new(different_actor).token + expect(lfs_token.token_valid?(different_secret_token)).to be_falsey + end + end + + context "because it's expired" do + it 'returns false' do + expired_token = lfs_token.token + # Needs to be at least 1860 seconds, because the default expiry is + # 1800 seconds with an additional 60 second leeway. + Timecop.freeze(Time.now + 1865) do + expect(lfs_token.token_valid?(expired_token)).to be_falsey + end + end + end + end + + context 'where the token is valid' do + it 'returns true' do + expect(lfs_token.token_valid?(lfs_token.token)).to be_truthy + end + end + end + + context 'for a LegacyRedisDevise token' do + before do + # We're not interested in testing HMACToken here + allow_any_instance_of(Gitlab::LfsToken::HMACToken).to receive(:token_valid?).and_return(false) + end + + context 'where the token is invalid' do + context "because it's junk" do + it 'returns false' do + expect(lfs_token.token_valid?('junk')).to be_falsey + end + end + + context "because it's been fiddled with" do + it 'returns false' do + generated_token = Gitlab::LfsToken::LegacyRedisDeviseToken.new(actor).store_new_token + fiddled_token = generated_token.tap { |token| token[0] = 'E' } + expect(lfs_token.token_valid?(fiddled_token)).to be_falsey + end + end + + context "because it was generated with a different secret" do + it 'returns false' do + different_actor = create(:user, username: 'test_user_lfs_2') + different_secret_token = described_class.new(different_actor).token + expect(lfs_token.token_valid?(different_secret_token)).to be_falsey + end + end + + context "because it's expired" do + it 'returns false' do + generated_token = Gitlab::LfsToken::LegacyRedisDeviseToken.new(actor).store_new_token(1) + # We need a real sleep here because we need to wait for redis to expire the key. + sleep(0.01) + expect(lfs_token.token_valid?(generated_token)).to be_falsey + end + end + end + + context 'where the token is valid' do + it 'returns true' do + generated_token = Gitlab::LfsToken::LegacyRedisDeviseToken.new(actor).store_new_token + expect(lfs_token.token_valid?(generated_token)).to be_truthy + end end end end diff --git a/spec/lib/gitlab/middleware/go_spec.rb b/spec/lib/gitlab/middleware/go_spec.rb index 7a3a9ab875b..f52095bf633 100644 --- a/spec/lib/gitlab/middleware/go_spec.rb +++ b/spec/lib/gitlab/middleware/go_spec.rb @@ -96,43 +96,36 @@ describe Gitlab::Middleware::Go do it_behaves_like 'unauthorized' end - end - - context 'using warden' do - before do - env['warden'] = double(authenticate: current_user) - end - context 'when active' do - it_behaves_like 'authenticated' - end - - context 'when blocked' do + context 'with user is blocked' do before do - current_user.block! + current_user.block end it_behaves_like 'unauthorized' end end - context 'using a personal access token' do - let(:personal_access_token) { create(:personal_access_token, user: current_user) } - - before do - env['HTTP_PRIVATE_TOKEN'] = personal_access_token.token - end - - context 'with api scope' do - it_behaves_like 'authenticated' - end + context 'using basic auth' do + context 'using a personal access token' do + let(:personal_access_token) { create(:personal_access_token, user: current_user) } - context 'with read_user scope' do before do - personal_access_token.update_attribute(:scopes, [:read_user]) + env['REMOTE_ADDR'] = "192.168.0.1" + env['HTTP_AUTHORIZATION'] = ActionController::HttpAuthentication::Basic.encode_credentials(current_user.username, personal_access_token.token) end - it_behaves_like 'unauthorized' + context 'with api scope' do + it_behaves_like 'authenticated' + end + + context 'with read_user scope' do + before do + personal_access_token.update_attribute(:scopes, [:read_user]) + end + + it_behaves_like 'unauthorized' + end end end end diff --git a/spec/lib/gitlab/middleware/read_only_spec.rb b/spec/lib/gitlab/middleware/read_only_spec.rb index ac3bc6b2dfe..bdb1f34d2f6 100644 --- a/spec/lib/gitlab/middleware/read_only_spec.rb +++ b/spec/lib/gitlab/middleware/read_only_spec.rb @@ -8,7 +8,6 @@ describe Gitlab::Middleware::ReadOnly do rack = Rack::Builder.new do use ActionDispatch::Session::CacheStore use ActionDispatch::Flash - use ActionDispatch::ParamsParser end rack.run(subject) diff --git a/spec/lib/gitlab/group_hierarchy_spec.rb b/spec/lib/gitlab/object_hierarchy_spec.rb index f3de7adcec7..4700a7ad2e1 100644 --- a/spec/lib/gitlab/group_hierarchy_spec.rb +++ b/spec/lib/gitlab/object_hierarchy_spec.rb @@ -1,6 +1,6 @@ require 'spec_helper' -describe Gitlab::GroupHierarchy, :postgresql do +describe Gitlab::ObjectHierarchy, :postgresql do let!(:parent) { create(:group) } let!(:child1) { create(:group, parent: parent) } let!(:child2) { create(:group, parent: child1) } @@ -105,9 +105,9 @@ describe Gitlab::GroupHierarchy, :postgresql do end end - describe '#all_groups' do + describe '#all_objects' do let(:relation) do - described_class.new(Group.where(id: child1.id)).all_groups + described_class.new(Group.where(id: child1.id)).all_objects end it 'includes the base rows' do @@ -123,13 +123,13 @@ describe Gitlab::GroupHierarchy, :postgresql do end it 'uses ancestors_base #initialize argument for ancestors' do - relation = described_class.new(Group.where(id: child1.id), Group.where(id: Group.maximum(:id).succ)).all_groups + relation = described_class.new(Group.where(id: child1.id), Group.where(id: Group.maximum(:id).succ)).all_objects expect(relation).to include(parent) end it 'uses descendants_base #initialize argument for descendants' do - relation = described_class.new(Group.where(id: Group.maximum(:id).succ), Group.where(id: child1.id)).all_groups + relation = described_class.new(Group.where(id: Group.maximum(:id).succ), Group.where(id: child1.id)).all_objects expect(relation).to include(child2) end diff --git a/spec/lib/gitlab/project_authorizations_spec.rb b/spec/lib/gitlab/project_authorizations_spec.rb index 00c62c7bf96..bd0bc2c9044 100644 --- a/spec/lib/gitlab/project_authorizations_spec.rb +++ b/spec/lib/gitlab/project_authorizations_spec.rb @@ -20,7 +20,7 @@ describe Gitlab::ProjectAuthorizations do end let(:authorizations) do - klass = if Group.supports_nested_groups? + klass = if Group.supports_nested_objects? Gitlab::ProjectAuthorizations::WithNestedGroups else Gitlab::ProjectAuthorizations::WithoutNestedGroups @@ -46,7 +46,7 @@ describe Gitlab::ProjectAuthorizations do expect(mapping[group_project.id]).to eq(Gitlab::Access::DEVELOPER) end - if Group.supports_nested_groups? + if Group.supports_nested_objects? context 'with nested groups' do let!(:nested_group) { create(:group, parent: group) } let!(:nested_project) { create(:project, namespace: nested_group) } diff --git a/spec/lib/gitlab/prometheus/metric_group_spec.rb b/spec/lib/gitlab/prometheus/metric_group_spec.rb index e7d16e73663..5cc6827488b 100644 --- a/spec/lib/gitlab/prometheus/metric_group_spec.rb +++ b/spec/lib/gitlab/prometheus/metric_group_spec.rb @@ -21,6 +21,13 @@ describe Gitlab::Prometheus::MetricGroup do common_metric_group_a.id, common_metric_group_b_q1.id, common_metric_group_b_q2.id) end + + it 'orders by priority' do + priorities = subject.map(&:priority) + names = subject.map(&:name) + expect(priorities).to eq([10, 5]) + expect(names).to eq(['Response metrics (AWS ELB)', 'System metrics (Kubernetes)']) + end end describe '.for_project' do diff --git a/spec/lib/gitlab/prometheus/query_variables_spec.rb b/spec/lib/gitlab/prometheus/query_variables_spec.rb index 78974cadb69..78c74266c61 100644 --- a/spec/lib/gitlab/prometheus/query_variables_spec.rb +++ b/spec/lib/gitlab/prometheus/query_variables_spec.rb @@ -4,7 +4,7 @@ require 'spec_helper' describe Gitlab::Prometheus::QueryVariables do describe '.call' do - set(:environment) { create(:environment) } + let(:environment) { create(:environment) } let(:slug) { environment.slug } subject { described_class.call(environment) } @@ -20,7 +20,7 @@ describe Gitlab::Prometheus::QueryVariables do it { is_expected.to include(kube_namespace: '') } end - context 'with deplyoment platform' do + context 'with deployment platform' do let(:kube_namespace) { environment.deployment_platform.actual_namespace } before do diff --git a/spec/lib/gitlab/safe_request_store_spec.rb b/spec/lib/gitlab/safe_request_store_spec.rb index c797171dbe2..bae87e43615 100644 --- a/spec/lib/gitlab/safe_request_store_spec.rb +++ b/spec/lib/gitlab/safe_request_store_spec.rb @@ -78,6 +78,12 @@ describe Gitlab::SafeRequestStore do described_class.write('foo', true) end.to change { described_class.read('foo') }.from(nil).to(true) end + + it 'does not pass the options hash to the underlying store implementation' do + expect(described_class.store).to receive(:write).with('foo', true) + + described_class.write('foo', true, expires_in: 15.seconds) + end end context 'when RequestStore is NOT active' do @@ -86,6 +92,12 @@ describe Gitlab::SafeRequestStore do described_class.write('foo', true) end.not_to change { described_class.read('foo') }.from(nil) end + + it 'does not pass the options hash to the underlying store implementation' do + expect(described_class.store).to receive(:write).with('foo', true) + + described_class.write('foo', true, expires_in: 15.seconds) + end end end diff --git a/spec/lib/gitlab/sql/glob_spec.rb b/spec/lib/gitlab/sql/glob_spec.rb index 1cf8935bfe3..3147b52dcc5 100644 --- a/spec/lib/gitlab/sql/glob_spec.rb +++ b/spec/lib/gitlab/sql/glob_spec.rb @@ -35,9 +35,8 @@ describe Gitlab::SQL::Glob do value = query("SELECT #{quote(string)} LIKE #{pattern}") .rows.flatten.first - check = Gitlab.rails5? ? true : 't' case value - when check, 1 + when true, 1 true else false diff --git a/spec/lib/gitlab/upgrader_spec.rb b/spec/lib/gitlab/upgrader_spec.rb deleted file mode 100644 index 6106f13c774..00000000000 --- a/spec/lib/gitlab/upgrader_spec.rb +++ /dev/null @@ -1,40 +0,0 @@ -require 'spec_helper' - -describe Gitlab::Upgrader do - let(:upgrader) { described_class.new } - let(:current_version) { Gitlab::VERSION } - - describe 'current_version_raw' do - it { expect(upgrader.current_version_raw).to eq(current_version) } - end - - describe 'latest_version?' do - it 'is true if newest version' do - allow(upgrader).to receive(:latest_version_raw).and_return(current_version) - expect(upgrader.latest_version?).to be_truthy - end - end - - describe 'latest_version_raw' do - it 'is the latest version for GitLab 5' do - allow(upgrader).to receive(:current_version_raw).and_return("5.3.0") - expect(upgrader.latest_version_raw).to eq("v5.4.2") - end - - it 'gets the latest version from tags' do - allow(upgrader).to receive(:fetch_git_tags).and_return([ - '6f0733310546402c15d3ae6128a95052f6c8ea96 refs/tags/v7.1.1', - 'facfec4b242ce151af224e20715d58e628aa5e74 refs/tags/v7.1.1^{}', - 'f7068d99c79cf79befbd388030c051bb4b5e86d4 refs/tags/v7.10.4', - '337225a4fcfa9674e2528cb6d41c46556bba9dfa refs/tags/v7.10.4^{}', - '880e0ba0adbed95d087f61a9a17515e518fc6440 refs/tags/v7.11.1', - '6584346b604f981f00af8011cd95472b2776d912 refs/tags/v7.11.1^{}', - '43af3e65a486a9237f29f56d96c3b3da59c24ae0 refs/tags/v7.11.2', - 'dac18e7728013a77410e926a1e64225703754a2d refs/tags/v7.11.2^{}', - '0bf21fd4b46c980c26fd8c90a14b86a4d90cc950 refs/tags/v7.9.4', - 'b10de29edbaff7219547dc506cb1468ee35065c3 refs/tags/v7.9.4^{}' - ]) - expect(upgrader.latest_version_raw).to eq("v7.11.2") - end - end -end diff --git a/spec/lib/gitlab/usage_data_spec.rb b/spec/lib/gitlab/usage_data_spec.rb index deb19fe1a4b..2a09f581f68 100644 --- a/spec/lib/gitlab/usage_data_spec.rb +++ b/spec/lib/gitlab/usage_data_spec.rb @@ -117,6 +117,7 @@ describe Gitlab::UsageData do releases remote_mirrors snippets + suggestions todos uploads web_hooks diff --git a/spec/lib/gitlab/utils/override_spec.rb b/spec/lib/gitlab/utils/override_spec.rb index fc08ebcfc6d..9e7c97f8095 100644 --- a/spec/lib/gitlab/utils/override_spec.rb +++ b/spec/lib/gitlab/utils/override_spec.rb @@ -25,11 +25,21 @@ describe Gitlab::Utils::Override do let(:klass) { subject } - def good(mod) + def good(mod, bad_arity: false, negative_arity: false) mod.module_eval do override :good - def good - super.succ + + if bad_arity + def good(num) + end + elsif negative_arity + def good(*args) + super.succ + end + else + def good + super.succ + end end end @@ -56,6 +66,14 @@ describe Gitlab::Utils::Override do described_class.verify! end + it 'checks ok for overriding method using negative arity' do + good(subject, negative_arity: true) + result = instance.good + + expect(result).to eq(1) + described_class.verify! + end + it 'raises NotImplementedError when it is not overriding anything' do expect do bad(subject) @@ -63,6 +81,14 @@ describe Gitlab::Utils::Override do described_class.verify! end.to raise_error(NotImplementedError) end + + it 'raises NotImplementedError when overriding a method with different arity' do + expect do + good(subject, bad_arity: true) + instance.good(1) + described_class.verify! + end.to raise_error(NotImplementedError) + end end shared_examples 'checking as intended, nothing was overridden' do diff --git a/spec/lib/json_web_token/rsa_token_spec.rb b/spec/lib/json_web_token/rsa_token_spec.rb index d6edc964844..a3c54651e80 100644 --- a/spec/lib/json_web_token/rsa_token_spec.rb +++ b/spec/lib/json_web_token/rsa_token_spec.rb @@ -25,7 +25,7 @@ describe JSONWebToken::RSAToken do rsa_token['key'] = 'value' end - subject { JWT.decode(rsa_encoded, rsa_key) } + subject { JWT.decode(rsa_encoded, rsa_key, true, { algorithm: 'RS256' }) } it { expect {subject}.not_to raise_error } it { expect(subject.first).to include('key' => 'value') } @@ -39,7 +39,7 @@ describe JSONWebToken::RSAToken do context 'for invalid key to raise an exception' do let(:new_key) { OpenSSL::PKey::RSA.generate(512) } - subject { JWT.decode(rsa_encoded, new_key) } + subject { JWT.decode(rsa_encoded, new_key, true, { algorithm: 'RS256' }) } it { expect {subject}.to raise_error(JWT::DecodeError) } end diff --git a/spec/lib/mattermost/session_spec.rb b/spec/lib/mattermost/session_spec.rb index f18f97a9c6a..77fea5b2d24 100644 --- a/spec/lib/mattermost/session_spec.rb +++ b/spec/lib/mattermost/session_spec.rb @@ -67,11 +67,13 @@ describe Mattermost::Session, type: :request do .with(query: hash_including({ 'state' => state })) .to_return do |request| post "/oauth/token", - client_id: doorkeeper.uid, - client_secret: doorkeeper.secret, - redirect_uri: params[:redirect_uri], - grant_type: 'authorization_code', - code: request.uri.query_values['code'] + params: { + client_id: doorkeeper.uid, + client_secret: doorkeeper.secret, + redirect_uri: params[:redirect_uri], + grant_type: 'authorization_code', + code: request.uri.query_values['code'] + } if response.status == 200 { headers: { 'token' => 'thisworksnow' }, status: 202 } diff --git a/spec/mailers/notify_spec.rb b/spec/mailers/notify_spec.rb index f6e5c9d33ac..f2d99872401 100644 --- a/spec/mailers/notify_spec.rb +++ b/spec/mailers/notify_spec.rb @@ -890,22 +890,14 @@ describe Notify do shared_examples 'an email for a note on a diff discussion' do |model| let(:note) { create(model, author: note_author) } - context 'when note is on image' do + context 'when note is not on text' do before do - allow_any_instance_of(DiffDiscussion).to receive(:on_image?).and_return(true) + allow_any_instance_of(DiffDiscussion).to receive(:on_text?).and_return(false) end it 'does not include diffs with character-level highlighting' do is_expected.not_to have_body_text '<span class="p">}</span></span>' end - - it 'ends the intro with a dot' do - is_expected.to have_body_text "#{note.diff_file.file_path}</a>." - end - end - - it 'ends the intro with a colon' do - is_expected.to have_body_text "#{note.diff_file.file_path}</a>:" end it 'includes diffs with character-level highlighting' do diff --git a/spec/migrations/backfill_releases_name_with_tag_name_spec.rb b/spec/migrations/backfill_releases_name_with_tag_name_spec.rb new file mode 100644 index 00000000000..6f436de84b7 --- /dev/null +++ b/spec/migrations/backfill_releases_name_with_tag_name_spec.rb @@ -0,0 +1,23 @@ +# frozen_string_literal: true + +require 'spec_helper' +require Rails.root.join('db', 'migrate', '20181212104941_backfill_releases_name_with_tag_name.rb') + +describe BackfillReleasesNameWithTagName, :migration do + let(:releases) { table(:releases) } + let(:namespaces) { table(:namespaces) } + let(:projects) { table(:projects) } + + let(:namespace) { namespaces.create(name: 'foo', path: 'foo') } + let(:project) { projects.create!(namespace_id: namespace.id) } + let(:release) { releases.create!(project_id: project.id, tag: 'v1.0.0') } + + it 'defaults name to tag value' do + expect(release.tag).to be_present + + migrate! + + release.reload + expect(release.name).to eq(release.tag) + end +end diff --git a/spec/migrations/migrate_cluster_configure_worker_sidekiq_queue_spec.rb b/spec/migrations/migrate_cluster_configure_worker_sidekiq_queue_spec.rb new file mode 100644 index 00000000000..b2d8f476bb2 --- /dev/null +++ b/spec/migrations/migrate_cluster_configure_worker_sidekiq_queue_spec.rb @@ -0,0 +1,68 @@ +require 'spec_helper' +require Rails.root.join('db', 'post_migrate', '20181219145520_migrate_cluster_configure_worker_sidekiq_queue.rb') + +describe MigrateClusterConfigureWorkerSidekiqQueue, :sidekiq, :redis do + include Gitlab::Database::MigrationHelpers + + context 'when there are jobs in the queue' do + it 'correctly migrates queue when migrating up' do + Sidekiq::Testing.disable! do + stubbed_worker(queue: 'gcp_cluster:cluster_platform_configure').perform_async('Something', [1]) + stubbed_worker(queue: 'gcp_cluster:cluster_configure').perform_async('Something', [1]) + + described_class.new.up + + expect(sidekiq_queue_length('gcp_cluster:cluster_platform_configure')).to eq 0 + expect(sidekiq_queue_length('gcp_cluster:cluster_configure')).to eq 2 + end + end + + it 'does not affect other queues under the same namespace' do + Sidekiq::Testing.disable! do + stubbed_worker(queue: 'gcp_cluster:cluster_install_app').perform_async('Something', [1]) + stubbed_worker(queue: 'gcp_cluster:cluster_provision').perform_async('Something', [1]) + stubbed_worker(queue: 'gcp_cluster:cluster_wait_for_app_installation').perform_async('Something', [1]) + stubbed_worker(queue: 'gcp_cluster:wait_for_cluster_creation').perform_async('Something', [1]) + stubbed_worker(queue: 'gcp_cluster:cluster_wait_for_ingress_ip_address').perform_async('Something', [1]) + stubbed_worker(queue: 'gcp_cluster:cluster_project_configure').perform_async('Something', [1]) + + described_class.new.up + + expect(sidekiq_queue_length('gcp_cluster:cluster_install_app')).to eq 1 + expect(sidekiq_queue_length('gcp_cluster:cluster_provision')).to eq 1 + expect(sidekiq_queue_length('gcp_cluster:cluster_wait_for_app_installation')).to eq 1 + expect(sidekiq_queue_length('gcp_cluster:wait_for_cluster_creation')).to eq 1 + expect(sidekiq_queue_length('gcp_cluster:cluster_wait_for_ingress_ip_address')).to eq 1 + expect(sidekiq_queue_length('gcp_cluster:cluster_project_configure')).to eq 1 + end + end + + it 'correctly migrates queue when migrating down' do + Sidekiq::Testing.disable! do + stubbed_worker(queue: 'gcp_cluster:cluster_configure').perform_async('Something', [1]) + + described_class.new.down + + expect(sidekiq_queue_length('gcp_cluster:cluster_platform_configure')).to eq 1 + expect(sidekiq_queue_length('gcp_cluster:cluster_configure')).to eq 0 + end + end + end + + context 'when there are no jobs in the queues' do + it 'does not raise error when migrating up' do + expect { described_class.new.up }.not_to raise_error + end + + it 'does not raise error when migrating down' do + expect { described_class.new.down }.not_to raise_error + end + end + + def stubbed_worker(queue:) + Class.new do + include Sidekiq::Worker + sidekiq_options queue: queue + end + end +end diff --git a/spec/models/appearance_spec.rb b/spec/models/appearance_spec.rb index 35415030154..ec2e7d672f0 100644 --- a/spec/models/appearance_spec.rb +++ b/spec/models/appearance_spec.rb @@ -26,4 +26,34 @@ describe Appearance do let(:uploader_class) { AttachmentUploader } end end + + shared_examples 'logo paths' do |logo_type| + let(:appearance) { create(:appearance, "with_#{logo_type}".to_sym) } + let(:filename) { 'dk.png' } + let(:expected_path) { "/uploads/-/system/appearance/#{logo_type}/#{appearance.id}/#{filename}" } + + it 'returns nil when there is no upload' do + expect(subject.send("#{logo_type}_path")).to be_nil + end + + it 'returns a local path using the system route' do + expect(appearance.send("#{logo_type}_path")).to eq(expected_path) + end + + describe 'with asset host configured' do + let(:asset_host) { 'https://gitlab-assets.example.com' } + + before do + allow(ActionController::Base).to receive(:asset_host) { asset_host } + end + + it 'returns a full URL with the system path' do + expect(appearance.send("#{logo_type}_path")).to eq("#{asset_host}#{expected_path}") + end + end + end + + %i(logo header_logo favicon).each do |logo_type| + it_behaves_like 'logo paths', logo_type + end end diff --git a/spec/models/blob_spec.rb b/spec/models/blob_spec.rb index e8c03b587e2..05cf242e84d 100644 --- a/spec/models/blob_spec.rb +++ b/spec/models/blob_spec.rb @@ -122,14 +122,14 @@ describe Blob do end end - describe '#raw_binary?' do + describe '#binary?' do context 'if the blob is stored externally' do context 'if the extension has a rich viewer' do context 'if the viewer is binary' do it 'returns true' do blob = fake_blob(path: 'file.pdf', lfs: true) - expect(blob.raw_binary?).to be_truthy + expect(blob.binary?).to be_truthy end end @@ -137,7 +137,7 @@ describe Blob do it 'return false' do blob = fake_blob(path: 'file.md', lfs: true) - expect(blob.raw_binary?).to be_falsey + expect(blob.binary?).to be_falsey end end end @@ -148,7 +148,7 @@ describe Blob do it 'returns false' do blob = fake_blob(path: 'file.txt', lfs: true) - expect(blob.raw_binary?).to be_falsey + expect(blob.binary?).to be_falsey end end @@ -156,7 +156,7 @@ describe Blob do it 'returns false' do blob = fake_blob(path: 'file.ics', lfs: true) - expect(blob.raw_binary?).to be_falsey + expect(blob.binary?).to be_falsey end end end @@ -166,7 +166,7 @@ describe Blob do it 'returns false' do blob = fake_blob(path: 'file.rb', lfs: true) - expect(blob.raw_binary?).to be_falsey + expect(blob.binary?).to be_falsey end end @@ -174,7 +174,7 @@ describe Blob do it 'returns true' do blob = fake_blob(path: 'file.exe', lfs: true) - expect(blob.raw_binary?).to be_truthy + expect(blob.binary?).to be_truthy end end end @@ -184,7 +184,7 @@ describe Blob do it 'returns false' do blob = fake_blob(path: 'file.ini', lfs: true) - expect(blob.raw_binary?).to be_falsey + expect(blob.binary?).to be_falsey end end @@ -192,7 +192,7 @@ describe Blob do it 'returns true' do blob = fake_blob(path: 'file.wtf', lfs: true) - expect(blob.raw_binary?).to be_truthy + expect(blob.binary?).to be_truthy end end end @@ -204,7 +204,7 @@ describe Blob do it 'returns true' do blob = fake_blob(path: 'file.pdf', binary: true) - expect(blob.raw_binary?).to be_truthy + expect(blob.binary?).to be_truthy end end @@ -212,7 +212,7 @@ describe Blob do it 'return false' do blob = fake_blob(path: 'file.md') - expect(blob.raw_binary?).to be_falsey + expect(blob.binary?).to be_falsey end end end diff --git a/spec/models/broadcast_message_spec.rb b/spec/models/broadcast_message_spec.rb index d6e5b557870..89839709131 100644 --- a/spec/models/broadcast_message_spec.rb +++ b/spec/models/broadcast_message_spec.rb @@ -49,7 +49,7 @@ describe BroadcastMessage do it 'caches the output of the query' do create(:broadcast_message) - expect(described_class).to receive(:where).and_call_original.once + expect(described_class).to receive(:current_and_future_messages).and_call_original.once described_class.current @@ -93,27 +93,6 @@ describe BroadcastMessage do expect(Rails.cache).to receive(:delete).with(described_class::LEGACY_CACHE_KEY) expect(described_class.current.length).to eq(0) end - - it 'gracefully handles bad cache entry' do - allow(described_class).to receive(:current_and_future_messages).and_return('{') - - expect(described_class.current).to be_empty - end - - it 'gracefully handles an empty hash' do - allow(described_class).to receive(:current_and_future_messages).and_return('{}') - - expect(described_class.current).to be_empty - end - - it 'gracefully handles unknown attributes' do - message = create(:broadcast_message) - - allow(described_class).to receive(:current_and_future_messages) - .and_return([{ bad_attr: 1 }, message]) - - expect(described_class.current).to eq([message]) - end end describe '#active?' do diff --git a/spec/models/ci/build_spec.rb b/spec/models/ci/build_spec.rb index 89f78f629d4..7baf4d93804 100644 --- a/spec/models/ci/build_spec.rb +++ b/spec/models/ci/build_spec.rb @@ -2114,6 +2114,7 @@ describe Ci::Build do { key: 'CI_JOB_NAME', value: 'test', public: true }, { key: 'CI_JOB_STAGE', value: 'test', public: true }, { key: 'CI_COMMIT_SHA', value: build.sha, public: true }, + { key: 'CI_COMMIT_SHORT_SHA', value: build.short_sha, public: true }, { key: 'CI_COMMIT_BEFORE_SHA', value: build.before_sha, public: true }, { key: 'CI_COMMIT_REF_NAME', value: build.ref, public: true }, { key: 'CI_COMMIT_REF_SLUG', value: build.ref_slug, public: true }, @@ -2385,6 +2386,8 @@ describe Ci::Build do end context 'when protected variable is defined' do + let(:ref) { Gitlab::Git::BRANCH_REF_PREFIX + build.ref } + let(:protected_variable) do { key: 'PROTECTED_KEY', value: 'protected_value', public: false } end @@ -2397,7 +2400,7 @@ describe Ci::Build do context 'when the branch is protected' do before do - allow(build.project).to receive(:protected_for?).with(build.ref).and_return(true) + allow(build.project).to receive(:protected_for?).with(ref).and_return(true) end it { is_expected.to include(protected_variable) } @@ -2405,7 +2408,7 @@ describe Ci::Build do context 'when the tag is protected' do before do - allow(build.project).to receive(:protected_for?).with(build.ref).and_return(true) + allow(build.project).to receive(:protected_for?).with(ref).and_return(true) end it { is_expected.to include(protected_variable) } @@ -2430,6 +2433,8 @@ describe Ci::Build do end context 'when group protected variable is defined' do + let(:ref) { Gitlab::Git::BRANCH_REF_PREFIX + build.ref } + let(:protected_variable) do { key: 'PROTECTED_KEY', value: 'protected_value', public: false } end @@ -2442,7 +2447,7 @@ describe Ci::Build do context 'when the branch is protected' do before do - allow(build.project).to receive(:protected_for?).with(build.ref).and_return(true) + allow(build.project).to receive(:protected_for?).with(ref).and_return(true) end it { is_expected.to include(protected_variable) } @@ -2450,7 +2455,7 @@ describe Ci::Build do context 'when the tag is protected' do before do - allow(build.project).to receive(:protected_for?).with(build.ref).and_return(true) + allow(build.project).to receive(:protected_for?).with(ref).and_return(true) end it { is_expected.to include(protected_variable) } @@ -2725,6 +2730,7 @@ describe Ci::Build do it 'returns static predefined variables' do keys = %w[CI_JOB_NAME CI_COMMIT_SHA + CI_COMMIT_SHORT_SHA CI_COMMIT_REF_NAME CI_COMMIT_REF_SLUG CI_JOB_STAGE] diff --git a/spec/models/ci/pipeline_spec.rb b/spec/models/ci/pipeline_spec.rb index b67c6a4cffa..17f33785fda 100644 --- a/spec/models/ci/pipeline_spec.rb +++ b/spec/models/ci/pipeline_spec.rb @@ -397,6 +397,10 @@ describe Ci::Pipeline, :mailer do end describe '#protected_ref?' do + before do + pipeline.project = create(:project, :repository) + end + it 'delegates method to project' do expect(pipeline).not_to be_protected_ref end diff --git a/spec/models/clusters/applications/cert_manager_spec.rb b/spec/models/clusters/applications/cert_manager_spec.rb index 170c6001eaf..e825f3e2392 100644 --- a/spec/models/clusters/applications/cert_manager_spec.rb +++ b/spec/models/clusters/applications/cert_manager_spec.rb @@ -14,7 +14,7 @@ describe Clusters::Applications::CertManager do let(:application) { create(:clusters_applications_cert_managers, :scheduled, version: 'v0.4.0') } it 'updates the application version' do - expect(application.reload.version).to eq('v0.5.0') + expect(application.reload.version).to eq('v0.5.2') end end end @@ -28,7 +28,7 @@ describe Clusters::Applications::CertManager do it 'should be initialized with cert_manager arguments' do expect(subject.name).to eq('certmanager') expect(subject.chart).to eq('stable/cert-manager') - expect(subject.version).to eq('v0.5.0') + expect(subject.version).to eq('v0.5.2') expect(subject).not_to be_rbac expect(subject.files).to eq(cert_manager.files.merge(cluster_issuer_file)) expect(subject.postinstall).to eq(['/usr/bin/kubectl create -f /data/helm/certmanager/config/cluster_issuer.yaml']) @@ -57,7 +57,7 @@ describe Clusters::Applications::CertManager do let(:cert_manager) { create(:clusters_applications_cert_managers, :errored, version: '0.0.1') } it 'should be initialized with the locked version' do - expect(subject.version).to eq('v0.5.0') + expect(subject.version).to eq('v0.5.2') end end end diff --git a/spec/models/clusters/applications/knative_spec.rb b/spec/models/clusters/applications/knative_spec.rb index a1579b90436..809880f5969 100644 --- a/spec/models/clusters/applications/knative_spec.rb +++ b/spec/models/clusters/applications/knative_spec.rb @@ -33,10 +33,10 @@ describe Clusters::Applications::Knative do end context 'application install previously errored with older version' do - let(:application) { create(:clusters_applications_knative, :scheduled, version: '0.1.3') } + let(:application) { create(:clusters_applications_knative, :scheduled, version: '0.2.2') } it 'updates the application version' do - expect(application.reload.version).to eq('0.1.3') + expect(application.reload.version).to eq('0.2.2') end end end @@ -105,7 +105,7 @@ describe Clusters::Applications::Knative do it 'should be initialized with knative arguments' do expect(subject.name).to eq('knative') expect(subject.chart).to eq('knative/knative') - expect(subject.version).to eq('0.1.3') + expect(subject.version).to eq('0.2.2') expect(subject.files).to eq(knative.files) end end diff --git a/spec/models/clusters/applications/runner_spec.rb b/spec/models/clusters/applications/runner_spec.rb index 47daa79873e..cae23daac8c 100644 --- a/spec/models/clusters/applications/runner_spec.rb +++ b/spec/models/clusters/applications/runner_spec.rb @@ -18,7 +18,7 @@ describe Clusters::Applications::Runner do let(:application) { create(:clusters_applications_runner, :scheduled, version: '0.1.30') } it 'updates the application version' do - expect(application.reload.version).to eq('0.1.39') + expect(application.reload.version).to eq('0.1.43') end end end @@ -46,7 +46,7 @@ describe Clusters::Applications::Runner do it 'should be initialized with 4 arguments' do expect(subject.name).to eq('runner') expect(subject.chart).to eq('runner/gitlab-runner') - expect(subject.version).to eq('0.1.39') + expect(subject.version).to eq('0.1.43') expect(subject).not_to be_rbac expect(subject.repository).to eq('https://charts.gitlab.io') expect(subject.files).to eq(gitlab_runner.files) @@ -64,7 +64,7 @@ describe Clusters::Applications::Runner do let(:gitlab_runner) { create(:clusters_applications_runner, :errored, runner: ci_runner, version: '0.1.13') } it 'should be initialized with the locked version' do - expect(subject.version).to eq('0.1.39') + expect(subject.version).to eq('0.1.43') end end end diff --git a/spec/models/clusters/platforms/kubernetes_spec.rb b/spec/models/clusters/platforms/kubernetes_spec.rb index 062d2fd0768..e6b076adc76 100644 --- a/spec/models/clusters/platforms/kubernetes_spec.rb +++ b/spec/models/clusters/platforms/kubernetes_spec.rb @@ -325,12 +325,13 @@ describe Clusters::Platforms::Kubernetes, :use_clean_rails_memory_store_caching context 'with valid pods' do let(:pod) { kube_pod(app: environment.slug) } + let(:pod_with_no_terminal) { kube_pod(app: environment.slug, status: "Pending") } let(:terminals) { kube_terminals(service, pod) } before do stub_reactive_cache( service, - pods: [pod, pod, kube_pod(app: "should-be-filtered-out")] + pods: [pod, pod, pod_with_no_terminal, kube_pod(app: "should-be-filtered-out")] ) end @@ -394,7 +395,7 @@ describe Clusters::Platforms::Kubernetes, :use_clean_rails_memory_store_caching context 'when namespace is updated' do it 'should call ConfigureWorker' do - expect(ClusterPlatformConfigureWorker).to receive(:perform_async).with(cluster.id).once + expect(ClusterConfigureWorker).to receive(:perform_async).with(cluster.id).once platform.namespace = 'new-namespace' platform.save @@ -403,7 +404,7 @@ describe Clusters::Platforms::Kubernetes, :use_clean_rails_memory_store_caching context 'when namespace is not updated' do it 'should not call ConfigureWorker' do - expect(ClusterPlatformConfigureWorker).not_to receive(:perform_async) + expect(ClusterConfigureWorker).not_to receive(:perform_async) platform.username = "new-username" platform.save diff --git a/spec/models/concerns/avatarable_spec.rb b/spec/models/concerns/avatarable_spec.rb index 7d617cb7b19..1ea7f2b9985 100644 --- a/spec/models/concerns/avatarable_spec.rb +++ b/spec/models/concerns/avatarable_spec.rb @@ -33,6 +33,43 @@ describe Avatarable do end describe '#avatar_path' do + context 'with caching enabled', :request_store do + let!(:avatar_path) { [relative_url_root, project.avatar.local_url].join } + let!(:avatar_url) { [gitlab_host, relative_url_root, project.avatar.local_url].join } + + it 'only calls local_url once' do + expect(project.avatar).to receive(:local_url).once.and_call_original + + 2.times do + expect(project.avatar_path).to eq(avatar_path) + end + end + + it 'calls local_url twice for path and URLs' do + expect(project.avatar).to receive(:local_url).exactly(2).times.and_call_original + + expect(project.avatar_path(only_path: true)).to eq(avatar_path) + expect(project.avatar_path(only_path: false)).to eq(avatar_url) + end + + it 'calls local_url twice for different sizes' do + expect(project.avatar).to receive(:local_url).exactly(2).times.and_call_original + + expect(project.avatar_path).to eq(avatar_path) + expect(project.avatar_path(size: 40)).to eq(avatar_path + "?width=40") + end + + it 'handles unpersisted objects' do + new_project = build(:project, :with_avatar) + path = [relative_url_root, new_project.avatar.local_url].join + expect(new_project.avatar).to receive(:local_url).exactly(2).times.and_call_original + + 2.times do + expect(new_project.avatar_path).to eq(path) + end + end + end + using RSpec::Parameterized::TableSyntax where(:has_asset_host, :visibility_level, :only_path, :avatar_path_prefix) do diff --git a/spec/models/concerns/cacheable_attributes_spec.rb b/spec/models/concerns/cacheable_attributes_spec.rb index 827fbc9d7d5..689e7d3058f 100644 --- a/spec/models/concerns/cacheable_attributes_spec.rb +++ b/spec/models/concerns/cacheable_attributes_spec.rb @@ -20,6 +20,10 @@ describe CacheableAttributes do @_last ||= new('foo' => 'a', 'bar' => 'b') end + def self.column_names + %w[foo bar baz] + end + attr_accessor :attributes def initialize(attrs = {}, *) @@ -75,13 +79,13 @@ describe CacheableAttributes do context 'without any attributes given' do it 'intializes a new object with the defaults' do - expect(minimal_test_class.build_from_defaults.attributes).to eq(minimal_test_class.defaults) + expect(minimal_test_class.build_from_defaults.attributes).to eq(minimal_test_class.defaults.stringify_keys) end end context 'with attributes given' do it 'intializes a new object with the given attributes merged into the defaults' do - expect(minimal_test_class.build_from_defaults(foo: 'd').attributes[:foo]).to eq('d') + expect(minimal_test_class.build_from_defaults(foo: 'd').attributes['foo']).to eq('d') end end diff --git a/spec/models/concerns/chronic_duration_attribute_spec.rb b/spec/models/concerns/chronic_duration_attribute_spec.rb index b14b773b653..51221e07ca3 100644 --- a/spec/models/concerns/chronic_duration_attribute_spec.rb +++ b/spec/models/concerns/chronic_duration_attribute_spec.rb @@ -43,7 +43,7 @@ shared_examples 'ChronicDurationAttribute writer' do end it "doesn't raise exception" do - expect { subject.send("#{virtual_field}=", '-10m') }.not_to raise_error(ChronicDuration::DurationParseError) + expect { subject.send("#{virtual_field}=", '-10m') }.not_to raise_error end it "doesn't change value" do @@ -87,7 +87,7 @@ shared_examples 'ChronicDurationAttribute writer' do end it "doesn't raise exception" do - expect { subject.send("#{virtual_field}=", nil) }.not_to raise_error(NoMethodError) + expect { subject.send("#{virtual_field}=", nil) }.not_to raise_error end end end diff --git a/spec/models/concerns/discussion_on_diff_spec.rb b/spec/models/concerns/discussion_on_diff_spec.rb index 73eb7a1160d..4b16e6e3902 100644 --- a/spec/models/concerns/discussion_on_diff_spec.rb +++ b/spec/models/concerns/discussion_on_diff_spec.rb @@ -50,11 +50,17 @@ describe DiscussionOnDiff do end context "when the diff line does not exist on a legacy diff note" do + subject { create(:legacy_diff_note_on_merge_request).to_discussion } + it "returns an empty array" do - legacy_note = LegacyDiffNote.new + expect(truncated_lines).to eq([]) + end + end - allow(subject).to receive(:first_note).and_return(legacy_note) + context 'when the discussion is on an image' do + subject { create(:image_diff_note_on_merge_request).to_discussion } + it 'returns an empty array' do expect(truncated_lines).to eq([]) end end diff --git a/spec/models/concerns/has_ref_spec.rb b/spec/models/concerns/has_ref_spec.rb new file mode 100644 index 00000000000..8aed72d77a4 --- /dev/null +++ b/spec/models/concerns/has_ref_spec.rb @@ -0,0 +1,59 @@ +# frozen_string_literal: true + +require 'spec_helper' + +describe HasRef do + describe '#branch?' do + let(:build) { create(:ci_build) } + + subject { build.branch? } + + context 'is not a tag' do + before do + build.tag = false + end + + it 'return true when tag is set to false' do + is_expected.to be_truthy + end + end + + context 'is not a tag' do + before do + build.tag = true + end + + it 'return false when tag is set to true' do + is_expected.to be_falsey + end + end + end + + describe '#git_ref' do + subject { build.git_ref } + + context 'when tag is true' do + let(:build) { create(:ci_build, tag: true) } + + it 'returns a tag ref' do + is_expected.to start_with(Gitlab::Git::TAG_REF_PREFIX) + end + end + + context 'when tag is false' do + let(:build) { create(:ci_build, tag: false) } + + it 'returns a branch ref' do + is_expected.to start_with(Gitlab::Git::BRANCH_REF_PREFIX) + end + end + + context 'when tag is nil' do + let(:build) { create(:ci_build, tag: nil) } + + it 'returns a branch ref' do + is_expected.to start_with(Gitlab::Git::BRANCH_REF_PREFIX) + end + end + end +end diff --git a/spec/models/diff_note_spec.rb b/spec/models/diff_note_spec.rb index 8624f0daa4d..fda00a693f0 100644 --- a/spec/models/diff_note_spec.rb +++ b/spec/models/diff_note_spec.rb @@ -318,25 +318,28 @@ describe DiffNote do end end - describe "image diff notes" do - let(:path) { "files/images/any_image.png" } + describe '#supports_suggestion?' do + context 'when noteable does not support suggestions' do + it 'returns false' do + allow(subject.noteable).to receive(:supports_suggestion?) { false } - let!(:position) do - Gitlab::Diff::Position.new( - old_path: path, - new_path: path, - width: 10, - height: 10, - x: 1, - y: 1, - diff_refs: merge_request.diff_refs, - position_type: "image" - ) + expect(subject.supports_suggestion?).to be(false) + end end - describe "validations" do - subject { build(:diff_note_on_merge_request, project: project, position: position, noteable: merge_request) } + context 'when line is not suggestible' do + it 'returns false' do + allow_any_instance_of(Gitlab::Diff::Line).to receive(:suggestible?) { false } + + expect(subject.supports_suggestion?).to be(false) + end + end + end + describe "image diff notes" do + subject { build(:image_diff_note_on_merge_request, project: project, noteable: merge_request) } + + describe "validations" do it { is_expected.not_to validate_presence_of(:line_code) } it "does not validate diff line" do diff --git a/spec/models/diff_viewer/base_spec.rb b/spec/models/diff_viewer/base_spec.rb index c90b32c5d77..f4efe5a7b3a 100644 --- a/spec/models/diff_viewer/base_spec.rb +++ b/spec/models/diff_viewer/base_spec.rb @@ -58,7 +58,7 @@ describe DiffViewer::Base do context 'when the binaryness does not match' do before do - allow_any_instance_of(Blob).to receive(:binary?).and_return(true) + allow_any_instance_of(Blob).to receive(:binary_in_repo?).and_return(true) end it 'returns false' do @@ -141,4 +141,25 @@ describe DiffViewer::Base do end end end + + describe '#render_error_message' do + it 'returns nothing when no render_error' do + expect(viewer.render_error).to be_nil + expect(viewer.render_error_message).to be_nil + end + + context 'when render_error error' do + before do + allow(viewer).to receive(:render_error).and_return(:too_large) + end + + it 'returns an error message' do + expect(viewer.render_error_message).to include('it is too large') + end + + it 'includes a "view the blob" link' do + expect(viewer.render_error_message).to include('view the blob') + end + end + end end diff --git a/spec/models/diff_viewer/server_side_spec.rb b/spec/models/diff_viewer/server_side_spec.rb index 98a8f6d4cc9..86b14b6ebf3 100644 --- a/spec/models/diff_viewer/server_side_spec.rb +++ b/spec/models/diff_viewer/server_side_spec.rb @@ -32,4 +32,24 @@ describe DiffViewer::ServerSide do end end end + + describe '#render_error_reason' do + context 'when the diff file is stored externally' do + before do + allow(diff_file).to receive(:stored_externally?).and_return(true) + end + + it 'returns error message if stored in LFS' do + allow(diff_file).to receive(:external_storage).and_return(:lfs) + + expect(subject.render_error_message).to include('it is stored in LFS') + end + + it 'returns error message if stored externally' do + allow(diff_file).to receive(:external_storage).and_return(:foo) + + expect(subject.render_error_message).to include('it is stored externally') + end + end + end end diff --git a/spec/models/event_spec.rb b/spec/models/event_spec.rb index 81748681528..a64720f1876 100644 --- a/spec/models/event_spec.rb +++ b/spec/models/event_spec.rb @@ -243,6 +243,20 @@ describe Event do expect(event.visible_to_user?(admin)).to eq true end end + + context 'private project' do + let(:project) { create(:project, :private) } + let(:target) { note_on_issue } + + it do + expect(event.visible_to_user?(non_member)).to eq false + expect(event.visible_to_user?(author)).to eq false + expect(event.visible_to_user?(assignee)).to eq false + expect(event.visible_to_user?(member)).to eq true + expect(event.visible_to_user?(guest)).to eq true + expect(event.visible_to_user?(admin)).to eq true + end + end end context 'merge request diff note event' do @@ -265,8 +279,8 @@ describe Event do it do expect(event.visible_to_user?(non_member)).to eq false - expect(event.visible_to_user?(author)).to eq true - expect(event.visible_to_user?(assignee)).to eq true + expect(event.visible_to_user?(author)).to eq false + expect(event.visible_to_user?(assignee)).to eq false expect(event.visible_to_user?(member)).to eq true expect(event.visible_to_user?(guest)).to eq false expect(event.visible_to_user?(admin)).to eq true diff --git a/spec/models/global_milestone_spec.rb b/spec/models/global_milestone_spec.rb index b6355455c1d..62699df5611 100644 --- a/spec/models/global_milestone_spec.rb +++ b/spec/models/global_milestone_spec.rb @@ -65,56 +65,103 @@ describe GlobalMilestone do ) end - before do - projects = [ + let!(:projects) do + [ project1, project2, project3 ] - - @global_milestones = described_class.build_collection(projects, {}) end - it 'has all project milestones' do - expect(@global_milestones.count).to eq(2) + let!(:global_milestones) { described_class.build_collection(projects, {}) } + + context 'when building a collection of milestones' do + it 'has all project milestones' do + expect(global_milestones.count).to eq(6) + end + + it 'has all project milestones titles' do + expect(global_milestones.map(&:title)).to match_array(['Milestone v1.2', 'Milestone v1.2', 'Milestone v1.2', 'VD-123', 'VD-123', 'VD-123']) + end + + it 'has all project milestones' do + expect(global_milestones.size).to eq(6) + end + + it 'sorts collection by due date' do + expect(global_milestones.map(&:due_date)).to eq [milestone1_due_date, milestone1_due_date, milestone1_due_date, nil, nil, nil] + end end - it 'has all project milestones titles' do - expect(@global_milestones.map(&:title)).to match_array(['Milestone v1.2', 'VD-123']) + context 'when adding new milestones' do + it 'does not add more queries' do + control_count = ActiveRecord::QueryRecorder.new do + described_class.build_collection(projects, {}) + end.count + + create_list(:milestone, 3, project: project3) + + expect do + described_class.build_collection(projects, {}) + end.not_to exceed_all_query_limit(control_count) + end end + end + + describe '.states_count' do + context 'when the projects have milestones' do + before do + create(:closed_milestone, title: 'Active Group Milestone', project: project3) + create(:active_milestone, title: 'Active Group Milestone', project: project1) + create(:active_milestone, title: 'Active Group Milestone', project: project2) + create(:closed_milestone, title: 'Closed Group Milestone', project: project1) + create(:closed_milestone, title: 'Closed Group Milestone', project: project2) + create(:closed_milestone, title: 'Closed Group Milestone', project: project3) + create(:closed_milestone, title: 'Closed Group Milestone 4', group: group) + end + + it 'returns the quantity of global milestones and group milestones in each possible state' do + expected_count = { opened: 2, closed: 5, all: 7 } - it 'has all project milestones' do - expect(@global_milestones.map { |group_milestone| group_milestone.milestones.count }.sum).to eq(6) + count = described_class.states_count(Project.all, group) + + expect(count).to eq(expected_count) + end + + it 'returns the quantity of global milestones in each possible state' do + expected_count = { opened: 2, closed: 4, all: 6 } + + count = described_class.states_count(Project.all) + + expect(count).to eq(expected_count) + end end - it 'sorts collection by due date' do - expect(@global_milestones.map(&:due_date)).to eq [nil, milestone1_due_date] + context 'when the projects do not have milestones' do + before do + project1 + end + + it 'returns 0 as the quantity of global milestones in each state' do + expected_count = { opened: 0, closed: 0, all: 0 } + + count = described_class.states_count(Project.all) + + expect(count).to eq(expected_count) + end end end describe '#initialize' do let(:milestone1_project1) { create(:milestone, title: "Milestone v1.2", project: project1) } - let(:milestone1_project2) { create(:milestone, title: "Milestone v1.2", project: project2) } - let(:milestone1_project3) { create(:milestone, title: "Milestone v1.2", project: project3) } - - before do - milestones = - [ - milestone1_project1, - milestone1_project2, - milestone1_project3 - ] - milestones_relation = Milestone.where(id: milestones.map(&:id)) - - @global_milestone = described_class.new(milestone1_project1.title, milestones_relation) - end + subject(:global_milestone) { described_class.new(milestone1_project1) } it 'has exactly one group milestone' do - expect(@global_milestone.title).to eq('Milestone v1.2') + expect(global_milestone.title).to eq('Milestone v1.2') end it 'has all project milestones with the same title' do - expect(@global_milestone.milestones.count).to eq(3) + expect(global_milestone.milestone).to eq(milestone1_project1) end end @@ -122,7 +169,7 @@ describe GlobalMilestone do let(:milestone) { create(:milestone, title: "git / test", project: project1) } it 'strips out slashes and spaces' do - global_milestone = described_class.new(milestone.title, Milestone.where(id: milestone.id)) + global_milestone = described_class.new(milestone) expect(global_milestone.safe_title).to eq('git-test') end @@ -132,11 +179,8 @@ describe GlobalMilestone do context 'when at least one milestone is active' do it 'returns active' do title = 'Active Group Milestone' - milestones = [ - create(:active_milestone, title: title), - create(:closed_milestone, title: title) - ] - global_milestone = described_class.new(title, milestones) + + global_milestone = described_class.new(create(:active_milestone, title: title)) expect(global_milestone.state).to eq('active') end @@ -145,11 +189,8 @@ describe GlobalMilestone do context 'when all milestones are closed' do it 'returns closed' do title = 'Closed Group Milestone' - milestones = [ - create(:closed_milestone, title: title), - create(:closed_milestone, title: title) - ] - global_milestone = described_class.new(title, milestones) + + global_milestone = described_class.new(create(:closed_milestone, title: title)) expect(global_milestone.state).to eq('closed') end diff --git a/spec/models/group_milestone_spec.rb b/spec/models/group_milestone_spec.rb index b60676afc91..fcc33cd95fe 100644 --- a/spec/models/group_milestone_spec.rb +++ b/spec/models/group_milestone_spec.rb @@ -20,13 +20,36 @@ describe GroupMilestone do end describe '.build_collection' do - before do - project_milestone + let(:group) { create(:group) } + let(:project1) { create(:project, group: group) } + let(:project2) { create(:project, path: 'gitlab-ci', group: group) } + let(:project3) { create(:project, path: 'cookbook-gitlab', group: group) } + + let!(:projects) do + [ + project1, + project2, + project3 + ] end it 'returns array of milestones, each with group assigned' do milestones = described_class.build_collection(group, [project], {}) expect(milestones).to all(have_attributes(group: group)) end + + context 'when adding new milestones' do + it 'does not add more queries' do + control_count = ActiveRecord::QueryRecorder.new do + described_class.build_collection(group, projects, {}) + end.count + + create(:milestone, title: 'This title', project: project1) + + expect do + described_class.build_collection(group, projects, {}) + end.not_to exceed_all_query_limit(control_count) + end + end end end diff --git a/spec/models/merge_request_diff_spec.rb b/spec/models/merge_request_diff_spec.rb index cbe60b3a4a5..33e984dc399 100644 --- a/spec/models/merge_request_diff_spec.rb +++ b/spec/models/merge_request_diff_spec.rb @@ -105,7 +105,7 @@ describe MergeRequestDiff do context 'when the raw diffs are empty' do before do - MergeRequestDiffFile.delete_all(merge_request_diff_id: diff_with_commits.id) + MergeRequestDiffFile.where(merge_request_diff_id: diff_with_commits.id).delete_all end it 'returns an empty DiffCollection' do diff --git a/spec/models/merge_request_spec.rb b/spec/models/merge_request_spec.rb index bf4117fbcaf..4cc3a6a3644 100644 --- a/spec/models/merge_request_spec.rb +++ b/spec/models/merge_request_spec.rb @@ -559,6 +559,57 @@ describe MergeRequest do end end + describe '#preload_discussions_diff_highlight' do + let(:merge_request) { create(:merge_request) } + + context 'with commit diff note' do + let(:other_merge_request) { create(:merge_request) } + + let!(:diff_note) do + create(:diff_note_on_commit, project: merge_request.project) + end + + let!(:other_mr_diff_note) do + create(:diff_note_on_commit, project: other_merge_request.project) + end + + it 'preloads diff highlighting' do + expect_next_instance_of(Gitlab::DiscussionsDiff::FileCollection) do |collection| + note_diff_file = diff_note.note_diff_file + + expect(collection) + .to receive(:load_highlight) + .with([note_diff_file.id]).and_call_original + end + + merge_request.preload_discussions_diff_highlight + end + end + + context 'with merge request diff note' do + let!(:unresolved_diff_note) do + create(:diff_note_on_merge_request, project: merge_request.project, noteable: merge_request) + end + + let!(:resolved_diff_note) do + create(:diff_note_on_merge_request, :resolved, project: merge_request.project, noteable: merge_request) + end + + it 'preloads diff highlighting' do + expect_next_instance_of(Gitlab::DiscussionsDiff::FileCollection) do |collection| + note_diff_file = unresolved_diff_note.note_diff_file + + expect(collection) + .to receive(:load_highlight) + .with([note_diff_file.id]) + .and_call_original + end + + merge_request.preload_discussions_diff_highlight + end + end + end + describe '#diff_size' do let(:merge_request) do build(:merge_request, source_branch: 'expand-collapse-files', target_branch: 'master') @@ -1358,7 +1409,7 @@ describe MergeRequest do it 'does not raises a NameError exception' do allow_any_instance_of(service_class_name.constantize).to receive(:execute).and_return(nil) - expect { subject }.not_to raise_error(NameError) + expect { subject }.not_to raise_error end end end @@ -2092,7 +2143,7 @@ describe MergeRequest do head_commit_sha: commit.sha ) - subject.merge_request_diff(true) + subject.reload_merge_request_diff end end diff --git a/spec/models/milestone_spec.rb b/spec/models/milestone_spec.rb index d11eb46159e..b3d31e65c85 100644 --- a/spec/models/milestone_spec.rb +++ b/spec/models/milestone_spec.rb @@ -316,6 +316,15 @@ describe Milestone do end end + describe '#reference_link_text' do + let(:project) { build_stubbed(:project, name: 'sample-project') } + let(:milestone) { build_stubbed(:milestone, iid: 1, project: project, name: 'milestone') } + + it 'returns the title with the reference prefix' do + expect(milestone.reference_link_text).to eq '%milestone' + end + end + describe '#participants' do let(:project) { build(:project, name: 'sample-project') } let(:milestone) { build(:milestone, iid: 1, project: project) } diff --git a/spec/models/namespace_spec.rb b/spec/models/namespace_spec.rb index 18b54cce834..475fbe56e4d 100644 --- a/spec/models/namespace_spec.rb +++ b/spec/models/namespace_spec.rb @@ -337,32 +337,40 @@ describe Namespace do 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, :legacy_storage, :repository, namespace: parent, name: 'foo1') - hashed_project_in_subgroup = create(:project, :repository, namespace: subgroup, name: 'foo2') - legacy_project_in_subgroup = create(:project, :legacy_storage, :repository, namespace: subgroup, name: 'foo3') - - parent.update(path: 'mygroup_new') - - # Routes are loaded when creating the projects, so we need to manually - # reload them for the below code to be aware of the above UPDATE. - [ - project_in_parent_group, - hashed_project_in_subgroup, - legacy_project_in_subgroup - ].each do |project| - project.route.reload + context 'for each project inside the namespace' do + let!(:parent) { create(:group, name: 'mygroup', path: 'mygroup') } + let!(:subgroup) { create(:group, name: 'mysubgroup', path: 'mysubgroup', parent: parent) } + let!(:project_in_parent_group) { create(:project, :legacy_storage, :repository, namespace: parent, name: 'foo1') } + let!(:hashed_project_in_subgroup) { create(:project, :repository, namespace: subgroup, name: 'foo2') } + let!(:legacy_project_in_subgroup) { create(:project, :legacy_storage, :repository, namespace: subgroup, name: 'foo3') } + + it 'updates project full path in .git/config' do + parent.update(path: 'mygroup_new') + + expect(project_rugged(project_in_parent_group).config['gitlab.fullpath']).to eq "mygroup_new/#{project_in_parent_group.path}" + expect(project_rugged(hashed_project_in_subgroup).config['gitlab.fullpath']).to eq "mygroup_new/mysubgroup/#{hashed_project_in_subgroup.path}" + expect(project_rugged(legacy_project_in_subgroup).config['gitlab.fullpath']).to eq "mygroup_new/mysubgroup/#{legacy_project_in_subgroup.path}" end - expect(project_rugged(project_in_parent_group).config['gitlab.fullpath']).to eq "mygroup_new/#{project_in_parent_group.path}" - expect(project_rugged(hashed_project_in_subgroup).config['gitlab.fullpath']).to eq "mygroup_new/mysubgroup/#{hashed_project_in_subgroup.path}" - expect(project_rugged(legacy_project_in_subgroup).config['gitlab.fullpath']).to eq "mygroup_new/mysubgroup/#{legacy_project_in_subgroup.path}" - end + it 'updates the project storage location' do + repository_project_in_parent_group = create(:project_repository, project: project_in_parent_group) + repository_hashed_project_in_subgroup = create(:project_repository, project: hashed_project_in_subgroup) + repository_legacy_project_in_subgroup = create(:project_repository, project: legacy_project_in_subgroup) + + parent.update(path: 'mygroup_moved') + + expect(repository_project_in_parent_group.reload.disk_path).to eq "mygroup_moved/#{project_in_parent_group.path}" + expect(repository_hashed_project_in_subgroup.reload.disk_path).to eq hashed_project_in_subgroup.disk_path + expect(repository_legacy_project_in_subgroup.reload.disk_path).to eq "mygroup_moved/mysubgroup/#{legacy_project_in_subgroup.path}" + end + + def project_rugged(project) + # Routes are loaded when creating the projects, so we need to manually + # reload them for the below code to be aware of the above UPDATE. + project.route.reload - def project_rugged(project) - rugged_repo(project.repository) + rugged_repo(project.repository) + end end end diff --git a/spec/models/notification_setting_spec.rb b/spec/models/notification_setting_spec.rb index 771d834c4bc..c8ab564e3bc 100644 --- a/spec/models/notification_setting_spec.rb +++ b/spec/models/notification_setting_spec.rb @@ -42,12 +42,7 @@ RSpec.describe NotificationSetting do expect(notification_setting.new_issue).to eq(true) expect(notification_setting.close_issue).to eq(true) expect(notification_setting.merge_merge_request).to eq(true) - - # In Rails 5 assigning a value which is not explicitly `true` or `false` ("nil" in this case) - # to a boolean column transforms it to `true`. - # In Rails 4 it transforms the value to `false` with deprecation warning. - # Replace `eq(Gitlab.rails5?)` with `eq(true)` when removing rails5? code. - expect(notification_setting.close_merge_request).to eq(Gitlab.rails5?) + expect(notification_setting.close_merge_request).to eq(true) expect(notification_setting.reopen_merge_request).to eq(false) end end diff --git a/spec/models/pool_repository_spec.rb b/spec/models/pool_repository_spec.rb index 3d3878b8c39..112d4ab56fc 100644 --- a/spec/models/pool_repository_spec.rb +++ b/spec/models/pool_repository_spec.rb @@ -23,4 +23,25 @@ describe PoolRepository do expect(pool.disk_path).to match(%r{\A@pools/\h{2}/\h{2}/\h{64}}) end end + + describe '#unlink_repository' do + let(:pool) { create(:pool_repository, :ready) } + + context 'when the last member leaves' do + it 'schedules pool removal' do + expect(::ObjectPool::DestroyWorker).to receive(:perform_async).with(pool.id).and_call_original + + pool.unlink_repository(pool.source_project.repository) + end + end + + context 'when the second member leaves' do + it 'does not schedule pool removal' do + create(:project, :repository, pool_repository: pool) + expect(::ObjectPool::DestroyWorker).not_to receive(:perform_async).with(pool.id) + + pool.unlink_repository(pool.source_project.repository) + end + end + end end diff --git a/spec/models/project_spec.rb b/spec/models/project_spec.rb index 5e63f14b720..4b6592020c1 100644 --- a/spec/models/project_spec.rb +++ b/spec/models/project_spec.rb @@ -145,25 +145,10 @@ describe Project do end describe 'ci_pipelines association' do - context 'when feature flag pipeline_ci_sources_only is enabled' do - it 'returns only pipelines from ci_sources' do - stub_feature_flags(pipeline_ci_sources_only: true) + it 'returns only pipelines from ci_sources' do + expect(Ci::Pipeline).to receive(:ci_sources).and_call_original - expect(Ci::Pipeline).to receive(:ci_sources).and_call_original - - subject.ci_pipelines - end - end - - context 'when feature flag pipeline_ci_sources_only is disabled' do - it 'returns all pipelines' do - stub_feature_flags(pipeline_ci_sources_only: false) - - expect(Ci::Pipeline).not_to receive(:ci_sources).and_call_original - expect(Ci::Pipeline).to receive(:all).and_call_original.at_least(:once) - - subject.ci_pipelines - end + subject.ci_pipelines end end end @@ -314,6 +299,13 @@ describe Project do expect(project.errors[:import_url].first).to include('Requests to localhost are not allowed') end + it 'does not allow import_url pointing to the local network' do + project = build(:project, import_url: 'https://192.168.1.1') + + expect(project).to be_invalid + expect(project.errors[:import_url].first).to include('Requests to the local network are not allowed') + end + it "does not allow import_url with invalid ports for new projects" do project = build(:project, import_url: 'http://github.com:25/t.git') @@ -1666,26 +1658,54 @@ describe Project do end describe '#track_project_repository' do - let(:project) { create(:project, :repository) } + shared_examples 'tracks storage location' do + context 'when a project repository entry does not exist' do + it 'creates a new entry' do + expect { project.track_project_repository }.to change(project, :project_repository) + end + + it 'tracks the project storage location' do + project.track_project_repository + + expect(project.project_repository).to have_attributes( + disk_path: project.disk_path, + shard_name: project.repository_storage + ) + end + end - it 'creates a project_repository' do - project.track_project_repository + context 'when a tracking entry exists' do + let!(:project_repository) { create(:project_repository, project: project) } + let!(:shard) { create(:shard, name: 'foo') } - expect(project.reload.project_repository).to be_present - expect(project.project_repository.disk_path).to eq(project.disk_path) - expect(project.project_repository.shard_name).to eq(project.repository_storage) + it 'does not create a new entry in the database' do + expect { project.track_project_repository }.not_to change(project, :project_repository) + end + + it 'updates the project storage location' do + allow(project).to receive(:disk_path).and_return('fancy/new/path') + allow(project).to receive(:repository_storage).and_return('foo') + + project.track_project_repository + + expect(project.project_repository).to have_attributes( + disk_path: 'fancy/new/path', + shard_name: 'foo' + ) + end + end end - it 'updates the project_repository' do - project.track_project_repository + context 'with projects on legacy storage' do + let(:project) { create(:project, :repository, :legacy_storage) } - allow(project).to receive(:disk_path).and_return('@fancy/new/path') + it_behaves_like 'tracks storage location' + end - expect do - project.track_project_repository - end.not_to change(ProjectRepository, :count) + context 'with projects on hashed storage' do + let(:project) { create(:project, :repository) } - expect(project.reload.project_repository.disk_path).to eq(project.disk_path) + it_behaves_like 'tracks storage location' end end @@ -2530,6 +2550,10 @@ describe Project do end context 'when the ref is not protected' do + before do + allow(project).to receive(:protected_for?).with('ref').and_return(false) + end + it 'contains only the CI variables' do is_expected.to contain_exactly(ci_variable) end @@ -2569,42 +2593,139 @@ describe Project do end describe '#protected_for?' do - let(:project) { create(:project) } + let(:project) { create(:project, :repository) } - subject { project.protected_for?('ref') } + subject { project.protected_for?(ref) } - context 'when the ref is not protected' do + shared_examples 'ref is not protected' do before do stub_application_setting( default_branch_protection: Gitlab::Access::PROTECTION_NONE) end it 'returns false' do - is_expected.to be_falsey + is_expected.to be false end end - context 'when the ref is a protected branch' do + shared_examples 'ref is protected branch' do before do - allow(project).to receive(:repository).and_call_original - allow(project).to receive_message_chain(:repository, :branch_exists?).and_return(true) - create(:protected_branch, name: 'ref', project: project) + create(:protected_branch, name: 'master', project: project) end it 'returns true' do - is_expected.to be_truthy + is_expected.to be true end end - context 'when the ref is a protected tag' do + shared_examples 'ref is protected tag' do before do - allow(project).to receive_message_chain(:repository, :branch_exists?).and_return(false) - allow(project).to receive_message_chain(:repository, :tag_exists?).and_return(true) - create(:protected_tag, name: 'ref', project: project) + create(:protected_tag, name: 'v1.0.0', project: project) end it 'returns true' do - is_expected.to be_truthy + is_expected.to be true + end + end + + context 'when ref is nil' do + let(:ref) { nil } + + it 'returns false' do + is_expected.to be false + end + end + + context 'when ref is ref name' do + context 'when ref is ambiguous' do + let(:ref) { 'ref' } + + before do + project.repository.add_branch(project.creator, 'ref', 'master') + project.repository.add_tag(project.creator, 'ref', 'master') + end + + it 'raises an error' do + expect { subject }.to raise_error(Repository::AmbiguousRefError) + end + end + + context 'when the ref is not protected' do + let(:ref) { 'master' } + + it_behaves_like 'ref is not protected' + end + + context 'when the ref is a protected branch' do + let(:ref) { 'master' } + + it_behaves_like 'ref is protected branch' + end + + context 'when the ref is a protected tag' do + let(:ref) { 'v1.0.0' } + + it_behaves_like 'ref is protected tag' + end + + context 'when ref does not exist' do + let(:ref) { 'something' } + + it 'returns false' do + is_expected.to be false + end + end + end + + context 'when ref is full ref' do + context 'when the ref is not protected' do + let(:ref) { 'refs/heads/master' } + + it_behaves_like 'ref is not protected' + end + + context 'when the ref is a protected branch' do + let(:ref) { 'refs/heads/master' } + + it_behaves_like 'ref is protected branch' + end + + context 'when the ref is a protected tag' do + let(:ref) { 'refs/tags/v1.0.0' } + + it_behaves_like 'ref is protected tag' + end + + context 'when branch ref name is a full tag ref' do + let(:ref) { 'refs/tags/something' } + + before do + project.repository.add_branch(project.creator, ref, 'master') + end + + context 'when ref is not protected' do + it 'returns false' do + is_expected.to be false + end + end + + context 'when ref is a protected branch' do + before do + create(:protected_branch, name: 'refs/tags/something', project: project) + end + + it 'returns true' do + is_expected.to be true + end + end + end + + context 'when ref does not exist' do + let(:ref) { 'refs/heads/something' } + + it 'returns false' do + is_expected.to be false + end end end end @@ -2824,7 +2945,7 @@ describe Project do it 'shows full error updating an invalid MR' do error_message = 'Failed to replace merge_requests because one or more of the new records could not be saved.'\ - ' Validate fork Source project is not a fork of the target project' + ' Validate fork Source project is not a fork of the target project' expect { project.append_or_update_attribute(:merge_requests, [create(:merge_request)]) } .to raise_error(ActiveRecord::RecordNotSaved, error_message) @@ -3690,7 +3811,7 @@ describe Project do expect(project.badges.count).to eq 3 end - if Group.supports_nested_groups? + if Group.supports_nested_objects? context 'with nested_groups' do let(:parent_group) { create(:group) } diff --git a/spec/models/prometheus_metric_spec.rb b/spec/models/prometheus_metric_spec.rb index 3692fe9a559..2b978c1c8ff 100644 --- a/spec/models/prometheus_metric_spec.rb +++ b/spec/models/prometheus_metric_spec.rb @@ -59,11 +59,65 @@ describe PrometheusMetric do end end + it_behaves_like 'group_title', :nginx_ingress_vts, 'Response metrics (NGINX Ingress VTS)' + it_behaves_like 'group_title', :nginx_ingress, 'Response metrics (NGINX Ingress)' + it_behaves_like 'group_title', :ha_proxy, 'Response metrics (HA Proxy)' + it_behaves_like 'group_title', :aws_elb, 'Response metrics (AWS ELB)' + it_behaves_like 'group_title', :nginx, 'Response metrics (NGINX)' + it_behaves_like 'group_title', :kubernetes, 'System metrics (Kubernetes)' it_behaves_like 'group_title', :business, 'Business metrics (Custom)' it_behaves_like 'group_title', :response, 'Response metrics (Custom)' it_behaves_like 'group_title', :system, 'System metrics (Custom)' end + describe '#priority' do + using RSpec::Parameterized::TableSyntax + + where(:group, :priority) do + :nginx_ingress_vts | 10 + :nginx_ingress | 10 + :ha_proxy | 10 + :aws_elb | 10 + :nginx | 10 + :kubernetes | 5 + :business | 0 + :response | -5 + :system | -10 + end + + with_them do + before do + subject.group = group + end + + it { expect(subject.priority).to eq(priority) } + end + end + + describe '#required_metrics' do + using RSpec::Parameterized::TableSyntax + + where(:group, :required_metrics) do + :nginx_ingress_vts | %w(nginx_upstream_responses_total nginx_upstream_response_msecs_avg) + :nginx_ingress | %w(nginx_ingress_controller_requests nginx_ingress_controller_ingress_upstream_latency_seconds_sum) + :ha_proxy | %w(haproxy_frontend_http_requests_total haproxy_frontend_http_responses_total) + :aws_elb | %w(aws_elb_request_count_sum aws_elb_latency_average aws_elb_httpcode_backend_5_xx_sum) + :nginx | %w(nginx_server_requests nginx_server_requestMsec) + :kubernetes | %w(container_memory_usage_bytes container_cpu_usage_seconds_total) + :business | %w() + :response | %w() + :system | %w() + end + + with_them do + before do + subject.group = group + end + + it { expect(subject.required_metrics).to eq(required_metrics) } + end + end + describe '#to_query_metric' do it 'converts to queryable metric object' do expect(subject.to_query_metric).to be_instance_of(Gitlab::Prometheus::Metric) diff --git a/spec/models/release_spec.rb b/spec/models/release_spec.rb index 3f86347c3ae..92ba2d82f58 100644 --- a/spec/models/release_spec.rb +++ b/spec/models/release_spec.rb @@ -1,12 +1,15 @@ require 'rails_helper' RSpec.describe Release do - let(:release) { create(:release) } + let(:user) { create(:user) } + let(:project) { create(:project, :public, :repository) } + let(:release) { create(:release, project: project, author: user) } it { expect(release).to be_valid } describe 'associations' do it { is_expected.to belong_to(:project) } + it { is_expected.to belong_to(:author).class_name('User') } end describe 'validation' do diff --git a/spec/models/remote_mirror_spec.rb b/spec/models/remote_mirror_spec.rb index 5d3c25062d5..224bc9ed935 100644 --- a/spec/models/remote_mirror_spec.rb +++ b/spec/models/remote_mirror_spec.rb @@ -24,6 +24,20 @@ describe RemoteMirror, :mailer do expect(remote_mirror).to be_invalid expect(remote_mirror.errors[:url].first).to include('Username needs to start with an alphanumeric character') end + + it 'does not allow url pointing to localhost' do + remote_mirror = build(:remote_mirror, url: 'http://127.0.0.2/t.git') + + expect(remote_mirror).to be_invalid + expect(remote_mirror.errors[:url].first).to include('Requests to loopback addresses are not allowed') + end + + it 'does not allow url pointing to the local network' do + remote_mirror = build(:remote_mirror, url: 'https://192.168.1.1') + + expect(remote_mirror).to be_invalid + expect(remote_mirror.errors[:url].first).to include('Requests to the local network are not allowed') + end end end diff --git a/spec/models/repository_spec.rb b/spec/models/repository_spec.rb index f09b4b67061..2063b4bbe75 100644 --- a/spec/models/repository_spec.rb +++ b/spec/models/repository_spec.rb @@ -1005,6 +1005,67 @@ describe Repository do end end + describe '#ambiguous_ref?' do + let(:ref) { 'ref' } + + subject { repository.ambiguous_ref?(ref) } + + context 'when ref is ambiguous' do + before do + repository.add_tag(project.creator, ref, 'master') + repository.add_branch(project.creator, ref, 'master') + end + + it 'should be true' do + is_expected.to eq(true) + end + end + + context 'when ref is not ambiguous' do + before do + repository.add_tag(project.creator, ref, 'master') + end + + it 'should be false' do + is_expected.to eq(false) + end + end + end + + describe '#expand_ref' do + let(:ref) { 'ref' } + + subject { repository.expand_ref(ref) } + + context 'when ref is not tag or branch name' do + let(:ref) { 'refs/heads/master' } + + it 'returns nil' do + is_expected.to eq(nil) + end + end + + context 'when ref is tag name' do + before do + repository.add_tag(project.creator, ref, 'master') + end + + it 'returns the tag ref' do + is_expected.to eq("refs/tags/#{ref}") + end + end + + context 'when ref is branch name' do + before do + repository.add_branch(project.creator, ref, 'master') + end + + it 'returns the branch ref' do + is_expected.to eq("refs/heads/#{ref}") + end + end + end + describe '#add_branch' do let(:branch_name) { 'new_feature' } let(:target) { 'master' } diff --git a/spec/models/snippet_spec.rb b/spec/models/snippet_spec.rb index 7a7272ccb60..664dc3fa145 100644 --- a/spec/models/snippet_spec.rb +++ b/spec/models/snippet_spec.rb @@ -423,4 +423,41 @@ describe Snippet do expect(blob.data).to eq(snippet.content) end end + + describe '#embeddable?' do + context 'project snippet' do + [ + { project: :public, snippet: :public, embeddable: true }, + { project: :internal, snippet: :public, embeddable: false }, + { project: :private, snippet: :public, embeddable: false }, + { project: :public, snippet: :internal, embeddable: false }, + { project: :internal, snippet: :internal, embeddable: false }, + { project: :private, snippet: :internal, embeddable: false }, + { project: :public, snippet: :private, embeddable: false }, + { project: :internal, snippet: :private, embeddable: false }, + { project: :private, snippet: :private, embeddable: false } + ].each do |combination| + it 'only returns true when both project and snippet are public' do + project = create(:project, combination[:project]) + snippet = create(:project_snippet, combination[:snippet], project: project) + + expect(snippet.embeddable?).to eq(combination[:embeddable]) + end + end + end + + context 'personal snippet' do + [ + { snippet: :public, embeddable: true }, + { snippet: :internal, embeddable: false }, + { snippet: :private, embeddable: false } + ].each do |combination| + it 'only returns true when snippet is public' do + snippet = create(:personal_snippet, combination[:snippet]) + + expect(snippet.embeddable?).to eq(combination[:embeddable]) + end + end + end + end end diff --git a/spec/models/suggestion_spec.rb b/spec/models/suggestion_spec.rb new file mode 100644 index 00000000000..cafc725dddb --- /dev/null +++ b/spec/models/suggestion_spec.rb @@ -0,0 +1,57 @@ +# frozen_string_literal: true + +require 'spec_helper' + +describe Suggestion do + let(:suggestion) { create(:suggestion) } + + describe 'associations' do + it { is_expected.to belong_to(:note) } + end + + describe 'validations' do + it { is_expected.to validate_presence_of(:note) } + + context 'when suggestion is applied' do + before do + allow(subject).to receive(:applied?).and_return(true) + end + + it { is_expected.to validate_presence_of(:commit_id) } + end + end + + describe '#appliable?' do + context 'when note does not support suggestions' do + it 'returns false' do + expect_next_instance_of(DiffNote) do |note| + allow(note).to receive(:supports_suggestion?) { false } + end + + expect(suggestion).not_to be_appliable + end + end + + context 'when patch is already applied' do + let(:suggestion) { create(:suggestion, :applied) } + + it 'returns false' do + expect(suggestion).not_to be_appliable + end + end + + context 'when merge request is not opened' do + let(:merge_request) { create(:merge_request, :merged) } + let(:note) do + create(:diff_note_on_merge_request, project: merge_request.project, + noteable: merge_request) + end + + let(:suggestion) { create(:suggestion, note: note) } + + it 'returns false' do + expect(suggestion).not_to be_appliable + end + end + end +end diff --git a/spec/models/user_spec.rb b/spec/models/user_spec.rb index ff075e65c76..33842e74b92 100644 --- a/spec/models/user_spec.rb +++ b/spec/models/user_spec.rb @@ -45,6 +45,7 @@ describe User do it { is_expected.to have_many(:uploads) } it { is_expected.to have_many(:reported_abuse_reports).dependent(:destroy).class_name('AbuseReport') } it { is_expected.to have_many(:custom_attributes).class_name('UserCustomAttribute') } + it { is_expected.to have_many(:releases).dependent(:nullify) } describe "#abuse_report" do let(:current_user) { create(:user) } @@ -1965,7 +1966,7 @@ describe User do subject { user.membership_groups } - if Group.supports_nested_groups? + if Group.supports_nested_objects? it { is_expected.to contain_exactly parent_group, child_group } else it { is_expected.to contain_exactly parent_group } @@ -2346,7 +2347,7 @@ describe User do group.add_owner(user) end - if Group.supports_nested_groups? + if Group.supports_nested_objects? it 'returns all groups' do is_expected.to match_array [ group, diff --git a/spec/policies/group_policy_spec.rb b/spec/policies/group_policy_spec.rb index 9d0093e8159..be1804c5ce0 100644 --- a/spec/policies/group_policy_spec.rb +++ b/spec/policies/group_policy_spec.rb @@ -25,7 +25,8 @@ describe GroupPolicy do :read_cluster, :create_cluster, :update_cluster, - :admin_cluster + :admin_cluster, + :add_cluster ] end @@ -147,7 +148,7 @@ describe GroupPolicy do let(:current_user) { owner } it do - allow(Group).to receive(:supports_nested_groups?).and_return(true) + allow(Group).to receive(:supports_nested_objects?).and_return(true) expect_allowed(*guest_permissions) expect_allowed(*reporter_permissions) @@ -161,7 +162,7 @@ describe GroupPolicy do let(:current_user) { admin } it do - allow(Group).to receive(:supports_nested_groups?).and_return(true) + allow(Group).to receive(:supports_nested_objects?).and_return(true) expect_allowed(*guest_permissions) expect_allowed(*reporter_permissions) @@ -173,7 +174,7 @@ describe GroupPolicy do describe 'when nested group support feature is disabled' do before do - allow(Group).to receive(:supports_nested_groups?).and_return(false) + allow(Group).to receive(:supports_nested_objects?).and_return(false) end context 'admin' do @@ -282,7 +283,7 @@ describe GroupPolicy do let(:current_user) { owner } it do - allow(Group).to receive(:supports_nested_groups?).and_return(true) + allow(Group).to receive(:supports_nested_objects?).and_return(true) expect_allowed(*guest_permissions) expect_allowed(*reporter_permissions) @@ -382,4 +383,14 @@ describe GroupPolicy do it { expect_disallowed(:change_share_with_group_lock) } end end + + it_behaves_like 'clusterable policies' do + let(:clusterable) { create(:group) } + let(:cluster) do + create(:cluster, + :provided_by_gcp, + :group, + groups: [clusterable]) + end + end end diff --git a/spec/policies/issuable_policy_spec.rb b/spec/policies/issuable_policy_spec.rb index d1bf98995e7..db3df760472 100644 --- a/spec/policies/issuable_policy_spec.rb +++ b/spec/policies/issuable_policy_spec.rb @@ -7,6 +7,33 @@ describe IssuablePolicy, models: true do let(:policies) { described_class.new(user, issue) } describe '#rules' do + context 'when user is author of issuable' do + let(:merge_request) { create(:merge_request, source_project: project, author: user) } + let(:policies) { described_class.new(user, merge_request) } + + context 'when user is able to read project' do + it 'enables user to read and update issuables' do + expect(policies).to be_allowed(:read_issue, :update_issue, :reopen_issue, :read_merge_request, :update_merge_request) + end + end + + context 'when project is private' do + let(:project) { create(:project, :private) } + + context 'when user belongs to the projects team' do + it 'enables user to read and update issuables' do + project.add_maintainer(user) + + expect(policies).to be_allowed(:read_issue, :update_issue, :reopen_issue, :read_merge_request, :update_merge_request) + end + end + + it 'disallows user from reading and updating issuables from that project' do + expect(policies).to be_disallowed(:read_issue, :update_issue, :reopen_issue, :read_merge_request, :update_merge_request) + end + end + end + context 'when discussion is locked for the issuable' do let(:issue) { create(:issue, project: project, discussion_locked: true) } diff --git a/spec/policies/project_policy_spec.rb b/spec/policies/project_policy_spec.rb index 69468f9ad85..9cb20854f6e 100644 --- a/spec/policies/project_policy_spec.rb +++ b/spec/policies/project_policy_spec.rb @@ -15,7 +15,7 @@ describe ProjectPolicy do read_project_for_iids read_issue_iid read_merge_request_iid read_label read_milestone read_project_snippet read_project_member read_note create_project create_issue create_note upload_file create_merge_request_in - award_emoji + award_emoji read_release ] end @@ -38,7 +38,7 @@ describe ProjectPolicy do update_commit_status create_build update_build create_pipeline update_pipeline create_merge_request_from create_wiki push_code resolve_note create_container_image update_container_image - create_environment create_deployment + create_environment create_deployment create_release update_release ] end @@ -48,7 +48,7 @@ describe ProjectPolicy do update_deployment admin_project_snippet admin_project_member admin_note admin_wiki admin_project admin_commit_status admin_build admin_container_image - admin_pipeline admin_environment admin_deployment + admin_pipeline admin_environment admin_deployment destroy_release add_cluster ] end @@ -56,7 +56,7 @@ describe ProjectPolicy do %i[ download_code fork_project read_commit_status read_pipeline read_container_image build_download_code build_read_container_image - download_wiki_code + download_wiki_code read_release ] end @@ -183,7 +183,8 @@ describe ProjectPolicy do :create_pipeline_schedule, :read_pipeline_schedule, :update_pipeline_schedule, :admin_pipeline_schedule, :destroy_pipeline_schedule, :create_environment, :read_environment, :update_environment, :admin_environment, :destroy_environment, :create_cluster, :read_cluster, :update_cluster, :admin_cluster, - :create_deployment, :read_deployment, :update_deployment, :admin_deployment, :destroy_deployment + :create_deployment, :read_deployment, :update_deployment, :admin_deployment, :destroy_deployment, + :destroy_release ] expect_disallowed(*repository_permissions) @@ -465,4 +466,14 @@ describe ProjectPolicy do expect_disallowed(*maintainer_abilities) end end + + it_behaves_like 'clusterable policies' do + let(:clusterable) { create(:project, :repository) } + let(:cluster) do + create(:cluster, + :provided_by_gcp, + :project, + projects: [clusterable]) + end + end end diff --git a/spec/presenters/clusterable_presenter_spec.rb b/spec/presenters/clusterable_presenter_spec.rb index 4f4ae5e07c5..05afe5347d1 100644 --- a/spec/presenters/clusterable_presenter_spec.rb +++ b/spec/presenters/clusterable_presenter_spec.rb @@ -14,4 +14,68 @@ describe ClusterablePresenter do expect(subject).to be_kind_of(ProjectClusterablePresenter) end end + + shared_examples 'appropriate member permissions' do + context 'with a developer' do + before do + clusterable.add_developer(user) + end + + it { is_expected.to be_falsy } + end + + context 'with a maintainer' do + before do + clusterable.add_maintainer(user) + end + + it { is_expected.to be_truthy } + end + end + + describe '#can_create_cluster?' do + let(:user) { create(:user) } + + subject { described_class.new(clusterable).can_create_cluster? } + + before do + allow(clusterable).to receive(:current_user).and_return(user) + end + + context 'when clusterable is a group' do + let(:clusterable) { create(:group) } + + it_behaves_like 'appropriate member permissions' + end + + context 'when clusterable is a project' do + let(:clusterable) { create(:project, :repository) } + + it_behaves_like 'appropriate member permissions' + end + end + + describe '#can_add_cluster?' do + let(:user) { create(:user) } + + subject { described_class.new(clusterable).can_add_cluster? } + + before do + clusterable.add_maintainer(user) + + allow(clusterable).to receive(:current_user).and_return(user) + end + + context 'when clusterable is a group' do + let(:clusterable) { create(:group) } + + it_behaves_like 'appropriate member permissions' + end + + context 'when clusterable is a project' do + let(:clusterable) { create(:project, :repository) } + + it_behaves_like 'appropriate member permissions' + end + end end diff --git a/spec/presenters/clusters/cluster_presenter_spec.rb b/spec/presenters/clusters/cluster_presenter_spec.rb index 72c5eac3ede..754ba0a594c 100644 --- a/spec/presenters/clusters/cluster_presenter_spec.rb +++ b/spec/presenters/clusters/cluster_presenter_spec.rb @@ -4,9 +4,10 @@ describe Clusters::ClusterPresenter do include Gitlab::Routing.url_helpers let(:cluster) { create(:cluster, :provided_by_gcp, :project) } + let(:user) { create(:user) } subject(:presenter) do - described_class.new(cluster) + described_class.new(cluster, current_user: user) end it 'inherits from Gitlab::View::Presenter::Delegated' do @@ -27,6 +28,129 @@ describe Clusters::ClusterPresenter do end end + describe '#item_link' do + let(:clusterable_presenter) { double('ClusterablePresenter', subject: clusterable) } + + subject { presenter.item_link(clusterable_presenter) } + + context 'for a group cluster' do + let(:cluster) { create(:cluster, cluster_type: :group_type, groups: [group]) } + let(:group) { create(:group, name: 'Foo') } + let(:cluster_link) { "<a href=\"#{group_cluster_path(cluster.group, cluster)}\">#{cluster.name}</a>" } + + before do + group.add_maintainer(user) + end + + shared_examples 'ancestor clusters' do + context 'ancestor clusters', :nested_groups do + let(:root_group) { create(:group, name: 'Root Group') } + let(:parent) { create(:group, name: 'parent', parent: root_group) } + let(:child) { create(:group, name: 'child', parent: parent) } + let(:group) { create(:group, name: 'group', parent: child) } + + before do + root_group.add_maintainer(user) + end + + context 'top level group cluster' do + let(:cluster) { create(:cluster, cluster_type: :group_type, groups: [root_group]) } + + it 'returns full group names and link for cluster' do + expect(subject).to eq("Root Group / #{cluster_link}") + end + + it 'is html safe' do + expect(presenter).to receive(:sanitize).with('Root Group').and_call_original + + expect(subject).to be_html_safe + end + end + + context 'first level group cluster' do + let(:cluster) { create(:cluster, cluster_type: :group_type, groups: [parent]) } + + it 'returns full group names and link for cluster' do + expect(subject).to eq("Root Group / parent / #{cluster_link}") + end + + it 'is html safe' do + expect(presenter).to receive(:sanitize).with('Root Group / parent').and_call_original + + expect(subject).to be_html_safe + end + end + + context 'second level group cluster' do + let(:cluster) { create(:cluster, cluster_type: :group_type, groups: [child]) } + + let(:ellipsis_h) do + /.*ellipsis_h.*/ + end + + it 'returns clipped group names and link for cluster' do + expect(subject).to match("Root Group / #{ellipsis_h} / child / #{cluster_link}") + end + + it 'is html safe' do + expect(presenter).to receive(:sanitize).with('Root Group / parent / child').and_call_original + + expect(subject).to be_html_safe + end + end + end + end + + context 'for a project clusterable' do + let(:clusterable) { project } + let(:project) { create(:project, group: group) } + + it 'returns the group name and the link for cluster' do + expect(subject).to eq("Foo / #{cluster_link}") + end + + it 'is html safe' do + expect(presenter).to receive(:sanitize).with('Foo').and_call_original + + expect(subject).to be_html_safe + end + + include_examples 'ancestor clusters' + end + + context 'for the group clusterable for the cluster' do + let(:clusterable) { group } + + it 'returns link for cluster' do + expect(subject).to eq(cluster_link) + end + + include_examples 'ancestor clusters' + + it 'is html safe' do + expect(subject).to be_html_safe + end + end + end + + context 'for a project cluster' do + let(:cluster) { create(:cluster, :project) } + let(:cluster_link) { "<a href=\"#{project_cluster_path(cluster.project, cluster)}\">#{cluster.name}</a>" } + + before do + cluster.project.add_maintainer(user) + end + + context 'for the project clusterable' do + let(:clusterable) { cluster.project } + + it 'returns link for cluster' do + expect(subject).to eq(cluster_link) + end + end + end + end + describe '#gke_cluster_url' do subject { described_class.new(cluster).gke_cluster_url } @@ -74,6 +198,20 @@ describe Clusters::ClusterPresenter do end end + describe '#cluster_type_description' do + subject { described_class.new(cluster).cluster_type_description } + + context 'project_type cluster' do + it { is_expected.to eq('Project cluster') } + end + + context 'group_type cluster' do + let(:cluster) { create(:cluster, :provided_by_gcp, :group) } + + it { is_expected.to eq('Group cluster') } + end + end + describe '#show_path' do subject { described_class.new(cluster).show_path } diff --git a/spec/requests/api/access_requests_spec.rb b/spec/requests/api/access_requests_spec.rb index e13129967b2..1af6602ea9e 100644 --- a/spec/requests/api/access_requests_spec.rb +++ b/spec/requests/api/access_requests_spec.rb @@ -145,7 +145,7 @@ describe API::AccessRequests do it 'returns 201' do expect do put api("/#{source_type.pluralize}/#{source.id}/access_requests/#{access_requester.id}/approve", maintainer), - access_level: Member::MAINTAINER + params: { access_level: Member::MAINTAINER } expect(response).to have_gitlab_http_status(201) end.to change { source.members.count }.by(1) diff --git a/spec/requests/api/applications_spec.rb b/spec/requests/api/applications_spec.rb index 6154be5c425..e47166544d9 100644 --- a/spec/requests/api/applications_spec.rb +++ b/spec/requests/api/applications_spec.rb @@ -11,7 +11,7 @@ describe API::Applications, :api do context 'authenticated and authorized user' do it 'creates and returns an OAuth application' do expect do - post api('/applications', admin_user), name: 'application_name', redirect_uri: 'http://application.url', scopes: '' + post api('/applications', admin_user), params: { name: 'application_name', redirect_uri: 'http://application.url', scopes: '' } end.to change { Doorkeeper::Application.count }.by 1 application = Doorkeeper::Application.find_by(name: 'application_name', redirect_uri: 'http://application.url') @@ -25,7 +25,7 @@ describe API::Applications, :api do it 'does not allow creating an application with the wrong redirect_uri format' do expect do - post api('/applications', admin_user), name: 'application_name', redirect_uri: 'http://', scopes: '' + post api('/applications', admin_user), params: { name: 'application_name', redirect_uri: 'http://', scopes: '' } end.not_to change { Doorkeeper::Application.count } expect(response).to have_gitlab_http_status(400) @@ -35,7 +35,7 @@ describe API::Applications, :api do it 'does not allow creating an application with a forbidden URI format' do expect do - post api('/applications', admin_user), name: 'application_name', redirect_uri: 'javascript://alert()', scopes: '' + post api('/applications', admin_user), params: { name: 'application_name', redirect_uri: 'javascript://alert()', scopes: '' } end.not_to change { Doorkeeper::Application.count } expect(response).to have_gitlab_http_status(400) @@ -45,7 +45,7 @@ describe API::Applications, :api do it 'does not allow creating an application without a name' do expect do - post api('/applications', admin_user), redirect_uri: 'http://application.url', scopes: '' + post api('/applications', admin_user), params: { redirect_uri: 'http://application.url', scopes: '' } end.not_to change { Doorkeeper::Application.count } expect(response).to have_gitlab_http_status(400) @@ -55,7 +55,7 @@ describe API::Applications, :api do it 'does not allow creating an application without a redirect_uri' do expect do - post api('/applications', admin_user), name: 'application_name', scopes: '' + post api('/applications', admin_user), params: { name: 'application_name', scopes: '' } end.not_to change { Doorkeeper::Application.count } expect(response).to have_gitlab_http_status(400) @@ -65,7 +65,7 @@ describe API::Applications, :api do it 'does not allow creating an application without scopes' do expect do - post api('/applications', admin_user), name: 'application_name', redirect_uri: 'http://application.url' + post api('/applications', admin_user), params: { name: 'application_name', redirect_uri: 'http://application.url' } end.not_to change { Doorkeeper::Application.count } expect(response).to have_gitlab_http_status(400) @@ -77,7 +77,7 @@ describe API::Applications, :api do context 'authorized user without authorization' do it 'does not create application' do expect do - post api('/applications', user), name: 'application_name', redirect_uri: 'http://application.url', scopes: '' + post api('/applications', user), params: { name: 'application_name', redirect_uri: 'http://application.url', scopes: '' } end.not_to change { Doorkeeper::Application.count } expect(response).to have_gitlab_http_status(403) @@ -87,7 +87,7 @@ describe API::Applications, :api do context 'non-authenticated user' do it 'does not create application' do expect do - post api('/applications'), name: 'application_name', redirect_uri: 'http://application.url' + post api('/applications'), params: { name: 'application_name', redirect_uri: 'http://application.url' } end.not_to change { Doorkeeper::Application.count } expect(response).to have_gitlab_http_status(401) diff --git a/spec/requests/api/avatar_spec.rb b/spec/requests/api/avatar_spec.rb index 26e0435a6d5..17e66725dc9 100644 --- a/spec/requests/api/avatar_spec.rb +++ b/spec/requests/api/avatar_spec.rb @@ -13,7 +13,7 @@ describe API::Avatar do end it 'returns the avatar url' do - get api('/avatar'), { email: 'public@example.com' } + get api('/avatar'), params: { email: 'public@example.com' } expect(response.status).to eq 200 expect(json_response['avatar_url']).to eql("#{::Settings.gitlab.base_url}#{user.avatar.local_url}") @@ -30,7 +30,7 @@ describe API::Avatar do end it 'returns the avatar url from Gravatar' do - get api('/avatar'), { email: 'private@example.com' } + get api('/avatar'), params: { email: 'private@example.com' } expect(response.status).to eq 200 expect(json_response['avatar_url']).to eq('https://gravatar') @@ -53,7 +53,7 @@ describe API::Avatar do end it 'returns the avatar url from Gravatar' do - get api('/avatar'), { email: 'public@example.com' } + get api('/avatar'), params: { email: 'public@example.com' } expect(response.status).to eq 200 expect(json_response['avatar_url']).to eq('https://gravatar') @@ -70,7 +70,7 @@ describe API::Avatar do end it 'returns the avatar url from Gravatar' do - get api('/avatar'), { email: 'private@example.com' } + get api('/avatar'), params: { email: 'private@example.com' } expect(response.status).to eq 200 expect(json_response['avatar_url']).to eq('https://gravatar') @@ -88,7 +88,7 @@ describe API::Avatar do context 'when authenticated' do it 'returns the avatar url' do - get api('/avatar', user), { email: 'public@example.com' } + get api('/avatar', user), params: { email: 'public@example.com' } expect(response.status).to eq 200 expect(json_response['avatar_url']).to eql("#{::Settings.gitlab.base_url}#{user.avatar.local_url}") @@ -97,7 +97,7 @@ describe API::Avatar do context 'when unauthenticated' do it_behaves_like '403 response' do - let(:request) { get api('/avatar'), { email: 'public@example.com' } } + let(:request) { get api('/avatar'), params: { email: 'public@example.com' } } end end end diff --git a/spec/requests/api/award_emoji_spec.rb b/spec/requests/api/award_emoji_spec.rb index 7f3f3ab0977..22f6fcdc922 100644 --- a/spec/requests/api/award_emoji_spec.rb +++ b/spec/requests/api/award_emoji_spec.rb @@ -148,7 +148,7 @@ describe API::AwardEmoji do context "on an issue" do it "creates a new award emoji" do - post api("/projects/#{project.id}/issues/#{issue.iid}/award_emoji", user), name: 'blowfish' + post api("/projects/#{project.id}/issues/#{issue.iid}/award_emoji", user), params: { name: 'blowfish' } expect(response).to have_gitlab_http_status(201) expect(json_response['name']).to eq('blowfish') @@ -162,21 +162,21 @@ describe API::AwardEmoji do end it "returns a 401 unauthorized error if the user is not authenticated" do - post api("/projects/#{project.id}/issues/#{issue.iid}/award_emoji"), name: 'thumbsup' + post api("/projects/#{project.id}/issues/#{issue.iid}/award_emoji"), params: { name: 'thumbsup' } expect(response).to have_gitlab_http_status(401) end it "normalizes +1 as thumbsup award" do - post api("/projects/#{project.id}/issues/#{issue.iid}/award_emoji", user), name: '+1' + post api("/projects/#{project.id}/issues/#{issue.iid}/award_emoji", user), params: { name: '+1' } expect(issue.award_emoji.last.name).to eq("thumbsup") end context 'when the emoji already has been awarded' do it 'returns a 404 status code' do - post api("/projects/#{project.id}/issues/#{issue.iid}/award_emoji", user), name: 'thumbsup' - post api("/projects/#{project.id}/issues/#{issue.iid}/award_emoji", user), name: 'thumbsup' + post api("/projects/#{project.id}/issues/#{issue.iid}/award_emoji", user), params: { name: 'thumbsup' } + post api("/projects/#{project.id}/issues/#{issue.iid}/award_emoji", user), params: { name: 'thumbsup' } expect(response).to have_gitlab_http_status(404) expect(json_response["message"]).to match("has already been taken") @@ -188,7 +188,7 @@ describe API::AwardEmoji do it 'creates a new award emoji' do snippet = create(:project_snippet, :public, project: project) - post api("/projects/#{project.id}/snippets/#{snippet.id}/award_emoji", user), name: 'blowfish' + post api("/projects/#{project.id}/snippets/#{snippet.id}/award_emoji", user), params: { name: 'blowfish' } expect(response).to have_gitlab_http_status(201) expect(json_response['name']).to eq('blowfish') @@ -202,7 +202,7 @@ describe API::AwardEmoji do it 'creates a new award emoji' do expect do - post api("/projects/#{project.id}/issues/#{issue.iid}/notes/#{note.id}/award_emoji", user), name: 'rocket' + post api("/projects/#{project.id}/issues/#{issue.iid}/notes/#{note.id}/award_emoji", user), params: { name: 'rocket' } end.to change { note.award_emoji.count }.from(0).to(1) expect(response).to have_gitlab_http_status(201) @@ -210,15 +210,15 @@ describe API::AwardEmoji do end it "normalizes +1 as thumbsup award" do - post api("/projects/#{project.id}/issues/#{issue.iid}/notes/#{note.id}/award_emoji", user), name: '+1' + post api("/projects/#{project.id}/issues/#{issue.iid}/notes/#{note.id}/award_emoji", user), params: { name: '+1' } expect(note.award_emoji.last.name).to eq("thumbsup") end context 'when the emoji already has been awarded' do it 'returns a 404 status code' do - post api("/projects/#{project.id}/issues/#{issue.iid}/notes/#{note.id}/award_emoji", user), name: 'rocket' - post api("/projects/#{project.id}/issues/#{issue.iid}/notes/#{note.id}/award_emoji", user), name: 'rocket' + post api("/projects/#{project.id}/issues/#{issue.iid}/notes/#{note.id}/award_emoji", user), params: { name: 'rocket' } + post api("/projects/#{project.id}/issues/#{issue.iid}/notes/#{note.id}/award_emoji", user), params: { name: 'rocket' } expect(response).to have_gitlab_http_status(404) expect(json_response["message"]).to match("has already been taken") diff --git a/spec/requests/api/badges_spec.rb b/spec/requests/api/badges_spec.rb index e232e2e04ee..1271324a2ba 100644 --- a/spec/requests/api/badges_spec.rb +++ b/spec/requests/api/badges_spec.rb @@ -103,7 +103,7 @@ describe API::Badges do it_behaves_like 'a 404 response when source is private' do let(:route) do post api("/#{source_type.pluralize}/#{source.id}/badges", stranger), - link_url: example_url, image_url: example_url2 + params: { link_url: example_url, image_url: example_url2 } end end @@ -114,7 +114,7 @@ describe API::Badges do user = public_send(type) post api("/#{source_type.pluralize}/#{source.id}/badges", user), - link_url: example_url, image_url: example_url2 + params: { link_url: example_url, image_url: example_url2 } expect(response).to have_gitlab_http_status(403) end @@ -126,7 +126,7 @@ describe API::Badges do it 'creates a new badge' do expect do post api("/#{source_type.pluralize}/#{source.id}/badges", maintainer), - link_url: example_url, image_url: example_url2 + params: { link_url: example_url, image_url: example_url2 } expect(response).to have_gitlab_http_status(201) end.to change { source.badges.count }.by(1) @@ -139,21 +139,21 @@ describe API::Badges do it 'returns 400 when link_url is not given' do post api("/#{source_type.pluralize}/#{source.id}/badges", maintainer), - link_url: example_url + params: { link_url: example_url } expect(response).to have_gitlab_http_status(400) end it 'returns 400 when image_url is not given' do post api("/#{source_type.pluralize}/#{source.id}/badges", maintainer), - image_url: example_url2 + params: { image_url: example_url2 } expect(response).to have_gitlab_http_status(400) end it 'returns 400 when link_url or image_url is not valid' do post api("/#{source_type.pluralize}/#{source.id}/badges", maintainer), - link_url: 'whatever', image_url: 'whatever' + params: { link_url: 'whatever', image_url: 'whatever' } expect(response).to have_gitlab_http_status(400) end @@ -173,7 +173,7 @@ describe API::Badges do it_behaves_like 'a 404 response when source is private' do let(:route) do put api("/#{source_type.pluralize}/#{source.id}/badges/#{badge.id}", stranger), - link_url: example_url + params: { link_url: example_url } end end @@ -184,7 +184,7 @@ describe API::Badges do user = public_send(type) put api("/#{source_type.pluralize}/#{source.id}/badges/#{badge.id}", user), - link_url: example_url + params: { link_url: example_url } expect(response).to have_gitlab_http_status(403) end @@ -195,7 +195,7 @@ describe API::Badges do context 'when authenticated as a maintainer/owner' do it 'updates the member' do put api("/#{source_type.pluralize}/#{source.id}/badges/#{badge.id}", maintainer), - link_url: example_url, image_url: example_url2 + params: { link_url: example_url, image_url: example_url2 } expect(response).to have_gitlab_http_status(200) expect(json_response['link_url']).to eq(example_url) @@ -206,7 +206,7 @@ describe API::Badges do it 'returns 400 when link_url or image_url is not valid' do put api("/#{source_type.pluralize}/#{source.id}/badges/#{badge.id}", maintainer), - link_url: 'whatever', image_url: 'whatever' + params: { link_url: 'whatever', image_url: 'whatever' } expect(response).to have_gitlab_http_status(400) end diff --git a/spec/requests/api/boards_spec.rb b/spec/requests/api/boards_spec.rb index 7710f19ce4e..ab4f42cad47 100644 --- a/spec/requests/api/boards_spec.rb +++ b/spec/requests/api/boards_spec.rb @@ -41,7 +41,7 @@ describe API::Boards do group_label = create(:group_label, group: group) board_parent.update(group: group) - post api(url, user), label_id: group_label.id + post api(url, user), params: { label_id: group_label.id } expect(response).to have_gitlab_http_status(201) expect(json_response['label']['name']).to eq(group_label.title) @@ -56,7 +56,7 @@ describe API::Boards do group.add_developer(user) sub_group.add_developer(user) - post api(url, user), label_id: group_label.id + post api(url, user), params: { label_id: group_label.id } expect(response).to have_gitlab_http_status(201) expect(json_response['label']['name']).to eq(group_label.title) @@ -73,7 +73,7 @@ describe API::Boards do group.add_developer(user) group_label = create(:group_label, group: group) - post api(url, user), label_id: group_label.id + post api(url, user), params: { label_id: group_label.id } expect(response).to have_gitlab_http_status(201) expect(json_response['label']['name']).to eq(group_label.title) diff --git a/spec/requests/api/branches_spec.rb b/spec/requests/api/branches_spec.rb index 7fff0a6cce6..b38cd66986f 100644 --- a/spec/requests/api/branches_spec.rb +++ b/spec/requests/api/branches_spec.rb @@ -20,8 +20,14 @@ describe API::Branches do let(:route) { "/projects/#{project_id}/repository/branches" } shared_examples_for 'repository branches' do + RSpec::Matchers.define :has_merged_branch_names_count do |expected| + match do |actual| + actual[:merged_branch_names].count == expected + end + end + it 'returns the repository branches' do - get api(route, current_user), per_page: 100 + get api(route, current_user), params: { per_page: 100 } expect(response).to have_gitlab_http_status(200) expect(response).to match_response_schema('public_api/v4/branches') @@ -30,6 +36,12 @@ describe API::Branches do expect(branch_names).to match_array(project.repository.branch_names) end + it 'determines only a limited number of merged branch names' do + expect(API::Entities::Branch).to receive(:represent).with(anything, has_merged_branch_names_count(2)) + + get api(route, current_user), params: { per_page: 2 } + end + context 'when repository is disabled' do include_context 'disabled repository' @@ -42,7 +54,7 @@ describe API::Branches do context 'when search parameter is passed' do context 'and branch exists' do it 'returns correct branches' do - get api(route, user), per_page: 100, search: branch_name + get api(route, user), params: { per_page: 100, search: branch_name } searched_branch_names = json_response.map { |branch| branch['name'] } project_branch_names = project.repository.branch_names.grep(/#{branch_name}/) @@ -53,7 +65,7 @@ describe API::Branches do context 'and branch does not exist' do it 'returns an empty array' do - get api(route, user), per_page: 100, search: 'no_such_branch_name_entropy_of_jabadabadu' + get api(route, user), params: { per_page: 100, search: 'no_such_branch_name_entropy_of_jabadabadu' } expect(json_response).to eq [] end @@ -252,7 +264,7 @@ describe API::Branches do end it 'protects a single branch and developers can push' do - put api(route, current_user), developers_can_push: true + put api(route, current_user), params: { developers_can_push: true } expect(response).to have_gitlab_http_status(200) expect(response).to match_response_schema('public_api/v4/branch') @@ -263,7 +275,7 @@ describe API::Branches do end it 'protects a single branch and developers can merge' do - put api(route, current_user), developers_can_merge: true + put api(route, current_user), params: { developers_can_merge: true } expect(response).to have_gitlab_http_status(200) expect(response).to match_response_schema('public_api/v4/branch') @@ -274,7 +286,7 @@ describe API::Branches do end it 'protects a single branch and developers can push and merge' do - put api(route, current_user), developers_can_push: true, developers_can_merge: true + put api(route, current_user), params: { developers_can_push: true, developers_can_merge: true } expect(response).to have_gitlab_http_status(200) expect(response).to match_response_schema('public_api/v4/branch') @@ -373,7 +385,7 @@ describe API::Branches do it 'updates that a developer cannot push or merge' do put api("/projects/#{project.id}/repository/branches/#{protected_branch.name}/protect", user), - developers_can_push: false, developers_can_merge: false + params: { developers_can_push: false, developers_can_merge: false } expect(response).to have_gitlab_http_status(200) expect(response).to match_response_schema('public_api/v4/branch') @@ -391,7 +403,7 @@ describe API::Branches do it 'updates that a developer can push and merge' do put api("/projects/#{project.id}/repository/branches/#{protected_branch.name}/protect", user), - developers_can_push: true, developers_can_merge: true + params: { developers_can_push: true, developers_can_merge: true } expect(response).to have_gitlab_http_status(200) expect(response).to match_response_schema('public_api/v4/branch') @@ -504,7 +516,7 @@ describe API::Branches do shared_examples_for 'repository new branch' do it 'creates a new branch' do - post api(route, current_user), branch: 'feature1', ref: branch_sha + post api(route, current_user), params: { branch: 'feature1', ref: branch_sha } expect(response).to have_gitlab_http_status(201) expect(response).to match_response_schema('public_api/v4/branch') @@ -549,25 +561,25 @@ describe API::Branches do end it 'returns 400 if branch name is invalid' do - post api(route, user), branch: 'new design', ref: branch_sha + post api(route, user), params: { branch: 'new design', ref: branch_sha } expect(response).to have_gitlab_http_status(400) expect(json_response['message']).to eq('Branch name is invalid') end it 'returns 400 if branch already exists' do - post api(route, user), branch: 'new_design1', ref: branch_sha + post api(route, user), params: { branch: 'new_design1', ref: branch_sha } expect(response).to have_gitlab_http_status(201) - post api(route, user), branch: 'new_design1', ref: branch_sha + post api(route, user), params: { branch: 'new_design1', ref: branch_sha } expect(response).to have_gitlab_http_status(400) expect(json_response['message']).to eq('Branch already exists') end it 'returns 400 if ref name is invalid' do - post api(route, user), branch: 'new_design3', ref: 'foo' + post api(route, user), params: { branch: 'new_design3', ref: 'foo' } expect(response).to have_gitlab_http_status(400) expect(json_response['message']).to eq('Invalid reference name') diff --git a/spec/requests/api/broadcast_messages_spec.rb b/spec/requests/api/broadcast_messages_spec.rb index fe8a14fae9e..0b48b79219c 100644 --- a/spec/requests/api/broadcast_messages_spec.rb +++ b/spec/requests/api/broadcast_messages_spec.rb @@ -56,13 +56,13 @@ describe API::BroadcastMessages do describe 'POST /broadcast_messages' do it 'returns a 401 for anonymous users' do - post api('/broadcast_messages'), attributes_for(:broadcast_message) + post api('/broadcast_messages'), params: attributes_for(:broadcast_message) expect(response).to have_gitlab_http_status(401) end it 'returns a 403 for users' do - post api('/broadcast_messages', user), attributes_for(:broadcast_message) + post api('/broadcast_messages', user), params: attributes_for(:broadcast_message) expect(response).to have_gitlab_http_status(403) end @@ -72,7 +72,7 @@ describe API::BroadcastMessages do attrs = attributes_for(:broadcast_message) attrs.delete(:message) - post api('/broadcast_messages', admin), attrs + post api('/broadcast_messages', admin), params: attrs expect(response).to have_gitlab_http_status(400) expect(json_response['error']).to eq 'message is missing' @@ -81,7 +81,7 @@ describe API::BroadcastMessages do it 'defines sane default start and end times' do time = Time.zone.parse('2016-07-02 10:11:12') travel_to(time) do - post api('/broadcast_messages', admin), message: 'Test message' + post api('/broadcast_messages', admin), params: { message: 'Test message' } expect(response).to have_gitlab_http_status(201) expect(json_response['starts_at']).to eq '2016-07-02T10:11:12.000Z' @@ -92,7 +92,7 @@ describe API::BroadcastMessages do it 'accepts a custom background and foreground color' do attrs = attributes_for(:broadcast_message, color: '#000000', font: '#cecece') - post api('/broadcast_messages', admin), attrs + post api('/broadcast_messages', admin), params: attrs expect(response).to have_gitlab_http_status(201) expect(json_response['color']).to eq attrs[:color] @@ -104,14 +104,14 @@ describe API::BroadcastMessages do describe 'PUT /broadcast_messages/:id' do it 'returns a 401 for anonymous users' do put api("/broadcast_messages/#{message.id}"), - attributes_for(:broadcast_message) + params: attributes_for(:broadcast_message) expect(response).to have_gitlab_http_status(401) end it 'returns a 403 for users' do put api("/broadcast_messages/#{message.id}", user), - attributes_for(:broadcast_message) + params: attributes_for(:broadcast_message) expect(response).to have_gitlab_http_status(403) end @@ -120,7 +120,7 @@ describe API::BroadcastMessages do it 'accepts new background and foreground colors' do attrs = { color: '#000000', font: '#cecece' } - put api("/broadcast_messages/#{message.id}", admin), attrs + put api("/broadcast_messages/#{message.id}", admin), params: attrs expect(response).to have_gitlab_http_status(200) expect(json_response['color']).to eq attrs[:color] @@ -132,7 +132,7 @@ describe API::BroadcastMessages do travel_to(time) do attrs = { starts_at: Time.zone.now, ends_at: 3.hours.from_now } - put api("/broadcast_messages/#{message.id}", admin), attrs + put api("/broadcast_messages/#{message.id}", admin), params: attrs expect(response).to have_gitlab_http_status(200) expect(json_response['starts_at']).to eq '2016-07-02T10:11:12.000Z' @@ -143,7 +143,7 @@ describe API::BroadcastMessages do it 'accepts a new message' do attrs = { message: 'new message' } - put api("/broadcast_messages/#{message.id}", admin), attrs + put api("/broadcast_messages/#{message.id}", admin), params: attrs expect(response).to have_gitlab_http_status(200) expect { message.reload }.to change { message.message }.to('new message') @@ -154,14 +154,14 @@ describe API::BroadcastMessages do describe 'DELETE /broadcast_messages/:id' do it 'returns a 401 for anonymous users' do delete api("/broadcast_messages/#{message.id}"), - attributes_for(:broadcast_message) + params: attributes_for(:broadcast_message) expect(response).to have_gitlab_http_status(401) end it 'returns a 403 for users' do delete api("/broadcast_messages/#{message.id}", user), - attributes_for(:broadcast_message) + params: attributes_for(:broadcast_message) expect(response).to have_gitlab_http_status(403) end diff --git a/spec/requests/api/commit_statuses_spec.rb b/spec/requests/api/commit_statuses_spec.rb index a43304c9b83..9388343c392 100644 --- a/spec/requests/api/commit_statuses_spec.rb +++ b/spec/requests/api/commit_statuses_spec.rb @@ -51,7 +51,7 @@ describe API::CommitStatuses do context 'all commit statuses' do before do - get api(get_url, reporter), all: 1 + get api(get_url, reporter), params: { all: 1 } end it 'returns all commit statuses' do @@ -66,7 +66,7 @@ describe API::CommitStatuses do context 'latest commit statuses for specific ref' do before do - get api(get_url, reporter), ref: 'develop' + get api(get_url, reporter), params: { ref: 'develop' } end it 'returns latest commit statuses for specific ref' do @@ -79,7 +79,7 @@ describe API::CommitStatuses do context 'latest commit statues for specific name' do before do - get api(get_url, reporter), name: 'coverage' + get api(get_url, reporter), params: { name: 'coverage' } end it 'return latest commit statuses for specific name' do @@ -133,7 +133,7 @@ describe API::CommitStatuses do context "for #{status}" do context 'uses only required parameters' do it 'creates commit status' do - post api(post_url, developer), state: status + post api(post_url, developer), params: { state: status } expect(response).to have_gitlab_http_status(201) expect(json_response['sha']).to eq(commit.id) @@ -153,12 +153,12 @@ describe API::CommitStatuses do context 'transitions status from pending' do before do - post api(post_url, developer), state: 'pending' + post api(post_url, developer), params: { state: 'pending' } end %w[running success failed canceled].each do |status| it "to #{status}" do - expect { post api(post_url, developer), state: status }.not_to change { CommitStatus.count } + expect { post api(post_url, developer), params: { state: status } }.not_to change { CommitStatus.count } expect(response).to have_gitlab_http_status(201) expect(json_response['status']).to eq(status) @@ -169,7 +169,7 @@ describe API::CommitStatuses do context 'with all optional parameters' do context 'when creating a commit status' do subject do - post api(post_url, developer), { + post api(post_url, developer), params: { state: 'success', context: 'coverage', ref: 'master', @@ -206,7 +206,7 @@ describe API::CommitStatuses do context 'when updatig a commit status' do before do - post api(post_url, developer), { + post api(post_url, developer), params: { state: 'running', context: 'coverage', ref: 'master', @@ -215,7 +215,7 @@ describe API::CommitStatuses do target_url: 'http://gitlab.com/status' } - post api(post_url, developer), { + post api(post_url, developer), params: { state: 'success', name: 'coverage', ref: 'master', @@ -244,10 +244,10 @@ describe API::CommitStatuses do context 'when retrying a commit status' do before do post api(post_url, developer), - { state: 'failed', name: 'test', ref: 'master' } + params: { state: 'failed', name: 'test', ref: 'master' } post api(post_url, developer), - { state: 'success', name: 'test', ref: 'master' } + params: { state: 'success', name: 'test', ref: 'master' } end it 'correctly posts a new commit status' do @@ -265,7 +265,7 @@ describe API::CommitStatuses do context 'when status is invalid' do before do - post api(post_url, developer), state: 'invalid' + post api(post_url, developer), params: { state: 'invalid' } end it 'does not create commit status' do @@ -287,7 +287,7 @@ describe API::CommitStatuses do let(:sha) { 'invalid_sha' } before do - post api(post_url, developer), state: 'running' + post api(post_url, developer), params: { state: 'running' } end it 'returns not found error' do @@ -297,8 +297,10 @@ describe API::CommitStatuses do context 'when target URL is an invalid address' do before do - post api(post_url, developer), state: 'pending', - target_url: 'invalid url' + post api(post_url, developer), params: { + state: 'pending', + target_url: 'invalid url' + } end it 'responds with bad request status and validation errors' do @@ -311,7 +313,7 @@ describe API::CommitStatuses do context 'reporter user' do before do - post api(post_url, reporter), state: 'running' + post api(post_url, reporter), params: { state: 'running' } end it 'does not create commit status' do @@ -321,7 +323,7 @@ describe API::CommitStatuses do context 'guest user' do before do - post api(post_url, guest), state: 'running' + post api(post_url, guest), params: { state: 'running' } end it 'does not create commit status' do diff --git a/spec/requests/api/commits_spec.rb b/spec/requests/api/commits_spec.rb index 9e599c2175f..6b9bc6eda6a 100644 --- a/spec/requests/api/commits_spec.rb +++ b/spec/requests/api/commits_spec.rb @@ -281,11 +281,11 @@ describe API::Commits do it 'does not increment the usage counters using access token authentication' do expect(::Gitlab::WebIdeCommitsCounter).not_to receive(:increment) - post api(url, user), valid_c_params + post api(url, user), params: valid_c_params end it 'a new file in project repo' do - post api(url, user), valid_c_params + post api(url, user), params: valid_c_params expect(response).to have_gitlab_http_status(201) expect(json_response['title']).to eq(message) @@ -294,7 +294,7 @@ describe API::Commits do end it 'a new file with utf8 chars in project repo' do - post api(url, user), valid_utf8_c_params + post api(url, user), params: valid_utf8_c_params expect(response).to have_gitlab_http_status(201) expect(json_response['title']).to eq(message) @@ -303,7 +303,7 @@ describe API::Commits do end it 'returns a 400 bad request if file exists' do - post api(url, user), invalid_c_params + post api(url, user), params: invalid_c_params expect(response).to have_gitlab_http_status(400) end @@ -312,7 +312,7 @@ describe API::Commits do let(:url) { "/projects/#{CGI.escape(project.full_path)}/repository/commits" } it 'a new file in project repo' do - post api(url, user), valid_c_params + post api(url, user), params: valid_c_params expect(response).to have_gitlab_http_status(201) end @@ -347,14 +347,14 @@ describe API::Commits do end it 'an existing file in project repo' do - post api(url, user), valid_d_params + post api(url, user), params: valid_d_params expect(response).to have_gitlab_http_status(201) expect(json_response['title']).to eq(message) end it 'returns a 400 bad request if file does not exist' do - post api(url, user), invalid_d_params + post api(url, user), params: invalid_d_params expect(response).to have_gitlab_http_status(400) end @@ -392,14 +392,14 @@ describe API::Commits do end it 'an existing file in project repo' do - post api(url, user), valid_m_params + post api(url, user), params: valid_m_params expect(response).to have_gitlab_http_status(201) expect(json_response['title']).to eq(message) end it 'returns a 400 bad request if file does not exist' do - post api(url, user), invalid_m_params + post api(url, user), params: invalid_m_params expect(response).to have_gitlab_http_status(400) end @@ -435,14 +435,14 @@ describe API::Commits do end it 'an existing file in project repo' do - post api(url, user), valid_u_params + post api(url, user), params: valid_u_params expect(response).to have_gitlab_http_status(201) expect(json_response['title']).to eq(message) end it 'returns a 400 bad request if file does not exist' do - post api(url, user), invalid_u_params + post api(url, user), params: invalid_u_params expect(response).to have_gitlab_http_status(400) end @@ -467,7 +467,7 @@ describe API::Commits do end it 'responds with success' do - post api(url, user), params + post api(url, user), params: params expect(response).to have_gitlab_http_status(201) expect(json_response['title']).to eq(message) @@ -477,7 +477,7 @@ describe API::Commits do let(:execute_filemode) { false } it 'responds with success' do - post api(url, user), params + post api(url, user), params: params expect(response).to have_gitlab_http_status(201) expect(json_response['title']).to eq(message) @@ -488,7 +488,7 @@ describe API::Commits do let(:file_path) { 'foo/bar.baz' } it "responds with 400" do - post api(url, user), params + post api(url, user), params: params expect(response).to have_gitlab_http_status(400) expect(json_response['message']).to eq("A file with this name doesn't exist") @@ -566,28 +566,28 @@ describe API::Commits do end it 'are committed as one in project repo' do - post api(url, user), valid_mo_params + post api(url, user), params: valid_mo_params expect(response).to have_gitlab_http_status(201) expect(json_response['title']).to eq(message) end it 'includes the commit stats' do - post api(url, user), valid_mo_params + post api(url, user), params: valid_mo_params expect(response).to have_gitlab_http_status(201) expect(json_response).to include 'stats' end it "doesn't include the commit stats when stats is false" do - post api(url, user), valid_mo_params.merge(stats: false) + post api(url, user), params: valid_mo_params.merge(stats: false) expect(response).to have_gitlab_http_status(201) expect(json_response).not_to include 'stats' end it 'return a 400 bad request if there are any issues' do - post api(url, user), invalid_mo_params + post api(url, user), params: invalid_mo_params expect(response).to have_gitlab_http_status(400) end @@ -613,13 +613,13 @@ describe API::Commits do end it 'allows pushing to the source branch of the merge request' do - post api(url, user), push_params('feature') + post api(url, user), params: push_params('feature') expect(response).to have_gitlab_http_status(:created) end it 'denies pushing to another branch' do - post api(url, user), push_params('other-branch') + post api(url, user), params: push_params('other-branch') expect(response).to have_gitlab_http_status(:forbidden) end @@ -651,7 +651,7 @@ describe API::Commits do context 'for a valid commit' do it 'returns all refs with no scope' do - get api(route, current_user), per_page: 100 + get api(route, current_user), params: { per_page: 100 } refs = project.repository.branch_names_contains(commit_id).map {|name| ['branch', name]} refs.concat(project.repository.tag_names_contains(commit_id).map {|name| ['tag', name]}) @@ -663,7 +663,7 @@ describe API::Commits do end it 'returns all refs' do - get api(route, current_user), type: 'all', per_page: 100 + get api(route, current_user), params: { type: 'all', per_page: 100 } refs = project.repository.branch_names_contains(commit_id).map {|name| ['branch', name]} refs.concat(project.repository.tag_names_contains(commit_id).map {|name| ['tag', name]}) @@ -673,7 +673,7 @@ describe API::Commits do end it 'returns the branch refs' do - get api(route, current_user), type: 'branch', per_page: 100 + get api(route, current_user), params: { type: 'branch', per_page: 100 } refs = project.repository.branch_names_contains(commit_id).map {|name| ['branch', name]} @@ -682,7 +682,7 @@ describe API::Commits do end it 'returns the tag refs' do - get api(route, current_user), type: 'tag', per_page: 100 + get api(route, current_user), params: { type: 'tag', per_page: 100 } refs = project.repository.tag_names_contains(commit_id).map {|name| ['tag', name]} @@ -750,14 +750,14 @@ describe API::Commits do end it "is false it does not include stats" do - get api(route, user), stats: false + get api(route, user), params: { stats: false } expect(response).to have_gitlab_http_status(200) expect(json_response).not_to include 'stats' end it "is true it includes stats" do - get api(route, user), stats: true + get api(route, user), params: { stats: true } expect(response).to have_gitlab_http_status(200) expect(json_response).to include 'stats' @@ -1063,7 +1063,7 @@ describe API::Commits do shared_examples_for 'ref cherry-pick' do context 'when ref exists' do it 'cherry-picks the ref commit' do - post api(route, current_user), branch: branch + post api(route, current_user), params: { branch: branch } expect(response).to have_gitlab_http_status(201) expect(response).to match_response_schema('public_api/v4/commit/basic') @@ -1078,7 +1078,7 @@ describe API::Commits do include_context 'disabled repository' it_behaves_like '403 response' do - let(:request) { post api(route, current_user), branch: 'master' } + let(:request) { post api(route, current_user), params: { branch: 'master' } } end end end @@ -1087,13 +1087,13 @@ describe API::Commits do let(:project) { create(:project, :public, :repository) } it_behaves_like '403 response' do - let(:request) { post api(route), branch: 'master' } + let(:request) { post api(route), params: { branch: 'master' } } end end context 'when unauthenticated', 'and project is private' do it_behaves_like '404 response' do - let(:request) { post api(route), branch: 'master' } + let(:request) { post api(route), params: { branch: 'master' } } let(:message) { '404 Project Not Found' } end end @@ -1107,7 +1107,7 @@ describe API::Commits do let(:commit_id) { 'unknown' } it_behaves_like '404 response' do - let(:request) { post api(route, current_user), branch: 'master' } + let(:request) { post api(route, current_user), params: { branch: 'master' } } let(:message) { '404 Commit Not Found' } end end @@ -1121,21 +1121,21 @@ describe API::Commits do context 'when branch is empty' do ['', ' '].each do |branch| it_behaves_like '400 response' do - let(:request) { post api(route, current_user), branch: branch } + let(:request) { post api(route, current_user), params: { branch: branch } } end end end context 'when branch does not exist' do it_behaves_like '404 response' do - let(:request) { post api(route, current_user), branch: 'foo' } + let(:request) { post api(route, current_user), params: { branch: 'foo' } } let(:message) { '404 Branch Not Found' } end end context 'when commit is already included in the target branch' do it_behaves_like '400 response' do - let(:request) { post api(route, current_user), branch: 'markdown' } + let(:request) { post api(route, current_user), params: { branch: 'markdown' } } end end @@ -1150,7 +1150,7 @@ describe API::Commits do let(:commit_id) { branch_with_slash.name } it_behaves_like '404 response' do - let(:request) { post api(route, current_user), branch: 'master' } + let(:request) { post api(route, current_user), params: { branch: 'master' } } end end @@ -1181,7 +1181,7 @@ describe API::Commits do end it 'returns 400 if you are not allowed to push to the target branch' do - post api(route, current_user), branch: 'feature' + post api(route, current_user), params: { branch: 'feature' } expect(response).to have_gitlab_http_status(:forbidden) expect(json_response['message']).to match(/You are not allowed to push into this branch/) @@ -1195,13 +1195,13 @@ describe API::Commits do let(:project_id) { forked_project.id } it 'allows access from a maintainer that to the source branch' do - post api(route, user), branch: 'feature' + post api(route, user), params: { branch: 'feature' } expect(response).to have_gitlab_http_status(:created) end it 'denies cherry picking to another branch' do - post api(route, user), branch: 'master' + post api(route, user), params: { branch: 'master' } expect(response).to have_gitlab_http_status(:forbidden) end @@ -1217,7 +1217,7 @@ describe API::Commits do shared_examples_for 'ref revert' do context 'when ref exists' do it 'reverts the ref commit' do - post api(route, current_user), branch: branch + post api(route, current_user), params: { branch: branch } expect(response).to have_gitlab_http_status(201) expect(response).to match_response_schema('public_api/v4/commit/basic') @@ -1233,7 +1233,7 @@ describe API::Commits do include_context 'disabled repository' it_behaves_like '403 response' do - let(:request) { post api(route, current_user), branch: branch } + let(:request) { post api(route, current_user), params: { branch: branch } } end end end @@ -1242,13 +1242,13 @@ describe API::Commits do let(:project) { create(:project, :public, :repository) } it_behaves_like '403 response' do - let(:request) { post api(route), branch: branch } + let(:request) { post api(route), params: { branch: branch } } end end context 'when unauthenticated', 'and project is private' do it_behaves_like '404 response' do - let(:request) { post api(route), branch: branch } + let(:request) { post api(route), params: { branch: branch } } let(:message) { '404 Project Not Found' } end end @@ -1262,7 +1262,7 @@ describe API::Commits do let(:commit_id) { 'unknown' } it_behaves_like '404 response' do - let(:request) { post api(route, current_user), branch: branch } + let(:request) { post api(route, current_user), params: { branch: branch } } let(:message) { '404 Commit Not Found' } end end @@ -1276,14 +1276,14 @@ describe API::Commits do context 'when branch is empty' do ['', ' '].each do |branch| it_behaves_like '400 response' do - let(:request) { post api(route, current_user), branch: branch } + let(:request) { post api(route, current_user), params: { branch: branch } } end end end context 'when branch does not exist' do it_behaves_like '404 response' do - let(:request) { post api(route, current_user), branch: 'foo' } + let(:request) { post api(route, current_user), params: { branch: 'foo' } } let(:message) { '404 Branch Not Found' } end end @@ -1311,7 +1311,7 @@ describe API::Commits do end it 'returns 400 if you are not allowed to push to the target branch' do - post api(route, current_user), branch: 'feature' + post api(route, current_user), params: { branch: 'feature' } expect(response).to have_gitlab_http_status(:forbidden) expect(json_response['message']).to match(/You are not allowed to push into this branch/) @@ -1329,7 +1329,7 @@ describe API::Commits do shared_examples_for 'ref new comment' do context 'when ref exists' do it 'creates the comment' do - post api(route, current_user), note: note + post api(route, current_user), params: { note: note } expect(response).to have_gitlab_http_status(201) expect(response).to match_response_schema('public_api/v4/commit_note') @@ -1344,7 +1344,7 @@ describe API::Commits do include_context 'disabled repository' it_behaves_like '403 response' do - let(:request) { post api(route, current_user), note: 'My comment' } + let(:request) { post api(route, current_user), params: { note: 'My comment' } } end end end @@ -1353,13 +1353,13 @@ describe API::Commits do let(:project) { create(:project, :public, :repository) } it_behaves_like '400 response' do - let(:request) { post api(route), note: 'My comment' } + let(:request) { post api(route), params: { note: 'My comment' } } end end context 'when unauthenticated', 'and project is private' do it_behaves_like '404 response' do - let(:request) { post api(route), note: 'My comment' } + let(:request) { post api(route), params: { note: 'My comment' } } let(:message) { '404 Project Not Found' } end end @@ -1370,7 +1370,7 @@ describe API::Commits do it_behaves_like 'ref new comment' it 'returns the inline comment' do - post api(route, current_user), note: 'My comment', path: project.repository.commit.raw_diffs.first.new_path, line: 1, line_type: 'new' + post api(route, current_user), params: { note: 'My comment', path: project.repository.commit.raw_diffs.first.new_path, line: 1, line_type: 'new' } expect(response).to have_gitlab_http_status(201) expect(response).to match_response_schema('public_api/v4/commit_note') @@ -1384,7 +1384,7 @@ describe API::Commits do let(:commit_id) { 'unknown' } it_behaves_like '404 response' do - let(:request) { post api(route, current_user), note: 'My comment' } + let(:request) { post api(route, current_user), params: { note: 'My comment' } } let(:message) { '404 Commit Not Found' } end end @@ -1405,7 +1405,7 @@ describe API::Commits do let(:commit_id) { branch_with_slash.name } it_behaves_like '404 response' do - let(:request) { post api(route, current_user), note: 'My comment' } + let(:request) { post api(route, current_user), params: { note: 'My comment' } } end end diff --git a/spec/requests/api/deploy_keys_spec.rb b/spec/requests/api/deploy_keys_spec.rb index 32fc704a79b..b93ee148736 100644 --- a/spec/requests/api/deploy_keys_spec.rb +++ b/spec/requests/api/deploy_keys_spec.rb @@ -72,14 +72,14 @@ describe API::DeployKeys do describe 'POST /projects/:id/deploy_keys' do it 'does not create an invalid ssh key' do - post api("/projects/#{project.id}/deploy_keys", admin), { title: 'invalid key' } + post api("/projects/#{project.id}/deploy_keys", admin), params: { title: 'invalid key' } expect(response).to have_gitlab_http_status(400) expect(json_response['error']).to eq('key is missing') end it 'does not create a key without title' do - post api("/projects/#{project.id}/deploy_keys", admin), key: 'some key' + post api("/projects/#{project.id}/deploy_keys", admin), params: { key: 'some key' } expect(response).to have_gitlab_http_status(400) expect(json_response['error']).to eq('title is missing') @@ -89,7 +89,7 @@ describe API::DeployKeys do key_attrs = attributes_for :another_key expect do - post api("/projects/#{project.id}/deploy_keys", admin), key_attrs + post api("/projects/#{project.id}/deploy_keys", admin), params: key_attrs end.to change { project.deploy_keys.count }.by(1) new_key = project.deploy_keys.last @@ -99,7 +99,7 @@ describe API::DeployKeys do it 'returns an existing ssh key when attempting to add a duplicate' do expect do - post api("/projects/#{project.id}/deploy_keys", admin), { key: deploy_key.key, title: deploy_key.title } + post api("/projects/#{project.id}/deploy_keys", admin), params: { key: deploy_key.key, title: deploy_key.title } end.not_to change { project.deploy_keys.count } expect(response).to have_gitlab_http_status(201) @@ -107,7 +107,7 @@ describe API::DeployKeys do it 'joins an existing ssh key to a new project' do expect do - post api("/projects/#{project2.id}/deploy_keys", admin), { key: deploy_key.key, title: deploy_key.title } + post api("/projects/#{project2.id}/deploy_keys", admin), params: { key: deploy_key.key, title: deploy_key.title } end.to change { project2.deploy_keys.count }.by(1) expect(response).to have_gitlab_http_status(201) @@ -116,7 +116,7 @@ describe API::DeployKeys do it 'accepts can_push parameter' do key_attrs = attributes_for(:another_key).merge(can_push: true) - post api("/projects/#{project.id}/deploy_keys", admin), key_attrs + post api("/projects/#{project.id}/deploy_keys", admin), params: key_attrs expect(response).to have_gitlab_http_status(201) expect(json_response['can_push']).to eq(true) @@ -131,7 +131,7 @@ describe API::DeployKeys do it 'updates a public deploy key as admin' do expect do - put api("/projects/#{project.id}/deploy_keys/#{deploy_key.id}", admin), { title: 'new title' } + put api("/projects/#{project.id}/deploy_keys/#{deploy_key.id}", admin), params: { title: 'new title' } end.not_to change(deploy_key, :title) expect(response).to have_gitlab_http_status(200) @@ -139,7 +139,7 @@ describe API::DeployKeys do it 'does not update a public deploy key as non admin' do expect do - put api("/projects/#{project.id}/deploy_keys/#{deploy_key.id}", user), { title: 'new title' } + put api("/projects/#{project.id}/deploy_keys/#{deploy_key.id}", user), params: { title: 'new title' } end.not_to change(deploy_key, :title) expect(response).to have_gitlab_http_status(404) @@ -149,7 +149,7 @@ describe API::DeployKeys do project_private_deploy_key expect do - put api("/projects/#{project.id}/deploy_keys/#{private_deploy_key.id}", admin), { title: '' } + put api("/projects/#{project.id}/deploy_keys/#{private_deploy_key.id}", admin), params: { title: '' } end.not_to change(deploy_key, :title) expect(response).to have_gitlab_http_status(400) @@ -158,7 +158,7 @@ describe API::DeployKeys do it 'updates a private ssh key with correct attributes' do project_private_deploy_key - put api("/projects/#{project.id}/deploy_keys/#{private_deploy_key.id}", admin), { title: 'new title', can_push: true } + put api("/projects/#{project.id}/deploy_keys/#{private_deploy_key.id}", admin), params: { title: 'new title', can_push: true } expect(json_response['id']).to eq(private_deploy_key.id) expect(json_response['title']).to eq('new title') diff --git a/spec/requests/api/doorkeeper_access_spec.rb b/spec/requests/api/doorkeeper_access_spec.rb index 308134eba72..d74484c8d29 100644 --- a/spec/requests/api/doorkeeper_access_spec.rb +++ b/spec/requests/api/doorkeeper_access_spec.rb @@ -7,20 +7,20 @@ describe 'doorkeeper access' do describe "unauthenticated" do it "returns authentication success" do - get api("/user"), access_token: token.token + get api("/user"), params: { access_token: token.token } expect(response).to have_gitlab_http_status(200) end include_examples 'user login request with unique ip limit' do def request - get api('/user'), access_token: token.token + get api('/user'), params: { access_token: token.token } end end end describe "when token invalid" do it "returns authentication error" do - get api("/user"), access_token: "123a" + get api("/user"), params: { access_token: "123a" } expect(response).to have_gitlab_http_status(401) end end @@ -41,7 +41,7 @@ describe 'doorkeeper access' do describe "when user is blocked" do it "returns authorization error" do user.block - get api("/user"), access_token: token.token + get api("/user"), params: { access_token: token.token } expect(response).to have_gitlab_http_status(403) end @@ -50,7 +50,7 @@ describe 'doorkeeper access' do describe "when user is ldap_blocked" do it "returns authorization error" do user.ldap_block - get api("/user"), access_token: token.token + get api("/user"), params: { access_token: token.token } expect(response).to have_gitlab_http_status(403) end diff --git a/spec/requests/api/environments_spec.rb b/spec/requests/api/environments_spec.rb index f3db0c122a0..493d3642255 100644 --- a/spec/requests/api/environments_spec.rb +++ b/spec/requests/api/environments_spec.rb @@ -47,7 +47,7 @@ describe API::Environments do describe 'POST /projects/:id/environments' do context 'as a member' do it 'creates a environment with valid params' do - post api("/projects/#{project.id}/environments", user), name: "mepmep" + post api("/projects/#{project.id}/environments", user), params: { name: "mepmep" } expect(response).to have_gitlab_http_status(201) expect(json_response['name']).to eq('mepmep') @@ -56,19 +56,19 @@ describe API::Environments do end it 'requires name to be passed' do - post api("/projects/#{project.id}/environments", user), external_url: 'test.gitlab.com' + post api("/projects/#{project.id}/environments", user), params: { external_url: 'test.gitlab.com' } expect(response).to have_gitlab_http_status(400) end it 'returns a 400 if environment already exists' do - post api("/projects/#{project.id}/environments", user), name: environment.name + post api("/projects/#{project.id}/environments", user), params: { name: environment.name } expect(response).to have_gitlab_http_status(400) end it 'returns a 400 if slug is specified' do - post api("/projects/#{project.id}/environments", user), name: "foo", slug: "foo" + post api("/projects/#{project.id}/environments", user), params: { name: "foo", slug: "foo" } expect(response).to have_gitlab_http_status(400) expect(json_response["error"]).to eq("slug is automatically generated and cannot be changed") @@ -77,13 +77,13 @@ describe API::Environments do context 'a non member' do it 'rejects the request' do - post api("/projects/#{project.id}/environments", non_member), name: 'gitlab.com' + post api("/projects/#{project.id}/environments", non_member), params: { name: 'gitlab.com' } expect(response).to have_gitlab_http_status(404) end it 'returns a 400 when the required params are missing' do - post api("/projects/12345/environments", non_member), external_url: 'http://env.git.com' + post api("/projects/12345/environments", non_member), params: { external_url: 'http://env.git.com' } end end end @@ -92,7 +92,7 @@ describe API::Environments do it 'returns a 200 if name and external_url are changed' do url = 'https://mepmep.whatever.ninja' put api("/projects/#{project.id}/environments/#{environment.id}", user), - name: 'Mepmep', external_url: url + params: { name: 'Mepmep', external_url: url } expect(response).to have_gitlab_http_status(200) expect(json_response['name']).to eq('Mepmep') @@ -102,7 +102,7 @@ describe API::Environments do it "won't allow slug to be changed" do slug = environment.slug api_url = api("/projects/#{project.id}/environments/#{environment.id}", user) - put api_url, slug: slug + "-foo" + put api_url, params: { slug: slug + "-foo" } expect(response).to have_gitlab_http_status(400) expect(json_response["error"]).to eq("slug is automatically generated and cannot be changed") @@ -111,7 +111,7 @@ describe API::Environments do it "won't update the external_url if only the name is passed" do url = environment.external_url put api("/projects/#{project.id}/environments/#{environment.id}", user), - name: 'Mepmep' + params: { name: 'Mepmep' } expect(response).to have_gitlab_http_status(200) expect(json_response['name']).to eq('Mepmep') diff --git a/spec/requests/api/events_spec.rb b/spec/requests/api/events_spec.rb index 573eebe314d..0ac23505de7 100644 --- a/spec/requests/api/events_spec.rb +++ b/spec/requests/api/events_spec.rb @@ -182,6 +182,68 @@ describe API::Events do end end + context 'with inaccessible events' do + let(:public_project) { create(:project, :public, creator_id: user.id, namespace: user.namespace) } + let(:confidential_issue) { create(:closed_issue, confidential: true, project: public_project, author: user) } + let!(:confidential_event) { create(:event, project: public_project, author: user, target: confidential_issue, action: Event::CLOSED) } + let(:public_issue) { create(:closed_issue, project: public_project, author: user) } + let!(:public_event) { create(:event, project: public_project, author: user, target: public_issue, action: Event::CLOSED) } + + it 'returns only accessible events' do + get api("/projects/#{public_project.id}/events", non_member) + + expect(response).to have_gitlab_http_status(200) + expect(json_response.size).to eq(1) + end + + it 'returns all events when the user has access' do + get api("/projects/#{public_project.id}/events", user) + + expect(response).to have_gitlab_http_status(200) + expect(json_response.size).to eq(2) + end + end + + context 'pagination' do + let(:public_project) { create(:project, :public) } + + before do + create(:event, + project: public_project, + target: create(:issue, project: public_project, title: 'Issue 1'), + action: Event::CLOSED, + created_at: Date.parse('2018-12-10')) + create(:event, + project: public_project, + target: create(:issue, confidential: true, project: public_project, title: 'Confidential event'), + action: Event::CLOSED, + created_at: Date.parse('2018-12-11')) + create(:event, + project: public_project, + target: create(:issue, project: public_project, title: 'Issue 2'), + action: Event::CLOSED, + created_at: Date.parse('2018-12-12')) + end + + it 'correctly returns the second page without inaccessible events' do + get api("/projects/#{public_project.id}/events", user), params: { per_page: 2, page: 2 } + + titles = json_response.map { |event| event['target_title'] } + + expect(titles.first).to eq('Issue 1') + expect(titles).not_to include('Confidential event') + end + + it 'correctly returns the first page without inaccessible events' do + get api("/projects/#{public_project.id}/events", user), params: { per_page: 2, page: 1 } + + titles = json_response.map { |event| event['target_title'] } + + expect(titles.first).to eq('Issue 2') + expect(titles).not_to include('Confidential event') + end + end + context 'when not permitted to read' do it 'returns 404' do get api("/projects/#{private_project.id}/events", non_member) @@ -217,13 +279,13 @@ describe API::Events do it 'avoids N+1 queries' do control_count = ActiveRecord::QueryRecorder.new(skip_cached: false) do - get api("/projects/#{private_project.id}/events", user), target_type: :merge_request + get api("/projects/#{private_project.id}/events", user), params: { target_type: :merge_request } end.count create_event(merge_request2) expect do - get api("/projects/#{private_project.id}/events", user), target_type: :merge_request + get api("/projects/#{private_project.id}/events", user), params: { target_type: :merge_request } end.not_to exceed_all_query_limit(control_count) expect(response).to have_gitlab_http_status(200) diff --git a/spec/requests/api/features_spec.rb b/spec/requests/api/features_spec.rb index c5354c2d639..7d3eff7d32d 100644 --- a/spec/requests/api/features_spec.rb +++ b/spec/requests/api/features_spec.rb @@ -79,7 +79,7 @@ describe API::Features do context 'when passed value=true' do it 'creates an enabled feature' do - post api("/features/#{feature_name}", admin), value: 'true' + post api("/features/#{feature_name}", admin), params: { value: 'true' } expect(response).to have_gitlab_http_status(201) expect(json_response).to eq( @@ -89,7 +89,7 @@ describe API::Features do end it 'creates an enabled feature for the given Flipper group when passed feature_group=perf_team' do - post api("/features/#{feature_name}", admin), value: 'true', feature_group: 'perf_team' + post api("/features/#{feature_name}", admin), params: { value: 'true', feature_group: 'perf_team' } expect(response).to have_gitlab_http_status(201) expect(json_response).to eq( @@ -102,7 +102,7 @@ describe API::Features do end it 'creates an enabled feature for the given user when passed user=username' do - post api("/features/#{feature_name}", admin), value: 'true', user: user.username + post api("/features/#{feature_name}", admin), params: { value: 'true', user: user.username } expect(response).to have_gitlab_http_status(201) expect(json_response).to eq( @@ -115,7 +115,7 @@ describe API::Features do end it 'creates an enabled feature for the given user and feature group when passed user=username and feature_group=perf_team' do - post api("/features/#{feature_name}", admin), value: 'true', user: user.username, feature_group: 'perf_team' + post api("/features/#{feature_name}", admin), params: { value: 'true', user: user.username, feature_group: 'perf_team' } expect(response).to have_gitlab_http_status(201) expect(json_response).to eq( @@ -130,7 +130,7 @@ describe API::Features do end it 'creates a feature with the given percentage if passed an integer' do - post api("/features/#{feature_name}", admin), value: '50' + post api("/features/#{feature_name}", admin), params: { value: '50' } expect(response).to have_gitlab_http_status(201) expect(json_response).to eq( @@ -152,7 +152,7 @@ describe API::Features do context 'when passed value=true' do it 'enables the feature' do - post api("/features/#{feature_name}", admin), value: 'true' + post api("/features/#{feature_name}", admin), params: { value: 'true' } expect(response).to have_gitlab_http_status(201) expect(json_response).to eq( @@ -162,7 +162,7 @@ describe API::Features do end it 'enables the feature for the given Flipper group when passed feature_group=perf_team' do - post api("/features/#{feature_name}", admin), value: 'true', feature_group: 'perf_team' + post api("/features/#{feature_name}", admin), params: { value: 'true', feature_group: 'perf_team' } expect(response).to have_gitlab_http_status(201) expect(json_response).to eq( @@ -175,7 +175,7 @@ describe API::Features do end it 'enables the feature for the given user when passed user=username' do - post api("/features/#{feature_name}", admin), value: 'true', user: user.username + post api("/features/#{feature_name}", admin), params: { value: 'true', user: user.username } expect(response).to have_gitlab_http_status(201) expect(json_response).to eq( @@ -193,7 +193,7 @@ describe API::Features do feature.enable expect(feature).to be_enabled - post api("/features/#{feature_name}", admin), value: 'false' + post api("/features/#{feature_name}", admin), params: { value: 'false' } expect(response).to have_gitlab_http_status(201) expect(json_response).to eq( @@ -206,7 +206,7 @@ describe API::Features do feature.enable(Feature.group(:perf_team)) expect(Feature.get(feature_name).enabled?(admin)).to be_truthy - post api("/features/#{feature_name}", admin), value: 'false', feature_group: 'perf_team' + post api("/features/#{feature_name}", admin), params: { value: 'false', feature_group: 'perf_team' } expect(response).to have_gitlab_http_status(201) expect(json_response).to eq( @@ -219,7 +219,7 @@ describe API::Features do feature.enable(user) expect(Feature.get(feature_name).enabled?(user)).to be_truthy - post api("/features/#{feature_name}", admin), value: 'false', user: user.username + post api("/features/#{feature_name}", admin), params: { value: 'false', user: user.username } expect(response).to have_gitlab_http_status(201) expect(json_response).to eq( @@ -235,7 +235,7 @@ describe API::Features do end it 'updates the percentage of time if passed an integer' do - post api("/features/#{feature_name}", admin), value: '30' + post api("/features/#{feature_name}", admin), params: { value: '30' } expect(response).to have_gitlab_http_status(201) expect(json_response).to eq( diff --git a/spec/requests/api/files_spec.rb b/spec/requests/api/files_spec.rb index 620f9f5e1d6..a0aee937185 100644 --- a/spec/requests/api/files_spec.rb +++ b/spec/requests/api/files_spec.rb @@ -54,7 +54,7 @@ describe API::Files do describe "HEAD /projects/:id/repository/files/:file_path" do shared_examples_for 'repository files' do it 'returns file attributes in headers' do - head api(route(file_path), current_user), params + head api(route(file_path), current_user), params: params expect(response).to have_gitlab_http_status(200) expect(response.headers['X-Gitlab-File-Path']).to eq(CGI.unescape(file_path)) @@ -68,7 +68,7 @@ describe API::Files do file_path = "files%2Fjs%2Fcommit%2Ejs%2Ecoffee" params[:ref] = "6f6d7e7ed97bb5f0054f2b1df789b39ca89b6ff9" - head api(route(file_path), current_user), params + head api(route(file_path), current_user), params: params expect(response).to have_gitlab_http_status(200) expect(response.headers['X-Gitlab-File-Name']).to eq('commit.js.coffee') @@ -87,7 +87,7 @@ describe API::Files do it "responds with a 404 status" do params[:ref] = 'master' - head api(route('app%2Fmodels%2Fapplication%2Erb'), current_user), params + head api(route('app%2Fmodels%2Fapplication%2Erb'), current_user), params: params expect(response).to have_gitlab_http_status(404) end @@ -97,7 +97,7 @@ describe API::Files do include_context 'disabled repository' it "responds with a 403 status" do - head api(route(file_path), current_user), params + head api(route(file_path), current_user), params: params expect(response).to have_gitlab_http_status(403) end @@ -115,7 +115,7 @@ describe API::Files do it "responds with a 404 status" do current_user = nil - head api(route(file_path), current_user), params + head api(route(file_path), current_user), params: params expect(response).to have_gitlab_http_status(404) end @@ -136,7 +136,7 @@ describe API::Files do context 'when authenticated', 'as a guest' do it_behaves_like '403 response' do - let(:request) { head api(route(file_path), guest), params } + let(:request) { head api(route(file_path), guest), params: params } end end end @@ -144,7 +144,7 @@ describe API::Files do describe "GET /projects/:id/repository/files/:file_path" do shared_examples_for 'repository files' do it 'returns file attributes as json' do - get api(route(file_path), current_user), params + get api(route(file_path), current_user), params: params expect(response).to have_gitlab_http_status(200) expect(json_response['file_path']).to eq(CGI.unescape(file_path)) @@ -157,7 +157,7 @@ describe API::Files do it 'returns json when file has txt extension' do file_path = "bar%2Fbranch-test.txt" - get api(route(file_path), current_user), params + get api(route(file_path), current_user), params: params expect(response).to have_gitlab_http_status(200) expect(response.content_type).to eq('application/json') @@ -168,7 +168,7 @@ describe API::Files do file_path = "files%2Fjs%2Fcommit%2Ejs%2Ecoffee" params[:ref] = "6f6d7e7ed97bb5f0054f2b1df789b39ca89b6ff9" - get api(route(file_path), current_user), params + get api(route(file_path), current_user), params: params expect(response).to have_gitlab_http_status(200) expect(json_response['file_name']).to eq('commit.js.coffee') @@ -180,7 +180,7 @@ describe API::Files do url = route(file_path) + "/raw" expect(Gitlab::Workhorse).to receive(:send_git_blob) - get api(url, current_user), params + get api(url, current_user), params: params expect(response).to have_gitlab_http_status(200) end @@ -188,9 +188,9 @@ describe API::Files do it 'forces attachment content disposition' do url = route(file_path) + "/raw" - get api(url, current_user), params + get api(url, current_user), params: params - expect(headers['Content-Disposition']).to match(/^attachment/) + expect(headers['Content-Disposition']).to eq('attachment; filename="popen.rb"') end context 'when mandatory params are not given' do @@ -203,7 +203,7 @@ describe API::Files do let(:params) { { ref: 'master' } } it_behaves_like '404 response' do - let(:request) { get api(route('app%2Fmodels%2Fapplication%2Erb'), current_user), params } + let(:request) { get api(route('app%2Fmodels%2Fapplication%2Erb'), current_user), params: params } let(:message) { '404 File Not Found' } end end @@ -212,7 +212,7 @@ describe API::Files do include_context 'disabled repository' it_behaves_like '403 response' do - let(:request) { get api(route(file_path), current_user), params } + let(:request) { get api(route(file_path), current_user), params: params } end end end @@ -233,7 +233,7 @@ describe API::Files do context 'when unauthenticated', 'and project is private' do it_behaves_like '404 response' do - let(:request) { get api(route(file_path)), params } + let(:request) { get api(route(file_path)), params: params } let(:message) { '404 Project Not Found' } end end @@ -246,7 +246,7 @@ describe API::Files do context 'when authenticated', 'as a guest' do it_behaves_like '403 response' do - let(:request) { get api(route(file_path), guest), params } + let(:request) { get api(route(file_path), guest), params: params } end end end @@ -257,7 +257,7 @@ describe API::Files do url = route(file_path) + "/raw" expect(Gitlab::Workhorse).to receive(:send_git_blob) - get api(url, current_user), params + get api(url, current_user), params: params expect(response).to have_gitlab_http_status(200) end @@ -266,7 +266,7 @@ describe API::Files do url = route('.gitignore') + "/raw" expect(Gitlab::Workhorse).to receive(:send_git_blob) - get api(url, current_user), params + get api(url, current_user), params: params expect(response).to have_gitlab_http_status(200) end @@ -277,7 +277,7 @@ describe API::Files do params[:ref] = "6f6d7e7ed97bb5f0054f2b1df789b39ca89b6ff9" expect(Gitlab::Workhorse).to receive(:send_git_blob) - get api(route(file_path) + "/raw", current_user), params + get api(route(file_path) + "/raw", current_user), params: params expect(response).to have_gitlab_http_status(200) end @@ -292,7 +292,7 @@ describe API::Files do let(:params) { { ref: 'master' } } it_behaves_like '404 response' do - let(:request) { get api(route('app%2Fmodels%2Fapplication%2Erb'), current_user), params } + let(:request) { get api(route('app%2Fmodels%2Fapplication%2Erb'), current_user), params: params } let(:message) { '404 File Not Found' } end end @@ -301,7 +301,7 @@ describe API::Files do include_context 'disabled repository' it_behaves_like '403 response' do - let(:request) { get api(route(file_path), current_user), params } + let(:request) { get api(route(file_path), current_user), params: params } end end end @@ -315,7 +315,7 @@ describe API::Files do context 'when unauthenticated', 'and project is private' do it_behaves_like '404 response' do - let(:request) { get api(route(file_path)), params } + let(:request) { get api(route(file_path)), params: params } let(:message) { '404 Project Not Found' } end end @@ -328,7 +328,7 @@ describe API::Files do context 'when authenticated', 'as a guest' do it_behaves_like '403 response' do - let(:request) { get api(route(file_path), guest), params } + let(:request) { get api(route(file_path), guest), params: params } end end @@ -341,7 +341,7 @@ describe API::Files do params[:ref] = "6f6d7e7ed97bb5f0054f2b1df789b39ca89b6ff9" expect(Gitlab::Workhorse).to receive(:send_git_blob) - get api(route(file_path) + "/raw", personal_access_token: token), params + get api(route(file_path) + "/raw", personal_access_token: token), params: params expect(response).to have_gitlab_http_status(200) end @@ -359,7 +359,7 @@ describe API::Files do end it "creates a new file in project repo" do - post api(route(file_path), user), params + post api(route(file_path), user), params: params expect(response).to have_gitlab_http_status(201) expect(json_response["file_path"]).to eq(CGI.unescape(file_path)) @@ -377,7 +377,7 @@ describe API::Files do it 'returns a 400 bad request if the commit message is empty' do params[:commit_message] = '' - post api(route(file_path), user), params + post api(route(file_path), user), params: params expect(response).to have_gitlab_http_status(400) end @@ -386,7 +386,7 @@ describe API::Files do allow_any_instance_of(Repository).to receive(:create_file) .and_raise(Gitlab::Git::CommitError, 'Cannot create file') - post api(route("any%2Etxt"), user), params + post api(route("any%2Etxt"), user), params: params expect(response).to have_gitlab_http_status(400) end @@ -395,7 +395,7 @@ describe API::Files do it 'returns 403 with `read_repository` scope' do token = create(:personal_access_token, scopes: ['read_repository'], user: user) - post api(route(file_path), personal_access_token: token), params + post api(route(file_path), personal_access_token: token), params: params expect(response).to have_gitlab_http_status(403) end @@ -403,7 +403,7 @@ describe API::Files do it 'returns 201 with `api` scope' do token = create(:personal_access_token, scopes: ['api'], user: user) - post api(route(file_path), personal_access_token: token), params + post api(route(file_path), personal_access_token: token), params: params expect(response).to have_gitlab_http_status(201) end @@ -413,7 +413,7 @@ describe API::Files do it "creates a new file with the specified author" do params.merge!(author_email: author_email, author_name: author_name) - post api(route("new_file_with_author%2Etxt"), user), params + post api(route("new_file_with_author%2Etxt"), user), params: params expect(response).to have_gitlab_http_status(201) expect(response.content_type).to eq('application/json') @@ -427,7 +427,7 @@ describe API::Files do let!(:project) { create(:project_empty_repo, namespace: user.namespace ) } it "creates a new file in project repo" do - post api(route("newfile%2Erb"), user), params + post api(route("newfile%2Erb"), user), params: params expect(response).to have_gitlab_http_status(201) expect(json_response['file_path']).to eq('newfile.rb') @@ -448,7 +448,7 @@ describe API::Files do end it "updates existing file in project repo" do - put api(route(file_path), user), params + put api(route(file_path), user), params: params expect(response).to have_gitlab_http_status(200) expect(json_response['file_path']).to eq(CGI.unescape(file_path)) @@ -460,7 +460,7 @@ describe API::Files do it 'returns a 400 bad request if the commit message is empty' do params[:commit_message] = '' - put api(route(file_path), user), params + put api(route(file_path), user), params: params expect(response).to have_gitlab_http_status(400) end @@ -468,7 +468,7 @@ describe API::Files do it "returns a 400 bad request if update existing file with stale last commit id" do params_with_stale_id = params.merge(last_commit_id: 'stale') - put api(route(file_path), user), params_with_stale_id + put api(route(file_path), user), params: params_with_stale_id expect(response).to have_gitlab_http_status(400) expect(json_response['message']).to eq('You are attempting to update a file that has changed since you started editing it.') @@ -479,7 +479,7 @@ describe API::Files do .last_for_path(project.repository, 'master', URI.unescape(file_path)) params_with_correct_id = params.merge(last_commit_id: last_commit.id) - put api(route(file_path), user), params_with_correct_id + put api(route(file_path), user), params: params_with_correct_id expect(response).to have_gitlab_http_status(200) end @@ -494,7 +494,7 @@ describe API::Files do it "updates a file with the specified author" do params.merge!(author_email: author_email, author_name: author_name, content: "New content") - put api(route(file_path), user), params + put api(route(file_path), user), params: params expect(response).to have_gitlab_http_status(200) last_commit = project.repository.commit.raw @@ -513,7 +513,7 @@ describe API::Files do end it "deletes existing file in project repo" do - delete api(route(file_path), user), params + delete api(route(file_path), user), params: params expect(response).to have_gitlab_http_status(204) end @@ -527,7 +527,7 @@ describe API::Files do it 'returns a 400 bad request if the commit message is empty' do params[:commit_message] = '' - delete api(route(file_path), user), params + delete api(route(file_path), user), params: params expect(response).to have_gitlab_http_status(400) end @@ -535,7 +535,7 @@ describe API::Files do it "returns a 400 if fails to delete file" do allow_any_instance_of(Repository).to receive(:delete_file).and_raise(Gitlab::Git::CommitError, 'Cannot delete file') - delete api(route(file_path), user), params + delete api(route(file_path), user), params: params expect(response).to have_gitlab_http_status(400) end @@ -544,7 +544,7 @@ describe API::Files do it "removes a file with the specified author" do params.merge!(author_email: author_email, author_name: author_name) - delete api(route(file_path), user), params + delete api(route(file_path), user), params: params expect(response).to have_gitlab_http_status(204) end @@ -568,11 +568,11 @@ describe API::Files do end before do - post api(route(file_path), user), put_params + post api(route(file_path), user), params: put_params end it "remains unchanged" do - get api(route(file_path), user), get_params + get api(route(file_path), user), params: get_params expect(response).to have_gitlab_http_status(200) expect(json_response['file_path']).to eq(CGI.unescape(file_path)) diff --git a/spec/requests/api/group_boards_spec.rb b/spec/requests/api/group_boards_spec.rb index 894c94688ba..b400a7f55ef 100644 --- a/spec/requests/api/group_boards_spec.rb +++ b/spec/requests/api/group_boards_spec.rb @@ -46,7 +46,7 @@ describe API::GroupBoards do it 'does not create lists for child project labels' do project_label = create(:label, project: project) - post api(url, user), label_id: project_label.id + post api(url, user), params: { label_id: project_label.id } expect(response).to have_gitlab_http_status(400) end diff --git a/spec/requests/api/group_variables_spec.rb b/spec/requests/api/group_variables_spec.rb index f87e035c89d..e52f4c70407 100644 --- a/spec/requests/api/group_variables_spec.rb +++ b/spec/requests/api/group_variables_spec.rb @@ -87,7 +87,7 @@ describe API::GroupVariables do it 'creates variable' do expect do - post api("/groups/#{group.id}/variables", user), key: 'TEST_VARIABLE_2', value: 'VALUE_2', protected: true + post api("/groups/#{group.id}/variables", user), params: { key: 'TEST_VARIABLE_2', value: 'VALUE_2', protected: true } end.to change {group.variables.count}.by(1) expect(response).to have_gitlab_http_status(201) @@ -98,7 +98,7 @@ describe API::GroupVariables do it 'creates variable with optional attributes' do expect do - post api("/groups/#{group.id}/variables", user), key: 'TEST_VARIABLE_2', value: 'VALUE_2' + post api("/groups/#{group.id}/variables", user), params: { key: 'TEST_VARIABLE_2', value: 'VALUE_2' } end.to change {group.variables.count}.by(1) expect(response).to have_gitlab_http_status(201) @@ -109,7 +109,7 @@ describe API::GroupVariables do it 'does not allow to duplicate variable key' do expect do - post api("/groups/#{group.id}/variables", user), key: variable.key, value: 'VALUE_2' + post api("/groups/#{group.id}/variables", user), params: { key: variable.key, value: 'VALUE_2' } end.to change {group.variables.count}.by(0) expect(response).to have_gitlab_http_status(400) @@ -145,7 +145,7 @@ describe API::GroupVariables do initial_variable = group.variables.reload.first value_before = initial_variable.value - put api("/groups/#{group.id}/variables/#{variable.key}", user), value: 'VALUE_1_UP', protected: true + put api("/groups/#{group.id}/variables/#{variable.key}", user), params: { value: 'VALUE_1_UP', protected: true } updated_variable = group.variables.reload.first diff --git a/spec/requests/api/groups_spec.rb b/spec/requests/api/groups_spec.rb index 688d91113ad..c9dfc5c4a7e 100644 --- a/spec/requests/api/groups_spec.rb +++ b/spec/requests/api/groups_spec.rb @@ -60,7 +60,7 @@ describe API::Groups do end it "does not include statistics" do - get api("/groups", user1), statistics: true + get api("/groups", user1), params: { statistics: true } expect(response).to have_gitlab_http_status(200) expect(response).to include_pagination_headers @@ -100,7 +100,7 @@ describe API::Groups do project1.statistics.update!(attributes) - get api("/groups", admin), statistics: true + get api("/groups", admin), params: { statistics: true } expect(response).to have_gitlab_http_status(200) expect(response).to include_pagination_headers @@ -112,7 +112,7 @@ describe API::Groups do context "when using skip_groups in request" do it "returns all groups excluding skipped groups" do - get api("/groups", admin), skip_groups: [group2.id] + get api("/groups", admin), params: { skip_groups: [group2.id] } expect(response).to have_gitlab_http_status(200) expect(response).to include_pagination_headers @@ -127,7 +127,7 @@ describe API::Groups do it "returns all groups you have access to" do public_group = create :group, :public - get api("/groups", user1), all_available: true + get api("/groups", user1), params: { all_available: true } expect(response).to have_gitlab_http_status(200) expect(response).to include_pagination_headers @@ -159,7 +159,7 @@ describe API::Groups do end it "sorts in descending order when passed" do - get api("/groups", user1), sort: "desc" + get api("/groups", user1), params: { sort: "desc" } expect(response).to have_gitlab_http_status(200) expect(response).to include_pagination_headers @@ -168,7 +168,7 @@ describe API::Groups do end it "sorts by path in order_by param" do - get api("/groups", user1), order_by: "path" + get api("/groups", user1), params: { order_by: "path" } expect(response).to have_gitlab_http_status(200) expect(response).to include_pagination_headers @@ -177,7 +177,7 @@ describe API::Groups do end it "sorts by id in the order_by param" do - get api("/groups", user1), order_by: "id" + get api("/groups", user1), params: { order_by: "id" } expect(response).to have_gitlab_http_status(200) expect(response).to include_pagination_headers @@ -186,7 +186,7 @@ describe API::Groups do end it "sorts also by descending id with pagination fix" do - get api("/groups", user1), order_by: "id", sort: "desc" + get api("/groups", user1), params: { order_by: "id", sort: "desc" } expect(response).to have_gitlab_http_status(200) expect(response).to include_pagination_headers @@ -195,7 +195,7 @@ describe API::Groups do end it "sorts identical keys by id for good pagination" do - get api("/groups", user1), search: "same-name", order_by: "name" + get api("/groups", user1), params: { search: "same-name", order_by: "name" } expect(response).to have_gitlab_http_status(200) expect(response).to include_pagination_headers @@ -204,7 +204,7 @@ describe API::Groups do end it "sorts descending identical keys by id for good pagination" do - get api("/groups", user1), search: "same-name", order_by: "name", sort: "desc" + get api("/groups", user1), params: { search: "same-name", order_by: "name", sort: "desc" } expect(response).to have_gitlab_http_status(200) expect(response).to include_pagination_headers @@ -221,7 +221,7 @@ describe API::Groups do it 'returns an array of groups the user owns' do group1.add_maintainer(user2) - get api('/groups', user2), owned: true + get api('/groups', user2), params: { owned: true } expect(response).to have_gitlab_http_status(200) expect(response).to include_pagination_headers @@ -241,7 +241,7 @@ describe API::Groups do end it 'returns an array of groups the user has at least master access' do - get api('/groups', user2), min_access_level: 40 + get api('/groups', user2), params: { min_access_level: 40 } expect(response).to have_gitlab_http_status(200) expect(response).to include_pagination_headers @@ -344,7 +344,7 @@ describe API::Groups do project = create(:project, namespace: group2, path: 'Foo') create(:project_group_link, project: project, group: group1) - get api("/groups/#{group1.id}", user1), with_projects: false + get api("/groups/#{group1.id}", user1), params: { with_projects: false } expect(response).to have_gitlab_http_status(200) expect(json_response['projects']).to be_nil @@ -426,7 +426,7 @@ describe API::Groups do context 'when authenticated as the group owner' do it 'updates the group' do - put api("/groups/#{group1.id}", user1), name: new_group_name, request_access_enabled: true + put api("/groups/#{group1.id}", user1), params: { name: new_group_name, request_access_enabled: true } expect(response).to have_gitlab_http_status(200) expect(json_response['name']).to eq(new_group_name) @@ -434,7 +434,7 @@ describe API::Groups do end it 'returns 404 for a non existing group' do - put api('/groups/1328', user1), name: new_group_name + put api('/groups/1328', user1), params: { name: new_group_name } expect(response).to have_gitlab_http_status(404) end @@ -442,7 +442,7 @@ describe API::Groups do context 'when authenticated as the admin' do it 'updates the group' do - put api("/groups/#{group1.id}", admin), name: new_group_name + put api("/groups/#{group1.id}", admin), params: { name: new_group_name } expect(response).to have_gitlab_http_status(200) expect(json_response['name']).to eq(new_group_name) @@ -451,7 +451,7 @@ describe API::Groups do context 'when authenticated as an user that can see the group' do it 'does not updates the group' do - put api("/groups/#{group1.id}", user2), name: new_group_name + put api("/groups/#{group1.id}", user2), params: { name: new_group_name } expect(response).to have_gitlab_http_status(403) end @@ -459,7 +459,7 @@ describe API::Groups do context 'when authenticated as an user that cannot see the group' do it 'returns 404 when trying to update the group' do - put api("/groups/#{group2.id}", user1), name: new_group_name + put api("/groups/#{group2.id}", user1), params: { name: new_group_name } expect(response).to have_gitlab_http_status(404) end @@ -480,7 +480,7 @@ describe API::Groups do end it "returns the group's projects with simple representation" do - get api("/groups/#{group1.id}/projects", user1), simple: true + get api("/groups/#{group1.id}/projects", user1), params: { simple: true } expect(response).to have_gitlab_http_status(200) expect(response).to include_pagination_headers @@ -493,7 +493,7 @@ describe API::Groups do it "filters the groups projects" do public_project = create(:project, :public, path: 'test1', group: group1) - get api("/groups/#{group1.id}/projects", user1), visibility: 'public' + get api("/groups/#{group1.id}/projects", user1), params: { visibility: 'public' } expect(response).to have_gitlab_http_status(200) expect(response).to include_pagination_headers @@ -507,7 +507,7 @@ describe API::Groups do create(:project_group_link, project: create(:project), group: group1) create(:project_group_link, project: create(:project), group: group1) - get api("/groups/#{group1.id}/projects", user1), with_shared: false + get api("/groups/#{group1.id}/projects", user1), params: { with_shared: false } expect(response).to have_gitlab_http_status(200) expect(response).to include_pagination_headers @@ -520,7 +520,7 @@ describe API::Groups do create(:project, group: subgroup) create(:project, group: subgroup) - get api("/groups/#{group1.id}/projects", user1), include_subgroups: true + get api("/groups/#{group1.id}/projects", user1), params: { include_subgroups: true } expect(response).to have_gitlab_http_status(200) expect(response).to include_pagination_headers @@ -554,7 +554,7 @@ describe API::Groups do it 'only returns the projects owned by user' do project2.group.add_owner(user3) - get api("/groups/#{project2.group.id}/projects", user3), owned: true + get api("/groups/#{project2.group.id}/projects", user3), params: { owned: true } expect(response).to have_gitlab_http_status(200) expect(json_response.length).to eq(1) @@ -564,7 +564,7 @@ describe API::Groups do it 'only returns the projects starred by user' do user1.starred_projects = [project1] - get api("/groups/#{group1.id}/projects", user1), starred: true + get api("/groups/#{group1.id}/projects", user1), params: { starred: true } expect(response).to have_gitlab_http_status(200) expect(json_response.length).to eq(1) @@ -663,7 +663,7 @@ describe API::Groups do context 'when using all_available in request' do it 'returns public subgroups' do - get api("/groups/#{group1.id}/subgroups", user2), all_available: true + get api("/groups/#{group1.id}/subgroups", user2), params: { all_available: true } expect(response).to have_gitlab_http_status(200) expect(json_response).to be_an Array @@ -702,7 +702,7 @@ describe API::Groups do context 'when using statistics in request' do it 'does not include statistics' do - get api("/groups/#{group1.id}/subgroups", user2), statistics: true + get api("/groups/#{group1.id}/subgroups", user2), params: { statistics: true } expect(response).to have_gitlab_http_status(200) expect(json_response).to be_an Array @@ -754,7 +754,7 @@ describe API::Groups do end it 'includes statistics if requested' do - get api("/groups/#{group1.id}/subgroups", admin), statistics: true + get api("/groups/#{group1.id}/subgroups", admin), params: { statistics: true } expect(response).to have_gitlab_http_status(200) expect(json_response).to be_an Array @@ -766,7 +766,7 @@ describe API::Groups do describe "POST /groups" do context "when authenticated as user without group permissions" do it "does not create group" do - post api("/groups", user1), attributes_for(:group) + post api("/groups", user1), params: attributes_for(:group) expect(response).to have_gitlab_http_status(403) end @@ -777,7 +777,7 @@ describe API::Groups do end it 'can create subgroups' do - post api("/groups", user1), parent_id: group2.id, name: 'foo', path: 'foo' + post api("/groups", user1), params: { parent_id: group2.id, name: 'foo', path: 'foo' } expect(response).to have_gitlab_http_status(201) end @@ -789,7 +789,7 @@ describe API::Groups do end it 'cannot create subgroups' do - post api("/groups", user1), parent_id: group2.id, name: 'foo', path: 'foo' + post api("/groups", user1), params: { parent_id: group2.id, name: 'foo', path: 'foo' } expect(response).to have_gitlab_http_status(403) end @@ -800,7 +800,7 @@ describe API::Groups do it "creates group" do group = attributes_for(:group, { request_access_enabled: false }) - post api("/groups", user3), group + post api("/groups", user3), params: group expect(response).to have_gitlab_http_status(201) @@ -815,7 +815,7 @@ describe API::Groups do parent.add_owner(user3) group = attributes_for(:group, { parent_id: parent.id }) - post api("/groups", user3), group + post api("/groups", user3), params: group expect(response).to have_gitlab_http_status(201) @@ -824,20 +824,20 @@ describe API::Groups do end it "does not create group, duplicate" do - post api("/groups", user3), { name: 'Duplicate Test', path: group2.path } + post api("/groups", user3), params: { name: 'Duplicate Test', path: group2.path } expect(response).to have_gitlab_http_status(400) expect(response.message).to eq("Bad Request") end it "returns 400 bad request error if name not given" do - post api("/groups", user3), { path: group2.path } + post api("/groups", user3), params: { path: group2.path } expect(response).to have_gitlab_http_status(400) end it "returns 400 bad request error if path not given" do - post api("/groups", user3), { name: 'test' } + post api("/groups", user3), params: { name: 'test' } expect(response).to have_gitlab_http_status(400) end diff --git a/spec/requests/api/helpers_spec.rb b/spec/requests/api/helpers_spec.rb index f7916441313..a0c64d295c0 100644 --- a/spec/requests/api/helpers_spec.rb +++ b/spec/requests/api/helpers_spec.rb @@ -283,7 +283,7 @@ describe API::Helpers do it 'sends the params, excluding confidential values' do expect(ProjectsFinder).to receive(:new).and_raise('Runtime Error!') - get api('/projects', user), password: 'dont_send_this', other_param: 'send_this' + get api('/projects', user), params: { password: 'dont_send_this', other_param: 'send_this' } expect(event_data).to include('other_param=send_this') expect(event_data).to include('password=********') diff --git a/spec/requests/api/internal_spec.rb b/spec/requests/api/internal_spec.rb index 2ebcb787d06..0fe63e2e517 100644 --- a/spec/requests/api/internal_spec.rb +++ b/spec/requests/api/internal_spec.rb @@ -12,7 +12,7 @@ describe API::Internal do it do expect_any_instance_of(Redis).to receive(:ping).and_return('PONG') - get api("/internal/check"), secret_token: secret_token + get api("/internal/check"), params: { secret_token: secret_token } expect(response).to have_gitlab_http_status(200) expect(json_response['api_version']).to eq(API::API.version) @@ -22,7 +22,7 @@ describe API::Internal do it 'returns false for field `redis` when redis is unavailable' do expect_any_instance_of(Redis).to receive(:ping).and_raise(Errno::ENOENT) - get api("/internal/check"), secret_token: secret_token + get api("/internal/check"), params: { secret_token: secret_token } expect(json_response['redis']).to be(false) end @@ -33,7 +33,7 @@ describe API::Internal do let!(:broadcast_message) { create(:broadcast_message, starts_at: 1.day.ago, ends_at: 1.day.from_now ) } it 'returns one broadcast message' do - get api('/internal/broadcast_message'), secret_token: secret_token + get api('/internal/broadcast_message'), params: { secret_token: secret_token } expect(response).to have_gitlab_http_status(200) expect(json_response['message']).to eq(broadcast_message.message) @@ -42,7 +42,7 @@ describe API::Internal do context 'broadcast message does not exist' do it 'returns nothing' do - get api('/internal/broadcast_message'), secret_token: secret_token + get api('/internal/broadcast_message'), params: { secret_token: secret_token } expect(response).to have_gitlab_http_status(200) expect(json_response).to be_empty @@ -53,7 +53,7 @@ describe API::Internal do it 'returns nothing' do allow(BroadcastMessage).to receive(:current).and_return(nil) - get api('/internal/broadcast_message'), secret_token: secret_token + get api('/internal/broadcast_message'), params: { secret_token: secret_token } expect(response).to have_gitlab_http_status(200) expect(json_response).to be_empty @@ -66,7 +66,7 @@ describe API::Internal do let!(:broadcast_message) { create(:broadcast_message, starts_at: 1.day.ago, ends_at: 1.day.from_now ) } it 'returns active broadcast message(s)' do - get api('/internal/broadcast_messages'), secret_token: secret_token + get api('/internal/broadcast_messages'), params: { secret_token: secret_token } expect(response).to have_gitlab_http_status(200) expect(json_response[0]['message']).to eq(broadcast_message.message) @@ -75,7 +75,7 @@ describe API::Internal do context 'broadcast message does not exist' do it 'returns nothing' do - get api('/internal/broadcast_messages'), secret_token: secret_token + get api('/internal/broadcast_messages'), params: { secret_token: secret_token } expect(response).to have_gitlab_http_status(200) expect(json_response).to be_empty @@ -86,8 +86,10 @@ describe API::Internal do describe 'GET /internal/two_factor_recovery_codes' do it 'returns an error message when the key does not exist' do post api('/internal/two_factor_recovery_codes'), - secret_token: secret_token, - key_id: 12345 + params: { + secret_token: secret_token, + key_id: 12345 + } expect(json_response['success']).to be_falsey expect(json_response['message']).to eq('Could not find the given key') @@ -97,8 +99,10 @@ describe API::Internal do deploy_key = create(:deploy_key) post api('/internal/two_factor_recovery_codes'), - secret_token: secret_token, - key_id: deploy_key.id + params: { + secret_token: secret_token, + key_id: deploy_key.id + } expect(json_response['success']).to be_falsey expect(json_response['message']).to eq('Deploy keys cannot be used to retrieve recovery codes') @@ -108,8 +112,10 @@ describe API::Internal do key_without_user = create(:key, user: nil) post api('/internal/two_factor_recovery_codes'), - secret_token: secret_token, - key_id: key_without_user.id + params: { + secret_token: secret_token, + key_id: key_without_user.id + } expect(json_response['success']).to be_falsey expect(json_response['message']).to eq('Could not find a user for the given key') @@ -123,8 +129,10 @@ describe API::Internal do .to receive(:generate_otp_backup_codes!).and_return(%w(119135e5a3ebce8e 34bd7b74adbc8861)) post api('/internal/two_factor_recovery_codes'), - secret_token: secret_token, - key_id: key.id + params: { + secret_token: secret_token, + key_id: key.id + } expect(json_response['success']).to be_truthy expect(json_response['recovery_codes']).to match_array(%w(119135e5a3ebce8e 34bd7b74adbc8861)) @@ -136,8 +144,10 @@ describe API::Internal do allow_any_instance_of(User).to receive(:two_factor_enabled?).and_return(false) post api('/internal/two_factor_recovery_codes'), - secret_token: secret_token, - key_id: key.id + params: { + secret_token: secret_token, + key_id: key.id + } expect(json_response['success']).to be_falsey expect(json_response['recovery_codes']).to be_nil @@ -156,9 +166,8 @@ describe API::Internal do expect(response).to have_gitlab_http_status(200) expect(json_response['username']).to eq(user.username) - expect(json_response['lfs_token']).to eq(Gitlab::LfsToken.new(key).token) - expect(json_response['repository_http_path']).to eq(project.http_url_to_repo) + expect(Gitlab::LfsToken.new(key).token_valid?(json_response['lfs_token'])).to be_truthy end it 'returns the correct information about the user' do @@ -166,9 +175,8 @@ describe API::Internal do expect(response).to have_gitlab_http_status(200) expect(json_response['username']).to eq(user.username) - expect(json_response['lfs_token']).to eq(Gitlab::LfsToken.new(user).token) - expect(json_response['repository_http_path']).to eq(project.http_url_to_repo) + expect(Gitlab::LfsToken.new(user).token_valid?(json_response['lfs_token'])).to be_truthy end it 'returns a 404 when no key or user is provided' do @@ -198,15 +206,15 @@ describe API::Internal do expect(response).to have_gitlab_http_status(200) expect(json_response['username']).to eq("lfs+deploy-key-#{key.id}") - expect(json_response['lfs_token']).to eq(Gitlab::LfsToken.new(key).token) expect(json_response['repository_http_path']).to eq(project.http_url_to_repo) + expect(Gitlab::LfsToken.new(key).token_valid?(json_response['lfs_token'])).to be_truthy end end end describe "GET /internal/discover" do it "finds a user by key id" do - get(api("/internal/discover"), key_id: key.id, secret_token: secret_token) + get(api("/internal/discover"), params: { key_id: key.id, secret_token: secret_token }) expect(response).to have_gitlab_http_status(200) @@ -214,7 +222,7 @@ describe API::Internal do end it "finds a user by user id" do - get(api("/internal/discover"), user_id: user.id, secret_token: secret_token) + get(api("/internal/discover"), params: { user_id: user.id, secret_token: secret_token }) expect(response).to have_gitlab_http_status(200) @@ -222,7 +230,7 @@ describe API::Internal do end it "finds a user by username" do - get(api("/internal/discover"), username: user.username, secret_token: secret_token) + get(api("/internal/discover"), params: { username: user.username, secret_token: secret_token }) expect(response).to have_gitlab_http_status(200) @@ -233,7 +241,7 @@ describe API::Internal do describe "GET /internal/authorized_keys" do context "using an existing key's fingerprint" do it "finds the key" do - get(api('/internal/authorized_keys'), fingerprint: key.fingerprint, secret_token: secret_token) + get(api('/internal/authorized_keys'), params: { fingerprint: key.fingerprint, secret_token: secret_token }) expect(response.status).to eq(200) expect(json_response["key"]).to eq(key.key) @@ -242,7 +250,7 @@ describe API::Internal do context "non existing key's fingerprint" do it "returns 404" do - get(api('/internal/authorized_keys'), fingerprint: "no:t-:va:li:d0", secret_token: secret_token) + get(api('/internal/authorized_keys'), params: { fingerprint: "no:t-:va:li:d0", secret_token: secret_token }) expect(response.status).to eq(404) end @@ -250,7 +258,7 @@ describe API::Internal do context "using a partial fingerprint" do it "returns 404" do - get(api('/internal/authorized_keys'), fingerprint: "#{key.fingerprint[0..5]}%", secret_token: secret_token) + get(api('/internal/authorized_keys'), params: { fingerprint: "#{key.fingerprint[0..5]}%", secret_token: secret_token }) expect(response.status).to eq(404) end @@ -258,20 +266,20 @@ describe API::Internal do context "sending the key" do it "finds the key" do - get(api('/internal/authorized_keys'), key: key.key.split[1], secret_token: secret_token) + get(api('/internal/authorized_keys'), params: { key: key.key.split[1], secret_token: secret_token }) expect(response.status).to eq(200) expect(json_response["key"]).to eq(key.key) end it "returns 404 with a partial key" do - get(api('/internal/authorized_keys'), key: key.key.split[1][0...-3], secret_token: secret_token) + get(api('/internal/authorized_keys'), params: { key: key.key.split[1][0...-3], secret_token: secret_token }) expect(response.status).to eq(404) end it "returns 404 with an not valid base64 string" do - get(api('/internal/authorized_keys'), key: "whatever!", secret_token: secret_token) + get(api('/internal/authorized_keys'), params: { key: "whatever!", secret_token: secret_token }) expect(response.status).to eq(404) end @@ -679,7 +687,7 @@ describe API::Internal do end it 'returns link to create new merge request' do - get api("/internal/merge_request_urls?project=#{repo_name}&changes=#{changes}"), secret_token: secret_token + get api("/internal/merge_request_urls?project=#{repo_name}&changes=#{changes}"), params: { secret_token: secret_token } expect(json_response).to match [{ "branch_name" => "new_branch", @@ -691,7 +699,7 @@ describe API::Internal do it 'returns empty array if printing_merge_request_link_enabled is false' do project.update!(printing_merge_request_link_enabled: false) - get api("/internal/merge_request_urls?project=#{repo_name}&changes=#{changes}"), secret_token: secret_token + get api("/internal/merge_request_urls?project=#{repo_name}&changes=#{changes}"), params: { secret_token: secret_token } expect(json_response).to eq([]) end @@ -700,7 +708,7 @@ describe API::Internal do let(:gl_repository) { "project-#{project.id}" } it 'returns link to create new merge request' do - get api("/internal/merge_request_urls?gl_repository=#{gl_repository}&changes=#{changes}"), secret_token: secret_token + get api("/internal/merge_request_urls?gl_repository=#{gl_repository}&changes=#{changes}"), params: { secret_token: secret_token } expect(json_response).to match [{ "branch_name" => "new_branch", @@ -801,7 +809,8 @@ describe API::Internal do gl_repository: gl_repository, secret_token: secret_token, identifier: identifier, - changes: changes + changes: changes, + push_options: push_options } end @@ -809,6 +818,11 @@ describe API::Internal do "#{Gitlab::Git::BLANK_SHA} 570e7b2abdd848b95f2f578043fc23bd6f6fd24d refs/heads/new_branch" end + let(:push_options) do + ['ci.skip', + 'another push option'] + end + before do project.add_developer(user) allow(described_class).to receive(:identify).and_return(user) @@ -817,9 +831,9 @@ describe API::Internal do it 'enqueues a PostReceive worker job' do expect(PostReceive).to receive(:perform_async) - .with(gl_repository, identifier, changes) + .with(gl_repository, identifier, changes, push_options) - post api("/internal/post_receive"), valid_params + post api("/internal/post_receive"), params: valid_params end it 'decreases the reference counter and returns the result' do @@ -827,13 +841,13 @@ describe API::Internal do .and_return(reference_counter) expect(reference_counter).to receive(:decrease).and_return(true) - post api("/internal/post_receive"), valid_params + post api("/internal/post_receive"), params: valid_params expect(json_response['reference_counter_decreased']).to be(true) end it 'returns link to create new merge request' do - post api("/internal/post_receive"), valid_params + post api("/internal/post_receive"), params: valid_params expect(json_response['merge_request_urls']).to match [{ "branch_name" => "new_branch", @@ -845,7 +859,7 @@ describe API::Internal do it 'returns empty array if printing_merge_request_link_enabled is false' do project.update!(printing_merge_request_link_enabled: false) - post api("/internal/post_receive"), valid_params + post api("/internal/post_receive"), params: valid_params expect(json_response['merge_request_urls']).to eq([]) end @@ -854,7 +868,7 @@ describe API::Internal do let!(:broadcast_message) { create(:broadcast_message, starts_at: 1.day.ago, ends_at: 1.day.from_now ) } it 'returns one broadcast message' do - post api("/internal/post_receive"), valid_params + post api("/internal/post_receive"), params: valid_params expect(response).to have_gitlab_http_status(200) expect(json_response['broadcast_message']).to eq(broadcast_message.message) @@ -863,7 +877,7 @@ describe API::Internal do context 'broadcast message does not exist' do it 'returns empty string' do - post api("/internal/post_receive"), valid_params + post api("/internal/post_receive"), params: valid_params expect(response).to have_gitlab_http_status(200) expect(json_response['broadcast_message']).to eq(nil) @@ -874,7 +888,7 @@ describe API::Internal do it 'returns empty string' do allow(BroadcastMessage).to receive(:current).and_return(nil) - post api("/internal/post_receive"), valid_params + post api("/internal/post_receive"), params: valid_params expect(response).to have_gitlab_http_status(200) expect(json_response['broadcast_message']).to eq(nil) @@ -886,7 +900,7 @@ describe API::Internal do project_moved = Gitlab::Checks::ProjectMoved.new(project, user, 'http', 'foo/baz') project_moved.add_message - post api("/internal/post_receive"), valid_params + post api("/internal/post_receive"), params: valid_params expect(response).to have_gitlab_http_status(200) expect(json_response["redirected_message"]).to be_present @@ -899,7 +913,7 @@ describe API::Internal do project_created = Gitlab::Checks::ProjectCreated.new(project, user, 'http') project_created.add_message - post api("/internal/post_receive"), valid_params + post api("/internal/post_receive"), params: valid_params expect(response).to have_gitlab_http_status(200) expect(json_response["project_created_message"]).to be_present @@ -911,7 +925,7 @@ describe API::Internal 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 + post api("/internal/post_receive"), params: valid_params expect(response).to have_gitlab_http_status(200) end @@ -928,7 +942,7 @@ describe API::Internal do .and_return(reference_counter) expect(reference_counter).to receive(:increase).and_return(true) - post api("/internal/pre_receive"), valid_params + post api("/internal/pre_receive"), params: valid_params expect(json_response['reference_counter_increased']).to be(true) end @@ -948,12 +962,14 @@ describe API::Internal do def pull(key, project, protocol = 'ssh') post( api("/internal/allowed"), - key_id: key.id, - project: project.full_path, - gl_repository: gl_repository_for(project), - action: 'git-upload-pack', - secret_token: secret_token, - protocol: protocol + params: { + key_id: key.id, + project: project.full_path, + gl_repository: gl_repository_for(project), + action: 'git-upload-pack', + secret_token: secret_token, + protocol: protocol + } ) end @@ -969,55 +985,56 @@ describe API::Internal do env: env } - if Gitlab.rails5? - post( - api("/internal/allowed"), - params: params - ) - else - post( - api("/internal/allowed"), - params - ) - end + post( + api("/internal/allowed"), + params: params + ) end def archive(key, project) post( api("/internal/allowed"), - ref: 'master', - key_id: key.id, - project: project.full_path, - gl_repository: gl_repository_for(project), - action: 'git-upload-archive', - secret_token: secret_token, - protocol: 'ssh' + params: { + ref: 'master', + key_id: key.id, + project: project.full_path, + gl_repository: gl_repository_for(project), + action: 'git-upload-archive', + secret_token: secret_token, + protocol: 'ssh' + } ) end def lfs_auth_project(project) post( api("/internal/lfs_authenticate"), - secret_token: secret_token, - project: project.full_path + params: { + secret_token: secret_token, + project: project.full_path + } ) end def lfs_auth_key(key_id, project) post( api("/internal/lfs_authenticate"), - key_id: key_id, - secret_token: secret_token, - project: project.full_path + params: { + key_id: key_id, + secret_token: secret_token, + project: project.full_path + } ) end def lfs_auth_user(user_id, project) post( api("/internal/lfs_authenticate"), - user_id: user_id, - secret_token: secret_token, - project: project.full_path + params: { + user_id: user_id, + secret_token: secret_token, + project: project.full_path + } ) end end diff --git a/spec/requests/api/issues_spec.rb b/spec/requests/api/issues_spec.rb index 1827da61e2d..0fd465da4f8 100644 --- a/spec/requests/api/issues_spec.rb +++ b/spec/requests/api/issues_spec.rb @@ -66,7 +66,7 @@ describe API::Issues do describe "GET /issues" do context "when unauthenticated" do it "returns an array of all issues" do - get api("/issues"), scope: 'all' + get api("/issues"), params: { scope: 'all' } expect(response).to have_http_status(200) expect(json_response).to be_an Array @@ -79,13 +79,13 @@ describe API::Issues do end it "returns authentication error when scope is assigned-to-me" do - get api("/issues"), scope: 'assigned-to-me' + get api("/issues"), params: { scope: 'assigned-to-me' } expect(response).to have_http_status(401) end it "returns authentication error when scope is created-by-me" do - get api("/issues"), scope: 'created-by-me' + get api("/issues"), params: { scope: 'created-by-me' } expect(response).to have_http_status(401) end @@ -103,21 +103,21 @@ describe API::Issues do end it 'returns an array of closed issues' do - get api('/issues', user), state: :closed + get api('/issues', user), params: { state: :closed } expect_paginated_array_response(size: 1) expect(first_issue['id']).to eq(closed_issue.id) end it 'returns an array of opened issues' do - get api('/issues', user), state: :opened + get api('/issues', user), params: { state: :opened } expect_paginated_array_response(size: 1) expect(first_issue['id']).to eq(issue.id) end it 'returns an array of all issues' do - get api('/issues', user), state: :all + get api('/issues', user), params: { state: :all } expect_paginated_array_response(size: 2) expect(first_issue['id']).to eq(issue.id) @@ -127,7 +127,7 @@ describe API::Issues do it 'returns issues assigned to me' do issue2 = create(:issue, assignees: [user2], project: project) - get api('/issues', user2), scope: 'assigned_to_me' + get api('/issues', user2), params: { scope: 'assigned_to_me' } expect_paginated_array_response(size: 1) expect(first_issue['id']).to eq(issue2.id) @@ -136,7 +136,7 @@ describe API::Issues do it 'returns issues assigned to me (kebab-case)' do issue2 = create(:issue, assignees: [user2], project: project) - get api('/issues', user2), scope: 'assigned-to-me' + get api('/issues', user2), params: { scope: 'assigned-to-me' } expect_paginated_array_response(size: 1) expect(first_issue['id']).to eq(issue2.id) @@ -145,7 +145,7 @@ describe API::Issues do it 'returns issues authored by the given author id' do issue2 = create(:issue, author: user2, project: project) - get api('/issues', user), author_id: user2.id, scope: 'all' + get api('/issues', user), params: { author_id: user2.id, scope: 'all' } expect_paginated_array_response(size: 1) expect(first_issue['id']).to eq(issue2.id) @@ -154,7 +154,7 @@ describe API::Issues do it 'returns issues assigned to the given assignee id' do issue2 = create(:issue, assignees: [user2], project: project) - get api('/issues', user), assignee_id: user2.id, scope: 'all' + get api('/issues', user), params: { assignee_id: user2.id, scope: 'all' } expect_paginated_array_response(size: 1) expect(first_issue['id']).to eq(issue2.id) @@ -163,7 +163,7 @@ describe API::Issues do it 'returns issues authored by the given author id and assigned to the given assignee id' do issue2 = create(:issue, author: user2, assignees: [user2], project: project) - get api('/issues', user), author_id: user2.id, assignee_id: user2.id, scope: 'all' + get api('/issues', user), params: { author_id: user2.id, assignee_id: user2.id, scope: 'all' } expect_paginated_array_response(size: 1) expect(first_issue['id']).to eq(issue2.id) @@ -172,7 +172,7 @@ describe API::Issues do it 'returns issues with no assignee' do issue2 = create(:issue, author: user2, project: project) - get api('/issues', user), assignee_id: 0, scope: 'all' + get api('/issues', user), params: { assignee_id: 0, scope: 'all' } expect_paginated_array_response(size: 1) expect(first_issue['id']).to eq(issue2.id) @@ -181,7 +181,7 @@ describe API::Issues do it 'returns issues with no assignee' do issue2 = create(:issue, author: user2, project: project) - get api('/issues', user), assignee_id: 'None', scope: 'all' + get api('/issues', user), params: { assignee_id: 'None', scope: 'all' } expect_paginated_array_response(size: 1) expect(first_issue['id']).to eq(issue2.id) @@ -191,7 +191,7 @@ describe API::Issues do # This issue without assignee should not be returned create(:issue, author: user2, project: project) - get api('/issues', user), assignee_id: 'Any', scope: 'all' + get api('/issues', user), params: { assignee_id: 'Any', scope: 'all' } expect_paginated_array_response(size: 3) end @@ -202,7 +202,7 @@ describe API::Issues do create(:award_emoji, awardable: issue, user: user2, name: 'thumbsup') - get api('/issues', user2), my_reaction_emoji: 'Any', scope: 'all' + get api('/issues', user2), params: { my_reaction_emoji: 'Any', scope: 'all' } expect_paginated_array_response(size: 2) end @@ -211,20 +211,20 @@ describe API::Issues do issue2 = create(:issue, project: project, author: user, assignees: [user]) create(:award_emoji, awardable: issue2, user: user2, name: 'star') - get api('/issues', user2), my_reaction_emoji: 'None', scope: 'all' + get api('/issues', user2), params: { my_reaction_emoji: 'None', scope: 'all' } expect_paginated_array_response(size: 2) end it 'returns issues matching given search string for title' do - get api("/issues", user), search: issue.title + get api("/issues", user), params: { search: issue.title } expect_paginated_array_response(size: 1) expect(json_response.first['id']).to eq(issue.id) end it 'returns issues matching given search string for description' do - get api("/issues", user), search: issue.description + get api("/issues", user), params: { search: issue.description } expect_paginated_array_response(size: 1) expect(first_issue['id']).to eq(issue.id) @@ -267,7 +267,7 @@ describe API::Issues do end it 'returns an array of labeled issues' do - get api("/issues", user), labels: label.title + get api("/issues", user), params: { labels: label.title } expect_paginated_array_response(size: 1) expect(first_issue['labels']).to eq([label.title]) @@ -280,20 +280,20 @@ describe API::Issues do create(:label_link, label: label_b, target: issue) create(:label_link, label: label_c, target: issue) - get api("/issues", user), labels: "#{label.title},#{label_b.title},#{label_c.title}" + get api("/issues", user), params: { labels: "#{label.title},#{label_b.title},#{label_c.title}" } expect_paginated_array_response(size: 1) expect(json_response.first['labels']).to eq([label_c.title, label_b.title, label.title]) end it 'returns an empty array if no issue matches labels' do - get api('/issues', user), labels: 'foo,bar' + get api('/issues', user), params: { labels: 'foo,bar' } expect_paginated_array_response(size: 0) end it 'returns an array of labeled issues matching given state' do - get api("/issues", user), labels: label.title, state: :opened + get api("/issues", user), params: { labels: label.title, state: :opened } expect_paginated_array_response(size: 1) expect(json_response.first['labels']).to eq([label.title]) @@ -301,27 +301,27 @@ describe API::Issues do end it 'returns an empty array if no issue matches labels and state filters' do - get api("/issues", user), labels: label.title, state: :closed + get api("/issues", user), params: { labels: label.title, state: :closed } expect_paginated_array_response(size: 0) end it 'returns an array of issues with any label' do - get api("/issues", user), labels: IssuesFinder::FILTER_ANY + get api("/issues", user), params: { labels: IssuesFinder::FILTER_ANY } expect_paginated_array_response(size: 1) expect(json_response.first['id']).to eq(issue.id) end it 'returns an array of issues with no label' do - get api("/issues", user), labels: IssuesFinder::FILTER_NONE + get api("/issues", user), params: { labels: IssuesFinder::FILTER_NONE } expect_paginated_array_response(size: 1) expect(json_response.first['id']).to eq(closed_issue.id) end it 'returns an array of issues with no label when using the legacy No+Label filter' do - get api("/issues", user), labels: "No Label" + get api("/issues", user), params: { labels: "No Label" } expect_paginated_array_response(size: 1) expect(json_response.first['id']).to eq(closed_issue.id) @@ -363,14 +363,14 @@ describe API::Issues do end it 'returns an array of issues found by iids' do - get api('/issues', user), iids: [closed_issue.iid] + get api('/issues', user), params: { iids: [closed_issue.iid] } expect_paginated_array_response(size: 1) expect(json_response.first['id']).to eq(closed_issue.id) end it 'returns an empty array if iid does not exist' do - get api("/issues", user), iids: [99999] + get api("/issues", user), params: { iids: [99999] } expect_paginated_array_response(size: 0) end @@ -506,58 +506,58 @@ describe API::Issues do end it 'returns group issues without confidential issues for non project members' do - get api(base_url, non_member), state: :opened + get api(base_url, non_member), params: { state: :opened } expect_paginated_array_response(size: 1) expect(json_response.first['title']).to eq(group_issue.title) end it 'returns group confidential issues for author' do - get api(base_url, author), state: :opened + get api(base_url, author), params: { state: :opened } expect_paginated_array_response(size: 2) end it 'returns group confidential issues for assignee' do - get api(base_url, assignee), state: :opened + get api(base_url, assignee), params: { state: :opened } expect_paginated_array_response(size: 2) end it 'returns group issues with confidential issues for project members' do - get api(base_url, user), state: :opened + get api(base_url, user), params: { state: :opened } expect_paginated_array_response(size: 2) end it 'returns group confidential issues for admin' do - get api(base_url, admin), state: :opened + get api(base_url, admin), params: { state: :opened } expect_paginated_array_response(size: 2) end it 'returns an array of labeled group issues' do - get api(base_url, user), labels: group_label.title + get api(base_url, user), params: { labels: group_label.title } expect_paginated_array_response(size: 1) expect(json_response.first['labels']).to eq([group_label.title]) end it 'returns an array of labeled group issues where all labels match' do - get api(base_url, user), labels: "#{group_label.title},foo,bar" + get api(base_url, user), params: { labels: "#{group_label.title},foo,bar" } expect_paginated_array_response(size: 0) end it 'returns issues matching given search string for title' do - get api(base_url, user), search: group_issue.title + get api(base_url, user), params: { search: group_issue.title } expect_paginated_array_response(size: 1) expect(json_response.first['id']).to eq(group_issue.id) end it 'returns issues matching given search string for description' do - get api(base_url, user), search: group_issue.description + get api(base_url, user), params: { search: group_issue.description } expect_paginated_array_response(size: 1) expect(json_response.first['id']).to eq(group_issue.id) @@ -570,40 +570,40 @@ describe API::Issues do create(:label_link, label: label_b, target: group_issue) create(:label_link, label: label_c, target: group_issue) - get api(base_url, user), labels: "#{group_label.title},#{label_b.title},#{label_c.title}" + get api(base_url, user), params: { labels: "#{group_label.title},#{label_b.title},#{label_c.title}" } expect_paginated_array_response(size: 1) expect(json_response.first['labels']).to eq([label_c.title, label_b.title, group_label.title]) end it 'returns an array of issues found by iids' do - get api(base_url, user), iids: [group_issue.iid] + get api(base_url, user), params: { iids: [group_issue.iid] } expect_paginated_array_response(size: 1) expect(json_response.first['id']).to eq(group_issue.id) end it 'returns an empty array if iid does not exist' do - get api(base_url, user), iids: [99999] + get api(base_url, user), params: { iids: [99999] } expect_paginated_array_response(size: 0) end it 'returns an empty array if no group issue matches labels' do - get api(base_url, user), labels: 'foo,bar' + get api(base_url, user), params: { labels: 'foo,bar' } expect_paginated_array_response(size: 0) end it 'returns an array of group issues with any label' do - get api(base_url, user), labels: IssuesFinder::FILTER_ANY + get api(base_url, user), params: { labels: IssuesFinder::FILTER_ANY } expect_paginated_array_response(size: 1) expect(json_response.first['id']).to eq(group_issue.id) end it 'returns an array of group issues with no label' do - get api(base_url, user), labels: IssuesFinder::FILTER_NONE + get api(base_url, user), params: { labels: IssuesFinder::FILTER_NONE } response_ids = json_response.map { |issue| issue['id'] } @@ -612,33 +612,33 @@ describe API::Issues do end it 'returns an empty array if no issue matches milestone' do - get api(base_url, user), milestone: group_empty_milestone.title + get api(base_url, user), params: { milestone: group_empty_milestone.title } expect_paginated_array_response(size: 0) end it 'returns an empty array if milestone does not exist' do - get api(base_url, user), milestone: 'foo' + get api(base_url, user), params: { milestone: 'foo' } expect_paginated_array_response(size: 0) end it 'returns an array of issues in given milestone' do - get api(base_url, user), state: :opened, milestone: group_milestone.title + get api(base_url, user), params: { state: :opened, milestone: group_milestone.title } expect_paginated_array_response(size: 1) expect(json_response.first['id']).to eq(group_issue.id) end it 'returns an array of issues matching state in milestone' do - get api(base_url, user), milestone: group_milestone.title, state: :closed + get api(base_url, user), params: { milestone: group_milestone.title, state: :closed } expect_paginated_array_response(size: 1) expect(json_response.first['id']).to eq(group_closed_issue.id) end it 'returns an array of issues with no milestone' do - get api(base_url, user), milestone: no_milestone_title + get api(base_url, user), params: { milestone: no_milestone_title } expect(response).to have_gitlab_http_status(200) @@ -674,7 +674,7 @@ describe API::Issues do end it 'sorts by updated_at ascending when requested' do - get api(base_url, user), order_by: :updated_at, sort: :asc + get api(base_url, user), params: { order_by: :updated_at, sort: :asc } response_dates = json_response.map { |issue| issue['updated_at'] } @@ -777,7 +777,7 @@ describe API::Issues do end it 'returns an array of labeled project issues' do - get api("#{base_url}/issues", user), labels: label.title + get api("#{base_url}/issues", user), params: { labels: label.title } expect_paginated_array_response(size: 1) expect(json_response.first['labels']).to eq([label.title]) @@ -790,7 +790,7 @@ describe API::Issues do create(:label_link, label: label_b, target: issue) create(:label_link, label: label_c, target: issue) - get api("#{base_url}/issues", user), labels: "#{label.title},#{label_b.title},#{label_c.title}" + get api("#{base_url}/issues", user), params: { labels: "#{label.title},#{label_b.title},#{label_c.title}" } expect_paginated_array_response(size: 1) expect(json_response.first['labels']).to eq([label_c.title, label_b.title, label.title]) @@ -811,14 +811,14 @@ describe API::Issues do end it 'returns an array of issues found by iids' do - get api("#{base_url}/issues", user), iids: [issue.iid] + get api("#{base_url}/issues", user), params: { iids: [issue.iid] } expect_paginated_array_response(size: 1) expect(json_response.first['id']).to eq(issue.id) end it 'returns an empty array if iid does not exist' do - get api("#{base_url}/issues", user), iids: [99999] + get api("#{base_url}/issues", user), params: { iids: [99999] } expect_paginated_array_response(size: 0) end @@ -830,14 +830,14 @@ describe API::Issues do end it 'returns an array of project issues with any label' do - get api("#{base_url}/issues", user), labels: IssuesFinder::FILTER_ANY + get api("#{base_url}/issues", user), params: { labels: IssuesFinder::FILTER_ANY } expect_paginated_array_response(size: 1) expect(json_response.first['id']).to eq(issue.id) end it 'returns an array of project issues with no label' do - get api("#{base_url}/issues", user), labels: IssuesFinder::FILTER_NONE + get api("#{base_url}/issues", user), params: { labels: IssuesFinder::FILTER_NONE } response_ids = json_response.map { |issue| issue['id'] } @@ -846,25 +846,25 @@ describe API::Issues do end it 'returns an empty array if no project issue matches labels' do - get api("#{base_url}/issues", user), labels: 'foo,bar' + get api("#{base_url}/issues", user), params: { labels: 'foo,bar' } expect_paginated_array_response(size: 0) end it 'returns an empty array if no issue matches milestone' do - get api("#{base_url}/issues", user), milestone: empty_milestone.title + get api("#{base_url}/issues", user), params: { milestone: empty_milestone.title } expect_paginated_array_response(size: 0) end it 'returns an empty array if milestone does not exist' do - get api("#{base_url}/issues", user), milestone: :foo + get api("#{base_url}/issues", user), params: { milestone: :foo } expect_paginated_array_response(size: 0) end it 'returns an array of issues in given milestone' do - get api("#{base_url}/issues", user), milestone: milestone.title + get api("#{base_url}/issues", user), params: { milestone: milestone.title } expect_paginated_array_response(size: 2) expect(json_response.first['id']).to eq(issue.id) @@ -872,21 +872,21 @@ describe API::Issues do end it 'returns an array of issues matching state in milestone' do - get api("#{base_url}/issues", user), milestone: milestone.title, state: :closed + get api("#{base_url}/issues", user), params: { milestone: milestone.title, state: :closed } expect_paginated_array_response(size: 1) expect(json_response.first['id']).to eq(closed_issue.id) end it 'returns an array of issues with no milestone' do - get api("#{base_url}/issues", user), milestone: no_milestone_title + get api("#{base_url}/issues", user), params: { milestone: no_milestone_title } expect_paginated_array_response(size: 1) expect(json_response.first['id']).to eq(confidential_issue.id) end it 'returns an array of issues with any milestone' do - get api("#{base_url}/issues", user), milestone: any_milestone_title + get api("#{base_url}/issues", user), params: { milestone: any_milestone_title } response_ids = json_response.map { |issue| issue['id'] } @@ -904,7 +904,7 @@ describe API::Issues do end it 'sorts ascending when requested' do - get api("#{base_url}/issues", user), sort: :asc + get api("#{base_url}/issues", user), params: { sort: :asc } response_dates = json_response.map { |issue| issue['created_at'] } @@ -913,7 +913,7 @@ describe API::Issues do end it 'sorts by updated_at descending when requested' do - get api("#{base_url}/issues", user), order_by: :updated_at + get api("#{base_url}/issues", user), params: { order_by: :updated_at } response_dates = json_response.map { |issue| issue['updated_at'] } @@ -922,7 +922,7 @@ describe API::Issues do end it 'sorts by updated_at ascending when requested' do - get api("#{base_url}/issues", user), order_by: :updated_at, sort: :asc + get api("#{base_url}/issues", user), params: { order_by: :updated_at, sort: :asc } response_dates = json_response.map { |issue| issue['updated_at'] } @@ -1051,7 +1051,7 @@ describe API::Issues do context 'support for deprecated assignee_id' do it 'creates a new project issue' do post api("/projects/#{project.id}/issues", user), - title: 'new issue', assignee_id: user2.id + params: { title: 'new issue', assignee_id: user2.id } expect(response).to have_gitlab_http_status(201) expect(json_response['title']).to eq('new issue') @@ -1061,7 +1061,7 @@ describe API::Issues do it 'creates a new project issue when assignee_id is empty' do post api("/projects/#{project.id}/issues", user), - title: 'new issue', assignee_id: '' + params: { title: 'new issue', assignee_id: '' } expect(response).to have_gitlab_http_status(201) expect(json_response['title']).to eq('new issue') @@ -1072,7 +1072,7 @@ describe API::Issues do context 'single assignee restrictions' do it 'creates a new project issue with no more than one assignee' do post api("/projects/#{project.id}/issues", user), - title: 'new issue', assignee_ids: [user2.id, guest.id] + params: { title: 'new issue', assignee_ids: [user2.id, guest.id] } expect(response).to have_gitlab_http_status(201) expect(json_response['title']).to eq('new issue') @@ -1088,7 +1088,7 @@ describe API::Issues do end it 'renders 403' do - post api("/projects/#{project.id}/issues", not_member), title: 'new issue' + post api("/projects/#{project.id}/issues", not_member), params: { title: 'new issue' } expect(response).to have_gitlab_http_status(403) end @@ -1098,7 +1098,7 @@ describe API::Issues do context 'by an admin' do it 'sets the internal ID on the new issue' do post api("/projects/#{project.id}/issues", admin), - title: 'new issue', iid: 9001 + params: { title: 'new issue', iid: 9001 } expect(response).to have_gitlab_http_status(201) expect(json_response['iid']).to eq 9001 @@ -1108,7 +1108,7 @@ describe API::Issues do context 'by an owner' do it 'sets the internal ID on the new issue' do post api("/projects/#{project.id}/issues", user), - title: 'new issue', iid: 9001 + params: { title: 'new issue', iid: 9001 } expect(response).to have_gitlab_http_status(201) expect(json_response['iid']).to eq 9001 @@ -1122,7 +1122,7 @@ describe API::Issues do it 'sets the internal ID on the new issue' do group.add_owner(user2) post api("/projects/#{group_project.id}/issues", user2), - title: 'new issue', iid: 9001 + params: { title: 'new issue', iid: 9001 } expect(response).to have_gitlab_http_status(201) expect(json_response['iid']).to eq 9001 @@ -1132,7 +1132,7 @@ describe API::Issues do context 'by another user' do it 'ignores the given internal ID' do post api("/projects/#{project.id}/issues", user2), - title: 'new issue', iid: 9001 + params: { title: 'new issue', iid: 9001 } expect(response).to have_gitlab_http_status(201) expect(json_response['iid']).not_to eq 9001 @@ -1142,8 +1142,7 @@ describe API::Issues do it 'creates a new project issue' do post api("/projects/#{project.id}/issues", user), - title: 'new issue', labels: 'label, label2', weight: 3, - assignee_ids: [user2.id] + params: { title: 'new issue', labels: 'label, label2', weight: 3, assignee_ids: [user2.id] } expect(response).to have_gitlab_http_status(201) expect(json_response['title']).to eq('new issue') @@ -1156,7 +1155,7 @@ describe API::Issues do it 'creates a new confidential project issue' do post api("/projects/#{project.id}/issues", user), - title: 'new issue', confidential: true + params: { title: 'new issue', confidential: true } expect(response).to have_gitlab_http_status(201) expect(json_response['title']).to eq('new issue') @@ -1165,7 +1164,7 @@ describe API::Issues do it 'creates a new confidential project issue with a different param' do post api("/projects/#{project.id}/issues", user), - title: 'new issue', confidential: 'y' + params: { title: 'new issue', confidential: 'y' } expect(response).to have_gitlab_http_status(201) expect(json_response['title']).to eq('new issue') @@ -1174,7 +1173,7 @@ describe API::Issues do it 'creates a public issue when confidential param is false' do post api("/projects/#{project.id}/issues", user), - title: 'new issue', confidential: false + params: { title: 'new issue', confidential: false } expect(response).to have_gitlab_http_status(201) expect(json_response['title']).to eq('new issue') @@ -1183,21 +1182,23 @@ describe API::Issues do it 'creates a public issue when confidential param is invalid' do post api("/projects/#{project.id}/issues", user), - title: 'new issue', confidential: 'foo' + params: { title: 'new issue', confidential: 'foo' } expect(response).to have_gitlab_http_status(400) expect(json_response['error']).to eq('confidential is invalid') end it "returns a 400 bad request if title not given" do - post api("/projects/#{project.id}/issues", user), labels: 'label, label2' + post api("/projects/#{project.id}/issues", user), params: { labels: 'label, label2' } expect(response).to have_gitlab_http_status(400) end it 'allows special label names' do post api("/projects/#{project.id}/issues", user), - title: 'new issue', - labels: 'label, label?, label&foo, ?, &' + params: { + title: 'new issue', + labels: 'label, label?, label&foo, ?, &' + } expect(response.status).to eq(201) expect(json_response['labels']).to include 'label' expect(json_response['labels']).to include 'label?' @@ -1208,7 +1209,7 @@ describe API::Issues do it 'returns 400 if title is too long' do post api("/projects/#{project.id}/issues", user), - title: 'g' * 256 + params: { title: 'g' * 256 } expect(response).to have_gitlab_http_status(400) expect(json_response['message']['title']).to eq([ 'is too long (maximum is 255 characters)' @@ -1227,8 +1228,10 @@ describe API::Issues do context 'resolving all discussions in a merge request' do before do post api("/projects/#{project.id}/issues", user), - title: 'New Issue', - merge_request_to_resolve_discussions_of: merge_request.iid + params: { + title: 'New Issue', + merge_request_to_resolve_discussions_of: merge_request.iid + } end it_behaves_like 'creating an issue resolving discussions through the API' @@ -1237,9 +1240,11 @@ describe API::Issues do context 'resolving a single discussion' do before do post api("/projects/#{project.id}/issues", user), - title: 'New Issue', - merge_request_to_resolve_discussions_of: merge_request.iid, - discussion_to_resolve: discussion.id + params: { + title: 'New Issue', + merge_request_to_resolve_discussions_of: merge_request.iid, + discussion_to_resolve: discussion.id + } end it_behaves_like 'creating an issue resolving discussions through the API' @@ -1251,7 +1256,7 @@ describe API::Issues do due_date = 2.weeks.from_now.strftime('%Y-%m-%d') post api("/projects/#{project.id}/issues", user), - title: 'new issue', due_date: due_date + params: { title: 'new issue', due_date: due_date } expect(response).to have_gitlab_http_status(201) expect(json_response['title']).to eq('new issue') @@ -1266,7 +1271,7 @@ describe API::Issues do context 'by an admin' do it 'sets the creation time on the new issue' do - post api("/projects/#{project.id}/issues", admin), params + post api("/projects/#{project.id}/issues", admin), params: params expect(response).to have_gitlab_http_status(201) expect(Time.parse(json_response['created_at'])).to be_like_time(creation_time) @@ -1275,7 +1280,7 @@ describe API::Issues do context 'by a project owner' do it 'sets the creation time on the new issue' do - post api("/projects/#{project.id}/issues", user), params + post api("/projects/#{project.id}/issues", user), params: params expect(response).to have_gitlab_http_status(201) expect(Time.parse(json_response['created_at'])).to be_like_time(creation_time) @@ -1287,7 +1292,7 @@ describe API::Issues do group = create(:group) group_project = create(:project, :public, namespace: group) group.add_owner(user2) - post api("/projects/#{group_project.id}/issues", user2), params + post api("/projects/#{group_project.id}/issues", user2), params: params expect(response).to have_gitlab_http_status(201) expect(Time.parse(json_response['created_at'])).to be_like_time(creation_time) @@ -1296,7 +1301,7 @@ describe API::Issues do context 'by another user' do it 'ignores the given creation time' do - post api("/projects/#{project.id}/issues", user2), params + post api("/projects/#{project.id}/issues", user2), params: params expect(response).to have_gitlab_http_status(201) expect(Time.parse(json_response['created_at'])).not_to be_like_time(creation_time) @@ -1307,7 +1312,7 @@ describe API::Issues do context 'the user can only read the issue' do it 'cannot create new labels' do expect do - post api("/projects/#{project.id}/issues", non_member), title: 'new issue', labels: 'label, label2' + post api("/projects/#{project.id}/issues", non_member), params: { title: 'new issue', labels: 'label, label2' } end.not_to change { project.labels.count } end end @@ -1328,7 +1333,7 @@ describe API::Issues do end it "does not create a new project issue" do - expect { post api("/projects/#{project.id}/issues", user), params }.not_to change(Issue, :count) + expect { post api("/projects/#{project.id}/issues", user), params: params }.not_to change(Issue, :count) expect(response).to have_gitlab_http_status(400) expect(json_response['message']).to eq({ "error" => "Spam detected" }) @@ -1344,7 +1349,7 @@ describe API::Issues do describe "PUT /projects/:id/issues/:issue_iid to update only title" do it "updates a project issue" do put api("/projects/#{project.id}/issues/#{issue.iid}", user), - title: 'updated title' + params: { title: 'updated title' } expect(response).to have_gitlab_http_status(200) expect(json_response['title']).to eq('updated title') @@ -1352,20 +1357,22 @@ describe API::Issues do it "returns 404 error if issue iid not found" do put api("/projects/#{project.id}/issues/44444", user), - title: 'updated title' + params: { title: 'updated title' } expect(response).to have_gitlab_http_status(404) end it "returns 404 error if issue id is used instead of the iid" do put api("/projects/#{project.id}/issues/#{issue.id}", user), - title: 'updated title' + params: { title: 'updated title' } expect(response).to have_gitlab_http_status(404) end it 'allows special label names' do put api("/projects/#{project.id}/issues/#{issue.iid}", user), - title: 'updated title', - labels: 'label, label?, label&foo, ?, &' + params: { + title: 'updated title', + labels: 'label, label?, label&foo, ?, &' + } expect(response.status).to eq(200) expect(json_response['labels']).to include 'label' @@ -1378,40 +1385,40 @@ describe API::Issues do context 'confidential issues' do it "returns 403 for non project members" do put api("/projects/#{project.id}/issues/#{confidential_issue.iid}", non_member), - title: 'updated title' + params: { title: 'updated title' } expect(response).to have_gitlab_http_status(403) end it "returns 403 for project members with guest role" do put api("/projects/#{project.id}/issues/#{confidential_issue.iid}", guest), - title: 'updated title' + params: { title: 'updated title' } expect(response).to have_gitlab_http_status(403) end it "updates a confidential issue for project members" do put api("/projects/#{project.id}/issues/#{confidential_issue.iid}", user), - title: 'updated title' + params: { title: 'updated title' } expect(response).to have_gitlab_http_status(200) expect(json_response['title']).to eq('updated title') end it "updates a confidential issue for author" do put api("/projects/#{project.id}/issues/#{confidential_issue.iid}", author), - title: 'updated title' + params: { title: 'updated title' } expect(response).to have_gitlab_http_status(200) expect(json_response['title']).to eq('updated title') end it "updates a confidential issue for admin" do put api("/projects/#{project.id}/issues/#{confidential_issue.iid}", admin), - title: 'updated title' + params: { title: 'updated title' } expect(response).to have_gitlab_http_status(200) expect(json_response['title']).to eq('updated title') end it 'sets an issue to confidential' do put api("/projects/#{project.id}/issues/#{issue.iid}", user), - confidential: true + params: { confidential: true } expect(response).to have_gitlab_http_status(200) expect(json_response['confidential']).to be_truthy @@ -1419,7 +1426,7 @@ describe API::Issues do it 'makes a confidential issue public' do put api("/projects/#{project.id}/issues/#{confidential_issue.iid}", user), - confidential: false + params: { confidential: false } expect(response).to have_gitlab_http_status(200) expect(json_response['confidential']).to be_falsy @@ -1427,7 +1434,7 @@ describe API::Issues do it 'does not update a confidential issue with wrong confidential flag' do put api("/projects/#{project.id}/issues/#{confidential_issue.iid}", user), - confidential: 'foo' + params: { confidential: 'foo' } expect(response).to have_gitlab_http_status(400) expect(json_response['error']).to eq('confidential is invalid') @@ -1448,7 +1455,7 @@ describe API::Issues do allow_any_instance_of(SpamService).to receive_messages(check_for_spam?: true) allow_any_instance_of(AkismetService).to receive_messages(spam?: true) - put api("/projects/#{project.id}/issues/#{issue.iid}", user), params + put api("/projects/#{project.id}/issues/#{issue.iid}", user), params: params expect(response).to have_gitlab_http_status(400) expect(json_response['message']).to eq({ "error" => "Spam detected" }) @@ -1466,7 +1473,7 @@ describe API::Issues do context 'support for deprecated assignee_id' do it 'removes assignee' do put api("/projects/#{project.id}/issues/#{issue.iid}", user), - assignee_id: 0 + params: { assignee_id: 0 } expect(response).to have_gitlab_http_status(200) @@ -1475,7 +1482,7 @@ describe API::Issues do it 'updates an issue with new assignee' do put api("/projects/#{project.id}/issues/#{issue.iid}", user), - assignee_id: user2.id + params: { assignee_id: user2.id } expect(response).to have_gitlab_http_status(200) @@ -1485,7 +1492,7 @@ describe API::Issues do it 'removes assignee' do put api("/projects/#{project.id}/issues/#{issue.iid}", user), - assignee_ids: [0] + params: { assignee_ids: [0] } expect(response).to have_gitlab_http_status(200) @@ -1494,7 +1501,7 @@ describe API::Issues do it 'updates an issue with new assignee' do put api("/projects/#{project.id}/issues/#{issue.iid}", user), - assignee_ids: [user2.id] + params: { assignee_ids: [user2.id] } expect(response).to have_gitlab_http_status(200) @@ -1504,7 +1511,7 @@ describe API::Issues do context 'single assignee restrictions' do it 'updates an issue with several assignees but only one has been applied' do put api("/projects/#{project.id}/issues/#{issue.iid}", user), - assignee_ids: [user2.id, guest.id] + params: { assignee_ids: [user2.id, guest.id] } expect(response).to have_gitlab_http_status(200) @@ -1519,14 +1526,14 @@ describe API::Issues do it 'does not update labels if not present' do put api("/projects/#{project.id}/issues/#{issue.iid}", user), - title: 'updated title' + params: { title: 'updated title' } expect(response).to have_gitlab_http_status(200) expect(json_response['labels']).to eq([label.title]) end it 'removes all labels and touches the record' do Timecop.travel(1.minute.from_now) do - put api("/projects/#{project.id}/issues/#{issue.iid}", user), labels: '' + put api("/projects/#{project.id}/issues/#{issue.iid}", user), params: { labels: '' } end expect(response).to have_gitlab_http_status(200) @@ -1537,7 +1544,7 @@ describe API::Issues do it 'updates labels and touches the record' do Timecop.travel(1.minute.from_now) do put api("/projects/#{project.id}/issues/#{issue.iid}", user), - labels: 'foo,bar' + params: { labels: 'foo,bar' } end expect(response).to have_gitlab_http_status(200) expect(json_response['labels']).to include 'foo' @@ -1547,7 +1554,7 @@ describe API::Issues do it 'allows special label names' do put api("/projects/#{project.id}/issues/#{issue.iid}", user), - labels: 'label:foo, label-bar,label_bar,label/bar,label?bar,label&bar,?,&' + params: { labels: 'label:foo, label-bar,label_bar,label/bar,label?bar,label&bar,?,&' } expect(response.status).to eq(200) expect(json_response['labels']).to include 'label:foo' expect(json_response['labels']).to include 'label-bar' @@ -1561,7 +1568,7 @@ describe API::Issues do it 'returns 400 if title is too long' do put api("/projects/#{project.id}/issues/#{issue.iid}", user), - title: 'g' * 256 + params: { title: 'g' * 256 } expect(response).to have_gitlab_http_status(400) expect(json_response['message']['title']).to eq([ 'is too long (maximum is 255 characters)' @@ -1572,7 +1579,7 @@ describe API::Issues do describe "PUT /projects/:id/issues/:issue_iid to update state and label" do it "updates a project issue" do put api("/projects/#{project.id}/issues/#{issue.iid}", user), - labels: 'label2', state_event: "close" + params: { labels: 'label2', state_event: "close" } expect(response).to have_gitlab_http_status(200) expect(json_response['labels']).to include 'label2' @@ -1580,7 +1587,7 @@ describe API::Issues do end it 'reopens a project isssue' do - put api("/projects/#{project.id}/issues/#{closed_issue.iid}", user), state_event: 'reopen' + put api("/projects/#{project.id}/issues/#{closed_issue.iid}", user), params: { state_event: 'reopen' } expect(response).to have_gitlab_http_status(200) expect(json_response['state']).to eq 'opened' @@ -1590,7 +1597,7 @@ describe API::Issues do it 'accepts the update date to be set' do update_time = 2.weeks.ago put api("/projects/#{project.id}/issues/#{issue.iid}", user), - labels: 'label3', state_event: 'close', updated_at: update_time + params: { labels: 'label3', state_event: 'close', updated_at: update_time } expect(response).to have_gitlab_http_status(200) expect(json_response['labels']).to include 'label3' @@ -1603,7 +1610,7 @@ describe API::Issues do it 'creates a new project issue' do due_date = 2.weeks.from_now.strftime('%Y-%m-%d') - put api("/projects/#{project.id}/issues/#{issue.iid}", user), due_date: due_date + put api("/projects/#{project.id}/issues/#{issue.iid}", user), params: { due_date: due_date } expect(response).to have_gitlab_http_status(200) expect(json_response['due_date']).to eq(due_date) @@ -1657,7 +1664,7 @@ describe API::Issues do it 'moves an issue' do post api("/projects/#{project.id}/issues/#{issue.iid}/move", user), - to_project_id: target_project.id + params: { to_project_id: target_project.id } expect(response).to have_gitlab_http_status(201) expect(json_response['project_id']).to eq(target_project.id) @@ -1666,7 +1673,7 @@ describe API::Issues do context 'when source and target projects are the same' do it 'returns 400 when trying to move an issue' do post api("/projects/#{project.id}/issues/#{issue.iid}/move", user), - to_project_id: project.id + params: { to_project_id: project.id } expect(response).to have_gitlab_http_status(400) expect(json_response['message']).to eq('Cannot move issue to project it originates from!') @@ -1676,7 +1683,7 @@ describe API::Issues do context 'when the user does not have the permission to move issues' do it 'returns 400 when trying to move an issue' do post api("/projects/#{project.id}/issues/#{issue.iid}/move", user), - to_project_id: target_project2.id + params: { to_project_id: target_project2.id } expect(response).to have_gitlab_http_status(400) expect(json_response['message']).to eq('Cannot move issue due to insufficient permissions!') @@ -1685,7 +1692,7 @@ describe API::Issues do it 'moves the issue to another namespace if I am admin' do post api("/projects/#{project.id}/issues/#{issue.iid}/move", admin), - to_project_id: target_project2.id + params: { to_project_id: target_project2.id } expect(response).to have_gitlab_http_status(201) expect(json_response['project_id']).to eq(target_project2.id) @@ -1694,7 +1701,7 @@ describe API::Issues do context 'when using the issue ID instead of iid' do it 'returns 404 when trying to move an issue' do post api("/projects/#{project.id}/issues/#{issue.id}/move", user), - to_project_id: target_project.id + params: { to_project_id: target_project.id } expect(response).to have_gitlab_http_status(404) expect(json_response['message']).to eq('404 Issue Not Found') @@ -1704,7 +1711,7 @@ describe API::Issues do context 'when issue does not exist' do it 'returns 404 when trying to move an issue' do post api("/projects/#{project.id}/issues/123/move", user), - to_project_id: target_project.id + params: { to_project_id: target_project.id } expect(response).to have_gitlab_http_status(404) expect(json_response['message']).to eq('404 Issue Not Found') @@ -1714,7 +1721,7 @@ describe API::Issues do context 'when source project does not exist' do it 'returns 404 when trying to move an issue' do post api("/projects/0/issues/#{issue.iid}/move", user), - to_project_id: target_project.id + params: { to_project_id: target_project.id } expect(response).to have_gitlab_http_status(404) expect(json_response['message']).to eq('404 Project Not Found') @@ -1724,7 +1731,7 @@ describe API::Issues do context 'when target project does not exist' do it 'returns 404 when trying to move an issue' do post api("/projects/#{project.id}/issues/#{issue.iid}/move", user), - to_project_id: 0 + params: { to_project_id: 0 } expect(response).to have_gitlab_http_status(404) end diff --git a/spec/requests/api/jobs_spec.rb b/spec/requests/api/jobs_spec.rb index cd4e480ca64..97aa71bf231 100644 --- a/spec/requests/api/jobs_spec.rb +++ b/spec/requests/api/jobs_spec.rb @@ -58,7 +58,7 @@ describe API::Jobs do before do |example| unless example.metadata[:skip_before_request] - get api("/projects/#{project.id}/jobs", api_user), query + get api("/projects/#{project.id}/jobs", api_user), params: query end end @@ -142,15 +142,25 @@ describe API::Jobs do end context 'unauthorized user' do - let(:api_user) { nil } + context 'when user is not logged in' do + let(:api_user) { nil } - it 'does not return project jobs' do - expect(response).to have_gitlab_http_status(401) + it 'does not return project jobs' do + expect(response).to have_gitlab_http_status(401) + end + end + + context 'when user is guest' do + let(:api_user) { guest } + + it 'does not return project jobs' do + expect(response).to have_gitlab_http_status(403) + end end end def go - get api("/projects/#{project.id}/jobs", api_user), query + get api("/projects/#{project.id}/jobs", api_user), params: query end end @@ -160,7 +170,7 @@ describe API::Jobs do before do |example| unless example.metadata[:skip_before_request] job - get api("/projects/#{project.id}/pipelines/#{pipeline.id}/jobs", api_user), query + get api("/projects/#{project.id}/pipelines/#{pipeline.id}/jobs", api_user), params: query end end @@ -229,22 +239,32 @@ describe API::Jobs do it 'avoids N+1 queries' do control_count = ActiveRecord::QueryRecorder.new(skip_cached: false) do - get api("/projects/#{project.id}/pipelines/#{pipeline.id}/jobs", api_user), query + get api("/projects/#{project.id}/pipelines/#{pipeline.id}/jobs", api_user), params: query end.count 3.times { create(:ci_build, :trace_artifact, :artifacts, :test_reports, pipeline: pipeline) } expect do - get api("/projects/#{project.id}/pipelines/#{pipeline.id}/jobs", api_user), query + get api("/projects/#{project.id}/pipelines/#{pipeline.id}/jobs", api_user), params: query end.not_to exceed_all_query_limit(control_count) end end context 'unauthorized user' do - let(:api_user) { nil } + context 'when user is not logged in' do + let(:api_user) { nil } - it 'does not return jobs' do - expect(response).to have_gitlab_http_status(401) + it 'does not return jobs' do + expect(response).to have_gitlab_http_status(401) + end + end + + context 'when user is guest' do + let(:api_user) { guest } + + it 'does not return jobs' do + expect(response).to have_gitlab_http_status(403) + end end end end @@ -479,7 +499,7 @@ describe API::Jobs do end def get_for_ref(ref = pipeline.ref, job_name = job.name) - get api("/projects/#{project.id}/jobs/artifacts/#{ref}/download", api_user), job: job_name + get api("/projects/#{project.id}/jobs/artifacts/#{ref}/download", api_user), params: { job: job_name } end context 'when not logged in' do @@ -712,7 +732,7 @@ describe API::Jobs do end def get_artifact_file(artifact_path, ref = pipeline.ref, job_name = job.name) - get api("/projects/#{project.id}/jobs/artifacts/#{ref}/raw/#{artifact_path}", api_user), job: job_name + get api("/projects/#{project.id}/jobs/artifacts/#{ref}/raw/#{artifact_path}", api_user), params: { job: job_name } end end diff --git a/spec/requests/api/labels_spec.rb b/spec/requests/api/labels_spec.rb index b8a4a04a7e4..49eea2e362b 100644 --- a/spec/requests/api/labels_spec.rb +++ b/spec/requests/api/labels_spec.rb @@ -70,10 +70,12 @@ describe API::Labels do describe 'POST /projects/:id/labels' do it 'returns created label when all params' do post api("/projects/#{project.id}/labels", user), - name: 'Foo', - color: '#FFAABB', - description: 'test', - priority: 2 + params: { + name: 'Foo', + color: '#FFAABB', + description: 'test', + priority: 2 + } expect(response).to have_gitlab_http_status(201) expect(json_response['name']).to eq('Foo') @@ -84,8 +86,10 @@ describe API::Labels do it 'returns created label when only required params' do post api("/projects/#{project.id}/labels", user), - name: 'Foo & Bar', - color: '#FFAABB' + params: { + name: 'Foo & Bar', + color: '#FFAABB' + } expect(response.status).to eq(201) expect(json_response['name']).to eq('Foo & Bar') @@ -96,9 +100,11 @@ describe API::Labels do it 'creates a prioritized label' do post api("/projects/#{project.id}/labels", user), - name: 'Foo & Bar', - color: '#FFAABB', - priority: 3 + params: { + name: 'Foo & Bar', + color: '#FFAABB', + priority: 3 + } expect(response.status).to eq(201) expect(json_response['name']).to eq('Foo & Bar') @@ -108,35 +114,41 @@ describe API::Labels do end it 'returns a 400 bad request if name not given' do - post api("/projects/#{project.id}/labels", user), color: '#FFAABB' + post api("/projects/#{project.id}/labels", user), params: { color: '#FFAABB' } expect(response).to have_gitlab_http_status(400) end it 'returns a 400 bad request if color not given' do - post api("/projects/#{project.id}/labels", user), name: 'Foobar' + post api("/projects/#{project.id}/labels", user), params: { name: 'Foobar' } expect(response).to have_gitlab_http_status(400) end it 'returns 400 for invalid color' do post api("/projects/#{project.id}/labels", user), - name: 'Foo', - color: '#FFAA' + params: { + name: 'Foo', + color: '#FFAA' + } expect(response).to have_gitlab_http_status(400) expect(json_response['message']['color']).to eq(['must be a valid color code']) end it 'returns 400 for too long color code' do post api("/projects/#{project.id}/labels", user), - name: 'Foo', - color: '#FFAAFFFF' + params: { + name: 'Foo', + color: '#FFAAFFFF' + } expect(response).to have_gitlab_http_status(400) expect(json_response['message']['color']).to eq(['must be a valid color code']) end it 'returns 400 for invalid name' do post api("/projects/#{project.id}/labels", user), - name: ',', - color: '#FFAABB' + params: { + name: ',', + color: '#FFAABB' + } expect(response).to have_gitlab_http_status(400) expect(json_response['message']['title']).to eq(['is invalid']) end @@ -147,8 +159,10 @@ describe API::Labels do project.update(group: group) post api("/projects/#{project.id}/labels", user), - name: group_label.name, - color: '#FFAABB' + params: { + name: group_label.name, + color: '#FFAABB' + } expect(response).to have_gitlab_http_status(409) expect(json_response['message']).to eq('Label already exists') @@ -156,17 +170,21 @@ describe API::Labels do it 'returns 400 for invalid priority' do post api("/projects/#{project.id}/labels", user), - name: 'Foo', - color: '#FFAAFFFF', - priority: 'foo' + params: { + name: 'Foo', + color: '#FFAAFFFF', + priority: 'foo' + } expect(response).to have_gitlab_http_status(400) end it 'returns 409 if label already exists in project' do post api("/projects/#{project.id}/labels", user), - name: 'label1', - color: '#FFAABB' + params: { + name: 'label1', + color: '#FFAABB' + } expect(response).to have_gitlab_http_status(409) expect(json_response['message']).to eq('Label already exists') end @@ -174,13 +192,13 @@ describe API::Labels do describe 'DELETE /projects/:id/labels' do it 'returns 204 for existing label' do - delete api("/projects/#{project.id}/labels", user), name: 'label1' + delete api("/projects/#{project.id}/labels", user), params: { name: 'label1' } expect(response).to have_gitlab_http_status(204) end it 'returns 404 for non existing label' do - delete api("/projects/#{project.id}/labels", user), name: 'label2' + delete api("/projects/#{project.id}/labels", user), params: { name: 'label2' } expect(response).to have_gitlab_http_status(404) expect(json_response['message']).to eq('404 Label Not Found') end @@ -199,10 +217,12 @@ describe API::Labels do describe 'PUT /projects/:id/labels' do it 'returns 200 if name and colors and description are changed' do put api("/projects/#{project.id}/labels", user), - name: 'label1', - new_name: 'New Label', - color: '#FFFFFF', - description: 'test' + params: { + name: 'label1', + new_name: 'New Label', + color: '#FFFFFF', + description: 'test' + } expect(response).to have_gitlab_http_status(200) expect(json_response['name']).to eq('New Label') expect(json_response['color']).to eq('#FFFFFF') @@ -211,8 +231,10 @@ describe API::Labels do it 'returns 200 if name is changed' do put api("/projects/#{project.id}/labels", user), - name: 'label1', - new_name: 'New Label' + params: { + name: 'label1', + new_name: 'New Label' + } expect(response).to have_gitlab_http_status(200) expect(json_response['name']).to eq('New Label') expect(json_response['color']).to eq(label1.color) @@ -220,8 +242,10 @@ describe API::Labels do it 'returns 200 if colors is changed' do put api("/projects/#{project.id}/labels", user), - name: 'label1', - color: '#FFFFFF' + params: { + name: 'label1', + color: '#FFFFFF' + } expect(response).to have_gitlab_http_status(200) expect(json_response['name']).to eq(label1.name) expect(json_response['color']).to eq('#FFFFFF') @@ -229,8 +253,10 @@ describe API::Labels do it 'returns 200 if description is changed' do put api("/projects/#{project.id}/labels", user), - name: 'bug', - description: 'test' + params: { + name: 'bug', + description: 'test' + } expect(response).to have_gitlab_http_status(200) expect(json_response['name']).to eq(priority_label.name) @@ -240,8 +266,10 @@ describe API::Labels do it 'returns 200 if priority is changed' do put api("/projects/#{project.id}/labels", user), - name: 'bug', - priority: 10 + params: { + name: 'bug', + priority: 10 + } expect(response.status).to eq(200) expect(json_response['name']).to eq(priority_label.name) @@ -250,8 +278,10 @@ describe API::Labels do it 'returns 200 if a priority is added' do put api("/projects/#{project.id}/labels", user), - name: 'label1', - priority: 3 + params: { + name: 'label1', + priority: 3 + } expect(response.status).to eq(200) expect(json_response['name']).to eq(label1.name) @@ -260,8 +290,10 @@ describe API::Labels do it 'returns 200 if the priority is removed' do put api("/projects/#{project.id}/labels", user), - name: priority_label.name, - priority: nil + params: { + name: priority_label.name, + priority: nil + } expect(response.status).to eq(200) expect(json_response['name']).to eq(priority_label.name) @@ -270,19 +302,21 @@ describe API::Labels do it 'returns 404 if label does not exist' do put api("/projects/#{project.id}/labels", user), - name: 'label2', - new_name: 'label3' + params: { + name: 'label2', + new_name: 'label3' + } expect(response).to have_gitlab_http_status(404) end it 'returns 400 if no label name given' do - put api("/projects/#{project.id}/labels", user), new_name: 'label2' + put api("/projects/#{project.id}/labels", user), params: { new_name: 'label2' } expect(response).to have_gitlab_http_status(400) expect(json_response['error']).to eq('name is missing') end it 'returns 400 if no new parameters given' do - put api("/projects/#{project.id}/labels", user), name: 'label1' + put api("/projects/#{project.id}/labels", user), params: { name: 'label1' } expect(response).to have_gitlab_http_status(400) expect(json_response['error']).to eq('new_name, color, description, priority are missing, '\ 'at least one parameter must be provided') @@ -290,33 +324,41 @@ describe API::Labels do it 'returns 400 for invalid name' do put api("/projects/#{project.id}/labels", user), - name: 'label1', - new_name: ',', - color: '#FFFFFF' + params: { + name: 'label1', + new_name: ',', + color: '#FFFFFF' + } expect(response).to have_gitlab_http_status(400) expect(json_response['message']['title']).to eq(['is invalid']) end it 'returns 400 when color code is too short' do put api("/projects/#{project.id}/labels", user), - name: 'label1', - color: '#FF' + params: { + name: 'label1', + color: '#FF' + } expect(response).to have_gitlab_http_status(400) expect(json_response['message']['color']).to eq(['must be a valid color code']) end it 'returns 400 for too long color code' do put api("/projects/#{project.id}/labels", user), - name: 'label1', - color: '#FFAAFFFF' + params: { + name: 'label1', + color: '#FFAAFFFF' + } expect(response).to have_gitlab_http_status(400) expect(json_response['message']['color']).to eq(['must be a valid color code']) end it 'returns 400 for invalid priority' do put api("/projects/#{project.id}/labels", user), - name: 'label1', - priority: 'foo' + params: { + name: 'label1', + priority: 'foo' + } expect(response).to have_gitlab_http_status(400) end diff --git a/spec/requests/api/lint_spec.rb b/spec/requests/api/lint_spec.rb index e3065840e6f..f52cdf1c459 100644 --- a/spec/requests/api/lint_spec.rb +++ b/spec/requests/api/lint_spec.rb @@ -8,7 +8,7 @@ describe API::Lint do end it 'passes validation' do - post api('/ci/lint'), { content: yaml_content } + post api('/ci/lint'), params: { content: yaml_content } expect(response).to have_gitlab_http_status(200) expect(json_response).to be_an Hash @@ -19,7 +19,7 @@ describe API::Lint do context 'with an invalid .gitlab_ci.yml' do it 'responds with errors about invalid syntax' do - post api('/ci/lint'), { content: 'invalid content' } + post api('/ci/lint'), params: { content: 'invalid content' } expect(response).to have_gitlab_http_status(200) expect(json_response['status']).to eq('invalid') @@ -27,7 +27,7 @@ describe API::Lint do end it "responds with errors about invalid configuration" do - post api('/ci/lint'), { content: '{ image: "ruby:2.1", services: ["postgres"] }' } + post api('/ci/lint'), params: { content: '{ image: "ruby:2.1", services: ["postgres"] }' } expect(response).to have_gitlab_http_status(200) expect(json_response['status']).to eq('invalid') diff --git a/spec/requests/api/markdown_spec.rb b/spec/requests/api/markdown_spec.rb index e369c1435f0..e82ef002d32 100644 --- a/spec/requests/api/markdown_spec.rb +++ b/spec/requests/api/markdown_spec.rb @@ -7,7 +7,7 @@ describe API::Markdown do let(:user) {} # No-op. It gets overwritten in the contexts below. before do - post api("/markdown", user), params + post api("/markdown", user), params: params end shared_examples "rendered markdown text without GFM" do diff --git a/spec/requests/api/members_spec.rb b/spec/requests/api/members_spec.rb index bb32d581176..79edbb301f2 100644 --- a/spec/requests/api/members_spec.rb +++ b/spec/requests/api/members_spec.rb @@ -78,7 +78,7 @@ describe API::Members do end it 'finds members with query string' do - get api(members_url, developer), query: maintainer.username + get api(members_url, developer), params: { query: maintainer.username } expect(response).to have_gitlab_http_status(200) expect(response).to include_pagination_headers @@ -88,7 +88,7 @@ describe API::Members do end it 'finds all members with no query specified' do - get api(members_url, developer), query: '' + get api(members_url, developer), params: { query: '' } expect(response).to have_gitlab_http_status(200) expect(response).to include_pagination_headers @@ -178,7 +178,7 @@ describe API::Members do it_behaves_like 'a 404 response when source is private' do let(:route) do post api("/#{source_type.pluralize}/#{source.id}/members", stranger), - user_id: access_requester.id, access_level: Member::MAINTAINER + params: { user_id: access_requester.id, access_level: Member::MAINTAINER } end end @@ -188,7 +188,7 @@ describe API::Members do it 'returns 403' do user = public_send(type) post api("/#{source_type.pluralize}/#{source.id}/members", user), - user_id: access_requester.id, access_level: Member::MAINTAINER + params: { user_id: access_requester.id, access_level: Member::MAINTAINER } expect(response).to have_gitlab_http_status(403) end @@ -201,7 +201,7 @@ describe API::Members do it 'transforms the requester into a proper member' do expect do post api("/#{source_type.pluralize}/#{source.id}/members", maintainer), - user_id: access_requester.id, access_level: Member::MAINTAINER + params: { user_id: access_requester.id, access_level: Member::MAINTAINER } expect(response).to have_gitlab_http_status(201) end.to change { source.members.count }.by(1) @@ -214,7 +214,7 @@ describe API::Members do it 'creates a new member' do expect do post api("/#{source_type.pluralize}/#{source.id}/members", maintainer), - user_id: stranger.id, access_level: Member::DEVELOPER, expires_at: '2016-08-05' + params: { user_id: stranger.id, access_level: Member::DEVELOPER, expires_at: '2016-08-05' } expect(response).to have_gitlab_http_status(201) end.to change { source.members.count }.by(1) @@ -233,7 +233,7 @@ describe API::Members do parent.add_developer(stranger) post api("/#{source_type.pluralize}/#{source.id}/members", maintainer), - user_id: stranger.id, access_level: Member::REPORTER + params: { user_id: stranger.id, access_level: Member::REPORTER } expect(response).to have_gitlab_http_status(400) expect(json_response['message']['access_level']).to eq(["should be higher than Developer inherited membership from group #{parent.name}"]) @@ -247,7 +247,7 @@ describe API::Members do parent.add_developer(stranger) post api("/#{source_type.pluralize}/#{source.id}/members", maintainer), - user_id: stranger.id, access_level: Member::MAINTAINER + params: { user_id: stranger.id, access_level: Member::MAINTAINER } expect(response).to have_gitlab_http_status(201) expect(json_response['id']).to eq(stranger.id) @@ -257,14 +257,14 @@ describe API::Members do it "returns 409 if member already exists" do post api("/#{source_type.pluralize}/#{source.id}/members", maintainer), - user_id: maintainer.id, access_level: Member::MAINTAINER + params: { user_id: maintainer.id, access_level: Member::MAINTAINER } expect(response).to have_gitlab_http_status(409) end it 'returns 404 when the user_id is not valid' do post api("/#{source_type.pluralize}/#{source.id}/members", maintainer), - user_id: 0, access_level: Member::MAINTAINER + params: { user_id: 0, access_level: Member::MAINTAINER } expect(response).to have_gitlab_http_status(404) expect(json_response['message']).to eq('404 User Not Found') @@ -272,21 +272,21 @@ describe API::Members do it 'returns 400 when user_id is not given' do post api("/#{source_type.pluralize}/#{source.id}/members", maintainer), - access_level: Member::MAINTAINER + params: { access_level: Member::MAINTAINER } expect(response).to have_gitlab_http_status(400) end it 'returns 400 when access_level is not given' do post api("/#{source_type.pluralize}/#{source.id}/members", maintainer), - user_id: stranger.id + params: { user_id: stranger.id } expect(response).to have_gitlab_http_status(400) end it 'returns 400 when access_level is not valid' do post api("/#{source_type.pluralize}/#{source.id}/members", maintainer), - user_id: stranger.id, access_level: 1234 + params: { user_id: stranger.id, access_level: 1234 } expect(response).to have_gitlab_http_status(400) end @@ -298,7 +298,7 @@ describe API::Members do it_behaves_like 'a 404 response when source is private' do let(:route) do put api("/#{source_type.pluralize}/#{source.id}/members/#{developer.id}", stranger), - access_level: Member::MAINTAINER + params: { access_level: Member::MAINTAINER } end end @@ -308,7 +308,7 @@ describe API::Members do it 'returns 403' do user = public_send(type) put api("/#{source_type.pluralize}/#{source.id}/members/#{developer.id}", user), - access_level: Member::MAINTAINER + params: { access_level: Member::MAINTAINER } expect(response).to have_gitlab_http_status(403) end @@ -319,7 +319,7 @@ describe API::Members do context 'when authenticated as a maintainer/owner' do it 'updates the member' do put api("/#{source_type.pluralize}/#{source.id}/members/#{developer.id}", maintainer), - access_level: Member::MAINTAINER, expires_at: '2016-08-05' + params: { access_level: Member::MAINTAINER, expires_at: '2016-08-05' } expect(response).to have_gitlab_http_status(200) expect(json_response['id']).to eq(developer.id) @@ -330,7 +330,7 @@ describe API::Members do it 'returns 409 if member does not exist' do put api("/#{source_type.pluralize}/#{source.id}/members/123", maintainer), - access_level: Member::MAINTAINER + params: { access_level: Member::MAINTAINER } expect(response).to have_gitlab_http_status(404) end @@ -343,7 +343,7 @@ describe API::Members do it 'returns 400 when access level is not valid' do put api("/#{source_type.pluralize}/#{source.id}/members/#{developer.id}", maintainer), - access_level: 1234 + params: { access_level: 1234 } expect(response).to have_gitlab_http_status(400) end @@ -457,7 +457,7 @@ describe API::Members do it 'returns 403' do expect do post api("/projects/#{project.id}/members", maintainer), - user_id: stranger.id, access_level: Member::OWNER + params: { user_id: stranger.id, access_level: Member::OWNER } expect(response).to have_gitlab_http_status(400) end.to change { project.members.count }.by(0) diff --git a/spec/requests/api/merge_requests_spec.rb b/spec/requests/api/merge_requests_spec.rb index 27bcde77860..dd40f3d1561 100644 --- a/spec/requests/api/merge_requests_spec.rb +++ b/spec/requests/api/merge_requests_spec.rb @@ -45,7 +45,7 @@ describe API::MergeRequests do describe 'GET /merge_requests' do context 'when unauthenticated' do it 'returns an array of all merge requests' do - get api('/merge_requests', user), scope: 'all' + get api('/merge_requests', user), params: { scope: 'all' } expect_paginated_array_response end @@ -57,19 +57,19 @@ describe API::MergeRequests do end it "returns authentication error when scope is assigned-to-me" do - get api("/merge_requests"), scope: 'assigned-to-me' + get api("/merge_requests"), params: { scope: 'assigned-to-me' } expect(response).to have_gitlab_http_status(401) end it "returns authentication error when scope is assigned_to_me" do - get api("/merge_requests"), scope: 'assigned_to_me' + get api("/merge_requests"), params: { scope: 'assigned_to_me' } expect(response).to have_gitlab_http_status(401) end it "returns authentication error when scope is created-by-me" do - get api("/merge_requests"), scope: 'created-by-me' + get api("/merge_requests"), params: { scope: 'created-by-me' } expect(response).to have_gitlab_http_status(401) end @@ -81,7 +81,7 @@ describe API::MergeRequests do let(:user2) { create(:user) } it 'returns an array of all merge requests except unauthorized ones' do - get api('/merge_requests', user), scope: :all + get api('/merge_requests', user), params: { scope: :all } expect(response).to have_gitlab_http_status(200) expect(response).to include_pagination_headers @@ -91,7 +91,7 @@ describe API::MergeRequests do end it "returns an array of no merge_requests when wip=yes" do - get api("/merge_requests", user), wip: 'yes' + get api("/merge_requests", user), params: { wip: 'yes' } expect(response).to have_gitlab_http_status(200) expect(response).to include_pagination_headers @@ -100,7 +100,7 @@ describe API::MergeRequests do end it "returns an array of no merge_requests when wip=no" do - get api("/merge_requests", user), wip: 'no' + get api("/merge_requests", user), params: { wip: 'no' } expect(response).to have_gitlab_http_status(200) expect(response).to include_pagination_headers @@ -113,7 +113,7 @@ describe API::MergeRequests do private_project = create(:project, :private) merge_request3 = create(:merge_request, :simple, source_project: private_project, target_project: private_project, source_branch: 'other-branch') - get api('/merge_requests', user), scope: :all + get api('/merge_requests', user), params: { scope: :all } expect_response_contain_exactly(merge_request2, merge_request_merged, merge_request_closed, merge_request, merge_request_locked) expect(json_response.map { |mr| mr['id'] }).not_to include(merge_request3.id) @@ -130,7 +130,7 @@ describe API::MergeRequests do it 'returns an array of merge requests authored by the given user' do merge_request3 = create(:merge_request, :simple, author: user2, assignee: user, source_project: project2, target_project: project2, source_branch: 'other-branch') - get api('/merge_requests', user), author_id: user2.id, scope: :all + get api('/merge_requests', user), params: { author_id: user2.id, scope: :all } expect_response_ordered_exactly(merge_request3) end @@ -138,7 +138,7 @@ describe API::MergeRequests do it 'returns an array of merge requests assigned to the given user' do merge_request3 = create(:merge_request, :simple, author: user, assignee: user2, source_project: project2, target_project: project2, source_branch: 'other-branch') - get api('/merge_requests', user), assignee_id: user2.id, scope: :all + get api('/merge_requests', user), params: { assignee_id: user2.id, scope: :all } expect_response_ordered_exactly(merge_request3) end @@ -146,7 +146,7 @@ describe API::MergeRequests do it 'returns an array of merge requests with no assignee' do merge_request3 = create(:merge_request, :simple, author: user, source_project: project2, target_project: project2, source_branch: 'other-branch') - get api('/merge_requests', user), assignee_id: 'None', scope: :all + get api('/merge_requests', user), params: { assignee_id: 'None', scope: :all } expect_response_ordered_exactly(merge_request3) end @@ -155,7 +155,7 @@ describe API::MergeRequests do # This MR with no assignee should not be returned create(:merge_request, :simple, author: user, source_project: project2, target_project: project2, source_branch: 'other-branch') - get api('/merge_requests', user), assignee_id: 'Any', scope: :all + get api('/merge_requests', user), params: { assignee_id: 'Any', scope: :all } expect_response_contain_exactly(merge_request, merge_request2, merge_request_closed, merge_request_merged, merge_request_locked) end @@ -163,7 +163,7 @@ describe API::MergeRequests do it 'returns an array of merge requests assigned to me' do merge_request3 = create(:merge_request, :simple, author: user, assignee: user2, source_project: project2, target_project: project2, source_branch: 'other-branch') - get api('/merge_requests', user2), scope: 'assigned_to_me' + get api('/merge_requests', user2), params: { scope: 'assigned_to_me' } expect_response_ordered_exactly(merge_request3) end @@ -171,7 +171,7 @@ describe API::MergeRequests do it 'returns an array of merge requests assigned to me (kebab-case)' do merge_request3 = create(:merge_request, :simple, author: user, assignee: user2, source_project: project2, target_project: project2, source_branch: 'other-branch') - get api('/merge_requests', user2), scope: 'assigned-to-me' + get api('/merge_requests', user2), params: { scope: 'assigned-to-me' } expect_response_ordered_exactly(merge_request3) end @@ -179,7 +179,7 @@ describe API::MergeRequests do it 'returns an array of merge requests created by me' do merge_request3 = create(:merge_request, :simple, author: user2, assignee: user, source_project: project2, target_project: project2, source_branch: 'other-branch') - get api('/merge_requests', user2), scope: 'created_by_me' + get api('/merge_requests', user2), params: { scope: 'created_by_me' } expect_response_ordered_exactly(merge_request3) end @@ -187,7 +187,7 @@ describe API::MergeRequests do it 'returns an array of merge requests created by me (kebab-case)' do merge_request3 = create(:merge_request, :simple, author: user2, assignee: user, source_project: project2, target_project: project2, source_branch: 'other-branch') - get api('/merge_requests', user2), scope: 'created-by-me' + get api('/merge_requests', user2), params: { scope: 'created-by-me' } expect_response_ordered_exactly(merge_request3) end @@ -196,14 +196,14 @@ describe API::MergeRequests do merge_request3 = create(:merge_request, :simple, author: user, assignee: user, source_project: project2, target_project: project2, source_branch: 'other-branch') award_emoji = create(:award_emoji, awardable: merge_request3, user: user2, name: 'star') - get api('/merge_requests', user2), my_reaction_emoji: award_emoji.name, scope: 'all' + get api('/merge_requests', user2), params: { my_reaction_emoji: award_emoji.name, scope: 'all' } expect_response_ordered_exactly(merge_request3) end context 'source_branch param' do it 'returns merge requests with the given source branch' do - get api('/merge_requests', user), source_branch: merge_request_closed.source_branch, state: 'all' + get api('/merge_requests', user), params: { source_branch: merge_request_closed.source_branch, state: 'all' } expect_response_contain_exactly(merge_request_closed, merge_request_merged, merge_request_locked) end @@ -211,7 +211,7 @@ describe API::MergeRequests do context 'target_branch param' do it 'returns merge requests with the given target branch' do - get api('/merge_requests', user), target_branch: merge_request_closed.target_branch, state: 'all' + get api('/merge_requests', user), params: { target_branch: merge_request_closed.target_branch, state: 'all' } expect_response_contain_exactly(merge_request_closed, merge_request_merged, merge_request_locked) end @@ -255,13 +255,13 @@ describe API::MergeRequests do end it 'returns merge requests matching given search string for title' do - get api("/merge_requests", user), search: merge_request.title + get api("/merge_requests", user), params: { search: merge_request.title } expect_response_ordered_exactly(merge_request) end it 'returns merge requests for project matching given search string for description' do - get api("/merge_requests", user), project_id: project.id, search: merge_request.description + get api("/merge_requests", user), params: { project_id: project.id, search: merge_request.description } expect_response_ordered_exactly(merge_request) end @@ -269,7 +269,7 @@ describe API::MergeRequests do context 'state param' do it 'returns merge requests with the given state' do - get api('/merge_requests', user), state: 'locked' + get api('/merge_requests', user), params: { state: 'locked' } expect_response_contain_exactly(merge_request_locked) end @@ -291,7 +291,7 @@ describe API::MergeRequests do end it "returns an array of no merge_requests when wip=yes" do - get api("/projects/#{project.id}/merge_requests", user), wip: 'yes' + get api("/projects/#{project.id}/merge_requests", user), params: { wip: 'yes' } expect(response).to have_gitlab_http_status(200) expect(response).to include_pagination_headers @@ -300,7 +300,7 @@ describe API::MergeRequests do end it 'returns merge_request by "iids" array' do - get api(endpoint_path, user), iids: [merge_request.iid, merge_request_closed.iid] + get api(endpoint_path, user), params: { iids: [merge_request.iid, merge_request_closed.iid] } expect(response).to have_gitlab_http_status(200) expect(json_response).to be_an Array @@ -364,7 +364,7 @@ describe API::MergeRequests do end it 'exposes description and title html when render_html is true' do - get api("/projects/#{project.id}/merge_requests/#{merge_request.iid}", user), render_html: true + get api("/projects/#{project.id}/merge_requests/#{merge_request.iid}", user), params: { render_html: true } expect(response).to have_gitlab_http_status(200) @@ -372,7 +372,7 @@ describe API::MergeRequests do end it 'exposes rebase_in_progress when include_rebase_in_progress is true' do - get api("/projects/#{project.id}/merge_requests/#{merge_request.iid}", user), include_rebase_in_progress: true + get api("/projects/#{project.id}/merge_requests/#{merge_request.iid}", user), params: { include_rebase_in_progress: true } expect(response).to have_gitlab_http_status(200) @@ -421,7 +421,7 @@ describe API::MergeRequests do it 'returns the commits behind the target branch when include_diverged_commits_count is present' do allow_any_instance_of(merge_request.class).to receive(:diverged_commits_count).and_return(1) - get api("/projects/#{project.id}/merge_requests/#{merge_request.iid}", user), include_diverged_commits_count: true + get api("/projects/#{project.id}/merge_requests/#{merge_request.iid}", user), params: { include_diverged_commits_count: true } expect(response).to have_gitlab_http_status(200) expect(json_response['diverged_commits_count']).to eq(1) @@ -587,13 +587,15 @@ describe API::MergeRequests do context 'between branches projects' do it "returns merge_request" do post api("/projects/#{project.id}/merge_requests", user), - title: 'Test merge_request', - source_branch: 'feature_conflict', - target_branch: 'master', - author: user, - labels: 'label, label2', - milestone_id: milestone.id, - squash: true + params: { + title: 'Test merge_request', + source_branch: 'feature_conflict', + target_branch: 'master', + author: user, + labels: 'label, label2', + milestone_id: milestone.id, + squash: true + } expect(response).to have_gitlab_http_status(201) expect(json_response['title']).to eq('Test merge_request') @@ -605,35 +607,37 @@ describe API::MergeRequests do it "returns 422 when source_branch equals target_branch" do post api("/projects/#{project.id}/merge_requests", user), - title: "Test merge_request", source_branch: "master", target_branch: "master", author: user + params: { title: "Test merge_request", source_branch: "master", target_branch: "master", author: user } expect(response).to have_gitlab_http_status(422) end it "returns 400 when source_branch is missing" do post api("/projects/#{project.id}/merge_requests", user), - title: "Test merge_request", target_branch: "master", author: user + params: { title: "Test merge_request", target_branch: "master", author: user } expect(response).to have_gitlab_http_status(400) end it "returns 400 when target_branch is missing" do post api("/projects/#{project.id}/merge_requests", user), - title: "Test merge_request", source_branch: "markdown", author: user + params: { title: "Test merge_request", source_branch: "markdown", author: user } expect(response).to have_gitlab_http_status(400) end it "returns 400 when title is missing" do post api("/projects/#{project.id}/merge_requests", user), - target_branch: 'master', source_branch: 'markdown' + params: { target_branch: 'master', source_branch: 'markdown' } expect(response).to have_gitlab_http_status(400) end it 'allows special label names' do post api("/projects/#{project.id}/merge_requests", user), - title: 'Test merge_request', - source_branch: 'markdown', - target_branch: 'master', - author: user, - labels: 'label, label?, label&foo, ?, &' + params: { + title: 'Test merge_request', + source_branch: 'markdown', + target_branch: 'master', + author: user, + labels: 'label, label?, label&foo, ?, &' + } expect(response).to have_gitlab_http_status(201) expect(json_response['labels']).to include 'label' expect(json_response['labels']).to include 'label?' @@ -645,20 +649,24 @@ describe API::MergeRequests do context 'with existing MR' do before do post api("/projects/#{project.id}/merge_requests", user), - title: 'Test merge_request', - source_branch: 'feature_conflict', - target_branch: 'master', - author: user + params: { + title: 'Test merge_request', + source_branch: 'feature_conflict', + target_branch: 'master', + author: user + } @mr = MergeRequest.all.last end it 'returns 409 when MR already exists for source/target' do expect do post api("/projects/#{project.id}/merge_requests", user), - title: 'New test merge_request', - source_branch: 'feature_conflict', - target_branch: 'master', - author: user + params: { + title: 'New test merge_request', + source_branch: 'feature_conflict', + target_branch: 'master', + author: user + } end.to change { MergeRequest.count }.by(0) expect(response).to have_gitlab_http_status(409) end @@ -673,13 +681,13 @@ describe API::MergeRequests do end it 'sets force_remove_source_branch to false' do - post api("/projects/#{project.id}/merge_requests", user), params.merge(remove_source_branch: false) + post api("/projects/#{project.id}/merge_requests", user), params: params.merge(remove_source_branch: false) expect(json_response['force_remove_source_branch']).to be_falsy end it 'sets force_remove_source_branch to true' do - post api("/projects/#{project.id}/merge_requests", user), params.merge(remove_source_branch: true) + post api("/projects/#{project.id}/merge_requests", user), params: params.merge(remove_source_branch: true) expect(json_response['force_remove_source_branch']).to be_truthy end @@ -698,8 +706,7 @@ describe API::MergeRequests do it "returns merge_request" do post api("/projects/#{forked_project.id}/merge_requests", user2), - title: 'Test merge_request', source_branch: "feature_conflict", target_branch: "master", - author: user2, target_project_id: project.id, description: 'Test description for Test merge_request' + params: { title: 'Test merge_request', source_branch: "feature_conflict", target_branch: "master", author: user2, target_project_id: project.id, description: 'Test description for Test merge_request' } expect(response).to have_gitlab_http_status(201) expect(json_response['title']).to eq('Test merge_request') expect(json_response['description']).to eq('Test description for Test merge_request') @@ -710,7 +717,7 @@ describe API::MergeRequests do expect(forked_project.forked?).to be_truthy expect(forked_project.forked_from_project).to eq(project) post api("/projects/#{forked_project.id}/merge_requests", user2), - title: 'Test merge_request', source_branch: "master", target_branch: "master", author: user2, target_project_id: project.id + params: { title: 'Test merge_request', source_branch: "master", target_branch: "master", author: user2, target_project_id: project.id } expect(response).to have_gitlab_http_status(201) expect(json_response['title']).to eq('Test merge_request') end @@ -719,37 +726,38 @@ describe API::MergeRequests do project.project_feature.update(merge_requests_access_level: 0) post api("/projects/#{forked_project.id}/merge_requests", user2), - title: 'Test', - target_branch: 'master', - source_branch: 'markdown', - author: user2, - target_project_id: project.id + params: { + title: 'Test', + target_branch: 'master', + source_branch: 'markdown', + author: user2, + target_project_id: project.id + } expect(response).to have_gitlab_http_status(403) end it "returns 400 when source_branch is missing" do post api("/projects/#{forked_project.id}/merge_requests", user2), - title: 'Test merge_request', target_branch: "master", author: user2, target_project_id: project.id + params: { title: 'Test merge_request', target_branch: "master", author: user2, target_project_id: project.id } expect(response).to have_gitlab_http_status(400) end it "returns 400 when target_branch is missing" do post api("/projects/#{forked_project.id}/merge_requests", user2), - title: 'Test merge_request', target_branch: "master", author: user2, target_project_id: project.id + params: { title: 'Test merge_request', target_branch: "master", author: user2, target_project_id: project.id } expect(response).to have_gitlab_http_status(400) end it "returns 400 when title is missing" do post api("/projects/#{forked_project.id}/merge_requests", user2), - target_branch: 'master', source_branch: 'markdown', author: user2, target_project_id: project.id + params: { target_branch: 'master', source_branch: 'markdown', author: user2, target_project_id: project.id } expect(response).to have_gitlab_http_status(400) end it 'allows setting `allow_collaboration`' do post api("/projects/#{forked_project.id}/merge_requests", user2), - title: 'Test merge_request', source_branch: "feature_conflict", target_branch: "master", - author: user2, target_project_id: project.id, allow_collaboration: true + params: { title: 'Test merge_request', source_branch: "feature_conflict", target_branch: "master", author: user2, target_project_id: project.id, allow_collaboration: true } expect(response).to have_gitlab_http_status(201) expect(json_response['allow_collaboration']).to be_truthy expect(json_response['allow_maintainer_to_push']).to be_truthy @@ -767,13 +775,13 @@ describe API::MergeRequests do it 'returns 422 if targeting a different fork' do unrelated_project.add_developer(user2) - post api("/projects/#{forked_project.id}/merge_requests", user2), params + post api("/projects/#{forked_project.id}/merge_requests", user2), params: params expect(response).to have_gitlab_http_status(422) end it 'returns 403 if targeting a different fork which user can not access' do - post api("/projects/#{forked_project.id}/merge_requests", user2), params + post api("/projects/#{forked_project.id}/merge_requests", user2), params: params expect(response).to have_gitlab_http_status(403) end @@ -781,7 +789,7 @@ describe API::MergeRequests do it "returns 201 when target_branch is specified and for the same project" do post api("/projects/#{forked_project.id}/merge_requests", user2), - title: 'Test merge_request', target_branch: 'master', source_branch: 'markdown', author: user2, target_project_id: forked_project.id + params: { title: 'Test merge_request', target_branch: 'master', source_branch: 'markdown', author: user2, target_project_id: forked_project.id } expect(response).to have_gitlab_http_status(201) end end @@ -877,21 +885,21 @@ describe API::MergeRequests do end it "returns 409 if the SHA parameter doesn't match" do - put api("/projects/#{project.id}/merge_requests/#{merge_request.iid}/merge", user), sha: merge_request.diff_head_sha.reverse + put api("/projects/#{project.id}/merge_requests/#{merge_request.iid}/merge", user), params: { sha: merge_request.diff_head_sha.reverse } expect(response).to have_gitlab_http_status(409) expect(json_response['message']).to start_with('SHA does not match HEAD of source branch') end it "succeeds if the SHA parameter matches" do - put api("/projects/#{project.id}/merge_requests/#{merge_request.iid}/merge", user), sha: merge_request.diff_head_sha + put api("/projects/#{project.id}/merge_requests/#{merge_request.iid}/merge", user), params: { sha: merge_request.diff_head_sha } expect(response).to have_gitlab_http_status(200) end it "updates the MR's squash attribute" do expect do - put api("/projects/#{project.id}/merge_requests/#{merge_request.iid}/merge", user), squash: true + put api("/projects/#{project.id}/merge_requests/#{merge_request.iid}/merge", user), params: { squash: true } end.to change { merge_request.reload.squash } expect(response).to have_gitlab_http_status(200) @@ -901,7 +909,7 @@ describe API::MergeRequests do allow_any_instance_of(MergeRequest).to receive(:head_pipeline).and_return(pipeline) allow(pipeline).to receive(:active?).and_return(true) - put api("/projects/#{project.id}/merge_requests/#{merge_request.iid}/merge", user), merge_when_pipeline_succeeds: true + put api("/projects/#{project.id}/merge_requests/#{merge_request.iid}/merge", user), params: { merge_when_pipeline_succeeds: true } expect(response).to have_gitlab_http_status(200) expect(json_response['title']).to eq('Test') @@ -913,7 +921,7 @@ describe API::MergeRequests do allow(pipeline).to receive(:active?).and_return(true) project.update_attribute(:only_allow_merge_if_pipeline_succeeds, true) - put api("/projects/#{project.id}/merge_requests/#{merge_request.iid}/merge", user), merge_when_pipeline_succeeds: true + put api("/projects/#{project.id}/merge_requests/#{merge_request.iid}/merge", user), params: { merge_when_pipeline_succeeds: true } expect(response).to have_gitlab_http_status(200) expect(json_response['title']).to eq('Test') @@ -936,7 +944,7 @@ describe API::MergeRequests do describe "PUT /projects/:id/merge_requests/:merge_request_iid" do context "to close a MR" do it "returns merge_request" do - put api("/projects/#{project.id}/merge_requests/#{merge_request.iid}", user), state_event: "close" + put api("/projects/#{project.id}/merge_requests/#{merge_request.iid}", user), params: { state_event: "close" } expect(response).to have_gitlab_http_status(200) expect(json_response['state']).to eq('closed') @@ -944,38 +952,38 @@ describe API::MergeRequests do end it "updates title and returns merge_request" do - put api("/projects/#{project.id}/merge_requests/#{merge_request.iid}", user), title: "New title" + put api("/projects/#{project.id}/merge_requests/#{merge_request.iid}", user), params: { title: "New title" } expect(response).to have_gitlab_http_status(200) expect(json_response['title']).to eq('New title') end it "updates description and returns merge_request" do - put api("/projects/#{project.id}/merge_requests/#{merge_request.iid}", user), description: "New description" + put api("/projects/#{project.id}/merge_requests/#{merge_request.iid}", user), params: { description: "New description" } expect(response).to have_gitlab_http_status(200) expect(json_response['description']).to eq('New description') end it "updates milestone_id and returns merge_request" do - put api("/projects/#{project.id}/merge_requests/#{merge_request.iid}", user), milestone_id: milestone.id + put api("/projects/#{project.id}/merge_requests/#{merge_request.iid}", user), params: { milestone_id: milestone.id } expect(response).to have_gitlab_http_status(200) expect(json_response['milestone']['id']).to eq(milestone.id) end it "updates squash and returns merge_request" do - put api("/projects/#{project.id}/merge_requests/#{merge_request.iid}", user), squash: true + put api("/projects/#{project.id}/merge_requests/#{merge_request.iid}", user), params: { squash: true } expect(response).to have_gitlab_http_status(200) expect(json_response['squash']).to be_truthy end it "returns merge_request with renamed target_branch" do - put api("/projects/#{project.id}/merge_requests/#{merge_request.iid}", user), target_branch: "wiki" + put api("/projects/#{project.id}/merge_requests/#{merge_request.iid}", user), params: { target_branch: "wiki" } expect(response).to have_gitlab_http_status(200) expect(json_response['target_branch']).to eq('wiki') end it "returns merge_request that removes the source branch" do - put api("/projects/#{project.id}/merge_requests/#{merge_request.iid}", user), remove_source_branch: true + put api("/projects/#{project.id}/merge_requests/#{merge_request.iid}", user), params: { remove_source_branch: true } expect(response).to have_gitlab_http_status(200) expect(json_response['force_remove_source_branch']).to be_truthy @@ -983,8 +991,10 @@ describe API::MergeRequests do it 'allows special label names' do put api("/projects/#{project.id}/merge_requests/#{merge_request.iid}", user), - title: 'new issue', - labels: 'label, label?, label&foo, ?, &' + params: { + title: 'new issue', + labels: 'label, label?, label&foo, ?, &' + } expect(response.status).to eq(200) expect(json_response['labels']).to include 'label' @@ -995,7 +1005,7 @@ describe API::MergeRequests do end it 'does not update state when title is empty' do - put api("/projects/#{project.id}/merge_requests/#{merge_request.iid}", user), state_event: 'close', title: nil + put api("/projects/#{project.id}/merge_requests/#{merge_request.iid}", user), params: { state_event: 'close', title: nil } merge_request.reload expect(response).to have_gitlab_http_status(400) @@ -1003,7 +1013,7 @@ describe API::MergeRequests do end it 'does not update state when target_branch is empty' do - put api("/projects/#{project.id}/merge_requests/#{merge_request.iid}", user), state_event: 'close', target_branch: nil + put api("/projects/#{project.id}/merge_requests/#{merge_request.iid}", user), params: { state_event: 'close', target_branch: nil } merge_request.reload expect(response).to have_gitlab_http_status(400) @@ -1011,13 +1021,13 @@ describe API::MergeRequests do end it "returns 404 for an invalid merge request IID" do - put api("/projects/#{project.id}/merge_requests/12345", user), state_event: "close" + put api("/projects/#{project.id}/merge_requests/12345", user), params: { state_event: "close" } expect(response).to have_gitlab_http_status(404) end it "returns 404 if the merge request id is used instead of iid" do - put api("/projects/#{project.id}/merge_requests/#{merge_request.id}", user), state_event: "close" + put api("/projects/#{project.id}/merge_requests/#{merge_request.id}", user), params: { state_event: "close" } expect(response).to have_gitlab_http_status(404) end diff --git a/spec/requests/api/notes_spec.rb b/spec/requests/api/notes_spec.rb index 3fb45449c74..9bf753fe049 100644 --- a/spec/requests/api/notes_spec.rb +++ b/spec/requests/api/notes_spec.rb @@ -28,7 +28,7 @@ describe API::Notes do # before do post api("/projects/#{private_issue.project.id}/issues/#{private_issue.iid}/notes", user), - body: 'Hi!' + params: { body: 'Hi!' } end it 'responds with resource not found error' do @@ -154,7 +154,7 @@ describe API::Notes do end context 'when a user is a team member' do - subject { post api("/projects/#{project.id}/merge_requests/#{merge_request.iid}/notes", user), body: 'Hi!' } + subject { post api("/projects/#{project.id}/merge_requests/#{merge_request.iid}/notes", user), params: { body: 'Hi!' } } it 'returns 200 status' do subject @@ -168,7 +168,7 @@ describe API::Notes do end context 'when a user is not a team member' do - subject { post api("/projects/#{project.id}/merge_requests/#{merge_request.iid}/notes", private_user), body: 'Hi!' } + subject { post api("/projects/#{project.id}/merge_requests/#{merge_request.iid}/notes", private_user), params: { body: 'Hi!' } } it 'returns 403 status' do subject diff --git a/spec/requests/api/notification_settings_spec.rb b/spec/requests/api/notification_settings_spec.rb index 3273cd26690..4ed667ad0dc 100644 --- a/spec/requests/api/notification_settings_spec.rb +++ b/spec/requests/api/notification_settings_spec.rb @@ -20,7 +20,7 @@ describe API::NotificationSettings do let(:email) { create(:email, user: user) } it "updates global notification settings for the current user" do - put api("/notification_settings", user), { level: 'watch', notification_email: email.email } + put api("/notification_settings", user), params: { level: 'watch', notification_email: email.email } expect(response).to have_gitlab_http_status(200) expect(json_response['notification_email']).to eq(email.email) @@ -31,7 +31,7 @@ describe API::NotificationSettings do describe "PUT /notification_settings" do it "fails on non-user email address" do - put api("/notification_settings", user), { notification_email: 'invalid@example.com' } + put api("/notification_settings", user), params: { notification_email: 'invalid@example.com' } expect(response).to have_gitlab_http_status(400) end @@ -49,7 +49,7 @@ describe API::NotificationSettings do describe "PUT /groups/:id/notification_settings" do it "updates group level notification settings for the current user" do - put api("/groups/#{group.id}/notification_settings", user), { level: 'watch' } + put api("/groups/#{group.id}/notification_settings", user), params: { level: 'watch' } expect(response).to have_gitlab_http_status(200) expect(json_response['level']).to eq(user.reload.notification_settings_for(group).level) @@ -68,7 +68,7 @@ describe API::NotificationSettings do describe "PUT /projects/:id/notification_settings" do it "updates project level notification settings for the current user" do - put api("/projects/#{project.id}/notification_settings", user), { level: 'custom', new_note: true } + put api("/projects/#{project.id}/notification_settings", user), params: { level: 'custom', new_note: true } expect(response).to have_gitlab_http_status(200) expect(json_response['level']).to eq(user.reload.notification_settings_for(project).level) @@ -79,7 +79,7 @@ describe API::NotificationSettings do describe "PUT /projects/:id/notification_settings" do it "fails on invalid level" do - put api("/projects/#{project.id}/notification_settings", user), { level: 'invalid' } + put api("/projects/#{project.id}/notification_settings", user), params: { level: 'invalid' } expect(response).to have_gitlab_http_status(400) end diff --git a/spec/requests/api/oauth_tokens_spec.rb b/spec/requests/api/oauth_tokens_spec.rb index bdda80cc229..3811ec751de 100644 --- a/spec/requests/api/oauth_tokens_spec.rb +++ b/spec/requests/api/oauth_tokens_spec.rb @@ -3,7 +3,7 @@ require 'spec_helper' describe 'OAuth tokens' do context 'Resource Owner Password Credentials' do def request_oauth_token(user) - post '/oauth/token', username: user.username, password: user.password, grant_type: 'password' + post '/oauth/token', params: { username: user.username, password: user.password, grant_type: 'password' } end context 'when user has 2FA enabled' do diff --git a/spec/requests/api/pages_domains_spec.rb b/spec/requests/api/pages_domains_spec.rb index 35b6ed8d5c0..3eb68a6abb6 100644 --- a/spec/requests/api/pages_domains_spec.rb +++ b/spec/requests/api/pages_domains_spec.rb @@ -236,7 +236,7 @@ describe API::PagesDomains do shared_examples_for 'post pages domains' do it 'creates a new pages domain' do - post api(route, user), params + post api(route, user), params: params pages_domain = PagesDomain.find_by(domain: json_response['domain']) expect(response).to have_gitlab_http_status(201) @@ -247,7 +247,7 @@ describe API::PagesDomains do end it 'creates a new secure pages domain' do - post api(route, user), params_secure + post api(route, user), params: params_secure pages_domain = PagesDomain.find_by(domain: json_response['domain']) expect(response).to have_gitlab_http_status(201) @@ -258,13 +258,13 @@ describe API::PagesDomains do end it 'fails to create pages domain without key' do - post api(route, user), pages_domain_secure_params.slice(:domain, :certificate) + post api(route, user), params: pages_domain_secure_params.slice(:domain, :certificate) expect(response).to have_gitlab_http_status(400) end it 'fails to create pages domain with key missmatch' do - post api(route, user), pages_domain_secure_key_missmatch_params.slice(:domain, :certificate, :key) + post api(route, user), params: pages_domain_secure_key_missmatch_params.slice(:domain, :certificate, :key) expect(response).to have_gitlab_http_status(400) end @@ -284,7 +284,7 @@ describe API::PagesDomains do end it_behaves_like '403 response' do - let(:request) { post api(route, user), params } + let(:request) { post api(route, user), params: params } end end @@ -294,7 +294,7 @@ describe API::PagesDomains do end it_behaves_like '403 response' do - let(:request) { post api(route, user), params } + let(:request) { post api(route, user), params: params } end end @@ -304,13 +304,13 @@ describe API::PagesDomains do end it_behaves_like '403 response' do - let(:request) { post api(route, user), params } + let(:request) { post api(route, user), params: params } end end context 'when user is not a member' do it_behaves_like '404 response' do - let(:request) { post api(route, user), params } + let(:request) { post api(route, user), params: params } end end end @@ -331,7 +331,7 @@ describe API::PagesDomains do end it 'updates pages domain adding certificate' do - put api(route_domain, user), params_secure + put api(route_domain, user), params: params_secure pages_domain.reload expect(response).to have_gitlab_http_status(200) @@ -341,7 +341,7 @@ describe API::PagesDomains do end it 'updates pages domain with expired certificate' do - put api(route_expired_domain, user), params_secure + put api(route_expired_domain, user), params: params_secure pages_domain_expired.reload expect(response).to have_gitlab_http_status(200) @@ -351,7 +351,7 @@ describe API::PagesDomains do end it 'updates pages domain with expired certificate not updating key' do - put api(route_secure_domain, user), params_secure_nokey + put api(route_secure_domain, user), params: params_secure_nokey pages_domain_secure.reload expect(response).to have_gitlab_http_status(200) @@ -360,19 +360,19 @@ describe API::PagesDomains do end it 'fails to update pages domain adding certificate without key' do - put api(route_domain, user), params_secure_nokey + put api(route_domain, user), params: params_secure_nokey expect(response).to have_gitlab_http_status(400) end it 'fails to update pages domain adding certificate with missing chain' do - put api(route_domain, user), pages_domain_secure_missing_chain_params.slice(:certificate) + put api(route_domain, user), params: pages_domain_secure_missing_chain_params.slice(:certificate) expect(response).to have_gitlab_http_status(400) end it 'fails to update pages domain with key missmatch' do - put api(route_secure_domain, user), pages_domain_secure_key_missmatch_params.slice(:certificate, :key) + put api(route_secure_domain, user), params: pages_domain_secure_key_missmatch_params.slice(:certificate, :key) expect(response).to have_gitlab_http_status(400) end diff --git a/spec/requests/api/pipeline_schedules_spec.rb b/spec/requests/api/pipeline_schedules_spec.rb index 997d413eb4f..870ef34437f 100644 --- a/spec/requests/api/pipeline_schedules_spec.rb +++ b/spec/requests/api/pipeline_schedules_spec.rb @@ -58,7 +58,7 @@ describe API::PipelineSchedules do end it 'returns matched pipeline schedules' do - get api("/projects/#{project.id}/pipeline_schedules", developer), scope: target + get api("/projects/#{project.id}/pipeline_schedules", developer), params: { scope: target } expect(json_response.map { |r| r['active'] }).to all(eq(active?(target))) end @@ -146,7 +146,7 @@ describe API::PipelineSchedules do it 'creates pipeline_schedule' do expect do post api("/projects/#{project.id}/pipeline_schedules", developer), - params + params: params end.to change { project.pipeline_schedules.count }.by(1) expect(response).to have_gitlab_http_status(:created) @@ -170,7 +170,7 @@ describe API::PipelineSchedules do context 'when cron has validation error' do it 'does not create pipeline_schedule' do post api("/projects/#{project.id}/pipeline_schedules", developer), - params.merge('cron' => 'invalid-cron') + params: params.merge('cron' => 'invalid-cron') expect(response).to have_gitlab_http_status(:bad_request) expect(json_response['message']).to have_key('cron') @@ -180,7 +180,7 @@ describe API::PipelineSchedules do context 'authenticated user with invalid permissions' do it 'does not create pipeline_schedule' do - post api("/projects/#{project.id}/pipeline_schedules", user), params + post api("/projects/#{project.id}/pipeline_schedules", user), params: params expect(response).to have_gitlab_http_status(:not_found) end @@ -188,7 +188,7 @@ describe API::PipelineSchedules do context 'unauthenticated user' do it 'does not create pipeline_schedule' do - post api("/projects/#{project.id}/pipeline_schedules"), params + post api("/projects/#{project.id}/pipeline_schedules"), params: params expect(response).to have_gitlab_http_status(:unauthorized) end @@ -203,7 +203,7 @@ describe API::PipelineSchedules do context 'authenticated user with valid permissions' do it 'updates cron' do put api("/projects/#{project.id}/pipeline_schedules/#{pipeline_schedule.id}", developer), - cron: '1 2 3 4 *' + params: { cron: '1 2 3 4 *' } expect(response).to have_gitlab_http_status(:ok) expect(response).to match_response_schema('pipeline_schedule') @@ -213,7 +213,7 @@ describe API::PipelineSchedules do context 'when cron has validation error' do it 'does not update pipeline_schedule' do put api("/projects/#{project.id}/pipeline_schedules/#{pipeline_schedule.id}", developer), - cron: 'invalid-cron' + params: { cron: 'invalid-cron' } expect(response).to have_gitlab_http_status(:bad_request) expect(json_response['message']).to have_key('cron') @@ -331,7 +331,7 @@ describe API::PipelineSchedules do it 'creates pipeline_schedule_variable' do expect do post api("/projects/#{project.id}/pipeline_schedules/#{pipeline_schedule.id}/variables", developer), - params + params: params end.to change { pipeline_schedule.variables.count }.by(1) expect(response).to have_gitlab_http_status(:created) @@ -352,7 +352,7 @@ describe API::PipelineSchedules do context 'when key has validation error' do it 'does not create pipeline_schedule_variable' do post api("/projects/#{project.id}/pipeline_schedules/#{pipeline_schedule.id}/variables", developer), - params.merge('key' => '!?!?') + params: params.merge('key' => '!?!?') expect(response).to have_gitlab_http_status(:bad_request) expect(json_response['message']).to have_key('key') @@ -362,7 +362,7 @@ describe API::PipelineSchedules do context 'authenticated user with invalid permissions' do it 'does not create pipeline_schedule_variable' do - post api("/projects/#{project.id}/pipeline_schedules/#{pipeline_schedule.id}/variables", user), params + post api("/projects/#{project.id}/pipeline_schedules/#{pipeline_schedule.id}/variables", user), params: params expect(response).to have_gitlab_http_status(:not_found) end @@ -370,7 +370,7 @@ describe API::PipelineSchedules do context 'unauthenticated user' do it 'does not create pipeline_schedule_variable' do - post api("/projects/#{project.id}/pipeline_schedules/#{pipeline_schedule.id}/variables"), params + post api("/projects/#{project.id}/pipeline_schedules/#{pipeline_schedule.id}/variables"), params: params expect(response).to have_gitlab_http_status(:unauthorized) end @@ -389,7 +389,7 @@ describe API::PipelineSchedules do context 'authenticated user with valid permissions' do it 'updates pipeline_schedule_variable' do put api("/projects/#{project.id}/pipeline_schedules/#{pipeline_schedule.id}/variables/#{pipeline_schedule_variable.key}", developer), - value: 'updated_value' + params: { value: 'updated_value' } expect(response).to have_gitlab_http_status(:ok) expect(response).to match_response_schema('pipeline_schedule_variable') diff --git a/spec/requests/api/pipelines_spec.rb b/spec/requests/api/pipelines_spec.rb index 2e4fa0f9e16..eb002de62a2 100644 --- a/spec/requests/api/pipelines_spec.rb +++ b/spec/requests/api/pipelines_spec.rb @@ -36,7 +36,7 @@ describe API::Pipelines do end it 'returns matched pipelines' do - get api("/projects/#{project.id}/pipelines", user), scope: target + get api("/projects/#{project.id}/pipelines", user), params: { scope: target } expect(response).to have_gitlab_http_status(:ok) expect(response).to include_pagination_headers @@ -54,7 +54,7 @@ describe API::Pipelines do end it 'returns matched pipelines' do - get api("/projects/#{project.id}/pipelines", user), scope: 'finished' + get api("/projects/#{project.id}/pipelines", user), params: { scope: 'finished' } expect(response).to have_gitlab_http_status(:ok) expect(response).to include_pagination_headers @@ -69,7 +69,7 @@ describe API::Pipelines do context 'when scope is branches' do it 'returns matched pipelines' do - get api("/projects/#{project.id}/pipelines", user), scope: 'branches' + get api("/projects/#{project.id}/pipelines", user), params: { scope: 'branches' } expect(response).to have_gitlab_http_status(:ok) expect(response).to include_pagination_headers @@ -80,7 +80,7 @@ describe API::Pipelines do context 'when scope is tags' do it 'returns matched pipelines' do - get api("/projects/#{project.id}/pipelines", user), scope: 'tags' + get api("/projects/#{project.id}/pipelines", user), params: { scope: 'tags' } expect(response).to have_gitlab_http_status(:ok) expect(response).to include_pagination_headers @@ -92,7 +92,7 @@ describe API::Pipelines do context 'when scope is invalid' do it 'returns bad_request' do - get api("/projects/#{project.id}/pipelines", user), scope: 'invalid-scope' + get api("/projects/#{project.id}/pipelines", user), params: { scope: 'invalid-scope' } expect(response).to have_gitlab_http_status(:bad_request) end @@ -107,7 +107,7 @@ describe API::Pipelines do end it 'returns matched pipelines' do - get api("/projects/#{project.id}/pipelines", user), status: target + get api("/projects/#{project.id}/pipelines", user), params: { status: target } expect(response).to have_gitlab_http_status(:ok) expect(response).to include_pagination_headers @@ -119,7 +119,7 @@ describe API::Pipelines do context 'when status is invalid' do it 'returns bad_request' do - get api("/projects/#{project.id}/pipelines", user), status: 'invalid-status' + get api("/projects/#{project.id}/pipelines", user), params: { status: 'invalid-status' } expect(response).to have_gitlab_http_status(:bad_request) end @@ -132,7 +132,7 @@ describe API::Pipelines do context 'when ref exists' do it 'returns matched pipelines' do - get api("/projects/#{project.id}/pipelines", user), ref: 'master' + get api("/projects/#{project.id}/pipelines", user), params: { ref: 'master' } expect(response).to have_gitlab_http_status(:ok) expect(response).to include_pagination_headers @@ -143,7 +143,7 @@ describe API::Pipelines do context 'when ref does not exist' do it 'returns empty' do - get api("/projects/#{project.id}/pipelines", user), ref: 'invalid-ref' + get api("/projects/#{project.id}/pipelines", user), params: { ref: 'invalid-ref' } expect(response).to have_gitlab_http_status(:ok) expect(response).to include_pagination_headers @@ -157,7 +157,7 @@ describe API::Pipelines do context 'when name exists' do it 'returns matched pipelines' do - get api("/projects/#{project.id}/pipelines", user), name: user.name + get api("/projects/#{project.id}/pipelines", user), params: { name: user.name } expect(response).to have_gitlab_http_status(:ok) expect(response).to include_pagination_headers @@ -167,7 +167,7 @@ describe API::Pipelines do context 'when name does not exist' do it 'returns empty' do - get api("/projects/#{project.id}/pipelines", user), name: 'invalid-name' + get api("/projects/#{project.id}/pipelines", user), params: { name: 'invalid-name' } expect(response).to have_gitlab_http_status(:ok) expect(response).to include_pagination_headers @@ -181,7 +181,7 @@ describe API::Pipelines do context 'when username exists' do it 'returns matched pipelines' do - get api("/projects/#{project.id}/pipelines", user), username: user.username + get api("/projects/#{project.id}/pipelines", user), params: { username: user.username } expect(response).to have_gitlab_http_status(:ok) expect(response).to include_pagination_headers @@ -191,7 +191,7 @@ describe API::Pipelines do context 'when username does not exist' do it 'returns empty' do - get api("/projects/#{project.id}/pipelines", user), username: 'invalid-username' + get api("/projects/#{project.id}/pipelines", user), params: { username: 'invalid-username' } expect(response).to have_gitlab_http_status(:ok) expect(response).to include_pagination_headers @@ -206,7 +206,7 @@ describe API::Pipelines do context 'when yaml_errors is true' do it 'returns matched pipelines' do - get api("/projects/#{project.id}/pipelines", user), yaml_errors: true + get api("/projects/#{project.id}/pipelines", user), params: { yaml_errors: true } expect(response).to have_gitlab_http_status(:ok) expect(response).to include_pagination_headers @@ -216,7 +216,7 @@ describe API::Pipelines do context 'when yaml_errors is false' do it 'returns matched pipelines' do - get api("/projects/#{project.id}/pipelines", user), yaml_errors: false + get api("/projects/#{project.id}/pipelines", user), params: { yaml_errors: false } expect(response).to have_gitlab_http_status(:ok) expect(response).to include_pagination_headers @@ -226,7 +226,7 @@ describe API::Pipelines do context 'when yaml_errors is invalid' do it 'returns bad_request' do - get api("/projects/#{project.id}/pipelines", user), yaml_errors: 'invalid-yaml_errors' + get api("/projects/#{project.id}/pipelines", user), params: { yaml_errors: 'invalid-yaml_errors' } expect(response).to have_gitlab_http_status(:bad_request) end @@ -243,7 +243,7 @@ describe API::Pipelines do context 'when sort parameter is valid' do it 'sorts as user_id: :desc' do - get api("/projects/#{project.id}/pipelines", user), order_by: 'user_id', sort: 'desc' + get api("/projects/#{project.id}/pipelines", user), params: { order_by: 'user_id', sort: 'desc' } expect(response).to have_gitlab_http_status(:ok) expect(response).to include_pagination_headers @@ -256,7 +256,7 @@ describe API::Pipelines do context 'when sort parameter is invalid' do it 'returns bad_request' do - get api("/projects/#{project.id}/pipelines", user), order_by: 'user_id', sort: 'invalid_sort' + get api("/projects/#{project.id}/pipelines", user), params: { order_by: 'user_id', sort: 'invalid_sort' } expect(response).to have_gitlab_http_status(:bad_request) end @@ -265,7 +265,7 @@ describe API::Pipelines do context 'when order_by is invalid' do it 'returns bad_request' do - get api("/projects/#{project.id}/pipelines", user), order_by: 'lock_version', sort: 'asc' + get api("/projects/#{project.id}/pipelines", user), params: { order_by: 'lock_version', sort: 'asc' } expect(response).to have_gitlab_http_status(:bad_request) end @@ -303,7 +303,7 @@ describe API::Pipelines do it 'creates and returns a new pipeline' do expect do - post api("/projects/#{project.id}/pipeline", user), ref: project.default_branch + post api("/projects/#{project.id}/pipeline", user), params: { ref: project.default_branch } end.to change { project.ci_pipelines.count }.by(1) expect(response).to have_gitlab_http_status(201) @@ -316,7 +316,7 @@ describe API::Pipelines do it 'creates and returns a new pipeline using the given variables' do expect do - post api("/projects/#{project.id}/pipeline", user), ref: project.default_branch, variables: variables + post api("/projects/#{project.id}/pipeline", user), params: { ref: project.default_branch, variables: variables } end.to change { project.ci_pipelines.count }.by(1) expect_variables(project.ci_pipelines.last.variables, variables) @@ -337,7 +337,7 @@ describe API::Pipelines do it 'creates and returns a new pipeline using the given variables' do expect do - post api("/projects/#{project.id}/pipeline", user), ref: project.default_branch, variables: variables + post api("/projects/#{project.id}/pipeline", user), params: { ref: project.default_branch, variables: variables } end.to change { project.ci_pipelines.count }.by(1) expect_variables(project.ci_pipelines.last.variables, variables) @@ -352,7 +352,7 @@ describe API::Pipelines do it "doesn't create a job" do expect do - post api("/projects/#{project.id}/pipeline", user), ref: project.default_branch + post api("/projects/#{project.id}/pipeline", user), params: { ref: project.default_branch } end.not_to change { project.ci_pipelines.count } expect(response).to have_gitlab_http_status(400) @@ -361,7 +361,7 @@ describe API::Pipelines do end it 'fails when using an invalid ref' do - post api("/projects/#{project.id}/pipeline", user), ref: 'invalid_ref' + post api("/projects/#{project.id}/pipeline", user), params: { ref: 'invalid_ref' } expect(response).to have_gitlab_http_status(400) expect(json_response['message']['base'].first).to eq 'Reference not found' @@ -376,7 +376,7 @@ describe API::Pipelines do end it 'fails to create pipeline' do - post api("/projects/#{project.id}/pipeline", user), ref: project.default_branch + post api("/projects/#{project.id}/pipeline", user), params: { ref: project.default_branch } expect(response).to have_gitlab_http_status(400) expect(json_response['message']['base'].first).to eq 'Missing .gitlab-ci.yml file' @@ -388,7 +388,7 @@ describe API::Pipelines do context 'unauthorized user' do it 'does not create pipeline' do - post api("/projects/#{project.id}/pipeline", non_member), ref: project.default_branch + post api("/projects/#{project.id}/pipeline", non_member), params: { ref: project.default_branch } expect(response).to have_gitlab_http_status(404) expect(json_response['message']).to eq '404 Project Not Found' diff --git a/spec/requests/api/project_export_spec.rb b/spec/requests/api/project_export_spec.rb index 0586025956f..1d2f81a397d 100644 --- a/spec/requests/api/project_export_spec.rb +++ b/spec/requests/api/project_export_spec.rb @@ -294,14 +294,14 @@ describe API::ProjectExport do context 'with upload strategy' do context 'when params invalid' do it_behaves_like '400 response' do - let(:request) { post(api(path, user), 'upload[url]' => 'whatever') } + let(:request) { post(api(path, user), params: { 'upload[url]' => 'whatever' }) } end end it 'starts' do allow_any_instance_of(Gitlab::ImportExport::AfterExportStrategies::WebUploadStrategy).to receive(:send_file) - post(api(path, user), 'upload[url]' => 'http://gitlab.com') + post(api(path, user), params: { 'upload[url]' => 'http://gitlab.com' }) expect(response).to have_gitlab_http_status(202) end @@ -374,7 +374,7 @@ describe API::ProjectExport do params = { description: "Foo" } expect_any_instance_of(Projects::ImportExport::ExportService).to receive(:execute) - post api(path, project.owner), params + post api(path, project.owner), params: params expect(response).to have_gitlab_http_status(202) end diff --git a/spec/requests/api/project_hooks_spec.rb b/spec/requests/api/project_hooks_spec.rb index 87997a48dc9..b88a8b95201 100644 --- a/spec/requests/api/project_hooks_spec.rb +++ b/spec/requests/api/project_hooks_spec.rb @@ -91,8 +91,7 @@ describe API::ProjectHooks, 'ProjectHooks' do it "adds hook to project" do expect do post api("/projects/#{project.id}/hooks", user), - url: "http://example.com", issues_events: true, confidential_issues_events: true, wiki_page_events: true, - job_events: true, push_events_branch_filter: 'some-feature-branch' + params: { url: "http://example.com", issues_events: true, confidential_issues_events: true, wiki_page_events: true, job_events: true, push_events_branch_filter: 'some-feature-branch' } end.to change {project.hooks.count}.by(1) expect(response).to have_gitlab_http_status(201) @@ -116,7 +115,7 @@ describe API::ProjectHooks, 'ProjectHooks' do token = "secret token" expect do - post api("/projects/#{project.id}/hooks", user), url: "http://example.com", token: token + post api("/projects/#{project.id}/hooks", user), params: { url: "http://example.com", token: token } end.to change {project.hooks.count}.by(1) expect(response).to have_gitlab_http_status(201) @@ -135,12 +134,12 @@ describe API::ProjectHooks, 'ProjectHooks' do end it "returns a 422 error if url not valid" do - post api("/projects/#{project.id}/hooks", user), url: "ftp://example.com" + post api("/projects/#{project.id}/hooks", user), params: { url: "ftp://example.com" } expect(response).to have_gitlab_http_status(422) end it "returns a 422 error if branch filter is not valid" do - post api("/projects/#{project.id}/hooks", user), url: "http://example.com", push_events_branch_filter: '~badbranchname/' + post api("/projects/#{project.id}/hooks", user), params: { url: "http://example.com", push_events_branch_filter: '~badbranchname/' } expect(response).to have_gitlab_http_status(422) end end @@ -148,7 +147,7 @@ describe API::ProjectHooks, 'ProjectHooks' do describe "PUT /projects/:id/hooks/:hook_id" do it "updates an existing project hook" do put api("/projects/#{project.id}/hooks/#{hook.id}", user), - url: 'http://example.org', push_events: false, job_events: true + params: { url: 'http://example.org', push_events: false, job_events: true } expect(response).to have_gitlab_http_status(200) expect(json_response['url']).to eq('http://example.org') @@ -168,7 +167,7 @@ describe API::ProjectHooks, 'ProjectHooks' do it "adds the token without including it in the response" do token = "secret token" - put api("/projects/#{project.id}/hooks/#{hook.id}", user), url: "http://example.org", token: token + put api("/projects/#{project.id}/hooks/#{hook.id}", user), params: { url: "http://example.org", token: token } expect(response).to have_gitlab_http_status(200) expect(json_response["url"]).to eq("http://example.org") @@ -179,7 +178,7 @@ describe API::ProjectHooks, 'ProjectHooks' do end it "returns 404 error if hook id not found" do - put api("/projects/#{project.id}/hooks/1234", user), url: 'http://example.org' + put api("/projects/#{project.id}/hooks/1234", user), params: { url: 'http://example.org' } expect(response).to have_gitlab_http_status(404) end @@ -189,7 +188,7 @@ describe API::ProjectHooks, 'ProjectHooks' do end it "returns a 422 error if url is not valid" do - put api("/projects/#{project.id}/hooks/#{hook.id}", user), url: 'ftp://example.com' + put api("/projects/#{project.id}/hooks/#{hook.id}", user), params: { url: 'ftp://example.com' } expect(response).to have_gitlab_http_status(422) end end diff --git a/spec/requests/api/project_import_spec.rb b/spec/requests/api/project_import_spec.rb index 204702b8a5a..594b42bb6c0 100644 --- a/spec/requests/api/project_import_spec.rb +++ b/spec/requests/api/project_import_spec.rb @@ -20,7 +20,7 @@ describe API::ProjectImport do it 'schedules an import using a namespace' do stub_import(namespace) - post api('/projects/import', user), path: 'test-import', file: fixture_file_upload(file), namespace: namespace.id + post api('/projects/import', user), params: { path: 'test-import', file: fixture_file_upload(file), namespace: namespace.id } expect(response).to have_gitlab_http_status(201) end @@ -28,7 +28,7 @@ describe API::ProjectImport do it 'schedules an import using the namespace path' do stub_import(namespace) - post api('/projects/import', user), path: 'test-import', file: fixture_file_upload(file), namespace: namespace.full_path + post api('/projects/import', user), params: { path: 'test-import', file: fixture_file_upload(file), namespace: namespace.full_path } expect(response).to have_gitlab_http_status(201) end @@ -36,7 +36,7 @@ describe API::ProjectImport do it 'schedules an import at the user namespace level' do stub_import(user.namespace) - post api('/projects/import', user), path: 'test-import2', file: fixture_file_upload(file) + post api('/projects/import', user), params: { path: 'test-import2', file: fixture_file_upload(file) } expect(response).to have_gitlab_http_status(201) end @@ -45,7 +45,7 @@ describe API::ProjectImport do expect_any_instance_of(ProjectImportState).not_to receive(:schedule) expect(::Projects::CreateService).not_to receive(:new) - post api('/projects/import', user), namespace: 'nonexistent', path: 'test-import2', file: fixture_file_upload(file) + post api('/projects/import', user), params: { namespace: 'nonexistent', path: 'test-import2', file: fixture_file_upload(file) } expect(response).to have_gitlab_http_status(404) expect(json_response['message']).to eq('404 Namespace Not Found') @@ -55,9 +55,11 @@ describe API::ProjectImport do expect_any_instance_of(ProjectImportState).not_to receive(:schedule) post(api('/projects/import', create(:user)), - path: 'test-import3', - file: fixture_file_upload(file), - namespace: namespace.full_path) + params: { + path: 'test-import3', + file: fixture_file_upload(file), + namespace: namespace.full_path + }) expect(response).to have_gitlab_http_status(404) expect(json_response['message']).to eq('404 Namespace Not Found') @@ -66,7 +68,7 @@ describe API::ProjectImport do it 'does not schedule an import if the user uploads no valid file' do expect_any_instance_of(ProjectImportState).not_to receive(:schedule) - post api('/projects/import', user), path: 'test-import3', file: './random/test' + post api('/projects/import', user), params: { path: 'test-import3', file: './random/test' } expect(response).to have_gitlab_http_status(400) expect(json_response['error']).to eq('file is invalid') @@ -77,10 +79,12 @@ describe API::ProjectImport do override_params = { 'description' => 'Hello world' } post api('/projects/import', user), - path: 'test-import', - file: fixture_file_upload(file), - namespace: namespace.id, - override_params: override_params + params: { + path: 'test-import', + file: fixture_file_upload(file), + namespace: namespace.id, + override_params: override_params + } import_project = Project.find(json_response['id']) expect(import_project.import_data.data['override_params']).to eq(override_params) @@ -91,10 +95,12 @@ describe API::ProjectImport do override_params = { 'not_allowed' => 'Hello world' } post api('/projects/import', user), - path: 'test-import', - file: fixture_file_upload(file), - namespace: namespace.id, - override_params: override_params + params: { + path: 'test-import', + file: fixture_file_upload(file), + namespace: namespace.id, + override_params: override_params + } import_project = Project.find(json_response['id']) expect(import_project.import_data.data['override_params']).to be_empty @@ -105,10 +111,12 @@ describe API::ProjectImport do perform_enqueued_jobs do post api('/projects/import', user), - path: 'test-import', - file: fixture_file_upload(file), - namespace: namespace.id, - override_params: override_params + params: { + path: 'test-import', + file: fixture_file_upload(file), + namespace: namespace.id, + override_params: override_params + } end import_project = Project.find(json_response['id']) @@ -121,7 +129,7 @@ describe API::ProjectImport do it 'does not schedule an import' do expect_any_instance_of(ProjectImportState).not_to receive(:schedule) - post api('/projects/import', user), path: existing_project.path, file: fixture_file_upload(file) + post api('/projects/import', user), params: { path: existing_project.path, file: fixture_file_upload(file) } expect(response).to have_gitlab_http_status(400) expect(json_response['message']).to eq('Name has already been taken') @@ -131,7 +139,7 @@ describe API::ProjectImport do it 'schedules an import' do stub_import(user.namespace) - post api('/projects/import', user), path: existing_project.path, file: fixture_file_upload(file), overwrite: true + post api('/projects/import', user), params: { path: existing_project.path, file: fixture_file_upload(file), overwrite: true } expect(response).to have_gitlab_http_status(201) end diff --git a/spec/requests/api/project_milestones_spec.rb b/spec/requests/api/project_milestones_spec.rb index 62613aa5938..0fa13dd71e2 100644 --- a/spec/requests/api/project_milestones_spec.rb +++ b/spec/requests/api/project_milestones_spec.rb @@ -46,7 +46,7 @@ describe API::ProjectMilestones do expect(Event).to receive(:create!) put api("/projects/#{project.id}/milestones/#{milestone.id}", user), - state_event: 'close' + params: { state_event: 'close' } end end end diff --git a/spec/requests/api/project_snapshots_spec.rb b/spec/requests/api/project_snapshots_spec.rb index 07a920f8d28..44b5ee1f130 100644 --- a/spec/requests/api/project_snapshots_spec.rb +++ b/spec/requests/api/project_snapshots_spec.rb @@ -35,14 +35,14 @@ describe API::ProjectSnapshots do end it 'requests project repository raw archive as administrator' do - get api("/projects/#{project.id}/snapshot", admin), wiki: '0' + get api("/projects/#{project.id}/snapshot", admin), params: { wiki: '0' } expect(response).to have_gitlab_http_status(200) expect_snapshot_response_for(project.repository) end it 'requests wiki repository raw archive as administrator' do - get api("/projects/#{project.id}/snapshot", admin), wiki: '1' + get api("/projects/#{project.id}/snapshot", admin), params: { wiki: '1' } expect(response).to have_gitlab_http_status(200) expect_snapshot_response_for(project.wiki.repository) diff --git a/spec/requests/api/project_snippets_spec.rb b/spec/requests/api/project_snippets_spec.rb index 5dec0bc778c..29f69b6ce20 100644 --- a/spec/requests/api/project_snippets_spec.rb +++ b/spec/requests/api/project_snippets_spec.rb @@ -97,7 +97,7 @@ describe API::ProjectSnippets do end it 'creates a new snippet' do - post api("/projects/#{project.id}/snippets/", admin), params + post api("/projects/#{project.id}/snippets/", admin), params: params expect(response).to have_gitlab_http_status(201) snippet = ProjectSnippet.find(json_response['id']) @@ -111,7 +111,7 @@ describe API::ProjectSnippets do it 'returns 400 for missing parameters' do params.delete(:title) - post api("/projects/#{project.id}/snippets/", admin), params + post api("/projects/#{project.id}/snippets/", admin), params: params expect(response).to have_gitlab_http_status(400) end @@ -119,7 +119,7 @@ describe API::ProjectSnippets do it 'returns 400 for empty code field' do params[:code] = '' - post api("/projects/#{project.id}/snippets/", admin), params + post api("/projects/#{project.id}/snippets/", admin), params: params expect(response).to have_gitlab_http_status(400) end @@ -128,7 +128,7 @@ describe API::ProjectSnippets do def create_snippet(project, snippet_params = {}) project.add_developer(user) - post api("/projects/#{project.id}/snippets", user), params.merge(snippet_params) + post api("/projects/#{project.id}/snippets", user), params: params.merge(snippet_params) end before do @@ -167,7 +167,7 @@ describe API::ProjectSnippets do new_content = 'New content' new_description = 'New description' - put api("/projects/#{snippet.project.id}/snippets/#{snippet.id}/", admin), code: new_content, description: new_description + put api("/projects/#{snippet.project.id}/snippets/#{snippet.id}/", admin), params: { code: new_content, description: new_description } expect(response).to have_gitlab_http_status(200) snippet.reload @@ -176,7 +176,7 @@ describe API::ProjectSnippets do end it 'returns 404 for invalid snippet id' do - put api("/projects/#{snippet.project.id}/snippets/1234", admin), title: 'foo' + put api("/projects/#{snippet.project.id}/snippets/1234", admin), params: { title: 'foo' } expect(response).to have_gitlab_http_status(404) expect(json_response['message']).to eq('404 Snippet Not Found') @@ -191,14 +191,14 @@ describe API::ProjectSnippets do it 'returns 400 for empty code field' do new_content = '' - put api("/projects/#{snippet.project.id}/snippets/#{snippet.id}/", admin), code: new_content + put api("/projects/#{snippet.project.id}/snippets/#{snippet.id}/", admin), params: { code: new_content } expect(response).to have_gitlab_http_status(400) end context 'when the snippet is spam' do def update_snippet(snippet_params = {}) - put api("/projects/#{snippet.project.id}/snippets/#{snippet.id}", admin), snippet_params + put api("/projects/#{snippet.project.id}/snippets/#{snippet.id}", admin), params: snippet_params end before do diff --git a/spec/requests/api/project_templates_spec.rb b/spec/requests/api/project_templates_spec.rb index 86e33f23951..ab5d4de7ff7 100644 --- a/spec/requests/api/project_templates_spec.rb +++ b/spec/requests/api/project_templates_spec.rb @@ -130,8 +130,10 @@ describe API::ProjectTemplates do describe 'GET /projects/:id/templates/licenses/:key' do it 'fills placeholders in the license' do get api("/projects/#{public_project.id}/templates/licenses/agpl-3.0"), - project: 'Project Placeholder', - fullname: 'Fullname Placeholder' + params: { + project: 'Project Placeholder', + fullname: 'Fullname Placeholder' + } expect(response).to have_gitlab_http_status(200) expect(response).to match_response_schema('public_api/v4/license') diff --git a/spec/requests/api/projects_spec.rb b/spec/requests/api/projects_spec.rb index e40db55cd20..ffe4512fa6f 100644 --- a/spec/requests/api/projects_spec.rb +++ b/spec/requests/api/projects_spec.rb @@ -52,7 +52,7 @@ describe API::Projects do describe 'GET /projects' do shared_examples_for 'projects response' do it 'returns an array of projects' do - get api('/projects', current_user), filter + get api('/projects', current_user), params: filter expect(response).to have_gitlab_http_status(200) expect(response).to include_pagination_headers @@ -61,7 +61,7 @@ describe API::Projects do end it 'returns the proper security headers' do - get api('/projects', current_user), filter + get api('/projects', current_user), params: filter expect(response).to include_security_headers end @@ -192,7 +192,7 @@ describe API::Projects do end it "includes statistics if requested" do - get api('/projects', user), statistics: true + get api('/projects', user), params: { statistics: true } expect(response).to have_gitlab_http_status(200) expect(response).to include_pagination_headers @@ -210,7 +210,7 @@ describe API::Projects do end it "does not include license if requested" do - get api('/projects', user), license: true + get api('/projects', user), params: { license: true } expect(response).to have_gitlab_http_status(200) expect(response).to include_pagination_headers @@ -315,7 +315,7 @@ describe API::Projects do context 'and using the visibility filter' do it 'filters based on private visibility param' do - get api('/projects', user), { visibility: 'private' } + get api('/projects', user), params: { visibility: 'private' } expect(response).to have_gitlab_http_status(200) expect(response).to include_pagination_headers @@ -326,7 +326,7 @@ describe API::Projects do it 'filters based on internal visibility param' do project2.update_attribute(:visibility_level, Gitlab::VisibilityLevel::INTERNAL) - get api('/projects', user), { visibility: 'internal' } + get api('/projects', user), params: { visibility: 'internal' } expect(response).to have_gitlab_http_status(200) expect(response).to include_pagination_headers @@ -335,7 +335,7 @@ describe API::Projects do end it 'filters based on public visibility param' do - get api('/projects', user), { visibility: 'public' } + get api('/projects', user), params: { visibility: 'public' } expect(response).to have_gitlab_http_status(200) expect(response).to include_pagination_headers @@ -346,7 +346,7 @@ describe API::Projects do context 'and using sorting' do it 'returns the correct order when sorted by id' do - get api('/projects', user), { order_by: 'id', sort: 'desc' } + get api('/projects', user), params: { order_by: 'id', sort: 'desc' } expect(response).to have_gitlab_http_status(200) expect(response).to include_pagination_headers @@ -357,7 +357,7 @@ describe API::Projects do context 'and with owned=true' do it 'returns an array of projects the user owns' do - get api('/projects', user4), owned: true + get api('/projects', user4), params: { owned: true } expect(response).to have_gitlab_http_status(200) expect(response).to include_pagination_headers @@ -376,7 +376,7 @@ describe API::Projects do end it 'returns the starred projects viewable by the user' do - get api('/projects', user3), starred: true + get api('/projects', user3), params: { starred: true } expect(response).to have_gitlab_http_status(200) expect(response).to include_pagination_headers @@ -398,7 +398,7 @@ describe API::Projects do context 'including owned filter' do it 'returns only projects that satisfy all query parameters' do - get api('/projects', user), { visibility: 'public', owned: true, starred: true, search: 'gitlab' } + get api('/projects', user), params: { visibility: 'public', owned: true, starred: true, search: 'gitlab' } expect(response).to have_gitlab_http_status(200) expect(response).to include_pagination_headers @@ -417,7 +417,7 @@ describe API::Projects do end it 'returns only projects that satisfy all query parameters' do - get api('/projects', user), { visibility: 'public', membership: true, starred: true, search: 'gitlab' } + get api('/projects', user), params: { visibility: 'public', membership: true, starred: true, search: 'gitlab' } expect(response).to have_gitlab_http_status(200) expect(response).to include_pagination_headers @@ -436,7 +436,7 @@ describe API::Projects do end it 'returns an array of groups the user has at least developer access' do - get api('/projects', user2), { min_access_level: 30 } + get api('/projects', user2), params: { min_access_level: 30 } expect(response).to have_gitlab_http_status(200) expect(response).to include_pagination_headers expect(json_response).to be_an Array @@ -479,14 +479,14 @@ describe API::Projects do context 'maximum number of projects reached' do it 'does not create new project and respond with 403' do allow_any_instance_of(User).to receive(:projects_limit_left).and_return(0) - expect { post api('/projects', user2), name: 'foo' } + expect { post api('/projects', user2), params: { name: 'foo' } } .to change {Project.count}.by(0) expect(response).to have_gitlab_http_status(403) end end it 'creates new project without path but with name and returns 201' do - expect { post api('/projects', user), name: 'Foo Project' } + expect { post api('/projects', user), params: { name: 'Foo Project' } } .to change { Project.count }.by(1) expect(response).to have_gitlab_http_status(201) @@ -497,7 +497,7 @@ describe API::Projects do end it 'creates new project without name but with path and returns 201' do - expect { post api('/projects', user), path: 'foo_project' } + expect { post api('/projects', user), params: { path: 'foo_project' } } .to change { Project.count }.by(1) expect(response).to have_gitlab_http_status(201) @@ -508,7 +508,7 @@ describe API::Projects do end it 'creates new project with name and path and returns 201' do - expect { post api('/projects', user), path: 'path-project-Foo', name: 'Foo Project' } + expect { post api('/projects', user), params: { path: 'path-project-Foo', name: 'Foo Project' } } .to change { Project.count }.by(1) expect(response).to have_gitlab_http_status(201) @@ -520,7 +520,7 @@ describe API::Projects do it 'creates last project before reaching project limit' do allow_any_instance_of(User).to receive(:projects_limit_left).and_return(1) - post api('/projects', user2), name: 'foo' + post api('/projects', user2), params: { name: 'foo' } expect(response).to have_gitlab_http_status(201) end @@ -544,7 +544,7 @@ describe API::Projects do merge_method: 'ff' }) - post api('/projects', user), project + post api('/projects', user), params: project expect(response).to have_gitlab_http_status(201) @@ -564,7 +564,7 @@ describe API::Projects do it 'sets a project as public' do project = attributes_for(:project, visibility: 'public') - post api('/projects', user), project + post api('/projects', user), params: project expect(json_response['visibility']).to eq('public') end @@ -572,7 +572,7 @@ describe API::Projects do it 'sets a project as internal' do project = attributes_for(:project, visibility: 'internal') - post api('/projects', user), project + post api('/projects', user), params: project expect(json_response['visibility']).to eq('internal') end @@ -580,7 +580,7 @@ describe API::Projects do it 'sets a project as private' do project = attributes_for(:project, visibility: 'private') - post api('/projects', user), project + post api('/projects', user), params: project expect(json_response['visibility']).to eq('private') end @@ -588,7 +588,7 @@ describe API::Projects do it 'creates a new project initialized with a README.md' do project = attributes_for(:project, initialize_with_readme: 1, name: 'somewhere') - post api('/projects', user), project + post api('/projects', user), params: project expect(json_response['readme_url']).to eql("#{Gitlab.config.gitlab.url}/#{json_response['namespace']['full_path']}/somewhere/blob/master/README.md") end @@ -596,7 +596,7 @@ describe API::Projects do it 'sets tag list to a project' do project = attributes_for(:project, tag_list: %w[tagFirst tagSecond]) - post api('/projects', user), project + post api('/projects', user), params: project expect(json_response['tag_list']).to eq(%w[tagFirst tagSecond]) end @@ -604,7 +604,7 @@ describe API::Projects do it 'uploads avatar for project a project' do project = attributes_for(:project, avatar: fixture_file_upload('spec/fixtures/banana_sample.gif', 'image/gif')) - post api('/projects', user), project + post api('/projects', user), params: project project_id = json_response['id'] expect(json_response['avatar_url']).to eq("http://localhost/uploads/-/system/project/avatar/#{project_id}/banana_sample.gif") @@ -613,7 +613,7 @@ describe API::Projects do it 'sets a project as not allowing outdated diff discussions to automatically resolve' do project = attributes_for(:project, resolve_outdated_diff_discussions: false) - post api('/projects', user), project + post api('/projects', user), params: project expect(json_response['resolve_outdated_diff_discussions']).to be_falsey end @@ -621,7 +621,7 @@ describe API::Projects do it 'sets a project as allowing outdated diff discussions to automatically resolve' do project = attributes_for(:project, resolve_outdated_diff_discussions: true) - post api('/projects', user), project + post api('/projects', user), params: project expect(json_response['resolve_outdated_diff_discussions']).to be_truthy end @@ -629,7 +629,7 @@ describe API::Projects do it 'sets a project as allowing merge even if build fails' do project = attributes_for(:project, only_allow_merge_if_pipeline_succeeds: false) - post api('/projects', user), project + post api('/projects', user), params: project expect(json_response['only_allow_merge_if_pipeline_succeeds']).to be_falsey end @@ -637,7 +637,7 @@ describe API::Projects do it 'sets a project as allowing merge only if merge_when_pipeline_succeeds' do project = attributes_for(:project, only_allow_merge_if_pipeline_succeeds: true) - post api('/projects', user), project + post api('/projects', user), params: project expect(json_response['only_allow_merge_if_pipeline_succeeds']).to be_truthy end @@ -645,7 +645,7 @@ describe API::Projects do it 'sets a project as allowing merge even if discussions are unresolved' do project = attributes_for(:project, only_allow_merge_if_all_discussions_are_resolved: false) - post api('/projects', user), project + post api('/projects', user), params: project expect(json_response['only_allow_merge_if_all_discussions_are_resolved']).to be_falsey end @@ -653,7 +653,7 @@ describe API::Projects do it 'sets a project as allowing merge if only_allow_merge_if_all_discussions_are_resolved is nil' do project = attributes_for(:project, only_allow_merge_if_all_discussions_are_resolved: nil) - post api('/projects', user), project + post api('/projects', user), params: project expect(json_response['only_allow_merge_if_all_discussions_are_resolved']).to be_falsey end @@ -661,7 +661,7 @@ describe API::Projects do it 'sets a project as allowing merge only if all discussions are resolved' do project = attributes_for(:project, only_allow_merge_if_all_discussions_are_resolved: true) - post api('/projects', user), project + post api('/projects', user), params: project expect(json_response['only_allow_merge_if_all_discussions_are_resolved']).to be_truthy end @@ -669,7 +669,7 @@ describe API::Projects do it 'sets the merge method of a project to rebase merge' do project = attributes_for(:project, merge_method: 'rebase_merge') - post api('/projects', user), project + post api('/projects', user), params: project expect(json_response['merge_method']).to eq('rebase_merge') end @@ -677,7 +677,7 @@ describe API::Projects do it 'rejects invalid values for merge_method' do project = attributes_for(:project, merge_method: 'totally_not_valid_method') - post api('/projects', user), project + post api('/projects', user), params: project expect(response).to have_gitlab_http_status(400) end @@ -685,7 +685,7 @@ describe API::Projects do it 'ignores import_url when it is nil' do project = attributes_for(:project, import_url: nil) - post api('/projects', user), project + post api('/projects', user), params: project expect(response).to have_gitlab_http_status(201) end @@ -698,7 +698,7 @@ describe API::Projects do end it 'does not allow a non-admin to use a restricted visibility level' do - post api('/projects', user), project_param + post api('/projects', user), params: project_param expect(response).to have_gitlab_http_status(400) expect(json_response['message']['visibility_level'].first).to( @@ -707,7 +707,7 @@ describe API::Projects do end it 'allows an admin to override restricted visibility settings' do - post api('/projects', admin), project_param + post api('/projects', admin), params: project_param expect(json_response['visibility']).to eq('public') end @@ -739,7 +739,7 @@ describe API::Projects do private_project1.add_developer(user2) private_project2.add_reporter(user2) - get api("/users/#{user4.id}/projects/", user2), { min_access_level: 30 } + get api("/users/#{user4.id}/projects/", user2), params: { min_access_level: 30 } expect(response).to have_gitlab_http_status(200) expect(response).to include_pagination_headers @@ -750,7 +750,7 @@ describe API::Projects do describe 'POST /projects/user/:id' do it 'creates new project without path but with name and return 201' do - expect { post api("/projects/user/#{user.id}", admin), name: 'Foo Project' }.to change { Project.count }.by(1) + expect { post api("/projects/user/#{user.id}", admin), params: { name: 'Foo Project' } }.to change { Project.count }.by(1) expect(response).to have_gitlab_http_status(201) project = Project.last @@ -760,7 +760,7 @@ describe API::Projects do end it 'creates new project with name and path and returns 201' do - expect { post api("/projects/user/#{user.id}", admin), path: 'path-project-Foo', name: 'Foo Project' } + expect { post api("/projects/user/#{user.id}", admin), params: { path: 'path-project-Foo', name: 'Foo Project' } } .to change { Project.count }.by(1) expect(response).to have_gitlab_http_status(201) @@ -787,7 +787,7 @@ describe API::Projects do jobs_enabled: true }) - post api("/projects/user/#{user.id}", admin), project + post api("/projects/user/#{user.id}", admin), params: project expect(response).to have_gitlab_http_status(201) @@ -801,7 +801,7 @@ describe API::Projects do it 'sets a project as public' do project = attributes_for(:project, visibility: 'public') - post api("/projects/user/#{user.id}", admin), project + post api("/projects/user/#{user.id}", admin), params: project expect(response).to have_gitlab_http_status(201) expect(json_response['visibility']).to eq('public') @@ -810,7 +810,7 @@ describe API::Projects do it 'sets a project as internal' do project = attributes_for(:project, visibility: 'internal') - post api("/projects/user/#{user.id}", admin), project + post api("/projects/user/#{user.id}", admin), params: project expect(response).to have_gitlab_http_status(201) expect(json_response['visibility']).to eq('internal') @@ -819,7 +819,7 @@ describe API::Projects do it 'sets a project as private' do project = attributes_for(:project, visibility: 'private') - post api("/projects/user/#{user.id}", admin), project + post api("/projects/user/#{user.id}", admin), params: project expect(json_response['visibility']).to eq('private') end @@ -827,7 +827,7 @@ describe API::Projects do it 'sets a project as not allowing outdated diff discussions to automatically resolve' do project = attributes_for(:project, resolve_outdated_diff_discussions: false) - post api("/projects/user/#{user.id}", admin), project + post api("/projects/user/#{user.id}", admin), params: project expect(json_response['resolve_outdated_diff_discussions']).to be_falsey end @@ -835,27 +835,27 @@ describe API::Projects do it 'sets a project as allowing outdated diff discussions to automatically resolve' do project = attributes_for(:project, resolve_outdated_diff_discussions: true) - post api("/projects/user/#{user.id}", admin), project + post api("/projects/user/#{user.id}", admin), params: project expect(json_response['resolve_outdated_diff_discussions']).to be_truthy end it 'sets a project as allowing merge even if build fails' do project = attributes_for(:project, only_allow_merge_if_pipeline_succeeds: false) - post api("/projects/user/#{user.id}", admin), project + post api("/projects/user/#{user.id}", admin), params: project expect(json_response['only_allow_merge_if_pipeline_succeeds']).to be_falsey end it 'sets a project as allowing merge only if pipeline succeeds' do project = attributes_for(:project, only_allow_merge_if_pipeline_succeeds: true) - post api("/projects/user/#{user.id}", admin), project + post api("/projects/user/#{user.id}", admin), params: project expect(json_response['only_allow_merge_if_pipeline_succeeds']).to be_truthy end it 'sets a project as allowing merge even if discussions are unresolved' do project = attributes_for(:project, only_allow_merge_if_all_discussions_are_resolved: false) - post api("/projects/user/#{user.id}", admin), project + post api("/projects/user/#{user.id}", admin), params: project expect(json_response['only_allow_merge_if_all_discussions_are_resolved']).to be_falsey end @@ -863,7 +863,7 @@ describe API::Projects do it 'sets a project as allowing merge only if all discussions are resolved' do project = attributes_for(:project, only_allow_merge_if_all_discussions_are_resolved: true) - post api("/projects/user/#{user.id}", admin), project + post api("/projects/user/#{user.id}", admin), params: project expect(json_response['only_allow_merge_if_all_discussions_are_resolved']).to be_truthy end @@ -875,7 +875,7 @@ describe API::Projects do end it "uploads the file and returns its info" do - post api("/projects/#{project.id}/uploads", user), file: fixture_file_upload("spec/fixtures/dk.png", "image/png") + post api("/projects/#{project.id}/uploads", user), params: { file: fixture_file_upload("spec/fixtures/dk.png", "image/png") } expect(response).to have_gitlab_http_status(201) expect(json_response['alt']).to eq("dk") @@ -1020,7 +1020,7 @@ describe API::Projects do end it 'includes license fields when requested' do - get api("/projects/#{project.id}", user), license: true + get api("/projects/#{project.id}", user), params: { license: true } expect(response).to have_gitlab_http_status(200) expect(json_response['license']).to eq({ @@ -1040,7 +1040,7 @@ describe API::Projects do end it "includes statistics if requested" do - get api("/projects/#{project.id}", user), statistics: true + get api("/projects/#{project.id}", user), params: { statistics: true } expect(response).to have_gitlab_http_status(200) expect(json_response).to include 'statistics' @@ -1397,7 +1397,7 @@ describe API::Projects do expires_at = 10.days.from_now.to_date expect do - post api("/projects/#{project.id}/share", user), group_id: group.id, group_access: Gitlab::Access::DEVELOPER, expires_at: expires_at + post api("/projects/#{project.id}/share", user), params: { group_id: group.id, group_access: Gitlab::Access::DEVELOPER, expires_at: expires_at } end.to change { ProjectGroupLink.count }.by(1) expect(response).to have_gitlab_http_status(201) @@ -1407,37 +1407,37 @@ describe API::Projects do end it "returns a 400 error when group id is not given" do - post api("/projects/#{project.id}/share", user), group_access: Gitlab::Access::DEVELOPER + post api("/projects/#{project.id}/share", user), params: { group_access: Gitlab::Access::DEVELOPER } expect(response).to have_gitlab_http_status(400) end it "returns a 400 error when access level is not given" do - post api("/projects/#{project.id}/share", user), group_id: group.id + post api("/projects/#{project.id}/share", user), params: { group_id: group.id } expect(response).to have_gitlab_http_status(400) end it "returns a 400 error when sharing is disabled" do project.namespace.update(share_with_group_lock: true) - post api("/projects/#{project.id}/share", user), group_id: group.id, group_access: Gitlab::Access::DEVELOPER + post api("/projects/#{project.id}/share", user), params: { group_id: group.id, group_access: Gitlab::Access::DEVELOPER } expect(response).to have_gitlab_http_status(400) end it 'returns a 404 error when user cannot read group' do private_group = create(:group, :private) - post api("/projects/#{project.id}/share", user), group_id: private_group.id, group_access: Gitlab::Access::DEVELOPER + post api("/projects/#{project.id}/share", user), params: { group_id: private_group.id, group_access: Gitlab::Access::DEVELOPER } expect(response).to have_gitlab_http_status(404) end it 'returns a 404 error when group does not exist' do - post api("/projects/#{project.id}/share", user), group_id: 1234, group_access: Gitlab::Access::DEVELOPER + post api("/projects/#{project.id}/share", user), params: { group_id: 1234, group_access: Gitlab::Access::DEVELOPER } expect(response).to have_gitlab_http_status(404) end it "returns a 400 error when wrong params passed" do - post api("/projects/#{project.id}/share", user), group_id: group.id, group_access: 1234 + post api("/projects/#{project.id}/share", user), params: { group_id: group.id, group_access: 1234 } expect(response).to have_gitlab_http_status(400) expect(json_response['error']).to eq 'group_access does not have a valid value' @@ -1498,7 +1498,7 @@ describe API::Projects do it 'returns 400 when nothing sent' do project_param = {} - put api("/projects/#{project.id}", user), project_param + put api("/projects/#{project.id}", user), params: project_param expect(response).to have_gitlab_http_status(400) expect(json_response['error']).to match('at least one parameter must be provided') @@ -1508,7 +1508,7 @@ describe API::Projects do it 'returns authentication error' do project_param = { name: 'bar' } - put api("/projects/#{project.id}"), project_param + put api("/projects/#{project.id}"), params: project_param expect(response).to have_gitlab_http_status(401) end @@ -1518,7 +1518,7 @@ describe API::Projects do it 'updates name' do project_param = { name: 'bar' } - put api("/projects/#{project.id}", user), project_param + put api("/projects/#{project.id}", user), params: project_param expect(response).to have_gitlab_http_status(200) @@ -1530,7 +1530,7 @@ describe API::Projects do it 'updates visibility_level' do project_param = { visibility: 'public' } - put api("/projects/#{project3.id}", user), project_param + put api("/projects/#{project3.id}", user), params: project_param expect(response).to have_gitlab_http_status(200) @@ -1543,7 +1543,7 @@ describe API::Projects do project3.update({ visibility_level: Gitlab::VisibilityLevel::PUBLIC }) project_param = { visibility: 'private' } - put api("/projects/#{project3.id}", user), project_param + put api("/projects/#{project3.id}", user), params: project_param expect(response).to have_gitlab_http_status(200) @@ -1557,7 +1557,7 @@ describe API::Projects do it 'does not update name to existing name' do project_param = { name: project3.name } - put api("/projects/#{project.id}", user), project_param + put api("/projects/#{project.id}", user), params: project_param expect(response).to have_gitlab_http_status(400) expect(json_response['message']['name']).to eq(['has already been taken']) @@ -1566,7 +1566,7 @@ describe API::Projects do it 'updates request_access_enabled' do project_param = { request_access_enabled: false } - put api("/projects/#{project.id}", user), project_param + put api("/projects/#{project.id}", user), params: project_param expect(response).to have_gitlab_http_status(200) expect(json_response['request_access_enabled']).to eq(false) @@ -1575,7 +1575,7 @@ describe API::Projects do it 'updates path & name to existing path & name in different namespace' do project_param = { path: project4.path, name: project4.name } - put api("/projects/#{project3.id}", user), project_param + put api("/projects/#{project3.id}", user), params: project_param expect(response).to have_gitlab_http_status(200) @@ -1587,7 +1587,7 @@ describe API::Projects do it 'updates jobs_enabled' do project_param = { jobs_enabled: true } - put api("/projects/#{project3.id}", user), project_param + put api("/projects/#{project3.id}", user), params: project_param expect(response).to have_gitlab_http_status(200) @@ -1599,7 +1599,7 @@ describe API::Projects do it 'updates merge_method' do project_param = { merge_method: 'ff' } - put api("/projects/#{project3.id}", user), project_param + put api("/projects/#{project3.id}", user), params: project_param expect(response).to have_gitlab_http_status(200) @@ -1611,7 +1611,7 @@ describe API::Projects do it 'rejects to update merge_method when merge_method is invalid' do project_param = { merge_method: 'invalid' } - put api("/projects/#{project3.id}", user), project_param + put api("/projects/#{project3.id}", user), params: project_param expect(response).to have_gitlab_http_status(400) end @@ -1622,7 +1622,7 @@ describe API::Projects do 'image/gif') } - put api("/projects/#{project3.id}", user), project_param + put api("/projects/#{project3.id}", user), params: project_param expect(response).to have_gitlab_http_status(200) expect(json_response['avatar_url']).to eq('http://localhost/uploads/'\ @@ -1634,7 +1634,7 @@ describe API::Projects do context 'when authenticated as project maintainer' do it 'updates path' do project_param = { path: 'bar' } - put api("/projects/#{project3.id}", user4), project_param + put api("/projects/#{project3.id}", user4), params: project_param expect(response).to have_gitlab_http_status(200) project_param.each_pair do |k, v| expect(json_response[k.to_s]).to eq(v) @@ -1649,7 +1649,7 @@ describe API::Projects do merge_method: 'ff', description: 'new description' } - put api("/projects/#{project3.id}", user4), project_param + put api("/projects/#{project3.id}", user4), params: project_param expect(response).to have_gitlab_http_status(200) project_param.each_pair do |k, v| expect(json_response[k.to_s]).to eq(v) @@ -1658,20 +1658,20 @@ describe API::Projects do it 'does not update path to existing path' do project_param = { path: project.path } - put api("/projects/#{project3.id}", user4), project_param + put api("/projects/#{project3.id}", user4), params: project_param expect(response).to have_gitlab_http_status(400) expect(json_response['message']['path']).to eq(['has already been taken']) end it 'does not update name' do project_param = { name: 'bar' } - put api("/projects/#{project3.id}", user4), project_param + put api("/projects/#{project3.id}", user4), params: project_param expect(response).to have_gitlab_http_status(403) end it 'does not update visibility_level' do project_param = { visibility: 'public' } - put api("/projects/#{project3.id}", user4), project_param + put api("/projects/#{project3.id}", user4), params: project_param expect(response).to have_gitlab_http_status(403) end end @@ -1685,7 +1685,7 @@ describe API::Projects do merge_requests_enabled: true, description: 'new description', request_access_enabled: true } - put api("/projects/#{project.id}", user3), project_param + put api("/projects/#{project.id}", user3), params: project_param expect(response).to have_gitlab_http_status(403) end end @@ -1971,41 +1971,41 @@ describe API::Projects do end it 'forks with explicit own user namespace id' do - post api("/projects/#{project.id}/fork", user2), namespace: user2.namespace.id + post api("/projects/#{project.id}/fork", user2), params: { namespace: user2.namespace.id } expect(response).to have_gitlab_http_status(201) expect(json_response['owner']['id']).to eq(user2.id) end it 'forks with explicit own user name as namespace' do - post api("/projects/#{project.id}/fork", user2), namespace: user2.username + post api("/projects/#{project.id}/fork", user2), params: { namespace: user2.username } expect(response).to have_gitlab_http_status(201) expect(json_response['owner']['id']).to eq(user2.id) end it 'forks to another user when admin' do - post api("/projects/#{project.id}/fork", admin), namespace: user2.username + post api("/projects/#{project.id}/fork", admin), params: { namespace: user2.username } expect(response).to have_gitlab_http_status(201) expect(json_response['owner']['id']).to eq(user2.id) end it 'fails if trying to fork to another user when not admin' do - post api("/projects/#{project.id}/fork", user2), namespace: admin.namespace.id + post api("/projects/#{project.id}/fork", user2), params: { namespace: admin.namespace.id } expect(response).to have_gitlab_http_status(404) end it 'fails if trying to fork to non-existent namespace' do - post api("/projects/#{project.id}/fork", user2), namespace: 42424242 + post api("/projects/#{project.id}/fork", user2), params: { namespace: 42424242 } expect(response).to have_gitlab_http_status(404) expect(json_response['message']).to eq('404 Target Namespace Not Found') end it 'forks to owned group' do - post api("/projects/#{project.id}/fork", user2), namespace: group2.name + post api("/projects/#{project.id}/fork", user2), params: { namespace: group2.name } expect(response).to have_gitlab_http_status(201) expect(json_response['namespace']['name']).to eq(group2.name) @@ -2013,7 +2013,7 @@ describe API::Projects do it 'forks to owned subgroup' do full_path = "#{group2.path}/#{group3.path}" - post api("/projects/#{project.id}/fork", user2), namespace: full_path + post api("/projects/#{project.id}/fork", user2), params: { namespace: full_path } expect(response).to have_gitlab_http_status(201) expect(json_response['namespace']['name']).to eq(group3.name) @@ -2021,13 +2021,13 @@ describe API::Projects do end it 'fails to fork to not owned group' do - post api("/projects/#{project.id}/fork", user2), namespace: group.name + post api("/projects/#{project.id}/fork", user2), params: { namespace: group.name } expect(response).to have_gitlab_http_status(404) end it 'forks to not owned group when admin' do - post api("/projects/#{project.id}/fork", admin), namespace: group.name + post api("/projects/#{project.id}/fork", admin), params: { namespace: group.name } expect(response).to have_gitlab_http_status(201) expect(json_response['namespace']['name']).to eq(group.name) @@ -2100,19 +2100,19 @@ describe API::Projects do it 'transfers the project to the new namespace' do group.add_owner(user) - put api("/projects/#{project.id}/transfer", user), namespace: group.id + put api("/projects/#{project.id}/transfer", user), params: { namespace: group.id } expect(response).to have_gitlab_http_status(200) end it 'fails when transferring to a non owned namespace' do - put api("/projects/#{project.id}/transfer", user), namespace: group.id + put api("/projects/#{project.id}/transfer", user), params: { namespace: group.id } expect(response).to have_gitlab_http_status(404) end it 'fails when transferring to an unknown namespace' do - put api("/projects/#{project.id}/transfer", user), namespace: 'unknown' + put api("/projects/#{project.id}/transfer", user), params: { namespace: 'unknown' } expect(response).to have_gitlab_http_status(404) end diff --git a/spec/requests/api/protected_branches_spec.rb b/spec/requests/api/protected_branches_spec.rb index 69a601d7b40..f90558d77a9 100644 --- a/spec/requests/api/protected_branches_spec.rb +++ b/spec/requests/api/protected_branches_spec.rb @@ -14,7 +14,7 @@ describe API::ProtectedBranches do shared_examples_for 'protected branches' do it 'returns the protected branches' do - get api(route, user), per_page: 100 + get api(route, user), params: { per_page: 100 } expect(response).to have_gitlab_http_status(200) expect(response).to include_pagination_headers @@ -114,7 +114,7 @@ describe API::ProtectedBranches do end it 'protects a single branch' do - post post_endpoint, name: branch_name + post post_endpoint, params: { name: branch_name } expect(response).to have_gitlab_http_status(201) expect(json_response['name']).to eq(branch_name) @@ -123,7 +123,7 @@ describe API::ProtectedBranches do end it 'protects a single branch and developers can push' do - post post_endpoint, name: branch_name, push_access_level: 30 + post post_endpoint, params: { name: branch_name, push_access_level: 30 } expect(response).to have_gitlab_http_status(201) expect(json_response['name']).to eq(branch_name) @@ -132,7 +132,7 @@ describe API::ProtectedBranches do end it 'protects a single branch and developers can merge' do - post post_endpoint, name: branch_name, merge_access_level: 30 + post post_endpoint, params: { name: branch_name, merge_access_level: 30 } expect(response).to have_gitlab_http_status(201) expect(json_response['name']).to eq(branch_name) @@ -141,7 +141,7 @@ describe API::ProtectedBranches do end it 'protects a single branch and developers can push and merge' do - post post_endpoint, name: branch_name, push_access_level: 30, merge_access_level: 30 + post post_endpoint, params: { name: branch_name, push_access_level: 30, merge_access_level: 30 } expect(response).to have_gitlab_http_status(201) expect(json_response['name']).to eq(branch_name) @@ -150,7 +150,7 @@ describe API::ProtectedBranches do end it 'protects a single branch and no one can push' do - post post_endpoint, name: branch_name, push_access_level: 0 + post post_endpoint, params: { name: branch_name, push_access_level: 0 } expect(response).to have_gitlab_http_status(201) expect(json_response['name']).to eq(branch_name) @@ -159,7 +159,7 @@ describe API::ProtectedBranches do end it 'protects a single branch and no one can merge' do - post post_endpoint, name: branch_name, merge_access_level: 0 + post post_endpoint, params: { name: branch_name, merge_access_level: 0 } expect(response).to have_gitlab_http_status(201) expect(json_response['name']).to eq(branch_name) @@ -168,7 +168,7 @@ describe API::ProtectedBranches do end it 'protects a single branch and no one can push or merge' do - post post_endpoint, name: branch_name, push_access_level: 0, merge_access_level: 0 + post post_endpoint, params: { name: branch_name, push_access_level: 0, merge_access_level: 0 } expect(response).to have_gitlab_http_status(201) expect(json_response['name']).to eq(branch_name) @@ -177,7 +177,7 @@ describe API::ProtectedBranches do end it 'returns a 409 error if the same branch is protected twice' do - post post_endpoint, name: protected_name + post post_endpoint, params: { name: protected_name } expect(response).to have_gitlab_http_status(409) end @@ -186,7 +186,7 @@ describe API::ProtectedBranches do let(:branch_name) { 'feature/*' } it "protects multiple branches with a wildcard in the name" do - post post_endpoint, name: branch_name + post post_endpoint, params: { name: branch_name } expect_protection_to_be_successful expect(json_response['push_access_levels'][0]['access_level']).to eq(Gitlab::Access::MAINTAINER) @@ -201,7 +201,7 @@ describe API::ProtectedBranches do end it "prevents deletion of the protected branch rule" do - post post_endpoint, name: branch_name + post post_endpoint, params: { name: branch_name } expect(response).to have_gitlab_http_status(403) end @@ -214,7 +214,7 @@ describe API::ProtectedBranches do end it "returns a 403 error if guest" do - post post_endpoint, name: branch_name + post post_endpoint, params: { name: branch_name } expect(response).to have_gitlab_http_status(403) end diff --git a/spec/requests/api/protected_tags_spec.rb b/spec/requests/api/protected_tags_spec.rb index f4f3ef31bc3..41363dcc1c3 100644 --- a/spec/requests/api/protected_tags_spec.rb +++ b/spec/requests/api/protected_tags_spec.rb @@ -15,7 +15,7 @@ describe API::ProtectedTags do shared_examples_for 'protected tags' do it 'returns the protected tags' do - get api(route, user), per_page: 100 + get api(route, user), params: { per_page: 100 } expect(response).to have_gitlab_http_status(200) expect(response).to include_pagination_headers @@ -102,7 +102,7 @@ describe API::ProtectedTags do end it 'protects a single tag with maintainers can create tags' do - post api("/projects/#{project.id}/protected_tags", user), name: tag_name + post api("/projects/#{project.id}/protected_tags", user), params: { name: tag_name } expect(response).to have_gitlab_http_status(201) expect(json_response['name']).to eq(tag_name) @@ -111,7 +111,7 @@ describe API::ProtectedTags do it 'protects a single tag with developers can create tags' do post api("/projects/#{project.id}/protected_tags", user), - name: tag_name, create_access_level: 30 + params: { name: tag_name, create_access_level: 30 } expect(response).to have_gitlab_http_status(201) expect(json_response['name']).to eq(tag_name) @@ -120,7 +120,7 @@ describe API::ProtectedTags do it 'protects a single tag with no one can create tags' do post api("/projects/#{project.id}/protected_tags", user), - name: tag_name, create_access_level: 0 + params: { name: tag_name, create_access_level: 0 } expect(response).to have_gitlab_http_status(201) expect(json_response['name']).to eq(tag_name) @@ -128,15 +128,15 @@ describe API::ProtectedTags do end it 'returns a 422 error if the same tag is protected twice' do - post api("/projects/#{project.id}/protected_tags", user), name: protected_name + post api("/projects/#{project.id}/protected_tags", user), params: { name: protected_name } expect(response).to have_gitlab_http_status(422) expect(json_response['message'][0]).to eq('Name has already been taken') end it 'returns 201 if the same tag is proteted on different projects' do - post api("/projects/#{project.id}/protected_tags", user), name: protected_name - post api("/projects/#{project2.id}/protected_tags", user), name: protected_name + post api("/projects/#{project.id}/protected_tags", user), params: { name: protected_name } + post api("/projects/#{project2.id}/protected_tags", user), params: { name: protected_name } expect(response).to have_gitlab_http_status(201) expect(json_response['name']).to eq(protected_name) @@ -146,7 +146,7 @@ describe API::ProtectedTags do let(:tag_name) { 'feature/*' } it 'protects multiple tags with a wildcard in the name' do - post api("/projects/#{project.id}/protected_tags", user), name: tag_name + post api("/projects/#{project.id}/protected_tags", user), params: { name: tag_name } expect(response).to have_gitlab_http_status(201) expect(json_response['name']).to eq(tag_name) @@ -161,7 +161,7 @@ describe API::ProtectedTags do end it 'returns a 403 error if guest' do - post api("/projects/#{project.id}/protected_tags/", user), name: tag_name + post api("/projects/#{project.id}/protected_tags/", user), params: { name: tag_name } expect(response).to have_gitlab_http_status(403) end diff --git a/spec/requests/api/redacted_events_spec.rb b/spec/requests/api/redacted_events_spec.rb deleted file mode 100644 index 086dd3df9ba..00000000000 --- a/spec/requests/api/redacted_events_spec.rb +++ /dev/null @@ -1,68 +0,0 @@ -require 'spec_helper' - -describe 'Redacted events in API::Events' do - shared_examples 'private events are redacted' do - it 'redacts events the user does not have access to' do - expect_any_instance_of(Event).to receive(:visible_to_user?).and_call_original - - get api(path), user - - expect(response).to have_gitlab_http_status(200) - expect(json_response).to contain_exactly( - 'project_id' => nil, - 'action_name' => nil, - 'target_id' => nil, - 'target_iid' => nil, - 'target_type' => nil, - 'author_id' => nil, - 'target_title' => 'Confidential event', - 'created_at' => nil, - 'author_username' => nil - ) - end - end - - describe '/users/:id/events' do - let(:project) { create(:project, :public) } - let(:path) { "/users/#{project.owner.id}/events" } - let(:issue) { create(:issue, :confidential, project: project) } - - before do - EventCreateService.new.open_issue(issue, issue.author) - end - - context 'unauthenticated user views another user with private events' do - let(:user) { nil } - - include_examples 'private events are redacted' - end - - context 'authenticated user without access views another user with private events' do - let(:user) { create(:user) } - - include_examples 'private events are redacted' - end - end - - describe '/projects/:id/events' do - let(:project) { create(:project, :public) } - let(:path) { "/projects/#{project.id}/events" } - let(:issue) { create(:issue, :confidential, project: project) } - - before do - EventCreateService.new.open_issue(issue, issue.author) - end - - context 'unauthenticated user views public project' do - let(:user) { nil } - - include_examples 'private events are redacted' - end - - context 'authenticated user without access views public project' do - let(:user) { create(:user) } - - include_examples 'private events are redacted' - end - end -end diff --git a/spec/requests/api/releases_spec.rb b/spec/requests/api/releases_spec.rb new file mode 100644 index 00000000000..a44e37f9cd5 --- /dev/null +++ b/spec/requests/api/releases_spec.rb @@ -0,0 +1,532 @@ +require 'spec_helper' + +describe API::Releases do + let(:project) { create(:project, :repository, :private) } + let(:maintainer) { create(:user) } + let(:reporter) { create(:user) } + let(:non_project_member) { create(:user) } + let(:commit) { create(:commit, project: project) } + + before do + project.add_maintainer(maintainer) + project.add_reporter(reporter) + + project.repository.add_tag(maintainer, 'v0.1', commit.id) + project.repository.add_tag(maintainer, 'v0.2', commit.id) + end + + describe 'GET /projects/:id/releases' do + context 'when there are two releases' do + let!(:release_1) do + create(:release, + project: project, + tag: 'v0.1', + author: maintainer, + created_at: 2.days.ago) + end + + let!(:release_2) do + create(:release, + project: project, + tag: 'v0.2', + author: maintainer, + created_at: 1.day.ago) + end + + it 'returns 200 HTTP status' do + get api("/projects/#{project.id}/releases", maintainer) + + expect(response).to have_gitlab_http_status(:ok) + end + + it 'returns releases ordered by created_at' do + get api("/projects/#{project.id}/releases", maintainer) + + expect(json_response.count).to eq(2) + expect(json_response.first['tag_name']).to eq(release_2.tag) + expect(json_response.second['tag_name']).to eq(release_1.tag) + end + + it 'matches response schema' do + get api("/projects/#{project.id}/releases", maintainer) + + expect(response).to match_response_schema('releases') + end + end + + context 'when tag does not exist in git repository' do + let!(:release) { create(:release, project: project, tag: 'v1.1.5') } + + it 'returns the tag' do + get api("/projects/#{project.id}/releases", maintainer) + + expect(json_response.count).to eq(1) + expect(json_response.first['tag_name']).to eq('v1.1.5') + expect(release).to be_tag_missing + end + end + + context 'when user is not a project member' do + it 'cannot find the project' do + get api("/projects/#{project.id}/releases", non_project_member) + + expect(response).to have_gitlab_http_status(:not_found) + end + + context 'when project is public' do + let(:project) { create(:project, :repository, :public) } + + it 'allows the request' do + get api("/projects/#{project.id}/releases", non_project_member) + + expect(response).to have_gitlab_http_status(:ok) + end + end + end + + context 'when feature flag is disabled' do + before do + stub_feature_flags(releases_page: false) + end + + it 'cannot find the API' do + get api("/projects/#{project.id}/releases", maintainer) + + expect(response).to have_gitlab_http_status(:not_found) + end + end + end + + describe 'GET /projects/:id/releases/:tag_name' do + context 'when there is a release' do + let!(:release) do + create(:release, + project: project, + tag: 'v0.1', + sha: commit.id, + author: maintainer, + description: 'This is v0.1') + end + + it 'returns 200 HTTP status' do + get api("/projects/#{project.id}/releases/v0.1", maintainer) + + expect(response).to have_gitlab_http_status(:ok) + end + + it 'returns a release entry' do + get api("/projects/#{project.id}/releases/v0.1", maintainer) + + expect(json_response['tag_name']).to eq(release.tag) + expect(json_response['description']).to eq('This is v0.1') + expect(json_response['author']['name']).to eq(maintainer.name) + expect(json_response['commit']['id']).to eq(commit.id) + end + + it 'matches response schema' do + get api("/projects/#{project.id}/releases/v0.1", maintainer) + + expect(response).to match_response_schema('release') + end + end + + context 'when specified tag is not found in the project' do + it 'cannot find the release entry' do + get api("/projects/#{project.id}/releases/non_exist_tag", maintainer) + + expect(response).to have_gitlab_http_status(:forbidden) + end + end + + context 'when user is not a project member' do + let!(:release) { create(:release, tag: 'v0.1', project: project) } + + it 'cannot find the project' do + get api("/projects/#{project.id}/releases/v0.1", non_project_member) + + expect(response).to have_gitlab_http_status(:not_found) + end + + context 'when project is public' do + let(:project) { create(:project, :repository, :public) } + + it 'allows the request' do + get api("/projects/#{project.id}/releases/v0.1", non_project_member) + + expect(response).to have_gitlab_http_status(:ok) + end + end + end + + context 'when feature flag is disabled' do + before do + stub_feature_flags(releases_page: false) + end + + it 'cannot find the API' do + get api("/projects/#{project.id}/releases/v0.1", maintainer) + + expect(response).to have_gitlab_http_status(:not_found) + end + end + end + + describe 'POST /projects/:id/releases' do + let(:params) do + { + name: 'New release', + tag_name: 'v0.1', + description: 'Super nice release' + } + end + + it 'accepts the request' do + post api("/projects/#{project.id}/releases", maintainer), params: params + + expect(response).to have_gitlab_http_status(:created) + end + + it 'creates a new release' do + expect do + post api("/projects/#{project.id}/releases", maintainer), params: params + end.to change { Release.count }.by(1) + + expect(project.releases.last.name).to eq('New release') + expect(project.releases.last.tag).to eq('v0.1') + expect(project.releases.last.description).to eq('Super nice release') + end + + context 'when description is empty' do + let(:params) do + { + name: 'New release', + tag_name: 'v0.1', + description: '' + } + end + + it 'returns an error as validation failure' do + expect do + post api("/projects/#{project.id}/releases", maintainer), params: params + end.not_to change { Release.count } + + expect(response).to have_gitlab_http_status(:bad_request) + expect(json_response['message']) + .to eq("Validation failed: Description can't be blank") + end + end + + it 'matches response schema' do + post api("/projects/#{project.id}/releases", maintainer), params: params + + expect(response).to match_response_schema('release') + end + + it 'does not create a new tag' do + expect do + post api("/projects/#{project.id}/releases", maintainer), params: params + end.not_to change { Project.find_by_id(project.id).repository.tag_count } + end + + context 'when user is a reporter' do + it 'forbids the request' do + post api("/projects/#{project.id}/releases", reporter), params: params + + expect(response).to have_gitlab_http_status(:forbidden) + end + end + + context 'when user is not a project member' do + it 'forbids the request' do + post api("/projects/#{project.id}/releases", non_project_member), + params: params + + expect(response).to have_gitlab_http_status(:not_found) + end + + context 'when project is public' do + let(:project) { create(:project, :repository, :public) } + + it 'forbids the request' do + post api("/projects/#{project.id}/releases", non_project_member), + params: params + + expect(response).to have_gitlab_http_status(:forbidden) + end + end + end + + context 'when tag does not exist in git repository' do + let(:params) do + { + name: 'Android ~ Ice Cream Sandwich ~', + tag_name: tag_name, + description: 'Android 4.0–4.0.4 "Ice Cream Sandwich" is the ninth' \ + 'version of the Android mobile operating system developed' \ + 'by Google.', + ref: 'master' + } + end + + let(:tag_name) { 'v4.0' } + + it 'creates a new tag' do + expect do + post api("/projects/#{project.id}/releases", maintainer), params: params + end.to change { Project.find_by_id(project.id).repository.tag_count }.by(1) + + expect(project.repository.find_tag('v4.0').dereferenced_target.id) + .to eq(project.repository.commit('master').id) + end + + it 'creates a new release' do + expect do + post api("/projects/#{project.id}/releases", maintainer), params: params + end.to change { Release.count }.by(1) + + expect(project.releases.last.name).to eq('Android ~ Ice Cream Sandwich ~') + expect(project.releases.last.tag).to eq('v4.0') + expect(project.releases.last.description).to eq( + 'Android 4.0–4.0.4 "Ice Cream Sandwich" is the ninth' \ + 'version of the Android mobile operating system developed' \ + 'by Google.') + end + + context 'when tag name is HEAD' do + let(:tag_name) { 'HEAD' } + + it 'returns an error as failure on tag creation' do + post api("/projects/#{project.id}/releases", maintainer), params: params + + expect(response).to have_gitlab_http_status(:internal_server_error) + expect(json_response['message']).to eq('Tag name invalid') + end + end + + context 'when tag name is empty' do + let(:tag_name) { '' } + + it 'returns an error as failure on tag creation' do + post api("/projects/#{project.id}/releases", maintainer), params: params + + expect(response).to have_gitlab_http_status(:internal_server_error) + expect(json_response['message']).to eq('Tag name invalid') + end + end + end + + context 'when release already exists' do + before do + create(:release, project: project, tag: 'v0.1', name: 'New release') + end + + it 'returns an error as conflicted request' do + post api("/projects/#{project.id}/releases", maintainer), params: params + + expect(response).to have_gitlab_http_status(:conflict) + end + end + + context 'when feature flag is disabled' do + before do + stub_feature_flags(releases_page: false) + end + + it 'cannot find the API' do + post api("/projects/#{project.id}/releases", maintainer), params: params + + expect(response).to have_gitlab_http_status(:not_found) + end + end + end + + describe 'PUT /projects/:id/releases/:tag_name' do + let(:params) { { description: 'Best release ever!' } } + + let!(:release) do + create(:release, + project: project, + tag: 'v0.1', + name: 'New release', + description: 'Super nice release') + end + + it 'accepts the request' do + put api("/projects/#{project.id}/releases/v0.1", maintainer), params: params + + expect(response).to have_gitlab_http_status(:ok) + end + + it 'updates the description' do + put api("/projects/#{project.id}/releases/v0.1", maintainer), params: params + + expect(project.releases.last.description).to eq('Best release ever!') + end + + it 'does not change other attributes' do + put api("/projects/#{project.id}/releases/v0.1", maintainer), params: params + + expect(project.releases.last.tag).to eq('v0.1') + expect(project.releases.last.name).to eq('New release') + end + + it 'matches response schema' do + put api("/projects/#{project.id}/releases/v0.1", maintainer), params: params + + expect(response).to match_response_schema('release') + end + + context 'when user tries to update sha' do + let(:params) { { sha: 'xxx' } } + + it 'does not allow the request' do + put api("/projects/#{project.id}/releases/v0.1", maintainer), params: params + + expect(response).to have_gitlab_http_status(:bad_request) + end + end + + context 'when params is empty' do + let(:params) { {} } + + it 'does not allow the request' do + put api("/projects/#{project.id}/releases/v0.1", maintainer), params: params + + expect(response).to have_gitlab_http_status(:bad_request) + end + end + + context 'when there are no corresponding releases' do + let!(:release) { } + + it 'forbids the request' do + put api("/projects/#{project.id}/releases/v0.1", maintainer), params: params + + expect(response).to have_gitlab_http_status(:forbidden) + end + end + + context 'when user is a reporter' do + it 'forbids the request' do + put api("/projects/#{project.id}/releases/v0.1", reporter), params: params + + expect(response).to have_gitlab_http_status(:forbidden) + end + end + + context 'when user is not a project member' do + it 'forbids the request' do + put api("/projects/#{project.id}/releases/v0.1", non_project_member), + params: params + + expect(response).to have_gitlab_http_status(:not_found) + end + + context 'when project is public' do + let(:project) { create(:project, :repository, :public) } + + it 'forbids the request' do + put api("/projects/#{project.id}/releases/v0.1", non_project_member), + params: params + + expect(response).to have_gitlab_http_status(:forbidden) + end + end + end + + context 'when feature flag is disabled' do + before do + stub_feature_flags(releases_page: false) + end + + it 'cannot find the API' do + put api("/projects/#{project.id}/releases/v0.1", non_project_member), + params: params + + expect(response).to have_gitlab_http_status(:not_found) + end + end + end + + describe 'DELETE /projects/:id/releases/:tag_name' do + let!(:release) do + create(:release, + project: project, + tag: 'v0.1', + name: 'New release', + description: 'Super nice release') + end + + it 'accepts the request' do + delete api("/projects/#{project.id}/releases/v0.1", maintainer) + + expect(response).to have_gitlab_http_status(:ok) + end + + it 'destroys the release' do + expect do + delete api("/projects/#{project.id}/releases/v0.1", maintainer) + end.to change { Release.count }.by(-1) + end + + it 'does not remove a tag in repository' do + expect do + delete api("/projects/#{project.id}/releases/v0.1", maintainer) + end.not_to change { Project.find_by_id(project.id).repository.tag_count } + end + + it 'matches response schema' do + delete api("/projects/#{project.id}/releases/v0.1", maintainer) + + expect(response).to match_response_schema('release') + end + + context 'when there are no corresponding releases' do + let!(:release) { } + + it 'forbids the request' do + delete api("/projects/#{project.id}/releases/v0.1", maintainer) + + expect(response).to have_gitlab_http_status(:forbidden) + end + end + + context 'when user is a reporter' do + it 'forbids the request' do + delete api("/projects/#{project.id}/releases/v0.1", reporter) + + expect(response).to have_gitlab_http_status(:forbidden) + end + end + + context 'when user is not a project member' do + it 'forbids the request' do + delete api("/projects/#{project.id}/releases/v0.1", non_project_member) + + expect(response).to have_gitlab_http_status(:not_found) + end + + context 'when project is public' do + let(:project) { create(:project, :repository, :public) } + + it 'forbids the request' do + delete api("/projects/#{project.id}/releases/v0.1", non_project_member) + + expect(response).to have_gitlab_http_status(:forbidden) + end + end + end + + context 'when feature flag is disabled' do + before do + stub_feature_flags(releases_page: false) + end + + it 'cannot find the API' do + delete api("/projects/#{project.id}/releases/v0.1", non_project_member) + + expect(response).to have_gitlab_http_status(:not_found) + end + end + end +end diff --git a/spec/requests/api/repositories_spec.rb b/spec/requests/api/repositories_spec.rb index de141377793..b6b57803a6a 100644 --- a/spec/requests/api/repositories_spec.rb +++ b/spec/requests/api/repositories_spec.rb @@ -171,7 +171,7 @@ describe API::Repositories do it 'forces attachment content disposition' do get api(route, current_user) - expect(headers['Content-Disposition']).to match(/^attachment/) + expect(headers['Content-Disposition']).to eq 'attachment' end context 'when sha does not exist' do @@ -297,7 +297,7 @@ describe API::Repositories do expect(::Gitlab::Git::Compare).to receive(:new).with(anything, anything, anything, { straight: false }).and_call_original - get api(route, current_user), from: 'master', to: 'feature' + get api(route, current_user), params: { from: 'master', to: 'feature' } expect(response).to have_gitlab_http_status(200) expect(json_response['commits']).to be_present @@ -308,7 +308,7 @@ describe API::Repositories do expect(::Gitlab::Git::Compare).to receive(:new).with(anything, anything, anything, { straight: false }).and_call_original - get api(route, current_user), from: 'master', to: 'feature', straight: false + get api(route, current_user), params: { from: 'master', to: 'feature', straight: false } expect(response).to have_gitlab_http_status(200) expect(json_response['commits']).to be_present @@ -319,7 +319,7 @@ describe API::Repositories do expect(::Gitlab::Git::Compare).to receive(:new).with(anything, anything, anything, { straight: true }).and_call_original - get api(route, current_user), from: 'master', to: 'feature', straight: true + get api(route, current_user), params: { from: 'master', to: 'feature', straight: true } expect(response).to have_gitlab_http_status(200) expect(json_response['commits']).to be_present @@ -327,7 +327,7 @@ describe API::Repositories do end it "compares tags" do - get api(route, current_user), from: 'v1.0.0', to: 'v1.1.0' + get api(route, current_user), params: { from: 'v1.0.0', to: 'v1.1.0' } expect(response).to have_gitlab_http_status(200) expect(json_response['commits']).to be_present @@ -335,7 +335,7 @@ describe API::Repositories do end it "compares commits" do - get api(route, current_user), from: sample_commit.id, to: sample_commit.parent_id + get api(route, current_user), params: { from: sample_commit.id, to: sample_commit.parent_id } expect(response).to have_gitlab_http_status(200) expect(json_response['commits']).to be_empty @@ -344,7 +344,7 @@ describe API::Repositories do end it "compares commits in reverse order" do - get api(route, current_user), from: sample_commit.parent_id, to: sample_commit.id + get api(route, current_user), params: { from: sample_commit.parent_id, to: sample_commit.id } expect(response).to have_gitlab_http_status(200) expect(json_response['commits']).to be_present @@ -352,7 +352,7 @@ describe API::Repositories do end it "compares same refs" do - get api(route, current_user), from: 'master', to: 'master' + get api(route, current_user), params: { from: 'master', to: 'master' } expect(response).to have_gitlab_http_status(200) expect(json_response['commits']).to be_empty @@ -410,7 +410,7 @@ describe API::Repositories do context 'using sorting' do context 'by commits desc' do it 'returns the repository contribuors sorted by commits desc' do - get api(route, current_user), { order_by: 'commits', sort: 'desc' } + get api(route, current_user), params: { order_by: 'commits', sort: 'desc' } expect(response).to have_gitlab_http_status(200) expect(response).to match_response_schema('contributors') @@ -420,7 +420,7 @@ describe API::Repositories do context 'by name desc' do it 'returns the repository contribuors sorted by name asc case insensitive' do - get api(route, current_user), { order_by: 'name', sort: 'asc' } + get api(route, current_user), params: { order_by: 'name', sort: 'asc' } expect(response).to have_gitlab_http_status(200) expect(response).to match_response_schema('contributors') @@ -478,7 +478,7 @@ describe API::Repositories do end subject(:request) do - get(api("/projects/#{project.id}/repository/merge_base", current_user), refs: refs) + get(api("/projects/#{project.id}/repository/merge_base", current_user), params: { refs: refs }) end shared_examples 'merge base' do diff --git a/spec/requests/api/runner_spec.rb b/spec/requests/api/runner_spec.rb index b36087b86a7..2f322cc7054 100644 --- a/spec/requests/api/runner_spec.rb +++ b/spec/requests/api/runner_spec.rb @@ -25,7 +25,7 @@ describe API::Runner, :clean_gitlab_redis_shared_state do context 'when invalid token is provided' do it 'returns 403 error' do - post api('/runners'), token: 'invalid' + post api('/runners'), params: { token: 'invalid' } expect(response).to have_gitlab_http_status 403 end @@ -33,7 +33,7 @@ describe API::Runner, :clean_gitlab_redis_shared_state do context 'when valid token is provided' do it 'creates runner with default values' do - post api('/runners'), token: registration_token + post api('/runners'), params: { token: registration_token } runner = Ci::Runner.first @@ -50,7 +50,7 @@ describe API::Runner, :clean_gitlab_redis_shared_state do let(:project) { create(:project) } it 'creates project runner' do - post api('/runners'), token: project.runners_token + post api('/runners'), params: { token: project.runners_token } expect(response).to have_gitlab_http_status 201 expect(project.runners.size).to eq(1) @@ -65,7 +65,7 @@ describe API::Runner, :clean_gitlab_redis_shared_state do let(:group) { create(:group) } it 'creates a group runner' do - post api('/runners'), token: group.runners_token + post api('/runners'), params: { token: group.runners_token } expect(response).to have_http_status 201 expect(group.runners.size).to eq(1) @@ -79,8 +79,10 @@ describe API::Runner, :clean_gitlab_redis_shared_state do context 'when runner description is provided' do it 'creates runner' do - post api('/runners'), token: registration_token, - description: 'server.hostname' + post api('/runners'), params: { + token: registration_token, + description: 'server.hostname' + } expect(response).to have_gitlab_http_status 201 expect(Ci::Runner.first.description).to eq('server.hostname') @@ -89,8 +91,10 @@ describe API::Runner, :clean_gitlab_redis_shared_state do context 'when runner tags are provided' do it 'creates runner' do - post api('/runners'), token: registration_token, - tag_list: 'tag1, tag2' + post api('/runners'), params: { + token: registration_token, + tag_list: 'tag1, tag2' + } expect(response).to have_gitlab_http_status 201 expect(Ci::Runner.first.tag_list.sort).to eq(%w(tag1 tag2)) @@ -100,9 +104,11 @@ describe API::Runner, :clean_gitlab_redis_shared_state do context 'when option for running untagged jobs is provided' do context 'when tags are provided' do it 'creates runner' do - post api('/runners'), token: registration_token, - run_untagged: false, - tag_list: ['tag'] + post api('/runners'), params: { + token: registration_token, + run_untagged: false, + tag_list: ['tag'] + } expect(response).to have_gitlab_http_status 201 expect(Ci::Runner.first.run_untagged).to be false @@ -112,8 +118,10 @@ describe API::Runner, :clean_gitlab_redis_shared_state do context 'when tags are not provided' do it 'returns 400 error' do - post api('/runners'), token: registration_token, - run_untagged: false + post api('/runners'), params: { + token: registration_token, + run_untagged: false + } expect(response).to have_gitlab_http_status 400 expect(json_response['message']).to include( @@ -124,8 +132,10 @@ describe API::Runner, :clean_gitlab_redis_shared_state do context 'when option for locking Runner is provided' do it 'creates runner' do - post api('/runners'), token: registration_token, - locked: true + post api('/runners'), params: { + token: registration_token, + locked: true + } expect(response).to have_gitlab_http_status 201 expect(Ci::Runner.first.locked).to be true @@ -135,8 +145,10 @@ describe API::Runner, :clean_gitlab_redis_shared_state do context 'when option for activating a Runner is provided' do context 'when active is set to true' do it 'creates runner' do - post api('/runners'), token: registration_token, - active: true + post api('/runners'), params: { + token: registration_token, + active: true + } expect(response).to have_gitlab_http_status 201 expect(Ci::Runner.first.active).to be true @@ -145,8 +157,10 @@ describe API::Runner, :clean_gitlab_redis_shared_state do context 'when active is set to false' do it 'creates runner' do - post api('/runners'), token: registration_token, - active: false + post api('/runners'), params: { + token: registration_token, + active: false + } expect(response).to have_gitlab_http_status 201 expect(Ci::Runner.first.active).to be false @@ -156,8 +170,10 @@ describe API::Runner, :clean_gitlab_redis_shared_state do context 'when maximum job timeout is specified' do it 'creates runner' do - post api('/runners'), token: registration_token, - maximum_timeout: 9000 + post api('/runners'), params: { + token: registration_token, + maximum_timeout: 9000 + } expect(response).to have_gitlab_http_status 201 expect(Ci::Runner.first.maximum_timeout).to eq(9000) @@ -165,8 +181,10 @@ describe API::Runner, :clean_gitlab_redis_shared_state do context 'when maximum job timeout is empty' do it 'creates runner' do - post api('/runners'), token: registration_token, - maximum_timeout: '' + post api('/runners'), params: { + token: registration_token, + maximum_timeout: '' + } expect(response).to have_gitlab_http_status 201 expect(Ci::Runner.first.maximum_timeout).to be_nil @@ -179,8 +197,10 @@ describe API::Runner, :clean_gitlab_redis_shared_state do let(:value) { "#{param}_value" } it "updates provided Runner's parameter" do - post api('/runners'), token: registration_token, - info: { param => value } + post api('/runners'), params: { + token: registration_token, + info: { param => value } + } expect(response).to have_gitlab_http_status 201 expect(Ci::Runner.first.read_attribute(param.to_sym)).to eq(value) @@ -190,8 +210,8 @@ describe API::Runner, :clean_gitlab_redis_shared_state do it "sets the runner's ip_address" do post api('/runners'), - { token: registration_token }, - { 'REMOTE_ADDR' => '123.111.123.111' } + params: { token: registration_token }, + headers: { 'REMOTE_ADDR' => '123.111.123.111' } expect(response).to have_gitlab_http_status 201 expect(Ci::Runner.first.ip_address).to eq('123.111.123.111') @@ -209,7 +229,7 @@ describe API::Runner, :clean_gitlab_redis_shared_state do context 'when invalid token is provided' do it 'returns 403 error' do - delete api('/runners'), token: 'invalid' + delete api('/runners'), params: { token: 'invalid' } expect(response).to have_gitlab_http_status 403 end @@ -219,7 +239,7 @@ describe API::Runner, :clean_gitlab_redis_shared_state do let(:runner) { create(:ci_runner) } it 'deletes Runner' do - delete api('/runners'), token: runner.token + delete api('/runners'), params: { token: runner.token } expect(response).to have_gitlab_http_status 204 expect(Ci::Runner.count).to eq(0) @@ -245,7 +265,7 @@ describe API::Runner, :clean_gitlab_redis_shared_state do context 'when invalid token is provided' do it 'returns 403 error' do - post api('/runners/verify'), token: 'invalid-token' + post api('/runners/verify'), params: { token: 'invalid-token' } expect(response).to have_gitlab_http_status 403 end @@ -253,7 +273,7 @@ describe API::Runner, :clean_gitlab_redis_shared_state do context 'when valid token is provided' do it 'verifies Runner credentials' do - post api('/runners/verify'), token: runner.token + post api('/runners/verify'), params: { token: runner.token } expect(response).to have_gitlab_http_status 200 end @@ -342,7 +362,7 @@ describe API::Runner, :clean_gitlab_redis_shared_state do context 'when invalid token is provided' do it 'returns 403 error' do - post api('/jobs/request'), token: 'invalid' + post api('/jobs/request'), params: { token: 'invalid' } expect(response).to have_gitlab_http_status 403 end @@ -499,8 +519,8 @@ describe API::Runner, :clean_gitlab_redis_shared_state do it "sets the runner's ip_address" do post api('/jobs/request'), - { token: runner.token }, - { 'User-Agent' => user_agent, 'REMOTE_ADDR' => '123.222.123.222' } + params: { token: runner.token }, + headers: { 'User-Agent' => user_agent, 'REMOTE_ADDR' => '123.222.123.222' } expect(response).to have_gitlab_http_status 201 expect(runner.reload.ip_address).to eq('123.222.123.222') @@ -752,7 +772,7 @@ describe API::Runner, :clean_gitlab_redis_shared_state do def request_job(token = runner.token, **params) new_params = params.merge(token: token, last_update: last_update) - post api('/jobs/request'), new_params, { 'User-Agent' => user_agent } + post api('/jobs/request'), params: new_params, headers: { 'User-Agent' => user_agent } end end end @@ -893,7 +913,7 @@ describe API::Runner, :clean_gitlab_redis_shared_state do def update_job(token = job.token, **params) new_params = params.merge(token: token) - put api("/jobs/#{job.id}"), new_params + put api("/jobs/#{job.id}"), params: new_params end def update_job_after_time(update_interval = 20.minutes, state = 'running') @@ -1113,7 +1133,7 @@ describe API::Runner, :clean_gitlab_redis_shared_state do end Timecop.travel(job.updated_at + update_interval) do - patch api("/jobs/#{job.id}/trace"), content, request_headers + patch api("/jobs/#{job.id}/trace"), params: content, headers: request_headers job.reload end end @@ -1244,7 +1264,7 @@ describe API::Runner, :clean_gitlab_redis_shared_state do end def authorize_artifacts(params = {}, request_headers = headers) - post api("/jobs/#{job.id}/artifacts/authorize"), params, request_headers + post api("/jobs/#{job.id}/artifacts/authorize"), params: params, headers: request_headers end def authorize_artifacts_with_token_in_params(params = {}, request_headers = headers) @@ -1300,7 +1320,7 @@ describe API::Runner, :clean_gitlab_redis_shared_state do end before do - fog_connection.directories.get('artifacts').files.create( + fog_connection.directories.new(key: 'artifacts').files.create( key: 'tmp/uploads/12312300', body: 'content' ) @@ -1347,7 +1367,7 @@ describe API::Runner, :clean_gitlab_redis_shared_state do context 'when artifacts post request does not contain file' do it 'fails to post artifacts without file' do - post api("/jobs/#{job.id}/artifacts"), {}, headers_with_token + post api("/jobs/#{job.id}/artifacts"), params: {}, headers: headers_with_token expect(response).to have_gitlab_http_status(400) end @@ -1355,7 +1375,7 @@ describe API::Runner, :clean_gitlab_redis_shared_state do context 'GitLab Workhorse is not configured' do it 'fails to post artifacts without GitLab-Workhorse' do - post api("/jobs/#{job.id}/artifacts"), { token: job.token }, {} + post api("/jobs/#{job.id}/artifacts"), params: { token: job.token }, headers: {} expect(response).to have_gitlab_http_status(403) end @@ -1372,7 +1392,7 @@ describe API::Runner, :clean_gitlab_redis_shared_state do before do stub_application_setting(default_artifacts_expire_in: default_artifacts_expire_in) - post(api("/jobs/#{job.id}/artifacts"), post_data, headers_with_token) + post(api("/jobs/#{job.id}/artifacts"), params: post_data, headers: headers_with_token) end context 'when an expire_in is given' do @@ -1427,7 +1447,7 @@ describe API::Runner, :clean_gitlab_redis_shared_state do let(:stored_metadata_sha256) { job.reload.job_artifacts_metadata.file_sha256 } before do - post(api("/jobs/#{job.id}/artifacts"), post_data, headers_with_token) + post(api("/jobs/#{job.id}/artifacts"), params: post_data, headers: headers_with_token) end context 'when posts data accelerated by workhorse is correct' do @@ -1545,7 +1565,7 @@ describe API::Runner, :clean_gitlab_redis_shared_state do 'file.name' => file.original_filename }) - post api("/jobs/#{job.id}/artifacts"), params, headers + post api("/jobs/#{job.id}/artifacts"), params: params, headers: headers end end @@ -1631,7 +1651,7 @@ describe API::Runner, :clean_gitlab_redis_shared_state do params = params.merge(token: token) job.reload - get api("/jobs/#{job.id}/artifacts"), params, request_headers + get api("/jobs/#{job.id}/artifacts"), params: params, headers: request_headers end end end diff --git a/spec/requests/api/runners_spec.rb b/spec/requests/api/runners_spec.rb index 49a79d2ccf9..7f11c8c9fe8 100644 --- a/spec/requests/api/runners_spec.rb +++ b/spec/requests/api/runners_spec.rb @@ -400,14 +400,14 @@ describe API::Runners do end def update_runner(id, user, args) - put api("/runners/#{id}", user), args + put api("/runners/#{id}", user), params: args end end context 'authorized user' do context 'when runner is shared' do it 'does not update runner' do - put api("/runners/#{shared_runner.id}", user), description: 'test' + put api("/runners/#{shared_runner.id}", user), params: { description: 'test' } expect(response).to have_gitlab_http_status(403) end @@ -415,14 +415,14 @@ describe API::Runners do context 'when runner is not shared' do it 'does not update project runner without access to it' do - put api("/runners/#{project_runner.id}", user2), description: 'test' + put api("/runners/#{project_runner.id}", user2), params: { description: 'test' } expect(response).to have_http_status(403) end it 'updates project runner with access to it' do description = project_runner.description - put api("/runners/#{project_runner.id}", admin), description: 'test' + put api("/runners/#{project_runner.id}", admin), params: { description: 'test' } project_runner.reload expect(response).to have_gitlab_http_status(200) @@ -741,14 +741,14 @@ describe API::Runners do it 'enables specific runner' do expect do - post api("/projects/#{project.id}/runners", user), runner_id: project_runner2.id + post api("/projects/#{project.id}/runners", user), params: { runner_id: project_runner2.id } end.to change { project.runners.count }.by(+1) expect(response).to have_gitlab_http_status(201) end it 'avoids changes when enabling already enabled runner' do expect do - post api("/projects/#{project.id}/runners", user), runner_id: project_runner.id + post api("/projects/#{project.id}/runners", user), params: { runner_id: project_runner.id } end.to change { project.runners.count }.by(0) expect(response).to have_gitlab_http_status(400) end @@ -757,20 +757,20 @@ describe API::Runners do project_runner2.update(locked: true) expect do - post api("/projects/#{project.id}/runners", user), runner_id: project_runner2.id + post api("/projects/#{project.id}/runners", user), params: { runner_id: project_runner2.id } end.to change { project.runners.count }.by(0) expect(response).to have_gitlab_http_status(403) end it 'does not enable shared runner' do - post api("/projects/#{project.id}/runners", user), runner_id: shared_runner.id + post api("/projects/#{project.id}/runners", user), params: { runner_id: shared_runner.id } expect(response).to have_gitlab_http_status(403) end it 'does not enable group runner' do - post api("/projects/#{project.id}/runners", user), runner_id: group_runner.id + post api("/projects/#{project.id}/runners", user), params: { runner_id: group_runner.id } expect(response).to have_http_status(403) end @@ -781,7 +781,7 @@ describe API::Runners do it 'enables any specific runner' do expect do - post api("/projects/#{project.id}/runners", admin), runner_id: new_project_runner.id + post api("/projects/#{project.id}/runners", admin), params: { runner_id: new_project_runner.id } end.to change { project.runners.count }.by(+1) expect(response).to have_gitlab_http_status(201) end @@ -789,7 +789,7 @@ describe API::Runners do it 'enables a instance type runner' do expect do - post api("/projects/#{project.id}/runners", admin), runner_id: shared_runner.id + post api("/projects/#{project.id}/runners", admin), params: { runner_id: shared_runner.id } end.to change { project.runners.count }.by(1) expect(shared_runner.reload).not_to be_instance_type @@ -808,7 +808,7 @@ describe API::Runners do let!(:new_project_runner) { create(:ci_runner, :project) } it 'does not enable runner without access to' do - post api("/projects/#{project.id}/runners", user), runner_id: new_project_runner.id + post api("/projects/#{project.id}/runners", user), params: { runner_id: new_project_runner.id } expect(response).to have_gitlab_http_status(403) end diff --git a/spec/requests/api/search_spec.rb b/spec/requests/api/search_spec.rb index f8e468be170..831f47debeb 100644 --- a/spec/requests/api/search_spec.rb +++ b/spec/requests/api/search_spec.rb @@ -16,7 +16,7 @@ describe API::Search do describe 'GET /search' do context 'when user is not authenticated' do it 'returns 401 error' do - get api('/search'), scope: 'projects', search: 'awesome' + get api('/search'), params: { scope: 'projects', search: 'awesome' } expect(response).to have_gitlab_http_status(401) end @@ -24,7 +24,7 @@ describe API::Search do context 'when scope is not supported' do it 'returns 400 error' do - get api('/search', user), scope: 'unsupported', search: 'awesome' + get api('/search', user), params: { scope: 'unsupported', search: 'awesome' } expect(response).to have_gitlab_http_status(400) end @@ -32,7 +32,7 @@ describe API::Search do context 'when scope is missing' do it 'returns 400 error' do - get api('/search', user), search: 'awesome' + get api('/search', user), params: { search: 'awesome' } expect(response).to have_gitlab_http_status(400) end @@ -41,7 +41,7 @@ describe API::Search do context 'with correct params' do context 'for projects scope' do before do - get api('/search', user), scope: 'projects', search: 'awesome' + get api('/search', user), params: { scope: 'projects', search: 'awesome' } end it_behaves_like 'response is correct', schema: 'public_api/v4/projects' @@ -51,7 +51,7 @@ describe API::Search do before do create(:issue, project: project, title: 'awesome issue') - get api('/search', user), scope: 'issues', search: 'awesome' + get api('/search', user), params: { scope: 'issues', search: 'awesome' } end it_behaves_like 'response is correct', schema: 'public_api/v4/issues' @@ -61,7 +61,7 @@ describe API::Search do before do create(:merge_request, source_project: repo_project, title: 'awesome mr') - get api('/search', user), scope: 'merge_requests', search: 'awesome' + get api('/search', user), params: { scope: 'merge_requests', search: 'awesome' } end it_behaves_like 'response is correct', schema: 'public_api/v4/merge_requests' @@ -71,7 +71,7 @@ describe API::Search do before do create(:milestone, project: project, title: 'awesome milestone') - get api('/search', user), scope: 'milestones', search: 'awesome' + get api('/search', user), params: { scope: 'milestones', search: 'awesome' } end it_behaves_like 'response is correct', schema: 'public_api/v4/milestones' @@ -81,7 +81,7 @@ describe API::Search do before do create(:snippet, :public, title: 'awesome snippet', content: 'snippet content') - get api('/search', user), scope: 'snippet_titles', search: 'awesome' + get api('/search', user), params: { scope: 'snippet_titles', search: 'awesome' } end it_behaves_like 'response is correct', schema: 'public_api/v4/snippets' @@ -91,7 +91,7 @@ describe API::Search do before do create(:snippet, :public, title: 'awesome snippet', content: 'snippet content') - get api('/search', user), scope: 'snippet_blobs', search: 'content' + get api('/search', user), params: { scope: 'snippet_blobs', search: 'content' } end it_behaves_like 'response is correct', schema: 'public_api/v4/snippets' @@ -102,7 +102,7 @@ describe API::Search do describe "GET /groups/:id/search" do context 'when user is not authenticated' do it 'returns 401 error' do - get api("/groups/#{group.id}/search"), scope: 'projects', search: 'awesome' + get api("/groups/#{group.id}/search"), params: { scope: 'projects', search: 'awesome' } expect(response).to have_gitlab_http_status(401) end @@ -110,7 +110,7 @@ describe API::Search do context 'when scope is not supported' do it 'returns 400 error' do - get api("/groups/#{group.id}/search", user), scope: 'unsupported', search: 'awesome' + get api("/groups/#{group.id}/search", user), params: { scope: 'unsupported', search: 'awesome' } expect(response).to have_gitlab_http_status(400) end @@ -118,7 +118,7 @@ describe API::Search do context 'when scope is missing' do it 'returns 400 error' do - get api("/groups/#{group.id}/search", user), search: 'awesome' + get api("/groups/#{group.id}/search", user), params: { search: 'awesome' } expect(response).to have_gitlab_http_status(400) end @@ -126,7 +126,7 @@ describe API::Search do context 'when group does not exist' do it 'returns 404 error' do - get api('/groups/9999/search', user), scope: 'issues', search: 'awesome' + get api('/groups/9999/search', user), params: { scope: 'issues', search: 'awesome' } expect(response).to have_gitlab_http_status(404) end @@ -136,7 +136,7 @@ describe API::Search do it 'returns 404 error' do private_group = create(:group, :private) - get api("/groups/#{private_group.id}/search", user), scope: 'issues', search: 'awesome' + get api("/groups/#{private_group.id}/search", user), params: { scope: 'issues', search: 'awesome' } expect(response).to have_gitlab_http_status(404) end @@ -145,7 +145,7 @@ describe API::Search do context 'with correct params' do context 'for projects scope' do before do - get api("/groups/#{group.id}/search", user), scope: 'projects', search: 'awesome' + get api("/groups/#{group.id}/search", user), params: { scope: 'projects', search: 'awesome' } end it_behaves_like 'response is correct', schema: 'public_api/v4/projects' @@ -155,7 +155,7 @@ describe API::Search do before do create(:issue, project: project, title: 'awesome issue') - get api("/groups/#{group.id}/search", user), scope: 'issues', search: 'awesome' + get api("/groups/#{group.id}/search", user), params: { scope: 'issues', search: 'awesome' } end it_behaves_like 'response is correct', schema: 'public_api/v4/issues' @@ -165,7 +165,7 @@ describe API::Search do before do create(:merge_request, source_project: repo_project, title: 'awesome mr') - get api("/groups/#{group.id}/search", user), scope: 'merge_requests', search: 'awesome' + get api("/groups/#{group.id}/search", user), params: { scope: 'merge_requests', search: 'awesome' } end it_behaves_like 'response is correct', schema: 'public_api/v4/merge_requests' @@ -175,7 +175,7 @@ describe API::Search do before do create(:milestone, project: project, title: 'awesome milestone') - get api("/groups/#{group.id}/search", user), scope: 'milestones', search: 'awesome' + get api("/groups/#{group.id}/search", user), params: { scope: 'milestones', search: 'awesome' } end it_behaves_like 'response is correct', schema: 'public_api/v4/milestones' @@ -187,7 +187,7 @@ describe API::Search do create(:milestone, project: project, title: 'awesome milestone') create(:milestone, project: another_project, title: 'awesome milestone other project') - get api("/groups/#{CGI.escape(group.full_path)}/search", user), scope: 'milestones', search: 'awesome' + get api("/groups/#{CGI.escape(group.full_path)}/search", user), params: { scope: 'milestones', search: 'awesome' } end it_behaves_like 'response is correct', schema: 'public_api/v4/milestones' @@ -198,7 +198,7 @@ describe API::Search do describe "GET /projects/:id/search" do context 'when user is not authenticated' do it 'returns 401 error' do - get api("/projects/#{project.id}/search"), scope: 'issues', search: 'awesome' + get api("/projects/#{project.id}/search"), params: { scope: 'issues', search: 'awesome' } expect(response).to have_gitlab_http_status(401) end @@ -206,7 +206,7 @@ describe API::Search do context 'when scope is not supported' do it 'returns 400 error' do - get api("/projects/#{project.id}/search", user), scope: 'unsupported', search: 'awesome' + get api("/projects/#{project.id}/search", user), params: { scope: 'unsupported', search: 'awesome' } expect(response).to have_gitlab_http_status(400) end @@ -214,7 +214,7 @@ describe API::Search do context 'when scope is missing' do it 'returns 400 error' do - get api("/projects/#{project.id}/search", user), search: 'awesome' + get api("/projects/#{project.id}/search", user), params: { search: 'awesome' } expect(response).to have_gitlab_http_status(400) end @@ -222,7 +222,7 @@ describe API::Search do context 'when project does not exist' do it 'returns 404 error' do - get api('/projects/9999/search', user), scope: 'issues', search: 'awesome' + get api('/projects/9999/search', user), params: { scope: 'issues', search: 'awesome' } expect(response).to have_gitlab_http_status(404) end @@ -232,7 +232,7 @@ describe API::Search do it 'returns 404 error' do project.update!(visibility_level: Gitlab::VisibilityLevel::PRIVATE) - get api("/projects/#{project.id}/search", user), scope: 'issues', search: 'awesome' + get api("/projects/#{project.id}/search", user), params: { scope: 'issues', search: 'awesome' } expect(response).to have_gitlab_http_status(404) end @@ -243,7 +243,7 @@ describe API::Search do before do create(:issue, project: project, title: 'awesome issue') - get api("/projects/#{project.id}/search", user), scope: 'issues', search: 'awesome' + get api("/projects/#{project.id}/search", user), params: { scope: 'issues', search: 'awesome' } end it_behaves_like 'response is correct', schema: 'public_api/v4/issues' @@ -253,7 +253,7 @@ describe API::Search do before do create(:merge_request, source_project: repo_project, title: 'awesome mr') - get api("/projects/#{repo_project.id}/search", user), scope: 'merge_requests', search: 'awesome' + get api("/projects/#{repo_project.id}/search", user), params: { scope: 'merge_requests', search: 'awesome' } end it_behaves_like 'response is correct', schema: 'public_api/v4/merge_requests' @@ -263,7 +263,7 @@ describe API::Search do before do create(:milestone, project: project, title: 'awesome milestone') - get api("/projects/#{project.id}/search", user), scope: 'milestones', search: 'awesome' + get api("/projects/#{project.id}/search", user), params: { scope: 'milestones', search: 'awesome' } end it_behaves_like 'response is correct', schema: 'public_api/v4/milestones' @@ -273,7 +273,7 @@ describe API::Search do before do create(:note_on_merge_request, project: project, note: 'awesome note') - get api("/projects/#{project.id}/search", user), scope: 'notes', search: 'awesome' + get api("/projects/#{project.id}/search", user), params: { scope: 'notes', search: 'awesome' } end it_behaves_like 'response is correct', schema: 'public_api/v4/notes' @@ -284,7 +284,7 @@ describe API::Search do wiki = create(:project_wiki, project: project) create(:wiki_page, wiki: wiki, attrs: { title: 'home', content: "Awesome page" }) - get api("/projects/#{project.id}/search", user), scope: 'wiki_blobs', search: 'awesome' + get api("/projects/#{project.id}/search", user), params: { scope: 'wiki_blobs', search: 'awesome' } end it_behaves_like 'response is correct', schema: 'public_api/v4/blobs' @@ -292,7 +292,7 @@ describe API::Search do context 'for commits scope' do before do - get api("/projects/#{repo_project.id}/search", user), scope: 'commits', search: '498214de67004b1da3d820901307bed2a68a8ef6' + get api("/projects/#{repo_project.id}/search", user), params: { scope: 'commits', search: '498214de67004b1da3d820901307bed2a68a8ef6' } end it_behaves_like 'response is correct', schema: 'public_api/v4/commits_details' @@ -300,7 +300,7 @@ describe API::Search do context 'for commits scope with project path as id' do before do - get api("/projects/#{CGI.escape(repo_project.full_path)}/search", user), scope: 'commits', search: '498214de67004b1da3d820901307bed2a68a8ef6' + get api("/projects/#{CGI.escape(repo_project.full_path)}/search", user), params: { scope: 'commits', search: '498214de67004b1da3d820901307bed2a68a8ef6' } end it_behaves_like 'response is correct', schema: 'public_api/v4/commits_details' @@ -308,14 +308,14 @@ describe API::Search do context 'for blobs scope' do before do - get api("/projects/#{repo_project.id}/search", user), scope: 'blobs', search: 'monitors' + get api("/projects/#{repo_project.id}/search", user), params: { scope: 'blobs', search: 'monitors' } end it_behaves_like 'response is correct', schema: 'public_api/v4/blobs', size: 2 context 'filters' do it 'by filename' do - get api("/projects/#{repo_project.id}/search", user), scope: 'blobs', search: 'mon filename:PROCESS.md' + get api("/projects/#{repo_project.id}/search", user), params: { scope: 'blobs', search: 'mon filename:PROCESS.md' } expect(response).to have_gitlab_http_status(200) expect(json_response.size).to eq(2) @@ -323,14 +323,14 @@ describe API::Search do end it 'by path' do - get api("/projects/#{repo_project.id}/search", user), scope: 'blobs', search: 'mon path:markdown' + get api("/projects/#{repo_project.id}/search", user), params: { scope: 'blobs', search: 'mon path:markdown' } expect(response).to have_gitlab_http_status(200) expect(json_response.size).to eq(8) end it 'by extension' do - get api("/projects/#{repo_project.id}/search", user), scope: 'blobs', search: 'mon extension:md' + get api("/projects/#{repo_project.id}/search", user), params: { scope: 'blobs', search: 'mon extension:md' } expect(response).to have_gitlab_http_status(200) expect(json_response.size).to eq(11) diff --git a/spec/requests/api/services_spec.rb b/spec/requests/api/services_spec.rb index 236f8d7faf5..e260aa21e25 100644 --- a/spec/requests/api/services_spec.rb +++ b/spec/requests/api/services_spec.rb @@ -14,7 +14,7 @@ describe API::Services do include_context service it "updates #{service} settings" do - put api("/projects/#{project.id}/services/#{dashed_service}", user), service_attrs + put api("/projects/#{project.id}/services/#{dashed_service}", user), params: service_attrs expect(response).to have_gitlab_http_status(200) @@ -22,7 +22,7 @@ describe API::Services do event = current_service.event_names.empty? ? "foo" : current_service.event_names.first state = current_service[event] || false - put api("/projects/#{project.id}/services/#{dashed_service}?#{event}=#{!state}", user), service_attrs + put api("/projects/#{project.id}/services/#{dashed_service}?#{event}=#{!state}", user), params: service_attrs expect(response).to have_gitlab_http_status(200) expect(project.services.first[event]).not_to eq(state) unless event == "foo" @@ -44,7 +44,7 @@ describe API::Services do expected_code = 400 end - put api("/projects/#{project.id}/services/#{dashed_service}", user), attrs + put api("/projects/#{project.id}/services/#{dashed_service}", user), params: attrs expect(response.status).to eq(expected_code) end @@ -127,7 +127,7 @@ describe API::Services do end it 'when the service is inactive' do - post api("/projects/#{project.id}/services/#{service_name}/trigger"), params + post api("/projects/#{project.id}/services/#{service_name}/trigger"), params: params expect(response).to have_gitlab_http_status(404) end @@ -142,7 +142,7 @@ describe API::Services do end it 'returns status 200' do - post api("/projects/#{project.id}/services/#{service_name}/trigger"), params + post api("/projects/#{project.id}/services/#{service_name}/trigger"), params: params expect(response).to have_gitlab_http_status(200) end @@ -150,7 +150,7 @@ describe API::Services do context 'when the project can not be found' do it 'returns a generic 404' do - post api("/projects/404/services/#{service_name}/trigger"), params + post api("/projects/404/services/#{service_name}/trigger"), params: params expect(response).to have_gitlab_http_status(404) expect(json_response["message"]).to eq("404 Service Not Found") @@ -170,7 +170,7 @@ describe API::Services do end it 'returns status 200' do - post api("/projects/#{project.id}/services/#{service_name}/trigger"), token: 'token', text: 'help' + post api("/projects/#{project.id}/services/#{service_name}/trigger"), params: { token: 'token', text: 'help' } expect(response).to have_gitlab_http_status(200) expect(json_response['response_type']).to eq("ephemeral") @@ -192,7 +192,7 @@ describe API::Services do end it 'accepts a username for update' do - put api("/projects/#{project.id}/services/mattermost", user), params.merge(username: 'new_username') + put api("/projects/#{project.id}/services/mattermost", user), params: params.merge(username: 'new_username') expect(response).to have_gitlab_http_status(200) expect(json_response['properties']['username']).to eq('new_username') diff --git a/spec/requests/api/settings_spec.rb b/spec/requests/api/settings_spec.rb index 84c7210f6bb..cfbda63bb30 100644 --- a/spec/requests/api/settings_spec.rb +++ b/spec/requests/api/settings_spec.rb @@ -42,27 +42,29 @@ describe API::Settings, 'Settings' do it "updates application settings" do put api("/application/settings", admin), - default_projects_limit: 3, - password_authentication_enabled_for_web: false, - repository_storages: ['custom'], - plantuml_enabled: true, - plantuml_url: 'http://plantuml.example.com', - default_snippet_visibility: 'internal', - restricted_visibility_levels: ['public'], - default_artifacts_expire_in: '2 days', - help_page_text: 'custom help text', - help_page_hide_commercial_content: true, - help_page_support_url: 'http://example.com/help', - project_export_enabled: false, - rsa_key_restriction: ApplicationSetting::FORBIDDEN_KEY_VALUE, - dsa_key_restriction: 2048, - ecdsa_key_restriction: 384, - ed25519_key_restriction: 256, - enforce_terms: true, - terms: 'Hello world!', - performance_bar_allowed_group_path: group.full_path, - instance_statistics_visibility_private: true, - diff_max_patch_bytes: 150_000 + params: { + default_projects_limit: 3, + password_authentication_enabled_for_web: false, + repository_storages: ['custom'], + plantuml_enabled: true, + plantuml_url: 'http://plantuml.example.com', + default_snippet_visibility: 'internal', + restricted_visibility_levels: ['public'], + default_artifacts_expire_in: '2 days', + help_page_text: 'custom help text', + help_page_hide_commercial_content: true, + help_page_support_url: 'http://example.com/help', + project_export_enabled: false, + rsa_key_restriction: ApplicationSetting::FORBIDDEN_KEY_VALUE, + dsa_key_restriction: 2048, + ecdsa_key_restriction: 384, + ed25519_key_restriction: 256, + enforce_terms: true, + terms: 'Hello world!', + performance_bar_allowed_group_path: group.full_path, + instance_statistics_visibility_private: true, + diff_max_patch_bytes: 150_000 + } expect(response).to have_gitlab_http_status(200) expect(json_response['default_projects_limit']).to eq(3) @@ -91,7 +93,7 @@ describe API::Settings, 'Settings' do it "supports legacy performance_bar_allowed_group_id" do put api("/application/settings", admin), - performance_bar_allowed_group_id: group.full_path + params: { performance_bar_allowed_group_id: group.full_path } expect(response).to have_gitlab_http_status(200) expect(json_response['performance_bar_allowed_group_id']).to eq(group.id) @@ -99,8 +101,10 @@ describe API::Settings, 'Settings' do it "supports legacy performance_bar_enabled" do put api("/application/settings", admin), - performance_bar_enabled: false, - performance_bar_allowed_group_id: group.full_path + params: { + performance_bar_enabled: false, + performance_bar_allowed_group_id: group.full_path + } expect(response).to have_gitlab_http_status(200) expect(json_response['performance_bar_allowed_group_id']).to be_nil @@ -108,7 +112,7 @@ describe API::Settings, 'Settings' do context "missing plantuml_url value when plantuml_enabled is true" do it "returns a blank parameter error message" do - put api("/application/settings", admin), plantuml_enabled: true + put api("/application/settings", admin), params: { plantuml_enabled: true } expect(response).to have_gitlab_http_status(400) expect(json_response['error']).to eq('plantuml_url is missing') diff --git a/spec/requests/api/snippets_spec.rb b/spec/requests/api/snippets_spec.rb index c546ba3e127..7c8512f7589 100644 --- a/spec/requests/api/snippets_spec.rb +++ b/spec/requests/api/snippets_spec.rb @@ -143,7 +143,7 @@ describe API::Snippets do it 'creates a new snippet' do expect do - post api("/snippets/", user), params + post api("/snippets/", user), params: params end.to change { PersonalSnippet.count }.by(1) expect(response).to have_gitlab_http_status(201) @@ -156,14 +156,14 @@ describe API::Snippets do it 'returns 400 for missing parameters' do params.delete(:title) - post api("/snippets/", user), params + post api("/snippets/", user), params: params expect(response).to have_gitlab_http_status(400) end context 'when the snippet is spam' do def create_snippet(snippet_params = {}) - post api('/snippets', user), params.merge(snippet_params) + post api('/snippets', user), params: params.merge(snippet_params) end before do @@ -205,7 +205,7 @@ describe API::Snippets do new_content = 'New content' new_description = 'New description' - put api("/snippets/#{snippet.id}", user), content: new_content, description: new_description + put api("/snippets/#{snippet.id}", user), params: { content: new_content, description: new_description } expect(response).to have_gitlab_http_status(200) snippet.reload @@ -214,14 +214,14 @@ describe API::Snippets do end it 'returns 404 for invalid snippet id' do - put api("/snippets/1234", user), title: 'foo' + put api("/snippets/1234", user), params: { title: 'foo' } expect(response).to have_gitlab_http_status(404) expect(json_response['message']).to eq('404 Snippet Not Found') end it "returns 404 for another user's snippet" do - put api("/snippets/#{snippet.id}", other_user), title: 'fubar' + put api("/snippets/#{snippet.id}", other_user), params: { title: 'fubar' } expect(response).to have_gitlab_http_status(404) expect(json_response['message']).to eq('404 Snippet Not Found') @@ -235,7 +235,7 @@ describe API::Snippets do context 'when the snippet is spam' do def update_snippet(snippet_params = {}) - put api("/snippets/#{snippet.id}", user), snippet_params + put api("/snippets/#{snippet.id}", user), params: snippet_params end before do diff --git a/spec/requests/api/submodules_spec.rb b/spec/requests/api/submodules_spec.rb index fa447c028c2..c482a85c68f 100644 --- a/spec/requests/api/submodules_spec.rb +++ b/spec/requests/api/submodules_spec.rb @@ -31,7 +31,7 @@ describe API::Submodules do describe "PUT /projects/:id/repository/submodule/:submodule" do context 'when unauthenticated' do it 'returns 401' do - put api(route(submodule)), params + put api(route(submodule)), params: params expect(response).to have_gitlab_http_status(401) end @@ -39,7 +39,7 @@ describe API::Submodules do context 'when authenticated', 'as a guest' do it 'returns 403' do - put api(route(submodule), guest), params + put api(route(submodule), guest), params: params expect(response).to have_gitlab_http_status(403) end @@ -53,13 +53,13 @@ describe API::Submodules do end it 'returns 400 if branch is missing' do - put api(route(submodule), user), params.except(:branch) + put api(route(submodule), user), params: params.except(:branch) expect(response).to have_gitlab_http_status(400) end it 'returns 400 if commit_sha is missing' do - put api(route(submodule), user), params.except(:commit_sha) + put api(route(submodule), user), params: params.except(:commit_sha) expect(response).to have_gitlab_http_status(400) end @@ -67,7 +67,7 @@ describe API::Submodules do it 'returns the commmit' do head_commit = project.repository.commit.id - put api(route(submodule), user), params + put api(route(submodule), user), params: params expect(response).to have_gitlab_http_status(200) expect(json_response['message']).to eq commit_message @@ -87,7 +87,7 @@ describe API::Submodules do .with(any_args, hash_including(submodule: submodule)) .and_call_original - put api(route(encoded_submodule), user), params + put api(route(encoded_submodule), user), params: params expect(response).to have_gitlab_http_status(200) expect(json_response['id']).to eq project.repository.commit(branch).id diff --git a/spec/requests/api/suggestions_spec.rb b/spec/requests/api/suggestions_spec.rb new file mode 100644 index 00000000000..3c2842e5725 --- /dev/null +++ b/spec/requests/api/suggestions_spec.rb @@ -0,0 +1,83 @@ +# frozen_string_literal: true + +require 'spec_helper' + +describe API::Suggestions do + let(:project) { create(:project, :repository) } + let(:user) { create(:user) } + + let(:merge_request) do + create(:merge_request, source_project: project, + target_project: project) + end + + let(:position) do + Gitlab::Diff::Position.new(old_path: "files/ruby/popen.rb", + new_path: "files/ruby/popen.rb", + old_line: nil, + new_line: 9, + diff_refs: merge_request.diff_refs) + end + + let(:diff_note) do + create(:diff_note_on_merge_request, noteable: merge_request, + position: position, + project: project) + end + + describe "PUT /suggestions/:id/apply" do + let(:url) { "/suggestions/#{suggestion.id}/apply" } + + context 'when successfully applies patch' do + let(:suggestion) do + create(:suggestion, note: diff_note, + from_content: " raise RuntimeError, \"System commands must be given as an array of strings\"\n", + to_content: " raise RuntimeError, 'Explosion'\n # explosion?") + end + + it 'returns 200 with json content' do + project.add_maintainer(user) + + put api(url, user), params: { id: suggestion.id } + + expect(response).to have_gitlab_http_status(200) + expect(json_response) + .to include('id', 'from_original_line', 'to_original_line', + 'from_line', 'to_line', 'appliable', 'applied', + 'from_content', 'to_content') + end + end + + context 'when not able to apply patch' do + let(:suggestion) do + create(:suggestion, :unappliable, note: diff_note) + end + + it 'returns 400 with json content' do + project.add_maintainer(user) + + put api(url, user), params: { id: suggestion.id } + + expect(response).to have_gitlab_http_status(400) + expect(json_response).to eq({ 'message' => 'Suggestion is not appliable' }) + end + end + + context 'when unauthorized' do + let(:suggestion) do + create(:suggestion, note: diff_note, + from_content: " raise RuntimeError, \"System commands must be given as an array of strings\"\n", + to_content: " raise RuntimeError, 'Explosion'\n # explosion?") + end + + it 'returns 403 with json content' do + project.add_reporter(user) + + put api(url, user), params: { id: suggestion.id } + + expect(response).to have_gitlab_http_status(403) + expect(json_response).to eq({ 'message' => '403 Forbidden' }) + end + end + end +end diff --git a/spec/requests/api/system_hooks_spec.rb b/spec/requests/api/system_hooks_spec.rb index 6c57d443cbf..b6e8d74c2e9 100644 --- a/spec/requests/api/system_hooks_spec.rb +++ b/spec/requests/api/system_hooks_spec.rb @@ -45,7 +45,7 @@ describe API::SystemHooks do describe "POST /hooks" do it "creates new hook" do expect do - post api("/hooks", admin), url: 'http://example.com' + post api("/hooks", admin), params: { url: 'http://example.com' } end.to change { SystemHook.count }.by(1) end @@ -56,7 +56,7 @@ describe API::SystemHooks do end it "responds with 400 if url is invalid" do - post api("/hooks", admin), url: 'hp://mep.mep' + post api("/hooks", admin), params: { url: 'hp://mep.mep' } expect(response).to have_gitlab_http_status(400) end @@ -68,7 +68,7 @@ describe API::SystemHooks do end it 'sets default values for events' do - post api('/hooks', admin), url: 'http://mep.mep' + post api('/hooks', admin), params: { url: 'http://mep.mep' } expect(response).to have_gitlab_http_status(201) expect(json_response['enable_ssl_verification']).to be true @@ -79,11 +79,13 @@ describe API::SystemHooks do it 'sets explicit values for events' do post api('/hooks', admin), - url: 'http://mep.mep', - enable_ssl_verification: false, - push_events: true, - tag_push_events: true, - merge_requests_events: true + params: { + url: 'http://mep.mep', + enable_ssl_verification: false, + push_events: true, + tag_push_events: true, + merge_requests_events: true + } expect(response).to have_http_status(201) expect(json_response['enable_ssl_verification']).to be false diff --git a/spec/requests/api/tags_spec.rb b/spec/requests/api/tags_spec.rb index 98f995df06f..d09b6fe72b1 100644 --- a/spec/requests/api/tags_spec.rb +++ b/spec/requests/api/tags_spec.rb @@ -107,9 +107,12 @@ describe API::Tags do context 'with releases' do let(:description) { 'Awesome release!' } - before do - release = project.releases.find_or_initialize_by(tag: tag_name) - release.update(description: description) + let!(:release) do + create(:release, + :legacy, + project: project, + tag: tag_name, + description: description) end it 'returns an array of project tags with release info' do @@ -193,7 +196,7 @@ describe API::Tags do shared_examples_for 'repository new tag' do it 'creates a new tag' do - post api(route, current_user), tag_name: tag_name, ref: 'master' + post api(route, current_user), params: { tag_name: tag_name, ref: 'master' } expect(response).to have_gitlab_http_status(201) expect(response).to match_response_schema('public_api/v4/tag') @@ -248,26 +251,26 @@ describe API::Tags do end it 'returns 400 if tag name is invalid' do - post api(route, current_user), tag_name: 'new design', ref: 'master' + post api(route, current_user), params: { tag_name: 'new design', ref: 'master' } expect(response).to have_gitlab_http_status(400) expect(json_response['message']).to eq('Tag name invalid') end it 'returns 400 if tag already exists' do - post api(route, current_user), tag_name: 'new_design1', ref: 'master' + post api(route, current_user), params: { tag_name: 'new_design1', ref: 'master' } expect(response).to have_gitlab_http_status(201) expect(response).to match_response_schema('public_api/v4/tag') - post api(route, current_user), tag_name: 'new_design1', ref: 'master' + post api(route, current_user), params: { tag_name: 'new_design1', ref: 'master' } expect(response).to have_gitlab_http_status(400) expect(json_response['message']).to eq('Tag new_design1 already exists') end it 'returns 400 if ref name is invalid' do - post api(route, current_user), tag_name: 'new_design3', ref: 'foo' + post api(route, current_user), params: { tag_name: 'new_design3', ref: 'foo' } expect(response).to have_gitlab_http_status(400) expect(json_response['message']).to eq('Target foo is invalid') @@ -275,7 +278,7 @@ describe API::Tags do context 'lightweight tags with release notes' do it 'creates a new tag' do - post api(route, current_user), tag_name: tag_name, ref: 'master', release_description: 'Wow' + post api(route, current_user), params: { tag_name: tag_name, ref: 'master', release_description: 'Wow' } expect(response).to have_gitlab_http_status(201) expect(response).to match_response_schema('public_api/v4/tag') @@ -294,7 +297,7 @@ describe API::Tags do system(*%W(#{Gitlab.config.git.bin_path} --git-dir=#{repo_path} config user.name #{user.name})) system(*%W(#{Gitlab.config.git.bin_path} --git-dir=#{repo_path} config user.email #{user.email})) - post api(route, current_user), tag_name: 'v7.1.0', ref: 'master', message: 'Release 7.1.0' + post api(route, current_user), params: { tag_name: 'v7.1.0', ref: 'master', message: 'Release 7.1.0' } expect(response).to have_gitlab_http_status(201) expect(response).to match_response_schema('public_api/v4/tag') @@ -360,7 +363,7 @@ describe API::Tags do shared_examples_for 'repository new release' do it 'creates description for existing git tag' do - post api(route, user), description: description + post api(route, user), params: { description: description } expect(response).to have_gitlab_http_status(201) expect(response).to match_response_schema('public_api/v4/release') @@ -372,8 +375,8 @@ describe API::Tags do let(:tag_name) { 'unknown' } it_behaves_like '404 response' do - let(:request) { post api(route, current_user), description: description } - let(:message) { 'Tag does not exist' } + let(:request) { post api(route, current_user), params: { description: description } } + let(:message) { '404 Tag Not Found' } end end @@ -381,7 +384,7 @@ describe API::Tags do include_context 'disabled repository' it_behaves_like '403 response' do - let(:request) { post api(route, current_user), description: description } + let(:request) { post api(route, current_user), params: { description: description } } end end end @@ -398,13 +401,10 @@ describe API::Tags do end context 'on tag with existing release' do - before do - release = project.releases.find_or_initialize_by(tag: tag_name) - release.update(description: description) - end + let!(:release) { create(:release, :legacy, project: project, tag: tag_name, description: description) } it 'returns 409 if there is already a release' do - post api(route, user), description: description + post api(route, user), params: { description: description } expect(response).to have_gitlab_http_status(409) expect(json_response['message']).to eq('Release already exists') @@ -420,13 +420,16 @@ describe API::Tags do shared_examples_for 'repository update release' do context 'on tag with existing release' do - before do - release = project.releases.find_or_initialize_by(tag: tag_name) - release.update(description: description) + let!(:release) do + create(:release, + :legacy, + project: project, + tag: tag_name, + description: description) end it 'updates the release description' do - put api(route, current_user), description: new_description + put api(route, current_user), params: { description: new_description } expect(response).to have_gitlab_http_status(200) expect(json_response['tag_name']).to eq(tag_name) @@ -437,9 +440,9 @@ describe API::Tags do context 'when tag does not exist' do let(:tag_name) { 'unknown' } - it_behaves_like '404 response' do - let(:request) { put api(route, current_user), description: new_description } - let(:message) { 'Tag does not exist' } + it_behaves_like '403 response' do + let(:request) { put api(route, current_user), params: { description: new_description } } + let(:message) { '403 Forbidden' } end end @@ -447,7 +450,7 @@ describe API::Tags do include_context 'disabled repository' it_behaves_like '403 response' do - let(:request) { put api(route, current_user), description: new_description } + let(:request) { put api(route, current_user), params: { description: new_description } } end end end @@ -464,9 +467,9 @@ describe API::Tags do end context 'when release does not exist' do - it_behaves_like '404 response' do - let(:request) { put api(route, current_user), description: new_description } - let(:message) { 'Release does not exist' } + it_behaves_like '403 response' do + let(:request) { put api(route, current_user), params: { description: new_description } } + let(:message) { '403 Forbidden' } end end end diff --git a/spec/requests/api/todos_spec.rb b/spec/requests/api/todos_spec.rb index b5cf04e7f22..f121a1d3b78 100644 --- a/spec/requests/api/todos_spec.rb +++ b/spec/requests/api/todos_spec.rb @@ -49,7 +49,7 @@ describe API::Todos do context 'and using the author filter' do it 'filters based on author_id param' do - get api('/todos', john_doe), { author_id: author_2.id } + get api('/todos', john_doe), params: { author_id: author_2.id } expect(response.status).to eq(200) expect(response).to include_pagination_headers @@ -62,7 +62,7 @@ describe API::Todos do it 'filters based on type param' do create(:todo, project: project_1, author: author_2, user: john_doe, target: merge_request) - get api('/todos', john_doe), { type: 'MergeRequest' } + get api('/todos', john_doe), params: { type: 'MergeRequest' } expect(response.status).to eq(200) expect(response).to include_pagination_headers @@ -73,7 +73,7 @@ describe API::Todos do context 'and using the state filter' do it 'filters based on state param' do - get api('/todos', john_doe), { state: 'done' } + get api('/todos', john_doe), params: { state: 'done' } expect(response.status).to eq(200) expect(response).to include_pagination_headers @@ -84,7 +84,7 @@ describe API::Todos do context 'and using the project filter' do it 'filters based on project_id param' do - get api('/todos', john_doe), { project_id: project_2.id } + get api('/todos', john_doe), params: { project_id: project_2.id } expect(response.status).to eq(200) expect(response).to include_pagination_headers @@ -95,7 +95,7 @@ describe API::Todos do context 'and using the group filter' do it 'filters based on project_id param' do - get api('/todos', john_doe), { group_id: group.id, sort: :target_id } + get api('/todos', john_doe), params: { group_id: group.id, sort: :target_id } expect(response.status).to eq(200) expect(response).to include_pagination_headers @@ -106,7 +106,7 @@ describe API::Todos do context 'and using the action filter' do it 'filters based on action param' do - get api('/todos', john_doe), { action: 'mentioned' } + get api('/todos', john_doe), params: { action: 'mentioned' } expect(response.status).to eq(200) expect(response).to include_pagination_headers diff --git a/spec/requests/api/triggers_spec.rb b/spec/requests/api/triggers_spec.rb index 658df6945d2..15dc901d06e 100644 --- a/spec/requests/api/triggers_spec.rb +++ b/spec/requests/api/triggers_spec.rb @@ -26,13 +26,13 @@ describe API::Triggers do context 'Handles errors' do it 'returns bad request if token is missing' do - post api("/projects/#{project.id}/trigger/pipeline"), ref: 'master' + post api("/projects/#{project.id}/trigger/pipeline"), params: { ref: 'master' } expect(response).to have_gitlab_http_status(400) end it 'returns not found if project is not found' do - post api('/projects/0/trigger/pipeline'), options.merge(ref: 'master') + post api('/projects/0/trigger/pipeline'), params: options.merge(ref: 'master') expect(response).to have_gitlab_http_status(404) end @@ -42,7 +42,7 @@ describe API::Triggers do let(:pipeline) { project.ci_pipelines.last } it 'creates pipeline' do - post api("/projects/#{project.id}/trigger/pipeline"), options.merge(ref: 'master') + post api("/projects/#{project.id}/trigger/pipeline"), params: options.merge(ref: 'master') expect(response).to have_gitlab_http_status(201) expect(json_response).to include('id' => pipeline.id) @@ -52,7 +52,7 @@ describe API::Triggers do end it 'returns bad request with no pipeline created if there\'s no commit for that ref' do - post api("/projects/#{project.id}/trigger/pipeline"), options.merge(ref: 'other-branch') + post api("/projects/#{project.id}/trigger/pipeline"), params: options.merge(ref: 'other-branch') expect(response).to have_gitlab_http_status(400) expect(json_response['message']).to eq('base' => ["Reference not found"]) @@ -64,21 +64,21 @@ describe API::Triggers do end it 'validates variables to be a hash' do - post api("/projects/#{project.id}/trigger/pipeline"), options.merge(variables: 'value', ref: 'master') + post api("/projects/#{project.id}/trigger/pipeline"), params: options.merge(variables: 'value', ref: 'master') expect(response).to have_gitlab_http_status(400) expect(json_response['error']).to eq('variables is invalid') end it 'validates variables needs to be a map of key-valued strings' do - post api("/projects/#{project.id}/trigger/pipeline"), options.merge(variables: { key: %w(1 2) }, ref: 'master') + post api("/projects/#{project.id}/trigger/pipeline"), params: options.merge(variables: { key: %w(1 2) }, ref: 'master') expect(response).to have_gitlab_http_status(400) expect(json_response['message']).to eq('variables needs to be a map of key-valued strings') end it 'creates trigger request with variables' do - post api("/projects/#{project.id}/trigger/pipeline"), options.merge(variables: variables, ref: 'master') + post api("/projects/#{project.id}/trigger/pipeline"), params: options.merge(variables: variables, ref: 'master') expect(response).to have_gitlab_http_status(201) expect(pipeline.variables.map { |v| { v.key => v.value } }.last).to eq(variables) @@ -91,7 +91,7 @@ describe API::Triggers do end it 'creates pipeline' do - post api("/projects/#{project.id}/trigger/pipeline"), options.merge(ref: 'master') + post api("/projects/#{project.id}/trigger/pipeline"), params: options.merge(ref: 'master') expect(response).to have_gitlab_http_status(201) expect(json_response).to include('id' => pipeline.id) @@ -104,14 +104,14 @@ describe API::Triggers do context 'when triggering a pipeline from a trigger token' do it 'does not leak the presence of project when token is for different project' do - post api("/projects/#{project2.id}/ref/master/trigger/pipeline?token=#{trigger_token}"), { ref: 'refs/heads/other-branch' } + post api("/projects/#{project2.id}/ref/master/trigger/pipeline?token=#{trigger_token}"), params: { ref: 'refs/heads/other-branch' } expect(response).to have_gitlab_http_status(404) end it 'creates builds from the ref given in the URL, not in the body' do expect do - post api("/projects/#{project.id}/ref/master/trigger/pipeline?token=#{trigger_token}"), { ref: 'refs/heads/other-branch' } + post api("/projects/#{project.id}/ref/master/trigger/pipeline?token=#{trigger_token}"), params: { ref: 'refs/heads/other-branch' } end.to change(project.builds, :count).by(5) expect(response).to have_gitlab_http_status(201) @@ -122,7 +122,7 @@ describe API::Triggers do project.repository.create_file(user, '.gitlab/gitlabhq/new_feature.md', 'something valid', message: 'new_feature', branch_name: 'v.1-branch') expect do - post api("/projects/#{project.id}/ref/v.1-branch/trigger/pipeline?token=#{trigger_token}"), { ref: 'refs/heads/other-branch' } + post api("/projects/#{project.id}/ref/v.1-branch/trigger/pipeline?token=#{trigger_token}"), params: { ref: 'refs/heads/other-branch' } end.to change(project.builds, :count).by(4) expect(response).to have_gitlab_http_status(201) @@ -199,7 +199,7 @@ describe API::Triggers do it 'creates trigger' do expect do post api("/projects/#{project.id}/triggers", user), - description: 'trigger' + params: { description: 'trigger' } end.to change {project.triggers.count}.by(1) expect(response).to have_gitlab_http_status(201) @@ -219,7 +219,7 @@ describe API::Triggers do context 'authenticated user with invalid permissions' do it 'does not create trigger' do post api("/projects/#{project.id}/triggers", user2), - description: 'trigger' + params: { description: 'trigger' } expect(response).to have_gitlab_http_status(403) end @@ -228,7 +228,7 @@ describe API::Triggers do context 'unauthenticated user' do it 'does not create trigger' do post api("/projects/#{project.id}/triggers"), - description: 'trigger' + params: { description: 'trigger' } expect(response).to have_gitlab_http_status(401) end @@ -241,7 +241,7 @@ describe API::Triggers do it 'updates description' do put api("/projects/#{project.id}/triggers/#{trigger.id}", user), - description: new_description + params: { description: new_description } expect(response).to have_gitlab_http_status(200) expect(json_response).to include('description' => new_description) diff --git a/spec/requests/api/users_spec.rb b/spec/requests/api/users_spec.rb index bb913ae0e79..f3431e0be3d 100644 --- a/spec/requests/api/users_spec.rb +++ b/spec/requests/api/users_spec.rb @@ -43,7 +43,7 @@ describe API::Users do end it "returns the user when a valid `username` parameter is passed" do - get api("/users"), username: user.username + get api("/users"), params: { username: user.username } expect(response).to match_response_schema('public_api/v4/user/basics') expect(json_response.size).to eq(1) @@ -52,7 +52,7 @@ describe API::Users do end it "returns the user when a valid `username` parameter is passed (case insensitive)" do - get api("/users"), username: user.username.upcase + get api("/users"), params: { username: user.username.upcase } expect(response).to match_response_schema('public_api/v4/user/basics') expect(json_response.size).to eq(1) @@ -61,7 +61,7 @@ describe API::Users do end it "returns an empty response when an invalid `username` parameter is passed" do - get api("/users"), username: 'invalid' + get api("/users"), params: { username: 'invalid' } expect(response).to have_gitlab_http_status(200) expect(json_response).to be_an Array @@ -74,7 +74,7 @@ describe API::Users do end it "returns authorization error when the `username` parameter refers to an inaccessible user" do - get api("/users"), username: user.username + get api("/users"), params: { username: user.username } expect(response).to have_gitlab_http_status(403) end @@ -243,7 +243,7 @@ describe API::Users do admin user - get api('/users', admin), { order_by: 'id', sort: 'asc' } + get api('/users', admin), params: { order_by: 'id', sort: 'asc' } expect(response).to match_response_schema('public_api/v4/user/admins') expect(json_response.size).to eq(2) @@ -256,7 +256,7 @@ describe API::Users do user user_with_2fa = create(:user, :two_factor_via_otp) - get api('/users', admin), { two_factor: 'enabled' } + get api('/users', admin), params: { two_factor: 'enabled' } expect(response).to match_response_schema('public_api/v4/user/admins') expect(json_response.size).to eq(1) @@ -264,7 +264,7 @@ describe API::Users do end it 'returns 400 when provided incorrect sort params' do - get api('/users', admin), { order_by: 'magic', sort: 'asc' } + get api('/users', admin), params: { order_by: 'magic', sort: 'asc' } expect(response).to have_gitlab_http_status(400) end @@ -375,12 +375,12 @@ describe API::Users do it "creates user" do expect do - post api("/users", admin), attributes_for(:user, projects_limit: 3) + post api("/users", admin), params: attributes_for(:user, projects_limit: 3) end.to change { User.count }.by(1) end it "creates user with correct attributes" do - post api('/users', admin), attributes_for(:user, admin: true, can_create_group: true) + post api('/users', admin), params: attributes_for(:user, admin: true, can_create_group: true) expect(response).to have_gitlab_http_status(201) user_id = json_response['id'] new_user = User.find(user_id) @@ -393,13 +393,13 @@ describe API::Users do optional_attributes = { confirm: true } attributes = attributes_for(:user).merge(optional_attributes) - post api('/users', admin), attributes + post api('/users', admin), params: attributes expect(response).to have_gitlab_http_status(201) end it "creates non-admin user" do - post api('/users', admin), attributes_for(:user, admin: false, can_create_group: false) + post api('/users', admin), params: attributes_for(:user, admin: false, can_create_group: false) expect(response).to have_gitlab_http_status(201) user_id = json_response['id'] new_user = User.find(user_id) @@ -409,7 +409,7 @@ describe API::Users do end it "creates non-admin users by default" do - post api('/users', admin), attributes_for(:user) + post api('/users', admin), params: attributes_for(:user) expect(response).to have_gitlab_http_status(201) user_id = json_response['id'] new_user = User.find(user_id) @@ -418,12 +418,12 @@ describe API::Users do end it "returns 201 Created on success" do - post api("/users", admin), attributes_for(:user, projects_limit: 3) + post api("/users", admin), params: attributes_for(:user, projects_limit: 3) expect(response).to have_gitlab_http_status(201) end it 'creates non-external users by default' do - post api("/users", admin), attributes_for(:user) + post api("/users", admin), params: attributes_for(:user) expect(response).to have_gitlab_http_status(201) user_id = json_response['id'] @@ -433,7 +433,7 @@ describe API::Users do end it 'allows an external user to be created' do - post api("/users", admin), attributes_for(:user, external: true) + post api("/users", admin), params: attributes_for(:user, external: true) expect(response).to have_gitlab_http_status(201) user_id = json_response['id'] @@ -443,7 +443,7 @@ describe API::Users do end it "creates user with reset password" do - post api('/users', admin), attributes_for(:user, reset_password: true).except(:password) + post api('/users', admin), params: attributes_for(:user, reset_password: true).except(:password) expect(response).to have_gitlab_http_status(201) @@ -455,7 +455,7 @@ describe API::Users do end it "creates user with private profile" do - post api('/users', admin), attributes_for(:user, private_profile: true) + post api('/users', admin), params: attributes_for(:user, private_profile: true) expect(response).to have_gitlab_http_status(201) @@ -468,40 +468,44 @@ describe API::Users do it "does not create user with invalid email" do post api('/users', admin), - email: 'invalid email', - password: 'password', - name: 'test' + params: { + email: 'invalid email', + password: 'password', + name: 'test' + } expect(response).to have_gitlab_http_status(400) end it 'returns 400 error if name not given' do - post api('/users', admin), attributes_for(:user).except(:name) + post api('/users', admin), params: attributes_for(:user).except(:name) expect(response).to have_gitlab_http_status(400) end it 'returns 400 error if password not given' do - post api('/users', admin), attributes_for(:user).except(:password) + post api('/users', admin), params: attributes_for(:user).except(:password) expect(response).to have_gitlab_http_status(400) end it 'returns 400 error if email not given' do - post api('/users', admin), attributes_for(:user).except(:email) + post api('/users', admin), params: attributes_for(:user).except(:email) expect(response).to have_gitlab_http_status(400) end it 'returns 400 error if username not given' do - post api('/users', admin), attributes_for(:user).except(:username) + post api('/users', admin), params: attributes_for(:user).except(:username) expect(response).to have_gitlab_http_status(400) end it 'returns 400 error if user does not validate' do post api('/users', admin), - password: 'pass', - email: 'test@example.com', - username: 'test!', - name: 'test', - bio: 'g' * 256, - projects_limit: -1 + params: { + password: 'pass', + email: 'test@example.com', + username: 'test!', + name: 'test', + bio: 'g' * 256, + projects_limit: -1 + } expect(response).to have_gitlab_http_status(400) expect(json_response['message']['password']) .to eq(['is too short (minimum is 8 characters)']) @@ -514,26 +518,30 @@ describe API::Users do end it "is not available for non admin users" do - post api("/users", user), attributes_for(:user) + post api("/users", user), params: attributes_for(:user) expect(response).to have_gitlab_http_status(403) end context 'with existing user' do before do post api('/users', admin), - email: 'test@example.com', - password: 'password', - username: 'test', - name: 'foo' + params: { + email: 'test@example.com', + password: 'password', + username: 'test', + name: 'foo' + } end it 'returns 409 conflict error if user with same email exists' do expect do post api('/users', admin), - name: 'foo', - email: 'test@example.com', - password: 'password', - username: 'foo' + params: { + name: 'foo', + email: 'test@example.com', + password: 'password', + username: 'foo' + } end.to change { User.count }.by(0) expect(response).to have_gitlab_http_status(409) expect(json_response['message']).to eq('Email has already been taken') @@ -542,10 +550,12 @@ describe API::Users do it 'returns 409 conflict error if same username exists' do expect do post api('/users', admin), - name: 'foo', - email: 'foo@example.com', - password: 'password', - username: 'test' + params: { + name: 'foo', + email: 'foo@example.com', + password: 'password', + username: 'test' + } end.to change { User.count }.by(0) expect(response).to have_gitlab_http_status(409) expect(json_response['message']).to eq('Username has already been taken') @@ -554,17 +564,19 @@ describe API::Users do it 'returns 409 conflict error if same username exists (case insensitive)' do expect do post api('/users', admin), - name: 'foo', - email: 'foo@example.com', - password: 'password', - username: 'TEST' + params: { + name: 'foo', + email: 'foo@example.com', + password: 'password', + username: 'TEST' + } end.to change { User.count }.by(0) expect(response).to have_gitlab_http_status(409) expect(json_response['message']).to eq('Username has already been taken') end it 'creates user with new identity' do - post api("/users", admin), attributes_for(:user, provider: 'github', extern_uid: '67890') + post api("/users", admin), params: attributes_for(:user, provider: 'github', extern_uid: '67890') expect(response).to have_gitlab_http_status(201) expect(json_response['identities'].first['extern_uid']).to eq('67890') @@ -593,7 +605,7 @@ describe API::Users do let!(:admin_user) { create(:admin) } it "updates user with new bio" do - put api("/users/#{user.id}", admin), { bio: 'new test bio' } + put api("/users/#{user.id}", admin), params: { bio: 'new test bio' } expect(response).to have_gitlab_http_status(200) expect(json_response['bio']).to eq('new test bio') @@ -601,14 +613,14 @@ describe API::Users do end it "updates user with new password and forces reset on next login" do - put api("/users/#{user.id}", admin), password: '12345678' + put api("/users/#{user.id}", admin), params: { password: '12345678' } expect(response).to have_gitlab_http_status(200) expect(user.reload.password_expires_at).to be <= Time.now end it "updates user with organization" do - put api("/users/#{user.id}", admin), { organization: 'GitLab' } + put api("/users/#{user.id}", admin), params: { organization: 'GitLab' } expect(response).to have_gitlab_http_status(200) expect(json_response['organization']).to eq('GitLab') @@ -616,7 +628,7 @@ describe API::Users do end it 'updates user with avatar' do - put api("/users/#{user.id}", admin), { avatar: fixture_file_upload('spec/fixtures/banana_sample.gif', 'image/gif') } + put api("/users/#{user.id}", admin), params: { avatar: fixture_file_upload('spec/fixtures/banana_sample.gif', 'image/gif') } user.reload @@ -628,7 +640,7 @@ describe API::Users do it 'updates user with a new email' do old_email = user.email old_notification_email = user.notification_email - put api("/users/#{user.id}", admin), email: 'new@email.com' + put api("/users/#{user.id}", admin), params: { email: 'new@email.com' } user.reload @@ -640,7 +652,7 @@ describe API::Users do end it 'skips reconfirmation when requested' do - put api("/users/#{user.id}", admin), email: 'new@email.com', skip_reconfirmation: true + put api("/users/#{user.id}", admin), params: { email: 'new@email.com', skip_reconfirmation: true } user.reload @@ -650,7 +662,7 @@ describe API::Users do end it 'updates user with his own username' do - put api("/users/#{user.id}", admin), username: user.username + put api("/users/#{user.id}", admin), params: { username: user.username } expect(response).to have_gitlab_http_status(200) expect(json_response['username']).to eq(user.username) @@ -658,14 +670,14 @@ describe API::Users do end it "updates user's existing identity" do - put api("/users/#{omniauth_user.id}", admin), provider: 'ldapmain', extern_uid: '654321' + put api("/users/#{omniauth_user.id}", admin), params: { provider: 'ldapmain', extern_uid: '654321' } expect(response).to have_gitlab_http_status(200) expect(omniauth_user.reload.identities.first.extern_uid).to eq('654321') end it 'updates user with new identity' do - put api("/users/#{user.id}", admin), provider: 'github', extern_uid: 'john' + put api("/users/#{user.id}", admin), params: { provider: 'github', extern_uid: 'john' } expect(response).to have_gitlab_http_status(200) expect(user.reload.identities.first.extern_uid).to eq('john') @@ -673,14 +685,14 @@ describe API::Users do end it "updates admin status" do - put api("/users/#{user.id}", admin), { admin: true } + put api("/users/#{user.id}", admin), params: { admin: true } expect(response).to have_gitlab_http_status(200) expect(user.reload.admin).to eq(true) end it "updates external status" do - put api("/users/#{user.id}", admin), { external: true } + put api("/users/#{user.id}", admin), params: { external: true } expect(response.status).to eq 200 expect(json_response['external']).to eq(true) @@ -688,14 +700,14 @@ describe API::Users do end it "updates private profile" do - put api("/users/#{user.id}", admin), { private_profile: true } + put api("/users/#{user.id}", admin), params: { private_profile: true } expect(response).to have_gitlab_http_status(200) expect(user.reload.private_profile).to eq(true) end it "does not update admin status" do - put api("/users/#{admin_user.id}", admin), { can_create_group: false } + put api("/users/#{admin_user.id}", admin), params: { can_create_group: false } expect(response).to have_gitlab_http_status(200) expect(admin_user.reload.admin).to eq(true) @@ -703,7 +715,7 @@ describe API::Users do end it "does not allow invalid update" do - put api("/users/#{user.id}", admin), { email: 'invalid email' } + put api("/users/#{user.id}", admin), params: { email: 'invalid email' } expect(response).to have_gitlab_http_status(400) expect(user.reload.email).not_to eq('invalid email') @@ -712,7 +724,7 @@ describe API::Users do context 'when the current user is not an admin' do it "is not available" do expect do - put api("/users/#{user.id}", user), attributes_for(:user) + put api("/users/#{user.id}", user), params: attributes_for(:user) end.not_to change { user.reload.attributes } expect(response).to have_gitlab_http_status(403) @@ -720,7 +732,7 @@ describe API::Users do end it "returns 404 for non-existing user" do - put api("/users/999999", admin), { bio: 'update should fail' } + put api("/users/999999", admin), params: { bio: 'update should fail' } expect(response).to have_gitlab_http_status(404) expect(json_response['message']).to eq('404 User Not Found') @@ -734,12 +746,14 @@ describe API::Users do it 'returns 400 error if user does not validate' do put api("/users/#{user.id}", admin), - password: 'pass', - email: 'test@example.com', - username: 'test!', - name: 'test', - bio: 'g' * 256, - projects_limit: -1 + params: { + password: 'pass', + email: 'test@example.com', + username: 'test!', + name: 'test', + bio: 'g' * 256, + projects_limit: -1 + } expect(response).to have_gitlab_http_status(400) expect(json_response['message']['password']) .to eq(['is too short (minimum is 8 characters)']) @@ -752,26 +766,26 @@ describe API::Users do end it 'returns 400 if provider is missing for identity update' do - put api("/users/#{omniauth_user.id}", admin), extern_uid: '654321' + put api("/users/#{omniauth_user.id}", admin), params: { extern_uid: '654321' } expect(response).to have_gitlab_http_status(400) end it 'returns 400 if external UID is missing for identity update' do - put api("/users/#{omniauth_user.id}", admin), provider: 'ldap' + put api("/users/#{omniauth_user.id}", admin), params: { provider: 'ldap' } expect(response).to have_gitlab_http_status(400) end context "with existing user" do before do - post api("/users", admin), { email: 'test@example.com', password: 'password', username: 'test', name: 'test' } - post api("/users", admin), { email: 'foo@bar.com', password: 'password', username: 'john', name: 'john' } + post api("/users", admin), params: { email: 'test@example.com', password: 'password', username: 'test', name: 'test' } + post api("/users", admin), params: { email: 'foo@bar.com', password: 'password', username: 'john', name: 'john' } @user = User.all.last end it 'returns 409 conflict error if email address exists' do - put api("/users/#{@user.id}", admin), email: 'test@example.com' + put api("/users/#{@user.id}", admin), params: { email: 'test@example.com' } expect(response).to have_gitlab_http_status(409) expect(@user.reload.email).to eq(@user.email) @@ -779,7 +793,7 @@ describe API::Users do it 'returns 409 conflict error if username taken' do @user_id = User.all.last.id - put api("/users/#{@user.id}", admin), username: 'test' + put api("/users/#{@user.id}", admin), params: { username: 'test' } expect(response).to have_gitlab_http_status(409) expect(@user.reload.username).to eq(@user.username) @@ -787,7 +801,7 @@ describe API::Users do it 'returns 409 conflict error if username taken (case insensitive)' do @user_id = User.all.last.id - put api("/users/#{@user.id}", admin), username: 'TEST' + put api("/users/#{@user.id}", admin), params: { username: 'TEST' } expect(response).to have_gitlab_http_status(409) expect(@user.reload.username).to eq(@user.username) @@ -801,14 +815,14 @@ describe API::Users do end it "does not create invalid ssh key" do - post api("/users/#{user.id}/keys", admin), { title: "invalid key" } + post api("/users/#{user.id}/keys", admin), params: { title: "invalid key" } expect(response).to have_gitlab_http_status(400) expect(json_response['error']).to eq('key is missing') end it 'does not create key without title' do - post api("/users/#{user.id}/keys", admin), key: 'some key' + post api("/users/#{user.id}/keys", admin), params: { key: 'some key' } expect(response).to have_gitlab_http_status(400) expect(json_response['error']).to eq('title is missing') @@ -817,7 +831,7 @@ describe API::Users do it "creates ssh key" do key_attrs = attributes_for :key expect do - post api("/users/#{user.id}/keys", admin), key_attrs + post api("/users/#{user.id}/keys", admin), params: key_attrs end.to change { user.keys.count }.by(1) end @@ -909,7 +923,7 @@ describe API::Users do it 'creates GPG key' do key_attrs = attributes_for :gpg_key expect do - post api("/users/#{user.id}/gpg_keys", admin), key_attrs + post api("/users/#{user.id}/gpg_keys", admin), params: key_attrs expect(response).to have_gitlab_http_status(201) end.to change { user.gpg_keys.count }.by(1) @@ -1058,7 +1072,7 @@ describe API::Users do end it "does not create invalid email" do - post api("/users/#{user.id}/emails", admin), {} + post api("/users/#{user.id}/emails", admin), params: {} expect(response).to have_gitlab_http_status(400) expect(json_response['error']).to eq('email is missing') @@ -1067,7 +1081,7 @@ describe API::Users do it "creates unverified email" do email_attrs = attributes_for :email expect do - post api("/users/#{user.id}/emails", admin), email_attrs + post api("/users/#{user.id}/emails", admin), params: email_attrs end.to change { user.emails.count }.by(1) email = Email.find_by(user_id: user.id, email: email_attrs[:email]) @@ -1084,7 +1098,7 @@ describe API::Users do email_attrs = attributes_for :email email_attrs[:skip_confirmation] = true - post api("/users/#{user.id}/emails", admin), email_attrs + post api("/users/#{user.id}/emails", admin), params: email_attrs expect(response).to have_gitlab_http_status(201) @@ -1379,32 +1393,32 @@ describe API::Users do it "creates ssh key" do key_attrs = attributes_for :key expect do - post api("/user/keys", user), key_attrs + post api("/user/keys", user), params: key_attrs end.to change { user.keys.count }.by(1) expect(response).to have_gitlab_http_status(201) end it "returns a 401 error if unauthorized" do - post api("/user/keys"), title: 'some title', key: 'some key' + post api("/user/keys"), params: { title: 'some title', key: 'some key' } expect(response).to have_gitlab_http_status(401) end it "does not create ssh key without key" do - post api("/user/keys", user), title: 'title' + post api("/user/keys", user), params: { title: 'title' } expect(response).to have_gitlab_http_status(400) expect(json_response['error']).to eq('key is missing') end it 'does not create ssh key without title' do - post api('/user/keys', user), key: 'some key' + post api('/user/keys', user), params: { key: 'some key' } expect(response).to have_gitlab_http_status(400) expect(json_response['error']).to eq('title is missing') end it "does not create ssh key without title" do - post api("/user/keys", user), key: "somekey" + post api("/user/keys", user), params: { key: "somekey" } expect(response).to have_gitlab_http_status(400) end end @@ -1523,14 +1537,14 @@ describe API::Users do it 'creates a GPG key' do key_attrs = attributes_for :gpg_key expect do - post api('/user/gpg_keys', user), key_attrs + post api('/user/gpg_keys', user), params: key_attrs expect(response).to have_gitlab_http_status(201) end.to change { user.gpg_keys.count }.by(1) end it 'returns a 401 error if unauthorized' do - post api('/user/gpg_keys'), key: 'some key' + post api('/user/gpg_keys'), params: { key: 'some key' } expect(response).to have_gitlab_http_status(401) end @@ -1685,18 +1699,18 @@ describe API::Users do it "creates email" do email_attrs = attributes_for :email expect do - post api("/user/emails", user), email_attrs + post api("/user/emails", user), params: email_attrs end.to change { user.emails.count }.by(1) expect(response).to have_gitlab_http_status(201) end it "returns a 401 error if unauthorized" do - post api("/user/emails"), email: 'some email' + post api("/user/emails"), params: { email: 'some email' } expect(response).to have_gitlab_http_status(401) end it "does not create email with invalid email" do - post api("/user/emails", user), {} + post api("/user/emails", user), params: {} expect(response).to have_gitlab_http_status(400) expect(json_response['error']).to eq('email is missing') @@ -1864,14 +1878,14 @@ describe API::Users do describe 'PUT /user/status' do it 'saves the status' do - put api('/user/status', user), { emoji: 'smirk', message: 'hello world' } + put api('/user/status', user), params: { emoji: 'smirk', message: 'hello world' } expect(response).to have_gitlab_http_status(:success) expect(json_response['emoji']).to eq('smirk') end it 'renders errors when the status was invalid' do - put api('/user/status', user), { emoji: 'does not exist', message: 'hello world' } + put api('/user/status', user), params: { emoji: 'does not exist', message: 'hello world' } expect(response).to have_gitlab_http_status(400) expect(json_response['message']['emoji']).to be_present @@ -1950,8 +1964,10 @@ describe API::Users do it 'returns a 404 error if user not found' do post api("/users/#{not_existing_user_id}/impersonation_tokens", admin), - name: name, - expires_at: expires_at + params: { + name: name, + expires_at: expires_at + } expect(response).to have_gitlab_http_status(404) expect(json_response['message']).to eq('404 User Not Found') @@ -1959,8 +1975,10 @@ describe API::Users do it 'returns a 403 error when authenticated as normal user' do post api("/users/#{user.id}/impersonation_tokens", user), - name: name, - expires_at: expires_at + params: { + name: name, + expires_at: expires_at + } expect(response).to have_gitlab_http_status(403) expect(json_response['message']).to eq('403 Forbidden') @@ -1968,10 +1986,12 @@ describe API::Users do it 'creates a impersonation token' do post api("/users/#{user.id}/impersonation_tokens", admin), - name: name, - expires_at: expires_at, - scopes: scopes, - impersonation: impersonation + params: { + name: name, + expires_at: expires_at, + scopes: scopes, + impersonation: impersonation + } expect(response).to have_gitlab_http_status(201) expect(json_response['name']).to eq(name) diff --git a/spec/requests/api/variables_spec.rb b/spec/requests/api/variables_spec.rb index be333df1d78..cdac5b2f400 100644 --- a/spec/requests/api/variables_spec.rb +++ b/spec/requests/api/variables_spec.rb @@ -73,7 +73,7 @@ describe API::Variables do context 'authorized user with proper permissions' do it 'creates variable' do expect do - post api("/projects/#{project.id}/variables", user), key: 'TEST_VARIABLE_2', value: 'VALUE_2', protected: true + post api("/projects/#{project.id}/variables", user), params: { key: 'TEST_VARIABLE_2', value: 'VALUE_2', protected: true } end.to change {project.variables.count}.by(1) expect(response).to have_gitlab_http_status(201) @@ -84,7 +84,7 @@ describe API::Variables do it 'creates variable with optional attributes' do expect do - post api("/projects/#{project.id}/variables", user), key: 'TEST_VARIABLE_2', value: 'VALUE_2' + post api("/projects/#{project.id}/variables", user), params: { key: 'TEST_VARIABLE_2', value: 'VALUE_2' } end.to change {project.variables.count}.by(1) expect(response).to have_gitlab_http_status(201) @@ -95,7 +95,7 @@ describe API::Variables do it 'does not allow to duplicate variable key' do expect do - post api("/projects/#{project.id}/variables", user), key: variable.key, value: 'VALUE_2' + post api("/projects/#{project.id}/variables", user), params: { key: variable.key, value: 'VALUE_2' } end.to change {project.variables.count}.by(0) expect(response).to have_gitlab_http_status(400) @@ -125,7 +125,7 @@ describe API::Variables do initial_variable = project.variables.reload.first value_before = initial_variable.value - put api("/projects/#{project.id}/variables/#{variable.key}", user), value: 'VALUE_1_UP', protected: true + put api("/projects/#{project.id}/variables/#{variable.key}", user), params: { value: 'VALUE_1_UP', protected: true } updated_variable = project.variables.reload.first diff --git a/spec/requests/api/wikis_spec.rb b/spec/requests/api/wikis_spec.rb index 08bada44178..f5092e8e2b5 100644 --- a/spec/requests/api/wikis_spec.rb +++ b/spec/requests/api/wikis_spec.rb @@ -39,7 +39,7 @@ describe API::Wikis do end it 'returns the list of wiki pages with content' do - get api(url, user), with_content: 1 + get api(url, user), params: { with_content: 1 } expect(response).to have_gitlab_http_status(200) expect(json_response.size).to eq(2) @@ -74,7 +74,7 @@ describe API::Wikis do shared_examples_for 'creates wiki page' do it 'creates the wiki page' do - post(api(url, user), payload) + post(api(url, user), params: payload) expect(response).to have_gitlab_http_status(201) expect(json_response.size).to eq(4) @@ -89,7 +89,7 @@ describe API::Wikis do it "responds with validation error on empty #{part}" do payload.delete(part) - post(api(url, user), payload) + post(api(url, user), params: payload) expect(response).to have_gitlab_http_status(400) expect(json_response.size).to eq(1) @@ -143,7 +143,7 @@ describe API::Wikis do it 'pushes attachment to the wiki repository' do allow(SecureRandom).to receive(:hex).and_return('fixed_hex') - post(api(url, user), payload) + post(api(url, user), params: payload) expect(response).to have_gitlab_http_status(201) expect(json_response).to eq result_hash.deep_stringify_keys @@ -152,7 +152,7 @@ describe API::Wikis do it 'responds with validation error on empty file' do payload.delete(:file) - post(api(url, user), payload) + post(api(url, user), params: payload) expect(response).to have_gitlab_http_status(400) expect(json_response.size).to eq(1) @@ -162,7 +162,7 @@ describe API::Wikis do it 'responds with validation error on invalid temp file' do payload[:file] = { tempfile: '/etc/hosts' } - post(api(url, user), payload) + post(api(url, user), params: payload) expect(response).to have_gitlab_http_status(400) expect(json_response.size).to eq(1) @@ -395,7 +395,7 @@ describe API::Wikis do context 'when user is guest' do before do - post(api(url), payload) + post(api(url), params: payload) end include_examples '404 Project Not Found' @@ -404,7 +404,7 @@ describe API::Wikis do context 'when user is developer' do before do project.add_developer(user) - post(api(url, user), payload) + post(api(url, user), params: payload) end include_examples '403 Forbidden' @@ -413,7 +413,7 @@ describe API::Wikis do context 'when user is maintainer' do before do project.add_maintainer(user) - post(api(url, user), payload) + post(api(url, user), params: payload) end include_examples '403 Forbidden' @@ -425,7 +425,7 @@ describe API::Wikis do context 'when user is guest' do before do - post(api(url), payload) + post(api(url), params: payload) end include_examples '404 Project Not Found' @@ -453,7 +453,7 @@ describe API::Wikis do context 'when user is guest' do before do - post(api(url), payload) + post(api(url), params: payload) end include_examples '404 Project Not Found' @@ -487,7 +487,7 @@ describe API::Wikis do context 'when user is guest' do before do - put(api(url), payload) + put(api(url), params: payload) end include_examples '404 Project Not Found' @@ -497,7 +497,7 @@ describe API::Wikis do before do project.add_developer(user) - put(api(url, user), payload) + put(api(url, user), params: payload) end include_examples '403 Forbidden' @@ -507,7 +507,7 @@ describe API::Wikis do before do project.add_maintainer(user) - put(api(url, user), payload) + put(api(url, user), params: payload) end include_examples '403 Forbidden' @@ -519,7 +519,7 @@ describe API::Wikis do context 'when user is guest' do before do - put(api(url), payload) + put(api(url), params: payload) end include_examples '404 Project Not Found' @@ -529,7 +529,7 @@ describe API::Wikis do before do project.add_developer(user) - put(api(url, user), payload) + put(api(url, user), params: payload) end include_examples 'updates wiki page' @@ -545,7 +545,7 @@ describe API::Wikis do before do project.add_maintainer(user) - put(api(url, user), payload) + put(api(url, user), params: payload) end include_examples 'updates wiki page' @@ -563,7 +563,7 @@ describe API::Wikis do context 'when user is guest' do before do - put(api(url), payload) + put(api(url), params: payload) end include_examples '404 Project Not Found' @@ -573,7 +573,7 @@ describe API::Wikis do before do project.add_developer(user) - put(api(url, user), payload) + put(api(url, user), params: payload) end include_examples 'updates wiki page' @@ -589,7 +589,7 @@ describe API::Wikis do before do project.add_maintainer(user) - put(api(url, user), payload) + put(api(url, user), params: payload) end include_examples 'updates wiki page' @@ -606,7 +606,7 @@ describe API::Wikis do let(:project) { create(:project, :wiki_repo, namespace: group) } before do - put(api(url, user), payload) + put(api(url, user), params: payload) end include_examples 'updates wiki page' @@ -751,7 +751,7 @@ describe API::Wikis do context 'when user is guest' do before do - post(api(url), payload) + post(api(url), params: payload) end include_examples '404 Project Not Found' @@ -760,7 +760,7 @@ describe API::Wikis do context 'when user is developer' do before do project.add_developer(user) - post(api(url, user), payload) + post(api(url, user), params: payload) end include_examples '403 Forbidden' @@ -769,7 +769,7 @@ describe API::Wikis do context 'when user is maintainer' do before do project.add_maintainer(user) - post(api(url, user), payload) + post(api(url, user), params: payload) end include_examples '403 Forbidden' @@ -781,7 +781,7 @@ describe API::Wikis do context 'when user is guest' do before do - post(api(url), payload) + post(api(url), params: payload) end include_examples '404 Project Not Found' @@ -809,7 +809,7 @@ describe API::Wikis do context 'when user is guest' do before do - post(api(url), payload) + post(api(url), params: payload) end include_examples '404 Project Not Found' diff --git a/spec/requests/git_http_spec.rb b/spec/requests/git_http_spec.rb index 0dc459d9b5a..939e870ec53 100644 --- a/spec/requests/git_http_spec.rb +++ b/spec/requests/git_http_spec.rb @@ -726,7 +726,7 @@ describe 'Git HTTP requests' do let(:params) { { service: 'git-upload-pack' } } before do - get path, params + get path, params: params end it "redirects to the .git suffix version" do @@ -738,7 +738,7 @@ describe 'Git HTTP requests' do let(:params) { { service: 'git-receive-pack' } } before do - get path, params + get path, params: params end it "redirects to the .git suffix version" do @@ -750,7 +750,7 @@ describe 'Git HTTP requests' do let(:params) { { service: 'git-implode-pack' } } before do - get path, params + get path, params: params end it "redirects to the sign-in page" do diff --git a/spec/requests/jwt_controller_spec.rb b/spec/requests/jwt_controller_spec.rb index e042d772718..4bb3b848e17 100644 --- a/spec/requests/jwt_controller_spec.rb +++ b/spec/requests/jwt_controller_spec.rb @@ -11,7 +11,7 @@ describe JwtController do end context 'existing service' do - subject! { get '/jwt/auth', parameters } + subject! { get '/jwt/auth', params: parameters } it { expect(response).to have_gitlab_http_status(200) } @@ -29,9 +29,9 @@ describe JwtController do let(:headers) { { authorization: credentials('gitlab-ci-token', build.token) } } context 'project with enabled CI' do - subject! { get '/jwt/auth', parameters, headers } + subject! { get '/jwt/auth', params: parameters, headers: headers } - it { expect(service_class).to have_received(:new).with(project, nil, parameters) } + it { expect(service_class).to have_received(:new).with(project, nil, ActionController::Parameters.new(parameters).permit!) } end context 'project with disabled CI' do @@ -39,7 +39,7 @@ describe JwtController do project.project_feature.update_attribute(:builds_access_level, ProjectFeature::DISABLED) end - subject! { get '/jwt/auth', parameters, headers } + subject! { get '/jwt/auth', params: parameters, headers: headers } it { expect(response).to have_gitlab_http_status(401) } end @@ -53,11 +53,11 @@ describe JwtController do stub_container_registry_config(enabled: true) end - subject! { get '/jwt/auth', parameters, headers } + subject! { get '/jwt/auth', params: parameters, headers: headers } it 'authenticates correctly' do expect(response).to have_gitlab_http_status(200) - expect(service_class).to have_received(:new).with(nil, user, parameters) + expect(service_class).to have_received(:new).with(nil, user, ActionController::Parameters.new(parameters).permit!) end end end @@ -66,9 +66,9 @@ describe JwtController do let(:user) { create(:user) } let(:headers) { { authorization: credentials(user.username, user.password) } } - subject! { get '/jwt/auth', parameters, headers } + subject! { get '/jwt/auth', params: parameters, headers: headers } - it { expect(service_class).to have_received(:new).with(nil, user, parameters) } + it { expect(service_class).to have_received(:new).with(nil, user, ActionController::Parameters.new(parameters).permit!) } context 'when passing a flat array of scopes' do # We use this trick to make rails to generate a query_string: @@ -83,7 +83,7 @@ describe JwtController do end let(:service_parameters) do - { service: service_name, scopes: %w(scope1 scope2) } + ActionController::Parameters.new({ service: service_name, scopes: %w(scope1 scope2) }).permit! end it { expect(service_class).to have_received(:new).with(nil, user, service_parameters) } @@ -115,7 +115,7 @@ describe JwtController do context 'when internal auth is enabled' do it 'rejects the authorization attempt' do - get '/jwt/auth', parameters, headers + get '/jwt/auth', params: parameters, headers: headers expect(response).to have_gitlab_http_status(401) expect(response.body).not_to include('You must use a personal access token with \'api\' scope for Git over HTTP') @@ -125,7 +125,7 @@ describe JwtController do context 'when internal auth is disabled' do it 'rejects the authorization attempt with personal access token message' do allow_any_instance_of(ApplicationSetting).to receive(:password_authentication_enabled_for_git?) { false } - get '/jwt/auth', parameters, headers + get '/jwt/auth', params: parameters, headers: headers expect(response).to have_gitlab_http_status(401) expect(response.body).to include('You must use a personal access token with \'api\' scope for Git over HTTP') @@ -136,7 +136,7 @@ describe JwtController do context 'when using unauthenticated request' do it 'accepts the authorization attempt' do - get '/jwt/auth', parameters + get '/jwt/auth', params: parameters expect(response).to have_gitlab_http_status(200) end @@ -144,12 +144,12 @@ describe JwtController do it 'allows read access' do expect(service).to receive(:execute).with(authentication_abilities: Gitlab::Auth.read_authentication_abilities) - get '/jwt/auth', parameters + get '/jwt/auth', params: parameters end end context 'unknown service' do - subject! { get '/jwt/auth', service: 'unknown' } + subject! { get '/jwt/auth', params: { service: 'unknown' } } it { expect(response).to have_gitlab_http_status(404) } end diff --git a/spec/requests/lfs_http_spec.rb b/spec/requests/lfs_http_spec.rb index e349181b794..f1514e90eb2 100644 --- a/spec/requests/lfs_http_spec.rb +++ b/spec/requests/lfs_http_spec.rb @@ -79,7 +79,7 @@ describe 'Git LFS API and storage' do end it 'responds with a 501 message on download' do - get "#{project.http_url_to_repo}/gitlab-lfs/objects/#{sample_oid}", nil, headers + get "#{project.http_url_to_repo}/gitlab-lfs/objects/#{sample_oid}", params: {}, headers: headers expect(response).to have_gitlab_http_status(501) end @@ -97,7 +97,7 @@ describe 'Git LFS API and storage' do end it 'responds with a 501 message on download' do - get "#{project.http_url_to_repo}/gitlab-lfs/objects/#{sample_oid}", nil, headers + get "#{project.http_url_to_repo}/gitlab-lfs/objects/#{sample_oid}", params: {}, headers: headers expect(response).to have_gitlab_http_status(501) end @@ -123,7 +123,7 @@ describe 'Git LFS API and storage' do end it 'responds with a 403 message on download' do - get "#{project.http_url_to_repo}/gitlab-lfs/objects/#{sample_oid}", nil, headers + get "#{project.http_url_to_repo}/gitlab-lfs/objects/#{sample_oid}", params: {}, headers: headers expect(response).to have_gitlab_http_status(403) expect(json_response).to include('message' => 'Access forbidden. Check your access level.') @@ -143,7 +143,7 @@ describe 'Git LFS API and storage' do end it 'responds with a 200 message on download' do - get "#{project.http_url_to_repo}/gitlab-lfs/objects/#{sample_oid}", nil, headers + get "#{project.http_url_to_repo}/gitlab-lfs/objects/#{sample_oid}", params: {}, headers: headers expect(response).to have_gitlab_http_status(200) end @@ -172,7 +172,7 @@ describe 'Git LFS API and storage' do let(:authorization) { authorize_user } before do - get "#{project.http_url_to_repo}/info/lfs/objects/#{sample_oid}", nil, headers + get "#{project.http_url_to_repo}/info/lfs/objects/#{sample_oid}", params: {}, headers: headers end it_behaves_like 'a deprecated' @@ -197,7 +197,7 @@ describe 'Git LFS API and storage' do enable_lfs update_permissions before_get - get "#{project.http_url_to_repo}/gitlab-lfs/objects/#{sample_oid}", nil, headers + get "#{project.http_url_to_repo}/gitlab-lfs/objects/#{sample_oid}", params: {}, headers: headers end context 'and request comes from gitlab-workhorse' do @@ -1123,7 +1123,7 @@ describe 'Git LFS API and storage' do context 'with valid remote_id' do before do - fog_connection.directories.get('lfs-objects').files.create( + fog_connection.directories.new(key: 'lfs-objects').files.create( key: 'tmp/uploads/12312300', body: 'content' ) @@ -1347,8 +1347,9 @@ describe 'Git LFS API and storage' do context 'when pushing the same lfs object to the second project' do before do - put "#{second_project.http_url_to_repo}/gitlab-lfs/objects/#{sample_oid}/#{sample_size}", nil, - headers.merge('X-Gitlab-Lfs-Tmp' => lfs_tmp_file).compact + put "#{second_project.http_url_to_repo}/gitlab-lfs/objects/#{sample_oid}/#{sample_size}", + params: {}, + headers: headers.merge('X-Gitlab-Lfs-Tmp' => lfs_tmp_file).compact end it 'responds with status 200' do @@ -1366,7 +1367,7 @@ describe 'Git LFS API and storage' do authorize_headers = headers authorize_headers.merge!(workhorse_internal_api_request_header) if verified - put "#{project.http_url_to_repo}/gitlab-lfs/objects/#{sample_oid}/#{sample_size}/authorize", nil, authorize_headers + put "#{project.http_url_to_repo}/gitlab-lfs/objects/#{sample_oid}/#{sample_size}/authorize", params: {}, headers: authorize_headers end def put_finalize(lfs_tmp = lfs_tmp_file, with_tempfile: false, args: {}) @@ -1387,7 +1388,7 @@ describe 'Git LFS API and storage' do end def put_finalize_with_args(args) - put "#{project.http_url_to_repo}/gitlab-lfs/objects/#{sample_oid}/#{sample_size}", args, headers + put "#{project.http_url_to_repo}/gitlab-lfs/objects/#{sample_oid}/#{sample_size}", params: args, headers: headers end def lfs_tmp_file @@ -1420,7 +1421,10 @@ describe 'Git LFS API and storage' do end def post_lfs_json(url, body = nil, headers = nil) - post(url, body.try(:to_json), (headers || {}).merge('Content-Type' => LfsRequest::CONTENT_TYPE)) + params = body.try(:to_json) + headers = (headers || {}).merge('Content-Type' => LfsRequest::CONTENT_TYPE) + + post(url, params: params, headers: headers) end def json_response diff --git a/spec/requests/lfs_locks_api_spec.rb b/spec/requests/lfs_locks_api_spec.rb index a44b43a591f..28cb90e450e 100644 --- a/spec/requests/lfs_locks_api_spec.rb +++ b/spec/requests/lfs_locks_api_spec.rb @@ -146,11 +146,11 @@ describe 'Git LFS File Locking API' do end def post_lfs_json(url, body = nil, headers = nil) - post(url, body.try(:to_json), (headers || {}).merge('Content-Type' => LfsRequest::CONTENT_TYPE)) + post(url, params: body.try(:to_json), headers: (headers || {}).merge('Content-Type' => LfsRequest::CONTENT_TYPE)) end def do_get(url, params = nil, headers = nil) - get(url, (params || {}), (headers || {}).merge('Content-Type' => LfsRequest::CONTENT_TYPE)) + get(url, params: (params || {}), headers: (headers || {}).merge('Content-Type' => LfsRequest::CONTENT_TYPE)) end def json_response diff --git a/spec/requests/oauth_tokens_spec.rb b/spec/requests/oauth_tokens_spec.rb index 000c3a2b868..3873e754060 100644 --- a/spec/requests/oauth_tokens_spec.rb +++ b/spec/requests/oauth_tokens_spec.rb @@ -6,11 +6,13 @@ describe 'OAuth Tokens requests' do def request_access_token(user) post '/oauth/token', - grant_type: 'authorization_code', - code: generate_access_grant(user).token, - redirect_uri: application.redirect_uri, - client_id: application.uid, - client_secret: application.secret + params: { + grant_type: 'authorization_code', + code: generate_access_grant(user).token, + redirect_uri: application.redirect_uri, + client_id: application.uid, + client_secret: application.secret + } end def generate_access_grant(user) diff --git a/spec/requests/openid_connect_spec.rb b/spec/requests/openid_connect_spec.rb index b1cf7a531f4..2b148c1b563 100644 --- a/spec/requests/openid_connect_spec.rb +++ b/spec/requests/openid_connect_spec.rb @@ -47,15 +47,17 @@ describe 'OpenID Connect requests' do login_as user post '/oauth/token', - grant_type: 'authorization_code', - code: access_grant.token, - redirect_uri: application.redirect_uri, - client_id: application.uid, - client_secret: application.secret + params: { + grant_type: 'authorization_code', + code: access_grant.token, + redirect_uri: application.redirect_uri, + client_id: application.uid, + client_secret: application.secret + } end def request_user_info! - get '/oauth/userinfo', nil, 'Authorization' => "Bearer #{access_token.token}" + get '/oauth/userinfo', params: {}, headers: { 'Authorization' => "Bearer #{access_token.token}" } end context 'Application without OpenID scope' do @@ -102,7 +104,7 @@ describe 'OpenID Connect requests' do expect(json_response).to match(id_token_claims.merge(user_info_claims)) expected_groups = [group1.full_path, group3.full_path] - expected_groups << group4.full_path if Group.supports_nested_groups? + expected_groups << group4.full_path if Group.supports_nested_objects? expect(json_response['groups']).to match_array(expected_groups) end diff --git a/spec/requests/rack_attack_global_spec.rb b/spec/requests/rack_attack_global_spec.rb index c0a3ea397df..49021f5d1b7 100644 --- a/spec/requests/rack_attack_global_spec.rb +++ b/spec/requests/rack_attack_global_spec.rb @@ -177,7 +177,7 @@ describe 'Rack Attack global throttles' do context 'when the request is to the api internal endpoints' do it 'allows requests over the rate limit' do (1 + requests_per_period).times do - get url_api_internal, secret_token: Gitlab::Shell.secret_token + get url_api_internal, params: { secret_token: Gitlab::Shell.secret_token } expect(response).to have_http_status 200 end end @@ -345,7 +345,7 @@ describe 'Rack Attack global throttles' do end def api_get_args_with_token_headers(partial_url, token_headers) - ["/api/#{API::API.version}#{partial_url}", nil, token_headers] + ["/api/#{API::API.version}#{partial_url}", params: nil, headers: token_headers] end def rss_url(user) diff --git a/spec/requests/request_profiler_spec.rb b/spec/requests/request_profiler_spec.rb index 9afeb2983b0..284a51fcc32 100644 --- a/spec/requests/request_profiler_spec.rb +++ b/spec/requests/request_profiler_spec.rb @@ -18,7 +18,7 @@ describe 'Request Profiler' do path = "/#{project.full_path}" Timecop.freeze(time) do - get path, nil, 'X-Profile-Token' => Gitlab::RequestProfiler.profile_token + get path, params: {}, headers: { 'X-Profile-Token' => Gitlab::RequestProfiler.profile_token } end profile_path = "#{Gitlab.config.shared.path}/tmp/requests_profiles/#{path.tr('/', '|')}_#{time.to_i}.html" diff --git a/spec/rubocop/cop/inject_enterprise_edition_module_spec.rb b/spec/rubocop/cop/inject_enterprise_edition_module_spec.rb new file mode 100644 index 00000000000..08ffc3c3a53 --- /dev/null +++ b/spec/rubocop/cop/inject_enterprise_edition_module_spec.rb @@ -0,0 +1,133 @@ +# frozen_string_literal: true + +require 'spec_helper' +require 'rubocop' +require 'rubocop/rspec/support' +require_relative '../../../rubocop/cop/inject_enterprise_edition_module' + +describe RuboCop::Cop::InjectEnterpriseEditionModule do + include CopHelper + + subject(:cop) { described_class.new } + + it 'flags the use of `prepend EE` in the middle of a file' do + expect_offense(<<~SOURCE) + class Foo + prepend EE::Foo + ^^^^^^^^^^^^^^^ Injecting EE modules must be done on the last line of this file, outside of any class or module definitions + end + SOURCE + end + + it 'flags the use of `prepend ::EE` in the middle of a file' do + expect_offense(<<~SOURCE) + class Foo + prepend ::EE::Foo + ^^^^^^^^^^^^^^^^^ Injecting EE modules must be done on the last line of this file, outside of any class or module definitions + end + SOURCE + end + + it 'flags the use of `include EE` in the middle of a file' do + expect_offense(<<~SOURCE) + class Foo + include EE::Foo + ^^^^^^^^^^^^^^^ Injecting EE modules must be done on the last line of this file, outside of any class or module definitions + end + SOURCE + end + + it 'flags the use of `include ::EE` in the middle of a file' do + expect_offense(<<~SOURCE) + class Foo + include ::EE::Foo + ^^^^^^^^^^^^^^^^^ Injecting EE modules must be done on the last line of this file, outside of any class or module definitions + end + SOURCE + end + + it 'flags the use of `extend EE` in the middle of a file' do + expect_offense(<<~SOURCE) + class Foo + extend EE::Foo + ^^^^^^^^^^^^^^ Injecting EE modules must be done on the last line of this file, outside of any class or module definitions + end + SOURCE + end + + it 'flags the use of `extend ::EE` in the middle of a file' do + expect_offense(<<~SOURCE) + class Foo + extend ::EE::Foo + ^^^^^^^^^^^^^^^^ Injecting EE modules must be done on the last line of this file, outside of any class or module definitions + end + SOURCE + end + + it 'does not flag prepending of regular modules' do + expect_no_offenses(<<~SOURCE) + class Foo + prepend Foo + end + SOURCE + end + + it 'does not flag including of regular modules' do + expect_no_offenses(<<~SOURCE) + class Foo + include Foo + end + SOURCE + end + + it 'does not flag extending using regular modules' do + expect_no_offenses(<<~SOURCE) + class Foo + extend Foo + end + SOURCE + end + + it 'does not flag the use of `prepend EE` on the last line' do + expect_no_offenses(<<~SOURCE) + class Foo + end + + Foo.prepend(EE::Foo) + SOURCE + end + + it 'does not flag the use of `include EE` on the last line' do + expect_no_offenses(<<~SOURCE) + class Foo + end + + Foo.include(EE::Foo) + SOURCE + end + + it 'does not flag the use of `extend EE` on the last line' do + expect_no_offenses(<<~SOURCE) + class Foo + end + + Foo.extend(EE::Foo) + SOURCE + end + + it 'autocorrects offenses by just disabling the Cop' do + source = <<~SOURCE + class Foo + prepend EE::Foo + include Bar + end + SOURCE + + expect(autocorrect_source(source)).to eq(<<~SOURCE) + class Foo + prepend EE::Foo # rubocop: disable Cop/InjectEnterpriseEditionModule + include Bar + end + SOURCE + end +end diff --git a/spec/serializers/entity_date_helper_spec.rb b/spec/serializers/entity_date_helper_spec.rb index 36da8d33a44..ae0f917415c 100644 --- a/spec/serializers/entity_date_helper_spec.rb +++ b/spec/serializers/entity_date_helper_spec.rb @@ -50,7 +50,7 @@ describe EntityDateHelper do end context 'when less than 31 days remaining' do - let(:milestone_remaining) { date_helper_class.remaining_days_in_words(build_stubbed(:milestone, due_date: 12.days.from_now.utc)) } + let(:milestone_remaining) { date_helper_class.remaining_days_in_words(12.days.from_now.utc.to_date) } it 'returns days remaining' do expect(milestone_remaining).to eq("<strong>12</strong> days remaining") @@ -58,7 +58,7 @@ describe EntityDateHelper do end context 'when less than 1 year and more than 30 days remaining' do - let(:milestone_remaining) { date_helper_class.remaining_days_in_words(build_stubbed(:milestone, due_date: 2.months.from_now.utc)) } + let(:milestone_remaining) { date_helper_class.remaining_days_in_words(2.months.from_now.utc.to_date) } it 'returns months remaining' do expect(milestone_remaining).to eq("<strong>2</strong> months remaining") @@ -66,7 +66,7 @@ describe EntityDateHelper do end context 'when more than 1 year remaining' do - let(:milestone_remaining) { date_helper_class.remaining_days_in_words(build_stubbed(:milestone, due_date: (1.year.from_now + 2.days).utc)) } + let(:milestone_remaining) { date_helper_class.remaining_days_in_words((1.year.from_now + 2.days).utc.to_date) } it 'returns years remaining' do expect(milestone_remaining).to eq("<strong>1</strong> year remaining") @@ -74,7 +74,7 @@ describe EntityDateHelper do end context 'when milestone is expired' do - let(:milestone_remaining) { date_helper_class.remaining_days_in_words(build_stubbed(:milestone, due_date: 2.days.ago.utc)) } + let(:milestone_remaining) { date_helper_class.remaining_days_in_words(2.days.ago.utc.to_date) } it 'returns "Past due"' do expect(milestone_remaining).to eq("<strong>Past due</strong>") @@ -82,7 +82,7 @@ describe EntityDateHelper do end context 'when milestone has start_date in the future' do - let(:milestone_remaining) { date_helper_class.remaining_days_in_words(build_stubbed(:milestone, start_date: 2.days.from_now.utc)) } + let(:milestone_remaining) { date_helper_class.remaining_days_in_words(nil, 2.days.from_now.utc.to_date) } it 'returns "Upcoming"' do expect(milestone_remaining).to eq("<strong>Upcoming</strong>") @@ -90,7 +90,7 @@ describe EntityDateHelper do end context 'when milestone has start_date in the past' do - let(:milestone_remaining) { date_helper_class.remaining_days_in_words(build_stubbed(:milestone, start_date: 2.days.ago.utc)) } + let(:milestone_remaining) { date_helper_class.remaining_days_in_words(nil, 2.days.ago.utc.to_date) } it 'returns days elapsed' do expect(milestone_remaining).to eq("<strong>2</strong> days elapsed") diff --git a/spec/serializers/environment_entity_spec.rb b/spec/serializers/environment_entity_spec.rb index b7324a26ed2..791b64dc356 100644 --- a/spec/serializers/environment_entity_spec.rb +++ b/spec/serializers/environment_entity_spec.rb @@ -40,4 +40,34 @@ describe EnvironmentEntity do expect(subject).to include(:metrics_path) end end + + context 'with deployment platform' do + let(:project) { create(:project, :repository) } + let(:environment) { create(:environment, project: project) } + + context 'when deployment platform is a cluster' do + before do + create(:cluster, + :provided_by_gcp, + :project, + environment_scope: '*', + projects: [project]) + end + + it 'should include cluster_type' do + expect(subject).to include(:cluster_type) + expect(subject[:cluster_type]).to eq('project_type') + end + end + + context 'when deployment platform is a Kubernetes Service' do + before do + create(:kubernetes_service, project: project) + end + + it 'should not include cluster_type' do + expect(subject).not_to include(:cluster_type) + end + end + end end diff --git a/spec/serializers/issue_board_entity_spec.rb b/spec/serializers/issue_board_entity_spec.rb index 06d9d3657e6..f6fa2a794f6 100644 --- a/spec/serializers/issue_board_entity_spec.rb +++ b/spec/serializers/issue_board_entity_spec.rb @@ -3,21 +3,40 @@ require 'spec_helper' describe IssueBoardEntity do - let(:project) { create(:project) } - let(:resource) { create(:issue, project: project) } - let(:user) { create(:user) } - - let(:request) { double('request', current_user: user) } + let(:project) { create(:project) } + let(:resource) { create(:issue, project: project) } + let(:user) { create(:user) } + let(:milestone) { create(:milestone, project: project) } + let(:label) { create(:label, project: project, title: 'Test Label') } + let(:request) { double('request', current_user: user) } subject { described_class.new(resource, request: request).as_json } it 'has basic attributes' do expect(subject).to include(:id, :iid, :title, :confidential, :due_date, :project_id, :relative_position, - :project, :labels) + :labels, :assignees, project: hash_including(:id, :path)) end it 'has path and endpoints' do expect(subject).to include(:reference_path, :real_path, :issue_sidebar_endpoint, :toggle_subscription_endpoint, :assignable_labels_endpoint) end + + it 'has milestone attributes' do + resource.milestone = milestone + + expect(subject).to include(milestone: hash_including(:id, :title)) + end + + it 'has assignee attributes' do + resource.assignees = [user] + + expect(subject).to include(assignees: array_including(hash_including(:id, :name, :username, :avatar_url))) + end + + it 'has label attributes' do + resource.labels = [label] + + expect(subject).to include(labels: array_including(hash_including(:id, :title, :color, :description, :text_color, :priority))) + end end diff --git a/spec/serializers/issue_serializer_spec.rb b/spec/serializers/issue_serializer_spec.rb index e8c46c0cdee..b8255e004d0 100644 --- a/spec/serializers/issue_serializer_spec.rb +++ b/spec/serializers/issue_serializer_spec.rb @@ -20,11 +20,19 @@ describe IssueSerializer do context 'sidebar issue serialization' do let(:serializer) { 'sidebar' } - it 'matches sidebar issue json schema' do + it 'matches issue_sidebar json schema' do expect(json_entity).to match_schema('entities/issue_sidebar') end end + context 'sidebar extras issue serialization' do + let(:serializer) { 'sidebar_extras' } + + it 'matches issue_sidebar_extras json schema' do + expect(json_entity).to match_schema('entities/issue_sidebar_extras') + end + end + context 'board issue serialization' do let(:serializer) { 'board' } diff --git a/spec/serializers/merge_request_basic_serializer_spec.rb b/spec/serializers/merge_request_basic_serializer_spec.rb deleted file mode 100644 index 1fad8e6bc5d..00000000000 --- a/spec/serializers/merge_request_basic_serializer_spec.rb +++ /dev/null @@ -1,16 +0,0 @@ -require 'spec_helper' - -describe MergeRequestBasicSerializer do - let(:resource) { create(:merge_request) } - let(:user) { create(:user) } - - let(:json_entity) do - described_class.new(current_user: user) - .represent(resource, serializer: 'basic') - .with_indifferent_access - end - - it 'matches basic merge request json' do - expect(json_entity).to match_schema('entities/merge_request_basic') - end -end diff --git a/spec/serializers/merge_request_serializer_spec.rb b/spec/serializers/merge_request_serializer_spec.rb index b259cb92962..276e0f6ff3d 100644 --- a/spec/serializers/merge_request_serializer_spec.rb +++ b/spec/serializers/merge_request_serializer_spec.rb @@ -20,8 +20,16 @@ describe MergeRequestSerializer do context 'sidebar merge request serialization' do let(:serializer) { 'sidebar' } - it 'matches basic merge request json schema' do - expect(json_entity).to match_schema('entities/merge_request_basic') + it 'matches merge_request_sidebar json schema' do + expect(json_entity).to match_schema('entities/merge_request_sidebar') + end + end + + context 'sidebar_extras merge request serialization' do + let(:serializer) { 'sidebar_extras' } + + it 'matches merge_request_sidebar_extras json schema' do + expect(json_entity).to match_schema('entities/merge_request_sidebar_extras') end end diff --git a/spec/serializers/suggestion_entity_spec.rb b/spec/serializers/suggestion_entity_spec.rb new file mode 100644 index 00000000000..047571f161c --- /dev/null +++ b/spec/serializers/suggestion_entity_spec.rb @@ -0,0 +1,23 @@ +# frozen_string_literal: true + +require 'spec_helper' + +describe SuggestionEntity do + include RepoHelpers + + let(:user) { create(:user) } + let(:request) { double('request', current_user: user) } + let(:suggestion) { create(:suggestion) } + let(:entity) { described_class.new(suggestion, request: request) } + + subject { entity.as_json } + + it 'exposes correct attributes' do + expect(subject).to include(:id, :from_original_line, :to_original_line, :from_line, + :to_line, :appliable, :applied, :from_content, :to_content) + end + + it 'exposes current user abilities' do + expect(subject[:current_user]).to include(:can_apply) + end +end diff --git a/spec/services/auth/container_registry_authentication_service_spec.rb b/spec/services/auth/container_registry_authentication_service_spec.rb index f2e9799452a..8021bd338e0 100644 --- a/spec/services/auth/container_registry_authentication_service_spec.rb +++ b/spec/services/auth/container_registry_authentication_service_spec.rb @@ -5,7 +5,7 @@ describe Auth::ContainerRegistryAuthenticationService do let(:current_user) { nil } let(:current_params) { {} } let(:rsa_key) { OpenSSL::PKey::RSA.generate(512) } - let(:payload) { JWT.decode(subject[:token], rsa_key).first } + let(:payload) { JWT.decode(subject[:token], rsa_key, true, { algorithm: 'RS256' }).first } let(:authentication_abilities) do [:read_container_image, :create_container_image, :admin_container_image] diff --git a/spec/services/ci/create_pipeline_service_spec.rb b/spec/services/ci/create_pipeline_service_spec.rb index 8b8021ecbc8..87b60387c52 100644 --- a/spec/services/ci/create_pipeline_service_spec.rb +++ b/spec/services/ci/create_pipeline_service_spec.rb @@ -19,12 +19,14 @@ describe Ci::CreatePipelineService do ref: ref_name, trigger_request: nil, variables_attributes: nil, - merge_request: nil) + merge_request: nil, + push_options: nil) params = { ref: ref, before: '00000000', after: after, commits: [{ message: message }], - variables_attributes: variables_attributes } + variables_attributes: variables_attributes, + push_options: push_options } described_class.new(project, user, params).execute( source, trigger_request: trigger_request, merge_request: merge_request) @@ -357,6 +359,22 @@ describe Ci::CreatePipelineService do end end + context 'when push options contain ci.skip' do + let(:push_options) do + ['ci.skip', + 'another push option'] + end + + it 'creates a pipline in the skipped state' do + pipeline = execute_service(push_options: push_options) + + # TODO: DRY these up with "skips builds creation if the commit message" + expect(pipeline).to be_persisted + expect(pipeline.builds.any?).to be false + expect(pipeline.status).to eq("skipped") + end + end + context 'when there are no jobs for this pipeline' do before do config = YAML.dump({ test: { script: 'ls', only: ['feature'] } }) @@ -667,7 +685,7 @@ describe Ci::CreatePipelineService do stub_ci_pipeline_yaml_file(YAML.dump(config)) end - let(:ref_name) { 'feature' } + let(:ref_name) { 'refs/heads/feature' } context 'when source is merge request' do let(:source) { :merge_request } @@ -696,7 +714,7 @@ describe Ci::CreatePipelineService do let(:merge_request) do create(:merge_request, source_project: project, - source_branch: ref_name, + source_branch: Gitlab::Git.ref_name(ref_name), target_project: project, target_branch: 'master') end @@ -709,7 +727,7 @@ describe Ci::CreatePipelineService do end context 'when ref is tag' do - let(:ref_name) { 'v1.1.0' } + let(:ref_name) { 'refs/tags/v1.1.0' } it 'does not create a merge request pipeline' do expect(pipeline).not_to be_persisted @@ -721,7 +739,7 @@ describe Ci::CreatePipelineService do let(:merge_request) do create(:merge_request, source_project: project, - source_branch: ref_name, + source_branch: Gitlab::Git.ref_name(ref_name), target_project: target_project, target_branch: 'master') end @@ -786,7 +804,7 @@ describe Ci::CreatePipelineService do let(:merge_request) do create(:merge_request, source_project: project, - source_branch: ref_name, + source_branch: Gitlab::Git.ref_name(ref_name), target_project: project, target_branch: 'master') end @@ -840,6 +858,37 @@ describe Ci::CreatePipelineService do end end + context "when config uses variables for only keyword" do + let(:config) do + { + build: { + stage: 'build', + script: 'echo', + only: { + variables: %w($CI) + } + } + } + end + + context 'when merge request is specified' do + let(:merge_request) do + create(:merge_request, + source_project: project, + source_branch: ref_name, + target_project: project, + target_branch: 'master') + end + + it 'does not create a merge request pipeline' do + expect(pipeline).not_to be_persisted + + expect(pipeline.errors[:base]) + .to eq(['No stages / jobs for this pipeline.']) + end + end + end + context "when config has 'except: [tags]'" do let(:config) do { @@ -897,7 +946,7 @@ describe Ci::CreatePipelineService do let(:merge_request) do create(:merge_request, source_project: project, - source_branch: ref_name, + source_branch: Gitlab::Git.ref_name(ref_name), target_project: project, target_branch: 'master') end diff --git a/spec/services/ci/register_job_service_spec.rb b/spec/services/ci/register_job_service_spec.rb index 56e2a405bcd..9d65ac15213 100644 --- a/spec/services/ci/register_job_service_spec.rb +++ b/spec/services/ci/register_job_service_spec.rb @@ -244,7 +244,9 @@ module Ci context 'when first build is stalled' do before do - pending_job.update(lock_version: 0) + allow_any_instance_of(Ci::RegisterJobService).to receive(:assign_runner!).and_call_original + allow_any_instance_of(Ci::RegisterJobService).to receive(:assign_runner!) + .with(pending_job, anything).and_raise(ActiveRecord::StaleObjectError) end subject { described_class.new(specific_runner).execute } diff --git a/spec/services/clusters/gcp/finalize_creation_service_spec.rb b/spec/services/clusters/gcp/finalize_creation_service_spec.rb index d69678c1277..2664649df47 100644 --- a/spec/services/clusters/gcp/finalize_creation_service_spec.rb +++ b/spec/services/clusters/gcp/finalize_creation_service_spec.rb @@ -20,7 +20,7 @@ describe Clusters::Gcp::FinalizeCreationService, '#execute' do subject { described_class.new.execute(provider) } before do - allow(ClusterPlatformConfigureWorker).to receive(:perform_async) + allow(ClusterConfigureWorker).to receive(:perform_async) end shared_examples 'success' do @@ -43,8 +43,8 @@ describe Clusters::Gcp::FinalizeCreationService, '#execute' do expect(platform.token).to eq(token) end - it 'calls ClusterPlatformConfigureWorker in a ascync fashion' do - expect(ClusterPlatformConfigureWorker).to receive(:perform_async).with(cluster.id) + it 'calls ClusterConfigureWorker in a ascync fashion' do + expect(ClusterConfigureWorker).to receive(:perform_async).with(cluster.id) subject end diff --git a/spec/services/clusters/update_service_spec.rb b/spec/services/clusters/update_service_spec.rb index 73f9be242a3..b2e6ebecd4a 100644 --- a/spec/services/clusters/update_service_spec.rb +++ b/spec/services/clusters/update_service_spec.rb @@ -37,7 +37,7 @@ describe Clusters::UpdateService do end before do - allow(ClusterPlatformConfigureWorker).to receive(:perform_async) + allow(ClusterConfigureWorker).to receive(:perform_async) stub_kubeclient_get_namespace('https://kubernetes.example.com', namespace: 'my-namespace') end diff --git a/spec/services/create_release_service_spec.rb b/spec/services/create_release_service_spec.rb deleted file mode 100644 index ac0a0458f56..00000000000 --- a/spec/services/create_release_service_spec.rb +++ /dev/null @@ -1,34 +0,0 @@ -require 'spec_helper' - -describe CreateReleaseService do - let(:project) { create(:project, :repository) } - let(:user) { create(:user) } - let(:tag_name) { project.repository.tag_names.first } - let(:description) { 'Awesome release!' } - let(:service) { described_class.new(project, user) } - - it 'creates a new release' do - result = service.execute(tag_name, description) - expect(result[:status]).to eq(:success) - release = project.releases.find_by(tag: tag_name) - expect(release).not_to be_nil - expect(release.description).to eq(description) - end - - it 'raises an error if the tag does not exist' do - result = service.execute("foobar", description) - expect(result[:status]).to eq(:error) - end - - context 'there already exists a release on a tag' do - before do - service.execute(tag_name, description) - end - - it 'raises an error and does not update the release' do - result = service.execute(tag_name, 'The best release!') - expect(result[:status]).to eq(:error) - expect(project.releases.find_by(tag: tag_name).description).to eq(description) - end - end -end diff --git a/spec/services/groups/create_service_spec.rb b/spec/services/groups/create_service_spec.rb index 224e933bebc..fe6a8691ae0 100644 --- a/spec/services/groups/create_service_spec.rb +++ b/spec/services/groups/create_service_spec.rb @@ -55,7 +55,7 @@ describe Groups::CreateService, '#execute' do context 'when nested groups feature is disabled' do it 'does not save group and returns an error' do - allow(Group).to receive(:supports_nested_groups?).and_return(false) + allow(Group).to receive(:supports_nested_objects?).and_return(false) is_expected.not_to be_persisted expect(subject.errors[:parent_id]).to include('You don’t have permission to create a subgroup in this group.') @@ -66,7 +66,7 @@ describe Groups::CreateService, '#execute' do context 'when nested groups feature is enabled' do before do - allow(Group).to receive(:supports_nested_groups?).and_return(true) + allow(Group).to receive(:supports_nested_objects?).and_return(true) end context 'as guest' do diff --git a/spec/services/groups/nested_create_service_spec.rb b/spec/services/groups/nested_create_service_spec.rb index 86fdd43c1e5..75d6ddb0a2c 100644 --- a/spec/services/groups/nested_create_service_spec.rb +++ b/spec/services/groups/nested_create_service_spec.rb @@ -30,7 +30,7 @@ describe Groups::NestedCreateService do let(:params) { { group_path: 'a-group' } } before do - allow(Group).to receive(:supports_nested_groups?) { false } + allow(Group).to receive(:supports_nested_objects?) { false } end it 'creates the group' do diff --git a/spec/services/groups/transfer_service_spec.rb b/spec/services/groups/transfer_service_spec.rb index dd8a1cee074..6b48c993c57 100644 --- a/spec/services/groups/transfer_service_spec.rb +++ b/spec/services/groups/transfer_service_spec.rb @@ -9,7 +9,7 @@ describe Groups::TransferService, :postgresql do shared_examples 'ensuring allowed transfer for a group' do context 'with other database than PostgreSQL' do before do - allow(Group).to receive(:supports_nested_groups?).and_return(false) + allow(Group).to receive(:supports_nested_objects?).and_return(false) end it 'should return false' do diff --git a/spec/services/groups/update_service_spec.rb b/spec/services/groups/update_service_spec.rb index 84cfa53ea05..d87a7dd234d 100644 --- a/spec/services/groups/update_service_spec.rb +++ b/spec/services/groups/update_service_spec.rb @@ -56,7 +56,7 @@ describe Groups::UpdateService do create(:project, :private, group: internal_group) expect(TodosDestroyer::GroupPrivateWorker).to receive(:perform_in) - .with(1.hour, internal_group.id) + .with(Todo::WAIT_FOR_DELETE, internal_group.id) end it "changes permission level to private" do diff --git a/spec/services/issuable/bulk_update_service_spec.rb b/spec/services/issuable/bulk_update_service_spec.rb index f0b0f7956ce..ca366cdf1df 100644 --- a/spec/services/issuable/bulk_update_service_spec.rb +++ b/spec/services/issuable/bulk_update_service_spec.rb @@ -28,6 +28,33 @@ describe Issuable::BulkUpdateService do expect(project.issues.opened).to be_empty expect(project.issues.closed).not_to be_empty end + + context 'when issue for a different project is created' do + let(:private_project) { create(:project, :private) } + let(:issue) { create(:issue, project: private_project, author: user) } + + context 'when user has access to the project' do + it 'closes all issues passed' do + private_project.add_maintainer(user) + + bulk_update(issues + [issue], state_event: 'close') + + expect(project.issues.opened).to be_empty + expect(project.issues.closed).not_to be_empty + expect(private_project.issues.closed).not_to be_empty + end + end + + context 'when user does not have access to project' do + it 'only closes all issues that the user has access to' do + bulk_update(issues + [issue], state_event: 'close') + + expect(project.issues.opened).to be_empty + expect(project.issues.closed).not_to be_empty + expect(private_project.issues.closed).to be_empty + end + end + end end describe 'reopen issues' do diff --git a/spec/services/issuable/common_system_notes_service_spec.rb b/spec/services/issuable/common_system_notes_service_spec.rb index fa1a421d528..fa5d5ebac5c 100644 --- a/spec/services/issuable/common_system_notes_service_spec.rb +++ b/spec/services/issuable/common_system_notes_service_spec.rb @@ -5,7 +5,7 @@ describe Issuable::CommonSystemNotesService do let(:project) { create(:project) } let(:issuable) { create(:issue) } - describe '#execute' do + context 'on issuable update' do it_behaves_like 'system note creation', { title: 'New title' }, 'changed title' it_behaves_like 'system note creation', { description: 'New description' }, 'changed the description' it_behaves_like 'system note creation', { discussion_locked: true }, 'locked this issue' @@ -20,7 +20,7 @@ describe Issuable::CommonSystemNotesService do end it 'creates a resource label event' do - described_class.new(project, user).execute(issuable, []) + described_class.new(project, user).execute(issuable, old_labels: []) event = issuable.reload.resource_label_events.last expect(event).not_to be_nil @@ -68,4 +68,47 @@ describe Issuable::CommonSystemNotesService do end end end + + context 'on issuable create' do + let(:issuable) { build(:issue) } + + subject { described_class.new(project, user).execute(issuable, old_labels: [], is_update: false) } + + it 'does not create system note for title and description' do + issuable.save + + expect { subject }.not_to change { issuable.notes.count } + end + + it 'creates a resource label event for labels added' do + label = create(:label, project: project) + + issuable.labels << label + issuable.save + + expect { subject }.to change { issuable.resource_label_events.count }.from(0).to(1) + + event = issuable.reload.resource_label_events.last + + expect(event).not_to be_nil + expect(event.label_id).to eq label.id + expect(event.user_id).to eq user.id + end + + it 'creates a system note for milestone set' do + issuable.milestone = create(:milestone, project: project) + issuable.save + + expect { subject }.to change { issuable.notes.count }.from(0).to(1) + expect(issuable.notes.last.note).to match('changed milestone') + end + + it 'creates a system note for due_date set' do + issuable.due_date = Date.today + issuable.save + + expect { subject }.to change { issuable.notes.count }.from(0).to(1) + expect(issuable.notes.last.note).to match('changed due date') + end + end end diff --git a/spec/services/issues/update_service_spec.rb b/spec/services/issues/update_service_spec.rb index bd519e7f077..ce20bf2bef6 100644 --- a/spec/services/issues/update_service_spec.rb +++ b/spec/services/issues/update_service_spec.rb @@ -77,7 +77,7 @@ describe Issues::UpdateService, :mailer do end it 'enqueues ConfidentialIssueWorker when an issue is made confidential' do - expect(TodosDestroyer::ConfidentialIssueWorker).to receive(:perform_in).with(1.hour, issue.id) + expect(TodosDestroyer::ConfidentialIssueWorker).to receive(:perform_in).with(Todo::WAIT_FOR_DELETE, issue.id) update_issue(confidential: true) end diff --git a/spec/services/members/destroy_service_spec.rb b/spec/services/members/destroy_service_spec.rb index 0a5220c7c61..5aa7165e135 100644 --- a/spec/services/members/destroy_service_spec.rb +++ b/spec/services/members/destroy_service_spec.rb @@ -22,7 +22,7 @@ describe Members::DestroyService do shared_examples 'a service destroying a member' do before do type = member.is_a?(GroupMember) ? 'Group' : 'Project' - expect(TodosDestroyer::EntityLeaveWorker).to receive(:perform_in).with(1.hour, member.user_id, member.source_id, type) + expect(TodosDestroyer::EntityLeaveWorker).to receive(:perform_in).with(Todo::WAIT_FOR_DELETE, member.user_id, member.source_id, type) end it 'destroys the member' do diff --git a/spec/services/members/update_service_spec.rb b/spec/services/members/update_service_spec.rb index 6d19a95ffeb..599ed39ca37 100644 --- a/spec/services/members/update_service_spec.rb +++ b/spec/services/members/update_service_spec.rb @@ -20,11 +20,28 @@ describe Members::UpdateService do shared_examples 'a service updating a member' do it 'updates the member' do + expect(TodosDestroyer::EntityLeaveWorker).not_to receive(:perform_in).with(Todo::WAIT_FOR_DELETE, member.user_id, member.source_id, source.class.name) + updated_member = described_class.new(current_user, params).execute(member, permission: permission) expect(updated_member).to be_valid expect(updated_member.access_level).to eq(Gitlab::Access::MAINTAINER) end + + context 'when member is downgraded to guest' do + let(:params) do + { access_level: Gitlab::Access::GUEST } + end + + it 'schedules to delete confidential todos' do + expect(TodosDestroyer::EntityLeaveWorker).to receive(:perform_in).with(Todo::WAIT_FOR_DELETE, member.user_id, member.source_id, source.class.name).once + + updated_member = described_class.new(current_user, params).execute(member, permission: permission) + + expect(updated_member).to be_valid + expect(updated_member.access_level).to eq(Gitlab::Access::GUEST) + end + end end before do diff --git a/spec/services/merge_requests/build_service_spec.rb b/spec/services/merge_requests/build_service_spec.rb index 1894d8c8d0e..536d0d345a4 100644 --- a/spec/services/merge_requests/build_service_spec.rb +++ b/spec/services/merge_requests/build_service_spec.rb @@ -3,6 +3,7 @@ require 'spec_helper' describe MergeRequests::BuildService do using RSpec::Parameterized::TableSyntax include RepoHelpers + include ProjectForksHelper let(:project) { create(:project, :repository) } let(:source_project) { nil } @@ -49,7 +50,7 @@ describe MergeRequests::BuildService do describe '#execute' do it 'calls the compare service with the correct arguments' do - allow_any_instance_of(described_class).to receive(:branches_valid?).and_return(true) + allow_any_instance_of(described_class).to receive(:projects_and_branches_valid?).and_return(true) expect(CompareService).to receive(:new) .with(project, Gitlab::Git::BRANCH_REF_PREFIX + source_branch) .and_call_original @@ -393,11 +394,27 @@ describe MergeRequests::BuildService do end end + context 'target_project is set but repo is not accessible by current_user' do + let(:target_project) do + create(:project, :public, :repository, repository_access_level: ProjectFeature::PRIVATE) + end + + it 'sets target project correctly' do + expect(merge_request.target_project).to eq(project) + end + end + context 'source_project is set and accessible by current_user' do let(:source_project) { create(:project, :public, :repository)} let(:commits) { Commit.decorate([commit_1], project) } - it 'sets target project correctly' do + before do + # To create merge requests _from_ a project the user needs at least + # developer access + source_project.add_developer(user) + end + + it 'sets source project correctly' do expect(merge_request.source_project).to eq(source_project) end end @@ -406,11 +423,43 @@ describe MergeRequests::BuildService do let(:source_project) { create(:project, :private, :repository)} let(:commits) { Commit.decorate([commit_1], project) } - it 'sets target project correctly' do + it 'sets source project correctly' do + expect(merge_request.source_project).to eq(project) + end + end + + context 'source_project is set but the user cannot create merge requests from the project' do + let(:source_project) do + create(:project, :public, :repository, merge_requests_access_level: ProjectFeature::PRIVATE) + end + + it 'sets the source_project correctly' do expect(merge_request.source_project).to eq(project) end end + context 'target_project is not in the fork network of source_project' do + let(:target_project) { create(:project, :public, :repository) } + + it 'adds an error to the merge request' do + expect(merge_request.errors[:validate_fork]).to contain_exactly('Source project is not a fork of the target project') + end + end + + context 'target_project is in the fork network of source project but no longer accessible' do + let!(:project) { fork_project(target_project, user, namespace: user.namespace, repository: true) } + let(:source_project) { project } + let(:target_project) { create(:project, :public, :repository) } + + before do + target_project.update(visibility_level: Gitlab::VisibilityLevel::PRIVATE) + end + + it 'sets the target_project correctly' do + expect(merge_request.target_project).to eq(project) + end + end + context 'when specifying target branch in the description' do let(:description) { "A merge request targeting another branch\n\n/target_branch with-codeowners" } diff --git a/spec/services/notes/update_service_spec.rb b/spec/services/notes/update_service_spec.rb index 533dcdcd6cd..fd9bff46a06 100644 --- a/spec/services/notes/update_service_spec.rb +++ b/spec/services/notes/update_service_spec.rb @@ -20,6 +20,29 @@ describe Notes::UpdateService do @note.reload end + context 'suggestions' do + it 'refreshes note suggestions' do + markdown = <<-MARKDOWN.strip_heredoc + ```suggestion + foo + ``` + + ```suggestion + bar + ``` + MARKDOWN + + suggestion = create(:suggestion) + note = suggestion.note + + expect { described_class.new(project, user, note: markdown).execute(note) } + .to change { note.suggestions.count }.from(1).to(2) + + expect(note.suggestions.order(:relative_order).map(&:to_content)) + .to eq([" foo\n", " bar\n"]) + end + end + context 'todos' do let!(:todo) { create(:todo, :assigned, user: user, project: project, target: issue, author: user2) } diff --git a/spec/services/notification_service_spec.rb b/spec/services/notification_service_spec.rb index 68ac3a00ab0..d20e712d365 100644 --- a/spec/services/notification_service_spec.rb +++ b/spec/services/notification_service_spec.rb @@ -2250,7 +2250,7 @@ describe NotificationService, :mailer do # Creates a nested group only if supported # to avoid errors on MySQL def create_nested_group - if Group.supports_nested_groups? + if Group.supports_nested_objects? parent_group = create(:group, :public) child_group = create(:group, :public, parent: parent_group) @@ -2277,7 +2277,7 @@ describe NotificationService, :mailer do end def add_member_for_parent_group(user, project) - return unless Group.supports_nested_groups? + return unless Group.supports_nested_objects? project.reload @@ -2285,13 +2285,13 @@ describe NotificationService, :mailer do end def should_email_nested_group_user(user, times: 1, recipients: email_recipients) - return unless Group.supports_nested_groups? + return unless Group.supports_nested_objects? should_email(user, times: 1, recipients: email_recipients) end def should_not_email_nested_group_user(user, recipients: email_recipients) - return unless Group.supports_nested_groups? + return unless Group.supports_nested_objects? should_not_email(user, recipients: email_recipients) end diff --git a/spec/services/preview_markdown_service_spec.rb b/spec/services/preview_markdown_service_spec.rb index b69977c812a..458cb8f1f31 100644 --- a/spec/services/preview_markdown_service_spec.rb +++ b/spec/services/preview_markdown_service_spec.rb @@ -19,6 +19,31 @@ describe PreviewMarkdownService do end end + describe 'suggestions' do + let(:params) { { text: "```suggestion\nfoo\n```", preview_suggestions: preview_suggestions } } + let(:service) { described_class.new(project, user, params) } + + context 'when preview markdown param is present' do + let(:preview_suggestions) { true } + + it 'returns users referenced in text' do + result = service.execute + + expect(result[:suggestions]).to eq(['foo']) + end + end + + context 'when preview markdown param is not present' do + let(:preview_suggestions) { false } + + it 'returns users referenced in text' do + result = service.execute + + expect(result[:suggestions]).to eq([]) + end + end + end + context 'new note with quick actions' do let(:issue) { create(:issue, project: project) } let(:params) do diff --git a/spec/services/projects/after_rename_service_spec.rb b/spec/services/projects/after_rename_service_spec.rb index b4718a07204..59c08b30f9f 100644 --- a/spec/services/projects/after_rename_service_spec.rb +++ b/spec/services/projects/after_rename_service_spec.rb @@ -99,6 +99,17 @@ describe Projects::AfterRenameService do expect(rugged_config['gitlab.fullpath']).to eq(project.full_path) end + + it 'updates storage location' do + allow(project_storage).to receive(:rename_repo).and_return(true) + + described_class.new(project).execute + + expect(project.project_repository).to have_attributes( + disk_path: project.disk_path, + shard_name: project.repository_storage + ) + end end context 'using hashed storage' do @@ -193,6 +204,15 @@ describe Projects::AfterRenameService do expect(rugged_config['gitlab.fullpath']).to eq(project.full_path) end + + it 'updates storage location' do + described_class.new(project).execute + + expect(project.project_repository).to have_attributes( + disk_path: project.disk_path, + shard_name: project.repository_storage + ) + end end end end diff --git a/spec/services/projects/lfs_pointers/lfs_download_service_spec.rb b/spec/services/projects/lfs_pointers/lfs_download_service_spec.rb index 6af5bfc7689..95c9b6e63b8 100644 --- a/spec/services/projects/lfs_pointers/lfs_download_service_spec.rb +++ b/spec/services/projects/lfs_pointers/lfs_download_service_spec.rb @@ -4,17 +4,15 @@ describe Projects::LfsPointers::LfsDownloadService do let(:project) { create(:project) } let(:oid) { '9e548e25631dd9ce6b43afd6359ab76da2819d6a5b474e66118c7819e1d8b3e8' } let(:download_link) { "http://gitlab.com/#{oid}" } - let(:lfs_content) do - <<~HEREDOC - whatever - HEREDOC - end + let(:lfs_content) { SecureRandom.random_bytes(10) } subject { described_class.new(project) } before do allow(project).to receive(:lfs_enabled?).and_return(true) WebMock.stub_request(:get, download_link).to_return(body: lfs_content) + + allow(Gitlab::CurrentSettings).to receive(:allow_local_requests_from_hooks_and_services?).and_return(false) end describe '#execute' do @@ -32,7 +30,7 @@ describe Projects::LfsPointers::LfsDownloadService do it 'stores the content' do subject.execute(oid, download_link) - expect(File.read(LfsObject.first.file.file.file)).to eq lfs_content + expect(File.binread(LfsObject.first.file.file.file)).to eq lfs_content end end @@ -54,6 +52,61 @@ describe Projects::LfsPointers::LfsDownloadService do end end + context 'when localhost requests are allowed' do + let(:download_link) { 'http://192.168.2.120' } + + before do + allow(Gitlab::CurrentSettings).to receive(:allow_local_requests_from_hooks_and_services?).and_return(true) + end + + it 'downloads the file' do + expect(subject).to receive(:download_and_save_file).and_call_original + + expect { subject.execute(oid, download_link) }.to change { LfsObject.count }.by(1) + end + end + + context 'when a bad URL is used' do + where(download_link: ['/etc/passwd', 'ftp://example.com', 'http://127.0.0.2', 'http://192.168.2.120']) + + with_them do + it 'does not download the file' do + expect { subject.execute(oid, download_link) }.not_to change { LfsObject.count } + end + end + end + + context 'when the URL points to a redirected URL' do + context 'that is blocked' do + where(redirect_link: ['ftp://example.com', 'http://127.0.0.2', 'http://192.168.2.120']) + + with_them do + before do + WebMock.stub_request(:get, download_link).to_return(status: 301, headers: { 'Location' => redirect_link }) + end + + it 'does not follow the redirection' do + expect(Rails.logger).to receive(:error).with(/LFS file with oid #{oid} couldn't be downloaded/) + + expect { subject.execute(oid, download_link) }.not_to change { LfsObject.count } + end + end + end + + context 'that is valid' do + let(:redirect_link) { "http://example.com/"} + + before do + WebMock.stub_request(:get, download_link).to_return(status: 301, headers: { 'Location' => redirect_link }) + WebMock.stub_request(:get, redirect_link).to_return(body: lfs_content) + end + + it 'follows the redirection' do + expect { subject.execute(oid, download_link) }.to change { LfsObject.count }.from(0).to(1) + end + end + end + context 'when an lfs object with the same oid already exists' do before do create(:lfs_object, oid: 'oid') diff --git a/spec/services/projects/transfer_service_spec.rb b/spec/services/projects/transfer_service_spec.rb index 132ad9a2646..766276fdba3 100644 --- a/spec/services/projects/transfer_service_spec.rb +++ b/spec/services/projects/transfer_service_spec.rb @@ -63,6 +63,15 @@ describe Projects::TransferService do expect(rugged_config['gitlab.fullpath']).to eq "#{group.full_path}/#{project.path}" end + it 'updates storage location' do + transfer_project(project, user, group) + + expect(project.project_repository).to have_attributes( + disk_path: "#{group.full_path}/#{project.path}", + shard_name: project.repository_storage + ) + end + context 'new group has a kubernetes cluster' do let(:group_cluster) { create(:cluster, :group, :provided_by_gcp) } let(:group) { group_cluster.group } @@ -139,6 +148,17 @@ describe Projects::TransferService do expect(service).not_to receive(:execute_system_hooks) end end + + it 'does not update storage location' do + create(:project_repository, project: project) + + attempt_project_transfer + + expect(project.project_repository).to have_attributes( + disk_path: project.disk_path, + shard_name: project.repository_storage + ) + end end context 'namespace -> no namespace' do diff --git a/spec/services/projects/update_service_spec.rb b/spec/services/projects/update_service_spec.rb index d58ff2cedc0..8adfc63222e 100644 --- a/spec/services/projects/update_service_spec.rb +++ b/spec/services/projects/update_service_spec.rb @@ -41,7 +41,7 @@ describe Projects::UpdateService do end it 'updates the project to private' do - expect(TodosDestroyer::ProjectPrivateWorker).to receive(:perform_in).with(1.hour, project.id) + expect(TodosDestroyer::ProjectPrivateWorker).to receive(:perform_in).with(Todo::WAIT_FOR_DELETE, project.id) result = update_project(project, user, visibility_level: Gitlab::VisibilityLevel::PRIVATE) @@ -191,7 +191,7 @@ describe Projects::UpdateService do context 'when changing feature visibility to private' do it 'updates the visibility correctly' do expect(TodosDestroyer::PrivateFeaturesWorker) - .to receive(:perform_in).with(1.hour, project.id) + .to receive(:perform_in).with(Todo::WAIT_FOR_DELETE, project.id) result = update_project(project, user, project_feature_attributes: { issues_access_level: ProjectFeature::PRIVATE } diff --git a/spec/services/releases/create_service_spec.rb b/spec/services/releases/create_service_spec.rb new file mode 100644 index 00000000000..612e9f152e7 --- /dev/null +++ b/spec/services/releases/create_service_spec.rb @@ -0,0 +1,72 @@ +require 'spec_helper' + +describe Releases::CreateService do + let(:project) { create(:project, :repository) } + let(:user) { create(:user) } + let(:tag_name) { project.repository.tag_names.first } + let(:tag_sha) { project.repository.find_tag(tag_name).dereferenced_target.sha } + let(:name) { 'Bionic Beaver' } + let(:description) { 'Awesome release!' } + let(:params) { { tag: tag_name, name: name, description: description, ref: ref } } + let(:ref) { nil } + let(:service) { described_class.new(project, user, params) } + + before do + project.add_maintainer(user) + end + + describe '#execute' do + shared_examples 'a successful release creation' do + it 'creates a new release' do + result = service.execute + expect(result[:status]).to eq(:success) + expect(result[:tag]).not_to be_nil + expect(result[:release]).not_to be_nil + expect(result[:release].description).to eq(description) + expect(result[:release].name).to eq(name) + expect(result[:release].author).to eq(user) + expect(result[:release].sha).to eq(tag_sha) + end + end + + it_behaves_like 'a successful release creation' + + context 'when the tag does not exist' do + let(:tag_name) { 'non-exist-tag' } + + it 'raises an error' do + result = service.execute + + expect(result[:status]).to eq(:error) + end + end + + context 'when ref is provided' do + let(:ref) { 'master' } + let(:tag_name) { 'foobar' } + + it_behaves_like 'a successful release creation' + + it 'creates a tag if the tag does not exist' do + expect(project.repository.ref_exists?("refs/tags/#{tag_name}")).to be_falsey + + result = service.execute + expect(result[:status]).to eq(:success) + expect(result[:tag]).not_to be_nil + expect(result[:release]).not_to be_nil + end + end + + context 'there already exists a release on a tag' do + let!(:release) do + create(:release, project: project, tag: tag_name, description: description) + end + + it 'raises an error and does not update the release' do + result = service.execute + expect(result[:status]).to eq(:error) + expect(project.releases.find_by(tag: tag_name).description).to eq(description) + end + end + end +end diff --git a/spec/services/releases/destroy_service_spec.rb b/spec/services/releases/destroy_service_spec.rb new file mode 100644 index 00000000000..dd5b8708f36 --- /dev/null +++ b/spec/services/releases/destroy_service_spec.rb @@ -0,0 +1,61 @@ +require 'spec_helper' + +describe Releases::DestroyService do + let(:project) { create(:project, :repository) } + let(:mainatiner) { create(:user) } + let(:repoter) { create(:user) } + let(:tag) { 'v1.1.0' } + let!(:release) { create(:release, project: project, tag: tag) } + let(:service) { described_class.new(project, user, params) } + let(:params) { { tag: tag } } + let(:user) { mainatiner } + + before do + project.add_maintainer(mainatiner) + project.add_reporter(repoter) + end + + describe '#execute' do + subject { service.execute } + + context 'when there is a release' do + it 'removes the release' do + expect { subject }.to change { project.releases.count }.by(-1) + end + + it 'returns the destroyed object' do + is_expected.to include(status: :success, release: release) + end + end + + context 'when tag is not found' do + let(:tag) { 'v1.1.1' } + + it 'returns an error' do + is_expected.to include(status: :error, + message: 'Tag does not exist', + http_status: 404) + end + end + + context 'when release is not found' do + let!(:release) { } + + it 'returns an error' do + is_expected.to include(status: :error, + message: 'Release does not exist', + http_status: 404) + end + end + + context 'when user does not have permission' do + let(:user) { repoter } + + it 'returns an error' do + is_expected.to include(status: :error, + message: 'Access Denied', + http_status: 403) + end + end + end +end diff --git a/spec/services/releases/update_service_spec.rb b/spec/services/releases/update_service_spec.rb new file mode 100644 index 00000000000..6c68f364739 --- /dev/null +++ b/spec/services/releases/update_service_spec.rb @@ -0,0 +1,50 @@ +require 'spec_helper' + +describe Releases::UpdateService do + let(:project) { create(:project, :repository) } + let(:user) { create(:user) } + let(:new_name) { 'A new name' } + let(:new_description) { 'The best release!' } + let(:params) { { name: new_name, description: new_description, tag: tag_name } } + let(:service) { described_class.new(project, user, params) } + let!(:release) { create(:release, project: project, author: user, tag: tag_name) } + let(:tag_name) { 'v1.1.0' } + + before do + project.add_developer(user) + end + + describe '#execute' do + shared_examples 'a failed update' do + it 'raises an error' do + result = service.execute + expect(result[:status]).to eq(:error) + end + end + + it 'successfully updates an existing release' do + result = service.execute + expect(result[:status]).to eq(:success) + expect(result[:release].name).to eq(new_name) + expect(result[:release].description).to eq(new_description) + end + + context 'when the tag does not exists' do + let(:tag_name) { 'foobar' } + + it_behaves_like 'a failed update' + end + + context 'when the release does not exist' do + let!(:release) { } + + it_behaves_like 'a failed update' + end + + context 'with an invalid update' do + let(:new_description) { '' } + + it_behaves_like 'a failed update' + end + end +end diff --git a/spec/services/suggestions/apply_service_spec.rb b/spec/services/suggestions/apply_service_spec.rb new file mode 100644 index 00000000000..3a483717756 --- /dev/null +++ b/spec/services/suggestions/apply_service_spec.rb @@ -0,0 +1,229 @@ +# frozen_string_literal: true + +require 'spec_helper' + +describe Suggestions::ApplyService do + include ProjectForksHelper + + let(:project) { create(:project, :repository) } + let(:user) { create(:user, :commit_email) } + + let(:position) do + Gitlab::Diff::Position.new(old_path: "files/ruby/popen.rb", + new_path: "files/ruby/popen.rb", + old_line: nil, + new_line: 9, + diff_refs: merge_request.diff_refs) + end + + let(:suggestion) do + create(:suggestion, note: diff_note, + from_content: " raise RuntimeError, \"System commands must be given as an array of strings\"\n", + to_content: " raise RuntimeError, 'Explosion'\n # explosion?\n") + end + + subject { described_class.new(user) } + + context 'patch is appliable' do + let(:expected_content) do + <<-CONTENT.strip_heredoc + require 'fileutils' + require 'open3' + + module Popen + extend self + + def popen(cmd, path=nil) + unless cmd.is_a?(Array) + raise RuntimeError, 'Explosion' + # explosion? + end + + path ||= Dir.pwd + + vars = { + "PWD" => path + } + + options = { + chdir: path + } + + unless File.directory?(path) + FileUtils.mkdir_p(path) + end + + @cmd_output = "" + @cmd_status = 0 + + Open3.popen3(vars, *cmd, options) do |stdin, stdout, stderr, wait_thr| + @cmd_output << stdout.read + @cmd_output << stderr.read + @cmd_status = wait_thr.value.exitstatus + end + + return @cmd_output, @cmd_status + end + end + CONTENT + end + + context 'non-fork project' do + let(:merge_request) do + create(:merge_request, source_project: project, + target_project: project) + end + + let!(:diff_note) do + create(:diff_note_on_merge_request, noteable: merge_request, + position: position, + project: project) + end + + before do + project.add_maintainer(user) + end + + it 'updates the file with the new contents' do + subject.execute(suggestion) + + blob = project.repository.blob_at_branch(merge_request.source_branch, + position.new_path) + + expect(blob.data).to eq(expected_content) + end + + it 'returns success status' do + result = subject.execute(suggestion) + + expect(result[:status]).to eq(:success) + end + + it 'updates suggestion applied and commit_id columns' do + expect { subject.execute(suggestion) } + .to change(suggestion, :applied) + .from(false).to(true) + .and change(suggestion, :commit_id) + .from(nil) + end + + it 'created commit has users email and name' do + subject.execute(suggestion) + + commit = project.repository.commit + + expect(user.commit_email).not_to eq(user.email) + expect(commit.author_email).to eq(user.commit_email) + expect(commit.committer_email).to eq(user.commit_email) + expect(commit.author_name).to eq(user.name) + end + end + + context 'fork-project' do + let(:project) { create(:project, :public, :repository) } + + let(:forked_project) do + fork_project_with_submodules(project, user) + end + + let(:merge_request) do + create(:merge_request, + source_branch: 'conflict-resolvable-fork', source_project: forked_project, + target_branch: 'conflict-start', target_project: project) + end + + let!(:diff_note) do + create(:diff_note_on_merge_request, noteable: merge_request, position: position, project: project) + end + + before do + project.add_maintainer(user) + end + + it 'updates file in the source project' do + expect(Files::UpdateService).to receive(:new) + .with(merge_request.source_project, user, anything) + .and_call_original + + subject.execute(suggestion) + end + end + end + + context 'no permission' do + let(:merge_request) do + create(:merge_request, source_project: project, + target_project: project) + end + + let(:diff_note) do + create(:diff_note_on_merge_request, noteable: merge_request, + position: position, + project: project) + end + + context 'user cannot write in project repo' do + before do + project.add_reporter(user) + end + + it 'returns error' do + result = subject.execute(suggestion) + + expect(result).to eq(message: "You are not allowed to push into this branch", + status: :error) + end + end + end + + context 'patch is not appliable' do + let(:merge_request) do + create(:merge_request, source_project: project, + target_project: project) + end + + let(:diff_note) do + create(:diff_note_on_merge_request, noteable: merge_request, + position: position, + project: project) + end + + before do + project.add_maintainer(user) + end + + context 'suggestion was already applied' do + it 'returns success status' do + result = subject.execute(suggestion) + + expect(result[:status]).to eq(:success) + end + end + + context 'note is outdated' do + before do + allow(diff_note).to receive(:active?) { false } + end + + it 'returns error message' do + result = subject.execute(suggestion) + + expect(result).to eq(message: 'Suggestion is not appliable', + status: :error) + end + end + + context 'suggestion was already applied' do + before do + suggestion.update!(applied: true, commit_id: 'sha') + end + + it 'returns error message' do + result = subject.execute(suggestion) + + expect(result).to eq(message: 'Suggestion is not appliable', + status: :error) + end + end + end +end diff --git a/spec/services/suggestions/create_service_spec.rb b/spec/services/suggestions/create_service_spec.rb new file mode 100644 index 00000000000..f1142c88a69 --- /dev/null +++ b/spec/services/suggestions/create_service_spec.rb @@ -0,0 +1,110 @@ +# frozen_string_literal: true + +require 'spec_helper' + +describe Suggestions::CreateService do + let(:project_with_repo) { create(:project, :repository) } + let(:merge_request) do + create(:merge_request, source_project: project_with_repo, + target_project: project_with_repo) + end + + let(:position) do + Gitlab::Diff::Position.new(old_path: "files/ruby/popen.rb", + new_path: "files/ruby/popen.rb", + old_line: nil, + new_line: 14, + diff_refs: merge_request.diff_refs) + end + + let(:markdown) do + <<-MARKDOWN.strip_heredoc + ```suggestion + foo + bar + ``` + + ``` + nothing + ``` + + ```suggestion + xpto + baz + ``` + + ```thing + this is not a suggestion, it's a thing + ``` + MARKDOWN + end + + subject { described_class.new(note) } + + describe '#execute' do + context 'should not try to parse suggestions' do + context 'when not a diff note for merge requests' do + let(:note) do + create(:diff_note_on_commit, project: project_with_repo, + note: markdown) + end + + it 'does not try to parse suggestions' do + expect(Banzai::SuggestionsParser).not_to receive(:parse) + + subject.execute + end + end + + context 'when diff note is not for text' do + let(:note) do + create(:diff_note_on_merge_request, project: project_with_repo, + noteable: merge_request, + position: position, + note: markdown) + end + + it 'does not try to parse suggestions' do + allow(note).to receive(:on_text?) { false } + + expect(Banzai::SuggestionsParser).not_to receive(:parse) + + subject.execute + end + end + end + + context 'should create suggestions' do + let(:note) do + create(:diff_note_on_merge_request, project: project_with_repo, + noteable: merge_request, + position: position, + note: markdown) + end + + context 'single line suggestions' do + it 'persists suggestion records' do + expect { subject.execute } + .to change { note.suggestions.count } + .from(0) + .to(2) + end + + it 'persists original from_content lines and suggested lines' do + subject.execute + + suggestions = note.suggestions.order(:relative_order) + + suggestion_1 = suggestions.first + suggestion_2 = suggestions.last + + expect(suggestion_1).to have_attributes(from_content: " vars = {\n", + to_content: " foo\n bar\n") + + expect(suggestion_2).to have_attributes(from_content: " vars = {\n", + to_content: " xpto\n baz\n") + end + end + end + end +end diff --git a/spec/services/todo_service_spec.rb b/spec/services/todo_service_spec.rb index c52515aefd8..253f2e44d10 100644 --- a/spec/services/todo_service_spec.rb +++ b/spec/services/todo_service_spec.rb @@ -19,6 +19,7 @@ describe TodoService do before do project.add_guest(guest) project.add_developer(author) + project.add_developer(assignee) project.add_developer(member) project.add_developer(john_doe) project.add_developer(skipped) diff --git a/spec/services/update_release_service_spec.rb b/spec/services/update_release_service_spec.rb deleted file mode 100644 index dc2d0e2d47a..00000000000 --- a/spec/services/update_release_service_spec.rb +++ /dev/null @@ -1,34 +0,0 @@ -require 'spec_helper' - -describe UpdateReleaseService do - let(:project) { create(:project, :repository) } - let(:user) { create(:user) } - let(:tag_name) { project.repository.tag_names.first } - let(:description) { 'Awesome release!' } - let(:new_description) { 'The best release!' } - let(:service) { described_class.new(project, user) } - - context 'with an existing release' do - let(:create_service) { CreateReleaseService.new(project, user) } - - before do - create_service.execute(tag_name, description) - end - - it 'successfully updates an existing release' do - result = service.execute(tag_name, new_description) - expect(result[:status]).to eq(:success) - expect(project.releases.find_by(tag: tag_name).description).to eq(new_description) - end - end - - it 'raises an error if the tag does not exist' do - result = service.execute("foobar", description) - expect(result[:status]).to eq(:error) - end - - it 'raises an error if the release does not exist' do - result = service.execute(tag_name, description) - expect(result[:status]).to eq(:error) - end -end diff --git a/spec/spec_helper.rb b/spec/spec_helper.rb index 3fedb9ed48c..89357056c93 100644 --- a/spec/spec_helper.rb +++ b/spec/spec_helper.rb @@ -115,7 +115,7 @@ RSpec.configure do |config| TestEnv.clean_test_path end - config.before(:example) do + config.before do # Enable all features by default for testing allow(Feature).to receive(:enabled?) { true } @@ -136,11 +136,11 @@ RSpec.configure do |config| RequestStore.clear! end - config.after(:example) do + config.after do Fog.unmock! if Fog.mock? end - config.after(:example) do + config.after do Gitlab::CurrentSettings.clear_in_memory_application_settings! end @@ -216,15 +216,19 @@ RSpec.configure do |config| # Each example may call `migrate!`, so we must ensure we are migrated down every time config.before(:each, :migration) do + use_fake_application_settings + schema_migrate_down! end config.after(:context, :migration) do schema_migrate_up! + + Gitlab::CurrentSettings.clear_in_memory_application_settings! end config.around(:each, :nested_groups) do |example| - example.run if Group.supports_nested_groups? + example.run if Group.supports_nested_objects? end config.around(:each, :postgresql) do |example| @@ -235,10 +239,6 @@ RSpec.configure do |config| example.run if Gitlab::Database.mysql? end - config.around(:each, :rails5) do |example| - example.run if Gitlab.rails5? - end - # This makes sure the `ApplicationController#can?` method is stubbed with the # original implementation for all view specs. config.before(:each, type: :view) do diff --git a/spec/support/api/boards_shared_examples.rb b/spec/support/api/boards_shared_examples.rb index 943c1f6ffd7..592962ebf7c 100644 --- a/spec/support/api/boards_shared_examples.rb +++ b/spec/support/api/boards_shared_examples.rb @@ -88,7 +88,7 @@ shared_examples_for 'group and project boards' do |route_definition, ee = false| let(:url) { "#{root_url}/#{board.id}/lists" } it 'creates a new issue board list for labels' do - post api(url, user), label_id: ux_label.id + post api(url, user), params: { label_id: ux_label.id } expect(response).to have_gitlab_http_status(201) expect(json_response['label']['name']).to eq(ux_label.title) @@ -96,13 +96,13 @@ shared_examples_for 'group and project boards' do |route_definition, ee = false| end it 'returns 400 when creating a new list if label_id is invalid' do - post api(url, user), label_id: 23423 + post api(url, user), params: { label_id: 23423 } expect(response).to have_gitlab_http_status(400) end it 'returns 403 for members with guest role' do - put api("#{url}/#{test_list.id}", guest), position: 1 + put api("#{url}/#{test_list.id}", guest), params: { position: 1 } expect(response).to have_gitlab_http_status(403) end @@ -112,23 +112,20 @@ shared_examples_for 'group and project boards' do |route_definition, ee = false| let(:url) { "#{root_url}/#{board.id}/lists" } it "updates a list" do - put api("#{url}/#{test_list.id}", user), - position: 1 + put api("#{url}/#{test_list.id}", user), params: { position: 1 } expect(response).to have_gitlab_http_status(200) expect(json_response['position']).to eq(1) end it "returns 404 error if list id not found" do - put api("#{url}/44444", user), - position: 1 + put api("#{url}/44444", user), params: { position: 1 } expect(response).to have_gitlab_http_status(404) end it "returns 403 for members with guest role" do - put api("#{url}/#{test_list.id}", guest), - position: 1 + put api("#{url}/#{test_list.id}", guest), params: { position: 1 } expect(response).to have_gitlab_http_status(403) end diff --git a/spec/support/api/milestones_shared_examples.rb b/spec/support/api/milestones_shared_examples.rb index 3bebb7aae90..5f709831ce1 100644 --- a/spec/support/api/milestones_shared_examples.rb +++ b/spec/support/api/milestones_shared_examples.rb @@ -45,7 +45,7 @@ shared_examples_for 'group and project milestones' do |route_definition| it 'returns an array of milestones specified by iids' do other_milestone = create(:milestone, project: try(:project), group: try(:group)) - get api(route, user), iids: [closed_milestone.iid, other_milestone.iid] + get api(route, user), params: { iids: [closed_milestone.iid, other_milestone.iid] } expect(response).to have_gitlab_http_status(200) expect(json_response).to be_an Array @@ -54,7 +54,7 @@ shared_examples_for 'group and project milestones' do |route_definition| end it 'does not return any milestone if none found' do - get api(route, user), iids: [Milestone.maximum(:iid).succ] + get api(route, user), params: { iids: [Milestone.maximum(:iid).succ] } expect(response).to have_gitlab_http_status(200) expect(json_response).to be_an Array @@ -73,7 +73,7 @@ shared_examples_for 'group and project milestones' do |route_definition| end it 'returns a milestone by searching for title' do - get api(route, user), search: 'version2' + get api(route, user), params: { search: 'version2' } expect(response).to have_gitlab_http_status(200) expect(response).to include_pagination_headers @@ -83,7 +83,7 @@ shared_examples_for 'group and project milestones' do |route_definition| end it 'returns a milestones by searching for description' do - get api(route, user), search: 'open' + get api(route, user), params: { search: 'open' } expect(response).to have_gitlab_http_status(200) expect(response).to include_pagination_headers @@ -117,7 +117,7 @@ shared_examples_for 'group and project milestones' do |route_definition| describe "POST #{route_definition}" do it 'creates a new milestone' do - post api(route, user), title: 'new milestone' + post api(route, user), params: { title: 'new milestone' } expect(response).to have_gitlab_http_status(201) expect(json_response['title']).to eq('new milestone') @@ -125,8 +125,7 @@ shared_examples_for 'group and project milestones' do |route_definition| end it 'creates a new milestone with description and dates' do - post api(route, user), - title: 'new milestone', description: 'release', due_date: '2013-03-02', start_date: '2013-02-02' + post api(route, user), params: { title: 'new milestone', description: 'release', due_date: '2013-03-02', start_date: '2013-02-02' } expect(response).to have_gitlab_http_status(201) expect(json_response['description']).to eq('release') @@ -141,14 +140,13 @@ shared_examples_for 'group and project milestones' do |route_definition| end it 'returns a 400 error if params are invalid (duplicate title)' do - post api(route, user), - title: milestone.title, description: 'release', due_date: '2013-03-02' + post api(route, user), params: { title: milestone.title, description: 'release', due_date: '2013-03-02' } expect(response).to have_gitlab_http_status(400) end it 'creates a new milestone with reserved html characters' do - post api(route, user), title: 'foo & bar 1.1 -> 2.2' + post api(route, user), params: { title: 'foo & bar 1.1 -> 2.2' } expect(response).to have_gitlab_http_status(201) expect(json_response['title']).to eq('foo & bar 1.1 -> 2.2') @@ -158,8 +156,7 @@ shared_examples_for 'group and project milestones' do |route_definition| describe "PUT #{route_definition}/:milestone_id" do it 'updates a milestone' do - put api(resource_route, user), - title: 'updated title' + put api(resource_route, user), params: { title: 'updated title' } expect(response).to have_gitlab_http_status(200) expect(json_response['title']).to eq('updated title') @@ -168,29 +165,27 @@ shared_examples_for 'group and project milestones' do |route_definition| it 'removes a due date if nil is passed' do milestone.update!(due_date: "2016-08-05") - put api(resource_route, user), due_date: nil + put api(resource_route, user), params: { due_date: nil } expect(response).to have_gitlab_http_status(200) expect(json_response['due_date']).to be_nil end it 'returns a 404 error if milestone id not found' do - put api("#{route}/1234", user), - title: 'updated title' + put api("#{route}/1234", user), params: { title: 'updated title' } expect(response).to have_gitlab_http_status(404) end it 'closes milestone' do - put api(resource_route, user), - state_event: 'close' + put api(resource_route, user), params: { state_event: 'close' } expect(response).to have_gitlab_http_status(200) expect(json_response['state']).to eq('closed') end it 'updates milestone with only start date' do - put api(resource_route, user), start_date: Date.tomorrow + put api(resource_route, user), params: { start_date: Date.tomorrow } expect(response).to have_gitlab_http_status(200) end diff --git a/spec/support/api/scopes/read_user_shared_examples.rb b/spec/support/api/scopes/read_user_shared_examples.rb index d7cef137989..683234264a8 100644 --- a/spec/support/api/scopes/read_user_shared_examples.rb +++ b/spec/support/api/scopes/read_user_shared_examples.rb @@ -77,7 +77,7 @@ shared_examples_for 'does not allow the "read_user" scope' do let(:token) { create(:personal_access_token, scopes: ['read_user'], user: user) } it 'returns a "403" response' do - post api_call.call(path, user, personal_access_token: token), attributes_for(:user, projects_limit: 3) + post api_call.call(path, user, personal_access_token: token), params: attributes_for(:user, projects_limit: 3) expect(response).to have_gitlab_http_status(403) end diff --git a/spec/support/api/time_tracking_shared_examples.rb b/spec/support/api/time_tracking_shared_examples.rb index fee464c15a3..e883d33f671 100644 --- a/spec/support/api/time_tracking_shared_examples.rb +++ b/spec/support/api/time_tracking_shared_examples.rb @@ -7,13 +7,13 @@ shared_examples 'time tracking endpoints' do |issuable_name| describe "POST /projects/:id/#{issuable_collection_name}/:#{issuable_name}_id/time_estimate" do context 'with an unauthorized user' do - subject { post(api("/projects/#{project.id}/#{issuable_collection_name}/#{issuable.iid}/time_estimate", non_member), duration: '1w') } + subject { post(api("/projects/#{project.id}/#{issuable_collection_name}/#{issuable.iid}/time_estimate", non_member), params: { duration: '1w' }) } it_behaves_like 'an unauthorized API user' end it "sets the time estimate for #{issuable_name}" do - post api("/projects/#{project.id}/#{issuable_collection_name}/#{issuable.iid}/time_estimate", user), duration: '1w' + post api("/projects/#{project.id}/#{issuable_collection_name}/#{issuable.iid}/time_estimate", user), params: { duration: '1w' } expect(response).to have_gitlab_http_status(200) expect(json_response['human_time_estimate']).to eq('1w') @@ -21,12 +21,12 @@ shared_examples 'time tracking endpoints' do |issuable_name| describe 'updating the current estimate' do before do - post api("/projects/#{project.id}/#{issuable_collection_name}/#{issuable.iid}/time_estimate", user), duration: '1w' + post api("/projects/#{project.id}/#{issuable_collection_name}/#{issuable.iid}/time_estimate", user), params: { duration: '1w' } end context 'when duration has a bad format' do it 'does not modify the original estimate' do - post api("/projects/#{project.id}/#{issuable_collection_name}/#{issuable.iid}/time_estimate", user), duration: 'foo' + post api("/projects/#{project.id}/#{issuable_collection_name}/#{issuable.iid}/time_estimate", user), params: { duration: 'foo' } expect(response).to have_gitlab_http_status(400) expect(issuable.reload.human_time_estimate).to eq('1w') @@ -35,7 +35,7 @@ shared_examples 'time tracking endpoints' do |issuable_name| context 'with a valid duration' do it 'updates the estimate' do - post api("/projects/#{project.id}/#{issuable_collection_name}/#{issuable.iid}/time_estimate", user), duration: '3w1h' + post api("/projects/#{project.id}/#{issuable_collection_name}/#{issuable.iid}/time_estimate", user), params: { duration: '3w1h' } expect(response).to have_gitlab_http_status(200) expect(issuable.reload.human_time_estimate).to eq('3w 1h') @@ -62,8 +62,7 @@ shared_examples 'time tracking endpoints' do |issuable_name| describe "POST /projects/:id/#{issuable_collection_name}/:#{issuable_name}_id/add_spent_time" do context 'with an unauthorized user' do subject do - post api("/projects/#{project.id}/#{issuable_collection_name}/#{issuable.iid}/add_spent_time", non_member), - duration: '2h' + post api("/projects/#{project.id}/#{issuable_collection_name}/#{issuable.iid}/add_spent_time", non_member), params: { duration: '2h' } end it_behaves_like 'an unauthorized API user' @@ -72,8 +71,7 @@ shared_examples 'time tracking endpoints' do |issuable_name| it "add spent time for #{issuable_name}" do Timecop.travel(1.minute.from_now) do expect do - post api("/projects/#{project.id}/#{issuable_collection_name}/#{issuable.iid}/add_spent_time", user), - duration: '2h' + post api("/projects/#{project.id}/#{issuable_collection_name}/#{issuable.iid}/add_spent_time", user), params: { duration: '2h' } end.to change { issuable.reload.updated_at } end @@ -89,8 +87,7 @@ shared_examples 'time tracking endpoints' do |issuable_name| end.to change { issuable.reload.updated_at } end - post api("/projects/#{project.id}/#{issuable_collection_name}/#{issuable.iid}/add_spent_time", user), - duration: '-1h' + post api("/projects/#{project.id}/#{issuable_collection_name}/#{issuable.iid}/add_spent_time", user), params: { duration: '-1h' } expect(response).to have_gitlab_http_status(201) expect(json_response['total_time_spent']).to eq(3600) @@ -103,8 +100,7 @@ shared_examples 'time tracking endpoints' do |issuable_name| Timecop.travel(1.minute.from_now) do expect do - post api("/projects/#{project.id}/#{issuable_collection_name}/#{issuable.iid}/add_spent_time", user), - duration: '-1w' + post api("/projects/#{project.id}/#{issuable_collection_name}/#{issuable.iid}/add_spent_time", user), params: { duration: '-1w' } end.not_to change { issuable.reload.updated_at } end diff --git a/spec/support/carrierwave.rb b/spec/support/carrierwave.rb index b4b016e408f..b376822d530 100644 --- a/spec/support/carrierwave.rb +++ b/spec/support/carrierwave.rb @@ -1,7 +1,7 @@ CarrierWave.root = File.expand_path('tmp/tests/public', Rails.root) RSpec.configure do |config| - config.after(:each) do + config.after do FileUtils.rm_rf(CarrierWave.root) end end diff --git a/spec/support/controllers/githubish_import_controller_shared_examples.rb b/spec/support/controllers/githubish_import_controller_shared_examples.rb index 140490f2dc2..697f999e4c4 100644 --- a/spec/support/controllers/githubish_import_controller_shared_examples.rb +++ b/spec/support/controllers/githubish_import_controller_shared_examples.rb @@ -17,7 +17,7 @@ shared_examples 'a GitHub-ish import controller: POST personal_access_token' do allow_any_instance_of(Gitlab::LegacyGithubImport::Client) .to receive(:user).and_return(true) - post :personal_access_token, personal_access_token: token + post :personal_access_token, params: { personal_access_token: token } expect(session[:"#{provider}_access_token"]).to eq(token) expect(controller).to redirect_to(status_import_url) @@ -29,7 +29,7 @@ shared_examples 'a GitHub-ish import controller: POST personal_access_token' do allow_any_instance_of(Gitlab::LegacyGithubImport::Client) .to receive(:user).and_return(true) - post :personal_access_token, personal_access_token: " #{token} " + post :personal_access_token, params: { personal_access_token: " #{token} " } expect(session[:"#{provider}_access_token"]).to eq(token) expect(controller).to redirect_to(status_import_url) @@ -214,7 +214,7 @@ shared_examples 'a GitHub-ish import controller: POST create' do expect(Gitlab::LegacyGithubImport::ProjectCreator) .to receive(:new).and_return(double(execute: project)) - expect { post :create, target_namespace: provider_repo.name, format: :json }.to change(Namespace, :count).by(1) + expect { post :create, params: { target_namespace: provider_repo.name }, format: :json }.to change(Namespace, :count).by(1) end it "takes the new namespace" do @@ -222,7 +222,7 @@ shared_examples 'a GitHub-ish import controller: POST create' do .to receive(:new).with(provider_repo, provider_repo.name, an_instance_of(Group), user, access_params, type: provider) .and_return(double(execute: project)) - post :create, target_namespace: provider_repo.name, format: :json + post :create, params: { target_namespace: provider_repo.name }, format: :json end end @@ -261,7 +261,7 @@ shared_examples 'a GitHub-ish import controller: POST create' do .to receive(:new).with(provider_repo, test_name, test_namespace, user, access_params, type: provider) .and_return(double(execute: project)) - post :create, { target_namespace: test_namespace.name, new_name: test_name, format: :json } + post :create, params: { target_namespace: test_namespace.name, new_name: test_name }, format: :json end it 'takes the selected name and default namespace' do @@ -269,7 +269,7 @@ shared_examples 'a GitHub-ish import controller: POST create' do .to receive(:new).with(provider_repo, test_name, user.namespace, user, access_params, type: provider) .and_return(double(execute: project)) - post :create, { new_name: test_name, format: :json } + post :create, params: { new_name: test_name }, format: :json end end @@ -288,7 +288,7 @@ shared_examples 'a GitHub-ish import controller: POST create' do .to receive(:new).with(provider_repo, test_name, nested_namespace, user, access_params, type: provider) .and_return(double(execute: project)) - post :create, { target_namespace: nested_namespace.full_path, new_name: test_name, format: :json } + post :create, params: { target_namespace: nested_namespace.full_path, new_name: test_name }, format: :json end end @@ -300,7 +300,7 @@ shared_examples 'a GitHub-ish import controller: POST create' do .to receive(:new).with(provider_repo, test_name, kind_of(Namespace), user, access_params, type: provider) .and_return(double(execute: project)) - post :create, { target_namespace: 'foo/bar', new_name: test_name, format: :json } + post :create, params: { target_namespace: 'foo/bar', new_name: test_name }, format: :json end it 'creates the namespaces' do @@ -308,7 +308,7 @@ shared_examples 'a GitHub-ish import controller: POST create' do .to receive(:new).with(provider_repo, test_name, kind_of(Namespace), user, access_params, type: provider) .and_return(double(execute: project)) - expect { post :create, { target_namespace: 'foo/bar', new_name: test_name, format: :json } } + expect { post :create, params: { target_namespace: 'foo/bar', new_name: test_name }, format: :json } .to change { Namespace.count }.by(2) end @@ -317,7 +317,7 @@ shared_examples 'a GitHub-ish import controller: POST create' do .to receive(:new).with(provider_repo, test_name, kind_of(Namespace), user, access_params, type: provider) .and_return(double(execute: project)) - post :create, { target_namespace: 'foo/bar', new_name: test_name, format: :json } + post :create, params: { target_namespace: 'foo/bar', new_name: test_name }, format: :json expect(Namespace.find_by_path_or_name('bar').parent.path).to eq('foo') end @@ -336,7 +336,7 @@ shared_examples 'a GitHub-ish import controller: POST create' do .to receive(:new).with(provider_repo, test_name, kind_of(Namespace), user, access_params, type: provider) .and_return(double(execute: project)) - post :create, { target_namespace: 'foo/foobar/bar', new_name: test_name, format: :json } + post :create, params: { target_namespace: 'foo/foobar/bar', new_name: test_name }, format: :json end it 'creates the namespaces' do @@ -344,7 +344,7 @@ shared_examples 'a GitHub-ish import controller: POST create' do .to receive(:new).with(provider_repo, test_name, kind_of(Namespace), user, access_params, type: provider) .and_return(double(execute: project)) - expect { post :create, { target_namespace: 'foo/foobar/bar', new_name: test_name, format: :json } } + expect { post :create, params: { target_namespace: 'foo/foobar/bar', new_name: test_name }, format: :json } .to change { Namespace.count }.by(2) end @@ -353,7 +353,7 @@ shared_examples 'a GitHub-ish import controller: POST create' do .to receive(:new).with(provider_repo, test_name, user.namespace, user, access_params, type: provider) .and_return(double(execute: build_stubbed(:project))) - expect { post :create, { target_namespace: "#{user.namespace_path}/test_group", new_name: test_name, format: :js } } + expect { post :create, params: { target_namespace: "#{user.namespace_path}/test_group", new_name: test_name }, format: :js } .not_to change { Namespace.count } end end @@ -367,7 +367,7 @@ shared_examples 'a GitHub-ish import controller: POST create' do .to receive(:new).with(provider_repo, test_name, user.namespace, user, access_params, type: provider) .and_return(double(execute: build_stubbed(:project))) - post :create, { target_namespace: 'foo/foobar/bar', new_name: test_name, format: :js } + post :create, params: { target_namespace: 'foo/foobar/bar', new_name: test_name }, format: :js end it 'does not create the namespaces' do @@ -375,7 +375,7 @@ shared_examples 'a GitHub-ish import controller: POST create' do .to receive(:new).with(provider_repo, test_name, kind_of(Namespace), user, access_params, type: provider) .and_return(double(execute: build_stubbed(:project))) - expect { post :create, { target_namespace: 'foo/foobar/bar', new_name: test_name, format: :js } } + expect { post :create, params: { target_namespace: 'foo/foobar/bar', new_name: test_name }, format: :js } .not_to change { Namespace.count } end end @@ -392,7 +392,7 @@ shared_examples 'a GitHub-ish import controller: POST create' do .to receive(:new).with(provider_repo, test_name, group, user, access_params, type: provider) .and_return(double(execute: build_stubbed(:project))) - post :create, { target_namespace: 'foo', new_name: test_name, format: :js } + post :create, params: { target_namespace: 'foo', new_name: test_name }, format: :js end end @@ -400,7 +400,7 @@ shared_examples 'a GitHub-ish import controller: POST create' do it 'returns 422 response' do other_namespace = create(:group, name: 'other_namespace') - post :create, { target_namespace: other_namespace.name, format: :json } + post :create, params: { target_namespace: other_namespace.name }, format: :json expect(response).to have_gitlab_http_status(422) end diff --git a/spec/support/controllers/sessionless_auth_controller_shared_examples.rb b/spec/support/controllers/sessionless_auth_controller_shared_examples.rb index 7e4958f177a..355555d9d19 100644 --- a/spec/support/controllers/sessionless_auth_controller_shared_examples.rb +++ b/spec/support/controllers/sessionless_auth_controller_shared_examples.rb @@ -16,14 +16,14 @@ shared_examples 'authenticates sessionless user' do |path, format, params| .and increment(:user_session_override_counter) .and increment(:user_sessionless_authentication_counter) - get path, default_params.merge(private_token: personal_access_token.token) + get path, params: default_params.merge(private_token: personal_access_token.token) expect(response).to have_gitlab_http_status(200) expect(controller.current_user).to eq(user) end it 'does not log the user in if page is public', if: params[:public] do - get path, default_params + get path, params: default_params expect(response).to have_gitlab_http_status(200) expect(controller.current_user).to be_nil @@ -37,7 +37,7 @@ shared_examples 'authenticates sessionless user' do |path, format, params| personal_access_token.update(scopes: [:read_user]) - get path, default_params.merge(private_token: personal_access_token.token) + get path, params: default_params.merge(private_token: personal_access_token.token) expect(response).not_to have_gitlab_http_status(200) end @@ -51,7 +51,7 @@ shared_examples 'authenticates sessionless user' do |path, format, params| .and increment(:user_sessionless_authentication_counter) @request.headers['PRIVATE-TOKEN'] = personal_access_token.token - get path, default_params + get path, params: default_params expect(response).to have_gitlab_http_status(200) end @@ -64,7 +64,7 @@ shared_examples 'authenticates sessionless user' do |path, format, params| .and increment(:user_session_override_counter) .and increment(:user_sessionless_authentication_counter) - get path, default_params.merge(feed_token: user.feed_token) + get path, params: default_params.merge(feed_token: user.feed_token) expect(response).to have_gitlab_http_status 200 end @@ -75,7 +75,7 @@ shared_examples 'authenticates sessionless user' do |path, format, params| expect(authentication_metrics) .to increment(:user_unauthenticated_counter) - get path, default_params.merge(feed_token: 'token') + get path, params: default_params.merge(feed_token: 'token') expect(response.status).not_to eq 200 end @@ -85,7 +85,7 @@ shared_examples 'authenticates sessionless user' do |path, format, params| expect(authentication_metrics) .to increment(:user_unauthenticated_counter) - get path, default_params.merge(private_token: 'token') + get path, params: default_params.merge(private_token: 'token') expect(response.status).not_to eq(200) end diff --git a/spec/support/db_cleaner.rb b/spec/support/db_cleaner.rb index 5edc5de2a09..34b9efaaecd 100644 --- a/spec/support/db_cleaner.rb +++ b/spec/support/db_cleaner.rb @@ -23,7 +23,7 @@ RSpec.configure do |config| DatabaseCleaner.clean_with(:deletion, cache_tables: false) end - config.before(:each) do + config.before do DatabaseCleaner.strategy = :transaction end @@ -39,11 +39,11 @@ RSpec.configure do |config| DatabaseCleaner.strategy = :deletion, { cache_tables: false } end - config.before(:each) do + config.before do DatabaseCleaner.start end - config.append_after(:each) do + config.append_after do DatabaseCleaner.clean end end diff --git a/spec/support/features/discussion_comments_shared_example.rb b/spec/support/features/discussion_comments_shared_example.rb index 922f3df144d..42a086d58d2 100644 --- a/spec/support/features/discussion_comments_shared_example.rb +++ b/spec/support/features/discussion_comments_shared_example.rb @@ -178,6 +178,16 @@ shared_examples 'discussion comments' do |resource_name| let(:note_id) { find("#{comments_selector} .note:first-child", match: :first)['data-note-id'] } let(:reply_id) { find("#{comments_selector} .note:last-child", match: :first)['data-note-id'] } + it 'can be replied to after resolving' do + click_button "Resolve discussion" + wait_for_requests + + refresh + wait_for_requests + + submit_reply('to reply or not reply') + end + it 'shows resolved discussion when toggled' do submit_reply('a') diff --git a/spec/support/features/variable_list_shared_examples.rb b/spec/support/features/variable_list_shared_examples.rb index bce1fb01355..0a464d77cb7 100644 --- a/spec/support/features/variable_list_shared_examples.rb +++ b/spec/support/features/variable_list_shared_examples.rb @@ -63,6 +63,52 @@ shared_examples 'variable list' do end end + context 'defaults to the application setting' do + context 'application setting is true' do + before do + stub_application_setting(protected_ci_variables: true) + + visit page_path + end + + it 'defaults to protected' do + page.within('.js-ci-variable-list-section .js-row:last-child') do + find('.js-ci-variable-input-key').set('key') + end + + values = all('.js-ci-variable-input-protected', visible: false).map(&:value) + + expect(values).to eq %w(false true true) + end + + it 'shows a message regarding the changed default' do + expect(page).to have_content 'Environment variables are configured by your administrator to be protected by default' + end + end + + context 'application setting is false' do + before do + stub_application_setting(protected_ci_variables: false) + + visit page_path + end + + it 'defaults to unprotected' do + page.within('.js-ci-variable-list-section .js-row:last-child') do + find('.js-ci-variable-input-key').set('key') + end + + values = all('.js-ci-variable-input-protected', visible: false).map(&:value) + + expect(values).to eq %w(false false false) + end + + it 'does not show a message regarding the default' do + expect(page).not_to have_content 'Environment variables are configured by your administrator to be protected by default' + end + end + end + it 'reveals and hides variables' do page.within('.js-ci-variable-list-section') do expect(first('.js-ci-variable-input-key').value).to eq(variable.key) diff --git a/spec/support/gitlab_stubs/gitlab_ci.yml b/spec/support/gitlab_stubs/gitlab_ci.yml index e55a61b2b94..f3755e52b2c 100644 --- a/spec/support/gitlab_stubs/gitlab_ci.yml +++ b/spec/support/gitlab_stubs/gitlab_ci.yml @@ -1,9 +1,8 @@ -image: ruby:2.1 +image: ruby:2.6 services: - postgres before_script: - - gem install bundler - bundle install - bundle exec rake db:create diff --git a/spec/support/helpers/fake_blob_helpers.rb b/spec/support/helpers/fake_blob_helpers.rb index bc9686ed9cf..801ca8b7412 100644 --- a/spec/support/helpers/fake_blob_helpers.rb +++ b/spec/support/helpers/fake_blob_helpers.rb @@ -23,7 +23,7 @@ module FakeBlobHelpers 0 end - def binary? + def binary_in_repo? @binary end diff --git a/spec/support/helpers/git_http_helpers.rb b/spec/support/helpers/git_http_helpers.rb index 9a5845af90c..cd49bb148f2 100644 --- a/spec/support/helpers/git_http_helpers.rb +++ b/spec/support/helpers/git_http_helpers.rb @@ -1,18 +1,18 @@ module GitHttpHelpers def clone_get(project, options = {}) - get "/#{project}/info/refs", { service: 'git-upload-pack' }, auth_env(*options.values_at(:user, :password, :spnego_request_token)) + get "/#{project}/info/refs", params: { service: 'git-upload-pack' }, headers: auth_env(*options.values_at(:user, :password, :spnego_request_token)) end def clone_post(project, options = {}) - post "/#{project}/git-upload-pack", {}, auth_env(*options.values_at(:user, :password, :spnego_request_token)) + post "/#{project}/git-upload-pack", headers: auth_env(*options.values_at(:user, :password, :spnego_request_token)) end def push_get(project, options = {}) - get "/#{project}/info/refs", { service: 'git-receive-pack' }, auth_env(*options.values_at(:user, :password, :spnego_request_token)) + get "/#{project}/info/refs", params: { service: 'git-receive-pack' }, headers: auth_env(*options.values_at(:user, :password, :spnego_request_token)) end def push_post(project, options = {}) - post "/#{project}/git-receive-pack", {}, auth_env(*options.values_at(:user, :password, :spnego_request_token)) + post "/#{project}/git-receive-pack", headers: auth_env(*options.values_at(:user, :password, :spnego_request_token)) end def download(project, user: nil, password: nil, spnego_request_token: nil) diff --git a/spec/support/helpers/graphql_helpers.rb b/spec/support/helpers/graphql_helpers.rb index 75827df80dc..dd32ea3985f 100644 --- a/spec/support/helpers/graphql_helpers.rb +++ b/spec/support/helpers/graphql_helpers.rb @@ -107,7 +107,7 @@ module GraphqlHelpers end def post_graphql(query, current_user: nil, variables: nil) - post api('/', current_user, version: 'graphql'), query: query, variables: variables + post api('/', current_user, version: 'graphql'), params: { query: query, variables: variables } end def post_graphql_mutation(mutation, current_user: nil) diff --git a/spec/support/helpers/kubernetes_helpers.rb b/spec/support/helpers/kubernetes_helpers.rb index 39bd305d88a..e7d97561bfc 100644 --- a/spec/support/helpers/kubernetes_helpers.rb +++ b/spec/support/helpers/kubernetes_helpers.rb @@ -58,6 +58,11 @@ module KubernetesHelpers .to_return(status: [status, "Internal Server Error"]) end + def stub_kubeclient_get_service_account(api_url, name, namespace: 'default') + WebMock.stub_request(:get, api_url + "/api/v1/namespaces/#{namespace}/serviceaccounts/#{name}") + .to_return(kube_response({})) + end + def stub_kubeclient_get_service_account_error(api_url, name, namespace: 'default', status: 404) WebMock.stub_request(:get, api_url + "/api/v1/namespaces/#{namespace}/serviceaccounts/#{name}") .to_return(status: [status, "Internal Server Error"]) @@ -73,6 +78,11 @@ module KubernetesHelpers .to_return(status: [500, "Internal Server Error"]) end + def stub_kubeclient_put_service_account(api_url, name, namespace: 'default') + WebMock.stub_request(:put, api_url + "/api/v1/namespaces/#{namespace}/serviceaccounts/#{name}") + .to_return(kube_response({})) + end + def stub_kubeclient_create_secret(api_url, namespace: 'default') WebMock.stub_request(:post, api_url + "/api/v1/namespaces/#{namespace}/secrets") .to_return(kube_response({})) @@ -93,6 +103,11 @@ module KubernetesHelpers .to_return(kube_response({})) end + def stub_kubeclient_get_role_binding(api_url, name, namespace: 'default') + WebMock.stub_request(:get, api_url + "/apis/rbac.authorization.k8s.io/v1/namespaces/#{namespace}/rolebindings/#{name}") + .to_return(kube_response({})) + end + def stub_kubeclient_get_role_binding_error(api_url, name, namespace: 'default', status: 404) WebMock.stub_request(:get, api_url + "/apis/rbac.authorization.k8s.io/v1/namespaces/#{namespace}/rolebindings/#{name}") .to_return(status: [status, "Internal Server Error"]) @@ -103,6 +118,11 @@ module KubernetesHelpers .to_return(kube_response({})) end + def stub_kubeclient_put_role_binding(api_url, name, namespace: 'default') + WebMock.stub_request(:put, api_url + "/apis/rbac.authorization.k8s.io/v1/namespaces/#{namespace}/rolebindings/#{name}") + .to_return(kube_response({})) + end + def stub_kubeclient_create_namespace(api_url) WebMock.stub_request(:post, api_url + "/api/v1/namespaces") .to_return(kube_response({})) diff --git a/spec/support/helpers/migrations_helpers.rb b/spec/support/helpers/migrations_helpers.rb index 5887c3eab74..cc1a28cb264 100644 --- a/spec/support/helpers/migrations_helpers.rb +++ b/spec/support/helpers/migrations_helpers.rb @@ -62,6 +62,22 @@ module MigrationsHelpers klass.reset_column_information end + # In some migration tests, we're using factories to create records, + # however those models might be depending on a schema version which + # doesn't have the columns we want in application_settings. + # In these cases, we'll need to use the fake application settings + # as if we have migrations pending + def use_fake_application_settings + # We stub this way because we can't stub on + # `current_application_settings` due to `method_missing` is + # depending on current_application_settings... + allow(ActiveRecord::Base.connection) + .to receive(:active?) + .and_return(false) + + stub_env('IN_MEMORY_APPLICATION_SETTINGS', 'false') + end + def previous_migration migrations.each_cons(2) do |previous, migration| break previous if migration.name == described_class.name diff --git a/spec/support/helpers/test_env.rb b/spec/support/helpers/test_env.rb index d52c40ff4f1..d352a7cdf1a 100644 --- a/spec/support/helpers/test_env.rb +++ b/spec/support/helpers/test_env.rb @@ -62,7 +62,8 @@ module TestEnv 'between-create-delete-modify-move' => '3f5f443', 'after-create-delete-modify-move' => 'ba3faa7', 'with-codeowners' => '219560e', - 'submodule_inside_folder' => 'b491b92' + 'submodule_inside_folder' => 'b491b92', + 'png-lfs' => 'fe42f41' }.freeze # gitlab-test-fork is a fork of gitlab-fork, but we don't necessarily diff --git a/spec/support/helpers/test_request_helpers.rb b/spec/support/helpers/test_request_helpers.rb index 187a0e07891..5a84d67bdfc 100644 --- a/spec/support/helpers/test_request_helpers.rb +++ b/spec/support/helpers/test_request_helpers.rb @@ -2,10 +2,6 @@ module TestRequestHelpers def test_request(remote_ip: '127.0.0.1') - if Gitlab.rails5? - ActionController::TestRequest.new({ remote_ip: remote_ip }, ActionController::TestSession.new) - else - ActionController::TestRequest.new(remote_ip: remote_ip) - end + ActionController::TestRequest.new({ remote_ip: remote_ip }, ActionController::TestSession.new) end end diff --git a/spec/support/import_export/export_file_helper.rb b/spec/support/import_export/export_file_helper.rb index a49036c3b80..ac320934f5a 100644 --- a/spec/support/import_export/export_file_helper.rb +++ b/spec/support/import_export/export_file_helper.rb @@ -133,6 +133,6 @@ module ExportFileHelper end def file_permissions(file) - File.stat(file).mode & 0777 + File.lstat(file).mode & 0777 end end diff --git a/spec/support/issuables_requiring_filter_shared_examples.rb b/spec/support/issuables_requiring_filter_shared_examples.rb index 439ef5ed92e..71bcc82ee58 100644 --- a/spec/support/issuables_requiring_filter_shared_examples.rb +++ b/spec/support/issuables_requiring_filter_shared_examples.rb @@ -10,6 +10,6 @@ shared_examples 'issuables requiring filter' do |action| it "loads issuables if at least one filter is set" do expect_any_instance_of(described_class).to receive(:issuables_collection).and_call_original - get action, author_id: user.id + get action, params: { author_id: user.id } end end diff --git a/spec/support/services/migrate_to_ghost_user_service_shared_examples.rb b/spec/support/services/migrate_to_ghost_user_service_shared_examples.rb index 1478c6b5a47..62ae95df8c0 100644 --- a/spec/support/services/migrate_to_ghost_user_service_shared_examples.rb +++ b/spec/support/services/migrate_to_ghost_user_service_shared_examples.rb @@ -86,7 +86,7 @@ shared_examples "migrating a deleted user's associated records to the ghost user end it "blocks the user before #{record_class_name} migration begins" do - expect(service).to receive("migrate_#{record_class_name.parameterize('_').pluralize}".to_sym) do + expect(service).to receive("migrate_#{record_class_name.parameterize(separator: '_').pluralize}".to_sym) do expect(user.reload).to be_blocked end diff --git a/spec/support/setup_builds_storage.rb b/spec/support/setup_builds_storage.rb index 2e7c88bfc09..1d2a4856724 100644 --- a/spec/support/setup_builds_storage.rb +++ b/spec/support/setup_builds_storage.rb @@ -11,7 +11,7 @@ RSpec.configure do |config| FileUtils.mkdir_p(builds_path) end - config.before(:each) do + config.before do FileUtils.rm_rf(builds_path) FileUtils.mkdir_p(builds_path) end diff --git a/spec/support/shared_examples/common_system_notes_examples.rb b/spec/support/shared_examples/common_system_notes_examples.rb index 96ef30b7513..da5a4f3e319 100644 --- a/spec/support/shared_examples/common_system_notes_examples.rb +++ b/spec/support/shared_examples/common_system_notes_examples.rb @@ -1,5 +1,5 @@ shared_examples 'system note creation' do |update_params, note_text| - subject { described_class.new(project, user).execute(issuable, [])} + subject { described_class.new(project, user).execute(issuable, old_labels: []) } before do issuable.assign_attributes(update_params) @@ -16,7 +16,7 @@ shared_examples 'system note creation' do |update_params, note_text| end shared_examples 'WIP notes creation' do |wip_action| - subject { described_class.new(project, user).execute(issuable, []) } + subject { described_class.new(project, user).execute(issuable, old_labels: []) } it 'creates WIP toggle and title change notes' do expect { subject }.to change { Note.count }.from(0).to(2) diff --git a/spec/support/shared_examples/controllers/issuable_notes_filter_shared_examples.rb b/spec/support/shared_examples/controllers/issuable_notes_filter_shared_examples.rb index 95e69328080..dbdca99b5aa 100644 --- a/spec/support/shared_examples/controllers/issuable_notes_filter_shared_examples.rb +++ b/spec/support/shared_examples/controllers/issuable_notes_filter_shared_examples.rb @@ -2,7 +2,7 @@ shared_examples 'issuable notes filter' do it 'sets discussion filter' do notes_filter = UserPreference::NOTES_FILTERS[:only_comments] - get :discussions, namespace_id: project.namespace, project_id: project, id: issuable.iid, notes_filter: notes_filter + get :discussions, params: { namespace_id: project.namespace, project_id: project, id: issuable.iid, notes_filter: notes_filter } expect(user.reload.notes_filter_for(issuable)).to eq(notes_filter) expect(UserPreference.count).to eq(1) @@ -13,7 +13,7 @@ shared_examples 'issuable notes filter' do expect_any_instance_of(issuable.class).to receive(:expire_note_etag_cache) - get :discussions, namespace_id: project.namespace, project_id: project, id: issuable.iid, notes_filter: notes_filter + get :discussions, params: { namespace_id: project.namespace, project_id: project, id: issuable.iid, notes_filter: notes_filter } end it 'does not expires notes e-tag cache for issuable if filter did not change' do @@ -22,14 +22,14 @@ shared_examples 'issuable notes filter' do expect_any_instance_of(issuable.class).not_to receive(:expire_note_etag_cache) - get :discussions, namespace_id: project.namespace, project_id: project, id: issuable.iid, notes_filter: notes_filter + get :discussions, params: { namespace_id: project.namespace, project_id: project, id: issuable.iid, notes_filter: notes_filter } end it 'does not set notes filter when database is in read only mode' do allow(Gitlab::Database).to receive(:read_only?).and_return(true) notes_filter = UserPreference::NOTES_FILTERS[:only_comments] - get :discussions, namespace_id: project.namespace, project_id: project, id: issuable.iid, notes_filter: notes_filter + get :discussions, params: { namespace_id: project.namespace, project_id: project, id: issuable.iid, notes_filter: notes_filter } expect(user.reload.notes_filter_for(issuable)).to eq(0) end @@ -37,7 +37,7 @@ shared_examples 'issuable notes filter' do it 'returns only user comments' do user.set_notes_filter(UserPreference::NOTES_FILTERS[:only_comments], issuable) - get :discussions, namespace_id: project.namespace, project_id: project, id: issuable.iid + get :discussions, params: { namespace_id: project.namespace, project_id: project, id: issuable.iid } discussions = JSON.parse(response.body) expect(discussions.count).to eq(1) @@ -47,7 +47,7 @@ shared_examples 'issuable notes filter' do it 'returns only activity notes' do user.set_notes_filter(UserPreference::NOTES_FILTERS[:only_activity], issuable) - get :discussions, namespace_id: project.namespace, project_id: project, id: issuable.iid + get :discussions, params: { namespace_id: project.namespace, project_id: project, id: issuable.iid } discussions = JSON.parse(response.body) expect(discussions.count).to eq(1) @@ -60,7 +60,7 @@ shared_examples 'issuable notes filter' do expect(ResourceEvents::MergeIntoNotesService).not_to receive(:new) - get :discussions, namespace_id: project.namespace, project_id: project, id: issuable.iid + get :discussions, params: { namespace_id: project.namespace, project_id: project, id: issuable.iid } end end end diff --git a/spec/support/shared_examples/controllers/set_sort_order_from_user_preference_shared_examples.rb b/spec/support/shared_examples/controllers/set_sort_order_from_user_preference_shared_examples.rb index b34948be670..d86838719d4 100644 --- a/spec/support/shared_examples/controllers/set_sort_order_from_user_preference_shared_examples.rb +++ b/spec/support/shared_examples/controllers/set_sort_order_from_user_preference_shared_examples.rb @@ -15,7 +15,7 @@ shared_examples 'set sort order from user preference' do expect_any_instance_of(UserPreference).not_to receive(:update_attribute).with(sorting_field, sorting_param) - get :index, namespace_id: project.namespace, project_id: project, sort: sorting_param + get :index, params: { namespace_id: project.namespace, project_id: project, sort: sorting_param } end end @@ -25,7 +25,7 @@ shared_examples 'set sort order from user preference' do expect_any_instance_of(UserPreference).to receive(:update_attribute).with(sorting_field, sorting_param) - get :index, namespace_id: project.namespace, project_id: project, sort: sorting_param + get :index, params: { namespace_id: project.namespace, project_id: project, sort: sorting_param } end end end diff --git a/spec/support/shared_examples/controllers/uploads_actions_shared_examples.rb b/spec/support/shared_examples/controllers/uploads_actions_shared_examples.rb index 7088fb1e5fb..59708173716 100644 --- a/spec/support/shared_examples/controllers/uploads_actions_shared_examples.rb +++ b/spec/support/shared_examples/controllers/uploads_actions_shared_examples.rb @@ -8,8 +8,7 @@ shared_examples 'handle uploads' do describe "POST #create" do context 'when a user is not authorized to upload a file' do it 'returns 404 status' do - post :create, params.merge(file: jpg, format: :json) - + post :create, params: params.merge(file: jpg), format: :json expect(response.status).to eq(404) end end @@ -22,7 +21,7 @@ shared_examples 'handle uploads' do context "without params['file']" do it "returns an error" do - post :create, params.merge(format: :json) + post :create, params: params, format: :json expect(response).to have_gitlab_http_status(422) end @@ -30,7 +29,7 @@ shared_examples 'handle uploads' do context 'with valid image' do before do - post :create, params.merge(file: jpg, format: :json) + post :create, params: params.merge(file: jpg), format: :json end it 'returns a content with original filename, new link, and correct type.' do @@ -54,7 +53,7 @@ shared_examples 'handle uploads' do context 'with valid non-image file' do before do - post :create, params.merge(file: txt, format: :json) + post :create, params: params.merge(file: txt), format: :json end it 'returns a content with original filename, new link, and correct type.' do @@ -67,7 +66,7 @@ shared_examples 'handle uploads' do describe "GET #show" do let(:show_upload) do - get :show, params.merge(secret: secret, filename: "rails_sample.jpg") + get :show, params: params.merge(secret: secret, filename: "rails_sample.jpg") end before do diff --git a/spec/support/shared_examples/issuables_list_metadata_shared_examples.rb b/spec/support/shared_examples/issuables_list_metadata_shared_examples.rb index f4bc6f8efa5..90d67fd00fc 100644 --- a/spec/support/shared_examples/issuables_list_metadata_shared_examples.rb +++ b/spec/support/shared_examples/issuables_list_metadata_shared_examples.rb @@ -3,9 +3,9 @@ shared_examples 'issuables list meta-data' do |issuable_type, action = nil| def get_action(action, project) if action - get action, author_id: project.creator.id + get action, params: { author_id: project.creator.id } else - get :index, namespace_id: project.namespace, project_id: project + get :index, params: { namespace_id: project.namespace, project_id: project } end end @@ -51,9 +51,9 @@ shared_examples 'issuables list meta-data' do |issuable_type, action = nil| it "doesn't execute any queries with false conditions" do get_empty = if action - proc { get action, author_id: project.creator.id } + proc { get action, params: { author_id: project.creator.id } } else - proc { get :index, namespace_id: project2.namespace, project_id: project2 } + proc { get :index, params: { namespace_id: project2.namespace, project_id: project2 } } end expect(&get_empty).not_to make_queries_matching(/WHERE (?:1=0|0=1)/) diff --git a/spec/support/shared_examples/lib/gitlab/background_migration/backfill_project_repositories_examples.rb b/spec/support/shared_examples/lib/gitlab/background_migration/backfill_project_repositories_examples.rb new file mode 100644 index 00000000000..1f688c0f9d3 --- /dev/null +++ b/spec/support/shared_examples/lib/gitlab/background_migration/backfill_project_repositories_examples.rb @@ -0,0 +1,44 @@ +shared_examples 'backfill migration for project repositories' do |storage| + describe '#perform' do + let(:storage_versions) { storage == :legacy ? [nil, 0] : [1, 2] } + let(:storage_version) { storage_versions.first } + let(:namespaces) { table(:namespaces) } + let(:project_repositories) { table(:project_repositories) } + let(:projects) { table(:projects) } + let(:shards) { table(:shards) } + let(:group) { namespaces.create!(name: 'foo', path: 'foo') } + let(:shard) { shards.create!(name: 'default') } + + it "creates a project_repository row for projects on #{storage} storage that needs one" do + storage_versions.each_with_index do |storage_version, index| + projects.create!(name: "foo-#{index}", path: "foo-#{index}", namespace_id: group.id, storage_version: storage_version) + end + + expect { described_class.new.perform(1, projects.last.id) }.to change(project_repositories, :count).by(2) + end + + it "does nothing for projects on #{storage} storage that have already a project_repository row" do + projects.create!(id: 1, name: 'foo', path: 'foo', namespace_id: group.id, storage_version: storage_version) + project_repositories.create!(project_id: 1, disk_path: 'phony/foo/bar', shard_id: shard.id) + + expect { described_class.new.perform(1, projects.last.id) }.not_to change(project_repositories, :count) + end + + it "does nothing for projects on #{storage == :legacy ? 'hashed' : 'legacy'} storage" do + projects.create!(name: 'foo', path: 'foo', namespace_id: group.id, storage_version: storage == :legacy ? 1 : nil) + + expect { described_class.new.perform(1, projects.last.id) }.not_to change(project_repositories, :count) + end + + it 'inserts rows in a single query' do + projects.create!(name: 'foo', path: 'foo', namespace_id: group.id, storage_version: storage_version, repository_storage: shard.name) + + control_count = ActiveRecord::QueryRecorder.new { described_class.new.perform(1, projects.last.id) } + + projects.create!(name: 'bar', path: 'bar', namespace_id: group.id, storage_version: storage_version, repository_storage: shard.name) + projects.create!(name: 'zoo', path: 'zoo', namespace_id: group.id, storage_version: storage_version, repository_storage: shard.name) + + expect { described_class.new.perform(1, projects.last.id) }.not_to exceed_query_limit(control_count) + end + end +end diff --git a/spec/support/shared_examples/milestone_tabs_examples.rb b/spec/support/shared_examples/milestone_tabs_examples.rb index 70b499198bf..8b757586941 100644 --- a/spec/support/shared_examples/milestone_tabs_examples.rb +++ b/spec/support/shared_examples/milestone_tabs_examples.rb @@ -10,7 +10,7 @@ shared_examples 'milestone tabs' do { namespace_id: project.namespace.to_param, project_id: project, id: milestone.iid } end - get path, params.merge(extra_params) + get path, params: params.merge(extra_params) end describe '#merge_requests' do diff --git a/spec/support/shared_examples/notify_shared_examples.rb b/spec/support/shared_examples/notify_shared_examples.rb index 66536e80db2..a38354060cf 100644 --- a/spec/support/shared_examples/notify_shared_examples.rb +++ b/spec/support/shared_examples/notify_shared_examples.rb @@ -1,5 +1,5 @@ shared_context 'gitlab email notification' do - set(:project) { create(:project, :repository) } + set(:project) { create(:project, :repository, name: 'a-known-name') } set(:recipient) { create(:user, email: 'recipient@example.com') } let(:gitlab_sender_display_name) { Gitlab.config.gitlab.email_display_name } @@ -62,9 +62,11 @@ end shared_examples 'an email with X-GitLab headers containing project details' do it 'has X-GitLab-Project headers' do aggregate_failures do + full_path_as_domain = "#{project.name}.#{project.namespace.path}" is_expected.to have_header('X-GitLab-Project', /#{project.name}/) is_expected.to have_header('X-GitLab-Project-Id', /#{project.id}/) is_expected.to have_header('X-GitLab-Project-Path', /#{project.full_path}/) + is_expected.to have_header('List-Id', "#{project.full_path} <#{project.id}.#{full_path_as_domain}.#{Gitlab.config.gitlab.host}>") end end end diff --git a/spec/support/shared_examples/only_except_policy_examples.rb b/spec/support/shared_examples/only_except_policy_examples.rb deleted file mode 100644 index 35240af1d74..00000000000 --- a/spec/support/shared_examples/only_except_policy_examples.rb +++ /dev/null @@ -1,167 +0,0 @@ -# frozen_string_literal: true - -shared_examples 'correct only except policy' do - context 'when using simplified policy' do - describe 'validations' do - context 'when entry config value is valid' do - context 'when config is a branch or tag name' do - let(:config) { %w[master feature/branch] } - - describe '#valid?' do - it 'is valid' do - expect(entry).to be_valid - end - end - - describe '#value' do - it 'returns refs hash' do - expect(entry.value).to eq(refs: config) - end - end - end - - context 'when config is a regexp' do - let(:config) { ['/^issue-.*$/'] } - - describe '#valid?' do - it 'is valid' do - expect(entry).to be_valid - end - end - end - - context 'when config is a special keyword' do - let(:config) { %w[tags triggers branches] } - - describe '#valid?' do - it 'is valid' do - expect(entry).to be_valid - end - end - end - end - - context 'when entry value is not valid' do - let(:config) { [1] } - - describe '#errors' do - it 'saves errors' do - expect(entry.errors) - .to include /policy config should be an array of strings or regexps/ - end - end - end - end - end - - context 'when using complex policy' do - context 'when specifying refs policy' do - let(:config) { { refs: ['master'] } } - - it 'is a correct configuraton' do - expect(entry).to be_valid - expect(entry.value).to eq(refs: %w[master]) - end - end - - context 'when specifying kubernetes policy' do - let(:config) { { kubernetes: 'active' } } - - it 'is a correct configuraton' do - expect(entry).to be_valid - expect(entry.value).to eq(kubernetes: 'active') - end - end - - context 'when specifying invalid kubernetes policy' do - let(:config) { { kubernetes: 'something' } } - - it 'reports an error about invalid policy' do - expect(entry.errors).to include /unknown value: something/ - end - end - - context 'when specifying valid variables expressions policy' do - let(:config) { { variables: ['$VAR == null'] } } - - it 'is a correct configuraton' do - expect(entry).to be_valid - expect(entry.value).to eq(config) - end - end - - context 'when specifying variables expressions in invalid format' do - let(:config) { { variables: '$MY_VAR' } } - - it 'reports an error about invalid format' do - expect(entry.errors).to include /should be an array of strings/ - end - end - - context 'when specifying invalid variables expressions statement' do - let(:config) { { variables: ['$MY_VAR =='] } } - - it 'reports an error about invalid statement' do - expect(entry.errors).to include /invalid expression syntax/ - end - end - - context 'when specifying invalid variables expressions token' do - let(:config) { { variables: ['$MY_VAR == 123'] } } - - it 'reports an error about invalid expression' do - expect(entry.errors).to include /invalid expression syntax/ - end - end - - context 'when using invalid variables expressions regexp' do - let(:config) { { variables: ['$MY_VAR =~ /some ( thing/'] } } - - it 'reports an error about invalid expression' do - expect(entry.errors).to include /invalid expression syntax/ - end - end - - context 'when specifying a valid changes policy' do - let(:config) { { changes: %w[some/* paths/**/*.rb] } } - - it 'is a correct configuraton' do - expect(entry).to be_valid - expect(entry.value).to eq(config) - end - end - - context 'when changes policy is invalid' do - let(:config) { { changes: [1, 2] } } - - it 'returns errors' do - expect(entry.errors).to include /changes should be an array of strings/ - end - end - - context 'when specifying unknown policy' do - let(:config) { { refs: ['master'], invalid: :something } } - - it 'returns error about invalid key' do - expect(entry.errors).to include /unknown keys: invalid/ - end - end - - context 'when policy is empty' do - let(:config) { {} } - - it 'is not a valid configuration' do - expect(entry.errors).to include /can't be blank/ - end - end - end - - context 'when policy strategy does not match' do - let(:config) { 'string strategy' } - - it 'returns information about errors' do - expect(entry.errors) - .to include /has to be either an array of conditions or a hash/ - end - end -end diff --git a/spec/support/shared_examples/policies/clusterable_shared_examples.rb b/spec/support/shared_examples/policies/clusterable_shared_examples.rb new file mode 100644 index 00000000000..d99f94c76c3 --- /dev/null +++ b/spec/support/shared_examples/policies/clusterable_shared_examples.rb @@ -0,0 +1,37 @@ +# frozen_string_literal: true + +require 'spec_helper' + +shared_examples 'clusterable policies' do + describe '#add_cluster?' do + let(:current_user) { create(:user) } + + subject { described_class.new(current_user, clusterable) } + + context 'with a developer' do + before do + clusterable.add_developer(current_user) + end + + it { expect_disallowed(:add_cluster) } + end + + context 'with a maintainer' do + before do + clusterable.add_maintainer(current_user) + end + + context 'with no clusters' do + it { expect_allowed(:add_cluster) } + end + + context 'with an existing cluster' do + before do + cluster + end + + it { expect_disallowed(:add_cluster) } + end + end + end +end diff --git a/spec/support/shared_examples/requests/api/custom_attributes_shared_examples.rb b/spec/support/shared_examples/requests/api/custom_attributes_shared_examples.rb index 9fc2fbef449..8a7fcf856a1 100644 --- a/spec/support/shared_examples/requests/api/custom_attributes_shared_examples.rb +++ b/spec/support/shared_examples/requests/api/custom_attributes_shared_examples.rb @@ -9,7 +9,7 @@ shared_examples 'custom attributes endpoints' do |attributable_name| context 'with an unauthorized user' do it 'does not filter by custom attributes' do - get api("/#{attributable_name}", user), custom_attributes: { foo: 'foo', bar: 'bar' } + get api("/#{attributable_name}", user), params: { custom_attributes: { foo: 'foo', bar: 'bar' } } expect(response).to have_gitlab_http_status(200) expect(json_response.size).to be 2 @@ -19,7 +19,7 @@ shared_examples 'custom attributes endpoints' do |attributable_name| context 'with an authorized user' do it 'filters by custom attributes' do - get api("/#{attributable_name}", admin), custom_attributes: { foo: 'foo', bar: 'bar' } + get api("/#{attributable_name}", admin), params: { custom_attributes: { foo: 'foo', bar: 'bar' } } expect(response).to have_gitlab_http_status(200) expect(json_response.size).to be 1 @@ -35,7 +35,7 @@ shared_examples 'custom attributes endpoints' do |attributable_name| context 'with an unauthorized user' do it 'does not include custom attributes' do - get api("/#{attributable_name}", user), with_custom_attributes: true + get api("/#{attributable_name}", user), params: { with_custom_attributes: true } expect(response).to have_gitlab_http_status(200) expect(json_response.size).to be 2 @@ -54,7 +54,7 @@ shared_examples 'custom attributes endpoints' do |attributable_name| end it 'includes custom attributes if requested' do - get api("/#{attributable_name}", admin), with_custom_attributes: true + get api("/#{attributable_name}", admin), params: { with_custom_attributes: true } expect(response).to have_gitlab_http_status(200) expect(json_response.size).to be 2 @@ -75,7 +75,7 @@ shared_examples 'custom attributes endpoints' do |attributable_name| describe "GET /#{attributable_name}/:id with custom attributes" do context 'with an unauthorized user' do it 'does not include custom attributes' do - get api("/#{attributable_name}/#{attributable.id}", user), with_custom_attributes: true + get api("/#{attributable_name}/#{attributable.id}", user), params: { with_custom_attributes: true } expect(response).to have_gitlab_http_status(200) expect(json_response).not_to include 'custom_attributes' @@ -91,7 +91,7 @@ shared_examples 'custom attributes endpoints' do |attributable_name| end it 'includes custom attributes if requested' do - get api("/#{attributable_name}/#{attributable.id}", admin), with_custom_attributes: true + get api("/#{attributable_name}/#{attributable.id}", admin), params: { with_custom_attributes: true } expect(response).to have_gitlab_http_status(200) expect(json_response['custom_attributes']).to contain_exactly( @@ -141,7 +141,7 @@ shared_examples 'custom attributes endpoints' do |attributable_name| describe "PUT /#{attributable_name}/:id/custom_attributes/:key" do context 'with an unauthorized user' do - subject { put api("/#{attributable_name}/#{attributable.id}/custom_attributes/foo", user), value: 'new' } + subject { put api("/#{attributable_name}/#{attributable.id}/custom_attributes/foo", user), params: { value: 'new' } } it_behaves_like 'an unauthorized API user' end @@ -149,7 +149,7 @@ shared_examples 'custom attributes endpoints' do |attributable_name| context 'with an authorized user' do it 'creates a new custom attribute' do expect do - put api("/#{attributable_name}/#{attributable.id}/custom_attributes/new", admin), value: 'new' + put api("/#{attributable_name}/#{attributable.id}/custom_attributes/new", admin), params: { value: 'new' } end.to change { attributable.custom_attributes.count }.by(1) expect(response).to have_gitlab_http_status(200) @@ -159,7 +159,7 @@ shared_examples 'custom attributes endpoints' do |attributable_name| it 'updates an existing custom attribute' do expect do - put api("/#{attributable_name}/#{attributable.id}/custom_attributes/foo", admin), value: 'new' + put api("/#{attributable_name}/#{attributable.id}/custom_attributes/foo", admin), params: { value: 'new' } end.not_to change { attributable.custom_attributes.count } expect(response).to have_gitlab_http_status(200) diff --git a/spec/support/shared_examples/requests/api/diff_discussions.rb b/spec/support/shared_examples/requests/api/diff_discussions.rb index 85a4bd8ca27..366c2955359 100644 --- a/spec/support/shared_examples/requests/api/diff_discussions.rb +++ b/spec/support/shared_examples/requests/api/diff_discussions.rb @@ -27,7 +27,8 @@ shared_examples 'diff discussions API' do |parent_type, noteable_type, id_name| it "creates a new diff note" do position = diff_note.position.to_h - post api("/#{parent_type}/#{parent.id}/#{noteable_type}/#{noteable[id_name]}/discussions", user), body: 'hi!', position: position + post api("/#{parent_type}/#{parent.id}/#{noteable_type}/#{noteable[id_name]}/discussions", user), + params: { body: 'hi!', position: position } expect(response).to have_gitlab_http_status(201) expect(json_response['notes'].first['body']).to eq('hi!') @@ -38,7 +39,8 @@ shared_examples 'diff discussions API' do |parent_type, noteable_type, id_name| it "returns a 400 bad request error when position is invalid" do position = diff_note.position.to_h.merge(new_line: '100000') - post api("/#{parent_type}/#{parent.id}/#{noteable_type}/#{noteable[id_name]}/discussions", user), body: 'hi!', position: position + post api("/#{parent_type}/#{parent.id}/#{noteable_type}/#{noteable[id_name]}/discussions", user), + params: { body: 'hi!', position: position } expect(response).to have_gitlab_http_status(400) end @@ -47,7 +49,7 @@ shared_examples 'diff discussions API' do |parent_type, noteable_type, id_name| describe "POST /#{parent_type}/:id/#{noteable_type}/:noteable_id/discussions/:discussion_id/notes" do it 'adds a new note to the diff discussion' do post api("/#{parent_type}/#{parent.id}/#{noteable_type}/#{noteable[id_name]}/"\ - "discussions/#{diff_note.discussion_id}/notes", user), body: 'hi!' + "discussions/#{diff_note.discussion_id}/notes", user), params: { body: 'hi!' } expect(response).to have_gitlab_http_status(201) expect(json_response['body']).to eq('hi!') diff --git a/spec/support/shared_examples/requests/api/discussions.rb b/spec/support/shared_examples/requests/api/discussions.rb index b6aeb30d69c..e44da4faa5a 100644 --- a/spec/support/shared_examples/requests/api/discussions.rb +++ b/spec/support/shared_examples/requests/api/discussions.rb @@ -42,7 +42,7 @@ shared_examples 'discussions API' do |parent_type, noteable_type, id_name| describe "POST /#{parent_type}/:id/#{noteable_type}/:noteable_id/discussions" do it "creates a new note" do - post api("/#{parent_type}/#{parent.id}/#{noteable_type}/#{noteable[id_name]}/discussions", user), body: 'hi!' + post api("/#{parent_type}/#{parent.id}/#{noteable_type}/#{noteable[id_name]}/discussions", user), params: { body: 'hi!' } expect(response).to have_gitlab_http_status(201) expect(json_response['notes'].first['body']).to eq('hi!') @@ -56,7 +56,7 @@ shared_examples 'discussions API' do |parent_type, noteable_type, id_name| end it "returns a 401 unauthorized error if user not authenticated" do - post api("/#{parent_type}/#{parent.id}/#{noteable_type}/#{noteable[id_name]}/discussions"), body: 'hi!' + post api("/#{parent_type}/#{parent.id}/#{noteable_type}/#{noteable[id_name]}/discussions"), params: { body: 'hi!' } expect(response).to have_gitlab_http_status(401) end @@ -65,7 +65,7 @@ shared_examples 'discussions API' do |parent_type, noteable_type, id_name| it 'accepts the creation date to be set' do creation_time = 2.weeks.ago post api("/#{parent_type}/#{parent.id}/#{noteable_type}/#{noteable[id_name]}/discussions", user), - body: 'hi!', created_at: creation_time + params: { body: 'hi!', created_at: creation_time } expect(response).to have_gitlab_http_status(201) expect(json_response['notes'].first['body']).to eq('hi!') @@ -81,7 +81,7 @@ shared_examples 'discussions API' do |parent_type, noteable_type, id_name| it 'responds with 404' do post api("/#{parent_type}/#{parent.id}/#{noteable_type}/#{noteable[id_name]}/discussions", private_user), - body: 'Foo' + params: { body: 'Foo' } expect(response).to have_gitlab_http_status(404) end @@ -91,7 +91,7 @@ shared_examples 'discussions API' do |parent_type, noteable_type, id_name| describe "POST /#{parent_type}/:id/#{noteable_type}/:noteable_id/discussions/:discussion_id/notes" do it 'adds a new note to the discussion' do post api("/#{parent_type}/#{parent.id}/#{noteable_type}/#{noteable[id_name]}/"\ - "discussions/#{note.discussion_id}/notes", user), body: 'Hello!' + "discussions/#{note.discussion_id}/notes", user), params: { body: 'Hello!' } expect(response).to have_gitlab_http_status(201) expect(json_response['body']).to eq('Hello!') @@ -109,7 +109,7 @@ shared_examples 'discussions API' do |parent_type, noteable_type, id_name| note.update_attribute(:type, nil) post api("/#{parent_type}/#{parent.id}/#{noteable_type}/#{noteable[id_name]}/"\ - "discussions/#{note.discussion_id}/notes", user), body: 'hi!' + "discussions/#{note.discussion_id}/notes", user), params: { body: 'hi!' } expect(response).to have_gitlab_http_status(400) end @@ -118,7 +118,7 @@ shared_examples 'discussions API' do |parent_type, noteable_type, id_name| describe "PUT /#{parent_type}/:id/#{noteable_type}/:noteable_id/discussions/:discussion_id/notes/:note_id" do it 'returns modified note' do put api("/#{parent_type}/#{parent.id}/#{noteable_type}/#{noteable[id_name]}/"\ - "discussions/#{note.discussion_id}/notes/#{note.id}", user), body: 'Hello!' + "discussions/#{note.discussion_id}/notes/#{note.id}", user), params: { body: 'Hello!' } expect(response).to have_gitlab_http_status(200) expect(json_response['body']).to eq('Hello!') @@ -127,7 +127,7 @@ shared_examples 'discussions API' do |parent_type, noteable_type, id_name| it 'returns a 404 error when note id not found' do put api("/#{parent_type}/#{parent.id}/#{noteable_type}/#{noteable[id_name]}/"\ "discussions/#{note.discussion_id}/notes/12345", user), - body: 'Hello!' + params: { body: 'Hello!' } expect(response).to have_gitlab_http_status(404) end diff --git a/spec/support/shared_examples/requests/api/merge_requests_list.rb b/spec/support/shared_examples/requests/api/merge_requests_list.rb index 92d4dd598d5..453f42251c8 100644 --- a/spec/support/shared_examples/requests/api/merge_requests_list.rb +++ b/spec/support/shared_examples/requests/api/merge_requests_list.rb @@ -123,7 +123,7 @@ shared_examples 'merge requests list' do end it 'returns an empty array if no issue matches milestone' do - get api(endpoint_path, user), milestone: '1.0.0' + get api(endpoint_path, user), params: { milestone: '1.0.0' } expect(response).to have_gitlab_http_status(200) expect(json_response).to be_an Array @@ -131,7 +131,7 @@ shared_examples 'merge requests list' do end it 'returns an empty array if milestone does not exist' do - get api(endpoint_path, user), milestone: 'foo' + get api(endpoint_path, user), params: { milestone: 'foo' } expect(response).to have_gitlab_http_status(200) expect(json_response).to be_an Array @@ -139,7 +139,7 @@ shared_examples 'merge requests list' do end it 'returns an array of merge requests in given milestone' do - get api(endpoint_path, user), milestone: '0.9' + get api(endpoint_path, user), params: { milestone: '0.9' } closed_issues = json_response.select { |mr| mr['id'] == merge_request_closed.id } expect(closed_issues.length).to eq(1) @@ -147,7 +147,7 @@ shared_examples 'merge requests list' do end it 'returns an array of merge requests matching state in milestone' do - get api(endpoint_path, user), milestone: '0.9', state: 'closed' + get api(endpoint_path, user), params: { milestone: '0.9', state: 'closed' } expect(response).to have_gitlab_http_status(200) expect(json_response).to be_an Array @@ -187,7 +187,7 @@ shared_examples 'merge requests list' do end it 'returns an array of merge requests with any label when filtering by any label' do - get api(endpoint_path, user), labels: IssuesFinder::FILTER_ANY + get api(endpoint_path, user), params: { labels: IssuesFinder::FILTER_ANY } expect_paginated_array_response expect(json_response.length).to eq(1) @@ -195,7 +195,7 @@ shared_examples 'merge requests list' do end it 'returns an array of merge requests without a label when filtering by no label' do - get api(endpoint_path, user), labels: IssuesFinder::FILTER_NONE + get api(endpoint_path, user), params: { labels: IssuesFinder::FILTER_NONE } response_ids = json_response.map { |merge_request| merge_request['id'] } @@ -286,7 +286,7 @@ shared_examples 'merge requests list' do context 'source_branch param' do it 'returns merge requests with the given source branch' do - get api(endpoint_path, user), source_branch: merge_request_closed.source_branch, state: 'all' + get api(endpoint_path, user), params: { source_branch: merge_request_closed.source_branch, state: 'all' } expect_response_contain_exactly(merge_request_closed, merge_request_merged, merge_request_locked) end @@ -294,7 +294,7 @@ shared_examples 'merge requests list' do context 'target_branch param' do it 'returns merge requests with the given target branch' do - get api(endpoint_path, user), target_branch: merge_request_closed.target_branch, state: 'all' + get api(endpoint_path, user), params: { target_branch: merge_request_closed.target_branch, state: 'all' } expect_response_contain_exactly(merge_request_closed, merge_request_merged, merge_request_locked) end diff --git a/spec/support/shared_examples/requests/api/notes.rb b/spec/support/shared_examples/requests/api/notes.rb index 0e20dfe0725..71499e85654 100644 --- a/spec/support/shared_examples/requests/api/notes.rb +++ b/spec/support/shared_examples/requests/api/notes.rb @@ -86,7 +86,7 @@ shared_examples 'noteable API' do |parent_type, noteable_type, id_name| describe "POST /#{parent_type}/:id/#{noteable_type}/:noteable_id/notes" do it "creates a new note" do - post api("/#{parent_type}/#{parent.id}/#{noteable_type}/#{noteable[id_name]}/notes", user), body: 'hi!' + post api("/#{parent_type}/#{parent.id}/#{noteable_type}/#{noteable[id_name]}/notes", user), params: { body: 'hi!' } expect(response).to have_gitlab_http_status(201) expect(json_response['body']).to eq('hi!') @@ -100,7 +100,7 @@ shared_examples 'noteable API' do |parent_type, noteable_type, id_name| end it "returns a 401 unauthorized error if user not authenticated" do - post api("/#{parent_type}/#{parent.id}/#{noteable_type}/#{noteable[id_name]}/notes"), body: 'hi!' + post api("/#{parent_type}/#{parent.id}/#{noteable_type}/#{noteable[id_name]}/notes"), params: { body: 'hi!' } expect(response).to have_gitlab_http_status(401) end @@ -108,7 +108,7 @@ shared_examples 'noteable API' do |parent_type, noteable_type, id_name| it "creates an activity event when a note is created" do expect(Event).to receive(:create!) - post api("/#{parent_type}/#{parent.id}/#{noteable_type}/#{noteable[id_name]}/notes", user), body: 'hi!' + post api("/#{parent_type}/#{parent.id}/#{noteable_type}/#{noteable[id_name]}/notes", user), params: { body: 'hi!' } end context 'setting created_at' do @@ -118,7 +118,7 @@ shared_examples 'noteable API' do |parent_type, noteable_type, id_name| context 'by an admin' do it 'sets the creation time on the new note' do admin = create(:admin) - post api("/#{parent_type}/#{parent.id}/#{noteable_type}/#{noteable[id_name]}/notes", admin), params + post api("/#{parent_type}/#{parent.id}/#{noteable_type}/#{noteable[id_name]}/notes", admin), params: params expect(response).to have_gitlab_http_status(201) expect(json_response['body']).to eq('hi!') @@ -131,7 +131,7 @@ shared_examples 'noteable API' do |parent_type, noteable_type, id_name| if parent_type == 'projects' context 'by a project owner' do it 'sets the creation time on the new note' do - post api("/#{parent_type}/#{parent.id}/#{noteable_type}/#{noteable[id_name]}/notes", user), params + post api("/#{parent_type}/#{parent.id}/#{noteable_type}/#{noteable[id_name]}/notes", user), params: params expect(response).to have_gitlab_http_status(201) expect(json_response['body']).to eq('hi!') @@ -149,7 +149,7 @@ shared_examples 'noteable API' do |parent_type, noteable_type, id_name| parent.update!(namespace: group) user2.refresh_authorized_projects - post api("/#{parent_type}/#{parent.id}/#{noteable_type}/#{noteable[id_name]}/notes", user2), params + post api("/#{parent_type}/#{parent.id}/#{noteable_type}/#{noteable[id_name]}/notes", user2), params: params expect(response).to have_gitlab_http_status(201) expect(json_response['body']).to eq('hi!') @@ -161,7 +161,7 @@ shared_examples 'noteable API' do |parent_type, noteable_type, id_name| elsif parent_type == 'groups' context 'by a group owner' do it 'sets the creation time on the new note' do - post api("/#{parent_type}/#{parent.id}/#{noteable_type}/#{noteable[id_name]}/notes", user), params + post api("/#{parent_type}/#{parent.id}/#{noteable_type}/#{noteable[id_name]}/notes", user), params: params expect(response).to have_gitlab_http_status(201) expect(json_response['body']).to eq('hi!') @@ -176,7 +176,7 @@ shared_examples 'noteable API' do |parent_type, noteable_type, id_name| it 'ignores the given creation time' do user2 = create(:user) parent.add_developer(user2) - post api("/#{parent_type}/#{parent.id}/#{noteable_type}/#{noteable[id_name]}/notes", user2), params + post api("/#{parent_type}/#{parent.id}/#{noteable_type}/#{noteable[id_name]}/notes", user2), params: params expect(response).to have_gitlab_http_status(201) expect(json_response['body']).to eq('hi!') @@ -190,7 +190,7 @@ shared_examples 'noteable API' do |parent_type, noteable_type, id_name| context 'when the user is posting an award emoji on a noteable created by someone else' do it 'creates a new note' do parent.add_developer(private_user) - post api("/#{parent_type}/#{parent.id}/#{noteable_type}/#{noteable[id_name]}/notes", private_user), body: ':+1:' + post api("/#{parent_type}/#{parent.id}/#{noteable_type}/#{noteable[id_name]}/notes", private_user), params: { body: ':+1:' } expect(response).to have_gitlab_http_status(201) expect(json_response['body']).to eq(':+1:') @@ -199,7 +199,7 @@ shared_examples 'noteable API' do |parent_type, noteable_type, id_name| context 'when the user is posting an award emoji on his/her own noteable' do it 'creates a new note' do - post api("/#{parent_type}/#{parent.id}/#{noteable_type}/#{noteable[id_name]}/notes", user), body: ':+1:' + post api("/#{parent_type}/#{parent.id}/#{noteable_type}/#{noteable[id_name]}/notes", user), params: { body: ':+1:' } expect(response).to have_gitlab_http_status(201) expect(json_response['body']).to eq(':+1:') @@ -213,7 +213,7 @@ shared_examples 'noteable API' do |parent_type, noteable_type, id_name| it 'responds with 404' do post api("/#{parent_type}/#{parent.id}/#{noteable_type}/#{noteable[id_name]}/notes", private_user), - body: 'Foo' + params: { body: 'Foo' } expect(response).to have_gitlab_http_status(404) end @@ -223,7 +223,7 @@ shared_examples 'noteable API' do |parent_type, noteable_type, id_name| describe "PUT /#{parent_type}/:id/#{noteable_type}/:noteable_id/notes/:note_id" do it 'returns modified note' do put api("/#{parent_type}/#{parent.id}/#{noteable_type}/#{noteable[id_name]}/"\ - "notes/#{note.id}", user), body: 'Hello!' + "notes/#{note.id}", user), params: { body: 'Hello!' } expect(response).to have_gitlab_http_status(200) expect(json_response['body']).to eq('Hello!') @@ -231,7 +231,7 @@ shared_examples 'noteable API' do |parent_type, noteable_type, id_name| it 'returns a 404 error when note id not found' do put api("/#{parent_type}/#{parent.id}/#{noteable_type}/#{noteable[id_name]}/notes/12345", user), - body: 'Hello!' + params: { body: 'Hello!' } expect(response).to have_gitlab_http_status(404) end diff --git a/spec/support/shared_examples/requests/api/resolvable_discussions.rb b/spec/support/shared_examples/requests/api/resolvable_discussions.rb index 408ad08cc48..7e2416b23f3 100644 --- a/spec/support/shared_examples/requests/api/resolvable_discussions.rb +++ b/spec/support/shared_examples/requests/api/resolvable_discussions.rb @@ -2,7 +2,7 @@ shared_examples 'resolvable discussions API' do |parent_type, noteable_type, id_ describe "PUT /#{parent_type}/:id/#{noteable_type}/:noteable_id/discussions/:discussion_id" do it "resolves discussion if resolved is true" do put api("/#{parent_type}/#{parent.id}/#{noteable_type}/#{noteable[id_name]}/"\ - "discussions/#{note.discussion_id}", user), resolved: true + "discussions/#{note.discussion_id}", user), params: { resolved: true } expect(response).to have_gitlab_http_status(200) expect(json_response['notes'].size).to eq(1) @@ -11,7 +11,7 @@ shared_examples 'resolvable discussions API' do |parent_type, noteable_type, id_ it "unresolves discussion if resolved is false" do put api("/#{parent_type}/#{parent.id}/#{noteable_type}/#{noteable[id_name]}/"\ - "discussions/#{note.discussion_id}", user), resolved: false + "discussions/#{note.discussion_id}", user), params: { resolved: false } expect(response).to have_gitlab_http_status(200) expect(json_response['notes'].size).to eq(1) @@ -27,14 +27,14 @@ shared_examples 'resolvable discussions API' do |parent_type, noteable_type, id_ it "returns a 401 unauthorized error if user is not authenticated" do put api("/#{parent_type}/#{parent.id}/#{noteable_type}/#{noteable[id_name]}/"\ - "discussions/#{note.discussion_id}"), resolved: true + "discussions/#{note.discussion_id}"), params: { resolved: true } expect(response).to have_gitlab_http_status(401) end it "returns a 403 error if user resolves discussion of someone else" do put api("/#{parent_type}/#{parent.id}/#{noteable_type}/#{noteable[id_name]}/"\ - "discussions/#{note.discussion_id}", private_user), resolved: true + "discussions/#{note.discussion_id}", private_user), params: { resolved: true } expect(response).to have_gitlab_http_status(403) end @@ -46,7 +46,7 @@ shared_examples 'resolvable discussions API' do |parent_type, noteable_type, id_ it 'responds with 404' do put api("/#{parent_type}/#{parent.id}/#{noteable_type}/#{noteable[id_name]}/"\ - "discussions/#{note.discussion_id}", private_user), resolved: true + "discussions/#{note.discussion_id}", private_user), params: { resolved: true } expect(response).to have_gitlab_http_status(404) end @@ -56,7 +56,7 @@ shared_examples 'resolvable discussions API' do |parent_type, noteable_type, id_ describe "PUT /#{parent_type}/:id/#{noteable_type}/:noteable_id/discussions/:discussion_id/notes/:note_id" do it 'returns resolved note when resolved parameter is true' do put api("/#{parent_type}/#{parent.id}/#{noteable_type}/#{noteable[id_name]}/"\ - "discussions/#{note.discussion_id}/notes/#{note.id}", user), resolved: true + "discussions/#{note.discussion_id}/notes/#{note.id}", user), params: { resolved: true } expect(response).to have_gitlab_http_status(200) expect(json_response['resolved']).to eq(true) @@ -65,7 +65,7 @@ shared_examples 'resolvable discussions API' do |parent_type, noteable_type, id_ it 'returns a 404 error when note id not found' do put api("/#{parent_type}/#{parent.id}/#{noteable_type}/#{noteable[id_name]}/"\ "discussions/#{note.discussion_id}/notes/12345", user), - body: 'Hello!' + params: { body: 'Hello!' } expect(response).to have_gitlab_http_status(404) end @@ -79,7 +79,7 @@ shared_examples 'resolvable discussions API' do |parent_type, noteable_type, id_ it "returns a 403 error if user resolves note of someone else" do put api("/#{parent_type}/#{parent.id}/#{noteable_type}/#{noteable[id_name]}/"\ - "discussions/#{note.discussion_id}/notes/#{note.id}", private_user), resolved: true + "discussions/#{note.discussion_id}/notes/#{note.id}", private_user), params: { resolved: true } expect(response).to have_gitlab_http_status(403) end diff --git a/spec/support/shared_examples/requests/api/status_shared_examples.rb b/spec/support/shared_examples/requests/api/status_shared_examples.rb index 0ed917e448a..ebfc5fed3bb 100644 --- a/spec/support/shared_examples/requests/api/status_shared_examples.rb +++ b/spec/support/shared_examples/requests/api/status_shared_examples.rb @@ -54,7 +54,7 @@ shared_examples_for '412 response' do context 'for a modified ressource' do before do - delete request, params, { 'HTTP_IF_UNMODIFIED_SINCE' => '1990-01-12T00:00:48-0600' } + delete request, params: params, headers: { 'HTTP_IF_UNMODIFIED_SINCE' => '1990-01-12T00:00:48-0600' } end it 'returns 412' do @@ -64,7 +64,7 @@ shared_examples_for '412 response' do context 'for an unmodified ressource' do before do - delete request, params, { 'HTTP_IF_UNMODIFIED_SINCE' => Time.now } + delete request, params: params, headers: { 'HTTP_IF_UNMODIFIED_SINCE' => Time.now } end it 'returns accepted' do diff --git a/spec/support/shared_examples/serializers/diff_file_entity_examples.rb b/spec/support/shared_examples/serializers/diff_file_entity_examples.rb index b8065886c42..1770308f789 100644 --- a/spec/support/shared_examples/serializers/diff_file_entity_examples.rb +++ b/spec/support/shared_examples/serializers/diff_file_entity_examples.rb @@ -32,7 +32,7 @@ shared_examples 'diff file entity' do it 'exposes correct attributes' do expect(subject).to include(:too_large, :added_lines, :removed_lines, :context_lines_path, :highlighted_diff_lines, - :parallel_diff_lines) + :parallel_diff_lines, :empty) end it 'includes viewer' do diff --git a/spec/support/shared_examples/update_invalid_issuable.rb b/spec/support/shared_examples/update_invalid_issuable.rb index 1490287681b..64568de424e 100644 --- a/spec/support/shared_examples/update_invalid_issuable.rb +++ b/spec/support/shared_examples/update_invalid_issuable.rb @@ -26,7 +26,7 @@ shared_examples 'update invalid issuable' do |klass| end it 'renders edit when format is html' do - put :update, params + put :update, params: params expect(response).to render_template(:edit) expect(assigns[:conflict]).to be_truthy @@ -35,7 +35,7 @@ shared_examples 'update invalid issuable' do |klass| it 'renders json error message when format is json' do params[:format] = "json" - put :update, params + put :update, params: params expect(response.status).to eq(409) expect(JSON.parse(response.body)).to have_key('errors') @@ -49,7 +49,7 @@ shared_examples 'update invalid issuable' do |klass| end it 'renders edit when merge request is invalid' do - put :update, params + put :update, params: params expect(response).to render_template(:edit) end diff --git a/spec/tasks/gitlab/backup_rake_spec.rb b/spec/tasks/gitlab/backup_rake_spec.rb index 8c4360d4cf0..3b8f7f5fe7d 100644 --- a/spec/tasks/gitlab/backup_rake_spec.rb +++ b/spec/tasks/gitlab/backup_rake_spec.rb @@ -74,6 +74,7 @@ describe 'gitlab:app namespace rake task' do it 'invokes restoration on match' do allow(YAML).to receive(:load_file) .and_return({ gitlab_version: gitlab_version }) + expect(Rake::Task['gitlab:db:drop_tables']).to receive(:invoke) expect(Rake::Task['gitlab:backup:db:restore']).to receive(:invoke) expect(Rake::Task['gitlab:backup:repo:restore']).to receive(:invoke) diff --git a/spec/tasks/gitlab/storage_rake_spec.rb b/spec/tasks/gitlab/storage_rake_spec.rb index 233076ad6fa..be902d7c679 100644 --- a/spec/tasks/gitlab/storage_rake_spec.rb +++ b/spec/tasks/gitlab/storage_rake_spec.rb @@ -46,6 +46,16 @@ describe 'rake gitlab:storage:*' do describe 'gitlab:storage:migrate_to_hashed' do let(:task) { 'gitlab:storage:migrate_to_hashed' } + context 'read-only database' do + it 'does nothing' do + expect(Gitlab::Database).to receive(:read_only?).and_return(true) + + expect(Project).not_to receive(:with_unmigrated_storage) + + expect { run_rake_task(task) }.to output(/This task requires database write access. Exiting./).to_stderr + end + end + context '0 legacy projects' do it 'does nothing' do expect(StorageMigratorWorker).not_to receive(:perform_async) @@ -92,7 +102,7 @@ describe 'rake gitlab:storage:*' do stub_env('ID_FROM', 99999) stub_env('ID_TO', 99999) - expect { run_rake_task(task) }.to output(/There are no projects requiring storage migration with ID=99999/).to_stdout + expect { run_rake_task(task) }.to output(/There are no projects requiring storage migration with ID=99999/).to_stderr end it 'displays a message when project exists but its already migrated' do @@ -100,7 +110,7 @@ describe 'rake gitlab:storage:*' do stub_env('ID_FROM', project.id) stub_env('ID_TO', project.id) - expect { run_rake_task(task) }.to output(/There are no projects requiring storage migration with ID=#{project.id}/).to_stdout + expect { run_rake_task(task) }.to output(/There are no projects requiring storage migration with ID=#{project.id}/).to_stderr end it 'enqueues migration when project can be found' do diff --git a/spec/uploaders/file_uploader_spec.rb b/spec/uploaders/file_uploader_spec.rb index c74e0bf1955..db9e5eb2ad6 100644 --- a/spec/uploaders/file_uploader_spec.rb +++ b/spec/uploaders/file_uploader_spec.rb @@ -201,7 +201,7 @@ describe FileUploader do end let!(:fog_file) do - fog_connection.directories.get('uploads').files.create( + fog_connection.directories.new(key: 'uploads').files.create( key: 'tmp/uploads/test/123123', body: 'content' ) diff --git a/spec/uploaders/object_storage_spec.rb b/spec/uploaders/object_storage_spec.rb index 7e673681c31..533e9d87ea6 100644 --- a/spec/uploaders/object_storage_spec.rb +++ b/spec/uploaders/object_storage_spec.rb @@ -716,7 +716,7 @@ describe ObjectStorage do end let!(:fog_file) do - fog_connection.directories.get('uploads').files.create( + fog_connection.directories.new(key: 'uploads').files.create( key: 'tmp/uploads/test/123123', body: 'content' ) diff --git a/spec/views/layouts/nav/sidebar/_project.html.haml_spec.rb b/spec/views/layouts/nav/sidebar/_project.html.haml_spec.rb index 98d4456b277..ec20c346234 100644 --- a/spec/views/layouts/nav/sidebar/_project.html.haml_spec.rb +++ b/spec/views/layouts/nav/sidebar/_project.html.haml_spec.rb @@ -49,4 +49,30 @@ describe 'layouts/nav/sidebar/_project' do expect(rendered).to have_css('.sidebar-top-level-items > li.active', text: 'Registry') end end + + describe 'releases entry' do + describe 'when releases feature flag is disabled' do + before do + stub_feature_flags(releases_page: false) + end + + it 'does not render releases link' do + render + + expect(rendered).not_to have_link('Releases', href: project_releases_path(project)) + end + end + + describe 'when releases feature flags is enabled' do + before do + stub_feature_flags(releases_page: true) + end + + it 'renders releases link' do + render + + expect(rendered).to have_link('Releases', href: project_releases_path(project)) + end + end + end end diff --git a/spec/views/notify/changed_milestone_email.html.haml_spec.rb b/spec/views/notify/changed_milestone_email.html.haml_spec.rb new file mode 100644 index 00000000000..194b58840a3 --- /dev/null +++ b/spec/views/notify/changed_milestone_email.html.haml_spec.rb @@ -0,0 +1,35 @@ +# frozen_string_literal: true + +require 'spec_helper' + +describe 'notify/changed_milestone_email.html.haml' do + let(:milestone) { create(:milestone, title: 'some-milestone') } + let(:milestone_link) { milestone_url(milestone) } + + before do + assign(:milestone, milestone) + assign(:milestone_url, milestone_link) + end + + context 'milestone without start and due dates' do + it 'renders without date range' do + render + + expect(rendered).to have_content('Milestone changed to some-milestone', exact: true) + expect(rendered).to have_link('some-milestone', href: milestone_link) + end + end + + context 'milestone with start and due dates' do + before do + milestone.update(start_date: '2018-01-01', due_date: '2018-12-31') + end + + it 'renders with date range' do + render + + expect(rendered).to have_content('Milestone changed to some-milestone (Jan 1, 2018–Dec 31, 2018)', exact: true) + expect(rendered).to have_link('some-milestone', href: milestone_link) + end + end +end diff --git a/spec/views/projects/merge_requests/show.html.haml_spec.rb b/spec/views/projects/merge_requests/show.html.haml_spec.rb index fa6c4ce4ac8..b0042be339c 100644 --- a/spec/views/projects/merge_requests/show.html.haml_spec.rb +++ b/spec/views/projects/merge_requests/show.html.haml_spec.rb @@ -32,6 +32,11 @@ describe 'projects/merge_requests/show.html.haml' do assign(:noteable, closed_merge_request) assign(:notes, []) assign(:pipelines, Ci::Pipeline.none) + assign( + :issuable_sidebar, + MergeRequestSerializer.new(current_user: user, project: project) + .represent(closed_merge_request, serializer: 'sidebar') + ) preload_view_requirements diff --git a/spec/workers/cluster_platform_configure_worker_spec.rb b/spec/workers/cluster_configure_worker_spec.rb index 0eead0ab13d..6918ee3d7d8 100644 --- a/spec/workers/cluster_platform_configure_worker_spec.rb +++ b/spec/workers/cluster_configure_worker_spec.rb @@ -2,7 +2,7 @@ require 'spec_helper' -describe ClusterPlatformConfigureWorker, '#perform' do +describe ClusterConfigureWorker, '#perform' do let(:worker) { described_class.new } context 'when group cluster' do diff --git a/spec/workers/cluster_provision_worker_spec.rb b/spec/workers/cluster_provision_worker_spec.rb index 0a2dfef36a4..da32f29fec0 100644 --- a/spec/workers/cluster_provision_worker_spec.rb +++ b/spec/workers/cluster_provision_worker_spec.rb @@ -23,7 +23,7 @@ describe ClusterProvisionWorker do end it 'configures kubernetes platform' do - expect(ClusterPlatformConfigureWorker).to receive(:perform_async).with(cluster.id) + expect(ClusterConfigureWorker).to receive(:perform_async).with(cluster.id) described_class.new.perform(cluster.id) end @@ -32,7 +32,7 @@ describe ClusterProvisionWorker do context 'when cluster does not exist' do it 'does not provision a cluster' do expect_any_instance_of(Clusters::Gcp::ProvisionService).not_to receive(:execute) - expect(ClusterPlatformConfigureWorker).not_to receive(:perform_async) + expect(ClusterConfigureWorker).not_to receive(:perform_async) described_class.new.perform(123) end diff --git a/spec/workers/mail_scheduler/notification_service_worker_spec.rb b/spec/workers/mail_scheduler/notification_service_worker_spec.rb index f725c8763a0..1033557ee88 100644 --- a/spec/workers/mail_scheduler/notification_service_worker_spec.rb +++ b/spec/workers/mail_scheduler/notification_service_worker_spec.rb @@ -17,10 +17,21 @@ describe MailScheduler::NotificationServiceWorker do end context 'when the arguments cannot be deserialized' do - it 'does nothing' do - expect(worker.notification_service).not_to receive(method) + context 'when the arguments are not deserializeable' do + it 'raises exception' do + expect(worker.notification_service).not_to receive(method) + expect { worker.perform(method, key.to_global_id.to_s.succ) }.to raise_exception(ArgumentError) + end + end + + context 'when the arguments are deserializeable' do + it 'does nothing' do + serialized_arguments = *serialize(key) + key.destroy! - worker.perform(method, key.to_global_id.to_s.succ) + expect(worker.notification_service).not_to receive(method) + expect { worker.perform(method, serialized_arguments) }.not_to raise_exception + end end end diff --git a/spec/workers/object_pool/destroy_worker_spec.rb b/spec/workers/object_pool/destroy_worker_spec.rb new file mode 100644 index 00000000000..ef74f0ba87c --- /dev/null +++ b/spec/workers/object_pool/destroy_worker_spec.rb @@ -0,0 +1,31 @@ +# frozen_string_literal: true + +describe ObjectPool::DestroyWorker do + describe '#perform' do + context 'when no pool is in the database' do + it "doesn't raise an error" do + expect do + described_class.new.perform(987654321) + end.not_to raise_error + end + end + + context 'when a pool is present' do + let(:pool) { create(:pool_repository, :obsolete) } + + subject { described_class.new } + + it 'requests Gitaly to remove the object pool' do + expect(Gitlab::GitalyClient).to receive(:call).with(pool.shard_name, :object_pool_service, :delete_object_pool, Object) + + subject.perform(pool.id) + end + + it 'destroys the pool' do + subject.perform(pool.id) + + expect(PoolRepository.find_by_id(pool.id)).to be_nil + end + end + end +end diff --git a/spec/workers/stuck_merge_jobs_worker_spec.rb b/spec/workers/stuck_merge_jobs_worker_spec.rb index c2c2a5f9121..5aaff27a6b2 100644 --- a/spec/workers/stuck_merge_jobs_worker_spec.rb +++ b/spec/workers/stuck_merge_jobs_worker_spec.rb @@ -38,7 +38,8 @@ describe StuckMergeJobsWorker do create(:merge_request, :locked, merge_jid: '123') create(:merge_request, :locked, merge_jid: '456') - expect(Rails).to receive_message_chain(:logger, :info).with('Updated state of locked merge jobs. JIDs: 123, 456') + expect(described_class).to receive_message_chain(:logger, :info) + .with('Updated state of locked merge jobs. JIDs: 123, 456') worker.perform end diff --git a/yarn.lock b/yarn.lock index b1273a9fb4a..90d3d6fb7cb 100644 --- a/yarn.lock +++ b/yarn.lock @@ -623,28 +623,28 @@ dependencies: bootstrap "4.1.3" -"@gitlab/eslint-config@^1.2.0": - version "1.2.0" - resolved "https://registry.yarnpkg.com/@gitlab/eslint-config/-/eslint-config-1.2.0.tgz#115568a70edabbc024f1bc13ba1ba499a9ba05a9" - integrity sha512-TnZO5T7JjLQjw30aIGtKIsAX4pRnSbqOir3Ji5zPwtCVWY53DnG6Lcesgy7WYdsnnkt3oQPXFTOZlkymUs2PsA== +"@gitlab/eslint-config@^1.4.0": + version "1.4.0" + resolved "https://registry.yarnpkg.com/@gitlab/eslint-config/-/eslint-config-1.4.0.tgz#2e59e55a7cd024e3a450d2a896060ec4d763a5dc" + integrity sha512-nkecTWRNS/KD9q5lHFSc3J6zO/g1/OV9DaKiay+0nLjnGO9jQVRArRIYpnzgbUz2p15jOMVToVafW0YbbHZkwg== dependencies: babel-eslint "^10.0.1" eslint-config-airbnb-base "^13.1.0" - eslint-config-prettier "^3.1.0" + eslint-config-prettier "^3.3.0" eslint-plugin-filenames "^1.3.2" eslint-plugin-import "^2.14.0" eslint-plugin-promise "^4.0.1" - eslint-plugin-vue "^5.0.0-beta.3" + eslint-plugin-vue "^5.0.0" -"@gitlab/svgs@^1.40.0": - version "1.41.0" - resolved "https://registry.yarnpkg.com/@gitlab/svgs/-/svgs-1.41.0.tgz#f80e3a0e259f3550af00685556ea925e471276d3" - integrity sha512-tKUXyqe54efWBsjQBUcvNF0AvqmE2NI2No3Bnix/gKDRImzIlcgIkM67Y8zoJv1D0w4CO87WcaG5GLpIFIT1Pg== +"@gitlab/svgs@^1.43.0": + version "1.43.0" + resolved "https://registry.yarnpkg.com/@gitlab/svgs/-/svgs-1.43.0.tgz#28dee2122d068cd3b925cd9884d97465ebaca12d" + integrity sha512-wuN3NITmyBV9bOXsFfAjtndFrjTlH6Kf3+6aqT5kHKKLe/B4w7uTU1L9H/cyR0wGD7HbOh584a05eDcuH04fPA== -"@gitlab/ui@^1.15.0": - version "1.15.0" - resolved "https://registry.yarnpkg.com/@gitlab/ui/-/ui-1.15.0.tgz#288e189cb99de354aeb4598f9ac8cced5f47e139" - integrity sha512-Aiv/WABr8lBVJk0eoanSoO07Lr5Nnvuq82IjDnNzcw9enB1DAKvlstC2r9iiMfg1pVgV/uLdDeRFqH9eI1X4Rg== +"@gitlab/ui@^1.18.0": + version "1.18.0" + resolved "https://registry.yarnpkg.com/@gitlab/ui/-/ui-1.18.0.tgz#5cc591b2c7958e59fa7b1b443d4235e0e8f956c9" + integrity sha512-JqmiRSGYmK0DbGBQJBpjeRrcgjK25rCqG6QW6/GPTVLtRjbPPZYGvVg5PyA6nJUGAnwFoeApUZVML6X3OpnV1Q== dependencies: babel-standalone "^6.26.0" bootstrap-vue "^2.0.0-rc.11" @@ -654,7 +654,7 @@ js-beautify "^1.8.8" lodash "^4.17.11" url-search-params-polyfill "^5.0.0" - vue "^2.5.16" + vue "^2.5.21" vue-loader "^15.4.2" "@sindresorhus/is@^0.7.0": @@ -900,6 +900,11 @@ resolved "https://registry.yarnpkg.com/@xtuc/long/-/long-4.2.1.tgz#5c85d662f76fa1d34575766c5dcd6615abcd30d8" integrity sha512-FZdkNBDqBRHKQ2MEbSC17xnPFOhZxeJ2YGSfr2BKf3sujG49Qe3bB+rGCwQfIaA7WHnGeGkSijX4FuBCdrzW/g== +"@yarnpkg/lockfile@^1.1.0": + version "1.1.0" + resolved "https://registry.yarnpkg.com/@yarnpkg/lockfile/-/lockfile-1.1.0.tgz#e77a97fbd345b76d83245edcd17d393b1b41fb31" + integrity sha512-GpSwvyXOcOOlV70vbnzjj4fW5xW/FdUF6nQEt1ENy7m4ZCczi1+/buVUPAqmGfqznsORNFzUMjctTIp8a9tuCQ== + abab@^2.0.0: version "2.0.0" resolved "https://registry.yarnpkg.com/abab/-/abab-2.0.0.tgz#aba0ab4c5eee2d4c79d3487d85450fb2376ebb0f" @@ -933,24 +938,22 @@ acorn-globals@^4.1.0: acorn "^6.0.1" acorn-walk "^6.0.1" -acorn-jsx@^4.1.1: - version "4.1.1" - resolved "https://registry.yarnpkg.com/acorn-jsx/-/acorn-jsx-4.1.1.tgz#e8e41e48ea2fe0c896740610ab6a4ffd8add225e" - integrity sha512-JY+iV6r+cO21KtntVvFkD+iqjtdpRUpGqKWgfkCdZq1R+kbreEl8EcdcJR4SmiIgsIQT33s6QzheQ9a275Q8xw== - dependencies: - acorn "^5.0.3" +acorn-jsx@^5.0.0: + version "5.0.1" + resolved "https://registry.yarnpkg.com/acorn-jsx/-/acorn-jsx-5.0.1.tgz#32a064fd925429216a09b141102bfdd185fae40e" + integrity sha512-HJ7CfNHrfJLlNTzIEUTj43LNWGkqpRLxm3YjAlcD0ACydk9XynzYsCBHxut+iqt+1aBXkx9UP/w/ZqMr13XIzg== acorn-walk@^6.0.1: version "6.1.1" resolved "https://registry.yarnpkg.com/acorn-walk/-/acorn-walk-6.1.1.tgz#d363b66f5fac5f018ff9c3a1e7b6f8e310cc3913" integrity sha512-OtUw6JUTgxA2QoqqmrmQ7F2NYqiBPi/L2jqHyFtllhOUvXYQXf0Z1CYUinIfyT4bTCGmrA7gX9FvHA81uzCoVw== -acorn@^5.0.0, acorn@^5.0.3, acorn@^5.5.3, acorn@^5.6.0, acorn@^5.6.2, acorn@^5.7.3: +acorn@^5.0.0, acorn@^5.5.3, acorn@^5.6.2, acorn@^5.7.3: version "5.7.3" resolved "https://registry.yarnpkg.com/acorn/-/acorn-5.7.3.tgz#67aa231bf8812974b85235a96771eb6bd07ea279" integrity sha512-T/zvzYRfbVojPWahDsE5evJdHb3oJoQfFbsrKM7w5Zcs++Tr257tia3BmMP8XYVjp1S9RZXQMh7gao96BlqZOw== -acorn@^6.0.1: +acorn@^6.0.1, acorn@^6.0.2: version "6.0.4" resolved "https://registry.yarnpkg.com/acorn/-/acorn-6.0.4.tgz#77377e7353b72ec5104550aa2d2097a2fd40b754" integrity sha512-VY4i5EKSKkofY2I+6QLTbTTN/UvEQPCo6eiwzzSaSWfpaDhOmStMCMod6wmuPciNq+XS0faCglFu2lHZpdHUtg== @@ -965,25 +968,15 @@ ajv-errors@^1.0.0: resolved "https://registry.yarnpkg.com/ajv-errors/-/ajv-errors-1.0.0.tgz#ecf021fa108fd17dfb5e6b383f2dd233e31ffc59" integrity sha1-7PAh+hCP0X37Xms4Py3SM+Mf/Fk= -ajv-keywords@^3.0.0, ajv-keywords@^3.1.0: +ajv-keywords@^3.1.0: version "3.2.0" resolved "https://registry.yarnpkg.com/ajv-keywords/-/ajv-keywords-3.2.0.tgz#e86b819c602cf8821ad637413698f1dec021847a" integrity sha1-6GuBnGAs+IIa1jdBNpjx3sAhhHo= -ajv@^6.0.1, ajv@^6.1.0, ajv@^6.5.3: - version "6.5.3" - resolved "https://registry.yarnpkg.com/ajv/-/ajv-6.5.3.tgz#71a569d189ecf4f4f321224fecb166f071dd90f9" - integrity sha512-LqZ9wY+fx3UMiiPd741yB2pj3hhil+hQc8taf4o2QGRFpWgZ2V5C8HA165DY9sS3fJwsk7uT7ZlFEyC3Ig3lLg== - dependencies: - fast-deep-equal "^2.0.1" - fast-json-stable-stringify "^2.0.0" - json-schema-traverse "^0.4.1" - uri-js "^4.2.2" - -ajv@^6.5.5: - version "6.5.5" - resolved "https://registry.yarnpkg.com/ajv/-/ajv-6.5.5.tgz#cf97cdade71c6399a92c6d6c4177381291b781a1" - integrity sha512-7q7gtRQDJSyuEHjuVgHoUa2VuemFiCMrfQc9Tc08XTAc4Zj/5U1buQJ0HU6i7fKjXU09SVgSmxa4sLvuvS8Iyg== +ajv@^6.1.0, ajv@^6.5.3, ajv@^6.5.5, ajv@^6.6.1: + version "6.6.1" + resolved "https://registry.yarnpkg.com/ajv/-/ajv-6.6.1.tgz#6360f5ed0d80f232cc2b294c362d5dc2e538dd61" + integrity sha512-ZoJjft5B+EJBjUyu9C9Hc0OZyPZSSlOF+plzouTrg6UlA8f+e/n8NIgBFG/9tppJtpPWfthHakK7juJdNDODww== dependencies: fast-deep-equal "^2.0.1" fast-json-stable-stringify "^2.0.0" @@ -1327,7 +1320,7 @@ asynckit@^0.4.0: resolved "https://registry.yarnpkg.com/asynckit/-/asynckit-0.4.0.tgz#c79ed97f7f34cb8f2ba1bc9790bcc366474b4b79" integrity sha1-x57Zf380y48robyXkLzDZkdLS3k= -atob@^2.0.0, atob@^2.1.1: +atob@^2.1.1: version "2.1.2" resolved "https://registry.yarnpkg.com/atob/-/atob-2.1.2.tgz#6d9517eb9e030d2436666651e86bd9f6f13533c9" integrity sha512-Wm6ukoaOGJi/73p/cl2GvLjTI5JM1k/O14isD73YML8StrH/7/lRFgmg8nICZgD3bZZvjwCGxtMOD3wWNAu8cg== @@ -2286,7 +2279,7 @@ combined-stream@^1.0.6, combined-stream@~1.0.6: dependencies: delayed-stream "~1.0.0" -commander@2, commander@^2.18.0, commander@^2.19.0: +commander@2, commander@^2.10.0, commander@^2.18.0, commander@^2.19.0: version "2.19.0" resolved "https://registry.yarnpkg.com/commander/-/commander-2.19.0.tgz#f6198aa84e5b83c46054b94ddedbfed5ee9ff12a" integrity sha512-6tvAOO+D6OENvRAh524Dh9jcfKTYDQAqvqezbCW82xj5X0pSrcpxtvRKHLG0yBY6SD7PSDrJaj+0AiOcKVd1Xg== @@ -2373,7 +2366,7 @@ concat-stream@^1.5.0: readable-stream "^2.2.2" typedarray "^0.0.6" -config-chain@^1.1.12, config-chain@~1.1.5: +config-chain@^1.1.12: version "1.1.12" resolved "https://registry.yarnpkg.com/config-chain/-/config-chain-1.1.12.tgz#0fde8d091200eb5e808caf25fe618c02f48e4efa" integrity sha512-a1eOIcu8+7lUInge4Rpf/n4Krkf3Dd9lqhljRzII1/Zno/kRtUWnznPO3jOKBmTEktkt3fkxisUcivoj0ebzoA== @@ -2970,6 +2963,13 @@ debug@^3.1.0, debug@^3.2.5: dependencies: ms "^2.1.1" +debug@^4.0.1, debug@^4.1.0: + version "4.1.0" + resolved "https://registry.yarnpkg.com/debug/-/debug-4.1.0.tgz#373687bffa678b38b1cd91f861b63850035ddc87" + integrity sha512-heNPJUJIqC+xB6ayLAMHaIrmN9HKa7aQO8MGqKpvCA+uJYVcvR6l5kgdrhRuwPFHU7P5/A1w0BjByPHwpfTDKg== + dependencies: + ms "^2.1.1" + debug@~3.1.0: version "3.1.0" resolved "https://registry.yarnpkg.com/debug/-/debug-3.1.0.tgz#5bb5a0672628b64149566ba16819e61518c67261" @@ -3323,7 +3323,7 @@ editions@^1.3.3: resolved "https://registry.yarnpkg.com/editions/-/editions-1.3.4.tgz#3662cb592347c3168eb8e498a0ff73271d67f50b" integrity sha512-gzao+mxnYDzIysXKMQi/+M1mjy/rjestjg6OPoYTtI+3Izp23oiGZitsl9lPDPiTGXbcSIk1iJWhliSaglxnUg== -editorconfig@^0.15.0, editorconfig@^0.15.2: +editorconfig@^0.15.2: version "0.15.2" resolved "https://registry.yarnpkg.com/editorconfig/-/editorconfig-0.15.2.tgz#047be983abb9ab3c2eefe5199cb2b7c5689f0702" integrity sha512-GWjSI19PVJAM9IZRGOS+YKI8LN+/sjkSjNyvxL5ucqP9/IqtYNXBaQ/6c/hkPNYQHyOHra2KoXZI/JVpuqwmcQ== @@ -3547,10 +3547,10 @@ eslint-config-airbnb-base@^13.1.0: object.assign "^4.1.0" object.entries "^1.0.4" -eslint-config-prettier@^3.1.0: - version "3.1.0" - resolved "https://registry.yarnpkg.com/eslint-config-prettier/-/eslint-config-prettier-3.1.0.tgz#2c26d2cdcfa3a05f0642cd7e6e4ef3316cdabfa2" - integrity sha512-QYGfmzuc4q4J6XIhlp8vRKdI/fI0tQfQPy1dME3UOLprE+v4ssH/3W9LM2Q7h5qBcy5m0ehCrBDU2YF8q6OY8w== +eslint-config-prettier@^3.3.0: + version "3.3.0" + resolved "https://registry.yarnpkg.com/eslint-config-prettier/-/eslint-config-prettier-3.3.0.tgz#41afc8d3b852e757f06274ed6c44ca16f939a57d" + integrity sha512-Bc3bh5bAcKNvs3HOpSi6EfGA2IIp7EzWcg2tS4vP7stnXu/J1opihHDM7jI9JCIckyIDTgZLSWn7J3HY0j2JfA== dependencies: get-stdin "^6.0.0" @@ -3605,12 +3605,12 @@ eslint-plugin-filenames@^1.3.2: lodash.snakecase "4.1.1" lodash.upperfirst "4.3.1" -eslint-plugin-html@4.0.5: - version "4.0.5" - resolved "https://registry.yarnpkg.com/eslint-plugin-html/-/eslint-plugin-html-4.0.5.tgz#e8ec7e16485124460f3bff312016feb0a54d9659" - integrity sha512-yULqYldzhYXTwZEaJXM30HhfgJdtTzuVH3LeoANybESHZ5+2ztLD72BsB2wR124/kk/PvQqZofDFSdNIk+kykw== +eslint-plugin-html@5.0.0: + version "5.0.0" + resolved "https://registry.yarnpkg.com/eslint-plugin-html/-/eslint-plugin-html-5.0.0.tgz#396e30a60dedee0122fe08f11d13c5ab22f20d32" + integrity sha512-f7p/7YQdgQUFVAX3nB4dnMQbrDeTalcA01PDhuvTLk0ZadCwM4Pb+639SRuqEf1zMkIxckLY+ckCr0hVP5zl6A== dependencies: - htmlparser2 "^3.8.2" + htmlparser2 "^3.10.0" eslint-plugin-import@^2.14.0: version "2.14.0" @@ -3643,12 +3643,12 @@ eslint-plugin-promise@^4.0.1: resolved "https://registry.yarnpkg.com/eslint-plugin-promise/-/eslint-plugin-promise-4.0.1.tgz#2d074b653f35a23d1ba89d8e976a985117d1c6a2" integrity sha512-Si16O0+Hqz1gDHsys6RtFRrW7cCTB6P7p3OJmKp3Y3dxpQE2qwOA7d3xnV+0mBmrPoi0RBnxlCKvqu70te6wjg== -eslint-plugin-vue@^5.0.0-beta.3: - version "5.0.0-beta.3" - resolved "https://registry.yarnpkg.com/eslint-plugin-vue/-/eslint-plugin-vue-5.0.0-beta.3.tgz#f3fa9f109b76e20fc1e45a71ce7c6d567118924e" - integrity sha512-EOQo3ax4CIM6Itcl522p4cGlSBgR/KZBJo2Xc29PWknbYH/DRZorGutF8NATUpbZ4HYOG+Gcyd1nL08iyYF3Tg== +eslint-plugin-vue@^5.0.0: + version "5.0.0" + resolved "https://registry.yarnpkg.com/eslint-plugin-vue/-/eslint-plugin-vue-5.0.0.tgz#4a2cc1c0e71ea45e1bd9c1a60f925bfe68bb5710" + integrity sha512-mSv2Ebz3RaPP+XJO/mu7F+SdR9lrMyGISSExnarLFqqf3pF5wTmwWNrhHW1o9zKzKI811UVTIIkWJJvgO6SsUQ== dependencies: - vue-eslint-parser "^3.2.1" + vue-eslint-parser "^4.0.2" eslint-restricted-globals@^0.1.1: version "0.1.1" @@ -3681,16 +3681,16 @@ eslint-visitor-keys@^1.0.0: resolved "https://registry.yarnpkg.com/eslint-visitor-keys/-/eslint-visitor-keys-1.0.0.tgz#3f3180fb2e291017716acb4c9d6d5b5c34a6a81d" integrity sha512-qzm/XxIbxm/FHyH341ZrbnMUpe+5Bocte9xkmFMzPMjRaZMcXww+MpBptFvtU+79L362nqiLhekCxCxDPaUMBQ== -eslint@~5.6.0: - version "5.6.0" - resolved "https://registry.yarnpkg.com/eslint/-/eslint-5.6.0.tgz#b6f7806041af01f71b3f1895cbb20971ea4b6223" - integrity sha512-/eVYs9VVVboX286mBK7bbKnO1yamUy2UCRjiY6MryhQL2PaaXCExsCQ2aO83OeYRhU2eCU/FMFP+tVMoOrzNrA== +eslint@~5.9.0: + version "5.9.0" + resolved "https://registry.yarnpkg.com/eslint/-/eslint-5.9.0.tgz#b234b6d15ef84b5849c6de2af43195a2d59d408e" + integrity sha512-g4KWpPdqN0nth+goDNICNXGfJF7nNnepthp46CAlJoJtC5K/cLu3NgCM3AHu1CkJ5Hzt9V0Y0PBAO6Ay/gGb+w== dependencies: "@babel/code-frame" "^7.0.0" ajv "^6.5.3" chalk "^2.1.0" cross-spawn "^6.0.5" - debug "^3.1.0" + debug "^4.0.1" doctrine "^2.1.0" eslint-scope "^4.0.0" eslint-utils "^1.3.1" @@ -3717,21 +3717,22 @@ eslint@~5.6.0: path-is-inside "^1.0.2" pluralize "^7.0.0" progress "^2.0.0" - regexpp "^2.0.0" + regexpp "^2.0.1" require-uncached "^1.0.3" semver "^5.5.1" strip-ansi "^4.0.0" strip-json-comments "^2.0.1" - table "^4.0.3" + table "^5.0.2" text-table "^0.2.0" -espree@^4.0.0: - version "4.0.0" - resolved "https://registry.yarnpkg.com/espree/-/espree-4.0.0.tgz#253998f20a0f82db5d866385799d912a83a36634" - integrity sha512-kapdTCt1bjmspxStVKX6huolXVV5ZfyZguY1lcfhVVZstce3bqxH9mcLzNn3/mlgW6wQ732+0fuG9v7h0ZQoKg== +espree@^4.0.0, espree@^4.1.0: + version "4.1.0" + resolved "https://registry.yarnpkg.com/espree/-/espree-4.1.0.tgz#728d5451e0fd156c04384a7ad89ed51ff54eb25f" + integrity sha512-I5BycZW6FCVIub93TeVY1s7vjhP9CY6cXCznIRfiig7nRviKZYdRnj/sHEWC6A7WE9RDWOFq9+7OsWSYz8qv2w== dependencies: - acorn "^5.6.0" - acorn-jsx "^4.1.1" + acorn "^6.0.2" + acorn-jsx "^5.0.0" + eslint-visitor-keys "^1.0.0" esprima@2.7.x, esprima@^2.7.1: version "2.7.3" @@ -4029,16 +4030,11 @@ extract-from-css@^0.4.4: dependencies: css "^2.1.0" -extsprintf@1.3.0: +extsprintf@1.3.0, extsprintf@^1.2.0: version "1.3.0" resolved "https://registry.yarnpkg.com/extsprintf/-/extsprintf-1.3.0.tgz#96918440e3041a7a414f8c52e3c574eb3c3e1e05" integrity sha1-lpGEQOMEGnpBT4xS48V06zw+HgU= -extsprintf@^1.2.0: - version "1.4.0" - resolved "https://registry.yarnpkg.com/extsprintf/-/extsprintf-1.4.0.tgz#e2689f8f356fad62cca65a3a91c5df5f9551692f" - integrity sha1-4mifjzVvrWLMplo6kcXfX5VRaS8= - fast-deep-equal@^2.0.1: version "2.0.1" resolved "https://registry.yarnpkg.com/fast-deep-equal/-/fast-deep-equal-2.0.1.tgz#7b05218ddf9667bf7f370bf7fdb2cb15fdd0aa49" @@ -4814,17 +4810,17 @@ html-entities@^1.2.0: resolved "https://registry.yarnpkg.com/html-entities/-/html-entities-1.2.0.tgz#41948caf85ce82fed36e4e6a0ed371a6664379e2" integrity sha1-QZSMr4XOgv7Tbk5qDtNxpmZDeeI= -htmlparser2@^3.8.2, htmlparser2@^3.9.0: - version "3.9.2" - resolved "https://registry.yarnpkg.com/htmlparser2/-/htmlparser2-3.9.2.tgz#1bdf87acca0f3f9e53fa4fcceb0f4b4cbb00b338" - integrity sha1-G9+HrMoPP55T+k/M6w9LTLsAszg= +htmlparser2@^3.10.0, htmlparser2@^3.9.0: + version "3.10.0" + resolved "https://registry.yarnpkg.com/htmlparser2/-/htmlparser2-3.10.0.tgz#5f5e422dcf6119c0d983ed36260ce9ded0bee464" + integrity sha512-J1nEUGv+MkXS0weHNWVKJJ+UrLfePxRWpN3C9bEi9fLxL2+ggW94DQvgYVXsaT30PGwYRIZKNZXuyMhp3Di4bQ== dependencies: domelementtype "^1.3.0" domhandler "^2.3.0" domutils "^1.5.1" entities "^1.1.1" inherits "^2.0.1" - readable-stream "^2.0.2" + readable-stream "^3.0.6" http-cache-semantics@3.8.1: version "3.8.1" @@ -4878,10 +4874,10 @@ https-browserify@^1.0.0: resolved "https://registry.yarnpkg.com/https-browserify/-/https-browserify-1.0.0.tgz#ec06c10e0a34c0f2faf199f7fd7fc78fffd03c73" integrity sha1-7AbBDgo0wPL68Zn3/X/Hj//QPHM= -iconv-lite@0.4, iconv-lite@^0.4.17, iconv-lite@^0.4.22, iconv-lite@^0.4.4, iconv-lite@~0.4.13: - version "0.4.23" - resolved "https://registry.yarnpkg.com/iconv-lite/-/iconv-lite-0.4.23.tgz#297871f63be507adcfbfca715d0cd0eed84e9a63" - integrity sha512-neyTUVFtahjf0mB3dZT77u+8O0QB89jFdnBkd5P1JgYPbPaia3gXXOVL2fq8VyU2gMMD7SaN7QukTB/pmXYvDA== +iconv-lite@0.4, iconv-lite@0.4.24, iconv-lite@^0.4.17, iconv-lite@^0.4.22, iconv-lite@^0.4.4, iconv-lite@~0.4.13: + version "0.4.24" + resolved "https://registry.yarnpkg.com/iconv-lite/-/iconv-lite-0.4.24.tgz#2022b4b25fbddc21d2f524974a474aafe733908b" + integrity sha512-v3MXnZAcvnywkTUEZomIActle7RXXeedOR31wwl7VlyoXO4Qi9arvSenNQWne1TcRwhCL1HwLI21bEqdpj8/rA== dependencies: safer-buffer ">= 2.1.2 < 3" @@ -4890,13 +4886,6 @@ iconv-lite@0.4.19: resolved "https://registry.yarnpkg.com/iconv-lite/-/iconv-lite-0.4.19.tgz#f7468f60135f5e5dad3399c0a81be9a1603a082b" integrity sha512-oTZqweIP51xaGPI4uPa56/Pri/480R+mo7SeU+YETByQNhDG55ycFyNLIgta9vXhILrxXDmF7ZGhqZIcuN0gJQ== -iconv-lite@0.4.24: - version "0.4.24" - resolved "https://registry.yarnpkg.com/iconv-lite/-/iconv-lite-0.4.24.tgz#2022b4b25fbddc21d2f524974a474aafe733908b" - integrity sha512-v3MXnZAcvnywkTUEZomIActle7RXXeedOR31wwl7VlyoXO4Qi9arvSenNQWne1TcRwhCL1HwLI21bEqdpj8/rA== - dependencies: - safer-buffer ">= 2.1.2 < 3" - icss-replace-symbols@^1.1.0: version "1.1.0" resolved "https://registry.yarnpkg.com/icss-replace-symbols/-/icss-replace-symbols-1.1.0.tgz#06ea6f83679a7749e386cfe1fe812ae5db223ded" @@ -6015,7 +6004,7 @@ jquery.waitforimages@^2.2.0: resolved "https://registry.yarnpkg.com/jquery/-/jquery-3.3.1.tgz#958ce29e81c9790f31be7792df5d4d95fc57fbca" integrity sha512-Ubldcmxp5np52/ENotGxlLe6aGMvmF4R8S6tZjsP6Knsaxd/xp3Zrh50cG93lR6nPXyUFwzN3ZSOQI0wRJNdGg== -js-beautify@^1.6.14: +js-beautify@^1.6.14, js-beautify@^1.8.8: version "1.8.9" resolved "https://registry.yarnpkg.com/js-beautify/-/js-beautify-1.8.9.tgz#08e3c05ead3ecfbd4f512c3895b1cda76c87d523" integrity sha512-MwPmLywK9RSX0SPsUJjN7i+RQY9w/yC17Lbrq9ViEefpLRgqAR2BgrMN2AbifkUuhDV8tRauLhLda/9+bE0YQA== @@ -6026,16 +6015,6 @@ js-beautify@^1.6.14: mkdirp "~0.5.0" nopt "~4.0.1" -js-beautify@^1.8.8: - version "1.8.8" - resolved "https://registry.yarnpkg.com/js-beautify/-/js-beautify-1.8.8.tgz#1eb175b73a3571a5f1ed8d98e7cf2b05bfa98471" - integrity sha512-qVNq7ZZ7ZbLdzorvSlRDadS0Rh5oyItaE95v6I4wbbuSiijxn7SnnsV6dvKlcXuO2jX7lK8tn9fBulx34K/Ejg== - dependencies: - config-chain "~1.1.5" - editorconfig "^0.15.0" - mkdirp "~0.5.0" - nopt "~4.0.1" - js-cookie@^2.1.3: version "2.1.3" resolved "https://registry.yarnpkg.com/js-cookie/-/js-cookie-2.1.3.tgz#48071625217ac9ecfab8c343a13d42ec09ff0526" @@ -6789,7 +6768,7 @@ minimalistic-crypto-utils@^1.0.0, minimalistic-crypto-utils@^1.0.1: dependencies: brace-expansion "^1.1.7" -minimist@0.0.8: +minimist@0.0.8, minimist@~0.0.1: version "0.0.8" resolved "http://registry.npmjs.org/minimist/-/minimist-0.0.8.tgz#857fcabfc3397d2625b8228262e86aa7a011b05d" integrity sha1-hX/Kv8M5fSYluCKCYuhqp6ARsF0= @@ -6799,11 +6778,6 @@ minimist@1.2.0, minimist@^1.1.1, minimist@^1.2.0: resolved "https://registry.yarnpkg.com/minimist/-/minimist-1.2.0.tgz#a35008b20f41383eec1fb914f4cd5df79a264284" integrity sha1-o1AIsg9BOD7sH7kU9M1d95omQoQ= -minimist@~0.0.1: - version "0.0.10" - resolved "http://registry.npmjs.org/minimist/-/minimist-0.0.10.tgz#de3f98543dbf96082be48ad1a0c7cda836301dcf" - integrity sha1-3j+YVD2/lggr5IrRoMfNqDYwHc8= - minipass@^2.2.1, minipass@^2.3.3: version "2.3.3" resolved "https://registry.yarnpkg.com/minipass/-/minipass-2.3.3.tgz#a7dcc8b7b833f5d368759cce544dccb55f50f233" @@ -7900,7 +7874,7 @@ pseudomap@^1.0.2: resolved "https://registry.yarnpkg.com/pseudomap/-/pseudomap-1.0.2.tgz#f052a28da70e618917ef0a8ac34c1ae5a68286b3" integrity sha1-8FKijacOYYkX7wqKw0wa5aaChrM= -psl@^1.1.24, psl@^1.1.28: +psl@^1.1.24: version "1.1.29" resolved "https://registry.yarnpkg.com/psl/-/psl-1.1.29.tgz#60f580d360170bb722a797cc704411e6da850c67" integrity sha512-AeUmQ0oLN02flVHXWh9sSJF7mcdFq0ppid/JkErufc3hGIV/AMa8Fo9VgDo/cT2jFdOWoFvHp90qqBH54W+gjQ== @@ -7948,17 +7922,17 @@ pumpify@^1.3.3: inherits "^2.0.3" pump "^2.0.0" -punycode@1.3.2, punycode@^1.2.4: +punycode@1.3.2: version "1.3.2" resolved "https://registry.yarnpkg.com/punycode/-/punycode-1.3.2.tgz#9653a036fb7c1ee42342f2325cceefea3926c48d" integrity sha1-llOgNvt8HuQjQvIyXM7v6jkmxI0= -punycode@^1.4.1: +punycode@^1.2.4, punycode@^1.4.1: version "1.4.1" resolved "https://registry.yarnpkg.com/punycode/-/punycode-1.4.1.tgz#c0d5a63b2718800ad8e1eb0fa5269c84dd41845e" integrity sha1-wNWmOycYgArY4esPpSachN1BhF4= -punycode@^2.1.0, punycode@^2.1.1: +punycode@^2.1.0: version "2.1.1" resolved "https://registry.yarnpkg.com/punycode/-/punycode-2.1.1.tgz#b58b010ac40c22c5657616c8d2c2c02c7bf479ec" integrity sha512-XRsRjdf+j5ml+y/6GKHPZbrF/8p2Yga0JPtdqTIY2Xe5ohJPD9saDJJLPvp9+NSBprVvevdXZybnj2cv8OEd0A== @@ -8132,6 +8106,15 @@ read-pkg@^3.0.0: string_decoder "~1.1.1" util-deprecate "~1.0.1" +readable-stream@^3.0.6: + version "3.0.6" + resolved "https://registry.yarnpkg.com/readable-stream/-/readable-stream-3.0.6.tgz#351302e4c68b5abd6a2ed55376a7f9a25be3057a" + integrity sha512-9E1oLoOWfhSXHGv6QlwXJim7uNzd9EVlWK+21tCU9Ju/kR0/p2AZYPz4qSchgO8PlLIH4FpZYfzwS+rEksZjIg== + dependencies: + inherits "^2.0.3" + string_decoder "^1.1.1" + util-deprecate "^1.0.1" + readable-stream@~2.0.6: version "2.0.6" resolved "https://registry.yarnpkg.com/readable-stream/-/readable-stream-2.0.6.tgz#8f90341e68a53ccc928788dacfcd11b36eb9b78e" @@ -8205,10 +8188,10 @@ regex-not@^1.0.0, regex-not@^1.0.2: extend-shallow "^3.0.2" safe-regex "^1.1.0" -regexpp@^2.0.0: - version "2.0.0" - resolved "https://registry.yarnpkg.com/regexpp/-/regexpp-2.0.0.tgz#b2a7534a85ca1b033bcf5ce9ff8e56d4e0755365" - integrity sha512-g2FAVtR8Uh8GO1Nv5wpxW7VFVwHcCEr4wyA8/MHiRkO8uHoR5ntAA8Uq3P1vvMTX/BeQiRVSpDGLd+Wn5HNOTA== +regexpp@^2.0.1: + version "2.0.1" + resolved "https://registry.yarnpkg.com/regexpp/-/regexpp-2.0.1.tgz#8d19d31cf632482b589049f8281f93dbcba4d07f" + integrity sha512-lv0M6+TkDVniA3aD1Eg0DVpfU/booSu7Eev3TDO/mZKHBfVjgCGTV4t4buppESEYDtkArYFOxTJWv6S5C+iaNw== regexpu-core@^1.0.0: version "1.0.0" @@ -8740,11 +8723,13 @@ slash@^1.0.0: resolved "https://registry.yarnpkg.com/slash/-/slash-1.0.0.tgz#c41f2f6c39fc16d1cd17ad4b5d896114ae470d55" integrity sha1-xB8vbDn8FtHNF61LXYlhFK5HDVU= -slice-ansi@1.0.0: - version "1.0.0" - resolved "https://registry.yarnpkg.com/slice-ansi/-/slice-ansi-1.0.0.tgz#044f1a49d8842ff307aad6b505ed178bd950134d" - integrity sha512-POqxBK6Lb3q6s047D/XsDVNPnF9Dl8JSaqe9h9lURl0OdNqy/ujDrOiIHtsqXMGbWWTIomRzAMaTyawAU//Reg== +slice-ansi@2.0.0: + version "2.0.0" + resolved "https://registry.yarnpkg.com/slice-ansi/-/slice-ansi-2.0.0.tgz#5373bdb8559b45676e8541c66916cdd6251612e7" + integrity sha512-4j2WTWjp3GsZ+AOagyzVbzp4vWGtZ0hEZ/gDY/uTvm6MTxUfTUIsnMIFb1bn8o0RuXiqUw15H1bue8f22Vw2oQ== dependencies: + ansi-styles "^3.2.0" + astral-regex "^1.0.0" is-fullwidth-code-point "^2.0.0" slugify@^1.3.1: @@ -8874,18 +8859,7 @@ source-list-map@^2.0.0: resolved "https://registry.yarnpkg.com/source-list-map/-/source-list-map-2.0.0.tgz#aaa47403f7b245a92fbc97ea08f250d6087ed085" integrity sha512-I2UmuJSRr/T8jisiROLU3A3ltr+swpniSmNPI4Ml3ZCX6tVnDsuZzK7F2hl5jTqbZBWCEKlj5HRQiPExXLgE8A== -source-map-resolve@^0.5.0: - version "0.5.1" - resolved "https://registry.yarnpkg.com/source-map-resolve/-/source-map-resolve-0.5.1.tgz#7ad0f593f2281598e854df80f19aae4b92d7a11a" - integrity sha512-0KW2wvzfxm8NCTb30z0LMNyPqWCdDGE2viwzUaucqJdkTRXtZiSY3I+2A6nVAjmdOy0I4gU8DwnVVGsk9jvP2A== - dependencies: - atob "^2.0.0" - decode-uri-component "^0.2.0" - resolve-url "^0.2.1" - source-map-url "^0.4.0" - urix "^0.1.0" - -source-map-resolve@^0.5.2: +source-map-resolve@^0.5.0, source-map-resolve@^0.5.2: version "0.5.2" resolved "https://registry.yarnpkg.com/source-map-resolve/-/source-map-resolve-0.5.2.tgz#72e2cc34095543e43b2c62b2c4c10d4a9054f259" integrity sha512-MjqsvNwyz1s0k81Goz/9vRBe9SZdB09Bdw+/zYyO+3CuPk6fouTaxscHkgtE8jKvf01kVfl8riHzERQ/kefaSA== @@ -9153,7 +9127,7 @@ string-width@^2.0.0, string-width@^2.1.0, string-width@^2.1.1: is-fullwidth-code-point "^2.0.0" strip-ansi "^4.0.0" -string_decoder@^1.0.0, string_decoder@~1.1.1: +string_decoder@^1.0.0, string_decoder@^1.1.1, string_decoder@~1.1.1: version "1.1.1" resolved "https://registry.yarnpkg.com/string_decoder/-/string_decoder-1.1.1.tgz#9cf1611ba62685d7030ae9e4ba34149c3af03fc8" integrity sha512-n/ShnvDi6FHbbVfviro+WojiFzv+s8MPMHBczVePfUpDJLwoLT0ht1l4YwBCbi8pJAveEEdnkHyPyTP/mzRfwg== @@ -9250,16 +9224,14 @@ symbol-tree@^3.2.2: resolved "https://registry.yarnpkg.com/symbol-tree/-/symbol-tree-3.2.2.tgz#ae27db38f660a7ae2e1c3b7d1bc290819b8519e6" integrity sha1-rifbOPZgp64uHDt9G8KQgZuFGeY= -table@^4.0.3: - version "4.0.3" - resolved "http://registry.npmjs.org/table/-/table-4.0.3.tgz#00b5e2b602f1794b9acaf9ca908a76386a7813bc" - integrity sha512-S7rnFITmBH1EnyKcvxBh1LjYeQMmnZtCXSEbHcH6S0NoKit24ZuFO/T1vDcLdYsLQkM188PVVhQmzKIuThNkKg== +table@^5.0.2: + version "5.1.1" + resolved "https://registry.yarnpkg.com/table/-/table-5.1.1.tgz#92030192f1b7b51b6eeab23ed416862e47b70837" + integrity sha512-NUjapYb/qd4PeFW03HnAuOJ7OMcBkJlqeClWxeNlQ0lXGSb52oZXGzkO0/I0ARegQ2eUT1g2VDJH0eUxDRcHmw== dependencies: - ajv "^6.0.1" - ajv-keywords "^3.0.0" - chalk "^2.1.0" - lodash "^4.17.4" - slice-ansi "1.0.0" + ajv "^6.6.1" + lodash "^4.17.11" + slice-ansi "2.0.0" string-width "^2.1.1" tapable@^0.1.8: @@ -9459,15 +9431,7 @@ touch@^3.1.0: dependencies: nopt "~1.0.10" -tough-cookie@>=2.3.3, tough-cookie@^2.3.4: - version "2.5.0" - resolved "https://registry.yarnpkg.com/tough-cookie/-/tough-cookie-2.5.0.tgz#cd9fb2a0aa1d5a12b473bd9fb96fa3dcff65ade2" - integrity sha512-nlLsUzgm1kfLXSXfRZMc1KLAugd4hqJHDTvc2hDIwS3mZAfMEuMbc03SujMF+GEcpaX/qboeycw6iO8JwVv2+g== - dependencies: - psl "^1.1.28" - punycode "^2.1.1" - -tough-cookie@~2.4.3: +tough-cookie@>=2.3.3, tough-cookie@^2.3.4, tough-cookie@~2.4.3: version "2.4.3" resolved "https://registry.yarnpkg.com/tough-cookie/-/tough-cookie-2.4.3.tgz#53f36da3f47783b0925afa06ff9f3b165280f781" integrity sha512-Q5srk/4vDM54WJsJio3XNn6K2sCG+CQ8G5Wz6bZhRZoAe/+TxjWB/GlFAnYEbkYVlON9FMk/fE3h2RLpPXo4lQ== @@ -9771,7 +9735,7 @@ useragent@2.2.1: lru-cache "2.2.x" tmp "0.0.x" -util-deprecate@~1.0.1: +util-deprecate@^1.0.1, util-deprecate@~1.0.1: version "1.0.2" resolved "https://registry.yarnpkg.com/util-deprecate/-/util-deprecate-1.0.2.tgz#450d4dc9fa70de732762fbd2d4a28981419a0ccf" integrity sha1-RQ1Nyfpw3nMnYvvS1KKJgUGaDM8= @@ -9853,17 +9817,17 @@ vue-apollo@^3.0.0-beta.25: chalk "^2.4.1" throttle-debounce "^2.0.0" -vue-eslint-parser@^3.2.1: - version "3.2.2" - resolved "https://registry.yarnpkg.com/vue-eslint-parser/-/vue-eslint-parser-3.2.2.tgz#47c971ee4c39b0ee7d7f5e154cb621beb22f7a34" - integrity sha512-dprI6ggKCTwV22r+i8dtUGquiOCn063xyDmb7BV/BjG5Oc/m5EoMNrWevpvTcrlGuFZmYVPs5fgsu8UIxmMKzg== +vue-eslint-parser@^4.0.2: + version "4.0.3" + resolved "https://registry.yarnpkg.com/vue-eslint-parser/-/vue-eslint-parser-4.0.3.tgz#80cf162e484387b2640371ad21ba1f86e0c10a61" + integrity sha512-AUeQsYdO6+7QXCems+WvGlrXd37PHv/zcRQSQdY1xdOMwdFAPEnMBsv7zPvk0TPGulXkK/5p/ITgrjiYB7k3ag== dependencies: - debug "^3.1.0" + debug "^4.1.0" eslint-scope "^4.0.0" eslint-visitor-keys "^1.0.0" - espree "^4.0.0" + espree "^4.1.0" esquery "^1.0.1" - lodash "^4.17.10" + lodash "^4.17.11" vue-functional-data-merge@^2.0.5: version "2.0.6" @@ -9940,11 +9904,16 @@ vue-virtual-scroll-list@^1.2.5: resolved "https://registry.yarnpkg.com/vue-virtual-scroll-list/-/vue-virtual-scroll-list-1.2.5.tgz#bcbd010f7cdb035eba8958ebf807c6214d9a167a" integrity sha1-vL0BD3zbA166iVjr+AfGIU2aFno= -vue@^2.5.16, vue@^2.5.17: +vue@^2.5.17: version "2.5.17" resolved "https://registry.yarnpkg.com/vue/-/vue-2.5.17.tgz#0f8789ad718be68ca1872629832ed533589c6ada" integrity sha512-mFbcWoDIJi0w0Za4emyLiW72Jae0yjANHbCVquMKijcavBGypqlF7zHRgMa5k4sesdv7hv2rB4JPdZfR+TPfhQ== +vue@^2.5.21: + version "2.5.21" + resolved "https://registry.yarnpkg.com/vue/-/vue-2.5.21.tgz#3d33dcd03bb813912ce894a8303ab553699c4a85" + integrity sha512-Aejvyyfhn0zjVeLvXd70h4hrE4zZDx1wfZqia6ekkobLmUZ+vNFQer53B4fu0EjWBSiqApxPejzkO1Znt3joxQ== + vuex@^3.0.1: version "3.0.1" resolved "https://registry.yarnpkg.com/vuex/-/vuex-3.0.1.tgz#e761352ebe0af537d4bb755a9b9dc4be3df7efd2" @@ -10170,14 +10139,7 @@ which-module@^2.0.0: resolved "https://registry.yarnpkg.com/which-module/-/which-module-2.0.0.tgz#d9ef07dce77b9902b8a3a8fa4b31c3e3f7e6e87a" integrity sha1-2e8H3Od7mQK4o6j6SzHD4/fm6Ho= -which@^1.1.1, which@^1.2.1, which@^1.2.9: - version "1.3.0" - resolved "https://registry.yarnpkg.com/which/-/which-1.3.0.tgz#ff04bdfc010ee547d780bec38e1ac1c2777d253a" - integrity sha512-xcJpopdamTuY5duC/KnTTNBraPK54YwpenP4lzxU8H91GudWpFv38u0CKjclE1Wi2EH2EDz5LRcHcKbCIzqGyg== - dependencies: - isexe "^2.0.0" - -which@^1.2.12, which@^1.3.0: +which@^1.1.1, which@^1.2.1, which@^1.2.12, which@^1.2.9, which@^1.3.0: version "1.3.1" resolved "https://registry.yarnpkg.com/which/-/which-1.3.1.tgz#a45043d54f5805316da8d62f9f50918d3da70b0a" integrity sha512-HxJdYWq1MTIQbJ3nw0cqssHoTNU267KlrDuGZ1WYlxDStUtKUhOaJmh112/TZmHxxUfuJqPXSOm7tDyas0OSIQ== @@ -10391,6 +10353,15 @@ yargs@^11.0.0: y18n "^3.2.1" yargs-parser "^9.0.2" +yarn-deduplicate@^1.0.5: + version "1.0.5" + resolved "https://registry.yarnpkg.com/yarn-deduplicate/-/yarn-deduplicate-1.0.5.tgz#e56016f1c29e77e323f401ea838f5e8c7cdbfd42" + integrity sha512-4nds6N7dxuXcfUZAVaSUVSlI4TvwEdMaZg/DRBf/KM3iFezNBdkhcTYptcwKaecAYAfVxx3g0Ex21kssSr8YsA== + dependencies: + "@yarnpkg/lockfile" "^1.1.0" + commander "^2.10.0" + semver "^5.3.0" + yeast@0.1.2: version "0.1.2" resolved "https://registry.yarnpkg.com/yeast/-/yeast-0.1.2.tgz#008e06d8094320c372dbc2f8ed76a0ca6c8ac419" |